Search

Software Engineer's Notes

Tag

JAVA

implements vs extends in Java

Difference between implements vs extends in Java

What does implements mean in Java?

An interface declares capabilities (method signatures, default methods, static methods, constants). A class uses implements to promise it provides those capabilities.

public interface Cache {
    Optional<String> get(String key);
    void put(String key, String value);
    default boolean contains(String key) { return get(key).isPresent(); }
}

public class InMemoryCache implements Cache {
    private final Map<String, String> store = new ConcurrentHashMap<>();
    public Optional<String> get(String key) { return Optional.ofNullable(store.get(key)); }
    public void put(String key, String value) { store.put(key, value); }
}

Key points:

  • A class can implement multiple interfaces: class A implements I1, I2 { ... }.
  • Interfaces can have default methods (since Java 8), helping evolve APIs without breaking implementors.
  • Records and enums can implement interfaces.
  • Interfaces cannot hold state (beyond public static final constants).

What does extends mean in Java?

1) Class → Class (single inheritance)

A class can extend one other class to reuse state/behavior and specialize it.

public abstract class Shape {
    public abstract double area();
}

public class Rectangle extends Shape {
    private final double w, h;
    public Rectangle(double w, double h) { this.w = w; this.h = h; }
    @Override public double area() { return w * h; }
}

2) Interface → Interface (multiple inheritance of type)

An interface can extend one or more interfaces to combine contracts.

public interface Startable { void start(); }
public interface Stoppable { void stop(); }
public interface Lifecycle extends Startable, Stoppable { }

Notes:

  • Classes cannot extend multiple classes.
  • Use super(...) to call a superclass constructor; use @Override to refine behavior.
  • If two parent interfaces provide the same default method, the implementing class must disambiguate by overriding.

Differences at a glance

Topicimplementsextends (class→class)extends (interface→interface)
PurposePromise behavior via interfaceReuse/ specialize implementation & stateCombine contracts
Multiple inheritanceClass can implement many interfacesNot allowed (single superclass)Allowed (an interface can extend many)
StateNo instance state in interfaceInherits fields and methodsNo instance state
ConstructorsN/ASubclass calls super(...)N/A
API evolutiondefault methods helpRisky; changes can rippledefault in parents propagate
Typical usePlug-in points, ports, test seamsTrue specialization (“is-a”)Build richer capability sets

When should I use each?

Use implements (interfaces) when:

  • You want flexible contracts decoupled from implementations (e.g., PaymentGateway, Cache, Repository).
  • You need multiple behaviors without tight coupling.
  • You care about testability (mock/fake implementations in unit tests).
  • You’re designing hexagonal/clean architecture ports and adapters.

Use extends (class inheritance) when:

  • There’s a strong “is-a” relationship and shared state/behavior that truly belongs in a base class.
  • You’re refining behavior of a framework base class (e.g., HttpServlet, Thread, java.io streams).
  • You need protected hooks / template methods for controlled extension.

Avoid overusing inheritance when composition (a field that delegates) is clearer and safer.

Why do we need them? Importance & benefits

  • Abstraction & decoupling: Interfaces let you program to capabilities, not concrete types, enabling swap-in implementations.
  • Reuse & specialization: Inheritance centralizes common behavior, reducing duplication (when it’s a true fit).
  • Polymorphism: Callers depend on supertype/interface; implementations can vary behind the scenes.
  • API evolution: Interfaces with default methods allow additive changes with fewer breaking changes.
  • Testability: Interfaces create clean boundaries for mocks/stubs; inheritance can provide test doubles via small overrides.

Practical examples (real-world flavored)

Spring Boot service port with an adapter

public interface EmailSender {
    void send(String to, String subject, String body);
}

@Service
public class SmtpEmailSender implements EmailSender {
    // inject JavaMailSender, etc.
    public void send(String to, String subject, String body) { /* ... */ }
}

// Usage: depend on EmailSender in controllers/use-cases, not on SMTP details.

Specializing a framework class (carefully)

