요즘 며칠동안 공부중인 캔버스 프리드로우 버전입니다영..^^

1단계 : 그냥 캔버스에 간단한 마우스 이벤트를 통한 프리드로우입니다.

2단계 : 1단계를 해보니 할 때마다 다시 페인트를 해서 번쩍번쩍 거림..쓰레드 구현 필요(우리 수업도 빨리 쓰레드,Db,IO가 나가야 할텐데..벌써 한달이 지났엉..==;)

3단계 : 이걸 DB든 파일이든 직렬화 저장해서 정말 스마트폰에 있는 그림메모처럼 만드는 게 목표.

최종단계 : 이걸 그대로 안드로이드폰에 옮겨봄...ㅋ

 

암튼 현재 1단계 성공..

 

소스첨부하니 참고하실 분 보세요.


import java.awt.BasicStroke;
import java.awt.Canvas;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseMotionAdapter;
import java.util.ArrayList;

import javax.swing.JFrame;


public class DrawTest extends JFrame {
 MyCanvas canvas;
 ArrayList<Vortex> list;
 public DrawTest(){
  super("DrawTest");
  
  System.out.println("DrawTest 시작");
  
  list = new ArrayList<Vortex>();
  
  canvas = new MyCanvas();
  
  canvas.setBackground(Color.WHITE);
  
  canvas.addMouseListener(new MouseAdapter(){
   public void mousePressed(MouseEvent me){
    
    list.add(new Vortex(me.getX(),me.getY(),false));
    
    
   }   
  });
  canvas.addMouseMotionListener(new MouseMotionAdapter(){
   public void mouseDragged(MouseEvent me){

    list.add(new Vortex(me.getX(),me.getY(),true));

    canvas.repaint();

   }
  });
  this.add(canvas);
  
  this.setSize(300,300);
 
 }
 
 class Vortex{
  float x;
  float y;
  boolean isDraw;
  public Vortex(float x,float y,boolean isDraw){
   this.x = x;
   this.y = y;
   this.isDraw = isDraw;   
  }
 }
 class MyCanvas extends Canvas{
  
  public void paint(Graphics g){
   
   Graphics2D g2d = (Graphics2D)g;

         g2d.setStroke(new BasicStroke(2));    // 선 굵기 설정

   int size = list.size();
   
   for(int i=0;i<size;i++){
    
    Vortex tex = (Vortex)list.get(i);
    
    if(tex.isDraw){
     
     g.drawLine((int)list.get(i-1).x, (int)list.get(i-1).y,
       (int)list.get(i).x, (int)list.get(i).y);
    }
   }
   
  }
 }
 public static void main(String[] args) {
  // TODO Auto-generated method stub
  new DrawTest().setVisible(true);
 }

}

음..

 

 업캐스팅이 처음 들어 보는 단어라고 그러셨는데..

 

 상속을 알고 있다면 그리고 인터페이스 와 추상 클래스, 매서드 오버라이딩의 개념을

 알고 있다면 이미 업캐스팅을 사용하는 겁니다.

 

 아니 이미 업캐스팅은 문장에 만들어져 있습니다

 

 모든 자바는 Objdec 클래스에서 상속받습니다.

 명시적으

 

    class A {

 

 

} 라는게 있다면

 이미 묵시적으로

 

  class A extends Object 가 형성되는 거죠.

 

자 그럼 업태스팅은 무엇인지 알아보죠.

 

 대표적인 구문을 하나 보여드리겠습니다.

 

 

 class A{

 public void draw(){

  System.out.println(" A번입니다");

 }

 }

 

 class B extends A{

 public void draw(){

 System.out.println("B클래스입니다.");

}

}

 

class C extends B{

  public void draw(){

 System.out.println("C클래스입니다");

 }

  public void set(){

  System.out.pirnln("set매서드입니다");

}

 

 

실행부

 

