Public vs Protected vs Package vs Private Access Modifier in Java
Access Modifiers in Java
In Java, access modifiers are used to control the visibility and accessibility of classes, methods, and variables. By using these modifiers, we provide the JVM with information like whether a class can be accessed from outside its package, whether child classes can be created, and whether object instantiation is allowed.
Modifier 1: Public Access Modifier
When a class is declared as public, it can be accessed from anywhere. The same applies to methods and variables declared as public within that class.
Example:
// Creating a package
package pack1;
// Declaring a public class
public class MyClass1 {
// Declaring a public method
public void display() {
System.out.println("Public Access Modifier Example");
}
}
In another package, you can import this class and use it:
// Creating a package
package pack2;
// Importing the class from pack1
import pack1.MyClass1;
public class Main {
public static void main(String[] args) {
// Creating an object of class MyClass1
MyClass1 obj = new MyClass1();
// Calling the public method
obj.display();
}
}
Output:
Public Access Modifier Example
If MyClass1 was not public, you would receive a compile-time error stating that MyClass1 is not accessible from another package.
Modifier 2: Protected Access Modifier
The protected modifier is applicable to data members, methods, and constructors but not for top-level classes or interfaces. When a member is declared as protected, it is accessible within the same package and in subclasses of other packages.
Example:
package pack1;
// Declaring a parent class
class MyClass2 {
// Declaring a protected method
protected void show() {
System.out.println("Protected Access Modifier Example");
}
}
Now, in another package, we can access it through inheritance:
package pack2;
// Importing the class from pack1
import pack1.MyClass2;
class ChildClass extends MyClass2 {
public static void main(String[] args) {
// Creating an instance of the child class
ChildClass obj = new ChildClass();
// Accessing the protected method
obj.show();
}
}
Output:
Protected Access Modifier Example
We can access the protected method in a subclass even from a different package, but not directly from a non-child class in another package.
Modifier 3: Private Access Modifier
The private modifier restricts access to members within the same class only. Neither child classes nor other classes, even within the same package, can access private members.
Example:
// Defining a class
class MyClass3 {
// Declaring a private method
private void secret() {
System.out.println("Private Access Modifier Example");
}
public void accessPrivate() {
// Accessing the private method within the same class
secret();
}
}
public class Main {
public static void main(String[] args) {
MyClass3 obj = new MyClass3();
// Accessing the public method
obj.accessPrivate();
}
}
Output:
Private Access Modifier Example
Modifier 4: Default (Package) Access Modifier
When no access modifier is specified, the default access level (also called “package-private”) applies. This means that the class or its members are accessible only within the same package and not from other packages.
Example:
// Defining a class with default access
class MyClass4 {
// Declaring a default-access method
void displayMessage() {
System.out.println("Default Access Modifier Example");
}
}
public class Main {
public static void main(String[] args) {
// Creating an object of MyClass4
MyClass4 obj = new MyClass4();
// Calling the default-access method
obj.displayMessage();
}
}
Output:
Default Access Modifier Example
Summary of Differences Between Access Modifiers
Modifier
Applicability
Accessibility From Same Package
Accessibility From Different Package
Accessibility from Subclass Outside Package
Accessibility from Non-subclass Outside Package
Public
Classes, Methods, Fields
Yes
Yes
Yes
Yes
Protected
Methods, Fields
Yes
No
Yes (only in subclasses)
No
Private
Methods, Fields
No
No
No
No
Default (Package)
Classes, Methods, Fields
Yes
No
No
No
Access and Non Access Modifiers in Java
Java is one of the most popular and widely-used programming languages, known for its speed, reliability, and security. Java applications can be found everywhere—from desktop software to web applications, scientific supercomputers to gaming consoles, and mobile phones to the Internet. In this guide, we’ll explore how to write a simple Java program.
Steps to Implement a Java Program
To implement a Java application, follow these key steps:
1. Creating the Program 2. Compiling the Program 3. Running the Program
If you’re looking to dive deeper into Java and gain a strong understanding of the entire development process, consider enrolling in a structured Java programming course. These courses provide hands-on experience and cover everything from basic to advanced topics, allowing you to develop efficient and scalable applications.
Example of a Simple Java Program
Here’s a basic example to illustrate the process:
// Class with multiple access modifiers
class AccessExample {
public int publicVar = 10;
private int privateVar = 20;
protected int protectedVar = 30;
int defaultVar = 40; // default access level (package-private)
// Public method
public void publicMethod() {
System.out.println("Public Method");
}
// Private method
private void privateMethod() {
System.out.println("Private Method");
}
// Protected method
protected void protectedMethod() {
System.out.println("Protected Method");
}
// Default method
void defaultMethod() {
System.out.println("Default Method");
}
}
public class MainClass {
public static void main(String[] args) {
AccessExample obj = new AccessExample();
// Accessing variables
System.out.println("Public variable: " + obj.publicVar);
System.out.println("Protected variable: " + obj.protectedVar);
System.out.println("Default variable: " + obj.defaultVar);
// Accessing methods
obj.publicMethod();
obj.protectedMethod();
obj.defaultMethod();
}
}
Output:
Public variable: 10
Protected variable: 30
Default variable: 40
Public Method
Protected Method
Default Method
The private member privateVar and method privateMethod() are not accessible from the MainClass since they are declared as private.
Non-Access Modifiers
Non-access modifiers in Java provide additional functionalities beyond access control. Java has several non-access modifiers, which can be applied to classes, methods, and variables to convey specific behavior to the JVM:
1. static: The static modifier is used to declare class-level methods and variables, meaning they belong to the class rather than to instances. 2. final: The final modifier is used to declare constants (final variables), methods that cannot be overridden, and classes that cannot be subclassed. 3. abstract: This modifier applies to classes that cannot be instantiated directly and to methods that must be implemented by subclasses. 4. synchronized: The synchronized keyword is used to control access to methods by multiple threads to ensure that only one thread executes the method at a time. transient: This modifier is used to declare that a variable should not be serialized. 5. volatile: The volatile modifier informs the JVM that a variable’s value may change in a way that is not visible to other threads. 6. native: The native keyword indicates that a method is implemented in platform-specific code, typically in C or C++.
Example: Non-Access Modifiers
// Class with non-access modifiers
class NonAccessExample {
// Static variable
static int staticVar = 50;
// Final variable
final int finalVar = 100;
// Transient variable
transient int transientVar = 200;
// Static method
static void staticMethod() {
System.out.println("Static Method");
}
// Final method
final void finalMethod() {
System.out.println("Final Method");
}
// Synchronized method
synchronized void synchronizedMethod() {
System.out.println("Synchronized Method");
}
}
public class MainClassNonAccess {
public static void main(String[] args) {
NonAccessExample obj = new NonAccessExample();
// Accessing static members
System.out.println("Static variable: " + NonAccessExample.staticVar);
NonAccessExample.staticMethod();
// Accessing final member and method
System.out.println("Final variable: " + obj.finalVar);
obj.finalMethod();
// Calling synchronized method
obj.synchronizedMethod();
}
}
Output:
Static variable: 50
Static Method
Final variable: 100
Final Method
Synchronized Method
Key Differences Between Access and Non-Access Modifiers
Access modifiers control the visibility and access of classes, methods, and fields.
Non-access modifiers provide additional characteristics such as immutability, concurrency control, or indicate that something is platform-specific.
In Java, keywords or reserved words are predefined terms that are used by the language for specific internal processes or actions. As such, these keywords cannot be used as variable names or identifiers; doing so will result in a compile-time error.
Example of Using Java Keywords as Variable Names
// Java Program to Illustrate the Consequences of Using Keywords as Variable Names
// Driver Class
class Example {
// Main Function
public static void main(String[] args) {
// Note "public" is a reserved word in Java
String public = "Hello, Java!";
System.out.println(public);
}
}
Output:
./Example.java:6: error: illegal start of expression
String public = "Hello, Java!";
^
1 error
Java Keywords List
Java contains a set of keywords that are typically highlighted in different colors in an IDE or editor to distinguish them from other words. Here’s a table summarizing the keywords and their associated actions:
Keyword
Usage
abstract
Specifies that a class or method will be implemented later in a subclass.
assert
Indicates that a condition is assumed to be true at a specific point in the program.
boolean
A data type that can hold true and false values.
break
A control statement used to exit from loops or switch statements.
byte
A data type that can hold 8-bit data values.
case
Used in switch statements to define code blocks.
catch
Handles exceptions thrown by try statements.
char
A data type that can hold a single 16-bit Unicode character.
class
Declares a new class.
continue
Skips the current iteration of a loop and proceeds to the next iteration.
default
Specifies the default block of code in a switch statement.
do
Begins a do-while loop.
double
A data type for 64-bit floating-point numbers.
else
Specifies the alternative branch in an if statement.
enum
Used to declare an enumerated type.
extends
Indicates that a class is derived from another class or interface.
final
Indicates that a variable holds a constant value or that a method cannot be overridden.
finally
A block of code in a try-catch structure that will always execute.
float
A data type for 32-bit floating-point numbers.
for
Used to start a for loop.
if
Tests a condition and executes code based on the result.
implements
Specifies that a class implements an interface.
import
References other classes or packages.
instanceof
Checks whether an object is an instance of a specific class or implements an interface.
int
A data type that can hold a 32-bit signed integer.
interface
Declares an interface.
long
A data type that can hold a 64-bit signed integer.
native
Specifies that a method is implemented in platform-specific code.
new
Creates new objects.
null
Indicates that a reference does not point to any object.
package
Declares a Java package.
private
An access specifier that restricts access to the class where it is declared.
protected
An access specifier that allows access to subclasses and classes in the same package.
public
An access specifier that makes a class, method, or variable accessible throughout the application.
return
Sends control and possibly a return value back from a called method.
short
A data type that can hold a 16-bit signed integer.
static
Indicates that a method or variable belongs to the class rather than an instance.
strictfp
Ensures floating-point calculations follow strict rules for precision.
super
Refers to the superclass of the current object.
switch
A statement that executes code based on a specified value.
synchronized
Indicates that a method or block is synchronized for thread safety.
this
Refers to the current object within a method or constructor.
throw
Used to explicitly throw an exception.
throws
Specifies which exceptions a method can throw.
transient
Indicates that a variable is not part of an object’s persistent state.
try
Starts a block of code that will be tested for exceptions.
void
Specifies that a method does not return a value.
volatile
Indicates that a variable may be changed unexpectedly, used in multithreading.
while
Starts a while loop.
sealed
Declares a class that restricts which classes can extend it.
permits
Used within a sealed class declaration to specify permitted subclasses.
Important Notes on Java Keywords
The keywords const and goto are reserved for potential future use but are not currently utilized in Java.
const: Reserved for future use.
goto: Reserved for future use.
Important Keywords in Java
Keywords are a reserved set of words in a programming language that are used for specific predefined actions.
abstract: This non-access modifier is used for classes and methods to achieve abstraction. For more information, see the abstract keyword in Java.
enum: This keyword is used to define an enumeration in Java.
instanceof: This keyword checks whether an object is an instance of a specified type (class, subclass, or interface).
private: This access modifier restricts visibility; anything declared as private is not accessible outside its class.
protected: Use this keyword to allow access to an element outside of its package, but only to classes that directly subclass your class.
public: Anything declared as public can be accessed from anywhere in the application. For more information on access modifiers, refer to Access Modifiers in Java.
static: This keyword is used to create members (blocks, methods, variables, nested classes) that can be accessed without a reference to a specific instance. For more details, refer to the static keyword in Java.
strictfp: This keyword restricts floating-point calculations, ensuring consistent results across different platforms. For more information, refer to the strictfp keyword in Java.
synchronized: This keyword can be applied to methods or blocks to achieve synchronization in Java. For more details, see Synchronized in Java.
transient: This variable modifier is used during serialization. When we don’t want to save the value of a particular variable in a file during serialization, we use the transient keyword. For more information, refer to the transient keyword in Java.
volatile: The volatile modifier indicates to the compiler that the variable can be modified unexpectedly by other parts of the program. For more details, see the volatile keyword in Java.
Example Program Using Some Keywords
// Java Program to Demonstrate the Use of Various Keywords
// Abstract class definition
abstract class Animal {
abstract void sound(); // Abstract method
}
// Enum definition
enum Color {
RED, GREEN, BLUE
}
// Class implementing the abstract class
class Dog extends Animal {
void sound() {
System.out.println("Bark");
}
}
// Main class
public class KeywordExample {
// Static variable
static int count = 0;
// Synchronized method
synchronized void increment() {
count++;
}
public static void main(String[] args) {
Dog dog = new Dog();
dog.sound(); // Outputs: Bark
Color myColor = Color.RED; // Using enum
System.out.println("Color: " + myColor); // Outputs: Color: RED
KeywordExample example = new KeywordExample();
example.increment();
System.out.println("Count: " + count); // Outputs: Count: 1
}
}
Output:
Bark
Color: RED
Count: 1
Super Keyword in Java
Characteristics of the super Keyword in Java
In Java, the super keyword is utilized to refer to the parent class of a subclass. Here are some key characteristics:
Calling Superclass Constructors: When a subclass is instantiated, its constructor must invoke the constructor of its parent class using super().
Calling Superclass Methods: A subclass can invoke a method defined in its parent class using the super keyword, which is helpful when the subclass wants to execute the parent class’s implementation of that method as well.
Accessing Superclass Fields: A subclass can reference a field from its parent class using the super keyword. This is useful in cases where both the subclass and parent class have a field with the same name.
Placement in Constructor: The super() statement must be the first line in the constructor of the subclass when invoking a superclass constructor.
Static Context Restriction: The super keyword cannot be used in a static context, such as within static methods or static variable initializers.
Optional Use for Method Calls: While the super keyword can be used to call a parent class method, it is not necessary if the method is not overridden in the subclass. In such cases, calling the method directly will invoke the parent class’s version.
Overall, the super keyword is a powerful tool for subclassing in Java, allowing subclasses to inherit and extend the functionality of their parent classes.
Uses of the super Keyword in Java
The super keyword is primarily used in the following contexts:
1. Using super with Variables 2. Using super with Methods 3.Using super with Constructors
1. Using super with Variables : This situation arises when both a derived class and its base class have identical data members, leading to potential ambiguity.
Example:
// Example of super keyword with variables
// Base class Animal
class Animal {
String type = "Mammal";
}
// Subclass Dog extending Animal
class Dog extends Animal {
String type = "Canine";
void display() {
// Print type from base class (Animal)
System.out.println("Animal Type: " + super.type);
}
}
// Driver Program
public class Test {
public static void main(String[] args) {
Dog dog = new Dog();
dog.display();
}
}
Output:
Drawing a circle.
Drawing a shape.
In this example, both the base class and subclass have a member type. The super keyword allows us to access the type variable of the base class.
2. Using super with Methods : This usage occurs when we need to call a method from the parent class. If both the parent and child classes have methods with the same name, the super keyword resolves ambiguity.
Example:
// Example of super keyword with methods
// Superclass Shape
class Shape {
void draw() {
System.out.println("Drawing a shape.");
}
}
// Subclass Circle extending Shape
class Circle extends Shape {
void draw() {
System.out.println("Drawing a circle.");
}
void display() {
// Calls the current class draw() method
draw();
// Calls the parent class draw() method
super.draw();
}
}
// Driver Program
public class Test {
public static void main(String[] args) {
Circle circle = new Circle();
circle.display();
}
}
In this example, when calling the draw() method, the current class’s implementation is executed, but the super keyword allows us to invoke the superclass’s method as well.
3. Using super with Constructors : The super keyword can also be employed to access the constructor of the parent class. It can call both parameterized and non-parameterized constructors, depending on the situation.
Example 1:
// Example of super keyword with constructors
// Superclass Animal
class Animal {
Animal() {
System.out.println("Animal class Constructor");
}
}
// Subclass Dog extending Animal
class Dog extends Animal {
Dog() {
// Invoke the parent class constructor
super();
System.out.println("Dog class Constructor");
}
}
// Driver Program
public class Test {
public static void main(String[] args) {
Dog dog = new Dog();
}
}
Output:
Animal class Constructor
Dog class Constructor
Advantages of Using the super Keyword in Java
The super keyword in Java offers several advantages in object-oriented programming:
Code Reusability: Using super allows subclasses to inherit functionality from their parent classes, promoting code reuse and reducing redundancy.
Supports Polymorphism: Subclasses can override methods and access fields from their parent classes using super, enabling polymorphism and allowing for more flexible and extensible code.
Access to Parent Class Behavior: Subclasses can utilize methods and fields defined in their parent classes through super, allowing them to leverage existing behavior without needing to reimplement it.
Customization of Behavior: By overriding methods and using super to invoke the parent implementation, subclasses can customize and extend the behavior of their parent classes.
Facilitates Abstraction and Encapsulation: The use of super promotes encapsulation and abstraction by allowing subclasses to focus on their own behavior while relying on the parent class to manage lower-level details.
final Keyword in Java
The final keyword in Java is a non-access modifier that can be applied to variables, methods, or classes. It is used to impose restrictions on the element to which it is applied.
Key Uses of final in Java:
1. Using super with Variables 2. Using super with Methods 3. Using super with Constructors
By exploring these uses in detail, the Java programming course allows developers to understand how and when to apply the final keyword effectively.
Characteristics of final in Java:
1. Final Variables: When a variable is declared as final, its value cannot be changed once it is initialized. This makes it ideal for defining constants. 2. Final Methods: Declaring a method as final prevents subclasses from modifying or overriding that method, ensuring its behavior is consistent. 3. Final Classes: A final class cannot be extended, meaning no other class can inherit from it. This is useful for creating classes that are intended to be used as-is. 4. Initialization of Final Variables: Final variables must be initialized when they are declared or in a constructor. If not, the program will not compile. 5. Performance: The use of final can sometimes improve performance, as the compiler optimizes final variables or methods better since their behavior is predictable. 6. Security: By making certain variables or methods final, you can prevent malicious code from altering critical parts of your program.
Example of final Variable:
The final keyword in Java is a non-access modifier that can be applied to variables, methods, or classes. It is used to impose restrictions on the element to which it is applied.
Key Uses of final in Java:
1. Variables : A variable declared with final cannot have its value changed after initialization. 2. Methods : A method declared with final cannot be overridden by subclasses. 3. Classes: A class declared with final cannot be subclassed or extended.
By exploring these uses in detail, the Java programming course allows developers to understand how and when to apply the final keyword effectively.
Characteristics of final in Java:
1. Final Variables: When a variable is declared as final, its value cannot be changed once it is initialized. This makes it ideal for defining constants. 2. Final Methods: Declaring a method as final prevents subclasses from modifying or overriding that method, ensuring its behavior is consistent. 3. Final Classes: A final class cannot be extended, meaning no other class can inherit from it. This is useful for creating classes that are intended to be used as-is. 4. Initialization of Final Variables: Final variables must be initialized when they are declared or in a constructor. If not, the program will not compile. 5. Performance: The use of final can sometimes improve performance, as the compiler optimizes final variables or methods better since their behavior is predictable. 6. Security: By making certain variables or methods final, you can prevent malicious code from altering critical parts of your program.
Example of final Variable:
public class Example {
public static void main(String[] args) {
// Declaring a final variable
final double CONSTANT = 3.14;
// Printing the value
System.out.println("Constant value: " + CONSTANT);
// Attempting to change the value would cause a compile-time error
// CONSTANT = 3.15;
}
}
Output:
Constant value: 3.14
Different Ways to Use final Variable:
1. Final Variable Initialization at Declaration:
final int MAX_LIMIT = 100;
2. Blank Final Variable:
final int MAX_LIMIT; // Must be initialized later in the constructor
3. Static Final Variable:
static final double E = 2.718;
4. Static Blank Final Variable Initialized in Static Block:
static final int MAX_VALUE;
static {
MAX_VALUE = 999;
}
Initialization of Final Variables:
Final variables must be initialized either at declaration or inside constructors. The Java compiler ensures that once a final variable is initialized, it cannot be reassigned.
Example of Blank Final Variable:
class Demo {
final int THRESHOLD;
// Constructor to initialize blank final variable
public Demo(int value) {
this.THRESHOLD = value;
}
public static void main(String[] args) {
Demo demo = new Demo(10);
System.out.println("Threshold: " + demo.THRESHOLD);
}
}
Output:
Threshold: 10
Final Reference Variable (Non-Transitivity):
In the case of reference variables declared as final, you can modify the internal state of the object, but the reference cannot be reassigned.
Example of Final Reference Variable:
class Example {
public static void main(String[] args) {
final StringBuilder message = new StringBuilder("Hello");
System.out.println(message);
// Modifying the internal state of the final object
message.append(", World!");
System.out.println(message);
// Reassigning the reference would cause a compile-time error
// message = new StringBuilder("Hi");
}
}
Output:
Hello
Hello, World!
Final Local Variable:
A final variable inside a method is called a local final variable. It can be initialized once, and any attempt to reassign it will result in an error.
class Example {
public static void main(String[] args) {
final int LIMIT;
LIMIT = 100; // Variable initialized
System.out.println("Limit: " + LIMIT);
// LIMIT = 200; // This line would cause a compile-time error
}
}
Output:
Limit: 100
Final Classes:
A class declared as final cannot be extended by any subclass. This is useful when you want to ensure the class’s functionality remains intact and is not modified by other developers.
Example of Final Class:
final class Car {
void start() {
System.out.println("Car is starting");
}
}
// The following class would cause a compile-time error
// class SportsCar extends Car { }
public class Main {
public static void main(String[] args) {
Car myCar = new Car();
myCar.start();
}
}
Output:
Car is starting
Final Methods:
When a method is declared as final, it cannot be overridden by any subclass.
class Parent {
final void show() {
System.out.println("This is a final method.");
}
}
class Child extends Parent {
// The following method would cause a compile-time error
// void show() {
// System.out.println("Trying to override.");
// }
}
static Keyword in Java
The static keyword in Java is primarily utilized for memory management. It allows variables or methods to be shared across all instances of a class. Users can apply the static keyword to variables, methods, blocks, and nested classes. Unlike instance members, static members belong to the class itself rather than any particular instance, making them ideal for defining constants or methods that should remain consistent across all objects of the class.
Key Uses of static in Java:
1. Blocks 2. Variables 3. Methods 4. Classes
Characteristics of the static Keyword:
The static keyword plays a crucial role in memory management by enabling class-level variables and methods. For a comprehensive understanding of how to effectively use static, the Java Programming Course offers detailed explanations and practical examples. Here are some key characteristics of the static keyword in Java:
Shared Memory Allocation: Static variables and methods are allocated memory space only once during the program’s execution. This shared memory space is accessible by all instances of the class, making static members ideal for maintaining global state or shared functionality.
Accessible Without Object Instantiation: Static members can be accessed without creating an instance of the class. This makes them useful for utility functions and constants that need to be accessible throughout the program.
Associated with the Class, Not Objects: Static members are tied to the class itself, not to individual objects. Therefore, any changes to a static member are reflected across all instances of the class. Static members can be accessed using the class name rather than an object reference.
Cannot Access Non-Static Members: Static methods and variables cannot directly access non-static members of a class because they are not associated with any particular instance of the class.
Can Be Overloaded, but Not Overridden: Static methods can be overloaded (multiple methods with the same name but different parameters), but they cannot be overridden since they are linked to the class rather than any instance.
Early Access: Static members can be accessed before any objects of the class are created and without referencing any object. For example, in the Java program below, the static method displayMessage() is called without creating an object of the Utility class.
Example Program Accessing Static Method Without Object Creation
// Java program to demonstrate accessing a static method without creating an object
class Utility {
// Static method
static void displayMessage() {
System.out.println("Welcome to the Utility class!");
}
public static void main(String[] args) {
// Calling the static method without creating an instance of Utility
Utility.displayMessage();
}
}
Output:
Welcome to the Utility class!
Static Blocks
Static blocks are used for initializing static variables or executing code that needs to run once when the class is loaded. They are executed in the order they appear in the class.
Example of Static Block Usage
// Java program to demonstrate the use of static blocks
class Configuration {
// Static variables
static String appName;
static int version;
// Static block
static {
System.out.println("Initializing Configuration...");
appName = "MyApp";
version = 1;
}
public static void main(String[] args) {
System.out.println("Application Name: " + appName);
System.out.println("Version: " + version);
}
}
Static variables, also known as class variables, are shared among all instances of a class. They are typically used to store common properties or constants.
Important Points about Static Variables:
Class-Level Scope: Static variables are declared at the class level and are shared by all instances.
Initialization Order: Static blocks and static variables are executed in the order they appear in the program.
Example Demonstrating Static Variable Initialization Order
// Java program to demonstrate the initialization order of static blocks and variables
class InitializationDemo {
// Static variable initialized by a static method
static int initialValue = initialize();
// Static block
static {
System.out.println("Inside static block.");
}
// Static method
static int initialize() {
System.out.println("Initializing static variable.");
return 50;
}
public static void main(String[] args) {
System.out.println("Value of initialValue: " + initialValue);
System.out.println("Inside main method.");
}
}
Output:
Initializing static variable.
Inside static block.
Value of initialValue: 50
Inside main method.
Static Methods
Static methods belong to the class rather than any particular instance. The most common example of a static method is the main() method. Static methods have several restrictions:
They can only directly call other static methods.
They can only directly access static data.
They cannot refer to this or super keywords.
Example Demonstrating Restrictions on Static Methods
// Java program to demonstrate restrictions on static methods
class Calculator {
// Static variable
static int total = 0;
// Instance variable
int count = 0;
// Static method
static void add(int value) {
total += value;
System.out.println("Total after addition: " + total);
// The following lines would cause compilation errors
// count += 1; // Error: non-static variable cannot be referenced from a static context
// displayCount(); // Error: non-static method cannot be referenced from a static context
}
// Instance method
void displayCount() {
System.out.println("Count: " + count);
}
public static void main(String[] args) {
Calculator.add(10);
Calculator.add(20);
}
}
Output:
Total after addition: 10
Total after addition: 30
When to Use Static Variables and Methods
Static Variables: Use static variables for properties that are common to all instances of a class. For example, if all students share the same school name, the school name can be a static variable.
Static Methods: Use static methods for operations that do not require data from instances of the class. Utility or helper methods that perform tasks independently of object state are ideal candidates for static methods.
Example Illustrating Static Variables and Methods
// Java program to demonstrate the use of static variables and methods
class School {
String studentName;
int studentId;
// Static variable for school name
static String schoolName;
// Static counter to assign unique IDs
static int idCounter = 1000;
public School(String name) {
this.studentName = name;
this.studentId = generateId();
}
// Static method to generate unique IDs
static int generateId() {
return idCounter++;
}
// Static method to set the school name
static void setSchoolName(String name) {
schoolName = name;
}
// Instance method to display student information
void displayInfo() {
System.out.println("Student Name: " + studentName);
System.out.println("Student ID: " + studentId);
System.out.println("School Name: " + schoolName);
System.out.println("--------------------------");
}
public static void main(String[] args) {
// Setting the static school name without creating an instance
School.setSchoolName("Greenwood High");
// Creating student instances
School student1 = new School("Emma");
School student2 = new School("Liam");
School student3 = new School("Olivia");
// Displaying student information
student1.displayInfo();
student2.displayInfo();
student3.displayInfo();
}
}
Output:
Student Name: Emma
Student ID: 1000
School Name: Greenwood High
--------------------------
Student Name: Liam
Student ID: 1001
School Name: Greenwood High
--------------------------
Student Name: Olivia
Student ID: 1002
School Name: Greenwood High
--------------------------
Static Classes (Nested Static Classes)
A class can be declared as static only if it is a nested class. Top-level classes cannot be declared as static. Static nested classes do not require a reference to an instance of the outer class and cannot access non-static members of the outer class.
Example of a Static Nested Class
// Java program to demonstrate the use of static nested classes
class OuterClass {
private static String outerMessage = "Hello from OuterClass!";
// Static nested class
static class NestedStaticClass {
void display() {
System.out.println(outerMessage);
}
}
public static void main(String[] args) {
// Creating an instance of the static nested class without an instance of OuterClass
OuterClass.NestedStaticClass nestedObj = new OuterClass.NestedStaticClass();
nestedObj.display();
}
}
Output:
Hello from OuterClass!
enum in Java
What is an Enum in Java?
In Java, Enum is a special data type used to define collections of constants. It allows you to represent a fixed set of predefined constants, such as the days of the week, the four seasons, etc. An enum is more than just a list of constants—it can contain methods, constructors, and variables, just like any other Java class.
Enums are particularly useful when you know all possible values at compile time and want to prevent invalid values from being used.
Key Properties of Enums
Enum Constants: Each enum constant is an object of the enum type.
Implicit Modifiers: Enum constants are public, static, and final.
Switch Compatibility: You can use enums with switch statements.
Constructor: An enum can contain a constructor that is invoked once for each constant.
Method Support: Enums can have methods like regular classes, including abstract methods that must be implemented by each enum constant.
Enum Declaration in Java
Enums can be declared both inside or outside a class, but not inside a method.
1. Declaration Outside the Class
// Enum declared outside the class
enum Direction {
NORTH,
SOUTH,
EAST,
WEST
}
public class TestEnum {
public static void main(String[] args) {
Direction direction = Direction.NORTH;
System.out.println("The direction is: " + direction);
}
}
Output:
The direction is: NORTH
2. Declaration Inside the Class
// Enum declared inside a class
public class Weather {
enum Season {
SPRING,
SUMMER,
FALL,
WINTER
}
public static void main(String[] args) {
Season current = Season.WINTER;
System.out.println("The current season is: " + current);
}
}
Output:
The current season is: WINTER
Enum in Switch Statements
Enums can be used in switch statements to handle different cases based on enum values.
// Enum in a switch statement
public class DaysOfWeek {
enum Day {
MONDAY,
TUESDAY,
WEDNESDAY,
THURSDAY,
FRIDAY,
SATURDAY,
SUNDAY
}
public static void main(String[] args) {
Day today = Day.FRIDAY;
switch (today) {
case MONDAY:
System.out.println("It's the start of the work week.");
break;
case FRIDAY:
System.out.println("Almost the weekend!");
break;
case SATURDAY:
case SUNDAY:
System.out.println("It's the weekend!");
break;
default:
System.out.println("It's a regular workday.");
}
}
}
Output:
Almost the weekend!
Looping Through Enum Constants
You can iterate over the constants in an enum using the values() method, which returns an array of all enum constants.
// Looping through enum constants
public class ColorExample {
enum Color {
RED, GREEN, BLUE, YELLOW
}
public static void main(String[] args) {
for (Color color : Color.values()) {
System.out.println("Color: " + color);
}
}
}
Output:
Color: RED
Color: GREEN
Color: BLUE
Color: YELLOW
Enum with Constructor and Method
Enums can contain constructors and methods, making them more powerful than just simple constants.
// Enum with constructor and method
public class CarTypeExample {
enum CarType {
SEDAN(4), SUV(6), TRUCK(8);
private int seats;
// Enum constructor
CarType(int seats) {
this.seats = seats;
}
public int getSeats() {
return seats;
}
}
public static void main(String[] args) {
CarType myCar = CarType.SUV;
System.out.println("My car type is: " + myCar + " with " + myCar.getSeats() + " seats.");
}
}
Output:
My car type is: SUV with 6 seats.
Enum with Abstract Methods
Enums can also contain abstract methods that each constant must implement.
// Enum with abstract method
public class PlanetExample {
enum Planet {
MERCURY {
public String getOrbitalPeriod() {
return "88 days";
}
},
EARTH {
public String getOrbitalPeriod() {
return "365 days";
}
},
MARS {
public String getOrbitalPeriod() {
return "687 days";
}
};
public abstract String getOrbitalPeriod();
}
public static void main(String[] args) {
Planet planet = Planet.EARTH;
System.out.println("The orbital period of " + planet + " is " + planet.getOrbitalPeriod());
}
}
Output:
// Using EnumSet to iterate over a specific range of enum values
import java.util.EnumSet;
public class DaysRangeExample {
enum Day {
MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY
}
public static void main(String[] args) {
EnumSet<Day> workdays = EnumSet.range(Day.MONDAY, Day.FRIDAY);
for (Day day : workdays) {
System.out.println("Workday: " + day);
}
}
}
Output:
The orbital period of EARTH is 365 days
transient keyword in Java
The transient keyword in Java is used to indicate that a particular field should not be serialized when the object is written to a stream. During serialization, if a field is marked as transient, its value is not saved, and when the object is deserialized, that field is assigned its default value according to its data type.
The transient keyword is especially useful for sensitive data that you don’t want to store, such as passwords or fields that can be recalculated at runtime, such as a person’s age or a timestamp.
Usage of transient Keyword
When an object is serialized, all its fields are saved unless they are marked with the transient modifier. If a field is marked as transient, it is skipped during the serialization process, and its value will be reset to the default when the object is deserialized.
This is often used to protect sensitive data, such as passwords, or for fields that can be derived from other fields.
Example of transient Keyword:
// A simple class to demonstrate the use of the transient keyword
import java.io.*;
class Example implements Serializable {
// Regular fields
private String username;
private String email;
// Password field marked as transient for security
private transient String password;
// Age field marked as transient because it can be recalculated
transient int age;
// Constructor
public Example(String username, String email, String password, int age) {
this.username = username;
this.email = email;
this.password = password;
this.age = age;
}
// Display user information
public void displayInfo() {
System.out.println("Username: " + username);
System.out.println("Email: " + email);
System.out.println("Password: " + password);
System.out.println("Age: " + age);
}
}
public class TestTransient {
public static void main(String[] args) throws Exception {
// Create an instance of the class
Example user = new Example("JohnDoe", "john@example.com", "secretPassword", 30);
// Serialization process
FileOutputStream fileOut = new FileOutputStream("user_data.txt");
ObjectOutputStream objectOut = new ObjectOutputStream(fileOut);
objectOut.writeObject(user);
objectOut.close();
fileOut.close();
// Deserialization process
FileInputStream fileIn = new FileInputStream("user_data.txt");
ObjectInputStream objectIn = new ObjectInputStream(fileIn);
Example deserializedUser = (Example) objectIn.readObject();
objectIn.close();
fileIn.close();
// Display the deserialized object's info
System.out.println("After Deserialization:");
deserializedUser.displayInfo();
}
}
The password and age fields are marked as transient, so they are not serialized.
When deserialized, the password field becomes null, and the age field is set to 0 (the default value for integers).
transient and static Fields
The transient keyword has no effect on static fields because static fields are not serialized as part of the object state. Similarly, marking a static field as transient has no impact, as static variables belong to the class and not the instance.
Example with static and final Fields:
// A simple class to demonstrate transient with static and final variables
import java.io.*;
class TestStaticFinal implements Serializable {
// Regular fields
int x = 100, y = 200;
// Transient field
transient int z = 300;
// Transient has no effect on static variables
transient static int a = 400;
// Transient has no effect on final variables
transient final int b = 500;
public static void main(String[] args) throws Exception {
TestStaticFinal object = new TestStaticFinal();
// Serialize the object
FileOutputStream fos = new FileOutputStream("static_final.txt");
ObjectOutputStream oos = new ObjectOutputStream(fos);
oos.writeObject(object);
oos.close();
fos.close();
// Deserialize the object
FileInputStream fis = new FileInputStream("static_final.txt");
ObjectInputStream ois = new ObjectInputStream(fis);
TestStaticFinal deserializedObject = (TestStaticFinal) ois.readObject();
ois.close();
fis.close();
// Display the values after deserialization
System.out.println("x = " + deserializedObject.x);
System.out.println("y = " + deserializedObject.y);
System.out.println("z = " + deserializedObject.z); // Will be 0 (default value)
System.out.println("a = " + TestStaticFinal.a); // Will be 400
System.out.println("b = " + deserializedObject.b); // Will be 500
}
}
Output:
x = 100
y = 200
z = 0
a = 400
b = 500
volatile Keyword
The volatile keyword in Java is used to ensure that updates to a variable are immediately visible to all threads. This is essential in a multithreaded environment to prevent inconsistencies due to threads caching variable values locally, which can lead to stale data being used. Let’s consider an example to better understand its behavior.
Problem Without volatile:
When multiple threads are operating on the same variable, they may maintain a local copy of the variable in their own cache. Changes made by one thread may not be immediately visible to the other threads, leading to unpredictable results.
For instance:
class SharedResource {
// Without volatile, changes made by one thread
// may not reflect immediately in others.
static int sharedValue = 10;
}
With volatile, the sharedValue is always read from the main memory and never from the thread’s cache, ensuring that all threads have the most up-to-date value.
Difference Between volatile and synchronized:
Mutual Exclusion: The synchronized keyword ensures that only one thread can access a critical section of code at any time.
Visibility: Both volatile and synchronized ensure that changes made by one thread are visible to other threads.
However, if you only need to ensure visibility and don’t require atomic operations (such as incrementing), volatile can be used to avoid the overhead of synchronization.
Example Using volatile:
// Java program to demonstrate the use of volatile keyword
public class VolatileDemo {
private static volatile int counter = 0;
public static void main(String[] args) {
new UpdateThread().start();
new MonitorThread().start();
}
// Thread that monitors changes to the volatile variable
static class MonitorThread extends Thread {
@Override
public void run() {
int localCounter = counter;
while (localCounter < 5) {
if (localCounter != counter) {
System.out.println("Detected change in counter: " + counter);
localCounter = counter;
}
}
}
}
// Thread that updates the volatile variable
static class UpdateThread extends Thread {
@Override
public void run() {
int localCounter = counter;
while (counter < 5) {
System.out.println("Incrementing counter to " + (localCounter + 1));
counter = ++localCounter;
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
Output (With volatile):
Incrementing counter to 1
Detected change in counter: 1
Incrementing counter to 2
Detected change in counter: 2
Incrementing counter to 3
Detected change in counter: 3
Incrementing counter to 4
Detected change in counter: 4
Incrementing counter to 5
Detected change in counter: 5
Output (Without volatile):
Incrementing counter to 1
Incrementing counter to 2
Incrementing counter to 3
Incrementing counter to 4
Incrementing counter to 5
final, finally and finalize in Java
Here’s the modified version of the content with different examples, and the output remains as requested:
final Keyword in Java
In Java, final is a reserved keyword, meaning we cannot use it as an identifier (variable name, method name, etc.). It serves distinct purposes depending on where it is applied — whether to variables, methods, or classes.
1. final with Variables
When a variable is declared as final, its value cannot be modified once initialized. Any attempt to change its value will result in a compile-time error.
class Example1 {
public static void main(String[] args) {
// Non-final variable
int x = 10;
// Final variable
final int y = 20;
// Modifying non-final variable: Allowed
x++;
// Modifying final variable: Gives a compile-time error
y++; // Error: Cannot assign a value to a final variable 'y'
}
}
Here, attempting to modify the value of the final variable y will cause a compile-time error.
2. final with Classes
If a class is declared final, it cannot be subclassed. In other words, no class can extend a final class.
final class SuperClass {
public void display() {
System.out.println("This is a final class.");
}
}
// The following class will cause a compile-time error as `SuperClass` is final and cannot be extended
class SubClass extends SuperClass {
// Compile-time error: Cannot inherit from final 'SuperClass'
}
3. final with Methods
When a method is declared as final, it cannot be overridden by subclasses.
class ParentClass {
final void show() {
System.out.println("Final method in the parent class.");
}
}
class ChildClass extends ParentClass {
// The following method will cause a compile-time error
void show() {
// Compile-time error: Cannot override the final method from 'ParentClass'
System.out.println("Trying to override the final method.");
}
}
In this case, the subclass ChildClass cannot override the final method show() from ParentClass.
Note: final with Classes and Methods
If a class is declared final, all its methods are implicitly final by default. However, its variables are not.
final class FinalClass {
// Method is final by default
void display() {
System.out.println("Final class method.");
}
// Static variables can still be modified
static int value = 50;
public static void main(String[] args) {
// Modifying the static variable
value = 60;
System.out.println("Value: " + value); // Output: 60
}
}
finally Keyword
The finally keyword is associated with try and catch blocks. It ensures that a block of code will always be executed, regardless of whether an exception occurs or not. The finally block is generally used for cleanup operations like closing resources (e.g., file handles, database connections).
Example: finally with Exception Handling
class ExampleFinally {
public static void main(String[] args) {
try {
System.out.println("Inside try block");
throw new RuntimeException("Exception in try");
} finally {
System.out.println("Finally block always executes");
}
}
}
In this example, even though an exception is thrown, the finally block still executes.
Cases Involving finally Block:
Case 1: No Exception Occurs
class NoException {
public static void main(String[] args) {
try {
System.out.println("Inside try block");
int result = 10 / 2;
} finally {
System.out.println("Finally block executed");
}
}
}
Output:
Inside try block
Finally block executed
Here, no exception occurs, but the finally block still executes.
Case 2: Exception Occurs and is Caught
class CatchException {
public static void main(String[] args) {
try {
System.out.println("Inside try block");
int result = 10 / 0;
} catch (ArithmeticException e) {
System.out.println("Caught ArithmeticException");
} finally {
System.out.println("Finally block executed");
}
}
}
class NoCatchBlock {
public static void main(String[] args) {
try {
System.out.println("Inside try block");
int result = 10 / 0;
} finally {
System.out.println("Finally block executed");
}
}
}
Output:
Inside try block
Finally block executed
Exception in thread "main" java.lang.ArithmeticException: / by zero
Case 4: System.exit(0) in try Block
class ExitExample {
public static void main(String[] args) {
try {
System.out.println("Inside try block");
System.exit(0);
} finally {
System.out.println("Finally block not executed");
}
}
}
Output:
Inside try block
Finalize Method
The finalize method is called by the garbage collector just before an object is destroyed. It is meant for resource cleanup before an object is deleted.
The first question that typically comes to mind is, “When we already have primitive data types, why do we need wrapper classes in Java?” The reason lies in the additional functionality provided by wrapper classes that primitive data types lack. These features primarily include useful methods like valueOf(), parseInt(), toString(), and more.
A wrapper class “wraps” around a primitive data type, giving it an object representation. These classes are final and immutable. Two key concepts related to wrapper classes are autoboxing and unboxing.
1. Autoboxing is the automatic conversion of a primitive value into an instance of its corresponding wrapper class (e.g., converting an int to an Integer). The Java compiler applies autoboxing when:
A primitive value is passed as an argument to a method that expects an object of the corresponding wrapper class.
A primitive value is assigned to a variable of the corresponding wrapper class.
2. Unboxing is the automatic conversion of an instance of a wrapper class to its corresponding primitive value (e.g., converting an Integer to an int). The Java compiler applies unboxing when:
An object of a wrapper class is passed as an argument to a method that expects a primitive type.
An object of a wrapper class is assigned to a variable of the corresponding primitive type.
Features of Wrapper Classes
Some of the notable benefits of wrapper classes include:
1. They allow conversion between primitive data types and objects. This is useful in cases where arguments passed to methods need to be modified, as primitives are passed by value. 2. Java classes, such as those in the java.util package, deal with objects rather than primitive types, making wrapper classes essential. 3. Data structures in the Collection Framework (e.g., ArrayList, Vector) store only objects, not primitives. 4. Wrapper classes enable object creation for synchronization in multithreading. They provide numerous utility methods. For instance, a float value can be converted to its integer equivalent using the provided method.
Example 1: Autoboxing in Java
The following code demonstrates autoboxing, where primitive types are automatically converted to their corresponding wrapper classes:
import java.util.*;
class WrapperDemo {
public static void main(String[] args) {
int x = 10;
double y = 7.25;
long z = 12345;
// Autoboxing: converting primitives to objects
Integer intObj = x;
Double doubleObj = y;
Long longObj = z;
// Output the wrapped values
System.out.println(intObj);
System.out.println(doubleObj);
System.out.println(longObj);
}
}
Output:
10
7.25
12345
Example 2: Wrapper Class Utility Methods
Here’s another example that illustrates the utility methods provided by wrapper classes:
import java.io.*;
class WrapperUtility {
public static void main(String[] args) {
// Converting a float to int using a wrapper class method
Float floatValue = Float.valueOf(28.97f);
int convertedValue = floatValue.intValue();
// Output the converted value
System.out.println(convertedValue);
// Converting a binary string to an integer
Integer binaryValue = Integer.valueOf("1101", 2);
// Output the converted integer from binary
System.out.println(binaryValue);
}
}
Output:
28
13
Character Class in Java
Java provides the Character class within the java.lang package as a wrapper for the primitive char data type. This class encapsulates a single char value and offers numerous useful methods to manipulate characters. The Character class also supports Autoboxing, where a primitive char is automatically converted to a Character object when necessary.
Creating a Character Object:
Character myChar = new Character('b');
This statement creates a Character object containing the character 'b'. The Character class constructor takes a single argument of type char.
Autoboxing allows Java to automatically convert a char to a Character object if a method expects an object. Conversely, unboxing converts the object back to a primitive char.
/** This is a documentation comment */
Methods of the Character Class
Here are some important methods provided by the Character class:
1. boolean isLetter(char ch)
This method checks whether a given character is a letter (A-Z or a-z).
Syntax:
boolean isLetter(char ch)
Example:
public class Demo {
public static void main(String[] args) {
System.out.println(Character.isWhitespace(' '));
System.out.println(Character.isWhitespace('\t'));
System.out.println(Character.isWhitespace('X'));
}
}
Output:
true
true
false
2. boolean isUpperCase(char ch)
This method checks whether the given character is uppercase.
Syntax:
boolean isUpperCase(char ch)
Example:
public class Demo {
public static void main(String[] args) {
System.out.println(Character.isUpperCase('K'));
System.out.println(Character.isUpperCase('k'));
}
}
Output:
true
false
3. char toUpperCase(char ch)
This method converts a lowercase character to its uppercase equivalent.
Syntax:
char toUpperCase(char ch)
Example:
public class Demo {
public static void main(String[] args) {
System.out.println(Character.toUpperCase('m'));
System.out.println(Character.toUpperCase('M'));
}
}
Output:
M
M
4. char toLowerCase(char ch)
This method converts an uppercase character to its lowercase equivalent.
Syntax:
char toLowerCase(char ch)
Examples:
public class Demo {
public static void main(String[] args) {
System.out.println(Character.toLowerCase('N'));
System.out.println(Character.toLowerCase('n'));
}
}
Output:
n
n
Escape Sequences
Java allows the use of escape sequences to represent special characters in strings. Here are some common escape sequences:
Escape Sequence
Description
\t
Inserts a tab
\b
Inserts a backspace
\n
Inserts a newline
\r
Inserts a carriage return
\f
Inserts a formfeed
\'
Inserts a single quote
\"
Inserts a double quote
\\
Inserts a backslash
Example:
public class EscapeDemo {
public static void main(String[] args) {
System.out.println("He said, \"Java is fun!\"");
System.out.println("Line1\nLine2");
}
}
Output:
He said, "Java is fun!"
Line1
Line2
Java.Lang.Byte class in Java
The Byte class in Java is a wrapper class for the primitive byte type, which provides methods for dealing with byte values, such as converting them to string representations and vice versa. A Byte object can hold a single byte value.
Constructors of Byte Class
There are two primary constructors used to initialize a Byte object:
1. Byte(byte b) Initializes a Byte object with the given byte value.
public Byte(byte b)
Parameters:b: The byte value to initialize the Byte object with.
2. Byte(String s) Initializes a Byte object using the byte value from the provided string representation. The string is parsed as a decimal value by default.
Syntax:
public Byte(String s) throws NumberFormatException
Parameter:value: string representing a byte value.
Fields in Byte Class
static int BYTES : The number of bytes used to represent a byte value in two’s complement binary form.
static byte MAX_VALUE : The maximum value a byte can have, which is 2⁷ – 1 (i.e., 127).
static byte MIN_VALUE : The minimum value a byte can have, which is -2⁷ (i.e., -128).
static int SIZE : The number of bits used to represent a byte value (8 bits).
static Class<Byte> TYPE : The Class instance representing the primitive type byte.
Methods in Byte Class
1. toString() Returns the string representation of the byte value.
Syntax:
public String toString(byte value)
2. valueOf() Returns a Byte object initialized with the provided byte value.
Syntax:
public static Byte valueOf(byte value)
3. valueOf(String value, int radix) Parses the string into a byte value based on the given radix and returns a Byte object.
Syntax:
public static Byte valueOf(String value, int radix) throws NumberFormatException
4. parseByte() Converts a string into a primitive byte value, with or without a specified radix.
Syntax:
public static byte parseByte(String value, int radix) throws NumberFormatException
5. decode() Decodes a string into a Byte object. The string can be in decimal, hexadecimal, or octal format.
Syntax:
public static Byte decode(String value) throws NumberFormatException
6. byteValue(), shortValue(), intValue(), longValue(), floatValue(), doubleValue() These methods return the respective primitive values corresponding to the Byte object.
7. hashCode() Returns the hash code for the Byte object.
8. equals() Compares two Byte objects for equality.
9. compareTo() Compares two Byte objects numerically.
The Short class is a wrapper for the primitive short data type. It provides various methods for handling short values, such as converting them to and from string representations. An instance of the Short class can store a single short value.
Constructors in the Short Class
The Short class has two main constructors:
1. Short(short value) This constructor creates a Short object initialized with the provided short value. Syntax:
public Short(short value)
Parameters:value: The short value used for initialization.
2. Short(String s) This constructor creates a Short object from a string representing a short value, with the default base (radix) of 10. Syntax:
public Short(String s) throws NumberFormatException
Parameters:s: A string representing the short value.
Throws:NumberFormatException if the string does not represent a valid short value.
Methods in the Short Class
1. toString() This method converts a short value to its string representation. Syntax:
public static String toString(short value)
2. valueOf() This method returns a Short object initialized with the given short value. Syntax:
public static Short valueOf(short value)
3. parseShort() This method parses the string argument to return a primitive short value. Syntax:
public static short parseShort(String s, int radix) throws NumberFormatException
4. decode() This method decodes a string to return a Short object. It supports decimal, hexadecimal, and octal representations. Syntax:
public static Short decode(String s) throws NumberFormatException
5. byteValue(), shortValue(), intValue(), longValue(), floatValue(), doubleValue() These methods return the corresponding primitive value from the Short object.
6. hashCode() Returns the hash code for this Short object. Syntax:
public int hashCode()
7. equals() Checks if two Short objects are equal. Syntax:
public boolean equals(Object obj)
8. compareTo() Compares two Short objects. Syntax:
public int compareTo(Short anotherShort)
9. compare() Compares two primitive short values. Syntax:
public static int compare(short x, short y)
10. reverseBytes() This method reverses the order of the bytes in the given short value. Syntax:
public static short reverseBytes(short value)
Example:
public class ShortExample {
public static void main(String[] args) {
// Short value and string representation
short sValue = 90;
String sString = "50";
// Creating two Short objects
Short first = new Short(sValue);
Short second = new Short(sString);
// toString()
System.out.println("toString(sValue) = " + Short.toString(sValue));
// valueOf()
Short obj1 = Short.valueOf(sValue);
System.out.println("valueOf(sValue) = " + obj1);
obj1 = Short.valueOf(sString);
System.out.println("valueOf(sString) = " + obj1);
obj1 = Short.valueOf(sString, 7);
System.out.println("valueOf(sString, 7) = " + obj1);
// parseShort()
short parsedValue = Short.parseShort(sString);
System.out.println("parseShort(sString) = " + parsedValue);
parsedValue = Short.parseShort(sString, 7);
System.out.println("parseShort(sString, 7) = " + parsedValue);
// decode()
String decimalString = "40";
String octalString = "006";
String hexString = "0x10";
Short decoded = Short.decode(decimalString);
System.out.println("decode(40) = " + decoded);
decoded = Short.decode(octalString);
System.out.println("decode(006) = " + decoded);
decoded = Short.decode(hexString);
System.out.println("decode(0x10) = " + decoded);
// Primitive value methods
System.out.println("byteValue(first) = " + first.byteValue());
System.out.println("shortValue(first) = " + first.shortValue());
System.out.println("intValue(first) = " + first.intValue());
System.out.println("longValue(first) = " + first.longValue());
System.out.println("floatValue(first) = " + first.floatValue());
System.out.println("doubleValue(first) = " + first.doubleValue());
// Hash code
int hash = first.hashCode();
System.out.println("hashCode(first) = " + hash);
// Equality check
boolean isEqual = first.equals(second);
System.out.println("first.equals(second) = " + isEqual);
// Comparison
int comparison = Short.compare(first, second);
System.out.println("compare(first, second) = " + comparison);
int compareTo = first.compareTo(second);
System.out.println("first.compareTo(second) = " + compareTo);
// Reverse bytes
short toReverse = 60;
System.out.println("Short.reverseBytes(toReverse) = " + Short.reverseBytes(toReverse));
}
}
The Long class is a wrapper for the primitive type long that provides methods to handle long values more effectively. It can convert a long to a String representation and vice versa. A Long object holds a single long value, and there are two primary constructors to initialize this object:
Long(long b): Initializes a Long object with the specified long value.
public Long(long b)
Parameter:s: String representation of the long value.
Key Methods
1. toString(): Converts a long value to a string representation.
public String toString(long b)
2. toHexString(): Converts a long value to its hexadecimal string representation.
public String toHexString(long b)
3. toOctalString(): Converts a long value to its octal string representation.
public String toOctalString(long b)
4. toBinaryString(): Converts a long value to its binary string representation.
public String toBinaryString(long b)
5. valueOf(): Converts a long or string to a Long object.
public static Long valueOf(long b)
public static Long valueOf(String val, long radix) throws NumberFormatException
public static Long valueOf(String s) throws NumberFormatException
6. parseLong(): Converts a string to a primitive long value.
public static long parseLong(String val, int radix) throws NumberFormatException
public static long parseLong(String val) throws NumberFormatException
7. getLong(): Retrieves the Long object associated with a system property or returns null if it doesn’t exist. An overloaded method allows for a default value.
public static Long getLong(String prop)
public static Long getLong(String prop, long val)
8. decode(): Decodes a string into a Long object (handles decimal, hex, and octal formats).
public static Long decode(String s) throws NumberFormatException
9. rotateLeft(): Rotates the bits of the given long value to the left by the specified distance.
public static long rotateLeft(long val, int dist)
Example:
public class LongExample {
public static void main(String[] args) {
long num = 30;
String strNum = "25";
// Creating two Long objects
Long obj1 = new Long(num);
Long obj2 = new Long(strNum);
// String conversion
System.out.println("String representation: " + Long.toString(num));
// Hexadecimal, Octal, and Binary conversion
System.out.println("Hexadecimal: " + Long.toHexString(num));
System.out.println("Octal: " + Long.toOctalString(num));
System.out.println("Binary: " + Long.toBinaryString(num));
// Using valueOf()
Long val1 = Long.valueOf(num);
System.out.println("valueOf(num): " + val1);
Long val2 = Long.valueOf(strNum);
System.out.println("valueOf(strNum): " + val2);
Long val3 = Long.valueOf(strNum, 8);
System.out.println("valueOf(strNum, 8): " + val3);
// Using parseLong()
long parsedValue1 = Long.parseLong(strNum);
System.out.println("parseLong(strNum): " + parsedValue1);
long parsedValue2 = Long.parseLong(strNum, 8);
System.out.println("parseLong(strNum, 8): " + parsedValue2);
// getLong() method examples
Long propValue = Long.getLong("java.specification.version");
System.out.println("System property value: " + propValue);
System.out.println("Default value: " + Long.getLong("nonexistent", 5));
// Decode
System.out.println("Decoded (hex): " + Long.decode("0x1f"));
System.out.println("Decoded (octal): " + Long.decode("007"));
// Bit rotation
System.out.println("rotateLeft(30, 2): " + Long.rotateLeft(num, 2));
System.out.println("rotateRight(30, 2): " + Long.rotateRight(num, 2));
}
}
The Float class in Java is a wrapper class for the primitive type float, and it provides several methods to work with float values, such as converting them to string representations and vice-versa. A Float object can hold a single float value. There are primarily two constructors to initialize a Float object:
Float(float f): This creates a Float object initialized with the specified float value.
Syntax:
public Float(float f)
Parameters:s: The string representation of a float value.
Throws:NumberFormatException: If the provided string cannot be parsed as a float.
Methods of the Float Class
1. toString(): Returns the string representation of the float value.
Synta
public String toString(float f)
Parameters: f: The float value for which the string representation is required.
2. valueOf():Returns a Float object initialized with the given float value.
Syntax:
public static Float valueOf(String s) throws NumberFormatException
3. parseFloat():Parses the string as a float and returns the primitive float value.
Syntax:
public static float parseFloat(String s) throws NumberFormatException
4. byteValue():Returns the byte value of the Float object.
Syntax:
public byte byteValue()
5. shortValue(): Returns the short value of the Float object.
Syntax:
public short shortValue()
6. intValue(): Returns the int value of the Float object.
Syntax:
public int intValue()
7. doubleValue():Returns the double value of the Float object.
Syntax:
Error: variable 'a' is already defined
8. longValue(): Returns the long value of the Float object.
Syntax:
public double doubleValue()
9. floatValue():Returns the float value of the Float object.
Syntax:
public float floatValue()
10. floatValue(): Returns the float value of the Float object.
Syntax:
public float floatValue()
11. isNaN(): Checks whether the float value is NaN (Not-a-Number).
Syntax:
public boolean isNaN()
12. isInfinite(): Checks whether the float value is infinite.
Syntax:
public boolean isInfinite()
13. toHexString():Returns the hexadecimal representation of the given float value.
Java handles memory management automatically, with the help of the Java Virtual Machine (JVM) and the Garbage Collector. But it’s essential for a programmer to understand how memory management works in Java, as it aids in writing efficient code and debugging potential memory issues. Knowing how to manage memory can also help improve performance and prevent memory leaks.
Why Learn Java Memory Management?
Even though Java automates memory management through the garbage collector, the programmer’s role isn’t eliminated. While developers don’t need to explicitly destroy objects like in languages such as C/C++, they must understand how Java memory management works. Mismanaging memory or not understanding what is managed by the JVM and what isn’t can lead to issues, such as objects not being eligible for garbage collection. In particular, understanding memory management enables writing high-performance programs that avoid memory crashes and helps debug memory issues effectively.
Introduction to Java Memory Management
Memory is a vital and limited resource in any programming language. Proper memory management ensures there are no memory leaks, improving the efficiency of programs. Unlike languages like C, where the programmer directly manages memory, Java delegates memory management to the JVM, which handles allocation and deallocation of memory. The Garbage Collector plays a significant role in managing memory automatically in Java.
Key Concepts in Java Memory Management
1. JVM Memory Structure 2. Garbage Collection Process
Java Memory Structure
The JVM manages different runtime data areas, some of which are created when the JVM starts and some by threads used in a program. These memory areas have distinct purposes and are destroyed when the JVM or the respective threads exit.
Key Components of JVM Memory:
1. Heap : The heap is a shared runtime data area used for storing objects and array instances. It is created when the JVM starts. The size of the heap can be fixed or dynamic, depending on system configuration, and can be controlled by the programmer. For instance, when using the new keyword, the object is allocated space in the heap, while its reference is stored in the stack.
Example:
List<String> list = new ArrayList<>();
In this case, the ArrayList object is created in the heap, and the reference list is stored in the stack.
Output:
Memory allocated for ArrayList in the heap.
2. Method Area : The method area is a logical part of the heap and holds class structures, method data, and field data. It stores runtime constant pool information as well. Although it’s part of the heap, garbage collection in the method area is not guaranteed.
3. JVM Stacks : Each thread in a Java program has its own stack, which stores data like local variables, method calls, and return values. The stack is created when a thread is instantiated and destroyed when the thread finishes.
Example:
public static void main(String[] args) {
int x = 5;
int y = calculate(x);
}
static int calculate(int val) {
return val * 2;
}
In this example, the local variables x and y are stored in the stack. The method call to calculate is also stored on the stack.
1. Native Method Stacks: These stacks support native methods (non-Java methods). Like JVM stacks, they are created for each thread and can be either dynamic or fixed. 2. Program Counter (PC) Register : Each thread in the JVM has a program counter register that tracks the current method instruction being executed. For native methods, the value is undefined.
How the Garbage Collector Works
Java’s garbage collection is an automatic process that identifies and reclaims memory from objects that are no longer in use. It frees the programmer from manually managing memory deallocation. However, the garbage collection process can be costly, as it pauses other threads during execution. To improve performance, Java employs various garbage collection algorithms, a process referred to as “Garbage Collector Tuning.”
Garbage Collection Algorithms:
1. Generational Garbage Collection: Java uses a generational garbage collection approach, where objects are classified based on their lifespan (age). Objects that survive multiple garbage collection cycles are promoted to an older generation, while newly created objects are placed in a younger generation. This improves efficiency, as older objects are collected less frequently.
Garbage Collection Example:
public class GarbageCollectionDemo {
public static void main(String[] args) {
GarbageCollectionDemo demo = new GarbageCollectionDemo();
demo = null; // Eligible for garbage collection
System.gc(); // Requesting garbage collection
System.out.println("Garbage collection triggered.");
}
@Override
protected void finalize() throws Throwable {
System.out.println("Garbage collected!");
}
}
Output:
Garbage collection triggered.
Garbage collected!
Here, the object demo is made eligible for garbage collection by setting it to null. The System.gc() method requests the JVM to run the garbage collector, although it’s not guaranteed to happen immediately.
Java Object Allocation on Heap
In Java, all objects are dynamically allocated on the heap. This differs from languages like C++, where objects can be allocated on either the stack or the heap. When you use the new keyword in Java, the object is allocated on the heap, whereas in C++, objects can also be stack-allocated, unless they are declared globally or statically.
When you declare a variable of a class type in Java, memory for the object is not allocated immediately. Only a reference is created. To allocate memory to an object, the new keyword must be used. This ensures that objects are always allocated on the heap.
Creating a String Object in Java
There are two ways to create a string in Java:
1. By String Literal 2. By using the new Keyword
1. String Literal : This is the most common way to create a string in Java, using double-quotes.
In this case, every time a string literal is created, the JVM checks whether the string already exists in the string constant pool. If it does, a reference to the pooled instance is returned. If it doesn’t, a new string instance is created in the pool. Therefore, only one object will be created for both str1 and str2 if they have the same value.
The JVM is not obligated to create new memory if the string already exists in the pool.
2. Using the new Keyword : You can also create strings using the new keyword.
Example:
String str1 = new String("Hello");
String str2 = new String("Hello");
Here, both str1 and str2 are different objects. Even though the string content is the same, the JVM creates separate memory locations in the heap for each object when using the new keyword. The JVM will not check if the string already exists in memory; it always creates new memory for each object.
The JVM is forced to allocate new memory for each string object created using new.
Uninitialized Object Example
If you attempt to use a reference to an object without initializing it, Java will throw a compilation error, as the object does not have memory allocated to it.
Example with Error:
class Demo {
void display() {
System.out.println("Demo::display() called");
}
}
public class Main {
public static void main(String[] args) {
Demo d; // No memory allocated yet
d.display(); // Error: d is not initialized
}
}
Output:
Error: variable d might not have been initialized
How many types of memory areas are allocated by JVM?
The Java Virtual Machine (JVM) is an abstract machine, essentially a software program that takes Java bytecode (compiled Java code) and converts it into machine-level code that the underlying system can understand, one instruction at a time.
JVM acts as the runtime engine for Java applications and is responsible for invoking the main() method in Java programs. It is a core part of the Java Runtime Environment (JRE), which provides the necessary libraries and environment for Java code execution.
Functions of the JVM
JVM performs several key functions:
1. Loading of code: It loads the bytecode into memory. 2. Verification of code: It checks the bytecode for security issues or invalid operations. 3. Execution of code: It executes the bytecode. 4. Runtime environment: Provides a runtime environment for Java programs.
ClassLoader
ClassLoader is a crucial subsystem of the JVM that loads .class files into memory. It is responsible for:
1. Loading: Loading the class into memory. 2. Linking: Resolving symbolic references and ensuring class dependencies are loaded. Initialization: Preparing the class for use, initializing variables, etc.
Types of Memory Areas Allocated by JVM
JVM memory is divided into several parts that perform different functions. These are:
1. Class (Method) Area : The Class Method Area is a memory region that stores class-related data, such as:
Class code
Static variables
Runtime constants
Method code (functions within classes)
This area holds data related to class-level information, including constructors and field data.
2. Heap : The Heap is where objects are dynamically created during the execution of a program. This memory area stores objects, including arrays (since arrays are objects in Java). The heap is where memory is allocated at runtime for class instances.
3. Stack : Each thread in a Java program has its own stack, which is created when the thread starts. The stack holds data like:
Method call frames
Local variables
Partial results
A new frame is created every time a method is called, and the frame is destroyed once the method call is completed.
4. Program Counter (PC) Register
Each thread has a Program Counter (PC) register. For non-native methods, it stores the address of the next instruction to execute. In native methods, the PC value is undefined. It also holds return addresses or native pointers in certain cases.
5. Native Method Stack
The Native Method Stack is used by threads that execute native (non-Java) code. These stacks, sometimes referred to as C stacks, store information about native methods written in other programming languages like C/C++. Each thread has its own native method stack, and it can be either fixed or dynamic in size.
JVM Example: Code Execution
Here’s an example demonstrating how the JVM manages memory:
class Demo {
static int x = 10; // Stored in the heap memory
int y = 20; // Stored in the heap memory
void display() {
System.out.println("x: " + x + ", y: " + y);
}
}
public class Main {
public static void main(String[] args) {
Demo obj = new Demo(); // obj created in heap
obj.display(); // Call method using obj
}
}
Output:
x: 10, y: 20
Garbage Collection in Java
Garbage Collection (GC) in Java is the process through which Java programs handle automatic memory management. Java programs are compiled into bytecode that runs on the Java Virtual Machine (JVM). As the program runs, objects are created on the heap memory, which is dedicated to the program’s use. Over time, some of these objects are no longer needed. The garbage collector identifies these unused objects and removes them, freeing up memory space.
What is Garbage Collection?
In languages like C and C++, developers are responsible for managing both the creation and destruction of objects. However, developers often neglect the destruction of objects that are no longer required, which can lead to memory shortages and eventual program crashes, resulting in OutOfMemoryErrors.
In contrast, Java handles memory management automatically. The garbage collector in Java’s JVM frees up heap memory by destroying unreachable objects. This background process is the best example of a daemon thread, which continuously runs to manage memory.
How Does Garbage Collection Work in Java?
Java’s garbage collection is fully automatic. It inspects heap memory, identifying objects that are still in use and those that are not. In-use objects are referenced by the program, while unused objects are no longer referenced by any part of the program and can have their memory reclaimed.
The garbage collector, part of the JVM, handles this process without the programmer needing to explicitly mark objects for deletion.
Types of Garbage Collection in Java
There are typically two types of garbage collection activities:
1. Minor (Incremental) GC: This occurs when objects that are no longer referenced in the young generation of the heap are removed. 2. Major (Full) GC: This occurs when objects that have survived multiple minor collections are promoted to the old generation of the heap and are later removed when they become unreachable.
Key Concepts Related to Garbage Collection
1. Unreachable Objects: An object is considered unreachable if no references to it exist within the program. Objects that are part of an “island of isolation” are also considered unreachable. For example:
Integer i = new Integer(5);
// 'i' references the new Integer object
i = null;
// The Integer object is now unreachable
2. Eligibility for Garbage Collection: An object becomes eligible for garbage collection when it becomes unreachable, such as after nullifying the reference:
Integer i = new Integer(5);
i = null; // Now, the object is eligible for GC.
How to Make an Object Eligible for Garbage Collection
While Java automatically handles garbage collection, programmers can help by making objects unreachable when they are no longer needed. Here are four ways to make an object eligible for GC:
1. Nullifying the reference variable 2. Re-assigning the reference variable 3. Using objects created inside a method 4. Islands of Isolation (when a group of objects reference each other but are no longer referenced elsewhere)
Requesting JVM to Run the Garbage Collector
Garbage collection doesn’t happen immediately when an object becomes eligible. The JVM will run the garbage collector at its discretion. However, we can request the JVM to perform garbage collection using these methods:
1. System.gc(): Invokes the garbage collector explicitly.
2. Runtime.getRuntime().gc(): The Runtime class allows an interface with the JVM, and calling its gc() method requests garbage collection.
Finalization
Before an object is destroyed, the garbage collector calls its finalize() method to allow cleanup activities (e.g., closing database connections). This method is defined in the Object class and can be overridden.
The finalize() method is never called more than once for an object.
If the finalize() method throws an uncaught exception, it is ignored, and finalization terminates.
After the finalize() method is invoked, the garbage collector reclaims the object.
Advantages of Garbage Collection
Memory efficiency: Garbage collection helps reclaim memory by removing unreferenced objects from heap memory.
Automation: Garbage collection happens automatically as part of the JVM, removing the need for manual intervention.
Example:
class Employee {
private int ID;
private String name;
private int age;
private static int nextId = 1; // Common across all objects
public Employee(String name, int age) {
this.name = name;
this.age = age;
this.ID = nextId++;
}
public void show() {
System.out.println("ID: " + ID + "\nName: " + name + "\nAge: " + age);
}
public void showNextId() {
System.out.println("Next employee ID: " + nextId);
}
}
public class Company {
public static void main(String[] args) {
Employee e1 = new Employee("Employee1", 30);
Employee e2 = new Employee("Employee2", 25);
Employee e3 = new Employee("Employee3", 40);
e1.show();
e2.show();
e3.show();
e1.showNextId();
e2.showNextId();
e3.showNextId();
{ // Sub-block for interns
Employee intern1 = new Employee("Intern1", 22);
Employee intern2 = new Employee("Intern2", 24);
intern1.show();
intern2.show();
intern1.showNextId();
intern2.showNextId();
}
// X and Y are out of scope, but nextId has incremented
e1.showNextId(); // Expected 4, but it will give 6 as output
}
}
Output:
ID: 1
Name: Employee1
Age: 30
ID: 2
Name: Employee2
Age: 25
ID: 3
Name: Employee3
Age: 40
Next employee ID: 4
Next employee ID: 4
Next employee ID: 4
ID: 4
Name: Intern1
Age: 22
ID: 5
Name: Intern2
Age: 24
Next employee ID: 6
Next employee ID: 6
Next employee ID: 6
Types of JVM Garbage Collectors in Java with implementation details
Garbage Collection: Garbage Collection (GC) is a key feature in Java that enables automatic memory management. GC is responsible for reclaiming memory used by objects that are no longer needed, making that memory available for future use. To achieve this, the garbage collector monitors objects in memory, identifies those that are still referenced, and deallocates the memory for objects that are no longer in use. One common approach used by garbage collectors is the Mark and Sweep algorithm, which marks objects that are still reachable and then sweeps away the unmarked ones to free up memory.
Types of Garbage Collection in Java
The Java Virtual Machine (JVM) provides several garbage collection strategies, each affecting the application’s throughput and the pause time experienced during collection. Throughput measures how fast the application runs, while pause time indicates the delay introduced during garbage collection.
1. Serial Garbage Collector : The Serial Garbage Collector is the simplest form of GC, using a single thread to perform garbage collection. When this collector is in use, it stops all application threads during the collection process, known as stop-the-world behavior. Since it uses only one thread, it is not ideal for multi-threaded applications or environments where responsiveness is crucial. As a result, while it reduces complexity, it increases application pause time, negatively impacting throughput. This collector is suitable for smaller applications or single-threaded systems.
Usage: To use the Serial Garbage Collector explicitly, execute your application with the following JVM argument:
java -XX:+UseSerialGC -jar MyApplication.jar
2. Parallel Garbage Collector : The Parallel Garbage Collector, also known as the Throughput Collector, is the default collector in Java 8. It improves upon the Serial Collector by using multiple threads to perform garbage collection, allowing for better throughput at the expense of longer pause times. Like the Serial Collector, it stops all application threads during the garbage collection process. However, it provides control over the number of threads the collector uses and allows you to specify maximum pause times.
Usage: To run the Parallel Garbage Collector with a specified number of threads:
3. CMS Garbage Collector (Concurrent Mark-Sweep) : The Concurrent Mark-Sweep (CMS) Garbage Collector attempts to minimize application pauses by performing most of its work concurrently with the application. It scans memory for unreferenced objects and removes them without freezing the entire application, except in two specific scenarios:
When there are changes in heap memory during the garbage collection process.
When marking referenced objects in the old generation space.
CMS typically uses more CPU than the Parallel Collector to achieve better application responsiveness. It is ideal for applications that can afford to allocate additional CPU resources for lower pause times. To enable the CMS Garbage Collector, use the following command:
4. G1 Garbage Collector (Garbage-First): Introduced in Java 7 and made the default in Java 9, the G1 Garbage Collector was designed for applications that require large heap sizes (greater than 4GB). Instead of treating the heap as a monolithic memory block, G1 divides it into equal-sized regions. During garbage collection, G1 focuses on the regions with the most garbage, collecting them first, hence the name Garbage-First. Additionally, G1 compacts memory during garbage collection, reducing fragmentation and enhancing performance. This garbage collector offers significant performance benefits for larger applications, especially those running on modern JVMs.
Usage:
If you’re using a Java version earlier than 9 and want to enable the G1 Garbage Collector, specify the following JVM argument:
java -XX:+UseG1GC -jar MyApplication.jar
5. Example with G1 Garbage Collector: Let’s update the example to demonstrate the use of the G1 Garbage Collector. In this scenario, we’ll manage memory for a simple Employee class.
class Employee {
private int id;
private String name;
private int age;
private static int nextId = 1;
public Employee(String name, int age) {
this.name = name;
this.age = age;
this.id = nextId++;
}
public void display() {
System.out.println("ID: " + id + "\nName: " + name + "\nAge: " + age);
}
public void displayNextId() {
System.out.println("Next employee ID will be: " + nextId);
}
@Override
protected void finalize() throws Throwable {
--nextId;
System.out.println("Finalize called for employee ID: " + id);
}
}
public class TestEmployee {
public static void main(String[] args) {
Employee emp1 = new Employee("Alice", 30);
Employee emp2 = new Employee("Bob", 25);
Employee emp3 = new Employee("Charlie", 35);
emp1.display();
emp2.display();
emp3.display();
emp1.displayNextId();
emp2.displayNextId();
emp3.displayNextId();
{
// Temporary employees
Employee tempEmp1 = new Employee("David", 28);
Employee tempEmp2 = new Employee("Eva", 22);
tempEmp1.display();
tempEmp2.display();
tempEmp1.displayNextId();
tempEmp2.displayNextId();
// Making temp employees eligible for GC
tempEmp1 = null;
tempEmp2 = null;
// Requesting garbage collection
System.gc();
}
emp1.displayNextId(); // After GC, nextId should be updated correctly
}
}
Output:
ID: 1
Name: Alice
Age: 30
ID: 2
Name: Bob
Age: 25
ID: 3
Name: Charlie
Age: 35
Next employee ID will be: 4
Next employee ID will be: 4
Next employee ID will be: 4
ID: 4
Name: David
Age: 28
ID: 5
Name: Eva
Age: 22
Next employee ID will be: 6
Next employee ID will be: 6
Finalize called for employee ID: 4
Finalize called for employee ID: 5
Next employee ID will be: 4
Memory leaks in Java
In C, programmers have full control over the allocation and deallocation of dynamically created objects. If a programmer neglects to free memory for unused objects, this results in memory leaks.
In Java, automatic garbage collection helps to manage memory, but there can still be scenarios where objects remain uncollected because they are still referenced. If an application creates a large number of objects that are no longer in use but are still referenced, the garbage collector cannot reclaim their memory. These unnecessary objects are referred to as memory leaks. If the memory allocated exceeds the available limit, the program may terminate with an OutOfMemoryError. Therefore, it is crucial to ensure that objects no longer needed are made eligible for garbage collection. Tools can also help in detecting and managing memory leaks, such as:
HP OVO
HP JMeter
JProbe
IBM Tivoli
Example of a Memory Leak in Java
import java.util.ArrayList;
public class MemoryLeakExample {
public static void main(String[] args) {
ArrayList<Object> list1 = new ArrayList<>(1000000);
ArrayList<Object> list2 = new ArrayList<>(100000000);
ArrayList<Object> list3 = new ArrayList<>(1000000);
System.out.println("Memory Leak Example");
}
}
Output:
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
Java Virtual Machine (JVM) Stack Area
In Java, when a new thread is created, the JVM assigns a separate stack for it. The memory for this stack does not need to be contiguous. The JVM performs two primary operations on this stack: it pushes and pops frames, and this stack can be referred to as the runtime stack. For each thread, every method invocation is stored in this runtime stack, which includes parameters, local variables, intermediate results, and other related data. After the method completes execution, the respective entry is removed from the stack. When all method calls finish, the stack becomes empty, and the JVM removes the empty stack just before terminating the thread. Each stack’s data is thread-specific, ensuring that the data within a thread’s stack is not accessible to other threads. Hence, local data in this stack is considered thread-safe. Each entry in the stack is known as a Stack Frame or Activation Record.
Stack Frame Structure
The stack frame consists of three key parts:
1. Local Variable Array (LVA) 2. Operand Stack (OS) 3. Frame Data (FD)
When the JVM invokes a Java method, it first checks the method’s class data to determine the required size of the local variable array and operand stack, measured in words. It then creates a stack frame of the appropriate size and pushes it onto the Java stack.
1. Local Variable Array (LVA): The local variable array is organized as a zero-based array of slots where each slot stores a 4-byte word.
It stores all parameters and local variables of a method.
Values of types int, float, and object references each occupy 1 slot (4 bytes).
double and long values occupy 2 consecutive slots (8 bytes total).
byte, short, and char values are converted to int before being stored.
Most JVM implementations allocate 1 slot for boolean values.
The parameters of a method are placed into the local variable array in the order they are declared. For instance, consider a method in the Example class:
class Example {
public void bike(int i, long l, float f, double d, Object o, byte b) {
// Method body
}
}
The local variable array for the bike() method would store each parameter in order.
2. Operand Stack (OS): The JVM uses the operand stack as a workspace for storing intermediate results from computations.
It is also structured as an array of words, similar to the local variable array, but the operand stack is accessed through specific instructions that push and pop values.
Certain instructions push values onto the operand stack, others perform operations on them, and some instructions pop the results back into the local variable array.
Example of Operand Stack Usage:
The following assembly instructions illustrate how the JVM might subtract two integers stored in local variables and store the result in another local variable:
iload_0 // Push the value of local variable 0 to the operand stack
iload_1 // Push the value of local variable 1 to the operand stack
isub // Subtract the two values on the operand stack
istore_2 // Pop the result and store it in local variable 2
3. Frame Data (FD): Frame data contains symbolic references and data related to method returns.
It includes a reference to the constant pool and handles normal method returns.
Additionally, it stores references to the exception table, which helps locate the correct catch block in case an exception is thrown during execution.
In summary, the stack frame in Java is structured to efficiently manage method calls, local variables, intermediate operations, and exception handling, providing a safe and organized environment for method execution within each thread.
Instance methods are methods that are called on an instance (object) of the class they belong to. To invoke an instance method, you first need to create an object of the class.
Example syntax:
public void display(String message) {
// Code to be executed
}
Instance methods can return any data type, including primitive types (e.g., int, float) or user-defined types.
Memory Allocation of Instance Methods
Instance methods themselves are stored in the Permanent Generation (PermGen) space of the heap until Java 7, but since Java 8, they are stored in Metaspace. However, their local variables and parameters (arguments passed) are stored in the stack.
These methods can be called from within the class in which they are defined or from other classes, depending on the access modifiers.
Key Points:
1. Instance methods belong to objects, not to the class itself. 2. They are stored in a single memory location and can access the object they belong to using the this reference. 3. Instance methods can be overridden, as they are resolved at runtime through dynamic binding.
Example:
// Example to demonstrate accessing an instance method.
class Car {
String model = "";
// Instance method to set car model
public void setModel(String model) {
this.model = model;
}
}
public class Test {
public static void main(String[] args) {
// Create an instance of the Car class
Car myCar = new Car();
// Call the instance method to set model
myCar.setModel("Tesla Model S");
System.out.println(myCar.model);
}
}
Output:
Tesla Model S
Java Static Methods
Static methods, unlike instance methods, belong to the class rather than an object. They can be called without creating an instance of the class.
Example syntax:
public static void display(String message) {
// Code to be executed
}
Static methods must include the static modifier in their declaration.
Memory Allocation of Static Methods
Static methods are stored in the Metaspace area (starting from Java 8) since they are associated with the class rather than any object. However, their local variables and arguments are stored in the stack.
Key Points:
1. Static methods are called using the class name, without the need for an instance. 2. They are shared across all instances of the class. 3. Static methods cannot be overridden, but they can be hidden by subclass methods if both superclass and subclass declare a static method with the same name. This is called Method Hiding.
Example:
// Example to demonstrate accessing static methods.
class MathUtil {
public static double pi = 3.14159;
// Static method to calculate the circumference of a circle
public static double circumference(double radius) {
return 2 * pi * radius;
}
}
public class Test {
public static void main(String[] args) {
// Access static method and field using class name
double result = MathUtil.circumference(5);
System.out.println("Circumference: " + result);
// Access static method using an instance (though it's unnecessary)
MathUtil util = new MathUtil();
double result2 = util.circumference(7);
System.out.println("Circumference: " + result2);
}
}
Output:
Circumference: 31.4159
Circumference: 43.98226
Abstract Method
In Java, there are situations where we need only method declarations in superclass, which is accomplished using the abstract type modifier. Abstraction can be implemented through abstract classes and methods. In this discussion, we will explore Java Abstract Methods.
Java Abstract Method
An abstract method serves as a blueprint for other classes or interfaces. In this context, methods are declared but not implemented. Abstract methods must be implemented by subclasses or classes that implement the respective interfaces.
These methods are often referred to as “subclass responsibilities” because they lack implementations in the superclass. Consequently, any subclass must override these methods to provide concrete definitions.
Declaring Abstract Methods in Java
To declare an abstract method, use the following general syntax:
abstract returnType methodName(parameterList);
Note that no method body is included. Any concrete class (a class not marked with the abstract keyword) that extends an abstract class must implement all abstract methods from that class.
Key Points about Abstract Methods
1. Any class containing one or more abstract methods must itself be declared as abstract. 2. A class containing an abstract method must be abstract, but the reverse is not necessarily true. 3. If a non-abstract class extends an abstract class, it must implement all abstract methods of the abstract class; otherwise, the non-abstract class must also be declared as abstract. 4. The following combinations of modifiers with abstract methods are illegal:final
abstract native
abstract synchronized
abstract static
abstract private
abstract strictf
Example of Java Abstract Method
Example 1: Performing Addition and Subtraction with Abstraction
Here is a program demonstrating how to perform addition and subtraction using abstraction.
// Java Program to implement addition and subtraction using abstraction
// Abstract Class
abstract class MathOperation {
abstract void displayInfo();
}
// Class Add
class Add extends MathOperation {
void displayInfo() {
int a = 5;
int b = 10;
System.out.println("Addition: " + (a + b));
}
}
// Class Subtract
class Subtract extends MathOperation {
void displayInfo() {
int c = 15;
int d = 4;
System.out.println("Subtraction: " + (c - d));
}
}
// Driver Class
public class AbstractionDemo {
public static void main(String args[]) {
MathOperation addition = new Add();
addition.displayInfo();
MathOperation subtraction = new Subtract();
subtraction.displayInfo();
}
}
Output:
Addition: 15
Subtraction: 11
Example 2: Using Abstract Keyword with Classes and Methods
Here is another example illustrating the use of the abstract keyword.
// A Java program demonstrating the use of abstract keyword
// Abstract class
abstract class Shape {
abstract void draw();
void show() {
System.out.println("This is a concrete method in the abstract class.");
}
}
// Concrete class Circle
class Circle extends Shape {
void draw() {
System.out.println("Drawing a circle.");
}
}
// Driver class
public class AbstractExample {
public static void main(String args[]) {
Circle circle = new Circle();
circle.draw();
circle.show();
}
}
Output:
Drawing a circle.
This is a concrete method in the abstract class.
Example 3: Abstract Class with Multiple Abstract Methods
This program illustrates an abstract class containing multiple abstract methods.
// Java Program to implement an abstract class with multiple abstract methods
abstract class AreaCalculator {
abstract void calculateRectangleArea(int height, int width);
abstract void calculateSquareArea(int side);
abstract void calculateCircleArea(float radius);
}
// Class implementing the abstract methods
class Calculator extends AreaCalculator {
public void calculateRectangleArea(int height, int width) {
int area = height * width;
System.out.println("Area of rectangle: " + area);
}
public void calculateSquareArea(int side) {
int area = side * side;
System.out.println("Area of square: " + area);
}
public void calculateCircleArea(float radius) {
float area = 3.14f * radius * radius;
System.out.println("Area of circle: " + area);
}
public static void main(String[] args) {
Calculator calc = new Calculator();
calc.calculateRectangleArea(10, 5);
calc.calculateSquareArea(4);
calc.calculateCircleArea(3.0f);
}
}
Output:
Area of rectangle: 50
Area of square: 16
Area of circle: 28.259999
Abstract Method in Interface
All methods in an interface are inherently public and abstract, allowing for the declaration of abstract methods within an interface.
Here’s an implementation of this concept:
// Java Program to demonstrate abstract methods in an interface
// Declaring an interface
interface Operations {
int addTwoNumbers(int a, int b);
int addThreeNumbers(int a, int b, int c);
}
// Main Class
public class InterfaceExample implements Operations {
public int addTwoNumbers(int a, int b) {
return a + b;
}
public int addThreeNumbers(int a, int b, int c) {
return a + b + c;
}
public static void main(String args[]) {
Operations ops = new InterfaceExample();
System.out.println("Sum of two numbers: " + ops.addTwoNumbers(5, 10));
System.out.println("Sum of three numbers: " + ops.addThreeNumbers(5, 10, 15));
}
}
Output:
Sum of two numbers: 15
Sum of three numbers: 30
Final Method in Abstract Class
While you cannot use final with an abstract method, you can define a final method in an abstract class.
class Vehicle {
final void fuel() {
System.out.println("Fuel type: Petrol");
}
}
class Car extends Vehicle {
// This will cause an error
// void fuel() { System.out.println("Fuel type: Diesel"); }
}
Output:
Error: Cannot override the final method from Vehicle
Static Methods Cannot Be Overridden (Method Hiding):
When a static method is redefined in a subclass, it is method hiding, not overriding.
class Parent {
static void show() {
System.out.println("Parent static show()");
}
}
class Child extends Parent {
static void show() {
System.out.println("Child static show()");
}
}
public class Main {
public static void main(String[] args) {
Parent p = new Child();
p.show(); // Calls Parent's static show()
}
}
Output:
Parent static show()
Private Methods Cannot Be Overridden:
Private methods are not visible to the child class and hence cannot be overridden.
class Parent {
private void message() {
System.out.println("Parent's private message()");
}
public void callMessage() {
message();
}
}
class Child extends Parent {
private void message() {
System.out.println("Child's private message()");
}
}
public class Main {
public static void main(String[] args) {
Parent p = new Child();
p.callMessage(); // Calls Parent's message()
}
}
Output:
Parent's private message()
Covariant Return Type:
The return type of an overriding method can be a subtype of the original return type.
class Parent {
public Object getObject() {
return "Parent object";
}
}
class Child extends Parent {
@Override
public String getObject() {
return "Child object";
}
}
public class Main {
public static void main(String[] args) {
Parent p = new Child();
System.out.println(p.getObject());
}
}
Output:
Child object
the Parent Class Method using super:
The overridden method in the subclass can call the method from the parent class using the super keyword.
class Parent {
void display() {
System.out.println("Parent display()");
}
}
class Child extends Parent {
@Override
void display() {
super.display();
System.out.println("Child display()");
}
}
public class Main {
public static void main(String[] args) {
Child c = new Child();
c.display(); // Calls both Parent's and Child's display()
}
}
Output:
Parent display()
Child display()
Method Overriding vs Method Overloading
1. Overloading is when methods have the same name but different signatures. Overriding is when a subclass provides a specific implementation of a method that already exists in the parent class. 2. Overloading is a form of compile-time polymorphism (method resolution at compile time), while overriding is a form of run-time polymorphism (method resolution at runtime).
Overriding
Overriding is a feature in Java where a subclass or child class provides a specific implementation of a method that is already present in its parent class or superclass. When a method in a subclass has the same name, parameters (signature), and return type (or a subtype) as a method in its superclass, it overrides the method from the superclass.
Method Overriding enables Run-Time Polymorphism. This means that the method that gets executed depends on the object that is used to call it, not the reference type. If the object is of the subclass, the subclass method will be called; otherwise, the superclass method will be executed.
Example of Method Overriding:
// Superclass
class Animal {
void sound() { System.out.println("Animal makes a sound"); }
}
// Subclass
class Dog extends Animal {
@Override
void sound() {
System.out.println("Dog barks");
}
}
// Driver class
class Main {
public static void main(String[] args) {
Animal a1 = new Animal();
a1.sound(); // Calls Animal's sound()
Animal a2 = new Dog();
a2.sound(); // Calls Dog's sound (Run-time Polymorphism)
}
}
Output:
Animal makes a sound
Dog barks
Rules for Method Overriding
1. Access Modifiers in Overriding : The overridden method in the subclass can provide more visibility, but not less. For example, a protected method in the superclass can be overridden as public in the subclass, but not private.
class Vehicle {
protected void start() {
System.out.println("Vehicle starts");
}
}
class Car extends Vehicle {
@Override
public void start() { // More accessible
System.out.println("Car starts");
}
}
class Main {
public static void main(String[] args) {
Vehicle v = new Car();
v.start(); // Calls Car's start()
}
}
Output:
Car starts
2. Final Methods Cannot Be Overridden : If a method is declared as final in the superclass, it cannot be overridden in the subclass.
class Bird {
final void fly() {
System.out.println("Bird is flying");
}
}
class Eagle extends Bird {
// This would produce an error if uncommented
// void fly() { System.out.println("Eagle flies faster"); }
}
Output:
Compilation error: cannot override final method
3. Static Methods and Method Hiding : Static methods cannot be overridden; they are hidden. A subclass can define a static method with the same signature as the one in its superclass, but this will hide the method in the superclass rather than overriding it.
class Parent {
static void display() {
System.out.println("Parent display");
}
void show() {
System.out.println("Parent show");
}
}
class Child extends Parent {
static void display() {
System.out.println("Child display");
}
@Override
void show() {
System.out.println("Child show");
}
}
class Main {
public static void main(String[] args) {
Parent p = new Child();
p.display(); // Calls Parent's static method
p.show(); // Calls Child's overridden method
}
}
Output:
Parent display
Child show
4. Private Methods Cannot Be Overridden : Private methods in the superclass cannot be overridden by subclasses. They are not visible to subclasses and are resolved at compile time.
class Super {
private void secretMethod() {
System.out.println("Super's secret method");
}
public void callSecret() {
secretMethod();
}
}
class Sub extends Super {
private void secretMethod() {
System.out.println("Sub's secret method");
}
}
public class Main {
public static void main(String[] args) {
Super sup = new Super();
sup.callSecret(); // Calls Super's private method
}
}
Output:
Super's secret method
5. Covariant Return Type in Overriding : The return type of the overriding method can be a subclass of the return type of the overridden method.
public class BitwiseOperators {
public static void main(String[] args) {
int a = 5; // 0101 in binary
int b = 3; // 0011 in binary
System.out.println("a & b: " + (a & b)); // AND operation
System.out.println("a | b: " + (a | b)); // OR operation
}
}
Output:
class SuperClass {
public Object getObject() {
return new Object();
}
}
class SubClass extends SuperClass {
@Override
public String getObject() {
return "This is a String";
}
}
public class Main {
public static void main(String[] args) {
SuperClass obj = new SubClass();
System.out.println(obj.getObject());
}
}
Output:
This is a String
6. Calling the Superclass Method : We can call the superclass version of the overridden method using the super keyword.
class Superhero {
void power() {
System.out.println("Superhero has generic powers");
}
}
class Superman extends Superhero {
@Override
void power() {
super.power();
System.out.println("Superman can fly and has super strength");
}
}
class Main {
public static void main(String[] args) {
Superman clark = new Superman();
clark.power();
}
}
Output:
Superhero has generic powers
Superman can fly and has super strength
Overriding and Exception Handling
1. Unchecked Exceptions: If the superclass method does not throw any exceptions, the subclass can only throw unchecked exceptions when overriding it.
2. Checked Exceptions : If the superclass method throws an exception, the overriding method can throw the same exception or its subclass, but not a higher-level exception.
class Animal {
void eat() throws Exception {
System.out.println("Animal is eating");
}
}
class Dog extends Animal {
@Override
void eat() throws RuntimeException {
System.out.println("Dog is eating");
}
}
Method Overloading
In Java, Method Overloading allows multiple methods to share the same name but differ in parameters—either by the number of parameters, types of parameters, or a combination of both. This feature is also called Compile-time Polymorphism, Static Polymorphism, or Early Binding. When overloaded methods are present, Java gives priority to the most specific match among the parameters.
Example of Method Overloading
// Java program demonstrating method overloading
public class Calculator {
// Overloaded add() method that accepts two integer parameters
public int add(int a, int b) {
return a + b;
}
// Overloaded add() method that accepts three integer parameters
public int add(int a, int b, int c) {
return a + b + c;
}
// Overloaded add() method that accepts two double parameters
public double add(double a, double b) {
return a + b;
}
public static void main(String[] args) {
Calculator calc = new Calculator();
System.out.println(calc.add(5, 10)); // Two integers
System.out.println(calc.add(5, 10, 15)); // Three integers
System.out.println(calc.add(4.5, 3.5)); // Two doubles
}
}
Output:
15
30
8.0
Ways to Achieve Method Overloading in Java:
1. Changing the Number of Parameters 2. Changing the Data Types of the Arguments 3. Changing the Order of Parameters
1. Changing the Number of Parameters : This approach achieves method overloading by varying the number of input parameters in methods that share the same name.
// Java program to demonstrate method overloading by changing the number of parameters
class Multiply {
// Multiply two integers
public int multiply(int a, int b) {
return a * b;
}
// Multiply three integers
public int multiply(int a, int b, int c) {
return a * b * c;
}
public static void main(String[] args) {
Multiply obj = new Multiply();
System.out.println("Product of two integers: " + obj.multiply(2, 3));
System.out.println("Product of three integers: " + obj.multiply(2, 3, 4));
}
}
Output:
Product of two integers: 6
Product of three integers: 24
2. Changing Data Types of the Arguments : This method achieves overloading by having the same method name but with different parameter types.
// Java program to demonstrate method overloading by changing the data types of parameters
class Volume {
// Calculate volume using integer values
public int calculateVolume(int a, int b, int c) {
return a * b * c;
}
// Calculate volume using double values
public double calculateVolume(double a, double b, double c) {
return a * b * c;
}
public static void main(String[] args) {
Volume vol = new Volume();
System.out.println("Volume with integers: " + vol.calculateVolume(2, 3, 4));
System.out.println("Volume with doubles: " + vol.calculateVolume(2.5, 3.5, 4.5));
}
}
Output:
Volume with integers: 24
Volume with doubles: 39.375
3. Changing the Order of Parameters : You can achieve method overloading by altering the order in which parameters are passed.
// Java program to demonstrate method overloading by changing the order of parameters
class Display {
// Display information by name followed by age
public void showDetails(String name, int age) {
System.out.println("Name: " + name + ", Age: " + age);
}
// Display information by age followed by name
public void showDetails(int age, String name) {
System.out.println("Age: " + age + ", Name: " + name);
}
public static void main(String[] args) {
Display obj = new Display();
obj.showDetails("Alice", 30);
obj.showDetails(25, "Bob");
}
}
Output:
Name: Alice, Age: 30
Age: 25, Name: Bob
What if the exact prototype doesn’t match?
In situations where the parameters don’t match any exact method signature, Java prioritizes methods based on data type compatibility. The compiler tries to:
1. Convert the parameter to a higher data type within the same group (e.g., from byte to int). 2. If no match is found, it attempts to move to a higher data type in another group (e.g., from int to float).
class Demo {
public void display(int x) {
System.out.println("Integer: " + x);
}
public void display(String s) {
System.out.println("String: " + s);
}
public void display(byte b) {
System.out.println("Byte: " + b);
}
}
public class UseDemo {
public static void main(String[] args) {
byte a = 20;
Demo obj = new Demo();
obj.display(a); // Byte method
obj.display("Hello"); // String method
obj.display(500); // Integer method
obj.display('C'); // Char is promoted to int (ASCII value)
// Uncommenting the line below would cause a compilation error:
// obj.display(10.5); // No suitable method for double
}
}
Output:
Byte: 20
String: Hello
Integer: 500
Integer: 67
Advantages of Method Overloading
Enhanced Readability and Reusability: By using method overloading, the code becomes more intuitive, reducing the need for verbose method names.
Reduced Complexity: Methods performing similar tasks can share a name, making code easier to manage.
Efficiency: Overloading allows different versions of methods to handle related tasks with varying parameters, optimizing the function call.
Multiple Constructor Options: Overloaded constructors enable different ways to initialize objects, depending on the given arguments.
Inheritance is a feature in object-oriented programming where a new class (called derived or child class) is created by inheriting properties and behaviors (methods and variables) from an existing class (called base or parent class). It promotes code reusability and reduces redundancy.
#include <iostream>
using namespace std;
class A {
int a, b;
public:
void add(int x, int y)
{
a = x;
b = y;
cout << "Addition of a + b is: " << (a + b) << endl;
}
};
class B : public A {
public:
void print(int x, int y)
{
add(x, y);
}
};
int main()
{
B b1;
b1.print(5, 6);
return 0;
}
Output:
Addition of a + b is: 11
Here, class B inherits the add() method from class A.
Polymorphism:
Polymorphism is a feature in object-oriented programming where an object can take multiple forms. Polymorphism allows one task to be performed in different ways, either at compile-time or run-time.
Types of Polymorphism:
Compile-time polymorphism (Method Overloading)
Run-time polymorphism (Method Overriding)
Example of Polymorphism:
#include <iostream>
using namespace std;
class A {
int a, b, c;
public:
// Compile-time polymorphism (Method Overloading)
void add(int x, int y)
{
a = x;
b = y;
cout << "Addition of a + b is: " << (a + b) << endl;
}
void add(int x, int y, int z)
{
a = x;
b = y;
c = z;
cout << "Addition of a + b + c is: " << (a + b + c) << endl;
}
// Run-time polymorphism (Method Overriding)
virtual void print()
{
cout << "Class A's method is running" << endl;
}
};
class B : public A {
public:
void print()
{
cout << "Class B's method is running" << endl;
}
};
int main()
{
A a1;
// Compile-time polymorphism (Method Overloading)
a1.add(6, 5);
a1.add(1, 2, 3);
B b1;
// Run-time polymorphism (Method Overriding)
b1.print();
}
Output:
Addition of a + b is: 11
Addition of a + b + c is: 6
Class B's method is running
Difference between Inheritance and Polymorphism:
Inheritance
Polymorphism
Inheritance allows a new class (derived class) to inherit features from an existing class (base class).
Polymorphism allows methods to take multiple forms.
Inheritance applies to classes.
Polymorphism applies to methods or functions.
Inheritance supports code reusability and reduces code duplication.
Polymorphism allows the program to choose which function to execute at compile-time (overloading) or run-time (overriding).
Inheritance can be single, multiple, hierarchical, multilevel, or hybrid.
Polymorphism can be compile-time (overloading) or run-time (overriding).
Inheritance is used to model relationships between classes.
Polymorphism allows flexibility in implementing methods.
Example of Inheritance: A class Car can be derived from a class Vehicle, and Car can further inherit properties like engine type, wheels, etc.
Example of Polymorphism: The class Car can have a method setColor(), which changes the car’s color based on the input color value provided.
Function Overriding in C++
Method Overriding and Runtime Polymorphism in Java:
Java supports runtime polymorphism through method overriding. Dynamic method dispatch is the mechanism that resolves which overridden method will be executed at runtime, not during compile-time.
When a method is called on a superclass reference, Java determines which version of the method (from the superclass or subclass) to execute based on the actual object being referenced at the time of the call. This decision is made at runtime, depending on the type of the object (not the reference variable).
A superclass reference variable can refer to an object of a subclass, which is known as upcasting. Java uses this concept to enable method overriding during runtime.
If a superclass contains a method that is overridden by a subclass, the version of the method executed depends on the object type being referred to, even though the reference variable is of the superclass type. Below is an example demonstrating dynamic method dispatch:
Java Example of Dynamic Method Dispatch:
// A Java program to demonstrate Dynamic Method Dispatch
class Animal {
void sound() {
System.out.println("Animal makes a sound");
}
}
class Dog extends Animal {
// overriding sound() method
void sound() {
System.out.println("Dog barks");
}
}
class Cat extends Animal {
// overriding sound() method
void sound() {
System.out.println("Cat meows");
}
}
public class DispatchDemo {
public static void main(String[] args) {
// creating objects of Animal, Dog, and Cat
Animal animal = new Animal();
Dog dog = new Dog();
Cat cat = new Cat();
// reference of type Animal
Animal ref;
// ref refers to Animal object
ref = animal;
ref.sound(); // calls Animal's version of sound()
// ref refers to Dog object
ref = dog;
ref.sound(); // calls Dog's version of sound()
// ref refers to Cat object
ref = cat;
ref.sound(); // calls Cat's version of sound()
}
}
Output:
Animal makes a sound
Dog barks
Cat meows
Explanation:
The above program has one superclass Animal and two subclasses Dog and Cat. Each subclass overrides the sound() method from the superclass.
First, an object of each class (Animal, Dog, Cat) is created.
Then a reference of type Animal is used to refer to objects of different types (upcasting).
The version of the sound() method called depends on the actual object type at runtime.
Example: Runtime Polymorphism with Data Members (Java):
In Java, runtime polymorphism works with methods but not with data members (variables). Variables are not overridden, so the reference variable will always access the data member of the superclass, not the subclass.
Java Example for Data Members:
// Java program to show that runtime polymorphism
// doesn't apply to data members (only methods)
class Animal {
int age = 5;
}
class Dog extends Animal {
int age = 10;
}
public class TestDemo {
public static void main(String[] args) {
Animal animal = new Dog(); // object of type Dog
// Data member of class Animal will be accessed
System.out.println(animal.age);
}
}
Output:
5
Explanation:
In this program, both the Animal and Dog classes have a common data member age. Even though the object is of type Dog and the reference variable is of type Animal, the data member of Animal will be accessed because data members are not overridden. Therefore, animal.age refers to the superclass Animal‘s age value.
Advantages of Dynamic Method Dispatch:
1. Supports Method Overriding: Dynamic method dispatch allows Java to support method overriding, which is crucial for implementing runtime polymorphism. 2. Provides Flexibility in Method Implementation: A class can define common methods that are shared by all its subclasses, while allowing each subclass to provide specific implementations for those methods. 3. Enhances Extensibility: It enables subclasses to add their unique behaviors while still using the reference variable of the superclass, promoting flexibility and scalability in code.
Difference between Compile-time and Run-time Polymorphism in Java
Polymorphism Explained:
The term polymorphism refers to the concept of having multiple forms. In simpler terms, polymorphism allows the same message or method to be processed in more than one way. In this discussion, we will explore the distinction between the two types of polymorphism: compile-time and runtime polymorphism.
Compile-Time Polymorphism:
When the binding of a method call to the method definition occurs at compile-time, it is known as compile-time polymorphism. Java resolves which method to call by examining the method signatures during compilation, which is why this type of polymorphism is also called static or early binding. Compile-time polymorphism is achieved through method overloading.
Method Overloading refers to having multiple methods in the same class with the same name but different parameter lists (method signatures). This is one way to implement polymorphism, although the specific method varies based on the language. In Java, method overloading is resolved at compile-time.
Here is an example demonstrating compile-time polymorphism:
Java Example of Compile-Time Polymorphism:
// Java program demonstrating compile-time polymorphism
public class Example {
// First version of the add method
public static int add(int a, int b) {
return a + b;
}
// Second version of the add method
public static double add(double a, double b) {
return a + b;
}
public static void main(String[] args) {
// The first add method is called
System.out.println(add(3, 4)); // Output: 7
// The second add method is called
System.out.println(add(3.5, 4.5)); // Output: 8.0
}
}
Output:
7
8.0
Run-Time Polymorphism:
When the method binding happens at runtime, it is called runtime polymorphism. This is achieved through method overriding in Java. The Java Virtual Machine (JVM) determines which overridden method to call at runtime based on the actual object, not during compilation.
Method Overriding occurs when a subclass provides its specific implementation of a method that is already defined in its superclass. This allows different classes to define the same method in their own way, which is resolved dynamically at runtime.
Java Example of Run-Time Polymorphism:
// Java program demonstrating run-time polymorphism
// Parent class
class Animal {
public void makeSound() {
System.out.println("Animal makes a sound");
}
}
// Child class
class Dog extends Animal {
// Overriding the parent method
public void makeSound() {
System.out.println("Dog barks");
}
}
public class Main {
public static void main(String[] args) {
Animal animalRef = new Dog(); // Upcasting
// The overridden method in Dog will be called
animalRef.makeSound(); // Output: Dog barks
}
}
Output:
Dog barks
In this example, even though the reference variable animalRef is of type Animal, it refers to an object of type Dog. At runtime, the JVM determines which makeSound method to execute based on the object type, resulting in the Dog class’s method being called.
Differences between Compile-Time and Run-Time Polymorphism:
Compile-Time Polymorphism
Run-Time Polymorphism
The method call is resolved by the compiler.
The method call is resolved during runtime by the JVM.
Also known as Static binding, Early binding, or Overloading.
Also known as Dynamic binding, Late binding, or Overriding.
Achieved by method overloading.
Achieved by method overriding.
Faster execution, as the method to execute is determined at compile time.
Slower execution in comparison, as the method is determined at runtime.
Does not involve inheritance.
Requires inheritance for method overriding.
Less flexible, since method calls are fixed at compile-time.
More flexible, since method calls are resolved at runtime.
Advantages of Polymorphism:
Compile-Time Polymorphism: Provides faster execution as the decision of which method to invoke is made at compile time, making the program more efficient.
Run-Time Polymorphism: Offers greater flexibility because method calls are determined at runtime, allowing different behaviors based on the actual object type being referenced.
Abstraction and Encapsulation in Object-Oriented Programming (OOP)
Abstraction and Encapsulation are key principles in Object-Oriented Programming (OOP). They are fundamental to building maintainable, reusable, and secure applications. Both concepts contribute to features like reusability, security, data hiding, and implementation concealment. Despite their relation, they serve different purposes and are implemented in unique ways. Let’s explore these differences with code examples.
Encapsulation in Java
Encapsulation is the process of bundling data (variables) and methods that operate on the data within a single unit or class. It ensures that the data is not accessible directly outside the class, thereby providing security and control over its modification. By restricting access to data through private variables and allowing controlled access via public methods, encapsulation prevents unauthorized access.
In simple terms, encapsulation can be thought of as a protective shield that keeps data safe from unauthorized access. This is why it is also referred to as data hiding.
Java Example of Encapsulation:
// Java program demonstrating encapsulation
class Student {
// Private variables, accessible only through public methods
private String studentName;
private int studentID;
private int studentAge;
// Getter method for age
public int getAge() {
return studentAge;
}
// Getter method for name
public String getName() {
return studentName;
}
// Getter method for ID
public int getID() {
return studentID;
}
// Setter method for age
public void setAge(int newAge) {
studentAge = newAge;
}
// Setter method for name
public void setName(String newName) {
studentName = newName;
}
// Setter method for ID
public void setID(int newID) {
studentID = newID;
}
}
public class TestEncapsulation {
public static void main(String[] args) {
// Creating an object of Student class
Student obj = new Student();
// Setting values of the variables
obj.setName("John");
obj.setAge(22);
obj.setID(1001);
// Displaying values of the variables
System.out.println("Student's name: " + obj.getName());
System.out.println("Student's age: " + obj.getAge());
System.out.println("Student's ID: " + obj.getID());
}
}
Output:
Student's name: John
Student's age: 22
Student's ID: 1001
In this example, encapsulation ensures that studentID, studentName, and studentAge can only be accessed or modified through the setter and getter methods, not directly.
Abstraction in Java
Abstraction is the concept of showing only the relevant details to the user and hiding unnecessary implementation details. It allows you to focus on what an object does rather than how it does it. Abstraction simplifies complex systems by breaking them into smaller, more understandable parts.
For example, you interact with a vehicle through its interface (like driving a car), but you do not need to know the internal mechanics of how the engine operates. Abstraction is often achieved in Java through abstract classes and interfaces.
Java Example of Abstraction:
// Java program demonstrating abstraction
abstract class Appliance {
String brand;
// Abstract methods that subclasses need to implement
abstract void turnOn();
abstract void turnOff();
// Constructor
public Appliance(String brand) {
this.brand = brand;
}
// Concrete method
public String getBrand() {
return brand;
}
}
class WashingMachine extends Appliance {
public WashingMachine(String brand) {
super(brand);
}
@Override
void turnOn() {
System.out.println(brand + " Washing Machine is now ON");
}
@Override
void turnOff() {
System.out.println(brand + " Washing Machine is now OFF");
}
}
class Refrigerator extends Appliance {
public Refrigerator(String brand) {
super(brand);
}
@Override
void turnOn() {
System.out.println(brand + " Refrigerator is now ON");
}
@Override
void turnOff() {
System.out.println(brand + " Refrigerator is now OFF");
}
}
public class TestAbstraction {
public static void main(String[] args) {
Appliance washingMachine = new WashingMachine("LG");
Appliance refrigerator = new Refrigerator("Samsung");
washingMachine.turnOn();
refrigerator.turnOn();
washingMachine.turnOff();
refrigerator.turnOff();
}
}
Output:
LG Washing Machine is now ON
Samsung Refrigerator is now ON
LG Washing Machine is now OFF
Samsung Refrigerator is now OFF
In this example, abstraction allows us to define the basic behavior of an appliance without showing the internal workings. The specific implementations for turnOn and turnOff are provided by the subclasses WashingMachine and Refrigerator.
Differences Between Abstraction and Encapsulation
Abstraction
Encapsulation
Abstraction focuses on hiding implementation details while showing only essential features to the user.
Encapsulation bundles the data and methods that operate on the data within one unit and restricts direct access to some of the object’s components.
It is used to reduce complexity and increase maintainability by defining clear interfaces.
It is used to hide data and provide controlled access, enhancing security and data integrity.
Abstraction is more about the design and involves creating abstract classes and interfaces.
Encapsulation is more about the implementation, using access modifiers like private, protected, and public.
Problems are solved at the interface level.
Problems are solved at the class level.
Abstraction can be achieved using abstract classes and interfaces.
Encapsulation is achieved using access modifiers and getter/setter methods.
Example: A TV remote control lets you operate the TV without showing how the circuits inside work.
Example: A person’s bank account details are hidden from unauthorized users, and access is granted only via public methods.
In Java, the abstract keyword is a non-access modifier applied to classes and methods, but not variables. It is primarily used to achieve abstraction, one of the core principles of Object-Oriented Programming (OOP). Below are the various contexts where the abstract keyword can be utilized in Java.
Characteristics of the abstract Keyword in Java
The abstract keyword is used to define abstract classes and methods. Here are its key characteristics:
Abstract classes cannot be instantiated: An abstract class is one that cannot be instantiated directly. It serves as a base class for other classes, which are responsible for providing concrete implementations of its abstract methods.
Abstract methods lack a body: An abstract method is declared using the abstract keyword and has no method body. It ends with a semicolon. Any class that extends an abstract class must provide implementations for all abstract methods.
Abstract classes can have both abstract and concrete methods: Along with abstract methods, an abstract class can also contain concrete methods with full implementations. These methods can be used by the abstract class itself or its subclasses.
Abstract classes can have constructors: While abstract classes cannot be instantiated, they can define constructors. These constructors are typically called during the instantiation of a concrete subclass.
Abstract classes can include instance variables: Abstract classes can declare instance variables, which can be accessed by both the abstract class and its subclasses.
Abstract classes can implement interfaces: An abstract class can implement interfaces and provide concrete implementations of the interface methods. However, the abstract class does not need to implement all methods immediately—this can be deferred to its subclasses.
Abstract Methods in Java
Abstract methods serve the purpose of declaring methods in a superclass without providing an implementation. Subclasses are responsible for implementing these methods. Abstract methods are often referred to as having “subclass responsibility.”
To declare an abstract method, use the following general syntax:
abstract returnType methodName(parameterList);
Since no method body is provided, any class that extends an abstract class must implement all of the abstract methods.
Rules for Abstract Methods
Some important rules associated with abstract methods are:
Any class containing one or more abstract methods must also be declared abstract.
You cannot combine the abstract modifier with the following modifiers: final, native, synchronized, static, private, or strictfp.
Abstract Classes in Java
An abstract class is a class that has partial implementation, meaning it may have methods that lack concrete definitions. To declare a class abstract, use the following syntax:
abstract class ClassName {
// class body
}
Abstract classes cannot be instantiated directly, and any class that extends an abstract class must either implement all abstract methods or be declared abstract itself.
Example of Abstract Classes and Methods
Here’s an example that demonstrates the use of abstract classes and methods:
// Abstract class representing a general vehicle
abstract class Vehicle {
// Abstract method (no implementation)
abstract void startEngine();
// Concrete method
void stopEngine() {
System.out.println("Engine stopped.");
}
}
// Concrete class representing a car
class Car extends Vehicle {
// Providing implementation for the abstract method
void startEngine() {
System.out.println("Car engine started.");
}
}
// Driver class to demonstrate abstract classes
public class Main {
public static void main(String[] args) {
Vehicle myCar = new Car(); // Vehicle reference, Car object
myCar.startEngine(); // Output: Car engine started.
myCar.stopEngine(); // Output: Engine stopped.
}
}
Output:
Car engine started.
Engine stopped.
Abstract Class in Java
In Java, an abstract class is defined using the abstract keyword. It is a class that cannot be instantiated on its own and may contain both abstract and concrete methods (methods with bodies). The abstract keyword can only be applied to classes and methods, not variables. In this article, we will explore the concept of abstract classes and their use in Java.
What is an Abstract Class?
An abstract class is a blueprint for other classes and cannot be used to create objects directly. It can only be subclassed, allowing other classes to inherit its properties. Declaring an abstract class in Java requires using the abstract keyword in its class definition. This approach allows for partial implementation of functionality, leaving subclasses to complete the abstract methods.
Illustration of Abstract Class
abstract class Shape {
int color;
// Abstract method (no implementation)
abstract void draw();
}
Important Points about Abstract Classes
Cannot Instantiate Abstract Classes: Instances of abstract classes cannot be created.
Constructors Are Allowed: Abstract classes can have constructors that are invoked when a subclass is instantiated.
No Abstract Methods Required: An abstract class can exist without any abstract methods.
Final Methods: Abstract classes can have final methods, but a method declared as abstract cannot be final, as this combination will result in an error.
Static Methods: Static methods can be defined in abstract classes.
Usage of Abstract Classes: Abstract classes can be used for both top-level (outer) and inner classes.
Incomplete Methods: If a subclass does not provide implementation for all abstract methods of a parent class, it must also be declared abstract.
Examples of Java Abstract Classes
1. Abstract Class with an Abstract Method: Here’s an example that demonstrates how an abstract class works in Java:
// Abstract class
abstract class Vehicle {
abstract void displayDetails();
}
// Class extending the abstract class
class Car extends Vehicle {
void displayDetails() {
String model = "Tesla";
int year = 2024;
double price = 55000.00;
System.out.println("Model: " + model);
System.out.println("Year: " + year);
System.out.println("Price: $" + price);
}
}
// Main class
public class Main {
public static void main(String[] args) {
Vehicle v = new Car();
v.displayDetails();
}
}
Output:
Model: Tesla
Year: 2024
Price: $55000.0
Examples of Java Abstract Classes
2. Abstract Class with an Abstract Method : Here’s an example that demonstrates how an abstract class works in Java:
// Abstract class
abstract class Course {
Course() {
System.out.println("Enrolled in the course");
}
abstract void courseSyllabus();
void study() {
System.out.println("Studying for exams!");
}
}
// Subclass extending the abstract class
class ComputerScience extends Course {
void courseSyllabus() {
System.out.println("Topics: Data Structures, Algorithms, AI");
}
}
// Main class
public class Main {
public static void main(String[] args) {
Course c = new ComputerScience();
c.courseSyllabus();
c.study();
}
}
Output:
Enrolled in the course
Topics: Data Structures, Algorithms, AI
Studying for exams!
An abstract class cannot be instantiated directly, but you can create references of the abstract class type.
// Abstract class
abstract class Animal {
abstract void sound();
}
// Concrete class
class Dog extends Animal {
void sound() {
System.out.println("Bark");
}
}
// Main class
public class Main {
public static void main(String[] args) {
// Animal a = new Animal(); // Error: Cannot instantiate the abstract class
Animal a = new Dog();
a.sound();
}
}
Output:
Bark
Observation 2: Abstract Class with Constructors
A constructor in an abstract class can be called when an instance of a subclass is created.
abstract class Appliance {
Appliance() {
System.out.println("Appliance Constructor");
}
abstract void use();
}
class WashingMachine extends Appliance {
WashingMachine() {
System.out.println("Washing Machine Constructor");
}
void use() {
System.out.println("Washing clothes");
}
}
public class Main {
public static void main(String[] args) {
WashingMachine wm = new WashingMachine();
wm.use();
}
}
Observation 3: Abstract Class Without Abstract Methods
Abstract classes can exist without having abstract methods.
abstract class Library {
void borrowBook() {
System.out.println("Borrowing a book");
}
}
class CityLibrary extends Library {}
public class Main {
public static void main(String[] args) {
CityLibrary cl = new CityLibrary();
cl.borrowBook();
}
}
Output:
Borrowing a book
Control Abstraction in Java with Examples
Before diving into control abstraction, let’s first understand the concept of abstraction.
Abstraction: Abstraction simplifies the complexity of a system by exposing only the essential features while hiding the intricate internal details. For example, when driving a car, the driver interacts with the steering wheel, pedals, and other controls, but the complex workings of the engine and electronics are abstracted away. The driver only needs to know how to operate the car, not how every internal mechanism functions.
Now, let’s explore an example of abstraction before delving into control abstraction:
abstract class Person {
abstract void displayDetails();
}
class Employee extends Person {
void displayDetails() {
String name = "John";
int age = 30;
double salary = 55000.50;
System.out.println("Name: " + name);
System.out.println("Age: " + age);
System.out.println("Salary: $" + salary);
}
}
class Main {
public static void main(String[] args) {
Person employee = new Employee();
employee.displayDetails();
}
}
Output:
Name: John
Age: 30
Salary: $55000.5
In the above example, the details of an employee are abstracted through the Person class, and the specifics are implemented in the Employee class. Only essential information is shown to the user.
Types of Abstraction
There are two main types of abstraction:
1. Data Abstraction: This involves creating complex data types and exposing only essential operations. 2. Control Abstraction: This focuses on simplifying the program logic by removing unnecessary execution details and structuring the program into manageable parts.
Control Abstraction in Java
Control Abstraction in programming refers to the process of using higher-level operations and constructs (such as functions, loops, and conditional statements) to manage and simplify complex control flows. Instead of repeatedly writing out specific instructions, control abstraction encourages modular and reusable code that follows the DRY (Don’t Repeat Yourself) principle.
Key Features of Control Abstraction:
It promotes reusability by using methods and functions, thereby reducing code duplication.
Control abstraction bundles control statements into a single unit to make code easier to understand and manage.
It’s a fundamental feature of higher-level languages, including Java.
It focuses on how a task is achieved rather than the detailed steps involved in doing it.
It is often seen in structured programming through control structures like loops, conditionals, and function calls.
Example of Control Abstraction:
// Abstract class
abstract class Vehicle {
// Abstract method (does not have a body)
public abstract void makeSound();
// Regular method
public void startEngine() {
System.out.println("Engine starting...");
}
}
// Subclass (inherit from Vehicle)
class Car extends Vehicle {
public void makeSound() {
// The body of makeSound() is provided here
System.out.println("Car sound: Vroom Vroom");
}
}
class Main {
public static void main(String[] args) {
// Create a Car object
Car myCar = new Car();
myCar.startEngine(); // Regular method
myCar.makeSound(); // Abstract method implementation
}
}
Output:
Engine starting...
Car sound: Vroom Vroom
In this example:
startEngine() is a regular method defined in the Vehicle abstract class, and it’s used by any subclass.
makeSound() is an abstract method in Vehicle, and each subclass (like Car) must provide its own implementation of this method.
Key Steps of Control Flow:
1. The necessary resources are obtained. 2. The block of code is executed. 3. When control exits the block, resources are released or closed.
This structured approach ensures a clean flow of execution, making the program easier to read and maintain.
Difference Between Data Hiding and Abstraction in Java
Abstraction is the process of hiding the internal implementation and showcasing only the essential features or services. It allows the user to interact with the system without needing to understand its inner workings. This is accomplished through the use of abstract classes and interfaces in Java. Essentially, abstraction highlights only the necessary characteristics of an object, which distinguishes it from other objects, while suppressing non-essential details from the user.
Real-Life Example of Abstraction:
Consider an ATM machine. When you use an ATM, you see a graphical user interface (GUI) that displays services such as withdrawals, deposits, and checking balances. However, the internal mechanisms—such as how the transactions are processed—are hidden from the user.
Types of Abstraction
There are three main types of abstraction:
1. Procedural Abstraction: Involves a series of procedures (or functions) that are executed sequentially to achieve abstraction through the use of classes and methods. 2. Data Abstraction: Focuses on representing an object using a set of data while hiding its underlying implementation details. 3. Control Abstraction: Involves writing a program in such a way that the control flow details (such as loops, conditions, or method calls) are hidden, encapsulating the operations into higher-level functions or objects.
Advantages of Abstraction:
Security: Internal implementation details are hidden, which provides protection from unauthorized access.
Ease of Enhancement: Changes can be made to the internal system without affecting end users.
Flexibility: The system is easier to use for end users since they only interact with essential features.
Enhanced Application Quality: It helps in building more sophisticated and efficient applications by focusing on the most important aspects.
Implementation of Abstraction in Java
Abstraction is implemented using classes and interfaces, which represent only the significant traits and hide internal implementations. Here’s an example:
// Abstract class representing a creature
abstract class Creature {
// Abstract method hiding specific details
abstract void numberOfLegs();
}
// A class representing an Elephant, inheriting from Creature
class Elephant extends Creature {
// Implementing the abstract method
void numberOfLegs() {
System.out.println("The elephant has four legs.");
}
}
// A class representing a Human, also inheriting from Creature
class Human extends Creature {
// Implementing the abstract method
public void numberOfLegs() {
System.out.println("Humans have two legs.");
}
}
public class Main {
public static void main(String[] args) {
// Creating Human object
Human human = new Human();
human.numberOfLegs();
// Creating Elephant object
Elephant elephant = new Elephant();
elephant.numberOfLegs();
}
}
Output:
Humans have two legs.
The elephant has four legs
In this example, the Creature class is abstract and hides the internal details about the number of legs of different creatures. The concrete classes Elephant and Human provide specific implementations of the numberOfLegs method.
Data Hiding in Java
Data Hiding refers to the practice of hiding internal data from external access. This ensures that an object’s internal state cannot be directly accessed by other classes, maintaining security. Data hiding is usually achieved using access modifiers, such as private, which prevent external classes from directly accessing certain fields or methods.
Example of Data Hiding:
class BankAccount {
// Private variable for account balance
private double accountBalance;
// Getter method to access account balance securely
public double getAccountBalance() {
return accountBalance;
}
// Setter method to update account balance securely
public void setAccountBalance(double accountBalance) {
this.accountBalance = accountBalance;
}
}
In this example, the accountBalance variable is hidden from other classes by using the private access modifier. External classes can only access or modify the account balance through the public getter and setter methods, ensuring data protection.
Key Differences Between Abstraction and Data Hiding:
Abstraction focuses on hiding unnecessary implementation details and showing only essential features to the user.
Data Hiding restricts access to the internal data of a class, ensuring that only certain methods can access or modify it.
Both concepts work together to promote encapsulation, ensuring the security and integrity of the system’s internal workings.
In Java, constructors are used to initialize the attributes of an object, making the language more reflective of real-world scenarios. By default, Java provides a no-argument constructor that is automatically invoked if no constructors are defined. However, if you create a custom constructor, such as a parameterized one, you must explicitly define the default constructor, as it will no longer be provided automatically.
Note:
If a base class has a no-argument constructor, it is called automatically when a constructor of the derived class is invoked.
Example:
// Java Program to Demonstrate
// Constructor Invocation Without Using super Keyword
// Parent class
class Parent {
// Constructor of the parent class
Parent() {
System.out.println("Parent Class Constructor Called");
}
}
// Child class
class Child extends Parent {
// Constructor of the child class
Child() {
System.out.println("Child Class Constructor Called");
}
}
// Main class
public class Main {
public static void main(String[] args) {
// Creating an object of the child class
Child childObj = new Child();
// Note: The constructor of the parent class is invoked first,
// followed by the constructor of the child class.
}
}
Output:
Parent Class Constructor Called
Child Class Constructor Called
In Java, constructors are used to initialize the attributes of an object, making the language more reflective of real-world scenarios. By default, Java provides a no-argument constructor that is automatically invoked if no constructors are defined. However, if you create a custom constructor, such as a parameterized one, you must explicitly define the default constructor, as it will no longer be provided automatically.
Note:
If a base class has a no-argument constructor, it is called automatically when a constructor of the derived class is invoked.
Example:
// Java Program to Demonstrate
// Constructor Invocation Using super Keyword
// Base class
class Vehicle {
int speed;
// Parameterized constructor of the base class
Vehicle(int spd) {
speed = spd;
}
}
// Subclass
class Car extends Vehicle {
String model;
// Constructor of the subclass
Car(int spd, String mdl) {
// Using super to call the base class constructor
super(spd);
model = mdl;
}
// Method to display details
void displayDetails() {
System.out.println("Speed: " + speed + ", Model: " + model);
}
}
// Main class
public class Main {
public static void main(String[] args) {
// Creating an object of the subclass
Car carObj = new Car(120, "Sedan");
// Displaying details of the car
carObj.displayDetails();
}
}
Output:
Speed: 120, Model: Sedan
Explanation:
In this example, the super() keyword is used to call the base class (Vehicle) constructor with a parameter (speed). This call must be the first statement in the subclass (Car) constructor. Afterward, the model attribute is initialized, and the displayDetails() method prints the values of speed and model.
Key Points:
The base class constructor is always called before the subclass constructor.
The super() keyword is used to explicitly invoke the base class constructor and must be the first line in the subclass constructor.
If super() is not used, the default constructor of the base class is invoked automatically.
Multiple Inheritance
Multiple inheritance is an object-oriented concept where a class can inherit from more than one parent class. However, a problem arises when both parent classes have methods with the same signature. In such cases, the compiler is unable to decide which method to execute, leading to ambiguity. Java avoids these complexities by not supporting multiple inheritance through classes.
Note:
Java does not support multiple inheritance through classes, but it handles this scenario for interfaces using default methods introduced in Java 8.
Example 1:
// Java Program to Demonstrate Lack of Support for Multiple Inheritance
// First parent class
class Parent1 {
void display() {
System.out.println("Parent1");
}
}
// Second parent class
class Parent2 {
void display() {
System.out.println("Parent2");
}
}
// Class trying to inherit from both Parent1 and Parent2 (Invalid)
class Child extends Parent1, Parent2 {
public static void main(String[] args) {
Child obj = new Child();
obj.display(); // Ambiguity issue
}
}
Output:
Compilation Error
Explanation:
Java does not support multiple inheritance using classes. If you try to inherit from both Parent1 and Parent2, the compiler will throw an error because it cannot determine which display() method to call.
How Java Handles Multiple Inheritance with Interfaces
While Java doesn’t allow multiple inheritance through classes, it allows it through interfaces. Java 8 introduced default methods, which allow interfaces to provide default implementations of methods. If a class implements multiple interfaces with default methods that have the same signature, it must resolve the conflict by overriding the method.
Example 2: Handling Multiple Inheritance with Interfaces
// Interface 1
interface Interface1 {
default void show() {
System.out.println("Default Interface1");
}
}
// Interface 2
interface Interface2 {
default void show() {
System.out.println("Default Interface2");
}
}
// Implementation class that implements both interfaces
class MyClass implements Interface1, Interface2 {
// Overriding the show method to resolve ambiguity
@Override
public void show() {
Interface1.super.show(); // Explicitly calling Interface1's show method
Interface2.super.show(); // Explicitly calling Interface2's show method
}
// Methods to call specific interface methods
public void showInterface1() {
Interface1.super.show();
}
public void showInterface2() {
Interface2.super.show();
}
public static void main(String[] args) {
MyClass obj = new MyClass();
obj.show(); // Resolving ambiguity
System.out.println("Calling individual interface methods:");
obj.showInterface1();
obj.showInterface2();
}
}
In this example, MyClass implements both Interface1 and Interface2, which both have a default show() method. By overriding the show() method in MyClass, we use Interface1.super.show() and Interface2.super.show() to specify which interface’s method to call. Additionally, the class provides methods to call individual interface methods if needed.
Example 3: Diamond Problem with Default Methods in Interfaces
// Root interface with a default method
interface RootInterface {
default void display() {
System.out.println("Default RootInterface");
}
}
// First child interface extending RootInterface
interface ChildInterface1 extends RootInterface {
}
// Second child interface extending RootInterface
interface ChildInterface2 extends RootInterface {
}
// Class implementing both child interfaces
class MyClass implements ChildInterface1, ChildInterface2 {
public static void main(String[] args) {
MyClass obj = new MyClass();
obj.display(); // No ambiguity, RootInterface's method is called
}
}
Output:
Default RootInterface
Explanation:
In this case, both ChildInterface1 and ChildInterface2 extend the same RootInterface, which has a default display() method. Since neither ChildInterface1 nor ChildInterface2 overrides the method, there is no ambiguity, and the RootInterface method is called directly.
Interfaces and Inheritance in Java
In Java, a class can extend another class and implement one or more interfaces. This relationship has a significant role in understanding Java’s approach to Multiple Inheritance.
Key Concepts:
1. Inheritance Hierarchy: Java enforces a clear hierarchy, where a class can inherit from another class using the extends keyword, and implement interfaces using the implements keyword. The hierarchy cannot be reversed; that is, a class cannot extend an interface, as doing so would violate the core principles of class-based inheritance.
Example of Class Implementing Multiple Interfaces
// Java program to demonstrate that a class can
// implement multiple interfaces
interface InterfaceA {
void method1();
}
interface InterfaceB {
void method2();
}
// class implements both interfaces
// and provides implementation for the methods
class ExampleClass implements InterfaceA, InterfaceB {
@Override
public void method1() {
System.out.println("Executing method1 from InterfaceA");
}
@Override
public void method2() {
System.out.println("Executing method2 from InterfaceB");
}
}
public class Main {
public static void main(String[] args) {
ExampleClass obj = new ExampleClass();
// calling the methods implemented in the class
obj.method1();
obj.method2();
}
}
2. Interface Inheritance: In Java, an interface can extend another interface, allowing for inheritance of method signatures, which can later be implemented in classes.
Example of Interface Inheritance
// Interface inheritance demonstration
interface InterfaceA {
void display();
}
interface InterfaceB extends InterfaceA {
void show();
}
class DemoClass implements InterfaceB {
@Override
public void display() {
System.out.println("Display method from InterfaceA");
}
@Override
public void show() {
System.out.println("Show method from InterfaceB");
}
}
public class Main {
public static void main(String[] args) {
DemoClass obj = new DemoClass();
obj.display();
obj.show();
}
}
Output:
Display method from InterfaceA
Show method from InterfaceB
Types of Inheritance in Java
Java supports different types of inheritance, such as single, multilevel, and hierarchical inheritance, while restricting multiple and hybrid inheritance through interfaces.
1. Single Inheritance: When a class inherits from only one superclass.
class A {
int num1;
void setNum1(int x) {
num1 = x;
}
}
class B extends A {
int num2;
void setNum2(int y) {
num2 = y;
}
void displayProduct() {
System.out.println("Product: " + (num1 * num2));
}
}
public class Main {
public static void main(String[] args) {
B obj = new B();
obj.setNum1(3);
obj.setNum2(4);
obj.displayProduct();
}
}
Output:
Product: 12
2. Multilevel Inheritance: Involves a chain of inheritance where a class is derived from another derived class.
class A {
int num1;
void setNum1(int x) {
num1 = x;
}
}
class B extends A {
int num2;
void setNum2(int y) {
num2 = y;
}
}
class C extends B {
void displaySum() {
System.out.println("Sum: " + (num1 + num2));
}
}
public class Main {
public static void main(String[] args) {
C obj = new C();
obj.setNum1(5);
obj.setNum2(10);
obj.displaySum();
}
}
Output:
Sum: 15
3. Hierarchical Inheritance: Multiple classes inherit from the same superclass.
class A {
void greet() {
System.out.println("Hello from Class A");
}
}
class B extends A {
void greetFromB() {
System.out.println("Greetings from Class B");
}
}
class C extends A {
void greetFromC() {
System.out.println("Greetings from Class C");
}
}
public class Main {
public static void main(String[] args) {
B objB = new B();
C objC = new C();
objB.greet();
objB.greetFromB();
objC.greet();
objC.greetFromC();
}
}
Output:
Hello from Class A
Greetings from Class B
Hello from Class A
Greetings from Class C
Inheritance in Interfaces
Java interfaces can inherit from multiple interfaces, enabling classes to implement complex behavior while avoiding ambiguity.
Employee Name: John
Department: IT
Role: Developer
Association, Composition and Aggregation in Java
In object-oriented programming, relationships between classes are fundamental for defining interactions between objects. Java, as an object-oriented language, allows modeling these relationships using association, aggregation, and composition. These concepts describe how instances of classes relate and interact with one another.
Association
Association represents a general relationship between two independent classes, where one class may use or interact with another. This relationship can be one-to-one, one-to-many, many-to-one, or many-to-many, without implying ownership. Classes in association are independent of each other.
Types of Association
Unidirectional Association: One class knows and interacts with another, but the reverse is not true. For example, a Customer class may be associated with an Order class, but the Order class does not need to be aware of the Customer.
Bidirectional Association: Both classes are aware of and interact with each other. For example, a Player class and a Team class might be associated in a bidirectional relationship, where a player belongs to a team, and the team knows which players are on it.
Example of Association:
// Java program illustrating Association
import java.util.*;
class Library {
private String libraryName;
private Set<Book> books;
public Library(String libraryName) {
this.libraryName = libraryName;
}
public String getLibraryName() {
return this.libraryName;
}
public void setBooks(Set<Book> books) {
this.books = books;
}
public Set<Book> getBooks() {
return this.books;
}
}
class Book {
private String title;
public Book(String title) {
this.title = title;
}
public String getTitle() {
return this.title;
}
}
public class AssociationExample {
public static void main(String[] args) {
// Creating Book objects
Book book1 = new Book("Java Basics");
Book book2 = new Book("Advanced Java");
// Adding books to a set
Set<Book> bookSet = new HashSet<>();
bookSet.add(book1);
bookSet.add(book2);
// Creating a Library object
Library library = new Library("City Library");
// Setting books for the library
library.setBooks(bookSet);
// Displaying library books
for (Book book : library.getBooks()) {
System.out.println(book.getTitle() + " is available at " + library.getLibraryName());
}
}
}
Output:
Java Basics is available at City Library
Advanced Java is available at City Library
In this example, the Library and Book classes are associated, where a library contains multiple books, forming a one-to-many relationship.
Aggregation
Aggregation is a specialized form of association that represents a “has-a” relationship. In aggregation, one class (the whole) contains other classes (the parts), but the lifecycle of the parts is independent of the whole. For instance, a University may contain multiple Departments, but a department can exist independently of the university.
Example of Aggregation:
// Java program illustrating Aggregation
import java.util.*;
class Course {
private String courseName;
private int courseId;
public Course(String courseName, int courseId) {
this.courseName = courseName;
this.courseId = courseId;
}
public String getCourseName() {
return this.courseName;
}
public int getCourseId() {
return this.courseId;
}
}
class Department {
private String deptName;
private List<Course> courses;
public Department(String deptName, List<Course> courses) {
this.deptName = deptName;
this.courses = courses;
}
public List<Course> getCourses() {
return this.courses;
}
}
class University {
private String universityName;
private List<Department> departments;
public University(String universityName, List<Department> departments) {
this.universityName = universityName;
this.departments = departments;
}
public int getTotalCoursesInUniversity() {
int totalCourses = 0;
for (Department dept : departments) {
totalCourses += dept.getCourses().size();
}
return totalCourses;
}
}
public class AggregationExample {
public static void main(String[] args) {
// Creating Course objects
Course c1 = new Course("Data Structures", 101);
Course c2 = new Course("Algorithms", 102);
Course c3 = new Course("Operating Systems", 103);
// Creating lists of courses
List<Course> csCourses = Arrays.asList(c1, c2);
List<Course> itCourses = Arrays.asList(c3);
// Creating Department objects
Department csDept = new Department("Computer Science", csCourses);
Department itDept = new Department("Information Technology", itCourses);
// Creating a list of departments
List<Department> departments = Arrays.asList(csDept, itDept);
// Creating a University object
University university = new University("Tech University", departments);
// Printing total courses in university
System.out.println("Total courses in university: " + university.getTotalCoursesInUniversity());
}
}
Output:
Total courses in university: 3
In this example, a university contains multiple departments, each having courses. The university and department are in an aggregation relationship, where departments can exist independently of the university.
Composition
Composition is a strong form of association where the lifecycle of the contained objects is dependent on the container object. If the container object is destroyed, the contained objects are also destroyed. This represents a “part-of” relationship, like a Car and its Engine. If the car is destroyed, the engine also ceases to exist.
Example of Composition:
// Java program illustrating Composition
import java.util.*;
class Engine {
private String engineType;
public Engine(String engineType) {
this.engineType = engineType;
}
public String getEngineType() {
return this.engineType;
}
}
class Car {
private String carName;
private Engine engine;
public Car(String carName, String engineType) {
this.carName = carName;
this.engine = new Engine(engineType); // Composition
}
public String getCarDetails() {
return this.carName + " has an engine of type " + engine.getEngineType();
}
}
public class CompositionExample {
public static void main(String[] args) {
// Creating a Car object with composition
Car car = new Car("Tesla", "Electric");
// Displaying car details
System.out.println(car.getCarDetails());
}
}
Output:
Tesla has an engine of type Electric
In this example, the Car and Engine have a composition relationship. The Engine cannot exist without the Car, representing strong ownership.
Key Differences Between Association, Aggregation, and Composition
Feature
Association
Aggregation
Composition
Definition
General relationship between two classes
“Has-a” relationship
“Part-of” relationship
Dependency
Classes can exist independently
Parts can exist independently of the whole
Parts depend on the whole
Lifecycle
Independent lifecycles
Independent lifecycles
Dependent lifecycles
Ownership
No ownership implied
Shared ownership
Exclusive ownership
Strength
Weak
Moderate
Strong
These relationships play a key role in designing well-structured, reusable, and maintainable software systems.
Object Oriented Programming (OOPs) Concept in Java
Object-Oriented Programming (OOP) in Java refers to a programming approach where objects are used to represent and execute the code’s functionality. Objects are the core components, performing specific tasks as defined by the programmer.
OOP is designed to model real-world entities, such as inheritance, encapsulation, polymorphism, etc., in code. The primary objective is to bind data and the functions that operate on the data, ensuring that no other part of the program can directly access this data except through those functions.
Key OOP Concepts in Java
Java is built on the foundation of Object-Oriented Programming. Understanding key OOP concepts such as inheritance, encapsulation, abstraction, and polymorphism is critical to writing scalable and maintainable Java code.
Prerequisites
Before diving into the pillars of OOP, let’s review the essentials of method declaration and message passing in Java.
Method Declaration
A method in Java consists of six main components:
1. Access Modifier: Defines the accessibility of the method within the application. The common types are:
public: Accessible in all classes.
protected: Accessible within the package and in subclasses.
private: Accessible only within the class.
Default (no modifier): Accessible only within the same package.
2. Return Type: Specifies the data type returned by the method, or void if no value is returned. 3. Method Name: Follows the same naming conventions as fields, but typically starts with a lowercase letter. 4. Parameter List: The input parameters (if any), defined as a comma-separated list within parentheses. 5. Exception List: Specifies any exceptions that the method might throw. 6. Method Body: Contains the block of code that is executed when the method is called.
Message Passing
Objects in Java communicate with one another by sending messages, which involve calling methods. A message consists of the object’s name, the method to be executed, and any required information.
Java OOP Concepts
OOP in Java is centered around several key concepts:
1. Class: A class is a blueprint or prototype that defines the properties and behaviors common to all objects of a specific type. A class typically consists of fields (attributes) and methods (behaviors). In Java, a class can be defined as:
public class Employee {
// Fields
String name;
int salary;
// Methods
void setDetails(String n, int s) {
name = n;
salary = s;
}
void displayDetails() {
System.out.println("Name: " + name);
System.out.println("Salary: " + salary);
}
}
2. Object: An object is an instance of a class. It represents a specific entity in a Java program, with its own state and behavior.
public class Main {
public static void main(String[] args) {
Employee emp = new Employee();
emp.setDetails("John Doe", 50000);
emp.displayDetails();
}
}
Output:
Name: John Doe
Salary: 50000
The Four Pillars of OOP
1. Abstraction: Abstraction involves hiding unnecessary details and showing only the essential features of an object. In Java, abstraction is achieved using abstract classes and interfaces.
Example of an abstract class:
abstract class Vehicle {
abstract void start();
}
class Car extends Vehicle {
void start() {
System.out.println("Car is starting");
}
}
2. Encapsulation: Encapsulation is the practice of wrapping data and methods into a single unit (class). It restricts direct access to the data by making the fields private and providing public getter and setter methods to modify and access them.
class Person {
private String name;
public void setName(String name) {
this.name = name;
}
public String getName() {
return name;
}
}
3. Inheritance: Inheritance allows a class to inherit properties and methods from another class. It promotes reusability and a hierarchical class structure. Inheritance is achieved using the extends keyword.
class Animal {
void sound() {
System.out.println("Animal makes a sound");
}
}
class Dog extends Animal {
void sound() {
System.out.println("Dog barks");
}
}
4. Polymorphism: Polymorphism allows methods or objects to behave in different ways based on the context. It is of two types: compile-time (method overloading) and runtime (method overriding).
Method Overloading (Compile-time Polymorphism):
class Calculator {
int add(int a, int b) {
return a + b;
}
int add(int a, int b, int c) {
return a + b + c;
}
}
Advantages of OOP over Procedural Programming
Code Reusability: By using classes and objects, you can reuse code efficiently without repetition.
Code Organization: Object-oriented code is better structured, making it easier to understand and maintain.
DRY Principle: OOP promotes the “Don’t Repeat Yourself” principle by allowing common code to be reused.
Faster Development: OOP speeds up development by encouraging modular components that can be reused across projects.
Java Classes and Objects
In Java, classes and objects are core concepts of Object-Oriented Programming (OOP), used to represent real-world entities and behaviors. A class acts as a blueprint for creating objects, while an object is an instance of a class. For example, “Dog” can be considered a class, while a specific dog named “Bruno” would be an object of that class.
In this article, we’ll explore Java classes, objects, and how to implement them in your programs.
Java Classes
A class in Java is a collection of objects with common properties and behaviors. It is essentially a user-defined prototype from which objects are created. For instance, “Car” is a class, while “BMW” is an object.
Properties of Java Classes
A class is not a physical entity but a blueprint used to create objects.
A class itself does not occupy memory; only objects do.
It groups variables (attributes) and methods (behaviors) under a common structure.
A class in Java can include the following components:
In this example, the Student class has two data members, id and name, with default values 0 and null, respectively.
Components of Java Classes
A class declaration typically contains the following:
Modifiers: A class can be public or have default access.
Class keyword: Used to declare a class.
Class name: Starts with an uppercase letter by convention.
Superclass: The parent class (if any), specified with the extends keyword.
Interfaces: Any implemented interfaces, separated by commas and declared with the implements keyword.
Body: The class body is enclosed in curly braces {}.
Constructors
Constructors initialize new objects. They have the same name as the class and no return type. Fields (data members) define the state of objects, while methods define the behavior.
Java Objects
An object in Java is an instance of a class, representing a specific entity or concept. Each object has:
State: Represented by the attributes (data members) of the object.
Behavior: Represented by the methods of the object.
Identity: A unique identifier that allows interaction between different objects.
For example, in a shopping system, objects could include “ShoppingCart”, “Product”, and “Customer”.
Declaring and Initializing Java Objects
When an object is created, the class is instantiated. The values of the object’s attributes (state) are unique for each instance.
Dog myDog; // Declaration
myDog = new Dog(); // Instantiation
Example of an Object Initialization
public class Dog {
// Instance variables
String name;
String breed;
int age;
String color;
// Constructor
public Dog(String name, String breed, int age, String color) {
this.name = name;
this.breed = breed;
this.age = age;
this.color = color;
}
@Override
public String toString() {
return "My dog's name is " + name + ", breed: " + breed + ", age: " + age + ", color: " + color;
}
public static void main(String[] args) {
Dog myDog = new Dog("Buddy", "Golden Retriever", 3, "Golden");
System.out.println(myDog.toString());
}
}
Output:
My dog's name is Buddy, breed: Golden Retriever, age: 3, color: Golden
Memory Allocation in Java Objects
When an object is created, it is allocated memory on the heap. All classes have at least one constructor; if none is explicitly defined, Java provides a default constructor.
Methods to Create Objects
There are several ways to create objects in Java:
1. Using the new keyword:
Car myCar = new Car();
2. Using Class.forName(String className):
Car obj = (Car)Class.forName("com.example.Car").newInstance();
3. Using clone() method:
Car car1 = new Car();
Car car2 = (Car)car1.clone();
4. Using Deserialization:
ObjectInputStream in = new ObjectInputStream(new FileInputStream("data.obj"));
Car car = (Car)in.readObject();
Anonymous Objects
Anonymous objects are objects created without a reference and are often used for immediate method calls. These objects are destroyed after the method call completes.
new Car().drive();
Difference Between Class and Object
Class
Object
A blueprint for creating objects.
An instance of the class.
Does not occupy memory.
Occupies memory when instantiated.
Logical entity.
Physical entity.
Declared only once.
Multiple objects can be created from the same class.
Java Naming Conventions
In software development, especially in Java, writing clean and readable code is essential. One key aspect of this is following proper naming conventions. This practice ensures that your code is easy to read and understand, both for yourself and for other developers who may work on the same project. While at a smaller scale these conventions may not seem critical, they become increasingly important in large, industrial-level projects where maintaining clean, organized code can significantly reduce development time and confusion.
For example, when naming a variable meant to store displacement, a meaningful name like “displacement” should be used, rather than something unclear like “x” or “d”. As the project grows, meaningful names enhance readability and reduce errors.
Key Naming Guidelines
Classes: When naming a class, it should be a noun, reflecting the purpose it serves in the program. The name should describe what the class is about without needing to read its contents. Examples of good class names include AddNumbers, ReverseString, etc. Poor names would be generic or meaningless like A1 or Programming.
Interfaces: Interfaces usually describe capabilities and are often named with adjectives. For example, common Java interfaces like Runnable and Serializable describe behavior. It’s also a good practice to use the suffix “able” (e.g., Readable), though it’s not mandatory.
Methods: Methods, being actions, should be named as verbs. Their names should clearly describe what they do. For example, the main() method in Java signifies the starting point of the program and is a good method name conventionally.
Constants: Constants are fixed values and should be named in uppercase with words separated by underscores (_). Examples include PI, MAX_VALUE, MIN_VALUE.
Java Naming Conventions Overview
In Java, it is considered best practice to name classes, variables, and methods according to their intended functionality. This approach enhances the readability and maintainability of the code. Java follows the CamelCase convention for naming classes, methods, and variables, where words are written together without spaces, and each word after the first starts with an uppercase letter. For example, firstName or calculateArea.
However, there are a few exceptions:
Packages: Package names should be written in all lowercase, even if they contain multiple words.
Constants: Constants should be written in uppercase, with words separated by underscores.
Naming Conventions for Specific Java Elements
1. Classes and Interfaces
Classes: Class names should be nouns, using CamelCase, with each internal word starting with a capital letter. Avoid abbreviations or acronyms, and instead use meaningful words.Example: Student, Integer, DataProcessor
Interfaces: Interfaces should follow the same convention as classes, but should often reflect actions or behaviors, typically in the form of adjectives.Example: Runnable, Serializable
2. Methods
Method Names: Methods should be verbs, following the camelCase convention. The first word is in lowercase, and each subsequent word starts with an uppercase letter.Example: calculateTotal, findMaximum, printDetails
3. Variables
Variable Names: Variables should have meaningful names and be short but descriptive. They should not start with an underscore (_) or dollar sign ($), even though Java permits it. Single-character variable names (like i, j, k) should generally be reserved for temporary variables in loops or similar short-term uses.Example: int score, double interestRate, String username
4. Constants
Constant Names: Constants are named in all uppercase letters, with words separated by underscores (_).Example: MAX_LIMIT, PI, DEFAULT_TIMEOUT
5. Packages
Package Names: Package names should always be written in lowercase, with each part separated by dots (.). The first part of the package name should usually be the top-level domain name (e.g., com, org), followed by the company’s domain and additional parts.Example: com.example.projectname, org.utilities.math
Importance of Naming Conventions
Adopting proper naming conventions improves code clarity, making it easier for other developers to understand the purpose of each class, method, variable, and constant without having to examine the underlying implementation. Following these guidelines also helps avoid confusion and errors in larger codebases. Furthermore, adhering to established naming conventions is a sign of professionalism and helps teams maintain a consistent coding style across projects.
These conventions, though simple, are critical in producing clean, readable, and maintainable Java code—allowing teams to work more effectively and reducing the chances of misinterpretation or mistakes during development.
Methods in Java
A method in Java is a collection of statements designed to perform a specific task and, optionally, return a result. Methods can also execute tasks without returning any value. By using methods, Java allows code reuse, eliminating the need to rewrite similar logic multiple times. Unlike languages such as C, C++, or Python, every method in Java must belong to a class.
Key Points about Methods:
A method is similar to a function and is used to describe the behavior of an object.
It is a block of code that performs a particular task.
Syntax of a Method:
<access_modifier> <return_type> <method_name>(list_of_parameters) {
// body of the method
}
Advantages of Methods:
Code Reusability: Methods allow code to be written once and reused as needed, saving time and effort.
Code Optimization: Methods simplify complex operations and improve the structure of the code.
Method Declaration
A method declaration generally has six components:
1. Modifier: Defines the access level of the method (e.g., public, private, protected, default).
public: Accessible in all classes.
protected: Accessible within the class and its subclasses.
private: Accessible only within the defining class.
default: Accessible within the same class and package, with no modifier.
2. Return Type: Specifies the data type of the value returned by the method or void if no value is returned. 3. Method Name: The name given to the method, following the same rules as variable names but typically being a verb. It is required. 4. Parameter List: A comma-separated list of input parameters (preceded by their data types). If no parameters are needed, empty parentheses are used. This is optional. 5. Exception List: Specifies any exceptions the method can throw. This is optional. Method Body: The actual code executed by the method, enclosed in braces. This is also optional.
Types of Methods in Java
1. Predefined Methods: These are methods already provided by Java libraries, also known as standard or built-in methods. We can directly call these methods within our program.
2. User-defined Methods: Methods written by the programmer, tailored to specific tasks according to the program’s needs.
Ways to Create Methods in Java
1. Instance Methods: Access instance data using the object name. These methods are declared within a class.
void methodName() {
// method body
}
2. Static Methods: Access static data using the class name. These methods are declared with the static keyword.
static void methodName() {
// method body
}
Method Signature
A method signature consists of the method name and its parameter list (the number, type, and order of parameters). The return type and exceptions are not part of the method signature.
Example:
int max(int x, int y) // Method signature
In this example, the method max has two parameters of type int.
Naming a Method
The convention for naming methods in Java is to use verbs in lowercase or multi-word names starting with a verb. If the name contains multiple words, the first letter of each word after the first should be capitalized (CamelCase).
Rules:
Method names should be verbs and start with a lowercase letter.
If the method name contains more than one word, the first word must be a verb, followed by an adjective or noun.
In multi-word names, capitalize the first letter of each subsequent word (e.g., findMax, computeSum).
Method Calling
Methods need to be called to execute. They can be called in three situations:
The method completes all its statements.
The method reaches a return statement.
The method throws an exception.
Example:
class Calculator {
public int add(int a, int b) {
return a + b;
}
}
public class Main {
public static void main(String[] args) {
Calculator calc = new Calculator();
int result = calc.add(5, 10);
System.out.println("Sum: " + result);
}
}
Output:
Sum: 15
Passing Parameters to Methods
Parameters can be passed in two ways:
1. Passing Arrays: Arrays can be passed as arguments to methods.
2. Varargs (Variable Arguments): When the number of parameters is unknown, we can use the varargs feature by specifying the parameter type followed by an ellipsis (...).
Method Overloading
Method overloading allows multiple methods with the same name but different parameter lists to coexist in the same class. This enables methods to handle various types of inputs.
Memory Allocation for Methods
Method calls are implemented using a stack. When a method is invoked, a stack frame is created to store its arguments, local variables, and return value. Once the method completes, its stack frame is removed from the stack.
Example:
class Example {
private int number;
private String name;
// Getter and Setter methods
public int getNumber() { return number; }
public String getName() { return name; }
public void setNumber(int number) { this.number = number; }
public void setName(String name) { this.name = name; }
// Other methods
public void printDetails() {
System.out.println("Number: " + number);
System.out.println("Name: " + name);
}
}
public class Main {
public static void main(String[] args) {
Example example = new Example();
example.setNumber(42);
example.setName("Java Example");
example.printDetails();
}
}
Output:
Number: 42
Name: Java Example
Access Modifiers in Java
Access modifiers in Java are essential for controlling the visibility and accessibility of classes, constructors, methods, variables, and data members. They provide a way to enforce security and manage how components of a program can interact with each other based on the access modifier used. In this section, we’ll explore the types of access modifiers available in Java, along with examples that demonstrate their use.
Types of Access Modifiers in Java
There are four access modifiers in Java:
1. Default (No keyword required) 2. Private 3. Protected 4. Public
1. Default Access Modifier
If no access modifier is specified, the default modifier is used. Members with the default access modifier are accessible only within the same package.
Example:
// Java program demonstrating default access modifier
package package1;
class ExampleClass {
void showMessage() {
System.out.println("This is a default access modifier example.");
}
}
// Attempting to access a default class from another package
package package2;
import package1.*;
public class TestClass {
public static void main(String[] args) {
ExampleClass example = new ExampleClass(); // Compile-time error
example.showMessage();
}
}
Output:
Compile-time error
2. Private Access Modifier
The private keyword is used to restrict access to class members. Members declared as private are accessible only within the class where they are defined.
Example:
// Java program illustrating private access modifier
package package1;
class ExampleClass {
private void displayMessage() {
System.out.println("This is a private method.");
}
}
public class TestClass {
public static void main(String[] args) {
ExampleClass example = new ExampleClass();
example.displayMessage(); // Compile-time error
}
}
Output:
error: displayMessage() has private access in ExampleClass
example.displayMessage();
3. Protected Access Modifier
The protected keyword allows access to members within the same package and also in subclasses, even if they are in different packages.
Example:
// Package 1 - Parent class with protected method
package package1;
public class ParentClass {
protected void displayMessage() {
System.out.println("This is a protected method.");
}
}
// Package 2 - Subclass inheriting from ParentClass
package package2;
import package1.ParentClass;
public class ChildClass extends ParentClass {
public static void main(String[] args) {
ChildClass child = new ChildClass();
child.displayMessage();
}
}
Output:
This is a protected method.
4. Public Access Modifier
The public keyword gives the widest access. Members declared as public can be accessed from any class, in any package.
Example:
// Java program demonstrating public access modifier
package package1;
public class ExampleClass {
public void showMessage() {
System.out.println("This is a public method.");
}
}
// Accessing a public method from another package
package package2;
import package1.ExampleClass;
public class TestClass {
public static void main(String[] args) {
ExampleClass example = new ExampleClass();
example.showMessage();
}
}
Output:
This is a public method.
Key Points:
Default access is package-private, meaning it’s accessible only within the same package.
Private access is the most restrictive, limiting access only to the class in which the member is declared.
Protected access allows visibility in the same package and in subclasses across different packages.
Public access provides unrestricted access throughout the program.
Best Practices for Using Access Modifiers
Always prefer the most restrictive access level for class members. For example, use private unless there’s a specific reason to expose a member.
Avoid using public fields, as this breaks encapsulation. Instead, use getter and setter methods to control access to fields.
Algorithm for Using Access Modifiers in Java
1. Define the class: Create a class representing your object or functionality. 2. Declare instance variables: Define the data members that represent the object’s state. 3. Choose access modifiers:
Use private for variables that should not be accessible outside the class.
Use protected for variables that can be accessed by subclasses, even in different packages.
Use public for variables that need to be accessed from any part of the program. Implement getter and setter methods: Even for public variables, it’s good practice to use accessors and mutators to control how values are accessed and modified.
4. Test visibility: Ensure that your chosen access levels make sense for the structure and interaction of your classes.
Java Constructors
In Java, a constructor is a special type of method used to initialize an object. Constructors are called when an object of a class is created, allowing developers to set initial values for the object’s attributes.
Whenever you create an object using the new keyword, a constructor is invoked to allocate memory for the object and initialize it.
Understanding constructors can greatly enhance your Java programming skills, particularly when working with large applications. Mastery of constructors is crucial for developing scalable, maintainable code, serving as a stepping stone toward more advanced programming concepts.
Example of a Java Constructor
Below is a simple demonstration of how constructors work in Java:
// Java Program to demonstrate
// Constructor usage
public class MyClass {
// Constructor
MyClass() {
System.out.println("Constructor called");
}
public static void main(String[] args) {
// Creating an object which calls the constructor
MyClass obj = new MyClass();
}
}
Output:
Constructor called
Differences Between Constructors and Methods
Name: A constructor must have the same name as the class, whereas methods can have any name.
Return Type: Constructors do not have a return type, while methods can return values (or be void if they return nothing).
Invocation: Constructors are called only once when an object is created, whereas methods can be called multiple times.
Syntax Example of a constructor:
class MyClass {
// A constructor
MyClass() {
// Initialization code here
}
}
// Creating an object, which invokes the constructor
MyClass obj = new MyClass();
Output:
Constructor called
Differences Between Constructors and Methods
Name: A constructor must have the same name as the class, whereas methods can have any name.
Return Type: Constructors do not have a return type, while methods can return values (or be void if they return nothing).
Invocation: Constructors are called only once when an object is created, whereas methods can be called multiple times.
Syntax example of a constructor:
class MyClass {
// A constructor
MyClass() {
// Initialization code here
}
}
// Creating an object, which invokes the constructor
MyClass obj = new MyClass();
Why Do We Need Constructors?
Constructors are used to initialize objects at the time of creation. For example, if we think of a class that represents a box, it might have fields like length, breadth, and height. When an object of this class is created, it wouldn’t make sense for it to have undefined dimensions, so constructors ensure these values are set.
When Is a Constructor Called?
A constructor is called whenever an object is instantiated using the new keyword. Some rules for writing constructors include:
The constructor must have the same name as the class.
A constructor cannot be abstract, final, static, or synchronized.
Access modifiers can be used with constructors to control which classes can instantiate objects.
1. Default Constructor : A default constructor is one without parameters. If no constructor is defined explicitly, Java automatically provides a default constructor.
Example:
// Java Program to demonstrate Default Constructor
public class MyClass {
// Default Constructor
MyClass() {
System.out.println("Default constructor called");
}
public static void main(String[] args) {
MyClass obj = new MyClass();
}
}
Output:
Default constructor called
2. Parameterized Constructor : A parameterized constructor takes arguments, allowing you to set object properties with specific values at the time of creation.
Example:
// Java Program to demonstrate Parameterized Constructor
public class MyClass {
String name;
int id;
// Parameterized Constructor
MyClass(String name, int id) {
this.name = name;
this.id = id;
}
public static void main(String[] args) {
MyClass obj = new MyClass("John", 101);
System.out.println("Name: " + obj.name + ", ID: " + obj.id);
}
}
Output:
Name: John, ID: 101
3. Copy Constructor : A copy constructor is used to create a new object as a copy of an existing object. While Java does not provide a built-in copy constructor, you can create one manually by passing an object of the same class to the constructor.
Example:
// Java Program to demonstrate Copy Constructor
public class MyClass {
String name;
int id;
// Parameterized Constructor
MyClass(String name, int id) {
this.name = name;
this.id = id;
}
// Copy Constructor
MyClass(MyClass obj) {
this.name = obj.name;
this.id = obj.id;
}
public static void main(String[] args) {
// Creating an object using a parameterized constructor
MyClass obj1 = new MyClass("Alice", 202);
// Creating a new object using the copy constructor
MyClass obj2 = new MyClass(obj1);
System.out.println("Name: " + obj2.name + ", ID: " + obj2.id);
}
}
Output:
Name: Alice, ID: 202
Constructor Overloading
Just like methods, constructors can also be overloaded by defining multiple constructors with different parameter lists. This allows for flexibility when creating objects with varying initial values.
Example:
// Java Program to demonstrate Constructor Overloading
public class MyClass {
// Constructor with one parameter
MyClass(String name) {
System.out.println("Name: " + name);
}
// Constructor with two parameters
MyClass(String name, int age) {
System.out.println("Name: " + name + ", Age: " + age);
}
public static void main(String[] args) {
// Calling different constructors
MyClass obj1 = new MyClass("Alice");
MyClass obj2 = new MyClass("Bob", 25);
}
}
Output:
Name: Alice
Name: Bob, Age: 25
Inheritance in Java
Java Inheritance Overview
Inheritance is one of the core concepts in Object-Oriented Programming (OOP), allowing a class to inherit properties and behaviors (fields and methods) from another class. In Java, this mechanism enables the creation of new classes that reuse, extend, and modify the functionality of existing ones. A class that inherits from another class can access the fields and methods of the parent class, while also adding its own methods and fields.
Why Do We Need Java Inheritance?
Inheritance offers several benefits, such as promoting code reusability, method overriding (which helps in achieving runtime polymorphism), and abstraction, allowing us to work with classes without needing to know the full details of their implementations. By leveraging inheritance, developers can create flexible and easily maintainable code.
Key Benefits:
Code Reusability: Common code in the superclass can be reused in the child classes, reducing redundancy.
Method Overriding: Enables the child class to provide specific implementations for methods defined in the parent class, facilitating runtime polymorphism.
Abstraction: Hides complex details from the user and only shows relevant functionalities.
Important Terms
Class: A blueprint for creating objects that share common attributes and behaviors.
Super Class (Parent Class): The class whose properties are inherited by another class.
Sub Class (Child Class): The class that inherits from another class. It can have additional fields and methods specific to itself.
Reusability: One of the major advantages of inheritance, where existing fields and methods from a parent class can be used in the child class without duplicating code.
How to Implement Inheritance in Java?
Inheritance in Java is implemented using the extends keyword. This keyword allows the subclass to extend the functionality of the superclass.
Syntax:
class SubClass extends SuperClass {
// fields and methods
}
Example of Java Inheritance
In this example, we have a base class Vehicle and a derived class Car that extends the Vehicle class. The TestDrive class is used to demonstrate the usage of inheritance.
// Base class
class Vehicle {
public int speed;
public int fuel;
// Constructor
public Vehicle(int speed, int fuel) {
this.speed = speed;
this.fuel = fuel;
}
// Methods
public void accelerate(int increment) {
speed += increment;
}
public void brake(int decrement) {
speed -= decrement;
}
@Override
public String toString() {
return "Speed: " + speed + " km/h, Fuel: " + fuel + " L";
}
}
// Derived class
class Car extends Vehicle {
public int numberOfDoors;
// Constructor
public Car(int speed, int fuel, int numberOfDoors) {
super(speed, fuel); // Calling the constructor of the superclass
this.numberOfDoors = numberOfDoors;
}
// Additional method for the Car class
public void lockDoors() {
System.out.println("Doors locked.");
}
@Override
public String toString() {
return super.toString() + ", Doors: " + numberOfDoors;
}
}
// Driver class
public class TestDrive {
public static void main(String[] args) {
Car myCar = new Car(80, 50, 4);
System.out.println(myCar.toString());
}
}
Output:
Speed: 80 km/h, Fuel: 50 L, Doors: 4
In this example, when an object of Car is created, the fields and methods from the Vehicle class are inherited, allowing the car to access properties like speed and fuel. The toString() method is overridden in the Car class to display additional information about the car.
Types of Inheritance in Java
Java supports several types of inheritance, each with its own structure and use cases:
1. Single Inheritance: A subclass inherits from one superclass. 2. Multilevel Inheritance: A chain of classes where a subclass inherits from a superclass, and another class inherits from that subclass. 3. Hierarchical Inheritance: One superclass is inherited by multiple subclasses. 4. Multiple Inheritance (Through Interfaces): In Java, a class can implement multiple interfaces, which allows it to inherit features from multiple sources. Java doesn’t support multiple inheritance with classes directly to avoid complexity, but interfaces provide a way to achieve this. 5. Hybrid Inheritance: A combination of multiple and other forms of inheritance. Java supports this through interfaces and combinations of single and hierarchical inheritance. Example:
In the following example, Dog is a subclass that inherits from the Animal superclass.
// Superclass
class Animal {
public void sound() {
System.out.println("This is an animal sound.");
}
}
// Subclass
class Dog extends Animal {
public void bark() {
System.out.println("The dog barks.");
}
}
// Driver class
public class TestAnimal {
public static void main(String[] args) {
Dog myDog = new Dog();
myDog.sound(); // Inherited from Animal
myDog.bark(); // Defined in Dog
}
}
Output:
This is an animal sound.
The dog barks.
Abstraction in Java
Abstraction in Java refers to the practice of showing only the necessary details and functionalities to the user while hiding the underlying implementation. By doing so, unnecessary complexities are kept out of sight, making it easier to work with the code.
Real-World Example of Abstraction:
A remote control for a TV is a great example of abstraction. You press buttons to change channels or adjust the volume, without needing to understand the inner workings of the TV or how the remote communicates with it.
What is Abstraction in Java?
In Java, abstraction is implemented using abstract classes and interfaces. If you want complete (100%) abstraction, interfaces are the tool of choice.
Data Abstraction is the process of focusing on the important characteristics of an object while ignoring irrelevant details. It helps in defining the key behaviors and properties of an object, distinguishing it from others in a meaningful way.
Real-Life Example of Abstraction:
Imagine driving a car. You know pressing the accelerator increases the speed, and pressing the brakes slows it down, but you don’t need to understand the detailed mechanism behind it. This is how abstraction simplifies interaction.
Java Abstract Classes and Abstract Methods
An abstract class is defined with the abstract keyword.
An abstract method is declared without any implementation.
An abstract class can have both abstract methods and regular methods.
If a class contains abstract methods, it must be declared as abstract.
Abstract classes cannot be instantiated directly; they must be extended.
Abstract classes can have constructors, both parameterized and default.
How to Implement Abstraction in Java
1. Define an abstract class or interface to represent the abstraction. 2. Declare common behaviors and properties as abstract methods within the class or interface. 3. Extend the abstract class or implement the interface in concrete classes. 4. Provide the specific implementations for the abstract methods in the concrete classes. 5. Use these concrete classes to achieve the desired functionality in your program.
When to Use Abstract Classes and Methods?
Abstract classes are useful when you want to define a template that outlines the structure of an abstraction but leave some details to be implemented by subclasses. A common example is the Shape hierarchy in design or simulation software, where shapes like circles, squares, and triangles share some characteristics but differ in how they calculate their areas.
Java Abstraction Example
Example 1:
// Abstract class for Shape
abstract class Shape {
String color;
// Abstract methods
abstract double area();
public abstract String toString();
// Constructor for Shape
public Shape(String color) {
this.color = color;
}
// Concrete method
public String getColor() {
return color;
}
}
// Circle class extending Shape
class Circle extends Shape {
double radius;
public Circle(String color, double radius) {
super(color);
this.radius = radius;
}
@Override
double area() {
return Math.PI * Math.pow(radius, 2);
}
@Override
public String toString() {
return "Circle color is " + getColor() + " and area is: " + area();
}
}
// Rectangle class extending Shape
class Rectangle extends Shape {
double length, width;
public Rectangle(String color, double length, double width) {
super(color);
this.length = length;
this.width = width;
}
@Override
double area() {
return length * width;
}
@Override
public String toString() {
return "Rectangle color is " + getColor() + " and area is: " + area();
}
}
// Main class
public class Main {
public static void main(String[] args) {
Shape circle = new Circle("Red", 5.0);
Shape rectangle = new Rectangle("Blue", 4.0, 6.0);
System.out.println(circle.toString());
System.out.println(rectangle.toString());
}
}
Output:
Circle color is Red and area is: 78.53981633974483
Rectangle color is Blue and area is: 24.0
Using Interfaces for Abstraction
Interfaces provide a way to achieve complete abstraction in Java. They contain method declarations but no method implementations.
Example:
// Shape interface
interface Shape {
double calculateArea();
}
// Circle class implementing Shape interface
class Circle implements Shape {
private double radius;
public Circle(double radius) {
this.radius = radius;
}
@Override
public double calculateArea() {
return Math.PI * radius * radius;
}
}
// Rectangle class implementing Shape interface
class Rectangle implements Shape {
private double length, width;
public Rectangle(double length, double width) {
this.length = length;
this.width = width;
}
@Override
public double calculateArea() {
return length * width;
}
}
// Main class
public class Main {
public static void main(String[] args) {
Shape circle = new Circle(5.0);
Shape rectangle = new Rectangle(4.0, 6.0);
System.out.println("Area of Circle: " + circle.calculateArea());
System.out.println("Area of Rectangle: " + rectangle.calculateArea());
}
}
Output:
Area of Circle: 78.53981633974483
Area of Rectangle: 24.0
Advantages of Abstraction
1. Simplifies Code: Reduces complexity by hiding implementation details. 2. Increases Reusability: Encourages code reuse and avoids duplication. 3. Enhances Security: Provides essential details while hiding the rest. 4. Improves Maintainability: Easier to update and maintain. 5. Supports Modularity: Breaks down complex systems into smaller parts.
Disadvantages of Abstraction
1. Increased Complexity: May add unnecessary layers if not used properly. 2. Debugging Challenges: Can make debugging more difficult due to hidden layers. 3. Potential Performance Overhead: Additional abstraction layers might affect performance.
Encapsulation in Java
Encapsulation is a core principle of object-oriented programming (OOP) in Java. It refers to the bundling of data (variables) and methods (functions) that operate on that data into a single unit, typically a class. Encapsulation ensures that the internal implementation of a class is hidden from outside access, only exposing a public interface that allows interaction with the class through methods. This technique enhances security and data integrity, as the internal state of an object can only be modified in controlled ways.
In Java, encapsulation is achieved by declaring instance variables as private, which means they cannot be accessed directly from outside the class. To provide access to these variables, public getter and setter methods are used, which enable controlled access and modification of the private variables.
Key Aspects of Encapsulation
Data hiding: By using private variables, you ensure that the internal details of a class are hidden.
Access through methods: Public getter and setter methods allow controlled access to the variables.
Modularity: It promotes better design by keeping data and methods together in a single unit.
Security and validation: Setters can include validation logic to prevent invalid data from being assigned to variables.
Java Encapsulation Example
// Employee Class demonstrating encapsulation
class Employee {
// Private fields, not accessible outside the class
private String name;
private int id;
// Getter method for name
public String getName() {
return name;
}
// Setter method for name
public void setName(String name) {
this.name = name;
}
// Getter method for id
public int getId() {
return id;
}
// Setter method for id
public void setId(int id) {
this.id = id;
}
}
// Main class to test encapsulation
public class Main {
public static void main(String[] args) {
// Creating an object of the Employee class
Employee emp = new Employee();
// Using setter methods to assign values
emp.setName("Alice");
emp.setId(101);
// Using getter methods to retrieve and display values
System.out.println("Employee Name: " + emp.getName());
System.out.println("Employee ID: " + emp.getId());
}
}
Output:
Employee Name: Alice
Employee ID: 101
Advantages of Encapsulation
Data Hiding: Encapsulation prevents direct access to sensitive data, making it more secure.
1. Improved Flexibility: You can make instance variables read-only or write-only by providing only a getter or setter method, depending on the requirement. 2. Reusability: Encapsulated code is easier to maintain, reuse, and extend because it minimizes interdependencies. 3. Easy Testing: Since encapsulated code separates data and functions, it is easier to test individual components. 4. Adaptability: The internal implementation can change without affecting code that interacts with the class.
Disadvantages of Encapsulation
Increased Complexity: Encapsulation may lead to more boilerplate code, such as writing getter and setter methods for each private variable.
Difficult to Understand: Excessive use of encapsulation can make code more difficult to follow, especially if it adds layers of abstraction.
Limited Flexibility: Over-encapsulation can sometimes restrict the flexibility of your design, making it harder to quickly implement new features.
Java Encapsulation Example (Area Calculation)
Here’s another example that demonstrates encapsulation in a different context, where it is used to calculate the area of a rectangle:
// Rectangle Class to calculate area using encapsulation
class Rectangle {
// Private fields for length and breadth
private int length;
private int breadth;
// Constructor to initialize length and breadth
public Rectangle(int length, int breadth) {
this.length = length;
this.breadth = breadth;
}
// Getter for length
public int getLength() {
return length;
}
// Setter for length
public void setLength(int length) {
this.length = length;
}
// Getter for breadth
public int getBreadth() {
return breadth;
}
// Setter for breadth
public void setBreadth(int breadth) {
this.breadth = breadth;
}
// Method to calculate and print area
public void calculateArea() {
int area = length * breadth;
System.out.println("Area of the Rectangle: " + area);
}
}
// Main class to test encapsulation in rectangle class
public class Main {
public static void main(String[] args) {
// Creating a rectangle object
Rectangle rect = new Rectangle(5, 10);
// Accessing and modifying private fields via getters and setters
rect.setLength(8);
rect.setBreadth(12);
// Calculating and printing the area
rect.calculateArea();
}
}
Output:
Area of the Rectangle: 96
What are Interfaces in Java?
In Java, an interface is an abstract type used to specify a set of behaviors that a class must implement. It provides a blueprint for a class, defining static constants and abstract methods (methods without a body). An interface is primarily used to achieve abstraction and multiple inheritance. Traditionally, interfaces in Java could only contain abstract methods and final variables, but with the introduction of Java 8, interfaces can also contain default and static methods. Interfaces help define a “contract” or “is-a” relationship between classes, specifying the methods they must implement, without worrying about the internal details.
Syntax for Java Interfaces
To declare an interface in Java, use the interface keyword. By default, all methods declared in an interface are abstract and public, and all fields are public, static, and final.
A class that implements an interface must provide implementations for all the methods declared in the interface, using the implements keyword.
Features of Interfaces
1. Achieves total abstraction: An interface allows complete abstraction since it contains no method implementation. 2. Multiple inheritance: While Java does not support multiple inheritance with classes, an interface allows classes to implement multiple interfaces. 3. Loose coupling: Interfaces enable loose coupling between classes as they provide a level of abstraction. 4. Final variables: All variables declared in an interface are final, static, and public by default.
Relationship Between Class and Interface
In Java, a class can implement one or more interfaces, but an interface can only be implemented by a class, not the other way around. Classes must implement all the methods defined by the interfaces they implement. Additionally, interfaces can extend other interfaces, allowing for a flexible hierarchy of behavior definitions.
Example of a Simple Java Interface
interface Player {
int id = 10; // Constant by default
void move(); // Abstract method
}
class TestClass implements Player {
public void move() {
System.out.println("Player moves");
}
public static void main(String[] args) {
TestClass player = new TestClass();
player.move();
System.out.println("Player ID: " + Player.id);
}
}
Output:
Player moves
Player ID: 10
Example: Vehicle Interface
Consider the example of vehicles like bicycles and cars that have common functionalities. We can create a Vehicle interface to define the general behaviors that every vehicle must implement, and let each vehicle type provide its specific implementation.
interface Vehicle {
void changeGear(int gear);
void speedUp(int increment);
void applyBrakes(int decrement);
}
class Bicycle implements Vehicle {
int speed;
int gear;
public void changeGear(int newGear) {
gear = newGear;
}
public void speedUp(int increment) {
speed += increment;
}
public void applyBrakes(int decrement) {
speed -= decrement;
}
public void display() {
System.out.println("Speed: " + speed + " Gear: " + gear);
}
}
class Car implements Vehicle {
int speed;
int gear;
public void changeGear(int newGear) {
gear = newGear;
}
public void speedUp(int increment) {
speed += increment;
}
public void applyBrakes(int decrement) {
speed -= decrement;
}
public void display() {
System.out.println("Speed: " + speed + " Gear: " + gear);
}
}
public class Main {
public static void main(String[] args) {
Bicycle bike = new Bicycle();
bike.changeGear(2);
bike.speedUp(10);
bike.applyBrakes(3);
System.out.println("Bicycle state:");
bike.display();
Car car = new Car();
car.changeGear(3);
car.speedUp(20);
car.applyBrakes(5);
System.out.println("Car state:");
car.display();
}
}
Java doesn’t allow multiple inheritance with classes, but interfaces provide a way to achieve it. A class can implement multiple interfaces, inheriting behaviors from each one.
interface Machine {
void start();
}
interface Sound {
void playSound();
}
class Robot implements Machine, Sound {
public void start() {
System.out.println("Robot started.");
}
public void playSound() {
System.out.println("Robot playing sound.");
}
}
public class Main {
public static void main(String[] args) {
Robot r = new Robot();
r.start();
r.playSound();
}
}
Output:
Robot started.
Robot playing sound.
‘this’ reference in Java
In Java, this is a reference variable that refers to the current object instance. It helps in distinguishing between instance variables and parameters with the same name and can be used for various purposes such as invoking class methods, passing the current object as a parameter, and improving code readability.
Java this Reference Example:
In Java, this refers to the object on which a method or constructor is being invoked. It allows access to the instance variables and methods of the current object.
Here’s an example demonstrating the usage of this reference:
// Java program demonstrating the use of 'this' reference
class Employee {
String name;
int id;
// Constructor
Employee(String name, int id) {
this.name = name; // 'this' refers to the instance variable 'name'
this.id = id; // 'this' refers to the instance variable 'id'
}
// Method to display employee details
public void displayDetails() {
System.out.println("Employee Name: " + this.name);
System.out.println("Employee ID: " + this.id);
}
// Main method
public static void main(String[] args) {
Employee emp1 = new Employee("John", 101);
Employee emp2 = new Employee("Alice", 102);
emp1.displayDetails();
emp2.displayDetails();
}
}
Output:
Employee Name: John
Employee ID: 101
Employee Name: Alice
Employee ID: 102
Explanation:
In the above example, this is used in the constructor to refer to the instance variables name and id, differentiating them from the constructor’s parameters. The displayDetails method prints out the details of each employee using the this reference.
Different Uses of this in Java:
1. Referring to the Current Class Instance Variables :You can use this to differentiate between instance variables and parameters with the same name.
class Test {
int x;
Test(int x) {
this.x = x; // 'this' is used to differentiate the instance variable from the constructor parameter
}
}
2. Invoking the Current Class Constructor :this() can be used to call another constructor in the same class.
class Test {
int x;
Test() {
this(10); // Calls the parameterized constructor
}
Test(int x) {
this.x = x;
}
}
3. Returning the Current Class Instance: this can be used to return the current instance of the class.
class Test {
int x;
Test(int x) {
this.x = x;
}
Test getObject() {
return this; // Returns the current object
}
}
4. Passing the Current Class Instance as a Method Parameter: this can be passed as an argument to methods or constructors.
class Test {
void display(Test obj) {
System.out.println("Object received: " + obj);
}
void sendThis() {
display(this); // Passes the current object as a parameter
}
}
5. Invoking the Current Class Method:this can be used to call a method from within another method.
class Test {
void display() {
this.show(); // Calls the show method
}
void show() {
System.out.println("Inside show method");
}
}