Technical details below! Some of the details are super important. Some you should memorize. Some are rather hard to memorize, but that’s okay; you can just look them up when needed. The point of this section is to act as an almost complete reference, rather than a tutorial.
class Circle { } // A class var c1 = new Circle(); // an instance var c2 = new Circle(); // another instance
class Dog { String name; // field Dog(String n) { // constructor name = n; System.out.println(n); } String bark() { // method return name + " says woof!"; } static { System.out.println("Dogs!"); } // static initializer (rare) { System.out.print("Creating dog "); } // instance initializer (rare) } // prints "Dogs!" var d1 = new Dog("Nimbus"); // prints "Creating dog Nimbus" var d2 = new Dog("Lucy"); // prints "Creating dog Lucy" d2.bark() // "Lucy says woof!"
private
or public
. If private, members are only available to code within the class; if public, they can be used outside the class. (Other modifiers will be described later.)
class Dog { // Make the field private so no one can change it from outside the class private String name; // Constructors and methods public so anyone can create dogs and make them bark public Dog(String n) { name = n; } public String bark() { return name + " says woof!"; } }
class Dog { private String name; // instance field private static int dogsCreated; // class field public static final String species = "Canis familiaris"; // another class field public String name() { return name; } // instance method public static int dogsCreated() { return dogsCreated; } // class method public Dog(String n) { name = n; dogsCreated++; } // constructor } var pet = new Dog("Sirius"); var friend = new Dog("Sebastian"); System.out.println(pet.name()); // Each dog has its own name System.out.println(friend.name()); // Each dog has its own name System.out.println(Dog.species); // all dogs share a species System.out.println(pet.species); // legal, but uncommon System.out.println(Dog.dogsCreated()); // 2
this
keyword. This is particularly useful in constructors, where parameters often have the same name as fields!class Location { private double latitude; private double longitude; public Location(double latitude, double longitude) { this.latitude = latitude; this.longitude = longitude; } }
==
operator on objects only checks if the references refer to the same object. It never compares the actual contents:var p1 = new Location(37.6308, 119.0326); var p2 = p1; var p3 = new Location(37.6308, 119.0326); p1 == p2; // true p1 == p3; // false
class Part { private String manufacturer; private String serialNumber; public Part(String manufacturer, String serialNumber) { this.manufacturer = manufacturer; this.serialNumber = serialNumber; } public String manufacturer() { return manufacturer; } public String serialNumber() { return serialNumber; } } class Tire extends Part { private String descriptor; private boolean winter; public Tire(String manufacturer, String serialNumber, String descriptor, boolean winter) { super(manufacturer, serialNumber); this.descriptor = descriptor; this.winter = winter; } public String descriptor() { return descriptor; } public boolean isWinter() { return winter; } // The methods manufacturer and serialNumber are inherited here! } var t = new Tire("Michelin", "3X8946P-988Q", "225/45R17", true); System.out.println(t.serialNumber()); System.out.println(t.isWinter());
Part p = new Tire("Michelin", "3X8946P-988Q", "225/45R17", true); System.out.println(p.serialNumber());
Part p = new Tire("Michelin", "3X8946P-988Q", "225/45R17", true); System.out.println(p.serialNumber()); // OK // p.isWinter() // ILLEGAL!!! Do you see why?
Object
(well...every class except Object
itself.) You don’t have to say extends Object
—that happens for free. So we can write this:
class Dog {} Object d = new Dog(); Object s = "Strings are objects too"; Object[] things = new Object[]{new Dog(), "Hello"};
Object
includes a number of its own methods. that all classes inherit. These include equals()
, hashCode()
, toString()
, getClass()
, wait()
, notify()
, notifyAll()
, and clone()
. Not all of these are commonly used.Part
and Tire
example), or you may be able to override it, by providing a specialized implementation:
class Point { private double x; private double y; public Point(double x, double y) { this.x = x; this.y = y; } @Override public String toString() { return "(" + x + "," + y + ")"; } } var p = new Point(5, 8); System.out.println(p); // Automatically invokes p.toString() ...cool right?
final
in a superclass. A final method cannot be overriden in a a subclass. Interestingly, but very sensibly, getClass
, wait
, notify
, and notifyAll
are all marked final
in Object
. (This makes sense, as overriding getClass
for example, would be plain nuts.) However you absolutely do want subclasses to provide their own overridden versions of equals, hashCode, and toString.toString
to make a nice printable string representation of an object, because the default toString
is pretty ugly. The default for equals
is just ==
so if you want to compare fields, overriding equals
is necessary. (For example the built-in String
class overrides this to compare characters.) There is a rule in Java that says If you override equals you must also override hashCode. If you don’t, putting your objects in sets and maps won’t work right. The pattern for overriding equals
and hashCode
is not so bad, just follow this example:
class Point { private double x; private double y; public Point(double x, double y) { this.x = x; this.y = y; } public double x() { return x; } public double y() { return y; } @Override public boolean equals(Object o) { return (o instanceof Point other) && x == other.x && y == other.y; } @Override public int hashCode() { return Objects.hash(x, y); } @Override public String toString() { return "Point[x=" + x + ", y=" + y + "]"; } }
equals
, hashCode
, and toString
are automatically overridden for you to do the right thing! So that big ugly class in the previous item could be written instead as:
record Point(double x, double y) {}
How important is overriding equals and hashCode?You probably won’t need to much in practice. If you make mutable data structures, they tend to be one-off objects for storing and retrieving information to help you with some larger application. You almost never build data stores like this for the purposes of seeing if they are equal to other stores.
But small data objects, like points and vectors and big numbers and lines and colors, for which you make lots of instances, will likely be put in sets or compared by value. But in this case, just use records and you will be fine.
That said, you should be aware of how wonky Java is in this area, so you understand the code you write.
Are records totally immutable?They are only shallowly immutable. Shallowness is something you see when you talk about deep vs. shallow copy, deep vs. shallow equality, and deep vs. shallow immutability. Details in class. Records only give you shallow benefits, but that’s still great.
String
is final. Can you see why? Subclassing String
would only be confusing to people; strings should all work the same for everyone. To declare one of your own classes final:
final class Atom { }Extending
Atom
is now an error.
abstract
. An abstract class is not allowed to have any instances; if you try instantiating with new
, you’ll get an error. Instead, you instantiate only its subclasses. Here’s a good example. Circles and rectangles are both shapes and we know how to compute their areas and perimeters. But there’s no such thing as a “plain” shape, only specific kinds of shapes. Study the example:
abstract class Shape { private String color; public Shape(String color) { this.color = color; } public String color() { return color; } public abstract double area(); public abstract double perimeter(); } class Circle extends Shape { private double radius; public Circle(String color, double radius) { super(color); this.radius = radius; } @Override public double area() { return Math.PI * radius * radius; } @Override public double perimeter() { return 2 * Math.PI * radius; } } class Rectangle extends Shape { private double length; private double width; public Rectangle(String color, double length, double width) { super(color); this.length = length; this.width = width; } @Override public double area() { return length * width; } @Override public double perimeter() { return 2 * (length + width); } }
area
in the Rectangle class.
final
and abstract
?
interface Group { void add(); void remove(); }Here a group is anything that can be added to and removed from.
Interface member modifiersAll methods in an interface are automatically public, whether you mark them public or not!
Unless marked
static
ordefault
, an interface method will be automatically abstract, whether you mark it abstract or not.
class Shape { ... } class Circle extends Shape { ... } interface Drawable { ... } interface Saveable { ... } interface Movable { ... } class SuperDuperCircle extends Circle implements Drawable, Saveable, Movable { ... }
interface Solid { double volume(); } record Sphere(double radius) implements Solid { double volume() { return 4.0 / 3.0 * Math.PI * Math.pow(radius, 3); } } record Box(double width, double height, double depth) implements Solid { double volume() { return width * height * depth; } }
interface Collection { void add(Object o); void remove(Object o); int size(); default boolean isEmpty() { return size == 0; } }
interface Geometry { static double circleArea(double r) { return Math.PI * r * r; } static double squareArea(double side) { return side * side; } static double perimeter(double side) { return side * 4.0; } static double boxVolume(double w, double h, double d) { return w * h * d; } }
Now you can simple call Geometry.perimeter(2)
for example.
More about Interfaces
public sealed interface BinaryTree permits EmptyTree, BinaryTreeNode { int size(); // More methods... } final class EmptyTree implements BinaryTree { public int size() { return 0; } // ... } final class BinaryTreeNode implements BinaryTree { private String data; private BinaryTree left; private BinaryTree right; public int size() { return left.size() + right.size() + 1; } // ... }
enum ArticleStatus { DRAFT, PUBLISHED, REDACTED, REPLACED; }
Now ArticleStatus.DRAFT
is one of the four instances of ArticleStatus
. There is much more to enums to know, but this above is a start.
Zero Allowed | Fixed Number of | |
---|---|---|
Instances | Abstract Class | Enum |
Subclasses | Final Class | Sealed Class |
Class
. Every class is an object of the class Class
. You can get the class of an object x with x.getClass()
. If c
is the name of a class (or a primitive type) you can write c.class
.
"hello".getClass() == String.class // true
Feel free to skip the following micro-items on a first read.
There are five kinds of classes: package-level, nested top-level, member, local, or anonymous. (The last four kinds are called inner classes.
/** * A throw-away class that illustrates the five kinds of Java classes. * 'A' is a package-level class, 'B' is a nested top-level class, * 'C' is a local class, 'D' is a local class, and there is an * anonymous class that is a subclass of Thread. */ class A { int x; static class B { int x; } class C { int x; int sum() {return x + A.this.x;} } void f() { class D { int x; } D d = new D(); Thread e = new Thread() { public void run() { System.out.print("Hello from " + getName()); } }; System.out.println(d.x); e.start(); } } /** * Application that shows off how one would use the demo class above. */ public class ClassExample { public static void main(String[] args) { A a = new A(); a.x = 1; A.B b = new A.B(); b.x = 2; A.C c = a.new C(); c.x = 3; System.out.println(c.sum()); a.f(); } }
See the official documentation for:
Here’s some good reference information. In the declaration of a class or member the entity can be marked with zero or more modifiers. (But private
, protected
and public
are supposed to be mutually exclusive.)
Class Modifiers | |
---|---|
(no modifier) | accessible only within its own package |
public | accessible wherever its package is |
abstract | cannot be instantiated (may have abstract methods) |
final | cannot be extended (subclassed) |
sealed | has only the subclasses indicated |
static | makes an inner-declared class really a top-level one |
Member Modifiers | |
private | accessible only within its class |
(no modifier) | accessible only within its package |
protected | accessible only within its package and to its subclasses |
public | accessible wherever its class is |
Field Modifiers | |
final | value may not be changed, once assigned |
static | only one instance of the field shared by all objects of the class |
transient | field is not serialized |
volatile | value may change asynchronously compiler must avoid certain optimizations |
Method Modifiers | |
final | may not be overridden |
static | method does not apply to a particular instance |
abstract | has no body; subclasses will implement |
synchronized | requires locking the object before execution |
native | implementation is not written in Java, but rather in some platform dependent way |
wait()
and notify()
are methods of the class Object
.class Greeter<V>; { private V target; // ... } class Pair<T1, T2> { private T1 first; private T2 second; // ... }
Greeter<Integer> r1 = new Greeter<Integer>(); Greeter<?> r2 = new Greeter<String>(); Greeter<? extends Animal> r3 = new Greeter<Dog>(); Greeter<? super Dog> r4 = new Greeter<Animal>();
List<Dog>
a superclass of List<Animal>
. If it did define subclasses that way, something called covariance, we would get horribleness:List<Dog> dogs = new ArrayList<Dog>(); List<Animal> animals = dogs; // static type list-of-animal, actual class list-of-dog animals.add(new Cat("Fluffy")); // compiler *would* allow this in a covariant world!
public class CollectionUtils { // ... public static <T> void fromArrayToCollection(T[] a, Collection<T> c) { for (T o : a) { c.add(o); } } }
Interface | Method | Description |
---|---|---|
Function<T,R> | apply | Function of one T argument producing an R result |
Consumer<T> | accept | Function of one T argument producing no result |
Supplier<T> | get | Function of no arguments producing a T result |
Predicate<T> | test | Function of one T argument producing a boolean result |
BiFunction<T,U,R> | apply | Function of two arguments producing an R result |
BiConsumer<T,U> | apply | Function of two arguments producing no result |
BiPredicate<T> | test | Function of two arguments producing a boolean result |
UnaryOperator<T> | apply | same as Function<T,T> |
BinaryOperator<T> | apply | same as BiFunction<T,T,T> |
Runnable | run | function with no arguments and no results |
Comparator<T> | compare | function of two arguments returning an int. |
What should you think about when building your Java application with classes? Here are ten best practices:
IllegalArgumentException
. Everytime you write a constructor or method that accepts parameters, ask yourself whether the argument being passed in is might be meaningless (or malicious!) and immediately just throw an IllegalArgumentException
.
public class Die { private final int sides; private int value; public Die(int sides, int value) { if (sides < 4) { throw new IllegalArgumentException("At least four sides required"); } if (value < 1 || value > sides) { throw new IllegalArgumentException("Die value not legal for die shape"); } this.sides = sides; this.value = value; } // ... }
IllegalStateException
is also common.
public void add(Object item) { if (isFull()) { throw new IllegalStateException("Cannot add to full collection"); } // ... }
IllegalArgumentException
and when you should use IllegalStateException
.
Objects
. These do a check and throw if the check fails, saving you from having to write an ugly if
statement:
Objects.requireNonNull(e); // throws NullPointerException if e is null Objects.requireNonNull(e, message); // throws NullPointerException with the given message if e is null x = Objects.requireNonNullElse(e1, e2); // returns e1 if e1 is non-null, otherwise returns e2 (as a backup, so to speak) checkIndex(e, length); // throws IndexOutOfBoundsException if e < 0 or e >= length0>
public
, static
, final
, and written in all caps.
public static final String SIX_SIDED_DIE_EMOJI = "🎲";
List<Integer> items = new ArrayList<>();
readObject
(if you are overriding that from streams).Try to figure out what this program does, without running it. After you think you have your answer, run it to see if you are right.
public class T { private final String name; private void printName() { System.out.println(name); } public T(String name) { this.name = name; } public static void main(String[] args) { new T("main").doIt(); } private void doIt() { new T("doIt") { void method() { printName(); } }.method(); } }
We’ve covered: