Programmation orientée objet (OO) avec Java

Introduction

Tutoriel pour une prise en main rapide de la programmation orientée objet avec Java : classes, méthodes, constructeurs, héritage, polymorphisme, encapsulation...

Les opérateurs et blocs d'instruction du langage Java ainsi que les packages sont également abordés.

L'utilisation de javac pour compiler des classes java et l'exécution des classes avec java est proposée à la fin de l'article avec le classique programme Hello World.

Design orienté objet avec Java

Avec Java, des classes sont créées. les trois composantes essentielles des classes sont :

  • la définition d'une classe;
  • les membres d'une classe (méthodes et champs);
  • les méthodes particulières d'une classe (constructeurs et méthode main()).

Anatomie d'une classe

L'anatomie d'une classe Java peut être schématisée dans la façon suivante :

class circle {
      // Liste des champs
      // Liste des méthodes
      // Constructeurs
      // méthode main()
}

Les champs d'une classe

Un champ dans une classe a pour objectif de stocker des informations. Deux types de champs sont implémentables dans une classe Java :

  • les champs d'instance;
  • les champs statiques;

Les champs d'instance

Ce champ est défini pour une instance. Il peut être initialisé à la déclaration.

double radius = 5;

Cette variable est assimilable aux variables d'instance définies dans une classe PowerBuilder.

static double pi=3.1416;

Ajouter des champs dans la définition d'une classe :

class circle {
      // Liste des champs
            double radius = 0;
            static double pi = 3.1416;

      // Liste des méthodes
      // Constructeurs
      // méthode main()
}

Les champs statiques

Ces champs peuvent être partagés par plusieurs instances d'un même objet. Ce champ est comparable aux variables partagées avec PowerBuilder. Ces derniers champs peuvent être obtenues sans avoir à instancier la classe.

L'encapsulation avec Java

L'encapsulation permet de cacher l'implémentation interne d'un objet à l'utilisateur.

Par ailleurs les objets ne devraient jamais directement modifier les champs d'un autre objet, des méthodes doivent être définies dans une classe pour lire ou modifier effectivement les valeurs de cette dernière classe, ces dernières méthodes doivent évidemment être définies comme publiques pour réaliser ces manipulations.

Propriétés d'accès dans les classes

Portée Description
Public Accessible partout où la classe est accessible
Protected Accessible par les sous-classes uniquement et les classes dans le même package
Private Accessible uniquement par la classe elle-même
Package Accessible par les classes dans le même package (par défaut)

Bénéfices de l'encapsulation

L'implémentation interne d'une classe ne doit être modifiée qu'en invoquant des méthodes définies dans la classe en question. Les méthodes permettent de gérer les erreurs alors que d'assigner des valeurs à des champs ne le permettent pas.

Exemple de protection d'un champ :

class circle {
      // Liste des champs
            private double radius = 0;
            static double pi = 3.1416;

      // Liste des méthodes
      // Constructeurs
      // méthode main()
}

Définition des méthodes

Les méthodes sont des instructions permettant de manipuler des données dans un objet et de retourner des valeurs. Ces dernières peuvent contenir aucun ou plusieurs arguments.

Les méthodes peuvent éventuellement retourner des valeurs. Lorsque ces méthodes sont définies avec void, ces dernières ne retournent alors aucune valeur.

Syntaxe d'une méthode :

<return type> <Nom méthode> (arg1Type arg1Nom, ...) {
  // Corps de la méthode
  return <returnvalue>;
}

Exemple :

double getCircumference( ) {
    return 2 * radius * PI;
}

Les méthodes statiques

Les méthodes statiques d'une classe n'agissent pas sur une instance particulière et peuvent être utilisées sans avoir à instancier la classe. Ces méthodes peuvent uniquement accéder aux variables statiques de la classe.

static double getCircumference(double radius) { }

Ajout de méthodes à la définition d'une classe :

class Circle {
      // liste des champs
      private double radius; 
      static double pi = 3.1416;

      // liste des méthodes
      public void setRadius(double radius) { }
      public double getRadius() { }
      public double getCircumference() { }
      public static double getCircumference(double radius) { }

      // constructeurs
      // méthode main()
}

Les constructeurs

