Warisan di Jawa, Bahagian 2: Objek dan kaedahnya

Java menyediakan pustaka kelas standard yang terdiri daripada ribuan kelas dan jenis rujukan lain. Walaupun terdapat perbezaan dalam kemampuan mereka, jenis ini membentuk satu hierarki pewarisan yang besar dengan secara langsung atau tidak langsung memperluas Objectkelas. Ini juga berlaku untuk kelas dan jenis rujukan lain yang anda buat.

Separuh pertama tutorial ini mengenai pewarisan Java menunjukkan kepada anda asas-asas pewarisan, khususnya bagaimana menggunakan Java  extendsdan superkata kunci untuk memperoleh kelas anak dari kelas induk, memohon pembina dan kaedah kelas induk, kaedah ganti, dan banyak lagi. Sekarang, kami akan mengalihkan tumpuan kita kepada induk hierarki warisan kelas Java, java.lang.Object.

Mempelajari Objectdan kaedahnya akan membantu anda memperoleh pemahaman warisan yang lebih berfungsi dan cara kerjanya dalam program Java anda. Mengetahui kaedah tersebut akan membantu anda memahami program Java secara umum. 

muat turun Dapatkan kod Muat turun kod sumber misalnya aplikasi dalam tutorial ini. Dicipta oleh Jeff Friesen untuk JavaWorld.

Objek: Superclass Java

Objectadalah kelas root, atau superclass utama, dari semua kelas Java yang lain. Disimpan dalam java.langpakej, Objectmenyatakan kaedah berikut, yang diwarisi oleh semua kelas lain:

  • 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. Sebagai contoh, finaltoString()kaedah bukan kaedah boleh diganti, sedangkan finalwait()kaedahnya tidak boleh.

Kami akan melihat setiap kaedah ini dan bagaimana kaedah ini membolehkan anda melakukan tugas khas dalam konteks kelas Java anda. Pertama, mari kita pertimbangkan peraturan dan mekanisme asas untuk Objectmewarisi.

Jenis generik

Dalam senarai di atas, anda mungkin telah memperhatikan getClass(), yang Classjenis pengembaliannya adalah contoh jenis generik . Saya akan membincangkan jenis generik dalam artikel akan datang.

Memanjangkan Objek: Contohnya

Kelas dapat diperluas secara eksplisit Object, seperti yang ditunjukkan dalam Penyenaraian 1.

Penyenaraian 1. Memperluas Objek dengan jelas

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

Kerana anda dapat memperluas paling banyak satu kelas lain (ingat dari Bahagian 1 bahawa Java tidak menyokong warisan berbilang kelas), anda tidak dipaksa untuk memperluas secara eksplisit Object; jika tidak, anda tidak dapat melanjutkan kelas lain. Oleh itu, anda akan meluas Objectsecara tersirat, seperti yang ditunjukkan dalam Penyenaraian 2.

Penyenaraian 2. Meluaskan Objek secara tersirat

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

Susun Penyenaraian 1 atau Penyenaraian 2 seperti berikut:

javac Employee.java

Jalankan aplikasi yang dihasilkan:

java Employee

Anda harus memerhatikan output berikut:

John Doe

Ketahui mengenai kelas: getClass ()

The getClass()Cara mengembalikan kelas runtime apa-apa objek di mana ia dipanggil. The kelas runtime diwakili oleh Classobjek, yang terdapat dalam java.langpakej. Classadalah titik masuk ke Java Reflection API, yang akan Anda pelajari ketika kita memasuki topik yang lebih maju dalam pemrograman Java. Buat masa ini, ketahui bahawa aplikasi Java menggunakan Classdan sisa Java Reflection API untuk mempelajari strukturnya sendiri.

Objek kelas dan kaedah penyegerakan statik

ClassObjek yang dikembalikan adalah objek yang dikunci dengan static synchronizedkaedah kelas yang diwakili; sebagai contoh , static synchronized void foo() {}. (Saya akan memperkenalkan penyegerakan Java dalam tutorial yang akan datang.)

Mendua objek: klon ()

The clone()kaedah mencipta dan mengembalikan salinan objek di mana ia dipanggil. Kerana clone()jenis pengembalian adalah Object, rujukan objek yang clone()mengembalikan mesti dilemparkan ke jenis objek yang sebenarnya sebelum memberikan rujukan itu ke pemboleh ubah jenis objek. Penyenaraian 3 menyajikan aplikasi yang menunjukkan pengklonan.

Penyenaraian 3. Mengklonkan objek

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

CloneDemoKelas senarai 3 menggunakan Cloneableantara muka, yang terdapat dalam java.langpakej. Cloneabledilaksanakan oleh kelas (melalui implementskata kunci) untuk mengelakkan Object's clone()kaedah daripada membuang contoh satu daripada CloneNotSupportedExceptionkelas (juga terdapat dalam java.lang).

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

main()pertama memberi contoh CloneDemodan menginisialisasi salinan contoh yang dihasilkan xke 5. Kemudian mengeluarkan nilai instance xdan memanggil clone()instance ini, menghantar 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

Klon mengatasi ()

The previous example didn't need to override clone() because the code that calls clone() is located in the class being cloned (CloneDemo). If the call to clone() were located in a different class, however, then you would need to override clone(). Because clone() is declared protected, you would receive a "clone has protected access in Object" message if you didn't override it before compiling the class. Listing 4 presents a refactored Listing 3 that demonstrates overriding clone().

Listing 4. Cloning an object from another class

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

Listing 4 declares a Data class whose instances are to be cloned. Data implements the Cloneable interface to prevent a CloneNotSupportedException from being thrown when the clone() method is called. It then declares int-based instance field x, and overrides the clone() method. The clone() method executes super.clone() to call its superclass's (that is, Object's) 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 the instance field, clones the Data object, and outputs its 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

Shallow cloning

Shallow cloning (also known as shallow copying) refers to duplicating an object's fields without duplicating any objects that are referenced from that object's reference fields (if there are any reference fields). Listings 3 and 4 actually demonstrated 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 the 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 demonstrates.

Listing 5. 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; } } 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()); } }

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

Deep cloning

Deep cloning (also known as deep copying) refers to duplicating an object's fields such that any referenced objects are duplicated. Furthermore, the referenced objects of referenced objects are duplicated, and so forth. Listing 6 refactors Listing 5 to demonstrate deep cloning.

Listing 6. Deep 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 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()); } }

Listing 6 shows that Employee's clone() method first calls super.clone(), which shallowly copies the name, age, and address fields. It then calls clone() on the address field to make a duplicate of the referenced Address object. Address 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. Pengecualian ini 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.