Exploring Advanced Java: Lambdas, Collections, Streams, I/O, Multithreading, Networking, and Reflection

Lambda Expressions

Lambda expressions enable concise representation of single-method interfaces. They eliminate boilerplate anonymous inner classes.

// Sorting an integer array
Integer[] values = {4, 7, 1, 9, 3};

// Anonymous inner class
Arrays.sort(values, new Comparator<Integer>() {
    @Override
    public int compare(Integer a, Integer b) {
        return a - b;
    }
});

// Lambda equivalent (block body)
Arrays.sort(values, (Integer a, Integer b) -> {
    return a - b;
});

// More concise lambda with expression body
Arrays.sort(values, (a, b) -> a - b);

Collections Framework

The Collections Framework includes single‑column (Collection) and double‑column (Map) groups.

  • List – ordered, indexable, allows duplicates
  • Set – unordered, no duplicates, no indexing

Common Collection methods:

Method Description
add(E e) Insert element
clear() Remove all elements
remove(Object o) Remove specific element
contains(Object o) Check existence
isEmpty() Test emptiness
size() Number of elements

Note: contains relies on equals. Always override equals (and hashCode) in your model classes.


List Interface

Additional index‑based operations:

Method Description
add(int index, E e) Insert at position
remove(int index) Remove and return element
set(int index, E e) Replace element
get(int index) Retrieve element

Iteration techniques

  • Iterator – safe for removal during traversal.
  • ListIterator – supports addition while iterating.
List<Integer> items = new ArrayList<>();
items.add(10);
items.add(20);
items.add(30);

ListIterator<Integer> iter = items.listIterator();
while (iter.hasNext()) {
    int val = iter.next();
    if (val == 20) {
        iter.add(25);
    }
}
System.out.println(items); // [10, 20, 25, 30]
  • Enhanced for – read‑only traversal.
  • Lambda / forEach – read‑only.
  • Indexed for – when you need the index.

Set Implementations

  • HashSet – no order guarantee, no duplicates.
  • LinkedHashSet – insertion order, no duplicates.
  • TreeSet – sorted, no duplicates.

Basic usage:

Set<String> names = new HashSet<>();
names.add("Alice");
names.add("Bob");
names.add("Charlie");
names.add("Alice"); // duplicate, false

names.forEach(System.out::println); // order varies

HashSet internals – uses a hash table; objects placed in buckets based on hashCode(). Override hashCode() and equals() for correct behaviour.

TreeSet sorting – implements Comparable or supply a Comparator.

TreeSet<Integer> sorted = new TreeSet<>();
sorted.add(42);
sorted.add(9);
sorted.add(17);
System.out.println(sorted); // [9, 17, 42]

Custom comparator:

TreeSet<String> words = new TreeSet<>((s1, s2) -> {
    int lenCmp = s1.length() - s2.length();
    if (lenCmp == 0) return s1.compareTo(s2);
    return lenCmp;
});
words.add("apple");
words.add("kiwi");
words.add("pear");
System.out.println(words); // [kiwi, pear, apple]

Map (Key‑Value Pairs)

Map stores entries with unique keys and possibly duplicate values.

Method Description
put(K key, V value) Add or update entry
remove(Object key) Remove entry by key
clear() Remove all entries
containsKey(Object key) Test key presence
containsValue(Object value) Test value presence
isEmpty() Test emptiness
size() Number of entries
Map<String, Double> prices = new HashMap<>();
prices.put("milk", 1.99);
prices.put("bread", 2.49);
prices.put("eggs", 3.29);
Double old = prices.put("eggs", 2.99); // returns old value
System.out.println(old); // 3.29

Iteration patterns

  • Key set – iterate keys, fetch values.
  • Entry set – most efficient.
for (Map.Entry<String, Double> entry : prices.entrySet()) {
    System.out.println(entry.getKey() + " -> " + entry.getValue());
}
  • Lambda forEach
prices.forEach((k, v) -> System.out.println(k + ": " + v));

