Java - java.lang.Object
Introduction
- Connaître et comprendre les méthodes fournies par la classe java.lang.Object
- Avoir créé ta propre classe de base en vue d'un usage futur
Quand on écrit nos propres classes, comme la classe Person ci-dessous, Java cache certains détails qu'il est bon de connaître. Prends par exemple la classe Java suivante
public class Person {
// ...
}
Cet extrait de code cache un détail important : chaque classe que l'on écrit hérite de la classe java.lang.Object. Si on omet la clause extends, le compilateur Java l'ajoute pour nous. Le code source complet devrait ressembler à ceci pour être exhaustif
public class Person extends Object {
// ...
}
Mais comme le compilateur ajoute la clause extends à notre place, on ne doit pas l'écrire dans notre code source.
public class Person /* extends Object */ {
// ...
}
Tu pourrais choisir n'importe quelle classe comme classe de base pour tes propres classes, mais au bout du compte, toutes les classes hériteront de java.lang.Object. On pourrait choisir la classe String comme classe de base pour notre Person, mais la classe String hérite elle-même de java.lang.Object.
public class Person extends String {
// ...
}
La classe java.lang.Object est, directement ou indirectement, la classe de base de toutes les classes définies. Toutes les classes forment un arbre dont la racine est java.lang.Object.

La classe racine, java.lang.Object, est fournie par Java. Imagine un instant qu'un ingénieur logiciel chez Sun ait écrit cette classe exactement comme on vient de le faire. Cet ingénieur a dû réfléchir aux propriétés et aux méthodes de cette classe.
☝ La méthode equals
boolean equals(Object obj)
La méthode equals() offre un moyen de comparer deux instances d'objets et de détecter l'égalité. Les deux instances n'ont même pas besoin d'être du même type.
L'implémentation par défaut de cette méthode compare la référence this à la référence d'objet fournie par le paramètre obj.
Person person1 = new Person();
person1.setLastname("Paul");
Person person2 = new Person();
person2.setFirstname("Paul");
if(person1 == person2) {
System.out.println("person1 and person2 are equal")
}
if(person1.equals(person2) {
System.out.println("person1 and person2 are the same");
}
Comme on l'a déjà vu, créer une nouvelle instance avec new alloue de la mémoire pour l'objet. Quand on crée deux instances, on alloue de la mémoire deux fois. Ici, on écrit la valeur de chaîne "Paul" dans les deux instances d'objets.
Comme les deux instances d'objets se trouvent à des emplacements différents en mémoire, comparer la référence avec == donnera false et on en conclurait que les instances ne sont pas les mêmes. Techniquement, c'est vrai.
Mais que faire si les deux instances représentent une seule et même personne nommée "Paul" ? Ce serait cette personne représentée deux fois. Quand on compare les deux instances, on aimerait donc que le résultat soit true.
On peut y arriver en fournissant notre propre méthode pour comparer les instances d'objets
public class Person {
@Override
public boolean equals(Object obj) {
if (obj == null) // null check
{
return false;
}
if (this == obj) // self check
{
return true;
}
if (getClass() != obj.getClass()) // type check
return false;
}
// type check and cast
Person person = (Person) obj; // cast to Person
return Objects.equals(firstname, person.firstname)
&& Objects.equals(lastname, person.lastName);
}
}
Dans ce code, on utilise la méthode statique equals() de la classe Objects. C'est une classe utilitaire (helper, classe d'aide regroupant des méthodes pratiques) qui fournit un equals null-safe (qui gère les références nulles sans planter). Elle fonctionne avec des références null et renvoie un résultat valide. L'implémentation est plutôt simple
public class Objects {
public static boolean equals(Object a, Object b)
{
return (a == b) || (a != null && a.equals(b));
}
}
Règles pour implémenter equals
Notre méthode equals, pour toute référence non nulle, doit être
réflexive
Pour toute instance d'objet x, le résultat de x.equals(x) doit être true
symétrique
Pour deux instances d'objets x et y, le résultat de x.equals(y) et de y.equals(x) doit être identique
transitive
Pour trois instances d'objets x, y et z, si x.equals(y) est true et y.equals(z) est true, alors x.equals(z) est également true
cohérente (consistent)
Pour plusieurs appels de x.equals(y), le résultat est toujours le même
non égale à null
Pour x.equals(null), le résultat doit être false
Garde ces règles importantes en tête : certaines implémentations de equals ne respectent pas ces règles, ce qui peut causer beaucoup de problèmes
int hashCode()
String toString()
Renvoie une chaîne représentant cette instance. L'implémentation par défaut affiche le nom canonique (canonical name, nom complet de la classe, package compris) de la classe
Class getClass()
Cette méthode fournit une instance de la classe qui représente, à l'exécution, la classe telle qu'elle est écrite dans le code source. Avec cette classe, tu as accès au plan (blueprint, modèle de la classe tel qu'il est écrit dans le code source) de la classe. Tu pourrais utiliser ces informations pour générer le code source à partir du byte code (code intermédiaire compilé exécuté par la machine virtuelle Java).
obj.getClass().getName(); // class name without package information
obj.getClass().getCanonicalName(); // class name including package
Cette méthode est utilisée par la méthode toString() pour obtenir la représentation en chaîne de la classe.
Est-ce que tout est un Object ?
Avec un langage de programmation orienté objet, on s'attendrait à ce que tout soit un objet ou, autrement dit, à ce que tout hérite de java.lang.Object. Mais ce n'est pas le cas avec Java, où beaucoup de choses ne sont pas des objets.
- les types de données primitifs (primitive data types, types de base comme int, boolean, char...)
- les instructions de contrôle de flux (control flow statements, if, for, while...)
Les types de données primitifs ne sont pas orientés objet. Ces types existent parce qu'ils reflètent l'architecture de la machine. Un type de données entier est un entier 32 bits parce que la machine virtuelle Java est un ordinateur 32 bits.
Mélanger objets et non-objets pose quelques problèmes. Pour les résoudre, Java fournit une classe pour chaque type de données primitif
- boolean et Boolean
- byte et Byte
- short et Short
- int et Integer
- long et Long
- float et Float
- double et Double
- char et Character
Ces classes sont de simples enveloppes (wrappers, classes qui emballent un type primitif dans un objet) autour des types de données primitifs
Garde en tête que la taille maximale requise pour n'importe quel type de données fondamental est de 64 bits, mais bien plus grande pour chaque instance de la classe wrapper. De plus, il te faut au moins une variable de référence pour accéder à l'instance de la classe wrapper.
Tu pourrais utiliser des instances des classes wrapper à la place des types de données primitifs, pour rester cohérent et ne manipuler que des objets, mais cela ralentirait l'exécution de ton programme. Pour accélérer les choses, les types de données primitifs sont toujours utilisés.
Questions
Qu'est-ce qu'un nom de classe canonique
Pourquoi n'est-il pas possible d'appeler des méthodes sur une référence null ?
Exercice
En guise d'exercice, on va créer notre propre classe de base qui fournit quelques méthodes, on l'espère, utiles
-
une propriété qui identifie de manière unique n'importe quelle instance d'objet
-
une propriété parent pour désigner l'ordre des objets
-
avoir une implémentation pour equals() et hashCode()
Introduction
- Know and understand the methods provided by the java.lang.Object class
- Have create your own base class for future use
When we write our own classes like the following Person class, Java hides some details that we should know about. For an example, look at the following Java class
public class Person {
// ...
}
The code snippet hides an important detail: Every class we write inherits from the java.lang.Object class. If we omit the extends clause, the Java compiler will add it for us. The full source code would have to look like the following to be complete
public class Person extends Object {
// ...
}
But since the compiler will provides the extends clause for us, we should not include it in our source code.
public class Person /* extends Object */ {
// ...
}
You could choose any class you want as the base class for your own classes but eventually, all classes will inherit from java.lang.Object. We could choose the String class as the base class for our Person, but the String class will inherit from java.lang.Object.
public class Person extends String {
// ...
}
The java.lang.Object class is either directly or indirectly the base class for every defined class. All classes build a tree with java.lang.Object at the root of the tree.

The root class, java.lang.Object is provided by Java. Just imagine for a moment that some software engineer at Sun wrote that class just like we did. This engineer had to think about the properties and methods for this class.
☝ The equals method
boolean equals(Object obj)
The equals() method provides a way to compare two object instances and detect equality. The instance may not even have the same type.
The default implementation of this method compares the this reference to the object reference provided by the obj parameter.
Person person1 = new Person();
person1.setLastname("Paul");
Person person2 = new Person();
person2.setFirstname("Paul");
if(person1 == person2) {
System.out.println("person1 and person2 are equal")
}
if(person1.equals(person2) {
System.out.println("person1 and person2 are the same");
}
As we already learned, creating a new instance with new will allocate memory for the object. When we create two instances, we will allocate memory twice. In this case, we will write the string value "Paul" to both object instances.
Since both object instances are located at different locations in memory, comparing the reference with == will yield false and we would conclude that the object instances are not the same. Technically, this is true.
But what if both object instances represent a single person with the name "Paul"? It would be this person represented twice. When we compare both object instances, we would therefore like the result to be true.
We can achieve this if we provide our own method to compare object instances
public class Person {
@Override
public boolean equals(Object obj) {
if (obj == null) // null check
{
return false;
}
if (this == obj) // self check
{
return true;
}
if (getClass() != obj.getClass()) // type check
return false;
}
// type check and cast
Person person = (Person) obj; // cast to Person
return Objects.equals(firstname, person.firstname)
&& Objects.equals(lastname, person.lastName);
}
}
Within this code, we use the static equals() method from the Objects class. This is a helper class that provides a null-safe equals. It will work with null references and provide a valid result. The implementation is rather simple
public class Objects {
public static boolean equals(Object a, Object b)
{
return (a == b) || (a != null && a.equals(b));
}
}
Rules for implementing equals
Our equals method for any non-null reference should be
reflexive
For any object instance x, the result of x.equals(x) should be true
symetric
For any two object instances x and y, the result of x.equals(y) and y.equals(x) should be the same
transitive
For any three object intances x, y and z, if x.equals(y) is true and y.equals(z) is true, then x.equals(z) is also true
consistent
For multiple invocations of x.equals(y), the result is always the same
not equal to null
For x.equals(null), the result should be false
Keep these important rulse in mind - some implementations of equals do not adhere to these rules which can cause a lot of trouble
int hashCode()
String toString()
Returns a string representing this instance. The default implementation will print the canonical name of the class
Class getClass()
This method provides an instance of the class that represents the class from the source code at runtime. With the class, you have access to the blueprint of the class as it is written in the source code. You could use the information to generate the source code from the byte code.
obj.getClass().getName(); // class name without package information
obj.getClass().getCanonicalName(); // class name including package
This method is used by the toString() method to get the string representation of the class.
Is everything an Object?
With an object oriented programming language, you would expect everything to be an object or, in other words, you would expect everything to be inherited from java.lang.Object. But this is not the case with Java where many things are not objects.
- primitive data types
- control flow statements
Primitive data types are not object oriented. These data types exist because they reflect the machine architecture. An integer data type is a 32-bit integer because the Java virtual machine is a 32-bit computer.
Mixing objects and non-objects imposes some problems. To solve these problems, Java provides classes for each primitive data type
- boolean and Boolean
- byte and Byte
- short and Short
- int and Integer
- long and Long
- float and Float
- double and Double
- char and Character
These classes are simple wrappers around the primitive data types
Keep in mind that the maximum size requirement for any fundamental data type is 64-bit but much larger for each instance of the wrapper class. In addition, you need at least one reference variable to access the instance of the wrapper class.
You could use instances of the wrapper classes instead of primitive data types to be consistent and only use objects but that would slow down the execution of your program. To speed things up, primitive data types are still in use.
Questions
What is a canonical class name
Why is it not possible to call methods on a null reference?
Excercise
As an excercise, we will create our own base class that provides some hopefully useful methods
-
a property that uniquely identifies any object instance
-
a parent property to designate order of objects
-
have an implementation for equals() and hashCode()