Java

Java is a well-known object-oriented programming language. It's maintained and owned by Oracle 🍵.

Java was designed as a cross-platform language. We write code once, and on any operating system, we would have the same output. This is done by executing the code on a virtual machine called the JVM.

➡️ The current version is Java 21 (2023). The last LTS is Java 21.

🗺️ Java developers heavily rely on reading/writing the Javadoc.

public class HelloWorld {
  public static void main(String[] args) {
    System.out.println("Hello, World");
  }
}

Java files have the extension .java. To compile it, you need a JDK (Java development kit). The output is a .class. Download JDK.

$ javac HelloWorld.java # output: HelloWorld.class

To execute your compiled program, you need a JRE (Java runtime environment). It's included in the JDK.

$ java HelloWorld # name of the class with the main
Hello, World!

Core knowledge

Declare a variable

int i = 5; // primitive type
Object o = new Object(); // object

Types

Java has 8 non-object types called primitive types. Aside from these, every other variable (object) has the type of a class 🤖.

Each primitive type also has a class, as listed below:

boolean xxx = true || false; // Boolean 🤖 | 1 bit 
byte xxx = 127;              // Byte 🤖 | 8 bits
short xxx = 32767;           // Short 🤖 | 16 bits
char xxx = 'c';              // Char 🤖 | 16 bits ⚠️
int xxx = 42;                // Integer 🤖 | 32 bits
float xxx = 42.0f;           // Float 🤖 | 32 bits
double xxx = 21.0d + 21.0;   // Double 🤖 | 64 bits
long xxx = 42L;              // Long 🤖 | 64 bits

Conversions

Conversions are done using casting or parsing:

// cast float to int
int xxx = (int) 13.0f;

// To parse into an "int", you use Integer 🤖
int xxx = Integer.parseInt("13");

// From primitive to object (☠️, implicit now)
Float a = Float.valueOf(5);
float b = a.floatValue();

Print some text in the terminal

System.out.println("Hello, World!");
System.out.println("Hello, " + variable + "!");
System.err.println("Hello, Exception."); // errors

Operators

Here is a list of most operators.

// arithmetic
int sum = 5 + 5;          // 10
int subtraction = 5 - 5;  // 0
int product = 5 * 5;      // 25
int division = 6 / 5;     // 1
x += 1;                   // same as x = x + 1
x++;                      // same as x = x + 1
                          // see also: --, -=, *=, and /= 
// logical
if (5 == 5) {}         // true ⚠️ see also "Object#equals"
if (5 != 5) {}         // false
                       // see also: >, >=, <, <=
if (!false) {}         // logical NOT => true
if (true || false) {}  // logical OR => true
if (true && false) {}  // logical AND => false
if (true ^ false) {}   // logical XOR => true

String x = ""+5;       // concatenation (+cast), see String

Final keyword

This keyword is mainly used to create "constants", while it can also be used to limit inheritance.

final int xxx = 5; // constant variable

➡️ A constant is a variable/attribute that cannot be reassigned again. For objects, we can STILL change their attributes (e.g. setters still work).

final Person person = new Person("xxx");
person.setName("yyy"); // ✅ works, named changed
person = null; // ❌ doesn't work

Control-flow structures

Branching

Usual if/else.

if (true) { }
if (true) { } else {}
if (true) { } else if (false) {} else {}

Ternary operator: condition ? value_if_true : value_if_value.

String value = true ? "true" : "false";

Switch-case (without break, more than one case may be executed)

int variable = 1;
switch(variable){
    case 1: /* if variable==1 */; break;
    case 2: /* if variable==2 */; break;
    default:
        /* else */
        break;
}

Branching - enhanced switch

Using the enhanced switch, you can use blocks syntax, or inline statements syntax to avoid the traditional break syntax.

switch (x){
    case v1 -> { /* ... */ }
    case v2, v3, v4 -> /* ... */;
    default -> /* ... */;
}

// switch-value with yield
String result = switch (s) {
    case "John", "Jane" -> "yes"; // inline
    default -> { // block
        int length = s.length();
        yield length % 2 == 0 ? "yes" : "no";
    }
};

Loops

In every loop, you can use break to exit the loop, and you can use continue to end the current iteration, and process to the next one.

// usual loop - i in [0, 10[
for (int i = 0; i < 10; ++i) {}
// reverse loop - i in ]0, 10]
for (int i = 10; i > 0; i--) {}
// nested loop
for (int i = 0; i < 5; ++i) {
    for (int j = 0; j < 5; ++j) {}
}
while(true) {}; // repeat while true
do {} while(true); // executed at least once

Loops - for each

It is a new loop to iterate Iterables. Iterables can be arrays, collections (ArrayList/...), or basically any class implementing Iterable.

