Just like abstract classes whose sole-purpose is to get inherited and overriden. They differ as the compiler inserts implicit modifiers, don’t have a constructor, uses implements
rather than extends
, and has default
methods.
abstract
implicitlypublic static final
implicitlyabstract
implicitly unless they are default
, static
, private
, or private static
public
(and not package default) unless they are private
default
, static
, private
, and private static
public interface Demo{
int a = 5;
void bar();
void foo(){ } // invalid; should've no body
}
public abstract interface Demo{
public static final int a = 5;
public abstract void bar();
}
public interface Demo extends Foo, Bar { } // Foo and Bar are interfaces
public class X implements Y, Z { } // Y and Z are interfaces
public class A extends B implements C, D { } // both inheriting and implementing
interface Test{
public void foo();
}
class Super{
public void foo(){ } // implementation in superclass
}
public class MyClass extends Super implements Test { // subclass inherits a foo() implementation
public static void main(String args[]) {
new MyClass();
System.out.println("Test");
}
}
// compiles fine
interface Test{
public void foo();
}
class Super{
public int foo(){ return 1; } // incompatible return type
}
public class MyClass extends Super implements Test { // subclass doesn't have a "void foo()" impl
public static void main(String args[]) {
new MyClass();
System.out.println("Test");
}
}
// compiler error
conflicting explicit modifiers over implicit modifiers are not allowed: we can’t make a variable private
or a method protected
, etc…
the 4 methods to override methods still apply to interfaces too
this
works in interfaces too despite the fact that they’re not classes that have an instance, super
doesn’t work though, we need to specify which parent super to call using InterfaceName.super
We can use interface reference too apart from parent or child (self) reference. We use this all the time in Collections framework and it hides what kinds of object it actually is. All we are concerned with is that it implements our interface and we can only access methods it implements from our interface reference. Example
All are implicitly public static final
and are accessible in implementing class as well as by interface ref (as is the normal behaviour of properties in class inheritance).
interface Demo{
int a = 5; // "public static final" implicitly; must initialize inline
}
class Main implements Demo{
public static void main(String[] args){
System.out.println(a); // 5 (inherited)
System.out.println(Main.a); // 5 (same as above)
System.out.println(Demo.a); // 5
}
}
To have a default
method inside an interface that is optional to implement by class implementing the interface, available to implementing class to call without any ref.
default
and must have a method bodypublic
(just like any other method in interface)abstract
, final
, static
interface X{
default int foobar(){
return 2;
}
}
class Demo implements X{
foobar(); // avaliable to class
}
InterfaceName.super.methodName()
as InterfaceName.methodName()
won’t work for non-static methodsstatic
and must have a method bodypublic
(just like any other method in interface)abstract
or final
static
class methods) and we need to call it with interface name (InterfaceName.methodName()
) since we can’t call it using implementing class ref or name. We can also have the same method defined in our inheriting interface or implementing class since they are never inherited so no overriding can happen.interface P{
static void foobar(){ }
}
// interface to interface - inheritance
interface C extends P{
static int foobar() { return 0; } // valid; since static method wasn't inherited
}
// interface to class - implementation
class Main implements P{
foobar(); // invalid
Main.foobar(); // invalid
P.foobar(); // valid
}
interface P{
void foo();
}
interface Q extends P{
default void foo(){ } // valid; foo gets impl
}
----------------------------------
interface P{
void foo();
}
interface Q extends P{
static void foo(){ } // compiler-error; static method method cannot override an abstract method; since it won't be going to the implementing class anyways even if we accept its impl here
}
Reduce code duplcation inside Interface: Interface methods can be private
or private static
. In the former case, they can only be accessed by other non-static methods within the interface and reduce code redundancy. The latter ones can only be accessed by other all methods within the interface. Both are hidden from the concrete class implementing the interface and from outside using class reference since they are private
and can only be accessed from within the interface only.
interface X{
private String foobar(){ // allowed to have a body
return "lorem ipsum";
}
private static String foobar(){ // same; allowed to have a body
return "lorem ipsum";
}
}
We can call them too from inside other non-static interface methods.
interface X{
String foo();
String bar();
void foobar(){
return foo()+bar();
}
}
// when we call them, there will be an instance of the class implemeting the interface so it's safe
public static final
implicitly) of an interface do get inherited by the implementing class, static
methods don’t!default
, abstract
methods do get inherited and belong to Instanceprivate
methods are accessible only from inside the interface and reduce code redundancythis
works inside interfaces too!InterfaceName.super
since multiple inheritance is allowed in interfaces and we need to know which parent super to call.Enumerations: Defines a set of constants having values that must be known at compile-time.
Internally represented as class
only, so we can’t have a public class
and a public enum
together in the same .java
file.
enum Days{ MON, TUE, WED, THURS FRI, SAT, SUN; } // semi-colon is optional for simple enums
Days s = Days.TUE;
System.out.println(Days.TUE); // TUE
System.out.println(s == Days.TUE); // true
Enum constants get and int
value stating from 0
and so on but we can’t compare and Enum value to an int
primitive since Enum is an object type
Days.TUE == 1 // error; comparing (TUE == 1) doesn't make any sense
for(var d: Days.values()){
System.out.println(d.name() + " " + d.ordinal());
}
/*
MON 0
TUE 1
WED 2
...
*/
Days d = Days.valueOf("TUE"); // TUE
Simple enums have: name, ordinal
Complex enums have: name, value, ordinal
// bare-minimum complex enum
enum Alphabet {
A("Z"),
B("Y"),
C("X");
Alphabet(String a) { } // mandatory for complex enums; can only be private or package-access
// create an optional instance field and set its value in the constructor
// create and use its getter to access - "Z", "Y", "X"
}
Multiple constant values are allowed too:
// multiple-constants complex enum
enum Alphabet {
A("Z", 26),
B("Y", 25),
C("X", 24);
Alphabet(String s, Integer i) { }
}
public enum Season{
WINTER("Low"), SPRING("Medium"), SUMMER("High"); // constructor calls to initialize
// ; not optional, values always comes before construtors, methods
public final String expVis; // optional field to set to and access value "Low"/"Medium"/"High", can be non-final too but bad practice
private Season(String expVis){ // mandatory constructor, always implicitly private, runs only on first access to a constant, never runs again on subsequent accesses
this.expVis = expVis; // important; set value to enum instance var
}
public void getExpVis(){ // getter method
System.out.println(expVis);
}
}
Season s = Season.SUMMER; // RHS is a constructor call but without "new", LHS is reference variable assignment
System.out.println(Season.SUMMER); // SUMMER
System.out.println(Season.SUMMER.ordinal()); // 2
Season.SUMMER.printExpVis(); // enum method call
s.getExpVis(); // "High"; enum method call with reference object
s.expVis; // "High"; enum instance variable direct access
Enum can have a method at last which can be abstract
or a “normal” method shared by all.
public enum Seasons{
WINTER { public int getTemp(){ return 5; } }, // override
SPRING { public int getTemp(){ return 25; } }; // override
public abstract int getTemp(); // abstract method (override is a must)
}
public enum Seasons{
WINTER { public int getTemp(){ return 5; } }, // override
SPRING { public int getTemp(){ return 25; } }, // override
SUMMER, FALL; // use shared method for these
public int getTemp(){ return 30; } // method shared by all (override is optional)
}
extend
ed or inherit from another class (since they already implicitly extend java.lang.Enum
class). We also can’t create an Enum object with new
like a normal class.implement
interfaces and they have to define abstract methods of those interfaces.private
(can’t be made public
or protected
) otherwise the programmer can call the constructor and create instances on the fly which defeats the purpose of fixed constants in enum.A class that restricts which other classes can directly extend from it.
sealed class Demo permits Foo, Bar{ } // sealed parent
final class Foo extends Demo { } // final child (no inheritance possible)
non-sealed class Bar extends Demo{ } // non-sealed child (can be inherited by any other classes)
sealed
, final
, or non-sealed
We can omit the permits
clause from class definition if:
Same rules as in Sealed Classes.
permits
here applies to classes that implement the interface or other interfaces that extend it.abstract
implicitly, they can’t be marked final
so only the other two (non-sealed
and sealed
) can be applied to interfaces dealing with a sealed interface.Records - Auto-generated immutable classes with no setters. (Immutability = Encapsulation). Everything inside of parameter list (instance fields) of a record is implicitly final
and the record itself is implicitly final
and cannot be extended.
We can define our own constructors, methods or constants (static final
only) inside the body.
record Demo(int foo, String bar){ }
A lot is created automatically for the above one line:
Demo(int, String)
foo()
and bar()
equals()
,hashCode()
, and toString()
Empty records is totally valid (but useless).
record DemoEmpty(){ }
Just like enums, a record can’t be extended or inherited but it can implement an interface, provided it defines all the abstract methods.
public interface Foobar{ }
public record Demo(int foo, String bar) implements Foobar{ }
Long Constructor/Canonical Constructor: Explicitly defining the implicit one. Since every field is final
in a record, we have to initialize every field in our explicit constructor.
Compact Constructor: Modifies constructor arguments only and not instance fields directly. After compact constructor, the implicit long constructor is always called with modified params in compact constructor which sets those values to instance fields of record.
public record Demo(int foo, String bar){
public Demo{ // notice no parentheses ()
bar = bar.substring(0, 1).toUpperCase(); // modification to constructor argument only
this.foo = foo; // compiler-error; foo is final in instance yet to be created
if(foo < 0) throw new IllegalArgumentException(); // validation
}
}
Constructor Overloading is possible here too and all rules apply. Only the long constructor (explicit or implicit) has the ability to initilalize all fields and hence whatever overloaded constructor we are writing must call this()
on the first line and if no other constructor is available, the long constructor is called. Do note that we can’t change any instance variables after line 1.
public record Demo(int foo, String bar){
public Demo(String foo, String bar){ // overloaded constructor; notice types
this(0, foo+bar);
foo = "John"; // modifies constructor argument
this.bar = "Doe"; // compiler error
}
}
public record Demo(int foo, String bar){
public int foo(){ return 10; }
public String toString(){ return "Testing..."; }
}
static final
fields are allowed in record body, naturally static initializer blocks are allowed toorecord Demo(String name, int age){
static final int marks = 5;
}
--------------------------------------
record Demo(String name, int age){
static final int marks;
static { marks = 5; }
}
// access from anywhere using: Demo.marks
final
for immutability; naturally instance initializer blocks aren’t allowed since initializaion is possible only via implicit long constructor, also no instance fields exist so no point of instance initializer blocksClass within another class.
Since nested classes are defined at member level in another class, it can be public
, package access, protected
and private
just like other members and unlike top-level classes.
Inner classes can even access private
members of its outer class, since it is also a member of it.
Java 16 allowed static
methods inside nested inner class which was not allowed before. Now they can exist in any of the 4 nested class types.
// from within outer class
class Out{
class In{ }
void createObj(){
var obj = new In();
}
}
// from out of both classes
class Out{
class In{
void say(){ }
}
}
class My{
public static void main(String args[]){
Out o = new Out();
In i = o.new In(); // new syntax
i.say();
}
}
//we can also use
new Out().new In().say();
// class files for nested classes
Out.class
Out$In.class
Since nested classes are also considered as members of the outer, we can’t initialize non-static inner classes this way:
class Out{
class In{ } // non-static member
public static void main(String args[]){
new In(); // error; accessing non-static from static context
}
}
class Out{
class In{ }
public static void main(String args[]){
Out o = new Out();
o.new In(); // instance reference provided; no errors
}
}
But static
inner classes can be instantiated without a instance reference:
class Out{
static class In{ }
public static void main(String args[]){
new In(); // no error since we are accessing from static context
}
}
Since they are local to a method, just like local variables, their lifetime is only the scope of the method, i.e. they go out of scope after method scope is over. We can return a reference to object in heap though.
They don’t have any access modifiers except final
just like local variables.
They can access all members of the enclosing class (ofc!).
They can only access final
and effectively final local variables.
void foo(){
final int a = 5;
int b = 6;
int c = 7;
class In{
void bar(){ System.out.println(a+b+c); } // error; c isn't final or effectively final
}
c = 8;
}
Since there are two class files created for each class by compiler, there is no way a class can access another class’s method’s local variables, so compiler created a copy with same value for local class too but it will not work if the value keeps changing.
Local class but it doesn’t have a name. Ends with a semi-colon (;
) since its a one-liner variable declaration only.
Two types of Anonymous Classes exist:
class
interface
Demo d = new Demo(){ // anon class extends "Demo" class
// define our own members and methods inside
// just like a typical class
int getCost(){ return 5; }
};
class Demo(){ }
Demo o = new Demo(){ }; // extends
Foobar o = new Foobar(1, "abc"){ }; // extends; with parameterized constructor of superclass
interface Demo(){ }
Demo o = new Demo(){ }; // implements "Demo" interface
We can’t extend and implement at the same time (unlike other nested classes incl. Local class). And when doing either one also, we don’t use extends
or implements
keywords.
We can’t implement two or more interfaces at the same time as the syntax doesn’t allow this.
Anonymous Classes don’t have a constructor as they don’t have a name. As a result, we can’t instantiate it more than one time and only a single instance exists for each distinct Anonymous Class.
Note that the methods defined in anonymous class aren’t accessible using reference of superclass:
class Super{ }
// anon class in main()
Super s = new Super(){
void getSup(){ }
};
s.getSup(); // compiler-error; cannot resolve method
Hence, we mostly use anon classes for overriding methods (of super class or implementing interface ones).
public class X{
public char foo(){
return 'A';
}
}
public interface Y{
public abstract char bar();
}
public class Z extends X implements Y{
char n = 'C';
char bar(){ return 'B'; }
}
Z o = new Z(); // self ref
o.bar(); // B
System.out.println(o.n); // C
Y p = o; // interface ref
p.bar(); // B
p.n; // error
X q = p; // parent ref
System.out.println(o.n); // error
q.foo(); // A
q.bar(); // error
// Only one object was created in heap memory, but we see so many access levels depending on ref variable we use to access it
Dog d = new Dog();
Cat c = d; // error; incompatible types: Dog can't be called a Cat
// Casting
Child c = new Child();
Parent p = c; // implicit cast to supertype, storing in ref of Parent
Child c2 = p; // error (can't cast from super type to subtype even if object is Child's)
Child c3 = (Child)p; // explicit cast to subtype; p is Child's object only, but stored in ref of Parent, that's why this explicit cast works otherwise compiler error
// a ClassCastException is thrown at runtime if they're NOT COMPATIBLE and explicit cast is used like in the below example
Object i = Integer.valueOf(42);
String s = (String) i; // ClassCastException thrown here
// compiler throws error on UNRELATED type casts like in the below example
class Foo{ }
class Bar{
public static void main(String args[]){
Foo o1 = new Foo();
Bar o2 = (Bar)o1; // error; unrelated class objects
}
}
While holding a reference to a class object its not possible to tell if its compatible with an interface reference since some subclass might be implementing that interface, so identifying bad casts at compile-time isn’t possible with interfaces. There is one exception to this, it is listed below the following code.
public interface FourLegged{ }
public interface Canine{ }
public class Wolf implements Canine{ }
Canine c = new Wolf(); // or Wolf w = new Wolf();
FourLegged dog = (FourLegged)c; // compiles just fine; even tho both interface are unrelated
// throws ClassCastException at runtime
// in the above case, class Wolf may have a subclass Dog which implements FourLegged interface, in that case above will be valid if "Canine c = new Dog();"
// this is why it is allowed to compile; since that subclass will be both a "Canine" and a "FourLegged" (multiple inheritance using interface)
// EXCEPTION - if class is marked "final" then the compiler will know that there are no possible subclasses that might implement an interface we are casting to, so in that case it leads to a compile-time error
// instanceof operator checks for compatible types and returns boolean; prevents ClassCastException at runtime
if(obj instanceof FoobarClass){ }
// applying it to unrelated types leads to compile-time error
// instanceof on null always returns false
null instanceof String
// false