Wednesday, June 9, 2021

Java Tutorial: Class

Chapters

Java Class

Class is one of the cornerstones of java and it defines the state and behavior of objects. Class is the blueprint/template of an object and object is an instance of class or class instance. Class defines a new data type. Using class, we can create our own object type that we can use to strengthen and enhance our program. Basically, classes is comprised of methods, instance and variables or fields, These are called class members. Class also has constructor. A constructor is a special class member that instantiate(create) a class instance(object). Let's look at this example:
public class SampleClass{
 //fields or instance variables
 public int field1;
 public String field2;
 public double field3;
 
 //constructor
 public SampleClass(){}
 
 //method
 public void classMethod(){
 }
}
A top-level class is a class that is on the very top of other classes and it's not nested to another class. In the example above, SampleClass is a top-level class because it's the very first class to be defined and not nested to any class.

Once we created a class, we can instantiate it by using the new keyword and access its members. Instantiation is a way to create an object(class instance) out of a class.
public class SampleClass{
 //fields or instance variables
 public int field1;
 public String field2;
 public double field3;
 
 //constructor
 public SampleClass(){
   System.out.println("Initializing object using constructor.");
 }
 
 //method
 public void classMethod(){
  System.out.println("SampleClass Method");
 }
 
 public static void main(String[]args){
   SampleClass sc = new SampleClass();
   System.out.println("field1: " + sc.field1);
   sc.classMethod();
 }
}

Result
Initializing object using constructor
field1: 0
SampleClass Method
Take a look at this code:
SampleClass sc = new SampleClass();
We use the new keyword to call a constructor of a class and the constructor creates an object(class instance) out of the class. If we assign class instantiation to a variable with the same type as the class, then once the object is created, its reference of address will be passed to the variable. Thus, we can use that reference to access the created object and its content(members).

If we don't define a constructor in a class, java will implicitly create a constructor with zero parameters. Take a look at this example.
public class SampleClass{
  
  void method(){
    System.out.println("Hello!");
  }
  
  public static void main(String[] args){
    SampleClass sc = new SampleClass();
    sc.method();
  }
}

Result
Hello!
As you can see from the example above, a constructor with zero parameters can be invoked even we didn't define one in our source code.

Take note that if we explicitly create a constructor with parameters or not, java won't implicitly create a constructor with zero parameters. Take a look at this example.
public class SampleClass{
  
  SampleClass(String msg){
    System.out.println(msg);
  }
  
  public static void main(String[] args){
    //will throw an exception
    SampleClass sc = new SampleClass();
  }
}


Depending on access modifier, Some class members can't be accessed.
public class SampleClass{

 public static void main(String[]args){
   Message msg = new Message();
   msg.createMessage();
   msg.sayHi();
 }
}

class Message{
 
 public void sayHi(){
   System.out.println("Hi!");
 }
 
 private void createMessage(){
   System.out.println("Message");
 }
}
This code will throw a compile-time error. methods,variables,etc. that have private access-modifier are only allowed to be accessed in class definition.

Differences Between Constructor and Method

1.) Constructor's task is to instantiate class whereas method's task depends on programmer's implementation.

2.) Constructor doesn't have return type whereas method does.Despite the fact that constructors don't have return type, they still return a value and that value is the reference or address of class instance.

3.)Constructor name must be the same to its class name whereas method name is defined by programmers.

Anonymous Object

Anonymous object is an object that is not assigned to any variable. Anonymous object is short lived.
public class SampleClass{
  
  void message(){
    System.out.println("SampleClass");
  }
  
  public static void main(String[]args){
    new SampleClass().message();
  }
}

The this Keyword

We use the this keyword to refer to a class instance where the keyword is used.
Example

public class SampleClass{

  public static void main(String[]args){
     MyClass mc = new MyClass();
 
     if(mc.getInstance() instanceof MyClass)
       System.out.println("mc is instance of MyClass");
  }
}

class MyClass{

  public MyClass getInstance(){
   System.out.println("Returning MyClass Instance...");
   return this;
  }
}

