[JAVA] Arrêtez les clés de type String et introduisez l'interface de marqueur

introduction

Dans les bibliothèques Java, nous voyons souvent des chaînes d'objets de type utilisés comme des ** valeurs ** (ci-après dénommées «clés») pour identifier des objets tels que des clés et des noms de propriétés. Par exemple, la classe java.util.Properties utilisée pour introduire la liste de propriétés, la classe java.beans.PropertyChangeSupport utilisée pour envoyer des événements tels que des notifications de changement de propriété, etc. On dit qu'elle est en déclin et qu'elle est toujours comparée à JavaFx ~~ La classe java.awt.CardLayout, qui est l'un des gestionnaires de mise en page de Swing ...

Dans la méthode utilisant le type String, si la valeur contient une erreur typographique ** ou si le nom de la clé est modifié en raison d'un changement dans l'implémentation **, l'objet ne peut pas être référencé correctement et un problème se produit. Tu peux.

Exemple d'échec d'acquisition de valeur en raison d'une faute de frappe dans le nom de la clé


Map<String, String> entries = new HashMap<>() {{
  put("hoge", "Hoge");
  put("huga", "Fuga");
  put("piyo", "Piyo");
}};
System.out.println(entries.get("hoge")); //Sortie: Hoge
System.out.println(entries.get("hige")); //Sortie: null (acquisition échouée)

Exemple d'échec de l'acquisition de valeur en raison d'un changement d'implémentation


Map<String, String> entries = new HashMap<>() {{
  put("foo", "Huh");  //Modifications de la mise en œuvre
  put("huga", "Fuga");
  put("bar", "Bah");  //Modifications de la mise en œuvre
}};
System.out.println(entries.get("hoge")); //Sortie: null (acquisition échouée)
System.out.println(entries.get("huga")); //Sortie: Fuga

Un tel danger peut être éliminé en introduisant l'interface de marqueur [^ interface de marqueur]. Cette fois, je voudrais expliquer l'utilisation de la classe CardLayout introduite plus tôt à titre d'exemple.

Avant l'introduction

Ensuite, considérez l'écran présenté par le code réduit A.

<détails> <résumé> Code A </ résumé>

python


public class App {

    public static void main(String[] args) {
        new JFrame("Sample") {{
            setSize(350, 300);
            setLocationRelativeTo(null);
            setDefaultCloseOperation(EXIT_ON_CLOSE);
            setVisible(true);
            //Génération CardLayout
            CardLayout cards = new CardLayout();
            //Centre du cadre
            JLabel[] labels = {
                new JLabel("Card 1"),
                new JLabel("Card 2"),
                new JLabel("Card 3"),
                new JLabel("Card 4")
            };
            Font fnt = new Font("Meilio", Font.PLAIN, 40);
            JPanel center = new JPanel();
            center.setLayout(cards);
            for (int i = 0; i < labels.length; i++) {
                labels[i].setFont(fnt);
                labels[i].setVerticalAlignment(JLabel.CENTER);
                labels[i].setHorizontalAlignment(JLabel.CENTER);
                center.add(labels[i], "Card" + (i + 1));
            }
            //Bas du cadre
            JButton[] buttons = {
                new JButton("Card 1"),
                new JButton("Card 2"),
                new JButton("Card 3"),
                new JButton("Card 4")
            };
            JPanel south = new JPanel();
            for (int i = 0; i < buttons.length; i++) {
                int l_i = i;
                buttons[i].addActionListener(e -> cards.show(center, "Card" + (l_i + 1)));
                south.add(buttons[i]);
            }
            //Ajout d'un panneau au cadre
            add(center, BorderLayout.CENTER);
            add(south, BorderLayout.SOUTH);
        }};
    }
}

En exécutant ce code et en cliquant sur chaque bouton, vous pouvez basculer entre les quatre étiquettes comme indiqué ci-dessous.

ok.gif

Où il est dans le code A

python


buttons[i].addActionListener(e -> cards.show(center, "Card" + (l_i + 1)));

