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
topublic
-
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 ifobj
is null -
Objects.toString(obj)
: printnull
or callobj.toString()
-
Objects.equals(a, b)
: check ifa == b
, then if it's false, check thata != null
and calla.equals(b)
, else returnfalse
- ...
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")
: missingsuper.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 column0
as anint
-
rs.getString(0)
: ... as aString
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
- bluej
- java-best-practices
- baeldung
- Java
- yguard (Obfuscator, Shrink), proguard (Shrink), stringer (obfuscator)
- 30 Seconds of Java
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);