Recherche…


Introduction

Cette page de documentation permet d’afficher des détails avec des exemples concernant les constructeurs de classes java et les méthodes de classe d’objets qui sont automatiquement héritées de l’ Object superclasse de toute classe nouvellement créée.

Syntaxe

  • Classe native finale publique <?> getClass ()
  • public final final void notify ()
  • public final final void notifyAll ()
  • Attente de void final native publique (long timeout) lève InterruptedException
  • public final void wait () lève InterruptedException
  • attente finale publique vide (long timeout, int nanos) lève InterruptedException
  • public natif Int hashCode ()
  • booléen public égal à (objet obj)
  • public String toString ()
  • Objet protégé natif clone () lève CloneNotSupportedException
  • protected void finalize () lance Throwable

méthode toString ()

La toString() permet de créer une représentation String d'un objet en utilisant le contenu de l'objet. Cette méthode doit être remplacée lors de l'écriture de votre classe. toString() est appelé implicitement lorsqu'un objet est concaténé en une chaîne comme dans "hello " + anObject .

Considérer ce qui suit:

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"
    }   
}

Ici, toString() from Object class est remplacé dans la classe User pour fournir des données significatives concernant l'objet lors de son impression.

Lors de l'utilisation de println() , la toString() l'objet est appelée implicitement. Par conséquent, ces déclarations font la même chose:

System.out.println(user); // toString() is implicitly called on `user`
System.out.println(user.toString());

Si toString() n'est pas remplacé dans la classe User mentionnée ci-dessus, System.out.println(user) peut renvoyer User@659e0bfd ou une String similaire avec presque aucune information utile à l'exception du nom de la classe. Cela sera dû au fait que l'appel utilisera l'implémentation de toString() de la classe d' Object Java de base qui ne connaît rien de la structure ou des règles métier de la classe User . Si vous voulez modifier cette fonctionnalité dans votre classe, remplacez simplement la méthode.

méthode equals ()

TL; DR

== teste l'égalité de référence (qu'ils soient le même objet )

.equals() teste l'égalité des valeurs (qu'elles soient logiquement "égales" )


equals() est une méthode utilisée pour comparer deux objets pour l'égalité. L'implémentation par défaut de la méthode equals() dans la classe Object renvoie true si et seulement si les deux références pointent vers la même instance. Il se comporte donc comme la comparaison par == .

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

Même si foo1 et foo2 sont créés avec les mêmes champs, ils pointent vers deux objets différents en mémoire. Par conséquent, l'implémentation equals() par défaut equals() évaluée à false .

Pour comparer le contenu d'un objet à l'égalité, equals() doit être remplacé.

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

Ici, la méthode equals() surchargée décide que les objets sont égaux si leurs champs sont identiques.

Notez que la hashCode() a également été remplacée. Le contrat de cette méthode indique que lorsque deux objets sont égaux, leurs valeurs de hachage doivent également être identiques. C'est pourquoi il faut presque toujours remplacer hashCode() et equals() ensemble.

Portez une attention particulière au type d’argument de la méthode equals . C'est Object obj , pas Foo obj . Si vous mettez cette dernière dans votre méthode, cela ne remplace pas la méthode equals .

Lors de l'écriture de votre propre classe, vous devrez écrire une logique similaire lors de la substitution de equals() et hashCode() . La plupart des IDE peuvent automatiquement générer cela pour vous.

Un exemple d'implémentation equals() peut être trouvé dans la classe String , qui fait partie de l'API Java principale. Plutôt que de comparer des pointeurs, la classe String compare le contenu de la String .

Java SE 7

Java 1.7 a introduit la classe java.util.Objects qui fournit une méthode pratique, equals , qui compare deux références potentiellement null , afin de simplifier les implémentations de la méthode 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);
}

Comparaison de classes

Comme la méthode equals peut s'exécuter sur n'importe quel objet, l'une des premières actions de la méthode (après avoir vérifié la valeur null ) consiste à vérifier si la classe de l'objet comparé correspond à la classe en cours.

@Override
public boolean equals(Object obj) {
    //...check for null
    if (getClass() != obj.getClass()) {
        return false;
    }
    //...compare fields
}

Cela se fait généralement comme ci-dessus en comparant les objets de classe. Cependant, cela peut échouer dans quelques cas particuliers qui peuvent ne pas être évidents. Par exemple, certains frameworks génèrent des proxies dynamiques de classes et ces proxy dynamiques sont en fait une classe différente. Voici un exemple d'utilisation de 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
}

