제네릭 프로그래밍

 

제네릭 프로그래밍(Generic Programming) : 작성한 코드를 다양한 타입의 객체에 대해 재사용하는 객체 지향 기법이다.

 

제네릭은 자바 버전 1.5부터 추가된 기능으로, 복잡한 애플리케이션을 개발할 때 발생하는 여러 가지 버그들을 많이 줄일 수 있다.

 

제네릭은 안드로이드와 같은 애플리케이션을 개발할 때 많이 사용되므로 정확하게 알고 있어야 한다.

 

- C++ 언어의 템플레이트와 거의 유사한 기능이다.

 

 

 

제네릭

 

제네릭(Generic) : 클래스를 정의할 때, 구체적인 타입(type)을 적지 않고 변수 형태로 적어 놓는 것이다

 

클래스를 선언하여 객체를 생성할 때, 구체적인 타입을 기재한다. 즉, 타입을 어떤 클래스 종류의 매개변수로 보는 것이다.

 

기존의 방법 : 이전에는 아래와 같이 Object 타입으로 객체를 받아서 다형성을 이용했는데, 그 이유는 모든 객체가 Object 클래스를 상속하므로 다양한 형태의 데이터를 담을 수 있었기 때문이다.

 

public class PrevBox {

private Object data;


public void set(Object data) { 

this.data = data; 

}


public Object get() { 

return data; 

}

}

  • , get() 메소드로 저장한 객체를 반환받을 때 원하는 타입으로 캐스팅(Casting)해야 하는 번거로움이 있었다.

 

제네릭 기법


//제네릭 클래스

public class Box<T> { // T는 타입을 의미한다.

private T data;


public void set(T data) { 

this.data = data; 

}


public T get() { 

return data; 

}

}

  • 위의 클래스를 선언할 때아래와 같이 구체적인 타입을 기재하여 제네릭 클래스를 사용한다.

Box<String> strBox = new Box<String>(); // String 타입만 저장한다.

Box<Integer> intBox = new Box<Integer>(); // Integer 타입만 저장한다.

 

제네릭 클래스(Genenric class)에서는 타입을 변수로 표시한다. 이것을 타입 매개변수(type parameter)”라고 하며, 타입 매개변수는 객체 생성 시에 프로그래머에 의하여 결정된다.

 

 

 

타입 매개변수의 표기

 

제네릭 클래스는 여러 개의 타입 매개변수를 가질 수 있으나 타입의 이름은 클래스나 인터페이스 안에서 유일하여야 한다.

 

관례에 의하여 타입의 이름은 하나의 대문자로 한다.

 

대문자로 하는 이유는 변수의 이름과 타입의 이름을 구별할 수 있게 하기 위함이다.

 

아래는 일반적으로 널리 사용되는 타입의 이름들이다.

  • E Element(요소 : 자바 컬렉션 라이브러리에서 많이 사용된다.)

  • K Key

  • N Number

  • T Type

  • V Value

  • S, U, V 2번째, 3번째, 4번째 타입

 

다이아몬드 : 자바 SE 7버전부터는 제네릭 클래스의 생성자를 호출할 때, 타입 인수를 구체적으로 주지 않아도 된다. 컴파일러가 문맥에서 타입을 추측하기 때문이다. “<>”를 다이아몬드라고 표현한다.

 

Box<String> Box = new Box<>(); //Box<String> strBox = new Box<String>(); 와 같다.

 

다중 타입 매개변수(Multiple Type Parameters)

interface Pair<K, V> {
	public K getKey();
	public V getValue();
}

public class OrderedPair<K, V> implements Pair<K, V> {
	private K key;
	private V value;
 
	public OrderedPair(K key, V value) {
		this.key = key;
		this.value = value;
	}
 
	public K getKey() { 
		return key; 
	}
	
	public V getValue() { 
		return value; 
	}
}


public class PairTest {
	public static void main(String[] args) {
		Pair<String, Integer> pair1 = new OrderedPair<String, Integer>(“Even”, 8);
		Pair<String, String> pair2 = new OrderedPair<String, String>(“Hi”, “nice~”);
		 
		//pair1과 pair2는 인터페이서 Pair 참조 변수로 선언되었다.
		//new OrderPair<String, Integer>은 K를 String으로 실체화하고, V를 Integer로 실체화한다.
		//오토박싱(autoboxing)에 의하여 int(위의 값 8)가 Integer 객체로 자동 변환된다.
		//오토박싱이란 기초 자료형을 대응되는 클래스 객체로 자동 변환해주는 기능이다.
		 
		//또는 아래와 같은 방식도 가능하다.
		 
		//Pair<String, Integer> pair1 = new OrderedPair<>(“Even”, 8);
		//Pair<String, String> pair2 = new OrderedPair<>(“Hi”, “nice~”);
 	}
}


- Raw 타입 : 타입 매개변수가 없는 제네릭 클래스의 이름이다.

 

Box<Integer> intBox = new Box<>(); //기존 방식

Box rawBox = new Box(); //Raw 방식

  • 주의할 점은 처음부터 제네릭 클래스가 아니면 Raw 타입이라고 하지 않는다.

  • Raw 타입은 JDK 5.0 이전에는 제네릭이 없었기 때문에 이전 코드와 호환성을 유지하기 위해 등장하였다. , 타입을 주지 않으면 무조건 Object 타입으로 간주하는 것이다.

 

 

 

제네릭 메소드

 

일반 클래스의 메소드에서도 타입 매개변수를 사용하여 제네릭 메소드를 정의할 수 있다.

 

제네릭 메소드에서의 타입 매개변수의 범위는 메소드 내부로 제한된다.

 

아래는 실제 Array 클래스에 있는 제네릭 메소드의 일부이다.

 

public class Array {

...

public static <T> T getLast(T[] a)

{

return a[a.length-1];

}

...

}

 

타입 매개변수(<T>)는 반드시 메소드의 수식자(public, static)와 반환형(T) 사이에 위치되어야 한다.


 public static <T> T getLast(T[] a)


제네릭 메소드를 호출하기 위해서는 실제 타입을 꺽쇠괄호 안에 넣어주어도 되고 생략하여도 된다.

 

String[] name = {“김철수”, “김영희”, “김숙자”, “김말년”};

String last = Array.<String>getLast(name);

String last = Array.getLast(name); // 컴파일러는 이미 타입 정보를 알고 있다!

 

한정된 타입 매개변수 : 타입 매개변수로 전달되는 타입의 종류를 제한하기 위한 기능으로 extends 키워드를 사용한다.

  • 아래는 한정된 타입 매개변수를 표현하는데 잘못된 예시이다. Array 클래스로 계속 예를 들면,

public class Array {

...

public static <T> T getMax(T[] a) {

if (a == null || a.length = 0) return null;

 

T largest = a[0];

 

for(int i = 1; i < a.length; i++) {

if (largest.compareTo(a[i]) > 0) largest = a[i];

}


return largest;

}

...

}

  • 위의 예시에서 TcompareTo()라고 하는 Comparable 인터페이스를 구현해야 한다. 따라서 클래스의 범위를 Comparable 인터페이스를 구현한 클래스로 제한하는 것이 바람직하다.

  • 아래는 위의 메소드 getMax()를 올바르게 변경한 것이다.

public static <T extends Comparable> T getMax(T[] a) {

...

}

  • “T extends Comparable”은 타입 TComparable 인터페이스를 구현한 클래스들에 대해서만 호출될 수 있음을 의미한다.

  • implements 키워드가 아닌 extends 키워드를 사용하는 것에 주의해야 한다. 이를 통해 타입 매개변수에 상속관계가 성립함을 추측할 수 있다.

 

 

 

