티스토리 뷰

주의 사항!

  • 이 글은 제가 직접 공부하는 중에 작성되고 있습니다.
  • 따라서 제가 이해하는 그대로의 내용이 포함됩니다.
  • 따라서 이 글은 사실과는 다른 내용이 포함될 수 있습니다.


클래스를 선언할 때 extends 키워드로 다른 클래스를 상속하지 않으면 암시적으로 java.lang.Object 클래스를 상속하게 됩니다. 따라서 자바의 모든 클래스는 Object 클래스의 자식이거나 자손 클래스입니다.

 

Object 클래스는 필드가 없고, 메서드들로 구성되어 있습니다. 이 메서드들은 모든 클래스가 Object를 상속하기 때문에 모든 클래스에서 사용할 수 있습니다.

 

1. 객체 비교 : equals()

다음은 Object의 equals() 메서드입니다.

public boolean equals(Object obj) { ... }

equals() 메서드의 매개 타입은 Object입니다. 이것은 모든 객체가 매개 값으로 대입될 수 있음을 의미합니다. 왜냐하면 Object가 최상위 타입이므로 모든 객체는 Object 타입으로 자동 타입 변환될 수 있기 때문입니다. Object 클래스의 equals() 메서드는 비교 연산자인 ==과 동일한 결과를 리턴합니다. 두 객체가 동일한 객체라면 true를 리턴하고 그렇지 않으면 flase를 리턴합니다.

//exam00.java
package chapter00.exam00;

public class exam00 
{	
	public static void main(String[] args)
	{		
		Object obj1 = new Object();
		Object obj2 = new Object();
		
		System.out.println(obj1 == obj2);
		System.out.println(obj1.equals(obj2));
		System.out.println();
		
		String str1 = "string";
		String str2 = "string";
		String str3 = new String("string");
		
		System.out.println(str1 == str2);
		System.out.println(str1 == str3);
		System.out.println(str1.equals(str2));
		System.out.println(str1.equals(str3));
	}
}

/*
실행결과

false
false

true
false
true
true

*/

자바에서는 두 객체를 동등 비교할 때 equals() 메서드를 사용합니다. equals() 메서드는 두 객체를 비교해서 논리적으로 동등하면 true를 리턴하고, 그렇지 않으면 false를 리턴합니다. 논리적으로 동등하다는 것은 같은 객체이건 다른 객체이건 상관없이 객체가 저장하고 있는 데이터가 동일함을 뜻합니다.

 

두 객체를 비교할 때 비교 연산자 ==를 사용하면 두 객체의 주소를 비교하게 됩니다. 하지만 equals() 메서드를 사용하면 그 객체가 저장하고 있는 데이터를 비교합니다. 따라서 위의 코드를 보면 String 객체에 대해 비교 연산자와 equals() 메서드를 사용한 결과가 다르게 나오는 것을 확인할 수 있습니다. Object 객체에 대해서는 같은 결과가 나왔던 이유는 Objcet의 경우 equals() 메서드를 사용해도 주소를 비교하기 때문입니다.

 

equals() 메서드를 사용했을 때 String 객체의 주소를 비교하는 것이 아니라 문자열이 동일한지 비교할 수 있었던 이유는 String 클래스가 Object 클래스의 equals() 메서드를 오버 라이딩하고 있기 때문입니다.

 

equals() 메서드를 오버 라이딩할 때는 매개 값(비교 객체)이 기준 객체와 동일한 타입의 객체인지 먼저 확인해야 합니다. Object 타입의 매개 변수는 모든 객체가 매개 값으로 제공될 수 있기 때문에 instanceof 연산자로 기준 객체와 동일한 타입인지 제일 먼저 확인해야 합니다. 만약 비교 객체가 다른 타입이라면 equals() 메서드는 false를 리턴해야 합니다. 비교 객체가 동일한 타입이라면 기준 객체 타입으로 강제 타입 변환해서 필드 값이 동일한지 검사하면 됩니다. 필드 값이 모두 동일하다면 true를 리턴하고 그렇지 않으면 false를 리턴합니다.

 

