Java предоставя стандартна библиотека на класове, състояща се от хиляди класове и други референтни типове. Въпреки различията във възможностите им, тези типове образуват една масивна йерархия на наследяване чрез пряко или косвено разширяване на Object
класа. Това важи и за всички класове и други референтни типове, които създавате.
Първата половина на този урок за наследяване на Java ви показа основите на наследяването, по-конкретно как да използвате Java extends
и super
ключови думи за извличане на детски клас от родителски клас, извикване на конструктори и методи на родителски клас, заместване на методи и др. Сега ще насочим вниманието си към майчинството на йерархията на наследяване на класа Java java.lang.Object
,.
Изучаването Object
и неговите методи ще ви помогнат да придобиете по-функционално разбиране за наследяването и как то работи във вашите Java програми. Познаването на тези методи като цяло ще ви помогне да разберете повече Java програмите.
Обект: суперкласът на Java
Object
е основният клас или крайният суперклас от всички останали Java класове. Съхранен в java.lang
пакета, 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
. Например, неметодът final
toString()
може да бъде заменен, докато final
wait()
методите не могат.
Ще разгледаме всеки от тези методи и как те ви позволяват да изпълнявате специални задачи в контекста на вашите Java класове. Първо, нека разгледаме основните правила и механизми за Object
наследяване.
Родови типове
В горния списък може би сте забелязали getClass()
, чийто Class
тип връщане е пример за родов тип . Ще обсъдя родови типове в бъдеща статия.
Разширяване на обект: Пример
Един клас може изрично да се разшири Object
, както е показано в листинг 1.
Листинг 1. Изрично разширяващ се обект
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()); } }
Тъй като можете да разширите най-много един друг клас (припомнете си от Част 1, че Java не поддържа множествено наследяване на базата на класове), не сте принудени изрично да разширявате Object
; в противен случай не бихте могли да разширите нито един друг клас. Следователно бихте разширили Object
неявно, както е показано в Листинг 2.
Листинг 2. Неявно разширяване на обект
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, както следва:
javac Employee.java
Стартирайте полученото приложение:
java Employee
Трябва да наблюдавате следния изход:
John Doe
Разберете за клас: getClass ()
В getClass()
метода връща по време на работа класа на всеки обект, в който той се нарича. Класът на изпълнение е представен от Class
обект, който се намира в java.lang
пакета. Class
е входната точка в Java Reflection API, за който ще научите, когато влезем в по-напреднали теми в програмирането на Java. Засега знайте, че приложението на Java използва Class
и останалата част от API за отразяване на Java, за да научи за собствената си структура.
Обекти на класа и статични синхронизирани методи
Върнатият Class
обект е обектът, който е заключен от static synchronized
методи на представения клас; например static synchronized void foo() {}
,. (Ще въведа синхронизация на Java в следващ урок.)
Дублиране на обекти: clone ()
В clone()
метода създава и връща копие на обекта, на който тя се нарича. Тъй като clone()
типът на връщане е Object
, референтът на обекта, който clone()
връща, трябва да бъде предаден на действителния тип на обекта, преди да се присвои тази референция на променлива от типа на обекта. Листинг 3 представя приложение, което демонстрира клониране.
Листинг 3. Клониране на обект
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); } }
CloneDemo
Класът на Списък 3 реализира Cloneable
интерфейса, който се намира в java.lang
пакета. Cloneable
се изпълнява от класа (чрез implements
Мрежа), за да се предотврати Object
е clone()
метод от хвърляне на копие на CloneNotSupportedException
клас (също така, в java.lang
).
CloneDemo
декларира int
еднобазово поле на име 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
Заместващ клон ()
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 implementCloneable
. It's not necessary because onlyObject
'sclone()
method requires that a class implement this interface, and thisclone()
method isn't being called.- Методът за заместване
clone()
не хвърляCloneNotSupportedException
. Това изключение е хвърлен само отObject
еclone()
метод, който не се поставя. Следователно, изключението не трябва да се обработва или да се предава в стека на метода-повикване чрез клауза за хвърляне. Object
еclone()
метод не се нарича (че нямаsuper.clone()
повикване), тъй като плитко копиране не се изисква заAddress
клас - има само едно поле за копиране.