public class AuditInputStream extends FilterInputStream {
    public AuditInputStream(InputStream in) { super(in); }
    @Override public int read() throws IOException {
        int b = super.read();
        // audit logic...
        return b;
    }
}

Modern features & gotchas

  • Default methods conflict: If A and B define the same default m(), a class implements A, B must override m() to resolve the diamond.
  • Abstract classes vs interfaces:
    • Use abstract classes when you need shared state, partial implementations, or constructors.
    • Use interfaces to define capabilities and support multiple inheritance of type.
  • Sealed classes (Java 17+): Control which classes can extend a base:
    public sealed class Token permits JwtToken, ApiKeyToken { }
  • Records: Can implements interfaces, great for DTOs with behavior contracts:
    public record Money(BigDecimal amount, Currency currency) implements Comparable<Money> { ... }

Integration into your team’s software development process

1) Architecture & layering

  • Define ports as interfaces in application/core modules (e.g., PaymentProcessor, UserRepository).
  • Implement adapters in infrastructure modules (JdbcUserRepository, StripePaymentProcessor).
  • Expose services via interfaces; keep controllers/use-cases depending on interfaces only.

2) Coding standards

  • Guideline: Prefer implements + composition; justify any extends in code review.
  • Naming: Interfaces describe capability (*Service, *Repository, *Gateway); implementations are specific (Jdbc*, S3*, InMemory*).
  • Visibility: Keep base classes package-private when possible; avoid protected fields.
  • Final classes/methods: Mark classes final unless a deliberate extension point.

3) Testing

  • Unit tests mock interfaces (Mockito/Stub implementations).
  • For inheritance, favor template methods and override only documented hooks in tests.

4) Code review checklist

  • Is this a true “is-a”? If not, prefer composition.
  • Are we depending on interfaces at boundaries?
  • Could an interface with a default help evolve this API safely?
  • Are we avoiding deep inheritance chains (max depth 1–2)?

5) Tooling & enforcement

  • Add static analysis rules (e.g., Error Prone/Checkstyle/Sonar) to flag deep inheritance and unused protected members.
  • Architectural tests (ArchUnit) to enforce “controllers depend on ports, not on adapters.”

Common pitfalls & how to avoid them

  • “God” base classes: Too much logic in a superclass → fragile subclasses. Split responsibilities; use composition.
  • Leaky abstractions: Interfaces that expose implementation details limit flexibility. Keep them capability-focused.
  • Over-mocking concrete classes: Depend on interfaces at boundaries to keep tests simple and fast.
  • Default method ambiguity: If combining interfaces with overlapping defaults, override explicitly.

FAQ

Can an interface extend a class?
No. Interfaces can only extend interfaces.

Can a class both extend and implement?
Yes: class C extends Base implements I1, I2 { ... }.

Is multiple inheritance supported?
For classes: no. For interfaces: yes (an interface may extend multiple interfaces; a class may implement multiple interfaces).

Interface vs abstract class—quick rule of thumb?
Need shared state/constructors → abstract class. Need flexible capability and multiple inheritance of type → interface.

Summary

  • Reach for implements to define what something can do.
  • Use extends to refine how something does it—only when it’s truly the same kind of thing.
  • Bake these choices into your architecture, guidelines, tests, and tooling to keep designs flexible and maintainable.

Inversion of Control in Software Development

Inversion of Control

What is Inversion of Control?

Inversion of Control (IoC) is a design principle in software engineering that shifts the responsibility of controlling the flow of a program from the developer’s custom code to a framework or external entity. Instead of your code explicitly creating objects and managing their lifecycles, IoC delegates these responsibilities to a container or framework.

This approach promotes flexibility, reusability, and decoupling of components. IoC is the foundation of many modern frameworks, such as Spring in Java, .NET Core Dependency Injection, and Angular in JavaScript.

A Brief History of Inversion of Control

The concept of IoC emerged in the late 1980s and early 1990s as object-oriented programming matured. Early implementations were seen in frameworks like Smalltalk MVC and later Java Enterprise frameworks.
The term “Inversion of Control” was formally popularized by Michael Mattsson in the late 1990s. Martin Fowler further explained and advocated IoC as a key principle for achieving loose coupling in his widely influential articles and books.