다음 예제는 Member 클래스에서 equals() 메서드를 오버 라이딩한 것입니다. Member 타입이면서 id 필드 값이 같을 경우에는 true를 리턴하고, 그 이외의 경우에는 false를 리턴합니다.

//Member.java
package chapter00.exam00;

public class Member {
	private String id;
	
	public Member(String id) {
		this.id = id;
	}
	
	public String getId() {
		return id;
	}
	
	public void setId(String id) {
		this.id = id;
	}
	
	@Override
	public boolean equals(Object obj) {
		if (obj instanceof Member) {
			Member member = (Member) obj;
			return (id.equals(member.getId())) ? true : false;
		} else {
			return false;
		}
	}
}
//exam00.java
package chapter00.exam00;

public class exam00 
{	
	public static void main(String[] args)
	{		
		Member mem1 = new Member("KOEY");
		Member mem2 = new Member("KOEY");
		
		System.out.println(mem1 == mem2);
		System.out.println(mem1.equals(mem2));
	}
}

/*
실행결과

false
true

*/

 

2. 객체 해시 코드 : hashCode()

객체 해시 코드란 객체를 식별할 하나의 정수 값을 말합니다. Object의 hashCode() 메서드는 객체의 메모리 번지를 이용해서 해시 코드를 만들어 리턴하기 때문에 객체마다 다른 값을 가지고 있습니다. 논리적 동등 비교 시 hashCode()를 오버 라이딩할 필요가 있는데, 다음에 배울 컬렉션 프레임워크에서 HashSet, HashMap, Hashtable은 다음과 같은 방법으로 두 객체가 동등한 지 비교합니다. 우선 hashCode() 메서드를 실행해서 리턴된 해시 코드 값이 같은지를 봅니다. 해시 코드 값이 다르면 다른 객체로 판단하고, 해시 코드 값이 같으면 equals() 메서드로 다시 비교합니다. 그렇기 때문에 hashCode() 메서드가 true가 나와도 equals()의 리턴 값이 다르면 다른 객체가 됩니다.

 

다음 예제를 보면 Key 클래스는 equals() 메서드를 오버 라이딩해서 number 필드 값이 같으면 true를 리턴하도록 했습니다. 그러나 hashCode() 메서드는 오버 라이딩하지 않았기 때문에 Object의 hashCode() 메서드가 사용됩니다.

//Key.java
package chapter00.exam00;

public class Key {
	private int number;
	
	public Key() {
		this.number = 0;
	}
	public Key(int number) {
		this.number = number;
	}
	
	public int getNumber() {
		return number;
	}
	public void setNumber(int number) {
		this.number = number;
	}
	@Override
	public boolean equals(Object obj) {
		if(obj instanceof Key) {
			Key key = (Key) obj;
			return (number == key.getNumber()) ? true : false;
		} else {
			return false;
		}
	}
}

이런 경우 HashMap의 식별키로 Key 객체를 사용하면 저장된 값을 찾아오지 못합니다. 왜냐하면 number 필드 값이 같더라도 hashCode() 메서드에서 리턴하는 해시 코드가 다르기 때문에 다른 식별키로 인식하기 때문입니다. 다음 예제는 "new Key(1)" 객체로 "홍길동"을 저장하고, 다시 "new Key(1)"객체로 저장된 "홍길동"을 읽으려고 했지만 결과는 null이 나옵니다.

//exam00.java
package chapter00.exam00;

import java.util.HashMap;

public class exam00 
{	
	public static void main(String[] args)
	{		
		//Key 객체를 식별키로 사용해서 String 값을 저장하는 HashMap 객체 생성
		HashMap<Key, String> hashMap = new HashMap<Key, String>();
		
		//식별키 "new Key(1)"로 "홍길동"을 저장함
		hashMap.put(new Key(1), "홍길동");
		
		//식별키 "new Key(1)"로 "홍길동"을 읽어옴
		String value = hashMap.get(new Key(1));
		System.out.println(value);
	}
}

/*
실행결과

null

*/

