자바로 우선 스노우크래프트 만든 다음

안드로이드로 옮겨보자..내실력에 과연 될까나..==;;

ListDataEvent.CONTENTS_CHANGED

/*Type: Interval Added
, Index0: 0
, Index1: 0
[First, a, b, c, d]
Type: Interval Added
, Index0: 5
, Index1: 5
[First, a, b, c, d, Last]
Type: Interval Added
, Index0: 3
, Index1: 3
[First, a, b, Middle, c, d, Last]
Type: Contents Changed
, Index0: 0
, Index1: 0
[New First, a, b, Middle, c, d, Last]
Type: Contents Changed
, Index0: 6
, Index1: 6
[New First, a, b, Middle, c, d, New Last]
Type: Interval Added
, Index0: 7
, Index1: 7
[New First, a, b, Middle, c, d, New Last, a]
Type: Interval Added
, Index0: 8
, Index1: 8
[New First, a, b, Middle, c, d, New Last, a, b]
Type: Interval Added
, Index0: 9
, Index1: 9
[New First, a, b, Middle, c, d, New Last, a, b, c]
Type: Interval Added
, Index0: 10
, Index1: 10
[New First, a, b, Middle, c, d, New Last, a, b, c, d]
Type: Interval Removed
, Index0: 0
, Index1: 10
[]

 * */
import java.awt.BorderLayout;

import javax.swing.DefaultListModel;
import javax.swing.JFrame;
import javax.swing.JList;
import javax.swing.JScrollPane;
import javax.swing.event.ListDataEvent;
import javax.swing.event.ListDataListener;

public class MainClass {
  static String labels[] "a""b""c""d" };

  public static void main(String args[]) {
    JFrame frame = new JFrame("Modifying Model");
    frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

    final DefaultListModel model = new DefaultListModel();
    for (int i = 0, n = labels.length; i < n; i++) {
      model.addElement(labels[i]);
    }
    JList jlist = new JList(model);
    JScrollPane scrollPane1 = new JScrollPane(jlist);
    frame.add(scrollPane1, BorderLayout.WEST);

    ListDataListener listDataListener = new ListDataListener() {
      public void contentsChanged(ListDataEvent listDataEvent) {
        appendEvent(listDataEvent);
      }
      public void intervalAdded(ListDataEvent listDataEvent) {
        appendEvent(listDataEvent);
      }
      public void intervalRemoved(ListDataEvent listDataEvent) {
        appendEvent(listDataEvent);
      }
      private void appendEvent(ListDataEvent listDataEvent) {
        switch (listDataEvent.getType()) {
        case ListDataEvent.CONTENTS_CHANGED:
          System.out.println("Type: Contents Changed");
          break;
        case ListDataEvent.INTERVAL_ADDED:
          System.out.println("Type: Interval Added");
          break;
        case ListDataEvent.INTERVAL_REMOVED:
          System.out.println("Type: Interval Removed");
          break;
        }
        System.out.println(", Index0: " + listDataEvent.getIndex0());
        System.out.println(", Index1: " + listDataEvent.getIndex1());
        DefaultListModel theModel = (DefaultListModellistDataEvent.getSource();
        System.out.println(theModel);
      }
    };

    model.addListDataListener(listDataListener);

    model.add(0"First");
    model.addElement("Last");
    int size = model.getSize();
    model.insertElementAt("Middle", size / 2);
    size = model.getSize();
    if (size != 0)
      model.set(0"New First");
    size = model.getSize();
    if (size != 0)
      model.setElementAt("New Last", size - 1);
    for (int i = 0, n = labels.length; i < n; i++) {
      model.addElement(labels[i]);
    }
    model.clear();
    size = model.getSize();
    if (size != 0)
      model.remove(0);

    model.removeAllElements();
    model.removeElement("Last");
    size = model.getSize();
    if (size != 0)
      model.removeElementAt(size / 2);
    size = model.getSize();
    if (size != 0)
      model.removeRange(0, size / 2);
    frame.setSize(640300);
    frame.setVisible(true);
  }
}


사용법 : File file = new FileRenamePolicy().rename(new File(원하는 파일명));

==============================================================================
import java.io.File;
import java.io.IOException;

public class FileRenamePolicy {
 
  public File rename(File f) {  //File f는 원본 파일
    if (createNewFile(f)) return f; //생성된 f가
   
    //확장자가 없는 파일 일때 처리
    String name = f.getName();
    String body = null;
    String ext = null;

    int dot = name.lastIndexOf(".");
    if (dot != -1) { //확장자가 없을때
      body = name.substring(0, dot);
      ext = name.substring(dot);
    } else {   //확장자가 있을때
      body = name;
      ext = "";
    }

    int count = 0;
    //중복된 파일이 있을때
    while (!createNewFile(f) && count < 9999) {  
      count++;
      String newName = body + count + ext;
      f = new File(f.getParent(), newName);
    }
    return f;
  }

  private boolean createNewFile(File f) { 
    try {
      return f.createNewFile();  //존재하는 파일이 아니면
    }catch (IOException ignored) {
      return false;
    }
  }
}

Introduction to Object Serialization

Java object serialization is used to persist Java objects to a file, database, network, process or any other system. Serialization flattens objects into an ordered, or serialized stream of bytes. The ordered stream of bytes can then be read at a later time, or in another environment, to recreate the original objects.

Java serialization does not cannot occur for transient or static fields. Marking the field transient prevents the state from being written to the stream and from being restored during deserialization. Java provides classes to support writing objects to streams and restoring objects from streams. Only objects that support the java.io.Serializable interface or the java.io.Externalizable interface can be written to streams.
public interface Serializable

  • The Serializable interface has no methods or fields. (Marker Interface)
  • Only objects of classes that implement java.io.Serializable interface can be serialized or deserialized

Transient Fields and Java Serialization

The transient keyword is a modifier applied to instance variables in a class. It specifies that the variable is not part of the persistent state of the object and thus never saved during serialization.

You can use the transient keyword to describe temporary variables, or variables that contain local information,


such as a process ID or a time lapse.

Input and Output Object Streams

ObjectOutputStream is the primary output stream class that implements the ObjectOutput interface for serializing objects. ObjectInputStream is the primary input stream class that implements the ObjectInput interface for deserializing objects.

These high-level streams are each chained to a low-level stream, such as FileInputStream or FileOutputStream.
The low-level streams handle the bytes of data. The writeObject method saves the state of the class by writingthe individual fields to the ObjectOutputStream. The readObject method is used to deserialize the object from
the object input stream.

Case 1: Below is an example that demonstrates object Serialization into a File

PersonDetails is the bean class that implements the Serializable interface

import java.io.Serializable;
public class PersonDetails implements Serializable {

	private String name;
	private int age;
	private String sex;
	public PersonDetails(String name, int age, String sex) {
		this.name = name;
		this.age = age;
		this.sex = sex;
	}
	public int getAge() {
		return age;
	}
	public void setAge(int age) {
		this.age = age;
	}
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	public String getSex() {
		return sex;
	}
	public void setSex(String sex) {
		this.sex = sex;
	}
}

GetPersonDetails is the class that is used to Deserialize object from the File (person.txt).

import java.io.FileInputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.util.ArrayList;
import java.util.List;
public class GetPersonDetails {

	public static void main(String[] args) {
		String filename = "person.txt";
		List pDetails = null;
		FileInputStream fis = null;
		ObjectInputStream in = null;
		try {
			fis = new FileInputStream(filename);
			in = new ObjectInputStream(fis);
			pDetails = (ArrayList) in.readObject();
			in.close();
		} catch (IOException ex) {
			ex.printStackTrace();
		} catch (ClassNotFoundException ex) {
			ex.printStackTrace();
		}
		// print out the size
		System.out.println("Person Details Size: " + pDetails.size());
		System.out.println();
	}
}

PersonPersist is the class that is used to serialize object into the File (person.txt).

public class PersonPersist {

	public static void main(String[] args) {
		String filename = "person.txt";
		PersonDetails person1 = new PersonDetails("hemanth", 10, "Male");
		PersonDetails person2 = new PersonDetails("bob", 12, "Male");
		PersonDetails person3 = new PersonDetails("Richa", 10, "Female");
		List list = new ArrayList();
		list.add(person1);
		list.add(person2);
		list.add(person3);
		FileOutputStream fos = null;
		ObjectOutputStream out = null;
		try {
			fos = new FileOutputStream(filename);
			out = new ObjectOutputStream(fos);
			out.writeObject(list);
			out.close();
			System.out.println("Object Persisted");
		} catch (IOException ex) {
			ex.printStackTrace();
		}
	}
}

——————————————————————————–

Case 2: Below is an example that demonstrates object Serialization into the database

PersonDetails remains the same as shown above

GetPersonDetails remains the same as shown above

Create SerialTest Table

create table SerialTest(
name BLOB,
viewname VARCHAR2(30)
);

PersonPersist is the class that is used to serialize object into the into the Database Table SerialTest.

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
public class PersonPersist {

	static String userid = "scott", password = "tiger";
	static String url = "jdbc:odbc:bob";
	static int count = 0;
	static Connection con = null;
	public static void main(String[] args) {
		Connection con = getOracleJDBCConnection();
		PersonDetails person1 = new PersonDetails("hemanth", 10, "Male");
		PersonDetails person2 = new PersonDetails("bob", 12, "Male");
		PersonDetails person3 = new PersonDetails("Richa", 10, "Female");
		PreparedStatement ps;
		try {
			ps = con
					.prepareStatement("INSERT INTO SerialTest VALUES (?, ?)");
			write(person1, ps);
			ps.execute();
			write(person2, ps);
			ps.execute();
			write(person3, ps);
			ps.execute();
			ps.close();
			Statement st = con.createStatement();
			ResultSet rs = st.executeQuery("SELECT * FROM SerialTest");
			while (rs.next()) {
				Object obj = read(rs, "Name");
				PersonDetails p = (PersonDetails) obj;
				System.out.println(p.getName() + "\t" + p.getAge() + "\t"
						+ p.getSex());
			}
			rs.close();
			st.close();
		} catch (Exception e) {
		}
	}
	public static void write(Object obj, PreparedStatement ps)
			throws SQLException, IOException {
		ByteArrayOutputStream baos = new ByteArrayOutputStream();
		ObjectOutputStream oout = new ObjectOutputStream(baos);
		oout.writeObject(obj);
		oout.close();
		ps.setBytes(1, baos.toByteArray());
		ps.setInt(2, ++count);
	}
	public static Object read(ResultSet rs, String column)
			throws SQLException, IOException, ClassNotFoundException {
		byte[] buf = rs.getBytes(column);
		if (buf != null) {
			ObjectInputStream objectIn = new ObjectInputStream(
					new ByteArrayInputStream(buf));
			return objectIn.readObject();
		}
		return null;
	}
	public static Connection getOracleJDBCConnection() {
		try {
			Class.forName("sun.jdbc.odbc.JdbcOdbcDriver");
		} catch (java.lang.ClassNotFoundException e) {
			System.err.print("ClassNotFoundException: ");
			System.err.println(e.getMessage());
		}
		try {
			con = DriverManager.getConnection(url, userid, password);
		} catch (SQLException ex) {
			System.err.println("SQLException: " + ex.getMessage());
		}
		return con;
	}
}

——————————————————————————–

Case 3: Below is an example that demonstrates object Serialization into the database using Base 64 Encoder

PersonDetails remains the same as shown above

GetPersonDetails remains the same as shown above

Create SerialTest Table

create table SerialTest(
name BLOB,
viewname VARCHAR2(30)
);

PersonPersist is the class that is used to serialize object into the Database Table SerialTest

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
public class PersonPersist {

	static String userid = "scott", password = "tiger";
	static String url = "jdbc:odbc:bob";
	static int count = 0;
	static Connection con = null;
	static String s;
	public static void main(String[] args) {
		Connection con = getOracleJDBCConnection();
		PersonDetails person1 = new PersonDetails("hemanth", 10, "Male");
		PersonDetails person2 = new PersonDetails("bob", 12, "Male");
		PersonDetails person3 = new PersonDetails("Richa", 10, "Female");
		PreparedStatement ps;
		try {
			ps = con
					.prepareStatement("INSERT INTO SerialTest VALUES (?, ?)");
			write(person1, ps);
			ps.execute();
			write(person2, ps);
			ps.execute();
			write(person3, ps);
			ps.execute();
			ps.close();
			Statement st = con.createStatement();
			ResultSet rs = st.executeQuery("SELECT * FROM SerialTest");
			while (rs.next()) {
				Object obj = read(rs, "Name");
				PersonDetails p = (PersonDetails) obj;
				System.out.println(p.getName() + "\t" + p.getAge() + "\t"
						+ p.getSex());
			}
			rs.close();
			st.close();
		} catch (Exception e) {
		}
	}
	public static void write(Object obj, PreparedStatement ps)
			throws SQLException, IOException {
		ByteArrayOutputStream baos = new ByteArrayOutputStream();
		ObjectOutputStream oout = new ObjectOutputStream(baos);
		oout.writeObject(obj);
		oout.close();
		byte[] buf = baos.toByteArray();
		s = new sun.misc.BASE64Encoder().encode(buf);
		ps.setString(1, s);
		// ps.setBytes(1, Base64.byteArrayToBase64(baos.toByteArray()));
		ps.setBytes(1, baos.toByteArray());
		ps.setInt(2, ++count);
	}
	public static Object read(ResultSet rs, String column)
			throws SQLException, IOException, ClassNotFoundException {
		byte[] buf = new sun.misc.BASE64Decoder().decodeBuffer(s);
		// byte[] buf = Base64.base64ToByteArray(new
		// String(rs.getBytes(column)));
		if (buf != null) {
			ObjectInputStream objectIn = new ObjectInputStream(
					new ByteArrayInputStream(buf));
			Object obj = objectIn.readObject(); // Contains the object
			PersonDetails p = (PersonDetails) obj;
			System.out.println(p.getName() + "\t" + p.getAge() + "\t"
					+ p.getSex());
		}
		return null;
	}
	public static Connection getOracleJDBCConnection() {
		try {
			Class.forName("sun.jdbc.odbc.JdbcOdbcDriver");
		} catch (java.lang.ClassNotFoundException e) {
			System.err.print("ClassNotFoundException: ");
			System.err.println(e.getMessage());
		}
		try {
			con = DriverManager.getConnection(url, userid, password);
		} catch (SQLException ex) {
			System.err.println("SQLException: " + ex.getMessage());
		}
		return con;
	}
}
Below is a program that shows the serialization of a JButton object to a file and a Byte Array Stream. As before theobject to be serialized must implement the Serializable interface.

PersonDetails is the bean class that implements the Serializable interface

import java.io.ByteArrayOutputStream;
import java.io.FileOutputStream;
import java.io.ObjectOutput;
import java.io.ObjectOutputStream;

public class ObjectSerializationExample {

