Datalbi  
Créé le 03/05/2016 par Laurent Bourcier

Java SE 8 Programmer : La Programmation Objet

Les packages

Un package permet de regrouper des classes dans un même espace de nom.

Ceci permet d'éviter des conflits de nom entre autre.

Un package est organisé en arborescence et les fichiers .class sont stockés dans une arborescence équivalente au découpage du package.

Les noms de package doivent être en minuscule. Les caractères autorisés sont les lettres, le "_" et le "$".

Exemple :

Package com.company.myapp :
/com
    /company
        /myapp
            MyClassOne.class
            MyClassTwo.class
            MyClassThree.class

Pour utiliser un package, il faut que le fichier .jar soit dans le CLASSPATH et utiliser la clause import afin de le pas devoir utiliser le fully qualified name.

Pour positionner une classe dans un package, il faut utiliser la clause package en toute premiere ligne.

Exemple :

package com.company.myapp;

import java.util.Arrays;
import java.awt.*;
import java.awt.events.*;
import static java.lang.Math.PI;   // import des constantes statiques

public class MyClassOne {
  // instructions
}

Un package contient :

Le nom de la classe, de l'interface, de l'enumeration doit etre le même que nom du fichier .java.

Le package java.lang est toujours importé par défaut.

Java core divisé en packages suivants qui sont présents dans le fichier rt.jar :

Java possède également des extensions, ce sont les packages commencant par javax :

Les classes

Une classe est constituée de champs et de méthodes. Son nom commence par une majuscule par convention.

Syntaxe :


[public] class MyClass [ extends ParentClass ] [ implements Interface_1, ..., Interface_n ] {
  // fields declaration
  // methods declaration
}

Syntaxe de la déclaration des champs :


[access modifiers] <type> <variable_name>;
// Exemple
public int age;

Par convention, le nom d'une méthode doit commencer par un verbe.

Syntaxe de la déclaration des methodes :


[access modifiers] <type> <method_name> ( [<parameters>] ) [throws exception] {
  // body 
};
// Exemple
public int getAge() {
  return this.age;
}

Le constructeur d'une classe est une méthode particulière.

Nom arbitraire d'arguments dans une méthode : il existe une syntaxe spéciale pour celà :


// Declaration
public int myFunction(int... myParam) {
  int argCount;
  
  argCount = myParam.length;
}

// Exemple d'appel : soit avec un nombre variable d'arguments, soit avec un tableau
myFunction(0,1,2);
int[] myArray = { 0, 1, 2 };
myFunction (myArray);

Le nom d'un parametre peut parfois être le meme que celui d'un champs de la classe (on parle de shadow field). Dans ce cas, le nom au sein de la méthode se réfère au paramètre, tandis que "this." se réfère au champs.

Le passage des arguments de type primitif se fait par valeur !