의도한 대로 "홍길동"을 읽으려면 다음과 같이 오버 라이딩한 hashCode() 메서드를 Key 클래스에 추가하면 됩니다. hashCode()의 리턴 값을 number 필드 값으로 했기 때문에 저장할 때의 "new Key(1)"와 읽을 때의 "new Key(1)"은 같은 해시 코드가 리턴됩니다.

//Key.java
package chapter00.exam00;

public class Key {
	private int number;
	
	public Key() {
		this.number = 0;
	}
	public Key(int number) {
		this.number = number;
	}
	
	public int getNumber() {
		return number;
	}
	public void setNumber(int number) {
		this.number = number;
	}
	@Override
	public boolean equals(Object obj) {
		if(obj instanceof Key) {
			Key key = (Key) obj;
			return (number == key.getNumber()) ? true : false;
		} else {
			return false;
		}
	}
	@Override
	public int hashCode() {    //추가된 코드
		return number;
	}
}
//exam00.java
package chapter00.exam00;

import java.util.HashMap;

public class exam00 
{	
	public static void main(String[] args)
	{		
		//Key 객체를 식별키로 사용해서 String 값을 저장하는 HashMap 객체 생성
		HashMap<Key, String> hashMap = new HashMap<Key, String>();
		
		//식별키 "new Key(1)"로 "홍길동"을 저장함
		hashMap.put(new Key(1), "홍길동");
		
		//식별키 "new Key(1)"로 "홍길동"을 읽어옴
		String value = hashMap.get(new Key(1));
		System.out.println(value);
	}
}

/*
실행결과

홍길동

*/

저장할 때의 new Key(1)와 읽을 때의 new Key(1)는 사실 서로 다른 객체이지만 HashMap은 hashCode()의 리턴 값이 같고, equals()의 리턴 값이 true가 나오기 때문에 동등 객체로 평가합니다. 즉, 같은 식별키로 인식한다는 것입니다. 결론적으로 말해서 객체의 동등 비교를 위해서는 Object의 equals() 메서드만 오버 라이딩하지 말고 hashCode() 메서드도 오버 라이딩해서 논리적 동등 객체일 경우 동일한 해시 코드가 리턴되도록 해야 합니다.

 

다음은 이전 예제에서 사용한 Member 클래스를 보완하는 측면에서 hashCode() 메서드를 오버 라이딩한 것입니다. id 필드 값이 같을 경우 같은 해시 코드를 리턴하도록 하기 위해 String의 hashCode() 메서드의 리턴 값을 활용했습니다. String의 hashCode()는 같은 문자열일 경우 동일한 해시 코드를 리턴합니다.

//Member.java
package chapter00.exam00;

public class Member {
	private String id;
	
	public Member(String id) {
		this.id = id;
	}
	
	public String getId() {
		return id;
	}
	
	public void setId(String id) {
		this.id = id;
	}
	
	@Override
	public boolean equals(Object obj) {
		if (obj instanceof Member) {
			Member member = (Member) obj;
			return (id.equals(member.getId())) ? true : false;
		} else {
			return false;
		}
	}
	@Override    //추가된 코드
	public int hashCode() {
		return id.hashCode();
	}
}

 

3. 객체 문자 정보 : toString()

Object 클래스의 toString() 메서드는 객체의 문자 정보를 리턴합니다. 객체의 문자 정보란 객체를 문자열로 표현한 값을 말합니다. 기본적으로 Object 클래스의 toString() 메서드는 "클래스명@16진수 해시 코드"로 구성된 문자 정보를 리턴합니다.

package Example;

public class Main {
	public static void main(String[] args) {
		Main main = new Main();
		System.out.println(main.toString());
	}
}

/*
실행결과

Example.Main@3ac3fd8b

*/

Object의 toString() 메서드의 리턴 값은 자바 애플리케이션에서는 별 값어치가 없는 정보이므로 Object 하위 클래스는 toSting() 메서드를 오버 라이딩하여 간결하고 유익한 정보를 리턴하도록 되어 있습니다. 예를 들어 java.util 패키지의 Data 클래스는 toString() 메서드를 오버 라이딩하여 현재 시스템의 날짜와 시간 정보를 리턴합니다. 그리고 String 클래스는 toString() 메서드를 오버 라이딩해서 저장하고 있는 문자열을 리턴합니다.

 