int[] numbers = {5, 6, 7};
for (int e: numbers) {
  // e=5 then e=6 then e=7
}

Branching - Instanceof

Due to Polymorphism, two objects may be stored in a variable of the same type, but have a different class. You can check the class using:

Object value = Integer.valueOf(5);
// Before JDK 14
if (value instanceof Integer) {
    Integer n = (Integer) value;
    System.out.println("this is a number:"+n);
}
// Since JDK 14
if (value instanceof Integer n){
    System.out.println("this is a number:"+n);
}

🔥 instanceof returns true if the class of value is the same as the given one, or a child class of the given one. A stricter approach only allowing the same class would be:

if (a.getClass() == b.getClass()) { /* ... */ }

Classes

Java classes usually have their own file. It's named after the class. A class name is in upper camel case by convention.

public class ClassNameHere {}

Packages

To make things cleaner, we usually use packages to group classes. A package is roughly equal to a folder.

package com.lgs.memorize; // ./com/lgs/memorize/Test.java

public class Test {}

Aside from default classes, every other class must be imported:

import java.io.File;
import java.io.*; // all classes in "java/io"
import static java.io.File.createTempFile; // a static method

Visibility

Each class/attribute/method/... has a visibility modifier determining who can use a method/access an attribute/...

For instance, if something is private, only methods/attributes in the same classes can access it.

modifier same class same package inheritors anyone else
public + + + +
protected + + +
package (default) + +
private +

🔥 You can have multiple package-private classes in one Java file.

Attributes

As a reminder, attributes are used to store data.

// Class
public static int xxx;
public static final float PI = 3.14f;
// Instance
public String name;
private final String country = "France";

Methods

From an instance method, you can access any class/instance member. For instance members, you can use this to explicitly reference the object calling a method (i.g. for a.b(), inside b(), this == a)

// Instance
public void setName(String newName) {
    this.name = newName;
}

public void resetName() {
    setName("John Doe"); // "this." implicit
}
    
// Class
public static float xxx(float f) { return f; }

➡️ Methods to access an attribute are usually starting with get such as getName. They are called getters. Methods to set an attribute usually start with set and are called setters.

✨ You can write recursive methods.

✨ You can overload methods and constructors.

🔥 You can create a variadic method e.g., taking a variable number of arguments as follows: public void myMethod(Integer ... numbers). Here, numbers has the type Integer[], and may be empty. This can only be used for the last argument. Call: myMethod(), myMethod(a, b)...


Instantiation

Classes are instantiated using the new keyword.

Person johnDoe = new Person();
Person janeDoe = new Person("Jane Doe", 25);
Person janeDoeCopy = new Person(janeDoe);

This calls a special method in the class called Constructor. They have the name of the class, and no return type. You can have multiple of them. There is a default parameterless constructor, but it's removed when explicitly declaring a constructor.

public class Person {
    ...

    // parameterless constructor
    public Person() {
        this("John Doe", 0); // call another
    }
    // valued constructor
    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }
    // copy constructor
    public Person(Person p) { this(p.name, p.age); }
}

Dot operator

From an object, you can call methods or access attributes (according to the visibility of the member) using the operator . (dot).

johnDoe.setName("Jane Doe");
johnDoe.resetName();

For class members, it's the same, but with the name of the class:

double pi = Math.PI;

Garbage collector

The garbage collector is a process that looks for unused variables (meaning no more references), and destroys them, freeing up memory.

There is no way to know when the garbage collector will destroy a variable, but setting a variable to null may speed up the process.

johnDoe = null;

Exceptions

An exception is a signal fired when something unexpected occurs. This is usually an error 🔥. If no one handles the signal, then the program crashes. It prints a stacktrace with the trace of methods the signal went through. There are two categories of exceptions.


Verified exceptions

Verified/Checked exceptions are exceptions that must be caught. These are usually raised by methods that may fail for a reason that the developer can't do anything about it (opening a file failed...).

➡️ IOException, Exception...


Runtime exceptions

Runtime exceptions are exceptions that usually occur due to a problem in the code that can be patched.

🔥 The most famous one is the NPE: NullPointerException, when calling a method on a null object.

➡️ RuntimeException, ArrayIndexOutOfBoundsException, IllegalArgumentException, IllegalStateException...


Raise an exception

To raise an exception, use throw:

throw new SomeException(args);

Catch and handle an exception

A try-catch-finally block is used to catch and handle exceptions. You can have multiple catch, the first one compatible with the exception is used (polymorphism applies, a parent will catch its subclasses).

try {
  // code raising an exception
} catch(TypeException e) { // TypeException and subclasses
  // print the stack trace
  e.printStackStace();
  // print error message
  System.err.println(e.getMessage());
  // it's common to wrap exceptions
  throw new RuntimeException(e);
} // both share the same block
catch(TypeException1|TypeException2 type1) {}
// optional, called after try or catch
// even if there is a "return/throw" in either
finally {
    // ...
}

