0% found this document useful (0 votes)
1 views

week12

This document discusses advanced concepts in Java, focusing on generics, including generic methods, bounded types, wildcards, and the implementation of the Comparable interface. It explains how to define and use generic methods, the significance of bounded types and wildcards for type safety, and how to sort collections using the Comparable and Comparator interfaces. The document provides code examples to illustrate these concepts and their practical applications.

Uploaded by

fatihuzuntas51
Copyright
© © All Rights Reserved
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
1 views

week12

This document discusses advanced concepts in Java, focusing on generics, including generic methods, bounded types, wildcards, and the implementation of the Comparable interface. It explains how to define and use generic methods, the significance of bounded types and wildcards for type safety, and how to sort collections using the Comparable and Comparator interfaces. The document provides code examples to illustrate these concepts and their practical applications.

Uploaded by

fatihuzuntas51
Copyright
© © All Rights Reserved
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 17

Week 12 - More about generics, inner and nested classes

GENERICS cont’d

Generic methods
The previous week, we discussed that we can define generic classes and interfaces. Generics
types can be also utilized to define generic methods. Generic (instance or static) methods can
be defined even in a non-generic class. This allows you to declare type parameters specific to
the method, even if the class itself does not use generics.
access_modifier <T> type methodName(ArgsIncludingTheGenericType)

A good example for a generic static method could be printing some arrays. Printing out the
contents of the arrays is a frequent practice. Instead of accepting an argument relying on a
specific type, we can have a generic type array in the argument list. Below is an example.
public class MyGenericMethodDemo{

public static <T> void printArray(T [ ] array){


for (int index = 0; index < array.length; index++) {
System.out.print(array[index] + ", ");
}
}
}
You can think of <T> as a declaration of type that is going to be used in the argument list.

To invoke a generic method, you should use the type as a prefix, this acts as defining the type
you are going to use in your method. If you omit it the compiler can discover it for you. As you
will see, we omit the type in the second call, Java replaces it with the one commented.
Integer[] integers = {10, 20, 40, 100};
String[] strings = {"Ali", "Mehmet", "Can"};

MyGenericMethodDemo.<Integer>printArray(integers); // you can omit the type prefix, see


//MyGenericMethodDemo.printArray(integers);
System.out.println("null");
MyGenericMethodDemo.printArray(strings); // a call omitting the type is replaced by the
// following by Java → MyGenericMethodDemo.<String>printArray(strings);

Bounded types and wildcards


Once a generic type is intended to be used, the developer actually announces that s/he will
accept any type. However, this might be very challenging at some points where you need some
bounded group of classes. Therefore, you might restrict the types you would like to work with by
specifying the bound.

Bounded types: You can limit the types to either subclasses. The extends keyword defines the
upper bound

1/17
Some examples
1. <T extends GeometricShape> : Restricts the types to any class that is
GeometricShapes.
2. <T extends Iterable> : remember Iterable was an interface and type definition restricts
the types to only classes that implement Iterable.
3. <T extends GeometricShape & Iterable<T>>: type type must be a subtype of the
GeometricShape and implement Iterable.

Wildcards: To represent an unknown type we use wildcards, which is represented by a question


mark, ?.

Similarly, you can define an upper bound with extends:


<? extends GeometricShape>:

You can use super keyword to define a lower bound, for example the below definition allows
only supertypes (this is not possible with bounded types)
<? super Rectangle>: this only allows supertypes of Rectangle. In our example, these are
GeometricShape and Object.

A scenario to use a bounded type


Suppose your non-generic class has a method to calculate a tax rate for the given amount and
tax rate. However, people could send different types of numeric values. The Number is a built-in
reference type in Java and wrapper classes like Float, Integer, Double and many other number
related classes are extended from it. So, in your method defining a generic type that only allows
the subtypes of Number could be logical. doubleValue() is a method in Number that will convert
the value you specified to double. By doing this, your method will work with any type of number.
See the Number documentation here:
https://docs.oracle.com/javase/8/docs/api/java/lang/Number.html
public class MyGenericMethodDemo{
public <T extends Number> double calculateTax(T amount, T rate){
return amount.doubleValue() * rate.doubleValue();
}
}

