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.
Next, consider the screen laid out by the collapsed 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.
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.
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.
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. ..
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.
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;
}
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]);
}
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.
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.