Java

advanced

Antoine Vernois / @avernois

Antoine Vernois

Agile Software Craftsman

Software Anarchist

blog : https://blog.crafting-labs.fr

twitter : @avernois

et vous ?

post-it

Vous attendez quoi de cette formation ?


tour de table

  • qui êtes vous ?
  • qu'est ce que vous faites ?
  • qu'est ce que vous aimez ?
  • qu'est ce que vous aimez moins ?
  • votre expérience avec les tests et le tdd ?

détails pratiques

  • horaires
  • repas
  • pauses

Maven

maven, un outil

  • de build
  • de gestion de dépendance
  • de documentation

Simple POM



  4.0.0
  fr.craftinglabs.training
  my-app
  jar
  1.0-SNAPSHOT
  My awesome project

                        

Sources structure


my-app
+- pom.xml
  +- src
    +- main
    | +- java
    | +- resources
    +- test
      +- java
      +- resources

                        

ajouter des dépendances



    
    
        
            junit
            junit
            4.11
            test
        
    

                        

3 types de scope

  • compile
  • test
  • runtime

Trouver les dépendances

Note: avoir son propre repository manager en interne peut être une bonne idée :)

Cycle de vie

les phases du cycle de vie du build les plus courantes :
  • process-resources
  • compile
  • test
  • package
  • install
  • deploy

Il y en a bien d'autres : Doc Maven

filtrer les resources

Remplacer un variable (ex: ${nom.variable}) par sa valeur définie dans le pom.



    
  
    
      
        src/main/resources
        true
      
    
  

                        

définir ses properties

${my.properties}



    
        hello
    

                        

multi modules


 
  4.0.0
 
  fr.craftinglabs.training
  app
  1.0-SNAPSHOT
  pom
 
  
    my-app
    my-webapp
  

                        

structure


+- pom.xml // le pom parent
+- my-app
| +- pom.xml
| +- src
|   +- main
|     +- java
+- my-webapp
| +- pom.xml
| +- src
|   +- main
|     +- webapp
                        

super pom

Dans les poms des modules, on rajoute un lien vers le pom parent


  
    fr.craftinglabs.training
    app
    1.0-SNAPSHOT
  
                        

Collections

Overview

sortir le schéma qui va bien :)

Iterable

Iterable est l'interface des Collection


public interface Iterable<T> {
    Iterator<T> iterator();
}

public interface Iterator<T> {
    boolean hasNext();
    T next();
    void remove(); // à implémenter uniquement si on le supporte
}         
                        

iterable et for-loop


Iterable<AType> iterable = .. ;
for(AType item : iterable) {
    doThings();
}
                       

Collection

ajout, retrait


String anElement = "an element";
Collection collection = new HashSet();

boolean collectionChanged = collection.add(anElement); 
boolean elementRemoved = collection.remove(anElement);

collection.addAll(aSet); // aSet is a Set :)
collection.removeAll(aSet);
collection.retainAll(aSet); // ne garde dans collection que les éléments présent dans aSet.
                        

List

  • Collection ordonnée
  • un élément peut être présent plusieurs fois
  • implem : ArrayList, LinkedList, Vector, Stack

accès


List<String> listA = new ArrayList<>();
listA.add("element 1");
listA.add("element 2");

listA.add(0, "element 0"); // qu'est ce qui se passe ?

listA.remove(0);
listA.remove("element 1");

                        

Set

  • un élement ne peut être présent qu'une fois
  • implem: HashSet, TreeSet, EnumSet, LinkedHashSet

accès


Set<String> setA = new HashSet<>();

setA.add("element 0");
setA.add("element 1");
setA.add("element 2");

setA.remove("element 0");      
                        

SortedSet

Un type de Set trié automatiquement.


SortedSet<String> setA = new TreeSet<>();

Comparator<String> comparator = new MyComparator<>();
SortedSet<String> setB = new TreeSet<String>(comparator);                        
                        

NavigableSet


NavigableSet<String> original = new TreeSet<>();
original.add("1");
original.add("2");
original.add("3");
original.add("4");

NavigableSet<String> reverse = original.descendingSet();

NavigableSet<String> headset = original.headSet("3"); // contient 1 et 2
NavigableSet<String> subset  = original.subSet("1", "3"); // contient 1, et 2
                        

Queue

Comme une liste, sauf que l'objectif est d'ajouter les élements à la fin, et de les rétirer au début.

accès


Queue<String > queue = new LinkedList<>();

queue.add("element 0");
queue.add("element 1");
queue.add("element 2");

queue.remove(); // retire et retourne "element 0"
queue.peek(); // retourne "element 0" sans le retirer
                        

