Superkelas terbaik, Bagian 1

Pengembang Java yang berpengalaman sering kali menerima begitu saja fitur-fitur Java yang membingungkan pendatang baru. Misalnya, seorang pemula mungkin bingung tentang Objectkelas tersebut. Posting ini meluncurkan seri tiga bagian di mana saya menyajikan dan menjawab pertanyaan tentang Objectdan metodenya.

King Object

T: Apa Objectkelasnya?

A: The Objectkelas, yang disimpan dalam java.langpaket, adalah superclass akhir dari semua kelas Java (kecuali Object). Juga, array diperpanjang Object. Namun, antarmuka tidak memperpanjang Object, yang menunjukkan dalam Bagian 9.6.3.4 Jawa Bahasa Spesifikasi: ... menganggap bahwa sementara antarmuka tidak memiliki Objectsebagai supertype ... .

Object menyatakan metode berikut, yang akan saya bahas sepenuhnya nanti di posting ini dan di sisa seri 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 metode ini dan dapat mengganti metode apa pun yang tidak dideklarasikan final. Misalnya, non- finaltoString()metode bisa diganti, sedangkan finalwait()metode tidak bisa diganti.

T: Bisakah saya memperluas Objectkelas secara eksplisit ?

J: Ya, Anda dapat secara eksplisit memperpanjang Object. Misalnya, lihat Daftar 1.

Daftar 1. Memperluas secara eksplisit 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 dapat mengkompilasi Listing 1 ( javac Employee.java) dan menjalankan Employee.classfile yang dihasilkan ( java Employee), dan Anda akan mengamati John Doesebagai outputnya.

Karena kompilator secara otomatis mengimpor tipe dari java.langpaket, import java.lang.Object;pernyataan itu tidak diperlukan. Selain itu, Java tidak memaksa Anda untuk memperluas secara eksplisit Object. Jika ya, Anda tidak akan dapat memperluas kelas apa pun selain Objectkarena Java membatasi ekstensi kelas ke satu kelas. Oleh karena itu, Anda biasanya akan memperluas Objectsecara implisit, seperti yang ditunjukkan pada Listing 2.

Kode 2. Memperluas secara implisit 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 pada Listing 1, Employeekelas Listing 2 meluas Objectdan mewarisi metodenya.

Mengkloning objek

T: Apa yang clone()dicapai metode ini?

A: The clone()Metode menciptakan dan mengembalikan salinan dari objek yang metode ini disebut.

T: Bagaimana clone()cara kerja metode ini?

J:Object mengimplementasikan clone()sebagai metode asli, yang berarti bahwa kodenya disimpan di perpustakaan asli. Ketika kode ini dijalankan, ia memeriksa kelas (atau superclass) dari objek pemanggilan untuk melihat apakah itu mengimplementasikan java.lang.Cloneableantarmuka - Objecttidak diimplementasikan Cloneable. Jika antarmuka ini tidak diimplementasikan, clone()throws java.lang.CloneNotSupportedException, yang merupakan pengecualian yang dicentang (harus ditangani atau diteruskan tumpukan panggilan metode dengan menambahkan klausa throws ke header metode yang clone()dipanggil). Jika antarmuka ini diimplementasikan, clone()mengalokasikan objek baru dan menyalin nilai bidang objek pemanggil ke bidang ekuivalen objek baru, dan mengembalikan referensi ke objek baru.

T: Bagaimana cara memanggil clone()metode untuk mengkloning objek?

J: Dengan adanya referensi objek, panggil clone()referensi ini dan transmisikan objek yang dikembalikan dari Objectke jenis objek yang sedang digandakan. Kode 3 menyajikan sebuah contoh.

Kode 3. Kloning 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); } }

Kode 3 mendeklarasikan CloneDemokelas yang mengimplementasikan Cloneableantarmuka. Interface ini harus dilaksanakan atau doa Object's clone()metode akan menghasilkan dilemparkan CloneNotSupportedExceptionmisalnya.

CloneDemomendeklarasikan intkolom instance berbasis tunggal bernama xdan main()metode yang melatih kelas ini. main()dideklarasikan dengan klausa throws yang melewatkan CloneNotSupportedExceptiontumpukan metode-panggilan.

main()pertama membuat CloneDemodan menginisialisasi salinan instance yang dihasilkan dari xto 5. Ini kemudian mengeluarkan nilai instance xdan memanggil clone()instance ini, mentransmisikan objek yang dikembalikan CloneDemosebelum menyimpan referensinya. Akhirnya, ini mengeluarkan nilai xbidang klon .

Kompilasi Listing 3 ( javac CloneDemo.java) dan jalankan application ( java CloneDemo). Anda harus mengamati keluaran berikut:

cd.x = 5 cd2.x = 5

T: Mengapa saya perlu mengganti clone()metode ini?

J: Contoh sebelumnya tidak perlu mengganti clone()metode karena kode yang dipanggil clone()terletak di kelas yang sedang digandakan (yaitu, CloneDemokelas). Namun, jika clone()pemanggilan terletak di kelas yang berbeda, Anda perlu menggantinya clone(). Jika tidak, Anda akan menerima pesan " clone has protected access in Object" karena clone()dideklarasikan protected. Kode 4 menyajikan Daftar 3 yang direfraktorisasi untuk mendemonstrasikan pengesampingan clone().

Kode 4. Kloning 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()Metode penggantian tidak melempar CloneNotSupportedException. Diperiksa pengecualian ini dilemparkan hanya dari Object's clone()metode, yang tidak disebut. Oleh karena itu, pengecualian tidak harus ditangani atau diteruskan tumpukan metode-panggilan melalui klausa throws.
  • Object's clone()metode tidak disebut (tidak ada super.clone()panggilan) karena dangkal menyalin tidak diperlukan untuk Addresskelas - hanya ada satu bidang untuk menyalin.

Untuk mengkloning Addressobjek, cukup dengan membuat Addressobjek baru dan menginisialisasinya menjadi duplikat objek yang direferensikan dari citylapangan. AddressObjek baru kemudian dikembalikan.