Result
Returning MyClass instance...
mc is instance of MyClass
We can also use the this keyword to access class instance members and resolve member hiding. Member hiding or shadowing is a phenomenon where local variables,parameters and instance variables have the same name which causes instance variables to be hidden by local variables and parameters.
public class SampleClass{

  public static void main(String[]args){
     MyClass mc = new MyClass();
     mc.setNum(4);
     
     System.out.println("num value: " + mc.getNum());
  }
}

class MyClass{
  private int num;
  
  public void setNum(int num){
    this.num = num;
  }
  
  public int getNum(){ return num; }
}

Result
num value: 4
This code worked fine. Now, remove the this keyword.
 
public void setNum(int num){
  num = num;
}

Result
num value: 0
The value is 0 because the parameter "num" of setNum() hid the "num" instance variable of MyClass. Thus, parameter "num" assigned the value of 4 to itself. To resolve member hiding, we can name instance,local variables and parameters differently to avoid equivalent names or we can use the this keyword.

Using the this keyword is preferrable to solving member hiding. Thinking of unique names is hard especially when building big applications. Though, there are times that naming variables is preferrable. Take a look at this code.
public class SampleClass{
  int num = 10;
  
  void checkDivisibility(int divisible){
    final int num = 5;
    
    class Divisible{
      int num = 20;
      
      void divisibility(){
        if(this.num % divisible == 0)
         System.out.println(this.num + " is "+
                            "divisible by " + divisible);
                            
        if(SampleClass.this.num % divisible == 0)
         System.out.println(SampleClass.this.num + " is "+
                            "divisible by " + divisible);
                            
        if(num % divisible == 0)
         System.out.println(num + " is "+
                            "divisible by " + divisible);
      }
      
    }
    
    new Divisible().divisibility();
  }
  
  public static void main(String[]args){
    new SampleClass().checkDivisibility(5);
  }
}

Result:
20 is divisible by 5
10 is divisible by 5
20 is divisible by 5
As you can see, num in Divisible clas has been accessed two times. To access the local variable num in checkDivisibility() method it's better to change its name. Divisible class in checkDivisibility() method is a local class.

The super Keyword

This topic requires an understanding of inheritance. We can use the super keyword to access visible super class members like constructors, instance variables, etc. Visibility of class members depends on their access modifiers. Let's look at this example.
public class SampleClass{

  public static void main(String[]args){
    Message msg = new Message("Hello!");
    
    msg.sayMessage();
  }
}

class Text{
  private String characters;
  
  Text(String characters){
    this.characters = characters;
  }
  
  String getChars(){ return characters; }
}

class Message extends Text{
  
  Message(String chars){
    super(chars);
  }
  
  public String getChars(){
   String chars = super.getChars();
   
   if(chars.length() > 10)
     return "Too much characters!";
     
   return chars;
  }
  
  public void sayMessage(){
    System.out.println("Message: " + getChars());
  }
}
We can achieve constructor chaining using super keyword and we can access hidden members of super class. Take note that hidden and visible are different, hidden members are visible to a class but some members of the class get in front of them whereas visibility refers to accessibility of members to a class.
public class SampleClass{

  public static void main(String[]args){
    ClassTwo ct = new ClassTwo();
    
    ct.showNum();
  }
}

class ClassOne{
  int num = 1;

}

class ClassTwo extends ClassOne{
  int num = 2;
  
  public void showNum(){
    System.out.println("num in ClassOne: " + super.num);
    System.out.println("num in ClassTwo: " + num);
  }
}

Result
num in ClassOne: 1
num in ClassTwo: 2
This code above is an example of hidden members. As you can see, num in ClassOne is accessible but we need to use super to access it because num in ClassTwo is in front of num in ClassOne. Next, let's look at this example.
public class SampleClass{

  public static void main(String[]args){
    ClassTwo ct = new ClassTwo();
    
    ct.showNum();
  }
}

class ClassOne{
  private int num = 1;

}

