javaswingcontainersawtjlayeredpane

Why does Container's add(panel, Integer) provide a different result than add(panel, int)?


To provide background and explanation of the question:

I was working with a base program (below) to figure out how to use JLayeredPane. I created a JLayeredPane and added it to the JFrame, then moved onto creating and adding the panels to the JLayeredPane. The GeeksForGeeks example I was going from had a red square on bottom, then an overlapping green square, and finally an overlapping blue square on top. It add()ed the panels to the JLayeredPane using the constants JLayeredPane.DEFAULT_LAYER, JLayeredPane.PALETTE_LAYER, and JLayeredPane.MODAL_LAYER. However, as I was planning on perhaps using more control over the layers, I didn't use those, but rather used numbers for the layers. The bottom layer is 0, with higher numbers indicating higher layers.

But when I tried running the code, the panels were reversed. The red was on top, green in middle, and blue on bottom. The problem was I had the code

layeredPane.add(panel1, 0);
layeredPane.add(panel2, 1);
layeredPane.add(panel2, 2);

To rectify the code, I had to use not the primitive int I had before, but rather Integers (which, it turns out, JLayeredPane.DEFAULT_LAYER etc. are).

layeredPane.add(panel1, Integer.valueOf(0));
...

I was curious why it exhibited this behaviour, so I did a little digging. It turns out that javax.swing.JLayeredPane is a descendant of java.awt.Container (which itself derives from java.lang.Object). Container (Oracle JavaDoc, ClassPath source code) has overloaded add methods (narrowing down to the methods I used):

public Component add(Component comp, int index) {
    addImpl(comp, null, index);
    return comp;
}

and

public void add(Component comp, Object constraints) {
    addImpl(comp, constraints, -1);
}

My understanding of the problem begins to get a little fuzzy here. I know they both called the addImpl method, with the int version passing a null constraint with the given index, while the Integer version passed the Integer as the constraint and -1 as the index. The JavaDoc for addImpl says (partly summarizing for conciseness):

...

The constraints are defined by the particular layout manager used. BorderLayout ... GridBagLayout ... LayoutManager2 ... if the current layout manager does not implement LayoutManager2 and constraints is a String, then LayoutManager... is invoked ...

If the component is not an ancestor of this container and has a non-null parent, it is removed from its current parent before it is added to this container.

...

@param constraints an object expressing layout constraints for this component

@param index the position in the container's list at which to insert the component, where -1 means appended to the end

...

