:: Enseignements :: Master :: M1 :: 2015-2016 :: Java Avancé ::
[LOGO]

Examen de Java Avance sur table


Exercice 1 - Questions de cours (6)

Répondez aux questions suivantes en deux ou trois phrases, pas plus.

  1. Pourquoi il n'est pas possible en Java d'avoir des tableaux de types paramétrés ?
  2. Qu'affiche le code suivant ?
          Integer i = 300;
          Integer j = 300;
          System.out.println(i == j);
         
    Attention il y a un piègle !
  3. Qu'affiche le code ci-dessous ? Pourquoi ?
         public static void main(String[] args) {
           String s = "hello";
           s.toUpperCase();
           System.out.println(s);
         }
         
    Attention il y a un piège !
  4. Le code suivant compile-t-il ? Pourquoi ?
    Si le code compile, qu'affiche-t-il ? Pourquoi ?
    interface Main {
      class A {
        void m() { System.out.println("1"); }
      }
      class B extends A {
        public void m() { System.out.println("2"); }
      }
      public static void main(String[] args) {
        A a = new B();
        a.m();
      }
    }
         
  5. Dans le code précédent, si la méthode m de la classe A est déclarée private, le code compile-t-il ? Pourquoi ? Si le code compile, qu'affiche-t-il ? Pourquoi ?
  6. Dans le code ci-dessous, la méthode f de A est gentiment récursif, sans test d'arrêt. En fait, ce que l'on veut, c'est que la méthode f de A rappelle la méthode f de I. Comment faire cela en Java ?
    interface I {
      default int f() {
        return ...;  // on suppose qu'il y a une implantation
      }
    }
    class A implements I {
      int f() {
        return f() + 1;
      }
    }
         

Exercice 2 - Exceptionnellement (4)

Vendredi dernier, après un pot un peu trop arrosé pour le départ de l'un de vos collègues, vous avez écrit le code suivant:
public class Locker {
  private ReentrantLock lock = new ReentrantLock();
  
  public R doLocked(Supplier<R> supplier) {
    R rValue;
    lock.lock();
    rValue = supplier.get();
    lock.unlock();
    return rValue;
  }
 }
 
 public class Main {
  public static void main(String[] args) {
    Locker locker = new Locker();
    String message = locker.doLocked(() -> {
      return "hello";
    });
    System.out.println(message);
  }
}
    
Le code de Locker était censé garantir que le traitement passé en paramètre de doLocked ne pouvait être fait que par une seule thread à la fois (pour un même objet Locker)... Mais le lundi, en redécouvrant le code de la classe Locker, vous vous promettez de respecter l'adage, "boire ou coder, il faut choisir !"

  1. La signature de la méthode doLocked est fausse, il y a deux problèmes dans la façon dont la méthode paramétrée est déclarée. Indiquez quelle est la bonne signature.
  2. Le code de la classe Locker a deux bugs de concurrence. Corrigez-les en indiquant le nouveau code.
  3. Sachant que Files.getLastModifiedTime() peut lever l'exception IOException, expliquez pourquoi le code ci-dessous ne compile pas.
      public static FileTime lastTime(Locker locker, Path path) throws IOException {
        return locker.doLocked(() -> {
          return Files.getLastModifiedTime(path);
        });
      }
         
  4. Modifiez le code de lastTime pour qu'il compile (sans modifier le code de Locker) et, bien sûr, qu'il marche correctement.

Exercice 3 - Une Liste, ça crée des liens (10)

On cherche à écrire une classe Link représentant une liste chaînée, avec quelques opérations sympathiques.
Il y a plusieurs représentations possibles des listes chaînées. Dans cet exercice, la liste chaînée sera manipulée par son premier maillon, donc Link représente à la fois la liste chaînée et un maillon.
La structure de données sera donc équivalente à cette déclaration Java :
class Link {
  Object element;  // élément du maillon
  Link next;       // référence sur l’élément suivant
}   
   

Le champs next contient null s'il s'agit du dernier maillon.
De plus, la liste possède une représentation textuelle, les éléments sont affichés séparés par le signe " -> ". Par exemple, une liste ayant deux éléments element1 et element2 sera représentée comme ceci:
     element1 -> élement2
   

  1. Donnez le code de la classe Link sachant que l'on veut que la liste soit typée par le type d’élément qu'elle peut contenir (une Link<String> si l'on stocke des chaînes de caractères par exemple), avec ses champs et son constructeur en faisant attention aux modificateurs de visibilités.
  2. Donnez le code qui permet de créer la liste chaînée suivante:
         baz -> bar -> foo
        
    Attention à l'ordre des éléments !
  3. En fait, on ne souhaite pas permettre à l'utilisateur de créer des maillons en accédant directement au constructeur. Nous allons ajouter deux méthodes à la classe Link: of et prepend qui permettent respectivement de créer une liste chaînée avec un seul maillon et d'ajouter un maillon avant la liste chaînée.
    Par exemple, la liste chaînée précédente peut être créée avec le code suivant, si l'on utilise of et prepend.
         Link<String> l = Link.of("foo").prepend("bar").prepend("baz");
        

    On peut noter que l'appel à la méthode of se fait sur la classe.
    Indiquez le code des méthodes of et prepend.
  4. Écrivez la méthode toString.
    (en récursif, c'est plus simple !)
  5. On cherche à écrire une méthode forEach qui prend en paramètre une fonction qui sera appelée pour chaque élément.
    Indiquez la signature de la méthode forEach (attention au sous-typage !).
    Proposez une implantation récursive de la méthode forEach.
    Proposez ensuite une implantation itérative de la méthode forEach.
  6. Écrivez une méthode iterator qui renvoie un Iterator permettant de parcourir les éléments de la liste chaînée.
    Attention à bien vérifier que les contrats des méthodes de l'Iterator sont bien respectés.
  7. Sachant qu'il est facile de créer un Stream à partir d'un itérateur, avec le code ci-dessous,
    public Stream<E> stream() {
      return StreamSupport.stream(
        Spliterators.spliteratorUnknownSize(iterator(),
           Spliterator.IMMUTABLE|Spliterator.NONNULL), false);
    }    
        

    proposez une nouvelle implantation de la méthode toString utilisant la méthode stream().
  8. Écrivez une méthode map qui prend en paramètre une fonction et renvoie une nouvelle liste chaînée en appliquant cette fonction à chaque élément de la liste.
    Voici un exemple d'utilisation
    Link<String> l = Link.of("foo").prepend("bar").prepend("baz");
    Link<Integer> l2 = l.map(String::length);
    System.err.println(l2);  // 3 -> 3 -> 3 
       
    Attention aux sous-typages des types paramétrés.
  9. Écrivez une méthode filter qui prend en paramètre une fonction et renvoie une nouvelle liste chaînée contenant les éléments de la liste initiale pour lesquels l'appel de cette fonction renvoie vrai.
    Voici un exemple d'utilisation:
    Link<String> l3 = l.filter(e -> e.length() >= 2 && e.charAt(1) == 'a');
    System.err.println(l3);  // baz -> bar 
       
  10. Écrivez une méthode reduce qui prend en paramètre une valeur initiale pour la valeur accumulée et une fonction à deux paramètres (valeur accumulée et élément courant) et qui applique la fonction à tous les éléments et renvoie la valeur accumulée.
    Puis utilisez la méthode reduce pour écrire une méthode reverse sur une liste qui renverse l'ordre de ses éléments.
    Exemple d'utilisation de la méthode reverse:
    Link<String> l4 = l.reverse();
    System.err.println(l4);  // foo -> bar -> baz