Java 7 : Amélioration de la boucle for-each, deuxième édition

J'en avais déjà parlé, Stephen Colebourne avait fait une première proposition concernant la boucle for-each en Java

Cette amélioration concernait l'utilisation de cette boucle avec des objets de type Map.

Un rapide exemple de code pour mémoire :
Map<String,Object> objects;
// [...]
for (String key, Object o : objects) {
    System.out.println(k + "=" + o);
}

Stephen reviens aujourd'hui sur sa proposition, avec une nouvelle fonctionnalité : Le contrôle d'accès sur une boucle de type for-each.

Cette nouvelle fonctionnalité est basée sur des inconvénients de cette boucle, entre autres :
  • L'impossibilité d'accéder à l'index de l'objet actuellement parcouru
  • L'impossibilité de supprimer l'objet courant

Dans les deux cas, des solutions étaient envisageables. Notamment passer par une boucle classique, en utilisant un Iterator. Mais on perdait tout l'intérêt de cette nouvelle boucle.

La solution proposée est plutôt simple. Il s'agirait de permettre au développeur d'accéder à certaines fonctionnalités de l'Iterator qui parcourt la Collection. La syntaxe proposée est la suivante :
Collection<String> coll = new ArrayList<String>();

for (String str : coll : it) {
    System.out.println("coll[" + it.index() + "] = " + str);
}

Un autre exemple :
List<String> list = new ArrayList<String>();

for (String str : list : it) {
    if (str == null || it.isFirst()) {
        it.remove();
    }
}

La nouvelle syntaxe introduirait donc une nouvelle variable (optionnelle, pour des raisons de comptabilité), qui permettrait d'avoir plus de contrôle sur la boucle.

Voici un premier aperçu des méthodes qui devraient être fournies à travers cette variable (tirées de la proposition originale) :
public interface IterableControl<E> {
    E set(E newValue);
    E remove();
    int index();
    int originalIndex();
    boolean isFirst();
    boolean isLast();
    int size();
}

Une rapide description de chacune de ces fonctions :
  • set() : Change la valeur de l'élément courant. Cette opération ne serait pas supportée pour les itérations sur les types Iterable, Collection et Set.
  • remove() : Supprime l'élément courant. Cette opération ne serait pas supportée sur les tableaux.
  • index() : Renvoie l'index de l'élément courant.
  • originalIndex() : Renvoie l'index original de l'élément courant. "Original" signifie "Sans ajustements lors des suppressions d'éléments".
  • isFirst() : Renvoie true si et seulement si l'élément courant est le premier élément de la boucle.
  • isLast() : Renvoie true si et seulement si l'élément courant est le dernier élément de la boucle.
  • size() : Renvoie la taille courante de la collection ou du tableau parcouru.

La question actuellement posée dans la proposition est "Est-ce qu'il faut passer par une interface spécifique, ou juste appeler les méthodes correspondantes directement dans la boucle ?".

Pour plus d'informations :

Permalink  |  Commentaires (0)

Java 7 : Meilleure gestion des exceptions

Une nouvelle proposition pour Java 7 est à l'ordre du jour : l'amélioration de la gestion des exceptions.

Cette amélioration consisterait en deux parties :
  • Une amélioration pour relever les exception attrapées.
  • La possibilité d'attraper plusieurs types d'exceptions dans un seul bloc catch

Relever une exception déjà attrapée

Lors de la rédaction de libraires en Java, il arrive couramment d'avoir à attraper une exception, d'effectuer certains traitements, certains enregistrements dans des fichiers de log, puis de vouloir la relancer derrière. Un petit exemple (inspiré de la proposition originale) :
try {
    throw new MyException();
} catch (Throwable t) {
    // Log the exception
    throw t;
}

Un tel code est entièrement valide, mais contraignant. En effet, essayons de l'encapsuler dans une méthode :
public void doSomeStuff() throws Throwable {
    try {
        throw new MyException();
    } catch (Throwable t) {
        // Log the exception
        throw t;
    }
}

J'ai été obligé de rajouter un magnifique throws Throwable. Pourquoi ? Tout simplement parce que la ligne throw t; lève une exception, de type Throwable. Le compilateur impose donc de déclarer Throwable dans le prototype de la méthode. Et ce, même si le bloc try ne lève que des exceptions de type MyException, dans notre exemple.

Des techniques pour palier à ce problème sont assez répandues, comme par exemple l'encapsulation de l'exception levée dans une RuntimeException. Mais on perd tout le bénéfice des exceptions vérifiées.

L'amélioration dans ce cas là consisterais à pouvoir attraper les exceptions en les marquant final. L'exception marquée final ne pouvant changer, le compilateur est certain que la ligne throw t; ne lèvera qu'une exception levée dans le bloc try. Dans notre cas, une exception de type MyException. L'exception MyException serait donc la seule à déclarer. Et ce code deviendrait complètement valide :
public void doSomeStuff() throws MyException {
    try {
        throw new MyException();
    } catch (final Throwable t) {
        // Log the exception
        throw t;
    }
}

