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)