CS 61B (Sp21) Notes 1, Introduction to Java
1. Intro, Hello World Java
Java and Object Orientation
Java is an object oriented language with strict requirements:
- Every Java file must contain a class declaration*.
- All code lives inside a class*, even helper functions, global constants, etc.
- To run a Java program, you typically define a main method using
public static void main(String[] args)
*: This is not completely true, e.g. we can also declare “interfaces” in .java
files that may contain code.
public class HelloWorld {
public static void main(String[] args) {
System.out.println("Hello world!");
}
}
Java and Static Typing
Java is statically typed.
- All variables, parameters, and methods must have a declared type.
- That type can never change.
- Expressions also have a type, e.g.
larger(5, 10) + 3
has type int. - The compiler checks that all the types in your program are compatible before the program ever runs.
Compilation

$ javac HelloWorld.java
$ java HelloWorld
Hello World!
Why make a class file at all?
-
.class
file has been type checked. Distributed code is safer. -
.class
files are ‘simpler’ for machine to execute. Distributed code is faster.
2. Defining and Using Classes
Classes
- Classes can contain not just functions (a.k.a. methods), but also data (a.k.a. variables).
- Classes can be instantiated as objects.
Static vs. Non-Static Members
- A variable or method defined in a class is also called a member of that class.
- A class may have a mix of static and non-static members.
- Static members are accessed using class name.
- Non-static members cannot be invoked using class name.
- Static methods must access instance variables via a specific instance.
3. Testing
[placeholder]
4. References, Recursion, and Lists
Primitive Types
Bits
Information is stored in memory as a sequence of ones and zeros. Each Java type has a different way to interpret the bits:
- 8 primitive types in Java: byte, short, int, long, float, double, boolean, char
Declaring a Variable (Simplified)
When you declare a variable of a certain type in Java:
- Your computer sets aside exactly enough bits to hold a thing of that type.
- Java creates an internal table that maps each variable name to a location.

The Golden Rule of Equals (GRoE)
Given variables y
and x
, y = x
copies all the bits from x
into y
.
Reference Types
Everything else, including arrays, is a reference type.
Class Instantiations
When we instantiate an Object:
- Java first allocates a box of bits for each instance variable of the class and fills them with a default value (e.g.
0
,null
). - The constructor then usually fills every such box with some other value.

Can think of new
as returning the address of the newly created object.
- Addresses in Java are 64 bits.
- Example: If object is created in memory location
2384723423
, then new returns2384723423
.

Reference Type Variable Declarations
When we declare a variable of any reference type:
- Java allocates exactly a box of size 64 bits, no matter what type of object.
- These bits can be either set to:
-
null
(all zeros). - The 64 bit address of a specific instance of that class (returned by
new
).
-

Reference types obey the Golden Rule of Equals: Just as with primitive types, the equals sign copies the bits.

Parameter Passing
Passing parameters obeys the same rule: Simply copy the bits to the new scope.
Instantiation of Arrays
Arrays are also Objects.
int[] a = new int[]{0, 1, 2, 95, 4};
- Creates a 64 bit box for storing an int array address. (declaration)
- Creates a new Object, in this case an int array. (instantiation)
- Puts the address of this new Object into the 64 bit box named
a
. (assignment)

5. SLLists, Nested Classes, Sentinel Nodes

