Programmation 2 : septième cours

Arnaud Labourel

14 ou 26 novembre 2018

Structure d’un projet et paquets

Structure d’un projet

En Java, un projet peut être découpé en paquets (package).

Les paquets permettent de :

  • associer des classes afin de mieux organiser le code
  • de créer des modules indépendants réutilisables
  • d’avoir plusieurs classes qui possèdent le même nom (du moment qu’elles ne sont pas dans le même paquet)

Un paquet (package) :

  • est une collection de classes
  • peut contenir des sous-paquets

Lors de l’exécution…

Java utilise l’arborescence de fichier pour retrouver les fichiers .class

  • Une classe (ou une interface) correspond à un fichier .class
  • Un dossier correspond à un paquet

Les .class du paquet com.univ_amu doivent :

  • être dans le sous-dossier com/univ_amu
  • le dossier com doit être à la racine d’un des dossiers du ClassPath

Le ClassPath inclut :

  • le répertoire courant
  • les dossiers de la variable d’environnement CLASSPATH
  • des archives JAR
  • des dossiers précisés sur le ligne de commande de java (-classpath path)

Lors de la compilation… (1/2)

Le mot-clé package permet de préciser le paquet des classes ou interfaces définies dans le fichier :

Java utilise l’arborescence pour retrouver le code des classes ou interfaces :

  • Une classe (ou une interface) MyClass est cherchée dans le fichier MyClass.java
  • Le fichier MyClass.java est cherché dans le dossier associé au paquet de MyClass

Lors de la compilation… (2/2)

Dans l’exemple précédent, il est donc conseillé que le fichier :

  • se nomme MyClass.java
  • se trouve dans le dossier com/univ_amu (Par défaut, la compilation crée MyClass.class dans com/univ_amu)

Utilisation d’une classe à partir d’un autre paquet

Accessibilité :

  • Seules les classes publiques sont utilisable à partir d’un autre paquet
  • Un fichier ne peut contenir qu’une seule classe publique

On peut désigner une classe qui se trouve dans un autre paquet :

Importer une classe

Vous pouvez également importer une classe :

Deux classes de deux paquets différents peuvent avoir le même nom :

  • Exemple : java.util.List et java.awt.List
  • Attention de choisir le bon import

Importer un paquet

Vous pouvez également importer toutes les classes d’un paquet :

Remarques :

  • Les classes des sous-paquets ne sont pas importées
  • Le paquet java.lang est importé par défaut

Importer des membres statiques

Depuis Java 5, il est possible d’importer directement des méthodes ou attributs de classes (static).

La syntaxe est la suivante :

Exemple :

Exemple sans import statique

Exemple d’import statique

Un exemple

Le fichier com/univ_amu/HelloWorld.java :

$ javac com/univ_amu/HelloWorld.java
$ ls com/univ_amu
HelloWorld.java HelloWorld.class
$ java com.univ_amu.HelloWorld
Hello world !

Quelques remarques

Nommage des paquets :

  • Les noms de paquets sont écrits en minuscules
  • Pour éviter les collisions, on utilise le nom du domaine à l’envers \(\Rightarrow\) com.google.gson, com.oracle.jdbc
  • Si le nom n’est pas valide, on utilise des underscores : \(\Rightarrow\) com.univ_amu

Fichier JAR (Java Archive) :

  • est une archive ZIP pour distribuer un ensemble de classes Java
  • contient un manifest (qui peut préciser la classe qui contient le main)
  • peut également faire partie du ClassPath
  • peut être généré en ligne de commande (jar) ou avec un IDE

Classe interne

Classe imbriquée statique

Il est possible de définir une classe à l’intérieur d’une autre (classe imbriquée ou nested class) :

Il est possible d’instancier la classe interne sans qu’une instance de LinkedList existe car elle est statique :

Classe imbriquée statique

Rappel

Une classe non-imbriquée publique (public) doit être dans un fichier portant son nom.

Interdit !

Fichier LinkedList.java

\(\Rightarrow\) erreur à la compilation :

Remarque

Une classe imbriquée peut être publique et accessible depuis l’extérieur.

Classe imbriquée statique

Il est également possible de la rendre privée à la classe LinkedList :

Dans ce cas, seules les méthodes de LinkedList pourront l’utiliser. Notez que des méthodes statiques définies dans LinkedList peuvent également utiliser cette classe interne du fait qu’elle soit statique.

Classe imbriquée statique

Exemple d’implémentation de méthodes dans la classe LinkedList :

Classe imbriquée statique

Exemple d’utilisation de la classe précédente :

Cet exemple produit la sortie suivante :

[c][b][a]

Classe imbriquée statique

Une classe imbriquée statique ne peut accéder qu’aux champs et méthodes statiques de la classe qui la contient :

Classe interne

En revanche, si la classe interne n’est pas statique, elle peut accéder aux champs de classe qui la contient :

Classe interne

Java insère dans l’instance de Node une référence vers l’instance de LinkedList qui a permis de la créer :

Classe interne

Il est possible d’utiliser la méthode isFirst dans LinkedList :

Classe interne

Exemple d’utilisation de la classe précédente :