class ClassTwo extends ClassOne{
  int num = 2;
  
  public void showNum(){
    System.out.println("num in ClassOne: " + super.num);
    System.out.println("num in ClassTwo: " + num);
  }
}
This code above will throw a compile-time error due to visibility of num in ClassOne. num in ClassOne is not visible to ClassTwo because it has private access modifier. This is an example of visibility.

Constructor Overloading

Constructors can't be overriden but they can be overloaded.
public class SampleClass{

 public static void main(String[]args){
   MessageCreator mc = new MessageCreator();
   mc.sayHi();
   mc.sayBye();
   mc = new MessageCreator("Hi! How are you?","See you later!");
   mc.sayHi();
   mc.sayBye();
 }
}

class MessageCreator{
 private String hi;
 private String bye;
 
 MessageCreator(){
  hi = "Hi!";
  bye = "Bye!";
 }
 
 MessageCreator(String hi,String bye){
   this.hi = hi;
   this.bye = bye;
 }
 
 void sayHi(){
   System.out.println(hi);
 }
 
 void sayBye(){
   System.out.println(bye);
 }
 
}

Result
Hi!
Bye!
Hi! How are you?
See you later!
Like method overloading. Overloaded constructor can't be identical. Their parameters must not be the same.
The this keyword refers to the class in which the keyword is used. In the example above, this keyword refers to SampleClass class. this keyword can resolve member hiding.
Try removing the this keyword in the example above and the result is going to be:
Hi!
Bye!
null
null
When we instantiate SampleClass using the constructor with parameters, the hi and bye parameters assign their values to themselves. It happens because the hi and bye instance variables are not visible in the scope of the constructor with parameters due to identical names between the constructor's parameters and SampleClass instance variables.

Constructor Chaining

The process of calling one constructor from another is called constructor chaining. There are two ways to do constructor chaining: chaining within the same class and chaining using inheritance. Let's try the first method.
public class SampleClass{

  public static void main(String[]args){
    Square sq = new Square(50f,150f,75f,75f);
  }
}

class Square{
 private float posX;
 private float posY;
 private float width;
 private float height;
 
 public Square(float posX, float posY, float width, float height){
   this(posX,posY);
   
   this.width = width;
   this.height = height;
   
   System.out.println("size is set: ");
   System.out.println("Width: " + this.width);
   System.out.println("Height: " + this.height);
 }
 
 private Square(float posX, float posY){
   this.posX = posX;
   this.posY = posY;
   
   System.out.println("Positions are set: ");
   System.out.println("Position X: " + this.posX);
   System.out.println("Position Y: " + this.posY);
 }

}
One of the usage of constructor chaining is to reduce the numbers of statements of some constructors and distribute the other statements in other constructors to make our code more readable. In the example above, I put statements that are related to Square's position in the Square() contructor with two parameters. Then, I put statements that are related to Square's size in the Square() constructor with four parameters. Constructors can be chained in any order.

There are rules that we need to follow when implementing constructor chaining within class:
1.) this statement must be the first statement in constructor.
2.) There must be one constructor in a chain that doesn't have this statement.
Java will throw compile-time errors if we don't follow the rules above.

Next, let's try the second method of constructor chaining. This method requires an understanding of inheritance.

import java.awt.geom.Point2D;

public class SampleClass{

  public static void main(String[]args){
    Point2D[] points = {new Point2D.Float(50f,100f), 
                        new Point2D.Float(100f,100f),
                        new Point2D.Float(100f,150f),
                        new Point2D.Float(50f,150f) };
    Rectangle rec = new Rectangle(points);
  }
}

class Shape{
  private String name;
  
  Shape(String name){
    this.name = name;
    System.out.println("Shape name is set.");
    System.out.println("Shape name is: " + name);
  }
}

class Polygon extends Shape{
  private Point2D[] nPoints;
  
  Polygon(String name,Point2D nPoints[]){
    super(name);
    this.nPoints = nPoints;
    
    System.out.println("Points are set.");
  }
  
}