Au moment où vous confondez «Carte» avec ** «Cerd» «**, la commutation ne fonctionnera pas correctement comme suit.

ng.gif

En effet, la méthode show ne peut plus faire référence à chaque étiquette associée au panneau au centre du cadre par la méthode add qui spécifie les identificateurs des quatre étiquettes dans le deuxième argument.

python


center.add(labels[i], "Card" + (i + 1));

buttons[i].addActionListener(e -> cards.show(center, "Cerd" + (l_i + 1))); // "Card"ne pas!

Cela ne cause pas d'erreur de compilation, donc à moins que vous ne le remarquiez ou que vous ne demandiez à quelqu'un de vous le dire, ce n'est pas grave si vous ne connaissez pas la cause du problème.

introduction

Donc, nous allons introduire une interface de marqueurs.

Cela n'a pas de sens d'introduire simplement une interface de marqueur, alors préparez une énumération, définissez les clés et demandez à cette énumération d'implémenter l'interface de marqueur.

python


//Interface de marqueur
interface CardKey {}
//Énumérateur qui implémente l'interface de marqueur
enum Card implements CardKey {
    CARD1, CARD2, CARD3, CARD4;
}

De plus, étant donné que la clé ajoutée et la méthode show apparues précédemment ne peuvent pas spécifier la clé définie comme argument en l'état, définissez la classe d'utilitaire réduite suivante afin que le composant à afficher en basculant avec la clé puisse être associé. ..

<détails>

Classe utilitaire </ summary>

python


class CardLayoutUtil {
    private CardLayout cards = new CardLayout();
    private JPanel panel = new JPanel();

    private CardLayoutUtil(JPanel panel) {
        this.panel = panel;
        cards = (CardLayout) panel.getLayout();
    }
    
    /**
     *Créez une instance de cette classe en fonction du panneau qu'elle prend comme argument.
     *
     * @panneau param Panneau associé à l'instance
     * @return Instance de cette classe
     * @jette NullPointerException Si l'argument est nul
     * @jette IllegalArgumentException lorsque CardLayout n'est pas défini dans le panneau pour l'argument
     */
    static CardLayoutUtil of(JPanel panel) {
        Objects.requireNonNull(panel);
        if (!(panel.getLayout() instanceof CardLayout))
            throw new IllegalArgumentException("CardLayout n'est pas défini dans le panneau qui prend l'argument.");
        return new CardLayoutUtil(panel);
    }

    /**
     *Enregistrez le composant dans le panneau associé à l'instance.
     *
     * @param comp Composant à enregistrer
     * @clé param La clé associée au composant
     * @jette NullPointerException Si un argument est nul
     */
    void addCard(Component comp, CardKey key) {
        Objects.requireNonNull(comp);
        Objects.requireNonNull(key);
        panel.add(comp, key.toString());
    }

    /**
     *Affichez le composant associé à la clé prise comme argument.
     *
     * @clé param La clé associée au composant
     * @jette NullPointerException Si l'argument est nul
     */
    void showCard(CardKey key) {
        Objects.requireNonNull(key);
        cards.show(panel, key.toString());
    }
}

Étant donné que le gestionnaire de disposition du panneau associé à la classe utilitaire n'est pas CardLayout et que le comportement censé être nul pour les clés et les composants pris comme arguments de chaque méthode ne peut pas être réalisé, la gestion des exceptions est effectuée.

Créez un objet de cette classe d'utilitaire et préparez un tableau de chaque clé afin que vous puissiez le transformer en boucle.

python


//Création d'un objet CardLayoutUtil
CardLayoutUtil util = CardLayoutUtil.of(center);
Card[] cardKeys = Card.class.getEnumConstants();

Le reste est en code A

python


center.add(labels[i], "Card" + (i + 1));

buttons[i].addActionListener(e -> cards.show(center, "Cerd" + (l_i + 1))); // "Card"ne pas!

Partie de

python


util.addCard(labels[i], cardKeys[i]);

