Introduction to Computer Science
(2010-11 Fall) Allan Gottlieb
Mondays and Wednesdays 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
(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.)

If instead of taking 0202, 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, Computer Architecture.

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.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 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, 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

1.7: A Simple Java Program

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

// Hello world in the C programming language #include <stdio.h> void main(int argc, char *argv[]) { printf("Hello, world.\n"); }

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

For comparison, the corresponding C program is below it. I put this program in a file called hello.c, but it could have been in a file called xyxxy.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 found in the class System.out.
  5. This line ends the method main.
  6. This line ends the class Hello.

Homework: 1.1, 1.3.

For the benefit of those students with the 7th edition, here are the problems.

1.1 (Displaying three messages) Write a program that displays Welcome to Java, Welcome to Computer Science, and 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

Start Lecture #2

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 vi, notepad, or a variety of alternatives. One 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 (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. For 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.

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

  // 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 bottom example shows the program without using this shortcut.

1.A: 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 the 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 inputs and not do it again.

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 representation of real numbers in Java.

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 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 often called scientific notation. The keyword double signifies that the variable would be given double the amount of storage as a would be 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(java.lang.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 it is fine to just remember how to read doubles. Other methods in the Scanner class include nextInt, nextString, and nextBoolean.

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. 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.

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 and the four real types is the amount of memory used for each value. As the name suggests a byte is stored in a 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 store both a fractional part and an exponent (unlike customary scientific notation, the exponent represents a power of 2 not 10).

Start Lecture #3

Remark: I mistakenly typed in the wrong problem 2.5 (I typed in 2.2). You may do either. By tomorrow the notes will be corrected.

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 them. 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

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 (e.g. 3*(5+2) is 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 *, /, and %, do all + and - in one left-to-right pass.
    So (7 + 3 * 2) / (1 + 2 * 2) == (7 + 6) / (1 + 4) = 13 / 5 = 2

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

Note that these operators are binary. We have unitary operators as well, e.g., unitary -, 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

2.9: Problem: Displaying the Current Time

2.9 (Alternate): Computing How long Ago

// Silly version of How Long Ago
import java.util.Scanner;
public class HowLongAgo {
    public static void main (String[] args) {
    final int daysInMonth = 30; // ridiculous
    final int daysInYear = 360; // 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)*daysInMonth
        + (year-oldYear)*daysInYear;
    // Convert to days / months / years
    int yearsAgo = deltaDays / daysInYear;
    int monthsAgo = (deltaDays % daysInYear) / daysInMonth;
    int daysAgo = (deltaDays % daysInYear) % daysInMonth;
    System.out.println ("The old date was " + yearsAgo +
                " years " + monthsAgo + " months and "
                + daysAgo + " days 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.

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.

The program (on the right) performs this task in three 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.

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 daysInYear is 100 and daysInMonth is 10.

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

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

How can an operating system convert a virtual address into segment number, page number, and offset
Ans: Set daysInYear 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 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 %%).

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.

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 happens automatically (but it does happen). Other times it must be explicitly requested and some of these requests fail.

Coercion vs. 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) {
    int a = 1234567891;   // one billion plus
    short b;
    double x;
    float y;
    x = a;
    y = a;
    b = a;  // error coercions cannot narrow
    b = (short)a;    // narrowing cast
    System.out.println(a + " " + b + " "
               + x + " " + y);
  }
}
javac Test.java; java Test
1234567891 723 1.234567891E9 1.23456794E9
  

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 disasterous: 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 emphasize that you really mean it!
  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.

2.12: Problem: Computing Load Payments

The load 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.

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.

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

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.

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 normal letters, digits, etc are in both ASCII and are in the same position (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 Sepcial Characters

Esc SeqBecomes


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


\bBackspace
\fFormfeed
\r(carriage) Return

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?

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

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);
  }
}

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.

The code on the right has two errors. Clearly a (16-bit) char may not fit into an (8-bit) byte. The (subtle) trouble with the short is that it is signed.

So coercions will not work. 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 not coerced).

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.

Start Lecture #4

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 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 simple things (reading an integer, declaring a string) uses fairly sophisticated concepts.

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 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.
The syntax is very simple: a double quote, some characters, a double quote. The second line is the same string.

Below the horizontal line are legal Java statements. The first declares two strings; they are currently uninitialized.
The second line assigns a string to the first variable The line declares a third string and initializes it.
The last two lines perform string concatenation

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

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, see the reference in the book.

2.16.2: Naming Conventions

Java programmers tend to 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

2.16.4: Block Styles

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

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 to find and fix error made by others. The best I can do, is to write programs in class. I will doubtless have errors and we can fix them together.

2.17.1: Syntax Errors

2.17.2: Runtime Errors

2.17.3: Logic Errors

2.1l.4: Debugging

Homework: 2.11 (copy of problem handed out in class for those w/o 8e).

2.18: (Gui) Getting Input from Input Dialogs

Chapter 3: Selections

3.1: Introduction