By the 2000s, IoC became mainstream with frameworks such as Spring Framework (2003) introducing dependency injection containers as practical implementations of IoC.

Components of Inversion of Control

Inversion of Control can be implemented in different ways, but the following components are usually involved:

1. IoC Container

A framework or container responsible for managing object creation and lifecycle. Example: Spring IoC Container.

2. Dependencies

The objects or services that a class requires to function.

3. Configuration Metadata

Instructions provided to the IoC container on how to wire dependencies. This can be done using XML, annotations, or code.

4. Dependency Injection (DI)

A specific and most common technique to achieve IoC, where dependencies are provided rather than created inside the class.

5. Event and Callback Mechanisms

Another IoC technique where the flow of execution is controlled by an external framework calling back into the developer’s code when needed.

Benefits of Inversion of Control

1. Loose Coupling

IoC ensures that components are less dependent on each other, making code easier to maintain and extend.

2. Improved Testability

With dependencies injected, mocking and testing become straightforward.

3. Reusability

Since classes do not create their own dependencies, they can be reused in different contexts.

4. Flexibility

Configurations can be changed without altering the core logic of the program.

5. Scalability

IoC helps in scaling applications by simplifying dependency management in large systems.

Why and When Do We Need Inversion of Control?

  • When building complex systems with multiple modules requiring interaction.
  • When you need flexibility in changing dependencies without modifying code.
  • When testing is critical, since IoC makes mocking dependencies easy.
  • When aiming for maintainability, as IoC reduces the risk of tight coupling.

IoC is especially useful in enterprise applications, microservices, and modular architectures.

How to Integrate IoC into Our Software Development Process

  1. Choose a Framework or Container
    • For Java: Spring Framework or Jakarta CDI
    • For .NET: Built-in DI Container
    • For JavaScript: Angular or NestJS
  2. Identify Dependencies
    Review your code and highlight where objects are created and tightly coupled.
  3. Refactor Using DI
    Use constructor injection, setter injection, or field injection to provide dependencies instead of creating them inside classes.
  4. Configure Metadata
    Define wiring via annotations, configuration files, or code-based approaches.
  5. Adopt IoC Practices Gradually
    Start with small modules and expand IoC adoption across your system.
  6. Test and Validate
    Use unit tests with mocked dependencies to confirm that IoC is working as intended.

Conclusion

Inversion of Control is a powerful principle that helps developers build flexible, testable, and maintainable applications. By shifting control to frameworks and containers, software becomes more modular and adaptable to change. Integrating IoC into your development process is not only a best practice—it’s a necessity for modern, scalable systems.

String vs StringBuilder vs StringBuffer in Java

String vs StringBuilder vs StringBuffer in Java

Why Are There Three Similar Types?

Java offers three string-related types to balance readability, safety, and performance:

  • String is simple and safe because it’s immutable.
  • StringBuilder is fast for single-threaded, heavy concatenation.
  • StringBuffer is like StringBuilder but synchronized for thread safety.

Immutability (String) prevents accidental changes and enables pooling/caching. Mutability (StringBuilder/StringBuffer) avoids creating many temporary objects during repeated modifications.

What Are They?

String (Immutable, Thread-Safe by Design)

  • Once created, its content never changes.
  • Any “change” (e.g., concatenation) returns a new String.
  • String literals are stored in the string pool for memory efficiency.
  • Great for constants, keys, logging messages that don’t change, and APIs that return stable values.

StringBuilder (Mutable, Not Synchronized)

  • Designed for fast, frequent modifications (append, insert, delete) in a single thread.
  • No synchronization overhead → typically the fastest way to build strings dynamically.

StringBuffer (Mutable, Synchronized)

  • Like StringBuilder but synchronized methods for thread safety if the same builder is shared across threads.
  • Synchronization adds overhead, so it’s slower than StringBuilder in single-threaded code.

Key Differences at a Glance

Mutability

  • String: Immutable
  • StringBuilder: Mutable
  • StringBuffer: Mutable

