Chapter 11 기본 클래스

26099 단어 JavaJava

1. Object 클래스

1-1 java.lang 패키지

  • 자바로 프로그램을 구현하면서 사용한 String, Integer와 같은 클래스들은 java.lang 패키지에 속해 있습니다.
  • String 클래스의 전체 이름은 java.lang.String이고, Integer 클래스의 전체 이름은 java.lang.Integer입니다.
  • java.lang 패키지에는 기본적으로 많이 사용하는 클래스들이 포함되어 있습니다.
  • 자바 프로그래밍에서 외부 패키지에 선언한 클래스를 사용할 때는 import문으로 클래스가 어느 패키지에 속해 있는지 선언해야 합니다.
  • 지금까지 String 클래스를 쓰면서 import java.lang.String; 문장을 쓴 적이 없습니다.
  • java.lang 패키지는 컴파일할 때 import java.lang.*; 문장이 자동으로 추가되어 java.lang 패키지 하위 클래스를 모두 사용할 수 있습니다.
  • 프로그래머가 import문을 직접 쓰지 않아도 java.lang 패키지의 모든 하위 클래스를 참조할 수 있습니다.

1-2 모든 클래스의 최상위 클래스 Object

  • Object 클래스는 모든 자바 클래스의 최상위 클래스입니다.
  • 모든 클래스는 Object 클래스로부터 상속을 받습니다.
  • 생각해보면 클래스를 만들 때 Object 클래스를 상속받는 코드를 작성한 적이 없습니다.
  • 컴파일 과정에서 extends Object가 자동으로 쓰입니다.
  • 직접 만드는 클래스뿐 아니라 기존 JDK에서 제공하는 클래스도 모두 Object 클래스에서 상속받습니다.
  • 모든 클래스가 Object 클래스를 상속받았으므로 Object의 메서드를 사용할 수 있고, 재정의할 수 있고, Object형으로 변환할 수도 있습니다.
  • 아래 표는 주로 사용되는 Object 메서드입니다.
  • Object의 메서드 중에는 재정의할 수 있는 메서드도 있고, 그렇지 않은 메서드도 있습니다.
  • final 예약어로 선언한 메서드는 스레드에 사용하거나 자바 가상머신과 관련된 메서드이기 때문에 재정의 할 수 없습니다.

1-3 toString( ) 메서드

  • Object 클래스에서 기본으로 제공하는 toString( ) 메서드는 이름처럼 객체 정보를 문자열(String)로 바꾸어 줍니다.
  • Object 클래스를 상속받은 모든 클래스는 toString( )을 재정의할 수 있습니다.
  • String이나 Integer 등 여러 JDK 클래스에는 toString( ) 메서드가 이미 재정의 되어 있습니다.

Object 클래스의 toString( ) 메서드

  • toString( ) 메서드는 인스턴스 정보를 문자열로 반환하는 메서드입니다.
  • toString( ) 메서드의 원형은 생성된 인스턴스의 클래스 이름과 주소 값을 보여줍니다.

예제 11-1 Object 클래스의 toString( ) 메서드 사용하기

class Book{
	int bookNumber;
	String bookTitle;
	
	// 책 번호와 제목을 매개변수로 입력받는 생성자
	Book(int bookNumber, String bookTitle){		
		this.bookNumber = bookNumber;
		this.bookTitle = bookTitle;
	}
}
							
public class ToStringEx {
	public static void main(String[] args) {
		Book book1 = new Book(200, "개미");
		
		// 인스턴스 정보(클래스 이름.주소 값)
		System.out.println(book1);
		// toString() 메서드로 인스턴스 정보(클래스 이름.주소 값)를 보여 줌
		System.out.println(book1.toString());			
	}
}

  • 출력문에 참조 변수를 넣으면 인스턴스 정보가 출력되는데, 이때 자동으로 호출되는 메서드가 toString( )입니다.
  • 여기서 호출되는 toString( )은 Book 클래스의 메서드가 아닌 Object의 클래스의 메서드입니다.

Object 클래스의 toString( ) 메서드 원형

  • 위 정의 내용을 보면 '클래스이름@해시 코드 값'임을 알 수 있습니다.
  • 즉, 클래스의 이름과 16진수 해시 코드 값이 출력됩니다.

String과 Integer 클래스의 toString( ) 메서드

  • toString( ) 메서드가 호출된 경우라도 출력 결과가 '클래스이름@해시 코드 값'이 아닌 경우가 있습니다.
  • toString( )가 재정의된 클래스는 '클래스이름@해시 코드 값을 출력하는 toString( ) 메서드의 원형이 아닌 재정의된 메서드가 호출됩니다.
  • String 클래스 = "test"
  • Integer 클래스 = 100

