1. Encapsulation
Encapsulation is a fundamental principle of Object-Oriented Programming (OOP) that involves bundling the data (fields) and methods (functions) that operate on the data into a single unit or class. It restricts direct access to some components and helps in achieving a modular and secure structure.
- Private Access Modifier: Declares fields as
private
, ensuring they can only be accessed through methods. - Getter and Setter Methods: Provides controlled access to private fields:
class Person { private String name; public String getName() { return name; } public void setName(String name) { this.name = name; } }
2. Interfaces
Interfaces define a contract or blueprint for classes, specifying methods that must be implemented. They play a crucial role in achieving abstraction and polymorphism in Java.
- Declaring Interfaces: Interfaces are declared using the
interface
keyword. - Implementing Interfaces: Classes use the
implements
keyword to provide concrete implementations. - Extending Interfaces: Interfaces can extend other interfaces to build complex contracts.
Example:
interface Animal {
void sound();
}
class Dog implements Animal {
public void sound() {
System.out.println("Woof!");
}
}
3. Object Composition vs Inheritance
Java supports both inheritance and composition as ways to achieve code reuse. Understanding their differences helps make better design decisions:
- Inheritance: Represents an "is-a" relationship, such as
Dog
is anAnimal
. It should be used when classes share a significant amount of common behavior. - Composition: Represents a "has-a" relationship, such as
Car
has anEngine
. It is more flexible than inheritance.
Example of Composition:
class Engine {
void start() {
System.out.println("Engine starts");
}
}
class Car {
private Engine engine = new Engine();
void drive() {
engine.start();
System.out.println("Car is driving");
}
}
4. Polymorphism
Polymorphism allows methods to take many forms, enabling flexibility and reusability in code. It can be achieved through method overriding and interfaces.
- Automatic Casting: The runtime determines the correct method to call based on the object type.
- Explicit Casting: Used when converting a superclass reference back to a subclass.
- Compile-time vs Runtime Errors: Ensures the program avoids type mismatches during runtime.
Example:
class Animal {
void sound() { System.out.println("Some sound"); }
}
class Cat extends Animal {
@Override
void sound() { System.out.println("Meow"); }
}
Animal a = new Cat();
a.sound(); // Outputs: Meow
5. Lambda Expressions
Lambdas were introduced in Java 8 to provide a concise way of writing anonymous functions. They simplify functional programming in Java.
- Syntax Options:
(parameters) -> { body }
- Usage in Java 8: Commonly used with the Stream API and functional interfaces like
Runnable
,Comparator
, andPredicate
.
Example:
List names = Arrays.asList("Alice", "Bob", "Charlie");
names.forEach(name -> System.out.println(name));
6. Design Principles
Design principles are guidelines that help developers create scalable and maintainable code.
- Established Ideas: Concepts like DRY (Don't Repeat Yourself) and SOLID principles.
- General Solutions: Avoid hardcoding and aim for modular code design.
7. Creational Patterns
Creational patterns deal with object creation mechanisms to promote flexibility and reuse.
- Singleton Pattern: Ensures only one instance of a class is created during runtime.
- Immutable Object Pattern: Ensures an object's state cannot be changed after its creation.
Example of Singleton:
class Singleton {
private static Singleton instance;
private Singleton() {}
public static Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
Example of Immutable Object Pattern:
// Immutable class
public final class Person {
private final String name;
private final int age;
// Constructor to initialize fields
public Person(String name, int age) {
this.name = name;
this.age = age;
}
// Getter methods
public String getName() {
return name;
}
public int getAge() {
return age;
}
@Override
public String toString() {
return "Person{name='" + name + "', age=" + age + '}';
}
public static void main(String[] args) {
// Creating an immutable object
Person person = new Person("Alice", 25);
// Trying to modify the object's state
System.out.println(person); // Output: Person{name='Alice', age=25}
// person.name = "Bob"; // Compilation Error: Field is final
// person.age = 30; // Compilation Error: Field is final
// Object remains unchanged
System.out.println(person); // Output: Person{name='Alice', age=25}
}
}
0 Comments