Skip to content

Latest commit

 

History

History
558 lines (400 loc) · 12 KB

JAVA.md

File metadata and controls

558 lines (400 loc) · 12 KB

Java Quick Reference

Terminology

Classpath

The classpath contains paths to user defined classes. If a project has library dependencies, the classpath should contain paths to those libraries.

Language features

Below are a some of the language features used in the worksheets and coursework.

Inheritance

One of the fundamental concepts of Object Oriented programming. Given some class Animal

class Animal {
	// some properties
}

We could create another class that inherits properties of Animal :

class Dog extends Animal {
	// some more properties
}

Methods in the base class Animal could be overridden to alter behavior.

Java also supports abstract classes. An abstract class could not be instantiated directly. Below is an example of abstract class by making Animal abstract and Dog a concrete implementation of Animal:

abstract class Animal {
}

class Dog extends Animal {
}

// to instantiate:
    new Animal(); // error
		    new Dog(); // ok

Generics

Generics are supported since Java 5. The simplest form looks like this:

class Pair<L, R> {
	final L left;
	final R right;

	//...
	Pair(L l, R r) {
		this.left = l;
		this.right = r;
	}
	//...
}

The Pair<L,R> class can be instantiated by:

Pair<Integer, String> pair=new Pair<Integer, String>(42,"foo");

The right hand side generic parameter could be inferred by the compiler so they can be reduced to new Pair<>(...).

Generics are also supported in interfaces and static methods, below is an example of generics in static method:

    //unchecked
static<T> T create(Object object){return(T)object;} 

Type inference

Java supports simple type inference since Java 7.

Without type inference:

List<String> strings=new ArrayList<String>();

With type inference:

List<String> strings=new ArrayList<>();

The <> symbol is known as the diamond operator. The operator could be used with any parameterised type, for example:

Map<String, Map<Long, Pair<Double, Double>>>map=new HashMap<>();

Type inference also works with static methods:

List<String> strings=Arrays.asList("foo","bar");

Which is actually:

List<String> strings=Arrays.<String>asList("foo","bar");

This usage is called the type witness. It is only required when the compiler does not have sufficient information to deduce the target type.

The compiler might not be able to deduce the target type when:

  • Instantiating an anonymous class (no longer the case in Java 9)
  • Method returns a raw type

Type erasure

Generics in Java are non-reifiable, meaning that generics are removed at compile time.

Due to this limitation, the following code is illegal:

static<T> T createMyObject(){return new T();} // illegal
static<T> T[]createMyArray(){return new T[42];} // illegal

In the above examples, T gets erased and effectively becomes java.lang.Object. The type information gets lost so during runtime the JVM could not decide which type to instantiate. There are tricks around these restrictions but those are out of the scope of this document.

Type erasure also forbids method overloading with generic types in some cases:

static void doIt(List<String> args){}
static void doIt(List<Integer> args){} // illegal

Both method has the same erasure so overload resolution fails.

Wildcards

Java supports wildcards through bounds. For example, the following method accepts a List of any type:

static void doIt(List<?> items){}

We could also limit the accepted type to some subtype(upper bound) or supertype(lower bound):

static void doA(List<?extends Animal> items){}
static void doB(List<? super Dog>items){}

This is called use-site variance as the use-site of the methods decide whether the parameters conform the the methods's generic wildcard expression.

Intersection types

Java have limited support of intersection types. For example:

static<T extends A & B> void foo(T t){}

The method foo restricts the type of T to some type that implements both interface A and B.

Covariance and Contravariance

For simple inheritance, Java methods has the following properties:

  • Parameters types - contravariant
  • Return type - covariant

For generics, the relationship could be illustrated in form of a graph:

Given:
    A<E> <- A'<E>
    B <- B'
      
        A<? extends B>
      /  |
     /   |
   A<B>  |
    |   A'<? extends B>    
    |   / 
    |  /
  A'<B>
   

All edges points upward.

Exceptions

Java supports two types of exceptions: checked and unchecked.

Unchecked exception

Unchecked exceptions does not need to be surrounded by try-catch blocks. For example:

throw new RuntimeException("Assertion error!");

The program crashes with a stacktrace to the given exception.

Checked exception

Checked exceptions must be surrounded by try-catch blocks. For example:

try{
	throw new IOException();
}catch(Exception e){
	throw new RuntimeException(e);
}

The above example also showcases a common usage of rethrowing exception as unchecked ones in case the original exception is not recoverable.

Varargs

