Skip to main content

Key Java Concepts

Interfaces

Core Understanding

Interfaces in Java define a contract that classes can implement. They specify the methods a class must implement but not their implementation.

✅ Good Example

public interface Drawable {
void draw();
}

public class Circle implements Drawable {
@Override
public void draw() {
System.out.println("Drawing a circle");
}
}

❌ Bad Example

public class Circle {
public void draw() {
System.out.println("Drawing a circle");
}
}

The bad example doesn't use an interface, reducing code flexibility and testability.

Best Practices

  • Define interfaces to capture common behaviors
  • Program to interfaces, not implementations
  • Use interfaces to enable effective unit testing

Use Cases

  • Defining a contract for services or repositories
  • Enabling dependency injection
  • Supporting multiple implementations of a behavior

Abstract Classes

Core Understanding

Abstract classes in Java provide a partial implementation that subclasses can extend and customize. They can contain abstract methods (without implementation) and concrete methods.

✅ Good Example

public abstract class Shape {
protected String color;

public Shape(String color) {
this.color = color;
}

public abstract double getArea();
}

public class Rectangle extends Shape {
private double width;
private double height;

public Rectangle(String color, double width, double height) {
super(color);
this.width = width;
this.height = height;
}

@Override
public double getArea() {
return width * height;
}
}

❌ Bad Example

public class Shape {
protected String color;

public Shape(String color) {
this.color = color;
}

public double getArea() {
throw new UnsupportedOperationException("getArea() must be implemented by subclasses");
}
}

The bad example doesn't use an abstract class, leading to an incomplete implementation in the base class.

Best Practices

  • Use abstract classes to provide base implementations for a class hierarchy
  • Define abstract methods for behaviors that must be implemented by subclasses
  • Don't overload abstract classes with too much functionality

Use Cases

  • Implementing design patterns like Template Method
  • Sharing code between related classes
  • Defining base types for class hierarchies

Collections API

Core Understanding

The Collections API in Java provides a unified architecture for representing and manipulating collections, enabling developers to use these data structures effectively.

✅ Good Example

List<String> fruits = new ArrayList<>();
fruits.add("Apple");
fruits.add("Banana");
fruits.add("Orange");

for (String fruit : fruits) {
System.out.println(fruit);
}

❌ Bad Example

String[] fruits = new String[3];
fruits[0] = "Apple";
fruits[1] = "Banana";
fruits[2] = "Orange";

for (int i = 0; i < fruits.length; i++) {
System.out.println(fruits[i]);
}

The bad example uses an array instead of a collection, leading to more verbose and less flexible code.

Best Practices

  • Use the most generic interface possible (e.g., List instead of ArrayList)
  • Leverage the methods provided by the Collections API
  • Choose the appropriate collection implementation based on performance characteristics

Use Cases

  • Managing groups of objects
  • Implementing complex data structures
  • Simplifying data manipulation and processing

Exception Handling

Core Understanding

Exception handling in Java allows developers to handle errors and exceptional conditions in a structured manner, preventing unexpected crashes and improving system robustness.

✅ Good Example

public void parseFile(String filePath) {
try {
File file = new File(filePath);
// Process the file
} catch (FileNotFoundException e) {
// Handle the specific exception
System.err.println("File not found: " + filePath);
} catch (IOException e) {
// Handle the general exception
System.err.println("Error processing file: " + filePath);
}
}

❌ Bad Example

public void parseFile(String filePath) {
File file = new File(filePath);
// Process the file
}

The bad example doesn't handle exceptions, which can lead to unexpected crashes and makes debugging difficult.

Best Practices

  • Catch more specific exceptions first, followed by more general ones
  • Avoid catching generic exceptions like Exception or Throwable
  • Use try-with-resources to handle resources that need to be closed
  • Don't ignore exceptions without handling them properly

Use Cases

  • Handling errors in I/O operations
  • Responding to exceptional conditions in business logic
  • Implementing retry and fallback mechanisms

Interview Questions

Q1: What's the difference between an interface and an abstract class? A1: An interface can only contain constants and method signatures, while an abstract class can contain method implementations, fields, and non-abstract constructors. A class can implement multiple interfaces but can only extend one abstract class.

