6.170 is a lab class where you will learn to design and implement software systems. In this recitation we will review Java syntax and object oriented programming concepts. The material covered in this recitation should prepare you for problem set 1.
In this recitation we will review basic Java and OOP. By the end of this recitation you should be familiar with Java syntax. You will also have the foundation for program design and implementation.
Please use the following as a guide.
First, take a look at the provided code: Hanoi.java and HanoiMain.java. Try to see how much sense you can make out of it with a cursory glance.
A Java program is made up of 1 or more classes. The Hanoi example has two classes. One is used to interface to the command line, and the other defines the towers of Hanoi puzzle. Each class has its own .java file, with the same name.
Each Java file has one class declaration. This includes the visibility of the class, the keyword class, the name of the class, optionally the classes and interfaces it extends or implements, and the body of the class enclosed in curly braces.
At the top of the class's body are field declarations followed by constructors, then methods.
/** * This is a JavaDoc comment block. Note that * it begins with two stars after the slash. * The vertical stars are ignored, and just make * it clearer that it is a comment. **/ // You can also make block comments // like this. This is not a JavaDoc // comment.
Comments may be used to temporarily remove code from a program. The most effective comment for this is // placed at the beginning of every line you intend to remove. Code removed with comments should never be left in a final version of your program.
Comments may be used to explain tricky parts of your program in English. It is also good to put a comment before every function describing what it does. If you have any trouble reading the comments in the Hanoi example, or in the problem set 1 code, feel free to ask a TA or LA to explain any of the comments.
Fields are where data is stored for each class. Fields may be public or private. They also may be static or not. Generally, it is good practice to make all of your fields private, except in special cases.
A method declaration defines a new method for a class. In Java all methods are associated with a class. Here's a simple example:
public int sum(int a, int b) {
return a+b;
}
The first identifier is the return type. This is the type of the value returned by the method. There is a special return type void which indicates that no value is returned. Next is the method name. Then are the parameters with their types, and finally is the method body. In functions which have a non-void return type, there must be a return statement in the body. There may be many return statements, and they may be anywhere in the body, even in deeply nested loop bodies.
A constructor is the piece of code which is executed to create a new instance of a class. Here is an example of a very simple class with a single constructor.
class ComplexNumber {
float real;
float imaginary;
ComplexNumber(float realPart, float imaginaryPart) {
real=realPart;
imaginary=imaginaryPart;
}
}
A constructor looks very much like a method declaration, except that it has only the name of the class, and not both a return type and a function name.
This is the first thing everyone learns in a new language, and it covers a lot of important constructs in Java.
public class HelloWorld {
public static void main(String args[]) {
System.out.println("Hello, World!");
}
}
You are already familiar with class and method declarations, but there are a few new things here too. You will notice that there are three identifiers before the method name here. The keywords public and static are prepended to the method declaration. Their meanings we mentioned earlier. This particular method declaration is the method which is always called when this class is invoked by the JVM. The method body here is just one line. System is the name of a class. It is followed by a dot operator. This operator is used to select a method or a field from object on its left. System.out refers to the out field of the System class. System.out.println refers to the println method of that field. This method is then passed the parameter "Hello, World!".
A method body is made up of statements. These include method calls, assignments, and flow control. HelloWorld has an example of a statement which is a method call. Many statements end in a semicolon. Assignments look like id = expr;. Here expr may be any expression. An expression may be a method call, a variable, or any combination of these with the operators.
Flow control includes if, for, and while.
if (foo == bar) {
// True branch
} else {
// False branch
}
Above is a typical if statement. The else portion is optional. The expression in the parenthesis after if is the condition. If this condition is true, the true branch of the if is executed, otherwise the else branch is executed.
if (foo == bar) {
// True branch
} else if (foo==baaz) {
// True branch
} else {
// False branch
}
Above the false branch of the previous if statement is replaced with another if statement. This new if statement is only executed if the first condition is false.
for (int i=0; i<10; i++) {
// loop body
}
Above is a typical for loop. The for loop may look complicated at first, but it will become familiar soon. The for loop is also very flexible, but is usually only used one or two ways. Inside the parenthesis are three expressions. The first is evaluated before the loop executes. The next is evaluated before each execution of the body of the loop. If it is true, the body is executed. If it is false, the for loop terminates. The final expression is evaluated after each execution of the loop body.
In this brief code example we encounter the increment operator. In short, i++ is the same as i=i+1. Similarly there is a decrement operator (--).
int i=0; int j=i++; int k=++i;
int i = 0;
while (i<10) {
// loop body
i++;
}
Above is a while loop which is (very nearly) functionally equivalent to the for loop above.
Consider the following simple Java method:
/**
This takes an integer array, a begin index into the array, and an
end index into the array, and returns the sum of the array elements
from the begin index, up to, but not including, the end index.
*/
public int computeSum(int[] elementArray, int beginIndex, int endIndex) {
int result;
result = 0;
for (int i=beginIndex; i<endIndex; ++i)
result += elementArray[i];
return result;
}
This seemingly innocent program is fraught with peril! The following must be true in order for the program to work correctly:
To ensure program correctness, either this method must check these conditions, or every method that calls this one must ensure that it passes in arguments that meet these requirements. This is by no means the end of the world for this program. In fact, this isn't even a problem with the code. It's only a matter of specification.
A program invariant is a property that is always true at a particular program point or points. Thus, an invariant is a static constraint on a program, and provides valuable insight into its correct intended behavior.
A violation of such a constraint implies some sort of error in the program. Such bugs are often introduced when code is modified, especially when it is modified by a person other than the one that wrote the original code. Thus, explicit statement of invariants is a very useful way to prevent bugs.
Well known fact:
The cost of a bug increases exponentially with the length of time it exists in a program
It is easier to fix a bug in a program that is in development phase, than in a program that is in test phase, or, even worse, one that has already been released.
Aside from helping the programmer ensure program correctness, invariants have many other benifits. They are part of the documentation, and provide a concise, formal description of constraints that ensure correct behavior. They help avoid bugs, especially when more than one individual is working on the same piece of code. Bugs are often introduced when code is modified. This often occurs when the original invariants are not explicitly stated. These original invariants are then easily forgotten by the programmer modifying the code, especially if he/she is not the programmer that wrote the original code.
Specifying your invariants aids in writing a comprehensive test set, and helps validate this set. Your invariants help suggest boundary conditions to include in test sets.
There should be plenty of motivation to use invariants. Unfortunately, in practice, explicit invariants are usually absent from code. Inexperienced programmers don't really know about invariants. Lazy programmers think it's too much hassle, and even experienced programmers sometimes forget to explicitly state some or all invariants in an important piece of code.
In practice invariants should always be written when you are designing or implementing. If you are required to determine invariants after coding, there may be a problem with your design methodology.
This term you will be introduced to a tool called Daikon which can automatically validate invariants, and even detect unspecified invariants in your code. This is not a replacement for writing invariants yourself. Instead it is to be used as a tool to determine if your invariants are correct, and possibly show you something you may have ommitted. If one of your invariants fails, you can be sure that either your invariant is not correct, or there is a bug in your code.