Java supports variadic arguments, or parameters that take an arbitrary length argument with the same type.

void foo(String...strings){...}

The resulting type of strings is simply an array of String, String[]. Varargs can only be used once and must be the last argument in a method signature. For example:

void foo(int a,int b,String...strings){} // legal
void bar(String...strings,int a,int b){} // illegal

Varargs could be useful at limiting ways to construct a object. Given:

class Foo {
	Foo(int first, int second, int... rest) { /*...*/ }
}

The constructor Foo effectively requires n+2 arguments to instantiate

Initialisers

Other than constructors, Java supports initialisers for instantiation:

Instance initialisers

For example

class Foo {
	int foo;

	{
		foo = 42;
	}
}

A common use case is the double brace initialisation of collection types:

new ArrayList<Integer>(){{
	add(1);
	add(2);
}};

NOTE: the use of double brace initialisation is debatable as the instantiated ArrayList is not an ArrayList itself, but an anonymous class that extends ArrayList

Static initialisers

class Foo {
	static final String constant;

	static {
		constant = "bar";
	}
}

Anonymous class

Java supports inline classes that loosely captures the notion of closures, for example:

List<String> strings=new ArrayList<>(){};

strings is now some anonymous class that extends ArrayList . The class can also capture effectively-final variables if used within a method call, for example

class Main {
	interface Action<T> {
		T doIt(T source);
	}

	public static final void main(String[] args) {
		final int value = 10;
		Action<Integer> addValue = new Action<Integer>() {
			@Override
			public Integer doIt(Integer source) {
				return source + value;
			}
		};
		addValue.doIt(10); // == 20;
	}
}

Annotations

Java supports annotations as a facility for metaprogramming. Annotations may be read at runtime or compile time to generate code.

The simplest form can be seen while overriding methods:

public class Foo {
	@Override
	public String toString() {
		return "foo";
	}
}

Where the @Override annotation notifies the compiler that the method must override a method.

Static imports

To reduce verbosity, Java allows import of static instances. For example:

List<Integer> list=Arrays.asList(1,2,3);

Can be rewritten as:

import static java.util.Arrays.asList;
// ...
List<Integer> list=asList(1,2,3);

Note that this only works when method names do not clash.

Java 8+

Java 8 introduced syntax support for functional programming as well as backwards compatible interface evolution.

Lambda expressions

Starting from Java 8, syntactic sugar for anonymous class has been introduced to improve readability. Given the anonymous class with its definition:

interface Action<T> {
	T doIt(T source);
}
Action<Integer> increment = new Action<Integer>() {
	@Override
    public Integer doIt(Integer source) {
		return source + 1;
	}
}

increment can be rewritten as:

Action<Integer> increment=source->source+1;

Method reference

To further reduce boilerplate, lambda expressions that take the exact same parameter could be further simplified. Given a definition and some prefix function:

interface Action<T> {
	T doIt(T source);
}
Action<String> prefixFoo = x -> "Foo".concat(x);

prefixFoo can be rewritten as:

    Action<String> prefixFoo="Foo"::concat;

Default methods

Interfaces in Java 8 now supports methods with default implementations. For example:

interface Foo {
	void doIt();

	default void doItTwice() {
		doIt();
		doIt();
	}
}

This is particularly useful for adding features to old interfaces without breaking binary compatibility. Notable uses include Collection#stream and Collection#forEach.

Libraries and API

Java has an active community and an abundance of third-party libraries that would shorten development time. Below is a list of commonly used libraries.

This list is by no means representative as there are many domain specific libraries as well.

Java 8+ Stream

Java 8+ introduced Streams for lazy processing. Previously tedious task such as filtering collection elements are now easy and safe.

For example:

List<Integer> ints=Arrays.asList(1,2,3,4,5);

//Java 7:
List<Integer> result=new ArrayList<>();
for(Integer value : ints){
    if(value%2) result.add(value);
}

//Java 8:
List<Integer> result=ints.stream().filter(i->i%2).collect(toList());

Java 8+ Optional

Java 8+ provided a simple utility class called Optional to safely navigate around null values. For example:

class Foo {
	String bar() {
		return null;
	}
}

Foo badFoo = null;
// Java 7
int barLength = 0;
if(badFoo!=null){
	String bar=badFoo.bar();
	if(bar!=null){
		barLength=bar.length();
	}
}

// Java 8
int barLength=Optional.ofNullable(badFoo)
    .map(Foo::bar)
    .map(String::length)
    .orElse(0);