Skip to main content

Java

  • Java works on different platforms (Windows, Mac, Linux, Raspberry Pi, etc.)
  • It is open-source and free
  • It is secure, fast and powerful
  • It has huge community support
  • Java is an object oriented language which gives a clear structure to programs and allows code to be reused, lowering development costs
  • Set up Java in computer
Compile & running in cmd | Terminal
  • javac build .java to .class
  • java run .class file
> javac Main.java
> java Main

Syntax

public class Main {
public static void main(String[] args) {
String name;
// Manual input in console
Scanner sc = new Scanner(System.in);

/* This is comment
in multiple lines
*/
// System.out.print(1 + 1); // This print not enter a new line
System.out.println("Enter name:");
name = sc.nextLine();
System.out.println("Hello " + name + "!");

sc.close(); // garbage collector
}
}

Variables

  • [static] [final] type variableName = value;
  • Variable's name are case-sensitive
  • List Java keywords

Naming

package com.company.appname.feature.layer;

enum Direction {NORTH, EAST, SOUTH, WEST}

public interface IControl {};
public class UserControl implements IControl {
private final String SECRET_KEY = "Nothing!";
private String username;
private Properties properties;

public void setUsername(String username) {
this.username = username;
}
public String getUsername() {
return this.username;
}
};

// Generic:
// <K,V> -> key, value
// <T> -> type
// <E> -> collection elements
// <S> -> service loaders
public interface UserAction<T extends Gender> extends Action<T> {}

// Annotation
public @interface FunctionalInterface {}
public @Test Documented {}

Data Types

Primitive

TypeDescribeRange
byte8-bit integer-128 to 127
short16-bit integer-32,768 to 32,767.
int32-bit integer-2^31 to 2^31-1
long64-bit integer-2^63 to 2^63-1
float32-bit floating-point
double64-bit floating-point
char16-bit Unicode character
booleantrue / false
boolean isValid = false;
int age = 20;
long onlineUsers = 3_000_000L; // or 3000000
float weight = 2.5f;
double balanceAmount = -2000.277d;

Reference

// The root class of the Java hierarchy. All classes inherit this
Object obj = new Object();

String str = "Hello world!";
int[] numbers = {1, 2, 3, 4, 5};
float[] amounts = new float[5]; // arr with length = 5

// (Integer|Long).MAX_VALUE
Integer iNumber = Integer.MIN_VALUE;

// (Float|Double)
// - NEGATIVE|POSITIVE_INFINITY
// - MIN_VALUE: The smallest positive value greater than zero that can be represented in a float|double variable.
// - MAX_VALUE: The largest positive value that can be represented in a float|double variable.
// - NaN: not a number of type
Float fNumber = Float.NEGATIVE_INFINITY;

Blocks

public class Demo {
private Integer number;

// constructor
public Demo() {}

// Non-static block statement
// Executed every Demo is created
{
System.out.println("Non-static block executed")
}

// Executed once when the class is loaded by JVM
static {
System.out.println("Static block executed")
}
}

Control Flow

If - else

if (condition) {
// code block
} else if (anotherCondition) {
// another code block
} else {
// default code block
}

// Ternary
value = condition ? trueExpression : falseExpression;

Switch - case - default

enum Day { MON, TUE, WED, THUR, FRI, SAT, SUN }
public static Boolean isWeekDay(Day day)
{
boolean result = false;
switch(day) {
case MON, TUE, WED, THUR, FRI:
result = true;
break;
case SAT, SUN:
result = false;
break;
default:
throw new IllegalArgumentException("Invalid day: " + day.name())
}
return result;
}

// switch + arrow break (JDK 13+)
int day = 3;
String dayType = switch (day) {
case 1, 2, 3, 4, 5 -> "Weekday";
case 6, 7 -> "Weekend";
default -> "Invalid day";
};

// instanceof parsing (JDK 17+)
Object o;
switch (o)
{
case Integer i -> String.format("int %d", i);
case Double d -> String.format("double %f", d);
case String s -> String.format("String %s", s);
default -> o.toString();
}