class Rectangle extends Polygon{
 
 public Rectangle(Point2D[] p){
   super("Rectangle",p);
   System.out.println("Rectangle is set and ready to use!");
 }

}
super statement can call any constructors of a super class as long as those constructors are visible. The visibility of constructors or any class members depend on their access modifiers.

Point2D is a pre-defined abstract class in java that holds x and y coordinates. Point2D.float is a static nested class of Point2D.

Note: this and super statement can't exist at the same time in a constructor. These two need to be the first statement in the constructor.

Initializer Block

We can use initializer or init block as an alternative to constructor. Unlike constructors, init block can't take any parameters and they are initialized before constructors are initialized. Though, initializing variable using assignment operator comes first before the initializer block.
public class SampleClass{
  int num;
  
  //correct way of initializing variable
  //in class block using assignment operator
  String name = "SampleClass";
  //can't do this assignment directly in class
  //or interface block
  //name = sample //compile-time error
  
  double decimal;
  
  //init block
  {
    System.out.println("name: " + name);
    System.out.println("initializing init block");
    num = 2;
    decimal = 3.5;
  }
  
  public SampleClass(){
    System.out.println("initializing constructor");
  }
  
  public void showValues(){
    System.out.println(num);
    System.out.println(name);
    System.out.println(decimal);
  }
  
  public static void main(String[]args){
    SampleClass sc = new SampleClass();
    
    sc.showValues();
  }
}
Everytime we create an instance of a class, init block will be automatically initialized.We can have multiple initializer blocks and place them anywhere in the class block.
public class SampleClass{
  int num;
  String name;
  double decimal;
  
  //init block1
  {
    System.out.println("initializing init block1");
    num = 2;
    name = "SampleClass";
  }
  
  public SampleClass(){
    System.out.println("initializing constructor");
  }
  
  public void showValues(){
    System.out.println(num);
    System.out.println(name);
    System.out.println(decimal);
  }
  
  public static void main(String[]args){
    SampleClass sc = new SampleClass();
    
    sc.showValues();
  }
  
  //init block2
  {
    System.out.println("initializing init block2");
    decimal = 3.5;
  }
}
We can use multiple blocks if we wanna reduce an amount of statements of some blocks and distribute the other statements to other blocks. Java evaluates initializer blocks from top to botoom of a class. We put the initializer block that we wanna be evaluated first at the top of a class. Try putting init block2 on top of init block1 and init block2 will be initialized first before init block1.

Static Block

We can use static block to initialize static members of a class. This block is like the initializer block but for static class members.
public class SampleClass{
  static int num;
  static String name;
  static double decimal;
  
  static{
    System.out.println("Initializing static block1");
    num = 3;
    name = "SampleClass";
  }
  
  static{
    System.out.println("Initializing static block2");
    decimal = 3.5f;
  }
  
  public static void main(String[]args){
    System.out.println("num: " + SampleClass.num);
    System.out.println("name: " + SampleClass.name);
    System.out.println("decimal: " + SampleClass.decimal);
  }
}
Just like init block, we can use multiple static blocks to reduce an amount of statements of some static blocks and distribute those statatements to other static blocks. Java also evaluates static blocks from top to bottom of a class. static blocks are initialized once the class is loaded to JVM.

You might suggest to make a class constructor static instead of using static block. The thing is, we can't make a constructor static. It's not allowed in java to have static constructors. Take a look at this example:
public class SampleClass{

  static SampleClass(){
  }
}
This code will throw a compile-time error. You might wonder why it's not allowed to have a static constructor. You need to have an understanding of inheritance and static keyword to understand the answer. Alright, the answer is simple.

1.) static class members belong to the class itself. The thing is, constructor initializes class instance and if we make the constructor static then we can't create a class instance because class instance has non-static components.

2.) static constructor may break inheritance concept. We know that we can call a constructor of a parent class by using the super keyword. If a constructor can be static then the call to static constructor is erroneous because class instance has non-static components and static constructor, like static block, isn't allowed to access those non-static components which leads to a broken class instance. thus, breaking inheritance.