Explicitly declare raising an exception

If a method does not handle an exception, you can explicitly specify it:

public void error() throws IllegalArgumentException {
    // code that may raise an IllegalArgumentException
    // and that won't be handled by this method
}

Inheritance

Java implements inheritance with the keyword extends. All classes inherit from Object (implicitly), and classes can only extend one class.

👉 Classes inherit any public or protected members (instance, ~static).

👉 Final classes (final class XXX {}) can't be inherited from.

public class Person {           // implicit extends Object
    private final String name;

    protected Person(String name) {
        this.name = name;
    }

    // can only be used by inheritor
    protected String getName() { return name; }
}

Subclass

We can create a subclass King inheriting from Person. The latter has a constructor which we need to call: this is done using super(args).

public class King extends Person {
    protected King(String name) {
        super(name);
    }
}
// Polymorphism: can be stored in a "Person" ✨
Person p = new King("John DOE");

✍️ Child classes' constructors must call super(args) before anything else. If the constructor is trivial, it can be omitted (implicit super()).

Override

Child classes can override a method, e.g., change the inner code of a method that was declared within their parent.

The child class uses super to reference the parent class.

Below, we call the parent getName() but prepend "King " to it.

public class King extends Person {
    @Override                 // see advanced override
    public String getName() { // to override the signature
        return "King " + super.getName();
    }
}

p.getName(); // King John DOE

✍️ @Override is optional, and used to explicitly declare an override.

🔥 final methods cannot be overridden.


Advanced override

You can change, to some extent, the parent class signature when overriding a method.

  • Visibility 👓: you can increase the visibility of a method from protected to public

  • Parameters 🪙: you can change any argument name, or replace any argument type with a subclass of the same type

  • Return Type 🔫: you can use a child class of the return type

You can also add annotations or throws.


Abstraction

Abstraction in Java can be achieved using abstract classes or interfaces. Both are used to write more generic methods using polymorphism and liskov principle, but they are not instantiable.

An abstract method is a method that has not been implemented yet.

Abstract classes

Abstract classes are the only classes that can have abstract methods, while they may not have any. Aside from that, they are normal classes:

public abstract class AbstractXXX { // ✍️ abstract class
    // can have attributes
    private final SomeType someAttribute;

    // ✍️ can have constructors, but they can't be called
    protected AbstractXXX(...) {
        // ...
    }

    // can have concrete methods
    public void xxx() { /* ... */ }
    // ✍️ can have abstract methods
    public abstract int yyy();
}

👉 Unless they implement abstract methods, inheritors must be abstract too. Also, you can have static and abstract.


Interfaces

An interface is a level above an abstract class. We can only inherit one class, but we can "inherit" (implement) multiple interfaces.

  • Interfaces can have public methods, static or not
  • Interfaces can have public static final attributes
  • Everything is public by default (keyword can be omitted)
  • Everything is abstract by default (keyword can be omitted)
public interface SomeInterface {
    // can have final attributes
    public static final int XXX = 0;
    // can have public abstract instance method
    public abstract SomeType xxx();
    // can have public static method
    public static boolean zzz(...) { /* ... */ }
}

Use implement to inherit from one or many interfaces. You'll have to implement methods, or use an abstract class:

public class XXX implements SomeInterface, AnotherInterface {
  @Override
  public SomeType xxx() { /* ... */ } 
}

An interface can inherit from another interface using extends:

public interface SomeInterface extends AnotherInterface {
  // ...
}

Since Java 8, interfaces can have methods with a body using default:

public interface XXX {
    default String XXX() { /* ... */ }
}

Since Java 9, interfaces can have private concrete methods:

public interface XXX {
    // instance
    private void myPrivateMethod () {}
    // static
    private static void myPrivateMethod2 () {}
}

Functional interfaces

Functional interfaces are interfaces with only one abstract method.

@FunctionalInterface // optional, enforce "one abstract"
public interface XXX {
    String getName(boolean upper); // only one abstract
    default YYY yyy() { /* ... */ }
}

They are convenient because they can be implemented dynamically using lambda expressions (~= a function stored in a variable).

String r = "xxx";
XXX xxx = (u) -> { // can access out-of-scope variables
    return u ? r.toUpperCase() : r.toLowerCase();
};

👉 If there is only one arg/line, braces and parenthesis are optional.


Special classes

Enumerations

An enumeration is a class that provides static objects. They are already instantiated and can have methods/attributes.

They are commonly used for classes with limited known objects.

public enum RPGClass {
  SORCERER, WARRIOR, ARCHER, TANK
}
// example
RPGClass sorcerer = RPGClass.SORCERER;