Loop

For loop

int[] numbers = {1, 2, 3, 4, 5};
for (int i = 0; i < numbers.length; i++) {
System.out.println(number);
}

// Enhanced for loop
for (int number : numbers) {
System.out.println(number);
}

// Collection.forEach
List<Integer> listNumber = List.of(1, 2, 3, 4, 5);
listNumber.forEach(num -> {}); // use with lambda

While, Do - while Loop

int i = 1, sum = 0;

// check before run
while(i <= 5) {
sum += i;
i++;
}

// Run 1st before check
do {
sum += i;
i++;
} while(i <= 5)

Break | Continue

// Labeled Statements
// "continue" works same
outer_loop:
for (int i = 0; i <= 5; i++) {
inner_loop:
for (int j = 0; j <= 5; j++) {
// break; // breaks inner loop only
break outer_loop;
}
}

Exception Handling

try {
// do something stuff
}
catch (Exception err) {
// It's fire, WATERRRRRRRRRR!
}
// OPTIONAL, this block always run
finally {
// You love me or hate me, I dont kare.
}

Try-with-resources

try (BufferedReader br = new BufferedReader(new FileReader("C:/temp/test.txt"))) {
String sCurrentLine;
while ((sCurrentLine = br.readLine()) != null) {
System.out.println(sCurrentLine);
}
}
catch (IOException e) {
// handle exception
}

throw

static void allowedAlcohol(int age) {
if (age >= 18) {
System.out.print("OK!");
} else {
// catch in class call this function
throw new WhyAreYouDrinkItException("This guy is cheater!");
}
}

OOP

Classes

public class Car {
String color;
int year;

// Default constructor with no arguments
public Car() {
this.color = "Blue";
this.year = 2024;
}

// Custom constructor
public Car(String color, int year) {
this.color = color;
this.year = year;
}

public void display() {
System.out.println("Color: " + color + ", Year: " + year);
}
}
// Using
Car defaultCar = new Car(); // "Blue", 2024
Car myCar = new Car("Red", 2020);
myCar.display();

Interfaces

public interface IAnimal {
static String sound = "E";
void eat();
void sleep();
// Can use without override method
default void wakeUp () {
System.out.println("Open eyes!");
}
}

Encapsulation

  • Keeping the data (attributes) private
  • Providing public getter and setter methods to access or modify that data.
// Encapsulation example with private attributes and public methods
class User {
// Private attributes can only be accessed within this class
private String name;
private Date dob;

// Public getter method to access the private 'name' attribute
public String getName() {
return this.name;
}

// Public setter method to modify the private 'name' attribute
public void setName(String name) {
this.name = name;
}
}

Polymorphism

  • Polymorphism allows a method to behave differently based on the object or input.

Method Overloading (Compile-Time Polymorphism)

class CustomMath {
// Overloaded method: same name 'sum' but different parameter types and counts

// Adds two integers
public int sum(int a, int b) {
return a + b;
}

// Adds three integers (method overloading with different number of parameters)
public long sum(int a, int b, int c) {
return a + b + c;
}

// Adds two float numbers (method overloading with different parameter types)
public float sum(float a, float b) {
return a + b;
}
}

Method Overriding (Runtime Polymorphism)

// Base class Shape with a 'draw' method
class Shape {
// Default behavior for drawing a shape
void draw() {
System.out.println("Drawing lines...");
}
}

// Derived class Circle that overrides the 'draw' method of Shape
class Circle extends Shape {
@Override
void draw() {
System.out.println("Drawing a circle...");
}
}

public class Main {
public static void main(String[] args) {
// Polymorphic behavior: Circle class's 'draw' method is called
Shape shape = new Circle();
shape.draw(); // Output: Drawing a circle...
}
}

Abstraction

  • Abstraction hides implementation details from the user, exposing only what’s necessary.
  • It can be achieved through abstract classes and interfaces.
// An interface defines a contract for behavior without implementing it
interface Battery {
void charge(); // Method without implementation
}

