🖤 1. java.lang패키지
1.1 Object클래스
- equals() clone() 등 11개의 메소드를 가짐
🟢 equals()
- 두 객체의 같고 다름을 참조변수의 값으로 판단한다.
class Main {
public static void main (String[] args) throws java.lang.Exception {
Value v1 = new Value(10);
Value v2 = new Value(10);
if ( v1.equals(v2) ) System.out.println("v1과 v2는 같습니다.");
else System.out.println("v1과 v2는 다릅니다.");
v2 = v1;
if ( v1.equals(v2) ) System.out.println("v1과 v2는 같습니다.");
else System.out.println("v1과 v2는 다릅니다.");
}
}
class Value {
int value;
Value(int value){
this.value = value;
}
}
v1과 v2는 다릅니다.
v1과 v2는 같습니다.
참조변수가 아닌 value값을 비교하게 하려면, equals메서드를 오버라이딩하여 저장된 내용을 비교하도록 하면 된다.
class Person {
long id;
public boolean equals(Object obj) {
if(obj!=null && obj instanceof Person) {
return id ==((Person)obj).id; // id값 참조를 위해서는 Person으로 형변환 필요
} else {
return false; // 타입이 Person이 아니면 값 비교 X
}
}
Person(long id) {
this.id = id;
}
}
class EqualsEx2 {
public static void main(String[] args) {
Person p1 = new Person(8011081111222L);
Person p2 = new Person(8011081111222L);
if(p1==p2)
System.out.println("p1과 p2는 같은 사람입니다.");
else
System.out.println("p1과 p2는 다른 사람입니다.");
if(p1.equals(p2))
System.out.println("p1과 p2는 같은 사람입니다.");
else
System.out.println("p1과 p2는 다른 사람입니다.");
}
}
p1과 p2는 다른 사람입니다.
p1과 p2는 같은 사람입니다.
🟢 hashCode()
- 같은 객체라면 hashCode메서드를 호출했을때 결과값인 해시코드도 같아야한다.
- 반면, System.identityHashCode(Object x)는 객체 주소값으로 해시코드를 생성하기 때문에 모든 객체에 대해 항상 다른 해시코드 값을 반환할 것을 보장한다.
class Main {
public static void main (String[] args) throws java.lang.Exception {
String str1 = new String("abc");
String str2 = new String("abc");
System.out.println(str1.equals(str2));
System.out.println(str1.hashCode());
System.out.println(str2.hashCode());
System.out.println(System.identityHashCode(str1));
System.out.println(System.identityHashCode(str2));
}
}
true
96354
96354
498931366
2060468723
String클래스는 문자열내용이 같으면 동일한 해시코드를 반환하도록 hashCode()를 오버라이딩했다.
따라서 위에서처럼 str1,str2는 동일한 해시코드값을 얻는다.
반면 System.identityHashCode(Object x)값이 다름을 통해서로 다른 객체라는 것을 알 수 있다.
🟢 clone()
- 자신을 복제해 새 인스턴스를 생성한다.
- 단순히 인스턴스변수 값만 복사한다.
- 참조타입 인스턴스 변수가 있는 클래스는 완전한 인스턴스 복제가 이루어지지 않는다.
- Cloneable 인터페이스를 구현한 클래스에서만 clone()을 호출할 수 있다.
class Point implements Cloneable { // Cloneable인터페이스 구현
int x;
int y;
Point(int x, int y) {
this.x = x;
this.y = y;
}
public String toString() {
return "x="+x +", y="+y;
}
public Object clone() { // 오버라이딩. 접근제어자를 protected에서 public으로 변경
Object obj = null;
try {
obj = super.clone(); // clone()은 반드시 예외처리를 해주어야 한다.
} catch(CloneNotSupportedException e) {}
return obj;
}
}
class CloneEx1 {
public static void main(String[] args){
Point original = new Point(3, 5);
Point copy = (Point)original.clone(); // 복제(clone)해서 새로운 객체를 생성
System.out.println(original);
System.out.println(copy);
}
}
x=3, y=5
x=3, y=5
JDK1.5부터 공변 반환타입(convariant return type)이 추가되었다.
이 기능은 오버라이딩할 때 조상 메서드 반환타입을 자손 타입으로 변경하는것을 허용하는 것이다.
public Point clone() { // 반환타입을 Object에서 Point로 변경
Object obj = null;
try {
obj = super.clone(); // clone()은 반드시 예외처리를 해주어야 한다.
} catch(CloneNotSupportedException e) {}
return (Point)obj; // Point타입으로 형변환
}
공변 변환타입을 사용하면 아래처럼 조상타입이 아닌 실제 반환되는 자손 객체 타입으로 반환할 수 있어 번거로운 형변환이 줄어든다는 장점이 있다.
Point copy = (Point)original.clone(); // 공변 변환타입 적용 전
Point copy = original.clone(); // 공변 변환타입 적용 후
공변 반환 타입의 효과적인 사용 예시를 보자.
import java.util.*;
class CloneEx2 {
public static void main(String[] args){
int[] arr = {1,2,3,4,5};
// 배열 arr을 복제해서 같은 내용의 새로운 배열을 만든다.
int[] arrClone = arr.clone();
arrClone[0]= 6;
System.out.println(Arrays.toString(arr));
System.out.println(Arrays.toString(arrClone));
}
}
[1, 2, 3, 4, 5]
[6, 2, 3, 4, 5]
int[] 타입의 arr에 대해 clone()을 호출하면, 반환 타입은 int[]가 된다. 이는 Object에서 정의된 반환 타입 Object보다 더 구체적인 타입이다.
이러한 방식은 개발자가 반환받은 객체를 캐스팅할 필요 없이 직접 해당 타입으로 사용할 수 있도록 해준다.
🔶얕은 복사와 깊은 복사
얕은 복사(shallow copy): 원본을 변경하면 복사본도 영향을 받는다.
깊은 복사(deep copy): 원본과 복사본이 서로 다른 객체를 참조하기 때문에 원본의 변경이 복사본에 영향을 미치지 않는다.
public class Circle implements Cloneable{
public Point getP() {
return p;
}
public double getR() {
return r;
}
Point p; //원점 - 참조변수
double r;
public Circle(Point p, double r) {
this.p = p;
this.r = r;
}
public Circle clone() { // 얕은 복사
Object obj = null;
try{
obj = super.clone();
}catch (CloneNotSupportedException e){}
return (Circle)obj;
}
}
public static void main(String[] args) {
SpringApplication.run(ItemServiceApplication.class, args);
Circle c1 = new Circle(new Point(1,1),2.0);
Circle c2 = c1.clone();
System.out.println("c1 = " + c1.getP());
System.out.println("c2 = " + c2.getP());
}
c1 = hello.itemservice.itemservice.ptr.Point@3b1dc579
c2 = hello.itemservice.itemservice.ptr.Point@3b1dc579
위를 보면 c1과 c2는 같은 Point인스턴스를 가리키게 되므로 완전한 복제라 볼 수 없다.
그러면 c1과 새 객체가 다른 Point인스턴스를 가리키도록 어떻게 할 수 있을까?
public Circle deepCloneCopy() {
Object obj = null;
try{
obj = super.clone();
}catch (CloneNotSupportedException e){}
Circle c = (Circle)obj;
c.p = new Point(this.p.x,this.p.y);
return c;
}
Circle c3 = c1.deepCloneCopy();
System.out.println("c1 = " + c1.getP());
System.out.println("c3 = " + c3.getP());
c1 = hello.itemservice.itemservice.ptr.Point@5edacf20
c3 = hello.itemservice.itemservice.ptr.Point@16a5eb6d
Circle c = (Circle)obj에서, 얕은 복사로 생성된 obj를 Circle 타입으로 캐스팅한다. 이 시점에서 c는 원본 객체와 동일한 p (Point 객체의 참조)를 가지고 있다.
c.p = new Point(this.p.x, this.p.y); 에서, 원본 Circle 객체에 포함된 Point 객체 p를 새로 생성하여 c의 p 참조에 할당한다.
p 객체의 x와 y 좌표를 사용하여 완전히 새로운 Point 객체를 생성함으로써, 원본 객체의 Point와는 독립된 복사본을 만든다. 이 단계가 바로 "깊은 복사"의 핵심이다.
복제된 객체가 원본 객체의 참조형 필드의 실제 객체도 별도로 복제하는 것이다.
🟢 getClass()
- 자신이 속한 클래스의 Class 객체를 반환한다.
- Class객체는 클래스의 모든 정보를 담고 있으며, 클래스 당 1개만 존재한다.
- Class객체를 이용하면 클래스에 정의된 멤버이름이나 개수 등, 클래스에 대한 모든 정보를 얻을 수 있다.
- class파일이 클래스 로더에 의해 메모리에 올라갈때 자동으로 생성된다.
- Card.class → ClassLoader → Class 객체 (클래스 파일을 메모리에 로드하고 변환하는 일은 클래스 로더가 한다.)
🔶Class 객체를 얻는 방법
Class cObj = new Card().getClass(); // 생성된 객체로부터 얻는 방법
Class cobj = Card.class; // 클래스 리터럴(*.class)로 부터 얻는 방법
Class cobj = Class.forName("Card"); // 클래스 이름으로부터 얻는 방법
아래는 Class객체를 이용해서 동적으로 객체를 생성하는 예제이다.
class Main {
public static void main (String[] args) throws java.lang.Exception {
Card c = new Card("HEART", 3); // new연산자로 객체 생성
Card c2 = Card.class.newInstance(); // Class객체를 통해서 객체 생성
Class cObj = c.getClass();
System.out.println(c);
System.out.println(c2);
System.out.println(cObj.getName()); // 클래스의 이름 반환
System.out.println(cObj.toGenericString()); // 클래스에 대한 전체 정보를 포함한 문자열을 반환
System.out.println(cObj.toString());
}
}
final class Card {
String kind;
int num;
Card() {
this("SPADE", 1);
}
Card(String kind, int num) {
this.kind = kind;
this.num = num;
}
public String toString() {
return kind + ":" + num;
}
}
HEART:3
SPADE:1
Card
final class Card
class Card
'Java' 카테고리의 다른 글
[Java] 김영한의 실전 자바 - 중급편 섹션2 불변 객체 (0) | 2024.05.05 |
---|---|
[Java] 김영한의 실전 자바 - 중급편 섹션1 Object 클래스 (0) | 2024.05.05 |
[Java] 김영한의 실전 자바 - 기본편 섹션9,10 상속,다형성 (0) | 2024.04.24 |
[Java] 김영한의 실전 자바 - 기본편 섹션7,8 자바 메모리 구조와 static, final (0) | 2024.04.18 |
[Java의 정석] chapter06 객체지향 프로그래밍 II - 요약정리 (0) | 2024.04.15 |