Introduction to Computer Science
(2011-12 Spring) Allan Gottlieb
Tuesdays and Thursdays 2:00-3:15pm Room 101 Ciww

Start Lecture #1

Chapter 0 Administrivia

I start at 0 so that when we get to chapter 1, the numbering will agree with the text.

0.1 Contact Information

0.2: Course Web Page

There is a web site for the course. You can find it from my home page, which is http://cs.nyu.edu/~gottlieb

0.3 Textbook

The course text is Liang, Introduction to Java Programming (Brief Version), Eighth Edition (8e)

0.4 Computer Accounts and the Mailman Mailing List

0.5 Grades

Grades are based on the labs and the final exam, with each very important. The weighting will be approximately
30%*LabAverage + 30%*MidtermExam + 40%*FinalExam (but see homeworks below).

0.6 The Upper Left Board

I use the upper left board for lab/homework assignments and announcements. I should never erase that board. Viewed as a file it is group readable (the group is those in the room), appendable by just me, and (re-)writable by no one. If you see me start to erase an announcement, let me know.

I try very hard to remember to write all announcements on the upper left board and I am normally successful. If, during class, you see that I have forgotten to record something, please let me know. HOWEVER, if I forgot and no one reminds me, the assignment has still been given.

0.7 Homeworks and Labs

I make a distinction between homeworks and labs.

Labs are

Homeworks are

0.7.1 Homework Numbering

Homeworks are numbered by the class in which they are assigned. So any homework given today is homework #1. Even if I do not give homework today, the homework assigned next class will be homework #2. Unless I explicitly state otherwise, all homeworks assignments can be found in the class notes. So the homework present in the notes for lecture #n is homework #n (even if I inadvertently forgot to write it to the upper left board).

0.7.2 Doing Labs on non-NYU Systems

You may solve lab assignments on any system you wish, but ...

I believe you all have accounts on i5.nyu.edu. Your username and password should be the same as on home.nyu.edu (at least that works for me).

0.7.3 Obtaining Help with the Labs

Good methods for obtaining help include

  1. Asking me during office hours (see web page for my hours).
  2. Asking the mailing list.
  3. Asking another student, but ...
    Your lab must be your own.
    That is, each student must submit a unique lab. Naturally, simply changing comments, variable names, etc. does not produce a unique lab.

0.7.4 Computer Language Used for Labs

You labs must be written in Java.

0.8 A Grade of Incomplete

The rules for incompletes and grade changes are set by the school and not the department or individual faculty member. The rules set by CAS can be found in http://cas.nyu.edu/object/bulletin0608.ug.academicpolicies.html. They state:

The grade of I (Incomplete) is a temporary grade that indicates that the student has, for good reason, not completed all of the course work but that there is the possibility that the student will eventually pass the course when all of the requirements have been completed. A student must ask the instructor for a grade of I, present documented evidence of illness or the equivalent, and clarify the remaining course requirements with the instructor.

The incomplete grade is not awarded automatically. It is not used when there is no possibility that the student will eventually pass the course. If the course work is not completed after the statutory time for making up incompletes has elapsed, the temporary grade of I shall become an F and will be computed in the student's grade point average.

All work missed in the fall term must be made up by the end of the following spring term. All work missed in the spring term or in a summer session must be made up by the end of the following fall term. Students who are out of attendance in the semester following the one in which the course was taken have one year to complete the work. Students should contact the College Advising Center for an Extension of Incomplete Form, which must be approved by the instructor. Extensions of these time limits are rarely granted.

Once a final (i.e., non-incomplete) grade has been submitted by the instructor and recorded on the transcript, the final grade cannot be changed by turning in additional course work.

0.9 Academic Integrity Policy

This email from the assistant director, describes the policy.

  Dear faculty,

  The vast majority of our students comply with the
  department's academic integrity policies; see

  www.cs.nyu.edu/web/Academic/Undergrad/academic_integrity.html
  www.cs.nyu.edu/web/Academic/Graduate/academic_integrity.html

  Unfortunately, every semester we discover incidents in
  which students copy programming assignments from those of
  other students, making minor modifications so that the
  submitted programs are extremely similar but not identical.

  To help in identifying inappropriate similarities, we
  suggest that you and your TAs consider using Moss, a
  system that automatically determines similarities between
  programs in several languages, including C, C++, and Java.
  For more information about Moss, see:

  http://theory.stanford.edu/~aiken/moss/

  Feel free to tell your students in advance that you will be
  using this software or any other system.  And please emphasize,
  preferably in class, the importance of academic integrity.

  Rosemary Amico
  Assistant Director, Computer Science
  Courant Institute of Mathematical Sciences

The university-wide policy is described here

0.10 An Introduction with a Programming Prerequisite

How weird is this?

The formal prerequisite for 0101 is 0002, which teaches the Python programming language. (I had a tiny, insignificant part in the development of Python when I first arrived at NYU, 30 years ago.) (Grayed out material is not part of the official course.)

If instead of taking 0002, you have programmed in some other language (say C/C++), that is fine.

If, however, you are already a wizard Java programmer (or even a mere expert), you are taking the wrong course—you would be wasting somebody's money and, more significantly, wasting much of your time.

Chapter 1 Introduction to Computers, Programs, and Java

1.1 Introduction

This course, indeed the CS major sequence, emphasizes software, i.e., computer programs, rather than hardware, i.e., the physical components of a computer.

We teach a little hardware in 201, Computer Systems Organization, giving a high-level, non-detailed view, and present much more in 436, the Computer Architecture elective.

In general, the NYU course sequence offers a top-down view: we first show you how to program in high-level languages such as Java and Python, later we present the assembly language that is essentially the language understood by the computer itself, and later still we describe the how the electronic components in a computer are able to actually execute these programs.

Many universities follow this approach. Others provide a bottom-up sequence beginning with the components, then low-level (assembly) languages, and then high-level languages.

1.2 What is a Computer?

Computers store and process data.

Modern computers store the programs on the same media as the data

.

Figure 1.1 in the book is quite dated: It shows the design of 1980s computers. Modern machines do not have a single bus over which all information must travel. Compare the diagrams in sections 1.3 and 1.3.6 of my OS class note.

1.2.1 Central Processing Unit (CPU)

The CPU contains the electronic components that actually execute the instructions given to the computer.

First of all the CPU decodes the instruction (i.e., determines what is to be done, for example the contents of two CPU memory units called registers are to be added and the result placed in another register). Other instructions require the CPU to access additional components of the computer, e.g., the central memory.

In addition to determining the action needed, the CPU performs many of the operations required. For example the ALU (Arithmetic/Logic Unit) portion of the CPU contains an adder and thus performs the register add mentioned above.

1.2.2 Memory

Within a computer all data is stored as a sequence of bits, each of which can take on one of two values. Computers today mostly represent numbers as words, each consisting of 32 or 64 bits.

A 32-bit word can take on 232 (approximately 4 billion) different values.

I very much believe that you should remember just one value, 210=1024. Then you can deduce that
    232 = 22*230 = 4*23*10 = 4*210*210*210 = 4*1024*1024*1024,
which is a little more than 4*1000*1000*1000 = 4,000,000,000.

Modern computers cannot access a single bit of memory. They can access a single word and most computers (including all those we shall consider) can access a smaller unit called a byte, which consists of 8 bits.

Since a byte is the smallest unit of memory that can be referred to directly, modern computers are called byte-addressable.

