Skip to main content

Introduction to Java Fundamentals Concepts

·5051 words·24 mins
Table of Contents

In this blog post we look at the fundamental concepts relatied to Java such as data types, keywords, operators, control statements, OOPs concepts.

What is Java?
#

Java is a programming language and computing platform first released by Sun Microsystems in 1995. Its core principle is “Write Once, Run Anywhere” (WORA), meaning Java code can run on any device or operating system that has a Java Virtual Machine (JVM).

What is Java Virtual Machine (JVM)?
#

Java Virtual Machine or JVM, loads, verifies, and runs Java bytecode. JVM is responsible for converting bytecode to machine-specific code and is necessary in both JDK and JRE.

JVM consists of three main components or subsystems:

  • Class Loader Subsystem is responsible for loading, linking, and initializing a Java class file, otherwise known as dynamic class loading.
  • Runtime Data Areas contain method areas, PC registers, stack areas, and threads.
  • Execution Engine contains an interpreter, compiler, and garbage collection area.

What is Java Runtime Environment (JRE)?
#

The Java Runtime Environment, or JRE, is a software layer that runs on top of a computer’s operating system software and provides the class libraries and other resources that a specific Java program requires to run.

If a you would like to run a Java program and not develop or compile code then only JRE is needed.

What is Java Development Kit (JDK)?
#

Java Development Kit, or JDK, is a software development kit that is a superset of JRE. It is the foundational component that enables Java application and Java applet development. It is platform-specific, so separate installers are needed for each operating system

What is Just-in-time Compiler (JIT)
#

JIT is part of JVM and optimizes the conversion of bytecode to machine code. It selects similar bytecodes to compile at the same time, reducing the overall duration of bytecode to machine code compilation.

Understanding few command line tools
#

To understand the various command line tools available with the JDK, we assume the following simple program:

public class Learn {

	public static void main(String[] args) {
		System.out.println("Hello World");
	}

}
  1. The javac command is used to compile Java programs, it takes .java file as input and produces bytecode.
# command
javac Learn.java

After running the javac command a new file with .class extension is created.

# command
ls
# output
Learn.java
Learn.class  <-- created after running javac
  1. The java command is used to execute the bytecode of java. It takes byte code as input and runs it and produces the output.
# command
java Learn
# output
Hello World

In case a package exists such as package com.example then the java command should be executed from the folder root and pass java com.example.Learn

# folder structure
project_folder     <-- run java command from here when package is com.example
|__com\
   |__example\
      |__Learn.java
      |__Learn.class
  1. The javap command is used to disassemble one or more class files.
# command
javap Learn
# output
Compiled from "Learn.java"
public class Learn {
  public Learn();
  public static void main(java.lang.String[]);
}

Understanding classpath and JAVA_HOME
#

classpath
#

The classpath is an environment variable used by the Java Virtual Machine (JVM) to locate and load classes when running a Java program. It specifies a list of directories, JAR files, and ZIP files where the JVM should look to find and load class files.

To set the classpath via the command line, we use the -classpath option when running the java command:

java -classpath /path/to/class/files Learn

JAVA_HOME
#

The JAVA_HOME environment variable points to the JDK installation directory. Subsequently, other Java-dependent programs can use this variable to access the JDK/JRE path

Data Types in Java
#

Data types specify the type of values a variable can hold. They define the size, range and nature of data stored in memory. Java has two main categories of data types:

  1. Primitive: byte, short, int, long, float, double, char, boolean
  2. Non-Primitive: String, Arrays, Classes, Interfaces, Objects

Primitive Data Types
#

There are eight primitive data types in Java:

  1. byte: The byte data type is an 8-bit signed two’s complement integer. It has a minimum value of -128 and a maximum value of 127 (inclusive).
byte b = 100;
  1. short: The short data type is a 16-bit signed two’s complement integer. It has a minimum value of -32,768 and a maximum value of 32,767 (inclusive).
short s = 30000;
  1. int: By default, the int data type is a 32-bit signed two’s complement integer, which has a minimum value of -2^31 and a maximum value of 2^31-1. In Java SE 8 and later, you can use the int data type to represent an unsigned 32-bit integer, which has a minimum value of 0 and a maximum value of 2^32-1.