Cet exemple produit la sortie suivante :

[c,true][b,false][a,false]

Types paramétrés (notions avancées)

Condition sur les paramètres – Problématique

Comment rendre la classe Greatest générique ?

Condition sur les paramètres

Problématique

Supposons que nous ayons les classes suivantes :

Il n’est pas possible d’écrire les lignes suivantes car PrettyCard n’implémente pas l’interface Comparable<PrettyCard> :

Syntaxe ? super

Supposons que nous ayons les classes suivantes :

Il est possible d’écrire les lignes suivantes car PrettyCard implémente l’interface Comparable<Card> et Card super PrettyCard:

? extends – Problématique

Supposons que nous ayons les classes suivantes :

Il n’est pas possible d’écrire les lignes suivantes :

? extends

Supposons que nous ayons les classes suivantes :

Il est maintenant possible d’écrire les lignes suivantes :

Méthodes paramétrées et conditions sur les types

Exemple :

Méthodes paramétrées et conditions sur les types

Méthode pour copier une liste src vers une autre liste dest :

static <T> void copy(List<? super T> dest, List<? extends T> src)

On suppose qu’on a une classe MovingPixel qui étend Pixel qui elle-même étend Point.

On peut écrire :

Utilisation extends et super

Lorsqu’on a une collection d’objets de type T :

  • En entrée/écriture, on veut donner des objets qui ont au moins tous les services des objets de type T.

    On doit donc donner des objets dont la classe étend T : ? extends T

  • En sortie/lecture, on veut récupérer des objets qui ont au plus tous les services des objets de type T.

    On doit donc récupérer des objets qui sont étendu par la classe T : ? super T

Interfaces (notions avancées)

Les classes anonymes

Supposons que nous ayons l’interface suivante :

Il est possible de :

  • définir une classe anonyme qui implémente cette interface
  • d’obtenir immédiatement une instance de cette classe

Les classes anonymes

Les classes anonymes

Il est possible d’utiliser des attributs de la classe “externe” :

Les classes anonymes

Il est possible d’utiliser des variables finales de la méthode :

Java 8 : Lambda expressions

Avec Java 8, il est possible d’écrire directement :

Explication : ActionListener possède une seule méthode donc on peut affecter une lambda expression à une variable de type ActionListener.

Interfaces fonctionnelles

En Java 8, une interface n’ayant qu’une méthode abstraite est une interface fonctionnelle. Les quatre interfaces fonctionnelles suivantes (et plein d’autres) sont déjà définies :

Syntaxe d’une lambda expression

Pour instancier une interface fonctionnelle, on peut utiliser une lambda expression :

L’interface suivante :

peut être instancier par :

Si T est void alors l’expression peut être void comme un println.

Exemples de lambda expression

On considère une classe Person avec deux attributs name et age et les getters et setters associés.

On a le droit d’écrire les lambda expressions suivantes en Java :

  • person -> person.getAge() >= 18 de type Predicate<Person>
  • person -> person.getName() de type Function<Person,String>
  • name -> System.out.println(name) de type Consumer<Person>

Remarques

  • Il n’est pas nécessaire de mettre le type des paramètres.
  • On peut omettre les parenthèses dans le cas où il n’y a qu’un seul paramètre

Référence de méthodes

Dans un certain nombre de cas, une lambda expression se contente d’appeler une méthode ou un constructeur.

Il est plus clair dans ce cas de se référer directement à la méthode ou au constructeur.

Lambda expression référence de méthode
x -> Math.sqrt(x) Math::sqrt
name -> System.out.println(name) System.out::println
person -> person.getName() Person::getName
name -> new Person(name) Person::new

java.util.stream.Stream

Stream = Abstraction d’un flux d’éléments sur lequel on veut faire des calculs

Ce n’est pas une Collection d’élément car un Stream ne contient pas d’élément

Création d’un Stream :

  • À partir d’une collection comme une liste avec list.stream()
  • À partir d’un fichier : Files.lines(Path path)
  • À partir d’un intervalle : IntStream.range(int start, int end)

Exemples d’utilisation

Types des paramètres et retours des méthodes :

  • stream()Stream<Person>
  • filter(Predicate<Person>)Stream<Person>
  • map(Function<Person, String>)Stream<String>
  • forEach(Consumer<String>)

Opérations intermédiaires possibles sur un Stream

  • Stream<E> filter(Predicate<? super E>) : sélectionne si un élement reste dans le Stream

  • <R> Stream<R> map(Function<? super E, ? extends R) : transforme les éléments du Stream en leur appliquant une fonction

  • Stream<E> sorted(Comparator<? super E>) : trie les éléments

Opérations terminales possibles sur un Stream

  • long count() : compte le nombre d’éléments

  • long sum() : somme les éléments (entiers ou double)

  • Stream<E> forEach(Consumer<? super E>) : Appele le consumer pour chaque élément

  • allMatch(Predicate<? super E>) : vrai si le prédicat est vrai pour tous les éléments

  • anyMatch(Predicate<? super E>) : vrai si le prédicat est vrai pour au moins un élément

  • collect(Collectors.toList()) : crée une liste avec les éléments du Stream