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
Type | Describe | Range |
---|---|---|
byte | 8-bit integer | -128 to 127 |
short | 16-bit integer | -32,768 to 32,767. |
int | 32-bit integer | -2^31 to 2^31-1 |
long | 64-bit integer | -2^63 to 2^63-1 |
float | 32-bit floating-point | |
double | 64-bit floating-point | |
char | 16-bit Unicode character | |
boolean | true / 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[]
ortype[] varName
type varName[][]
ortype[][] 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
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.
- Fast insertions and deletions (O(1) for
- 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 likefloor()
,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 multiplenull
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 likefirstEntry()
,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 implementsDeque
)
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/Interface | Strength | Best Use Case |
---|---|---|
ArrayList | Fast random access | When you need fast access by index and rare insertions/deletions |
LinkedList | Fast insertions/deletions | When you frequently insert/remove elements at the beginning/middle |
HashSet | No duplicates, fast lookup | When you need uniqueness and don't care about order |
TreeSet | Sorted, no duplicates | When you need uniqueness and sorted order |
HashMap | Fast key-value lookup | When you need fast access by key and don't care about order |
TreeMap | Sorted key-value pairs | When you need a sorted map |
PriorityQueue | Elements sorted by priority | When you need to process elements in priority order |
ArrayDeque | Fast stack/queue operations | When 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; }
-
No Parameters:
Runnable task = () -> System.out.println("Task executed");
-
Single Parameter:
List<String> names = Arrays.asList("John", "Jane", "Tom");
names.forEach(name -> System.out.println(name)); // Single parameter lambda -
Multiple Parameters:
BinaryOperator<Integer> sum = (a, b) -> a + b;
System.out.println(sum.apply(3, 4)); // Output: 7 -
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
-
@FunctionalInterface Example
@FunctionalInterface
interface MyFunctionalInterface {
void performTask();
}MyFunctionalInterface task = () -> System.out.println("Task performed");
task.performTask(); // Output: Task performed -
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.
- If an interface annotated with
Functional Interface | Abstract Method Signature | Use Case |
---|---|---|
Runnable | void 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
}
}