Programmation 2 : Septième cours

Arnaud Labourel

21 octobre 2019

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:

Wildcard ?

Dans un paramètre de généricité, le symbole ? (appelé wildcard) dénote une variable de type anonyme.

On peut la contraindre avec les mot-clés super et extends.

Exemples :

  • List<?> : une liste de type quelconque.
  • List<? extends Shape> : une liste d’instances d’une sous-classe de Shape.
  • List<? super Disc> : une liste d’instances d’une classe ancêtre de Disc.
  • E extends Comparable<? super E> : un type E implémentant l’interface Comparable<P> pour P ancêtre de E.

? 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>)

Cycle de vie d’un Stream

Un Stream est toujours utilisé en trois phases :

  • Création du Stream (à partir d’une collection, d’un fichier, …),
  • Opérations intermédiaires sur le Stream (suppression d’éléments, transformation de chaque élément, combinaison) qui prenne un Stream et renvoie un Stream,
  • Une seule opération terminale du Stream (calcul de la somme, de la moyenne, application d’une fonction sans retour sur chaque élément, …).

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