Un mécanisme pour contourner cette limitation consiste à comparer les classes en utilisant instanceof

@Override
public final boolean equals(Object obj) {
    if (!(obj instanceof Foo)) {
        return false;
    }
    //...compare fields
}

Cependant, il y a quelques pièges à éviter lors de l'utilisation de instanceof . Puisque Foo pourrait potentiellement avoir d'autres sous-classes et que ces sous-classes pourraient remplacer equals() vous pourriez entrer dans un cas où un Foo est égal à un FooSubclass mais le FooSubclass n'est pas égal à Foo .

Foo foo = new Foo(7);
FooSubclass fooSubclass = new FooSubclass(7, false);
foo.equals(fooSubclass) //true
fooSubclass.equals(foo) //false

Cela viole les propriétés de symétrie et de transitivité et constitue donc une implémentation invalide de la méthode equals() . Par conséquent, lorsque vous utilisez instanceof , une bonne pratique consiste à rendre la méthode equals() final (comme dans l'exemple ci-dessus). Cela garantira qu'aucune sous-classe ne remplace equals() et viole les hypothèses clés.

Méthode hashCode ()

Lorsqu'une classe Java remplace la méthode equals , elle devrait également remplacer la méthode hashCode . Comme défini dans le contrat de la méthode :

  • Chaque fois qu'il est appelé sur le même objet plus d'une fois lors de l'exécution d'une application Java, la méthode hashCode doit systématiquement renvoyer le même entier, à condition qu'aucune information utilisée dans les comparaisons d'égal à égal sur l'objet ne soit modifiée. Cet entier ne doit pas nécessairement rester cohérent d'une exécution d'une application à une autre exécution de la même application.
  • Si deux objets sont égaux selon la méthode equals(Object) , alors l'appel de la méthode hashCode sur chacun des deux objets doit produire le même résultat entier.
  • Il n'est pas obligatoire que si deux objets sont inégaux selon la méthode equals(Object) , alors l'appel de la méthode hashCode sur chacun des deux objets doit produire des résultats entiers distincts. Cependant, le programmeur doit savoir que produire des résultats entiers distincts pour des objets inégaux peut améliorer les performances des tables de hachage.

Les codes de hachage sont utilisés dans les implémentations de hachage telles que HashMap , HashTable et HashSet . Le résultat de la fonction hashCode détermine le compartiment dans lequel un objet sera placé. Ces implémentations de hachage sont plus efficaces si l'implémentation hashCode fournie est bonne. Une propriété importante de l'implémentation de hashCode est que la distribution des valeurs hashCode est uniforme. En d'autres termes, il existe une faible probabilité que de nombreuses instances soient stockées dans le même compartiment.

Un algorithme de calcul d'une valeur de code de hachage peut être similaire à celui-ci:

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;
    }
}

Utiliser Arrays.hashCode () comme raccourci

Java SE 1.2

Dans Java 1.2 et versions ultérieures, au lieu de développer un algorithme pour calculer un code de hachage, vous pouvez en générer un en utilisant java.util.Arrays#hashCode en fournissant un tableau Object ou primitives contenant les valeurs de champ:

@Override
public int hashCode() {
    return Arrays.hashCode(new Object[] {field1, field2, field3});
}
Java SE 7

Java 1.7 a introduit la classe java.util.Objects qui fournit une méthode pratique, hash(Object... objects) , qui calcule un code de hachage basé sur les valeurs des objets qui lui sont fournis. Cette méthode fonctionne comme java.util.Arrays#hashCode .

@Override
public int hashCode() {
    return Objects.hash(field1, field2, field3);
}

Remarque: cette approche est inefficace et produit des objets indésirables chaque fois que votre hashCode() personnalisée est appelée:

  • Un Object[] temporaire Object[] est créé. (Dans la version Objects.hash() , le tableau est créé par le mécanisme "varargs".)
  • Si l'un des champs est de type primitif, il doit être encadré et créer plus d'objets temporaires.
  • Le tableau doit être rempli.
  • Le tableau doit être itéré par la méthode Arrays.hashCode ou Objects.hash .
  • Les appels à Object.hashCode() Arrays.hashCode ou Objects.hash doit effectuer (probablement) ne peuvent pas être intégrés.