	public static void main(String args[]) {
		try {
			Object object = new javax.swing.JButton("Submit");
			// Serialize to a file namely "filename.dat"
			ObjectOutput out = new ObjectOutputStream(
					new FileOutputStream("filename.dat"));
			out.writeObject(object);
			out.close();
			// Serialize to a byte array
			ByteArrayOutputStream bos = new ByteArrayOutputStream();
			out = new ObjectOutputStream(bos);
			out.writeObject(object);
			out.close();
			// Get the bytes of the serialized object
			byte[] buf = bos.toByteArray();
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
}

 

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

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

 

자바 게임 프로그래밍 강좌 6

하이텔 자바 동호회 김명현 (MadOx@hitel.net)님의 글입니다.
이글은 김명현님의 동의없이 함부로 배포하실 수 없습니다.

안녕하세요? 친소맨입니다. 이번 강좌는 소스가 길어서 내용은 별루 안되는 데, 길이만 길군요.. 앞으로도 계속 이런 사태가 벌어질것 같아서 걱정입니 다. 대신 주석을 너무하다 싶을정도로 붙여놓은 소스이므로 참고 하시면 많 은 도움이 될듯 합니다. * 본 문서는 Java Development Kit 1.0.2 를 기준으로 작성되었습니다. * Java Development Kit 1.1.x 버젼을 사용할 경우 일부 소스가 실행이 되지 않을 수 있습니다. 3.1.3 마음대로 움직이는 탱크를 만들어 봅시다. 지금까지 배운 것을 모두 총괄 복습하는 의미에서 다소 덩치가 있는 프로 그램을 만들어 보도록 하겠습니다. 제목에서 알 수 있듯이 우리가 임의로 조정가능한 탱크 애플릿을 만들어 볼 것입니다. 우선 간단하게 계획부터 세 우기로 합시다. ● 탱크 애플릿의 목적 "화면상에 탱크 이미지를 출력하고, 우리가 누르는 방향키에 따라 상/하/ 좌/우 네방향으로 이동 하도록한다. 각각의 방향에 따라 서로 다른 이미 지가출력되도록 한다." ● 계략적인 프로그램의 흐름 ?start method ① 애플릿에서 사용할 이미지 데이터를 서버로부터 전송해 온다. ② 전송해 온 이미지를 CropImageFilter를 이용해 4방향 각각의 이미지 로 분리한다. ③ 실제 작업을 할 메모리 버퍼(더블 버퍼링용)를 생성한다. ④ 애플릿의 메인 쓰레드를 생성하고 활성화 시킨다. ⑤ 각종 변수들을 초기화 한다. (탱크의 초기 위치, 탱크의 방향 등) ?run method ⑥ 화면을 갱신하고 일정기간 쓰레드를 쉬게 한다. ⑦ 키보드가 PUSH 됐다면, 해당 키 값에 따라 탱크를 이동시킨다. ⑧ ⑥번으로 이동한다. ?paint method 탱크 이미지가 로드 완료 되었나? 예) 탱크의 방향에 맞춰 이미지를 탱크의 현재 위치에 출력한다. 아니오) "Loading Image..."를 출력해 이미지 로드중임을 표시한다. ?keyDown method PUSH 된 키 값이 상/하/좌/우 중 한 값이면 해당 키보드 플랙을 true로 셋팅한다. 탱크의 현재 방향을 설정한다. ?keyUp method POP된 키보드 값이 상/하/좌/우 중 한 값이면 해당 키보드 플랙을 fal- se로 셋팅한다. 탱크 애플릿의 수행 절차입니다. keyDown메소드와 keyUp메소드, paint 메 소드는 OS로 부터 통보 되는 이벤트에 의해 불려지는 핸들러 이므로 우리가 따로 불러주지 않더라도 자동으로 불려지게 됩니다. 탱크의 이동처리를 ke- yDown메소드에서 직접 처리하지 않는 이유는 keyDown메소드로는 연속적으로 눌려져 있는 키 값을 처리하지 못하기 때문입니다. (KeyDown 이벤트는 키가 눌려지는 시점에서 단 1회 발생합니다) 이 절차에 따라 만들어진 소스가 아 래 [소스2]입니다. ! 주의 : 아래의소스중 라인번호가 붙어 있지 않은 라인은 원래는 위의줄 과 한 라인이나, 편집을 위해 나눠 놓은 것입니다. 001: import java.awt.*; 002: import java.applet.*; 003: import java.awt.image.*; 004: import java.lang.Runnable; 005: 006: public class Tank extends Applet implements Runnable 007: { 008: // 애플릿의 주 쓰레드 009: private Thread trdMain; 010: 011: private MediaTracker tracker; 012: //서버로 부터 받을 이미지 013: private Image imgTankAll; 014: // 각 방향의 이미지 015: private Image imgUp; 016: private Image imgLeft; 017: private Image imgRight; 018: private Image imgDown; 019: 020: // 탱크 이미지를 그릴 메모리 버퍼 021: private Image imgOffScr; 022: private Graphics grpOffScr; 023: 024: // 탱크의 방향과 위치 025: private int tankDir; 026: private int tankXp, tankYp; 027: 028: // 탱크의 방향 설정및 체크에 사용할 상수 029: private final int UP = 1; 030: private final int LEFT = 2; 031: private final int RIGHT = 3; 032: private final int DOWN = 4; 033: 034: // UP/DOWN/LEFT/RIGHT 키의 상태를 저장할 Flag 035: private boolean Key_UP = false; 036: private boolean Key_DOWN = false; 037: private boolean Key_LEFT = false; 038: private boolean Key_RIGHT = false; 039: 040: public void start() 041: { 042: ImageProducer producer; 043: ImageFilter filter; 044: 045: // 4방향의 탱크 이미지를 서버로 부터 가져온다 046: tracker = new MediaTracker(this); 047: imgTankAll = getImage(getCodeBase(), "Tank.GIF"); 048: 049: tracker.addImage(imgTankAll, 0); 050: 051: producer = imgTankAll.getSource(); 052: 053: // 서버로부터가져온 이미지에서 4방향이미지를 잘라낸다 054: filter = new CropImageFilter(1, 1, 136, 136); 055: imgUp = createImage(new FilteredImageSource(producer, filter)); 056: tracker.addImage(imgUp, 1); 057: 058: filter = new CropImageFilter(138, 1, 136, 136); 059: imgRight = createImage(new FilteredImageSource( producer, filter)); 060: tracker.addImage(imgRight, 1); 061: 062: filter = new CropImageFilter(275, 1, 136, 136); 063: imgDown = createImage(new FilteredImageSource( producer, filter)); 064: tracker.addImage(imgDown, 1); 065: 066: filter = new CropImageFilter(412, 1, 136, 136); 067: imgLeft = createImage(new FilteredImageSource( producer, filter)); 068: tracker.addImage(imgLeft, 1); 069: 070: // 애플릿의 크기에 맞춰 더블버퍼링을 할 버퍼를 만든다 071: imgOffScr = createImage(size().width, size().height); 072: grpOffScr = imgOffScr.getGraphics(); 073: 074: // 탱크의 초기 방향을 위로 정하고, 탱크를 화면 중앙에 위치시키기 075: // 위해 좌표를 설정한다. (탱크 이미지의 가로, 세로 크기가 136 Pixel임) 076: tankDir = UP; 077: tankXp = (size().width - 136) / 2; 078: tankYp = (size().height - 136) / 2; 079: 080: // 메인 쓰레드를 생성하고 활성화 시킨다 081: if (trdMain == null) { 082: trdMain = new Thread(this); 083: trdMain.start(); 084: } 085: 086: // 애플릿에게 키보드 이벤트가 발생하도록 OS에게 Focus를 요청한다 087: requestFocus(); 088: } 089: 090: public void run() 091: { 092: // 그림이 완전히 로드될때 가지 기다린다 093: try { 094: tracker.waitForAll(); 095: } catch (InterruptedException ie) {} 096: 097: // 주 루프 098: while (true) { 099: repaint(); // 화면을 갱신한다 100: try { 101: Thread.sleep(50); // 0.05초간 쉰다 102: } catch (InterruptedException ie) {} 103: 104: // 방향에 따라 탱크를 이동시킨다 105: if (Key_UP) 106: tankYp -= 2; 107: if (Key_DOWN) 108: tankYp += 2; 109: if (Key_LEFT) 110: tankXp -= 2; 111: if (Key_RIGHT) 112: tankXp += 2; 113: } 114: } 115: 116: public void stop() 117: { 118: // 쓰레드를 종료한다 119: if (trdMain != null) { 120: trdMain.stop(); 121: trdMain = null; 122: } 123: } 124: 125: public void update(Graphics g) 126: { 127: // update의 고약한 버릇을 없애기 위해 덮어썼음 128: paint(g); 129: } 130: 131: public void paint(Graphics g) 132: { 133: // 이미지가 완전리 로드 되었는지 체크 134: if ((tracker.statusAll(true) & MediaTracker.COMPLETE) != 0) { 135: // 이전 위치의 탱크를 지우기 위해 메모리 버퍼를 배경색으로 136: // 깨끗히 지운다. 137: grpOffScr.setColor(getBackground()); 138: grpOffScr.fillRect(0, 0, size().width, size().height); 139: // 탱크의 현재 방향에 따라 탱크의 이미지를 메모리 버퍼에 그린다 140: switch (tankDir) { 141: case UP: 142: grpOffScr.drawImage( imgUp, tankXp, tankYp, this); 143: break; 144: case RIGHT: 145: grpOffScr.drawImage( imgRight, tankXp, tankYp, this); 146: break; 147: case DOWN: 148: grpOffScr.drawImage( imgDown, tankXp, tankYp, this); 149: break; 150: case LEFT: 151: grpOffScr.drawImage( imgLeft, tankXp, tankYp, this); 152: break; 153: } 154: // 메모리 버퍼의 내용을 화면으로 옮긴다 155: g.drawImage(imgOffScr, 0, 0, this); 156: } else 157: // 이미지가 로드 중이면 메시지를 출력한다 158: g.drawString("Loading Image....", 20, 20); 159: } 160: 161: public boolean keyDown(Event evt, int key) 162: { 163: // PUSH된 키값에 따라 해당키의 플랙을 true로 설정하고 164: // 탱크의 방향을 바꾼다 165: switch (key) { 166: case 1004: 167: Key_UP = true; 168: tankDir = UP; 169: break; 170: case 1005: 171: Key_DOWN = true; 172: tankDir = DOWN; 173: break; 174: case 1006: 175: Key_LEFT = true; 176: tankDir = LEFT; 177: break; 178: case 1007: 179: Key_RIGHT = true; 180: tankDir = RIGHT; 181: break; 182: } 183: return true; // 처리 완료! 184: } 185: 186: public boolean keyUp(Event evt, int key) 187: { 188: // POP된 키의 플랙을 false 로 설정한다 189: switch (key) { 190: case 1004: 191: Key_UP = false; 192: break; 193: case 1005: 194: Key_DOWN = false; 195: break; 196: case 1006: 197: Key_LEFT = false; 198: break; 199: case 1007: 200: Key_RIGHT = false; 201: break; 202: } 203: return true; 204: } 205: } [소스2] Tank.java 소스가 다소 길지만 지금까지 해온 내용들을 이해하고 오셨다면 보시는데 큰 무리는 없었을 것입니다. Tank애플릿을 실행해 보면 화면 중앙에 탱크가 나올 것입니다. 상/하/좌/우키를 누르면 탱크는 방향을 바꾸고 앞으로 전진 을 할 것입니다. ☞ 새로 등장한 것들 ◇ final [소스2]의 29-32라인에서 사용된 final 키워드는 상수를 정의 하거나 더 이상 서브클래스에서 바꿀수 없는 메소드를 만들고 싶을때 사용합니 다. final 키워드가 붙은 데이터멤버는 우리가 임의로 바꿀 수 없고 참 조만 가능합니다. 만약 우리가 데이터 멤버의 값을 바꾸려 한다면 Java 컴파일러는 컴파일을 거부할 것입니다. ◇ Component.size() 이 메소드는 해당 Component 객체의 크기를 Dimension 객체형태로 되 돌립니다. Dimension 클래스는 width 와 height 데이터 멤버를 가지며, 각각 너비와 높이에 대한 데이터를 가지고 있습니다. [소스2]에서는 애 플릿이 홈페이지 상에서 얼마 만큼의 크기를 가지는지를 알아내기 위해 사용하고 있습니다. ◇ Component.requestFocus() requestFocus 메소드는 OS에게 Focus를 요청합니다. Focus를 가진 윈 도우만이 키보드 입력을 받을 수 있으므로 Tank애플릿이 키보드 입력을 받을 수 있게 하기위해 사용되었습니다. ◇ Component.keyDown(Event evt, int key) Component.keyUp(Event evt, int key) 키보드가 PUSH 되거나 POP 될 때 OS에 의해 불려지는 핸들러 입니다. 첫번째 인수로 발생된 이벤트에 대한 정보가 넘어오고, 두번째 인수로 는 PUSH/POP된 키의 코드가 전달 됩니다. 이 두메소드는 boolean 값을 리턴하는데 true 는 모든 처리가 끝났음을 알리는 것이고, false 는 두 메소드가 포함된 Component 의 부모 Component 에게 이벤트를 넘기라는 의미 입니다. 보통 true를 사용합니다. 3.2 애플릿에서 마우스는 어떻게 처리합니까? 입력장치 제어 방법 두번째로 마우스에 대해서 알아보겠습니다. 마우스는 키보드에 비해서 훨씬 다양한 이벤트를 발생시킵니다. 마우스의 버튼이 PU- SH/POP될 때, 마우스 커서가 애플릿 영역을 돌아다닐 때, 마우스 버튼을 누 른채로 애플릿 영역안을 돌아다닐 때, 마우스 커서가 애플릿 영역을 빠져나 가고 들어올 때 각각 OS에 의해 이벤트가 발생됩니다. 이 이벤트들에 대한 핸들러는 Component에 다음과 같이 정의 되어 있습니다. public boolean mouseEnter(Event evt, int x, int y) : 마우스 커서가 애플릿안으로 들어옴 public boolean mouseExit(Event evt, int x, int y) : 마우스 커서가 애플릿 밖으로 나감 public boolean mouseMove(Event evt, int x, int y) : 마우스 커서가 애플릿 내를 돌아다님 public boolean mouseDown(Event evt, int x, int y) : 마우스 버튼이 눌려짐 public boolean mouseDrag(Event evt, int x, int y) : 버튼을 누른채로 마우스를 움직임 public boolean mouseUp(Event evt, int x, int y) : 마우스 버튼을 놓음 [표2] 마우스 이벤트 처리 핸들러 마우스 이벤트 핸들러들은 공통적인 인수를 가지고 있는데, 두 번째와 세 번째 인수인 (x, y)는 해당 이벤트가 발생한 시점에서 마우스커서의 애플릿 내의 위치를 뜻합니다. 연습삼아 간단한 프로그램을 만들어 보도록 하겠습 니다. 화면내에 푸른색 상자가 하나 디스플레이 되고, 윈도우즈에서 아이콘 을 옮기듯이 마우스로 옮길 수 있도록 해보죠. 이것을 구현하는 방법은 상자의 내부에서 마우스의 버튼이 눌려지면 상자 는 이동 가능한 상태로 변하고, 마우스 드래깅(버튼을 누르고 커서를 옮김) 이 일어나면 상자의 위치를 변화시키면 될것입니다. 현재 상자의 상태가 이 동가능한 상태가 아닐 경우 마우스 드래깅에는 별 반응을 보이지 않아야 합 니다. 소스를 보시기 바랍니다. 01: import java.awt.*; 02: import java.applet.*; 03: 04: public class MouseTest extends Applet 05: { 06: private Rectangle box; // 화면에 나타날 상자 07: private Color boxColor; // 상자의 색깔 08: private boolean boxFlag; // 상자의 상태 플랙 09: 10: public void start() 11: { 12: int x, y, w, h; 13: 14: // 애플릿의 크기에 따라 상자의 크기를 조절 (애플릿 크기의 1/10으로) 15: w = size().width / 10; 16: h = size().height / 10; 17: x = (size().width - w) / 2; 18: y = (size().height- h) / 2; 19: 20: // 상자를 만든다 21: box = new Rectangle(x, y, w, h); 22: 23: // 상자의 초기 색깔과 상태를 설정 24: boxColor = Color.blue; 25: boxFlag = false; 26: } 27: 28: public void update(Graphics g) 29: { 30: // 나쁜버릇 제거.. 31: paint(g); 32: } 33: 34: public void paint(Graphics g) 35: { 36: // 화면을 바탕색으로 지우고 37: g.setColor(getBackground()); 38: g.fillRect(0, 0, size().width, size().height); 39: // 상자를 그린다 40: g.setColor(boxColor); 41: g.fillRect(box.x, box.y, box.width, box.height); 42: } 43: 44: public boolean mouseDown(Event e, int x, int y) 45: { 46: // 상자위에서 버튼이 PUSH됐으면 47: if (box.inside(x, y)) { 48: // 상자의 색깔을 바꾸고 눌려짐 상태를 true로 한다 49: boxFlag = true; 50: boxColor = Color.red; 51: repaint(); 52: } 53: return true; 54: } 55: 56: public boolean mouseUp(Event e, int x, int y) 57: { 58: // 버튼이 POP 됐으면 상자 상태를 false로 하고 색깔을 바꾼다 59: boxFlag = false; 60: boxColor = Color.blue; 61: repaint(); 62: return true; 63: } 64: 65: public boolean mouseDrag(Event e, int x, inty) 66: { 67: // 상자의 눌려짐 상태가 true일 경우 드래그가 발생하면 68: if (boxFlag) { 69: // 상자를 이동시킨다 70: box.x = x - box.width / 2; 71: box.y = y - box.height / 2; 72: repaint(); 73: } 74: return true; 75: } 76: } [소스3] MouseTest.java 앞의 설명대로 상자는 현재의 상태를 boxFlag에 기록하고 있습니다. 사용 자에 의해 버튼이 눌려져 44번 라인의 mouseDown 메소드가 호출되면 버튼이 눌려진 위치가 상자의 영역에 포함되는지 검사합니다. 만약 포함이 되어 있 다면 상자의 상태를 이동가능한 상태로 바꾸고, 상자의 색상을 붉은 색으로 바꿔준 후 화면을 갱신합니다. 눌려진 버튼이 놓여지면 56번 라인의 mouse- Up 메소드가 호출되고, 상자의 상태를 이동 불가능 상태로 바꾸고, 색깔을 푸른색으로 되돌리고 화면을 갱신합니다. 드래깅을 했을 경우 65번 라인의 mouseDrag 메소드가 호출되고 현재 상자 의 상태가 이동 가능한 상태이면 상자의 위치를 이동시키고, 이동 불가능한 상태라면 그냥 무시합니다. ☞ 새로 등장한 것들 ◇ Rectangle - Rectangle(int x, int y, int w, int h) 이 클래스는 사각형을 정의하고 사각형에 대한 각종 연산을 제공합니 다. 생성시에 사각형의 좌측 상단좌표 (x, y)와 사각형의 가로 세로 길 이 (w, h)를 넘겨줘야 합니다. Rectangle클래스의 데이터 멤버는 x, y, width, height 등이 있습니다. ◇ Rectangle.inside(int x, int y) 주어진 좌표 (x, y)가 사각형내에 포함되는지 여부를 체크합니다. 사 각형 내에 포함되면 true값을 리턴하고, 포함되지 않으면 false값을 리 턴합니다. 이상으로 화면처리에 관한 것은 모두 끝이 났습니다. 이정도로 게임에 사 용되는 모든 그래픽 기법을 배운 것은 아니지만, 앞으로 공부해 나갈것들의 기본이 되는 내용들이므로 무척 중요한 내용들 입니다. 뒷장을 공부하면서 이해가 잘 안되는 부분이나 막히는 부분이 있을 때는 다시 차분히 이 절을 읽어 보시면 많은 도움이 될 것입니다.

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

자바게임프로그래밍#5  (0) 2010.10.28
자바게임프로그래밍#4  (0) 2010.10.28
자바게임프로그래밍#3  (0) 2010.10.28
자바게임프로그래밍#2  (1) 2010.10.28
자바게임프로그래밍#1  (1) 2010.10.28

 

자바 게임 프로그래밍 강좌 5

하이텔 자바 동호회 김명현 (MadOx@hitel.net)님의 글입니다.
이글은 김명현님의 동의없이 함부로 배포하실 수 없습니다.

안녕하세요? 강좌가 좀 늦었습니다. 주말에는 개인적인 일로 무척 바빴습니 다. 그래서 강좌가 늦어진거죠. 이번 강좌는 스프라이트와 키보드 처리에 관련된 내용입니다. 키보드나 마우스 처리는 JDK1.0.x대와 1.1.x대 에서 차 이가 나는 부분이므로, 테스트는 해보지 않았으나, 문제가 발생할 소지가 다분히 있습니다. 될 수 있으면 자료실에 있는 JDK1.0.2 를 사용 하시는 것 이 좋을 것입니다. 만약에 원인모를 이유로 애플릿이 정상 작동 안된다면, 십중 팔구 JDK1.0.x와 JDK1.1.x의 이벤트 처리방식의 차이 때문일 것입니다 * 본 문서는 Java Development Kit 1.0.2 를 기준으로 작성되었습니다. * Java Development Kit 1.1.x 버젼을 사용할 경우 일부 소스가 실행이 되지 않을 수 있습니다. 2.3 스프라이트는 어떻게 처리하나요? 게임에서 스프라이트의 처리는 거의 필수적입니다. (스프라이트라는 것은 배경을 파손하지 않고 이동하는 이미지를 뜻합니다. 게임기나 8Bit MSX등에 서는 스프라이트를 처리하기 위해 VDP 라고 하는 칩들이 있고, 이들이 독립 적으로 스프라이트들을 관리하며 배경 손상없이 이동을 가능하게 해줍니다. 하지만 PC나 SUN Sparc 등의 컴퓨터에서는 VDP가 따로 존재하지 않고, 대부 분의 VGA 나 그래픽 카드들이 스프라이트를 독립적으로 관리해 주지 않으므 로, 일일이 프로그램으로 배경복구를 해줘야 합니다.) Java 는 스프라이트 관련 메소드를 지원하지 않습니다. 그런데 어떻게 스프라이트를 지원하느냐 구요? 그것은 앞서 배운 DoubleBuffering기법을 이용하면 간단히 해결 됩니 다. 배경을 복구하는 작업과 스프라이트를 다른 곳으로 이동시키는 작업을 우리의 눈에 보이지 않는 메모리 버퍼내에서 하게 되므로 게임을 즐기는 플 레이어 입장에서는 하드웨어 스프라이트와 별반 차이를 느끼지 못하게 됩니 다. ● 투명색의 구현 우선 지난 강좌의 [소스4] CutBitmap.java 를 실행하여 보십시오. 잘려진 빨강, 녹색, 파랑 3개의 공이 따로따로 아래에 그려져 있을 것입니다. 그런 데 원래그림의 흰색 배경도 함께 짤려져 공이 아니라 마치 스케치북에 그린 그림조각 같아 보일 것입니다. 실제 게임에서도 캐릭터가 사각형의 배경에 갖혀서 돌아 다니는 것을 여러분은 원하지 않을 것입니다. 그런데 CropIma- geFilter에 의해 짤려지는 그림은 어짜피 사각형이므로 동그랗게 공을 짤라 낼 수는 없습니다. 셀 애니메이션(Cell Animation)에서는 그림을 투명한 셀 로판지위에 그리고, 그것을 배경위에 놓아서 애니메이션을 구현합니다. Ja- va에서도 마찬가지로, 흰색 바탕이 투명한 색이라면 사각형으로 잘려지더라 도 큰 문제는 없을 듯 합니다. (실제 그림은 사각형이지만, 투명한 영역은 뒷부분의 배경이 보이게 되므로, 그림이 폐곡선 형태인것 처럼 보이게 될껍 니다.) Java는 GIF89a형식을 지원합니다. GIF89a는 발전된 GIF로써 투명색이라는 것을 지원하고 있습니다. Java프로그램에서는 투명색을 위해 아무것도 해줄 것이 없습니다. 단순히 이미지 파일내의 바탕색을 투명색으로 지정해 주는 것만으로 투명색 구현을 할 수 있는 것입니다. 앞 절에서 만들었던 CutImage애플릿에 사용되는 이미지는 "Ball.GIF"입니 다. 이 이미지를 GIF89a형식으로 바꾸고, 투명색을 지정해 보겠습니다. ◇ 투명색을 지원하는 유틸리티들 GIF89a형식에서 투명색을 지원하는 유틸리티는 여러가지가 있습니다. 이들 중에서 우리가 사용할 것은 "GIFTRANS.EXE"라고 하는 프로그램 입 니다. 사용방법을 배워 봅시다. (이것 외에도 LViewPro, GifConstruct- on Kit, PhotoShop등 많은 유틸리티가 투명GIF를 지원합니다.) ① 투명색으로 선정할 색깔의 번호를 알아낸다 먼저 "GIFTRANS.EXE" 를 이용해 투명색으로 만들 색깔의 번호를 알아 내야 합니다. "Ball.GIF"에서 투명색은 완전한 흰색으로 (RGB농도가 각 각 255, 255, 255)입니다. "GIFTRANS.EXE"에서 색상리스트를 보는 옵션 은 '-l'입니다. <실행의 예> C:\JavaGame>GIFTRANS.EXE -l BALL.GIF Global Color Table: Color 0: Red 255, Green 255, Blue 255, #ffffff Color 1: Red 199, Green 43, Blue 43, #c72b2b Color 2: Red 199, Green 43, Blue 87, #c72b57 Color 3: Red 199, Green 43, Blue 131, #c72b83 Color 4: Red 199, Green 43, Blue 175, #c72baf Color 5: Red 179, Green 43, Blue 199, #b32bc7 . . . Color 250: Red 170, Green 0, Blue 170, #aa00aa Color 251: Red 170, Green 0, Blue 0, #aa0000 Color 252: Red 0, Green 170, Blue 170, #00aaaa Color 253: Red 0, Green 170, Blue 0, #00aa00 Color 254: Red 0, Green 0, Blue 170, #0000aa Color 255: Red 0, Green 0, Blue 0, #000000 실행 결과로 색번호와 RGB농도가 출력됩니다. 그런데 너무 빨리 지나 가서 제대로 알아 볼 수가 없을 것입니다. "GIFTRANS.EXE" 는 Color T- able List를 표준에러 스트림으로 출력합니다. 이것을 파일로 저장하는 방법은 "-e 출력파일명"옵션을 주면 됩니다. 위의 컬러 테이블에서 0번 색깔의 RGB 농도가 255, 255, 255로 "Ball.GIF"의 배경색임을 알 수 있 습니다. ② 투명색 처리를 한다 이제 투명색으로 만들 색번호를 알아냈으니 투명색 처리를 하도록 하 겠습니다. "GIFTRANS.EXE"는 원본 이미지를 GIF89a로 바꾼 내용을 표준 출력 스트림으로 출력합니다. (MS-DOS에서 표준 출력은 화면이므로, 화 면에 변환된 내용들이 주욱 나올것입니다.) "-o 출력파일명" 옵션으로 표준 출력을 파일로 보낼 수 있습니다. <실행의 예> C:\JavaGame>GIFTRANS.EXE -t 0 -o Ball2.GIF Ball.GIF -t 0 : 0번색을 투명색으로 지정한다 -o Ball2.GIF : 표준출력(GIF89a로 변환된 내용)을 Ball2.GIF로 출력 Ball.GIF : 원본 이미지의 파일명 변환이 끝났습니다. 디렉토리의 내용을 살펴보면 "Ball2.GIF"가 생성 되어 있을것입니다. 이 이미지의 이름을 "Ball.GIF" 로 바꾸고 바로 앞 절에서 만든 [소스4] CutBitmap.java 를 실행해 보십시오. 그림의 바깥 쪽 흰색 테두리가 없어진 것을 확인하실수 있을 것입니다. 시간이 된다 면 <BODY> Tag에 배경그림을 다른것으로 줘서 실제 뒷 배경을 파손하지 않는지 테스트 해보셔도 좋을것입니다. 3. 그림을 마음대로 움직여 볼까요? 이번 절에서는 지금까지 배워온 기법들에 덧붙여 화면상의 그림을 마음대 로 움직일 수 있는 기법을 배우게될 것입니다. Java에서 사용가능한 입력장 치는 키보드, 마우스 두가지 입니다. 불행이도 게임패드, 죠이스틱은 아직 지원하지를 않습니다. 3.1 애플릿에서 키보드는 어떻게 처리합니까? GUI 환경의 운영체제에서 사용자가 할 수 있는 일은 무척 많습니다. 윈도 우상에 존재하는 각종 버튼을 누를수도 있을 것이고, 키보드를 직접 누를수 도 있을 것이고, 아예 윈도우를 닫아버릴수도 있을 것입니다. 이런 사용자 의 행동을 응용프로그램이 직접 체크를 한다면 응용프로그램을 만드는 일이 무척 고역일 것입니다. 어떤 사람이 어떤 행동을 취할지 일일히 경우의 수 를 따져 프로그램하기란 여간 힘든일이 아닐테니까요. MS-Windows를 비롯한 대부분의 GUI 운영체제들은 사용자들의 행동을 감시 하고, 친절하게 이들의 행동을 응용 프로그램에게 통보해 줍니다. 덕분에 응용프로그램들은 일일이 사용자들의 행동을 체크할 필요가 없습니다. 이때 운영체제로 부터 응용프로그램으로 통보되는 내용을 메시지(Message) 또는 이벤트(event)라고 부릅니다. 응용 프로그램에서는 각각의 이벤트에 대한 핸들러(Handler : 해당 이벤트를 처리하기 위한 메소드)를 만들어 줌으로써 사용자의 요구를 수용할 수 있게 되는 것입니다. 예를 들어 사용자가 윈도 우를 Minimize 시킨후 다시 이를 원래 크기로 환원시켰다면, OS 는 응용 프 로그램에게 '윈도우의 내용을 다시 그리시오' 라고 'repaint' 이벤트를 발 생시키고 통보를 할 것입니다. 응용 프로그램은 repaint이벤트에 대한 핸들 러로 paint메소드를 가지고 있으므로 paint 메소드를 실행해 윈도우의 내용 을 다시 그리게 되고 우리는 정상적인 화면을 볼 수 있게 되는 것입니다. 'repaint'이벤트와 마찬가지로 사용자가 키보드를 눌렀을때에도 이벤트가 발생되고, 해당 응용 프로그램으로 OS가 통보합니다. 3.1.1 키보드에서 발생할 수 있는 이벤트의 종류는? 키보드에서 발생할 수 있는 이벤트는 두 가지 입니다. 우리가 키를 누를 때 KeyDown 이벤트가 발생되고 눌렀던 키를 놓을 때 KeyUp 이벤트가 발생되 어 현재 포커스(focus)를 가지고 있는 Component(거의 모든 AWT요소들의 최 상위 클래스입니다)에게 통보됩니다. 각각의 이벤트에 대해서 Component 는 해당 이벤트를 처리하기 위한 메소 드를 제공 하는데, KeyDown 이벤트에는 keyDown 메소드, KeyUp 이벤트에는 keyUp메소드가 불려지게 됩니다. 이 두 개의 메소드는 Component 클래스에 다음과 같이 정의되어 있습니다. public boolean keyDown(Event evt, int key); public boolean keyUp(Event evt, int key); [표1] 키보드 이벤트 처리 핸들러 기본적으로 위의 두 메소드는 별다른 일을 하지 않도록 정의 되어 있으므 로, 우리는 paint 메소드들 다시 정의한 것처럼 이 메소드들을 다시 정의함 으로써 원하는 일을 할 수 있을 것입니다. 3.1.2 키코드를 출력해주는 프로그램을 만들어 봅시다. 키보드 처리를 하는 방법을 연습하기 위해 간단히 키를 누르면 해당 키의 코드 값을 출력해주는 애플릿을 만들어 보도록 합시다. 게임에서 사용되는 각종 키들의 코드 값을 알아내는데 사용할 것이므로 꼭 필요한 유틸리티 입 니다. 01: import java.awt.*; 02: import java.applet.*; 03: 04: public class ReadKeyCode extends Applet 05: { 06: private String strMsg; // 출력할 메시지를 담을 변수 07: 08: public void start() 09: { 10: // 처음에는 아무것도 안눌려졌음 11: strMsg = "No key pressed"; 12: } 13: 14: public void paint(Graphics g) 15: { 16: // 화면에 메시지 출력 17: g.drawString(strMsg, 10, 45); 18: } 19: 20: public boolean keyDown(Event e, int key) 21: { 22: // 눌려진 키값을 출력하기 위해 문자열을 만들고 23: strMsg = "Key code is (" + key + ")"; 24: // 화면 갱신 25: repaint(); 26: return true; // 처리 완료! 27: } 28: 29: public boolean keyUp(Event e, int key) 30: { 31: // 키보드에서 손을 뗏으므로 아무것도 안눌려졌음 32: strMsg = "No key pressed"; 33: // 화면 갱신 34: repaint(); 35: return true; // 처리 완료! 36: } 37: } [소스1] ReadKeyCode.java ReadKeyCode 애플릿을 실행해 봅시다. 화면 중앙에 "No key pressed"라는 메시지가 나와 있을 것입니다. 그런데 키보드를 누르면 키 값이 화면에 "K- ey code is (xxx)" 식으로 출력 되어야 될것인데 이상하게도 아무런 반응이 없습니다. 이유는 Web Browser나 AppletViewer가 Focus를 가지고 있기 때문 입니다. 다른 이벤트와는 달리 키보드 이벤트는 Focus를 가지고 있는 윈도 우에게만 전달 됩니다. 때문에 우리가 누른 키보드에 대한 이벤트는 ReadK- eyCode 애플릿이 가져간 것이 아니라 Web Browser나 AppletViewer가 가져간 것이므로, 애플릿은 아무 반응이 없는 것입니다. 마우스커서를 애플릿 영역 으로 옮긴후 한 번 클릭을 해줘서 애플릿이 Focus를 가지게 한 후 키보드를 눌러봅시다. 제대로 하셨다면 우리가 누른 키보드에 대한 코드가 디스플레 이 될 것입니다. 그렇다면 키보드 입력을 받는 모든 애플릿에서 매번 마우 스로 한 번 클릭을 한 후 사용해야 할까요? 그렇지는 않습니다. Component 객체의 메소드중 requestFocus() 라고 하는 메소드가 있는데, 이 메소드를 start()메소드 등에서 한 번 실행해 주면 현재 Focus가 Web Browser나 App- letViewer에서 애플릿으로 넘어가게 됩니다.

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

자바프로그래밍#6  (0) 2010.10.28
자바게임프로그래밍#4  (0) 2010.10.28
자바게임프로그래밍#3  (0) 2010.10.28
자바게임프로그래밍#2  (1) 2010.10.28
자바게임프로그래밍#1  (1) 2010.10.28

 

자바 게임 프로그래밍 강좌 4

하이텔 자바 동호회 김명현 (MadOx@hitel.net)님의 글입니다.
이글은 김명현님의 동의없이 함부로 배포하실 수 없습니다.

안녕하세요? 친소맨입니다. 아 벌써 새벽 3시군요.. 괜히 이상한 사이트에 접속했다가, 컴퓨터가 다운되는 바람에 두번씩이나 편집을 하느라, 이 시간 까지 잠도 못자고.. 흑흑.. 이번에는 비트맵 그래픽을 처리하는 방법을 배우게 됩니다. 게임에서 비트 맵 그래픽은 거의 필수라고 할 수 있죠. 이번 절을 보고 나면, 최소한 애플 릿에 그림한장 정도는 띠우실 수 있을 껍니다. :) * 본 문서는 Java Development Kit 1.0.2 를 기준으로 작성되었습니다. * Java Development Kit 1.1.x 버젼을 사용할 경우 일부 소스가 실행이 되지 않을 수 있습니다. 2. 일단 그림을 띠우고 봅시다. 2.1 화면에 비트맵(Bitmap) 그래픽을 출력해 봅시다. 2.1.1 비트맵(Bitmap) 그래픽이란 무엇입니까? 비트맵 그래픽은 우리가 일상적으로 흔히 접할 수 있는 그림 파일들을 말 합니다. AUTOCAD나 3D Studio등으로 만들어진 도면들은 Vector 방식으로 저 장되어 있습니다. 3차원 공간 또는 2차원 공간속에서 정점(vertex)를 정의 하고, 각 정점으로 부터의 선분(segment)을 정의하고, 또 면(face)들을 정 의하고... 이런식으로 구성, 저장되는 그림은 비트맵이라 부르지 않습니다. 비트맵 그래픽이란 화소 단위로 구성된 그림을 뜻하며, 화소는 화면상에 나타날 수 있는 최소한의 단위인 점(Pixel)이라고 부릅니다. 흔히 접할 수 있는 비트맵 그래픽 파일은 MS-Window95의 바탕화면에 쓰이는 BMP파일이나, 인터넷 상에서 홈페이지를 꾸미기 위해 사용되는 JPEG, GIF 등이 있습니다. 각각 독특한 알고리즘으로 데이터를 압축하여 저장하지만, 화면에 보여질때 는 화면이 나타낼 수 있는 최소단위인 점단위로 표현이 됩니다. 2.1.2 우리는 어떤 비트맵 파일을 사용합니까? 아마 인터넷에서 가장 많이 사용되는 그래픽 파일 형식은 GIF형식일 것입 니다. MS-Windows에서는 BMP를 많이 사용하고, X-Windows에서는 XBM을 주로 사용합니다. 하지만 GIF는 어떤 한 기종에 국한되지 않고, 여러 기종에 걸 쳐 골고루 사용되며, Java 가 지원가능한 형태중 투명색을 유일하게 지원하 는 형식이기도 합니다. 투명색은 게임에서 상당히 중요하므로 특별히 큰 배 경을 제외하고는 GIF89a 형식을 사용할 것이며, GIF 의 용량이 너무클 경우 에 한하여 압축률이 좋은 JPEG를 사용할 것입니다. 2.1.3 그림을 화면에 출력해 봅시다. 비트맵 그래픽파일을 읽어 애플릿 영역에 나타내어 보도록 합시다. 예쁘 게 웃고 있는 노란 스마일 그림 어떻습니까? 좋으시다구요? 네, 그럼 화면 에 노란 스마일을 출력해 보도록 노력해 보죠. 01: import java.awt.*; 02: import java.applet.*; 03: 04: public class ShowBitmap extends Applet 05: { 06: private Image imgSmile; // 스마일그림을 담을 Image데이터 07: 08: public void init() 09: { 10: resize(200, 200); // 애플릿의 크기를 200x200으로 11: } 12: 13: public void start() 14: { 15: // 스마일 그림을 애플릿으로 가져온다 16: imgSmile = getImage(getCodeBase(), "Smile.GIF"); 17: } 18: 19: public void paint(Graphics g) 20: { 21: // 화면에 스마일을 그린다 22: g.drawImage(imgSmile, 0, 0, this); 23: } 24: } [소스1] ShowBitmap.java 위의 소스는 스마일 비트맵을 읽어 애플릿 영역에 뿌려주는 일을 합니다. 실행 결과는 웹브라우저 상의 애플릿 영역에 징그럽게 웃고 있는 노란 스마 일이 보일것입니다. (자료실에서 소스를 다운받아 실행해 보십시오) 스마일을 출력하기 위해 ShowBitmap 애플릿이 어떠한 행동을 했는지 따라 가 보면, init메소드에서 애플릿의 크기를 200x200으로 조절합니다. 다음으 로 start메소드에서 16번 라인의 getImage메소드에 의해 스마일의 이미지인 "Smile.GIF"를 데이터 멤버 imgSmile 에 저장시킵니다. 마지막으로 화면에 스마일 이미지를 출력해 줍니다. ☞ 새로 등장한 것들 ◇ Applet.getImage(URL url, String name) getImage 메소드는 Applet 클래스의 멤버 함수입니다. 지정된 url로 부터 name의 이름을 가진 이미지를 가져올 것입니다. 이미지가 실제 로 드되는 시점은 getImage메소드가 실행된 시점이 아니라 그 이미지를 최 초로 사용하는 시점입니다. [소스1]에서 본다면 최초로 paint 메소드가 호출되어서, 22번 라인의 drawImage메소드가 실행되면서 로드되는 것입 니다. ◇ Applet.getCodeBase() 이 메소드는 현재 실행중인 애플릿을 로드한 시스템의 URL 을 되돌립 니다. [소스1]을 살펴 보면 이미지가 위치하고 있는 URL을 명시하기 위 해 애플릿의 URL을 읽어올 목적으로 사용되었음을 알 수 있습니다. 이미지를 서버로부터 가져오고, 이것을 화면에 출력하는 것을 알아보았습 니다. 생각보다 무척 간단했을 것입니다. 그런데 아직 우리가 해결해야 할 문제점이 남아있습니다. 이 문제점을 다음단원에서 알아보도록 하겠습니다. 2.2 효과적인 그림 로딩법을 배워봅시다. 2.2.1 비트맵의 실제 로드 타이밍에 의한 문제점 앞 단원에서 getImage 메소드를 설명할 때 잠시 언급한 바 있었던 문제점 입니다. getImage메소드는 실행되는 즉시 비트맵 이미지를 서버로에서 사용 자 시스템으로 전송하지 않는다고 했습니다. 이 때문에 [소스1] 의 22번 라 인에서 실제로 그 이미지를 사용할 때 로드가 되는데, 만약 넷트웍 정체로 인해 그림이 반 정도 전송되고 멈춰있는 상태라고 가정해 봅시다. 이미 pa- int메소드가 호출되어 화면을 갱신하기 위해 drawImage 메소드가 실행이 되 었고, drawImage메소드는 아직 반밖에 전송되지 않은 스마일을 화면에 출력 할 것입니다. 우리는 반쪽짜리 스마일을 보게 되는 것이죠. 이러한 문제는 로드해야할 이미지의 숫자가 많을 수록 더욱더 심각해 집니다. Java는 쓰레 드 기반의 언어이므로 이미지를 서버로부터 가져오는 역할을 프로그램의 주 흐름이 맡지 않고, 각각 따로 쓰레드를 생성시켜 처리합니다. 덕분에 이미 지가 로드되는 동안 다른 일을 할 수 있고, 여러개의 이미지를 한꺼번에 서 버로 부터 전송해 올 수 있지만 네트워크의 속도가 우리가 원하는 만큼 빠 르지않기 때문에 미완성된 이미지들이 화면상을 활개치며 돌아다니게 될 것 입니다. (서서히 이미지가 로드되어 가면서 완성되는 과정을 눈으로 확인할 수 있겠죠) 실제 게임에서는 많은 수의 이미지들이 사용됩니다. 비트맵의 로드 타이 밍문제로 인해 반쪽짜리 플레이어가 반쪽짜리 총알에 맞아 반쪽짜리 화염을 내뿜으며 화면상에서 사라지는 것을 플레이어들은 별로 원하지 않을 것입니 다. 2.2.2 HTTP 컨넥션에 의한 문제점 Java는 서버에서 이미지를 가져오기 위해 HTTP 컨넥션을 이용합니다. HT- TP 프로토콜은 데이터를 가져오기 위해 매번 'Connect - Transfer - Close' 3단계 과정을 거치며, 이 때문에 전송효율이 좋지를 않습니다. 더욱이 많은 수의 이미지를 사용하는 JavaGame 애플릿의 경우 그 정도가 더욱더 심해 수 시로 'Connect - Transfer - Close' 를 하게되고, 게임에서 사용되는 작은 그래픽 조각 하나하나를 모두 별개의 이미지 파일로 저장했을 경우, 그림이 전송되어 오는 시간보다 'Connect/Close'에 더 많은 시간을 허비하게 될 수 도 있습니다. 2.2.3 MediaTracker 사용하기 2.2.1절에서 제시되었던 문제점은 Java의 MediaTracker 라는 도구를 이용 하여 해결 할 수 있습니다. MediaTracker는 일종의 유틸리티 클래스로서, 각종 매체의 현 상태를 추적할 수 있습니다. 여기에서 매체란 Image 데이터 나 AudioClip(음향) 데이터 등을 말합니다. 현재는 AudioClip은 추적할 수 없고, 이미지 데이터에 대한 추적만 가능합니다. ● MediaTracker의 사용예제 2.1.3절에서 다루었던 ShowBitmap 애플릿을 MediaTracker를 사용한 버전 으로 수정한 것이 [소스2]입니다. 먼저 소스를 살펴보십시오. 01: import java.awt.*; 02: import java.applet.*; 03: 04: public class ShowBitmap2 extends Applet 05: { 06: private MediaTracker tracker; 07: private Image imgSmile; 08: private boolean isLoaded; 09: 10: public void init() 11: { 12: resize(200, 200); 13: } 14: 15: public void start() 16: { 17: // MediaTracker 객체를 생성한다 18: tracker = new MediaTracker(this); 19: // 스마일 그림을 애플릿으로 가져온다 20: imgSmile = getImage(getCodeBase(), "Smile.GIF"); 21: // 스마일 그림을 MediaTracker에 등록한다 22: tracker.addImage(imgSmile, 0); 23: // 이미지의 로드완료 여부를 나타내는 flag 24: isLoaded = false; 25: 26: // 이미지가 로드될때까지 기다린다 27: try { 28: tracker.waitForID(0); 29: } catch (InterruptedException ie) {} 30: isLoaded = true; 31: } 32: 33: public void paint(Graphics g) 34: { 35: // 이미지 로드에 실패했다면 화면을 빨간색으로 36: if ((tracker.statusAll(true) & 37: MediaTracker.ERRORED) != 0) { 38: g.setColor(Color.red); 39: g.fillRect(0, 0, size().width, size().height); 40: return; 41: } 42: if (isLoaded) 43: // 이미지가 로드 됐다면 화면에 스마일을 그린다 44: g.drawImage(imgSmile, 0, 0, this); 45: else 46: // 로드중이면 메시지 출력 47: g.drawString("Loading Image....", 0, 0); 48: } 49: } [소스2] ShowBitmap2.java ◇ MediaTracker 객체 생성 [소스2]의 18번 라인에서 MediaTracker객체를 생성하고 있습니다. M- ediaTracker 객체의 생성자는 이미지가 그려질 Component객체를 요구하 는데, 현재 실행중인 애플릿의 클라이언트 영역에 그리는 경우가 대부 분이므로, 'this'를 넣어 주면됩니다. ◇ 미디어 등록 생성된 MediaTracker 객체에 추적을 원하는 미디어를 등록해 줘야 합 니다. [소스2]의 22번 라인에서 미디어를 등록하고 있는데, 이때 각각 의 미디어마다 고유의 아이디 번호를 부여하고, 이 아이디 번호를 통해 관리를 합니다. 한 개의 아이디로 여러개의 미디어를 한꺼번에 관리할 수 있으므로 같은 그룹의 이미지들은 동일한 아이디를 부여해 함께 관 리할 수 있습니다. ◇ 등록된 미디어 감시 이번에는 MediaTracker객체에 등록된 미디어를 감시하는 방법입니다. MediaTracker는 이와 관련된 여러 가지 메소드를 제공하는데 앞의 소스 에서 사용된것만 설명하도록 하겠습니다. ① 이미지 로드가 완료될때까지 대기하기 이미지로드가 완료될때까지 프로그램의 수행을 중지시킬 수 있는 방법입니다. MediaTracker의 waitForID 메소드를 이용하여 해당 아 이디의 이미지가 로드완료 될때까지 프로그램을 멈추게 할 수 있습 니다. [소스2]의 27-29번 라인에서 직접 사용되고 있으니 참고하십 시오. ② 이미지의 상태 감시하기 [소스2]의 36번 라인에서 사용된 statusAll 메소드는 현재 Medi- aTracker에 등록된 모든 미디어들에 대한 상태를 알려줍니다. 만약 이미지를 로드하는 중에 에러가 발생했다면 statusAll메소드는 ER- RORED 비트플랙을 1 로 설정하게 되고, 우리는 MediaTracker의 ER- RORED 상수를 논리 AND 연산을 취하여 에러 여부를 검사할 수 있습 니다. ☞ 새로 등장한 것들 ◇ MediaTracker MediaTracker 클래스는 각종 미디어(Image, AudioClip 등)의 상태를 추적, 감시할 수 있도록 하는 기능을 가진 유틸리티 클래스입니다. Me- diaTracker 객체를 생성할 때 추적 대상이 될 미디어가 출력될 Compon- ent객체를 인수로 넘겨줘야 하며, 일반적으로 애플릿 자신을 나타내는 'this' 포인터를 넘겨주게 됩니다. ◇ MediaTracker.addImage(Image img, int id) MediaTracker 객체에 추적을 위한 이미지를 등록하는 메소드 입니다. 각각의 이미지마다 고유의 숫자 ID를 부여할 수 있고, 함께 로드되어야 할 이미지들일 경우 같은 ID를 부여해 한꺼번에 관리할 수도 있습니다. ◇ MediaTracker.waitForID(int id) 주어진 ID의 미디어가 로드될때까지 기다리도록 하는 메소드 입니다. 또 해당 아이디가 부여된 미디어중 로드가 되지않은 것들이 있다면, 이 들을 로드하도록 할 것입니다. waitForID 메소드는 미디어의 로드가 끝 나지 않고 중단된 경우 InterruptedException을 발생시키므로 try - c- atch 문을 이용해 예외를 잡아줘야 합니다. ◇ MediaTracker.waitForAll() 아직 등장하지 않은 메소드이지만 한꺼번에 설명 하도록 하겠습니다. waitForID메소드는 ID별로 관리를 하지만 waitForAll 메소드는 현재 M- ediaTracker 에 등록된 모든 미디어들에 대해 로드가 완료 될때까지 대 기 하게 됩니다. 그외의 사항은 waitForID메소드와 같습니다. ◇ MediaTracker.statusAll(boolean load) 이 메소드는 MediaTracker에 등록된 모든 미디어들에 대한 상태를 되 돌립니다. statusAll 메소드에 의해 넘어오는 미디어들의 상태는 4가지 이며, 그 값들은 아래와 같습니다. MediaTracker.LOADING : 미디어가 로드되는 중이다 MediaTracker.ABORTED : 미디어의 로드를 취소했다 MediaTracker.ERRORED : 미디어를 로드하는 도중 오류가 발생했다 MediaTracker.COMPLETE: 미디어 로드를 완료했다 [표1] MediaTracker가 제공하는 미디어의 상태 값 각각의 상태를 체크하는 방법은 statusAll메소드가 되돌린값과 [표1] 의 값을 논리 AND 한 값을 이용하면 됩니다. [소스2]의 36번 라인에서 미디어 로드에 오류가 발생한것인지를 체크하기 위해 MediaTracker.ER- RORED값과 AND 한 후 값을 비교하고 있음을 알 수 있습니다. statusAll 메소드의 인수로 넘어가는 값은 미디어가 로드되지 않은 경우, 그 값이 true면 미디어를 로드시키고, false면 보류합니다. 일반적으로 true 값 을 사용합니다. ◇ MediaTracker.statusID(int id, boolean load) statusAll 메소드와 하는 일은 거의 같습니다. 다만 MediaTracker에 등록된 미디어 전체의 상태를 되돌리는 것이 아니라, 주어진 ID를 가지 는 미디어들에 대한 상태만을 되돌립니다. 그외의 사항은 statusAll과 동일합니다. 2.2.4 큰 그림 조각내기 2.2.2 절에서 알아 본 HTTP 의 전송방식 때문에 많은수의 작은 이미지를 가져오는것 보다 큰 사이즈의 그림 하나를 가져오는 것이 더 효율 적이라는 것을 알 수 있었습니다. 게임에서는 많은 수의 이미지들이 사용 됩니다. 이 것들을 따로따로 별개의 이미지 파일로 관리하는 것도 쉬운일이 아니고, 서 버로 부터 로드시 소요되는 시간도 무척 길어질 것이므로, 이것을 해결하기 위해 다수의 이미지를 하나의 그림파일에 저장 하고 그 파일을 서버로 부터 가져온후 그림을 조각내어 사용하는 법을 알아보겠습니다. ● ImageFilter Java는 큰 그림을 작은 몇 개의 조각으로 나눌 수 있도록 ImageFilter 라 는 클래스를 제공하고 있습니다. ImageFilter 는 원본 이미지를 좀더 작은 사각형형태로 잘라내기 위한 CropImageFilter와 원본 이미지의 색상에 변화 를 주기위해 주로 사용되는 RGBImageFilter 이렇게 두 가지가 있습니다. 이 절에서는 앞으로 사용될 CropImageFilter에 대해서만 설명 하도록 하겠습니 다. (RGBImageFilter를 이용할 수 있는곳을 하나 예로 들자면, 스트리트 파 이터 등의 게임에 등장하는 캐릭터의 옷색깔을 바꿀 때 사용하면 될 것입니 다) CropImageFilter는 createImage메소드가 기존의 이미지를 Filter처리하여 새로운 이미지를 만들어 낼 때 사용합니다. CropImageFilter는 말그대로 원 본 이미지를 자르기 위한 하나의 필터일 뿐이고, 실제 컷팅 작업을 하는 것 은 FilteredImageSource라는 클래스가 담당합니다. FilteredImageSource 는 createImage메소드가 인수로 요구하는 ImageProduce(인터페이스 입니다) 타 입의 서브클래스 이므로, FilteredImageSource 객체를 바로 createImage 메 소드의 인수로 넘겨줄 수 있습니다. 실제 소스를 보도록 합시다. 01: ImageFilter filter; 02: 03: orgImage = getImage(getCodeBase(), "TEST.GIF"); 04: filter = new CropImageFilter(10, 10, 50, 50); 05: smallImage = createImage(new FilteredImageSource( 06: orgImage.getSource(), filter)); [소스3] ImageFilter사용의 예 위 소스중 4번라인에서 CropImageFilter를 생성합니다. 원본 그림인 org- Image의 좌표(10, 10)을 좌측 상단으로 하는 50x50 크기의 이미지를 얻어오 기 위한 필터입니다. 다음으로 5번 라인에서 실질적으로 원본 이미지에 Fi- lter 처리를 하여 새로운 이미지를 만들어냅니다. 지금까지 알아본 이미지 절단법을 이용하여 만든 애플릿의 소스가 아래에 있습니다. 빨강, 녹색, 파랑 세개의 공이 그려져있는 이미지를가져와서 각 각의 공을 따로 분리해서 출력하는 예제입니다. 앞에서 거의다 설명한 내용 이므로 소스의 이해에는 문제가 없을 것입니다. 01: import java.awt.*; 02: import java.applet.*; 03: import java.awt.image.*; // Image관계 클래스들이 정의 04: 05: public class CutBitmap extends Applet 06: { 07: private MediaTracker tracker; 08: 09: // 원본 이미지(빨강/녹색/파랑 3개의 공) 10: private Image imgBallAll; 11: 12: // 원본 이미지에서 오려낸 각각의 공 13: private Image imgRedBall; 14: private Image imgGreenBall; 15: private Image imgBlueBall; 16: 17: public void init() 18: { // 하는일이 없다 19: } 20: 21: public void start() 22: { 23: ImageProducer producer; 24: ImageFilter filter; 25: 26: tracker = new MediaTracker(this); 27: // 원본 이미지를 서버로부터 가져온다 28: imgBallAll = getImage(getCodeBase(), "Ball.GIF"); 29: 30: tracker.addImage(imgBallAll, 0); 31: 32: // 원본 이미지로부터 ImageProducer를 추출 33: producer = imgBallAll.getSource(); 34: 35: // 빨간공을 위한 필터를 만든다 36: filter = new CropImageFilter(0, 0, 63, 63); 37: // 그림을 잘라 낸다 38: imgRedBall = createImage( 39: new FilteredImageSource(producer, filter)); 40: tracker.addImage(imgRedBall, 1); 41: 42: // 녹색공을 위한 필터를 만든다 43: filter = new CropImageFilter(63, 0, 63, 63); 44: // 그림을 잘라 낸다 45: imgGreenBall = createImage( 46: new FilteredImageSource(producer, filter)); 47: tracker.addImage(imgGreenBall, 1); 48: 49: // 파란공을 위한 필터를 만든다 50: filter = new CropImageFilter(126, 0, 63, 63); 51: // 그림을 잘라 낸다 52: imgBlueBall = createImage( 53: new FilteredImageSource(producer, filter)); 54: tracker.addImage(imgBlueBall, 1); 55: 56: try { 57: tracker.waitForAll(); 58: } catch (InterruptedException ie) {} 59: } 60: 61: public void paint(Graphics g) 62: { 63: // 모든 이미지가 로드됐으면, 화면에 이미지를 그린다 64: if ((tracker.statusAll(true) 65: & MediaTracker.COMPLETE) != 0) { 66: g.drawImage(imgBallAll, 0, 0, this); 67: g.drawImage(imgRedBall, 0, 100, this); 68: g.drawImage(imgGreenBall, 100, 100, this); 69: g.drawImage(imgBlueBall, 200, 100, this); 70: } else 71: g.drawString("Loading Image....", 20, 20); 72: } 73: } [소스4] CutBitmap.java ☞ 새로 등장한 것들 ◇ ImageFilter ImageFilter 는 Image 데이터를 가공하기 위해 제공되는 클래스 입니 다. ImageFilter는 이미지 데이터 가공을 위한 각종 메소드들을 가지고 있고, FilteredImageSource 클래스에 의해 새로운 이미지를 만들어 낼 때 사용하게 됩니다. ◇ CropImageFilter(int x, int y, int w, int h) CropImageFilter 클래스는 원본 이미지를 작은 조각으로 잘라내는 수 단을 제공하며, ImageFilter 의 서브 클래스 입니다. (x, y)를 좌측 상 단으로 하고, w x h 만큼의 크기로 이미지를 절단합니다. ◇ ImageProducer ImageProducer는 이미지를 재 구성 하기 위해 필요한 클래스 입니다. 33번 라인에서 원본 이미지로 부터 ImageProducer를 추출해내고 있습니 다. ◇ Image.getSource() 해당 이미지로 부터 ImageProducer 인터페이스를 되돌립니다. ◇ FilteredImageSource(ImageProducer orgImg, ImageFilter filter) 이 클래스는 주어진 orgImg(원래 이미지)에 filter 를 적용하여 새로 운 ImageProducer를 되돌려 줍니다. createImage(ImageProducer produ- cer)메소드의 인수로 사용되어 필터 처리가 된 새로운 이미지를 만들어 내게 됩니다. ◇ Component.createImage(ImageProducer producer) 3회 강좌 1.3.3절에서 본바 있는 createImage(int w, int h)메소드와 함께 Component클래스에 정의되어 있습니다. 이 메소드는 주어진 Imag- eProducer로 부터 새로운 이미지를 만들어 냅니다. [소스4]의 38 번 라 인에서 실제로 사용되고 있습니다.

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

자바프로그래밍#6  (0) 2010.10.28
자바게임프로그래밍#5  (0) 2010.10.28
자바게임프로그래밍#3  (0) 2010.10.28
자바게임프로그래밍#2  (1) 2010.10.28
자바게임프로그래밍#1  (1) 2010.10.28

 

자바 게임 프로그래밍 강좌 3

하이텔 자바 동호회 김명현 (MadOx@hitel.net)님의 글입니다.
이글은 김명현님의 동의없이 함부로 배포하실 수 없습니다.

안녕하세요? 두 개 연속 편집 (2 Hit Combo?)의 과업을 달성한 친소맨 입니 다. 꼴은 모니터를 장장 두시간이나 쳐다보고 있었더니, 눈이 아른 아른 거 리는 군요.. @_@ 이번 강좌로 자바 게임 프로그래밍의 1장이 끝났습니다. 이번 강좌까지 만 들어 내게될 각종 소스파일들을 자료실에 업로드 해놓겠습니다. 잘라서 편 집하기 싫으신 분들은 자료실에서 다운로드 받아서 바로 실행해 보세요. * 본 문서는 Java Development Kit 1.0.2 를 기준으로 작성되었습니다. * Java Development Kit 1.1.x 버젼을 사용할 경우 일부 소스가 실행이 되지 않을 수 있습니다. ● 완성된 PingPong2 이제 더블버퍼링을 구현하는 법도 배웠고, Multi-Thread를 구현하는 법도 배웠으니, 실전에 써먹어 보도록 하겠습니다. 아래에 쓰레드와 더블버퍼링 을 이용한 PingPong2 애플릿의 소스가 있습니다. 지금까지 배운 것을 적용 하며 읽다보면 반이상은 이해가 되실테니 천천히 읽어 보고, 다음으로 넘어 가시길 바랍니다. 모르는 부분이 많더라도 낙담하실 필요는 없습니다. 앞으 로 수십번은 더봐야 할 내용이니까요. 01: import java.awt.*; 02: import java.applet.*; 03: import java.lang.Runnable; 04: 05: public class PingPong2 extends Applet implements Runnable 06: { 07: Thread trdMyThread;// 애플릿의 주 쓰레드 08: 09: Image imgOffScr; // 메모리 버퍼로 쓸 Image 10: Graphics grpOffScr; // 메모리 버퍼의 Graphics 핸들 11: 12: int ballXp, ballYp, ballXs, ballYs; 13: 14: public void init() 15: { 16: resize(400, 400); 17: } 18: 19: public void start() 20: { 21: // 아직 쓰레드가 만들어져 있지 않으면 새로 만드시오 22: if (trdMyThread == null) 23: trdMyThread = new Thread(this); 24: 25: // 메모리 버퍼가 만들어져 있지 않다면 400x400의 26: // 크기로 만드시오(애플릿의 영역이 400x400이므로) 27: if (imgOffScr == null) 28: imgOffScr = createImage(400, 400); 29: 30: // 메모리 버퍼에 대한 Graphics 핸들을 얻어 온다 31: grpOffScr = imgOffScr.getGraphics(); 32: 33: ballXp = 100; 34: ballYp = 200; 35: ballXs = 4; 36: ballYs = 4; 37: 38: // 만들어진 쓰레드를 활성화 시킨다 39: trdMyThread.start(); 40: } 41: 42: // 쓰레드의 주 실행 루틴 43: public void run() 44: { 45: // 무한 루프가 쓰레드 안으로 옮겨졌다! 46: while (true) { 47: // paint를 한번 불러준다 48: repaint(); 49: // 0.1초간 쉬고 50: try { 51: Thread.sleep(100); 52: } catch (InterruptedException ie) {} 53: 54: // 공을 옮긴다 55: ballXp += ballXs; 56: ballYp += ballYs; 57: 58: // 벽에 부딪혔으면 방향을 반전 59: if (ballXp > 400 || ballXp < 0) 60: ballXs = -ballXs; 61: if (ballYp > 400 || ballYp < 0) 62: ballYs = -ballYs; 63: } 64: } 65: 66: public void paint(Graphics g) 67: { 68: // 메모리 버퍼를 까맣게 지우고 69: grpOffScr.setColor(Color.black); 70: grpOffScr.fillRect(0, 0, 400, 400); 71: 72: // 메모리 버퍼에 공을 그린다 73: grpOffScr.setColor(Color.white); 74: grpOffScr.fillArc(ballXp, ballYp, 25, 25, 0, 360); 75: 76: // 완성된 그림을 화면에 뿌린다 77: g.drawImage(imgOffScr, 0, 0, this); 78: } 79: 80: public void update(Graphics g) 81: { 82: paint(g); 83: } 84: 85: public void stop() 86: { 87: // 쓰레드가 살아 있다면 활동을 중지시키고, 없애버린다 88: if (trdMyThread != null) { 89: trdMyThread.stop(); 90: trdMyThread = null; 91: } 92: } 93: } [소스5] PingPong2.java 소스의 구조가 2 회 강좌에서 봤던 [표3]의 Runnable Interface를 포함한 Applet의 기본구조와 거의 같아 졌습니다. 내용을 살펴보도록 하겠습니다. ◇ Thread 의 생성 [소스5]에서 쓰레드를 생성하는 곳은 23번 라인입니다. 23번 라인에 서 new 연산자를 이용해 Thread 객체를 생성 합니다. 이때 this라고 불 려지는 특수한 포인터를 넘겨주게 되는데, 여기에서 this 라는 것은 애 플릿 자체를 뜻합니다. Thread의 생성자(Constructor)중 Runnable 인터 페이스를 인수(Parameter)로 받아 들이는 것이 있습니다. 이 생성자는 Runnable 인터페이스의 run메소드를 쓰레드의 주 루틴으로 연결해 주는 역할을 합니다.(Thread의 주 루틴도 run 으로 Runnable 인터페이스에서 의 run과 메소드 명이 같습니다) PingPong2 애플릿 역시 Runnable 인터 페이스를 상속받은 서브 클래스이므로, Runnable 인터페이스를 인수로 하는 생성자에게 넘겨질 수 있고, 그 덕택에 우리의 PingPong2 애플릿 이 쓰레드화(?) 되는 것입니다. 39번 라인에서 trdMyThread라는 쓰레드 를 활성화 시키는데, 이 쓰레드의 주 루틴은 PingPong2 애플릿의 run메 소드이므로, PingPong2애플릿의 run 메소드 부분이 쓰레드가 되어 실행 이 되는 것입니다. (소스의 어느 곳을 봐도 run메소드를 직접 불러주는 곳은 없습니다. 그 이유는 Thread의 start 메소드가 쓰레드를 생성시키 고, 바로 run메소드를 불러주기 때문입니다) ◇ 메모리 버퍼의 생성 이 내용은 앞에서 배운 `Double Buffering의 구현'에서 이미 다룬 내 용이지만, 확인도 할겸해서 한번 더 설명하도록 하겠습니다. [소스5]의 28번 라인에서 더블버퍼링을 위해 메모리버퍼를 만드는 작업을 하고 있 습니다. createImage메소드로 애플릿이 화면상에 차지하고 있는 영역과 크기가 같은 400x400 짜리 Image를 생성 합니다. 다음으로 31번 라인에 서 메모리내에 할당된 Image에 대한 Graphics핸들을 getGraphics메소드 를 이용해 얻어내서 이것을 grpOffScr에 저장합니다. 이후 모든 작업은 grpOffScr을 통해 이루어질 것입니다. ◇ run 메소드 (쓰레드의 주 루틴) paint메소드에서 무한 루프가 없어지는 대신 새로 생긴 run 메소드에 무한 루프가 생겼습니다. 한 두 차례 실행하고 말것이라면 궂이 쓰레드 로 만들 필요가 없기 때문에 쓰레드의 주 루틴은 무한루프인 경우가 많 습니다. run메소드는 우선 repaint메소드를 호출해서 paint메소드를 간 접적으로 불러 줍니다. 바로 paint 메소드를 부르지 않고 repaint 메소 드를 불러주는 이유는 run 메소드 내에서는 paint메소드에 넘겨질 애플 릿에 대한 Graphics 핸들을 얻을 수 없기 때문입니다. 다음으로 Threa- d.sleep(100) 이라는 문장을 수행하게 되는데, Thread 클래스의 메소드 인 sleep는 밀리초 (1/1000초) 단위로 쓰레드의 활동을 중단 시키는 역 할을 합니다. PingPong2 애플릿 에서는 이 수치가 100 이므로 0.1초 정 도의 시간동안 공이 멈추게 됩니다. 이 수치를 작게하면 작게할수록 공 이 빨리 움직일 것입니다. 만약에 이 문장을 빼 버린다면 공은 애플릿 영역속을 미친 듯이 튀어 다닐 것입니다. (너무 빨라서 공이 순간이동 을 하는 것 처럼 보일지도 모릅니다) sleep는 단순히 공의 이동 속도를 지연 시키는것 이외에도 CPU를 다른 프로그램들에게 양보하는 의미에서 주로 사용되어 집니다. ◇ update 메소드 못보던 녀석이 나타났습니다. update메소드를 살펴보면 별로 하는 일 이 없어 보입니다. 단순히 paint 메소드를 불러주는군요. 왜 이렇게 했 을까요? 이유를 알고 싶다면 다음 글을 읽기 전에 update메소드 부분을 주석처리하고 컴파일하여 애플릿을 다시 Web Browser로 불러 보시기 바 랍니다. 아마 엄청나게 껌벅이는 화면을 보실수 있을 것입니다. 우리가 repaint메소드를 불러주면 바로 paint메소드가 불려 지는 것이 아니라, update메소드가 먼져 불려지고, 다음으로 paint메소드가 update 메소드 에 의해 불려지게 됩니다. 그런데 update 메소드는 아주 고약한 버릇을 가지고 있습니다. 그 버릇이 무엇이냐면, 화면을 흰색으로 깨끗이 지우 고 paint메소드를 부르는 것입니다. 이 때문에 화면이 심하게 껌벅이게 되는 것이죠. 그래서 그 못된 버릇을 고쳐주기 위해 부모의 update메소 드를 재정의하여 나쁜 버릇을 없앤 것 입니다. ☞ 새로 등장한 것들 ◇ Component.createImage(int w, int h) 거의 모든 AWT 요소들의 부모인 Component의 메소드로서 지정된 크기 (w x h)의 Image를 만들고 이것을 되돌립니다. ◇ Image.getGraphics() createImage 메소드에 의해서 만들어진 Image 에 한해서 실행 가능한 메소드 입니다. 이 메소드는 메모리에 만들어진 Image에 대한 Graphics 핸들을 되돌립니다. ◇ Graphics.drawImage(Image img, int x, int y, ImageObserver obsrvr) Graphics 핸들로 지정되는 사각영역의 (x, y)좌표를 좌측상단으로 하 는 Image 를 그립니다. 이때 Image 가 완전하지 못할 경우 obsrvr 에게 통보합니다. 보통 obsrvr는 this(애플릿 자체)를 넣어 주면 됩니다. 1.3.4 좀더 범용적인 PingPong을 만들어 봅시다. 이번 단원에서는 APPLET Tag 중 PARAM Tag 를 어떻게 사용하는 지를 배울 수 있을 것입니다. 앞 단원에서 완성시킨 PingPong 애플릿은 범용성이 떨어 집니다. 우리는 공의 크기를 바꾸거나 공의 속도를 바꾸기 위해 매번 컴파 일을 다시 하고 싶지는 않습니다. 또 공의 색깔을 바꾸고 싶어도 다시 컴파 일을 해야 합니다. 무척 불편하군요. 이제 이런 불펀함을 고쳐보도록 합시 다. ● 어떻게 HTML 로 부터 데이터를 넘겨 받을 수 있습니까? 우리는 첫번째 강좌의 1.2 절에서 APPLET Tag 의 사용법에 대해서 배웠습 니다. (기억이 잘 안나시는 분은 다시 첫번째 강좌를 펴서 275 번째 줄부터 읽어 보십시오) 여기에서 <PARAM...> 이라고 적혀있던 Tag가 기억날 것입니 다. 바로 이 Tag 를 이용하여 HTML 이 Java 애플릿에게 데이터를 넘겨줄 수 있습니다. 1.2 절에서 만들어 보았던 HelloWorld 애플릿을 개선하여 우리가 원하는 문장을 출력하는 애플릿으로 만들어 봅시다. 01: import java.awt.*; 02: import java.applet.*; 03: 04: public class ShowMessage extends Applet 05: { 06: private String strMsg; 07: 08: public void init() 09: { 10: resize(100, 100); 11: } 12: 13: public void start() 14: { 15: strMsg = getParameter("MESSAGE"); 16: } 17: 18: public void paint(Graphics g) 19: { 20: g.drawString(strMsg, 20, 45); 21: } 22: } [소스6] ShowMessage.java [소스6] 은 HelloWorld 애플릿을 개조하여 만든 ShowMessage 라는 애플릿 입니다. 이 애플릿은 HTML파일에서 넘어온 메시지를 화면에 출력해 줄 것입 니다. 이 소스에서 주의해서 살펴볼 곳은 15 번 라인 입니다. getParameter 라는 메소드는 인수로 넘어간 문자열과 일치 하는 변수명을 가진 PARAM Tag 로 부터 VALUE 뒤에 명시된 값을 String 형태로 받아 오는 역할을 합니다. 만약 우리가 'I am a boy!' 라는 문장을 출력하고 싶다면 다음과 같이 HTML 파일을 만들어 주면 될 것입니다. 01: <HTML> 02: <BODY> 03: <CENTER> 04: ShowMessage Applet 05: <HR> 06: <APPLET 07: CODE=ShowMessage.class 08: WIDTH=100 09: HEIGHT=100> 10: <PARAM NAME="MESSAGE" VALUE="I am a boy!"> 11: </APPLET> 12: <HR> 13: </BODY> 14: </HTML> [소스7] ShowMessage.html ● PingPong 애플릿을 어떻게 개조할까요? HTML로 부터 데이터를 넘겨 받는 방법을 배웠으니, 이제 PingPong 애플릿 을 좀더 실용적으로 바꿔 봅시다. 다음과 같은 기능을 추가시킬 것입니다. ① RADIUS 변수를 통해 공의 반지름을 넘겨줄수 있도록 한다. ② XSPEED, YSPEED 변수를 통해 한 번에 공이 이동하는 거리를 넘겨 주도 록 한다. ③ DELAY변수를 통해 PingPong애플릿이 쉬는 시간을 조정할 수 있도록 한 다. 우선 알아야할 내용을 정리한후, 소스를 보도록 하겠습니다. ◇ String 값을 int 값으로 바꾸는 방법 getParameter 메소드를 통해 HTML로 부터 받아오는 값은 String 형태 의 값들 입니다. 반지름이나 속도, 지연시간등은 String 값이 아닌 int 값이어야 하므로, 이 값들을 PingPong 애플릿에서 사용하려면 int 형으 로 바꿔야 합니다. <Tip!> (Integer.valueOf(getParameter("변수명"))).intValue(); String을 int로 바꾸기 위해서는 위와 같은 방법을 사용하면 됩니다. 다소 복잡해 보이지만 풀어서 보면 크게 복잡하지는 않습니다. getPar- ameter 메소드로 받아온 변수값을 String형에서 Integer 클래스의 val- ueOf메소드를 이용해 Integer 형으로 변환합니다. 다음으로 Integer 클 래스의 메소드인 intValue메소드로 다시 int 값으로 변환 하면 됩니다. ● 완벽한 PingPong 애플릿 지금까지 배운 지식을 총동원 하여 만들어낸 완벽한 PingPong애플릿의 소 스가 아래에 있습니다. [소스5] 의 PingPong2.java와 거의 대부분의 코드가 동일 하고, HTML 로부터 몇몇파라메터들을 읽어와서 적용하는 부분만 추가 되었습니다. [소스9]는 완성된 PingPong 애플릿을 테스트 하기위한 HTML 파 일 소스입니다. 001: import java.awt.*; 002: import java.applet.*; 003: import java.lang.Runnable; 004: 005: public class PingPong extends Applet implements Runnable 006: { 007: Thread trdMyThread;// 애플릿의 주 쓰레드 008: 009: Image imgOffScr; // 메모리 버퍼로 쓸 Image 010: Graphics grpOffScr; // 메모리 버퍼의 Graphics 핸들 011: 012: int ballXp, ballYp, ballXs, ballYs; 013: int ballRad, delay; 014: 015: public void init() 016: { 017: resize(400, 400); 018: } 019: 020: public void start() 021: { 022: // 아직 쓰레드가 만들어져 있지 않으면 새로 만드시오 023: if (trdMyThread == null) 024: trdMyThread = new Thread(this); 025: 026: // 메모리버퍼가 만들어져 있지 않다면 400x400의 크기로 027: // 만드시오 028: if (imgOffScr == null) 029: imgOffScr = createImage(400, 400); 030: 031: // 메모리 버퍼에 대한 Graphics 핸들을 얻어 온다 032: grpOffScr = imgOffScr.getGraphics(); 033: 034: ballXp = 100; 035: ballYp = 200; 036: ballXs = 037: (Integer.valueOf(getParameter("XSPEED"))).intValue() 038: ballYs = 039: (Integer.valueOf(getParameter("YSPEED"))).intValue() 040: ballRad = 041: (Integer.valueOf(getParameter("RADIUS"))).intValue() 042: delay = 043: (Integer.valueOf(getParameter("DELAY"))).intValue(); 044: 045: // 만들어진 쓰레드를 활성화 시킨다 046: trdMyThread.start(); 047: } 048: 049: // 쓰레드의 주 실행 루틴 050: public void run() 051: { 052: // 무한 루프가 쓰레드 안으로 옮겨졌다! 053: while (true) { 054: // paint를 한번 불러준다 055: repaint(); 056: // 0.1초간 쉬고 057: try { 058: Thread.sleep(delay); 059: } catch (InterruptedException ie) {} 060: 061: // 공을 옮긴다 062: ballXp += ballXs; 063: ballYp += ballYs; 064: 065: // 벽에 부딪혔으면 방향을 반전 066: if (ballXp > 400 || ballXp < 0) 067: ballXs = -ballXs; 068: if (ballYp > 400 || ballYp < 0) 069: ballYs = -ballYs; 070: } 071: } 072: 073: public void paint(Graphics g) 074: { 075: // 메모리 버퍼를 까맣게 지우고 076: grpOffScr.setColor(Color.black); 077: grpOffScr.fillRect(0, 0, 400, 400); 078: 079: // 메모리 버퍼에 공을 그린다 080: grpOffScr.setColor(Color.white); 081: grpOffScr.fillArc( 082: ballXp, ballYp, ballRad, ballRad, 0, 360); 083: 084: // 완성된 그림을 화면에 뿌린다 085: g.drawImage(imgOffScr, 0, 0, this); 086: } 087: 088: public void update(Graphics g) 089: { 090: paint(g); 091: } 092: 093: public void stop() 094: { 095: // 쓰레드가 살아 있다면 활동을 중지시키고 096: // 없애버린다 097: if (trdMyThread != null) { 098: trdMyThread.stop(); 099: trdMyThread = null; 100: } 101: } 102: } [소스8] PingPong.java 01: <HTML> 02: <BODY> 03: <CENTER> 04: PingPong Applet 05: <HR> 06: <APPLET 07: CODE=PingPong.class 08: WIDTH=400 09: HEIGHT=400> 10: <PARAM NAME="RADIUS" VALUE="30"> ← 공의 반지름을 30으로 11: <PARAM NAME="XSPEED" VALUE="2"> ← 가로축 속도를 2 12: <PARAM NAME="YSPEED" VALUE="3"> ← 세로축 속도를 3 13: <PARAM NAME="DELAY" VALUE="10"> ← 지연시간을 10밀리초로 14: </APPLET> 15: <HR> 16: </BODY> 17: </HTML> [소스9] PingPong.html

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

자바프로그래밍#6  (0) 2010.10.28
자바게임프로그래밍#5  (0) 2010.10.28
자바게임프로그래밍#4  (0) 2010.10.28
자바게임프로그래밍#2  (1) 2010.10.28
자바게임프로그래밍#1  (1) 2010.10.28

 

자바 게임 프로그래밍 강좌 2

하이텔 자바 동호회 김명현 (MadOx@hitel.net)님의 글입니다.
이글은 김명현님의 동의없이 함부로 배포하실 수 없습니다.

1.3 절을 다 올릴려구 했는데, 너무 길어져서 도저히 안되겠군요. 근데, 혹 시 GIF로 그려놓은 설명용 그림을 쉽게 TEXT로 바꿀 수 있는 방법이 없을까 요? 설명용 그림을 대부분 생략하고 넘어가고, 글의 내용을 바꿔 버리기는 하고 있는데, 꼭 필요한 그림들도 있어서 뺄 수 도 없고... 일일히 그리자 니 너무 노가다틱 하군요.. 저에게 이런 고통을 안겨준 애아범 호진 대삽님이 미울 따름이에요 ... T_T * 본 문서는 Java Development Kit 1.0.2 를 기준으로 작성되었습니다. * Java Development Kit 1.1.x 버젼을 사용할 경우 일부 소스가 실행이 되지 않을 수 있습니다. 1.3 PingPong 애플릿을 만들어 봅시다. 이번에 우리가 만들게 될 PingPong애플릿은 화면의 애플릿 영역내에서 공 하나가 좌충우돌 튀어 다니는 것입니다. 이번 절을 통해 우리는 Java에서 어떻게 애니메이션(Animation)을 구현하는 지를 배울 수 있을 것입니다. 1.3.1 PingPong 애플릿의 설계 일단 화면상에 공이 튀어 다니는 것을 목표로 하고, 프로그램의 구조같은 것은 추후에 신경을 쓰도록 하겠습니다. PingPong 애플릿이 해야할 일을 대 충 정해보면 아래와 같습니다. ① 화면을 깨끗하게 지운다. ② 이전에 그려졌던 공을 지운다. ③ 화면에 공을 그린다. ④ 공을 임의의 방향으로 움직인다. ⑤ 공이 벽에 충돌했는가? 예) ⑥번으로 이동 아니오) ⑦번으로 이동 ⑥ 공의 충돌 방향에 따라 X, Y 방향의 속도를 조절한다. ⑦ ②번으로 이동한다. 1.3.2 첫 번째 PingPong 애플릿 첫 번째 PingPong 애플릿은 프로그램의 구조가 상당히 좋지 않습니다. 어 떤점이 좋지 못한지는 소스를 보면서 설명하도록 하고, 우리가 해야할 일은 이 좋지못한 PingPong 애플릿을 하나 하나 고쳐나가면서 Java AWT 프로그래 밍에 능숙해지고, Java 애니메이션에 대한 기법들을 배워 나가는 것입니다. 01: import java.awt.*; 02: import java.applet.*; 03: 04: public class PingPong1 extends Applet 05: { 06: int ballXp, ballYp, ballXs, ballYs; 07: int oldBallXp, oldBallYp; 08: 09: public void init() 10: { 11: resize(400, 400); 12: } 13: 14: public void start() 15: { 16: ballXp = 100; 17: ballYp = 200; 18: ballXs = 1; 19: ballYs = 1; 20: 21: repaint(); 22: } 23: 24: public void paint(Graphics g) 25: { 26: g.setColor(Color.black); 27: g.fillRect(0, 0, 400, 400); 28: 29: while (true) 30: { 31: g.setColor(Color.black); // 이전의 공을 지운다 32: g.fillArc(oldBallXp, oldBallYp, 25, 25, 0, 360); 33: g.setColor(Color.white); // 새로운 공을 그린다 34: g.fillArc(ballXp, ballYp, 25, 25, 0, 360); 35: oldBallXp = ballXp; // 이전 공의 좌표를 기억한다 36: oldBallYp = ballYp; 37: ballXp += ballXs; // 공을 이동시킨다 38: ballYp += ballYs; 39: // 벽에 공이 부딪혔으면 속도를 반전시킨다 40: if (ballXp > 400 || ballXp < 0) 41: ballXs = -ballXs; 42: if (ballYp > 400 || ballYp < 0) 43: ballYs = -ballYs; 44: } 45: } 46: } [소스3] PingPong1.java ● PingPong1 애플릿에 대한 설명 PingPong1 애플릿의 구조는 HelloWorld 애플릿과 거의 같습니다. 다만 몇 몇 데이터들이 추가 되고, 각각의 메소드들이 좀 더 많은 일들을 하는것에 불과 합니다. 우선 추가된데이터 멤버(Data Member)들에 대해서 설명 하겠 습니다. ◇ ballXp, ballYp : 현재 공의 위치를 나타냅니다. ◇ ballXs, ballYs : 공의 X, Y 축으로의 속도를 나타냅니다. ◇ oldBallXp, oldBallYp : 바로 이전에 공이 있던 위치를 나타냅니다. 다음으로 각각의 메소드들에 대해서 살펴 봅시다. 우선 init() 메소드는 애플릿의 크기를 400x400으로 조절하고, start() 메소드는 데이터 멤버들을 초기한후 repaint() 메소드들 호출하여, paint() 메소드를 부르게 됩니다. paint() 메소드는 우선 26번 라인에서 브러시(Brush) 를 검은색으로 정하 고, 27번 라인에서 애플릿 영역 전체를 덮는 속이 꽉찬 사각형을 그려서 애 플릿 영역을 지우게 됩니다. 29번 라인을 보면 무조건 참인 while() 루프를 볼 수 있는데 이것은 무한 루프입니다. while() 루프는 조건식이 거짓일 때 루프를 탈출하게 되므로, 여기에서는 하늘에서 벼락이 쳐서 컴퓨터를 맞춰 메모리 내부의 'true' 값이 'false'로 바뀌지 않는한, 'true'가 'false'로 바뀔리 없으므로 무한루프가 되는 것입니다. (혹시 이런 경우가 생겨 무한 루프에서 탈출하는 일을 겪은 분이 계시면 저에게 연락 바랍니다) 루프의 내용을 살펴보면 앞절에서 제시한 순서에 따라 진행되고 있을 것입니다. ☞ 새로 등장한 것들 이번 단원 부터는 AWT에 대한 새로운 개념들이 하나 둘씩 등장하므로, 이 들에 대한 설명을 각 단원의 "☞ 새로 등장한 것들"이라는 제목의 소단원에 서 설명하겠습니다. 단, AWT와 관련이 없는 Java 의 일반 문법에 해당하는 내용들은 이 강좌의 내용과 맞지 않으므로, 여기에서 다루지 않겠습니다. ◇ Graphics 객체 paint() 메소드의 인자로 넘겨지는Graphics 객체에 대해서 알아보겠 습니다. Graphics 객체는 AWT 요소(Component)가 화면상 또는 메모리속 에서 차지하고 있는 사각형의 영역을 뜻합니다. 지금까지 배워온 paint (Graphics g)에서의 'g' 는 현재 paint() 메소드가 속해 있는 애플릿이 화면상에서 차지하고 있는 영역을 뜻하는 것입니다. AWT가 화면상에 무 엇인가를 출력하기 위해서 행하는 모든 행동은 이 Graphics객체를 통해 서만 이루어 지며, Graphics 객체는 AWT가 필요로 하는 거의 모든 메소 드들을 가지고 있습니다. 우리가 게임에서 주로 사용할 여러 가지 메소 드들은 결국 Graphics 클래스 내부에 존재하는 메소드들인 것입니다. (다른 곳에서는 Graphics 객체를 Device Context라고 부르기도 합니다. 왜냐하면 Graphics 객체가 일종의 장치핸들이기 때문입니다.) ◇ Graphics.setColor(Color c) [소스3]의 26번 라인에서 볼 수 있는 setColor(Color c) 메소드는 G- raphics 객체의 브러시 색상을 정하게 됩니다. 여기에서 브러시(Brush) 란 Graphics 객체가 자신의 영역에 그림을 그릴 때 사용하는 붓을 뜻합 니다. 우리가 도화지에 붉은색 줄을 긋고 싶으면 붉은 색연필이나, 크 레용을 들 듯이 Graphics 객체도 그림을 그리기 이전에 어떤 붓으로 그 림을 그릴지, 어떤 색상으로 그림을 그릴지등을 설정해 주는 것입니다. ◇ Graphics.fillRect(int x, int y, int w, int h) fillRect() 메소드는 속이꽉찬 사각형을 그립니다. 이 메소드는 주로 화면을 지우거나 이전의 그림을 지우는 등의 작업을 할 때 사용하게 될 것입니다. 사각형의 색상은 setColor() 메소드에서 설정된 색상을 사용 합니다. ◇ Graphics.fillArc(int x, int y, int xr, int yr, int sAg, int aAg) 이 메소드는 속이꽉찬 부채꼴을 그리는데 사용됩니다. (x, y)를 중심 으로 가로반지름 xr, 세로반지름 yr의 부채꼴을 시작각(sAg) 부터 지정 된각도(aAg)만큼의 부채꼴을 그립니다. 만약 화면에 원을 그리고 싶다 면 [소스3] 에서 처럼 시작각을 0 으로 하고, 부채꼴의 각도를 360으로 정해주면 됩니다. ● 무엇이 문제인가요? 첫 번째 PingPong 애플릿을 만들고 실행해 보았습니다. 상당히 실망이 큰 분들도 계실 것입니다. 결과는 번쩍번쩍 이는 공과 번쩍번쩍 이는 마우스커 서, 그리고 애플릿 영역을 다른 윈도우로 덮은후 다시 나오게 한 다음의 황 당함 등등, 아직 개선해야할 여러 문제들이 남아 있습니다. 어째서 이러한 문제점이 생기게 되었을까요? 하나 하나 짚어 보도록 합시다. ◇ 번쩍번쩍이는 공 이 문제점의 원인은 공을 그리고, 지우는 작업이 우리가 보는 화면내 에서 일어 난다는데 있습니다. [소스3]의 31-34라인에서 이루어지는 작 업을 우리가 볼 수 있는 화면내에서 작업함으로서('g' 객체가 화면상에 서 애플릿이 차지하고 있는 공간에 대한 핸들이므로) 공을 지우고, 다 시 그리는 작업이 눈에 보이게 되고, 이로 인해 심한 깜박임이 발생하 는 것입니다. 이것을 해결하기 위해서는 여러 가지 방법이 있으나, 가 장 좋은 방법은 안보이는데서 작업을 한 후 완성된 이미지만 보여 주는 것이겠습니다. ◇ 번쩍번쩍이는 마우스 커서 애플릿 때문에 마우스 커서가 상당히 고생을 하는 것 같습니다. 이같 은 문제점이 발생하는 이유는 애플릿이 CPU를 너무 많이 점유하기 때문 에 발생하게 됩니다. paint 메소드는 원칙적으로 자신의 영역을 갱신하 고 제어권을 넘겨 줘야만 합니다. 하지만, 우리의 PingPong1의 paint메 소드는 무한루프를 내부에 가지고 있고, Web Browser를 끝내거나, 다른 홈페이지로 이동하지 않는한 paint 메소드 속에서 무한이 공을 새로 그 리고, 지우고 하는 작업을 반복할 것입니다. 이 때문에 마우스 커서가 심하게 번쩍이게 되는 것입니다. 이를 해결하기 위해서는 Thread를 이 용한 프로그래밍을 해야만 합니다. ◇ 다른 윈도우로 덮여져 있던 애플릿 영역이 복구가 안되는군요! 예, 그렇습니다. 덮여져 있던 영역은 우리의 PingPong1 애플릿에서는 도저히 복구될 수 없습니다. 애플릿의 영역을 덮고 있던 다른 윈도우가 사라짐과 동시에 PingPong1 애플릿에게는 repaint메시지가 발생하게 됩 니다. repaint 메시지는 paint메소드를 호출하게 되겠죠. 하지만, Pin- gPong1 의 paint메소드는 영원히 빠져나올수 없는 무한루프속에서 열심 히 공을 튕기고 있으므로, 운영체제에서 내리는 명령 조차 무시 하면서 공튕기기에만 열중할 것입니다. 이것의 해결 방법 역시 Thread를 사용 해 paint메소드 내부의 무한루프를 없애는 것입니다. 1.3.3 개선된 PingPong 애플릿 앞 단원에서 살펴본 바와 같이 PingPong1 애플릿에는 몇가지 문제점이 발 견되었습니다. 이제 이것들을 수정해 보도록 하겠습니다. ● Multi-Thread 의 구현 Java는 java.lang.Thread라는 패키지에 쓰레드 클래스가 정의되어 있습니 다. 이것을 상속받으면 멀티 쓰레드 버전의 PingPong 을 만들 수 있겠군요! 라고 생각할 수 있으나, Java는 애석하게도 다중상속이 허용되지 않습니다. PingPong은 이미 Applet으로 부터 파생된 클래스이므로 Thread를 같이 상속 받을수는 없는 것입니다. 그렇다면 방법이 없는 것일까요? 그렇지는 않습니 다. Java를 만드신 창조주님들께서 클래스로 부터의 다중상속은 허용하지 않았지만, implements라는 문장을 통해 다수의 인터페이스 (Interface : 클 래스의 축소판이라고 보시면 됩니다, 자세한 내용 설명은 이 강좌의 설명범 위를 벗어나므로, Java 입문서를 참고하시기 바랍니다)로 부터의 다중 상속 은 허용해 주셨습니다. 이제 그 사용법을 알아보도록 합시다. ◇ Runnable Interface Runnable Interface는 java.lang.Thread를 인터페이스화 시켜놓은 것 입니다. 우리는 Runnable 인터페이스를 PingPong 애플릿의 조상(완전한 조상이라고 보기엔 좀 그렇습니다만...)으로 둠으로써 Multi-Thread 기 반의 PingPong 애플릿을 만들 수 가 있을 것입니다. ◇ Runnable Interface를 포함한 애플릿의 기본골격 import java.awt.*; import java.Applet.*; import java.lang.Runnable; public class 애플릿이름 extends Applet implements Runnable { Thread myThread; // 애플릿 쓰레드 public void init() { // 애플릿이 최초 로드되어서 해야할 일들 } public void start() { // 애플릿의 실행이 시작될 때 해야할 일들 // 아직 쓰레드가 생성되어 있지 않다면 새로운 // 쓰레드를 생성하고 쓰레드를 활성화 시킨다 if (myThread == null) { myThread = new Thread(this); myThread.start(); } } public void run() { // 쓰레드의 메인 루틴 } public void paint(Graphics g) { // 애플릿이 갱신되거나 새로 그려져야할 때 해야할 일들 } public void stop() { // 애플릿의 실행을 중지할 때 해야할 일들 // 쓰레드가 실행중이면 이를 중지시키고, // 메모리에서 제거시킨다 if (myThread != null) { myThread.stop(); myThread = null; } } public void destroy() { // 애플릿을 메모리에서제거할 때 해야할 일들 } } [표3] Runnable 인터페이스를 포함한 애플릿의 기본구조 [표3]에서 볼 수 있듯, Runnable 인터페이스를 포함한 애플릿의 구조 는 저번 강좌에서 봤던 [표1]의 애플릿의 구조와 거의 같습니다. 몇가 지 추가된 사항으로 Thread형 데이터 멤버 한 개와, start, stop메소드 내부에 if 문, 그리고 run 이라는 새로운 메소드가 추가된 것 정도입니 다. 이들에 대한 자세한 설명은 PingPong2 애플릿의 설명에서 함께 하 겠습니다. ● Double Buffering 의 구현 공이 번쩍이는 것을 해결하기 위해서는, 보이지 않는 곳에서 작업을 하는 것이 제일이라고 했습니다. 보이지 않는 곳에서 작업을 하는 알고리즘을 흔 히 게임프로그래밍에서 "더블버퍼링"이라고 부릅니다. 이제 "더블버퍼링"이 라는 것을 배워 보도록 하겠습니다. ◇ 더블버퍼링이란 무엇인가요? 더블버퍼링이라는 것은 게임이나, 컴퓨터 애니메이션등에서 화면에서 바로 갱신을 할 경우, 화면상에 일어나는 깜박임을 제거하기 위하여 메 모리 속의 임시 영역에서 작업을 하여, 완성된 이미지를 화면에 출력하 는 방식을 말합니다. (이때 고속으로 화면상에 전송되므로 사용자가 깜 빡임을 거의 느낄 수 없습니다) ◇ 좋군요, 그런데 어떻게 더블버퍼링이라는 것을 구현할 수 있습니까? Java 는 Image 클래스를 제공합니다. Image 클래스는 게임의 그래픽 데이터(캐릭터나 배경등)를 저장하기 위해 많이 사용되는데, 이외에도 더블버퍼링을 위한 버퍼역할을 할 수 도 있습니다. 더블 버퍼링의 알고 리즘은 아래의 그림과 같습니다. 그림 보기 ◇ 못알아 듣겠습니다. 실제로 소스를 보여주십시오. 백문이불여일견! 소스를 보도록 하십시오. 01: Image imgOffScr; 02: Graphics grpOffScr; 03: 04: public void start() 05: { 06: imgOffScr = createImage(100, 100); 07: grpOffScr = imgOffScr.getGraphics(); 08: } 09: 10: public void paint(Graphics g) 11: { 12: grpOffScr.drawString("안녕하세요?", 10, 10); 13: g.drawImage(imgOffScr, 0, 0, this); 14: } [소스4] 더블버퍼링의 구현 더블 버퍼링을 이용하여 화면에 "안녕하세요?"라는 문자열을 찍는 애 플릿의 일부분 입니다. 소스의 내용을 살펴보면 start메소드에서 임시 버퍼를 만들고 있습니다. 6라인의 createImage메소드는 주어진 크기(가 로, 세로)의 이미지를 메모리속에 생성시키는 역할을 합니다. 7라인의 getGraphics메소드는 Image객체의 멤버로서, Image자신에 대한 핸들을 Graphics 객체형태로 되돌려 줍니다. 이 Graphics 객체를 이용하여 메 모리버퍼에서 그림을 그리는 작업을 하게 되는 것입니다. 12번 라인을 보면 7번라인에서 얻은 Graphics 객체를 이용해 메모리 버퍼에 "안녕하 세요?"라는 문자열을 (10, 10)의 좌표에 출력하고 있습니다. 다음으로 13번 라인에서 메모리 버퍼를 실제 화면으로 옮기는 작업을 하게 되고, 결국 우리는 완성된 이미지를 볼 수 있게 되는 것입니다.

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

자바프로그래밍#6  (0) 2010.10.28
자바게임프로그래밍#5  (0) 2010.10.28
자바게임프로그래밍#4  (0) 2010.10.28
자바게임프로그래밍#3  (0) 2010.10.28
자바게임프로그래밍#1  (1) 2010.10.28

 

자바 게임 프로그래밍 강좌 1

하이텔 자바 동호회 김명현 (MadOx@hitel.net)님의 글입니다.
이글은 김명현님의 동의없이 함부로 배포하실 수 없습니다.

안녕하십니까? 친소맨 김명현입니다. 이미 만들어 놓은 문서를 새로 편집하
는것도 장난이 아니군요.. <장장 한시간이나 칸맞추고, 줄그리고, 말바꾸고
등등 난리를 쳤어요> 1 회 분량으로 꽤 많은 내용이 될지 모르겠는데, 다음
부터는 이거보다 더 많아질것 같아서 걱정이네요 :) 잘쓴 문서는 아니지만,
초보자나, 자바 게임프로그래밍에 관심이 있으신 분들에게 도움이 되었으면
좋겠군요...

  * 본 문서는 Java Development Kit 1.0.2 를 기준으로 작성되었습니다.
  * Java Development Kit 1.1.x 버젼을 사용할 경우 일부 소스가 실행이
    되지 않을 수 있습니다.


1. 기본적인 AWT(Abstract Windows Tookit) 사용법을 배워봅시다.

1.1 AWT란 무엇인가요?
  AWT는 Java에서 지원되는 기본적인 GUI 컴포넌트들의 집합입니다. Java는
여러 가지 다른 기종들 사이에서 실행될 수 있도록 고안되어졌으므로, 어느
한가지 운영체제에 맞는 GUI라이브러리를 가지고 있을 수 는 없습니다. AWT
는 OS 차원의 GUI 컴포넌트들을 추상화 시키고, 이들에 대해 동일한 인터페
이스를 통하여 접근 통제할 수 있도록 만들어져 있습니다.   때문에 각각의
운영체제마다 가지고 있는 독특한 GUI 요소들을 사용할수 는 없게 되었지만
GUI 운영체제들이 가지고 있는 일반적 요소들을 거의 대부분 포함하고 있으
므로, Java 프로그래머들이 기종을 가리지 않고 실행될 수 있는 GUI 프로그
램을 만들수 있게 합니다.

  우리가 앞으로 공부하게 될 Game Programming 도 그래픽 환경에서 수행되
는 것이 대부분이므로, 필연적으로 AWT를 사용하게 될 것입니다. Java 에서
그래픽 프로그래밍은 AWT 를 빼놓고는 이야기 할 수 없는 것이기 때문이죠.
대부분의 Java프로그래머들이 AWT 의 사용법에 대해서 잘 알고 있을 것이라
생각은 되지만, 몇가지 AWT에 대한 예제들을 통해 자신의 실력을 확인해 보
고 넘어가는 것도 좋을 것입니다.

1.2 AWT용 "Hello, world!"를 만들어 봅시다.
  C언어 시절부터 전통적으로 최초의 프로그램은 "Hello, world!"라는 문장
을 화면에 출력하는 예제였습니다. 우리도 이 아름다운(?) 전통을 이어나가
기 위해 최초로 만들게 되는 프로그램으로 "Hello, world!"를  화면 중앙에
출력하는 것으로 하게 될 것입니다.

● Applet기반의 Hello, world!

  독립적인 프레임(Frame) 윈도우를 가지는  AWT 프로그램은 나중에 기회가
닿으면 배우기로 하고, 우선 이해하기 쉽고 프로그램이 간단한 Applet 기반
의 "Hello, world!"를 만들어 보도록 하겠습니다. 소스를 먼저 보고 설명을
하도록 하겠습니다.

01: import java.awt.*;                                            
02: import java.applet.*;                                         
03:                                                               
04: public class HelloWorld extends Applet                        
05: {                                                             
06:     public void init()                                        
07:     {                                                         
08:         resize(100, 100);                                     
09:  }                                                         
10:                                                               
11:     public void start()                                       
12:     {                                                         
13:         repaint();                                            
14:     }                                                         
15:                                                               
16:     public void paint(Graphics g)                             
17:     {                                                         
18:         g.drawString("Hello, world!", 20, 45);                
19:     }                                                         
20: }                                                             
                      [소스1] HelloWorld.java

  너무나 간단한 소스라서  설명할 것도 없을 것 같습니다만,  처음 보시는
분들을 위해 간단히 설명을 하도록 하겠습니다. 여러분들이 Java의 기초 문
법은 알고 있다는 가정하에 설명을 해나갈 것이므로  중간에 모르는 부분이
나오면 과감하게 입문서 등을 참고하시기 바랍니다.

● Applet의 기본적인 구조

  애플릿(Applet)은 Web Browser에 의해 인터프리팅되는 형태의 프로그램을
말합니다. 즉, 단독 실행은 불가능 하고, HTML 내에 삽입되어져 홈페이지와
함께 보여지는 형태로 실행됩니다.  애플릿은 다음과 같은 기본적인 구조를
가지고 있습니다.

  import java.awt.*;                                              
  import java.Applet.*;                                           
                                                                  
  public class 애플릿이름 extends Applet                          
  {                                                               
      public void init()                      
      {                                                           
          // 애플릿이 최초 로드되어서 해야할 일들                 
      }                                                           
                                                                  
      public void start()                                         
      {                                                           
          // 애플릿의 실행이 시작될 때 해야할 일들                
      }                                                           
                                                                  
      public void paint(Graphics g)                               
      {                                                           
          // 애플릿이 갱신되거나 새로 그려져야할 때 해야할 일들   
      }                                                           
                                    
      public void stop()                                          
      {                                                           
          // 애플릿의 실행을 중지할 때 해야할 일들                
      }                                                           
                                                                  
      public void destroy()                                       
      {                                           
          // 애플릿을 메모리에서 제거할 때 해야할 일들            
      }                                                           
  }                                                               
                                                                  
                      [표1] 애플릿의 기본구조

  애플릿은 AWT기반의 프로그램입니다.  Web Browser가 GUI기반의 프로그램
임을 감안할 때 당연한 것이라 할 수 있겠습니다. 그러므로 애플릿은 AWT패
키지를 import해야 합니다.(import는 C언어에서 include와 비슷한 개념입니
다) 또, 우리의 애플릿은 기본적인 애플릿 뼈대에서 파생되는 서브클래스로
구현이 되므로,  기본적인 애플릿의 내용을 담고 있는  java.applet.Applet
패키지도 import 해야 할 것입니다. (제일 위에 두줄은 무조건 적어줘야 한
다고 생각하시는게 편할껍니다)
  다음으로 위의 [표1]에서 볼 수 있듯 애플릿은 기본적으로 5개의 내부 메
소드를 가지고 있습니다. 이것 외에도 여러 가지의 메소드를 가지고 있습니
다만, 그런것들은 차차 배우기로 하고 이 절에서 필요한 것들만 알아보도록
하겠습니다.

  ◇ public void init() 메소드
      이 메소드는 Web Browser나 AppletViewer에 의해  애플릿이 시스템으
    로 로드된후 최초로 불려지게 됩니다.  init()메소드는 최초로 start()
    메소드가 실행되기전에 반드시 실행됩니다.

  ◇ public void start() 메소드
      이 메소드는 Web Browser나 AppletViewer에 의해  애플릿이 실행되는
    때에 불려지게 됩니다. 위에서 말했듯이 init()메소드가 실행된후 불려
    지게 되고, 이미 방문했던 홈페이지를 다시 방문할 때 init() 메소드가
    아닌 start() 메소드 부터 불려지게 됩니다. (여기에서 이미  방문했던
    홈페이지라는 것은 아주 매우 오래전에 방문하여 브라우져 캐시에 겨우
    남아 있을 법한 홈페이지를 말하는 것이 아니라, 몇번 이전 페이지버튼
    을 눌러서 갈 수 있는 정도의 페이지를 말합니다. 다시 말하자면, 애플
    릿 인스턴스가 메모리에서 완전히 제거되기 이전의 상태인  홈페이지를
    말합니다.)

  ◇ public void stop() 메소드
      이 메소드는 Web Browser나 AppletViewer에 의해 애플릿의 실행이 중
    단되는 때에 불려지게 됩니다. 보통 현재 애플릿이 포함되어 있는 홈페
    이지에서 다른 홈페이지로 이동할 때 불려지며, Web Browser나 Applet-
    Viewer를 종료할때에도 destroy() 메소드에 앞서 이 메소드가 불려지게
    됩니다.

  ◇ public void destroy() 메소드
      이 메소드는 Web Browser나 AppletViewer가 현재 실행중인  애플릿의
    수행을 완전히 종료시키고, 애플릿을 위해 할당한 자원(Resource)을 반
    환시키는 등의 작업을 하게 됩니다. 물론 이 메소드도 자신이 수행되기
    이전에 stop()메소드를 먼저 수행하게 됩니다.

  ◇ public void paint(Graphics g) 메소드
      이 메소드는 원래 애플릿의 메소드가 아니라,  애플릿의 까마득한 조
    상인 Component라는 클래스의 메소드입니다. 애플릿의 족보를 살펴보면
    Component 클래스로부터 파생되어 Container 를 거치고, Panel 을 이어
    받아 Applet까지 오게 되는데,  이런 연유로 인해 paint 메소드가 애플
    릿에 포함되게 된 것입니다.  이 메소드가 하는 일은 이름 그대로 현재
    자신의 영역을 새로히 그리는 일을 하게 됩니다. 예를들어 애플릿의 영
    역위에 MS-Windows의 계산기가 나타났다가 없어졌다면 애플릿의 상당부
    분이 계산기에 의해 지워졌을 것입니다. 이럴 때 Web Browser나 Apple-
    tViewer는 paint()메소드를 호출하여 애플릿 영역을 새로그리도록 합니
    다.

  이상으로 애플릿의 기본적인 구조에 대해 알아 보았습니다. 이제 다시 본
론으로 돌아가서 [소스1]에 대한 분석을 해보도록 하겠습니다.  (너무 오래
전 일이라 소스가 기억나지 않으시는 분들은 다시 앞으로 돌아가 천천히 읽
어보시기 바랍니다. 좀전에 알아본 애플릿의 기본구조 덕택에 좀더 많은 부
분을 이해하실 수 있을지도 모릅니다)

  우선 1-2번 라인은 AWT 패키지와, Applet 패키지를 import 시키는 것입니
다. 다음으로 4번 라인을 살펴보면 HelloWorld라는 이름의 클래스를 Applet
으로 부터 파생시키는 것을 알 수 있을 것입니다.  이제 HelloWorld는 애플
릿의 일가족이 되었습니다.

  6-9번 라인까지는 init() 메소드에 대해서 정의되어 있습니다.  여기에서
하는 일은 애플릿의 사이즈를 100x100으로 조정하는 일입니다. resize() 메
소드 역시 애플릿의 여러 가지 메소드 중 하나이며,  애플릿 영역의 크기를
조절하는 역할을 합니다.

  11-14번 라인은 start()메소드에 대해서 정의되어 있는데, 별로 하는일은
없어 보입니다. 단순히 repaint()라는 메소드를 한 번 불러주는군요. repa-
int() 라는 메소드는 paint() 메소드와 마찬가지로 애플릿의 메소드가 아닌
애플릿의 윗대 조상의 메소드 입니다. repaint()가 하는 일은 단순히 pain-
t()메소드를 불러주는 일입니다.  궂이 우리가 repaint()메소드를 불러주지
않더라도 Web Browser나 AppletViewer에서 자동으로 paint( )메소드를 불러
줄 것입니다만, 이 기회를 빌어 repaint()메소드를 설명하기 위해서 한줄을
넣어 보았습니다.

  16-19번 라인까지는 paint()메소드에 대해서 정의되어 있습니다.  내용을
살펴보면

    g.drawString("Hello, world!", 20, 45);

  위와 같습니다. g 라는 객체의 drawString() 메소드를 호출 하는 것인데,
g는 paint(Graphics g)라는 문장을 볼때 Graphics형태의 객체임을 알 수 있
습니다. Graphics객체는 나중에 공부하도록 하고, 위의 문장을 계속 분석해
보면 (20, 45)영역을 좌측 상단으로 Hello, world! 라는 문장을 출력하시오
라고 되어 있으므로 Java는 우리가 시키는 데로 (20, 45)영역에 Hello, wo-
rld! 라고 예쁘게 출력해 줄 것입니다.

  그런데 stop() 이나 destroy() 메소드는 [소스1]에서 보이지를 않습니다.
잘 보셨습니다. [소스1]에서는 별도의 메모리 할당이나  쓰레드를 생성시키
지 않았으므로,  애플릿을 종료할 때 특별하게 해주어야 할 일이 없습니다.
(단순히 화면에 글자 몇 개 출력하는 것으로역할을 다하는 것이죠) 그래서
stop()이나 destroy()메소드는 생략한 것입니다. 사실 init()나 start() 메
소드도 생략가능합니다.  물론 생략했을 때 Web Browser나 AppletViewer 는
HelloWorld의 부모인 Applet의 start(), init(), stop(), destroy() 메소드
들을 호출 할것입니다.

  분석도 끝냈으니 실행을 해봐야겠죠?. 위의 [소스1]을 'HelloWorld.java'
라는 이름으로 저장했다면, 컴파일을 해봅시다.

● 컴파일 방법
  ◇ JDK1.x 에서의 컴파일
     javac 프로그램파일명.java
  ◇ Symantec Cafe 또는 VisualCafe
     javac 프로그램파일명.java
     또는 통합환경에서 소스코드를 불러와서 Compile메뉴를 선택해도 됩니
     다.
  ◇ Microsoft Visual J++
     jvc 프로그램파일명.java
     또는 통합환경에서 소스코드를 불러와서 Compile메뉴를 선택해도 됩니
     다.

  컴파일을 해보면  다른 종류의 언어 컴파일러에 비해 결과가 무척이나 무
뚝뚝 합니다. 에러가 있다는 메시지가 보이지 않으면 실행을 해보도록 하겠
습니다.  (만약 에러가 난다면, 위의 오자가 난것이 없는지 잘 체크 하십시
오.) 아차! 한가지 빼먹은게 있군요.  우리가 만든 프로그램은 애플릿 형태
의 프로그램이 었습니다. 즉, Web Browser나 AppletViewer에 의해 실행가능
한 프로그램이란 이야기이죠. 그러므로 실행에 앞서 Web Browser가 읽을 수
있도록 HTML파일을 우선 만들어 줘야 합니다. 우리가 만들어 줘야할 HTML파
일의 내용은 [소스2]와 같습니다.

01:<HTML>                                                        
02: <BODY>                  
03: <CENTER>                                                      
04: HelloWorld Applet                                             
05: <HR>                                                          
06: 
07:<APPLET  CODE=HelloWorld.class                                     
08:     WIDTH=100                                                 
09:     HEIGHT=100>                         
10: </APPLET>                                                     
11: <HR>                                                          
12: </BODY>                                                       
13: </HTML>  
                                                     
11: <HR>                                                          
12: </BODY>                                                       
13: </HTML>                                                       
    
                   [소스2] HelloWorld.html

  HTML Tag를 자세히 설명하는 것은 이 책의 내용을 벗어나는것이므로, 우
리가 알아야 할 APPLET Tag에 대해서만 설명하도록 하겠습니다. 다른 Tag는
HTML문법에 관련된 책자들을 참고하시기 바랍니다.

  <APPLET                                                         
      CODE={애플릿의 URL}                                         
      CODEBASE={URL}                                              
      WIDTH={애플릿의 가로사이즈}                                 
      HEIGHT={애플릿의 세로사이즈}                                
      ALIGN={left, right, center}>                                
  <PARAM NAME={변수명1} VALUE={변수값1}>                          
  <PARAM NAME={변수명2} VALUE={변수값2}>                          
  <PARAM ...>                                                     
  <BLOCKQUOTE>                                                    
  {애플릿이 로드되는 동안 출력될 임시 HTML Tag}   
  </BLOCKQUOTE>                                                   
  </APPLET>                                                       


                   [표2] APPLET Tag의 기본 형식

 ◇ CODE={애플릿의 URL}
      애플릿이 위치하고 있는곳을 URL(Uniform Resource Location) 형태로
    기술해 줍니다.  애플릿이 꼭 HTML 파일이 있는 곳에 위치할 필요는 없
    으므로, 해당 애플릿이 있는 URL을 기술하도록 되어 있습니다. [소스2]
    에서는 단순히 애플릿의 파일명만 적혀 있는데,  이것은 애플릿이 있는
    위치와 HTML 파일이 있는 위치가 같기 때문에 생략할수 있는 것입니다.

  ◇ CODEBASE={URL}
      애플릿이 요구하는 각종 이미지나 사운드 파일들을 어디로 부터 읽어
    올지를 URL 형태로 기술하여 줍니다. CODEBASE를 생략하면 애플릿이 로
    드된 URL을 기본 CODEBASE로 인식합니다.

  ◇ WIDTH={애플릿의 가로사이즈}
      애플릿의 가로크기를 10진수 형태로 기술하여 줍니다.

  ◇ HEIGHT={애플릿의 세로사이즈}
      애플릿의 세로크기를 10진수 형태로 기술하여 줍니다.

  ◇ ALIGN={left, right, center}
      홈페이지 상에서 애플릿의 정렬위치를 설정하여 줍니다.

  ◇ PARAM NAME={변수명} VALUE={변수값}
      홈페이지에서 애플릿으로 어떠한 인수를 넘겨줄 필요가 있을 때 사용
    하게 됩니다. PARAM Tag 를 통하여 넘긴 변수명과 변수값을 애플릿에서
    읽어, 그 값을 이용하여 애플릿의 실행 결과등을 바꿀 수 있게 합니다.

  ◇ BLOCKQUOTE
      애플릿이 로드되는 동안 홈페이지상의  애플릿이 나타날 영역에 대신
    하여 출력될 HTML Tag를 기술해 주면 됩니다. 애플릿의 사이즈가 큰 경
    우 로드에 상당한 시간이 걸리므로 "잠시만 기다리세요"와 같은 문장을
    넣어 주면 기다리는 사람도 좀 덜 지루할 것입니다. (덩치가 있는 애플
    릿의 경우 시스템이 다운된 것 같은 기분이 들정도로 시간이 오래 걸리
    기도 한답니다)

  이제 'HelloWorld.java'를 컴파일 하여 'HelloWorld.class'파일도 생성되
었고, 'HelloWorld.html'파일도 만들었으니 Web Browser를 실행하여 결과를
살펴 보십시오. 실행 결과는 애플릿의 중앙영역에 'Hello, world!' 를 출력
하는 것입니다.

───────────────────────────────────
 DO NOT DISTRIBUTE!                            (C)opyright 1997 MadOx


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

자바프로그래밍#6  (0) 2010.10.28
자바게임프로그래밍#5  (0) 2010.10.28
자바게임프로그래밍#4  (0) 2010.10.28
자바게임프로그래밍#3  (0) 2010.10.28
자바게임프로그래밍#2  (1) 2010.10.28

이거 때문에 한 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


로그인UI랑 조금 수정된 파일

5번째 만든 내 맘대로 만든 채팅소스...ㅋㅋ

'자바 > 채팅방' 카테고리의 다른 글

자바로그인UI가 추가된 소스  (0) 2010.10.27
swing으로 만든 멀티채팅소스  (0) 2010.10.27
자바 멀티채팅클라이언트 기본소스  (0) 2010.10.24
자바 멀티서버 기본소스  (0) 2010.10.24
채팅방클라이언트  (0) 2010.10.22

클래스패스와 환경 변수, 그것이 알고 싶다.
김세곤
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
import java.awt.*;
import javax.swing.*;

public class ComponetSize
{
JFrame jf = null;

public ComponetSize(JFrame jf)
{
this.jf = jf;
}
public int[] getCenterLocation()
{
int[] pos = new int[2];

Dimension dimen = Toolkit.getDefaultToolkit().getScreenSize();
Dimension dimen1 = jf.getSize();
pos[0] = (int) (dimen.getWidth() / 2 - dimen1.getWidth()/2);
pos[1] = (int) (dimen.getHeight() / 2 -dimen1.getHeight()/2);
return pos;
}
}

'자바 > 추천소스모음' 카테고리의 다른 글

안드로이드 조그버튼(휠) 예제소스  (0) 2017.11.07
자바 스노우크래프트 소스  (0) 2010.11.26
자바메모장추천소스  (0) 2010.10.20


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

public class ChatClientByConsole
{

 static int port = 0;
 static String host = "";
 public ChatClientByConsole(String host,int port)
 {
  this.port = port;
  this.host = host;
  

 }
 public static void main(String[] args)
 {
  new ChatClientByConsole("127.0.0.1",10001);

  Socket sock = null;;
  PrintWriter pw=null;
  BufferedReader br=null;
  BufferedReader br2=null;
  InputStream in;
  OutputStream out;

  try
  {
   sock = new Socket(host,port);
   br = new BufferedReader(new InputStreamReader(System.in));
   in = sock.getInputStream();
   out = sock.getOutputStream();
   pw = new PrintWriter(new OutputStreamWriter(out));
   br2 = new BufferedReader(new InputStreamReader(in));
   
   pw.println(args[0]);
   pw.flush();
   
   String line= null;
   
   //입력쓰레드 생성
   InputThread it = new InputThread(sock,br2);
   it.start();

   while((line=br.readLine())!=null)
   {
    //System.out.println(args[0]+":"+line);
    pw.println(line);
    pw.flush();
   }

   
  }
  catch (SocketException sqe)
  {
   System.out.println("서버와 연결이 안됨");
  }
  catch(Exception ex)
  {
   ex.printStackTrace();
  }
  finally
  {
   try
   {
    if(pw!=null)
     pw.close(); 
    if(br2!=null)
     br2.close(); 
    //if(br2!=null)
    // br2.close();
    if(sock!=null)
     sock.close(); 
   }
   catch (Exception ex)
   {
   }
   
  }
  //키보드로 부터 입력받는다.
  //입력받은 걸 inputStream으로 바꾼다.
  //그런다음

 }
}

class InputThread extends Thread
{
 Socket sock = null;
 BufferedReader br = null;
 PrintWriter pw = null;
 InputStream in = null;
 OutputStream out = null;

 public InputThread(Socket sock,BufferedReader br)
 {
  this.sock = sock;
  this.br = br;
 }

 public void run()
 {
  try
  {
   in=sock.getInputStream();
   out=sock.getOutputStream();

   //br = new BufferedReader(new InputStreamReader(in));
   //pw = new PrintWriter(new OutputStreamWriter(out));
   
   String line = null;

   while( (line=br.readLine()) != null)
   {
    System.out.println(line);
    
   }
  }
  catch (Exception ex)
  {
   ex.printStackTrace();
  }
  finally
  {
   try
   {
    if(pw!=null)
     pw.close(); 
    if(br!=null)
     br.close();
    //if(br2!=null)
    // br2.close();
    if(sock!=null)
     sock.close(); 
   }
   catch (Exception ex)
   {
   }
   
  }
  
 }
}

'자바 > 채팅방' 카테고리의 다른 글

swing으로 만든 멀티채팅소스  (0) 2010.10.27
멀티서버네번째소스  (0) 2010.10.26
자바 멀티서버 기본소스  (0) 2010.10.24
채팅방클라이언트  (0) 2010.10.22
채팅방 Client UI  (0) 2010.10.21


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


public class MultiChatServer
{

 public static void main(String[] args)
 {
  try
  {
   ServerSocket serverSoc = new ServerSocket(10001);
   System.out.println("접속을 기다립니다.");
   HashMap hm = new HashMap();
   while(true)
   {
    Socket sock = serverSoc.accept();
    ChatThread chatthread = new ChatThread(sock,hm);
    chatthread.start();
   }
  }
  catch (Exception ex)
  {
   System.out.println(ex);
  }
  System.out.println("Hello World!");
 }
}

class ChatThread extends Thread
{
 private Socket socket;
 private String id;
 private BufferedReader br;
 private PrintWriter pw;
 private HashMap hm;
 private boolean initFlag = false;
 public ChatThread(Socket socket,HashMap hm)
 {
  this.socket = socket;
  this.hm = hm;
 
  try
  {
   pw = new PrintWriter(new OutputStreamWriter(socket.getOutputStream()));
   br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
   //System.out.println("11111");
   id=br.readLine();
   //System.out.println("22222");
   broadcast(id +"님이 접속했습니다.");
   System.out.println("접속한 사용자의 아이디는 "+ id +"입니다.");
   
   synchronized(hm)
   {
    hm.put(this.id,pw);
   }
   
   initFlag = true;
  }
  catch (Exception ex)
  {
   //System.out.println("111111111");
   ex.printStackTrace();
  }
 }
 public void run()
 {
  try
  {
   String line = null;
   System.out.println("1111111111");
   while((line = br.readLine()) != null)
   {
    broadcast(id + ":" +line);
   }
   System.out.println("22222222222");
  }
  catch (Exception ex)
  {
   //System.out.println("2222222");
   ex.printStackTrace();
  }
  finally
  {
   synchronized(hm)
   {
    hm.remove(id);
   }
   try
   {
    if(socket!=null)
     socket.close(); 
   }
   catch (Exception ex)
   {
   }
  }

 }
 public void broadcast(String msg)
 {
  synchronized(hm)
  {
   Collection col = hm.values();
   Iterator iter = col.iterator();
   while(iter.hasNext())
   {
    PrintWriter pw = (PrintWriter)iter.next();
    pw.println(msg);
    pw.flush();
   }
  }
 }
}

'자바 > 채팅방' 카테고리의 다른 글

swing으로 만든 멀티채팅소스  (0) 2010.10.27
멀티서버네번째소스  (0) 2010.10.26
자바 멀티채팅클라이언트 기본소스  (0) 2010.10.24
채팅방클라이언트  (0) 2010.10.22
채팅방 Client UI  (0) 2010.10.21


import javax.swing.*;
import java.awt.*;
import java.io.*;
import java.net.*;
import java.awt.event.*;

/*
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()메소드를 호출한다.
*/
public class ChattingClient 
{
public static void main(String[] args) 
{
CreateChattingUI ccu =new CreateChattingUI();

ccu.setSize(500,500);
ccu.pack();
ccu.setVisible(true);
}
}

//채팅방UI 만드는 클래스
class CreateChattingUI extends JFrame
{
JPanel jp,bottom_jp;
BorderLayout bl;
static JTextArea top_jta;
JTextArea member_jta;
static JTextField username_jtf,msg_jtf;
// JComponent msg_jtf;
JButton msgSend_jb;
static Socket soc;

public CreateChattingUI()
{
super("자바로 만든 채팅방");
//소켓접속
try
{
soc = new Socket("127.0.0.1",10001);
//InputStream in = new ByteArrayInputStream();

}
catch (ConnectException ce)
{
System.out.println("호스트와 연결안됨");
}
catch(Exception e)
{
e.printStackTrace();
}

this.setLayout(new BorderLayout());

jp = new JPanel();
bl = new BorderLayout(2,2);
top_jta = new JTextArea();//왼쪽위 텍스트창
member_jta = new JTextArea(2,12);
username_jtf = new JTextField("대화명",10);
msg_jtf = new JTextField("메세지를 입력하시길 바랍니다.",42);
bottom_jp = new JPanel(new FlowLayout());
msgSend_jb = new JButton("메세지보내기");

top_jta.setEditable(false);
top_jta.setFont(new Font("굴림",Font.BOLD,12));
//System.out.println(msg_jtf.isRequestFocusEnable());

/*
textarea에 스크롤 붙이기
*/
JScrollPane scrollPane = new JScrollPane();
scrollPane.setPreferredSize(new Dimension(600,300));
scrollPane.setBorder(BorderFactory.createTitledBorder(""));
scrollPane.getViewport().add(top_jta, null);
jp.setLayout(bl);
jp.add(scrollPane,BorderLayout.WEST);

jp.add(member_jta,BorderLayout.EAST);
//msg_jtf.setSize(350,30);
bottom_jp.add(username_jtf);
bottom_jp.add(msg_jtf);
bottom_jp.add(msgSend_jb);
this.add(jp,BorderLayout.NORTH);
this.add(bottom_jp,BorderLayout.SOUTH);
//msg_jtf.addFocusListener((new EventManager()).focusGained(new FocusEvent()));
/*
텍스트 필드 클릭시 초기화 이벤트
*/
msg_jtf.addFocusListener(new FocusAdapter()
{
public void focusGained(FocusEvent fe)
{
JTextField tepJtf = (JTextField)fe.getSource();
tepJtf.setText("");
//System.out.println(((JTextField)fe.getSource()).getText());
}
});

username_jtf.addFocusListener(new FocusAdapter()
{
public void focusGained(FocusEvent fe)
{
JTextField tepJtf = (JTextField)fe.getSource();
tepJtf.setText("");
//System.out.println(((JTextField)fe.getSource()).getText());
}
});
/*
1.내용입력하고 보낼시 서버에 전달한다.2번
2.자기 메세지는 바로 텍스트에어리어창에 첫줄에 기록한다.1번
3.그리고 다른 클라이언트 메세지 입력시 창에 뜨게 한다.3번
*/
ActionListener al = new ActionListener()
{
public void actionPerformed(ActionEvent ae)
{
//JTextField jtd = (JTextField)ae.getSource();
String username = username_jtf.getText();
String msg = msg_jtf.getText();
if(username.equals("") || username.equals("대화명"))
{
username = "손님";
}

//username = (Font.getFont(username,new Font("바탕",Font.ITALIC,12))).getFontName();
top_jta.append(username+" : "+msg+"\n");
//top_jta.setLineWrap(true);
msg_jtf.setText("");

sendMsg(username,msg,soc);
}
};
msg_jtf.addActionListener(al);
msgSend_jb.addActionListener(al);
}
static void sendMsg(String username,String msg,Socket soc)
{
InputStream in = null;
OutputStream out = null;
OutputStream out2 = null;
PrintWriter pw = null;
BufferedReader br = null;
OutputStreamWriter osw = null;
BufferedReader br2 = null;

try
{
//in = new ByteArrayInputStream(msg.getBytes());
//in = 
//System.out.println("11111111111111111");
if(soc!=null)
{
//System.out.println("2222222222222");
in = soc.getInputStream();
out = soc.getOutputStream();
// out2 = new ByteArrayOutputStream();
// out2.write(msg.getBytes());
//InputStream in2 = new InputStream();
//br2 = new BufferedReader(new InputStreamReader(in));

//System.out.println(msg.getBytes());
br = new BufferedReader(new InputStreamReader(in));
pw = new PrintWriter(new OutputStreamWriter(out));
pw.println(msg.trim());
pw.flush();
String line=null;
//System.out.println("11111111");
//System.out.println("줄 : "+out2.readLine());
//System.out.println(out2.readLine());
while((line=br.readLine())!=null)
{
//pw.println(line);
//pw.flush();
//String echo = br.readLine();
System.out.println(line);
//System.out.println(msg);
//System.out.println("서버 : "+line);
}
//pw.flush();
}

}
catch (Exception ioe)
{
ioe.printStackTrace();
}
finally
{

try
{
if(br!=null)
br.close();
if(soc!=null)
{
soc.close();
}
}                                                                                                                                                        
catch (IOException ioe)
{

}
}
}
}

'자바 > 채팅방' 카테고리의 다른 글

swing으로 만든 멀티채팅소스  (0) 2010.10.27
멀티서버네번째소스  (0) 2010.10.26
자바 멀티채팅클라이언트 기본소스  (0) 2010.10.24
자바 멀티서버 기본소스  (0) 2010.10.24
채팅방 Client UI  (0) 2010.10.21
import javax.swing.*;
import java.awt.*;

public class ChattingClient 
{
public static void main(String[] args) 
{
CreateChattingUI ccu =new CreateChattingUI();

ccu.setSize(500,500);
ccu.pack();
ccu.setVisible(true);
}
}

//채팅방UI 만드는 클래스
class CreateChattingUI extends JFrame
{
JPanel jp,bottom_jp;
BorderLayout bl;
JTextArea top_jta,member_jta;
JTextField username_jtf,msg_jtf;
JButton msgSend_jb;
public CreateChattingUI()
{
super("자바로 만든 채팅방");
this.setLayout(new BorderLayout());

jp = new JPanel();
bl = new BorderLayout(2,2);
top_jta = new JTextArea();//왼쪽위 텍스트창
member_jta = new JTextArea(2,12);
username_jtf = new JTextField("대화명",10);
msg_jtf = new JTextField("메세지를 입력하시길 바랍니다.",42);
bottom_jp = new JPanel(new FlowLayout());
msgSend_jb = new JButton("메세지보내기");

/*
textarea에 스크롤 붙이기
*/
JScrollPane scrollPane = new JScrollPane();
scrollPane.setPreferredSize(new Dimension(600,300));
scrollPane.setBorder(BorderFactory.createTitledBorder(""));
scrollPane.getViewport().add(top_jta, null);
jp.setLayout(bl);
jp.add(scrollPane,BorderLayout.WEST);

jp.add(member_jta,BorderLayout.EAST);
//msg_jtf.setSize(350,30);
bottom_jp.add(username_jtf);
bottom_jp.add(msg_jtf);
bottom_jp.add(msgSend_jb);
this.add(jp,BorderLayout.NORTH);
this.add(bottom_jp,BorderLayout.SOUTH);

}


}

'자바 > 채팅방' 카테고리의 다른 글

swing으로 만든 멀티채팅소스  (0) 2010.10.27
멀티서버네번째소스  (0) 2010.10.26
자바 멀티채팅클라이언트 기본소스  (0) 2010.10.24
자바 멀티서버 기본소스  (0) 2010.10.24
채팅방클라이언트  (0) 2010.10.22

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
import java.io.*;
import java.beans.*;
import java.awt.*;
import java.awt.datatransfer.*;
import java.awt.print.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.text.*;
import javax.swing.event.*;
import javax.swing.undo.*;
import javax.swing.border.*;

public class Jmemo extends JFrame implements ClipboardOwner,ActionListener,Printable,UndoableEditListener
{
   Container contentPane;
 private JTextArea ta = new JTextArea(); //글을쓸 텍스트에어리어
 private JMenuBar mb = new JMenuBar();//메뉴바
 private JMenu m1,m2,m3,m4;//메뉴
 private JMenuItem mi11,mi12,mi13,mi14,mi15,mi16,mi17
  ,mi21,mi22,mi23,mi24,mi25,mi26,mi27,mi28,mi29,mi2a,mi2b
  ,mi32
  ,mi41,mi42 ;//메뉴 아이템
 private JCheckBoxMenuItem mi31;
 
 String st="";
 File file;
 private JOptionPane jOptionPane;
 Object options[]={"예","아니오","취소"};
 int cnt;
 JViewport viewPort;
 JScrollPane scrollPane;
 
 UndoManager undoManager=new UndoManager();
  public Jmemo(String title)
  {
  super(title);
  
/*  
  try
  {//룩앤필 설정 : 윈도우(윈도우에서만 가능)
   UIManager.setLookAndFeel("com.sun.java.swing.plaf.windows.WindowsLookAndFeel");
  }
  catch(Exception e)
  {
   System.err.println("자바 룩앤필 에러 :"+e.getMessage());
   JOptionPane.showMessageDialog(this,"Windows환경에서만 가능합니다."
    ,"자바 룩앤필 에러",JOptionPane.ERROR_MESSAGE);
  }
*/
  try
  {//룩앤필 설정 : 크로스
   UIManager.setLookAndFeel(UIManager.getCrossPlatformLookAndFeelClassName());
  }
  catch(Exception e)
  {
   System.err.println("자바 룩앤필 에러 :"+e.getMessage());
     JOptionPane.showMessageDialog(this,"설정하신 룩앤필이 존재하지 않습니다.","자바 룩앤필 에러",JOptionPane.ERROR_MESSAGE);
  }

  
  Image img=getToolkit().getImage("img/memo.gif");
  setIconImage(img);//윈도우 아이콘 변경
  addWindowListener(new WindowAdapter(){
                    public void windowClosing(WindowEvent we){
           System.exit(0);}});//윈도우 종료시 프로그램 종료   
  
  ta.getDocument().addUndoableEditListener(this);   
  contentPane = getContentPane();  
      scrollPane =new JScrollPane(ta);
      //scrollPane.add(ta);
      viewPort=scrollPane.getViewport();    
      viewPort.add(ta);     
  contentPane.add(scrollPane);//TextArea       
       
      
     
  
  this.setJMenuBar(mb);//메뉴바  
 ///////////////////////////////메뉴 구성////////////////////////////////////////////// 
  m1 = new JMenu("파일");  
  
  mi11 = new JMenuItem("새로만들기");
  mi12 = new JMenuItem("열기");
  mi13 = new JMenuItem("저장");
  mi14 = new JMenuItem("다른이름으로저장");
  mi15 = new JMenuItem("페이지설정");
  mi16 = new JMenuItem("인쇄");
  mi17 = new JMenuItem("끝내기");
  mi11.addActionListener(this);//액션리스너 등록
  mi12.addActionListener(this);
  mi13.addActionListener(this);
  mi14.addActionListener(this);
  mi15.addActionListener(this);
  mi16.addActionListener(this);
  mi17.addActionListener(this);
  
  mi11.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_N, ActionEvent.CTRL_MASK));
  mi12.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_O, ActionEvent.CTRL_MASK));
  mi13.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_S, ActionEvent.CTRL_MASK));
  mi14.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_A, ActionEvent.ALT_MASK));
  mi15.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_U, ActionEvent.ALT_MASK));
  mi16.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_P, ActionEvent.CTRL_MASK));
  mi17.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_X, ActionEvent.ALT_MASK));
  
  mb.add(m1);  
  
  m1.add(mi11);
  m1.add(mi12);
  m1.add(mi13);
  m1.add(mi14);
  m1.addSeparator();
  m1.add(mi15);
  m1.add(mi16);
  m1.addSeparator();
  m1.add(mi17);
  
  m2 = new JMenu("편집");
  
  mi21 = new JMenuItem("실행취소");
  mi22 = new JMenuItem(new DefaultEditorKit.CutAction());
      mi22.setText("잘라내기");
      mi23 = new JMenuItem(new DefaultEditorKit.CopyAction());
      mi23.setText("복사");
  mi24 = new JMenuItem(new DefaultEditorKit.PasteAction());
      mi24.setText("붙여넣기");
  mi25 = new JMenuItem("삭제");
  mi26 = new JMenuItem("찾기");
  mi27 = new JMenuItem("다음찾기");
  mi28 = new JMenuItem("바꾸기");
  mi29 = new JMenuItem("이동");
  mi2a = new JMenuItem("모두선택");
  mi2b = new JMenuItem("시간날짜");
  
  mi21.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_Z, ActionEvent.CTRL_MASK));
  mi22.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_X, ActionEvent.CTRL_MASK));
  mi23.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_C, ActionEvent.CTRL_MASK));
  mi24.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_V, ActionEvent.CTRL_MASK));
  //mi25.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_DELETE));
  mi26.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_F, ActionEvent.CTRL_MASK));
  //mi27.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_F3,ActionEvent.META_MASK));
  mi28.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_H, ActionEvent.CTRL_MASK));
  mi29.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_G, ActionEvent.CTRL_MASK));
  mi2a.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_A, ActionEvent.CTRL_MASK));
  //mi2b.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_F5,ActionEvent.META_MASK));
  
  mi21.addActionListener(this);
  mi22.addActionListener(this);
  mi23.addActionListener(this);
  mi24.addActionListener(this);
  mi25.addActionListener(this);
  mi26.addActionListener(this);
  mi27.addActionListener(this);
  mi28.addActionListener(this);
  mi29.addActionListener(this);
  mi2a.addActionListener(this);
  mi2b.addActionListener(this);
  
  mb.add(m2);
  
  m2.add(mi21);
  m2.addSeparator();
  m2.add(mi22);
  m2.add(mi23);
  m2.add(mi24);
  m2.add(mi25);
  m2.addSeparator();
  m2.add(mi26);
  m2.add(mi27);
  m2.add(mi28);
  m2.add(mi29);
  m2.addSeparator();
  m2.add(mi2a);
  m2.add(mi2b);
  
  m3 = new JMenu("서식");
  
  mi31 = new JCheckBoxMenuItem("자동줄바꿈");
  mi32 = new JMenuItem("글꼴");
  
  mi31.addActionListener(this);
  mi32.addActionListener(this);
  
  mb.add(m3);
  
  m3.add(mi31);
  m3.add(mi32);
  
  m4 = new JMenu("도움말");
  
  mi41 = new JMenuItem("도움말항목");
  mi42 = new JMenuItem("메모장정보");
  
  mi41.addActionListener(this);
  mi42.addActionListener(this);
  
  mb.add(m4);
  
  m4.add(mi41);
  m4.addSeparator();
  
  m4.add(mi42);  
      
  }
 
 public boolean keyDown(Event e,int key)
 {
  cnt++;
  System.out.println("SD");
  return false;
 }
 
 public void actionPerformed(ActionEvent ae)//메뉴 액션 처리
 {
  try{
   String gac=ae.getActionCommand();
   if(gac.equals("새로만들기"))
   {    
    File f=getFile();
    if(!("".equals(ta.getText()))&&f==null)  
    {
     jOptionPane = new JOptionPane();
     jOptionPane.showOptionDialog(this,getTitle().replaceAll("- 메모장","")+
      " 파일의 내용이 변경되 었습니다.\n 변경된 내용을 저장 하시겠습니까?",
      "메모장",JOptionPane.YES_NO_CANCEL_OPTION,JOptionPane.WARNING_MESSAGE,
        null, options,options[0]);
     if(jOptionPane.getValue().equals(options[0]))
       saveDocument(true);
     else
      ta.setText("");
     /*
     JOptionPane.showOptionDialog(this,getTitle().replaceAll("- 메모장","")+
     " 파일의 내용이 변경되 었습니다.\n 변경된 내용을 저장 하시겠습니까?",
            "메모장",JOptionPane.YES_NO_CANCEL_OPTION,JOptionPane.WARNING_MESSAGE,null, options,options[0]);
     */
    }   
    else if(cnt>0)
    {
     JOptionPane.showOptionDialog(this,getTitle().replaceAll("- 메모장","")+
     " 파일의 내용이 변경되 었습니다.\n 변경된 내용을 저장 하시겠습니까?",
            "메모장",JOptionPane.YES_NO_CANCEL_OPTION,JOptionPane.WARNING_MESSAGE,null, options,options[0]);
    }
   }
   else if(gac.equals("열기"))
   {
    cnt=0;
    ta.setText("");
    openDocument();
   }
   else if(gac.equals("저장"))
   {
    saveDocument(false);
   }
   else if(gac.equals("다른이름으로저장"))
   {
    saveDocument(true);
   }
   else if(gac.equals("페이지설정"))
   {
      printPage();
   }
   else if(gac.equals("인쇄"))
   {
      printDocument();
   }
   else if(gac.equals("끝내기"))
   {
    System.exit(0);
   }
   else if(gac.equals("실행취소"))
   {
    undoManager.undo();
   }
   else if(gac.equals("삭제"))
   {
    ta.replaceRange("",ta.getSelectionStart(), ta.getSelectionEnd());
   }
   else if(gac.equals("찾기"))
   {
    FindDialog fd=new FindDialog(this);
    fd.setBounds(getX()+40,getY()+70,450,150);
    fd.show();    
   }
   else if(gac.equals("바꾸기"))
   {
    ChangeDialog cd=new ChangeDialog(this);
    cd.setBounds(getX()+40,getY()+70,450,200);
    cd.show();      
   }
   else if(gac.equals("이동"))
   {
    MoveDialog md=new MoveDialog(this);
    md.setBounds(getX()+40,getY()+70,250,130);
    md.show();  
    int line= md.lineNumber();
    //viewPort.setViewPosition(new Point(0,line));
    ta.setCaretPosition(line);
    //System.out.print(line);
   }   
   else if(gac.equals("자동줄바꿈"))
   {
    if(mi31.getState())
     ta.setLineWrap(true);
    else
     ta.setLineWrap(false);
   }
   else if(gac.equals("글꼴"))
   {
    FontDialog ftd=new FontDialog(this);
    ftd.setBounds(getX()+40,getY()+70,530,320);    
    ftd.show(); 
    Font ft= ftd.fontSet();
    ta.setFont(ft);
   }
   else if(gac.equals("모두선택"))
   {    
    ta.selectAll ();
    ta.requestFocus();
   }
   else if(gac.equals("메모장정보"))
   {    
    HelpDialog hd=new HelpDialog(this);
    hd.setBounds(getX()+40,getY()+70,411,313);
    hd.show();
   }
   else if(gac.equals("도움말항목"))
   {
    //String [] cmdar={"/img/hh.exe","/img/notepad.chm","/img/hh.exe"};
    //Process p = Runtime.getRuntime().exec("/img/hh.exe notepad.chm");
    //Process p = Runtime.getRuntime().exec(cmdar);
    //Process p = Runtime.getRuntime().exec("C:/WINNT/help/hh.exe notepad.chm");
    Process p = Runtime.getRuntime().exec("C:/WINNT/notepad.exe"); 
   }
  }catch(Exception e){}
 }
 
 public void openDocument()
 {
   JFileChooser chooser=new JFileChooser();
  System.out.println(chooser.getFileFilter() );
   chooser.setDialogTitle("파일 열기");
   int returnVal=chooser.showOpenDialog(this);
   if(returnVal !=JFileChooser.APPROVE_OPTION)//cancel 버튼이 눌려지면 취소
    return;
    File f=chooser.getSelectedFile();//대화상자에서 선택된 파일객체 인스턴스를 구한다
   
   if(!f.exists())
   {//파일이 존재하지 않으면 에러 메세지를 띄운후 취소
     JOptionPane.showMessageDialog(this,file.getName()+" 파일을 찾을 수 없습니다.",
      "파일 열기 에러",JOptionPane.ERROR_MESSAGE);
      return;
   }
  openFile(f);
 }///////////////////////openDocument() ////////////////////
 
 public void openFile(File file)
 {///파일을 읽어들여 jta에 표시
  
  BufferedReader in=null;//버퍼 문자 입력 스트림
  //ta.setText("");//텍스트에리어 내용 초기화
  setTitle(file.getName());//윈도우 제목을 파일 이름으로
   try
   {
   in=new BufferedReader(new FileReader(file));
   }
   catch(FileNotFoundException fnfe)
   {//에러 메세지
    System.err.println("파일 열기 에러 :"+file.getName()+"파일을 찾을 수 없습니다.");
    JOptionPane.showMessageDialog(this,file.getName()+"파일을 찾을 수 없습니다.","파일 열기에러",JOptionPane.ERROR_MESSAGE);
    return;
   }
   catch(Exception e)
   {
    System.err.println("파일 열기 에러 :"+file.getName()+"파일을 찾을 수 없습니다.");
    JOptionPane.showMessageDialog(this,e.getMessage(),"파일 열기에러",JOptionPane.ERROR_MESSAGE);
    return;
   }
   
   try
   {////한 줄씩 읽어서 string형에 저장한후 ta에 보낸다
    String string="";    
    while((string=in.readLine())!=null)
    {
     st=st+(string+'\n');
    }
   ta.setText("");
   ta.setText(st);   
   }
   catch(IOException ie)
   {
   System.err.println("파일 읽기 에러 :"+ie.getMessage());
   }
   try
   {
    in.close();//입력 스트림 닫는다.
   }catch(IOException ie){}
   
   ta.setCaretPosition(0);//커서를 처음으로 위치
   viewPort.setViewPosition(new Point(0,0));//위쪽을 보여줌
  System.out.print(file.getName());
   this.file=file;//읽어들인 파일을 file멤버 필드로 설정
 }//////////////////////////////////////openFile(File file) end/////////////////
 public File getFile()
 {///문서 윈도우의 내용을 파일로 알림
  return file;
 }
 
 public void saveDocument(boolean isSaveAs)
 {//true면 새이름으로 false면 저장
  if(isSaveAs==true)
  {//새이름으로 저장할 경우 파일 대화상자 표시
   JFileChooser chooser=new JFileChooser();
   chooser.setDialogTitle("새 이름으로 저장");
   int returnVal=chooser.showSaveDialog(this);
   
    if(returnVal !=JFileChooser.APPROVE_OPTION)
     return;
    File f=chooser.getSelectedFile();
    if(f.exists())
    {//파일 이름이 이미 존재하면 덮어쓸 것인지 물어본다
     Object options[]={"예","아니오"};
     if(JOptionPane.showOptionDialog(this,"파일이 이미 존재 합니다.덮어쓸까요?","경고",
      JOptionPane.DEFAULT_OPTION,JOptionPane.WARNING_MESSAGE,null,options,options[0]) !=0)
       return;//아니오를 선택하면 저장취소
    }
    saveFile(f);
   }
    else if(isSaveAs==false)
    {//현재 문서 윈도우에 지정된 파일 인스턴스를 가져온다.
    File f=getFile();
   if(f==null)
   {
    saveDocument(true);
   }
     if(!f.exists())
     {//파일이 이미 존재하지 않으면 저장을 계속할 것인지 한번더 확인 한다.
      Object options[]={"예","아니오"};
     if(JOptionPane.showOptionDialog(this,"파일이 존재하지 않습니다.그래도 저장 할까요?","경고",
      JOptionPane.DEFAULT_OPTION,JOptionPane.WARNING_MESSAGE,null,options,options[0]) !=0)
       return;
     }     
    }
   }/////////saveDocument(boolean isSaveAs) end//////////////
 
 public void saveFile(File file)
 {///////////file 인자로 지정된 파일로 현재 내용 저장
  PrintWriter out=null;//문자 출력 스트림
  
   try
   {
    out=new PrintWriter(new BufferedWriter(new FileWriter(file)));
   }
   catch(IOException ie)
   {
    System.err.println("파일 저장 에러 :"+file.getName()+"파일을 생성 할 수 없습니다.");
    JOptionPane.showMessageDialog(this,file.getName()+"파일을 생성 할 수 없습니다.",
     "파일 저장 에러",JOptionPane.ERROR_MESSAGE);
   }
   
   String string=ta.getText();
   out.print(string);//실제 파일에 쓴다
   if(out.checkError())
   {//print() 메서드는  IOException을 던지지 않으므로 직접 검사를 해보아야 한다.
     System.err.println("파일 쓰기 에러");
   }
   else
   {
     JOptionPane.showMessageDialog(this,file.getName()+" 파일을 저장 하였습니다.","안내 메세지",JOptionPane.INFORMATION_MESSAGE);
   }
   out.close();//출력 스트림을 닫는다.
   setTitle(file.getName());//현재 파일 이름으로 윈도우 제목 설정
   this.file=file;//현재 저장된 파일 객체 인스턴스를 멤버 필드값으로 저장
 }////////////////////////////////////////saveFile(File file) end//////////////////
 
 public void printPage()
 {/////쪽 설정////
  PrinterJob pj=PrinterJob.getPrinterJob();
  pj.setPrintable(this);
  pj.pageDialog(pj.defaultPage());     
 }///쪽설정 끝////
 
 public void printDocument()
 {////프린트//////
  try{
   PrinterJob pj=PrinterJob.getPrinterJob();
   pj.setPrintable(this);
   pj.printDialog();
   pj.print();
   }catch(Exception e){}
 }////프린트 끝//// 
  
 public int print(Graphics g, PageFormat pf, int pi)throws PrinterException
 {////////Printable 구현/////////  
  if (pi >= 1)
  {
   return Printable.NO_SUCH_PAGE;
  }
  Graphics g2=(Graphics2D)g;
  g2.translate((int)pf.getImageableX(),(int)pf.getImageableY());
  this.paint(g2);
  return Printable.PAGE_EXISTS;
  }////////Printable 구현 끝/////////
 
 public void lostOwnership(Clipboard cb, Transferable contents){}//ClipboardOwner 구현
 
 public void undoableEditHappened(UndoableEditEvent undoe)
 {//UndoableEditListener 구현
  if(undoManager !=null)
   undoManager.addEdit(undoe.getEdit());
 }//UndoableEditListener 구현 끝   
 
  public static void main(String args[])
 {
  Jmemo jm=new Jmemo("제목 없음 - 메모장");
  jm.setBounds(300,200,600,400);
  jm.setVisible(true) ;
 }
}
class FindDialog extends JDialog implements ActionListener
{
  private JLabel fjdl = new JLabel();
  private JTextField jfdt = new JTextField();
  private JButton jdsbn = new JButton();
  private JButton jfdc = new JButton();
  private JPanel jfdp = new JPanel();
  private TitledBorder titledBorder1;
  private JRadioButton jfdup = new JRadioButton();
  private JRadioButton jfddown = new JRadioButton();
  private JCheckBox jfdul = new JCheckBox();
  public FindDialog(Frame parent)
 {
    super(parent,"찾기",false);
  
  setResizable(false);
    titledBorder1 = new TitledBorder(new EtchedBorder(EtchedBorder.RAISED,Color.gray,Color.white),"방향");
    jfdc.setBounds(342, 63, 98, 26);
    jfdc.setText("취소");
  jfdc.addActionListener(this);
    jdsbn.setBounds(342, 28, 98, 26);
    jdsbn.setText("다음 찾기");  
  jdsbn.addActionListener(this);  
  jdsbn.setEnabled(false);//첫 프로그램 실행시 선택 되지 않게 설정
  fjdl.setText("찾을 내용:");
    fjdl.setBounds(17, 29, 58, 24);
    this.getContentPane().setLayout(null);
    jfdt.setBounds(94, 29, 235, 21);
    jfdp.setBorder(titledBorder1);
    jfdp.setBounds(128, 59, 200, 45);
    jfdp.setLayout(null);
    jfdup.setText("위쪽");
    jfdup.setBounds(18, 20, 62, 14);
    jfddown.setText("아래쪽");
    jfddown.setBounds(108, 20, 62, 14);
    jfdul.setText("대/소문자 구분");
    jfdul.setBounds(20, 75, 101, 26);
    this.getContentPane().add(fjdl, null);
    this.getContentPane().add(jfdt, null);
    this.getContentPane().add(jdsbn, null);
    this.getContentPane().add(jfdc, null);
    this.getContentPane().add(jfdp, null);
    jfdp.add(jfdup, null);
    jfdp.add(jfddown, null);
    this.getContentPane().add(jfdul, null);
  
  //jfdt.requestFocus();
   jfdt.setCaretPosition(0);
  }
 