Essentially 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 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 infamous 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-else 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 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

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

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.

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.

Selecting One of Several Possibilities

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

Often deeply nested if-the-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.

The 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 we just saw is only one statement, even though it may have hundreds of statements inside its many {} blocks.

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

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, in particular, that the indenting is somewhat misleading as the symmetric placement of ss1, ss2, s3, and ss_default 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).

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. Handout for those w/o 8e.

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 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.

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

3.9: Problem: An Improved Math Learning Tool

Read

Start Lecture #5

Homework: The problems from lecture 4 that weren't assigned (3.7 and 3.9).

3.9 (Alternate): 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.

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.

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.

3.13: Problem: Determining Leap Year

As you 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.

One solution is in the book.

Start Lecture #6

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:  def-stmts
}

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 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 stmts1...stmtsN 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, Handout for those w/o 8e.

3.16: Conditional Expressions

bool-expr ? expr1 : expr2

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

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

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.

  System.out.println ("Please input " + N + "number" + N ? "." : "s.");

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

Homework: Redo 3.7, this time using a switch.

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 6).

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 conversions automatically (e.g., an integer value will be converted 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);
  

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 a spread sheet displays 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. That mean 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)


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 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 (notable 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.

Note that when 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!

Lets write a program that adds two positive 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 number 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 in class; one solution is in the book.

Start Lecture #7

Remark:

  1. Lab 1 part 1 was described last time. It is due monday, 4 October.
  2. Lab 1 part 2 will be demoed in class today.

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. For these first two possibilities a for loop would be more convenient that an while (see section 4.4).
  3. Use a sentinel.
  4. Check for EOF (end of file).
    If, instead of Scanner, we use read(), the program can detect when we reach the EOF. 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.
  5. When we officially learn about methods, we will see that we could pull out the body 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.

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<o 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 is actually a nested loop (loop inside a loop), which officially we haven't seen yet.
  5. The inner is itself a simple loop that sums n numbers.
  6. The outer loop is there since we want to sum n numbers several times, with possibly different values for n each time.

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

public class UseEOF {
  final static int EOF = -1;
   public static void main(String[] args) {
     int c;
     while ( (c = nextCharOrEOF()) != EOF) {
       System.out.write(c);
     }
     System.out.flush();
   }
   public static int nextCharOrEOF() {
      try { return System.in.read(); }
      catch (java.io.IOException e) { return EOF; }
   }
}
  

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 method (think of function) nextCharOrEOF either returns the next character from System.in or it returns the value EOF.

EOF is not special; it is declared in the program.

The magic is the try/catch mechanism. The read() method will raise an exception, namely an IOException if the end of file is reached. This is caught by nextCharOrEOF and the value EOF is returned instead of the value returned by read().

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 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.

do {
  stmts
} while (BE);

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.

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 he finally reaches 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++)

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.

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

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 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 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 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 you get a zero, at which point the nonzero value is the GCD (if they are equal call one of them larger).

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

4.8.2: Predicting the Future Tuition

Start Lecture #8

Remark: Lab1 part three 40% (we will be covering methods today).

  1. Write the following method
    public static int second(int a, int b, int c, int d);
          
    This method returns the second largest value of its four parameters.
  2. Write the main method that
  3. Put both methods in a class named Lab1Part3 (which is in a file Lab1Part3.java). Test your lab on any machine you wish.
  4. Copy Lab1Part3.java to i5.nyu.edu. I advise testing it here as well since that is the machine Radhesh will be using.
  5. Mail the file from i5.nyu.edu to the grader, Radhesh Gupta <radheshg@nyu.edu>. Use the correct subject for the message.

Remark: Tutor/E-tutor available.
Anagha Ashok Pande <anagha.pande@nyu.edu> is the tutor/e-tutor for this class. Anagha is reading the mailing list and may respond to questions there and also via the direct email addr above. In addition, Anagha will be in room 328 WWH the following 5 hours each week for office hours. Feel free to drop by with questions, no appointment is necessary. On Mondays and Tuesdays the hours are 1-2:30; on Wednesdays and Thursdays the hour is 11-12.

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, or for.

In the case of nested loops, the break just breaks out of the inner one.

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 quite complicated and use very 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.

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.

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. For example, how could we possibly have gotten the parameter names we used with Math.pow() correct, since we have never seen the definition?

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.

Homework: 5.1, 5.3.

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 is 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 the 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. This means the on 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 n the right does not set a to 13.

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

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.

Of course 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.

Other 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.

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 double, 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 would coerce a 9 to a double if needed. But if the 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 variable is the portion of the program in which the variable 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. The exceptions are one statement bodies of then clauses, else clauses, and loops that are written without {}.

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 the one exception: if the initialization portion of a for statement contains a declaration, that local variable has scope including the body of the loop.

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.

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 Metrics

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.

public static double floor(double x)
public static double ceil(double x)
public static double rint(double x)
  
  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?

