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