InnocentZero's Treasure Chest

HomeFeedAbout Me

17 May 2025

java

Java basics

  • Every application begins with a class. The class must match the filename.
  • Class names have to start with an uppercase character.
// comments are written like cpp
/* or like C */
public class Main {
    public static void main(String[] args) {
        System.out.println("Hello World");
    }
}
  • Compile using javac Main.java and run using java Main.
  • It has all the standard types, Strings, int, float, char, boolean. Strings are immutable in Java to allow interning.
  • Enums work the same way as they do in C. Same for switch-case. Additionally, looping is possible like this:

    for (Level myVar : Level.values()) {
      System.out.println(myVar);
    }
    
  • Additionally, enums can implement interfaces, and have methods, only that they are public, static, and final by default.
  • Overriding is compile time, overloading is runtime.
  • final allows for constant/final values in java.
  • Non-primitive types usually start with an upper letter.
  • Typecasts are the usual C typecasts.
  • Functions here are always methods in a class and have the same scope specifiers as C++. Can also be overloaded or made static.
  • Field access works the same way. If final, can only be instantiated once.
  • Java's classes can be public or private. Public classes are accessible from any other class in any other package. Top level public classes match the name of the file.
  • Static methods cannot be overridden with non-static methods (compilation detection fails).
  • Constructors work pretty much the same way as C++.
  • Default visibility is to be available to classes in the same module.
  • Regular public private protected, with public and private being for cross file classes as well.
  • final for classes means no more inheritance.
  • abstract for classes means that the class cannot be used to create an object, only inherit from the class. abstract methods/attributes are only allowed here, and these things are provided by the sub-classes after that.
// Code from filename: Main.java
// abstract class
abstract class Main {
  public String fname = "John";
  public int age = 24;
  public abstract void study(); // abstract method
}

// Subclass (inherit from Main)
class Student extends Main {
  public int graduationYear = 2018;
  public void study() { // the body of the abstract method is provided here
    System.out.println("Studying all day long");
  }
}
// End code from filename: Main.java

// Code from filename: Second.java
class Second {
  public static void main(String[] args) {
    // create an object of the Student class (which inherits attributes and methods from Main)
    Student myObj = new Student();

    System.out.println("Name: " + myObj.fname);
    System.out.println("Age: " + myObj.age);
    System.out.println("Graduation Year: " + myObj.graduationYear);
    myObj.study(); // call abstract method
  }
}

Some special labels for methods/attributes:

  • transient: Do not serialize these when serializing the object.
  • synchronized: Only one thread can enter the body at a time, others wait on it (recursive allowed).
  • volatile: Flush/have side effects/etc.

Java packaging

file tree structure


└── root
  └── mypack
    └── MyPackageClass.java

Here's how it's done in code

package mypack;
class MyPackageClass {
  public static void main(String[] args) {
    System.out.println("This is my package!");
  }
}

Execution happens with java mypack.MyPackageClass

Inheritance/Class nesting

  • Happens through the extends keyword.
  • final doesn't let any more inheritance happen.
  • All functions are virtual by default.
  • Class nesting
  • Inner classes can also be private/protected to make outside accesses illegal.
  • Static inner classes can be created without creating object of the outer class.
  • Inner classes can access anything from the outer classes.
class OuterClass {
  int x = 10;

  class InnerClass {
    int y = 5;
  }
}

public class Main {
  public static void main(String[] args) {
    OuterClass myOuter = new OuterClass();
    OuterClass.InnerClass myInner = myOuter.new InnerClass();
    System.out.println(myInner.y + myOuter.x);
  }
}

// Outputs 15 (5 + 10)

Interfaces

  • Completely abstract class. Used via class Foo implements Bar, Baz where multiple interfaces are separated via commas
// Interface
interface Animal {
  public void animalSound(); // interface method (does not have a body)
  public void sleep(); // interface method (does not have a body)
}

// Pig "implements" the Animal interface
class Pig implements Animal {
  public void animalSound() {
    // The body of animalSound() is provided here
    System.out.println("The pig says: wee wee");
  }
  public void sleep() {
    // The body of sleep() is provided here
    System.out.println("Zzz");
  }
}

class Main {
  public static void main(String[] args) {
    Pig myPig = new Pig();  // Create a Pig object
    myPig.animalSound();
    myPig.sleep();
  }
}

Object creation best practices

Static factory methods

  • Use them when constructors do not adequately describe what they're doing:
    • BigInteger(int, int, Random) vs BigInteger.probablePrime(int, int, Random)
    • They will not necessarily create a new object. Best for static classes.
    • They can return subtypes/subclasses that are private.

Builder pattern

  • Employed when many parameters and not all of them are necessary.
  • Inner static class that keeps returning an instance of itself and allows you to call setter methods on it.