제네릭과 상속

 

- 제네릭에서는 타입 매개변수에 상속관계가 성립한다.


 예를 들어 Number를 타입 매개변수로 주어 객체를 생성했다면, Number의 자식 클래스인 Integer, Double, Float 객체도 모두 처리할 수 있다. 맨 처음 작성했던 제네릭 클래스인 Box 를 사용해보자.


public class Box<T> {

private T data;


public void set(T data) { 

this.data = data; 

}


public T get() { 

return data; 

}

}

 

Box<Number> box = new Box<Number>(); // 객체 생성 시 구체적인 타입을 기재한다.

box.add(new Integer(10)); // 타입의 하위 클래스들도 모두 처리된다.

box.add(new Double(10.1));

box.add(new Float(0.0));

 

- 제네릭에서의 상속에서는 한 가지 주의할 점이 있다.


 타입 매개변수에서 상속관계가 성립하는 것과 어떤 타입 매개변수를 가진 제네릭 클래스에서 상속관계가 성립하는 것은 서로 다르다는 것이다.

 

public void exMethod(Box<Number> number) { ... }

 

 위와 같은 메소드에서는 Box<Integer>Box<Double>과 같은 제네릭 클래스는 매개변수로 대입할 수 없다. 그 이유는 Integer 와 Double은 Number와 상속관계가 성립하지만 Box<Integer> 와 Box<Double>은 Box<Number> 와 상속관계가 성립하지 않기 때문이다. 쉽게 아래의 그림을 보면 차이를 알 수 있다.



 그러나 Box<Number>와 Box<Integer>, Box<Double> 사이에 상속관계가 성립할 수 있다.

 

제네릭 클래스의 상속 : 제네릭 클래스들 간의 상속도 일반 클래스처럼 extendsimplements 키워드를 사용하여 표시할 수 있다아래는 나중에 배울 컬렉션 클래스의 일부이다.


ArrayList<E> implements List<E> { ... }

List<E> extends Collection<E> { ... }

 

 즉, Collection ← List<String> ← ArrayList<String> 순서로 부모 ← 자식 상속관계가 생긴다.

 

 

 

와일드 카드

 

와일드 카드(Wild Card) : 제네릭을 사용하는 코드에서 타입 매개변수를 기재하는 꺽쇠괄호 속 물음표(?)로 표현되며, 카드 게임에서 조커와 유사한 역할을 한다. , 어떤 타입이던지 나타낼 수 있다.

 

와일드 카드는 매개변수, 필드, 지역 변수의 타입을 나타내는 등 다양하게 사용된다.

 

상한이 있는 와일드 카드 : 전체 타입이 아닌 일정한 상한이 있는 타입을 표시하는데 사용된다


 코드를 작성할 때, <? extends (상한)> 과 같이 작성한다. 예를 들어 List<Integer>, List<Double>, List<Number>에만 적용되는 메소드를 작성하고 싶다면, Integer, Double 클래스는 모두 Number 클래스를 상속받기 때문에 아래와 같이 작성하면 된다.

 

public static void exMethod(List<? extends Number> list) { ... }

 

 List<? extends Number>의 의미는 Number를 상속받은 어떤 클래스도 ? 자리에 올 수 있다는 것을 의미한다. 이는List<Number>보다는 적용 대상을 넓힌 것이다. List<Number>는 타입 매개변수로 Number에 대해서만 매치되지만 List<? extends Number>Number의 자식클래스까지도 타입 매개변수로 매치되기 때문이다.

 

public static double sumOfList(List<? extends Number> list) {

double s = 0.0;

 

for(Number n : list) {

s += n.doubleValue();

}

 

return s;

}


public static void main(String[] args) {

List<Integer> li = Arrays.asList(1, 2, 3, 4, 5);

System.out.println(“sum = ” + sumOfList(li));

}

 

[출력 결과]

List<Integer> 타입의 li 가 List<? extends Number> 타입을 매개변수로 받는 sumOfList의 메소드에 적용된다.



하한이 있는 와일드 카드 : 특정한 타입을 가진 모든 객체에 대해 작동할 때 사용되는 카드로, <? super (하한)>와 같은 문법을 사용한다.


 예를 들어 Integer 객체를 가질 수 있는 모든 객체를 리스트에 추가하는 메소드를 작성한다면, List<Integer>, List<Number>, List<Object>와 같은 Integer 값을 가지고 있는 모든 객체에 대하여 해당 메소드를 적용시킬 수 있다.

 

public static void addNumbers(List<? super Integer> list) {

for(int i = 1; I <= 10; i++) {

list.add(i);

}

}

 


한도가 없는 와일드 카드 : 모든 타입에 매치되는 와일드 카드로, 단순히 <?> 표현된다. , List<?> 라고 코드를 작성하면 모든 타입의 리스트를 사용하게 되는 것이다.


 주의할 점은 List<?>List<Object>는 혼동할 수 있으나 차이가 있다는 것이다.

 

public static void printList(List<Object> list) {

for(Object element : list) {

System.out.println(element + ", ");

}


System.out.println();

}

 

 위의 경우 printList() 메소드는 Object 객체의 리스트(List<Object>)만 출력할 수 있다. 그 이유는 List<Integer>, List<String>, List<Double>와 같은 클래스들은 List<Object>의 자식이 아니기 때문이다.


 앞에서 언급했듯이 타입 매개변수 간의 상속관계가 성립한 것이지 클래스간의 상속관계는 성립되지 않았다.


 아래는 모든 타입에 매치되는 리스트를 매개변수로 받을 수 있는 올바르게 작성된 코드이다.

 

public static void printList(List<?> list) {

for(Object element : list) {

System.out.println(element + “, ”);

}


System.out.println();

}

 

 






 

Exception Event

 

예외(Exception) : Exception Event의 약자로, 프로그램의 실행 중에 발생하는 이벤트로서 프로그램의 정상적인 실행 흐름을 중단시킨다. , 자바에서 발생하는 오류를 의미한다.

 

예외 객체(Exception Object) : 메소드 안에서 오류가 발생하면 메소드가 자바 런타임 시스템으로 넘기는 객체로, 발생된 오류를 설명한다. 이 객체는 오류의 타입과 오류 발생 시의 프로그램의 상태 등의 정보를 포함하고 있다.

 

예외 던지기 : 예외 객체를 생성하는 것을 표현하는 말이다.


- 예외 잡기 : 예외 객체를 처리하는 것을 표현하는 말이다.



 

 

 

예외 처리기

 

예외가 발생한 지점 이후의 문장들은 실행되지 않는다. 그러나 프로그램 실행 상태를 유지하기 위해 예외가 발생하는 즉시 프로그램을 종료하지 않고, 예외를 처리하는 방법을 자바에서 제공한다.

 

예외 처리기의 기본 형식

  • 예외가 발생했을 경우 : try → catch ( finally)

  • 예외가 발생하지 않았을 경우 : try ( finally)

try {

//예외가 발생할 것 같은 코드

} catch(예외타입명 참조변수명) { //예외의 최상위 객체는 Exception 이다.

//예외를 처리하는 코드

} finally {

// try 또는 catch 블록이 끝나면 무조건 시작하는 코드로, 해당 블록은 생략할 수 있다.

}


- trycatch 블록은 별도의 독립된 블록이므로 try 블록에서 선언된 변수는 catch 블록에서 사용할 수 없다.


예제 : 0으로 나누는 경우