buttons[i].addActionListener(e -> util.showCard(cardKeys[l_i]));

Si vous le remplacez par, vous n'avez pas à vous soucier des fautes de frappe ou des changements d'implémentation qui causent les problèmes ci-dessus.

point important

Cette méthode est réalisée en utilisant la représentation sous forme de chaîne de caractères de la clé (énumérateur) obtenue par toString (). Par conséquent, ** si la même clé est associée à un autre composant et réutilisée **, ou ** un énumérateur du même nom est défini entre différents énumérateurs **, cela ne fonctionnera pas bien [^] toString].

Un exemple de définition d'un énumérateur avec le même nom entre des énumérateurs


enum CardsA implements CardKey {
    CARD1, CARD2, CARD3, CARD4;
}
enum CardsB implements CardKey {
    CARD1, CARD2, CARD3, CARD4;
}
Exemple de code qui ne fonctionne pas correctement

python


//Centre du cadre
JLabel[] labelsA = {
    new JLabel("Card 1"),
    new JLabel("Card 2"),
    new JLabel("Card 3"),
    new JLabel("Card 4")
};
JLabel[] labelsB = {
    new JLabel("Card 5"),
    new JLabel("Card 6"),
    new JLabel("Card 7"),
    new JLabel("Card 8")
};
... //réduction
//Création d'un objet CardLayoutUtil
CardLayoutUtil util = CardLayoutUtil.of(center);
CardsA[] cardKeysA = CardsA.class.getEnumConstants();
CardsB[] cardKeysB = CardsB.class.getEnumConstants();
for (int i = 0; i < labelsA.length; i++) {
    ... //réduction
    util.addCard(labelsA[i], cardKeysA[i]);
}
for (int i = 0; i < labelsB.length; i++) {
    ... //réduction
    util.addCard(labelsB[i], cardKeysB[i]);
}
//Bas du cadre
JButton[] buttonsA = {
    new JButton("Card 1"),
    new JButton("Card 2"),
    new JButton("Card 3"),
    new JButton("Card 4")
};
JButton[] buttonsB = {
    new JButton("Card 5"),
    new JButton("Card 6"),
    new JButton("Card 7"),
    new JButton("Card 8")
};
JPanel south = new JPanel();
for (int i = 0; i < buttonsA.length; i++) {
    ... //réduction
    south.add(buttonsA[i]);
}
for (int i = 0; i < buttonsB.length; i++) {
    ... //réduction
    south.add(buttonsB[i]);
}

ng2.gif

Cependant, comme il s'agit d'un phénomène qui se produit car la clé n'est identifiée que par le nom de l'énumérateur, elle se trouve dans la classe utilitaire.

python


cards.show(panel, key.toString());

panel.add(comp, key.toString());

Partie de

python


cards.show(panel, key.getClass().getName() + "." + key.toString());

panel.add(comp, key.getClass().getName() + "." + key.toString());

En réécrivant comme, l'identification de la clé sera faite avec ** nom de classe pleinement qualifié [^ nom de classe complet] + nom de l'énumération ** de l'énumération, et cela fonctionnera normalement.

ok2.gif

Il lance également ʻArrayIndexOutOfBoundsException` lors de la boucle si le nombre de clés et de composants ne correspondent pas. Ce domaine peut être amélioré en rendant la classe utilitaire un peu plus désordonnée.

[^ Interface marqueur]: Une interface marqueur est une interface qui n'implémente rien à l'intérieur. Il est souvent utilisé pour donner du sens à la classe à implémenter.

[^ toString]: Même si vous remplacez toString () pour que toutes les mêmes représentations sous forme de chaîne soient renvoyées, cela ne sert à rien car la clé ne peut pas être déterminée.

[^ Nom de classe complet]: le nom de la classe, y compris le nom du package utilisé dans l'instruction d'importation.

Recommended Posts

Arrêtez les clés de type String et introduisez l'interface de marqueur
interface et résumé
Héritage et interface.