Introduction to Computer Graphics, Section A.1 -- The Java Programming Language [ Next Section | Appendix Index | Main Index ] Subsections Basic Language Structure Objects and Data Structures Windows and Events Section A.1 The Java Programming Language Java is taught as a first programming language in many college and high school computer science programs. It is a large and complex language, with features that make it suitable for large and complex programming projects. Those features can make it seem a little verbose and overly strict, but they also make it possible for programming environments to provide excellent support for writing and debugging programs. If you are going to write Java code, you should consider using a full-featured programming environment such as Eclipse (https://eclipse.org/). Subsection 3.6.2 explains how to set up Eclipse for programming with JOGL, the Java API for OpenGL. This book comes with several "starter" programs for writing graphical Java programs, such as java2d/EventsStarter.java for Java Graphics2D and jogl/JoglStarter.java for JOGL. Although this section doesn't tell you enough to let you write Java programs from scratch, it might have enough information to let you "fill in the blanks" in the starter programs and modify other sample programs that come with the book. If you want to learn Java in more detail, you can consider my free on-line Java textbook, http://math.hws.edu/javanotes. Java has had several 2D graphics APIs: AWT, Swing, and JavaFX. Swing is built on top of the AWT, while JavaFX is a completely new API. JavaFX is not used in this textbook, but you will see references here both to Swing and to the AWT. A.1.1 Basic Language Structure Java is object-oriented. A Java program is made up of classes, which can contain variable definitions and method definitions. ("Method" is the object-oriented term for function or subroutine.) A class is defined in its own file, whose name must match the name of the class: If the class is named "MyClass", then the name of the file must be MyClass.java. Classes can also occur as nested classes within other classes; a nested class, of course, doesn't get its own file. The basic syntax for defining a class is public class MyClass {
.
. // Variable, method, and nested class definitions.
.
} There are variations on this syntax. For example, to define a class as a subclass of an existing class, you need to say that the new class "extends" an the existing class: public class MyClass extends ExistingClass { ... A class in Java can only extend one class. However, in addition to or instead of extending a class, a new class can also implement one or more "interfaces." An interface in Java specifies some methods that must be defined in every class that implements the interface. With all of these options, a class definition might look something like this: public class MyGUI extends JPanel implements KeyListener, MouseListener { ... In fact, a class exactly like this one might be used in a GUI program. A class can contain a main() method, and one of the classes that make up a program must contain such a method. The main() method is where program execution begins. It has one parameter, of type String[], representing an array of command-line arguments. There is a confusing distinction in Java between static and non-static variables and methods, which we can mostly ignore here. The main() method is static. Often, in a graphical program, main is the only thing that is static, so the distinction will not be very important for us. In a GUI program, the main method usually just creates a window and makes it visible on the screen; after that, the window takes care of itself. A non-static method definition in a class actually defines a method for each object that is created from that class. Inside the method definition, the special variable this can be used to refer to the object of which the method is a part. You might be familiar with the same special variable in JavaScript. However, unlike in JavaScript, the use of this is optional in Java, so that a variable that is part of the same object might be referred to either as x or this.x, and a method could be called from within the same class as doSomething() or this.doSomething(). Variables, methods and nested classes can be marked as private, public, or protected. Private things can only be used in the class where they are defined. Public things can be accessed from anywhere. Protected things can be accessed in the same class and in subclasses of that class. The programs in this book use a main class that defines the window where the graphical display will be seen. That class also contains the main() routine. (This is not particularly good style, but it works well for small programs.) In some cases, the program depends on other classes that I have written; the files for those classes should be in the same folder as the file that defines the main class. The programs can then be compiled on the command line, working in that folder, with the command javac *.java To run the program whose main class is MyClass, use java MyClass However, programs that use JOGL require some extra options in these commands. What you need to know is explained in Subsection 3.6.2. (The Eclipse IDE has its own simple commands for running a program.) There are many standard classes that are available for use in programs. A few of the standard classes, such as Math and System, are automatically available to any program. Others have to be "imported" into a source code file before they can be used in that file. A class can be part of a package, which is a collection of classes. For example, class Graphics2D is defined in the package java.awt. This class can be imported into a source code file by adding the line import java.awt.Graphics2D; to the beginning of the file, before the definition of the class. Alternatively, all of the classes in package java.awt can be imported with import java.awt.*; It is possible to put your own classes into packages, but that adds some complications when compiling and using them. My sample programs in this book are not defined in named packages. Officially, they are said to be in the "default package." Recent versions of Java also have "modules," which are collections of packages. Again, using modules complicates things, and they are not used in this textbook. Java is a strongly typed language. Every variable has a type, and it can only hold values of that type. Every variable must be declared, and the declaration specifies the type of the variable. The declaration can include an initial value. For example, String name; // Declares name as a variable whose value must be a String.
int x = 17; // x is a variable whose value must an int, with initial value 17.
Graphics2D g; // g is a variable whose value is an object of type Graphics2D. Java has eight "primitive" types, whose values are not objects: int, long, short, byte, double, float, char, and boolean. The first four are integer types with different numbers of bits. The real number types are double and float. A constant such as 3.7 is of type double. To get a constant of type float, you need to add an 'F': 3.7F. (This comes up when programming in JOGL, where some methods require parameters of type float.) Constant char values are enclosed in single quotes; for example, 'A' and '%'. Double quotes are used for strings, which are not primitive values in Java. In addition to the eight primitive types, any class defines a type. If the type of a variable is a class, then the possible values of the variable are objects belonging to that class. An interface also defines a type, whose possible values are objects that implement the interface. An object, unlike a primitive value, contains variables and methods. For example, Point is a class. An object of type Point contains int variables x and y. A String is an object, and it contains several methods for working with the string, including one named length() that returns its length and another named charAt(i) that returns the i-th character in the string. Variables and methods in an object are always accessed using the "." period operator: If pt is a variable of type Point, referring to an object of type Point, then pt.x and pt.y are names for the instance variables in that object. If str is a variable of type String, then str.length() and str.charAt(i) are methods in the String object to which str refers. A method definition specifies the type of value that is returned by the method and a type for each of its parameters. It is usually marked as being public or private. Here is an example: public int countChars( String str, char ch ) {
int count = 0;
for ( int i = 0; i < str.length(); i++) {
if ( str.charAt(i) == ch )
count++;
}
return count;
} Here, countChars is the name of the method. It takes two parameters of type String and char, and it returns a value of type int. For a method that does not return a value, the return type (int in the above example) is specified as void. A method in Java can be used throughout the class where it is defined, even if the definition comes after the point where it is used. (This is in contrast to C, where functions must be declared before they are used, but similar to JavaScript.) The same is true for global variables, which are declared outside any method. All programming code, such as assignment statements and control structures, must be inside method definitions. Java has the same set of basic control structures as C and JavaScript: if statements, while and do..while loops, for loops, and switch statements all take essentially the same form in the three languages. Assignment statements are also the same. Similarly, the three languages have pretty much the same set of operators, including the basic arithmetic operators (+, −, * and /), the increment (++) and decrement (--) operators, the logical operators (||, &&, and !), the ternary operator (?:), and the bitwise operators (such as & and |). A peculiarity of Java arithmetic, as in C, is that the division operator, /, when applied to integer operands produces an integer result. So, 18/5 is 3 and 1/10 is 0. The + operator can be used to concatenate strings, so that "Hello" + "World" has the value "HelloWorld". If just one of the operands of + is a string, then the other operand is automatically converted into a string. Java's standard functions are defined in classes. For example, the mathematical functions include Math.sin(x), Math.cos(x), Math.sqrt(x), and Math.pow(x,y) for raising x to the power y. Math.random() returns a random number in the range 0.0 to 1.0, including 0.0 but not including 1.0. The method System.out.println(str) outputs a string to the command line. In graphical programs, System.out.println is useful mainly for debugging. To output more than one item, use string concatenation: System.out.println("The values are x = " + x + " and y = " + y); There is also a formatted output method, System.out.printf, which is similar to C's printf function. A.1.2 Objects and Data Structures In addition to the primitive types, Java has "object types" that represent values that are objects. A variable of object type doesn't hold an object; it can only hold a pointer to an object. (Sometimes it's said that Java doen't use pointers, but it's more correct to say that it forces you to use them.) The name of a class or of an interface is an object type. Objects are created from classes using the new operator. For example, Point pt; // Declare a variable of type Point.
pt = new Point( 100, 200 ); // Create an object of type Point. Here, the class is Point, which also acts as a type that can be used to create variables. A variable of type Point can refer to an object belonging to the class Point or to any subclass of that class. The expression new Point(100,200) in the assignment statement calls a special kind of routine in the Point class that is known as a constructor. The purpose of a constructor is to initialize an object. In this case, the parameters to the constructor, 100 and 200, become the values of the variables pt.x and pt.y in the new object. The effect of the above code is that the value of pt is a pointer to the newly created object. We say that pt "refers" to that object. Instead of referring to an object, pt could have the special value null. When the value of a variable is null, the variable does not refer to any object. If the value of pt is null, then the variables pt.x and pt.y don't exist, and an attempt to use them is an error. The error is called a NullPointerException. Strings, by the way, are special objects. They are not not created with the new operator. Instead, a string is created as a literal value, enclosed in double quotes. For example String greeting = "Hello World!"; Arrays are also special objects. Any type in Java defines an array type. An array type is an object type. From the type int, for example, we get the array type int[]. From String and Point, we get the types String[] and Point[]. The value of a variable of type int[] is an array of ints (or the value can be null). The value of a variable of type Point[] is an array of Points. Arrays can be created with a version of the new operator: int[] intList; // Declare a variable that can refer to any array of ints.
intList = new int[100]; // Create an array that can hold 100 ints. An array has a fixed length that is set at the time it is created and cannot be changed. If intList refers to an array, then the length of that array is given by the read-only variable intList.length. The elements of the array are intList[0], intList[1], and so on. An attempt to use intList[i] where i is outside the range from 0 to intList.length − 1 generates an error of type ArrayIndexOutOfBoundsException. The initial value for array elements is "binary zero"; that is, 0 for numeric values, false for boolean, and null for objects. An array can be created and initialized to hold arbitrary values at the time it is created using the syntax intList = new int[] {2, 3, 5, 7, 11, 13, 15, 17, 19}; This version of the new operator creates an array of ints of length nine that initially holds the nine specified values. If the initialization of an array is done as part of a variable declaration, then only the list of values, enclosed between { and }, is required: String[] commands = { "New", "Open", "Close", "Save", "Save As" }; Java comes with several standard classes that define common data structures, including linked lists, stacks, queues, trees, and hash tables, which are defined by classes in the package java.util. The classes define "generic" or "parameterized" types that will work for a variety of element types. For example, an object of type LinkedList
is a list of items of type String. Unfortunately, it is not possible to use these classes with the primitive types;. There is no "linked list of int". (However, you can have LinkedList, where an object of type Integer is a "wrapper" for a primitive int value.) Perhaps the most commonly used of the generic data structures is the ArrayList. Like an array, an ArrayList contains a numbered sequence of items. However, an ArrayList can grow and shrink. For example, to create an ArrayList that can hold objects of type Point: ArrayList pointList;
pointList = new ArrayList(); This creates an initially empty list. The method pointList.add(pt) can be used to add a Point to the end of the list, increasing its length by one. The value of pointList.size() is the number of items currently in the list. The method pointList.get(i) returns the i-th element of the list, and pointList.set(i,pt) replaces the i-th element with pt. Similarly, pointList.remove(i) removes the i-th element, decreasing the length of the list by one. For all of these methods, an error occurs if i is not in the range from 0 to pointList.size() −1. It is also possible to build linked data structures directly, remembering that the value of a variable whose type is given by a class is either null or is a pointer to an object. For example, a linked list of integer values can be created using objects defined by the simple class class ListNode {
int item; // One of the integers in the list
ListNode next; // Pointer to next node in list, or null for end-of-list.
} A more useful data structure for this course is a scene graph, like the ones discussed in Subsection 2.4.2 and implemented in the sample program java2d/SceneGraphAPI2D.java. In that API, a node in a scene graph is represented by an object belonging to the class SceneGraphNode or to a subclass of that class. For example, a CompoundObject represents a graphical object made up of subobjects. It needs to store pointers to all of its subobjects. They can conveniently be stored in an ArrayList. Then drawing a CompoundObject just means drawing its subobjects. The class can be defined as follows: class CompoundObject extends SceneGraphNode {
ArrayList subobjects = new ArrayList();
CompoundObject add(SceneGraphNode node) {
subobjects.add(node);
return this;
}
void doDraw(Graphics2D g) {
for (SceneGraphNode node : subobjects)
node.draw(g);
}
} (The for loop in this class is one that is specific to Java. It iterates automatically through all of the objects in the list.) A.1.3 Windows and Events Java comes with a set of standard classes for working with windows and events. I will mention some of the most common. I will try to tell you enough to understand and work with the sample programs in this book. Writing programs from scratch will require more in-depth knowledge. All of the classes that I discuss are part of the Swing GUI API, and are contained in the packages java.awt, javax.swing, and java.awt.event. Many of my programs begin with the following import directives to make the classes that they contain available: import java.awt.*;
import java.awt.event.*;
import javax.swing.*; A window can be represented by an object of class JFrame. A JFrame can hold a menu bar and a large content area known as its "content pane." The content pane often belongs to a subclass of JPanel. A JPanel can be used in two ways: as a drawing surface or as a container for other components such as buttons, text input boxes, and nested panels. When a panel is to be used as a drawing surface, it is defined by a subclass that includes a paintComponent() method. The paintComponent method is called when the panel first appears on the screen and when it needs to be redrawn. Its task is to completely redraw the panel. It has a parameter of type Graphics, which is the graphics context that is used to do the drawing. It takes the form public void paintComponent(Graphics g) { ... The paintComponent method is discussed further in Section 2.5. In general, all drawing should be done in this method, and paintComponent should only be called by the system. When redrawing is necessary, a call to paintComponent can be triggered by calling the panel's repaint() method. (For OpenGL programming in Chapter 3 and Chapter 4, I use a GLJPanel, which is a subclass of JPanel. In that case, the drawing is done in a display() method, instead of in paintComponent, but you can still call repaint() to trigger a redraw. See Subsection 3.6.2.) When a panel is to be used as a container for other components, those components will usually be created and added to the panel in a constructor, a special routine that is called to initialize an object when the object is created by the new operator. A constructor routine can be recognized by the fact that it has the same name as the class that contains it, and it has no return type. The sizes and positions of the components in a panel will generally be set by a "layout manager," which is an object that implements some policy for laying out the components in a container. For example, a BorderLayout is a layout manager that puts one large component in the center of the panel, with space for up to four additional components on the north, south, east, and west edges of the panel. And a GridLayout lays out components in rows and columns, with all components having the same size. In addition to nested panels, possible component types include typical interface components such as JButton, JCheckBox, and JRadioButton. You will see examples of all of these things in the sample programs. A GUI program must be able to respond to events, including low-level events such as those generated when the user manipulates a mouse or keyboard, and high level events such as those generated when the user selects an item from a menu or clicks on a button. To respond to events, a program defines event-handling methods, which will be called when the event occurs. In Java, an object that includes event-handling methods is said to "listen" for those events. For example, the basic mouse-event handlers are specified by an interface named MouseListener. An object that implements this interface can respond to mouse events. It must define methods such as mousePressed(), which will be called when the user presses a button on the mouse. MouseListener defines five methods in all. A class that implements the interface would take the form class MouseHandler implements MouseListerner {
public void mousePressed(MouseEvent evt) {
.
. // respond when the user presses a mouse button
.
}
public void mouseClicked(MouseEvent evt) { }
public void mouseReleased(MouseEvent evt) { }
public void mouseEntered(MouseEvent evt) { }
public void mouseExited(MouseEvent evt) { }
} The MouseEvent parameter in each of these methods is an object that will contain information about the event. For example, evt.getX() and evt.getY() can be called in the event-handler method to find the x and y coordinates of the mouse. An event is usually associated with some component, called the "target" of the event. For example, a mouse press event is associated with the component that contained the mouse when the user pressed the mouse button. A button click event is associated with the button that was clicked. To receive events from a component, a program must register an event-listening object with that component. For example, if you want to respond to mouse clicks on a JPanel named panel, you need to create a MouseListener object and register it with the panel: MouseHandler handler = new MouseHandler(); // create the listener
panel.addMouseListener(handler); // register it with the panel In many cases, I create a class, often a nested class, to define an event listener that I need. However, any class, can implement an interface, and sometimes I let my main class implement the listener interface: public class MyPanel extends JPanel implements MouseListener { ... Inside such a class, the panel and the listener are the same object, and the special variable this refers to that object. So, to register the panel to listen for mouse events on itself, I would say this.addMouseListener( this ); This statement can be shortened to simply addMouseListener(this). Other event types work similarly to mouse event types. You need an object that implements a listener interface for events of that type, and you need to register that object as a listener with the component that will be the target of the events. The MouseMotionListener interface defines methods that handle events that are generated when the user moves or drags the mouse. It is separate from the MouseListener interface for the sake of efficiency. Responding to a mouse-drag action usually requires an object that acts both as a mouse listener and a mouse motion listener. The KeyListener interface is used for handling keyboard events. An event is generated when the user presses a key and when the user releases a key on the keyboard. Another kind of event is generated when the user types a character. Typing a character such as upper case 'A' would generate several key-pressed and key-released events as well as a character-typed event. The ActionListener interface is used to respond to a variety of user actions. An ActionEvent is generated, for example, when the user clicks a button, selects a command from a menu, or changes the setting of a checkbox. It is also used in one context where the event doesn't come from the user: A Timer is an object that can generate a sequence of ActionEvents at regularly spaced intervals. An ActionListener can respond to those events to implement an animation. See the sample program java2d/AnimationStarter.java to see how its done. Finally, I will note that JOGL uses an event listener of type GLEventListener for working with OpenGL. Its use is explained in Subsection 3.6.2. [ Next Section | Appendix Index | Main Index ]