Toute classe dérive implicitement de la classe Object, membre du paquet java.lang. Autrement dit, les deux définitions suivantes sont équivalentes :
class A { ... } class A extends Object { ... }
Toute classe est donc un sous-type d'Object. Par suite, toute méthode déclarée avec un paramètre de type Object accepte en argument une instance de n'importe quelle classe (mais pas une valeur d'un type primitif). Tous les types de tableaux sont également des sous-types d'Object (ainsi que des interfaces Cloneable et java.io.Serializable). Ainsi, un tableau quelconque peut être affecté à une variable de type Object. Voici quelques unes des méthodes qui sont définies par la classe Object :
package java.lang; public class Object { public String toString() { ... } public final Class getClass() { ... } public boolean equals(Object o) { ... } public int hashCode() { ... } protected Object clone() throws CloneNotSupportedException { ... } ... }
La méthode toString retourne une représentation de l'objet par une chaîne de caractères. C'est cette méthode qui est invoquée quand un objet figure en argument de la méthode print ou println ou dans une expression de concaténation de chaînes. Il est souvent utile de la redéfinir, pour obtenir une chaîne de caractères plus lisible. Par exemple, la classe Point pourrait la redéfinir ainsi :
class Point { // ... public String toString() { return "(" + x + ", " + y + ")"; } }
Voici une définition de cette méthode pour le type d'énumération
Feu (voir § ), auquel on a rajouté un champ de
type String qui contient la chaîne à imprimer pour chaque
constante :
class Feu { private String couleur; static final Feu ROUGE = new Feu("Rouge"); static final Feu ORANGE = new Feu("Orange"); static final Feu VERT = new Feu("Vert"); private Feu(String couleur) { this.couleur = couleur; } public String toString() { return couleur; } }
La méthode getClass retourne une référence à une instance de la classe Class qui représente la classe de l'objet. Cette instance permet d'obtenir diverses informations sur cette classe (son nom, ses membres, sa surclasse, etc.), par exemple :
void printClassName() { System.out.println("La classe de " + this + " est " + this.getClass().getName()); }
La méthode equals permet de tester l'égalité de deux objets. La méthode définie dans la classe Object est la plus discriminante possible, ce qui signifie que x.equals(y) retourne true si et seulement si x et y sont des références au même objet, c'est-à-dire si x == y. Par exemple, si la classe Point ne redéfinit pas equals, la valeur de l'expression
new Point().equals(new Point())
qui compare deux instances différentes, retourne false. Il est donc utile de redéfinir equals. Dans le cas de la classe Point, un point est égal à un objet o si o est un point et si les champs correspondants sont égaux. L'expression o instanceof Point, de type boolean, permet de tester si le type de l'objet désigné par o est un sous-type de Point :
package geometrie; class Point { // ... public boolean equals(Object o) { return o instanceof Point && this.x == ((Point)o).x && this.y == ((Point)o).y; } }
Il serait tentant de définir une méthode qui compare seulement des instances de Point entre elles :
class Point { // ... boolean equals(Point p) { ... } }
Cependant, ce ne serait pas une redéfinition de la méthode equals de la classe Object, car elle n'a pas le même profil, donc le mécanisme de liaison tardive ne l'invoquera pas. Dans la situation suivante, l'égalité des deux points serait donc testée à l'aide de l'equals d'Object et non par celle de Point :
Object o = new Point(), p = new Point(1, 2); boolean b = o.equals(p);
La méthode clone permet de dupliquer un objet. Cette opération importante sera examinée plus loin.
On peut utiliser la classe Object pour transformer une structure de données en une structure générique, par exemple les arbres à noeuds de type int en arbres à noeuds de types référencés quelconques. Il suffit de remplacer int par Object. La définition de la classe devient :
package structures; class ArbreBinaire { Object étiquette; ArbreBinaire gauche, droit; ArbreBinaire(Object étiquette, ArbreBinaire gauche, ArbreBinaire droit) { this.étiquette = étiquette; this.gauche = gauche; this.droit = droit; } // ... }
On obtient ainsi des arbres génériques hétérogènes (ce qui signifie que les étiquettes ne sont pas nécessairement du même type). Pour passer un argument d'un type primitif à une méthode demandant un objet, on a recours aux classes enveloppantes Integer, Double, etc. :
ArbreBinaire t = new ArbreBinaire("toto", new ArbreBinaire(new Integer(3), null, null), new ArbreBinaire(new Double(0.4), null, null));
Rappelons que les chaînes littérales, comme "toto", sont des objets de classe String.