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.


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.

Main method

A class can define multiple overloaded main methods. The following code will compile,

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

    public static void main(int a, String... args) {}

    private static void main() {}

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 anywhere you like and be able to produce Main.class file, using ~$ javac But you need the classpath and folder structure to execute the Main.class file.

So instead using above command, use ~$ javac -d . 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.


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) {
        System.out.println("One arg");

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

    public Flight(int p, int q) {
        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() {

    int getS() { return s; }

class CargoFlight extends Flight {
    int s = 100;

    void addS() {

    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;

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;


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) {
        } 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:

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) {
        } 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)


Checked exception and Unchecked exception

Checked and Unchecked Exception


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.


If there are multiple exceptions occur, Java keeps track of those exceptions as suppressed exceptions.

for (Throwable t : e.getSuppressed()) {


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) {}

Java stream

Stream represents an ordered sequence of data. Streams are unidirectional. InputStream classes are used for reading binary data and Reader classes are used for reading text.

InputStream input = // create input stream
int data;

while((data = >= 0) {
	byte byteData = (byte) data;

InputStreamReader allows to create a reader over an InputStream. FileReader class inherits from InputStreamReader


Usually streams are backed by physical storage and exist outside JVM, so Java can’t reliably clean up or release the resources. We should call the close() by ourselves. However before calling it we have to make sure the stream was opened successfully, otherwise calling close() could throw exception.

There is another way called AutoClosable interface. It is actually base interface of Closeable interface. It provides support for try-with-resources. Remember, a resource has to implement AutoClosable interface. Java will take care of closing the resource.

try (Reader reader = /* open reader stream */ ) {

} catch (IOException e) {

} // No need for the finally block which close the resource

try-with-resources can declare multiple resources. Moreover, if there is a stream chain, then it will close all the lower level streams.

Note that, if there is any Exception then close() will not be called in try-with-resources.

Buffered streams

Direct file access multiple times could be inefficient. Buffered strams can improve efficiency. It buffers content in memory and performs read/write in large chunks and thus reduces interaction with underlying streams.

Buffered streams have line break support across multiple platforms. BufferedWriter.newLine() generates appropriate line break based on the platform.

Java IO vs NIO

The following classes from are deprecated,

Java NIO provides better exception reporting and greater scalability with large files and more file system feature support. It also simplifies a number of common tasks.

In Java IO package we created a FileReader or FileWriter. In NIO package it’s more compact and you can say I need a BufferedReader over a file.

try (BufferedReader br = Files.newBufferedReader(Paths.get("path"))) {
	String line;
	while ((line = br.readLine()) != null) {


File System

Besides default file system, Java also supports specialized file system, like Zip file. Path instances are tied to a file system. We can use FileSystem which can create Path instances within a file system.

Contents of the Zip file is a separate file system namely, Zip file system. Zip file system uses jar:file scheme.

try (FileSystem zipFs = openZip(Paths.get(""))) { // creating zip file in default file system
	writeToFileInZip(zipFs, data);
	writeToFileInZip2(zipFs, data);
} catch (Exception e) {


private static FileSystem openZip(Path path) throws IOException, URISyntaxException {
	Map<String, String> providerProps = new HashMap<>();
	providerProps.put("create", "true");

	URI zipUri = new URI("jar:file", path.toUri().getPath() /*Fully qualified path*/, null);
	FileSystem ret = FileSystems.newFileSystem(zipUri, providerProps);

	return ret;

private static void copyToZip(FileSystem zipFs) throws IOException {
	Path sourceFile = Paths.get("file.txt"); // same as, FileSystems.getDefault().getPath("file.txt");
	Path destFile = zipFs.getPath("/fileCopied.txt"); // can be same as source file name
	Files.copy(sourceFile, destFile, StandardCopyOption.REPLACE_EXISTING);

// open up file, write the contents, putting the new lines and closing it
private static void writeToFileInZip(FileSystem zipFs, String [] data) throws IOException {
	Files.write(zipFs.getPath("/newFile.txt"), Arrays.asList(data), Charset.defaultCharset(), StandardOption.CREATE);

// old ways, same as previous method
private static vodi writeToFileInZip2(FileSystem zipFs, String [] data) throws IOException {
	try (BufferedWriter writer = Files.newBufferedWriter(zipFs.getPath("/newFile2.txt"))) {
		for (String d : data) {


StringJoiner sj = new StringJoiner("], [", "[", "]");
sj.toString(); // [This], [is], [Java]

String format

If you print an object with %s then,
If the class implements Formatable, the format() will be called, otherwise toString() will be called.

Format flag: Radix #

String.format("%o", 32); // 40
String.format("%#o", 32); // 040

Format flat: ,

String.format("%,d", 1234567); // 1,234,567
String.format("%,.2f", 1234567f); // 1,234,567.00

Format flat: space, +, (

String.format("% d", 123); //   123
String.format("% d", -123); // -123
String.format("%+d", 123); //  +123
String.format("%+d", -123); // -123
String.format("%(d", 123); //   123
String.format("%(d", -123); // (123)

Argument index

String.format("%$3d %$2d %$1d", 1, 2, 3); // 3 1 2
String.format("%$2d %< 04d %$1d", 1, 2, 3); // 2, 0002, 1


Few notable facts:

  • Map collections do not implement collection interface.
  • Collection interface extends Iterable interface
  • retainAll() removes all elements not contained in another collection
  • remove(object) calls equals() to check equality. It will remove the first object in the list that will return true on equals().

We can use lambda in collections. For example, removeIf() takes a particular type of lambda expression called a predicte, which resolve to either true or false

list.forEach(m -> System.out.println(m.getValue()));
list.removeIf(m -> m.getValue().equals("abc"));
map.replaceAll((k, v) -> v.toUpperCase());

Retrieve array

Object [] objArray = list.toArray();
String [] strArray = list.toArray(new String[0]); // typed array

Common collection interfaces and classes

Interface Class
List ArrayList
Queue LinkedList
Set HashSet
SortedSet TreeSet


  • headMap() in SortedMap, return a map for all keys that are less than the specified keys (exclusive).
  • tailMap() return a map of all keys that are greater than or equal to the specified keys (incluside)
  • subMap() return a map of all keys that are greater than or equal to the starting key and less than the ending key


We can use Properties to persist values that exist beyond application execution. Properties inherits HashTable


  • Properties can be written to & read from a stream
  • You can include comments (optionally)
  • Supports text and XML format.

Store values

Properties props = new Properties();
props.setProperty("FirstName", "Visual");
props.setProperty("LastName", "Code");
props.setProperty("MiddleName", "Studio");

try (Writer writer = Files.newBufferedWriter(Paths.get(""))) {, "Optional comment");
} catch (Exception e) {


This will create a file named

#Optional comment
#Wed Sep 06 20:07:59 BDT 2017

Load values

Properties props = new Properties();

try (Reader reader = Files.newBufferedReader(Paths.get(""))) {

    String firsName = props.getProperty("FirtsName");
    String middleName = props.getProperty("MiddleName");
    String lastName = props.getProperty("LastName");
} catch (Exception e) {


Default properties

Default properties take precedent over default value passed to getProperty()

Properties defaults = new Properties();
defaults.setProperty("val", 1);

Properties props = new Properties(defaults);
String val = props.getProperty("val"); // if not present then default 1 will be returned

Default properties from XML file

try {
    Properties defaultProps = new Properties();
    try(InputStream inputStream = Main.class.getResourceAsStream("defaults.xml")) {
    Properties userProps = new Properties(defaultProps);

} catch (IOException e) {
    System.out.println("Exception <" + e.getClass().getSimpleName() + "> " + e.getMessage());

private static Properties loadUserProps(Properties userProps) throws IOException{
    Path prefs = Paths.get("prefs.xml");
    if(Files.exists(prefs)) {
        try(InputStream inputStream = Files.newInputStream(prefs)) {

    return userProps;

Class loading

By default Java searches current directory. Classes has to be present inside the correct directory sturcture, based on the package structure. However, if you specify search path then currect directly will no longer be searched by default, you have to add it explicitly.

Path can be set in CLASSPATH variable and it will be available in whole environment.
Otherwise, you can specify path on per execution basis.

To reference classes in *.class files, you have specify the path to folder that containing the package root.
In case of *.jar files, specify the path to jar file including the jar file name.

For example, in windows system, you can specify
java -cp \libdir;\anotherdir com.glab.Firstapp.Main
In case on unix system,
java -cp /libdir:/anotherdir com.glab.Firstapp.Main
In case of jar file,
java -cp /libdir/hello.jar:/anotherdir com.glab.Firstapp.Main

System properties

You can ask Java some limited system properties in which the application is running. For example,


The above code outputs the following in my case,
Oracle Corporation

In addition to that we can also access environment variables.

System.out.println(System.getenv()); // print all environment variables
System.out.println(System.getenv("LOGNAME")); // print specific key variables

The above code outputs the following in my case,
... { lots of variable }


static Logger logger = LogManager.getLogManager().getLogger(Logger.GLOBAL_LOGGER_NAME);
public static void main(String... method) {
    logger.log(Level.INFO, "Info log");

The output will contain timestamp, calling class name and calling method name,

Sep 07, 2017 5:38:08 PM Main main
INFO: Info log

Precise log method

Standard log method infer calling info and sometimes get it wrong and thus prints wrong class or method name. We can use precise log method to specify the class and method name.

Precise log has two special methods, entering() and exiting() and typically used at the beginning and end of a method. Both of these are logged as Level.Finer

logger.entering("com.glab.firstApp", "mainMethod");
logger.logp(Level.Warning, "com.glab.firstApp", "mainMethod", "Hello world log");
logger.exiting("com.glab.firstApp", "mainMethod");

Parameterized log

logger.log(Level.INFO, "Hello {0}, there, How {1} you?", new Object[] {"World!", "are"});

It works a bit differently for special methods entering() and exiting()

void mainMethod(String a, String b) {
	logger.entering("com.glab.FirstApp", "mainMethod", new Object[] {a, b});
	String ret = "return value";
	logger.exiting("com.glab.FirstApp", "mainMethod", ret);

Core log components

Log consists of 3 core components:

  • Logger
    • Accepts app calls
  • Handler
    • Publishes logging information
    • A logger can have multiple handler
  • Formatter
    • Formats log info for publication
    • Each handler can have one formatter

Using multiple handler, a logger can publish same log into multiple destination. For example, in console and in log file. Handler can be set more restrictive log level than the logger.

Java has several built-in handlers and formatters. The most common formatter is SimpleFormatter and we can set custom formatting in it. Another common formatter is XMLFormatter.

Built-in Handlers

  • ConsoleHandler
    • Writes to System.err
  • StreamHandler
    • Writes to specified OutputStream
  • SocketHandler
    • Writes to a network socket
  • FileHandler
    • Writes to one or multiple files

FileHandler substitution pattern

  • %t/foo.log (temp directory)
    • Unix: var/tmp/foo.log
    • Win: C:\Users\Shahab\AppData\Loca\Temp\foo.log
  • %h/foo.log (home directory)
  • %g/foo.log (rotating log generation)
    • foo_0.log, foo_1.log, foo_2.log and get back to foo_0.log
FileHandler h = new FileHandler("%h/foo_%g.log", 1000 /*1000 bytes limit*/, 4 /*rotating set*/);

Logging config file

We can use log config file to configure our logging mechanism.

java.util.logging.ConsoleHandler.level = ALL
java.util.logging.ConsoleHandler.formatter = java.util.logging.SimpleFormatter
com.glab.handlers = java.util.logging.ConsoleHandler
com.glab.level = ALL
java.util.logging.SimpleFormatter.format = %5$s, %2$s, %4$s%n

Then load this config file like the following,

java com.glab.firstApp.Main

Logger naming

Logger naming implies a parent-child relationship. LogManager links Logger in a hierarchy based on each logger’s name. For example, if we create 3 loggers,

static Logger logger1 = Logger.getLogger("com.glab.firstApp");
static Logger logger2 = Logger.getLogger("com.glab.secondApp");
static Logger loggerParent = Logger.getLogger("com.glab");

It means, loggerParent is parent of the other two logger, logger1 and logger2. If you call log on logger1 then it will be handled by logger1 handler but then the information will pass to parent loggerParent and again the information will be handled by loggerParent handler.

For such hierarchy, you will get lots of duplicated logs. To make it use effectively follow the rules,

  • Manage setup primarily on parent
  • Manage log calls primarily on children
  • Logger do not require their level to be set, it’s log level can be null, in that case it will inherit parent level.
    • If we need some detailed information we can set level to children loggers
  • Logger does not require handler, in that case it won’t log anything, but logger does pass the information to parent logger
  • Primarily add handlers to upper parents, so only important logs will be written
    • If we need some detailed information we can add handlers to children loggers
public class Main {
	static Logger pkgLogger = Logger.getLogger("com.glab");
	static Logger logger = Logger.getLogger("com.glab.firstApp");

	public static void main(String... args) {
		logger.entering("com.glab.firstApp", "Main");
		logger.log(Level.INFO, "log information");
		logger.exiting("com.glab.firstApp", "Main");

Now we will set the config in file.

com.glab.handlers = java.util.logging.ConsoleHandler
com.glab.level = INFO

We will get only log information in this case. Now another config,

com.glab.handlers = java.util.logging.ConsoleHandler
com.glab.level = INFO

java.util.logging.FileHandler.level = ALL
java.util.logging.FileHandler.pattern = ./main_%g.log
com.glab.firstApp.handlers = java.util.logging.FileHandler

Now we will get the same log log information but in two different places. Once in console and another in a file.

com.glab.handlers = java.util.logging.ConsoleHandler
com.glab.level = INFO

java.util.logging.FileHandler.level = ALL
java.util.logging.FileHandler.pattern = ./main_%g.log
com.glab.firstApp.handlers = java.util.logging.FileHandler
com.glab.firstApp.level = ALL

Now we will get all 3 logs in file, but only one log in console.


Multithreading enables a more complete usage of CPU resources. To use multithreading you must break your problem in parts. First we will look into two concepts:

Runnable interface

Runnable interface represents some tasks to be run on a thread.

Thread class

Represents a thread of execution and allows you to interact with that thread and affect the thread state.

If we create multiple threads from main thread and delegate all tasks to other threads then the main thread has nothing to do and it terminates. Once the main thread terminates the entire process get terminated. We have to make sure the main thread waits for other threads to finish up their work.

for (Thread t : threds) {
	t.join(); // main thread is waiting for child threads to complete execution 


Thread pool

There are two core types of thread pools. One is ExecutorService interface, it models thread pool behavior and the other is Executor class, it provides method to create thread pools and enables us to create dynamically sized thread pools, put a limit on number of threads and schedule tasks for later.

ExecutorService es = Executors.newFixedThreadPool(5); 

for (int i = 0; i < 100; i++) {
	es.submit(new Runnable () {});

try {
	es.shutdown(); // will not take any new tasks 
	es.awaitTermination(60, TimeUnit.SECONDS); // will wait 60 seconds to finish the existing tasks 

Thread callback

Callable interface represents a task to be run on a thread. It can return a result or throw an exception. It works combinedly with Future> interface, which wraps the result of a thread task

class Test implements Callable<String> {
	public String call() throws Exception {
		return result;

Let’s see how to use it wil Future interface,

ExecutorService es = Executors.newFixedThreadPool(5);
Future<String>[] results = new Future [100];
for (int i = 0; i < 100; i++) {
	results [i] = es.submit(new Runnable () {});

for (Future<String> result : results) {
	try {
		String val = result.get(); // get the returned value 
	} catch (ExecutionException e) {
		Throwable ex = e.getCause(); // Actual exception occurred 
	} catch (Exception e) { 


Synchronized method

Synchronization managed per instance basis. That means, if one thread is working in a synchronized method, then no other thread can access any other synchronized methods of that instance. However, synchronize methods have significant overhead.

Constructors are never synchronized. A given object instance always created on exactly one thread.

So whenever one thread gets acess of a synchronized method, it checks if it is locked. Every object instance has one lock space on it.

Synchronized statement block

It provides flexibility and enables you to call non-thread safe method and also you can call a method synchronizedly or not based on condition.

synchronized (instance) {;

Even if all the methods are synchronized, there are still cases where multli-threaded environment can cause issues.

For example, let deposit(), getBalance() and withdraw() are all synchronized methods.

if (account.getBalance() > 100) {
	double newBalance = (account.getBalance() - 50) * .1;

Thread 1, after entering the if condition, Thread 2 calls the withdraw() method. So the getBalance() on line 3 will get less balance than expected.

To resolve this case, we can use synchronized statement block.

synchronized (account) {
	if (account.getBalance() > 100) {
		double newBalance = (account.getBalance() - 50) * .1;

Blocking collections

Java provides thread-safe wrapper for collections. So we can create list, maps etc as we normally do and pass those to the Collections class static methods and get back synchronized list or map.

To coordinate producers and consumers mechanism, Java provides blocking queues, LinkedBlockingQueue and PriorityBlockingQueue

java.util.concurrent.atomic package has atomic integer, atomic Boolean etc and the set and get are thread-safe, but there are also more advanced things like getAndAdd() that get the current value and then add some value to it, and that happens as one atomic operation.


Reflection capables us to examine types at runtime and dynamically execute plus access members when we don’t know the type at compile time or there’s no type-specific source code. Modern IDEs have the capability to show declarations even without the source code.

Every type has a corresponding instance of Class class. This instance describes that data type in detail. Java models the information of a type with the class instance. Some members of Class instances –

  • simpleName
  • fields
  • constructors
  • methods

Every type has a member that can be used to reference the class instance. That means, if we can have a reference of any object, even if we don’t know the type, we can get the Class class instance that describes it, we can find out things about the type.

How to obtain the reference of Class class instance,

Class <?> cls = obj.getClass();
Class <?> cls = Class.forName("com.glab.User");
Class <?> cls = User.class;

As we don’t know the type, we are using . We could define it like,

Class <User> cls = User.class;

Let’s look at a method that will print the class name from the class instance,

void showClassName(Class <?> cls) {

Access type information

void classInformation(Object obj) {
	Class<?> cls = obj.getClass();
	Class<?> superCls = cls.getSuperClass();

	Class<?> [] interfaces = cls.getInterfaces();

	int modifiers = cls.getModifiers();

	if ((modifiers & Modifier.FINAL)) > 0) {}

	if (Modifier.isFinal(modifiers)) {}
	if (Modifiers.isPrivate(modifiers)) {}
	if (Modifiers.isProtected(modifiers)) {}


Java provides specific classes for each kind of member to interact with. Like, Field, Method, Constructor

We can access all the private, protected and public members using class instance and only public inherited members.

Constructors are never inherited. It means, getConstructors only shows the public constructors of the current type, not the super type. Whereas, getDeclaredConstructors returns all constructors, public, protected and private

Field [] fields = cls.getFields(); // public fields of current and super type
fields = cls.getDeclaredFields(); // all fields of current type 

Method [] methods = cls.getMethods(); // public methods of current all all super type
methods = cls.getDeclaredMethods(); // all methods of current type 

for (Method m : methods) {
	if (m.getDeclaringClass() != Object.class) {
		// we won't get the public methods of Object class 

Safe to compare with != because there’s exactly one instace of class describing each type.

Member access

Calling method,

Class <?> cls = obj.getClass();
Method m = cls.getMethod("methodName");
Object result = m.invoke(obj);

// with param
Method m = cls.getMethod("methodName", int.class);
Object result = m.invoke(obj, 5);

Create new instance

Class <?> cls = obj.getClass();
Class <?> clsParam = param.getClass();

Constructor c = cls.getConstructor(clsParam);
Object newInstance = c.newInstance(param);
Method m = cls.getMethod("methodName");

We can check if an object is of a particular type, just like instanceof

if (User.class.isInstance(obj)) {

No arg constructor,

Class <?> cls = obj.getClass();
User user = (User) cls.newInstance();


Our programs always incorporate some context and intent. There’s always some assumptions involved. Standard type may not be enough in all cases and sometimes we need metadata to fully capture our ideas of context and intent.

For example, when we use @Override, compiler verifies there is a method with matching signature that can be overriden in super type. Similarly, we often use @Deprecated and SuppressWarnings("warningType")

Annotations act as metadata, it’s a special kind of interface and it does not change the behavior of their target. Annotations are meant to be interpreted by tools or runtime environment.

public @interface CustomAnnotation {
	boolean isExtraCheeseAvailable();

@CustomAnnotation(isExtraCheeseAvailable = false)
class Pizza {

Annotations are accessed with reflection. getAnnotation returns reference to annotation interface and null if does not have annotation of requested type

void pizzaMaker(Pizza pizza) {
	CustomAnnotation ca = pizza.getAnnotations(CustomAnnotation.class);

	if (ca == null) {

	} else if (ca.isExtraCheeseAvailable()) {

	} else {


In addition to setup annotations we have to set the retention to control their availability. By default annotations do not load up into the runtime unless we properly set the retention. There are 3 types of RetentionPolicy

  • Source: Annotation available within source file. It never makes it’s way into the class file.
  • Class: Compiled into the class file, but discarded by runtime (default)
  • Runtime: Loaded into runtime and accessible with reflection

The Source and Class policy are intended for tools which analyze the source code statically. In our case we will use Runtime,

public @interface CustomAnnotation {
	boolean isExtraCheeseAvailable();

We can set the target on an annotation to narrow its use by setting allowable target. For instance we can set Target(ElementType.CONSTRUCTOR) and narrow the annotation use only for constructors. We can also set multiple targets, Target({ElementType.TYPE, ElementType.METHOD}).

public @interface CustomAnnotation {
	boolean isExtraCheeseAvailable();

We can set default values for the elements in annotation.

public @interface CustomAnnotation {
	boolean isExtraCheeseAvailable() default true;

In case we have only one must be setting element then we can change the element name to value

public @interface CustomAnnotation {
	boolean value() default true;

class Pizza {

Valid annotations element types can be,

  • Primitive type
  • String
  • Enum
  • Annotation
  • Class
  • Can also be an array of any of these types
public @interface CustomAnno {
	Class<?> value();

clas Pizza {

void pizzaMaker(Pizza pizza) {
	CustomAnno ca = pizza.getAnnotation(CustomAnno.class);
	Class<?> userType = ca.value();
	User user = (User) userType.newInstance();


Serialization provides a way for object persistence, a way to store and retrieve objects. We can store them ,pass objects between processes or across a network. Primitive types are implicitly serializable. The classes must implement the serializable interface. Serialization doesn’t store only the object, but other objects that it ponts to and build an object-graph.

ObjectOutputStream class uses reflection to look class instances and writing that content out to a Serialization stream, and ObjectinputStream take that Serialization stream and then rebuild our objects from that information.

try(ObjectOutputStream objStream = new ObjectOutputStream(Files.newOutputStream(Paths.get(fileName)))) {

try(ObjectInputStream inpStream = new ObjectInputStream(Files.newInputStream(Paths.get(fileName)))) {
	user = (User) inpStream.readObject();

Deserializing may throw ClassNotFoundException, if any type is not present during deserialization.

Serial version is a unique identifier and used to determine if different versions of our type were compatible. By default, Java will perform a calculation to arrive at that value. As we make changes to our type, we tend to break compatibility between versions of that type. But we can also explicitly set our serial version unique identifier.

To generate the serial ver value run the command, ~$ serialver com.glab.FirstApp.User
Alternatively, run ~$ serialver -show, it will open up a GUI interface, input the full class name and it will give you the value.

private static final long serialVersionUID = -623452345098203498502734L;

We can add writeObject() to our types, that would be called during the process of serializing the object. We also have a readObject() method that the system would call during the deserialization process to give us control. Serialization system will use reflection to find these two methods.

private void writeObject(ObjectOutputStream out) throws IOException {
	out.defaultWriteObject(); // using default serialization scheme 

private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
	ObjectInputStream.GetField fields = in.readFields();
	firstName = (String) fields.get("firstName", "John"); // default value if not found in file 

We can exclude a field from the Serialization process using transient.

Leave a Reply

Fill in your details below or click an icon to log in: Logo

You are commenting using your account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.