Java Language
Методы и конструкторы классов объектов
Поиск…
Вступление
На этой странице документации приведены сведения о конструкторах классов java и методах класса объектов, которые автоматически унаследованы от Object
суперкласса любого вновь созданного класса.
Синтаксис
- public final native Class <?> getClass ()
- public final native void notify ()
- public final native void notifyAll ()
- public final native void wait (long timeout) throws InterruptedException
- public final void wait () throws InterruptedException
- public final void wait (long timeout, int nanos) бросает InterruptedException
- public native int hashCode ()
- public boolean equals (Object obj)
- public String toString ()
- защищенный native Object clone () бросает CloneNotSupportedException
- protected void finalize () throws Throwable
Метод toString ()
toString()
метод используется для создания String
представления объекта с помощью object's контента. Этот метод следует переопределить при написании вашего класса. toString()
называется неявно, когда объект конкатенируется с строкой, как в "hello " + anObject
.
Рассмотрим следующее:
public class User {
private String firstName;
private String lastName;
public User(String firstName, String lastName) {
this.firstName = firstName;
this.lastName = lastName;
}
@Override
public String toString() {
return firstName + " " + lastName;
}
public static void main(String[] args) {
User user = new User("John", "Doe");
System.out.println(user.toString()); // Prints "John Doe"
}
}
Здесь toString()
из класса Object
переопределяется в классе User
для предоставления значимых данных об объекте при его печати.
При использовании функции println()
toString()
метод toString()
объекта неявно называется. Поэтому эти утверждения делают то же самое:
System.out.println(user); // toString() is implicitly called on `user`
System.out.println(user.toString());
Если toString()
не переопределяется в вышеупомянутом классе User
, System.out.println(user)
может возвращать User@659e0bfd
или аналогичную строку без почти полезной информации, кроме имени класса. Это будет связано с тем, что вызов будет использовать реализацию toString()
базового класса Java Object
который ничего не знает о структуре или бизнес-правилах класса User
. Если вы хотите изменить эту функциональность в своем классе, просто переопределите метод.
метод equals ()
TL; DR
==
тесты для ссылочного равенства (являются ли они одним и тем же объектом )
.equals()
для равенства значений (независимо от того, являются ли они логически «равными» )
equals()
- метод, используемый для сравнения двух объектов для равенства. По умолчанию реализация метода equals()
в классе Object
возвращает true
тогда и только тогда, когда обе ссылки указывают на один и тот же экземпляр. Поэтому он ведет себя так же, как и сравнение ==
.
public class Foo {
int field1, field2;
String field3;
public Foo(int i, int j, String k) {
field1 = i;
field2 = j;
field3 = k;
}
public static void main(String[] args) {
Foo foo1 = new Foo(0, 0, "bar");
Foo foo2 = new Foo(0, 0, "bar");
System.out.println(foo1.equals(foo2)); // prints false
}
}
Хотя foo1
и foo2
создаются с одинаковыми полями, они указывают на два разных объекта в памяти. Таким образом, реализация equals()
умолчанию вычисляет значение false
.
Чтобы сравнить содержимое объекта для равенства, equals()
должно быть переопределено.
public class Foo {
int field1, field2;
String field3;
public Foo(int i, int j, String k) {
field1 = i;
field2 = j;
field3 = k;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null || getClass() != obj.getClass()) {
return false;
}
Foo f = (Foo) obj;
return field1 == f.field1 &&
field2 == f.field2 &&
(field3 == null ? f.field3 == null : field3.equals(f.field3));
}
@Override
public int hashCode() {
int hash = 1;
hash = 31 * hash + this.field1;
hash = 31 * hash + this.field2;
hash = 31 * hash + (field3 == null ? 0 : field3.hashCode());
return hash;
}
public static void main(String[] args) {
Foo foo1 = new Foo(0, 0, "bar");
Foo foo2 = new Foo(0, 0, "bar");
System.out.println(foo1.equals(foo2)); // prints true
}
}
Здесь переопределенный метод equals()
решает, что объекты равны, если их поля одинаковы.
Обратите внимание, что метод hashCode()
также был перезаписан. В договоре для этого метода указано, что, когда два объекта равны, их хэш-значения также должны быть одинаковыми. Вот почему нужно почти всегда переопределять hashCode()
и equals()
вместе.
Обратите особое внимание на тип аргумента метода equals
. Это Object obj
, а не Foo obj
. Если вы поместите последнее в свой метод, это не является переопределением метода equals
.
При написании собственного класса вам придется писать аналогичную логику при переопределении equals()
и hashCode()
. Большинство IDE могут автоматически генерировать это для вас.
Пример реализации equals()
можно найти в классе String
, который является частью основного Java API. Вместо сравнения указателей класс String
сравнивает содержимое String
.
В Java 1.7 представлен класс java.util.Objects
который обеспечивает метод удобства, equals
, который сравнивает две потенциально null
ссылки, поэтому его можно использовать для упрощения реализации метода equals
.
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null || getClass() != obj.getClass()) {
return false;
}
Foo f = (Foo) obj;
return field1 == f.field1 && field2 == f.field2 && Objects.equals(field3, f.field3);
}
Сравнение классов
Поскольку метод equals может работать против любого объекта, одна из первых вещей, которую метод часто делает (после проверки на null
), - это проверить, соответствует ли класс сравниваемого объекта текущему классу.
@Override
public boolean equals(Object obj) {
//...check for null
if (getClass() != obj.getClass()) {
return false;
}
//...compare fields
}
Обычно это делается, как указано выше, путем сравнения объектов класса. Тем не менее, это может потерпеть неудачу в нескольких особых случаях, которые могут быть не очевидны. Например, некоторые фреймворки генерируют динамические прокси классов, и эти динамические прокси-серверы фактически являются другим классом. Вот пример использования JPA.
Foo detachedInstance = ...
Foo mergedInstance = entityManager.merge(detachedInstance);
if (mergedInstance.equals(detachedInstance)) {
//Can never get here if equality is tested with getClass()
//as mergedInstance is a proxy (subclass) of Foo
}
Один из механизмов, позволяющих обойти это ограничение, заключается в сравнении классов с использованием instanceof
@Override
public final boolean equals(Object obj) {
if (!(obj instanceof Foo)) {
return false;
}
//...compare fields
}
Тем не менее, есть несколько подводных камней, которых следует избегать при использовании instanceof
. Поскольку Foo потенциально может иметь другие подклассы, и эти подклассы могут переопределять equals()
вы можете попасть в случай, когда Foo
равен FooSubclass
но FooSubclass
не равен Foo
.
Foo foo = new Foo(7);
FooSubclass fooSubclass = new FooSubclass(7, false);
foo.equals(fooSubclass) //true
fooSubclass.equals(foo) //false
Это нарушает свойства симметрии и транзитивности и, таким образом, является недопустимой реализацией метода equals()
. В результате при использовании instanceof
хорошая практика заключается в том, чтобы сделать метод equals()
final
(как в приведенном выше примере). Это гарантирует, что никакие переопределения подклассов не equals()
и нарушают ключевые допущения.
метод hashCode ()
Когда класс Java переопределяет метод equals
, он также должен переопределять метод hashCode
. Как определено в контракте метода :
- Всякий раз, когда он вызывается на одном и том же объекте более одного раза во время выполнения приложения Java, метод
hashCode
должен последовательно возвращать одно и то же целое число, если информация, используемая при равных сравнениях на объекте, не изменяется. Это целое число не должно оставаться согласованным с одним исполнением приложения на другое выполнение одного и того же приложения.- Если два объекта равны в соответствии с методом
equals(Object)
, то вызов методаhashCode
для каждого из двух объектов должен давать одинаковый целочисленный результат.- Не требуется, чтобы, если два объекта неравны в соответствии с методом
equals(Object)
, то вызов методаhashCode
для каждого из двух объектов должен производить различные целочисленные результаты. Тем не менее, программист должен знать, что получение отдельных целых результатов для неравных объектов может улучшить производительность хеш-таблиц.
Хэш коды используются в хэш-реализации, такие как HashMap
, HashTable
и HashSet
. Результат функции hashCode
определяет ведро, в которое будет помещен объект. Эти хэш-реализации более эффективны, если реализована реализация hashCode
. Важным свойством хорошей hashCode
является то, что распределение значений hashCode
является однородным. Другими словами, существует небольшая вероятность того, что многочисленные экземпляры будут сохранены в одном ковше.
Алгоритм вычисления значения хэш-кода может быть аналогичен следующему:
public class Foo {
private int field1, field2;
private String field3;
public Foo(int field1, int field2, String field3) {
this.field1 = field1;
this.field2 = field2;
this.field3 = field3;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null || getClass() != obj.getClass()) {
return false;
}
Foo f = (Foo) obj;
return field1 == f.field1 &&
field2 == f.field2 &&
(field3 == null ? f.field3 == null : field3.equals(f.field3);
}
@Override
public int hashCode() {
int hash = 1;
hash = 31 * hash + field1;
hash = 31 * hash + field2;
hash = 31 * hash + (field3 == null ? 0 : field3.hashCode());
return hash;
}
}
Использование Arrays.hashCode () в виде короткой вырезки
В Java 1.2 и выше вместо разработки алгоритма для вычисления хеш-кода можно сгенерировать с помощью java.util.Arrays#hashCode
, предоставив массив Object или primitives, содержащий значения полей:
@Override
public int hashCode() {
return Arrays.hashCode(new Object[] {field1, field2, field3});
}
В Java 1.7 представлен класс java.util.Objects
который предоставляет метод удобства hash(Object... objects)
, который вычисляет хэш-код на основе значений объектов, предоставленных ему. Этот метод работает так же, как java.util.Arrays#hashCode
.
@Override
public int hashCode() {
return Objects.hash(field1, field2, field3);
}
Примечание: этот подход неэффективен и создает объекты мусора каждый раз, когда вы вызываете свой собственный метод hashCode()
:
- Создается временный
Object[]
. (В версииObjects.hash()
массив создается механизмом «varargs».) - Если какое-либо из полей является примитивными типами, они должны быть помещены в бокс и могут создавать более временные объекты.
- Массив должен быть заполнен.
- Массив должен быть повторен с помощью
Arrays.hashCode
илиObjects.hash
. -
Object.hashCode()
которыйArrays.hashCode
илиObjects.hash
должен сделать (возможно) не может быть встроен.
Внутреннее кэширование хеш-кодов
Поскольку вычисление хеш-кода объекта может быть дорогостоящим, может быть привлекательным кэшировать значение хеш-кода внутри объекта при первом его вычислении. Например
public final class ImmutableArray {
private int[] array;
private volatile int hash = 0;
public ImmutableArray(int[] initial) {
array = initial.clone();
}
// Other methods
@Override
public boolean equals(Object obj) {
// ...
}
@Override
public int hashCode() {
int h = hash;
if (h == 0) {
h = Arrays.hashCode(array);
hash = h;
}
return h;
}
}
Этот подход отменяет стоимость (многократно) вычисления хеш-кода накладных расходов дополнительного поля для кэширования хеш-кода. Независимо от того, будет ли это окупаться, так как оптимизация производительности будет зависеть от того, насколько часто данный объект хэшируется (искал) и другие факторы.
Вы также заметите, что если истинный хэш-код объекта ImmutableArray
равен нулю (один шанс в 2 32 ), кеш неэффективен.
Наконец, этот подход намного сложнее реализовать правильно, если объект, который мы хешируем, изменен. Однако, если хэш-коды меняются; см. контракт выше.
wait () и notify ()
wait()
и notify()
работают в тандеме - когда один поток вызовов wait()
на объекте, что поток будет заблокирован до тех пор , другой поток вызовов notify()
или notifyAll()
на тот же объект.
(См. Также: wait () / notify () )
package com.example.examples.object;
import java.util.concurrent.atomic.AtomicBoolean;
public class WaitAndNotify {
public static void main(String[] args) throws InterruptedException {
final Object obj = new Object();
AtomicBoolean aHasFinishedWaiting = new AtomicBoolean(false);
Thread threadA = new Thread("Thread A") {
public void run() {
System.out.println("A1: Could print before or after B1");
System.out.println("A2: Thread A is about to start waiting...");
try {
synchronized (obj) { // wait() must be in a synchronized block
// execution of thread A stops until obj.notify() is called
obj.wait();
}
System.out.println("A3: Thread A has finished waiting. "
+ "Guaranteed to happen after B3");
} catch (InterruptedException e) {
System.out.println("Thread A was interrupted while waiting");
} finally {
aHasFinishedWaiting.set(true);
}
}
};
Thread threadB = new Thread("Thread B") {
public void run() {
System.out.println("B1: Could print before or after A1");
System.out.println("B2: Thread B is about to wait for 10 seconds");
for (int i = 0; i < 10; i++) {
try {
Thread.sleep(1000); // sleep for 1 second
} catch (InterruptedException e) {
System.err.println("Thread B was interrupted from waiting");
}
}
System.out.println("B3: Will ALWAYS print before A3 since "
+ "A3 can only happen after obj.notify() is called.");
while (!aHasFinishedWaiting.get()) {
synchronized (obj) {
// notify ONE thread which has called obj.wait()
obj.notify();
}
}
}
};
threadA.start();
threadB.start();
threadA.join();
threadB.join();
System.out.println("Finished!");
}
}
Пример вывода:
A1: Could print before or after B1
B1: Could print before or after A1
A2: Thread A is about to start waiting...
B2: Thread B is about to wait for 10 seconds
B3: Will ALWAYS print before A3 since A3 can only happen after obj.notify() is called.
A3: Thread A has finished waiting. Guaranteed to happen after B3
Finished!
B1: Could print before or after A1
B2: Thread B is about to wait for 10 seconds
A1: Could print before or after B1
A2: Thread A is about to start waiting...
B3: Will ALWAYS print before A3 since A3 can only happen after obj.notify() is called.
A3: Thread A has finished waiting. Guaranteed to happen after B3
Finished!
A1: Could print before or after B1
A2: Thread A is about to start waiting...
B1: Could print before or after A1
B2: Thread B is about to wait for 10 seconds
B3: Will ALWAYS print before A3 since A3 can only happen after obj.notify() is called.
A3: Thread A has finished waiting. Guaranteed to happen after B3
Finished!
Метод getClass ()
Метод getClass()
может использоваться для поиска типа класса выполнения для объекта. См. Пример ниже:
public class User {
private long userID;
private String name;
public User(long userID, String name) {
this.userID = userID;
this.name = name;
}
}
public class SpecificUser extends User {
private String specificUserID;
public SpecificUser(String specificUserID, long userID, String name) {
super(userID, name);
this.specificUserID = specificUserID;
}
}
public static void main(String[] args){
User user = new User(879745, "John");
SpecificUser specificUser = new SpecificUser("1AAAA", 877777, "Jim");
User anotherSpecificUser = new SpecificUser("1BBBB", 812345, "Jenny");
System.out.println(user.getClass()); //Prints "class User"
System.out.println(specificUser.getClass()); //Prints "class SpecificUser"
System.out.println(anotherSpecificUser.getClass()); //Prints "class SpecificUser"
}
Метод getClass()
возвращает наиболее специфический тип класса, поэтому, когда getClass()
вызывается в anotherSpecificUser
, возвращаемое значение является class SpecificUser
потому что это ниже дерева наследования, чем User
.
Стоит отметить, что, хотя метод getClass
объявлен как:
public final native Class<?> getClass();
Действительным статическим типом, возвращаемым вызовом getClass
является Class<? extends T>
где T
- статический тип объекта, на который вызывается getClass
.
т.е. следующее:
Class<? extends String> cls = "".getClass();
метод clone ()
Метод clone()
используется для создания и возврата копии объекта. Этот метод следует избегать, так как он проблематичен, и конструктор копирования или какой-либо другой подход к копированию следует использовать в пользу clone()
.
Для использования метода все классы, вызывающие метод, должны реализовывать интерфейс Cloneable
.
Сам интерфейс Cloneable
- это просто интерфейс тега, используемый для изменения поведения метода native
clone()
который проверяет, реализует ли класс вызывающих объектов Cloneable
. Если вызывающий абонент не реализует этот интерфейс, будет CloneNotSupportedException
.
Сам класс Object
не реализует этот интерфейс, поэтому CloneNotSupportedException
будет вызываться, если вызывающий объект имеет класс Object
.
Чтобы клон был прав, он должен быть независим от объекта, из которого он клонируется, поэтому может потребоваться изменить объект до его возвращения. Это означает, что по существу создать «глубокую копию», также копируя любой из изменяемых объектов, которые составляют внутреннюю структуру клонируемого объекта. Если это не выполняется правильно, клонированный объект не будет независимым и будет иметь те же ссылки на изменяемые объекты, что и объект, из которого он был клонирован. Это приведет к непоследовательному поведению, так как любые изменения в них повлияют на другие.
class Foo implements Cloneable {
int w;
String x;
float[] y;
Date z;
public Foo clone() {
try {
Foo result = new Foo();
// copy primitives by value
result.w = this.w;
// immutable objects like String can be copied by reference
result.x = this.x;
// The fields y and z refer to a mutable objects; clone them recursively.
if (this.y != null) {
result.y = this.y.clone();
}
if (this.z != null) {
result.z = this.z.clone();
}
// Done, return the new object
return result;
} catch (CloneNotSupportedException e) {
// in case any of the cloned mutable fields do not implement Cloneable
throw new AssertionError(e);
}
}
}
метод finalize ()
Это защищенный и нестатический метод класса Object
. Этот метод используется для выполнения некоторых окончательных операций или очистки операций над объектом до его удаления из памяти.
Согласно документу, этот метод вызывается сборщиком мусора на объекте, когда сбор мусора определяет, что больше нет ссылок на объект.
Но нет никаких гарантий того, что метод finalize()
будет вызван, если объект по-прежнему доступен или нет, когда сборщик мусора запускается, когда объект становится подходящим. Вот почему лучше не полагаться на этот метод.
В основных библиотеках Java могут быть найдены некоторые примеры использования, например, в FileInputStream.java
:
protected void finalize() throws IOException {
if ((fd != null) && (fd != FileDescriptor.in)) {
/* if fd is shared, the references in FileDescriptor
* will ensure that finalizer is only called when
* safe to do so. All references using the fd have
* become unreachable. We can call close()
*/
close();
}
}
В этом случае это последний шанс закрыть ресурс, если этот ресурс не был закрыт раньше.
Как правило, считается неправильной практикой использовать метод finalize()
в приложениях любого типа и его следует избегать.
Финализаторы не предназначены для освобождения ресурсов (например, закрытие файлов). Сборщик мусора вызывается, когда (если!) Система работает на куче. Вы не можете полагаться на это, чтобы вызываться, когда система работает на ручках файлов или по какой-либо другой причине.
Предполагаемый прецедент для финализаторов предназначен для объекта, который должен быть возвращен, чтобы уведомить какой-либо другой объект о его предстоящей гибели. Для этого теперь существует лучший механизм - класс java.lang.ref.WeakReference<T>
. Если вы считаете, что вам нужно написать метод finalize()
, вы должны изучить, можете ли вы решить ту же проблему, используя WeakReference
. Если это не решит вашу проблему, вам может потребоваться переосмыслить свой дизайн на более глубоком уровне.
Для дальнейшего чтения здесь приведен пункт о методе finalize()
из книги «Эффективная Java» Джошуа Блоха.
Конструктор объектов
Все конструкторы в Java должны сделать вызов конструктору Object
. Это делается с помощью вызова super()
. Это должна быть первая строка в конструкторе. Причиной этого является то, что объект может быть фактически создан в куче до выполнения любой дополнительной инициализации.
Если вы не укажете вызов super()
в конструкторе, компилятор поместит его для вас.
Таким образом, все три из этих примеров функционально идентичны
с явным вызовом конструктора super()
public class MyClass {
public MyClass() {
super();
}
}
с неявным вызовом конструктора super()
public class MyClass {
public MyClass() {
// empty
}
}
с неявным конструктором
public class MyClass {
}
Как насчет Constructor-Chaining?
В качестве первой инструкции конструктора можно вызвать другие конструкторы. Поскольку и явный вызов супер-конструктора, и вызов другому конструктору должны быть как первыми инструкциями, так и взаимоисключающими.
public class MyClass {
public MyClass(int size) {
doSomethingWith(size);
}
public MyClass(Collection<?> initialValues) {
this(initialValues.size());
addInitialValues(initialValues);
}
}
Вызов нового MyClass(Arrays.asList("a", "b", "c"))
вызовет второй конструктор с аргументом List, который, в свою очередь, делегирует первому конструктору (который будет делегировать неявно super()
), а затем вызовите addInitialValues(int size)
со вторым размером списка. Это используется для уменьшения дублирования кода, когда несколько конструкторов должны выполнять одну и ту же работу.
Как вызвать конкретный конструктор?
В приведенном выше примере можно либо вызвать new MyClass("argument")
либо new MyClass("argument", 0)
. Другими словами, подобно перегрузке метода , вы просто вызываете конструктор с параметрами, которые необходимы для вашего выбранного конструктора.
Что произойдет в конструкторе класса Object?
В подклассе, который имеет пустой конструктор по умолчанию (минус вызов super()
), ничего не может произойти.
Пустой конструктор по умолчанию может быть явно определен, но если он не будет компилятором, он будет установлен для вас до тех пор, пока не будут определены другие конструкторы.
Как объект создается из конструктора в Object?
Фактическое создание объектов зависит от JVM. Каждый конструктор Java появляется как специальный метод с именем <init>
который отвечает за инициализацию экземпляра. Этот метод <init>
предоставляется компилятором, и поскольку <init>
не является допустимым идентификатором в Java, он не может использоваться непосредственно на языке.
Как JVM вызывает этот метод
<init>
?
JVM будет вызывать метод <init>
используя invokespecial
инструкцию invokespecial
и может быть вызван только в неинициализированных экземплярах класса.
Для получения дополнительной информации см. Спецификацию JVM и спецификацию Java Language:
- Специальные методы (JVM) - JVMS - 2.9
- Конструкторы - JLS - 8.8