Eg:

public abstract class Pizza {
    public enum Topping { HAM, MUSHROOM, ONION, PEPPER, SAUSAGE }
    final Set<Topping> toppings;
    abstract static class Builder<T extends Builder<T>> {
        EnumSet<Topping> toppings = EnumSet.noneOf(Topping.class);
        public T addTopping(Topping topping) {
            toppings.add(Objects.requireNonNull(topping));
            return self();
        }
        abstract Pizza build();
        // Subclasses must override this method to return "this"
        protected abstract T self();
    }
    Pizza(Builder<?> builder) {
        toppings = builder.toppings.clone(); // See Item 50
    }
}

public class NyPizza extends Pizza {
    public enum Size { SMALL, MEDIUM, LARGE }
    private final Size size;
    public static class Builder extends Pizza.Builder<Builder> {
        private final Size size;
        public Builder(Size size) {
            this.size = Objects.requireNonNull(size);
        }
        @Override public NyPizza build() {
            return new NyPizza(this);
        }
        @Override protected Builder self() { return this; }
    }
    private NyPizza(Builder builder) {
        super(builder);
        size = builder.size;
    }
}

Enforce Singleton property with private constructors

  • provide a public static final INSTANCE = new Cons() class.
  • provide a public static factory method.
  • Can also use an enum with a single instance (probably the best approach).

Enforce non-instantiability with private constructors

  • Basically used when classes act as a collection of static methods and fields.
  • Compiler provides a default public parameterless constructor.
  • Also holds for things that are only instantiated once in mutable classes (basically computed once).
  • Lazy initialization (checking for first invocation with null checks) might not be as performance friendly as you think.

TODO Prefer dependency injection

Best practices for writing classes

If you can manage the memory, you should

  • Reduces garbage collection overhead and memory footprint.
  • The garbage collector may think that there is still a valid reference to the object.
  • This usually happens when a class manages its own memory, such as an array containing objects.

Avoid Finalizers and Cleaners

  • These are usually run when the object is going to be collected.
  • Do not use them as they are unpredictable in nature.
  • Implement AutoCloseable with a close method to make sure that objects are cleaned up.
  • Do it in a try-finally block to make sure that it gets executed even when there is an exception.
  • Cleaners act as a safety net when the user forgets to call close. They also help with native peers, although that's legacy now.

try-with

  • Best way to autoclean for objects that implement autocloseable.
  • If they provide a custom termination method then use try-finally.
static String firstLineOfFile(String path) throws IOException {
    try (BufferedReader br = new BufferedReader(new FileReader(path))) {
        return br.readLine();
    }
}

Overriding equals

  • Do it only when needed.
  • Notion of logical equality.
  • Does not violate the contract:
    • reflexive (x.equals(x))
    • symmetric (y.equals(x) same as x.equals(y))
    • transitive
    • consistent across invocations
    • non-null reference never equals null.

High quality equals check:

  • Use == operator when doing reference checks (worth doing if the overall comparison is large).
  • instanceof operator to check for class/interface similarity.
  • typecast after using instanceof.
  • Check equality for each of the elements. Use == if the elements are scalar (barring double and float), for double and float do a bit cast (using doubleToLongBits and floatToIntBits), and for others apply equals method again.
  • Don't accidentally overload equals declaration.

HashCode/ToString

  • When overriding equals, also override hashCode.
  • HashCode must return the same integer as long as the information used in object's equal implementation hasn't changed.
  • Two equal objects also return the same hashcode.
  • To write a good hashcode, translate most fields to an int, and then do result = 37*result + c where seed value of result is 17 and c is the integer.
  • Override toString as it will return garbage otherwise.

Override clone judiciously

  • Cloneable interface decides whether Object class' clone is usable or not.
  • If the class implements Cloneable then it does a bit by bit copy, else it throws an exception.
  • Treat clone method like it's another constructor.
  • TL;DR, preferably don't implement it at all if possible.
  • First call super.clone() and then fix whatever's necessary.
  • Just provide a copy constructor.

Implement Comparable

  • compareTo is a feature of Comparable interface.
  • Implementations must ensure that sgn(x.compareTo(y)) == -sgn(y.compareTo(x))
  • Also transitivity.
  • Usually, (x.compareTo(y)==0)==x.equals(y). However it is not necessary.
  • If the above statement is not true it must be documented. This is true for floats.
  • Usually it's not expected or needed for it to work across classes.
  • While using integer substraction, be careful for overflows.

Encapsulate

  • Favour immutability.
    • Don't provide mutators.
    • No methods may be overriden (not necessarily making them final).
    • All fields are final.
    • All fields are private.
    • Exclusive access to mutable components.
  • Static factory method classes are probably the best way to make immutable classes.
  • Serializable implementations must have an explicit readObject or readResolve method. More on this later.

