Featured image of post Java Fundamental

Java Fundamental

Learning Java

Prerequisition

  • Java Development Kit (JDK) installed via sdkman -> https://sdkman.io
  • check the installation via javac --version for compiler and java --version for runtime

Build from scratch

  1. create project directory and navigate to it
  2. create file Main.java with content:
    1
    2
    3
    4
    5
    
    public class Main {
        public static void main(String[] args) {
            System.out.println("Hello World");
        }
    }
    
  3. compile the file via javac Main.java
  4. run the file via java Main

Passing arguments

  1. modify the file Main.java with content:
    1
    2
    3
    4
    5
    6
    
    public class Main {
        public static void main(String[] args) {
            System.out.println("Hello World");
            System.out.println(args[0]);
        }
    }
    
  2. recompile the file via javac Main.java
  3. run the file via java Main test
  4. check the output
  5. try to run the file via java Main without any arguments then you will get error
    1
    2
    3
    
    Hello from java!
    Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: Index 0 out of bounds for length 0
        at Main.main(Main.java:4)
    

Manage class directory

  1. in the project directory, create directory for class package mkdir -p com/example/java
  2. move the file Main.java to the directory com/example/java. You may delete the class file in the root, it’s optional.
  3. compile the file via javac com/example/java/Main.java
  4. run the file via java com.example.java.Main
  5. you should get error
    1
    2
    
    Error: Could not find or load main class com.example.java.Main
    Caused by: java.lang.NoClassDefFoundError: com/example/java/Main (wrong name: Main)
    
  6. The error is because the package name is not matching the directory structure. Modify the file Main.java with content:
    1
    2
    3
    4
    5
    6
    7
    
    package com.example.java;
    
    public class Main {
        public static void main(String[] args) {
            System.out.println("Hello World");
        }
    }
    
  7. recompile and rerun again via java com.example.java.Main sampleArgs

Java Modifiers

Java modifiers are keywords that define the properties and access levels of classes, methods, and variables. There are two types of modifiers:

Access Modifiers

Access modifiers control the visibility and accessibility of classes, methods, and variables.

Modifier Same Class Same Package Subclass Different Package
public
protected
default (no modifier)
private

Examples:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
public class MyClass {
    public String publicVar;      // Accessible everywhere
    protected String protectedVar; // Accessible in same package and subclasses
    String defaultVar;            // Accessible only in same package (default)
    private String privateVar;    // Accessible only within the same class
    
    public void publicMethod() { }      // Accessible everywhere
    protected void protectedMethod() { } // Accessible in same package and subclasses
    void defaultMethod() { }            // Accessible only in same package
    private void privateMethod() { }    // Accessible only within the same class
}

Non-Access Modifiers

Non-access modifiers provide additional information about classes, methods, and variables.

Common Non-Access Modifiers:

  • static - Belongs to the class rather than instances
  • final - Cannot be modified/overridden
  • abstract - Abstract class or method (must be implemented)
  • synchronized - Thread-safe access
  • volatile - Variable may be modified by different threads
  • transient - Not serialized when persisting object

Examples:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
public class Example {
    static int count = 0;           // Shared by all instances
    final int MAX_VALUE = 100;      // Cannot be changed
    volatile boolean flag = false;  // Visible to all threads
    
    public static void staticMethod() { }  // Called on class, not instance
    
    public final void finalMethod() { }    // Cannot be overridden
    
    public synchronized void syncMethod() { } // Thread-safe method
}

Using IDE

Using IntelliJ

  1. Open Intellij
  2. click File -> New -> Project from top menu bar
  3. on the “New Project” window, select “Java”, fill the project name and location, choose Build tool (IntelliJ or Maven or Gradle), select JDK version, then click Create New Project
  4. create new class in src directory by selecting src directory then right click -> New -> Java Class
  5. fill the class name com.example.java.Main
  6. inside the Main.java file, create main method by typing psvm and press Tab. You should get public static void main(String[] args) { }
  7. inside the method, type sout and press Tab. You should get System.out.println();
  8. inside the brackets, type "Hello World"
  9. run the file by clicking play button on the top bar or press Shift + F10

Using VSCode

  1. install necessary plugins
  2. click cmd/ctrl + shift + p to open command palette
  3. type java: create java project and press Enter
  4. Select project type (we choose ‘No Build Tool’ for now)
    • No Build Tool, work with source code directly without any build tools
    • Maven, provided by Maven for Java
    • Gradle, provided by Gradle for Java
    • Spring Boot, provided by Spring Initializr Java Support
    • Quarkus, provided by Quarkus
    • MicroProfile, provided by MicroProfile Starter
    • JavaFX, provided by Maven for Java
    • Micronout, Provided by Launch for Micronout Framework
    • Graal Development Kit for Micronout
  5. Select project location
  6. Input a Java project name, e.g. todo-project
  7. click right click on the src directory then select New Java File -> Class
  8. Input the class name. e.g. Main or you can prefix it with package name, e.g. com.example.java.Main
  9. inside the Main.java file, create main method by typing psvm and press Tab. You should get public static void main(String[] args) { }
  10. inside the method, type sout and press Tab. You should get System.out.println();
  11. inside the brackets, type "Hello World"
  12. run the file by clicking play button on the top bar or press F5