int i = 100000;
  1. long: The long data type is a 64-bit two’s complement integer. The signed long has a minimum value of -2^63 and a maximum value of 2^63-1. In Java SE 8 and later, you can use the long data type to represent an unsigned 64-bit long, which has a minimum value of 0 and a maximum value of 2^64-1.
long l = 10000000000L;
  1. float: The float data type is a single-precision 32-bit IEEE 754 floating point. Sufficient for storing 6 to 7 decimal digits. This data type should never be used for precise values, such as currency.
float f = 3.14f;
  1. double: The double data type is a double-precision 64-bit IEEE 754 floating point. Sufficient for storing 15 to 16 decimal digits. This data type should never be used for precise values, such as currency.
double d = 3.14159265359;
  1. char: The char data type is a single 16-bit Unicode character. It has a minimum value of ‘\u0000’ (or 0) and a maximum value of ‘\uffff’ (or 65,535 inclusive).
char c = 'A';
  1. boolean: The boolean data type has only two possible values: true and false. This data type represents one bit of information, but its “size” isn’t something that’s precisely defined.
boolean flag = true;

Non-Primitive Data Types
#

Non-primitive data types, also known as reference types, represent complex data and more sophisticated structures than primitive types.

  1. Class and Objects

Class is a user-defined data type that is used to create objects. A class contains a set of properties and methods that are common and exhibited by all the objects of the class.

A static method is a method that is associated with the class in which it is defined rather than with any object. Every instance of the class shares its static methods.

You can create several different types of nested classes in Java. The different Java nested class types are:

  • Static nested classes
  • Non-static nested classes
  • Local classes
  • Anonymous classes
// define a single public class in a .java file
public class Learn {
	public static void main(String[] args) {
		System.out.println("Hello World");
	}
}

Static inner class

public class Outer {

	void display() {
		System.out.println("method on outer class");
	}
	// static inner class
	static class Inner {
		// cannot access display() method since inner class is static
		void show() {
			System.out.println("method inside static inner class");
		}
	}

	public static void main(String[] args) {
		Outer o = new Outer();
		o.display();

		// static inner class instantiation
		Outer.Inner i = new Outer.Inner();
		i.show();
	}
}

Output

method on outer class
method inside static inner class

Non-static inner class

public class Outer {
	void display() {
		System.out.println("method on outer class");
	}

	void close() {
		System.out.println("close method from outer class");
	}

	class Inner {
		void show() {
			System.out.println("method inside static inner class");
			// accessed from Outer class. possible since Inner class is not static
			close();
		}
	}

	public static void main(String[] args) {
		Outer o = new Outer();
		o.display();
		
		// non-static inner class instantiation
		Outer.Inner i = new Outer().new Inner();
		i.show();
	}
}

Output

method on outer class
method inside static inner class
close method from outer class

Inner Class Shadowing

public class Learn {

	void main() {
		Outer o = new Outer();
		System.out.println(o.getText());
		
		Outer.Inner i = o.new Inner();
		i.printText();
	}

}

class Outer {
	private String text = "I am Outer private!";

	public class Inner {

		private String text = "I am Inner private";

		public void printText() {
			System.out.println(text);
			System.out.println(Outer.this.text);
		}
	}

	public String getText() {
		return text;
	}
}

Output

I am Outer private!
I am Inner private
I am Outer private!

Anonymous classes in Java are nested classes without a class name. They are typically declared as either subclasses of an existing class, or as implementations of some interface.

Interface implemented as anonymous class

public class Learn {
	public static void main(String[] args) {
		// anonymous class implementation
		VerifyAge a = new VerifyAge() {

			@Override
			public boolean above18(int age) {
				return (age >= 18) ? true : false;
			}
		};
		System.out.println(a.above18(20));
	}
}

interface VerifyAge {
	boolean above18(int age);
}

Output

true

Anonymous Class Extends Example

public class Learn {

	public static void main(String[] args) {
		SuperClass anom = new SuperClass() {

			public void disp() {
				System.out.println("Anonymous class");
			}

			public void newDisp() {
				System.out.println("new impl");
			}
		};
		// anonymous class prints extended overriden method output
		// according to java 'class Learn$1 extends SuperClass {}
		anom.disp();
		// anom.newDisp(); gives compile error as anom variable is of type SuperClass
	}
}

