Astuce Java #6 : Enum et toString

Depuis Java 5, le langage Java dispose d'une nouvelle fonctionnalité : les énumérations.

Les énumérations sont des structures permettant de représenter une variable ayant un nombre fini de valeurs, comme par exemple le sexe d'une personne, qui sera soit "homme", soit "femme".

Les énumérations sont mises en place de la manière suivante :
enum Sex {
    MALE,
    FEMALE;
}

Il est ensuite possible de créer des variables du type de l'énumération, et de les utiliser :
Sex sex = Sex.MALE;
System.out.println(sex);

Et c'est là qu'apparaît notre problème : la console nous affiche "MALE". Certes, il s'agit de la bonne valeur, mais la présentation n'est pas très esthétique. Heureusement pour nous, en Java une énumération est également un objet. Il nous est possible de définir nos propres méthodes, et aussi de récrire certaines méthodes de la classe Object. Ainsi, on peut avoir le code suivant :
enum Sex {
    MALE,
    FEMALE;
    
    @Override
    public String toString() {
        switch (this) {
            case MALE:
                return ("Male");
            case FEMALE:
                return ("Female");
        }
        throw new RuntimeException("Invalid value for this");
    }
}

L'exécution du même code nous donne ainsi "Male" au lieu de "MALE". On obtient le résultat souhaité, à savoir une chaîne de caractères plus facilement lisible par l'utilisateur.

Le soucis que l'on rencontre maintenant viens du switch dans la méthode toString. Nous devons gérer toutes les valeurs possible dans cette méthode, sous peine de lancer une RuntimeException. Une des solutions possibles (que j'ai trouvé dans un commentaire d'un (très) vieux billet de blog), et que je trouve de loin ma préférée, est de fournir un constructeur à notre énumération. Ce constructeur prend en paramètre une chaîne de caractères qui sera utilisée en tant que valeur de retour de la méthode toString :
enum Sex {
    MALE("Male"),
    FEMALE("Female");
    
    private String value;
    
    private Sex(String value) {
        this.value = value;
    }
    
    @Override
    public String toString() {
        return (value);
    }
}

Ainsi, nous obtenons on comportement optimal :
  • Chaque valeur de l'énumération renverra une chaîne de caractères "esthétique" représentant sa valeur
  • Chaque valeur devra posséder une chaîne de caractères (au risque de générer une erreur de compilation)

Le problème majeur qui est ainsi rencontré concerne la conversion énumération -> chaîne de caractères -> énumération. La chaîne de caractères renvoyait de base par toString était le nom de la constante dans l'énumération (MALE ou FEMALE dans notre cas), qui pouvait ensuite être reconverti en énumération via la méthode valueOf. Dans notre cas, la conversion ne se ferra plus puisque toString ne renverra plus une valeur compréhensible par valueOf. Cependant, la méthode name, définie dans toute énumération, renvoie toujours le nom de la constante tel que définit dans le code.

Permalink  |  Commentaires (3)

Astuce Java #5 : for-each sur une map

Petite astuce pour iterer sur une Map dans une boucle for-each, en attendant Java 7.

Il s'agit de considérer une Map comme une collection de Map.Entry :
Map<Integer, String> myMap = new HashMap<Integer, String>();
// ...
for (Map.Entry entry : myMap.entrySet()) {
    System.out.println(entry.getKey() + "=" + entry.getValue());
}


Via A better way to iterate java maps.

Permalink  |  Commentaires (0)

Astuce Java #4 : Swing et le System Look and Feel

Tu pourrais me faire vite fait un jar qui affiche une message box stp ?
Matt

Aussitôt dit aussitôt fait. Mais une fois terminé, une chose m'est passé par la tête.
Existe-t-il un moyen de dire à Swing de choisir automatiquement le bon Look and Feel en fonction du système d'exploitation ?
Mon esprit torturé

GTK Message Box
Et me voilà parti à la recherche de cette fonctionnalité. La réponse a été assez rapide. Il suffit de demander au gestionnaire graphique de Swing de nous récupérer le Look and Feel du système, et ensuite de lui dire de l'appliquer. Un petit exemple de code :
package mattbox;

import javax.swing.JOptionPane;
import javax.swing.UIManager;

public class Main {
    
    public static void main(String[] args) {
        try {
            String laf = UIManager.getSystemLookAndFeelClassName();
            UIManager.setLookAndFeel(laf);
        } catch (Exception ex) {
            ex.printStackTrace();
        }
        JOptionPane.showMessageDialog(null, "Hello Matt !");
    }

}

Et voilà, notre belle message box est devenue bien intégrée au système.
OS X Message BoxAero Message Box