Un constructeur est une méthode spéciale pour instancier une classe.

  • le constructeur est appelé avec le mot clé new()
  • le constructeur possède le même nom que la classe;
  • peut accepter aucun ou plusieurs paramètres;
  • ne possède aucune valeur à retourner;
  • un constructeur non publique restreint les classes pouvant créer l'objet.

Un constructeur sans arguments est créé par le compilateur si la classe est fournie sans constructeur.

Si la classe possède un constructeur personnalisé, le constructeur sans argument doit être également explicitement créé.

Exemple de constructeurs :

class Circle {

      private double radius; 
      static double pi = 3.1416;

      // constructors
      public Circle() { 
      }
      public Circle(double r) { 
         radius = r;
      }

      // main() method
}

La méthode main()

La méthode main() est invoquée lorsqu'une application démarre. Cette dernière n'est pas nécessaire si la classe en question n'est pas le point d'entrée de l'application. Elle peut être appelée sans créer l'instance.

Cette méthode créé typiquement une instance.

La signature de la méthode main() est la suivante :

La méthode main() doit

  • être déclarée comme public
  • être une méthode statique
  • ne rien retourner : void
  • accepter comme argument un tableau de string
public static void main(String args[ ]) {
       Circle c = new Circle( );
}

Instanciation d'une classe

Une classe peut être déclarée, en aucun cas cette dernière est instanciée au cours de la déclaration. Une déclaration ne créé pas une instance, elle créé une référence à un objet de ce type.

La référence est initialement à null :

 Circle myCircle;

L'objet est créé en utilisant le mot clé new :

 myCircle = new Circle();

Les objets nouvellement créés sont alloués dans une zone de la mémoire système connue sous le nom de 'heap'. Tous les objets Java sont obtenus par référence.

Accéder aux membres d'une classe

Les membres d'un objet sont invoqués en utilisant la 'dot notation' : objet.méthode, objet.propriete

myCircle.getCircumference()
myCircle.pi

Si la référence est null, une exception de type NullPointerException est levée.

Utilisation du pronom this

this fait référence à l'objet. Dans une méthode qui n'est pas statique, this fait référence à l'objet sur lequel la méthode agit.

Ce pronom peut être utilisé à la première ligne d'un constructeur pour appeler un autre constructeur.

Collecteur de mémoire

Lorsqu'un objet n'est plus référencé, l'espace occupé par ce dernier est sujet à la prise en charge par le collecteur de mémoire. Les objets ne sont pas explicitement détruits (aucun delete, aucun destructeur).

Il est toutefois possible d'invoquer directement le collecteur de mémoire pour détruire prématurément un objet.

La méthode finalize()

La méthode finalize() est invoquée implicitement par le collecteur de mémoire avant la destruction d'un objet.

Le langage Java

Caractéristiques du langage

Java est case-sensitive, ce qui signifie sensible à la casse. Toutes les lignes de commande se terminent par un point virgule ;

Les espaces sont ignorés exceptés ceux dans les chaînes de caractères utilisés dans le code Java.

Commentaires en Java

// comment Les caractères depuis // jusqu'à la fin de la ligne sont ignorés.
/* comment */ Les caractères entre /* et */ sont ignorés.
/** comment */ Les caractères entre /** et */ sont ignorés et automatiquement générés dans la documentation.

Variables

Une variable est un identifiant donné à un stockage d'une donnée. Trois types de variables sont disponibles :

  • variables locales
  • variables d'instance
  • champs statiques

La déclaration d'une variable locale peut être réalisée à n'importe quel endroit du code source et sa durée de vie est égale à l'exécution du bloc qui la contient.

public class MyClass {
    int a;
    static int b;

    public void myMethod( ) {
      int c;  
      
      if (condition) {
         int d;
      } // =====================> d n'est plus disponible
    } // =======================> c n'est plus disponible
} // ===========================> a n'est plus disponible

Constantes

Une fois initialisées, les constantes ne peuvent être modifiées. Il est nécessaire d'utiliser le mot clé final pour rendre un champ constant et non modifiable.

Une constante statique peut être utilisée sans avoir à instancier la classe.

class Circle {
   static final double pi = 3.1416;
}

Tableaux

Un tableau est une collection ordonnée d'éléments de données primitives ou d'objets. La longueur est définie à la création et ne peut alors être modifiée.