🔥 Useful: RPGClass.values(), XXX.name(), XXX.ordinal().

🚸 Enums can't extend classes, but can implement interfaces.

👉 You can add attributes and methods as follows:

public enum RPGClass {
  SORCERER(10)
  ; // don't forget the ";" at the end

  private final int baseHP;
  RPGClass(int baseHP) { // package-private
    this.baseHP = baseHP;
  }

  // some method
  public int getBaseHP() { return baseHP; }
}
// example
int sorcererBaseHP = RPGClass.SORCERER.getBaseHP();

Records

Records were introduced in Java 16 for classes that only handle data.

// automatically generate getters, #equals, #toString...
public record MyRecord(int xxx, int yyy) {}
public record MyRecord(int xxx, int yyy) {
  public MyRecord {}                 // canonical constructor
  public MyRecord() { this(0,0,0); } // additional constructor
}

Inner class

Each instance has a class. It's rarely seen/used. The main advantage is that BBB can implicitly access attributes declared in AAA.

public class AAA {       // outer class
    public class BBB {}  // inner class
}
// Usage
AAA aaa = new AAA();
AAA.BBB bbb = aaa.new BBB();

➡️ You may use AAA.this.attribute for explicit usage.

Nested class

A class inside another class. It's commonly used to wrap classes related to the internal implementation (they are usually private).

public class AAA {              // outer class
    private static class BBB {} // nested class, private
}
// Usage
AAA.BBB aaa = new AAA.BBB();

Anonymous classes

These are classes dynamically created during compilation.

// Runnable is an interface
Runnable r = new Runnable() {
    @Override
    public void run() {}
};

Local class

A local class is declared in a method and only exists within its scope.


Well-known types

Object

Every class is inherited from the class Object implicitly.

class Object {
    // compare two object
    public boolean equals(Object obj);
    
    // equals => same hashcode
    public int hashCode();
    
    // clone an object, not accessible by default
    protected Object clone();
    
    // returns the object as a string
    public String toString();
    
    // called before destroying object
    protected void finalize();
}

👉 a.equals(b) is the same as a == b by default, but subclasses can override equals. Use == with null, NOT equals (null.equals(...) will fail).

➡️ An IDE can generate implementations for these methods.


Objects 💎

As variables can be null, a.somMethod() can raise a NPE. Some helpers:

  • Objects.requireNonNull(obj): raise an exception if obj is null
  • Objects.toString(obj): print null or call obj.toString()
  • Objects.equals(a, b): check if a == b, then if it's false, check that a != null and call a.equals(b), else return false
  • ...

String

String s = "some text";
int xxx = s.length();           // number of characters
String xxx = s.toLowerCase();   // s.toUpperCase()
String xxx = s.trim();          // remove leading/trailing \s
boolean xxx = s.isEmpty();      // s.equals("")
boolean xxx = s.isBlank();      // since JDK 11
boolean xxx = s.equals(xxx);    // same as ==
// Printf-like formatting (%n == newline)
System.out.println("%s: %d".formatted("string", 0));
System.out.printf(("%s: %d") + "%n", "string", 0); // same
System.out.println(String.format("%4.0f", 5.0));   // "    5"

➡️ Since JDK 13, you can use """ """ for multiline strings.

🔥 Concatenation is done using + (plus): "a" + "b" gives us "ab".


Arrays

An array is a fixed-size list of values. Empty cells are filled with the default value for primitive types (false, 0...) or null for objects.

int[] tab = {1,2,3,4};
tab = new int[4];
tab = new int[]{1,2,3,4};

int one = tab[0];
int length = tab.length;

If you try to use an index that does not exist, you will get an exception: IndexOutOfBoundsException.


Annotations

Annotations such as @Override or @Deprecated allow us to enforce some compiler checks on a method (ex: deprecated triggers a warning).

For instance, org.jetbrains:annotations provides: @Nullable, @NotNull, or @Contract which are helpful for contract programming.


SuppressWarnings

You can use @SuppressWarnings to suppress compiler warnings:

  • @SuppressWarnings("deprecation"): deprecation
  • @SuppressWarnings("unchecked"): unchecked casting
  • @SuppressWarnings("SameParameterValue"): a non-public method is always given the same parameters
  • @SuppressWarnings("MethodDoesntCallSuperMethod"): missing super.xxx(...) when overriding a method
  • ...

Create annotations

You can create annotations.

@Retention(...) // RetentionPolicy: CLASS = compiler, RUNTIME = runtime too
@Target(...) // ElementType: TYPE, METHOD, FIELD, PARAMETER,  LOCAL_VARIABLE, TYPE_USE... 
public @interface AnnotationName {
	// optional, args
	String field1() default "";
	int[] field2() default {};
	float field3();
}

Which can be used as follows:

@AnnotationName(field1 = "", field2 = {}, field3 = 1)
@AnnotationName(field2 = {}, field3 = 1)
@AnnotationName(field3 = 1)

Input/output streams

Files, and sockets (networking) are two examples of streams. We read in an InputStream 📖, and we write in an OutputStream ✍️. There is also:

  • System.in: standard input stream (terminal input, stdin)
  • System.out: standard output stream (terminal output, stdout)
  • System.err: error output stream (terminal output, stderr)
// import java.io.*;
InputStream is = System.in;
OutputStream os = System.out;

☠️ With an input/output stream, we can only read/write integers:

int read = is.read();         // read one
os.write(5);                  // write one

⚠️ ️ Both raise a verified IOException that must be caught. Whether you have one or multiple try-catch is up to your needs.

If you open a stream (not stdin/...), you have to close it. A new alternative is the try-ressource which automatically closes a resource.

// try-with-resources
try (InputStream is = ...) {
    // ...
}

🔥 When closing a buffer, any nested stream is automatically closed.

Buffers

We usually wrap streams within buffers to read more characters at once. Each read/write still raises a verified IOException to catch.

// for character streams:
BufferedReader reader = new BufferedReader(new InputStreamReader(is));
BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(os));
String line;
while((line = reader.readLine()) != null) {
    // read line by line
}
writer.write("....");             // write a line
writer.newLine();                 // write a \n or \r\n
// for binary streams:
BufferedInputStream reader = new BufferedInputStream(is);
BufferedOutputStream writer = new BufferedOutputStream(os);

Files

For binary files, you can use:

InputStream is = new FileInputStream("file");
OutputStream os = new FileOutputStream("file");

Otherwise, for text files, you can use:

FileReader fileReader = new FileReader("file");
BufferedReader reader = new BufferedReader(fileReader);

⚠️ ️ Both raise a verified FileNotFoundException that must be caught.

💡 See also: FileWriter (write into text files), and File (file operations).


Java Generics

It's possible to write generic code using Polymorphism. For instance, this function takes an instance of AAA or subclasses of AAA.

public static AAA xxx(AAA aaa){
    return /* ... */;
}

But, it's limited to subclasses. Java Generics allow us to factorize methods with the same code (and with no useful polymorphism usable):

public static Integer firstElement(Integer[] array){
    return array.length == 0 ? null : array[0];
}

public static Float firstElement(Float[] array){
    return array.length == 0 ? null : array[0];
}

We could declare a type <T> and use it as follows:

public static <T> T firstElement(T[] array){
    return array.length == 0 ? null : array[0];
}

// Usage:
Integer[] xxx = {0, 1, 2};
Integer first = firstElement(xxx); // 0

A generic type can be declared in a method, or a class. For the latter, you must provide the type when calling the constructor:

public class XXX<T> {}
// examples
XXX<Integer[]> xxx = new XXX<Integer[]>();
XXX<Integer[]> xxx = new XXX<>(); // omitted (inferred)

👉 You can also add constraints (<XXX extends ...>) or declare multiple types (<K, V>).

👉 You can use the "?" wildcard such as XXX<?> xxx = ... when you don't want to declare a generic type or don't need to enforce a type.

⚠️ <T> or any generic type can only be a class, not a primitive type.


Common Java interfaces

Java has way too many interfaces, even if we only keep the most commonly used ones, and I will only put some here.

Comparator<T>, Comparable<T>: sort

Comparator/Comparable are interfaces implemented by classes whose values can be compared. The only difference is that Comparator is usually implemented in a separate class, while Comparable is not.

public class MyComparator implements Comparator<XXX> {
  @Override
  public int compare(XXX o1, XXX o2) {
    // 0 for o1 == o2 | 1 for o1 > o2 | -1 for o1 < o2
    return /* return 0, 1, or -1 */;
  }
}
public class XXX implements Comparable<XXX> {
  @Override
  public int compareTo(XXX o) {
    return /* o1 == this, o2 == o */;
  }
}

See also: Integer::compareTo, ...

Optional<T>: wrapper for "null"

Optional<T> is wrapping a nullable value, allowing you to factorize code or easily write code to handle them.

Optional<Integer> x = Optional.of(5); // wrap "5"
int y = 7 + x.get();     // y = 7 + 5
x = Optional.empty();    // wrap "null"
y = 7 + x.orElse(3);     // if "x" is null, use "3" | y = 10

See also: isPresent() (is not null?) or isEmpty() (is null?).

Iterator<E>, Iterable<T>: iterate an object

Both classes were made to uniformize a way of iterating an object. Classes implementing Iterable<T> can be iterated using foreach.

Iterable<T> creates an Iterator<E>, which is something with a cursor returning the next value to read when prompted.

Common implementation

