Computer Science Canada

Scala and Java: A Quick Swing Comparison by Example

Author:  wtd [ Sat Dec 03, 2005 7:36 pm ]
Post subject:  Scala and Java: A Quick Swing Comparison by Example

To be equitable, Java syntax highlighting is not used.

More on the interesting features of Scala (as seen here) in a bit.

Java:

code:
import javax.swing.*;
import java.awt.event.*;
import static javax.swing.JFrame.*;

class MyWindow extends JFrame {
   public MyWindow() {
      super("Hello World");

      setDefaultLookAndFeelDecorated(true);

      setDefaultCloseOperation(EXIT_ON_CLOSE);

      JButton button = new JButton("Hello World");
      button.addActionListener(new ActionListener() {
         public void actionPerformed(ActionEvent e) {
            System.out.println("Hello world");
         }
      });
      getContentPane().add(button);

      pack();
      setSize(400, 300);
      setVisible(true);
   }
}

public class HelloWorldSwing {
   public static void main(String[] args) {
      MyWindow win = new MyWindow();
   }
}


Scala:

code:
import javax.swing._;

class MyWindow2 extends JFrame("Hello World") {
   import JFrame._;

   setDefaultLookAndFeelDecorated(true);

   setDefaultCloseOperation(EXIT_ON_CLOSE);

   val button = new JButton("Hello world");
   button addActionListener Print("Hello world");
   getContentPane() add button;

   pack;
   setSize(400, 300);
   setVisible(true);
   
   import java.awt.event._;
   
   case class Print(msg : String)
    with ActionListener
    with Application {
      def actionPerformed(e : ActionEvent) =
         Console println msg
   }
}

object HelloWorldScala with Application {
   val win = new MyWindow2
}

Author:  wtd [ Sun Dec 04, 2005 3:17 pm ]
Post subject: 

