Multi-dimensional arraysTopFinishing GUIArrays in Java

Arrays in Java

Note that the chapter on arrays from the Java version of our text is available on-line at
http://eventfuljava.cs.williams.edu/chapters/Bruce_chapter14.pdf
It goes into much more detail than we can here.

Java has a class ArrayList<T> that is similar to Grace's lists. However, it also has a more primitive data structure, arrays, that form the building blocks of ArrayList. Here we are going to focus on arrays as it will provide you good intuition on the complexity of list operations. You can look up information about ArrayList is the documentation on Java libraries.

Arrays differ from Grace's lists in several important ways. The most important is that arrays have a fixed size that is determined when the array is created. Another is that the operations on an array are limited. You can ask for an element at a given location, update an element at a location, or ask the array for the number of slots it has. However, more complex operations must be hand-coded by the programmer. Finally, the elements of an array are indexed starting at 0 rather than 1 like Grace.

Array types and expressions in Java use a special syntax. If T is a type then T[] is the type of arrays holding elements of type T. Similarly, a new array of T of size n is constructed by writing new T[n]. Thus we can declare and initialize an array a of double with 20 slots by writing:

    double[] a = new double[20]

We access the elements of an array using a similar notation to refer to individual elements. Thus we can obtain the values of the first three elements of the array a by writing a[0], a[1], and a[2]. (Remember that we start numbering the array elements at 0, not 1.)

We can update the elements using a similar notation:

    a[0] = 7.47

(We note here that Grace also allows programmers to access and update list elements using a similar notation. Where we wrote b.at(5), we could obtain the same value using b[5]. Where we updated a value by writing b.at(3)put(3.12), we could also have written b[3] := 3.12. However, we preferred the more traditional method request style because it made it clear that these accesses were all via method requests.)