interface Horn {
void horn(); // Method without implementation
}

// Abstract class cannot be instantiated and can have abstract methods
abstract class Car {
int wheels;
String name;

// Abstract method must be implemented by derived classes
abstract void run();
}

Inheritance

  • Inheritance allows one class to acquire properties and behaviors of another class.
  • The final keyword can be used to prevent inheritance.
// Base class Car with some common attributes
class Car {
String name;

// Constructor to initialize the car's name
public Car(String name) {
this.name = name;
}
}

// Class that cannot be inherited because of 'final' keyword
final class NotInherited {
// This class can't be extended by another class
}

// Derived class ElectricCar that extends the base class Car
class ElectricCar extends Car {
float enginePower;

// Constructor that calls the parent constructor using 'super'
public ElectricCar(String name, float enginePower) {
super(name); // Passing name to the parent class constructor
this.enginePower = enginePower;
}

// Additional methods and behavior specific to ElectricCar can be added here
}

Records

  • JDK Version: 14+
  • Records are immutable data classes that require only the type and name of fields.
// Yep, it's done! Now you can create Object like normal class.
public record Person(String name, String address) {}

// Using
Person person = new Person('John', 'Americano');
System.out.print(person.name()); // getters
// Static variables & methods
public record Person(String name, String address) {
public static String UNKNOWN_ADDRESS = "Unknown";
public static Person unnamed(String address) {
return new Person("Unnamed", address);
}
}

Person.UNKNOWN_ADDRESS;
Person.unnamed("Some where");

Arrays

  • type varName[] or type[] varName
  • type varName[][] or type[][] varName
// String status[] = { "Active", "Inactive", "Purged" };
String status[] = new String[] { "Active", "Inactive", "Purged" };

// multi-dimentional
int[][] myNumbers = { {1, 2, 3, 4}, {5, 6, 7} };

// Printing
System.out.println(Arrays.toString( status ));
System.out.println(Arrays.deepToString( myNumbers ));

Arrays Concepts

Concatenate Arrays
    int[] array1 = {1, 2, 3};
int[] array2 = {4, 5, 6};

// Create a new array with size equal to the sum of both arrays
int[] concatenatedArray = new int[array1.length + array2.length];

// Copy elements from the first array
System.arraycopy(array1, 0, concatenatedArray, 0, array1.length);

// Copy elements from the second array
System.arraycopy(array2, 0, concatenatedArray, array1.length, array2.length);

System.out.println(Arrays.toString(concatenatedArray)); // Output: [1, 2, 3, 4, 5, 6]
Splitting
    int[] array = {1, 2, 3, 4, 5, 6};
int splitIndex = 3;

// First part of the array
int[] firstHalf = Arrays.copyOfRange(array, 0, splitIndex);

// Second part of the array
int[] secondHalf = Arrays.copyOfRange(array, splitIndex, array.length);

System.out.println("First Half: " + Arrays.toString(firstHalf)); // Output: [1, 2, 3]
System.out.println("Second Half: " + Arrays.toString(secondHalf)); // Output: [4, 5, 6]
Resize
    int[] array = {1, 2, 3};

// Resize array to hold 5 elements
int[] resizedArray = Arrays.copyOf(array, 5);

// The new elements are initialized to the default value (0 for integers)
System.out.println(Arrays.toString(resizedArray)); // Output: [1, 2, 3, 0, 0]
Filter
    int[] array = {1, 2, 3, 4, 5, 6};

// Filter even numbers using Java Streams (Java 8+)
int[] filteredArray = Arrays.stream(array)
.filter(n -> n % 2 == 0)
.toArray();

System.out.println(Arrays.toString(filteredArray)); // Output: [2, 4, 6]
Map
    int[] array = {1, 2, 3, 4, 5};

// Apply a function to multiply each element by 2
int[] mappedArray = Arrays.stream(array)
.map(n -> n * 2)
.toArray();

System.out.println(Arrays.toString(mappedArray)); // Output: [2, 4, 6, 8, 10]
Clone
    int[] array = {1, 2, 3, 4, 5};

