Survey
* Your assessment is very important for improving the workof artificial intelligence, which forms the content of this project
* Your assessment is very important for improving the workof artificial intelligence, which forms the content of this project
Modern Programming Concepts in Engineering Exercise Notes Matthias Baitsch October 2008 2 Contents 1 Our Computer Labs 5 2 Fundamental Programming Structures in Java 2.1 The Programming Environment Eclipse . . 2.2 A Minimalistic Java Program . . . . . . . . 2.2.1 Reviewing Hello World . . . . . . . 2.3 Primitive Types . . . . . . . . . . . . . . . 2.4 Variables and Assignments . . . . . . . . . 2.5 Operators . . . . . . . . . . . . . . . . . . 2.6 Methods and Control Structures . . . . . . 2.6.1 Sequential Steps . . . . . . . . . . 2.6.2 Method . . . . . . . . . . . . . . . 2.6.3 Selection . . . . . . . . . . . . . . 2.6.4 Indeterminate Loops . . . . . . . . 2.7 Java Keywords . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9 9 10 11 12 12 13 15 15 15 16 17 19 3 Objects and classes 3.1 Using existing classes . . . . . . . . . . . . . . . . . . . . . . . . . . 3.1.1 Three-dimensional objects . . . . . . . . . . . . . . . . . . . 3.1.2 Another practical example: Plotting a function R2 → R . . . 3.2 Program your own classes . . . . . . . . . . . . . . . . . . . . . . . 3.2.1 A vector class . . . . . . . . . . . . . . . . . . . . . . . . . . 3.2.2 Practical example: Complex numbers and the Mandelbrot set . 3.3 Computer memory, variables and objects . . . . . . . . . . . . . . . . 3.4 Strings in Java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3.5 Understanding Java arrays . . . . . . . . . . . . . . . . . . . . . . . 3.6 Static methods and constants . . . . . . . . . . . . . . . . . . . . . . 3.6.1 Static methods . . . . . . . . . . . . . . . . . . . . . . . . . 3.6.2 Static attributes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 21 21 21 24 25 25 33 37 39 39 41 41 41 4 Inheritance and Polymorphism 4.1 Extending Classes . . . . . . . . . . . . . . . . . . . . . . 4.1.1 Using the Base Class Instead of the Derived Class 4.2 Abstract Classes . . . . . . . . . . . . . . . . . . . . . . . 4.2.1 Polymorphism . . . . . . . . . . . . . . . . . . . 4.2.2 The Three Pillars of Object-Oriented Programming . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 43 43 46 47 49 49 3 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . CONTENTS 4 4.3 4.4 4.2.3 Abstract Classes in Finite Element Programming . . . . . . . . . . . . . . . Practical Example: Sections . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Interfaces . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5 Connecting Objects 5.1 Programming Associations / Aggregation / Composition 5.2 Catalog of Books for a Library . . . . . . . . . . . . . . 5.2.1 Java Implementation of the Classes . . . . . . . 5.3 Truss Structures . . . . . . . . . . . . . . . . . . . . . . 5.3.1 Designing the classes . . . . . . . . . . . . . . . 5.3.2 Assignment: Implementing the Classes in Java . 5.3.3 Reading a Structure from a File . . . . . . . . . 5.3.4 Example for an Object Diagram . . . . . . . . . 6 Programming Graphical User Interfaces 6.1 Introduction . . . . . . . . . . . . . . . . . . . . . . . 6.2 Swing Components . . . . . . . . . . . . . . . . . . . 6.2.1 Using Swing Components . . . . . . . . . . . 6.3 Laying out Components . . . . . . . . . . . . . . . . . 6.3.1 Combining Layout Managers . . . . . . . . . 6.4 Event Handling . . . . . . . . . . . . . . . . . . . . . 6.5 The Model-View-Controller Pattern . . . . . . . . . . 6.5.1 Step 1: Preliminaries . . . . . . . . . . . . . . 6.5.2 Software Design . . . . . . . . . . . . . . . . 6.5.3 Implementation . . . . . . . . . . . . . . . . . 6.5.4 Creating Nodes and Elements Using the Mouse . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 49 50 53 . . . . . . . . 55 55 56 57 59 60 61 63 64 . . . . . . . . . . . 69 69 70 71 72 73 75 77 78 82 83 88 Chapter 1 Our Computer Labs IA6/56 IA6/164 IA IB IC ICFW03/255 Figure 1.1: Locations of the Labs The Institute of Computational Engineering (ICE) manages three computer labs: two in the building IA on the sixth floor (8 PCs in room 6/164 and 12 PCs in room 6/56) and one in the building ICFW 03/255 (23 PCs). This makes a total of 43 computers. Software The computers run under Windows XP and Linux. All computers have identical software installed including Ansys, Maple, Mathematica, Microsoft Office, AutoCAD (only german version) and various Java development tools. System Administration Responsible for the administration of the lab-computers is M. Baitsch (room IA6/144). In the case that something is not working as expected or you need further software to be installed, please contact us and we will assist as soon as possible. Access to the Labs The labs in building IA can be used all day, except if there are lectures or exercises (see schedule on the doors to the labs). The lab in ICFW is only open during the lectures or exercises. Logging on You can log on at each of the computers (use logon to: CIP) using the account name given to you. The account remains valid during your stay at the Ruhr-University of Bochum. 5 CHAPTER 1. OUR COMPUTER LABS 6 Your data The computers are configured such that your user profile is stored on a file server. This means that your personal settings like desktop color and bookmarks are the same at each computer. The folder where your own files (Java programs, own writing, drawings, . . . ) should reside is called “My Documents”. This folder is redirected to the file-server and and therefore accessible from each computer. Note 1: All data stored elsewhere is likely to be lost. Note 2: Disk space in this folder limited to 300MB. Printing and Scanning A black and white laser printer, a color inkjet printer and a scanner are in room IA6/164. Bring your own paper for printing. Before you can use the printer, you have to establish a connection to it (has to be done only once). Open “Printers and Faxes” (Startmenu), click “Add a Printer”. After clicking “next” two times, a dialog appears, in which you choose the radiobutton “Connect to this . . . ” and enter \\cipfs1\InnoLaser (cipfs1 is our file and print server). The computers are provided for your studies. This includes for using them in the exercises, doing assignments, case studies, thesis and searching information. Writing private e-mail is no problem but if all computers are occupied, their use for studying purposes has priority. Computer abuse, such as downloading large amounts of data from the Internet results in complaints from the central computer services and can forfeit your account, so don’t overdo it. Responsibilities Our responsibilities to you Your responsibilities • Keep the computers running • Don’t misuse the computers • Install necessary software • Help to keep the rooms tidy • Provide disk space for your files • Turn off computer/monitor after use • General support in using the computers 7 s 8 CHAPTER 1. OUR COMPUTER LABS Chapter 2 Fundamental Programming Structures in Java This chapter is a guide to the fundamental programming structures in Java. It covers the Java way of dealing with basic concepts present in most programming languages. While working your way through this chapter, you will: • use the programming environment Eclipse, • write and run a minimalistic Java program, • learn about the basic Java data types, • use the most important operators, • program and execute algorithms. 2.1 The Programming Environment Eclipse Software development is an iterative procedure which is greatly facilitated by using a programming environment (a program to develop programs). Throughout this course we will use the freely available program Eclipse. Within Eclipse you can enter program codes as well as you can run and test your programs. Figure 2.1 on the next page shows the toolbar of Eclipse. In Eclipse Java program files are collected in projects which in turn reside in a workspace. Let us now create a project for this chapter. Follow the steps below to create a project 1. Start Eclipse. 2. A workspace launcher dialog will pop up. Click OK and you will be greeted by a welcome window. Just close it. You now should see the Eclipse workbench shown in Figure 2.1 3. From the menu select “File→New→Project”. 4. Select “Java Project”, then “Next”. The new project dialog pops up. 5. Fill in the “Project name” text box with the name “chapter 2” for this chapter and click “Finish”. 9 CHAPTER 2. FUNDAMENTAL PROGRAMMING STRUCTURES IN JAVA 10 run program new package new class Figure 2.1: The Eclipse workbench 2.2 A Minimalistic Java Program Since the fundamental book of Kerningham and Richie about the programming language C, nearly every introduction to a programming language starts with a program writing out “Hello World”. We will stick to this tradition by beginning with the following program: 1 public class HelloWorld { 2 public static void main(String[] args) { System.out.println("Hello World"); } 3 4 5 6 } Listing 2.1: Print “Hello World” to the Console The following steps will guide you until you can run the program: 1. Create the class HelloWorld. To do so, click the corresponding button1 in the toolbar (see Figure 2.1). The new class dialog pops up. Enter HelloWorld as the class name. Note that you must not use blanks inside the name of a class (i.e. Hello World is not a valid name for a class). Click OK to close the dialog. 1 Alternatively you could use the menu or the context menu of the project. 2.2. A MINIMALISTIC JAVA PROGRAM 11 2. Complete the program according to Listing 2.1. You may note that Eclipse makes some words red and others blue. This is called syntax coloring and shall improve the readability of the source code. If the code contains mistakes, the errors will be underlined with red lines. 3. Run your program. From the menu select “Run→Run as→Java Application” to run the program. If there are errors in your code, the error messages will be listed in the “Problems view”. Double clicking an error message, the cursor jumps to the according place in your code. 2.2.1 Reviewing Hello World It is worth spending all the time that you need to go through this program; the pieces will recur in all applications. • Java is case sensitive. If you type Main instead of main the program will not run. You may want to try this out! • In line 1, the keyword class followed by the identifier HelloWorld defines a new class called HelloWorld2 . Because Java is strictly object-oriented, everything must happen inside a class. The concept of classes will become clear in the next exercises; for now you may think of a class as a container for the program logic that defines the behavior an an application. The preceding keyword public is called access modifier; these modifiers control what other parts of a program can use this code. For our purposes, we can state that all classes are public. The opening curly brace { after the class name indicates the beginning of the class and the closing curly brace } in line 6 its end. Everything inbetween these curly braces is said to belong to the class. • In line 3, the method main is declared. For the moment don’t worry neither about the keywords public static void nor about the argument String[] args. The point to remember for now is that each class you want to execute from within Eclipse must have a main method whose header is identical to line 3. As with the class in line 1, the body of the method is enclosed by a pair of curly braces. Whenever a method is called, each statement within its body is executed. The class HelloWorld is rather untypical since it has just a single method main. Usually, a program consists of many classes having many methods, whereby only one of the classes has a main method. • In line 4, the statement System.out.println("Hello World"); causes the string “Hello World” to be printed in the “Console view”. At the moment, it is impossible for you to understand how this works, just remember that this is the way how to print something. Notice also, that the statement in line 4 is terminated by a semicolon as in Maple. You may argue that this is a lot to type for such an easy thing. You’re right, but the strictly objectoriented design and the general applicability of Java imply the backdraw that simple things can look complicated. On the other hand, this strictness of Java turns out to be an advantage when it comes to complex software projects and this is what Java is made for. 2 A keyword is a reserved word which has a special, predefined meaning whereas identifiers are names chosen by the programmer. A complete list of all Java keywords is in the last section of this chapter. CHAPTER 2. FUNDAMENTAL PROGRAMMING STRUCTURES IN JAVA 12 2.3 Primitive Types Java is a strongly typed language (other than e.g. FORTRAN). This means that every variable must have a declared type. There are eight primitive types in Java which are used for “simple” things as numbers, boolean values and characters. Table 2.1 contains a list of all Java primitive types. Throughout this course, you will only need the types shown in bold face in Table 2.1. The other types (apart from char) are normally only used when storage space or computing time is very critical. Type Contains Size byte short int long float double boolean char integer integer integer integer floating-point floating-point boolean value unicode character 8 bits 16 bits 32 bits 64 bits 32 bits 64 bits 1 bit 16 bits Range -128 . . . 127 -32768 . . . 32767 -2147483648 . . . 2147483647 -9223372036854775808 . . . 9223372036854775807 approximately ±3.40282347 · 1038 approximately ±1.79769313486231570 · 10308 true or false ISO Unicode character set Table 2.1: Java Primitive Types Choosing an Adequate Type The data type you choose for a variable depends on the type of data you want to store in it. As a little food for thought, insert after the arrow the data type you would use to store • the age of a person in years → • the average age of persons in this room → • the square root of two → • whether you like German food → • the number of working computers in this room → 2.4 Variables and Assignments In Java, each variable has a type. You declare a variable by writing first the type and then the name of the variable after the type. Here are some examples of variable declarations: double temperature; int hoursPerDay; int daysPerWeek; int hoursPerWeek; boolean ready; double x, y, z; Note that you can also declare several variables of the same type in one line. After you declare a variable, you must explicitly initialize it by means of an assignment statement. 2.5. OPERATORS 13 temperature = -9.3; hoursPerDay = 24; daysPerWeek = 7; hoursPerWeek = hoursPerDay * daysPerWeek; ready = true; Be aware that an assignment is only possible if the expression on the right has an appropriate type. For instance, it is not possible to assign a double value to an int variable. Thus, the assignment hoursPerDay = temperature; would cause an error during compilation. More general, an assignment is possible, if the value on right hand side can be represented by the type on the left hand side without loss of precision. Because any integer can be safely converted into a floating-point value, the assignment temperature = hoursPerDay; is completely legal. Also, is not possible to assign a number to a boolean variable. Use the literals true and false in such situations. A nice feature of Java is the ability to both declare and initialize a variable on the same line. There are some examples: int secondsPerMinute = 60; int minutesPerHour = 60; int secondsPerHour = secondsPerMinute * minutesPerHour; Programming Assignment 1 Create a new class Time and add a main method as you did before. In this first step, the program should compute and print out the number of minutes per week. The output should look like One week has N minutes where N is replaced by the correct value. In order to get comfortable with the use of variables, please stick to the scheme used above for the number of seconds per hour. The following code fragment gives you an idea how to produce the desired output: int minutesPerHour = 60; System.out.println("An hour has " + minutesPerHour + " minutes"); The trick is to use the + operator to concatenate the individual entries to be printed out. 2.5 Operators Arithmetic Operators The arithmetic operators +, -, *, / are used in Java for addition, subtraction, multiplication and division respectively. The / operator denotes integer division (the decimal part is truncated) if both arguments are integers. Example: double x = 1 / 4; Now, x has a value of 0.0 because 1 and 4 both are interpreted as int. If one of the arguments is not an integer, floating-point division is performed. Example: double x = 1.0 / 4; CHAPTER 2. FUNDAMENTAL PROGRAMMING STRUCTURES IN JAVA 14 Now, x has a value of 0.25 as expected because 1.0 is interpreted as a double. Unintended integer division is a common source of errors. The integer remainder (i.e. the mod function) is denoted by %. For example; int rem = 15%2; Now, rem has the value 1 because 15 divided by 2 is 7, remainder 1. Increment and Decrement Operators The increment operator ++ adds 1 to a variable; the decrement operator -- subtracts 1 from a variable. Example: int n = 10; double x = 6.3; n--; x++; Now, n has the value 9 and x the value 7.3. Relational Operators The relational operators == != < <= > >= take numerical values and evaluate to a boolean. To test for equality or inequality, use the == operator or the != operator, respectively. For example, the value of 4.3 != 5 is true whereas 9%2 == 0 evaluates to false (this is actually a test whether 9 is an even number). The other relational operators work similarly and their meaning should be self explaining. Boolean Operators The two boolean operators && (logical and) and || (logical or) take two boolean arguments and evaluate to a boolean. Examples: The value of (5 < 6) && (8%2 == 0) is true (both arguments are true) and the value of (7%2 == 0) || (8 <=8 ) is also true because one of the arguments is true. Complete List of Operators in Java For the sake of completeness, here is a list of all operators defined in Java. Again, the operators you will need are in bold face. = == + += > <= -= < >= * *= ! ~ != && / & /= &= ? || | |= : ++ ˆ ˆ= -% %= << <<= >> >>= >>> >>>= 2.6. METHODS AND CONTROL STRUCTURES 15 2.6 Methods and Control Structures You already know Nassi-Shneiderman diagrams from the lecture. In this section, you will learn how to translate a Nassi-Shneiderman diagram into Java. 2.6.1 Sequential Steps The most fundamental element of a structure diagram is a single statement (step) which is represented by a rectangle. Statements in the structure diagram can be often translated directly into Java. Note that variables do not have to be declared in structure diagrams, so don’t forget to do so in Java. x = 3.7 y = 5.9 z = 7.5 * x - y Output z double x, y, z; x = 3.7; y = 5.9; z = 7.5 * x - y; System.out.println("Value of z: " + z); Figure 2.2: Example for a Sequence of Statements One special statement in a structure diagram is Output. This translates into a print statement, preferably providing some information about the meaning of the output. In the lecture notes, you find an example where Output is used to denote the result of an algorithm. This practice is not used in the exercises (neither in the exam). 2.6.2 Method Each algorithm takes one or more values as input and computes a result which is returned to the user of the algorithm. This shall be illustrated by a very simple algorithm which just adds two integers. add2(a: int, b: int): int sum = a + b return sum public static int add2(int a, int b) { int sum = a + b; return sum; } Figure 2.3: Example for a Method In the structure diagram on the left side, you find the name of the algorithm followed by the list of the input values. Each input value has a name followed by its type. After the parameter list, separated by a colon, is the type of the result. Return is another special statement in a structure diagram which indicates that the following value should be returned to the user. The Java equivalent starts with the keywords public static which you are kindly asked to accept for the moment (but keep in mind that static methods are a special case rarely used in general classes). The keyword int denotes the type of the return value of the method, then comes the method name followed by the parameter list enclosed in parenthesis. The parameter list contains the input values of the method having a type and a name. Note that the parameter list can also be empty. Inside the method body, the keyword return hands the result of adding a and b back to the caller of the method. CHAPTER 2. FUNDAMENTAL PROGRAMMING STRUCTURES IN JAVA 16 The class MyMath Create a new class MyMath in Eclipse (it will contain some mathematical functions by the end of this chapter, therefore the name). Type in verbatim the Java code from the example above. Note that this class will not contain a main method. When the file compiles create another class called TestMyMath which you will use to test the methods in MyMath. Add a main method to the class which looks as follows: 1 2 public static void main(String[] args) { int r1; 3 r1 = MyMath.add2(3, 48); System.out.println("MyMath.add2(3, 48) returns " + r1); 4 5 6 } Listing 2.2: Testing the add2 method Note that in line 4 you invoke the method add2 by using the class name MyMath followed by a dot and the method name. The return value of the method is stored in the variable r1. Compile and run the program, check that the result is correct. Next, introduce a new method add3 to MyMath which takes three integers i1, i2, i3 as input and returns the sum i1 + i2 + i3. Extend the main method of TestMyMath to invoke the new method (you may want to use a new variable r2). 2.6.3 Selection In a conditional statement (or a selection), a condition (which is either fulfilled or not) is used to decide whether one sequence of statements is to be executed or another. XXX XX condition X T XXX F block 1 block 2 if( condition ) { block 1 } else { block 2 } Figure 2.4: General form of a selection In Java, a conditional statement starts with the keyword if followed by the condition enclosed in parenthesis. The condition can be any expression evaluating to a boolean value. The block immediately after the condition is executed if the condition evaluates to true. After the first block you see the keyword else which is followed by the block being executed otherwise. Note that the else clause is optional. 2.6. METHODS AND CONTROL STRUCTURES 17 The following example determines the maximum of two double values: max(a: double,b: double): double XX » XXX a ≥ b »»» T F XXX»»»» result = a result = b return result public static double max(double a, double b) { double result; if (a >= b) { result = a; } else { result = b; } return result; } Figure 2.5: Taking the maximum of two doubles In the lecture notes you find another branch-like construct called switch (or case differentiation). It is not essential because any case differentiation can be constructed using a sequence of if and else if directives. Programming assignment Add a method isEven to the class MyMath that takes an integer value as input and returns a boolean value indicating whether the argument is an even number. Test this new method! 2.6.4 Indeterminate Loops In Java, as in all programming languages, there are control structure that let you repeat statements. There are two forms for repeating loops that are best when you do not know how many times a loop should be processed (these are “indeterminate loops”). The while loop tests the continuation condition at the beginning whereas the do/while loop test the continuation condition at the end. Only the first form is introduced here. continuation condition block while( continuation condition ) { block } Figure 2.6: General form of a while-loop CHAPTER 2. FUNDAMENTAL PROGRAMMING STRUCTURES IN JAVA 18 Indeterminate loops are often applied to algorithms which iteratively improve an approximate solution. An example of such an algorithm is the bracketing algorithm to find the square root of a positive real number. public static double sqrt(double x) { double low = 0; double mid = 0; double high = x; sqrt(x: double) : double low = 0 mid = 0 high = x while (high - low > 1e-12) { mid = 0.5 * (low + high); high - low > 10−12 mid = 0.5 (low + high) if (mid * mid > x) { high = mid; } else { low = mid; } mid2 > x T F high = mid low = mid Return mid } return mid; } Figure 2.7: Bracketing the square root of a number (we assume that x is nonnegative) Programming assignment Implement and test the above algorithm. Determinate Loops Determinate loops are suited for situations when the number of iterations is known in advance. A typical applications is to process each element of an array. The for loop is a very general construct to support iteration that is controlled by a counter that is updated after each iteration. initialization continuation condition update block for (init.; cont.; update) { block } Figure 2.8: General form of a while-loop Remark: Each for loop can be easily translated into a while loop (see Figure 2.9). initialization continuation condition block update Figure 2.9: for-loop as while-loop 2.7. JAVA KEYWORDS 19 In the following example, a for loop is employed to compute the factorial n! of a nonnegative integer n. factorial(n: int): int public static int factorial(int n) { int fct = 1; fct = 1 i=2 i≤n i = i+1 for (int i = 2; i <= n; i++) { fct = fct * i; } return fct; fct = fct * i Return fct } Figure 2.10: Compute the Factorial of a Positive Integer Programming Assignment Add the above method to MyMath and test it for several input values. Carefully study the results; do you trust them? What did eventually go wrong? Improve the method by using another data type for the return value. Advanced: in the lecture notes you find something about recursive method calls. Implement a method factorialRecursive that uses a recursive algorithm. Additional Programming Exercise In the lecture notes, you find three different algorithms to compute the greatest common divisor of two whole numbers. Implement them inside of your class MyMath. Don’t forget to test the methods. 2.7 Java Keywords At the end of this section, a listing of all Java keywords is provided for completeness. abstract boolean break byte case catch char class const continue default do double else extends final finally float for goto if implements import instanceof int interface long native new package private protected public return short static strictfp super switch synchronized this throw throws transient try void volatile while The keywords you should be familiar with after this semester are typed in boldface. 20 CHAPTER 2. FUNDAMENTAL PROGRAMMING STRUCTURES IN JAVA Chapter 3 Objects and classes This Chapter introduces the most fundamental concepts of object-oriented programming: objects and classes. In Section 3.1 you will learn how to use existing classes, i.e. classes which have been already programmed by somebody else. Next, in Section 3.2, you will learn how to program new classes by yourself. In order to use object-type variables correctly, some theory is crucial. This is given in Section 3.3 which is on computer memory, variables and objects. Sections 3.4 and 3.5 cover Java strings and arrays. Finally, Section 3.6 clarifies the use of the static modifier which you have used a lot in the previous chapter. 3.1 Using existing classes In order to introduce object-oriented programming, a software package for 3D computer graphics is employed. The package is called View3D and comprises a set of classes which represent threedimensional objects like cubes, spheres or arbitrarily shaped surfaces. 3.1.1 Three-dimensional objects Step 1: Set up the project Before you can actually start, create a new project for this chapter and name it ‘’‘chapter-3’. In order to be able to use the 3D graphics package, you have to adjust the project settings. To make Eclipse aware of the 3D library, right click the project “‘chapter-3”’ and choose “Properties” from the context menu. A dialog will show up. Choose “Java Build Path” from the list to the left and select the “Libraries” tab. Click “Add Library...” and select the “ICEB MPCE” entry. After clicking “Next” and “Finish” the project settings are complete. 21 CHAPTER 3. OBJECTS AND CLASSES 22 1 2 import inf.v3d.obj.*; import inf.v3d.view.*; 3 4 public class MyScene { 5 public static void main(String[] args) { Box box1 = new Box(); box1.setColor("red"); 6 7 8 9 Viewer viewer = new Viewer(); viewer.setVisible(true); 10 11 } 12 13 } Step 2: Create a red box object Create a new class MyScene and enter the code given above. Run your program. A window displaying the red box shows up. Note that you can interact with the scene using the mouse: • Press the left mouse button and move the mouse to rotate. • Press the right mouse button and move the mouse up and down in order to zoom in or out. • Press the mouse wheel and move the mouse to pan. What happens when you run the MyScene class? Lines 1–2 If you want to use classes from the View3D library, you have to import the corresponding packages. Line 7 An object of type Box is constructed using the new operator and the box1 variable (of type Box) points to the new object. In Java, objects are always constructed using the new operator along with the type name. Line 8 The color of the Box object, the box1 variable points to is changed to “red”. In Java, you invoke a method on an object always using the variable pointing to the object along with the method name and the “dot” operator. Line 10 You create an object of type Viewer using the new operator. The variable viewer points the the newly created object. Line 11 The Viewer object, the viewer variable points to is set visible. This statement lets the viewer show up on the screen. 3.1. USING EXISTING CLASSES Step 3: Add more objects method: 1 2 3 4 23 To add more objects to the scene, add the following lines to your main Box box2 = new Box(); box2.setVertex(2.0, 0.0, 0.0); box2.setSize(0.75, 0.5, 1.0); box2.setColor("green"); 5 6 7 8 Sphere sphere = new Sphere(1.5, 1.7, 0.5); sphere.setRadius(0.2); sphere.setColor("blue"); Note that these lines should be placed before the creation of the viewer. Run the program again. Discussion: If you compare lines one and six, you’ll notice that that the statements differ. In line one, a box object is created without passing any arguments. This means that the box is created with its default properties. On the other hand, in line six, the sphere is constructed explicitly by specifying the coordinates of the center. The method being called during object creation is called constructor and that classes can define several constructors. You’ll find more about constructors later in this chapter. Step 4: Read documentation and create a cone 1. Read the online documentation to get more information about the methods setVertex and setSize of the Box class. To do so, put the cursor in the method name and press “shift + F2” (sometimes, you will get an error message in the browser – just go back to eclipse and hit “shift + F2” again). 2. Add a yellow (or whatever color you like) cone such that your scene looks similar to the image below. Make use of the online documentation of the Cone class! CHAPTER 3. OBJECTS AND CLASSES 24 3.1.2 Another practical example: Plotting a function R2 → R This example is about generating a three-dimensional plot of the function f (φ, r) = e−r sin(πr) which is defined using polar coordinates. The domain on which the function should be plotted is given in Cartesian coordinates by −2.5 ≤ x ≤ 2.5 and −2.5 ≤ y ≤ 2.5. In order to plot the function, you can use the class Mesh from the inf.v3d package. This class represents a regular mesh of mxn points (see Figure 5.4). An individual mesh point is accessed by its i, j-index in the grid (similar to the entries of a matrix). In order to find out more about the class, consider the available documentation. One way to create the plot is shown as a structure diagram in Figure 5.4. The algorithm takes as input the domain to plot (first four arguments) and the number of points for each coordinate direction. plot(xmin: double, xmax: double, ymin: double, ymax: double, npx: int, npy: int): void dx = (xmax - xmin) / (npx - 1) dy = (ymax - ymin) / (npy - 1) mesh = new Mesh(npx, npy) bb = new BoundingBox(mesh) i = 0; i < npx; i = i + 1 j = 0; j < npy; j = j + 1 x = xmin + i * dx y = ymin + j * dy z y x r= x2 + y2 z = e−r sin(πr) A mesh consisting of 3 x 4 points. mesh.setCoordinates(i, j, x, y, z) mesh.setData(i, j, z) mesh.createColors() Figure 3.1: A Mesh (left) and the Algorithm to Plot the function (right) In order to generate the plot, you can take the following steps as a guideline: 1. Create a new class PlotFunction. 2. Add a method plot to the class PlotFunction, choose the parameter list according to the structure diagram and make it static. Note that the return type of the method is void. Implement the algorithm to plot the function according to the above algorithm. The necessary mathematical functions as well as the constant π are defined in the class Math available in Java (examples: use Math.PI for π or Math.exp(x) to compute ex ). To find out more, you can read the online documentation for the Math class. 3. Add a main method to your class PlotFunction. Inside this method, create the viewer, call the plot function (reasonable values for the resolution are npx = 150 and npy = 150) and finally set the viewer visible (similar to the example above). 3.2. PROGRAM YOUR OWN CLASSES 25 4. Run your program. 5. Modify your code and see how the output changes. For instance, plot the function on another domain or change the number of grid points. N OTE : The techniques employed to visualize the results of a finite element analysis are very similar to what you did now. In fact, the library behind the view3D package is the Visualization Toolkit (VTK), a C++ library for the visualization of huge scientific datasets. It is widely used e.g. in medical imaging, geological sciences and computational fluid dynamics. 3.2 Program your own classes 3.2.1 A vector class In this section, we will go into the details of designing and implementing a new class. We will do this using of a class for vectors in R3 . This section is rather lengthy since many new concepts are introduced. It is also thought to serve you as a reference, when you want to design and implement your own classes. The concept of vectors You should all be familiar with the mathematical concept of vectors. Let us recall our knowledge in order to get a clear idea of the functionalities our new class should provide. Consider a vector in three-dimensional euclidean space. A vector x ∈ R3 is defined by x = ei xi , where i ∈ {1, 2, 3}, ei are orthogonal unit vectors and xi are the components of the vector. Furthermore, Einstein’s summation convention is employed (the notation follows the lecture notes Finite Element Methods in Linear Structural Mechanics by D. Kuhl and G. Meschke). Having this definition of a vector, we can do several things: 1. define vectors x = [3.2, 5.6, 8.1]T , y = [6.1, 4.1, 9.9]T ; 2. compute the scalar product of two vectors α = x · y, = xi yi ; (remember the summation convention) CHAPTER 3. OBJECTS AND CLASSES 26 3. multiply a vector by a scalar to get another vector u = x 1.5; 4. of course, vectors can also be added and subtracted x = y + u, u = x − y; 5. and finally, we can compute the euclidean norm of a vector α = kuk2 , √ u · u; = where the scalar product is used. Having the above operations at hand, computations like v = kuk2 (5.2 x + y − x · y u) can be carried out. Designing the class Let us think about a vector class which stores the components of the vector and provides methods to perform the above operations at the same time. E XCURSION : T HE U NIFIED M ODELING L ANGUAGE (UML) Just like architects and engineers draw plans and build models of a building before it is actually erected, programmers establish a model of the software before they start programming. A software model describes the most relevant aspects of a program without dealing with each detail (just like a good FE model of a whole bridge captures the overall structural behavior without haggling for every bolt). The Unified Modeling Language (UML) is a language to express software models. It defines twelve different diagram types to describe various aspects of a program. We will now use a class diagram to establish a model of our vector class. In a class diagram, a class is represented by a box having three compartments: the first one holds the class name, the second one the attributes and the third one is for the methods. We now go through the three compartments of a class in UML notation by analyzing the above mathematical definition of a vector in R3 . Class Name The class name should briefly reflect the conceptual idea behind the class. For the vector class the concept is a vector in 3D space and thus Vector3D is a fine name. Spending some time thinking about a good name is always a good idea, since that helps to get a clear idea of the class’s purpose and responsibility. 3.2. PROGRAM YOUR OWN CLASSES 27 Vector3D -c1: double -c2: double -c3: double +Vector3D(c1: double, c2: double, c3: double) +print(): void +scalarProduct(Vector3D: v): double +multiply(alpha: double): Vector3D +add(v: Vector3D): Vector3D +subtract(v: Vector3D): Vector3D +norm2(): double N OTE : Java defines two other types of visibility in addition to public and private: protected and default visibility. We will not use them in the exercises. It is good practice to make all attributes private and most methods public (except those only to be used internally). Non private attributes diminish encapsulation! Figure 3.2: UML Class Diagram of the Vector Class Attributes A vector in 3D is represented by its three components, one for each base vector. The components are real numbers and therefore double is the right data type. We will store the components in three individual attributes named c1, c2 and c3 as a shorthand form for component one, two and three1 . Methods Deciding about the methods in a class is most important in the design process because methods define the way how an object of this class interacts with other parts of the program. Note, that in this stage of software development we are talking about the purpose of the method, the arguments it takes and the result it produces. We do not decide how to implement the method! We’ll now discuss the operations defined mathematically in the steps 1–5 above. 1. In step 1, the vectors x and y are initialized. In Java the methods used to create and initialize an object are called constructors. Constructors have the same name as the class, can take any parameter list and have no return type (in that constructors are special). For our purpose, it is convenient to define a constructor taking the three components of the new vector as arguments. 2. In step 2, we take the vector x and ask it to compute the scalar product with y; the result is a real number. Thus we equip the Vector3D class with a method scalarProduct taking another Vector3D object as argument and returning a double. 3. In step 3, a new vector is created by multiplying x with a scalar value (i.e. a number). We define a method multiply taking a double as argument and returning a new Vector object. 4. The operations addition and subtraction in step 4 are similar. We call the methods add and subtract; both take a Vector object as argument and return another, new Vector object. 5. In order to compute the euclidean norm like in step 5, we define a method norm2 (for brevity – the euclidean norm is also called two norm). In order to compute its euclidean norm, a vector needs no arguments; the result is a double. 1 Using an array of doubles would be another option. CHAPTER 3. OBJECTS AND CLASSES 28 Finally, we should introduce another method print to conveniently display a vector in the console (the DOS window). It does not compute anything and therefore the return type is void (which means nothing). The class diagram expressing the above modeling decisions is shown in Figure 3.2. In the attribute section, attributes are stated by their name and the data type – separated by a colon. The minus sign at the beginning indicates that the attributes are private and are thus not visible from outside the class. In the method compartment, you find the methods described by their name, their argument list and the return type. The plus sign indicates that the method is public and thus usable in other parts of the program. In the lecture notes, you also find so called assertions and property strings along with methods: we won’t use this feature of the UML in the exercises (neither in the exam). Implementing the class The last step is translate the above UML description into a Java definition and to test the implementation. We start with an incomplete definition of the class and then discuss the additional methods subsequently. You find a complete listing of the class at the end of this section. Well, here is the starting point for our class: 1 public class Vector3D { 2 private double c1_; private double c2_; private double c3_; 3 4 5 6 public Vector3D(double c1, double c2, double c3) { c1_ = c1; c2_ = c2; c3_ = c3; } 7 8 9 10 11 12 public void print() { System.out.println("[" + c1_ + ", " + c2_ + ", " + c3_ + "]"); } 13 14 15 16 } In line 1, you find the declaration of the class – you are already familiar with that. Lines 3 – 5 contain the declaration of the attributes of the class. The clue about attributes is, that they can be used in any method (except static ones) just like ordinary variables. N OTE : The names of the attributes end with an underscore ( ). This is a coding convention we use in this course for two reasons: i) the underscore allows you to clearly distinct attributes of the class from local variables and ii) it prevents the hassle with shadowed variables. We strongly ask you to stick to this convention. The underscore is not shown in UML notation. The first method is the so called constructor. A constructor is a special method invoked whenever you create an object via new. It has the same name as the class and no return type. In our case, the 3.2. PROGRAM YOUR OWN CLASSES 29 constructor takes three arguments that are used to initialize the individual components of the vector (lines 8 – 10). The print method is straightforward, here you just prints out the current values of the vector’s components. How To Read This Section In the following paragraphs, each method of the class is discussed in detail. You may want to read each paragraph and then implement that method and test it. To do so, create right now two new classes: Vector3D and Vector3DTest. Start with the incomplete class as listed above and put the testing code in the main method of the test class. For the tests you can use the mathematical statements at the beginning. If you want to, you can also take the output at the end of this section as a guideline. Scalar Product The scalar product x · y of the vectors x and y is mathematically defined as x · y = x1 y1 + x2 y2 + x3 y3 . For implementing the corresponding Java method, you can think of being inside the vector x (thus, the attributes c1 c2 c3 belong to x) and y is passed as a method argument. The method implementation is public double scalarProduct(Vector3D v) { return c1_ * v.c1_ + c2_ * v.c2_ + c3_ * v.c3_; } Note, that you use the dot operator to access the components of the second operand. Go implement and test the method. . . Multiplication by a Scalar The multiplication of a vector by a scalar is performed component wise. In the method, you first compute the components of the new vector, then you create it and finally, the new vector is returned as the result of the method. public Vector3D multiply(double alpha) { Vector3D result; double c1 = alpha * c1_; double c2 = alpha * c2_; double c3 = alpha * c3_; result = new Vector3D(c1, c2, c3); return result; } Addition and Subtraction The methods to add and subtract vectors are somewhat similar to the multiplication by a scalar: first the components of the new vector are computed and then a new vector is constructed and returned as result. Note that the way it is implemented here is equivalent to the above, it just save two lines of code. public Vector3D add(Vector3D v) { double c1 = c1_ + v.c1_; double c2 = c2_ + v.c2_; double c3 = c3_ + v.c3_; return new Vector3D(c1, c2, c3); } The subtract method is similar, implement it on your own. CHAPTER 3. OBJECTS AND CLASSES 30 Euclidean Norm In order to compute the euclidean norm of a vector, you first compute the scalar product of the vector with itself and then return the square root of that number. In Java, you use the keyword this, if you want to refer to the object on which the method actually is invoked. public double norm2() { return Math.sqrt(scalarProduct(this)); } 3.2. PROGRAM YOUR OWN CLASSES 31 Complete Listing Finally, here is the complete listing of the class. Study the methods multiply, add and subtract carefully. You will realize that they perform the same in general (compute components of new vector and create it) with increasing compactness of the code. public class Vector3D { private double c1_; private double c2_; private double c3_; public Vector3D(double c1, double c2, double c3) { c1_ = c1; c2_ = c2; c3_ = c3; } public Vector3D double c1 = double c2 = double c3 = add(Vector3D v) { c1_ + v.c1_; c2_ + v.c2_; c3_ + v.c3_; return new Vector3D(c1, c2, c3); } public Vector3D multiply(double alpha) { Vector3D result; double c1 = alpha * c1_; double c2 = alpha * c2_; double c3 = alpha * c3_; result = new Vector3D(c1, c2, c3); return result; } public double norm2() { return Math.sqrt(scalarProduct(this)); } public void print() { System.out.println("[" + c1_ + ", " + c2_ + ", " + c3_ + "]"); } public double scalarProduct(Vector3D v) { return c1_ * v.c1_ + c2_ * v.c2_ + c3_ * v.c3_; } public Vector3D subtract(Vector3D v) { return new Vector3D(c1_ - v.c1_, c2_ - v.c2_, c3_ - v.c3_); } } CHAPTER 3. OBJECTS AND CLASSES 32 Sample Output The output of the testing routine used for the reference implementation is listed below. It is also a nice example for the effect of rounding errors. The last seven lines are of special interest: they test for the correctness of the implementation by checking if some mathematical properties of vectors are fulfilled. x = [3.2, 5.6, 8.1] y = [6.1, 4.1, 9.9] alpha = x * y alpha = 122.66999999999999 u = x 1.5 u = [4.800000000000001, 8.399999999999999, 12.149999999999999] x x u u = = = = y + u [10.9, 12.499999999999998, 22.049999999999997] x - y [4.800000000000001, 8.399999999999999, 12.149999999999997] alpha = |u| alpha = 15.531339285457642 v = |u| (x 5.2 + y - u x * y) v = [-24076.495784245013, -42767.00266840044, -61477.160579586794] Test mathematical properties Must be the null vector v - v = [0.0, 0.0, 0.0] v + v (-1) = [0.0, 0.0, 0.0] The norm of a vector is a linear operator: |v 4| = 314658.81050855166 4 |v| = 314658.81050855166 Compound Statements To compute x = [1, 2, 3]T y = [2, 3, 4]T z = 2.5 (x + y 1.5) by using the Vector3D class, you can write Vector3D x = new Vector3D(1, 2, 3); Vector3D y = new Vector3D(2, 3, 4); Vector3D z; z = x.add(y.multiply(1.5)).multiply(2.5); where you directly call a method for the result of another method call. Conclusions Now that you have implemented your first class, let us briefly review the individual steps: 1. We started from the mathematical concept of vectors and analyzed what kind of data we need to represent a vector and which operations we can perform with vectors. 3.2. PROGRAM YOUR OWN CLASSES 33 2. In the design phase, we identified the vector components as relevant data of the class and decided to store them in three individual attributes, one for each component. Furthermore, we decided about the methods by choosing the return type, the name of the method and the argument it takes. These design decisions have been expressed in a UML class diagram. It should be emphasized that in this step, we only care about what the individual methods do (e.g. compute the norm of a vector) and not how they do it. 3. In the implementation step, the class diagram has been translated into Java code. This is the point, where we decided how to carry out the computations. Some of the methods generate a new vector as result (the result of adding two vectors is a new vector). Inside these methods, we therefore had to create a new Vector3D object which we then returned as the method result. 4. While testing the code, we created several instances of the Vector3D class by using the keyword new. Although all the objects belong to the class Vector3D, each has its own individual values for the components Although the Vector3D example is rather simple, it comprises all steps of object-oriented software development: analysis, design and implementation. 3.2.2 Practical example: Complex numbers and the Mandelbrot set In this practical example you will first implement a class for complex numbers and then employ this class to plot a fractal set. A class for complex numbers Complex numbers are an extension to the concept of real numbers. Originally, complex numbers have been introduced for the sake of a closed formulation of the problem to find the roots of algebraic equations. In structural engineering, complex numbers play a fundamental role in the theory of vibrations. Reminder The set C = {z | z = α + i β; α, β ∈ R} √ is called the set of complex numbers in which i denotes the imaginary unit defined by i = −1 i.e. i2 = −1. For the complex number z = α + i β we call α the real part and β the imaginary part. The following operations are defined for two complex numbers z1 = α1 + i β1 and z2 = α2 + i β2 : 1. Addition and subtraction z1 ± z2 = (α1 ± α2 ) + i (β1 ± β2 ) 2. Multiplication z1 · z2 = (α1 α2 − β1 β2 ) + i (α1 β2 + α2 β1 ) 3. Division α2 β1 − α1 β2 α1 α2 + β1 β2 z1 +i = 2 2 z2 α2 + β2 α22 + β22 In addition, we can compute the absolute value of a complex number: |z| = q α2 + β 2 CHAPTER 3. OBJECTS AND CLASSES 34 Assignment 1 1. Complete the UML class diagram to contain the operations declared above. Note that we use the term real for α and imag for β. Complex -real: double -imag: double +Complex(real: double, imag: double) +print(): void 2. Implement the class in Java and write a test class in which you should test each implemented method (for the moment, the method to compute the division can be omitted). Plotting the Mandelbrot set The Mandelbrot set has been introduced in 1975 by the mathematician Benoit Mandelbrot (born in Poland in 1924). The Mandelbrot set is defined as follows: Pick a point z0 in the complex plane and calculate z1 = z20 + z0 z2 = z21 + z0 z3 = z22 + z0 ... If the sequence z0 , z1 , z2 , z3 , . . . remains within a distance of 2 of the origin forever, then the point z0 is said to be in the Mandelbrot set. If the sequence diverges from the origin, then the point is not in the set. For practical reasons, the iteration is terminated after a finite number of steps or when the absolute value is larger than 2. We can then define the Mandelbrot function, which returns either the number of steps until divergence occurs or the maximum number of steps ration between the number of performed steps and the maximum number of steps or 1. The function value indicates how fast the function diverges. 3.2. PROGRAM YOUR OWN CLASSES 35 Assignment 2 1. Create the class MandelbrotFunction and add the static method evaluate according to the following structure diagram. evaluate(x: double, y: double): double z0 = new Complex(x, y) zn = new Complex(x, y) m=0 m < 30 and zn.abs() < 2.0 zn = zn.multiply(zn).add(z0) m=m+1 return m/30.0 2. Change your PlotFunction class from the last exercise such that it uses MandelbrotFunction.evaluate(x, y) for the computation of z. N OTE : The previous version of the class Mesh had a design flaw which caused the method setData to be unnecessarily slow. This has been improved but you now must invoke mesh.createColors() at the very end of your plot method. Make sure it is not inside the loops since it is time consuming. 3. Plot the Mandelbrot function for −1.7 ≤ x ≤ 0.7 and −1.2 ≤ y ≤ 1.2. Start with a resolution of npx = npy = 250. Your plot should roughly look like Figure 3.3 but nicer since you use a higher resolution. 4. Increase the resolution to get a nicer image but keep in mind that the complexity of plotting the function is O(npx*npy). 5. The fascinating thing about the Mandelbrot function is that its border is a complicated curve no matter how close you look. Check this out by changing the plot range to 0.21 ≤ x ≤ 0.435 and 0.45 ≤ y ≤ 0.675. You might also experiment with different scaling factors for the z-value and/or other plot ranges. 36 CHAPTER 3. OBJECTS AND CLASSES Figure 3.3: Plot of the Mandelbrot function with a resolution of 500 in both directions. 3.3. COMPUTER MEMORY, VARIABLES AND OBJECTS 37 3.3 Computer memory, variables and objects 126 125 124 Computer memory On binary computers as we use them, information is encoded into a sequence of so called bits (binary digits). A bit is either on (1) or off (0). Computer memory can be thought of as a long strip of cells containing 0s or 1s. Eight bits are collected in a so called byte and each bite has a memory address as shown in Figure 3.4. ... 1 1 0 1 0 0 0 1 0 1 0 1 0 0 1 1 1 0 1 0 ... byte byte Figure 3.4: Computer memory Today’s computers often have a memory size of one gigabite (1GB) which is 8 · 10243 = 8589934592 bytes. In Figure 3.4, one cell takes 3.5mm and for 1GB, the length of the strip would be 8589934592 · 3.5 · 10−6 = 30060km. 548 memory (raw) variables a ... 0 1 ... 0 1 ... 1 0 ... ... 1 1 124 Variables of different data types A variable can be thought of as a place in computer memory where your program can store information. Figure 3.5 shows two variables a and b that both occupy some amount of memory. b Figure 3.5: Variables In Java, every variable has a data type that specifies how the sequence of bits is decoded. Java distinguishes between two different types of variables: primitive type variables and object type variables. Both types have a specific meaning. Primitive type variables In Java, there are eight basic data types like int. A complete listing of primitive types is given in Table 2.1. For primitive type variables, we can say: The content of a primitive type variable is the value. Using the type information, the bit sequence for one variable can be decoded into a human readable format. Figure 3.6 shows a piece of Java code and the corresponding memory state. From now on, we will only use the decoded represention of computer memory. 32 bits 64 bits 321 0 1 1 ... memory (raw) 321 124 124 ... 1 memory (decoded) x 0 0 0 ... 1 0 ... 3.0 program code ... ... 1 1 ... double x = 3.0; int a = 1; a Figure 3.6: Primitive type variables variables CHAPTER 3. OBJECTS AND CLASSES 38 ... double x = 3.0; x = 3 * x; ... 2 542 1 542 Because primitive type variables store the value, assigning a value to a primitive type variable changes the content of the variable’s memory. Figure 3.7 shows a code sequence and the corresponding memory state. 3.0 ... at step 1 9.0 ... at step 2 program code x Figure 3.7: Assigning to a primitive type variable Object type variables An object can be thought of as a collection of data that resides somewhere in memory. For object type variables, we can say: The content of an object type variable is the address of an object. 2 1075 987 v1 v2 at step 1 1075 987 874 ... #987 ... #987 ... vector obj. ... vector obj. ... program code ... #987 ... #1075 ... vector obj. ... vector obj. ... 542 1 Vector3D v1 = new Vector3D(1,2,3); Vector3D v2 = new Vector3D(3,2,1); v2 = v1; 874 542 When an object is created via the operator new, the address is returned and can be stored in a variable. When we assign a new address to an object type variable, the variable value changes to the new address. This is illustrated in Figure 3.8 (we use the #-sign to indicate that a number represents an address). First, we have two variables both pointing to two distinct boxes. After the assignment, both variables point to one box and none to the other. at step 2 variables Figure 3.8: Object type variables At this point, we introduce another notation for object variables. This new notation makes it easier to visualize the state of an object and connections between objects. As shown in Figure 3.9, an object is visualized as a box where the name of the object’s type is underlined. Variables are drawn below the boxes and point to the boxes. 1 2 Vector3D v1 = new Vector3D(1,2,3); Vector3D v2 = new Vector3D(3,2,1); v2 = v1; program code Vector3D Vector3D Vector3D Vector3D c1 = 1 c2 = 2 c3 = 3 c1 = 3 c2 = 2 c3 = 1 c1 = 1 c2 = 2 c3 = 3 c1 = 3 c2 = 2 c3 = 1 b1 b2 b1 b2 state at step 1 Figure 3.9: New notation for object type variables state at step 2 3.4. STRINGS IN JAVA 39 3.4 Strings in Java Strings like ”Hello World” are represented in Java by a class called String, you already met this class in every main-method you have implemented. This section briefly introduces Java strings. For more information, consider the Javadoc for the String class. The statement String s1 = "Hello Bochum"; declares the variable s to be of type String and initializes it to “Hello Bochum”. Use the + operator to concatenate strings String s2 = s1 + ", how are you?"; You can combine each type of variable with a string, they automatically converted: String s3 = Math.PI + " is roughly the value of pi"; The String class has lots of methods, for instance a method to extract substrings: String s4 = s3.substring(0, 5); String comparison Sometimes, you will need to check whether two strings are equal. In the previous chapter, you used the == to test primitive types for equality. You can’t do that for strings! For object type variables, the operator == tests whether two variable refer to the same object and not whether they have equal content (test for identity not equality). In order to compare two strings, the String class provides the method equals that takes another string as an argument and returns a boolean value. Example: if(s2.equals(s3)) { System.out.println("This should never happen"); } if(!s1.equals("Hello Bochum")) { System.out.println("Neither that"); } 3.5 Understanding Java arrays Arrays are a fundamental data structure in each programming language. An array is a data structure that stores a collection of values of the same type. You access each individual value through an integer index specifying the position of the value in the array. Compared to C, C++ or FORTRAN, arrays in Java are much easier to use: • Java array know how large they are. You do not have to pass the length of the array to a function as an extra argument. • A bounds checking mechanism gives an error in the case of an out of bounds access to the array. This greatly facilitates software development because bugs are much easier to find this way. You declare an array variable by specifying the type of the values in the array followed by [] and the name of the variable. For example, here is the declaration of an array of boolean values: boolean[] b; CHAPTER 3. OBJECTS AND CLASSES 40 However, this statement only declares the variable b to be an array of booleans. It does not yet initialize it. Because arrays are some sort of objects in Java (they share a lot with objects, but differ in some aspects), an array must also be created via new. The number of elements of the array is given in brackets. For example, the statement double[] x = new double[20]; creates an array of 20 doubles and stores it in the variable x. During array creation, the array elements are initialized to their default values (which is zero for the numerical primitive types). Note, that the difference to the creation of “normal” objects is that brackets [] are used instead of the parenthesis (). Access to an element of an array looks like x[0] = 44.2; double y = x[19]; where the first statement stores a value in the array and the second reads a value from the array. Note that array indexing is zero based in Java (other than in FORTRAN) and thus the statement x[20] = 2.0; results in an error message and program termination (remember that the array bounds are checked during runtime). To find the number of elements in an array named x, use x.length. A typical example is to process each element of the array in a for loop: for(int i = 0; i < x.length; i++) { x[i] = 1.0 /(i + 1); } Arrays can not only be created for the Java primitive types as above, but also for object type variables (remember that each class defines a type). Thus, the statement String[] days = new String[7]; defines an array of strings. The values of object type arrays are initialized to null which indicates that they do not refer to an object yet. Access to the array elements is similar as for primitive types: days[0] = "Monday"; To be complete, it shall also be mentioned that two-dimensional arrays can be created in Java. The statement int[][] a = new int[30][10]; declares a matrix of 30 x 10 integers. To access the elements, two pairs of brackets must be used: a[3][8] = 444; Assignments In order to become comfortable with arrays, create a class ArrayTest, add a main method2 and take the following points as a guideline: 1. enter the statements above 2. print the values of the array x 3. compute the sum of the entries of x and print it 4. make the array days complete and print it 2 From now on, we assume that you know that a main method is needed if you want to run a program. 3.6. STATIC METHODS AND CONSTANTS 41 The Parameter List of the Main-Method At the beginning of the course you might have wondered about the String[] args in the argument list of the main method. Now you know that it is an array of strings! The argument can be used to pass some information to the program at startup. 3.6 Static methods and constants In the sample programs you have written, the main method is tagged with the static modifier. Also, the methods in your MyMath class were static. We are now ready to discuss the meaning of this keyword. 3.6.1 Static methods In order to understand static methods, let us quickly recall the way “normal” methods work. In the last chapter, you created a Box object and changed its color: Box box1 = new Box(); box1.setColor("red"); In the first line, the new Box object is constructed and stored in the variable box1. Then, in the second line you invoke the setColor method on your Box object that is stored in the variable box1. In short: “normal” methods operate on objects that you have constructed using new. In contrast, static methods are methods that do not operate on objects. You do not need to create an object using new in order to invoke a static method. Remember the MyMath class from Chapter 3: public class MyMath { public static double add2(double a, double b) { return a + b; } } Because the add2 method is qualified with the static keyword, you can use the name of the class to invoke add2: double x = MyMath.add2(3.1, 4.5); Another good example for the use of static methods is the Math class, you already know. Because static methods belong to the class they are also called classifier methods. Normal methods, that operate on instances of a class are often called instance methods. When to use static methods? Sparingly. Static methods make sense for simple operations that only need a few parameters. Good examples can be found in the Math class. Also, static methods are used if only access to the static attributes of a class is needed. 3.6.2 Static attributes If you define an attribute as static, that attribute only exists once. In contrast, “normal” attributes exist for each object that you construct. Remember the several instances of the Vector3D class you created: each of them had its own values for the components. CHAPTER 3. OBJECTS AND CLASSES 42 Constants If you qualify an attribute as final, the value of this attribute can not be changed after initialization. Often, static attributes are declared as final to define some predefined constant. An example is Math.PI. The definition of π in the Math class is ... public static final double PI = 3.14159265358979323846; ... As you already did, you access a public static attribute by using the name of the containing class and the dot operator: double u = 4 * Math.PI; On the other hand, you get an error during compilation if you would write Math.PI = 4.0; This is because PI is qualified as final in the Math class. Finally, let us review the mysterious System.out.println("Hello World"); System is a class that contains several useful static attributes and methods. One specific static attribute of the System class is out; it refers to an instance of the class PrintStream that represents a stream on which you can write. Have a look at the Javadoc to find out more about print streams. Chapter 4 Inheritance and Polymorphism Chapter 3 introduced you to objects and classes. In this chapter, you will learn about inheritance, another essential concept of the object-oriented programming paradigm. Basically, inheritance allows you to build new classes upon existing classes. When you inherit from an existing class, you reuse (or inherit) methods and attributes of the base class and you add new methods and fields to adapt your class to a specific situation. Thus, it is also often said that a derived class extends the base class. Closely related to inheritance is the concept of polymorphism that makes up much of the power of object-oriented programming. Polymorphism means that variables of the base class type can refer to any object of derived types whereby methods invoked on the base type are dispatched to the derived type. In this chapter you will: • program classes that inherit from another class, • define abstract classes, • read about polymorphism, • program classes for cross sections. 4.1 Extending Classes Inheritance provides a formal mechanism for code reuse. Using inheritance, you build new classes which add attributes and methods to an existing class. We will do that in order to represent the employees of a company in a Java program. Every employee of a company has a name. In addition, special kinds of employees exist, for instance, an employee can be a programmer using a specific programming language or a sales manager who is responsible for a certain number of clients. In order to establish a class model of the above situation, we first identify suitable classes and then the relations amongst the classes. For the classes, let us use Employee, Programmer and SalesManager. Since both programmers and sales managers are special kinds of employees, the classes Programmer and SalesManager inherit from the class Employee. Let us also introduce a method doWork that prints out what programmers and sales managers are doing at work time. The corresponding UML class diagram is shown in Figure 4.1 (next page). Remember that we use the triangle pointing to the base class to indicate inheritance in a UML class-diagram. 43 CHAPTER 4. INHERITANCE AND POLYMORPHISM 44 Employee -name: String +setName(name: String): void +getName(): String SalesManager Programmer -language: String -clients: int +setLanguage(lang: String): void +getLanguage(): String +doWork(): void +setClients(clients: int): void +getClients(): int +doWork(): void Figure 4.1: UML Class Diagram for Employees How To Read This Section It is suggested, that you program the classes while reading the text. In order to keep your files organized, you may want to create a new project “chapter5” in eclipse. The Base Class The implementation of the Employee is straightforward: public class Employee { private String name_; public void setName(String name) { name_ = name; } public String getName() { return name_; } } N OTE : In the class diagram (and of course in the Java code), you find for each attribute of the classes a pair of set and get methods. Using such pairs of set and get methods to modify and access the attributes of an object is common practice in Java programming. 4.1. EXTENDING CLASSES 45 The Inheriting Classes Let us start with the Programmer class: public class Programmer extends Employee { private String language_; public void setLanguage(String language) { language_ = language; } public void doWork() { System.out.println("Programs in " + language_ + "."); System.out.println("Drinks a cup of coffee."); } } In the first line, the keyword extends indicates that the Programmer class adds something to (and thus extends) the Employee class. The other way round, the Programmer class inherits everything from the Employee class. Inside the Programmer class the additional features are then implemented. The SalesManager class is very similar: public class SalesManager extends Employee { private int clients_; public void setClients(int clients) { clients_ = clients; } public void doWork() { System.out.println("Makes a phone call to one of his " + clients_ + " clients."); System.out.println("Checks the sales of the last month."); } } CHAPTER 4. INHERITANCE AND POLYMORPHISM 46 Using the Derived Classes In order to find out how derived classes can be used, let us introduce a EmployeeTest class: 1 public class EmployeeTest { 2 public static void main(String[] args) { 3 4 Programmer p1 = new Programmer(); p1.setName("Valery Miller"); p1.setLanguage("FORTRAN"); 5 6 7 8 SalesManager sm1 = new SalesManager(); sm1.setName("John Wood"); sm1.setClients(16); 9 10 11 12 // the programmer System.out.println(p1.getName() + " is working:"); p1.doWork(); 13 14 15 16 // the sales manager System.out.println(sm1.getName() + " is working:"); sm1.doWork(); 17 18 19 } 20 21 } In line 7, we declare and a variable p1 of type Programmer and construct a new Programmer object. In line 8, we give our employee a name. But wait! The method setName is not implemented in the class Programmer, so how can we invoke it?. We can, because the Programmer class inherits from the class Employee which has a method setName. In the lines 11–13, a sales manager is constructed and initialized. In the following lines, we print out what the employees are doing during work time. 4.1.1 Using the Base Class Instead of the Derived Class An important issue when dealing with derived classes is that you can use the type of the base class to store an object of the derived class. Thus, the last two lines in Programmer p1 = new Programmer(); p1.setName("Valery Miller"); p1.setLanguage("FORTRAN"); Employee e1 = p1; System.out.println(e1.getName()); are perfectly legal because each programmer is also an employee. For the moment, the advantage of using the base type instead of the derived type may not be obvious. But together with polymorphism it is a concept that is absolutely essential for efficient object-oriented programming. 4.2. ABSTRACT CLASSES 47 4.2 Abstract Classes Suppose we want to use the above classes to represent the employees of a larger company. A company may employ many programmers and sales managers. Therefore it would be very convenient to manage the employees in a unified manner instead of treating programmers and sales manager differently (as we did above). For instance, we could think of storing the employees in an array and then use a for loop to print out what the employees are doing. In order to do so, we can change the EmployeeTest class so that it looks like: 1 package step2; 2 3 public class EmployeeTest { 4 public static void main(String[] args) { 5 6 Programmer p1 = new Programmer(); p1.setName("Valery Miller"); p1.setLanguage("FORTRAN"); 7 8 9 10 SalesManager sm1 = new SalesManager(); sm1.setName("John Wood"); sm1.setClients(16); 11 12 13 14 Employee[] employees = new Employee[2]; employees[0] = p1; employees[1] = sm1; 15 16 17 18 for (int i = 0; i < employees.length; i++) { System.out.println(employees[i].getName() + " is working:"); employees[i].doWork(); System.out.println(); } 19 20 21 22 23 } 24 25 } In contrast to the previous version, in the lines 13–15 we create an array of two employees and store the programmer and the sales manager in that array. Since both variables p1 and sm1 are of type Employee we can do that. Next, we use a loop to process each employee. Line 18 is fine because getName is defined in the Employee class. But the compiler will complain about line 19 because the Employee class does not have a method doWork (try this out). On the other hand, the approach makes sense, since all employees are working; we already implemented the methods. What we have to do is to introduce a doWork method in the Employee class. But how should we? We can not know in the Employee class what a programmer or a sales manager does during work. The solution is to introduce the doWork method in the Employee class and to declare it as abstract. An abstract method only specifies that an object of that type does have the method but does not implement it. The implementation of the method is delegated to the derived classes. A class containing abstract methods is itself also called abstract. You can not create objects of an abstract class! Let us now change the Employee class such that the above program works. CHAPTER 4. INHERITANCE AND POLYMORPHISM 48 Employee -name: String +setName(name: String): void +getName(): String +doWork(): void printed version (uses italic font) sketch (uses {abstract}) Figure 4.2: The Improved Employee Class Figure 4.2 shows the improved version of the Employee class in a UML class diagram. In addition to the previous version, we now have a doWork method which is abstract. In the class diagram this is indicated by either using an italic font or the annotation {abstract} in the hand-drawn version. Because the Employee class contains abstract methods it is also marked as abstract. Note that no changes have to be introduced in the Programmer and the SalesManager classes since they already have a doWork method. In Java, you use the keyword abstract to qualify both classes and methods as being abstract: package step1; public class Employee { private String name_; public void setName(String name) { name_ = name; } public String getName() { return name_; } } Note that the doWork method does not have a method body but is just completed with a semicolon after the parameter list (which is empty in this case). After you have changed the Employee class, the compiler should be willing to compile the EmployeeTest class. The output of the program should look something like Valery Miller is working: Programs in FORTRAN. Drinks a cup of coffee. John Wood is working: Makes a phone call to one of his 16 clients. Checks the sales of the last month. The program output makes clear that in Java object variables are polymorphic. This is discussed in the next section. 4.2. ABSTRACT CLASSES 49 4.2.1 Polymorphism In order to discuss polymorphism, let us revisit the code doing the output: 1 2 3 Employee[] employees = new Employee[2]; employees[0] = p1; employees[1] = sm1; 4 9 In the lines 1–3, we store the Programmer object and the SalesManager object in an array of the type Employee. A variable of type Employee can refer to an object of type Employee or any subclass of the Employee type (such as Programmer or SalesManager). In line 7, you invoke the doWork method on an object of type Employee (as you declared in line 1). On the other hand, you can observe – when studying the program output – that nevertheless the correct method implemented in the derived class is invoked. This behavior is called polymorphism. Despite the fact that polymorphism is relatively hard to understand (and also to explain) in theory, it is rather intuitive to use. 4.2.2 The Three Pillars of Object-Oriented Programming In the previous chapter, you learned about encapsulation. In this chapter, you used inheritance and polymorphism. These three concepts are the most fundamental ones to object-oriented programming. Some textbooks use also the term “the three pillars of object-oriented programming” in this context. Figure 4.3 illustrates them. Polymorphism 8 Inheritance 7 for (int i = 0; i < employees.length; i++) { System.out.println(employees[i].getName() + " is working:"); employees[i].doWork(); System.out.println(); } Encapsulation 5 6 Figure 4.3: The Three Pillars of Object-Oriented Programming 4.2.3 Abstract Classes in Finite Element Programming In the context of the Finite Element method, there are plenty of candidates for abstract classes: Material models: A material model calculates for a given strain tensor the corresponding stress tensor. Conceptually, this computation is the same for a simple isotropic elastic material model as for a complex plastic material model. By introducing an abstract class MaterialModel all specific material models could be used in the same manner. Polymorphism delegates the stress calculation to the concrete class (e.g. IsotropicElasticMM) as needed. CHAPTER 4. INHERITANCE AND POLYMORPHISM 50 Element formulations: An abstract class Element declares abstract methods to compute the element stiffness matrix ke and the element load vector re . The concrete subclasses (e.g. Truss3D) are then responsible for these computations. Cross sections: One dimensional elements need to know the properties of the cross section such as the area or the moments of inertia. An abstract class CrossSection declares the corresponding methods which are implemented in the concrete subclasses like RectangularCS. The mechanism of polymorphism has a very important property: it makes programs extensible. For instance, in the context of Finite Element programming you could introduce a new material model without having to change any code inside the element classes that use a material model. The element only has to know that the material model computes stresses. It does not have to know anything about how this is done. 4.3 Practical Example: Sections In order to establish a model of a structure consisting of one-dimensional members, we need classes that represent cross sections. For our purposes, a cross section should be able to compute the area and it should provide the vertexes of its outline for visualization. The outline is defined in two-dimensional space. The functionality described above should be declared in an abstract class CrossSection. Starting Point As a starting point, here is a class for rectangular cross sections: public class RectangularCS extends CrossSection { private double width_; private double height_; public RectangularCS(double width, double height) { width_ = width; height_ = height; } public double getArea() { return width_ * height_; } public double[][] getOutline() { double[][] outline = new double[4][2]; outline[0][0] outline[0][1] outline[1][0] outline[1][1] outline[2][0] outline[2][1] outline[3][0] outline[3][1] = = = = = = = = -width_ / 2.0; -height_ / 2.0; width_ / 2.0; -height_ / 2.0; width_ / 2.0; height_ / 2.0; -width_ / 2.0; height_ / 2.0; 4.3. PRACTICAL EXAMPLE: SECTIONS 51 return outline; } public double getHeight() { return height_; } public double getWidth() { return width_; } } Be aware that this class needs the abstract class CrossSection to compile properly. Assignments • Sketch a class diagram containing at least four different types of cross sections. Identify the relevant parameters for those classes (e.g. width and height for a rectangular cross section). • Implement in Java the abstract base class CrossSection and at least one concrete class in addition to RectangularCS that extends CrossSection. Hint: a circular outline can be approximated by a polygon. • Create another class to test your classes. Create different sections and compute the area. • Use the Extrusion class from the inf.v3d package to visualize a simple structure. Look at the Javadoc of the Extrusion class to find out how to use it. Note that you can use the outline of one section for several Extrusion objects: RectangularCS cs1 = new RectangularCS(0.1, 0.1); Extrusion e1 = new Extrusion(); e1.setOutline(cs1.getOutline()); e1.setPoint1(0,0,0); e1.setPoint2(0,1,0); Extrusion e2 = new Extrusion(); e2.setOutline(cs1.getOutline()); e2.setPoint1(3,0,0); e2.setPoint2(3,1,0); The figure shows an example that uses I-shaped cross sections. 52 CHAPTER 4. INHERITANCE AND POLYMORPHISM 4.4. INTERFACES 53 4.4 Interfaces In Java, interfaces provide a way to specify a set of methods without giving any implementation. A class that provides the methods specified in an interface is said to implement that interface. In that sense, interfaces are similar to classes that have only abstract methods. The difference is that a class can only have one superclass but it can implement any number of interfaces. Figure 4.4 shows a UML class diagram that contains two interfaces and a class that implements both interfaces. Note that we can use two different notations to express that a class implements an interface: One inheritance-like style and one lollypop-style. <<interface>> PublicTransport +getCapacity(): int <<interface>> MotorVehicle +getEnginePower(): int MotorBus MotorBus PublicTransport MotorVehicle Alternative notation Figure 4.4: Some sorts of vehicles In Java, interfaces are declared using the interface keyword. public interface PublicTransport { public int getCapacity(); } public interface MotorVehicle { public double getEnginePower(); } Use the implements keyword to indicate that a class implements an interface. public class MotorBus implements PublicTransport, MotorVehicle { public int getCapacity() { return 30; } public double getEnginePower() { public double return 154.2; } } 54 CHAPTER 4. INHERITANCE AND POLYMORPHISM The important issue about interfaces is that each interface defines a datatype that can be used to declare variables. Consider for instance the following code fragment. It contains two methods that take objects of type PublicTransport or MotorVehicle resp. as arguments. public void printCapacity(PublicTransport p) { System.out.println(p.getCapacity()); } public void printEnginePower(MotorVehicle m) { System.out.println(m.getEnginePower()); } The most difficult thing about interfaces is to explain why we need them. For me, the most important issue is that interfaces introduce a higher level of abstraction to a software project that improves software quality. In some sense, interfaces isolate areas of high complexity and thus help to split a software in smaller independent units. Chapter 5 Connecting Objects Chapter 4 was about inheritance which is a relationship amongst classes where one class adds something to an existing class. This chapter is about relations between objects that are established during the runtime of a program: associations, aggregation and composition. While inheritance is directly supported by the Java programming language (remember the keyword extends) there is no direct language support for associations, aggregation and composition: You program these types of relations by means of attributes. Since programming associations is best learned by example, in this chapter you will program • a catalog for a library (simple example); • classes to represent structures consisting of truss elements (more complex example). 5.1 Programming Associations / Aggregation / Composition Object-oriented programs consist of many cooperating classes. Thus, the classes must be related. The UML defines three different types of relationships: Associations represent relationships between instances of classes (a person works for a company; a pedestrian walks on a bridge; a bridge has several pedestrians walking on it). In a UML class diagram, an association is represented by a line connecting the two classes involved in the association. Aggregation is the part-of relationship. It’s like saying that a personal computer consists of a main board, a CPU, a hard disk and so on. In UML, aggregation is indicated by a line connecting the involved classes where the line has a diamond at the end of the aggregated class. Composition is a stronger form of aggregation. With composition, the part object may belong to only one whole; further the lifetime of the parts is usually the same as the lifetime of the whole. In UML, you draw composition like aggregation but the diamond is black. It is crucial to understand that the different types of relationships defined by the UML are used to specify the meaning of a relation (its semantics); from an implementation point of view, this distinction is less important: All these kinds of relationships are programmed by means of attributes. In UML, associations are the most general type of relations; aggregation and composition are more specialized types of associations. Therefore, in the following often the term association is used collectively for association, aggregation and composition. 55 CHAPTER 5. CONNECTING OBJECTS 56 For the implementation, multiplicities (i.e. the number of objects involved in an association) are much more important than the specific type of the association: Multiplicities determine the type of attribute you use the program the association. As a rule of thumb, you can use the following scheme: • Use a simple attribute to program an association with an individual object. Example: the author of a book. • Use an array for an association involving several objects when the number of objects does not change during the runtime of the program. Example: the seats in an airplane. • Use a resizable collection (e.g. java.util.ArrayList) for an association if the number of the involved objects changes during runtime. Example: the books in a library catalog. Please note, that this scheme is rather coarse and that the choice of an approach depends highly on the specific situation you have to deal with. Because of the wide variety of ways to program associations, it doesn’t seem possible to give a systematic introduction to this topic. Rather, the subject is discussed with two examples: Section 5.2 is about a catalog of books and Section 5.3 about structures consisting of truss elements. 5.2 Catalog of Books for a Library This simple example is about programming a catalog that stores the books of a library. The situation to program is the following: The catalog of a library consists of entries for each book the library contains. Over time new books are added to the catalog and other books (that got lost) are removed from the catalog. Each book has an author whereby we do not consider the situation where a book has more than one author. In order to write the program, first the UML class diagram shown in Figure 5.1 is established. The Catalog class is associated with an unspecified number of books (you could also argue, that the catalog consists of books and we should use aggregation). Furthermore, the method addBook adds the specified book to the catalog, the method removeBook removes the specified book from the catalog and the method print prints the content of the catalog. The Book class has an attribute for the title and an association with the person who wrote the book, i.e. the author. Both properties, the author and the title are passed to the constructor of the class. The Person class is straightforward and needs no explanation. Of course, the author could also be represented by an attribute of type String, but using a special class is more flexible. For instance, it could become necessary to also store the date of birth of the author. Catalog +addBook(b: Book): void +removeBook(b: Book): void +print(): void books 0..* Book -title: String +Book(author: Person, title: String) +getTitle(): String +getAuthor(): Person Figure 5.1: UML class diagram author 1 Person -name: String +Person(name: String) +getName(): String 5.2. CATALOG OF BOOKS FOR A LIBRARY 57 5.2.1 Java Implementation of the Classes In the following, one possible implementation of the above class diagram is presented. In order to get a better understanding, you may want to program the classes along with reading the text. The Person Class The Person class does not need any explanation. 1 public class Person { 2 private String name_; 3 4 public Person(String name) { name_ = name; } 5 6 7 8 public String getName() { return name_; } 9 10 11 12 } The Book Class In the Book class the association to the author is represented by the attribute author of type Person. Both attributes, the author and the title of the book are initialized in the constructor of the Book class. 1 public class Book { 2 private Person author_; private String title_; 3 4 5 public Book(Person author, String title) { author_ = author; title_ = title; } 6 7 8 9 10 public Person getAuthor() { return author_; } 11 12 13 14 public String getTitle() { return title_; } 15 16 17 18 } N OTE : Although the association with the Person class is represented by the attribute author that attribute does not appear in the UML diagram of the Book class. This is common practice: Attributes representing associations are usually not listed in the attribute section of the class. CHAPTER 5. CONNECTING OBJECTS 58 The Catalog Class The Catalog class is the most complicated one because we want to be able to add and remove books from the catalog. For such purposes, Java comes with the so-called collections framework. One class from this framework that is well suited for our purpose is the ArrayList class. Note that the classes from the collections framework are in the package java.util which must therefore be imported (Line 1). The association to the books is implemented by the attribute books which is of type ArrayList. Note that in the corresponding Line 5, the attribute is also initialized (i.e. an ArrayList object is constructed). The methods to add and remove a book are straightforward, we just add or remove the specified book to/from our list of books (look at the Javadoc of the ArrayList class for information about the methods add and remove). In the print method, each book stored in this catalog is processed. To find out the number of objects stored in an ArrayList, this class provides the method getSize. In order to retrieve an object from the collection, we can use the get method. The trick is to perform the explicit type conversion (Book) in Line 17 because the get method returns an object of type Object and not of type Book. In Java, the Object class is the base class of any any class that is not explicitly derived from another class. 1 import java.util.*; 2 3 public class Catalog { 4 private ArrayList books_ = new ArrayList(); 5 6 public void addBook(Book b) { books_.add(b); } 7 8 9 10 public void removeBook(Book b) { books_.remove(b); } 11 12 13 14 public void print() { for (int i = 0; i < books_.size(); i++) { Book b = (Book) books_.get(i); 15 16 17 18 System.out.println(b.getTitle() + " by " + b.getAuthor().getName()); 19 } 20 } 21 22 } N OTE : You can use the code in the Catalog class as a template if you have to deal with resizable collections. Of course, you have to adequately replace the class name Book in the explicit type conversion (Line 17). 5.3. TRUSS STRUCTURES 59 Testing the classes You can use the following code to test the classes. 1 public class CatalogTest { 2 public static void main(String[] args) { Catalog catalog = new Catalog(); 3 4 5 Person author1 = Book book1 = new Book book2 = new Person author2 = Book book3 = new Person author3 = Book book4 = new 6 7 8 9 10 11 12 new Person("Herman Melville"); Book(author1, "Moby Dick"); Book(author1, "Pierre"); new Person("Sylvia Plath"); Book(author2, "The Bell Jar"); new Person("Isaac B. Singer"); Book(author3, "Short Friday and Other Stories"); 13 catalog.addBook(book1); catalog.addBook(book2); catalog.addBook(book3); catalog.addBook(book4); 14 15 16 17 18 System.out.println("The catalog contains:"); catalog.print(); 19 20 21 System.out.println("Removed one book from the catalog:"); catalog.removeBook(book2); catalog.print(); 22 23 24 } 25 26 } 5.3 Truss Structures This section is about developing a collection of classes to represent structures consisting of truss elements. By using the classes, it should be possible to define a structure of truss elements to calculate the volume and to draw the structure in 3D. Figure 5.2 shows an instance of such a structure. Figure 5.2: Curved Girder CHAPTER 5. CONNECTING OBJECTS 60 5.3.1 Designing the classes In order to design the classes, let’s start with a description of a truss structure: A truss structure consists of cross sections, nodes and elements. Each element has a cross section and two nodes. There are different types of cross sections: rectangular cross sections, circular cross sections and so on. Based upon this description, the class diagram in Figure 5.3 has been created. Note that description and the class diagram are closely related. TrussStructure +addNode(x : double, y : double, z : double) : void +addCrossSection(cs : CrossSection) : void +addElement(idxN1 : int, idxN2 : int, idxCS : int) : void +draw() : void +getVolume() : double 0..* elements Element +Element(n1 : Node, n2 : Node, cs : CrossSection) +getVolume() : double +draw() : void 0..* crossSections 1 crossSection 2 nodes CrossSection 0..* nodes Node +getArea() : double +getOutline() : double[][] -x : double -y : double -z : double +Node(x : double, y : double, z : double) +distance(n : Node) : double +getX() : double +getY() : double +getZ() : double RectangularCS ... Figure 5.3: UML Class Diagram 5.3. TRUSS STRUCTURES 61 5.3.2 Assignment: Implementing the Classes in Java It is now up to you to implement the classes in Figure 5.3 in Java. Of course it is intended, that you reuse the classes for the cross sections you programmed in the last chapter. Take the following steps as a guideline. Step 1: Preliminaries In order to use the existing cross section classes, you have to adjust the settings for your current project in eclipse. In order to do so, right click your current project and choose “Properties”. In the properties dialog select “Java Build Path” on the left and then the “Projects” tab on the right. Click the “Add...” button and select your project which contains the sections. Next, create a new class TrussStructureTest and add a main-method. This is the place where your testing code goes. Step 2: The Node Class The Node class is very similar to the Vector3D class – implement it on your own. You can use the following code fragment (put it in the main-method of your TrussStructureTest class) to test your Node class: Node n1 = new Node(0, 0, 0); Node n2 = new Node(1, 2, 3); System.out.println("Distance: " + n2.distance(n1)); System.out.print("n2: "); System.out.println(n2.getX() + ", " + n2.getY() + ", " + n2.getZ()); The output should look like Distance: 3.7416573867739413 n2: 1.0, 2.0, 3.0 Of course, you can also implement all the classes first and then start to test them. But generally, testing the classes individually and early makes it easier to find errors, especially if the program is more complex. By the way, in professional software projects testing takes as much time as the programming part itself. Step 3: The Element Class The Element class is more complex. You will need attributes to represent the associations with the CrossSection object and the two Node objects. Decide on your own, whether you want to use an attribute nodes of type Node[] or two attributes node1 and node2 for the association with the two nodes of the element. The attributes are initialized in the constructor. You use the attributes in the getVolume method to calculate the volume of the element. In the draw method, you create an Extrusion object for the graphical representation (import the inf.view3d package). The following code is an example of how the Element class can be used. Viewer viewer = new Viewer(); Node n1 = new Node(0, 0, 0); Node n2 = new Node(1, 0, 1); RectangularCS cs = new RectangularCS(0.1, 0.2); Element e1 = new Element(n1, n2, cs); CHAPTER 5. CONNECTING OBJECTS 62 System.out.println("Volume: " + e1.getVolume()); e1.draw(); viewer.setVisible(true); Step 4: The TrussStructure Class In the TrussStructure class, you can use three attributes of the type ArrayList to program the relationships with nodes, cross sections and elements (have a look at the Catalog class for a reminder). The method addCrossSection works similar to the addBook method of the Catalog class. In the addNode method, you first create a new Node object according to the specified coordinates and then add it to the ArrayList attribute for the nodes. The addElement method takes the indexes of the two nodes and the index of the cross section as arguments. Here, the index corresponds to the sequence of addition, i.e. the first node that was added has the index 0. Inside the addElement method, you first retrieve the nodes and the cross section (as specified by the indexes in the parameter list) and store them in local variables. Use the get method of the ArrayList class and don’t forget the explicit type conversion. Second, you create the new Element object which is finally added to the list of elements. The methods draw and getVolume involve a loop over all elements. This code fragment shows, how the TrussStructure class can be used: Viewer viewer = new Viewer(); TrussStructure ts = new TrussStructure(); CircularCS ccs = new CircularCS(0.01); IShapedCS iscs = new IShapedCS(0.05, 0.1, 0.001, 0.005); ts.addCrossSection(iscs); ts.addCrossSection(ccs); ts.addNode(0.0, 0.0, 0.0); ts.addNode(1.0, 0.0, 1.0); ts.addNode(2.0, 0.0, 0.0); ts.addElement(0, 1, 0); ts.addElement(1, 2, 0); ts.addElement(0, 2, 1); System.out.println("The volume is: " + ts.getVolume()); ts.draw(); viewer.setVisible(true); Step 5: Create Your Own Structure Make a sketch of a interesting structure that comes into your mind. Establish a model of that structure using the classes you have programmed. 5.3. TRUSS STRUCTURES 63 5.3.3 Reading a Structure from a File Often it is more convenient to store the description of a structure in a text file than writing a program. In order to do so, we first have to decide about the file format. Here, a format that is similar to that of the Finite Element software Ansys is used. Example: n, 0.0, 0.0, 0.0 n, 1.0, 0.0, 1.0 rcs, 0.05, 0.1 e, 0, 1, 0 The syntax is as follows: every line of the input file describes one item of the structure (i.e. a node, an element, a cross section). Each line consists of several entries separated by commata. The first entry is the key: n stands for node, rcs for rectangular cross section and e for element (of course, the syntax has to be extended for other cross section types). The other entries describe the item itself. The following code is a starting point for your implementation: 1 import java.io.*; 2 3 public class TrussStructureReader { 4 public static TrussStructure read(String filename) throws Exception { TrussStructure ts = new TrussStructure(); BufferedReader reader = new BufferedReader(new FileReader(filename)); String line = reader.readLine(); 5 6 7 8 9 while (line != null) { String[] items; 10 11 12 // remove trailing and leading spaces line = line.trim(); 13 14 15 // split line at commata items = line.split("\\s*,\\s*"); 16 17 18 if (items.length == 0) { 19 20 } else if (items[0].equals("n")) { 21 22 23 } // other cases go here 24 25 26 line = reader.readLine(); 27 } 28 29 return ts; 30 } 31 32 } Complete the code above! Useful methods: Integer.parseInt and Double.parseDouble to convert strings into numbers. CHAPTER 5. CONNECTING OBJECTS 64 5.3.4 Example for an Object Diagram An object-oriented program running on a computer consists of a number of collaborating objects residing in the computer’s memory. In order to visualize the state of a program at a certain point in time, we can employ object diagrams depicting existing objects and connections between objects. Object diagrams are specified in the UML (Unified Modeling Language) and have certain similarities with class diagrams. In this course, we use a notation which differs slightly from the UML because we depict object variables and object attributes as pointers to objects. In the book “Core Java, Volume 1 - Fundamentals” by Horstmann and Cornell you can find a similar notation. Face Edge Vertex Figure 5.4: A mesh consisting of faces Object diagrams – as we use them – are explained at hand of an example: Consider a mesh consisting of several faces (see Fig. 5.4 and http://en.wikipedia.org/wiki/Polytope for further information). A face is bounded by a certain number of edges where an edge has a startpoint and an endpoint. In the UML class diagram in Fig. 5.5 we introduce four corresponding classes. A Mesh consists of an arbitrary number of Face objects and has a method addFace which adds an additional Face to the mesh. A Face object is associated with an arbitrary number of Edge objects but at least three edges are required. Accordingly, the constructor of the Face class takes an array of Edge objects. An Edge is associated with exactly two Vertex objects such that the constructor of Edge expects two variables of type Vertex. The Vertex class has two double attributes to store the location and a constructor taking two double parameters. Mesh 0..* +addFace(f: Face): void Face +Face(edges: Edge[]) 3..* Edge +Edge(v1: Vertex, v2: Vertex) 2 Vertex -x: double -y: double +Vertex(x: double, y: double) Figure 5.5: UML class diagram for the mesh classes 5.3. TRUSS STRUCTURES 65 A possible implementation of the classes in Java is given below. Note that we use an ArrayList to store the faces of the Mesh but an array of Edge objects in the Face class. The reason for this decision is that faces can be added after a mesh has been created while the number of edges does not change after a face has been constructed. 1 public class Vertex { 2 private double x_; private double y_; 3 4 5 public Vertex(double x, double y) { x_ = x; y_ = y; } 6 7 8 9 10 1 } public class Edge { 2 private Vertex v1_; private Vertex v2_; 3 4 5 public Edge(Vertex v1, Vertex v2) { v1_ = v1; v2_ = v2; } 6 7 8 9 10 1 } public class Face { 2 private Edge[] edges_; 3 4 public Face(Edge[] edges) { edges_ = edges; } 5 6 7 8 } 1 import java.util.ArrayList; 2 3 public class Mesh { 4 private ArrayList<Face> faces_ = new ArrayList<Face>(); 5 6 public void addFace(Face f) { faces_.add(f); } 7 8 9 10 } CHAPTER 5. CONNECTING OBJECTS 66 Consider the main-program given in the listing below which is supposed to create the mesh shown in Fig. 5.4. Note that lines 16 and 17 show the shorthand form of array declaration and initialization. 1 public class MeshTest { 2 public static void main(String[] args) { Mesh m = new Mesh(); Vertex v1 = new Vertex(0, 0); Vertex v2 = new Vertex(1, 0); Vertex v3 = new Vertex(2, 0); Vertex v4 = new Vertex(0, 1); Vertex v5 = new Vertex(1, 1); Edge e1 = new Edge(v1, v2); Edge e2 = new Edge(v2, v3); Edge e3 = new Edge(v1, v4); Edge e4 = new Edge(v2, v5); Edge e5 = new Edge(v4, v5); Edge e6 = new Edge(v5, v3); Edge[] es1 = { e1, e4, e5, e3 }; Edge[] es2 = { e2, e6, e4 }; Face f1 = new Face(es1); Face f2 = new Face(es2); m.addFace(f1); m.addFace(f2); } 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 } Mesh faces Edge v1 v2 Face edges Edge v1 v2 ArrayList Face edges Edge v1 v2 Edge v1 v2 Edge v1 v2 Edge v1 v2 m es1 es2 es1 es2 e1 e2 e3 e4 e5 e6 Figure 5.6: Mesh objects in an object diagram Vertex x=0 y=0 Vertex x=1 y=0 Vertex x=2 y=0 Vertex x=0 y=1 Vertex x=1 y=1 v5 v4 v3 v2 v1 5.3. TRUSS STRUCTURES 67 The state of the program at the end of the main method is shown in the object diagram in Fig. 5.6. In the diagram, we have two regions separated by a dashed line. • The lower region contains the variables we use in the part of the program we are investigating. Variables point to objects which are shown in the upper area. • The upper region contains the objects we have created in our program. An object is depicted as a box with two compartments. The upper compartment contains the type name underlined while the lower compartment contains attributes. Values of primitive type attributes are given directly (as with the Vertex objects) while object type attributes simply point to another object. Arrays are depicted as strips of boxes whereas the boxes of object type arrays hold pointers to other objects. Let us stop the main method at certain points of execution and discuss what happened. Line 4 We have created one Mesh object and the variable m points to this object. In line 5 of the file Mesh.java, the attribute faces of type ArrayList is declared and the corresponding object is created. Therefore, in the diagram, we have an additional ArrayList object and the faces attribute of our Mesh object points to this ArrayList object. Line 9 Several Vertex objects have been created. The attributes contain the coordinates specified during object construction. Line 15 The required Edge objects have been created. In the constructor, the vertices of the edges are specified and therefore the v1 and v2 attributes of the Edge objects point to the corresponding Vertex objects. Line 17 We have created two arrays of Edge objects using a shorthand notation where the array content is specified in just one line. The array elements hold pointers to the corresponding Edge objects. Alternatively to the shorthand notation, we could have written 1 2 3 4 Edge[] es2 = new Edge[3]; es[0] = e2; es[1] = e6; es[2] = e4; for the second edges array, for instance Line 19 Two Face objects have been created and the arrays of edges have been passed to the constructor. Therefore, the edges attributes of the Face objects point to the corresponding array objects. Line 21 The two faces have been added to the mesh and the ArrayList object holds pointers to the two Face objects. Since we are not interested in the way, the ArrayList stores the pointers (encapsulation), we just draw pointers from the ArrayList box to the Face objects. 68 CHAPTER 5. CONNECTING OBJECTS Chapter 6 Programming Graphical User Interfaces 6.1 Introduction Nowadays, a graphical user interface (GUI) is the standard paradigm for realizing the interaction between the user and an application. Other than for instance C++, Java includes a rich set of classes for the realization of Gui’s. The standard Java class library contains mainly two packages for Gui’s that can be used alternatively: 1. The Abstract Windowing Toolkit (AWT) is in the java.awt package. AWT was introduced in the first version of Java and contains only the most essential classes. 2. The classes of the so-called Swing toolkit are in the package javax.swing. Swing was introduced later in Java 1.2 and provides a richer set of classes. Using Swing, it is possible to build professional and nice looking Gui’s. Note that Swing uses many classes from AWT. In this course, we will use Swing. Swing contains about 140 classes and interfaces. In this chapter, you will learn about the most basic concepts used in Swing. For further information, the Swing tutorial (http://java.sun.com/docs/books/tutorial/uiswing) provides an excellent introduction, it covers a wide range of problems and contains many example programs. Moreover, programming Swing without access to the javadoc documentation is impossible! 69 CHAPTER 6. PROGRAMMING GRAPHICAL USER INTERFACES 70 6.2 Swing Components Graphical applications consist of components like windows, buttons, text fields, scrollbars, checkboxes etc. All these components are in Java represented by classes. Figure 6.1 shows some important Swing classes. Note that the diagram also contains AWT classes (those that do not start with a J) because Swing sits on top of AWT. It is also obvious, that the design of AWT/Swing makes strong use of inheritance. Graphics Component 1 +setVisible(v: boolean): void +getGraphics(): Graphics +addMouseListener(l: MouseListener): void +addMouseMotionListener(l: MouseMotionListener): void ... +setColor(c: Color): void +drawLine(...): void ... 0..* Container Canvas ... +setLayout(l: LayoutManager): void +add(c: Component): Component ... JPanel ... Window JTabbedPane JComponent +addTab(t:String, c:Component): void ... ... ... Frame ... JFrame ... AbstractButton JLabel +addActionListener(l: ActionListener): void ... +JLabel(l: String) ... JButton +JButton(t: String) ... JComboBox +JComboBox(enries: Object[]) +getSelectedItem(): Object ... JList +JList(enries: Object[]) +getSelectedValue(): Object ... JToggleButton +JToggleButton(t: String) ... JCheckBox +JCheckBox(t: String) +isSelected(): boolean ... Figure 6.1: Essential Swing classes Component The base class for everything that can be drawn on the screen. A Component is associated with a Graphics object on which the Component paints its graphical representation. Also, a Component captures mouse events (see event handling later in this chapter). JFrame A window on the screen with border and title. 6.2. SWING COMPONENTS 71 JPanel A container that you can use to layout components inside a frame. JTabbedPane A Component that holds other components in tabs. JLabel A short piece of text that is drawn on the screen. JButton A button with a label that can be clicked. JCheckBox A component that is either selected or not. JComboBox A component that contains a set of selectable items (usually strings). 6.2.1 Using Swing Components In order to get an idea of using Swing components, enter the code below and check out what happens if you run it. import java.awt.*; import javax.swing.*; public class SwingComponents extends JFrame { private private private private private JList list_; JComboBox comboBox_; JButton button_ = new JButton("JButton"); JCheckBox checkBox_ = new JCheckBox("JCheckbox"); JTextField textField_ = new JTextField("JTextfield"); public static void main(String[] args) { new SwingComponents().setVisible(true); } public SwingComponents() { String[] listEntries = { "Concrete", "Cast Iron", "Masonry" }; // create more complex components list_ = new JList(listEntries); comboBox_ = new JComboBox(listEntries); // layout components setLayout(new FlowLayout()); add(textField_); add(list_); add(button_); add(comboBox_); add(checkBox_); // configure setDefaultCloseOperation(EXIT_ON_CLOSE); setSize(300, 300); } } Listing 6.1: Using Swing components 72 CHAPTER 6. PROGRAMMING GRAPHICAL USER INTERFACES The class is typical for the main window of a swing application: 1. It inherits from JFrame and thus represents a window on the desktop. 2. Attributes represent the visual components of the application. If possible, components are immediately constructed. 3. In the constructor of the class, • components that need additional information are constructed (list and combo-box). • components are added to the frame. The add method is defined in the Container class. • we specify that the application should exit when the window closes. • using setSize, a certain size for the window is set. 4. In the main method, we solely create the frame and make it visible. 6.3 Laying out Components Have a look at the Container class in Figure 6.1. A Container object is a special Component that contains other components (indicated by the association in the class diagram). In order to arrange the components in a container, objects that implement the LayoutManager interface are used. Figure 6.2 shows three commonly used layout managers (there are more). <<interface>> LayoutManager GridLayout +GridLayout(rows: int, cols: int) ... BorderLayout +NORTH: String +WEST: String +CENTER: String +EAST: String +SOUTH:String +BorderLayout() ... CENTER SOUTH Figure 6.2: Three Layout Managers EAST NORTH WEST FlowLayout +LEFT: int +CENTER: int +RIGHT: int +FlowLayout(alignment: int) ... 6.3. LAYING OUT COMPONENTS 73 N OTE : In Figure 6.2, the LayoutManager box is labeled with the stereotype interface. You can think of an interface as an abstract class that contains only abstract methods and no (instance) attributes. Classes that provide the methods specified in an interface are said to implement that interface. The UML notation of implementation is similar to inheritance, but a dashed line is used. Flow Layout A flow layout arranges components in a left-to-right flow, much like letters in a word while components retain their natural size. Using a parameter in the constructor, you can specify the orientation of components. The example 6.1 already uses a flow layout. Check out another orientation by specifying 1 setLayout(new FlowLayout(FlowLayout.RIGHT)); in the SwingComponents constructor. Grid Layout A grid layout arranges the components in an equally-sized grid, components are resized as needed. The number of rows and columns of the grid is specified in the constructor of the GridLayout class. Use 1 setLayout(new GridLayout(3, 2)); to see how a grid layout manager works. Border Layout A border layout manager arranges components in five compartments: north, west, center, east and south. The way how the component’s size is handled is special: Components in the north and south retain natural height but are scaled in horizontal direction. The components in the west and east retain natural width while being scaled in the height. Finally, the component in the center fills the remaining space. Often, not all compartments are occupied. When using a border layout, the position of the component is specified using constants defined in the BorderLayout class as second argument to the add method. Check out 1 2 3 4 5 6 setLayout(new BorderLayout()); add(textField_, BorderLayout.NORTH); add(list_, BorderLayout.WEST); add(button_, BorderLayout.CENTER); add(comboBox_, BorderLayout.EAST); add(checkBox_, BorderLayout.SOUTH); to understand the border layout. 6.3.1 Combining Layout Managers In most situations, user interfaces are more complex than the above examples. In such situations you can use the GridBagLayout layout manager which is more powerful but at the same time CHAPTER 6. PROGRAMMING GRAPHICAL USER INTERFACES 74 notoriously complicated to use. The alternative is to use combinations of the above layout managers. Here, several JPanel objects each having a suitable layout manager are combined. Listing 6.2 demonstrates this approach at hand of a graphical user interface for calculating the greatest common divisor of two integers. Enter the code to see how it works. import java.awt.*; import javax.swing.*; public class GraphicalGCD extends JFrame { public static void main(String[] args) { new GraphicalGCD().setVisible(true); } private private private private private JTextField nominatorTF_ = new JTextField(9); JTextField denominator_ = new JTextField(9); JLabel resultLabel_ = new JLabel(); JButton computeButton_ = new JButton("Compute"); JButton exitButton_ = new JButton("Exit"); public GraphicalGCD() { setLayout(new BorderLayout()); // panel for textfields JPanel p1 = new JPanel(new GridLayout(3, 2)); p1.add(new JLabel("Nominator:", JLabel.RIGHT)); p1.add(nominatorTF_); p1.add(new JLabel("Denominator:", JLabel.RIGHT)); p1.add(denominator_); p1.add(new JLabel("GCD:", JLabel.RIGHT)); p1.add(resultLabel_); add(p1, BorderLayout.CENTER); // buttons JPanel p4 = new JPanel(new FlowLayout(FlowLayout.RIGHT)); p4.add(computeButton_); p4.add(exitButton_); add(p4, BorderLayout.SOUTH); // configure setDefaultCloseOperation(EXIT_ON_CLOSE); pack(); } } Listing 6.2: Combining layout managers 6.4. EVENT HANDLING 75 Here’s some explanation: In the constructor of the GraphicalGCD class, JPanel p1 is used to hold text fields along with some description. This panel uses a grid layout which arranges the components row by row in the sequence they have been added. JPanel p2 arranges the two buttons aligned to the right hand side. 6.4 Event Handling In order to add some functionality to the graphical GCD program, you have to handle events invoked by the user of the program. The classes and interfaces involved in the event handling for a button are shown in Figure 6.3. JButton 0..* +addActionListener(l: ActionListener): void ... <<interface>> ActionListener +actionPerformed(e: ActionEvent): void ActionEvent +getSource(): Object ... Figure 6.3: Button and Event Listener A button is associated with a list of objects the implement the ActionListener interface. Listeners are added to the button using the addActionListener method. When the button is clicked by the user, it invokes the actionPerformed method for each listener that has been previously added. An ActionEvent object is passed to the listener. Using the getSource method of the event object, the listener can determine which object initiated the event. Implementing the ActionListener Interface For our graphical GCD example, we first have to implement the ActionListener interface. This is conveniently done in the current class i.e. GraphicalGCD. By changing the class header to 1 public class GraphicalGCD extends JFrame implements ActionListener { we specify, that the GraphicalGCD class implements the ActionListener interface. Next, the actionPerformed method specified in the ActionListener interface must be implemented. Let us start with a simple “dummy” implementation that just prints out a string: 1 2 3 public void actionPerformed(ActionEvent e) { System.out.println("Action performed"); } Registering the Listeners Next, we have to tell the two buttons, that the program wants to be notified when buttons are clicked. In order to do so, add the following two lines to the constructor of the GraphicalGCD class: CHAPTER 6. PROGRAMMING GRAPHICAL USER INTERFACES 76 1 2 computeButton_.addActionListener(this); exitButton_.addActionListener(this); If you now run your program, the string “Action performed” should appear on the console when you click one of the two buttons. Getting the Event Source For the correct behavior of the program, we finally have to distinguish whether the “Compute” or the “Exit” button has been clicked. For that, the ActionEvent class provides a method getSource that returns the object that caused the event. Add the following code to the actionPerformed method. Note that the exit method of the System class is used to terminate the application. 1 2 3 4 public void actionPerformed(ActionEvent e) { if (e.getSource() == computeButton_) { String result = nominatorTF_.getText() + " and " + denominatorTF_.getText(); 5 resultLabel_.setText(result); } else if (e.getSource() == exitButton_) { System.exit(0); } 6 7 8 9 10 } Assignment: Implement the computation of the greatest common divisor correctly. Use Integer.parseInt to convert the content of the text fields into a number. 6.5. THE MODEL-VIEW-CONTROLLER PATTERN 77 6.5 The Model-View-Controller Pattern For the implementation of graphical user interfaces, the so called model-view-controller (MVC) pattern has proven to be a practical way to organize responsibilities within the application. In this section, we develop an interactive program to create and manipulate truss-structures (previous chapter) using the MVC approach. The desired functionality of the program is as follows: • The structure is modified using textual commands issued in a command line (similar to e.g. AutoCAD or Ansys). • A graphical area shows a 3D representation of the structure. • Additionally, nodes, sections and elements are textually represented in a table. Figure 6.4 shows a possible layout of the program. listing of nodes... graphical display command line Figure 6.4: Sketch of an interactive truss-structure program According to the MVC pattern, the application logic is split into Model, View and Controller: Model The domain specific information on which the application operates. In our case, the model is the truss structure. View Renders the model to the user, either in a graphical or a textual representation. In our application, we have a graphical view of the model and several textual views. Controller The controller takes input from the user and modifies the model accordingly. Obviously, the command line is the controller in our case. The most important aspect of MVC is that the model keeps a list of associated views and provides a method that notifies all views about a change of the model. This makes it easy to add views without having to change much code. Basically, the control flow in a MVC application is as follows: 1. The user interacts with the application (enters a command, clicks a button, . . . ). 2. A controller receives the user input and modifies the model (eg. adds a node or an element). Then, the controller asks the model to inform the views that something has changed. CHAPTER 6. PROGRAMMING GRAPHICAL USER INTERFACES 78 3. The views retrieve the current state of the model and update themselves accordingly. Figure 6.5 shows the MVC pattern in its simplest form. interacts Controller user modifies notifies View Model retrieves state Figure 6.5: Model-view-controller, simple version 6.5.1 Step 1: Preliminaries Before we can start with the graphical application, the truss structure classes from the previous chapter have to be extended (you can download everything discussed here from the course web-site). First of all, we need an interface, that specifies a view on a truss structure: public interface TrussStructureView { public void update(TrussStructure ts); } Listing 6.3: Truss structure view interface Next, we have to add functionality to the truss-structure class. The TrussStructure class needs the list of views, a method to add a view and another method to notify all views. 1 private ArrayList views_ = new ArrayList(); 2 3 4 5 public void addView(TrussStructureView view) { views_.add(view); } 6 7 8 9 public void notifyViews() { for (int i = 0; i < views_.size(); i++) { TrussStructureView view = (TrussStructureView) views_.get(i); 10 view.update(this); 11 } 12 13 } 6.5. THE MODEL-VIEW-CONTROLLER PATTERN 79 In order to generate the various views of the truss structure, we need several methods to access the elements of the truss structure. Therefore, the following methods have to be added to the TrussStructure class: 1 2 3 4 5 public void clear() { crossSections_.clear(); nodes_.clear(); elements_.clear(); } 6 7 8 9 public int countCrossSections() { return crossSections_.size(); } 10 11 12 13 public CrossSection getCrossSection(int idx) { return (CrossSection) crossSections_.get(idx); } 14 15 16 17 public int countNodes() { return nodes_.size(); } 18 19 20 21 public Node getNode(int idx) { return (Node) nodes_.get(idx); } 22 23 24 25 public int countElements() { return elements_.size(); } 26 27 28 29 public Element getElement(int idx) { return (Element) elements_.get(idx); } 30 31 32 33 public int indexOf(Node node) { return nodes_.indexOf(node); } 34 35 36 37 public int indexOf(CrossSection cs) { return crossSections_.indexOf(cs); } Also, the Element class has to be extended: 1 2 3 4 5 6 7 8 9 public CrossSection getCrossSection() { return crossSection_; } public Node getNode1() { return node1_; } public Node getNode2() { return node2_; } CHAPTER 6. PROGRAMMING GRAPHICAL USER INTERFACES 80 Finally, we need a class that interprets commands issued on the command line. The idea is, to use a simple syntax of single commands where the arguments are separated by commas. For instance, the sequence 1 2 3 4 5 n,0,0,0 n,5,0,0 ccs,0.1 e,0,1,0 save,struct.txt should create two nodes, a circular section and an element. Finally the structure is saved to a file ”struct.txt”. Note that the syntax is similar to the one used by the commercial FE-Code Ansys. For the purpose of interpreting user commands, the Interpreter class is introduced. Here’s a fragment of the class: 1 public class Interpreter { 2 public static void executeCommand(String command, TrussStructure ts) { String[] args = command.split(","); String cmd = args[0].trim(); 3 4 5 6 try { // a node if (cmd.equals("n")) { double x = Double.parseDouble(args[1]); double y = Double.parseDouble(args[2]); double z = Double.parseDouble(args[3]); 7 8 9 10 11 12 13 ts.addNode(x, y, z); } // other commands omitted } catch (Exception e) { System.out.println("Illegal command: " + command); } 14 15 16 17 18 19 } 20 21 } Download the complete class from our course pages. The remaining part of this chapter is handed out next exercise! 6.5. THE MODEL-VIEW-CONTROLLER PATTERN 81 Commands understood by the interpreter Currently, the interpreter understands the following commands: n,x,y,z Create a node at the specified location rcs,w,h Create a rectangular section of the specified width and height ccs,r Create a circular section of the specified radius e,idxN1,idxN2,idxCs Create an element using the specified node and the specified section save,filename Saves the current structure to the specified file open,filename Reads the commands in the specified file In the case that you want to use your own cross-section classes (e.g. I-shaped), extend the interpreter class accordingly. CHAPTER 6. PROGRAMMING GRAPHICAL USER INTERFACES 82 6.5.2 Software Design The software design for the truss-structure editor is shown in Figure 6.6. The TrussStructureEditor class represents the application frame on the screen and is at the same time the entry point to the program (main-method). According to the interface design in Figure 6.4, a truss structure editor contains a CommandPanel, a GraphicalView and a SidePanel. The CommandPanel is the controller that modifies the TrussStructure object representing the model in the MVC application. After each user interaction, the controller tells the model to notify all dependent views to update using the notifyViews method. TrussStructure JFrame +addView(v: TrussStructureView): void +notifyViews(): void ... 0..* <<interface>> TrussStructureView +update(ts: TrussStructure): void JPanel 1 TrussStructureEditor +main(args: String[]): void +TrussStructureEditor() CommandPanel 1 +CommandPanel(ts: TrussStructure) VTKCanvas GraphicalView 1 +GraphicalView(ts: TrussStructure) +update(ts: TrussStructure): void ListSectionsPanel +ListSectionPanel(ts: TrussStructure) 1 +update(ts: TrussStructure): void 1 ListNodesPanel SidePanel +SidePanel(ts: TrussStructure) 1 +ListNodesPanel(ts: TrussStructure) +update(ts: TrussStructure): void ListElementsPanel 1 +ListElementsPanel(ts: TrussStructure) +update(ts: TrussStructure): void Figure 6.6: UML class diagram of the truss structure editor application 6.5. THE MODEL-VIEW-CONTROLLER PATTERN 83 In the GraphicalView, the truss-structure is rendered. Because the class represents a view in the MVC application, it implements the TrussStructureView interface. Moreover, it inherits from VTKPanel – an existing class in the VTK package – and is therefore capable of generating a 3D view of the model. Finally, the SidePanel is shown at the left-hand side of the application frame and contains listings of cross-sections, nodes and elements. These listings are contained in the ListSectionsPanel, ListNodesPanel and ListElementsPanel objects. Again, these classes implement the TrussStructureView interface. 6.5.3 Implementation The following pages will guide you step by step through the implementation of the truss-structure editor application. The TrussStructureEditor class Let us start with the main class of the application; it will be extended later as needed. Create the class according to the class diagram. In the main-method, create the frame and set it visible. For the moment, the constructor just creates a truss-structure object, specifies a layout manager and does some configuration: 1 2 public TrussStructureEditor() { TrussStructure ts = new TrussStructure(); 3 setLayout(new BorderLayout()); 4 5 setSize(800, 600); setDefaultCloseOperation(EXIT_ON_CLOSE); 6 7 8 } Run your application for the first time; you should see an empty frame. The CommandLinePanel class The CommandLinePanel class basically contains a JTextField that captures the user input and registers itself as ActionListener at the JTextField which raises an ActionEvent when the user hits the return-key. Now implement the CommandLinePanel class; it inherits from JPanel and implements the ActionListener interface. The text field is stored in an attribute. In the constructor, • the panel registers itself as action listener at the text field. • the textfield is added to the panel. Use a GridLayout with 1 × 1 cells such that the textfield fills the entire panel. • Finally, don’t forget the association to the truss structure object. In the actionPerformed method, the content of the text field is used as a command to the Interpreter.executeCommand method. Then, the text field is cleared and the observers of the truss structure are notified. CHAPTER 6. PROGRAMMING GRAPHICAL USER INTERFACES 84 Here’s the corresponding code (adapt the attribute names to your choice): 1 2 public void actionPerformed(ActionEvent arg0) { Interpreter.executeCommand(textField_.getText(), structure_); 3 textField_.setText(""); structure_.notifyViews(); 4 5 6 } After you completed the class, include it in the constructor of the TrussStructureEditor class and add it to the frame in the south-compartment of the border layout. Run the application and issue some commands in the command line. The GraphicalView class Step 1 The graphical view shows the nodes and the elements of the truss structure. According to the UML class-diagram in Figure 6.6, the base class for GraphicalView is VTKPanel which is capable of rendering 3D objects; also it implements the TrussStructureView interface. Here’s a starting point for the implementation of the class: 1 2 3 import java.util.*; import inf.vtk.*; import inf.v3d.obj.*; 4 5 public class GraphicalView extends VTKCanvas implements TrussStructureView { 6 public GraphicalView(TrussStructure ts) { ts.addView(this); } 7 8 9 10 public void update(TrussStructure ts) { // remove all objects currently visible GetRenderer().RemoveAllViewProps(); 11 12 13 14 // create 3D objects updateNodes(ts); updateElements(ts); 15 16 17 18 // update view resetCamera(); Render(); 19 20 21 } 22 23 private void updateNodes(TrussStructure ts) { System.out.println("Updating nodes"); } 24 25 26 27 private void updateElements(TrussStructure ts) { System.out.println("Updating elements"); } 28 29 30 31 } In the constructor, the graphical view just adds itself as view to the model. In the update method, first all element that are currently visible are removed. Then, nodes and elements are updated and 6.5. THE MODEL-VIEW-CONTROLLER PATTERN 85 finally, the view is adjusted and the elements are rendered. Enter the program code listed above and add a GraphicalView object to the center of your TrussStructureEditor frame. Run your program; when you enter commands in the command line, you should see the output of the print statements above. Step 2 The next step is to implement the updateNodes method. In order to represent the nodes visually, we use Sphere objects that are located at the nodes’ positions. The following code does the job: 1 2 3 4 private void for (int i Node n = Sphere s updateNodes(TrussStructure ts) { = 0; i < ts.countNodes(); i++) { ts.getNode(i); = new Sphere(n.getX(), n.getY(), n.getZ()); 5 s.setRadius(0.1); s.setColor("BLUE"); GetRenderer().AddActor(s.getActor()); 6 7 8 } 9 10 } Note the last line: Here, we have to use VTK methods in order to make the sphere visible on the screen. Step 3 Finally, program the updateElements method yourself. Don’t forget to add the actor of the extrusion object to the renderer. CHAPTER 6. PROGRAMMING GRAPHICAL USER INTERFACES 86 The ListNodesPanel class The purpose of the ListNodesPanel class is to provide a textual representation of nodes. Such information can be conveniently presented using a JTable object (JTable is a powerful class that is part of Swing). Here is the code of the ListNodesPanel class: 1 2 3 import java.awt.*; import javax.swing.*; import javax.swing.table.*; 4 5 public class ListNodesPanel extends JPanel implements TrussStructureView { 6 private JTable table_ = new JTable(); 7 8 public ListNodesPanel(TrussStructure structure) { structure.addView(this); 9 10 11 // add components setLayout(new GridLayout(1, 1)); add(new JScrollPane(table_)); setBorder(BorderFactory.createTitledBorder("Nodes")); 12 13 14 15 } 16 17 public void update(TrussStructure structure) { int nn = structure.countNodes(); DefaultTableModel tm = new DefaultTableModel(nn, 4); String[] cis = { "Id.", "X", "Y", "Z" }; 18 19 20 21 22 tm.setColumnIdentifiers(cis); for (int i = 0; i < nn; i++) { Node n = structure.getNode(i); 23 24 25 26 tm.setValueAt("" + i, i, 0); tm.setValueAt(n.getX(), i, 1); tm.setValueAt(n.getY(), i, 2); tm.setValueAt(n.getZ(), i, 3); 27 28 29 30 } table_.setModel(tm); 31 32 } 33 34 } The JTable object associated with our ListNodesPanel is as usual stored in an attribute. In the constructor, first the ListNodesPanel adds itself as a view to the structure. Then, a GridLayout object is specified as layout manager. Because 1 × 1 cells are used for the grid layout, the JTable added in the next line fills the complete panel. When adding the table, it is embedded in a JScrollPane objects that adds scrollbars to the table if needed. Finally, a titled border is specified for the panel. The update method populates the table with the node coordinates. Interestingly, the JTable class follows the MVC pattern: The data is stored in an object that implements the TableModel interface. The corresponding JTable object renders the data on the screen. Moreover it can also be used to modify the data. Therefore, a JTable object is at the same time view and controller. In the update method, an object of type DefaultTableModel is used to store the table data. 6.5. THE MODEL-VIEW-CONTROLLER PATTERN 87 The DefaultTableModel class is a straightforward default implementation of the TableModel interface. When the DefaultTableModel is constructed, the number of rows and columns are specified. Then, headers for the colums are specified. In the loop over all nodes, the individual cells of the table model are populated. Finally, the table model object is passed to the table. The SidePanel class The SidePanel hosts the three list panels and is straightforward. A grid layout with three rows and one column is used. The ListNodesPanel is constructed and added to the panel in one step. Finally, a certain size is specified such that the side panel is wide enough to display its content later. 1 2 import java.awt.*; import javax.swing.*; 3 4 public class SidePanel extends JPanel { 5 public SidePanel(TrussStructure ts) { // layout components setLayout(new GridLayout(3, 1)); add(new ListNodesPanel(ts)); 6 7 8 9 10 // configure setPreferredSize(new Dimension(200, 100)); 11 12 } 13 14 } Now go to the constructor of the TrussStructure class. Create a SidePanel object and add it to the west-compartment. Run your application and see the nodes being listed as you create them. The ListSections and ListElements classes These classes are very much similar to the ListNodes class. Implement them on your own. Hints: Use getClass().getName() to find out the name of a cross section object. When listing elements, the indexOf methods may be useful to find out the index of nodes and cross-sections. CHAPTER 6. PROGRAMMING GRAPHICAL USER INTERFACES 88 6.5.4 Creating Nodes and Elements Using the Mouse The last and most difficult task is to create nodes and element by clicking in the graphical viewer. For that, we have to implement a controller that captures user input from a view. By that, the get the (mostly) complete form of the Model-View-Controller pattern shown in Figure 6.7. The difference to Figure 6.5 is that a controller now also listens to events generated by a view and that it performs model modifications according to these events. interacts Controller user interacts modifies generates event notifies View Model retrieves state Figure 6.7: Complete model-view-controller pattern Preliminaries In order to be able to create elements by clicking nodes, the TrussStructure class needs a method that finds a node that is close to a certain position in space. Here it is; add it to your TrussStructure class. 1 2 3 4 public Node findNode(double x, double y, double z) { for (int i = 0; i < countNodes(); i++) { Node n = getNode(i); double d = n.distance(new Node(x, y, z)); 5 if (d < 0.1) { return n; } 6 7 8 } return null; 9 10 11 } Note that a predefined radius of 0.1 is used as tolerance. For a more sopisticated application, this value had to be adjusted according to the size of the model. Implementing the Controller The controller to create nodes and elements is realized in the EditorToolbar class which inherits from JToolbar such that it can be part of the application’s toolbar. The purpose of this controller is to capture mouse events from the graphical view and to translate these mouse events into commands that create nodes or elements. Therefore, it must implement the MouseListener interface and register itself as listener at the graphical view. Moreover, the controller needs to know the graphical view and the truss structure. As usual, we use attributes to 6.5. THE MODEL-VIEW-CONTROLLER PATTERN 89 store these associations. Let us start with a basic implementation and add things later step by step. Here’s the starting point: public class EditorToolbar extends JToolbar implements MouseListener { 1 2 private GraphicalView graphicalView_; private TrussStructure structure_; 3 4 5 public EditorToolbar(TrussStructure ts, GraphicalView gv) { // initialize attributes graphicalView_ = gv; structure_ = ts; 6 7 8 9 10 // add components add(new JLabel("Remove me later")); 11 12 13 // add listeners graphicalView_.addMouseListener(this); 14 15 } 16 17 } Enter the code above and let Eclipse help you add the methods defined in the MouseListener interface: Put the cursor into the name of the class (underlined in red) and press ctrl+1. Eclipse suggests to add the unimplemented methods. Modify the mouseClicked method such that you can see that something happens: 1 2 3 public void mouseClicked(MouseEvent e) { System.out.println("mouse clicked"); } Now, let us add the toolbar to the application. Go to your TrussStructureEditor class and add an EditorToolbar to the north of the TrussStructureEditor frame. Run your application, click inside the graphical view and see the print statements. Getting the Pick Position in 3D Space One difficulty in interactive 3D graphics is to convert 3D screen coordinates into 3D world coordinates. Fortunately, VTK provides the vtkWorldPointPicker class that helps us. In the getPickPosition method, first we set up the screen coordinates as expected by VTK. Then, we use the picker object to get the 3D coordinates corresponding to our mouse event. Add the code to your EditorToolbar class. 1 2 3 4 5 6 7 8 9 10 11 12 private double[] getPickPosition(MouseEvent e) { double[] worldCoords; vtkWorldPointPicker picker = new vtkWorldPointPicker(); // screen coordinates int screenX = e.getX(); int screenY = graphicalView_.getSize().height - e.getY(); double[] screenCoords = { screenX, screenY, 0 }; // convert into world coordinates and return picker.Pick(screenCoords, graphicalView_.GetRenderer()); worldCoords = picker.GetPickPosition(); return worldCoords; } 90 CHAPTER 6. PROGRAMMING GRAPHICAL USER INTERFACES Creating Nodes Having the getPickPosition method, creating nodes is easy. Add a method createNode(MouseEvent e) and invoke it from within the mouseClicked method. In order to create the node, first convert the mouse event into 3D coordinates, then create the node and finally, notify all observers of the truss structure. Implement the methods and check out if your code works. Creating Elements Currently, we are only able to create nodes; whenever you click, a new node is added to the structure. This is not what we want! In order to create either nodes or elements, the user must be able to tell the program what he/she wants to do. Moreover, when creating elements, we have to know the index of the cross-section to use. Therefore, two JToggleButton objects and one JTextField objects are introduced. Add the corresponding attributes to your class: 1 2 3 private JToggleButton createNodeB_ = new JToggleButton("Node"); private JToggleButton createElementB_ = new JToggleButton("Element"); private JTextField crossSectionIdTF_ = new JTextField("0", 3); Not that the second argument in the constructor of JTextField specifies the width of the text field. Next, the components have to be added to the toolbar in the EditorToolbar constructor: 1 2 3 4 5 6 // add components add(createNodeB_); add(createElementB_); add(new JSeparator(JSeparator.VERTICAL)); add(new JLabel("Section Index", JLabel.RIGHT)); add(crossSectionIdTF_); In order to separate the buttons from the textfield and its label, a JSeparator is used. Run you program and see how it looks like. If you don’t want the text field to take the whole remaining toolbar space, you can specify a maximum size for this component: 1 crossSectionIdTF_.setMaximumSize(crossSectionIdTF_.getPreferredSize()); In the mouseClicked method, we now can use the selection status of the buttons in order to decide whether nodes or elements should be created: 1 2 3 4 5 if (createNodeB_.isSelected()) { createNode(e); } else if (createElementB_.isSelected()) { createElement(e); } 6.5. THE MODEL-VIEW-CONTROLLER PATTERN 91 The createElement method is a bit tricky because we need two nodes for an element. We do the following: An attribute is introduced to store the first node being selected: 1 private Node node1_ = null; If node1 is null, it means that the selected node is the first node. If it is non-null, the selected node is the second node and we can go on creating the element. Here’s the code: 1 2 private void createElement(MouseEvent e) { double[] p = getPickPosition(e); 3 if (node1_ == null) { node1_ = structure_.findNode(p[0], p[1], p[2]); } else { Node node2 = structure_.findNode(p[0], p[1], p[2]); 4 5 6 7 8 if (node2 != null && node2 != node1_) { int idxCs = Integer.parseInt(crossSectionIdTF_.getText()); int idxN1 = structure_.indexOf(node1_); int idxN2 = structure_.indexOf(node2); 9 10 11 12 13 node1_ = null; structure_.addElement(idxN1, idxN2, idxCs); structure_.notifyViews(); 14 15 16 } 17 } 18 19 } Check out if your program works. You may realize that it is possible to activate both buttons, the one for the nodes and the one for the elements. In order to overcome this unwanted feature, we have to deactivate the other button if one button becomes activated. For that the EditorToolbar class also implements the ActionListener interface and the constructor registers the object at the two buttons: 1 2 createNodeB_.addActionListener(this); createElementB_.addActionListener(this); In the actionPerformed method, we just deselect buttons if needed: 1 2 3 4 5 6 7 8 public void actionPerformed(ActionEvent e) { if (e.getSource() == createElementB_ && createElementB_.isSelected()) { createNodeB_.setSelected(false); } if (e.getSource() == createNodeB_ && createNodeB_.isSelected()) { createElementB_.setSelected(false); } } 92 CHAPTER 6. PROGRAMMING GRAPHICAL USER INTERFACES