Java – advanced – Antoine Vernois



Java – advanced – Antoine Vernois

0 0


training-java


On Github avernois / training-java

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

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemalocation="http://maven.apache.org/POM/4.0.0 
        http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelversion>4.0.0</modelversion>
  <groupid>fr.craftinglabs.training</groupid>
  <artifactid>my-app</artifactid>
  <packaging>jar</packaging>
  <version>1.0-SNAPSHOT</version>
  <name>My awesome project</name>
</project>

Sources structure

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

ajouter des dépendances

<project>
    <!-- snip -->
    <dependencies>
        <dependency>
            <groupid>junit</groupid>
            <artifactid>junit</artifactid>
            <version>4.11</version>
            <scope>test</scope>
        </dependency>
    </dependencies>
</project>
                        

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.

<project>
    <!-- snip -->
  <build>
    <resources>
      <resource>
        <directory>src/main/resources</directory>
        <filtering>true</filtering>
      </resource>
    </resources>
  </build>
</project>
                        

définir ses properties

${my.properties}

<project>
    <properties>
        <my.properties>hello</my.properties>
    </properties>
</project>
                        

multi modules

 <project>
  <modelversion>4.0.0</modelversion>
 
  <groupid>fr.craftinglabs.training</groupid>
  <artifactid>app</artifactid>
  <version>1.0-SNAPSHOT</version>
  <packaging>pom</packaging>
 
  <modules>
    <module>my-app</module>
    <module>my-webapp</module>
  </modules>
</project>
                        

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

  <parent>
    <groupid>fr.craftinglabs.training</groupid>
    <artifactid>app</artifactid>
    <version>1.0-SNAPSHOT</version>
  </parent>
                        

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.

le CL regarde si la classe est déjà chargée si non, il demande à son parent de le faire 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 :

La personne 1 boit une boisson alcoolisée. La personne 2 à moins de 18 ans. La personne 3 à plus de 18 ans. 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.

Java advanced Antoine Vernois / @avernois