Java avancé
Transcription
Java avancé
Java avancé Programmation Objet Le concept objet ● Un module d'un programme doit souvent interagir avec d'autres, éventuellement sans « scrupules » Fonctionnalité Autre ● questi réponson e Données de l'objet Un objet protège son intégrité des autres objets, il n'expose que des fonctionnalités But de la programmation objet Le but principal de la programmation objet est d'aider à la conception et la maintenance de logiciels. ● Modularité ● Extensibilité ● Généricité ● Réutilisabilité Écueils à éviter ● ● ● ● Effet “papillon” : une petite modification entraîne un gros problème Le copier/coller : et si le code dupliqué a un bug ? L'objet Dieu fait tout mais… Les spaghetti (préférer les lasagnes) Principes de la lutte ● Responsabilité – ● Encapsulation – ● ● les données sont protégées de l'extérieur Principe de localité – ● 1 responsabilité pour 1 objet une fonctionnalité n'est située qu'à un endroit Découplage interface/implémentation Les objets ne servent pas seulement à stocker des données Les Types et Java ● ● Philosophiquement, Java sépare les types primitifs des types Objets. – Les types primitifs : Ex: boolean, int, double – Les types objet Ex: String, int[], Point Les types primitifs sont manipulés par leur valeur, les types objets par référence. Les types primitifs ● ● Il existe 8 types primitifs (et c'est tout) : – Valeur booléen : boolean (true/false) – Valeur numérique entière signée : byte(8 bits)/short(16)/int(32)/long(64) – Valeur numérique flottante (IEEE 754) float(32 bits)/double(64) – Charactère unicode : char(16 bits) Attention à la promotion entière pour byte et short – short s = 12; – short s2 = s+s; // erreur Les types objet ● ● Les types objets sont définis comme une “composition” de types primitifs et de types objets. Les types objets sont – soit définis par l'utilisateur SeaLion, Truck – soit prédéfinis et existant dans l'API du JDK. Date, String, Object Les types objet Il existe quatre façons de définir un type objet : ● ● ● ● Définir une classe Définir une interface (type que l'on ne peut pas instancier) Définir une énumération (ensemble fini de valeurs, non instanciable non plus) Définir une annotation (interface spéciale) Définition d'une classe en Java ● ● En Java, « class » permet de définir une classe « new » effectue l'instanciation … { public class Point { public void translate(int dx,int dy) { x+=dx; y+=dy; } private int x; private int y; Point p=new Point(); } System.out.printf("%d %d\n",p.x,p.y); p.translate(2,3); System.out.printf("%d %d\n",p.x,p.y); } … ● Par défaut les champs (pas les variables locales) sont initialisés avec zéro (null, 0, 0.0, false). Membres de classe En java, un membre de classe est : ● Un champ ● Une méthode ● Une classe… Méthode public class Point { public void translate(int dx,int dy) { … } private int x; private class X { … } } Champ Classe interne Notation UML ● La notation UML définit une classe sous la forme d'une boîte avec trois compartiments : – Le premier correspond au nom de la classe – Le second aux champs (aucuns) – Le dernier aux méthodes Customer name:String address:Address validate():boolean ● Les types sont écrits en notation Pascal L'objet courant (this) ● ● Les membres non statiques d'une classe possèdent donc une référence sous-entendue vers l'instance courante public class Holder { Cet objet est noté « this » … public void print() { System.out.println(this); } Holder h1=… public class Printer { public void print() { System.out.println(this); System.out.println(Holder.this); } } System.out.println(h1); h1.print(); } Holder.Printer p = h1.new Printer(); p.print(); L'objet courant (this) ● Autre exemple d'utilisation de this : public class Holder { … public int getValue() { return this.value; } public void setValue(int value) { this.value=value; } private int value; Holder h1=… Holder h2=… h1.setValue(3); h2.setValue(5); System.out.println(h1.getValue()); System.out.println(h2.getValue()); } ● this permet de faire référence aux champs en utilisant la notation '.' L'objet courant (this) ● Dernier exemple public interface Task { public Runnable asRunnable(); } public class TaskFactory { public static Task createTask(TaskData data) { return new TaskImpl(data); } private class ArrayImpl implements Runnable { public Runnable asRunnable() { return this; } public void run() { … } … } } Contexte statique ● ● ● Une classe a la possibilité de déclarer des membres statiques (mot-clef static), qui sont liés à la classe et non à une instance particulière: – champs – méthodes – classes et autres – bloc d'initialisation Tout code utilisé dans ces membres est dans un contexte statique, i.e. il ne peut faire référence ni à this ni aux membres non statiques. Les membres statiques sont utilisés sans instance de la classe. Champs statiques ● Un champ statique est un champs qui n'est pas propre à un objet mais commun à l'ensemble des objets d'une classe. public class StaticTest { private int value; private static int staticValue; public static void main(String[] args) { StaticTest st1=new StaticTest(); StaticTest st2=new StaticTest(); System.out.println(st1.value++); System.out.println(StaticTest.staticValue++); System.out.println(st2.value++); System.out.println(StaticTest.staticValue++); } } // // // // 0 0 0 1 Accès à un champs statique ● ● On accède à un champs statique à partir du nom de la classe Attention : Le compilateur considère l'accès en utilisant un objet comme légal !! public class StaticTest { private int value; private static int staticValue; public static void main(String[] args) { StaticTest st1=new StaticTest(); System.out.println(st1.value++); System.out.println(StaticTest.staticValue++); System.out.println(st1.staticValue++); } } // 0 // 0 // 1 Les constantes ● ● Définir une constante en C, on utilise #define En Java, on utilise une variable static et final (donc une variable typée). public class ConstExample { public static String getLine(Scanner scanner) { return scanner.findWithinHorizon("[a-z]+", MAX_SIZE); } private static final int MAX_SIZE=4096; } ● Les conventions veulent que les constantes soient en majuscules (mots sépares par de _) Static, final et vitesse ● Vitesse d'exécution (nano-benchmark) : public class IfDef { public void test() { for(int i=0;i<1000000;i++) { if (DEBUG) System.out.println(i); } } private static final boolean DEBUG=false; public static void main(String[] args) { IfDef ifdef=new IfDef(); long time=System.nanoTime(); ifdef.test(); long time2=System.nanoTime(); System.out.println(time2-time); } } Temps si if commenté : 2,2ms Temps si if présent : / final / 3,4 ms 2,2 ms static 3,3 ms 2,2 ms Méthode statique ● Une méthode statique est une méthode qui peut-être appeler sans nécessité d'objet (comme un fonction en C) public class Sum { private static void sum(int[] values) { int sum=0; for(int v:values) sum+=v; return sum; } public static void main(String[] args) { Sum.sum(new int[]{2,3,4,5}); // ou sum(new int[]{2,3,4,5}); } } Méthode statique ● Une méthode statique ne possède pas d'objet courant (pas de this) donc elle ne peut pas accéder aux membres non statiques public class Point { private static int test() { int v=value; // legal return x+y; // illegal, quel objet Point utilisé ? } private int x,y; private static double value; } Initialisateur de classe ● Le bloc statique sert à déclarer un code qui sera exécuté une fois lors de l'initialisation de la classe. public class Colors { public static Color getColorByName(String name) { return colorMap.get(name); } private static final HashMap<String,Color> colorMap; static { colorMap = new HashMap<String,Color>(); colorMap.put("Rouge",Color.RED); colorMap.put("Vert",Color.GREEN); … } } ● Le bloc statique est la seule manière d'initialiser les champs statiques complexes. Chargement des classes ● En Java, les classes ne sont chargées que si nécessaire public class ClassLoadingExample { public static void main(String[] args) { if (args.length!=0) new AnotherClass(); System.out.println(args.length); } } ● AnotherClass n'est chargée que si args.length!=0 java -verbose:class ClassLoadingExample [Loaded ClassLoadingExample from file:/C:/java-avancé/] 0 java -verbose:class ClassLoadingExample test [Loaded ClassLoadingExample from file:/C:/java-avancé/] [Loaded AnotherClass from file:/C:/java-avancé/] 1 Classe Interne Statique ● Classe interne qui n'a pas besoin d'un objet de la classe englobante pour exister. public class Coords { private int top; private final Pair[] array; public void add(int x,int y) { array[top++]=new Pair(x,y); // ou new Coords.Pair(x,y); } private static class Pair { private final int x,y; ... } } ● On déclare un classe interne à une autre quand son existence n'a pas de sens sans l'existence de la classe englobante. Classe interne et membre statique ● Il est interdit de déclarer des membres statiques à l'intérieur d'une classe interne non statique public class A { public class B { static void m() { // illégal } } } ● Il est possible de déclarer ce membre dans la classe englobante. public class A { public class B { } static void m() { // correct } } Classe interne (non statique) ● Classe interne qui à besoin d'un objet de la classe englobante pour exister : le code n'est pas dans le contexte statique de la classe englobante. public class Sequence { private final char[] array; public class Sub { private final int offset; private final int length; public char charAt(int index) { if (index<0 || index>=length) throw new IllegalArgumentException(...); return array[offset+index]; } } } ● Une classe interne non statique accède aux membres de l'objet. Rapport avec le C ● Une classe interne est complètement différente d'une sous-structure en C – en C, les sous-structures sont toutes instanciées en mêmetemps que la structure englobante – plusieurs ou aucune instances de la classe interne d'objet peut être instanciée sur une même objet Instantiation de classe interne ● Lors de la construction, une classe interne non statique doit être construite sur un objet de la classe englobante public class Sequence { private final char[] array; public Sequence(String s) { array=s.toCharArray(); } public class Sub { private final int offset; private final int length; public Sub(int offset,int length) { this.offset=offset; this.length=length; } public char charAt(int index) { return array[offset+index]; } } Sequence s=new Sequence("toto"); } Sub sub=s.new Sub(1,3); System.out.println(sub.charAt(0)); Instantiation de classe interne ● Création de la classe interne à l'intérieur de la classe englobante : public class Sequence { private final char[] array; ... public class Sub { public Sub(int offset,int length) { ... } ... } public Sub subsequence(int offset) { return new Sub(offset,array.length-offset); // ou return this.new Sub(...) } } Référence sur la classe englobante ● On souhaite obtenir une référence sur l'instance de la classe englobante. public class Sequence { private char[] array; public class Sub { private int offset; private int length; public char charAt(int index) { //DEBUG System.out.println(Sequence.this); return Sequence.this.array[ this.offset+this.index]; } } } ● référence: OuterClass.this Sequence Sequence.this Sub Accès et visibilité ● ● Une classe englobante à accès à tous les membres de sa classe interne (même privés) Une classe interne a accès à tous les membres (même privés) des instances classe englobante public class Coords { private final Pair[] array; ... public int getX(int index) { return array[index].x; // accès à x } private static class Pair { private final int x,y; ... } } Classe interne sur le disque ● Le compilateur génère deux classes différentes : Coords.class et Coords$Pair.class Classe interne et accesseurs ● Comme La VM ne connaît pas les classes internes, le compilateur génère un accesseur qui permet d'accéder aux champs privés public class Coords { private final Pair[] array; ... public int getX(int index) { return array[index].x; // return access$000(array[index]); } private static class Pair { % javap -private Coords.Pair Compiled from "Coords.java" private final int x,y; class Coords$Pair extends } java.lang.Object{ } private int x; private int y; private Coords$Pair(); static int access$000(Coords$Pair); } Classe interne et accesseur ● ● Inconvénients : – Bytecode plus gros – Code un peu plus lent (en fait, pas d'impact grâce à l'inlining) Règle de programmation : essayer d'éviter la génération d'accesseurs en mettant la visibilité de paquetage Classes internes de méthode ● ● ● ● ● On peut déclarer une classe interne à une méthode (sans modificateur de visibilité) La classe n'est visible que dans la méthode La classe peut accéder aux variables locales et paramètres finaux de la méthode La valeur de ces variables et paramètres sont stockés dans la classe à son instanciation Le compilateur impose aux variables et paramètres d'être déclarées final pour le rappeler au développeur Classe interne de méthode public class Test { public static Iterator<Integer> intList(final int n) { class TestIterator implements Iterator<Integer> { private int pos = 0; public boolean hasNext() { return pos<n; } public Integer next() { if (pos==n) throw new NoSuchElementException(); return pos++; } public void remove() { throw new UnsupportedOperationException(); } } return new TestIterator(); } } Classe Anonyme ● Il est possible d'implanter une interface sans donner de nom à la classe interface Filter { boolean accept(File file); } public class Test { public File[] subDirectories(File directory) { directory.listFiles(new Filter() { public boolean accept(File file) { return file.isDirectory(); } }); } } ● Ici, on crée un objet d'une classe implantant l'interface Filter Syntaxe des classes anonymes ● ● On peut créer des classes anonyme à partir : – D'interface – De classe abstraite – De classe concrète Syntaxe : new Type(param1,param2...) { //définition de membres //(méthode/champs/classe) } Variable locale et classe anonyme ● La valeur des variables locales et paramètres de méthode finaux est disponible dans la classe anonyme interface Operation { int eval(); } public class OperatorFactory { public static Operation plus(final int e1,final int e2) { return new Operation() { public int eval() { return e1+e2; } }; } } Classes anonymes & limitation ● ● Au vu de la syntaxe, il est impossible de créer une classe anonyme : – Implantant plusieurs interfaces – Héritant d'une classe et implantant une interface – Dont on veut utiliser un champ en dehors de la classe interne Dans ces cas, il est toujours possible de créer une classe interne à une méthode Les énumérations ● ● ● Une énumération est un type qui regroupe un ensemble de constantes Les valeurs de l'énumération sont des objets constants (final) et unique (static) qui possède une valeur entière unique (ordinal()) et un nom (name()) unique. Une énumération connaît l'ensemble de ses valeurs (values()) et est capable d'associer un nom à une valeur (valueOf()) Exemple d'énumération public enum Option { l, a, v; public static Option getOption(String s) { if (s.length()!=2) return null; if (s.charAt(0)!='-') return null; return Option.valueOf(s.substring(1)); } public static void main(String[] args) { for(String s:args) { Option option=getOption(s); if (option!=null) System.out.println(option.name()+" "+option.ordinal()); } } //java Option -a -v -l // a 1 v 2 l 0 } Champs de l'énumération ● Les champs de l'énumération sont accessible par la notation “.” ● Ils sont static final par défaut. ● Ils peuvent être utilisés avec un import static ● ● Les champs ont le type de l'énumération (attention aux enumérations abstraites) Les énumérations vides sont interdites Énumération et Constructeur ● Il est possible de spécifier un ou plusieurs constructeurs à une énumération public enum Age { jeune(20), mure(40), âgé(60), vieux(80), cadavérique(999); Age(int année) { this.année=année; } private final int année; private static Age getAge(int année) { for(Age age:Age.values()) if (age.année>=année) return age; return cadavérique; } public static void main(String[] args) { System.out.println(getAge( new Scanner(System.in).nextInt())); } } Classe interne et enumération ● ● Il n'est pas possible de définir une énumération dans une méthode ou dans une classe interne non statique une énumération interne est statique public class Myclass { class Inner { enum Arg { // interdit toto } } void f() { enum Arg2 { // interdit toto } } } Énumération et switch ● Il y a un import static implicite en fonction du type de l'argument à l'intérieur du switch public static void performs(Set<Option> set) { for(Option option:set) switch(option) { case l: // et pas besoin de Option.l System.out.println("l"); break; case a: System.out.println("a"); break; case v: System.out.println("v"); break; } } } ● Inconvénient: Code pas objet Énumération abstraite ● Les membres d'une énumération peuvent déclarer un bloc selon les mêmes règles que les classes anonymes public enum Option { public static void performs(Set<Option> set) l { { public void performs() { for(Option option:set) System.out.println("l"); option.performs(); } } }, a { } public void performs() { System.out.println("a"); } }, v { public void performs() { System.out.println("a"); } }; L'énumération ne doit pas être déclarée abstract !! public abstract void performs(); } Méthodes et énumération ● Il est possible de déclarer des méthodes, champs, blocs d'initialisation, classes internes : – Statique ou non pour l'énumération – Non statique pour un membre de l'énumération (même si pas accessible de l'exérieur) public enum MyEnum { max { int f() { // ok, mais pas accesible return 3; } }; void g(int index) { return ordinal()+index; } } Énumération et java.lang.Enum ● ● Les enumérations héritent de java.lang.Enum La class Enum est paramétré : Enum<E extends Enum>, E est le type de l'énumération public enum Option { l, a, v; public static void main(String[] args) { Enum<Option> opt=Option.l; Enum<?> opt2=Option.a; } } ● ● Option est sous-type de Enum<Option>, lui même sous-type de Enum<?> Ne jamais utiliser le type inhabité Enum<Option> Énumération et héritage ● Le compilateur garantit que seules les enum héritent de java.lang.Enum. – Une classe ne peut hériter (avec extends) de Enum – Une classe ne peut hériter d'une énumération – Une énumération ne peut hériter d'une classe ou d'une énumération public class A extends Enum { } public class A extends Option { } public enum Option extends A { } // erreur // erreur // erreur Enumération et interface ● Une énumération peut implanter une interface ou plusieurs interfaces public interface Performer { public void performs(); } public enum Option implements Performer { l { public void performs() { System.out.println("l"); } },... } Énumération et champs static ● Les constructeurs ou initialiseurs ne peuvent accéder aux champs statiques (problème de circularité d'initialisation) public enum MyColor { RED, GREEN, BLUE; static final Map<String,MyColor> map= new HashMap<String,Color>(); MyColor() { map.put(name(),this); // si c'était possible // NullPointerException } } ● Utiliser un bloc static (qui sera exécuté après l'initialisation des champs de l'enum)