class SuperClass {
	public void disp() {
		System.out.println("SuperClass");
	}
}
  1. String

Strings are capable of containing sequences of characters within a single variable, unlike character arrays where each character is stored separately. When a String is formed as a literal with the assistance of an assignment operator, it makes its way into the String constant pool so that String Interning can take place. This same object in the heap will be referenced by a different String if the content is the same for both of them.

String pool is a subset of the heap memory.

// Both a and b refer to the same string in the pool area
String a = "Hello";
String b = "Hello";

// Both a and b refer to two different memory address in the heap
String a = new String("World");
String b = new String("World");
  1. Array

Arrays are non primitive data types in Java that are used to store elements of the same data type in a contiguous manner. Arrays have a unique reference name by which all their elements are accessed. Elements are stored in an indexed manner where the index starts from 0.

int[] arr = new int[5];
int arr[] = {1,2,3,4,5}; // Declaration & Initialization as well
  1. Interface

Interace in Java is a tool to achieve abstraction. Interface can contain non-implemented methods (without the method body) also known as abstract methods. All method declarations in an interface are implicitly public.

Default methods in Interfaces enable you to add new functionality to the interfaces of your libraries and ensure binary compatibility with code written for older versions of those interfaces. If you don’t want the implementation to be overridden in the implementing class, then you can declare the method as static.

interface Vehicle {
	default void start() {
		System.out.println("Starting vehicle");
	}

	void stop();

	static int add(int a, int b) {
		return a + b;
	}
}

class Car implements Vehicle {
	@Override
	public void stop() {
		System.out.println("Stoping vehicle");
	}
}

public class Learn {
	public static void main(String[] args) {
		Car c = new Car();
        // default method
		c.start();
        // implemented in Car class
		c.stop();
        // static method is not accessible from implementation class
		System.out.println(Vehicle.add(1, 5));
	}
}

Output:

Starting vehicle
Stoping vehicle
6

An interface with exactly one abstract method is called Functional Interface. @FunctionalInterface annotation is added so that we can mark an interface as functional interface.

@FunctionalInterface
interface Calculator {
    int calculate(int a, int b);
}

public class Learn {
	public static void main(String[] args) {
		Calculator c = (a,b)->{
			return a+b;
		};
		int res = c.calculate(10, 15);
		System.out.println(res);
	}
}

Output

25

Interfaces are able to use private methods to hide details on implementation from classes that implement the interface. As a result, one of the main benefits of having these in interfaces is encapsulation.

interface Building {

	static void structure() {
		System.out.println("creating structure");
		walls();
	}

	private static void walls() {
		System.out.println("generating walls!");
	}

	default void paint() {
		System.out.println("starting paint");
		decoration();
	}

	private void decoration() {
		System.out.println("adding decoration!");
	}
}

class MyHome implements Building {
}

public class Learn {

	public static void main(String[] args) {
		// not accessible via MyHome as method is static
		Building.structure();
		MyHome h = new MyHome();
		// h.decoration() is not accessible as its private
		h.paint();
	}

}

Output

creating structure
generating walls!
starting paint
adding decoration!

Sealing allows classes and interfaces to define their permitted subtypes. In other words, a class or interface can define which classes can implement or extend it. It’s a useful feature for domain modeling, and increasing the security of libraries.

non-sealed just “breaks the seal”, so the effect doesn’t have to be carried on down the hierarchy. The extending class is open for being extended by unknown subclasses itself.
final is effectively the same as sealed without any class specified after the permits clause. Notice that specifying nothing after permits is not possible, so sealed cannot replace final.

// Car and Truck class should be available during compile time
// with sealed keyword permits should be used
sealed interface Service permits Car, Truck {
	int getMaxServiceIntervalInMonths();

	default int getMaxDistanceBetweenServicesInKilometers() {
		return 100000;
	}
}
// no class can inherit Car class
final class Car implements Service {
	@Override
	public int getMaxServiceIntervalInMonths() {
		return 10;
	}
}
// non-sealed allows any unknown class to extend Truck class
non-sealed class Truck implements Service {
	@Override
	public int getMaxServiceIntervalInMonths() {
		return 150;
	}
}