Book 클래스에서 toString( ) 메서드 직접 재정의하기

예제 11-2 toString( ) 메서드 재정의하기

class Book{

	...
	
	// toString() 메서드 재정의
	@Override
	public String toString() {					
		return bookTitle + ", " + bookNumber;
	}
}
							
public class ToStringEx {
	public static void main(String[] args) {
		Book book1 = new Book(200, "개미");
		
		// 인스턴스 정보(클래스 이름.주소 값)
		System.out.println(book1);
		// toString() 메서드로 인스턴스 정보(클래스 이름.주소 값)를 보여 줌
		System.out.println(book1.toString());			
	}
}

  • Book 클래스의 참조 변수를 사용해 '책 이름, 책 번호'를 출력합니다.
  • toString( ) 메서드를 재정의하여 '책 이름, 책 번호'를 반환하는 코드를 작성했습니다.

1-4 equals( ) 메서드

  • equals( ) 메서드의 원래 기능은 두 인스턴스의 주소 값을 비교하여 boolean 값(true/false)을 반환해주는 것입니다.
  • 주소 값이 같다면 당연히 같은 인스턴스입니다.
  • 서로 다른 주소 값을 가질 때도 같은 인스턴스라고 정의할 수 있는 경우가 있습니다.
  • 물리적 동일성(인스턴스의 메모리 주소가 같음)뿐 아니라 논리적 동일성(논리적으로 두 인스턴스가 같음)을 구현할 때도 equals( ) 메서드를 재정의하여 사용합니다.

Object 클래스의 equals( ) 메서드

  • 인스턴스를 가리키는 참조 변수가 두 개 있을 때 이 두 인스턴스가 물리적으로 같다는 것은, 두 인스턴스의 주소 값이 같은 경우를 말합니다.
  • 위 인스턴스는 두 변수가 같은 메모리 주소를 가리키고 있다는 뜻입니다.
  • 위 인스턴스는 서로 다른 주소를 가리키지만, 저장된 정보가 같은 경우 논리적으로는 같은 객체로 처리하는것이 맞습니다.

예제 11-3 Object 클래스의 equals( ) 메서드 사용하기

package study.object;

class Student{
	int studentId;
	String studentName;
	
	public Student(int studentId, String studentName) {
		this.studentId = studentId;
		this.studentName = studentName;
	}
	public String toString() {
		return studentId + ", " + studentName;
	}
}

