Posts Tagged - java11

Generics and Collections

Using Method References

Like lambdas, they make code easier to read.

We have a functional interface

@FunctionalInterface
public interface LearnToSpeak {
	void speak(String sound);
}

public class DuckHelper {
	public static void teacher(String name, LearnToSpeak trainer) {
		trainer.speak(name);
	}
}

public class Duckling {
	public static void makeSound(String sound) {
		LearnToSpeak learner = s -> System.out::println;
		DuckHelper.teacher(sound, learner);
	}
}

There’re four formats for methods references

Calling Static Methods

The Collection class has a staic method that can be used for sorting. The Consumer functional interface takes one param and does not return anthing.

Consumer<List<Integer>> methodRef = Collections::sort;
Consumer<List<Integer>> lambda = x -> Collections.sort(x);

Calling Instance Methods on a Particular Object

The String class has a startsWith() method that takes one param and returns a boolean.

var str = "abc";
Predicate<String> methodRef = str::startsWith;
Predicate<String> lambda = s -> str.startsWith(s);

A method reference doesn’t have to take any parameters. For the next example, we use a Supplier, which takes zero params and returns a value.

var random = new Random();
Supplier<Integer> methodRef = random::nextInt;
Supplier<Integer> lambda = () -> random.nextInt();

Calling Instance Methods on a Parameter

We call an instance method that doesn’t take any params. The trick is that we do so without knowing the instance in advance.

Predicate<String> methodRef = String::isEmpty;
Predicate<String> lambda = s -> s.isEmpty();

It looks like a static method, but it isn’t.

You can even combine the two types of instance method references with a BiPredicate, which takes two params and returns a boolean.

BiPredicate<String, String> methodRef = String::startsWith;
BiPredicate<String, String> lambda = (s, p) -> s.startsWith(p);

Calling Constructors

A constructor reference is a special type of method reference that uses new instead of a method, and it instantiates an object. It’s common for a constructor reference to use a Supplier as shown here.

Supplier<List<String>> methodRef = ArrayList::new;
Supplier<List<String>> lambda = () -> new ArrayList();

Number of Params in a Method Reference

A method reference can look the same, even when it will behave differently based on the surrounding context.

Given the following method

public class Penguin {
	public static Integer countBabies(Penguin... cuties) {
		return cuties.length;
	}
}

The method can be interpreted in three ways, for zero or more values.

// no param
Supplier<Integer> methodRef = Penguin::countBabies;
Supplier<Integer> lambda = () -> Penguin.countBabies();

// one param
Function<Penguin, Integer> methodRef = Penguin::countBabies;
Function<Penguin, Integer> lambda = (x) -> Penguin.countBabies(x);

// multiple params
BiFunction<Penguin, Penguin, Integer> methodRef = Penguin::countBabies;
BiFunction<Penguin, Penguin, Integer> lambda =
	(x, y) -> Penguin.countBabies(x, y);

Using Lists, Sets, Maps, and Queues

A collection is a group of objects contained in a single object, There are four main interfaces.

  • List - Ordered collection of elements that allows duplicate entries. It can be accesed by an int index.
  • Set - collection that does not allow duplicate entries.
  • Queue - Collection that orders its elements in an specific order for processing. A typical queue is FIFO or LIFO.
  • Map - Collection that maps keys to values, with no duplicate keys allowed. The elements in it are key/value pairs.

(add image of Collections hierarchy)

Common Collections Methods

add()

Inserts a new element into the Collection and returns whether it was successful.

boolean add(E element)

Usage

Collection<String> list = new ArrayList<>();
sout(list.add("one")); // true
sout(list.add("one")); // true

Collection<String> set = new HashSet<>();
sout(set.add("one")); // true
sout(set.add("one")); // false

remove()

Removes a single matching value and returns whether it was successful.

boolean remove(Object object)

Usage

Collection<String> birds = new ArrayList<>();
birds.add("hawk");
birds.add("hawk");
sout(birds.remove("cardinal"));  // false
sout(birds.remove("hawk")); // true
sout(birds); // hawk
Deleting while Looping

Java does not allow removing elements from a list while using the enhanced for loop.

Collection<String> birds = new ArrayList<>();
birds.add("hawk");
birds.add("hawk");
birds.add("hawk");

for(String bird : birds) {
	birds.remove(bird); // ConcurrentModificationException
}

isEmpty() and size()

They look at how many elements are in the Collection.

boolean isEmpty()
int size()

clear()

It provides an easy way to discard all elements of the Collection.

void clear()

How to use it

Collection<String> birds = new ArrayList<>();
birds.add("hawk"); [hawk]
birds.add("hawk"); [hawk, hawk]
birds.clear(); []

contains()

It checks whether a certain value is in the Collection.

boolean contains(Object object)

Usage

 Collection<String> birds = new ArrayList<>();
 birds.add("hawk");
 sout(birds.contains("hawk")); // true
 sout(birds.contains("robin")); // false

removeIf()

It removes all elements that match a condition.

boolean removeIf(Predicate<? super E> filter)

It uses a Predicate.

Collection<String> list = new ArrayList<>();
list.add("Magician"); [Magician]
list.add("Assistant"); [Magician, Assistant]
list.removeIf(s -> s.startsWith("M")); [Assistant]

forEach()

We use it to loop through a Collection.

void forEach(Consumer<? super T> action)

Usage

Collection<String> cats = Arrays.asList("Annie", "Ripley");
cats.forEach(System.out::println);

Using the List Interface

You use a list when you want an ordered collection that can contain duplicate entries.
Items can be retrieved and inserted at specific positions in the list, based on an int index. Unlike an array, many List implementations can change in size after they’re declared.

Comparing List Implementations

An ArrayList is like a resizable array. When elements are added, it automatically grows.
Its main benefit, is that you can look up any element in constant time. Adding or removing an element is slower than accessing an element. It’s a good choice when you’re reading more often than writing.

A LinkedList is special because it implements both List and Queue. It has all the methods of a List. It also has additional methods to facilitate adding or removing from the beginning and/or end of the list.
It’s mein benefits are that you can access, add, and remove from the beginning and end of the list. The trade-off is that dealing with an arbitrary index takes linear time. This makes it a good choice when you’ll be using it as a Queue.

Creating a List with a Factory

There’re a few methods that let you create a List back, but don’t know the type of it.

Method Description Can add elements? replace? delete?
Arrays.asList(varargs) Returns fixed size list backed by an array no yes no
List.of(varargs) Returns immutable list no no no
List.copyOf(collection) Returns immutable list with copy of original collection’s values no no no

Immutable lists throw an UnsupportedOperationException when adding or removing a value.

Working with List Methods

This methods are for working with indexes.

Method Description
boolean add(E element) Adds element to end
void add(int index, E element) Adds element at index
E get(int index) Returns element at index
E remove(int index) Removes element at index
void replaceAll(UnaryOperator<E> op) Replaces each element in the list with the result of the operator
E set(int index, E e) Replaces element at index and returns original. Throws IndexOutOfBoundsException if the index is larger than maximum.
List<String> list = new ArrayList<>();
list.add("SD"); // [SD]
list.add(0, "NY"); // [NY, SD]
list.set(1, "FL"); // [NY, FL]
sout(list.get(0)); // NY
list.remove("NY"); // [FL]
list.remove(0); // []
list.set(0, "?"); // IndexOutOfBoundsException

Now, let’s look at using the replaceAll() method. It takes a UnaryOperator that takes one parameter and returns a value of the same type.

List<Integer> numbers = Arrays.asList(1, 2, 3);
numbers.replaceAll(x -> x*2); // [2, 4, 6]

Using the Set Interface

You use a set when you don’t want to allow duplicate entries and you aren’t concerned with the order.

Comparing Set Implementations

A HashSet stores its elements in a hash table, which means the keys are a hash and the values are an Object. This means that it uses the hashCode() method of the objects to retrieve them more efficiently.
The main benefit is that adding elements and checking whether an element is in the set both have constant time. The trade-off is that you lose the order in which you inserted the elements.

A TreeSet stores its elements in a sorted tree structure. The main benefit is that the set is always in sorted order. The trade-off is that adding and checking whether an element exists take longer than with a HashSet.

Working with Set Methods

Like List, you can create an immutable Set in one line or make a copy of an existing one.

Set<Character> letters = Set.of('z', 'o', 'o');
Set<Character> copy = Set.copyOf(letters);
Set<Integer> set = new HashSet<>();
boolean b1 = set.add(66); // true
boolean b2 = set.add(10); // true
boolean b3 = set.add(66); // false
boolean b4 = set.add(8); // true

Using the Queue Interface

You use a queue when elements are added and removed in a specific order. They’re typically used for sorting elements prior to processing them.

Comparing Queue Implementations

We use LinkedList, as in addition to being a list, it’s a double-ended queue. It’s different from a regular queue in that you can insert and remove elements from both the front and back of the queue. It’s main benefit is that it implements both List and Queue interfaces. The trade-off is that it isn’t as efficient as a pure queue.

Working with Queue Methods

|Method|Description|Throws exception on failure| |:—:|:—:|:—:| |boolean add(E e)|Adds an element to the back of the queue and returns true or throws exception|Yes| |E element()|Returns next element or throws exception if empty|yes| |boolean offer(E e)|Adds an element to the back of the queue and returns if it was successful|No| |E remove()|Removes and returns next element or throws exception if empty|yes| |E poll()|removes and returns next element or returns null if empty|no| |E peek()|returns next element or returns null is empty|no|

Queue<Integer> queue = new LinkedList<>();
queue.offer(10); // true
queue.offer(4); // true
queue.peek(); // 10
queue.poll(); // 10
queue.poll(); // 4
queue.peek(); // null

Using the Map Interface

You use a map when you want to identify values by a key. All Map classes have keys and values.

Comparing Map Implementations

A HahsMap stores the keys in a hash table. This means that it uses the hashCode() method of the keys to retrieve their values more effitiently.
The main benefit is that adding elements and retrieving the element by key both have constant time. The trade-off is that you lose the order in which you inserted the elements.

A TreeMap stores the keys in a sorted tree structure. The main benefit is that keys are always in a sorted order. The trade-off is that adding and checking whether a key is present takes longer as the tree grows larger.

Working with Map Methods

|Method|Description| |:—:|:—:| |void clear()|Removes all keys and values| |boolean containsKey(Object key)|Returns whether key is in map| |boolean containsValue(Object value)|Returns whether value is in map| |Set<Map.Entry<K,V>> entrySet()|Returns a Set of key/value pairs| |void forEach(BiConsumer(K key, V value))|Loop through each key/value pair| |V get(Object key)|Returns the value mapped by key or null| |V getOrDefault(Object key, V defaultValue)|Returns the value mapped by the key or the default value| |boolean isEmpty()|Returns whether the map is empty| |Set<K> keySet()|Returns set of all keys| |V merge(K key, V value, Function(<V, V, V> func))|Sets value if key is not set. Runs the function if the key is set to determine the new value. Removes if null| |V put(K key, V value)|Adds or replaces key/value pair. Returns previous value or null| |V putIfAbsent(K key, V value)|Adds value if key not present and returns null. Otherwise, returns existing value.| |V remove(Object key)|Removes and returns value mapped to key or null| |V replace(K key, V value)|Replaces the value for a given key if the key is set or null| |void replaceAll(BiFunction<K,V, V> func)|Replaces each value with the results of the function| |Collection<V> values()|Returns Collection of all values|

forEach() and entrySet()

The map version has two parameters - key and value.