Thread Safety

  • String: Safe to share (cannot change)
  • StringBuilder: Not thread-safe
  • StringBuffer: Thread-safe (synchronized)

Performance (Typical)

  • String: Fine for few ops; costly in large loops with + or +=
  • StringBuilder: Fastest for many concatenations in one thread
  • StringBuffer: Slower than StringBuilder due to synchronization

Common APIs

  • String: rich APIs (substring, replace, split, equals, hashCode, compareTo)
  • StringBuilder/StringBuffer: builder-style APIs (append, insert, delete, reverse, setCharAt), then toString()

How Do I Choose?

Quick Decision Guide

  • Need a constant or rarely change the text? Use String.
  • Building text in a loop or via many appends in one thread? Use StringBuilder.
  • Building text shared across threads without external locks? Use StringBuffer (or prefer StringBuilder with your own synchronization strategy if you control access).

Rule of Thumb

  • Use String by default for readability and safety.
  • Switch to StringBuilder when performance matters during repeated concatenations.
  • Use StringBuffer only when you truly need shared mutation across threads.

Practical Examples

Example 1: Costly Loop with String

String s = "";
for (int i = 0; i < 10000; i++) {
    s += i; // creates many temporary objects → avoid
}

Example 2: Efficient Loop with StringBuilder

StringBuilder sb = new StringBuilder(10000); // optional capacity hint
for (int i = 0; i < 10000; i++) {
    sb.append(i);
}
String s = sb.toString();

Example 3: When StringBuffer Makes Sense

// Only if 'shared' is truly accessed by multiple threads concurrently.
StringBuffer shared = new StringBuffer();
Runnable task = () -> {
    for (int i = 0; i < 1000; i++) {
        shared.append(i).append(",");
    }
};

Benefits of Each

String

  • Simplicity and clarity
  • Inherent thread safety via immutability
  • Works well with string pooling (memory optimization)
  • Safe as map keys and for caching

StringBuilder

  • Best performance for intensive concatenation
  • Low GC pressure versus many temporary Strings
  • Fluent, builder-style API

StringBuffer

  • Built-in thread safety without external locks
  • Drop-in API similarity to StringBuilder

When to Use Them (and When Not To)

Use String When

  • Defining constants and literals
  • Passing values across layers/APIs
  • Storing keys in collections (immutability prevents surprises)

Avoid String When

  • You’re repeatedly concatenating in loops (prefer StringBuilder)

Use StringBuilder When

  • Building JSON, CSV, logs, or messages in loops
  • Formatting output dynamically in a single thread

Avoid StringBuilder When

  • The builder is accessed by multiple threads simultaneously (unless you guard it externally)

Use StringBuffer When

  • Multiple threads must mutate the same buffer at the same time and you can’t refactor for confinement

Avoid StringBuffer When

  • You’re single-threaded or can confine builders per thread (prefer StringBuilder for speed)

Additional Tips

About the + Operator

  • In a single expression, the compiler typically uses an internal StringBuilder.
  • In loops, += often creates many intermediate objects. Prefer an explicit StringBuilder.

Capacity Planning

  • Builders start with a default capacity and grow (usually doubling plus a small constant).
  • If you can estimate size, call new StringBuilder(expectedLength) or ensureCapacity to reduce reallocations.

Interoperability

  • Convert builders to String with toString().
  • For equality checks, compare String values, not builders.

Summary

  • String: Immutable, simple, safe → use by default for stable text.
  • StringBuilder: Mutable, fastest for repeated concatenations in one thread.
  • StringBuffer: Mutable, synchronized for shared multi-threaded mutation—use only when you truly need it.

With these guidelines, choose the simplest type that meets your thread-safety and performance needs, and only optimize to builders when profiling or repeated concatenation calls for it.

OptionalInt vs Optional in Java: When to Use Which (and Why)

If you’ve worked with Java’s Optional<T>, you’ve probably also seen OptionalInt, OptionalLong, and OptionalDouble. Why does Java have both Optional<Integer> and OptionalInt? Which should you choose—and when?

