Look around and you'll find many examples of real-world objects such as
Real-world objects share two characteristics: they all have state and behavior.
Object-oriented programming uses the concept of objects as a metaphor for information. An object is a stateful entity and has associated actions that can be performed to modify its state.
For a software object,
Methods operate on an object's internal state and serve as the primary mechanism for object-to-object communication.
Hiding internal state and requiring all interaction to be performed through an object's methods is known as data encapsulation — a fundamental principle of object-oriented programming.
Consider a bicycle object, for example.
By attributing it state, for example, current speed, current pedal cadence, and current gear, and providing methods for changing its state, the object remains in control of how the outside world is allowed to use it. For example, if the bicycle only has 6 gears, a method to change gears could reject any value that is less than 1 or greater than 6.
Bundling code into individual software objects provides a number of benefits.
In the real world, you'll often find many individual objects all of the same kind. There may be thousands of other bicycles in existence, all of the same make and model. Each bicycle was built from the same set of blueprints and therefore contains the same components.
In object-oriented terms, we say that your bicycle is an instance of the class of objects known as bicycles. A class is the blueprint from which individual objects are created.
The following Bicycle
class is one possible implementation of a
bicycle.
class Bicycle {
int cadence = 0;
int speed = 0;
int gear = 1;
void changeCadence(int newValue) {
cadence = newValue;
}
void changeGear(int newValue) {
gear = newValue;
}
void speedUp(int increment) {
speed = speed + increment;
}
void applyBrakes(int decrement) {
speed = speed - decrement;
}
void printStates() {
System.out.println("cadence:" +
cadence + " speed:" +
speed + " gear:" + gear);
}
}
The fields cadence
, speed
, and
gear
represent the object's state.
The methods changeCadence()
, changeGear()
, speedUp()
, applyBreaks()
, and printStates()
define how we can interact with bicycle objects.
You may have noticed that the Bicycle
class does not contain a
main method. That's because it's not a complete application; it's just the blueprint for bicycles that
might be used in an application. The responsibility of creating and using new Bicycle
objects belongs to some other class in your application.
Here is a BicycleDemo
class that creates two separate Bicycle
objects and invokes their methods.
class BicycleDemo {
public static void main(String[] args) {
// Create two different
// Bicycle objects
Bicycle bike1 = new Bicycle();
Bicycle bike2 = new Bicycle();
// Invoke methods on
// those objects
bike1.changeCadence(50);
bike1.speedUp(10);
bike1.changeGear(2);
bike1.printStates();
bike2.changeCadence(50);
bike2.speedUp(10);
bike2.changeGear(2);
bike2.changeCadence(40);
bike2.speedUp(10);
bike2.changeGear(3);
bike2.printStates();
}
}
The output of this test prints the ending pedal cadence, speed, and gear for the two bicycles:
cadence:50 speed:10 gear:2
cadence:40 speed:20 gear:3
The relationships of classes and interfaces through inheritance give rise to a hierarchy.
Example:
The figure uses the UML class diagram notation and depicts a class hierarchy where
Car
, Truck
, and Motorcycle
specialize Vehicle
;SUV
specializes Car
; GarbageTruck
and FireTruck
specialize Truck
.Java supports class inheritance via the extends
keyword.
When present, extends
specifies an inheritance relationship
between two classes, in which
extends
identifies the subclass, and extends
identifies the superclass.A class can extend up to 1 other class.
Here are two examples of class inheritance.
class Vehicle {
// member declarations
}
class Car extends Vehicle {
// inherit accessible members from Vehicle
// provide own member declarations
}
class Account {
// member declarations
}
class SavingsAccount extends Account {
// inherit accessible members from Account
// provide own member declarations
}
In these examples:
Car
is a subclass of Vehicle
SavingsAccount
is a subclass of Account
We generally use the following terminology.
Vehicle
and Account
are
called the base classes, parent classes, or
superclasses.Car
and SavingsAccount
are
called derived classes, child classes, or
subclasses.Inheritance has the following "consequences" for the child classes.
class Account{
private String name;
private long amount;
Account(String name, long amount) {
this.name = name;
setAmount(amount);
}
void deposit(long amount) {
this.amount += amount;
}
String getName() {
return name;
}
long getAmount() {
return amount;
}
void setAmount(long amount) {
this.amount = amount;
}
}
class SavingsAccount extends Account {
SavingsAccount(long amount) {
super("savings", amount);
}
}
Note that SavingsAccount
does not declare
additional fields or methods. It does, however, declare a constructor that initializes the fields in
its Account
superclass.
Initialization happens when Account's constructor is called via Java's super
keyword, followed by a parenthesized argument list.
Note that super()
can only be called from a child's constructor,
not from other methods!
Here is another subclass of Account
.
class CheckingAccount extends Account {
CheckingAccount(long amount) {
super("checking", amount);
}
void withdraw(long amount) {
setAmount(getAmount() - amount);
}
}
Notice that it declares a withdraw()
method, which calls setAmount()
and getAmount()
from
Account
.
It does that because it cannot directly access the amount
field in
Account
because it is declared as private
.
Access level modifiers determine whether other classes can use a particular field or invoke a particular method.
There are two levels of access control.
public
: a class that is visible by all classes.public
: a member that is accessible by all classes.protected
: a member that is accessible by classes in the
same package or by subclasses anywhere.private
: a member that can only be accessed in its own
class.The following table shows the access to members permitted by each modifier:
Within class | Within pkg | Diff pkg in subclass | Diff pkg | |
---|---|---|---|---|
public | + | + | + | + |
protected | + | + | + | |
default | + | + | ||
private | + |
Legend:
If other programmers use your class, you want to ensure that errors from misuse cannot happen. Access levels can help you do this.
Here are some tips on choosing an access level.
private
unless you have a good reason not to.public
fields except for constants. Public fields tend to
link you to a particular implementation and limit your flexibility in changing your code.Try to access members of different levels of access with the class Runner below.
class Runner {
public static void main(String[] args) {
SavingsAccount sa = new SavingsAccount(10000);
System.out.println("account name: " + sa.getName());
System.out.println("initial amount: " + sa.getAmount());
sa.deposit(5000);
System.out.println("new amount after deposit: " + sa.getAmount());
CheckingAccount ca = new CheckingAccount(20000);
System.out.println("account name: " + ca.getName());
System.out.println("initial amount: " + ca.getAmount());
ca.deposit(6000);
System.out.println("new amount after deposit: " + ca.getAmount());
ca.withdraw(3000);
System.out.println("new amount after withdrawal: " + ca.getAmount());
}
}
account name: savings
initial amount: 10000
new amount after deposit: 15000
account name: checking
initial amount: 20000
new amount after deposit: 26000
new amount after withdrawal: 23000
public
class named Vehicle
.
licensePlate
field.licensePlate
.Vehicle
with the public
class Car
.
numberOfSeats
fieldnumberOfSeats
.
Runner
class where you create a Vehicle and a Car and
invoke all its accessible members.You can find the solution to this exercise here.
You might declare a class that should not be extended using the final
keyword.
Simply prefix a class header with final.
final class Password {
// class body
}
Given this declaration, the compiler will report an error if someone attempts to extend Password
.
Some reasons to declare classes as final are the following.
Implementing equals becomes tricky. Suppose an Animal
has a single property, that is, name. Should an Animal
with name "Fido"
equal a
Dog
with name "Fido"
?
@Override
boolean equals(Object o) {
return o instanceof Animal
&& this.name.equals(((Animal) o).name);
}
What if Dog
has an additional property
that an Animal
lacks?
There’s no obvious solution here!
Seemingly innocent overrides can have surprising effects. If a private method calls a public method in the base class, then overriding the public method may have unexpected side-effects on the inner workings of the base class.
Class invariants may break. The base class perhaps maintains certain internal invariants. Perhaps it’s immutable, or intended to be thread-safe. There’s no way to enforce such design decisions upon subclasses. If you receive an object of the base class type, you technically can’t assume it’s immutable or thread-safe unless it’s final.