[JAVA] Stop String type keys and introduce marker interface

Introduction

In Java libraries, we often see String type objects used as ** values ** (hereinafter referred to as "keys") to identify objects such as keys and property names. For example, the java.util.Properties class used to introduce property lists, the java.beans.PropertyChangeSupport class used to send events such as property change notifications, and so on. It is said that it is declining and is always compared with JavaFx ~~ It is one of Swing's layout managers, java.awt.CardLayout class ...

In the method using the String type, if the value contains typographical errors ** or the key name is changed due to the change of implementation **, the object cannot be referenced correctly and a problem occurs. You may.

Example of value acquisition failure due to typographical error in key name


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

Example of value acquisition failure due to implementation change


Map<String, String> entries = new HashMap<>() {{
  put("foo", "Huh");  //Implementation changes
  put("huga", "Fuga");
  put("bar", "Bah");  //Implementation changes
}};
System.out.println(entries.get("hoge")); //Output: null (acquisition failed)
System.out.println(entries.get("huga")); //Output: Fuga

Such dangers can be eliminated by introducing a marker interface [^ marker interface]. This time, I would like to explain using the CardLayout class introduced earlier as an example.

Before introduction

Next, consider the screen laid out by the collapsed code A.

Code A

python


public class App {

    public static void main(String[] args) {
        new JFrame("Sample") {{
            setSize(350, 300);
            setLocationRelativeTo(null);
            setDefaultCloseOperation(EXIT_ON_CLOSE);
            setVisible(true);
            //CardLayout generation
            CardLayout cards = new CardLayout();
            //Center of frame
            JLabel[] labels = {
                new JLabel("Card 1"),
                new JLabel("Card 2"),
                new JLabel("Card 3"),
                new JLabel("Card 4")
            };
            Font fnt = new Font("Meiryo", 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));
            }
            //Bottom of frame
            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]);
            }
            //Adding a panel to the frame
            add(center, BorderLayout.CENTER);
            add(south, BorderLayout.SOUTH);
        }};
    }
}

By executing this code and clicking each button, you can switch between the four labels as shown below.

ok.gif

Where it is in code A

python


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

The moment you mistake the " Card " for ** " Cerd " **, the switch will not work properly as follows.

ng.gif

This is because the show method can no longer refer to each label associated with the panel in the center of the frame by the add method that specifies the identifiers of the four labels in the second argument.

python


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

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

It doesn't cause a compile error, so unless you notice it or ask someone to tell you, it's okay if you don't know the cause of the problem.

Introduction

So, we will introduce a marker interface.

It doesn't make sense to just introduce a marker interface, so prepare an enumeration, define a key, and have that enumeration implement the marker interface.

python


//Marker interface
interface CardKey {}
//Enumerator that implements the marker interface
enum Card implements CardKey {
    CARD1, CARD2, CARD3, CARD4;
}

Also, since the key defined in the add method and show method that appeared earlier cannot be specified as an argument as it is, the utility class that is collapsed next is defined so that the component to be displayed by switching with the key can be linked. ..

Utility class

python


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

    private CardLayoutUtil(JPanel panel) {
        this.panel = panel;
        cards = (CardLayout) panel.getLayout();
    }
    
    /**
     *Create an instance of this class based on the panel it takes as an argument.
     *
     * @param panel Panel associated with the instance
     * @return Instance of this class
     * @throws NullPointerException If the argument is null
     * @throws IllegalArgumentException When CardLayout is not set in the panel for the argument
     */
    static CardLayoutUtil of(JPanel panel) {
        Objects.requireNonNull(panel);
        if (!(panel.getLayout() instanceof CardLayout))
            throw new IllegalArgumentException("CardLayout is not set in the panel that takes the argument.");
        return new CardLayoutUtil(panel);
    }

    /**
     *Register the component in the panel associated with the instance.
     *
     * @param comp Component to register
     * @param key The key associated with the component
     * @throws NullPointerException If any argument is null
     */
    void addCard(Component comp, CardKey key) {
        Objects.requireNonNull(comp);
        Objects.requireNonNull(key);
        panel.add(comp, key.toString());
    }

    /**
     *Display the component associated with the key taken as an argument.
     *
     * @param key The key associated with the component
     * @throws NullPointerException If the argument is null
     */
    void showCard(CardKey key) {
        Objects.requireNonNull(key);
        cards.show(panel, key.toString());
    }
}

Since the layout manager of the panel associated with the utility class is not CardLayout, and the behavior expected to be null for the keys and components taken as arguments of each method cannot be realized, exception handling is performed.

Create an object of this utility class, and prepare an array of each key so that you can turn it in a loop.

python


//Creating a CardLayoutUtil object
CardLayoutUtil util = CardLayoutUtil.of(center);
Card[] cardKeys = Card.class.getEnumConstants();

The rest is in code A

python


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

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

Part of

python


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

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

If you replace it with, you don't have to worry about the above problems caused by typographical errors or implementation changes.

important point

This method is realized by using the character string representation of the key (enumerator) obtained by toString (). Therefore, ** if the same key is associated with another component and reused **, or ** an enumerator with the same name is defined between different enumerators **, it will not work well [^] toString].

An example of defining an enumerator with the same name between enumerators


enum CardsA implements CardKey {
    CARD1, CARD2, CARD3, CARD4;
}
enum CardsB implements CardKey {
    CARD1, CARD2, CARD3, CARD4;
}
Code example that does not work properly

python


//Center of frame
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")
};
... //abridgement
//Creating a CardLayoutUtil object
CardLayoutUtil util = CardLayoutUtil.of(center);
CardsA[] cardKeysA = CardsA.class.getEnumConstants();
CardsB[] cardKeysB = CardsB.class.getEnumConstants();
for (int i = 0; i < labelsA.length; i++) {
    ... //abridgement
    util.addCard(labelsA[i], cardKeysA[i]);
}
for (int i = 0; i < labelsB.length; i++) {
    ... //abridgement
    util.addCard(labelsB[i], cardKeysB[i]);
}
//Bottom of frame
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++) {
    ... //abridgement
    south.add(buttonsA[i]);
}
for (int i = 0; i < buttonsB.length; i++) {
    ... //abridgement
    south.add(buttonsB[i]);
}

ng2.gif

However, since this is a phenomenon that occurs because the key is identified only by the enumerator name, it is in the utility class.

python


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

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

Part of

python


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

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

By rewriting, the key identification will be done by ** fully qualified class name [^ fully qualified class name] + enumerator name ** of the enumeration, and it will work normally.

ok2.gif

It also throws ʻArrayIndexOutOfBoundsException` when looping if the number of keys and components do not match. That area may be improved by making the utility class a little more messy.

[^ Marker Interface]: A marker interface is an interface that does not implement anything inside. It is often used to give meaning to the class to be implemented.

[^ toString]: Even if you override toString () so that they all return the same string representation, you can't tell the key.

[^ Fully qualified class name]: The class name that contains the package name used in the import statement.

Recommended Posts

Stop String type keys and introduce marker interface
interface and abstract
Inheritance and interface.