상속
자바에서 상속이란 필수적이다. 여러분이 상속하건 하지 않았건 기본적인 상속을 하게 된다.
1 2 3 | package org.opentutorials.javatutorials.progenitor; class O {} |
위의 코드는 아래와 코드가 같다.
1 2 3 | package org.opentutorials.javatutorials.progenitor; class O extends Object {} |
자바에서 모든 클래스는 사실 Object를 암시적으로 상속받고 있는 것이다. 그런 점에서 Object는 모든 클래스의 조상이라고 할 수 있다. 그 이유는 모든 클래스가 공통으로 포함하고 있어야 하는 기능을 제공하기 위해서다.
API 문서를 보자.
http://docs.oracle.com/javase/7/docs/api/java/lang/Object.html
메소드의 목록을 살펴보자.
위의 그림은 Object 클래스가 가지고 있는 메소드를 보여준다. 다시 말해서 자바의 객체는 위의 메소드들을 반드시 가지고 있다고 할 수 있다. 이 중에 중요하면서 입문 단계에서 이해할 수 있는 API들을 살펴보자.
toString
toString은 객체를 문자로 표현하는 메소드이다. 기본 예제인 계산기 코드를 보자.
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 | 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을 화면에 출력하고 있다.
1 | System.out.println(c1); |
필자의 결과는 아래와 같다. @ 뒤의 내용은 각자 다를 것이다.
1 | org.opentutorials.javatutorials.progenitor.Calculator @11be650f |
이것은 인스턴스 c1이 클래스 Calculator의 인스턴스라는 의미다. @ 뒤의 내용은 인스턴스에 대한 고유한 식별 값이라고 생각하자.
위의 정보도 유용한 정보이지만 클래스 설계자의 필요에 따라서 toString의 결과를 더욱 유용하게 만들 수 있다. 예를들어 계산기 인스턴스의 left, right 값을 알 수 있다면 개발을 좀 더 편하게 할 수 있을 것이다.
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 32 33 | 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 String toString(){ return "left : " + this .left + ", right : " + this .right; } } public class CalculatorDemo { public static void main(String[] args) { Calculator c1 = new Calculator(); c1.setOprands( 10 , 20 ); System.out.println(c1); System.out.println(c1.toString()); } } |
차이점
실행결과
1 2 | left : 10, right : 20 left : 10, right : 20 |
클래스 Calculator에 toString을 재정의(overiding)했다. 그리고 인스턴스를 System.out.println의 인자로 전달하니까 toString을 명시적으로 호출하지 않았음에도 동일한 효과가 나고 있다. toString 메소드는 자바에서 특별히 취급하는 메소드다. toString을 직접 호출하지 않아도 어떤 객체를 System.out.print로 호출하면 자동으로 toString이 호출되도록 약속되어 있다.
이를 통해서 인스턴스 c1의 상태를 쉽게 파악할 수 있게 되었다.
equals
equals는 객체와 객체가 같은 것인지를 비교하는 API이다. 객체 간에 같고 다름은 필요에 따라서 달라질 수 있기 때문이다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | 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)); } } |
결과는 아래와 같다.
1 2 | false true |
아래 코드를 보자.
1 | System.out.println(s1 == s2); |
결과는 false다.
그 이유는 s1과 s2가 서로 다른 객체이기 때문이다. 어찌 보면 당연한 결과다. 하지만 두 개의 객체가 논리적으로는 egoing이라는 값을 가지고 있기 때문에 객체를 만든 필자는 저 두 개의 객체가 같은 객체로 간주 되었으면 좋겠다. 이럴 때 클래스 Object의 메소드 equals를 overiding하면 된다.
1 2 3 4 | public boolean equals(Object obj) { Student _obj = (Student)obj; return name == _obj.name; } |
위의 코드 (Student)obj 는 메소드 equals로 전달된 obj의 데이터 타입이 Object이기 때문에 이를 Student 타입으로 형 변환하는 코드다. 아래 코드를 통해서 현재 객체의 변수 name과 equals의 인자로 전달된 객체의 변수 name을 비교한 결과를 Boolean 값으로 리턴하고 있다. 이 값에 따라서 두 개의 객체는 같거나 다른 것이 된다.
1 | return name == _obj.name; |
그런데 eqauls를 제대로 사용하기 위해서는 hashCode라는 클래스도 함께 구현해야 한다. 하지만 이에 대한 이야기는 우리 수업의 범위를 넘어서고 그 효용(사용 빈도)도 높지 않기 때문에 더 이상 설명을 하지 않겠다. 하지만 이 메소드의 취지를 이해하는 것은 또한 중요하기 때문에 언급을 하지 않을 수는 없었다. 메소드 equals에 대해서 필자가 권고하는 입장은 아래와 같다.
1. 객체 간에 동일성을 비교하고 싶을 때는 ==를 사용하지 말고 equals를 이용하자.
2. equals를 직접 구현해야 한다면 hashCode도 함께 구현해야 함을 알고 이에 대한 분명한 학습을 한 후에 구현하자.
3. equals를 직접 구현해야 한다면 eclipse와 같은 개발도구들은 equals와 hashCode를 자동으로 생성해주는 기능을 가지고 있다. 이 기능을 이용하는 것을 고려해보자. 아래 그림을 참고하자.
4. 그 이유가 분명하지 않다면 비교 연산자 == 은 원시 데이터형을 비교할 때만 사용하자.
finalize
finalize는 객체가 소멸될 때 호출되기로 약속된 메소드이다. 여기서는 이 메소드의 취지만 이해하면 된다. 많은 자바의 전문가들이 이 메소드의 사용을 만류하고 있다.
이 메소드 보다는 가비지 컬렉션(garbage collection)에 대해서 알아보자. 인스턴스를 만드는 것은 내부적으로는 컴퓨터의 메모리를 사용하는 것이다. 여기서 말하는 메모리는 RAM을 의미한다. 램은 가장 빠른 저장 장치이기 때문에 컴퓨터 프로그램들은 이 램에 저장된 후에 동작하게 된다. 하지만 램은 가격이 비싸고 용량이 적기 때문에 램은 컴퓨터에서 가장 소중한 저장 장치라고 할 수 있다. 그러므로 램의 적게 사용하는 프로그램이 좋은 프로그램이다. 그런 이유로 많은 프로그래밍 언어들이 램을 효율적으로 사용하기 위해서 더 이상 사용하지 않는 데이터를 램에서 제거할 수 있는 방법들을 제공한다.
하지만 자바에서는 이러한 방법이 제한적으로 제공되고 있는데 그것은 자동으로 해주기 때문이다. 이 작업을 자동화한 것을 가비지 컬렉션이라고 한다. 이를테면 어떤 인스턴스를 만들었고, 그것을 변수에 담았다. 그런데 그 변수를 사용하는 곳이 더 이상 없다면 이 변수와 변수에 담겨있는 인스턴스는 더 이상 메모리에 머물고 있을 필요가 없는 것이다. 자바는 이를 감지하고 자동으로 쓰지 않은 데이터를 삭제한다. 따라서 개발자가 사용하지 않는 데이터를 직접 삭제하는 작업을 하지 않아도 되는 것이다. 이것은 어려운 메모리 관리로부터 개발자들의 부담을 경감시킨 도약이라고 할 수 있다.
그럼에도 불구하고 좋은 에플리케이션을 만들기 위해서는 가비지 컬렉션에 대한 이해는 필요하다. 하지만 이는 우리 수업의 취지를 벗어나기 때문에 링크로 대체하겠다.
Java Garbage collection(NHN Hello world 블로그)
clone
clone은 복제라는 뜻이다. 어떤 객체가 있을 때 그 객체와 똑같은 객체를 복제해주는 기능이 clone 메소드의 역할이다. 예를 보자.
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 | 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(s1.name); System.out.println(s2.name); } catch (CloneNotSupportedException e) { e.printStackTrace(); } } } |
차이점
Student 예를 조금 변형했다. 결과는 아래와 같다. 복사에 성공했다.
1 2 | egoing egoing |
위의 코드에서 클래스 Student가 인터페이스 Cloneable을 구현하고 있는 것을 주의 깊게 살펴보자. 인터페이스 Cloneable의 코드는 실제 내용은 아래와 같다.
1 | public interface Cloneable {} |
비어있는 인터페이스다. 그럼에도 불구하고 이것을 사용한 이유는 클래스 Student가 복제 가능하다는 것을 표시하기 위한 것이다. 만약 이 인터페이스를 구현하지 않고 있는 클래스에 대한 복제를 시도하면 오류가 발생할 것이다.
만약 복사를 보다 정교하게 하고 싶다면 오버라이딩 한 메소드 clone를 직접 구현하면 된다. 하지만 이에 대한 논의는 우리 수업의 범위를 애매하게 넘어서는 것이기 때문에 필자는 설명하지 않는 것을 택하겠다.
이해해야 하는 것과 알아야 하는 것
Object 클래스를 놓고 생각해보자. 이 클래스가 모든 클래스의 부모라는 사실은 이해의 영역이 아니라 약속의 영역이다. 즉 자바를 만든 측과 자바를 사용하는 측의 약속이다. 그리고 이 클래스가 clone이나 toString과 같은 메소드를 가지고 있다는 것 또한 이해의 영역이 아니라 숙지해야 하는 영역이다. 한편 모든 클래스가 toString을 사용할 수 있고 또한 이 메소드를 새롭게 재정의 할 수 있다는 점은 이해의 영역이다. 어떤 지식을 배울 때는 이해해야 하는 것과 그냥 알아야 하는 것을 잘 분별하는 것이 중요하다.