Données de type primitives Tableaux d'objets
int[ ] x;
x = new int[3];
x[0] = 10;
x[1] = 20;
x[2] = 30;
myclasse[ ] x;
x = new myclasse[3];
x[0] = new myclasse();
x[1] = new myclasse();
x[2] = new myclasse(); 

x.length permet de connaître la dimension d'un tableau.

Opérateurs

Les opérateurs en Java sont au nombre de 4 :

  • opérateurs d'assignation;
  • opérateurs arithmétiques;
  • opérateurs relationnels;
  • opérateurs logiques;

Opérateurs d'assignation

Pour assigner une valeur, le symbole = est utilisé :

validated = true

Opérateurs arithmétiques

Opérateurs arithmétiques classiques :

+ Addition / Division
- Soustraction % Reste
* Multiplication

Opérateurs d'incrémentation et de décrémentation :

i++ <=> i = i+1;
i-- <=> i = i-1;

Raccourcis d'opérateurs :

var op= expression <=> var = var op (expression)
a *= (b+1) <=>  a = a *(b+1)

Opérateurs relationnels

> Supérieur strictement < Inférieur strictement
>= Supérieur ou égal <= Inférieur ou égal à
== Égal à != Différent de

Opérateurs logiques

&& || ! And Or Not
boolean gift = (price > 100 && ! onSale) || (price > 300);

Structures de programmation

if else

Syntaxe Exemple
If ( <expression > ) { }
if ( result == 0 ) {
  System.out.println( "No record found" );
}
if ( <expression > ) {  
  // Bloc à exécuter
} else {
  // Bloc à exécuter
}
if ( country.equals("USA") ) {
   export = false;
} else {
   export = true;
}
Alternative avec l'opérateur ?
Var = ( <expression ? var1 : var2);
netAmount = ( noTax ? amount : amount * 1.05);

if ( noTax ) {
   netAmount = amount;
} else {
   netAmount = amount * 1.05;
}
if ( <expression1 > ) {
   // Bloc à exécuter
} else if ( <expression2> ) {
   // Bloc à exécuter
} else {
   // Bloc à exécuter
}
if ( numberOfYears < 5 ) { 
    holidays = 10;
} else if  (  numberOfYears < 10 ) {  
    holidays = 15;
} else { 
    holidays = 20;
}

switch

Syntaxe Exemple
switch ( <expression> ) {
      case <expression>:
         ...
         break;
      case <expression>:
         ... 
         break;
      default:
         ...
}
L'expression switch autorise la sélection multiple. Cette dernière instruction doit évaluer une expression retournant une valeur de type int. case < expression> doit être une expression littérale ou un champ statique final. break permet de sortir de la commande switch sans se préoccuper des cas suivants.
public static void main(String args[ ]) { 
  switch ( args.length ) {
     case 0:
       FileEditor e = new FileEditor( );
       break;
     case 1:
       FileEditor e = new FileEditor(args[0]);
       break;
     default:
       // display error message and exit
  }
}

Boucles for loop

Syntaxe Exemple
for (counter_init; 
boolean_expression; counter_increment)
{ block }
counter_init et counter_increment peuvent être des listes d'expressions séparées par des virgules.
for ( x=0; x<10; x++ ) {
   // execute le bloc tant que x < à 10
}

for ( x=0, y=0; y<20; x++, y+=x ) {
   // execute le bloc tant que y < à 20
}

Boucles indéterminées

Syntaxe Exemple
while ( boolean_expression )
{ block } 
Bloc éxecuté aucune fois ou plusieurs fois
do 
  { block }
while ( boolean_expression ); 
Bloc éxécuté au moins une fois
int counter = 1;

while(counter < 5 )
{
  ...
  counter = counter + 1 ;
}

Commandes identifiées

Les blocs d'instructions peuvent être identifiés par des labels. Ces labels sont typiquement utilisés dans les blocs et les boucles.

Syntaxe Exemple
<NOM_LABEL>:
   <blocs>
search:
 for (...) { 
  for (...) {
   if (...) {
    break search;
   }
  }
 }

Conversions

Java vérifie la compatibilité au cours de la compilation. Dans le cas où cette compabilité ne peut être vérifiée qu'à l'exécution, des fonctions de casting sont fournies par Java.

Conversions implicites

Une valeur numérique est implicitement convertie en un autre type de valeur numérique à condition que la gamme du nouveau type soit supérieur :

byte > short > int > long > float > double
Plus petit -------------------> Plus grand
int i = 1000;
double d = i;