public class EqualsTest {
	public static void main(String[] args) {
		Student studentKim = new Student(100, "이상원");
		Student studentKim2 = studentKim;	// 주소 복사
		Student studentDock = new Student(100, "이상원");
		
		System.out.println();
		// 동일한 주소의 두 인스턴스 비교
		// == 기호로 비교
		if(studentKim == studentKim2){	
			System.out.println("studentKim와 studentKim2의 주소는 
            					같습니다.");
		} else {
			System.out.println("studentKim와 studentKim2의 주소는 
            					다릅니다.");
		}
		
		// equals() 메서드로 비교
		if(studentKim.equals(studentKim2)) {	
			System.out.println("studentKim와 studentKim2의 동일합니다.");
		} else {
			System.out.println("studentKim와 
            					studentKim2의 동일하지 않습니다.");
		}
		
		// 동일인이지만 인스턴스의 주소가 다른 경우
		// == 기호로 비교
		if(studentKim == studentDock){			
			System.out.println("studentKim와 studentDock의 주소는 
            					같습니다.");
		} else {
			System.out.println("studentKim와 studentDock의 주소는 
            					다릅니다.");
		}

		// equals() 메서드로 비교
		if(studentKim.equals(studentDock)) {	
			System.out.println("studentKim와 studentDock의 동일합니다.");
		} else {
			System.out.println("studentKim와 
            					studentDock의 동일하지 않습니다.");
		}
	}
}

  • Object의 equals( ) 메서드의 원래 기능은 두 인스턴스의 주소를 비교하는 것입니다.
  • 같은 주소인 경우만 equals( ) 메서드의 결과가 true가 됩니다.
  • StudentLee 참조 변수와 StudentLee2 참조 변수는 동일한 주소를 가리키므로 true이고, studentSang의 경우는 다른 주소를 가리키므로 false입니다.
  • 두 인스턴스가 있을 때 ==는 단순히 물리적으로 같은 메모리 주소인지 여부를 확인할 수 있고, Object의 equals( ) 메서드는 재정의를 하여 논리적으로 같은 인스턴스인지(메모리 주소가 다르더라도 같은 학생인지) 확인하도록 구현할 수 있습니다.

String과 Integer 클래스의 equals( ) 메서드

  • JDK에서 제공하는 String 클래스와 Integer 클래스에는 equals( ) 메서드가 이미 재정의되어 있습니다.

예제 11-4 String과 Integer 클래스의 equals( ) 메서드

public class StringEquals {
	public static void main(String[] args) {
		String str1 = new String("abc");
		String str2 = new String("abc");
		
		// 두 인스턴스 주소 값이 같은지 비교하여 출력
		System.out.println(str1 == str2);
		// String 클래스의 equals() 메서드 사용
		// 두 인스턴스의 문자열 값이 같은지 비교하여 출력
		System.out.println(str1.equals(str2));
	}
}

  • str1과 str2는 서로 다른 인스턴스를 가리키기 때문에 str1 == str2의 결과는 false입니다.
  • String 클래스의 equals( ) 메서드는 같은 문자열의 경우 true를, 그렇지 않은 경우 false를 반환하도록 재정의되어 있습니다.
  • str1과 str2는 "abc"로 같은 문자열 값을 가지므로 str.equals(str2)의 반환 값은 true입니다.
  • Integer 클래스의 경우 정수 값이 같은 경우 true를 반환하도록 equals( ) 메서드가 재정의되어 있습니다.

Student 클래스에서 equals( ) 메서드 직접 재정의하기

예제 11-5 equals( ) 메서드 재정의하기

class Student{

	...
	
	// equals() 메서드 재정의
	@Override
	public boolean equals(Object obj) {				
		if(obj instanceof Student) {
			Student std = (Student)obj;
			// 재정의한 equalse() 메서드는 학생의 학번이 같으면 true 반환
			if(this.studentId == std.studentId) {	
				return true;
			} else {
				return false;
			}
		}
		return false;
	}
}

public class EqualsTest {
	public static void main(String[] args) {
    
		...
        
	}
}

  • equals( ) 메서드를 재정의했습니다.
  • equals( ) 메서드의 매개변수는 Object형으로 비교될 객체가 Object형 매개변수로 전달되면 instanceof를 사용하여 매개 변수의 원래 자료형이 Student인지 확인합니다.
  • this의 학번과 매개변수로 전달된 객체의 학번이 같으면 true를 반환합니다.
  • 결과를 보면 studentLee와 studentSang은 서로 다른 메모리 주소에 존재하는 인스턴스이므로 ==연산의 결과 값은 false를 반환하지만, 학번이 같으므로 equals( )는 true를 반환합니다.

1-5 hashCode( ) 메서드

  • 해시(hash)는 정보를 저장하거나 검색할 때 사용하는 자료 구조입니다.
  • 정보를 어디에 저장할 것인지, 어디서 가져올 것인지 해시 함수를 사용하여 구현합니다.
  • 해시 함수는 객체의 특정 정보(키 값)를 매개변수 값으로 넣으면 그 객체가 저장되어야 할 위치나 저장된 해시 테이블 주소(위치)를 반환합니다.
  • 객체 정보를 알면 해당 객체의 위치를 빠르게 검색할 수 있습니다.
  • 자바에서는 인스턴스를 힙 메모리에 생성하여 관리할 때 해시 알고리즘을 사용합니다.
  • Object 클래스의 toString( ) 메서드 원형을 다시 살펴보면
    getClass( ).getName( ) + '@' + Integer.toHexString(hashCode( ))
    입니다.
  • 참조 변수를 출력할 때 본 16진수 숫자 값이 '해시 코드 값'이고, 이 값은 자바 가상 머신이 힙 메모리에 저장한 '인스턴스의 주소 값'입니다.
  • 자바에서는 두 인스턴스가 같다면 hashCode( ) 메서드에서 반환하는 해시 코드 값이 같아야 합니다.
  • 논리적으로 같은 두 객체도 같은 해시 코드 값을 반환하도록 hashCode( ) 메서드를 재정의해야 합니다.
  • 다시 말해 equals( ) 메서드를 재정의했다면 hashCode( ) 메서드도 재정의해야 합니다.

String과 Integer 클래스의 hashCode( ) 메서드

  • String과 Integer 클래스의 equals( ) 메서드는 재정의되어 있으므로, hashCode( ) 메서드도 함께 재정의되어 있을 것입니다.

예제 11-6 String 클래스의 hashCode( ) 메서드

public class HashCodeTest {
	public static void main(String[] args) {
		String str1 = new String("abc");
		String str2 = new String("abc");
		
		// abc 문자열의 해시 코드 값 출력
		System.out.println(str1.hashCode());
		// abc 문자열의 해시 코드 값 출력
		System.out.println(str2.hashCode());
	}
}
  • String 클래스는 equals( ) 메서드의 결과 값이 true인 경우 hashCode( ) 메서드는 동일한 해시 코드 값을 반환합니다.

Student 클래스에서 hashCode( ) 메서드 재정의하기

  • 앞에서 서로 다른 인스턴스로 생성된 두 학생이 논리적으로 같은 학생이라는 의미를 구현하기 위해 equals( ) 메서드를 재정의하였습니다.
  • 논리적으로 동일한 두 학생이 같은 해시 코드 값을 반환하도록 hashCode( ) 메서드도 재정의를 해야합니다.
  • 일반적으로 hashCode( ) 메서드를 재정의할 때는 equals( ) 메서드에서 논리적으로 같다는 것을 구현할 때 사용한 멤버 변수를 활용하는 것이 좋습니다.
  • Student 클래스에서는 hashCode( ) 메서드가 학번을 반환하는 것이 가장 합리적입니다.

예제 11-7 hashCode( ) 메서드 재정의하기

class Student{

	...

	// 해시 코드 값으로 학번을 반환하도록 메서드 재정의
	@Override
	public int hashCode() {		
		return studentId;
	}
	
}

public class EqualsTest {
	public static void main(String[] args) {
    
		...
		
		System.out.println("studentKim의 hashCode : " 
        				+ studentKim.hashCode());
		System.out.println("studentDock의 hashCode : " 
        				+ studentDock.hashCode());
		
		System.out.println("studentKim의 실제 주소 값 : " 
        				+ System.identityHashCode(studentKim));
		System.out.println("studentDock의 실제 주소 값 : " 
        				+ System.identityHashCode(studentDock));
		
		...
        
	}
}

  • studentLee와 studentSang은 학번이 같이 때문에 논리적으로 확인하는 equals( ) 메서드 출력 값이 true이며, 같은 해시 코드 값을 반환하고 있습니다.
  • hashCode( ) 메서드를 재정의했을 때 실제 인스턴스의 주소 값은 identityHashCode( ) 메서드를 사용하면 알 수 있습니다.
  • 실제 메모리 주소 값은 다르므로, studentLee와 studentSang은 논리적으로는 같지만 실제로는 다른 인스턴스임을 확인할 수 있습니다.

1-6 clone( ) 메서드

  • 객체 원본을 유지해 놓고 복사본을 사용한다거나, 기본 틀(prototype)의 복사본을 사용해 동일한 인스턴스를 만들어 복잡한 생성 과정을 간단히 하려는 경우에 clone( ) 메서드를 사용할 수 있습니다.
  • clone( ) 메서드는 객체를 복제해 또 다른 객체를 반환해주는 메서드입니다.
  • 형식

예제 11-8 clone( ) 메서드로 인스턴스 복제하기

// 원점을 의미하는 Point 클래스
class Point {
	int x;
	int y;

	public Point(int x, int y) {
		this.x = x;
		this.y = y;
	}

	public String toString() {
		return "x = " + x + ", " + "y = " + y;
	}
}				

// 객체를 복제해도 된다는 의미로 Cloneable 인터페이스를 함께 선언
class Circle implements Cloneable {	
	Point point;
	int radius;
	
	public Circle(int x, int y, int radius) {
		point = new Point(x, y);
		this.radius = radius;
	}
	
	public String toString() {
		return "원점은 " + point + "이고, 반지름은 " + radius + "입니다.";
	}

	@Override
	protected Object clone() throws CloneNotSupportedException {
		// clone() 메서드를 사용할 때 발생할 수 있는 오류를 예외처리함
		return super.clone();
	}
}

public class ObjectCloneTest {
	public static void main(String[] args) throws CloneNotSupportedException {
		Circle circle = new Circle(10, 20, 30);
		// clone() 메서드를 사용해 circle 인스턴스를 copyCircle에 복제함
		Circle copycircle = (Circle)circle.clone(); 
		
		System.out.println(circle);
		System.out.println(copycircle);
		System.out.println("circle HashCode : " 
        				+ System.identityHashCode(circle));
		System.out.println("copycircle HashCode : " 
        				+ System.identityHashCode(copycircle));	
	}
}

  • clone( ) 메서드를 사용하려면 객체를 복제해도 된다는 의미로 클래스에 Cloneable 인터페이스를 구현해야 합니다.
  • clone( ) 메서드만 재정의하고 Cloneable 인터페이스를 명시하지 않으면 clone( ) 메서드를 호출할 때 CloneNotSupportedException이 발생합니다.
  • Object의 clone( ) 메서드는 클래스의 인스턴스를 새로 복제하여 생성해줍니다.
  • 멤버 변수가 동일한 인스턴스가 다른 메모리에 새로 생성되는 것입니다.
  • 인스턴스의 멤버 변수 값은 같고 주소 값은 다른 copyCircle이 생성되었음을 알 수 있습니다.

2. String 클래스

2-1 String을 선언하는 두 가지 방법

  • 자바는 문자열을 사용할 수 있도록 String 클래스를 제공합니다.
  • 문자열은 프로그램을 구현할 때 많이 활용합니다.
  • String을 사용할 때 문자열을 생성자의 매개변수로 하여 생성하는 방식과 이미 생성된 문자열 상수를 가리키는 방식이 있습니다.
  • 위 두 가지 방식은 내부적으로 큰 차이가 있습니다.
  • new 예약어를 사용하여 객체를 생성하는 경우는 "abc" 문자열을 위한 메모리가 할당되고 새로운 객체가 생성됩니다.
  • str2 = "test"와 같이 생성자를 이용하지 않고 바로 문자열 상수를 가리키는 경우에는 str2가 기존에 만들어져 있던 "test"라는 문자열 상수의 메모리 주소를 가리키게 됩니다.
  • String str3 = "test" 코드를 작성하면 str2와 str3는 주소 값이 같게 됩니다.
  • 프로그램에서 사용되는 상수 값을 저장하는 공간을 '상수 풀(constant pool)'이라고 합니다.

예제 11-9 주소 값 비교하기

public class StringTest1 {
	public static void main(String[] args) {
		String str1 = new String("abc");
		String str2 = new String("abc");
		
		// 인스턴스가 매번 새로 생성되므로 str1과 str2의 주소 값이 다름
		System.out.println(str1 == str2);
		// 문자열 값은 같으므로 true 반환
		System.out.println(str1.equals(str2));
		
		String str3 = "abc";
		String str4 = "abc";
		
		// 문자열 abc는 상수 풀에 저장되어 있으므로 
		// str3과 str4가 가리키는 주소 값이 같음
		System.out.println(str3 == str4);
		// 문자열 값도 같으므로 true 반환
		System.out.println(str3.equals(str4));
	}
}

  • 문자열 상수를 가리키는 경우에는 주소 값이 같음을 알 수 있습니다.

2-2 String 클래스의 final char[ ] 변수

  • 다른 프로그래밍 언어는 문자열을 구현할 때 일반적으로 char[ ] 배열을 사용합니다.
  • 자바String 클래스를 제공해 char[ ] 배열을 직접 구현하지 않고도 편리하게 문자열을 사용할 수 있습니다.
  • 프로그램에서 String s = new String("abc")라고 쓰면 abc는 String 클래스의 value 변수에 저장됩니다.
  • 위 변수는 final로 선언되어 있습니다.
  • final은 문자열을 변경할 수 없다는 뜻이며, 한 번 생성된 문자열은 변경되지 않습니다.
  • 위와 같은 문자열의 특징을 '문자열은 불변(immutable)한다'라고 합니다.
  • 두 개의 문자열을 연결하면 둘 중 하나의 문자열로 변경되는 것이 아니라 두 문자열이 연결된 새로운 문자열이 생성됩니다.

예제 11-10 두 문자열 연결하기

public class StringTest2 {
	public static void main(String[] args) {
		String javaStr = new String("Java");
		String androidStr = new String("Android");
		
		System.out.println(javaStr);
		System.out.println("처음 문자열 주소 값 : " 
        				+ System.identityHashCode(javaStr));
		
		// 문자열 javaStr와 문자열 androidStr를 연결하여 javaStr에 대입
		javaStr = javaStr.concat(androidStr);	
		
		System.out.println(javaStr);
		System.out.println("연결된 문자열 주소 값 : " 
        				+ System.identityHashCode(javaStr));
	}
}

  • concat( ) 메서드두 문자열을 연결합니다.
  • 결과만 보면 "Java" 문자열에 "Android" 문자열이 연결된 것 같지만, 앞서 설명했듯이 문자열은 불변(immutable)하므로 javaStr 변수 값 자체가 변하는 것이 아니라 새로운 문자열이 생성된 것입니다.
  • "JavaAndroid" 문자열이 새로 생성되고 javaStr은 그 문자열을 가리키게 되므로 주소 값이 달라집니다.

2-3 StringBuffer와 StringBuilder 클래스 활용하기

  • 프로그램을 만들다 보면 문자열을 변경하거나 연결해야 할 때가 많습니다.
  • String 클래스는 한번 생성되면 그 내부의 문자열이 변경되지 않기 때문에 String 클래스를 사용하여 문자열을 계속 연결하거나 변경하는 프로그램을 작성하면 메모리가 많이 낭비됩니다.
  • 위 문제를 해결하는 것이 바로 StringBuffer와 StringBuilder 클래스입니다.
  • StringBuffer와 StringBuilder는 내부에 변경 가능한(final이 아닌) char[ ]를 변수로 가지고 있습니다.
  • 이 두 클래스를 사용하여 문자열을 연결하면 기존에 사용하던 char[ ] 배열이 확장되므로 추가 메모리를 사용하지 않습니다.
  • 문자열을 연결하거나 변경할 경우 두 클래스 중 하나를 사용하면 됩니다.
  • 두 클래스의 차이는 여러 작업(스레드)이 동시에 문자열을 변경하려 할 때 문자열이 안전하게 변경되도록 보장해 주는가 그렇지 않은가의 차이입니다.
  • StringBuffer 클래스는 문자열이 안전하게 변경되도록 보장하지만, StringBuilder 클래스는 보장되지 않습니다.
  • 프로그램에서 따로 스레드를 생성하는 멀티스레드 프로그램이 아니라면 StringBuilder를 사용하는 것이 실행 속도가 좀 더 빠릅니다.

예제 11-11 StringBuilder 클래스 예제

public class StringBuilderTest {
	public static void main(String[] args) {
		String javaStr = new String("Java");
		// 인스턴스가 처음 생성되었을 때 메모리 주소
		System.out.println("JavaStr 문자열 주소 : " 
        				+ System.identityHashCode(javaStr));
		
		// String으로 부터 StringBuilder 생성
		StringBuilder buffer = new StringBuilder(javaStr); 
		System.out.println("연산 전 buffer 메모리 주소 : " 
        				+ System.identityHashCode(buffer));
		
		// 문자열 추가
		buffer.append(" and");
		buffer.append(" Android");
		buffer.append(" programming is fun!!");
		System.out.println("연산 후 buffer 메모리 주소 : " 
        				+ System.identityHashCode(buffer));
		
		// String 클래스로 반환
		javaStr = buffer.toString(); 
		System.out.println(javaStr);
		System.out.println("연결된 JavaStr 메모리 주소 : " 
        				+ System.identityHashCode(javaStr));
	}
}

  • "java" 문자열에 여러 문자열을 추가해야하는 경우에 일단 StringBuilder 클래스를 생성하고 여기에 문자열을 추가(append)합니다.
  • append( ) 메서드가 실행될 때마다 메모리가 새로 생성되는 것이 아니라, 하나의 메모리에 계속 연결되는 것을 해시 코드 값을 통해 알 수 있습니다.

3. Wrapper 클래스

3-1 기본 자료형을 위한 클래스

  • 정수를 사용할 때 기본형인 int를 사용했습니다.
  • 정수를 객체형으로 사용할 경우(매개변수가 객체거나 반환 값이 객체형일 때) Integer를 사용합니다.
  • 자바에서는 기본 자료형처럼 사용할 수 있는 클래스를 제공합니다.
  • 클래스를 기본 자료형으로 감쌌다는 의미로 Wrapper 클래스라고합니다.

3-2 Integer 클래스 사용하기

  • Integer 클래스의 JavaDoc을 살펴보면 int 자료형을 감싼 클래스라고 설명되어 있습니다.
  • Integer 클래스의 생성자는 다음과 같이 특정 정수를 매개 변수로 받는 경우와 문자열을 받는 경우 두 가지가 있습니다.
  • Integer 클래스는 int 자료형의 특성이 그대로 구현되어 있습니다.
  • 사용 가능한 최댓값과 최솟값이 static 변수로 정의되어 있습니다.
  • 대부분의 Wrapper 클래스가 Integer 클래스 정의와 크게 다르지 않습니다.
  • Integer 클래스는 멤버 변수로 기본 자료형 int를 가지고 있고, int 값을 객체로 활용할 수 있는 여러 메서드를 제공합니다.
  • int value는 final 변수이며 한번 생성되면 변경할 수 없습니다.

Integer 클래스의 메서드

  • Integer 클래스의 여러 메서드 중 자주 사용하는 메서드 몇 가지를 살펴보겠습니다.
  • Integer 클래스 내부의 int 자료형 값을 가져오기 위해서는 intValue( ) 메서드를 사용합니다.
  • 현재 Integer는 deprecated 되었습니다.
  • valueOf( ) 정적 메서드를 사용하면 생성자를 사용하지 않고 정수나 문자열을 바로 Integer 클래스로 반환받을 수 있습니다.
  • parseInt( ) 메서드를 활용하면 문자열이 어떤 숫자를 나타낼 때, 이를테면 학번이나 개수 등이 문자열로 전달된 경우에 문자열에서 int 값을 바로 가져와서 반환할 수도 있습니다.

3-3 오토박싱과 언박싱

  • 어떤 정수 값을 사용할 때 int로 선언하는 경우와 Integer로 선언하는 경우는 전혀 다릅니다.
  • int는 기본 자료형으로 4바이트지만, Integer는 클래스이기 때문에 인스턴스로 생성하려면 생성자를 호출하고 정수 값을 인수로 넣어야 합니다.
  • 기본 자료형과 Wrapper 클래스는 같은 값을 나타내지만, 그 쓰임과 특성이 전혀 다릅니다.
  • 오토박싱(autoboxing)은 기본형을 객체형으로 바꾸는 방식입니다.
  • 언박싱(unboxing)은 객체형을 기본형으로 꺼내는 방식입니다.

4. Class 클래스

  • 자바의 모든 클래스와 인터페이스는 컴파일 되고 나면 class 파일로 생성됩니다.
  • 예를 들면 a.java 파일이 컴파일되면 a.class 파일이 생성되고 이 class 파일에는 클래스나 인터페이스에 대한 변수, 메서드, 생성자 등의 정보가 들어 있습니다.
  • Class 클래스는 컴파일된 class 파일에 저장된 클래스나 인터페이스 정보를 가져오는데 사용합니다.

4-1 Class 클래스란?

  • 변수를 선언할 때 자료형을 미리 파악하고 그 자료형에 따라 변수를 선언했습니다.
  • 클래스를 사용할 때도 이미 그 클래스 정보(변수, 메서드 등)를 알고 있는 상황에서 프로그램을 만듭니다.
  • 어떤 경우에는 여러 클래스 중에 상황에 따라 다른 클래스를 사용해야 할 때도 있고, 반환받는 클래스가 정확히 어떤 자료형인지 모를 때도 있습니다.
  • 모르는 클래스 정보를 사용할 경우에는 클래스 정보를 직접 찾아야 하며, 이때 Class 클래스를 활용합니다.

Class 클래스를 선언하고 클래스 정보를 가져오는 방법

  • Class 클래스를 선언하고 클래스 정보를 가져오는 방법으로 세가지가 있습니다.

1. Object 클래스의 getClass( ) 메서드 사용하기

  • Object에 선언한 getClass( ) 메서드는 모든 클래스가 사용할 수 있는 메서드입니다.
  • 이 메서드를 사용하려면 이미 생성된 인스턴스가 있어야 합니다.

2. 클래스 파일 이름을 Class 변수에 직접 대입하기

  • 컴파일된 클래스 파일이 있다면 이름만으로 Class 클래스를 반환받습니다.

3. Class.forName("클래스 이름") 메서드 사용하기

  • 컴파일된 클래스 파일이 있다면 이름만으로 Class 클래스를 반환받습니다.

예제 11-12 Person 클래스 생성하기

public class Person {
	private String name;
	private int age;
	
	// 디폴트 생성자
	public Person() {}
	
	// 이름만 입력받는 생성자
	public Person(String name) {		
		this.name = name;
	}

	// 이름과 나이를 입력 받는 생성자(풀생성자)
	public Person(String name, int age) {
		this.name = name;
		this.age = age;
	}

	String getName() {
		return name;
	}

	void setName(String name) {
		this.name = name;
	}

	int getAge() {
		return age;
	}

	void setAge(int age) {
		this.age = age;
	}
}
  • Person 클래스는 생성자가 3개이고, 각 멤버 변수에 get( ) 메서드와 set( ) 메서드를 제공합니다.

예제 11-13 Person의 클래스 가져오기

public class ClassTest {
	// forName() 메서드에서 발생하는 예외를 처리함.
	// 이름과 일치하는 클래스가 없는 경우 ClassNotFoundException 발생
	public static void main(String[] args) throws ClassNotFoundException {
		
		Person person = new Person();
		// Object의 getClass( ) 메서드 사용하기
		Class pClass1 = person.getClass(); 			
		System.out.println(pClass1.getName());
		
		// 직접 class 파일 대입하기
		Class pClass2 = Person.class;				
		System.out.println(pClass2.getName());
		
		// 클래스 이름으로 가져오기
		Class pClass3 = Class.forName("study.classex.Person");	
		System.out.println(pClass3.getName());
		
	} 
}

  • forName( ) 메서드를 살펴보면, 클래스 이름(패키지 이름 포함)으로 가져오는 경우에는 매개변수로 쓰이는 값이 문자열입니다.
  • 매개변수로 받은 문자열에 해당하는 클래스가 존재하지 않으면 클래스를 가져오는데 실패하는데, 이 때 ClassNotFoundException이 발생합니다.
  • Class 클래스를 가져온 후 getName( ) 메서드를 호출하면 클래스의 이름인 study.classex.Person이 잘 출력되는 것을 볼 수 있습니다.
  • Class 클래스를 통하여 클래스 정보를 알 수 있습니다.

4-2 Class 클래스를 활용해 클래스 정보 알아보기

  • 프로그래밍을 하다 보면 내가 사용할 클래스의 자료형을 모르는 경우가 있을 수 있습니다.
  • 예로 내 컴퓨터에 저장되어 있지 않은 객체를 메모리에 로드하고 생성하는 경우 그 객체의 정보를 알 수 없습니다.
  • 이때 Class 클래스를 가져올 수 있다면 해당 클래스 정보, 즉 생성자 · 메서드 · 멤버 변수 정보를 찾을 수 있습니다.
  • 사용하려는 클래스의 자료형을 모르는 상태에서 Class 클래스를 활용하여 그 클래스의 정보를 가져오고, 이 정보를 활용하여 인스턴스를 생성하거나 메서드를 호출하는 방식을 '리플렉션(reflection)'이라고 합니다.

예제 11-14 String 클래스 정보 가져오기

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;

public class StringClassTest {
	public static void main(String[] args) throws ClassNotFoundException {
		// 클래스 이름으로 가져오기
		Class strClass = Class.forName("java.lang.String");	
		
		// 모든 생성자 가져오기
		Constructor[] cons = strClass.getConstructors();
		for(Constructor c : cons) {
			System.out.println(c);
		}
		
		System.out.println("============================================");
		// 모든 멤버 변수(필드) 가져오기
		Field[] fields = strClass.getFields();		
		for(Field f : fields) {
			System.out.println(f);
		}
		
		System.out.println("============================================");
		// 모든 메서드 가져오기
		Method[] methods = strClass.getMethods();	
		for(Method m : methods) {
			System.out.println(m);
		}
	} 
}

  • Constructor, Method, Field 등의 클래스는 java.lang.reflect 패키지에 정의되어 있습니다.
  • Class 클래스를 가져오기 위해 forName( ) 메서드를 사용합니다.
  • forName( ) 메서드는 정적 메서드이므로 클래스를 생성하지 않아도 사용할 수 있습니다.
  • 향상된 for문을 사용하여 모든 정보를 출력했습니다.

4-3 Class.forName( )을 사용해 동적 로딩하기

  • 대부분의 클래스 정보는 프로그램이 로딩될 때 이미 메모리에 있습니다.
  • 여러 종류의 데이터베이스 중 시스템을 구동할 때 어떤 데이터베이스와 연결할지만 결정된다면 해당 드라이버(라이브러리)만 로딩하면 됩니다.
  • 프로그램 실행 이후 클래스의 로딩이 필요한 경우 클래스의 '동적 로딩(dynamic loading)' 방식을 사용합니다.
  • 자바는 Class.forName( ) 메서드를 동적 로딩으로 제공합니다.
  • forName( ) 메서드를 살펴보면 매개변수로 문자열을 받는데, 이때 입력받는 문자열을 변수로 선언하여 변수 값만 바꾸면 다른 클래스를 로딩할 수 있습니다.
  • 여러 데이터베이스 드라이버 중 필요한 드라이버의 값을 설정 파일에서 읽어 문자열 변수로 저장한다면, 설정 파일을 변경함으로써 필요한 드라이버를 간단하게 로딩할 수 있습니다.

forName( ) 메서드를 사용할 때 유의할 점

  • forName( ) 메서드를 이용하면 Class 클래스를 가져올 때 가장 유의해야 할 점은 해당 forName("클래스 이름")의 클래스 이름이 문자열 값이므로, 문자열에 오류가 있어도 컴파일할 때에는 그 오류를 알 수 없습니다.
  • 따라서 동적 로딩 방식은 컴파일할 때 오류를 알 수 없습니다.
  • 여러 클래스 중 하나를 선택한다거나, 시스템 연동 중 매개변수로 넘어온 값에 해당하는 클래스가 로딩되고 실행되는 경우에는 동적 로딩 방식을 유연하게 사용할 수 있습니다.
  • 동적 로딩을 통해 Class 클래스를 가져올 수 있다면 리플렉션 프로그래밍으로 객체를 생성하고 활용할 수 있습니다.

좋은 웹페이지 즐겨찾기