 public void actionPerformed(ActionEvent ae)
 {
  try{
  String gac=ae.getActionCommand();
  
   if(gac.equals("취소"))
   {
    dispose();
   }
   else if(gac.equals("다음 찾기"))
   {
   }
  }catch(Exception e){} 
 }  
}
class ChangeDialog extends JDialog implements ActionListener
{
  private JLabel jcdl1 = new JLabel();
  private JLabel jcdl2 = new JLabel();
  private JTextField jcdtf1 = new JTextField();
  private JTextField jcdtf2 = new JTextField();
  private JButton jcdb1 = new JButton();
  private JButton jcdb2 = new JButton();
  private JButton jcdb3 = new JButton();
  private JButton jcdb4 = new JButton();
  private JCheckBox jcdcb = new JCheckBox();
  public ChangeDialog(Frame parent)
 {
    super(parent,"바꾸기",false);
  
  setResizable(false);
    jcdcb.setText("대/소문자 구분");
    jcdcb.setBounds(24, 125, 178, 24);
    jcdb4.setBounds(318, 135, 106, 28);
    jcdb4.setText("취소");
    jcdb3.setBounds(318, 102, 106, 28);
    jcdb3.setText("모두 바꾸기");
    jcdb2.setBounds(318, 68, 106, 28);
    jcdb2.setText("바꾸기");
    jcdb1.setBounds(318, 35, 106, 28);
    jcdb1.setText("다음 찾기");
    jcdtf2.setBounds(100, 75, 197, 22);
    jcdtf1.setBounds(100, 39, 197, 22);
    jcdl2.setText("바꿀 내용: ");
    jcdl2.setBounds(20, 59, 79, 28);
    jcdl1.setText("찾을 내용: ");
    jcdl1.setBounds(20, 29, 79, 28);
    this.getContentPane().setLayout(null);
    this.getContentPane().add(jcdtf2, null);
    this.getContentPane().add(jcdl1, null);
    this.getContentPane().add(jcdl2, null);
    this.getContentPane().add(jcdtf1, null);
    this.getContentPane().add(jcdb1, null);
    this.getContentPane().add(jcdb2, null);
    this.getContentPane().add(jcdb3, null);
    this.getContentPane().add(jcdb4, null);
    this.getContentPane().add(jcdcb, null);
  }
 
