Basic elements of object-oriented programming (OOP) in Java include classes, objects, and interfaces.
Classes define entities that usually represent something in the real world. They consist of a set of values that holds data and a set of methods that operates on the data.
An instance of a class is called an object, and it is allocated memory. There can be multiple instances of a class.
Classes can inherit data members and methods from other classes. A class can directly inherit from only one class—the super__class. A class can have only one direct superclass. This is called inheritance.
When implementing a class, the inner details of the class should be private
and accessible only through public interfaces. This is called encapsulation. The JavaBean convention is to use accessor and mutator methods (e.g., getFirstName()
and setFirstName("Leonardina")
) to indirectly access the private members of a class and to ensure that another class cannot unexpectedly modify private members. Returning immutable values (i.e., strings, primitive values, and objects intentionally made immutable) is another way to protect the data members from being altered by other objects.
A class has a class signature, optional constructors, data members, and methods:
[
javaModifiers
]
class
className
[
extends
someSuperClass
]
[
implements
someInterfaces
separated
by
commas
]
{
// Data member(s)
// Constructor(s)
// Method(s)
}
An object is an instance of a class. Once instantiated, objects have their own set of data members and methods:
// Sample class definitions
public
class
Candidate
{...}
class
Stats
extends
ToolSet
{...}
public
class
Report
extends
ToolSet
implements
Runnable
{...}
Separate objects of class Candidate
are created (instantiated) using the keyword new
:
Candidate
candidate1
=
new
Candidate
();
Candidate
candidate2
=
new
Candidate
();
Data members, also known as fields, hold data about a class. Data members that are nonstatic are also called instance variables:
[
javaModifier
]
type
dataMemberName
Methods operate on class data:
[
javaModifiers
]
type
methodName
(
parameterList
)
[
throws
listOfExceptionsSeparatedByCommas
]
{
// Method body
}
The following is an example of class Candidate
and its data members and methods:
public
class
Candidate
{
// Data members or fields
private
String
firstName
;
private
String
lastName
;
private
int
year
;
// Methods
public
void
setYear
(
int
y
)
{
year
=
y
;
}
public
String
getLastName
()
{
return
lastName
;}
}
// End class Candidate
The dot operator (.) is used to access data members and methods in objects. It is not necessary to use the dot operator when accessing data members or methods from within an object:
candidate1
.
setYear
(
2016
);
String
name
=
getFirstName
()
+
getLastName
();
Methods including constructors can be overloaded. Overloading means that two or more methods have the same name but different signatures (parameters and return values). Note that overloaded methods must have different parameters, and they may have different return types; but having only different return types is not overloading. The access modifiers of overloaded methods can be different:
public
class
VotingMachine
{
...
public
void
startUp
()
{...}
private
void
startUp
(
int
delay
)
{...}
}
When a method is overloaded, it is permissible for each of its signatures to throw different checked exceptions:
private
String
startUp
(
District
d
)
throws
new
IOException
{...}
A subclass can override the methods it inherits. When overridden, a method contains the same signature (name and parameters) as a method in its superclass, but it has different implementation details.
The method startUp()
in superclass Display
is overridden in class TouchScreenDisplay
:
public
class
Display
{
void
startUp
(){
System
.
out
.
println
(
"Using base display."
);
}
}
public
class
TouchScreenDisplay
extends
Display
{
void
startUp
()
{
System
.
out
.
println
(
"Using new display."
);
}
}
Rules regarding overriding methods include the following:
final, private
, or static
can be overridden.
package, public, private, protected
) than the original method.
Constructors are called upon object creation and are used to initialize data in the newly created object. Constructors are optional, have exactly the same name as the class, and they do not have a return
in the body (as methods do).
A class can have multiple constructors. The constructor that is called when a new object is created is the one that has a matching signature:
public
class
Candidate
{
...
Candidate
(
int
id
)
{
this
.
identification
=
id
;
}
Candidate
(
int
id
,
int
age
)
{
this
.
identification
=
id
;
this
.
age
=
age
;
}
}
// Create a new Candidate and call its constructor
class
ElectionManager
{
int
i
=
getIdFromConsole
();
Candidate
candidate
=
new
Candidate
();
}
Classes implicitly have a no-argument constructor if no explicit constructor is present. Note that if a constructor with arguments is added, there will be no no-argument constructor unless it is manually added.
In Java, a class (known as the subclass) can inherit directly from one class (known as the superclass). The Java keyword extends
indicates that a class inherits data members and methods from another class. Subclasses do not have direct access to private
members of its superclass, but do have access to the public
and protected
members of the superclass. A subclass also has access to members of the superclass where the same package is shared (package-private or protected
). As previously mentioned, accessor and mutator methods provide a mechanism to indirectly access the private
members of a class, including a superclass:
public
class
Machine
{
boolean
state
;
void
setState
(
boolean
s
)
{
state
=
s
;}
boolean
getState
()
{
return
state
;}
}
public
class
VotingMachine
extends
Machine
{
...
}
The keyword super
in the Curtain
class’s default constructor is used to access methods in the superclass overridden by methods in the subclass:
public
class
PrivacyWall
{
public
void
printSpecs
()
{...}
}
public
class
Curtain
extends
PrivacyWall
{
public
void
printSpecs
()
{
...
super
.
printSpecs
();
}
}
Another common use of the keyword super
is to call the constructor of a superclass and pass it parameters. Note that this call must be the first statement in the constructor calling super
:
public
PrivacyWall
(
int
l
,
int
w
)
{
int
length
=
l
;
int
width
=
w
;
}
public
class
Curtain
extends
PrivacyWall
{
// Set default length and width
public
Curtain
()
{
super
(
15
,
25
);}
}
If there is not an explicit call to the constructor of the superclass, an automatic call to the no-argument constructor of the superclass is made.
The three common uses of the this
keyword are to refer to the current object, to call a constructor from within another constructor in the same class, and to pass a reference of the current object to another object.
To assign a parameter variable to an instance variable of the current object:
public
class
Curtain
extends
PrivacyWall
{
String
color
;
public
setColor
(
String
color
)
{
this
.
color
=
color
;
}
}
To call a constructor from another constructor in the same class:
public
class
Curtain
extends
PrivacyWall
{
public
Curtain
(
int
length
,
int
width
)
{}
public
Curtain
()
{
this
(
10
,
9
);}
}
To pass a reference of the current object to another object:
public
class
Curtain
{
Builder
builder
=
new
Builder
();
builder
.
setWallType
(
this
);
}
public
class
Builder
{
public
void
setWallType
(
Curtain
c
)
{...}
}
Since Java 5.0, methods can have a variable-length argument list. Called varargs, these methods are declared such that the last (and only the last) argument can be repeated zero or more times when the method is called. The vararg parameter can be either a primitive or an object. An ellipsis (…) is used in the argument list of the method signature to declare the method as a vararg. The syntax of the vararg parameter is as follows:
type
...
objectOrPrimitiveName
Here is an example of a signature for a vararg method:
public
setDisplayButtons
(
int
row
,
String
...
names
)
{...}
The Java compiler modifies vararg methods to look like regular methods. The previous example would be modified at compile time to:
public
setDisplayButtons
(
int
row
,
String
[]
names
)
{...}
It is permissible for a vararg method to have a vararg parameter as its only parameter:
// Zero or more rows
public
void
setDisplayButtons
(
String
...
names
)
{...}
A vararg method is called the same way an ordinary method is called except that it can take a variable number of parameters, repeating only the last argument:
setDisplayButtons
(
"Jim"
);
setDisplayButtons
(
"John"
,
"Mary"
,
"Pete"
);
setDisplayButtons
(
"Sue"
,
"Doug"
,
"Terry"
,
"John"
);
The printf
method is often used when formatting a variable set of output, as printf
is a vararg method. From the Java API, type the following:
public
PrintStream
printf
(
String
format
,
Object
...
args
)
The printf
method is called with a format string and a variable set of objects:
System
.
out
.
printf
(
"Hello voter %s%n
This is machine %d%n"
,
"Sally"
,
1
);
For detailed information on formatting a string passed into the printf
method, see java.util.Formatter
.
The enhanced for
loop (for each) is often used to iterate through the variable argument:
printRows
()
{
for
(
String
name:
names
)
System
.
out
.
println
(
name
);
}
Abstract classes and methods are declared with the keyword abstract
.
An abstract class is typically used as a base class and cannot be instantiated. It can contain abstract and nonabstract methods, and it can be a subclass of an abstract or a nonabstract class. All of its abstract methods must be defined by the classes that inherit (extend
) it unless the subclass is also abstract:
public
abstract
class
Alarm
{
public
void
reset
()
{...}
public
abstract
void
renderAlarm
();
}
Static data members, methods, constants, and initializers reside with a class and not instances of classes. Static data members, methods, and constants can be accessed in the class they are defined in or in another class using the dot operator.
Static data members have the same features as static methods, plus they are stored in a single location in memory.
They are used when only one copy of a data member is needed across all instances of a class (e.g., a counter):
// Declaring a static data member
public
class
Voter
{
static
int
voterCount
=
0
;
public
Voter
()
{
voterCount
++;}
public
static
int
getVoterCount
()
{
return
voterCount
;
}
}
...
int
numVoters
=
Voter
.
voterCount
;
Static methods have the keyword static
in the method declaration:
// Declaring a static method
class
Analyzer
{
public
static
int
getVotesByAge
()
{...}
}
// Using the static method
Analyzer
.
getVotesByAge
();
Static methods cannot access nonstatic methods or variables because static methods are associated with a class, not an object.
Static constants are static members declared constant. They have the keywords static
and final
, and a program cannot change them:
// Declaring a static constant
static
final
int
AGE_LIMIT
=
18
;
// Using a static constant
if
(
age
==
AGE_LIMIT
)
newVoter
=
"yes"
;
Static initializers include a block of code prefaced by the keyword static
. A class can have any number of static initializer blocks, and it is guaranteed that they will run in the order in which they appear. Static initializer blocks are executed only once per class initialization. A block is ran when the JVM class loader loads StaticClass
, which is upon the initial reference to the code.
// Static Initializer
static
{
numberOfCandidates
=
getNumberOfCandidates
();
}
Interfaces provide a set of declared public
methods that do not have method bodies. A class that implements an interface must provide concrete implementations of all the methods defined by the interface, or it must be declared abstract.
An interface is declared using the keyword interface
, followed by the name of the interface and a set of method declarations.
Interface names are usually adjectives and end with “able” or “ible,” as the interface provides a capability:
interface
Reportable
{
void
genReport
(
String
repType
);
void
printReport
(
String
repType
);
}
A class that implements an interface must indicate so in its class signature with the keyword implements
:
class
VotingMachine
implements
Reportable
{
public
void
genReport
(
String
repType
)
{
Report
report
=
new
Report
(
repType
);
}
public
void
printReport
(
String
repType
)
{
System
.
out
.
println
(
repType
);
}
}
In simplest terms, enumerations are a set of objects that represent a related set of choices:
enum
DisplayButton
{
ROUND
,
SQUARE
}
DisplayButton
round
=
DisplayButton
.
ROUND
;
Looking beyond simplest terms, an enumeration is a class of type enum
and it is a singleton. Enum classes can have methods, constructors, and data members:
enum
DisplayButton
{
// Size in inches
ROUND
(.
50
f
),
SQUARE
(.
40
f
);
private
final
float
size
;
DisplayButton
(
float
size
)
{
this
.
size
=
size
;}
private
float
size
()
{
return
size
;
}
}
The method values()
returns an array of the ordered list of objects defined for the enum:
for
(
DisplayButton
b
:
DisplayButton
.
values
())
System
.
out
.
println
(
"Button: "
+
b
.
size
());
Annotations provide a way to associate metadata (data about data) with program elements at compile time and runtime. Packages, classes, methods, fields, parameters, variables, and constructors can be annotated.
Java annotations provide a way to obtain metadata about a class. Java has three built-in annotation types, as depicted in Table 5-1. These annotation types are contained in the java.lang
package.
Annotations must be placed directly before the item being annotated. They do not have any parameters and do not throw exceptions. Annotations return primitive types, enumerations, class String
, class Class
, annotations, and arrays (of these types).
Annotation type | Description |
| Indicates that the method is intended to override a method in a superclass. |
| Indicates that a deprecated API is being used or overridden. |
| Used to selectively suppress warnings. |
The following is an example of their use:
@Override
public
String
toString
()
{
return
super
.
toString
()
+
" more"
;
}
Because @Override
is a marker annotation, a compile warning will be returned if the method to be overridden cannot be found.
Developers can define their own annotations using three annotation types. A marker annotation has no parameters, a single value annotation has a single parameter, and a multivalue annotation has multiple parameters.
The definition of an annotation is the symbol @, followed by the word interface
, followed by the name of the annotation.
Repeated annotations are permitted.
The meta-annotation Retention
indicates that an annotation should be retained by the VM so that it can be read at runtime. Retention
is in the package java.lang.annotation
:
@Retention
(
RetentionPolicy
.
RUNTIME
)
public
@interface
Feedback
{}
// Marker
public
@interface
Feedback
{
String
reportName
();
}
// Single value
public
@interface
Feedback
{
String
reportName
();
String
comment
()
default
"None"
;
}
// Multi value
Place the user-defined annotation directly before the item being annotated:
@Feedback
(
reportName
=
"Report 1"
)
public
void
myMethod
()
{...}
Programs can check the existence of annotations and obtain annotation values by calling getAnnotation()
on a method:
Feedback
fb
=
myMethod
.
getAnnotation
(
Feedback
.
class
);
The Type Annotations Specification (also known as “JSR 308”) allows for annotations to be written in array positions and generic type arguments. Annotations may also be written with superclasses, implemented interfaces, casts, instanceof
checks, exception specifications, wildcards, method references and constructor references. See Java SE 8 for the Really Impatient, by Cay S. Horstmann (Addison-Wesley Professional, January 2014) for detailed information on Annotations in these contexts.
A functional interface, a.k.a., Single Abstract Method (SAM) interace, is an inteface that defines one and only one abstract method. The annotation @FunctionalInterface
may be placed in front of an interface to declare its intention as a functional interface. An interface can have any number of default methods.
@FunctionalInterface public interface InterfaceName { // Only one abstract method allowed public void doAbstractTask(); // Multiple default methods allowed default public void performTask1(){ System.out.println("Msg from task 1."); } default public void performTask2(){ System.out.println("Msg from task 2."); } }
Instances of functional interfaces can be created with lambda expressions, method references, or constructor references.