다음 예제는 Object 클래스와 Data 클래스의 toString() 메서드의 리턴 값을 출력해본 것입니다.

package Example;

import java.util.Date;

public class Main {
	public static void main(String[] args) {
		Object obj = new Object();
		Date date = new Date();
		
		System.out.println(obj.toString());
		System.out.println(date.toString());
	}
}

/*
실행결과

java.lang.Object@6e8dacdf
Wed Apr 14 15:05:03 KST 2021

*/

 

우리가 만드는 클래스도 toString() 메서드를 오버 라이딩해서 좀 더 유용한 정보를 리턴하도록 할 수 있습니다. SmartPhone 클래스에서 toString() 메서드를 오버 라이딩하여 제작회사와 운영체제를 리턴하도록 만들어 보겠습니다.

package Example;

public class Main {
	public static void main(String[] args) {
		SmartPhone myPhone = new SmartPhone("오성", "호문클루스");
		System.out.println(myPhone.toString());
		System.out.println(myPhone);    //출력값으로 객체만 넣을 경우 toString()메서드를 자동 호출
		
		Main main = new Main();
		System.out.println(main);    //출력값으로 객체만 넣을 경우 toString()메서드를 자동 호출
	}
}

/*
실행결과

오성, 호문클루스
오성, 호문클루스
Example.Main@5594a1b5

*/

 

4. 객체 복제 : clone()

객체 복제란 원본 객체의 필드 값과 동일한 값을 가지는 새로운 객체를 생성하는 것을 말합니다. 객체를 복제하는 이유는 원본 객체를 안전하게 보호하기 위함입니다. 신뢰하지 않는 영역으로 원본 객체를 넘겨 작업할 경우 원본 객체의 데이터가 훼손될 수 있기 때문에, 복제된 객체를 만들어 신뢰하지 않는 영역으로 넘기는 것이 좋습니다. 객체를 복제하는 방법에는 얕은 복제와 깊은 복제가 있습니다.

 

4. 1. 얕은 복제(thin clone)

얕은 복제란 단순히 필드 값을 복사해서 객체를 복제하는 것을 말합니다. 필드 값만 복제하기 때문에 필드가 기본 타입일 경우 값 복사가 일어나고, 필드가 참조 타입일 경우에는 객체의 주소가 복사됩니다. 예를 들어 다음과 같이 필드가 선언되어 있을 때,

int number = 10;
int[] array = {1, 3, 5};

이를 얕은 복제를 하게 되면 number 값은 10을 그대로 복사해오지만, array의 경우 배열 {1, 3, 5,}를 복사해오는 것이 아닌 이 배열의 주소를 복사해오게 됩니다. 따라서 clone 객체의 number는 원본 객체의 number와 값만 같을 뿐 다른 필드가 되지만, array의 경우에는 clone 객체와 원본 객체가 같은 배열을 참조하게 됩니다.

 

Object의 clone() 메서드는 자신과 동일한 필드 값을 가진 얕은 복제된 객체를 리턴합니다. 이 메서드로 객체를 복제하려면 원본 객체는 반드시 java.lang.Clonable 인터페이스를 구현하고 있어야 합니다. 이 인터페이스는 아무런 필드나 메서드가 정의되어 있지 않습니다. 그럼에도 Clonable 인터페이스를 명시적으로 구현하는 이유는 클래스 설계자가 복제를 허용한다는 의도적인 표시를 하기 위함입니다.

 

클래스 설계자가 복제를 허용하지 않으면 clone() 메서드를 호출할 때 CloneNotSupportedException 예외가 발생하여 복제가 실패됩니다. clone()은 CloneNotSupportedException 예외 처리가 필요한 메서드이기 때문에 try - catch 구문이 필요합니다.

 

다음 예제를 보면 Member 클래스가 Clonable 인터페이스를 구현했기 때문에 getMember() 메서드에서 clone() 메서드로 자신을 복제한 후, 복제한 객체를 외부로 리턴할 수 있습니다. 그리고 원본 Member를 복제한 후, 복제 Member의 password, age, myPhone의 company 필드 값을 변경해 보았습니다.

