4
\$\begingroup\$

I'm learning Java at the moment and I'm currently trying to make a GUI using Swing. I've done some reading and people usually prefer and advice to use composition instead of inheritance of e.g. JFrames.

I'm trying to code the GUI by hand instead of using the GUI builders, since they all generate a lot of code and I want to have a proper understanding of what's happening, and I can't seem to do that with the builders.

Questions: Is this a proper use of 'composition' over inheritance, in this case of using JFrames? I want the code behind the GUI to be as efficient as possible, before I begin on expanding the GUI.

Also: Swing is not thread-safe, so I read that it's good to use invokeLater. Am I using it correctly? Can I use it like that in the main, or should it go under the createGUI method?

package com.company;
import javax.swing.*;

public class Main {

    public static void main(String[] args) {

        SwingUtilities.invokeLater(new Runnable() {
            public void run() {
                Main initiateProgram = new Main();
                initiateProgram.createGUI();
            }
        });
    }

    public void createGUI() {

        JFrame programGUI = new JFrame("GUI Program");

        programGUI.setSize(500, 500);
        programGUI.setLocationRelativeTo(null);
        programGUI.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

        programGUI.setVisible(true);

        // And here on, an ActionListener for e.g. a button...
    }
}
\$\endgroup\$
3
  • 3
    \$\begingroup\$ This isn't composition. I highly suggest looking further into the topic. Composition is when an object owns another object. When object A becomes garbage, so does object B. In this case, there is no ownership. \$\endgroup\$
    – Dioxin
    Mar 21, 2016 at 1:39
  • 3
    \$\begingroup\$ Please do not update the code in your question to incorporate feedback from answers, doing so goes against the Question + Answer style of Stack Exchange. I have rolled back your edit to allow more answers to answer the original question. If you want to improve your code further, consider asking a follow-up question. Additionally, your question was migrated here from Stack Overflow because this question is considered "too broad" on Stack Overflow. \$\endgroup\$ Mar 21, 2016 at 19:25
  • \$\begingroup\$ So the thing is, we didn't rollback far enough and the answers didn't make sense, so we rollbacked some more. This because another reviewer wanted to review your code but found that his answer completely didn't match with the other answers. \$\endgroup\$
    – Pimgd
    Mar 23, 2016 at 16:13

3 Answers 3

5
\$\begingroup\$

Yes, that is proper use of composition over inheritance. Although some may say this is actually aggregation, due to the nature of Swing.

Composotion is the act of one object owning another object. When Main becomes garbage, so should the objects it's composed of.

Aggregation is a similar concept, except the composed objects do not become garbage. Composition is a "has-a" relationship, while aggregation is a "uses-a" relationship. Aggregation is another form of composition, with the difference being ownership (no ownership when using aggregation).

When you create a JFrame, the window is also referenced "behind the scenes", and can be accessed via Window.getWindows(). Even if Main becomes garbage, the window will still exist until you call dispose() on it.


It's recommended to avoid putting logic in your constructors. It helps lower the cost of actually creating the object, as well as makes testing easier. I recommend adding your components to a JPanel, then passing that to the constructor to be added to the frame via JFrame#setContentPane(Container).

The way you use invokeLater is fine in the sense that it forwards GUI code to the Event Dispatch Thread, which is what you always want to do. But hiding it in the constructor could lead to confusions. From outside of the class, we cannot be sure of whats inside the constructor. Someone may assume that the code is not already forwarded to the EDT, leading them to them wrapping the constructor call in an invokeLater aswell, resulting in posting 2 events to the EDT.


To remove the logic from your constructor, you should pass in the JFrame once you have prepared it:

public static void main(String[] args) {
    EventQueue.invokeLater(() -> {
        JFrame frame = new JFrame();
        //add components

        new Test(frame);
    });
}

Your Test class would then look something like this:

class Test {
    private JFrame frame;

    public Test(JFrame frame) {
        this.frame = frame;
    }

    //...
}

This is referred to as Dependency Injection. The idea is that Test needs a frame. Without a frame, there shouldn't be a Test object. Create Test objects when you are certain that there is a frame for it to use.

The benifits of this include avoiding creating garbage objects (creating Test would be pointless if there was a problem creating the frame it needs), as well as allowing you to pass different frames, rather than having the frame hard-coded into it. The ability to use different frames, rather than be forced to use a single hard-coded frame, is referred to as Inversion of control, and increases reusability.


You shouldn't size your frame directly. You shouldn't size any component directly. Instead, use a layout manager to handle the size and location of your components.

Once you have chosen a layout manager, size your frame by using pack(), which sizes the frame based on the components inside of it.

\$\endgroup\$
16
  • \$\begingroup\$ 1) Is there something I can do for it to actually act as composition than aggregation, or is it, as you said, that’s the nature of Swing, and thus this is fine? 2) “It's recommended to avoid putting logic in your constructors” - OK, can I avoid that by doing what I have done now? I’ve edited the main post to include the new code. I did what you said about getContentPane, and adding components to it. But I am not sure if I've done it 100% correctly, and whether it’s okay to place it where I’ve done. \$\endgroup\$
    – learningJava123
    Mar 21, 2016 at 17:40
  • \$\begingroup\$ Due to limited characters, I'm posting my two last questions here. Sorry for all these questions. :-( 3) I’ve put invokeLater under main. Is that better? 4) Also, is it fine if I put main on the top of the code instead of at the bottom, as well as putting the variables of frameGUI and panelGUI right before initializeGUI? \$\endgroup\$
    – learningJava123
    Mar 21, 2016 at 17:42
  • \$\begingroup\$ @learningJava123 1: It's fine, aggregation is a type of composition, just without ownership. Can't bypass this in swing. 2: That's still putting work in the constructor, it's just decomposed into a method now. Create the JFrame, prepare it, then pass it to Test. If Test requires a JFrame, it's better to pass it the frame, rather than have it create the frame itself. This is called Dependency Injection. 3: Yes, although I recommend using a lambda if you're using Java 8+. 4: You can place the main method anywhere within the class. \$\endgroup\$
    – Dioxin
    Mar 21, 2016 at 17:49
  • \$\begingroup\$ Would Test then need a returntype? Or can I directly do: JFrame frameGUI = new JFrame("GUI Program"); new Test(); under main below the invokeLater section? Could you help me with this one? \$\endgroup\$
    – learningJava123
    Mar 21, 2016 at 17:59
  • \$\begingroup\$ @learningJava123 Test would need a parameter for it's constructor. I have edited my answer \$\endgroup\$
    – Dioxin
    Mar 21, 2016 at 18:21