More on wildcards?*
This example is adopted from here,
https://angelikalanger.com/GenericsFAQ/FAQSections/TypeArguments.html, which explains the
generics in detail.

Consider the following method code segment in our MyGenericMethodDemo class (I skipped
the code for the class to save some space): It copies the contents of one array list to another.
// assume we have method to copy a source arraylist to a destination arraylist
// we use T for a generic type because we do not know what types will be inside

2/17
// each array list
public static <T> void copyTo (ArrayList<T> dest, ArrayList<T> src){
// make sure we clear the destination arraylist
if(dest!=null)
dest.clear();
for (int i = 0; i < src.size(); i++) {
dest.add(src.get(i));
}
}

The following call to the method generates a compile-time error because types do not match.
Review the code snippet to understand that we require the same type.
ArrayList <Integer> src = new ArrayList<>();
src.add(10);
src.add(20);
ArrayList <Object> dest = new ArrayList<>();
MyGenericMethodDemo.<Integer>copyTo(dest, src);
→ an error here because types do not match. dest is Object

We use a generic type T because we do not know what types will be inside the ArrayList. This
approach requires that both the source and destination ArrayList be of the same type, but what
if we want the source and destination lists to hold different types? At this point, we can remove T
that restricts us to supply the same type of arguments and replace it with a wildcard, ?.

See the following code. Be aware that we have removed <T> type declaration after static, too,
because we do not need to define a generic type this type, we want to work with any type.
When we replace T with ?, we tell the method to accept an ArrayList of any type.
public static void copyTo (ArrayList<?> dest, ArrayList<?> src){
if(dest!=null)
dest.clear();
for (int i = 0; i < src.size(); i++) {
dest.add(src.get(i)); → compile time error again
}
}

Why are now getting a compile-time error?


The problem here is that ? means an unknown type, so the compiler does not know if we are
adding Integers, Strings, or other objects. Since the destination is an ArrayList<?>, we cannot
safely use add() because it is unsure of the type being added. src.get(i) will return a type (could
be Object, Integer, etc. do not try to guess it. It is not our intention here) but dest.add() requires
an unknown (a) type but this does not mean it is the type returned by the src.get(i). Assume it is
that Integer, dest.add() does not require that specified type.

How to correct it?

3/17
We should define the unknown type in terms of the type we define in this method. For example,
we require the destination to be any type that is the super class of T and source to be any class
that extends T. When T is Integer and Integer is supplied as a source, for example, any
destination which is an instance of any superclass of Integer is valid. Why? Because T is Integer
because it can be safely cast to any superclass (one of them Number for example)
when used with the dest.add() method.
public static <T> void copyTo (ArrayList<? super T> dest, ArrayList<? extends T> src){
// make sure we clear the destination arraylist
if(dest!=null)
dest.clear();
for (int i = 0; i < src.size(); i++) {
dest.add(src.get(i));

}
}

Now, we can run the code with no problem.


ArrayList <Integer> src = new ArrayList<>();
src.add(10);
src.add(20);
ArrayList <Object> dest = new ArrayList<>();
// copy to Object array list
MyGenericMethodDemo.<Integer>copyTo(dest, src);
MyGenericMethodDemo.printArray(dest.toArray());
System.out.println(" ");

// copy to Number array list


ArrayList <Number> destNumber = new ArrayList<>();
MyGenericMethodDemo.<Integer>copyTo(destNumber, src);
MyGenericMethodDemo.printArray(dest.toArray());
System.out.println(" ");

// copy to Integer array list


ArrayList <Integer> destInteger = new ArrayList<>();
MyGenericMethodDemo.<Integer>copyTo(destInteger, src);
MyGenericMethodDemo.printArray(dest.toArray());
System.out.println(" ");

