Le langage Java en quelques mots
- Langage de programmation créé en 1995 par James Gosling et Patrick Naughton au sein de Sun
- API assez étendue avec support des entrées/sorties, collections...
-
Devise WORA (Write Once, Run Anywhere), comment ?
- Le code est compilé en bytecode
- Le bytecode est exécuté par une machine virtuelle (JVM)
- Le bytecode peut être converti en code natif à l'exécution (JIT)
- Implantations de JVM sur de nombreuses plate-formes (même sur des smartcards)
- Langage également utilisé sous Android (mais pas avec la JVM)
-
Un petit historique de Java
- Version 1.0 (1995) : 1ère version stable
- Version 1.1 (1997) : classes internes, introspection...
- Version 1.2 (1998) : API collections, API graphique Swing, JIT...
- Version 1.3 (2000) : changement de VM, JNDI, JPDA...
- Version 1.4 (2002) : NIO...
- Version 1.5 (2004) : generics, annotations, enums, for each...
- Version 1.6 (2006) : améliorations diverses, support de langages de script
- Version 1.7 (2011) : invokedynamic, try-resource, nouvelle API fichiers
- Version 1.8 (2014) : méthodes par défaut dans interface, expressions lambda...
Pourquoi développer en Java ?
-
Pour réaliser des applications multi-plateformes sédentaires
- Applications serveurs (i.e. applications web utilisant des servlettes)
- Applications de bureau (en ligne de commande ou utilisant la bibliothèque graphique Swing)
- Pour réaliser des applications mobiles sous Android
Les multiples facettes de Java
- C'est la principale île d'Indonésie
- C'est un langage (décrit dans la Java Language Specification)
- Ce sont des interfaces de programmation (API) : Standard Edition et Enterprise Edition
-
Et également une spécification de machine virtuelle
- Il existe des compilateurs en bytecode Java pour de nombreux langages : Python, Ruby, Groovy, Eiffel...
Les interfaces de programmations (API)
-
Interfaces Java (proposées par Sun puis Oracle)
-
Java Standard Edition : classes d'usage général (java.lang) entrées/sorties, collections
- JDK Open Source OpenJDK
- Java Enterprise Edition : pour un usage professionnel notamment pour développer des applications web
- Java Micro Edition : pour téléphones et PDAs (en désuétude)
-
Java Standard Edition : classes d'usage général (java.lang) entrées/sorties, collections
-
Interface Android (proposé par Google)
- Réimplantation compatible du JDK d'Oracle
- Ajout de classes pour exposer des fonctionnalités spécifiques (communication inter-processus, réseau, usage de capteurs)
Un Hello World en Java
public class HelloWorld { public static void main(String[] args) { System.out.println("Hello World !"); } }
Remarque :
- L'unité élémentaire de programmation en Java est la classe, chaque classe est placée dans un fichier indépendant qui porte le même nom que la classe avec l'extension ".java"
- Il n'y a pas de séparation entre déclaration de types et implantation (contrairement au C(++), Pascal...)
- System.out est un objet représentant la sortie standard
- Il existe également l'objet System.err qui représente la sortie erreur standard où il est possible d'écrire des erreurs rencontrées
Compilation et exécution
-
Transformation du code source Java en bytecode Java avec le compilateur javac : javac HelloWorld.java
- → génération du fichier HelloWorld.class
- En cas d'erreurs (lexicales, syntaxiques, sémantiques), signalement par le compilateur
- Exécution du bytecode par la machine virtuelle : java HelloWorld
Sûreté du typage
Contrairement à PHP (et comme le Pascal objet), Java est un langage compilé (en bytecode) avec un système de typage fort :
- La cohérence des types est vérifiée à la compilation
- Une conversion implicite entre types est quelquefois possible
int a = 7; a = 8; // Cette affectation ne pose pas de problème a = 8.0; // On affecte un double qui sera automatiquement converti en int a = true; // Cette affectation est impossible, le compilateur émettra une erreur String s = "le contenu de a est : " + a; // la concaténation entre une string et un int est possible: l'int est converti en string
Quels sont les résultats affichés ?
System.out.println("a: " + 3/2.0); System.out.println("b: " + 3/(int)2); System.out.println("c: " + 1 + 1.0); System.out.println("d: " + 1 + 1); System.out.println("e: " + (1 + 1)); System.out.println("f: " + (1 / 0)); System.out.println("g: " + (1 / 0.0));
Variables locales
- Variables locales déclarables n'importe où dans la méthode (contrairement à Pascal).
- La portée de la variable est limitée au bloc de déclaration
Exemple : une méthode calculant la factorielle d'une nombre (cette méthode est implantée dans une classe)
public static long fact(int n) { long result = 1L; for (int i = 2; i <= n; i++) result *= i; // la portée de i est limitée au bloc de la boucle for System.out.println(i); // cette instruction ne compile pas car i n'est plus accessible return result; }
Cette méthode est équivalente :
public static long fact2(int n) { long result = 1L; int i; i = 2; while (i <= n) { result = result * i; i += 1; } return result; }
On pourrait également proposer une telle implantation :
public static long fact3(int n) { return (n < 2) ? 1L : n * fact3(n-1); }
Deux types de types
En Java, il existe deux catégories de types :
-
les types primitifs
-
types représentant des entiers :
- byte (8 bits), short (16 bits), int (32 bits), long (64 bits)
- la taille des entiers ne change pas selon les architectures
- les entiers sont signés
- type boolean : deux valeurs possibles (false ou true)
- type char : pour représenter un caractère Unicode (par exemple 'a' ou alors '是')
- types représentant des flottants : float (32 bits) et double (64 bits)
-
types représentant des entiers :
-
les types objets : le type ancêtre de tous les types objet est Object
- il existe également des versions objets de tous les types primitifs
- les types objets sont toujours manipulés par référence
Les noms de types primitifs commencent toujours par une minuscule. Les noms de types objet doivent toujours commencer par une majuscule et adopter un nommage CamelCase (mais ce n'est qu'une convention non contraignante).
La classe Scanner
Scanner permet de lire une entrée en la découpant en unités élémentaires.
On instancie d'abord un Scanner afin de lire sur l'entrée standard de la console (mais on pourrait lire sur n'importe quel entrée telle qu'un fichier ou une socket réseau) :Scanner s = new Scanner(System.in);On peut ensuite récupérer un mot (s.next()), une ligne (s.nextLine()), un entier (s.nextInt()), un double (s.nextDouble())... Mais auparavant, il ne faut pas oublier d'interroger le scanner pour savoir si une telle donnée existe (s.hasNext(), s.hasNextLine(), s.hasNextInt()...).
Un petit exemple :
public class Parrot { public static int listenAndRepeat(int n) throws IOException { int counter = 0; Scanner s = new Scanner(System.in); while (s.hasNextLine()) { String line = s.nextLine(); for (int i = 0; i < n; i++) { System.out.println(line); counter++; } } s.close(); return counter; } public static void main(String[] args) throws IOException { int n = Integer.parseInt(args[0]); listenAndRepeat(n); } }
La Javadoc, l'ami du développeur Java
Toute la documentation à propos des classes Java peut être trouvée dans la Javadoc. Une Javadoc est disponible pour l'API du J2SE. Il est possible également de générer la Javadoc pour ses propres programmes. Par exemple, documentons la classe Parrot :
/** A nice class to emulate a parrot * repeating all the lines that are fed to him */ public class Parrot { ... /** Retrieve lines from the standard input * and print them n times on the standard output * * @param n the number of times to print the lines * @return the number of printed lines */ public static int listenAndRepeat(int n) { ... } }
Générons la Javadoc avec le programme javadoc:
`` javadoc -d destination Parrot.java
Un premier objet
- Style procédural à déconseiller : champs et méthodes statiques
- Programmation objet : instantiations de classes sous la forme d'objets
public class Parrot { public static final int DEFAULT_REPEAT_NUMBER = 1; final String name; final int repeatNumber; public Parrot(String name, int repeatNumber) { this.name = name; this.repeatNumber = repeatNumber; } public Parrot() { this(DEFAULT_REPEAT_NUMBER); } public void present() { for (int i = 0; i < repeatNumber; i++) System.out.println("My name is " + name); } public void listenAndRepeat() throws IOException { ... } public static void main(String[] args) throws IOException { Parrot p = new Parrot("Coco", Integer.parseInt(args[0])); p.present(); p.listenAndRepeat(); }
-
Une classe peut avoir zéro, un ou plusieurs constructeurs
- Si zéro constructeur : un constructeur sans argument est implicitement créé
- Possibilité d'appeler un constructeur depuis un autre constructeur (pour pallier l'absence d'arguments par défaut)
-
Le constructeur initialise les champs de la classe (les champs déclarés final doivent obligatoirement être initialisés et ne peuvent être modifiés par la suite)
- En cas d'ambiguïté entre champ et argument de constructeur, le champ est désigné par this.nomDuChamp
-
On créé un nouvel objet avec new NomDeLaClasse(arg1, arg2, ...) ; cela réalise deux opérations
- L'allocation de l'objet en mémoire (dans une zone appelée tas)
- L'initialisation de l'objet en appelant le constructeur correspondant aux types des arguments
- En Java, pas de destructeur : la gestion de la mémoire est automatique (avec un ramasse-miettes réalisant les désallocations)
Manipulation des objets par référence
- En Java, les objets sont manipulables exclusivement par référence.
- Que fait le code suivant ?
public class Point { final int x; final int y; public Point(int x, int y) { this.x = x; this.y = y; } public String toString() { return String.format("(%d,%d)", x, y); } }
public class PointHandler { public static void invertPoint(Point p) { Point invertedPoint = new Point(p.y, p.x); p = invertedPoint; } public static void main(String[] args) { //Point p = null; //System.out.println(p.x); // raise NullPointerException Point p = new Point(1, 2); System.out.println(p); invertPoint(p); System.out.println(p); } }
-
Comment remédier au problème ?
- En créant dans Point une méthode invert() instantiant un nouveau Point et retournant sa référence
- Possibilité également de créer une méthode invert() réalisant la modification sur le point lui-même : les champs x et y ne doivent plus être finaux dans ce cas.
Une bonne habitude : les getters et setters
- Contrairement au Pascal objet (ou C#), pas de concept de propriétés : on ne dispose que de méthodes
-
Pour simuler le comportement de propriété :
- Création d'un getter pour obtenir une information de l'objet : type getInfo()
- Création d'un setter pour modifier l'information de l'objet : void setInfo(type arg)
-
Protection de l'accès et l'écriture des champs
- Les champs sont déclarés private : seules les méthodes de la classe peuvent y accéder
- Les getters et setters sont déclarés public : tout le monde peut les appeler
- Pour certains champs correspondant à un état interne de la classe, on pourra ne pas utiliser de getter ou setter
Un petit exemple avec un point :
public class Point { private int x = 0; private int y = 0; public int getX() { return x; } public void setX(int x) { this.x = x; } public int getY() { return y; } public void setY(int y) { this.y = y; } public void invert() { int tmp = x; this.x = y; this.y = tmp; } }
Structures de contrôle
En Java, les structures de contrôle classiques disponibles en C(++), PHP... sont utilisables :
-
Structures conditionnelles
- if (condition) block
- if (condition) block1 else block2
- switch (var) { case val1: ...; case val2: ...; ...; default: ...;}
-
Structures de répétition
- while (condition) block
- do block while (condition)
- for (initInstr; condition; postInstr) block
- for (type var1: var) block (structure dite bloc for-each disponible depuis Java 1.5)
-
Remarques :
- block est une instruction unique terminée par un point-virgule ou alors une suite d'instructions entourée par des accolades ({ instr1; instr2; ... })
- Une variable locale déclarée dans un bloc a une portée d'utilisation limitée à ce bloc
- Sortie prématurée d'une structure de répétition avec l'instruction break
- Saut direct à la prochaine itération avec l'instruction continue
Un exemple : calcul du n-ième terme de la suite de Fibonacci
Suite de Fibonacci ainsi définie :
- fib(0) = 0
- fib(1) = 1
- pour i ≥ 2, fib(n) = fib(n-1) + fib(n-2)
public static long fib(final int n) { long a = 0; // penultimate term long b = 1; // ultimate term long result; switch (n) { case 0: result = a; break; case 1: result = b; break; default: for (int i = 2; i <= n; i++) { final long tmp = a; a = b; b += tmp; } result = b; } return result; } public static long main(String[] args) { int n = Integer.parseInt(args[1]); // rank to compute System.out.println(String.format("fib(%d)=%d", n, fib(n))); }
Remarques :
- Déclarer un argument ou une variable locale final permet de la rendre constante (pour éviter un changement accidentel)
Un petit jeu : deviner un nombre
- L'ordinateur tire au sort un entier et propose à l'utilisateur de le trouver
- L'utilisateur fait des propositions et l'ordinateur lui indique si celle-ci est inférieure, supérieure ou égale au nombre tiré ; dans ce dernier cas le joueur a gagné et on affiche son nombre de tentatives
Quelques indications pour réaliser ce jeu :
- Pour tirer un entier au sort : Random r = new Random(); int n = r.nextInt(N)
- Pour lire des entiers entrés au clavier : on utilise un scanner et les méthodes hasNextInt() et nextInt()
Crééons des points
Nous allons instantier plein de points aléatoires avec une fabrique de points.
public class PointFactory { final Random r; public PointFactory() { this.r = new Random(); // we initialize the random number generator } public Point createRandomPoint() { return new Point(r.nextInt(), r.nextInt()); } }
Et nous écrivons maintenant une méthode main :
public class PointTester { public static void main(String[] args) { PointFactory pf = new PointFactory(); // List<Point> l = new ArrayList<Point>(); while (true) { Point p = pf.createRandomPoint(); // l.add(p); } } }
- La méthode main ne termine jamais à cause de la boucle while (true).
- Décommentons les deux lignes pour insérer chaque point dans une liste... le programme s'arrête... mais pas pour de bonnes raisons !
- Tous les objets sont alloués dans le tas (zone mémoire réservée pour le processus)... mais le tas n'est pas de taille infinie
- Lorsque des objets ne sont plus accessibles, le ramasse-miette les supprime du tas
Les tableaux
Un tableau est une suite finie de cellules contiguës en mémoire pouvant accueillir des éléments d'un type donné :
- d'un type primitif : chaque cellule contient la valeur de l'élément (int, float, boolean...)
- d'un type objet : la cellule stocke une référence de l'objet
Quel est l'espace mémoire maximal occupé sur le tas par l'exécution du code suivant :
public class ArrayTest { public static void main(String[] args) { Point[] pointArray = new Point[1000]; Point p = new Point(0,0); for (int i = 0; i < pointArray.length; i++) pointArray[i] = p; p.setX(1); for (int i = 0; i < pointArray.length; i++) System.out.println(p); } }
Espace mémoire occupé :
- Allocation d'un Point : 2 int de 32 bits soit 8 octets
- Allocation d'un tableau de 1000 références vers des Point : 1000 références (pointeurs) de 32 bits (sur architecture 32 bits) soit 4000 octets
Remarques :
- Il est possible de connaître la taille d'un tableau grâce au champ length
- L'indice de départ d'un tableau est 0 (comme en C) ; le dernier élément d'un tableau de taille N est indicé N-1
- Attention à ne pas dépasser la borne supérieure d'un tableau (ni à demander un indice négatif) → ArrayIndexOutOfBoundsException
- On peut créer des tableaux à plusieurs dimensions (il s'agit en fait de tableaux de tableaux...)
Utilisation d'un tableau : le crible d'Eratosthène
package fr.upem.jacosa.general; import java.io.PrintStream; import java.util.Arrays; /** Implementation of the Eratosthenes's sieve to identify the first prime numbers */ public class PrimeSieve { private final boolean[] primeStatus; public PrimeSieve(int n) { primeStatus = new boolean[n]; Arrays.fill(primeStatus, true); // by default all the integers are prime... until being declared as a multiple by a sieve iteration } /** Mark integers that are multiple of k */ public void mark(int k) { for (int i = 2*k; i < primeStatus.length; i += k) primeStatus[i] = false; } /** Do all the marking iterations */ public void mark() { if (primeStatus.length >= 0) primeStatus[0] = false; if (primeStatus.length >= 1) primeStatus[1] = false; for (int i = 2; i * i < primeStatus.length; i++) if (primeStatus[i]) mark(i); } public void printPrimes(PrintStream out) { for (int i = 0; i < primeStatus.length; i++) if (primeStatus[i]) out.println(i); } public static void main(String[] args) { PrimeSieve ps = new PrimeSieve(Integer.parseInt(args[0])); ps.mark(); ps.printPrimes(System.out); } }