The Math class has three methods that correspond directly to these mathematical functions, but return a type of double. They are shown on the upper 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 (as normal 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 lower case (so called latin) letters are contiguous as are upper case letters.

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

Start Lecture #9

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 print something like 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 only implement 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.

You may recall that early in the course I sometimes wrote String args[] and noticed that it worked as well as String[] args. In fact this has been added to Java just so that C language programmers would be comfortable. However, the String[] args format is preferred.

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 above right or written together as shown below the line.

The new operator allocates space, in the example on the right, enough space for 10 doubles. The effect is that the array x can hold 10 elements.

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

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 is easy, just write name.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.

  1. Declare the array.
  2. Create the array with room for 4 elements.
  3. Place the given 4 values into the 4 slots.

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};

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 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 are the most convenient for stepping through a range of values. The first entry of the array is always index 0 and 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.

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.

6.3: Problem Lotto Numbers

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));
  }
}

Read

6.3 (Alternate): The Mean and the Standard Deviation

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

A Java program to calculate these two values is on the right.

Note that the first loop, which modifies the x array, must use the conventional for loop; whereas, the second loop, which only uses the x array can use the for-each variant.

6.4: Problem: Deck of Cards

public class DeckOfCards {
  public static void main(String[] args) {
    int[] deck = new int[52];
    String[] suits = {"Spades", "Hearts", "Diamonds",
                      "Clubs"};
    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);
      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 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..4) 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 down to the 2 of Clubs (Liang had Clubs and Diamonds reversed).

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. Notice the 3 statement sequence used to swap two values.

Liang printed the first 4 cards; I print the entire deck. The last loop could be written as a one-liner.

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

6.5: Copying Arrays

array1

Serious business. We can no longer put off learning about references.

Consider the situation on the near right. We have created two arrays each holding 6 integers. The first has been initialized, the second has not. The goal is to make the second 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. Both arrays refer to the same content. In this state, changing the contents of one, changes the other.

If the goal is to get the situation in the lower right, you need a loop to copy each entry (or use some built in method that itself has a loop).

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 we see that the array Array1 refers to (or points at) the actual array where the values reside; whereas, the int a and the int b ARE the values 5 and 10. 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 static void printSum(int x, int y) {
  System.out.println(x+y);
}

On the right is the simple printSum method that we saw earlier. It has two integer parameters. If our main method has a declaration int[] A=new int[10], then we can write, printSum(A[4],A[1]). The printSum() method will add the two values and print the sum just as if it was invoked via printSum(8,2);

6.6.A: Passing an Entire Array as an Argument

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

The printArray() method on the right has an (entire) int array as parameter. To call the array is easy: printArray(B) where B is a single-dimensional array of integers. Note that printArray can handle any size array.

The tricky part is the melding of pass by value method invocation and reference semantics for arrays, which we address next.

array-param

Assume we have a method, such as printArray that has an array parameter A and that we call it with an array argument named B.

At the point of the method call the value in B is copied to A, but, as we know, when the method returns any new value in A is NOT copied back to B. This rule still holds for arrays; no change.

The diagram at the right show the configuration when the method is called. Even, if the method changes the value in A, the value in B will not change. It will remain a pointer to the 6 element structure shown.

But now consider what happens if the called method executes A[0]+=1;. This changes not the value in B (a pointer) but instead changes the value in a cell pointed to by B. Hence when the method returns, assuming nothing else happened, the value in B[0] has been increased.

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

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

Start Lecture #10

Remark: The username for the homework solutions is introCS (case sensitive). The password is [to be given orally in class].

Homework: 6.1. Write a program that reads student scores, tets 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.

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).

Remark: Lab1 part4 assigned.

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. (Hint: Declare a counts array of 100 integers. When you read in x, execute counts[x]++.)

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 and the types of the arguments cannot be determined until run time.

I don't believe you can write printf() in Java since Java does not support such a general version of varargs. Of course that does not prevent you from using printf().

Java does permit the last parameter to be of varying length but all of the arguments corresponding to that parameter must be of the same type. Java treats the parameter as an array.

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.

Homework: 6.13. Write a method that returns a random number between 1 and 54, excluding the numbers passin the argument. The method header is specified as follows:

    public static int getRandom(int... numbers)
  

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 there are no is that 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.

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.

Homework: 6.15. Write a method to eliminate the duplicate values in the array using the following method header

    public static int[] eliminateDuplicates(int[] numbers)
  
Write a test program that reads in ten integers, invokes the method, and displays the result.

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.

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.

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 my selection sort is that you first find the minimum element and put it into the first slot (A[0]) and then repeat for the rest of the array (A[1]...A[A.length-1]).

for (j=0; j<A.length; j++)
  if (A[j] < A[0]) {
    temp = A[0];
    A[0] = A[j];
    A[j] = temp;
  }

The difference is in how you find the minimum and get it into A[0]. The code on the right shows the simple method used by bubble sort. The code in the book is very slightly more complicated and perhaps very 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. 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: in A[11]. Then where does A[11] go?
Answer: in A[12].