Abstract Class

You need to have an understanding of inheritance and method overriding to understand this topic. Abstract class is a class that can't be instantiated. a class that has abstract keyword in its class definition is an abstract class. Abstract class can have abstract methods and abstract class preferrable use is to be a parent(super) class.
public class SampleClass{

  public static void main(String[]args){
    Car car = new Car();
    car.engineStart();
    car.engineStop();
  }
}

class Car extends Engine{

  @Override
  public void engineStart(){
    System.out.println("Engine is starting...");
    System.out.println("Engine has started.");
  }
  
  @Override
  public void engineStop(){
    System.out.println("Engine is stopping...");
    System.out.println("Engine has stopped.");
  }
}

//abstract class
abstract class Engine{
  abstract void engineStart();
  abstract void engineStop();
}
In the example above, Car class extended Engine class which is an abstract class. Abstract class and interface can achieve abstraction. Abstract class can still have regular(concrete) methods, fields and constructor.
public class SampleClass{

  public static void main(String[]args){
    Car car = new Car();
    car.engineStart();
    car.engineStop();
    System.out.println(car.getEngineName());
  }
}

class Car extends Engine{
  
  Car(Sring name){
    super(name);
  }
  
  @Override
  public void engineStart(){
    System.out.println("Engine is starting...");
    System.out.println("Engine has started.");
  }
  
  @Override
  public void engineStop(){
    System.out.println("Engine is stopping...");
    System.out.println("Engine has stopped.");
  }
}

//abstract class
abstract class Engine{
  private String engineName;
  
  Engine(){
    engineName = "default";
  }
  
  Engine(String engineName){
    this.engineName = engineName;
  }
  
  abstract void engineStart();
  abstract void engineStop();
  public String getEngineName(){ return engineName; }
}
Even though Engine class can have constructors, Those constructors can only be used by the subclass of the Engine class which is the Car class. Due to the fact that abstract class can have regular(concrete) methods, abstract class can achieve partial abstraction.

In the example above, Engine class achieved partial abstraction. In the first example before the example above, Engine class achieved full abstraction because it only has abstract methods.

Abstract class can extend another abstract class.
import java.awt.geom.Point2D;

public class SampleClass{

  public static void main(String[]args){
   Point2D[] p = {new Point2D.Float(100f,150f),
                  new Point2D.Float(150f,50f),
                  new Point2D.Float(200f,150f)};
                  
   Triangle tri = null;
   if(p.length == 3)
     tri = new Triangle(p);
   else System.out.println("Number of points doesn't"
                           +" represent a triangle");
  }
}

abstract class Shape{

 protected abstract void initShape();
 abstract void translate();
 abstract void rotate();
}

abstract class Polygon extends Shape{
  protected Point2D[] nPoints;
  
  Polygon(){
    initShape();
  }
  
  @Override
  protected void initShape(){
   System.out.println("Initializing Shape...");
  }
  
  protected abstract void initPolygon(Point2D[] nPoints);
  
  //Empty implementations
  @Override
  void translate(){}
  void rotate(){}
}

class Triangle extends Polygon{
  
  Triangle(Point2D[] nPoints){
    System.out.println("Initializing Triangle's components...");
    initPolygon(nPoints);
    System.out.println("Initializing Triangle...");
    System.out.println("Triangle is initialized and ready to use!");
  }
  
  @Override
  protected void initPolygon(Point2D[] nPoints){
    this.nPoints = nPoints;
    System.out.println("Initializing Polygon with " + 
                       nPoints.length
                       +"points");
  }
  
  @Override
  void translate(){
   System.out.println("Translating triangle...");
   System.out.println("Translation Complete!");
  }
}
One of the good things about extending an abstract class to another abstract class is that, we can override abstract methods of a parent abstract class in its abstract sub class with empty implementation and then optionally override those abstract methods in their concrete subclass.

