Introducing Classes

I won't be diving deep into OO concepts (there are numerous books on the subject that are far better explaining the OO concepts than me) but I will cover the basic OO concepts and any rules that you need to know

Object Oriented Programming (OOP) encapsulates data (attributes) and methods (behaviors) into objects, the data and methods are intimately tied together, implementation details are hidden within the objects themselves. In Java the unit of programming is the Class from which objects are eventually instantiated. Methods and data are encapsulated within the walls of the class. A class contains the data and the methods that access the data contained within the class.

When you run a Java program the JVM looks for a method called main(), this is the entrypoint from where the program starts to execute, its possible to have multiple main() methods but they must be overloaded, something I will discuss in another section.

I have a separate section on discussing inner classes, which covers Inner Class, Method-Local Inner Class, Anonymous Inner Class, Static Nested Class

Structure of a Class

A Class is a blueprint on which an Object is created, Objects are constructed (basically live in memory of the computer/server), you cannot make a new object without invoking a constructor (Java will proivde one even if you don't). Constructors are code that runs whenever you use the keyword new. Constructors cannot specify a return type or return values.

Every class even abstract classes must have a constructor, if you don't specify a constructor the Object class will provide a default one and as every object inheriates class Object, you will regardless get at least a default constructor. Interfaces do not have a constructor you cannot instantiate a interface.

Classes have the following rules

There are two types of modifiers that can be used for class declaration

Every class you declare has an access control, whether you explicity type one or not. When a class has access to another class it means he can do one of 3 things

Access means visibility, if you do not have access to a class you cannot use any methods or variables of that class.

Class Access Modifiers
Default Access When no modifier is present then default access is applied, think of default access as package access, a class with default access can be seen only by classes within the same package
Public Access When declared with public access, it gives all classes from all packages access to the public class, although you still need to import the class
Class non-Access Modifers
Final Classes When used with classes this means that the class cannot be subclassed (cannot extend). Many of classes in the Java library are final classes.
Abstract Classes

An abstract class can never be instantiated, its sole purpose is to be extended (subclassed). If a single method in a class is abstract the whole class must be declared abstract. You can put non-abstract methods in a abstract class (also known as a concrete method), you will be unable to override the non-abstract method. An abstract class can extend another abstract class.

You cannot mark a class as both abstract and final as they mean the opposite, you will get a compiler error (illegal combination of modifiers).

The first concrete class to extend an abstract class must implement all abstract methods.

Abstract classes can have constructors.

Strictfp Classes The effect of strictfp is to restrict floating-point calculations to ensure portability across platforms

Below are some example of class declarations

Class Declarations
public class Test
public class Test extends SuperTest
public final class Test
public abstract class Test extends SuperTest                     // you can only extend one superclass
public class Test implements AInterface
public class Test implements AInterface, BIterface               // you can implement many interfaces
public class Test extends SupperTest implements AInterface

Class initializer and code block ordering, one note to make is the ordering of variables and if they are used in the blocks, make sure they are initialized before the block otherwise you get a illegal forward reference error

Class initializer and ordering
public class ClassTest1 {

  {
    // this wont work as class is not initialized, entry point is the main method
    System.out.println("Hello from ClassTest1");
  }

  public static void main(String[] args) {

    System.out.println("ClassTest1: NewClass static x: " + NewClass.x);   // we can access the static variable x, even though we have not created NewClass object

    NewClass nc1 = new NewClass();

    System.out.println("ClassTest1:  nc1 A: " + nc1.a);
    System.out.println("ClassTest1 has now finished!!!!");
  }
}

class NewClass {

  static int x = 101;
  int a = 5;
  int b;

  // Initializer block
  { System.out.println("  NewClass (initializer) A: " + a); }

  // Initializer block
  { System.out.println("  NewClass (initializer) B: " + b); }

  // Initializer block
  { System.out.println("  NewClass (initializer) X: " + x + "\n"); }
  
  // static blocks are always called first, when the class gets loaded
  static { System.out.println("  NewClass static block"); }

  // Constructor
  public NewClass() {

    this.b = 10;            // we can change the value of b using this keyword, b is part of the object
    x++;                    // we can change the value of x without new keyword, you dont need to initialize object to get x, its static

    System.out.println("  NewClass (constructor) A: " + this.a);
    System.out.println("  NewClass (constructor) B: " + this.b);
    System.out.println("  NewClass (constructor) Static X: " + this.x);
  }
}

NOTE: output would be below:

  NewClass static block
ClassTest1: NewClass static x: 101
  NewClass (initializer) A: 5
  NewClass (initializer) B: 0
  NewClass (initializer) X: 101

  NewClass (constructor) A: 5
  NewClass (constructor) B: 10
  NewClass (constructor) Static X: 102
ClassTest1:  nc1 A: 5
ClassTest1 has now finished!!!!
Illegal forward reference error
class Test1 {
    
    static {
        System.out.println("A: " + a);          // You are using variable a before its been referenced
    }
    
    static int a = 5;
}

Below are some example on declarations and instantiations, the new operator dynamically allocates memory and returns a reference to the memory location (it's address in memory).

Declarations and Instantiations
Object a = new Object();                                 // The default object constructor will be used

Object a = new Object(), Object b = new Object;          // you can create multiple Objects on oneline

String s = new String("Paul"), Object a = new Object();  // ERROR: You cannot declare different types on oneline
        

Constructors and Instantiation

As I mentioned at the start Objects are constructed, you cannot make a new Object without invoking a constructor.

Constructors have the following rules

Below is are some examples of a simple constructors

Example Constructor
class Test {
  Test() { ... }          // The constructor for the Test class
}

Note: there is no return type and the constructors name must match exactly the class name
Example constructor using varargs
public class Test2 {

    public static void main(String[] args) {

        Test2 t1 = new Test2();
        t1.test(1, 2);
        t1.test(1, 2, 3);
        t1.test(1, 2, 3,4 ,5 ,6, 7, 8, 9, 10);
    }
}

class Test2 {

    void Test2(int a, int... paulArgs) {                                        // you can name the varargs anything senible and it must be the last argument

        System.out.println("test.constructor normal variable a: " +  a);

        System.out.print("test.constructor with varargs: ");

        for(int i: paulArgs) {                                                  // you can loop through the args like an array
            System.out.print(i + " ");
        }
        System.out.println("\n");
    }
}

Constructors are normally used to initialize instance variables, I talk about the this keyword later

Initialize instance variables using the constructor
class Test {
  int size;
  String name;
  
  Test(String name, int size) {
    this.name = name;                    // the this keyword refers to the classes instance variables, see below for the this keyword 
    this.size = size
  }
}

Note: see below for more information on this 

When you invoke a constructor using the new keyword you can create a constructor chain

Constructor Chaining

What happens when you create a new object in this case Horse extends Animal

Horse h = new Horse();

  1. Horse constructor is invoked
  2. Animal constructor is invoked (Animal is superclass of Horse)
  3. Object constructor in invoked (Object is the ultimate superclass of all classes)
  4. Object instance variables are given their explicit values (if any)
  5. Object constructor completes
  6. Animal instance variables are given their explicit values (if any)
  7. Animal constructor completes
  8. Horse instance variables are given their explicit values (if any)
  9. Horse constructor completes
4. Object()
3. Animal() calls super() - Objects constructor
2. Horse() calls super() - Animals constructor
1. Main() calls new Horse () - Horses constructor

The below tables details what the compiler will generate for your class regardless if you have typed it or not.

Class Code (What you type) Compiler-Generated Constructor Code (in Bold type)
class Foo { } class Foo {
   Foo() {
      super();
   }
}
class Foo {
   Foo() { ... }
}
class Foo {
   Foo() {
      super();
   }
}
public class Foo { } class Foo {
   public Foo() {
      super();
   }
}
class Foo {
   Foo(String s) { ... }
}
class Foo {
   Foo(String s) {
      super();
 
  }
}
class Foo {
   Foo(String s) {
      super();
   }
}
Nothing - compiler does not need to insert anything
class Foo {
   void Foo() { ... }
}
class Foo {
   void Foo() { ... }

   Foo(){
      super();
   }
}

Note: void Foo is a method because of the return type

You can overload constructors just the same as methods, I will discuss overloading in the methods section

Constructor overloading class Foo {
   Foo() { ... }
   Foo(String s) { ... }
   Foo(String s, int t) { ... }
}

this Keyword

The this keyword has the following meaning

access attributes from an Object
String getname() { return this.name; }
call the no args constructor
this();
use inside a constructor
class Test {
  Test() {}	           // no args constructor
  
  Test(String a) {
	this();		   // this() must be on the first line of the constructor
	// do something
  }
}
assign a variable to this
Object o = this;
return an Object
Object returnObject() { return this; }

Garbage Collection

Java's garbage collector provides an automatic solution to memory management, in most cases it frees you from having to add memory management logic to your code, the only downside is that you have no control when it runs and when it doesn't.

The purpose of the garbage collector is to find and delete any objects that are no longer in use (Java calls them reachable) and free the memory so that it can be used again. The garbage collector is under the control of the JVM, only the JVM decides when it runs. You can only ask the JVM to run the garbage collector but it decides if it runs now of later, generally the JVM will run the garbage collector when it's low on memory.

Every Java program will at least run one thread (main()), but can have many threads running. A thread can be either alive or dead so an object is eligible for garbage collection when no live thread can access it. So when the JVM discovers an object that cannot be reached by any live thread it will consider that object eligible for deletion and it might even delete it at some point (it might never delete it).

So a reachable object is a object that is available to a live thread.

Here are some examples on how to make an object eligible for garbage collection

nulling a reference

public class GarbageTruck {
   public static void main(String [] args) {
      StringBuffer sb = new StringBuffer("Hello");

      // object sb is not eligible for garbage collection
      System.out.println(sb);

      // object is now available for garbage collection
      sb = null;
   }
}

Reassigning a Reference

public class GarbageTruck {
   public static void main(String [] args) {
      StringBuffer s1 = new StringBuffer("Hello");
      StringBuffer s2 = new StringBuffer("Goodbye");

      // object sb is not eligible for garbage collection
      System.out.println(s1);

      // Now the StringBuffer "Hello" is eligible for collection
      s1 = s2;
   }
}

Isolating a reference

public class Island {
   Island i;

   public static void main(String [] args) {
   
      Island i2 = new Island();
      Island i3 = new Island();
      Island i4 = new Island();

      i2.i = i3;
      i3.i = i4;
      i4.i = i2;

      i2 = null;
      i3 = null;
      i4 = null;

      // Once here i2, i3 and i4 are eligible for collection
   }
}

The first thing to remind you about is that you cannot force a garbage collection, you politely ask the JVM to collect the garbage but it may fall on deaf ears.

Asking to garbage collect import java.util.Date;

public class stringTest {
   public static void main(String[] args) {

      Runtime rt = Runtime.getRuntime();

      System.out.println("Total JVM memory: " + rt.totalMemory());
      System.out.println("Before JVM memory: " + rt.freeMemory());

      Date d = null;

      for (int i = 0; i < 10000000; i++) {
         d = new Date();
         d = null;
      }

      System.out.println("After JVM memory: " + rt.freeMemory());
      rt.gc();                                                    // politely ask to collect garbage
      // Runtime.getRuntime().gc();                               // other way to run the gc
      System.out.println("After GC memory: " + rt.freeMemory());
   }
}

There are a number of garbage collector options you can pass to the java command and the types of garage collector

Java GC Options
-Xlog:gc                 // Display the Garbage collector information
-verbose:gc              // Displays information about each garbage collection (GC) event (less info than above)
-XX:+UseSerialGC         // serial collector uses a single thread to perform the garbage collection
-XX:+UseParallelOldGC    // parallel collector uses multiple threads to collect the garbage
-XX:+UseG1GC             // server-type collector for multi-processor machines with large memory (default as of Java 11)
-XX:+UseZGC              // scalable low latency GC, performing all work concurrently without stopping other threads 
-XX:+UseConcMarkSweepGC  // concurrent GC, used for shorted GC pauses and could afford to share processor resources with GC (no longer used from Java 9) 

Java provides you a mechanism to run some code just before the object is deleted, this code is located in the method finalize() (inherited from class Object) this sounds like a good idea but you cannot count on the garbage collector to ever delete an object (so the finalize method might not run). It is recommended that you do not override the finalize() method at all, if you do overide it then it should be protected so that subclasses have access to the method.