Day 3 was focused on the small but powerful language features that make Java code safe, readable, and maintainable.
Constructors control how objects are created; this
and super
let you navigate object relationships; try-with-resources makes resource management robust; and file I/O lets your backend read and write data. In this deep dive I’ll explain each concept clearly, show practical code, discuss pitfalls, and share real backend use-cases.
1) Constructors — what, why, and how
A constructor is a special method used to initialize new objects. Key points:
- Name matches the class name.
- No return type (not even
void
). - Called automatically when using
new
. - If you don’t declare any constructor, Java provides a default (no-arg) constructor.
Types of constructors
- No-arg (default) constructor
- Parameterized constructor
- Constructor overloading
- Copy-like constructors (user-defined)
Example: basic constructors and overloading
public class User {
private String username;
private int age;
// No-arg constructor
public User() {
this.username = "guest";
this.age = 0;
}
// Parameterized constructor
public User(String username, int age) {
this.username = username;
this.age = age;
}
// Constructor overloading: another convenience constructor
public User(String username) {
this(username, 18); // constructor chaining
}
@Override
public String toString() {
return username + " (" + age + ")";
}
}
// Usage
User u1 = new User(); // guest (0)
User u2 = new User("alice", 30); // alice (30)
User u3 = new User("bob"); // bob (18)
Constructor chaining
Use this(...)
to call another constructor in the same class. This avoids duplicated initialization logic.
Rule: this(...)
must be the first statement in the constructor.
Order of initialization (simple diagram)
1. Static fields / static initializer blocks (class-load time)
2. Superclass constructors
3. Instance fields / instance initializer blocks
4. Current class constructor body
This explains why superclass constructors run before subclass constructors.
2) this
keyword — current instance
this
refers to the current object instance and is useful in several scenarios:
- Distinguishing instance variables from parameters.
- Calling another constructor (
this(...)
) in the same class. - Returning the current instance to support method chaining.
- Passing the current instance to other methods or constructors.
Example: using this
to resolve shadowing and method chaining
public class BuilderExample {
private String title;
private int count;
public BuilderExample setTitle(String title) {
this.title = title; // this resolves field vs param
return this; // enable chaining
}
public BuilderExample setCount(int count) {
this.count = count;
return this;
}
@Override
public String toString() {
return title + " - " + count;
}
}
// Usage
BuilderExample b = new BuilderExample()
.setTitle("Jobs")
.setCount(42);
this(...)
constructor call example
public class Box {
int width, height;
public Box() {
this(10, 10); // call parameterized constructor
}
public Box(int w, int h) {
this.width = w;
this.height = h;
}
}
3) super
keyword — accessing the parent
super
is used to refer to the immediate parent class. Common uses:
- Call a parent class constructor:
super(...)
- Call a parent class method:
super.someMethod()
- Access parent fields:
super.fieldName
(rarely needed if well-encapsulated)
Rule: If used in a constructor, super(...)
must be the first statement.
Example: using super
to call parent constructor and method
class Animal {
String name;
Animal(String name) { this.name = name; }
void speak() { System.out.println("Animal sound"); }
}
class Dog extends Animal {
String breed;
Dog(String name, String breed) {
super(name); // call parent constructor
this.breed = breed;
}
@Override
void speak() {
super.speak(); // optionally call parent behavior
System.out.println(name + " the " + breed + " barks");
}
}
// Usage
Dog d = new Dog("Rex", "Beagle");
d.speak();
// Output:
// Animal sound
// Rex the Beagle barks
Practical note: this(...)
vs super(...)
They cannot both be used in the same constructor because both must be the first statement.
4) try-with-resources — safe and clean resource management
Introduced in Java 7, try-with-resources ensures resources that implement AutoCloseable
are closed automatically.
Why it matters
Manual close()
in finally
blocks is error-prone and verbose. Try-with-resources closes resources reliably and simplifies code.
Basic example: reading a file
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
public class FileReadExample {
public static String readFirstLine(String path) throws IOException {
try (BufferedReader br = new BufferedReader(new FileReader(path))) {
return br.readLine();
}
}
}
Multiple resources
try (InputStream in = new FileInputStream("a.bin");
OutputStream out = new FileOutputStream("b.bin")) {
// copy bytes
}
Suppressed exceptions
If both the try
block throws and close()
throws, the exception from close()
becomes a suppressed exception. You can inspect suppressed exceptions via Throwable.getSuppressed()
.
try (MyResource r = new MyResource()) {
// code that throws
} catch (Exception e) {
for (Throwable s : e.getSuppressed()) {
System.out.println("Suppressed: " + s);
}
}
Implementing AutoCloseable
You can create custom closable resources by implementing AutoCloseable
or Closeable
:
class MyResource implements AutoCloseable {
public MyResource() { System.out.println("open"); }
public void doWork() { System.out.println("working"); }
@Override
public void close() { System.out.println("close"); }
}
// Usage
try (MyResource r = new MyResource()) {
r.doWork();
}
// Output: open -> working -> close
5) File Handling Basics — practical IO for backends
Java offers both legacy I/O (java.io
) and modern NIO (java.nio.file
). Use NIO (Path
, Files
, Paths
) for most tasks — it's more powerful, clearer, and supports streaming and atomic operations.
Path vs File (quick comparison)
-
java.io.File
: older, still useful (exists(), delete(), mkdirs()). -
java.nio.file.Path
&Files
: preferred for reading/writing, copying, moving, walking directories, and handling options/attributes.
Common file operations (NIO)
import java.nio.file.*;
import java.nio.charset.StandardCharsets;
import java.io.IOException;
import java.util.List;
Path path = Paths.get("data/users.txt");
// Check existence
boolean exists = Files.exists(path);
// Create directories
Files.createDirectories(path.getParent());
// Create an empty file (if not exists)
if (!Files.exists(path)) Files.createFile(path);
// Write small text (overwrites)
Files.write(path, List.of("line1", "line2"), StandardCharsets.UTF_8);
// Append text
Files.write(path, List.of("line3"), StandardCharsets.UTF_8, StandardOpenOption.APPEND);
// Read all lines (not for very large files)
List<String> lines = Files.readAllLines(path, StandardCharsets.UTF_8);
// Copy and move
Files.copy(path, Paths.get("backup/users.bak"), StandardCopyOption.REPLACE_EXISTING);
Files.move(path, Paths.get("archive/users.txt"), StandardCopyOption.REPLACE_EXISTING);
// Delete
Files.deleteIfExists(path);
Streaming large files (avoid loading whole file into memory)
try (BufferedReader br = Files.newBufferedReader(path, StandardCharsets.UTF_8)) {
String line;
while ((line = br.readLine()) != null) {
// process line
}
}
Binary file I/O
// Read all bytes (beware of very large files)
byte[] all = Files.readAllBytes(Paths.get("image.png"));
// Streamed copy
try (InputStream in = Files.newInputStream(src);
OutputStream out = Files.newOutputStream(dest)) {
byte[] buf = new byte[8192];
int r;
while ((r = in.read(buf)) != -1) {
out.write(buf, 0, r);
}
}
Directory traversal
try (Stream<Path> stream = Files.walk(Paths.get("data"))) {
stream.filter(Files::isRegularFile)
.forEach(p -> System.out.println(p.toString()));
}
6) Best Practices & Gotchas
-
Prefer
java.nio.file
(Path + Files) for modern I/O. - Always use try-with-resources for any resource that must be closed.
-
Be careful with encodings — use
StandardCharsets.UTF_8
explicitly. -
Avoid
readAllLines
for huge files; use streaming instead. - Validate file paths from user input to prevent path traversal attacks.
-
Use buffering (
BufferedReader
,BufferedInputStream
) for performance. -
Respect file permissions and handle
AccessDeniedException
. -
Clean up temp files and consider using
Files.createTempFile()
for short-lived files.
7) Real-world backend use cases
- Config files: Read application configuration or secrets at startup (consider secure storage!).
- File uploads/downloads: Use streaming so the server doesn't load whole files into memory.
- Logging and audit trails: Rotate logs and use safe append methods.
- Data import/export: Stream CSV/JSON processing for ETL jobs.
-
Temporary processing files: Use
createTempFile()
and ensuretry-with-resources
+ delete-on-exit logic.
🎯 Day 3 Takeaways
- Constructors initialize objects; use constructor chaining (
this(...)
) to keep initialization DRY. -
this
helps with clarity (shadowing, chaining, passing the instance). -
super
links child classes to parent behavior and constructors — the parent runs first. - Try-with-resources is the go-to pattern for safe resource management and should be used whenever possible.
- Use
java.nio.file
for file operations; stream large files, handle encodings, and always validate external input.
💡 Stay tuned for Day 4
Top comments (0)