public static void insSort(int[] a) {
  for (int i=1; <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: Eliminating duplicates. 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/6/docs/api/ (really http://java.sun.com/javase/6/docs/api/).

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.

In fact there are other possibilities 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

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

2d-matrix

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.

Finally, note that you cannot write M[1,0] for M[1][0], further showing 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 a length 1 array, M[0], is a length 3 array, and M[1] is a length 1 array.

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.

7.3.A: Some Examples

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];
    else if (Matrix[i][j]<minimun)
      minimum = Matrix[i][j];
    sum += Matrix[i][j];
  }

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

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.

For a possible ragged array, simply replace n by Matrix.length and replace n by Matrix[0].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+1)*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).

7.4: Passing Two-Dimensional Arrays to Methods

Read

Start Lecture #11

Remark: Using the PC browse, http://java.sun.com/javase/6/docs/api

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 will involve loops (to ensure that the arrays are not ragged).

An alternative would be to ask the user for 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.

Remark: Lab 2, Part 1 assigned. Due in 7 days. Changed to 9 days due to midterm.

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 of the given points P minimizes the total trip, which consists of traveling from S to P and from P to E.

Note the following points about 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 three 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: lab 2 part 2 assigned. Due in 7 days. Changed to 9 days due to midterm.

Remark: End of material on midterm.

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, 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.

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

Publis 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().