Map<Integer, Character> map = new HashMap<>();
map.put(1, 'a');
map.put(2, 'b');
map.put(3, 'c');
map.forEach((k, v) -> sout(v))
map.values().forEach(System.out::println);
map.entrySet().forEach(e -> sout(e.getKey() + e.getValue());
replace() and replaceAll()
Map<Integer, Integer> map = new HashMap<>();
map.put(1, 2);
map.put(2, 4);
Integer original = map.replace(2, 10); // 4
map.replaceAll((k, v) -> k + v); // {1=3, 2=12}
putIfAbsent()

This sets a value but skips it if the value is already set to a non-null value.

merge()

It adds logic of what to choose.

The next example takes two String as input, and a BiFunction which is the logic. In this case it takes the longest String.

BiFunction<String, String, String> mapper = (v1, v2) -> v1.length() > v2.length ? v1 : v2;

Map<String, String> favorites = new HashMap<>();
favorites.put("Jenny", "Bus Tour");
favorites.put("Tom", "Tram");

String jenny = favorites.merge("Jenny", "Skyride", mapper);
String tom = favorites.merge("Tom", "Skyride", mapper);
// {Tom = Skyride, Jenny = Bus Tour}

(for more information on merge() check page 130 of 2nd book)

Sorting Data

(!) Comparable and Comparator are similar enough to be tricky. The exam likes to see if it can trick you into mixing the two. (!)

Creating a Comparable Class

Comparable has just one Method.

public interface Comparable<T> {
	int compareTo(T o);
}

As it implements T, any object can be Comparable.

public class Duck implements Comparable<Duck> {
	private String name;

	public Duck(String name) {
		this.name = name;
	}

	public int compareTo(Duck d) {
		return name.compareTo(d.name); // sorts ascendingly by name
	}

	public static void main(String[] args) {
		var ducks = new ArrayList<Duck>();
		duck.add(new Duck("Quack"));
		duck.add(new Duck("Puddles"));
		Collections.sort(duck); // sort by name
	}

}

Here, Duck implements compareTo(). Since Duck is comparing objects of type String, and it already has a compareTo() method, it can just delegate.

There are three rules to know about the compareTo()

  • 0 is returned, when the current object is equivalent to the argument to compareTo()
  • A negative number, when the current object is smaller than the argument to compareTo()
  • A positive number, when the current object is larger than the argument to compareTo()
public class Animal implements Comparable<Animal> {
	private int id;

	public int compareTo(Animal a) {
		return id - a.id; // sorts ascending by id
	}

	public static void main(String[] args) {
		var a1 = new Animal();
		var a2 = new Animal();
		a1.id = 5;
		a2.id = 7;
		sout(a1.compareTo(a2)); // -2
		sout(a1.compareTo(a1)); // 0
		sout(a2.compareTo(a1)); // 2
	}
}

Casting the compareTo() Argument

When dealing with legacy code or code that does not use generics, the compareTo() method requires a cast since it’s passed an Object.

public class LegacyDuck implements Comparable {
	private String name;

	public int compareTo(Object obj) {
		LegacyDuck d = (LegacyDuck) obj; // cast because no generics
		return name.compareTo(d.name);
	}
}

Checking for null

When writing your own compare methods, you should check the data before comparing it, if it’s not validated ahead of time.

public class MissingDuck implements Comparable<MissingDuck> {
	private String name;

	public int compareTo(MissingDuck quack) {
		if(quack == null) {
			throw new IllegalArgumentException("Poorly formed duck!");
		}

		if(this.name == null && quack.name == null) {
			return 0;
		} else if(this.name == null) {
			return -1;
		} else if(quack.name == null) {
			return 1;
		} else {
			return name.compareTo(quack.name);
		}
	}
}

Keeping compareTo() and equals() Consistent

The compareTo() method returns 0 if two objects are equal, while equals() returns true if two objects are equal.

Comparing Data with a Comparator

Sometimes you want to sort an object that did not implement Comparable, or you want to sort objects in different ways at different times.

Comparator is a functional interface.

public class Duck implements Comparable<Duck> {

private String name;
	private int weight;

	// constructor
	// getters & setters...

	public String toString() {
		return name;
	}

	public int compareTo(Duck d) {
		return name.compareTo(d.name);
	}

	public static void main(String[] args) {
		var ducks = new ArrayList<Duck>();
		ducks.add(new Duck("Quack", 8));
		ducks.add(new Duck("Puddles", 10));

		// Comparator<Duck> byWeight = (d1, d2) -> d1.getWeight() - d2.getWeight();
		Comparator<Duck> byWeight = Comparator.comparing(Duck::getWeight);
		Collections.sort(ducks); // [Puddles, Quack]
		Collections.sort(ducks, byWeight); // [Quack, Puddles]
	}

}

(!) Comparable can be used without an import statement. Comparator cannot (!)

Comparing Comparable and Comparator

|Difference|Comparable|Comparator| |:—:|:—:|:—:| |Package name|java.lang|java.util| |Interface must be implemented by class comparing?|Yes|No| |Method name in interface|compareTo()|compare()| |Number of parameters|1|2| |Common to declare using a lambda|No|Yes|

(!) The exam will try to trick you by mixing up the two and seeing if you can catch it. It also may try to trick you with the methods’ names. Pay attention when you see Comparator and Comparable in questions (!)

var byWeight = new Comparator<Duck>() { // DOES NOT COMPILE
	public int compareTo(Duck d1, Duck d2) {
		return d1.getWeight() - d2.getWeight();
	}
}

The previous code doesn’t compile. The method name is wrong. A Comparator must implement a method named compare().

Comparing Multiple Fields

When comparing multiple instance variables, the code gets messy.

public class Squirrel {
	private int weight;
	private String species;

	// constructors
	// getters / setters
}

We want to sort first by species name, then if they are the same species, by weight.

public class MultiFieldComparator implements Comparator<Squirrel> {
	public int compare(Squirrel s1, Squirrel s2) {
		int result = s1.getSpecies().compareTo(s2.getSpecies());
		if(result != 0) {
			return result;
		}

		return s1.getWeight() - s2.getWeight();
	}
}

This works assuming no null values.

Alternatively, we can use method references and build the comparator. This does the same

Comparator<Squirrel> c = Comparator.comparing(Squirrel::getSpecies)
	.thenComparingInt(Squirrel::getWeight);

Suppose we want to sort in descending order by species.

var c = Comparator.comparing(Squirrel::getSpecies).reversed();
Helper static methods for Comparator

|Method|Description| |:—:|:—:| |comparing(function)|Compare by the results of a function that returns any Object| |comparingDouble(function)|Compare by the results of a function that returns a double| |comparingInt(function)|Compare by the results of a function that returns an int| |comparingLong(function)|Compare by the results of a function that returns a long| |naturalOrder()|Sort using the order specified by the Comparable implementation on the object itself| |reverseOrder()|Sort using the reverse of the order specified by the Comparable interface on the object itself|

Helper default methods for building a Comparator

|Method|Description| |:—:|:—:| |reversed()|Reverse the order of the chained Comparator| |thenComparing(function)|If the previous Comparator returns 0, use this comparator that returns an Object| |thenComparingDouble(function)|If the previous Comparator returns 0, use this comparator that returns a double| |thenComparingInt(function)|If the previous Comparator returns 0, use this comparator that returns an int| |thenComparingLong(function)|If the previous Comparator returns 0, use this comparator that returns a long|

Sorting and Searching

Now that we’ve learned about Comparable and Comparator, we can finally do something useful, like sorting. The Collections.sort() method uses the compareTo() method to sort. It expects the objects to be sorted to be Comparable.

public class SortRabbits {
	static class Rabbit { int id; }

	public static void main(String[] args) {
		List<Rabbit> rabbits = new ArrayList<>();
		rabbits.add(new Rabbit());
		Collections.sort(rabbits); // DOES NOT COMPILE
	}
}

(!) This doesn’t compile, because Rabbit is not Comparable. You can fix this by passing a Comparator to sort()(!)

public class SortRabbits {
	static class Rabbit { int id; }

	public static void main(String[] args) {
		List<Rabbit> rabbits = new ArrayList<>();
		rabbits.add(new Rabbit());
		Comparator<Rabbit> c = (r1, r2) -> r1.id -> r2.id;
		Collections.sort(rabbits, c);
	}
}

The sort() and binarySearc() methods allow you to pass in a Comparator object, when you don’t want to use natural order.

Reviweing binarySearch()

The binarySearch() requires a sorted List.

List<Integer> list = Arrays.asList(6, 9, 1, 8);
Collections.sort(list); // [1, 6, 8, 9]
Collections.binarySearch(list, 6); // 1
Collections.binarySearch(list, 3); // -2

(I left info out, more on this at page 139)

Working with Generics

Generic Classes

The syntax for introducing a generic is to declare a formal type parameter in angle brackets.

public class Crate<T> {
	private T contents;

	public T emptyCrate() {
		return contents;
	}

	public void packCrate(T contents) {
		this.contents = contents;
	}
}

This are the naming conventions for generics.

Name Usage
E Element
K Map key
V Map Value
N number
T generic data type
S, U, V… multiple generic types
Elephant elephant = new Elephant();
Crate<Elephant> crateForElephant = new Crate<>();
crateForElephant.packCrate(elephant);
Elephant inNewHome = crateForElephant.emptyCrate();

Generic classes aren’t limited to having a single type parameter.

public class SizeLimitedCrate<T, U> {
	private T contents;
	private U sizeLimit;

	// constructor
}
Elephant elephant = new Elephant();
Integer numPounds = 15_000;
SizeLimitedCrate<Elephant, Integer> c1 = new SizeLimitedCrate<>(elephant, numPounds);

Generic Interfaces

Just like a class, an interface can declare a formal type parameter.

public interface Shippable<T> {
	void ship(T t);
}
class ShippableRobotCrate implements Shippable<Robot> {
	public void ship(Robot t) {}
}
class ShippableAbstractCrate<U> implements Shippable<U> {
	public void ship(U t);
}

Raw Types

The final way is to not use generics at all. This is the old way of writing code. It generates a compiler warning, but it does compile.

class ShippableCrate implements Shippable {
	public void ship(Object t) {}
}

Generic Methods

This is often useful for static methods since they aren’t part if an instance that can declare the type.

public class Handler {
	public static <T> void prepare(T t) {}

	public static <T> Crate<T> ship(T t) {}
}

(!) Unless a method is obtaining the type from the class/interface, it’s specified immediately before the return type of the method. (!)

public class More {
	public static <T> void sink(T t) {}
	public tatic <T> T identity(T t) { return t; }
	public static T noGood(T t) { return t; } // DOES NOT COMPILE
}

When you have a method declare a generic param type, it’s independent of the class generics.

public class Crate<T> {
	public <T> T tricky(T t) {
		return t;
	}
}
public static String createName() {
	Crate<Robot> crate = new Crate<>();
	return crate.tricky("bot");
}

Bounding Generic Types

(more on this on page 145~ from 2nd book)

They restrict what types can be used in a wildcard. A wildcard generic type is an unknown generic type represented with a question mark ?.

Type of bound Syntax Example
Unbounded ? List<?> a = new ArrayList<String>();
Upper bound ? extends type List<? extends Exception> a = new ArrayList<RuntimeException>();
Lower bound ? super type List<? super Exception> a = new ArrayList<Object>();

Unbounded Wildcards

An unbounded wildcard represents any data type. You use ? when you want to specify that any type is okay.

public static void printList(List<?> list) {
	for(Object x : list)
		sout(x);
}

public static void main(String[] args) {
	List<String> keywords = new ArrayList<>();
	keywords.add("java");
	printList(keywords);
}

These two statements are not equivalent

List<?> x1 = new ArrayList<>();
var x2 = new ArrayList<>();

The first is of type List, while the second is of type ArrayList. Also, we can only assign x2 to a List<Object>.

Upper-Bounded Wildcards

(!) A generic type can’t juse use a subclass, instead they use wildcards. (!)

ArrayList<Number> list = new ArrayList<Integer>(); // DOES NOT COMPILE
// instead, use
List<? extends Number> list = new ArrayList<Integer>();

The upper-bounded wildcard says that any class that extends Number or Number itself can be used as a formal param type.

public static long total(List<? extends Number> list) {
	long count = 0;
	for(Number number : list) {
		count += number.longValue();
	}
	return count;
}

When we work with upper bounds or unbounded wildcards, the list becomes logically immutable and therefore cannot be modified.

static class Sparrow extends Bird {}
static class Bird {}

public static void main(String[] args) {
	List<? extends Bird> birds = new ArrayList<Bird>();
7:	birds.add(new Sparrow()); // DOES NOT COMPILE
8:	birds.add(new Bird()); // DOES NOT COMPILE
}

The problem stems from the fact that Java doesn’t know what type List<? extends Bird> really is. It could be List<Bird> or List<Sparrow>.
Line 7 doesn’t compile because we can’t add a Sparrow to List<? extends Bird>.
Line 8 doesn’t compile because we can’t add a Bird to List<Sparrow>.

(!) Upper bounds are like anonymous classes in that they use extends regardless of whether we’re working with a class or an interface. (!)

Lower-Bounded Wildcards

I want to give a method a param which may be a List<String> or List<Object>.

We have the following “solutions”

  Method compiles Can pass List<String> Can pass List<Object>
List<?> No (unbounded generics are immutable) Yes Yes
List<? extends Object> No (upper-bounded generics are immutable) Yes Yes
List<Object> Yes No (with generics, must pass exact match) Yes

To solve this problem, we need to use a lower bound.

public static void addSound(List<? super String> list) {
	list.add("quack");
}

With a lower bound, we are telling Java that the list will be a list of String objects or a list of some objects that are superclass of String.

(check again page 148 from 2nd book. there’s more than this)

Summary

A method reference is a compact syntax for writing lambdas that refer to methods. There are four types:

  • static methods
  • instance methods on a particular object
  • instance methods on a parameter
  • constructor references.

Each primitive class has a corresponding wrapper class. For example, long’s wrapper class is Long. Java can automatically convert between primitive and wrapper classes when needed. This is called autoboxing and unboxing. Java will use autoboxing only if it doesn’t find a matching method signature with the primitive. For example, remove(int n) will be called rather than remove(Object o) when called with an int.

The diamond operator <> is used to tell Java that the generic type matches the declaration without specifying it again. The diamond operator can be used for local variables or instance variables as well as one-line declarations.

The Java Collections Framework includes four main types of data structures: lists, sets, queues, and maps.
The Collection interface is the parent interface of List, Set, and Queue. The Map interface does not extend Collection. You need to recognize the following:

  • List: An ordered collection of elements that allows duplicate entries
    • ArrayList: Standard resizable list
    • LinkedList: Can easily add/remove from beginning or end
  • Set: Does not allow duplicates
    • HashSet: Uses hashCode() to find unordered elements
    • TreeSet: Sorted. Does not allow null values
  • Queue: Orders elements for processing
    • LinkedList: Can easily add/remove from beginning or end
  • Map: Maps unique keys to values
    • HashMap: Uses hashCode() to find keys
    • TreeMap: Sorted map. Does not allow null keys

The Comparable interface declares the compareTo() method. This method returns a negative number if the object is smaller than its argument, 0 if the two objects are equal, and a positive number otherwise. The compareTo() method is declared on the object that is being compared, and it takes one parameter. The Comparator interface defines the compare() method. A negative number is returned if the first argument is smaller, zero if they are equal, and a positive number otherwise. The compare() method can be declared in any code, and it takes two parameters. Comparator is often implemented using a lambda.

The Arrays and Collections classes have methods for sort() and binarySearch(). Both take an optional Comparator parameter. It is necessary to use the same sort order for both sorting and searching, so the result is not undefined.

Generics are type parameters for code. To create a class with a generic parameter, add <T> after the class name. You can use any name you want for the type parameter. Single uppercase letters are common choices.

Generics allow you to specify wildcards. <?> is an unbounded wildcard that means any type. <? extends Object> is an upper bound that means any type that is Object or extends it. <? extends MyInterface> means any type that implements MyInterface. <? super Number> is a lower bound that means any type that is Number or a superclass. A compiler error results from code that attempts to add an item in a list with an unbounded or upper-bounded wildcard.

Exam Essentials

Translate method references to the “long form” lambda. Be able to convert method references into regular lambda expressions and vice versa. For example, System.out::print and x -> System.out.print(x) are equivalent. Remember that the order of method parameters is inferred for both based on usage.

Use autoboxing and unboxing. Autoboxing converts a primitive into an Object. For example, int is autoboxed into Integer. Unboxing converts an Object into a primitive. For example, Character is autoboxed into char.

Pick the correct type of collection from a description. A List allows duplicates and orders the elements. A Set does not allow duplicates. A Queue orders its elements to facilitate retrievals. A Map maps keys to values. Be familiar with the differences of implementations of these interfaces.

Work with convenience methods. The Collections Framework contains many methods such as contains(), forEach(), and removeIf() that you need to know for the exam. There are too many to list in this paragraph for review, so please do review the tables in this chapter.

Differentiate between Comparable and Comparator. Classes that implement Comparable are said to have a natural ordering and implement the compareTo() method. A class is allowed to have only one natural ordering. A Comparator takes two objects in the compare() method. Different Comparators can have different sort orders. A Comparator is often implemented using a lambda such as (a, b) -> a.num – b.num.

Write code using the diamond operator. The diamond operator (<>) is used to write more concise code. The type of the generic parameter is inferred from the surrounding code. For example, in List<String> c = new ArrayList<>(), the type of the diamond operator is inferred to be String.

Identify valid and invalid uses of generics and wildcards. <T> represents a type parameter. Any name can be used, but a single uppercase letter is the convention. <?> is an unbounded wildcard. <? extends X> is an upper-bounded wildcard and applies to both classes and interfaces. <? super X> is a lower-bounded wildcard.

Read More

Oracle 1Z0-819 (Java11) Certification - Index

The new 1Z0-819 certification is the combination of the old existing ones (1Z0-815 & 1Z0-816) together.

OCP Java SE 11 Programmer I - Study guide for 1Z0-815

Welcome to Java
Java Building Blocks
Java Operators
Making Decisions
Core Java APIs
Lambdas and Functional Interfaces
Methods and Encapsulation
Class Design
Advanced Class Design
Exceptions
Java Modules

OCP Java SE 11 Programmer II - Study guide for 1Z0-816

Java Fundamentals
Java Annotations
Generics and Collections

Read More

Java Annotations

Introducing Annotations

Annotations are all about metadata.

Purpose of Annotations

The purpose of an annotation is to assign metadata attributes to classes, methods, variables and other Java types.

They function a lot like interfaces. They allow us to mark a class, without chaning its inheritance structure. While interfaces can be applies only to classes, annotations can be applied to classes, methods, expressions and other annotations. Unlike interfaces, annotations allow us to pass a set of values where they’re applied.

public class Veterinarian {
	@ZooAnimal(habitat="Infirmary")
	private Lion sickLion;

	@ZooAnimal(habitat="Safari")
	private Lion healthyLion;

	@ZooAnimal(habitat="Special Enclosure")
	private Lion blindLion;
}

This class defines three variables, each with an associated habitat value. The habitat is part of the type declaration of each variable, not an individual object.

public class Lion {
	@ZooSchedule(hours={"9am", "5pm", "10pm"})
	void feedLions() {
		sout("time to feed the lions!");
	}
}

public class Peacock {
	@ZooSchedule(hours={"4pm", "5pm"})
	void cleanPeacocksPen() {
		sout("time to sweep up!");
	}
}

With this approach, the task and its schedule are defined right next to each other.

Rules of annotations

  1. Annotations function a lot like interfaces. (They allow us to mark a class without changing its inheritance structure)
  2. Annotations establish relationships that make it easier to manage data about our application.
  3. An annotation ascribes custom information on the declaration where it’s defined. (The same annotation can often be applied to completely unrelated classes or variables)
  4. Annotations are optional metadata and by themselves do not do anything. (You have to be able to take a project filled with thousands of annotations, remove all of them, and it will still compile and run. Just with potentially different behaviour at runtime)
  5. To use an annotation, all required values must be provided
  6. To declare a @Repeteable annotation, you must define a containing annotation type value

Creating Custom Annotations

Creating an Annotation

We use the @interface annotation to declare one. They’re commonly defined in their own file as a top-level type, although they can be defined inside a class declaration like an inner class.

public @interface Exercise {}

When declaring an annotation, any element without a default value, is considered required.

We use this interface like this

@Exercise() public class Cheetah {}

@Exercise public class Sloth {}

@Exercise public class ZooEmployee {}

Like interfaces, annotations can be applied to unrelated classes.
When using an annotation, parentheses are optional. Once we start adding elements, though, they’re required if the annotation includes any values.

If an annotation is declared on a line by itself, then it applies to the next nonannotation type found on the proceeding lines. This applies when there’re multiple annotations.

@Scaley		@Flexible
@Food("insect") public class Snake {}

Specifying a Required Element

An annotation element is an attribute that stores values about the particular usage of an annotation.

public @interface Exercise {
	int hoursPerDay();
}

Let’s check the usage

@Exercise(hoursPerDay=3) public class Cheetah {}
@Exercise hoursPerDay=3 public class Sloth {} // DOES NOT COMPILE
@Exercise public class ZooEmployee {} // DOES NOT COMPILE

The Sloth class does not compile because it’s missing parentheses.
The last one does not compile neither because hoursPerDay is required.

Providing an Optional Element

For an element to be optional, rather than required, it must include a default value.

public @interface Exercise {
	int hoursPerDay();
	int startHour() default 6;
}

Let’s apply it.

@Exercise(startHour=5, hoursPerDay=3) public class Cheetah {}
@Exercise(hoursPerDay=0) public class Sloth {}

@Exercise(hoursPerDay=7, startHour="8") // DOES NOT COMPILE
public class ZooEmployee {}

The order for each element does not matter.

The last one does not compile because the value type is invalid.

The default value of an annotation cannot be just any value. It has to be a non-null constant expression.

public @interface BadAnnotation {
	String name() default new String(""); // DOES NOT COMPILE
	String address() default "";
	String title() default null; // DOES NOT COMPILE
}

Selecting an Element type

An annotation element cannot be declared with just any type.
It has to be a

  • primitive
  • String
  • Class
  • enum
  • another annotation
  • an array of these types
public class Bear {}

public enum Size { SMALL, MEDIUM, LARGE }

public @interface Panda {
	Integer height(); // DOES NOT COMPILE
	String[][] generalInfo(); // DOES NOT COMPILE
	Size size() default Size.SMALL;
	Bear friendlyBear(); // DOES NOT COMPILE
	Exercise exervise() default @Exercise(hoursPerDay=2);
}

Wrapper classes like Integer or Long are not supported.
String[] is supported but String[][] is not.
Bear is not supported as is not Class.

Applying Element Modifiers

Annotation elements are implicitly abstract and public.

public @interface Material {}

public @interface Fluffy {
	int cuteness();
	public abstract int softness() default 11;

	protected Material material(); // DOES NOT COMPILE
	private String friendly(); // DOES NOT COMPILE
	final boolean isBunny(); // DOES NOT COMPILE
}

The elements material() and friendly() do not compile because the access modifier conflicts with the elements being implicitly public.
The method isBunny() does not compile because it cannot be marked final.

Adding a Constant Variable

Annotations can include constant variables that cna be accessed by other classes without actually creating the annotation.

public @interface ElectricitySource {
	public int voltage();
	int MIN_VOLTAGE = 2;
	public static final int MAX_VOLTAGE = 18;
}

They’re also implicitly public, static and final.

Reviewing Annotation Rules

public @interface Hazard {
	int danger();
	public String description() default "Fire";
	public static final int UNKNOWN = -1;
}

@Hazard(danger=100, description="Wind Damage")
public class Tornado {}

public @interface Hazard - public is optional, as it may be package-private.
public static final int UNKNOWN - public static final is implicitly applied, so they’re also optional.

Applying Annotations

Using Annotations in Declarations

They can be applied to a miriad of things. The following compiles, assuming they exist.

@FunctionalInterface interface Speedster {
	void go(String name);
}

@LongEars
@Soft
@Cuddly
public class Rabbit {

	@Deprecated
	public Rabbit(@NotNull Integer size) {}

	@Speed(velocity="fast")
	public void eat(@Edible String input) {
		@Food(vegetarian=true)
		String m = (@Tasty String) "carrots";

		Speedster s1 = new @Racer Speedster() {
			public void go(@FirstName @NotEmpty String name) {
				sout("start!" + name);
			}
		}

		Speedster s2 = (@Valid String n) -> sout(n);
	}

}

Mixing Required and Optional Elements

To use an annotation, all required values must be provided. While it may have many elements, valure are required only for ones without default values.

public @interface Swimmer {
	int armLength = 10;
	String stroke();
	String name();
	String favoriteStroke() default "Backstroke";
}

Implementations

@Swimmer class Amphibian {} // DOES NOT COMPILE
@Swimmer(favoriteStroke="Breaststroke", name="Sally") class Tadpole {} // DOES NOT COMPILE
@Swimmer(stroke="FrogKick", name="Kermit") clas Frog {}
@Swimmer(stroke="Butterfly", name="Kip", armLength=1) class Reptile {} // DOES NOT COMPILE
@Swimmer(strike="", name="", favoriteStroke="") class Snake {}

The 1st line does not compile because it’s missing the required elements stroke() and name().
The 2nd one does not compile neither as it’s missing the required element stroke().
The 4th lines does not compile as armLenght is a constant, not an element, and cannot be included in an annotation.

Creating a value() element

@Injured("Broken Tail") public class Monkey {}

Notice the previous annotation does only include a value and not a property name. This may compile as long as:

  • The annotation declaration has an element named value(), which may be optional or required.
  • The annotation declaration must not contain any other elements that are required.
  • The annotation usage must not provide values for any other elements.
public @interface Injured {
	String veterinarian() default "unassigned";
	String value() default "foot";
	int age() default 1;
}

Valid usages

public abstract class Elephant {
	@Injured("Legs") public void fallDown() {}
	@Injured(value="Legs") public abstract int trip();
	@Injured String injuries[];
}

The last one is allowed as @Injured does not have any required elements.
Typically, the value() of an annotation should be related ot its name.

(!) For the exam, make sure that if the shorthand notation is used, then there’s an element named value(). Also check that there’re no other required elements.
For example, the following declarations cannot be used with a shorthand annotation.

public @interface Sleep {
	int value();
	String hours();
}

public @interface Wake {
	String hours();
}
@Injured("Fur", age=2) public class Bear{} // DOES NOT COMPILE

The last annotation is not valid as it provides more than one value.

Passing an Array of Values

Annotations support a shorthand notation for providing an array that contains a single element.

public @interface Music {
	String[] genres();
}

If we want to provide only one value to the array, either way is correct

public class Giraffe {
	@Music(genres={"Rock and roll"}) String mostDisliked;
	@Music(genres="Classical") String favorite;
}

The first annotation is considered the regular form, as it’s clear the usage for an array.
The second one is the shorthand notation, where the array brackets are dropped for convenience. This notation can be used only if the array is composed of a single element.

public class Reindeer {
	@Music(genres="Blues", "Jazz") String favorite; // DOES NOT COMPILE
	@Music(genres=) mostDisliked; // DOES NOT COMPILE
	@Music(genres=null) String other; // DOES NOT COMPILE
	@Music(genres={}) String alternate;
}

While this shorthand notation can be used for arrays, it does not work for List or Collection.

Declaring Annotation-Specific Annotations

Limiting Usage with @Target

Many annotations declarations include @Target annotation, which limits the types the annotation can be applied to.
This takes an array of ElementType as its value() element.

ElementType Values

|ElementType value|Applies to| |:—:|:—:| |TYPE|Classes, interfaces, enums, annotations| |FIELD|Instance and static variables, enum values| |METHOD|method declarations| |PARAMETER|Constructor, method, lambda parameters| |CONSTRUCTOR|Constructor declarations| |LOCAL_VARIABLE|Local variables| |ANNOTATION_TYPE|Annotations| |PACKAGE|Packages declared in package-info.java| |TYPE_PARAMETER|Parameterized types, generic declarations| |TYPE_USE|Able to be applied anywhere there’s a Java type declared or used| |MODULE|Modules|

Some of this elements overlap.

(!) Make sure you’re able to properly recognize usage of them for the exam. (!)

@Target({ElementType.METHOD, ElementType.CONSTRUCTOR})
public @interface ZooAttraction {}

Understanding the TYPE_USE value

The TYPE_USE parameter can be used anywhere there’s a Java type.
By including it in @Target it actually includes nearly all the values, with a few exceptions, such as methods that return void.

It also allows annotations in places where types are used, such as cast operations, object creation with new, inside type declarations etc.

(!) For the exam, you only need to recognize that it can be applied in this manner if TYPE_USE is one of the @Target options (!)

@Target(ElementType.TYPE_USE)
@interface Technical {}

public class NetworkRepair {

	class OutSrc extends @Technical NetworkRepair {}

	public void repair() {
		var repairSubclass = new @Technical NetworkRepair() {};
		var o = new @Technical NetworkRepair().new @Technical OutSrc();
		int remaining = (@Technical int) 10.0;
	}

}

Storing Annotations with @Retention

The compiler discards certain types of information when converting your source code into a .class file. This may include annotations, which may be discarded by the compiler or at runtime.

This is specified by the @Retention annotation. It uses the following values of RetentionPolicy.

RetentionPolicy value Description
SOURCE Used only in the source file, discarded by the compiler
CLASS Stored in the .class file but not available at runtime (default compiler behaviour)
RUNTIME Stored in the .class file and available at runtime
@Retention(RetentionPolicy.CLASS) @interface Flier {}
@Retention(RetentionPolicy.RUNTIME) @interface Swimmer {}

Generating Javadoc with @Documented

If present, then the generated Javadoc will include annotation information defined on Java types.
Because it’s a marker annotation, it doesn’t take any values.

@Documented public @interface Hunter {}

@Hunter public class Lion {}

Here, the @Hunter annotation would be published with the Lion Javadoc information.

Inheriting Annotations with @Inherited

When this annotation is applied to a class, subclasses will inherit the annotation information found in the parent class.

@Inherited public @interface Vertebrate {}

@Vertebrate public class Mammal {}

public class Dolphin extends Mammal {} // includes @Vertebrate

Supporting Duplicates with @Repeatable

It’s used when you want to specify an annotation more than once on a type. Generally this happens when you want to apply the same annotation with different values.

public class Zoo {
	public static class Monkey {}

	@Risk(danger="Silly")
	@Risk(danger="Aggressive", level=5)
	@Risk(danger="Violent", level=10)
	private Monkey monkey;
}

(!) In the exam, if an annotation is being repeated for the same type, be sure it correctly implements @Repeteable (!)

To declare a @Repeteable annotation, you must define a containing annotation type value. A containing annotation type is a separate annotation that defines a value() array element. The type of this array is the particular annotation you want to repeat. By convention, the name is often the plural form of the repeteable annotation.

public @interface Risks {
	Risk[] value();
}

@Repeteable(Risks.class)
public @interface Risk {
	String danger();
	int level() default 1;
}
  • The repeteable annotation must be declared with @Repeteable and contain a value that refers to the containing type annotation.
  • The containing type annotation must include an element named value(), which is a primitive array of the repeteable annotation type.

Using Common Annotations

This set of annotations apply to various types and methods, and have special rules. If they’re used incorrectly, the compiler will report an error.

Marking Methods with @Override

It’s a marker annotation that’s used to indicate a method is overriding an inherited method.

The overriding method must have

  • the same signature
  • same or broader access modifier
  • a covariant return tyep
  • not declare any new or broader checked exceptions
public interface Intelligencce {
	int cunning();
}

public class Canine implements Intelligence {
	@Override
	public int cunning() {
		return 500;
	}
}

(!) During the exam, you should be able to identify anywhere this annotation is used incorrectly (!)

public class Dog extends Canine {
	@Override // DOES NOT COMPILE
	public boolean playFetch() {
		return true;
	}

	@Override // DOES NOT COMPILE
	void cunning(int timeOfDay) {
	}
}

Declaring interfaces with @FunctionalInterface

This marker annotation can be applied to any valid functional interface.

@FunctionalInterface
public interface Intelligence {
	int cunning();
}

The compiler will throw an error when applied to anything other than a valid functional interface.

@FunctionalInterface
abstract class Reptile { // DOES NOT COMPILE
	abstract String getName();
}

(!) check and be sure the it’s applied only to interfaces (!)

Retiring Code with @Deprecated

This notifiers users that a new version of the method is available and gives them time to migrate their code to the new version, before we finally remove the old version.

It supports two optional values since() and boolean forRemoval().

@Deprecated
public class ZooPlanner {
}
@Deprecated(since="1.8", forRemoval=true)
public class ZooPlanner {
}

Ignoring Warnings with @SuppressWarnings

While the compiler can be helpful in warning you of potential coding problems, sometimes you need to perform a particular operation. Applyin this annotation disables this warnings.

It takes two values.

Value Description
“deprecation” Ignore warnings related to types or methods marked with @Deprecated annotation
“unchecked” Ignore warnings related to the use of raw types, such as List
@SupressWarnings("deprecation")
public void wakeUp() {
	SongBird.sing(10);
}

@SupressWarnings("unchecked")
public void goToBed() {
	SongBird.chirp(new ArrayList());
}

Protecting Arguments with @SafeVarargs

It indicates that a method does not perform any potential unsafe operations on its varargs parameters. It can be applied only to constructors or methods that cannot be overriden.

The annotation it’s used to indicate to other developers that your method does not perform any unsafe operations.

public class NeverDoThis {
	final int thisIsUnsafe(List<Integer>... carrot) {
		Object[] stick = carrot;
		stick[0] = Arrays.asList("nope!");
		return carrot[0].get(0); // ClassCastException at runtime
	}

	public static void main(String[] a) {
		var carrot = new ArrayList<Integer>();
		new NeverDoThis().thisIsUnsafe(carrot);
	}
}

This code compiles, although it generates two compiler warnings.

[Line 4] Type safety: Potential heap pollution via varargs parameter carrot
[Line 11] Type safety: A generic array of List<Integer> is created for a varargs parameter

We can remove both compiler warnings by adding the @SafeVarargs annotation.

@SafeVarargs
final int thisIsUnsafe(List<Integer>... carrot) {

We didn’t fix the unsafe operation. It still throws ClassCastException. However, we made it so the compiler won’t warn us about it anymore.

(!) For the exam, you just need to be able to identify unsafe operations and know they often involve generics. You should also know the annotation cna be applied only to methods that contain a varargs parameter and are not able to be overriden. (!)

@SafeVarargs
public static void eat(int meal) {} // DOES NOT COMPILE (missing varargs)

@SafeVarargs
protected void drink(String... cup) {} // DOES NOT COMPILE (not static, final or private)

@SafeVarargs
void chew(boolean... food) {} // DOES NOT COMPILE (not static, final or private)

Summary

In this chapter, we taught you everything you need to know about annotations for the exam. Ideally, we also taught you how to create and use custom annotations in your daily programming life. As we mentioned early on, annotations are one of the most convenient tools available in the Java language.

For the exam, you need to know the structure of an annotation declaration, including how to declare required elements, optional elements, and constant variables. You also need to know how to apply an annotation properly and ensure required elements have values. You should also be familiar with the two shorthand notations we discussed in this chapter. The first allows you to drop the elementName under certain conditions. The second allows you to specify a single value for an array element without the array braces ({}).

You need to know about the various built-in annotations available in the Java language. We sorted these into two groups: annotations that apply to other annotations and common annotations. The annotationspecific annotations provide rules for how annotations are handled by the compiler, such as specifying an inheritance or retention policy. They can also be used to disallow certain usage, such as using a methodtargeted annotation applied to a class declaration.

The second set of annotations are common ones that you should know for the exam. Many, like @Override and @FunctionalInterface, are quite useful and provide other developers with additional information about your application.

Exam Essentials

Be able to declare annotations with required elements, optional elements, and variables. An annotation is declared with the @interface type. It may include elements and public static final constant variables. If it does not include any elements, then it is a marker annotation. Optional elements are specified with a default keyword and value, while required elements are those specified without one.

Be able to identify where annotations can be applied. An annotation is applied using the at (@) symbol, followed by the annotation name. Annotations must include a value for each required element and can be applied to types, methods, constructors, and variables. They can also be used in cast operations, lambda expressions, or inside type declarations.

Understand how to apply an annotation without an element name. If an annotation contains an element named value() and does not contain any other elements that are required, then it can be used without the elementName. For it to be used properly, no other values may be passed.

Understand how to apply an annotation with a single-element array. If one of the annotation elements is a primitive array and the array is passed a single value, then the annotation value may be written without the array braces ({}).

Apply built-in annotations to other annotations. Java includes a number of annotations that apply to annotation declarations. The @Target annotation allows you to specify where an annotation can and cannot be used. The @Retention annotation allows you to specify at what level the annotation metadata is kept or discarded. @Documented is a marker annotation that allows you to specify whether annotation information is included in the generated documentation. @Inherited is another marker annotation that determines whether annotations are inherited from super types. The @Repeatable annotation allows you to list an annotation more than once on a single declaration. It requires a second containing type annotation to be declared.

Apply common annotations to various Java types. Java includes many built-in annotations that apply to classes, methods, variables, and expressions. The @Override annotation is used to indicate that a method is overriding an inherited method. The @FunctionalInterface annotation confirms that an interface contains exactly one abstract method. Marking a type @Deprecated means that the compiler will generate a depreciation warning when it is referenced. Adding @SuppressWarnings with a set of values to a declaration causes the compiler to ignore the set of specified warnings. Adding @SafeVarargs on a constructor or private, static, or final method instructs other developers that no unsafe operations will be performed on its varargs parameter. While all of these annotations are optional, they are quite useful and improve the quality of code when used.

Read More

Java Modules

(the code for this is available at http://www.selikoff.net/ocp11-1

(do a more in-depth review of this chapter, if there’s enough time)

Introducing Modules

The Java Platform Module System (JPMS) was introduced in Java 9 to group code at a higher level. The main purpose of a module is to provide groups of related packages to offer a particular set of functionality to developers.
It’s like a JAR file except a developer chooses which packages are accessible outside the module.

Exploring a Module

A module is a group of one or more packages plus a special file called module-info.java.

(insert image of module)

Each block is a module. The arrows between them represent dependencies where one module relies on code in another.

(insert image of module)

Inside them, there’re three packages with two classes each. There’s also a module-info.java. This class is required to be inside all modules.

Benefits of Modules

Better Access Control

Aside from the existing access control (private, public…), they allow us to restrict access to just some packages, or implement complex access logic.
They act as a fifth level of access control. They can expose packages within the modular JAR to specific other packages (internal packages).

Clearer Dependency Management

It’s common for libraries to depend on other libraries. Usually you need to find this by reading documentation or project files.
In a fully modular environment, each project specifies its dependencies in a module-info.java file. If a dependency is missing, Java complains when launching the program.

Custom Java Builds

The JDK is way to big. The module system allows developers to specify what modules they actually need. This makes possible to create a smaller runtime image that’s customized to what the app needs and nothing more. Users can run that image without having Java installed at all.

The tool jlink is used to create this runtime image.

Improved Performance

Since Java now knows which modules are required, it only needs to look at those at class loading time. This improves startup time for big programs and requires less memory to run.

Unique Package Enforcement

It prevents the scenario of the same package being in two JARs. A package is only allowed to be supplied by one module.

Creating and Running a Modular Program

(insert image of module)

Creating the Files

Create the following classes and packages.

Task.java

package zoo.animal.feeding;

public class Task {
	public static void main(String[]args) {
		sout("All fed!");
	}
}

module-info.java

module zoo.animal.feeding {
}

Key differences between module-info.java and a regular Java class

  • This file must be in the root directory of your module. Regular Java classes should be in packages.
  • This file must use the keyword module instead of class.
  • Its name follows the naming rules for package names. It often includes periods in its name.

The file may be empty, but it won’t create a .class.

Directory structure

- mods
- feeding
	- module-info.java
	- zoo
		- animal
			- feeding
				- Task.java

The directory called mods (short for module) is for storing the module artifacts.

Compiling Our First Module

Before we can run modular code, we need to compile it.

javac --module-path mods
	-d feeding
	feeding/zoo/animal/feeding/*.java
	feeding/module-info.java

-d specifies the directory to place the class files in.
The syntax --module-path and -p are equivalent.

Running Our First Module

java --module-path feeding
	--module zoo.animal.feeding/zoo.animal.feeding.Task

The short version for --module is -m.

Packaging Out First Module

A module isn’t much use if we can run it only in the folder it was cerated in.

jar -cvf mods/zoo.animal.feeding.jar -C feeding/ .

Versioning

It’s possible to version your module using --module-version,

Updating Our Example For Multiple Modules

Updating the Feeding Module

We need to declare the intent of our modules calling each other inside module-info.
The exports keyword is used to indicate that a module intends for those packages to be used by Java code outside the module. Without it, a module is only available to be run from the command line on its own.

module zoo.animal.feeding {
	exports zoo.animal.feeding;
}

Recompiling and repackaging the module will update the module-info inside our zoo.animals.feeding.jar file.

javac --module-path mods
	-d feeding
	feeding/zoo/animal/feeding/*.java
	feeding/module-info.java

jar -cvf mods/zoo.animal.feeding.jar -C feeding/ .

Creating a Care Module

Let’s create the zoo.animal.care module. We’re going to have two packages. The zoo.animal.care.medical package will have the classes and methods that are intended for use by other modules. The zoo.animal.care.details package is only going ot be used by this module. It won’t be exported from the module.

(insert image of module)

package zoo.animal.care.details;

import zoo.animal.feeding.*;

public class HippoBirthday {
	private Task task;
}
package zoo.animal.care.medical;

public class Diet { }

This time the module-info.java specifies three things.

module zoo.animal.care {
	exports zoo.animal.care.medical;
	requires zoo.animal.feeding;
}
  • It specifies the name of the module.
  • It lists the package we are exporting so it can be used by other modules.
  • It specifies that a module is needed.

We now compile and package the module

javac -p mods
	-d care
	care/zoo/animal/care/details/*.java
	care/zoo/animal/care/medical/*.java
	care/module-info.java

Note that when compiling a module, order matters! module-info goes last.

Now that we have compiled code, it’s time to create a modular JAR.

jar -cvf mods/zoo.animal.care.jar -C care/ .

Creating the Talks Module

So far we’ve used only one exports and requires in a module. Now we’ll handle exporting multiple packages or requiring multiple modules.

module zoo.animals.talks {
	// allow other modules to reference all three packages
	exports zoo.animal.talks.content;
	exports zoo.animal.talks.media;
	exports zoo.aniaml.talks.schedule;

	// specifies two modules that this module depends on
	requires zoo.animal.feeding;
	requires zoo.animal.care;
}

Then we have the six classes:

package zoo.animal.talks.content;

public class ElephantScript {}
package zoo.animal.talks.content;

public class SeaLionScript {}
package zoo.animal.talks.media;

public class Announcement {
	public static void main(String[] args) {
		sout("we will be having talks");
	}
}
package zoo.animal.talks.media;

public class Signage {}
package zoo.animal.talks.schedule;

public class Weekday {}

We compile and build the module

javac -p mods
	-d talks
	talks/zoo/animal/talks/content/*.java
	talks/zoo/animal/talks/media/*.java
	talks/zoo/animal/talks/schedule/*.java
	talks/module-info.java

jar -cvf mods/zoo.animal.talks.jar -C talks/

Creating the Staff Module

This is the module-info

module zoo.staff {
	requires zoo.animal.feeding;
	requires zoo.animal.care;
	requires zoo.animal.talks;
}

We have a single class in this module

package zoo.staff;

public class Jobs {}

Compile & build

javac -p mods
	-d staff
	staff/zoo/staff/*.java
	staff/module-info.java

jar -cvf mods/zoo.staff.jar -C staff/

Diving into the module-info File

exports and requires are “keywords”, meaning that they’re only keywords inside a module-info.java file. In other files, like classes or interfaces, they’re free to be used. This kind of special “keyword” are called directives.

exports

exports packageName exports a package to other modules.
It’s also possible to export a package to a specific module.

module zoo.animal.talks {
	exports zoo.animal.talks.content to zoo.animal.staff;
	exports zoo.animal.talks.media;
	exports zoo.animal.talks.schedule;

	requires zoo.animal.feeding;
	requires zoo.animal.care;
}

From zoo.animal.staff module nothing has changed. However, no other modules would be allowed to access that package.

Exported Types

Exporting a package means, that all public classes, interfaces and enums are exported. Any public and protected fields and methods in those files are visible.

requires transitive

(check again: book 1. Page 528)

requires moduleName specifies that the current module depends on moduleName.
There’s also requires transitive moduleName, which means any module that requires this module will also depend on moduleName.

module zoo.animal.care {
	exports zoo.animal.care.medical;
	requires transitive zoo.animal.feeding;
}

Effects of requires transitive

(check again: book 1. Page 530)

Duplicate requires Statements

(!) One place the exam might try to trick you is mixing requires and requires transitive together. (!)

module bad.module {
	requires zoo.animal.talks;
	requires transitive zoo.animal.talks;
}

Thsi doesn’t compile, because Java doesn’t allow you to repeat the same module in a requires clause. It’s redundant. Keep in mind that requires transitive is like requires with some extra behaviour.

provides, uses, and opens

  • provides - specifies that a class provides an implementation of a service. It’s kind of a service as a fany interface. To use it you supply the API and class name that implements it.
provides zoo.staff.ZooApi with zoo.staff.ZooImpl
  • uses - specifies that a module is relying on a service. To code it you supply the API you want to call.
uses zoo.staff.ZooApi
  • opens - It specifies explicitly that developers are allowed to use reflection.
opens zoo.animal.talks.schedule;
opens zoo.animal.taslks.media to zoo.staff;

The first example allows any module using this one to use reflection.
The second one, gives that privilege only to the zoo.staff package.

Discovering Modules

Since Java 9, the classes built into the JDK were modularized as well.

The java Command

It has three module-related options.

Describing a Module

Suppose you’re given the zoo.animal.feeding module JAR file and want to know about its module structure.
You could unjar it and open the module-info. This would show you that the moduel exports one package and doesn’t require any modules.

module zoo.animal.feeding {
	exports zoo.animal.feeding;
}

There’s an easier way. The java command now has an option to describe a module.

This 2 commands are equivalent

java -p mods
    -d zoo.animal.feeding
java -p mods
	--describe-module zoo.animal.feeding

Each prints information about the module such as

zoo.animal.feeding file:///aboslutePath/mods/zoo.animal.feeding.jar
exports zoo.animal.feeding
requires java.base mandated

The first line is the module we asked about.
The second one, starts information about the module.
The third one, we see mandated as keyword. The java.base module is special. It’s automatically added as a dependency to all modules.

Listing Available Modules

List all modules that are available.

java --list-modules

the output are 70 lines that looked like this

java.base@11.0.2
java.compiler@11.0.2
java.datatransfer@11.0.2

This is a listing of all the modules that come with Java and their version numbers. Here 11.0.2 is the Java version.

This command may be used with custom code.

java -p mods --list-modules

Showing Module Resolution

In case listing modules didn’t give you enough output, you can also use --show-module-resolution. You can think of it as a way of debugging modules. It spits out a lot of output when the program starts up.

java --show-module-resolution
	-p feeding
	-m zoo.animal.feeding/zoo.animal.feeding.Task

The jar Command

It can describe a module, just like the java command.

Both of this commands are equivalent.

jar -f mods/zoo.animal.feeding.jar -d
jar --file mods/zoo.animal.feeding.jar --describe-module

The jar version includes the module-info in the filename.

You just need to know that both commands can describe a module.

The jdeps Command

It gives you information about dependencies within a module. Unlike describing a module, it looks at the code in addition to the module-info file. This tells you what dependencies are actually used rather than simply declared.

Both of these commands give the same output.

jdeps -s mods/zoo.animal.feeding.jar
jdeps -summary mods/zoo.animal.feeding.jar

(review this again more in depth. book 1 page 536)

The jmod Command

JMOD files are recommended only when you have native libraries or something can’t go inside a JAR file.

jmod is only for working with the JMOD files.

The following table are JMOD’s modes or syntax

operation description
create creates a JMOD file
extract extracts all files frmo the JMOD. Works like unzipping.
describe prints the module details such as requires
list lists all files in the JMOD file
hash shows a long string that goes with the file

Reviewing Command-Line Options

This table shows the command lines you should expect to encounter on the exam.

Description Syntax
Compile nonmodular code javac -cp classpath -d directory classesToCompile
  javac –class-path classpath -d directory classesToCompile
  javac -classpath classpath -d directory classesToCompile
Run nonmodular code java -cp classpath package.className
  java –classpath classpath package.className
  java –class-path classpath package.className
Compile a module javac -p moduleFOlderName -d directory classesToCompileIncludingModuleInfo
  javac –module-path moduleFOlderName -d directory classesToCompileIncludingModuleInfo
Run a module java -p moduleFolderName -m moduleName/package.className
  java –module-path moduleFolderName –module moduleName/package.className
Describe a module java -p moduleFolderName -d moduleName
  java –module-path moduleFolderName –describe-module moduleName
  jar –file jarName –describe-module
  jar -f jarName -d
List available modules java –module-path moduleFolderName –list-modules
  java -p moduleFolderName –list-modules
  java –list-modules
View dependencies jdeps -summary –module-path moduleFolderName jarName
  jdeps -s –module-path moduleFolderName jarName
Show module resolution java –show-module-resolution -p moduleFolderName -d moduelName
  java –show-module-resolution –module-path moduleFolderName –describe-module moduleName

javac modifiers

|Modifier|Description| |:—:|:—:| |-cp classpath|location of JARs in a nonmodular program| |-classpath classpath|| |–class-path classpath|| |-d dir|directory to place generated class files| |-p path|location of JARs in a modular program| |–module-path path||

java modifiers

|Modifier|Description| |:—:|:—:| |-p path|location of JARs in a modular program| |–module-path path|| |-m name|module name to run| |–module name|| |-d|describes the details of a module| |–describe-module|| |–list-modules|list observable modules without running a program| |–show-module-resolution|shows modules when running a program|

jar modifiers

|Modifier|Description| |:—:|:—:| |-c| craete a new JAR file| |–create|| |-v|prints details when working with JAR files| |–verbose|| |-f|JAR filename| |–file|| |-C|Directory containing giles to be used to create the JAR| |-d|Describes the details of a module| |–describe-module||

jdeps modifiers

|Modifier|Description| |:—:|:—:| |–module-path path|Location of JARs in a modular program| |-s|summarizes output| |-summary||

Summary

The Java Platform Module System organizes code at a higher level than packages. Each module contains one or more packages and a module-info file. Advantages of the JPMS include better access control, clearer dependency management, custom runtime images, improved performance, and unique package enforcement.

The process of compiling and running modules uses the –module-path, also known as -p. Running a module uses the –module option, also known as -m. The class to run is specified in the format moduleName/className.

The module-info file supports a number of keywords. The exports keyword specifies that a package should be accessible outside the module. It can optionally restrict that export to a specific package. The requires keyword is used when a module depends on code in another module. Additionally, requires transitive can be used when all modules that require one module should always require another. The provides and uses keywords are used when sharing and consuming an API. Finally, the opens keyword is used for allowing access via reflection.

Both the java and jar commands can be used to describe the contents of a module. The java command can additionally list available modules and show module resolution. The jdeps command prints information about packages used in addition to module-level information. Finally, the jmod command is used when dealing with files that don’t meet the requirements for a JAR.

Exam Essentials

Identify benefits of the Java Platform Module System. Be able to identify benefits of the JPMS from a list such as access control, dependency management, custom runtime images, performance, and unique package enforcement. Also be able to differentiate benefits of the JPMS from benefits of Java as a whole. For example, garbage collection is not a benefit of the JPMS.

Use command-line syntax with modules. Use the command-line options for javac, java, and jar. In particular, understand the module (-m) and module path (-p) options.

Create basic module-info files. Place the module-info.java file in the root directory of the module. Know how to code using exports to expose a package and how to export to a specific module. Also, know how to code using requires and requires transitive to declare a dependency on a package or to share that dependency with any modules using the current module.

Identify advanced module-info keywords. The provides keyword is used when exposing an API. The uses keyword is for consuming an API. The opens keyword is for allowing the use of reflection.

Display information about modules. The java command can describe a module, list available modules, or show the module resolution. The jar command can describe a module similar to how the java command does. The jdeps command prints details about a module and packages. The jmod command provides various modes for working with JMOD files rather than JAR files.

Read More

Exceptions

Understanding Exceptions

Understanding Exception Types

An exception is an event that alters program flow. There’s a Throwable superclass for all objects that represent these events.

The class Error is for when something goes so horribly wrong that your program should not attempt to recover from it. From example, the disk drive has disappeared or the program runs out of memory.

Throwable is the parent class of all exceptions, including Error.

Checked Exceotions

A checked exception is an exception that must be declared or handled by the application code where it’s thrown. They all inherit from Exception, but not from Error or RuntimeException.
They tend to be more anticipated - from example, trying to read a file that doesn’t exist.

The handle or declare rule means that all checked exceptions that could be thrown within a method, are either wrapped in compatible try and catch blocks, or declared in the method signature.

void fall(int distance) throws IOException {
	if(distance > 10) {
		throw new IOException();
	}
}

The throw keyword tells Java that you want to throw an Exception.
The throws keyword simply declares that the method might throw an Exception. It also might not.

To handle the exception

void fall(int distance) {
	try {
		if(distance > 10) {
			throw new IOException();
		}
	} catch(Exception e) {
		e.printStackTrace();
	}
}

Unchecked Exceptions

An unchecked exception is any exception that does not need to be declared or handled by the application code where it’s thrown. They’re often referred to as runtime exceptions. They include any class that inherits from RuntimeException or Error.
They tend to be unexpected, but not necessarily fatal. For example, NullPointerException.

void fall(String input) {
	sout(input.toLowerCase());
}

void(null);

Throwing an Exception

On the exam, you will see two types of code that result in an exception.

The first is code that’s wrong

// throws ArrayIndexOutOfBoundsException
String[] animals = new String[0];
sout(animals[0]);

(!) Pay special attention to code that calls a method on a null reference or that references an invalid array or List index. (!)

The second way is to explicitly request to throw the Exception.

// all these are valid
throw new Exception();
throw new Exception("Ow! I fell. ");
throw new RuntimeException();
throw new RuntimeException("Ow! I fell. ");

(!) Anytime you see throw or throws, make sure the correct one is being used. (!)
throw is used to throw a new exception
throws is used only at the end of a method declaration.

An Exception is an Object. You can store it in a variable.

Exception e = new RuntimeException();
throw e;

They need to be instantiated

throw RuntimeException(); // DOES NOT COMPILE

Be careful with unreacheable code.

try {
	throw new RuntimeException();
	throw new ArrayIndexOutOfBoundsException(); // DOES NOT COMPILE!
} catch(Exception ex) {
}

Recognizing Exception Classes

RuntimeException Classes

RuntimeException and its subclasses are unchecked exceptions that don’t have to be handled or declared. They can be thrown by the programmer or by the JVM.

  • ArithmeticException - Thrown when code attempts to divide by zero
  • ArrayIndexOutOfBoundsException - code uses an illegal index to access an array
  • ClassCastException - an attempt is made to cast an object to a class of which is not an instance
  • NullPointerException - there’s a null reference where an object is required
  • IllegalArgumentException - thrown by the programmer to indicate that a method has been passed an illegal or inappropiate argument
  • NumberFormatException - Subclass of IllegalArgumentException. When an attempt is made to convert a String to a numeric type, but it doesn’t have an appropiate format

Checked Exception Classes

They have Exception in their hierarchy but not RuntimeException. They must be handled or declared.

  • IOException - thrown programmatically when there’s a problem reading or writing a file.
  • FileNotFoundException - subclass of IOException. thrown when code tries to reference a file that does not exist.

Error Classes

Errors are unchecked exceptions that extend from Error. They’re thrown by the JVM and should not be handled or declared. They’re rare.

  • ExceptionInInitializerError - Thrown when a static initializer throws an exception and doesn’t handle it.
static {
	int[] countsOfMoose = new int[3];
	int num = countsOfMoose[-1];
}
  • StackOverflowError - a method calls itself too many times (infinite recursion).
  • NoClassDefFoundError - a class that the code uses is available at compile time but not at runtime.

Handling Exceptions

Using try and catch Statements

Java uses a try statement to separate the logic that might throw an exception, from the logic to handle that exception.

The curly braces are required for try and catch blocks.

try {

} catch(Exception ex) {

}

Some invalid try statements

try // DOES NOT COMPILE
	fall();
catch(Exception ex)
	sout();
try { // DOES NOT COMPILE
	fall();
}

Chaining catch Blocks

First, you must be able to recognize if the exception is a checked or an unchecked exception.
Second, you need to determine whether any of the exceptions are subclasses of the others.

// unchecked exceptions
class AnimalsOutForAWalk extends RuntimeException { }
class ExhibitClosed extends RuntimeException { }
class ExhibitClosedForLunch extends ExhibitClosed { }
public void visitPorcupine() {
	try {
		seeAnimal();
	} catch (AnimalsOutForAWalk e) { // first catch block
		System.out.print("try back later");
	} catch (ExhibitClosed e) { // second catch block
		System.out.print("not today");
	}
}

A rule exists for the order of the catch blocks. Java looks at them in the order they appear. If it’s impossible for one of the catch blocks to be executed, a compiler error about unreachable code occurs.
(!) This happens when a superclass catch block appears before a subclass catch block. Pay attention to any subclass exceptions. (!)

public void visitMonkeys() {
	try {
		seeAnimal();
	} catch(ExhibitClosedForLunch e) { // subclass
		sout("try back later");
	} catch(ExhibitClosed e) { // superclass
		sout("not today");
	}
}

If the order was reversed, they wouldn’t compile as the second one couldn’t ever be reached.

public void visitMonkeys() {
	try {
		seeAnimal();
	} catch(ExhibitClosed e) {
		sout("try back later");
	} catch(ExhibitClosedForLunch e) { // DOES NOT COMPILE
		sout("not today");
	}
}

Applying a Multi-catch Block

If we want to apply the same result to multiple exceptions, we may re-use code.

public static void main(String args[]) {
	try {
		soutln(Integer.parseInt(args[1]));
	} catch(ArrayIndexOutOfBoundsException | NumberFormatException ex) {
		sout("Missing or invalid input");
	}
}

(!) The exam might try to trick you with invalid syntax (!)

catch(Exception1 e | Exception2 e) // DOES NOT COMPILE
catch(Exception1 e1 | Exception2 e2) // DOES NOT COMPILE
catch(Exception1 | Exception2 e) // COMPILES

Multi-cath is intented to be used for exceptions that aren’t related, and it prevents you from specifying redundant types in a multi-catch.

try {
	throw new IOException();
} catch(FileNotFoundException | IOException p) {} // DOES NOT COMPILE

This fails, because FileNotFoundException is already caught by the alternative IOException.
The Exceptions’ order doesn’t matter inside a single block.
You can’t list the same exception type more than once in the same try statement.
The more general superclasses must be caught after their subclasses.

Adding a finally Block

The try statement also lets you run code at the end with a finally clause, regardless of whether an exception is thrown.

try {
	seeAnimals();
	fall();
} catch(Exception e) {
	getHug();
} finally {
	seeMoreAnimals();
}

If an exception is thrown, the finally block is run after the catch block. If no exception is thrown, the finally block is run after the try block completes.

The exam will try to trick you with missing clasuses, or clasuse in the wrong order.

try { // DOES NOT COMPILE
	fall();
} finally {
	sout("all better");
} catch(Exception e) {
	sout("get up");
}

try { // DOES NOT COMPILE
	fall();
}

try { // COMPILES
	fall();
} finally {
	sout("all better");
}

The catch block is not required if finally is present.

StringBuilder sb = new StringBuilder();
try {
	sb.append("t");
} catch(Exception e) {
	sb.append("c");
} finally {
	sb.append("f");
}

sb.append("a");
sout(sb.toString());

This will print tfa.

If there’s a finally block, this will always be executed.

int goHome() {
	try {
		sout("1");
		return -1;
	} catch(Exception e) {
		sout("2");
		return -2;
	} finally {
		sout("3");
		return -3;
	}
}

This will print 13 and it will always return -3.

There’s one exception to this: System.exit(0).

try {
	System.exit(0);
} finally {
	sout("Never going to get here"); // not printed.
}

Finally Closing Resources

It avoids resource leaks by closing resources.

public void readFile(String file) {
	try (FileInputStream is = new FileInputStream("myfile.txt")) {
		// do something
	} catch(IOException e) {
		e.printStackTrace();
	}
}

As soon as a connection passes out of scope, Java will attempt to close it.

Behind the scenes, the compiler replaces this try-with-resources block for a try with a finally block. You can still create a programmer defined finally block, just be aware that the implicit one will be called first.

Basics of Try-with-Resources

One or more resources can be opened in the try clause. When there’re multiple resources opened, they’re closed in the reverse order from which they were created.

try (FileInputStream in = new FileInputStream("data.txt");
		FileOutputStream out = new FileOutputStream("output.txt")) {

}

The catch block is optional in try-with-resources!

You can’t put any random class inside the try-with-resources statement. Java requires a class which implements AutoCloseable interface, which includes a void close() method.

Declaring Resources

While try-with-resources does support declaring multiple variables, each variable must be declared in a separate statement.

try (MyFileClass is = new MyFileClass(1), // DOES NOT COMPILE
		os = new MyFileClass(2)) {

}
try (MyFileClass ab = new MyFileClass(1),
		MyFileClass cd = new MyFileClass(2)) { // DOES NOT COMPILE

}

You can declare a resource using var as the data type.

try (var f = new FileInputStream("it.txt")) {
	// process file
}

Scope of Try-with-Resources

The resources created are in scope only within the try block.

try (Scanner s = new Scanner(System.in)) {
	s.nextLine();
} catch(Exception e) {
	s.nextLine(); // DOES NOT COMPILE
} finally {
	s.nextLine(); // DOES NOT COMPILE
}

The problem is that Scanner has gone out of scope. You can’t accidentally use an object that has been closed.

Following Order of Operation

  • Resources are closed after the try clause ends and before any catch/finally clauses.
  • Resources are closed in the reverse order from which they were created.
public class MyFileClass implements AutoCloseable {
	private final int num;

	public MyFileClass(int num) { this.num = num; }

	public void close() {
		sout("Closing: " + num);
	}
}

public static void main(String... xyz) {
	try(MyFileClass a1 = new MyFileClass(1);
			MyFileClass a2 = new MyFileClass(2)) {
		throw new RuntimeException();		
	} catch (Exception e) {
		sout("ex");
	} finally {
		sout("finally");
	}
}

Since the resources are closed in reverse order, this prints

Closing: 2
Closing: 1
ex
finally

Throwing Additional Exceptions

There’re situations where we need to catch an exception inside of a catch or finally block.

public static void main(String[] a) {
	FileReader reader = null;
	try {
		reader = read();
	} catch(IOException ex) {
		try {
			if(reader != null)
				reader.close();
		} catch (IOException inner) {
			//
		}
	}
}

private static FileReader read() throws IOException {
	//
}

The following example shows that only the last exception to be thrown matter.

try {
	throw new RuntimeException();
} catch(RuntimeException ex) {
	throw new RuntimeException();
} finally {
	throw new Exception();
}

Another example

public String exceptions() {
	StringBuilder result = new StringBuilder();
	String v = null;

	try {
		try {
			result.append("before_");
			v.length();
			result.append("after_");
		} catch(NullPointerException e) {
			result.append("catch_");
			throw new RuntimeException();
		} finally {
			result.append("finally_");
			throw new Exception();
		}
	} catch(Exception e) {
		result.append("done");
	}
	return result.toString();
}

This prints before_catch_finally_done

Calling Methods That Throw Exceptions

When you’re calling a method that throws an exception, the rules are the same as within a method.

class NoMoreCarrotsException extends Exception {}

public class Bunny {
	private static void eatCarrot() throws NoMoreCarrotsException {
	}

	public static void main(String[] args) {
		eatCarrot(); // DOES NOT COMPILE
	}
}

Checked exceptions must be handled or declared. The solution would be either of this.

public static void main(String[] args)
	throws NoMoreCarrotsException {
	eatCarrot();
}
public static void main(String[] args) {
	try {
		eatCarrot();
	} catch(NoMoreCarrotsException ex) {
		//
	}
}

The reverse is also true, we cannot try to catch an exception when a method doesn’t include it in its declaration.

private void eatCarrot() {}

public void bad() {
	try {
		eatCarrot();
	} catch(NoMoreCarrotsException ex) { // DOES NOT COMPILE
		// ex
	}
}

(!) When you see a checked exception declared inside a catch block on the exam, check and make sure the code in the associated try block is capable of throwing the exception or a subclass of the exception. If it’s not capable, the code is unreachable and does not compile.
Remember that this rules does not extend to unchecked exceptions or exceptions declared in a method signature. (!)

Declaring and Overriding Methods with Exceptions

When a class overrides a method from a superclass or implements a method from an interface, it’s not allowed to add new checked exceptions to the method signature.

class CanNotHopException extends Exception { }

class Hopper {
	public void hop() {}
}

class Bunny extends Hopper {
	public void hop() throws CanNotHopException {} // DOES NOT COMPILE
}

An overriden method in a subclass is allowed to declare fewer exceptions than the superclass or interface. This is legal because callers are already handling them.

class Hopper {
	public void hop() throws CanNotHopException { }
}

class Bunny extends Hopper {
	public void hop() { }
}

Similarly, a class is allowed to declare a subclass of an exception type.

class Hopper {
	public void hop() throws Exception {}
}

class Bunny extends Hopper {
	public void hop() throws CanNotHopException {}
}

The following code is legal because it has an unchecked exception in the subclass.

class Hopper {
	public void hop() {}
}

class Bunny extends Hopper {
	public void hop() throws IllegalStateException {}
}

The declaration is redundant. Methods are free to throw any unchecked exceptions they want without mentioning them in the method declaration.

Printing an Exception

There’re three ways to print an exception.

  • You can let Java print it out
  • Print just the message
  • Print where the stack trace comes from
private static void hop() {
	throw new RuntimeException("cannot hop");
}

public static void main(String[] args) {
	try {
		hop();
	} catch(Exception ex) {
		sout(ex);
		sout(ex.getMessage());
		ex.printStackTrace();
	}
}

They result in the following output

java.lang.RuntimeException: cannot hop
cannot hop
java.lang.RuntimeException: cannot hop
	at Handling.hop(Handling.java:15)
	at Handling.main(Handling.java:7)

Summary

An exception indicates something unexpected happened. A method can handle an exception by catching it or declaring it for the caller to deal with. Many exceptions are thrown by Java libraries. You can throw your own exceptions with code such as throw new Exception().

All exceptions inherit Throwable. Subclasses of Error are exceptions that a programmer should not attempt to handle. Classes that inherit RuntimeException and Error are runtime (unchecked) exceptions. Classes that inherit Exception, but not RuntimeException, are checked exceptions. Java requires checked exceptions to be handled with a catch block or declared with the throws keyword.

A try statement must include at least one catch block or a finally block. A multicatch block is one that catches multiple unrelated exceptions in a single catch block. If a try statement has multiple catch blocks chained together, at most one catch block can run. Java looks for an exception that can be caught by each catch block in the order they appear, and the first match is run. Then execution continues after the try statement. If both catch and finally throw an exception, the one from finally gets thrown.

A try-with-resources block is used to ensure a resource like a database or a file is closed properly after it is created. A try-with-resources statement does not require a catch or finally block but may optionally include them. The implicit finally block is executed before any programmer-defined catch or finally blocks.

RuntimeException classes you should know for the exam include the following:

  • ArithmeticException
  • ArrayIndexOutOfBoundsException
  • ClassCastException
  • IllegalArgumentException
  • NullPointerException
  • NumberFormatException

IllegalArgumentException is typically thrown by the programmer, whereas the others are typically thrown by the standard Java library.

Checked Exception classes you should know for the exam include the following:

  • IOException
  • FileNotFoundException

Error classes you should know for the exam include the following:

  • ExceptionInInitializerError
  • StackOverflowError
  • NoClassDefFoundError

For the exam, remember that NumberFormatException is a subclass of IllegalArgumentException, and FileNotFoundException is a subclass of IOException. When a method overrides a method in a superclass or interface, it is not allowed to add checked exceptions. It is allowed to declare fewer exceptions or declare a subclass of a declared exception. Methods declare exceptions with the keyword throws.

Exam Essentials

Understand the various types of exceptions. All exceptions are subclasses of java.lang.Throwable. Subclasses of java.lang.Error should never be caught. Only subclasses of java.lang.Exception should be handled in application code.

Differentiate between checked and unchecked exceptions. Unchecked exceptions do not need to be caught or handled and are subclasses of java.lang.RuntimeException and java.lang.Error. All other subclasses of java.lang.Exception are checked exceptions and must be handled or declared.

Understand the flow of a try statement. A try statement must have a catch or a finally block. Multiple catch blocks can be chained together, provided no superclass exception type appears in an earlier catch block than its subclass. A multi-catch expression may be used to handle multiple exceptions in the same catch block, provided one exception is not a subclass of another. The finally block runs last regardless of whether an exception is thrown.

Be able to follow the order of a try-with-resources statement. A try-with-resources statement is a special type of try block in which one or more resources are declared and automatically closed in the reverse order of which they are declared. It can be used with or without a catch or finally block, with the implicit finally block always executed first.

Identify whether an exception is thrown by the programmer or the JVM. IllegalArgumentException and NumberFormatException are commonly thrown by the programmer. Most of the other unchecked exceptions are typically thrown by the JVM or built-in Java libraries.

Write methods that declare exceptions. The throws keyword is used in a method declaration to indicate an exception might be thrown. When overriding a method, the method is allowed to throw fewer or narrower checked exceptions than the original version.

Recognize when to use throw versus throws. The throw keyword is used when you actually want to throw an exception—for example, throw new RuntimeException(). The throws keyword is used in a method declaration.

Read More

Advanced Class Design

Creating Abstract Classes

Introducing Abstract Classes

A subclass can override an inherited method defined in a parent class. Overriding a method potentially changes the behavior of a method in the parent class.

We use abstract classes when we want to define a class that other developers can extend and use, but you want them to specify particular types.
We can ensure that every class that extends it is required to provide its own overriden version.

An abstract class is one that cannot be instantiated and may contain abstract methods.
An abstract method is one that does not define an implementation when it’s declared.
Both of them are denoted with the abstract keyword.

abstract class Bird {
	public abstract String getName();

	public void printName() {
		sout(getName());
	}
}

public class Stork extends Bird {
	public String getName() { return "Stork!"; }

	public static void main(String[] args) {
		new Stork().printName();
	}
}

The class Stork must now override abstract getName(). The following implementation without it, wouldn’t compile.

public class Stork extends Bird {} // DOES NOT COMPILE

An abstract class is most commonly used when you want another class to inherit properties of a particular class, but you want the subclass to fill in some of the implementation details.

Defining Abstract Methods

An abstract class may include non-abstract methods. It may also include all kind of members such as variables, static, inner classes, constructors…

An abstract class it not required to include abstract methods. The following code compiles.

public abstract class Llama {
	public void chet() {}
}
// this is also fine
abstract public class Llama {
	public void chet() {}
}
abstract public class Llama {
	// this is also fine
	abstract public void chet() {}
}

The modifier cannot be placed after the class keyword nor after the return type in a method.

public class abstract Jackal { // DOES NOT COMPILE
	public int abstract howl(); // DOES NOT COMPILE
}

An abstract method can only be defined in an abstract class, or an interface.

public class Egret { // DOES NOT COMPILE
	public abstract void peck();
}

(!) If you see a class that contains an abstract method, make sure the class is marked as abstract. This modifier can be placed before or after the access modifier. (!)

It’s also not possible to define an abstract method that has a body, or default implementation. As long as you don’t mark the method as final, the subclass has the option to override an inherited method.

Constructors in Abstract Classes

abstract classes cannot be instantiated, but they’re still able to be initialized through constructors by their subclasses.

abstract class Bear {
	abstract CharSequence chew();

	public Bear() {
		sout(chew());
	}
}

public class Panda extends Bear {
	String chew() { return "yummy!"; }

	public static void main(String... args) {
		new Panda();
	}
}

This will print yummy! on runtime.

The main difference between constructors in an abstract class and a non-abstract one, is that the constructor in the abstract class can be called only when it’s being initialized by a non-abstract subclass. This makes sense, as abstract classes cannot be instantiated.

Invalid Abstract Method Declarations

Why don’t compile any of the following methods?

public abstract class Turtle {
	public abstract long eat()
	public abstract void swim() {};
	public abstract int getAge() { return 10; }
	public void sleep;
	public void goInShell();
}

eat() does not compile because it’s missing ;
swim() and getAge() are marked as abstract but implement a body for the method. An abstract method cannot contain an implementation.
sleep is missing parentheses.
goInShell() is not marked as abstract, but then it has to implement a body.

(!) If you come across a question on the exam in which a class or method is marked as abstract, make sure the class is properly implemented before attempting to solve the problem. (!)

Invalid Modifiers

We review the abstract modifier and which modifiers it’s not compatible with.

abstract and final Modifiers

If you mark something abstract, you’re intending for someone else to extend or implement it.
If you mark something final, you’re preventing anyone from extending or implementing it. These concepts are in direct conflict with each other.

public abstract final class Tortoise { // DOES NOT COMPILE
	public abstract final void walk(); // DOES NOT COMPILE
}

abstract and private Modifiers

A method cannot be marked both as abstract and private. How would you define a subclass that implements a required method if the method is not inherited by the subclass? You can’t.

public abstract class Whale {
	private abstract void sing(); // DOES NOT COMPILE
}

public class HumpbackWhale extends Whale {
	private void sing() {
		sout("X");
	}
}

The sing() method defined in the parent is not visible to the subclass.

abstract and static Modifiers

A static method cannot be overriden. It’s defined as belonging to the class, not an instance of the class. If it cannot be overriden, then it also cannot be marked abstract since it could never be implemented.

abstract class Hippopotamus {
	abstract static void swim(); // DOES NOT COMPILE
}

Creating a Concrete Class

A concrete class is a non-abstract class. The first concrete subclass that extends an abstract class is required to implement all inherited abstract methods.

(!) When you see a concrete class extending an abstract class on the exam, check to make sure that it implements all of the required abstract methods. (!)

public abstract class Animal {
	public abstract String getName();
}

public class Walrus extends Animal { // DOES NOT COMPILE
}

We highlight the first concrete subclass for a reason. An abstract class can extend a non-abstract class, and vice versa. Any time a concrete class is extending an abstract class, it must implement all of the methods that are inherited as abstract.

This compiles fine.

abstract class Mammal {
	abstract void showHorn();
	abstract void eatLeaf();
}

abstract class Rhino extends Mammal {
	void showHorn() {}
}

public class BlackRhino extends Rhino {
	void eatLeaf() {}
}

Reviewing Abstract Class Rules

For the exam you should know the following rules.

#### Abstract Class Definition Rules

  1. Abstract classes cannot be instantiated.
  2. All top-level types, including abstract classes, cannot be marked protected or private.
  3. Abstract classes cannot be marked final.
  4. Abstract classes may include zero or more abstract and nonabstract methods.
  5. An abstract class that extends another abstract class inherits all of its abstract methods.
  6. The first concrete class that extends an abstract class must provide an implementation for all of the inherited abstract methods.
  7. Abstract class constructors follow the same rules for initialization as regular constructors, except they can be called only as part of the initialization of a subclass.

Abstract Method Definition Rules

  1. Abstract methods can be defined only in abstract classes or interfaces.
  2. Abstract methods cannot be declared private or final.
  3. Abstract methods must not provide a method body/implementation in the abstract class in which they’re declared.
  4. Implementing an abstract method in a subclass follows the same ruels for overriding a method, including covariant return types, exception declarations etc.

Implementing Interfaces

Java allows a class to implement any number of interfaces. An interface is an abstract data type that declares a list of abstract methods that any class implementing the interface must provide. It can also include constant variables.
Both abstract methods and constant variables included with an interface are implicitly assumed to be public.

With Java 8, interfaces were updated to include static and default methods. A default method is one in which the interface method has a body and is not marked abstract. It was added for backward compatibility.

In Java 9, interfaces were updated to support private and private static methods. Both of these types were added for code reusability.

Defining an interface

Interface variables are referred to as constants, because they’re assumed to be public, static, and final. They’re initialized with a constant value when they’re declared.

Interfaces are not required to define any method.

public abstract interface WalksOnTwoLegs {}

An interface cannot be instantiated.

var e = new WalksOnTwoLegs(); // DOES NOT COMPILE

Interfaces cannot be marked as final.

public final interface WalksOnEightLegs {} // DOES NOT COMPILE

How do you use an interface?

interface Climb {
	Number getSpeed(int age);
}

public class FieldMouse implements Climb {
	public Float getSpeed(int age) {
		return 11f;
	}
}

The access modifier of the interface method is assumed to be public, although the concrete class must explicitly declare it.

If any of the interfaces implemented define abstract methods, then the concrete class is required to override them.

Like a class, an interface can extend another interface using extends. An interface, unlike a class, may extend multiple interfaces at once (a class may only extend one class).

interface Nocturnal {
	public int hunt();
}

interface CanFly {
	public void flap();
}

interface HasBigEyes extends Nocturnal, CanFly {}

Interfaces, unlike abstract classes, do not contain constructors and are not part of instance initialization. Interfaces simply define a set of rules that a class implementing them must follow.

  • A Java file may have at most one public top-level class or interface
  • A top-level class or interface can only be declared with public or package-private access.

What about Enums?

An enum is a specialized type that defines a set of fixed values. It’s declared with enum.

public enum Color {
	RED, YELOW, BLUE, GREEN;
}

Like classes and interfaces, enums can have more complex formations including methods, private constructors and instance variables.

Inserting Implicit Modifiers

An implicit modifier is one that the compiler will automatically insert.

  • Interfaces are assumed to be abstract.
  • Interface variables are assumed to be public, static, and final.
  • Interface methods without a body are assumed to be abstract, and public.

The following two interface definitions are equivalent, as the compiler will convert them both.

public interface Soar {
	int MAX_HEIGHT = 10;
	final static boolean UNDERWATER = true;
	void fly(int speed);
	abstract void takeoff();
	public abstract double dive();
}
public interface Soar {
	public static final int MAX_HEIGHT = 10;
	public final static boolean UNDERWATER = true;
	public abstract void fly(int speed);
	public abstract void takeoff();
	public abstract double dive();
}

Conflicting Modifiers

If a developer wants to mark a method or variable with an invalid modifier such as private or protected, it won’t compile, as the compiler will apply the public modifier to both.

public interface Dance {
	private int count = 4; // DOES NOT COMPILE
	protected void step(); // DOES NOT COMPILE
}

What about package-private? When working with interface members, the lack of access modifier always indicates public access, unlike classes.

(!) Spot compiler errors here (!)

1: private final interface Crawl {
2: 	String distance;
3: 	private int MAXIMUM_DEPTH = 100;
4: 	protected abstract boolean UNDERWATER = false;
5: 	private void dig(int depth);
6: 	protected abstract double depth();
7: 	public final void surface();
}

Line 1 doesn’t compile because it’s marked final, which cannot be applied to an interface. It’s also marked as private, which conflicts for top-level interfaces.
Line 2, because distance is not initialized.
Line 3 and 4, because they’re assumed to be public. Line 4 also again because variables cannot be marked abstract.
Line 5 and 6, because all interface abstract methods are assumed to be public.
Line 7, because it’s marked as final and methods without a body are assumed to be abstract, so it cannot be final.

Differences between Interfaces and Abstract Classes

Even though both are considered abstract types, only interfaces make use of implicit modifiers.

abstract class Husky {
	abstract void play();
}

interface Poodle {
	void play();
}

Both of these definitions are considered abstract, although Husky will not compile if play() is not marked as abstract, whereas the method in Poodle will compile fine either way.
They also don’t have the same access level. play() from Husky is considered package-private, whereas play() from Poodle is assumed to be public.

class Webby extends Husky {
	void play() {}
}

class Georgette implements Poodle {
	void play() {}
}

The class Webby compiles fine, but Georgette does not as it breaks overriding rules. It reduces the visibility from the method play() from public in Poodle to package-private in Georgette.

Inheriting an Interface

An interface can be inherited as

  • An interface can extend another interface.
  • A class can implement an interface.
  • A class can extend another class whose ancestor implements an interface.

When an interface is inherited, all of the abstract methods are inherited. If the type inheriting the interface is also abstract, then it’s not required to implement the interface methods. The first concrete subclass that inherits the interface must implement all of the inherited abstract methods.

Mixing Class and Interface Keywords

(!) The exam creators are fond of questions that mix class and interface terminology. (!)

Altough a class can implement an interface, a class cannot extend an interface. Likewise, an interface cannot implement another interface.

public interface CanRun{}
public class Cheetah extends CanRun {} // DOES NOT COMPILE

public class Hyena {}
public interface HasFur extends Hyena {} // DOES NOT COMPILE

Duplicate Interface Method Declarations

A class may inherit from two interfaces that contain the same abstract method.

public interface Herbivore {
	public void eatPlants();
}

public interface Omnivore {
	public void eatPlants();
	public void eatMeat();
}

public class Bear implements Herbivore, Omnivore {
	public void eatMeat() {
		sout("eating meat");
	}

	public void eatPlants() {
		sout("eating plants");
	}
}

You just need to be able to create a single method that overrides both inherited abstract methods at the same time.

If they have different method signatures it’s also not a problem, as it’s considered method overloading.

The methods are duplicates, but they’re also considered compatible. The compiler can resolve the differences between the two declarations without finding any conflicts.

If the duplicate methods have the same signature, but different return types, you need to implement it with return types that are covariant.

interface Dances {
	String swingArms();
}

interface EatsFish {
	CharSequence swingArms();
}

public class Penguin implements Dances, EatsFish {
	// covariant return type
	public String swingArms() {
		return "Swing!";
	}
}

If the return type is not covariant it won’t compile.

interface Dances {
	int countMoves();
}

interface EatsFish {
	boolean countMoves();
}

public class Penguin implements Dances, EatFish { // DOES NOT COMPILE

}

The same happens for a class or an interface.

interface LongEars {
	int softSkin();
}

interface LongNose {
	void softSkin();
}

// DOES NOT COMPILE
interface Donkey extends LongEars, LongNose {}

// DOES NOT COMPILE
abstract class Aardvark implements LongEars, LongNose {}

Polymorphism and Interfaces

Abstract Reference Types

When working with abstract types, you may prefer to work with the abstract reference type, rather than the concrete class.

import java.util.*;
public class Zoo {
	public void sortAndPrintZooAnimals(List<String> animals) {
		Collections.sort(animals);
		for(String a : animals) {
			sout(a);
		}
	}
}

The input may be any type of list, such as ArrayList or LinkedList instead of List.

Casting Interfaces

If you need access to a method that’s only declared in the concrete subclass, then you will need to cast the interface reference to that type, assuming the cast is supported at runtime.

The following is not permitted as the compiler detects they’re not related.

String l = "Bert";
Long t = (Long) l; // DOES NOT COMPILE

With interfaces, there’re limitations as to what the compiler can validate

interface Canine {}
class Dog implements Canine {}
class Wolf implements Canine {}

public class BadCasts {
	public static void main(String... args) {
		Canine canine = new Wolf();
		Canine badDog = (Dog) canine;
	}
}

This compiles, but it will throw a ClassCastException at runtime.

Interfaces and the instanceof Operator

The compiler has limited ability to report an error if two interfaces are related, because even though a reference type may not implement an interface, one of its subclasses could.

// COMPILES
Number tickets = 5;
if(tickets instanceof List) {}

A subclass that fits

public class MyNumber extends Number implements List {}

That said, the compiler can check for unrelated interfaces if this reference is a class that’s marked final.

Integer tickets = 6;
if(tickets instanceof List) {} // DOES NOT COMPILE

Reviewing Interface Rules

Interface Definition Rules

  1. Interfaces cannot be instantiated.
  2. All top-level types, including interfaces, cannot be marked protected or private.
  3. Interfaces are assumed to be abstract and cannot be marked final.
  4. Interfaces may include zero or more abstract methods.
  5. An interface can extend any number of interfaces.
  6. An interface reference may be cast to any reference that inherits the interface, although this may produce an exception at runtime if the classes aren’t related.
  7. The compiler will only report an unrelated type error for an instanceof operation with an interface on the right side if the reference on the left side is a final class that does not inherit the interface.
  8. An interface method with a body must be marked default, private, static or private static.

Abstract Interface Method Rules

  1. Abstract methods can be defined only in abstract classes or interfaces.
  2. Abstract methods cannot be declared private or final.
  3. Abstract methods must not provide a method body/implementation in the abstract class in which it’s declared.
  4. Implementing an abstract method in a subclass follows the same rules for overriding a method, including covariant return types, exception declaretions, etc.
  5. Interface methods without a body are assumed to be abstract and public.

The first 4 rules for abstract methods, whether they be defined in abstract classes or interfaces are exactly the same.

Interface Variable Rules

  1. Interface variables are assumed to be public, static, and final
  2. Because interface variables are marked final, they must be initialized with a value when they’re declared.

The primary difference between interfaces and abstract classes are that interfaces

  • Include implicit modifiers
  • Do not contain constructors
  • Do not participate in the instance initialization process
  • Support multiple inheritance

Introducing Inner Classes

Defining a Member Inner Class

A member inner class is a class defined at the member level of a class, or the same level as methods, instance variables, and constructors.
It is the opposite of a top-level class, in that it cannot be declared unless it’s inside another class.

This is useful is the relationship between the two classes is very close.

public class Zoo {
	private interface Paper {}
	public class Ticket implements Paper {}
}

While top-level classes and interfaces can only be set with public or package-private access, member inner classes don’t have the same restriction.
A member inner class can be declared with all access modifiers. Some members are disallowed in them though, such as static members.

public class Zoo {
	private interface Paper {
		public String getId();
	}

	public class Ticket implements Paper {
		private String serialNumber;
		public String getId() { return serialNumber; }
	}
}

Using a Member Inner Class

They can be used by calling it in the outer class.

public class Zoo {
	private interface Paper {
		public String getId();
	}

	public class Ticket implements Paper {
		private String serialNumber;
		public String getId() { return serialNumber; }
	}

	public Ticket sellTicket(String serialNumber) {
		var t = new Ticket();
		t.serialNumber = serialNumber;
		return t;
	}
}

The advantage here, is that Zoo completely manages the lifecycle of Ticket.

Summary

In this chapter, we presented advanced topics in class design, starting with abstract classes. An abstract class is just like a regular class except that it cannot be instantiated and may contain abstract methods. An abstract class can extend a nonabstract class, and vice versa. Abstract classes can be used to define a framework that other developers write subclasses against.

An abstract method is one that does not include a body when it is declared. An abstract method may be placed inside an abstract class or interface. Next, an abstract method can be overridden with another abstract declaration or a concrete implementation, provided the rules for overriding methods are followed. The first concrete class must implement all of the inherited abstract methods, whether they are inherited from an abstract class or interface.

An interface is a special type of abstract structure that primarily contains abstract methods and constant variables. Interfaces include implicit modifiers, which are modifiers that the compiler will automatically apply to the interface declaration. For the 1Z0-815 exam, you should know which modifiers are assumed in interfaces and be able to spot potential conflicts. When you prepare for the 1Z0-816 exam, you will study the four additional nonabstract methods that interfaces now support. Finally, while the compiler can often prevent casting to unrelated types, it has limited ability to prevent invalid casts when working with interfaces.

We concluded this chapter with a brief presentation of member inner classes. For the exam, you should be able to recognize member inner classes and know which access modifiers are allowed. Member inner classes, along with the other types of nested classes, will be covered in much more detail when you study for the 1Z0-816 exam.

Exam Essentials

Be able to write code that creates and extends abstract classes. In Java, classes and methodscan be declared as abstract. An abstract class cannot be instantiated. An instance of an abstract class can be obtained only through a concrete subclass. Abstract classes can include any number, including zero, of abstract and nonabstract methods. Abstract methods follow all the method override rules and may be defined only within abstract classes. The first concrete subclass of an abstract class must implement all the inherited methods. Abstract classes and methods may not be marked as final.

Be able to write code that creates, extends, and implements interfaces. Interfaces are specialized abstract types that focus on abstract methods and constant variables. An interface may extend any number of interfaces and, in doing so, inherits their abstract methods. An interface cannot extend a class, nor can a class extend an interface. A class may implement any number of interfaces.

Know the implicit modifiers that the compiler will automatically apply to an interface. All interfaces are assumed to be abstract. An interface method without a body is assumed to be public and abstract. An interface variable is assumed to be public, static, and final and initialized with a value when it is declared. Using a modifier that conflicts with one of these implicit modifiers will result in a compiler error.

Distinguish between top-level and inner classes/interfaces and know which access modifiers are allowed. A top-level class or interface is one that is not defined within another class declaration, while an inner class or interface is one defined within another class. Inner classes can be marked public, protected, package-private, or private.

Read More

Class Design

Understanding Inheritance

Inheritance is the process by which a subclass automatically includes any public or protected members of the class, including primitives, objects or methods defined in the parent class.

We refer to any class that inherits from another class a subclass or child class, as it’s considered a descendant of that class. Alternatively, we refer to the class that the child inherits from as the superclass or parent class, as it’s considered an ancestor of the class. Inheritance is transitive.

public class BigCat {
	public double size;
}

public class Jaguar extends BigCat {
	public Jaguar() {
		size = 10.2;
	}

	public void printDetails() {
		sout(size);
	}
}

size is accesible because it’s marked as public. Jaguar can read or write it as if it were its own member.

Single vs Multiple Inheritance

Java supports single inheritance, by which a class may inherit from only one direct parent class. It also supports multiple levels of inheritance, by which one class may extend another class, which in turn extends another class. You can have any number of levels of inheritance.

By design, Java doesn’t support multiple inheritance because it can lead to complex, often difficult-to-maintain data models. It allows though one exception - a class may implement multiple interfaces.

It is possible to prevent a class from being extended by marking it with final modifier. If you try to inherit from such a class, then the class will fail to compile.

Inheriting Object

All classes inherit from a single class: java.lang.Object. Object is the only class in Java that doesn’t have a parent class. You don’t need to extend Obect manually. The compiler automatically does the job for you, if you didn’t specify any extends to another class.

Primitives don’t inherit from Object, since they’re not classes.

Creating Classes

Extending a class

public abstract class ElephantSeal extends Seal {

}

Let’s check out an example

public class Animal {

	private int age;
	protected String name;

	public int getAge() {
		return age;
	}
	public void setAge(int newAge) {
		age = newAge;
	}

}

public class Lion extends Animal {
	public void setProperties(int age, String n) {
		setAge(age);
		name = n;
	}

	public void roar() {
		sout(name + ", age" + getAge() + ", says: Roar!");
	}
}

Lion may access to the getters, but be careful when accessing age directly, as it’s private and it wouldn’t compile.

public void roar() {
	sout("Lions age: " + age); // DOES NOT COMPILE
}

The name variable can be accessed directly, as it’s marked as protected.

Applying Class Access Modifiers

You can also apply access modifiers to class definitions. In Java, a top-level class is one that’s not defined inside another class. They can only have public or package-private access.

An inner class is a class defined inside of another class and is the opposite of a top-level class. Inner classes can also have protected and private access.

A Java file can have many top-level classes but at most one (or none) public top-level class. There’s also no requirement for the public class to be the first class in the file. One benefit of using the package-private access is that you can define many classes within the same Java file.

class Rodent {}

public class Groundhog extends Rodent {}

Accessing this reference

What do you think the following program prints?

public class Flamingo {
	private String color;

	public void setColor(String color) {
		color = color;
	}

	public static void main(String... unused) {
		Flamingo f = new Flamingo();
		f.setColor("PINK");
		sout(f.color);
	}
}

It prints null. When it sees color = color, it thinks you’re assigning the method parameter value to itself.

The fix is to use the this keyword. It refers to the current instance of the class and can be used to access any member of the class, including inherited members.
It cannot be used when there’s no implicit instance of the class, such as in a static method or static initializer block.

public void setColor(String color) {
	this.color = color;
}

Watch out for examples that aren’t common but you might see on the exam.

public void setColor(String color) {
	color = this.color; // BACKWARDS. NOT GOOD.  
}

If you see this example, be careful with data types and its default values.

Calling the super Reference

A variable or method can be defined in both a parent class and a child class. We reference the version in the parent or the current class with this and super. The super reference is similar to this reference, except that it excludes any members found in the current class. The member must instead be accessible via inheritance.

class Mammal {
	String type = "mammal";
}

public class Bat extends Mammal {
	String type = "bat";

	public String getType() {
		return super.type + ":" + this.type;
	}

	public static void main(String... zoo) {
		sout(new Bat().getType()); // mammal:bat
	}
}

Declaring Constructors

A constructor is a special method that matches the name of the class and has no return type. It’s called when a new instance of the class is created.

Creating a Constructor

public class Bunny {
	public bunny() { } // DOES NOT COMPILE
	public void Bunny() { } // VALID
}

The first one doesn’t match the class name, because it’s case sensitive, and it expects it to be a method, but it hasn’t a return type so it doesn’t compile.

The second one is perfectly valid but is a method, not a constructor as it has a return type.

(!) Like method parameters, constructor params can be any valid class, arrays… but may not include var. (!)

class Bonobo {
	public Bonobo(var food) { // DOES NOT COMPILE

	}
}

A class may have multiple constructors, so long as each constructor has a unique signature. This is refered to as constructor overloading.

public class Turtle {
	private String name;

	public Turtle() {
		name = "John Doe";
	}

	public Turtle(int age) {}
	public Turtle(long age) {}

	public Turtle(String age, String... favoriteFoods) {}
}

When calling the constructor with new keyword:

  1. Java allocates memory for the new object
  2. It looks for a constructor with a matching signature
  3. Calls it

Default constructor

Every class in Java has a constructor whether you code one or not. If you don’t include any constructors in the class, Java will create one for you without any parameters. This is called the default constructor and is added anytime a class is declared without any constructors.

public class Rabbit {
	public static void main(String... args) {
		Rabbit rabbit = new Rabbit(); // calls default constructor
	}
}

This happens during compile time.

public class Rabbit1 { } // DEFAULT CONSTRUCTOR

public class Rabbit2 {
	public Rabbit2() {} // NORMAL CONSTRUCTOR. IT'S NOT DEFAULT!
}

Having only private constructors in a class tells the compiler not to provide a default no-argument constructor. It also prevents other classes from instantiating the class.
This is useful when a class has only static methods or the developer wants to have full control of all calls to create new instances of the class.

(!) Remember, static methods in the class, including main(), may access private members and constructors. (!)

Calling Overloaded Constructors with this()

public class Hamster {
	private String color;
	private int weight;

	public Hamster(int weight) { // First constructor
		this.weight = weight;
		color = "brown";
	}

	public Hamster(int weight, String color) { // Second constructor
		this.weight = weight;
		this.color = color;
	}
}

For this example there’s a bit of duplication. To remove it, how can we have a constructor call another constructor?

public Hamster(int weight) {
	Hamster(weight, "brown"); // DOES NOT COMPILE
}

Constructors can be called only by writing new before the name of the constructor. They’re not like normal methdos that you can just call.

public Hamster(int weight) {
	new Hamster(weight, "brown"); // Compiles, but is incorrect
}

This would create a new object with default weight and color. It then constructs a different object with the desired weight and color.

public Hamster(int weight) {
	this(weight, "brown"); // correct solution
}

Instead, Java provides a solution this(). When it’s used with parentheses, it calls another constructor on the same instance of the class.

It has one rule though, if you choose to call it this() must be the first statement in the constructor. The side effect of this is that there can be only one call to this() in any constructor.

public Hamster(int weight) {
	sout("constructor"); // DOES NOT COMPILE
	this(weight, "brown");
}

There’s another rule, a constructor cannot call itself (or other constructors) in a loop.

public class Hamster {
	public Hamster(int food) {
		this(5); // DOES NOT COMPILE
	}
}
public class Hamster {
	public Hamster() {
		this(5); // DOES NOT COMPILE
	}

	public Hamster(int food) {
		this(); // DOES NOT COMPILE
	}
}

this vs this()

They’re very different. Be sure to know which is which.

  • this refers to an instance of the class.
  • this() refers to a constructor call within the class.

Calling Parent Constructors with super()

The first statement of every constructor is either a call to another constructor within the class, using this(), or a call to a constructor in the direct parent class, using super().

public class Animal {
	private int age;

	public Animal(int age) {
		super(); // refers to java.lang.Object
		this.age = age;
	}
}

public class Zebra extends Animal {
	public Zebra(int age) {
		super(age); // refers to animal
	}

	public Zebra() {
		this(4); // refers to Zebra with int arg
	}
}

Like calling this(), calling super() can only be used as the first statement of the constructor. The following example won’t compile.

public class Zoo {
	public Zoo() {
		sout("Zoo created");
		super(); // DOES NOT COMPILE
	}
}
public class Zoo {
	public Zoo() {
		super();
		sout("Zoo created");
		super(); // DOES NOT COMPILE
	}
}

If the parent class has more than one constructor, the child class may use any valid parent constructor in its definition.

super vs super()

super is used to reference members of the parent class, while super() calls a parent constructor.

Understanding Compiler Enhancements

The Java compiler automatically inserts a call to the no-argument constructor super() if you do not explicitly call this() or super().

Are Classes with only private Constructors Considered final?

A final class cannot be extended. If you have a class that’s not marked as final but only contains private constructors, you can extend the class, but only an inner class defined in the class itself can extend it.
An inner class is the only one that would have access to a private constructor and be able to all super().

Missing a Default No-Argument Constructor

What happens if the parent class doesn’t have a no-argument constructor? Remember it’s only inserted by the compiler only if there’s no constructor defined in the class.

public class Mammal {
	public Mammal(int age) {}
}

public class Elephant extends Mammal { // DOES NOT COMPILE
}

Since Elephant does not define any constructor, the Java compiler will attempt to insert a default no-argument constructor. It will also auto-insert a call to super(), but Mammal has at least one constructor, so the compiler does not insert a default no-args constructor. Therefore, it doesn’t compiles.

// the previous example is the same as:
public class Elephant extends Mammal {
	public Elephant() {
		super(); // DOES NOT COMPILE
	}
}

This is fixed by creating explicit constructor with an explicit call to the existing parent’s constructor.

public class Elephant extends Mammal {
	public Elephant() {
		super(10);
	}
}

(!) If an exam question has code regarding inheritance, check that the code compiles before answering a question about if. Be wary of any exam question in which a class defines a constructor that takes arguments and doesn’t define a no-argument constructor. (!)

super() always refers to the most direct parent

A class may have multiple ancestors via inheritance. For constructors, super() always refers to the most direct parent.

Constructors and final Fields

final static variables must be assigned a value exactly once. This may happen in the line of declaration and in a static initializer.

public class MouseHouse {
	private final int volume;
	private final String name = "The Mouse House";

	{
		volume = 10;
	}
}

They may also be initialized inside a constructor. By the time the constructor completes, all final instance variables must be assigned a value.

public class MouseHouse {
	private final int volume;
	private final String type;

	public MouseHouse() {
		this.volume = 10;
		type = "happy";
	}
}

Unlike local final variables, which are not required to have a value unless they’re actually used, final instance variables must be assigned a value. (!) Default values are not used for these variables. (!)
If they’re not assigned a value in the line where they’re declared or in an instance initializer, then they must be assigned a value in the constructor declaration. Failure to do so will result in a compiler error.

(!) On the exam, be wary of any instance variables marked final. Make sure they’re assigned a value in the line where they’re declared, in an instance initializer, or in a constructor. They should be assigned a value only once, and failure to assign a value is considered a compiler error in the constructor. (!)

Order of Initialization

Class Initialization

First you need to initialize the class, which involves invoking all static members in the class hierarchy, starting with the highest superclass and working downward. This is referred as loading the class.
The most important rule is that it happens at most once for each class.

Initialize Class X
  1. If there’s a superclass Y of X, the initialize class Y first.
  2. Process all static variable declarations in the order they appear in the class.
  3. Process all static initializers in the order they appear in the class.
public class Animal {
	static { sout("A"); }
}

public class Hippo extends Animal {
	static { sout("B"); }

	public static void main(String... grass) {
		sout("C");
		new Hippo();
		new Hippo();
		new Hippo();
	}
}

This will print ABC, since main() is inside Hippo class, the class will be initialized first, starting with its superclass.

A class must be initialized before it’s referenced or used. Also the class containing the program entry point is loaded before the main() method is executed.

Instance Initialization

An instance is initialized anytime the new keyword is used. Instance initialization is not the same as class initialization, because a class or superclass may have many constructors declared but only a handful used as part of instance initialization.

  1. First, start at the lowest level constructor where the new keyword is used.
    The first line of every constructor is a call this() or super() and if omitted, the compiler will automatically insert a call to the parent no-argument constructor super().
  2. Then, progress upward and note the order of constructors.
  3. Finally, initialize each class starting with the superclass, processing each instance initializer and constructor in the reverse order in which it was called.
Initialize Instance of X
Example 1
  1. If there’s a superclass Y of X, then initialize the instance of Y first.
  2. Process all instance variable declarations in the order they appear in the class.
  3. Process all instance initializers in the order they appear in the class.
  4. Initialize the constructor including any overloaded constructors referenced with this().
public class ZooTickets {
	private String name = "BestZoo";

	{ sout(name + "-"); }

	private static int COUNT = 0;

	static { sout(COUNT + "-"); }
	static { COUNT += 10; sout(COUNT+"-"); }

	public ZooTickets() {
		sout("z-");
	}

	public static void main(String... patrons) {
		new ZooTickets();
	}
}

This outputs 0-10-BestZoo-z-.

  1. First we have to initialize the class.
    Since there’s no superclass declared (which means it’s Object), we can start with the static components of ZooTickets.
  2. Next, we initialize the instance, so we start with the instance components.
  3. Finally, we run the constructor.
Example 2
class Primate {
	public Primate() {
		sout("Primate-");
	}
}

class Ape extends Primate {
	public Ape(int fur) {
		sout("Ape1-");
	}

	public Ape() {
		sout("Ape2-");
	}
}

public class Chimpanzee extends Ape {
	public Chimpanzee() {
		super(2);
		sout("Chimpanzee-");
	}

	public static void main(String... args) {
		new Chimpanzee();
	}
}

This outputs Primate-Ape1-Chimpanzee-.

Example 3
public class Cuttlefish {
	private String name = "swimmy";
	{ sout(name) }
	private static int COUNT = 0;
	static { sout(COUNT); }
	{ COUNT++; sout(COUNT); }

	public Cuttlefish() {
		sout("Constructor");
	}

	public static void main(String... args) {
		sout("Ready");
		new Cuttlefish();
	}
}

This outputs the following (wtf?)

0
Ready
swimmy
1
Constructor
  1. There’s no superclass declared, so we can skip any steps that relate to inheritance.
  2. We first process static variables, and static initializers.
  3. Then the main() method can run.
  4. Then goes instance initializers.
  5. Finally, the constructor.
Example 4

(This example is a bit too harsh, take a look on page 378 of the book to review it again)

class GiraffeFamily {
	static { sout("A"); }
	{ sout("B"); }

	public GiraffeFamily(String name) {
		this(1);
		sout("C");
	}

	public GiraffeFamily() {
		sout("D");
	}

	public GiraffeFamily(int stripes) {
		sout("E");
	}
}

public class Okapi extends GiraffeFamily {
	static { sout("F"); }

	public Okapi(int stripes) {
		super("sugar");
		sout("G");
	}

	{ sout("H"); }

	public static void main(String[] grass) {
		new Okapi(1);
		soutln();
		new Okapi(2);
	}
}

This outputs

AFBECHG
BECHG
  1. Start with initializing Okapi class. Since it has a superclass, initialize it first printing A.
  2. Next initialize Okapi, printing F.
  3. After the classes are initialized, execute main(). The first line creates a new Okapi object, triggering the instance initialization process. Per the first rule, the superclass instance is initialized first, and within it the instance initializer is called and B printed.
  4. Initialize the constructors, which calls the overloaded constructor. This prints EC.
  5. Initialization of the Okapi instance itself. This prints HG.
  6. Line break
  7. Initialization of a new Okapi object. The order is the same as the previous one, sans the class initialization, so BECHG is printed again.

Reviewing Constructor Rules

  1. The first statement of every constructor is a call to an overloaded construcctor via this(), or a direct parent constructor via super().
  2. If the first statement of a constructor is not either this() or super(), then the compiler will insert a no-argument super() as the first statement.
  3. Calling this() and super() after the first statement of a constructor results in a compiler error.
  4. If the parent class doesn’t have a no-argument constructor, then every constructor in the child class must start with an explicit this() or super()
  5. If the parent class doesn’t have a no-argument constructor and the child doesn’t define any constructor, then the child class will not compile.
  6. If a class only defines private constructors, then it cannot be extended by a top-level class.
  7. All final instance variables must be assigned a value exactly once by the end of the constructor. Any final instance variables not assigned a value will be reported as a compiler error on the line the constructor is declared.

Make sure you understand these rules. The exam will often provide code that breaks one or many of these rules and therefore, doesn’t compile.

Inheriting Members

Calling Inherited Members

Java classes may use any public or protected member of the parent class, including methods, primitives or object references. If the parent class and child class are part of the same package, then it may also use any package-private members defined in the parent class. Finally, a child class may never access a private member of the parent class.

Inheriting Methods

Inheriting a class also sets the stage for collisions between methods defined in both the parent and the subclass.

Overriding a Method

If there’s a method defined in both the parent and child classes, with the same signature and you want to define a new version of the method and have it behave differently for that subclass, the solution is to override the method in the child class.
Overriding a method occurs when a subclass declares a new implementation for an inherited method with the same signature (method’s name and parameters) and compatible return type.

When you override a method, you may reference the parent version using super. In this manner this and super allow you to select between the current and parent versions.

public class Canine {
	public double getAverageWeight() {
		return 50;
	}
}

public class Worf extends Canine {
	public double getAverageWeight() {
		return super.getAverageWeight() + 20;
	}

	public static void main(String... args) {
		sout(new Canine().getAverageWeight()); // 50.0
		sout(new Worlf().getAverageWeight()); // 70.0
	}
}

If we didn’t use super. in the previous example, the method would call it itself recursively in a closed loop.

To override a method, the compiler performs the following checks

  1. The method in the child class, must have the same signature as the parents’
  2. The method in the child class, must be at least as accessible as the method in the parent class.
  3. The method in the child class may not declare a checked exception that’s new or broader than the class of any exception declared in the parent.
  4. If the method returns a value, it must be the same or a subtype of the method in the parent class, known as covariant return types.
Overloading vs Overriding

An overloaded method will use a different list of method parameters. This allows overloaded methods a great deal more freedom in syntax than an overriden method would have.

First Rule - modify return type
public class Bird {
	public void fly() {
		sout("Bird is flying");
	}

	public void eat(int food) {
		sout("Bird is eating + food + " units of food);
	}
}

public class Eagle extends Bird {
	public int fly(int height) {
		sout("Bird is flying at " + height + " meters");
		return height;
	}

	public int eat(int food) { // DOES NOT COMPILE
		sout("Bird is eating " + food + " units of food");
		return food;
	}
}

The fly() method is overloaded in Eagle. Because it’s being overloaded and not overridden, the return type can be changed from void to int.

The eat() method is overridden in Eagle, since the signature is the same as in its parent class. The new return type must be compatible with the return type from the parent.

(!) Any time you see a method on the exam with the same name as a method in the parent class, determine whether the method is being overloaded or overriden first; doing so will help you with questions about whether the code will compile. (!)

Second Rule - access modifier
public class Camel {
	public int getNumberOfHumps() {
		return 1;
	}
}

public class BactrianCamel extends Camel {
	private int getNumberOfHumps() { // DOES NOT COMPILE
		return 2;
	}
}

This fails because it tries to override the method, but fails because the access modifier private is more restrictive than the one defined in the parent version.

Third Rule - Checked exceptions

Overriding a method cannot declare new checked exceptions or checked exceptions broader than the inherited method. It may declare though, a checked exception more restricted than the inherited version.

public class Reptile {
	protected void sleep() throws IOException {}

	protected void exit() throws FileNotFoundException {}
}

public class GalapagosTortoise extends Reptile {
	public void sleep() throws FileNotFouncException {} // COMPILES

	public void exit() throws IOException {} // DOES NOT COMPILE
}
Fourth Rule - Covariant return type

This is the most complicated, as it requires knowing the relationships between the return types. The overriding method must use a return type that is covariant with the return type of the inherited method.

public class Rhino {
	protected CharSequence getName() {
		return "rhino";
	}

	protected String getColor() {
		return "grey, black, or white";
	}
}

class JavanRhino extends Rhino {
	public String getName() {
		return "javan rhino";
	}

	public CharSequence getColor() { // DOES NOT COMPILE
		return "grey";
	}
}

You should know that String implemenents CharSequence interface, making it a subtype. All String values are CharSequence values, but not viceversa.

(!) How to check if they’re covariant?: Given an inherited return type A and an overriding return type B, can you assign an instance of B to a reference variable for A without a cast? If so, then they’re covariant. (!)

Overriding a Generic Method

Review of Overloading a Generic Method

You cannot overload methods by changing the generyc type, due to type erasure.

public class LongTailAnimal {
	protected void chew(List<Object>) input {}
	protected void chew(List<Double>) input {} // DOES NOT COMPILE
}

For the same reason, you also can’t overload a generic method in a parent class.

public class LongTailAnimal {
	protected void chew(List<Object> input) {}
}

public class Anteater extends LongTailAnimal {
	protected void chew(List<Double> input) {} // DOES NOT COMPILE
}

Both of them fail to compile because of type erasure. When compiled, the generic type is dropped.

Generic Method Parameters

You can override though a method with generic params, but you must match the signature exactly.

public class LongTailAnimal {
	protected void chew(List<String> input) {}
}

public class Anteater extends LongTailAnimal {
	protected void chew(List<String> input) {}
}

The generic class or interface may change, but then it’s considered overloading, not overriding as the method signature is not the same.

public class LongTailAnimal {
	protected void chew(List<String> input) {}
}

public class Anteater extends LongTailAnimal {
	protected void chew(ArrayList<String> input) {} // COMPILES
}
Generics and Wilcards

Java includes support for generic wildcards using the question mark ? character.

void sing1(List<?> v) {} // unbounded wildcard
void sing2(List<? super String> v) {} // lower bounded wildcard
void sing3(List<? extends String> v) {} // upper bounded wildcard
Generic Return Types

When you’re working with overriden methods that return generics, the return values must be covariant. In terms of generics, this means that the return type of the class or interface declared in the overriding method must be a subtype of the class defined in the parent class. The generic param type must match its parent’s type exactly.

public class Mammal {
	public List<CharSequence> play() {}
	public CharSequence sleep() {}
}

public class Monkey extends Mammal {
	public ArrayList<CharSequence> play() {}
}

public class Goat extends Mammal {
	public List<String> play() {} // DOES NOT COMPILE
	public String sleep() {}
}

Monkey compiles because ArrayList is a subtype of List.
The play() method in the Goat class doesn’t compile, because the generic type param must match. Even though String is a subtype of CharSequence, it doesn’t exactly match the generic type defined in Mammal.

Redeclaring private Methods

In Java, you can’t override private methods since they’re not inherited. Just because a child class doesn’t have access to the parent method doesn’t mean the child class can’t define its own version of the method. It just means, strictly speaking, that the new method is not an overriden version of the parent class’ method.

public class Camel {
	private String getNumberOfHumps() {
		return "Undefined";
	}
}

public class DromedaryCamel extends Camel {
	private int getNumberOfHumps() {
		return 1;
	}
}

The methods are unrelated. If the paren’t method were public or protected, the method in the child class wouldn’t compile.

Hiding Static Methods

A hidden method occurs when a child class defines a static method with the same name and signature as an inherited static method defined in a parent class.

It’s not exactly the same as method overriding. The same previous four rules apply here. In addition, a new rule is added:

The method defined in the child class must be marked as static if it’s marked as static in a parent class. If one is marked static and the other is not, the class will not compile.

public class Bear {
	public static void eat() {
		sout("Bear is eating");
	}
}

public class Panda extends Bear {
	public static void eat() {
		sout("Panda is chewing");
	}
}

This compiles and runs. The eat() method in Panda class hides the eat() method in Bear. Because they’re both marked static, this is not considered an overridden method.

public class Bear {
	public static void sneeze() {
		sout("Bear is sneezing");
	}

	public void hibernate() {
		sout("Bear is hibernating");
	}

	public static void laugh() {
		sout("Bear is laughing");
	}
}

public class Panda extends Bear {
	public void sneeze() { // DOES NOT COMPILE

	}

	public static void hibernate() { // DOES NOT COMPILE

	}

	protected static void laugh() { // DOES NOT COMPILE

	}
}
Creating final Methods

final methods cannot be replaced. By marking a method as final, you forbid a child class from replacing this method. This rule applies both, when you try to override a method and when you hide a method.

public class Bird {
	public final boolean hasFeathers() {
		return true;
	}

	public final static void flyAway() {}
}

public class Penguin extends Bird {
	public final boolean hasFeathers() { // DOES NOT COMPILE
		return false;
	}

	public final static void flyAway() {} // DOES NOT COMPILE
}
Hiding Variables

Java doesn’t allow variables to be overriden. They can be hidden, though.
A hidden variable occurs when a child class defines a variable with the same name as an inherited variable defined in the parent class.

class Carnivore {
	protected boolean hasFur = false;  
}

public class Meerkat extends Carnivore {
	protected boolean hasFur = true;

	public static void main(String... args) {
		Meerkat m = new Meerkat() ;
		Carnivore c = m;
		sout(m.hasFur()); // true
		sout(c.hasFur()); // false
	}
}

Understanding Polymorphism

Polymorphism is the property of an object to take on many different forms. A Java object may be accessed using a reference with the same type as the object, a reference that’s a superclass of the object, or a reference that defines an interface the object implements, either directly or through a superclass.

Interface Primer

  • An interface can define abstract methods.
  • A class can implement any number of interfaces.
  • A class implements an interface by overriding the inherited abstract methods.
  • An object that implements an interface can be assigned to a reference for that interface.
public class Primate {
	public boolean hasHair() {
		return true;
	}
}

public interface HasTail {
	public abstract boolean isTailStriped();
}

public class Lemur extends Primate implements HasTail {
	public int age = 10;

	public boolean isTailStriped() {
		return false;
	}

	public static void main(String[] args) {
		Lemur lemur = new Lemur();
		sout(lemur.age);

		HasTail hasTail = lemur;
		sout(hasTail.isTailStriped());

		Primate primate = lemur;
		sout(primate.hasHair());
	}
}

This compiles and outputs

10
false
true

There’s only one object (lemur) created and referenced. Polymorphism enables an instance of Lemur to be reassigned or passed to a method using one of its supertypes, such as Primate or HasTail.

Once the object has been assigned to a new reference type, only the methods and variables available to the reference type are callable on the object without an explicit cast.

HasTail hasTail = lemur;
sout(hasTail.age); // DOES NOT COMPILE  

Primate primate = lemur;
sout(primate.isTailStriped()); // DOES NOT COMPILE  

In this example, hasTail has direct access only to methods defined in HasTail interface.

Object vs Reference

In Java, all objects are accessed by reference. You should consider the object as the entity that exists in memory, allocated by the Java runtime environment. Regardless of the type of the reference you have for the object in memory, the object itself doesn’t change.

Since all objects inherit from Object, they can all be reassigned to it.

Lemur lemur = new Lemur();
Object lemurAsObject = lemur;

The object itself doesn’t change and still exists as a Lemur object in memory. What has changed though, is our ability to access methods within the Lemur class with the lemurAsObject reference. Without an explicit cast back to Lemur we no longer have access to the Lemur properties of the object.

  1. The type of the object determines which properties exist within the object in memory.
  2. The type of the reference to the object determines which methods and variables are accessible to the Java program.

Casting Objects

Once we change the reference type, we loose access to more specific members defined in the subclass that still exist within the object. We can reclaim those references by casting the object back to the specific subclass it came from.

Primate primate = new Lemur(); // implicit cast

Lemur lemur2 = primate; // DOES NOT COMPILE

Lemur lemur3 = (Lemur) primate;
sout(lemur3.age);

If the underlying object is not compatible with the type, then a ClassCastException will be thrown at runtime.

  1. Casting a reference from subtype to a supertype doesn’t require an explicit cast.
  2. Casting a reference from a supertype to a subtype requires an explicit cast
  3. The compiler disallows cast to an unrelated class.
  4. At runtime, an invalid cast of a reference to an unrelated type results in ClassCastException

(!) The exam may trick you with a cast that the compiler doesn’t allow. For example (!)

public class Bird {}

public class Fish {
	public static void main(String[] args) {
		Fish fish = new Fish();
		Bird bird = (Bird) fish; // DOES NOT COMPILE
	}
}

Casting is not without limitations.

public class Rodent {}

public class Capybara extends Rodent {
	public static void main(String[] args) {
		Rodent rodent = new Rodent();
		Capybara capybara = (Capybara) rodent; // ClassCastException
	}
}

Keep in mind the Rodent object created does not inherit the Capybara class in any way.

(!) When reviewing a question that involves casting and polymorphism, be sure to remember what the instance of the object actually is. Then, focus on whether the compiler will allow the object to be referenced with or without explicit casts. (!)

The instanceof Operator

It can be used to check whether an object belongs to a particular class or interface and to prevent ClassCastExceptions at runtime.

if(rodent instanceof Capybara) {
	Capybara capybara = (Capybara) rodent;
}

Just as the compiler doesn’t allow casting an object to unrelated types, it also doesn’t allow instanceof to be used with unrelated types.

Fish fish = new Fish();
if(fish instanceof Bird) { // DOES NOT COMPILE

}

Polymorphism and Method Overriding

Polymorphism states that when you override a method, you replace all calls to it, even those defind in the parent class.

class Penguin {
	public int getHeight() { return 3; }

	public void printInfo() {
		sout(this.getHeight());
	}
}

public class EmperorPenguin extends Penguin {
	public int getHeight() { return 8; }

	public static void main(String[] fish) {
		new EmperorPenguin().printInfo(); // prints 8
	}
}

The getHeight() method is overridden in the subclass, meaning all calls to it are replaced at runtime. Despite printInfo() being defined in the Penguin class, calling getHeight() on the object calls the method associated with the precise object in memory, not the current reference type where it’s called.

Remember, you can choose to limit polymorphic behaviour by marking methods final, which prevents them from being overriden by a subclass.

class Penguin {
	public int getHeight() { return 3; }

	public void printInfo() {
		sout(this.getHeight());
	}
}

public class EmperorPenguin extends Penguin {
	public int getHeight() { return 8; }

	public void printInfo() {
		sout(super.getHeight());
	}

	public static void main(String[] fish) {
		new EmperorPenguin().printInfo(); // prints 3!
	}
}

Overriding vs Hiding Members

Overriding replaces the method everywhere it’s called. static method and variable hiding does not. Hiding members is not a form of polymorphism since the methods and variables maintain their individual properties.

Hiding members is very sensitive to the reference type and location where the member is being used.

class Penguin {
	public static int getHeight() { return 3; }

	public void printInfo() {
		sout(this.getHeight());
	}
}

public class CrestedPenguin extends Penguin {
	public static int getHeight() { return 8; }

	public static void main(String[] fish) {
		new CrestedPenguin().printInfo(); // prints 3
	}
}

The printInfo() method is hidden, not overriden. Calling getHeight() in CrestedPenguin returns a different value than calling it in Penguin, even if the underlying object is the same.

This constrasts with overriding a method, where it returns the same value for an object regardless of which class it’s called in.

(!) It’s permitted to use an instance reference to access a static variable or method. It’s discouraged though, but it works. (!)

class Marsupial {
	protected int age = 2;

	public static boolean isBiped() {
		return false;
	}
}

public class Kangaroo extends Marsupial {
	protected int age = 6;

	public static boolean isBiped() {
		return true;
	}

	public static void main(String[] args) {
		Kangaroo k = new Kangaroo();
		Marsupial m = k;
		sout(k.isBiped());
		sout(m.isBiped());
		sout(k.age);
		sout(m.age);
	}
}

This outputs

true
false
6
2

In this example, only one object, of type Kangaroo is created and stored in memory. Since static methods can only be hidden and not overriden, Java uses the reference type to determine which version of isBiped() should be called, resulting in k.isBiped() printing true and m.isBiped() printing false.
Likewise, the age variable is hidden, not overriden, so the reference type is used to determine which value to output.

Summary

This chapter took the basic class structures we’ve presented throughout the book and expanded them by introducing the notion of inheritance. Java classes follow a multilevel single-inheritance pattern in which every class has exactly one direct parent class, with all classes eventually inheriting from java.lang.Object.

Inheriting a class gives you access to all of the public and protected members of the class. It also gives you access to package-private members of the class if the classes are in the same package. All instance methods, constructors, and instance initializers have access to two special reference variables: this and super. Both this and super provide access to all inherited members, with only this providing access to all members in the current class declaration.

Constructors are special methods that use the class name and do not have a return type. They are used to instantiate new objects. Declaring constructors requires following a number of important rules. If no constructor is provided, the compiler will automatically insert a default no-argument constructor in the class. The first line of every constructor is a call to an overloaded constructor, this(), or a parent constructor, super(); otherwise, the compiler will insert a call to super() as the first line of the constructor. In some cases, such as if the parent class does not define a no-argument constructor, this can lead to compilation errors. Pay close attention on the exam to any class that defines a constructor with arguments and doesn’t define a no-argument constructor.

Classes are initialized in a predetermined order: superclass initialization; static variables and static initializers in the order that they appear; instance variables and instance initializers in the order they appear; and finally, the constructor. All final instance variables must be assigned a value exactly once. If by the time a constructor finishes, a final instance variable is not assigned a value, then the constructor will not compile.

We reviewed overloaded, overridden, hidden, and redeclared methods and showed how they differ, especially in terms of polymorphism. A method is overloaded if it has the same name but a different signature as another accessible method. A method is overridden if it has the same signature as an inherited method, with access modifiers, exceptions, and a return type that are compatible. A static method is hidden if it has the same signature as an inherited static method. Finally, a method is redeclared if it has the same name and possibly the same signature as an uninherited method.

We also introduced the notion of hiding variables, although we strongly discourage this in practice as it often leads to confusing, difficult-to-maintain code.

Finally, this chapter introduced the concept of polymorphism, central to the Java language, and showed how objects can be accessed in a variety of forms. Make sure you understand when casts are needed for accessing objects, and be able to spot the difference between compile-time and runtime cast problems.

Exam Essentials

Be able to write code that extends other classes. A Java class that extends another class inherits all of its public and protected methods and variables. If the class is in the same package, it also inherits all package-private members of the class. Classes that are marked final cannot be extended. Finally, all classes in Java extend java.lang.Object either directly or from a superclass.

Be able to distinguish and make use of this, this(), super, and super(). To access a current or inherited member of a class, the this reference can be used. To access an inherited member, the super reference can be used. The super reference is often used to reduce ambiguity, such as when a class reuses the name of an inherited method or variable. The calls to this() and super() are used to access constructors in the same class and parent class, respectively.

Evaluate code involving constructors. The first line of every constructor is a call to another constructor within the class using this() or a call to a constructor of the parent class using the super() call. The compiler will insert a call to super() if no constructor call is declared. If the parent class doesn’t contain a no-argument constructor, an explicit call to the parent constructor must be provided. Be able to recognize when the default constructor isprovided. Remember that the order of initialization is to initialize all classes in the class hierarchy, starting with the superclass. Then, the instances are initialized, again starting with the superclass. All final variables must be assigned a value exactly once by the time the constructor is finished.

Understand the rules for method overriding. Java allows methods to be overridden, or replaced, by a subclass if certain rules are followed: a method must have the same signature, be at least as accessible as the parent method, must not declare any new or broader exceptions, and must use covariant return types. The generic parameter types must exactly match in any of the generic method arguments or a generic return type. Methods marked final may not be overridden or hidden.

Understand the rules for hiding methods and variables. When a static method is overridden in a subclass, it is referred to as method hiding. Likewise, variable hiding is when an inherited variable name is reused in a subclass. In both situations, the original method or variable still exists and is accessible depending on where it is accessed and the reference type used. For method hiding, the use of static in the method declaration must be the same between the parent and child class. Finally, variable and method hiding should generally be avoided since it leads to confusing and difficult-to-follow code.

Recognize the difference between method overriding and method overloading. Both method overloading and overriding involve creating a new method with the same name as an existing method. When the method signature is the same, it is referred to as method overriding and must follow a specific set of override rules to compile. When the method signature is different, with the method taking different inputs, it is referred to as method overloading, and none of the override rules are required. Method overriding is important to polymorphism because it replaces all calls to the method, even those made in a superclass.

Understand polymorphism. An object may take on a variety of forms, referred to as polymorphism. The object is viewed as existing in memory in one concrete form but is accessible in many forms through reference variables. Changing the reference type of an object may grant access to new members, but the members always exist in memory.

Recognize valid reference casting. An instance can be automatically cast to a superclass or interface reference without an explicit cast. Alternatively, an explicit cast is required if the reference is being narrowed to a subclass of the object. The Java compiler doesn’t permit casting to unrelated class types. Be able to discern between compiler-time casting errors and those that will not occur until runtime and that throw a ClassCastException.

Read More

Methods and Encapsulation

Designing methods

Two of the methods parts - method name and parameter list - are called the method signature.

Access Modifiers

  • private. It means the method can be called only from within the same class.
  • default package-private. The method can be called only from classes in the same package.
  • protected. It means the method can be called only from classes in the same package or subclasses.
  • public. Means the method can be called from any class.

(!) The exam creators like to trick you by putting method elements in the wrong order or using incorrect values (!)

public void walk1() {}
default void walk2() {} // DOES NOT COMPILE
void public walk3() {} // DOES NOT COMPILE
void walk4() {}

Optional Specifiers

  • static. It’s used for class methods
  • abstract. It’s used when a method body is not provided
  • final It’s used when a method is not allowed to be overriden by a subclass
  • synchronized It’s used with multithreaded code
  • native it’s used when interacting with code written in another language, such as C++
  • strictfp. It’s used for making float-point calculations portable
public void walk() {}
public final void walk() {}
public static final void walk() {}
public final static void walk() {}
public modifier void walk5() {} // DOES NOT COMPILE
public void final walk() {} // DOES NOT COMPILE
final public void walk() {}

Return Type

Methods with a return type other than void are required to have a return statement inside the method body.

public void walk() {}
public void walk() { return; }
public String walk() { return ""; }
public String walk() {} // DOES NOT COMPILE
public walk() {} // DOES NOT COMPILE
public String int walk() {} // DOES NOT COMPILE
String walk(int a) { if(a==4) return ""; } // DOES NOT COMPILE
int longMethod() {
	return 9L; // DOES NOT COMPILE
}

Method Name

They follow the same rules as variable names.

  • They mey only contain letters, numbers, $ or _
  • The first character is not allowed to be a number.
  • Reserved words are not allowed.
  • A single underscore char is not allowed.
public void walk1() {}
public void 1walk() {} // DOES NOT COMPILE
public walk1 void() {} // DOES NOT COMPILE
public void Walk_$() {}
public _() {} // DOES NOT COMPILE
public void() {} // DOES NOT COMPILE

Parameter List

Altough the list is required, it doesn’t have to contain any parameters. If you have multiple parameters, you separate them with a comma.

public void walk() {}
public void walk {} // DOES NOT COMPILE
public void walk(int a) {}
public void walk(int a; int b) {} // DOES NOT COMPILE
public void walk(int a, int b) {}

Optional Exception List

You can list as many types of exceptions as you want, separated by commas

public void zeroExceptions() {}
public void oneException() throws IllegalArgumentException {}
public void twoExceptions() throws IllegalArgumentException, InterruptedException {}

Method Body

It’s simply a code of block. It’s required.

Working with Varargs

A varargs parameter must be the last element in a method’s parameter list.

public void walk(int)... nums {}
public void walk(int start, int... nums) {}
public void walk(int... nums, int start) {} // DOES NOT COMPILE
public void walk(int... start, int... nums) {} // DOES NOT COMPILE

When calling a method with vararg params, you can either pass in an array, or list the elements of the array and let Java create it for you. You can even omit the varargs values in the method call and it will create an array of 0 length.

public static void walk(int start, int... nums) {
	sout(nums.length());
}

public static void main(String... args) {
	walk(1); // 0
	walk(1, 2); // 1
	walk(1, 2, 3); // 2
	walk(1, new int[] {2, 3}); // 2

	walk(1, null); // NullPointerException
}

Applying Access Modifiers

Private Access

Only code in the same class can call private methods or access private fields.

Default (Package-Private) Access

When there’s no access modifier, Java uses the default, which is package-private. This means that the member is “private” to classes in the same package. In other words, only classes in the package may access it.

Protected Access

It allows everything that default access allows and more. It adds the ability to access members of a parent class.
The definition of protected allows access to subclasses and classes in the same package.

We have the following class with protected fields and methods

package pond.shore;

public class Bird {
	protected String text = "floating";

	protected void floatInWater() {
		sout(text);
	}
}

Now we create a subclass

package pond.goose;

import pond.shore.Bird; // different package

public class Gosling extends Bird {

	public void swim() {
		floatInWater(); // calling protected member
		sout(text); // accessing protected member
	}

}

Class in same package, but not a subclass

package pond.shore; // same package as Bird

public class BirdWatcher {

	public void watchBird() {
		Bird bird = new Bird();
		bird.floatInWater(); // calling protected member
		sout(bird.text); // accessing protected member
	}

}

Class in different package, not a subclass

package pond.inland;

import pond.shore.Bird; // different package

public class BirdWatcherFromAfar {

	public void watchBird() {
		Bird bird = new Bird();
		bird.floatInWater(); // DOES NOT COMPILE
		sout(bird.text); // DOES NOT COMPILE
	}

}

Subclasses and classes in the same package are the only ones allowed to access protected members.

Check the following case

package pond.swan;

import pond.shore.Bird; // different package than Bird

public class Swan extends Bird { // subclass of Bird

	public void swim() {
		floatInWater(); // subclass access to superclass
	}

	public void helpOtherSwanSwim() {
		Swan other = new Swan();
		other.floatInWater(); // subclass access to superclass (is same class)
	}

	public void helpOtherBirdSwim() {
		Bird other = new Bird();
		other.floatInWater(); // DOES NOT COMPILE
	}

}

Altough Bird is the superclass, it’s referenced as a variable rather than inherited. This is why it won’t work.

The protected rules apply under two scenarios:

  • A member is used without referring to a variable. We are taking advantage of inheritance and protected access is allowed.
  • A member is used through a variable. If it’s a subclass, protected access is allowed.

Public Access

public means anyone can access the member from anywhere.

Applying the static keyword

The static keyword applies to the class, rather than a specific instance of the class.

Designing static Methods and Fields

static methods don’t require an instance of the class. They’re shared among all users of the class. It exists independently of any instances of that class.

public class KoalaTester {
	public static void main(String... args) {
		Koala.main(new String[0]);
	}
}

They have two main purposes

  • For utility or helper methods that don’t require any object state, since there’s no need to access instance variables.
  • For state that’s shared by all instances of a class, like a counter.

Accessing a static Variable or Method

You can use an instance of the object to call a static method.
This is legal.

spit(Koala.count); // 0

Koala k = new Koala();
sout(k.count); // 0

k = null;
sout(k.count); //0! This still works

For the last example, Java doesn’t care that k happens to be null, since we are looking for a static it doesn’t matter.

Static vs Instance

A static member cannot call an instance member without referencing an instance of the class.

public class Static {
	private String name = "Static class";

	public static void first() {}
	public static void second() {}
	public void third() {sout(name)}
	public static void main(String... args) {
		first();
		second();
		third(); // DOES NOT COMPILE
		// new Static().third(); // would compile
	}
}

Another example

1: 	public class Gorilla {
2: 		public static int count;
3: 		public static void addGorilla() { count++; }
4: 		
5: 		public void babyGorilla() { count++; }
6: 		
7:	 	public void announceBabies() {
8: 			addGorilla();
9: 			babyGorilla();
10: 	}
11: 	
12: 	public static void announceBabiesToEveryone() {
13: 		addGorilla();
14: 		babyGorilla(); // DOES NOT COMPILE
15: 	}
16: 	
17: 	public int total;
18: 	public static double average = total / count; // DOES NOT COMPILE
19: }

Lines 7-10 are fine because both static and instance methods can refer to a static variable.

Lines 12-15 doesn’t compile because a static method cannot call an instance method.

Line 18 doesn’t compile because a static variable is trying to use an instance variable.

static Variables

Some static variables are meant to change as the programs runs, such as counters.

public class Initializers {
	private static int counter = 0;
}

Others are meant to never change during the program. This type of variable is known as a constant.

public class Initializers {
	private static final int NUM_BUCKETS = 435;

	public static void main(String... args) {
		NUM_BUCKETS = 5; // DOES NOT COMPILE
	}
}

Static Initialization

static initialization specifies they should be run when the class is first loaded.

private static final int NUM_SECONDS_PER_MINUTE;

static {
	NUM_SECONDS_PER_MINUTE = 60;
}

They’re run when the class is first used in the order they’re defined. The statements in them run and assign any static variables as needed.

final variables aren’t allowed to be reassigned.

private static int one;
private static final int two;
private static final int three = 3;
private static final int four; // DOES NOT COMPILE

static {
	one = 1;
	two = 2;
	three = 3; // DOES NOT COMPILE
	two = 20; // DOES NOT COMPILE
}
  • one compiles because it’s not final, so it can be re-assigned as many times as we want
  • two is final but not initialized until later. as final it can only be assigned once.
  • three was already assigned when created.
  • four never gets initialized and as final, static blocks are the only place where it could be initialized.

(!) They’re good for when you need to initialize a static field and the code required to do so requires more than one line. This occurs when you want to initialize a collection like an ArrayList.

Static Imports

import java.utils.List;
import static java.util.Arrays.asList; // static import

public class StaticImports {
	public static void main(String... args) {
		List<String> list = asList("one", "two"); // no Arrays.
	}
}

Regular imports are for importing classes. Static imports are for importing static member of classes.
The idea is that you shouldn’t have to specify where each static method or variable comes from each time you use it.

If we created an asList method in our class, Java would give it preference over the imported one, and our method would be used.

1: import static java.util.Arrays; // DOES NOT COMPILE
2: import static java.util.Arrays.asList;
3: static import java.util.Arrays.*; // DOES NOT COMPILE

4: public class BadStaticImports {
5: 	public static void main(String... args) {
6: 		Arrays.asList("one"); // DOES NOT COMPILE
7: 	}
8: }

Line 1 fails because static imports are only for importing static members.
Line 6 fails, because the asList method is imported on line 2, however the Arrays class is not imported anywhere. This makes it okay to write asList("one"), but not Arrays.asList("one").

You cannot do an static import of two methods with the same name or two static variables with the same name.

import static statics.A.TYPE;
import static statics.B.TYPE; // DOES NOT COMPILE

Passing Data among Methods

Java is pass-by-value. A copy of the variable is made and the method receives that copy. Assignments made in the method do not affect the caller.

This is an example for variable assignments.

public static void main(String... args) {
	String name = "Webby";
	speak(name);
	sout(name); // Webby
}

public static void speak(String name) {
	name = "Sparky";
}

We can also call methods on the parametes passed into methods.

public static void main(String... args) {
	StringBuilder name = new StringBuilder();
	speak(name);
	sout(name); // Webby
}

public static void speak(StringBuilder s) {
	s.append("Webby");
}

In the last case, the output is Webby because the method calls a method on the param. It doesn’t reassign name to a different object.

Getting data back from a method is easier. A copy is made of the primitive or reference and returned from the method. Most of the time, this returned value is used or stored in a variable. If the return value is not used, the result is ignored.

public class ReturningValues {
	public static void main(String... args) {
		int number = 1; // number = 1
		String letters = "abc"; // letters = abc
		number(number); // still, number = 1
		letters = letters(letters); // letters = abcd
	}

	public static int number(int number) {
		number++;
		return number;
	}

	public static String letters(String letters) {
		letters += "d";
		return letters;
	}
}

Overloading Methods

Method overloading occurs when methods have the same name but different method signatures, which means they differ by method parameters. Aside from this, they may have different access modifiers, specifiers (static), return types and exception lists.

These are all valid overloaded methods.

public void fly(int numMiles) {}
public void fly(short numFeet) {}
public boolean fly() { return false; }
void fly(int numMiles, short numFeet) {}
public void fly(short numFeet, int numMiles) throws Exception {}

We can overload by changing anything in the parameter list.

public void fly(int numMiles) {}
public fly(int numMiles) {} // DOES NOT COMPILE

This method doesn’t compile because it differs from the original only by return type.

public void fly(int numMiles) {}
public static void fly(int numMiles) {} // DOES NOT COMPILE

Again, the parameter list is the same. They must differ.

varargs

public void fly(int[] lengths) {}
public void fly(int... lengths) {} // DOES NOT COMPILE

Remember that Java treats varargs as if they were an array. This means that the method signature is the same for both methods.

Autoboxing

public void fly(int numMiles) {}
public void fly(Integer numMiles) {}

When the primitive version isn’t present, Java will autobox. However, when the primitive int version is provided, there’s no reason for Java to do the extra work of autoboxing.

Reference Types

public class ReferenceTypes {
	public void fly(String s) {
		sout("string");
	}

	public void fly(Object o) {
		sout("object")
	}

	public void main(String[] args) {
		ReferenceTypes r = new ReferenceTypes();
		r.fly("test");
		sout("-");
		r.fly(56)
	}
}

This prints string-object. When it doesn’t find a primitive int version, it autoboxes into Integer, and then it fits as Object.

Primitives

Primitives work in a way that’s similar to reference variables. Java tries to find the most specific matching overloaded method.

public class Plane {
	public void fly(int i) {
		sout("int");
	}

	public void fly(long l) {
		sout("long");
	}

	public static void main(String[] args) {
		Plane p = new Plane();
		p.fly(123);
		sout("-");
		p.fly(123L);
	}
}

This will print int-long. Java can only accept wider types. It won’t automatically convert to a narrower type.

Generics

You might be surprised that these are not valid overloads.

public void walk(List<String> strings) {}
public void walk(List<Integer> integers) {} // DOES NOT COMPILE

Java hasa concept called type erasure, where generics are used only at compile time. This means the compiled code looks like this

public void walk(List strings) {}
public void walk(List integers) {} // DOES NOT COMPILE

Arrays

Unlike the previous example, this code works just fine

public static void walk(int[] ints) {}
public static void walk(Integer[] integers) {}

Encapsulating Data

Encapsulation means only methods in the class with the variables can refer to the instance variables. Callers are required to use these methods.

public class Swan {
	private int eggs; // private

	public int getNumberEggs() { // getter
		return eggs;
	}

	public void setNumberEggs(int newNumber) { // setter
		if(newNumber >= 0) { // guard condition
			eggs = newNumber;
		}
	}
}

Exam Essentials

Be able to identify correct and incorrect method declarations. A sample method declaration is public static void method(String… args) throws Exception {}.

Identify when a method or field is accessible. Recognize when a method or field is accessed when the access modifier (private, protected, public, or default access) does not allow it.

Recognize valid and invalid uses of static imports. Static imports import static members. They are written as import static, not static import. Make sure they are importing static methods or variables rather than class names.

State the output of code involving methods. Identify when to call static rather than instance methods based on whether the class name or object comes before the method. Recognize that instance methods can call static methods and that static methods need an instance of the object in order to call an instance method.

Recognize the correct overloaded method. Exact matches are used first, followed by wider primitives, followed by autoboxing, followed by varargs. Assigning new values to method parameters does not change the caller, but calling methods on them does.

Identify properly encapsulated classes. Instance variables in encapsulated classes are private. All code that retrieves the value or updates it uses methods. These methods are allowed to be public.

Read More

Java Fundamentals

Working with Enums

An Enum is a type that can only have a finite set of values. Like a fixed set of contants, but they’re better because they provide type-safe checking.

Creating Simple Enums

public enum Season {
	WINTER, SPRING, SUMMER, FALL
}

How to use an Enum

Season s = Season.SUMMER;
sout(Season.SUMMER); // SUMMER
sout(s == Season.SUMMER); // true

They can be compared using == because they’re like static final constants. You can also use equals() to compare enums.

An enum provides a values() method to get an array of all the values.

for(Season season: Season.values()) {
	sout(season.name() + " " + season.ordinal());
}

This outputs

WINTER 0
SPRING 1
SUMMER 2
FALL 3

You can’t compare an int and enum value directly.

if(Season.SUMMER == 2) // DOES NOT COMPILE

You can retrieve an enum value from a String using valueOf(). The String passed in must match the enum value exactly.

Season s = Season.valueOf("SUMMER"); // SUMMER

Season t = Season.valueOf("summer"); // IllegalArgumentException

You can’t extend an enum

public enum ExtendedSeason extends Season {} // DOES NOT COMPILE

Using Enums in Switch Statements

Season summer = Season.SUMMER;

switch(summer) {
	case WINTER:
		sout("get out the sled");
		break;
	case SUMMER:
		sout("time for the pool");
		break;
	default:
		sout("is it summer yet?");
		berak;
}

Java treats the enum type as implicit. So neither something like Season.FALL nor case 0 would compile. It has to be FALL.

switch(summer) {
	case Season.FALL: // DOES NOT COMPILE
		break;
	case 0: // DOES NOT COMPILE
		break;
}

(!) On the exam, pay special attention when working with enums that they’re used only as enums (!)

Adding Constructors, Fields, and Methods

Enums may hold more than just a list of values.

public enum Season {
	WINTER("Low"),
	SPRING("Medium"),
	SUMMER("High"),
	FALL("Medium");

	private final String expectedVisitors;

	private Season(String expectedVisitors) {
		this.expectedVisitors = expectedVisitors;
	}

	public void printExpectedVisitors() {
		sout(expectedVisitors);
	}
}

The semicolon ; is option just if there’s only a list of values. It’s required if there’s anything else.

All enum constructors are implicitly private, with the modifier being optional. This is reasonable since you can’t extend an enum and the constructor acn be called only within itself. An Enum will not compile if it contains a public or protected modifier.

How to call an enum method.

Season.SUMMER.printExpectedVisitors();

(!) The first time we ask for any enum value, Java constructs all of the enum values. After that, Java just returns the already constructed enum values. (!)

Sometimes, we need to get track of a list of values. We can create an abstract method, and implement it. Every value has to implement it.

public enum Season {
	WINTER {
		public String getHours() {
			return "10am-3pm";
		}
	},
	SPRING {
		public String getHours() {
			return "9am-5pm";
		}
	},
	SUMMER {
		public String getHours() {
			return "9am-7pm";
		}
	},
	FALL {
		public String getHours() {
			return "9am-5pm";
		}
	},
}

If one value doesn’t implement it. It will throw an exception.
If we don’t want each value to implement it, we may create a default implementation and override it only for the special cases.

public enum Season {
	WINTER {
		public String getHours() { return "10am-3pm"; }
	},
	SUMMER {
		public String getHours() { return "9am-7pm"; }
	},
	SPRING,FALL;
	public String getHours() { return "9am-5pm"; }
}

Creating Nested Classes

A nested class is a class that’s defined within another class. It can be:

  • Inner class - A non-static type defined at the member level of a class
  • static nested class - a static type defined at the member level of a class
  • local class - a class defined within a method body
  • anonymous class - a special case of local class that does not have a name.

Nested classes can encapsulate helper classes by restricting them to the containing class. They can make it easy to craete a class that will be used in only one place.

Declaring an Inner Class

An inner class or member inner class is a non-static type defined at the member level of a class. Inner classes have the following properties

  • they can be declared public, protected, default or private
  • they can extend any class and implement interfaces
  • be marked abstract or ginal
  • cannot declare static fields or methods, except for static final fields
  • can access members of the outer class including private members
public class Outer {
	private String greeting = "hi";

	protected class Inner {
		public void go() {
			sout(greeting);
		}
	}

	public void callInner() {
		Inner inner = new Inner();
		inner.go();
	}
}

Since an inner class is not static, it has to be used with an instance of the outer class.
There’s another way to instantiate Inner.

Outer outer = new Outer();
Inner inner = outer.new Inner(); // create the inner class
inner.go();

We need an instance of Outer to create Inner. We can’t just call new Inner(). We call new as if it was a method on the outer variable.

Inner classes can have the same variable names as outer classes.

public class A {
	private int x = 10;

	class B {
		private int x = 20;

		class C {
			private int x = 30;

			public void allTheX() {
				sout(x); // 30
				sout(this.x); // 30
				sout(B.this.x); // 20
				sout(A.this.x); // 10
			}
		}
	}

	public static void main(String[] args) {
		A a = new A();
		A.B b = a.new B();
		A.B.C c = b.new C();
		c.allTheX();
	}
}

Inner Classes Require an Instance

public class Fox {
	private class Den {}

	public void goHome() {
		new Den();
	}

	public static void visitFriend() {
		new Den(); // DOES NOT COMPILE
	}
}

public class Squirrel {
	public void visitFox() {
		new Den(); // DOES NOT COMPILE
	}
}

The first constructor compiles, because goHome() is an instance method.
The second doesn’t because it’s called inside a static method. You can still call the constructor, but you have to explicitly give it a reference to Fox instead.
The last one doesn’t, because it’s not an instance method inside the Fox class. Den is private and only accessible inside Squirrel.

Creating a static Nested Class

A static nested class is a static type defined at the member leve. It can be instantiated without an instance of the enclosing class. It can’t access instance variables or methods in the outer class directly.

It’s like a top-level class except for:

  • It can be made private or use any other access modifiers.
  • The enclosing class can refer to the fields and methods of it
public class Enclosing {
	static class Nested {
		private int price = 6;
	}

	public static void main(String[] args) {
		Nested nested = new Nested();
		sout(nested.price);
	}
}

Since the class is static, you don’t need an instance of Enclosing to use it. You’re also allowed to access private instance variables.

Importing a static Nested Class

You can import it using a regular import, or, since it’s static you can also use a static import.

package bird;
public class Toucan {
	public static class Beak {}
}
package watcher;

import bird.Toucan.Beak;

public class BirdWatcher {
	Beak beak;
}

or

import static bird.Toucan.Beak; // also ok

Writing a Local Class

A local class is a nested class defined within a method. It does not exist until the method is invoked, and it goes out of scope when the method returns. You can create instances only from within the method and you can return those instances.

They can be declared inside methods, constructors and initializers.

They have the following properties:

  • They don’t have access modifiers
  • They cannot be declared static and cannot declare static fields or methods, except for static final
  • They have access to all fields and methods of the enclosing class
  • They can access local variables if the variables are final or effectively final
public class PrintNumbers {
	private int length = 5;

	public void calculate() {
		final int width = 20;

		class MyLocalClass {
			public void multiply() {
				sout(length * width);
			}
		}

		MyLocalClass local = new MyLocalClass();
		local.multiply();
	}

	public static void main(String[] args) {
		PrintNumbers outer = new PrintNumbers();
		outer.calculate();
	}
}

At this example, we can refer to length because it’s effectively final. The following does not compile.

public void processData() {
	final int length = 5;
	int width = 10;
	int height = 2;

	class VolumeCalculator {
		public int multiply() {
			return length * with * height; // DOES NOT COMPILE
		}
	}

	width = 8;
}

Defining an Anonymous Class

An anonymous class is a specialized form of a local class, that does not have a name. It’s declared and instantiated all in one statement. They’re required to extend an existing class or implement an existing interface.
They’re useful when you have a short implementation that will not be used anywhere else.

public class ZooGiftShop {
	abstract class SaleTodayOnly {
		abstract int dollarsOff();
	}

	public int admission(int basePrice) {
		SaleTodayOnly sale = new SaleTodayOnly() {
			int dollarsOff() { return 3; }
		};
		return basePrice - sale.dollarsOff();
	}
}

They can’t both extend a class and implement an interface. You have to choose.

You can define anonymous classes right where they’re needed, even if that’s an argument to a method.

public class ZooGiftShop {
	interface SaleTodayOnly {
		int dollarsOff();
	}

	public int pay() {
		return admission(5, new SaleTodayOnly() {
			public int dollarsOff() { return 3; }
		})
	}

	public int admission(int basePrice, SaleTodayOnly sale) {
		return basePrice - sale.dollarsOff();
	}
}

You can even define anonymous classes outside a method body.

public class Gorilla {
	interface Climb {}
	CLib climbing = new Climb() {};
}

Reviewing Nested Classes

You’ve to learn this tables for the exam, about which syntax rules are permitted in Java.

Permitted Modifiers Inner class static nested class local class anonymous class
Access modifiers all all none none
abstract yes yes yes no
final yes yes yes no
instance methods yes yes yes yes
instance variables yes yes yes yes
static methods no yes no no
static variables yes(if final) yes yes(if final) yes(if final)

You should also know the following information about type of access.

  inner class static nested class local class anonymous class
can extend any class or implement any number of interfaces yes yes yes no - must havev exactly one superclass or one interface
can access instance members of enclosing class without a reference yes no yes (if declared in an instance method) yes (if declared in an instance method)
can access local variables of enclosing method N/A N/A yes (if final or effectively final) yes (if final or effectively final)

Understanding Interface Members

Since Java 8 and 9 four new method types have bee added.

Relying on a default Interface Method

A default method is a method defined in an interface with the default keyword, and includes a method body.
It may be overriden by a class implementing the interface. It’s considered the default implementation.

They were added for backward compatibility. It allows you to add a new method to an existing interface, without the need to modify older code that implements the interface.

public interface IsWarmBlooded {
	boolean hasScales();

	default double getTemperature() {
		return 10.0;
	}
}

(!) Remember: both of these methods include the implicit public modifier, so overriding them with a different access modifier is not allowed. (!)

Default Interface Method Definition Rules
  • It may be declared only within an interface
  • It must be marked with the default keyword and include a method body
  • It’s assumed to be public
  • It cannot be marked abstract, final, or static
  • It may be overridden by a class that implements the interface
  • If a class inherits two or more default methods with the same method signature, then the class must override the method
public interface Carnivore {
	public default void eatMeat(); // DOES NOT COMPILE

	public int getRequiredFood() { // DOES NOT COMPILE
		return 13;
	}
}
Inheriting Duplicate Default Methods
public interface Walk {
	public default int getSpeed() { return 5; }
}

public interface Run {
	public default int getSpeed() { return 10; }
}

public class Cat implements Walk, Run { // DOES NOT COMPILE

}

If a class implements two interfaces that have a default method with the same signature, the compiler will throw an error. The class implementing the interfaces has to override the duplicate default method.

public class Cat implements Walk, Run {
	public int getSpeed() { return 1; }
}
Calling a Hidden default Method

With two inherited default getSpeed() methods. How would you call the version of the default method in the Walk interface?

You can’t call Walk.getSpeed(), as a default method is treated as part of the instance, so they cannot be called like static methods.

You also can’t call super.getSpeed(), because it couldn’t diferentiate between both inherited methods.

The solution is a combination of both of these answers.

public class Car implements Walk, Run {
	public int getSpeed() { return 1; }

	public int getWalkSpeed() {
		return Walk.super.getSpeed();
	}
}

Using static Interface Methods

Java now supports static interface methods.

Static Interface Method Definition Rules
  • It must be marked with static and include a method body
  • A static method without an access modifier is assumed to be public
  • It cannot be marked abstract or final
  • It’s not inherited and cannot be accessed in a class implementing the interface without a reference to the interface name
public interface Hop {
	static int getJumpHeight() {
		return 8;
	}
}
Hop.getJumpHeight();

This is an example for the fourth rule. It implements Hop and does not compile.

public class Bunny implements Hop {
	public void printDetails() {
		sout(getJumpHeight()); // DOES NOT COMPILE
	}
}

Without an explicit reference to the name of the interface, the code will not compile, even though it implements Hop.

public class Bunny implements Hop {
	public void printDetails() {
		sout(Hop.getJumpHeight());
	}
}
Introducing private Interface Methods

Since Java 9, interfaces may now include private interface methods. They’re useful to reduce code duplication for default methods.

They cannot be used outside the interface definition, nor inside static interface methods without a static modifier.

public interface Schedule {
	default void wakeUp() { checkTime(7); }
	default void haveBreakfast() { checkTime(9); }

	private void checkTime(int hour) {
		...
	}
}

Rules:

  • It must be marked with the private modifier and include a method body
  • It may be called only by default and private (non- static) methods within the interface definition

They cannot be declared abstract since they’re not inherited.

Introducing private static Interface Methods

Java 9 added private static interface methods. They can also be accessed by default and private methods.

public interface Swim {
	private static void breathe(String type) {
		...
	}

	static void butterfly() { breathe("butterfly"); }
	public static void freestyle() { breathe("freestyle"); }
	default void backstroke() { breathe("backstroke"); }
	private void breaststroke() { breathe("breastsroke"); }
}

Rules:

  • It must be marked with private and static modifiers and include a method body.
  • It may be called only by other methods within the interface definition.

Introducing Functional Programming

A functional interface is an interface that contains a single abstract method (SAM). They’re the basis for lambda expressions.

Defining a Functional Interface

@FunctionalInterface
public interface Spring {
	public void sprint(int speed);
}

public class Tiger implements Sprint {
	public void sprint(int speed) {
		...
	}
}

This is a functional interface, because it contains exactly one abstract method.

public interface Dash extends Sprint {}

This is a valid functional interface because it inherits and contains a single abstract method.

public interface Skip extends Sprint {
	void skip();
}

This is not because it has two abstract methods. The inherited one and skip()

public interface Sleep {
	private void snore() {}
	default int getZZZ() { return 1; }
}

This isn’t neither because not 1 method match the criteria.

public interface Climb {
	void reach();
	default void fall() {}
	static int getBackUp() { return 100; }
	private static boolean checkHeight() { return true; }
}

This is a functional interface. Despite defining a slew of methods, it contains one abstrac method: reach().

Declaring a Functional Interface with Object Methods

All classes inherit certain methods from Object. For the exam you should be familiar with

String toString();

boolean equals(Object);

int hashCode();

There’s one exception to the single abstract method rule. If a functional interface includes an abstract method with the same signature as a public method found in Object, then those methods do not count towards single abstract method.

public interface Soar {
	abstract String toString();
}

This isn’t a functional interface, since toString() is a public method implemented inside Object.

On the other hand this is a functional interface.

public interface Dive {
	String toString();
	public boolean equals(Object o);
	public abstract int hashCode();
	public void dive();
}

(!) Be wary of examples that resemble methods in the Object class. (!)

public interface Hibernate {
	String toString();
	public boolean equals(Hibernate o);
	public abstract int hashCode();
	public void rest();
}

Hibernate is not a valid interface.

Implementing Functional Interfaces with Lambdas

In addition to functional interfaces you write youtself, Java provides a number of predefined ones.

For example, let’s take a look at Predicate.

public interface Predicate<T> {
	boolean test(T t);
}

We have our own class Animal.

public class Animal {
	private String species;
	private boolean canHop;
	private boolean canSwim;

	// constructor
	// getters
}

Now if we want to check if the animal canHop().

public static void main(String[] args) {
	var animals = new ArrayList<Animal>();
	// add animals

	print(animals, animal -> a.canHop());
}

private static void print(List<Animal> animals,
						Predicate<Animal> checker) {
	for(Animal animal : animals) {
		if(checker.test(animal)) {
			sout(animal + " ");
		}
	}						
}

Writing Lambda Expressions

Normal lambda syntax

a -> a.canHop()

Full lambda syntax

(Animal a) -> { return a.canHop(); }

Ther parentheses can be omitted, only if there’s a single parameter and its type is not explicitly stated. We can also omit braces when we have only a single statement. Java doesn’t require you to type return or use a semicolon when no braces are used.

All the following are valid lambda expressions, assuming that there’re functional interfaces that can consume them.

() -> new Duck();
d -> { return d.quack(); }
(Duck d) -> d.quack()
(Animal a, Duck d) -> d.quack()

The first one can be used by an interface containing a method that takes no args and returns a Duck.
The second and third, both can be used by an interface that takes a Duck as input and returns whatever the type quack() is.
The last can be used by an interface that takes as input Animal and Duck objects and returns the type of quack().

Now let’s check for invalid syntax.

a, b -> a.startsWith("test");        // DOES NOT COMPILE
Duck d -> d.canQuack();              // DOES NOT COMPILE
a -> { a.startsWith("test"); }       // DOES NOT COMPILE
a -> { return a.startsWith("test") } // DOES NOT COMPILE
(Swan s, t) -> s.compareTo(t) != 0   // DOES NOT COMPILE

Lines 1 and 2 require each parentheses around each parameter list. Params are optional only when there’s one param and it doesn’t have a type declared.
Line 3 is missing the return keyword, which is required since we said the lambda must return a boolean.
Line 4 is missing the semicolon ; inside the braces.
Line 5 is missing the param type for t. If the param type is specified for one param, then it must be specified for all of them.

Working with Lambda Variables

Parameter List

Specifying the type of params is optional. Now var can be used in a lambda param list. This means all of this 3 are valid.

Predicate<String> p = x -> true;
Predicate<String> p = (var x) -> true;
Predicate<String> p = (String x) -> true;
Restrictions on Using var in the param list

If var is used for one of the types in the param list, then it must be used for all params in hte list.

(var num) -> 1
(var a, var b) -> "Hello"
(var b, var k, var m) -> 3.14159

var w -> 99 // DOES NOT COMPILE
(var a, Integer b) -> true // DOES NOT COMPILE
(String x, var y, Integer z) -> true // DOES NOT COMPILE

Line 6 does not compile because parentheses are required when using the param name. Line 7 and 8 don’t compile, because the param types include a mix of var and type names.

Local Variables Inside the Lambda Body

It’s legal to define a lambda as a block.

(a, b) -> {
	int c = 0;
	return 5;
}

It’s not allowed to redeclare a variable though.

(a, b) -> {
	int a = 0; // DOES NOT COMPILE
	return 5;
}

(!) Lambda blocks need to end with a semicolon! (!)

public void variables(int a) {
	int b = 1;

	Predicate<Integer> p1 = a -> {
		int c = 0;
		return b == c; } // DOES NOT COMPILE. MISSING ;
}
Variables Referenced from the Lambda Body

Lambda bodies are allowed to use static variables, instance variables, and local variables if they’re effectively final.

public class Crow {
	private String color;

	public void caw(String name) {
		String volume = "loudly";
		Predicate<String> p = s -> (name+volume+color).length() == 10;
	}
}

(!) If the local variable is not final or effectively final, then the code does not compile. (!)

public class Crow {
	private String color;

	public void caw(String name) {
		String volume = "loudly";
		color = "allowed";
		name = "not allowed";
		volume = "not allowed";

		Predicate<String> p =
			s -> (name+volume+color).length()==9; // DOES NOT COMPILE
	}
}

Summary

Exam Essentials

This chapter focused on core fundamentals of the Java language that you will use throughout this book. We started with the final modifier and showed how it could be applied to local, instance, and static variables, as well as methods and classes.

We next moved on to enumerated types, which define a list of fixed values. Like boolean values, enums are not integers and cannot be compared this way. Enums can be used in switch statements. Besides the list of values, enums can include instance variables, constructors, and methods. Methods can even be abstract, in which case all enum values must provide an implementation. Alternatively, if an enum method is not marked final, then it can be overridden by one of its value declarations.

There are four types of nested classes. An inner class requires an instance of the outer class to use, while a static nested class does not. A local class is one defined within a method. Local classes can access final and effectively final local variables. Anonymous classes are a special type of local class that does not have a name. Anonymous classes are required to extend exactly one class by name or implement exactly one interface. Inner, local, and anonymous classes can access private members of the class in which they are defined, provided the latter two are used inside an instance method.

As of Java 9, interfaces now support six different members. Constant variables (static final) and abstract methods should have been familiar to you. Newer member types include default, static, private, and private static methods. While interfaces now contain a lot of member types, they are still distinct from abstract classes and do not participate in the class instantiation.

Last but certainly not least, this chapter included an introduction to functional interfaces and lambda expressions. A functional interface is an interface that contains exactly one abstract method. Any functional interface can be implemented with a lambda expression. A lambda expression can be written in a number of different forms, since many of the parts are optional. Make sure you understand the basics of writing lambda expressions as you will be using them throughout the book.

Exam Essentials

Be able to correctly apply the final modifier. Applying the final modifier to a variable means its value cannot change after it has been assigned, although its contents can be modified. An instance final variable must be assigned a value when it is declared, in an instance initializer, or in a constructor at most once. A static final variable must be assigned a value when it is declared or in a static initializer. A final method is one that cannot be overridden by a subclass, while a final class is one that cannot be extended.

Be able to create and use enum types. An enum is a data structure that defines a list of values. If the enum does not contain any other elements, then the semicolon (;) after the values is optional. An enum can have instance variables, constructors, and methods. Enum constructors are implicitly private. Enums can include methods, both as members or within individual enum values. If the enum declares an abstract method, each enum value must implement it.

Identify and distinguish between types of nested classes. There are four types of nested types: inner classes, static classes, local classes, and anonymous classes. The first two are defined as part of a class declaration. Local classes are used inside method bodies and scoped to the end of the current block of code. Anonymous classes are created and used once, often on the fly. More recently, they are commonly implemented as lambda expressions.

Be able to declare and use nested classes. Instantiating an inner class requires an instance of the outer class, such as calling new Outer.new Inner(). On the other hand, static nested classes can be created without a reference to the outer class, although they cannot access instance members of the outer class without a reference. Local and anonymous classes cannot be declared with an access modifier. Anonymous classes are limited to extending a single class or implementing one interface.

Be able to create default, static, private, and private static interface methods. A default interface method is a public interface that contains a body, which can be overridden by a class implementing the interface. If a class inherits two default methods with the same signature, then the class must override the default method with its own implementation. An interface can include public static and private static methods, the latter of which can be accessed only by methods declared within the interface. An interface can also include private methods, which can be called only by default and other private methods in the interface declaration.

Determine whether an interface is a functional interface. Use the single abstract method (SAM) rule to determine whether an interface is a functional interface. Other interface method types (default, private, static, and private static) do not count toward the single abstract method count, nor do any public methods with signatures found in Object.

Write simple lambda expressions. Look for the presence or absence of optional elements in lambda code. Parameter types are optional. Braces and the return keyword are optional when the body is a single statement. Parentheses are optional when only one parameter is specified and the type is implicit. If one of the parameters is a var, then they all must use var.

Determine whether a variable can be used in a lambda body. Local variables and method parameters must be final or effectively final to be referenced in a lambda expression. Class variables are always allowed. Instance variables are allowed if the lambda is used inside an instance method.

Read More

Lambdas and Functional Interfaces

Writing Simple Lambdas

You can think of a lambda expression as an unnamed method. It has parameters and a body, but it doesn’t have a name like a real method.

They’re good to abstract function usage, for places when we want to change just one function for another. They allow os to pass functions as params.

print(animals, a -> a.canHop());

This code uses a concept called deferred execution. This means that code is specified now but will run later.

Lambda Syntax

Lambdas work with interfaces that have only one abstract method.
The syntax is tricky because many parts are optional. These two lines do the exact same thing.

a -> a.canHop();
(Animal a) -> { return a.canHop(); }
  • a single parameter
  • -> arrow operator
  • a.canHop() a body that calls a single method and returns the result of that method

This is a valid lambda

s -> {}

If there’s no code on the right side, you don’t need the semicolon or the return statement.

Examples of valid lambdas with 0, 1 and 2 parameters

() -> true
a -> a.startsWith("test");
(String a) -> a.startsWith("test");
(a, b) -> a.startsWith("test");
(String a, String b) -> a.startsWith("test");

INVALID lambdas

a,b -> a.startsWith("test"); // missing parentheses
a -> { s.startsWith("test"); } // missing return
a -> { return a.startsWith("test") } // missing semicolon

Remember that parentheses are optional only when there’s one parameter and it doesn’t have a type declared.

Introducing Functional Interfaces

A functional interface is an interface with just one abstract method. Lambdas work just with them.

boolean test(Animal a);

This is known as the Single Abstract Method (SAM) rule.
Java provides the @FunctionalInterface annotation to mark interfaces that will be compatible with lambdas. It’s not needed to use them, though. It’s optional.

Predicate

It’s used to test a class usage that we come across. It’s in the package java.util.function.

public interface Predicate<T> {
	boolean test(T t);
}

Example

public class PredicateSearch {
	public static void main(String... args) {
		List<Animal> animals = new ArrayList<Animal>();
		animals.add(new Animal("fish", false, true));

		print(animals, a -> a.canHop());
	}

	private static void print(List<Animal> animals,
							  Predicate<Animal> checker) {
		for(Animal animal : animals) {
			if(checker.test(animal))
				sout(animal + " ");
		}
		soutln();
	}
}

Consumer

It receives a value and returns nothing.

void accept(T t);

A common reason for this is printing a message.

public static void main(String... args) {
	print(x -> sout(x), "Hello word");
}

private static void print(Consumer<String> consumer,
							String value) {
	consumer.accept(value);							
}

Supplier

It receives nothing and returns a value.

T get();

A good use case is when generating values.

public static void main(String... args) {
	Supplier<Integer> random = () -> new Random().nextInt();
	sout(returnNumber(random));
}

private static int returnNumber(Supplier<Integer> supplier) {
	return supplier.get();
}

Comparator

A negative number means the first value is smaller.
Zero means both values are equal.
A positive number means the first value is bigger.

int compare(T o1, T o2);

For the following lambdas

Comparator<Integer> ints = (i1, i2) -> i1 - i2;

The ints comparator uses natural sorf order and follows the above rules. If the first number is bigger, it will return a positive number.

Comparator<String> strings = (s1, s2) -> s2.compareTo(s1);
Comparator<String> moreStrings = (s1, s2) -> - s1.compareTo(s2);

Both of the above comparator do the same thing. Sort in descending order.
The first one is “backwards”, making it descending.
The second uses the default order, however it applies a - sign reversing it.

Working with Variables in Lambdas

Variables can appear in three places with respect to lambdas

Parameter List

Specifying the type of params is optional. Additionally var can be used in place of the specific type.

All these statements are valid and the type of x for all of them is String.

Predicate<String> p = x -> true;
Predicate<String> p = (var x) -> true;
Predicate<String> p = (String x) -> true;

A lambda infers the types from the surrounding context. Another place to look for the type is in method signature.

// the type is Integer
public void whatAmI() {
	consume((var x) -> sout(x), 123)
}

public void consume(Consumer<Integer> c, int num) {
	c.accept(num);
}

You can also determine the type without even seeing the method signature.

// the type is again Integer
public void counts(List<Integer> list) {
	list.sort((var x, var y) -> x.compareTo(y));
}

Local Variables inside the Lambda Body

It’s legal to define a lambda with a block. That block can include local variable declarations.

(a, b) -> {
	int c = 0;
	return 5;
}

When writing your own code, a lambda block with a local variable is a good hint that you should extract that code into a method.

(a, b) -> {
	int a = 0; // DOES NOT COMPILE
	return 5;
}

You cannot redeclare a variable (a) with the name as one already declared in that scope.

11: public void variables(int a) {
12: 	int b = 1;
13: 	Predicate<Integer> p1 = a -> {
14: 		int b = 0;
15: 		int c = 0;
16: 		return b == c;
17: 	}
}

There’re three syntax errors on the above code.
The first on line 13. The variable a was already used in this scope, so it cannot be reused.
The next one comes on line 14 where it attempts to redeclare local variable b.
The third and last one is at line 17. It’s missing a ; to close the Predicate block.

(!) Beware with missing semicolons for lambdas. They’re tricky (!)

Variables Referenced from the Lambda Body

Lambda bodies are allowed to reference some vars from the surrounding code.

public class Crow {
	private String color;

	public void caw(String name) {
		String volume = "loudly";
		Consumer<String> consumer = s -> sout(name + color);
	}
}

Lambdas can access an instance variable, method parameter or local variable under certain conditions.

Instance and class variables are always allowed.
Method parameters and local variables are allowed to be referenced if they’re effectively final. This means that the value doesn’t change after it’s set, regardless of whether it’s explicitly marked as final. If a local variable had it’s value changed, it cannot be used by a lambda.

Rules for accessing a variable from a lambda body inside a method

Variable type Rule
Instance variable Allowed
Static variable Allowed
Local variable Allowed if effectively final
Method variable Allowed if effectively final
Lambda variable Allowed

Calling APIs with Lambdas

These are the most common methods that use lambdas.

removeIf()

List and Set declare a removeIf() method that takes a Predicate.

List<String> bunnies = new ArrayList<>();
bunnies.add("long ear");
bunnies.add("floppy");
bunnies.add("hoppy");
bunnies.removeIf(s -> s.charAt(0) != 'h');

sort()

While you can call Collections.sort(list), you can now also sort directly on the list.

List<String> bunnies = new ArrayList<>();
bunnies.add("long ear");
bunnies.add("floppy");
bunnies.add("hoppy"); // [long ear, floppy, hoppy]
// list alphabetically
bunnies.sort((b1, b2) -> b1.compareTo(b2)); // [floppy, hoppy, long ear]

There’s not a sort method on Set or Map! Neither of those types has indexing.

forEach()

It takes a Consumer and calls that lambda for each element encountered.

List<String> bunnies = new ArrayList<>();
bunnies.add("long ear");
bunnies.add("floppy");
bunnies.add("hoppy");
bunnies.forEach(b -> sout(b)); // long ear
							   // floppy
							   // hoppy

For a Set it works the same as a List.
For a Map you have to choose whether you want to go through the keys or values.

Map<String, Integer> bunnies = new HashMap<>();
bunnies.put("long ear", 3);
bunnies.put("floppy", 8);
bunnies.put("hoppy", 1);
bunnies.keySet().forEach(b -> sout(b));
bunnies.values().forEach(b -> sout(b));
// it's also possible to use forEach directly with a Map
bunnies.forEach((k,v) -> sout(k + " " + v));

Summary

Lambda expressions, or lambdas, allow passing around blocks of code. The full syntax looks like this:

(String a, String b) -> { return a.equals(b); }

The parameter types can be omitted. When only one parameter is specified without a type the parentheses can also be omitted. The braces and return statement can be omitted for a single statement, making the short form as follows:

a -> a.equals(b)

Lambdas are passed to a method expecting an instance of a functional interface. A functional interface is one with a single abstract method. Predicate is a common interface that returns a boolean and takes any type. Consumer takes any type and doesn’t return a value. Supplier returns a value and does not take any parameters. Comparator takes two parameters and returns an int.

A lambda can define parameters or variables in the body as long as their names are different from existing local variables. The body of a lambda is allowed to use any instance or class variables. Additionally, it can use any local variables or method parameters that are effectively final.

We covered three common APIs that use lambdas. The removeIf() method on a List and a Set takes a Predicate. The sort() method on a List interface takes a Comparator. The forEach() methods on a List and a Set interface both take a Consumer.

Exam Essentials

Write simple lambda expressions. Look for the presence or absence of optional elements in lambda code. Parameter types are optional. Braces and the return keyword are optional when the body is a single statement. Parentheses are optional when only one parameter is specified and the type is implicit.

Identify common functional interfaces. From a code snippet, identify whether the lambda is a Comparator, Consumer, Predicate, or Supplier. You can use the number of parameters and return type to tell them apart.

Determine whether a variable can be used in a lambda body. Local variables and method parameters must be effectively final to be referenced. This means the code must compile if you were to add the final keyword to these variables. Instance and class variables are always allowed.

Use common APIs with lambdas. Be able to read and write code using forEach(), removeIf(), and sort().

Read More

Core Java APIs

Creating and Manipulating Strings

A String is a fundamental reference type.
It implements the interface CharSequence. This interface is a general way of representing several classes, including String and SringBuilder.

These two snippets both create a String.

String name = "Fluffy";
String name = new String("Fluffy");

Concatenation

The exam creatos like String concatenation because the + operator can be used in two ways within the same line of code.

Rules:

  1. If both operands are numeric + means numeric addition
  2. If either operand is a String + means concatenation
  3. The expression is evaluated from left to right
System.out.println(1 + 2); // 3
System.out.println("a" + "b"); // ab
System.out.println("a" + "b" + 3); // ab3
System.out.println(1 + 2 + "c"); //3c
System.out.println("c" + 1 + 2); //c12

If you see questions like this, just take your time and check the types.

int three = 3;
String four = "4";
System.out.println(1 + 2 + three + four);
String s = "1";
s += "2";
s += 3;
System.out.println(s);

Immutability

A String is immutable. Once a String object is created, it’s not allowed to change. It cannot be made larger nor smaller, and you cannot change one of the characters inside it.

String s1 = "1";
String s2 = s1.concat("2");
s2.concat("3");
System.out.println(s2); // prints "12" because String is immutable

Important String Methods

For all these methods, you need to remember that a String is a sequence of characters anda Java counts from 0 when indexed.

length()

int length();

It returns the number of characters in the String.

String string = "animals";
System.out.println(string.length()); // 7

Strings counts from 0, only when you’re using indexes or positions within a list. When determining the total size or length, Java uses normal counting.

charAt()

char charAt(int index);

It lets you query the String to find out what character is at a specific index.

String string = "animals";
System.out.println(string.charAt(0)); // a
System.out.println(string.charAt(6)); // s
System.out.println(string.charAt(7)); // throws Exception

indexOf()

int indexOf(int ch);
int indexOf(int ch, int fromIndex);

int indexOf(String str);
int indexOf(String str, int fromIndex);

It looks at the chars in the string and finds the first index that matches the desired value.

It can work with an individual char or a whole String as input.
It can also start from a requested position.

String string = "animals";
System.out.println(string.indexOf('a')); // 0
System.out.println(string.indexOf("al")); // 4
System.out.println(string.indexOf('a', 4)); // 4
System.out.println(string.indexOf("al", 5)); // -1

It returns -1 when no match is found.

substring()

String substring(int beginIndex);
String substring(int beginIndex, int endIndex);

It also looks for charactes in a String. It returns part of the String. The first parameter is the index to start with for the returned String. This is zero-based index. There’s an optional second parameter which is the index you want to stop at. It’s “stop at”, not “include”. The endIndex parameter is allowed to be 1 past the end of the sequence if you want to stop at the end of the sequence. That woul be redundant though since you could omit the second parameter in that case. Don’t be surprised if the exam uses it.

The method returns the string starting from the requested index. If an index is requested, it stops right before that index. Otherwise, it goes to the end of the String.

String string = "animals";
System.out.println(string.substring(3)); // mals
System.out.println(string.substring(string.indexOf('m'))); // mals
System.out.println(string.substring(3, 4)); // m
System.out.println(string.substring(3, 7)); // mals
System.out.println(string.substring(3, 3)); // empty String
System.out.println(string.substring(3, 2)); // throws Exception
System.out.println(string.substring(3, 8)); // throws Exception

The substring() method is the trickets String method on the exam.

toLowerCase() and toUpperCase()

String toLowerCase();
String toUpperCase();

These methods do exactly what they say. They convert any lowercase characters to uppercase and viceversa.

String string = "animals";
System.out.println(string.toUpperCase()); // ANIMALS
System.out.println("Abc123".toLowerCase()); // abc123

equals() and equalsIgnoreCase()

boolean equals(Object obj);
boolean equalsIgnoreCase(String str);

The method equals() check whether two String objects contain exactly the same characters in the same order. It takes an Object instead of an String. This is because the method is the same for all objects. If you pass something that isn’t a String, it will just return false.

The method equalsIgnoreCase() checks whether two String objects contain the same characters without considering characters’ cases.

System.out.println("abc".equals("ABC"));  // false
System.out.println("ABC".equals("ABC")); // true
System.out.println("abc".equalsIgnoreCase("ABC")); // true

startsWith() and endsWith()

boolean startsWith(String prefix);
boolean endsWith(String suffix);

They look at whether the provided valeu matches part of the String.

System.out.println("abc".startsWith("a")); // true
System.out.println("abc".startsWith("A")); // false
System.out.println("abc".endsWith("c")); // true
System.out.println("abc".endsWith("a")); // false

replace()

String replace(char oldChar, char newChar);
String replace(CharSequence target, CharSequence replacement);

It does a simple search and replace on the String. There’s a version that takes char parameters as well as a version that takes CharSequence parameters.

System.out.println("abcabc".replace('a', 'A')); // AbcAbc
System.out.println("abcabc".replace("a", "A")); // AbcAbc

contains()

boolean contains(CharSequence charSeq);

It looks for matches in the String. The match can be anywhere in the String. It’s case sensitive.

System.out.println("abc".contains("b")); // true
System.out.println("abc".contains("B")); // false

trim(), strip(), stripLeading(), stripTrailing()

String strip();
String stripLeading();
String stripTrailing();

String trim();

Removes blank space from the begginning and/or end of a String. strip() and trim() remove whitespace from the beginning and end of a String. Whitespace consists of spaces along with the \t, \r and \n characters.

The strip() method is new in Java11. It does everything that trim() does, but it supports Unicode.

Additionally, stripLeading() and stripTrailing() methods were added in Java11. stripLeading() method removes whitespace from the beginning of the String and leaves it at the end. The stripTrailing()method does the opposite. It removes from the end and leaves it at the beginning.

System.out.println("abc".strip()); // abc
System.out.println("\t a b c\n".strip()); // a b c

String text = " abc\t ";
System.out.println(text.trim().length()); // 3
System.out.println(text.strip().length()); // 3
System.out.println(text.stripLeading().length()); // 5
System.out.println(text.stripTrailing().length()); // 4

Remember \t is a single character.

intern()

String intern();

It returns the value from the string pool if it’s there. Otherwise it adds the value to the string pool.

Method Chaining

On the exam there’s a tendency to cram as much code as possible into a small space. You’ll see a lot of method chaining such as

String result = "AniMaL     ".trim().toLowerCase().replace('a', 'A');
System.out.println(result);

Remember that String is immutable.

String a = "abc";
String b = a.toUpperCase();
b = b.replace("B", "2").replace('C', '3');
System.out.println("a=" + a); // "abc"
System.out.println("b=" + b); // "A23"

Using the StringBuilder Class

String is immutable. For every iteration of the following loop, a new String object is created and the old one becomes eligible for garbage collection. After 26 iterations, a total of 27 objects are instantiated.

String alpha = "";
for(char current = 'a'; current <= 'z'; current++)
	alpha += current;
sout(alpha);

This is a far better solution.

StringBuilder alpha = new StringBuilder();
for(char current = 'a'; current <= 'z'; current++)
	alpha.append(current);
sout(alpha);

Mutability and Chaining

StringBuilder is not immutable. The exam will likely try to trick you with respect to String and StringBuilder being mutable.

When we chain String method calls, the result is a new String with the answer. StringBuilder changes its own state and returns a reference to itself.

1: StringBuilder sb = new StringBuilder("start");
2: sb.append("+middle"); // sb = "start+middle"
3: StringBuilder same = sb.append("+end"); "start+middle+end"

Line 2 adds text to the end of sb and returns a reference, which is ignored. Line 6 also adds text to the end of sb a returns a reference, which is stored in same. Which means sb and same point to the same object and would print the same.

StringBuilder a = new StringBuilder("abc");
StringBuilder b = a.append("de");
b = b.append("f").append("g");
sout(a);
sout(b);

At this example, there’s only one object. The result is both point to the same object and both print abcdefg.

Creating a StringBuilder

There’re three ways to construct a StringBuilder.

StringBuilder sb1 = new StringBuilder();
StringBuilder sb2 = new StringBuilder("animal");
StringBuilder sb3 = new StringBuilder(10);

The third one tells Java that we have some idea of how big the eventual value will be, and how StringBuilder should reserve a certain capacity for it.

Important StringBuilder Methods

charAt(), indexOf(), length(), substring()

These four methods work exactly the same as for String.

StringBuilder sb = new StringBuilder("animals");
String sub = sb.substring(sb.indexOf("a"), sb.indexOf("al"));
int len = sb.length();
char ch = sb.charAt(6);
sout(sub + " " + len + " " + ch);

The correct answer is anim 7 s.
indexOf() return 0 and 4. substring() return the String starting with index 0 and ending right before index 4. length() returns 7 because it’s the number of chars rather than an index. charAt() returns the char at index 6. Here we do start with 0 because we’re referring to indexes.
Notice that substring() returns a String rather than a StringBuilder. It’s a method that inquires about what the state of the StringBuilder happens to be.

append()

StringBuilder append(String str); // there're more for more data types.

This is the most frequently used method. It does what it soulds like. It adds the param to the StringBuilder and returns a reference to the current StringBuilder.

insert()

StringBuilder insert(int offset, String str); // there're more for more data types.

It adds chars to the StringBuilder at the requested index and returns a reference to the current StringBuilder.

StringBuilder sb = new StringBuilder("animals");
sb.insert(7, "-"); // animals-
sb.insert(0, "-"); // -animals-
sb.insert(4, "-"); // -ani-mals-

(!) The exam creators will try to trip you up on this. As we add and remove chars, their indexes change. When you see a question such as this, draw what’s going on so you won’t be confused. (!)

delete() and deleteCharAt()

StringBuilder deleteCharAt(int index);
StringBuilder delete(int startIndex, int endIndex);

This is the opposite of insert(). It removes chars from the sequence and returns a reference to the current StringBuilder. This method is convenient when you want to delete only one character.

StringBuilder sb = new StringBuilder("abcdef");
sb.delete(1, 3); // adef
sb.deleteCharAt(5); // throws an exception.

The delete() method is more flexible than some others when it comes to array indexes. If you specify a second param that’s past the end of the StringBuilder, java will assume you meant the end.

This is valid code.

StringBuilder sb = new StringBuilder("abcdef");
sb.delete(1, 100); // a

replace()

StringBuilder replace(int startIndex, int endIndex, String newString);

This method works differently for StringBuilderthan for String.

StringBuilder builder = new StringBuilder("pigeon dirty");
builder.replace(3, 6, "sty");
sout(builder); // pigsty dirty

First, Java deletes ther chars starting with index 3 and ending right before index 6. Then it inserts the value of sty.

StringBuilder builder = new StringBuilder("pigeon dirty");
builder.replace(3, 100, "");
sout(builder);

This prints pig. The method first does a delete and replace() allows specifying a second param that’s past the end of the StringBuilder.

reverse()

StringBuilder reverse();

It does exactly what it sounds like. It reverses the chars sequence and returns a reference to the current StringBuilder.

StringBuilder sb = new StringBuilder("ABC");
sb.reverse();
sout(sb);

This will print CBA.

toString()

String toString();

This converts a StringBuilder into a String.

StringBuilder sb = new StringBuilder("ABC");
String s = sb.toString();

Understanding Equality

Comparing equals() and ==

StringBuilder one = new StringBuilder();
StringBuilder two = new StringBuilder();
StringBuilder three = one.append("a");
sout(one == two);  // false
sout(one == three); // true

Since we aren’t dealing with primitives, == checks for references. The last check works as StringBuilder methods like to return the current reference for chaining.

String x = "Hello World";
String z = "Hello World".trim();
sout(x.equals(z)); // true

This checks the inside of the String rather than the reference itself. If a class doesn’t have an equals method, Java determines whether the references point to the same object - which is exactly what == does. StringBuilder does not implement equals() so when using it, it checks for reference instead of content.

String string = "a";
StringBuilder builder = new StringBuilder("a");
sout(string == builder); // DOES NOT COMPILE. NOT SAME TYPE.

The String Pool

Since String are everywhere, they use up a lot of memory. As many Strings repeat in the program, java reuses common ones. The string pool, also known as the intern pool, is a location in the JVM that collects all these Strings.

Let’s visit a more complex and confusing scenario, String equality, made so in part because of the way the JVM reuses String literals.

String x = "Hello World";
String y = "Hello World";
System.out.println(x == y); // true

Strings are immutable and literals are pooled. The JVM created only one literal in memory and both x and y point to the same location in memory.
It gets trickier.

String x = "Hello World";
String y = new String("Hello World");
sout(x == y); // false

(!) Here the former says to use the String pool normally, but the second option with an explicit new says to not use the string pool amd create a new object, even if it’s less eficient. (!)

String x = "Hello World";
String z = " Hello World".trim();
sout(x == z); // false

In this example, we don’t have two of the same String literal as one is computed at runtime and the other at compile-time.

You can also do the opposite and tell Java to use the String pool. This is done with the intern() method. It will use an object from the pool if one is present. It it’s not present, Java will add it this time.

String name = "Hello World";
String name2 = new String("Hello World").intern();
sout(name == name2); // true

Understanding Java Arrays

String and StringBuilder are implemented using an array of characters. An array is an area of memory on the heap with space for a designated number of elements. An array is an ordered list. It can contain duplicates.

StringBuilder is implemented as an array where the array object is replaced with a new bigger array object when it runs out of space to store characters.

Creating an Array of Primitives

This is most common way to create an array.

int[] numbers1 = new int[3];

When you use this form to instantiate an array, all elements are set to default value for that type.
The indexes start with 0 and count up, just as they did for a String.

Another way to create an array, is to specify all the elements it should start out with

 int[] numbers2 = new int[] {42, 55, 59}; // this is redundant
 int[] numbers2 = {42, 55, 59}; // anonymous array

Beware multiple “Arrays” in declaration

 // this declares two int arrays
 int[] ids, types;

 // this declares ONE int array and ONE int
 int ids[], types;

Creating an Array with Reference Variables

 String[] bugs = { "cricket", "beetle" };
 sout(bugs.toString()); // [Ljava.lang.String;@160bcc9]

The .equals() method on arrays does not look at the elements of the array.
The .toString() method shows the following. [L means it’s an array. java.lang.String is the reference type and 160bcc9 is the hascode.

(!) The array does not allocate space for the String objects. Instead, it allocates space for a reference to where the objects are really stored. (!)

 String[] strings = { "stringValue" };
 Object[] objects = strings;
 String[] againString = (String[]) objects;

 againString[0] = new StringBuilder(); // DOES NOT COMPILE
 objects[0] = new StringBuilder(); // (!)

The last line will compile, but will throw a runtime error (ArrayStoreException).

Using an Array

 String[] mammals = {"monkey", "chimp", "donkey"};
 sout(mammals.length);
 sout(mammals[0]);
 sout(mammals[1]);
 sout(mammals[2]);

(!) Watch out for ArrayIndexOutOfBoundsException. (!)

 int numbers = new int[10];
 // this line tries to access 10 indexes. only 0-9 are valid!
 for(int i = 0; i<= numbers.length; i++)
 	numbers[i] = i + 5;

Sorting

You can pass almost any array to Arrays.sort().

 int[] numbers = {6,9,1};
 Arrays.sort(number);

This will print 6 9 1 as expected.

Watch out when sorting Strings

 String[] strings = {"10","9","100"};
 Arrays.sort(strings);

This time the code outputs 10 100 9. The problem is that String sorts in alphabetic order and 1 sorts before 9.

Java provides a convenient way to search - only if the array is already sorted.

 1: int[] numbers = {2,4,6,8};
 2: sout(Arrays.binarySearch(numbers, 2)); // 0
 3: sout(Arrays.binarySearch(numbers, 4)); // 1
 4: sout(Arrays.binarySearch(numbers, 1)); // -1
 5: sout(Arrays.binarySearch(numbers, 3)); // -2
 6: sout(Arrays.binarySearch(numbers, 9)); // -5

If the target is not found in the sorted array, it shows the negative value showing one smaller than the negative of the index. Take note that we need a sorted Array. If it wasn’t, we couldn’t apply the rules.

Line 2 and 3 both return the found index.
Line 4 searches for the index of 1. It isn’t at the list, altough it can determine that it should be inserted at element 0 to.
Line 5 is similar. Altough the element 3 isn’t in the list, it would need to be inserted at element 1 to preserve the sorted order.
Line 6 tells us that 9 should be inserted at index 4.

Comparing

Java also provides methods to compare two arrays to arrays to determine which is “smaller”.

compare()

There’re a bunch of rules to learn before using it.

This are the rules to compare a single value. First you need to learn what the return value means.

  • A negative number means the first array is smaller than the second.
  • A zero means the arrays are equal.
  • A positive number means the first array is larger than the second.
sout(Arrays.compare(new int[] {1}, new int[] {2}));

This code prints a negative number.

This are the rules on how to compare arrays of different lengths:

  • If both arrays are the same length and have the same values in each spot, in the same order, return zero.
  • If all the elements are the same but the second array has extra elements at the end, return a negative number.
  • If all the elements are the same but the first array has extra elements at the end, return a positive number.
  • If the first element that differs is smaller in the first array, return a negative number.
  • If the first element that differs is larger in the first array, return a positive number.

What does smaller mean?

  • null is smaller than any other value.
  • For numbers, normal numeric order applies.
  • For Strings, one is smaller if it’s a prefix of another.
  • For Strings/chars, numbers are smaller than letters.
  • For Strings/chars, uppercase is smaller than lowercase.
First array Second array Result Reason
new int[]{1,2} new int[]{1} positive number The first array is longer
new int[]{1,2} new int[]{1,2} zero exact match
new String[]{“a”} new String[]{“aa”} negative number the first element is a substring of the second
new String[]{“a”} new String[]{“A”} positive number uppercase is smaller than lowercase
new String[]{“a”} new String[]{null} positive number null is smaller than a letter

Finally, this code does not compile because the types are different. When comparing two arrays, they must be the same type.

Arrays.compare(new int[] {1}, new String[] {"a"}); // DOES NOT COMPILE

mismatch()

If the arrays are equal, mismatch() returns -1. Otherwise, it returns the first index where they differ.

sout(Arrays.mismatch(new int[] {1}, new int[] {1})); // -1
sout(Arrays.mismatch(new String[] "a", new String[] {"A"})); // 0
sout(Arrays.mismatch(new int[] {1,2}, new int[] {1})); // 1

The last case is special. One array has an element at index 1 and the other does not, therefore the result is 1.

equals vs comparison vs mismatch

|method|when arrays are the same|when arrays differ| |:—:|:—:|:—:| |equals()|true|false| |compare()|0|positive or negative number| |mismatch()|-1|zero or positive index|

Varargs

When you’re passing an array to your method, you can do it with varargs.

public static void main(String... args);

Multidimensional Arrays

Creating a Multidimensional Array

int[][] vars; // 2D array
int vars [][]; // 2D array
int[] vars[]; // 2D array
int[] vars[], vars[][]; // 2D array AND 3D array

They don’t need to be rectangular in shape.

String[][] rectangle = new String[3][2];
int[][] differentSizes = \{\{1,4}, {3}, {9,8,7}};

Using a multidimensional Array

int[][] twoD = new int[3][2];
for(int i = 0; i < twoD.length;i++) {
	for(int j = 0; j < twoD[i].length; j++)
		System.out.print(twoD[i][j] + " "); // print element
	System.out.println(); // print new row
}

Understanding an ArrayList

Just like a StringBuilder an ArrayList can change capacity at runtime as needed. It’s an ordered sequence that allows duplicates. It’s inside java.util.ArrayList package.

Creating an ArrayList

As with StringBuilder, there’re three ways to create an ArrayList.

// all this versions are pre Java5
ArrayList list = new ArrayList();
ArrayList list2 = new ArrayList(10);
ArrayList list3 = new ArrayList(list2);

// from Java5 on (generics)
ArrayList<String> list4 = new ArrayList<String>();
// from Java7 on
ArrayList<String> list5 = new ArrayList<>();

The first says to create it containing space for the default number of elements, but not to fill any slots yet.

The second says to create it containing a specific number of slots, but again not to assign any.

Using var with ArrayList

Consider mixing var with ArrayList and generics.

var strings = new ArrayList<String>();
strings.add("a");
// the type of var is ArrayList<String>

var strings = new ArrayList<>(); // DOES COMPILE
strings.add("a");
// the type of var is ArrayList<Object>

Why doesn’t this compile?

var list = new ArrayList<>();
list.add("a");
for (String s : list) { } // DOES NOT COMPILE

The type for list is ArrayList<Object> so adding a String inside is fine, but afterwards inside the for we’d need to set it to Object and not String.

ArrayList extends from a List interface.

List<String> list = new ArrayList<>(); // fine
ArrayList<String> list2 = new List<>(); // DOES NOT COMPILE

Using an ArrayList

add()

It inserts a new value into the ArrayList.

boolean add(E element);
void add(int index, E element);

The boolean always returns true. It’s there for compatibility.

ArrayList list = new ArrayList();
list.add("hawk");
list.add(Boolean.TRUE);
sout(list); // [hawk, TRUE]

Without generics, the type is ArrayList<Object> so we may store whatever inside. Let’s use now generics.

ArrayList<String> list = new ArrayList();
list.add("hawk");
list.add(Boolean.TRUE); // DOES NOT COMPILE

Let’s add now inside a specific index

List<String> birds = new ArrayList();
birds.add("hawk"); // [hawk]
birds.add(1, "robin"); // [hawk, robin]
birds.add(0, "blue jay"); // [blue jay, hawk, robin]
birds.add(1, "cardinal"); // [blue jay, cardinal, hawk, robin]

(!) When a question has code that adds objects at indexed positions, draw it so that you won’t lose track of which value is at which index. (!)

remove()

It removes the first matching value in the ArrayList or remove the element at a specified index.

boolean remove(Object object);
E remove(int index);

The boolean tells whether a match was removed. The E return type is the element that was actually removed.

List<String> birds = new ArrayList<>();
birds.add("hawk"); // [hawk]
birds.add("hawk"); // [hawk, hawk]
sout(birds.remove("cardinal")); // false
sout(birds.remove("hawk")); // true
sout(birds.remove(0)); // hawk

Using an intthat doesn’t exist will throw an IndexOutOfBoundsException. There’s also a removeIf() method.

set()

It changes one of the elements of the ArrayList without changing the size.

E set(int index, E newElement);

The E return type is the element that got replaces.

List<String> birds = new ArrayList<>();
birds.add("hawk"); // [hawk]
sout(birds.size()); // 1
birds.set(0, "robin"); // [robin]
sout(birds.size()); // 1
birds.set(1, "robin"); // IndexOutOfBoundsException

isEmpty() and size()

They look at how many of the slots are in use.

boolean isEmpty();
int size();

Usage:

List<String> birds = new ArrayList<>();
sout(birds.isEmpty()); // true
sout(birds.size()); // 0
birds.add("hawk"); // [hawk]
birds.add("hawk"); // [hawk, hawk]
sout(birds.isEmpty()); // false
sout(birds.size()); // 2

clear()

It provides an easy way to discard all elements of the ArrayList.

void clear();

Usage:

List<String> birds = new ArrayList<>();
birds.add("hawk"); // [hawk]
birds.add("hawk"); // [hawk, hawk]
sout(birds.isEmpty()); // false
sout(birds.size()); // 2
birds.clear();
sout(birds.isEmpty()); // true
sout(birds.size()); // 0

contains()

It checks whether a certain value is in the ArrayList.

boolean contains(Object object)

Usage:

List<String> birds = new ArrayList<>();
birds.add("hawk"); // [hawk]
sout(birds.contains("hawk")); // true
sout(birds.contains("robin")); // false

The method calls equals() on each element of the ArrayList to see whether there’re any matches.

equals()

ArrayList has a custom implementation of equals(). You can compare two lists to see whether they contain the same elements in the same order.

boolean equals(Object object);

Usage:

List<String> one = new ArrayList<>();
List<String> two = new ArrayList<>();
sout(one.equals(two)); // true
one.add("a"); // [a]
sout(one.equals(two)); // false
two.add("a"); // [a]
sout(one.equals(two)); // true
one.add("b");    // [a, b]
two.add(0, "b"); // [b, a]
sout(one.equals(two)); // false

Wrapper Classes

Wrapper class is an object type that corresponds to the primitive. Each wrapper class also has a constructor, but isn’t recommended for new code. Use valueOf() instead.

There’re also methods for converting a String to a primitive or wrapper class. The parse methods, such as parseInt() return a primitive, and the valueOf() methods return a wrapper class.

int primitive = Integer.parseInt("123");
Integer wrapper = Integer.valueOf("123");

If the String passed in is not valid for the given type, Java throws a NumberFormatException.

int bad1= Integer.parseInt("a"); // NumberFormatException
Integer bad2 = Integer.valueOf("123.45"); // NumberFormatException
Wrapper Class String to primitive String to wrapper class
Boolean Boolean.parseBoolean(“true”); Boolean.valueOf(“TRUE”);
Byte Byte.parseByte(“1”); Byte.valueOf(“2”);

Wrapper Classes and Null

One advantage of a wrapper class over a primitive is that because it’s an object, it can be used to store a null value.

Autoboxing and Unboxing

Since Java5, you can just type the primitive value, and Java will convert it into the relevant wrapper class for you. This is called autoboxing. The reverse is called unboxing.

List<Integer> weights = new ArrayList<>();
Integer w = 50;
weights.add(w); // [50]
weights.add(Integer.valueOf(60)); // [50, 60]
weights.remove(50); // [60]
double first = weights.get(0); // [60]

If you try to unbox a null, it will throw a NullPointerException.

List<Integer> heights = new ArrayList<>();
heights.add(null);
int h = heights.get(0); // NullPointerException

Storing a null reference is legal, the trouble comes when we try to unbox that null to a primitive int.

Also be careful when autoboxing into Integer.

List<Integer> numbers = new ArrayList<>();
numbers.add(1);
numbers.add(2);
numbers.remove(1);
sout(numbers); // 1

When we request to .remove(1); what we’re actualling requesting is to remove the index 1. If you want to remove the number 1 instead, you’d need something like .remove(new Integer(1)); to force wrapper class use.

Converting between Array and List

ArrayList to Array

Turning an ArrayList into an array.

List<String> list = new ArrayList<>();
list.add("hawk");
list.add("robin");
Object[] objectArray = list.toArray();
String[] stringArray = list.toArray(new String[0]);
list.clear();
sout(objectArray.length); // 2
sout(stringArray.length); // 2

ArrayList knows how to convert itself to an array. The only problem is that it default to an Object[]. This isn’t usually what you want.

The advantage of specifying a size of 0 for the parameter, is that Java will create a new array of the proper size for the return value. If you like, you can suggest a larger array to be used instead. The newly created object has no relationship to the original List. It’s simply a copy.

Array to List - option 1 (backed List)

Converting from array to List is more Interesting. One option is to create a List that’s linked to the original array. When a change is made to one, it’s available in the other. It’s a fixed size list and is also known as a backed list because the array changes with it.

String[] array = { "hawk", "robin" }; // [hawk, robin]
List<String> list = Arrays.asList(array); // returns fixed size list
sout(list.size()); // 2
list.set(1, "test"); // [hawk, test]
array[0] = "new"; // [new, test]
sout(Arrays.toString(array)); // [new, test]
list.remove(1); // UnsupportedOperationException

The line List<String> list = Arrays.asList(array); converts the array to a List. This is no ArrayList, it’s a fixed-size list. Then set() is okay because it replaces an existing value. It updates both array and List because they point to the same data store. Then .remove(1); throws an exception because this is a fixed-size list.

Array to List - option 2 (immutable List)

Another option is to create an immutable List. This means you cannot change the values or the size of the List. You can change the original array, but changes will not be reflected in the immutable List.

String[] array = { "hawk", "robin" }; // [hawk, robin]
List<String> list = List.of(array); // returns immutable list
sout(list.size()); // 2
array[0] = "new";
sout(Arrays.toString(array)); // [new, robin]
sout(list); // [hawk, robin]
list.set(1, "test"); // UnsupportedOperationException

Using varargs to create a List

List<String> list1 = Arrays.asList("one", "two");
List<String> list2 = List.of("one", "two");

Both of these methods take varargs, which let you pass in an array or just type out the String values.

- toArray() Arrays.asList() List.of()
Type converting from List Array (or varargs) Array (or varargs)
Type created Array List List
Allowed to remove values from created object No No No
Allowed to change values in the created object Yes Yes No
Changing values in the created object affects the original or vice versa No Yes N/A

Sorting

Sorting an ArrayList is similar to sorting an array.

List<Integer> numbers = new ArrayList<>();
numbers.add(99);
numbers.add(5);
numbers.add(81);
Collections.sort(numbers); // [5, 81, 99]

Creating Sets and Maps

Introducing Sets

A Set is a collection of objects that cannot contain duplicates.
All the methods you learned for ArrayList apply to a Set with the exception of those taking an index as a patameter. This is because Set is not ordered.

If you try to add() an object which already existed, this method will return false.

The two common classes that implement Set are HashSet and TreeSet. The former is the most common and the last is used when sorting is important.

Set<Integer> set = new HashSet<>();
sout(set.add(66)); // true
sout(set.add(66)); // false
sout(set.size()); // 1
set.remove(66);
sout(set.isEmpty()); // true

Introducing Maps

A Map uses a key to identify values.

key value
George 555-555-5555
Mary 777-777-7777

The most common implementation is HashMap. Some of the methods are the same as those in ArrayList. There’re also methods specific to dealing with key and value pairs such as the following.

Method Description
V get(Object key) Returns the value mapped by key or null if none is mapped
V getOrDefault(Object key, V other) Returns the value mapped by key or other if none is mapped
V put(K key, V value) Adds or replaces key/value pair. Returns previous value or null
V remove(Object key) Removes and returns value mapped to key. Returns null if none
boolean containsKey(Object key) Removes whether key is in map
boolean containsValue(Object key) Returns whether value is in map
Set<K> keySet() Returns set of all keys
Collection<V> values() Returns Collection of all values

Let’s check some examples

Map<String, String> map = new HashMap<>();
map.put("koala", "bamboo");
String food = map.get("koala"); // bamboo
String other = map.getOrDefault("ant", "leaf"); // leaf
for (String key: map.keySet())
	sout(key + " " + map.get(key)); // koala bamboo

Calculating with Math APIs

min() and max()

double min(double a, double b);
float min(float a, float b);
int min(int a, int b);
long min(long a, long b);

They compare two values and return one of them. max() has the same overloaded methods such as min().

int second = Math.min(7, -9); // -9

round()

It gets rid of the decimal portion of the value. If the fractional part is .5 or higher, we round up.

long round(double num);
int round(float num);

There’re two overloaded methods to ensure there’s enough room to store a rounded double if needed.

long low = Math.round(123.45); // 123
long high = Math.round(123.50); // 124
int fromFloat = Math.round(123.45f); // 123

pow()

double pow(double number, double exponent);

It handles exponents. Fractionals exponents are allowed as well.

double squared = Math.pow(5,2); // 25.0

random()

double random();

It returns a value greater than or equal to 0 and less than 1. It cannot be negative.

double num = Math.random();

Summary

In this chapter, you learned that Strings are immutable sequences of characters. The new operator is optional. The concatenation operator (+) creates a new String with the content of the first String followed by the content of the second String. If either operand involved in the + expression is a String, concatenation is used; otherwise, addition is used. String literals are stored in the string pool. The String class has many methods.

StringBuilders are mutable sequences of characters. Most of the methods return a reference to the current object to allow method chaining. The StringBuilder class has many methods.

Calling == on String objects will check whether they point to the same object in the pool. Calling == on StringBuilder references will check whether they are pointing to the same StringBuilder object. Calling equals() on String objects will check whether the sequence of characters is the same. Calling equals() on StringBuilder objects will check whether they are pointing to the same object rather than looking at the values inside.

An array is a fixed-size area of memory on the heap that has space for primitives or pointers to objects. You specify the size when creating it—for example, int[] a = new int[6];. Indexes begin with 0, and elements are referred to using a[0]. The Arrays.sort() method sorts an array. Arrays.binarySearch() searches a sorted array and returns the index of a match. If no match is found, it negates the position where the element would need to be inserted and subtracts 1. Arrays.compare() and Arrays .mismatch() check whether two arrays are the equivalent. Methods that are passed varargs (…) can be used as if a normal array was passed in. In a multidimensional array, the second-level arrays and beyond can be different sizes.

An ArrayList can change size over its life. It can be stored in an ArrayList or List reference.

Generics can specify the type that goes in the ArrayList. Although an ArrayList is not allowed to contain primitives, Java will autobox parameters passed in to the proper wrapper type. Collections.sort() sorts an ArrayList.

Exam Essentials

Be able to determine the output of code using String. Know the rules for concatenating Strings and how to use common String methods. Know that Strings are immutable. Pay special attention to the fact that indexes are zero-based and that substring() gets the string up until right before the index of the second parameter.

Be able to determine the output of code using StringBuilder. Know that StringBuilder is mutable and how to use common StringBuilder methods. Know that substring() does not change the value of a StringBuilder, whereas append(), delete(), and insert() do change it. Also note that most StringBuilder methods return a reference to the current instance of StringBuilder.

Understand the difference between == and equals(). == checks object equality. equals() depends on the implementation of the object it is being called on. For Strings, equals() checks the characters inside of it.

Be able to determine the output of code using arrays. Know how to declare and instantiate one-dimensional and multidimensional arrays. Be able to access each element and know when an index is out of bounds. Recognize correct and incorrect output when searching and sorting.

Be able to determine the output of code using ArrayList. Know that ArrayList can increase in size. Be able to identify the different ways of declaring and instantiating an ArrayList. Identify correct output from ArrayList methods, including the impact of autoboxing.

Read More

Making Decisions

Creating making-decisions statements

Statements and Blocks

A block of code in Java is a group of zero or more statements between balanced braces {} and can be used anywhere a single statement is allowed.

patrons++; // single statement

// statement inside a block
{ 
	patrons++;
}

A statement or block often functions as the target of a decision-making statement

if(ticketsTaken > 1)
patrons++; // single statement

// statement inside a block
if(ticketsTaken > 1)
{ 
	patrons++;
}

The if statement

It executes a code of block only under certain circumstances.

One area where the exam writers will try to trip you up is on if statements without braces {}. The following example will print the statement only if the condition is met, but it will always execute the increment operation.

if(hourOfDay < 11)
	Sytem.out.println("Good Morning");
	morningGreetingCount++;

The else (if) statement

The order is important.

if(hourOfDay < 15) {
	System.out.println("Good Afternoon");
} else if(hourOfDay < 11) {
	// UNREACHABLE CODE
	System.out.println("Good Morning");
} else {
	System.out.println("Good Evening");
}

(!) Verify also, that the boolean expression inside the if statement, is actually a boolean expression.

int hourOfDay = 1;
if(hourOfDay) { // DOES NOT COMPILE
}

1 and 0 are not considered valid booleans.

int hourOfDay = 1;
if(hourOfDay = 5) { // DOES NOT COMPILE
}

switch statement

A single value is evaluated and flow is redirected to the first matching case. If no such statement is found that matches the value, an optional default statement will be called. If no such default option is available, the entire switch will be skipped.

proper switch syntax

the exam may present invalid switch syntax to see whether you’re paying attention.

int month = 5;

switch month { // DOES NOT COMPILE. NO PARENTHESIS.
	case 1: sout("january");
}

switch (month) // DOES NOT COMPILE. NO BRACKETS.
	case 1: sout("january");
	
switch (month) {
	case 1: 2: sout("january"); // DOES NOT COMPILE
}

switch (month) {
	case 1 || 2: sout("january"); // DOES NOT COMPILE
}

switch(month) { // COMPILES. perfectly valid. 
}

switch data types

The switch’s target variable is evaluated at runtime.

The following data types are permitted: int, byte, short, char, enum values, String, var (if its type is supported). Also the wrapper classes: Integer, Byte, Character… The following data types are not allowed in a switch: boolean, long, float, double.

var dayOfWeek = 5;
switch(dayOfWeek) {
	case 0:
		sout("sunday");
	default:
		sout("weekday");
	case 6:
		sout("saturday");
	break;
}
// weekday
// saturday

The default block doesn’t need to be at the end of the statement.

var dayOfWeek = 0;
switch(dayOfWeek) {
	case 0:
		sout("sunday");
	default:
		sout("weekday");
	case 6:
		sout("saturday");
	break;
}
// sunday
// weekday
// saturday

At the last example, it would also jump to the default value as there’s no break keyword.

At the exam, watch your for switch examples that are missing the break statement!

Acceptable case values

Not just any variable or value can be used in a case statement. The values must be compile-time constant values of the same data type. This means only literals, enums or final constant variables of the same type.

final int getCookies() { return 4; }
void feedAnimals() {
	final int bananas = 1;
	int apples = 2;
	int numberOfAnimals = 3;
	final int cookies = getCookies();
	switch(numberOfAnimals) {
		case bananas: 
		case apples: // DOES NOT COMPILE;
		case getCookies(): // DOES NOT COMPILE;
		case cookies: // DOES NOT COMPILE;
		case 3 * 5;
	}
}

This is valid, because it’s a final constant variable.

final int bananas = 1;
[...]
case bananas:

This is valid. It’s possible as expressions are allowed, provided the value can be resolved at compile-time. They also must be able to fit without an explicit cast.

case 3 * 5:

This is not valid, because it’s not final.

int bananas = 1;
[...]
case bananas: // DOES NOT COMPILE

This is not valid. It’s not evaluated until runtime. Cannot use methods.

final int bananas = getBananas();
[...]
case bananas: // DOES NOT COMPILE

This is not valid. It’s not evaluated until runtime. Cannot use methods.

case getBananas(): // DOES NOT COMPILE

A switch statement must have the same data type.

private void test(String firstName) {
	switch(firstName) {
		case 'J': // DOES NOT COMPILE. CHAR IS NOT STRING.
			break;
	}
}

Numeric Promotion and Casting

Switchs support numeric promotion that does not require an explicit cast

short size = 4;
final int small = 15;
final int big = 1_000_000;
switch(size) {
	case small:
	case 1+2:
	case big: // DOES NOT COMPILE
}

The compiler can easily cast from int to short at compile-time if a value is small enough to fit inside a short. If a number is too big, it wouldn’t compile.

while loops

One thing to remember is that a while loop may terminate after its first evaluation.

int full = 5;
while(full < 5) {
	// this would never print
	sout("Not full!");
	full++;
}

do / while statement

It guarantees that the statement or block will be executed at least once.

int lizard = 0;
do {
	lizard++;
} while(false);
sout(lizard); // 1

infinite loops

You have to make sure they always terminate.

int pen = 2;
int pigs = 5;
while(pen < 10) // infinite loop
	pigs++;

for loops

Variables declared in the initialization block of a for loop have limited score and are accessible only within the for loop. Be wary of this at the exam.

for(int i = 0; i < 10; i++)
	sout("value is: " +i);
sout(i); // DOES NOT COMPILE

var in a loop

Since Java10, you may now use var in a for loop. The compiler treats it as having an int.

for (var counter = 5; counter > 0; counter--) {
	sout(counter + " ");
}

For the exam you have to be able to read forward and backward loops. Pay atention to the decrement operator -- and the condition. < is not the same as <=. If you see a for loop with a decrement operator, you should assume they’re trying to test your knowledge of loop operations.

Working with for loops

Although most for loops are well defined, there’re five variations and edge cases you could see in the exam.

1. Creating an infinite loop
for( ; ; ) // COMPILES
	sout("Hello world!");

The semicolons are required. Without it wouldn’t compile.

for ( ) // DOES NOT COMPILE
	sout("Hello world!");
2. Adding multiple terms to the for
int x = 0;
for(long y = 0, z = 4; x < 5 && y < 10; x++, y++) {
	sout(y + " "); }
sout(x + " ");

You can declare a variable, such as x, before the loop begins and use it after it completes. The update statement can modify multiple variables.

3. Redeclaring a variable in the initialization block
int x = 0;
for(int x = 4; x < 5; x++) { // DOES NOT COMPILE
	sout(x + " ");
}

This does not compile because x is trying to be initialized twice and results in the compiler stopping because of a duplicate variable declaration.

We can fix this by removing the declaration of x from the for loop

int x = 0;
for(x = 0; x < 5; x++) {
	sout(x + " ");
}
4. Using incompatible data types in the initialization block
int x = 0;
for(long y = 0, int z = 4; x < 5; x++) { // DOES NOT COMPILE
	sout(y + " ");
}

The variables in the initialization block must all be of the same type. If both y and z were both long, the could would’ve compiled without issue.

5. Using loop variables outside the loop
for(long y = 0, x = 4; x < 5 && y < 10; x++, y++) {
	sout(y + " ");
}
sout(x); // DOES NOT COMPILE

This is important. If you notice x is defined in the initialization block of the loop, and then used after the loop terminates. Since x was only scoped for the loop, it will cause a compiler error.

Modifying loop variables

for(int i=0; i<10; i++) { // INFINITE LOOP
	i = 0;
}

Java does let you modify loop varaibles, whether they be in for, while, or do/while loops.

for(int k=0; k<10; ) // COMPILES. IT LOOPS 10 TIMES.
	k++;

for-each loop

public void printNames(String[] names) {
	for(String s : names) {
		sout(s);
	}
}

It’s designed to iterate over arrays and various Collection Framework classes.
The right side has to be one of the following:

  • An array
  • An object which implements from java.lang.Iterable

This does not inclue Map. It cannot be used in a for-each loop.

It also accepts a var.

public void printNames(String[] names) {
	for(var s : names) {
		sout(s);
	}
}

For the exam, when you see a for-each loop, make sure the right side is an array or Iterable object and the left side has a matching type.

String[] names = new String[3];
for(int name : names) { // DOES NOT COMPILE
	sout(name + " ");
}

Notice, the array is initialized with three null pointer values.

Controlling Flow with Branching

Other ways loops could end or branch

Nested Loops

int[][] myComplexArray = { {5,2,1,3}, {3,9,8,9}, {5,7,12,700} };

for(int[] mySimpleArray : myComplexArray) {
	for(int i=0; i<mySimpleArray.length; i++) {
		sout(mySimpleArray[i]+"\t");
	}
}

Nested loops can also inclued while and do/while

int hungryHippo = 8;
while(hungryHippo>0) {
	do {
		hungryHippo -= 2;
	} while(hungryHippo>5);
	hungryHippo--;
	sout(hungryHippo + ", ");
}

Optional labels

if, switch and loops can all have optional labels. A label is an optional pointer to the head of a statement that allows the flow to jump to it or break from it.

Taking the previous example

int[][] myComplexArray = { {5,2,1,3}, {3,9,8,9}, {5,7,12,700} };

OUTER_LOOP: for(int[] mySimpleArray : myComplexArray) {
	INNER_LOOP: for(int i=0; i<mySimpleArray.length; i++) {
		sout(mySimpleArray[i]+"\t");
	}
}

Labels follow the same rules for formatting as identifiers. They’re commonly expressed using uppercase lettters, with underscores between words. They’re extremely useful in nested structures.

(!) The following is out of exams’ scope(!) It’s also possible to add optional labels to control and block statements

int frog = 15;
BAD_IDEA: if(frog>10)
EVEN_WORSE_IDEA: {
	frog++;
}

The break statement

A break statement transfers the flow of control out of the enclosing statement. The same holds true for a while, do/while or for loop.

Without a label parameter, the break statement will terminate the nearest inner loop it’s currently in the process of executing. The optional label parameter allows us to break out of a higher level outer loop.

PARENT_LOOP: for(int i=0; i<list.length; i++) {
	for(int j=0; j<list[i].length; j++) {
		if(list[i][j]==searchValue) {
			positionX = i;
			positionY = j;
			break PARENT_LOOP;
		}
	}
}

The previous break PARENT_LOOP; statement will break out of the entire loop structure. The alternative without a label

PARENT_LOOP: for(int i=0; i<list.length; i++) {
	for(int j=0; j<list[i].length; j++) {
		if(list[i][j]==searchValue) {
			positionX = i;
			positionY = j;
			break;
		}
	}
}

Would break from the inner loop.

The continue statement

The syntax of the continue statement mirrors that of the break. They’re identical in how they’re used, but with different results.

The continue statement ends the current iteration of the loop. It’s applied to the nearest inner loop under execution, with optional label statement to override this behaviour

PARENT_LOOP: for(int i=0; i<list.length; i++) {
	for(int j=0; j<list[i].length; j++) {
		if(list[i][j]==searchValue) {
			positionX = i;
			positionY = j;
			continue;
		}
	}
}

The return statement

Using return statements can be used as alternative to using labels and break statements. Check this revised example

PARENT_LOOP: for(int i=0; i<list.length; i++) {
	for(int j=0; j<list[i].length; j++) {
		if(list[i][j]==searchValue) {
			return new int[] {i, j};
		}
	}
}

Unreachable Code

Any code places after break, continue and return is that any code placed immediately after them in the same block, is considered unreachable and will not compile.

int checkDate = 0;
while(checkDate<10) {
	checkDate++;
	if(checkDate>100) {
		break;
		checkDate++; // DOES NOT COMPILE. UNREACHABLE.
	}
}

It doesn’t matter if the structure actually visits the line of coed. It won’t compile.

Exam Essentials

Understand if and else decision control statements. The if and else statements come up frequently throughout the exam in questions unrelated to decision control, so make sure you fully understand these basic building blocks of Java.

Understand switch statements and their proper usage. You should be able to spot a poorly formed switch statement on the exam. The switch value and data type should be compatible with the case statements, and the values for the case statements must evaluate to compile-time constants. Finally, at runtime a switch statement branches to the first matching case , or default if there is no match, or exits entirely if there is no match and no default branch. The process then continues into any proceeding case or default statements until a break or return statement is reached.

Understand while loops. Know the syntactical structure of all while and do / while loops. In particular, know when to use one versus the other.

Be able to use for loops. You should be familiar with for and for-each loops and know how to write and evaluate them. Each loop has its own special properties and structures. You should know how to use for-each loops to iterate over lists and arrays.

Understand how break , continue , and return can change flow control. Know how to change the flow control within a statement by applying a break , continue , or return statement. Also know which control statements can accept break statements and which can accept continue statements. Finally, you should understand how these statements work inside embedded loops or switch statements.

Read More

Java Operators

Types of operators

In general, Java has three types of operators: unary, binary and ternary. There are for, respectively, one, two or three operands.

Java operators are not necessarily evaluated from left-to-right order, altough this may be overriden by parenthesis ().

int cookies = 4;
double reward = 3 + 2 * --cookies;
print("You receive " + cookies + " cookies!")

The result is 9

operator precedence

Operator Symbols and examples
post-unary operators expression++, expression
pre-unary operators ++expression, expression
other unary operators -, !, ~, +, (type)
multiplication/division/modulus *, /, %
addition/subtraction +, -
shift operators «, », »>
relational operators <, >, <=, >=, instanceof
equal to/not equal to ==, !=
logical operators &, ^, |
short-circuit logical operators &&, ||
ternary operators boolean expression ? expression1:expression2
assignment operators =, +=, -=, *=, /=, %=, &=, ^=, |=, «=, »=, »>=

Watch out

Some operators need specific types. Be aware of tricks like the following

// in java true and 1 are not related at all. 
int pelican = !5; // DOES NOT COMPILE
boolean penguin = -true; // DOES NOT COMPILE
boolean peacock = !0; // DOES NOT COMPILE

Pay also attention for the difference between attendance++ and ++atendance.

What does this print?

int lion = 3;
int tiger = ++lion * 5 / lion--;
System.out.println("lion is " + lion);
System.out.println("tiger is " + tiger);

Hint: The result is lion == 3, tiger == 5.

Always verify parentheses syntax. You need to make sure they’re always valid and balanced.

long pigeon = 1 + ((3 * 5) / 3; // DOES NOT COMPILE
int blueJay = (9 + 2) + 3) / (2 * 4; // DOES NOT COMPILE
short robin = 3 + [(4 * 2) + 4]; // DOES NOT COMPILE

Brackets [] are not allowed in Java to be used in place of parentheses ().

Numeric Promotion Rules

  1. If two values have different data types, Java will automatically promote one of the values to the larger of the two data types.
  2. If one if the values is integral and the other is floating point, Java will automatically protmote the integral value to the floating-point type.
  3. Smaller data types such as byte, short, char are first promoted to int any time they’re used with a binary arithmetic operator, even if neither of the operands is int.
  4. After all promotion has occurred and the operands have the same data type, the resulting value will have the same data type as its promoted operands.
int x = 1;
long y = 33;
var z = x * y;
// z would be long

double x = 39.21;
float y = 2.1;
var z = x + y;
// this does not compile, because float are assumed to be double, unless postfixed with f

short x = 10;
short y = 3;
var z = x * y;
// z would be int

short w = 14;
float x = 13;
double y = 30;
var z = w * x / y;
// z would be double

Assigning Values

Pay attention to compilation errors from assignment operators. They’re often overlooked on the exam.

Assignment Operator

An assignment operator is a binary operator that modifies or assigns the variable on the left side of the operator, with the result of the value on the right side.

// this is the simplest assignment operator. 
int herd = 1;

Java automatically promotes from smaller to larger data types, but it will throw a compiler exception if you’re trying to convert from larget to smaller data types without casting.

Casting values

Casting is a unary operation where one data type is interpreted as another data type. It is optional and unnecessary when converting to a larger or widening data type, but it’s required whenconverting to a smaller or narrowing data type. Without casting, the compiler will generate an error when trying to put a larger data type inside a smaller one.

int hair = (short)20;
short tail = (short) (4+10); //applies to both
short tail = (short) 4+10; //only applies to 4!

long feathers = 10 (long) // DOES NOT COMPILE
float egg = 2.0 / 9; // DOES NOT COMPILE
int tadpole = (int)5 * 2L; // DOES NOT COMPILE
short frog = 3 - 2.0; // DOES NOT COMPILE

During ther exam, remember to kep track of parentheses and return types any time casting is involved.

Reviewing Primitive Assignment

// NONE OF THESE EXAMPLES COMPILE(!)
// trying to save a double in an Integer
int fish = 1.0;
// outside of the range of short
short bird = 1921222;
// trying to save a floating-point
int mammal = 9f; 
// this is an int because missing L;
long reptile = 1932012354896546321654687;

Applying Casting

// THIS EXAMPLES COMPILE
int trainer = (int) 1.0;
short ticketTaker = (short)1921222; // stored as 20678
int usher = (int)9f;
long manager = 192301238193810323L;
short mouse = 10;
short hamster = 3;
short capybara = mouse * capybara; // DOES NOT COMPILE

The last example does not compile, because short values are automatically promoted to int when applying any arithmetic operator, with the resulting value being of type int.

We can fix this by casting:

short mouse = 10;
short hamster = 3;
short capybara = (short)(mouse * hamster);

Watch out for the following examples.

short mouse = 10;
short hamster = 3;
// casting is only applies to 'mouse'
short capybara = (short) mouse * hamster; // DOES NOT COMPILE
// promoted to int, because short is being used at '+' operation
short gerbil = 1 + (short) (mouse * hamster); // DOES NOT COMPILE

Compund Assignment Operators

(+=, -=, *=, /=)

They’re just glorified forms of the simple assignment operator.

itn camel = 2, giraffe = 3;
// simple assignment operator
camel = camel * giraffe;
// compund assignment operator
camel *= giraffe;

The left side of the operator cannot be used to declare a new variable.

It can also save us from having to explicitly cast a value. The following example does not compile, because we’re trying to save a long in an int.

long goar = 10;
int sheep = 5;
sheep = sheep * goat; // DOES NOT COMPILE

This could be solved with an explicit cast to (int), but also with the compound operator. It will first cast sheep to a long, apply the multiplication and then cast the result to int.

long goat = 10;
int sheep = 5;
sheep *= goat; // COMPILES

Assignment Operator Return Value

The result of an assignment is an expression in and of itself, equal to the value of the assignment.

This is perfectly valid.

long wolf = 5;
long coyote = (wolf=3);
sout(wolf); // 3
sout(coyote); // 3

(wolf=3) does two things.

  1. It sets the value of the variable wolf to be 3.
  2. It returns a value of the assignment, which is also 3.

This is something to be expected at an exam

boolean healthy = false;
if(healthy = true) // (!) = is not the same as ==
	sout("Good!"); 
// this code snippet will print Good!

The previous if() is not comparing, it’s assigning healthy a value of true!.

Comparing Values

Equality Operators (== / !=)

In Java there’s a semantic difference between “two objects are the same” and “two objects are equivalent”. For numeric and boolean primitives, there’s no such distinction.

They’re used for the following scenarios:

  • Compare two numeric or char primitive types. If the numeric values are of different data types, the values are automatically promoted. f.e. 5 == 5.0 returns true since the left side is promoted to double.
  • Comparing two boolean values
  • Comparing two objects, including null and String values.

You cannot mix and match types.

boolean monkey = false != "Grape"; // DOES NOT COMPILE
boolean gorilla = true == 3; // DOES NOT COMPILE

Pay attention to mixed assignment and equality operators.

boolean bear = false;
boolean polar = (bear = true);
System.out.println(polar); // true

For object comparison, the equality operator is applies to the references to the objects, not the objects they point to. Two references are equal if and only if they point to the same object or both point to null.

File monday = new File("schedule.txt");
File tuesday = new File("schedule.txt");
File wednesday = tuesday;
System.out.println(monday == tuesday); // false
System.out.println(tuesday == wednesday); // true

Relational Operators (<, >=, a instanceof b)

a instanceof b - returns true if the reference that a points to, is an instance of a class, subclass or class that implements a particular interface, as named in b.

Numeric Comparison Operators

The operators (<, <=, >, >=) apply only to numeric values. If the two numeric operands are not of the same data type, the smaller one is promoted.

instanceof Operator

It is useful for determining whether an arbitrary object is a member of a particular class or interface at runtime (polymorphism).

All classes inherit from java.lang.Object. This means that any instance can be assigned to an Object reference.

Integer zooTime = Integer.valueOf(9);
Number num = zooTime;
Object obj = zooTime;

At the previous example, there’s only one object created in memory, but three different references to it. Integer inherits both Number and Object. This means you can call instanceof on any of these references and it would return true for the three of them.

Polymorphism often comes into play is when you create a method that takes a data type with many possible subclasses.

public void openZoo(Number time) {
	if(time instanceof Integer) 
		sout((Integer)time + "O'clock");
	else
		sout(time);
}

It’s considered a good coding practice to use the instanceof operator, prior to casting from one object to a narrower type.

Invalid instanceof

Be aware of trying to use instanceof with incompatible types during the exam.

public static void openZoo(Number time) {
	if(time instanceof String) // DOES NOT COMPILE
	// ...
}
null and the instanceof operator

Calling instanceof on the null literal or reference (almost) always returns false.

sout(null instanceof Object); // false 

Object none = null;
sout(none instanceof String); // false

sout(null instanceof null); // DOES NOT COMPILE

Logical Operators

Operators Description
& AND
| Inclusive OR
^ Exclusive XOR; only true if only one value is true

Logical operators may be applied to both numeric and boolean data types. When they’re applies to booleans, the’re referred to as logical operators. When they’re applied to numeric data types, they’re referred to as bitwise operators.

Short-Circuit Operators

Operators Description
&& Short-curcuit AND
|| Short-curcuit OR

If the final result can be determined by the left side of the expression, the right side will never be evaluated.

Avoiding a NullPointerException

The previous is usefull for checking null objects before performing an operation.

// if duck may be null, this wouldn't ever throw NPE. 
if(duck!=null && duck.getAge() < 5) {
}

Making Decisions with the Ternary Operator

The ternary operator ? : is notable in that it’s the only operator that takes three operands.

int food = (owl < 2) ? 3 : 4;

The first operand must be a boolean. The second and third operands can be any expression that returns a value.

There is no requirement for the second and third expressions to be the same data types.

int stripes = 7; 
System.out.print((stripes > 5) ? 21 : "Zebra"); // COMPILES
int animal = (stripes < 9) ? 3 : "Horse"; // DOES NOT COMPILE

Summary

Watch out, there will likely be numerous questions on the exam that appear to test one thing, such as StringBuilder or exception handling, when in fact the answer is related to the misuse of a particular operator that causes the application to fail to compile.

When you see an operator involving numbers on the exam, always check that the appropiate datatypes are used and that they match each other where applicable.

Exam Essentials

Be able to write code that uses Java operators. This chapter covered a wide variety of operator symbols.

Be able to recognize which operators are associated with which data types. Some operators may be applied only to numeric primitives, some only to boolean values, and some only to objects. It is important that you notice when an operator and operand(s) are mismatched, as this issue is likely to come up in a couple of exam questions.

Understand when casting is required or numeric promotion occurs. Whenever you mix operands of two different data types, the compiler needs to decide how to handle the resulting data type. When you’re converting from a smaller to a larger data type, numeric promotion is automatically applied. When you’re converting from a larger to a smaller data type, casting is required.

Understand Java operator precedence. Most Java operators you’ll work with are binary, but the number of expressions is often greater than two. Therefore, you must understand the order in which Java will evaluate each operator symbol.

Be able to write code that uses parentheses to override operator precedence. You can use parentheses in your code to manually change the order of precedence.

Read More

Java Building Blocks

Creating Objects

Calling constructors

When you use a constructor

Park p = new Park();

First you declare the type you’re declaring Park and give the variable a name p. This gives a place to store a reference to an object.

Then you write new Park(); to actually create the object .

Structure of a constructor
  • Its name matches the name of the class
  • It has no return type
public class Meerkat {
	public Meerkat() { // this is a constructor
	}

	public void Meerkat() {
	// this is a method with a Capital letter. It will compile
	// 	but this is no constructor.
	}
}

Read More

Welcome to Java

Major Java components

The Java development kit (JDK) contains the minimum software you need to do Java development. It includes

  • the virtual machine JVM to execute your programs.
  • the compiler javac compiles .java files into .class. It converts instructions into bytecode.
  • the launcher java creates the VM and executes the program. Runs this bytecode on the actual machine.
  • the archiver jar command can package files together
  • the api documentation javadoc for generating documentation
  • jlink creates a executable that contains the required pieces that would have been in the JRE

Where did the JRE go?

From Java11 on, the JRE is no longer available. People use the full JDK when running a program.

The JRE was used for running a program but could not compile one. It was a subset of the JDK.

Read More

Java11 - Run file as a script

(This uses > java11)

To run a file as Java, we don’t need to do anything special to the .java file. Just write a class with a main() method and call it with java --source 11 file.java

Linux Shebang

To start it as a script in Linux we need to add java’s shebang #!/opt/jdk-11/bin/java --source 11 and do it executable chmod +x file.java. The shebang may need to be replaced if the java path is different.

Important

If we’re starting a java file as a script, the file’s name cannot end with .java or it won’t work.

Read More

From Java8 to Java11

This is a list of the changes at Java’s API I found interesting or that I may use frecuently. Not all the changes from Java9, 10 & 11 are listed here.

Java 9

Java REPL (JShell)

It stands for Java Shell. It’s used to easily execute and test any Java construction like a class, interface, enum, etc.

Module System

The way we deploy Java-Based applications using jars has a lot of limitations & drawbacks. Some of these are: The JRE & JDK are too big; JAR files are too big to use in small devices and applications; There’s no strong encapsulation, public is open to everyone.

The new Module System introduces new features to avoid all this. More information here.

Factory Methods for Inmutable Collections (List, Map, Set & Map.Entry)

(I’ll use Lists as an example in this file, but this is valid for Maps and Sets too)

Until Java8 we could use Collections.unmodifiableList() to achieve this, but this is really verbose. Now we can do the same with

List inmutableList = List.of("bla", "ble", "bli");

Read More