DeQue

Comme une Queue, sauf qu'on peut y accéder des deux côté

accès


DeQue<String > dequeue = new LinkedList<>();

deque.add("element 1"); // ajout à la fin
deque.addFirst("element 0"); // ajout au début
deque.addLast("element 2"); // ajout à la fin

deque.remove(); // retire et retourne "element 0"
deque.peek(); // retourne "element 0" sans le retirer
                        

Stack

Une stack est une implémentation de List, dont l'objectif est d'être une LIFO.


Stack<String> stack = new Stack<>();

stack.push("1");
stack.push("2");
stack.push("3");

String top = stack.peek(); // retourne "3" sans le retirer

int index = stack.search("3"); // retourne 1 ("3" est en tête)
                               // yep, l'index des stack commence à 1 :)

stack.pop(); //retourne "3" et le retire
stack.pop(); //retourne "2" ...
stack.pop(); //retourne "1"
                        

Map

Permet de stocker des ensembles clés-valeurs. Tous les élements sont itérables

Map<Integer, String>> map = new HashMap<>();
map.put(1, "1");
String value = map.get(1);

Set<Map.Entry<K, V>> entries = map.entrySet();
Set< keys = map.keySet();
Collection<V> values = map.values();
                        

SortedMap

Comme les SortedSet, mais avec des Map :)

NavigableMap

idem

Equals et Hashcode

Pour que equals() et contains() fonctionne comme on peut s'y attendre sur les listes, il est indispensable de que equals et hashcode des objets contenus soit correctement défini !


WARNING !

equals et hashcode doivent toujours être surchargé ensemble !

L'unique règle du hashcode

Si obj1.equals(obj2) alors obj1 et obj2 doivent avoir le même hashcode.

Trier les listes

Collections.sort(List) ou List.sort depuis 1.8


List<String> list = Arrays.asList("1", "3", "2");
Collections.sort(list); // tri selon l'ordre naturel

Comparator<String> comparator = new MyStringComparator();

Collections.sort(list, comparator);  
list.sort(comparator); // 1.8
                        

Comparator


public interface Comparator<T> {
    int compare(T object1, T object2);
}

public class MyStringComparator implements Comparator<String> {

    public int compare(String s1, String s2){
        int compare;
       // si s1 > s2, compare > 0
       // si s1 == s2, compare = 0
       // si s1 < s2, compare < 0
       return compare;
    }
}

Generics et collections

Lambda

en java 7


public interface StateChangeListener {
    public void onStateChange(State oldState, State newState);
}
--
public class StateOwner {
    public void addStateListener(StateChangeListener listener) { ... }
}
--
StateOwner stateOwner = new StateOwner();

stateOwner.addStateListener(new StateChangeListener() {
    public void onStateChange(State oldState, State newState) {
        System.out.println("State changed")
    }
});                        
                        

en java 8


stateOwner.addStateListener(new StateChangeListener() {
    public void onStateChange(State oldState, State newState) {
        System.out.println("State changed")
    }
});
                        
devient

stateOwner.addStateListener(
    (oldState, newState) -> System.out.println("State changed"));
                        

Lambda et collections

On passe par les streams pour relier les deux.


List<String> items = new ArrayList<String>();

items.add("un");
items.add("deux");
items.add("trois");

Stream<String> stream = items.stream();
                        

Ensuite la manipulation se passe en deux phases :

  • la config
  • la processing

config

filter


stream.filter(item -> item.startWith("u"));
                        

map


stream.map(item -> item.toUpperCase());
                        

procession

collect


List<String> strings = stream.map(item -> item.toUpperCase())
                                   .collect(Collectors.toList());
                        

reduce


String reduced = items.stream()
        .reduce((acc, item) -> acc + " " + item)
        .orElse("");
                        

min maw


String shortest = items.stream()
        .min(Comparator.comparing(item -> item.length()))
        .orElse("");
                        

Debugger

Logging

Réflexion

& introspection

Example


Class helloClass = HelloWorld.class;                            
Method[] methods = helloClass.getMethods();
for(Method method: methods) {
    System.out.println(methods.getName());
}
                        

Class


Class helloClass = HelloWorld.class;
String fullClassName = helloClass.getName(); // fully qualified

// sans le package                        
Sring simpleClassName = helloClass.getSimpleName(); 
                        

modifiers


int modifiers = helloClass.getModifiers();

// les modifiers sont encodés dans un int.
// il faut passer par Modifier pour le décoder
Modifier.isPublic(modifiers);
Modifier.isFinal(modifiers);
...                            
                        