Operators in Java
#

Arithmetic Operators
#

public class Learn {
	public static void main(String[] args) {
		int a = 10;
		int b = 3;

		System.out.println("a + b = " + (a + b));
		System.out.println("a - b = " + (a - b));
		System.out.println("a * b = " + (a * b));
		// / returns the quotient
		System.out.println("a / b = " + (a / b));
		// % returns the reminder 
		System.out.println("a % b = " + (a % b));
	}
}

Output

a + b = 13
a - b = 7
a * b = 30
a / b = 3
a % b = 1

Relational Operators
#

public class Learn {
	public static void main(String[] args) {
		int a = 10;
		int b = 3;

		// return false
		System.out.println("a == b : " + (a == b));
		// return true
		System.out.println("a != b : " + (a != b));
		// return true as 10 is greater than 3
		// when both a=b then a > b is false
		System.out.println("a > b  : " + (a > b));
		// return false as 3 is not greater than 10
		// when both a=b then a > b is false
		System.out.println("a < b  : " + (a < b));
		// return true
		System.out.println("a >= b : " + (a >= b));
		// return false
		System.out.println("a <= b : " + (a <= b));
	}
}

Output

a == b : false
a != b : true
a > b  : true
a < b  : false
a >= b : true
a <= b : false

Logical Operator
#

public class Learn {
	public static void main(String[] args) {
		boolean x = true;
        boolean y = false;

        // logical &&
        System.out.println("x && y : " + (x && y));
        // logical or
        System.out.println("x || y : " + (x || y));
        // logical not
        System.out.println("!x : " + (!x));
	}
}

Output

x && y : false
x || y : true
!x : false

Assignment Operator
#

public class Learn {
	public static void main(String[] args) {
		int c = 5;

		// c = c + 3
		// c =+ 3 is not the same. It is assignment of +3 to c.
		c += 3;
		System.out.println("c += 3: " + c);
		// c = c - 2
		c -= 2;
		System.out.println("c -= 2: " + c);
		// c = c * 4
		c *= 4;
		System.out.println("c *= 4: " + c);
		// c = c / 2
		c /= 2;
		System.out.println("c /= 2: " + c);
		// c = c % 3
		c %= 3;
		System.out.println("c %= 3: " + c);
	}
}

Output

c += 3: 8
c -= 2: 6
c *= 4: 24
c /= 2: 12
c %= 3: 0

Unary Operator
#

public class Learn {
	public static void main(String[] args) {
		int x = 1;

		// unary +
		System.out.println("+x: " + (+x));
		// unary -
		System.out.println("-x: " + (-x));
		// value of x is 1
		System.out.println("x: " + x);
		// value of x++ returns the same value that is 1 but it increment the value of x by 1
		System.out.println("x++: " + (x++));
		// value of x is 2
		System.out.println("x: " + x);
		// value of ++x return the incremented value of x that is 3
		System.out.println("++x: " + (++x));
		// value of x is still 3
		System.out.println("x: " + x);
	}
}

Output

+x: 1
-x: -1
x: 1
x++: 1
x: 2
++x: 3
x: 3

Ternary Operator
#

public class Learn {
	public static void main(String[] args) {
		int a = 10;
		int b = 3;

		int max = (a > b) ? a : b;
		System.out.println("max of a and b = " + max);
	}
}

Output

max of a and b = 10

Bitwise Operator
#

public class Learn {
	public static void main(String[] args) {
		int p = 5; // 0101
		int q = 3; // 0011

		// 0101 AND 0011 = 0001
		System.out.println("p & q: " + (p & q));
		// 0101 OR 0011 = 0111
		System.out.println("p | q: " + (p | q));
		// 0101 XOR 0011 = 0110
		System.out.println("p ^ q: " + (p ^ q));
		// COMPLEMENT 0000 0101 = 1111 1010
		// left most bit is 1 means number is negative
		// to get actual value take 2's complement and add 1
		// 2's complement 1111 1010 = 0000 0101 +1 is 0000 0110
		// 0000 0110 is -6
		// it’s equivalent to the ! operator.
		System.out.println("~p: " + (~p));
		// signed Left Shift << by 1
		System.out.println("p << 1: " + (p << 1));
		// signed right Shift >> by 2
		System.out.println("p >> 2: " + (p >> 2));
		// unsigned right shift [>>>]
		System.out.println("p >>> 1: " + (p >>> 1));
	}
}

