Programmation Orientée Objet Introduction à Java
Transcription
Programmation Orientée Objet Introduction à Java
Programmation Orientée Objet Collections et algorithmes Julien Provillard http://www.i3s.unice.fr/~provilla/poo/ [email protected] Programmation orientée objet Objectifs Réutilisation Utilisation des types génériques Structures de contrôle for-each & Iterable Structures de données Collections Tables d’association 2 Programmation orientée objet java.util.Collection Groupe d’éléments capacité extensible taille dynamique Les collections sont des objets Iterable<E> Collection<E> Les méthodes [*] boolean add(E) [*] boolean addAll(Collection<?>) [*] void clear() boolean contains(E) boolean containsAll(Collection<?>) boolean isEmpty() [*] boolean remove(E) [*] boolean removeAll(Collection<?>) [*] boolean retainAll(Collection<?>) int size() 3 Programmation orientée objet java.util.Collection Groupe d’éléments Iterable<E> capacité extensible taille dynamique Les collections sont des objets Collection<E> Différentes sous-interfaces Ensemble Liste Set<E> List<E> Queue<E> java.util.Set java.util.List • Ensemble ordonné, accès par indice Files (double sens) java.util.Queue (Deque) Et des interfaces liées Table association (clé/valeur) java.util.Map 4 Programmation orientée objet Classes concrètes: Set Ensemble java.util.Set java.util.SortedSet Table de hachage : Arbre balancé : Hachage + liste chaînée : java.util.HashSet java.util.TreeSet java.util.LinkedHashSet AbstractCollection<E> Collection<E> AbstractSet<E> Set<E> Hashset<E> AbstractSortedSet<E> LinkedHashSet<E> TreeSet<E> SortedSet<E> 5 Programmation orientée objet Classes concrètes: Set Ensemble Table de hachage : Arbre balancé : Hachage + liste chaînée : java.util.Set java.util.SortedSet java.util.HashSet java.util.TreeSet java.util.LinkedHashSet Attention: Les classes concrètes garantissent qu’on n’ajoute pas un objet qui est déjà présent (au sens de la méthode equals) => Aucune garantie avec des objets modifiables HashSet utilise les méthodes equals et hashCode de la classe Object • Lorsque deux objets sont égaux (au sens de equals), leur méthode hashCode doit renvoyer le même code ! 6 Programmation orientée objet Classes concrètes : List Liste Tableau à capacité variable : Liste chaînée : java.util.List java.util.ArrayList java.util.LinkedList Collection<E> Les méthodes List<E> [*] void add(int, E) [*] void addAll(int, Collection<?>) E get(int) AbstractCollection<E> int indexOf(Object) int lastIndexOf(Object) AbstractList<E> [*] void remove(int) [*] E set(int, E) List<E> subList(int, int) RandomAccess ArrayList<E> 7 Programmation orientée objet Classes concrètes : Queue File Tableau à capacité variable : Liste chaînée : java.util.Queue java.util.Deque java.util.ArrayDeque java.util.LinkedList Collection<E> AbstractCollection<E> Queue<E> List<E> AbstractList<E> Deque<E> AbstractSequentialList<E> ArrayDeque<E> LinkedList<E> ArrayList<E> 8 Programmation orientée objet Collections : syntaxe Déclaration (référence) List<Complexe> complexes; Construction d’une liste (pas de ses éléments) complexes = new ArrayList<Complexe>(); complexes = new ArrayList<>(); // Depuis le JDK 7.0 Affectation de collections (pas de copie globale) List<Complexe> complexesRef = complexes; 9 Programmation orientée objet Collections : syntaxe Taille (dynamique) complexes.size() → 0 Ajout complexes.add(new ComplexeCartesien(0,1)); Accès en lecture (par indice) Complexe c1 = complexes.get(0); 10 Programmation orientée objet Parcours des éléments List<Complexe> complexes = new ArrayList<>(); … Par indice (seulement pour les listes) for (int i = 0; i < complexes.size(); i++) { Complexe c = complexes.get(i); c.reelle(); } For-each : itérateurs for (Complexe c : complexes) { c.reelle(); } 11 Programmation orientée objet Parcours des éléments : Iterator Interface java.util.Iterator interface Iterator<T> { boolean hasNext(); T next(); void remove(); } Iterator<T> + hasNext() : boolean + next() : T + remove() Usage Collection<Complexe> complexes = …; Iterator<Complexe> it = complexes.iterator(); while (it.hasNext()) { Complexe c = it.next(); c.reelle(); } 12 Programmation orientée objet Parcours des éléments : Iterator Méthode next() NoSuchElementException si hasNext() renvoie false Méthode remove() Optionnelle • NoSuchOperationException si non supportée Enlève le dernier élément énuméré • IllegalStateException si next() n’a pas été appelé La collection ne doit pas être modifiée pendant l’itération ConcurrentModificationException si on essaye d’enlever ou d’ajouter des éléments pendant l’itération (sans passer par l’itérateur) 13 Programmation orientée objet Parcours des éléments : ListIterator Seulement avec des List (pas avec toutes les collections) Interface java.util.ListIterator interface ListIterator<T> extends Iterator<T> { void add(T); boolean hasPrevious(); int nextIndex(); T previous(); int previousIndex(); void set(E); } ListIterator<T> + + + + + + add(T) hasPrevious() : boolean nextIndex() : int previous() : T previousIndex() : int set(T) 14 Programmation orientée objet Parcours des éléments Interface java.lang.Iterable interface Iterable<T> { Iterator<T> iterator(); } Usage Collection<Complexe> complexes = …; Iterator<Complexe> it = complexes.iterator(); while (it.hasNext()) { Complexe c = it.next(); c.reelle(); } for (Complexe c : complexes) c.reelle(); Iterable<T> + iterator() : Iterator<T> Collection<T> 15 Programmation orientée objet Surcharge de remove Pour les listes, il y a deux méthodes remove void remove(int index); boolean remove(E element); Avec les listes d’entiers : E = Integer void remove(int index); boolean remove(Integer element); Collection<E> + add(E) + remove(E) : boolean + size():int Usage liste.remove(12); liste.remove(new Integer(12)); List<E> + add(int, E) + get(int): E + remove(int) 16 Programmation orientée objet Classe utilitaire : java.util.Collections Manipulations courantes de collections (signatures simplifiées) Recherche: int binarySearch(List<T> li, T val); T min(Collection<T> c); T max(Collection<T> c); Copy: Comparaison: Remplissage: Divers: void copy(List<T> l1, List<T> l2); boolean disjoint(List<T> l1, List<T> l2); void fill(List<T> li, T val); void replaceAll(List<T> li, T old, T new); void reverse(List<T> li); void rotate(List<T> li, int distance); void shuffle(List<T> li); Tri: void sort(List<T> li) 17 TRI ET RECHERCHE DANS UNE COLLECTION 18 Programmation orientée objet Exemple Tri et affichage d’une liste de chaînes de caractères List<String> liste = new ArrayList<String>(); liste.add("Pierre Jacques"); liste.add("Pierre Paul"); liste.add("Jacques Pierre"); liste.add("Paul Jacques"); System.out.println("Avant tri : " + liste); Collections.sort(liste); System.out.println("Après tri : " + liste); Avant tri : [Pierre Jacques, Pierre Paul, Jacques Pierre, Paul Jacques] Après tri : [Jacques Pierre, Paul Jacques, Pierre Jacques, Pierre Paul] <T extends Comparable<? super T>> void sort(List<T> list) Implémente un tri stable (deux éléments égaux restent dans le même ordre) String est un Comparable<String> 19 Programmation orientée objet Interface Comparable<T> Ordre total entre les objets qui réalisent cette interface Ordre naturel de la classe ! Pas nécessairement consistant avec equals mais c’est fortement recommandé int compareTo(T element); Règles: Antisymétrie: • sgn(x.compareTo(y)) == -sgn(y.compareTo(x)) Transitivité: • x.compareTo(y) > 0 && y.compareTo(z) > 0 => x.compareTo(z) > 0 Equivalence • x.compareTo(y) == 0 => sgn(x.compareTo(z)) == sgn(y.compareTo(z)) 20 Programmation orientée objet Exemple Ordre lexicographique sur les couples d’entiers class Couple implements Comparable<Couple> { private final int first, second; public int compareTo(Couple c) { if (this.first == c.first) { return this.second - c.second; } else { return this.first - c.first; } } Pourquoi cette implémentation est erronée? } Usage Couple[] tc = { … }; Arrays.sort(tc); Avant:[(1, 0), (1, 1), (0, 1)] Après:[(0, 1), (1, 0), (1, 1)] 21 Programmation orientée objet Interface Comparator<T> Réalise un ordre Lorsqu’on ne veut pas utiliser l’ordre naturel Lorsque les éléments ne réalisent pas Comparable et qu’on ne peut/veut pas modifier la classe • Exemple: – il n’y a pas d’ordre naturel sur les couples – Deux ordres « non » naturels: lexicographique ou produit Signature interface Comparator<T> { int compare(T o1, T o2); } 22 Programmation orientée objet Un ordre pour les couples Ordre lexicographique class OrdreLexicographique implements Comparator<Couple> { public int compareTo(Couple c) { if (this.first() == c.first()) { return this.second() - c.second(); } else { return this.first() - c.first(); } } } Usage Avant:[(1, 0), (1, 1), (0, 1)] Après:[(0, 1), (1, 0), (1, 1)] Couple[] tc = { … }; Arrays.sort(tc, new OrdreLexicographique()); 23 Programmation orientée objet Recherche dichotomique Deux méthodes <T> int binarySearch(List<? extends Comparable<? super T>> l, T e); • Calcule l’indice de l’élément e dans la liste l. • Suppose que la liste est triée dans l’ordre naturel de T <T> int binarySearch(List<? extends T> l, T e, Comparator<? super T> c); • Identique au précédent sauf que l’ordre est donné par le paramètre c Usage (T = Couple) List<Couple> liste = …. Collections.sort(liste, new OrdreLexicographique()); Collections.binarySearch(liste, new Couple(0,1), new OrdreLexicographique()); 24 Programmation orientée objet Collections dépréciées (JDK 1.1) JDK 1.1 java.util.Vector java.util.Enumeration java.util.HashTable => => => java.util.ArrayList java.util.Iterator java.util.HashMap Thread-safe List<T> Collections.synchronizedList(List<T> l) Types génériques Java 5 (JDK 1.5) 25 Programmation orientée objet Liste vers tableau Deux méthodes possibles Object[] toArray() List<String> liste = new ArrayList<String>(); liste.add("premier"); Object[] t = liste.toArray(); <T> T[] toArray(T[] a) String[] t = liste.toArray(new String[0]); t.length → 1 26 Programmation orientée objet Tableau vers liste Pour une vue du tableau (impactée par les modifications) <T> List<T> Arrays.asList(T... t) String[] t = new String[] { "premier" }; List<String> liste = Arrays.asList(t); Pour une copie en profondeur String[] t = new String[] { "premier" }; List<String> liste = new List<>(Arrays.asList(t)); 27 Programmation orientée objet Méthodes optionnelles Certaines méthodes de l’interface Collection sont dites optionnelles add, addAll, clear, remove, removeAll, retainAll Elles renvoient parfois Exemples: java.lang.UnsupportedOperationException • Dans une liste non-modifiable on ne veut pas pouvoir enlever des éléments 28 Programmation orientée objet Convention Une interface ne peut pas imposer la forme des constructeurs (comme le feraient des constructeurs) Par convention, les classes qui réalisent l’interface Collection doivent avoir au moins 2 constructeurs Un constructeur sans paramètre Un constructeur avec en paramètre une Collection dont les éléments sont de type compatible Exemple ArrayList<E>() ArrayList<E>(Collection<? extends E> c) 29 TABLES D’ASSOCIATION (MAP) 30 Programmation orientée objet Classes concrètes Table d’association java.util.Map,java.util.SortedMap Table de hachage : java.util.HashMap Arbre balancé : java.util.TreeMap Hachage + liste chaînée : java.util.LinkedHashMap Utilisation Association 1 clé => 1 valeur Les clés sont uniques (au sens de equals) Map<K,V> SortedMap<K,V> Exemples: HashMap<Integer, String> map; HashMap<String, String> dictionnaire; HashMap<String, Complexe> variables; 31 Programmation orientée objet HashMap<K,V> Propriétés Accès à une valeur en temps constant • Temps d’accès similaire à un tableau • Mais K n’est pas nécessairement un entier (cf. hashCode()) Example HashMap<String, Integer> variables = new HashMap<String, Integer>(); variables.put("x", 12); variables.put("y", 24); System.out.println("Avant: " + variables); variables.put("x", variables.get("x") + variables.get("y")); System.out.println("Après: " + variables); Avant: {y=24, x=12} Après: {y=24, x=36} 32 Programmation orientée objet Interface Map<K,V> Les méthodes [*] void clear() boolean containsKey(Object) boolean containsValue(Object) V get(Object) boolean isEmpty(); Set<K> keySet(); Collection<V> values(); Set<Map.Entry<K,V>> entrySet(); [*] V put(K, V) [*] void putAll(Map<? Extends K, ? extends V>) [*] V remove(Object) int size() 33 Programmation orientée objet Attention Les clés doivent être des objets immutables Le fonctionnement n’est pas garanti si on modifie une clé Pour modifier une clé • Il faut d’abord enlever l’ancienne clé • Puis ajouter la nouvelle Convention Les classes qui réalisent l’interface Map doivent avoir au moins deux constructeurs • 1 par défaut • 1 qui prend en paramètre un Map avec des types compatibles Impossible d’imposer cette règle de typage avec une interface ! 34 Programmation orientée objet Parcours des éléments d’une Map Deux parcours possibles Par les clés (cf. méthode keySet()) Par les valeurs (cf. methode entrySet()) On manipule alors des ensembles (non ordonnés) • Avec leurs itérateurs • L’ordre de parcours n’est pas garanti constant • => sinon il faut utiliser TreeMap<K,V> qui est une SortedMap 35 Programmation orientée objet TreeMap<K,V> Table d’association avec des éléments ordonnés Soit des Comparable Soit avec Comparator Structure d’arbre (binaire) Accès en O(log2(n)) où n est le nombre d’éléments log2 (n) est une borne supérieure de la profondeur de l’arbre Map<K,V> SortedMap<K,V> AbstractMap<K,V> HashMap<K,V> TreeMap<K,V> 36 STREAMS 37 Programmation orientée objet Introduction Les streams ont été introduites par le JDK 8.0 pour faciliter la manipulation des collections Manipulations des collections de manière intentionnelle par les lambdaexpressions Parallélisation automatique (pipe et streams parallèles) Les opérations s’appuient sur le nouveau paquetage java.util.function qui contient de nombreuses interfaces fonctionnelles. 38 Programmation orientée objet java.util.function Interfaces génériques Nom de l’interface Méthode fonctionnelle Supplier<T> T get() Consumer<T> void accept(T t) BiConsumer<T, U> void accept(T t, U u) Function<T, R> R apply(T t) BiFunction<T, U, R> R apply(T t, U u) UnaryOperator<T> T apply(T t) BinaryOperator<T> T apply(T t1, T t2) Predicate<T> boolean test(T t) BiPredicate<T, U> boolean test(T t, U u) 39 Programmation orientée objet java.util.function Interfaces spécialisée (pour les types primitifs) XXX et YYY désignent Int, Long ou Double xxx et yyy désignent int, long ou double Nom de l’interface Méthode fonctionnelle BooleanSupplier boolean getAsBoolean() XXXSupplier xxx getAsXXX() XXXConsumer void accept(xxx x) XXXFunction<T> T apply(xxx x) ToXXXFunction<T> xxx applyAsXXX(T t) XXXToYYYFunction yyy applyAsYYY(xxx x) ToXXXBiFunction<T, U> xxx applyAsXXX(T t, U u) XXXUnaryOperator xxx applyAsXXX(xxx x) XXXBinaryOperator xxx apply(xxx x1, xxx x2) XXXPredicate boolean test(xxx x) 40 Programmation orientée objet Exemple d’utilisation d’une stream Integer[] tab = new Integer[]{0, 1, 2, 3, 4, 5}; Stream<Integer> stream = Stream.of(tab); stream.filter(x -> x % 2 == 0) .map(Math::sqrt) .forEach(System.out::println); Appels chaînés sur les streams successives 0.0 1.4142135623730951 2.0 Création d’une stream Les différents appels sont effectués en parallèle dans un ‘pipe’. Le parallélisme est géré automatiquement par la JVM. Toutes les opérations sont paresseuses. 41 Programmation orientée objet Streams Streams génériques Stream<T> Streams pour les types primitifs Pour éviter les emballages/déballages automatiques IntStream, LongStream, DoubleStream Il n’y a pas de stream pour les types primitifs boolean, char, byte, short et float. • Il faut exploiter la promotion automatique. Peut poser des problèmes de typages. 42 Programmation orientée objet Streams: Création A partir de la méthode Stream<T> stream() de l’interface Collection<T> A partir d’une liste de valeurs ou d’un tableau IntStream stream1 = IntStream.of(0, 1, 2, 3, 4); Stream<Integer> stream2 = Stream.of(0, 1, 2, 3, 4); IntStream stream3 = IntStream.of(new int[]{0, 1, 2, 3, 4}); Stream<Integer> stream4 = Stream.of(new Integer[]{0, 1, 2, 3, 4}); A partir d’une fonction génératrice DoubleStream stream5 = DoubleStream.generate(Math::random); Stream<String> stream6 = Stream.generate(() -> "toto"); Ces streams sont infinies, on peut limiter leur taille avec la méthode limit. DoubleStream stream7 = stream5.limit(100); 43 Programmation orientée objet Streams: Création A partir d’un BufferedReader BufferedReader reader = new BufferedReader(new FileReader("fic.txt")); Stream<String> stream8 = reader.lines(); stream8.close(); // Stream implémente AutoCloseable A partir d’une méthode itérative public static LongStream syracuse(int seed) { return LongStream.iterate(seed, val -> val % 2 == 0 ? val / 2 : 3 * val + 1); } 44 Programmation orientée objet Stream séquentielles et parallèles Par défaut, une stream est séquentielle: ces éléments sont parcourus dans l’ordre. Lorsque l’ordre n’importe pas, on peut rendre une stream parallèle en appelant la méthode parallel(). On peut également directement créer une stream parallèle en appelant parallelStream() à la place de stream() sur une collection. Finalement, il est possible de retrouver une stream séquentielle avec la méthode sequential(). 45 Programmation orientée objet Méthodes intermédiaire Les méthodes intermédiaires transforment des streams en streams en leur appliquant un comportement. map transforme une Stream<T> en Stream<R> en appliquant une Function<T, R> à tous les éléments. filter qui ignore les éléments de la stream ne vérifiant pas un prédicat. sorted trie la stream en fonction de l’ordre naturel ou d’un comparateur. concat parmet de concaténer deux streams. On a déjà vu limit, parallel et sequential 46 Programmation orientée objet Méthodes terminales Les méthodes terminales consomment la stream et produisent un résultat. La principale méthode terminale est forEach qui applique un Consumer<T> à chaque élément d’une Stream<T>. La variante forEachOrdered impose que les éléments soient pris dans l’ordre. count compte le nombre d’élement dans la stream. reduce effectue une réduction (une agrégation des résultats à l’aide d’une fonction) collect permet de transformer une stream en collection. 47 Programmation orientée objet Méthodes terminales De nombreuses méthodes terminales renvoient un résultat en fonction d’un prédicat. min et max, prennent un comparateur en argument et renvoient un Optional<T> findAny, findFirst renvoient également un Optional<T> noneMatch, allMatch, anyMatch 48 Programmation orientée objet Résumés Les streams sont créées à partir d’une source et représentent un flux de valeurs ou d’objet. Les méthodes intermédiaires transforment et manipulent le flux. Les méthodes terminales le consomment. Les streams s’appuient sur les lambdas-expressions et la bibliothèque java.util.function. Elles facilitent le parallélisme et les comportements paresseux. Elle favorise un style de codage fonctionnel. 49 Programmation orientée objet Exemple: suite (infinie!) de Fibonnacci public static LongStream fibonnacci() { class Pair { public final long first; public final long second; public Pair(long first, long second) { this.first = first; this.second = second; } } Stream<Pair> sequence = Stream.iterate(new Pair(0, 1), pair -> new Pair(pair.second, pair.first + pair.second)); return sequence.mapToLong(pair -> pair.first); } 50 Programmation orientée objet Exemple: Gestion de données public class Etudiant { public final String nom; public final double moyenne; public Etudiant(String nom, double moyenne) { this.nom = nom; this.moyenne = moyenne; } @Override public String toString() { return String.format("%-20s %.2f", nom, moyenne); } } 51 Programmation orientée objet Exemple: Gestion de données public static void main(String[] args) { List<Etudiant> etudiants = IntStream.iterate(0, i -> i + 1).limit(10) .mapToObj(i -> new Etudiant("etudiant" + i, Math.random() * 20)) .collect(Collectors.toCollection(ArrayList::new)); Map<Boolean, List<Etudiant>> etudiantsSepares = etudiants.parallelStream() .collect(Collectors.partitioningBy(etudiant -> etudiant.moyenne >= 10)); Comparator<Etudiant> comp = (e1, e2) -> (int) Math.signum(e2.moyenne - e1.moyenne); System.out.println("Etudiants ayant validé l'année"); etudiantsSepares.get(true).stream().sorted(comp).forEach(System.out::println); System.out.println("Etudiants passant le rattrapage"); etudiantsSepares.get(false).stream().sorted(comp).forEach(System.out::println); System.out.println(); double sommeNotes = etudiants.stream().mapToDouble(etudiant -> etudiant.moyenne) .reduce(0, (somme, val) -> somme + val); double moyenne = sommeNotes / etudiants.size(); System.out.printf("Moyenne = %.3f" + System.lineSeparator(), moyenne); } 52 Programmation orientée objet Exemple: Gestion de données public static void main(String[] args) { List<Etudiant> etudiants = IntStream.iterate(0, i -> i + 1).limit(10) .mapToObj(i -> new Etudiant("etudiant" + i, Math.random() * 20)) .collect(Collectors.toCollection(ArrayList::new)); Etudiants ayant validé l'année = etudiants.parallelStream() Map<Boolean, List<Etudiant>> etudiantsSepares etudiant6 18,51 .collect(Collectors.partitioningBy(etudiant -> etudiant.moyenne >= 10)); etudiant0 Comparator<Etudiant> comp = (e1, e2)14,37 -> (int) Math.signum(e2.moyenne - e1.moyenne); etudiant1 14,28 System.out.println("Etudiants ayant validé l'année"); etudiant7 14,03 etudiantsSepares.get(true).stream().sorted(comp).forEach(System.out::println); etudiant2passant 13,55 System.out.println("Etudiants le rattrapage"); etudiant4 11,11 etudiantsSepares.get(false).stream().sorted(comp).forEach(System.out::println); System.out.println(); Etudiants passant le rattrapage etudiant8 8,22 double sommeNotes = etudiants.stream().mapToDouble(etudiant -> etudiant.moyenne) etudiant5 5,85 + val); .reduce(0, (somme, val) -> somme etudiant3 3,89 double moyenne = sommeNotes / etudiants.size(); etudiant9 System.out.printf("Moyenne = %.3f" +1,68 System.lineSeparator(), moyenne); } Moyenne = 10,550 53