The class below is a simplified example. It has an attribute numbers which represents the data it has, and that we will iterate on.

public class YYY implements Iterable<Integer> {
  private final int[] numbers = {5, 6, 7};

  @Override
  public Iterator<Integer> iterator() {
    return new YYYIterator();
  }

  private class YYYIterator implements Iterator<Integer> {
    private int cursorIndex = 0;

    @Override
    public boolean hasNext() {
      return cursorIndex < numbers.length;
    }

    @Override
    public Integer next() {
      if (!hasNext()) throw new NoSuchElementException("Invalid call of next.");
      // return and move cursor
      return numbers[cursorIndex++];
    }
    
    // delete this unless you want to override "remove"
    @Override
    public void remove() {
      Iterator.super.remove();
    }
  }
}
🔥 Manually iterating an iterator 🧯

Some iterators need to be used manually, especially when we need to use remove:

Iterator<Integer> iterator = new YYY().iterator();
while (iterator.hasNext()) { // ✅ check if we can load "next"
  Integer next = iterator.next(); // ⚠️ load "next"
  // next is "5" then "6" then "7"
  iterator.remove(); // 🔥 if supported
}

Stream<E>: a sequence of elements

A Stream<E> is convenient for applying (multiple) operations to a sequence of elements. We call intermediate methods ⛓️ the ones returning a new stream. All methods empty the original stream.

Stream<Integer> stream = someCollection.stream(); // example
// some examples using lambdas
stream = stream.filter((i) -> i > 0);  // keep values > 0
stream = stream.map(i -> i + 1);      // transform each value
stream = stream.sorted(Integer::compareTo); // Comparable<T>
stream.forEach(System.out::println);
// see also: anyMatch(Predicate), allMatch(Predicate)
//           noneMatch(Predicate), count(),
//           flatMap(), collect(Collectors.toList())...

Collections and maps

Collection<E> is an interface implemented by all data structures such as arrays, lists, sets... Map<K, V> is used for dictionaries.

  • E is the type of the elements stored in the collection ✨
E element = null;
  • Common constructors 🏡
//// common constructors 🏡
SomeCollection<E> c1 = new SomeCollection<>();
SomeCollection<E> c2 = new SomeCollection<>(c1);
  • Common methods 🏘️
c1.add(element);              // add
c1.addAll(c2);                // add all from collection

c1.remove(element);           // remove element
c1.clear();                   // remove all

boolean in = c1.contains(element);    // true if in
Iterator<E> iterator = c1.iterator(); // see Iterator<E>
Stream<E> stream = c1.stream();       // see Stream<E>

int size = c1.size();                 // number of elements
boolean b = c1.isEmpty();             // size == 0

E[] tab = new E[size];                // store in "tab"
tab = c1.toArray(tab);

ArrayList<E>: dynamic array

Arrays have a fixed size. An ArrayList is a data structure that behaves like an array, but has a variable size as it automatically resizes itself.

A few methods were added such as:

c1.add(0, element);           // add at index
c1.remove(0);                 // remove element at "0"
E found = c1.get(0);          // get by index

HashSet<E>: set of unique values

HashSet is a collection with no duplicates.

// it's common to create a "set" from a "list"
HashSet<XXX> set = new HashSet<>(list);

LinkedList<E> as a queue

LinkedList<E> is a class commonly used to implement a queue.

Queue<E> queue = new LinkedList<>();
boolean offer = queue.offer(element); // add
E element = queue.element();          // get last
E peek = queue.peek();                // get last
E poll = queue.poll();                // take last
E remove = queue.remove();            // remove last

HashMap<K, V>: a dictionary

A HashMap is a dictionary. A key of type K is associated with a value of type V. It implements the interface Map<K, V>.

HashMap<Integer, String> map = new HashMap<>();
// 0 is a key, "zero" is a value
String oldValue = map.put(0, "zero"); // 0 => "zero"
String value = map.get(0);            // "zero"
String removed = map.remove(0);       // OK
boolean in = map.containsKey(0);      // false
map.clear();

// common template to iterate a map 💎
for(Map.Entry<Integer, String> entry: map.entrySet()){
    Integer key = entry.getKey();
    String value = entry.getValue();
}

Threads

Threads allow us to run tasks in parallel 🔥. We will create a Runnable with the code executed by the thread.

// version 1 - lambda expression
Runnable r = () -> {
    System.out.println("Hello, World!");
};
// version 2 - CodeRunByTheThread.java
public class CodeRunByTheThread implements Runnable {
	@Override
	public void run() {
		System.out.println("Hello, World!");
	}
}
Runnable r = new CodeRunByTheThread();

To create and execute a thread:

Thread thread = new Thread(r);
thread.start();

👉 Use Thread.sleep(duration); to wait for a duration.