Output

p & q: 1
p | q: 7
p ^ q: 6
~p: -6
p << 1: 10
p >> 2: 1
p >>> 1: 2

Java Control Flow Statements
#

If Statement
#

public class Learn {
	public static void main(String[] args) {
		int score = 75;
		char grade;

		if (score >= 90) {
			grade = 'A';
		} else if (score >= 80) {
			grade = 'B';
		} else if (score >= 70) {
			grade = 'C';
		} else {
			grade = 'D';
		}
		System.out.println("Your grade is " + grade);
	}
}

Output

Your grade is C

Switch Statement
#

public class Learn {
	public static void main(String[] args) {
		int score = 75;
		char grade;

		switch (score / 10) {
		case 10:
		case 9:
			grade = 'A';
			break;
		case 8:
			grade = 'B';
			break;
		case 7:
			grade = 'C';
			break;
		default:
			grade = 'D';
			break;
		}
		System.out.println("Your grade is " + grade);
	}
}

Output

Your grade is C

With Java 14 and above

public class Learn {
	public static void main(String[] args) {
		int score = 75;
		char grade;

		switch (score / 10) {
		case 10, 9 -> grade = 'A';
		case 8 -> grade = 'B';
		case 7 -> grade = 'C';
		default -> grade = 'D';
		}
		System.out.println("Your grade is " + grade);
	}
}

Output

Your grade is C

Java Loop Statements
#

public class Learn {
	public static void main(String[] args) {
		for (int i = 0; i < 10; i++) {
			System.out.print(i + " ");
		}
	}
}

Output

0 1 2 3 4 5 6 7 8 9 

While Loop
#

public class Learn {
	public static void main(String[] args) {
		int i = 0;
		//While loop is used when the number of iterations is unknown and depends on a condition that changes during execution
		while (i < 10) {
			System.out.print(i + " ");
			i++;
		}
	}
}

Output

0 1 2 3 4 5 6 7 8 9 

Do-While Loop
#

public class Learn {
	public static void main(String[] args) {
		int i = 0;
		// The do-while loop ensures that the code block executes at least once before checking the condition.
		do {
			System.out.print(i + " ");
			i++;
		} while (i < 10);
	}
}

Output

0 1 2 3 4 5 6 7 8 9 

Modifying the same with i = 11

public class Learn {
	public static void main(String[] args) {
		int i = 11;
		// The do-while loop ensures that the code block executes at least once before checking the condition.
		do {
			System.out.print(i + " ");
			i++;
		} while (i < 10);
	}
}

Output

11 

Continue and Break in Loop Statements
#

Break in loop statement

public class Learn {
	public static void main(String[] args) {
		for (int i = 0; i < 10; i++) {
			// prints upto 3 and exits out of loop at 4
			if (i == 4) {
				break;
			}
			System.out.print(i + " ");
		}
	}
}

Output

0 1 2 3 

Continue in loop statement

public class Learn {
	public static void main(String[] args) {
		for (int i = 0; i < 10; i++) {
			// skips 4 and prints every other numner between 0 and 9
			if (i == 4) {
				continue;
			}
			System.out.print(i + " ");
		}
	}
}

Output

0 1 2 3 5 6 7 8 9 

Java Object Oriented Programming Concepts
#

Object-Oriented Programming or OOPs refers to languages that use objects in programming. Object-oriented programming aims to implement real-world entities like inheritance, hiding, polymorphism, etc in programming.

Class
#

A Class is a user-defined blueprint or prototype from which objects are created. It represents the set of properties or methods that are common to all objects of one type.

// A simple class example having two private instance, a constructor and public getter and setter
class Person {
	private String name;
	private int age;

	public Person(String name, int age) {
		super();
		this.name = name;
		this.age = age;
	}

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	public int getAge() {
		return age;
	}