Attraper plusieurs types d'exceptions dans un bloc catch

Un simple petit exemple devrait clarifier la situation sur cette nouvelle fonctionnalité :
try {
    klass.newInstance();
} catch (InstantiationException ex) {
    ex.printStackTrace(System.err);
} catch (IllegalAccessException ex) {
    ex.printStackTrace(System.err);
}

Pour récupérer deux exceptions (InstantiationException et IllegalAccessException), deux blocs catch sont obligatoires. Même si le même traitement est effectué à l'intérieur. Dans notre cas, l'amélioration consisterais à permettre au développeur d'attraper deux types d'exceptions dans un seul bloc catch (inspiré de la proposition originale) :
try {
    klass.newInstance();
} catch (InstantiationException | IllegalAccessException ex) {
    ex.printStackTrace(System.err);
}

Dans cet exemple, le bloc catch est capable d'attraper les exception de type InstantiationException et celles de type IllegalAccessException.

Une autre syntaxe a été proposée, cette fois-ci basée sur les generics :
try {
    // Some code...
} <T extends MyException> catch (T t) {
    // Catch here...
}

Une évolution plus radicale

Une autre évolution concernant les exceptions est débattue par les utilisateurs de Java ici et .

Le débat tourne autour de la justification des exceptions vérifiées en Java. Est-ce que les exceptions vérifiées sont utiles, où alors devraient elles être toutes remplacées par des exceptions non vérifiées ?

Pour rappel, une exception vérifiée est une exception qui doit obligatoirement être soit déclarée (via throws), soit être attrapée (via catch).

Je dois avouer que je suis plutôt du coté des "défenseurs" des exceptions vérifiées, même si elles sont assez contraignantes à mettre en place dans certaines situation, notamment pour l'introduction des closures.

Permalink  |  Commentaires (0)

Java 7 : Chained invocation

Après l'amélioration de la boucle for-each et les chaînes de caractères multi-lignes, je continue aujourd'hui ma série de billets autour de Java 7.

Au sujet du jour, les invocation chaînées. Derrière un nom barbare se cache un principe simple : pouvoir enchaîner les invocations de méthodes sans devoir rappeler le nom de l'objet à chaque invocation. Un petit exemple :
List list = new ArrayList();
list.add(0, "a");
list.add(1, "b");
list.add(2, "c");
list.add(3, "d");

Pourrait devenir :
List list = new ArrayList().add(0, "a")
            .add(1, "b")
            .add(2, "c")
            .add(3, "d");

Cette amélioration se base sur une proposition simple : Faire que chaque méthode qui a pour type de retour void renvoie implicitement une référence vers l'objet appelé (this). Un peu comme si toutes les méthodes ayant void pour type de retour étaient récrites :
public SomeClass {
    public void doSomeStuff() {
        // Some stuff
    }
}

Pourrait être considérée comme :
public SomeClass {
    public Class doSomeStuff() {
        // Some stuff
        return (this);
    }
}

Les avantages d'une telle fonctionnalité sont présentés un peu partout, dans différents cas d'utilisations, et avec différentes syntaxes.

Un premier exemple, en Swing (par Matthias Ernst) :
JFrame frame = new JFrame().setTitle("Frame").setBounds(100, 100, 100, 100).setVivible(true);

Un deuxième, avec des JavaBeans (par Stefan Schulz) :
Person person = new Person()  
        .setName("Costanza")  
        .setFirstName("George")  
        .setGender(Gender.MALE)  
        .setAge(35);
Et le même exemple, avec une syntaxe particulière, basée sur le do :
Person person = new Person() do {
    setName("Costanza");
    setFirstName("George");
    setGender(Gender.MALE);
    setAge(35);
};
Cette syntaxe permettrait d'ajouter de la logique dans les invocations chaînées :
String str = new StringBuilder() do {  
    append(person.getName());  
    if (person.isMarried()) {  
        append("(married)");  
    }  
}.toString(); 
Je dois avouer que j'ai un petit faible pour cette solution :)

Et un dernier, avec des collections, présentant une nouvelle syntaxe spécifique pour les invocations chaînées (par Dr. Howard Lovatt) :
list -> { add( 1, "A" ); add( 2, "B" ); };

Liens bonus :

Permalink  |  Commentaires (0)

Java 7 : Chaînes de caractères multilignes

Ça y est, c'est décidé, je me lance. Je vais enchaîner une petite série d'entrées qui auront pour sujet Java 7.

Mon objectif est assez simple : Rassembler le plus possible d'informations sur les fonctionnalités et améliorations proposées pour la version 7 de Java, les développer, et discuter des aspects techniques de celles-ci.