Noteworthy differentiations from Java, in no particular order:


  • Import difference #1

    code:
    import javax.swing.*;


    vs.

    code:
    import javax.swing._;


    * is a valid identifier in Scala, and thus cannot be used the same way it is in Java. The used of _ does fit with how underscores are used in pattern matching, which is a nice bit of consistency.

  • Import difference #2: static imports and relativism

    code:
    import static javax.swing.JFrame.*;


    vs.

    code:
    import JFrame._;


    Static imports in Scala are less verbose and clearer than their Java counterparts.

    Imports in general are also relative in Scala. In Java, the JFrame class had to be fully qualified. In Scala, we can simply specify the JFrame class, because it tries to look in javax.swing first.

  • Import difference #3: local imports

    code:
    import java.awt.event.*;


    vs.

    code:
    import java.awt.event._;


    In the Java program, we had to place this import at the top of the program, and import the names in java.awt.event into everything in this file.

    In Scala, we used a local import.

  • Parameterized classes

    code:
    class MyWindow extends JFrame {
       public MyWindow() {
          super("Hello World");


    vs.

    code:
    class MyWindow2 extends JFrame("Hello World")


    Scala permits paramaterized classes, rather than having constructors in the Java sense. This makes a lot of sense, since it reduces repeated code. A contrived example:

    code:
    class Foo {
       int bar;
       double baz;
       
       public Foo(int initBar, double initBaz) {
          bar = initBar;
          baz = initBaz;
       }
    }


    vs.

    code:
    class Foo(initBar : int, initBaz : double) {
       var bar = initBar;
       var baz = initBaz;
    }


    Parameterized classes are a familiar site to anyone who has worked with Objective-Caml.

  • Local type inferencing

    code:
    JButton button = new JButton("Hello World");


    vs.

    code:
    val button = new JButton("Hello world");


    Local type inferencing is visible here. Why specify the type of "button"? "new JButton" does it quite well.

    Additionally, "val" rather than "var" serves to make the variable "final".

  • Inner classes and magical infix operators from methods

    code:
          button.addActionListener(new ActionListener() {
             public void actionPerformed(ActionEvent e) {
                System.out.println("Hello world");
             }
          });


    vs.

    code:
    button addActionListener Print("Hello world");


    code:
       case class Print(msg : String)
        with ActionListener
        with Application {
          def actionPerformed(e : ActionEvent) =
             Console println msg
       }


    Scala does not support anonymous inner classes. It does however support inner classes.

    In this case we've made it a "case class", which removes the need to use "new".

    The Print class mixes in ActionListener and Application. From ActionListener we get the obligation to implement actionPerformed, and from Application we get access to Console. Print of course is parameterized with the string that we actually want it to print, so that we can reuse this class.

    Visible in this code snippet is the use of Scala's ability to turn methods into infix operators.

    code:
    Console println msg


    Can also be called as:

    code:
    Console.println(msg)


    Also visible is a reduction of braces and semi-colons.

    Since actionPerformed contains only one expression, it can be expressed without braces. Additionally, since the braces for the Print class form natural punctuation, there's no need for a semi-colon following the actual println expression.

  • Main... or not

    code:
    public class HelloWorldSwing {
       public static void main(String[] args) {
          MyWindow win = new MyWindow();
       }
    }


    vs.

    code:
    object HelloWorldScala with Application {
       val win = new MyWindow2
    }


    Objects in Scala represent an alternative way of organizing variables and data. They are not instantiated like classes. They are essentially modules.

    Here I've mixed Application into my object, meaning that I don't need a "main" method for such a simple program.

    Again, local type inferencing is used to avoid repetitive typing of "MyWindow2".

Author:  wtd [ Sun Dec 04, 2005 6:09 pm ]
Post subject: 

Modified examples, where the button is a subclass of JButton.

Java:

code:
import javax.swing.*;
import java.awt.event.*;
import static javax.swing.JFrame.*;

class MyButton
 extends JButton
 implements ActionListener {
   private String msgToPrint;

   public MyButton(String title, String toPrint) {
          super(title);

          msgToPrint = toPrint;
          addActionListener(this);
   }

   public void actionPerformed(ActionEvent e) {
          System.out.println(msgToPrint);
   }
}

class MyWindow extends JFrame {
   public MyWindow() {
      super("Hello World");

      setDefaultLookAndFeelDecorated(true);

      setDefaultCloseOperation(EXIT_ON_CLOSE);

      MyButton button = new MyButton("Hello World", "Hello world");
      getContentPane().add(button);

      pack();
      setSize(400, 300);
      setVisible(true);
   }
}

public class HelloWorldSwing {
   public static void main(String[] args) {
      MyWindow win = new MyWindow();
   }
}


Scala:

code:
import javax.swing._;   
import java.awt.event._;

class MyButton2(title : String, msgToPrint : String)
 extends JButton(title)
 with ActionListener
 with Application {
   addActionListener(this);
   
   def actionPerformed(e : ActionEvent) =
      Console println msgToPrint
}

class MyWindow2 extends JFrame("Hello World") {
   import JFrame._;

   setDefaultLookAndFeelDecorated(true);

   setDefaultCloseOperation(EXIT_ON_CLOSE);

   val button = new MyButton2("Hello world", "Hello world");
   getContentPane() add button;

   pack;
   setSize(400, 300);
   setVisible(true);
}

object HelloWorldScala with Application {
   val win = new MyWindow2
}

Author:  Cervantes [ Sun Dec 04, 2005 10:12 pm ]
Post subject: 

So wtd, what's your review of Scala, so far? You seem to be spending a lot of time with it.

Author:  wtd [ Sun Dec 04, 2005 10:25 pm ]
Post subject: 

Parameterized classes

The major distainction between the two examples in the previous post boils down to:

code:
class MyButton
 extends JButton
 implements ActionListener {
   private String msgToPrint;

   public MyButton(String title, String toPrint) {
     super(title);

     msgToPrint = toPrint;
     addActionListener(this);
   }

   public void actionPerformed(ActionEvent e) {
     System.out.println(msgToPrint);
   }
}


vs.

code:
class MyButton2(title : String, msgToPrint : String)
 extends JButton(title)
 with ActionListener
 with Application {
   addActionListener(this);
   
   def actionPerformed(e : ActionEvent) =
      Console println msgToPrint
}


In the Java example, the MyButton class has a constructor which takes two Strings. The first is the text to display in the button. This it passes to the constructor for the JButton class. The second String is the message to print to the console when the button is clicked.

In order to use this second value in the actionPerformed method, we have to assign it first to an instance variable which will still be in scope when the constructor is finished.

Scala dramatically simplifies this. The values passed to the class become accessible to any method.

Author:  wtd [ Sun Dec 04, 2005 11:14 pm ]
Post subject: 

Cervantes wrote:
So wtd, what's your review of Scala, so far? You seem to be spending a lot of time with it.


I rather like it.

It's not flawless, but it addresses a number of concerns I had in the Why Java Sucks thread.

Author:  wtd [ Mon Dec 05, 2005 12:23 pm ]
Post subject: 

A further use of mix-in composition to implement this absurdly simple program.

code:
import javax.swing._;   
import java.awt.event._;

class PrintAction(msg : String)
 with ActionListener {
   def actionPerformed(e : ActionEvent) =
      Console println msg 
}

class MyButton2(title : String, msgToPrint : String)
 extends JButton(title)
 with PrintAction(msgToPrint) {
   addActionListener(this)
}

class MyWindow2 extends JFrame("Hello World") {
   import JFrame._;

   setDefaultLookAndFeelDecorated(true);

   setDefaultCloseOperation(EXIT_ON_CLOSE);

   val button = new MyButton2("Hello world", "Hello world");
   getContentPane() add button;

   pack;
   setSize(400, 300);
   setVisible(true);
}

object HelloWorldScala with Application {
   val win = new MyWindow2
}


The actionPerformed method is here defined within the PrintAction class, and then that class is mixed into the new button class.

I've also come to learn that Scala does in fact support anonymous inner classes, so the first Scaa example I gave can be more directly translated from the Java, and can better demonstrated the brevity the language makes possible.

code:
import javax.swing._;
import java.awt.event._;

class MyWindow2 extends JFrame("Hello World") {
   import JFrame._;

   setDefaultLookAndFeelDecorated(true);

   setDefaultCloseOperation(EXIT_ON_CLOSE);

   val button = new JButton("Hello world");
   button addActionListener new ActionListener {
      def actionPerformed(e : ActionEvent) =
         Console println "Hello world"
   };
   getContentPane() add button;

   pack;
   setSize(400, 300);
   setVisible(true);
}

object HelloWorldScala with Application {
   val win = new MyWindow2
}

Author:  wtd [ Tue Dec 06, 2005 1:51 pm ]
Post subject: 

In the previous post I had this code:

code:
val button = new JButton("Hello world");
button addActionListener new ActionListener {
   def actionPerformed(e : ActionEvent) =
      Console println "Hello world"
};
getContentPane() add button


That's all well and good, and it's a pretty straightforward transition from Java, but with somewhat prettier syntax. Let's diverge a little bit further.

code:
getContentPane() add new JButton("Hello world") with ActionListener {
   addActionListener(this);

   def actionPerformed(e : ActionEvent) =
      Console println "Hello world"
};

Author:  wtd [ Tue Dec 06, 2005 8:16 pm ]
Post subject: 

New app.

Java:

code:
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;

public class JTest {
   public static void main(String[] args) {
      JMyWindow win = new JMyWindow();
   }
}

class JMyWindow extends JFrame {
   private final JTextField inputField;
   private final JSpinner   numberOfCopies;
   private final JTextArea  displayArea;

   public JMyWindow() {
      super("JTest");

      setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

      inputField = new JTextField(50);
      numberOfCopies = new JSpinner(new SpinnerNumberModel(1, 1, 256, 1));
      displayArea = new JTextArea();

      inputField.addActionListener(new ActionListener() {
         public void actionPerformed(ActionEvent e) {
            String inputText = inputField.getText();
            int numCopies = ((Integer)numberOfCopies.getValue()).intValue();

            for (int i = 0; i < numCopies; ++i) {
               displayArea.append(inputField.getText());
            }

            displayArea.append("\n");
         }
      });

      displayArea.setEditable(false);
      displayArea.setLineWrap(true);

      Container contentPane = getContentPane();
      contentPane.setLayout(new GridBagLayout());

      GridBagConstraints c = new GridBagConstraints();

      c.gridx = 0;
      c.gridy = 0;
      c.insets = new Insets(2, 2, 2, 2);
      c.fill = GridBagConstraints.BOTH;
      c.weightx = 10.0;
      contentPane.add(inputField, c);

      c.gridx = 1;
      c.weightx = 0.0;
      contentPane.add(new JLabel("Copies"), c);

      c.gridx = 2;
      c.weightx = 0.0;
      contentPane.add(numberOfCopies, c);

      c.gridx = 0;
      c.gridy = 1;
      c.gridwidth = 3;
      c.weightx = 10.0;
      c.weighty = 10.0;
      contentPane.add(new JScrollPane(displayArea), c);

      pack();
      setSize(300, 400);
      setVisible(true);
   }
}


Scala:

code:
import javax.swing._;
import java.awt._;
import java.awt.event._;

object STest with Application {
   val win = new SMyWindow
}

class SMyWindow extends JFrame("STest") {
   setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

   private val inputField = new JTextField with ActionListener {
      addActionListener(this);
   
      def actionPerformed(e : ActionEvent) = {
         val inputText = getText();
         val numCopies = numberOfCopies.getValue().asInstanceOf[java.lang.Integer].intValue();
         
         for (val i <- Iterator.range(0, numCopies))
            displayArea append inputText;
         
         displayArea append "\n"
      }
   };
   private val numberOfCopies = new JSpinner(new SpinnerNumberModel(1, 1, 256, 1));
   private val displayArea = new JTextArea;
   
   displayArea setEditable false;
   displayArea setLineWrap true;
   
   private val contentPane = getContentPane();
   contentPane setLayout new GridBagLayout;

   private val c = new GridBagConstraints;

   c.gridx = 0;
   c.gridy = 0;
   c.insets = new Insets(2, 2, 2, 2);
   c.fill = GridBagConstraints.BOTH;
   c.weightx = 10.0;
   contentPane.add(inputField, c);

   c.gridx = 1;
   c.weightx = 0.0;
   contentPane.add(new JLabel("Copies"), c);

   c.gridx = 2;
   c.weightx = 0.0;
   contentPane.add(numberOfCopies, c);

   c.gridx = 0;
   c.gridy = 1;
   c.gridwidth = 3;
   c.weightx = 10.0;
   c.weighty = 10.0;
   contentPane.add(new JScrollPane(displayArea), c);

   pack;
   setSize(300, 400);
   setVisible(true);
}

Author:  wtd [ Thu Dec 08, 2005 4:34 pm ]
Post subject: 

For a possibly fairer comparison, and an example of using Java code within Scala seamlessly, I created the layout for my app in Java, but left actions for a superclass.

code:
package myui;

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

public class JMyWindow3UI extends JFrame {
   protected final JTextField inputField;
   protected final JSpinner   numberOfCopies;
   protected final JTextArea  displayArea;

   protected final JButton cut;
   protected final JButton copy;
   protected final JButton paste;

   public JMyWindow3UI() {
      super("JTest");

      setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

      inputField = new JTextField(50);
      numberOfCopies = new JSpinner(new SpinnerNumberModel(1, 1, 256, 1));
      displayArea = new JTextArea();
      cut   = new JButton("Cut");
      copy  = new JButton("Copy");
      paste = new JButton("Paste");

      displayArea.setLineWrap(true);

      Container contentPane = getContentPane();
      contentPane.setLayout(new GridBagLayout());

      JSplitPane splitPane = new JSplitPane(JSplitPane.VERTICAL_SPLIT, true);
      splitPane.setOneTouchExpandable(true);
      splitPane.setResizeWeight(1.0);

      JPanel cutCopyPasteRow = new JPanel();
      cutCopyPasteRow.setLayout(new BoxLayout(cutCopyPasteRow, BoxLayout.X_AXIS));
      cutCopyPasteRow.add(cut);
      cutCopyPasteRow.add(copy);
      cutCopyPasteRow.add(paste);

      splitPane.setTopComponent(new JScrollPane(displayArea));
      splitPane.setBottomComponent(cutCopyPasteRow);

      GridBagConstraints c = new GridBagConstraints();

      c.gridx = 0;
      c.gridy = 0;
      c.insets = new Insets(2, 2, 2, 2);
      c.fill = GridBagConstraints.BOTH;
      c.weightx = 10.0;
      contentPane.add(inputField, c);

      c.gridx = 1;
      c.weightx = 0.0;
      contentPane.add(new JLabel("Copies"), c);

      c.gridx = 2;
      c.weightx = 0.0;
      contentPane.add(numberOfCopies, c);

      c.gridx = 0;
      c.gridy = 1;
      c.gridwidth = 3;
      c.weightx = 10.0;
      c.weighty = 10.0;
      contentPane.add(splitPane, c);

      pack();
   }
}


Then I wrote a Java program that implements actions for the window:

code:
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import myui.*;

public class JTest3 {
   public static void main(String[] args) {
      JMyWindow3 win = new JMyWindow3();
   }
}

class JMyWindow3 extends JMyWindow3UI {
   public JMyWindow3() {
      inputField.addActionListener(new ActionListener() {
         public void actionPerformed(ActionEvent e) {
            String inputText = inputField.getText();
            int numCopies = ((Integer)numberOfCopies.getValue()).intValue();

            for (int i = 0; i < numCopies; ++i) {
               displayArea.append(inputField.getText());
            }

            displayArea.append("\n");
         }
      });

      cut.addActionListener(new ActionListener() {
                   public void actionPerformed(ActionEvent e) {
            displayArea.cut();
         }
      });

      copy.addActionListener(new ActionListener() {
         public void actionPerformed(ActionEvent e) {
            displayArea.copy();
         }
      });

      paste.addActionListener(new ActionListener() {
         public void actionPerformed(ActionEvent e) {
            displayArea.paste();
         }
      });

      setSize(300, 400);
      setVisible(true);
   }
}


And I wrote a Scala program that does the same.

code:
import javax.swing._;
import java.awt._;
import java.awt.event._;
import myui._;

object STest3 with Application {
   val win = new SMyWindow3
}

class SMyWindow3 extends JMyWindow3UI {
   inputField addActionListener new ActionListener {
      def actionPerformed(e : ActionEvent) = {
         val inputText = inputField.getText();
         val numCopies = numberOfCopies.getValue().asInstanceOf[java.lang.Integer].intValue();
         
         for (val i <- Iterator.range(0, numCopies))
            displayArea append inputText;
         
         displayArea append "\n"
      }
   };
   
   cut addActionListener new ActionListener {
     def actionPerformed(e : ActionEvent) = displayArea.cut()
   };
   
   copy addActionListener new ActionListener {
     def actionPerformed(e : ActionEvent) = displayArea.copy()
   };
   
   paste addActionListener new ActionListener {
     def actionPerformed(e : ActionEvent) = displayArea.paste()
   };
   
   setSize(300, 400);
   setVisible(true)
}

Author:  rizzix [ Thu Dec 08, 2005 7:06 pm ]
Post subject: 

Language seems to fit OK with the Java Philosophy. It's nice. But there are somethings I particularly don;t like, although it' just a matter of taste here.. really.. The language seems to be well-designed otherwise.

#1: I don't like replacing the * with _ in the import statements. * is universal as a "match everything" delimiter in many different languages and syntaxes.

#2: The infix notations is odd and can be very confusing. It fails to effectively establish a relationship between the object and the message (speaking in OO terms). Otherwise it's quite acceptable.

#3: I never did like mixins. I know it is a very powerful feature, but I can only imagine how complex OOD can be having it around. Although I believe Scala places particular restrictions on what classes can be mixed-in thus making it a whole lot "safer". Which is a good thing. Yet I still believe it can be improved further, can't it?

#4: I'm not a big fan of two character operators. Specially those "<-" ones. Although I can live with it.

Author:  wtd [ Thu Dec 08, 2005 7:40 pm ]
Post subject: 

rizzix wrote:
#1: I don't like replacing the * with _ in the import statements. * is universal as a "match everything" delimiter in many different languages and syntaxes.


code:
> val a = 2
val a: scala.Int(2) = 2
> Array(1,2,3).elements map a.* foreach Console.println
2
4
6
(): scala.Unit


You can perhaps see how, when * is a valid identifier, and we have (the equivalent of) static imports, that using * as a catch-all for imports is a problem.

Plus, think of Haskell, where _ is the catch-all, throw-away pattern. Scala does pattern matching. Smile

rizzix wrote:
#2: The infix notations is odd and can be very confusing. It fails to effectively establish a relationship between the object and the message (speaking in OO terms). Otherwise it's quite acceptable.


Either syntax is available. Smile

Plus, it offers a nice way to eliminate some syntactic noise, such as in the addition of the action listeners.

Or consider:

code:
myString.equals(otherString)


vs.

code:
myString equals otherString

Author:  rizzix [ Thu Dec 08, 2005 9:53 pm ]
Post subject: 

why not:
code:
myString.equals otherString

Author:  wtd [ Thu Dec 08, 2005 11:04 pm ]
Post subject: 

rizzix wrote:
why not:
code:
myString.equals otherString


I knew you were a closet Ruby fan! Wink

You could do that, but the dilemna with allowing parentheses to be removed are precedence issues. Ruby certainly has this problem, and while it's surmountable, I can't help but think you'd find it rather distasteful.

I've found Scala's precendence rules quite nice.

Author:  rizzix [ Thu Dec 08, 2005 11:21 pm ]
Post subject: 

I'm not well versed in Ruby.. could you elaborate? Btw I was going to suggest
code:
mystring.equals: another_string;
(as in ObjC-ish) Would that resolve the issues in any way?

Author:  wtd [ Thu Dec 08, 2005 11:27 pm ]
Post subject: 

Well, you might have:

code:
foo "hello", "world"


Which is equivalent to:

code:
foo("hello", "world")


And that's all well and good, but...

code:
bar foo "hello", "world", "wooble"


Is this:

code:
bar(foo("hello"), "world", "wooble")


One thing that constructing the Swing comparisons in previous posts did make me envy from the Smalltalk side of things... Smalltalk's semi-colon.

Being able to easily send multiple messages to the same receiver without having to specify the receiver repeatedly.

Author:  rizzix [ Thu Dec 08, 2005 11:48 pm ]
Post subject: 

that's why Obj-C's/Smalltalk's multi-part methods are soo cool! (but implementing that would mean "No Java Compatibility".

edit: wait.. OR.. define a tuple as (T, K, L, ...) then you could have the cake and eat it too Wink where all java methods are methods with tuples.


: