Start Lecture #1
I start at 0 so that when we get to chapter 1, the numbering will agree with the text.
There is a web site for the course. You can find it from my home page, which is http://cs.nyu.edu/~gottlieb
The course text is Liang,
Introduction to Java Programming (Brief Version)
,
Eighth Edition (8e)
Replyto contribute to the current thread, but NOT to start another topic.
top post, that is, when replying, I ask that you either place your reply after the original text or interspersed with it.
musttop post.
Grades are based on the labs and the final exam, with each
very important.
The weighting will be approximately
30%*LabAverage + 30%*MidtermExam + 40%*FinalExam
(but see homeworks below).
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.
I make a distinction between homeworks and labs.
Labs are
Homeworks are
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).
You may solve lab assignments on any system you wish, but ...
I sent it ... I never received itdebate. Thank you.
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).
Good methods for obtaining help include
You labs must be written in Java.
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.
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
Introductionwith a Programming Prerequisite
How weird is this?
The formal prerequisite for 0101 is 0002, which teaches the Python programming language. (I had a tiny, insignificant part in the development of Python when I first arrived at NYU, 30 years ago.) (Grayed out material is not part of the official course.)
If instead of taking 0002, you have programmed in some other language (say C/C++), that is fine.
If, however, you are already a wizard Java programmer (or even a mere expert), you are taking the wrong course—you would be wasting somebody's money and, more significantly, wasting much of your time.
This course, indeed the CS major sequence, emphasizes software, i.e., computer programs, rather than hardware, i.e., the physical components of a computer.
We teach a little hardware in 201, Computer Systems Organization, giving a high-level, non-detailed view, and present much more in 436, the Computer Architecture elective.
In general, the NYU course sequence offers a top-down view: we first show you how to program in high-level languages such as Java and Python, later we present the assembly language that is essentially the language understood by the computer itself, and later still we describe the how the electronic components in a computer are able to actually execute these programs.
Many universities follow this approach. Others provide a bottom-up sequence beginning with the components, then low-level (assembly) languages, and then high-level languages.
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.
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.
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.
Computers can access any byte in RAM quite quickly, which is wonderful. However, there are at least three problems with RAM.
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).
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.
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.
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).
blanked) and then CD-RW can be rewritten. The user cannot simply rewrite a single word or byte, leaving the remaining data unchanged. So it is not rewritable the way RAM is.
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
.
These are becoming less important and we will not discuss them.
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.
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.
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).
I assume you have written programs (perhaps in 0002) and thus know what they are.
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.
You should all have accounts on i5.nyu.edu. Your username and password on i5 is the same as on home.nyu.edu. You will need to access i5 for lab1 (which has not yet been assigned.
If your personal computer runs MS Windows, please download two important free apps: putty and winscp. Putty is used to connect to outside machines. You should be able to connect to i5.nyu.edu
If your personal computer is an Apple MAC, discover how to bring up a terminal window (a.k.a. a command window). MacOS already has the needed commands, type ssh <username>@i5.nyu.edu, where <username> is your username.
Java is a very popular, modern, general purpose, programming language. It comes with an extensive standard library that aids in writing graphical programs, especially those, called applets, that are invoked from browsers, e.g., firefox.
Java has extensive support for the modern software development
methodology called object-oriented programming
.
Java is a full-featured, and thus large, programming language. In its entirety, Java is not simple; but, in the beginning at least, we will be able to avoid most of the tricky parts.
Any programming language needs a detailed, precise specification describing the syntax and semantics of the language. It is basically the rules that determine a legal Java program. We will not need this level of precision.
Changing the specification essentially changes the language. The Java spec is stable.
The Application Program Interface (API) is defined by the standard library that comes with Java. It is comparatively easy to extend the API—write another library routine—and this does occur.
There are several versions of Java; we use Java SE 1.6 (or perhaps 1.7), which we will just call Java.
The programs used to compile and run Java programs are part of the Java Development Toolkit (JDK).
Instead of using the JDK, one can use an Integrated Development Environment (IDE). Several IDEs are available. I will use only the JDK. You may develop your labs using either the JDK or an IDE, but the final product must be a Java program that can be run with just the JDK.
The various IDEs are all slightly different; whereas the JDKs (in principle) all accept the same language and produce the same results.
You may develop you lab assignments on any Java system you wish. However, we shall run you program using a Java SDK so you should test your program that way as well. I shall give examples of SDK usage (it is very easy).
// Hello world in the C programming language #include <stdio.h> void main(int argc, char *argv[]) { printf("Hello, world.\n"); }
// Hello world in Java public class Hello { public static void main (String[] args) { System.out.println("Hello, world."); } }
On the right we see a simple Java program that prints the sentence
Hello, world.
.
This program must be contained in the file
named Hello.java
.
For comparison, the corresponding C program is directly above the
Java program.
I put this program in a file called hello.c
, but it
could have been in a file called xyzzy.c
(the .c
is important).
Although they may look different, these two programs are basically the same. We now discuss briefly the Java version, line by line.
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.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
:
main
.args
, which is an
array each of who's elements is of
type String.main
) invokes
the method println
, which is part ofthe field out in the class System.out.
Start Lecture #2
Remark: MacOS users need to set textedit to produce text files (handout). Windows users need putty and winScp.
Homework: 1.1, 1.3.
For the benefit of those students who may not yet have the book, here are the problems.
1.1 (Displaying three messages) Write a program that displays
Welcome to Java Welcome to Computer Science Programming is fun
1.3 (Displaying a pattern) Write a program that displays the following pattern:
J A V V A J A A V V A A J J AAAAA V V AAAAA J J A A V A A
Unless otherwise stated homeworks are from the
Programming Exercises
at the end of the current chapter.
They are not from the Review Questions
A Java program is created using a text editor. I use emacs. Others use vim, textedit, notepad, or a variety of alternatives. Another possibility is the editor included with a Java IDE.
Java programs are compiled using a Java compiler. The standard compiler included with a JDK is called javac. To compile our Hello program, located in the file Hello.java, one would write
javac Hello.java
Javac translates Java into an intermediate form normally called bytecode. This bytecode is portable. That is the bytecode produced on one type of computer can be executed on a computer of a different type.
The bytecode is placed in a so-called class file, in this case the file Hello.class
Our C version of Hello, if contained in the file Hello.c, could be compiled via the command
cc -o Hello Hello.c
The resulting file Hello is not portable. Instead it has instructions suitable for one specific machine type (and software system).
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.
Since essentially no hardware/OS can execute Java bytecode
directly, another program is run and is given the bytecode as data.
This program is typically included in any Java IDE.
When using the JDK the program is called java
(lower
case).
For our example the bytecode located in the file
Hello.class
is executed via the JDK command
java Hello
(Note that we must write Hello and not Hello.class.)
// Hello world in Java -- gui version public class HelloGui { public static void main (String[] args) { javax.swing.JOptionPane.showMessageDialog(null, "Hello, world."); } }
Java comes with a large library of predefined functions. The top example on the right shows how just changing the output function causes a dialog box to be produced. Naturally, this code only works on a graphical display.
A difficulty is that actually explaining how the dialog box appears on the screen is quite complicated, involving widgets, fill-rectangle, and other graphics concepts.
// Hello world in Java -- pedantic version public class HelloPedantic { public static void main (String[] args) { java.lang.System.out.println("Hello, world."); } }
In fact, our original Hello
example used a shortcut.
The class System
is actually found in the package
java.lang
(which is searched automatically by javac).
The code on the right shows the program without using the shortcut.
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.
Let's solve quadratic equations Ax2 + Bx + C = 0. Computational problems like this often have the form
For our first example we will hard-wire
the input (thereby
avoiding 1) and not do it again (thereby avoiding 4).
What is the input?
Ans: The three coefficients A, B, and C.
How is the output computed?
Ans: -B +- sqrt(B2-4AC)
What about sqrt of a negative number?
What about it?
Mathematically, you get complex numbers.
We will choose A, B, C avoiding this case.
A better program would check.
public class Quadratic1 { public static void main (String[] args) { double A, B, C; // double precision floating point A = 1.0; B = -3.0; C = 2.0; double discriminant = B*B - 4.0*A*C; // We assume discriminant >= 0 for now double ans1 = (-B + Math.pow(discriminant,0.5))/(2.0*A); double ans2 = (-B - Math.pow(discriminant,0.5))/(2.0*A); System.out.println("The roots are " + ans1 + " and " + ans2); } }
The program is on the right. Note the variables are declared to have type double. This is the normal declaration for real numbers in Java. Out of habit I capitalized A,B,C. A bad idea.
We can assign values to variables either when we declare them or separately. Both are illustrated in the example.
Note that the first two lines and the last two lines are the same as from the first example.
How does the println work?
In particular, what are we adding?
Ans: The + is overloaded
.
X+Y means add if X and Y are numbers it means concatenate if X and Y
are strings.
But in the program we have both strings and numbers.
When the operands are mixed, numbers are converted (coerced) to
strings.
The name double is historical.
On early systems real numbers were called
floating point because the decimal point was not in a fixed
location but instead could float.
This way of writing real numbers is akin to
scientific notation
.
The keyword double signifies that the variable is
given double the amount of storage as is given to a variable
declared to be float.
The previous example was quite primitive. In order to solve a different quadratic equation it is necessary to change the program, recompile, and re-run.
In this section we will save the first two steps, by having the program read in the coefficients A, B, and C. Later we will use a loop to enable one run to solve many quadratic equations.
On the right is the program in a pedantic style. We show the program as it would normally be written below.
public class Quadratic2Pedantic { public static void main (String[] Args) { double A, B, C; double discriminant, ans1, ans2; java.util.Scanner getInput; getInput = new java.util.Scanner(java.lang.System.in); A = getInput.nextDouble(); B = getInput.nextDouble(); C = getInput.nextDouble(); discriminant = B*B - 4.0*A*C; // We assume discriminant >= 0 for now ans1 = (-B + Math.pow(discriminant,0.5))/(2.0*A); ans2 = (-B - Math.pow(discriminant,0.5))/(2.0*A); java.lang.System.out.println("The roots are " + ans1 + " and " + ans2); } }
Note the following new features in this program.
import java.util.Scanner; public class Quadratic2 { public static void main (String[] Args) { Scanner getInput = new Scanner(System.in); double a = getInput.nextDouble(); double b = getInput.nextDouble(); double c = getInput.nextDouble(); double discriminant = b*b - 4.0*a*c; // We assume discriminant >= 0 for now double ans1 = (-b + Math.pow(discriminant,0.5))/(2.0*a); double ans2 = (-b - Math.pow(discriminant,0.5))/(2.0*a); System.out.println("The roots are " + ans1 + " and " + ans2); } }
On the right we see the program rewritten in the style that would normally be used.
The above explanation is way more complicated that the program
itself!
For now, but not for later, it is fine to just
remember how to read doubles.
Other methods in the Scanner class include
nextInt(), nextBoolean(),
and next().
The last method finds the next token
(very roughly the next
string).
Homework: 2.1, 2.5
For those without the 8e.
2.1: Write a program that reads Celsius degrees (in double), converts it to Fahrenheit, and displays the result. The formula is F=(9/5)C+32.
2.5: Write a program that reads in the subtotal and the gratuity rate, then computes the gratuity and the total.
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.
keyword. (e.g., class, double, if, and else).
As we have said classes contain data (normally called fields) and methods. Fields are examples of variables, but we haven't seen any yet. Note that the various doubles above are not (directly) members of the class. Instead, they are part of the main() method, which itself is a member of the class.
Later we shall learn that there are two kinds of fields, static and non-static.
Another kind of variable is the local variable, which is a variable declared inside a method. We have seen several, e.g. discriminant.
The final kind of variable is the parameter. We have seen one example so far, the args parameter always present in the main method.
Note that discriminant is not a parameter. The variable in Math.pow() corresponding to discriminant is the parameter. In general variables are classified by how they are declared, not by how they are used.
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.
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.
Numerical values in Java come in two basic types, corresponding to mathematical integers and real numbers. However each of these come in various sizes.
Mathematical integers have four Java types.
Mathematical real numbers have two Java types
The difference between the four integer types is the amount of memory used for each value. As the name suggests a byte is stored in a single byte. A short is stored in two bytes; an integer is stored in four; and a long is stored in eight.
A float is stored in four bytes and a double is stored in eight.
For integers, allocating more storage permits a wider range of values.
Floating point is more complicated. The extra storage is used to extend the range of both the fractional part and the exponent. Recall that floating point numbers are represented in what is essentially scientific notation, so they contain both a fractional part and an exponent (unlike customary scientific notation, the exponent represents a power of 2 not 10).
Start Lecture #3
Remark: You might like http://http://www.drjava.org
Java, like essentially all languages, defines +, -, *, and / to be addition, subtraction, multiplication, and division.
(-1) % 5 == -1 (-1) modulo 5 == 4
Java defines % to be the remainder operator. Some people (and some books and some web pages) will tell you that Java defines % to be the mathematical modulo operator. Don't believe it! Remainder and modulo do agree when you have positive arguments, but not in general.
Literals are constants that appear directly in a program. We have already seen examples of both numeric and string literals.
For integers, if the literal begins with 0 followed by a digit, the value is interpreted as octal (base 8). If it begins with 0x, it is interpreted as hexadecimal (base 16). Otherwise, it is interpreted as decimal (base 10).
What is the use of base 8 and base 16 for us humans with 10 fingers
(i.e, digits)?
Ans: Getting at individual bits.
The literal is considered an int unless it ends with an l or an L, in which case it is considered to be a long.
Real numbers are always decimal. If the literal ends with an f or an F, it is considered a float. By default a real literal is a double, but this can be emphasized by ending it with a d or a D.
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.
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.
The last step also illustrated integer division, which in Java
rounds towards zero.
So (5 - 12) / 4 = (-7) / 4 == -1.
Note that these operators are binary (i.e they operate on two
values).
Java has unary operators as well, e.g., unary -, which are evaluated
first.
So 5 + - 3 == 2 and - 5 + - 3 == -8
Unary + is defined as well, but doesn't do much.
Unary (prefix) operators naturally are applied right to left
So 5 - - - 3 == 2 and 5 + + + + 3 == 8
+ + x is very different from ++ x. The same holds for - - versus --. This will be made clear in section 2.10.
It is no fun to do one from the book so instead we do a silly
version of a program to tell how long it is from one date to
another.
For example from 1 July 1980 to 5 September 1985 is
5 years, 2 months and 4 days
.
Such a program would actually be useful. However, we will do a silly version since we don't yet know about arrays and if-then-else.
// Silly version of How Long Ago import java.util.Scanner; public class HowLongAgo { public static void main (String[] args) { final int MONTH_DAYS = 30; // ridiculous final int YEAR_DAYS = 12*MONTH_DAYS; // equally ridiculous Scanner getInput = new Scanner(System.in); System.out.println("Enter today's day, month, and year"); int day = getInput.nextInt(); int month = getInput.nextInt(); int year = getInput.nextInt(); System.out.println("Enter old day, month, and year"); int oldDay = getInput.nextInt(); int oldMonth = getInput.nextInt(); int oldYear = getInput.nextInt(); // Compute total number of days ago int deltaDays = (day-oldDay) + (month-oldMonth)*MONTH_DAYS + (year-oldYear)*YEAR_DAYS; // Convert to days / months / years int yearsAgo = deltaDays / YEAR_DAYS; int monthsAgo = (deltaDays % YEAR_DAYS) / MONTH_DAYS; int daysAgo = (deltaDays % YEAR_DAYS) % MONTH_DAYS; System.out.println ("The old date was " + yearsAgo + " years " + monthsAgo + " months and " + daysAgo + " days ago."); } }
The program (on the right) performs this task in four steps.
Note that monthsAgo is not the total
number of months ago since we have already removed
the
years.
I computed the three values in the order days, months, years, which is from most significant to least significant. You can instead compute the values in the reverse order (least to most significant). To see an example, read the book's solution.
Splitting a combined value into parts is actually quite useful. Consider splitting a 3-digit number into its three digits. For example given six hundred fifteen, we want 6, 1, and 5. This is the same problem as in our program but YEAR_DAYS is 100 and MONTH_DAYS is 10.
How would you convert a number into millions, thousands, and
the rest?
Ans: Set YEAR_DAYS = 1,000,000 and MONTH_DAYS = 1,000.
How would you convert dollars into hundreds, twenties, and
singles?
Ans: Set YEAR_DAYS to 100 and MONTH_DAYS to 20.
How can an operating system convert a virtual address into
segment number, page number, and offset
Ans: Set YEAR_DAYS to the number of bytes in a segment and
... oops wrong course (and real OSes do it differently).
Homework: 2.7 Write a program that prompts the user to enter the number of minutes and then calculates the (approximate) number of years and days it represents. Assume all years have 365 days.
Statements like x = x + y; are quite common and several languages, including Java and C, have a shorthand form x += y;.
Similarly Java et al. has -=, *=, /=, and %=. Note that there is NO space between the arithmetic operator and the equal sign.
An especially common case is x = x + 1; and Java and friends have an especially short form x++. Similarly, we have x-- (but NOT ** or // or %%). Note that there is NO space between the two arithmetic operators. If you write x + +, you are specifying two unary additions (see section 2.8.3 and its warning) not one ++ operator.
In fact x++ can be part of an expression rather than as a statement by itself. In this case, a question arises. Is the value used in the expression the old (un-incremented) value of x, or the new (incremented) value? Both are useful and both are provided.
x = 5; y1 = x++ + 10; y2 = ++x + 10; y3 = x-- + 10; y4 = --x + 10;
Consider the code sequence on the right where all 5 variables are declared to be ints.
Since there were two increments and two decrements, we expect x to end with the same value as it had in the beginning ... and it does.
On the board show how to calculate the index for queues (rear++ and front++) and for stacks (top++ and --top). What about moving all four ++ and -- operators to the other side?
What happens if we try to add a short to an int? How about multiplying a byte by a double?
Even simpler perhaps, how about assigning an integer value to a real variable, or vice versa?
The strict answer is that you can't do any of these things directly.
Instead one of the values must be converted to another type.Sometimes this conversion happens automatically (but it does happen). Other times it must be explicitly requested and some of these requests fail.
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.
Any short value is also a legal int. Similarly any float value is a legal double.
Conversions of this kind are called widenings since the new type in a sense is wider than the old. Similarly, the reverse conversions are called narrowing.
public class Test { public static void main (String[] args) { long a = 1234567890123L; // one trillion plus short b; double x; float y; x = a; y = a; // b = a; won't compile; coercions cannot narrow b = (short)a; // narrowing cast System.out.println(a+ " "+b + " "+x + " "+y); } } javac Test.java; java Test 1234567890123 1227 1.234567890123E12 1.23456795E12
Java will perform widening coercions but not narrowing coercions. To perform a narrowing conversion, the programmer must use an explicit cast. This is done by writing the target type in parenthesis.
The code on the right illustrates these points. Two problems have arisen.
The last example showed that Java will coerce a 64-bit long into a 32-bit float. Clearly the 32-bit number is narrower than the 64-bit number, but the coercion is permitted.
I believe the explanation is that the range of possible floats (vastly) exceeds the range of longs, so the assigned value will be approximately equal to the original, only some precision will be lost.
Start Lecture #4
The loan payment problem requires you to accept some complicated formula for monthlyPayment. That is not my style. Instead, we will do a simpler problem where we can understand the formula used.
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.
finalBalance = origBalance + interest = origBalance + origBalance * interestRate = origBalance * (1 + interestRate)
finalBalance = origBalance * (1 + interestRate/12)12
finalBalance = origBalance * (1 + interestRate/365)365
finalBalance = origBalance * (1 + interestRate/n)n
import java.util.Scanner; public class CompoundInterest { public static void main (String[] args) { Scanner getInput = new Scanner(System.in); double origBal = getInput.nextDouble(); double interestRate = getInput.nextDouble(); int n = getInput.nextInt(); // numberCoumpoundings double finalBal = origBal*Math.pow(1+interestRate/n,n); System.out.println(finalBal); } } javac CompoundInterest.java; java CompoundInterest 1000. .03 1 1030.0 java CompoundInterest 1000. .03 2 1030.2249999999997 java CompoundInterest 1000. .03 12 1030.4159569135068 java CompoundInterest 1000. .03 100000 1030.4545293116412 java CompoundInterest 1000. .03 1000000 1030.4545335307425
The program on the right is fairly straigtforward, given what we have done already. First we read the input data: the original balance, the (annual) interest rate, and the number of compoundings.
Then we compute the final balance after one year. We could do k years by changing the exponent from n to k*n.
Finally, we print the result.
The only interesting line is the computation of finalBal. Let's read it carefully in class to check that it is the same as the formula above.
Make sure you see the coercion that occurs in the division.
We show five runs, the first for so called simple interest (only compounded once). We did this above and sure enough we again get $1030 for 3% interest on $1000.
The second is semi-annual compounding. Note that we do not need to recompile (javac).
The third is monthly compounding.
The fourth and fifth suggest that we are approaching a limit.
char c = 'x'; String s = "x"; s = c; // compile error char apostrophe = '\'';
The char (character) datatype is used to hold a single character. A literal char is written as a character surrounded by single quotes.
The variables c and s on the right are most definitely not the same.
What do you do if you want the character '?
Answer: Escape it with a backlash as shown.
Java's char datatype uses 16-bits to represent a character, thereby allowing 216=65,536 different characters. This size was inspired by the original Unicode, which was also 16 bits. The goal of Unicode was that it would supply all the characters in all the world's languages.
However, 65,536 characters proved to be way too few; Unicode has since been revised to support many more characters (over a million), but we won't discuss how it was shoehorned into 16-bit Java characters. It turns out that the character 'A' has the 16-bit value 0000000000100001 (41 in hex, 65 in decimal).
There are two ways to write this character.
Naturally 'A' is one, but the other looks wierd, namely
'\u0041'.
This representation consists of a backslash, a lower case u
(presumably for unicode), and exactly
4 hexadecimal digits
.
I don't like this terminology—to me digit implies base
10—but it is standard.
public class As { public static void main(String[] Args) { String str = "A\u0041\u0041A \u0041"; System.out.println(str); } } javac As.java; java As AAAA A
So 'A' is the 4*16+1=65th character in the Unicode set.
The two character representations can be mixed freely, as we see on the right.
In the not so distant past, computers (at least US computers) used the 7-bit ASCII code (rather nationalistically, ASCII abbreviates American Standard Code for Information Interchange). The 65th ASCII character is also 'A'. All the letters, digits, etc are in both ASCII an unicode and are in the same position in each code (65 for A). It might be right that the 127 ASCII codes make up the first 127 unicode characters and are at the same position, but I am not sure.
Although it will not be emphasized in this course, it is indeed wonderful that alphabets from around the world can be represented.
Esc Seq | Becomes |
---|---|
\" | Double quote |
\\ | Backslash |
\' | Single quote |
\t | Tab |
\n | Newline |
\b | Backspace |
\f | Formfeed |
\r | 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?
The answer is that we use an escape sequence beginning with \. In particular we use \" to include a " inside a string.
But then how do we get \ itself?
Ans: We use a \\ (of course).
On the right is a list of Java escape sequences. The ones above the line are more commonly used than the ones below.
I believe a better heading would be converting between char and numeric types since not all of the conversions used are (explicit) casts; some are (implicit) coercions.
public class Test { public static void main (String[] args) { int i; short s; byte b; char c = 'A'; i = c; s = c; b = c; System.out.println(c + " " + i + " " + s + " " + b); } }
The code on the right has two errors. Clearly a (16-bit) char might not fit into an (8-bit) byte. The (subtle) trouble with the short is that it is signed, whereas char is not so the latter can be about twice as large.
Hence coercions will not work and the program on the right will not compile. You may write casts such as b=(byte)c; this will compile, but will produce wrong answers if the actual value in the char c does not fit in the byte b.
Similarly, integer values may be cast into chars (but Java will not coerce them).
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
Let's start this in class. One solution is here.
In Java a String (note the capital S) is very different from a double, short, or char. Whereas all the latter are primitive types, String is actually a class and hence the String type is a reference type.
So what?
It actually is an important, indeed crucial, distinction, but not yet. Remember that Java was not designed as a teaching language, but as a heavy-duty production language. Once consequence is that doing some simple things (reading an integer, declaring a string) uses fairly sophisticated concepts.
For now we just want to learn how to declare String variables, write String constants, assign Strings, and concatenate Strings. Fortunately, we can do all of these tasks without having to deal with the complications. Later we will learn much, much more about classes.
"This is A string"
"This is \u0041 string"
String s1, s2;
s1 = "A string";
String s3 = "Another string";
s2 = s1 + " " + s3;
s2 += " and more";
A string
A string Another string and more
Another string
The top line on the right shows a String constant.
The syntax is very simple: a double quote, some characters, a double
quote.
The second line is the same String constant in Unicode.
The next group contains legal Java statements.
The last group of lines show the output when we println() each of the three strings.
String s1 = "A1", s2 = "A2"; s1 += " " + s2; s1 += s1; System.out.println(s1);
Homework: What would be the output when the program on the right is run? I omitted the boilerplate at the beginning and end of nearly every program.
There are three types of Java comments.
I will use only the first form, but you may use any of the three.
To learn about javadoc, which is quite cool, see the reference in the book.
Java programmers use the following conventions. I will try to adhere to them. I don't know if you can ask javac to tell you if you have violated the conventions.
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).
I wish I could write a few words or pages (or books) that would show you how to avoid making errors and how to find and fix error made by others.
Instead, it will be a semester-long effort: We will write programs in class, which will doubtless have errors that we can fix together.
Homework: 2.11
Essentially all computer languages have a way to indicate that some statements are executed only if a certain condition holds.
Java syntax is basically the same as in C, which is OK but not great.
We have seen four integer types (byte, short, int, and long), two real types (float and double), and a character type (char). These 7 types are called primitive.
We have also see one reference type (the String class). Note the capitalization, which reminds you that String is a class.
Java has one more primitive type (boolean), used to express truth and falsehood.
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.
No fun to use booleans before learning about if.
Found in basically all languages.
We will see that the C/Java syntax for if permits the
notorious dangling else
problem.
if (boolean expression) { one or more statements; }
The simplest form of if statement is shown on the right.
The semantics (meaning) of a one-way if statement is simple and the same as in many languages.
Read.
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.
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.
As the name suggests, the semantics of an if-then-else
is to
do the then
or the else
depending on the if.
Specifically,
if (boolean expression) exactly one statement if (boolean expression) exactly one statement else exactly one statement if (boolean expression) { one or more statements } else exactly one statement
If any of the three blocks (the then block in the if-then statement, the then block in the if-then-else statement, or the else block in the if-then-else statement) consists of only one statement, its surrounding {} can be omitted.
Experienced Java programmers would almost always omit such {}, but it is probably better for beginners like us to leave them in for a while so that all if-then's and all if-then-else's have the same structure. There is an exception, see below.
On the right we see three of the four possibilities.
What is the remaining possibility?
Ans: The then block
has exactly one statement and no {} while
the else
block has one or more statements and does have the
{}.
The statement(s) in either the then block or the else block can include another if statement, in which case we say the two if statements are nested.
if (be1) { // be is Boolean expr if (be2) { ss1 // ss is statement(s) } else { ss2 } } else { if (be3) { ss3 } else { ss4 } }
On the right we see three if statements, with the second and third nested inside the first.
If be1, the Boolean expression of the outside if statement evaluates to true, the second if statement is executed. If instead be1 is false, the third if statement executes.
There are eight possible truth values for the three Boolean
expressions, but only four possible actions
(ss1–ss4).
Why?
Because for be1==true the value of be3 is
irrelevant and for be1==false, the value of
be2 is irrelevant.
What are the values of be1..be3 if ss1 is executed? Similarly for ss2..ss4.
if (be1) { ss1 } else { if (be2) { ss2 } else { if (be3) { ss3 } else { ssDefault } } }
Often deeply nested if-then-else's have the nesting only in the else part. This gives rise to code similar to that shown on the right.
Be sure you understand why this is not the same as separate (non-nested) if statements.
One trouble with the code shown is that it keeps moving to the right and, should there be many condition, you either get very wide lines or only have a few columns to use.
This is one situation where it pays to make use of the fact that the {} can be omitted when they enclose exactly one statement. To make use of this possibility, you must realize that an if statement, in its entirety, is only one statement. For example, the entire nested if structure on the right is only one statement, even though it may have hundreds of statements inside its many {} blocks. Similarly, each of the nested ifs is just one statement.
if (be1) { ss1 } else if (be2) { ss2 } else if (be3) { ss3 } else { ssDefault }
As a result we can remove the { and } from else { if ... } and convert the above to the code on the right.
Please read the code sequence carefully and understand why it is the same as the one above.
Note that the indenting is somewhat misleading as the symmetric placement of ss1, ss2, s3, and ssDefault suggests that they have equal status. In particular, since be1==true implies that ss1 gets execute, you might think that be3==true implies that ss3 gets executed. But that is wrong! Be sure you understand why it is wrong (hint: what if all the boolean expressions are true?).
Again be sure you can determine the truth values of be1..be3 if ss1..ss3 or ssDefault is executed.
Indeed this pattern with all the statements in the
then block
is so common that some languages (but not Java)
have a keyword elsif that is used in place of
the else if above.
Other languages require an explicit ending to an if statement, most commonly end if. In that case no {} are ever needed for if statements.
Homework: 3.7 and 3.9.
Don't forget the {} when you have more that one statement in the
then block
or else block
.
I recommend that, while you are learning Java, you use {} for each
ss even if it is just one statement (except for the
else if situation illustrated just above).
Don't mistakenly put a ; after the boolean expression right before
the then block
.
Don't write if (be==true). Instead, write the equivalent if (be). Although the first form is not wrong; it is poor style.
if (be1) if (be2) ss2 else ss1
The indenting on the right mistakenly suggests that should be1 evaluate to false, ss1 will be executed. That is WRONG. The else in the code sequence is part of the second (i.e., inner) if. Indeed, the Java (and C) rule is that an else pairs with the nearest unfinished if.
In a language like Python that enforces proper indentation, no such misleading information can occur.
In a language such as Ada with an explicit
end if
, the problem does not arise.
if (be1) { if (be1) { if (be2) if (be2) { ss2 ss2 } else { } else { ss1 ss1 } } }
On the right, I have added {} to make the meaning clear. The code on the far right has the same semantics as the dangling else code above. There is no ambiguity: the outer then has not yet ended so the else must be part of the inner if.
The code on the near right has the semantics that the original
indentation above mistakenly suggested.
Again there is no ambiguity:
The inner if has ended (it is part of the outer
then block
) so the else must be part of the outer
if
Start Lecture #5
Remark: Class taught by Prof. Marsha Berger.
Read
Let's fix two problems with our previous solution to solving the quadratic equation Ax2+Bx+C=0.
Let's do this in class; one possible solution is
here.
However, I don't like the that solution. Why?
Hint -C/B.
Fixing this problem leads to rather confusing looking code due to all the nested tests.
How can we further beat this dead horse?
Answer: When the discriminant is negative, we could compute the
complex roots and we could (actually should) add comments to make
the cases clear.
Read.
Let's do this one in class. Handout sheet with tax table. A scan of the table is here.
The input is the filing status and the income.
Start Lecture #6
Remark: Class taught by Prof. Marsha Berger.
Operator | Name |
---|---|
! | not |
&& | and |
|| | or |
^ | exclusive or |
Like most computer languages, Java permits Boolean expressions to contain so-called "logical operators". These operators have Boolean expressions as both operands and results.
The table on the right shows the four possibilities.
The not
operator is unary (takes one operand) and returns the
opposite Boolean value as result.
The and
operator is binary and returns false unless
when both operands are true.
The (inclusive) or
operator is binary and returns
true unless both operands are false.
That is, the meaning of || is
one or the other or both
.
The exclusive or
operator is binary and returns
true if and only if exactly one operand is true.
That is, the meaning of ^ is
one or the other but not both
.
Note that ^ can also be thought of as not equal
,
but remember that the operands are Booleans not numbers.
The logical operators have lower precedence than the comparison operators
operators, which in tern have lower precedence than the arithmetic operators.
In particular,
x!=0 && 1/x < y+2
is evaluated as
(x!=0) && ( (1/x) < (y+2) )
which is normally what you want.
Since A&&B==false if either (or both) A==false or B==false, once we know that one of the operands to && is false, we need not evaluate the other.
Java makes use of this observation and ensures that, if the left
operand of && is false, then the right
operand is not evaluated.
This, so-called short-circuit evaluation
of A&&B saves a little time but, much more
importantly, guarantees that
x!=0 && 1/x
< y+2 will never divide by zero.
Similarly, if the left operand of || is true, the right operand is not evaluated.
As you probably know a day is the time it takes the earth to rotate about its axis, and a year is the time it takes for the earth to revolve around the sun.
By these definitions a year is a little less than 365.25 days. To keep the calendars correct (meaning that, e.g., the vernal equinox occurs around the same time each year), most years have 365 days, but some have 366.
A very good approximation, which will work up to at least the year 4000 and likely much further is as follows (surprisingly, we are not able to predict exactly when the vernal equinox will occur far in the future; see wikipedia).
Let's write in class a program that asks for the year and replies by saying if the year is a leap year. One solution is in the book.
The book has an interesting lottery game. The program generates a random 2-digit number and accepts another 2-digit number from the user.
We will write this in class, but need a new library method from Java, Math.random() returns a random number between 0 and 1. The value returned, a double, might be 0, but will not be 1. Since math.Random() is in java.lang we do not have to import it.
One solution is in the book.
We have seen how to use if-then-else to specify one of a number of different actions, with the chosen action determined by a series of Boolean expressions.
Often the Boolean expressions consist of different values for a single (normally arithmetic) expression.
switch(expression) { case value1: stmts1 case value2: stmts2 ... case valueN: stmtsN default: defaultStmts }
For example we may want to do one action if i+j is 4, a different action if i+j is 8, a third action if i+j is 15, and a fourth action if i+j is any other value. In such cases the switch statement, shown on the right, is appropriate.
When a switch statement is executed, the expression is evaluated and the result specifies, which case is executed.
The semantics of switch are somewhat funny. Specifically, given the visual similarity with if-then-else, one might assume that after stmts1 is executed, control transfers to the end of the switch. However, this is wrong: Unless explicitly directed, control flows from one case to another.
The semantics of the C/Java switch closely resemble those
of an assembler-level jump table and the computed goto
of
Fortran.
switch (expression) { case value1: stmts1 break; case value2: stmts2 break; ... case valueN: stmtsN break; default: def-stmts }
It is quite easy to explicitly transfer control from the end of one case to the end of the entire switch. Java, again borrowing from C, has a break statement that serves this purpose.
We see on the right the common form of a switch in which the last statement of each case is a break.
When a break is executed within a switch, control
breaks out of
the switch.
That is, execution proceeds directly to the end of
the switch.
In particular, the code on the right will execute exactly one
case or the default.
Homework: 3.11, 3.25.
Recall that Java has +=, which shortens x=x+5 to x+=5. Java has another shortcut, the tertiary operator ?...:, which can be used to shorten an if-then-else.
bool-expr ? expr1 : expr2 if (x==5) y = 2; else y = 3; y = x==5 ? 2 : 3;
The general form of the so-called conditional expression
is
shown on the top right.
The value of the entire expression is either expr1 or
expr2, depending on the value of the Boolean expression
bool-expr.
For example, the middle right if-then-else can be replaced by the bottom right assignment statement containing the equivalent conditional expression.
Note that this is not limited to arithmetic and can often be used
to produce grammatically correct output.
For example, consider the following beauty
.
System.out.println ("Please input " + n + " number" + ((n==1) ? "." : "s."));
I realize this looks weird; be sure you see how it works.
Homework: Redo 3.7, this time using a switch.
Start Lecture #7
A comparatively recent addition to Java is the C-like printf method. (It was added in Java 2 Standard Edition 5; we are using J2SE 7).
System.out.printf(format, item1, item2, ..., item n);
The first point to make is that printf() takes a variable number of arguments, as indicated on the right. The required first argument, which must be a string, indicates how many additional arguments are needed and how those arguments are to be printed.
Wherever the first argument contains a %
, the value
of the next argument is inserted (a double %%
is
printed as a single %
).
System.out.printf("i = %d and j = %d", i, j); System.out.printf("x = %f and y = %e", x, y);
The first line on the right prints the integer values of x and
y.
The second line prints two real values, the second using scientific
notation.
Specifier | Output |
---|---|
%b | Boolean |
%c | Character |
%d | Integer |
%f | Floating Point |
%e | Scientific Notation |
%s | String |
The table on the right gives the most common specifiers. Although Java will perform a few coercions automatically (e.g., an integer value will be coerced to a string if the corresponding specifier is %s), most conversions are illegal. For example
int i; double x; int one=1, two=2; System.out.printf("Both %f and %d are bad\n", i, x); System.out.printf("%d plus %d is %d\n", one, two, one+two); // OK
You could try to find which conversions are OK, but I very much suggest that instead you do not use any. That is use %f and %e only for floats and doubles; use %d only for the four integer types; use %s only for Strings, etc.
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.
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.
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.
Operator | Associativity |
---|---|
var++, var-- | Left-to-right |
+,- (unary), ++var, --var | Right-to-left |
(type) | Right-to-left |
! | Left-to-right |
*,/,% | Left-to-right |
+,- (binary) | Left-to-right |
<,<=,>,>= | Left-to-right |
==,!= | Left-to-right |
^ | Left-to-right |
&& | Left-to-right |
|| | Left-to-right |
?: (tertiary) | Right-to-left |
=,+=,-=,*=,/=,%= | Right-to-left |
The table on the right lists the operators we have seen in decreasing order of precedence. This order of precedence means that, in the absence of parentheses, if an expression contains two operators from different rows of the table, the operator in the higher row is done first.
The second column gives the associativity of the operators. If an expression contains two operators from the same row and that row has left-to-right associativity, the left operator is done first. Similarly if the row has right-to-left associativity, the right operator is done first. As with precedence, parentheses can be used to override this default ordering.
Not many programmers have the entire table memorized (not to mention the fact that there are other operators we have not yet encountered). Instead, they use parenthesis even when their use might not be necessary due to precedence and associativity.
However, it is wise to remember the precedence and associativity of
some of the frequently used operators.
For example, one should remember that *,/,% are executed before
(binary +,-) and that both groups have left-to-right associativity.
It would clutter up a program to see an expression like
((x*y)/z)/(((-x)+z)-y) rather than the equivalent
(x*y/z)/(-x+z-y)
Lab 1 Part 2 is here. Due 21 February, 2012.
Homework: Write a java program that reads four integers. If any two (or any three, or all four) are equal, just print an error message. If all four are distinct, print the 2nd largest.
Homework: Contemplate but do NOT write a java program that first reads 11 integers. If any two (or any three, or ..., or or all eleven) are equal, just print an error message. If all eleven are distinct, print the 6th largest (i.e., the middle value). There must be a better way! There is.
As you know from Python, loops are used to execute the same block
of code multiple times.
There are several kinds of Java loops:
The while (and do-while) are fairly general;
the for is most convenient when the loops are
counting
, but can be used (abused?) in quite general ways.
while (BE) { stmts }
Some early languages (notably early Fortran) did not have a while loop, but essentially all modern languages (including Fortran) do.
The semantics are fairly simple:
The idea of a while loop is that the body is executed while (i.e, as long as) the Boolean expression is true.
The flowchart on the right illustrates these simple semantics.
Note that if the Boolean expression is false initially, the loop body (the statements in the diagram) are not executed at all. We shall see in the next section a loop in which the body is executed at least once.
Similarly, note that right after the while loop is
executed, the BE is FALSE.
Please don't get this wrong.
while ((i=getInput.nextInt) >= 0) { // process the non-negative i }
For example, when the loop on the right ends (assuming no EOF), the value of i is negative!
Let us write a program that adds two non-negative numbers just using ++ and --. One solution is here.
This is actually how one defines addition starting with Peano's postulates
The program picks a random integer between 0 and 100 inclusive (101
possibilities).
The users repeatedly guess until they are correct.
For each guess, the program states higher
, lower
,
or correct
.
Let's do this in class; one solution is in the book, another is here.
There are four parts to a loop and hence 4 tasks for the programmer to accomplish.
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.
extrainput.
extra.
extra. This example is sometimes called an N-and-a-half times loop since we execute all the loop N times and
halfof the loop an extra time to read the
extrainput.
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.
Start Lecture #8
import java.util.Scanner; public class SumN { public static void main (String[] args) { Scanner getInput = new Scanner(System.in); double x, sum; int i, n; while (true) { System.out.println("How many numbers do you want to add?"); n = getInput.nextInt(); if (n < 0) { break; } System.out.printf("Enter %d numbers: ", n); i = 0; sum = 0; while (i++ < n) { x = getInput.nextDouble(); sum += x; } System.out.printf("The sume of %d values is %f\n", n, sum); } } }
On the right is a simple program to sum n numbers. Since it does not make sense to sum a negative number of numbers, we let n<0 be the sentinel that ends the program.
There are again a few points to note.
n and 1/2times loop: The first part of the body is executed one more time than the second part.
Lab 1 Part 3 is here. It is due 23 February 2012.
public class UseEOF { public static void main(String[] args) throws java.io.IOException { final int EOF = -1; int c; while ( (c = System.in.read()) != EOF) System.out.printf("%c",c); } }
On the right is a very simple loop that is terminated by EOF (end-of-file). However, it does use an advanced feature of Java, namely exceptions.
The read()method either returns the next byte (an
integer in the range 0..255) from System.in or it
returns -1 to signify an end of file.
(If an I/O error occurs an exception
is thrown.)
The Java library did the heavy lifting (the EOF technique); enabling my program to use the simpler sentinel technique.
As you know System.in is normally the keyboard and System.out is normally the display. However, they can be redirected to be an ordinary file in the file system. When System.in is redirected to a file f, input is taken from f instead of from the keyboard, and when System.out is redirected to a file g, output goes to g.
For example the program UseEOF with the mysterious implementation, simply copies all its input to its output.
I copied it to i5 so we can run it (ignore how it works).
If we type simply java UseEOF, then whatever we type will appear on the screen.
If we type instead java UseEOF <f, then the file f will appear on the screen.
Similarly java UseEOF <f >g copies the file f to the file g.
As we have seen a while loop repeatedly performs a test-action sequence: first a BE is tested and then, if the test passes, the body is executed.
In particular, if the test fails initially, the body is not executed at all.
Most of the time, this is just what is wanted; however, there are occasions when we want the body to be first and the test second. In such situations we use a do-while loop.
do { stmts } while (BE);
Note that unlike the behavior we have seen for while loops a do-while loop is guaranteed to execute its body at least once.
We see the code skeleton for this loop on the near right and the flowchart on the far right.
final int quota = 250; int count=0; do { count++; } while ((quota-=getInput.nextInt()) > 0); System.out.printf("Needed %d values.\n", count);
In the code on the right a quota is hardwired for brevity. Then the user inputs a series of numbers (say the dollar value of a sale). When they finally reach or surpasses the quota, the program ends and reports how many values were needed.
Recall the four components of a loop as stated in 4.2.2
On the right we show a flowchart illustrating these four components. This particular flowchart is representative of a while loop.
Homework: Draw the corresponding flowchart for a do-while loop.
Parts 1, 2, and 4 can all be written in one for statement. After the word for comes a parenthesize list with three elements separated by semicolons. The first component is the initialization, the second the test, and the third the update to prepare for the next iteration.
for(i=0; i<10; i++){ body } for(int i=0; i<10; i++) for(i=0, j=n; i*j < 100; i++, j--) for( ; ; )
The first code shown on the right presents the for loop corresponding to the example mentioned in the list of loop components given just above.. Indeed, this is a common usage of for, to have a counter step through consecutive values.
The second for on the right shows that the loop variable can be declared in the for itself. One effect of this declaration is that the variable, i in this case, is not visible outside the loop. If there was another declaration of i inside another loop, the two variables named i would be unrelated.
It is also possible to initialize and update more than one variable as the third example shows.
Finally, the various components can be omitted, in which case the omitted item is essentially erased from the flowchart. For example, if the first component is omitted, there is no initialization performed; if the middle item is omitted there is no test (it is perhaps better to say that the test always yield true); and, if the third item is omitted there is no update. So the last, rather naked, for, similar to a while(true), will just keep executing the body until some external reason ends the loop (e.g., a break).
Homework: 4.5, 4.1 (the book forgot to show inputing the number of numbers), 4.9
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.
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.
This concerns the hazards of floating point arithmetic. Although we will not be emphasizing numeric errors, two general points can be made.
Given two positive integers, the book just tries every number from one up to the smaller input and chooses the largest one that divides both of the inputs.
Let try a different way to get the GCD. Keep subtracting the smaller from the larger until both are equal, at which point you have the GCD.
(54, 36) → (18, 36) → (18, 18) → 18.
(7, 6) → (1, 6) → (1, 5) → ... → (1, 1) → 1.
Do this in class. A solution is here.
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.
We have already seen break for breaking out
of a
switch statement.
When break is used inside a loop, it does the analogous
thing: namely it breaks out
of the loop.
That is execution transfers to the first statement
after the loop.
This works for any of the three forms of loops we have seen:
while, do-while, and for.
In the case of nested loops, the break just breaks out of the innermost loop.
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:
We want to display the first N primes. Unlike the book, we will
Although the second improvement does make the program much faster for large primes, it is still quite inefficient compared to the best methods, which are very complicated and use serious mathematics.
Start it in class.
Homework: Write this program in Java and test it for N=25.
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().
We know how to do this since we have repeatedly defined the main method. The general format of a method definition is.
modifiers returnType name (list of parameters) { body }
For now we will always use two modifiers
public static
.
The main() method had returnType void since it did not return a value. It also had a single argument, an array of Strings.
The returnType and parameters will vary from method to method.
public static int myAdd(int x, int y) { return x+y; }
On the right we see a very simple method that accepts two int parameters and returns an int result. Note the return statement, which ends execution of the method and optionally returns a value to the calling method.
The type of the value returned will be the returnType named in the method definition.
Start Lecture #9
public class DemoMethods { public static void main(String[]) { int a = getInput.nextInt(); int b = getInput.nextInt(); System.out.println(myAdd(a,b)); } public static int myAdd(int x, int y) { return x+y; } }
We have called a number of methods already. For example getInput.nextInt(), Math.pow(), Math.random(). There is very little difference between how we called the pre-defined methods above and how we call methods we write.
One difference is that if we write a method and call it from a sibling method in the same class, we do not need to mention the class name.
On the right we see a full example with a simple main() method that calls the myAdd() method written above. You can mention the class name if you like. so the argument to println() could have been DemoMethods.myAdd(a,b)
Method such as myAdd() that return a value are used in expressions the same way as variables and constants are used, except that they contain a (possibly empty) list of parameters. They act like mathematical functions. Indeed, they are called functions in some other programming languages.
Note that the names of the arguments in the call do NOT need to agree with the names of the parameters in the method definition. This is a good thing. For example, how could we know the parameter names of Math.pow() , since we have never seen the method definition?
Lab 1 Part 4 (the last part) and Lab 1 Honors Supplement 1 (the only supplement for lab 1) are both here. They are due 28 February 2012.
Homework: 5.1, 5.3.
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.
return address(the address in f where g should return to). This is called a stack frame, or simply frame.
public class DemoMethods { public static void main(String[]) { int a = getInput.nextInt(); int b = getInput.nextInt(); printSum(a,b); } public static void printSum(int x, int y) { System.out.println(x+y); } }
Methods that do not return a value (technically their return type is void) are invoked slightly differently. Instead of being part of an expression, void methods are invoked as standalone statements. That is, the invocation consists of the method name, the argument list, and a terminating semicolon.
An example is the printSum() method invoked by main() in the example on the right.
Homework: 5.5, 5.9.
Referring to the example above, the arguments a,b in main() are paired with the parameters x,y in printSum in the order given, a is paired with x and b is paired with y.
The result is that x and y are initialized to the current values in a and b. Note that this works fine if an argument a or b is a complex expression and not just a variable. So printSum(4,a-8) is possible.
public class DemoPassByValue { public static void main(String[]) { int a = 5; int b = 6; int c = 7; tryToAdd(a,b,c); } public static void tryToAdd(int x, int y, int z) { x = y + z; } }
The parameters, however, must be variables.
Java is a pass-by-value language (a.k.a. call-by-value). This means that, at method invocation, the values in the arguments are transmitted to the corresponding parameters, but, at method return, the final values of the parameters are not, repeat NOT sent back to the parameters.
For example the code on the right does Not set a to 13.
When we learn about objects and their reference semantics, we will need to revisit this material.
Programs are often easier to understand if they are broken into smaller pieces. When doing so, the programmer should look for self-contained, easy to describe pieces.
public class DemoModularizing { public static void main(String[] args) { // input 3 points (x1,y1), (x2,y2), (x3,y3) double perimeter = length(x1,y1,x2,y2) + length(x2,y2,x3,y3) + length(x3,y3,x1,y1); System.out.println(perimeter); } public static length(double x1, double y1, double x2, double y2) { return Math.sqrt((x2-x1)*(x2-x1) + (y2-y1)*(y2-y1)); } }
Another advantage is the ability for code reuse
.
In the very simple example on the right, we want to calculate the
perimeter of a triangle specified by giving the (x,y) coordinates of
its three vertices.
Recall that the perimeter is the sum of the lengths of the three sides and the length of a line is the square root of the sum of the squares of the difference in coordinate values.
Without modularization, we would have one large formula that was the sum of the three square roots.
As written on the right, I abstracted out the length calculation, which in my opinion, makes the code easier to understand.
Less trivial examples are the methods in the Math
class.
Imagine writing a geometry package and having to code the square
root routine each time it was used.
public class Pi { public static void main (String[] args) { final long NUM_BALLS = (long)1.0E11; // could read this in long inside = 0; // number in circle double x, y; System.out.println(" Dropped Inside Estimate of pi"); for (long dropped=1; dropped<=NUM_BALLS; dropped++) { x = Math.random(); y = Math.random(); if (dist(x,y,0.5,0.5)<0.5) inside++; if (interesting(dropped)) System.out.printf("%12d%12d%14.10f\n", dropped,inside,estimate(dropped,inside)); } } public static double dist(double x1, double y1, double x2, double y2) { return Math.sqrt((x1-x2)*(x1-x2) + (y1-y2)*(y1-y2)); } public static double estimate(long dropped, long inside) { return 4.0*((double)inside / (double)dropped); } public static boolean interesting(long x) { double l10 = Math.log10(x); // Powers of 10 = l10 an integer return Math.abs(l10 - Math.rint(l10)) < 1.0E-14; } } javac Pi.java && time java Pi Dropped Inside Estimate of pi 1 1 4.0000000000 10 9 3.6000000000 100 82 3.2800000000 1000 780 3.1200000000 10000 7806 3.1224000000 real 222m10.434s 100000 78634 3.1453600000 user 168m54.970s 1000000 786032 3.1441280000 sys 0m35.810s 10000000 7853077 3.1412308000 100000000 78539849 3.1415939600 1000000000 785396741 3.1415869640 10000000000 7853941114 3.1415764456 100000000000 78539619440 3.1415847776
On the right we see a modularized version of the Monte Carlo method of approximating π discussed in section 4.8.3.
Recall that we simply drop balls onto the unit square and keep track of what proportion lie inside the inscribed circle. This proportion is approximately equal to the ratio of the area of the circle to the area of the square.
We then assume the ratios are equal and solve the resulting equation for π
I wrote this program in a top down fashion (see 5.12) starting with the main() and defining a new method whenever there was anything to figure out.
This generated three methods: dist() used to see if the ball was inside the circle, estimate() to calculate the current approximation, and interesting() to decide if the current estimate is worth printing (the hardest part of the program).
Below the program we see the output when it is run. The time command times the execution of the command that follows. To save vertical space, I moved the timing information to the right of the regular output; in reality it occurs below that output.
We see that the program ran for 222 minutes. For 169 minutes the program was itself executing; for 36 minutes, the operating system was executing on the program's behalf. For the remaining time, some other program must have been running or this program was block doing I/O (Pi itself probably does less than 1 second of I/O but javac does several seconds).
Read.
A very nice feature of Java and other modern languages is the ability to overload method names.
public class DemoOverloading { public static void main(String[] args) { System.out.printf ("%d %f %f\n", max(4,8), max(9.,3.), max(9,3.)); } public static int max(int x, int y) { return x>y ? x : y; } public static double max(double x, double y) { return x>y ? x : y; } public static double max(int x, double y) { return ((double) x)>y ? x : y; } }
Of course if you use a method named max and another named min each with two integer arguments, Java is not confused and invokes max when you write max and invokes min when you write min.
All programming languages do that.
But now look on the right and see three methods all named max, one returning the maximum of two ints one returning the maximum of two doubles, and the third returning (as a double) the max of an int and a double.
When compiling a method definition javac notes
the signature
of the method, which for overloading consists
of the method name, the number of parameters, and the type of each
parameter.
When an overloaded method is called, the method chosen is the one with signature matching the arguments given at the call site.
Technical fine points: Java will coerce a 9 to a double if needed. But if an overloaded method has two signatures, one requiring coercion and the other not, the second is chosen. If the overloaded method has two signature, each with two parameters, having types so that one would require coercing the first argument and the other would require coercing the second argument, the program does not compile.
The scope of a declaration is the portion of the program in which the declared item can be referenced.
The rules for scope vary from language to language.
In Java a block is a group of statements enclosed in {}. For example, the body of most loops, the body of most then and else clauses of an if, and the body of a method are all blocks. (One statement bodies of then clauses, else clauses, and loops can be written without {} and then are not blocks).
A variable declared inside a method (the only kind of variables we have used so far) is called a local variable.
In Java the scope of a local variable begins with its declaration and continues to the end of the block containing the declaration.
We have seen an exception: if the initialization portion of a for statement contains a declaration, that local variable has scope from the declaration to the end of the loop body.
Similarly, a parameter in a method definition is a local variable and its scope extends from the parameter declaration to the end of the method body.
On the right we see two skeletons. The near right skeleton contains two blocks block1 and block2, neither of which is nested inside the other.
something { | something { block1 | start of block3 } | something { something { | block4 block2 | } } | }
In this case it is permitted to declare two variables with the same name, one in block1 and one in block2. These two variables are unrelated. That is, the situation is the same as if they had different names.
On the far right we again see two blocks, but this time block4 is nested inside block3. In this situation it is not permitted to have the same variable name declared in both blocks. Such a program will not compile.
Some languages do permit a local variable to be
redeclared in a nested block.
When that occurs, the outer declaration is hidden when execution is
in the inner block.
This is sometimes called a hole in the scope
of the outer
declaration.
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)
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.
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.
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)
The Math class has three methods that correspond directly to these mathematical functions, but return a type of double. They are shown on the right. For the third function, ties are resolved toward the even number so rint(-1.5)==rint(-2.5)==-2.
public static int round(float x) public static long round(double x)
In addition Math has an overloaded method round() that rounds a floating point value to an integer value. The two round method are distinguished by the type of the argument (standard practice for overloaded methods). Given a float, round() returns an int. Given a double, round() returns a long.
This choice prevents the loss of precision, but of course there are many floats and doubles (e.g., 10E30) that are too big to be represented as an ints or longs.
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).
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()).
Let's do one slightly different from the book's.
Print N (assumed an even number) random characters, the odd ones lower case and the even ones upper case.
public class RandomChars { public static void main(String[] args) { final int n = 1000; // assumed EVEN for(int i=0; i<n/2; i++) { System.out.printf("%c", pickChar('a')); System.out.printf("%c", pickChar('A')); } } public static char pickChar(char c) { return (char)((int)(c)+(26*Math.random())); } }
The program on the right is surprisingly short. The one idea used to create it was the choice of the pickChar() method. This method is given a character and returns a random character between the given argument and 26 characters later. So, when given 'a, it returns a random lower case character and, when given 'A, it returns a random upper case character.
Remember that in Unicode, the representation used for Java chars, lower case (so called latin) letters are contiguous as are upper case letters.
Be sure you understand the value returned by pickChar().
Homework: 5.17, 5.35.
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.
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.
Given a fleshed out design as in the diagram, we now need to implement all the boxes. There are two styles top-down and bottom-up.
In the first, you perform a pre-order traversal of the design. That is, you start by implementing the root calling the (unimplemented) child methods as needed.
In order to test what you have done before you are finished, you
implement stub
versions of the methods that have been called
but not yet implemented.
For example, the stub for a void method such
as printMonthBody might do nothing or might just
System.out.println("printMonthBody called");.
Stubs for non-void methods need to return a value of the correct type, but that value need not be correct.
In a bottom-up approach, you perform a post-order traversal of the design tree. That is, you implement only boxes all of whose children have been implemented (we are ignoring recursive procedures here). In the beginning this means you implement a leaf of the tree.
Since you implement the main program last, you need to write test programs along the way to test what you have implemented so far.
Read. In particular read over the full implementation of printCalendar.java
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 [].
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.
There is an important difference between declaring scalars as we have done many times previously and declaring an array. When the scalar is declared, it can be used (i.e., given a value). For an array another step is needed.
double[] x; x = new double[10]; double[] x = new double[10];
The declaration double[] x, declares x but does not reserve space for any of the elements x[i]. This must either be done separately as shown on the top right or written together as shown on bottom right.
The new operator allocates space; in the example on the right space is allocated for 10 doubles. The effect is that the array x can hold 10 elements, each a double.
In Java the first element always has index 0, so x consists of x[0], x[1], ..., x[9].
We do not officially know about objects, but recall that java.util.Scanner is a class and getInput is an element of that class, i.e. an object.
Scanner getInput = new Scanner(System.in); Scanner getInput; getInput = new Scanner(System.in);
On the top right is our standard definition of the getinput object. This is actually another Java shortcut for the full two line form shown below. So we see that, as for arrays, arbitrary objects must be both declared and also created (the latter again uses new).
The size of an array is fixed; it cannot be changed.
To retrieve the size of a 1D array arr is easy, just write arr.length. For example x.length would be 10 for the example above.
Another difference between arrays and scalars is that when the array is created (using the new operator), initial values are given to each element.
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.
Creating an integer array containing the values 1, 8, 29, and -2 is logically a three step procedure.
int[] A; A = new int[4]; int[] A = new int[4]; int[] A; A = new int[4]; A[0]=1; A[1]=8; A[2]=29; A[3]=-2; int[] A = {1,8,29,-2};
We have already seen how the first two steps can be combined into one Java statement. For example see the first two sections of code on the right.
In fact the Java programmer can write one statement that combines all three steps. The third code group on the right, which has each step as one statement, can be written much more succinctly as shown on the fourth group.
Note that the usage of curly brackets as shown is restricted to initialization and a few other situations, you cannot use {1,8,29,-2} in other places.
public class Test { public static void main(String[] args) { int[] a = new int[10]; int[] b = new int[10]; int[] c = new int[10]; for (int i=0; i<a.length; i++) { b[i] = 2*i; c[i] = i+100; a[i] = b[i] + c[i]; // 3i+100 System.out.printf("a[%d]=%d b[%d]=%2d c[%d]=%d\n", i, a[i], i, b[i], i, c[i]); } } }
Very often arrays are used with loops, one iteration of the loop corresponding to one entry in the array.
In particular for loops are often used since they provide the most convenient syntax for stepping through a range of values. The first entry of a Java array is always index 0; hence, the last entry of an array A is A.length-1.
For example the simple code on the right steps through three arrays, setting the value of two of them and calculating from these values the value of the third.
Start Lecture #10
Remark: If you email homeworks, please send plain text (preferred) or pdf. Please do not send doc or docx as there is trouble printing them at nyu (they print fine at my home).
Remark: See right before 5.7 for the output of Pi.
We have seen several short-cuts provided by Java for common
situations.
Here is another.
If you want to loop through an array accessing, but
not changing, the ith element
during the ith iteration, you can use the
for-each
variant of a for loop.
for(int i=0; i<a.length; i++) { for(double x: a) { // use (NOT modify) a[i] // use (NOT modify) x } }
Assume that a has been defined as an array of doubles.
Then instead of the standard for loop on the near right,
we can use the shortened, so-called for-each
variant on the
far right.
The shorten variant executes the body once for each element of the
array.
Read
import java.util.Scanner; public class StdDev { public static void main(String[] args) { Scanner getInput = new Scanner(System.in); System.out.println("How many numbers do you have?"); final int n = getInput.nextInt(); System.out.printf("Enter the %d numbers\n", n); double[] x = new double[n]; double sum=0; for (int i=0; i<n; i++) { x[i] = getInput.nextDouble(); sum += x[i]; } double mean = sum / n; double diffSquared=0; // std dev is sqrt(diffSquared/n) for (double z : x) diffSquared += Math.pow(mean-z,2); System.out.printf("Mean is %f; standard deviation is %f\n", mean, Math.sqrt(diffSquared/n)); } }
Given n values, x0, ..., xn-1, the mean (often called average and normally written as μ) is the sum of the values divided by n.
The variance is the average of the squared deviations from the mean.
That is, [(x0-μ)2 + ... +
(xn-1-μ)2]/n
Finally the standard deviation is the square root of the variance.
A Java program to calculate the mean and standard deviation is on the right.
Note that the first loop, which assigns values to the x
array, must use the conventional for loop; whereas, the
second loop, which only uses the values already in x can
employ the for-each
variant.
The normal rules apply concerning the need for {}. Since the second loop has a one-line body, the {} can be omitted.
public class DeckOfCards { public static void main(String[] args) { int[] deck = new int[52]; String[] suits = {"Spades", "Hearts", "Clubs", "Diamonds"}; String[] ranks = {"Ace", "2", "3", "4", "5", "6", "7", "8", "9", "10", "Jack", "Queen", "King"}; // Initialize cards for (int i = 0; i < deck.length; i++) deck[i] = i; // Shuffle the cards for (int i = 0; i < deck.length; i++) { // Generate an index randomly int index = (int)(Math.random() * deck.length); // Swap deck[i] and deck[index] int temp = deck[i]; deck[i] = deck[index]; deck[index] = temp; } // Display the shuffled deck for (int i = 0; i < 4; i++) { String suit = suits[deck[i] / 13]; String rank = ranks[deck[i] % 13]; System.out.println("Card number " + deck[i] + ": " + rank + " of " + suit); } } }
Let's look at this example from the book with some care. I downloaded it from the companion web site. You can download all the code examples.
The idea is to have a deck of cards represented by an array of 52 integers, the ith entry of the array represents the ith card in the deck.
How does an integer represent a card?
First of all the integers are themselves chosen to be between 0 and
51.
Given an integer C (for card), we divide C by 13 and look at the
quotient and remainder.
The quotient (from 0..3) represents the suit of the card and the
remainder (from 0..12) represents the rank.
Look at the String arrays; they give us a way to print the name of the card given its integer value by using the quotient and remainder.
The deck initialized to be in order
starting wit the Ace of
Spades and proceeding to the King of Diamonds (Liang must not be an
avid card player).
Notice how the deck is shuffled: The card in deck[i] is swapped with one from a random location. This can move a single card multiple times. Pay particular attention to the 3 statement sequence used to swap two values; it is a common idiom.
for (int C : deck) System.out.printf("Card number %2d: %s of %s\n", C, ranks[C%13], suits[C/13]);
Liang printed the first 4 cards; I print the entire deck.
The last loop, rewritten as a one-liner
, is shown on the right.
Serious business. We can no longer put off learning about reference semantics.
Consider the situation on the near right. We have created two arrays each capable of holding 6 integers. The first has been initialized; the second has not. We now wish to make the second array a copy of the first.
Note that the array name is not the array itself. Instead it points to (technically, refers to) the contents of the array. One consequence is that the size of array1 does not depend on the number of elements in the array, which turns out to be quite helpful later on.
If we simply execute array1 = array2;, we get the situation in the upper right. We have copied the reference (i.e., the pointer) contained in array1 so that this same reference is contained in array2.
As a result both arrays refer to the same content, which is normally not what is desired. In this state, changing the contents of one array, changes the other.
Be sure you see why array2 = array1; gives the depicted situation and be sure you see why now changing either array1[3] or array2[3] changes the other as well.
If the goal is to get the situation shown in the lower right, you
need a loop such as
for (int i = 0; i <array1.length; i++) {
array2[i] = array1[i];
}
to copy each entry of array1 to the corresponding entry
of array2.
After the loop is executed and we have the picture in the lower right, the arrays are still independent: changing one does not change the other.
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?
The technical answer is that primitive types, for example int, have value semantics; whereas, arrays (and objects in general) have reference semantics.
Looking at the diagram on the right, which shows explicitly the memory used for variable array1 and for the array itself, we see that the contents array1 refers to (or points at) the actual array where the values reside; whereas, the int variables a and b ARE the containers where the values 5 and 10 are stored. With primitive types such as int, there are no pointers or references involved.
When you change a or b, you change the value; when you change array1, you change the reference (pointer).
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.
There is very little to say about passing an element of a one dimensional array as an argument. The element is simply a value of the base type of the array.
public class DemoArrayArgs { public static void printSum(int x, int y) { System.out.println(x+y); } public static void main(String[] args) { int[] x = {5, 10, -1, 25}; printSum(x[2],x[1]); } }
On the right is the simple printSum() method that we saw earlier. It accepts two integer parameters and prints their sum.
The main() method defines and initializes an array x containing 4 ints, and then invokes printSum(x[2],x[1]).
The printSum() method will add the two values and print the sum just as if it was invoked via printSum(-1,10);
public static void printArray(int[] a) { for (int i : a) { System.out.println(i); } }
The printArray() method on the right has an (entire) int array as parameter. To call the method is easy: printArray(b) where b is a single-dimensional array of integers. The argument b of the caller is assigned to the parameter a of printarray(). Note that the types must match: the argument in the caller must be of type int[], i.e., an array of ints. As written printArray() can accept any size 1D array.
In this example, the called method (printArray() does not alter its parameter. The only subtlety in passing an entire array is analyzing the behavior when the called method does change a component of the parameter, which we study next.
public class DemoArrayArgUpdate { public static void BumpZerothEntry(int[] a) { a[0]++; } public static void main(String[] args) { int[] b = {5, 10, -1, 25}; BumpZerothEntry(b); } }
The tricky part of this situation is melding Java's pass-by-value method invocation with its reference semantics for arrays.
Assume we have a method, such as BumpZerothEntry() that accepts an array parameter a and updates at least one component. Assume, as shown on the right, the main() method invokes BumpZerothEntry() with argument b, an integer parameter.
At the point of the method call, the value in b is copied to a, but, as we know, when the method returns, any current value in a is NOT copied back to b. This rule still holds for arrays; NO change.
The diagram at the right show the configuration when the method is called.
Due to pass-by-value semantics, even if the called method changes the value in the parameter a, the value in the argument b would not change; it would remain a pointer to the 4-element structure shown.
However, that is not what happens here. The called method does not change the value in the parameter a, the parameter remains a pointer to the 4-element structure. Naturally, the argument a also does not change.
Now consider what does happen. The called method executes a[0]+=1;, which changes not the value in a (a pointer), but instead changes the value in a cell pointed to by a. Since the same cells are pointed to by both b and a, a cell point to by the argument b has changed.
Hence when the method returns, the value in b[0] is now 6.
Summary, the call above cannot change the value b, but it can change the value of an individual b[i].
Homework: 6.1. Write a program that reads student scores, calculates the best score, and then assigns grades based on the following scheme.
The program prompts the user to enter the total number of students, then prompts the user to enter all of the scores, and concludes by displaying the grades.
Notes:
public class ArrayVsScalarParam { public static void main(String[] args) { int xScalar = 5 ; int[] yArray = {5}; int[] zArray = {5}; System.out.printf("xScalar=%d yArray[0]=%d zArray[0]=%d\n", xScalar, yArray[0], zArray[0]); tryToChange(xScalar, yArray[0], zArray); System.out.printf("xScalar=%d yArray[0]=%d zArray[0]=%d\n", xScalar, yArray[0], zArray[0]); } public static void tryToChange(int scalar, int component, int[] array) { scalar = 10; // will NOT change argument component = 10; // will NOT change argument array[0] = 10; // WILL change component of argument } } xScalar=5 yArray[0]=5 zArray[0]=5 xScalar=5 yArray[0]=5 zArray[0]=10
The code on the right shows 3 attempts to change a 5 to a 10. Two fail; the third is successful.
Java also has anonymous arrays, i.e., arrays without a name. So you can write printArray(new int[]{1,5,9});
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).
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.
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.
public class VarArgsDemo { public static void main(String args[]) { printMax(34, 3, 3, 2, 56.5); printMax(new double[]{1, 2, 3}); } public static void printMax(double... numbers) { if (numbers.length == 0) { System.out.println("No argument passed"); return; } double result = numbers[0]; for (int i = 1; i < numbers.length; i++) if (numbers[i] > result) result = numbers[i]; System.out.println("The max value is " + result); } }
We have used System.out.printf() which has a variable number of arguments.
Java permits the last parameter to correspond to a varying number of arguments, but all of these arguments must be of the same type. Java treats the parameter as an array of that type.
The example on the right is from the book.
Pay attention to the parameter of printMax. Java uses ellipses to indicate varargs. In this case a variable number of double arguments are permitted and will all be assigned to the array double... numbers.
Other non-vararg parameters could have preceded the double... numbers parameter.
public static int getRandom(int... numbers)
Homework: 6.13. Write a method that returns a random number between 1 and 54, excluding the numbers passed in the argument. The method header is shown on the right.
Start Lecture #11
Remark: See new piece added just before 6.7.
Remark: Lab 2 assigned.
public static int search(int[] A, int val)
Given an array A and a value val, find an index i such that A[i]==val.
What if there is more than one i that works?
Normally, we just report one of them.
What if none work?
We must indicate this in some way, often we return -1, which cannot
be an index (all Java arrays have index starting at 0).
What if we want to search integers and also search reals?
We define two overloaded searches (or we learn about generics).
public static int linearSearch(int[] A, int val) { final int NOT_FOUND = -1; for (int i=0; i<A.length; i++) if (A[i] == val) return i; return NOT_FOUND; }
There is an obvious solution: Try A[0], if that fails try A[1], etc. If they all fail return -1. This is shown on the right.
The only problem with this approach is that it is slow if the array is large. If the item is found, it will require on average about n/2 loop iterations for an array of size n and if the item is not found, n iterations are required.
public static int[] eliminateDuplicates(int[] numbers)
Homework: 6.15. Write a method eliminateDuplicates() that removes the duplicate values in array. Use the following header line on the right. Write a test program that reads in ten integers, invokes the method, and displays the result.
A good solution would work for any size array and create the resulting array of just the right size.
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).
Although the routine is easy conceptually it is notoriously easy to get wrong, (e.g. lo=mid).
Sorting is crucial. As we just saw, fast searching depends on sorting.
There are many sorting algorithms; we will learn only two, both are bad. That is, both take time proportional to N2, where N is the number of values to be sorted. Good algorithms take time proportional to N*logN. This doesn't matter when N is a few hundred, but is critically important when N is a few million.
Moreover, serious sorting does not assume that all the values can be in memory at once.
We treat sorting much more extensively in Data Structures (102).
Having said all this, sorting is important enough that it is quite worth our while to learn some algorithms. Naturally, it also helps us learn to translate algorithms into Java.
Read.
The basic idea in both the 8e and bubble sort is that you start by ensuring that the smallest element is in the first slot (a[0]) and then repeat for the rest of the array (a[1]...a[a.length-1]).
for (j=1; j<a.length; j++) if (a[j] < a[0]) { // a[0] not min temp = a[0]; // so swap with a[j] a[0] = a[j]; // swapping takes a[j] = temp; // 3 instructions }
The difference between the selection sort in 8e and the bubble we
will do is how each gets the minimum element into A[0].
The code on the right shows the simple method used by bubble sort.
The code in the book is slightly more complicated and perhaps
slightly faster (both give bad
,
i.e., N2, sorts).
What remains is to wrap the code with another loop to find the 2nd smallest element and put it in A[1], etc.
Let's do that in class.
for (i=1; i<A.length; i++) // Insert A[i] correctly into A[0]..A[i-1]
Insertion sort is well described by the code on the right. A key point to note is that at each iteration A[0]...A[i-i] is already sorted.
The remaining question is how do we insert A[i] into A[0]..A[i-1] maintaining the sorted order?
In looking for the correct place to put A[i], we could either start with A[0] and proceed down the array or start with A[i-1] and proceed up the array. Why do we do the latter?
Consider an example where i is 20 and the right place to
put A[i] is in slot A[10].
Where are we going to put A[10]?
Answer: It must go in A[11].
Then where does A[11] go?
Answer: in A[12].
public static void insSort(int[] a) { for (int i=1; i<a.length; i++) { int ai = a[i]; int j = i-1; while (j>=0 && ai<a[j]) { a[j+1] = a[j]; j--; } a[j+1] = ai; } }
Thus we need to move the higher elements before we can move the lower. We can easily do this while searching for the correct spot to put A[i], providing we are traveling from higher to lower indices.
The code on the right deserves a few comments.
Homework:
Write a method that accepts an array of integers and prints out the
elements in sorted order with a count of how many times it appeared.
For example if the array contains. 1,5,2,3,8,2,1,5,5 you would
print
1 appears 2 times
2 appears 2 times
3 appears 1 time
5 appears 3 times
8 appears 1 time
The book notes that Java has many predefined methods for arrays. In particular java.util.Arrays.sort(a) would have sorted the array a (for any primitive type, thanks to overloading).
The best place to see all the methods (and classes, and more) that are guaranteed to be in all Java implementations is http://download.oracle.com/javase/7/docs/api/ (I use http://java.sun.com/javase/7/docs/api/).
Remark: Midterm exam Thursday 8 March, 2012. Tuesday 6 March is also possible, if preferred. Chapters 1-7. I have a practice exam on the web page already. This practice exam references a practice exam from 202; the latter shows the format that your real exam will have.
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
.
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.
These can be done separately or can be combined.
On the right we see three possibilities, the analogue of what we
did last chapter for one-dimensional (1D) arrays.
I wrote the third possibility twice using different spacings.
Arrays like this one are often called rectangular
.
They correspond to mathematical matrices.
In fact there are other possible ways to do the initialization since a 2D array in Java is really a 1D array of 1D arrays as we shall see in the very next section.
The simple diagram on the right is very important. It illustrates what is meant by the statement that Java 2D arrays are really 1D arrays of 1D arrays. That is, Java 1D arrays can have elements that are themselves 1D arrays.
The M in the diagram is the same as in the example code above.
Note first of all, that we again have reference semantics for the arrays. Specifically, M refers to (points to) the 1D array of length two in the middle of the diagram. Similarly M[0] refers to the top right 1D array of size 3. However, M[0][0] is a primitive type and thus it IS (as opposed to refers to) a double.
Next note that there are three 1D arrays in the diagram (M, M[0], and M[1]) of lengths, 2, 3, and 3 respectively. The difference between M and the others is that each element of M is of type double[]; whereas each element of M[0] and M[1] is of type double.
Finally, note that you cannot write M[1,0] for M[1][0], further suggesting that Java does not have 2D arrays as a native type.
One advantage of having 1D arrays of 1D arrays rather than true 2D arrays is that the inner 1D arrays can be of different lengths. For example, on the right, M is an array of length 2, M[0] is an array of length 3, and M[1] is an array of length 1.
Text that does not have its right margin aligned is said to have
ragged right
alignment.
Since the right hand boundary
of the component 1D arrays
look somewhat like ragged right text, arrays such as M are
called ragged arrays.
double [][] M = { {3.1,4,6}, {5.5} }; double [][] M; M = new double [2][]; // the 2 is needed M[0] = new double [3]; M[1] = new double [1]; M[0][0]=3.1; M[0][1]=4; M[0][2]=6; M[1][0]=5.5;
There are several ways to declare, create, and initialize a ragged array like M. The simplest is all at once as on the upper right.
The most explicit is the bottom code. Note how clear it is that M has length 2, M[0] has length 3, and M[1] has length 1.
Intermediates are possible. For example the first two lines can be combined. Another possibility is to combine the initialization of M[0] with its creation.
for (int i=0; i<n; i++) { | for (int i=0; i<M.length; i++) { for(int j=0; j<m; j++) { | for (int j=0; j<M[i].length; j++) { // process M[i][j] | // process M[i][j] } | } } | }
On the far right is the generic code often used when processing a single 2D array (I know 2D arrays are really 1D arrays of 1D arrays). This code works fine for ragged and non-ragged (often called rectangular) arrays. Note that the bound in the inner loop depends on i.
When the array is known to be rectangular (the most common case) with dimensions n and m, the simpler looking code on the near right is used.
Start Lecture #12
public static void main(String[] args) { double[][] m = { {1,2,3}, {4,5,6} }; method1(m); method2(m[1]); method3(m[1][1]); { public static void method1(????? x) {} public static void method2(????? x) {} public static void method3(????? x) {}
The code fragment on the right shows the declaration of a 2D array m and its use in three method invocations.
In the first invocation the entire array is passed; in the second we instantiate one of the dimensions to 1; in the third we instantiate both dimensions to 1.
What should the ????? be in the three method definitions that follow?
int n = getInput.nextInt(); int m = getInput.nextInt(); float[][] arr = new float[n][m]; for (int i = 0; i<n; i++) for (int j = 0; j<m; j++) arr[i][j] = getInput.nextFloat();
The code to define and read a 2D rectangular array is not hard and is shown on the right. Recall that I normally use the name getInput for my java.util.Scanner object.
Note that, although you can declare the array before knowing the bounds, you need the bounds before doing the allocation (invoking new).
double[][] Matrix; // compute and/or read Matrix double maximum = Matrix[0][0]; double minimum = Matrix[0][0]; double sum = 0; for (int i=0; i<n; i++) for (int j=0; j<m; j++) { if (Matrix[i][j] > maximum) maximum = Matrix[i][j]; if (Matrix[i][j] < minimum) minimum = Matrix[i][j]; sum += Matrix[i][j]; }
These are quite easy since the computation for each element of the matrix is independent of all other elements. The idea is simple, use 2 nested loops to index through all the all the elements. The loop body contains the computation for the generic element.
The code fragment on the right shows three parts of the program.
For a possible ragged array of unknown, simply replace n by Matrix.length and replace m by Matrix[i].length.
for (int i=0; i<A.length; i++) for (int j=0; j<A[0].length; j++) A[i][j] = 1 + (int)((N)*Math.random());
We use the nested loop construction appropriate for ragged arrays. The body requires remembering how to scale Math.random() to produce numbers from 1 to N.
The reason for (int) is in case the matrix A is floating point (but we want integer values).
5 4 11. 12. 13. 14. 2 21. 22. 1 31. 7 41. 42. 43. 44. 45. 46. 47. 7 51. 52. 53. 55. 55. 56. 57.
11. 12. 13. 14. 55. 21. 22. 31. 41. 42. 43. 44. 45. 46. 47. 51. 52. 53. 55. 55. 56. 57.
Suppose we want to read in the ragged array of floats shown on the near right. It is drawn to indicate that it has 5 rows, the first with 5 columns, the second with 2, the third with 1, the fourth and fifth with 7.
Somehow, we must convey this size/shape information to the program. Often this is done with counts as shown above on the far right.
import java.util.Scanner; public class ReadingRaggedArrays { public static void main(String[] args) { Scanner getInput; getInput = new Scanner(System.in); int n = getInput.nextInt(); // n rows (const) int m; // m cols (varies) float[][] arr = new float[n][]; for (int i = 0; i < n; i++) { m = getInput.nextInt(); // num cols this row arr[i] = new float[m]; for (int j = 0; j < m; j++) arr[i][j] = getInput.nextFloat(); } } }
The Java program on the right reads data in the format shown above and allocates all the relevant arrays (remember that a 2D array with 5 rows is really six 1D arrays).
After the first five lines, which we have seen many times before, we read n, the number of rows and allocate arr, a 1D array each of whose n components is a 1D array of floats.
Then, for each of the n rows we read m,the number of columns and allocate a 1D array of floats, to hold the elements of the row
Finally, we are able to actually read and store the actual elements of the array.
Read
public static void matrixMult (double [][] A, double [][] B, double [][] C) { // check that the dimensions are legal for (int i=0; i<A.length; i++) for (int j=0; j<A[0].length; j++) { A[i][j] = 0; for (int k=0; k<C.length; k++) A[i][j] += B[i][k]*C[k][j]; } }
Remark: Write the formula on the board. I do not assume you have memorized the formula. If it is needed on an exam, it will be supplied.
For multiplication the matrices must be rectangular. Indeed, if we are calculating A=B×C, then B must be an n×p matrix and C must be a p×m matrix (note the two ps). Finally, the result A must be an n×m matrix.
The code on the right does the multiplication, but it omits the dimensionality checking. Note that this checking would involve loops (to ensure that the arrays are not ragged).
Since the first parameter is an array, the values stored in the array will be visible in the corresponding argument of the calling method.
An alternative implementation would be to have the caller supply the array bounds and trust that the arrays are of the size specified. Then the method definition would begin
public static void matrixMult (int n, int m, int p, double [][] A, double [][] B, double [][] C) { // A is an n by m matrix, B is n by p matrix, C is a p by m matrix
A full program for rectangular matrices is here.
Homework: 7.5.
Read
Read
import java.util.Scanner; public class Intermediate { public static void main (String[] args) { final double [][] points = { {0,0}, {3,4}, {1,9}, {-10,-10} }; System.out.println("Input 4 doubles for start and end points"); Scanner getInput = new Scanner(System.in); double [] start = {getInput.nextDouble(),getInput.nextDouble()}; double [] end = {getInput.nextDouble(), getInput.nextDouble()}; int minIndex = 0; double minDist = dist(start, points[0]) + dist(points[0], end); for (int i=1; i<points.length; i++) if (dist(start,points[i])+dist(points[i],end) < minDist) { minIndex = i; minDist = dist(start,points[i])+dist(points[i],end); } System.out.printf("Intermediate point number %d (%f,%f) %s%f\n", minIndex, points[minIndex][0], points[minIndex][1], " is the best. The distance is ", minDist); } public static double dist(double [] p, double [] q) { return Math.sqrt((p[0]-q[0])*(p[0]-q[0])+ (p[1]-q[1])*(p[1]-q[1])); } }
Assume you are given a set of (x,y) pairs that represent points on a map. For this problem we join the Flat Earth Society and assume the world is flat like the map.
Read in two (x,y) pairs S and E representing your starting and ending position and find which point P of the given points minimizes the total trip consisting of traveling from S to P and from P to E.
Note the following aspects of the solution on the right.
Not as interesting as the title suggests. The program in this section just checks if an alleged solution is correct.
double x[][][] = { { {1,2,3,4}, {1,2,0,-1}, {0,0,-8,0} }, { {1,2,3,4}, {1,2,0,-1}, {0,0,-8,0} } ];
There is nothing special about the two in two-dimensional arrays. We can have 3D arrays, 4D arrays, etc.
On the right we see a 3D array x[2][3][4]. Note that both x[0] and x[1] are themselves 2D arrays.
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.
A cute guessing game. The Java is not especially interesting, but you might want to figure out how the game works.
Homework: 7.7
Remark: End of material on midterm.
Remark: Lab 2 due date extended to 20 March.
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.
An object is an entity containing both data (called fields in Java) and actions (called methods in Java). Two examples would be a circle in the plane and a stack.
For a circle, the data could be the radius and the (x,y) coordinates of the center. Actions might include, moving the center, changing the radius, computing the area, or (in a graphical setting) changing the color in which the circle is drawn.
For a stack, the data could include the contents and the current top of stack pointer. Actions might include pushing an element on to the top of the stack and popping the top element off.
In Java, objects of the same type, e.g., many circles, are grouped together in a class.
Start Lecture #13
Remark: Homework solutions for chapters 4-6 are posted. I will post chapter 7 after class. Remind me via the mailing list tonight if I forget.
Remark: I posted practice midterm solutions.
Public class LongStack { int top = 0; long[] theStack; void push(long elt) { theStack[top++] = elt; } long pop() { return theStack[--top]; } }
On the right is the beginnings of a class for
stacks containing Java long's.
The fields
in the class are top and
theStack; the methods are push() and
pop().
Unlike local variables, which we have seen many times before, a field is not local to (i.e., inside of) a method.
Three obvious problems with this class are.
Given a class, a new object is created by instantiating the class. Indeed, an object is often called an instance of its class.
Many classes supply one or more special method called constructors that Java invokes automatically whenever an object is instantiated. Although a constructor can in principle perform any action, normally its task is to allocate and initialize the fields. For example, a longStack constructor would create the array theStack.
public class TestCircle1 { /* Main method */ public static void main(String[] args) { // Create a circle with radius 5.0 Circle1 myCircle = new Circle1(5.0); System.out.println("The area of the circle of radius " + myCircle.radius + " is " + myCircle.getArea()); // Create a circle with radius 1 Circle1 yourCircle = new Circle1(); System.out.println("The area of the circle of radius " + yourCircle.radius + " is " + yourCircle.getArea()); // Modify circle radius yourCircle.radius = 100; System.out.println("The area of the circle of radius " + yourCircle.radius + " is " + yourCircle.getArea()); } } // Define the circle class with two constructors class Circle1 { double radius; /** Construct a circle with radius 1 */ Circle1() { radius = 1.0; } /** Construct a circle with a specified radius */ Circle1(double newRadius) { radius = newRadius; } /** Return the area of this circle */ double getArea() { return radius * radius * Math.PI; } }
On the right is an example from the book. Note several points.
A method can be declared static. The main() method in the class TestCircle is an example. Such a method is called a class method or static method and is associated with the class as a whole. In contrast a non-static method, called an instance method, is associated with an individual object. The Circle1 class has one instance method, no class methods, and two constructors.
Thus when the two Circle1 objects are created, each gets its own getArea() method.
In a like manner a field can be declared to be static (none are in this example) in which case it is called a class field (or sometimes a static field) and there is only one that is shared by all objects instantiated from the class.
A field not declared to be static is called an instance field and each object gets its own copy; changing one, does not affect the others. An object instantiated from a class is often called an instance of the class.
Putting this together we see that when the main() class invokes getArea() it must specify which getArea() is desired, i.e., which object's getArea() should be invoked. So myCircle.getArea() invokes the getArea() associated with myCircle and hence when it mentions radius (an instance field) it gets the radius associated with myCircle.
public class TV { int channel = 1; // Default channel is 1 int volumeLevel = 1; // Default volume level is 1 boolean on = false; // By default TV is off public TV() {} public void turnOn() { on = true; } public void turnOff() { on = false; } public void setChannel(int newChannel) { if (on && newChannel >= 1 && newChannel <= 120) channel = newChannel; } public void setVolume(int newVolumeLevel) { if (on&&newVolumeLevel>=1 && newVolumeLevel<=7) volumeLevel = newVolumeLevel; } public void channelUp() { if (on && channel < 120) channel++; } public void channelDown() { if (on && channel > 1) channel--; } public void volumeUp() { if (on && volumeLevel < 7) volumeLevel++; } public void volumeDown() { if (on && volumeLevel > 1) volumeLevel--; } }
public class TestTV { public static void main(String[] args) { TV tv1 = new TV(); tv1.turnOn(); tv1.setChannel(30); tv1.setVolume(3); TV tv2 = new TV(); tv2.turnOn(); tv2.channelUp(); tv2.channelUp(); tv2.volumeUp(); System.out.println("tv1's channel is " +tv1.channel+" and volume level is " +tv1.volumeLevel); System.out.println("tv2's channel is " +tv2.channel+" and volume level is " +tv2.volumeLevel); } }
Above and to the right we see our first program written in two files. It is from the book. Each file has one public class that determines the name of the file.
Again note that the instance methods are referred to as objectName.methodName.
To compile and run this program you would type.
javac TestTV.java TV.java java TestTV
The last line could not have been java TV since you must invoke the file that has the main() method.
We have seen constructors above. Note the following points.
The purpose of many constructors is to (allocate and) initialize the object being created via new.
Normally, a class provides a constructor with no parameters (as well
as possibly other constructors with one or more parameters).
This constructor is often called the no argument
or
no-arg
constructor.
If the class contains NO constructors at all, the system provides a no-arg constructor with empty body. Thus the no-arg constructor in class TV could have been omitted.
class LongStack { int top = 0; long[] theStack; LongStack() { theStack = new long[100]; } LongStack(int n) { theStack = new long[n]; } void push(long elt) { if (top>=theStack.length) System.out.println("No room to push!"); else theStack[top++] = elt; } long pop() { if (top<=0) { System.out.println("Nothing to pop!"); return -99; // awful!! } return theStack[--top]; } } public class DemoLongStack { public static void main (String[] args) { long x = 333, y = 444; LongStack myLStk = new LongStack(4); myLStk.push(x); myLStk.push(y); System.out.printf("Before: x=%d and y=%d\n", x, y); x = myLStk.pop(); y = myLStk.pop(); System.out.printf("After: x=%d and y=%d\n", x, y); } } Before: x=333 and y=444 After: x=444 and y=333
Now that we know a little more about constructors, we can add them to class LongStack, and, while we are at it, put in some error checks. The revised code is on the right.
If we knew about Java exceptions, it would be good to utilize them for these errors. In particular, returning a -99 is awful.
The default stack, i.e., the one created with the no-arg constructor, has room for 100 elements. The user can, however, specify the size when invoking new and then the other constructor will be called.
Note that LongStack is no longer a public
class.
With no public (or private or protected)
declaration, the default is that LongStack is visible
within the current package
.
Packages are important for large programs and will be discussed a little in section 8.8. For now you can think of LongStack as a public name but, since the file has only one real public class (DemoLongStack, it must be named DemoLongStack.java.
The main() method declares a longStack and uses it a little. Below the line, we see the output produced, which indeed exhibits stack-like (FIFO) semantics.
Note especially the command myLStk.push(x), which we now discuss.
When compared to most programming languages something looks amiss
with this statement.
The purpose of the command is to push x on to the stack
myLStk and hence should
be written something like
push(x,myLStk).
Similarly, the purpose of x=myLStk.pop() is to pop the
stack and place the value into x.
So it should be something like x=pop(myLStk).
However, in Java, and other object-oriented languages, it is written as shown in DemoLongStack. The idea is that the LongStack object myLStk contains not only the data (in this case top and theStack) but the methods push() and pop() as well.
We have previously seen this dotted notation (object dot methodName). For example, getInput.nextInt().
If push() were a class method instead of an instance method, then an invocation would include the stack as a parameter, perhaps LongStack.push(x,myLStk).
Start Lecture #14
Start Lecture #15
Remarks:
Median grade on the midterm exam was 86.
Lab 3 is available.
We have a second grader Taolun Chai <tc1112@nyu.edu>.
If your last name starts with M-Z, please submit your future labs to
Taolun.
Recall the serious business
we mentioned in
section 6.5 when discussing copying arrays.
Liang finally comes to grips with it here.
As with arrays, variables for objects contain references to the objects not the objects themselves.
Naturally, if a class ClassBig has many large fields, objects of the class are large. Similarly, if ClassSmall has just one int as a field, objects of this class are small. However, an important technical point to note is to consider
ClassBig big = new ClassBig(); ClassSmall small = new ClassSmall();
Then big and small are the same size. Neither contains an object; instead each contains of a reference to an object (i.e., a pointer, which is essentially an address). You don't need a bigger address label to send a letter to a mansion than to a studio apartment.
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
.
int top, which is a primitive type so actually contains the value.
The above is very important. It is a key tenant of Java that primitive types have value semantics and that both arrays and objects have reference semantics.
We will learn later that, for each primitive type, there is a corresponding class with a similar name (but naturally with reference, not value, semantics). Specifically, the names are Byte, Short, Integer, Long, Boolean, Character, Float, Double.
After creation, methods and data fields within objects are referenced using dot notation as mentioned above and illustrated by getInput.nextInt(), myLStk.pop(), and myLStk.top.
Note that the fields and methods just mentioned do not have the static modifier. We shall see static fields later and have seen many static methods already. Fields and methods that are not static are called instance fields (or instance variables) and instance methods.
If in the DemoLongStack example above, the main() method created two LongStack's, each one would have its own top, theStack, push(), and pop().
As I mentioned several lectures ago, Java assigns default values to many variables, but I do not use or recommend learning the default values. I always (try to remember to) explicitly initialize the variables at their declaration or ensure that they are assigned a value before there value is requested.
As mentioned previously Java does not always assign a default value. Since I suggest not making use of default values, I don't see a need to remember which variables get defaults (and what the defaults are) and which variables do not.
Nonetheless there is one default you will need to recognize as it will indicate a fairly common error. If you declare an object or array but do not create it (with new), then it receives the default value (really default reference or default pointer) null. If you then attempt to use the object, you can get a NullPointerException.
Here is an example from the LongStack class above. If the class had no constructors, then the declaration/creation of myLStk in DemoLongStack would be erroneous since there would be no constructor to match the new operator. If, in addition. the declaration/creation was changed to not specify the size, the program would compile and the default, do-nothing no-arg constructor would be called. The result would be that theStack would be declared but not created. Then, when the first push is invoked, a NullPointerException would be raised because theStack contains null.
Start Lecture #16
Remark: Answer questions about lab 3 part 3
int[][] c = { {4,5,6}, {7,8,9} }; int[] a = c[0]; int[] b = a; b[1] = 999; System.out.println(a[1]); System.out.println(c[0][1]);
This section of the book finally gives the pictures showing that reference types only point to values; they do not contain values. All this applies equally well to arrays where we previously discussed it.
The example on the right illustrates reference semantics. Both values printed are 999.
A significant strength of Java is its very extensive class library.
Let's use this opportunity as an excuse for reviewing how to obtain information from http://java.sun.com/javase/7/docs/api.
The 8e mentions two constructors and three methods (toString(), getTime(), and setTime()).
If you knew that Date was java.util.Date you could restrict the classes, but let's not and use the default of all classes.
The same two constructors are present as in the 8e (along with several that are deprecated). The methods are there as well, together with many others (some deprecated).
import java.util.Date Date myDate = new Date(); System.out.println(myDate.toString());
Don't forget the syntax of objectName.methodName.
On the right is a simple example.
How would the example have differed without
the import?
Answer: Date would have been java.util.Date
twice in the declaration/creation.
Again use the web and find the constructors and methods.
Homework: 8.3.
We have now seen several methods in classes that are associated with objects. For example myDate.toString() invokes the toString() method that is associated with the object myDate. If we had also declared another Date, say yourDate, then myDate.toString() would be a different method from yourDate.toString().
This is a good thing since the string format of myDate is different from the string format of yourDate (unless myDate and yourDate happen to refer to the exact same time).
Similarly, we have seen several data fields defined in classes that are associated with each instance of the class. For example, the top of each instance of LongStack is unique.
This is also good. If you have several LongStack's, you don't expect their top's to be equal and surely pushing one LongStack does not affect the top of any other LongStack.
class ShortStack { int top = 0; short[] theStack; ShortStack() { theStack = new short[100]; } ShortStack(int n) { theStack = new short[n]; } // Maintain total number of items stacked static int totalStacked = 0; // should be pvt static int getTotalStacked() { return totalStacked; } void push(short elt) { if (top>=theStack.length) System.out.println("No room to push"); else theStack[top++] = elt; totalStacked++; } short pop() { if (top<=0) { System.out.println("Nothing to pop."); return -99; // awful!! } totalStacked--; return theStack[--top]; } } public class DemoShortStack { public static void main (String[] args) { ShortStack mySStk1 = new ShortStack(4); ShortStack mySStk2 = new ShortStack(); mySStk1.push((short)12); mySStk1.push((short)13); mySStk2.push((short)22); mySStk2.push((short)23); System.out.printf("Before pops: stacked=%d\n", ShortStack.getTotalStacked()); short x = mySStk1.pop(); short y = mySStk1.pop(); System.out.printf("After pops: x=%d, y=%d %s%d\n", x, y, "and stacked=", ShortStack.getTotalStacked()); } } Before pops: stacked=4 After pops: x=13, y=12 and stacked = 2
But sometimes having separate instances of each method and field for each object is not what we want. On the right we see ShortStack. I derived it from LongStack, by first replacing all long's with short's.
Then I decided to keep track of the total number of items stacked on all the ShortStack's. This single variable totalStacked is incremented by every push(), no matter which ShortStack is being pushed, and is decremented by every pop(). We indicate that there is one totalStacked associated with the entire class ShortStack rather than one totalStacked associated with each instance of shortStack (i.e., one associated with each object) by declaring totalStacked to be static.
A variable like totalStacked that is associated with the class and not with each instance is called a static variable or a class variable.
Unlike instance variables that are referred to as objectName.variableName, static variables (or class variables) are referred to as className.variableName.
I could have printed this value in the main() method, but did not. Instead, I had the class define a method getTotalStacked() that returns the value of totalStacked. and had main() call getTotalStacked().
As we will see in the next section, it is possible to prevent other classes from accessing a variable, which has the great advantage that the author of the original class can be sure that no other class either changes the variable or depends on how the variable is implemented.
In such cases it is common to supply an accessor method
like getTotalStacked()
to get the value and sometimes another
method to alter the value.
We could have writen getTotalStacked() as an instance method, so that there would be one associated with each object of type ShortStack. However, that would be a little silly (but would work) since the method just returns the value of a class field and hence all instances of the method would be the same. Hence we declare getTotalStacked() to be static so that there is only one getTotalStacked() method for the entire class. That is, we make it a class method or static method.
One more point, one that will reveal a great secret. Since a class method is dependent on only the class and not on any object of the class, we can define and use a class method even if there are no objects of the class. At long last we can understand
public static void main (String[] args)
which must be used in every Java program.
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.
(Top level classes, i.e., classes not inside any other class, can not be declared private or protected.)
An entity (a class, a field, or a method) that is declared to be public can be accessed from any class.
An entity that is declared to be private is accessible only within its own class.
public class PubClass { public int x; // accessible everywhere private int f2() { // accessible only in PubClass return 2; } private class PvtClass { // x and f2() accessible here } } // Following is a new file public class C2 { // x accessible f2() is not private class C1 { // x accessible f2() is not } }
Look at the example on the right and note the following.
Java includes the notion of a package, which is a
collection of classes.
A .java file can begin with a line
package packageName;
which declares all the classes in the file to be in the package
packageName.
Packages are very important for large programs, but less so for those we will write.
None of our .java files have included a
package statement.
In such cases Java places the classes declared in the file into the
so called default package
.
If an entity is declared without a visibility modifier (public, private, or protected), then the entity has package visibility, which means it is visible in all classes belonging to the same package that the entity belongs to.
The protected visibility modifier comes into play only when there are subclasses and inheritence so we defer it to Chapter 11.
Until we learn about subclasses and protected, our use of modifers will be fairly simple (in part because we are not learning about packages).
Homework: 8.7
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?
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).
mutatormethod is interesting. If we want to let clients modify the radius field, why didn't we just make it public?
Homework: 8.9.
class ShortStack { private int top = 0; private short[] theStack; ShortStack() { theStack = new short[100]; } ShortStack(int n) { theStack = new short[n]; } private static int totalStacked = 0; static int getTotalStacked() { return totalStacked; } int getTop() { return top; } void push(short elt) { if (top>=theStack.length) System.out.println("No room to push"); else theStack[top++] = elt; totalStacked++; } short pop() { if (top<=0) { System.out.println("Nothing to pop."); return -99; // awful!! } totalStacked--; return theStack[--top]; } } public class DemoShortStack2 { public static void main (String[] args) { ShortStack mySStk1 = new ShortStack(4); ShortStack mySStk2 = new ShortStack(); mySStk1.push((short)12); mySStk1.push((short)13); mySStk2.push((short)22); mySStk2.push((short)23); System.out.printf("Before pops: stacked=%d\n", ShortStack.getTotalStacked()); short x = mySStk1.pop(); short y = mySStk1.pop(); System.out.printf("After pops: x=%d, y=%d %s%d\n", x, y, "and stacked=", ShortStack.getTotalStacked()); System.out.println("Printing and emptying stack #1"); printAndEmptyShortStack(mySStk1); System.out.println("Printing and emptying stack #2"); printAndEmptyShortStack(mySStk2); } public static void printAndEmptyShortStack(ShortStack SStk){ System.out.printf("%d items stacked, %d in this stack.\n", ShortStack.getTotalStacked(), SStk.getTop()); for (int i=1; SStk.getTop()>0; i++) System.out.printf("Item #%d is %d\n", i, SStk.pop()); System.out.println(); } } Before pops: stacked=4 After pops: x=13, y=12 and stacked=2 Printing and emptying stack #1 2 items stacked, 0 in this stack. Printing and emptying stack #2 2 items stacked, 2 in this stack. Item #1 is 23 Item #2 is 22
As with arrays, passing an object to a method actually passes a reference (pointer) to the object.
The book illustrates this with one of the circle classes. You should read that.
For variety (i.e., a different example) we consider our ShortStack class.
As illustrated on the right, I have included several improvements
and enhancements that I will describe just below.
Although I wrote both classes, it is useful to think of
the ShortStack class as written by the class
writer
or the author
and to think
of DemoShortStack as written by the user
.
The simplest improvement is that the Data fields top, theStack and totalStacked are now private. As mentioned previously, this prevents the user from modify these variables, which makes the author's job much easier.
Accessors are provided for top and totalStacked giving the user read-only access to the variables.
In addition to the author's enhancements to ShortStack the user added a method printAndEmptyShortStack(). Since ShortStack is public, it is visible to printAndEmptyShortStack() as are all of ShortStack's public methods. For this reason printAndEmptyShortStack() is as easy for the user to write as it would be for the class author. Since it is a rather specialized method, it doesn't seem worthwhile to include in the general-purpose ShortStack class.
The printAndEmptyShortStack() method has one parameter; it is of type ShortStack.
Note the use of the accessor functions getTotalStacked() and getTop(). Specifically, note that, since getTotalStacked() is a class method, it is referenced as className.methodName(), namely ShortStack.getTotalStacked(). In contrast, since getTop() is an instance method, it is referenced as objectName.methodName, namely SStk.getTop().
Start Lecture #17
How can you pass a reference (an object is a reference) by value? I thought values were for primitive types, not for references.
That does sound bad but yes the reference is passed by value, it is copied from the argument to the parameter and is not copied back (standard call-by-value semantics)
This is exactly the same as passing an array, which we did a few weeks ago.
The diagram on the right shows the situation when main() has called printAndEmptyShortStack() for the first time and the loop is just starting.
For technical reasons, covered in elective courses in compilers and comparative programming languages, objects themselves are stored in a region of memory separate from local variables. This region is normally called the heap.
The variables local to a method are stored in a region called the stack (yes, it does have stack-like properties).
These variables disappear when the method returns, and their size is known when the program is written. Both these properties need not hold for entities stored in the heap.
Although this separation of stack from heap is quite important for efficiency, we will not emphasize it in this course.
The diagram illustrates value semantics for the int and
short variables x, y,
and i.
Specifically the memory location assigned to one of these variables
contains the current value of the variable (this so called
value semantics
holds for all primitive types).
In contrast, consider the objects SStk and mySStk1 (really, we are considering the variables that are declared to be objects) In this example all objects are ShortStack's. These have reference semantics. The memory assigned to one of these variables contains only a pointer to the object and not the object itself.
Also note the call-by-value semantics. The contents of the argument myStk1 is copied into the parameter SStk.
To understand arrays of objects we just combine the semantics of arrays with the semantics of objects. Consider the diagram on the right.
In Java a String is an object. Thus, like a ShortStack, a String has reference semantics.
In some languages a string is an array of characters. Although in Java an array of char's is similar to a String (e.g., both have reference semantics), they are not the same (e.g., you do not use [] to extract a char from a String).
As shown on the right, String's can be created several ways.
System.out.println("A string"); String s0; String s1 = new String(); String s2 = new String("A string"); if (s2 != "A string") System.out.println("Outrageous"); char[] c1 = {'c','h','a','r','s'}; String s3 = new String(c1);
referring toit).
String objects are immutable, they cannot be changed. However, a string variable is mutable and can reference different string objects at different times.
String str1 = "joe"; String str2 = "linda"; str1 = "linda";
On the right we have two String objects: "joe" and "linda", and two String variables: str1 and str2. The objects cannot change but we have changed the str1 variable.
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.
if (str1 == str2) // Yes System.out.println("Of course"); String str3 = new String("linda"); if (str1 != str3) // Yes !! System.out.println("Outrageous!"); String str4 = new String(str1); if (str1 != str4) // Yes !! System.out.println("Outrageous!"); javac Test.java && java Test Of course Outrageous! Outrageous!
After the three statements above, both str1 and str2 refer to this one literal. That is, str1 and str2 contain the same reference and, as indicated to the right they of course compare equal (==).
However, when you create a string using the one-argument constructor as done on the right, a copy is made. Since str3 refers to this copy it is NOT equal (!=) to either str1, str2 or "linda", which seems outrageous!
Equally outrageous is the situation with str4
These last two paragraph seem very bad. We have three String variables str1, str3, and str4, each of which would print the same string linda, but they are not equal. Fortunately, there are more sections to this chapter.
The problem we just had was that we were comparing the references and not the values they referred to. To do the latter we need to use a method in the String class.
boolean equals (Object anObject) String s1 = "joe"; String s2 = new String(s1); if ("joe".equals(s1)) // YES System.out.println("Of course"); if ("joe".equals(s2)) // YES System.out.println("Of course"); if (s1.equals(s2)) // YES System.out.println("Of course"); Of course Of course Of course
On the right we see the equals() method. Technically, it has an Object as parameter, but for now pretend that the only permitted Object is a String.
Something looks wrong! How come equals has only one parameter. Surely, we want to see if two strings are equal. The answer is that equals() is an instance method and thus is attached to a string object, which it then compares to its parameter.
We see examples of the usage on the right.
Note that, in these examples if(s1==s2)
would be
NO.
There are other string comparison methods defined, e.g., one that
ignores case and one (compareTo() that distinguishes
less than
from greater than
(see below).
Java has many methods dealing with strings. We will just touch on a few. See the API for a list.
int length() char charAt(int index) String concat (String s)
The three methods on the right length(), charAt(), and concat() are all instance methods. That is, they are associated with an instance of a class, i.e., an object. The notation used is therefore objectName.methodName(args). Note that the named object is a de facto extra parameter.
String substring(int start) String substring(int start, int end)
There are two overloaded substring() instance methods. The first, one argument, variant gives the substring starting at the position specified by the argument. The other, two argument, variant gives the substring starting at the position specified by the first argument and ending just before the position specified by the second argument.
For example, "linda".substring(1,2)=="i" .
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.
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.
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.
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.
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.
The other class method is String.format(). It takes the same arguments as System.out.printf() but, instead of printing the result, it returns it as a String.
String s = String.format("6.2f",3.78);
For example, the statement on the right sets s equal to " 3.78".
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.
Read
The compareTo() String instance method makes alphabetizing essentially the same as sorting integers, as shown by the side by side code comparison below.
public class DemoSortString { public static void main (String[] args) { String[] strArray = {"a","zzz","abc","ab","Z","6"}; System.out.println("Before"); for (int i=0; i<strArray.length; i++) System.out.println(strArray[i]); sortString(strArray); System.out.println("After"); for (int i=0; i<strArray.length; i++) System.out.println(strArray[i]); } public static void sortString(String[] strArray) { for (int i=0; i<strArray.length-1; i++) for (int j=i+1; j<strArray.length; j++) if(strArray[i].compareTo(strArray[j]) > 0) { String t = strArray[i]; strArray[i] = strArray[j]; strArray[j] = t; } } }
public class DemoSortInt { public static void main (String[] args) { int[] intArray = {6, 9, 2, 4, 1, 8}; System.out.println("Before"); for (int i=0; i<intArray.length; i++) System.out.println(intArray[i]); sortInt(intArray); System.out.println("After"); for (int i=0; i<intArray.length; i++) System.out.println(intArray[i]); } public static void sortInt(int[] intArray) { for (int i=0; i<intArray.length-1; i++) for (int j=i+1; j<intArray.length; j++) if(intArray[i] > intArray[j]) { int t = intArray[i]; intArray[i] = intArray[j]; intArray[j] = t; } } }
The only real difference between the two code snippets is that the integer comparison via > is replaced by compareTo(), which returns a positive, zero, or negative integer depending on whether the given object is greater than, equal to, or less than the argument.
public static int compareStr(String s1, String s2) { final int EQUAL=0, LESS=-123, GREATER=321; for (int i=0; i<s1.length() && i<s2.length(); i++) if (s1.charAt(i) < s2.charAt(i)) return LESS; else if (s1.charAt(i) > s2.charAt(i)) return GREATER; if (s1.length() < s2.length()) return LESS; else if (s1.length() > s2.length()) return GREATER; else // s1.length() == s2.length() return EQUAL; }
For fun let's try to write a class method compareStr() that takes two arguments and returns a positive, zero, or negative integer depending on whether the first argument is greater than, equal to, or less than the second.
This is one of those programs where I found it useful to think first and just sketch out the idea before starting to code. It is actually fairly easy, but you can be lead astray.
The code on the right has two parts.
Wrapper) Class(es)
As mentioned, Java is an object oriented (OO) language and objects/classes play a central part in serious Java programming.
However, the advantages of OO languages comes in to play mostly for large programs, much larger than any we will write. So, for us as of now there isn't all that much advantage to using an object of type Character rather than a value of type char.
We call the class Character a wrapper
for the
primitive type char
.
Similarly, Java provides wrapper classes Boolean,
Byte, Short, Integer, Long,
Float, and Double for the corresponding primitive
types boolean, byte, short, int,
long, float, and double.
Do remember that the wrapper classes provide objects, and thus have reference semantics.
It is easy to obtain the Character corresponding to a given char: The Character class provides a constructor for just this purpose. Thus, new Character('a') produces the Character corresponding to the char 'a' .
boolean isDigit(char c) boolean isLetter(char c) boolean isLetterOrDigit(char c) boolean isLowerCase(char c) boolean isUpperCase(char c) char toLowerCase(char c) char toUpperCase(char c)
Perhaps the piece of the Character class most useful to us now, is the collection of class methods that operate on char's (not Character's).
On the right we see seven of these useful class methods. As an example to test if the char c) is a digit, you would write
if (String.isDigit(c))
Note the consistent naming: the prefix is
is used for a test,
and the prefix to
is used for a conversions.
Homework 9.1 and 9.5.
Start Lecture #18
Java has 3 classes whose objects are strings
, namely
String, StringBuilder, and StringBuffer.
import java.util.Date; class Test { public static void main (String[] Args) { StringBuilder sB = new StringBuilder("Hello"); String s = new String ("Hello"); System.out.printf("s is \"%s\" and sB is \"%s\"\n", s, sB); System.out.printf("The lengths of s and sB are %d and %d\n", s.length(), sB.length()); System.out.printf("The capacity of sB is %d\n", sB.capacity()); s = s.concat(", world."); sB.append(", world."); System.out.printf("s is \"%s\" and sB is \"%s\"\n", s, sB); } } s is "Hello" and sB is "Hello" The lengths of s and sB are 5 and 5 The capacity of sB is 21 s is "Hello, world." and sB is "Hello, world."
Superficially a String s and a StringBuilder sB appear to act the same. Indeed, a quick glance at the code on the right suggests that StringBuilder adds nothing.
In this case, however, looks can be deceiving. Specifically, s.concat() and sB.append() are really quite different because the String referenced by s is immutable; whereas, the StringBuilder referenced by sB is not.
The expression sB.append(", world") actually appends the argument onto the object referenced by sB. It does not copy the original contents of that object.
In contrast s.concat(", world") does not, indeed cannot change the string referenced by s. What happens is that
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.
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.
All of these are instance methods. As expected sb.toString() returns the String equivalent of the StringBuilder sb, sb.length() returns the length, and sb.charAt() returns the character at the position specified by the argument.
The capacity() instance method is less obvious. A Java StringBuilder object is generally bigger than its current length (this larger size is called the capacity). That way it is easy for the system to append characters. Except for efficiency considerations, we can ignore capacity since the JVM will extend the StringBuilder whenever needed.
You can also reduce the length of a StringBuilder (eliminating characters at the end) or increase its length (padding with null characters).
The book's solution is basically an exercise in using a few of the methods. The basic steps are
The reason for converting back to String's is that (surprisingly?) I don't think StringBuilder has an equals() or compareTo() method.
I did this without a StringBuilder, but it is less efficient since Strings are immutable so my solution involves more copying
Homework: 9.11
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.
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.
Remark: One night I was editing my magazine column and needed a function to reverse a number. Since I just taught it a few hours previously in 101, I actually wrote one of those routines that is all library method calls. Then, for fun I made it an ugly one-liner. Here is the result.
import java.util.Scanner; import java.io.File; public class TechRev { public static int reverse (int n) { // StringBuilder sb = new StringBuilder(Integer.toString(n)); // return Integer.parseInt(sb.reverse().toString()); return Integer.parseInt(new StringBuilder(Integer.toString(n)).reverse().toString()); } }
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.
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.
In Java reading and writing files is divided into two tasks.
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.
The File class includes exists(), canRead(), canWrite(), isDirectory(), isFile, isAbsolute(), and isHidden().
Their names describe well their actions.
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.
System.out.println(new java.util.Date(file.lastModified()));prints the modification date in a human-readable format.
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.
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.
We have used the Scanner class many times, but always with System.in, which is predefined to correspond to the keyboard. In order to obtain a Scanner capable of reading from the file named existing.text we write (assuming the necessary import's have been executed.
Scanner input = new Scanner(new File ("existing.text"));
You can also write this declaration (and the one for PrintWriter) in two steps.
File f = new File("existing.text"); Scanner input = new Scanner(f);
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.
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.
The nextLine() method is different and is not considered token-reading. It acts as follows.
Remark: Lab 4 is available.
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.
Remark: The real work is done in the one statement beginning String s2, in particular by the library method replaceAll(). The rest of the code is essentially just checking and preparing for the library call. It is not uncommon for the actual work to be done by a small portion of the code your write.
Homework: 9.19.
We have seen already some differences when using objects. With instance methods one of the parameters is the object itself and is written differently. For example given a String s1, we get its length via s1.length() rather than length(s1). However designing (large) systems using objects as centerpieces (so called object-oriented design) has deeper differences from conventional design than the essentially syntactic change in length().
An object is called immutable if, once created, it cannot be changed.
Let me be clearer about that. When we say the object cannot be changed, we mean the data fields of the object cannot be changed.
For example, any String object is immutable (its only data field is the sequence of characters that constitute the string). A class, such as String, all of whose objects are immutable is also called immutable.
In contrast a StringBuilder can change after construction, as we have seen. So the StringBuilder class is mutable
What must we do to write an immutable class?
Naturally, we must make all the data fields private and
no public method can change a data field.
Such methods are often called mutators
.
But that is not enough!
public class NoChanges { private int x; public NoChanges(int z) { x = z; } public int getX() { return x; } }
Looking to the right, clearly the class NoChanges is immutable: the only data field is x, which cannot be directly accessed (it is private). With the accessor getX() we can find the value of x, but cannot change it. Thus once we create a NoChanges object with say
NoChanges nc = new NoChanges(5).
then the x component of this NoChanges object has the value 5 and will never change.
But the variable nc can be assigned to another NoChanges object.
public class Changes { public int a; public int b; public Changes(int r, int s) { a = r; b = s; } }
In contrast, the class Changes is not immutable (it is mutable). For example, consider the code below
Changes c = new Changes (5,6); c.a = 9;
This example has changed the object c by changing its first component from 5 to 9.
public class Maybe { private Changes c; public Maybe(int w) { c = new Changes (w,w); } public Changes getC() { return c; } } public static void main (String[] args) { Maybe mb = new Maybe(12); mb.c.a = 1; // Will not compile mb.getC().a = 1; // Works fine }
The interesting case is class Maybe. At first (and even second) glance it looks like class NoChanges. Indeed each line of Maybe looks like the corresponding line of NoChanges. The only difference is the initialization of c. But just as with NoChanges, we do not have direct access to c (it is private) and the accessor lets us only find c's current value, not change it.
But there is a difference! The variable c is of type Changes, which is an object and hence has reference semantics. Also the object to which we now have a reference is itself mutable. Therefore the accessor has returned a reference that enables us to change the value of the components.
Code to exploit this hole is shown in the bottom frame on the right.
This should remind you of passing an array to a method. The method cannot change the array itself (since Java is pass-by-value), but it can use the array (with its reference semantics) to make changes to the components of the array.
There are three requirements for a class to be immutable.
There are two kinds of variables found in a class.
Data fields.
These have scope the entire class (except where hidden
,
see below) including prior to their declaration.
If one data field, say x, is initialized to an expression involving another field y, then y must be declared before x.
Variables (including parameters) declared in a method.
These are referred to as local variables
.
Naturally, the same variable name can be declared in multiple methods. As we have seen previously, the same name can be declared in disjoint blocks in the same method. In both these cases each instance of the name refers to a separate variable.
If a local variable has the same name as a data field, the field name is hidden (i.e., not directly accessible) in that method. For this reason as well as for increased clarity, it is not recommended (but is permitted) to use the same name for both a class variable and a non-parameter local variable.
We shall see next section that it is common to have parameters with the same name as fields.
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); } }
set methods, one for setting each of the two fields, and a main() method.
set methodsthe 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.
exposesthe hidden field name.
Class abstraction is the separation of how a class is used from how a class is defined.
Another name is information hiding
, the implementation of
the class is hidden from the users of the class.
How to use a given class is described by the class specification, also called is public interface. In Java classes, fields and methods declared public form the interface. In contrast, private fields and private methods are not part of the specification, but are part of the implementation.
When looked at from the user's point of view, the class encapsulates the knowledge needed to use these objects. In the next section we discuss an example.
public class CheckingAcct { private String acctName; private double balance = 0; CheckingAcct(String acctName) { } public void makeDeposit (double amt) { } public boolean makeWithdrawal (double amt) { } } public class TestChecking { public static void main (String [] Args) { CheckingAcct acct = new CheckingAcct( "Allan Gottlieb's checking acct"); acct.makeDeposit(100.00); acct.makeWithdrawal(30.00); } } Allan Gottlileb's checking account Description Amount Balance Deposit 100.00 100.00 Withdrawal 30.00 70.00
The book discuss a loan example, which you should read. We will do the beginnings of a checking account example.
On the right is the very beginning of a CheckingAccount class. Currently, we can open an account (in Java-speak we can create a CheckingAccount object), make deposits, and make withdrawals.
The bottom section shows the output produced.
This is not what a bank would use, but what customers would used for themselves. For example, we will not generate monthly statements, but might process a monthly statement received by the bank. Also banks do not permit withdrawals that drive the balance negative, but we will permit it since the user may know that certain previous debits have not cleared. Most significantly, we do not do any of the hard work involving actual money.
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.
You should read this section in the book.
We will continue with our checking account.
An obvious omission is that we can't handle checks themselves. Before taking on that task, let's try something that should be simpler.
Each transaction should have a date. Many times the date of the transaction is today, but sometimes you might enter the transaction days after it was performed (remember this is our personal account history, not the bank's).
public class TestChecking { public static void main (String[] Args) { CheckingAcct acct = new CheckingAcct( "Allan Gottlileb's checking account"); acct.makeDeposit(100.0, 7,15,2010); acct.makeWithdrawal(30.0); acct.makeDeposit(50.00); } } Allan Gottlileb's checking account Date Description Amount Balance ---------- ------------------------- ------- -------- 7/15/2010 Deposit 100.00 100.00 4/3/2012 Withdrawal 30.00 70.00 4/3/2012 Deposit 50.00 120.00 public void makeDeposit (double amt, int month, int day, int year) {...} import java.util.Date; private Date now = new Date(); private int nowMonth = now.getMonth(); private int nowDate = now.getDate(); private int nowYear = now.getYear()+1900;
Hence the public interface of CheckintAcct must be changed so that there are two makeDeposit() methods, one accepting a date and the other defaulting to today's date. Similarly, for makeWithdrawal().
On the right, the top frame is what we expect the client (user) of our class to write. Below that we see the output we wish to have.
What changes are needed to CheckingAcct.java?
abstract classand we don't yet know how to deal with those.
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", "----------","-------------------------","-------","--------"); } }
We already did stacks.
You should read this.
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.
Follow the Java naming and style conventions (see this short section for a brief review).
Most fields should be private to ease maintenance and modifiability, you can't easily change the meaning of a data field to which the users have access. You should provide get and set methods as appropriate. In general, only expose to the user (i.e., make public) items whose meaning you guarantee will not change.
Read.
Read. A good example is the String class. It is not likely that any one application would use all the methods, but it is good that they are there.
Declare data fields to be static if the value is constant for all objects in the class. Static fields should be set with a set method not via a constructor. For one thing, there may not be any objects created when the field is used (in a static method).
Some methods do not need any object of the class. For example, our friend, the main method, has this property. Such methods must be declared static as there is no object to which they can be attached.
Instance methods can reference both instance and class fields, as well as both instance and class methods.
Class methods can reference class methods and fields, but cannot invoke instance methods and fields (what object would the instance method or field refer to?).
Homework: 10.3
The idea of inheritance, which is fundamental in object-oriented programming, is that sometimes classes B and C are refinements of class A and it is silly to have to reproduce all the A methods for both B and C.
For example, you could have a class for quadrilaterals that would have as data fields the four vertices, the color to draw the figure in, and some complicated method for calculating the area.
Then you would have a subclass for a rhombus that would include, in addition, the the (unique) side length. You would also have a subclass for rectangles that would override the area method with a much simpler one as well as providing additional data fields for height and width.
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 } }
every quadrilateral is a point. The
is-arelationship is normally needed for a subclass to be appropriate. A quadrilateral is a geometric object.
every rhombus is a quadrilateral.
Start Lecture #20
Remark: Must discuss overriding (#7 above).
Remark: Briefly discuss lab 4 part 3.
A terminology comment. A subclass usually contains more information (data fields) than the superclass. It is called a subclass because each object in the subclass is also a member of the superclass. Thus the set of objects in the subclass can be considered a subset of the set of objects in the superclass.
We mentioned above that there is a complicated formula
for
the area of a quadrilateral.
Just for fun, not part of the course, I worked it out.
Assume the 4 points p1, p2, p3, and p4 are given in an order so that the resulting edges do not cross each other. I shall always assume the quadrilateral is convex as shown in the diagram on the right (but I suspect much of what follows would work for concave quadrilaterals as well). Draw one of the diagonals, say connecting p1 and p3.
It is easy to get the lengths of each side as the square root of delta-x squared + delta-y squared. So in the diagram we know all the sij.
Now the quadrilateral is composed of two triangles and we know the side lengths for each triangle. There is a cute formula for the area of any triangle with side lengths a, b, and c. Let s be the semiperimeter s=(a+b+c)/2, then the area of the triangle is the square root of s(s-a)(s-b)(s-c).
This analysis enables us to compute the complicated formula
for the quadrilateral area mention above.
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
When we create a new object, a constructor is called. Let's say that class Child is a child of the class Parent and we make a Child object child. The Child constructor is called and produces/initializes any relevant data fields in child.
But the Child object child contains, in addition to its own data, the data fields in Parent as well. How are these created/initialized?
The answer is that a constructor in the subclass invokes a constructor in the superclass. In our case, constructors in Child would invoke constructors in Parent.
How is this done?
One wrong way would be to have the child constructor invoke new Parent(). That would create a new Parent object rather than creating/initializing a part of the current Child object child.
Indeed it is an error to mention the superclass constructor inside a subclass; instead super() is used.
public class Rhombus extends Quadrilateral { private double sideLength; public Rhombus (Point p1, Point p2, Point p3, Point p4) { super(p1, p2, p3, p4); sideLength = p1.distTo(p2); } } public class Quadrilateral extends GeometricObject { Point p1, p2, p3, p4; public Quadrilateral (Point p1, Point p2, Point p3, Point P4) { this.p1 = p1; this.p2 = p2; this.p3 = p3; this.p4 = p4; } }
On the right we see a part of an improved class for Rhombus and below it part of the base class Quadrilateral. Although not explicitly included in the class, a derived class inherits the fields from its base. Thus a Rhombus object has 4 Point fields that must be created/initialized by the constructor (as well as the double field sideLength.
Were there no inheritance involved, the rhombus constructor would have 4 assignment statements using this to define/initialize the four points. However, the points are not declared in Rhombus so should not be referenced by this. Instead we invoke super() to have a constructor in the base class Quadrilateral do the necessary work.
The idea is that the constructors in a derived class deal with the fields introduced in the derived class; dealing with the fields inherited from the base class is left to the base class constructors, furthering abstraction/encapsulation.
When super() is used in a constructor, it must be the first statement.
You might wonder why these same considerations don't apply to Quadrilateral as they did to Rhombus. After all, Quadrilateral is itself a derived class (its parent is GeometricObject).
The answer is that they do apply; a default super() has been supplied by Java, as will be explain in the next section.
As we have seen a few lectures ago, one constructor can invoke another constructor in the same class. We have just seen that a constructor in a derived class can utilize super(). If a constructor in a derived class does neither of these actions, Java itself supplies a no-arg super() as the first statement of the constructor.
As a result of this automatic insertion of super() when needed, if A is derived from B, which in turn is derived from C, any A construct will, as its first action, invoke a B constructor, which, as its first action, will invoke an C constructor.
Thus the real actions will be performed in the order C, B, A, i.e., from base to derived. This naturally applies as well when there are more than 3 classes.
For example, consider the class tree on the right, which represents the parent-child relations in our geometry example.
The process of applying constructors from base to derived is called constructor chaining.
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
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.
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
public class Base { public void f(double x) { System.out.printf("Base says x=%f\n", x); } } public class Derived { public void f(double x) { System.out.printf("Derived says x=%f\n", x); } } public class Test { public static void main(String[] Args) { Derived d = new Derived(); d.f(5.0); d.f(5); } } Derived says x=5.000000 Derived says x=5.000000 public class Derived extends Base { public void f(int x) { System.out.printf("Derived says x=%d\n", x); } } Base says x=5.000000 Derived says x=5
It is important to understand the distinction between overriding, which we just learned, and overloading, which have seen previously.
The first frame on the right demonstrates overriding. Both the base class and the derived class define an instance method f(). Both f()'s have the same signature (i.e, the same number and types of parameters) and the same return type. In such a case, an object of the derived class sees only the method defined in the derived class.
In the example, both d.f(5.0) and d.f(5) invoke the f() in the child class. In the second case the 5 is coerced from int to double.
The second example is almost the same, but with a key difference. Both Base and Test are unchanged. The f() in Derived is changed so that it's parameter is of type int (and the printf() now uses %d not %f).
This tiny change has a large effect.
The f() in the derived class no longer has the same signature as the f() defined in the base. Thus, both are available (i.e., f() is overloaded).
For this reason the invocation f(5) invokes the f() in the derived class; whereas, the f(5.0) invokes the f() in the base.
public class GeometricObject { protected String color = "blue"; } public class Point extends GeometricObject { double x, y; public Point(double x, double y) { this.x = x; this.y = y; } double distTo (Point p) { return Math.sqrt(Math.pow(this.x-p.x,2) + Math.pow(this.y-p.y,2)); } } public class Quadrilateral extends GeometricObject { protected Point p1, p2, p3, p4; public Quadrilateral (Point p1,Point p2,Point p3,Point P4) { this.p1 = p1; this.p2 = p2; this.p3 = p3; this.p4 = p4; } double area() { return 123.4; // A stub, should use complicated formula } } public class Rhombus extends Quadrilateral { private double sideLength; public Rhombus (Point p1, Point p2, Point p3, Point p4) { super(p1, p2, p3, p4); sideLength = p1.distTo(p2); if (sideLength!=p2.distTo(p3) || sideLength!=p3.distTo(p4) || sideLength!=p4.distTo(p1)) { System.out.println("Error: Rhombus with unequal sides"); System.exit(0); // an exception would be better } } public double getSideLength() { return sideLength; } } public class Rectangle extends Quadrilateral { private double width, height; public Rectangle(Point p1, Point p2, Point p3, Point p4) { super(p1, p2, p3, p4); width = p1.distTo(p2); height = p2.distTo(p3); // should check really a rectangle (how?) } public Rectangle(Point p1, Point p3) { // Parallel to axes super(p1, new Point(p3.x,p1.y), p3, new Point(p1.x,p3.y)); width = Math.abs(p1.x-p3.x); height = Math.abs(p1.y-p3.y); // Nothing to check } public double getWidth() { return width; } public double getHeight() { return height; } public double area() { return width * height; } public void areaCheck() { System.out.printf("Rectangle says %f, but quad says %f\n", this.area(), super.area()); } } public class TestQuad { public static void main (String[] args) { Point origin = new Point(0.0,0.0); Point p1 = new Point(0.0,0.0); Point p2 = new Point(1.0,0.0); Point p3 = new Point(1.0,1.0); Point p4 = new Point(0.0,1.0); Point p5 = new Point(5.0,5.0); Quadrilateral quad = new Quadrilateral (p1,p2,p3,p4); Rectangle rect1 = new Rectangle (p1,p2,p3,p4); Rectangle rect2 = new Rectangle (p1,p3); Rectangle rect3 = new Rectangle (origin, new Point(1.,4.)); System.out.printf("rect1: width=%f, height=%f, color=%s\n", rect1.getWidth(), rect1.getHeight(), rect1.color); System.out.printf("rect2: width=%f, height=%f, color=%s\n", rect2.getWidth(), rect2.getHeight(), rect2.color); System.out.printf("rect3: width=%f, height=%f, color=%s\n", rect3.getWidth(), rect3.getHeight(), rect3.color); rect1.areaCheck(); Rhombus rhom1 = new Rhombus(p1,p2,p3,p4); System.out.printf("rhom1 has side length=%f\n", rhom1.getSideLength()); Rhombus rhomBad = new Rhombus(p1,p2,p3,p5); } } rect1: width=1.000000, height=1.000000, color=blue rect2: width=1.000000, height=1.000000, color=blue rect3: width=1.000000, height=4.000000, color=blue Rectangle says 1.000000, but quad says 123.400000 rhom1 has side length=1.000000
Let's look at a larger example. I have filled in some of the details for the geometric methods sketched above.
Recall the hierarchy of classes we have defined. It is illustrated on the right.
The Geometric Object sits at the top of the hierarchy. As we see on the right it is a trivial class with only a single data field, color.
Next we have the simple class Point. As is normally done in Cartesian geometry, a point is defined by two real numbers, the x and y coordinates. In our class these instance fields are doubles.
Several of the classes below will need to calculate the length of various lines, i.e., the distance between two points. Rather than define essentially the same method in Rhombus, Rectangle, etc., we define it here so that whenever we have two points, we can find the distance between them.
For us a quadrilateral is simply 4 points.
In this version of the code, I just gave a stub for the area of an arbitrary quadrilateral. The use of stubs during program development is fairly common. Later versions of the code have the full method.
A rhombus is a quadrilateral with all four sides equal. Since the lengths are equal a rhombus has a well define side length, which the class calculates and makes available via an accessor method. The constructor checks that the side lengths are in fact equal.
Note that, in this implementation the 4 vertices of a rhombus, are components of the underlying quadrilateral. Hence super() is used to give the points their final value.
A rectangle is a quadrilateral with each angle 90 degrees. The first constructor does not check this condition, a clear weakness.
The second constructor, can be used in the common case where the sides of the rectangle are parallel to the coordinate axes. In this case only a pair of points is needed; they for opposite vertices of the rectangle and the other two vertices are calculated from these two, which guarantees that do have a rectangle.
Rectangles have well defined widths and heights, which are calculated by the constructor and made available via accessors methods.
Also supplied is a debugging method areaCheck() that compares the area calculated by the Rectangle class, with the more general area calculation in the parent Quadrilateral class. At this stage of development, they do not match since Quadrilateral.area() is just a stub..
Testing class hierarchies is a slow process. You have to test each class separately and then need to worry about interactions between them.
One weakness (really a bug) in this code is that the coordinates of a Point are declared public and thus can be changed by the user. This could mean that the side length is wrong and that the rhombus isn't really a rhombus.
Another problem is that when we check to see if the sides of rhombus are of equal length, we check for exact equality, which is not a good idea for floating point numbers.
Note that, if the points in the quadrilateral are given in the wrong order, the quadrilateral's sides will intersect each other. A better program would check that the sides do not intersect. We will ignore this subtlety and always make sure to give the points in the right order.
Where in the class tree should we put a new class Circle?
Where in the class tree should we put a new class Square?
Where in the class tree should we put a new class Ellipse?
Start Lecture #21
Remark: Mention possible extra credit to learn
Java graphics and then extend our
Quadrilateral and Friends
.
You would think from looking at a class definition beginning
public class TopLevel {
with no extends clause that this class has no parent. That, however, is wrong.
Any class without an extends clause, whether defined in the Java library or by you and me, actually extends the built in class java.lang.Object.
Hence, any method defined in Object is available to all classes, unless it is overloaded or overridden.
One interesting instance method defined in Object is
toString(), which when applied to an object, produces
a string describing the object.
The actual description given is the class name, followed by @
followed by the memory address of the object.
The class name is certainly useful, the memory address at least
enables us to distinguish one object from another.
One advantage of having a toString() method defined for all objects is that other methods can count on its existence. For example the various print methods (e.g., printf(), println(), etc) use this to coerce objects to strings. Thus if obj is any object at all,
printf("The object is %s\n", obj);
is guaranteed to work.
I use the automatic application of toString() in the next section and in the 2nd version of the geometry classes below.
We have already seen encapsulation/abstraction and inheritance, which are major constituents of object oriented programming. We now turn our attention to polymorphism (and dynamic binding/dispatch), the third major constituent).
public static void printColor(GeometricObject geoObj) { System.out.printf("The color of %s is %s\n", geoObj, geoObj.getColor()); } }
Say we are given the geometry classes and want to print the color. We don't want to add this print to the geometry classes since it is useful only for us and not to all geometry users. We do modify GeometricObject to add a get method for the color and then write ourselves the simple method on the right. Now if we call the method with any GeometricObject all is well.
But we actually don't have variables of type GeometricObject; instead we have variables of various subtypes such as Rhombus or Point. Thus the natural call PrintColor(rhom1) would be a type error, we are passing a Rhombus as the argument and the method has a GeometricObject as its parameter.
But no it is not an error!
A variable of a supertype
(the type defined by a superclass)
can refer to an object of any of its subtypes
).
This concept of polymorphism can also be stated
in terms of objects as
an object of a subclass can be used anywhere that an object of its
superclass could be used
.
In section 11.B we see an enhanced version of the geometry example that includes printColor() and a few polymorphic calls.
How should we implement printArea() in our geometry classes?
It would be a one-line method in the Rectangle class, namely
void printArea() { System.out.printf("The area of %s is %f\n", this, this.area()); }
(Recall that the Rectangle class already has the method area() and toString(), inherited from Object, is invoked automatically by the %s in printf().
But the exact same (character for character) one-line method printArea() would be perfect in the class Rhombus since Rhombus inherits area() from Quadrilateral and toString() from Object.
Similarly, the exact same method would work for Quadrilateral.
Once we put a trivial area() method in Point (it always returns 0), the exact same printArea() method would work there as well.
So is that the solution, cut-and-paste the identical method in all these classes? It would work, but it sure seems ugly.
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.
Let's say that we don't want to add printArea() to any of the geometry classes since it is somewhat special purpose. Our clients will probably have different ways of printing. So we write the following one-line method in the TestQuad class.
static void myPrintArea(GeometricObject geoObj) { System.out.printf("My area of %s is %f\n", geoObj, geoObj.area()); }
Then, in the main() method, we write myPrintArea(rhom1).
At first glance this looks unlikely to compile since we have a type mismatch. The argument to myPrintArea() is a Rhombus, but the parameter is a GeometricObject. However, we know that polymorphism will save us. The parameter of type GeometricObject can legally point to an object of type Rhombus.
At second glance, it looks like it will work, but poorly. When compiling myPrintArea(), Java will see geoObj.area() where geoObj is of type GeometricObject and will thus always invoke the area() method in the class GeometricObject no matter what class the actual argument is in. This is bad, we have different formulas for the area of points, general quadrilaterals, and rectangles.
Failure.
Wrong!
It actually works great.
We now have to understand how.
As we know well, Java requires that, prior to using a variable, we must declare it to be of a specific type (int, Object, Point, etc).
Previously, we have called the type in the declaration, simply the type of the variable. But now, in the presence of objects and polymorphism, we need to make a finer distinction and refer to the type in the declaration as the declared type of the variable.
When the declared type is a primitive such as int or char, there is very little more to say. The variable will always contain a value of its (declared) type. Recall that a double x=3; first converts the 3 to a double, which is then stored in x. The variable always contains a value of its (declared) type.
When the declared type is class, (e.g., String, or Rectangle), the situation is more interesting. First, we remember that a variable of declared type Quadrilateral never contains a Quadrilateral. It often does contain a reference to a Quadrilateral, but never contains the object itself (reference vs. value semantics).
Moreover, a variable of declared type Quadrilateral can, due to polymorphism, at times contain a reference to a Quadrilateral, and at other times a reference to a Rectangle, and at other times a reference to a Rhombus.
The type of the object to which a variable refers is called its actual type. Note that the actual type of a variable can change during execution of the program.
Recall the situation.
We have a method myPrintArea() that has one
parameter geoObj, whose declared type
is GeometricObject.
We call this method in several places with arguments of various
geometric types.
myPrintArea() contains a call of geoObject.area().
We previously concluded that, since the type
of geoObject
is GeometricObject, the call would
always be to the area() method in GeometricObject.
We are wiser now and can question this conclusion. If myPrintArea() was called with an argument that referenced a Rectangle, then geoObj will indeed have declared type GeometricObject, but it will have actual type Rectangle. Thus the question becomes, does the method invocation geoObj.area() select the area() method to call based on the declared type of geoObj or on its actual type. Another wording is does geoObj.area() use static dispatch (declared type) or dynamic dispatch (actual type).
The answer in Java is that is uses dynamic dispatch, and hence our second implementation does indeed work. This situation is illustrated in section 11.B.
In C++ static dispatch is used by default but
dynamic dispatch can be chosen by declaring the method to be
virtual
.
Recall the situation for converting one primitive type to another. Java performs safe type conversions (called coercions) automatically, but will not perform unsafe conversions, unless told told by an explicit cast.
int i1=1, i2=2, i3=3; double d1=1., d2=2., d3=3.E50; d1 = i1; d1 = (double)i1; // i2 = d2; Compile-time error i2 = (int)d2; // Fine i3 = (int)d3; // Gives wrong answer
Referring to the code on the right we see illustrations of the various conversion possibilities
We now want to understand the corresponding actions for objects in the class hierarchy. In this situation the terminology is upcasting and downcasting.
The familiar diagram on the right motivates the names.
Upcasting refers to a conversion from a class lower in the diagram to a class higher in the diagram (really there is the class Object above GeometricObject).
Downcasting refers to a conversion from a class higher in the diagram to one lower in the diagram.
The code below is the rough analogue for objects of the code above for primitive types. Note that the variable p2 after initialization has declared type Parent, but actual type Child. This situation is not present with primitive types since those variables always contain a value of the declared type of the value. In contrast variables of any object type contain a reference to an object whose type can be a subtype of the declared type of the variable.
public class Parent {...} public class Child extends Parent {...} Parent p1 = new Parent(); Parent p2 = new Child(); // NOT Parent() Parent p3 = new Parent(); Child c1 = new Child(); Child c2 = new Child(); Child c3 = new Child(); p1 = c1; p1 = (Parent) c1; // c2 = p2; Compile-time error c2 = (Child)p2; // Fine c3 = (Child)p3; // Run-time error
Referring to the code we see
The general rules are (compare with primitive types above):
We have just seen that downcasting with an explicit cast will compile but might fail at run-time depending on the actual type of the variable at that time.
What should we do to avoid this run-time error?
public class Parent {...} public class Child extends Parent {...} Parent p; Child c; // much code involving p if (p instanceof Child) c = (Child) p;
The code on the right gives one technique. The omitted code could be complicated and, depending on various values that are known only at run time, might result in the actual type of p being either parent or child.
We wish to do the downcast only if legal, i.e., only if the actual type of p is Child (or a descendant of Child). The instanceof operator is defined for exactly this purpose it returns true if the object on the left is in the class on the right (or a descendant of that class).
Why would I want to upcast and why would I ever want to try to downcast to something not matching the actual type of the variable.
Consider some sort of container class, i.e., a class each of whose instance is some container of GeometricObject's. For some examples consider
Each of these three examples illustrates a container for Object's. Let's say in some geometry program you have the array geoObjArr onto which you have placed various geometric objects, some points, some rhombuses, etc.
If you try something like geoObjArr[i].sideLength, you will get a compile-time error since geoObjArr[i] has declared type GeometricObject, which has no sideLength field.
A naked assignment to a variable of declared type Rhombus will not compile, so you need an explicit cast to do the downconvertion. But this will give a run-time error if the actual type of geoObjArr[i] is not a Rhombus (or a descendant).
Thus you need an if statement with instanceof as shown above.
Start Lecture #22
public class GeometricObject { private String color = "blue"; public String getColor() { return color; } public void setColor(String color) { this.color = color; } public void printArea() { System.out.printf("The area of %s is %f\n", this, this.area()); } public double area() { System.out.printf("Error area not overridden in %s\n", this); return 0; } }
A compilable and less cramped version of this code is here. It contains some features that we have not yet learned.
gettermethod) supplies the desired read-only access.
mutators(and apparently also called
setters.
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 class Quadrilateral extends GeometricObject { protected Point p1, p2, p3, p4; public Quadrilateral (Point p1, Point p2, Point p3, Point p4) { // should check pts form convex quadrillateral in this order this.p1 = p1; this.p2 = p2; this.p3 = p3; this.p4 = p4; } public double area() { // use triangle semiperimeter formula double diag = p1.distTo(p3); double s12 = p1.distTo(p2); double s23 = p2.distTo(p3); double s34 = p3.distTo(p4); double s41 = p4.distTo(p1); double semi1 = (s12 + s23 + diag) / 2; double semi2 = (diag + s34 + s41) / 2; return Math.sqrt(semi1*(semi1-s12)*(semi1-s23)*(semi1-diag))+ Math.sqrt(semi2*(semi2-s34)*(semi2-s41)*(semi2-diag)); } }
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 } public Rectangle(Point p1, Point p3) { // parallel to axes super(p1, new Point(p3.x,p1.y), p3, new Point(p1.x,p3.y)); width = Math.abs(p1.x-p3.x); height = Math.abs(p1.y-p3.y); // Nothing to check } public double getWidth() { return width; } public double getHeight() { return height; } public double area() { return width * height; } }
The summary is that there is a great deal to test, much more than is shown. As mentioned in class, testing / correcting / maintaining a serious software project typically requires much more time and effort than designing and programming the system
The code on the right is inadequate for full testing, but does illustrate some of the Java features we just learned.
public class TestQuad { public static void main (String[] args) { Point origin = new Point(0.0,0.0); Point p1 = new Point(1.0,0.0); Point p2 = new Point(1.0,1.0); Point p3 = new Point(0.0,1.0); Point p4 = new Point(5.0,5.0); Quadrilateral quad1 = new Quadrilateral (origin, p3, new Point(5.,0.), new Point(5.,-8.)); Rectangle rect1 = new Rectangle (origin,p1,p2,p3); Rectangle rect2 = new Rectangle (origin, new Point(1.0,4.0)); Rhombus rhom1 = new Rhombus(origin,p1,p2,p3); p1.printArea(); printColor(rhom1); // Polymorphic call (section 11.7) printColor(rect1); // Polymorphic call quad1.printArea(); // Inherited method (section try 1) rect2.printArea(); // Inherited method myPrintArea(rhom1); // Dynamic binding/dispatch (sec try 2) myPrintArea(p4); // Dynamic binding/dispatch quad1 = rect1; // Implicit casting (section 11.9) quad1.printArea(); // Dynamic binding/dispatch } public static void printColor(GeometricObject geoObj) { System.out.printf("The color of %s is %s\n", geoObj, geoObj.getColor()); } public static void myPrintArea(GeometricObject geoObj) { System.out.printf("My area of %s is %f\n", geoObj, geoObj.area()); } } // Output The area of Point@4830c221 is 0.000000 The color of Rhombus@2ce83912 is blue The color of Rectangle@41fae3c6 is blue The area of Quadrilateral@3e7ffe01 is 22.500000 The area of Rectangle@44fd13b5 is 4.000000 My area of Rhombus@2ce83912 is 1.000000 My area of Point@4318f375 is 0.000000 The area of Rectangle@41fae3c6 is 1.000000
On the board let's develop a Circle class with data fields center and radius.
normalconstructor is given the center point and the radius.
Homework: 11.1. UML diagram not required. The area formula they references is the same one I used in the Quadrilateral class. The filled data field is in the book's GeometricObject but not in ours. You may ignore it.
Homework: Use cut and paste to extract our geometry classes from the notes. Better is to get the compilable and neater version linked at the beginning of section 11.B. Add your Triangle class from the homework above, and the Circle class we developed to these files and incorporate your tests of Triangle and Circle into our TestQuad (which should be named TestGeom).
public boolean equals (Object obj) { return (this == obj); } String s1= new String("xy"); String s2= new String("xy"); System.out.printf("%b %b\n", s1==s2, s1.equals(s2)); public boolean equals(Object obj) { if (obj instanceof Circle) return radius == ((Circle)obj).radius; return false; }
In addition to toString(), which we learned earlier, the Object class has an instance method equals(), shown on the right.
Note carefully that obj1.equals(obj2) checks if the
variables are ==, which means that both variable refer to
the same object.
Often you want equals() to return true if the
two objects have the same contents.
For example the String class
overrides equals() so that the middle code on the right
prints
false true
.
If the bottom code is placed in the Circle class then, Circle c1,c2; with equal radii will result in ci.equals(c2) yielding true.
You should do that if you try the extra credit.
class GeometricObject { // Fields and methods omitted } class Circle extends GeometricObject { double radius; public Circle (double radius) { this.radius = radius; } public boolean equals (Circle c) { return radius == c.radius; } // Other fields and methods omitted } public class TestOverload { public static void main(String[] args) { GeometricObject g1 = new Circle(11.0); GeometricObject g2 = new Circle(11.0); Circle c1 = new Circle(11.0); Circle c2 = new Circle(11.0); System.out.printf("c1.equals(c2)=%b\n", c1.equals(c2)); System.out.printf("c1.equals(g2)=%b\n", c1.equals(g2)); System.out.printf("g1.equals(c2)=%b\n", g1.equals(c2)); System.out.printf("g1.equals(g2)=%b\n", g1.equals(g2)); } } javac TestOverload.java && java TestOverload c1.equals(c2)=true c1.equals(g2)=false g1.equals(c2)=false g1.equals(g2)=false class GeometricObject { // Fields and methods omitted } class Circle extends GeometricObject { double radius; public Circle (double radius) { this.radius = radius; } public boolean equals (Object o) { if (o instanceof Circle) return radius == ((Circle) o).radius; return false; } // Other fields and methods omitted } public class TestOverride { public static void main(String[] args) { GeometricObject g1 = new Circle(11.0); GeometricObject g2 = new Circle(11.0); Circle c1 = new Circle(11.0); Circle c2 = new Circle(11.0); System.out.printf("c1.equals(c2)=%b\n", c1.equals(c2)); System.out.printf("c1.equals(g2)=%b\n", c1.equals(g2)); System.out.printf("g1.equals(c2)=%b\n", g1.equals(c2)); System.out.printf("g1.equals(g2)=%b\n", g1.equals(g2)); } } javac TestOverride.java && java TestOverride c1.equals(c2)=true c1.equals(g2)=true g1.equals(c2)=true g1.equals(g2)=true
The equals() code on the right, looks simpler that the one above and seems to do the same job: When two circles are compared, their radii are checked.
The difference is that the equals() in 11.10 overrides the equals() defined in Object since both have the same signature. In contrast the equals() on the right has a different signature and thus we have two overloaded implementation of equals().
In the overloaded case, the choice of which equals() to invoke depends on the declared type of the argument. In the overriding case the choice depends on the actual type of the argument.
Look at the main() procedure. Four variables are declared, two GeometricObjects and two Circles. Each of the variables is initialized to a Circle with radius 11.0.
Since each variable refers to a different object, they would all yield false if compared using ==.
However, we are comparing them using the equals() method.
The question is which equals() method, the one in Object, which requires that the same object is referenced, or the one in Circle, which requires only that the radii are equal.
Since these two methods are overloading (not overriding, it is the declared types of the variables that are relevant and only the first invocation c1.equals(c2) uses the equals defined in circle.
If we change the definition of equals() in Circle to the one in 11.10, the situation changes dramatically. For clarity, we also changed the class name to TestOverride
Now the two equals definitions have the same signature so the one in Circle overrides the one in GeometricObject.
As a result the choice of method implementation defends on the actual types (of the referenced objects), not on the declared types (of the referencing variables).
Since all four variables g1, g2, c1, c2 references objects with actual type Circle, each of the four invocations, executes the equals in Circle. Thus only the radii are compared and the comparison yields true in all four cases.
Start Lecture #23
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.
Shows how to use the ArrayList class to design a stack class that holds arbitrary objects.
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)
We have seen the final keyword use for constants. It has other analogous uses as well.
May be helpful for the extra credit project
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.
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.
matching(explained later) catch block is executed.
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?
The answer to the above question is that using exceptions in the manner of the previous section to more-or-less exactly mirror the actions without an exception is not a good idea.
Remember the problem with the original solution. The author of the geometry package does not know what the author of the client wants to do when an error occurs (try to forget that I am the author of both). Without any such knowledge the package author terminated the run as there was no better way to let the client know that a problem occurred.
The better idea is for the package to detect the error, but let the client decide what to do.
public Rhombus (Point p, double sideLength, double theta) { super (p, new Point(p.x+sideLength*Math.cos(theta), p.y+sideLength*Math.sin(theta)), new Point(p.x+sideLength*(Math.cos(theta)+1.), p.y+sideLength*Math.sin(theta)), new Point(p.x+sideLength, p.y)); this.sideLength = sideLength; } double s12 = p1.distTo(p2); if (s12!=p2.distTo(p3) || s12!=p3.distTo(p4) || s12!=p4.distTo(p1)) throw new Exception ("Rhombus with unequal sides."); sideLength = s12; try { rhom1 = new Rhombus(origin,origin,p2,p3); } catch (Exception ex) { System.out.printf("rhom1 error: %s Use unit square.\n", ex.getMessage()); rhom1 = new Rhombus (origin, 1.0, Math.PI/2.0); }
The first step is to add a rhombus constructor having parameters a point, a side-length, and an angle and producing the rhombus shown in the diagram on the right (if Θ=Π/2, the rhombus is a square).
The constructor itself is in the 2nd frame.
This enhancement has nothing to do with exceptions and could have
been there all along.
You will see below how this standard rhombus
is used when the
client mistakenly attempts to construct an invalid rhombus.
The next step, shown in the 3rd frame, is to have the regular rhombus constructor throw an exception, but not catch it.
The client code is in the last frame. We see here the try and catch. In this frame the client uses the original 4-point rhombus constructor, but the points chosen do not form a rhombus. The constructor detects the error and raises (throws in Java-speak) an exception. Since the constructor does not catch this exception, Java automatically re-raises it in the caller, namely the client code, where it is finally caught. This particular client chooses to fall back to a unit square.
It is this automatic call-back that exceptions provide that the original code does not.
Remark: Lab 5 Assigned; due 1 May 2012
Remark: I think the write-up for the extra credit is finished. Let me know if you find that something is missing.
There is not much difference between versions 2 and 3. A compilable, less cramped implementation is here.
public abstract class GeometricObject { // Will learn about abstract protected String color = "blue"; public void printArea() { System.out.printf("The area of %s is %f\n", this, this.area()); } public String getColor() { return color; } public void setColor(String color) { this.color = color; } public abstract double area(); // Must be overridden } } public class Point extends GeometricObject { protected double x, y; public Point(double x, double y) { this.x = x; this.y = y; } public double distTo (Point p) { return Math.sqrt(Math.pow(this.x-p.x,2)+Math.pow(this.y-p.y,2)); } public double area() { return 0; } public double getX() { return this.x; } public double getY() { return this.y; } } public class Quadrilateral extends GeometricObject { protected Point p1, p2, p3, p4; public Quadrilateral (Point p1, Point p2, Point p3, Point p4) { // should check these points for a convex quadrillateral in this order this.p1 = p1; this.p2 = p2; this.p3 = p3; this.p4 = p4; } public double area() { // diagonal gives 2 triangles; use semiperimeter formula double diag = p1.distTo(p3); double s12 = p1.distTo(p2); double s23 = p2.distTo(p3); double s34 = p3.distTo(p4); double s41 = p4.distTo(p1); double semi1 = (s12 + s23 + diag) / 2; double semi2 = (diag + s34 + s41) / 2; return Math.sqrt(semi1 * (semi1-s12) * (semi1-s23) * (semi1-diag)) + Math.sqrt(semi2 * (semi2-s34) * (semi2-s41) * (semi2-diag)); } } public class Rhombus extends Quadrilateral { private double sideLength; public Rhombus (Point p1, Point p2, Point p3, Point p4) throws Exception { super(p1, p2, p3, p4); double s12 = p1.distTo(p2); if (s12!=p2.distTo(p3) || s12!=p3.distTo(p4) || s12!=p4.distTo(p1)) throw new Exception ("Rhombus with unequal sides."); sideLength = s12; } public Rhombus (Point p, double sideLength, double theta) { super (p, new Point(p.x+sideLength*Math.cos(theta), p.y+sideLength*Math.sin(theta)), new Point(p.x+sideLength*(Math.cos(theta)+1.), p.y+sideLength*Math.sin(theta)), new Point(p.x+sideLength, p.y)); this.sideLength = sideLength; } public double getSideLength() { return sideLength; } } public class Rectangle extends Quadrilateral { private double width, height; public Rectangle(Point p1, Point p2, Point p3, Point p4) { super(p1, p2, p3, p4); width = p1.distTo(p2); height = p2.distTo(p3); // should check that all angles are right angles via slopes neg recip // or check both widths equal, both heights equal, both diags equal } public Rectangle(Point p1, Point p3) { // Sides parallel to the axes super(p1, new Point(p3.x,p1.y), p3, new Point(p1.x,p3.y)); width = Math.abs(p1.x-p3.x); height = Math.abs(p1.y-p3.y); // Nothing to check } public double getWidth() { return width; } public double getHeight() { return height; } public double area() { return width * height; } } public class Circle extends GeometricObject { private Point center; private double radius; private static double maxRadius = -1; private static Circle maxCircle = null; public Circle(Point center, double radius) throws Exception { if (radius < 0) throw new Exception ("Circle with negative radius."); circleNoException(center, radius); } private void circleNoException(Point center, double radius) { this.center = center; this.radius = radius; this.color = "pink"; if (radius > maxRadius) { maxRadius = radius; maxCircle = this; } } public Circle(double radius) throws Exception{ this(new Point(0.,0.), radius); } public Circle(Point center) { // No exception possible circleNoException(center, 1.0); } public Circle() { // No exception possible circleNoException(new Point(0.,0.), 1.0); } public Point getCenter() { return this.center; } public double getRadius() { return this.radius; } public double area () { return Math.PI * radius * radius; } public static Circle largestCircle() { if (maxRadius < 0) System.out.println("No circles created, so maximum circle is null"); return maxCircle; } public boolean equals(Object obj) { if (obj instanceof Circle) return radius == ((Circle)obj).radius; // Only radius counts return false; } } public class TestQuad { public static void main (String[] args) throws Exception { Point origin = new Point(0.0,0.0); Point p1 = new Point(1.0,0.0); Point p2 = new Point(1.0,1.0); Point p3 = new Point(0.0,1.0); Point p4 = new Point(5.0,5.0); Quadrilateral quad1 = new Quadrilateral(origin, p3, new Point(5.,0.), new Point(5.,-8.)); Rectangle rect1 = new Rectangle (origin,p1,p2,p3); Rectangle rect2 = new Rectangle (origin, new Point(1.0,4.0)); Rhombus rhom1; try { rhom1 = new Rhombus(origin,origin,p2,p3); } catch (Exception ex) { System.out.printf("rhom1 error: %s Using unit square.\n", ex.getMessage()); rhom1 = new Rhombus (origin, 1.0, Math.PI/2.0); } p1.printArea(); printColor(rhom1); // Polymorphic call (section 11.7) printColor(rect1); // Polymorphic call quad1.printArea(); // Inherited method (section first try) rect2.printArea(); // Inherited method myPrintArea(rhom1); // Dynamic binding/dispatch (section second try) myPrintArea(p4); // Dynamic binding/dispatch quad1 = rect1; // implicit casting (section 11.9) quad1.printArea(); // dynamic binding/dispatch Circle c1 = new Circle(); Circle c2 = new Circle(2.); Circle c3 = new Circle(); System.out.println(c3.equals(c1)); Circle c = Circle.largestCircle(); c.printArea(); System.out.println(c.getColor()); GeometricObject geo1 = new Rectangle(origin, p2); GeometricObject geo2 = new Rhombus(origin, new Point(3,4), new Point(8,4), new Point(5,0)); System.out.println(geo1.area()); } public static void printColor(GeometricObject geoObj) { System.out.printf("The color of %s is %s\n", geoObj.toString(), geoObj.getColor()); } public static void myPrintArea(GeometricObject geoObj) { System.out.printf("My area of %s is %f\n", geoObj.toString(), geoObj.area()); } } rhom1 error: Rhombus with unequal sides. Using unit square. The area of Point@40a0dcd9 is 0.000000 The color of Rhombus@2ce83912 is blue The color of Rectangle@41fae3c6 is blue The area of Quadrilateral@3e7ffe01 is 22.500000 The area of Rectangle@44fd13b5 is 4.000000 My area of Rhombus@2ce83912 is 1.000000 My area of Point@4318f375 is 0.000000 The area of Rectangle@41fae3c6 is 1.000000 true The area of Circle@2e471e30 is 12.566371 pink 1.0
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.
The diagram on the right shows the class tree for exceptions.
As always, Object is the root of the tree and naturally has many children in addition to Throwable, which is shown. As the name suggests, the Throwable class includes objects that can be thrown, which are exceptions.
From our simplified point of view, there are three important classes of pre-defined exceptions, namely Error, Exception, and Runtime Exception. They are highlighted in the diagram.
Java divides exceptions into two groups: checked exceptions and unchecked exceptions. Referring to the diagram above, red exceptions are unchecked, blue exceptions are checked.
More precisely, I write exception for the concept and Exception for the class of exceptions shown in blue. Then exceptions in either the Error or RuntimeException are unchecked. Those in the Exception class but not in the RuntimeException class are checked.
The difference for us between checked and unchecked exceptions is that any method that might throw a checked exception must declare this fact in its header line. For example consider the following constructor from the Rhombus class
public Rhombus (Point p1, Point p2, Point p3, Point p4) throws Exception { super(p1, p2, p3, p4); double s12 = p1.distTo(p2); if (s12!=p2.distTo(p3) || s12!=p3.distTo(p4) || s12!=p4.distTo(p1)) throw new Exception ("Rhombus with unequal sides."); sideLength = s12; }
Please note two important points.
The purpose of this declaration is to alert users (clients) of the method that the exception may be raised while executing the client code since the throw is not caught internally and thus would propagate to the caller.
For example, the main() method in version 3 above, has header line
public static void main (String[] args) throws Exception
Note that by examining only the code in main(), you will find no statement that directly throws an exception.
The point is that main() calls the rhombus() constructor, which throws and does not catch an exception. Thus if the exception is in fact thrown by rhombus(), Java will automatically re-throw it in main().
An important point of this declaration is that if you read a method header line and do not see throws, then you can be sure that calling that method will not result in a checked exception being thrown by your call.
Throwing an exception is easy; just throw it. But first you need to create an object in some subclass of throwable (i.e., a member of Error, Exception, or a descendant of one of these).
public class Test { public static void main(String[]Args) throws Exception { throw new Exception; throw new BufferOverflowException; } } public class MyException extends Exception {} public class Test { static MyException myE = new MyException(); public static void main(String[]args) throws MyException { throw myE; } }
The first frame shows two examples of throwing predefined exceptions. Since BufferOverflowException is a subclass of RuntimeException (a red box), it does not appears in the throws clause.
The second frame shows an example of creating a custom exception class and throwing an instance of this class. If MyException extended a red box rather than the blue one, the throws in the method header would not be needed.
Catching an exception is a two step procedure involving try and catch blocks.
It is an compile time error for a statement not in a try block to throw a checked exception.
If an unchecked exception occurs in a statement not within a try block, the exception is not caught by this method. Instead, the current method ends and the same unchecked exception is raised in the statement that called the method. If the current method is the main method, the program is terminated.
try { statements; } catch (Exception1 ex1) { handler for exception1; } catch (Exception2 ex2) { handler for exception2; } ... catch (ExceptionN exN) { handler for exceptionN; } statements after the catches
To catch an exception you must first delimit a sequence of statements with a try block, as shown on the right.
If no exception occurs within the try block, the corresponding catch blocks are ignored.
If an exception does occur within the try block, the corresponding catch blocks are searched to see if one catches this class of exception. If such a block is found, its handler is executed followed by the statements after the catches.
If no catch block matches, the exception is NOT caught and once again, the current method is ended and the exception is re-raised in the statement that called the method.
Start Lecture #24
The order of the catch blocks is significant as they are checked for matching in the order they are written.
Note that if a catch block is declared to handle exceptions in a class C, it also catches exceptions in any descendant class of C.
As a result, if a later catch block handles a subclass of a prior cache block, the later catch block can never be executed. Indeed, it is compile-time error to list cache blocks in this nonsensical order
Draw on the board a deeper example with f() calling g() calling h(), handlers in various places, and h() raising various 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).
You should read the book's example. Here is an example from our geometry example.
public class Rhombus extends Quadrilateral { private double sideLength; public Rhombus (Point p1, Point p2, Point p3, Point p4) throws Exception { super(p1, p2, p3, p4); double s12 = p1.distTo(p2); if (s12!=p2.distTo(p3) || s12!=p3.distTo(p4) || s12!=p4.distTo(p1)) throw new Exception ("Rhombus with unequal sides."); sideLength = s12; } public Rhombus (Point p, double sideLength, double theta) { super (p, new Point(p.x+sideLength*Math.cos(theta), p.y+sideLength*Math.sin(theta)), new Point(p.x+sideLength*(Math.cos(theta)+1.), p.y+sideLength*Math.sin(theta)), new Point(p.x+sideLength, p.y)); this.sideLength = sideLength; } public double getSideLength() { return sideLength; } } public class TestQuad { public static void main (String[] args) { Point origin = new Point(0.0,0.0); Point p1 = new Point(1.0,0.0); Point p2 = new Point(1.0,1.0); Point p3 = new Point(0.0,1.0); Point p4 = new Point(5.0,5.0); Rhombus rhom1; try { rhom1 = new Rhombus(origin,origin,p2,p3); } catch (Exception ex) { System.out.printf("rhom1 error: %s Using unit square.\n", ex.getMessage()); rhom1 = new Rhombus (origin, 1.0, Math.PI/2.0); } } } rhom1 error: Rhombus with unequal sides. Using unit square.
The constructor call inside the try is clearly not a rhombus since one side has length zero and the other three are not zero. Hence the constructor throws (but does not catch) a new Exception.
The main method executes the constructor inside a try block so, if the exception is thrown, the catch block is checked and sure enough the only catch there matches. Hence ex becomes the new Exception.
The handler first prints the message included in the throw (via ex.getMessage() and then announces that it will use a unit square instead. The unit square is obtained by invoking the other rhombus constructor that takes the side length and angle. This handler cannot raise an exception.
Since the exception thrown by the first invocation of the Rhombus() constructor, is caught and the catch block cannot raise an exception, main() cannot raise an exception and hence its header line does not mention exceptions.
The full example (version 3) of main() constructs several objects. Some of the constructions can raise exceptions that main does not catch. Hence its header line does announce that an exception can be thrown.
Homework: 13.1.
Homework: Do 13.3 two ways.
Out of Bounds.
In addition to throw and catch blocks there is occasionally a finally block that gets executed in any case. Consider the example on the right (there can be many catch blocks).
try { try block } catch (something) { catch block } catch (somethingElse) { finally { finally block } the rest
As mentioned above, it is often silly to use an exception as a complicated if statement. Exceptions are useful when the throw and catch are in separate routines as this usage takes advantage of the automatic up-call that exceptions provide and simple code does not.
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.
Another possibility is that a catch block for one exception can throw a different exception.
Most of the exceptions raised either have no argument or have a string as an argument. The exception in the Rhombus class has a string argument.
However, you can define your own subclass of the Exception class (say MyException) and have a MyException constructor take whatever arguments you want.
public class MyException extends Exception { MyException(int code, String str) { super(String.format("Exception raised: (code=%d) %s\n"), code, str); } }
The example on the right takes an integer code in addition to the usual string. It uses String.format() to combine the two arguments into a single string that is then passed to the Exception() constructor.
The program on the right copies one file to another. Each file is specified as a command line argument. That is to run the program one would type.
java CopyFile inputFileName outputFileName
The program uses exceptions to detect improper input. Several points are worth noting.
// Need File(Reader/Writer) (IO/FileNotFound)Exception import java.io.*; public class CopyFile { public static void main(String[]Args) throws IOException { FileReader getInput = null; FileWriter putOutput = null; try { getInput = new FileReader(Args[0]); } catch (FileNotFoundException fileNotFoundEx) { System.out.printf("Can not read %s\n", Args[0]); System.exit(0); } try { putOutput = new FileWriter(Args[1]); } catch (IOException IOEx) { System.out.printf("Can not write %s\n", Args[1]); System.exit(0); } // If an IOException occurs here, we can't help int c; while (getInput.ready()) { // false at EOF c = getInput.read(); putOutput.write(c); } putOutput.flush(); } }
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.
public class GeometricObject { protected String color = "blue"; public void printArea() { System.out.printf("The area of %s is %f\n", this, this.area()); } public String getColor() { return color; } public double area() { System.out.printf("Error: area not overridden in %s\n", this); return 0; } }
Consider the area() methods sprinkled throughout our geometry example. In particular, consider the area() method defined in the GeometricObject class. The code from version 2 of geometry is shown to the right.
The method certainly does not compute the area and the important point is that there is no way it can compute the area of a GeometricObject itself since the only field guaranteed to exist is the color.
The reason for including area() here was twofold.
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.
public abstract class GeometricObject { protected String color = "blue"; public void printArea() { System.out.printf("The area of %s is %f\n", this, this.area()); } public String getColor() { return color; } public abstract double area(); // must be overridden }
On the right we see the version 3 reimplementation of GeometricObject as an abstract class, with area() an abstract method. Note that this method has no body and cannot be called.
The compiler insures that this abstract method is overridden in the subclasses of GeometricObject.
Since this class contains an abstract method, the class must itself be declared abstract, as we have done on the first line.
You can declare a variable of type GeometricObject, but you cannot construct an object of the class. Thus a variable of declared type GeometricObject can never have actual type GeometricObject. Instead, the object referred to by the variable must be a member of a subclass of GeometricObject. Specifically a GeometricObject variable can contain a reference to a Rectangle, a Point, a Circle, etc. In all these cases the abstract area() method in GeometricObject is overridden and becomes a real (i.e., non-abstract) method.
If all methods in a class are abstract and all data fields are constants (static final in Java), the class is called an interface.
Imagine that you want to write a class MyClass that extends two base classes ClassA and ClassB. That is, a MyClass object has all the data fields of both ClassA and ClassB (plus any others you add to MyClass). Also MyClass starts with all the methods of the two base classes (minus any that are overridden in MyClass, plus any new methods added in MyClass).
public class MyClass extends ClassA, ClassB { statements; }
You would try to write the class as shown on the right.
This is called multiple inheritance
since MyClass
inherits from multiple base classes.
However, Java does not support multiple inheritance
(although some languages, notably C++, do support it).
One question with multiple inheritance is what to do if two base classes include methods with the same signature but different bodies.
public class MyClass implements InterfaceA, InterfaceB { statements; }
Java does support a somewhat similar notation. If ClassA and ClassB are very simple, specifically if they each consist of just constants and abstract methods, then they are called interfaces, say InterfaceA and InterfaceB, and MyClass can be defined as shown on the right. Since all methods in an interface are abstract and hence body-free, the question mentioned just above for multiple inheritance cannot arise.
package java.lang; public interface Comparable { public abstract int compareTo(Object o); }
As shown on the right the Comparable interface consists of just one method, compareTo().
Many classes in the java library implement this interface, which means that they supply an integer valued compareTo() instance method having one parameter, an Object.
If the instance object is less than
the argument object, the
value returned is negative; if the two are equal, the value returned
is 0; and if the instance object is greater than
the argument
object, the value return is positive.
public class String implements Comparable
Recall that we used compareTo() when studying the String class. If we looked at the header line of the class it would be similar to what is shown on the right.
public final class String implements Serializable, Comparable<String>, CharSequence
If we access the online Java documentation, we can see the actual header line, which is in the next frame. So String implements three interfaces. More interesting is the <String> appended to Comparable. This is an manifestation of Java generics, introduced in Java 1.5 (1.7 is current). Generics several purposes. Here they signify that a String can only be compared to another String, not an arbitrary object.
public class Student implements Comparable { public int stuId; public String name; public Student(int stuId, String name) { this.stuId = stuId; this.name = name; } public int compareTo(Object obj) { Student stu = (Student) obj; return name.compareTo(stu.name); } } import java.util.Scanner; import java.io.File; import java.io.FileNotFoundException; public class TestStudent { public static void main(String[]args) throws FileNotFoundException { Scanner getInput = new Scanner(new File("stu.db")); int n = getInput.nextInt(); Student[] student = new Student[n]; for (int i=0; i<n; i++) { int stuId = getInput.nextInt(); String name = getInput.next(); student[i] = new Student(stuId, name); } sort(n, student); System.out.printf("Sorted by name\n"); for (int i=0; i<n; i++) { System.out.printf("%s\t%3d\n", student[i].name, student[i].stuId); } } public static void sort(int n, Student[] student) { for (int i=0; <n-1; i++) for (int j=i+1; j<n; j++) if (student[i].compareTo(student[j]) > 0) { Student tmpStudent = student[i]; student[i] = student[j]; student[j] = tmpStudent; } } } 10 Sorted by name 1 Robert Alice 3 2 John Alice 9 3 Alice Harry 10 4 Jessica Jessica 4 5 Sam John 2 6 Sarah Judy 8 7 Mary Mary 7 8 Judy Robert 1 9 Alice Sam 5 10 Harry Sarah 6
The example on the right illustrates a class extending Comparable as well as reviewing a number of concepts we have learned previously.
As an added benefit, it previews sorting arrays of objects, which we will study very soon.
The top frame shows a very simple Student class that extends the Comparable interface. The class contains
Note that, although compareTo() will generate a runtime error unless its argument can be cast to student, the parameter itself is of type Object. Indeed, in order to implement Comparable, the defined compareTo() must override the compareTo() in Comparable and thus must have an Object parameter.
The generic implement would avoid this run-time error.
The second frame shows a test program using the Student class. The first loop reads in student IDs and names from a database file stu.db (shown in the third frame) and calls the Student() constructor to create the entries in the student array.
After the student records are read into the student array, the sort routine is invoked.
Finally the records are printed, to verify that they are now in sorted order.
You should compare the sort() method to the alphabetizing routing (sorting strings) that we did in section 9.2.13.
To conserve vertical space, the third frame contains two items. On the left is the stu.db file. It begins with a count of the number of students followed by the records themselves, one per line.
On the right is the output of the TestStudent program.
As expected, the records are now sorted by the name field.
Since the name Alice
appeared in two input records, it also
appears twice in the output.
Since the sort used is stable
, the relative order of the two
Alice
records is preserved.
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.
For performance reasons, the Java primitive types (int, char, etc) are not objects. Reference semantics requires an extra level of indirection.
However, the full power of object orientation, does require objects
so, for each primitive type, there is a corresponding, so called,
wrapper class
with a very similar name.
We have already seen Character, the wrapper class for
char.
Also present are Byte, Short, Integer,
Long, Boolean, Float,
and Double.
Double d = new Double(4.3); Character c = new Character('c');
To create a Double object, we naturally use a Double() constructor. The argument is a double (lower case, i.e., a primitive type). Examples for Double() and Character() are shown on the right. The Float(), Long(), etc. constructors are just the same.
Start Lecture #25
public static void sort(int n, Student[] student) { for (int i=0; i<n-1; i++) for (int j=i+1; j<n; j++) if (student[i].compareTo(student[j]) > 0) { Student tmpStudent = student[i]; student[i] = student[j]; student[j] = tmpStudent; } } public static void sort(int n, Comparable[] obj) { for (int i=0; i<n-1; i++) for (int j=i+1; j<n; j++) if (obj[i].compareTo(obj[j]) > 0) { Comparable tmpObj = obj[i]; obj[i] = obj[j]; obj[j] = tmpObj; } }
Recall the sort routine in Section 14.A. which has been reproduced on the right.
There is something curious about this routine: It sorts items in the Student class, but uses nothing about the Student class except for the compareTo method, which is present in any class implementing Comparable.
This raises the question of what would need to be changed for this sort to work for any class implementing Comparable.
The answer is that essentially nothing has to be changed. Specifically, the two occurrences of the class name Student, need be changed to Comparable. Although not necessary, the code on the right changes other names as well. In particular, the variable name student would be misleading so has been changed to obj.
When this new version is dropped into TestStudent, the
results are the same (ignoring the unchecked
warning, see
below).
The input is sorted alphabetically on the name field.
import java.util.Scanner; import java.io.File; import java.io.FileNotFoundException; public class TestStudent { public static void main(String[]args) throws FileNotFoundException { Scanner getInput = new Scanner(new File("stu.db")); int n = getInput.nextInt(); Student[] student = new Student[n]; for (int i=0; i<n; i++) { int stuId = getInput.nextInt(); String name = getInput.next(); student[i] = new Student(stuId, name); } Float[] f={new Float(3.),new Float (0.),new Float (-5.)}; Long[] l={new Long(333),new Long (0), new Long (-555)}; // Sort and print sort(3, student); sort(3, f); sort(3, l); System.out.printf("Sorted by name\n"); for (int i=0; i<3; i++) { System.out.printf("%s\t%3d %6.1f %5d\n", student[i].name, student[i].stuId, f[i], l[i]); } } public static void sort(int n, Comparable[] obj) { for (int i=0; i<n-1; i++) for (int j=i+1; j<n; j++) if (obj[i].compareTo(obj[j]) > 0) { Comparable tmpObj = obj[i]; obj[i] = obj[j]; obj[j] = tmpObj; } } }
The great news is that the identical sorting routine can be used to sort objects of any class that implements Comparable.
On the right is an expanded version of TestStudent. Note the following points.
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>
public class Student implements Comparable { public int stuId; public String name; public Student(int stuId, String name) { this.stuId = stuId; this.name = name; } public int compareTo(Object obj) { Student stu = (Student) obj; return name.compareTo(stu.name); } } import java.util.Scanner; import java.io.File; import java.io.FileNotFoundException; public class TestStudent { public static void main(String[]args) throws FileNotFoundException { Scanner getInput = new Scanner(new File("stu.db")); int n = getInput.nextInt(); Student[] student = new Student[n]; for (int i=0; i<n; i++) { int stuId = getInput.nextInt(); String name = getInput.next(); student[i] = new Student(stuId, name); } Float[] f={3.F,0.F,-5.F,2.5F,-8.F,8.2F,7.8F,0.F,-5.F,-8.F}; Double[] d = {3.,0.,-5.,2.5,-8.,8.2,7.8,0.,-5.,-8.}; Byte[] b = {33, 0,-55, 2,-8,8,7,0,-5,-8}; Short[] s = {333,0,-555,2,-8,8,7,0,-5,-8}; Integer[] g = {333,0,-555,2,-8,8,7,0,-5,-8}; Long[] l = {333L,0L,-555L,2L,-8L,8L,7L,0L,-5L,-8L}; Character[] c = {'q','8','A','a','Z','q','z','Q','8','8'}; String[] t = {"q","8","A","a","Z","q","z","Q","88","78"}; // Sort and print sort(10, student); sort(10, f); sort(10, d); sort(10, b); sort(10, s); sort(10, g); sort(10, l); sort(10, c); sort(10, t); System.out.printf("Sorted by name\n"); for (int i=0; i<10; i++) { System.out.printf("%s\t%3d%7.1f%7.1f%6d%6d%6d%6d%6c%6s\n", student[i].name,student[i].stuId, f[i],d[i],b[i],g[i],s[i],l[i],c[i],t[i]); } } public static void sort(int n, Comparable[] obj) { for (int i=0; i<n-1; i++) for (int j=i+1; j<n; j++) if (obj[i].compareTo(obj[j]) > 0) { Comparable tmpObj = obj[i]; obj[i] = obj[j]; obj[j] = tmpObj; } } }
The previous section illustrated both an advantage and slight
annoyance with the wrapper classes
Float,
Long, etc. when compared to the corresponding
primitive data types (float, long, etc).
The advantage is that, since the wrappers are classes, they can make use of many object oriented features. The minor annoyance is the relative wordiness of
Short s[]={new short[5],new short[7]};
when compared to
short s[]={5,7};
In most cases the annoyance can be avoided because Java will automatically convert between the primitive type and the corresponding wrapper class. Conversion to the wrapper class is called boxing and the reverse conversion is called unboxing.
As an illustration, on the right is yet one more version of the TestStudent class, this time using the autoboxing and also sorting many more types. For completeness we repeat the Student class and the student database.
Since both Student and TestStudent are top-level public classes, they must be in separate files.
The wrapper classes do work but it would be nicer if we didn't have to deal with them. The reason they are there is that, due to efficiency considerations, the primitive types are not classes and hence cannot make use of the properties of objects.
In contrast to my lukewarm assessment of wrapper classes, I would say that the ability to write a generic sorting method is simply wonderful. The identical method works for any class that implements comparable.
The requirement to implement comparable is exactly right. That is, to sort a bunch of items, you must be able to compare two of them and decide which is smaller. This is exactly what implementing comparable entails.
10 | Sorted by name 1 Robert | Alice 3 -8.0 -8.0 -55 -555 -555 -555 8 78 2 John | Alice 9 -8.0 -8.0 -8 -8 -8 -8 8 8 3 Alice | Harry 10 -5.0 -5.0 -8 -8 -8 -8 8 88 4 Jessica | Jessica 4 -5.0 -5.0 -5 -5 -5 -5 A A 5 Sam | John 2 0.0 0.0 0 0 0 0 Q Q 6 Sarah | Judy 8 0.0 0.0 0 0 0 0 Z Z 7 Mary | Mary 7 2.5 2.5 2 2 2 2 a a 8 Judy | Robert 1 3.0 3.0 7 7 7 7 q q 9 Alice | Sam 5 7.8 7.8 8 8 8 8 q q 10 Harry | Sarah 6 8.2 8.2 33 333 333 333 z z
Homework: 14.5 (omit UML diagram).
class LongStack { private int top = 0; private Long[] theStack; private static int totalStacked = 0; public LongStack() { theStack = new Long[100]; } public LongStack(int n) { theStack = new Long[n]; } public void push(Long elt) throws Exception { if (top>=theStack.length) throw new Exception("No room to push!"); totalStacked++; theStack[top++] = elt; } public Long pop() throws Exception { if (top<=0) throw new Exception("Nothing to pop!"); totalStacked--; return theStack[--top]; } public static int getTotalStacked() { return totalStacked; } } class ShortStack { private int top = 0; private Short[] theStack; private static int totalStacked = 0; public ShortStack() { theStack = new Short[100]; } public ShortStack(int n) { theStack = new Short[n]; } public void push(Short elt) throws Exception { if (top>=theStack.length) throw new Exception("No room to push!"); totalStacked++; theStack[top++] = elt; } public Short pop() throws Exception { if (top<=0) throw new Exception("Nothing to pop!"); totalStacked--; return theStack[--top]; } public static int getTotalStacked() { return totalStacked; } } class ObjectStack { private int top = 0; private Object[] theStack; private static int totalStacked = 0; public ObjectStack() { theStack = new Object[100]; } public ObjectStack(int n) { theStack = new Object[n]; } public void push(Object elt) throws Exception { if (top>=theStack.length) throw new Exception("No room to push!"); totalStacked++; theStack[top++] = elt; } public Object pop() throws Exception { if (top<=0) throw new Exception("Nothing to pop!"); totalStacked--; return theStack[--top]; } public static int getTotalStacked() { return totalStacked; } }
I have marked this chapter optional to indicate that it will not be included on the final exam. However generic classes are important in Java and will be covered in 102. I did discuss this material in class.
When describing stacks, we wrote two classes LongStack and ShortStack, which implemented respectively stacks of longs and shorts.
On the right is a version for Longs (the corresponding wrapper class). It has been cleaned up a little since we now know about private and exceptions. It also calculates (and provides access to) the total number of elements on all the stacks.
Directly below the LongStack we see
ShortStack, which is nearly identical.
The only difference is changing Long
to Short
in a number of places.
I wrote stacks of
these types as well.
Again the only modification was to change one word
in a
number of places
The only extra change was to add the line.
import java.util.Scanner;
I didn't write this one but it would be easy. The only extra change would be to include the GeometricObject class itself.
All these stacks are the same! Why do we have to write each one. Isn't there some analogue to the generic sorting method we wrote in 14.11?
Indeed we can do better, but we must first decide what we want. Our sorting method was generic, that is, the method accepted an array of any type (providing it implemented Comparable), but not an array of mixed types (Java doesn't have such heterogeneous arrays). The analogy for stacks would be to write just one stack class but, when creating a specific stack, you specify the type of elements it will contain.
A heterogeneous stack or sorting method would be one that processed elements of differing types. When creating an array in Java, we must specify they type, but the elements can be any subtype of that type.
For stacks, the analogy would be to have a single stack that can hold elements of differing types at the same time. This is easy (but perhaps not what we want, see below).
This looks no different than a stack of Integers as you can see on the right. Since every class extends Object, items of every (non-primitive) type can appear on such a type, it is truly heterogeneous. This seems perfect; but is it?
Imagine we write just the ObjectStack class and then create several stacks in this class. We can use one of these stacks to hold Doubles, we can use another one to hold Scanners, and can use a third one to hold a bunch of different items.
ObjectStack dblStk = new ObjectStack(); Double d1=2.5, d2; dblStk.push(d1); // always works d2 = dblStk.pop(); // compile error d2 = (Double)dblstk.pop(); // risky Object o = dbleStk.pop(); if (o instanceof Double) d2 = o; // safe else // What goes here? A runtime error msg
A stack of Objects is indeed perfect
for the last (mixed bag
) application, but not for the first
two.
Assume we use ObjectStack to create a
stack for Doubles as shown on the
right.
Java does not understand that we have a stack of just one type (a homogeneous stack) so a naked pop will not compile.
A downcast will work if the program is correct. The trouble is that if we erred and could possible push say a String on to the stack, then the downcast would generate a run-time error, a very bad outcome. The only worse outcome would be a wrong answer produced with no visible error.
Using instanceof is probably the best we can do, but still any error is not detected until runtime.
What we want for these homogeneous cases is to write just one generic stack class, but be able to create homogeneous stacks of any (non-primitive) type.
How can we do this?
Come back in September; it is covered in 102!
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.
For example let's compute f(3)
f(3) = f(2) + g(2) = f(1) + g(1) + f(2) + 1 = f(0) + g(0) + f(1) + 1 + f(1) + g(1) + 1 = 0 + f(0) + 1 + f(0) + g(0) + 1 + f(0) + g(0) + f(1) + 1 + 1 = 0 + 0 + 1 + 0 + f(0) + 1 + 1 + 0 + f(0) + 1 + f(0) + g(0) + 1 + 1 = 0 + 0 + 1 + 0 + 0 + 1 + 1 + 0 + 0 + 1 + 0 + g(0) + 1 + 1 = 0 + 0 + 1 + 0 + 0 + 1 + 1 + 0 + 0 + 1 + 0 + f(0) + 1 + 1 + 1 = 0 + 0 + 1 + 0 + 0 + 1 + 1 + 0 + 0 + 1 + 0 + 0 + 1 + 1 + 1 = 7
public class FG { 0 0 public static void main(String[]arg) { 1 1 for (int i=0; i<10; i++) 2 3 System.out.printf("%2d %4d\n", i, f(i)); 3 7 } 4 15 public static int f(int n) { 5 31 if (n <= 0) 6 63 return 0; 7 127 return f(n-1) + g(n-1); 8 255 } 9 511 public static int g(int n) { return f(n) + 1; } }
The program is quite simple and is shown on the right together with the output produced.
What is surprising is that it works! After all when we invoke f(3), this sets n=3 and invokes f(2), which sets n=2. So now in the same method, namely f(), the same variable, namely n, has two different values at the same time, namely 3 and 2.
How can this be?
Actually, the situation gets even worse when we consider the call of f(9), and worse still when we remember that f() calls g(), which in turn calls f(). There will be very many values for n in f() at the same time.
public class Factorial { public static void main(String[]arg) { System.out.printf(" n Recursive Iterative\n"); for (int i=-1; i<10; i++) if (i<0) System.out.printf("(%d)! is not defined!!\n", i); else System.out.printf("%2d %8d %8d\n", i, recFac(i), iterFac(i)); } public static int recFac(int n) { if (n <= 1) return 1; else return n * recFac(n-1); } public static int iterFac(int n) { int fact=1; if (n>1) for (int i=2; i<=n; i++) fact = fact * i; return fact; } } n Recursive Iterative (-1)! is not defined!! 0 1 1 1 1 1 2 2 2 3 6 6 4 24 24 5 120 120 6 720 720 7 5040 5040 8 40320 40320 9 362880 362880
Factorial is a very easy function to compute either with or without recursion. It is normally written using ! rather than the usual parenthesized notation. That is, instead of factorial(7), we usually write 7!
For a positive integer n, n! is defined to be
1 * 2 * 3 * ... * n
For example 5! = 1 * 2 * 3 * 4 * 5 = 120
If instead of defining n! to be multiplying from 1 up to n, we define n! (equivalently) to be multiplying from n down to 1, we see that the definition is the same as defining (recursively).
n! = n * (n-1)!
It is conventional to define 0! = 1.
Sometimes factorial is defined to be 1 for negative numbers as well, but I believe it is more common to consider factorial undefined for negative numbers.
The program on the right computes factorial twice, once with each definition. The factorial methods would return 1 if given a negative argument, but the main program declares this to be an error.
As in the previous section, perhaps the most interesting question is "How can the Java program possible work with n having so many different values at the same time?".
On the board show the stack growing and shrinking when computing recFact(4).
Homework: 14.5 (omit UML diagram).
public class Fibon { public static void main(String[]arg) { System.out.printf(" n Recursive Iterative\n"); for (int i=-1; i<8; i++) if (i<0) System.out.printf("Fibonacci undefined for %d\n", i); else System.out.printf("%2d %8d %8d\n", i, recFibon(i), iterFibon(i)); } public static int recFibon(int n) { if (n <= 1) return 1; else return recFibon(n-1) + recFibon(n-2); } public static int iterFibon(int n) { int a=1, b=1, c=1; for (int i=2; i<=n; i++) { c = a + b; a = b; b = c; } return c; } } n Recursive Iterative Fibonacci is not defined for -1 0 1 1 1 1 1 2 2 2 3 3 3 4 5 5 5 8 8 6 13 13 7 21 21
The Fibonacci sequence is normally defined recursively by the following two rules.
From the formulas above, we see that the sequence begins
1, 1, 2, 3, 5, 8, 13, ...
The limiting ratio f(n)/f(n-1)
as n approaches infinity is called the
golden mean
and comes up in a number of mathematical and
biological settings.
The code for both recursive and iterative solutions is on the right.
Again the methods will calculate Fibonacci values for negative n, but the main() method correctly flags it as an error.
Draw on the board the diagram showing all the recursive calls that occur when you invoke recFib(4).
I must note that the recursive version is horribly inefficient. On my laptop an execution of iterFibon(50) is essentially instantaneous, but recFibon(50) did not finish in a minute.
This inefficiency is due to the many recursive calls required. For example recFibon(39) made well over 204 million recursive calls to arrive at the answer of 102,334,155.
Start Lecture #26
It is important when designing a recursive solution, that you avoid
infinite recursion where the method f()
always calls itself.
There must be a so called base case
that can be solved
directly.
For example, in the factorial problem, we specified that 0!=1.
Another requirement is that when you are not in a base case, the
recursive calls bring you closer
to the base case, so that
you eventually reach a base case.
For example, in the factorial problem, if n>0, then we
define n! = n*(n-1)!, which means that when
trying to compute factorial for n, we need to compute
factorial for n-1.
Since the base case is n=0, n-1 is closer to the
base case than is n.
Often the base case occurs for a small value of an integer parameter, (e.g., n for factorial or Fibonacci, the length for many string problems, array bounds for some array problems, etc). Then, if the recursive call has a smaller value of the parameter than the original, it is closer to the base case.
Thus, many times the high-level structure of a recursive solution is
if (the parameters fit the base case) return the solution directly else call the method recursively with a smaller parameter value return the answer using the answer from the recursive call
Here is an (inefficient, overly complex) method of adding non-negative integers that uses the above pattern.
public static int sum(int x, int y) { if (y == 0) then return x int z = sum(x,y-1); return z+1; }
Sometimes we have more than one base case. For example consider the isPalindrome() procedure on the right. Recall that a string is a palindrome if it reads the same from left to right as from right to left.
public static boolean isPalindrome(String s) { if (s.length() <= 1) // base case 1 return true; if (s.charAt(0) == x.charAt(s.length()-1)) // base case 2 return false; return isPalindrome(s.substring(1,s.length()-1)); }
Two base cases are that an empty string or a string of length one is always a palindrome.
Another base case is that if the first and last characters are not equal, then it is not a palindrome.
If neither base case applies than the original string is a palindrome if and only if the substring omitting the first and last characters (a smaller problem closer to the first two base cases) is a palindrome.
The above code is from the book. I might prefer to view the solution with only two base cases as follows.
public static boolean isPalindrome(String s) { if (s.length() <= 1) // base case return true; return (s.charAt(0)==x.charAt(s.length()-1)) && isPalindrome(s.substring(1,s.length()-1)); }
The base cases are that empty and length 1 strings are palindromes.
If we are not in a base case, the original string is a palindrome if and only if (1) the first and last characters are the same and (2) the substring omitting these two characters is a palindrome. This interpretation gives rise to the code on the right.
Sometimes it is easier or more efficient to solve a more general problem. For example, an inefficiency in the above palindrome program is that at each recursive call it builds a new string when in fact all that we need to do is to restrict our attention to a contiguous portion of the original string.
public static boolean isPalindrome(String s, int lo, int hi) { if (hi <= lo) return true; // base cases return (s.charAt(lo)==s.charAt(hi)) && isPalindrome(s, lo+1, hi-1); }
So instead of asking if a string is a palindrome, we as a more
general question:
is the portion of the string from position
lo to position hi a palindrome
and then recursively restrict the range lo...hi,
while keeping the same string.
This program is shown on the right.
One objection to the last program is that it is more awkward for the user who must now invoke the program as isPalindrome(s,0,s.length()-1). In other words, the helper program may have helped the implementer (in this case to make their program more efficient), but is sure didn't help the user who much preferred writing isPalindrome(s).
public static boolean isPalindrome(String s) { return isPalindrome(s, 0, s.length()-1)); }
To answer this objection, we write a second isPalindrome() method that meets the user's expectation and properly invokes the first isPalindrome() method. The code is shown on the right. Note that the method name isPalindrome() is overloaded: If invoked with just a string argument, the second version is called; if invoked, with a string and two integers, the first version is called.
Start Lecture #27
Remark: Chris Nelson points us at an
infinite recursive game
http://insideastarfilledsky.net/.
Chris adds
The description of the game isn't perfect on the front page but
the bullets points page answers a question about the term
"infinite": http://insideastarfilledsky.net/bulletPoints.php
I watched the trailer and you can see
the recursion.
In particular the part entitled
Wait, where are you now?
is reminiscent of the questions one asks when trying to understand a
recursive method.
On the right is an preliminary look at an important data structure, much studied in 102: a binary tree.
These trees have two kinds of nodes: interior nodes drawn as
squares, and leaves drawn as circles.
Each interior node has one or two children (hence the
name binary
; in 102 we also study general trees with interior
nodes having many children).
Each leaf has no child.
A node also contains data, in this case a character.
If we look closely, we see that the tree on the right has no node with exactly one child. A tree in which all nodes have either two or zero children is called a proper binary tree.
public class Tree { char data; Tree left; Tree right; Tree(char data, Tree left, Tree right) { this.data = data; this.left = left; this.right = right; } Tree (char data) { // construct a leaf this(data, null, null); } }
The code on the right is a Java class for the binary tree data structure. Note that we use the name Tree when the object is in fact a single node of a tree (the justification for this terminology will be soon clear).
We see that a Tree has three components, two Tree's and a char, which seems crazy! How can a Tree contain two Tree's? For one thing how can a Tree be large enough to hold two trees?
The answer is an old friend (enemy?), reference semantics. The variables left and right do not contain trees, but instead contain references to trees. Recall that all references are the same (modest) size. Thus a Tree object contains two references to Tree's and a char.
For a leaf the references are null For an interior node, the references are not null, but point to actual trees.
Informally, you can think of a tree (i.e., a tree node) as it is shown in the diagram. That is, you can view a node as containing two arrows and a character (with arrows corresponding to null references drawn differently (or omitted).
The program on the right first constructs the tree in the diagram above, then counts the nodes in the tree and finally traverses the tree in three different orders.
public class Tree { char data; Tree left; Tree right; Tree(char data, Tree left, Tree right) { this.data = data; this.left = left; this.right = right; } Tree (char data) { // constructs a leaf this(data, null, null); } public static void main(String[]arg) { Tree t1 = new Tree('A'); Tree t2 = new Tree('l'); t1 = new Tree('C',t1,t2); t2 = new Tree('l'); Tree t3 = new Tree('S',t1,t2); t1 = new Tree('a'); t2 = new Tree('n'); t1 = new Tree('1',t1,t2); t2 = new Tree('G'); t1 = new Tree('0',t1,t2); t1 = new Tree('1',t3,t1); System.out.printf("The tree has %d nodes\n", count(t1)); System.out.printf("\nPreorder Traversal\n"); t1.preOrderTraverse(); System.out.printf("\n\nPostorder Traversal\n"); t1.postOrderTraverse(); System.out.printf("\n\nInorder Traversal\n"); t1.inOrderTraverse(); } public static int count(Tree t) { if (t == null) return 0; return 1 + count(t.left) + count(t.right); } public void visit() { System.out.printf("%c ", this.data); } public void preOrderTraverse() { if (this==null) // base case return; this.visit(); if (this.left != null) this.left.preOrderTraverse(); if (this.right != null) this.right.preOrderTraverse(); } public void postOrderTraverse() { if (this==null) // base case return; if (this.left != null) this.left.postOrderTraverse(); if (this.right != null) this.right.postOrderTraverse(); this.visit(); } public void inOrderTraverse() { if (this==null) // base case return; if (this.left != null) this.left.inOrderTraverse(); this.visit(); if (this.right != null) this.right.inOrderTraverse(); } } There are 11 nodes in the tree Preorder Traversal 1 S C A l l 0 1 a n G Postorder Traversal A l C l S a n 1 G 0 1 Inorder Traversal A C l S l 1 a 1 n 0 G
First let us concentrate on how the beginning of the main() method uses both constructors to create the desired tree, which I have redrawn here for convenience.
To create an interior node, we use the first constructor and thus need to supply two child trees in addition to the data. This means we must construct both children before constructing their parent. Hence, the tree must be constructed from the bottom up.
To construct a leaf we use the second constructor and thus supply only the data item, which in this small example is simply a character. The left and right fields are set to null by the first constructor.
In main() I deliberately reused the Tree variables t1,t2,t3 as I believe it is instructive to see which tree nodes must be remembered to enable constructing other nodes.
On the board execute each line of main() showing the variable name, the reference, and the referred to Tree. As execution proceeds, we see why we called the objects referred to by t1,t2,t3 trees not nodes.
With recursion available counting is a very easy, very short program. Without recursion it would be considerably more challenging. The idea for counting is that the number of nodes in any (nonempty) tree is 1 (for the root) plus the number in the left subtree (a recursive call of the same routine) plus the number in the right subtree (another recursive call of the same routine).
The base case is that the number of nodes in an empty tree is zero. A reference to an empty tree has the value null.
In a tree traversal all the tree nodes are visited. In the three traversals we study, each node is visited exactly once. In general, what to do during a visit is specified by the user method visit(). In our example, visit() consists simply of printing the node's character data item.
How do we ensure we visit each node (exactly once) and in what order should we visit them? The key to the first question is that traversing a node has three tasks, visit the node, traverse the left subtree (a recursive call) and visit the right subtree (another recursive call).
Three orderings are common: pre-order, post-order, and in-order. In all three, a left child is visited before its right sibling. The pre/post/in refers to when a node is visited in relation to its children.
On the board, without looking at the code, manually perform all three traversals for the tree above. Then repeat the exercise for a non-proper binary tree.
Homework: For each of the trees below, determine the order of node visitation for all three traversals.
Now that we understand trees and how to recursively walk through them, we can solve a number of problems. We will look at a familiar structure, the file system tree.
import java.io.File; import java.util.Scanner; public class DirOrFileSize { public static void main(String[] args) { System.out.print("Enter a directory or a file: "); Scanner input = new Scanner(System.in); String directory = input.nextLine(); System.out.printf("There are %d bytes.\n", getSize(new File(directory))); } public static long getSize(File file) { if (! file.isDirectory()) // Base case, 1 file return file.length(); else { // All files and subdirectories long size = 0; // Total size in this directory File[] files = file.listFiles(); for (int i = 0; i < files.length; i++) size += getSize(files[i]); return size; } } }
In this tree the interior nodes are the (sub-)directories and the leaves are the simple files. In 202 you will learn that file system trees are more complicated than this and actually are often not trees.
Note that a directory can have any number of files and sub-directories so some interior nodes have more than 2 children. Thus we do not have a binary tree and cannot use inorder traversal.
Since the trees are not binary, the data structure in 20.A cannot
be used.
However this is not our problem
.
The authors of java.io.File must have taken 102 or
equivalent in order to write that class.
We are just clients of the class and do not need to look under the
covers to see how it is implemented.
The program on the right reads in a file or directory name and prints the total size of all the files there. Specifically, if given a file it gives the size of the file; whereas, if given a directory, it prints the size of all the files under that directory (which includes files under subdirectories of the directory).
The base case is a simple file, in which case the size is obtained by calling the File.length() library method.
When encountering a directory, getSize() sums the sizes of all the files and subdirectories in the given directory. Note that this involves many recursive calls, one for each file and subdirectory. Much of the work is done by the library method listFiles() that conveniently constructs and returns an array of Files, giving the contents of the directory.
Consider the file system tree on the right, where boxes are directories and circles are ordinary files.
The program above produces the output to the left, when the program is run on this file system.
Note that the items within the a directory appear below it and are indented. All the work is done by the second (recursive) listFiles(). Its first parameter is the file or directory to list and the second argument is the indentation to use.
We overload listFiles() as a convenience to the user.
The base case is when the first argument is a file, which is simply listed using the File.getName() library routine.
import java.io.File; import java.util.Scanner; public class ListFiles { public static void main(String[] args) { System.out.print("Enter a directory or a file: "); Scanner getInput = new Scanner(System.in); String dirOrFile = getInput.next(); listFiles(new File(dirOrFile)); } public static void listFiles(File dirOrFile) { listFiles(dirOrFile, ""); } public static void listFiles(File dirOrFile, String indent) { System.out.println(indent + dirOrFile.getName()); if (! dirOrFile.isDirectory()) return; // base case -- simple file File[] dirOrFiles = dirOrFile.listFiles(); for (int i = 0; i < dirOrFiles.length; i++) listFiles(dirOrFiles[i], indent + " "); return; } } T E F b y A a x y D
When given a directory, listFiles() finds all the files and subdirectories and then calls itself recursively on each one giving an indentation 2 greater than its own indentation.
The algorithm is actually a preorder traversal extended to non-binary trees.
Homework: Compute the postorder traversal of the above tree. Note that there is no inorder traversal defined since this is not a binary tree and we wouldn't know when to visit the parent.
Assume we have a binary tree, whose data component is
an int (or any class that
extends comparable.
Also assume that the tree is sorted
in the sense that, for
any node all the data to the left is less than the data at the
node and all the data to the right is greater than the data at the
node.
Such a tree is called a binary search tree. An example is drawn on the right.
Let's say we want to search the tree to see if the number 18 is present (it is) and then we want to search to see if 35 is present (it isn't).
In each case we could use one of our traversal methods and have visit check to see if the desired data item is present.
But we can do much better.
Once we have examined the root we know right away that there is no need to check the right subtree for 18 and no need to check the left subtree for 35.
boolean search (Binsrchtree tree, int value) { if (tree==null) return false; if (value==tree.data) return true; if (value<tree.data) return search(tree.left, value); return search(tree.right, value); }
This will be covered in great detail in 102.
An important point is that we would prefer bushy
trees,
that is, trees which have many leaves (this will be made precise
in 102).
We can search such trees in time log(N), where N
is the number of nodes.
The (untested) code on the right illustrates the idea of eliminating half the tree at each step since it never searches both left and right subtrees.
The interesting question is how do we add and remove items from
this tree and keep the crucial property
left subtree < node < right subtree
.
On the right we see a representative game tree
, a key data
structure in many 2-person computer games such as chess and
checkers.
Placing the numbers in the leaves and then searching the tree
constitutes is how such programs choose their next move.
For the moment, ignore the numbers in the nodes and assume it is
the computer's turn to move.
The root of the tree represents the current game position.
The arcs leaving a node represent the possible moves from the
corresponding position.
In the given diagram, there are only two possible moves from each
position; in real games this so-called branching factor
is
much larger and the tree correspondingly much wider.
Since the tree has three levels below the root we are considering
three half-moves
(i.e., moves by one side).
Specifically the arcs leaving the root represent the possible
moves of the computer and the two nodes below the root are the
resulting position.
The arcs leaving these nodes represent the possible replies of the
opponent and the bottom row of arcs represent our possible
rejoinders to these replies.
We have chosen to stop after three half-moves; real programs go
deeper.
Since we are not considering any further moves the nodes at depth
3 are leaves.
Now for the numbers.
Another key component of a game program is the
static board evaluator
, a method that evaluates how
favorable the board position is for the computer.
Those are the numbers in the leaves.
So the computer would most prefer to be in the position with
label 10 and least prefer the position with label -20.
Since the third row of arrows represent the computer's move, it will choose the ones that lead to higher score. Those (maximized) values are the numbers in the bottommost row of internal nodes.
Since the middle row of arrows are the oponent's moves, the choice will be the minimum, which gives the numbers in the middle row of internal nodes.
Finally, the top row of arrows are again the computer's moves so the maximum is computed and that is the value in the root.
As a result of this analysis, the computer will make the
right-hand move and expects that the game will have a value of
4.
The arcs in magenta represent the computer's belief of the game
continuation and the magenta arc is called the
principal variation
.
// Evaluate expressions in prefix form // 1. A double // 2. op Expr Expr (op = + - * /) import java.util.Scanner; public class EvalExpr { static Scanner getInput = new Scanner(System.in); public static void main (String[] args) throws Exception{ System.out.print("Enter an expression: "); System.out.println("Answer: " + evalNextExpr()); } static double evalNextExpr() throws Exception { if (getInput.hasNextDouble()) return getInput.nextDouble(); String s = getInput.next(); if (s.length()!=1) throw new Exception ("Invalid expression."); char c = s.charAt(0); if (c == '+') return evalNextExpr() + evalNextExpr(); if (c == '-') return evalNextExpr() - evalNextExpr(); if (c == '*') return evalNextExpr() * evalNextExpr(); if (c == '/') return evalNextExpr() / evalNextExpr(); throw new Exception ("Invalid expression."); } }
We normally write expressions using infix form, where the operator is placed in between the operands. For example, 6+4. We have precedence rules so that 6+4*3=18.
Two other forms are used, prefix form and postfix form. One of these is called Polish and the other reverse Polish, but I forget which is which.
In prefix form the operator is placed before the two operands and in suffix form the operator is placed after the two operands.
There is no concept of precedence since there is only one way to evaluate +6*4 3
The program on the right makes the simplifying assumption that spaces are placed after each token. For example the previous expression would be written + 6 * 4 3
Note how short this program is; I suspect it would be quite a bit
longer and harder if we didn't have recursion available.
In particular the line
return evalNextExpr()+evalNextExpr();
(and the equivalent ones with +,-,*,/) would be more difficult.
public class Hanoi { public static void main(String[]args) { movePile (Integer.parseInt(args[0]),"left","right","middle",""); } public static void movePile (int n, String from, String to, String aux, String indent) { if (n > 0) { // n==0 is base case, do nothing movePile (n-1, from, aux, to, indent+" "); System.out.printf ("%sMove disk (#%d) from %s to %s\n", indent, n, from, to); movePile (n-1, aux, to, from, indent+" "); } } }
Show demo on emacs.
Although the problem might seem difficult, it is actually simple once the following (recursive) key is recognized.
To move an n-disk pile from one peg to a second peg
Both recursion and iteration are control-flow techniques. That is both determine when other statements are executed.
In theory, they are equivalent: any program using one of them can be converted to using the other.
It is normally rather easy to convert iteration into recursion, but is rarely done for languages like Java where recursion carries extra overhead,
It is in general quite difficult to convert a recursive method into an iterative one, except for ...
If a method calls itself only once and this call is the last action of the method, we have tail recursion.
In this case it is easy to convert the method to use a form of iteration.
Very roughly speaking, the reason recursion is difficult to
eliminate is that an extra stack frame
is needed to hold
the values of the local variables in the new invocation.
When this invocation terminates, the new frame is dropped and the
old frame, corresponding to the caller, is used.
With tail recursion the called method can use the frame of the caller because the caller has nothing left to do and hence no longer needs its frame.
Some compliers automatically convert tail recursion into iteration. This is most common in languages emphasizing recursion such as lisp.
int recursiveFactorial(int n) { if (n == 0) return 1 return n * recursiveFactorial(n-1); } int tailRecFactorial(int n) { tailRecFactorial(n, 1); } int tailRecFactorial(int n, int value) { if (n == 0) return value; return tailRecFactorial(n-1, n*result) }
Because tail recursion can be readily converted to iteration, it is advantageous to arrange for only one recursive call with that call the last action of the method
The code on the right illustrates one such conversion.
The top version is our familiar recursive implementation of factorial. It is not tail recursive since after the recursive call is performed the caller must still do a multiplication. Close, but no cigar!
The bottom version has cleverly pushed the multiplication into the recursive call so that it is now performed before the recursion takes place not after. This minor change would permit several compilers (not for Java) to convert the program into iteration.