 main(String[] args){

 A  a=new A();

 a.draw();

 

 // 이경우는  A클래스의 A 클래스입니다. 출력

 

 B b=new B();

 b.draw();

 

// B클래스입니다. 출력

 

 C c=new C();

 c.draw();

 c.set(); 

 

 //C클래스의 C클래스입니다. set 매서드입니다. 출력

 

 A ab=new C();

 c.draw();

 c.set();

 

 이경우는 C클래스입니다. 출력 c.set()는 에러.

 

 

 자 마지막을 보죠.

 A  ab=new C();

 라는 것을 만들었죠.

 A클래스는 C클래스로 상속이 되 있습니다.

 그런데 이 클래스를 풀이해보면

 A클래스 형을 가지는 ab는 C클래스의 메모리를 사용한다. 라는 뜻이됩니다.

 

 여기서 매서드 오버라이드 개념이 사용됐죠. draw()는 오버라이드 됐습니다.

 오버라이드가 되면 재정의를 통해 아들의 매서드를 사용합니다. 이미 알고 계시니 오버라이드는

 생략하죠.

 이상하게 c.set()은 에러가 나왔습니다. c클래스이 메모리를 사용하면서 왜 C 클래스의 매서들르

 사용 못할까요?

 

 수행순서를 생각해 보죠.

 A ab=new C(); 가 만들어지면

 상속 받은 클래스 A 에 대해 재정의 된 매서드가 있는지 검사합니다.

 draw()는 재정의가 됐으니 당연히 사용가능합니다.

 하지만 set() 은 재정의가 이루어지지 않았죠. 당연히 사용 불능입니다.

 

 

 하지만 이것이 무얼 의미하는지 진정한 의미를 파악하는게 중요합니다.

 인터페이스와 추상클래스 개념에서 업캐스팅은 무하한 위력을 발휘죠.

 또한 우리가 쉽게 사용하는 구문들 중에서 캐스팅 관련에서 업캐스팅은 많이 일어납니다.

 무심코 사용하는 구문들 대부분이 업 캐스팅의 개념을 포함하고 있습니다.

 

 실용적인 예로 인터페이스의 리스너의 들면

 마우스 리스러는 마우스의 조작을 처리합니다.

 우리는 리스터를 통해 마우스의 여러가지 특성을 부여하면

 

 자바 vm은 알아서 마우스를 제어하는 컴퓨터의 드라이버를 찾아 그 실행에 맞게

 마우스를 조작하는 역활을 해주죠. 우선 이 의미를 파악하고 리스너를 사용하십시오

 

모든 리스너는 다 이런 식입니다.

 마우스가 컴퓨터의 윈도우 환경에서 동작하려면 드라이버를 깔아야죠? ps/2나 usb드라이버를

 찾지 않습니까? 하지만 자바는 vm기반 언어기에 일일이 드라이버에 대한 정의를 해쥐 않아도 됩니다.

 리스너라는 편리한 도구가 있고, 그 리스너에서 제공하는 메서드를 사용해  마우스의 조작을

 해주는 겁니다.

 키보드 리스너의 경우도 마찬가지입니다. 이것 오에 리스너는 상당 종류가 있습니다면

 그 원리를 이해한다면 이미 반은 이해한 겁니다.

 

 

 

 그런데 아시다시피 인터페이스도 클래스의 한 종류 입니다.

 하지만 인터페이스는 껍데기입니다. 메모리를 가지지 않습니다. 메모리가 없기에 구현을 통해

 사용하는 것이죠. (메모리가 없다는 것은 그것을 동작한 자료를 넣을 공간이 없다는 겁니다.)

 

 

 마우스 리스너의 형을 가지고

 A 라는 클래스를 만들면 (A 클래스는 마우스 리스너를 구현했고, 메모리도 가지고 있게 되겠죠.)

 

 우리는 A  라는 클래스에서 리스너의 메서드를 모두 적어줘야 합니다. (그걸 방지하는게 아답터죠.) 여기서 일단 매서드 오버라이딩이 일어나고 (오버라이딩이 일어나지 않으면 구현받은 인터페이스에 접근하는게 불가능하니까요.)

 

 우리는 A클래스를 가지고 인터페이스 마우스 리스너의 제어담당 매서드에 접근해 그것을

 사용하게 해주는 겁니다.

 왜 그럴까요?

 

 인터페이스는 클래스이지만 메모리가 없습니다.

 그래소 implemets를 통해 구현하죠.

 느낌이 오십니까? (이런것이 여러가지 제작툴에서 사용되는 업캐스팅의 예입니다. 사실... 업캐스팅이 진짜 의미는 이정도를 넘어섭니다. 엄청난 아주 대단한 위력이 있죠. 남의 소스를 고치거나

 클래스를 고치고 할때도 업캐스팅은 강력합니다.)

 

 제 설명이 부족하다면 책을 찾아 보시길 바랍니다.

 아주 자세히 설명된 책도 많습니다.

 

 

 

 추상클래스의 경우도 마찬가지입니다.  추상 클래스는 클래스이지만 껍데기 입니다.

 상속 받아 클래스 안에 메모리를 만들어 줘야 사용가능하죠.

 

 업캐스팅을 처음 들어보셨다면... 책을 좀 더 보야 될것 같습니다.

 아주중요한 개념이고 이 개념을 이해해야 뒤에가서도 막히지 않습니다.

 상속 과 캐스팅이 자바의 전부라 해도 과헌이 아닙니다.

 자바는 기초가 아주 중요합니다. 지금도 계속 책을 찾아 보고 있죠.

 

 사실 그림 맞추기 게임에서 진짜 알야될 부분은 이런 기초적인 것에 대한 확실한 이해를 다지는 겁니다.

 이것 외에 배열에 대해서도 상당히 많으 부분을 알야 될겁니다.

 애플릿은 단지 툴입니다. 솔직히 제 생각에 에플릿은 그리 대단치 않습니다. 차라리 플래쉬가

 백배 낫죠.  하지만 애플릿은 그 자체로도 상당히 복잡합니다.

 버튼 하나를 넣기 위해서 여러가지 틀을 생각해서 순서에 맞게 구현해야 되지 않나요?

 그걸 외우는건 나중일입니다. 일단 기초적인 공부가 받쳐주면 왜 그런 순서로 작성해야 되는지가

 자연스럽게 들어오고 해당 클래스와 매서드들이 자연스럽게 머리에 들어오게 됩니다.

 

 

 

 

 한가지 더 예를 든다면

 무심코 사용하는 super에 대한 것을 들죠

 슈퍼는 아버지 클래스의 생성자를 참조하는 방법입니다.

 하지만 이게 어떤 의미인지 왜 그런지에 대해서는 자세히 알지 못하면 사용하는 의미가 없습니다.

 

 부모 클래스를 상속 받은 아들 클래스는 부모 클래스의 모든 기능을 가집니다.

 오버라이딩을 통해서, 업캐스팅을 통해서 모든 기능을 다 구현할 수 있죠.

 그런데 여기서 생성자를 생각해 봅시다.

 

 부모도 생성자가 있겠고, 아들도 생성자가 있겠죠. 표현하지 않더라도 디폴트 생성자라는 것을 무조건적으로 가지니까요.

 

 상속시 매소드처럼 오버라이드되어 부모 클래스의 생성자가 호출되지 않는다면 어떨까요?

 그렇다면 부모 클래스의 초기화 작업을 아들 클래스에서 모두 해줘야겠죠.

 이런 불편함이 있어서 super가 존재합니다.

 하지만 super의 명확한 의미는 이게 다가 아닙니다.

 

 상속시 생성자의 순서를 살펴보죠.

 일단 아버지 의 디폴트(매개변수가 없는 생성자)가 생성되고

 그다음 아들의 생성자가 생성됩니다.

 

 아버의 생성자는 무조건적으로 생성되지만, 오직 디폴트 생성자만 가능합니다.

 만약 매개 변수를 가지고 아버지 생성자에 접근하려 해도, 디폴트 생성자 밖에

 나오지 않게 됩니다.

 

 이것은 c++ 의 개념과 똑 같습니다. 하지만 자바는 이 문제를 super를 통해 해결합니다.

 인자가 붙은 부모 클래스의 생성자에 나는 접근하고 싶다.라고 할때

 super();를 사용하는겁니다.

 

 즉 인자가 없는 부모 클래스의 생성자에 접근할 때는 super를 사용할 필요가 없습니다

 오직 인자(매개변수)가 있는 부모 클래스에 접근할 때 super를 사용하는 것이죠

 

 이것이 주는 것은 상상합니다.

 방금 생성자 생성 순서를 알아봤죠?

 생성자는 초기화의 기능을 합니다.

 그리고 무조건 적으로 아버지 클래스 부터 생성하고 그다음 아들 클래스를 생성합니다.

 

 

 왜 super()를 가장 처음에 쓰는지 이것으로 이해됐겠죠?

 일단 아버지 클래스가 초기화 되야, 상속받을 수 있고, 그래야 아들 클래스도 구현할 수 있죠.

 

 

그렇다면 우리는 상속 받은 상태에서 아버지 클래스의 초기값을 마음대로 바꾸어 프로그램

 전반을 제어하는 기능을 가질 수 있는 겁니다.

 이 의미까지 이해했다면 상속에 대해 거의 다 이해한 겁니다.

 

 초기값을 바꾼다는 것은 프로그램 자체를 변화시키를 수 있는 거죠.

 그것이 바로 자바가 가지는 가장 큰 장점입니다 상속을 통해 아버지 클래스의 내용을

 변화 시킬 수 있다는 거죠.

 

 사실 이것 외에도 여러가지가 더 있습니다. 하지만 이정도만 알아도 어지간한 개념은 다 이해되어

 넘어갈 것같습니다. 기회가 되면 다음에는 자료구조와 디자인 패턴에 대한 이야기를 더 해보죠.

 그럼 공부 열심히 하세요.

출처 : http://cafe.naver.com/javachobostudy.cafe 자바초보스터디.
Chapter 10. 내부클래스(inner class)


1. 내부클래스(inner class)란?

내부클래스란, 클래스 내에 선언된 클래스이다. 클래스에 다른 클래스 선언하는 이유는 간단하다. 두 클래스가 서로 긴밀한 관계에 있기 때문이다. .
한 클래스를 다른 클래스의 내부클래스로 선언하면 두 클래스의 멤버들간에 서로 쉽게 접근할 수 있다는 것과 외부에는 불필요한 클래스를 감춤으로써 코드의 복잡성을 줄일 수 있다는 장점을 얻을 수 있다. 

.내부클래스의 장점
- 내부클래스에서 외부클래스의 멤버들을 쉽게 접근할 수 있다. 
- 코드의 복잡성을 줄일 수 있다.(캡슐화) 

[참고]내부 클래스는 JDK1.1버젼 이후에 추가된 개념이다. 


왼쪽의 A와 B 두 개의 독립적인 클래스를 오른쪽과 같이 바꾸면 B는 A의 내부클래스(inner class)가 되고 A는 B를 감싸고 있는 외부클래스(outer class)가 된다. 이 때 내부클래스인 B는 외부클래스인 A를 제외하고는 다른 클래스에서 사용되지 않아야한다.

내부클래스는 주로 AWT나 Swing과 같은 GUI어플리케이션의 이벤트처리 외에는 잘 사용하지 않을 정도로 사용빈도가 높지 않으므로 내부클래스의 기본 원리와 특징을 이해하는 정도까지만 학습해도 충분하다. 실제로는 발생하지 않을 경우까지 이론적으로 만들어 내서 고민하지말자.

내부클래스는 클래스 내에 선언된다는 점을 제외하고는 일반적인 클래스와 다르지 않다. 다만 앞으로 배우게 될 내부클래스의 몇 가지 특징만 잘 이해하면 실제로 활용하는데 어려움이 없을 것이다.



2. 내부클래스의 종류와 특징

내부클래스의 종류는 변수의 선언위치에 따른 종류와 같다. 내부클래스는 마치 변수를 선언하는 것과 같은 위치에 선언할 수 있으며, 변수의 선언위치에 따라 인스턴스변수, 클래스변수(static변수), 지역변수로 구분되는 것과 같이 내부클래스도 선언위치에 따라 다음과 같이 구분되어 진다. 내부클래스의 유효범위와 성질이 변수와 유사하므로 서로 비교해보면 이해하는데 많은 도움이 된다.

인스턴스클래스
(instance class)
외부클래스의 멤버변수 선언위치에 선언하며, 외부클래스의 인스턴스멤버처럼 다루어진다. 주로 외부클래스의 인스턴스멤버들과 관련된 작업에 사용될 목적으로 선언된다.
스태틱클래스
(static class)
외부클래스의 멤버변수 선언위치에 선언하며, 외부클래스의 static멤버처럼 다루어진다. 주로 외부클래스의 static멤버, 특히 static메서드에서 사용될 목적으로 선언된다.
지역클래스
(local class)
외부클래스의 메서드나 초기화블럭 안에 선언하며, 선언된 영역 내부에서만 사용될 수 있다.

익명클래스
(anonymous class)
클래스의 선언과 객체의 생성을 동시에 하는 이름없는 클래스(일회용)



[표10-1]내부클래스의 종류와 특징
[참고]초기화블럭 관련내용은 1권 p.167를 참고 



3. 내부클래스의 선언 

아래의 오른쪽 코드에는 외부클래스(Outer)에 3개의 서로 다른 종류의 내부클래스를 선언했다. 양쪽의 코드를 비교해 보면 내부클래스의 선언위치가 변수의 선언위치와 동일함을 알 수 있다.
변수가 선언된 위치에 따라 인스턴스변수, 스태틱변수(클래스변수), 지역변수로 나뉘듯이 내부클래스도 이와 마찬가지로 선언된 위치에 따라 나뉜다. 그리고, 각 내부클래스의 선언위치에 따라 같은 선언위치의 변수와 동일한 유효범위(scope)와 접근성(accessibility)을 갖는다.




4. 내부클래스의 제어자와 접근성

아래코드에서 인스턴스클래스(InstanceInner)와 스태틱클래스(StaticInner)는 외부클래스(Outer)의 멤버변수(인스턴스변수와 클래스변수)와 같은 위치에 선언되며, 또한 멤버변수와 같은 성질을 갖는다.
따라서 내부클래스가 외부클래스의 멤버와 같이 간주되고, 인스턴스멤버와 static멤버간의 규칙이 내부클래스에도 똑같이 적용된다.


내부클래스도 클래스이기 때문에 abstract나 final과 같은 제어자를 사용할 수 있을 뿐만 아니라, 멤버변수들처럼 private, protected과 접근제어자도 사용이 가능하다.

[예제10-1] InnerEx1.java
class InnerEx1 { 
      class InstanceInner { 
            int iv=100; 
//             static int cv=100;                   // 에러! static변수를 선언할 수 없다. 
            final static int CONST = 100;       // static final은 상수이므로 허용한다. 

      } 
      static class StaticInner { 
            int iv=200; 
            static int cv=200;       //       static클래스만 static멤버를 정의할 수 있다. 
      } 

      void myMethod() { 
            class LocalInner { 
                  int iv=300; 
//                   static int cv=300;                   // 에러! static변수를 선언할 수 없다. 
                  final static int CONST = 300;       // static final은 상수이므로 허용한다. 
            } 
      } 

      public static void main(String args[]) { 
            System.out.println(InstanceInner.CONST); 
            System.out.println(StaticInner.cv); 
      } 
}
[실행결과]
100
200

[참고]final이 붙은 변수는 상수(constant)이기 때문에 어떤 경우라도 static을 붙이는 것이 가능하다.

내부클래스 중에서 스태틱클래스(StaticInner)만 static멤버를 가질 수 있다. 드문 경우지만 내부클래스에 static변수를 선언해야한다면 스태틱클래스로 선언해야한다.
다만 final과 static이 동시에 붙은 변수는 상수이므로 모든 내부클래스에서 정의가 가능하다.


[예제10-2] InnerEx2.java
class InnerEx2 { 
      class InstanceInner {} 
      static class StaticInner {} 

      InstanceInner iv = new InstanceInner(); // 인스턴스 멤버간에는 서로 직접 접근이 가능하다. 
      static StaticInner cv = new StaticInner();    // static 멤버간에는 서로 직접 접근이 가능하다. 

      static void staticMethod() { 
//       static멤버는 인스턴스 멤버에 직접 접근할 수 없다. 
//             InstanceInner obj1 = new InstanceInner();       
            StaticInner obj2 = new StaticInner(); 

//       굳이 접근하려면 아래와 같이 객체를 생성해야한다. 
//       인스턴스 내부클래스는 외부클래스를 먼저 생성해야만 생성할 수 있다. 
            InnerEx2 outer = new InnerEx2(); 
            InstanceInner obj1 = outer.new InstanceInner(); 
      } 

      void instanceMethod() { 
// 인스턴스 메서드에서는 인스턴스멤버와 static멤버 모두 접근 가능하다. 
            InstanceInner obj1 = new InstanceInner(); 
            StaticInner obj2 = new StaticInner(); 
//       메서드 내에 지역적으로 선언된 내부클래스는 외부에서 접근할 수 없다. 
//             LocalInner lv = new LocalInner(); 
      } 

      void myMethod() { 
            class LocalInner {} 
            LocalInner lv = new LocalInner(); 
      } 
}

인스턴스멤버는 같은 클래스에 있는 인스턴스멤버와 static멤버 모두 직접 호출이 가능하지만, static멤버는 인스턴스멤버를 직접 호출할 수 없는 것처럼, 인스턴스클래스는 외부클래스의 인스턴스멤버를 객체 생성없이 바로 사용할 수 있지만, 스태틱클래스는 외부클래스의 인스턴스멤버를 객체 생성없이 사용할 수 없다.

마찬가지로 인스턴스클래스는 스태틱클래스의 멤버들을 객체생성없이 사용할 수 있지만, 스태틱클래스에서는 인스턴스클래스의 멤버들을 객체생성없이 사용할 수 없다.


[예제10-3] InnerEx3.java
class InnerEx3 { 
      private int outerIv = 0; 
      static int outerCv = 0; 

      class InstanceInner { 
            int iiv = outerIv;             // 외부클래스의 private멤버도 접근가능하다. 
            int iiv2 = outerCv; 
      } 

      static class StaticInner { 
// static클래스는 외부클래스의 인스턴스 멤버에 접근할 수 없다. 
//             int siv = outerIv; 
            static int scv = outerCv; 
      } 

      void myMethod() { 
            int lv = 0; 
            final int LV = 0; 

            class LocalInner { 
                  int liv = outerIv; 
                  int liv2 = outerCv; 
//       외부클래스의 지역변수는 final이 붙은 변수(상수)만 접근가능하다. 
//                   int liv3 = lv; 
                  int liv4 = LV; 
            } 
      } 
}


내부클래스에서 외부클래스의 변수들에 대한 접근성을 보여 주는 예제이다. 내부클래스의 전체 내용 중에서 제일 중요한 부분이므로 잘봐두도록 하자.

 

인스턴스클래스(InstanceInner)는 외부클래스(InnerEx3)의 인스턴스멤버이기 때문에 인스턴스변수 outerIv와 static변수 outerCv를 모두 사용할 수 있다. 심지어는 outerIv의 접근제어자가 private일지라도 사용가능하다. 

스태틱클래스(StaticInner)는 외부클래스(InnerEx3)의 static멤버이기 때문에 외부클래스의 인스턴스멤버인 outerIv와 InstanceInner를 사용할 수 없다. 단지 static멤버인 outerCv만을 사용할 수 있다. 

지역클래스(LocalInner)는 외부클래스의 인스턴스멤버와 static멤버를 모두 사용할 수 있으며, 지역클래스가 포함된 메서드에 정의된 지역변수도 사용할 수 있다.
 단, final이 붙은 지역변수만 접근가능한데 그 이유는 메서드가 수행을 마쳐서 지역변수가 소멸된 시점에도, 지역클래스의 인스턴스가 소멸된 지역변수를 참조하려는 경우가 발생할 수 있기 때문이다.


[예제10-4] InnerEx4.java
class Outer { 
      class InstanceInner { 
            int iv=100; 
      } 
      static class StaticInner { 
            int iv=200; 
            static int cv=300; 
      } 

      void myMethod() { 
            class LocalInner { 
                  int iv=400; 
            } 
      } 


class InnerEx4 { 
      public static void main(String args[]) { 
            // 인스턴스 내부클래스의 인스턴스를 생성하려면 
            // 외부클래스의 인스턴스를 먼저 생성해야한다. 
            Outer oc = new Outer(); 
            Outer.InstanceInner ii = oc.new InstanceInner(); 

            System.out.println("ii.iv : "+ ii.iv); 
            System.out.println("Outer.StaticInner.cv : "+ Outer.StaticInner.cv); 

            // Static내부클래스의 인스턴스는 외부클래스를 생성하지 않아도 된다. 
            Outer.StaticInner si = new Outer.StaticInner(); 

            System.out.println("si.iv : "+ si.iv); 
      } 
}
[실행결과]
ii.iv : 100
Outer.StaticInner.cv : 300
si.iv : 200


외부클래스가 아닌 다른 클래스에서 내부클래스를 생성하고 내부클래스의 멤버에 접근하는 예제이다. 실제로 이런 경우가 발생했다는 것은 내부클래스로 선언해서는 안되는 클래스를 내부클래스로 선언했다는 의미이다. 참고로만 봐두고 가볍게 넘어가도록 하자.

위 예제를 컴파일했을 때 생성되는 클래스 파일은 다음과 같다. 

InnerEx4.class
Outer.class
Outer$InstanceInner.class
Outer$StaticInner.class
Outer$1LocalInner.class


컴파일 했을 때 생성되는 파일명은 '외부클래스명$내부클래스명.class'형식으로 되어 있다. 다만 서로 다른 메서드 내에서는 같은 이름의 지역변수를 사용하는 것이 가능한 것처럼, 지역내부클래스는 다른 메서드에 같은 이름의 내부클래스가 존재할 수 있기 때문에 내부클래스명 앞에 숫자가 붙는다. 


만일 오른쪽의 코드를 컴파일 한다면 다음과 같은 클래스파일이 생성될 것이다.


Outer.class
Outer$1LocalInner.class
Outer$2LocalInner.class


[예제10-5] InnerEx5.java
class Outer { 
      int value=10;       // Outer.this.value                         

      class Inner { 
            int value=20;       // this.value 
            void method1() { 
                  int value=30; 
                  System.out.println(" value :" + value); 
                  System.out.println(" this.value :" + this.value); 
                  System.out.println("Outer.this.value :" + Outer.this.value); 
            } 
      } // Inner클래스의 끝 
// Outer클래스의 끝 

class InnerEx5 { 
      public static void main(String args[]) { 
            Outer outer = new Outer(); 
            Outer.Inner inner = outer.new Inner(); 
            inner.method1(); 
      } 
// InnerEx5 끝
[실행결과]
value :30 
this.value :20 
Outer.this.value :10 

위의 예제는 내부클래스와 외부클래스에 선언된 변수의 이름이 같을 때 변수 앞에 'this' 또는 '외부클래스명.this'를 붙여서 서로 구별할 수 있다는 것을 보여준다.



5. 익명클래스(anonymous class)

이제 마지막으로 익명 클래스에 대해서 알아보도록 하자. 익명클래스는 특이하게도 다른 내부클래스들과는 달리 이름이 없다. 클래스의 선언과 객체의 생성을 동시에 하기 때문에 한 단번만 사용될 수 있고 오직 하나의 객체만을 생성할 수 있는 일회용 클래스이다. 


이름이 없기 때문에 생성자도 가질 수 없으며, 조상클래스의 이름이나 구현하고자 하는 인터페이스의 이름을 사용해서 정의하기 때문에 하나의 클래스로 상속받는 동시에 인터페이스를 구현하거나 하나 이상의 인터페이스를 구현할 수 없다. 오로지 단 하나의 클래스를 상속받거나 단 하나의 인터페이스만을 구현할 수 있다.

익명클래스는 구문이 다소 생소하지만, 인스턴스클래스를 익명클래스로 바꾸는 연습을 몇 번만 해 보면 금새 익숙해 질 것이다.


[예제10-6] InnerEx6.java
class InnerEx6{ 
      Object iv = new Object(){ void method(){} };             // 익명클래스 
      static Object cv = new Object(){ void method(){} };       // 익명클래스 

      void myMethod() { 
            Object lv = new Object(){ void method(){} };       // 익명클래스 
      } 
}

위의 예제는 단순히 익명클래스의 사용 예를 보여 준 것이다. 이 예제를 컴파일 하면 다음과 같이 4개의 클래스파일이 생성된다.


InnerEx6.class
InnerEx6$1.class ← 익명클래스
InnerEx6$2.class ← 익명클래스
InnerEx6$3.class ← 익명클래스


익명클래스는 이름이 없기 때문에 '외부클래스명$숫자.class'의 형식으로 클래스파일명이 결정된다.


[예제10-7] InnerEx7.java
import java.awt.*; 
import java.awt.event.*; 

class InnerEx7 

      public static void main(String[] args) 
      { 
            Button b = new Button("Start"); 
            b.addActionListener(new EventHandler()); 
      } 


class EventHandler implements ActionListener 

      public void actionPerformed(ActionEvent e) { 
            System.out.println("Action Event occured!!!"); 
      } 


[예제10-8] InnerEx8.java
import java.awt.*; 
import java.awt.event.*; 

class InnerEx8 

      public static void main(String[] args) 
      { 
            Button b = new Button("Start"); 
            b.addActionListener(new ActionListener() { 
                        public void actionPerformed(ActionEvent e) { 
                              System.out.println("Action Event occured!!!"); 
                        } 
                  }       // 익명 클래스의 끝 
            ); 
      }       // main메서드의 끝 
}       // InnerEx8클래스의 끝

예제 InnerEx7.java를 익명클래스를 이용해서 변경한 것이 예제 InnerEx8.java이다. 먼저 두 개의 독립된 클래스를 작성한 다음에, 다시 익명클래스를 이용하여 변경하면 보다 쉽게 코드를 작성할 수 있다. 

'자바 > 자바팁' 카테고리의 다른 글

자바 프리드로우 버전이다.  (0) 2010.11.15
업캐스팅 관련 설명  (0) 2010.11.15
XML DOM 자바로 이해하기  (0) 2010.11.07
객체직렬화 설명  (0) 2010.10.29
objectinputstream 생성시 주의사항  (0) 2010.10.28
기본적으로 XML을 파실할 적에 고려해야 할 게 있다.
1.유효성 검사에 필요한 .dtd파일이 있나..

dtd파일 작성은 다음과 같다.

book.xml
 


	
		배우자 자바
		25000원
	
	
		배우자 자바2
		2123000원
	
	
		배우자 자바3
		25123000원
	





그럼 여기서 book.dtd는 다음과 같다.
 




	
		
		









마지막으로 자바 XML 입,출력 및 기본파싱은 다음과 같다.
 
import javax.xml.parsers.*;
import javax.xml.transform.*;
import org.w3c.dom.*;
import javax.xml.transform.dom.*;
import javax.xml.transform.stream.*;
import java.io.*;

public class  XmlParseExam
{
	public static void main(String[] args) 
	{

		try
		{
			DocumentBuilderFactory factory
					= DocumentBuilderFactory.newInstance();
			factory.setIgnoringElementContentWhitespace(true);

			DocumentBuilder builder = factory.newDocumentBuilder();
			
			Document doc = builder.parse("book.xml");

			Element eRoot = doc.getDocumentElement();
			
			Element booklist_ele = (Element)eRoot.getFirstChild();
			
			Element book_ele = (Element)booklist_ele.getFirstChild();
			
			Element bookSec = (Element)book_ele.getNextSibling();

			Text txt = (Text)book_ele.getFirstChild();

			NodeList booklist = doc.getElementsByTagName("book");
			
			for(int i=0;i


나중에 꼭 참고하자.

'자바 > 자바팁' 카테고리의 다른 글

업캐스팅 관련 설명  (0) 2010.11.15
내부클래스 설명  (1) 2010.11.10
객체직렬화 설명  (0) 2010.10.29
objectinputstream 생성시 주의사항  (0) 2010.10.28
자바 날짜 관련 함수모음  (0) 2010.10.28

 

객체 직렬화(Object Serialization):

송 재승(angel1011@hanmail.net)

 

자바에서는 자바에서 제공하는 기본적인 데이터 유형 이외에도 여러 객체들을 스트림에 쓰고, 읽을 수 있는 기능을 제공하는데 이것이 바로 객체 직렬화를 통해서 가능하다.
이러한 객체 직렬화 기법은 원격 메소드 호출, 보안 및 암호화 등 실제 프로그래밍 시에 유용하게 사용되어 질 수 있다.

 객체 직렬화의 비밀

그러면 먼저 객체 직렬화가 어떠한 과정을 거쳐서 이루어 지는지 비밀을 벗겨보도록 하자.
먼저 객체들은 ObjectOutputStream에 의해서 직렬화가 되며, ObjectInputStream에 의해서 직렬화가 해제된다는 사실을 기억하도록 하자. 이러한 일련의 과정을 그림으로 나타내면 다음과 같다.

[그림 객체 직렬화]

여기에 사용되는 ObjectOutputStream과 ObjectInputStream은 모두 java.io 패키지의 일부분이고, 각각 DataOutput과 DataInput클래스를 implement한 ObjectOutput과 ObjectInput을 extends 해서 만들어 진것이다. 그리고 이 두 클래스에는 비원시 객체와 배열등을 스트림에 읽고 쓰기 위한 메쏘드들이 추가되어져 있다.

[그림 클래스 구조도]

객체 직렬화의 과정

지금부터는 보다 자세히 객체의 직렬화 과정에 대해 알아보도록 하자.

객체는 ObjectOutputStream의 writeObject() 메쏘드에 자신을 넘김으로써 직렬화 된다.
WirteObject()메쏘드는 private 필드와 super 클래스로부터 상속받은 필드를 포함, 객체의 모든 것을 기록하게된다.
직렬화 해제는 직렬화와 반대의 과정을 거치게 되는데 ObjectInputStream의 readObject()메쏘드를 호출함으로써 스트림으로부터 읽어 들이고 이를 직렬화가 되기전의 객체로 다시 만들게 된다.

다음은 객체가 직렬화 되고 해제되어 원래의 객체로 복원되는 과정에 대한 간단한 예제이다.

눈여겨 볼 부분은 ObjectInputStream을 생성해서 writeObject()를 사용해서 객체를 직렬화 하는것과 ObjectInputStream을 생성해서 readObject()를 통해서 객체를 복원하는 부분이다. 또한 SerializableClass가 Serializable을 implements한 것을 주의해서 보길 바란다.

 

import java.io.*;

public class ObjectSerializeTest {

    public static void main(String[] args) throws Exception {
/* 파일을 열어서 그곳에 객체를 직렬화 시켜서 저장한다. */
// 파일 출력 스트림을 연다. 

    FileOutputStream fos = new FileOutputStream("_imsi.txt");

// 객체 스트림을 열고, 객체스트림을 통해 객체를 파일에 저장
    ObjectOutputStream oos = new ObjectOutputStream(fos);
    oos.writeObject(new SerializableClass("Serialize Test Program", 1004));

// 스트림을 닫는다. 
    oos.close();

/* 직렬화 된 객체가 저장된 파일로 부터 객체를 해제시켜 원래의 객체를 복원*/
// 파일 입력 스트림으로부터 객체 입력 스트림을 연다.

    FileInputStream fis = new FileInputStream("_imsi.txt");
    ObjectInputStream ois = new ObjectInputStream(fis);

// 객체 입력 스트림으로부터 객체를 읽어온다.
    SerializableClass sc = (SerializableClass)ois.readObject();

// 스트림을 닫는다. 
    ois.close();

/* 스트림으로부터 읽어들인 객체의 내용을 출력 원래 생성되었던 객체와 같은 값을 갖는다는 것을 알수가 있다. */
    System.out.println("String : " + sc.mString);
    System.out.println("Integer : " + sc.mInt);
   }
}

/* 하나의 문자열과 정수를 저장하고있는 클래스
Serializable을 implements 함으로써 스트림을 통해 직렬화되고 해제되어질수 있다. */

class SerializableClass implements Serializable {

    public String mString;
    public int mInt;

// 생성자 
    public SerializableClass(String s, int i) {
        this.mString = s;
        this.mInt = I;
    }
}

  • 실횅방법

    javac.exe ObjectSerializeTest.java
    java.exe ObjectSerializeTest

  • 실행결과

String : Serialize Test Program
Integer : 1004

 

 

커스텀 직렬화

  • Serializable

스트림을 통해서 직렬화 또는 해제 될수 있는 객체는 Serialiazable을 implements한 객체만이 가능하다.자바 2에서는 java.awt, javax.swing, etc등의 클래스들이 Serializable을 implements하고 있으며, 이러한 상위 클래스들이 implements하므로 하위 클래스들 또한 같이 직렬화/해제의 범주에 들 수가 있다.

다음은 Serializable을 implements하고있어서 객체의 직렬화가 가능한 객체들의 리스트이다.

[직렬화 가능 객체 리스트]

 

패키지

Serializable

java.awt

BorderLayout, CardLayout, CheckboxGroup, Color, Component, ComponentOrientation, Cursor, Dimension, Event, FlowLayout, FocusManager, Font, FontMetrics, GraphicsConfigTemplate, GridBagConstraints, GridBagLayout, GridBagLayoutInfo, GridLayout, ImageMediaEntry, Insets, LightweightDispatcher, MediaTracker, MenuComponent, MenuShortcut, Point, Polygon, Rectangle, ScrollPaneAdjustable, SystemColor

java.awt.dnd

DropTarget

java.awt.font

TransformAttribute

java.awt.geom

AffineTransform

java.awt.image.renderable

ParameterBlock

java.beans

PropertyChangesSupport, VetoableChangeSupport, BeanContext, BeanContextChildSupport, BeanContextSupport

java.io

ExternalizableFile, FilePermission, FilePermissionCollection, ObjectStreamClass

java.net

InetAddress, SocketPermission, SocketPermissionCollection, URL

java.rmi

MarshalledObject

java.rmi.activation

ActivationDesc, ActivationGroupDesc, ActivationGroupID, ActivationID

java.rmi.dgc

Lease, VMID

java.rmi.server

ObjID, RemoteObject, UID

java.security

AllPermissionCollection, BasicPermission, BasicPermissionCollection, CodeSource, GuardedObject, Identity, Key, KeyPair, Permission, PermissionCollection, Permissions, PermissionsHash, SecureRandomSpi, SignedObject, UnresolvedPermission, UnresolvedPermissionCollection

java.text

BreakIterator, Collector, DateFormatSymbols, DecimalFormatSymbols, Format, SpecialMapping, TextBoundaryData, UnicodeClassMapping, WordBreakTable

java.util

ArrayList, BitSet, Calendar, Date, EventObject, HashMap, HashSet, Hashtable, LinkedList, Locale, PropertyPermissionCollection, Random, TimeZone, TreeMap, TreeSet, Vector

javas.swing.table

AbstractTableModel, DefaultTableCellRenderer, DefaultTableColumnModel, DefaultTableModel, TableColumn

javax.swing.text

AbstractDocument, EditorKit, GapContext, SimpleAttributeSet, StringContent, StyleContext, TabSet, TabStop

javax.swing.tree

DefaultMutableTreeNode, DefaultTreeModel, DefaultTreeSelectionModel, TreePath

 

Serializable은 Cloneable과 같은 마커 인터페이스로서, 어떠한 메쏘드들을 정의해놓은 것이 아닌, serizlVersionUDI라는 변수 하나만을 가지며, 이 객체가 직렬화가 가능하다는 것을 알려주는 역할만을 하는 인터페이스일 뿐이다.

  • transient

스트림을 이용해서 직렬화 하는데 있어서, 커다란 프로그램 전체가 직렬화된다면, 여러가지 면에서 많은 낭비일 것이다. 예를 들어, 한 객체가 마우스가 눌려진 위치를 알기 위해서 마우스 클릭시에 위치를 저장하는 필드를 가지고 있다고 가정하자, 이 경우 마우스의 위치값은 프로그램이 돌아가는 상태에서 마우스가 눌려졌을 당시에만 유효한 값으로, 객체가 직렬화 되었다가 해제 되었을 경우에는 쓸모없는 값이 되어버린다.

이런 객체 직렬화에 쓸모없는 값들은 transient로 선언해 줌으로써 객체 직렬화에서 제외되어질수 있다.

Private transient int x;

이러한 선언은 플랫폼에 따라 다른 값을 가지는 필드나, 현재의 환경에서만 유효한 필드등을 객체 직렬화에서 제외하는데 유용하게 쓰일 수가 있다.

transient선언이 적용된 예이다.

 

import java.io.*;

public class ObjectSerializeTest1 extends TestClass implements Serializable {

// 변수 선언, x는 객체직렬화에서 제외되도록 transient로 선언 
    private int i;
    private transient int x;

// 생성자 Serializable을 implements한 i와 x는 받아온 인자를 그대로 대입하고, TestClass를 extends한 j는
// i값에 5배를 한 값을 대입했다. 

    public ObjectSerializeTest1(int i, int x) {
        this.i = i;
        this.x = x;
        j = i*5;
    }

/* 객체직렬화를 하기전의 객체의 값들을 알아본뒤에, 해당 객체를 스트림에 직렬화 시킨다.*/
    private void writeObject(ObjectOutputStream out) throws IOException {
        System.out.println("writeObject");
        System.out.println("write ==> i = "+this.i+", j = "+j+", x = "+this.x);
        out.defaultWriteObject(); 
// 객체를 직렬화 한다.
        out.writeInt(j); 
// 임시파일에 기록
    }

/* 스트림으로 부터 객체를 해제시킨다. */
    private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
        System.out.println("readObject");
        in.defaultReadObject(); 
// 객체 직렬화 해제 작업
        j=in.readInt(); 
// 임시파일에서 읽어옴
    }

/* 객체의 값을 알기 위해서 오버라이드 */ 
    public String toString() { 
        return "i = "+i+", j = "+j+", x = "+x;
    }

    public static void main(String[] args) throws Exception {
// 객체직렬화에 사용될 화일을 열고 스트림을 얻는다. 
        FileOutputStream fos = new FileOutputStream("temp.txt");
        ObjectOutputStream oos = new ObjectOutputStream(fos);

// 객체를 생성시킨뒤, 스트림에 객체를 쓴다.(10은 transient로 선언된 필드의 값임을 유의)
        oos.writeObject(new ObjectSerializeTest1(1, 10));
        oos.close();

// 정보를 저장한 화일을 열고 스트림을 통해 읽은뒤 객체직렬화를 해제시킨다.
        FileInputStream fis = new FileInputStream("temp.txt");
        ObjectInputStream ois = new ObjectInputStream(fis);
        ObjectSerializeTest1 ser = (ObjectSerializeTest1)ois.readObject();
        ois.close();

// print문 수행시 형 변환이 이루어져서 이미 오버라이드된 toString()메쏘드가 수행된다.
        System.out.println("read ==> "+ser);
    }
}

/* Serialize되지 않은 클래스 객체 직렬화 작업으로부터는 제외, 만약 사용자가 이 클래스를 객체 직렬화에 사용하고 싶다면 Serializable을 implements하면 된다. 만약 그렇게 할수 없을 경우에는 위와 같이 하도록 한다. */
class TestClass {
    int j;
}

  • 실횅방법

    javac.exe ObjectSerializeTest1.java
    java.exe ObjectSerializeTest1

  • 실행결과

writeObject
write ==> i = 1, j = 5, x = 10
readObject
read ==> i = 1, j = 5, x = 0

 

 

  • externalizable

객체직렬화의 또 다른 방법으로는 Externalizable 인터페이스를 들수 있는데, 이 방법은 Serializable보다 더 강력하게 객체 직렬화를 제어하고 싶을 경우에 사용되어진다. 어떤 클래스가 이 인터페이스를 구현하게 되면 readExternal()과 writeExternal()메쏘드를 사용하여 객체를 스트림으로부터 읽고 쓸수있다. Externalizable객체들은 자신의 상태를 직렬화 및 해제하는 과정에서 강력한 제어를 할수 있으며, Serializable 객체에서의 기본 데이터 포멧을 사용하고싶지 않을때에 사용한다.

다음은 externalizable을 사용한 객체 직렬화의 예이다.

 

import java.io.*;

public class ObjectExternalizeTest 
   
 implements Externalizable {

    int i;
    String s;

/* 인자없는 생성자(직렬화된 데이터를 읽어들여서 객체를 만들기 위해 필요) */
    public ObjectExternalizeTest() { }

/* 생성자 */
    public ObjectExternalizeTest(String s, int i) {
        this.s = s;
        this.i = i;
    }

/* readObject()메쏘드로부터 호출이 되며, 직렬화된 객체를 해제시킨다. */
    public void 
readExternal(ObjectInput oi) throws IOException, ClassNotFoundException {
        System.out.println("Execute readExternal()");
        s = (String)oi.readObject();
        i = oi.readInt();
    }

/* writeObject()메쏘드로부터 호출이 되며, 변수들을 직렬화한다.*/
    public void 
writeExternal(ObjectOutput oo) throws IOException {
        System.out.println("Execute writeExternal()");
        oo.writeObject(s);
        oo.writeInt(i);
    }

/* 변수들의 값을 보여준다.*/
    public String toString() {
        return Integer.toString(i)+", "+s;
    }

    public static void main(String[] args) throws Exception {
        ObjectExternalizeTest oet1 = new ObjectExternalizeTest("Externalizable Test", 1000);

// 객체를 직렬화시켜 저장할 화일로부터 쓰기 스트림을 얻어온다.
        FileOutputStream out = new FileOutputStream("temp.txt");
        ObjectOutputStream oos = new ObjectOutputStream(out);

/* 객체를 직렬화 시킨다. 이경우 writeObject는 oet1의 writeExternal을호출하게되고writeExternal() 메쏘드의 코드들이 확장되어 실행이 된다. */
       oos.writeObject(oet1);

// 객체가 직렬화되어 저장된 파일로부터 스트림을 얻어온다.
        FileInputStream in = new FileInputStream("temp.txt");
        ObjectInputStream ois = new ObjectInputStream(in);
        ObjectExternalizeTest oet2 = (ObjectExternalizeTest)ois.readObject();

// 객체 직렬화 이전의 객체와 직렬화를 거친 뒤 다시 해제된 객체의 값을 출력한다.
        System.out.println(oet1);
        System.out.println(oet2);
// 만약 모든 과정이 제대로 이루어졌다면 oet1과 oet2객체는 같은 값을 가지고있을 것이다. 
    }
}

  • 실횅방법

    javac.exe ObjectExternalizeTest.java
    java.exe ObjectExternalizeTest

  • 실행결과

Execute writeExternal()
Execute readExternal()

1000, Externalizable Test
1000, Externalizable Test

 

 

애플릿 직렬화

객체직렬화는 애플릿을 직렬화하는데에도 사용이 되어진다. 이러한 경우 <Applet>태그는 클래스 파일 대신에 직렬화 된 객체 파일을 지정하게 되고, 브라우저는 이러한 태그를 만나면 직렬화된 애플릿을 해제하여 화면에 보여주게 된다. 즉, 그래픽 유저 인터페이스를 생성하기 위한 코드를 전혀 가지고 있지 않으면서도 완벽한 그래픽 유저 인터페이스를 가지게 되는것이다.

일반적으로 직렬화된 애플릿은 *.ser의 확장자를 가진다.

직렬화된 애플릿을 호출하는 html 파일은 다음과 같은 내용을 가진다.

<Applet Object="SerializableApplet.ser" Width=300 Height=300></Applet>
 

소켓을 통한 객체 직렬화

소켓을 생성한 뒤 스트림을 얻어서 ObjectInputStream과 ObjectOutputStream을 얻어서 사용한다면, 파일이 아닌 네트웍 너머의 다른 컴퓨터와도 객체를 직접 주고 받을수 있다.

소켓을 통해 직렬화 되어 쓰고 읽혀질 객체는 역시 Serializable을 구현하고 있어야 하며, 사용법은 파일에 쓰고 읽는것과 동일하다.

다음은 서버에서 이미지를 포함하고 있는 Serializable을 구현한 객체를 생성하여 소켓을 이용하여 클라이언트 애플릿에게 직렬화된 객체를 보내고, 애플릿에서는 이를 해제하여 화면에 나타내는 예제이다.

여기서 주의깊게 살펴보아야 할 점은 이미지를 직렬화하기위한 방법이다.

이미지는 Serializable을 구현하고 있지 않기 때문에 직접적으로 객체 직렬화에 사용되어질 수는 없다. 하지만, 자바 2에서는 배열을 직렬화에 사용할 수가 있고, 이미지는 각각의 픽셀들이 배열에 저장되어질수 있으므로 직접적으로 이미지를 직렬화 할 수는 없지만 배열을 사용하여 간접적으로나마 이미지를 객체 직렬화에 사용할 수가 있다.

  • 서버측 프로그램

 

import java.awt.*;
import java.awt.image.*;
import java.net.*;
import java.io.*;
import java.util.*;

public class ImageSerializeServer {

// 서버는 3434 포트로 임의로 설정 
    public static final int DEFAULT_PORT = 3434;
    protected int port;
    protected ServerSocket server;

// ImageSerializeServer를 실행한다.
    public static void main (String args[]) {
        new ImageSerializeServer ();
    }

/* 생성자. 서버의 소켓을 생성하고 클라이언트의 연결을 대기 */
    public ImageSerializeServer() {

// 포트를 지정(3434) 
        port = DEFAULT_PORT;
        this.port = port;

// 지정된 포트로 서버의 소켓을 생성한다.
        try {
            server = new ServerSocket (port);
        } catch (IOException e) {
            System.err.println ("Error creating server");
            System.exit (1);
        }

// 클라이언트의 연결을 대기 
        connectClient();
    }

/* 클라이언트로 부터의 연결을 기다리면서 무한루프를 돈다. */
    public void connectClient() {
        System.out.println ("Server Running");
        try {

// 클라이언트로 부터의 연결이 요청되면 클라이언트의 소켓을 생성 하고 이미지 픽셀을 클라이언트로 보내는 쓰레드를 생성한다. 
            while (true) {
                Socket client = server.accept();
                System.out.println ("Connection from: " + client.getInetAddress().getHostName());
                appletConnection c = new appletConnection (client);
 // 쓰레드 생성
            }
        } catch (IOException e) {
            System.err.println ("Exception listening");
            System.exit (1);
        }
        System.exit (0);
    }
}

/* 간단한 이미지를 이루는 픽셀을 만들어 이를 네트웍너머의 클라이언트로 보내는 클래스 */
class appletConnection extends Thread {

// 변수 선언 
    private final static int b = Color.blue.getRGB();
    private final static int g = Color.green.getRGB();
    private final static int r = Color.red.getRGB();
    protected ObjectOutputStream oos;

// 간단한 이미지 픽셀 
    protected int ipTemp[] = { b, b, b, b, b, b, b, b, b, b,
                                         b, b, b, b, b, b, b, b, b, b,
                                         b, b, g, g, g, g, g, g, b, b,
                                         b, b, g, g, g, g, g, g, b, b,
                                         b, b, g, g, r, r, g, g, b, b,
                                         b, b, g, g, r, r, g, g, b, b,
                                         b, b, g, g, g, g, g, g, b, b,
                                         b, b, g, g, g, g, g, g, b, b,
                                         b, b, b, b, b, b, b, b, b, b,
                                         b, b, b, b, b, b, b, b, b, b };

    public appletConnection (Socket client) {

// 객체 직렬화를 위한 출력 스트림을 생성 
        try {
            oos = new ObjectOutputStream(client.getOutputStream());
        } catch (IOException e) {
            try {
                client.close();
            } catch (IOException e2) { }
            System.err.println ("Unable to connect.");
            return;
        }
        start();
    }

    public void run () {

/* Serializable을 구현한 imagePixel객체를 생성해서 Vector에 집어넣는다. 따라서 이미지가 하나가 아닌 여러개 일지라도 객체 직렬화를 통해 전달하는것이 가능하다.*/
        Vector vt = new Vector();
        ImagePixel ip = new imagePixel(ipTemp);
        vt.addElement(ip);

// 이미지 픽셀들을 포함하고 있는 벡터를 객체 직렬화 시켜 스트림에 쓴다. 
        try {
         
   oos.writeObject(vt);
        } catch (Exception e) {
            System.out.println("Object write exception : " + e);
        }
    }
}

/* 객체의 직렬화를 위해 Serializable을 구현한 imagePixel 클래스 */
class imagePixel implements Serializable {
    protected int ip[];

// 생성자
    public imagePixel(int[] ip) {
        this.ip = ip;
    }
}

  •  실행방법

    javac.exe ImageSerializeServer.java
    java.exe ImageSerializeServer

    Server Running.....(서버가 대기하고 있는 상태)

 

 

  • 클라이언트측 프로그램

 

import java.applet.*;
import java.awt.*;
import java.awt.image.*;
import java.io.*;
import java.net.*;
import java.util.*;

public class ImageSerializeClient extends Applet {

// 기본 포트는 서버와 같이 3434로 하고 host는 localhost로 설정한다. 
    private int defaultPort = 3434;
    private String host = "localhost";
    private Socket s;
    private ObjectInputStream oin;

// 변서 선언
    Vector vt;
    Image myImage;

    public void init() {

/* 서버에 접속하여 소켓을 구한뒤에 객체 직렬화를 해제하기 위한 스트림을 얻는다. 이것은 마치 파일로부터 스트림을 얻는 과정과 비슷하다. */
        try{
            s = new Socket(host, defaultPort);
            oin = new ObjectInputStream(s.getInputStream());
        } catch(IOException e) { }
    }

    public void start() {

// 객체를 스트림으로 부터 읽어들여 직렬화를 해제한다. 
        try {
         
   vt = (Vector)oin.readObject();
        } catch(Exception e) {}

// 직렬화가 해제된 객체는 Vector에 들어가게 되고 Vector의 요소인 imagePixel을 구한다. 
        imagePixel myPixel = (imagePixel)vt.elementAt(0); 

// 이렇게 구해진 imagePixel의 이미지 픽셀을 가지고 실제 이미지를 생성 
        myImage = createImage(new MemoryImageSource(10, 10, myPixel.ip, 0, 10));
    }

// 생성된 이미지로 그림을 그린다. 
    public void paint(Graphics g) {
        g.drawImage(myImage, 0, 0, 100, 100, this);
    }
}

  •   실행방법

    javac.exe ImageSerializeClient.java
    java.exe ImageSerializeClient

     

 

  • 애플릿 포함 Html 내용

 

<HTML>
<TITLE>객체 직렬화</TITLE>
<BODY>

<APPLET CODE = "ImageSerializeClient.class" WIDTH = 100 HEIGHT = 100 ></APPLET>

</BODY>
</HTML>

 

 

  • 최종 결과 화면

 프로그램을 실행하기 위해서는 다음의 과정으로 실행하면 된다.

  1. 서버측 프로그램인 ImageSerializeServer.java를 컴파일하고, 서버 쪽에서 실행시면, "Server Running..."이라는 메시지가 나타나면서 서버측 프로그램이 제대로 작동을 하는 것이다.
  2. 다음으로는 클라이언트 애플릿 프로그램인 ImagSerializeClient.java를 컴파일한다.
  3. 브라우져를 실행시킨뒤에 클라이언트 애플릿 클래스를 실행시키기 위한 html파일이 있는 URL주소를 주소 입력란에 넣는다.
  4. 이때 서버프로그램이 돌아가는 곳과 애플릿을 위한 코드 및 html파일이 위치한 곳은 같은 Machine상에 있어야 한다.

참고로 이번 예제에서는 이미지 픽셀을 직접 그 값을 대입해서 이미지를 대신했는데, 실제 이미지를 이미지 픽셀로 바꾸는 기능이 자바에서 지원되고 있으므로 이를 간단히 소개토록 하겠다.

이미지는 여러개의 색깔을 표현하고 있는 픽셀들의 집합으로 볼 수 있고, 보다 정교하게 이미지를 다루는 작업을 하고 싶다면, 이미지의 픽셀들을 얻어서 조작을 해야한다. 그렇다면 어떻게 이미지의 픽셀들을 얻을 수 있을까? 그것은 PixelGrabber를 통해서 가능하다.

PixelGrabber는 이미지와 해당 이미지의 정보들을 기반으로 이미지 픽셀을 구해준다. 
다음은 실제로 이러한 것을 보여주는 코드이다.

 

.
.

int imagePixel[];                         // 이미지의 픽셀들을 저장할 배열
int imageWidth, imageHeight;       
// 이미지의 폭과 높이를 가지게 될 변수들
Image imageObject;                   
// 이미지 객체
.
.

/* imageObject로부터 픽셀들을 얻어서 imagePixel배열에 저장한다. */
private void getImagePixel() {

    imagePixel = new int[imageWidth * imageHeight];    // imagePixel을 초기화 한다. 
    PixelGrabber grabber = new PixelGrabber(imageObject, 0, 0, imageWidth, imageHeight, imagePixel, 0,                                                           imageWidth);

// grabber를 생성해서 픽셀들을 얻는다. 
    try {
        grabber.grabPixels();
    } catch(Exception e) {
        return; }

    if((grabber.status() & ImageObserber.ABORT) != 0)
        return;
}

.
.

 

 참고 문헌

  • Java Examples In a Nutshell (O'RELLY)
  • Java I/O (O'RELLY)

 

 

'자바 > 자바팁' 카테고리의 다른 글

내부클래스 설명  (1) 2010.11.10
XML DOM 자바로 이해하기  (0) 2010.11.07
objectinputstream 생성시 주의사항  (0) 2010.10.28
자바 날짜 관련 함수모음  (0) 2010.10.28
클래스패스 설정  (0) 2010.10.25

이거 때문에 한 2,3일 고생했다...아..힘들다..=='

ObjectInputStream과 ObjectOutputStream의 생성 위치를 바꿔보시기 바랍니다.

 

서버 쪽에서 ObjectInputStream, ObjectOutputStream 순으로 생성했다면,

 

클라이언트 쪽에서는 ObjectOutputStream, ObjectInputStream 순으로 생성해야만 합니다.

 

역도 성립하구요.

 

이유는 ObjectInputStream 생성자에서 쌍이 되는 ObjectOutputStream이 생성되어 있지 않다면,

 

block되기 때문입니다

'자바 > 자바팁' 카테고리의 다른 글

XML DOM 자바로 이해하기  (0) 2010.11.07
객체직렬화 설명  (0) 2010.10.29
자바 날짜 관련 함수모음  (0) 2010.10.28
클래스패스 설정  (0) 2010.10.25
Borderlayout 기본설명  (0) 2010.10.21

import java.util.Locale;
import java.util.Date;
import java.util.Calendar;
import java.util.GregorianCalendar;
import java.text.SimpleDateFormat;

public class TestDate 
{
 public static void main(String args[]) 
 {
  int     iWeekName;
  int     nMoveDay;
  int     nEndDay;
  long     lCurTime;
  long    lCurTimeTemp;
  long    lDiff;
  Date     curDate;
  Date     curDateTemp;
  String     strCurTime;
  Calendar    cal;
  GregorianCalendar  gcal;
  SimpleDateFormat  sdf;
  
  // ---------------------------------------------------------------
  // 1. 시스템의 밀리초 구하기(1000은 1초)
  // ---------------------------------------------------------------
  lCurTime = System.currentTimeMillis();
  System.out.println(lCurTime);
  
  // ---------------------------------------------------------------
  // 2. 현재 시각을 가져오기
  // ---------------------------------------------------------------
  curDate = new Date();  
  System.out.println(curDate);

 

  // ---------------------------------------------------------------
  // 3. 포맷을 지정해서 날짜 구하기
  // ---------------------------------------------------------------
  sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'.0Z'", Locale.KOREA); 
  curDate = new Date();                                 
  strCurTime = sdf.format(curDate);
  System.out.println(strCurTime);

                                    

  // ---------------------------------------------------------------
  // 4. Date를 Calendar로 맵핑하기
  // ---------------------------------------------------------------
  curDate = new Date();
  cal = Calendar.getInstance();
  cal.setTime(curDate);
  System.out.println(cal.get(Calendar.YEAR) + "년" 
      + (cal.get(Calendar.MONTH) + 1) + "월"
      +  cal.get(Calendar.DAY_OF_MONTH) +"일");
  
  // ---------------------------------------------------------------
  // 5-1. 날짜를 n일 만큼 이동시키기
  // ---------------------------------------------------------------
  curDate = new Date();
  lCurTime = curDate.getTime();
  nMoveDay = 1;
  lCurTime = lCurTime + (24*60*60*1000) * nMoveDay;
  System.out.println(lCurTime);
  
  // ---------------------------------------------------------------
  // 5-2. 날짜를 n일 만큼 이동시키기                           
  // ---------------------------------------------------------------
  cal = Calendar.getInstance ( );                                                              
  cal.add(cal.MONTH, -2);    // 2달 전         
  cal.add(cal.DAY_OF_MONTH, 2);  // 2일 후
  cal.add(Calendar.YEAR, 2);   // 2년 후
  System.out.println(cal.get(Calendar.YEAR) + "년" 
      + (cal.get(Calendar.MONTH) + 1) + "월"
      +  cal.get(Calendar.DAY_OF_MONTH) +"일");
  
  // ---------------------------------------------------------------
  // 6-1. 해당하는 달의 마지막 일 구하기
  // ---------------------------------------------------------------
  gcal = new GregorianCalendar();
  nEndDay = gcal.getActualMaximum((gcal.DAY_OF_MONTH));             
  System.out.println(nEndDay);

 

  // ---------------------------------------------------------------
  // 6-2. 해당하는 달의 마지막 일 구하기
  // ---------------------------------------------------------------
  cal = Calendar.getInstance ( );            
  cal.set(2009, 1, 1);    //월은 0부터 시작
  nEndDay = cal.getActualMaximum(Calendar.DATE);
  System.out.println(nEndDay);
  
  // ---------------------------------------------------------------
  // 7. 요일 구하기                                              
  // ---------------------------------------------------------------
  cal= Calendar.getInstance ( );                    
  iWeekName = cal.get(Calendar.DAY_OF_WEEK); // 1이면 일요일, 2이면 월요일... 7이면 토요일        
  
  // ---------------------------------------------------------------
  // 8. 날짜 유효 검사
  // ---------------------------------------------------------------
  //String result = "";                                         
  sdf = new SimpleDateFormat("yyyyMMdd", Locale.KOREA);                                                 
  // 일자, 시각해석을 엄밀하게 실시할지 설정함  true일 경우는 엄밀하지 않는 해석, 디폴트       
  sdf.setLenient (false);                                           
  try {                                            
   curDate = sdf.parse("20090229"); 
  }
  catch(java.text.ParseException e) {             
   System.out.println("Error");
  }
  
  // ---------------------------------------------------------------
  // 9. 두 날짜 비교하기                                 
  // ---------------------------------------------------------------
  curDate = new Date();
  curDateTemp = new Date();

  lCurTime = curDate.getTime();                     
  lCurTimeTemp = curDateTemp.getTime();                     
                                                     
  lDiff = lCurTimeTemp - lCurTime;
  cal= Calendar.getInstance();          
  cal.setTimeInMillis(lDiff);   // 결과값이 몇시간, 몇일 차이나는지 확인하기 위해선.
 }
}

'자바 > 자바팁' 카테고리의 다른 글

객체직렬화 설명  (0) 2010.10.29
objectinputstream 생성시 주의사항  (0) 2010.10.28
클래스패스 설정  (0) 2010.10.25
Borderlayout 기본설명  (0) 2010.10.21
에코 클라이언트 과정  (0) 2010.10.21

클래스패스와 환경 변수, 그것이 알고 싶다.
김세곤
2001년 4월 17일

서론
초보 자바 프로그래머를 괴롭히는 큰 문제 중에 그놈의 클래스패스는 빠지지 않는다. 클래스패스는 사실 이렇게 하나의 글로 설명하기조차 매우 부끄러운 사소한 것인데, 초보 자바 프로그래머에게는 절대 사소하지 않은 것이 현실이다. 더군다나, 가슴 아프게도 대부분의 자바 관련 서적은 클래스패스에 지면을 할애할 만한 형편도 안되고, 대부분의 저자들이 별것도 아닌 것에 공들여 설명하려 하지 않기 때문에, 당연히 클래스패스에 대해서는 많은 독자들이 제대로 이해하지 못한 채 끙끙댄다. 한편으로는, 이것은 기초를 제대로 다지지 않은 독자들의 책임이 크다. 클래스패스를 잘 설정해서 자바 프로그램을 컴파일하고 실행하는 기술은 자바 언어의 첫번째 단추이고, 이 내용은 대부분의 자바 책(자바 관련 서적이 아닌 자바 그 자체를 설명하는 책)에서는 설명이 되어 있기 때문이다.

필자는 JSP Bible이라는 책을 집필하고 독자로부터 많은 질문을 받았는데, 거짓말 보태지 않고 50% 이상의 독자가 클래스패스로 골머리를 썩고 있었다. JSP로 웹 개발을 시도하려 한다면 자바 언어의 첫번째 단추인 클래스패스와 컴파일 정도에는 문제가 없어야 하는데, 아쉽게도 이 첫번째 단추를 못 껴서 진도를 못 나가다니 가슴 아픈 현실이 아닐 수 없다.

가장 좋은 방법은 역시 자바 언어를 제대로 공부하고 나서 JSP, Servlet, EJB 등 그 다음 단계의 공부를 진행하는 것이다. 그런데, 이유야 어찌 됐건, 대부분의 독자들은 자바에 대한 기초없이 응용 단계인 JSP, Servlet, EJB 등으로 마구 앞질러 나간다. 그리고, 막히면 다시 자바 언어를 체계적으로 공부하지 않고, 끙끙거리며 당장 진도를 나가려고 발버둥친다. 이 대목에서 찔리는 독자 여러분들이 분명히 많을 것이다.

솔직히 말해서, 필자는 JSP Bible 집필 당시에 자바의 생기초라 할 수 있는 클래스패스 설정하기 및 컴파일하기 등에 대해서 이렇게 많은 독자들이 모르리라고 예상하지 못했다. 그리고, 끊임없이 클래스패스에 대한 질문을 받으면서, 같은 답변을 계속 하다보니 이제는 클래스패스에 대한 질문만 만나면 경기가 난다.

필자는 이 글로 더 이상 클래스패스나 환경 변수 혹은 컴파일하기 등에 관련된 질문이 없기를 간절히 기대한다. 더 나아가서 많은 자바를 공부하고자 하는 개발자들이 제발 자바 언어에 대해서는 탄탄하게 기초를 다졌으면 한다.

클래스 이름과 패키지
갑돌이가 A.java 라는 자바 프로그램을 만들었다고 하자. 그리고, 자신의 컴퓨터 C 드라이브의 myClass라는 폴더에 A.java를 컴파일한 A.class를 넣어 두었다고 치자. 그리고는 A.class를 잘 사용하다가 어느날 똑순이가 만든 클래스를 사용할 일이 있어서 똑순이의 클래스들을 건네 받았는데, 그 중에 A.class라는 같은 이름의 클래스가 있다는 사실을 알았다. 갑돌이는 갑자기 답답해졌다. 자, 어찌 하면 갑돌이가 만든 클래스 A와 똑순이가 만든 클래스 A를 구별할 수 있을까?

해답은 바로 패키지이다.

갑돌이의 회사는 gabdol이고, 똑순이의 회사는 ddogsun이므로, 이 이름을 클래스 이름 A에 붙여쓰면 구별이 될 수가 있는 것이다. 갑돌이의 클래스 A의 이름을 gabdol.A로 똑순이의 클래스 A의 이름을 ddogsun.A로 사용하면 고민이 해결된다는 말이다. 그런데, 덕팔이의 회사 이름이 갑돌이의 회사 이름과 같다면 또 이름이 충돌하게 된다. 그렇다면 전세계에서 유일한 이름을 클래스 이름 앞에 붙여주면 아주 간단해진다. 전세계에서 유일한 이름으로는 머리속에 팍 떠오르는 것이 회사의 도메인 명이다. 갑돌이네 회사가 www.gabdol.com이라는 도메인 이름을 소유하고 있다면, 갑돌이네 회사에서 만드는 모든 클래스의 이름을 com.gabdol.A와 같은 식으로 만들면 문제가 해결된다. 바로, 이 com.gabdol이 클래스 A의 패키지 이름이 되는 것이다. 만일, 갑돌이네 회사가 두 개의 자바 소프트웨어를 개발했는데, 둘 다 A.java라는 클래스가 있다면 이를 또 구별해야 한다. 이런 경우라면 com.gabdol 뒤에 임의로 갑돌이네 회사가 알아서 패키지 이름을 만들면 된다. 두 개의 소프트웨어 이름이 sw1, sw2라고 하면 com.gabdol.sw1.A, com.gabdol.sw2.A 식으로 클래스 이름을 사용하는 것이다.

이렇게 패키지를 사용하게 되면 이름 충돌의 문제도 해결할 수 있을 뿐만 아니라, 클래스를 분류하기도 쉽다. 예를 들어, 갑돌이네 회사에서 고객에 관련된 클래스들의 패키지 이름을 com.gabdol.client로 하고, 상품에 관련된 클래스들의 패키지 이름을 com.gabdol.product로 정하면 클래스들의 성격을 패키지 이름만 보고도 쉽게 파악할 수 있는 것이다.

클래스의 위치
이제, 갑돌이가 만드는 클래스의 이름이 com.gabdol.sw1.A로 정해졌다고 하자. 그렇다면 이 클래스는 어디에 위치해야 할까? com.gabdol.sw2.A와 같은 폴더에 존재하면 안 되므로, 패키지 이름에 따라 클래스의 위치가 유일하게 구별될 수 있어야 한다. 갑돌이는 자신의 클래스르 모두 C:\myClass라는 폴더에 두고 있으므로, com.gabdol.sw1.A 클래스는 C:\myClass\com\gabdol\sw1 폴더에 두고, com.gabdol.sw2.A 클래스는 C:\myClass\com\gabdol\sw2 폴더에 두면 두 클래스가 충돌하지 않을 것이다. 이렇게 하면, 소스 파일과 컴파일된 클래스 파일이 다음처럼 위치하게 된다.

C:\myClass\com\gabdol\sw1\A.java
C:\myClass\com\gabdol\sw1\A.class
C:\myClass\com\gabdol\sw2\A.java
C:\myClass\com\gabdol\sw2\A.class


이제, 패키지의 이름에 따른 클래스의 위치에 대해서 감이 오는가?

패키지 선언과 임포트
com.gabdol.sw1.A 클래스의 패키지는 com.gabdol.sw1이고 com.gabdol.sw2.A 클래스의 패키지는 com.gabdol.sw2이다. 이 정보는 당연히 두 소스 코드 내에 들어가야 한다. 방법은 간단하다. C:\myClass\com\gabdol\sw1\A.java 코드의 첫머리에 다음의 한 줄만 쓰면 된다.


package com.gabdol.sw1;


여기서, com.gabdol.sw1.A 클래스가 com.gabdol.sw2 패키지에 들어있는 클래스들을 사용하고 싶다고 하자. 어떻게 해야 할까? 방법은 두 가지이다.

첫번째는 클래스의 이름을 완전히 써 주는 것이다. 다음처럼 말이다.


package com.gabdol.sw1;
...
com.gabdol.sw2.B b = new com.gabdol.sw2.B();
...


이렇게 하면 클래스 B가 com.gabdol.sw2 패키지 내에 있는 것이 드러나므로 컴파일 시에 문제가 없다.

두번째는 패키지를 임포트(import)하는 것이다. 다음처럼 하면 된다.


package com.gabdol.sw1;
import com.gabdol.sw2.*;
...
B b = new B();
...


위의 코드의 import com.gabdol.sw2.*; 부분은 com.gabdol.sw1.A 클래스가 com.gabdol.sw2 패키지 내의 클래스들을 사용하겠다는 뜻이다. 이렇게 하면 자바 컴파일러가 B b = new B(); 부분을 컴파일하면서 B라는 클래스를 com.gabdol.sw1.A가 속해있는 com.gabdol.sw1 패키지와 임포트한 com.gabdol.sw2 패키지 내에서 찾게 된다. 만일, com.gabdol.sw1과 com.gabdol.sw2 패키지에 모두 B라는 이름의 클래스가 있다면 당연히 컴파일러는 어떤 것을 써야할지 모르므로 에러를 낸다. 이 경우에는 다음처럼 하는 수밖에 없다.


package com.gabdol.sw1;
import com.gabdol.sw2.*;
...
com.gabdol.sw1.B b1 = new com.gabdol.sw1.B();
com.gabdol.sw2.B b2 = new com.gabdol.sw2.B();
...


import com.gabdol.sw1.*과 같이 *를 사용하면 com.gabdol.sw1 패키지 내의 모든 클래스를 사용하겠다는 뜻이고, com.gabdol.sw1.B 클래스만 사용한다면 import com.gabdol.sw1.B라고 명시하면 된다.

클래스패스
이제, 갑돌이는 C:\myClass\com\gabdol\sw1\A.java 파일을 컴파일하려 한다. 갑돌이는 C:\myClass\com\gabdol\sw1\ 폴더로 이동해서 다음처럼 할 것이다.

javac A.java


결과는 당연히 클래스 B를 찾을 수 없다는 에러 메시지이다. com.gabdol.sw1.B 클래스가 컴파일된 바이트코드인 B.class가 도대체 어디에 있는지 자바 컴파일러로서는 알 길이 없기 때문이다. 이 때 필요한 것이 클래스패스이다. 갑돌이가 다음처럼 하면 컴파일이 성공적으로 이루어진다.

javac -classpath C:\myClass A.java


여기에 "-classpath C:\myClass" 부분은 자바 컴파일러에게 C:\myClass폴더를 기준으로 패키지를 찾으라는 뜻이다. 즉, com.gabdol.sw1.B 클래스는 C:\myClass 폴더를 시작으로 C:\myClass\com\gabdol\sw1 폴더에서 찾고, com.gabdol.sw2.B 클래스는 C:\myClass\com\gabdol\sw2 폴더에서 찾는 것이다.

그런데, 갑돌이는 돌쇠에게 건네 받은 클래스들을 C:\otherClass 라는 폴더에 저장하고 있었는데, 이 중에 일부 클래스를 A.java에서 사용할 일이 생겼다. 돌쇠가 건네 준 클래스는 com.dolsse.ddol 이라는 패키지에 포함되어 있고, 이 클래스들은 C:\otherClass\pr\com\dolsse\ddol 폴더에 있다면, 돌쇠가 준 클래스들은 C:\otherClass\pr 폴더를 시작으로 찾아야 한다. 따라서, 돌쇠의 클래스를 사용하는 A.java 파일을 컴파일하기 위해서는 다음처럼 해야 한다.

javac -classpath "C:\myClass;C:\otherClass\pr" A.java


이렇게 하면 C:\myClass 폴더에 저장되어 있는 com.gabdol.sw1.A, com.gabdol.sw2.B 클래스와 C:\otherClass\pr 폴더에 저장되어 있는 com.dolsse.ddol.* 클래스들을 자바 컴파일러가 제대로 찾을 수 있게 되는 것이다.

A.java를 컴파일해서 얻어진 클래스를 직접 실행하려면 자바 가상 머신을 구동해야 하는데, 이 때에서 당연히 A.class가 사용하는 다른 클래스를 자바 가상 머신이 찾을 수 있어야 한다. 따라서, 역시 컴파일할 때와 마찬가지로 클래스패스가 필요하다. A.class를 실행하려면 다음처럼 해야 한다.

java -classpath "C:\myClass;C:\otherClass\pr" com.gabdol.sw1.A


실행할 때에는 실행하고자 하는 클래스의 패키지 이름까지 명시해야 한다. 왜냐하면, 자바 가상 머신이 클래스를 실행할 때에는 지정된 이름이 패키지 이름을 포함한 것으로 생각하기 때문이다. 만일, 다음처럼 한다면,

java -classpath "C:\myClass;C:\otherClass\pr" A


자바 가상 머신은 C:\myClass\A.class 혹은 C:\otherClass\pr\A.class를 찾으려고 할 것이다.

클래스패스란 여러 폴더에 산재한 클래스들의 위치를 지정해서 패키지에 따라 클래스를 제대로 찾을 수 있게 해 주는 값이다.

환경 변수
매번, 컴파일할 때와 실행할 때에 classpath 옵션에 클래스패스를 명시하는 것을 불편하다. 한 번의 설정으로 이런 문제를 해결하기 위해서 환경 변수라는 것을 사용한다. 많은 독자들이 역시 환경 변수가 무엇인지, 어떻게 설정하는지 모르는 경우가 많으므로 여기서 잘 정리해 보겠다.

환경 변수는 말 그래로 변수에 어떤 값을 저장시키되 이를 환경에 저장하는 것이다. 환경에 저장한다는 말은 그 환경 내의 모든 프로그램이 그 변수의 값을 알 수 있게 된다는 뜻이다. 여러분이 윈도우 95/98/Me의 도스창 혹은 윈도우 NT/2000의 명령창을 실행시키면, 그 명령창 내에서 각종 프로그램을 구동할 수가 있는데, 이 때, 그 명령창의 환경에 변수 값을 설정하면 그 명령창 내에서 구동되는 프로그램들은 환경 변수 값을 얻을 수가 있게 된다.

명령창 내에서 환경 변수를 설정하는 방법은 간단하다.

C:\> SET 변수이름=값


혹은

C:\> SET 변수이름="값"


처럼 하면 된다. 여러분의 명령창 또는 도스창을 띄워서 다음처럼 해 보자.

C:\> SET myname="Hong Gil Dong"


이렇게 하면 환경 변수 myname에는 "Hong Gil Dong"이 설정된다. 제대로 설정이 되었는지 확인해 보기 위해서는 다음처럼 하면 된다.

C:\> echo %myname%
"Hong Gil Dong"


첫 번째 줄은 환경 변수 myname의 값을 출력하라는 명령이고, 두 번째 줄은 그 결과가 나온 것이다.

환경 변수는 메모리가 허락하는 양만큼 설정할 수 있다. 즉, 다음처럼 복수 개의 환경 변수를 설정할 수 있다는 말이다.

C:\> SET myname="Hong Gil Dong"

C:\> SET yourname="Kim Gap Soon"


자, 이번에는 이 명령창을 종료하고 다른 명령창을 띄워 보자. 그리고, 다음처럼 myname 환경 변수의 값을 출력해 보자.

C:\> echo %myname%
%myname%


두 번째 줄과 같은 결과가 나온다. 조금 전에 설정했던 값을 온데간데 없다. 왜 그럴까? 이유는 간단하다. 환경 변수는 그 명령창 내에서만 존재하기 때문이다. myname 변수를 설정한 명령창을 종료했기 때문에, 이와 동시에 myname 환경 변수가 사라진 것이다. 또, myname 변수를 설정한 명령창을 종료하지 않았다고 해도, 다른 명령창에서는 myname 변수 값을 공유하지 않는다. 오로지, 변수를 설정한 그 명령창 내에서만 그 변수는 의미가 있게 되는 것이다.

이렇게 환경 변수가 명령창 내에서만 의미가 있기 때문에, 온갖 프로그램에서 공유하기 위해서는 명령창에서 환경 변수를 설정하는 방법 외에 다른 방법을 사용해야 한다. 윈도우 95/98/Me라면 autoexec.bat 파일을 사용하고, 윈도우 NT/2000이라면 글로벌 환경 변수를 설정하는 별도의 방법이 존재한다.

우선, 윈도우 NT/2000이라면 다음처럼 한다.

1. 바탕화면의 "내 컴퓨터"를 오른쪽 클릭한다.

2. "고급" 메뉴를 택한다.

3. 가운데 "환경 변수" 메뉴를 택한다.

4. 두 창이 나오는데 위 쪽은 특정 로그인 사용자에 대한 환경 변수를 설정하는 것이고, 아래 쪽은 모든 사용자에게 적용되는 환경 변수를 설정하는 것이다. 관리자(Administrator) 권한을 갖고 있다면 아래 시스템 변수의 값을 설정할 수 있다. 상황에 맞게 선택하면 된다.

5. 버튼이 "새로 만들기", "편집", "삭제"가 있는데, 새로운 환경 변수를 만드는 경우에는 "새로 만들기"를 클릭하고, 기존 변수 값을 수정하고자 한다면 "편집"을 누른다. "삭제"는 물론 변수를 아예 없애는 경우에 클릭한다.

6. "새로 만들기"나 "편집"을 클릭하면 변수 이름과 값을 설정하게 되어 있는데 여기에 원하는 이름과 값을 입력하면 된다.


윈도우 95/98/Me라면, autoexec.bat 파일을 편집기로 열어서 "set 변수이름=값"을 아무데나 추가하면 된다.

자, 이제는 글로벌 환경 변수에 클래스패스 변수를 설정해 보자.

윈도우 NT/2000이라면 위의 단계를 차례로 실행한 후에, 이미 CLASSPATH 혹은 classpath 변수가 있다면 이를 편집하고, 없다면 새로 만들기를 하면 된다. 지금까지 예로 든 대로라면 다음의 값을 입력하면 된다.

C:\myClass;C:\otherClass\pr

<김상욱 주: 이렇게만 하면 한 디렉토리에서 방금 만들어진 클래스파일 즉 현재 디렉토리의 클래스파일을 찾지 못하기 때문에 현재 디렉토리를 나타내는 . 을 추가한다.>

<즉, .\C:\myClass;C:\otherClass\pr 이렇게 입력하여야 합니다.>


윈도우 95/98/Me라면 autoexec.bat 파일을 열어서, 다음의 한 줄을 추가한다.

set CLASSPATH="C:\myClass;C:\otherClass\pr"

<김상욱 주: 마찬가지 이유로 set CLASSPATH=".;C:\myClass;C:\otherClass\pr" 로 입력되어야 합니다.>


그렇다면, 자바 컴파일러와 자바 가상 머신은 이렇게 설정한 환경 변수와는 어떤 관계가 있을까? 자바 컴파일러와 자바 가상 머신은 -classpath 옵션을 지정하지 않으면 환경 변수 CLASSPATH 혹은 classpath의 값을 시스템으로부터 얻어서 이를 클래스패스로 사용한다. 클래스패스의 값을 환경 변수로부터 얻을 수 있도록 애초부터 만들어진 것이다.

jar 파일과 클래스패스
jar 파일은 클래스들을 묶어 놓은 것이다. 즉, 갑돌이는 자신이 만든 com.gabdol 이하의 모든 클래스를 하나의 파일인 gabdol.jar 파일로 묶을 수가 있다. 이렇게, gabdol.jar 파일을 C:\gg\cc 폴더 아래에 두었다면 이 압축 파일 내의 클래스를 역시 자바 컴파일러와 자바 가상 머신이 찾을 수 있도록 해야 한다. 이 때에는 파일 이름까지 포함해서 클래스패스에 추가해야 한다. 다음처럼 말이다.

set CLASSPATH=C:\gg\cc\gabdol.jar;C:\myClass;C:\otherClass\pr


마치며


지금까지 자바 프로그램의 가장 기초라고 할 수 있는 클래스패스에 대해서 알아 보았다. 필자는 이 글로 더 이상의 클래스패스에 대한 질문이 사라졌으면 하는 바램이다. 독자 여러분이 클래스패스를 고민하는 것도 시간 낭비이고, 저자가 이에 대해서 일일이 답변해 주는 것도 시간 낭비라고 생각된다. 만일, 독자 여러분이 클래스를 찾지 못한다는 에러를 만난다면 100% 클래스패스 설정 잘못이므로, 이 글을 잘 읽어서 근본적으로 클래스패스에 대해서 이해한 후에 문제를 해결하는 노력을 기울이도록 하자.

'자바 > 자바팁' 카테고리의 다른 글

objectinputstream 생성시 주의사항  (0) 2010.10.28
자바 날짜 관련 함수모음  (0) 2010.10.28
Borderlayout 기본설명  (0) 2010.10.21
에코 클라이언트 과정  (0) 2010.10.21
JDialog 고급활용  (0) 2010.10.18

GUI 를 이루는 기본 요소 네 가지는 각각 컴포넌트, 컨테이너, 레이아웃 관리자, 그리고 그래픽스라는 것을 처음에 언급한 바 있다. 우리는 이미 컴포넌트와 컨테이너에 대해 공부하였으며, 이제부터는 레이아웃 관리자 (Layout Manager) 에 대해 공부하도록 하자. 레이아웃(layout)이란 컴포넌트들을 컨테이너 상에 어떻게 배치할 것인지를 결정하는 것을 말한다.

많은 레이아웃 관리자가 있지만, 대표적인 것들로 다음의 다섯 가지를 들 수 있다.

  • BorderLayout - 동, 서, 남, 북, 중앙으로 배치
  • FlowLayout - 위에서 아래로, 왼쪽에서 오른쪽으로 배치
  • GridLayout - 동일 크기를 갖는 격자에 배치
  • GridBagLayout - 다른 크기를 가질 수 있는 격자에 배치
  • BoxLayout - 수평 또는 수직 방향으로 배치

우리는 이미 BorderLayout 과 FlowLayout 에 대해 어느 정도 공부한 바가 있다. BorderLayout 은 JFrame 에서, FlowLayout 은 JPanel 에서 각각 기본적으로 사용되었던 것을 기억할 것이다.

이미 배운 두 레이아웃을 간단히 정리해보고, GridLayout, GridBagLayout, 그리고 BoxLayout 에 대해 공부해보자. 




BorderLayout

BorderLayout은 컴포넌트를 동, 서, 남, 북, 중앙에 각각 배치하며, 각각의 위치에는 최대 한 개의 컴포넌트만 둘 수 있다. 만일 한 위치에 다수 개의 컴포넌트를 두려면 앞에서 배운 것처럼 해당 위치에 JPanel 을 두고, JPanel 상에 컴포넌트들을 배치하는 우회적 방법을 사용하면 된다.

컴포넌트들을 컨테이너 상에 위치하게 하려면 add() 메소드를 사용한다.

  • void add(Component c, int index)

처음 파라미터인 Component 는 우리가 여태까지 배운 라벨, 버튼, 텍스트필드 등 어느 컴포넌트든지 올 수 있고, 두번째 파라미터인 int 에는 EAST, WEST, SOUTH, NORTH, CENTER가 올 수 있다. 이 값들은 BorderLayout 에 포함되어 있으므로 BorderLayout.EAST 등과 같이 표기해야 한다.

BorderLayout 은 JFrame 에서 기본적으로 사용된다. 만일 JPanel 에서 BorderLayout 을 사용하려고 한다면 다음과 같이 setLayout() 메소드를 호출해야 한다.

BorderLayout layout = new BorderLayout();
setLayout(layout);

즉 BorderLayout 클래스의 인스턴스를 생성한 다음, 그것을 setLayout() 메소드의 파라미터로 넘겨주면 되는 것이다. setLayout() 은 Container 클래스가 가지고 있는 메소드이며, 따라서 그것의 하위 클래스인 JFrame, JPanel 등 임의의 컨테이너에서도 상속성에 따라 사용 가능하다.

다음 예제는 JPanel 상에 BorderLayout 을 설정하고, 동, 서, 남, 북, 중앙에 각각 "East", "West", "South", "North", "Center" 라는 라벨을 배치하는 것이다. 4번째 강의록, 즉 JLabel 에 대해 배울 때 이미 작성했던 프로그램이며, 단지 JFrame 대신 JPanel 상에 라벨을 배치했다는 점이 다르다.

import javax.swing.*;
import java.awt.*;

public class Test {
 public static void main(String[] args) {
	MyFrame f = new MyFrame();
 }
}

class MyFrame extends JFrame {
 MyFrame() {
	setTitle("My Frame");
	setSize(300, 200);
	makeUI();
	setVisible(true);
 }
 private void makeUI() {
	/* create a panel and set the layout */
	JPanel p = new JPanel();
	p.setLayout(new BorderLayout());

	JLabel le, lw, ls, ln, lc;
	le = new JLabel("East");
	lw = new JLabel("West");
	ls = new JLabel("South");
	ln = new JLabel("North");
	lc = new JLabel("Center");

	le.setHorizontalAlignment(JLabel.CENTER);
	lw.setHorizontalAlignment(JLabel.CENTER);
	ls.setHorizontalAlignment(JLabel.CENTER);
	ln.setHorizontalAlignment(JLabel.CENTER);
	lc.setHorizontalAlignment(JLabel.CENTER);

	p.add(le, BorderLayout.EAST);
	p.add(lw, BorderLayout.WEST);
	p.add(ls, BorderLayout.SOUTH);
	p.add(ln, BorderLayout.NORTH);
	p.add(lc, BorderLayout.CENTER);

	/* attach panel to the frame */
	add(p, BorderLayout.CENTER);
 }
}


이 프로그램의 실행 결과는 아래와 같으며 4번째 강의록에서 소개한 결과와 동일함을 알 수 있다. 즉 우리는 기본적으로 FlowLayout 를 따르는 JPanel 에 대해 의도적으로 BorderLayout 을 사용하게 한 것이다.

 

'자바 > 자바팁' 카테고리의 다른 글

자바 날짜 관련 함수모음  (0) 2010.10.28
클래스패스 설정  (0) 2010.10.25
에코 클라이언트 과정  (0) 2010.10.21
JDialog 고급활용  (0) 2010.10.18
JFrame 관련 팁  (0) 2010.10.18
1.Socket 생성자에 서버의IP와 서버의 동작 포트값을(10001)을 인자로 넣어 생성한다.소켓이 성공적으로 생성되었다면 서버와 접속이 성공적으로 되었다는 것을 의미한다.
2.생선된 socket으로부터 InputStream 과 OutputStream을 구한다.
3.InputStream 은BufferReader 형식으로 변환하고 OutputStream은 PrintWriter 형식으로 변환한다.
4.키보드로 부터 한줄 씩 입력닫는 BufferReader객체를 생성한다.
5.키보드로부터 한줄을 입력받아 PrintWriter에 있는 Printl()메소드를 이용해서 서버에게 전송한다.
6.서버가 다시 봔환하는 문자열을 BufferReader에 있는 readLine()메소드를 이용해서 읽어 들인다.
읽어들인 문자열은 화면에 출력한다.
7.4,5,6을 키보드로부터 quit문자열을 입력받을 때까지 반복한다.
8.키보드로부터 quit문자열이 입력되면 IO객체와 소켓의 close()메소드를 호출한다.


'자바 > 자바팁' 카테고리의 다른 글

클래스패스 설정  (0) 2010.10.25
Borderlayout 기본설명  (0) 2010.10.21
JDialog 고급활용  (0) 2010.10.18
JFrame 관련 팁  (0) 2010.10.18
action event 를 쓸때 컴파일시 inner class 에러 나는 경우  (0) 2010.10.18

잘사용하시면, 간편하게 훌륭한 대화상자들을 만들어 낼수 있습니다.

끝부분에 커스터마이징 부분을 잘 보시길 바랍니다.

 

BEYOND THE BASICS OF JOPTIONPANE

The Swing component set in J2SE includes a special class for creating a panel to be placed in a popup window. You can use this panel to display a message to the user and to get a response from the user. The panel presents content in four areas, one each for an icon, message, input, and buttons. You don't need to use any of the areas, although you would typically present at least a message and a button.

The icon area is for the display of a javax.swing.Icon. The icon is meant to indicate the type of message being displayed. There are default icons provided by the specific look and feel you use. The default images for these icons are specified through a property setting to the look and feel. The property setting points to the appropriate resource for each message type:

  • OptionPane.informationIcon
  • OptionPane.warningIcon
  • OptionPane.errorIcon
  • OptionPane.questionIcon

You don't have to change the default properties, though you could with a call to UIManager.put(property name, new resource). With the defaults in place, you simply identify the appropriate message type with a constant of JOptionPane:

  • JOptionPane.PLAIN_MESSAGE
  • JOptionPane.INFORMATION_MESSAGE
  • JOptionPane.WARNING_MESSAGE
  • JOptionPane.ERROR_MESSAGE
  • JOptionPane.QUESTION_MESSAGE

The plain variety maps to no icon, while the others use a default icon from the look and feel.

The second area of the JOptionPane is the message area. This is typically used to display a text message. However, you could show any number of objects here. The message type determines how a message is displayed.

The input area is next. Here is where you can get a response to a message from the user. To prompt for a response you can use a free form text field, a combo box, or even a list control. For Yes/No type prompts, you would instead use the button area, described next.

Last is the button area, another response-like area. When a user selects a button it signals the end of usage for the JOptionPane. Default sets of button labels are available. (As is the case for icons, these too come from property settings, such as OptionPane.yesButtonText.) You can also provide your own button labels. You can display any number of buttons (including no buttons) with any set of labels. The predefined button label is localized for several languages: English (en), German (de), Spanish (es), French (fr), Italian (it), Japanese (ja), Korean (ko), Swedish (sv), and two varieties of Chinese (zh_CN / zh_TW). If you provide your own labels, you need to localize them yourself.

The predefined sets of buttons are defined by a set of JOptionPane constants:

  • DEFAULT_OPTION
  • YES_NO_OPTION
  • YES_NO_CANCEL_OPTION
  • OK_CANCEL_OPTION

Default maps to a single OK button.

Using JOptionPane

The JOptionPane class contains seven constructors, each returns a component that you can place anywhere. However, more typically you would use a factory method that creates the component and automatically places it inside a JDialog or JInternalFrame. There are eight of these factory methods (two varieties of four methods) that JOptionPane provides:

  • show[Internal]MessageDialog
  • show[Internal]ConfirmDialog
  • show[Internal]InputDialog
  • show[Internal]OptionDialog

The Internal variety creates a JOptionPane and shows it in a JInternalFrame. The other variety creates a JOptionPane and shows it in a JDialog. This tip only discusses the dialog variety.

The message dialog is meant to show a message that the user confirms by selecting the button (or closing the dialog). The message dialog is not meant to return a value.

In its simplest case, you would use code like the following to show a message dialog:

   JOptionPane.showMessageDialog(
      component, "Hello, World");

The system centers the dialog over the component argument.

There are two other variations for showing a message dialog. These allow you to customize the window title, customize the message type (to use the default icon), and set a customized icon:

  • showMessageDialog(Component parentComponent,
    Object message,
    String title,
    int messageType)
  • showMessageDialog(Component parentComponent,
    Object message,
    String title,
    int messageType,
    Icon icon)

The method for showing a confirm dialog has four variations:

  • showConfirmDialog(Component parentComponent,
    Object message)
  • showConfirmDialog(Component parentComponent,
    Object message,
    String title,
    int optionType)
  • showConfirmDialog(Component parentComponent,
    Object message,
    String title,
    int optionType,
    int messageType)
  • showConfirmDialog(Component parentComponent,
    Object message,
    String title,
    int optionType,
    int messageType,
    Icon icon)

The simplest variation brings up a dialog with a question icon. The dialog displays Select an Option as the title, has Yes, No, and Cancel as button labels.

   JOptionPane.showConfirmDialog(component, "Lunch?");

If you don't like the defaults, you can change them by using one of the other methods. You'll find that everything is changeable with JOptionPane.

Unlike the message dialog, the confirm dialog does require a return value. Here, you really do want to know which button the user selected. The confirm dialog returns an integer, indicated by one of the following constants of the JOptionPane class:

  • CANCEL_OPTION
  • CLOSED_OPTION (used when the used closed popup window)
  • NO_OPTION
  • OK_OPTION
  • YES_OPTION

Passing in a setting of OK_CANCEL_OPTION for the option type changes the buttons shown in the confirm dialog to OK and Cancel. Comparing the value returned to the constant, indicates which button the user selected.

Checking for the return value changes the one line above to quite a few more:

   int returnValue = JOptionPane.showConfirmDialog(
     component, "Lunch?");
   String message = null;
   if (returnValue == JOptionPane.YES_OPTION) {
     message = "Yes";
   } else if (returnValue == JOptionPane.NO_OPTION) {
     message = "No";
   } else if (returnValue == JOptionPane.CANCEL_OPTION) {
     message = "Cancel";
   } else if (returnValue == JOptionPane.CLOSED_OPTION) {
     message = "Closed";
   }
   System.out.println("User selected: " + message);

The input dialog method has six variations. Five return a String:

  • showInputDialog(Object message)
  • showInputDialog(Object message,
    Object initialSelectionValue)
  • showInputDialog(Component parentComponent,
    Object message)
  • showInputDialog(Component parentComponent,
    Object message,
    Object initialSelectionValue)
  • showInputDialog(Component parentComponent,
    Object message,
    String title,
    int messageType)

One of the variations returns an Object:

  • showInputDialog(Component parentComponent,
    Object message,
    String title,
    int messageType,
    Icon icon,
    Object[] selectionValues,
    Object initialSelectionValue)

The simplest variation shows the message in a question dialog. The dialog displays Input as the frame title, and has OK and Cancel buttons. Because there is no component provided, the frame is centered over the whole screen.

   String value = JOptionPane.showInputDialog("Name");

As is the case for other types, when you show the input dialog, you can set the parent component, frame title, message type, or icon. However the buttons are fixed at OK and Cancel. You can also set the initial value in the text field.

The last method variation for the input dialog is special. Instead of offering the user a text field to enter selections, you provide an array of objects (typically strings) from which to choose. Depending on how many values you provide, the look and feel will present either a JComboBox or JList for the user selections. Here's an example that uses this variation. Here an input dialog prompts a user to select a day of the week. The default is the last day of the week. The example uses a smallList for the days of the week.

   String smallList[] = {
     "Sunday",
     "Monday",
     "Tuesday",
     "Wednesday",
     "Thursday",
     "Friday",
     "Saturday"
   };
   String value = 
     (String)JOptionPane.showInputDialog(
       component, 
       "Which Day?", 
       "Day", 
       JOptionPane.QUESTION_MESSAGE, 
       null, // Use default icon
       smallList, 
       smallList[smallList.length-1]);
   System.out.println("Day: " + value);

For a larger list, the code looks essentially the same. Here the list of system properties are used as the choices.

   Object largeList[] = 
     System.getProperties().keySet().toArray();
   String value = 
     (String)JOptionPane.showInputDialog(
       component, 
       "Which Property?", 
       "Property", 
      JOptionPane.QUESTION_MESSAGE, 
       null, 
       largeList, 
       largeList[largeList.length-1]);


   System.out.println("Property: " + value);

The final dialog type is an all encompassing one, showOptionDialog. There is only one version of the method. Here, you can set everything:

  • showOptionDialog(Component parentComponent,
    Object message,
    String title,
    int optionType,
    int messageType,
    Icon icon,
    Object[] options,
    Object initialValue)

The only thing new here is the options array. This is how you can customize the set of available buttons. Notice that this is an Object array, not a String array. If the Object is a Component, that component is used in the dialog. This allows you to place icons in the buttons, for instance. More typically, you would provide an array of strings, and their labels would be used as the button labels. The initialValue is then used to select which of the options gets the default selection.

The showOptionDialog method returns an int. This indicates the position selected from the options array. CLOSED_OPTION is still returned if the user closed the dialog.

   String options[] = {"Yes", "Not Now", "Go Away"};
   int value = JOptionPane.showOptionDialog(
       component,
       "Lunch?",
       "Lunch Time",
       JOptionPane.YES_NO_OPTION, // Need something  
         JOptionPane.QUESTION_MESSAGE,
       null, // Use default icon for message type
       options,
       options[1]);
   if (value == JOptionPane.CLOSED_OPTION) {
     System.out.println("Closed window");
   } else {
     System.out.println("Selected: " + options[value]);
   }

If you don't want buttons, pass in an empty array for the options.

Adding Word Wrap

The JOptionPane component has a read-only property (MaxCharactersPerLineCount) for the maximum number of characters per line. By default, this is Integer.MAX_VALUE. By subclassing JOptionPane, you can override this setting. Changing this setting allows the component to word-wrap when a message is really long.

   public static JOptionPane getNarrowOptionPane(
                       int maxCharactersPerLineCount) {
     // Our inner class definition
     class NarrowOptionPane extends JOptionPane {
       int maxCharactersPerLineCount;
       NarrowOptionPane(int maxCharactersPerLineCount) {
         this.maxCharactersPerLineCount = 
           maxCharactersPerLineCount;
       }
       public int getMaxCharactersPerLineCount() {
         return maxCharactersPerLineCount;
       }
     }


     return new NarrowOptionPane(
       maxCharactersPerLineCount);
   }

By subclassing, you can no longer use the static helper methods such as showMessageDialog. Here's how you manually create and show the component:

   String msg = "This is a really long message. ...";
   JOptionPane pane = getNarrowOptionPane(50);
   pane.setMessage(msg);
   pane.setMessageType(JOptionPane.INFORMATION_MESSAGE);
   JDialog dialog = pane.createDialog(
     component, "Width 50");
   dialog.show();

Of course, you can add a "\n" to the message, but then you have to count the characters per line.

Message as Object

At this point you might ask why the message argument to all the showXXXDialog methods is an Object. The answer is that it's because the message argument to all these methods doesn't have to be a String. You can pass in any Object. Each object is "added" to the message area, one on top of the other. For instance, if you pass in an array of two strings, it creates a message on two lines:

   String msg[] = {"Welcome", "Home"};
   JOptionPane.showMessageDialog(component, msg);

The objects you add can be components, icons, or objects. Components are added as such. Icons are shown in a label, as are strings. For other objects, their string representation (toString()) is shown. For instance, the following component can be added to an option pane. As the user changes the selection in the slider, the option pane's value is updated:

   public static JSlider getSlider(
                        final JOptionPane optionPane) {
     JSlider slider = new JSlider();
     slider.setMajorTickSpacing (10);
     slider.setPaintTicks(true);
     slider.setPaintLabels(true);
     ChangeListener changeListener = 
       new ChangeListener() {
       public void stateChanged(
                   ChangeEvent changeEvent) {
         JSlider theSlider = 
             (JSlider)changeEvent.getSource();
         if (!theSlider.getValueIsAdjusting()) {
           optionPane.setInputValue(
             new Integer(theSlider.getValue()));
         }
       }
     };
     slider.addChangeListener(changeListener);
     return slider;
   }

Because you have to pass the option pane to the method, here again, you can't use one of the shortcuts to create the dialog:

   JOptionPane optionPane = new JOptionPane();
   JSlider slider = getSlider(optionPane);
   Object msg[] = {"Select a value:", slider};
   optionPane.setMessage(msg);
   optionPane.setMessageType(
     JOptionPane.QUESTION_MESSAGE);
   optionPane.setOptionType(
     JOptionPane.OK_CANCEL_OPTION);
   JDialog dialog = optionPane.createDialog(
       Options.this, "Select Value");
   dialog.show();
   Object value = optionPane.getValue();
   if (value == null || !(value instanceof Integer)) {
     System.out.println("Closed");
   } else {
     int i = ((Integer)value).intValue();
     if (i == JOptionPane.CLOSED_OPTION) {
       System.out.println("Closed");
     } else if (i == JOptionPane.OK_OPTION) {
       System.out.println("OKAY - value is: " +
                  optionPane.getInputValue());
     } else if (i == JOptionPane.CANCEL_OPTION) {
       System.out.println("Cancelled");
     }
   }

Notice that as you start getting fancier with JOptionPane, you lose the ability to use the shortcut methods. And, you have to do special handling of the return value. You first need to use getValue for the JOptionPane to determine which button the user selected. Then, for when the user presses the OK button, you need to use the getInputValue method to get the value from the underlying slider.

Sounds

Swing provides for auditory cues related to the four types of icons:

  • OptionPane.errorSound
  • OptionPane.informationSound
  • OptionPane.questionSound
  • OptionPane.warningSound

By setting these properties, you can get sounds when your option panes are displayed. You can set the individual sounds with lines like the following:

   UIManager.put("OptionPane.questionSound", 
     "sounds/OptionPaneError.wav");

Or set all of them with the system defaults as follows:

   UIManager.put("AuditoryCues.playList",
     UIManager.get("AuditoryCues.defaultCueList"));

Audio cues are disabled by default because they might have problems on some platforms. It is recommended that you use them with care until the issues have been resolved.

Complete Example

Here is the source code for a complete example that uses the features explored in this tip.

   import javax.swing.*;
   import javax.swing.event.*;
   import java.awt.*;
   import java.awt.event.*;
   import java.util.Locale;

   public class Options extends JFrame {
     private static class FrameShower
         implements Runnable {
       final Frame frame;
       public FrameShower(Frame frame) {
         this.frame = frame;
       }
       public void run() {
         frame.show();
       }
     }

     public static JOptionPane getNarrowOptionPane(
         int maxCharactersPerLineCount) {
       // Our inner class definition
       class NarrowOptionPane extends JOptionPane {
         int maxCharactersPerLineCount;
         NarrowOptionPane(
                   int maxCharactersPerLineCount) {
           this.maxCharactersPerLineCount = 
              maxCharactersPerLineCount;
     }
         public int getMaxCharactersPerLineCount() {
           return maxCharactersPerLineCount;
         }
       }

    return new NarrowOptionPane(
               maxCharactersPerLineCount);
  }

     public static JSlider getSlider(
                        final JOptionPane optionPane) {
       JSlider slider = new JSlider();
       slider.setMajorTickSpacing (10);
       slider.setPaintTicks(true);
       slider.setPaintLabels(true);
       ChangeListener changeListener = 
                            new ChangeListener() {
         public void stateChanged(
                           ChangeEvent changeEvent) {
           JSlider theSlider = (
             JSlider)changeEvent.getSource();
           if (!theSlider.getValueIsAdjusting()) {
             optionPane.setInputValue(new Integer(  
               theSlider.getValue()));
           }
         }
       };
       slider.addChangeListener(changeListener);
       return slider;
     }

     public Options() {
       super("JOptionPane Usage");
       setDefaultCloseOperation(EXIT_ON_CLOSE);
       Container contentPane = getContentPane();
       contentPane.setLayout(new FlowLayout());
       JButton message = new JButton("Message");
       ActionListener messageListener = 
                               new ActionListener() {
         public void actionPerformed(ActionEvent e) {
           JOptionPane.showMessageDialog(
             Options.this, "Hello, World");
         }
       };
       message.addActionListener(messageListener);
       contentPane.add(message);
       JButton confirm = new JButton("Confirm");  
       ActionListener confirmListener = 
        new ActionListener() {
         public void actionPerformed(ActionEvent e) {
           int returnValue = 
             JOptionPane.showConfirmDialog(
           Options.this, "Lunch?");
           String message = null;           
           if (returnValue == JOptionPane.YES_OPTION) {
             message = "Yes";
           } else if (
               returnValue == JOptionPane.NO_OPTION) {
             message = "No";
           } else if (
               returnValue == JOptionPane.CANCEL_OPTION) {
             message = "Cancel";
           } else if (
               returnValue == JOptionPane.CLOSED_OPTION) {
             message = "Closed";
           }
           System.out.println("User selected: " + message);
         }
       };
       confirm.addActionListener(confirmListener);
       contentPane.add(confirm);
       JButton inputText = new JButton("Input Text");
       ActionListener inputTextListener = 
                               new ActionListener() {
         public void actionPerformed(ActionEvent e) {
           String value = 
             JOptionPane.showInputDialog("Name");
           System.out.println("Name: " + value);
         }
       };
       inputText.addActionListener(inputTextListener);
       contentPane.add(inputText);
       JButton inputCombo = new JButton("Input Combo");
       ActionListener inputComboListener = 
                                 new ActionListener() {
         public void actionPerformed(ActionEvent e) {
           String smallList[] = {
             "Sunday",
             "Monday",
             "Tuesday",
             "Wednesday",
             "Thursday",
             "Friday",
             "Saturday"
           };
           String value = 
             (String)JOptionPane.showInputDialog(
             Options.this, "Which Day?", "Day", 
             JOptionPane.QUESTION_MESSAGE, null, 
             smallList, smallList[smallList.length-1]);
           System.out.println("Day: " + value);
         }
       };
       inputCombo.addActionListener(inputComboListener);
       contentPane.add(inputCombo);
       JButton inputList = new JButton("Input List");     
       ActionListener inputListListener = 
                              new ActionListener() {
        public void actionPerformed(ActionEvent e) {
           Object largeList[] = 
             System.getProperties().keySet().toArray();
           String value = 
             (String)JOptionPane.showInputDialog(
             Options.this, "Which Property?", "Property", 
             JOptionPane.QUESTION_MESSAGE, null, 
             largeList, largeList[largeList.length-1]);
             System.out.println("Property: " + value);
         }
       };              
       inputList.addActionListener(inputListListener);
       contentPane.add(inputList);
       JButton all = new JButton("All");
       ActionListener allListener = 
                               new ActionListener() {
         public void actionPerformed(ActionEvent e) {
           String options[] = 
             {"Yes", "Not Now", "Go Away"};
           int value = JOptionPane.showOptionDialog(
               Options.this,
               "Lunch?",
               "Lunch Time",
               JOptionPane.YES_NO_OPTION, 
               // Message type
               JOptionPane.QUESTION_MESSAGE,
               null, // Use default icon for message type
               options,
               options[1]);
           if (value == JOptionPane.CLOSED_OPTION) {
             System.out.println("Closed window");
           } else {
             System.out.println(
               "Selected: " + options[value]);
           }
         }
       };       
       all.addActionListener(allListener);
       contentPane.add(all);
       JButton wide = new JButton("Wide");
       ActionListener wideListener = 
                               new ActionListener() {
         public void actionPerformed(ActionEvent e) {
           String msg = 
             "This is a really long message. " + 
             "This is a really long message. " + 
             "This is a really long message. " + 
             "This is a really long message. " + 
             "This is a really long message. " +
             "This is a really long message.";
           JOptionPane pane = getNarrowOptionPane(50);
           pane.setMessage(msg);
           pane.setMessageType(
             JOptionPane.INFORMATION_MESSAGE);
           JDialog dialog = 
              pane.createDialog(Options.this, "Width 50");
           dialog.show();
         }
       };       
       wide.addActionListener(wideListener);
       contentPane.add(wide);
       JButton twoLine = new JButton("Two Line");
       ActionListener twoLineListener = 
                               new ActionListener() {
         public void actionPerformed(ActionEvent e) {
           String msg[] = {"Welcome", "Home"};
           JOptionPane.showMessageDialog(
             Options.this, msg);
         }
       };       
       twoLine.addActionListener(twoLineListener);
       contentPane.add(twoLine);
       JButton slider = new JButton("Slider");
       ActionListener sliderListener = 
                               new ActionListener() {
         public void actionPerformed(ActionEvent e) {
           JOptionPane optionPane = new JOptionPane();
           JSlider slider = getSlider(optionPane);
           Object msg[] = {"Select a value:", slider};
           optionPane.setMessage(msg);
           optionPane.setMessageType(
             JOptionPane.QUESTION_MESSAGE);
           optionPane.setOptionType(
             JOptionPane.OK_CANCEL_OPTION);
             JDialog dialog = optionPane.createDialog(
                Options.this, "Select Value");
           dialog.show();
           Object value = optionPane.getValue();
           if (value == null || !(value instanceof Integer)) {
             System.out.println("Closed");
           } else {
             int i = ((Integer)value).intValue();
             if (i == JOptionPane.CLOSED_OPTION) {
               System.out.println("Closed");
             } else if (i == JOptionPane.OK_OPTION) {
               System.out.println("OKAY - value is: " +
                          optionPane.getInputValue());
             } else if (i == JOptionPane.CANCEL_OPTION) {
               System.out.println("Cancelled");
             }
           }
         }
       };
       slider.addActionListener(sliderListener);
       contentPane.add(slider);
       setSize(300, 200);
     }
     
     public static void main(String args[]) {
       UIManager.put("AuditoryCues.playList",
         UIManager.get("AuditoryCues.defaultCueList"));
       JFrame frame = new Options();
       Runnable runner = new FrameShower(frame);
       EventQueue.invokeLater(runner);
     }
   }

For additional information about JOptionPane, see the How to Make Dialogs trail in The Java Tutorial and the javadoc for the JOptionPane class.


'자바 > 자바팁' 카테고리의 다른 글

클래스패스 설정  (0) 2010.10.25
Borderlayout 기본설명  (0) 2010.10.21
에코 클라이언트 과정  (0) 2010.10.21
JFrame 관련 팁  (0) 2010.10.18
action event 를 쓸때 컴파일시 inner class 에러 나는 경우  (0) 2010.10.18
★ Top-level component - JFrame ★ - Java/Servlet/JSP

2006/11/28 23:21

복사 http://blog.naver.com/airdasom/90011367478

JFrame
 

'자바 > 자바팁' 카테고리의 다른 글

클래스패스 설정  (0) 2010.10.25
Borderlayout 기본설명  (0) 2010.10.21
에코 클라이언트 과정  (0) 2010.10.21
JDialog 고급활용  (0) 2010.10.18
action event 를 쓸때 컴파일시 inner class 에러 나는 경우  (0) 2010.10.18

addActionListener() 호출시 사용한 new ActionListener() { ... } 부분이

anonymous inner class이고 asd()의 인자인 str을 여기로 고이 전달하려면

str을 final로 선언해야 한다는 얘깁니다.

아래와 같이 final 붙여주면 됩니다.

 

 

class asd extends Frame
{
 public asd(final String str){
  
  setSize(300,200);
  setVisible(true);
  Button bt = new Button("go");
  add(bt);
  bt.addActionListener(new ActionListener(){
   
   public void actionPerformed(ActionEvent e){
    System.out.println(str);
   }

  });

 }

 

'자바 > 자바팁' 카테고리의 다른 글

클래스패스 설정  (0) 2010.10.25
Borderlayout 기본설명  (0) 2010.10.21
에코 클라이언트 과정  (0) 2010.10.21
JDialog 고급활용  (0) 2010.10.18
JFrame 관련 팁  (0) 2010.10.18

+ Recent posts