//SmartPhone.java
package Example;

public class SmartPhone {
	public String company;
	
	public SmartPhone(String company) {
		this.company = company;
	}
}
//Member.java
package Example;

public class Member implements Cloneable {
	public String id;
	public String name;
	public String passward;
	public int age;
	public boolean isAdult;
	public SmartPhone myPhone;
	
	public Member(String id, String name, String passward, int age, boolean isAdult, SmartPhone myPhone) {
		this.id = id;
		this.name = name;
		this.passward = passward;
		this.age = age;
		this.isAdult = isAdult;
		this.myPhone = myPhone;
	}
	
	public Member getMember() {
		Member cloned = null;
		try {
			cloned = (Member) clone();
		} catch(CloneNotSupportedException e) {
			System.out.println("CloneNotSupportedException 예외 발생");
		}
		return cloned;
	}
}

 

//Main.java
package Example;

public class Main {
	public static void main(String[] args) {
		//원본 객체 생성
		Member original = new Member("KOEY", "홍길동", "123456", 25, true, new SmartPhone("samsung"));
		
		//복제 객체를 얻은 후에 패스워드, 나이, 스마트폰 제조사 변경
		Member cloned = original.getMember();
		cloned.passward = "7890";
		cloned.age = 30;
		cloned.myPhone.company = "LG";
		
		System.out.println("[복제 객체의 필드 값]");
		System.out.println("id : " + cloned.id);
		System.out.println("name : " + cloned.name);
		System.out.println("passward : " + cloned.passward);
		System.out.println("age : " + cloned.age);
		System.out.println("isAdult : " + cloned.isAdult);
		System.out.println("company : " + cloned.myPhone.company);
		
		System.out.println();
		
		System.out.println("[원본 객체의 필드 값]");
		System.out.println("id : " + original.id);
		System.out.println("name : " + original.name);
		System.out.println("passward : " + original.passward);
		System.out.println("age : " + original.age);
		System.out.println("isAdult : " + original.isAdult);
		System.out.println("company : " + original.myPhone.company);
	}
}

/*
실행결과

[복제 객체의 필드 값]
id : KOEY
name : 홍길동
passward : 7890
age : 30
isAdult : true
company : LG

[원본 객체의 필드 값]
id : KOEY
name : 홍길동
passward : 123456
age : 25
isAdult : true
company : LG

*/

실행결과를 보면 원본 객체의 password와 age는 변화가 없지만, company는 바뀐 것을 볼 수 있습니다. age의 타입은 int 타입으로 기본 타입입니다. 따라서 이는 얕은 복사를 해도 복사본이 제대로 생성되지만, SmartPhone 타입은 참조 타입이므로 주소만 복사를 하게 되어 복제된 객체와 원본 객체는 같은 myPhone 객체를 참조하게 됩니다. 그래서 복제 객체를 통해 myPhone에 접근하여 company 값을 바꾸었을 때, 원본 객체에서도 값이 변하게 된 것입니다. 이것이 얕은 복사의 단점입니다.

 

사실 password의 타입인 String도 참조 타입입니다. 그런데 password의 경우 복제된 객체에서 값을 변경해도 원본 객체의 값은 변경되지 않았습니다. 이유는 잘 모르겠으나 제가 짐작하기로는 String이라는 클래스 자체가 이런 부분을 방지하고 기본 타입처럼 사용하기 편하게 하기 위해 원천적으로 보완해둔 것 같습니다. String 클래스에도 문자열을 저장하기 위한 배열이 필드로서 선언되어 있을 것으로 생각됩니다. 하지만 이 필드에 직접적으로 접근하여 값을 변경시킬 수 없도록 만들어 둔 것 같습니다. String 객체에 다른 문자열을 저장하고자 하면 새로운 객체를 생성하도록 하기 때문에 복제된 객체에서 String 타입의 값을 바꾸어도 원본 객체에는 영향을 끼치지 않습니다. 따라서 적어도 String 타입만큼은 객체를 복사할 때 기본 타입처럼 생각해도 좋을 것 같습니다.

 