Two 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 and empty stack, 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 a special method called a constructor that Java calls automatically whenever an object is instantiated. Although a constructor can in principle perform any action, normally its task is to initialize fields. For example the 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 the 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 (called 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 ...
  10. It doesn't say static and ...
  11. Different circles have differing radii. How does getArea() know which radius to use?

The last two points above are closely related.

Start Lecture #12

Remark: reviewed practice midterm

Homework: UML diagrams are not required for all homework. 8.1, 8.3 (all ch 8 problems are at the 7th floor front desk of 715 bway.)

A static method (also called a class method) is associated with the class as a whole; whereas a non-static method (called an instance method) is associated with an individual object. The Circle1 class has one instance method along with the 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 and there is only one that is shared by all objects instantiated from the class. (An object instantiated from a class is often called an instance of 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.

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.

Start Lecture #13

Remark: Midterm

Start Lecture #14

Remark: Lecture given by Prof. Victor Shoup. (8.7 covered in lecture #15).

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 straight 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 className.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 constructors is to initialize the object being created via new.

Normally, a class provides a constructor with no parameters (as well as 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 withing the current package. Since we don't know about packages, we will say it is visible within the current directory.

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 on the right. 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().

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.

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 4 long's.
  3. There is the block of 4 long's 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.

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 will see f 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 give the variables values at their declaration or ensure that they are assigned a value before there value is requested.

We see here that in some cases, Java does not 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 default is) 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, then it receives the default value (really default reference or pointer) null. If you then use the object, you can get a nullpointerexception.

For example, if LongStack class had no constructors, then the declaration/creation of myLStk in DemoLongStack would be erroneous and the program would not compile 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.

8.5.4: Differences between Variables of Primitive Types and Reference Types

int[] a = {1,2,3}, b;
b = a;
b[1] = 999;
System.out.println(a[1]);

This section finally gives the pictures showing that reference types only point to values; they do not contain the values. All this applies equally well to arrays where we previously discussed it.

The example on the right illustrates reference semantics: The value printed is 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 opportunity as an excuse for learning how to obtain information from http://java.sun.com/javase/6/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 ClassName.methodName. On the right is a simple example basically from the book. The one difference is that I used an import statement. How would it 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 same constructors and a superset of the methods().

8.6.3: Displaying Gui Components

Skipped for now.

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 totalStacked (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.

I could have printed this value in the main() method, but did not. Instead, I defined a method getTotalStacked() that simply returns its value and had the main() method call getTotalStacked(). As we will see in the next section, it is possible to prevent other classes from seeing a variable, which has the great advantage that the programmer 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, field, or 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 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 as does the entity.

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.
  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("The stack has %d items.\n",
                      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 the first stack 2 items stacked, 0 in this stack. Printing and emptying the second stack 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, we have included the following improvements/enhancements.

shortStack2

The diagram shows the situation when main() has called the method for the first time.

We see that the integers are using value semantics (they are their values); whereas the objects (in this case ShortStack's) have reference semantics and only refer to (point at) their values.

Also note the call-by-value semantics. The contents of the argument myStk1 is copied into the parameter SStk.

Start Lecture #15

Remark: Reviewed most of midterm.

Start Lecture #16

Remark: Finished review of midterm.

Remark: Covered section 8.7.

8.11: Array of Objects

Arrays of objects just puts together 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 an array of char's is similar to a Java String (e.g., both have reference semantics), an array of char's is not the same as a String (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.

String s1 = new String();
String s2 = new String("A string");
char[] c1 = {'c','h','a','r','','a','r','r','a','y'};
String s3 = new String(c1);
String s4 = "Another String";
  1. There is a constructor that has no parameters and produces the empty string.
  2. There is another (overloaded) constructor that has a String parameter. This produces a new object with the same contents as the parameter. That is, a copy is made.
  3. 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 s1 = "joe";
s1 = "linda";
String s2 = "linda";

String objects are immutable, they cannot be changed. However, a string variable is mutable and can reference different string objects at different times.

On the right we have two String objects "joe" and "linda", and two String variables s1 and s2. The objects cannot change but we have changed one of the variables.

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.

String s3 = new String ("linda");
if (s1==s2)  YES
if (s1==s3)  NO

Both s1 and s2 refer to this one literal. That is, s1 and s2 contain the same reference and, as indicated to the right they are equal (==).

However, again as mentioned previously, when you create a string from another using the constructor as on the right, a copy is made. Since s3 refers to this copy it is NOT equal (!=) to either s1 or s2.

The last paragraph seems very bad. You have two variables s1 and s2 both of which evaluate to "linda", but they are unequal. We can't seem to tell that the referred values are 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)

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.

String s1 = "joe";
String s2 = new String(s1);
if ("joe".equals(s1)) YES
if ("joe".equals(s2)) YES

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 this is attached to a string object, which it then compares to its parameter. We see an example of the usage on the right. Note that in this example if(s1==s2) would be NO.

There are other string comparison methods defined, e.g., one that ignores case and one (compare.To() 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 objectName.methodName(args). Note that the named object is a de facto extra parameter.

Start Lecture #17

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, "jane".substring(1,2)=="a" .

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.

For example,

String s = String.format("3.777 rounded to 2 places is %4.2f", 3.777);
  
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.

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.

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;
}

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 used for a test, and the prefix to used for a conversions.

Homework: 9.1 and 9.5.

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 the 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 can 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 StringBuffer 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 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.

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.

Start Lecture #18

Homework: 9.1, 9.5, 9.11.

Remark: On monday night I was editing my magazine column and needed a function to reverse a number. Since I just taught it a few hours previously, 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());
    }
}
  

Remark: Added the missing two lines to the Calculator.

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.

0.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

    Scanner input = new Scanner(new File ("existing.text"));
  
(assuming the necessary import's have been executed.

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.

0.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.

Start Lecture #19

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). 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 change, 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.

public class NoChanges {
  private int x;
  public NoChanges(int z) {
    x = z;
  public int getX() {
    return x;
  }
}

public class Changes { public int a; public int b; public Changes(int r, int s) { a = r; b = s; } }
public class Maybe { private Changes c; public Maybe(int w) { c = new Changes (w,w); } public Changes getC() { return c; } }
public class Immutable { public static void main (String[] Args) { NoChanges nc = new NoChanges(5); Changes c = new Changes (5,6); c.a = 9; Maybe mb = new Maybe(12); System.out.println(mb.getC().a); // mb.c.a = 1; Will not compile but mb.getC().a = 1; // works fine } }

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!

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 nc.x==5 and will never change.

Similarly the class Changes is not immutable (it is mutable). We can say

    Changes c = new Changes (5,6);
    c.a = 9;
  
and have changed the object c by changing its first component from 5 to 9.

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.

Thus 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 (a.k.a. class variables). 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 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, 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 have no name we can use. Hence, Java defines this to serve such a purpose.
  4. Access 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 methods form the implementation.

When looked at from the user's point of view, the class encapsulates the knowledge needed to use these object. 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 acct 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 actually 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 10/13/2010 Withdrawal 30.00 70.00 10/13/2010 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.

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. Provide get and set methods as appropriate. In general only expose to the user what you guarantee will not change.

10.11.4: Clarity

Read.

10.11.5: Completeness

Read.

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 (why set the same variable to the same thing everything a new object is made).

Some methods do not need any object of the class; typically the main method has this property. Such methods must be declared static as there is no object they can be attached to.

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 (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.

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 the order so that the resulting quadrilateral is convex as shown in the diagram on the right. 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.

Start Lecture #20

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 new 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 invoke constructors in Parent. How is this done. If, in the child constructor, we write new Parent(), we would create a new object rather than creating/initializing a part of the current object, namely 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.

Were there no inheritance involved, the rhombus constructor would have 4 assignment statements using this. 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.

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 constructor can invoke another constructor in the same class and, if it is 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

Start Lecture #21

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() that have the same signature (i.e, the same number and types of parameters) and the same return type. In such a case, an object in 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 %i).

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

Let's look at a larger example. I have filled in some of the details for the geometric methods sketched above. 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, need to 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 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 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.0,4.0));
    System.out.printf("rect1 has width=%f, height=%f, and color=%s\n",
              rect1.getWidth(), rect1.getHeight(), rect1.color);
    System.out.printf("rect2 has width=%f, height=%f, and color=%s\n",
              rect2.getWidth(), rect2.getHeight(), rect2.color);
    System.out.printf("rect3 has width=%f, height=%f, and 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 has width=1.000000, height=1.000000, and color=blue rect2 has width=1.000000, height=1.000000, and color=blue rect3 has width=1.000000, height=4.000000, and color=blue Rectangle says 1.000000, but quad says 123.400000 rhom1 has side length=1.000000

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?

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 toString() in the 2nd version of the geometry classes.

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.toString(), 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 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, but instead 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 Rectangle.java, namely

    void printArea() { System.out.printf("The area of %s is %f\n", this.toString(), this.area()); }
  
(Recall that the Rectangle class already has the method area() and inherits toString() from Object).

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.

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 the classes? It would work, but it sure seems ugly.

First Try

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.

Second Try

Let's say that we don't want to add printArea() to any of the geometry classes since it is somewhat special purpose. So we write the following one-line method in our main class

    static void myPrintArea(GeometricObject geoObj) {
      System.out.printf("My area of %s is %f\n", geoObj.toString(), geoObj.area());
    }
  
and 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; converts the 3 to floating point, 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 Rectangle never contains a rectangle. It often does contain a reference to a rectangle, but never contains the object itself (reference vs. value semantics).

Even more interesting a variable of declared type rectangle can, due to polymorphism, at times contain a reference to a GeometricObject, at other times a reference to a Quadrilateral, and even sometimes a reference to an Object.

The type of the object to which a variable refers is called its actual type.

How the Second Try Works

Recall the situation. We have a method myPrintArea() defined in the main class 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 try does indeed work. This situation is illustrated in the code constituting section 11.B.

In C++ static dispatch is used by default but dynamic dispatch can be chosen by declaring the method to be virtual.

Start Lecture #22

11.9: Casting Objects and the instanceOf Operator

int    i1=1,  i2=2,  i3=3;
double d1=1., d2=2., d3=3.E50;
d1 = i1;      // or d1 = (double)i1;
// i2 = d2;      Compile-time error
i2 = (int)d2; // Fine
i3 = (int)d3; // Gives wrong answer

Recall the situation for primitive types. Java performs safe type conversions (called coercions) automatically, but will not perform unsafe conversions, unless told by an explicit cast. Referring to the code on the right we see an illustration that

Upcasting and Downcasting

geometry-tree

We now want to understand the corresponding actions for objects in the class hierarchy rather than for primitive types. 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.

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;
//    c3 = (Child)p3; run-time error

The code on the right 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 the variable contains the value and hence both are of the same type; whereas, variables of any object type contain a reference (to an object) and the object itself has a type.

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?

Parent p;
Child  c;
// much code involving p and c
c = (Child) 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.

11.B: Quadrilateral and Friends (ver 2)

geometry-tree
 
public class GeometricObject {
  protected String color = "blue";
  public void printArea() {
    System.out.printf("The area of %s is %f\n", this.toString(), this.area());
  }
  public String getColor() {
    return color;
  }
  public double area() {
    System.out.printf("Error area not overridden in %s\n", this.toString());
    return 0;
  }
}

public class Point extends GeometricObject { public 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 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) { 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 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 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 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 } 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()); } }
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

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. Add your triangle class from the homework above to these files and incorporate your tests of triangle into our TestQuad (which should be named TestGeom).

Start Lecture #23

Remark: Lab 4 Assigned.

11.10: The Object's equals() Method

public boolean equals (Object obj) {
  return (this == obj);
}

String s1="xy", s2="xy"; if (s1.equals(s2)) System.out.println("yes");
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 yes.

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.

An Illustrative Technical Point: Overriding vs. Overloading

public boolean equals(Circle c) {
  return radius==c.radius

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.

Thus, if we have an object array Object[]obj and if a circle is placed on this array, then

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 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.

std-rhomb

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.


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 improvement is to add another rhombus constructor. This one takes a point, a side-length, and an angle and produces the rhombus shown in the diagram on the top right. The constructor itself is in the 2nd frame.

Note that if Θ=Π/2, the rhombus is a square.

This improvement has little 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 4th frame. We see the try and catch. What happens is that the client calls the constructor, which raises (throws in Java-speak) the exception. Since the constructor does not catch this exception, Java automatically raises it in the caller, namely the client code, where it is finally caught.

It is this automatic call-back that exceptions provide that the original code does not.

13.A: Quadrilateral and Friends (ver 3)

This code includes my implementation of the Circle class.

public class GeometricObject {
  protected String color = "blue";
  public void printArea() {
    System.out.printf("The area of %s is %f\n", this.toString(), this.area());
  }
  public String getColor() {
    return color;
  }
  public double area() {
    System.out.printf("Error: area not overridden in %s\n", this.toString());
    return 0;
  }
}

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 { public Point center; public double radius; private static double maxRadius = -1; private static Circle maxCircle = null; public Circle(Point center, double radius) { this.center = center; this.radius = radius; this.color = "pink"; if (radius > maxRadius) { maxRadius = radius; maxCircle = this; } } public Circle(double radius) { this(new Point(0.,0.), radius); } public Circle(Point center) { this(center, 1.0); } public Circle() { this(1.0); } 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; return false; } }
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; 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); } 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()); } 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. Use 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

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 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 decedents). There is very little we can say or do about these other than they are rare.
  2. Many errors throw exceptions in (a subclass of) the Exception class. 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. Exceptions in (a descendant of) Error or RuntimeException are unchecked. Exceptions in (a descendant of) Exception are checked.