Mise en cache interne des codes de hachage

Étant donné que le calcul du code de hachage d'un objet peut être coûteux, il peut être intéressant de mettre en cache la valeur du code de hachage dans l'objet la première fois qu'il est calculé. Par exemple

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;
    }
}

Cette approche permet d'échanger le coût de (répétition) du calcul du code de hachage par rapport à la surcharge d'un champ supplémentaire pour mettre en cache le code de hachage. Que cela soit rentable en tant qu'optimisation des performances dépendra de la fréquence à laquelle un objet donné est haché (recherché) et d'autres facteurs.

Vous remarquerez également que si le vrai hashcode d'un ImmutableArray arrive à zéro (une chance sur 2 32), le cache est inefficace.

Enfin, cette approche est beaucoup plus difficile à implémenter correctement si l’objet que nous sommes en train de hacher est mutable. Cependant, il y a de plus grandes préoccupations si les codes de hachage changent; voir le contrat ci-dessus.

Méthodes wait () et notify ()

wait() et notify() fonctionnent en tandem - quand un thread appelle wait() sur un objet, ce thread va bloquer jusqu'à ce qu'un autre thread appelle notify() ou notifyAll() sur le même objet.

(Voir aussi: 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!");
    }
}

Un exemple de sortie:

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!

Méthode getClass ()

La méthode getClass() peut être utilisée pour rechercher le type de classe d'exécution d'un objet. Voir l'exemple ci-dessous:

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"
}

La méthode getClass() renvoie le type de classe le plus spécifique, ce qui explique pourquoi lorsque getClass() est appelée sur un anotherSpecificUser , la valeur getClass() est la class SpecificUser car celle-ci est inférieure à celle de l' User .


Il est à noter que, tandis que la méthode getClass est déclarée comme:

public final native Class<?> getClass();

Le type statique réel renvoyé par un appel à getClass est Class<? extends T>T est le type statique de l'objet sur lequel getClass est appelé.

c'est à dire que ce qui suit compilera:

Class<? extends String> cls = "".getClass();

méthode clone ()

La méthode clone() est utilisée pour créer et renvoyer une copie d'un objet. Cette méthode discutable devrait être évitée car elle pose problème et un constructeur de copie ou une autre méthode de copie devrait être utilisée en faveur de clone() .

Pour que la méthode soit utilisée, toutes les classes appelant la méthode doivent implémenter l'interface Cloneable .

L'interface Cloneable elle-même n'est qu'une interface de balise utilisée pour modifier le comportement de la méthode clone() native qui vérifie si la classe d'objets appelants implémente Cloneable . Si l'appelant CloneNotSupportedException pas cette interface, une CloneNotSupportedException sera lancée.

La classe Object elle-même CloneNotSupportedException pas cette interface, donc une CloneNotSupportedException sera lancée si l'objet appelant est de classe Object .

Pour qu'un clone soit correct, il doit être indépendant de l'objet à partir duquel il est cloné. Par conséquent, il peut être nécessaire de modifier l'objet avant qu'il ne soit renvoyé. Cela signifie essentiellement créer une "copie profonde" en copiant également l'un des objets mutables qui constituent la structure interne de l'objet en cours de clonage. Si cela n'est pas implémenté correctement, l'objet cloné ne sera pas indépendant et aura les mêmes références aux objets mutables que l'objet à partir duquel il a été cloné. Cela se traduirait par un comportement incohérent, car toute modification de l'un de ces éléments affecterait l'autre.

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);
        }
    }
}

méthode finalize ()

C'est une méthode protégée et non statique de la classe Object . Cette méthode est utilisée pour effectuer certaines opérations finales ou pour effectuer des opérations de nettoyage sur un objet avant qu'il ne soit supprimé de la mémoire.

Selon le document, cette méthode est appelée par le ramasse-miettes sur un objet lorsque la récupération de la mémoire détermine qu'il n'y a plus de références à l'objet.

Mais il n'y a aucune garantie que la méthode finalize() soit appelée si l'objet est toujours accessible ou si aucun récupérateur de place n'est exécuté lorsque l'objet devient éligible. C'est pourquoi il vaut mieux ne pas compter sur cette méthode.

Dans les librairies Java, des exemples d'utilisation ont pu être trouvés, par exemple dans 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();
    }
}