Setting Up Code Formatter

  1. click cmd/ctrl + shift + p to open command palette
  2. type preferences: open workspace settings (JSON) and press Enter
  3. add the following line to the JSON file
    1
    2
    3
    
    {
        "editor.formatOnSave": true,
    }
    
  4. click OK to apply the formatter

Variables

  • Java is a statically typed language
  • All variable must have their types declared
  • Java naming conventions require that variable name always start with lowercase or camel case.
    • example: int myVariable = 10;
    • example: String myString = "Hello World";

Primitive Data Types

These are not objects — they store values directly in memory (for speed and simplicity).

Type Size Example Description
byte 8-bit byte b = 10; small integers (-128 to 127)
short 16-bit short s = 200; small integers (-32,768 to 32,767)
int 32-bit int x = 1000; standard integer
long 64-bit long l = 100000L; large integer
float 32-bit float f = 3.14f; decimal number (low precision)
double 64-bit double d = 3.14159; decimal number (high precision)
char 16-bit char c = 'A'; single Unicode character
boolean 1-bit (logical) boolean b = true; true/false

Helper Classes (Wrapper Classes)

Java provides object-based counterparts for each primitive type — these are classes in java.lang package that wrap primitive values in objects.

Primitive Wrapper Class Example
byte Byte Byte b = 10;
short Short Short s = 200;
int Integer Integer x = 1000;
long Long Long l = 100000L;
float Float Float f = 3.14f;
double Double Double d = 3.14159;
char Character Character c = 'A';
boolean Boolean Boolean b = true;

Stack vs Heap

Primitive data types are stored in the stack memory, while wrapper classes are stored in the heap memory.

Aspect Primitive Wrapper Class
Type basic object
Stored in stack heap
Can be null
Default value 0 / false null
Used in Collections
Example int, double Integer, Double

Stack Memory

What it stores

  • Primitive values (int, boolean, etc.)
  • References to objects (not the object itself)
  • Local variables (inside methods)
  • Method call frames (function call hierarchy)

Characteristic

  • Fast access (because it’s small and directly managed)
  • Last In, First Out (LIFO) structure
  • Automatically cleaned when a method finishes
  • Limited size → can cause StackOverflowError

Example

1
2
3
4
5
6
public class StackExample {
    public static void main(String[] args) {
        int a = 10; // Stored in stack
        Integer b = 20; // Stored in heap
    }
}

When demo() ends →

  • variable x is removed from stack
  • reference y is removed too
  • but object 20 in heap remains until garbage collected

Heap Memory

What it stores:

  • All objects
  • Arrays
  • Instance variables (inside objects)
  • Wrapper classes (Integer, Double, etc.)

🔹 Characteristics:

  • Slower access (larger and dynamically managed)
  • Shared by all threads
  • Cleaned by the Garbage Collector (GC)
  • Can cause OutOfMemoryError if you create too many objects

Summary

Aspect Stack Heap
Speed Very fast Slower
Lifetime Tied to method execution Exists until GC removes it
Scope Local (per thread) Global (shared)
Error StackOverflowError OutOfMemoryError
Null allowed? No (for primitives) Yes (objects can be null)
Who cleans it? JVM automatically after method ends Garbage Collector

Collections

The Java Collections Framework is a set of interfaces, classes, and algorithms for storing and manipulating groups of objects.

