Un Runnable “fonctionnel” sans classe anonyme

Run Forest, run!

En attendant l’arrivée des closures en Java, la solution la plus proche consiste à utiliser le sucre syntaxique que constituent les classes anonymes.

L’utilisation la plus courante est celle des Runnables, bien pratiques pour exécuter du code dans un nouveau thread :


1
2
3
4
5
6
7
8
9
public static void main(String[] args) {
    Runnable r1 = new Runnable() {
        @Override
        public void run() {
            System.out.println("Hello World !");
        }
    };
    new Thread(r1).start();
}

Je viens de découvrir une technique pour utiliser une syntaxe encore plus courte :


1
2
3
4
5
public static void main(String[] args) {
    class F {{ System.out.println("Hello Excilys !");}}
    Runnable r2 = new Runner(F.class);
    new Thread(r2).start();
}

WTF?!

Intéressons nous tout d’abord à la classe Runner :


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class Runner implements Runnable {
    private final Class<?> classToRun;

    public Runner(Class<?> classToRun) {
        this.classToRun = classToRun;
    }

    @Override
    public void run() {
        try {
            classToRun.newInstance();
        } catch (InstantiationException e) {
            throw new RuntimeException(e);
        } catch (IllegalAccessException e) {
            throw new RuntimeException(e);
        }
    }
}

Chaque fois que la méthode run() est appelée, Runner créé une nouvelle instance de la classe qui lui a été donnée en paramètre de constructeur.
Du coup, la partie suivante est facile à interpréter :


1
2
Runnable r2 = new Runner(F.class);
new Thread(r2).start();

La classe F est instanciée dans le thread nouvellement créé.
Quid de class F {{ System.out.println("Hello Excilys !");}} ? Ce sera peut-être plus clair en formatant le code :


1
2
3
4
5
class F {
    {
        System.out.println("Hello Excilys !");
    }
}

Il s’agit bien d’un bloc d’initialisation, appelé à la construction de la classe F. Nous avons ici déclaré la classe F directement dans le corps de la méthode.

Je ne sais pas si l’on peut vraiment qualifier cette approche de “fonctionnelle”, mais elle a le mérite de permettre une syntaxe plus concise qu’une classe anonyme. Notez que je n’ai rien inventé : c’est un message sur la manière de déclarer des modules Guice qui m’a fait découvrir cette technique.

VN:R_U [1.9.22_1171]
Rating: +1 (from 1 vote)
Share

À propos de Pierre-Yves Ricau

Découvrez mon cv dynamique en ligne !
Ce contenu a été publié dans Non classé. Vous pouvez le mettre en favoris avec ce permalien.

