Java의 상속, Part 2 : 객체 및 메서드

Java는 수천 개의 클래스와 기타 참조 유형으로 구성된 표준 클래스 라이브러리를 제공합니다. 기능의 차이에도 불구하고 이러한 유형은 Object클래스를 직접 또는 간접적으로 확장하여 하나의 대규모 상속 계층을 형성합니다 . 이는 생성하는 모든 클래스 및 기타 참조 유형에도 적용됩니다.

Java 상속에 대한이 튜토리얼의 전반부에서는 상속의 기초, 특히 Java  extendssuper키워드를 사용하여 부모 클래스에서 자식 클래스를 파생시키고 부모 클래스 생성자와 메서드를 호출하고 메서드를 재정의하는 방법 등을 설명했습니다. 이제 Java 클래스 상속 계층 구조의 모선 인 java.lang.Object.

학습 Object과 그 방법은 상속에 대한 기능적 이해와 Java 프로그램에서 어떻게 작동하는지 이해하는 데 도움이됩니다. 이러한 방법에 익숙해지면 일반적으로 Java 프로그램을 더 잘 이해할 수 있습니다. 

다운로드 코드 받기이 자습서에서 예제 응용 프로그램의 소스 코드를 다운로드합니다. JavaWorld를 위해 Jeff Friesen이 만들었습니다.

개체 : 자바의 슈퍼 클래스

Object다른 모든 Java 클래스의 루트 클래스 또는 궁극적 인 수퍼 클래스입니다. java.lang패키지에 저장되며 Object다른 모든 클래스가 상속하는 다음 메서드를 선언합니다.

  • protected Object clone()
  • boolean equals(Object obj)
  • protected void finalize()
  • Class getClass()
  • int hashCode()
  • void notify()
  • void notifyAll()
  • String toString()
  • void wait()
  • void wait(long timeout)
  • void wait(long timeout, int nanos)

Java 클래스는 이러한 메서드를 상속하며 선언되지 않은 모든 메서드를 재정의 할 수 있습니다 final. 예를 들어, finaltoString()메서드가 아닌 메서드는 재정의 finalwait()할 수 없지만 재정의 할 수 있습니다 .

이러한 각 메서드를 살펴보고 Java 클래스 컨텍스트에서 특수 작업을 수행 할 수있는 방법을 살펴 보겠습니다. 먼저 Object상속에 대한 기본 규칙과 메커니즘을 고려해 보겠습니다 .

일반 유형

위의 목록에서, 당신은 눈치 챘을 수도 getClass()누구의 Class반환을 입력의 예입니다 제네릭 형식 . 향후 기사에서 제네릭 유형에 대해 설명하겠습니다.

확장 대상 : 예

클래스는 Object목록 1에 설명 된대로 명시 적으로 확장 할 수 있습니다 .

Listing 1. 명시 적으로 확장하는 Object

public class Employee extends Object { private String name; public Employee(String name) { this.name = name; } public String getName() { return name; } public static void main(String[] args) { Employee emp = new Employee("John Doe"); System.out.println(emp.getName()); } }

최대 하나의 다른 클래스를 확장 할 수 있기 때문에 (1 부에서 Java가 클래스 기반 다중 상속을 지원하지 않는다는 것을 기억하십시오) 명시 적으로 확장 할 필요가 없습니다 Object. 그렇지 않으면 다른 클래스를 확장 할 수 없습니다. 따라서 Object목록 2에서 설명한 것처럼 암시 적으로 확장 할 수 있습니다 .

Listing 2. 암시 적으로 확장하는 Object

public class Employee { private String name; public Employee(String name) { this.name = name; } public String getName() { return name; } public static void main(String[] args) { Employee emp = new Employee("John Doe"); System.out.println(emp.getName()); } }

다음과 같이 목록 1 또는 목록 2를 컴파일하십시오.

javac Employee.java

결과 애플리케이션을 실행하십시오.

java Employee

다음 출력을 관찰해야합니다.

John Doe

클래스에 대해 알아보기 : getClass ()

getClass()메서드는 호출 된 모든 개체의 런타임 클래스를 반환합니다. 런타임 클래스는 a로 표시됩니다 Class에서 발견되는 개체, java.lang패키지. ClassJava Reflection API의 시작점으로, Java 프로그래밍의 고급 주제에 대해 배울 때 배울 수 있습니다. 지금은 Java 애플리케이션 Class이 자체 구조에 대해 학습하기 위해 Java Reflection API의 나머지 부분을 사용한다는 것을 알고 있습니다.

클래스 객체 및 정적 동기화 메서드

반환 된 Class객체는 static synchronized표현 된 클래스의 메서드에 의해 잠긴 객체입니다 . 예 : static synchronized void foo() {}. (향후 튜토리얼에서 Java 동기화를 소개하겠습니다.)

객체 복제 : clone ()