Favour composition over Inheritance

  • Inheritance means you need to update the codebase of subclass if superclass is changed.
  • Wrappers would solve this problem.
  • Wrappers have a problem where the enclosed object may return a reference to itself for callback purposes or something along those lines.
  • Another thing is to write all the forwarding methods.

If you are letting a class be inherited by another:

  • Design and document the inheritance.
  • Provide hooks to inernal behaviour to improve the ease and performance of the subclass reimplementation.
  • Constructors must not utilize overridable methods, directly or indirectly.
  • Try not to implement Cloneable or Serializable.
  • If you do, make sure to treat clone and readObject like constructors (no overridable function calls)

Favour Interfaces over Abstract classes

  • Multiple interfaces can be implemented.
  • New interfaces can be easily implemented for existing classes without disturbing class heirarchies.
  • Allow for construction of non-heirarchical frameworks.
  • One pitfall is that Interfaces don't allow for extensions as adding a new method breaks all the existing classes that use the method.
  • Only design an abstract class if you think that evolution is more important.
  • Only use Interfaces to define types, and nothing else.

Favour static member classes over nonstatic

  • Each nonstatic member class instance is associated with the enclosing class.
  • When you construct the inner class, it will automatically associate with the outer class taking time and space in its construction.
  • There are local classes and anonymous classes as well, these depend on the usage of them. If it's a oneshot class, create anonymous, else create a local.

Single toplevel class in a single source file

  • May make compilation and runtime results dependent on the order in which you passed source files.

Generics

Use generics to constrain types wherever possible

  • Simple reason, compile time checks.

Valid use of raw type

if (o instanceof Set) {
    Set<?> s = (Set<?>) o;
}

Favor generic methods over raw

public static <E> Set<E> union(Set<E> s1, Set<E> s2) {
    Set<E> result = new HashSet<>(s1);
    result.addAll(s2);
    return result;
}

Recursive type bounds

// Using a recursive type bound to express mutual comparability
public static <E extends Comparable<E>> E max(Collection<E> c);

This indicates that E implements Comparable<E>, i.e., it can be compared to itself.

Bounded generics

// Wildcard type for a parameter that serves as an E producer
public void pushAll(Iterable<? extends E> src) {
    for (E e : src)
        push(e);
}
  • Anything that can safely be typecast to an E is expressed this way.
// Wildcard type for parameter that serves as an E consumer
public void popAll(Collection<? super E> dst) {
    while (!isEmpty())
        dst.add(pop());
}
  • That is because dst can be anything that can hold an E, which includes its parent types as well.

Wildcards should be invisible

The user of the API shouldn't be bothered with the presence of a wildcard. To him, it should be transparent.

Careful with varargs and generics

  • They don't mix well.
// Mixing generics and varargs can violate type safety!
static void dangerous(List<String>... stringLists) {
    List<Integer> intList = List.of(42);
    Object[] objects = stringLists;
    objects[0] = intList;
    // Heap pollution
    String s = stringLists[0].get(0); // ClassCastException
}
  • Don't expose the varargs array in any way, shape or form.
  • Only pass the parameter array to something that has @SafeVarargs annotation/does some gen compute on the array.

C programming to Java

Replace structs with Classes

Replace unions with class heirarchies

  • Have an abstract class from which the various subclasses derive.
  • Implement any function of the enum as a member function of the abstract class.

Replace enums with Java enums

  • Each implementation can contain different forms of the abstract method in the class.
  • EnumSet and EnumMap are optimized.
  • Overriding fromString

    // Implementing a fromString method on an enum type
    private static final Map<String, Operation> stringToEnum =
        Stream.of(values()).collect(toMap(Object::toString, e -> e));
    // Returns Operation for string, if any
    public static Optional<Operation> fromString(String symbol) {
        return Optional.ofNullable(stringToEnum.get(symbol));
    }
    
  • In a method, don't switch on this, instead overload every variant with a new type.

Use EnumSet instead of bitfields

  • Simply better, representation is about the same.

Replace enums with typesafe enums

  • Only to be done if extensions are needed in enums.
class Foo {
    private final String name;
    private Foo() {}

    private Foo(String s) {
        name = s;
    }

    public String toString() {
        return name;
    }

    public static final Suit CLUBS = new Suit("clubs");
    public static final Suit DIAMONDS = new Suit("diamonds");
    public static final Suit HEARTS = new Suit("hearts");
    public static final Suit SPADES = new Suit("spades");
}
  • You can also have a certain behaviour attached to the enum variants.
