Java Core Programming: From Syntax Fundamentals to Reflection and Streams
Operators and Control Flow
Java provides arithmetic, relational, bitwise, logical, assignment, and conditional (?:) operators.
Program execution order is managed through sequential, branching (if, switch), and looping (for, while, do...while) structures.
Arrays
Creating and Initializing Arrays
Declaration, creation, and initialization are the three key steps for an array. Dynamic initialization specifies the length, while static initialization specifies element values directly.
Memory Allocation
- Stack Memory: Stores local variables.
- Heap Memory: Stores
new-created instances and objects. Each has an address value; garbage collection reclaims it when no longer referenced.
Aliasing Arrays
Assigning one array variable to another makes them reference the same heap object. Modifying elements through one alias affects the other.
int[] scores = new int[5];
scores[0] = 10;
int[] aliasedScores = scores;
aliasedScores[0] = 20; // scores[0] is now also 20
Object-Oriented Fundamentals
Classes and Objects
A class is a blueprint; an object is a concrete instance. A constructor initializes new objects and must have the same name as the class, with no return type. Always invoke constructors with the new operator.
public class Employee {
private String name;
private double salary;
public Employee(String name, double salary) {
this.name = name;
this.salary = salary;
}
}
Object variables store references to objects, not the objects themselves.
Fields: Static vs. Instance
Static fields (class variables) are declared with static, belong to the class, and are shared across all instances. Access them using the class name.
public class Configuration {
public static String appName;
private static int instanceCount;
public static int getInstanceCount() {
return instanceCount;
}
}
Instance fields (member variables) lack static and are unique to each object. They should generally be private, with access provided via getters/setters.
Methods
Methods define behavior.
Static methods can only directly access static members of the class. They cannot access instance fields or methods without an object reference.
public class MathUtils {
public static double average(double x, double y) {
return (x + y) / 2;
}
}
Member methods operate on specific objects. Java passes object references by value, meaning a method can modify the referenced object's state but cannot make the original reference point to a new object. this distinguishes instance fields from local parameters.
Parameter Passing
- Primitive types are passed by value; modifications inside a method do not affect the original variable.
- Object references are also passed by value. The method receives a copy of the reference. Modifying the object's internal state is visible to the caller, but reassigning the copy does not affect the caller's reference.
public class ValueDemo {
public static void modify(int primitive, StringBuilder sb) {
primitive = 100; // No effect outside
sb.append(" updated"); // Object state changed
sb = new StringBuilder("New"); // Reference reassigned, no effect outside
}
}
Method Overloading
Multiple methods in the same class can share a name, provided their parameter lists differ in type, count, or order. Return types alone cannot distinguish overloaded methods.
Inheritance and Polymorphism
Inheritance (extends)
Java supports single inheritance. A subclass inherits all non-private members of its superclass but can only directly access visible ones. To initialize inherited private fields, a subclass constructor must invoke a superclass constructor using super().
public class Manager extends Employee {
private double bonus;
public Manager(String name, double salary, double bonus) {
super(name, salary);
this.bonus = bonus;
}
}
Method Overriding
A subclass may provide a specific implementation for an inherited method, keeping the signature and return type compatible. final, static, and private methods cannot be overridden.
Keywords this and super
thisresolves naming conflicts between instance fields and local variables;this(args)invokes another constructor in the same class.superinvokes overridden superclass methods or constructors, or accesses shadowed instance fields.
Polymorphism
A superclass reference can point to any of its subclass objects. The compile-time type determines which methods are accessible, while the runtime type determines which overriding implementation is executed. Fields do not exhibit polymorphic behavior; they are resolved at compile time.
class Shape {
public String type = "Shape";
public void draw() { System.out.println("Generic shape"); }
}
class Circle extends Shape {
public String type = "Circle";
@Override
public void draw() { System.out.println("Drawing circle"); }
}
// Usage:
Shape shape = new Circle();
shape.draw(); // Output: Drawing circle
System.out.println(shape.type); // Output: Shape
Casting allows treating an object as its actual type. Downcasting ((SubClass) superclassRef) may throw a ClassCastException if the object's runtime type is incompatible. Always verify using instanceof.
if (shape instanceof Circle) {
Circle c = (Circle) shape;
c.specificCircleMethod();
}
Final Modifier
finalclass: Cannot be subclassed.finalmethod: Cannot be overridden.finalfield: Value must be assigned once. For object references, the binding to the object is fixed, but the object's internal mutable state can still change.
Abstract Classes and Interfaces
An abstract class can contain both abstract and concrete methods, while interfaces (keyword interface) primarily define abstract behavior. A class implements an interface and must provide concrete implementations for all abstract methods.
Since Java 8, interfaces can also include default methods (with a body) and static methods. Default methods resolve conflicts via class-priority rules or explicit overriding.
interface Printable {
void print();
default void log() { System.out.println("Logging..."); }
static String format(String s) { return "---" + s + "---" ; }
}
Enumerations (enum)
An enum defines a fixed set of named constant objects. Each constant can have its own fields, constructors, and method implementations.
enum Size {
SMALL("S"), MEDIUM("M"), LARGE("L");
private String abbreviation;
private Size(String abbreviation) { this.abbreviation = abbreviation; }
public String getAbbreviation() { return abbreviation; }
}
Exception Handling
Exceptions are objects thrown when errors occur. Throwable is the root class.
- Checked exceptions must be caught or declared in the method signature (
throws). - Unchecked exceptions (RuntimeException and its subclasses) do not require explicit handling.
Try-Catch-Finally
The try block encloses guarded code. Matching catch blocks handle thrown exceptions. The finally block always executes, regardless of exceptions or return statements, making it ideal for resource cleanup.
try {
FileReader reader = new FileReader("data.txt");
// operations
} catch (FileNotFoundException e) {
System.err.println("File missing.");
} finally {
// close resources
}
Manually Throwing Exceptions
Use the throw keyword to explicitly create and throw an exception object.
if (amount < 0) {
throw new IllegalArgumentException("Amount cannot be negative");
}
Generics
Generics parameterize types, enforcing compile-time safety and eliminating casts. A generic type variable is a placeholder replaced with a concrete type upon usage.
Defining Generic Classes and Methods
public class Container<T> {
private T content;
public T getContent() { return content; }
public void setContent(T content) { this.content = content; }
}
public <E> void printArray(E[] elements) { /* ... */ }
Type Erasure
At runtime, generic type information is erased. Type parameters are replaced with their bounds (Object if unbounded). The compiler inserts bridge methods and casts to maintain polymorphism and type safety.
Constraints
- Cannot create instances of type parameters (
new T()). - Cannot create arrays of parameterized types (
new ArrayList<String>[10]). - Runtime type checks apply only to raw types.
Multithreading
Creating Threads
Two primary approaches exist:
- Extend the
Threadclass and override itsrun()method. - Implement the
Runnableinterface and pass an instance to aThreadconstructor.
The second approach is preferred due to Java's single-inheritance model. Start a thread with the start() method, not by calling run() directly.
class Task implements Runnable {
@Override
public void run() {
System.out.println("Task running in " + Thread.currentThread().getName());
}
}
new Thread(new Task()).start();
Thread Synchronization
Multiple threads accessing shared, mutable data cause race conditions.
- Synchronized blocks and methods use an intrinsic lock (the
thisobject for instance methods, the class object for static methods). Onnly one thread can hold a specific lock at a time. - The
LockAPI (e.g.,ReentrantLock) offers explicit, more flexible lock management.
private int counter;
public synchronized void increment() {
counter++;
}
// Or using ReentrantLock
private final ReentrantLock lock = new ReentrantLock();
public void increment() {
lock.lock();
try { counter++; } finally { lock.unlock(); }
}
Thread Communication
wait(), notify(), and notifyAll() (defined in Object) enable threads to communicate about lock availability. These must be called from within a synchronized context on the same lock object. wait() temporarily releases the lock and suspends the thread until notified.
Key Difference: sleep() vs wait()
The core distinction is that sleep() retains the lock while pausing, whereas wait() releases it.
Core APIs
String, StringBuffer, StringBuilder
Stringis immutable, meaning operations like concatenation+with variables create new objects.StringBuilder(non-synchronized) andStringBuffer(synchronized) are mutable sequences, ideal for repeated modification.
Comparators
- The
Comparableinterface (methodcompareTo) defines the natural sorting order within a class. - The
Comparatorinterface (methodcompare) allows on-the-fly definition of custom sorting logic.
Collections Framework
Key Interfaces
Collection: Root forListandSet.List(e.g.,ArrayList,LinkedList): Ordered, allows duplicates.ArrayListexcels at random access;LinkedListexcels at ensertions/deletions.Set(e.g.,HashSet,TreeSet): Disallows duplicates.HashSetrelies onhashCode()andequals()for uniqueness;TreeSetrelies onComparableorComparatorand maintains sorted order.Map(e.g.,HashMap,TreeMap): Stores key-value pairs.HashMappermits one null key;TreeMapsorts by keys.
Iteration Patterns
- Use an explicit
Iteratoror an enhanced for-loop. - The
for-eachloop translates to an iterator under the hood. During iteration, you may modify an object's state, but you cannot replace the current element in the collection.
for (String item : list) {
System.out.println(item);
}
I/O Streams
- Byte Streams (
InputStream/OutputStream) handle raw binary data. - Character Streams (
Reader/Writer) handle text data. - Buffered streams (
BufferedInputStream,BufferedOutputStream) improve performance by reducing system calls.
The File class represents file and directory pathnames in an abstract, system-independent manner. It does not read or write data but provides metadtaa.
Reflection
Reflection allows a program to inspect and manipulate classes, fields, methods, and constructors at runtime.
- Obtain a
Classinstance viaClass.forName(),ClassName.class, orobject.getClass(). - Use
getConstructor()to create instances. - Use
getField()orgetDeclaredField()to access and modify field values. - Use
getMethod()andinvoke()to call methods.
To access private members, call setAccessible(true) on the target reflectively obtained object.
Class<?> clazz = Class.forName("com.example.User");
Constructor<?> ctor = clazz.getConstructor(String.class);
Object obj = ctor.newInstance("Jane");
Field nameField = clazz.getDeclaredField("name");
nameField.setAccessible(true);
System.out.println(nameField.get(obj));
Method setName = clazz.getMethod("setName", String.class);
setName.invoke(obj, "Janet");
Lambda Expressions and Stream API
Lambda and Functional Interfaces
A functional interface contains exactly one abstract method. A lambda expression provides a concise implementation of this interface.
Runnable task = () -> System.out.println("Run via lambda");
Four core functional interfaces in java.util.function are:
Consumer<T>:void accept(T t)Supplier<T>:T get()Function<T,R>:R apply(T t)Predicate<T>:boolean test(T t)
Method references (ClassName::methodName) are syntactic sugar for lambdas where a single existing method's signature matches the target interface.
Stream API
Streams perform functional-style operations on collections. They are not data structures but convey elements from a source through a pipeline of computational steps.
Creation: collection.stream(), Arrays.stream(array), Stream.of(elements).
Intermediate Operations (lazy):
filter(Predicate)map(Function)sorted(Comparator)distinct(),limit(n),skip(n)
Terminal Operations (trigger processing):
forEach(Consumer)collect(Collectors.toList())reduce(identity, accumulator)count(),min(),max(),findFirst()