Introduction: Supplemental Material


A simple list

The following code has a number of variations on a simple list of integers.

// Need to import the LinkedList class from java.util:
import java.util.*;

public class ListExample {

    public static void main (String[] argv)
    {
        originalExample ();

        // This variation has tighter, cleaner code.
        variation1 ();

        // These variations use different data structures, or
        // different ways of using the data structures.
        variation2 ();
        variation3 ();
        variation4 ();

        // These variations use different loop options.
        variation5 ();
        variation6 ();
    }
    

    static void originalExample () 
    {
        // Create an empty  list:
        LinkedList<Integer> intList = new LinkedList<Integer> ();

        // Produce data and add each to list.
        for (int i=0; i < 10; i++) {
            // Compute i-th square number.
            int square = i*i;
            intList.add (square);
        }

        // Print.
        for (int i=0; i < 10; i++) {
            int element = intList.get (i);
            System.out.println ("Element #" + i + " = " + element);
        }
    }


    static void variation1 ()
    {
        // Create an empty  list:
        LinkedList<Integer> intList = new LinkedList<Integer> ();

        // Produce data and add each to list.
        for (int i=0; i < 10; i++) {
            intList.add (i*i);
        }

        // Print.
        for (int i=0; i < 10; i++) {
            System.out.println ("Element #" + i + " = " + intList.get(i));
        }
    }


    static void variation2 ()
    {
        // Create an empty list that can hold any kind of object:
        LinkedList<Object> intList = new LinkedList<Object> ();

        for (int i=0; i < 10; i++) {
            // Make an Integer object for each value and put that into the list:
            intList.add ( new Integer(i*i) );
            // This is possible because Integer is derived from Object.
        }

        // Print.
        for (int i=0; i < 10; i++) {
            // To extract, we need to cast:
            Integer I = (Integer) intList.get (i);
            System.out.println ("Element #" + i + " = " + I);
        }
    }
    
    static void variation3 ()
    {
        // Different kind of list:
        ArrayList<Integer> intList = new ArrayList<Integer> ();

        // Produce data and add each to list.
        for (int i=0; i < 10; i++) {
            intList.add (i*i);
        }

        // Print.
        for (int i=0; i < 10; i++) {
            System.out.println ("Element #" + i + " = " + intList.get(i));
        }
    }

    static void variation4 ()
    {
        // Plain old array:
        int[] intList = new int [10];

        for (int i=0; i < 10; i++) {
            intList[i] = i*i;
        }

        // Print.
        for (int i=0; i < 10; i++) {
            System.out.println ("Element #" + i + " = " + intList[i]);
        }
    }


    static void variation5 ()
    {
        // Create an empty  list:
        LinkedList<Integer> intList = new LinkedList<Integer> ();

        // Using a while loop (not preferred):
        int i = 0;
        while (i < 10) {
            intList.add (i*i);
            i ++;
        }

        // Using a do-while (again, a for-loop is preferred):
        i = 0;
        do {
            System.out.println ("Element #" + i + " = " + intList.get(i));
            i ++;
        } while (i < 10);

        // Rule of thumb: use a for-loop if possible. Then prefer while-loops.
        // The do-while should be a last resort.
    }


    static void variation6 ()
    {
        // Create an empty  list:
        LinkedList<Integer> intList = new LinkedList<Integer> ();

        // DON'T: declare a for-loop variable outside the loop.
        int i = 0;
        for (i=0; i < 10; i++) {
            intList.add (i*i);
        }
        
        // This is an alternative, simpler loop for collections of
        // objects. However, it doesn't give you the index variable,
        // which is why the output doesn't have a counter.
        for (Integer I: intList) {
            System.out.println ("Element: " + I);
        }
    }

    static void variation7 ()
    {
        // Create an empty  list:
        LinkedList<Integer> intList = new LinkedList<Integer> ();

        for (int i=0; i < 10; i++) {
            intList.add (i*i);
        }
        
        // Most data structures allow "iteration" through their contents
        // via so-called iterators. For lists, one can use
        // ListIterators, which we get from the list itself.
        ListIterator<Integer> iter = intList.listIterator();
        while (iter.hasNext()) {
            int k = iter.next();
            System.out.println ("Element: " + k);
        }
    }