 public void actionPerformed(ActionEvent ae)
 {
  dispose();
 }  
}
class MoveDialog extends JDialog implements ActionListener
{
 private JTextField jmdt = new JTextField();
 private JButton jmdb1 = new JButton();
 private JButton jmdb2 = new JButton();
 String getline   = new String();
 
 public MoveDialog(Frame parent)
 {
    super(parent,"이동",true);
  
  jmdt.setBounds(14, 21, 115, 26);
    this.getContentPane().setLayout(null);
    jmdb1.setBounds(143, 16, 87, 29);
    jmdb1.setText("확인");
  jmdb1.addActionListener(this);
    jmdb2.setBounds(144, 60, 87, 29);
    jmdb2.setText("취소");
  jmdb2.addActionListener(this);
    this.getContentPane().add(jmdb1, null);
    this.getContentPane().add(jmdb2, null);
    this.getContentPane().add(jmdt, null);  
 }
 
 public int lineNumber()
 {  
  return Integer.parseInt(getline);
 }
 
 public void actionPerformed(ActionEvent ae)
 {
  try{
   String gac=ae.getActionCommand();
   
   if(gac.equals("확인"))
   {    
    getline=jmdt.getText();  
    dispose();
   }
   else if(gac.equals("취소"))
   {
    dispose();
   }
  }catch(Exception e){}
 } 
}
class FontDialog extends Dialog implements ActionListener
{
  private Label jfdl1 = new Label();
  private Label jfdl2 = new Label();
  private Label jfdl3 = new Label();
  private Label jfdl4 = new Label();
  private Label jfdl5 = new Label();
  private Label jfdl6 = new Label();
  private TextField jfdtf1 = new TextField();
  private TextField jfdtf2 = new TextField();
  private TextField jfdtf3 = new TextField();
  private List jfdls1 = new List();
  private List jfdls2 = new List();
  private List jfdls3 = new List();
  private Choice jfdcb = new Choice();
  private Button jfdb1 = new Button();
  private Button jfdb2 = new Button();
  private TextField jfdtf4 = new TextField();
  Graphics g;
 