// Clone the array
int[] clonedArray = array.clone();

// Modify the cloned array (original array remains unchanged)
clonedArray[0] = 10;

System.out.println("Original Array: " + Arrays.toString(array)); // Output: [1, 2, 3, 4, 5]
System.out.println("Cloned Array: " + Arrays.toString(clonedArray)); // Output: [10, 2, 3, 4, 5]

Collection Framework

java-collections-cheat-sheet

List Interface

  • Strength: Ordered collection (elements maintain their insertion order), allows duplicates, can access elements by index.
  • Common Implementations: ArrayList, LinkedList

ArrayList

  • Strengths:
  • Fast random access (O(1) time complexity for get).
  • Dynamic resizing of the array.
  • When to use: When you need fast access by index and don't need to frequently insert/delete elements in the middle.
import java.util.ArrayList;

public class ArrayListExample {
public static void main(String[] args) {
// Creating an ArrayList of Strings
ArrayList<String> list = new ArrayList<>();
list.add("Apple");
list.add("Banana");
list.add("Orange");

// Access by index
System.out.println(list.get(1)); // Output: Banana

// Iterate over list
for (String fruit : list) {
System.out.println(fruit);
}
}
}

LinkedList

  • Strengths:
    • Fast insertions and deletions (O(1) for add/remove at the beginning or end).
    • Implements Deque, so it can be used as a queue or stack.
  • When to use: When you frequently insert or delete elements in the middle or at the ends of the list.
import java.util.LinkedList;

public class LinkedListExample {
public static void main(String[] args) {
// Creating a LinkedList of Strings
LinkedList<String> list = new LinkedList<>();
list.add("Dog");
list.add("Cat");
list.addFirst("Elephant"); // Add to the beginning
list.addLast("Tiger"); // Add to the end

// Iterate over the list
for (String animal : list) {
System.out.println(animal);
}
}
}

Set Interface

  • Strength: Unordered collection that does not allow duplicates.
  • Common Implementations: HashSet, TreeSet

HashSet

  • Strengths:
    • Fast lookup and insertion (O(1) time complexity).
    • Best when you only care about uniqueness, not the order.
  • When to use: When you need a collection that ensures no duplicate elements and don't care about order.
import java.util.HashSet;

public class HashSetExample {
public static void main(String[] args) {
// Creating a HashSet of integers
HashSet<Integer> set = new HashSet<>();
set.add(1);
set.add(2);
set.add(3);
set.add(2); // Duplicate value will not be added

// Output will be in no particular order
for (int num : set) {
System.out.println(num);
}
}
}

TreeSet

  • Strengths:
    • Automatically sorted set.
    • Implements the NavigableSet interface, so it provides additional methods like floor(), ceiling(), etc.
  • When to use: When you need a sorted set and fast access to the next or previous elements.
import java.util.TreeSet;

public class TreeSetExample {
public static void main(String[] args) {
// Creating a TreeSet of integers
TreeSet<Integer> set = new TreeSet<>();
set.add(10);
set.add(5);
set.add(20);

// TreeSet maintains elements in sorted order
for (int num : set) {
System.out.println(num); // Output: 5, 10, 20
}
}
}

Map Interface

  • Strength: Stores key-value pairs, no duplicate keys allowed.
  • Common Implementations: HashMap, TreeMap

HashMap

  • Strengths:
    • Fast lookup by key (O(1) time complexity for most operations).
    • Allows one null key and multiple null values.
  • When to use: When you need fast access to key-value pairs and don't care about the order of entries.
import java.util.HashMap;

public class HashMapExample {
public static void main(String[] args) {
// Creating a HashMap to store key-value pairs
HashMap<String, Integer> map = new HashMap<>();
map.put("Alice", 30);
map.put("Bob", 25);
map.put("Charlie", 35);

// Access value by key
System.out.println(map.get("Bob")); // Output: 25

// Iterate through the map
for (String key : map.keySet()) {
System.out.println(key + ": " + map.get(key));
}
}
}