HashMap, LinkedHashMap, TreeMap

  • HashMap – no order, requires hashCode()/equals() overrides for custom keys.
  • LinkedHashMap – maintains insertion order.
  • TreeMap – sorted by key (natural ordering or Comparator).

Varargs

public int sum(int... numbers) {
    int total = 0;
    for (int n : numbers) {
        total += n;
    }
    return total;
}
// sum(1,2,3) => 6

Rules: at most one varargs parameter, and it must be the last parameter.


Collections Utility Class

Static helper methods for collections.

List<String> list = new ArrayList<>();
Collections.addAll(list, "one", "two", "three");
Collections.shuffle(list);
Collections.sort(list);
int idx = Collections.binarySearch(list, "two");

Stream API

Streams bring functional‑style operations to collections.

Immutable Collections

Factory methods in List, Set, Map interfaces:

List<String> immutableList = List.of("a", "b", "c");
Set<Integer> immutableSet = Set.of(1, 2, 3);
Map<String, Integer> immutableMap = Map.of("key1", 1, "key2", 2);

These collections reject any modification attempt.


Creating Streams

Source Method
Collection stream()
Array Arrays.stream(arr)
Values Stream.of(...)
Stream<String> stream = list.stream();
IntStream intStream = Arrays.stream(new int[]{10, 20, 30});
Stream<Integer> numberStream = Stream.of(5, 10, 15);

Intermediate Operations

Method Purpose
filter(Predicate) Keep matching elements
limit(long) First n elements
skip(long) Drop first n elements
distinct() Remove duplicates
map(Function) Transform elements
sorted() / sorted(Comparator) Sort elements
names.stream()
     .filter(n -> n.startsWith("A"))
     .map(String::toUpperCase)
     .sorted()
     .forEach(System.out::println);

Terminal Operations

Method Result
forEach(Consumer) Perform action for each element
count() Number of elements
collect(Collector) Reduce to collection or value
toArray() Collect to array
List<Integer> lengths = words.stream()
                              .map(String::length)
                              .collect(Collectors.toList());

Integer[] array = lengths.toArray(Integer[]::new);

Advanced collect – building a map:

Map<String, Integer> wordLengths = words.stream()
    .collect(Collectors.toMap(Function.identity(), String::length));

Method References

Shorthand when a lambda simply calls an existing method.

Four forms:

  • Static method: ClassName::staticMethod
  • Instance method of a specfiic object: obj::method
  • Instance method of any object of a type: ClassName::instanceMethod
  • Constructor: ClassName::new
Consumer<String> printer = System.out::println;
Function<String, Integer> parser = Integer::parseInt;
Supplier<ArrayList<String>> listSupplier = ArrayList::new;

Exception Handling

Exceptions represent runtime problems. Two categories:

  • Checked – must be handled at compile time.
  • Unchecked (runtime) – optional handling.

try‑catch

try {
    int result = 10 / 0;
} catch (ArithmeticException e) {
    System.out.println("Cannot divide by zero: " + e.getMessage());
}

Throwable Methods

getMessage(), toString(), printStackTrace()

Throwing Exceptions

public void validate(int age) throws InvalidAgeException {
    if (age < 0 || age > 150) {
        throw new InvalidAgeException("Invalid age: " + age);
    }
}

Custom Exceptions

public class InvalidAgeException extends RuntimeException {
    public InvalidAgeException(String message) {
        super(message);
    }
}

try‑with‑resources

Automatically closes resources implementing AutoCloseable.

try (FileInputStream in = new FileInputStream("input.txt");
     FileOutputStream out = new FileOutputStream("output.txt")) {
    byte[] buffer = new byte[4096];
    int len;
    while ((len = in.read(buffer)) != -1) {
        out.write(buffer, 0, len);
    }
}

File Handling

java.io.File represents a file or directory path.

Construcotr Example
File(String path) new File("/data/report.txt")
File(String parent, String child) new File("/data", "report.txt")
File(File parent, String child) new File(dir, "report.txt")

