Modifiers
Class
- Public: Accessible from any other class
- Private: Only accessible from within the same class
- Static: Can only be used in nested classes
- Final: Class cannot be inherited from
- Abstract: Class cannot be instantiated, has to be inherited
Variable (Field)
- Public: Accessible from any other class
- Private: Only accessible from within the same class
- Static: Shared between all instances of the class
- Final: Value cannot be changed after initialisation (can only be assigned once)
Method
- Public: Accessible from any other class
- Private: Only accessible from within the same class
- Static: Can be called without creating an instance of the class
- Final: Cannot be overridden in subclasses
- Abstract: Implementation not provided, subclasses have to implement it
Polymorphism
Overloading
Java classes can contain multiple methods with the same name, if they have different signatures, e.g.
class Circle {
int r;
// ...
@Override
public boolean equals(Object x) {
return false;
}
public boolean equals(Circle c) {
return c.r == this.r;
}
}Dynamic Binding
How do we know which method to call when we execute the program?
class ColouredCircle extends Circle {
int colour;
@Override
public boolean equals(Object x) {
return false;
}
public boolean equals(ColouredCircle c) {
return super.equals(c) && this.colour = c.colour;
}
}
ColouredCircle cc = new ColouredCircle(1,1);
Circle c = new ColouredCircle(1,2);
System.out.println(c.equals(cc)); // <- How does this line work
// prints true1. At compile time
- Identify the compile-time type of
c(Circle) - Check the class
Circlefor methods calledequals - Choose the most specific method signature that does not lead to a compilation error in this case
Circle::equals(Circle)- since it is more specific than
Circle::equals(Object) - does not lead to a compilation error when a
ColouredCircle(compile-time type ofcc) is passed to it
- since it is more specific than
- Store this method descriptor in the compiled bytecode
- (Perform type erasure if necessary)
2. At run time
- Retrieve the method descriptor from step 1
- Determine run-time type of
c(ColouredCircle) - Look for an accessible method with a matching descriptor
- First, look in the current class (
ColouredCircle) - If not found, look up the parents until found (this will always find something, because the compiler got the method descriptor from a parent in the first place)
- The first matching method will be executed (most specific signature after type erasure)
- First, look in the current class (
Most Specific Method Signature?
A method is more specific than a method if the arguments to can be passed to without compilation error. So hi(ColouredCircle) is more specific than hi(Circle)
Bridge Method
// Before
public class Node<T> {
public T data;
public Node(T data) { this.data = data; }
public void setData(T data) {
this.data = data;
}
}
public class MyNode extends Node<Integer> {
public MyNode(Integer data) { super(data); }
public void setData(Integer data) {
super.setData(data);
} // This intends to override setData
}
MyNode mn = new MyNode(1);
mn.setData("hello");
// After type erasure & bridge method
public class Node {
public Object data;
public Node(Object data) { this.data = data; }
public void setData(Object data) {
this.data = data;
}
}
public class MyNode extends Node {
public MyNode(Integer data) { super(data); }
public void setData(Object data) {
setData((Integer) data); // this refers to MyNode::setData
} // BRIDGE METHOD
public void setData(Integer data) {
super.setData(data);
}
}- A bridge method is generated when both:
- A type extends/implements a parameterised type
- Type erasure changes the signature of one or more inherited methods
- The bridge method is generated right after type erasure
- Note that in the example above
- We expect the signatures before type erasure to match, since
Node::setData(T)andTis integer, andMyNode::setData(Integer) - But the signatures after type erasure do not match:
Node::setData(Object)vsMyNode::setData(Integer) - So, calling
myNode.setData("hello")will work, becauseMyNode::setDatais just overloadingNode::setData. - The bridge method ensures the overriding works as expected
- We expect the signatures before type erasure to match, since
Syntax
General Stuff
// Point.java
public class Point {
int x;
int y;
public Point(int x, int y) {
this.x = x;
this.y = y;
}
}// Shape.java
abstract class Shape implements GetAreable, Comparable { // comparable not actually implemented, just an example
private int numAxesOfSymmetry;
public boolean isSymmetric() {
return numAxesOfSymmetry > 0;
}
}// Circle.java
public class Circle extends Shape {
Point centre;
int radius;
public Circle(Point centre, int radius) {
this.centre = centre;
this.radius = radius;
}
@Override
public double getArea() {
return Math.PI * Math.pow(r, 2);
}
@Override
public boolean equals(Object x) {
return false;
}
public boolean equals(Circle c) {
return this.centre.equals(c.centre) && this.radius == c.radius;
}
}// ColouredCircle.java
public class ColouredCircle extends Circle {
String colour;
public ColouredCircle(Point centre, int radius, String colour) {
super(centre, radius);
this.colour = colour;
}
@Override
public boolean equals(ColouredCircle c) {
return super.equals(c) && this.colour == c.colour;
}
}// GetAreable.java
public interface GetAreable {
double getArea();
}// Main.java
public class Main {
public static void main(String[] args) {
Point origin = new Point(0,0);
Shape[] shapes = {
new Circle(origin, 1),
new ColouredCircle(origin, 1, "green") }
// or Shape[] shapes = new Shape[2] for empty array
// shapes.length is the array length
for (Shape shape : shapes) {
System.out.println(shape.getArea());
}
}
}Exceptions
// MyFileNotFoundException.java
// This is a checked exception, which means we have to handle it with a try catch, or declare it with a throws statement. It is checked at compile time.
// This is used when the exception is caused by something out of our control e.g. a file not existing, web request fail etc.
class MyFileNotFoundException extends Exception {
public MyFileNotFoundException(String message) {
super(message);
}
@Override
public String getMessage() {
return "File not found! Message: " + super.getMessage();
}
}// ArrayIndexException.java
// This is an unchecked exception, which means we do not need to handle it in the code.
// This is used for programmer error e.g. null pointer exception, array index out of bounds
class ArrayIndexException extends RuntimeException {
}class Example {
public String readFile(String path) throws MyFileNotFoundException {
// ...
throw new MyFileNotFoundException("file " + path + " not found!");
}
public String readFiles(String[] paths) {
String result = "";
for (String path : paths) {
try {
result += this.readFile(path)
} catch (MyFileNotFoundException e) {
System.out.println(e.getMessage());
} finally {
// this code is always run
System.out.println("done with a file!");
}
}
}
}Generic Types
Generic Class
// Pair.java
public class Pair<S, T> {
// convention: type parameters are single capital letters
private S first;
private T second;
public Pair(S first, T second) {
this.first = first;
this.second = second;
}
public S getFirst() {
return this.first;
}
public T getSecond() {
return this.second;
}
}
// note: generic types need to be reference types, not primatives
// does not work, int is not a reference type:
Pair<int, String> a = new Pair<int, String>(123, "Hello");
// works:
Pair<Integer, String> a = new Pair<Integer, String>(123, "Hello");
// syntax to use the type parameters of the variable:
Pair<Integer, String> a = new Pair<>(123, "Hello"); // <> diamond operator is equivalent to above
// Generic types can be bound,
public class Pair<S extends Abc, T> {// ... }
// Abc can be a class or interface. This just means that S should be a subtype of Abc
// Multiple bounds
public class Pair<S extends Abc & Def, T> {// ... }Generic Method
// Contains.java
public class Contains {
public static <T> boolean contains(T[] items, T target) {
for (T item : items) {
if (item.equals(target)) {
return true;
}
}
return false;
}
}
// application
Integer[] ints = new Integer[] {1,2,3};
Contains.<Integer>contains(ints, 2);Generics cannot be used with arrays, due to type erasure
new Pair<String, Integer>[2]; // error
class Abc<S, T> {
private S[] arr; // ok
// ...
new Pair<S, T>[2]; // error
this.arr = new S[2]; // error
this.arr = (S[]) new Object[2]; // ok, but produces warning
// to suppress this warning:
@SuppressWarnings("unchecked") // be careful of how items can be added to the array
S[] a = (S[]) new Object[2];
this.arr = a;
}Wildcards
class Shape {}
class Circle extends Shape {}
class Container<T> {
private T item;
public Container(T item) {
this.item = item;
}
public T get() {
return item;
}
public void set(T item) {
this.item = item;
}
// upper bound wildcard
// we want to allow copying from a container that contains anything that is a subtype of T
public void copyFrom(Container<? extends T> c) {
this.item = c.get();
}
// lower bound wildcard
// we want to allow copying to a container that contains anything that is a supertype of T
public void copyTo(Container<? super T> c) {
c.set(this.item);
}
}
// demo
Shape s = new Shape();
Circle c = new Circle();
Container<Shape> sc = new Container<>(s);
Container<Circle> cc = new Container<>(c);
sc.copyFrom(cc); // ok
cc.copyFrom(sc); // error
cc.copyTo(sc); // ok
sc.copyTo(cc); // error@SuppressWarnings("unchecked")
Queue<Passenger>[] q = (Queue<Passenger>[]) new Queue<?>[nStops];
this.queues = q; // Queue<Passenger> <: Queue<?>Java Types
Primitive types
- byte short int long float double
- char int
Compile-time vs Run-time types
Circle c = new ColouredCircle(...);The compile-time type of c is Circle. This does not change.
The run-time type of c is ColouredCircle, which can change, e.g. c = new Circle();
Variance of Types
- Let be a complex type based on type . is:
- Covariant if implies
- Contravariant if implies
- Invariant if is neither covariant nor contravariant: and have no relationship
- Examples
- Covariant: Java arrays are covariant (reference type arrays only, not primitive arrays)
- Invariant: Java generics are invariant
// Covariant
Integer[] ints;
Object[] objects;
objects = ints; // no compiler error, Integer[] <: Object[] (arrays are covariant)
ints = objects; // error
// Invariant
Pair<Integer, Integer> ints = // ...
Pair<Object, Object> objects = // ...
objects = ints; // error
ints = objects; // errorHow Generic Types Work
- Compiler checks types
- Compiler removes generics (Type Erasure), replaces them with either
Objectfor unbound generic types or the first type bound for bound generic types
class Pair<S implements Abc & Def, T> {
private S first;
private T second;
public Pair(S first, T second) {// ... }
public S getFirst() { return this.first; }
public T getSecond() { return this.second; }
}After the compiler has checked the types, and is happy, this becomes:
class Pair {
private Abc first;
private Object second;
public Pair(Abc first, Object second) {// ... }
public Abc getFirst() { return this.first; }
public Object getSecond() { return this.second; }
}also,
Pair<String, Integer> p = new Pair<String, Integer>("hello", 1);
Integer i = p.getSecond();becomes:
Pair p = new Pair("hello", 1);
Integer i = (Integer) p.getSecond();Wildcard Types
- See Wildcard Syntax
- Upper-bound wildcard
A<? extends S>means the generic type parameter can be anything that is a subtype of S- For any type
S,A<S><:A<? extends S>(noteS<:S) - Covariance: If
S<:T, thenA<? extends S><:A<? extends T>- So,
A<S><:A<? extends T>, due to transitive property
- So,
- Lower-bound wildcard
A<? super S>means the generic type parameter can be anything that is a supertype of S- For any type
S,A<S><:A<? super S>(noteS<:S) - Contravariance: If
S<:T, thenA<? super T><:A<? super S>- So,
A<T><:A<? super S>, due to transitive property
- So,
- Unbounded wildcard
A<?>means the generic type parameter can be anythingA<?>is a supertype of allA<...>
- mnemonic PECS: producer
extends; consumersuper- In above example, in the
copyFrommethod,Container<? extends T> c- We call
c.get(), wherecis “producing” a value - So, we use
extends
- We call
- In the
copyTomethod,Container<? super T> c- We call
c.set(), wherecis “consuming” a value - So, we use
super
- We call
- In above example, in the
Type Inference
// example class A
public <T extends GetAreable> T findLargest(Seq<? extends T> seq)
// ...
Shape s = A.findLargest(new Seq<Circle>(0));Sources of Type Constraints
- Type bound:
<T extends GetAreable>→T <: GetAreable - Target typing:
Shape s = ...→T <: Shape - Upper bound wildcard:
Seq<? extends T>→Seq<Circle> <: Seq<? extends T>→Circle <: T
Inference
- Consider all type constraints, in solving for
TT <: GetAreableT <: ShapeCircle <: T (<: Object)
- Solve the constraints, if possible, else, compiler error. note
Shape <: GetAreable4.Circle <: T <: Shape - Determine lower bound, that is the type of
T, or use the rules: 2.Type1 <: T <: Type2→Tinferred asType13.Type1 <: T (<: Object)→Tinferred asType 14.T <: Type2→Tinferred asType 2