public class DivideByZero {
	public static void main(String[] args) {
		int x = 10;
		int y = 0;

		try {
			int reault = x/y;
		} catch(ArithmeticException e) { // Arithmetic 은 “산술”의 의미를 가진다.
			System.out.println(“0으로 나눌 수 없습니다.”);
		}

		System.out.println(“프로그램 진행중”);
	}
}

[ 출력 결과 ]


출력 결과를 보면, 예외가 발생하더라도 예외 처리를 통해 프로그램이 종료되지 않고 계속 실행되는 것을 볼 수 있다. 하나의 예제를 또 살펴보자.


예제 : 배열에서 인덱스가 배열의 크기를 벗어나는 경우

public class ArrayException {
	public static void main(String[] args) {
		int[] array = {1, 2, 3, 4, 5};
		int i = 5;
		
		try {
			int result = array[5];
		} catch (ArrayIndexOutOfBoundsException e) {
			System.out.println("array 배열은 0~4까지의 인덱스를 가지므로 인덱스 5는 참조불가");
		}

		System.out.println("프로그램 진행중");
	}
}

 [ 출력 결과 ]


배열의 인덱스가 4가 마지막인데 5번 인덱스를 찾으면 위처럼 ArrayIndexOutOfBoundsException이 발생하게 된다. 그러나 예외 처리를 통해 프로그램이 여전히 실행 중인 것을 볼 수 있다.



- 예외 처리를 하는 목적 : 오류 처리 코드 즉, 오류가 발생할 수 있는 코드를 안전하고 정상적인 코드와 분리할 수 있다. 제일 중요한 것은 오류가 나지 않게 코딩을 하는 것이다. 하지만 초보 개발자에겐 어렵기 때문에 예외 처리를 꼼꼼히 하는 것이 좋다.

 

 

 

finally 블록

 

- finally 블록은 try-catch 블록이 종료될 때 반드시 실행된다.

 

- finally 블록을 사용하는 이유는 자원 정리를 하지 않고 try-catch 블록을 종료하는 것을 방지하기 위함이다.

 

자원을 정리한다는 것은 파일이나 메모리와 같은 자원을 반납하는 코드를 사용하는 것을 말한다. 따라서 이와 같은 코드는 finally 블록 안에 작성하는 것이 좋다.

 

- finally 블록을 적절하게 사용하면 자원의 누출을 막을 수 있다.


- 예제 : 파일 작성하기

public class FileError {
	private int[] intArray;
	private static final int SIZE = 10;
	 
	public FileError() {
		intArray = new int[SIZE];
		for(int i = 0; i < SIZE; i++) {
			intArray[i] = i;
		}
		writeList();
	}
	 
	public void writeList() {
		PrintWriter out = null; //PrintWriter란 파일에 데이터를 기록하는 출력 스트림 객체이다.
	 
		try {
			out = new PrintWriter(new FileWriter("file.txt"));
			for(int i = 0; i < SIZE; i++) {
				out.println("배열 원소 " + i + " = " + intArray[i]);
			}
		} catch (ArrayIndexOutOfBoundsException e) { //배열 인덱스 오류 발생 시 실행
			System.err.println("ArrayIndexOutOfBoundsException : ");
		} catch (IOException e) {//입출력 오류 발생 시 실행
			System.err.println("IOException : ");
		} finally {
			if(out != null) {
				System.out.println("finally 블록에서 자원 정리");
				out.close();
			}
		}
	}
	 
	public static void main(String[] args) {
		new FileError();
	}
}

[ 출력 결과 ]


정상적으로 파일이 생성되었으며, 자원 정리까지 마친 모습이다.


디렉토리 경로를 서술할 때 "file.txt" 처럼 파일명만 적게 되면 프로젝트 폴더(Chapter) 아래에 파일이 생성된다. 즉, 작업 디렉토리 아래에 파일이 생성되는 것이다. 



파일의 내용 또한 정상적으로 기재되었다.



 

 

try-with-resources 문장

 

하나 이상의 자원을 선언하는 try 문장이다. 즉, 메모리 공간을 빌려 쓰는 등의 경우가 무조건 있다면! try-with-resource 문장을 사용하는 것이 자원을 반납하는데 용이하다.

 

자원은 프로그램이 종료되면서 반드시 반납해야 하는데, try-with-resources 문장은 문장의 끝에서 자원들을 자동으로 반납할 수 있게 한다.

 

- Java SE 7버전부터 추가되었다.


- 아래의 예시는 readList() 메소드에서 예외가 발생하면 해당 메소드를 호출한 곳으로 예외를 던지는 경우이다. 즉, 메소드 내에서 예외를 처리하지 않는다.

  • 이 기능을 사용하려면 자원 객체가 java.lang.AutoCloseable 인터페이스를 구현하여야 한다. Java SE 7버전부터 BufferedReader 클래스가 이 인터페이스를 구현하고 있다. 