Q2: When would you use a LinkedList over an ArrayList? A2: A LinkedList is more efficient for frequent insertions and deletions, especially in the middle of the list, due to its linked data structure. An ArrayList is more efficient for random access and is generally more memory-efficient.

Q3: What's the difference between checked and unchecked exceptions? A3: Checked exceptions are exceptions that a method can throw and must be declared in the method signature or handled within the method. They are usually exceptions from which a program can recover. Unchecked exceptions are exceptions that do not need to be explicitly declared or handled, typically programming errors like NullPointerException or ArrayIndexOutOfBoundsException.


And here's the content for `java-8-features.md` in English, with the complete markdown:

```markdown
---
sidebar_position: 3
title: "Java 8 Features"
description: "Key features introduced in Java 8 for software architects and senior engineers"
---

# Java 8 Features

## Lambda Expressions

### Core Understanding

Lambda expressions in Java 8 allow treating functionality as a method argument, or code as data. They are commonly used to define inline functions by implementing functional interfaces.

#### ✅ Good Example

```java
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
numbers.forEach(n -> System.out.println(n));

❌ Bad Example

List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
for (Integer n : numbers) {
System.out.println(n);
}

The bad example uses a traditional for loop instead of a lambda expression, leading to more verbose code.

Best Practices

  • Use lambda expressions to implement functional interfaces
  • Keep lambda expressions concise and focused on a single task
  • Use method references when appropriate for better clarity

Use Cases

  • Implementing event listeners and callbacks
  • Defining inline behaviors for stream operations
  • Enabling functional programming in Java

Streams

Core Understanding

The Streams API in Java 8 allows processing collections in a declarative way. Streams support data processing operations like map, filter, reduce, etc.

✅ Good Example

List<String> names = Arrays.asList("Alice", "Bob", "Charlie");
long count = names.stream()
.filter(name -> name.length() > 4)
.count();

❌ Bad Example

List<String> names = Arrays.asList("Alice", "Bob", "Charlie");
int count = 0;
for (String name : names) {
if (name.length() > 4) {
count++;
}
}

The bad example uses a for loop to filter and count, which is more verbose and less expressive compared to the streams approach.

Best Practices

  • Prefer streams over for loops for collection processing
  • Use parallel streams to improve performance on large datasets
  • Avoid side effects in stream operations to maintain predictable behavior

Use Cases

  • Filtering and transforming collections
  • Implementing map-reduce operations
  • Processing data in parallel

Optional

Core Understanding

Optional in Java 8 is a container for values that may or may not be present. It provides methods to elegantly handle potentially null values, avoiding NullPointerExceptions.

✅ Good Example

Optional<String> optional = Optional.of("Hello");
String result = optional.orElse("Default");

❌ Bad Example

String value = null;
String result = (value != null) ? value : "Default";

The bad example uses explicit null checks, which can lead to cluttered and error-prone code.

Best Practices

  • Use Optional to represent values that may be absent
  • Prefer methods like orElse, orElseGet over explicit null checks
  • Avoid using Optional in class fields, method parameters, and return types

Use Cases

  • Returning values from methods that may be null
  • Chaining dependent operations in the presence of potentially null values
  • Enabling functional programming by avoiding null-related errors

Interview Questions

  • Q1: What's the difference between a lambda expression and an anonymous class?

    • A1: A lambda expression is an anonymous function with concise syntax, while an anonymous class is an inline definition of a class. Lambda expressions are limited to implementing functional interfaces (interfaces with a single abstract method), while anonymous classes can implement any interface or extend any class.

  • Q2: When would you use a parallel stream over a sequential stream?

    • A2: A parallel stream is appropriate when processing a large dataset and the stream operations are independent and stateless. It can significantly improve performance in these cases by leveraging multiple threads. However, for smaller datasets, the overhead of parallelization may outweigh any potential benefits.
  • Q3: What is the purpose of the Optional class?

    • A3: The Optional class is designed to provide a more expressive and safer way to handle potentially null values. It acts as a container for a value that may or may not be present. By wrapping potentially null values in an Optional, it becomes explicit that the absence of the value must be properly handled, promoting cleaner error handling and reducing the likelihood of NullPointerExceptions.