TreeMap

  • Strengths:
    • Stores keys in sorted order.
    • Implements the NavigableMap interface, providing methods like firstEntry(), lastEntry(), etc.
  • When to use: When you need a map with sorted keys.
import java.util.TreeMap;

public class TreeMapExample {
public static void main(String[] args) {
// Creating a TreeMap to store key-value pairs
TreeMap<String, Integer> map = new TreeMap<>();
map.put("Alice", 30);
map.put("Bob", 25);
map.put("Charlie", 35);

// TreeMap maintains keys in sorted order
for (String key : map.keySet()) {
System.out.println(key + ": " + map.get(key));
}
}
}

Queue Interface

  • Strength: First-in-first-out (FIFO) order for processing elements.
  • Common Implementations: PriorityQueue, LinkedList (also implements Deque)

PriorityQueue

  • Strengths:
    • Automatically orders elements based on their priority (natural ordering or a custom comparator).
  • When to use: When you need to process elements in a prioritized order.
import java.util.PriorityQueue;

public class PriorityQueueExample {
public static void main(String[] args) {
// Creating a PriorityQueue of integers
PriorityQueue<Integer> queue = new PriorityQueue<>();
queue.add(10);
queue.add(5);
queue.add(20);

// Elements are dequeued in priority order (natural ordering in this case)
while (!queue.isEmpty()) {
System.out.println(queue.poll()); // Output: 5, 10, 20
}
}
}

Deque Interface

  • Strength: Double-ended queue that supports element insertion and removal at both ends.
  • Common Implementations: ArrayDeque

ArrayDeque

  • Strengths:
    • Fast insertions and removals at both ends (O(1)).
    • More efficient than LinkedList for stack/queue implementations.
  • When to use: When you need a queue or stack and need to insert/remove elements at both ends.
import java.util.ArrayDeque;

public class ArrayDequeExample {
public static void main(String[] args) {
// Creating an ArrayDeque to use as a stack
ArrayDeque<Integer> deque = new ArrayDeque<>();
deque.push(1); // Add element at the top
deque.push(2);
deque.push(3);

// Pop elements from the top (stack behavior)
while (!deque.isEmpty()) {
System.out.println(deque.pop()); // Output: 3, 2, 1
}
}
}
Class/InterfaceStrengthBest Use Case
ArrayListFast random accessWhen you need fast access by index and rare insertions/deletions
LinkedListFast insertions/deletionsWhen you frequently insert/remove elements at the beginning/middle
HashSetNo duplicates, fast lookupWhen you need uniqueness and don't care about order
TreeSetSorted, no duplicatesWhen you need uniqueness and sorted order
HashMapFast key-value lookupWhen you need fast access by key and don't care about order
TreeMapSorted key-value pairsWhen you need a sorted map
PriorityQueueElements sorted by priorityWhen you need to process elements in priority order
ArrayDequeFast stack/queue operationsWhen you need to add/remove elements at both ends

Optional

  • JDK Version: 8+
  • Optional is meant to be used as a return type
  • Using Optional in a serializable class will result in a NotSerializableException
// Create empty Object
Optional<String> empty = Optional.empty();
empty.isPresent(); // Check there is value in object, current is false
empty.isEmpty(); // Check there is empty, current is true (ver 11+)

// Create Optional with static method
String name = "baeldung";
Optional<String> opt1 = Optional.of(name); // Maybe get NullPointerException
Optional<String> opt2 = Optional
.ofNullable(name)
.orElse("Default name"); // add-ons
//.orElseGet(() -> "Default name"); // same above, but use function
//.orElseThrow(IllegalArgumentException::new); // custom handling

Lambda

Lambda expressions are a way to represent instances of functional interfaces (interfaces with a single abstract method) in a concise and functional style. They allow us to treat functionality as a method argument or pass behavior around in the form of code.

Basic Syntax