 Font f;
 String fontname="SansSerif";
 int fontstyle=Font.PLAIN;
 int size=8;
 
  String [] allFonts=GraphicsEnvironment.getLocalGraphicsEnvironment().getAvailableFontFamilyNames();
  private String [] allSizes = {"8","9","10","11","12","14","16","18","20","22","24","26","28","36","48","72"};
  private String [] allStyle ={"보통","기울임꼴","굵게","굵은 기울임꼴"};
  FontDialog(Frame parent) 
 {
    super(parent,"글꼴",true);
  
  setResizable(false);
  addWindowListener(new WindowAdapter(){public void windowClosing(WindowEvent we){dispose();}});
      setLayout(null);
  
  for(int i=0;i<allFonts.length;i++)
  {
  jfdls1.add(allFonts[i]);
  }
  
  for(int i=0;i<16;i++)
  {
   jfdls3.add(allSizes[i]);
  }  
  
  for(int i=0;i<4;i++)
  {
   jfdls2.add(allStyle[i]);
  }
    jfdl1.setText("글꼴");
    jfdl1.setBounds(10, 24, 98, 26);
    jfdl2.setText("글꼴 스타일");
    jfdl2.setBounds(198, 26, 98, 26);
    jfdl3.setText("크기");
    jfdl3.setBounds(340, 24, 98, 26);
    jfdl4.setText("보기");
  jfdl4.setBounds(266,157,49,25);
  jfdl5.setText("스크립트");
    jfdl5.setBounds(266, 236, 98, 25);
    jfdtf1.setBounds(10, 49, 181, 24);
    jfdtf2.setBounds(201, 49, 129, 24);
    jfdtf3.setBounds(340, 49, 91, 24);
    jfdls1.setBounds(10, 77, 181, 79);
  jfdls1.addActionListener(this);
    jfdls2.setBounds(201, 77, 129, 79);
  jfdls2.addActionListener(this);
    jfdls3.setBounds(340, 77, 91, 79);
  jfdls3.addActionListener(this);
    jfdcb.setBounds(258, 263, 180, 22);
    jfdb1.setBounds(436, 51, 82, 28);
    jfdb1.setLabel("확인");
  jfdb1.addActionListener(this);
    jfdb2.setBounds(436, 85, 82, 28);
    jfdb2.setLabel("취소");
  jfdb2.addActionListener(this);
  add(jfdcb);
  add(jfdl4);
  add(jfdl5);
  add(jfdl1);
  add(jfdtf1);
  add(jfdls1);
  add(jfdb1);
  add(jfdb2);
  add(jfdls3);
  add(jfdl3);
  add(jfdtf3);
  add(jfdls2);
  add(jfdtf2);
  add(jfdl2);  
  
  jfdl6.setBounds(212, 190,220,40);
  jfdl6.setText("가나다AaBbYyZz");
  add(jfdl6);
 }
 
