DEV Community

Chandana prabhakar
Chandana prabhakar

Posted on

From Zero to Java: Day 3 of My Backend Development Journey

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)
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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);
Enter fullscreen mode Exit fullscreen mode

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;
    }
}
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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();
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

Multiple resources

try (InputStream in = new FileInputStream("a.bin");
     OutputStream out = new FileOutputStream("b.bin")) {
    // copy bytes
}
Enter fullscreen mode Exit fullscreen mode

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);
    }
}
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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);
Enter fullscreen mode Exit fullscreen mode

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
    }
}
Enter fullscreen mode Exit fullscreen mode

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);
    }
}
Enter fullscreen mode Exit fullscreen mode

Directory traversal

try (Stream<Path> stream = Files.walk(Paths.get("data"))) {
    stream.filter(Files::isRegularFile)
          .forEach(p -> System.out.println(p.toString()));
}
Enter fullscreen mode Exit fullscreen mode

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 ensure try-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)