protected void addImpl(Component comp, Object constraints, int index) {

...

As far as I can tell, JLayeredPane (nor Integer) does not derive from any of those layouts (or from String). I thus suspect the last standard line of the JavaDoc comes into play. Beyond that, I have zero idea as to why the Object of a number as the constraints provides different behavior than null constraints and a numbered index. When passing Integer, it doesnt appear (to me) that -1 index is used as the component's index, but as a routing flag within addImpl. But the actual mechanics? I don't understand.

How is the Integer object functioning to place it in the proper layer, rather than an int? It doesn't appear the order the components are added matters, just the passed numbers. Any help/explanations would be appreciated.

import javax.swing.*;
import java.awt.*;

public class SandboxGuiOne
{
    public static void main(String[] args)
    {
        // Create JFrame (main window of application)
        JFrame frame = new JFrame("JLayeredPane Example");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.setSize(500, 500);

        // Create JLayeredPane to manage layering of components
        JLayeredPane layeredPane = new JLayeredPane();
        frame.add(layeredPane); // add JLayeredPane to JFrame

        // Create three colored panels to add to layered pane
        JPanel panel1 = createColoredPanel(Color.RED, 100, 100, 200, 200);
        JPanel panel2 = createColoredPanel(Color.GREEN, 150, 150, 200, 200);
        JPanel panel3 = createColoredPanel(Color.BLUE, 200, 200, 200, 200);

        // Add panels to layered pane with different layer values.
        // Layers determine stacking order of the panels.
        layeredPane.add(panel1, Integer.valueOf(0));
        layeredPane.add(panel2, Integer.valueOf(1));
        layeredPane.add(panel3, Integer.valueOf(2));

        frame.setVisible(true);
    }

    private static JPanel createColoredPanel(Color color, int x, int y, int width, int height)
    {
        // Create colored JPanel with specific color and position
        JPanel panel = new JPanel();
        panel.setBackground(color);
        panel.setBounds(x, y, width, height);
        return panel;
    }
}

Layouting when add(Component comp, int index) (primitive int argument) is used: int result

Layouting when add(Component comp, Object constraints) (with Integer objects) is used: Integer result


Solution

  • You're going to be asking why pretty soon, if not already. Without looking at history there is no answer to this question. The design of this API makes no sense. Unless, history.

    The history of all this

    Part 1: Generics

    In a distant past, java didn't have generics (the <String> in List<String>). When using lists you'd just write List foo = new ArrayList();. To retrieve objects from it, you'd call foo.get(0) as normal, but, the type of that expression is Object.

    As you can imagine, we did a lot of casting in those days, and many APIs had Object all over the place.

    But nowadays, we don't do that anymore. We have generics.

    Generics do need some sort of thing to hang itself on - a coatrack. In other words, to link the type of list.get(0) and the type of x in list.add(x), we need something to convey the notion 'the same type for both please'. With List, it's simply the list - it's typed List<T>.

    With swing's API design this is not really possible; not without tying ourselves into crazy knots. The original swing design is as follows:

    In such a design you'd want to associate the types same as List does: That any given LayoutManager explains exactly what type of constraint object it understands, and that you are then prevented from associating anything but the right kind of object with a component when adding it to the container.

    But the coat rack is missing. You don't call .add(component, constraint) on the layout manager. You invoke it on the container. The only coat rack we can find is the container itself, but that's a bit bizarre. We'd have to write:

    class javax.swing.JContainer<C extends LayoutConstraint> {
      public void setLayoutManager(LayoutManager<? super C>) { ... }
      public void add(Component component, C constraint) { ... }
    }
    

    Which could have been done and might even have been a good idea but it is misleading; we'd normally assume that if containers have a generics type, that, like java.util.List, that's referring to the kinds of things you can put in this container. It's not exactly obvious that it's referring to the kind of layout constraint. So, this choice wasn't made.

    And thus, the type of 'constraint' is java.lang.Object eventhough the LayoutManager obviously can't do anything with e.g. new Object(). It has a spec as to what constraint object it can understand. For example, GridBagLayout says that the only constraint object it actually understands is GridBagConstraint.

    Its examples even use a different path to associate constraint with component. Instead of:

    container.add(component, new GridBagConstraint());
    

    They use:

    container.add(component);
    gridBagLayout.setConstraints(component, new GridBagConstraint());
    

    So why does that wonky add method exist?

    It does. It's 30 years old, you'd have to ask the author. It was added. Why is probably lost to the mists of history.

    But given that it exists, removing it is a breaking change. Any java code out there that called it needs to rewrite. Which is not generally how java rolls. OpenJDK doesn't break APIs unless it is impossible to maintain the API, it is fundamentally insecure, or the maintenance burden to keep it is excessive. "It is terribly designed, misleading API that results in hard to read and hard to test code" is not sufficient reason to delete functionality.

    Hence, at least, we have an explanation for why it's still around.

    Part 2: no enums

    Java didn't have enums either. Any API designed today, if you have a limited set of parameterless 'choices', which describes exactly the constraint that JLayeredPane wants, would be delivered as an enum. You'd have:

    public enum JLayeredPane {
      DEFAULT_LAYER,
      PALETTE_LAYER,
    }
    

    Though in this case that's debatable; the point of this design is that you can use any integer value as layer. Even then, you'd use some wrapper object to avoid this confusion.

    Because enums didn't exist back then, primitive constants were used instead. They had all sorts of nasty downsides (if you try to System.out.println the DEFAULT_LAYER value you get a useless '0'; if it had been an enum, you'd have gotten DEFAULT_LAYER).

    Part 3: Autoboxing

    Less relevant but autoboxing didn't exist either. In today's java, int and Integer are still separate concepts but the compiler will automatically translate one to the other if the code would not be valid if it didn't. But 25 years ago when swing was designed that wasn't true. You had to write it explicitly. Hence, int and Integer were better understood and treated as completely independent concepts, hence having a method where you're meant to call .add(component, SomeIntegerRef) as being different from .add(component, someInt) was slightly less bizarre than it is today.

    Part 4: ... um ...

    It's still even with all that in mind pretty bad API design. There are downsides to the whole 'we do not break APIs lightly' thing, which is that a design mistake lasts forever. We still have java.util.Date. It's terrible. Don't ever use that class. It's drivel. But it's still there.

    Also keep in mind that the JVM was way less good at optimising and lots of APIs were designed with the especially now but still to some extent also back in the past misguided notion of making the API harder to use if it feels like it might make things faster. Using a bunch of integers instead of more convoluted layout constraint objects 'felt' faster - way back when, lots of objects was an issue. The benefits of modern garbage collectors that make cheap fast garbage essentially free didn't exist yet.

    With that context:

    So what's happening here?

    The difference between the calls

    The Container object just has an ordered list of the components inside. It has no idea how to lay them out, but the container is the one responsible both for maintaining the bunch of components that it contains as well as the ordering of them. This ordering has no inherent meaning (i.e. 'a is ordered before b' does not require as per spec that 'a is shown behind b' if that ever becomes relevant. 'a is ordered before b' is the only thing it means. Because the container is not responsible for laying things out).

    It's the layout manager that decides how to render those components. Each component has a 'constraint' object associated with it (or null). The layout manager takes the ordered list of [component, constraint] pairs and renders it according to its code.

    With .add(component, 0) you're just adding the components (as the index number is the same as 'the end'), with no constraint object. With .add(component, Integer.valueOf(0)), you're adding to the end, but with a constraint object that happens to be an integer. The container just stores this; it's the layout manager that fetches this integer and uses it as it wants.

    For JLayeredPane, that integer is also kinda used as a component ordering thing.

    Closing notes

    Swing API is 25 years old, which is its only excuse, because, lordy lord it's terrible. Don't use it unless you absolutely have to. It's essentially obsolete (barely maintained). 99% of all java code out there doesn't have a GUI, or if it is, it's web based: The java app runs as a server and emits HTML which a browser renders into a GUI. If you must write a desktop app, you should use javafx, swt, or some other higher level less terrible API design.

    Note that swing works. It's just hard to use and unintuitive. You run into issues like this all the time with it. It's also hard to find help and tutorials because so few people use it.