11 réponses à Un Runnable “fonctionnel” sans classe anonyme

  1. Après réflexion, ya même moyen de faire encore plus court :

    
    
    1
    2
    class F {{System.out.println("Hello Excilys !");}}
    new Thread(R.o(F.class)).start();

    Comment ? En modifiant légèrement la classe Runner :

    
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    public class R implements Runnable {
        private final Class<?> classToRun;

        private R(Class<?> classToRun) {
            this.classToRun = classToRun;
        }
       
        public static Runnable o(Class<?> classToRun) {
            return new R(classToRun);
        }

        @Override
        public void run() {
            try {
                classToRun.newInstance();
            } catch (InstantiationException e) {
                throw new RuntimeException(e);
            } catch (IllegalAccessException e) {
                throw new RuntimeException(e);
            }
        }
    }
    VN:R_U [1.9.22_1171]
    Rating: 0 (from 0 votes)
  2. Sympa comme code :D

    J’avais déjà utilisé la technique des double-brace pour initialiser une Map par exemple :

    1
    2
    3
    4
    Map<String, String> plop = new HashMap<String, String>() {{
      put("Excilys", "rocks");
      put("Piwaï", "rules");
    }};
    VN:R_U [1.9.22_1171]
    Rating: +1 (from 1 vote)
  3. Vous voulez du vraiment vicieux ?

    
    
    1
    2
    3
    4
    5
    6
    7
    8
    public static void main(String[] args) {
        List<String> names = Arrays.asList("Excilys","Bastien");
       
        class Hello extends F<String, String> {{r = "Hello "+ p;}};
        List<String> results = F.transform(names, Hello.class);
       
        System.out.println(results);
    }

    En gros, je viens de définir à la volée une fonction qui transforme une liste en une autre. La fonction de transformation à appliquer est la classe Hello, qui retourne en résultat “Hello” plus le paramètre.

    Comment ça marche ? Voici le code de la classe F.

    
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    public abstract class F<PARAM, RESULT> {
        private static ThreadLocal<Object> pHolder = new ThreadLocal<Object>();
       
        public static <PARAM, RESULT> List<RESULT> transform(List<PARAM> list, Class<? extends F<PARAM, RESULT>> functionClass) {
            List<RESULT> resultList = new ArrayList<RESULT>();
            for(PARAM p : list) {
                pHolder.set(p);
                F<PARAM, RESULT> fInstance;
                try {
                    fInstance = functionClass.newInstance();
                } catch (InstantiationException e) {
                    throw new RuntimeException(e);
                } catch (IllegalAccessException e) {
                    throw new RuntimeException(e);
                }
                resultList.add(fInstance.r);
            }
            return resultList;
        }
       
        protected PARAM p;

        protected RESULT r;
       
        @SuppressWarnings("unchecked")
        F() {
            p = (PARAM) pHolder.get();
        }
    }

    Oui, oui, c’est bien ça, un ThreadLocal ! C’est goret, mais ça marche et c’est thread safe ;-)

    VN:R_U [1.9.22_1171]
    Rating: 0 (from 0 votes)
    • cledru@excilys.com dit :

      Si je me souviens bien de ma SCJP, les blocs d’initialisation sont exécutés AVANT le constructeur.
      Donc p n’est pas encore initialisé au moment où ta “fonction” est exécutée.

      Mais sinon la technique est intéressante.

      JMock s’en sert pour simplifier l’écriture des expectations:

      1
      2
      3
      context.checking(new Expectations() {{
          oneOf (subscriber).receive(message);
      }});
      VN:R_U [1.9.22_1171]
      Rating: 0 (from 0 votes)
  4. Fun, mais horrible… :)

    Au passage, est-ce que quelqu’un connait une autre technique que celle ci-dessous pour éviter que le formatage eclipse n’introduise des retours à la ligne sur tous les double braces (sinon, cette technique perd de son intérêt…)?

    1
    2
    3
    // @formatter:off
    ...
    // @formatter:on
    VN:R_U [1.9.22_1171]
    Rating: 0 (from 0 votes)
  5. @cledru oui, mais non ;-) ! Les blocs d’init sont appelés avant le constructeur, mais après l’appel à super. Donc ça marche :-)

    @Stéphane : ne pas formater ? Bien configurer son formateur ? Ecrire un plugin pour le formatage Eclipse ?

    VN:R_U [1.9.22_1171]
    Rating: 0 (from 0 votes)
    • Cyril Ledru dit :

      Effectivement, la SCJP est loin, le constructeur du parent est appelé avant le bloc fils :).

      Par contre, si la déclaration de ta classe-fonction n’est pas dans une méthode static ( au hasard, dans une méthode d’un test junit), tu te prends une InstantiationException dont la cause n’est pas accessible (c’est ce qui m’a fait croire à une erreur dans l’ordre d’appel..).

      Après un peu d’investigation, lorsque tout se fait dans une static method, la classe-fonction est une static nested class alors que si tu la déclares dans une méthode d’instance, tu te retrouves avec une local nested class.

      La grosse différence est que la local nested est complètement dépendante de l’instance qui l’a créée et son declaredConstructor prend en paramètre cette même instance.

      Du coup newInstance() n’est pas capable de trouver de constructeur sans arguments.

      Moralité, PY, utilise JUnit pour tes expérimentations ! ;)

      VN:R_U [1.9.22_1171]
      Rating: 0 (from 0 votes)
  6. Ok guys! J’ai eu envie de pousser cette idée plus loin… Dites bonjour à FunkyJFunctional !!

    => https://github.com/pyricau/FunkyJFunctional

    VN:R_U [1.9.22_1171]
    Rating: 0 (from 0 votes)
    • Cyril Ledru dit :

      Cool, tu as trouvé comment contourner le problème dans FJF.
      Documente le changement d’accessibilité, ça mérite une petite explication.

      En tout cas même si c’est loin d’être une solution parfaite en matière de lisibilité, c’est toujours mieux qu’un anonymous class.
      Keep it up!

      VN:R_U [1.9.22_1171]
      Rating: 0 (from 0 votes)
  7. @Cyril, tu as tout à fait raison et je m’en étais rendu compte en testant un peu plus ;-)

    Du coup, effectivement j’ai créé des tests unitaires : https://github.com/pyricau/FunkyJFunctional/blob/master/funkyjfunctional/src/test/java/info/piwai/funkyjfunctional/FTest.java

    Par ailleurs, le problème peut se contourner en filant “null” ou bien la vrai instance à l’appel du constructeur :
    https://github.com/pyricau/FunkyJFunctional/blob/master/funkyjfunctional/src/main/java/info/piwai/funkyjfunctional/F.java

    VN:R_U [1.9.22_1171]
    Rating: 0 (from 0 votes)

Laisser un commentaire