The difference for us between checked and unchecked exceptions are that the header of any method that can 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.

13.5.2: Throwing Exceptions

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; } }

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).

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.

If an 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 exception is raised in the statement that called the method. If the current method is the main method, the program is terminated.

The above paragraph is for red-box (i.e., unchecked) exceptions. Java will not compile a method in which a blue-box (checked) exceptions might occur not within a try block.

try {
  statements;
}
catch (Exception1 ex1) {
  handler for exception1;
}
catch (Exception2 ex2) {
  handler for exception2;
}
...
catch (ExceptionN exN) {
  handler for exceptionN;
}
statements after the catches

Thus, to catch an exception you must first delimit a sequence of statements withing 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.

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 actually catches exceptions in any descendant class of C as we.

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 the relevant portion of 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 Use unit square.\n", ex.getMessage()); rhom1 = new Rhombus (origin, 1.0, Math.PI/2.0); } } }
rhom1 error: Rhombus with unequal sides. Use 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 ans 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.

Start Lecture #24

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.

Remark: Lab 4 is accessible directly off the home page.

13.6: The finally Clause

try {
  try block
}
catch (something){
  catch block
}
finally {
  finally block
}
the rest

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).

  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.

13.7: When to Use Exceptions

As mentioned above, it is silly to use an exception as a complicated if statement. It is the automatic up-call from the called method back to the caller 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.