Conversions explicites

Lorsque la conversion implicite n'est pas réalisable, une conversion explicite doit être réalisée. Quelques conversions sont en revanche irréalisables (se reporter à la documentation à ce sujet).

Exemples de conversion de types de données :

Valeurs numériques en chaînes de caractères
String str = String.valueOf(100);
String str = new Double(100).toString();
Chaînes de caractères en valeurs numériques
int i = Integer.valueOf(myString).intValue();
int i = new Integer(myString).intValue(); 

Héritage de classes

L'héritage

L'héritage permet à une classe d'hériter des propriétés et des méthodes d'une autre classe. Les méthodes et les champs sont généralisés au niveau d'un ancêtre. Les méthodes particulières sont transcrites dans la classe descendante.

Toutes les classes dérivent implicitement de la classe Object. La clause Extends permet de spécifier un ancêtre intermédiaire. L'héritage multiple n'est pas autorisé avec Java.

Une sous classe hérite de toutes les méthodes et de toutes les propriétés de la superclasse dont elle dérive.

class Shape {
      int x, y;
      public void setX(int xpos) { x = xpos; }
      public void setY(int ypos) { y = ypos; }
}

class Rectangle extends Shape {
      int width, height; 
      public void setWidth(int w) { width = w; }
      public void setHeight(int h) { height = h; }
}

Les classes abstraites

Ces classes définissent une partie de l'implémentation et ne peuvent être instanciées. Les classes abstraites peuvent posséder des méthodes concrètes réutilisables dans les descendants.

public abstract class Shape {
      public abstract void draw();
}

public class Rectangle extends Shape {
      public void draw( ) {
         // programme
      }
}

L'opérateur instanceof

L'opérateur instanceof est utilisé pour déterminer si un objet est d'une classe donnée.

if (shape instanceof Circle) {
    Circle circle = (Circle) shape;
    
    // programme
}

Polymorphisme et redirection des méthodes

Les méthodes dans une classe peuvent avoir le même nom mais des arguments différents. Lorsque la méthode est invoquée, le compilateur vérifie les arguments et les codes de retour pour invoquer la méthode appropriée.

La signature d'une méthode consiste à donner un nom à une méthode ainsi que des arguments.

class Account {
    public void deposit(double amt) {
         this.deposit(amt, "USD");
    }
    public void deposit(double amt, String currency) {
         // process deposit
    }
}

Surcharge des méthodes

La surcharge d'une méthode permet de remplacer l'implémentation d'une méthode dans l'ancêtre au niveau du descendant.

La signature et le type de retour sont les mêmes que ceux définis au niveau de la classe ancêtre.

Le niveau d'accès doit être le même ou fournir plus d'accès (une fonction protégée ne peut être définie comme privée au niveau de la classe descendante).

Seules les méthodes non statiques peuvent être surchargées.

Le mot clé super est utilisé pour invoquer une méthode implémentée au niveau de l'ancêtre. Il fait référence à l'ancêtre direct dans l'arborescence de l'héritage. Ce mot clé est disponible pour toutes les méthodes non statiques d'une classe étendue.

public class Employee {
        public int getHolidays( ) {
           return 20;
        }
}

public class Manager extends Employee {
        public int getHolidays( ) {
           return super.getHolidays( ) + 5;
        }
}

Les constructeurs dans les classes étendues

Le constructeur d'une classe étendue doit invoquer le constructeur de la superclasse.

Explicitement en utilisant le mot clé super() à la première ligne.

Implicitement, si le constructeur de la superclasse n'est pas invoqué explicitement, le constructeur de la superclasse sans arguments est automatiquement invoqué : une erreur d'exécution en résulte si le constructeur sans arguments n'est pas défini au niveau de la superclasse.

class Shape {
        int left, top;

        Shape(int x, int y) {
             left = x;
             top = y;
        }
}

class Rectangle extends Shape {
        int width, height;

        Rectangle(int x, int y, int w, int h) {
            super(x,y);
            width = w;
            height = h;
        }
}

Lorsqu'une instance d'une classe est créée : le processus suivant est déclenché

  1. Les valeurs par défaut des champs sont initialisées.
  2. Le constructeur de la classe est invoqué.
  3. Le constructeur de la superclasse est invoqué.
  4. Les champs de la superclasse sont initialisés.
  5. Le constructeur de la superclasse est exécuté.
  6. Les champs de la classe sont initialisés.
  7. Le constructeur de la classe est exécuté.

