Encapsulation means keeping the internal workings of your code safe from other programs that use it, allowing only what you choose to be accessed. This makes the code that uses the encapsulated code much simpler and easier to maintain since much of the complexity of the latter is hidden.
The main benefit of encapsulation is that it protects a class from being used in a way that it wasn't meant to be. By controlling the way the code is used, it can only ever do what you want it to do.
For example, if we set a variable like this:
car.model = 2015;
We can't prevent an invalid assignment like this:
car.model = 2343242;
To implement encapsulation, we set up the class so only its methods can refer to its instance variables. External code will access these private instance variables only through public get/set methods. This is a convention used in reusable components called JavaBeans. The rules are:
So if we have a class like the following:
public class Car {
int model;
String name;
String color;
}
The encapsulated version would look like this:
public class Car {
private int model;
private String name;
private String color;
public int getModel() {
return model;
}
public String getName() {
return name;
}
public String getColor() {
return color;
}
public void setModel(int model) {
this.model = model;
}
public void setName(int name) {
this.name = name;
}
public void setColor(int color) {
this.color = color;
}
}
Notice the use of this
in the setter methods. The parameter name can be anything, but if it's the same as the instance variable's name, this
(that references the instance) must be used to differentiate between them.
Of course, just like that, in the surface this code does the same as the non-encapsulated version, but by using a method instead of getting/setting the instance variable directly, we can add something like a validation without breaking the external code:
public void setModel(int model) {
if(model >= 2000 && model <= 2015) {
this.model = model;
} else {
this.model = 2000;
}
}
Now, if we set an invalid value:
car.setModel(2343242);
We'll get a default and valid value that will protect the class from being used in a way it wasn't meant to be.
Encapsulation can also be used with constructors and methods of a class. The key thing is to restrict the access to any member of the class that can break things when something changes or that doesn't want to be exposed.
With inheritance, you created classes based on the other classes so they can get the attributes and methods from the base class to reuse them and even redefine them.
The keyword extends
is used to make a new class that derives from an existing class. The base class is called the superclass and the new class is called the subclass.
Let's begin for example with this class:
public class Animal {
public void eat() { /* Do something */ }
}
A subclass can be:
public class Dog extends Animal {
}
By inheriting from Animal, the Dog class automatically gets the method eat()
. We can add a method to the class:
public class Dog extends Animal {
public void bark() { /* Do something */ }
}
So now Dog has two methods, eat()
that is inherited from Animal and bark()
that is specific to the Dog class. Animal still has one method, inheritance only works from the superclasses to the subclasses.
Not everything can be inherited, and this depends directly from the used visibility modifiers.
When a subclass extends a superclass in Java, all protected and public attributes and methods of the superclass are inherited by the subclass. Attributes and methods with default (package) access modifiers can be accessed by subclasses only if the subclass is located in the same package as the superclass. Private attributes and methods of the superclass are not inherited.
Modifier | Inherited |
---|---|
public | Yes |
protected | Yes |
default | Only for subclasses in the same package |
private | No |
When you create a subclass, the methods in the subclass cannot have less accessible access modifiers assigned to them than they had in the superclass. For instance, if a method in the superclass is public then it must be public in the subclass too.
In Java, a class is only allowed to inherit from a single superclass (singular inheritance). In some programming languages, like C++, it is possible for a subclass to inherit from multiple superclasses (multiple inheritance).
You do composition by having an instance of another class as a field of your class instead of extending.
When to use which?
For example:
class Toy {}
class Animal{}
// Cat is an Animal, so Cat class extends Animal class.
class Cat extends Animal {
// Cat has a Toy, so Cat has an instance of Toy as its member.
private Toy toy;
}
Favoring composition over inheritance is a popular object-oriented design principle that helps to create flexible and maintainable code. For example, composition facilitates testing. If one class is composed of another class, you can easily create a mock object representing the composed class.
With inheritance, you can take advantage of polymorphism. Polymorphism is based on the fact that you can use a variable of a superclass type to hold a reference to an object whose class is any of its subclasses, for example:
Animal animal = new Dog();
This way, polymorphism allows the program to act differently based on the object performing the action. For example:
class Animal {
public void eat() {
System.out.println("Animal Food");
}
}
class Dog extends Animal {
public void eat() {
System.out.println("Dog Food");
}
}
class Cat extends Animal {
public void eat() {
System.out.println("Cat Food");
}
}
public class Test {
public static void main(String args[]) {
Animal a = new Dog();
System.out.println(a.eat());
a = new Cat();
System.out.println(a.eat());
}
}
The first System.out.println(a.eat());
will print Dog Food since in that moment, the a reference is holding a Dog object. In the second call, it will print Cat Food, since now the reference holds a Cat object.
So polymorphism can help make code easier to change. If you have a fragment of code that uses a variable of a superclass type, such as Animal, you could later create a brand new subclass with another behavior, such as Spider, polymorphism will ensure that Spider's implementation of Animal's method gets executed and the fragment will work without change.
From javadoc:
public boolean equals(Object obj)
Indicates whether some other object is "equal to" this one.
The equals method implements an equivalence relation on non-null object references:
It is generally necessary to override the hashCode
method whenever this method is overridden, so as to maintain the general contract for the hashCode
method, which states that equal objects must have equal hash codes.
From javadoc:
public int hashCode()
Returns a hash code value for the object. This method is supported for the benefit of hash tables such as those provided by HashMap
.
The general contract of hashCode is:
hashCode
method must consistently return the same integer, provided no information used in equals comparisons on the object is modified. This integer need not remain consistent from one execution of an application to another execution of the same application.hashCode
method on each of the two objects must produce the same integer result.hashCode
method on each of the two objects must produce distinct integer results. However, the programmer should be aware that producing distinct integer results for unequal objects may improve the performance of hash tables.Object
does return distinct integers for distinct objects. (This is typically implemented by converting the internal address of the object into an integer, but this implementation technique is not required by Java.)The relation between the two methods is:
Whenever a.equals(b
), then a.hashCode()
must be same as b.hashCode()
.
From javadoc:
public String toString()
Returns a string representation of the object. In general, the toString
method returns a string that "textually represents" this object. The result should be a concise but informative representation that is easy for a person to read. It is recommended that all subclasses override this method.
The toString
method for class Object
returns a string consisting of the name of the class of which the object is an instance, the at-sign character '@', and the unsigned hexadecimal representation of the hash code of the object. In other words, this method returns a string equal to the value of:
getClass().getName() + '@' + Integer.toHexString(hashCode())
The following is an example of an implementation of these methods:
public class Person {
private final String lastName;
private final String firstName;
private final boolean female;
@Override
public boolean equals(Object obj)
{
if (obj == null)
{
return false;
}
if (getClass() != obj.getClass())
{
return false;
}
final Person other = (Person) obj;
if ((this.lastName == null) ? (other.lastName != null) : !this.lastName.equals(other.lastName))
{
return false;
}
if ((this.firstName == null) ? (other.firstName != null) : !this.firstName.equals(other.firstName))
{
return false;
}
if (this.female != other.female)
{
return false;
}
return true;
}
@Override
public int hashCode()
{
int hash = 3;
hash = 19 * hash + (this.lastName != null ? this.lastName.hashCode() : 0);
hash = 19 * hash + (this.firstName != null ? this.firstName.hashCode() : 0);
hash = 19 * hash + (this.female ? 1 : 0);
return hash;
}
@Override
public String toString()
{
return "Person{" + "lastName=" + lastName + ", firstName=" + firstName
+ ", female=" + female + '}';
}
}
Singleton is a design pattern that provides a way for a class to create only one object from that class.
The key for a Singleton class is to make the constructor private, have a static instance of itself and a method to access it:
public class Singleton {
//Create an object
private static final Singleton instance = new Singleton();
//Make the constructor private so that this class cannot be instantiated
private Singleton(){}
//Get the only object available
public static Singleton getInstance(){
return instance;
}
}
The line private static final Singleton instance = new Singleton();
is only executed when the class Singleton is actually used. This guaranteed the instantiation to be thread safe. To use the singleton class:
Singleton s = Singleton.getInstance();
The other ways to build a Singleton class are:
Using an Enum
public enum Singleton{
INSTANCE;
}
Locking /Lazy loading with Double checked Locking
public class Singleton{
private static volatile Singleton instance;
private Singleton(){}
public static Singleton getInstance(){
if(instance == null){
synchronized(Singleton.class){
//double checking Singleton instance
if(instance == null){
instance = new Singleton();
}
}
}
return instance;
}
}
Immutable objects are simply objects whose state (data) cannot change after construction, for examples the String class. They are useful in concurrent applications, since they cannot change state, they cannot be corrupted by threads.
There are several ways for creating immutable objects:
A static member belongs to the class rather than to an instance of the class.
A static block is used to initialize a static variable or execute some initialize code since the block is executed at the time of the class loading, before any constructors or methods.
class Block {
static {
System.out.println("static block executed");
}
Block() {
System.out.println("constructor executed");
}
public static void main(String args[]) {
new Block();
System.out.println("main method executed");
}
}
The output is:
static block executed
constructor executed
main method executed
Static blocks are executed in the order they are defined.
A static variable is used to refer a common property of all objects or instances (something that is not unique for each object) of a class. It's initialized at the time of class loading.
public class Man {
String name;
static String gender = "M";
Man(String name) {
this.name = name;
}
void display() {
System.out.println(name+" "+name+" "+gender+" "+gender);
}
public static void main(String args[]) {
Man m1 = new Man("Bob");
Man m2 = new Man("Richard");
m1.display();
m2.display();
}
}
The output is:
Bob M
Richard M
A static method also belongs to the class rather than object of a class and can be invoked without the need for creating an instance of a class. The only restrictions are:
this
and super
cannot be used in a static method.
public class Square {
static int calculate(int x){
return x*x;
}
public static void main(String args[]){
int result = Square.calculate(9);
System.out.println(result);
}
}
We can define a class within another class. Such a class is called a nested class. We can't make a top level class static. Only nested classes can be static.
class OuterClass {
// Static nested class
public static class NestedStaticClass{
public void print() {
System.out.println("Message from nested static class");
}
}
}
public class Test {
public static void main(String args[]) {
// create instance of nested Static class
OuterClass.NestedStaticClass sc = new OuterClass.NestedStaticClass();
// call non static method of nested static class
sc.print();
}
}
The characteristics of a static nested class are: