Superclass terhebat, Bahagian 1

Pembangun Java yang berpengalaman sering menganggap fitur Java yang dianggap membingungkan pendatang baru. Contohnya, seorang pemula mungkin keliru mengenai Objectkelas. Catatan ini melancarkan siri tiga bahagian di mana saya mengemukakan dan menjawab soalan mengenai Objectdan kaedahnya.

Objek Raja

Q: Apa Objectkelasnya?

A: The Objectkelas, yang disimpan di dalam java.langpakej, adalah superclass muktamad semua kelas Java (kecuali Object). Juga, tatasusunan meluas Object. Walau bagaimanapun, antara muka tidak melanjutkan Object, yang dinyatakan dalam Seksyen 9.6.3.4 Java Bahasa Spesifikasi: ... menganggap bahawa manakala antara muka yang tidak mempunyai Objectsebagai supertype yang ... .

Object menyatakan kaedah berikut, yang akan saya bincangkan dengan lengkap dalam catatan ini dan dalam siri ini:

  • 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)

Kelas Java mewarisi kaedah ini dan dapat menggantikan kaedah yang tidak dinyatakan final. Contohnya, finaltoString()kaedah bukan kaedah boleh diganti, sedangkan finalwait()kaedah tidak boleh ditolak.

Q: Bolehkah saya melanjutkan Objectkelas secara eksplisit ?

J: Ya, anda boleh memperluaskan secara jelas Object. Contohnya, lihat Penyenaraian 1.

Penyenaraian 1. Meluaskan secara jelas 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()); } }

Anda boleh menyusun Penyenaraian 1 ( javac Employee.java) dan menjalankan Employee.classfail yang dihasilkan ( java Employee), dan anda akan melihatnya John Doesebagai output.

Kerana penyusun mengimport jenis dari java.langpakej secara automatik , import java.lang.Object;pernyataan itu tidak diperlukan. Juga, Java tidak memaksa anda untuk memperluas secara eksplisit Object. Sekiranya ia berlaku, anda tidak akan dapat melanjutkan kelas selain daripada ObjectJava menghadkan peluasan kelas kepada satu kelas. Oleh itu, anda biasanya akan meluas Objectsecara tersirat, seperti yang ditunjukkan dalam Penyenaraian 2.

Penyenaraian 2. Memanjang secara tidak langsung 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()); } }

Seperti dalam Penyenaraian 1, Employeekelas Penyenaraian 2 meluas Objectdan mewarisi kaedahnya.

Objek pengklonan

S: Apakah clone()kaedah yang dicapai?

A: The clone()kaedah mencipta dan mengembalikan salinan objek di mana kaedah ini dipanggil.

S: Bagaimana clone()kaedah ini berfungsi?

J:Object diimplementasikan clone()sebagai kaedah asli, yang bermaksud bahawa kodnya disimpan di perpustakaan asli. Apabila kod ini dilaksanakan, ia memeriksa kelas (atau superclass) objek yang diminta untuk melihat apakah ia menerapkan java.lang.Cloneableantara muka - Objecttidak dilaksanakan Cloneable. Sekiranya antara muka ini tidak dilaksanakan, clone()lempar java.lang.CloneNotSupportedException, yang merupakan pengecualian yang diperiksa (ia harus ditangani atau dilewatkan kaedah panggilan-tumpukan dengan menambahkan klausa lemparan ke tajuk kaedah yang clone()digunakan). Sekiranya antara muka ini dilaksanakan, clone()peruntukkan objek baru dan salin nilai medan objek panggilan ke medan setara objek baru, dan mengembalikan rujukan ke objek baru.

S: Bagaimana saya menggunakan clone()kaedah untuk mengklon objek?

J: Mengingat rujukan objek, gunakan clone()rujukan ini dan lemparkan objek yang dikembalikan dari Objectjenis objek yang diklon. Penyenaraian 3 memberikan contoh.

Penyenaraian 3. Mengklonkan objek

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); } }

Penyenaraian 3 menyatakan CloneDemokelas yang melaksanakan Cloneableantara muka. Antara muka ini mesti dilaksanakan atau doa Object's clone()kaedah akan menyebabkan dibuang CloneNotSupportedExceptionmisalnya.

CloneDemomengisytiharkan intbidang contoh berdasarkan tunggal bernama xdan main()kaedah yang menjalankan kelas ini. main()dinyatakan dengan klausa lemparan yang melepasi CloneNotSupportedExceptiontimbunan panggilan kaedah.

main()pertama memberi contoh CloneDemodan menginisialisasi salinan contoh yang dihasilkan xke 5. Ia kemudian mengeluarkan nilai instance xdan memanggil clone()instance ini, melemparkan objek yang dikembalikan CloneDemosebelum menyimpan rujukannya. Akhirnya, ia menghasilkan xnilai medan klon .

Susun Penyenaraian 3 ( javac CloneDemo.java) dan jalankan aplikasi ( java CloneDemo). Anda harus memerhatikan output berikut:

cd.x = 5 cd2.x = 5

S: Mengapa saya perlu mengganti clone()kaedah ini?

J: Contoh sebelumnya tidak perlu mengesampingkan clone()kaedah kerana kod yang meminta clone()terletak di kelas yang diklon (iaitu CloneDemokelas). Walau bagaimanapun, jika clone()permintaan tersebut berada di kelas yang berbeza, anda perlu mengganti clone(). Jika tidak, anda akan menerima mesej " clone has protected access in Object" kerana clone()dinyatakan protected. Penyenaraian 4 menyajikan Penyenaraian 3 yang direfleksikan untuk menunjukkan yang utama clone().

Penyenaraian 4. Mengklonkan objek dari kelas lain

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()Kaedah mengatasi tidak membuang CloneNotSupportedException. Ini terkecuali diperiksakan dibuang sahaja dari Object's clone()kaedah, yang tidak dipanggil. Oleh itu, pengecualian tidak perlu ditangani atau dilewatkan kaedah panggilan kaedah melalui klausa lemparan.
  • Object's clone()kaedah tidak dipanggil (tidak ada super.clone()panggilan) kerana cetek penyalinan tidak diperlukan untuk Addresskelas - terdapat hanya satu medan untuk menyalin.

Untuk mengklon Addressobjek, cukup untuk membuat Addressobjek baru dan menginisialisasi objek ke pendua objek yang dirujuk dari citylapangan. AddressObjek baru kemudian dikembalikan.