Java fundamentals (Beyond basic)


Java editions

Java has 5 editions/platforms

  1. Java SE – Standard Edition
  2. Java EE – Enterprise level application
  3. Java FX – Java smart client application
  4. Java ME – Micro Edition
  5. Android

Android is the most popular phone platform using Java. However, Java Micro Edition still plays a really important role in smart devices and the internet of things (IoT). All of the editions are superset/subset of the Java Standard Edition (SE).

Java runtime

There can be multiple runtime environment of Java language. For instance, Androids provides a very different runtime than Oracle. But all those environments support effectively the same Java language.

JDK vs JRE

JRE (Java Runtime Environment) and JDK (Java Development Kit) have separate use cases. Low level language like C, when compiled produces executable file. In Java, we feed our source code to JDK and it produces platform independent Bytecode in *.class files.

JRE is installed in the host machine and knows how to execute the Bytecode in host environment. So JDK and JRE are platform dependent but help us to write and execute platform independent Java code.

It enough to have JRE to run a Java application. For developing Java application we need JDK.

Java package

Java requires no correlation between package names and source code file structure. But most IDE’s require sub-folder for each part of the package name.

More specifically, Java (JDK) doesn’t require correct structure to compile source files but does require it to execute class (bytecode) files. It’s less confusing to be consistent for both and that’s why IDEs do so.

package com.gnrcs.lab;

public class Main {
    public static void main (String... args) {
        System.out.println("Homo Sapiens");
    }
}

You can place the file Main.java anywhere you like and be able to produce Main.class file, using ~$ javac Main.java. But you need the classpath and folder structure to execute the Main.class file.

So instead using above command, use ~$ javac -d . Main.java. This will create the folder structure com/gnrcs/lab and place the Main.class file inside it.

Now you can run the class file, ~$ java -cp . com.gnrcs.lab.Main

When you use package and compile to a *.class file, remember to use the full class name including the package name. That means, class name becomes full package name (separted by dot) and the actual class name.

Naming convention

Java allows letters, numbers, $ and _ (underscore) in naming variable. Rule requires that the first character is not a digit.

These are valid variable names,

int $glb;
int _glb;
int $$glb;
int __glb;

Java primitive types

To declare a long variable, add ‘l’ or ‘L’ suffix. Similarly, for floating point variable add ‘f’ or ‘F’. We don’t need to add any suffix for double, but ‘d’ or ‘D’ can be added just to be explicit.

Java supports unicode, char uniCodeChar = '\u00CA';

Operator Precedence

Postfix: a++ (evaluates first, but increments later)
Prefix: ++a
Multiplicative: * / %
Additive: + –

Logical Operators

Logical operators: And (&), Or (|), Exclusive Or (^), Negation (!)
Conditional logical operators: And (&&), Or (||)

Conditional logical operators only execute the right side if needed to determine the result.

Initialization

Java provides 3 mechanisms to establish initial state:

  1. Field initializers
  2. Constructors
  3. Initialization blocks

Field receive a “zero” value by default. For example, char gets ‘\u0000’ value.
Once you write any constructors, Java won’t provide you the default constructor.

You can make the class public but keep the constructor private. User won’t be able to create an instance but can use the class as reference. It is more aplicable for the base classes.

Initialization block

Initialization block will run before all the constructors and for once per object creation.

public class Main {
    public static void main (String... args) {
        Flight f = new Flight();
        Flight f1 = new Flight(1);
        Flight f2 = new Flight(2, 3);
    }
}

class Flight {
    {
        System.out.println("Init block 1");
    }

    public Flight() {
        System.out.println("No arg");
    }

    public Flight(int p) {
        this();
        System.out.println("One arg");
    }

    {
        System.out.println("Init block 2");
    }

    public Flight(int p, int q) {
        this(p);
        System.out.println("Two arg");
    }

    {
        System.out.println("Init block 3");
    }

}

All the initializer block will execute in the order they appear.

Initialization order,

  1. Field initializer executes first
  2. Initialization block executes second
  3. Constructor block executes next

Parameter Immutability

Java always pass parameter by value. So you can’t write a simple swap function and swap the value of the parameters (whether they are primitive types or reference). Changes made to the passed value are not visible outsize of the function. If you pass the reference then you can make changes inside object, but swapping the referneces won’t have any effect outside.

Field hiding

Same field name gets overrided in subclass. But if you directly access the field using parent reference, you will get value from parent class.

public class Main {
    public static void main (String... args) {
        Flight f = new Flight();
        f.addS(); // 11 

        Flight f1 = new CargoFlight();
        System.out.println(f1.s); // 10
        System.out.println(f1.getS()); // 100
        f1.addS(); // 101

        CargoFlight f2 = new CargoFlight();
        f2.addS(); // 101
    }
}

