Java Language
Objektklassmetoder och konstruktör
Sök…
Introduktion
Dokumentationen sida är för att visa detaljer med exempel om Java klass konstruktörer och om objektklass Metoder som automatiskt ärvs från super Object
någon nyskapade klassen.
Syntax
- public final native Class <?> getClass ()
- offentligt slutgiltigt tomt meddelande ()
- offentligt slutligt inhemskt ogiltigt ogiltigt meddelandeAll ()
- offentligt slutgiltigt tomt väntetid (lång timeout) kastar InterruptException
- public final void wait () kastar InterruptException
- public final void wait (lång timeout, int nanos) kastar InterruptException
- public native int hashCode ()
- offentliga booleska jämlikar (Object obj)
- public String toString ()
- skyddad native Object-klon () kastar CloneNotSupportedException
- skyddat tomrum finalisera () kastar kasta
toString () -metoden
toString()
används för att skapa en String
av ett objekt med hjälp av objektets innehåll. Denna metod bör åsidosättas när du skriver din klass. toString()
kallas implicit när ett objekt är sammankopplat till en sträng som i "hello " + anObject
.
Tänk på följande:
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"
}
}
Här toString()
från Object
klassen åsidosätts i User
för att ge meningsfulla uppgifter om objektet vid utskrift den.
När du använder println()
kallas objektets toString()
metod implicit. Därför gör dessa uttalanden samma sak:
System.out.println(user); // toString() is implicitly called on `user`
System.out.println(user.toString());
Om toString()
inte åsidosätts i ovannämnda User
, kan System.out.println(user)
returnera User@659e0bfd
eller en liknande sträng med nästan ingen användbar information utom klassnamnet. Detta kommer att bero på att samtalet kommer att använda toString()
genomförandet av basen Java Object
klass som inte vet något om User
klassens struktur eller affärsregler. Om du vill ändra den här funktionen i din klass, åsidosätter du metoden.
lika (metod)
TL; DR
==
test för referensjämlikhet (om de är samma objekt )
.equals()
tester för värdejämlikhet (om de logiskt sett är "lika" )
equals()
är en metod som används för att jämföra två objekt för jämlikhet. Standardimplementeringen av metoden equals()
i klassen Object
returnerar true
om och bara om båda referenser pekar på samma instans. Den uppför sig därför på samma sätt som jämförelse med ==
.
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
}
}
Även om foo1
och foo2
skapas med samma fält, pekar de på två olika objekt i minnet. Standardvärdet är equals()
implementering utvärderas därför till false
.
För att jämföra innehållet i ett objekt för jämlikhet måste equals()
åsidosättas.
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
}
}
Här bestämmer metoden åsidosatt equals()
att objekten är lika om deras fält är desamma.
Lägg märke till att hashCode()
också har skrivits över. Avtalet för den metoden anger att när två objekt är lika, måste deras hashvärden också vara desamma. Det är därför man nästan alltid måste åsidosätta hashCode()
och är equals()
tillsammans.
Var särskilt uppmärksam på argumenttypen för metoden equals
. Det är Object obj
, inte Foo obj
. Om du lägger det senare i din metod är det inte en åsidosättning av metoden equals
.
När du skriver din egen klass måste du skriva liknande logik när du åsidosätter equals()
och hashCode()
. De flesta IDE: er kan automatiskt generera detta åt dig.
Ett exempel på implementering av equals()
kan hittas i String
klassen, som är en del av kärn-Java API. I stället för att jämföra pekare jämför String
klassen innehållet i String
.
Java 1.7 introducerade klassen java.util.Objects
som tillhandahåller en bekvämhetsmetod, equals
, som jämför två potentiellt null
, så den kan användas för att förenkla implementeringar av metoden 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);
}
Klassjämförelse
Eftersom metoden lika kan köras mot alla objekt, är en av de första saker som metoden ofta gör (efter att ha kontrollerat för null
) att kontrollera om klassen på objektet som jämförs stämmer med den aktuella klassen.
@Override
public boolean equals(Object obj) {
//...check for null
if (getClass() != obj.getClass()) {
return false;
}
//...compare fields
}
Detta görs vanligen som ovan genom att jämföra klassobjekten. Men det kan misslyckas i några speciella fall som kanske inte är uppenbara. Till exempel genererar vissa ramverk dynamiska proxys av klasser och dessa dynamiska proxyer är faktiskt en annan klass. Här är ett exempel med 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
}
En mekanism för att lösa den begränsningen är att jämföra klasser med instanceof
@Override
public final boolean equals(Object obj) {
if (!(obj instanceof Foo)) {
return false;
}
//...compare fields
}
Det finns dock några fallgropar som måste undvikas när instanceof
. Eftersom Foo potentiellt kan ha andra underklasser och dessa underklasser kan åsidosätta equals()
du komma in i ett fall där en Foo
är lika med en FooSubclass
men FooSubclass
inte är lika med Foo
.
Foo foo = new Foo(7);
FooSubclass fooSubclass = new FooSubclass(7, false);
foo.equals(fooSubclass) //true
fooSubclass.equals(foo) //false
Detta bryter mot egenskaperna hos symmetri och transitivitet och är därför en ogiltig implementering av metoden equals()
. Som ett resultat, när man använder instanceof
, är en bra praxis att göra metoden equals()
final
(som i exemplet ovan). Detta säkerställer att ingen underklass åsidosätter equals()
och bryter mot viktiga antaganden.
metoden hashCode ()
När en Java-klass åsidosätter metoden equals
bör den också hashCode
metoden hashCode
. Såsom definieras i metodens kontrakt :
- Närhelst det åberopas på samma objekt mer än en gång under en körning av en Java-applikation måste
hashCode
metoden konsekvent returnera samma heltal, förutsatt att ingen information som används i lika jämförelser på objektet ändras. Detta heltal behöver inte förbli konsekvent från en körning av en applikation till en annan exekvering av samma applikation.- Om två objekt är lika enligt metoden
hashCode
equals(Object)
, måste man ringahashCode
metoden på vart och ett av de två objekten som ger samma heltalresultat.- Det krävs inte att om två objekt är ojämlika enligt metoden
hashCode
equals(Object)
, måste man ringahashCode
metoden på vart och ett av de två objekten ge distinkta heltal. Programmeraren bör dock vara medveten om att att producera distinkta heltalresultat för ojämna objekt kan förbättra prestandan för hashtabeller.
Hash-koder används i HashMap
som HashMap
, HashTable
och HashSet
. Resultatet av hashCode
funktionen bestämmer skopan i vilken ett objekt ska placeras. Dessa hash-implementationer är mer effektiva om den tillhandahållna hashCode
implementeringen är bra. En viktig egenskap för god hashCode
implementering är att fördelningen av hashCode
värdena är enhetlig. Med andra ord, det finns en liten sannolikhet för att många instanser kommer att lagras i samma hink.
En algoritm för beräkning av ett hashkodvärde kan likna följande:
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;
}
}
Använda Arrays.hashCode () som en genväg
I Java 1.2 och senare, istället för att utveckla en algoritm för att beräkna en hash-kod, kan en genereras med java.util.Arrays#hashCode
genom att tillhandahålla ett objekt eller primitiv-array som innehåller fältvärden:
@Override
public int hashCode() {
return Arrays.hashCode(new Object[] {field1, field2, field3});
}
Java 1.7 introducerade klassen java.util.Objects
som tillhandahåller en bekvämhetsmetod, hash(Object... objects)
, som beräknar en hash-kod baserad på värdena på de objekt som levereras till den. Den här metoden fungerar precis som java.util.Arrays#hashCode
.
@Override
public int hashCode() {
return Objects.hash(field1, field2, field3);
}
Obs: detta tillvägagångssätt är ineffektivt och producerar sopobjekt varje gång din anpassade hashCode()
metod kallas:
- Ett tillfälligt
Object[]
skapas. (IObjects.hash()
skapas matrisen av mekanismen "varargs".) - Om något av fälten är primitiva typer, måste de vara i rutan och det kan skapa mer tillfälliga objekt.
- Matrisen måste fyllas i.
- Arrayen måste itereras med
Arrays.hashCode
ellerObjects.hash
. -
Object.hashCode()
tillObject.hashCode()
somArrays.hashCode
ellerObjects.hash
måste ringa (antagligen) kan inte inriktas.
Intern cachning av hashkoder
Eftersom beräkningen av ett objekts hashkod kan vara dyr kan det vara attraktivt att cachera hashkodvärdet inom objektet första gången det beräknas. Till exempel
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;
}
}
Detta tillvägagångssätt handlar om kostnaden för (upprepade gånger) att beräkna hashkoden mot omkostnaderna för ett extra fält för att cache hashkoden. Huruvida detta lönar sig som prestationsoptimering beror på hur ofta ett visst objekt hashas (letas upp) och andra faktorer.
Du kommer också att märka att om den riktiga hashkoden för en ImmutableArray
råkar vara noll (en chans på 2 32 ), är cachen ineffektiv.
Slutligen är detta tillvägagångssätt mycket svårare att implementera korrekt om objektet vi har hash är muterbara. Det finns emellertid större problem om hash-koder ändras. se kontraktet ovan.
vänta () och meddela () metoder
wait()
och notify()
arbeta i tandem - när en tråd samtal wait()
på ett objekt kommer den tråden att blockera tills en annan trådsamtal notify()
eller notifyAll()
på samma objekt.
(Se också: vänta () / meddela () )
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!");
}
}
Några exempel:
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 () -metoden
getClass()
kan användas för att hitta ett objekts runtime class-typ. Se exemplet nedan:
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()
kommer att returnera den mest specifika klasstypen, varför när getClass()
anropas på anotherSpecificUser
är returvärdet class SpecificUser
eftersom det är lägre ner i arvträdet än User
.
Det är anmärkningsvärt att getClass
metoden förklaras som:
public final native Class<?> getClass();
Den faktiska statiska typen som returneras av ett samtal till getClass
är Class<? extends T>
där T
är den statiska typen av objektet som getClass
kallas på.
dvs följande kommer att sammanställas:
Class<? extends String> cls = "".getClass();
klon () -metod
clone()
-metoden används för att skapa och returnera en kopia av ett objekt. Denna metod som kan diskuteras bör undvikas eftersom den är problematisk och en kopieringskonstruktör eller någon annan metod för kopiering bör användas till förmån för clone()
.
För att metoden ska användas måste alla klasser som anropar metoden implementera Cloneable
gränssnittet.
Själva Cloneable
gränssnittet är bara ett taggränssnitt som används för att ändra beteendet för den native
clone()
-metoden som kontrollerar om den anropande Cloneable
implementerar Cloneable
. Om den som ringer inte implementerar detta gränssnitt CloneNotSupportedException
en CloneNotSupportedException
.
Object
implementerar inte detta gränssnitt så att en CloneNotSupportedException
kommer att kastas om det anropande objektet är av Object
.
För att en klon ska vara korrekt bör den vara oberoende av objektet den klonas från, därför kan det vara nödvändigt att modifiera objektet innan det returneras. Detta innebär att väsentligen skapa en "djup kopia" genom att också kopiera något av de muterbara föremål som utgör den inre strukturen för objektet som klonas. Om detta inte implementeras korrekt kommer det klonade objektet inte att vara oberoende och har samma referenser till de muterbara objekten som objektet som det klonades från. Detta skulle resultera i inkonsekvent beteende eftersom alla förändringar av dem i det ena skulle påverka den andra.
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);
}
}
}
slutföra () -metoden
Detta är en skyddad och icke-statisk metod för Object
. Den här metoden används för att utföra vissa slutliga operationer eller rensa operationer på ett objekt innan det tas bort från minnet.
Enligt doktorn kallas denna metod av avfallssamlaren på ett objekt när skräpuppsamlingen avgör att det inte finns fler referenser till objektet.
Men det finns inga garantier för att finalize()
-metoden skulle ringas om objektet fortfarande kan nås eller om inga sopor samlar körs när objektet blir berättigat. Det är därför det är bättre att inte lita på den här metoden.
I Java-kärnbibliotek kan vissa användningsexempel hittas, till exempel i 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();
}
}
I det här fallet är det den sista chansen att stänga resursen om resursen inte har stängts tidigare.
I allmänhet anses det vara dålig praxis att använda finalize()
-metoden i applikationer av något slag och bör undvikas.
Finaliserare är inte avsedda för att frigöra resurser (t.ex. stänga filer). Soporuppsamlaren blir uppringd när (om!) Systemet går lite på högutrymmet. Du kan inte lita på att det ska ringas när systemet tar slut på filhandtag eller av någon annan anledning.
Det avsedda användningsfallet för slutbehandlare är för ett objekt som håller på att återkrävas för att meddela något annat objekt om dess överhängande undergång. En bättre mekanism finns nu för detta ändamål --- klassen java.lang.ref.WeakReference<T>
. Om du tror att du behöver skriva en finalize()
metod, bör du undersöka om du kan lösa samma problem med hjälp av WeakReference
istället. Om det inte löser ditt problem, kan du behöva tänka om din design på en djupare nivå.
För ytterligare läsning här är en artikel om finalize()
metod från "Effektiv Java" -bok av Joshua Bloch.
Objektkonstruktör
Alla konstruktörer i Java måste ringa till Object
. Detta görs med samtalet super()
. Detta måste vara den första raden i en konstruktör. Anledningen till detta är så att objektet faktiskt kan skapas på högen innan ytterligare initialisering utförs.
Om du inte anger anropet till super()
i en konstruktör kommer kompilatorn att lägga in det åt dig.
Så alla dessa tre exempel är funktionellt identiska
med uttryckligt uppmaning till super()
konstruktör
public class MyClass {
public MyClass() {
super();
}
}
med implicit uppmaning till super()
konstruktör
public class MyClass {
public MyClass() {
// empty
}
}
med implicit konstruktör
public class MyClass {
}
Vad sägs om konstruktör-kedja?
Det är möjligt att kalla andra konstruktörer som en första instruktion av en konstruktör. Eftersom både det uttryckliga samtalet till en superkonstruktör och samtalet till en annan konstruktör måste vara båda de första instruktionerna, är de ömsesidigt exklusiva.
public class MyClass {
public MyClass(int size) {
doSomethingWith(size);
}
public MyClass(Collection<?> initialValues) {
this(initialValues.size());
addInitialValues(initialValues);
}
}
Att ringa ny MyClass(Arrays.asList("a", "b", "c"))
kommer att ringa den andra konstruktören med List-argumentet, som i sin tur delegerar till den första konstruktören (som delegerar implicit till super()
) och ring sedan addInitialValues(int size)
med listans andra storlek. Detta används för att minska kodduplikering där flera konstruktörer behöver göra samma arbete.
Hur ringer jag en specifik konstruktör?
Med hjälp av exemplet ovan kan man antingen kalla new MyClass("argument")
eller new MyClass("argument", 0)
. Med andra ord, precis som metodöverbelastning , ringer du bara konstruktören med de parametrar som är nödvändiga för din valda konstruktör.
Vad kommer att hända i Objektklasskonstruktören?
Ingenting mer än skulle hända i en underklass som har en standard tom konstruktör (minus samtalet till super()
).
Standard tomkonstruktör kan definieras uttryckligen men om inte kommer kompilatorn att lägga in den för dig så länge inga andra konstruktörer redan är definierade.
Hur skapas ett objekt sedan från konstruktören i Object?
Den faktiska skapandet av objekt är ned till JVM. Varje konstruktör i Java visas som en speciell metod med namnet <init>
som är ansvarig för att initialisera. Denna <init>
-metod tillhandahålls av kompilatorn och eftersom <init>
inte är en giltig identifierare i Java kan den inte användas direkt på språket.
Hur åberopar JVM denna
<init>
-metod?
JVM kommer att åberopa <init>
-metoden med hjälp av den invokespecial
instruktionen och kan endast åberopas i invokespecial
.
För mer information, titta på JVM-specifikationen och Java Language Specification:
- Special Methods (JVM) - JVMS - 2.9
- Konstruktörer - JLS - 8.8