In the example above, Polygon overrides Shape's translate() and rotate() abstract methods then the Triangle class can optionally override translate() or rotate() if needed. Try removing the empty implementation of rotate() from Polygon class and the compiler will throw an error which is requiring to override rotate() in Polygon or Triangle class.

Nested Class

Reference: The Java™ Tutorials
Nested class is a class that is defined inside a class block. Nested class is divided into two types: non-static nested class which is also called inner class and static nested class which is also called static nested class.

Inner Class

Inner class is a non-static class that is nested in another class.
public class SampleClass{
  
  public static void main(String[]args){
  
    Message msg = new Message();
    System.out.println("Is there a message? " + msg.messageExist());
    
    Message.MessageReceiver mr = msg.new MessageReceiver();
    Message.MessageSender ms = msg.new MessageSender();
    mr.receive("Hello Everybody!");
    System.out.println("Is there a message? " + msg.messageExist());
    System.out.println("Here's the message: " + ms.send());
  }
}

class Message{
  private String rawMsg;
  
  private String filteredMessage(){
    String msg = null;
    
    if(!rawMsg.equals("bad"))
      msg = rawMsg;
    else msg = "Message can't"+
               " be displayed.";
    return msg;
  }
  
  public boolean messageExist(){
  
    if(rawMsg != null) return true;
    else return false;
    
  }
  
  class MessageSender{
    
    String send(){
      String msg = null;
      
      if(rawMsg != null){
        msg = filteredMessage();
        rawMsg = null;
      }
      else
       msg = "No message received.";
       
      return msg;
    }
  }
  
  class MessageReceiver{
    
    void receive(String rawMsg){
      System.out.println("Receiving message...");
      Message.this.rawMsg = rawMsg;
      System.out.println("Message received!");
    }
  }
}
There are lots of interesting things going on in the example above. Take a look at these codes:
Message msg = new Message();
Message.MessageReceiver mr = msg.new MessageReceiver();
Message.MessageSender ms = msg.new MessageSender();

First, we need to instantiate Message class then we can instantiate its inner classes like MessageReceiver and MessageSender. First, we need to declare a variable with the inner class type, to do that we need to write the outer class name, in this case is the Message class then use dot and then write the inner class name and then the variable name.

To instantiate an inner class, we need to call its constructor. To do that write the reference of the instance of outer class, In this case, msg holds the reference instance of Message class which is the outer class of the inner classes. Then, call the inner class constructor. We can also see in the example that inner classes can have access to outer class members regardless of access-modifier.

Take a look at this another interesting syntax:
Message.this.rawMsg = rawMsg;
I already explained member hiding or shadowing in the this keyword topic. In the receive() method, rawMsg in receive() and rawMsg in Message class have the same name, Thus, shadowing occurs. For class methods and constructor we resolve shadowing by writing this expression: this.field-name.

When it comes to inner class enclosing scope or block, we need to include the outer class name first before writing the this keyword and the field name. Try removing the outer class name in this statement: Message.this.rawMsg = rawMsg; in the example above and you will encounter a "cannot find symbol" error. That error indicates that rawMsg can't be found in MessageReceiver() class.

Inner class can't have static members because inner classes are associated with class instance.
public class SampleClass{

  class InnerOne{
    //compile-time error
    static int num;
  }
}
If you look at the directory where you compiled the example above, you will see these generated .class files:
Message$MessageReceiver.class
Message$MessageSender.class
These files are the inner classes that we defined in the example above. Java strongly discouraged the serialization of inner, local and anonymous classes because those classes create synthetic constructs: constructs that don't correspond to the source code construct.
This is the code construct of MessageReceiver inner class that has been decompiled.
class MessageReceiver
{
    MessageReceiver(final Message this$0) {
        this.this$0 = this$0;
    }
    
    void receive(final String rawMsg) {
        System.out.println("Receiving message...");
        this.this$0.rawMsg = rawMsg;
        System.out.println("Message received!");
    }
}
As you can see, the code construct is kinda unusual.