    static void variation8 ()
    {
        LinkedList<Integer> intList = new LinkedList<Integer> ();

        for (int i=0; i < 10; i++) {
            intList.add (i*i);
        }
        
        // It's possible to mimic the for-loop with an iterator:
        for (ListIterator<Integer> iter = intList.listIterator(); iter.hasNext(); ) {
            // Note shorter version with the "next()" directly in the println argument:
            System.out.println ("Element: " + iter.next());
        }
        // This makes for a strange looking for-loop. Note the lack of the
        // third segment in the for-loop (Can you see why?). 
    }

    static void variation9 ()
    {
        LinkedList<Integer> intList = new LinkedList<Integer> ();

        for (int i=0; i < 10; i++) {
            intList.add (i*i);
        }
        
        // One can also use a more general type of Iterator called,
        // you guessed it, Iterator. 
        Iterator<Integer> iter = intList.iterator();
        while (iter.hasNext()) {
            System.out.println ("Element: " + iter.next());
        }
    }

}


Stack

This example and its variations are about stacks:

import java.util.*;

public class StackExample {

    public static void main (String[] argv)
    {
        // This example uses String's.
        originalExample ();

        // This one uses numbers.
        exampleWithIntegers ();

        // We'll implement our own stack.
        stackInternalsExample ();
    }
    
    static void originalExample ()
    {
        // The Java library has a Stack data structure. Here, we'll
        // make a stack to hold strings.
        Stack<String> toDoList = new Stack<String> ();

        // Add some strings.
        toDoList.push ("Pay bills");
        toDoList.push ("Clean room");
        toDoList.push ("Do homework");
        toDoList.push ("See movie");
        toDoList.push ("Hang out");
        
        // Print.
        System.out.println ("Priorities: ");
        while (! toDoList.isEmpty() ) {
            String nextPriority = toDoList.pop ();
            System.out.println (" " + nextPriority);
        }
    }
    

    static void exampleWithIntegers ()
    {
        // Now use Integer instead of String.
        // Note: we don't declare it as Stack<int> but Stack<Integer>,
        // because Integer's are objects while int's are not. Yet,
        // Java allows using these structures with int's (through
        // a feature called autoboxing).
        Stack<Integer> intStack = new Stack<Integer> ();

        // Add some values.
        intStack.push (1);
        intStack.push (2);
        intStack.push (3);
        
        // Print.
        System.out.println ("Extracting from intStack: ");
        while (! intStack.isEmpty() ) {
            System.out.println ( " " + intStack.pop() );
        }
    }
    

    // We'll use a simple array as the underlying structure.
    // This stack is for real numbers (double's).
    static double[] myStack;

    // This will tell us where in the array the top-element is:
    static int stackTop;


    // A method to get the array set up.

    static void initializeStack ()
    {
        myStack = new double [100];
        stackTop = -1;
    }
    

    // push() takes a double.
    
    static void push (double value)
    {
        stackTop ++;
        myStack[stackTop] = value;
    }


    // pop() returns a double.

    static double pop ()
    {
        double value = myStack[stackTop];
        stackTop --;
        return value;
    }
    

    // See if it's empty.

    static boolean isEmpty ()
    {
        if (stackTop < 0) {
            return true;
        }
        else {
            return false;
        }
    }
    

    static void stackInternalsExample ()
    {
        // Similar to stack creation.
        initializeStack ();

        // Put stuff in.
        push (3.141);
        push (2.718);
        push (1.618);
        
        // Like before, extract using pop()
        while (! isEmpty() ) {
            double value = pop ();
            System.out.println ("Removed: " + value);
        }
        
    }
    
}

Next, as a preview of things to come, let's look at an implementation of a stack as an object:

// This object will implement the stack.

class MyStack {

    // The actual structure used will be an array.
    double[] stack;

    // To keep track of the top:
    int stackTop;
    

    // A method to initialize. We'll assume no more than 100 elements.

    public void initialize ()
    {
        stack = new double [100];
    }
    

    // Push and pop as usual.

    public void push (double value)
    {
        stackTop ++;
        stack[stackTop] = value;
    }
    
    public double pop ()
    {
        double value = stack[stackTop];
        stackTop --;
        return value;
    }
    