Dans ce cas, c'est la dernière chance de fermer la ressource si cette ressource n'a pas encore été fermée.

En général, l'utilisation de la méthode finalize() dans des applications de toute nature est considérée comme une mauvaise pratique et devrait être évitée.

Les finaliseurs ne sont pas destinés à libérer des ressources (par exemple, la fermeture de fichiers). Le ramasse-miettes est appelé lorsque (si!) Le système manque de place sur le tas. Vous ne pouvez pas compter sur elle pour être appelée lorsque le système est à court de descripteurs de fichiers ou pour toute autre raison.

Le cas d'utilisation prévu pour les finaliseurs concerne un objet sur le point d'être récupéré pour notifier un autre objet de son sort imminent. Un meilleur mécanisme existe maintenant à cet effet - la classe java.lang.ref.WeakReference<T> . Si vous pensez avoir besoin d'écrire une méthode WeakReference finalize() , alors vous devriez vérifier si vous pouvez résoudre le même problème en utilisant WeakReference place. Si cela ne résout pas votre problème, vous devrez peut-être repenser votre conception à un niveau plus profond.

Pour plus de lecture, voici un article sur la méthode finalize() du livre "Effective Java" de Joshua Bloch.

Constructeur d'objet

Tous les constructeurs de Java doivent appeler le constructeur Object . Ceci est fait avec l'appel super() . Ce doit être la première ligne d'un constructeur. La raison en est que l'objet peut réellement être créé sur le segment de mémoire avant toute initialisation supplémentaire.

Si vous ne spécifiez pas l'appel à super() dans un constructeur, le compilateur le mettra pour vous.

Donc, ces trois exemples sont fonctionnellement identiques

avec appel explicite au constructeur super()

public class MyClass {

    public MyClass() {
        super();
    }
}

avec appel implicite au constructeur super()

public class MyClass {

    public MyClass() {
        // empty
    }
}

avec constructeur implicite

public class MyClass {

}

Qu'en est-il du constructeur-chaînage?

Il est possible d'appeler d'autres constructeurs comme première instruction d'un constructeur. Comme l'appel explicite à un super constructeur et l'appel à un autre constructeur doivent être les deux premières instructions, elles s'excluent mutuellement.

public class MyClass {

    public MyClass(int size) {

        doSomethingWith(size);

    }

    public MyClass(Collection<?> initialValues) {

        this(initialValues.size());
        addInitialValues(initialValues);
    }
}

L'appel de nouveau MyClass(Arrays.asList("a", "b", "c")) appellera le second constructeur avec l'argument List, qui à son tour déléguera au premier constructeur (qui déléguera implicitement à super() ), puis appelez addInitialValues(int size) avec la deuxième taille de la liste. Ceci permet de réduire la duplication de code lorsque plusieurs constructeurs doivent effectuer le même travail.

Comment appeler un constructeur spécifique?

Compte tenu de l'exemple ci-dessus, on peut appeler new MyClass("argument") ou new MyClass("argument", 0) . En d'autres termes, tout comme la surcharge de méthodes , il vous suffit d'appeler le constructeur avec les paramètres nécessaires au constructeur choisi.

Que se passera-t-il dans le constructeur de la classe Object?

Rien de plus que cela n'arriverait dans une sous-classe qui a un constructeur vide par défaut (moins l'appel à super() ).

Le constructeur vide par défaut peut être explicitement défini, mais sinon le compilateur le mettra pour vous tant qu'aucun autre constructeur n'est déjà défini.

Comment un objet est-il alors créé à partir du constructeur dans Object?

La création réelle des objets incombe à la machine virtuelle Java. Chaque constructeur en Java apparaît comme une méthode spéciale nommée <init> qui est responsable de l'initialisation de l'instance. Cette méthode <init> est fournie par le compilateur et comme <init> n'est pas un identifiant valide en Java, il ne peut pas être utilisé directement dans le langage.

Comment la JVM appelle-t-elle cette méthode <init> ?

La JVM invokespecial la méthode <init> à l'aide de l'instruction invokespecial et ne peut être invoquée que sur des instances de classe non initialisées.

Pour plus d'informations, consultez la spécification JVM et la spécification du langage Java:



Modified text is an extract of the original Stack Overflow Documentation
Sous licence CC BY-SA 3.0
Non affilié à Stack Overflow