public class MyException extends Exception {
  MyException(int code, String str) {
    super(String.format("Exception raised: (code=%d) %s\n"),
                        code, str);
  }
}

However, you can define your own subclass of the Exception class (say MyException) and have a MyException constructor take whatever arguments you want. The example on the right takes an integer code in addition to the usual string.

13B: Another Example: Copying One File to Another

import java.io.*; // FileReader/Writer IO/FileNotFoundException
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();
  }
}

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.

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.toString(),
                      this.area());
  }
  public String getColor() {
    return color;
  }
  public double area() {
    System.out.printf("Error: area not overridden in %s\n",
                      this.toString());
    return 0;
  }
}

Consider the area() methods sprinkled throughout our geometry example. In particular, look at the area() method defined in the GeometricObject class, 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 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.toString(),
                      this.area());
  }
  public String getColor() {
    return color;
  }
  public abstract double area();         // must be overridden
}

On the right we see a 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. That is, the value of the variable must be an object 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

Start Lecture #25

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++, does 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

// Interface for comparing objects.
// Defined in java.lang
package java.lang;
public interface Comparable {
  public abstract int compareTo(Object o);
}

This entire interface is shown on the right. We have not studied packages so ignore the package statement.

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.

For all these classes: 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.

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 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, which appears right after the main() method in the second frame, 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.

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 abut 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 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.

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;
        }
  }
}

Generic Sorting

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.

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

The previous section illustrated both an advantage and slight annoyance with the wrapper classes Double, Character, etc. when compared to the corresponding primitive data types.

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 here 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.

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 %6.1f %6.1f %5d %5d %5d %5d %5c %5s\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; } } }

Sorted by name
Alice     3   -8.0   -8.0   -55  -555  -555  -555     8    78
Alice     9   -8.0   -8.0    -8    -8    -8    -8     8     8
Harry    10   -5.0   -5.0    -8    -8    -8    -8     8    88
Jessica   4   -5.0   -5.0    -5    -5    -5    -5     A     A
John      2    0.0    0.0     0     0     0     0     Q     Q
Judy      8    0.0    0.0     0     0     0     0     Z     Z
Mary      7    2.5    2.5     2     2     2     2     a     a
Robert    1    3.0    3.0     7     7     7     7     q     q
Sam       5    7.8    7.8     8     8     8     8     q     q
Sarah     6    8.2    8.2    33   333   333   333     z     z
10                           |
1   Robert                   |
2   John                     |
3   Alice                    |
4   Jessica                  |
5   Sam                      |
6   Sarah                    |
7   Mary                     |
8   Judy                     |
9   Alice                    |
10  Harry                    |

14.12 The BigInteger and BigDecimal Classes

14.13: Case Study: The Rational Class

Homework: 14.5 (omit UML diagram).

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?

Naturally, 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("\t\t\t\t(%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).

Start Lecture #25

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<10; i++)
      if (i<0)
        System.out.printf("\t\t\t\tFibonacci is not defined 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;
    if (n>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 8 34 34 9 55 55

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, 21, 34, ...
  

The limiting ratio f(n)/f(n-1) as n approaches infinity is called the golden mean and comes up in a number of biological settings.

The code for both recursive and iterative solutions is on the right.

Again the methods liberally define Fibonacci values for negative n, but the main() method flags it as an error.

Draw on the board the diagram showing all the recursive calls that occur when you invoke recFib(4).

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 the base case, the recursive calls bring you closer to the base case. For example, in the factorial problem (excluding negative values), 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 the recursive call has a smaller value of the parameter than the original and is thus 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));
}  

