06/02/2021 16:57 | Category: java

Tags: javaoop

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 be null checking to avoid throwing NullPointerException errors if we were checking a null with .equals()
  • 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 Iteratorfrom 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 the next() and hasNext() 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 using compareTo() 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 a Comparator 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;
    }

}