static String readList() throws IOException {

try (BufferedReader br = new BufferedReader(new FileReader(“file.txt”)) {

return br.readLine();

}

}

 

- try 키워드 바로 다음에 소괄호가 있으면 소괄호 안의 내용을 자원으로 취급한다.

 

- try 문장이 정상적으로 종료되건 예외가 발생하건 간에 무조건 자원은 닫힌다.


 

 

 

예외 처리

 

예외는 하나의 객체이다.


예외의 종류는 크게 3가지가 있다.

  • Error

  • RuntimeException

  • 기타 예외

예외의 종류에 따라 처리되지 않아도 되는 예외도 존재한다.

 

모든 예외는 Throwable 클래스로부터 상속되어서 ErrorException 이라고 하는 두 개의 클래스로 나누어진다Exception은 다시 RuntimeException과 기타 예외로 나누어진다.


- Error : 자바 가상 기계 안에서 치명적인 오류가 발생하면 생성된다.

  • 대부분의 애플리케이션은 이러한 오류를 예측하거나 복구할 수 없다. 예를 들어, 하드웨어의 오류로 파일을 읽을 수 없는 경우가 있는데, 이런 경우 IOError가 발생한다.

  • Error는 자주 발생하지 않으며, 예외 처리 대상이 아니다. 따라서 컴파일러는 체크하지 않는다.

 

- RuntimeException : 주로 프로그래밍 버그나 논리 오류에서 기인한다

  • 예를 들어 파일 이름을 FileReader 생성자로 전달하는 과정에서 논리 오류로 항상 null값을 전달한다면 생성자가 NullPointerException을 발생한다애플리케이션은 이러한 예외를 물론 잡아서 처리(예외 처리기)할 수 있지만 보다 합리적인 방법은 예외를 일으킨 버그를 잡는 것이다.

  • RuntimeException은 예외 처리의 주된 대상이 아니므로 컴파일러가 체크하지 않는다.

 

- ErrorRuntimeException을 합쳐서 비체크 예외(unchecked exceptions)라고 한다.

 

기타 예외 : 체크 예외(checked exception)라고 하며, 충분히 예견될 수 있고 회복할 수 있으므로 프로그램은 반드시 이 예외들을 처리하여야 한다.

  • 예를 들어 애플리케이션이 사용자에게 입력 파일 이름을 받아서 파일을 오픈하는 상황이 있다면, 정상적인 작동에 의해 사용자가 이미 존재하는 파일 이름을 입력하고 FileReader 객체가 생성될 것이다. 그러나 실수로 잘못된 파일의 이름을 입력한 경우 FileNotFoundException 예외가 발생한다. 이 때 프로그램이 실행 중단되길 바라는 게 아니라, 예외 처리의 주된 대상으로 보고 예외를 잡아서 다시 사용자에게 정확한 파일 이름을 받게 해야 한다.

  • 기타 예외는 컴파일러가 예외를 처리했는지 확인한다. 만약 처리하지 않았다면 컴파일 오류가 발생한다.


- 예외의 포괄적 처리 : 예외도 객체임은 앞서 설명했었다. 따라서 처리할 때 상위 클래스를 이용하여 하위 클래스까지 포괄적으로 처리할 수 있다.

 

try {

getInputExample(); //이 함수에 예외가 발생한다고 가정하자.

} catch (NumberException e) { //NumberException 의 하위 클래스를 모두 잡는다.

//예외 처리 코드

}


위의 방법 외에도 예외의 최상위 클래스인 Exception을 예외종류로 catch 소괄호에 선언 catch (Exception e) 하면 모든 예외를 잡을 수 있다. 그러나 어떤 예외가 발생했는지 분간할 수 없다. 그러므로 구체적인 클래스 이름을 먼저 쓰고 일반적인 클래스 이름(상위 클래스)은 나중에 쓰는 것이 좋다. , catch 블록을 사용할 때는 하위 클래스부터 상위 클래스 순서로 작성하여야 한다.

 

 

 

예외와 메소드

 

자바에서는 예외를 처리하는 방법이 크게 두 가지가 있다.

  • 예외를 잡아서 그 자리에서 처리하는 방법 : try-catch 블록 사용

  • 메소드가 예외를 발생시킨다고 기술하는 방법 : throws 키워드 사용하여 다른 메소드한테 예외 처리 맡기기

- “메소드가 예외를 발생시킨다고 기술하는 방법은 가끔 메소드가 발생되는 예외를 그 자리에서 처리하지 않고, 자신을 호출한 상위 메소드로 예외를 전달하는 편이 더 적절할 때 사용된다.


 이와 같은 방식을 사용하는 이유는 발생하는 모든 예외를 그 자리에서 처리하는 것은 상당한 양의 코드를 필요로 하고 또 반드시 상위 메소드가 그 예외를 처리하도록 해야 하는 경우도 있기 때문이다.

 

상위 메소드가 예외를 처리하도록 하려면, 반드시 메소드가 이들 예외를 던진다고 메소드 정의에 표시하여야 한다.

 

public void writeList() throws IOException {

...

}

 

비체크 예외는 상위 메소드로 전달하지 않아도 된다. 그러나 기술할 수는 있다.

 

public void writeList() throws IOException, ArrayIndexOutOfBoundsException {

...

}

 

보통 RuntimeException은 비체크 예외지만, 프로그램의 다른 영역으로 넘기지 말고 직접 처리해야 한다.

 

계속 언급해온 예외 처리라는 것은 정확하게 실제 발생한 예외의 종류와 예외 처리기가 처리하기로 한 예외의 종류가 일치하는 것을 의미한다.

 

예외 처리 과정


 어떤 메소드 안에 예외가 발생하면 런타임 시스템은 그 메소드 안에 예외 처리기가 있는 지를 살핀다. 만약 그 자리에 예외 처리기가 없다면 호출 스택(call stack)에 있는 상위 메소드를 조사하게 된다. 


 즉, 탐색은 예외가 발생한 메소드부터 시작하여서 메소드가 호출된 순서의 역순으로 진행된다. 아래의 과정 중 예외가 전달되는 상황에서 호출 스택을 탐색하게 되는 것이다.

 

예외가 발생한다. 예외가 전달된다. 예외를 잡는다.

 

 만약 전체 호출 스택을 런타임 시스템이 다 뒤졌는데도 처리기를 발견하지 못하면 그냥 프로그램을 종료시킨다.

 

 호출 스택은 메소드의 호출 순서를 기억하며, 지금까지 호출된 메소드를 스택에 모아놓는다. 호출 스택이 있어야만 메소드가 종료되었을 경우, 되돌아갈 메소드를 찾을 수 있다.

 

 

 

예외 생성하기

 

예외는 주로 자바 라이브러리에서 많이 발생하지만 실제로는 어떤 코드라도 예외를 발생시킬 수 있다.

 

자바에서는 예외 객체를 생성하는 키워드를 throw 라고 한다.

 

- throws는 예외를 던지는 키워드이고, throw는 예외 객체를 생성하는 키워드임에 주의해야 한다.

 

자바에서는 오류가 감지되면 throw 문을 사용하여 예외를 생성한다.

 

- throw 문장은 Throwable 객체만 하나의 인수로 요구한다. Throwable 객체는 Throwable 클래스를 상속받는 자식 클래스들의 인스턴스(객체)이다. 예외를 던질 때는 예외를 다중으로 던질 수 있지만 예외를 생성할 때는 throw 키워드 하나에 오직 하나의 예외만 생성할 수 있는 것이다.

 

- catch 블록에서 예외를 처리하지 못하거나 다른 예외 처리기로 작업을 위임할 경우에는 예외를 다시 발생시켜서 전달할 수도 있다. , 연속적인 예외가 발생하는 것이다


 단아래에 서술한 ExampleException은 예시이므로 실제로 생성할 예외 클래스는 미리 정의되어 있어야 한다.


try {

...

} catch (IOException e) {

throw new ExampleException(“다른 예외”, e);

}


 새로운 예외 객체에는 원래의 예외 객체가 첨부된다. 즉, 위에는 ExampleException과 IOException이 서로 세트처럼 붙어 다니게 된다. 따라서 이 두 개를 모두 처리할 수 있는 올바른 처리기를 만날 때까지 자바 런타임 시스템은 메소드 호출 스택을 거슬러 올라가며 탐색한다.

 

 

 

사용자 정의 예외

 

다른 예외와 구별하여 특별히 처리하려는 오류가 있다면 사용자 정의 예외 클래스를 생성한다.

 

- Exception 클래스를 상속하여 만든다.

 

public class MyException extends Exception {

...

}

 






 

패키지(Package)

 

키지(package) : 관련 있는 클래스나 인터페이스들을 묶은 것이다.

 

클래스를 찾아서 사용하기 쉬워지며 서로 다른 내용의 동일 이름의 충돌을 막을 수 있고 접근을 제어할 수 있다.

 

자바가 제공하는 라이브러리도 기능별로 패키지로 묶여서 제공되고 있다.

  • 기초적인 기능 제공 패키지 : java.lang

  • 네트워크 담당 기능 패키지 : java.net

 

- 그러나 사용자가 직접 패키지를 만들어 사용할 수도 있다. src 폴더 아래에 패키지 폴더를 생성하면, 그 아래 소스 파일 맨 위에 package 키워드를 기재해야 한다.


package (패키지명); //끝에 세미콜론 필수


public class (클래스명) {

...

}


- 사용자 라이브러리 패키지의 예는 아래와 같다.

 

package graphics;


//Drawable.java 파일로 저장

public interface Drawable {

...

} 


package graphics;


//Shape.java 파일로 저장

public abstract class Shape {

...

}


package graphics;


//Circle.java 파일로 저장

public class Circle extends Shape implements Drawable {

...

}


package graphics;


//Rectangle.java 파일로 저장

public class Rectangle extends Shape implements Drawable {

...

}



-  패키지를 이용하면 개발자는 다양한 장점을 가질 수 있다.

  • 개발자들은 클래스들이 서로 연관되어 있음을 쉽게 알 수 있다.

  • 개발자들은 그래픽을 제공하는 클래스들을 쉽게 찾을 수 있다.

  • 패키지마다 독립적인 이름 공간을 가지므로 다른 패키지의 동일한 이름의 클래스가 있더라도 충돌을 일으키지 않는다.


- 각각의 패키지는 접근에 제약을 가할 수 있으므로 동일 패키지 안의 클래스들은 서로 간에 자유롭게 사용할 수 있고 다른 패키지는 그러지 못하게 설정할 수 있다.

 

위에서 지정한 패키지는 실제로 자바의 java.awt 패키지에 있는 클래스와 동일한 이름의 클래스를 가지고 있다. 그러나 자바는 서로 다른 패키지에서 동일한 이름의 클래스를 허용한다.

  • 기존의 클래스 이름 : java.awt.Rectangle

  • 사용자 클래스 이름 : graphics.Rectangle

 

, 패키지 이름이 같으면 동일한 이름이 허용되지 않으며, 이를 위한 규칙이 존재한다.

  • 패키지의 이름은 클래스나 인터페이스의 이름과의 중복을 피하기 위해 일반적으로 소문자만 사용한다.

  • 패키지 이름으로 인터넷 도메인 이름의 역순을 사용한다. 기업에서는 회사명.부서명.프로젝트이름으로 짓는 경우가 많다. 예를 들어, A회사의 Network 부서의 wifi라는 프로젝트이름을 가지고 패키지를 짓는다면 network.A.wifi 가 패키지명이 될 것이다.

  • 자바 언어 자체의 패키지는 javajavax로 시작한다.


 

 

패키지 사용하기

 

패키지 안에 포함된 클래스인터페이스패키지 멤버라고 불린다.

 

- 위에서 설명했듯이 패키지 내에서는 클래스와 인터페이스 간의 참조가 자유롭지만 다른 패키지 간의 멤버에 서로 접근할 때는 제약이 가해진다. 따라서 제약 조건인 아래의 세 가지 방법을 통해 외부 패키지로 접근할 수 있다.

  • 경로까지 포함하는 완전한 이름으로 참조하는 방법

  • 원하는 패키지 멤버만을 import 하는 방법

  • 패키지 전체를 import 하는 방법

 

완전한 이름으로 참조 : 외부 패키지에 있는 클래스를 사용하려면 클래스의 완전한 이름을 써주어야 한다. 예를 들어 아까 만들었던 graphics 패키지의 Rectangle 클래스를 외부 패키지에서 사용한다면 아래와 같이 기재해야 한다.


graphics.Rectangle rect = new graphics.Rectangle();


자주 사용하지 않는 클래스의 경우 이 방식을 주로 사용한다.



패키지 멤버를 import : 외부 패키지의 특정한 멤버를 import 하려면 다음과 같은 문장을 사용한다.


import graphics.Rectangle;

...

Rectangle rect = new Rectangle();


 

전체 패키지 import : 하나의 패키지 안의 모든 멤버를 사용하고자 할 때 사용한다.


import graphics.*;

...

Circle circle = new Circle();

Rectangle rect = new Rectangle();


 

똑같은 이름의 클래스를 가지는 패키지가 동시에 import 된 경우, 모호성을 제거하기 위해 정식 이름을 사용하여야 한다.


import A;

import B;

...

A.ClassOrder = new A.ClassOrder(); //패키지 AClassOrder 클래스 사용

B.ClassOrder = new B.ClassOrder(); //패키지 BClassOrder 클래스 사용

 

 

 

계층 구조의 패키지

 

하나의 패키지 안에 또 다른 패키지가 있을 수 있다. 그러나 상위 패키지라는 개념이 자바에는 적용되지 않는다.

 

예를 들어 java.awt 패키지 안에는 font 패키지가 있으나 java.awt 패키지만 import해서는 font 패키지를 사용할 수 없다. , font 패키지와 awt 패키지 모두 사용하고 싶다면 아래와 같은 방법이어야 한다.

  • java.awt.*;

  • java.awt.font.*;

쉽게 말해, java.awt 패키지와 java.awt.font 패키지는 서로 다른 패키지이다.

 

 

 

정적 import 문장

 

클래스 안에 정의된 정적 상수정적 메소드를 사용하는 경우에는 일반적으로 클래스 이름을 앞에 적어서 사용한다.

 

예를 들어, java.lang.Math 클래스 안에는 PI가 상수로 정의되어 있고, sin(), cos(), tan()와 같은 수많은 정적 메소드들이 정의되어 있다. 이들을 사용하려면 아래와 같이 클래스 이름을 앞에 붙여야 한다.

 

double r = Math.cos(Math.PI*theta);



정적 import 문장을 사용하면 클래스 이름은 생략하여도 된다.

 

import static java.lang.Math.*;

...

double r = cos(PI*theta);

 

너무 남용하면 읽기 어려운 코드가 되니 주의해야 한다.



 

소스 파일과 클래스 파일 관리

 

자바는 계층 디렉토리 구조를 이용하여 소스 파일과 클래스 파일을 관리한다.

 

, 패키지의 계층 구조를 반영한 디렉토리 구조에 소스들을 저장한다.

 

실제 자바 파일은 (기반 디렉토리)\(패키지명)\(클래스명).java 로 저장된다.

 

여기서 기반 디렉토리란 작업 디렉토리가 된다. 예를 들어, 이클립스에서 설정했던 워크스페이스(workspace)가 작업 디렉토리이다.

 

패키지의 멤버의 완전한 이름과 파일의 경로 이름과는 일치하여야 한다.

  • 완전한 이름 : graphics.Rectangle

  • 파일의 경로 이름 : graphics\Rectangle.java

 

소스 파일을 컴파일하면 컴파일러는 각 클래스들을 서로 다른 출력 파일로 저장한다. 출력 파일의 이름은 클래스 이름과 같고, 확장자는 “.class”이다.

 

소스 파일들과 마찬가지로 클래스 파일도 패키지 이름을 반영하는 디렉토리 구조에 저장된다. 참고로 클래스 파일들에 대한 경로는 반드시 자바 소스 파일에 대한 경로와 같을 필요는 없다.

  • 소스 파일 경로 → C:\source\com\company\graphics\Rectangle.java

  • 클래스 파일 경로  C:\class\com\company\graphics\Rectangle.class

 

, 소스와 클래스 파일을 서로 다른 위치에 저장하려면 컴파일러와 자바 가상 기계(JVM)가 이 파일들을 찾을 수 있도록 설정하여야 한다.

 

위에 서술한 클래스 파일 경로의 일부분 “C:\class”는 실행에 필요한 클래스 파일들이 저장되는 디렉토리이다. 이를 클래스 경로(class path)”라고 하며, 시스템 변수 CLASSPATH에 저장되어 있어야 한다.

 

자바 컴파일러와 자바 가상 기계(JVM)는 모두 .class 파일에 대한 경로를 만들 때, 클래스 경로에 패키지 이름을 붙여서 만든다. , 클래스 경로가 “C:\class”로 설정하고 패키지 이름이 com.company.graphics 라면 컴파일러와 JVM이 클래스 파일을 찾는 디렉토리는 다음과 같다.

C:\class\com\company\graphics

 

클래스 경로의 디폴트 값으로 컴파일러와 JVM은 현재 디렉토리와 자바 플랫폼 클래스들을 포함하고 있는 JAR 파일을 탐색한다.

 

 

 

JAR 파일

 

클래스 파일은 JAR(Java archive) 파일 형태로 저장될 수 있다.

 

- JAR 파일은 여러 개의 클래스 파일을 디렉토리의 계층 구조를 유지한 채로 압축하여서 가지고 있을 수 있다.

 

실행 파일(.exe)을 만들 때도 JAR 파일이 이용된다.

 

 

 

자바에서 지원하는 패키지


java.applet

 애플릿을 생성하는 데 필요한 클래스

 java.awt

 그래픽과 이미지를 위한 클래스

 java.beans

 자바빈즈 구조에 기초한 컴포넌트를 개발하는 데 필요한 클래스

 java.io

 입력과 출력 스트림을 위한 클래스

 java.lang

 자바 프로그래밍 언어에 필수적인 클래스

 java.math

 수학에 관련된 클래스

 java.net

 네트워킹 클래스

 java.nio

 새로운 네트워킹 클래스

 java.rmi

 원격 메소드 호출(RMI) 관련 클래스

 java.security

 보안 프레임워크를 위한 클래스와 인터페이스

 java.sql

 데이터베이스에 저장된 데이터를 접근하기 위한 클래스

 java.util

 날짜, 난수 생성기 등의 유틸리티 클래스

 javax.imageio

 자바 이미지 입출력 API

 javax.net

 네트워킹 애플리케이션을 위한 클래스

 javax.swing

 스윙 컴포넌트를 위한 클래스

 javax.xml 

 XML을 지원하는 패키


 


 

자주 사용되는 java.lang 패키지

 

- 위의 표에 나와있듯 java.lang 패키지는 자바 프로그래밍 언어에 필수적인 클래스를 포함하고 있다.


 클래스 이름

설명 

 Object

 기초적인 메소드를 제공하는 모든 클래스의 최상위 클래스

 Math

 각종 수학 함수들을 포함하는 클래스

 지수나 로그제곱근삼각함수 등과 같은 기본적인 수치 연산을 위한 메소드들을 제공한다.

 Wrapper

 Integer와 같이 기초 자료형을 감싸서 제공하는 랩퍼 클래스

 String, StringBuffer

 문자열을 다루는 클래스

 System

 시스템 정보를 제공하거나 입출력을 제공하는 클래스

 Thread

 스레드 기능을 제공하는 클래스

 Class

 객체를 생성한 클래스에 대한 정보를 얻기 위한 클래스



- Class 클래스 : 실행 중인 자바 애플리케이션 안에 포함된 클래스들과 인터페이스를 나타낸다. Class는 생성자가 없다. 대신에 해당 객체는 자바 가상 기계에 의하여 자동적으로 생성된다. 객체 자체를 출력, 객체의 이름만 출력 등을 수행하는 메소드가 포함된다.


 

- System 클래스 : 실행 시스템과 관련된 속성과 메소드를 제공한다. 예시로 우리가 자주 사용하는 콘솔 출력 메소드인 System.out.println() 이 바로 System 클래스가 제공하는 메소드 중의 하나이다. System 클래스 안에 들어 있는 outPrintStream 타입의 객체로 정적 변수로 선언되어 있으며 모니터를 나타낸다. 반면, inInputStream 타입의 객체로 정적 변수이며 키보드를 나타낸다.


 필드

 static PrintStream err

 표준 오류 출력 스트림

 static InputStream in

 표준 입력 스트림

 static PrintStream out 

 표준 출력 스트림

 주요 메소드

 static void arraycopy(Object src, int srcPos, Object dest, int destPos, int length)

 지정된 소스 배열을 목적지 배열로 복사한다.

 static long currentTimeMillis()

 밀리초 단위로 현재 시각을 반환한다.

 static void exit(int status)

 현재 실행 중인 자바 가상 기계를 중단한다.

 static String getenv(String name)

 지정된 환경 변수의 값을 얻는다.

 static String getProperty(String key)

 키에 의해서 지정된 시스템의 특성을 얻는다.

 static long nanoTime()

 나노초 단위로 현재 시각을 반환한다.

 


Wrapper 클래스 : 기초 자료형을 객체로 포장할 때 사용하는 클래스이다.

 

기초자료형

 랩퍼 클래스

 byte

 Byte

 short

 Short

 int

 Integer

 long

 Long

 float

 Float

 double

 Double

 char

 Character

 boolean

 Boolean

 void

 Void


  • 랩퍼 클래스에서 가장 많이 사용되는 메소드는 기초 자료형을 문자열로 변환하거나, 문자열을 기초 자료형으로 변환하는 메소드이다.

Integer 클래스 제공하는 일부 메소드

 static int intValue()

 int형으로 변환한다.

 static double doubleValue()

 double형으로 변환한다.

 static float floatValue()

 float형으로 변환한다.

 static int parseInt(String s)

 문자열을 int형으로 변환한다.

 static String toBinaryString(int i)

 int형의 정수를 2진수 형태의 문자열로 변환한다.

 static String toHexString(int i)

 int형의 정수를 16진수 형태의 문자열로 변환한다.

 static String toOctalString(int i)

 int형의 정수를 8진수 형태의 문자열로 변환한다.

 static String toString(int i)

 int형의 정수를 10진수 형태의 문자열로 변환한다.

 static Integer valueOf(String s)

 문자열 s를 Integer 객체로 변환한다.

 static Integer valueOf(String s, int radix)

 문자열 s radix 법의 Integer객체로 변환한다. 

  • 이 메소드들은 많이 사용되고, 정적 메소드이므로 객체를 생성하지 않고 클래스의 이름에 도트 연산자를 붙여서 사용할 수 있다.

  • 자바는 Wrapper 객체와 기초 자료형 사이의 변환을 자동으로 하여 주는 오토 박싱(auto-boxing)"기능을 제공한다.

Integer box;

box = 10; //정수를 자동으로 Integer 객체로 포장한다.(boxing)

System.out.println(box+1); //box는 자동으로 int형으로 변환된다.(unboxing)



- StringBuffer 클래스 : 변경 가능한 문자열을 저장하는 클래스이다.


 String 클래스는 주로 상수 문자열, 즉 변경이 불가능한 문자열을 나타낼 때 사용된다. String 클래스의 경우 빈번하게 문자열을 변경하는 경우에는 비효율적일 수 있다. 예를 들어 문자열 연산자인 + 연산자를 사용할 경우, 연산을 할 때마다 새로운 객체가 생성되고 기존의 String 객체도 힙 영역에 남아있게 되므로 기존 객체를 사용하지 않으면 메모리 공간을 낭비하게 된다


 이 때 변경 가능한 문자열을 위한 위 상황의 해결책으로 StringBufferStringBuilder 클래스가 제공된다. 두 클래스는 유사한 메소드를 가지고 있으나 속도 면에서 차이가 있다. 이는 나중에 알아보도록 하고, 가장 큰 차이는 StringBuffer는 다중 스레드 환경에서 안전하다는 것이고, 다중 스레드 환경이 아닐 때는 StringBuilder가 더 효율적이다. 따라서 스레드를 사용하지 않는다면 StringBuilder 객체를 사용하는 것이 좋다.


 StringBufferStringBuilder 객체 모두 내부적으로 문자열을 저장하는 메모리를 가지고 있다. 이 메모리를 버퍼(buffer)라고 하며, 가변적이기 때문에 문자열 연산에서 빼진 문자열은 저장하지 않고 지운다. , 버퍼의 크기는 자동적으로 조정된다.


  • StringBuffer 클래스 설명 (메소드의 경우 StringBuilder 클래스의 메소드명과 일치하므로 필요할 때 StringBuilder 클래스의 해당 메소드이름을 호출하여도 된다.)

 생성자

 StringBuffer()

 버퍼가 비어 있는 StringBuffer 객체를 생성한다.

 StringBuffer(String s)

 매개변수로 넘겨 받은 문자열을 초기값으로 버퍼에 저장한  StringBuffer 객체를 생성한다.

 메소드

 StringBuffer append(String s)

 인수는 먼저 문자열로 변환되어서 문자열 뒤에 추가된다.

 StringBuffer append(char[] str)

 StringBuffer append(char[] str, int offset, int len)

 StringBuffer delete(int start, int end)

 지정된 문자를 삭제한다.

 StringBuffer deleteCharAt(int index)

 StringBuffer insert(int offset, char[] str)

 offset은 시작 위치를 의미하며, offset부터 두 번째 매개변수로 주어진 배열을 문자열로 변환한 뒤 삽입한다.

 StringBuffer insert(int index, char[] str, int offset, int len)

 StringBuffer replace(int start, int end, String s)

 start부터 end까지의 위치를 문자열 s로 변경한다이 때 start위치는 포함되나 end 위치는 포함되지 않는다.

 void setCharAt(int index, char c)  해당 인덱스의 문자를 매개변수로 받은 문자(c)로 설정
 StringBuffer reverse() 지정된 문자들의 순서를 역순으로 한다.





java.util 패키지

 

- Random 클래스 : 난수를 얻는데 사용된다

  • 48 비트 길이의 시드(seed)를 사용하여 알고리즘으로는 변형된 선형 합동 수식을 이용한다

  • 동일한 시드를 이용하여서 두 개의 Random 객체가 생성된다면 동일한 난수를 발생하게 된다.

 생성자

 Random()

 새로운 난수 발생기 객체를 생성한다.

 Random(longseed seed)

 주어진 시드를 사용하는 새로운 난수 발생기 객체를 생성한다.

 메소드

 protected int next(int bits)

 다음 난수를 반환한다.

 boolean nextBoolean()

 boolean 형의 다음 난수를 반환한다.

 void nextBytes(byte[] bytes)

 byte형의 난수를 발생하여서 주어진 배열을 채운다.

 double nextDouble()

 double 형의 0.0과 1.0 사이의 난수를 반환한다.

 float nextFloat()

 float 형의 0.0과 1.0 사이의 난수를 반환한다.

 int nextInt()

 int형의 난수를 반환한다.

 int nextIng(int n)

 0과 n사이의 int형의 난수를 발생한다.

 long nextLong()

 long 형의 난수를 발생한다.

 void setSeed(long seed) 

 시드를 설정한다.


  • Random 예시

import java.util.Random;
 
public class RandomTest {
	public static void main(String[] args) {
		Random random = new Random();
		for(int i = 0; i < 10; i++) {
			System.out.println(random.nextInt(100)); //0~100사이의 난수를 발생한다.
		}
	}
}


- Arrays 클래스 : 배열을 다루는 다양한 메소드들을 가지고 있다. 대표적으로 정렬 및 탐색과 같은 메소드가 있고, 배열을 리스트로 보게 하는 정적 팩토리 메소드도 제공한다. 이 클래스는 다음에 배울 자바 컬렉션 프레임 워크에 속한다.


메소드

 static List asList(Object[] a)

 주어진 배열을 고정 길이의 리스트로 변환한다.

 static int binarySearch(int[] a, int key)

 주어진 값을 int형의 배열에서 이진 탐색한다.

 static int binarySearch(Object[] a, Object key)

 Object 타입의 배열에서 key를 이진 탐색하여 해당 key의 인덱스를 반환한다.

 static int[] copyOf(int[] original, int len)

 주어진 배열을 새로운 크기의 배열에 길이 len만큼 복사한다. (0~len)

 static int[] copyOfRange(int[] original, int from, int to)

 배열에서 주어진 구간의 값들을 새로운 배열로 복사한다.

 static boolean equals(int[] a, int[] a2)

 주어진 두 개의 배열이 같으면 true를 반환한다.

 static void fill(int[] a, int val)  주어진 val 값을 가지고 배열을 채운다.
 static void sort(int[] a) 지정된 int형의 배열을 정렬한다.


  • 위의 메소드는 double 형의 배열, float 형의 배열 등의 다양한 타입의 배열에도 타입만 다르게 하여 사용가능하다.

  • 정적메소드이므로 "클래스이름.메소드이름" 으로 호출해야 한다.

  • 추가로 java.lang.reflect 패키지 안에 Array 라는 클래스가 존재한다. 따라서 import 할 때 스펠링을 잘보고 Arrays 클래스와 Array 클래스를 구별하는데 주의해야 한다.

 

- Date 클래스 : 밀리초 단위로 현재 시각을 나타낸다. Date 객체를 연도, , 일로 변환하는 메소드가 있다. 현재는 국제화에 맞지 않아 권장되지 않는다.


Date date = new Date(); //현재 날짜를 Date 객체로 생성한다.



- Calendar 클래스 : 추상 크래스로서 날짜와 시간에 대한 정보를 가지고 있고 특정 시각을 연도, , 일 등으로 변한하는 메소드도 가지고 있다. 시각은 197011일부터 흘러온 시간으로 나타낸다.

 

Calendar calendar = Calendar.getInstance(); //현재 시각을 나타내는 Calendar 객체 얻는 방법

  • int 형의 정적 상수들이 선언되어 있고 이 상수들을 이용하여서 특정 시각에서 날짜와 시간에 대한 정보를 추출할 수 있다. 상수에 대한 정보는 아래의 사이트를 참조하면 자세히 나와있다.

https://docs.oracle.com/javase/7/docs/api/java/util/Calendar.html

 


- StringTokenizer 클래스 : 문자열을 분석하여 토큰으로 분리시켜 주는 기능을 제공한다

  • 토큰이란 문법적으로 더 이상 나눌 수 없는 기본적인 언어요소를 의미하며, 키워드, 연산자 또는 구두점이 될 수 있다. 예를 들어 오늘은 2017612일입니다.”라는 문자열에서 “2017”, “6”, “12”만 따로 가져오고 싶을 때 이 클래스를 사용하면 된다.

 

생성자

 StringTokenizer(String str)

 주어진 문자열을 위한 StringTokenizer 객체를 생성한다구분자의 디폴트 값은 공백(“ ”)이다.

 StringTokenizer(String str, String delim)

 주어진 문자열을 구분자 delim을 기준으로 분할하는 StringTokenizer 객체를 생성한다.

 StringTokenizer(String str, String delim, boolean returnDelims)

 returnDelims는 분리자를 포함하여 분할할 거면 true를 포함하지 않고 분할할 거면 false로 설정하면 된다.

 메소드

 int countTokens()

 문자열에 존재하는 토큰의 개수를 반환한다.

 boolean hasMoreTokens()

 다음 토큰을 가지는지를 반환한다.

 String nextToken()

 다음 토큰을 반환한다.

 String nextToken(String delim)

 다음 토큰을 반환하고 분리자를 delim으로 변경한다.


  • StringTokenizer 예시

import java.util.*;
 
public class StringTest {
    public static void main(String[] args) {
        StringTokenizer st = new StringTokenizer(“I just wanna be a programmer”);

        while(st.hasMoreTokens()) { //공백으로 분할했을 때 토큰이 남아있다면,
            System.out.println(st.nextToken()); //다음 토큰을 가져온다.
        }
    }
} 


결과

I

just

wanna

be

a

programmer







 

 

JTable


데이터를 테이블 형식으로 표현하고, 사용자가 값을 편집할 수 있게 하는 컴포넌트이다.

 

, 데이터를 저장하지 않고, 단순히 보여주는 용도로 사용된다.

 

생성자

 JTable() 

 기본 테이블 모델을 가진 빈 테이블을 생성한다

 JTable(int numRows, int numColumns)

 지정된 행과 열의 수를 가진 테이블을 생성한다.

 JTable(Object[][] rowData, Object[] columnName)

 첫 번째 매개변수는 테이블에 들어갈 데이터이므로 오브젝트 타입의 2차원 배열을 받는다두 번째 매개변수는 테이블의 첫 번째 행에 들어가 데이터를 구분 짓는 열의 이름이 들어간다.

 JTable(TableModel dm)

 새 테이블 모델을 가진 테이블을 생성한다.

 JTable(TableModel dm, TableColumnModel cm)

 새 테이블 모델과 테이블 컬럼 모델을 가진 테이블을 생성한다.

 JTable(TableModel dm, TableColumnModel cm, ListSelectionModel sm)

 테이블의 모든 모델들을 새로 지정받은 테이블을 생성한다.


- 메소드는 너무 많은 자료를 가지고 있어서 아래에 링크를 남깁니다.


https://docs.oracle.com/javase/7/docs/api/javax/swing/JTable.html

 

- 테이블 생성하기 예시


private String[] columnType = { "번호", "이름", "나이", "성별"};

private Object[][] data = {

       {"1", "김철수", "20", "남성"},

       {"2", "김옥자", "43", "여성"},

       {"3", "이순신", "100", "남성"},

       {"4", "유관순", "18", "여성"},

       {"5", "이 도", "54", "남성"}

};

JTable table = new JTable(data, columnType);


이전에도 설명했듯이 ScrollPane은 동적으로 변할 수 있는 컴포넌트와 함께 쓰인다. 주로 JTree, JTable처럼 데이터를 보여주되 사용자의 동작에 따라 컴포넌트의 크기가 변할 수 있는 컴포넌트가 있다. 쉽게 말해 트리나 테이블에는 스크롤바가 붙어야 실용성이 있다는 의미이다.

 

JScrollPane scrollPane = new JScrollPane(table); //스크롤 페인이 테이블의 컨테이너가 된다.

table.setFillsViewportHeight(true); //컨테이너의 전체 높이를 테이블이 전부 사용하도록 설정한다.

 

테이블은 각 셀의 데이터가 변경되면 다른 컴포넌트와 달리 TableModelListener 인터페이스를 구현하여 이벤트를 처리한다. 또한 JTable 객체는 하나의 테이블 모델(model)을 가진다. 따라서 리스너를 추가하는 메소드는 테이블을 관리하는 모델로부터 호출되어야 한다. 아래의 예시에서는 한 번에 보여주기 위해 익명 객체를 사용하였다. 리스너를 구현하는 방법은 본인이 선택하면 될 것이다.

 

table.getModel().addTableModelListener(new TableModelListener {

@Override

public void tableChanged(TableModelEvent e) {

int row = e.getFirstRow(); //첫 번째 행을 얻는다.

int column = e.getColumn(); //변경된 셀의 컬럼 인덱스를 얻는다.

TableModel model = (TableModel) e.getSource();

String columnName = model.getColumnName(column); //해당 인덱스를 가진 열의 이름을 얻는다.

Object data = model.getValueAt(row, column);

//데이터를 처리하는 구간

...

}

});

 

테이블은 정렬필터링을 할 수 있는데, 이들 모두 정렬기 객체를 이용하여 수행할 수 있다. 가장 쉬운 방법은 테이블의 autoCreateRowSorter 특성을 true로 설정하는 것이다. 이 속성을 true로 설정하면, 컬럼 헤더를 클릭했을 때 행을 자동으로 정렬해주는 정렬기를 정의한다.

 

table.setAutoCreateRowSorter(true);

 

테이블은 사용자가 셀의 값을 변경할 수 있는데, 개발자에 의해 정해진 셀의 값을 가질 수도 있다. 또는 정해진 선택지 속에서 사용자가 셀의 값을 변경할 수 있다. 자주 사용되는 것은 콤보 박스이다.

 

[좌측 아래 콤보 박스에 프로그래밍 언어가 나열되있는 것을 볼 수 있다.]


콤보박스를 이용하여 셀 에디터 만들기


TableColumn gender = table.getColumnModel().getColumn(2); //인덱스가 2인 컬럼얻기

...

JComboBox genderCells = new JComboBox();

comboBox.addItem(“남성”);

comboBox.addItem(“여성”);

gender.setCellEditor(new DefaultCellEditor(genderCells);

 

자바에서는 테이블을 출력할 수 있는 기능을 API로 제공한다.

 

...

table.print(); //해당 메소드를 호출할 때는 JMenuBar를 구현하여 메뉴 선택 시 호출하도록 하는 것이 일반적이다.

...

try {

if (!table.print()) {

//사용자가 인쇄를 취소하였을 때의 동작 정의

}

} catch(java.awt.print.PrinterException e) {

System.err.format(“인쇄 도중 오류 발생 : %s%n”, e.getMessage());

}

 

테이블 예제

import javax.swing.*;
import javax.swing.event.TableModelEvent;
import javax.swing.event.TableModelListener;
import javax.swing.table.TableColumn;
import javax.swing.table.TableModel;
import java.awt.*;

/**
 * Created by with2J on 2017-07-21.
 */
public class JTableTest extends JFrame implements TableModelListener {

    public static void main(String[] args)
    {
        new JTableTest();
    }

    //FIELDS
    private JTable table;
    private JScrollPane scrollPane;
    private String[] columnType = { "번호", "이름", "나이", "성별"};
    private Object[][] data = {
            {"1", "김철수", "20", "남성"},
            {"2", "김옥자", "43", "여성"},
            {"3", "이순신", "100", "남성"},
            {"4", "유관순", "18", "여성"},
            {"5", "이 도", "54", "남성"}
    };

    //CONSTRUCTOR
    JTableTest()
    {
        super("JTable Test!"); //setTitle() 도 가능
        setSize(500, 300);
        setResizable(false);
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        setLocationRelativeTo(null);

        table = new JTable(data, columnType);
        scrollPane = new JScrollPane(table);

        table.setPreferredScrollableViewportSize(new Dimension(500, 300));
        table.setFillsViewportHeight(true);

        table.getModel().addTableModelListener(this); // 테이블에 소속된 하나의 모델이 셀들을 관리하므로 항상 getModel() 을 호출해야함
        table.setAutoCreateRowSorter(true); //자동 행 정렬기능

        //성별 컬럼에 지정된 선택지만 추가할 수 있도록 설정한다.
        TableColumn genderColumn = table.getColumn("성별");
        JComboBox gender = new JComboBox();
        gender.addItem("여성");
        gender.addItem("남성");
        genderColumn.setCellEditor(new DefaultCellEditor(gender));

        add(scrollPane);
        setVisible(true);
    }

    @Override
    public void tableChanged(TableModelEvent e) {
        int row = e.getFirstRow();
        int column = e.getColumn();

        if (column == 2) { // 컬럼번호가 2이면 "나이" 컬럼이다. 컬럼인덱스는 0부터 시작한다.
            TableModel model = (TableModel) e.getSource();
            String colName = model.getColumnName(column); //해당 인덱스의 컬럼이름을 받아온다.
            String str = (String) model.getValueAt(row, column); // data는 object 타입이므로 형변환해야 한다.
            if (Integer.parseInt(str) > 100) { //입력한 나이값이 100이 넘을 경우, 경고창을 띄운다.
                JOptionPane.showMessageDialog(this, "나이 범위를 초과하였습니다. 100 미만으로 입력해주세요.", "경고",
                        JOptionPane.WARNING_MESSAGE);
            }
        }
    }
}



[나이의 범위를 넘어서면 팝업창이 뜨는 것을 볼 수 있다.]



[이름을 클릭하면, 이름순으로 행정렬이 이루어지는 것을 볼 수 있다.]




+ Recent posts