// however the following is not possible (compile-time error), so it is in comments


/*
ArrayList <Double> destDouble = new ArrayList<>();
MyGenericMethodDemo.<Integer>copyTo(destDouble, src);
MyGenericMethodDemo.printArray(dest.toArray());
System.out.println(" ");
*/

4/17
You can now tell why the last commented code segment produces a compile-time error. It is
because Double does not satisfy <? super T> constraint where T is actually Integer in this
example.

Implementing Comparable interface


We have been using ArrayList, sorting the elements in an array list or any other kind of
collection types. To sort elements of a collection, Collections (java.util.Collections)class
provides a static method. Collections class generally provides static methods that operate on
collections (https://docs.oracle.com/javase/8/docs/api/java/util/Collections.html). The sort
method is one of them, go to the Collections class’s documentation and locate the sort method. I
am giving you the method overloaded method signatures.
● public static <T extends Comparable<? super T>> void sort(List<T> list)
● public static <T> void sort(List<T> list, Comparator<? super T> c)

The sort method sorts the specified list into ascending order, according to the natural ordering of
its elements. Let’s review these method signatures in terms of what we know about generics.

Analysis of public static <T extends Comparable<? super T>> void sort(List<T> list)
The method parameter “List<T> list” wants us to supply a List. List is an interface
(https://docs.oracle.com/javase/8/docs/api/java/util/List.html). ArrayList
(https://docs.oracle.com/javase/8/docs/api/java/util/ArrayList.html) is a subclass of the generic
AbstractList class (https://docs.oracle.com/javase/8/docs/api/java/util/AbstractList.html), which
implements the List interface. So, we can pass an ArrayList into the sort method. The <T> in the
List<T> denotes a generic list. As you know, we can define our own type in our ArrayList.
Assume I want to add instances of GeometricShapes (this could be a Rectangle, Triangle,
Circle, etc.).
// do not forget → import java.util.Collections;
ArrayList<GeometricShape> elements = new ArrayList<>();
elements.add(new Triangle("blue", false, 3, 4, 5));
elements.add(new Rectangle("blue", false, 3, 5));
elements.add(new Square("blue", false, 5));
elements.add(new Circle("blue", false, 2));

Collections.sort(elements); → error
for (GeometricShape geometricShape : elements) {
System.out.println(geometricShape);
}

Why? To understand this, we have to have a full understanding of the method signature. The
sort method wants the T to be a class that implements Comparable interface. T is defined as
GeometricShape (keep this in mind). How do I know this? I understand it through bounded type
definition <T extends Comparable<? super T>>.

5/17
The Comparable interface
(https://docs.oracle.com/javase/8/docs/api/java/lang/Comparable.html) has one abstract method
compareTo as you can see in the documentation. We need to provide an implementation to it.
int compareTo(T o)

According to the documentation, the method should return a negative integer, zero, or a positive
integer as this object is less than, equal to, or greater than the specified object.

Should ArrayList or GeometricShape class hierarchy implement it? Of course, the elements we
are sorting are instances of GeometricShape, we need to implement it at the GeometricShape
level (we can also say here that T is a GeometricShape to explain this). Collections’s sort
method will use this compareTo method to compare if the current object is less than (or equal to
or greater than) the object passed. We do not have to know the implementation details but we
know that we should return a negative integer if the current object is less than, 0 if the current
object is equal to, and a positive integer if the current object is greater than. Let’s do it.

I will compare GeometricShape objects by their areas. It is up to me, the implementor.


public class GeometricShape extends Object implements Comparable<GeometricShape>{
// PLACE THE CODE SKIPPED HERE
// I AM TRYING TO SAVE SOME SPACE

@Override
public int compareTo(GeometricShape o) {
double diff = this.calculateArea() - o.calculateArea();
if (diff > 0)
return 1;
else if (diff < 0)
return -1;
return 0;
}
}

My code implements Comparable<GeometricShape>. Why? Remember the type definition,


<T extends Comparable<? super T>>, our attempt to implement Comparable interface is due
to <T extends Comparable … > part. When to implement Comparable, we see that the type
related to the interface is defined as Comparable<? super T>. <? super T> means the
unknown type should be at least T or any type that is a superclass of T. T is
GeometricShape. So, we implement Comparable<GeometricShape>. If you go back to
check the documentation of Comparable, you will see it is a generic interface. So, I have
defined the type as GeometricShape since we will be dealing with this type of element. Then,
we have implemented the method obeying the method signature. Here, notice how I have
changed the argument type from Object to GeometricShape (marked red). Is subtracting the
area of the current object from the passed one enough for the implementation? Yes, indeed.
Because it gives a positive integer when the current object’s area is larger (which means that

6/17
the current object is greater than the passed object in my implementation), a negative value
when it is less, and 0 when they are equal.

The following code is executed with no problem now.


// do not forget → import java.util.Collections;
ArrayList<GeometricShape> elements = new ArrayList<>();
elements.add(new Triangle("blue", false, 3, 4, 5));
elements.add(new Rectangle("blue", false, 3, 5));
elements.add(new Square("blue", false, 5));
elements.add(new Circle("blue", false, 2));

Collections.sort(elements); for (GeometricShape geometricShape : elements) {


System.out.println(geometricShape);
}

Analysis of public static <T> void sort(List<T> list, Comparator<? super T> c)
The method parameter “List<T> list” wants us to supply a List. What I have just discussed above
is true for here. So, I am skipping to the type of the second parameter, Comparator<? super
T>. Go to this link, https://docs.oracle.com/javase/8/docs/api/java/util/Comparator.html, and see
the abstract methods to implement this interface. By implementing this interface we can define
how our ordering should be in the sort method of the Collections. The interface has two
methods to be implemented, which are int compare(T o1, T o2) and boolean equals(Object
obj). The first abstract method wants us to compare two arguments for order. The return logic is
the same with our compareTo implementation we did with the Comparable interface. The
second method is the equals method. Although it is an abstract method, we do not need to
provide an implementation to it because the Object class has already done so (this does not
mean you should not, you can provide an implementation). So, let’s define two Comparator
implementations. Since the T will be GeometricShape, we must specify a type that fits the ?
super T. So, I am going to implement Comparator<GeometricShape>.

// GeometricShapeAreaComparator.java
import java.util.Comparator;

public class GeometricShapeAreaComparator implements Comparator<GeometricShape>{

@Override
public int compare(GeometricShape o1, GeometricShape o2) {
return o1.compareTo(o2); // we have already have an implementation of comparing
}
}

//GeometricShapePerimeterComparator.java
import java.util.Comparator;

public class GeometricShapePerimeterComparator implements


Comparator<GeometricShape>{

7/17
@Override
public int compare(GeometricShape o1, GeometricShape o2) {
return (int)(o1.calculatePerimeter() - o2.calculatePerimeter());
}
}
You should supply your Comparator instances to the sort method.

ArrayList<GeometricShape> elements = new ArrayList<>();


elements.add(new Triangle("blue", false, 3, 4, 5));
elements.add(new Rectangle("blue", false, 3, 5));
elements.add(new Square("blue", false, 5));
elements.add(new Circle("blue", false, 2));

System.out.println("sort by implementing Comparable");


Collections.sort(elements);
for (GeometricShape geometricShape : elements) {
System.out.println(geometricShape);
}

System.out.println("sort with a comparator to compare by area");


Collections.sort(elements, new GeometricShapeAreaComparator());
for (GeometricShape geometricShape : elements) {
System.out.println(geometricShape);
}

System.out.println("sort with a comparator to compare by perimeter");


Collections.sort(elements, new GeometricShapePerimeterComparator());
for (GeometricShape geometricShape : elements) {
System.out.println(geometricShape);
}

NESTED AND INNER CLASSES


Nested classes, which are classes declared within another class, were introduced in Java
1.1.
A nested class is part of its enclosing class and cannot exist independently. The scope of
a nested class is restricted by the scope of its outer class. A nested class can be declared
directly within its enclosing class as a member, or it can be declared locally within a block (for
example in a method).

Nested classes are divided into two types: static and non-static. Non-static nested classes, also
known as inner classes (many people prefer this term), can directly access and use all
variables and methods of their outer class, similar to other non-static members of the outer class
like methods. Static nested classes do not have access to the instance variables and methods
of the outer class. It acts like a top-level class but is scoped within the outer class.

8/17
Why do we need nested classes
(from: https://docs.oracle.com/javase/tutorial/java/javaOO/nested.html) ?
● It is a way of logically grouping classes that are only used in one place: If a class is
useful to only one other class, then it is logical to embed it in that class and keep the two
together. Nesting such "helper classes" makes their package more streamlined.
● It increases encapsulation: Consider two top-level classes, A and B, where B needs
access to members of A that would otherwise be declared private. By hiding class B
within class A, A’s members can be declared private and B can access them. In addition,
B itself can be hidden from the outside world.
● It can lead to more readable and maintainable code: Nesting small classes within
top-level classes places the code closer to where it is used.

Inner classes (Non-static nested classes)


An inner class can be utilized when a set of services are required only by its enclosing class.
Can you recall such a case in our designs? Let me tell you that our linked list implementation is
a good example of this. Remember our Node class. The purpose of the Node class was too
specific to linked lists. We can embed that class definition into our CustomLinkedList class.
Inner classes allow us to hide implementation details or internal logic. We do not have to expose
these details to others (Encapsulation and abstraction concepts). So, the CustomLinkedList
implementation may use an inner Node class to manage its structure, but the user of the code
only interacts with the List interface. Node is not needed to be known by others just because we
define a class.
// CustomLinkedList.java
public class CustomLinkedList<T> implements Iterable<T>{
private Node head;
private int size;

// Constructor
public CustomLinkedList() {
this.head = null;
this.size = 0;
}

// Private getter for head (only for internal control)


private Node getHead() {
return head;
}
// Private setter for head (only for internal control)
private void setHead(Node head) {
this.head = head;
}

// Add an element to the end of the list


public void add(T data) {
Node newNode = new Node(data);
if (isEmpty()) {

9/17
setHead(newNode);
} else {
Node current = getHead();
while (current.getNext() != null) {
current = current.getNext();
}
current.setNext(newNode);
}
size++;
}

// Remove an element by value


// first occurrences
public boolean remove(T data) {
if (isEmpty()) {
return false;
}

if (getHead().getData().equals(data)) {
setHead(getHead().getNext());
size--;
return true;
}

Node current = getHead();


while (current.getNext() != null &&
!current.getNext().getData().equals(data)) {
current = current.getNext();
}

if (current.getNext() == null) {
return false;
}

current.setNext(current.getNext().getNext());
size--;
return true;
}

// Get the size of the list


public int size() {
return size;
}

// Check if the list is empty


public boolean isEmpty() {
return size == 0; // return getHead() == null;
}

// Print the elements of the list

10/17
public void printList() {
Node current = getHead();
while (current != null) {
System.out.print(current.getData() + " -> ");
current = current.getNext();
}
System.out.println("null");
}

// Get an element by index


public T get(int index) throws IndexOutOfBoundsException{
if (index < 0 || index >= size) {
throw new IndexOutOfBoundsException("Index: " + index + ", Size: " + size);
}

Node current = getHead();


for (int i = 0; i < index; i++) {
current = current.getNext();
}
return current.getData();
}

@Override
public Iterator<T> iterator() {
return new CustomLinkeListIterator(this.getHead());
}

// private Node class defined as an inner class


private class Node {
// you could make this one final to assure immutability
// because the get method in CustomLinkedList returns it.
private final T data;
private Node next;

// Constructor
public Node(T data) {
this.data = data;
this.next = null;
}

// Getter for data


public T getData() {
return data;
}

// No need to set this one, it is final


/*
public void setData(T data) {
this.data = data;
}

11/17
*/
// Getter for next
public Node getNext() {
return next;
}

// Setter for next


// default package-access
void setNext(Node next) {
this.next = next;
}
}
}

Here, check our earlier implementation where the Node was a separate (top-level) class. There,
Node was generic. Here, the outer class CustomLinkedList specifies T; therefore, we do not
specify it with the Node class.

Remember that we used to implement Iterable in the CustomLinkedList using the


CustomLinkeListIterator that implements the Iterator interface. The CustomLinkeListIterator
implementation is so specific to our design. We could include the CustomLinkeListIterator class
implementing the Iterator here in the CustomLinkedList class. The changes I have made are
marked bold. The plain font is the exact same as the previous class definition.
import java.util.Iterator; // do not forget this one.
// CustomLinkedList.java
public class CustomLinkedList<T> implements Iterable<T>{
private Node head;
private int size;

// Constructor
public CustomLinkedList() {
this.head = null;
this.size = 0;
}

// Private getter for head (only for internal control)


private Node getHead() {
return head;
}
// Private setter for head (only for internal control)
private void setHead(Node head) {
this.head = head;
}

// Add an element to the end of the list


public void add(T data) {
Node newNode = new Node(data);

12/17
if (isEmpty()) {
setHead(newNode);
} else {
Node current = getHead();
while (current.getNext() != null) {
current = current.getNext();
}
current.setNext(newNode);
}
size++;
}

// Remove an element by value


// first occurrences
public boolean remove(T data) {
if (isEmpty()) {
return false;
}

if (getHead().getData().equals(data)) {
setHead(getHead().getNext());
size--;
return true;
}

Node current = getHead();


while (current.getNext() != null &&
!current.getNext().getData().equals(data)) {
current = current.getNext();
}

if (current.getNext() == null) {
return false;
}

current.setNext(current.getNext().getNext());
size--;
return true;
}

// Get the size of the list


public int size() {
return size;
}

// Check if the list is empty


public boolean isEmpty() {
return size == 0; // return getHead() == null;
}

13/17
// Print the elements of the list
public void printList() {
Node current = getHead();
while (current != null) {
System.out.print(current.getData() + " -> ");
current = current.getNext();
}
System.out.println("null");
}

// Get an element by index


public T get(int index) throws IndexOutOfBoundsException{
if (index < 0 || index >= size) {
throw new IndexOutOfBoundsException("Index: " + index + ", Size: " + size);
}

Node current = getHead();


for (int i = 0; i < index; i++) {
current = current.getNext();
}
return current.getData();
}

@Override
public Iterator<T> iterator() {
return new CustomLinkeListIterator(this.getHead());
}

// private Node class defined as an inner class


private class Node {
// you could make this one final to assure immutability
// because get method in CustomLinkedList returns it.
private final T data;
private Node next;

// Constructor
public Node(T data) {
this.data = data;
this.next = null;
}

// Getter for data


public T getData() {
return data;
}

// No need to set this one, it is final


/*
public void setData(T data) {
this.data = data;

14/17
}
*/
// Getter for next
public Node getNext() {
return next;
}

// Setter for next


// default
void setNext(Node next) {
this.next = next;
}
}
private class CustomLinkeListIterator implements Iterator<T>{
// private field controlling the current node in our linked list
private Node currentNode;

// constructor
// assign head
public CustomLinkeListIterator(Node head) {
this.currentNode = head;
}

@Override
public boolean hasNext() {
return currentNode != null;
}

@Override
public T next() {
// if there is not next.
// so calling next before hasNext will cause an exception
// if there is no next
if(!hasNext())
throw new java.util.NoSuchElementException();
T temp = currentNode.getData(); // get the current
currentNode = currentNode.getNext(); // make the current next anymore, move to next
return temp; // return the current
}

}
}

Again, compare our implementation with the one we did in week 11. You will see that we
removed the generic definition from CustomLinkeListIterator because it has been already
specified in the outer class.

Static nested classes

15/17
First, be aware that only nested classes can be static. You cannot define a top-level (separate)
class static in Java. Your class definition might not require an instance creation, this does not
mean that your top-level class is static. For example, when working with Math class, we do not
need its instance not because it is static, but because its members are static1. Notice the static
modifier applies to nested classes, methods, or fields, but not to top-level classes.

Suppose you need to have a set of utility methods to validate different user inputs, such as
checking if a string is an email or a phone number. A static nested class can group related
functionality together while keeping the implementation encapsulated.
public class Utility {
// Static nested class for validation methods
public static class Validator {
// Validates if a string is a valid email
public static boolean isValidEmail(String email) {
if (email == null || email.isEmpty())
return false;
// do not mind the implementation. it is a regex.
return email.matches("^[A-Za-z0-9+_.-]+@[A-Za-z0-9.-]+$");
}

// Validates if a string is a valid phone number


public static boolean isValidPhoneNumber(String phoneNumber) {
if (phoneNumber == null || phoneNumber.isEmpty())
return false;
// do not mind the implementation. it is a regex.
return phoneNumber.matches("^\\+?[0-9]{10,15}$");
}
}
}

As you can see, we have provided a nested Validator class inside Utility class to provide some
checks regarding some Strings (In this example, Utility is just a class to illustrate the idea. It
does have no purpose other than having a static class member). You can have instances of the
Utility class; however, the class has only static member(s), we do not need it because static
members can be access via the class, it does not require an instance. To invoke the methods
(isValidEmail, isValidPhoneNumber) of the static nested class, you should access the static
nested class like you access any static member, Utility.Validator.
Utility.Validator.isValidEmail("[email protected]"); → invoke the method through Validator
Utility.Validator.isValidPhoneNumber("1111111"); → invoke the method through Validator

1
The Math class (https://docs.oracle.com/javase/8/docs/api/java/lang/Math.html) is indeed final and its
constructor is private. Being final means that you cannot further extend it for customization or
modification. As you know, final avoids misuse or unintended behavior through subclassing.Having a
private constructor means no code outside the class can create an instance of it. All its methods are
static, so creating an object of the class serves no purpose.You should know that the Math class is NOT
static and such a top-level class cannot be static. The reason we cannot create an instance is because its
constructor is private.

16/17
It may sound awkward to you but the static nested classes can be instantiated. Why? The
answer is obvious, they are not abstract classes. Can you tell how you can instantiate them?
Utility.Validator uv = new Utility.Validator();

Do you really need to instantiate a static nested class? If you really need the instance of the
static nested class, you can do so. For example, you can change our Node class to a static
nested one because it does not need any instance members of the enclosing
(CustomLinkedList) class (it is fine to define it as a static nested class). While traversing in a for
loop, you would have an instance of the static Node class for clarity in your code.You can try it
yourself this time (do not forget to use proper access modifier, in our implementation it is
private).

Local inner classes


By now, you should know what local means in our lecture discussions although It might not
appear in lecture notes explicitly. Local defines the scope restricted to a block. The lifetime and
visibility of entities like variables, classes, or methods are restricted to the block in which they
are declared, which is why they are referred to as local. So, you can define local classes inside
any block (but in the body of a method) and are only accessible within that block.
public class Outer {
public void display() {
class MethodLocalInner {
void printAMessage(String mess) {
System.out.println(mess);
}
}
MethodInner inner = new MethodInner();
inner.print();
}
}

You can define such a class, which is temporary and its functionality is not needed elsewhere.

Next, we will have a look at anonymous classes.

17/17

You might also like