This guide breaks it down with clear examples and a simple decision checklist.

  • Optional<Integer> is the generic Optional for reference types. It’s flexible, works everywhere generics are needed, but boxes the int (adds memory & CPU overhead).
  • OptionalInt is a primitive specialization for int. It avoids boxing, is faster and lighter, and integrates nicely with IntStream, but is less flexible (no generics, fewer methods).

Use OptionalInt inside performance-sensitive code and with primitive streams; use Optional<Integer> when APIs require Optional<T> or you need a uniform type.

What Are They?

Optional<Integer>

A container that may or may not hold an Integer value:

Optional<Integer> maybeCount = Optional.of(42);     // present
Optional<Integer> emptyCount = Optional.empty();    // absent

OptionalInt

A container specialized for the primitive int:

OptionalInt maybeCount = OptionalInt.of(42);     // present
OptionalInt emptyCount = OptionalInt.empty();    // absent

Both types model “a value might be missing” without using null.

Why Do We Have Two Types?

  1. Performance vs. Flexibility
    • Optional<Integer> requires boxing (intInteger). This allocates objects and adds GC pressure.
    • OptionalInt stores the primitive directly—no boxing.
  2. Stream Ecosystem
    • Primitive streams (IntStream, LongStream, DoubleStream) return primitive optionals (OptionalInt, etc.) for terminal ops like max(), min(), average().

Key Differences at a Glance

AspectOptional<Integer>OptionalInt
TypeGeneric Optional<T>Primitive specialization (int)
BoxingYes (Integer)No
Interop with IntStreamIndirect (must box/unbox)Direct (IntStream.max()OptionalInt)
Methodsget(), map, flatMap, orElse, orElseGet, orElseThrow, ifPresentOrElse, etc.getAsInt(), orElse, orElseGet, orElseThrow, ifPresentOrElse, stream(); no generic map (use primitive ops)
Use in generic APIsYes (fits Optional<T>)No (type is fixed to int)
Memory/CPUHigher (boxing/GC)Lower (no boxing)

How to Choose (Quick Decision Tree)

  1. Are you working with IntStream / primitive stream results?
    Use OptionalInt.
  2. Do you need to pass the result through APIs that expect Optional<T> (e.g., repository/service interfaces, generic utilities)?
    Use Optional<Integer>.
  3. Is this code hot/performance-sensitive (tight loops, high volume)?
    → Prefer OptionalInt to avoid boxing.
  4. Do you need to “map” the contained value using generic lambdas?
    Optional<Integer> (richer map/flatMap).
    (With OptionalInt, use primitive operations or convert to Optional<Integer> when necessary.)

Common Usage Examples

With Streams (Primitive Path)

int[] nums = {1, 5, 2};
OptionalInt max = IntStream.of(nums).max();

int top = max.orElse(-1);          // -1 if empty
max.ifPresent(m -> System.out.println("Max: " + m));

With Collections / Generic APIs

List<Integer> ages = List.of(18, 21, 16);
Optional<Integer> firstAdult =
    ages.stream().filter(a -> a >= 18).findFirst();  // Optional<Integer>

int age = firstAdult.orElseThrow(); // throws if empty

Converting Between Them (when needed)

OptionalInt oi = OptionalInt.of(10);
Optional<Integer> o = oi.isPresent() ? Optional.of(oi.getAsInt()) : Optional.empty();

Optional<Integer> og = Optional.of(20);
OptionalInt op = og.isPresent() ? OptionalInt.of(og.get()) : OptionalInt.empty();

Benefits of Using Optional (Either Form)

  • Eliminates fragile null contracts: Callers are forced to handle absence.
  • Self-documenting APIs: Return type communicates “might not exist.”
  • Safer refactoring: Missing values become compile-time-visible.

Extra Benefit of OptionalInt

  • Performance: No boxing/unboxing. Less GC. Better fit for numeric pipelines.

When to Use Them

Good fits:

  • Return types where absence is valid (e.g., findById, max, “maybe present” queries).
  • Stream terminal results (IntStreamOptionalInt).
  • Public APIs where you want to make “might be empty” explicit.