clone()메서드는 호출 된 개체의 복사본을 만들고 반환합니다. clone()의 반환 유형이 이므로 반환 Object하는 객체 참조 clone()는 객체 유형의 변수에 해당 참조를 할당하기 전에 객체의 실제 유형으로 캐스트되어야합니다. 목록 3은 복제를 보여주는 애플리케이션을 보여줍니다.

Listing 3. 객체 복제

class CloneDemo implements Cloneable { int x; public static void main(String[] args) throws CloneNotSupportedException { CloneDemo cd = new CloneDemo(); cd.x = 5; System.out.println("cd.x = " + cd.x); CloneDemo cd2 = (CloneDemo) cd.clone(); System.out.println("cd2.x = " + cd2.x); } }

목록 3의 CloneDemo클래스 Cloneablejava.lang패키지 에있는 인터페이스를 구현합니다 . 의 메소드가 클래스 의 인스턴스를 던지는 것을 방지하기 위해 (키워드 Cloneable를 통해 implements) 클래스에 의해 구현됩니다 (에서도 발견됨 ).Objectclone()CloneNotSupportedExceptionjava.lang

CloneDemoint명명 된 단일 기반 인스턴스 필드 와이 클래스를 실행 x하는 main()메서드를 선언합니다 . 메소드 호출 스택 을 전달 main()하는 throws절로 선언됩니다 CloneNotSupportedException.

main()먼저 CloneDemo결과 인스턴스의 복사본을 x에 인스턴스화 하고 초기화합니다 5. 그런 다음 인스턴스의 x값 을 출력 clone()하고이 인스턴스를 호출 CloneDemo하여 참조를 저장 하기 전에 반환 된 객체를로 캐스팅합니다 . 마지막으로 복제본의 x필드 값을 출력합니다 .

목록 3 ( javac CloneDemo.java)을 컴파일 하고 애플리케이션 ( java CloneDemo)을 실행합니다 . 다음 출력을 관찰해야합니다.

cd.x = 5 cd2.x = 5

clone () 재정의

이전 예제는 clone()호출하는 코드 clone()가 복제되는 클래스 ( CloneDemo) 에 있으므로 재정의 할 필요가 없습니다 . 그러나에 대한 호출 clone()이 다른 클래스에있는 경우을 재정의해야합니다 clone(). clone()이 선언 되었기 때문에 클래스를 컴파일하기 전에 재정의하지 않은 경우 protected" clone has protected access in Object "메시지를 받게됩니다 . Listing 4는 재정의를 보여주는 리팩토링 된 Listing 3을 보여준다 clone().

Listing 4. 다른 클래스에서 객체 복제하기

class Data implements Cloneable { int x; @Override public Object clone() throws CloneNotSupportedException { return super.clone(); } } class CloneDemo { public static void main(String[] args) throws CloneNotSupportedException { Data data = new Data(); data.x = 5; System.out.println("data.x = " + data.x); Data data2 = (Data) data.clone(); System.out.println("data2.x = " + data2.x); } }