Key methods: isFile(), isDirectory(), exists(), length(), getName(), delete(), mkdir(), mkdirs(), listFiles().

Recursive directory size:

public long directorySize(File dir) {
    File[] files = dir.listFiles();
    if (files == null) return 0;
    long size = 0;
    for (File f : files) {
        if (f.isFile()) {
            size += f.length();
        } else {
            size += directorySize(f);
        }
    }
    return size;
}

I/O Streams

Byte Streams

  • FileInputStream – read bytes from a file.
  • FileOutputStream – write bytes to a file.
// Copy file using byte arrays
try (FileInputStream fis = new FileInputStream("source.dat");
     FileOutputStream fos = new FileOutputStream("target.dat")) {
    byte[] buf = new byte[8192];
    int bytesRead;
    while ((bytesRead = fis.read(buf)) != -1) {
        fos.write(buf, 0, bytesRead);
    }
}

Character Streams

  • FileReader / FileWriter – for text data with default encoding.
try (BufferedReader reader = new BufferedReader(new FileReader("story.txt"))) {
    String line;
    while ((line = reader.readLine()) != null) {
        System.out.println(line);
    }
}

Buffered Streams

  • BufferedInputStream / BufferedOutputStream – improved performance.
  • BufferedReader / BufferedWriter – with readLine() / newLine().

Serialization

Object serialization converts an object into a byte stream; deserializasion reconstructs it.

public class Product implements Serializable {
    private static final long serialVersionUID = 1L;
    private String name;
    private double price;
    // constructors, getters, setters
}

// Serialize
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("product.ser"));
oos.writeObject(new Product("Laptop", 999.99));

// Deserialize
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("product.ser"));
Product p = (Product) ois.readObject();

Always define a serialVersionUID to maintain version compatibility.

Print Streams

PrintStream / PrintWriter provide convenient print, println, printf methods. They can auto‑flush.

Compression Streams

  • ZipInputStream / ZipOutputStream for zip archives.

Character Encoding

InputStreamReader / OutputStreamWriter bridge byte and character streams with explicit charset.

try (OutputStreamWriter writer = new OutputStreamWriter(
        new FileOutputStream("data.txt"), StandardCharsets.UTF_8)) {
    writer.write("Hello, 世界!");
}

Commons IO / Hutool

Third‑party libraries like Apache Commons IO (FileUtils, IOUtils) and Hutool simplify file I/O tasks.


Multithreading

Creating Threads

  1. Extend Thread
class Worker extends Thread {
    public void run() {
        for (int i = 0; i < 5; i++) {
            System.out.println(getName() + ": " + i);
        }
    }
}
new Worker().start();
  1. Implement Runnable
class Task implements Runnable {
    public void run() {
        System.out.println(Thread.currentThread().getName());
    }
}
new Thread(new Task(), "worker-1").start();
  1. Callable and Future – return results and throw exceptions.
Callable<Integer> calculation = () -> {
    Thread.sleep(100);
    return 42;
};
FutureTask<Integer> futureTask = new FutureTask<>(calculation);
new Thread(futureTask).start();
Integer result = futureTask.get();

Thread Methods

getName(), setName(), currentThread(), sleep(), setPriority(), join(), yield().

Synchronization

Prevents race conditions using synchronized blocks or methods.

class Counter {
    private int count = 0;
    public synchronized void increment() {
        count++;
    }
    public int getCount() { return count; }
}

Or use an explicit lock:

Lock lock = new ReentrantLock();
lock.lock();
try {
    // critical section
} finally {
    lock.unlock();
}

Producer‑Consumer with BlockingQueue

BlockingQueue<String> queue = new ArrayBlockingQueue<>(5);

// Producer
new Thread(() -> {
    for (int i = 0; i < 10; i++) {
        try {
            queue.put("item-" + i);
        } catch (InterruptedException e) { }
    }
}).start();