class Flight {
    int s = 10;

    void addS() {
        s++;
        System.out.println(s);
    }

    int getS() { return s; }
}

class CargoFlight extends Flight {
    int s = 100;

    void addS() {
        s++;
        System.out.println(s);
    }

    int getS() { return s; }
}

equals method

By default, object references are only equal when referencing the same instance. We can override Object.equals to provide new behavior.

In equals method you should check instanceof first, because equals method takes Object as parameter.

Also you should check super.equals(). If true, then they are referring to the same object and thus return true;

@Override
public boolean equals (Object o) {
	if (super.equals(o)) return true;
	if (!(o instanceof Flight)) return false;
	Flight f = (Flight) o);
	return this.s == f.s;
}

Constructor

Child class can not implicitely inherit constructor of parent class. It has to define it’s own version of constructor and may call parent class constructor. But calling the parent constructor super(); has to be the first line.

String Equality

The following code is supposed to print “not same”,

public class Main {
    public static void main (String... args) {
        String s = "Hello";
        String s1 = "Hello";

        if (s == s1) {
            System.out.println("same");
        } else {
            System.out.println("not same");
        }
    }
}

Becasue, s and s1 are not pointing to same object, right?

Wrong! They are pointing to same object, created in String pool.

But we know String.equals() will work, becasue it will compare char by char. However, for a large string it can be very expensive operation.

To utilize the reference checking in String.equals() we can use intern().

When the intern() method is invoked on a String object it looks the string contained by this String object in the pool, if the string is found there then the string from the pool is returned. Otherwise, this String object is added to the pool and a reference to this String object is returned. (ref: https://dzone.com/articles/string-interning-what-why-and)

So whenever we call intern() we will get a reference of that string object in a pool. We can easily take advantage of reference equality to test if the strings are equal.

public class Main {
    public static void main (String... args) {
        String s = new String("Any String");
        String s1 = new String("Any String");

        String s2 = s.intern();
        String s3 = s1.intern();

        if (s == s1) {
            System.out.println("same");
        } else {
            System.out.println("not same");
        }
    }
}

Of course there is an overhead to interning a string. So in general, for few string comparisons use equals(). But, when you have to do a lot of comparisons to a given set of string values, that’s where the intern method comes in.

Primitive wrapper classes

Few notes:

  • All numeric wrapper classes extend from Number class
  • All wrapper class instances are immutable
  • Primitive to wrapper (valueOf) known as Boxing
  • Wrapper to primitive (xxxValue) known as Unboxing
  • Parse methods convert string to primitive types
  • ValueOf methods convert string to wrapper types

Benefits of wrapper classes:

  • We can add Integer in an Object type array
  • null value can be assigned in Integer type reference, denoting absence of value, instead of 0 or -1
  • Classes have static members like MIN_VALUE, MAX_VALUE
  • Convenient methods like, toBinaryString(), isDigit(), isInfinite(), isNan()

Demerits of wraper classes:

  • We can’t compare to wrapper classes using ==, as it will compare referneces, not values
  • Boxing and unboxing equality check dilemma

Consider the following code,

public class Main {
    public static void main (String... args) {
        Integer i1 = 1000;
        Integer i2 = 1000;

        System.out.println(i1 == i2 ? "same" : "not same"); // not same 

        Integer i3 = 100;
        Integer i4 = 100;

        System.out.println(i3 == i4 ? "same" : "not same"); // same
    }
}

Remember, == returns true when both references point to exact same object. Now, certain boxing conversions are defined to always return back a reference to the exact same instance. This happens when values are in the range (-128 to 127) for int, short and byte. When these values are converted to corresponding wrapper class, you will get exact same instance.

For char type, the range is ‘\u0000’ to ‘\u00ff’.

For boolean, it’s applicable for all it’s values (True and False)

Exceptions

Checked exception and Unchecked exception

exception_classes

Checked and Unchecked Exception

Ref: https://www3.ntu.edu.sg/home/ehchua/programming/java/J5a_ExceptionAssert.html

Important
The throws clause of an overriding method must be compatible with throws clause of the overridden method.

If you want to provide a specific exception, always include the originating exception so that you don’t loose information. All exception classes support initCause method. Many exception class provide a constructor that accepts the originating exception.

Final fields

A simple final field must be set during creation of an object instance. That means

  1. In field initializer
  2. In initialization block
  3. In constructor

Jar file: IntelliJIDEA

File -> Project Structure -> Artifacts -> Click (+) -> JAR -> From modules with dependencies -> Choose Main class

Build -> Build Artifacts -> Action (Build)

You will find the jar file in: Project -> out -> artifacts -> Project name -> jar file.

Run it: java -jar FILE_NAME.jar

Anonymous class

An anonymous class use the constructors of its parent.

new ArrayList(10) {}