Limitation de l'héritage en utilisant le mot clé final

Cette particularité peut être utilisée :

  • au niveau de la classe pour empêcher l'héritage à partir d'une classe
  • au niveau d'une méthode pour empêcher la surcharge d'une méthode

Les classes finales

Un héritage à partir d'une classe finale ne peut être réalisé et les méthodes sont implicitement finales.

final class Password {
        public boolean validate( ) {
            // validate password
        }
}

Les méthodes finales

Ces méthodes ne peuvent être surchargées.

class Password {
       final public boolean validate() {
            // validate password
       }
}

Packages Java

Définition d'un package

Un package est une collection de classes et d'interfaces interdépendantes. Le groupement par packages a deux objectifs majeurs :

  • éviter les conflits de noms;
  • gérer les niveaux d'accès aux classes;

Hiérarchie des packages

Les packages java standards sont donnés ci-dessous :

java.lang java.applet java.sql
java.awt java.util java.text
java.io java.rmi
java.net java.security

Les noms des packages sont hiérarchiques : java.awt.event

Les classes doivent être stockées dans un sous répertoire qui correspond exactement au nom du package : une classe appartenant au package donné ci-dessus doit être stocké dans le répertoire /java/awt/event.

Les sous répertoires des packages peuvent être stockés n'importe ou dans le système de fichiers.

5-3- Variable d'environnement CLASSPATH

La variable d'environnement CLASSPATH est utilisée pour la recherche des packages.

SET CLASSPATH=.;c:\jdk\lib;c:\Program Files\java

Cette variable d'environnement peut être initialisée dans une commande bat.

structure packages

Incorporation d'une classe dans un package

Afin d'incorporer une classe dans un package, il convient d'utiliser la déclaration package dans le fichier source puis de stocker la classe dans un répertoire qui correspond avec le nom du package.
package com.powersoft.util;
  class Calculator {
        // class implementation  
  }

Accessibilité des classes

Par défaut, une classe est accessible uniquement par les classes du même package. Cette dernière classe doit être déclarée comme publique pour pouvoir être accessible par les classes des autres packages.
package com.powersoft.util;
  public class Calculator {
        // class implementation  
  }

Référencement des packages dans les classes

Référencement en utilisant explicitement les noms des classes Référencement avec les commandes import :
import java.util.*;    // (import des classes d'un package)
import java.util.date; // (import spécifique d'une classe)
class PrintDate {
 public static void main(String[] args) {
  java.util.Date today = new java.util.Date();
  System.out.println(today);
 }
}
import java.util.*;

class PrintDate {
 public static void main(String[] args) {
 Date today = new Date( );
 System.out.println(today);
 }
}
Recherche des packages :
SET CLASSPATH =.;c:\jdk\lib;c:\mylibs

Fichiers zip

La variable d'environnement CLASSPATH peut être également utilisée pour rechercher les packages dans des fichiers zip

SET CLASSPATH=.;c:\jdk\lib\classes.zip;c:\mylibs

La structure interne des répertoires dans le fichier Zip est identique à celle des packages libres.

Compilation et exécution (JDK, javac, java)

Pour démonstration, le programme helloworld.java ci-dessous va être compilé :

helloworld.java
class helloworld {
   public static void main (String[] args){
   System.out.println("Hello World");

   }
}

Ce programme basique affiche à la console "Hello World".

Les classes Java sont compilées avec la distribution Java Developer's Toolkit (JDK) téléchargeable gratuitement, et non la distribution JRE (Java RunTime Environment).

Le binaire javac dans le répertoire %JAVA_SDK_HOME%\bin compile un code source .java en fichier compilé avec l'extension .class.

cmd> javac helloworld.java

La commande ci-dessus génère un fichier helloworld.class, fichier compilé exécutable avec le binaire java.

javac accepte de très nombreux arguments, notamment l'argument -cp permettant de modifier la variable CLASSPATH au moment de la compilation pour la recherche des packages et classes : javac -cp <path>

Le programme est alors exécutable avec java :

cmd> java helloworld
Hello World

Comme pour javac, java accepte également l'argument -cp pour modifier la variable CLASSPATH afin d'y ajouter des archives java zip/jar.