4. 2. 깊은 복제(deep clone)

깊은 복제란 참조하고 있는 객체도 복제하는 것을 말합니다. 깊은 복제를 하려면 Object의 clone() 메서드를 오버 라이딩해서 참조 객체를 복제하는 코드를 직접 작성해야 합니다. 얕은 복제에서 보였던 예제를 수정하여 깊은 복제를 하도록 해보겠습니다.

//Member.java
package Example;

public class Member implements Cloneable {
	public String id;
	public String name;
	public String passward;
	public int age;
	public boolean isAdult;
	public SmartPhone myPhone;
	
	public Member(String id, String name, String passward, int age, boolean isAdult, SmartPhone myPhone) {
		this.id = id;
		this.name = name;
		this.passward = passward;
		this.age = age;
		this.isAdult = isAdult;
		this.myPhone = myPhone;
	}
	
	public Member getMember() {
		Member cloned = null;
		try {
			cloned = (Member) clone();
		} catch(CloneNotSupportedException e) {
			System.out.println("CloneNotSupportedException 예외 발생");
		}
		return cloned;
	}
	protected Object clone() throws CloneNotSupportedException {    //추가된 코드
		Member member = (Member) super.clone();
		member.myPhone = new SmartPhone(myPhone.company);
		return member;
	}
}
//Main.java
package Example;

public class Main {
	public static void main(String[] args) {
		//원본 객체 생성
		Member original = new Member("KOEY", "홍길동", "123456", 25, true, new SmartPhone("samsung"));
		
		//복제 객체를 얻은 후에 패스워드, 나이, 스마트폰 제조사 변경
		Member cloned = original.getMember();
		cloned.passward = "7890";
		cloned.age = 30;
		cloned.myPhone.company = "LG";
		
		System.out.println("[복제 객체의 필드 값]");
		System.out.println("id : " + cloned.id);
		System.out.println("name : " + cloned.name);
		System.out.println("passward : " + cloned.passward);
		System.out.println("age : " + cloned.age);
		System.out.println("isAdult : " + cloned.isAdult);
		System.out.println("company : " + cloned.myPhone.company);
		
		System.out.println();
		
		System.out.println("[원본 객체의 필드 값]");
		System.out.println("id : " + original.id);
		System.out.println("name : " + original.name);
		System.out.println("passward : " + original.passward);
		System.out.println("age : " + original.age);
		System.out.println("isAdult : " + original.isAdult);
		System.out.println("company : " + original.myPhone.company);
	}
}

/*
실행결과

[복제 객체의 필드 값]
id : KOEY
name : 홍길동
passward : 7890
age : 30
isAdult : true
company : LG

[원본 객체의 필드 값]
id : KOEY
name : 홍길동
passward : 123456
age : 25
isAdult : true
company : samsung

*/

복제할 때 myPhone에 대해서는 동일한 멤버를 가지는 다른 객체를 생성하도록 했습니다. 이로써 복제된 객체와 원본 객체는 서로 다른 myPhone 객체를 가지게 되고 실행결과에서 보이듯이 복제된 객체에서 myPhone의 company를 바꿔도 원본 객체의 company는 바뀌지 않았습니다.

 

5. 객체 소멸자 : finalize()

참조하지 않는 배열이나 객체는 쓰레기 수집기가 힙 영역에서 자동적으로 소멸시킵니다. 쓰레기 수집기는 객체를 소멸하기 직전에 마지막으로 객체의 소멸자를 실행시킵니다. 소멸자는 Object의 finalize() 메서드를 말하는데, 기본적으로 실행내용은 없습니다. 만약 객체가 소멸되기 전에 마지막으로 사용했던 자원(데이터 연결, 파일 등)을 닫고 싶거나, 중요한 데이터를 저장하고 싶다면 Object의 finalize() 메서드를 오버 라이딩할 수 있습니다. 다음은 finalize() 메서드를 오버 라이딩한 클래스입니다. finalize() 메서드가 실행되면 번호를 출력하게 해서 어떤 객체가 소멸되는지 확인할 수 있도록 했습니다.

//Counter.java
package Example;

public class Counter {
	private int number;
	