    public boolean isEmpty ()
    {
        if (stackTop < 0) {
            return true;
        }
        else {
            return false;
        }
    }

}


// This class has the same name as the file and has main().

public class StackExample2 {

    public static void main (String[] argv) 
    {
        // Create an instance of the MyStack class.
        MyStack stack = new MyStack ();

        // Initialize before using.
        stack.initialize ();
        
        // Dump stuff on stack.
        stack.push (3.141);
        stack.push (2.718);
        stack.push (1.618);
        
        // Print while extracting in order.
        while (! stack.isEmpty ()) {
            System.out.println ("Removed " + stack.pop());
        }
        
    }
    
}
Note:

Exercise 1: Compile and execute the above program. Then fix the bug in the program.

Exercise 2: Let's look again at the code in pop()

    public double pop ()
    {
        double value = stack[stackTop];
        stackTop --;
        return value;
    }
  
Can you re-write this to use only two statements? Only one statement?

As a further preview, let's examine a stack implementation that can store objects:


class MyStack {

    // This is now an array of Object's.
    Object[] stack;

    int stackTop;
    

    public void initialize ()
    {
        stack = new Object [100];
    }
    

    // Note: the parameter is now of type Object:

    public void push (Object value)
    {
        // A shorter version of push using the pre-increment operator.
        stack[++stackTop] = value;
    }
    

    // Note: the return value is now of type Object:

    public Object pop ()
    {
        Object value = stack[stackTop];
        stackTop --;
        return value;
    }
    
    public boolean isEmpty ()
    {
        if (stackTop < 0) {
            return true;
        }
        else {
            return false;
        }
    }

}


public class StackExample3 {

    public static void main (String[] argv) 
    {
        MyStack stack = new MyStack ();

        stack.initialize ();
        
        // Dump strings on stack. A String is an Object, so
        // it's OK to put strings.
        stack.push ("Alice");
        stack.push ("Bob");
        stack.push ("Chen");
        
        while (! stack.isEmpty ()) {
            // Notice the cast required from Object to String.
            String name = (String) stack.pop ();
            System.out.println ("Removed " + name);
        }
        
    }
    
}
Note: this has the same bug as before.

Next, let's look at the code for creating a stack that can store "generic" objects:

// This is how a class is defined to take in objects of
// so-called generic types. That is, the class definition
// uses a generic type (that we call StackDataType). At compile time,
// MyStack can be compiled without knowing what types will actually 
// be used. 

class MyStack <StackDataType> {

    // This is now an array of the generic type.
    StackDataType [] stack;

    int stackTop;
    
    public void initialize ()
    {
        // Arrays of generic types cannot be instantiated directly
        // in Java. This is the workaround.
        stack = (StackDataType[]) new Object [100];
    }
    

    // Note: the parameter is now of the generic type.

    public void push (StackDataType value)
    {
        stack[++stackTop] = value;
    }
    

    // Note: the return value is now of the generic type.

    public StackDataType pop ()
    {
        StackDataType value = stack[stackTop];
        stackTop --;
        return value;
    }
    
    public boolean isEmpty ()
    {
        if (stackTop < 0) {
            return true;
        }
        else {
            return false;
        }
    }

}


public class StackExample4 {

    public static void main (String[] argv) 
    {
        // Now MyStack can be defined to accept only String's.
        MyStack<String> stack = new MyStack<String> ();
        stack.initialize ();

        stack.push ("Alice");
        stack.push ("Bob");
        stack.push ("Chen");
        
        while (! stack.isEmpty ()) {
            // No cast required.
            String name = stack.pop ();
            System.out.println ("Removed " + name);
        }
        
    }
    
}

Finally, remember how Java's own Stack didn't need initialization? Let's clean up our code to perform initialization on its own:

class MyStack <StackDataType> {

    StackDataType [] stack;

    int stackTop;
    
    
    // Define a constructor to initialize.
    // Note: a constructor has the same name as the class and no 
    // return type.

    public MyStack ()
    {
        stack = (StackDataType[]) new Object [100];
    }
    

    public void push (StackDataType value)
    {
        stack[++stackTop] = value;
    }
    

    public StackDataType pop ()
    {
        StackDataType value = stack[stackTop];
        stackTop --;
        return value;
    }
    

