1.1. Abstraction
Different types of objects and inheritances in Java.
Class
A class is a template to instantiate an object.
What is the relationship between a class and an object?
Let us create a class called Numeral
that we want to be a super class of all numeral types:
L1
:package
indicates the name of the package that this class belongs to in a hierarchy.L3
:public
is an access-level modifier.
What are the acceptable access-level modifiers to declare a top-level class?
Let us declare a method, add()
, that is an operation expected by all numeral types:
L4
:@param
adds a javadoc comment about the parameter.
The issue is that we cannot define the methods unless we know what specific numeral type this class should implement; in other words, it is too abstract to define those methods. Thus, we need to declare Numeral
as a type of abstract class.
What are the advantages of having
Numeral
as a super class of all numeral types?
There are two types of abstract classes in Java, abstract class
and interface
.
Can an object be instantiated by an abstract class or an interface?
Interface
Let us define Numeral
as an interface:
L2
: abstract methodAll methods in an interface are
public
that does not need to be explicitly coded.Abstract methods in an interface are declared without their bodies.
Who defines the bodies of the abstract methods?
Let us create a new interface called SignedNumeral
that inherits Numeral
and adds two methods, flipSign()
and subtract()
:
Can an interface inherit either an abstract class or a regular class?
L1
:extends
inherits exactly one class or interface.L9
:default
allows an interface to define a method with its body (introduced in Java 8).
Can we call
add()
that is an abstract method without a body in the default methodsubtract()
?
Although the logic of subtract()
seems to be correct, n.flipSign()
gives a compile error because n
is a type of Numeral
that does not include flipSign()
, which is defined in SignedNumeral
that is a subclass of Numeral
.
What kind of a compile error does
n.flipSign()
cause?
There are three ways of handling this error: casting, polymorphism, and generics.
Casting
The first way is to downcast the type of n
to SignedNumeral
, which forces the compiler to think that n
can invoke the flipSign()
method:
This removes the compile error; however, it will likely cause a worse kind, a runtime error.
Why is a runtime error worse than a compile error?
Downcasting, although allowed in Java, is generally not recommended unless there is no other way of accomplishing the job without using it.
How can downcasting cause a runtime error in the above case?
Polymorphism
The second way is to change the type of n
to SignedNumeral
in the parameter setting:
This seems to solve the issue. Then, what about add()
defined in Numeral
? Should we change its parameter type to SignedNumeral
as well?
It is often the case that you do not have access to change the code in a super class unless you are the author of it. Even if you are the author, changing the code in a super class is not recommended.
Why is it not recommended to change the code in a super class?
How about we override the add()
method as follows?
L2
:@Override
is a predefined annotation type to indicate the method is overridden.
The annotation @Override
gives an error in this case because it is not considered an overriding.
What are the criteria to override a method?
When @Override
is discarded, the error goes away and everything seems to be fine:
However, this is considered an overloading, which defines two separate methods for add()
, one taking n
as Numeral
and the other taking it as SignedNumeral
. Unfortunately, this would decrease the level of abstraction that we originally desired.
What are good use cases of method overriding and overloading?
Generics
The third way is to use generics, introduced in Java 5:
L1
:T
is a generic type that is a subtype ofNumeral
.A generic type can be recursively defined as
T
extendsNumeral<T>
.
L2
:T
is considered a viable type in this interface such that it can be used to declareadd()
.
Can we define more than one generic type per interface or class?
The generic type T
can be specified in a subclass of Numeral
:
L1
:T
is specified asSignedNumeral
.
This would implicitly assign the parameter type of add()
as follows:
The issue is that the implementation of add()
may require specific features defined in the subclass that is not available in SignedNumeral
. Consider the following subclass inheriting SignedNumeral
:
L1
:implements
inherits multiple interfaces.L2-6
:LongInteger
is a regular class, so all abstract methods declared in the super classes must be defined in this class.
Since the n
is typed to SignedNumeral
in L6
, it cannot call any method defined in LongInteger
, which leads to the same issue addressed in the casting section.
Would the type of
n
beingSignedNumeral
an issue for thesubtract()
method as well?
Thus, SignedNumeral
needs to define its own generic type and pass it onto Numeral
:
L1
:T
is a generic type inheritingSignedNumeral
, that implies all subclasses ofSignedNumeral
.
T
can be safely passed onto Numeral
because if it is a subclass of SignedNumeral
, it must be a subclass of Numeral
, which is how T
is defined in the Numeral
class.
Generics are used everywhere in Java, so it is important to understand the core concept of generics and be able to adapt it in your code to make it more modular.
Enum
Let us create an enum class called Sign
to represent the "sign" of the numeral:
All items in an enum have the scope of
static
and the access-level ofpublic
.Items must be delimited by
,
and ends with;
.
The items in the enum can be assigned with specific values to make them more indicative (e.g., +
, -
):
L5
:final
makes this field a constant, not a variable, such that the value cannot be updated later.L8
:this
points to the object created by this constructor.L11
:@return
adds a javadoc comment about the return value of this method.
Why should the member field
value
beprivate
in the above example?
Note that value
in L8
indicates the local parameter declared in the constructor whereas value
in L13
indicates the member field declared in L5
.
Limit of Interface
In SignedNumeral
, it would be convenient to have a member field that indicates the sign of the numeral:
L2
: All member fields of an interface arestatic
andpublic
.
Can you declare a member field in an interface without assigning a value?
Given the sign
field, it may seem intuitive to define flipSign()
as a default method:
L3
: condition?
A:
B is a ternary expression that returns A if the condition is true; otherwise, it returns B.
Is there any advantage of using a ternary operator instead of using a regular if statement?
Unfortunately, this gives a compile error because sign
is a constant whose value cannot be reassigned. An interface is not meant to define so many default methods, which were not even allowed before Java 8. For such explicit implementations, it is better to declare SignedNumeral
as an abstract class instead.
Abstract Class
Let us turn SignedNumeral
into an abstract class:
L9
: the default constructor with no parameter.L17
: another constructor with thesign
parameter.L10
:this()
calls the constructor inL17
.
Why calling
this(Sign.POSITIVE)
inL10
instead of statingthis.sign = Sign.POSITIVE
?
L29
:abstract
indicates that this is an abstract method.
Member fields and methods in an abstract class can be decorated by any modifiers, which need to be explicitly coded.
Is there anything that is not allowed in an abstract class but allowed in a regular class?
In summary, SignedNumeral
includes 2 abstract methods, add()
inherited from Numeral
, and multiply()
declared in this class.
Can you define an abstract class or an interface without declaring an abstract method?
Last updated