Because you must allocate all the space for an array when you create it, very often the number of active elements in an array is smaller than the total number of slots. If a is an array, then a.length will return the total number of slots in the array. Thus if a.length returns 20, then the array has elements a[0] through a[19] (because we start counting at 0, the last slot is always at one less than the length). By the way, notice that there are no open and closed parentheses after the method request of length. That is because length is a public instance variable of the array class. We think that is terrible style (if you want an instance variable to be public then you should write a method that returns its value - in Grace when you declare a variable to be readable, the system automatically generates such a method for you - and if you declare it to be public the system generates both "getter" and "setter" methods.

The fact that length returns the number of slots as opposed to the number in use makes the programmer's task more complicated.

To polish these skills let's look back at the DrawingList program in Grace and the corresponding program DrawingArray in Java. Here is a link to the Grace program. Take a quick look over it to refresh your memory and now we'll take a look at the equivalent Java program.

import java.awt.*;

import javax.swing.*;

import objectdraw.*;

public class Drawing extends WindowController {
   
   // max number of objects to be displayed
   private static final int SIZE = 100;
   private static final int MAX_OBJECTS = 20;
   
   // menus for shape, color, and command
   private JComboBox<String> shapeChoice;
   private JComboBox<String> colorChoice;
   private JComboBox<String> commandChoice;
   
   // Array of objects on screen.
   private DrawableInterface[] shapes = new DrawableInterface[MAX_OBJECTS];
   
   // number of objects on screen
   private int numShapes = 0;
   
   // item currently selected for dragging
   private DrawableInterface selected;
   
   // mouse loc'n when last handled mouse
   private Location lastPoint;
   
   /**
    *  Set up GUI components for program
    */
   public void begin() {
      // create panel to hold choice buttons
      JPanel menuPanel = new JPanel();
   
      // menu for selecting or adding
      commandChoice = new JComboBox<String>();
      commandChoice.addItem("Add new item");
      commandChoice.addItem ("Recolor item");
      commandChoice.addItem("Move item");
      commandChoice.addItem("Delete item");
      menuPanel.add(commandChoice);
   
      // Set up menu for shapes
      shapeChoice = new JComboBox<String>();
      shapeChoice.addItem("Circle");
      shapeChoice.addItem("Square");
      menuPanel.add(shapeChoice);
   
      // Set up menu for colors
      colorChoice = new JComboBox<String>();
      colorChoice.addItem("Red");
      colorChoice.addItem("Green");
      colorChoice.addItem("Blue");
      menuPanel.add(colorChoice);
   
      // Add the panel to screen
      getContentPane().add(menuPanel, BorderLayout.SOUTH);
      validate();
   }
   
   /**
    *  When the user clicks in the canvas, check the settings of the command 
    *  menu to determine what action to take.
    */
   public void onMousePress(Location point) {
      selected = null; // indicate nothing currently selected
      Object buttonLabel = commandChoice.getSelectedItem();
      if (buttonLabel.equals("Add new item")) {
         addNew(point);
      } else if (buttonLabel.equals("Recolor item")) {
         recolorShapeAt (point);
      } else if (buttonLabel.equals("Move item")) {
         selectShapeAt(point);
      } else {
         deleteShapeAt(point);
      }
   }
   
   /**
    * This method implements the "Add new item" command. Add new geometric
    * shape where clicked. Type and color of object is determined by the
    * settings of the color and shape menus.
    */
   private void addNew(Location point) {
      // only add if still room for more objects
      if (numShapes < MAX_OBJECTS) {
         Location centeredLocation = new Location (point.getX() - SIZE/2, 
                                 point.getY() - SIZE/2);
         Object shapeString = shapeChoice.getSelectedItem();
         DrawableInterface newShape;
            
         // create new object to be shape chosen
         if (shapeString.equals("Square")) {
            newShape =
               new FilledRect(centeredLocation, SIZE, SIZE, canvas);
         } else {
            newShape =
               new FilledOval(centeredLocation, SIZE, SIZE, canvas);
         }
            
         newShape.setColor(getSelectedColor());
         shapes[numShapes] = newShape;
         numShapes++;
      }
   }
   
   /**
    * @return the color corresponding to the string selected in the color menu.
    */
   private Color getSelectedColor() {
      Color objectColor; // local variable - color of object
   
      // get color showing in the color menu
      Object colorString = colorChoice.getSelectedItem();
   
      // set objectColor to chosen color
      if (colorString.equals("Red")) {
         objectColor = Color.RED;
      } else if (colorString.equals("Green")) {
         objectColor = Color.GREEN;
      } else {
         objectColor = Color.BLUE;
      }
      return objectColor;
   }

   /**
    * Change the color of the item the user clicks on.
    * This implements the "Recolor Item" command.  
    */
   private void recolorShapeAt(Location point) {
      int selectIndex = getIndexOf(point);

      if (selectIndex != -1) {
         shapes[selectIndex].setColor(getSelectedColor());
      }
   }
   
   /**
    * @param point location of interest
    * @return the index of the last element of shapes containing point
    *         return -1 if no element of shapes contains point
    */
   private int getIndexOf(Location point) {
      // Walk the array until we find the selected shape
      for (int selectIndex = numShapes - 1; selectIndex >= 0; selectIndex--) {
         if (shapes[selectIndex].contains(point)) {
            return selectIndex;
         }
      }
      return -1;
   }
   
   /**
    *  Remove top-most geometric item clicked in.  If didn't click in any 
    *  then don't do anything.
    * @param point location where user clicked.
    */
   private void deleteShapeAt(Location point) {
      int selectIndex = getIndexOf(point);
   
      // if point is in one of the objects, delete it
      if (selectIndex != -1) {
         shapes[selectIndex].removeFromCanvas();
         removeEltWithIndex(selectIndex);
         shapes[numShapes] = null;
      }
   }
   
   /**
    *  remove shapes[index] by moving later elements back.
    * @param index: element to be removed
    */
   private void removeEltWithIndex(int index) {
      for (int objNum = index; objNum < numShapes - 1; objNum++) {
         shapes[objNum] = shapes[objNum + 1];
      }
      numShapes--;
   }
   
   /**
    *  Set select to indicate top-most geometric item clicked in, and 
    *  send it to front of screen.  If didn't click in any then 
    *  set select to null.
    *  Update lastPoint to last place clicked so can drag it
    * @param point: where user clicked
    */
   private void selectShapeAt(Location point) {
      int selectIndex = getIndexOf(point);
   
      // Remember which shape is selected so onMouseDrag can move it.
      if (selectIndex != -1) {
         selected = shapes[selectIndex];
         lastPoint = point;
         
         // Move the selected object to the front of the display
         // and to the end of the array if it is not already there
         if (selectIndex != numShapes-1) {
            selected.sendToFront();
            removeEltWithIndex(selectIndex);
            shapes[numShapes] = selected;
            numShapes++;
         }
      }
   }
   
   /**
    *  If something was selected then drag it and remember where left off
    */
   public void onMouseDrag(Location point) {
      if (selected != null) {
         selected.move(
            point.getX() - lastPoint.getX(),
            point.getY() - lastPoint.getY());
         lastPoint = point;
      }
   }
   
   public static void main(String[] args) {
      new Drawing().startController(400,400);
   }

}

Let's talk through this program. For now, I'm going to skip the GUI component set up, which is handled in the begin method. Instead just trust that it also creates and installs three pop-up menus (called JComboBoxes in Java) at the bottom of the window, below the canvas.

The identifier shapes is declared as an instance variable that holds values of type DrawableInterface, an interface that is satisfied by all of the graphical objects in the objectdraw library. It is initialized to hold 20 objects. When the array is created the 20 slots all hold the value null, which represents an uninitialized value. You can test a value to see if it is null (e.g., shapes[4] == null), but if you send a method request to null, you will get a null pointer error, which generally happens when you forget to initialize a value. (In Grace we would have created an "empty" object that gave appropriate behavior for each method, but in Java, programmers often just fill slots with null. However, you then have to make sure a slot is different from null before using it.)

Other instance variables keep track of the number of shapes on the screen (numShapes), recall the item currently selected for dragging (if any), and the location where the mouse was when the last event was triggered (lastPoint.

IMPORTANT: In Java, array subscripts always start at 0. Thus if there are 10 elements in an array numShapes, then they are referred to as numShapes[0], ... numShapes[9]. Thus if the array currently contains numShapes objects, then a new element added at the end will be put in shapes[numShapes], not shapes[numShapes + 1]

The onMousePress method determines what action is to be taken by checking the item selected in the commandChoice test field. The actual actions taken are the responsibility of the methods addNew. recolorShapeAt, selectShapeAt, and deleteShapeAt.

The method addNew creates a new geometric object on the screen, guided by the values selected in the pop-up menus. Based on what is showing on the shapeChoice menu, it will create a square or circle. It will then set its color according to the value returned by method getSelectedColor(), which returns the color corresponding to the string showing on the colorChoice menu. Once the object has been created and its color set, it is added to the shapes array at the end, and numShapes is incremented.

The method getIndexOf is a helper method (and is hence declared to be private). It determines the index of the top-most element in shapes that contains a point. It is very similar to the corresponding Grace method indexOf. They differ only in the syntax of the for loop.

We now have several private methods to help us write the methods to change color, delete, and move objects. Method getSelectedColor just grabs the text showing on the color menu and using an if-else-if-else returns the corresponding color. We'll talk about getting strings from menus later.

The method getIndexOf is a helper method (and is hence declared to be private). It determines the index of the top-most element in shapes that contains a point. It is very similar to the corresponding Grace method indexOf. They differ only in the syntax of the for loop.

Method recolorShapeAt uses getIndexOf to determine what object was clicked on. If there was on object clicked on then its color is changed. Method deleteShapeAt is similar, but with one twist. The selected element is removed from the canvas, but now we must remove it from the array. Unfortunately, there is no remove method for arrays (in fact, only length is defined). Thus we have to write our won. This is done in method removeEltWithIndex. The idea here is that we start with the element one to the right of the one we wish to remove and shove the elements to the left by one slot. Hence the assignment shapes[objNum] = shapes[objNum + 1]; that goes through all the elements from index to the end. After all the elements have been shifted, we cut back the number of elements by 1.

Method selectShapeAt finds the object selected, moves it to the front of the screen and then moves it in the array to the last element (which makes it the top element in terms of searches). The element is moved to the last element of the array by first removing it from the array (leaving the array one element shorter) and then adding it as the new last element (which is easy) and bumping up numShapes back to the original value.

Method onMouseDrag is as before, except that the name of the method to shift an element is move rather than moveBy.

Hopefully this comparison helped you understand the differences between lists in Grace and arrays in Java. But let's now take a look at how we write some of the methods for lists in Grace that are not available in Java for arrays.

Let's look first at adding a new element to an array. For simplicity, let's assume that we are working with an array seq of type SomeThing and instance variable size keeps track of the number of elements in seq that are in use. We assume that size <= seq.length.

Adding a new element to the end of seq is pretty straightforward:

public void addLast(SomeThing newElt) {
   if (size < seq. length) {
      seq[size] = newElt;
      size++;
   }

However, adding an element to the beginning of a list is much trickier. To add the new element we need to make a space by shifting all of the other elements over by 1.

public void addFirst(SomeThing newElt) {
   if (size < seq. length) {
      // shift over existing elements
      for (int index = size-1; index >= 0; index--) {
         seq[index+1] = seq[index]
      }
      seq[0] = newElt;
      size++;
   }

Exercise Why do we shift the last element to the right first, rather than shifting the element at 0 first?

Exercise Explain in detail what would happen if you made the horrible mistake of replacing the +1 in the assignment statement in the forloop by.

         seq[index++] = seq[index]

Explain why these horrible things happened and write on the board 100 times "I will never use ++ in the middle of an expression!".


Multi-dimensional arraysTopFinishing GUIArrays in Java