	public void setAge(int age) {
		this.age = age;
	}
}

Object
#

An Object is a basic unit of Object-Oriented Programming that represents real-life entities. A typical Java program creates many objects, interact by invoking methods.

// creating an object of class Person
Person p = new Person("Raj", 25);

Abstraction
#

Objects only reveal internal mechanisms that are relevant for the use of other objects, hiding any unnecessary implementation code. Abstraction in Java is the process of hiding the implementation details and only showing the essential details or features to the user. Abstraction is achieved by interfaces and abstract classes.

class Dog extends Animal {
	public void animalSound() {
		System.out.println("The dog says: woof woof");
	}
}

abstract class Animal {
	public abstract void animalSound();

	public void sleep() {
		System.out.println("Zzz");
	}
}

public class Learn {
	public static void main(String[] args) {
		Dog d = new Dog();
		d.animalSound();
		// sleep method is not defined in the Dog class
		// a simple example of abstraction
		d.sleep();
	}

}

Output

The dog says: woof woof
Zzz

Encapsulation
#

The encapsulation principle states that all important information is contained inside an object and only select information is exposed. Encapsulation is defined as the process of wrapping data and the methods into a single unit, typically a class. It is the mechanism that binds together the code and the data.

The Person class shows an example of encapsulation where the instance variables are private and these variable are accessiable through public getter and setter methds.

Inheritance
#

Classes can reuse code and properties from other classes. It is the mechanism in Java by which one class is allowed to inherit the features (fields and methods) of another class. We are achieving inheritance by using extends keyword. Inheritance is also known as “is-a” relationship.

Polymorphism
#

Objects are designed to share behaviors, and they can take on more than one form.

Polymorphism in Java is mainly of 2 types as mentioned below:

  1. Compile-Time Polymorphism (Method overloading)

Compile-Time Polymorphism in Java is also known as static polymorphism and also known as method overloading.

public class Learn {
	public static void main(String[] args) {
		// method overloading by have same method name but different argument data type
		System.out.println(Helper.Multiply(2, 4));
		System.out.println(Helper.Multiply(5.5, 6.3));
	}

}

class Helper {
	static int Multiply(int a, int b) {
		return a * b;
	}

	static double Multiply(double a, double b) {
		return a * b;
	}
}

Output

8
34.65
  1. Runtime Polymorphism (Method Overriding)

Runtime Polymorphism in Java known as Dynamic Method Dispatch. It is a process in which a function call to the overridden method is resolved at Runtime. This type of polymorphism is achieved by Method Overriding.

public class Learn {
	public static void main(String[] args) {
		Apartment a = new Apartment();
		a.Print();

		// dynamically replace Print method of parent with child
		a = new DinnerRoom();
		a.Print();

		a = new LivingRoom();
		a.Print();
	}

}

class Apartment {
	void Print() {
		System.out.println("Apartment structure");
	}
}

class DinnerRoom extends Apartment {
	@Override
	void Print() {
		System.out.println("Dinner room");
	}
}

class LivingRoom extends Apartment {
	@Override
	void Print() {
		System.out.println("Living room");
	}
}

Output

Apartment structure
Dinner room
Living room

Java Records
#

A Java Record is a special kind of Java class which has a concise syntax for defining immutable data-only classes. Java Record instances can be useful for holding records returned from a database query, records returned from a remote service call, records read from a CSV file, or similar types of use cases.

The Java compiler auto generates getter methods, toString(), hashcode() and equals() methods for these data fields, so you don’t have to write that boilerplate code yourself. Since a Java Record is immutable, no setter methods are generated.

Example of record class

public class Learn {
	public static void main(String[] args) {
		DataUnit du = new DataUnit(1, "Apple");
		System.out.println(du.id());
		System.out.println(du.type());
	}
}

record DataUnit(int id, String type) {
}

Output

1
Apple

A record class can have multiple constructors

public class Learn {
	public static void main(String[] args) {
		DataUnit du = new DataUnit(1, "Apple");
		System.out.println(du.id());
		System.out.println(du.type());

		DataUnit du1 = new DataUnit(2);
		System.out.println(du1.id());
		System.out.println(du1.type());

	}
}