When multiple threads try to access a variable, there may be concurrency problems 💥. You can prevent them by only allowing one thread at a time to call a method using synchronized:

[...]
    public synchronized void myMethod() { [...] }
[...]

Otherwise, you can use a mutex and synchronized to only allow the one that could lock the mutex to run some code:

private Object lock = new Object();

public void myMethod() {
    synchronized (lock) { /* ... */ }
}

⚠️ If you're using threads to update a variable, you need to mark it as volatile, to ensure other threads will notice the variable changed.


Await an async function

Async functions are functions executed in another thread. For instance, to query a database/an API. Sometimes, we want to wait for the result to resume the execution.

// import java.util.concurrent.CountDownLatch;
// import java.util.concurrent.atomic.AtomicReference;
public class APIHelper {
    public static int getResult() throws InterruptedException {
        CountDownLatch latch = new CountDownLatch(1);
        AtomicReference<Integer> ref = new AtomicReference<>();
        // Do an async call here, update ref, and latch
        latch.await();
        return ref.get();
    }
}

To simulate an API call, we can create a thread as follows:

Thread thread = new Thread(() -> {
    ref.set(10);       // AtomicReference<Integer>
    latch.countDown(); // decrease the latch by 1
});
thread.start();

When the latch is empty, latch.await() will stop waiting.

If you've got multiple async functions to call, you can simply increase CountDownLatch starting value.


Networking

You can create a server from which local clients can connect to 🏠. As a client, you can connect to any server (local or not) 🌍.

Server

try (ServerSocket serverSocket = new ServerSocket(port)) {
    while (true) {
        Socket clientSocket = serverSocket.accept();
        // handle client, usually in a new thread
    }
} catch (IOException e) {
    throw new RuntimeException(e);
}

Client

// "host" could be "localhost" or an IP
Socket clientSocket = new Socket(host, port);

Sockets

Assuming s is a Socket, you can get streams using:

InputStream i = s.getInputStream();
OutputStream o = s.getOutputStream();

Unlike common streams, you will have to flush them after using them:

i.flush();
o.flush();

Properties and Preferences

Properties and preferences are two common ways of storing user settings, such as the user language, or the theme...

Preferences

Preferences are available in two scopes: user and system. Each preference is associated with a class.

  • Static (replace XXX.class with any class)
final Preferences preferences = Preferences.userNodeForPackage(XXX.class);
  • Instance
final Preferences preferences = Preferences.userNodeForPackage(getClass());

Preferences are dictionaries of key values.

preferences.put("key", "value");                 // save
String stored = preferences.get("key", default); // load
preferences.remove("key");                       // delete

Properties

Properties are .properties files such as gradle.properties. See INI.

try (FileReader reader = new FileReader("xxx.properties")) {
    final Properties p = new Properties();
    p.load(reader);
    // read values
    String value = p.getProperty("key", "default");
} catch (IOException e) {
    throw new RuntimeException(e);
}
try (FileWriter writer = new FileWriter("xxx.properties")) {
    final Properties p = new Properties();
    // add/set properties
    p.put("key", "value");
    p.store(writer, null);
} catch (IOException e) {
    throw new RuntimeException(e);
}

External communication

Web requests

You can make an HTTP request using native code as follows:

try {
    URL url = new URL("https://example.com");
    HttpURLConnection req = (HttpURLConnection) url.openConnection();
    try (InputStream is = req.getInputStream()) {
        // refer to the streams section
    }
    req.disconnect();
} catch (IOException e) {
    throw new RuntimeException(e);
}

Database

You can directly interact with databases such as SQLite or MariaDB from your Java code while it's recommended to use APIs. You'll have to download and load a driver java -cp ".:/path/to/driver" [...].

⚠️ All methods raise a verified SQLException that needs to be handled.

// connect to SQLite database
Connection c = DriverManager.getConnection("jdbc:sqlite:host", "username", "password");
c.close();

// execute some SQL
try (Statement stmt = c.createStatement()) {
    stmt.executeUpdate("sql");
    // iterate results (if applicable) 
    ResultSet rs = stmt.getResultSet();
    while (rs.next()) {
        System.out.println("x");
    }
    rs.close();
}

To properly handle user input, we usually use PreparedStatement.

PreparedStatement stmt = c.prepareStatement("... where x=?");
// replace the nth "?" with a properly escaped value
// indexes start at 1
stmt.setInt(index, value);
stmt.setString(index, value);
// execute
stmt.executeUpdate();                // no result
ResultSet rs = stmt.executeQuery();  // has result

When reading results, some methods aside from next() may be used:

  • rs.previous(): if available, returns the previous result
  • rs.first(): returns the first result
  • rs.findColumn("name"): return the index of a column
  • rs.getInt(0): returns the value in column 0 as an int
  • rs.getString(0): ... as a String

Snippets