Constructeur


Constructor[] constructors = helloClass.getConstructors();

//le constructeur avec une String en paramêtre
Constructor<HelloWorld> constructor = helloClass.getConstructors(new Class[]{String.class});

                        

instanciation



HelloWorld hello = constructor.newInstance("myArgs");                         
                        

fields


Field[] fields = helloClass.getFields();

Field field = helloClass.getField("myField");
Class fieldType = field.getType();


HelloWorld hello = new HelloWorld();
field.get(hello);
field.set(hello, value);

//pour un champ static
someStaticField.set(null, value);
                        

Methods


Method[] methods = helloClass.getMethods();

// si HelloWorld a une methode myMethodName(String param);
Method method = helloClass.getMethod("myMethodName", String.class);

Class returnType = method.getReturnType();
Class[] paramTypes = method.getParameterTypes();


method.invoke(hello, "first param");
                        

Méthodes et champs privés

Les méthodes getFields, getMethods précédentes ne retourne que les méthodes publiques. Pour toutes les avoir, il faut passer par:


helloClass.getDeclaredMethods();
helloClass.getDeclaredMethod("myPrivateMethodName", String.class);

helloClass.getDeclaredFields();
helloClass.getDeclaredField("myPrivateField");
                        
Pour invoquer une méthode privée ou changer une valeur d'un champ privé, il faut d'abord le rendre visible

aPrivateField.setAccessible(true);
aPrivateMethod.setAccessible(true);
                        

les arrays

Il faut passer par la classe Array

int[] array = (int[]) Array.newInstance(int.class, 5);

Array.set(array, 0, 42);
Array.get(array, 0):

String[] strings = new String[3];
Class stringArrayClass = strings.getClass();
Class stringArrayComponentType = stringArrayClass.getComponentType(); 
                        

ClassLoader

Toutes les classes sont chargées par une instance de ClassLoader (pas forcemment la même pour toutes les classes).

Les différent ClassLoader d'une application sont organisé de façon hierarchique.

  1. le CL regarde si la classe est déjà chargée
  2. si non, il demande à son parent de le faire
  3. si le parent ne trouve pas la classe, il le fait lui même.

Dynamic ClassLoading



public class MyClass {

  public static void main(String[] args) throws ClassNotFoundException {

    ClassLoader classLoader = MyClass.class.getClassLoader();

    Class clazz = classLoader.loadClass("fr.craftinglabs.training.java.ToLoad");
}
                        

Dynamic reload

Vu que par défaut, un ClassLoader ne recharge pas une classe qu'il a déjà chargée, pour faire du reloading, il faut créer son propre ClassLoader

Sauf que. Une même classe, chargée par deux ClassLoader différents est vu comme deux classes distinctes (et non compatible).

Proxy

La réflexion permet de créer dynamiquement des implmentations d'interface.


//le truc vers qui seront transférer tous les appels
InvocationHandler handler = new MyInvocationHandler();


MyInterface proxy = (MyInterface) Proxy.newProxyInstance(
                            MyInterface.class.getClassLoader(),
                            new Class[] { MyInterface.class }, // la liste des interfaces à implémenter
                            handler);
                        

Invocation handler

                      
public interface InvocationHandler{
  Object invoke(Object proxy, Method method, Object[] args)
         throws Throwable;
}
                        

Expérience

expérience 1

Vous voyez quatre carte

                     ___    ___    ___    ___
                    |   |  |   |  |   |  |   |
                    | D |  | 7 |  | 5 |  | K |
                    |___|  |___|  |___|  |___|

Sur chaque carte, il y a un chiffre d'un côté, une lettre de l'autre

.

Quelle(s) carte(s) faut-il retourner pour savoir si la règle suivante est vérifiée :
S'il y a un D d'un côté, alors il y a 5 de l'autre ?

expérience 2

Quatres personnes sont dans un bar. Vous savez :

  1. La personne 1 boit une boisson alcoolisée.
  2. La personne 2 à moins de 18 ans.
  3. La personne 3 à plus de 18 ans.
  4. La personne 4 boit une boisson non-alcoolisée?

Quelle(s) personne(s) faut-il intéroger pour savoir si la règle suivante est vérifiée :
Si un personne boit de l'alcool, elle doit avoir plus de 18 ans ?

Annotations

Format - utilisation

devant l'élement auquel il s'applique

@Author(name="Antoine", date="2016/04/04")
public class MyAnnotatedClass {