The core collection interface encapsulate different types of collection, which are shown in the figure below (https://docs.oracle.com/javase/tutorial/collections/interfaces/index.html)

Collections Core Interfaces

  • Collection – the root of the collecion hierrachy. A collection represents a group of objects known as its elements.
  • Set – a collection that does not contain duplicate elements.
  • List – an ordered collection (also known as a sequence). Lists may contain duplicate elements.
  • Queue – a FIFO collection designed for holding elements prior to processing.
  • Deque – a double-ended queue that allows elements to be added or removed from both ends, used for implementing both FIFO and LIFO collections.
  • Map – an object that maps keys to values. A map cannot contain duplicate keys; each key can map to at most one value.

List

There are serveral ways to create a List:

Using new with Constructor

1
2
3
List<Integer> list = new ArrayList<>();
// or
List<Integer> list = new LinkedList<>();
  • where ArrayList and LinkedList are classes that implement the List interface.
  • the Integer is type parameter that specifies the type of elements that the list can store.

Example

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25

import java.util.ArrayList;
import java.util.List;

public class Main {
    public static void main(String[] args) {

        class Student {
            String name;
            int age;

            public Student(String name, int age) {
                this.name = name;
                this.age = age;
            }
        }
        List<Student> students = new ArrayList<>();
        students.add(new Student(
                "Alice",
                20));

        System.out.println(students.get(0).name);
        System.out.println(students.get(0).age);
    }
}

Using Arrays.asList

```java
List<Integer> list = Arrays.asList(1, 2, 3);
```
  • Available since Java 1.2, so it’s supported in all Java versions.
  • Creates a mutable list (to some extent). The list returned by Arrays.asList is a view of the underlying array, meaning you can modify the elements of the list (e.g., using set()), but you cannot add or remove elements (doing so throws an UnsupportedOperationException).
  • Allows null elements in the list because it wraps an array, and arrays in Java can contain null.
  • Returns a list backed by the original array. It’s a lightweight wrapper (ArrayList from java.util.Arrays, not java.util.ArrayList), so changes to the list reflect in the underlying array and vice versa (if the array is modified directly).
  • Useful when you need a quick, lightweight way to convert an array to a list and might want to modify the elements (but not the structure).
  • Less memory-efficient for immutable use cases because it’s backed by an array that could be modified elsewhere.
  • Example use case: Temporary list for iteration or minor updates to elements.
    1
    2
    3
    
    List<String> fixMembers = Arrays.asList(null, "Bob", "Charlie");
    fixMembers.set(0, "David"); // Works: changes null to "David"
    fixMembers.add("Eve"); // Throws UnsupportedOperationException
    
  • where Arrays.asList is a method that returns a fixed-size list backed by the specified array.
  • the Integer is type parameter that specifies the type of elements that the list can store.

Using List.of

  • Introduced in Java 9, so it’s only available in Java 9 and later.
  • Creates an immutable list. The list returned by List.of (introduced in Java 9) cannot be modified in any way—no adding, removing, or updating elements. Any attempt to modify it throws an UnsupportedOperationException.
  • Does not allow null elements. Passing null to List.of will throw a NullPointerException at creation time.
  • Returns a completely independent, immutable list. It does not rely on the original data and is optimized for immutability. The implementation is typically a compact, internal class (e.g., ImmutableCollections.ListN in Java).
  • There’s no way to modify the list or have it reflect changes elsewhere.
  • Preferred for creating immutable lists, especially when you want to ensure the list cannot be modified (safer for concurrent or functional programming).
  • More memory-efficient for immutable lists because it uses a compact internal representation.
  • Example use case: Defining constant lists or returning immutable collections from methods.

List.of or Arrays.asList ?

  • Use Arrays.asList when you need a quick list from an array and might want to modify elements (but not the list’s size).
  • Use List.of when you want a truly immutable list, especially in modern Java (9+), or when immutability is critical for safety or clarity. If you need a mutable list, you can wrap List.of in a new ArrayList:
1
List<Integer> list = new ArrayList<>(List.of(1, 2, 3));

Using Stream API (Java 8+)

The Stream API provides a functional way to create lists, especially useful for transforming or generating data.

  • From Elements or Collections:

    • Use Case: When creating lists from streams, especially with transformations or filtering.
    • Example:
       1
       2
       3
       4
       5
       6
       7
       8
       9
      10
      11
      12
      
      import java.util.ArrayList;
      import java.util.List;
      import java.util.stream.Collectors;
      import java.util.stream.Stream;
      
      public class Main {
          public static void main(String[] args) {
              List<String> list = Stream.of("Alice", "Bob", "Charlie")
                      .collect(Collectors.toCollection(ArrayList::new));
              System.out.println(list); // [Alice, Bob, Charlie]
          }
      }
      
  • Example from a Generated Sequence

    • Use Case: When creating lists from a sequence of values, such as a range or a computed series.
    • Example:
       1
       2
       3
       4
       5
       6
       7
       8
       9
      10
      11
      12
      
      import java.util.List;
      import java.util.stream.Collectors;
      import java.util.stream.Stream;
      
      public class Main {
          public static void main(String[] args) {
              List<Integer> list = Stream.iterate(1, n -> n + 1)
                      .limit(3)
                      .collect(Collectors.toList());
              System.out.println(list); // [1, 2, 3]
          }
      }
      

References

for more references see Oracle Java 8 Documentation or choose other versions here Oracle Java SE

Licensed under CC BY-NC-SA 4.0
comments powered by Disqus