Chapter 11 기본 클래스
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( ) 메서드 원형
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());
}
}
- 위 정의 내용을 보면 '클래스이름@해시 코드 값'임을 알 수 있습니다.
- 즉, 클래스의 이름과 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));
}
}
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);
}
}
}
- 자바의 모든 클래스와 인터페이스는 컴파일 되고 나면 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 클래스를 가져올 수 있다면 리플렉션 프로그래밍으로 객체를 생성하고 활용할 수 있습니다.
Author And Source
이 문제에 관하여(Chapter 11 기본 클래스), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://velog.io/@ruinak_4127/Chapter-11-기본-클래스저자 귀속: 원작자 정보가 원작자 URL에 포함되어 있으며 저작권은 원작자 소유입니다.
우수한 개발자 콘텐츠 발견에 전념 (Collection and Share based on the CC Protocol.)