This is the code construct of MessageReceiver static nested class that has been decompiled.
static class MessageReceiver
{
    void receive(final Message message, final String rawMsg) {
        System.out.println("Receiving message...");
        message.rawMsg = rawMsg;
        System.out.println("Message received!");
    }
}
As you can see, Static nested class code construct is pretty normal.

Static Nested Class

Static nested class is a static class that is nested in another class.
public class SampleClass{
  
  public static void main(String[]args){
  
    Message msg = new Message();
    System.out.println("Is there a message? " + msg.messageExist());
    
    Message.MessageReceiver mr = new Message.MessageReceiver();
    Message.MessageSender ms = new Message.MessageSender();
    mr.receive(msg,"Hello Everybody!");
    System.out.println("Is there a message? " + msg.messageExist());
    System.out.println("Here's the message: " + ms.send(msg));
  }
}

class Message{
  String rawMsg;
  
  private String filteredMessage(){
    String msg = null;
    
    if(!rawMsg.equals("bad"))
      msg = rawMsg;
    else msg = "Message can't"+
               " be displayed.";
    return msg;
  }
  
  public boolean messageExist(){
  
    if(rawMsg != null) return true;
    else return false;
    
  }
  
  static class MessageSender{
    
    String send(Message message){
      String msg = null;
      
      //static inner class can't directly access
      //non-static members of outer class
      //This syntax will cause error
      //msg = rawMsg; 
      
      if(message.rawMsg != null){
        msg = message.filteredMessage();
        message.rawMsg = null;
      }
      else
       msg = "No message received.";
       
      return msg;
    }
  }
  
  static class MessageReceiver{
    
    void receive(Message message,String rawMsg){
      System.out.println("Receiving message...");
      message.rawMsg = rawMsg;
      System.out.println("Message received!");
    }
  }
}
This example is similar to the inner class example with slight changes. Take a look at this comment first:
//static inner class can't directly access
//non-static members of outer class
//This syntax will cause error
//msg = rawMsg;
Static nested class can't directly access non-static members of outer class, as the comment says. Though, it can indirectly access non-static members (public or non-public) of its outer class by using the reference of its outer class. That's why I changed the send() and receive() parameters and include the instance reference of Message class.

Static nested class behaves like outer class even though they're nested in an outer class.Its behavior is one of the reasons why it can't access non-static members of its outer class. Static nested classes are nested in an outer class for package convinience.



We can also instantiate an outer class in a nested class or a nested class in another nested class.
public class SampleClass{
  
  void message(){
    System.out.println("SampleClass");
  }
  
  private class InnerOne{
    SampleClass sc = new SampleClass();
   
    void message(){
      System.out.println("InnerOne");
    }
  }
  
  class InnerTwo{
    InnerOne io = new InnerOne();
    
    InnerTwo(){
      io.sc.message();
      io.message();
      message();
    }
    
    void message(){
      System.out.println("InnerTwo");
    }
  }
  
  public static void main(String[]args){
    SampleClass sc = new SampleClass();
    SampleClass.InnerTwo it = sc.new InnerTwo();
    
  }
}

Local Class

Reference: The Java™ Tutorials
Local class is a class that is defined in a block like method block. Remember, When a non-static class is nested in a top-level(outer) class then that class is an inner class. If it's nested in a block like method block then that class is a local class.
public class SampleClass{
  int num = 10;
  
  void checkDivisibility(int divisible){
    final int number = 5;
    //effectively-final variable is a variable
    //that is initialized once and never change
    //since.
    // Valid in JDK 8 and later
    int numbers = 25;
    
    //uncomment this statement and an error will occur
    //due to the state change of numbers from effectively-final
    //to not final variable
    // Valid in JDK 8 and later
    //numbers = 35;
    
    class Divisible{
      int num = 20;
      
      void divisibility(){
        if(num % divisible == 0)
         System.out.println(num + " is "+
                            "divisible by " + divisible);
                            
        if(SampleClass.this.num % divisible == 0)
         System.out.println(SampleClass.this.num + " is "+
                            "divisible by " + divisible);
                            
        if(number % divisible == 0)
         System.out.println(number + " is "+
                            "divisible by " + divisible);
                            
        if(numbers % divisible == 0)
         System.out.println(numbers + " is "+
                            "divisible by " + divisible);
      }
      
    }
    
    new Divisible().divisibility();
  }
  