Le passage des arguments de type Objet se fait aussi par valeur (la référence de l'objet) !

Exemples :


int i = 1;
modifierInt(i);
// => la variable i n'est pas modifiée, même si on la modifie dans la méthode

myClass o;
o = new MyClass(1);
modifierObjet(o);
// => la variable o référence toujours le même objet mais cet objet a pu être modifié dans la méthode

Les modifieurs d'accès

Un membre peut avoir les modifieurs d'accès suivants : public, protected, private et aucun.

On remarque que par défaut, si aucun modifier n'est précisé, l'accès est "package-private" c'est-à-dire juste un peu plus large que private : la classe elle-meme peut y accéder en interne et les objets du même package peuvent y accéder.

Ceci est aussi valable pour les classes définies sans modifier : dans ce cas elle ne peut être accédée qu'à l'intérieur du package.

Une classe ne peut avoir que deux modifiers possibles : public et sans modifier.
Modifier Classe Package Sous-Classe Tout le monde
public X X X X
protected X X X -
nom défini X X - -
private X - - -

Les modifieurs static et final

Une variable de classe (non instantiée et valable pour toutes les instances d'une classe) est définié par le modifier "static".

Il en va de même pour les méthodes de classe.

Exemple :


class MyClass {
  static int MAX = 1000;
  
  static boolean isMax(int i) {
    return (i < MAX ? false : true);
  }
}

Le mot cle "final" sur un champs indique que la valeur ne peut pas être modifiée (donc read-only). C'est ainsi qu'on définit des constantes :


class MyClass {
  static final double PI = 3.14159265358979;
}

Le mot clé "final", lorsqu'il est défini au niveau d'une méthode, signifie que la méthode ne peut pas être redéfinie dans une sous-classe.

Le mot clé "final", lorsqu'il est défini au niveau d'une classe, signifie que la classe ne peut pas avoir de sous-classe.

Le modifier abstract

Le mot clé "abstract" devant une méthode signifie que la méthode doit être redéfinie dans la sous-classe.

Le mot clé "abstract" devant une classe signifie que la classe ne peut être instantiée. Il faut définir une sous-classe.

Remarque : le mot clé abstract est optionnel pour les méthodes d'interface. Ces méthodes sont abstract implicitement.

L'héritage

Une classe peut hériter des champs et des méthodes d'une autre classe en utilisant le mot clé "extends".

Une sous-classe hérite des membres public et protected mais elle n'hérite pas des membres private de la classe parent.

Une classe peut hériter d'une classe au plus. Pas défaut, tout classe hérite de la classe Object. Comme l'héritage multiple n'existe pas, on utilise les interfaces à la place.

Le constructeur n'est pas hérité car ce n'est pas un membre de classe.

Le constructeur peut être redéfini au niveau d'une sous-classe. Dans ce cas, le code du parent n'est pas exécuté.

Pour exécuter le code du constructeur parent, il faut appeller la méthode super(arguments) obligatoirement sur la première ligne.

Important : Si un constructeur dans la sous-classe ne fait pas appel à super, alors Java insère automatiquement un appel à super sans argument, et si ce constructeur n'existe pas, il y a une erreur à la compilation.


public class Animal {
  public Animal() {
    // instructions
  }
}

public class Chien extends Animal {
  public Chien() {
    super();
    // instructions
  }
}

Il est possible dans une sous-classe :

Il est conseillé d'utiliser l'annotation @override lorsqu'on redéfinit une méthode. On a ainsi moins de chance de se tromper de signature.


@Override
public void modifierValeur(int value) {
  // Utilisation possible de super :
  super.modifierValeur(value);
  // instructions
}

Il est possible de redéfinir une méthode de classe. Dans ce cas, elle ne sera valable que dans la sous-classe. On parle de hidding et non pas overriding.

Casting :

Un objet peut être affecté à une variable de la super classe mais pas l'inverse.


// Cas de casting implicite :
Bike bike = new MountainBike();

// Cas particulier de casting explicite :
Object obj = new MountainByke();
MountainByke m;
if (obj instanceof MountainByke) {
  m = (MountainByke) obj;
}

La classe Object

Par défaut, une classe qui n'hérite d'aucune classe va hériter de la classe Object.

Si une classe hérite d'une autre classe, elle héritera de déscendance, de la classe Object.

La classe java.lang.Object possède les méthodes suivantes, qu'il est possible de surcharger (override) :

Il est possible de redéfinir ces méthodes.

La classe String redéfinit déjà la méthode equals.

D'autres méthodes sont liées aux Threads et ne peuvent pas être modifiées (override) :

Les classes abstraites

Une classe abstraite est une classe qui ne peut pas être instanciée.

Une méthode abstraite est une méthode déclarée uniquement avec sa signature et qui doit être redéfinie dans la sous-classe. Elle définit en quelque sorte un "contrat de classe".

Une méthode abstraite doit toujours être définie dans une classe abstraite, mais une classe abstraite peut ne pas avoir de méthodes abstraites.

Une classe abstraite peut avoir pour enfant une classe abstraite.

Différence Classes abstraites / Interfaces :

Une interface peut avoir des champs, mais ils doivent être obligatoirement public static final

Une interface peut avoir des méthodes, mais elles doivent être obligatoirement public

On utilise donc une classe abstraite lorsque :

Les interfaces

Une interface est un type référence à l'instar d'une classe.

Une interface peut contenir :

Par défaut, toutes les méthodes sont implicitement public, donc le mot clé public est optionnel.


public interface IMyInterface {
  static final int MY_CONSTANT;
  
  public void start();
  public void stop();
  public int speedUp(int speed);
  public int speedDown(int speed);
}

L'interface définit un contrat pour la classe qui l'impémente (mot clé implements).

Une interface peut hériter d'une interface (mot clé extends) et même de plusieurs interfaces.

Une interface peut être :

Une classe peut hériter des méthodes et champs static de zéro ou plusieurs interfaces.


public class MyClass implements Interface1, Interface2 {
  public void start() {
    // instructions
  }
  public void stop() {
    // instructions
  }
  public int speedUp(int speed) {
    // instructions
  }
  public int speedDown(int speed) {
    // instructions
  }
}

Utilisation d'une interface comme un type

Un object o qui implémente l'interface i peut être manipulé comme un objet de type i.

Evolution d'une interface : default méthodes

Une premiere version d'interface se présente comme suit :


// Version 1
public interface DoIt {
   void doSomething(int i, double x);
   int doSomethingElse(String s);
}

On veut ajouter une troisieme methode alors que l'interface est déjà largement utilisé :


// Version 2
public interface DoIt {
   void doSomething(int i, double x);
   int doSomethingElse(String s);
   default boolean didItWork(int i, double x, String s) {
       // Method body 
   }
}

La default methode peut être implémentée (nouveaux programmes) ou pas (anciens programmes).

Cas Particulier : si une classe implémente deux interfaces qui ont chacunes une méthode de même nom avec la même signature, alors la classe doit redéfinir la méthode en question.

Le polymorphisme

Polymorphisme paramétrique :

Le polymorphistme consiste à écrire une méthode de plusieurs manières avec des signatures différentes (différence entre nombre et type d'arguments).

Polymorphisme d'héritage :

En redéfinissant une méthde dans une sous-classe, on peut spécialiser le comportement d'une méthode. Ainsi, selon que l'on sera dans la super classe ou la sous-classe, le résultat de l'appel sera différent.

L'encapsulation

L'encapsulation a pour but de cacher les champs d'une classe et permettre des modifications uniquement au travers de classes.

Un des aventages est de permettre d'établir des droits en lecture / écriture.


class Person {
  private String firstname;
  private String lastname;
  
  public String getFirstname() {
    return this.firstname;
  }
  public void setFirstname(String firstname) {
    this.firstname = firstname;
  }
  public String getLastname() {
    return this.lastname;
  }
  public void setLastname(String lastname) {
    this.lastname = lastname;
  }
}

Les nested classes

Une nested class est une classe définie à l'intérieur d'une autre. On les appelle aussi "helper class".

On distingue deux types de nested class :

Les nested class peuvent être déclarées public, private, protected ou package-private. Alors que la outer class ne peut être que public ou package private.


class OuterClass {
    ...
    static class StaticNestedClass {
        ...
    }
    class InnerClass {
        ...
    }
}

Utilisation d'une static class :


OuterClass.StaticNestedClass nestedObject = new OuterClass.StaticNestedClass();

Utilisation d'une inner class :


outerObject = new OuterClass();
OuterClass.InnerClass innerObject = outerObject.new InnerClass();

Intéret d'utiliser une nested class : regrouper le code au même endroit si cette classe n'est utilisée qu'à un seul endroit. On peut aussi augmenter l'encapsulation car la inner class peut accéder aux champs private de la outer class.

Priorité des variables

On parle de shadowing lorsqu'une variable de inner class éclipse une variable de outer class.

Exemple :


public class ShadowTest {

    public int x = 0;

    class FirstLevel {

        public int x = 1;

        void methodInFirstLevel(int x) {
            System.out.println("x = " + x);
            System.out.println("this.x = " + this.x);
            System.out.println("ShadowTest.this.x = " + ShadowTest.this.x);
        }
    }

    public static void main(String... args) {
        ShadowTest st = new ShadowTest();
        ShadowTest.FirstLevel fl = st.new FirstLevel();
        fl.methodInFirstLevel(23);
    }
}

Résultat :
x = 23
this.x = 1
ShadowTest.this.x = 0

Local classes versus anonymous classes :

Une Inner Class peut être une "local class" ou une "anonymous class".

Une local class est ce que l'on a vu jusqu'à présent.

Une anonymous class est différence dans sa déclaration : on mixe la déclaration avec l'opérateur new :


public void OuterClass() {
    interface HelloWorld {
        public void greet();
        public void greetSomeone(String someone);
    }
    
    HelloWorld frenchGreeting = new HelloWorld() {
        String name = "tout le monde";
        public void greet() {
          greetSomeone("tout le monde");
        }
        public void greetSomeone(String someone) {
          name = someone;
          System.out.println("Salut " + name);
        }
    };
}

Dans l'exemple ci-dessus, l'objet frenchGreeting implémente l'interface HelloWorld mais ne possède pas de classe à proprement parler.

Une "anonymous class" doit implémenter une interface ou hériter d'une classe.

Les "anonymous class" se rencontrent souvent dans les api des interfaces graphiques, comme dans l'exemple ci-dessous :


    // Implementation de l'interface EventHandler
    btn.setOnAction(new EventHandler<ActionEvent>() {
          @Override
          public void handle(ActionEvent event) {
              System.out.println("Hello World!");
          }
    });
    
    // Héritage de la classe TextField
    final TextField sum = new TextField() {
            @Override
            public void replaceText(int start, int end, String text) {
                if (!text.matches("[a-z, A-Z]")) {
                    super.replaceText(start, end, text);                     
                }
                label.setText("Enter a numeric value");
            }
 
            @Override
            public void replaceSelection(String text) {
                if (!text.matches("[a-z, A-Z]")) {
                    super.replaceSelection(text);
                }
            }
        };

Les énumérations

Une énumération est un type qui permet à une variable de prendre plusieurs valeurs prédéfinies qui sont des constantes.

Parce que les valeurs sont des constantes, elles doivent être écrites en majuscule.


public enum Day {
    SUNDAY, 
    MONDAY, 
    TUESDAY, 
    WEDNESDAY,
    THURSDAY, 
    FRIDAY, 
    SATURDAY 
}

Tous les types enum sont des classes qui étende java.lang.Enum. On trouve y la méthode statique values() qui liste toutes les valeurs définies dans l'enum par ordre de déclaration.

Important : Un type enum peut donc avoir un constructeur avec des parametres ainsi que des méthodes. Il peut même avoir une méthode main.


public enum Planet {
    // Declaration des constantes avec le constructeur
    MERCURY (3.303e+23, 2.4397e6),
    VENUS   (4.869e+24, 6.0518e6),
    EARTH   (5.976e+24, 6.37814e6),
    MARS    (6.421e+23, 3.3972e6),
    JUPITER (1.9e+27,   7.1492e7),
    SATURN  (5.688e+26, 6.0268e7),
    URANUS  (8.686e+25, 2.5559e7),
    NEPTUNE (1.024e+26, 2.4746e7);

    private final double mass;   // in kilograms
    private final double radius; // in meters
    Planet(double mass, double radius) {
        this.mass = mass;
        this.radius = radius;
    }
    private double mass() { return mass; }
    private double radius() { return radius; }

    // universal gravitational constant  (m3 kg-1 s-2)
    public static final double G = 6.67300E-11;

    double surfaceGravity() {
        return G * mass / (radius * radius);
    }
    double surfaceWeight(double otherMass) {
        return otherMass * surfaceGravity();
    }
    public static void main(String[] args) {
        if (args.length != 1) {
            System.err.println("Usage: java Planet <earth_weight>");
            System.exit(-1);
        }
        double earthWeight = Double.parseDouble(args[0]);
        double mass = earthWeight/EARTH.surfaceGravity();
        for (Planet p : Planet.values())
           System.out.printf("Your weight on %s is %f%n",
                             p, p.surfaceWeight(mass));
    }
}

Le constructeur d'un enum doit être private ou package private.