interface
and abstract class
)animal.speak()
method call)final
))Why Java isn’t a purely OOP language?: Because a purely OOP language needs to have every data type as an object, and everything we do on those objects must be done via calling methods.
Java fails this criteria because of:
static
methods and fields as they can be called without an instanceSmalltalk is considered the first purely OOP language. While Simula is considered the first OOP language.
public class Demo {
int a = 5; // instance field
void foo() { // instance method
return;
}
public Demo(int x) { // constructor
a = x;
}
public static void main(String[] args) {
Demo o = new Demo(6);
System.out.println(o.a); // 6
}
}
All eligible fields and methods are inherited by the Children from Parents i.e. they can be accessed from the subclass.
Note that inheritance doesn’t create a “different copy” of members and methods of superclass inside the subclass, rather it makes all eligible members and methods accessible via the subclass. This is possible because a superclass instance is created (Instantiation chain) within the subclass’s instance when we explicitly create a subclass instance.
class X { }
class Y extends X { }
// member inheritance
class X {
int a = 5;
}
class Y extends X {
void print(){
System.out.println(this.a + "," + super.a);
}
}
// in Main class
Y y = new Y();
y.a = 6; // variable "a" is accessible in Y; its inherited
y.print();
// Output: "6, 6"
// method inheritance - same as member inheritance
class X {
String foo() {
return "Lorem Ipsum";
}
}
class Y extends X {
void bar() {
System.out.println(foo()); // "Lorem Ipsum"; but its calling Y's foo() method since it was inherited!
}
}
NOTE: static
methods belong to class, but they are inherited just like any other method. Reason - they are part of the “blueprint”, this way the child also gets its very own static method through inheritance.
private
methods of a superclass are not inherited by subclass (unaccessible) but they can be accessed using inherited eligible methods in the subclass.
// only method caller() is inherited by the Child class
// but it can access private members and methods of Parent class too via public method
public class Parent {
public void caller() {
printHello();
}
private void printHello() {
System.out.println("Parent");
}
}
public class Child extends Parent { }
public class Main {
public static void main(String[] args) {
Child c = new Child();
// calling inherited public method of Child
c.caller();
}
}
// Output: "Parent"
// this is how Getters and Setters work (access private members)
private String name;
public void setName(String name){
this.name = name;
}
public String getName(){
return name;
}
public
and package (default) access modifiers are allowed on a top-level class. A class with no access modifier (package) access can only be accessed inside the same package.final
- class can’t be extended; leads to compiler errorabstract
- may contain absract methods, and requires a concrete subclass to instantiateAll classes in Java inherit from java.lang.Object
class and it is only class in Java which doesn’t have a parent class. Due to this the methods toString()
, equals()
, and hashcode()
are available to all classes to override and implement themselves.
The compiler automatically puts the extends
code that does this inheritance.
Top-level classes can’t be private
or protected
(only Nested ones can be). Only one top-level class can be public
in a .java file.
String name = "John";
//setter
void setFoo(String name){
name = name; //set to itself (local var only)
}
In the above case, the name is set to "John"
since Java thinks we are assigning value to name
variable itself. Use this
to solve this issue.
String name = "John";
//setter
void setFoo(String name){
this.name = name; // set to instance variable
}
// we can use this just like a reference variable - pass in method arguments, call methods using it, we can even use "return this"
super - just like this
but used to refer to parent class.
In inheritance, this
can access both parent and current (subclass) members, but super
can only access parent class members. This is trivial as child can access parent data but not the other way round.
class Foo{
int a = 5;
int c = 3;
}
class Bar extends Foo{
int a = 8; // variable hiding
int b = 9;
void printer(){
System.out.println(this.a); // 8
System.out.println(super.a); // 5
System.out.println(this.b); // 9
System.out.println(this.c); // 3
System.out.println(super.b); // compiler-error
}
}
super
comes in real handy when we call overriden methods in parent from child and hidden members & methods from parent in child.
super
void
abstract
, final
, or static
class Num{
int n;
public Num(){
n = 5;
}
}
new
and the object is allocated memory after setting the default values acc to constructornew
, when we use new
though, a new object is created in memoryEvery Java class has a default constructor wheather we code it or not.
Default constructor has no parameters (no args).
The default constructor is inserted only when no explicit constructor is defined.
We can use this to call constructor of current object and not create a new object in memory while doing so.
public Demo(int a, char c){ } // const 1
public Demo(int b){ // const 2
this(b, 'A'); // calling const 1 here
}
NOTE: The this()
call has to be the very first non-comment statement in the constructor body. The side-effect is that there can only be one call to this()
in any constructor.
Cyclic constructor calls: When there is only one constructor and we use this()
inside it, it is similar to infinite loop but here compiler throws error.
public Demo(int a){
this(4); // compiler error
}
public Demo(int a){
this(); // compiler error
}
Calls direct parent’s construtor. Any constructor that matches argument is called if multiple are present.
The first call in any constructor has to be super()
or this()
, always. Java compiler inserts an empty super()
call if they aren’t there!
The problem arises when there is no default constructor in parent class and we allow compiler to insert super()
call in subclass. It doesn’t compiler since no matching constructor is found in parent.
So its a good practice to leave a no-args constructor in every class.
class X{
public X(int a){ }
}
class Y extends X{ }
// in Y, super() is inserted by compiler and called, but X only has a parameterized constructor and no empty constructor (not even default constructor)
// compiler error ensues
When a constructor is made private, it can’t be called from other class, this means that we won’t be able to instantiate the class from any other class. And since a constructor is present, the default one won’t be generated by Java too. We won’t be able to extend this class too because the constructor is hidden from the super()
call from subclass.
A private constructor can be called from inside the class though and it is often used to make a class Singleton.
// 1. using new
Demo o = new Demo();
// 2. using newInstance()
Demo o = (Demo)Class.forName("com.test.Demo").newInstance();
// 3. using clone()
Demo o1 = new Demo();
// creating clone of above object
Demo o2 = (Demo)o1.clone();
// 1. By reference variable
Demo o = new Demo();
o.a = 1;
o.b = 2;
// 2. By a non-constructor method
// 3. By a constructor, default or user-defined
foobar(new Demo()); // initialized but not stored in a reference variable
new
-> Instance (inline, instance initializer block of super, constructor super(), instance initializer block of this, constructor of this resumes)Notice that we call constructor of subclass in new
, and it calls super()
but before it executes, instance initializer block of super
executes and after super()
finishes, instance initializer block of this
runs before this()
is resumed. Example below.
For same type of blocks, order of apperance is tie-breaker.
class GiraffeFamily {
static {
System.out.print("A");
} {
System.out.print("B");
}
public GiraffeFamily(String name) {
this(1);
System.out.print("C");
}
public GiraffeFamily() {
System.out.print("D");
}
public GiraffeFamily(int stripes) {
System.out.print("E");
}
}
public class Okapi extends GiraffeFamily {
static {
System.out.print("F");
}
public Okapi(int stripes) {
super("sugar"); // here
System.out.print("G");
} {
System.out.print("H");
}
public static void main(String[] grass) {
new Okapi(1);
System.out.println();
new Okapi(2);
}
}
//Output: AFBECHG
// BECHG
static
members, executing inline and static initializer blocksRules:
static
member declarations are initialized in order of appearancestatic
blocks are executed in order of appearanceclass Y{
static {System.out.print("A");}
}
class X extends Y{
public static void main(String args[]){
System.out.print("C");
new X();
new X();
new X();
}
static {System.out.print("B");}
}
// Output: ABC (exactly once)
// if we move main() from class X to another "friend" class Z
class Z{
public static void main(String args[]){
System.out.print("C");
new X(); // init class X now; upon use
}
static{ System.out.print("E"); }
}
//Output: ECAB (exactly once)
No default value.
final
variables of the three types:
static
) -> must be initialized exactly once during class initializationIf constructor chaining is there, make sure every final
instance variable is initialized before we exit the chain
class Demo{
final int a;
final String b;
public Demo(String s){ // c1
this.a = 5;
}
public Demo(){ // c2
this.b = "John";
}
}
// c1 fails to set value for b; compiler-error
// c2 also fails to set value for a; compiler-error
class Demo{
final int a;
final String b;
{
a = 5;
b = "foobar";
}
}
// works fine; no errors
static
methods are bind to the class in which they are defined but they are inherited, and behave just like you’d expect non-static methods to behave.
class Foo{
static void fun(){
System.out.println("Hello!");
}
}
public class Main extends Foo{
public static void main(String[] args) {
fun(); // prints "Hello!"; accessible directly because it is inherited; can use "Main.fun()" too
}
}
-----------------------------------------------
class Foo{
static void fun(){
System.out.println("Hello!");
}
}
public class Main{ // no inheritance
public static void main(String[] args) {
fun(); // compiler-error; Foo.fun() will work here
}
}
When method is a child class have the same signature as in the parent class. (Signature = name + parameter list)
Rules:
1. Must be exact same signature (name + parameter list)
2. Must be atleast as accessible or more in child class
3. The method in child doesn't declare a checked exception that is new or broader than one in parent class
4. If method returns a value, it must be same or subtype of method in parent class, no broader type
If the method in parent is private and thus not accessible, then any of the above rules don't matter since its not overriding.
Covariant return types: Rule 4 above. CharSequence
can be overriden by String
type (narrower) but not the other way round since CharSequence
is parent interface of String
class. NOTE - This is NOT AUTOBOXING or UNBOXING and thus primitives and their corresponding wrapper classes are incompatible here!.
Overriding a method replaces the parent method on all reference variables except super
:
class P{
void foo(){
System.out.print("A");
}
}
class C extends P{
void foo(){
System.out.print("B");
}
public static void main(String[] args) {
C x = new C();
P y = x;
x.foo();
y.foo();
x.printer();
}
void printer(){
this.foo();
super.foo();
}
}
//Output: BBBA
Accidental Overloading in Child: if methods' signature doesn’t match (Override Rule #1) then it is an overload in child and not override! To explicitly specify that we want override, use @Override
annotation for a compile-time check.
@Override
annotation on top of override method in child lets us know at compile time if no methods matching it is found in parent class. When everything goes fine, it doesn’t impact code in any way.
class X{
public int foo(char c){
return 1;
}
}
class Y extends X{
public int foo(){ // parent method is overloaded in child
return 2;
}
}
// no compiler error unless we place a "@Override" atop Y's foo() to let it know that we want to override
We follow same 4 rules as overriding, a static method is bound to class so it will depend on the reference or classname we use to call it. They can’t be overriden though, only hidden.
Hiding only replaces the parent method on child reference and not on parent’s reference unlike Overriding. We use respective references to access them.
Compilation error if one is marked static
and the other is not (redefinition error)
Also, similar to Overriding, final static
methods can’t be hidden (compilation error if we’re trying to hide a method in subclass that is declared as final static
in parent class).
public class MyClass {
int a = 8;
public static void main(String args[]) {
C x = new C();
P y = x;
x.foo();
y.foo();
}
}
class P{
static void foo(){
System.out.print("A");
}
}
class C extends P{
static void foo(){
System.out.print("B");
}
}
//Output: BA
We can also use super
to call parent’s version of the hidden method.
Defining a variable with the same name as in parent class.
class P{
int a = 1;
}
public class C extends P{
int a = 8;
public static void main(String[] args){
C x = new C();
P y = x;
System.out.println(x.a); // 8
System.out.println(y.a); // 1
}
}
We can also use super
to access hidden parent’s variable.
NOTE - variable hiding is possible even if they are final
in the parent class unlike final
methods (see below section). This is because final
means diff things when it comes to variables and methods.
We cannot override or hide a method declared using final
in the parent class.
This applies to methods that are inherited. Remember how we can mark methods as private
and no overriding can happen. So a method can be private final
and then it can exist in both parent and child independently with the exact same signature.
A class that cannot be instantiated and may have abstract
methods to force overriding by subclasses.
Rules:
1. only instance methods can be abstract, not variables, constructors, or static methods
2. an abstract method can only be declared in an abstract class or an interface
3. the first non-abstract (concrete) class extending from an abstract one must implement ALL the abstract methods it inherits
4. the 4 method overriding rules are followed here too
public abstract class Demo{
public abstract void foo(); // notice that there is no body and a semicolon (;) at the end
}
public abstract class Demo{
public abstract void foo(int a, String b); // with params
}
public abstract class Demo{
public abstract void foo(int, String); // compiler-error; no param names, only type
}
An abstract class can extend from other abstract classes and normal classes too. Multiple inheritance is not allowed, just like a normal class.
An abstract class can implement interfaces without the need to define abstract methods of the interface.
An abstract class can have any members of a typical class like constructors, static members, etc…
Trivial: An abstract class can exist without any abstract methods, but an abstract method must exist inside an abstract class or interface only.
public abstract class X{ } // valid
abstract public class X{ } // valid
public class abstract X{ } // invalid
Constructors exists (even public
ones) and behave the same as in a normal class. But, they are only called via their subclass constructor using super()
since we can’t instantiate abstract classes by using new
explicitly.
If a class is marked final abstract
, it doesn’t make any sense and is a compiler error.
static
methods aren’t overriden but hidden, so using static abstract
on methods is also compiler error. We can have normal static
methods though.
abstract
methods' sole purpose is to get overridden, so making them private
, final
, or static
is compiler-error.
An abstract
class is more closer to a Java class than an interface
in the sense that it can have members, methods, no default modifiers, even public constructors. The only thing is that they can’t be instantiated (new
) and may have abstract
methods. Also, they don’t need to implement abstract
methods of an interface
when they implement
it.
The first non-abstract class to extend an abstract class. It has to implement all the abstract methods inherited to it.
public abstract class X{
abstract void foo();
abstract void bar();
}
public abstract class Y extends X{
void foo(){ }
}
public class Z extends Y{
void bar(){ }
}
// since Z only inherits bar() as abstract, it only needs to provide implementation for it
Make a class immutable to tighten security and not have to deal with concurrency issues.
final
or make all constructors private
(stops inheritance)final private
(immutable and inaccessible members (encapsulation))class X{
private final List<string> foo = new ArrayList<>();
public List<String> getFoo(){
return foo;
}
}
class Y{
public static void main(String args[]){
var obj = new X();
List<String> bar = obj.getFoo();
bar.clear();
bar.add("Malicious Data");
}
}
// solution : add another getter than pinpoints to element in List
public String getFooElement(int index){
return foo.get(index);
}
// defensive copy
class X{
private final List<string> foo = new ArrayList<>();
public X(List<String> foo){
this.foo = foo;
}
}
class Y{
public static void main(String args[]){
List<String> bar = new ArrayList<>();
var obj = new X(bar);
bar.add("Malicious Data");
}
}
// solution: create a defensive copy in constructor
public X(List<String> foo){
this.foo = new ArrayList<String>(foo);
}