 public void paint(Graphics g)
 {
  g.setColor(Color.lightGray);
  g.draw3DRect (202, 182, 232, 50,false);
  g.setColor(Color.black);  
 }
 
 public Font fontSet()
 {
  jfdls1.getSelectedItem();
  fontname=jfdls1.getSelectedItem();
  
  if(jfdls2.getSelectedItem().equals("보통"))
   fontstyle=Font.PLAIN;
  if(jfdls2.getSelectedItem().equals("기울임꼴"))
   fontstyle=Font.ITALIC;   
  if(jfdls2.getSelectedItem().equals("굵게"))
   fontstyle=Font.BOLD;
  if(jfdls2.getSelectedItem().equals("굵은 기울임꼴"))
   fontstyle=Font.ITALIC+Font.BOLD;
  
  jfdtf3.setText(jfdls3.getSelectedItem());   
   size=Integer.parseInt(jfdls3.getSelectedItem());
  
  return f=new Font(fontname,fontstyle,size);
 }
  
 public void actionPerformed(ActionEvent ae)
 {
  if((ae.getSource()).equals(jfdls1))
  {
   jfdtf1.setText(jfdls1.getSelectedItem());
   fontname=jfdls1.getSelectedItem();
   f=new Font(fontname,fontstyle,size);
   jfdl6.setFont(f); 
  }
  else if((ae.getSource()).equals(jfdls2))
  {
   jfdtf2.setText(jfdls2.getSelectedItem());
   
   if(jfdls2.getSelectedItem().equals("보통"))
    fontstyle=Font.PLAIN;
   if(jfdls2.getSelectedItem().equals("기울임꼴"))
    fontstyle=Font.ITALIC;   
   if(jfdls2.getSelectedItem().equals("굵게"))
    fontstyle=Font.BOLD;
   if(jfdls2.getSelectedItem().equals("굵은 기울임꼴"))
    fontstyle=Font.ITALIC+Font.BOLD;
  
   f=new Font(fontname,fontstyle,size);
   jfdl6.setFont(f);   
  }
  else if((ae.getSource()).equals(jfdls3))
  {
   jfdtf3.setText(jfdls3.getSelectedItem());   
   size=Integer.parseInt(jfdls3.getSelectedItem());
   f=new Font(fontname,fontstyle,size);
   jfdl6.setFont(f);  
  }
  else if((ae.getSource()).equals(jfdb1))
  {
   dispose();
  }
  else if((ae.getSource()).equals(jfdb2))
  {
   dispose();
  }
 }  
}
class HelpDialog extends Dialog implements ActionListener
{//도움말 새창
 Button ok;//누르면 창이 닫히는 버튼
 Image img=getToolkit().getImage("img/memo.gif");
 Image mh=getToolkit().getImage("img/mh.gif");
 Image minfo=getToolkit().getImage("img/minfo.gif");
 Font f=new Font("굴림",Font.PLAIN,12);
 
