1. Introduction to Java
Java, devised by James Gosling and introduced by Sun Microsystems in 1995, champions the "Write Once, Run Anywhere" philosophy through the Java Virtual Machine (JVM) using bytecode. This versatile, object-oriented language is celebrated for blending simplicity with efficiency.
The language's success and longevity can be attributed to several key characteristics:
1.1 Simplicity
The creators of Java aimed to design a language that was easier to use than C++ but retained its powerful capabilities. In this endeavor:
- Java discarded the use of pointers, which are notorious for being a source of errors.
- Memory management in Java is automated, with a feature called Garbage Collection, minimizing the risk of memory leaks.
- It supports a rich set of APIs (Application Programming Interfaces) which simplify tasks ranging from file handling to network communication.
- The language syntax is clean, with a focus on readability, which aids in reducing the complexity of code.
1.2 Portability
One of the pioneering visions behind Java was its ability to be platform-independent. This is achieved through:
- Bytecode: Instead of compiling code directly into machine language, Java compiles into an intermediate form called bytecode. This bytecode is then interpreted or compiled at runtime by the JVM, making it adaptable to any operating system or platform that has a JVM.
- Standard Libraries: Java provides a standard set of libraries which ensures that Java applications behave consistently across all platforms.
- Network Centric: Java was designed in the early days of the internet, with built-in support for web and network operations, making it ideal for internet-based applications.
1.3 Performance-Oriented
While initially, Java faced criticism for its performance, significant advancements have been made over the years:
- Just-In-Time (JIT) Compilation: Instead of interpreting bytecode line by line, the JIT compiler translates bytecode into native machine instructions, which are directly executed by the host machine, drastically improving performance.
- Optimized Libraries: Java's standard libraries have been heavily optimized over the years, providing efficient data structures and algorithms.
- Hardware Acceleration: Modern JVMs can leverage hardware acceleration for tasks like graphics rendering, improving the performance of graphic-intensive applications.
1.4 Security
Java places a strong emphasis on security:
- Bytecode Verification: Before execution, bytecode is checked for any malicious code or potential issues.
- Sandboxing: Java applets, which run in web browsers, operate in a sandbox environment, restricting them from accessing certain resources on the local machine, thus safeguarding the user.
- Classloaders: They separate the classes of the Java API from those created by the developer, adding an additional layer of security.
- Public Key Encryption: Java supports public key encryption for authentication and secure data transfer.
1.5 Object-Oriented
Java is inherently object-oriented, which fosters:
- Modularity: Code can be organized into objects and classes, making it reusable and modular.
- Real-world Modeling: Real-world scenarios can be easily modeled using classes and objects in Java, aiding in clear conceptualization and solution designing.
- Code Maintenance: OOP principles, when properly applied, make Java code easier to maintain and extend.
1.6 Performance & Architecture
Java's efficient architecture boosts performance:
- Speed enhancement via JIT compilation.
- Optimized, high-efficiency libraries.
- Architecture neutrality with universal bytecode.
- Native support for multithreaded programming.
1.7 Distributed & Dynamic
Java's adaptability:
- Facilitates creation of distributed apps.
- Integrates seamlessly with protocols.
- Supports dynamic, on-demand class loading.
- Compatible with native languages, like C++.
In conclusion, Java's design principles and its continuous evolution in response to the changing technological landscape make it a robust, secure, and versatile language and ensure its relevance across diverse applications, from web applications to enterprise software systems to mobile applications.
2. Core Language Syntax and Semantics
2.1 Data Types
In Java, data types define the size and nature of values. They split into primitive and reference categories.
-
Primitive data types: These are fundamental data types that include:
byte
: 8-bit signed integer. Example:byte a = 10;
short
: 16-bit signed integer. Example:short b = 5000;
int
: 32-bit signed integer. Example:int c = 100000;
long
: 64-bit signed integer. Example:long d = 5000000000L;
float
: 32-bit floating point. Example:float e = 2.5f;
double
: 64-bit floating point. Example:double f = 5.75;
char
: 16-bit Unicode character. Example:char g = 'A';
boolean
: Represents true or false. Example:boolean h = true;
-
Reference data types: Object-based types that store references to memory locations.
String
: Represents strings. Example:String name = "Java";
Array
: A container object holding a fixed number of elements. Example:int[] array = {1, 2, 3, 4, 5};
- User-defined classes: Custom data types created by users. Example:
class Person { String name; int age; }
2.2 Operators and Expressions
Operators perform tasks on variables/values, while expressions combine these to produce a singular outcome.
- Arithmetic Operators:
+, -, *, /, %
. Example:int result = 5 * 10;
- Relational Operators:
==, !=, >, <, >=, <=
. Example:boolean isEqual = (a == b);
- Logical Operators:
&&, ||, !
. Example:boolean isTrue = (a > b && c < d);
2.3 Control Flow Statements
Java employs these to steer execution flow:
-
if-else: Checks conditions and dictates the execution block. Example:
if (age > 18) { System.out.println("Adult"); } else { System.out.println("Minor"); }
-
loops: Executes code blocks multiple times.
- For loop:
for(int i=0; i < 5; i++) { System.out.println(i); }
- While loop:
int i = 0; while(i < 5) { System.out.println(i); i++; }
- Do-while loop:
int i = 0; do { System.out.println(i); i++; } while(i < 5);
- For loop:
2.4 Methods in Java
In Java, methods are blocks of code that encapsulate a specific operation or computation. They help organize and reuse code and improve modularity and readability. Here are some key points about methods:
- Declaration: A method in Java is declared with a return type, method name, and a pair of parentheses which may include parameters. The syntax for method declaration is:
returnType methodName(parameter1Type parameter1Name, parameter2Type parameter2Name, ...);
- Method Signature: The combination of the method's name and its parameter list is called its signature. Return type is not a part of the method signature.
- Return Type: Every method in Java is declared with a return type and it is mandatory for all methods. If a method does not return any value, its return type is `void`.
- Method Call: Once a method is defined, it can be called from another method, a constructor, or any block of code. The general form of a method call is:
objectReference.methodName(argument1, argument2, ...);
- Recursion: In Java, a method can call itself. Such methods are called recursive methods. They are useful for certain tasks, like calculating factorial, but must have a termination condition to prevent infinite looping.
2.5 Java Keywords and Modifiers
Java has a set of keywords that have special meanings in the language. These keywords or modifiers determine the visibility, behavior, and other properties of Java classes, methods, and variables.
- public: The `public` keyword is an access modifier that means the member (class, method, or variable) is accessible from any other class. It has the widest scope among all other modifiers.
- private: The `private` keyword means the member is only accessible within its own class. It has the narrowest scope.
- protected: The `protected` keyword means the member is accessible within its own package and by subclasses.
- static: The `static` keyword means that a member (method or variable) belongs to the class rather than an instance of the class. It can be accessed without creating an instance of the class. For methods, this means that the method can be called on the class itself, rather than on instances of the class.
public class MyClass { public static void staticMethod() { System.out.println("This is a static method."); } } MyClass.staticMethod(); // correct way to call a static method
- final: The `final` keyword means that the value can't be modified after it's been assigned. For variables, it means they can't be changed. For methods, it means they can't be overridden by subclasses. For classes, it means they can't be subclassed.
- abstract: The `abstract` keyword is used to declare either a method or a class. If a method within an interface or abstract class is declared as abstract, it doesn't have a body and it needs to be implemented by any class that inherits the interface or abstract class.
- transient: The `transient` keyword indicates that a variable should not be serialized when the class instance containing it is persisted to storage.
- volatile: The `volatile` keyword indicates that a variable may change unexpectedly. It tells the JVM that multiple threads might access this variable.
3. Object-Oriented Programming (OOP) in Java
OOP is a programming paradigm centered around objects, which can contain data and behavior. Java, being an object-oriented language, fully supports these OOP concepts.
3.1 Classes and Objects
At the heart of OOP in Java are classes and objects.
- Class: A class serves as a blueprint for objects. It defines the structure and behaviors that its objects will have.
- Object: An instance of a class. It's a self-contained unit that consists of data attributes and methods to perform operations on the data.
class Car {
String color;
int speed;
void accelerate() {
speed++;
}
}
Car myCar = new Car(); // Creating an object of the Car class
class Car {
String color;
int speed;
// Constructor
Car(String c) {
color = c;
speed = 0;
}
}
Car redCar = new Car("red"); // Using the constructor to create a Car object
3.2 Inheritance
Inheritance allows a class to adopt attributes and behaviors from another class.
- Base Class (Parent Class): The source of attributes and behaviors for other classes.
class Vehicle { int speed; // Method to increase speed void accelerate() { speed++; } // Method to display current speed void displaySpeed() { System.out.println("Speed: " + speed); } } public class MainBase { public static void main(String[] args) { Vehicle car = new Vehicle(); car.accelerate(); // Increase speed car.displaySpeed(); // Should display: Speed: 1 } }
- Derived Class (Child Class): Inherits attributes and behaviors from the base class.
class Truck extends Vehicle { // Truck-specific attribute int cargoCapacity; }
- `super` keyword: Refers to the parent class's elements in the child class.
class Parent { int value = 100; Parent() { System.out.println("Parent constructor."); } } class Child extends Parent { int value = 200; Child() { // Calls parent's constructor super(); System.out.println("Child constructor."); } void displayValues() { // Displays parent's value System.out.println("Parent value: " + super.value); // Displays child's value System.out.println("Child value: " + value); } } public class MainDerived { public static void main(String[] args) { Child child = new Child(); child.displayValues(); // Should display both values } }
3.3 Polymorphism
Polymorphism, derived from Greek meaning "many shapes", allows objects of different classes to be treated as objects of a common superclass.
- Method Overloading: Same method name but with different parameters.
class Calculator { // Method to add two integers public int add(int a, int b) { return a + b; } // Method to add three integers (overloaded) public int add(int a, int b, int c) { return a + b + c; } // Method to add two double values (overloaded) public double add(double a, double b) { return a + b; } // Method to concatenate two strings (overloaded for demonstration) public String add(String a, String b) { return a + b; } } public class Main { public static void main(String[] args) { Calculator calculator = new Calculator(); System.out.println("Add two integers: " + calculator.add(5, 3)); System.out.println("Add three integers: " + calculator.add(5, 3, 2)); System.out.println("Add two doubles: " + calculator.add(5.5, 3.3)); System.out.println("Concatenate two strings: " + calculator.add("Hello, ", "world!")); } }
- Method Overriding: Child class provides a specific implementation of a method that is already defined in its parent class.
class Animal {
void sound() {
System.out.println("Animal makes a sound");
}
}
class Cat extends Animal {
@Override
void sound() {
System.out.println("Cat meows");
}
}
3.4 Encapsulation
Encapsulation is the bundling of data and methods that operate on that data, restricting the access to some of the object's components.
- Access Modifiers: Java provides access modifiers (private, protected, public) to set the level of access.
// Define a class named "Person" class Person { // PRIVATE member: Can only be accessed within the class itself private String name; // PROTECTED member: Can be accessed within the class, its subclasses, and within the same package protected int age; // PUBLIC member: Can be accessed from any other class public String address; // Constructor to initialize members public Person(String name, int age, String address) { this.name = name; this.age = age; this.address = address; } // PRIVATE method: Can only be accessed within the class private void printName() { System.out.println("Name: " + name); } // PROTECTED method: Can be accessed within the class, its subclasses, and within the same package protected void printAge() { System.out.println("Age: " + age); } // PUBLIC method: Can be accessed from any other class public void printAddress() { System.out.println("Address: " + address); } // Method to demonstrate accessing members/methods public void displayInfo() { printName(); // Accessing the private method printAge(); // Accessing the protected method printAddress(); // Accessing the public method } } // Demonstration public class Main { public static void main(String[] args) { // Create an object of the Person class Person person = new Person("John", 25, "123 Main St"); // Call the method to display information person.displayInfo(); // Directly accessing the public member System.out.println("Accessing directly: " + person.address); } }
- Getters and Setters: Methods used to access and modify private data members of the class.
class Circle {
private double radius;
public double getRadius() {
return radius;
}
public void setRadius(double r) {
if(r > 0) {
radius = r;
}
}
}
3.5 Abstraction
Abstraction is the concept of hiding the complex implementation details and showing only the essential features of an object.
- Abstract Classes: Cannot be instantiated and may contain abstract (unimplemented) methods.
// Define an abstract class named "Animal" abstract class Animal { // Data member private String name; // Constructor to set the name public Animal(String name) { this.name = name; } // Getter for name public String getName() { return name; } // An abstract method named "sound" // This method does not have a body (it's unimplemented) abstract void sound(); // A regular method public void sleep() { System.out.println(name + " is sleeping."); } } // Define a concrete class "Dog" that extends the abstract class "Animal" class Dog extends Animal { // Constructor to set the name of the dog public Dog(String name) { super(name); } // Implement the abstract method "sound" @Override void sound() { System.out.println(getName() + " barks."); } } // Demonstration public class Main { public static void main(String[] args) { // Create an object of the Dog class Dog myDog = new Dog("Buddy"); // Call the methods myDog.sound(); myDog.sleep(); } }
- Interfaces: Defines a contract of methods that a class must implement.
abstract class Animal {
abstract void sound();
}
interface Runner {
void run();
}
class Dog extends Animal implements Runner {
@Override
void sound() {
System.out.println("Dog barks");
}
@Override
public void run() {
System.out.println("Dog runs");
}
}
4. Core Libraries
Java's power and versatility derive from its comprehensive libraries. These provide predefined methods, simplifying complex tasks and promoting code reusability.
4.1 java.lang
The foundational package of Java, java.lang
, is automatically imported into every Java program. It offers classes integral to the language's essence.
4.1.1 String
String represents sequences of characters. Java treats strings as immutable, ensuring memory efficiency and security.
// Initializing a String
String greeting = "Java";
// Using a method from the String class
System.out.println(greeting.toUpperCase()); // JAVA
4.1.2 StringBuilder
StringBuilder offers a mutable character sequence, ideal for dynamic string manipulations, making operations faster than with String.
// Initializing StringBuilder
StringBuilder builder = new StringBuilder("Hello");
// Appending to StringBuilder
builder.append(" Java");
System.out.println(builder); // Hello Java
4.1.3 Math
The Math class provides ready-to-use mathematical functions.
// Calculating power using Math class
double square = Math.pow(5, 2);
System.out.println(square); // 25.0
4.2 java.util
The java.util
package houses utility functions, ranging from data structures to date-time operations and randomizers.
4.2.1 Collections Framework
This set of classes and interfaces manages group data efficiently. Structures like ArrayList and HashMap store and organize data in specific ways.
// Creating an ArrayList and adding elements
ArrayList<String> fruits = new ArrayList<>();
fruits.add("Apple");
fruits.add("Mango");
System.out.println(fruits); // [Apple, Mango]
4.2.2 Date & Time API
Java 8 introduced a revamped Date & Time API, allowing precise and human-readable date-time operations.
// Fetching the current date
LocalDate currentDate = LocalDate.now();
System.out.println(currentDate); // Outputs date in the format: YYYY-MM-DD
4.2.3 Utilities
Other utilities, such as Random for generating random numbers and Scanner for input, are crucial for diverse tasks.
// Generating a random number between 0 and 99
Random randomizer = new Random();
int randomNum = randomizer.nextInt(100);
System.out.println(randomNum);
5. Exception Handling
In Java, unexpected events during program execution, like file not found or division by zero, are managed using exceptions. This mechanism helps maintain the program's flow without abrupt halts.
5.1 Understanding Exceptions
Exceptions are objects that encapsulate error information. Java has a robust hierarchy of exception classes—broadly classified into checked (handled at compile time) and unchecked (runtime) exceptions.
5.2 try-catch-finally
The try-catch-finally
construct is used to capture and manage exceptions.
- try: Encloses code that might throw an exception.
- catch: Catches the exception and defines how it should be handled.
- finally: Executes code regardless of whether an exception was thrown or not. Commonly used for cleanup operations.
try {
int result = 10 / 0; // This will throw an ArithmeticException
} catch (ArithmeticException e) {
System.out.println("Division by zero is not allowed!");
} finally {
System.out.println("Execution completed.");
}
5.3 Custom exceptions
You can create tailored exception types to depict specific error scenarios by extending the Exception
class or its descendants.
// Custom exception
class AgeOutOfRangeException extends Exception {
public AgeOutOfRangeException(String message) {
super(message);
}
}
public class Main {
static void checkAge(int age) throws AgeOutOfRangeException {
if(age < 18 || age > 60) {
throw new AgeOutOfRangeException("Age out of valid range!");
} else {
System.out.println("Age is within the valid range.");
}
}
public static void main(String[] args) {
try {
checkAge(65); // This will throw the custom exception
} catch (AgeOutOfRangeException e) {
System.out.println(e.getMessage());
}
}
}
5.4 throw and throws
throw: Used within methods to throw an exception.
throws: Used in method signatures to declare the exceptions the method might throw, alerting callers to handle or propagate them.
6. Java I/O
Java I/O (Input/Output) allows programs to read from and write to various data sources, be it files, network sockets, or console.
6.1 Streams
Streams are sequences of data that can be read from (input stream) or written to (output stream). They are categorized based on the type of data they handle:
- Byte Streams: Handle I/O of raw binary data.
- Character Streams: Handle I/O of character data, automatically considering character encoding.
// Byte stream example
FileInputStream fis = new FileInputStream("example.txt");
int data = fis.read(); // Reads a single byte
fis.close();
// Character stream example
FileReader fr = new FileReader("example.txt");
int charData = fr.read(); // Reads a single character
fr.close();
6.2 File Handling
The File
class offers an abstraction for file and directory paths. It doesn't read/write, but allows operations like checking if a file exists, directory listing, etc.
File file = new File("example.txt");
if(file.exists()) {
System.out.println("File exists!");
} else {
System.out.println("File not found.");
}
6.3 Reader and Writer classes
These are higher-level I/O classes focused on character data. They include useful subclasses like BufferedReader
and PrintWriter
, which offer efficient reading/writing and formatting capabilities, respectively.
// Using BufferedReader for efficient reading
BufferedReader br = new BufferedReader(new FileReader("example.txt"));
String line;
while((line = br.readLine()) != null) {
System.out.println(line);
}
br.close();
// Using PrintWriter for easy formatted writing
PrintWriter pw = new PrintWriter("output.txt");
pw.println("Hello, World!");
pw.close();
7. Concurrency
Concurrency in Java allows multiple threads to execute tasks in parallel, boosting performance, especially in CPU-bound applications.
7.1 Threads and the java.util.concurrent package
Threads represent individual units of concurrent execution. Java provides tools to manage and control these threads efficiently.
// Creating and starting a thread
Thread thread = new Thread(() -> {
System.out.println("Thread is running!");
});
thread.start();
// Using the concurrent package for thread-safe collections
java.util.concurrent.ConcurrentHashMap concurrentMap = new java.util.concurrent.ConcurrentHashMap<>();
concurrentMap.put("Key", 1);
7.2 Synchronization and locks
When multiple threads access shared resources, synchronization is used to ensure that resource access remains consistent and thread-safe.
// Synchronized method
synchronized void synchronizedMethod() {
// code...
}
// Using ReentrantLock from java.util.concurrent.locks for more advanced synchronization
java.util.concurrent.locks.ReentrantLock lock = new java.util.concurrent.locks.ReentrantLock();
lock.lock();
try {
// access shared resources
} finally {
lock.unlock();
}
7.3 Java Memory Model
The Java Memory Model (JMM) defines how Java threads interact through memory. It ensures that consistent values are seen by each thread, even when changes are made by other threads concurrently.
// volatile keyword ensures a variable's updates are visible to all threads
volatile boolean flag = false;
The JMM ensures that threads have a consistent view of memory. Understanding the JMM is crucial for writing safe multithreaded code.
8. Java Annotations and Reflection
Annotations provide metadata about code, whereas Reflection enables dynamic runtime behavior analysis and manipulation of Java applications.
8.1 Built-in annotations
Java offers predefined annotations to convey additional information about your code, aiding both the compiler and developers.
// Using @Override to indicate that a subclass method is intended to override a superclass method
@Override
public String toString() {
return "ExampleClass{}";
}
// Marking a method as no longer recommended for use with @Deprecated
@Deprecated
public void oldMethod() {
// ...
}
8.2 Creating custom annotations
You can create your own annotations to add custom metadata to your code, often used in frameworks and libraries.
// Defining a custom annotation
public @interface CustomAnnotation {
String author() default "Unknown";
String date();
}
// Using the custom annotation
@CustomAnnotation(date = "2023-09-10")
public class ExampleClass {
// ...
}
8.3 Reflection API
Reflection enables Java code to inspect and manipulate the structure and behavior of applications at runtime, making it powerful but also risky if misused.
// Using Reflection API to get class name and methods
Class> exampleClass = ExampleClass.class;
System.out.println("Class Name: " + exampleClass.getName());
for (Method method : exampleClass.getMethods()) {
System.out.println("Method: " + method.getName());
}
While the Reflection API is powerful, use it judiciously as it can compromise security, increase overhead, and lead to code complexity.
9. Java Virtual Machine (JVM)
The JVM runs Java bytecode, making Java's "write once, run anywhere" promise a reality.
9.1 Understanding JVM architecture
The JVM is the cornerstone of Java's runtime environment, designed with a rich architecture that optimally executes Java applications.
- Classloader: JVM employs a classloader to load .class files (compiled Java code) when they are required. It works in three phases:
- Loading: Loads the binary data from the .class file.
- Linking: Verifies the loaded class, prepares static variables, and stores the bytecode in the method area.
- Initialization: Executes static initializers and static initialization blocks.
- Runtime Data Area: The JVM's memory is segmented for different kinds of data:
- Method Area: Stores per-class structures, such as runtime constant pool, field and method data, and the code for methods and constructors.
- Heap: All objects are allocated here. It is further divided into Young and Old generations for garbage collection optimization.
- Stack: Java Stack stores frames and holds local variables and partial results. Each thread in Java has a private JVM stack.
- Program Counter (PC) Register: Contains the address of the current instruction being executed.
- Native Method Stack: Similar to the Java Stack, but for native methods written in other languages.
- Execution Engine: Converts bytecode into machine code.
- Interpreter: Reads bytecode instructions and executes them directly.
- Just-In-Time Compiler (JIT): Optimizes the process by converting bytecode to native machine code, which is then executed directly by the host operating system.
- Java Native Interface (JNI): JNI allows Java code to interoperate with applications and libraries written in other programming languages. JNI provides a bridge to invoke functions written in languages like C and C++.
- Native Method Libraries: This is a collection of native libraries (C, C++ libraries) necessary for the Execution Engine.
9.2 Class loading
Java classes aren't loaded all at once, but only when they're needed. This dynamic class loading is the Classloader's responsibility.
// Dynamically loading a class using the ClassLoader
Class> loadedClass = Class.forName("com.example.MyClass");
There are three primary types of Classloaders: Bootstrap, Extension, and System (or Application). They follow a parent-child relationship to ensure safe class loading.
9.3 Just-In-Time (JIT) compilation
While the JVM can interpret bytecode, this isn't always the fastest way to execute code. Enter the JIT compiler, which converts bytecode into native machine code just before execution, significantly speeding up the process.
// A sample code snippet that gets optimized by JIT over time
for (int i = 0; i < 100000; i++) {
// Repeated computations here are optimized by the JIT compiler.
}
JIT works alongside the JVM's interpreter. While the interpreter quickly translates individual bytecode instructions into machine code, JIT takes larger chunks, optimizes them, and then translates. This synergy helps Java maintain both portability and performance.
10. Design Patterns
Design patterns offer standard solutions to recurring problems encountered during software development. They represent best practices, derived from the collective experience of software professionals.
10.1 Singleton
This pattern restricts a class from instantiating multiple objects. It's often used for things like database connections or logging where you want a single instance to coordinate actions across a system.
public class Singleton {
private static Singleton instance;
private Singleton() {}
public static Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
10.2 Factory
The Factory pattern provides an interface for creating instances of a class, with its subclasses deciding which class to instantiate.
interface Product {}
class ConcreteProductA implements Product {}
class ConcreteProductB implements Product {}
class Factory {
public Product createProduct(String type) {
if ("A".equals(type)) {
return new ConcreteProductA();
} else if ("B".equals(type)) {
return new ConcreteProductB();
}
return null;
}
}
10.3 Observer
Defines a one-to-many dependency between objects. When one object (the subject) changes state, all its dependents are notified.
interface Observer {
void update();
}
class ConcreteObserver implements Observer {
public void update() {
// React to the update
}
}
class Subject {
private List<Observer> observers = new ArrayList<>();
public void addObserver(Observer observer) {
observers.add(observer);
}
public void removeObserver(Observer observer) {
observers.remove(observer);
}
public void notifyObservers() {
for (Observer observer : observers) {
observer.update();
}
}
}
10.4 Decorator
Adds additional responsibilities to objects dynamically, providing a flexible alternative to subclassing for extending functionality.
interface Coffee {
double cost();
}
class SimpleCoffee implements Coffee {
public double cost() {
return 5;
}
}
class MilkDecorator implements Coffee {
Coffee coffee;
MilkDecorator(Coffee coffee) {
this.coffee = coffee;
}
public double cost() {
return coffee.cost() + 2;
}
}
10.5 Strategy
Defines a set of encapsulated algorithms that can be swapped to carry out a specific behavior.
interface Strategy {
void execute();
}
class ConcreteStrategyA implements Strategy {
public void execute() {
// Implementation of A
}
}
class Context {
private Strategy strategy;
Context(Strategy strategy) {
this.strategy = strategy;
}
public void executeStrategy() {
strategy.execute();
}
}
11. Conclusion
Java, with its versatility, has found its place in various domains of software development. Whether you're a beginner looking to dip your toes in programming or a PhD scholar diving deep into complex systems, Java offers a world of opportunities. Its rich set of features, combined with a robust architecture, makes it a top choice for developers around the world.