record DataUnit(int id, String type) {
	// canonical constructor performing a check on the arguments
	public DataUnit {
		if (id <= 0 || type == null) {
			throw new java.lang.IllegalArgumentException("invalid");
		}
	}

	// new constructor accepting only one argument
	public DataUnit(int id) {
		this(id, "AUTO");
	}
}

Record class support adding instance and static methods

public class Learn {
	public static void main(String[] args) {
		DataUnit du = new DataUnit(1, "Apple");
		System.out.println(du.id());
		System.out.println(du.type());
		System.out.println(du.typeAsLowerCase());

		DataUnit du1 = new DataUnit(2);
		System.out.println(du1.id());
		System.out.println(du1.type());
		System.out.println(DataUnit.brandAsUpperCase(du1));

	}
}

record DataUnit(int id, String type) {
	// canonical constructor performing a check on the arguments
	public DataUnit {
		if (id <= 0 || type == null) {
			throw new java.lang.IllegalArgumentException("invalid");
		}
	}

	// new constructor accepting only one argument
	public DataUnit(int id) {
		this(id, "AUTO");
	}

	// instance method
	public String typeAsLowerCase() {
		return type().toLowerCase();
	}

	// static method
	public static String brandAsUpperCase(DataUnit unit) {
		return unit.type.toUpperCase();
	}
}

Output

1
Apple
apple
2
AUTO
AUTO

Java Lambda Expressions
#

Java lambda expressions are new in Java 8. Java lambda expressions are Java’s first step into functional programming. A Java lambda expression is thus a function which can be created without belonging to any class. A Java lambda expression can be passed around as if it was an object and executed on demand.

Java lambda expression example

public class Learn {

	public static void main(String[] args) {
		// lambda expression
		Printer p = a -> System.out.println(a);
		// calling the lambda expression
		p.print("hello world");
	}
}

interface Printer {
	public void print(String value);
}

Output

hello world

Another example of lambda expressions

public class Learn {
	public static void main(String[] args) {
		// lambda expression to find sum
		Calculator add = (a, b) -> a + b;
		// lambda expression to find difference
		Calculator subtract = (a, b) -> a - b;

		// result: 8
		int resultAdd = add.calculate(5, 3);
		// result: 2
		int resultSubtract = subtract.calculate(5, 3);

		System.out.println("Addition: " + resultAdd);
		System.out.println("Subtraction: " + resultSubtract);
	}
}

interface Calculator {
	int calculate(int a, int b);
}

Output

Addition: 8
Subtraction: 2

Even though lambda expressions are close to anonymous interface implementations, there are a few differences that are worth noting. The major difference is, that an anonymous interface implementation can have state (member variables) whereas a lambda expression cannot.

In an anonymous class, this keyword refers to the anonymous class itself. But in the case of a lambda expression, this refers to its enclosing class.

We can also declare member variables in an anonymous class, which isn’t possible in the case of a lambda expression. Thus, an anonymous class can have a state. Variables declared inside the lambda expression act as local variables. Both of them, though, have access to member variables of the enclosing class.

An example of state with anonymous and lambda expression:

  1. Anonymous
public class Learn {
	public static void main(String[] args) {
		SimpleInterface impl = new SimpleInterface() {
			// count variable is the instance variable (field variable)
			private int count = 0;

			@Override
			public int func() {
				return ++count;
			}
		};

		System.out.println(impl.func());
		System.out.println(impl.func());
	}
}

interface SimpleInterface {
	int func();
}

Output

1
2
  1. Lambda expression
public class Learn {
	public static void main(String[] args) {

		SimpleInterface impl = () -> {
			// the count variable in the lambda expression is local variable
			int count = 0;
			return ++count;
		};
		System.out.println(impl.func());
		System.out.println(impl.func());
	}
}

interface SimpleInterface {
	int func();
}

Output

1
1

Java Access Modifiers
#

A Java access modifier specifies which classes can access a given class and its fields, constructors and methods. Classes, fields, constructors and methods can have one of four different Java access modifiers:

  1. private
  2. default (package)
  3. protected
  4. public

References
#

Vaibhav
Author
Vaibhav
Full Stack Developer