	public Counter(int number) {
		this.number = number;
	}
	
	@Override
	protected void finalize() throws Throwable {
		System.out.println(number + "번 객체의 finalize()가 실행됨");
	}
}
//Main.java
package Example;

public class Main {
	public static void main(String[] args) {
		Counter counter = null;
		for(int i = 1; i <= 50; i++) {
			counter = new Counter(i);
			counter = null;
			System.gc();    //쓰레기 수집기 실행 요청
		}
	}
}

/*
실행결과

1번 객체의 finalize()가 실행됨
24번 객체의 finalize()가 실행됨
26번 객체의 finalize()가 실행됨
31번 객체의 finalize()가 실행됨
33번 객체의 finalize()가 실행됨
34번 객체의 finalize()가 실행됨
38번 객체의 finalize()가 실행됨
39번 객체의 finalize()가 실행됨
41번 객체의 finalize()가 실행됨
45번 객체의 finalize()가 실행됨
46번 객체의 finalize()가 실행됨
49번 객체의 finalize()가 실행됨
50번 객체의 finalize()가 실행됨
47번 객체의 finalize()가 실행됨
48번 객체의 finalize()가 실행됨
43번 객체의 finalize()가 실행됨
44번 객체의 finalize()가 실행됨
42번 객체의 finalize()가 실행됨
40번 객체의 finalize()가 실행됨
36번 객체의 finalize()가 실행됨
37번 객체의 finalize()가 실행됨
35번 객체의 finalize()가 실행됨
32번 객체의 finalize()가 실행됨
28번 객체의 finalize()가 실행됨
29번 객체의 finalize()가 실행됨
30번 객체의 finalize()가 실행됨
27번 객체의 finalize()가 실행됨
25번 객체의 finalize()가 실행됨
23번 객체의 finalize()가 실행됨
22번 객체의 finalize()가 실행됨
21번 객체의 finalize()가 실행됨
20번 객체의 finalize()가 실행됨
19번 객체의 finalize()가 실행됨
18번 객체의 finalize()가 실행됨
17번 객체의 finalize()가 실행됨
16번 객체의 finalize()가 실행됨
14번 객체의 finalize()가 실행됨
15번 객체의 finalize()가 실행됨
12번 객체의 finalize()가 실행됨
13번 객체의 finalize()가 실행됨
11번 객체의 finalize()가 실행됨
10번 객체의 finalize()가 실행됨

*/

한 두 개의 객체를 쓰레기로 만들었다고 해서 쓰레기 수집기가 곧바로 실행되는 것은 아니기 때문에 반복해서 객체를 생성하고 쓰레기로 만들었습니다. 그리고 System.gc()를 호출해서 쓰레기 수집기를 가급적 빨리 실행해 달라고 JVM에게 요청했습니다.

 

실행결과를 보면 순서대로 소멸시키지 않고, 무작위로 소멸시키는 것을 볼 수 있습니다. 그리고 전부 소멸시키는 것이 아니라 메모리의 상태를 보고 일부만 소멸시킵니다. 쓰레기 수집기는 메모리가 부족할 때, 그리고 CPU가 한가할 때 JVM에 의해서 자동으로 실행됩니다. 그렇기 때문에 finalize() 메서드가 호출되는 시점은 명확하지 않습니다. 프로그램이 종료될 때 즉시 자원을 해제하거나 즉시 데이터를 최종 저장해야 한다면, 일반 메서드에서 작성하고 프로그램이 종료될 때 명시적으로 메서드를 호출하는 것이 좋습니다.

'공부 일지 > JAVA 공부 일지' 카테고리의 다른 글

자바, System 클래스  (0) 2021.04.20
자바, Objects 클래스  (0) 2021.04.20
자바, java.lang 패키지와 java.util 패키지  (0) 2021.04.14
자바, API 도큐먼트  (0) 2021.04.13
자바, 예외 정보 얻기  (0) 2021.04.13
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
TAG
more
«   2024/10   »
1 2 3 4 5
6 7 8 9 10 11 12
13 14 15 16 17 18 19
20 21 22 23 24 25 26
27 28 29 30 31
글 보관함