    public boolean isEmpty ()
    {
        if (stackTop < 0) {
            return true;
        }
        else {
            return false;
        }
    }

}


public class StackExample5 {

    public static void main (String[] argv) 
    {
        // Now MyStack can be defined to accept only String's.
        MyStack<String> stack = new MyStack<String> ();

        // Don't need to explicitly initialize.

        stack.push ("Alice");
        stack.push ("Bob");
        stack.push ("Chen");
        
        while (! stack.isEmpty ()) {
            // No cast required.
            String name = stack.pop ();
            System.out.println ("Removed " + name);
        }
        
    }
    
}
Note:


GUI's in Java

Let's go back to the image rendering example and learn a little about how simple GUI's work in Java.

To start, let's do HelloWorld as a GUI:

// We need to import some GUI-related libraries:
import java.awt.*;
import javax.swing.*;


// This is a small class (object) where drawing/image-rendition is done.
// It must extend or "subclass" JPanel. JPanel is itself a class in 
// the javax.swing library. 

class HelloPanel extends JPanel {

    // We override the paintComponent method.

    public void paintComponent (Graphics g)
    {
        // This is where we "do our thing"
        g.drawString ("Hello World!", 50, 50);

        // We could do a lot more drawing, image-rendering etc.
    }

}


// This is an object that creates the outer frame (window, really)
// inside of which the drawing panel is placed. That's just the
// way Java does it.

class HelloFrame extends JFrame {

    public HelloFrame ()
    {
        // Set frame parameters.
	this.setSize (400, 400);
	this.setTitle ("Hello World");

        // Create the panel and place inside frame.
	HelloPanel panel = new HelloPanel ();
	Container cPane = this.getContentPane();
	cPane.add (panel);

        // Show the frame.
	this.setVisible (true);
    }
}


public class HelloWorldGUI {
    public static void main (String[] argv)
    {
        // Bring up the frame, which does everything else.
	HelloFrame h = new HelloFrame ();
    }

}

So, how would you go about doing simple drawing?

  • First, let's examine the basic structure of the overall program:
    import java.awt.*;
    import javax.swing.*;
    
    
    // Define your own class to extend JPanel
    
    class HelloPanel extends JPanel {
    
        // We override the paintComponent method.
    
        public void paintComponent (Graphics g)
        {
            // This is where you issue drawing commands
        }
    
    }
    
    
    // Define your own class that extends JFrame. 
    // You can pretty much copy the code below, except for the minor
    // changes required to accomodate your own panel (what you named it etc).
    
    
    class HelloFrame extends JFrame {
    
        public HelloFrame ()
        {
            // Similar ... 
        }
    }
    
    
    public class HelloWorldGUI {
        public static void main (String[] argv)
        {
            // This is where you create an instance of your frame 
    	HelloFrame h = new HelloFrame ();
        }
    
    }
    

  • Thus, the steps are:
    • Create a panel class (that extends JPanel) with a paintComponent() method in which you do the drawing/rendering.
    • Create a frame class (that extends JFrame) to hold the panel.
    • Make an instance of your frame class. This fires up the GUI.

  • The actual drawing/rendering:
    • This occurs via calling methods in the Graphics class in the Java library.
    • An instance of that class is already given to you as a parameter that comes in to your paintComponent() method.
    • The Graphics class has all kinds methods for drawing (e.g., drawing rectangles, circles, polygons etc).
    • It turns out that the Graphics instance passed to you can be converted to a Graphics2D instance, which is even more powerful for drawing:
      class HelloPanel extends JPanel {
      
          public void paintComponent (Graphics g)
          {
              // Cast into Graphics2D instance
              Graphics2D g2 = (Graphics2D) g;
      
              // ... use more powerful methods in Graphics2D ...
              
          }
      
      }
      

  • If you've never done GUI's in Java before:
    • You don't really need to understand the details, but merely structure your code as above and use methods in Graphics.
    • You can learn about GUI's as you go along. Other than understanding Java's unusual conventions, there's not much to it.
    • For a deep understanding of how it all works, you'll have to know the in's and out's of objects. See the advanced Java material for that.

Exercise 3: Go to the Java API and look over the methods in the Graphics class. Then write a GUI to draw a filled black square, like this:




© 2006-2020, Rahul Simha & James Taylor (revised 2020)