Superclass terhebat, Bahagian 1
Pembangun Java yang berpengalaman sering menganggap fitur Java yang dianggap membingungkan pendatang baru. Contohnya, seorang pemula mungkin keliru mengenai Object
kelas. Catatan ini melancarkan siri tiga bahagian di mana saya mengemukakan dan menjawab soalan mengenai Object
dan kaedahnya.
Objek Raja
Q: Apa Object
kelasnya?
A: The Object
kelas, yang disimpan di dalam java.lang
pakej, 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 Object
sebagai 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, final
toString()
kaedah bukan kaedah boleh diganti, sedangkan final
wait()
kaedah tidak boleh ditolak.
Q: Bolehkah saya melanjutkan Object
kelas 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.class
fail yang dihasilkan ( java Employee
), dan anda akan melihatnya John Doe
sebagai output.
Kerana penyusun mengimport jenis dari java.lang
pakej 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 Object
Java menghadkan peluasan kelas kepada satu kelas. Oleh itu, anda biasanya akan meluas Object
secara 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, Employee
kelas Penyenaraian 2 meluas Object
dan 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.Cloneable
antara muka - Object
tidak 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 Object
jenis 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 CloneDemo
kelas yang melaksanakan Cloneable
antara muka. Antara muka ini mesti dilaksanakan atau doa Object
's clone()
kaedah akan menyebabkan dibuang CloneNotSupportedException
misalnya.
CloneDemo
mengisytiharkan int
bidang contoh berdasarkan tunggal bernama x
dan main()
kaedah yang menjalankan kelas ini. main()
dinyatakan dengan klausa lemparan yang melepasi CloneNotSupportedException
timbunan panggilan kaedah.
main()
pertama memberi contoh CloneDemo
dan menginisialisasi salinan contoh yang dihasilkan x
ke 5
. Ia kemudian mengeluarkan nilai instance x
dan memanggil clone()
instance ini, melemparkan objek yang dikembalikan CloneDemo
sebelum menyimpan rujukannya. Akhirnya, ia menghasilkan x
nilai 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 CloneDemo
kelas). 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 implementCloneable
. It's not necessary because onlyObject
'sclone()
method requires that a class implement this interface, and thisclone()
method isn't being called.clone()
Kaedah mengatasi tidak membuangCloneNotSupportedException
. Ini terkecuali diperiksakan dibuang sahaja dariObject
'sclone()
kaedah, yang tidak dipanggil. Oleh itu, pengecualian tidak perlu ditangani atau dilewatkan kaedah panggilan kaedah melalui klausa lemparan.Object
'sclone()
kaedah tidak dipanggil (tidak adasuper.clone()
panggilan) kerana cetek penyalinan tidak diperlukan untukAddress
kelas - terdapat hanya satu medan untuk menyalin.
Untuk mengklon Address
objek, cukup untuk membuat Address
objek baru dan menginisialisasi objek ke pendua objek yang dirujuk dari city
lapangan. Address
Objek baru kemudian dikembalikan.