One base case is that an empty string or a string of length one is a palindrome.

Another base case is that if the first and last characters are not equal than 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 is a palindrome.

An Alternate Interpretation

The above code is from the book. I might prefer to view the solution with only one base case 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 case is that empty and length 1 strings are palindromes.

If we are not in the 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 case
  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 the more general question is the portion of the string from position low to position high a palindrome and then recursively restrict the range low...high, while keeping the same string. This program is shown in the top frame on the right.

One objection to the program is that it is more awkward for the user who would typically 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.

20.A: Binary Trees: A Recursive Data Structure

tree

On the right is an preliminary look at an important data structure in 102: a binary tree.

These trees have two kinds of nodes: interior nodes drawn as squares, and leaves drawn as circles. Each node has two children; each leaf has no child. A node also contains data, in this case a character.

This data structure is called a 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) {  // constructs a leaf
    this(data, null, null);
  }
}

The code on the right is a class for this data structure. Note that we use the name Tree when the object is in fact the single node of a tree.

We see that a Tree has three components, two Tree's and a char, which seems crazy! How can a Tree contain 2 Tree's?

The answer is an old friend, reference semantics. The variables left and right do not contain trees, but instead contain references to trees. Thus a Tree object contains two references to Tree's and a char.

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.

20.A.1: Constructing and Traversing a 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) {  // constructs a leaf
    this(data, null, null);
  }
  public void preOrderTraverse () {
    System.out.printf("%c ", this.data);
    if (this.left != null) {  // leaf?
      this.left.preOrderTraverse();
      this.right.preOrderTraverse();
    }
  }
  public void postOrderTraverse () {
    if (this.left != null) {  // leaf?
      this.left.postOrderTraverse();
      this.right.postOrderTraverse();
    }
    System.out.printf("%c ", this.data);
  }
  public void inOrderTraverse () {
    if (this.left != null) {  // leaf?
      this.left.inOrderTraverse();
    }
    System.out.printf("%c ", this.data);
    if (this.right != null) {  // leaf?
      this.right.inOrderTraverse();
    }
  }
  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.println("Preorder Traversal");
    t1.preOrderTraverse();
    System.out.printf("\n\nPostorder Traversal\n");
    t1.postOrderTraverse();
    System.out.printf("\n\nInorder Traversal\n");
    t1.inOrderTraverse();
  }
}

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

The program on the right constructs the tree in the diagram and then traverses the tree in three different orders.

Construction

First let's ignore the three traverse methods and concentrate on how the beginning of the main() method constructs the desired tree, which I have redrawn here for convenience.

tree

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 constructor. In main() I deliberately reused some Tree variables as I believe it is instructive.

To construct an interior node, we use the first construct and thus need to supply two child trees in addition to the data. This means we must construct the children before constructing the interior node. The tree is constructed from the bottom up.

On the board execute each line of main() showing the variable name, the reference, and the referred to Tree. As execution proceeds, you will see why we called the objects trees not nodes.

Start Lecture #26

Traversals

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. In our example, visiting a node consists simply of printing the character data item.

How do we ensure we visit each node (exactly once) and in what order should we visit them?

Three orderings are common: pre/post/in-order. In all three left children are visited before right children. 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.

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 just look at a familiar structure, the file system tree.

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 not trees, but we ignore that complication).

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) {
    long size = 0; // Size of this file or directory
    if (! file.isDirectory())  // Base case, 1 file
      size += file.length();
    else { // All files and subdirectories
      File[] files = file.listFiles();
      for (int i = 0; i < files.length; i++)
        size += getSize(files[i]);
    }
    return size;
  }
}

Note that a directory can have any number of files and sub-directories so not all interior nodes have exactly 2 children. Thus we do not have a binary tree and cannot use inorder traversal.

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 and then the size is obtained by calling the File.length() library routine.

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.

20.B: Listing Files in or Under a Directory

Consider the file system tree just below, where boxes are directories and circles are ordinary files.

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, 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;
  }
}
dir-tre

The program on the top right produces the output on the bottom right, when the program is run on this file system.

T
  E
  F
    b
    y
    A
      a
  x
  y
  D

Note that the items within the a directory appear below it and are indented. All the work is done by listFiles(). Its first parameter is the file or directory to list and the second argument is the indentation to use.

The base case is when the first argument is a file, which is simply listed using the File.getName() library routine.

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.

20.7: Problem: Towers of Hanoi

We already discussed this a little.

Show demo on emacs.

20.8: Problem: Fractals

20.9: Problem: Eight Queens

20.10: Recursion vs. Iteration





The End: Good luck on the final