상속
자바에서 상속이란 필수적이다. 여러분이 상속하건 하지 않았건 기본적인 상속을 하게 된다.
package org.opentutorials.javatutorials.progenitor;
class O {}
위의 코드는 아래와 코드가 같다.
class O extends Object {}
자바에서 모든 클래스는 사실 Object를 암시적으로 상속받고 있는 것이다. 그런 점에서 Object는 모든 클래스의 조상이라고 할 수 있다. 그 이유는 모든 클래스가 공통으로 포함하고 있어야 하는 기능을 제공하기 위해서다.
API 문서를 보자.
메소드의 목록을 살펴보자.
위의 그림은 Object 클래스가 가지고 있는 메소드를 보여준다. 다시 말해서 자바의 객체는 위의 메소드들을 반드시 가지고 있다고 할 수 있다. 이 중에 중요하면서 입문 단계에서 이해할 수 있는 API들을 살펴보자.
toString
toString은 객체를 문자로 표현하는 메소드이다. 기본 예제인 계산기 코드를 보자.
package org.opentutorials.javatutorials.progenitor;
class Calculator{
int left, right;
public void setOprands(int left, int right){
this.left = left;
this.right = right;
}
public void sum(){
System.out.println(this.left+this.right);
}
public void avg(){
System.out.println((this.left+this.right)/2);
}
}
public class CalculatorDemo {
public static void main(String[] args) {
Calculator c1 = new Calculator();
c1.setOprands(10, 20);
System.out.println(c1);
}
}
25라인에 아래 코드는 클래스 Calculator의 인스턴스 c1을 화면에 출력하고 있다.
필자의 결과는 아래와 같다. @ 뒤의 내용은 각자 다를 것이다.
org.opentutorials.javatutorials.progenitor.Calculator@11be650f
이것은 인스턴스 c1이 클래스 Calculator의 인스턴스라는 의미다. @ 뒤의 내용은 인스턴스에 대한 고유한 식별 값이라고 생각하자.
위의 정보도 유용한 정보이지만 클래스 설계자의 필요에 따라서 toString의 결과를 더욱 유용하게 만들 수 있다. 예를들어 계산기 인스턴스의 left, right 값을 알 수 있다면 개발을 좀 더 편하게 할 수 있을 것이다.
아래와 같이 Object Class의 toString() 메소드를 오버라이딩 한다.
public String toString(){
return "left : " + this.left + ", right : "+ this.right;
}
실행결과
left : 10, right : 20
left : 10, right : 20
toString 메소드는 자바에서 특별히 취급하는 메소드다. toString을 직접 호출하지 않아도 어떤 객체를 System.out.print로 호출하면 자동으로 toString이 호출되도록 약속되어 있다.
이를 통해서 인스턴스 c1의 상태를 쉽게 파악할 수 있게 되었다.
equals
equals는 객체와 객체가 같은 것인지를 비교하는 API이다. 객체 간에 같고 다름은 필요에 따라서 달라질 수 있
package org.opentutorials.javatutorials.progenitor;
class Student{
String name;
Student(String name){
this.name = name;
}
public boolean equals(Object obj) {
Student _obj = (Student)obj;
return name == _obj.name;
}
}
class ObjectDemo {
public static void main(String[] args) {
Student s1 = new Student("egoing");
Student s2 = new Student("egoing");
System.out.println(s1 == s2);
System.out.println(s1.equals(s2));
}
}
결과는 아래와 같다.
false
true
첫번째 결과가 false인 이유는 s1과 s2가 서로 다른 객체이기 때문이다(객체가 각각 물리적으로 다른 메모리 주소에 위치한다). 어찌 보면 당연한 결과다. 하지만 두 개의 객체가 논리적으로는 egoing이라는 값을 가지고 있기 때문에 객체를 만든 필자는 저 두 개의 객체가 같은 객체로 간주 되었으면 좋겠다. 이럴 때 클래스 Object의 메소드 equals를 overiding하면 된다.
아래 코드를 통해서 현재 객체의 변수 name과 equals의 인자로 전달된 객체의 변수 name을 비교한 결과를 Boolean 값으로 리턴하고 있다. Student 클래스는 이름만 같으면 같은 것으로 취급되도록 한 것이다.
그런데 eqauls를 제대로 사용하기 위해서는 hashCode라는 메소드도 함께 구현해야 한다. 여기서는 equals()가 true 이면 hashCode()의 결과도 같아야 하기 때문이라고만 알아두자.
1. 객체 간에 동일성을 비교하고 싶을 때는 ==를 사용하지 말고 equals를 이용하자.
2. equals를 직접 구현해야 한다면 hashCode도 함께 구현해야 함을 알고 이에 대한 분명한 학습을 한 후에 구현하자.
3. equals를 직접 구현해야 한다면 eclipse와 같은 개발도구들은 equals와 hashCode를 자동으로 생성해주는 기능을 가지고 있다. 이 기능을 이용하는 것을 고려해보자. 아래 그림을 참고하자.
4. 그 이유가 분명하지 않다면 비교 연산자 == 은 원시 데이터형을 비교할 때만 사용하자.
원시 데이터 형(Primitive Data Type)이란 자바에서 기본적으로 제공하는 데이터 타입으로 byte, short, int, long, float, double, boolean, char가 있다. 모두 소문자로 시작하며, 클래스가 아니기 때문에 이러한 데이터 타입들은 new 연산자를 이용해서 생성하지 않아도 사용될 수 있다는 특징이 있다.
finalize
finalize는 객체가 소멸될 때 호출되기로 약속된 메소드이다. 여기서는 이 메소드의 취지만 이해하면 된다. 많은 자바의 전문가들이 이 메소드의 사용을 만류하고 있다.
이 메소드 보다는 가비지 컬렉션(garbage collection)에 대해서 알아보자. 인스턴스를 만드는 것은 내부적으로는 컴퓨터의 메모리를 사용하는 것이다. 여기서 말하는 메모리는 RAM을 의미한다. 램은 가장 빠른 저장 장치이기 때문에 컴퓨터 프로그램들은 이 램에 저장된 후에 동작하게 된다. 하지만 램은 가격이 비싸고 용량이 적기 때문에 램은 컴퓨터에서 가장 소중한 저장 장치라고 할 수 있다. 그러므로 램의 적게 사용하는 프로그램이 좋은 프로그램이다. 그런 이유로 많은 프로그래밍 언어들이 램을 효율적으로 사용하기 위해서 더 이상 사용하지 않는 데이터를 램에서 제거할 수 있는 방법들을 제공한다.
하지만 자바에서는 이러한 방법이 제한적으로 제공되고 있는데 그것은 자동으로 해주기 때문이다. 이 작업을 자동화한 것을 가비지 컬렉션이라고 한다. 이를테면 어떤 인스턴스를 만들었고, 그것을 변수에 담았다. 그런데 그 변수를 사용하는 곳이 더 이상 없다면 이 변수와 변수에 담겨있는 인스턴스는 더 이상 메모리에 머물고 있을 필요가 없는 것이다. 자바는 이를 감지하고 자동으로 쓰지 않은 데이터를 삭제한다. 따라서 개발자가 사용하지 않는 데이터를 직접 삭제하는 작업을 하지 않아도 되는 것이다. 이것은 어려운 메모리 관리로부터 개발자들의 부담을 경감시킨 도약이라고 할 수 있다.
clone
clone은 복제라는 뜻이다. 어떤 객체가 있을 때 그 객체와 똑같은 객체를 복제해주는 기능이 clone 메소드의 역할이다. 예를 보자.
package org.opentutorials.javatutorials.progenitor;
class Student implements Cloneable{
String name;
Student(String name){
this.name = name;
}
protected Object clone() throws CloneNotSupportedException{
return super.clone();
}
}
class ObjectDemo {
public static void main(String[] args) {
Student s1 = new Student("egoing");
try {
Student s2 = (Student)s1.clone();
System.out.println("student 1's name is " + s1.name);
System.out.println("student 2's name is " + s2.name);
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
}
}
위의 코드에서 클래스 Student가 인터페이스 Cloneable을 구현하고 있는 것을 주의 깊게 살펴보자. 인터페이스 Cloneable의 코드의 실제 내용은 아래와 같다.
public interface Cloneable {}
비어있는 인터페이스다. 그럼에도 불구하고 이것을 사용한 이유는 클래스 Student가 복제 가능하다는 것을 표시하기 위한 것이다. 만약 이 인터페이스를 구현하지 않고 있는 클래스에 대한 복제를 시도하면 오류가 발생할 것이다.
이해해야 하는 것과 알아야 하는 것
Object 클래스가 모든 클래스의 부모라는 사실은 이해의 영역이 아니라 약속의 영역이다. 즉 자바를 만든 측과 자바를 사용하는 측의 약속이다. 그리고 이 클래스가 clone이나 toString과 같은 메소드를 가지고 있다는 것 또한 이해의 영역이 아니라 숙지해야 하는 영역이다. 한편 모든 클래스가 toString을 사용할 수 있고 또한 이 메소드를 새롭게 재정의 할 수 있다는 점은 이해의 영역이다. 어떤 지식을 배울 때는 이해해야 하는 것과 그냥 알아야 하는 것을 잘 분별하는 것이 중요하다.