// Consumer
new Thread(() -> {
    while (true) {
        try {
            String item = queue.take();
            System.out.println("Consumed: " + item);
        } catch (InterruptedException e) { }
    }
}).start();

Thread Pools

Executors factory methods or manual ThreadPoolExecutor.

ExecutorService pool = Executors.newFixedThreadPool(3);
for (int i = 0; i < 5; i++) {
    pool.submit(() -> System.out.println(Thread.currentThread().getName()));
}
pool.shutdown();

Custom thread pool with rejection policy:

ThreadPoolExecutor exec = new ThreadPoolExecutor(
    2, 4, 60L, TimeUnit.SECONDS,
    new ArrayBlockingQueue<>(10),
    Executors.defaultThreadFactory(),
    new ThreadPoolExecutor.CallerRunsPolicy()
);

Networking

Core elements: IP address, port number, protocol.

InetAddress

InetAddress addr = InetAddress.getByName("example.com");
System.out.println(addr.getHostAddress());
System.out.println(addr.getHostName());

UDP Communication

Sender:

DatagramSocket socket = new DatagramSocket();
byte[] data = "Hello".getBytes();
InetAddress ip = InetAddress.getByName("127.0.0.1");
DatagramPacket packet = new DatagramPacket(data, data.length, ip, 9876);
socket.send(packet);
socket.close();

Receiver:

DatagramSocket server = new DatagramSocket(9876);
byte[] buffer = new byte[1024];
DatagramPacket packet = new DatagramPacket(buffer, buffer.length);
server.receive(packet);
String msg = new String(packet.getData(), 0, packet.getLength());
System.out.println(msg);
server.close();

TCP Communication

Client:

Socket socket = new Socket("localhost", 8080);
PrintWriter out = new PrintWriter(socket.getOutputStream(), true);
out.println("Hello, server");
socket.close();

Server:

ServerSocket server = new ServerSocket(8080);
Socket client = server.accept();
BufferedReader in = new BufferedReader(new InputStreamReader(client.getInputStream()));
String line = in.readLine();
System.out.println("Received: " + line);
client.close();
server.close();

Reflection

Reflection allows runtime inspection and manipulation of classes, methods, fields, and constructors.

Getting Class Objects

// 1. Class.forName
Class<?> c1 = Class.forName("java.util.ArrayList");

// 2. .class literal
Class<?> c2 = ArrayList.class;

// 3. getClass() on instance
ArrayList<?> list = new ArrayList<>();
Class<?> c3 = list.getClass();

Constructors

Constructor<?> constructor = String.class.getConstructor(String.class);
String str = (String) constructor.newInstance("hello");

Access private constructor:

Constructor<MyClass> privateCons = MyClass.class.getDeclaredConstructor(int.class);
privateCons.setAccessible(true);
MyClass obj = privateCons.newInstance(42);

Fields

Field field = MyClass.class.getDeclaredField("secret");
field.setAccessible(true);
Object value = field.get(obj);
field.set(obj, newValue);

Methods

Method method = MyClass.class.getDeclaredMethod("compute", int.class, int.class);
method.setAccessible(true);
int result = (int) method.invoke(obj, 10, 20);

Dynamic Proxies

Proxies create a "wrapper" object that adds behaviour transparently.

public interface Service {
    String process(String input);
}

public class ServiceImpl implements Service {
    public String process(String input) {
        return "processed: " + input;
    }
}

// Creating a proxy
Service proxy = (Service) Proxy.newProxyInstance(
    Service.class.getClassLoader(),
    new Class[]{Service.class},
    (proxyObj, method, args) -> {
        System.out.println("Before " + method.getName());
        Object result = method.invoke(new ServiceImpl(), args);
        System.out.println("After " + method.getName());
        return result;
    }
);

String output = proxy.process("test");
// Output:
// Before process
// After process
// processed: test

The proxy delegates to the real object after adding pre‑/post‑processing logic.

Tags: java lambda Collections Streams IO

Posted on Wed, 26 Aug 2026 16:38:26 +0000 by catchy