(parameters) -> expression
(parameters) -> { statements; }
  1. No Parameters:

    Runnable task = () -> System.out.println("Task executed");
  2. Single Parameter:

    List<String> names = Arrays.asList("John", "Jane", "Tom");
    names.forEach(name -> System.out.println(name)); // Single parameter lambda
  3. Multiple Parameters:

    BinaryOperator<Integer> sum = (a, b) -> a + b;
    System.out.println(sum.apply(3, 4)); // Output: 7
  4. Multiple Parameters with Code Block:

    BiFunction<String, String, Integer> compareLengths = (str1, str2) -> {
    return str1.length() - str2.length();
    };
    System.out.println(compareLengths.apply("apple", "banana")); // Output: -1

Using Lambdas with @FunctionalInterface

  1. @FunctionalInterface Example

    @FunctionalInterface
    interface MyFunctionalInterface {
    void performTask();
    }
    MyFunctionalInterface task = () -> System.out.println("Task performed");
    task.performTask(); // Output: Task performed
  2. Predicate Interface (used for filter in streams)

    Predicate<String> isLongerThan5 = s -> s.length() > 5;
    List<String> names = Arrays.asList("John", "Jennifer", "Alice");
    List<String> longNames = names.stream()
    .filter(isLongerThan5)
    .collect(Collectors.toList()); // Output: ["Jennifer", "Alice"]

Key Lambda Usages in Java Streams:

  • forEach: Executes an action (usually a Consumer<T>) on each element in the stream.
  • filter: Filters elements based on a condition (accepts a Predicate<T>).
  • map: Transforms elements (accepts a Function<T, R> that takes an input and returns a result).
  • collect: A terminal operation that collects the stream into a data structure (like List, Set, etc.).

Functional Interface

  • @FunctionalInterface is an annotation used to define a functional interface in Java.
  • A functional interface is an interface that contains exactly one abstract method, which can be represented by a lambda expression.
  • It can have multiple default or static methods, but only one abstract method.
  • Functional interfaces are widely used in lambda expressions, method references, and stream API operations.
  • Key Characteristics:
    • If an interface annotated with @FunctionalInterface violates the rule of having only one abstract method, the compiler will throw an error.
    • Even without the annotation, any interface with a single abstract method can still be treated as a functional interface.
Functional InterfaceAbstract Method SignatureUse Case
Runnablevoid run()Running tasks (e.g., in threads)
Supplier<T>T get()Provides a value (e.g., deferred execution)
Consumer<T>void accept(T t)Performs an action on a single input (e.g., forEach)
Function<T, R>R apply(T t)Transforms an input to an output (e.g., map)
Predicate<T>boolean test(T t)Tests a condition (e.g., filter)
BiFunction<T, U, R>R apply(T t, U u)Transforms two inputs into a result
BiConsumer<T, U>void accept(T t, U u)Performs an action on two inputs
UnaryOperator<T>T apply(T t)Unary operation (same input/output type)
BinaryOperator<T>T apply(T t1, T t2)Binary operation on two operands of the same type

Stream

  • Stream API is used to process collections of objects
  • Features
    • A stream is not a data structure instead it takes input from the Collections, Arrays or I/O channels.
    • Streams don’t change the original data structure, they only provide the result as per the pipelined methods.
  • Key Benefits
    • Declarative Style: Streams allow you to focus on what you want to do with the data (e.g., filter, map) rather than how to do it (e.g., loops).
    • Parallel Processing: Easy to parallelize operations to improve performance for large datasets.
    • Lazy Evaluation: Intermediate operations are only evaluated when a terminal operation is invoked, improving efficiency.
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

public class StreamExample {
public static void main(String[] args) {
List<String> names = Arrays.asList("John", "Jane", "Tom", "Doe", "Emily", "Anna", "George");

// Stream processing
List<String> filteredNames = names.stream()
.filter(name -> name.startsWith("J")) // Intermediate operation: filter
.map(String::toUpperCase) // Intermediate operation: map
.sorted() // Intermediate operation: sorted
.collect(Collectors.toList()); // Terminal operation: collect to List

// Output the result
// JANE
// JOHN
filteredNames.forEach(System.out::println); // Terminal operation: forEach
}
}

Resources