목록 4는 Data인스턴스가 복제 될 클래스를 선언합니다 . 메서드가 호출 될 때가 throw 되지 않도록 인터페이스를 Data구현합니다 . 그런 다음 기반 인스턴스 field 를 선언 하고 메서드를 재정의합니다 . 이 메서드 는 수퍼 클래스의 (즉, 's) 메서드 를 호출하기 위해 실행 됩니다 . 재정의 메서드 는 해당 절 에서 식별 합니다.CloneableCloneNotSupportedExceptionclone()intxclone()clone()super.clone()Objectclone()clone()CloneNotSupportedExceptionthrows

목록 4는 또한 CloneDemo인스턴스화하고 Data, 인스턴스 필드를 초기화하고, 인스턴스 필드의 값을 출력하고, Data객체를 복제하고, 인스턴스 필드 값을 출력 하는 클래스를 선언 합니다.

목록 4 ( javac CloneDemo.java)를 컴파일 하고 애플리케이션 ( java CloneDemo)을 실행합니다 . 다음 출력을 관찰해야합니다.

data.x = 5 data2.x = 5

얕은 복제

얕은 복제 ( 얕은 복사 라고도 함 )는 해당 객체의 참조 필드에서 참조 된 객체를 복제하지 않고 객체의 필드를 복제하는 것을 말합니다 (참조 필드가있는 경우). 목록 3과 4는 실제로 얕은 복제를 보여줍니다. 의 각 cd, - cd2-, data-, 및 data2-referenced 필드 식별의 자체 복사본이있는 객체 int기반 x필드.

얕은 복제는 모든 필드가 기본 유형이고 (대부분의 경우) 참조 필드가 변경 불가능한 (변경 불가능한 ) 오브젝트 를 참조 할 때 잘 작동 합니다. 그러나 참조 된 객체가 변경 가능한 경우 이러한 객체 중 하나에 대한 변경 사항은 원본 객체와 해당 복제본에서 볼 수 있습니다. 목록 5는이를 보여줍니다.

Listing 5. 참조 필드 컨텍스트에서 얕은 복제의 문제점

class Employee implements Cloneable { private String name; private int age; private Address address; Employee(String name, int age, Address address) { this.name = name; this.age = age; this.address = address; } @Override public Object clone() throws CloneNotSupportedException { return super.clone(); } Address getAddress() { return address; } String getName() { return name; } int getAge() { return age; } } class Address { private String city; Address(String city) { this.city = city; } String getCity() { return city; } void setCity(String city) { this.city = city; } } class CloneDemo { public static void main(String[] args) throws CloneNotSupportedException { Employee e = new Employee("John Doe", 49, new Address("Denver")); System.out.println(e.getName() + ": " + e.getAge() + ": " + e.getAddress().getCity()); Employee e2 = (Employee) e.clone(); System.out.println(e2.getName() + ": " + e2.getAge() + ": " + e2.getAddress().getCity()); e.getAddress().setCity("Chicago"); System.out.println(e.getName() + ": " + e.getAge() + ": " + e.getAddress().getCity()); System.out.println(e2.getName() + ": " + e2.getAge() + ": " + e2.getAddress().getCity()); } }

목록 5는 Employee, AddressCloneDemo클래스를 제공합니다. Employee선언 name, ageaddress필드; 복제 가능합니다. Address도시로 구성된 주소를 선언하고 인스턴스는 변경 가능합니다. CloneDemo애플리케이션을 구동합니다.

CloneDemomain()메서드는 Employee개체를 만들고이 개체를 복제합니다. 그런 다음 원래 Employee개체의 address필드 에서 도시 이름을 변경합니다 . 두 Employee객체가 동일한 Address객체를 참조 하기 때문에 변경된 도시는 두 객체 모두에서 보입니다.

목록 5 ( javac CloneDemo.java)를 컴파일 하고이 애플리케이션 ( java CloneDemo)을 실행합니다 . 다음 출력을 관찰해야합니다.

John Doe: 49: Denver John Doe: 49: Denver John Doe: 49: Chicago John Doe: 49: Chicago

딥 클로닝

전체 복제 ( 딥 복사 라고도 함 )는 참조 된 개체가 복제되도록 개체의 필드를 복제하는 것을 말합니다. 또한 참조 된 개체의 참조 된 개체가 복제됩니다. Listing 6 리팩터링 Listing 5는 딥 클로닝을 보여줍니다.

Listing 6. 주소 필드 딥 클로닝

class Employee implements Cloneable { private String name; private int age; private Address address; Employee(String name, int age, Address address) { this.name = name; this.age = age; this.address = address; } @Override public Object clone() throws CloneNotSupportedException { Employee e = (Employee) super.clone(); e.address = (Address) address.clone(); return e; } Address getAddress() { return address; } String getName() { return name; } int getAge() { return age; } } class Address { private String city; Address(String city) { this.city = city; } @Override public Object clone() { return new Address(new String(city)); } String getCity() { return city; } void setCity(String city) { this.city = city; } } class CloneDemo { public static void main(String[] args) throws CloneNotSupportedException { Employee e = new Employee("John Doe", 49, new Address("Denver")); System.out.println(e.getName() + ": " + e.getAge() + ": " + e.getAddress().getCity()); Employee e2 = (Employee) e.clone(); System.out.println(e2.getName() + ": " + e2.getAge() + ": " + e2.getAddress().getCity()); e.getAddress().setCity("Chicago"); System.out.println(e.getName() + ": " + e.getAge() + ": " + e.getAddress().getCity()); System.out.println(e2.getName() + ": " + e2.getAge() + ": " + e2.getAddress().getCity()); } }

6은 그 목록 Employeeclone()메소드 호출 제 super.clone()얕게 복사 name, ageaddress필드. 그런 다음 호출 clone()상의 address참조의 중복 만들기 위해 필드 Address개체를. Address를 겹쳐 clone()방법은 이전의 클래스가 재정이 방법에서 몇 가지 차이점을 보여준다 :

  • Address구현하지 않습니다 Cloneable. Objectclone()메서드 만 클래스가이 인터페이스를 구현해야하고이 clone()메서드가 호출되지 않기 때문에 필요 하지 않습니다.
  • 재정의 clone()메서드는 CloneNotSupportedException. 이 예외는 호출되지 않은 Objectclone()메서드 에서만 발생합니다 . 따라서 예외는 throws 절을 통해 메서드 호출 스택으로 처리되거나 전달 될 필요가 없습니다.
  • Object클래스에 대해 얕은 복사가 필요하지 않기 때문에 의 clone()메서드가 호출되지 않습니다 (호출이 없음 super.clone()) Address. 복사 할 필드가 하나뿐입니다.