최고의 슈퍼 클래스, 1 부

숙련 된 Java 개발자는 종종 초보자가 혼란스러워하는 당연한 Java 기능을 받아들입니다. 예를 들어, 초보자는 Object수업 에 대해 혼란 스러울 수 있습니다 . 이 게시물은 제가 Object및 그 방법 에 대한 질문을 제시하고 답변하는 3 부작 시리즈를 시작 합니다.

킹 개체

Q :Object 수업 은 무엇입니까 ?

A :Object 에 기억되는 클래스, java.lang패키지 (을 제외한 모든 자바 클래스의 궁극적 인 슈퍼 클래스입니다 Object). 또한 배열은 Object. 그러나 인터페이스가 확장되지 않는 ObjectJava 언어 사양의 섹션 9.6.3.4에서 지적되는 : 고려 ... 그 인터페이스가없는 동안 Object슈퍼로 ... .

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()할 수 없습니다.

Q :Object 클래스를 명시 적으로 확장 할 수 있습니까 ?

A : 예, 명시 적으로 Object. 예를 들어, 목록 1을 확인하십시오.

Listing 1. 명시 적으로 확장 Object

import java.lang.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()); } }

Listing 1 ( javac Employee.java)을 컴파일 하고 결과 Employee.class파일 ( java Employee)을 실행 John Doe하면 출력으로 관찰 할 수 있습니다 .

컴파일러는 java.lang패키지 에서 유형을 자동으로 가져 오므로 import java.lang.Object;명령문이 필요하지 않습니다. 또한 Java는 명시 적으로 확장하도록 강제하지 않습니다 Object. 그렇다면 ObjectJava가 클래스 확장을 단일 클래스로 제한하기 때문에 다른 클래스를 확장 할 수 없습니다 . 따라서 일반적으로 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의 Employee클래스 Object는 메소드를 확장 하고 상속합니다.

개체 복제

Q :clone()방법 은 무엇을 수행합니까?

A :clone() 방법을 만들고,이 메소드가 호출되는 객체의 복사본을 반환합니다.

Q : 방법은 어떻게 clone()작동합니까?

A : 네이티브 메서드로 Object구현 clone()됩니다. 즉, 코드가 네이티브 라이브러리에 저장됩니다. 이 코드가 실행, 그것은 그것이 구현하고 있는지 확인하기 위해 호출하는 객체의 클래스 (또는 슈퍼 클래스)를 확인하면 java.lang.Cloneable인터페이스 - Object구현하지 않습니다 Cloneable. 이 인터페이스가 구현되지 않은 경우 확인 된 예외 인을 clone()throw java.lang.CloneNotSupportedException합니다 (호출 된 메서드의 헤더에 throws 절을 추가하여 메서드 호출 스택을 처리하거나 전달해야 함 clone()). 이 인터페이스가 구현되면 clone()새 개체를 할당하고 호출 개체의 필드 값을 새 개체의 해당 필드에 복사하고 새 개체에 대한 참조를 반환합니다.

Q :clone() 개체를 복제하는 메서드를 호출하려면 어떻게해야 합니까?

A : 객체 참조가 주어지면이 참조를 호출 clone()하고 반환 Object된 객체를 복제중인 객체 유형으로 캐스트합니다 . 목록 3은 예를 보여줍니다.

Listing 3. 객체 복제

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

목록 3은 인터페이스 CloneDemo를 구현 하는 클래스를 선언합니다 Cloneable. 이 인터페이스를 구현해야합니다. 그렇지 않으면 Objectclone()메서드를 호출 하면 CloneNotSupportedException인스턴스 가 throw 됩니다.

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

Q :clone() 메서드 를 재정의해야하는 이유는 무엇 입니까?

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

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

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

Listing 4 declares a Data class whose instances are to be cloned. This class implements the Cloneable interface to prevent CloneNotSupportedException from being thrown when the clone() method is called, declares int-based instance field x, and overrides the clone() method. This method executes super.clone() to invoke its superclass's (Object's, in this example) clone() method. The overriding clone() method identifies CloneNotSupportedException in its throws clause.

Listing 4 also declares a CloneDemo class that instantiates Data, initializes its instance field, outputs the value of this instance's instance field, clones the Data instance, and outputs this instance's instance field value.

Compile Listing 4 (javac CloneDemo.java) and run the application (java CloneDemo). You should observe the following output:

data.x = 5 data2.x = 5

Q: What is shallow cloning?

