Java Review
This is a general set of notes for Java review during a DS&A course.
Visibility modifiers
Visibility of variables allows control over the interaction of the data that is presented to the user.
public class Point {
// private variables allow control
private double x;
private double y;
// getter/setter is public for accessing variables
// can limit or give complete access, could be omitted
// to remove all access by outside entities
public double getX() { return x; }
public void setX(double x) { this.x = x; }
}
Visibility | Class and inner class | Package | Subclass | World |
---|---|---|---|---|
public | x | x | x | x |
protected | x | x | x | |
package-private (no modifier) | x | x | ||
private | x |
Constructors
These create the class object, similar to def __init__()
in Python.
public class Point {
private double x;
private double y;
// classic constructor
public Point(double x, double y) {
this.x = x; // instance variable is `this.<some var>`
this.y = y;
}
// constructor chaining
public Point(double a) {
this(a, 0); // this is chaining the constructors
// allows us to assign the value to `a`,
// creates an obj. without both vars
}
}
Value and reference equality
primitives and strings
- reference eq.: checks if 2 obj are the exact same obj in memory using
==
- the primary use of
==
for the class is going to benull
checking to avoid throwingNullPointerException
errors if we were checking anull
with.equals()
- the primary use of
- value eq.: checks if objs are equal based on user's use case and design using
.equals()
Wrapper objects
These are often used as wrappers around primitives to make them into classes. This is helpful for allowing the use of a collection.
Integer primitive = 1; // primitive
int also_primitive = 1; // primitive
Integer object = new Integer(1); // wrapped
Pass by value and reference
public static void main(String[] args) {
int count = 0;
helper(count);
System.out.println(count);
}
public static void helper(int x) {
x = x + 1;
}
main
outputs 0
because:
- pass by value: the helper method takes in a value of what was passed in. Changing the value changes the local variable, but not the original variable.
- these have two different locations in memory
- pass by reference: the helper method takes in a referene to what is passed. This changes the value on both the local and original variables.
Java is a pass by value language
// pass by reference with primitives
public static void main(String[] args) {
int count = 0; // step 1: create a new int named count
count = helper(count); // step 2: call helper on count
// step 5: assign the returned value to count (still on the line above, but after the function call)
System.out.println(count); // step 6: print the value of count
}
public static int helper(int x) {
x = x + 1; // step 3: increment the value of x
return x; // step 4: return the value of x
}
// pass by reference with objects
public static void main(String[] args) {
Container count = new Container(0); // step 1: create new Container named count
helper(count); // step 2: call helper on count
System.out.println(count.getItem()); // step 4: print the value of count
}
public static void helper(Container x) {
x.setItem(x.getItem() + 1); // step 3: set x's item to x's item + 1, modifying the original object
}
Generics
Rather than use generic Object
types that would cause us to cast when retrieving values, use generics.
// an attempt at generics that utilizes `Object`
public class Container {
private Object object;
public void set(Object object) {
this.object = object;
}
public Object get() {
return object;
}
}
// to retrieve values from the object we
// would be stuck using `cast` improperly
// we can actually assign multiple values
// of different types using this method
// which returns a `ClassCastException`
Container c1 = new Container();
c1.set("hello");
String s = (String)c1.get();
Container c2 = new Container();
c2.set("hello"); // assign
Integer i = (Integer)c2.get(); // get a `ClassCastException`
Generics would solve this issue by allowing us to pass any type we want without using a cast
.
// use the `T` type to pass a generic type parameter
// we can reuse `T` to refer to that same type
public class Container<T> {
private T t;
public void set(T t) {
this.t = t;
}
public T get() {
return t;
}
}
// the previous example is now updated to this
Container<String> c1 = new Container<String>();
c1.set("hello");
String s = c1.get();
Container<String> c2 = new Container<String>();
c2.set("hello");
Integer i = (Integer)c2.get(); // the error is now recognized at compile
Generics syntax
Declaring a generic class:
- have one or more type parameters in angle brackets
class ArrayList<T> { ... }
- instance declaration can omit the second type argument
// this can have the second `Integer` removed
ArrayList<Integer> listOne = new ArrayList<Integer>();
// is the same as above
ArrayList<String> listTwo = new ArrayList<>();
Generic arrays
We usually will create arrays of generic types, this uses the Object
class.
// creates a new array of `Objects` that is
// cast to our generic type
T[] backingArray = (T[]) new Object[10];
Multiple types
Having multiple general types is handled similarly.
class HashMap<K, V> { ... }
Hashmap<String, Integer> map = new HashMap<>();
Iterables
Iterable
allows something to be iterated obver either by
a return from Iterator
from the iterator()
method or by
a for-each loop (which uses Iterator
).
// example 1
List<String> structures = new ArrayList<>();
structures.add("BST");
structures.add("HashMap");
structures.add("Graph");
// simple for each loop that iterates through the ArrayList "structures"
for (String structure: structures) {
System.out.println("I love " + structure + "s!");
}
// example 2
List<String> foods = new LinkedList<>();
structures.add("pasta");
structures.add("pizza");
structures.add("soup");
// this for-each loop is more efficient...
for (String food: foods) {
System.out.println("I love eating " + food + "!");
}
// ...rather than this regular for loop
for (int i = 0; i < foods.size(); i++) {
String food = foods.get(i); // this line requires Java to internally iterate through a portion of the linkedlist again
System.out.println("I love eating " + food + "!");
}
Iterable
- contained in the
java.lang
package - allows for direct control of the object iteration for custom behavior
- depends on data structure and
Iterator
Iterator interface
- an obj. that implements the
Iterator
interface directly we have to override thenext()
andhasNext()
methods - this uses some cursor variable to keep track of iteration progress
- belongs to
java.util
- abstracts control for convenience/generic needs
- has no dependencies besides data structure to iterate
Implementing Iterable
When implementing Iterable
we can provide a layer of abstraction between
the class and the iteration.
import java.util.Iterator;
public class BookList<Book> implements Iterable<Book> {
// implement
public Iterator<Book> iterator() { ... }
}
// create some books `bookList`
// alternative for-each loop:
for (Book book : bookList) {
System.out.println(book);
}
Comparable and comparator
These are tools that are used to compare objects, which are independent of each other.
- Comparable: An object using
Comparable
can compare itselt usingcompareTo()
to establish a
"natural ordering" based on the return values.
- Comparator: An object that defines a custom "ordering" scheme different than the natural
ordering of the comparable compareTo()
method. This does things like >, <, etc.
public class Student {
String name;
int age;
String major;
}
// some comparator examples could be
// NameComparator
// x < y based on alphabetical
// AgeComparator
// define x < y based on age
// MajorComparator
// x < y if major based on alphabetical
- Note: The
collections.sort()
allows us to pass aComparator
object along with a collection of data to customize
how we sort the data.
Comparator
There are a set of different implementations, either within a class or outside.
import java.util.Comparator;
public class HDTV implements Comparable<HDTV> {
private int size;
private String brand;
public int getSize() { return size; }
public String getBrand() { return brand; }
// we can implement this directly inside
// the class so we don't use getter/setter
public int compareTo(HDTV tb) {
if (size < tv.size) { return -1; }
else if (size > tv.size) { return 1; }
else { return 0; }
}
}
// alternative implementation
// create a Comparator object
class SizeComparator implements Comparator<HDTV> {
public int compare(HDTV tv1, HDTV tv2) {
return tv1.getSize() - tv2.getSize();
}
}
Comparable
Comparable
is independent of the Comparator
.
Comparator
is useful for defining a different ordering scheme than that of the
default ordering scheme. * This is often for sorting things in different orders to customize how things are sorted.
// Comparable example
public class HDTV implements Comparable<HDTV> {
private int size;
private String brand;
public int getSize() { return size; }
public String getBrand() { return brand; }
// we can compare in a few different ways
// we'll compare based on size for this example
public int compareTo(HDTV tv) {
return size - tv.size;
}
}