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:
containsrelies onequals. Always overrideequals(andhashCode) 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– withreadLine()/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/ZipOutputStreamfor 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
- Extend
Thread
class Worker extends Thread {
public void run() {
for (int i = 0; i < 5; i++) {
System.out.println(getName() + ": " + i);
}
}
}
new Worker().start();
- Implement
Runnable
class Task implements Runnable {
public void run() {
System.out.println(Thread.currentThread().getName());
}
}
new Thread(new Task(), "worker-1").start();
CallableandFuture– 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.