Since the bytes can be accessed in any order (not just sequentially in order byte #1, byte #2, ...), the memory is said to support random access and is called random access memory or RAM.

1.2.3 Storage Devices

Computers can access any byte in RAM quite quickly, which is wonderful. However, there are at least three problems with RAM.

  1. Limited size.
  2. Volatile.
  3. Non-transportable.

Limited Size

Today's (personal) computers have around a gigabyte GB of RAM. The exact size of a gigabyte is controversial. It is either a billion (109) bytes or the binary equivalent (230). When you purchase a gigabyte of RAM you are getting the latter, but when you purchase a gigabyte of disk storage you are getting only the former.

It is clearly nonsensical that an 80GB disk cannot hold 10 copies of the data contained in an 8GB RAM. Nonetheless, it is true. In fact, the proper terminology is that the disk contains only 80GiB (abbreviating 80 gibibytes) not 80GB. However common usage is still 80GB.

Although a gigabyte of RAM is huge by historical standards, it is still insufficient to hold all the data we want on a computer system. For example, my (lavishly equipped) laptop has 8GB, which can store one movie in standard definition (one DVD) but not one hi-def movie (one blu-ray).

Disk Drives

Disks (i.e., so called hard drives) provide several hundred times more bytes per dollar than does RAM. Disks are not byte addressable (i.e., you can't refer to a single byte store on a disk). Instead the smallest addressable unit is called a sector, which is typically 512 bytes. Disks form the primary storage medium for most computer systems that are at least as big as a laptop.

Volatile

Current RAM does not maintain its contents when the power is shut off and hence is not suitable for storing permanent data. Hard drives, various types of CDs, and flash storage do maintain their contents without power.

CDs

The book's words are a little garbled. CDs come in basically three flavors: read-only, write-once, and rewritable. (In this course CDs refer to data CDs; audio CDs organize the data stored in a different manner). DVDs and Blu-ray are (for us) simply higher density CDs (in 202 you will learn that the filesystems stored on DVDs differs from that of CDs).

Flash Drives

Flash drives are physically small storage units (they are often called thumb drives due to their size and shape). Unlike disks and CDs, flash drives have no moving parts and are thus potentially much faster. Like disks they are not byte addressable; their smallest accessible unit is called a block.

Blocks can be rewritten a large number of times. However, the large number is not large enough to be ignored.

Flash drives are sometimes called solid-state disks.

Tape Drives

These are becoming less important and we will not discuss them.

Non-transportable

RAM cannot be moved easily from one machine to another. You would lose the data present (due to volatility) and if done often or carelessly, might damage the device. Some disk drives (called external disks) can be transported, but CDs and flash drives are much better in this regard.

1.2.4 Input and Output Devices

Note the CPU-centric terminology. Devices that produce output, such as mice and keyboards, are called input devices and devices that accept input such as monitors are called output devices.

How does moving a mouse, cause the pointer to move?

How does a keyboard send a 'X' as opposed to a 'x'?

Screen resolution and dot pitch of a monitor are defined correctly in the book, but the statements about quality and clarity are too simplistic; the size of the monitor must be considered as well.

1.2.5 Communication Devices

We will not study these. The book is somewhat dated here. Some homes (e.g., mine) have LANs; a typical NIC now is at least 100 megabits per second not 10 (many are now 1000 megabits per second).

1.3 Programs

I assume you have written programs (perhaps in 0002) and thus know what they are.

1.4 Operating Systems

An operating system (OS) is a software system that raises the level of abstraction provided by the hardware to a more convenient virtual machine that other software can then use. For example, when we write programs accessing disk files, we do not worry about (or even have knowledge of) how the data is actually stored on the disk. Indeed, they very concept of a file is foreign to a disk and is an abstraction provided by the OS.

The OS also acts as a resource manager permitting multiple users to share the hardware resources.

Naturally much more detail is provided in my OS class notes. A short summary is in section 1.1 of those notes.

1.4.1 Controlling and Monitoring System Activities

1.4.2 Allocating and Assigning System Resources

1.4.3 Scheduling Operations

1.A Connecting to a Remote Computer

You should all have accounts on i5.nyu.edu. Your username and password on i5 is the same as on home.nyu.edu. You will need to access i5 for lab1 (which has not yet been assigned.

If your personal computer runs MS Windows, please download two important free apps: putty and winscp. Putty is used to connect to outside machines. You should be able to connect to i5.nyu.edu

If your personal computer is an Apple MAC, discover how to bring up a terminal window (a.k.a. a command window). MacOS already has the needed commands, type ssh <username>@i5.nyu.edu, where <username> is your username.

1.5 Java, World Wide Web, and Beyond

Java is a very popular, modern, general purpose, programming language. It comes with an extensive standard library that aids in writing graphical programs, especially those, called applets, that are invoked from browsers, e.g., firefox.

Java has extensive support for the modern software development methodology called object-oriented programming.

Java is a full-featured, and thus large, programming language. In its entirety, Java is not simple; but, in the beginning at least, we will be able to avoid most of the tricky parts.

1.6 The Java Language Specification, API, JDK, and IDE

Any programming language needs a detailed, precise specification describing the syntax and semantics of the language. It is basically the rules that determine a legal Java program. We will not need this level of precision.

Changing the specification essentially changes the language. The Java spec is stable.

The Application Program Interface (API) is defined by the standard library that comes with Java. It is comparatively easy to extend the API—write another library routine—and this does occur.

There are several versions of Java; we use Java SE 1.6 (or perhaps 1.7), which we will just call Java.

The programs used to compile and run Java programs are part of the Java Development Toolkit (JDK).

Instead of using the JDK, one can use an Integrated Development Environment (IDE). Several IDEs are available. I will use only the JDK. You may develop your labs using either the JDK or an IDE, but the final product must be a Java program that can be run with just the JDK.

The various IDEs are all slightly different; whereas the JDKs (in principle) all accept the same language and produce the same results.

You may develop you lab assignments on any Java system you wish. However, we shall run you program using a Java SDK so you should test your program that way as well. I shall give examples of SDK usage (it is very easy).

1.7 A Simple Java Program

// Hello world in the C programming language
#include <stdio.h>
void main(int argc, char *argv[]) {
    printf("Hello, world.\n");
}
// Hello world in Java public class Hello { public static void main (String[] args) { System.out.println("Hello, world."); } }

On the right we see a simple Java program that prints the sentence Hello, world.. This program must be contained in the file named Hello.java.

For comparison, the corresponding C program is directly above the Java program. I put this program in a file called hello.c, but it could have been in a file called xyzzy.c (the .c is important).

Although they may look different, these two programs are basically the same. We now discuss briefly the Java version, line by line.

  1. This line is a comment and is, in a sense, not part of the program. It is there to aid anyone reading the program. A comment begins with two consecutive slashes and ends at the end of the line.
  2. This line introduces the class named Hello. Java is case sensitive and, by convention, class names are capitalized. We will have much more to say about classes later. Now we just note that they can contain data (this simple class does not) and methods (this class has the method main). Methods in Java are akin to procedures in other programming languages.
    Hello is public, which means it can be accessed from any other class Many simple .java files contain just a single public class. In this case the file must be named X.java, where X is the name of the class.
    The { at the end of the line marks the beginning of the body of the class.
  3. This line introduces the method main. The name main is special. When a program is run the system begins by executing the main method. This line tells us several things about main: The { at the end of the line marks the beginning of the body of the method.
  4. This line (the bulk of the body of main) invokes the method println, which is part of the field out in the class System.out.
  5. This line ends the method main.
  6. This line ends the class Hello.

Start Lecture #2

Remark: MacOS users need to set textedit to produce text files (handout). Windows users need putty and winScp.

Homework: 1.1, 1.3.

For the benefit of those students who may not yet have the book, here are the problems.

1.1 (Displaying three messages) Write a program that displays

  Welcome to Java
  Welcome to Computer Science
  Programming is fun

1.3 (Displaying a pattern) Write a program that displays the following pattern:


      J     A     V     V    A
      J    A A     V   V    A A
  J   J   AAAAA     V V    AAAAA
   J J   A     A     V    A     A

Unless otherwise stated homeworks are from the Programming Exercises at the end of the current chapter. They are not from the Review Questions

1.8 Creating, Compiling and Executing a Java Program

Creating a Java Program

A Java program is created using a text editor. I use emacs. Others use vim, textedit, notepad, or a variety of alternatives. Another possibility is the editor included with a Java IDE.

Compiling a Java Program

Java programs are compiled using a Java compiler. The standard compiler included with a JDK is called javac. To compile our Hello program, located in the file Hello.java, one would write

  javac Hello.java

Javac translates Java into an intermediate form normally called bytecode. This bytecode is portable. That is the bytecode produced on one type of computer can be executed on a computer of a different type.

The bytecode is placed in a so-called class file, in this case the file Hello.class

Our C version of Hello, if contained in the file Hello.c, could be compiled via the command

    cc -o Hello Hello.c
  

The resulting file Hello is not portable. Instead it has instructions suitable for one specific machine type (and software system).

The Java Virtual Machine (JVM)

Portability of Java bytecode has been obtained by defining a virtual machine on which to run the bytecode. This virtual machine is called the JVM, Java Virtual Machine.

Each platform (hardware + software, e.g., Intel x86 + MacOS or SPARC + Solaris) on which Java is to run includes an emulator of this virtual machine. Somewhat ambiguously, the emulator of the Java Virtual Machine is itself also called the JVM.

The penalty for this portability is that executing bytecode by the (typically software) JVM is not as efficient as executing non-portable, so-called native, code tailored for a specific machine.

Executing a Java Program

Since essentially no hardware/OS can execute Java bytecode directly, another program is run and is given the bytecode as data. This program is typically included in any Java IDE. When using the JDK the program is called java (lower case).

For our example the bytecode located in the file Hello.class is executed via the JDK command

  java Hello

(Note that we must write Hello and not Hello.class.)

1.9 (GUI) Displaying Text in a Message Dialog Box

  // Hello world in Java -- gui version
  public class HelloGui {
    public static void main (String[] args) {
    javax.swing.JOptionPane.showMessageDialog(null, "Hello, world.");
    }
  }

Java comes with a large library of predefined functions. The top example on the right shows how just changing the output function causes a dialog box to be produced. Naturally, this code only works on a graphical display.

A difficulty is that actually explaining how the dialog box appears on the screen is quite complicated, involving widgets, fill-rectangle, and other graphics concepts.

1.B A Pedantic Version of Hello.java

  // Hello world in Java -- pedantic version
  public class HelloPedantic {
    public static void main (String[] args) {
    java.lang.System.out.println("Hello, world.");
    }
  }

In fact, our original Hello example used a shortcut. The class System is actually found in the package java.lang (which is searched automatically by javac). The code on the right shows the program without using the shortcut.

1.C Review Questions

These have answers on the web in the companion. See the book for details. I tried it and it works.

If you cannot understand an answer, ask! A good question for the mailing list.

Chapter 2 Elementary Programming

2.1 Introduction

2.2 Writing Simple Programs

Let's solve quadratic equations Ax2 + Bx + C = 0. Computational problems like this often have the form

  1. Get the input
  2. Compute the output
  3. Print the results
  4. Do it again.

For our first example we will hard-wire the input (thereby avoiding 1) and not do it again (thereby avoiding 4).

What is the input?
Ans: The three coefficients A, B, and C.

How is the output computed?
Ans: -B +- sqrt(B2-4AC)

What about sqrt of a negative number?
What about it? Mathematically, you get complex numbers. We will choose A, B, C avoiding this case. A better program would check.

public class Quadratic1 {
    public static void main (String[] args) {
        double A, B, C;     // double precision floating point
        A = 1.0;
        B = -3.0;
        C = 2.0;
        double discriminant = B*B - 4.0*A*C;
        // We assume discriminant >= 0 for now
        double ans1 = (-B + Math.pow(discriminant,0.5))/(2.0*A);
        double ans2 = (-B - Math.pow(discriminant,0.5))/(2.0*A);
        System.out.println("The roots are " + ans1 + " and " + ans2);
    }
}

The program is on the right. Note the variables are declared to have type double. This is the normal declaration for real numbers in Java. Out of habit I capitalized A,B,C. A bad idea.

We can assign values to variables either when we declare them or separately. Both are illustrated in the example.

Note that the first two lines and the last two lines are the same as from the first example.

How does the println work? In particular, what are we adding?
Ans: The + is overloaded. X+Y means add if X and Y are numbers it means concatenate if X and Y are strings.

But in the program we have both strings and numbers.
When the operands are mixed, numbers are converted (coerced) to strings.

The name double is historical. On early systems real numbers were called floating point because the decimal point was not in a fixed location but instead could float. This way of writing real numbers is akin to scientific notation. The keyword double signifies that the variable is given double the amount of storage as is given to a variable declared to be float.

2.3: Reading Input from the Console

The previous example was quite primitive. In order to solve a different quadratic equation it is necessary to change the program, recompile, and re-run.

In this section we will save the first two steps, by having the program read in the coefficients A, B, and C. Later we will use a loop to enable one run to solve many quadratic equations.

On the right is the program in a pedantic style. We show the program as it would normally be written below.

public class Quadratic2Pedantic {
  public static void main (String[] Args) {
    double A, B, C;
    double discriminant, ans1, ans2;
    java.util.Scanner getInput;
    getInput = new java.util.Scanner(java.lang.System.in);
    A = getInput.nextDouble();
    B = getInput.nextDouble();
    C = getInput.nextDouble();
    discriminant = B*B - 4.0*A*C;
    // We assume discriminant >= 0 for now
    ans1 = (-B + Math.pow(discriminant,0.5))/(2.0*A);
    ans2 = (-B - Math.pow(discriminant,0.5))/(2.0*A);
    java.lang.System.out.println("The roots are " + ans1
                                 + " and " + ans2);
  }
}

Note the following new features in this program.

import java.util.Scanner;
public class Quadratic2 {
  public static void main (String[] Args) {
    Scanner getInput = new Scanner(System.in);
    double a = getInput.nextDouble();
    double b = getInput.nextDouble();
    double c = getInput.nextDouble();
    double discriminant = b*b - 4.0*a*c;
    // We assume discriminant >= 0 for now
    double ans1 = (-b + Math.pow(discriminant,0.5))/(2.0*a);
    double ans2 = (-b - Math.pow(discriminant,0.5))/(2.0*a);
    System.out.println("The roots are " + ans1 + " and
                       " + ans2);
  }
}

On the right we see the program rewritten in the style that would normally be used.

The above explanation is way more complicated that the program itself! For now, but not for later, it is fine to just remember how to read doubles. Other methods in the Scanner class include nextInt(), nextBoolean(), and next(). The last method finds the next token (very roughly the next string).

Homework: 2.1, 2.5

For those without the 8e.

2.1: Write a program that reads Celsius degrees (in double), converts it to Fahrenheit, and displays the result. The formula is F=(9/5)C+32.

2.5: Write a program that reads in the subtotal and the gratuity rate, then computes the gratuity and the total.

2.4: Identifiers

These are the names that appear when writing a program. Examples include variable names, method names, and object names. There are rules for the names.

2.5: Variables

As we have said classes contain data (normally called fields) and methods. Fields are examples of variables, but we haven't seen any yet. Note that the various doubles above are not (directly) members of the class. Instead, they are part of the main() method, which itself is a member of the class.

Later we shall learn that there are two kinds of fields, static and non-static.

Another kind of variable is the local variable, which is a variable declared inside a method. We have seen several, e.g. discriminant.

The final kind of variable is the parameter. We have seen one example so far, the args parameter always present in the main method.

Note that discriminant is not a parameter. The variable in Math.pow() corresponding to discriminant is the parameter. In general variables are classified by how they are declared, not by how they are used.

2.6: Assignment Statements and Assignment Expressions

Many programming languages have assignment statements, which in Java are of the form

variable = expression ;

Executing this statement evaluates the expression and places the resulting value in the variable.

This ability to change the value of a variable (so called mutable state) is a big deal in programming language design. Proponents of functional languages believe that imperative languages, such as Java, are harder to understand due to the changing state.

Java, like the C language on which the Java syntax is based, also includes assignment expressions, which are of the form

  variable = expression

(NO semicolon). An assignment expression evaluates the RHS (right hand side) expression, assigns the value to the LHS variable, and then returns this value as the result of the assignment expression itself.

  System.out.println(x = Math.pow(2,0.5));
  

a = b = c = 0;


a = 1 + (b = 2 + (c = 0));

The first example evaluates the square root of 2, assigns the value to x, and then passes this value to println.

The second example first performs c=0, which results in c becoming 0 and the value 0 returned. This 0 is then assigned to b and returned where it is assigned to a. Note the right to left evaluation.

The third example is ugly, confusing, and not recommended. It works as follows: c=0 is evaluated assigning 0 to c and returning 0. Then 2+0 is evaluated to 2, assigned to b, and returned. Then 1+2 is evaluated to 3, assigned to a, and discarded.

Java, again like C, uses = for assignment and == to test if two values are equal. Another common choice is to use := for assignment and = to test for equality.

2.7: Named Constants

We have seen constant numbers and constant strings. But these were literal constants; that is the value itself was used (e.g., 2 or "Hello, world.").

A string constant like "Hello, world." is certainly descriptive, but a numeric constant like 2 is not (does its usage signify that we are computing base 2, that our computer game program is playing 2-handed cribbage, or what?). In addition, if we wanted to change either constant, we would need to change all relevant occurrences.

Instead of explicitly writing the 2 or "Hello, world." at each occurrence, we can defined a named constant (a.k.a. a symbolic constant) for each value.

  final int numberBase = 2;
  final int cribbageHands = 2;
  final String msg = "Hello, world.";

The first two lines on the right could be used in a two-handed cribbage program that somehow relied on base 2 representations. Then if the program was to be extended to 4-handed cribbage as well, we would have a clue where to look.

The final keyword says that definition gives the final value this identifier will have. That is, the identifier will not be assigned a new value. It is read-only or constant.

2.8: Numeric Data Types and Operations

Numerical values in Java come in two basic types, corresponding to mathematical integers and real numbers. However each of these come in various sizes.

Mathematical integers have four Java types.

Mathematical real numbers have two Java types

The difference between the four integer types is the amount of memory used for each value. As the name suggests a byte is stored in a single byte. A short is stored in two bytes; an integer is stored in four; and a long is stored in eight.

A float is stored in four bytes and a double is stored in eight.

For integers, allocating more storage permits a wider range of values.

Floating point is more complicated. The extra storage is used to extend the range of both the fractional part and the exponent. Recall that floating point numbers are represented in what is essentially scientific notation, so they contain both a fractional part and an exponent (unlike customary scientific notation, the exponent represents a power of 2 not 10).

Start Lecture #3

Remark: You might like http://http://www.drjava.org

2.8.1: Numeric Operators

Java, like essentially all languages, defines +, -, *, and / to be addition, subtraction, multiplication, and division.

  (-1) % 5 == -1
  (-1) modulo 5 == 4

Java defines % to be the remainder operator. Some people (and some books and some web pages) will tell you that Java defines % to be the mathematical modulo operator. Don't believe it! Remainder and modulo do agree when you have positive arguments, but not in general.

2.8.2: Numeric Literals

Literals are constants that appear directly in a program. We have already seen examples of both numeric and string literals.

For integers, if the literal begins with 0 followed by a digit, the value is interpreted as octal (base 8). If it begins with 0x, it is interpreted as hexadecimal (base 16). Otherwise, it is interpreted as decimal (base 10).

What is the use of base 8 and base 16 for us humans with 10 fingers (i.e, digits)?
Ans: Getting at individual bits.

The literal is considered an int unless it ends with an l or an L, in which case it is considered to be a long.

Real numbers are always decimal. If the literal ends with an f or an F, it is considered a float. By default a real literal is a double, but this can be emphasized by ending it with a d or a D.

Scientific Notation

Literals such as 6.02×1023 (Avogadro's number) can also be expressed in Java (and most other programming languages). Although the exact rules are detailed, the basic idea is easy: First write the part before the × then write an e or an E (for exponent) then write the exponent. So Avogadro number would be 6.02E23. Negative exponents get a minus sign (9.8E-3).

The details concern the optional + in the exponent, the optional trailing d or D, the trailing f or F for float, and the ability to omit the decimal point if it is at the right.

2.8.3: Evaluating Java Expressions

This is quite a serious subject if one considers arbitrary Java expressions. In general, to find the details, one should search the web using site:sun.com. This leads to the Java Language Specification http://java.sun.com/docs/books/jls/third_edition/html/j3TOC.html. The chapter on expressions http://java.sun.com/docs/books/jls/third_edition/html/expressions.html is 103 pages according to print preview on my browser!

For now we restrict ourselves to arithmetic expressions involving just +, -, *, /, %, (, and ). In particular, we do not now consider method evaluation. In this case the rules are not hard and are very similar to the rules you learned in algebra.

  1. Evaluate (innermost) parenthesized parts first and then erase the parentheses.
    So 3*(5+2) == 3*7 == 21).
  2. Within a parenthesized part evaluate all *, /, % in one left-to-right pass.
    So (7 * 4 % 5) + 12 = 3 + 12 == 15
  3. Within a parenthesized part, after doing *, /, %, do all +, - in one left-to-right pass.
    So (7 + 3 * 2) / (1 + 2 * 2) == (7 + 6) / (1 + 4) = 13 / 5 = 2

The last step also illustrated integer division, which in Java rounds towards zero.
So (5 - 12) / 4 = (-7) / 4 == -1.

Note that these operators are binary (i.e they operate on two values). Java has unary operators as well, e.g., unary -, which are evaluated first.
So 5 + - 3 == 2 and - 5 + - 3 == -8

Unary + is defined as well, but doesn't do much. Unary (prefix) operators naturally are applied right to left
So 5 - - - 3 == 2 and 5 + + + + 3 == 8

WARNING!!

+ + x is very different from ++ x. The same holds for - - versus --. This will be made clear in section 2.10.

2.9: Problem: Displaying the Current Time

2.9 (Alternate): Computing How long Ago

It is no fun to do one from the book so instead we do a silly version of a program to tell how long it is from one date to another. For example from 1 July 1980 to 5 September 1985 is 5 years, 2 months and 4 days.

Such a program would actually be useful. However, we will do a silly version since we don't yet know about arrays and if-then-else.

  1. We pretend that all months have 30 days and hence that a year has 30×12=360 days.
  2. We require that the user give the later date first. Actually we call it today's date, but it can be any date providing it is later than the second date.
// Silly version of How Long Ago
import java.util.Scanner;
public class HowLongAgo {
  public static void main (String[] args) {
    final int MONTH_DAYS = 30; // ridiculous
    final int YEAR_DAYS = 12*MONTH_DAYS; // equally ridiculous
    Scanner getInput = new Scanner(System.in);
    System.out.println("Enter today's day, month, and year");
    int day = getInput.nextInt();
    int month = getInput.nextInt();
    int year = getInput.nextInt();
    System.out.println("Enter old day, month, and year");
    int oldDay = getInput.nextInt();
    int oldMonth = getInput.nextInt();
    int oldYear = getInput.nextInt();
    // Compute total number of days ago
    int deltaDays = (day-oldDay) + (month-oldMonth)*MONTH_DAYS
        + (year-oldYear)*YEAR_DAYS;
    // Convert to days / months / years
    int yearsAgo = deltaDays / YEAR_DAYS;
    int monthsAgo = (deltaDays % YEAR_DAYS) / MONTH_DAYS;
    int daysAgo = (deltaDays % YEAR_DAYS) % MONTH_DAYS;
    System.out.println ("The old date was " + yearsAgo +
                " years " + monthsAgo + " months and "
                + daysAgo + " days ago.");
  }
}

The program (on the right) performs this task in four steps.

  1. Prompt for and read in the input.
  2. Compute the total number of days between the two dates.
  3. Convert the days into years/months/days. This uses the remainder operator % and should be studied.
  4. Print the output.

Note that monthsAgo is not the total number of months ago since we have already removed the years.

I computed the three values in the order days, months, years, which is from most significant to least significant. You can instead compute the values in the reverse order (least to most significant). To see an example, read the book's solution.

Splitting a combined value into parts is actually quite useful. Consider splitting a 3-digit number into its three digits. For example given six hundred fifteen, we want 6, 1, and 5. This is the same problem as in our program but YEAR_DAYS is 100 and MONTH_DAYS is 10.

How would you convert a number into millions, thousands, and the rest?
Ans: Set YEAR_DAYS = 1,000,000 and MONTH_DAYS = 1,000.

How would you convert dollars into hundreds, twenties, and singles?
Ans: Set YEAR_DAYS to 100 and MONTH_DAYS to 20.

How can an operating system convert a virtual address into segment number, page number, and offset
Ans: Set YEAR_DAYS to the number of bytes in a segment and ... oops wrong course (and real OSes do it differently).

Homework: 2.7 Write a program that prompts the user to enter the number of minutes and then calculates the (approximate) number of years and days it represents. Assume all years have 365 days.

2.10 Shorthand Operators

Statements like x = x + y; are quite common and several languages, including Java and C, have a shorthand form x += y;.

Similarly Java et al. has -=, *=, /=, and %=. Note that there is NO space between the arithmetic operator and the equal sign.

An especially common case is x = x + 1; and Java and friends have an especially short form x++. Similarly, we have x-- (but NOT ** or // or %%). Note that there is NO space between the two arithmetic operators. If you write x + +, you are specifying two unary additions (see section 2.8.3 and its warning) not one ++ operator.

In fact x++ can be part of an expression rather than as a statement by itself. In this case, a question arises. Is the value used in the expression the old (un-incremented) value of x, or the new (incremented) value? Both are useful and both are provided.

  x  = 5;
  y1 = x++ + 10;
  y2 = ++x + 10;
  y3 = x-- + 10;
  y4 = --x + 10;

Consider the code sequence on the right where all 5 variables are declared to be ints.

  1. The first line simply sets x to 5.
  2. The second line clearly increments x to 6, but is y1 set to 5+10 or 6+10? Ans: x++ is a post-increment, that is the increment is done after the value of x is supplied to the remaining expression. Thus y1 becomes 5+10=15. As a mnemonic hint the ++ comes after and the increment comes after the value of x is returned.
  3. The third line clearly increments x to 7, but this time we have a pre-increment (the ++ comes before and the increment is done before the value of x is returned. Thus y2 becomes 7+10=17.
  4. The rules for -- are the same as for ++ so the fourth line clearly decrements x to 6. It is a post-decrement, occurring after the value of x is returned. Thus y3 becomes 7+10=17.
  5. Clearly the last line decrements x back to 5. It is a pre-decrement occurring before the value of x is returned. Thus y4 becomes 5+10=15;

Since there were two increments and two decrements, we expect x to end with the same value as it had in the beginning ... and it does.

On the board show how to calculate the index for queues (rear++ and front++) and for stacks (top++ and --top). What about moving all four ++ and -- operators to the other side?

2.11 Numeric Type Conversions

What happens if we try to add a short to an int? How about multiplying a byte by a double?

Even simpler perhaps, how about assigning an integer value to a real variable, or vice versa?

The strict answer is that you can't do any of these things directly.

Instead one of the values must be converted to another type.

Sometimes this conversion happens automatically (but it does happen). Other times it must be explicitly requested and some of these requests fail.

Coercion vs. Type Casting

When the programming language automatically converts a value of one type (e.g. integer) to another type (e.g., double), we call the conversion a coercion.

When the programmer explicitly requests the conversion, we call it type casting.

Widening vs. Narrowing

Any short value is also a legal int. Similarly any float value is a legal double.

Conversions of this kind are called widenings since the new type in a sense is wider than the old. Similarly, the reverse conversions are called narrowing.

public class Test {
  public static void main (String[] args) {
    long a = 1234567890123L;   // one trillion plus
    short b;
    double x;
    float y;
    x = a;
    y = a;
    // b = a;  won't compile; coercions cannot narrow
    b = (short)a;    // narrowing cast
    System.out.println(a+ " "+b + " "+x + " "+y);
  }
}

javac Test.java; java Test
1234567890123 1227 1.234567890123E12 1.23456795E12

Java will perform widening coercions but not narrowing coercions. To perform a narrowing conversion, the programmer must use an explicit cast. This is done by writing the target type in parenthesis.

The code on the right illustrates these points. Two problems have arisen.

  1. The narrowing from a to b was disastrous: 1234567891 has become 723. Not all 32-bit numbers fit in 16 bits. No wonder Java won't do the coercion and requires you to explicitly do the type cast!
  2. The coercion from a to y resulted in some loss of precision. A 32-bit float uses some bits for the exponent so does not have as many available for the mantissa as does a 32-bit int, which has no exponent. A 64-bit double has more than enough mantissa bits to exactly represent any int, but not enough for very large longs.

Wider Range vs. Wider Numbers

The last example showed that Java will coerce a 64-bit long into a 32-bit float. Clearly the 32-bit number is narrower than the 64-bit number, but the coercion is permitted.

I believe the explanation is that the range of possible floats (vastly) exceeds the range of longs, so the assigned value will be approximately equal to the original, only some precision will be lost.

Start Lecture #4

2.12 Problem: Computing Loan Payments

The loan payment problem requires you to accept some complicated formula for monthlyPayment. That is not my style. Instead, we will do a simpler problem where we can understand the formula used.

2.12 (Alternate) Problem: Computing Compound Interest

Say you have a bank account with $1000 that pays 3% interest per year and want to know what you will have after a year.

import java.util.Scanner;
public class CompoundInterest {
  public static void main (String[] args) {
    Scanner getInput = new Scanner(System.in);
    double origBal = getInput.nextDouble();
    double interestRate = getInput.nextDouble();
    int n = getInput.nextInt();   // numberCoumpoundings
    double finalBal = origBal*Math.pow(1+interestRate/n,n);
    System.out.println(finalBal);
  }
}
javac CompoundInterest.java; java CompoundInterest
1000. .03 1
1030.0

java CompoundInterest
1000. .03 2
1030.2249999999997

java CompoundInterest
1000. .03 12
1030.4159569135068

java CompoundInterest
1000. .03 100000
1030.4545293116412

java CompoundInterest
1000. .03 1000000
1030.4545335307425

The program on the right is fairly straigtforward, given what we have done already. First we read the input data: the original balance, the (annual) interest rate, and the number of compoundings.

Then we compute the final balance after one year. We could do k years by changing the exponent from n to k*n.

Finally, we print the result.

The only interesting line is the computation of finalBal. Let's read it carefully in class to check that it is the same as the formula above.

Make sure you see the coercion that occurs in the division.

We show five runs, the first for so called simple interest (only compounded once). We did this above and sure enough we again get $1030 for 3% interest on $1000.

The second is semi-annual compounding. Note that we do not need to recompile (javac).

The third is monthly compounding.

The fourth and fifth suggest that we are approaching a limit.

2.13: Character Data Type and Operations

  char c = 'x';
  String s = "x";
  s = c;   // compile error

  char apostrophe = '\'';

The char (character) datatype is used to hold a single character. A literal char is written as a character surrounded by single quotes.

The variables c and s on the right are most definitely not the same.

What do you do if you want the character '?
Answer: Escape it with a backlash as shown.

2.13.1: Unicode and ASCII code

Java's char datatype uses 16-bits to represent a character, thereby allowing 216=65,536 different characters. This size was inspired by the original Unicode, which was also 16 bits. The goal of Unicode was that it would supply all the characters in all the world's languages.

However, 65,536 characters proved to be way too few; Unicode has since been revised to support many more characters (over a million), but we won't discuss how it was shoehorned into 16-bit Java characters. It turns out that the character 'A' has the 16-bit value 0000000000100001 (41 in hex, 65 in decimal).

There are two ways to write this character. Naturally 'A' is one, but the other looks wierd, namely '\u0041'. This representation consists of a backslash, a lower case u (presumably for unicode), and exactly 4 hexadecimal digits. I don't like this terminology—to me digit implies base 10—but it is standard.

public class As {
  public static void main(String[] Args) {
    String str = "A\u0041\u0041A \u0041";
    System.out.println(str);
  }
}

javac As.java; java As
AAAA A
  

So 'A' is the 4*16+1=65th character in the Unicode set.

The two character representations can be mixed freely, as we see on the right.

In the not so distant past, computers (at least US computers) used the 7-bit ASCII code (rather nationalistically, ASCII abbreviates American Standard Code for Information Interchange). The 65th ASCII character is also 'A'. All the letters, digits, etc are in both ASCII an unicode and are in the same position in each code (65 for A). It might be right that the 127 ASCII codes make up the first 127 unicode characters and are at the same position, but I am not sure.

Although it will not be emphasized in this course, it is indeed wonderful that alphabets from around the world can be represented.

2.13.2 Escape Sequences for Special Characters

Esc SeqBecomes

\"Double quote
\\Backslash
\'Single quote
\tTab
\nNewline

\bBackspace
\fFormfeed
\rReturn

We have a problem. The character " is used to end a string. What do we do if we want a " to be part of a string?

The answer is that we use an escape sequence beginning with \. In particular we use \" to include a " inside a string.

But then how do we get \ itself?
Ans: We use a \\ (of course).

On the right is a list of Java escape sequences. The ones above the line are more commonly used than the ones below.

2.13.3: Casting Between char and Numeric Types

I believe a better heading would be converting between char and numeric types since not all of the conversions used are (explicit) casts; some are (implicit) coercions.

public class Test {
  public static void main (String[] args) {
    int i;
    short s;
    byte b;
    char c = 'A';
    i = c;
    s = c;
    b = c;
    System.out.println(c + " " + i + " " + s + " " + b);
  }
}

The code on the right has two errors. Clearly a (16-bit) char might not fit into an (8-bit) byte. The (subtle) trouble with the short is that it is signed, whereas char is not so the latter can be about twice as large.

Hence coercions will not work and the program on the right will not compile. You may write casts such as b=(byte)c; this will compile, but will produce wrong answers if the actual value in the char c does not fit in the byte b.

Similarly, integer values may be cast into chars (but Java will not coerce them).

2.14: Problem: Counting Monetary Units

We already did a simpler version of this.

Let's do the design of a more complicated version: Read in a double containing an amount of dollars and cents, e.g., 123456.23 and prints the number of twenties, tens, fives, singles, quarters, dimes, nickels, pennies.

There is a subtle danger here that we will ignore (the book doesn't even mention it). A number like 1234.1 cannot be represented exactly as a double since 0.1 cannot be written in base 2 (using a finite number of bits) just as 1/3 cannot be written as a decimal.

Ignoring the problem, the steps to solve the problem are

  1. Convert the dollars and cents to an integer number of pennies.
  2. Find the number of twenties and subtract the corresponding number of pennies.
  3. Find the number of tens and subtract the corresponding number of pennies.
  4. ...
  5. Find the number of nickels and subtract the corresponding number of pennies.
  6. What is left is the number of pennies.

Let's start this in class. One solution is here.

2.15 The String Type

In Java a String (note the capital S) is very different from a double, short, or char. Whereas all the latter are primitive types, String is actually a class and hence the String type is a reference type.

So what?

It actually is an important, indeed crucial, distinction, but not yet. Remember that Java was not designed as a teaching language, but as a heavy-duty production language. Once consequence is that doing some simple things (reading an integer, declaring a string) uses fairly sophisticated concepts.

For now we just want to learn how to declare String variables, write String constants, assign Strings, and concatenate Strings. Fortunately, we can do all of these tasks without having to deal with the complications. Later we will learn much, much more about classes.

"This is A string"
"This is \u0041 string"

String s1, s2;
s1 = "A string";
String s3 = "Another string";
s2 = s1 + " " + s3;
s2 += " and more";

A string
A string Another string and more
Another string

The top line on the right shows a String constant.
The syntax is very simple: a double quote, some characters, a double quote.
The second line is the same String constant in Unicode.

The next group contains legal Java statements.

The last group of lines show the output when we println() each of the three strings.

    String s1 = "A1", s2 = "A2";
    s1 += " " + s2;
    s1 += s1;
    System.out.println(s1);

Homework: What would be the output when the program on the right is run? I omitted the boilerplate at the beginning and end of nearly every program.

2.16 Programming Style and Documentation

2.16.1 Appropriate Comments and Comment Styles

There are three types of Java comments.

  1. // comments the rest of the line
  2. /* ... */ comments the material ...
  3. /** ... */ uses javadoc

I will use only the first form, but you may use any of the three.

To learn about javadoc, which is quite cool, see the reference in the book.

2.16.2 Naming Conventions

Java programmers use the following conventions. I will try to adhere to them. I don't know if you can ask javac to tell you if you have violated the conventions.

2.16.3 Proper Indentation and Spacing

You should all be familiar with proper indenting to show the meaning of the program since Python requires it.

2.16.4 Block Styles

I use the end-of-line style but many prefer the next-line style. You may use either (see the book for examples of next-line style).

2.17 Programming Errors

I wish I could write a few words or pages (or books) that would show you how to avoid making errors and how to find and fix error made by others.

Instead, it will be a semester-long effort: We will write programs in class, which will doubtless have errors that we can fix together.

Homework: 2.11

2.17.1 Syntax Errors

2.17.2 Runtime Errors

2.17.3 Logic Errors

2.17.4 Debugging

2.18 (Gui) Getting Input from Input Dialogs

Chapter 3 Selections

3.1 Introduction

Essentially all computer languages have a way to indicate that some statements are executed only if a certain condition holds.

Java syntax is basically the same as in C, which is OK but not great.

3.2 boolean Data Type

We have seen four integer types (byte, short, int, and long), two real types (float and double), and a character type (char). These 7 types are called primitive.

We have also see one reference type (the String class). Note the capitalization, which reminds you that String is a class.

Java has one more primitive type (boolean), used to express truth and falsehood.

Comparison Operators
Operator  Name

==equal to
!=not equal to
<less than
<=less than or equal to
>greater than
>=greater than or equal to

Mathematicians, when discussing Boolean algebra, capitalize Boolean since it is named after a person, specifically the logician George Boole. Java of course does not capitalize boolean. Why?
Because boolean is a primitive type, not a class.

There are two boolean constants, true and false.

There are 6 comparison operators that are used to compare values in an ordered type (a type in which values are ordered). So we can compare ints with ints, doubles with doubles, and chars with chars. The result of a comparison is a boolean, namely true or false.

If values from different (ordered) types are being compared, one must be converted to the type of the other. Again this can be via coercion or type casting

int i = 1, j = 2;
boolean x, y = true, z;
x = i == j; // false
z = i < j; // true

On the right we see some simple uses of booleans. The one point to note is the distinction between = and == in Java.
The single = is the assignment operator that causes the value on the RHS to be placed into the variable on the LHS.
The double == is the comparison operator that evaluates its LHS and RHS, compares the results, and results in either true or false.

3.3 Problem: A Simple Math Learning Tool

No fun to use booleans before learning about if.

3.4 if Statements (a.k.a if-then Statements)

Found in basically all languages. We will see that the C/Java syntax for if permits the notorious dangling else problem.

3.4.1 One-Way if Statements

if (boolean expression) {
  one or more statements;
}

The simplest form of if statement is shown on the right.

if-then

The semantics (meaning) of a one-way if statement is simple and the same as in many languages.

3.5 Problem: Guessing Birthdays

Read.

3.5 (Alternate) A Better Quadratic Equation Solver

Write a program to read in the coefficients A,B,C of a quadratic equation and print the answers. Handle the cases of positive, zero, and negative discriminant separately.

Let's do this in class. One solution is here.

3.6 Two-Way if Statements (a.k.a. if-then Statements)

if (boolean expression) {
  one or more statements;
} else {
  one or more statements;
}

Often if is used to choose between two actions, one when the Boolean expression is true and one when it is false.

The skeleton code for this if-then-else construct is shown on the right. The previous simpler skeleton is normally called an if-then even though Java (and C) do not actually employ a then keyword.

if-then-else

As the name suggests, the semantics of an if-then-else is to do the then or the else depending on the if. Specifically,

Omitting the {}

if (boolean expression)
   exactly one statement


if (boolean expression)
   exactly one statement
else
   exactly one statement


if (boolean expression) {
   one or more statements
} else
   exactly one statement

If any of the three blocks (the then block in the if-then statement, the then block in the if-then-else statement, or the else block in the if-then-else statement) consists of only one statement, its surrounding {} can be omitted.

Experienced Java programmers would almost always omit such {}, but it is probably better for beginners like us to leave them in for a while so that all if-then's and all if-then-else's have the same structure. There is an exception, see below.

On the right we see three of the four possibilities.
What is the remaining possibility?
Ans: The then block has exactly one statement and no {} while the else block has one or more statements and does have the {}.

3.7 Nested if Statements

The statement(s) in either the then block or the else block can include another if statement, in which case we say the two if statements are nested.

if (be1) { // be is Boolean expr
  if (be2) {
    ss1 // ss is statement(s)
  } else {
    ss2
  }
} else {
  if (be3) {
    ss3
  } else {
    ss4
  }
}

On the right we see three if statements, with the second and third nested inside the first.

If be1, the Boolean expression of the outside if statement evaluates to true, the second if statement is executed. If instead be1 is false, the third if statement executes.

There are eight possible truth values for the three Boolean expressions, but only four possible actions (ss1ss4).
Why?
Because for be1==true the value of be3 is irrelevant and for be1==false, the value of be2 is irrelevant.

What are the values of be1..be3 if ss1 is executed? Similarly for ss2..ss4.

Selecting One of Several Possibilities

if (be1) {
  ss1
} else {
  if (be2) {
    ss2
  } else {
    if (be3) {
      ss3
    } else {
      ssDefault
    }
  }
}

Often deeply nested if-then-else's have the nesting only in the else part. This gives rise to code similar to that shown on the right.

Be sure you understand why this is not the same as separate (non-nested) if statements.

One trouble with the code shown is that it keeps moving to the right and, should there be many condition, you either get very wide lines or only have a few columns to use.

This is one situation where it pays to make use of the fact that the {} can be omitted when they enclose exactly one statement. To make use of this possibility, you must realize that an if statement, in its entirety, is only one statement. For example, the entire nested if structure on the right is only one statement, even though it may have hundreds of statements inside its many {} blocks. Similarly, each of the nested ifs is just one statement.

if (be1) {
  ss1
} else if (be2) {
  ss2
} else if (be3) {
  ss3
} else {
  ssDefault
}

As a result we can remove the { and } from else { if ... } and convert the above to the code on the right.

Please read the code sequence carefully and understand why it is the same as the one above.

Note that the indenting is somewhat misleading as the symmetric placement of ss1, ss2, s3, and ssDefault suggests that they have equal status. In particular, since be1==true implies that ss1 gets execute, you might think that be3==true implies that ss3 gets executed. But that is wrong! Be sure you understand why it is wrong (hint: what if all the boolean expressions are true?).

Again be sure you can determine the truth values of be1..be3 if ss1..ss3 or ssDefault is executed.

Indeed this pattern with all the statements in the then block is so common that some languages (but not Java) have a keyword elsif that is used in place of the else if above.

Other languages require an explicit ending to an if statement, most commonly end if. In that case no {} are ever needed for if statements.

Homework: 3.7 and 3.9.

3.8 Common Errors in Selection Statements

Don't forget the {} when you have more that one statement in the then block or else block. I recommend that, while you are learning Java, you use {} for each ss even if it is just one statement (except for the else if situation illustrated just above).

Don't mistakenly put a ; after the boolean expression right before the then block.

Don't write if (be==true). Instead, write the equivalent if (be). Although the first form is not wrong; it is poor style.

The Notorious Dangling else

if (be1)
  if (be2)
    ss2
else
  ss1

The indenting on the right mistakenly suggests that should be1 evaluate to false, ss1 will be executed. That is WRONG. The else in the code sequence is part of the second (i.e., inner) if. Indeed, the Java (and C) rule is that an else pairs with the nearest unfinished if.

In a language like Python that enforces proper indentation, no such misleading information can occur.

In a language such as Ada with an explicit end if, the problem does not arise.

if (be1) {   if (be1) {
  if (be2)     if (be2) {
    ss2          ss2
} else {       } else {
  ss1            ss1
}              }
             }

On the right, I have added {} to make the meaning clear. The code on the far right has the same semantics as the dangling else code above. There is no ambiguity: the outer then has not yet ended so the else must be part of the inner if.

The code on the near right has the semantics that the original indentation above mistakenly suggested. Again there is no ambiguity: The inner if has ended (it is part of the outer then block) so the else must be part of the outer if

Start Lecture #5

Remark: Class taught by Prof. Marsha Berger.

3.9 Problem: An Improved Math Learning Tool

Read

3.9 (Alternate) Problem: A Yet Better Quadratic Solver

Let's fix two problems with our previous solution to solving the quadratic equation Ax2+Bx+C=0.

  1. We shamefully divided by 2A without checking if A==0. In that case we don't have a quadratic equation at all.
  2. After we determine that the discriminant is negative, we need not check if it is zero or if it is positive.

Let's do this in class; one possible solution is here.
However, I don't like the that solution. Why?
Hint -C/B.

Fixing this problem leads to rather confusing looking code due to all the nested tests.

How can we further beat this dead horse?
Answer: When the discriminant is negative, we could compute the complex roots and we could (actually should) add comments to make the cases clear.

3.10 Problem: Computing the Body Mass Index

Read.

3.11 Problem: Computing Taxes

Let's do this one in class. Handout sheet with tax table. A scan of the table is here.

The input is the filing status and the income.

Start Lecture #6

Remark: Class taught by Prof. Marsha Berger.

3.12 Logical Operators

Boolean Operators
OperatorName

!not
&&and
||or
^exclusive or

Like most computer languages, Java permits Boolean expressions to contain so-called "logical operators". These operators have Boolean expressions as both operands and results.

The table on the right shows the four possibilities. The not operator is unary (takes one operand) and returns the opposite Boolean value as result.

The and operator is binary and returns false unless when both operands are true.

The (inclusive) or operator is binary and returns true unless both operands are false. That is, the meaning of || is one or the other or both.

The exclusive or operator is binary and returns true if and only if exactly one operand is true. That is, the meaning of ^ is one or the other but not both. Note that ^ can also be thought of as not equal, but remember that the operands are Booleans not numbers.

Precedence of Logical, Comparison, and Arithmetic Operators

The logical operators have lower precedence than the comparison operators operators, which in tern have lower precedence than the arithmetic operators. In particular,
      x!=0 && 1/x < y+2   is evaluated as   (x!=0) && ( (1/x) < (y+2) )
which is normally what you want.

Short-Circuit Evaluation of && and ||

Since A&&B==false if either (or both) A==false or B==false, once we know that one of the operands to && is false, we need not evaluate the other.

Java makes use of this observation and ensures that, if the left operand of && is false, then the right operand is not evaluated. This, so-called short-circuit evaluation of A&&B saves a little time but, much more importantly, guarantees that
x!=0 && 1/x < y+2 will never divide by zero.

Similarly, if the left operand of || is true, the right operand is not evaluated.

3.13 Problem: Determining Leap Year

As you probably know a day is the time it takes the earth to rotate about its axis, and a year is the time it takes for the earth to revolve around the sun.

By these definitions a year is a little less than 365.25 days. To keep the calendars correct (meaning that, e.g., the vernal equinox occurs around the same time each year), most years have 365 days, but some have 366.

A very good approximation, which will work up to at least the year 4000 and likely much further is as follows (surprisingly, we are not able to predict exactly when the vernal equinox will occur far in the future; see wikipedia).

Let's write in class a program that asks for the year and replies by saying if the year is a leap year. One solution is in the book.

3.14 Problem: Lottery

The book has an interesting lottery game. The program generates a random 2-digit number and accepts another 2-digit number from the user.

We will write this in class, but need a new library method from Java, Math.random() returns a random number between 0 and 1. The value returned, a double, might be 0, but will not be 1. Since math.Random() is in java.lang we do not have to import it.

One solution is in the book.

3.15 switch Statements

We have seen how to use if-then-else to specify one of a number of different actions, with the chosen action determined by a series of Boolean expressions.

Often the Boolean expressions consist of different values for a single (normally arithmetic) expression.

switch(expression) {
  case value1: stmts1
  case value2: stmts2
  ...
  case valueN: stmtsN
  default:  defaultStmts
}

For example we may want to do one action if i+j is 4, a different action if i+j is 8, a third action if i+j is 15, and a fourth action if i+j is any other value. In such cases the switch statement, shown on the right, is appropriate.

When a switch statement is executed, the expression is evaluated and the result specifies, which case is executed.

The semantics of switch are somewhat funny. Specifically, given the visual similarity with if-then-else, one might assume that after stmts1 is executed, control transfers to the end of the switch. However, this is wrong: Unless explicitly directed, control flows from one case to another.

The semantics of the C/Java switch closely resemble those of an assembler-level jump table and the computed goto of Fortran.

switch (expression) {
  case value1: stmts1
               break;
  case value2: stmts2
               break;
  ...
  case valueN: stmtsN
               break;
  default:  def-stmts
}

It is quite easy to explicitly transfer control from the end of one case to the end of the entire switch. Java, again borrowing from C, has a break statement that serves this purpose.

We see on the right the common form of a switch in which the last statement of each case is a break.

When a break is executed within a switch, control breaks out of the switch. That is, execution proceeds directly to the end of the switch. In particular, the code on the right will execute exactly one case or the default.

Homework: 3.11, 3.25.

3.16 Conditional Expressions

Recall that Java has +=, which shortens x=x+5 to x+=5. Java has another shortcut, the tertiary operator ?...:, which can be used to shorten an if-then-else.

bool-expr ? expr1 : expr2

if (x==5)
  y = 2;
else
  y = 3;

y = x==5 ? 2 : 3;

The general form of the so-called conditional expression is shown on the top right. The value of the entire expression is either expr1 or expr2, depending on the value of the Boolean expression bool-expr.

For example, the middle right if-then-else can be replaced by the bottom right assignment statement containing the equivalent conditional expression.

Note that this is not limited to arithmetic and can often be used to produce grammatically correct output. For example, consider the following beauty.

  System.out.println ("Please input " + n + " number" + ((n==1) ? "." : "s."));

I realize this looks weird; be sure you see how it works.

Homework: Redo 3.7, this time using a switch.

Start Lecture #7

3.17 Formatting Console Output

A comparatively recent addition to Java is the C-like printf method. (It was added in Java 2 Standard Edition 5; we are using J2SE 7).

System.out.printf(format, item1, item2, ..., item n);

The first point to make is that printf() takes a variable number of arguments, as indicated on the right. The required first argument, which must be a string, indicates how many additional arguments are needed and how those arguments are to be printed.

Wherever the first argument contains a %, the value of the next argument is inserted (a double %% is printed as a single %).

System.out.printf("i = %d and j = %d", i, j);
System.out.printf("x = %f and y = %e", x, y);

The first line on the right prints the integer values of x and y.
The second line prints two real values, the second using scientific notation.

Common Specifiers
SpecifierOutput

%bBoolean
%cCharacter
%dInteger
%fFloating Point
%eScientific Notation
%sString

The table on the right gives the most common specifiers. Although Java will perform a few coercions automatically (e.g., an integer value will be coerced to a string if the corresponding specifier is %s), most conversions are illegal. For example

  int i;    double x;    int one=1, two=2;
  System.out.printf("Both %f and %d are bad\n", i, x);
  System.out.printf("%d plus %d is %d\n", one, two, one+two); // OK

You could try to find which conversions are OK, but I very much suggest that instead you do not use any. That is use %f and %e only for floats and doubles; use %d only for the four integer types; use %s only for Strings, etc.

Width and Precision

System.out.printf("%d\n%d\n",45,7);

45
7

45
 7

System.out.printf("%2d\n%2d\n",45,7);

System.out.printf("%2d\n%2d\n",45,789);

45
789

Note that %d uses just enough space to print the actual number; it does not pad with blanks. If you print several integers one per line using %d they won't line up properly. For example, the printf() on the right will produce the first output, not the second.

To remedy this problem you can specify a minimum width between the % and the key-letter. For example the second printf() does produce the second output.

But what would happen if we tried to print 543 using %2d? There are two reasonable choices.

  1. Print something like ** the way some spread sheets display a value in a cell that is too narrow.
  2. Use three columns.

Java chooses the second option. So the bottom printf() produces the bottom output.

These same considerations apply to the other 5 specifiers in the table: if the value is not as wide as the specified width, the value is right justified and padded on the left with blanks.

For the real number specifiers %f and %e one can specify the precision in addition to the (minimum) width. This is done by writing the width, then a period, and then the precision between the % and the letter.

For example %6.2 means that the floating-point value will be written with exactly 2 digits after the decimal point and if the result (including a minus sign if needed) is less than 6 characters, the value will be padded with blanks on the left.

Left Justification

As we have seen, when the width is specified (with or without precision), the value is right justified if needed and padded on the left with blanks. This is normally just what you want for numbers, but not for strings.

For all specifiers (numbers, strings, etc) Java supports left justification (padding on the right with blanks) as well: Simply put a minus sign right before the width as in %-9f, %-10.2e, or %-7s.

3.18: Operator Precedence and Associativity

Operator Precedence in
decreasing order
OperatorAssociativity

var++, var--Left-to-right
+,- (unary), ++var, --varRight-to-left
(type)Right-to-left
!Left-to-right
*,/,%Left-to-right
+,- (binary)Left-to-right
<,<=,>,>=Left-to-right
==,!=Left-to-right
^Left-to-right
&&Left-to-right
||Left-to-right
?: (tertiary)Right-to-left
=,+=,-=,*=,/=,%=Right-to-left

The table on the right lists the operators we have seen in decreasing order of precedence. This order of precedence means that, in the absence of parentheses, if an expression contains two operators from different rows of the table, the operator in the higher row is done first.

The second column gives the associativity of the operators. If an expression contains two operators from the same row and that row has left-to-right associativity, the left operator is done first. Similarly if the row has right-to-left associativity, the right operator is done first. As with precedence, parentheses can be used to override this default ordering.

Not many programmers have the entire table memorized (not to mention the fact that there are other operators we have not yet encountered). Instead, they use parenthesis even when their use might not be necessary due to precedence and associativity.

However, it is wise to remember the precedence and associativity of some of the frequently used operators. For example, one should remember that *,/,% are executed before (binary +,-) and that both groups have left-to-right associativity. It would clutter up a program to see an expression like
((x*y)/z)/(((-x)+z)-y) rather than the equivalent (x*y/z)/(-x+z-y)

Lab 1 Part 2 is here. Due 21 February, 2012.

Homework: Write a java program that reads four integers. If any two (or any three, or all four) are equal, just print an error message. If all four are distinct, print the 2nd largest.

Homework: Contemplate but do NOT write a java program that first reads 11 integers. If any two (or any three, or ..., or or all eleven) are equal, just print an error message. If all eleven are distinct, print the 6th largest (i.e., the middle value). There must be a better way! There is.

3.19: (Gui) Confirmation Dialogs

Chapter 4: Loops

4.1: Introduction

As you know from Python, loops are used to execute the same block of code multiple times. There are several kinds of Java loops: The while (and do-while) are fairly general; the for is most convenient when the loops are counting, but can be used (abused?) in quite general ways.

4.2: The while Loop

while (BE) {
  stmts
}

Some early languages (notably early Fortran) did not have a while loop, but essentially all modern languages (including Fortran) do.

The semantics are fairly simple:

while
  1. The Boolean expression (BE) is tested. If it is false the while loop ends.
  2. If the BE is true, the statements (stmts) in the loop body are executed.
  3. The process repeats from the beginning.

The idea of a while loop is that the body is executed while (i.e, as long as) the Boolean expression is true.

The flowchart on the right illustrates these simple semantics.

Note that if the Boolean expression is false initially, the loop body (the statements in the diagram) are not executed at all. We shall see in the next section a loop in which the body is executed at least once.

Similarly, note that right after the while loop is executed, the BE is FALSE.
Please don't get this wrong.

while ((i=getInput.nextInt) >= 0) {
  // process the non-negative i
}

For example, when the loop on the right ends (assuming no EOF), the value of i is negative!

Let us write a program that adds two non-negative numbers just using ++ and --. One solution is here.

This is actually how one defines addition starting with Peano's postulates

4.2.1 Guessing a Number

The program picks a random integer between 0 and 100 inclusive (101 possibilities). The users repeatedly guess until they are correct. For each guess, the program states higher, lower, or correct.

Let's do this in class; one solution is in the book, another is here.

4.2.2 Loop Design Strategies

There are four parts to a loop and hence 4 tasks for the programmer to accomplish.

  1. Initialization (e.g., i = 0;)
  2. Continuation test (e.g., i < 10)
  3. Body
  4. Update for next iteration (e.g., i++)

As you have seen in Python and we shall see in Java, loops can be quite varied. For one thing the body can be almost arbitrary. For now let's assume that the body is the quadratic equation solver that we have written in class. Currently it just solves one problem and then terminates. How can we improve it to solve many quadratic equations?

final int n=10;
int i = 0;
while (i<n) {
  // input A, B, C
  // solve one equation
  i++;
}

n=getInput.nextInt();
int i = 0;
while (i<n) {
  // input A, B, C
  // solve one equation
  i++;
}

// input A, B, C
while (A!=0 || B!=0 || C!=0) {
  // solve one equation
  // input A, B, C
}

while (true) {
  // input A, B, C
  if (A==0 && B==0 && C=0) {
    break;
  }
  // solve one equation
}

There are at least four techniques, each of which can be applied to many different problems.

  1. Hardwire the count.
    The code on the upper right has the count (10) within the code itself. (This can be done without n, by initializing i=10; testing i>0, and decrementing i--.)
  2. Read the count.
    We can input n rather that hardwiring it. This is illustrated in the second code sequence on the right. As with the hardwired count, one variable suffices. For these first two possibilities a for loop would be more convenient that a while (see section 4.4).
  3. Use a sentinel, that is, choose a special value, which will never be used for a real computation (say A, B, and C all zero in our quadratic equation solver) and, if an input has this sentinel, the loop terminates.
  4. Check for EOF (end of file).
    If, instead of using one of the nextX() methods in the Scanner class, we studied and used the read() method in the System.in object, the program could detect when the EOF is reached. In this way, we can continue to input problems and solve them until there are none remaining. A very simple example is in the optional 4.2.A; however, this requires a concept (exceptions) that we will not cover for quite a while.

4.2.3 An Advanced Math Learning Tool

4.2.3 (Alternate) Improving the Quadratic Solver

import java.util.Scanner;
public class Quadratic5 {
  public static void main (String[] Args) {
    System.out.println("Solving quadratic equations Ax^2 + Bx + C");
    // The next line is for hardwiring; we are reading the count
    // final int count = 10;
    int count;
    System.out.println("How many equations are to be solved?");
    Scanner getInput = new Scanner(System.in);
    count = getInput.nextInt();
    while (count-- > 0) {
      System.out.println("Enter real numbers A, B, C");
      double A = getInput.nextDouble();
      double B = getInput.nextDouble();
      double C = getInput.nextDouble();
      double discriminant = B*B - 4.0*A*C;
      double ans;
      if (A == 0) {
        System.out.println("A linear equation; only one root");
        ans = -C/B;
        System.out.println("The one root is " + ans);
      } else if (discriminant < 0) {
        System.out.println("No (real) roots");
      } else if (discriminant == 0) {
        System.out.println("One (double) root");
        ans = -B/(2*A);
        System.out.println("The double root is/are " + ans);
      } else {          // discriminant > 0
        double ans1 = (-B + Math.pow(discriminant,0.5))/(2.0*A);
        double ans2 = (-B - Math.pow(discriminant,0.5))/(2.0*A);
        System.out.println("The roots are " + ans1 + " and " + ans2);
      }
    }
  }
}

On the right is the code for a yet further improved quadratic equation solver. This code illustrates several of the points made above.

  1. Commented out is a line that would have been added to hardwire the count.
  2. As written, the count is read in. Thus the first two alternatives in 4.2.2 are shown.
  3. By executing
      while(count-- > 0))
    we ensure that the loop body, which inputs and solves one quadratic equation, is executed exactly count times. Be sure you understand why count-- was used, and why --count would not work.
  4. Notice how the code as written clearly separates the work of inputting and solving an equation from the work of ensuring that we solve the correct number of equations. The former is the loop body; the latter the Boolean expression.
  5. When we learn Java methods, we will see that we could pull the loop body out into a separate method.
  6. Alternatively we could write a method
      solve(A,B,C)
    That would solve one equation and print the results. This method would be called from the body of the loop right after the values of A, B, and C have been read it.

Start Lecture #8

4.2.4 Controlling (i.e., Ending) a Loop with a Sentinel Value

import java.util.Scanner;
public class SumN {
  public static void main (String[] args) {
    Scanner getInput = new Scanner(System.in);
    double x, sum;
    int i, n;
    while (true) {
      System.out.println("How many numbers do you want to add?");
      n = getInput.nextInt();
      if (n < 0) {
        break;
      }
      System.out.printf("Enter %d numbers: ", n);
      i = 0;
      sum = 0;
      while (i++ < n) {
        x = getInput.nextDouble();
        sum += x;
      }
      System.out.printf("The sume of %d values is %f\n", n, sum);
    }
  }
}

On the right is a simple program to sum n numbers. Since it does not make sense to sum a negative number of numbers, we let n<0 be the sentinel that ends the program.

There are again a few points to note.

  1. while(true) gives an non-ending loop sine the BE (boolean expression) is always true. So there must be some other way the loop ends.
  2. This is an n and 1/2 times loop: The first part of the body is executed one more time than the second part.
  3. break is used to break out of the loop, see section 4.9.
  4. This example illustrates a nested loop, that is, a loop inside a loop, which officially we haven't seen yet.
  5. The inner loop is itself a simple loop that sums n numbers.
  6. The outer loop is needed since we want to sum n numbers several times, with possibly different values for n each time.

Lab 1 Part 3 is here. It is due 23 February 2012.

4.2.A Ending a Loop with an EOF (End of File)

public class UseEOF {
  public static void main(String[] args)
         throws java.io.IOException {
    final int EOF = -1;
    int c;
    while ( (c = System.in.read()) != EOF)
      System.out.printf("%c",c);
  }
}
  

On the right is a very simple loop that is terminated by EOF (end-of-file). However, it does use an advanced feature of Java, namely exceptions.

The read()method either returns the next byte (an integer in the range 0..255) from System.in or it returns -1 to signify an end of file. (If an I/O error occurs an exception is thrown.)

The Java library did the heavy lifting (the EOF technique); enabling my program to use the simpler sentinel technique.

4.2.5 Input and Output Redirections

As you know System.in is normally the keyboard and System.out is normally the display. However, they can be redirected to be an ordinary file in the file system. When System.in is redirected to a file f, input is taken from f instead of from the keyboard, and when System.out is redirected to a file g, output goes to g.

For example the program UseEOF with the mysterious implementation, simply copies all its input to its output.

I copied it to i5 so we can run it (ignore how it works).

If we type simply  java UseEOF,  then whatever we type will appear on the screen.

If we type instead  java UseEOF <f,  then the file f will appear on the screen.

Similarly  java UseEOF <f >g copies the file f to the file g.

4.3 The do-while Loop

do-while

As we have seen a while loop repeatedly performs a test-action sequence: first a BE is tested and then, if the test passes, the body is executed.

In particular, if the test fails initially, the body is not executed at all.

Most of the time, this is just what is wanted; however, there are occasions when we want the body to be first and the test second. In such situations we use a do-while loop.

do {
  stmts
} while (BE);

Note that unlike the behavior we have seen for while loops a do-while loop is guaranteed to execute its body at least once.

We see the code skeleton for this loop on the near right and the flowchart on the far right.

final int quota = 250;
int count=0;
do {
  count++;
} while ((quota-=getInput.nextInt()) > 0);
System.out.printf("Needed %d values.\n", count);

4.3.A Reaching a Quota

In the code on the right a quota is hardwired for brevity. Then the user inputs a series of numbers (say the dollar value of a sale). When they finally reach or surpasses the quota, the program ends and reports how many values were needed.

4.4 The for Loop

for

Recall the four components of a loop as stated in 4.2.2

  1. Initialization (e.g., i = 0;)
  2. Continuation test (e.g., i < 10)
  3. Body
  4. Update for next iteration (e.g., i++)

On the right we show a flowchart illustrating these four components. This particular flowchart is representative of a while loop.

Homework: Draw the corresponding flowchart for a do-while loop.

Parts 1, 2, and 4 can all be written in one for statement. After the word for comes a parenthesize list with three elements separated by semicolons. The first component is the initialization, the second the test, and the third the update to prepare for the next iteration.

for(i=0; i<10; i++){
  body
}

for(int i=0; i<10; i++)

for(i=0, j=n; i*j < 100;  i++, j--)

for( ; ; )

The first code shown on the right presents the for loop corresponding to the example mentioned in the list of loop components given just above.. Indeed, this is a common usage of for, to have a counter step through consecutive values.

The second for on the right shows that the loop variable can be declared in the for itself. One effect of this declaration is that the variable, i in this case, is not visible outside the loop. If there was another declaration of i inside another loop, the two variables named i would be unrelated.

It is also possible to initialize and update more than one variable as the third example shows.

Finally, the various components can be omitted, in which case the omitted item is essentially erased from the flowchart. For example, if the first component is omitted, there is no initialization performed; if the middle item is omitted there is no test (it is perhaps better to say that the test always yield true); and, if the third item is omitted there is no update. So the last, rather naked, for, similar to a while(true), will just keep executing the body until some external reason ends the loop (e.g., a break).

Homework: 4.5, 4.1 (the book forgot to show inputing the number of numbers), 4.9

4.5 Which Loop to Use?

They are equivalent. Any loop written with one construct can be written with either of the other two.

The for loop puts all the non-body in one place. If these pieces are small, e.g., for(i=0;i<n;i++), this is quite convenient. If they are large and or complicated, the while form is generally easier to read.

Much of this is personal preference. I don't believe there is a right or wrong answer.

4.6 Nested Loops

We have already done a nested loop in section 4.2.4.

for (int i=1; i<n-1; i++) {
  for (int j=i+1; j<n; j++) {
    // if the ith element exceeds the jth,
    // swap them
  }
}

It is essentially the same as in Python or any other programming language. The inner loop is done repeatedly for each execution of the outer loop.

For example, once we learn about arrays, we will see that the code on the right is a simple (but, alas, inefficient) way to sort an array.

4.7 Minimizing Numeric Errors

This concerns the hazards of floating point arithmetic. Although we will not be emphasizing numeric errors, two general points can be made.

  1. When comparing floating point values do not use equality (or inequality) testing. Either
  2. When adding many floating point values try to add the smaller ones first.

4.8 Case Studies

4.8.1 Finding the Greatest Common Divisor

Given two positive integers, the book just tries every number from one up to the smaller input and chooses the largest one that divides both of the inputs.

Let try a different way to get the GCD. Keep subtracting the smaller from the larger until both are equal, at which point you have the GCD.

(54, 36) → (18, 36) → (18, 18) → 18.
(7, 6) → (1, 6) → (1, 5) → ... → (1, 1) → 1.

Do this in class. A solution is here.

4.8.2 Predicting the Future Tuition

4.8.3 Monte Carlo Simulation

I hate to take problems straight from the book since you can read it there, but this is too good to pass up.

Calculate π by choosing random points in a square and seeing how many are inside a circle.

Do in class. One solution is here.

4.9 Keywords break and continue

We have already seen break for breaking out of a switch statement.

When break is used inside a loop, it does the analogous thing: namely it breaks out of the loop. That is execution transfers to the first statement after the loop. This works for any of the three forms of loops we have seen: while, do-while, and for.

In the case of nested loops, the break just breaks out of the innermost loop.

There is an alternate form of break that can be used to break out of many levels of looping or to break out of other constructs as well.

The continue statement can be used only inside a loop. When executed, it continues the loop by ending the current iteration of the body and proceeding to the next iteration. Specifically:

  1. In a while loop, a continue transfers control to the test at the top of the loop.
  2. In a do-while loop, a continue transfers control to the test at the bottom of the loop.
  3. In a for loop, a continue transfers control to the update at the bottom of the loop (from where control transfers to the test at the top).

4.9.1 Displaying Prime Numbers

We want to display the first N primes. Unlike the book, we will

  1. Input N, not have it hardwired.
  2. Try divisors up to only the square root of the candidate prime.

Although the second improvement does make the program much faster for large primes, it is still quite inefficient compared to the best methods, which are very complicated and use serious mathematics.

Start it in class.

Homework: Write this program in Java and test it for N=25.

4.10 (GUI) Controlling a Loop with a Confirmation Dialog

Chapter 5 Methods

5.1 Introduction

Like all modern programming languages, Java permits the user to abstract a series of actions, give it a name, and invoke it from several places.

Many programming languages call the abstracted series of actions a procedure, a function, or a subroutine. Java, and some other programming languages, call it a method.

We have seen a method already, namely main().

5.2 Defining a Method

We know how to do this since we have repeatedly defined the main method. The general format of a method definition is.

  modifiers returnType name (list of parameters) { body }

For now we will always use two modifiers public static.

The main() method had returnType void since it did not return a value. It also had a single argument, an array of Strings.

The returnType and parameters will vary from method to method.

public static int myAdd(int x, int y) {
  return x+y;
}

On the right we see a very simple method that accepts two int parameters and returns an int result. Note the return statement, which ends execution of the method and optionally returns a value to the calling method.

The type of the value returned will be the returnType named in the method definition.

Start Lecture #9

5.3 Calling a Method (that Returns a Value)

public class DemoMethods {
  public static void main(String[]) {
    int a = getInput.nextInt();
    int b = getInput.nextInt();
    System.out.println(myAdd(a,b));
  }
  public static int myAdd(int x, int y) {
    return x+y;
  }
}

We have called a number of methods already. For example getInput.nextInt(), Math.pow(), Math.random(). There is very little difference between how we called the pre-defined methods above and how we call methods we write.

One difference is that if we write a method and call it from a sibling method in the same class, we do not need to mention the class name.

On the right we see a full example with a simple main() method that calls the myAdd() method written above. You can mention the class name if you like. so the argument to println() could have been DemoMethods.myAdd(a,b)

Method such as myAdd() that return a value are used in expressions the same way as variables and constants are used, except that they contain a (possibly empty) list of parameters. They act like mathematical functions. Indeed, they are called functions in some other programming languages.

Note that the names of the arguments in the call do NOT need to agree with the names of the parameters in the method definition. This is a good thing. For example, how could we know the parameter names of Math.pow() , since we have never seen the method definition?

Lab 1 Part 4 (the last part) and Lab 1 Honors Supplement 1 (the only supplement for lab 1) are both here. They are due 28 February 2012.

Homework: 5.1, 5.3.

5.3.1 Call Stacks

The LIFO (Last In, First Out) semantics of method invocation (f calls g and then g calls h, means that, when h returns, control goes back to g, not to f.

Method invocation and return achieves this semantics as follows.

5.4 void Method Example

public class DemoMethods {
  public static void main(String[]) {
    int a = getInput.nextInt();
    int b = getInput.nextInt();
    printSum(a,b);
  }
  public static void printSum(int x, int y) {
    System.out.println(x+y);
  }
}

Methods that do not return a value (technically their return type is void) are invoked slightly differently. Instead of being part of an expression, void methods are invoked as standalone statements. That is, the invocation consists of the method name, the argument list, and a terminating semicolon.

An example is the printSum() method invoked by main() in the example on the right.

Homework: 5.5, 5.9.

5.5 Passing Parameters by Values

Referring to the example above, the arguments a,b in main() are paired with the parameters x,y in printSum in the order given, a is paired with x and b is paired with y.

The result is that x and y are initialized to the current values in a and b. Note that this works fine if an argument a or b is a complex expression and not just a variable. So printSum(4,a-8) is possible.

public class DemoPassByValue {
  public static void main(String[]) {
    int a = 5;
    int b = 6;
    int c = 7;
    tryToAdd(a,b,c);
  }
  public static void tryToAdd(int x, int y, int z) {
    x = y + z;
  }
}

The parameters, however, must be variables.

Java is a pass-by-value language (a.k.a. call-by-value). This means that, at method invocation, the values in the arguments are transmitted to the corresponding parameters, but, at method return, the final values of the parameters are not, repeat NOT sent back to the parameters.

For example the code on the right does Not set a to 13.

When we learn about objects and their reference semantics, we will need to revisit this material.

5.6 Modularizing Code

Programs are often easier to understand if they are broken into smaller pieces. When doing so, the programmer should look for self-contained, easy to describe pieces.

public class DemoModularizing {
  public static void main(String[] args) {
    // input 3 points (x1,y1), (x2,y2), (x3,y3)
    double perimeter = length(x1,y1,x2,y2) +
      length(x2,y2,x3,y3) + length(x3,y3,x1,y1);
    System.out.println(perimeter);
  }
  public static length(double x1, double y1,
                       double x2, double y2) {
    return Math.sqrt((x2-x1)*(x2-x1) + (y2-y1)*(y2-y1));
  }
}

Another advantage is the ability for code reuse. In the very simple example on the right, we want to calculate the perimeter of a triangle specified by giving the (x,y) coordinates of its three vertices.

Recall that the perimeter is the sum of the lengths of the three sides and the length of a line is the square root of the sum of the squares of the difference in coordinate values.

Without modularization, we would have one large formula that was the sum of the three square roots.

As written on the right, I abstracted out the length calculation, which in my opinion, makes the code easier to understand.

Less trivial examples are the methods in the Math class. Imagine writing a geometry package and having to code the square root routine each time it was used.

A Modularized Monte Carlo Approximation of Pi

public class Pi {
  public static void main (String[] args) {
    final long NUM_BALLS = (long)1.0E11; // could read this in
    long inside = 0;                     // number in circle
    double x, y;
    System.out.println("     Dropped      Inside  Estimate of pi");
    for (long dropped=1; dropped<=NUM_BALLS; dropped++) {
      x = Math.random();
      y = Math.random();
      if (dist(x,y,0.5,0.5)<0.5)
        inside++;
      if (interesting(dropped))
        System.out.printf("%12d%12d%14.10f\n",
           dropped,inside,estimate(dropped,inside));
    }
  }
  public static double dist(double x1, double y1, double x2, double y2) {
    return Math.sqrt((x1-x2)*(x1-x2) + (y1-y2)*(y1-y2));
  }
  public static double estimate(long dropped, long inside) {
    return 4.0*((double)inside / (double)dropped);
  }
  public static boolean interesting(long x) {
    double l10 = Math.log10(x);    // Powers of 10 = l10 an integer
    return Math.abs(l10 - Math.rint(l10)) < 1.0E-14;
  }
}

javac Pi.java  && time java Pi
     Dropped      Inside  Estimate of pi
           1           1  4.0000000000
          10           9  3.6000000000
         100          82  3.2800000000
        1000         780  3.1200000000
       10000        7806  3.1224000000      real    222m10.434s
      100000       78634  3.1453600000      user    168m54.970s
     1000000      786032  3.1441280000      sys       0m35.810s
    10000000     7853077  3.1412308000
   100000000    78539849  3.1415939600
  1000000000   785396741  3.1415869640
 10000000000  7853941114  3.1415764456
100000000000 78539619440  3.1415847776

On the right we see a modularized version of the Monte Carlo method of approximating π discussed in section 4.8.3.

Recall that we simply drop balls onto the unit square and keep track of what proportion lie inside the inscribed circle. This proportion is approximately equal to the ratio of the area of the circle to the area of the square.

We then assume the ratios are equal and solve the resulting equation for π

I wrote this program in a top down fashion (see 5.12) starting with the main() and defining a new method whenever there was anything to figure out.

This generated three methods: dist() used to see if the ball was inside the circle, estimate() to calculate the current approximation, and interesting() to decide if the current estimate is worth printing (the hardest part of the program).

Below the program we see the output when it is run. The time command times the execution of the command that follows. To save vertical space, I moved the timing information to the right of the regular output; in reality it occurs below that output.

We see that the program ran for 222 minutes. For 169 minutes the program was itself executing; for 36 minutes, the operating system was executing on the program's behalf. For the remaining time, some other program must have been running or this program was block doing I/O (Pi itself probably does less than 1 second of I/O but javac does several seconds).

5.7 Problem Converting Decimals to Hexadecimals

Read.

5.8: Overloading Methods

A very nice feature of Java and other modern languages is the ability to overload method names.

public class DemoOverloading {
  public static void main(String[] args) {
    System.out.printf ("%d %f %f\n", max(4,8),
                       max(9.,3.), max(9,3.));
  }
  public static int max(int x, int y) {
    return x>y ? x : y;
  }
  public static double max(double x, double y) {
    return x>y ? x : y;
  }
  public static double max(int x, double y) {
    return ((double) x)>y ? x : y;
  }
}

Of course if you use a method named max and another named min each with two integer arguments, Java is not confused and invokes max when you write max and invokes min when you write min.

All programming languages do that.

But now look on the right and see three methods all named max, one returning the maximum of two ints one returning the maximum of two doubles, and the third returning (as a double) the max of an int and a double.

When compiling a method definition javac notes the signature of the method, which for overloading consists of the method name, the number of parameters, and the type of each parameter.

When an overloaded method is called, the method chosen is the one with signature matching the arguments given at the call site.

Technical fine points: Java will coerce a 9 to a double if needed. But if an overloaded method has two signatures, one requiring coercion and the other not, the second is chosen. If the overloaded method has two signature, each with two parameters, having types so that one would require coercing the first argument and the other would require coercing the second argument, the program does not compile.

5.9 the Scope of Variables

The scope of a declaration is the portion of the program in which the declared item can be referenced.

The rules for scope vary from language to language.

In Java a block is a group of statements enclosed in {}. For example, the body of most loops, the body of most then and else clauses of an if, and the body of a method are all blocks. (One statement bodies of then clauses, else clauses, and loops can be written without {} and then are not blocks).

A variable declared inside a method (the only kind of variables we have used so far) is called a local variable.

In Java the scope of a local variable begins with its declaration and continues to the end of the block containing the declaration.

We have seen an exception: if the initialization portion of a for statement contains a declaration, that local variable has scope from the declaration to the end of the loop body.

Similarly, a parameter in a method definition is a local variable and its scope extends from the parameter declaration to the end of the method body.

5.9.A Nested vs Non-nested Blocks

On the right we see two skeletons. The near right skeleton contains two blocks block1 and block2, neither of which is nested inside the other.

something { |   something {
  block1    |     start of block3
}           |     something {
something { |       block4
  block2    |     }
}           |   }

In this case it is permitted to declare two variables with the same name, one in block1 and one in block2. These two variables are unrelated. That is, the situation is the same as if they had different names.

On the far right we again see two blocks, but this time block4 is nested inside block3. In this situation it is not permitted to have the same variable name declared in both blocks. Such a program will not compile.

Some languages do permit a local variable to be redeclared in a nested block. When that occurs, the outer declaration is hidden when execution is in the inner block. This is sometimes called a hole in the scope of the outer declaration.

5.10 The Math Class

We have already used a few methods from the Math class.

In addition to various methods, some of which are described below, the Math class defines two important mathematical constants, E, the base of the natural logarithm, and PI, the ratio of a circle's perimeter to its diameter.

public static double sin(double theta)
public static double cos(double theta)
public static double tan(double theta)
public static double asin(double x)
public static double acos(double x)
public static double atan(double x)

public static double toDegrees(double radians)
public static double toRadians(double degrees)

5.10.1 Trigonometric Methods

The six routines on the upper right compute the three basic trig functions and their inverses. In all cases, the angles are expressed in radians.

The two below are used to convert to and from degrees.

5.10.2 Exponent Methods

public static double pow(double a, double b)
public static double sqrt(double a)

public static double log(double x)
public static double exp(double x)

public static double log10(double x)

The first routine on the right computes ab. Since the special case of square root, where b=.5 is so widely used it has its own function, which is given next.

Mathematically, logs and exponentials based on Math.E are more important than the corresponding functions using base 10. They come next.

The base 10 exponential is handled with pow, but there is a special function for the base 10 log as shown.

5.10.3 The Rounding Methods

Given a floating point value, one can think of three integer values that correspond to this floating point value.

  1. The floor, i.e., the greatest integer not greater than.
  2. The ceiling, i.e., the least integer not less than.
  3. The nearest integer. What about ties?
public static double floor(double x)
public static double ceil(double x)
public static double rint(double x)

The Math class has three methods that correspond directly to these mathematical functions, but return a type of double. They are shown on the right. For the third function, ties are resolved toward the even number so rint(-1.5)==rint(-2.5)==-2.

public static int round(float x)
public static long round(double x)

In addition Math has an overloaded method round() that rounds a floating point value to an integer value. The two round method are distinguished by the type of the argument (standard practice for overloaded methods). Given a float, round() returns an int. Given a double, round() returns a long.

This choice prevents the loss of precision, but of course there are many floats and doubles (e.g., 10E30) that are too big to be represented as an ints or longs.

5.10.4 The min, max, and abs Method

These three methods are heavily overloaded, they can be given int, long, float, or double arguments (or, for min and max one argument of one type and the other of another type).

5.10.5 The random Method

We have already used Math.random() several times in our examples. As mentioned random() returns a double x satisfying 0≤x<1.

To obtain a random integer n satisfying m≤x<n you would write m+(int)((n-m)*Math.random()). For example, to get a random integer 10≤x<100, you write 10+(int)(90*random()).

5.11 Case Study: Generating Random Characters

Let's do one slightly different from the book's.

Print N (assumed an even number) random characters, the odd ones lower case and the even ones upper case.

public class RandomChars {
  public static void main(String[] args) {
    final int n = 1000;     // assumed EVEN
    for(int i=0; i<n/2; i++) {
      System.out.printf("%c", pickChar('a'));
      System.out.printf("%c", pickChar('A'));
    }
  }
  public static char pickChar(char c) {
    return (char)((int)(c)+(26*Math.random()));
  }
}

The program on the right is surprisingly short. The one idea used to create it was the choice of the pickChar() method. This method is given a character and returns a random character between the given argument and 26 characters later. So, when given 'a, it returns a random lower case character and, when given 'A, it returns a random upper case character.

Remember that in Unicode, the representation used for Java chars, lower case (so called latin) letters are contiguous as are upper case letters.

Be sure you understand the value returned by pickChar().

Homework: 5.17, 5.35.

5.12 Method Abstraction and Stepwise Refinement

One big advantage of methods is that users of a method do not need to understand how the method is implemented. The user needs to know only the method signature (parameters, return value, thrown exceptions) and the effect of the method.

Although I remember enough calculus to derive the Taylor series for sine, that might not be the best way to implement sin(). Moreover, I would need to work very hard to produce an acceptable implementation of atan(). Nonetheless, I would have no problem writing a program accepting a pair (x,y) representing a point in the plane and producing the corresponding (r,θ) representation. To do this I need to just use atan(), not implement it.

As mentioned previously, when given a problem that by itself is too complicated to figure out directly, it is often helpful to break it into pieces. A very simple example was in the previous section. Separating out pickChar() made the main program easy and pickChar itself was not very hard.

5.12.1 Top-Down Design

figure-5.12

We haven't done anything large enough to illustrate top-down design in all its glory. The idea is to keep breaking up the problem. That is, you first specify a few methods that would enable you to write the main() method.

Then for each of these new methods M you define other submethods that are used to implement M and the process repeats.

The figure on the right (a very slightly modified version of figure 5.12 from the 8e) shows this design philosophy applied to the problem of printing a calendar for any given month.

You can guess the functionality of most of the methods from their name (an indication of good naming). Only getTotalNumberOfDays requires explanation. It calculates the number of days from 1 January 1800 to the first day of the month read from the input.

5.12.2 Top-Down or Bottom-Up Implementation

Given a fleshed out design as in the diagram, we now need to implement all the boxes. There are two styles top-down and bottom-up.

In the first, you perform a pre-order traversal of the design. That is, you start by implementing the root calling the (unimplemented) child methods as needed.

In order to test what you have done before you are finished, you implement stub versions of the methods that have been called but not yet implemented. For example, the stub for a void method such as printMonthBody might do nothing or might just System.out.println("printMonthBody called");.

Stubs for non-void methods need to return a value of the correct type, but that value need not be correct.

In a bottom-up approach, you perform a post-order traversal of the design tree. That is, you implement only boxes all of whose children have been implemented (we are ignoring recursive procedures here). In the beginning this means you implement a leaf of the tree.

Since you implement the main program last, you need to write test programs along the way to test what you have implemented so far.

5.12.3 Implementation Details

Read. In particular read over the full implementation of printCalendar.java

Chapter 6 Single-Dimensional Arrays

6.1 Introduction

6.2 Array Basics

Arrays are an extremely important and popular data structure that are found in essentially all programming languages. An array contains multiple elements all of the same type.

In this chapter we restrict ourselves to a 1-dimensional (1D) array, which is essentially a list of values of a given type. The next chapter presents the more general multi-dimensional array.

In Java the ith element of a 1D array named myArray is referred to as myArray[i]. Note the square brackets [].

6.2.1 Declaring Array Variables

main(String[] args)

In the example on the right, which we have used in every program we have written, the only parameter args is a 1D array of Strings.

Note the syntax, first the base type (the type of each element of the array), then [], and finally the name of the array.

double[] y;

When the array is not a parameter as above, but is instead a locally declared variable, the syntax is essentially identical and is shown on the right. The only difference being a semicolon added at the end.

6.2.2 Creating Arrays

There is an important difference between declaring scalars as we have done many times previously and declaring an array. When the scalar is declared, it can be used (i.e., given a value). For an array another step is needed.

double[] x;
x = new double[10];

double[] x = new double[10];

The declaration double[] x, declares x but does not reserve space for any of the elements x[i]. This must either be done separately as shown on the top right or written together as shown on bottom right.

The new operator allocates space; in the example on the right space is allocated for 10 doubles. The effect is that the array x can hold 10 elements, each a double.

In Java the first element always has index 0, so x consists of x[0], x[1], ..., x[9].

Comparison with Objects

We do not officially know about objects, but recall that java.util.Scanner is a class and getInput is an element of that class, i.e. an object.

  Scanner getInput = new Scanner(System.in);

  Scanner getInput;
  getInput = new Scanner(System.in);

On the top right is our standard definition of the getinput object. This is actually another Java shortcut for the full two line form shown below. So we see that, as for arrays, arbitrary objects must be both declared and also created (the latter again uses new).

6.2.3 Array Size and Default Values

The size of an array is fixed; it cannot be changed.

To retrieve the size of a 1D array arr is easy, just write arr.length. For example x.length would be 10 for the example above.

Another difference between arrays and scalars is that when the array is created (using the new operator), initial values are given to each element.

6.2.4 Array Indexed Variables

for (int i=0; i<x.length; i++) {
  x[i] = 34;
}

As suggested above, elements of a 1D array are accessed by giving the array name, then [, then a value serving as index, and finally a ]. For example, the code on the right, sets every element of the array x to 34.

Note the use of .length.

6.2.5 Array Initializers

Creating an integer array containing the values 1, 8, 29, and -2 is logically a three step procedure.

int[] A;
A = new int[4];

int[] A = new int[4];

int[] A;
A = new int[4];
A[0]=1; A[1]=8; A[2]=29; A[3]=-2;

int[] A = {1,8,29,-2};
  1. Declare the array.
  2. Create the array with room for 4 elements.
  3. Place the given 4 values into the 4 slots.

We have already seen how the first two steps can be combined into one Java statement. For example see the first two sections of code on the right.

In fact the Java programmer can write one statement that combines all three steps. The third code group on the right, which has each step as one statement, can be written much more succinctly as shown on the fourth group.

Note that the usage of curly brackets as shown is restricted to initialization and a few other situations, you cannot use {1,8,29,-2} in other places.

6.2.6 Processing Arrays

public class Test {
  public static void main(String[] args) {
    int[] a = new int[10];
    int[] b = new int[10];
    int[] c = new int[10];
    for (int i=0; i<a.length; i++) {
      b[i] = 2*i;
      c[i] = i+100;
      a[i] = b[i] + c[i];   // 3i+100
      System.out.printf("a[%d]=%d b[%d]=%2d c[%d]=%d\n",
                        i, a[i], i, b[i], i, c[i]);
    }
  }
}

Very often arrays are used with loops, one iteration of the loop corresponding to one entry in the array.

In particular for loops are often used since they provide the most convenient syntax for stepping through a range of values. The first entry of a Java array is always index 0; hence, the last entry of an array A is A.length-1.

For example the simple code on the right steps through three arrays, setting the value of two of them and calculating from these values the value of the third.

Start Lecture #10

Remark: If you email homeworks, please send plain text (preferred) or pdf. Please do not send doc or docx as there is trouble printing them at nyu (they print fine at my home).

Remark: See right before 5.7 for the output of Pi.

6.2.7 For-each Loops (or Enhanced For Loops)

We have seen several short-cuts provided by Java for common situations. Here is another. If you want to loop through an array accessing, but not changing, the ith element during the ith iteration, you can use the for-each variant of a for loop.

for(int i=0; i<a.length; i++) {    for(double x: a) {
  // use (NOT modify) a[i]           // use (NOT modify) x
}                                  }

Assume that a has been defined as an array of doubles. Then instead of the standard for loop on the near right, we can use the shortened, so-called for-each variant on the far right. The shorten variant executes the body once for each element of the array.

6.3 Problem Lotto Numbers

Read

6.3 (Alternate) The Mean and the Standard Deviation

import java.util.Scanner;
public class StdDev {
  public static void main(String[] args) {
    Scanner getInput = new Scanner(System.in);
    System.out.println("How many numbers do you have?");
    final int n = getInput.nextInt();
    System.out.printf("Enter the %d numbers\n", n);
    double[] x = new double[n];
    double sum=0;
    for (int i=0; i<n; i++) {
      x[i] = getInput.nextDouble();
      sum += x[i];
    }
    double mean = sum / n;
    double diffSquared=0;   // std dev is sqrt(diffSquared/n)
    for (double z : x)
      diffSquared += Math.pow(mean-z,2);
    System.out.printf("Mean is %f; standard deviation is %f\n",
              mean, Math.sqrt(diffSquared/n));
  }
}

Given n values, x0, ..., xn-1, the mean (often called average and normally written as μ) is the sum of the values divided by n.

The variance is the average of the squared deviations from the mean.
That is, [(x0-μ)2 + ... + (xn-1-μ)2]/n

Finally the standard deviation is the square root of the variance.

A Java program to calculate the mean and standard deviation is on the right.

Note that the first loop, which assigns values to the x array, must use the conventional for loop; whereas, the second loop, which only uses the values already in x can employ the for-each variant.

The normal rules apply concerning the need for {}. Since the second loop has a one-line body, the {} can be omitted.

6.4 Problem: Deck of Cards

public class DeckOfCards {
  public static void main(String[] args) {
    int[] deck = new int[52];
    String[] suits = {"Spades", "Hearts", "Clubs",
                      "Diamonds"};
    String[] ranks = {"Ace", "2", "3", "4", "5", "6",
      "7", "8", "9", "10", "Jack", "Queen", "King"};
    // Initialize cards
    for (int i = 0; i < deck.length; i++)
      deck[i] = i;
    // Shuffle the cards
    for (int i = 0; i < deck.length; i++) {
      // Generate an index randomly
      int index = (int)(Math.random() * deck.length);
      // Swap deck[i] and deck[index]
      int temp = deck[i];
      deck[i] = deck[index];
      deck[index] = temp;
    }
    // Display the shuffled deck
    for (int i = 0; i < 4; i++) {
      String suit = suits[deck[i] / 13];
      String rank = ranks[deck[i] % 13];
      System.out.println("Card number " + deck[i]
         + ": " + rank + " of " + suit);
    }
  }
}

Let's look at this example from the book with some care. I downloaded it from the companion web site. You can download all the code examples.

The idea is to have a deck of cards represented by an array of 52 integers, the ith entry of the array represents the ith card in the deck.

How does an integer represent a card?
First of all the integers are themselves chosen to be between 0 and 51. Given an integer C (for card), we divide C by 13 and look at the quotient and remainder. The quotient (from 0..3) represents the suit of the card and the remainder (from 0..12) represents the rank.

Look at the String arrays; they give us a way to print the name of the card given its integer value by using the quotient and remainder.

The deck initialized to be in order starting wit the Ace of Spades and proceeding to the King of Diamonds (Liang must not be an avid card player).

Notice how the deck is shuffled: The card in deck[i] is swapped with one from a random location. This can move a single card multiple times. Pay particular attention to the 3 statement sequence used to swap two values; it is a common idiom.

  for (int C : deck)
    System.out.printf("Card number %2d: %s of %s\n",
                      C, ranks[C%13], suits[C/13]);

Liang printed the first 4 cards; I print the entire deck. The last loop, rewritten as a one-liner, is shown on the right.

6.5 Copying Arrays

array1

Serious business. We can no longer put off learning about reference semantics.

Consider the situation on the near right. We have created two arrays each capable of holding 6 integers. The first has been initialized; the second has not. We now wish to make the second array a copy of the first.

Note that the array name is not the array itself. Instead it points to (technically, refers to) the contents of the array. One consequence is that the size of array1 does not depend on the number of elements in the array, which turns out to be quite helpful later on.

If we simply execute array1 = array2;, we get the situation in the upper right. We have copied the reference (i.e., the pointer) contained in array1 so that this same reference is contained in array2.

As a result both arrays refer to the same content, which is normally not what is desired. In this state, changing the contents of one array, changes the other.

Be sure you see why array2 = array1; gives the depicted situation and be sure you see why now changing either array1[3] or array2[3] changes the other as well.

If the goal is to get the situation shown in the lower right, you need a loop such as
    for (int i = 0; i <array1.length; i++) {
        array2[i] = array1[i];
    }

to copy each entry of array1 to the corresponding entry of array2.

After the loop is executed and we have the picture in the lower right, the arrays are still independent: changing one does not change the other.

6.5.A Why Didn't This Happen Before???

int a, b;
a = 5;
b = a;
b = 10;

Look at the simple code on the right. According to the picture with arrays, after we execute b=a;, both a and b will refer to the same value, namely 5. Then when we execute b=10;, both a and b will refer to the same value, namely 10.

But this does NOT happen. Instead, b==10, but a==5 as we expected.

Why?

val-vs-ref

The technical answer is that primitive types, for example int, have value semantics; whereas, arrays (and objects in general) have reference semantics.

Looking at the diagram on the right, which shows explicitly the memory used for variable array1 and for the array itself, we see that the contents array1 refers to (or points at) the actual array where the values reside; whereas, the int variables a and b ARE the containers where the values 5 and 10 are stored. With primitive types such as int, there are no pointers or references involved.

When you change a or b, you change the value; when you change array1, you change the reference (pointer).

6.6 Passing Arrays to Methods

Liang first talks about passing the entire array and then about passing individual members. I will reverse the order since I believe passing individual members is easier.

6.6.1 Passing Array Arguments

There is very little to say about passing an element of a one dimensional array as an argument. The element is simply a value of the base type of the array.

public class DemoArrayArgs {
  public static void printSum(int x, int y) {
    System.out.println(x+y);
  }
  public static void main(String[] args) {
    int[] x = {5, 10, -1, 25};
    printSum(x[2],x[1]);
  }
}

On the right is the simple printSum() method that we saw earlier. It accepts two integer parameters and prints their sum.

The main() method defines and initializes an array x containing 4 ints, and then invokes printSum(x[2],x[1]).

The printSum() method will add the two values and print the sum just as if it was invoked via printSum(-1,10);

6.6.A Passing an Entire Array as an Argument

public static void printArray(int[] a) {
  for (int i : a) {
    System.out.println(i);
  }
}

The printArray() method on the right has an (entire) int array as parameter. To call the method is easy: printArray(b) where b is a single-dimensional array of integers. The argument b of the caller is assigned to the parameter a of printarray(). Note that the types must match: the argument in the caller must be of type int[], i.e., an array of ints. As written printArray() can accept any size 1D array.

In this example, the called method (printArray() does not alter its parameter. The only subtlety in passing an entire array is analyzing the behavior when the called method does change a component of the parameter, which we study next.

Updating a Component of an Array Parameter

public class DemoArrayArgUpdate {
  public static void BumpZerothEntry(int[] a) {
    a[0]++;
  }
  public static void main(String[] args) {
    int[] b = {5, 10, -1, 25};
    BumpZerothEntry(b);
  }
}

The tricky part of this situation is melding Java's pass-by-value method invocation with its reference semantics for arrays.

Assume we have a method, such as BumpZerothEntry() that accepts an array parameter a and updates at least one component. Assume, as shown on the right, the main() method invokes BumpZerothEntry() with argument b, an integer parameter.

At the point of the method call, the value in b is copied to a, but, as we know, when the method returns, any current value in a is NOT copied back to b. This rule still holds for arrays; NO change.

array-param

The diagram at the right show the configuration when the method is called.

Due to pass-by-value semantics, even if the called method changes the value in the parameter a, the value in the argument b would not change; it would remain a pointer to the 4-element structure shown.

However, that is not what happens here. The called method does not change the value in the parameter a, the parameter remains a pointer to the 4-element structure. Naturally, the argument a also does not change.

Now consider what does happen. The called method executes a[0]+=1;, which changes not the value in a (a pointer), but instead changes the value in a cell pointed to by a. Since the same cells are pointed to by both b and a, a cell point to by the argument b has changed.

Hence when the method returns, the value in b[0] is now 6.

Summary, the call above cannot change the value b, but it can change the value of an individual b[i].

Homework: 6.1. Write a program that reads student scores, calculates the best score, and then assigns grades based on the following scheme.

The program prompts the user to enter the total number of students, then prompts the user to enter all of the scores, and concludes by displaying the grades.

Notes:

Updating a Component of an Array Parameter: Example

public class ArrayVsScalarParam {
  public static void main(String[] args) {
    int   xScalar =  5 ;
    int[] yArray  = {5};
    int[] zArray  = {5};
    System.out.printf("xScalar=%d   yArray[0]=%d   zArray[0]=%d\n",
                      xScalar,      yArray[0],     zArray[0]);
    tryToChange(xScalar, yArray[0], zArray);
    System.out.printf("xScalar=%d   yArray[0]=%d   zArray[0]=%d\n",
                      xScalar,      yArray[0],     zArray[0]);
  }

  public static void tryToChange(int scalar, int component, int[] array) {
    scalar    = 10;              // will NOT change argument
    component = 10;              // will NOT change argument
    array[0]  = 10;              // WILL     change component of argument
  }
}

xScalar=5   yArray[0]=5   zArray[0]=5
xScalar=5   yArray[0]=5   zArray[0]=10

The code on the right shows 3 attempts to change a 5 to a 10. Two fail; the third is successful.

  1. The first attempt passes a scalar to a method, which updates the corresponding parameter. Hopeless. Java is a pass-by-value language.
  2. The second attempt passes an array component to the method, which again updates the corresponding parameter. Hopeless. Java is a pass-by-value language.
  3. The third attempt passes an array itself to the method, which does not, repeat NOT attempt to modify the corresponding parameter (that would be hopeless: Java is a pass-by-value language). Instead the method modifies a component of the passed array.

Anonymous Arrays

Java also has anonymous arrays, i.e., arrays without a name. So you can write printArray(new int[]{1,5,9});

6.7 Returning an Array from a Method

public static int[] reverse(int[] a) {
  int[] b = new int[a.length];
  for (int i=0; i<a.length; i++) {
    b[a.length-1-i] = a[i];
  }
}

We have seen how arrays can be passed into a method. They can also be the result of a method as shown on the right. Note the following points about reverse (which returns an array that has the same elements as its input but in reverse order).

6.7.1 Case Study: Counting the Occurrences of Each Letter

Read

Homework: 6.3 Write a program that reads integers between 1 and 100 and counts the occurrences of each. Assume the input ends with 0.

6.7.1 (Alternate) Case Study: Histogram of Math.random()

import java.util.Scanner;
public class Histogram {
  public static void main(String[] args) {
    System.out.printf("How many random numbers? ");
    Scanner getInput = new Scanner(System.in);
    int numRandom = getInput.nextInt();
    int[] count = {0,0,0,0,0,0,0,0,0,0};
    double[] limit = {0.0,0.1,0.2,0.3,0.4,0.5,0.6,
                      0.7,0.8,0.9,1.0};
    for(int i=0; i<numRandom; i++) {
      double y = Math.random();
      for (int j=0; j<10; j++) {
        if (y < limit[j+1]) {  // one will succeed
          count[j]++;
          break;
        }
      }
    }
    for (int j=0; j<10; j++) {
      System.out.printf("The number of random numbers "
            + "between %3.1f and %3.1f is %d\n",
            limit[j], limit[j+1], count[j]);
} } }

If Math.random() is any good, about 1/10 of the values should be less than 0.1, about 1/10 between 0.1 and 0.2, etc.

The program on the right tests that out by taking a bunch of numbers returned by Math.random() counting how many fall in each 0.1 range.

Note the following points.

The program can be downloaded from here.

Lets compile and run it for different number of random numbers and see what happens.

6.8 Variable-Length Argument Lists

public class VarArgsDemo {
  public static void main(String args[]) {
    printMax(34, 3, 3, 2, 56.5);
    printMax(new double[]{1, 2, 3});
  }
  public static void printMax(double... numbers) {
    if (numbers.length == 0) {
      System.out.println("No argument passed");
      return;
    }
    double result = numbers[0];
    for (int i = 1; i < numbers.length; i++)
      if (numbers[i] > result)
        result = numbers[i];
    System.out.println("The max value is " + result);
  }
}

We have used System.out.printf() which has a variable number of arguments.

Java permits the last parameter to correspond to a varying number of arguments, but all of these arguments must be of the same type. Java treats the parameter as an array of that type.

The example on the right is from the book.

Pay attention to the parameter of printMax. Java uses ellipses to indicate varargs. In this case a variable number of double arguments are permitted and will all be assigned to the array double... numbers.

Other non-vararg parameters could have preceded the double... numbers parameter.

  public static int getRandom(int... numbers)

Homework: 6.13. Write a method that returns a random number between 1 and 54, excluding the numbers passed in the argument. The method header is shown on the right.

Start Lecture #11

Remark: See new piece added just before 6.7.

Remark: Lab 2 assigned.

6.9 Searching Arrays

public static int search(int[] A, int val)

Given an array A and a value val, find an index i such that A[i]==val.

What if there is more than one i that works?
Normally, we just report one of them.

What if none work?
We must indicate this in some way, often we return -1, which cannot be an index (all Java arrays have index starting at 0).

What if we want to search integers and also search reals?
We define two overloaded searches (or we learn about generics).

6.9.1 The Linear Search Approach

public static int linearSearch(int[] A, int val) {
  final int NOT_FOUND = -1;
  for (int i=0; i<A.length; i++)
    if (A[i] == val)
      return i;
  return NOT_FOUND;
}

There is an obvious solution: Try A[0], if that fails try A[1], etc. If they all fail return -1. This is shown on the right.

The only problem with this approach is that it is slow if the array is large. If the item is found, it will require on average about n/2 loop iterations for an array of size n and if the item is not found, n iterations are required.

  public static int[] eliminateDuplicates(int[] numbers)

Homework: 6.15. Write a method eliminateDuplicates() that removes the duplicate values in array. Use the following header line on the right. Write a test program that reads in ten integers, invokes the method, and displays the result.

A good solution would work for any size array and create the resulting array of just the right size.

6.9.2 The Binary Search Approach

mid = (hi + lo) / 2; // 1.
if (A[mid]==val)
  return mid;  // 2.
else if (A[mid]<val)
  lo = mid + 1;    // 3.
else // A[mid]>val
  hi= mid-1;    // 4.

public static int binarySearch(int[]A, int val) {
  final int NOT_FOUND = -1;
  int lo = 0;
  int hi = A.length -1;
  int mid = (hi+lo)/2;
  while (lo <= hi) { // 5
    // above if-then-else
  }
  return NOT_FOUND; // 6
}

If the array is unsorted, there is nothing better than the above. But of course if we are doing many searches it pays to have the array sorted.

Given a sorted array, we can preform a divide and conquer attack. Assume the array is sorted in increasing order (smallest number first).

  1. Try the middle element.
  2. If it is the desired value, done.
  3. If the middle is smaller than desired, restrict to the bottom half.
  4. If the middle is larger than desired, restrict to the top half.
  5. Repeat the above.
  6. If you restrict to the empty set, the desired element is not there.

Although the routine is easy conceptually it is notoriously easy to get wrong, (e.g. lo=mid).

6.10 Sorting Arrays

Sorting is crucial. As we just saw, fast searching depends on sorting.

There are many sorting algorithms; we will learn only two, both are bad. That is, both take time proportional to N2, where N is the number of values to be sorted. Good algorithms take time proportional to N*logN. This doesn't matter when N is a few hundred, but is critically important when N is a few million.

Moreover, serious sorting does not assume that all the values can be in memory at once.

We treat sorting much more extensively in Data Structures (102).

Having said all this, sorting is important enough that it is quite worth our while to learn some algorithms. Naturally, it also helps us learn to translate algorithms into Java.

6.10.1 Selection Sort

Read.

6.10.A Selection Sort (Alternate Version—Bubble Sort)

The basic idea in both the 8e and bubble sort is that you start by ensuring that the smallest element is in the first slot (a[0]) and then repeat for the rest of the array (a[1]...a[a.length-1]).

for (j=1; j<a.length; j++)
  if (a[j] < a[0]) { // a[0] not min
    temp = a[0]; // so swap with a[j]
    a[0] = a[j]; // swapping takes
    a[j] = temp; // 3 instructions
  }

The difference between the selection sort in 8e and the bubble we will do is how each gets the minimum element into A[0]. The code on the right shows the simple method used by bubble sort. The code in the book is slightly more complicated and perhaps slightly faster (both give bad, i.e., N2, sorts).

What remains is to wrap the code with another loop to find the 2nd smallest element and put it in A[1], etc.

Let's do that in class.

6.10.2 Insertion Sort

for (i=1; i<A.length; i++)
  // Insert A[i] correctly into A[0]..A[i-1]

Insertion sort is well described by the code on the right. A key point to note is that at each iteration A[0]...A[i-i] is already sorted.

The remaining question is how do we insert A[i] into A[0]..A[i-1] maintaining the sorted order?

In looking for the correct place to put A[i], we could either start with A[0] and proceed down the array or start with A[i-1] and proceed up the array. Why do we do the latter?

Consider an example where i is 20 and the right place to put A[i] is in slot A[10]. Where are we going to put A[10]?
Answer: It must go in A[11]. Then where does A[11] go?
Answer: in A[12].

public static void insSort(int[] a) {
  for (int i=1; i<a.length; i++) {
    int ai = a[i];
    int j = i-1;
    while (j>=0 && ai<a[j]) {
      a[j+1] = a[j];
      j--;
    }
    a[j+1] = ai;
  }
}

Thus we need to move the higher elements before we can move the lower. We can easily do this while searching for the correct spot to put A[i], providing we are traveling from higher to lower indices.

The code on the right deserves a few comments.

Homework: Write a method that accepts an array of integers and prints out the elements in sorted order with a count of how many times it appeared. For example if the array contains. 1,5,2,3,8,2,1,5,5 you would print
1 appears 2 times
2 appears 2 times
3 appears 1 time
5 appears 3 times
8 appears 1 time

6.11 The Arrays Class

The book notes that Java has many predefined methods for arrays. In particular java.util.Arrays.sort(a) would have sorted the array a (for any primitive type, thanks to overloading).

The best place to see all the methods (and classes, and more) that are guaranteed to be in all Java implementations is http://download.oracle.com/javase/7/docs/api/ (I use http://java.sun.com/javase/7/docs/api/).

Remark: Midterm exam Thursday 8 March, 2012. Tuesday 6 March is also possible, if preferred. Chapters 1-7. I have a practice exam on the web page already. This practice exam references a practice exam from 202; the latter shows the format that your real exam will have.

Chapter 7 Multidimensional Arrays

7.1 Introduction

As we have seen, a one-dimensional arrays corresponds to a list of values of some type. Similarly, two-dimensional arrays correspond to tables of such values and arrays of higher dimension correspond to analogous higher dimensional tables.

7.2 Two-Dimensional Array Basics

7.2.1 Declaring Variables of Two-Dimensional Arrays and Creating Two-Dimensional Arrays

double [][] M; // M for Matrix
M = new double [2][3];
M[0][0]=4.1; M[0][1]=3; M[0][2]=4;
M[1][0]=6.1; M[1][1]=8; M[1][2]=3;

double [][] M = new double [2][3];

double [][] M = {
  {4.1, 3, 4},
  {6.1, 8, 3}
};

double [][] M = {{4.1, 3, 4}, {6.1, 8, 3}};

As with one-dimensional arrays, there are three steps in declaring and creating arrays of higher dimension.

  1. Declaring the array.
  2. Creating the array.
  3. Initializing the array (optional).

These can be done separately or can be combined.

On the right we see three possibilities, the analogue of what we did last chapter for one-dimensional (1D) arrays. I wrote the third possibility twice using different spacings. Arrays like this one are often called rectangular. They correspond to mathematical matrices.

In fact there are other possible ways to do the initialization since a 2D array in Java is really a 1D array of 1D arrays as we shall see in the very next section.

7.2.2 Obtaining the Lengths of Two-Dimensional Arrays

2d-matrix

The simple diagram on the right is very important. It illustrates what is meant by the statement that Java 2D arrays are really 1D arrays of 1D arrays. That is, Java 1D arrays can have elements that are themselves 1D arrays.

The M in the diagram is the same as in the example code above.

Note first of all, that we again have reference semantics for the arrays. Specifically, M refers to (points to) the 1D array of length two in the middle of the diagram. Similarly M[0] refers to the top right 1D array of size 3. However, M[0][0] is a primitive type and thus it IS (as opposed to refers to) a double.

Next note that there are three 1D arrays in the diagram (M, M[0], and M[1]) of lengths, 2, 3, and 3 respectively. The difference between M and the others is that each element of M is of type double[]; whereas each element of M[0] and M[1] is of type double.

Finally, note that you cannot write M[1,0] for M[1][0], further suggesting that Java does not have 2D arrays as a native type.

7.2.3 Ragged Arrays

ragged

One advantage of having 1D arrays of 1D arrays rather than true 2D arrays is that the inner 1D arrays can be of different lengths. For example, on the right, M is an array of length 2, M[0] is an array of length 3, and M[1] is an array of length 1.

Text that does not have its right margin aligned is said to have ragged right alignment. Since the right hand boundary of the component 1D arrays look somewhat like ragged right text, arrays such as M are called ragged arrays.

double [][] M = { {3.1,4,6},  {5.5} };

double [][] M;
M = new double [2][]; // the 2 is needed
M[0] = new double [3];
M[1] = new double [1];
M[0][0]=3.1; M[0][1]=4; M[0][2]=6;
M[1][0]=5.5;

There are several ways to declare, create, and initialize a ragged array like M. The simplest is all at once as on the upper right.

The most explicit is the bottom code. Note how clear it is that M has length 2, M[0] has length 3, and M[1] has length 1.

Intermediates are possible. For example the first two lines can be combined. Another possibility is to combine the initialization of M[0] with its creation.

7.3 Processing Two-Dimensional Arrays

for (int i=0; i<n; i++) {  | for (int i=0; i<M.length; i++) {
  for(int j=0; j<m; j++) { |   for (int j=0; j<M[i].length; j++) {
    // process M[i][j]     |     // process M[i][j]
  }                        |     }
}                          |   }

On the far right is the generic code often used when processing a single 2D array (I know 2D arrays are really 1D arrays of 1D arrays). This code works fine for ragged and non-ragged (often called rectangular) arrays. Note that the bound in the inner loop depends on i.

When the array is known to be rectangular (the most common case) with dimensions n and m, the simpler looking code on the near right is used.

Start Lecture #12

7.3.A Some Examples

The Types of a 2D Matrix and its Components

  public static void main(String[] args) {
    double[][] m = { {1,2,3}, {4,5,6} };
    method1(m);
    method2(m[1]);
    method3(m[1][1]);
  {
  public static void method1(????? x) {}
  public static void method2(????? x) {}
  public static void method3(????? x) {}

The code fragment on the right shows the declaration of a 2D array m and its use in three method invocations.

In the first invocation the entire array is passed; in the second we instantiate one of the dimensions to 1; in the third we instantiate both dimensions to 1.

What should the ????? be in the three method definitions that follow?

Declaring, Allocating, and Reading an n×m Rectangular Array

  int n = getInput.nextInt();
  int m = getInput.nextInt();
  float[][] arr = new float[n][m];
  for (int i = 0; i<n; i++)
    for (int j = 0; j<m; j++)
      arr[i][j] = getInput.nextFloat();

The code to define and read a 2D rectangular array is not hard and is shown on the right. Recall that I normally use the name getInput for my java.util.Scanner object.

Note that, although you can declare the array before knowing the bounds, you need the bounds before doing the allocation (invoking new).

Max, Min, and sum of a Rectangular n by m Array

  double[][] Matrix;
  // compute and/or read Matrix
  double maximum = Matrix[0][0];
  double minimum = Matrix[0][0];
  double sum = 0;
  for (int i=0; i<n; i++)
    for (int j=0; j<m; j++) {
      if (Matrix[i][j] > maximum)
        maximum = Matrix[i][j];
      if (Matrix[i][j] < minimum)
        minimum = Matrix[i][j];
      sum += Matrix[i][j];
    }

These are quite easy since the computation for each element of the matrix is independent of all other elements. The idea is simple, use 2 nested loops to index through all the all the elements. The loop body contains the computation for the generic element.

The code fragment on the right shows three parts of the program.

  1. First Matrix is declared to be a 2D array of doubles.
  2. The allocation and initializing/reading of the matrix is not shown. The previous section shows how this can be done.
  3. Next we show the declaration and initialization of the desired results.
  4. Finally, the computation loop is given.

For a possible ragged array of unknown, simply replace n by Matrix.length and replace m by Matrix[i].length.

Initializing a (Possibly Ragged) Array with Random Integers from 1 to N

for (int i=0; i<A.length; i++)
  for (int j=0; j<A[0].length; j++)
    A[i][j] = 1 + (int)((N)*Math.random());

We use the nested loop construction appropriate for ragged arrays. The body requires remembering how to scale Math.random() to produce numbers from 1 to N.

The reason for (int) is in case the matrix A is floating point (but we want integer values).

Declaring, Allocating, and Reading a Ragged Array

  5
  4  11. 12. 13. 14.
  2  21. 22.
  1  31.
  7  41. 42. 43. 44. 45. 46. 47.
  7  51. 52. 53. 55. 55. 56. 57.
  
  11. 12. 13. 14. 55.
  21. 22.
  31.
  41. 42. 43. 44. 45. 46. 47.
  51. 52. 53. 55. 55. 56. 57.

Suppose we want to read in the ragged array of floats shown on the near right. It is drawn to indicate that it has 5 rows, the first with 5 columns, the second with 2, the third with 1, the fourth and fifth with 7.

Somehow, we must convey this size/shape information to the program. Often this is done with counts as shown above on the far right.

import java.util.Scanner;
public class ReadingRaggedArrays {
  public static void main(String[] args) {
    Scanner getInput;
    getInput = new Scanner(System.in);
    int n = getInput.nextInt(); // n rows (const)
    int m;                      // m cols (varies)
    float[][] arr = new float[n][];
    for (int i = 0; i < n; i++) {
      m = getInput.nextInt();   // num cols this row
      arr[i] = new float[m];
      for (int j = 0; j < m; j++)
        arr[i][j] = getInput.nextFloat();
    }
  }
}

The Java program on the right reads data in the format shown above and allocates all the relevant arrays (remember that a 2D array with 5 rows is really six 1D arrays).

After the first five lines, which we have seen many times before, we read n, the number of rows and allocate arr, a 1D array each of whose n components is a 1D array of floats.

Then, for each of the n rows we read m,the number of columns and allocate a 1D array of floats, to hold the elements of the row

Finally, we are able to actually read and store the actual elements of the array.

7.4 Passing Two-Dimensional Arrays to Methods

Read

7.4.A Matrix Multiply

public static void matrixMult (double [][] A,
                double [][] B, double [][] C) {
  // check that the dimensions are legal
  for (int i=0; i<A.length; i++)
    for (int j=0; j<A[0].length; j++) {
      A[i][j] = 0;
      for (int k=0; k<C.length; k++)
        A[i][j] += B[i][k]*C[k][j];
    }
}

Remark: Write the formula on the board. I do not assume you have memorized the formula. If it is needed on an exam, it will be supplied.

For multiplication the matrices must be rectangular. Indeed, if we are calculating A=B×C, then B must be an n×p matrix and C must be a p×m matrix (note the two ps). Finally, the result A must be an n×m matrix.

The code on the right does the multiplication, but it omits the dimensionality checking. Note that this checking would involve loops (to ensure that the arrays are not ragged).

Since the first parameter is an array, the values stored in the array will be visible in the corresponding argument of the calling method.

An alternative implementation would be to have the caller supply the array bounds and trust that the arrays are of the size specified. Then the method definition would begin

public static void matrixMult (int n, int m, int p, double [][] A, double [][] B, double [][] C) {
  // A is an n by m matrix, B is n by p matrix, C is a p by m matrix

A full program for rectangular matrices is here.

Homework: 7.5.

7.5: Problem: Grading a Multiple-Choice Test

Read

7.6: Problem: Finding a Closest Pair

Read

7.6 (Alternate): Problem Choosing an Intermediate Point

import java.util.Scanner;
public class Intermediate {
  public static void main (String[] args) {
    final double [][] points = {  {0,0}, {3,4}, {1,9}, {-10,-10} };
    System.out.println("Input 4 doubles for start and end points");
    Scanner getInput = new Scanner(System.in);
    double [] start = {getInput.nextDouble(),getInput.nextDouble()};
    double [] end = {getInput.nextDouble(), getInput.nextDouble()};

    int minIndex = 0;
    double minDist = dist(start, points[0]) + dist(points[0], end);
    for (int i=1; i<points.length; i++)
      if (dist(start,points[i])+dist(points[i],end) < minDist) {
        minIndex = i;
        minDist = dist(start,points[i])+dist(points[i],end);
      }
    System.out.printf("Intermediate point number %d (%f,%f) %s%f\n",
              minIndex, points[minIndex][0], points[minIndex][1],
              " is the best.  The distance is ", minDist);
  }
  public static double dist(double [] p, double [] q) {
    return Math.sqrt((p[0]-q[0])*(p[0]-q[0])+
                     (p[1]-q[1])*(p[1]-q[1]));
  }
}

Assume you are given a set of (x,y) pairs that represent points on a map. For this problem we join the Flat Earth Society and assume the world is flat like the map.

Read in two (x,y) pairs S and E representing your starting and ending position and find which point P of the given points minimizes the total trip consisting of traveling from S to P and from P to E.

Note the following aspects of the solution on the right.

7.7: Problem: Sudoku

Not as interesting as the title suggests. The program in this section just checks if an alleged solution is correct.

7.8: Multidimensional (i.e., Higher Dimensional) Arrays

double x[][][] = {
               { {1,2,3,4}, {1,2,0,-1}, {0,0,-8,0} },
               { {1,2,3,4}, {1,2,0,-1}, {0,0,-8,0} }
             ];

There is nothing special about the two in two-dimensional arrays. We can have 3D arrays, 4D arrays, etc.

On the right we see a 3D array x[2][3][4]. Note that both x[0] and x[1] are themselves 2D arrays.

7.8.1: Problem: Daily Temperature and Humidity

double[][][] data =
    new double[numDays][numHours][2];

double[][] temperature =
    new double[numDays][numHours];
double[][] humidity =
    new double[numDays][numHours];

The three dimensional array data on the upper right holds the temperature and humidity readings for each of 24 hours during a 10 day period.
data[d][h][0] gives the temperature on day d at hour h.
data[d][h][1] gives the humidity on day d at hour h.

However, I do not like this data structure since the temperature and humidity are really separate quantities so I believe the second data structure is better (the naming would be better).

double[][][] temperature =
    new double[numLatitudes][numLongitudes][numAltitudes];

On the right we see a legitimate 3D array that was used in a NASA computer program for weather prediction. The temperature is measured at each of 20 altitudes sitting over each latitude and longitude.

7.8.2: Problem: Guessing Birthdays

A cute guessing game. The Java is not especially interesting, but you might want to figure out how the game works.

Homework: 7.7

Remark: End of material on midterm.

Remark: Lab 2 due date extended to 20 March.

Chapter 8: Objects and Classes

8.1: Introduction

Object-oriented programming (OOP) has become an important methodology for developing large programs. It helps to support encapsulation so that different programming teams can work on different aspects of the same large programming project.

Java has good support for OOP.

In Java, OOP is how gui programs are usually developed.

8.2: Defining Classes for Objects

An object is an entity containing both data (called fields in Java) and actions (called methods in Java). Two examples would be a circle in the plane and a stack.

For a circle, the data could be the radius and the (x,y) coordinates of the center. Actions might include, moving the center, changing the radius, computing the area, or (in a graphical setting) changing the color in which the circle is drawn.

For a stack, the data could include the contents and the current top of stack pointer. Actions might include pushing an element on to the top of the stack and popping the top element off.

In Java, objects of the same type, e.g., many circles, are grouped together in a class.

Start Lecture #13

Remark: Homework solutions for chapters 4-6 are posted. I will post chapter 7 after class. Remind me via the mailing list tonight if I forget.

Remark: I posted practice midterm solutions.

Public class LongStack {
  int top = 0;
  long[] theStack;
  void push(long elt) {
    theStack[top++] = elt;
  }
  long pop() {
    return theStack[--top];
  }
}

On the right is the beginnings of a class for stacks containing Java long's. The fields in the class are top and theStack; the methods are push() and pop().

Unlike local variables, which we have seen many times before, a field is not local to (i.e., inside of) a method.

Three obvious problems with this class are.

  1. The array theStack is declared but is not created (there is no new operation).
  2. It is an error to pop an empty stack (we would reference theStack[-1], but this condition is not checked for.
  3. It is an error to push a full stack (we would reference theStack[theStack.length]), but this condition is not checked for.

Given a class, a new object is created by instantiating the class. Indeed, an object is often called an instance of its class.

Many classes supply one or more special method called constructors that Java invokes automatically whenever an object is instantiated. Although a constructor can in principle perform any action, normally its task is to allocate and initialize the fields. For example, a longStack constructor would create the array theStack.

8.3: Example: Defining Classes and Creating Objects

public class TestCircle1 {
  /* Main method */
  public static void main(String[] args) {
    // Create a circle with radius 5.0
    Circle1 myCircle = new Circle1(5.0);
    System.out.println("The area of the circle of radius "
      + myCircle.radius + " is " + myCircle.getArea());

    // Create a circle with radius 1
    Circle1 yourCircle = new Circle1();
    System.out.println("The area of the circle of radius "
      + yourCircle.radius + " is " + yourCircle.getArea());

    // Modify circle radius
    yourCircle.radius = 100;
    System.out.println("The area of the circle of radius "
      + yourCircle.radius + " is " + yourCircle.getArea());
  }
}

// Define the circle class with two constructors
class Circle1 {
  double radius;

  /** Construct a circle with radius 1 */
  Circle1() {
    radius = 1.0;
  }

  /** Construct a circle with a specified radius */
  Circle1(double newRadius) {
    radius = newRadius;
  }

  /** Return the area of this circle */
  double getArea() {
    return radius * radius * Math.PI;
  }
}

On the right is an example from the book. Note several points.

  1. There are two classes here. The top one is like many others we have seen in that it contains just the main() method. Since this class is public the file is named TestCircle1.java.
  2. The main method instantiates the second class Circle1 twice, thereby creating two Circle1 objects.
  3. In the first instantiation the radius is given; the second time it is not. Different constructors (named Circle1()) are called for the two cases (see below).
  4. The resulting objects myCircle and yourCircle can now be used in the main() method. For example both have their area computed and one has its radius changed.
  5. The Circle1 class is not public. It can only be used in this file (really this package). A single file can have only one (non-nested) public class; that class determines the name of the file.
  6. The class Circle1 has one field and three methods.
  7. The two methods, also named Circle, are constructors due to their name being the same as the name of the class.
  8. Since the constructors are overloaded, the signature is used to tell them apart. Both constructors serve the same purpose, they initialize the radius field.
  9. The third method getArea looks simple, we know the area of a circle is πr2, but ...

Class vs. Instance Fields and Methods

A method can be declared static. The main() method in the class TestCircle is an example. Such a method is called a class method or static method and is associated with the class as a whole. In contrast a non-static method, called an instance method, is associated with an individual object. The Circle1 class has one instance method, no class methods, and two constructors.

Thus when the two Circle1 objects are created, each gets its own getArea() method.

In a like manner a field can be declared to be static (none are in this example) in which case it is called a class field (or sometimes a static field) and there is only one that is shared by all objects instantiated from the class.

A field not declared to be static is called an instance field and each object gets its own copy; changing one, does not affect the others. An object instantiated from a class is often called an instance of the class.

Putting this together we see that when the main() class invokes getArea() it must specify which getArea() is desired, i.e., which object's getArea() should be invoked. So myCircle.getArea() invokes the getArea() associated with myCircle and hence when it mentions radius (an instance field) it gets the radius associated with myCircle.

8.3.A: A Two-File Program

public class TV {
  int channel = 1; // Default channel is 1
  int volumeLevel = 1; // Default volume level is 1
  boolean on = false; // By default TV is off
  public TV() {}
  public void turnOn() {
    on = true;
  }
  public void turnOff() {
    on = false;
  }
  public void setChannel(int newChannel) {
    if (on && newChannel >= 1 && newChannel <= 120)
      channel = newChannel;
  }
  public void setVolume(int newVolumeLevel) {
    if (on&&newVolumeLevel>=1 && newVolumeLevel<=7)
      volumeLevel = newVolumeLevel;
  }
  public void channelUp() {
    if (on && channel < 120)
      channel++;
  }
  public void channelDown() {
    if (on && channel > 1)
      channel--;
  }
  public void volumeUp() {
    if (on && volumeLevel < 7)
      volumeLevel++;
  }
  public void volumeDown() {
    if (on && volumeLevel > 1)
      volumeLevel--;
  }
}
public class TestTV {
  public static void main(String[] args) {
    TV tv1 = new TV();
    tv1.turnOn();
    tv1.setChannel(30);
    tv1.setVolume(3);

    TV tv2 = new TV();
    tv2.turnOn();
    tv2.channelUp();
    tv2.channelUp();
    tv2.volumeUp();

    System.out.println("tv1's channel is "
      +tv1.channel+" and volume level is "
      +tv1.volumeLevel);
    System.out.println("tv2's channel is "
      +tv2.channel+" and volume level is "
      +tv2.volumeLevel);
  }
}

Above and to the right we see our first program written in two files. It is from the book. Each file has one public class that determines the name of the file.

Again note that the instance methods are referred to as objectName.methodName.

To compile and run this program you would type.

  javac TestTV.java TV.java
  java TestTV

The last line could not have been java TV since you must invoke the file that has the main() method.

8.4: Constructing Objects Using Constructors

We have seen constructors above. Note the following points.

The purpose of many constructors is to (allocate and) initialize the object being created via new.

Normally, a class provides a constructor with no parameters (as well as possibly other constructors with one or more parameters). This constructor is often called the no argument or no-arg constructor.

If the class contains NO constructors at all, the system provides a no-arg constructor with empty body. Thus the no-arg constructor in class TV could have been omitted.

8.4A LongStack Revisited

class LongStack {
  int top = 0;
  long[] theStack;
  LongStack() {
    theStack = new long[100];
  }
  LongStack(int n) {
    theStack = new long[n];
  }
  void push(long elt) {
    if (top>=theStack.length)
      System.out.println("No room to push!");
    else
      theStack[top++] = elt;
  }
  long pop() {
    if (top<=0) {
      System.out.println("Nothing to pop!");
      return -99;  // awful!!
    }
    return theStack[--top];
  }
}
public class DemoLongStack {
  public static void main (String[] args) {
    long x = 333, y = 444;
    LongStack myLStk = new LongStack(4);
    myLStk.push(x);
    myLStk.push(y);
    System.out.printf("Before: x=%d and y=%d\n", x, y);
    x = myLStk.pop();
    y = myLStk.pop();
    System.out.printf("After:  x=%d and y=%d\n", x, y);
  }
}

Before: x=333 and y=444
After:  x=444 and y=333

Now that we know a little more about constructors, we can add them to class LongStack, and, while we are at it, put in some error checks. The revised code is on the right.

If we knew about Java exceptions, it would be good to utilize them for these errors. In particular, returning a -99 is awful.

The default stack, i.e., the one created with the no-arg constructor, has room for 100 elements. The user can, however, specify the size when invoking new and then the other constructor will be called.

Note that LongStack is no longer a public class. With no public (or private or protected) declaration, the default is that LongStack is visible within the current package.

Packages are important for large programs and will be discussed a little in section 8.8. For now you can think of LongStack as a public name but, since the file has only one real public class (DemoLongStack, it must be named DemoLongStack.java.

The main() method declares a longStack and uses it a little. Below the line, we see the output produced, which indeed exhibits stack-like (FIFO) semantics.

Note especially the command myLStk.push(x), which we now discuss.

When compared to most programming languages something looks amiss with this statement. The purpose of the command is to push x on to the stack myLStk and hence should be written something like push(x,myLStk). Similarly, the purpose of x=myLStk.pop() is to pop the stack and place the value into x. So it should be something like x=pop(myLStk).

However, in Java, and other object-oriented languages, it is written as shown in DemoLongStack. The idea is that the LongStack object myLStk contains not only the data (in this case top and theStack) but the methods push() and pop() as well.

We have previously seen this dotted notation (object dot methodName). For example, getInput.nextInt().

If push() were a class method instead of an instance method, then an invocation would include the stack as a parameter, perhaps LongStack.push(x,myLStk).

Start Lecture #14

Midterm exam

Start Lecture #15

Remarks: Median grade on the midterm exam was 86.
Lab 3 is available. We have a second grader Taolun Chai <tc1112@nyu.edu>. If your last name starts with M-Z, please submit your future labs to Taolun.

8.5 Accessing Objects via Reference Variables

Recall the serious business we mentioned in section 6.5 when discussing copying arrays. Liang finally comes to grips with it here.

8.5.1 Reference Variables and Reference Types

As with arrays, variables for objects contain references to the objects not the objects themselves.

Naturally, if a class ClassBig has many large fields, objects of the class are large. Similarly, if ClassSmall has just one int as a field, objects of this class are small. However, an important technical point to note is to consider

  ClassBig big = new ClassBig();   ClassSmall small = new ClassSmall();

Then big and small are the same size. Neither contains an object; instead each contains of a reference to an object (i.e., a pointer, which is essentially an address). You don't need a bigger address label to send a letter to a mansion than to a studio apartment.

ref-types

The diagram on the right illustrates the situation right after main() executes

  LongStack myLStk = new LongStack(4);

We see that myLStk refers to (points at) the new object.

We also see that the object has three pieces.

  1. There is the int top, which is a primitive type so actually contains the value.
  2. There is the one dimensional array theStack which is a reference to a bunch of longs, currently it references 4 longs.
  3. There is the block of 4 long themselves. Since long is a primitive type these four cells will contain values. If it were instead an array of 4 other objects or 4 other arrays, the cells would be references.

The above is very important. It is a key tenant of Java that primitive types have value semantics and that both arrays and objects have reference semantics.

We will learn later that, for each primitive type, there is a corresponding class with a similar name (but naturally with reference, not value, semantics). Specifically, the names are Byte, Short, Integer, Long, Boolean, Character, Float, Double.

8.5.2 Accessing an Object's Data and Methods

After creation, methods and data fields within objects are referenced using dot notation as mentioned above and illustrated by getInput.nextInt(), myLStk.pop(), and myLStk.top.

Note that the fields and methods just mentioned do not have the static modifier. We shall see static fields later and have seen many static methods already. Fields and methods that are not static are called instance fields (or instance variables) and instance methods.

If in the DemoLongStack example above, the main() method created two LongStack's, each one would have its own top, theStack, push(), and pop().

8.5.3 Reference Data Fields and the null Value

As I mentioned several lectures ago, Java assigns default values to many variables, but I do not use or recommend learning the default values. I always (try to remember to) explicitly initialize the variables at their declaration or ensure that they are assigned a value before there value is requested.

As mentioned previously Java does not always assign a default value. Since I suggest not making use of default values, I don't see a need to remember which variables get defaults (and what the defaults are) and which variables do not.

Nonetheless there is one default you will need to recognize as it will indicate a fairly common error. If you declare an object or array but do not create it (with new), then it receives the default value (really default reference or default pointer) null. If you then attempt to use the object, you can get a NullPointerException.

Here is an example from the LongStack class above. If the class had no constructors, then the declaration/creation of myLStk in DemoLongStack would be erroneous since there would be no constructor to match the new operator. If, in addition. the declaration/creation was changed to not specify the size, the program would compile and the default, do-nothing no-arg constructor would be called. The result would be that theStack would be declared but not created. Then, when the first push is invoked, a NullPointerException would be raised because theStack contains null.

Start Lecture #16

Remark: Answer questions about lab 3 part 3

8.5.4 Differences between Variables of Primitive Types and Reference Types

int[][] c = { {4,5,6}, {7,8,9} };
int[] a = c[0];
int[] b = a;
b[1] = 999;
System.out.println(a[1]);
System.out.println(c[0][1]);

This section of the book finally gives the pictures showing that reference types only point to values; they do not contain values. All this applies equally well to arrays where we previously discussed it.

The example on the right illustrates reference semantics. Both values printed are 999.

8.6 Using Classes from the Java Library

A significant strength of Java is its very extensive class library.

8.6.1: The Date Class

Let's use this opportunity as an excuse for reviewing how to obtain information from http://java.sun.com/javase/7/docs/api.

The 8e mentions two constructors and three methods (toString(), getTime(), and setTime()).

If you knew that Date was java.util.Date you could restrict the classes, but let's not and use the default of all classes.

The same two constructors are present as in the 8e (along with several that are deprecated). The methods are there as well, together with many others (some deprecated).

  import java.util.Date
  Date myDate = new Date();
  System.out.println(myDate.toString());

Don't forget the syntax of objectName.methodName. On the right is a simple example. How would the example have differed without the import?
Answer: Date would have been java.util.Date twice in the declaration/creation.

8.6.2 The Random Class

Again use the web and find the constructors and methods.

8.6.3 Displaying Gui Components

Homework: 8.3.

8.7 Static Variables, Constants, and Methods

We have now seen several methods in classes that are associated with objects. For example myDate.toString() invokes the toString() method that is associated with the object myDate. If we had also declared another Date, say yourDate, then myDate.toString() would be a different method from yourDate.toString().

This is a good thing since the string format of myDate is different from the string format of yourDate (unless myDate and yourDate happen to refer to the exact same time).

Similarly, we have seen several data fields defined in classes that are associated with each instance of the class. For example, the top of each instance of LongStack is unique.

This is also good. If you have several LongStack's, you don't expect their top's to be equal and surely pushing one LongStack does not affect the top of any other LongStack.

class ShortStack {
  int top = 0;
  short[] theStack;
  ShortStack() {
    theStack = new short[100];
  }
  ShortStack(int n) {
    theStack = new short[n];
  }
  // Maintain total number of items stacked
  static int totalStacked = 0; // should be pvt
  static int getTotalStacked() {
    return totalStacked;
  }
  void push(short elt) {
    if (top>=theStack.length)
      System.out.println("No room to push");
    else
      theStack[top++] = elt;
    totalStacked++;
  }
  short pop() {
    if (top<=0) {
      System.out.println("Nothing to pop.");
      return -99;  // awful!!
    }
    totalStacked--;
    return theStack[--top];
  }

}
public class DemoShortStack {
  public static void main (String[] args) {
    ShortStack mySStk1 = new ShortStack(4);
    ShortStack mySStk2 = new ShortStack();
    mySStk1.push((short)12);  mySStk1.push((short)13);
    mySStk2.push((short)22);  mySStk2.push((short)23);
    System.out.printf("Before pops: stacked=%d\n",
              ShortStack.getTotalStacked());
    short x = mySStk1.pop();
    short y = mySStk1.pop();
    System.out.printf("After pops:  x=%d, y=%d %s%d\n",
              x, y, "and stacked=",
              ShortStack.getTotalStacked());
  }
}

Before pops: stacked=4
After pops:  x=13, y=12 and stacked = 2

But sometimes having separate instances of each method and field for each object is not what we want. On the right we see ShortStack. I derived it from LongStack, by first replacing all long's with short's.

Then I decided to keep track of the total number of items stacked on all the ShortStack's. This single variable totalStacked is incremented by every push(), no matter which ShortStack is being pushed, and is decremented by every pop(). We indicate that there is one totalStacked associated with the entire class ShortStack rather than one totalStacked associated with each instance of shortStack (i.e., one associated with each object) by declaring totalStacked to be static.

A variable like totalStacked that is associated with the class and not with each instance is called a static variable or a class variable.

Unlike instance variables that are referred to as objectName.variableName, static variables (or class variables) are referred to as className.variableName.

I could have printed this value in the main() method, but did not. Instead, I had the class define a method getTotalStacked() that returns the value of totalStacked. and had main() call getTotalStacked().

As we will see in the next section, it is possible to prevent other classes from accessing a variable, which has the great advantage that the author of the original class can be sure that no other class either changes the variable or depends on how the variable is implemented.

In such cases it is common to supply an accessor method like getTotalStacked() to get the value and sometimes another method to alter the value.

We could have writen getTotalStacked() as an instance method, so that there would be one associated with each object of type ShortStack. However, that would be a little silly (but would work) since the method just returns the value of a class field and hence all instances of the method would be the same. Hence we declare getTotalStacked() to be static so that there is only one getTotalStacked() method for the entire class. That is, we make it a class method or static method.

One more point, one that will reveal a great secret. Since a class method is dependent on only the class and not on any object of the class, we can define and use a class method even if there are no objects of the class. At long last we can understand

  public static void main (String[] args)

which must be used in every Java program.

8.8 Visibility Modifiers

Some of our classes, data fields, and methods have been public and some have not. What gives?
There are actually four possibilities, of which we have used two.

  1. public
  2. private
  3. protected
  4. default (unspecified), which is package-private or package-access

(Top level classes, i.e., classes not inside any other class, can not be declared private or protected.)

8.8.A public and private

An entity (a class, a field, or a method) that is declared to be public can be accessed from any class.

An entity that is declared to be private is accessible only within its own class.

public class PubClass {
  public int x; // accessible everywhere
  private int f2() { // accessible only in PubClass
    return 2;
  }
  private class PvtClass {
    // x and f2() accessible here
  }
}
// Following is a new file
public class C2 {
  // x accessible f2() is not
  private class C1 {
    // x accessible f2() is not
  }
}

Look at the example on the right and note the following.

8.8.B Packages and Default Visibility

Java includes the notion of a package, which is a collection of classes. A .java file can begin with a line
      package packageName;
which declares all the classes in the file to be in the package packageName.

Packages are very important for large programs, but less so for those we will write.

None of our .java files have included a package statement. In such cases Java places the classes declared in the file into the so called default package.

If an entity is declared without a visibility modifier (public, private, or protected), then the entity has package visibility, which means it is visible in all classes belonging to the same package that the entity belongs to.

8.8.C protected

The protected visibility modifier comes into play only when there are subclasses and inheritence so we defer it to Chapter 11.

8.8.D The Bottom Line (for now)

Until we learn about subclasses and protected, our use of modifers will be fairly simple (in part because we are not learning about packages).

  1. Each .java file must have at most one top-level public class.
  2. The main() method must be declared public and must be in a public class..
  3. Use private for entities that should not be visible outside the current class.
  4. For other entities the public visibility modifier and no visibility modifier have the same effect.

Homework: 8.7

8.9 Data Field Encapsulation

8.9.A Improving ShortStack

class ShortStack {
  int top = 0;
  short[] theStack;

class ShortStack {
  private int top = 0;
  private short[] theStack;
  int getTop() {
    return top;
  }

On the upper right we see the beginning of the ShortStack class we saw previously. In the second group, we added private visibility modifiers, which improves the class considerably. Why?

  1. Users of ShortStack must not alter top. Altering top would distroy the LIFO semantics required for a stack. If we wished to give the user read-only access to top, we could supply a getTop() accessor method as shown at the bottom.
  2. Since users of ShortStack have no access to theStack, the ShortStack programmer can change the implementation to not use a single array. In general, you want to make public the published interface to the class, and keep private the implementation details.

Improving the Circle1 Class

public class Circle3 {
  private double radius = 1;
  private static int numberOfObjects = 0;

  public Circle3() {
    numberOfObjects++;
  }
  public Circle3(double newRadius) {
    radius = newRadius;
    numberOfObjects++;
  }

  public double getRadius() {
    return radius;
  }
  public static int getNumberOfObjects() {
    return numberOfObjects;
  }
  public double getArea() {
    return radius * radius * Math.PI;
  }

  public void setRadius(double newRadius) {
    radius = (newRadius >= 0) ? newRadius : 0;
  }
}

Until we learn about packages and subclasses, we need only distinguish public and private. Both protected and the default package-private are equivalent to public in the absence of subclasses and packages.

On the right we see an improved circle class, now called Circle3 (we did not do Circle2).

Homework: 8.9.

8.10 Passing Objects to Methods

class ShortStack {
  private int top = 0;
  private short[] theStack;
  ShortStack() {
    theStack = new short[100];
  }
  ShortStack(int n) {
    theStack = new short[n];
  }
  private static int totalStacked = 0;
  static int getTotalStacked() {
    return totalStacked;
  }
  int getTop() {
    return top;
  }
  void push(short elt) {
    if (top>=theStack.length)
      System.out.println("No room to push");
    else
      theStack[top++] = elt;
    totalStacked++;
  }
  short pop() {
    if (top<=0) {
      System.out.println("Nothing to pop.");
      return -99;  // awful!!
    }
    totalStacked--;
    return theStack[--top];
  }
}
public class DemoShortStack2 {
  public static void main (String[] args) {
    ShortStack mySStk1 = new ShortStack(4);
    ShortStack mySStk2 = new ShortStack();
    mySStk1.push((short)12);  mySStk1.push((short)13);
    mySStk2.push((short)22);  mySStk2.push((short)23);
    System.out.printf("Before pops: stacked=%d\n",
                      ShortStack.getTotalStacked());
    short x = mySStk1.pop();
    short y = mySStk1.pop();
    System.out.printf("After pops:  x=%d, y=%d %s%d\n",
                      x, y, "and stacked=",
                      ShortStack.getTotalStacked());
    System.out.println("Printing and emptying stack #1");
    printAndEmptyShortStack(mySStk1);
    System.out.println("Printing and emptying stack #2");
    printAndEmptyShortStack(mySStk2);
  }
  public static void printAndEmptyShortStack(ShortStack SStk){
    System.out.printf("%d items stacked, %d in this stack.\n",
                      ShortStack.getTotalStacked(),
                      SStk.getTop());
    for (int i=1; SStk.getTop()>0; i++)
      System.out.printf("Item #%d is %d\n", i, SStk.pop());
    System.out.println();
  }
}

Before pops: stacked=4
After pops:  x=13, y=12 and stacked=2
Printing and emptying stack #1
2 items stacked, 0 in this stack.

Printing and emptying stack #2
2 items stacked, 2 in this stack.
Item #1 is 23
Item #2 is 22

As with arrays, passing an object to a method actually passes a reference (pointer) to the object.

The book illustrates this with one of the circle classes. You should read that.

For variety (i.e., a different example) we consider our ShortStack class.

As illustrated on the right, I have included several improvements and enhancements that I will describe just below. Although I wrote both classes, it is useful to think of the ShortStack class as written by the class writer or the author and to think of DemoShortStack as written by the user.

The simplest improvement is that the Data fields top, theStack and totalStacked are now private. As mentioned previously, this prevents the user from modify these variables, which makes the author's job much easier.

Accessors are provided for top and totalStacked giving the user read-only access to the variables.

In addition to the author's enhancements to ShortStack the user added a method printAndEmptyShortStack(). Since ShortStack is public, it is visible to printAndEmptyShortStack() as are all of ShortStack's public methods. For this reason printAndEmptyShortStack() is as easy for the user to write as it would be for the class author. Since it is a rather specialized method, it doesn't seem worthwhile to include in the general-purpose ShortStack class.

The printAndEmptyShortStack() method has one parameter; it is of type ShortStack.

Note the use of the accessor functions getTotalStacked() and getTop(). Specifically, note that, since getTotalStacked() is a class method, it is referenced as className.methodName(), namely ShortStack.getTotalStacked(). In contrast, since getTop() is an instance method, it is referenced as objectName.methodName, namely SStk.getTop().

Start Lecture #17

Passing a Reference by Value??

How can you pass a reference (an object is a reference) by value? I thought values were for primitive types, not for references.

That does sound bad but yes the reference is passed by value, it is copied from the argument to the parameter and is not copied back (standard call-by-value semantics)

This is exactly the same as passing an array, which we did a few weeks ago.

A Snapshot During Execution

The diagram on the right shows the situation when main() has called printAndEmptyShortStack() for the first time and the loop is just starting.

shortStack2

For technical reasons, covered in elective courses in compilers and comparative programming languages, objects themselves are stored in a region of memory separate from local variables. This region is normally called the heap.

The variables local to a method are stored in a region called the stack (yes, it does have stack-like properties).

These variables disappear when the method returns, and their size is known when the program is written. Both these properties need not hold for entities stored in the heap.

Although this separation of stack from heap is quite important for efficiency, we will not emphasize it in this course.

The diagram illustrates value semantics for the int and short variables x, y, and i. Specifically the memory location assigned to one of these variables contains the current value of the variable (this so called value semantics holds for all primitive types).

In contrast, consider the objects SStk and mySStk1 (really, we are considering the variables that are declared to be objects) In this example all objects are ShortStack's. These have reference semantics. The memory assigned to one of these variables contains only a pointer to the object and not the object itself.

Also note the call-by-value semantics. The contents of the argument myStk1 is copied into the parameter SStk.

8.11 Array of Objects

To understand arrays of objects we just combine the semantics of arrays with the semantics of objects. Consider the diagram on the right.

array-of-objs

Chapter 9 Strings and Text I/O

9.1 Introduction

9.2 The String Class

In Java a String is an object. Thus, like a ShortStack, a String has reference semantics.

In some languages a string is an array of characters. Although in Java an array of char's is similar to a String (e.g., both have reference semantics), they are not the same (e.g., you do not use [] to extract a char from a String).

9.2.1 Constructing a String

As shown on the right, String's can be created several ways.

System.out.println("A string");


String s0;


String s1 = new String();

String s2 = new String("A string");
if (s2 != "A string")
  System.out.println("Outrageous");

char[] c1 = {'c','h','a','r','s'};
String s3 = new String(c1);
  1. The first example shows a string literal. This is an object but there is no variable containing it (really, we should say referring to it).
  2. The next example declares a string variable but creates no string (similar to int[] a;). This variable cannot be used until it is assigned a value.
  3. Next we use the String constructor with no parameters. This produces the empty string, (a reference to) which is assigned to s1. Unlike s0, s1 is ready to be used.
  4. There is another String constructor, which has a String parameter. This constructor produces a new object with the same contents as the parameter. That is, a copy is made, which can give outrageous results (see 9.2.3, below).
  5. There is a third constructor that has a character array as parameter and produces a string with those characters.

9.2.2 Immutable Strings and Interned Strings

String objects are immutable, they cannot be changed. However, a string variable is mutable and can reference different string objects at different times.

String str1 = "joe";
String str2 = "linda";
str1 = "linda";

On the right we have two String objects: "joe" and "linda", and two String variables: str1 and str2. The objects cannot change but we have changed the str1 variable.

String Literals are Interned

Java creates only one copy of each string literal, even if you write it several times. Thus after the code above is executed, there is only one object "linda". The technical term is that the literal is interned.

When are Strings Equal (==)?

if (str1 == str2)  // Yes
  System.out.println("Of course");

String str3 = new String("linda");
if (str1 != str3)  // Yes !!
  System.out.println("Outrageous!");

String str4 = new String(str1);
if (str1 != str4)  // Yes !!
  System.out.println("Outrageous!");

javac Test.java  &&  java Test
Of course
Outrageous!
Outrageous!

After the three statements above, both str1 and str2 refer to this one literal. That is, str1 and str2 contain the same reference and, as indicated to the right they of course compare equal (==).

However, when you create a string using the one-argument constructor as done on the right, a copy is made. Since str3 refers to this copy it is NOT equal (!=) to either str1, str2 or "linda", which seems outrageous!

Equally outrageous is the situation with str4

These last two paragraph seem very bad. We have three String variables str1, str3, and str4, each of which would print the same string linda, but they are not equal. Fortunately, there are more sections to this chapter.

9.2.3 String Comparisons

The problem we just had was that we were comparing the references and not the values they referred to. To do the latter we need to use a method in the String class.

boolean equals (Object anObject)


String s1 = "joe";
String s2 = new String(s1);
if ("joe".equals(s1)) // YES
  System.out.println("Of course");
if ("joe".equals(s2)) // YES
  System.out.println("Of course");
if (s1.equals(s2)) // YES
  System.out.println("Of course");

Of course
Of course
Of course

On the right we see the equals() method. Technically, it has an Object as parameter, but for now pretend that the only permitted Object is a String.

Something looks wrong! How come equals has only one parameter. Surely, we want to see if two strings are equal. The answer is that equals() is an instance method and thus is attached to a string object, which it then compares to its parameter.

We see examples of the usage on the right. Note that, in these examples if(s1==s2) would be NO.

There are other string comparison methods defined, e.g., one that ignores case and one (compareTo() that distinguishes less than from greater than (see below).

9.2.4 String Length, Characters, and Combining Strings

Java has many methods dealing with strings. We will just touch on a few. See the API for a list.

int length()
char charAt(int index)
String concat (String s)

The three methods on the right length(), charAt(), and concat() are all instance methods. That is, they are associated with an instance of a class, i.e., an object. The notation used is therefore objectName.methodName(args). Note that the named object is a de facto extra parameter.

9.2.5 Obtaining Substrings

  String substring(int start)
  String substring(int start, int end)

There are two overloaded substring() instance methods. The first, one argument, variant gives the substring starting at the position specified by the argument. The other, two argument, variant gives the substring starting at the position specified by the first argument and ending just before the position specified by the second argument.

For example, "linda".substring(1,2)=="i" .

9.2.6 Converting, Replacing, and Splitting Strings

  String toLowerCase()
  String toUpperCase()
  String trim()
  String replace(char old, char new)
  String replaceFirst(String old, String new)
  String replaceAll(String old, String new)
  String[] split(String delim)
  
  String[] ans = "ab cd e".split(" ");
  
  ans[0]=="ab"  ans[1]=="cd"  ans[2]=="e"

The methods on the right are well described by their names except perhaps for trim() and split.

trim() removes leading and trailing blanks.

split() is quite nice, splitting the original string at the specified delimiter returning the pieces as an array.

For example, executing the statement shown in the middle right, declares ans to be an array of strings having three elements. These elements are shown on the bottom right.

9.2.7 Matching, Replacing and Splitting by Patterns.

Many of the string arguments in the preceding section are interpreted as regular expressions, which can be quite useful. For example the regular expression *\.java would match any string that ended in .java.

In addition to the methods in 9.2.6, we note matches(), which is like equals() except that its string argument is interpreted as a regular expression.

9.2.8 Finding a Character of a Substring in a String

  int indexOf(char c)
  int indexOf(String s)

The two methods on the right find the first occurrence of the character or string in the object. They return -1 if the character or string does not occur.

There are variants that look for the first occurrence after a specified position, the last occurrence, or the last occurrence before a specified index. They all return -1 if the search is unsuccessful.

9.2.9 Converting between Srrings and (Character) Arrays

We have already seen the String(char[] charArray) constructor. The inverse operation is provided by the toCharArray() method.

  char[] ca = "Joseph".toCharArray();
  String s1 = new String(ca);

  String s2 = new String("Joseph".toCharArray());

For example, the first code snippet initializes s1 to Joseph in a complicated way, using the character array ca as an intermediary.
The second (one-line, no intermediary) snippet, is perhaps even more complicated.

9.2.10 Converting Characters and Numeric Values to Strings

So far we have see constructors and instance methods. No we will see two class methods, one of which is heavily overloaded.

The class method String.valueOf() converts many data types into strings. Note again that this is a class (i.e., static) method so is associated with the class String and not with any specific string. It is heavily overloaded; there are versions with a char argument, a char[] argument, a double argument, an int argument, a boolean argument, and others as well.

9.2.11 Formatting Strings

The other class method is String.format(). It takes the same arguments as System.out.printf() but, instead of printing the result, it returns it as a String.

String s = String.format("6.2f",3.78);

For example, the statement on the right sets s equal to "  3.78".

9.2.12 Problem: Checking Palindromes

A palindrome is a string that reads the same from left to right as from right to left. Note that blanks count. For example the following are palindromes.

import java.util.Scanner;
class DemoPalindrome {
  public static void main (String[] Args) {
    Scanner getInput = new Scanner(System.in);
    while (true) {
      System.out.println("Type a string, use !!EXIT!! to exit");
      String s = getInput.nextLine();
      if (isPalindrome(s))
        System.out.println
         (String.format("\"%s\" is a palindrome.", s));
      else
        System.out.println
          (String.format("\"%s\" is not a palindrome.", s));
    }
  }
  public static boolean isPalindrome(String s) {
    int lo = 0;
    int hi = s.length()-1;
    while (lo < hi) {   // this works for *all* s
      if (s.charAt(lo) != s.charAt(hi))
        return false;
      lo++;
      hi--;
    }
    return true;
  }
}

The program on the right, queries the user for a string and then checks if it is a palindrome. Note the following points.

  1. The new instance method getInput.nextLine() reads the entire next line as a string and strips off the final newline. By using this function, we permit the input to contain blanks.
  2. The use of String.format() is somewhat contrived. The natural code would use printf(); I used String.format() just to show how it works.
  3. The format string doesn't end with \n because I used println, which appends a \n. Had I used just print(), I would have had a \n at the end of the format.
  4. Names similar to isPalindrome() are commonly used for boolean methods that return whether a property holds.
  5. Note that the while loop in isPanlindrome() successfully handles the case where s has just one character as well as the case where s has no characters.

9.2.13 Problem: Converting Hexadecimals to Decimals

Read

9.2.13 (Alternate): Alphabetizing

The compareTo() String instance method makes alphabetizing essentially the same as sorting integers, as shown by the side by side code comparison below.

public class DemoSortString {
  public static void main (String[] args) {
    String[] strArray = {"a","zzz","abc","ab","Z","6"};
    System.out.println("Before");
    for (int i=0; i<strArray.length; i++)
      System.out.println(strArray[i]);
    sortString(strArray);
    System.out.println("After");
    for (int i=0; i<strArray.length; i++)
      System.out.println(strArray[i]);
  }
  public static void sortString(String[] strArray) {
    for (int i=0; i<strArray.length-1; i++)
      for (int j=i+1; j<strArray.length; j++)
        if(strArray[i].compareTo(strArray[j]) > 0) {
          String t = strArray[i];
          strArray[i] = strArray[j];
          strArray[j] = t;
    }
  }
}
public class DemoSortInt {
  public static void main (String[] args) {
    int[] intArray = {6, 9, 2, 4, 1, 8};
    System.out.println("Before");
    for (int i=0; i<intArray.length; i++)
      System.out.println(intArray[i]);
    sortInt(intArray);
    System.out.println("After");
    for (int i=0; i<intArray.length; i++)
      System.out.println(intArray[i]);
  }
  public static void sortInt(int[] intArray) {
    for (int i=0; i<intArray.length-1; i++)
      for (int j=i+1; j<intArray.length; j++)
        if(intArray[i] > intArray[j]) {
          int t = intArray[i];
          intArray[i] = intArray[j];
          intArray[j] = t;
    }
  }
}

The only real difference between the two code snippets is that the integer comparison via > is replaced by compareTo(), which returns a positive, zero, or negative integer depending on whether the given object is greater than, equal to, or less than the argument.

public static int compareStr(String s1, String s2) {
  final int EQUAL=0, LESS=-123, GREATER=321;
  for (int i=0; i<s1.length() && i<s2.length(); i++)
    if (s1.charAt(i) < s2.charAt(i))
      return LESS;
    else if (s1.charAt(i) > s2.charAt(i))
      return GREATER;
  if (s1.length() < s2.length())
    return LESS;
  else if (s1.length() > s2.length())
    return GREATER;
  else // s1.length() == s2.length()
    return EQUAL;
}

For fun let's try to write a class method compareStr() that takes two arguments and returns a positive, zero, or negative integer depending on whether the first argument is greater than, equal to, or less than the second.

This is one of those programs where I found it useful to think first and just sketch out the idea before starting to code. It is actually fairly easy, but you can be lead astray.

The code on the right has two parts.

  1. The loop compares corresponding characters of the two strings making sure not to exceed the length of either string. The first inequality ends the show.
  2. If we get through the loop that means all the characters are equal until we run out of characters in at least one string. Thus, if we get here, the answer is simply determined by the length of the strings.

9.3 The Character (and other Wrapper) Class(es)

As mentioned, Java is an object oriented (OO) language and objects/classes play a central part in serious Java programming.

However, the advantages of OO languages comes in to play mostly for large programs, much larger than any we will write. So, for us as of now there isn't all that much advantage to using an object of type Character rather than a value of type char.

We call the class Character a wrapper for the primitive type char. Similarly, Java provides wrapper classes Boolean, Byte, Short, Integer, Long, Float, and Double for the corresponding primitive types boolean, byte, short, int, long, float, and double.

Do remember that the wrapper classes provide objects, and thus have reference semantics.

It is easy to obtain the Character corresponding to a given char: The Character class provides a constructor for just this purpose. Thus, new Character('a') produces the Character corresponding to the char 'a' .

boolean isDigit(char c)
boolean isLetter(char c)
boolean isLetterOrDigit(char c)
boolean isLowerCase(char c)
boolean isUpperCase(char c)
char toLowerCase(char c)
char toUpperCase(char c)

Perhaps the piece of the Character class most useful to us now, is the collection of class methods that operate on char's (not Character's).

On the right we see seven of these useful class methods. As an example to test if the char c) is a digit, you would write

  if (String.isDigit(c))

Note the consistent naming: the prefix is is used for a test, and the prefix to is used for a conversions.

Homework 9.1 and 9.5.

Start Lecture #18

9.4 The StringBuilder/StringBuffer Class

Java has 3 classes whose objects are strings, namely String, StringBuilder, and StringBuffer.

  1. String is the simplest, most efficient, and most limited of the three classes. The limitation is that a String object cannot be changed after it has been created.
    Do not forget, however, that a String variable can change during execution, first referencing one String object and then referencing another
  2. StringBuilder is more complicated: The resulting objects can be changed after construction, as we shall see.
  3. StringBuffer is yet more complicated as it supports multi-tasking, a very important property, but one we shall not discuss. For that reason we will not mention StringBuffer again.

9.4.1 Modifying Strings in the StringBuilder

import java.util.Date;
 class Test {
    public static void main (String[] Args) {
      StringBuilder sB = new StringBuilder("Hello");
      String s = new String ("Hello");
      System.out.printf("s is \"%s\" and sB is \"%s\"\n", s, sB);
      System.out.printf("The lengths of s and sB are %d and %d\n",
                s.length(), sB.length());
      System.out.printf("The capacity of sB is %d\n", sB.capacity());
      s = s.concat(", world.");
      sB.append(", world.");
      System.out.printf("s is \"%s\" and sB is \"%s\"\n", s, sB);
    }
}

s is "Hello" and sB is "Hello"
The lengths of s and sB are 5 and 5
The capacity of sB is 21
s is "Hello, world." and sB is "Hello, world."

Superficially a String s and a StringBuilder sB appear to act the same. Indeed, a quick glance at the code on the right suggests that StringBuilder adds nothing.

In this case, however, looks can be deceiving. Specifically, s.concat() and sB.append() are really quite different because the String referenced by s is immutable; whereas, the StringBuilder referenced by sB is not.

The expression sB.append(", world") actually appends the argument onto the object referenced by sB. It does not copy the original contents of that object.

In contrast s.concat(", world") does not, indeed cannot change the string referenced by s. What happens is that

  1. this string is copied
  2. the argument is appended to the copy
  3. a reference is returned to the copy

When we assign the resulting reference to s, s now refers to the copy and, the original is inaccessible (assuming no other variable contains a reference to the original).

There are other StringBuilder instance methods that permit insertions and modifications in the middle of the object as well as deletion of parts of the object. One could write String methods to do the same, but they would be a little more complicated.

There are other methods as well.

Why Does this Matter?

For our programs it hardly matters at all. The real advantage of StringBuilder is that these manipulations are done on the original, with no copy being made. For strings of length a few dozen, making copies is no big deal (unless done very many times). However, for much larger strings, the copying can become expensive.

9.4.2 The toString, capacity, length, and charAt Methods

All of these are instance methods. As expected sb.toString() returns the String equivalent of the StringBuilder sb, sb.length() returns the length, and sb.charAt() returns the character at the position specified by the argument.

The capacity() instance method is less obvious. A Java StringBuilder object is generally bigger than its current length (this larger size is called the capacity). That way it is easy for the system to append characters. Except for efficiency considerations, we can ignore capacity since the JVM will extend the StringBuilder whenever needed.

You can also reduce the length of a StringBuilder (eliminating characters at the end) or increase its length (padding with null characters).

9.4.3 Problem: Ignoring Nonalphanumeric Characters When Checking Palindromes

The book's solution is basically an exercise in using a few of the methods. The basic steps are

  1. Read in a String s.
  2. Create an empty StringBuilder sb
  3. Using Character.isLetterOrDigit(s.charAt), append only the letters and digits to sb.
  4. Convert this back to a String s1.
  5. Convert s1 to a StringBuilder sb1
  6. Reverse sb1 and convert it back to a String s2.
  7. The answer is s2.equals(s1)

The reason for converting back to String's is that (surprisingly?) I don't think StringBuilder has an equals() or compareTo() method.

I did this without a StringBuilder, but it is less efficient since Strings are immutable so my solution involves more copying

Homework: 9.11

9.5 Command-Line Arguments

We know that the main() method has a single parameter, which is an array of String's. However, we have not yet made use of this. Technically, we have used it all the time; we just had the empty array.

9.5.1 Passing Strings to the main Method

Recall that, if we have a program Prog.java, we compile it with the command javac Prog.java and run the result with the command java Prog.

To introduce command-line arguments, there is no change to the compilation, but we run the program differently. Specifically we add the arguments after the name of the program.

Say we want to give Prog three arguments, one integer and two strings. Specifically the arguments are: 25, arg 2, last. We would write

  java Prog 25 "arg 8" last

Several comments are needed.

  1. Although we think of 25 as an integer and we write it as an integer, arg[0] is a String. Indeed, the jvm passes all arguments as strings.
  2. The last argument last does not have quotes. This is correct. It would also be correct to write "last". In either case the main() method would receive a string of length 4.
  3. The second argument "arg 8 does have quotes. This is necessary since arg 8 would be two arguments. To repeat, quotes are needed if the argument contains spaces; they are optional if the argument does not have spaces.

Remark: One night I was editing my magazine column and needed a function to reverse a number. Since I just taught it a few hours previously in 101, I actually wrote one of those routines that is all library method calls. Then, for fun I made it an ugly one-liner. Here is the result.

import java.util.Scanner;
import java.io.File;
public class TechRev {
    public static int reverse (int n) {
    // StringBuilder sb = new StringBuilder(Integer.toString(n));
    // return Integer.parseInt(sb.reverse().toString());
    return Integer.parseInt(new StringBuilder(Integer.toString(n)).reverse().toString());
    }
}

9.5.2 Problem: Calculator

public class Calculator {
  public static void main(String[] args) {
    if (args.length != 3  || args[1].length != 1) {
      System.out.println(
        "Usage: java Calculator operand1 [+-*/] operand2");
      System.exit(0);
    }
    char operator = args[1].charAt(0);
    int result;
    switch (operator) {
      case '+': result = Integer.parseInt(args[0]) +
                         Integer.parseInt(args[2]);
                break;
      case '-': result = Integer.parseInt(args[0]) -
                         Integer.parseInt(args[2]);
                break;
      case '*': result = Integer.parseInt(args[0]) *
                         Integer.parseInt(args[2]);
                break;
      case '/': result = Integer.parseInt(args[0]) /
                         Integer.parseInt(args[2]);
                break;
      default:  System.out.printf("Illegal operator %c\n",
                                  operator);
                System.exit(0);
    }
    System.out.printf("%s %c %s = %d\n",
                      args[0], operator, args[2], result);
  }
}

The code on the right (only slightly modified from the book) is for a trivial calculator that can do one operation, providing the operation is +, -, *, or /. There are a few points to say about it.

  1. Note the error checking for the right number of arguments and that the operator is just one character.
  2. System.exit() terminates the job. The integer argument gives the status, but we will not use the value so it might as well be zero.
  3. switch requires an integer, which in Java can be a char but cannot be a String. Hence we convert the one-character String (the length of the string is checked) to a char using the instance method charAt().
  4. As mentioned above, in addition to Character, java has wrapper classes for all the primitive types. The Integer class contains a class method parseInt() that converts a String to an int.

Homework: 9.13, 9.15.

One of the major uses of command-line arguments is to tell programs which files to process. We next learn how to do that.

9.6 The File Class

In Java reading and writing files is divided into two tasks.

  1. First we need to name the file in question. For this we use the File class.
  2. Second we need to perform the actual input and output, creating the file if necessary. For this there are several classes that can be used. In this chapter we will learn PrintWriter and (an old friend) Scanner.

The File class is used to construct a File object from a file name and to perform certain operations on the object. However, it is not used to create a file, to read a file, or to write a file.

File(String pathname)
File(String parent, String child)
File(File parent, String child)

The primary form of the File constructor is the first one shown on the right. This form accepts a single String fn as parameter and constructs a file object corresponding to the file with name fn.

This file may or may not exist and the constructor does not create the file in any case. The name may be relative (to the current directory) or may be absolute. One downside of absolute addresses is that they are OS dependent.

Some boolean Instance Methods

The File class includes exists(), canRead(), canWrite(), isDirectory(), isFile, isAbsolute(), and isHidden().

Their names describe well their actions.

Some String Instances Methods Returning Portions of the Name

The File class includes getName(), getPath(), and getParent().

They return respectively the file name without any directories, the full file name, and the full file name of the parent.

Some Utility Instance Methods

9.7 File Input and Output

Once we have an object File f1 we can actually perform input/output on the corresponding file in the file system.

One complication that can occur is that the I/O operation can fail. For example, you might try to read from a file that doesn't exits or write to a file for which you do not have the needed permissions. The technical term is that an I/O exception can occur. We will learn about exceptions later; for now we simply add throws Exception to the header line for any method that (directly or indirectly) invokes I/O.

9.7.1 Writing Data Using PrintWriter

The first step in writing data to a file is to create a PrintWriter object, which in turn needs a File object. We can create both at once with

  java.io.PrintWriter output = new java.io.PrintWriter(new java.io.File("x.text"));

If the above looks too imposing, use some import statements.

  import java.io.PrintWriter;
  import java.io.File;
  PrintWriter output = new PrintWriter(new File("x.text"));

Now we can use output the way we have used System.out in the past. For example we can write output.printf("hello");

After all the data has been sent to the PrintWriter object, its close() instance method should be called.

9.7.2 Reading Data Using Scanner

We have used the Scanner class many times, but always with System.in, which is predefined to correspond to the keyboard. In order to obtain a Scanner capable of reading from the file named existing.text we write (assuming the necessary import's have been executed.

  Scanner input = new Scanner(new File ("existing.text"));

You can also write this declaration (and the one for PrintWriter) in two steps.

  File f = new File("existing.text");
  Scanner input = new Scanner(f);

9.7.3 How Does Scanner Work?

9.7.3 (my title) What Does Scanner Do?

Remark: Note that the book is rather confusing at the end of this section. It seems to be saying that the behavior is different when you change from an input file to input from the keyboard. As far as I can see, the real change is where the newlines are placed.

Token-Reading Methods and nextLine()

import java.util.Scanner;
import java.util.Date;
import java.io.File;
public class Test {
  public static void main (String[] Args) throws Exception {
    Scanner input = new Scanner (new File ("t.txt"));
    Scanner getInput = new Scanner (System.in);
    String s1 = input.next();
    String s2 = input.next();
    String line = input.nextLine();
    System.out.printf("s1=\"%s\"  and  s2=\"%s\"\n", s1, s2);
    System.out.printf("line=\"%s\"\n", line);
    s1 = getInput.next();
    s2 = getInput.next();
    line = getInput.nextLine();
    System.out.printf("s1=\"%s\"  and  s2=\"%s\"\n", s1, s2);
    System.out.printf("line=\"%s\"\n", line);
  }
}

File t.txt contains one line (ending with a newline)
   34   567   |<-- the line ends here

I entered the same characters from the keyboard

s1="34"  and  s2="567"
line="   "
s1="34"  and  s2="567"
line="   "

The instance methods nextByte(), nextShort(), nextInt(), nextLong(), nextFloat(), nextDouble(), and next() are often referred to as token-reading methods. They all work basically the same and depend on the value of the delimiter, which by default is equal to whitespace. Although there are other characters often considered whitespace and one can change the delimiter, for now just think of delimiter as meaning any non-empty mixture of blanks, tabs, and newlines. Then the token-reading methods work as follows.

  1. They start at the current position of the file (think of System.in for now as just another file).
  2. They read and discard any delimiters found at this position.
  3. They read the next characters up to but not including any delimiters that follow.
  4. The characters just read are converted into a byte, short, etc. (The method next() produces a String, which you might not consider a conversion.)
  5. That is it! In particular, any delimiters that follow the converted characters have not been read. The current position of the file is the first of these delimiters.

The nextLine() method is different and is not considered token-reading. It acts as follows.

  1. All the characters starting with the current position (even if it is a delimiter) and continuing up to and including the next newline are read.
  2. The newline is discarded.
  3. The remaining characters, including any delimiters, are formed into a string, which is returned.

Remark: Lab 4 is available.

9.7.4 Problem: Replacing Text

import java.io.*;
import java.util.*;
public class ReplaceText {
  public static void main(String[] args) throws Exception {
    if (args.length != 4) {
      System.out.printf("Usage: java ReplaceText %s\n",
                    "sourceFile targetFile oldStr newStr");
      System.exit(0);
    }
    File sourceFile = new File(args[0]);
    if (!sourceFile.exists()) {
       System.out.println("Source file " + args[0] +
                          " does not exist");
       System.exit(0);
    }
    File targetFile = new File(args[1]);
    if (targetFile.exists()) {
      System.out.println("Target file " + args[1] +
                         " already exists");
      System.exit(0);
    }
    Scanner input = new Scanner(sourceFile);
    PrintWriter output = new PrintWriter(targetFile);
    while (input.hasNext()) {
      String s1 = input.nextLine();
      String s2 = s1.replaceAll(args[2], args[3]);
      output.println(s2);
    }
    input.close();
    output.close();
  }
}

The program on the right illustrates many of the concepts we have just learned and is worth some study.

The program is executed with four command-line arguments, the first two are file names and the second two are strings (technically, all command-line arguments are Strings; the first two are interpreted by the program as names of files).

The program copies the first file to the second replacing all occurrences of the third argument with the fourth argument.

  1. Note the error checking. It checks for the correct number of arguments, checks that the old file exists, and checks that the new file does not exist.
  2. Note how it uses args[i] for the command line arguments, and don't forget that the first index is 0.
  3. The while loop keeps reading lines until end-of-file (EOF).
  4. The loop body writes the lines read to the new file after doing the replacement.
  5. Finally, the files are closed.

Remark: The real work is done in the one statement beginning String s2, in particular by the library method replaceAll(). The rest of the code is essentially just checking and preparing for the library call. It is not uncommon for the actual work to be done by a small portion of the code your write.

Homework: 9.19.

9.8 (Gui) File Dialogs

Chapter 10 Thinking in Objects

10.1 Introduction

We have seen already some differences when using objects. With instance methods one of the parameters is the object itself and is written differently. For example given a String s1, we get its length via s1.length() rather than length(s1). However designing (large) systems using objects as centerpieces (so called object-oriented design) has deeper differences from conventional design than the essentially syntactic change in length().

10.2 Immutable Objects and Classes

An object is called immutable if, once created, it cannot be changed.

Let me be clearer about that. When we say the object cannot be changed, we mean the data fields of the object cannot be changed.

For example, any String object is immutable (its only data field is the sequence of characters that constitute the string). A class, such as String, all of whose objects are immutable is also called immutable.

In contrast a StringBuilder can change after construction, as we have seen. So the StringBuilder class is mutable

What must we do to write an immutable class?

Naturally, we must make all the data fields private and no public method can change a data field. Such methods are often called mutators. But that is not enough!

An Immutable Class NoChanges

public class NoChanges {
  private int x;
  public NoChanges(int z) {
    x = z;
  }
  public int getX() {
    return x;
  }
}

Looking to the right, clearly the class NoChanges is immutable: the only data field is x, which cannot be directly accessed (it is private). With the accessor getX() we can find the value of x, but cannot change it. Thus once we create a NoChanges object with say

      NoChanges nc =  new NoChanges(5).

then the x component of this NoChanges object has the value 5 and will never change.

But the variable nc can be assigned to another NoChanges object.

A Mutable Class Changes

public class Changes {
  public int a;
  public int b;
  public Changes(int r, int s) {
    a = r;   b = s;
  }
}

In contrast, the class Changes is not immutable (it is mutable). For example, consider the code below

  Changes c = new Changes (5,6);
  c.a = 9;

This example has changed the object c by changing its first component from 5 to 9.

A Harder Example: Class Maybe

public class Maybe {
  private Changes c;
  public Maybe(int w) {
    c = new Changes (w,w);
  }
  public Changes getC() {
    return c;
  }
}

public static void main (String[] args) {
  Maybe mb = new Maybe(12);
  mb.c.a = 1;        // Will not compile
  mb.getC().a = 1;   // Works fine
}

The interesting case is class Maybe. At first (and even second) glance it looks like class NoChanges. Indeed each line of Maybe looks like the corresponding line of NoChanges. The only difference is the initialization of c. But just as with NoChanges, we do not have direct access to c (it is private) and the accessor lets us only find c's current value, not change it.

But there is a difference! The variable c is of type Changes, which is an object and hence has reference semantics. Also the object to which we now have a reference is itself mutable. Therefore the accessor has returned a reference that enables us to change the value of the components.

Code to exploit this hole is shown in the bottom frame on the right.

This should remind you of passing an array to a method. The method cannot change the array itself (since Java is pass-by-value), but it can use the array (with its reference semantics) to make changes to the components of the array.

There are three requirements for a class to be immutable.

  1. All data fields must be private.
  2. No mutator methods.
  3. No accessor method returns a reference to a mutable data field.

10.3 The Scope of Variables

There are two kinds of variables found in a class.

  1. Data fields. These have scope the entire class (except where hidden, see below) including prior to their declaration.

    If one data field, say x, is initialized to an expression involving another field y, then y must be declared before x.

  2. Variables (including parameters) declared in a method. These are referred to as local variables.

    Naturally, the same variable name can be declared in multiple methods. As we have seen previously, the same name can be declared in disjoint blocks in the same method. In both these cases each instance of the name refers to a separate variable.

If a local variable has the same name as a data field, the field name is hidden (i.e., not directly accessible) in that method. For this reason as well as for increased clarity, it is not recommended (but is permitted) to use the same name for both a class variable and a non-parameter local variable.

We shall see next section that it is common to have parameters with the same name as fields.

10.4 The this Reference

The special name this is used in several ways with respect to objects and classes. We see two of them on the right as well as an illustration of a few other points. Although the code is quite simple, it warrants some careful study.

public class C {
  int i;
  static int si = 4;
  C (int i) {
    this.i = i;
  }
  C () {       // no-arg constructor
    this(8);
  }
  int getI () { // nothing hidden
    return i;
  }
  void setI (int i) { // instance field
    this.i = i;
  }
  void setSi (int si) { // class field
    C.si = si;
  }
  public static void main (String[] args) {
    C c = new C();
    System.out.println(c.i);
  }
}
  1. The class C contains an instance field i, a class field si, two constructors, an accessor method, two set methods, one for setting each of the two fields, and a main() method.
  2. The 1-arg constructor follows a standard convention of using the same name for the parameter as for the field it is to initialize. Thus the field name i is hidden. However, the Java keyword this refers to the calling object itself and hence this.i recovers the hidden instance field i.
  3. As is often the case, the no-arg constructor performs the same actions as the 1-arg constructor, but with a default value. Thus we would like to have the no-arg constructor invoke the 1-arg constructor, but there is no name we can use for the constructor. Hence, Java defines this() to serve such a purpose.
  4. Accessor functions typically have no parameters and no local variables so hide nothing.
  5. For set methods the convention is to use as parameter the name of the field being set, thus hiding the field name. For an instance set method, we need to reference the current object and hence use this.
  6. For a class set method we need to reference the class and hence the class name exposes the hidden field name.

10.5 Class Abstraction and Encapsulation

Class abstraction is the separation of how a class is used from how a class is defined.

Another name is information hiding, the implementation of the class is hidden from the users of the class.

How to use a given class is described by the class specification, also called is public interface. In Java classes, fields and methods declared public form the interface. In contrast, private fields and private methods are not part of the specification, but are part of the implementation.

When looked at from the user's point of view, the class encapsulates the knowledge needed to use these objects. In the next section we discuss an example.

10.6 Object-Oriented Thinking

public class CheckingAcct {
  private String acctName;
  private double balance = 0;
  CheckingAcct(String acctName) { }
  public void makeDeposit (double amt) { }
  public boolean makeWithdrawal (double amt) { }
}
public class TestChecking {
  public static void main (String [] Args) {
    CheckingAcct acct = new CheckingAcct(
      "Allan Gottlieb's checking acct");
    acct.makeDeposit(100.00);
    acct.makeWithdrawal(30.00);
  }
}

   Allan Gottlileb's checking account
    Description   Amount   Balance
Deposit           100.00    100.00
Withdrawal         30.00     70.00

The book discuss a loan example, which you should read. We will do the beginnings of a checking account example.

On the right is the very beginning of a CheckingAccount class. Currently, we can open an account (in Java-speak we can create a CheckingAccount object), make deposits, and make withdrawals.

The bottom section shows the output produced.

This is not what a bank would use, but what customers would used for themselves. For example, we will not generate monthly statements, but might process a monthly statement received by the bank. Also banks do not permit withdrawals that drive the balance negative, but we will permit it since the user may know that certain previous debits have not cleared. Most significantly, we do not do any of the hard work involving actual money.

10.7 Object Composition

To handle writing a check, we need a data structure for the check itself, including the date, amount, payee, and note. We would also need a data structure for a statement. These would also be objects so would need class definitions. The class for checks might contain no methods.

A CheckingAccount object would need to contain (many instances of) a Check object, each of which would contain String objects for the payee and note.

Since a Check object can belong to only one CheckingAccount object, we call this a composition between the Check class and the CheckingAccount class.

10.8 Designing the Course Class

You should read this section in the book.

10.8 (Alternate) Extending the CheckingAcct Class

We will continue with our checking account.

An obvious omission is that we can't handle checks themselves. Before taking on that task, let's try something that should be simpler.

Each transaction should have a date. Many times the date of the transaction is today, but sometimes you might enter the transaction days after it was performed (remember this is our personal account history, not the bank's).

public class TestChecking {
  public static void main (String[] Args) {
    CheckingAcct acct = new CheckingAcct(
                 "Allan Gottlileb's checking account");
    acct.makeDeposit(100.0, 7,15,2010);
    acct.makeWithdrawal(30.0);
    acct.makeDeposit(50.00);
  }
}

          Allan Gottlileb's checking account
      Date  Description                 Amount   Balance
----------  -------------------------  -------  --------
 7/15/2010  Deposit                     100.00    100.00
  4/3/2012  Withdrawal                   30.00     70.00
  4/3/2012  Deposit                      50.00    120.00

public void makeDeposit (double amt, int month, int day,
                             int year) {...}

import java.util.Date;
private Date now = new Date();
private int nowMonth = now.getMonth();
private int nowDate  = now.getDate();
private int nowYear  = now.getYear()+1900;

Hence the public interface of CheckintAcct must be changed so that there are two makeDeposit() methods, one accepting a date and the other defaulting to today's date. Similarly, for makeWithdrawal().

On the right, the top frame is what we expect the client (user) of our class to write. Below that we see the output we wish to have.

What changes are needed to CheckingAcct.java?

  1. As mentioned we need another makeDeposit() method as shown in the next frame. An analogous makeWithdrawal() method is needed but is not shown.
  2. We need to get the today's date. As shown in the next frame, java.util.Date will give us that, but we want a certain format and have to extract the parts of the date ourselves. Actually, this code is deprecated in favor of some fancy stuff in java.util.Calendar, but that is an abstract class and we don't yet know how to deal with those.
  3. We need some mundane, but a little tedious, programming to get the columns to line up nicely in the output. Actually, I cheated and added some blanks to the output in addition to compensate for days and months with 1 digit.

Here is my version.

import java.util.Date;
public class CheckingAcct {
  private Date now = new Date();
  private int nowMonth = now.getMonth();
  private int nowDate  = now.getDate();
  private int nowYear  = now.getYear()+1900;
  private String acctName;
  private double balance = 0;

  CheckingAcct(String acctName) {
    this.acctName = acctName;
    printHeading();
  }
  public void makeDeposit (double amt) {
    makeDeposit(amt, nowMonth, nowDate, nowYear);
  }
  public void makeDeposit (double amt, int month, int day, int year) {
    balance += amt;
    String dateString = String.format("%d/%d/%d", month, day, year);
    System.out.printf("%10s  Deposit %26.2f %9.2f\n", dateString, amt, balance);
  }
  public void makeWithdrawal (double amt) {
    makeWithdrawal(amt, nowMonth, nowDate, nowYear);
  }
  public void makeWithdrawal (double amt, int month, int day, int year) {
    balance -= amt;
    String dateString = String.format("%d/%d/%d", month, day, year);
    System.out.printf("%10s  Withdrawal %23.2f %9.2f\n", dateString, amt, balance);
  }
  private void printHeading() {
    System.out.printf("%40s\n\n", acctName);
    System.out.printf("%10s  %-25s %8s %9s\n",
                      "Date","Description", "Amount", "Balance");
    System.out.printf("%s  %s  %s  %s\n", "----------","-------------------------","-------","--------");
  }
}

10.9 Designing a Class for Stacks

We already did stacks.

10.10 Designing the GuessDate Class

You should read this.

10.11 Class Design Guidelines

10.11.1 Cohesion

A class should describe a single entity. So having a class StacksAndQueues does not sound like a good idea.

But you might say that both stacks and queues are related; they both have insert and remove methods and perhaps boolean methods saying the structure is full, empty, or neither. We will learn about inheritance in the next chapter.

10.11.2 Consistency

Follow the Java naming and style conventions (see this short section for a brief review).

10.11.3 Encapsulation

Most fields should be private to ease maintenance and modifiability, you can't easily change the meaning of a data field to which the users have access. You should provide get and set methods as appropriate. In general, only expose to the user (i.e., make public) items whose meaning you guarantee will not change.

10.11.4 Clarity

Read.

10.11.5 Completeness

Read. A good example is the String class. It is not likely that any one application would use all the methods, but it is good that they are there.

10.11.6 Instance vs. Static

Declare data fields to be static if the value is constant for all objects in the class. Static fields should be set with a set method not via a constructor. For one thing, there may not be any objects created when the field is used (in a static method).

Some methods do not need any object of the class. For example, our friend, the main method, has this property. Such methods must be declared static as there is no object to which they can be attached.

Instance methods can reference both instance and class fields, as well as both instance and class methods.

Class methods can reference class methods and fields, but cannot invoke instance methods and fields (what object would the instance method or field refer to?).

Homework: 10.3

Chapter 11 Inheritance and Polymorphism

11.1 Introduction

The idea of inheritance, which is fundamental in object-oriented programming, is that sometimes classes B and C are refinements of class A and it is silly to have to reproduce all the A methods for both B and C.

For example, you could have a class for quadrilaterals that would have as data fields the four vertices, the color to draw the figure in, and some complicated method for calculating the area.

Then you would have a subclass for a rhombus that would include, in addition, the the (unique) side length. You would also have a subclass for rectangles that would override the area method with a much simpler one as well as providing additional data fields for height and width.

11.2 Superclasses and Subclasses

The terminology for the situation in the previous section is that quadrilateral is called a superclass, parent class, or base class. Both rectangle and rhombus would be called a subclass, a child class, an extended class, or a derived class.

Consider the example on the right.



public class GeometricalObject {
  String color = "blue";
}




public class Point extends GeometricObject {
  double x;
  double y;
}


public class Quadrilateral extends GeometricObject {
  private Point p1, p2, p3, p4;
  // constructors
  double area() {
    // complicated general area formula
  }
  // more
}


public class Rhombus extends Quadrilateral {
  private double sideLength;
  // constructors (check side lengths equal)
  // more
}

public class Rectangle extends Quadrilateral {
  private double width, height;
  // constructors
  double area() {
    return width * height;
  // more
  }
}
  1. We first define a GeometricObject class. This very general class would include quadrilaterals, circles, triangles, etc. There is not much data that is common to all these kinds of objects so the GeometricObject class has very few data fields. We define only the color, which might be used in drawing the object. As written the color can be modified by any client, which is not a good idea and is improved below.
  2. Since there are no constructors written, Java supplies a default no-arg constructor with an empty body.
  3. We then define a Point class, which extends GeometricObject and is used in Quadrilateral. Note that every point has a color.
  4. Next we have the definition of the Quadrilateral class, which is a subclass of GeometricObject. We shall see in section 11.13 that the Point's p1,...,p4 should be protected rather than private so that the subclasses Rhombus and Rectangle have access to these data fields.
  5. Note that Quadrilateral is not declared to be a subclass of Point. An easy way to see that a Quadrilateral should not be a subclass of Point is to note that it is NOT true that every quadrilateral is a point. The is-a relationship is normally needed for a subclass to be appropriate. A quadrilateral is a geometric object.
  6. Next is the Rhombus class. Note the phrase extends Quadrilateral in the class header. This phrase tells us that Rhombus is a subclass of the of the (superclass) quadrilateral. Note that in geometry, every rhombus is a quadrilateral.
  7. Finally, we have the class Rectangle, another subclass of the superclass Rhombus. Note that Rectangle redefines the area() method. This action is referred to as overriding as opposed to overloading, which we have seen before. The distinction between these two possibilities is discussed in section 11.5.

Start Lecture #20

Remark: Must discuss overriding (#7 above).

Remark: Briefly discuss lab 4 part 3.

A terminology comment. A subclass usually contains more information (data fields) than the superclass. It is called a subclass because each object in the subclass is also a member of the superclass. Thus the set of objects in the subclass can be considered a subset of the set of objects in the superclass.

Area of a Quadrilateral

quadrilateral

We mentioned above that there is a complicated formula for the area of a quadrilateral. Just for fun, not part of the course, I worked it out.

Assume the 4 points p1, p2, p3, and p4 are given in an order so that the resulting edges do not cross each other. I shall always assume the quadrilateral is convex as shown in the diagram on the right (but I suspect much of what follows would work for concave quadrilaterals as well). Draw one of the diagonals, say connecting p1 and p3.

It is easy to get the lengths of each side as the square root of delta-x squared + delta-y squared. So in the diagram we know all the sij.

Now the quadrilateral is composed of two triangles and we know the side lengths for each triangle. There is a cute formula for the area of any triangle with side lengths a, b, and c. Let s be the semiperimeter s=(a+b+c)/2, then the area of the triangle is the square root of s(s-a)(s-b)(s-c).

This analysis enables us to compute the complicated formula for the quadrilateral area mention above.

11.3 Using the super Keyword

Recall that this refers to the current object. In an analogous way super refers to the superclass of the current class. It is used to

11.3.1 Calling Superclass Constructors

When we create a new object, a constructor is called. Let's say that class Child is a child of the class Parent and we make a Child object child. The Child constructor is called and produces/initializes any relevant data fields in child.

But the Child object child contains, in addition to its own data, the data fields in Parent as well. How are these created/initialized?

The answer is that a constructor in the subclass invokes a constructor in the superclass. In our case, constructors in Child would invoke constructors in Parent.

How is this done?

One wrong way would be to have the child constructor invoke new Parent(). That would create a new Parent object rather than creating/initializing a part of the current Child object child.

Indeed it is an error to mention the superclass constructor inside a subclass; instead super() is used.

public class Rhombus extends Quadrilateral {
  private double sideLength;
  public Rhombus (Point p1, Point p2,
                  Point p3, Point p4) {
    super(p1, p2, p3, p4);
    sideLength = p1.distTo(p2);
  }
}

public class Quadrilateral extends GeometricObject {
  Point p1, p2, p3, p4;
  public Quadrilateral (Point p1, Point p2,
                        Point p3, Point P4) {
    this.p1 = p1;
    this.p2 = p2;
    this.p3 = p3;
    this.p4 = p4;
  }
}

On the right we see a part of an improved class for Rhombus and below it part of the base class Quadrilateral. Although not explicitly included in the class, a derived class inherits the fields from its base. Thus a Rhombus object has 4 Point fields that must be created/initialized by the constructor (as well as the double field sideLength.

Were there no inheritance involved, the rhombus constructor would have 4 assignment statements using this to define/initialize the four points. However, the points are not declared in Rhombus so should not be referenced by this. Instead we invoke super() to have a constructor in the base class Quadrilateral do the necessary work.

The idea is that the constructors in a derived class deal with the fields introduced in the derived class; dealing with the fields inherited from the base class is left to the base class constructors, furthering abstraction/encapsulation.

When super() is used in a constructor, it must be the first statement.

You might wonder why these same considerations don't apply to Quadrilateral as they did to Rhombus. After all, Quadrilateral is itself a derived class (its parent is GeometricObject).

The answer is that they do apply; a default super() has been supplied by Java, as will be explain in the next section.

11.3.2 Constructor Chaining

As we have seen a few lectures ago, one constructor can invoke another constructor in the same class. We have just seen that a constructor in a derived class can utilize super(). If a constructor in a derived class does neither of these actions, Java itself supplies a no-arg super() as the first statement of the constructor.

As a result of this automatic insertion of super() when needed, if A is derived from B, which in turn is derived from C, any A construct will, as its first action, invoke a B constructor, which, as its first action, will invoke an C constructor.

Thus the real actions will be performed in the order C, B, A, i.e., from base to derived. This naturally applies as well when there are more than 3 classes.

For example, consider the class tree on the right, which represents the parent-child relations in our geometry example.

geometry-tree

The process of applying constructors from base to derived is called constructor chaining.

11.3.3 Calling Superclass Methods

The keyword super can be used to reference superclass methods as well as superclass constructors. Normally the superclass method would be available in the subclass without any decoration. However, if the superclass and subclass both have a method with the same name, prepending super. references the method in the superclass whereas the naked name references the method in the subclass. We see an example in the next section

11.4 Overriding Methods

public class Parent {
  int f() { return 1; }
  int g() { return 10; }
}

public class Child extends Parent {
  int f() { return 100; }
  int h() { return f()+2*super.f()+g(); }
}

public class Test {
  public static void main(String[] args) {
    Parent p = new Parent;
    Child c  = new Child;
    System.out.println(c.h());
    System.out.println(c.f()+c.g());
  }
}

112
110

Consider the example on the right. The parent class defines two methods f() and g(). The child class defines two methods f() and h().

In the parent class (for example, inside f()), nothing about the child class is known. So f() means the f() in the parent, the same for g(), and there is no h().

In the child class the situation is more interesting since everything in the parent (that is not private) is known in the child. So g() means the g() in the parent and h() means the h() in the child, but what about f()?

The rule is that a naked f() means the f() in the child. However, the f() in the parent can be obtained as super.f(). That is why the first line printed, c.h(), is 100+2*1+10.

What about usage in the class Test, a client of the child class. In the client, super cannot be used so c.f() in the example invokes the f() in the child. Since g() occurs only in the parent c.g() refers to that g(), which explains why the second line printed is 100+10.

What about static Methods?

The example above used instance methods in both the parent and child. If a method f() in the child overrides f() in the parent you must have either both static or neither static.

If they are both static then

11.5 Overriding vs. Overloading

public class Base {
  public void f(double x) {
    System.out.printf("Base says x=%f\n", x);
  }
}
public class Derived {
  public void f(double x) {
    System.out.printf("Derived says x=%f\n", x);
  }
}
public class Test {
  public static void main(String[] Args) {
    Derived d = new Derived();
    d.f(5.0);
    d.f(5);
  }
}
Derived says x=5.000000
Derived says x=5.000000


public class Derived extends Base {
  public void f(int x) {
    System.out.printf("Derived says x=%d\n", x);
  }
}
Base says x=5.000000
Derived says x=5

It is important to understand the distinction between overriding, which we just learned, and overloading, which have seen previously.

The first frame on the right demonstrates overriding. Both the base class and the derived class define an instance method f(). Both f()'s have the same signature (i.e, the same number and types of parameters) and the same return type. In such a case, an object of the derived class sees only the method defined in the derived class.

In the example, both d.f(5.0) and d.f(5) invoke the f() in the child class. In the second case the 5 is coerced from int to double.

The second example is almost the same, but with a key difference. Both Base and Test are unchanged. The f() in Derived is changed so that it's parameter is of type int (and the printf() now uses %d not %f).

This tiny change has a large effect.

The f() in the derived class no longer has the same signature as the f() defined in the base. Thus, both are available (i.e., f() is overloaded).

For this reason the invocation f(5) invokes the f() in the derived class; whereas, the f(5.0) invokes the f() in the base.

11.A Quadrilateral and Friends (version 1)

geometry-tree

public class GeometricObject {
  protected String color = "blue";
}

public class Point extends GeometricObject {
  double x, y;
  public Point(double x, double y) {
    this.x = x;
    this.y = y;
  }
  double distTo (Point p) {
    return Math.sqrt(Math.pow(this.x-p.x,2) +
                     Math.pow(this.y-p.y,2));
  }
}

public class Quadrilateral extends GeometricObject {
  protected Point p1, p2, p3, p4;
  public Quadrilateral (Point p1,Point p2,Point p3,Point P4) {
    this.p1 = p1;
    this.p2 = p2;
    this.p3 = p3;
    this.p4 = p4;
  }
  double area() {
    return 123.4;     // A stub, should use complicated formula
  }
}

public class Rhombus extends Quadrilateral {
  private double sideLength;
  public Rhombus (Point p1, Point p2, Point p3, Point p4) {
    super(p1, p2, p3, p4);
    sideLength = p1.distTo(p2);
    if (sideLength!=p2.distTo(p3) || sideLength!=p3.distTo(p4)
         || sideLength!=p4.distTo(p1)) {
      System.out.println("Error: Rhombus with unequal sides");
      System.exit(0);       // an exception would be better
    }
  }
  public double getSideLength() {
    return sideLength;
  }
}

public class Rectangle extends Quadrilateral {
  private double width, height;
  public Rectangle(Point p1, Point p2, Point p3, Point p4) {
    super(p1, p2, p3, p4);
    width = p1.distTo(p2);
    height = p2.distTo(p3);
    // should check really a rectangle (how?)
  }
  public Rectangle(Point p1, Point p3) { // Parallel to axes
    super(p1, new Point(p3.x,p1.y), p3, new Point(p1.x,p3.y));
    width = Math.abs(p1.x-p3.x);
    height = Math.abs(p1.y-p3.y);
    // Nothing to check
  }
  public double getWidth() {
    return width;
  }
  public double getHeight() {
    return height;
  }
  public double area() {
    return width * height;
  }
  public void areaCheck() {
    System.out.printf("Rectangle says %f, but quad says %f\n",
              this.area(), super.area());
  }
}

public class TestQuad {
  public static void main (String[] args) {
    Point origin = new Point(0.0,0.0);
    Point p1 = new Point(0.0,0.0);
    Point p2 = new Point(1.0,0.0);
    Point p3 = new Point(1.0,1.0);
    Point p4 = new Point(0.0,1.0);
    Point p5 = new Point(5.0,5.0);
    Quadrilateral quad = new Quadrilateral (p1,p2,p3,p4);
    Rectangle rect1 = new Rectangle (p1,p2,p3,p4);
    Rectangle rect2 = new Rectangle (p1,p3);
    Rectangle rect3 = new Rectangle (origin, new Point(1.,4.));
    System.out.printf("rect1: width=%f, height=%f, color=%s\n",
              rect1.getWidth(), rect1.getHeight(), rect1.color);
    System.out.printf("rect2: width=%f, height=%f, color=%s\n",
              rect2.getWidth(), rect2.getHeight(), rect2.color);
    System.out.printf("rect3: width=%f, height=%f, color=%s\n",
              rect3.getWidth(), rect3.getHeight(), rect3.color);
    rect1.areaCheck();
    Rhombus rhom1 = new Rhombus(p1,p2,p3,p4);
    System.out.printf("rhom1 has side length=%f\n",
                      rhom1.getSideLength());
    Rhombus rhomBad = new Rhombus(p1,p2,p3,p5);
  }
}

rect1: width=1.000000, height=1.000000, color=blue
rect2: width=1.000000, height=1.000000, color=blue
rect3: width=1.000000, height=4.000000, color=blue
Rectangle says 1.000000, but quad says 123.400000
rhom1 has side length=1.000000

Let's look at a larger example. I have filled in some of the details for the geometric methods sketched above.

Recall the hierarchy of classes we have defined. It is illustrated on the right.

Geometric Object

The Geometric Object sits at the top of the hierarchy. As we see on the right it is a trivial class with only a single data field, color.

Point

Next we have the simple class Point. As is normally done in Cartesian geometry, a point is defined by two real numbers, the x and y coordinates. In our class these instance fields are doubles.

Several of the classes below will need to calculate the length of various lines, i.e., the distance between two points. Rather than define essentially the same method in Rhombus, Rectangle, etc., we define it here so that whenever we have two points, we can find the distance between them.

Quadrilateral

For us a quadrilateral is simply 4 points.

In this version of the code, I just gave a stub for the area of an arbitrary quadrilateral. The use of stubs during program development is fairly common. Later versions of the code have the full method.

Rhombus

A rhombus is a quadrilateral with all four sides equal. Since the lengths are equal a rhombus has a well define side length, which the class calculates and makes available via an accessor method. The constructor checks that the side lengths are in fact equal.

Note that, in this implementation the 4 vertices of a rhombus, are components of the underlying quadrilateral. Hence super() is used to give the points their final value.

Rectangle

A rectangle is a quadrilateral with each angle 90 degrees. The first constructor does not check this condition, a clear weakness.

The second constructor, can be used in the common case where the sides of the rectangle are parallel to the coordinate axes. In this case only a pair of points is needed; they for opposite vertices of the rectangle and the other two vertices are calculated from these two, which guarantees that do have a rectangle.

Rectangles have well defined widths and heights, which are calculated by the constructor and made available via accessors methods.

Also supplied is a debugging method areaCheck() that compares the area calculated by the Rectangle class, with the more general area calculation in the parent Quadrilateral class. At this stage of development, they do not match since Quadrilateral.area() is just a stub..

Testing

Testing class hierarchies is a slow process. You have to test each class separately and then need to worry about interactions between them.

Weaknesses

One weakness (really a bug) in this code is that the coordinates of a Point are declared public and thus can be changed by the user. This could mean that the side length is wrong and that the rhombus isn't really a rhombus.

Another problem is that when we check to see if the sides of rhombus are of equal length, we check for exact equality, which is not a good idea for floating point numbers.

Note that, if the points in the quadrilateral are given in the wrong order, the quadrilateral's sides will intersect each other. A better program would check that the sides do not intersect. We will ignore this subtlety and always make sure to give the points in the right order.

Questions

Where in the class tree should we put a new class Circle?

Where in the class tree should we put a new class Square?

Where in the class tree should we put a new class Ellipse?

Start Lecture #21

Remark: Mention possible extra credit to learn Java graphics and then extend our Quadrilateral and Friends.

11.6 The Object Class and its toString Method

You would think from looking at a class definition beginning

  public class TopLevel {

with no extends clause that this class has no parent. That, however, is wrong.

Any class without an extends clause, whether defined in the Java library or by you and me, actually extends the built in class java.lang.Object.

Hence, any method defined in Object is available to all classes, unless it is overloaded or overridden.

One interesting instance method defined in Object is toString(), which when applied to an object, produces a string describing the object. The actual description given is the class name, followed by @ followed by the memory address of the object. The class name is certainly useful, the memory address at least enables us to distinguish one object from another.

One advantage of having a toString() method defined for all objects is that other methods can count on its existence. For example the various print methods (e.g., printf(), println(), etc) use this to coerce objects to strings. Thus if obj is any object at all,

  printf("The object is %s\n", obj);

is guaranteed to work.

I use the automatic application of toString() in the next section and in the 2nd version of the geometry classes below.

11.7 Polymorphism

We have already seen encapsulation/abstraction and inheritance, which are major constituents of object oriented programming. We now turn our attention to polymorphism (and dynamic binding/dispatch), the third major constituent).

public static void printColor(GeometricObject geoObj) {
  System.out.printf("The color of %s is %s\n",
                 geoObj, geoObj.getColor());
  }
}

Say we are given the geometry classes and want to print the color. We don't want to add this print to the geometry classes since it is useful only for us and not to all geometry users. We do modify GeometricObject to add a get method for the color and then write ourselves the simple method on the right. Now if we call the method with any GeometricObject all is well.

But we actually don't have variables of type GeometricObject; instead we have variables of various subtypes such as Rhombus or Point. Thus the natural call PrintColor(rhom1) would be a type error, we are passing a Rhombus as the argument and the method has a GeometricObject as its parameter.

But no it is not an error!

A variable of a supertype (the type defined by a superclass) can refer to an object of any of its subtypes). This concept of polymorphism can also be stated in terms of objects as an object of a subclass can be used anywhere that an object of its superclass could be used .

In section 11.B we see an enhanced version of the geometry example that includes printColor() and a few polymorphic calls.

11.8 Dynamic Binding

How should we implement printArea() in our geometry classes?

It would be a one-line method in the Rectangle class, namely

    void printArea() { System.out.printf("The area of %s is %f\n", this, this.area()); }

(Recall that the Rectangle class already has the method area() and toString(), inherited from Object, is invoked automatically by the %s in printf().

But the exact same (character for character) one-line method printArea() would be perfect in the class Rhombus since Rhombus inherits area() from Quadrilateral and toString() from Object.

Similarly, the exact same method would work for Quadrilateral.

Once we put a trivial area() method in Point (it always returns 0), the exact same printArea() method would work there as well.

So is that the solution, cut-and-paste the identical method in all these classes? It would work, but it sure seems ugly.

Try 1

Since GeometricObject is at the top of our geometry class hierarchy, why not put printArea() there? Since all the geometric classes extend GeometricObject, this has the effect of placing printArea in all of them.

Hence when, for example, we write rect1.printArea(), we will invoke the printArea() written in GeometricObject. Since the object in question is rect1, when printArea() issues this.area(), the area() in Rectangle will be called as desired.

Success!
The code is in section 11.B.

Try 2

Let's say that we don't want to add printArea() to any of the geometry classes since it is somewhat special purpose. Our clients will probably have different ways of printing. So we write the following one-line method in the TestQuad class.

  static void myPrintArea(GeometricObject geoObj) {
    System.out.printf("My area of %s is %f\n", geoObj, geoObj.area());
  }

Then, in the main() method, we write myPrintArea(rhom1).

At first glance this looks unlikely to compile since we have a type mismatch. The argument to myPrintArea() is a Rhombus, but the parameter is a GeometricObject. However, we know that polymorphism will save us. The parameter of type GeometricObject can legally point to an object of type Rhombus.

At second glance, it looks like it will work, but poorly. When compiling myPrintArea(), Java will see geoObj.area() where geoObj is of type GeometricObject and will thus always invoke the area() method in the class GeometricObject no matter what class the actual argument is in. This is bad, we have different formulas for the area of points, general quadrilaterals, and rectangles.

Failure.

Wrong!
It actually works great. We now have to understand how.

Declared Type vs. Actual Type

As we know well, Java requires that, prior to using a variable, we must declare it to be of a specific type (int, Object, Point, etc).

Previously, we have called the type in the declaration, simply the type of the variable. But now, in the presence of objects and polymorphism, we need to make a finer distinction and refer to the type in the declaration as the declared type of the variable.

When the declared type is a primitive such as int or char, there is very little more to say. The variable will always contain a value of its (declared) type. Recall that a double x=3; first converts the 3 to a double, which is then stored in x. The variable always contains a value of its (declared) type.

When the declared type is class, (e.g., String, or Rectangle), the situation is more interesting. First, we remember that a variable of declared type Quadrilateral never contains a Quadrilateral. It often does contain a reference to a Quadrilateral, but never contains the object itself (reference vs. value semantics).

Moreover, a variable of declared type Quadrilateral can, due to polymorphism, at times contain a reference to a Quadrilateral, and at other times a reference to a Rectangle, and at other times a reference to a Rhombus.

The type of the object to which a variable refers is called its actual type. Note that the actual type of a variable can change during execution of the program.

How the Second Implementation Works

Recall the situation. We have a method myPrintArea() that has one parameter geoObj, whose declared type is GeometricObject. We call this method in several places with arguments of various geometric types. myPrintArea() contains a call of geoObject.area(). We previously concluded that, since the type of geoObject is GeometricObject, the call would always be to the area() method in GeometricObject.

We are wiser now and can question this conclusion. If myPrintArea() was called with an argument that referenced a Rectangle, then geoObj will indeed have declared type GeometricObject, but it will have actual type Rectangle. Thus the question becomes, does the method invocation geoObj.area() select the area() method to call based on the declared type of geoObj or on its actual type. Another wording is does geoObj.area() use static dispatch (declared type) or dynamic dispatch (actual type).

The answer in Java is that is uses dynamic dispatch, and hence our second implementation does indeed work. This situation is illustrated in section 11.B.

In C++ static dispatch is used by default but dynamic dispatch can be chosen by declaring the method to be virtual.

11.9 Casting Objects and the instanceOf Operator

Recall the situation for converting one primitive type to another. Java performs safe type conversions (called coercions) automatically, but will not perform unsafe conversions, unless told told by an explicit cast.

int    i1=1,  i2=2,  i3=3;
double d1=1., d2=2., d3=3.E50;
d1 = i1;
d1 = (double)i1;
// i2 = d2;      Compile-time error
i2 = (int)d2; // Fine
i3 = (int)d3; // Gives wrong answer

Referring to the code on the right we see illustrations of the various conversion possibilities

Upcasting and Downcasting

geometry-tree

We now want to understand the corresponding actions for objects in the class hierarchy. In this situation the terminology is upcasting and downcasting.

The familiar diagram on the right motivates the names.

Upcasting refers to a conversion from a class lower in the diagram to a class higher in the diagram (really there is the class Object above GeometricObject).

Downcasting refers to a conversion from a class higher in the diagram to one lower in the diagram.

The code below is the rough analogue for objects of the code above for primitive types. Note that the variable p2 after initialization has declared type Parent, but actual type Child. This situation is not present with primitive types since those variables always contain a value of the declared type of the value. In contrast variables of any object type contain a reference to an object whose type can be a subtype of the declared type of the variable.

public class Parent {...}
public class Child extends Parent {...}
Parent p1 = new Parent();
Parent p2 = new Child(); // NOT Parent()
Parent p3 = new Parent();
Child  c1 = new Child();
Child  c2 = new Child();
Child  c3 = new Child();
p1 = c1;
p1 = (Parent) c1;
//   c2 = p2;        Compile-time error
c2 = (Child)p2;   // Fine
c3 = (Child)p3;   // Run-time error

Referring to the code we see

The general rules are (compare with primitive types above):

Using the instanceof Operator to Avoid the Run-time Error

We have just seen that downcasting with an explicit cast will compile but might fail at run-time depending on the actual type of the variable at that time.

What should we do to avoid this run-time error?

public class Parent {...}
public class Child extends Parent {...}
Parent p;
Child  c;
// much code involving p

if (p instanceof Child)
  c = (Child) p;

The code on the right gives one technique. The omitted code could be complicated and, depending on various values that are known only at run time, might result in the actual type of p being either parent or child.

We wish to do the downcast only if legal, i.e., only if the actual type of p is Child (or a descendant of Child). The instanceof operator is defined for exactly this purpose it returns true if the object on the left is in the class on the right (or a descendant of that class).

Who Needs All This?

Why would I want to upcast and why would I ever want to try to downcast to something not matching the actual type of the variable.

Consider some sort of container class, i.e., a class each of whose instance is some container of GeometricObject's. For some examples consider

Each of these three examples illustrates a container for Object's. Let's say in some geometry program you have the array geoObjArr onto which you have placed various geometric objects, some points, some rhombuses, etc.

If you try something like geoObjArr[i].sideLength, you will get a compile-time error since geoObjArr[i] has declared type GeometricObject, which has no sideLength field.

A naked assignment to a variable of declared type Rhombus will not compile, so you need an explicit cast to do the downconvertion. But this will give a run-time error if the actual type of geoObjArr[i] is not a Rhombus (or a descendant).

Thus you need an if statement with instanceof as shown above.

Start Lecture #22

11.B Quadrilateral and Friends (version 2)

Compilable Code


geometry-tree
public class GeometricObject { private String color = "blue"; public String getColor() { return color; } public void setColor(String color) { this.color = color; } public void printArea() { System.out.printf("The area of %s is %f\n", this, this.area()); } public double area() { System.out.printf("Error area not overridden in %s\n", this); return 0; } }

A compilable and less cramped version of this code is here. It contains some features that we have not yet learned.

GeometricObject

  1. The color is now private. This visibility means that no other class can access the color. However, we do wish to give full read-write access to all subclasses and to all clients (i.e., to all classes).
  2. The getColor() accessor method (apparently, also referred to as a getter method) supplies the desired read-only access.
  3. Similarly setColor() supplies write access. Such methods are called mutators (and apparently also called setters.
  4. The printArea() method should not be invoked for a plain geometricObject but is define here so that it is available in subclasses where it is applicable.
  5. A general geometricObject does not have a well-defined area. The area() method should be abstract, but we have not yet learned how.
  6. Supplying both get- and set- methods seems the same as declaring color to be public. There is a difference, however. These two methods promise our clients that they can access the color using the name of the color, but in the future we may wish to store the color in a different format (e.g., RGB). As defined here we have that flexibility since we know no other class can assume that color is a string. If we do change the format, we must change the accessor and mutator methods to convert the user's color name to the internal RGB format.

Point

public class Point extends GeometricObject {
  protected double x, y;
  public Point(double x, double y) {
    this.x = x;
    this.y = y;
  }
  public double distTo (Point p) {
    return Math.sqrt(Math.pow(this.x-p.x,2)+
                     Math.pow(this.y-p.y,2));
  }
  public double area() {
    return 0;
  }
}
  1. In this class the fields are protected so that subclasses, but not clients can access the fields. It would be good to add accessor (but not mutator) methods for these fields so that clients would have read-only access.
  2. An area() method has been added.
  3. Remember that a Point has a Color, inherited from GeometricObject. Although you don't see a reference to super() inside Point, Java has supplied a call to the no-arg constructor.
  4. It would be good to add another constructor, one that takes a Point as parameter.

Quadrilateral

public class Quadrilateral extends GeometricObject {
  protected Point p1, p2, p3, p4;
  public Quadrilateral (Point p1, Point p2, Point p3, Point p4) {
    // should check pts form convex quadrillateral in this order
    this.p1 = p1;
    this.p2 = p2;
    this.p3 = p3;
    this.p4 = p4;
  }
  public double area() { // use triangle semiperimeter formula
    double diag  = p1.distTo(p3);
    double s12   = p1.distTo(p2);
    double s23   = p2.distTo(p3);
    double s34   = p3.distTo(p4);
    double s41   = p4.distTo(p1);
    double semi1 = (s12 + s23 + diag) / 2;
    double semi2 = (diag + s34 + s41) / 2;
    return Math.sqrt(semi1*(semi1-s12)*(semi1-s23)*(semi1-diag))+
           Math.sqrt(semi2*(semi2-s34)*(semi2-s41)*(semi2-diag));
  }
}
  1. The fields are protected and should have an accessor method, possibly on that returns an array of four Points.
  2. The promised method to compute the area of a general quadrilateral has been added.
  3. Again, it would be good to have a constructor that accepts a quadrilateral as its parameter.
  4. More difficult would be to have the original constructor check that the points are given in the correct order. For a start you can find the intersection of the line p1-p2 with the line p3-p4. This intersection should not be between p1 and p2. If it is between them, swap the points p1 and p2. That should fix it.

Rhombus

public class Rhombus extends Quadrilateral {
  private double sideLength;
  public Rhombus (Point p1, Point p2, Point p3, Point p4) {
    super(p1, p2, p3, p4);
    sideLength = p1.distTo(p2);
    if (sideLength!=p2.distTo(p3) || sideLength!=p3.distTo(p4) ||
          sideLength!=p4.distTo(p1)) {
      System.out.println("Error: Rhombus with unequal sides");
      System.exit(0);       // an exception would be better
    }
  }
  public double getSideLength() {
    return sideLength;
  }
}
  1. This time the field is private, instead of protected. Since there are no subclasses of Rhombus, these two visibility modifiers have the same effect.
  2. An accessor again enables all classes to read the value.
  3. Remember that, although you can't see them, the Rhombus has four Points as fields too, inherited from Quadrilateral. The constructor uses super() to let the Quadrilateral constructor initialize the Point's.
  4. The constructor checks that all the sides are equal. The proper response to failure would be to raise an exception.

Rectangle

public class Rectangle extends Quadrilateral {
  private double width, height;
  public Rectangle(Point p1, Point p2, Point p3, Point p4) {
    super(p1, p2, p3, p4);
    width = p1.distTo(p2);
    height = p2.distTo(p3);
    // should check that all angles are right angles
  }
  public Rectangle(Point p1, Point p3) { // parallel to axes
    super(p1, new Point(p3.x,p1.y), p3, new Point(p1.x,p3.y));
    width = Math.abs(p1.x-p3.x);
    height = Math.abs(p1.y-p3.y);
    // Nothing to check
  }
  public double getWidth() {
    return width;
  }
  public double getHeight() {
    return height;
  }
  public double area() {
    return width * height;
  }
}
  1. Don't forget that a Rectangle is a Quadrilateral and hence also a GeometricObject. Thus a Rectangle has a number of fields and methods beyond what is shown in its own class.
  2. A rectangle has a width and height. We define the width as going from the first to the second point.
  3. The first constructor, which is given 4 points, should check that the angles are all 90 degrees, for example by calculating the slopes of the sides.
  4. The second constructor, which produces a rectangle with sides parallel to the axes, only needs two points and by construction has all right angles.
  5. Since Rectangle extends Quadrilateral, the former inherits an area() method from the later.
  6. However, the area of a rectangle is much easier and faster to compute as base*height, so we override (not just overload) the general method with a one specialized for rectangles.

Testing

The summary is that there is a great deal to test, much more than is shown. As mentioned in class, testing / correcting / maintaining a serious software project typically requires much more time and effort than designing and programming the system

The code on the right is inadequate for full testing, but does illustrate some of the Java features we just learned.

TestQuad

public class TestQuad {
  public static void main (String[] args) {
    Point origin = new Point(0.0,0.0);
    Point p1 = new Point(1.0,0.0);
    Point p2 = new Point(1.0,1.0);
    Point p3 = new Point(0.0,1.0);
    Point p4 = new Point(5.0,5.0);
    Quadrilateral quad1 = new Quadrilateral
             (origin, p3, new Point(5.,0.), new Point(5.,-8.));
    Rectangle rect1 = new Rectangle (origin,p1,p2,p3);
    Rectangle rect2 = new Rectangle (origin, new Point(1.0,4.0));
    Rhombus rhom1 = new Rhombus(origin,p1,p2,p3);
    p1.printArea();
    printColor(rhom1);  // Polymorphic call (section 11.7)
    printColor(rect1);  // Polymorphic call
    quad1.printArea();  // Inherited method (section try 1)
    rect2.printArea();  // Inherited method
    myPrintArea(rhom1); // Dynamic binding/dispatch (sec try 2)
    myPrintArea(p4);    // Dynamic binding/dispatch
    quad1 = rect1;      // Implicit casting (section 11.9)
    quad1.printArea();  // Dynamic binding/dispatch
  }
  public static void printColor(GeometricObject geoObj) {
    System.out.printf("The color of %s is %s\n",
                      geoObj, geoObj.getColor());
  }
  public static void myPrintArea(GeometricObject geoObj) {
    System.out.printf("My area of %s is %f\n",
                      geoObj, geoObj.area());
  }
}
// Output
The area of Point@4830c221 is 0.000000
The color of Rhombus@2ce83912 is blue
The color of Rectangle@41fae3c6 is blue
The area of Quadrilateral@3e7ffe01 is 22.500000
The area of Rectangle@44fd13b5 is 4.000000
My area of Rhombus@2ce83912 is 1.000000
My area of Point@4318f375 is 0.000000
The area of Rectangle@41fae3c6 is 1.000000
  1. The first non-constructor invoked prints the area of a Point. Naturally the area is 0.
  2. More interesting is the Point@4830c221 that is printed as the %s conversion of the Point itself. The (hexadecimal) number after the @ is the memory address of the point. The only use we can make of the address, is to tell if two references are to the same point.
  3. Printing colors is polymorphic. We pass a Rhombus or Rectangle to a method that expects a GeometricObject. All is well since an object of a subtype can be used anywhere an object of a supertype is needed, a form of upcasting.
  4. One way to print areas is to use printArea() defined in GeometricObject, and inherited by all its subclasses.
  5. When quad1.printArea() invokes printArea(), the internal call this.area() invokes area() in Quadrilateral. Similarly rect2.printArea() calls area() in Rectangle.
  6. Another way to print areas is to use myPrintArea() in the client. Rhomb1 refers to a Rhombus and this is copied to the geoObj parameter, a legal (upcasting) assignment. Due to dynamic dispatch, geoObj.area() invokes area() in Rhombus (not in GeometricObject).
  7. Similarly myPrintArea(p4) eventually invokes area() in Point.
  8. Finally, after quad1 is assigned a Rectangle, quad1.printArea() invokes area() in Rectangle.

11.B.1 Adding a Circle Class

On the board let's develop a Circle class with data fields center and radius.

  1. The normal constructor is given the center point and the radius.
  2. Another constructor just gets the center and gives a default radius of 1.
  3. Another constructor just gets the radius and supplies the center at the origin.
  4. The final constructor gets neither and supplies both defaults.
  5. Circles are pink, not blue.
  6. Have an instance method for area.
  7. Have a class method that returns a largest circle created so far.

Homework: 11.1. UML diagram not required. The area formula they references is the same one I used in the Quadrilateral class. The filled data field is in the book's GeometricObject but not in ours. You may ignore it.

Homework: Use cut and paste to extract our geometry classes from the notes. Better is to get the compilable and neater version linked at the beginning of section 11.B. Add your Triangle class from the homework above, and the Circle class we developed to these files and incorporate your tests of Triangle and Circle into our TestQuad (which should be named TestGeom).

11.10 The Object's equals() Method

public boolean equals (Object obj) {
  return (this == obj);
}

String s1= new String("xy");
String s2= new String("xy");
System.out.printf("%b %b\n",
                  s1==s2, s1.equals(s2));

public boolean equals(Object obj) {
  if (obj instanceof Circle)
    return radius == ((Circle)obj).radius;
  return false;
}

In addition to toString(), which we learned earlier, the Object class has an instance method equals(), shown on the right.

Note carefully that obj1.equals(obj2) checks if the variables are ==, which means that both variable refer to the same object. Often you want equals() to return true if the two objects have the same contents. For example the String class overrides equals() so that the middle code on the right prints false true.

If the bottom code is placed in the Circle class then, Circle c1,c2; with equal radii will result in ci.equals(c2) yielding true.

You should do that if you try the extra credit.

An Illustrative Technical Point: Overriding vs. Overloading

class GeometricObject {
  // Fields and methods omitted
}
class Circle extends GeometricObject {
  double radius;
  public Circle (double radius) {
    this.radius = radius;
  }
  public boolean equals (Circle c) {
    return radius == c.radius;
  }
  // Other fields and methods omitted
}
public class TestOverload {
  public static void main(String[] args) {
    GeometricObject g1 = new Circle(11.0);
    GeometricObject g2 = new Circle(11.0);
    Circle  c1         = new Circle(11.0);
    Circle  c2         = new Circle(11.0);
    System.out.printf("c1.equals(c2)=%b\n", c1.equals(c2));
    System.out.printf("c1.equals(g2)=%b\n", c1.equals(g2));
    System.out.printf("g1.equals(c2)=%b\n", g1.equals(c2));
    System.out.printf("g1.equals(g2)=%b\n", g1.equals(g2));
  }
}
javac TestOverload.java  && java TestOverload
c1.equals(c2)=true
c1.equals(g2)=false
g1.equals(c2)=false
g1.equals(g2)=false


class GeometricObject {
  // Fields and methods omitted
}
class Circle extends GeometricObject {
  double radius;
  public Circle (double radius) {
    this.radius = radius;
  }
  public boolean equals (Object o) {
    if (o instanceof Circle)
      return radius == ((Circle) o).radius;
    return false;
  }
  // Other fields and methods omitted
}
public class TestOverride {
  public static void main(String[] args) {
    GeometricObject g1 = new Circle(11.0);
    GeometricObject g2 = new Circle(11.0);
    Circle  c1         = new Circle(11.0);
    Circle  c2         = new Circle(11.0);
    System.out.printf("c1.equals(c2)=%b\n", c1.equals(c2));
    System.out.printf("c1.equals(g2)=%b\n", c1.equals(g2));
    System.out.printf("g1.equals(c2)=%b\n", g1.equals(c2));
    System.out.printf("g1.equals(g2)=%b\n", g1.equals(g2));
  }
}
javac TestOverride.java  && java TestOverride
c1.equals(c2)=true
c1.equals(g2)=true
g1.equals(c2)=true
g1.equals(g2)=true

The equals() code on the right, looks simpler that the one above and seems to do the same job: When two circles are compared, their radii are checked.

The difference is that the equals() in 11.10 overrides the equals() defined in Object since both have the same signature. In contrast the equals() on the right has a different signature and thus we have two overloaded implementation of equals().

In the overloaded case, the choice of which equals() to invoke depends on the declared type of the argument. In the overriding case the choice depends on the actual type of the argument.

Look at the main() procedure. Four variables are declared, two GeometricObjects and two Circles. Each of the variables is initialized to a Circle with radius 11.0.

Since each variable refers to a different object, they would all yield false if compared using ==.

However, we are comparing them using the equals() method.

The question is which equals() method, the one in Object, which requires that the same object is referenced, or the one in Circle, which requires only that the radii are equal.

Since these two methods are overloading (not overriding, it is the declared types of the variables that are relevant and only the first invocation c1.equals(c2) uses the equals defined in circle.

If we change the definition of equals() in Circle to the one in 11.10, the situation changes dramatically. For clarity, we also changed the class name to TestOverride

Now the two equals definitions have the same signature so the one in Circle overrides the one in GeometricObject.

As a result the choice of method implementation defends on the actual types (of the referenced objects), not on the declared types (of the referencing variables).

Since all four variables g1, g2, c1, c2 references objects with actual type Circle, each of the four invocations, executes the equals in Circle. Thus only the radii are compared and the comparison yields true in all four cases.

Start Lecture #23

11.11 The ArrayList Class

It is straightforward to create an array of Object's; just do it. The fancy ArrayList permits all sorts of extra operations. If you look on http:java.sun.com, you will find that there are even more goodies than the book suggests, as well as some nifty performance guarantees.

Another example of the justly-deserved fame accorded the Java libraries.

11.12 A Custom Stack Class

Shows how to use the ArrayList class to design a stack class that holds arbitrary objects.

11.13 The protected Data and Methods

Since we will not be studying packages (and they are primarily useful for large projects and libraries, which we are not writing), for us the rules are simple.

For a class itself, the only legal modifiers are public and the default (no modifier)

Most of our classes will be public.

For members of a class (i.e., for fields and method)

11.14 Preventing Extending and Overriding

We have seen the final keyword use for constants. It has other analogous uses as well.

  1. A final class cannot be extended.
  2. A final method cannot be overridden.

Chapter 12 Gui Basics

May be helpful for the extra credit project

Chapter 13 Exception Handling

13.1 Introduction

We have seen several places where errors can occur that can be detected only at run time. For example, a PrintWriter might be trying to write a file for which the user doesn't have sufficient privileges. Another example, this one from our geometry methods occurs when the Rhombus constructor detects that the given points do not yield a quadrilateral with all sides equal.

In Java, situations like this are often handled with exceptions, which we now study.

13.2 Exception-Handling Overview

double s12 = p1.distTo(p2);
if (s12!=p2.distTo(p3) || s12!=p3.distTo(p4) || s12!=p4.distTo(p1)) {
  System.out.println("Error: Rhombus with unequal sides");
  System.exit(0);       // an exception would be better
}
sidelength = s12;

double s12 = p1.distTo(p2);
try {
  if (s12!=p2.distTo(p3) || s12!=p3.distTo(p4) || s12!=p4.distTo(p1))
    throw new Exception("Rhombus with unequal side");
  sidelength = s12;
}
catch (Exception ex) {
  System.out.println("Error: Rhombus with unequal sides");
  System.exit(0);
}

The top frame on the right shows (a slight variant of) the body of the Rhombus constructor.

The frame below it shows one way to accomplish the same task using an exception. There are four Java terms introduced try, throw, catch, and Exception.

try and catch each introduce a block of code, which are related as follows.

In the example code, if all the side lengths are equal, sidelength is set. If they are not all equal an exception in the class Exception is thrown, sidelength is not set, an error message is printed and the program exits.

This behavior nicely mimics that of the original, but is much more complicated. Why would anyone use it?

13.3 Exception-Handling Advantages

The answer to the above question is that using exceptions in the manner of the previous section to more-or-less exactly mirror the actions without an exception is not a good idea.

Remember the problem with the original solution. The author of the geometry package does not know what the author of the client wants to do when an error occurs (try to forget that I am the author of both). Without any such knowledge the package author terminated the run as there was no better way to let the client know that a problem occurred.

The better idea is for the package to detect the error, but let the client decide what to do.


std-rhomb


 public Rhombus (Point p, double sideLength, double theta) {
   super (p,
          new Point(p.x+sideLength*Math.cos(theta),
                    p.y+sideLength*Math.sin(theta)),
          new Point(p.x+sideLength*(Math.cos(theta)+1.),
                    p.y+sideLength*Math.sin(theta)),
          new Point(p.x+sideLength, p.y));
   this.sideLength = sideLength;
 }
 
 
 double s12 = p1.distTo(p2);
 if (s12!=p2.distTo(p3) || s12!=p3.distTo(p4) || s12!=p4.distTo(p1))
   throw new Exception ("Rhombus with unequal sides.");
 sideLength = s12;
 
 
 try {
   rhom1 = new Rhombus(origin,origin,p2,p3);
 }
 catch (Exception ex) {
   System.out.printf("rhom1 error: %s  Use unit square.\n",
                     ex.getMessage());
   rhom1 = new Rhombus (origin, 1.0, Math.PI/2.0);
 }

The first step is to add a rhombus constructor having parameters a point, a side-length, and an angle and producing the rhombus shown in the diagram on the right (if Θ=Π/2, the rhombus is a square).

The constructor itself is in the 2nd frame.

This enhancement has nothing to do with exceptions and could have been there all along. You will see below how this standard rhombus is used when the client mistakenly attempts to construct an invalid rhombus.

The next step, shown in the 3rd frame, is to have the regular rhombus constructor throw an exception, but not catch it.

The client code is in the last frame. We see here the try and catch. In this frame the client uses the original 4-point rhombus constructor, but the points chosen do not form a rhombus. The constructor detects the error and raises (throws in Java-speak) an exception. Since the constructor does not catch this exception, Java automatically re-raises it in the caller, namely the client code, where it is finally caught. This particular client chooses to fall back to a unit square.

It is this automatic call-back that exceptions provide that the original code does not.

Remark: Lab 5 Assigned; due 1 May 2012

Remark: I think the write-up for the extra credit is finished. Let me know if you find that something is missing.

13.A Quadrilateral and Friends (version 3)

There is not much difference between versions 2 and 3. A compilable, less cramped implementation is here.

public abstract class GeometricObject { // Will learn about abstract
  protected String color = "blue";
  public void printArea() {
    System.out.printf("The area of %s is %f\n", this, this.area());
  }
  public String getColor() {
    return color;
  }
  public void setColor(String color) {
    this.color = color;
  }
  public abstract double area();        // Must be overridden
  }
}

public class Point extends GeometricObject {
  protected double x, y;
  public Point(double x, double y) {
    this.x = x;
    this.y = y;
  }
  public double distTo (Point p) {
    return Math.sqrt(Math.pow(this.x-p.x,2)+Math.pow(this.y-p.y,2));
  }
  public double area() {
    return 0;
  }
  public double getX() {
    return this.x;
  }
  public double getY() {
    return this.y;
  }
}

public class Quadrilateral extends GeometricObject {
  protected Point p1, p2, p3, p4;
  public Quadrilateral (Point p1, Point p2, Point p3, Point p4) {
    // should check these points for a convex quadrillateral in this order
    this.p1 = p1;
    this.p2 = p2;
    this.p3 = p3;
    this.p4 = p4;
  }
  public double area() { // diagonal gives 2 triangles; use semiperimeter formula
    double diag  = p1.distTo(p3);
    double s12   = p1.distTo(p2);
    double s23   = p2.distTo(p3);
    double s34   = p3.distTo(p4);
    double s41   = p4.distTo(p1);
    double semi1 = (s12 + s23 + diag) / 2;
    double semi2 = (diag + s34 + s41) / 2;
    return Math.sqrt(semi1 * (semi1-s12) * (semi1-s23) * (semi1-diag))  +
        Math.sqrt(semi2 * (semi2-s34) * (semi2-s41) * (semi2-diag));
  }
}

public class Rhombus extends Quadrilateral {
  private double sideLength;
  public Rhombus (Point p1, Point p2, Point p3, Point p4) throws Exception {
    super(p1, p2, p3, p4);
    double s12 = p1.distTo(p2);
    if (s12!=p2.distTo(p3) || s12!=p3.distTo(p4) || s12!=p4.distTo(p1))
      throw new Exception ("Rhombus with unequal sides.");
    sideLength = s12;
  }
  public Rhombus (Point p, double sideLength, double theta) {
    super (p,
           new Point(p.x+sideLength*Math.cos(theta), p.y+sideLength*Math.sin(theta)),
           new Point(p.x+sideLength*(Math.cos(theta)+1.), p.y+sideLength*Math.sin(theta)),
           new Point(p.x+sideLength, p.y));
    this.sideLength = sideLength;
  }
  public double getSideLength() {
    return sideLength;
  }
}

public class Rectangle extends Quadrilateral {
  private double width, height;
  public Rectangle(Point p1, Point p2, Point p3, Point p4) {
    super(p1, p2, p3, p4);
    width = p1.distTo(p2);
    height = p2.distTo(p3);
    // should check that all angles are right angles via slopes neg recip
    // or check both widths equal, both heights equal, both diags equal
  }
  public Rectangle(Point p1, Point p3) { // Sides parallel to the axes
    super(p1, new Point(p3.x,p1.y), p3, new Point(p1.x,p3.y));
    width = Math.abs(p1.x-p3.x);
    height = Math.abs(p1.y-p3.y);
    // Nothing to check
  }
  public double getWidth() {
    return width;
  }
  public double getHeight() {
    return height;
  }
  public double area() {
    return width * height;
  }
}

public class Circle extends GeometricObject {
  private Point center;
  private double radius;
  private static double maxRadius = -1;
  private static Circle maxCircle = null;
  public Circle(Point center, double radius) throws Exception {
    if (radius < 0)
        throw new Exception ("Circle with negative radius.");
    circleNoException(center, radius);
  }
  private void circleNoException(Point center, double radius) {                 
    this.center = center;
    this.radius = radius;
    this.color  = "pink";
    if (radius > maxRadius) {
      maxRadius = radius;
      maxCircle = this;
    }
  }
  public Circle(double radius) throws Exception{
    this(new Point(0.,0.), radius);
  }
  public Circle(Point center) { // No exception possible
    circleNoException(center, 1.0);
  }
  public Circle() { // No exception possible
    circleNoException(new Point(0.,0.), 1.0);
  }
  public Point getCenter() {
    return this.center;
  }
  public double getRadius() {
    return this.radius;
  }
  public double area () {
    return Math.PI * radius * radius;
  }
  public static Circle largestCircle() {
    if (maxRadius < 0)
      System.out.println("No circles created, so maximum circle is null");
    return maxCircle;
  }
  public boolean equals(Object obj) {
    if (obj instanceof Circle)
      return radius == ((Circle)obj).radius;  // Only radius counts
    return false;
  }
}

public class TestQuad {
  public static void main (String[] args) throws Exception {
    Point origin = new Point(0.0,0.0);
    Point p1 = new Point(1.0,0.0);
    Point p2 = new Point(1.0,1.0);
    Point p3 = new Point(0.0,1.0);
    Point p4 = new Point(5.0,5.0);
    Quadrilateral quad1 = new Quadrilateral(origin, p3, new Point(5.,0.), new Point(5.,-8.));
    Rectangle rect1 = new Rectangle (origin,p1,p2,p3);
    Rectangle rect2 = new Rectangle (origin, new Point(1.0,4.0));
    Rhombus rhom1;
    try {
      rhom1 = new Rhombus(origin,origin,p2,p3);
    }
    catch (Exception ex) {
      System.out.printf("rhom1 error: %s  Using unit square.\n", ex.getMessage());
      rhom1 = new Rhombus (origin, 1.0, Math.PI/2.0);
    }
    p1.printArea();
    printColor(rhom1);      // Polymorphic call (section 11.7)
    printColor(rect1);      // Polymorphic call
    quad1.printArea();      // Inherited method (section first try)
    rect2.printArea();      // Inherited method
    myPrintArea(rhom1);     // Dynamic binding/dispatch (section second try)
    myPrintArea(p4);        // Dynamic binding/dispatch
    quad1 = rect1;      // implicit casting (section 11.9)
    quad1.printArea();      // dynamic binding/dispatch
    Circle c1 = new Circle();
    Circle c2 = new Circle(2.);
    Circle c3 = new Circle();
    System.out.println(c3.equals(c1));
    Circle c = Circle.largestCircle();
    c.printArea();
    System.out.println(c.getColor());
    GeometricObject geo1 = new Rectangle(origin, p2);
    GeometricObject geo2 = new Rhombus(origin, new Point(3,4), new Point(8,4), new Point(5,0));
    System.out.println(geo1.area());
  }
  public static void printColor(GeometricObject geoObj) {
    System.out.printf("The color of %s is %s\n", geoObj.toString(), geoObj.getColor());
  }
  public static void myPrintArea(GeometricObject geoObj) {
    System.out.printf("My area of %s is %f\n", geoObj.toString(), geoObj.area());
  }
}

rhom1 error: Rhombus with unequal sides.  Using unit square.
The area of Point@40a0dcd9 is 0.000000
The color of Rhombus@2ce83912 is blue
The color of Rectangle@41fae3c6 is blue
The area of Quadrilateral@3e7ffe01 is 22.500000
The area of Rectangle@44fd13b5 is 4.000000
My area of Rhombus@2ce83912 is 1.000000
My area of Point@4318f375 is 0.000000
The area of Rectangle@41fae3c6 is 1.000000
true
The area of Circle@2e471e30 is 12.566371
pink
1.0

13.4 Exception Types

Our use of exceptions so far has just been to define one of our own. Specifically, we threw a new Exception. Every exception is an object and thus is a member of some class. So our exception was a member of the class Exception.

Many of the Java library methods can throw exceptions as well. For example, the Scanner constructor we use to create a Scanner object will throw an exception in the FileNotFoundException class if the argument to the Scanner constructor names a file that does not exist.

exception-tree

The diagram on the right shows the class tree for exceptions.

As always, Object is the root of the tree and naturally has many children in addition to Throwable, which is shown. As the name suggests, the Throwable class includes objects that can be thrown, which are exceptions.

From our simplified point of view, there are three important classes of pre-defined exceptions, namely Error, Exception, and Runtime Exception. They are highlighted in the diagram.

  1. System errors throw exceptions in the Error class (or one of its descendents). There is very little we can say or do about these exceptions. Fortunately, they rarely occur.
  2. Many errors throw exceptions in the Exception class (or one of its descendents). One example, given above, is that the Scanner constructor can throw an exception in the FileNotFoundException subclass of the IOException subclass of the Exception class. Also our geometry program throws an exception in the Exception class.
  3. The RuntimeException subclass of the Exception class is singled out for a reason explained in Section 13.5.1.

13.5 More on Exception Handling

13.5.1 Declaring Exceptions

Java divides exceptions into two groups: checked exceptions and unchecked exceptions. Referring to the diagram above, red exceptions are unchecked, blue exceptions are checked.

More precisely, I write exception for the concept and Exception for the class of exceptions shown in blue. Then exceptions in either the Error or RuntimeException are unchecked. Those in the Exception class but not in the RuntimeException class are checked.

The difference for us between checked and unchecked exceptions is that any method that might throw a checked exception must declare this fact in its header line. For example consider the following constructor from the Rhombus class

public Rhombus (Point p1, Point p2, Point p3, Point p4) throws Exception {
  super(p1, p2, p3, p4);
  double s12 = p1.distTo(p2);
  if (s12!=p2.distTo(p3) || s12!=p3.distTo(p4) || s12!=p4.distTo(p1))
    throw new Exception ("Rhombus with unequal sides.");
  sideLength = s12;
}

Please note two important points.

  1. The declaration asserts that the method might throw the exception. There is no guarantee that the method will throw the exception.
  2. The declaration asserts that the method might throw the exception. That is, we assert that executing the method might cause the exception to be thrown, and that the method does not itself catch the exception. If the method handles the exception internally, then the header does not list the exception.

The purpose of this declaration is to alert users (clients) of the method that the exception may be raised while executing the client code since the throw is not caught internally and thus would propagate to the caller.

For example, the main() method in version 3 above, has header line

  public static void main (String[] args) throws Exception

Note that by examining only the code in main(), you will find no statement that directly throws an exception.

The point is that main() calls the rhombus() constructor, which throws and does not catch an exception. Thus if the exception is in fact thrown by rhombus(), Java will automatically re-throw it in main().

An important point of this declaration is that if you read a method header line and do not see throws, then you can be sure that calling that method will not result in a checked exception being thrown by your call.

13.5.2 Throwing Exceptions

Throwing an exception is easy; just throw it. But first you need to create an object in some subclass of throwable (i.e., a member of Error, Exception, or a descendant of one of these).

public class Test {
  public static void main(String[]Args) throws Exception {
    throw new Exception;
    throw new BufferOverflowException;
  }
}

public class MyException extends Exception {}
public class Test {
  static MyException myE = new MyException();
  public static void main(String[]args) throws MyException {
    throw myE;
  }
}

The first frame shows two examples of throwing predefined exceptions. Since BufferOverflowException is a subclass of RuntimeException (a red box), it does not appears in the throws clause.

The second frame shows an example of creating a custom exception class and throwing an instance of this class. If MyException extended a red box rather than the blue one, the throws in the method header would not be needed.

13.5.3 Catching Exceptions

Catching an exception is a two step procedure involving try and catch blocks.

It is an compile time error for a statement not in a try block to throw a checked exception.

If an unchecked exception occurs in a statement not within a try block, the exception is not caught by this method. Instead, the current method ends and the same unchecked exception is raised in the statement that called the method. If the current method is the main method, the program is terminated.

try {
  statements;
}
catch (Exception1 ex1) {
  handler for exception1;
}
catch (Exception2 ex2) {
  handler for exception2;
}
...
catch (ExceptionN exN) {
  handler for exceptionN;
}
statements after the catches

To catch an exception you must first delimit a sequence of statements with a try block, as shown on the right.

If no exception occurs within the try block, the corresponding catch blocks are ignored.

If an exception does occur within the try block, the corresponding catch blocks are searched to see if one catches this class of exception. If such a block is found, its handler is executed followed by the statements after the catches.

If no catch block matches, the exception is NOT caught and once again, the current method is ended and the exception is re-raised in the statement that called the method.

Start Lecture #24

Order of Catch Blocks

The order of the catch blocks is significant as they are checked for matching in the order they are written.

Note that if a catch block is declared to handle exceptions in a class C, it also catches exceptions in any descendant class of C.

As a result, if a later catch block handles a subclass of a prior cache block, the later catch block can never be executed. Indeed, it is compile-time error to list cache blocks in this nonsensical order

An Example

Draw on the board a deeper example with f() calling g() calling h(), handlers in various places, and h() raising various exceptions.

13.5.4 Getting Information from Exceptions

So far, inside catch (Exception ex) we have not used the object ex at all. Moreover sometimes in a throw we have included a string, which again we have not used.

The key to using the exception object and the string included with the throw is to employ one of the methods supplied by the class throwable. Two useful instance methods are getMessage(), which is used in our geometry program and printStackTrace().

Our geometry program uses getMessage(), which returns the string argument passed to the throw clause. As its name suggests printStackTrace prints a trace of the call stack (main called joe, joe called sam, sam threw an exception).

13.5.5 Example: Declaring, Throw, and Catching Exceptions

You should read the book's example. Here is an example from our geometry example.

public class Rhombus extends Quadrilateral {
  private double sideLength;
  public Rhombus (Point p1, Point p2, Point p3, Point p4) throws Exception {
    super(p1, p2, p3, p4);
    double s12 = p1.distTo(p2);
    if (s12!=p2.distTo(p3) || s12!=p3.distTo(p4) || s12!=p4.distTo(p1))
      throw new Exception ("Rhombus with unequal sides.");
    sideLength = s12;
  }
  public Rhombus (Point p, double sideLength, double theta) {
    super (p,
           new Point(p.x+sideLength*Math.cos(theta), p.y+sideLength*Math.sin(theta)),
           new Point(p.x+sideLength*(Math.cos(theta)+1.), p.y+sideLength*Math.sin(theta)),
           new Point(p.x+sideLength, p.y));
    this.sideLength = sideLength;
  }
  public double getSideLength() {
    return sideLength;
  }
}

public class TestQuad {
  public static void main (String[] args) {
    Point origin = new Point(0.0,0.0);
    Point p1 = new Point(1.0,0.0);
    Point p2 = new Point(1.0,1.0);
    Point p3 = new Point(0.0,1.0);
    Point p4 = new Point(5.0,5.0);
    Rhombus rhom1;
    try {
      rhom1 = new Rhombus(origin,origin,p2,p3);
    }
    catch (Exception ex) {
      System.out.printf("rhom1 error: %s  Using unit square.\n", ex.getMessage());
      rhom1 = new Rhombus (origin, 1.0, Math.PI/2.0);
    }
  }
}

rhom1 error: Rhombus with unequal sides.  Using unit square.

The constructor call inside the try is clearly not a rhombus since one side has length zero and the other three are not zero. Hence the constructor throws (but does not catch) a new Exception.

The main method executes the constructor inside a try block so, if the exception is thrown, the catch block is checked and sure enough the only catch there matches. Hence ex becomes the new Exception.

The handler first prints the message included in the throw (via ex.getMessage() and then announces that it will use a unit square instead. The unit square is obtained by invoking the other rhombus constructor that takes the side length and angle. This handler cannot raise an exception.

Since the exception thrown by the first invocation of the Rhombus() constructor, is caught and the catch block cannot raise an exception, main() cannot raise an exception and hence its header line does not mention exceptions.

The full example (version 3) of main() constructs several objects. Some of the constructions can raise exceptions that main does not catch. Hence its header line does announce that an exception can be thrown.

Homework: 13.1.

Homework: Do 13.3 two ways.

  1. Check the index with an if statement to see if Out of Bounds.
  2. Always reference the array and catch the ArrayIndexOutBoundsException. Print the error msg in the catch block.

13.6 The finally Clause

In addition to throw and catch blocks there is occasionally a finally block that gets executed in any case. Consider the example on the right (there can be many catch blocks).

try {
  try block
}
catch (something) {
  catch block
}
catch (somethingElse) {
finally {
  finally block
}
the rest
  1. If no exceptions occur in the try block, the catch blocks are skipped, the finally block is executed, and then the rest.
  2. If an exception occurs in the try block that is caught in a catch block, the rest of the try block is skipped, the matching catch block is executed, the finally block is executed, and then the rest.
  3. If an exception occurs in the try block that is not caught in a catch block, the rest of the try block is skipped, the finally block is executed, and the exception is re-raised in the caller of the method.
  4. Even if the try block contains a return statement, it is still true that if the try block begins, the corresponding finally block will also be started.

13.7 When to Use Exceptions

As mentioned above, it is often silly to use an exception as a complicated if statement. Exceptions are useful when the throw and catch are in separate routines as this usage takes advantage of the automatic up-call that exceptions provide and simple code does not.

13.8 Rethrowing Exceptions

try {
  statements;
}
catch (SomeException ex) {
  statements
  throw ex
}

A catch block can re-throw the exception it has just caught so that the exception is also handled in the caller.

Typical syntax is shown on the right.

13.9 Chained Exceptions

Another possibility is that a catch block for one exception can throw a different exception.

13.10 Creating Custom Exception Classes

Most of the exceptions raised either have no argument or have a string as an argument. The exception in the Rhombus class has a string argument.

However, you can define your own subclass of the Exception class (say MyException) and have a MyException constructor take whatever arguments you want.

public class MyException extends Exception {
  MyException(int code, String str) {
    super(String.format("Exception raised: (code=%d) %s\n"),
                        code, str);
  }
}

The example on the right takes an integer code in addition to the usual string. It uses String.format() to combine the two arguments into a single string that is then passed to the Exception() constructor.

13B Another Example: Copying One File to Another

The program on the right copies one file to another. Each file is specified as a command line argument. That is to run the program one would type.

  java CopyFile inputFileName outputFileName

The program uses exceptions to detect improper input. Several points are worth noting.

// Need File(Reader/Writer) (IO/FileNotFound)Exception
import java.io.*;
public class CopyFile {
  public static void main(String[]Args) throws IOException {
    FileReader getInput  = null;
    FileWriter putOutput = null;
    try {
      getInput  = new FileReader(Args[0]);
    }
    catch (FileNotFoundException fileNotFoundEx) {
      System.out.printf("Can not read %s\n", Args[0]);
      System.exit(0);
    }
    try {
      putOutput = new FileWriter(Args[1]);
    }
    catch (IOException IOEx) {
      System.out.printf("Can not write %s\n", Args[1]);
      System.exit(0);
    }

    // If an IOException occurs here, we can't help
    int c;
    while (getInput.ready()) { // false at EOF
      c = getInput.read();
      putOutput.write(c);
    }
    putOutput.flush();
  }
}

Chapter 14 Abstract Classes and Interfaces

14.1 Introduction

We shall soon see that it is sometimes useful to define methods without bodies (called abstract methods) just to act as placeholders for real methods that will override this abstract method.

14.2 Why Abstract Methods?

public class GeometricObject {
  protected String color = "blue";
  public void printArea() {
    System.out.printf("The area of %s is %f\n", this, this.area());
  }
  public String getColor() {
    return color;
  }
  public double area() {
    System.out.printf("Error: area not overridden in %s\n", this);
    return 0;
  }
}

Consider the area() methods sprinkled throughout our geometry example. In particular, consider the area() method defined in the GeometricObject class. The code from version 2 of geometry is shown to the right.

The method certainly does not compute the area and the important point is that there is no way it can compute the area of a GeometricObject itself since the only field guaranteed to exist is the color.

The reason for including area() here was twofold.

  1. It reminds us that all the geometric objects we plan to actually construct (rectangles, circles, etc) do have areas so we should be overriding this area() method with one in each subclass.
  2. It allows container classes of geometric objects to have the area computed for each contained object.

So What Was Wrong, i.e. Why Abstract Methods

The problem with the area() method in the GeometricObject class is that it only reminds us to override area() in each class derived from GeometricObject. We want more than a reminder, we want a guarantee.

Another advantage of abstract methods is that they permit us to have interfaces, which we will very briefly touch on soon.

Defining an Abstract area() Method

public abstract class GeometricObject {
  protected String color = "blue";
  public void printArea() {
    System.out.printf("The area of %s is %f\n", this, this.area());
  }
  public String getColor() {
    return color;
  }
  public abstract double area();         // must be overridden
}

On the right we see the version 3 reimplementation of GeometricObject as an abstract class, with area() an abstract method. Note that this method has no body and cannot be called.

The compiler insures that this abstract method is overridden in the subclasses of GeometricObject.

Since this class contains an abstract method, the class must itself be declared abstract, as we have done on the first line.

You can declare a variable of type GeometricObject, but you cannot construct an object of the class. Thus a variable of declared type GeometricObject can never have actual type GeometricObject. Instead, the object referred to by the variable must be a member of a subclass of GeometricObject. Specifically a GeometricObject variable can contain a reference to a Rectangle, a Point, a Circle, etc. In all these cases the abstract area() method in GeometricObject is overridden and becomes a real (i.e., non-abstract) method.

14.3 Example: Calendar and GregorianCalendar

14.4 Interfaces

If all methods in a class are abstract and all data fields are constants (static final in Java), the class is called an interface.

Imagine that you want to write a class MyClass that extends two base classes ClassA and ClassB. That is, a MyClass object has all the data fields of both ClassA and ClassB (plus any others you add to MyClass). Also MyClass starts with all the methods of the two base classes (minus any that are overridden in MyClass, plus any new methods added in MyClass).

public class MyClass extends ClassA, ClassB {
    statements;
}

You would try to write the class as shown on the right. This is called multiple inheritance since MyClass inherits from multiple base classes. However, Java does not support multiple inheritance (although some languages, notably C++, do support it).

One question with multiple inheritance is what to do if two base classes include methods with the same signature but different bodies.

public class MyClass
       implements InterfaceA, InterfaceB {
    statements;
}

Java does support a somewhat similar notation. If ClassA and ClassB are very simple, specifically if they each consist of just constants and abstract methods, then they are called interfaces, say InterfaceA and InterfaceB, and MyClass can be defined as shown on the right. Since all methods in an interface are abstract and hence body-free, the question mentioned just above for multiple inheritance cannot arise.

14.5 Example: The Comparable Interface

package java.lang;
public interface Comparable {
  public abstract int compareTo(Object o);
}

As shown on the right the Comparable interface consists of just one method, compareTo().

Many classes in the java library implement this interface, which means that they supply an integer valued compareTo() instance method having one parameter, an Object.

If the instance object is less than the argument object, the value returned is negative; if the two are equal, the value returned is 0; and if the instance object is greater than the argument object, the value return is positive.

  public class String implements Comparable

Recall that we used compareTo() when studying the String class. If we looked at the header line of the class it would be similar to what is shown on the right.

  public final class String implements Serializable,
         Comparable<String>, CharSequence

If we access the online Java documentation, we can see the actual header line, which is in the next frame. So String implements three interfaces. More interesting is the <String> appended to Comparable. This is an manifestation of Java generics, introduced in Java 1.5 (1.7 is current). Generics several purposes. Here they signify that a String can only be compared to another String, not an arbitrary object.

14.A Example: Sorting Student Records

public class Student implements Comparable {
  public int    stuId;
  public String name;
  public Student(int stuId, String name) {
    this.stuId   = stuId;
    this.name = name;
  }
  public int compareTo(Object obj) {
    Student stu = (Student) obj;
    return name.compareTo(stu.name);
  }
}


import java.util.Scanner;
import java.io.File;
import java.io.FileNotFoundException;
public class TestStudent {
  public static void main(String[]args)
         throws FileNotFoundException {
    Scanner getInput = new Scanner(new File("stu.db"));
    int n = getInput.nextInt();
    Student[] student = new Student[n];
    for (int i=0; i<n; i++) {
      int    stuId   = getInput.nextInt();
      String name    = getInput.next();
      student[i] = new Student(stuId, name);
    }
    sort(n, student);
    System.out.printf("Sorted by name\n");
    for (int i=0; i<n; i++) {
      System.out.printf("%s\t%3d\n", student[i].name,
                        student[i].stuId);
    }
  }
  public static void sort(int n, Student[] student) {
    for (int i=0; <n-1; i++)
      for (int j=i+1; j<n; j++)
        if (student[i].compareTo(student[j]) > 0) {
          Student tmpStudent = student[i];
          student[i] = student[j];
          student[j] = tmpStudent;
        }
  }
}


10                                Sorted by name
1   Robert                        Alice     3
2   John                          Alice     9
3   Alice                         Harry    10
4   Jessica                       Jessica   4
5   Sam                           John      2
6   Sarah                         Judy      8
7   Mary                          Mary      7
8   Judy                          Robert    1
9   Alice                         Sam       5
10  Harry                         Sarah     6

The example on the right illustrates a class extending Comparable as well as reviewing a number of concepts we have learned previously.

As an added benefit, it previews sorting arrays of objects, which we will study very soon.

The top frame shows a very simple Student class that extends the Comparable interface. The class contains

Note that, although compareTo() will generate a runtime error unless its argument can be cast to student, the parameter itself is of type Object. Indeed, in order to implement Comparable, the defined compareTo() must override the compareTo() in Comparable and thus must have an Object parameter.

The generic implement would avoid this run-time error.

The second frame shows a test program using the Student class. The first loop reads in student IDs and names from a database file stu.db (shown in the third frame) and calls the Student() constructor to create the entries in the student array.

After the student records are read into the student array, the sort routine is invoked.

Finally the records are printed, to verify that they are now in sorted order.

You should compare the sort() method to the alphabetizing routing (sorting strings) that we did in section 9.2.13.

To conserve vertical space, the third frame contains two items. On the left is the stu.db file. It begins with a count of the number of students followed by the records themselves, one per line.

On the right is the output of the TestStudent program. As expected, the records are now sorted by the name field. Since the name Alice appeared in two input records, it also appears twice in the output. Since the sort used is stable, the relative order of the two Alice records is preserved.

14.6 Example: The ActionListener Interface

14.7 Example: The Cloneable Interface

14.8 Interfaces vs. Abstract Classes

As mentioned previously, Java does not support multiple inheritance, but a class can implement multiple interfaces. This is not as hard since interfaces are extremely simple classes.

14.9 Processing Primitive Data Type Values as Objects

For performance reasons, the Java primitive types (int, char, etc) are not objects. Reference semantics requires an extra level of indirection.

However, the full power of object orientation, does require objects so, for each primitive type, there is a corresponding, so called, wrapper class with a very similar name. We have already seen Character, the wrapper class for char. Also present are Byte, Short, Integer, Long, Boolean, Float, and Double.

  Double d    = new Double(4.3);
  Character c = new Character('c');

To create a Double object, we naturally use a Double() constructor. The argument is a double (lower case, i.e., a primitive type). Examples for Double() and Character() are shown on the right. The Float(), Long(), etc. constructors are just the same.

Start Lecture #25

14.10 Sorting an Array of Objects

public static void sort(int n, Student[] student) {
  for (int i=0; i<n-1; i++)
    for (int j=i+1; j<n; j++)
      if (student[i].compareTo(student[j]) > 0) {
        Student tmpStudent = student[i];
        student[i] = student[j];
        student[j] = tmpStudent;
      }
}

public static void sort(int n, Comparable[] obj) {
  for (int i=0; i<n-1; i++)
    for (int j=i+1; j<n; j++)
      if (obj[i].compareTo(obj[j]) > 0) {
        Comparable tmpObj = obj[i];
        obj[i] = obj[j];
        obj[j] = tmpObj;
      }
}

Recall the sort routine in Section 14.A. which has been reproduced on the right.

There is something curious about this routine: It sorts items in the Student class, but uses nothing about the Student class except for the compareTo method, which is present in any class implementing Comparable.


This raises the question of what would need to be changed for this sort to work for any class implementing Comparable.

The answer is that essentially nothing has to be changed. Specifically, the two occurrences of the class name Student, need be changed to Comparable. Although not necessary, the code on the right changes other names as well. In particular, the variable name student would be misleading so has been changed to obj.

When this new version is dropped into TestStudent, the results are the same (ignoring the unchecked warning, see below). The input is sorted alphabetically on the name field.

Generic Sorting

import java.util.Scanner;
import java.io.File;
import java.io.FileNotFoundException;
public class TestStudent {
  public static void main(String[]args)
         throws FileNotFoundException {
    Scanner getInput = new Scanner(new File("stu.db"));
    int n = getInput.nextInt();
    Student[] student = new Student[n];
    for (int i=0; i<n; i++) {
      int    stuId   = getInput.nextInt();
      String name   = getInput.next();
      student[i] = new Student(stuId, name);
    }
    Float[] f={new Float(3.),new Float (0.),new Float (-5.)};
    Long[]  l={new Long(333),new Long (0),  new Long (-555)};
    // Sort and print
    sort(3, student);
    sort(3, f);
    sort(3, l);
    System.out.printf("Sorted by name\n");
    for (int i=0; i<3; i++) {
      System.out.printf("%s\t%3d %6.1f %5d\n",
           student[i].name, student[i].stuId, f[i], l[i]);
    }
  }
  public static void sort(int n, Comparable[] obj) {
    for (int i=0; i<n-1; i++)
      for (int j=i+1; j<n; j++)
        if (obj[i].compareTo(obj[j]) > 0) {
          Comparable tmpObj = obj[i];
          obj[i] = obj[j];
          obj[j] = tmpObj;
        }
  }
}

The great news is that the identical sorting routine can be used to sort objects of any class that implements Comparable.

On the right is an expanded version of TestStudent. Note the following points.

  1. The sort() method is unchanged! That is, we have produces a generic sort routine, one that is able to sort objects of many different types. The only requirements are that all the objects in one invocation are of the same type, and that the type implements Comparable.
  2. The same data file stu.db and the same Student class were used. They are not shown to the right to save space.
  3. Arrays of Float's and Long's are created in the normal manner. Had we used arrays of float's and long's, the program would NOT compile since these are not classes that implement Comparable. In fact, they are not classes at all!
  4. The calls of the Sort() method are the same for our Student class as for the standard library Float and Long classes.
  5. We used shorter arrays just to save space.
  6. To repeat, we needed to use Float and Long (rather than float and long) since we needed a class implementing comparable.

A Secret Difficulty (Don't Tell Anyone)

The above program, if run on a modern Java system, will give a somewhat cryptic warning and then work fine. If the same program is run on an older (version 1.4) Java, and the printf() methods replaced by println(), there would not be any warnings and again it would work.

This difficulty has to do with the addition of Java generics in version 1.5, and the resulting addition of Comparable<T>

.

14.11 Automatic Conversion Between Primitive Types and Wrapper Class Types

public class Student implements Comparable {
  public int    stuId;
  public String name;
  public Student(int stuId, String name) {
    this.stuId   = stuId;
    this.name = name;
  }
  public int compareTo(Object obj) {
    Student stu = (Student) obj;
    return name.compareTo(stu.name);
  }
}

import java.util.Scanner;
import java.io.File;
import java.io.FileNotFoundException;
public class TestStudent {
  public static void main(String[]args)
                throws FileNotFoundException {
    Scanner getInput = new Scanner(new File("stu.db"));
    int n = getInput.nextInt();
    Student[] student = new Student[n];
    for (int i=0; i<n; i++) {
      int    stuId   = getInput.nextInt();
      String name   = getInput.next();
      student[i] = new Student(stuId, name);
    }
    Float[]  f={3.F,0.F,-5.F,2.5F,-8.F,8.2F,7.8F,0.F,-5.F,-8.F};
    Double[]    d = {3.,0.,-5.,2.5,-8.,8.2,7.8,0.,-5.,-8.};
    Byte[]      b = {33, 0,-55, 2,-8,8,7,0,-5,-8};
    Short[]     s = {333,0,-555,2,-8,8,7,0,-5,-8};
    Integer[]   g = {333,0,-555,2,-8,8,7,0,-5,-8};
    Long[]      l = {333L,0L,-555L,2L,-8L,8L,7L,0L,-5L,-8L};
    Character[] c = {'q','8','A','a','Z','q','z','Q','8','8'};
    String[]    t = {"q","8","A","a","Z","q","z","Q","88","78"};
    // Sort and print
    sort(10, student);
    sort(10, f);
    sort(10, d);
    sort(10, b);
    sort(10, s);
    sort(10, g);
    sort(10, l);
    sort(10, c);
    sort(10, t);
    System.out.printf("Sorted by name\n");
    for (int i=0; i<10; i++) {
      System.out.printf("%s\t%3d%7.1f%7.1f%6d%6d%6d%6d%6c%6s\n",
            student[i].name,student[i].stuId,
            f[i],d[i],b[i],g[i],s[i],l[i],c[i],t[i]);
    }
  }
  public static void sort(int n, Comparable[] obj) {
    for (int i=0; i<n-1; i++)
      for (int j=i+1; j<n; j++)
    if (obj[i].compareTo(obj[j]) > 0) {
      Comparable tmpObj = obj[i];
      obj[i] = obj[j];
      obj[j] = tmpObj;
    }
  }
}

The previous section illustrated both an advantage and slight annoyance with the wrapper classes Float, Long, etc. when compared to the corresponding primitive data types (float, long, etc).

The advantage is that, since the wrappers are classes, they can make use of many object oriented features. The minor annoyance is the relative wordiness of

Short s[]={new short[5],new short[7]};

when compared to

short s[]={5,7};
 

In most cases the annoyance can be avoided because Java will automatically convert between the primitive type and the corresponding wrapper class. Conversion to the wrapper class is called boxing and the reverse conversion is called unboxing.

As an illustration, on the right is yet one more version of the TestStudent class, this time using the autoboxing and also sorting many more types. For completeness we repeat the Student class and the student database.

Since both Student and TestStudent are top-level public classes, they must be in separate files.

Assessment: Wrappers

The wrapper classes do work but it would be nicer if we didn't have to deal with them. The reason they are there is that, due to efficiency considerations, the primitive types are not classes and hence cannot make use of the properties of objects.

Assessment: Generic Sorting

In contrast to my lukewarm assessment of wrapper classes, I would say that the ability to write a generic sorting method is simply wonderful. The identical method works for any class that implements comparable.

The requirement to implement comparable is exactly right. That is, to sort a bunch of items, you must be able to compare two of them and decide which is smaller. This is exactly what implementing comparable entails.

10              |       Sorted by name
1   Robert      |       Alice     3   -8.0   -8.0   -55  -555  -555  -555     8    78
2   John        |       Alice     9   -8.0   -8.0    -8    -8    -8    -8     8     8
3   Alice       |       Harry    10   -5.0   -5.0    -8    -8    -8    -8     8    88
4   Jessica     |       Jessica   4   -5.0   -5.0    -5    -5    -5    -5     A     A
5   Sam         |       John      2    0.0    0.0     0     0     0     0     Q     Q
6   Sarah       |       Judy      8    0.0    0.0     0     0     0     0     Z     Z
7   Mary        |       Mary      7    2.5    2.5     2     2     2     2     a     a
8   Judy        |       Robert    1    3.0    3.0     7     7     7     7     q     q
9   Alice       |       Sam       5    7.8    7.8     8     8     8     8     q     q
10  Harry       |       Sarah     6    8.2    8.2    33   333   333   333     z     z

14.12 The BigInteger and BigDecimal Classes

14.13 Case Study: The Rational Class

Homework: 14.5 (omit UML diagram).

Chapter A (not from the text) Generic Classes

class LongStack {
  private int top = 0;
  private Long[] theStack;
  private static int totalStacked = 0;
  public LongStack() {
    theStack = new Long[100];
  }
  public LongStack(int n) {
    theStack = new Long[n];
  }
  public void push(Long elt) throws Exception {
    if (top>=theStack.length)
      throw new Exception("No room to push!");
    totalStacked++;
    theStack[top++] = elt;
  }
  public Long pop() throws Exception {
    if (top<=0)
      throw new Exception("Nothing to pop!");
    totalStacked--;
    return theStack[--top];
  }
  public static int getTotalStacked() {
    return totalStacked;
  }
}

class ShortStack {
  private int top = 0;
  private Short[] theStack;
  private static int totalStacked = 0;
  public ShortStack() {
    theStack = new Short[100];
  }
  public ShortStack(int n) {
    theStack = new Short[n];
  }
  public void push(Short elt) throws Exception {
    if (top>=theStack.length)
      throw new Exception("No room to push!");
    totalStacked++;
    theStack[top++] = elt;
  }
  public Short pop() throws Exception {
    if (top<=0)
      throw new Exception("Nothing to pop!");
    totalStacked--;
    return theStack[--top];
  }
  public static int getTotalStacked() {
    return totalStacked;
  }
}

class ObjectStack {
  private int top = 0;
  private Object[] theStack;
  private static int totalStacked = 0;
  public ObjectStack() {
    theStack = new Object[100];
  }
  public ObjectStack(int n) {
    theStack = new Object[n];
  }
  public void push(Object elt) throws Exception {
    if (top>=theStack.length)
      throw new Exception("No room to push!");
    totalStacked++;
    theStack[top++] = elt;
  }
  public Object pop() throws Exception {
    if (top<=0)
      throw new Exception("Nothing to pop!");
    totalStacked--;
    return theStack[--top];
  }
  public static int getTotalStacked() {
    return totalStacked;
  }
}
  

I have marked this chapter optional to indicate that it will not be included on the final exam. However generic classes are important in Java and will be covered in 102. I did discuss this material in class.

A Stack of longs (really Longs)

When describing stacks, we wrote two classes LongStack and ShortStack, which implemented respectively stacks of longs and shorts.

On the right is a version for Longs (the corresponding wrapper class). It has been cleaned up a little since we now know about private and exceptions. It also calculates (and provides access to) the total number of elements on all the stacks.

A Stack of Shorts

Directly below the LongStack we see ShortStack, which is nearly identical. The only difference is changing Long to Short in a number of places.

Integer / Character / String

I wrote stacks of these types as well. Again the only modification was to change one word in a number of places

A Stack of Scanners

The only extra change was to add the line.

    import java.util.Scanner;
  

A Stack of GeometricObjects

I didn't write this one but it would be easy. The only extra change would be to include the GeometricObject class itself.

Can't We Do Better

All these stacks are the same! Why do we have to write each one. Isn't there some analogue to the generic sorting method we wrote in 14.11?

Generic Versus Heterogeneous

Indeed we can do better, but we must first decide what we want. Our sorting method was generic, that is, the method accepted an array of any type (providing it implemented Comparable), but not an array of mixed types (Java doesn't have such heterogeneous arrays). The analogy for stacks would be to write just one stack class but, when creating a specific stack, you specify the type of elements it will contain.

A heterogeneous stack or sorting method would be one that processed elements of differing types. When creating an array in Java, we must specify they type, but the elements can be any subtype of that type.

For stacks, the analogy would be to have a single stack that can hold elements of differing types at the same time. This is easy (but perhaps not what we want, see below).

A Stack of Objects

This looks no different than a stack of Integers as you can see on the right. Since every class extends Object, items of every (non-primitive) type can appear on such a type, it is truly heterogeneous. This seems perfect; but is it?

What More Could We Want?

Imagine we write just the ObjectStack class and then create several stacks in this class. We can use one of these stacks to hold Doubles, we can use another one to hold Scanners, and can use a third one to hold a bunch of different items.

ObjectStack dblStk = new ObjectStack();
Double d1=2.5, d2;
dblStk.push(d1);           // always works
d2 = dblStk.pop();         // compile error
d2 = (Double)dblstk.pop(); // risky
Object o = dbleStk.pop();
if (o instanceof Double)
  d2 = o;                  // safe
else
  // What goes here?  A runtime error msg
  

A stack of Objects is indeed perfect for the last (mixed bag) application, but not for the first two. Assume we use ObjectStack to create a stack for Doubles as shown on the right.

Java does not understand that we have a stack of just one type (a homogeneous stack) so a naked pop will not compile.

A downcast will work if the program is correct. The trouble is that if we erred and could possible push say a String on to the stack, then the downcast would generate a run-time error, a very bad outcome. The only worse outcome would be a wrong answer produced with no visible error.

Using instanceof is probably the best we can do, but still any error is not detected until runtime.

A Generic Stack Class

What we want for these homogeneous cases is to write just one generic stack class, but be able to create homogeneous stacks of any (non-primitive) type.

How can we do this?

Come back in September; it is covered in 102!

Chapter 15 Graphics

Chapter 16 Event-Driven Programming

Chapter 17 Creating User Interfaces

Chapter 18 Applets and Multimedia

Chapter 19 Binary I/O

Chapter 20 Recursion

20.1 Introduction

A method is recursive if it directly or indirectly calls itself. So if the method f() invokes f(), we call f() recursive. Also if f() invokes g() and g() invokes f(), we call both f() and (g) recursive.

For an example consider two mathematical functions f() and g() defined on the integers by these three rules.

  1. f(n) = 0               if n≤0
  2. f(n) = f(n-1) + g(n-1) if n>0
  3. g(n) = f(n) + 1        for all n

For example let's compute f(3)

f(3) =                f(2)                    +                          g(2)
     =     f(1)         +              g(1)   +               f(2)        +             1
     = f(0) +    g(0)   +     f(1)        + 1 +     f(1)        +               g(1)  + 1
     =  0   + f(0) + 1  + f(0) +    g(0)  + 1 + f(0) +    g(0)  +    f(1)         + 1 + 1
     =  0   +  0   + 1  +  0   + f(0) + 1 + 1 +   0  + f(0) + 1 + f(0) + g(0)     + 1 + 1
     =  0   +  0   + 1  +  0   +   0  + 1 + 1 +   0  +   0  + 1 +   0  + g(0)     + 1 + 1
     =  0   +  0   + 1  +  0   +   0  + 1 + 1 +   0  +   0  + 1 +   0  + f(0) + 1 + 1 + 1
     =  0   +  0   + 1  +  0   +   0  + 1 + 1 +   0  +   0  + 1 +   0  +   0  + 1 + 1 + 1
     =  7
public class FG {                                0    0
  public static void main(String[]arg) {         1    1
    for (int i=0; i<10; i++)                     2    3
      System.out.printf("%2d %4d\n", i, f(i));   3    7
  }                                              4   15
  public static int f(int n) {                   5   31
    if (n <= 0)                                  6   63
      return 0;                                  7  127
    return f(n-1) + g(n-1);                      8  255
  }                                              9  511
  public static int g(int n) {
    return f(n) + 1;
  }
}

The program is quite simple and is shown on the right together with the output produced.

What is surprising is that it works! After all when we invoke f(3), this sets n=3 and invokes f(2), which sets n=2. So now in the same method, namely f(), the same variable, namely n, has two different values at the same time, namely 3 and 2.

How can this be?

Actually, the situation gets even worse when we consider the call of f(9), and worse still when we remember that f() calls g(), which in turn calls f(). There will be very many values for n in f() at the same time.

20.2 Problem: Computing Factorials

public class Factorial {
  public static void main(String[]arg) {
    System.out.printf(" n Recursive  Iterative\n");
    for (int i=-1; i<10; i++)
      if (i<0)
        System.out.printf("(%d)! is not defined!!\n", i);
      else
        System.out.printf("%2d %8d  %8d\n",
                          i, recFac(i), iterFac(i));
  }
  public static int recFac(int n) {
    if (n <= 1)
      return 1;
    else
      return n * recFac(n-1);
  }
  public static int iterFac(int n) {
    int fact=1;
    if (n>1)
      for (int i=2; i<=n; i++)
        fact = fact * i;
    return fact;
  }
}

 n Recursive  Iterative
 (-1)! is not defined!!
 0        1         1
 1        1         1
 2        2         2
 3        6         6
 4       24        24
 5      120       120
 6      720       720
 7     5040      5040
 8    40320     40320
 9   362880    362880

Factorial is a very easy function to compute either with or without recursion. It is normally written using ! rather than the usual parenthesized notation. That is, instead of factorial(7), we usually write 7!

For a positive integer n, n! is defined to be

  1 * 2 * 3 * ... * n

For example 5! = 1 * 2 * 3 * 4 * 5 = 120

If instead of defining n! to be multiplying from 1 up to n, we define n! (equivalently) to be multiplying from n down to 1, we see that the definition is the same as defining (recursively).

  n! = n * (n-1)!

It is conventional to define 0! = 1.

Sometimes factorial is defined to be 1 for negative numbers as well, but I believe it is more common to consider factorial undefined for negative numbers.

The program on the right computes factorial twice, once with each definition. The factorial methods would return 1 if given a negative argument, but the main program declares this to be an error.

As in the previous section, perhaps the most interesting question is "How can the Java program possible work with n having so many different values at the same time?".

The Call Stack for Factorial

On the board show the stack growing and shrinking when computing recFact(4).

Homework: 14.5 (omit UML diagram).

20.3 Problem: Computing Fibonacci Numbers

public class Fibon {
  public static void main(String[]arg) {
    System.out.printf(" n Recursive  Iterative\n");
    for (int i=-1; i<8; i++)
      if (i<0)
        System.out.printf("Fibonacci undefined for %d\n", i);
      else
        System.out.printf("%2d %8d  %8d\n",
                          i, recFibon(i), iterFibon(i));
  }
  public static int recFibon(int n) {
    if (n <= 1)
      return 1;
    else
      return recFibon(n-1) + recFibon(n-2);
  }
  public static int iterFibon(int n) {
    int a=1, b=1, c=1;
    for (int i=2; i<=n; i++) {
      c = a + b;
      a = b;
      b = c;
    }
    return c;
  }
}

 n Recursive  Iterative
 Fibonacci is not defined for -1
 0        1         1
 1        1         1
 2        2         2
 3        3         3
 4        5         5
 5        8         8
 6       13        13
 7       21        21

The Fibonacci sequence is normally defined recursively by the following two rules.

  1. f(0) = f(1) = 1
  2. f(n) = f(n-1) + f(n-2) for n>1

From the formulas above, we see that the sequence begins

  1, 1, 2, 3, 5, 8, 13, ...

The limiting ratio f(n)/f(n-1) as n approaches infinity is called the golden mean and comes up in a number of mathematical and biological settings.

The code for both recursive and iterative solutions is on the right.

Again the methods will calculate Fibonacci values for negative n, but the main() method correctly flags it as an error.

Draw on the board the diagram showing all the recursive calls that occur when you invoke recFib(4).

I must note that the recursive version is horribly inefficient. On my laptop an execution of iterFibon(50) is essentially instantaneous, but recFibon(50) did not finish in a minute.

This inefficiency is due to the many recursive calls required. For example recFibon(39) made well over 204 million recursive calls to arrive at the answer of 102,334,155.

Start Lecture #26

20.4 Problem Solving Using Recursion

It is important when designing a recursive solution, that you avoid infinite recursion where the method f() always calls itself. There must be a so called base case that can be solved directly. For example, in the factorial problem, we specified that 0!=1.

Another requirement is that when you are not in a base case, the recursive calls bring you closer to the base case, so that you eventually reach a base case. For example, in the factorial problem, if n>0, then we define n! = n*(n-1)!, which means that when trying to compute factorial for n, we need to compute factorial for n-1. Since the base case is n=0, n-1 is closer to the base case than is n.

Often the base case occurs for a small value of an integer parameter, (e.g., n for factorial or Fibonacci, the length for many string problems, array bounds for some array problems, etc). Then, if the recursive call has a smaller value of the parameter than the original, it is closer to the base case.

Thus, many times the high-level structure of a recursive solution is

  if (the parameters fit the base case)
     return the solution directly
  else
     call the method recursively with a smaller parameter value
     return the answer using the answer from the recursive call

Here is an (inefficient, overly complex) method of adding non-negative integers that uses the above pattern.

public static int sum(int x, int y) {
   if (y == 0) then
      return x
   int z = sum(x,y-1);
   return z+1;
}

Multiple Base Cases

Sometimes we have more than one base case. For example consider the isPalindrome() procedure on the right. Recall that a string is a palindrome if it reads the same from left to right as from right to left.

public static boolean isPalindrome(String s) {
  if (s.length() <= 1) // base case 1
    return true;
  if (s.charAt(0) == x.charAt(s.length()-1)) // base case 2
    return false;
  return isPalindrome(s.substring(1,s.length()-1));
}

Two base cases are that an empty string or a string of length one is always a palindrome.

Another base case is that if the first and last characters are not equal, then it is not a palindrome.

If neither base case applies than the original string is a palindrome if and only if the substring omitting the first and last characters (a smaller problem closer to the first two base cases) is a palindrome.

An Alternate Interpretation

The above code is from the book. I might prefer to view the solution with only two base cases as follows.

public static boolean isPalindrome(String s) {
  if (s.length() <= 1) // base case
    return true;
  return (s.charAt(0)==x.charAt(s.length()-1)) &&
         isPalindrome(s.substring(1,s.length()-1));
}

The base cases are that empty and length 1 strings are palindromes.

If we are not in a base case, the original string is a palindrome if and only if (1) the first and last characters are the same and (2) the substring omitting these two characters is a palindrome. This interpretation gives rise to the code on the right.

20.5 Recursive Helper Methods

Sometimes it is easier or more efficient to solve a more general problem. For example, an inefficiency in the above palindrome program is that at each recursive call it builds a new string when in fact all that we need to do is to restrict our attention to a contiguous portion of the original string.

public static boolean isPalindrome(String s, int lo, int hi) {
  if (hi <= lo)
    return true;    // base cases
  return (s.charAt(lo)==s.charAt(hi)) &&
         isPalindrome(s, lo+1, hi-1);
}

So instead of asking if a string is a palindrome, we as a more general question: is the portion of the string from position lo to position hi a palindrome and then recursively restrict the range lo...hi, while keeping the same string. This program is shown on the right.

One objection to the last program is that it is more awkward for the user who must now invoke the program as isPalindrome(s,0,s.length()-1). In other words, the helper program may have helped the implementer (in this case to make their program more efficient), but is sure didn't help the user who much preferred writing isPalindrome(s).

public static boolean isPalindrome(String s) {
  return isPalindrome(s, 0, s.length()-1));
}

To answer this objection, we write a second isPalindrome() method that meets the user's expectation and properly invokes the first isPalindrome() method. The code is shown on the right. Note that the method name isPalindrome() is overloaded: If invoked with just a string argument, the second version is called; if invoked, with a string and two integers, the first version is called.

Start Lecture #27

Remark: Chris Nelson points us at an infinite recursive game http://insideastarfilledsky.net/. Chris adds The description of the game isn't perfect on the front page but the bullets points page answers a question about the term "infinite": http://insideastarfilledsky.net/bulletPoints.php I watched the trailer and you can see the recursion. In particular the part entitled Wait, where are you now? is reminiscent of the questions one asks when trying to understand a recursive method.

20.A Binary Trees: A Recursive Data Structure

tree

On the right is an preliminary look at an important data structure, much studied in 102: a binary tree.

These trees have two kinds of nodes: interior nodes drawn as squares, and leaves drawn as circles. Each interior node has one or two children (hence the name binary; in 102 we also study general trees with interior nodes having many children). Each leaf has no child. A node also contains data, in this case a character.

If we look closely, we see that the tree on the right has no node with exactly one child. A tree in which all nodes have either two or zero children is called a proper binary tree.

public class Tree {
  char data;
  Tree left;
  Tree right;
  Tree(char data, Tree left, Tree right) {
    this.data  = data;
    this.left  = left;
    this.right = right;
  }
  Tree (char data) {  // construct a leaf
    this(data, null, null);
  }
}

The code on the right is a Java class for the binary tree data structure. Note that we use the name Tree when the object is in fact a single node of a tree (the justification for this terminology will be soon clear).

We see that a Tree has three components, two Tree's and a char, which seems crazy! How can a Tree contain two Tree's? For one thing how can a Tree be large enough to hold two trees?

The answer is an old friend (enemy?), reference semantics. The variables left and right do not contain trees, but instead contain references to trees. Recall that all references are the same (modest) size. Thus a Tree object contains two references to Tree's and a char.

For a leaf the references are null For an interior node, the references are not null, but point to actual trees.

Informally, you can think of a tree (i.e., a tree node) as it is shown in the diagram. That is, you can view a node as containing two arrows and a character (with arrows corresponding to null references drawn differently (or omitted).

20.A.1 Constructing, Counting, and Traversing a Binary Tree

The program on the right first constructs the tree in the diagram above, then counts the nodes in the tree and finally traverses the tree in three different orders.

public class Tree {
  char data;
  Tree left;
  Tree right;
  Tree(char data, Tree left, Tree right) {
    this.data  = data;
    this.left  = left;
    this.right = right;
  }
  Tree (char data) {  // constructs a leaf
    this(data, null, null);
  }
  public static void main(String[]arg) {
    Tree t1 = new Tree('A');
    Tree t2 = new Tree('l');
    t1 = new Tree('C',t1,t2);
    t2 = new Tree('l');
    Tree t3 = new Tree('S',t1,t2);
    t1 = new Tree('a');
    t2 = new Tree('n');
    t1 = new Tree('1',t1,t2);
    t2 = new Tree('G');
    t1 = new Tree('0',t1,t2);
    t1 = new Tree('1',t3,t1);
    System.out.printf("The tree has %d nodes\n",
                      count(t1));
    System.out.printf("\nPreorder Traversal\n");
    t1.preOrderTraverse();
    System.out.printf("\n\nPostorder Traversal\n");
    t1.postOrderTraverse();
    System.out.printf("\n\nInorder Traversal\n");
    t1.inOrderTraverse();
  }
  public static int count(Tree t) {
    if (t == null)
      return 0;
    return 1 + count(t.left) + count(t.right);
  }
  public void visit() {
    System.out.printf("%c ", this.data);
  }
  public void preOrderTraverse() {
    if (this==null)             // base case
      return;
    this.visit();
    if (this.left != null)
      this.left.preOrderTraverse();
    if (this.right != null)
      this.right.preOrderTraverse();
  }
  public void postOrderTraverse() {
    if (this==null)             // base case
      return;
    if (this.left != null)
      this.left.postOrderTraverse();
    if (this.right != null)
      this.right.postOrderTraverse();
    this.visit();
  }
  public void inOrderTraverse() {
    if (this==null)             // base case
      return;
    if (this.left != null)
      this.left.inOrderTraverse();
    this.visit();
    if (this.right != null)
      this.right.inOrderTraverse();
  }
}

There are 11 nodes in the tree

Preorder Traversal
1 S C A l l 0 1 a n G

Postorder Traversal
A l C l S a n 1 G 0 1

Inorder Traversal
A C l S l 1 a 1 n 0 G

Constructing

First let us concentrate on how the beginning of the main() method uses both constructors to create the desired tree, which I have redrawn here for convenience.

tree

To create an interior node, we use the first constructor and thus need to supply two child trees in addition to the data. This means we must construct both children before constructing their parent. Hence, the tree must be constructed from the bottom up.

To construct a leaf we use the second constructor and thus supply only the data item, which in this small example is simply a character. The left and right fields are set to null by the first constructor.

In main() I deliberately reused the Tree variables t1,t2,t3 as I believe it is instructive to see which tree nodes must be remembered to enable constructing other nodes.

On the board execute each line of main() showing the variable name, the reference, and the referred to Tree. As execution proceeds, we see why we called the objects referred to by t1,t2,t3 trees not nodes.

Counting

With recursion available counting is a very easy, very short program. Without recursion it would be considerably more challenging. The idea for counting is that the number of nodes in any (nonempty) tree is 1 (for the root) plus the number in the left subtree (a recursive call of the same routine) plus the number in the right subtree (another recursive call of the same routine).

The base case is that the number of nodes in an empty tree is zero. A reference to an empty tree has the value null.

Traversing

In a tree traversal all the tree nodes are visited. In the three traversals we study, each node is visited exactly once. In general, what to do during a visit is specified by the user method visit(). In our example, visit() consists simply of printing the node's character data item.

How do we ensure we visit each node (exactly once) and in what order should we visit them? The key to the first question is that traversing a node has three tasks, visit the node, traverse the left subtree (a recursive call) and visit the right subtree (another recursive call).

Three orderings are common: pre-order, post-order, and in-order. In all three, a left child is visited before its right sibling. The pre/post/in refers to when a node is visited in relation to its children.

On the board, without looking at the code, manually perform all three traversals for the tree above. Then repeat the exercise for a non-proper binary tree.

Homework: For each of the trees below, determine the order of node visitation for all three traversals.

traversal-hw

20.6 Problem: Finding the Directory Size

Now that we understand trees and how to recursively walk through them, we can solve a number of problems. We will look at a familiar structure, the file system tree.

import java.io.File;
import java.util.Scanner;
public class DirOrFileSize {
  public static void main(String[] args) {
    System.out.print("Enter a directory or a file: ");
    Scanner input = new Scanner(System.in);
    String directory = input.nextLine();
    System.out.printf("There are %d bytes.\n",
                      getSize(new File(directory)));
  }
  public static long getSize(File file) {
    if (! file.isDirectory())  // Base case, 1 file
      return file.length();
    else { // All files and subdirectories
      long size = 0; // Total size in this directory
      File[] files = file.listFiles();
      for (int i = 0; i < files.length; i++)
        size += getSize(files[i]);
      return size;
    }
  }
}

In this tree the interior nodes are the (sub-)directories and the leaves are the simple files. In 202 you will learn that file system trees are more complicated than this and actually are often not trees.

Note that a directory can have any number of files and sub-directories so some interior nodes have more than 2 children. Thus we do not have a binary tree and cannot use inorder traversal.

Since the trees are not binary, the data structure in 20.A cannot be used. However this is not our problem. The authors of java.io.File must have taken 102 or equivalent in order to write that class. We are just clients of the class and do not need to look under the covers to see how it is implemented.

The program on the right reads in a file or directory name and prints the total size of all the files there. Specifically, if given a file it gives the size of the file; whereas, if given a directory, it prints the size of all the files under that directory (which includes files under subdirectories of the directory).

The base case is a simple file, in which case the size is obtained by calling the File.length() library method.

When encountering a directory, getSize() sums the sizes of all the files and subdirectories in the given directory. Note that this involves many recursive calls, one for each file and subdirectory. Much of the work is done by the library method listFiles() that conveniently constructs and returns an array of Files, giving the contents of the directory.

20.B Listing Files in or Under a Directory

Consider the file system tree on the right, where boxes are directories and circles are ordinary files.

dir-tre


The program above produces the output to the left, when the program is run on this file system.

Note that the items within the a directory appear below it and are indented. All the work is done by the second (recursive) listFiles(). Its first parameter is the file or directory to list and the second argument is the indentation to use.

We overload listFiles() as a convenience to the user.

The base case is when the first argument is a file, which is simply listed using the File.getName() library routine.

import java.io.File;
import java.util.Scanner;
public class ListFiles {
  public static void main(String[] args) {
    System.out.print("Enter a directory or a file: ");
    Scanner getInput = new Scanner(System.in);
    String dirOrFile = getInput.next();
    listFiles(new File(dirOrFile));
  }
  public static void listFiles(File dirOrFile) {
    listFiles(dirOrFile, "");
  }
  public static void listFiles(File dirOrFile, String indent) {
    System.out.println(indent + dirOrFile.getName());
    if (! dirOrFile.isDirectory())
      return; // base case -- simple file
    File[] dirOrFiles = dirOrFile.listFiles();
    for (int i = 0; i < dirOrFiles.length; i++)
      listFiles(dirOrFiles[i], indent + "  ");
    return;
  }
}
T
  E
  F
    b
    y
    A
      a
  x
  y
  D

When given a directory, listFiles() finds all the files and subdirectories and then calls itself recursively on each one giving an indentation 2 greater than its own indentation.

The algorithm is actually a preorder traversal extended to non-binary trees.

Homework: Compute the postorder traversal of the above tree. Note that there is no inorder traversal defined since this is not a binary tree and we wouldn't know when to visit the parent.

20.C Problem: Binary Search Trees

search-tree

Assume we have a binary tree, whose data component is an int (or any class that extends comparable. Also assume that the tree is sorted in the sense that, for any node all the data to the left is less than the data at the node and all the data to the right is greater than the data at the node.

Such a tree is called a binary search tree. An example is drawn on the right.

Let's say we want to search the tree to see if the number 18 is present (it is) and then we want to search to see if 35 is present (it isn't).

In each case we could use one of our traversal methods and have visit check to see if the desired data item is present.

But we can do much better.

Once we have examined the root we know right away that there is no need to check the right subtree for 18 and no need to check the left subtree for 35.

boolean search (Binsrchtree tree, int value) {
  if (tree==null)
    return false;
  if (value==tree.data)
    return true;
  if (value<tree.data)
    return search(tree.left, value);
  return  search(tree.right, value);
}
  

This will be covered in great detail in 102. An important point is that we would prefer bushy trees, that is, trees which have many leaves (this will be made precise in 102). We can search such trees in time log(N), where N is the number of nodes.

The (untested) code on the right illustrates the idea of eliminating half the tree at each step since it never searches both left and right subtrees.

The interesting question is how do we add and remove items from this tree and keep the crucial property
left subtree < node < right subtree.

20. Problem: Game Trees (Min-Max Search)

game-tree

On the right we see a representative game tree, a key data structure in many 2-person computer games such as chess and checkers. Placing the numbers in the leaves and then searching the tree constitutes is how such programs choose their next move.

For the moment, ignore the numbers in the nodes and assume it is the computer's turn to move. The root of the tree represents the current game position. The arcs leaving a node represent the possible moves from the corresponding position. In the given diagram, there are only two possible moves from each position; in real games this so-called branching factor is much larger and the tree correspondingly much wider.

Since the tree has three levels below the root we are considering three half-moves (i.e., moves by one side). Specifically the arcs leaving the root represent the possible moves of the computer and the two nodes below the root are the resulting position. The arcs leaving these nodes represent the possible replies of the opponent and the bottom row of arcs represent our possible rejoinders to these replies. We have chosen to stop after three half-moves; real programs go deeper.

Since we are not considering any further moves the nodes at depth 3 are leaves. Now for the numbers. Another key component of a game program is the static board evaluator, a method that evaluates how favorable the board position is for the computer. Those are the numbers in the leaves. So the computer would most prefer to be in the position with label 10 and least prefer the position with label -20.

Since the third row of arrows represent the computer's move, it will choose the ones that lead to higher score. Those (maximized) values are the numbers in the bottommost row of internal nodes.

Since the middle row of arrows are the oponent's moves, the choice will be the minimum, which gives the numbers in the middle row of internal nodes.

Finally, the top row of arrows are again the computer's moves so the maximum is computed and that is the value in the root.

As a result of this analysis, the computer will make the right-hand move and expects that the game will have a value of 4. The arcs in magenta represent the computer's belief of the game continuation and the magenta arc is called the principal variation.

20.E Problem: Evaluating Expressions in Prefix Form

// Evaluate expressions in prefix form
// 1.  A double
// 2.  op Expr Expr (op = + - * /)
import java.util.Scanner;
public class EvalExpr {
  static Scanner getInput = new Scanner(System.in);
  public static void main (String[] args)
                          throws Exception{
    System.out.print("Enter an expression: ");
    System.out.println("Answer: " + evalNextExpr());
  }
  static double evalNextExpr() throws Exception {
    if (getInput.hasNextDouble())
      return getInput.nextDouble();
    String s = getInput.next();
    if (s.length()!=1)
      throw new Exception ("Invalid expression.");
    char c = s.charAt(0);
    if (c == '+')
      return evalNextExpr() + evalNextExpr();
    if (c == '-')
      return evalNextExpr() - evalNextExpr();
    if (c == '*')
      return evalNextExpr() * evalNextExpr();
    if (c == '/')
      return evalNextExpr() / evalNextExpr();
    throw new Exception ("Invalid expression.");
  }
}

We normally write expressions using infix form, where the operator is placed in between the operands. For example, 6+4. We have precedence rules so that 6+4*3=18.

Two other forms are used, prefix form and postfix form. One of these is called Polish and the other reverse Polish, but I forget which is which.

In prefix form the operator is placed before the two operands and in suffix form the operator is placed after the two operands.

There is no concept of precedence since there is only one way to evaluate +6*4 3

The program on the right makes the simplifying assumption that spaces are placed after each token. For example the previous expression would be written + 6 * 4 3

Note how short this program is; I suspect it would be quite a bit longer and harder if we didn't have recursion available. In particular the line
return evalNextExpr()+evalNextExpr();
(and the equivalent ones with +,-,*,/) would be more difficult.

20.7 Problem: Towers of Hanoi

public class Hanoi {
  public static void main(String[]args) {
    movePile (Integer.parseInt(args[0]),"left","right","middle","");
  }
  public static void movePile (int n, String from, String to,
                               String aux, String indent) {
    if (n > 0) {    // n==0 is base case, do nothing
      movePile (n-1, from, aux, to, indent+"  ");
      System.out.printf ("%sMove disk (#%d) from %s to %s\n",
                         indent, n, from, to);
      movePile (n-1, aux, to, from, indent+"  ");
    }
  }
}

Illustrating the Problem

Show demo on emacs.

Solving the Problem

Although the problem might seem difficult, it is actually simple once the following (recursive) key is recognized.

To move an n-disk pile from one peg to a second peg

  1. Move the top n-1 disks to the third peg
  2. Move the (now) top disk to the second peg
  3. Move the n-1 disks from the third peg to the second peg

20.8 Problem: Fractals

20.9 Problem: Eight Queens

20.10 Recursion vs. Iteration

Both recursion and iteration are control-flow techniques. That is both determine when other statements are executed.

In theory, they are equivalent: any program using one of them can be converted to using the other.

It is normally rather easy to convert iteration into recursion, but is rarely done for languages like Java where recursion carries extra overhead,

It is in general quite difficult to convert a recursive method into an iterative one, except for ...

20.10.1 Tail Recursion

If a method calls itself only once and this call is the last action of the method, we have tail recursion.

In this case it is easy to convert the method to use a form of iteration.

Very roughly speaking, the reason recursion is difficult to eliminate is that an extra stack frame is needed to hold the values of the local variables in the new invocation. When this invocation terminates, the new frame is dropped and the old frame, corresponding to the caller, is used.

With tail recursion the called method can use the frame of the caller because the caller has nothing left to do and hence no longer needs its frame.

Some compliers automatically convert tail recursion into iteration. This is most common in languages emphasizing recursion such as lisp.

Converting Recursion into Tail Recursion

int recursiveFactorial(int n) {
  if (n == 0)
    return 1
  return n * recursiveFactorial(n-1);
}

int tailRecFactorial(int n) {
  tailRecFactorial(n, 1);
}
int tailRecFactorial(int n, int value) {
  if (n == 0)
    return value;
  return tailRecFactorial(n-1, n*result)
}
  

Because tail recursion can be readily converted to iteration, it is advantageous to arrange for only one recursive call with that call the last action of the method

The code on the right illustrates one such conversion.

The top version is our familiar recursive implementation of factorial. It is not tail recursive since after the recursive call is performed the caller must still do a multiplication. Close, but no cigar!

The bottom version has cleverly pushed the multiplication into the recursive call so that it is now performed before the recursion takes place not after. This minor change would permit several compilers (not for Java) to convert the program into iteration.

The End: Good luck on the final!