public abstract class Operation {
    private final String name;
    Operation(String name) { this.name = name; }
    public String toString() { return this.name; }
    // Perform arithmetic operation represented by this constant
    abstract double eval(double x, double y);
    public static final Operation PLUS = new Operation("+") {
            double eval(double x, double y) { return x + y; }
        };
    public static final Operation MINUS = new Operation("-") {
            double eval(double x, double y) { return x - y; }
        };
    public static final Operation TIMES = new Operation("*") {
            double eval(double x, double y) { return x * y; }
        };
    public static final Operation DIV = new Operation("/") {
            double eval(double x, double y) { return x / y; }
        };
}

Functional Programming

Prefer Lambdas to anonymous classes

  • Omit types unless they make it more readable.
  • If you can, use method references, unless they are annoying.

Use standard functional interfaces > purpose built one

  • Easier on the API.
  • If what you need is a bit too funky, write your own.

Use streams judiciously

  • This is not functional programming; these OOPS normies can't handle long pipelines of code.
  • Ok their arguments are kinda fair ngl.
  • Use side-effect free functions in streams.

try returning collections

  • The only problem is the size limit on them.
  • Storage might also be a concern.

Don't parallelize streams indiscriminately

  • These work on heuristics to parallelize stuff.
  • Best to be done only when it's a stream over array based stuff (HashSet, ArrayList etc).

Methods

Document the restrictions that are placed on parameters

  • @throws is helpful to tell when an exception is thrown wrt what condition.
  • Throw early and throw fast.
  • Might be elided if the implicit computation is going throw the exception anyways.

Make defensive copies

  • If you're writing an immutable class that provides accessors, make sure that you make copies of every mutable input and every return.
  • Do not use clone for making copies of the input because of a malicious subclass being possible.

Return ZLAs, not null

  • Always return zero length arrays wherever possible.

Be careful about varargs

Use Optional instead of throws and null

  • Properties are similar to Rust.
  • Don't return Optional for primitive types, use the variants meant for those.

Gen best practices

Reduce number of parameters

  • No more than 3 preferably.

Reduce local variables

  • No more than 5 preferably.

Minimize scope of local variables

  • Especially true for loop variables, as we tend to reuse the same names for them.

Use loop idioms to minimize compute costs

for (int i = 0, n = list.size(); i < n; i++) {
    do_something(i);
}

Use libs instead of your own funky code

Avoid floats and doubles if precision is the key

String concatenation is quadratic in nature

  • So much for string interning eh?
  • If repetitive addition is gonna happen, use StringBuffer

Use interfaces to describe objects

  • allows you far greater freedom in swapping out implementations if needed.
  • If relevant interfaces don't exist, then use the base abstract class to describe it.

Use interfaces over reflection

  • I don't understand who in the world needed reflection so badly and why people thought it was a good idea.

No premature optimization

screenshot-2025-05-23-05-59-46.png

Exceptions

Use exceptions for what they're meant for

Types of exceptions

  • Checked exceptions are those caught using try-catch or declared in the method signature.
  • Throw checked exceptions if the caller can actually recover from it.
  • Unchecked exceptions are those that are, well, not checked.
  • These are thrown when there are no checks to guard them.
  • Inside unchecked exceptions, errors are thrown when there's a resource deficiency by JVM.
  • Runtime exceptions are thrown in every other scenario.

Use standard exceptions

Throw exceptions for what they mean

Document the ones that are thrown by each method

Failure atomicity

In case of failure, leave the object in the state it was before the failure.

Threads

Synchronize access to shared mutable data

  • Even if the data is atomic, it may be read partially.
  • If you want to lazy initialize, create a nested class with a static final and use that. That's the best method.

Keep synchronized space small

  • Lower cost
  • Lower chances of error

Don't call alien methods in synchronized

  • Basically, methods you know nothing about (because they can be overridden).
  • This can easily lead to deadlocks and leaving the object in an inconsistent state.

Call wait always in a loop

  • Same reason why you call semaphore checks in a loop in C.
  • standard idiom:

    synchronized (obj) {
        while (!cond) obj.wait();
    }
    
  • Before wait, it is to make sure that notify hasn't been called, else the thread may never wake up.
  • After wait, it does safety check of the condition not holding.
  • Conservatively, use notifyAll wherever possible as the while guard will protect threads not meant to wake up.

Don't depend on the thread scheduler

Document thread safety of the object

Use executors, tasks, and streams

  • Significantly better abstractions than rawdogging threads.

Be careful about implementing serializable

Don't accept the default

  • Everything gets serialized, including the private and package private fields, and you'd have to support them forever.
  • Classes meant for inheritance shouldn't implement it and interfaces should rarely extend it.
  • Inner classes should not implement serializable unless they are static since they contain compiler generated fields to refer to the outer class.

Find alternatives

  • Yeah, third edition basically gave up on the whole idea.

Other posts
Creative Commons License
This website by Md Isfarul Haque is licensed under a Creative Commons Attribution-ShareAlike 3.0 Unported License.