 HelpDialog(Frame parent)
 {
  super(parent,"메모장 정보",true);
  
  setResizable(false);  
  
  setLayout(null);
  ok=new Button("확 인");
  ok.addActionListener(this);
  ok.setBounds(290,280,90,25);  
  add(ok);    
    
  addWindowListener(new WindowAdapter(){public void windowClosing(WindowEvent we){dispose();}}); 
 } 
 
 public void paint(Graphics g)
 {
  Insets insets=getInsets();  
  
  g.setFont(f);
  g.drawImage(minfo,insets.left,insets.top,410,77,this);
  g.drawImage(mh,15,105,32,30,this);  
  g.drawString("Wicrosoft (R) 메모장",66,120);
  g.drawString("버전 1.0 (빌드 2195: Service Pack 3)",66,137);
  g.drawString("Copyright (C) 1981-1999 Wicrosoft Corp.",66,153);
  g.drawString("이 제품은 다음 사용자에게 사용이 허가되었습니다.",66,200);
  g.drawString("jdk 깔린 컴텨",66,216);
  g.setColor(Color.lightGray);
  g.draw3DRect (66,248,334,1,false);
  g.setColor(Color.black);
  String p= Runtime.getRuntime().totalMemory()/1024+"";   
  g.drawString("Virtual Machine에서 사용할 수 있는 실제 메모리: "+p ,66,265); 
 }
 
 public void actionPerformed(ActionEvent ae)
 {
  dispose();
 }  
}

첨부 파일 - 이미지+ 도움말... ( 경로 /img )


+ Recent posts