3
\$\begingroup\$
JFrame programGUI = new JFrame("GUI Program");

programGUI.setSize(500, 500);
programGUI.setLocationRelativeTo(null);
programGUI.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

programGUI.setVisible(true);

// And here on, an ActionListener for e.g. a button...

You seem to be planning to add elements to (hopefully a JPanel in) the JFrame after the comment. This is a bad idea. Always do the sizing and positioning of your JFrame after you add every element you want to it. That way, the JFrame will know the size of all its subcomponents to allow for proper sizing and positioning:

JFrame programGUI = new JFrame("GUI Program");

// In here, add components; you should probably use functions such as
// initializeComponents();
// addComponents();

// The following would also be best in a function such as
// setupFrame();
programGUI.setSize(500, 500);
programGUI.setLocationRelativeTo(null);
programGUI.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

programGUI.setVisible(true);

It's also much better to use pack() than setSize, because you don't want to be relying on specific dimensions in GUI applications, in order to allow the GUI to work well in all systems. What if my screen has 2 or 3 times the resolution as yours? Then everything sized in this way would be appear half the size!

Additionally, it doesn't make a huge difference but setLocationRelativeTo(null) centers the frame in the screen (good job doing that after sizing the frame), whereas you might want to use setLocationByPlatform(true), which would make consecutive frames open in different locations.


Naming

programGUI is kind of an awkward name. First, the JFrame isn't the "program", so it would be better to call it frameGUI. However, if I'm programming in Swing, I already know what a frame is; just remove the GUI suffix to get frame. Build the names of GUI components from that.


programGUI.setVisible(true);

// And here on, an ActionListener for e.g. a button...

I strongly recommend that you do all of your setup before you call setVisible(true). That should be the absolute last thing you do. What if you decided you wanted a modal JDialog instead of a JFrame? Because it is modal, your code would halt execution at the setVisible(true), and everything past that won't execute until the dialog closes. Always make setVisible(true) (along with sizing and positioning) the last thing you do.

\$\endgroup\$
5
  • \$\begingroup\$ this answer is now invalidated =/ \$\endgroup\$
    – Pimgd
    Mar 23, 2016 at 16:13
  • \$\begingroup\$ @Pimgd Fixed :D \$\endgroup\$
    – Justin
    Mar 23, 2016 at 17:30
  • \$\begingroup\$ @Justin Thank you very much for your answer! I greatly appreciate it, even though this topic is considered to be “answered”. Every answer helps me understand Swing better. Yes, I’ve added a JPanel in a JFrame, but the comment is a mistake. Ignore it. :-P And yes, I’ve realized that pack() is indeed better to use than setting manual sizes, so I’m trying that at the moment with GridBagLayout. I’m experimenting with the layout at the moment, and it is going well, but I still need to figure out how I can place a button to exactly where I want. \$\endgroup\$ Mar 23, 2016 at 22:04
  • \$\begingroup\$ Maybe it’s because I need to add an extra JPanel… I’m not sure yet, still need to experiment and play with it. OK, interesting, so does ’setLocationByPlatform‘ replace ‘setLocationRelativeTo’? Or can they work together? Regarding the naming, it was just something quick I wrote. What I am trying to make now is not actually the application I’m going to make for my project. It’s just to play with Swing and get better at it, so I can apply the knowledge I've gotten to my future application. Yes, I realized that (the programGUI name) the other day, funnily enough I named it exactly frameGUI :-P \$\endgroup\$ Mar 23, 2016 at 22:05
  • \$\begingroup\$ But I am going to name my variables and methods properly for the app. Sorry for the long response in multiple answers! Not enough characters to work with. :-) \$\endgroup\$ Mar 23, 2016 at 22:11
0
\$\begingroup\$

A good start might be to make JFrame programGUI a private member of class Main. Currently programGUI only exists in the scope of function createGUI. So:

public class Main {
     JFrame programGUI; 

     ...

     public void createGUI() {

         programGUI = new JFrame("GUI Program");

     ...

As a bit of a fragment. That would then be an example of composition.

Also, for threading in a Swing application, consider SwingWorker see http://www.javacreed.com/swing-worker-example/

\$\endgroup\$
3
  • \$\begingroup\$ I gave it another shot. Could you look at the main post? I've edited it. Regarding SwingWorker, I don't think my application requires it. Is it fine to use invokeLater with Runnable for now? Or can I leave that out too? \$\endgroup\$
    – learningJava123
    Mar 21, 2016 at 12:19
  • \$\begingroup\$ Yes that is now using composition. \$\endgroup\$ Mar 21, 2016 at 20:28
  • \$\begingroup\$ There are reasons in a Swing application to use SwingWorker over a Runnable. In general we don't use the basic primitive thread types these days, there are better abstractions of them. This is the other tutorial I was trying to find which explains some reasons: javaadvent.com/2012/12/… \$\endgroup\$ Mar 21, 2016 at 20:37

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.