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.