public class SLList {
private static class IntNode {
public int item;
public IntNode next;
public IntNode(int i, IntNode n) {
item = i;
next = n;
}
}
private IntNode sentinel;
private int size;
public SLList() {
sentinel = new IntNode(-1, null);
size = 0;
}
public void addFirst(int x) {
sentinel.next = new IntNode(x, sentinel.next);
size += 1;
}
public int getFirst() {
return sentinel.next.item;
}
public void addLast(int x) {
IntNode p = sentinel;
while (p.next != null) {
p = p.next;
}
p.next = new IntNode(x, null);
size += 1;
}
public int size() {
return size;
}
}
6. DLLists, Arrays
Doubly Linked Lists
One downside of SLLists is that inserting at the back of an SLList is much slower than the front. We can have a last
pointer to make add
and get
operations at the back fast. However, this is insufficient to support fast remove
. We may want to add backwards links from every node.
Reverse pointers allow all operations (
add
,get
,remove
) to be fast.
This approach has an annoying special case: last
sometimes points at the sentinel, and sometimes points at a ‘real’ node.
- Solution 1: have two sentinels
- Solution 2: circular sentinel
Arrays
Arrays consist of:
- A fixed integer length (cannot change!)
- A sequence of
N
memory boxes whereN=length
, such that:- All of the boxes hold the same type of value (and have same # of bits).
- The boxes are numbered
0
throughlength-1
.
A 2D Array is an array of array addresses.
7. ALists, Resizing
Naive Array Lists
Retrieval from any position of an array is very fast. How to build an array version of a list?
AList Invariants:
- The position of the next item to be inserted is always
size
. -
size
is always the number of items in the AList. - The last item in the list is always in position
size - 1
.
public class AList {
private int[] items;
private int size;
public AList() {
items = new int[100]; size = 0;
}
public void addLast(int x) {
items[size] = x;
size += 1;
}
public int getLast() {
return items[size - 1];
}
public int get(int i) {
return items[i];
}
public int size() {
return size;
}
}
Resizing Arrays
When the array gets too full (i.e., size==items.length
), just make a new array:
public void addLast(int x) {
if (size == items.length) {
// resize(size + 1); // Unusably bad.
resize(size * RFACTOR); // Great performance.
// This is how the Python list is implemented.
}
items[size] = x;
size += 1;
}
private void resize(int capacity) {
int[] a = new int[capacity];
System.arraycopy(items, 0, a, 0, size);
items = a;
}
An AList should not only be efficient in time, but also efficient in space.
- Define the “usage ratio”
R = size / items.length
; - Half array size when
R < 0.25
.
8. Inheritance, Implements
Interface Inheritance
Our AList
and SLList
classes have the following methods (exact same method signatures for both classes). SLLists
and ALists
are both clearly some kind of “list”. List
is a hypernym of SLList
and AList
.
Interface and Implements Keywords
Use the new keyword interface
instead of class
to define a List61B
. Interface is a specification of what a List is able to do, not how to do it.
Use the implements
keyword to tell the Java compiler that SLList and AList are hyponyms of List61B.
Overriding vs. Overloading
Method signature is the method name and the number and type of its parameters.
- overriding: If a “subclass” has a method with the exact same signature as in the “superclass”, we say the subclass overrides the method.
- overloading: Methods with the same name but different signatures are overloaded.
We mark overriding methods with the @Override
annotation.
public class AList<Item> implements List61B<Item> {
...
@Override
public void addLast(Item x) {
...
Why use @Override
?
- Protects against typos. The code won’t compile if it is not actually an overriding method.
- Reminds programmer that method definition came from somewhere higher up in the inheritance hierarchy.
Even if you don’t write @Override
, subclass still overrides the method.
Interface Inheritance
Specifying the capabilities of a subclass using the implements
keyword is known as interface inheritance. Subclasses must override all of these methods. Will fail to compile otherwise. Interface inheritance is a powerful tool for generalizing code.
If X
is a superclass of Y
, then memory boxes for X
may contain Y
. An AList
is-a List
. Therefore List
variables can hold ALList
addresses.
Implementation Inheritance: Default Methods
- Interface inheritance: Subclass inherits signatures, but NOT implementation.
- Implementation inheritance: Subclasses can inherit signatures AND implementation.
- Use the
default
keyword to specify a method that subclasses should inherit from an interface.public interface List61B<Item> { ... default public void print() { for (int i = 0; i < size(); i += 1) { System.out.print(get(i) + " "); } System.out.println(); } }
- If you don’t like a default method, you can override it.
public interface SLList<Item> implements { @Override public void print() { for (Node p = sentinel.next; p != null; p = p.next) { System.out.print(p.item + " "); } System.out.println(); } }
- Use the
Static and Dynamic Type
- Compile-time type (static type): the type specified at declaration. Never changes.
- Run-time type (dynamic type): the type specified at instantiation (e.g. when using
new
).
Suppose we call a method of an object using a variable with compile-time type X
and run-time type Y
. Then if Y
overrides the method, Y
’s method is used instead.
9. Extends, Casting, Higher Order Functions
The Extends Keyword
If you want one class to be a hyponym of another class (instead of an interface), you use extends
.
RotatingSLList
We’d like to build RotatingSLList
that can perform any SLList
operation as well as rotateRight()
: Moves back item the front.
Because of extends
, RotatingSLList
inherits all members of SLList
:
- All instance and static variables.
- All methods.
- All nested classes. Constructors are not inherited.
VengefulSLList
We want to build an SLList that:
- Remembers all Items that have been destroyed by
removeLast
. - Has an additional method
printLostItems()
, which prints all deleted items.
Constructor Behavior
Constructors are not inherited. However, the rules of Java say that all constructors must start with a call to one of the super class’s constructors.
- You can explicitly call the constructor with the keyword
super
. - If you don’t explicitly call the constructor, Java will automatically do it for you.
- If you want to use a super constructor other than the no-argument constructor, can give parameters to super.
Encapsulation
Module: A set of methods that work together as a whole to perform some task or set of related tasks. A module is said to be encapsulated if its implementation is completely hidden, and it can be accessed only through a documented interface.
Implementation inheritance (e.g. extends) breaks encapsulation.
Type Checking and Casting
Compile-Time Type Checking
- Compiler allows method calls based on compile-time type of variable.
-
sl
’s runtime type:VengefulSLList
. But cannot callprintLostItems
.
-
- Compiler also allows assignments based on compile-time types.
- Even though
sl
’s runtime-type isVengefulSLList
, cannot assign tovsl2
.
- Even though
Compile-Time Types and Expressions
Expressions have compile-time types:
- An expression using the
new
keyword has the specified compile-time type.
- Method calls have compile-time type equal to their declared type.
Casting
Java has a special syntax for specifying the compile-time type of any expression: Put desired type in parenthesis before the expression.
- Compile-time type Dog:
maxDog(frank, frankJr);
- Compile-time type Poodle:
(Poodle) maxDog(frank, frankJr);
Casting is a powerful but dangerous tool. Tells Java to treat an expression as having a different compile-time type.
If we run the code above, we get a ClassCastException at runtime.
Higher Order Functions
Higher Order Function: A function that treats another function as data. e.g. takes a function as input.
and earlier)
Fundamental issue: Memory boxes (variables) cannot contain pointers to functions. Can use an interface instead. Note: The old way is still widely used, e.g. Comparators.
or Later
In Java 8, new types were introduced: now can can hold references to methods.
10. Subtype Polymorphism vs. HoFs
Subtype Polymorphism (多态)
The biggest idea of the last couple of lectures: Subtype Polymorphism. Polymorphism: “providing a single interface to entities of different types”.
Comparable
The built-in Comparable
interface is already defined and used by tons of libraries.
Comparators
Suppose we want to write a program that prints a string representation of the larger of two objects according to some specific comparison function.
11. Exceptions, Iterators, Object Methods
Exceptions
When something goes really wrong, break the normal flow of control.
Iteration
Java allows us to iterate through Lists and Sets using a convenient shorthand syntax sometimes called the “foreach” or “enhanced for” loop.
How Iteration Really Works?
To support ugly iteration:
- Add an
iterator()
method toArraySet
that returns anIterator<T>
. -
The Iterator<T>
that we return should have a usefulhasNext()
andnext()
method.
To support the enhanced for loop, we need to make ArraySet
implement the Iterable
interface.
Object Methods
toString()
The toString()
method provides a string representation of an object. The implementation of toString()
in Object is the the name of the class, then an @ sign, then the memory location of the object.
One approach to implement toString
for ArraySet
. This code is slow Adding even a single character to a string creates an entirely new string. It’s because Strings are “immutable”.
Much faster approach is shown below. Append operation for a StringBuilder
is fast.
Equals
==
compares the bits. For references, ==
means “referencing the same object.” Default implementation of .equals
uses ==
.
Implement equals
for ArraySet
.