Merci à Matt pour l'image de la messagebox sous Vista, et à Colin pour celle sous OS X :)

Pièces jointes en bonus :

Permalink  |  Commentaires (0)

Astuce Java #3 : ...

Derrière le nom bizarre de cet article se cache une vraie astuce en Java, concernant la déclaration des méthodes. Penchons nous sur le C. On retrouve en C une utilisation du ... similaire à celle que nous allons retrouver en Java. Regardons en particulier le prototype de la méthode printf :
int printf (__const char *__restrict __format, ...);

Si l'on analyse ce prototype, on peut constater que printf prends un premier argument, de type char*, appelé __format. Ensuite, nous retrouvons notre fameux ..., qui signifie littéralement : "Passez moi d'autres paramètres, autant que vous en voulez, je m'occuperais de les traiter". Notamment dans le cas de printf, ou nous allons lui passer une liste des valeurs à afficher sur la sortie standard.

Penchons nous maintenant sur l'équivalent de printf, mais en Java cette fois-ci. J'ai nommé la méthode PrintWriter.printf(String, Object...).

Le prototype de cette méthode ressemble à ça :
public PrintWriter printf(String format, Object... args);

De même qu'en C, nous avons une méthode qui prends un format à imprimer, et une liste d'arguments à remplacer dans le format. Un nombre non connu d'arguments, qui sont ici de type Object.

Maintenant, voyons comment nous allons pouvoir créer nos propres méthodes en utilisant ce fameux .... Nous allons essayer de créer une méthode qui prends un nombre inconnu de chaînes de caractères, et les affiche sur la sortie standard.

Première chose à faire, déclarer notre méthode :
public void printStrings(String... arguments) {

}

Dans la méthode ainsi déclarée, nous prenons un nombre inconnu de chaînes de caractères (String...), qui seront stockés dans la variable arguments. Maintenant, comment récupérer le contenu de arguments ? Quel est son type ? Demandons à Java de nous le dire ;)
public void printStrings(String... arguments) {
    System.out.println(arguments.getClass().getName());
}

L'exécution de ce morceau de code nous donne :
[Ljava.lang.String;

Nous avons donc une variable arguments de type String[]. Il ne nous reste donc plus qu'à itérer sur ce tableau :
public void printStrings(String... arguments) {
    for (String argument : arguments) {
        System.out.println(argument);
    }
}

Et voilà, nous n'avons plus qu'à appeler notre méthode :
printStrings("toto", "tata", "titi");

Ce qui nous produira la sortie suivante :
toto
tata
titi

Dernière remarque, le ... ne peut être utilisé que sur le dernier argument d'une méthode !

PS : Cette entrée était la douzième. Les intéressés se reconnaîtrons ;)

Permalink  |  Commentaires (0)

Astuce Java #2 : abstract et final vs interface et enum

Nouvelle question existentielle (je commence à croire que les questions nous viennent au rythme de une par jour en ce moment...).

Ce fois ci : Quelles sont les combinaisons possible en mélangeant les mots clés abstract et final avec les mots clés interface et enum ?

On pourrait résumer l'intégralité de cette question de cette manière :
Lesquelles de ces différentes combinaisons de mots clés sont autorisées pour déclarer un type ?
abstract final
interface ? ?
enum ? ?

Voici ce à quoi on aurait pu s'attendre :
abstract final
interface Illégal Légal
enum Légal Illégal

Je m'explique :
  • Une interface est par définition abstraite, abstract serait donc bien absurde dans ce cas.
  • Une interface pourrait être finale comme les classes le sont pour interdire l'héritage. On pourrait donc continuer à implémenter une interface, mais on ne pourrait pas en hériter (pour rappel, une interface hérite d'une autre).
  • Une énumération peut contenir des méthodes. Donc pourquoi pas des méthodes abstraites ?
  • Les types "enum" se sont pas extensibles (on ne peut pas les hériter), donc un final ici serait absurde une fois de plus.

Et bien voilà, après tests, ce que l'on obtient :
abstract final
interface Légal Illégal
enum Illégal Illégal

Trois surprises :
  • Il est possible de rendre une interface abstraite. Le comportement de l'interface n'est en rien modifié. D'après la Java Language Specification (Third edition), § 9.1.1.1 :
    Every interface is implicitly abstract. This modifier is obsolete and should not be used in new programs.
    The Java Language Specification, Third Edition, § 9.1.1.1
  • Il n'est pas possible de rendre une interface finale.
  • Il n'est pas possible de mettre des méthodes abstraites dans une énumération.

Je m'attendais pas vraiment à ces résultats, je vais retourner traîner à la recherche d'une explication sur ces points (car explication il doit y avoir).

Permalink  |  Commentaires (1)