J'ai trouvé pas mal d'informations chez Alex Miller. Il semblerais qu'une petite synthèse aie déjà été faite. Je vais donc essayer de développer un peu plus toutes ces fonctionnalités.

La première amélioration que j'aimerais traiter aujourd'hui est l'apparition des chaînes de caractères multi-lignes.

L'idée originale est de Stephen Colebourne. Comme il l'a justifié sur son blog, beaucoup de langages disposent de cette fonctionnalité, et il serait intéressant de l'intégrer à Java.

Même si la fonctionnalité en elle même fait plus ou moins l'unanimité (ou pas), la question de la syntaxe reste encore ouverte.

La syntaxe originalement proposée par Stephen sur son blog est inspirée de Scala et de Groovy. Il s'agirait de tripler le caractère " ouvrant et fermant les chaînes de caractères. On se rapprocherait donc d'une syntaxe comme celle-ci :
String s = """This string
is a multi-line
String""";

L'avantage de cette syntaxe serait de permettre aussi d'éviter l'échappement des caractères ". Un rapide exemple :
String s = """Hello world, my name is "Vivien".""";

Stephen évoque également un problème au niveau de cette syntaxe, particulièrement vis-à-vis de l'indentation du code. Tous les caractères étant inclus, il faudrait supprimer les caractères d'indentation pour ne pas les retrouver dans la chaîne de caractères produite :
public class StringMaker {
    public static String getString() {
        return (""" This
is
my
String""");
    }
}
On y perd donc toute notion de code "bien indenté".

Une des solutions apportées consisterait en une nouvelle méthode de la classe String, qui permettrait d'appliquer un trim() sur chaque ligne de la chaîne de caractères. Avec un code final ressemblant à celui-ci :
public class StringMaker {
    public static String getString() {
        return (""" This
                is
                my
                String""".trimLines());
    }
}
Le problème vient ici de la gestion des performances. Appliquer un trim sur chaque ligne n'est pas la meilleure solution en terme de temps CPU, et les applications risquent de devenir très lourdes.

Plusieurs solutions ont été proposées dans les commentaires, mélangeant ", """ et autres <<" avant les chaînes ou après les chaînes, avec à chaque fois des significations différentes

Personnellement, je pense qu'une telle fonctionnalité mérite d'être considérée, mais je ne pense pas que les chaînes de caractères multi-lignes soient suffisamment importantes pour bénéficier d'une modification directe du langage. À mon humble avis, une méthode supplémentaire dans la classe String serait suffisante :
public static String toMultiLine(String... lines) {
    StringBuffer multiLine = new StringBuffer();
    boolean firstLine = true;
    for (String line : lines) {
        if (!firstLine)
            multiLine.append(System.getProperty("line.separator"));
        else
            firstLine = false;
        multiLine.append(line);
    }
    return (multiLine.toString());
}

Il suffirait ensuite d'appeler cette méthode pour former une nouvelle chaîne de caractères multi-lignes :
String.toMultiLine("This",
        "is",
        "my",
        "line");

Cela permettrait de gérer les chaînes de caractères avec des espaces avant et/ou après la ligne à ajouter, résoudrait les problèmes d'indentation, et tout ça, sans modifier la syntaxe actuelle du langage.

Une autre solution, à mon avis, serait d'utiliser un système à la PHP (système Heredoc). Ce système à l'avantage de laisser le choix au développeur des balises à employer pour délimiter sa chaîne de caractères multi-lignes. Et donc d'éviter au développeur l'échappement des caractères délimitant les chaînes. J'immagine donc bien un système ainsi formé :
String s = <<<DELIM
This
is
my
string
DELIM;

Permalink  |  Commentaires (0)

Java 7 : Amélioration de la boucle for-each

Stephen Colebourne viens de publier une proposition concernant une amélioration de la boucle for-each en Java.

Petit historique :
La boucle for-each à été introduite en Java 5. Cette nouvelle boucle permettait (et permet toujours) d'itérer très facilement sur un tableau, une Collection ou un Iterator.
Collection<String> strings;
[...]
for (String s : strings) {
    System.out.println(s);
}

L'amélioration de celle-ci devrait permettre d'itérer sur une Map de la même manière que sur une Collection ou un tableau.

La nouvelle syntaxe qui permettrait ceci devrait ressembler à ça :
Map<String,Object> objects;
[...]
for (String key, Object o : objects) {
    System.out.println(k + "=" + o);
}

D'où une grande simplification dans le parcours des Map.

En plus de ça, Stephen ne s'est pas arrêté là. Il a déjà mis à disposition une version de javac modifiée, basée sur Kijaro, permettant d'utiliser cette nouvelle boucle.

Permalink  |  Commentaires (1)