    @MyAnnotation
    public void myAnnotatedMethod(@MyParamAnnotation String param) {
    ...
    }
}
                    

Format - écriture


public @interface Author {
    String name() default "Antoine";
    String date();
}

Format - écriture

Les annotations sont définies à l'aide d'annotations:

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.CLASS})
@Inherited
public @interface Author {
    String name() default "Antoine";
    String date();
}

@Retention

La durée de vie de l'annotation
  • RUNTIME : disponible via reflexion au runtime
  • CLASS (défaut): stockée dans la classe, mais innaccessible au runtime
  • SOURCE : disparait à la compile (pour l'analyse statique)

@Target

Sur quoi porte l'annotation. ElementType[]
  • CONSTRUCTOR
  • FIELD
  • LOCAL_VARIABLE
  • METHOD
  • PARAMETER
  • PACKAGE
  • ANNOTATION_TYPE
  • TYPE : class, interface, enum ou annotation

les autres

  • @Inherited : l'annotation est-elle hérité par les sous-classes(défaut false)
  • @Documented : l'annotation apparait dans la javadoc de l'élément à laquelle elle est appliquée

Accès : runtime


...                            
Class classe = MyAnnotatedClass.class;
Author author = (Author) classe.getAnnotation(Author.class);
if (author != null) {
        System.out.println("Author:" + author.name());
}
...
                        

Accès : à la compil

Pour cela il faut créer un Processor qui s'occupera de gérer notre annotation

AbstractProcessor fait une partie du boulot pour vous.


@SupportedAnnotationTypes({"fr.craftinglabs.training.java.annotation.Print"})
public class MyProcessor extends AbstractProcessor {
    @Override
    public boolean process(Set<? extends TypeElement> annotations, 
                           RoundEnvironment roundEnv) {
        //process your annotation here
        return true;
    }
}
                        

On précise les annotation supportées par ce processor via une annotation


@SupportedAnnotationTypes({"fr.craftinglabs.training.java.annotation.Print"})
                        

Enregistrement

Pour être utilisé, il faut déclarer le processor. Il faut créer une fichier


META-INF/services/javax.annotation.processing.Processor

Il doit contenir le nom des classes de processor.

(N)IO(2)

IO / NIO

  • IO : historique, orienté stream, bloquant
  • NIO : orienté buffer, non bloquant
  • NIO2 : 1.7, dépoussiérage de l'API NIO

streams vs buffers

streams

lecture d'un ou plusieurs octets depuis un stream.

pas de mise en cache.

impossible de remonter le flux.

buffers

lecture depuis un buffer que l'on accède comme on veux.

le buffer contient il assez d'info pour être pertinent ?

le buffer courant est écrasé à chaque lecture.

Non blocking IO

les opérations d'écriture/lecture sont non bloquantes. Il est donc possible de gérer des sources simultanés depuis un unique thread.

Cas d'usage ?

Java IO

Peu de connexions.

Beacoup de données.

Java NIO

Beaucoup de connexionx

Peu de données.

Fichiers

Java 7 simplifie grandement l'utilisation de NIO pour la manipulation de fichier via Files et FileSystem.

Exceptions

date et timezones

JSR 310

Pendant longtemps, la gestion des dates/times en java était un enfer.

Depuis Java 7 et la JSR310, les choses se sont améliorées.

Toutes les objets Date/Time sont immutables.

LocalTime LocalDate

Local* représentent la moment du point de vue de celui qui la demande. Il n'y a aucune info de timezone. C'est l'heure/date, à l'endroit où le système s'exécute.


LocalDate date = LocalDate.now();
LocalDateTime dateTime = LocalDateTime.parse("2015-12-14T16:12:17");
LocalTime time = LocalTime.of(17,18);

time.getHour();
date.getMonth(); retourne un enum
date.getMonthValue();

date.getDayOfYear();
                        

Ajustement


LocalDateTime another = dateTime.withDayOfMonth(15).withYear(2016);
LocalDateTime yetAnother = dateTime.plusWeeks(3).plus(4, WEEKS);

// import static java.time.temporal.TemporalAdjusters.*;
LocalDateTime ldt = dateTime.with(lastInMonth(TUESDAY));
                        

TimeZone

  • ZonedDateTime: un DateTime associé à une TimeZone.
  • OffsetTime: avec un offset, mais sans la zone
  • OffsetDateTime

ZonedDateTime zoned = ZonedDateTime.of(dateTime, id);
ZonedDateTime.parse("2016-04-14T22:30:30+02:00[Europe/Paris]");

OffsetDateTime time = OffsetDateTime.now(ZoneId.of("Europe/Paris"));
time = time.withOffsetSameInstant(ZoneOffset.of("+04:00"));
                        

ZoneId et ZoneOffset

  • ZoneOffset: le décalage par rapport à GMT/UTC
  • ZoneId : l'identifiant de TimeZone (ex: "Europe/Paris")

Par défaut, la JVM utilise les ZoneId défini par l'IANA. Il en existe 2 autres : les ZoneId de l'IATA (basé sur les code aéroport) et ceux de Microsoft.

La config semble se faire du côté de ZoneRulesProvider

charset

La plupart des méthodes qui manipulent des String peuvent prendre un Charset en paramètre.

Java Persistence API

JPA

JPA est une spécification dont l'objectif est d'uniformiser l'accès aux ORM.

Il s'agit d'un ensemble d'interface du package javax.persistence


EntityManagerFactory managerFactory = Persistence.createEntityManagerFactory("persistenceUnit");
EntityManager entityManager = managerFactory.createEntityManager();

Employee employee = new Employee("John");
entityManager.persist(employee);
                        

Intérêt

  • dispo dans JEE et Java SE
  • Mapping objet relationnel avec la BD par annotations
  • un langage de requêtage standardisé

select e from Employee e order by e.nom asc
                        

Entités


public class Employee {
@Id
@GeneratedValue
private Long id;

private String name;
// Accessors
}
                        

Entity Manager Factory

Récupère les infos des Persistence Unit pour pouvoir créer des EntityManager.

Dans un contexte JEE, il est fourni par le serveur d'application.

Entity Manager

gère les échanges entre le code et la BD. Ses actions sont généralement englobé dans des transactions.

Relations entre entités


public class Employee {
    //...
    @OneToOne(cascade = CascadeType.ALL, fetch = FetchType.LAZY)
    @JoinColumn(name = "adresse", unique = true, nullable = false)
    private Adresse adresse;
    //...
                        

public class Adresse {
    // optionnel
    @OneToOne(mappedBy = "adresse")
    private Employee personne;
    //...
                        

Relations entre entités


public class Employee {
//...
    @ManyToOne
    private Department department;
//...
                        

public class Departement {

@OneToMany
private List<Employee> employees = new ArrayList<>();
//...
                        

Mémoire

& garbage collection

Concurence

C'est quoi ?

C'est faire tourner plusieurs programme ou partie d'un programme en parallèle.

Avec le nombre de processeur/croissant dans nos machines, ne pas en tirer partie semble dommage.

Problèmes

  • tous n'est pas parallélisable
  • il y a toujours un léger overhead à la parallélisation
  • visibilité des changements de données entre threads
  • accès concurrent
  • deadlock

Thread

Il est dépendant du processus qui l'a crée.

Il peut partager des éléments avec d'autres Thread (provenant du même processus).

Chaque Thread à son propre cache mémoire pour les données partagées.

Créer un thread


Thread t = new Thread(new MyThreadedCode());
t.start();
                        

public class MyThreadedCode implements Runnable {
    public void run() {
        System.out.println("Runs in a thread.");
    }
}
                        

synchronized

Mot clé du langage, il s'applique à des blocs de code.

Un bloc synchronized ne peut être accéder que par un seul thread à la fois.

Un thread qui entre dans un bloc synchronized est sur de voir les modifications faites par celui qui vient d'en sortir.

volatile

Mot clé du langage, il s'applique à aux variables de classe.

Tout thread lisant une variable est sur d'en voir la dernière version.

Les classes Atomic

Ensemble de wrapper de type primitives Thread safe et sans lock.

java.util.concurrent

Propose des constructions Thread safe pour les Collections.

Note : si elles sont Thread safe, les opérations ne sont pas forcemment atomique.

Thread et Executors

Les Threads sont coûteux à créer et démarrer. Les Executors sont des pools de Thread.



List<Runnable> runnables = new ArrayList<Runnable>();
// mettre des runables dans runnables        

ExecutorService execute = Executors.newFixedThreadPool(10);
for(Runnable r : runnables){
    service.execute(r);
}
service.shutdown();
                        

Callable et Future

La méthode run() d'un Runnable, ne retourne rien,


public interface Callable<V> {
    public V call() throws Exception;
}

ExecutorService execute = Executors.newSingleThreadExecutor();  
Future<Integer> future = execute.submit(new MonCallable());

future.get(); // bloquant, attend que le Callable est rendu son résultat.
                        

Immutabilité

un des grosses difficultés de la concurrence est la gestion des changements d'états des données partagées. Si ces données deviennent immutable, les problèmes disparaissent, non ?

Some rest server

JAX RS & Jersey

Une API standardisée pour créer des services REST.

Jersey : l'implementation de référence de JAX RS.