Get the ID generated for an insert statement
try(ResultSet generatedKeys = stmt.getGeneratedKeys()) {
    if(!generatedKeys.next()) return -1;
    return (int) generatedKeys.getLong(1);
}

Modules

Modules were introduced in Java 9. They encapsulate projects and require developers to explicitly import SDK packages into their projects. Create a module-info.java, with a unique module name:

module com.example.project {
}

To import a module, use requires some_module;:

  requires java.base;      // Base packages
  requires java.desktop;   // AWT + SWING
  requires jdk.crypto.cryptoki; // HTTP SSL

You can export a package to allow other projects to requires it.

  // one per package, not recursive
  exports com.example.project.package;

You can give full access to a package to other projects using open. Otherwise, they can't do things like introspecting private members.

    opens com.example.project.package;

Compile with: javac -d mods /path/to/module-info.java /path/to/com/example/project/Main.java


Random notes

UTF-8

Non-ASCII characters are incorrectly displayed on most machines. Either use: java -Dfile.encoding=UTF-8 [...], or unicode characters.

System.out.println("\u00E9"); // print é

JAR files

JAR files are used to bundle JAVA code. Some are executable, while others are libraries that can be used in other projects. It can contain files (images...), libraries, and other stuff needed by your program. 🗃️

$ java -jar some_jar.jar # execute

⚠️ Once bundled, files inside the JAR cannot be modified.

🔥 You can create a JAR manually, or you can use tools such as Gradle.

From the code, there are 3 ways to read a file that is inside a JAR.

// Method 1
InputStream s = this.getClass().getResourceAsStream("/path");
// Method 2
InputStream s = AClass.class.getResourceAsStream("/path");
// Method 3
URL url = this.getClass().getResource("/path");
File f = new File(url.toURI());

Assertions

Assertions can be used for testing, for since they are not enabled by default, they are rarely used. Run java with -ea or -enableassertions to see assertions. See also: -da:package.

assert(condition);              // raises "AssertionError"
assert(condition) : "message";  // custom message

JPackage

JPackage (JDK 14+) was introduced to package Java applications (.deb, .exe, .zip...). To build a .exe on Windows:

# "out" has a jar "xxx.jar"
# "out" has .class files
# "out/dist" will have the .exe
$ jpackage --name eden --type exe --input out --dest "out\dist" --main-jar "xxx.jar" --icon "path\to\icon.ico" --vendor "XXX" --app-version "X.Y.Z" --description "xxx"
# Useful options:
#   --win-shortcut  | create a shortcut on the desktop
#   --win-menu      | add in Windows menu
#   --runtime-image | path to bundled jre
#   --java-options  | options, such as "-Dfile.encoding=UTF-8"

See also: install4j and launch4j.


JLink

JLink can be used to create a minimal JRE that can be bundled with your application (e.g., there is no need to install Java to run it). With Gradle:

plugins {
    id 'org.beryx.jlink' version '2.24.0'
}
jlink {
    addOptions('--strip-debug', '--compress', '2', '--no-header-files', '--no-man-pages')
    launcher {
        name = 'eden'
    }
    imageDir.set(file("$buildDir/../out/myjre"))
}

The JRE can be found in out/myjre/. See also EasyJRE.

Static constructors

There are constructors that can be used to instantiate static attributes (including those final).

⚠️ It is called when a class is loaded in memory, which is not necessarily when the program is executed.

public class Math {
    private static final float PI;

    static {
        PI = 3.14f;
    }
}

Libraries

JSON

You can use JSON-java to parse JSON.

JSONObject o = new JSONObject("some JSON");
String string = o.getString("...");

👻 To-do 👻

Stuff that I found, but never read/used yet.

  • jar module path first
  • java shell (JShell)
  • const
  • object vs primitive
    • objects can be null
    • == between primitive
    • == between enum values
Address 📬

In Java, the notion of address doesn't exist. You are calling a method using a parameter

  • if it's a primitive type: then the value is passed
  • if it's an object: a reference is passed

A reference is simply something that's referencing your object meaning that you can change the attributes/call methods on it, and the real object will be modified, but you can't "destroy"your object because that's simply a reference.

  • sealed and hidden classes
  • method references (A::b)
  • Complete JAR notes (common functions/asset handling...)
  • native keyword
  • var
  • JNI, jnicookbook
  • LibGDX and lwjgl (litiengine)
  • xtend
StringBuilder str = new StringBuilder();

EnumMap<EnumClass, ValueClass> map;
map = new EnumMap<>(EnumClass.class);
// instropection
.class/getClass()
Class.isInstance()
Class.getSimpleName()
// OS
System.lineSeparator();
System.getenv("os")

List.of(...)
Arrays.XXX
Collections.emptyList()
Collections.singletonList(xxx);