Introduction: Supplemental Material
A simple list
The following code
has a number of variations on a simple
list of integers.
- Read through the variations and study how they differ.
- If some of the later examples don't make sense, don't worry,
try to get a sense of how they work.
// 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:
- This is a very minimal stack program, written only for double's.
- There is hardly anything by way of "sanity checks". For
example, push() could be improved as:
public void push (double value)
{
if (stackTop == 99) {
System.out.println ("Stack overflow - push did not execute");
return;
}
stackTop ++;
stack[stackTop] = value;
}
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:
- We've now used a constructor to initialize:
- Constructors execute only once, at the time an instance is
created with the new operator, as in:
MyStack<String> stack = new MyStack<String> ();
- A constructor must be defined using the class name and no
return value, as in:
public MyStack ()
{
stack = (StackDataType[]) new Object [100];
}
- It is possible to define multiple constructors, and have
any of them used, e.g.,
public MyStack ()
{
stack = (StackDataType[]) new Object [100];
}
public MyStack (int maxSize)
{
stack = (StackDataType[]) new Object [maxSize];
}
// ... later ...
public static void main (String[] argv)
{
MyStack<String> stack = new MyStack<String> (200);
}
- This version is a lot closer to a "professional"
implementation, except for:
- We shouldn't limit the size to 100. Instead, we should grow
as necessary.
- Errors check are needed if a user pops an empty stack or
requests too large an initial stack size.
- We might want more operators, such as a method called
peek() that'll let us look at the stack top without
actually removing it.
- That bug is still there (Did you find it?)
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)