  public static void main(String[]args){
    new SampleClass().checkDivisibility(5);
  }
}
As we can see from the example above. Local class can access the members of its enclosing class, local variables, parameters and of course, its own members.Local class captures local variables and parameters of an enclosing block when accessing them. Those captures are known as captured variables. Local variables must be final or effectively-final in order to be accessed by the local class. Try uncommenting this statement: numbers = 35; and you will encounter an error regarding the effectively-final.

Local class is similar to inner class. Just like inner class, local class has shadowing(member hiding) effect. In the example above, num in Divisible class hides num in SampleClass. The shadowing effect in the example above can be solved by using this keyword. Also, local class can't have static initializers or member interfaces. Though, local class can have static members but they must be constants.
public class SampleClass{

  void method(){
  
    class Local{
     static final int num = 5;
    }
  }
}
Remove the final keyword in the constant variable declaration and the compiler will throw a compile-time error.

Anonymous Class

Reference: The Java™ Tutorials
This topic requires an understanding of interface and local class. Anonymous class is the way of defining class in a form of expression. It has similarities to local class and it doesn't have a name. Take a look at this example.
interface Divisible{

  void divisibility();
}

public class SampleClass{
  int num = 10;
  
  void checkDivisibility(int divisible){
    final int number = 5;
    //effectively-final variable is a variable
    //that is initialized once and never change
    //since.
    // Valid in JDK 8 and later
    int numbers = 25;
    
    //uncomment this statement and an error will occur
    //due to the state change of numbers from effectively-final
    //to not final variable
    // Valid in JDK 8 and later
    //numbers = 35;
    
    //Anonymous Class
    Divisible d = new Divisible(){
      int num = 20;
      
      @Override
      public void divisibility(){
        if(num % divisible == 0)
         System.out.println(num + " is "+
                            "divisible by " + divisible);
                            
        if(SampleClass.this.num % divisible == 0)
         System.out.println(SampleClass.this.num + " is "+
                            "divisible by " + divisible);
                            
        if(number % divisible == 0)
         System.out.println(number + " is "+
                            "divisible by " + divisible);
                            
        if(numbers % divisible == 0)
         System.out.println(numbers + " is "+
                            "divisible by " + divisible);
      }
      
    };
    d.divisibility();
  }
  
  public static void main(String[]args){
    new SampleClass().checkDivisibility(5);
  }
}
You might ask: "Why we can instantiate Divisible interface? Interface can't be instantiated right?".
In this example, we didn't instantiate Divisible interface but we instantiated an "anonymous class" that implicitly implements Divisible interface.

Just like local class, anonymous class can acess members of its enclosing class, local variable that is final or effectively-final and method parameters.

Anonymous class also has shadowing effect. Local class and anonymous class have the same restrictions. We can add fields, extra methods, instance initializers but constructor is not allowed and local classes in anonymous class block.

Anonymous class is very much used in java. Most of the time, they're used in conjunction with interface. Here are some excerpts.
Example #1

Thread thread = new Thread(new Runnable(){

  @Override
  public void run(){
   System.out.println("Thread is running");
  }
});

Example #2

addActionListener(new ActionListener(){

  @Override
  public void actionPerformed(ActionEvent e){
    System.out.println("Button Pressed.");
  }
});
The syntax of anonymous class is confusing sometimes but we can break it down to understand it better. Let's start by not putting codes in anonymous class block.
addActionListener(new ActionListener(){//put codes here} );
Then combine the right brace with the right parenthesis, make space and put codes in the block.
addActionListener(new ActionListener(){

  @Override
  public void actionPerformed(ActionEvent e){
    System.out.println("Button Pressed.");
  }
  
});

No comments:

Post a Comment