A:Shallow cloning (also known as shallow copying) is the duplication of an object's fields without duplicating any objects that are referenced from the object's reference fields (if it has any). Listings 3 and 4 demonstrate shallow cloning. Each of the cd-, cd2-, data-, and data2-referenced fields identifies an object that has its own copy of the int-based x field.

Shallow cloning works well when all fields are of primitive type and (in many cases) when any reference fields refer to immutable (unchangeable) objects. However, if any referenced objects are mutable, changes made to any one of these objects can be seen by the original object and its clone(s). Listing 5 presents a demonstration.

Listing 5. Demonstrating the problem with shallow cloning in a reference field context

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; } } public class CloneDemo { public static void main(String[] args) throws CloneNotSupportedException { Employee e = new Employee("John Doe", 49, new Address("Denver")); System.out.printf("%s: %d: %s%n", e.getName(), e.getAge(), e.getAddress().getCity()); Employee e2 = (Employee) e.clone(); System.out.printf("%s: %d: %s%n", e2.getName(), e2.getAge(), e2.getAddress().getCity()); e.getAddress().setCity("Chicago"); System.out.printf("%s: %d: %s%n", e.getName(), e.getAge(), e.getAddress().getCity()); System.out.printf("%s: %d: %s%n", e2.getName(), e2.getAge(), e2.getAddress().getCity()); } }

Listing 5 presents Employee, Address, and CloneDemo classes. Employee declares name, age, and address fields; and is cloneable. Address declares an address consisting of a city and its instances are mutable. CloneDemo drives the application.

CloneDemo's main() method creates an Employee object and clones this object. It then changes the city's name in the original Employee object's address field. Because both Employee objects reference the same Address object, the changed city is seen by both objects.

Compile Listing 5 (javac CloneDemo.java) and run this application (java CloneDemo). You should observe the following output:

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

Q: What is deep cloning?

A:Deep cloning (also known as deep copying) is the duplication of an object's fields such that any referenced objects are duplicated. Furthermore, their referenced objects are duplicated -- and so on. For example, Listing 6 refactors Listing 5 to leverage deep cloning. It also demonstrates covariant return types and a more flexible way of cloning.

Listing 6. Deeply cloning the address field

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 Employee clone() throws CloneNotSupportedException { Employee e = (Employee) super.clone(); e.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 Address clone() { return new Address(new String(city)); } String getCity() { return city; } void setCity(String city) { this.city = city; } } public class CloneDemo { public static void main(String[] args) throws CloneNotSupportedException { Employee e = new Employee("John Doe", 49, new Address("Denver")); System.out.printf("%s: %d: %s%n", e.getName(), e.getAge(), e.getAddress().getCity()); Employee e2 = (Employee) e.clone(); System.out.printf("%s: %d: %s%n", e2.getName(), e2.getAge(), e2.getAddress().getCity()); e.getAddress().setCity("Chicago"); System.out.printf("%s: %d: %s%n", e.getName(), e.getAge(), e.getAddress().getCity()); System.out.printf("%s: %d: %s%n", e2.getName(), e2.getAge(), e2.getAddress().getCity()); } }

Listing 6 leverages Java's support for covariant return types to change the return type of Employee's overriding clone() method from Object to Employee. The advantage is that code external to Employee can clone an Employee object without having to cast this object to the Employee type.

Employee's clone() method first invokes super.clone(), which shallowly copies the name, age, and address fields. It then invokes clone() on the address field to make a duplicate of the referenced Address object.

The Address class overrides the clone() method and reveals a few differences from previous classes that override this method:

  • Address doesn't implement Cloneable. It's not necessary because only Object's clone() method requires that a class implement this interface, and this clone() method isn't being called.
  • 재정의 clone()메서드는 CloneNotSupportedException. 이 확인 된 예외는 호출되지 않은 Objectclone()메서드 에서만 발생합니다 . 따라서 예외는 throws 절을 통해 메서드 호출 스택으로 처리되거나 전달 될 필요가 없습니다.
  • Object클래스에 대해 얕은 복사가 필요하지 않기 때문에 의 clone()메서드가 호출되지 않습니다 (호출이 없음 super.clone()) Address. 복사 할 필드가 하나뿐입니다.

Address개체 를 복제하려면 새 Address개체 를 만들고 city필드 에서 참조하는 개체의 복제본으로 초기화하면됩니다 . Address그런 다음 새 개체가 반환됩니다.