Avoid or be cautious:

  • Fields in entities/DTOs: Prefer plain fields with domain defaults; Optional fields complicate serialization and frameworks.
  • Method parameters: Usually model “optional input” with method overloading or builders, not Optional parameters.
  • Collections of Optional: Prefer filtering to keep collections of concrete values.
  • Overuse in internal code paths where a simple sentinel (like -1) is a clear domain default.

Practical Patterns

Pattern: Prefer Domain Defaults for Internals

If your domain has a natural default (e.g., “unknown count” = 0), returning int may be simpler than OptionalInt.

Pattern: Optional for Query-Like APIs

When a value may not be found and absence is legitimate, return an Optional:

OptionalInt findWinningScore(Game g) { ... }

Pattern: Keep Boundaries Clean

  • At primitive stream boundariesOptionalInt.
  • At generic/service boundariesOptional<Integer>.

Pitfalls & Tips

  • Don’t call get()/getAsInt() without checking. Prefer orElse, orElseGet, orElseThrow, or ifPresentOrElse.
  • Consider readability. If every call site immediately does orElse(-1), a plain int with a documented default may be clearer.
  • Measure before optimizing. Choose OptionalInt for hot paths, but don’t prematurely micro-optimize.

Cheatsheet

  • Need performance + primitives? OptionalInt
  • Need generic compatibility or richer ops? Optional<Integer>
  • Returning from IntStream ops? OptionalInt
  • Public service/repo interfaces? Often Optional<Integer>
  • Don’t use as fields/parameters/inside collections (usually).

Mini Examples: Correct vs. Avoid

Good (return type)

public OptionalInt findTopScore(UserId id) { ... }

Avoid (parameter)

// Hard to use and read
public void updateScore(OptionalInt maybeScore) { ... }

Prefer overloads or builder/setter methods.

Avoid (entity field)

class Player {
  OptionalInt age; // complicates frameworks/serialization
}

Prefer int age with a domain default or a nullable wrapper managed at the edges.

Conclusion

  • Use OptionalInt when you’re in the primitive/stream world or performance matters.
  • Use Optional<Integer> when you need generality, compatibility with Optional<T> APIs, or richer functional methods.
  • Keep Optionals at API boundaries, not sprinkled through fields and parameters.

Pick the one that keeps your code clear, fast, and explicit about absence.

int vs Integer in Java: What They Are, Why Both Exist, and How to Choose

Quick Decision Guide

  • Use int for primitive number crunching, counters, loops, and performance-critical code.
  • Use Integer when you need null, work with collections/generics/Streams, use it as a map key, or interact with APIs that require objects.

What Are int and Integer?

  • int is a primitive 32-bit signed integer type.
    • Range: −2,147,483,648 to 2,147,483,647.
    • Stored directly on the stack or inside objects as a raw value.
  • Integer is the wrapper class for int (in java.lang).
    • An immutable object that contains an int value (or can be null).
    • Provides methods like compare, parseInt (static in Integer), etc.

Why Do We Have Two Types?

Java was designed with primitives for performance and memory efficiency. Later, Java introduced generics, Collections, and object-oriented APIs that need reference types. Wrapper classes (like Integer) bridge primitives and object APIs, enabling features primitives can’t provide (e.g., nullability, method parameters of type Object, use as generic type arguments).

Key Differences at a Glance

Aspectint (primitive)Integer (wrapper class)
NullabilityCannot be nullCan be null
Memory4 bytes for the valueObject header + 4 bytes value (+ padding)
PerformanceFast (no allocation)Slower (allocation, GC, boxing/unboxing)
Generics/CollectionsNot allowed as type parameterAllowed: List<Integer>
Default value (fields)0null
Equality== compares values== compares references; use .equals() for value
AutoboxingNot applicableWorks with int via autoboxing/unboxing
MethodsN/AUtility & instance methods (compareTo, hashCode, etc.)

Autoboxing & Unboxing (and the Gotchas)

Java will automatically convert between int and Integer:

Integer a = 5;    // autoboxing: int -> Integer
int b = a;        // unboxing: Integer -> int

Pitfall: Unboxing a null Integer throws NullPointerException.

Integer maybeNull = null;
int x = maybeNull; // NPE at runtime!

Tip: When a value can be absent, prefer OptionalInt/Optional<Integer> or check for null before unboxing.

Integer Caching (−128 to 127)

Integer.valueOf(int) caches values in [−128, 127]. This can make some small values appear identical by reference:

Integer x = 100;
Integer y = 100;
System.out.println(x == y);      // true (same cached object)
System.out.println(x.equals(y)); // true

Integer p = 1000;
Integer q = 1000;
System.out.println(p == q);      // false (different objects)
System.out.println(p.equals(q)); // true

Rule: Always use .equals() for value comparison with wrappers.

When to Use int

  • Counters, indices, arithmetic in tight loops.
  • Performance-critical code paths to avoid allocation/GC.
  • Fields that are always present (never absent) and don’t need object semantics.
  • Switch statements and bit-level operations.

Example:

int sum = 0;
for (int i = 0; i < nums.length; i++) {
  sum += nums[i];
}

When to Use Integer

  • Collections/Generics/Streams require reference types:
List<Integer> scores = List.of(10, 20, 30);

  • Nullable numeric fields (e.g., optional DB columns, partially populated DTOs).
  • Map keys or values where object semantics and equals/hashCode matter:
Map<Integer, String> userById = new HashMap<>();

  • APIs that expect Object or reflection/serialization frameworks.

Benefits of Each

Benefits of int

  • Speed & low memory footprint.
  • No NullPointerException from unboxing.
  • Straightforward arithmetic.

Benefits of Integer

  • Nullability to represent “unknown/missing”.
  • Works with Collections, Generics, Streams.
  • Provides utility methods and can be used in APIs requiring objects.
  • Immutability makes it safe as a map key.

When Not to Use Them

  • Avoid Integer in hot loops or large arrays where performance/memory is critical (boxing creates many objects).
    • Prefer int[] over List<Integer> when possible.
  • Avoid int when a value might be absent or needs to live in a collection or generic API.
  • Beware of unboxing nulls—if a value can be null, don’t immediately unbox to int.

Practical Patterns

1) DTO with Optional Field

class ProductDto {
  private Integer discountPercent; // can be null if no discount
  // getters/setters
}

2) Streams: Primitive vs Boxed

int sum = IntStream.of(1, 2, 3).sum();          // primitive stream: fast
int sum2 = List.of(1, 2, 3).stream()
                 .mapToInt(Integer::intValue)
                 .sum();                         // boxed -> primitive

3) Safe Handling of Nullable Integer

Integer maybe = fetchCount();           // might be null
int count = (maybe != null) ? maybe : 0; // avoid NPE

4) Overloads & Method Selection

If you provide both:

void setValue(int v) { /* ... */ }
void setValue(Integer v) { /* ... */ }

  • Passing a literal (setValue(5)) picks int.
  • Passing null only compiles for Integer (setValue(null)).

Common FAQs

Q: Why does List<int> not compile?
A: Generics require reference types; use List<Integer> or int[].

Q: Why does x == y sometimes work for small Integers?
A: Because of Integer caching (−128 to 127). Don’t rely on it—use .equals().

Q: I need performance but also collections—what can I do?
A: Use primitive arrays (int[]) or primitive streams (IntStream) to compute, then convert minimally when you must interact with object-based APIs.

Cheat Sheet

  • Performance: int > Integer
  • Nullability: Integer
  • Collections/Generics: Integer
  • Equality: int uses ==; Integer use .equals() for values
  • Hot loops / big data: prefer int / int[]
  • Optional numeric: Integer or OptionalInt (for primitives)

Mini Example: Mixing Both Correctly

class Scoreboard {
  private final Map<Integer, String> playerById = new HashMap<>(); // needs Integer
  private int totalScore = 0;                                      // fast primitive

  void addScore(int playerId, int score) {
    totalScore += score; // primitive math
    playerById.put(playerId, "Player-" + playerId);
  }

