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 SE 7

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 ringa hashCode 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 ringa hashCode 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

Java SE 1.2

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 SE 7

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. (I Objects.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 eller Objects.hash .
  • Object.hashCode() till Object.hashCode() som Arrays.hashCode eller Objects.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:



Modified text is an extract of the original Stack Overflow Documentation
Licensierat under CC BY-SA 3.0
Inte anslutet till Stack Overflow