  Integer findPlayer(Integer playerId) {
    // Accepts null safely; returns null if absent
    return (playerId == null) ? null : playerId;
  }
}

Final Guidance

  • Default to int for computation and tight loops.
  • Choose Integer for nullability and object-centric APIs (Collections, Generics, frameworks).
  • Watch for NPE from unboxing and avoid boxing churn in performance-sensitive code.
  • Use .equals() for comparing Integer values; not ==.

Understanding ArrayLists in Programming

When working with data in programming, choosing the right data structure is critical. One of the most flexible and widely used data structures is the ArrayList. In this post, we’ll explore what ArrayLists are, why we need them, when to use them, and their time and memory complexities—with a real-world example to tie it all together.

What is an ArrayList?

An ArrayList is a resizable array implementation provided in many programming languages (for example, java.util.ArrayList in Java or List<T> in C#). Unlike regular arrays that have a fixed size, ArrayLists can grow and shrink dynamically as elements are added or removed.

Think of an ArrayList as a dynamic array that provides built-in methods for managing data efficiently.

Why Do We Need an ArrayList?

Arrays are powerful, but they come with limitations:

  • Fixed size: once created, their size cannot change.
  • Manual resizing: you need to manage memory and copy elements if more space is needed.

ArrayLists solve these problems by:

  • Automatically resizing when more elements are added.
  • Providing handy methods like add(), remove(), contains(), and get() for easier management.
  • Allowing both random access (like arrays) and dynamic growth.

When Should We Use ArrayLists?

You should use an ArrayList when:

  • The number of elements in your collection is not known in advance.
  • You frequently need to add, remove, or search for elements.
  • You want random access to elements by index.
  • Performance is important, but you can tolerate occasional resizing overhead.

If you know the size in advance and memory efficiency is critical, a simple array might be better. But if flexibility matters, ArrayLists are the way to go.

Real-World Example of an ArrayList

Imagine you’re building a shopping cart in an e-commerce application.

  • Users can add items (products).
  • They can remove items at any time.
  • The cart needs to expand dynamically as users shop.

Here’s a Java snippet:

import java.util.ArrayList;

public class ShoppingCart {
    public static void main(String[] args) {
        ArrayList<String> cart = new ArrayList<>();

        // Adding items
        cart.add("Laptop");
        cart.add("Smartphone");
        cart.add("Headphones");

        System.out.println("Cart: " + cart);

        // Removing an item
        cart.remove("Smartphone");
        System.out.println("Cart after removal: " + cart);

        // Accessing an item
        System.out.println("First item: " + cart.get(0));
    }
}

Output:

Cart: [Laptop, Smartphone, Headphones]
Cart after removal: [Laptop, Headphones]
First item: Laptop

This example shows how ArrayLists let us manage collections dynamically without worrying about resizing manually.

Time and Memory Complexities

Understanding performance helps you make better design decisions. Here are the typical complexities for ArrayLists:

  • Populating (adding at the end):
    • Average case: O(1) (amortized constant time)
    • Worst case (when resizing happens): O(n)
  • Inserting an element at a specific index:
    • O(n) (because elements may need to shift)
  • Deleting an element:
    • O(n) (elements after the removed one shift left)
  • Accessing an element by index:
    • O(1) (direct access like an array)
  • Memory usage:
    • Slightly higher than arrays due to dynamic resizing overhead (extra space allocated to reduce frequent copying).

Conclusion

ArrayLists are one of the most useful data structures for everyday programming. They combine the fast access of arrays with the flexibility of dynamic collections. Whether you’re building a shopping cart, managing user sessions, or keeping track of tasks, ArrayLists provide a balance of performance and convenience.

Next time you’re faced with a growing list of elements, consider reaching for an ArrayList—it just might be the perfect fit.

MIRTH CONNECT – USING JAVA CODE

We can call java classes and functions from MIRTH CONNECT.

First we need to create a class:

Continue reading “MIRTH CONNECT – USING JAVA CODE”

Blog at WordPress.com.

Up ↑