Sitemap

Java Essentials: A Deep Dive into the java.lang Package

27 min readOct 11, 2024

--

Press enter or click to view image in full size

The java.lang package is one of the most fundamental packages in Java and is automatically imported into every Java program. It contains essential classes, interfaces, and methods that are commonly used in Java applications, making it the backbone of the Java language.

Key Characteristics of the java.lang Package

1. Default Package: The java.lang package is automatically available to all Java programs, so you don’t need to explicitly import it. This is why you can directly use classes like String, System, Math, and others without importing them.

2. Purpose: It provides a wide range of classes and interfaces that perform basic and essential operations, such as string manipulation, mathematical operations, system operations, and object manipulation.

3. Essential Classes: The classes in the java.lang package are fundamental to Java programming. These classes and interfaces provide critical functionalities that form the foundation for creating Java applications.

Example: Usage of java.lang Classes

Here’s a simple example demonstrating the use of some classes from the java.lang package:

package com.P1;
class LMS {
public static void main(String arg[]) {
Object o1 = new Object(); // java.lang.Object class object
String s1 = new String(); // java.lang.String class object
}
}

In the example above:

- We are creating an object of the Object class and the String class, both of which belong to the java.lang package.

- We do not need to import the java.lang.Object or java.lang.String classes, proving that the java.lang package is always available by default.

Important Classes, Interfaces, and Enums in java.lang

Here are some of the most commonly used classes, interfaces, and enums in the java.lang package:

1. java.lang.Object: The root class of the Java class hierarchy. Every class in Java directly or indirectly inherits from this class.

2. java.lang.String: Represents immutable sequences of characters. Widely used for string manipulations.

3. java.lang.StringBuffer: A thread-safe, mutable sequence of characters used for string manipulations.

4. java.lang.StringBuilder: A mutable sequence of characters, similar to StringBuffer but not synchronized, making it faster in single-threaded scenarios.

5. java.lang.System: Provides access to system resources, standard input/output, and other system-level properties.

6. java.lang.Runtime: Provides methods that interface with the Java runtime environment.

7. java.lang.Class: Used to obtain information about classes and objects at runtime.

8. java.lang.Throwable: The superclass of all errors and exceptions in Java.

9. java.lang.Exception: The base class for all checked exceptions.

10. java.lang.Error: Represents serious problems that a reasonable application should not try to catch.

11. java.lang.RuntimeException: Represents exceptions that can be thrown during the normal operation of the Java Virtual Machine (JVM).

12. java.lang.Math: Provides methods for performing basic numeric operations such as trigonometry, logarithms, square roots, and random number generation.

13. java.lang.Thread: Represents a thread of execution in a program.

14. java.lang.Runnable: An interface that should be implemented by any class whose instances are intended to be executed by a thread.

Wrapper Classes in java.lang

Java also provides wrapper classes for the eight primitive data types, and all these classes are part of the java.lang package. Wrapper classes allow you to treat primitive data types as objects.

1. java.lang.Byte: Wraps the primitive data type byte.

2. java.lang.Short: Wraps the primitive data type short.

3. java.lang.Integer: Wraps the primitive data type int.

4. java.lang.Long: Wraps the primitive data type long.

5. java.lang.Float: Wraps the primitive data type float.

6. java.lang.Double: Wraps the primitive data type double.

7. java.lang.Boolean: Wraps the primitive data type boolean.

8. java.lang.Character: Wraps the primitive data type char.

Why is java.lang a Default Package?

The java.lang package is included by default in every Java program because it contains the most fundamental classes and methods that are required for basic operations in Java. Without these classes, it would be difficult to perform even the simplest tasks in Java, such as:

- System Operations: Using the System class for input/output operations.

- String Manipulation: Using String, StringBuffer, and StringBuilder for handling text.

- Mathematical Calculations: Using the Math class for basic arithmetic operations.

- Object Methods: Using methods like toString(), equals(), and hashCode() from the Object class.

The availability of these classes by default ensures that Java developers have easy access to the essential tools needed to build applications without the hassle of manually importing basic classes.

java.lang.Object Class

Importance of java.lang.Object Class

- The java.lang.Object class is the root of the Java class hierarchy. Every class in Java implicitly inherits from the Object class unless it explicitly extends another class.

- This means that all classes in Java, directly or indirectly, are subclasses of Object.

Members of java.lang.Object Class

The java.lang.Object class contains several important methods that are frequently used in Java programming. Here is a detailed explanation of these methods:

1. public java.lang.Object()

- Type: Constructor

- Access Modifier: public

- Description: This is the default constructor of the Object class. It initializes a new instance of the class.

2. public final native java.lang.Class<?> getClass()

- Type: Final Instance Method

- Access Modifier: public

- Description: Returns the Class object associated with the object’s runtime class. This method is used to get information about the object such as its class name, superclass, interfaces, and other details.

3. public native int hashCode()

- Type: Instance Method

- Access Modifier: public

- Description: Returns a unique hash code for the object. The hash code is used for efficient storage and retrieval of objects in data structures like hash tables.

4. public boolean equals(Object obj)

- Type: Instance Method

- Access Modifier: public

- Description: Compares the current object with the specified object for equality. By default, it checks if both objects refer to the same memory location.

5. protected native Object clone() throws CloneNotSupportedException

- Type: Instance Method

- Access Modifier: protected

- Description: Creates and returns a copy (or clone) of the object. The clone() method is used to perform shallow copying.

6. public String toString()

- Type: Instance Method

- Access Modifier: public

- Description: Returns a string representation of the object. The default implementation of toString() prints the class name and the hash code in hexadecimal form.

7. public final native void notify()

- Type: Instance Method

- Access Modifier: public

- Description: Wakes up a single thread that is waiting on the object’s monitor (typically used in thread synchronization).

8. public final native void notifyAll()

- Type: Instance Method

- Access Modifier: public

- Description: Wakes up all threads that are waiting on the object’s monitor.

9. public final native void wait(long timeout) throws InterruptedException

- Type: Instance Method

- Access Modifier: public

- Description: Causes the current thread to wait until another thread invokes the notify() or notifyAll() methods for this object, or a specified amount of time has passed.

10. public final void wait(long timeout, int nanos) throws InterruptedException

- Type: Instance Method

- Access Modifier: public

- Description: Similar to the previous method, but allows for greater precision with the inclusion of nanoseconds.

11. protected void finalize() throws Throwable

- Type: Instance Method

- Access Modifier: protected

- Description: This method is called by the garbage collector before an object is destroyed to perform any cleanup activities.

Example Program Demonstrating Object Class Methods

class LMS {
public static void main(String arg[]) {
A a1 = new A();
System.out.println(a1.hashCode());
System.out.println(a1.toString());
}
}
class A {
}

Explanation:

- Since class A does not explicitly extend any other class, Object is its superclass by default.

- We create an object of class A using A a1 = new A(); and use it to call hashCode() and toString(), which are inherited from the Object class.

Exploring the getClass() Method

Example:

package com.P1;
interface I1 {}
interface I2 {}
interface I3 {}
class A implements I1, I2, I3 {}
class LMS {
public static void main(String arg[]) {
A a1 = new A();
Class c1 = a1.getClass();
System.out.println("----A.class file information----");
System.out.println("Class Name :- " + c1.getName());
System.out.println("Class Simple Name :- " + c1.getSimpleName());
System.out.println("Package Name :- " + c1.getPackage().getName());
System.out.println("Super Class Name :- " + c1.getSuperclass().getName());

System.out.println("---A.class interface information----");
Class<?>[] interfaces = c1.getInterfaces();
for (Class<?> iface : interfaces) {
System.out.println("Interface implemented by class A :- " + iface.getName());
}
}
}

Output:

----A.class file information----
Class Name :- com.P1.A
Class Simple Name :- A
Package Name :- com.P1
Super Class Name :- java.lang.Object
Super Class Package Name :- java.lang
---A.class interface information----
Interface implemented by class A :- com.P1.I1
Interface implemented by class A :- com.P1.I2
Interface implemented by class A :- com.P1.I3

Explanation:

  • This code demonstrates the use of the getClass() method to get detailed information about the A class, including its package, superclass, and interfaces.

Garbage collection in Java

Garbage collection in Java is an automatic memory management process that handles the cleanup of unused or unreferenced objects from the heap.

Key Concepts of Garbage Collection in Java

1. Dynamic Memory Allocation

- Java uses dynamic memory allocation, primarily in the heap area, where memory for objects is allocated when the new keyword is used.

- Unlike C/C++, where you have to manually deallocate memory using free() or delete, Java automates this cleanup process using garbage collection.

2. Garbage Collection Mechanism

- Purpose: Garbage collection is used to identify and dispose of objects that are no longer referenced, making their memory available for future allocation.

- The JVM automatically performs this task when it detects that memory resources are low or inefficiently utilized.

How JVM Identifies Unreferenced Objects

Here are two examples illustrating when an object becomes eligible for garbage collection:

Example 1

class LMS {
public static void main(String arg[]) {
A a1 = new A();
A a2 = new A();
a1 = null; // line - 7
}
}
class A {
}

- In this example, two objects of class A are created.

- When a1 is set to null at line 7, the first object becomes eligible for garbage collection because it no longer has a reference.

Example 2

class A {
static void m1() {
A a1 = new A();
A a2 = new A();
}
public static void main(String arg[]) {
m1();
}
}

- Here, the objects created inside the m1() method are local to that method.

- Once the m1() method execution completes, the local variables a1 and a2 go out of scope, and their objects in the heap become eligible for garbage collection.

When Does Garbage Collection Occur?

- The JVM triggers the garbage collection process when it detects that memory resources are low or when there’s a need to reclaim space for new object creation.

- Garbage collection is not guaranteed to run immediately upon request but is managed by the JVM based on memory availability and the CPU scheduler.

Important Garbage Collection Methods

1. System.gc()

- Syntax: public static void gc()

- This method suggests that the JVM should make an effort to start the garbage collection process.

- Internally, it calls Runtime.getRuntime().gc().

- Note: Calling System.gc() does not guarantee immediate garbage collection.

2. System.runFinalization()

- Syntax: public static void runFinalization()

- This method requests the JVM to run the finalization methods of objects that have been discarded but not yet finalized.

- Internally, it calls Runtime.getRuntime().runFinalization().

Steps Followed by JVM for Garbage Collection

1. Listing Unused Objects: The JVM first identifies objects that are no longer referenced.

2. gc() Method Invocation: If there is insufficient memory, the JVM calls the System.gc() method to start garbage collection.

3. Finalization: The JVM may also call runFinalization() to ensure that any necessary cleanup operations for discarded objects are completed.

Key Points to Remember

- Garbage collection is automatic in Java and does not require manual intervention like in languages such as C or C++.

- Although you can request garbage collection using System.gc(), the timing of the actual cleanup depends on the JVM’s memory management strategy.

- Understanding garbage collection is crucial for efficient memory management and to avoid memory leaks in Java applications.

Garbage collection ensures that Java applications run smoothly without running out of memory, helping developers focus on logic without worrying about memory cleanup.

Cloning

Please Refer this article

Cloning in Java is a technique to create a copy of an existing object. This process ensures that the new object is a separate instance with its own memory space, even though it may contain the same data as the original object. Java provides two main types of cloning: Shallow Cloning and Deep Cloning. Let’s explore both in detail with examples.

Shallow Cloning

Shallow Cloning creates a new object and copies the values of the instance variables from the original object to the new one. However, if the object contains references to other objects (nested objects), only the references are copied, not the actual objects. This means that the cloned object and the original object share references to the same memory locations for nested objects.

Example of Shallow Cloning

class A {
int a;
A(int a) {
this.a = a;
}
}
class B {
int b;
A a1;
B(int b, A a1) {
this.b = b;
this.a1 = a1;
}
}
class C implements Cloneable {
int c;
B b1;
C(int c, B b1) {
this.c = c;
this.b1 = b1;
}
C getClone() throws CloneNotSupportedException {
return (C) this.clone();
}
}
public class LMS {
public static void main(String arg[]) throws CloneNotSupportedException {
A a1 = new A(101);
B b1 = new B(202, a1);
C c1 = new C(303, b1);
C clone = c1.getClone();
System.out.println("c1.b1.a1.hashCode: " + c1.b1.a1.hashCode());
System.out.println("clone.b1.a1.hashCode: " + clone.b1.a1.hashCode());
}
}

Output:

c1.b1.a1.hashCode: 123456
clone.b1.a1.hashCode: 123456

In this example, c1.b1.a1 and clone.b1.a1 have the same hash code, indicating that both objects share the same reference. This is a clear indication of shallow cloning.

Deep Cloning

Deep Cloning involves creating a copy of the object as well as all objects that are referenced by the original object. It creates an entirely new instance of the object, including any nested objects, ensuring that the clone and the original object do not share any references.

Example of Deep Cloning

class A {
int a;
A(int a) {
this.a = a;
}
}
class B {
int b;
A a1;
B(int b, A a1) {
this.b = b;
this.a1 = a1;
}
}
class C implements Cloneable {
int c;
B b1;
C(int c, B b1) {
this.c = c;
this.b1 = b1;
}
@Override
protected Object clone() throws CloneNotSupportedException {
A clonedA = new A(this.b1.a1.a);
B clonedB = new B(this.b1.b, clonedA);
return new C(this.c, clonedB);
}
C getClone() throws CloneNotSupportedException {
return (C) this.clone();
}
}
public class LMS {
public static void main(String arg[]) throws CloneNotSupportedException {
A a1 = new A(101);
B b1 = new B(202, a1);
C c1 = new C(303, b1);
C clone = c1.getClone();
System.out.println("c1.b1.a1.hashCode: " + c1.b1.a1.hashCode());
System.out.println("clone.b1.a1.hashCode: " + clone.b1.a1.hashCode());
}
}

Output:

c1.b1.a1.hashCode: 123456
clone.b1.a1.hashCode: 789012

In this example, c1.b1.a1 and clone.b1.a1 have different hash codes, indicating that they are distinct objects. This demonstrates that deep cloning was successfully implemented.

Key Differences between Shallow and Deep Cloning

Press enter or click to view image in full size

Conclusion

- Shallow Cloning is useful when you are only concerned with creating a copy of the top-level object.

- Deep Cloning is necessary when the nested objects should also be independently cloned to avoid unwanted reference sharing.

In Java, deep cloning typically requires manually overriding the clone() method, as the default implementation only provides shallow cloning.

java.lang.String

The java.lang.String class in Java is a powerful and fundamental part of the Java programming language, used to create and manipulate strings. Here’s a detailed breakdown of its characteristics and important methods:

Key Characteristics of java.lang.String Class

1. Not a Primitive Data Type:

- String is not a primitive data type but a class in the java.lang package.

- It is one of the most commonly used classes in Java for handling sequences of characters.

2. Immutable Nature:

- String objects are immutable, meaning that once a String object is created, its value cannot be changed.

- If you perform operations like concatenation or modification on a String, a new object is created instead of modifying the existing one.

3. Final Class:

- The String class is declared as final, which means it cannot be extended or subclassed.

4. String Constant Pool (SCP):

- Java uses a special memory area called the String Constant Pool (SCP) to store string literals.

- If two strings have the same value, they share the same object in the SCP, which helps save memory.

5. String Creation:

- Strings can be created in two ways:

1. Using String Literals:

String s1 = "Hello";

2. Using the new Operator:

String s2 = new String("Hello");

String Creation and SCP Behavior

String Object Creation in Java

In Java, when you create String objects, the JVM treats them differently based on whether you use the new keyword or string literals.

1. String Literal Creation:

When you create a String using a string literal (e.g., String s3 = “Hello”;), the JVM checks the String Constant Pool (SCP) to see if an object with that value already exists.

- If it exists: It reuses that existing object.

- If it does not exist: A new String object is created in the SCP.

2. Using the new Operator:

When you use the new keyword (e.g., String s1 = new String(“Hello”);), the following steps occur:

- The JVM checks the SCP for the string literal “Hello”.

- If it exists: The existing object in the SCP is not used; instead, a new String object is created on the heap.

- If it does not exist: A new object is created both in the SCP and on the heap.

Example Breakdown

Consider the following code snippet:

class A {
public static void main(String arg[]) {
String s1 = new String("Hello");
String s2 = new String("Hello");
String s3 = "Hello";

System.out.println("s1 == s2 :- " + (s1 == s2));
System.out.println("s1 == s3 :- " + (s1 == s3));
System.out.println("s2 == s3 :- " + (s2 == s3));
}
}

Analysis of the Code:

- String s1 = new String(“Hello”);:

- A new String object is created on the heap.

- The SCP is checked, and since “Hello” might not exist there yet, it’s also added to the SCP.

- s1 refers to the heap object.

- String s2 = new String(“Hello”);:

- Again, a new String object is created on the heap.

- The SCP is checked, but since “Hello” already exists there (created by s1), it does not create a new SCP object.

- s2 refers to a different heap object than s1.

- String s3 = “Hello”;:

- The SCP is checked for “Hello”, which now exists.

- s3 refers to the String object in the SCP, not a new object.

Comparison Results

Now, when you perform comparisons:

- s1 == s2:

- s1 and s2 reference different objects (both created in the heap).

- Output: false

- s1 == s3:

- s1 references the heap object, while s3 references the SCP object.

- Output: false

- s2 == s3:

- s2 also references a different heap object than the SCP object referenced by s3.

- Output: false

Why are strings immutable in Java?

Strings in Java are immutable for several reasons:

1. Security: Prevents sensitive data from being altered. For example, if a password string is passed to a method, it can’t be changed accidentally within that method.

2. Hashing and Caching: Ensures consistent hash codes, which is crucial for using strings as keys in hash maps. For example, if String key = “example”; is used as a key, it will always hash the same way.

3. Thread Safety: Multiple threads can access the same string without concern for it being modified, making it easier to write safe concurrent code.

4. Performance Optimization: Allows string interning. For example, String s1 = “Hello”; and String s2 = “Hello”; will refer to the same object in memory, saving space.

5. Simplified Code: Code is easier to reason about. If a method returns a string, you can trust it won’t change after being returned.

Example:

String s1 = "Hello";
String s2 = s1.toUpperCase(); // s1 remains "Hello", s2 becomes "HELLO"

In this example, s1 is unchanged, demonstrating immutability in action.

Get Pratik T’s stories in your inbox

Join Medium for free to get updates from this writer.

Important Methods in java.lang.String

1. hashCode():

- The hashCode() method in the String class is overridden to generate a hash code based on the string’s content.

- Two strings with the same content will have the same hash code.

Example:

String s1 = new String("abc");
String s2 = new String("abc");
System.out.println(s1 == s2); // false
System.out.println(s1.hashCode()); // 96354
System.out.println(s2.hashCode()); // 96354

2. equals():

- The equals() method in the String class is overridden to compare the actual content of strings rather than their references.

Example:

String s1 = new String("abc");
String s2 = new String("abc");
System.out.println(s1 == s2); // false
System.out.println(s1.equals(s2)); // true

3. compareTo():

- The compareTo() method compares two strings lexicographically based on their Unicode values.

- It returns:

- 0 if the strings are equal.

- A negative number if the first string is lexicographically less than the second.

- A positive number if the first string is lexicographically greater than the second.

Example:

String s1 = "ABC";
String s2 = "ABC";
String s3 = "ACC";
String s4 = "ABCLMS";
System.out.println(s1.compareTo(s2)); // 0
System.out.println(s1.compareTo(s3)); // -1
System.out.println(s1.compareTo(s4)); // -3
System.out.println(s3.compareTo(s4)); // 1

4. split():

- The split() method splits a string into an array of substrings based on a specified delimiter.

Example:

String s1 = "Hello Welcome to the LMS Tutorial";
String[] ar1 = s1.split(" ");
for (int i = 0; i < ar1.length; i++) {
System.out.println("Index: " + i + " Value: " + ar1[i]);
}

Output:

Index: 0 Value: Hello
Index: 1 Value: Welcome
Index: 2 Value: to
Index: 3 Value: the
Index: 4 Value: LMS
Index: 5 Value: Tutorial

Immutability in Action

- When you concatenate strings, a new string is created:

String s1 = "Hello";
String s2 = "LMS";
String s3 = s1 + s2;
String s4 = "HelloLMS";
System.out.println(s3 == s4); // false

In this example, the comparison s3 == s4 evaluates to false because of how Java handles string creation and memory allocation.

1. String Creation:
— String s1 = “Hello”; creates a String literal in the String Constant Pool (SCP).
— String s2 = “LMS”; also creates another String literal in the SCP.
— String s3 = s1 + s2; creates a new String object in the heap, as it results from concatenation. Even though s3 will contain the value “HelloLMS”, it is a different object in memory.

2. String Literal:
— String s4 = “HelloLMS”; creates another String literal in the SCP.

Comparison:
— The expression s3 == s4 checks if both s3 and s4 reference the same object in memory.
— Since s3 is a new object created by concatenation and s4 is a literal in the SCP, they are two distinct objects.

System.out.println(s3.equals(s4)); // true

This will return true since both strings have the same content.

Using final makes the concatenated strings refer to the same object:

final String s1 = "Hello";
final String s2 = "LMS";
String s3 = s1 + s2;
String s4 = "HelloLMS";
System.out.println(s3 == s4); // true

In this example, the expression s3 == s4 evaluates to true due to the use of final keywords for s1 and s2. Here’s a detailed explanation:

1. Final Variables:
— The final keyword indicates that the variables s1 and s2 cannot be reassigned. This means their values are known at compile time.

2. String Concatenation:
— When you create String s3 = s1 + s2;, the Java compiler recognizes that both s1 and s2 are final. Therefore, it can optimize the concatenation at compile time.
— The compiler effectively replaces s1 + s2 with the literal ”HelloLMS” during the compilation process.

3. String Literal:
— String s4 = “HelloLMS”; creates a String literal in the String Constant Pool (SCP).

Memory Reference:
— Since both s3 (the result of the concatenation) and s4 (the literal) resolve to the same string literal ”HelloLMS” in the SCP, they reference the same memory object.

Comparison:
— The expression s3 == s4 checks if both references point to the same object.
— In this case, they do, so the result is true.

System.out.println(s3.equals(s4)); // true

StringBuffer

The StringBuffer class in Java, which is part of the java.lang package, is designed for mutable string manipulation. Here’s a breakdown of the key points regarding StringBuffer:

Key Characteristics of StringBuffer

1. Mutability: Unlike String objects, StringBuffer objects are mutable, meaning they can be modified after creation without creating a new object.

2. Memory Allocation: Objects of StringBuffer are stored in the heap area and do not use the string pool like String objects.

3. Final Class: StringBuffer is a final class, so it cannot be extended.

4. Thread-Safety: All methods in StringBuffer are synchronized, making it thread-safe, but this synchronization can lead to performance issues in a multi-threaded environment.

Creating StringBuffer Objects

- Default Constructor: When you create a StringBuffer using the default constructor, the initial capacity is set to 16.

StringBuffer s1 = new StringBuffer(); // Capacity is 16

- Custom Capacity: You can also specify a custom initial capacity.

StringBuffer s2 = new StringBuffer(20); // Capacity is 20

- With String Literal: If you create a StringBuffer with a string literal, the initial capacity is set to the length of the string plus 16.

StringBuffer s3 = new StringBuffer("LMS"); // Capacity is 19 (3 + 16)

Example of StringBuffer’s Mutable Behavior

Here’s a demonstration of how mutability works with the append() method:

class LMS {
public static void main(String arg[]) {
StringBuffer s1 = new StringBuffer("Hello");
StringBuffer s2 = s1.append("LMS");
System.out.println("s1 :- " + s1); // Output: HelloLMS
System.out.println("s2 :- " + s2); // Output: HelloLMS
System.out.println("s1 == s2 :- " + (s1 == s2)); // Output: true
}
}

In this example, both s1 and s2 reference the same object. Since StringBuffer objects are mutable, the append() operation modifies the original object itself.

Capacity and Growth Mechanism

The capacity of a StringBuffer can increase as more characters are added. The formula to calculate the new capacity when the current capacity is exceeded is:

New Capacity = (Current Capacity + 1) *2

Example of Capacity Growth

class LMS {
public static void main(String arg[]) {
StringBuffer s1 = new StringBuffer();
System.out.println("s1.capacity :- " + s1.capacity()); // 16
System.out.println("s1.length :- " + s1.length()); // 0
s1.append("1234567890111213");
System.out.println("s1.capacity :- " + s1.capacity()); // 16
System.out.println("s1.length :- " + s1.length()); // 16
s1.append("14");
System.out.println("s1.capacity :- " + s1.capacity()); // 34
System.out.println("s1.length :- " + s1.length()); // 18
s1.append("1516171819202122232425");
System.out.println("s1.capacity :- " + s1.capacity()); // 70
System.out.println("s1.length :- " + s1.length()); // 40
s1.append("1234567890123456789012345678901");
System.out.println("s1.capacity :- " + s1.capacity()); // 142
System.out.println("s1.length :- " + s1.length()); // 71
}
}

In this example, you can see how the capacity changes when the length of the StringBuffer exceeds its current capacity.

Important Methods of StringBuffer

1. length()

- Description: Returns the length of the string in the StringBuffer.

- Syntax: public synchronized int length();

2. capacity()

- Description: Returns the current capacity of the StringBuffer.

- Syntax: public synchronized int capacity();

3. append()

- Description: Appends the specified string to the existing StringBuffer and returns the same object.

- Syntax: public synchronized StringBuffer append(String str);

Performance Consideration

Although StringBuffer is thread-safe due to synchronization, this can cause performance issues in multi-threaded applications because only one thread can operate on a StringBuffer object at a time. To overcome this limitation, consider using StringBuilder, which is similar to StringBuffer but without synchronization, making it faster in single-threaded scenarios.

Use Cases

- Frequent String Modifications: Use StringBuffer when you need to perform frequent modifications to strings to avoid the overhead of creating multiple String objects.

- Thread Safety: If thread safety is required, StringBuffer is a better choice than StringBuilder.

Understanding when to use String, StringBuffer, or StringBuilder is crucial for writing efficient Java code, especially when dealing with string manipulation and concurrency.

StringBuilder class

The StringBuilder class in Java is a mutable sequence of characters, very similar to the StringBuffer class. It is part of the java.lang package and provides almost identical functionalities as StringBuffer but with a key difference in how it handles synchronization.

Key Characteristics of StringBuilder

1. Mutability: StringBuilder objects are mutable, meaning you can change the content of a StringBuilder object without creating a new object.

2. Thread-Unsafety: Unlike StringBuffer, StringBuilder is not synchronized, which means it is not thread-safe. This lack of synchronization makes StringBuilder faster than StringBuffer in single-threaded scenarios.

3. Memory Allocation: StringBuilder objects are stored in the heap area, similar to StringBuffer objects.

4. Final Class: StringBuilder is a final class, so it cannot be extended.

Creating StringBuilder Objects

- Default Constructor: When you create a StringBuilder using the default constructor, the initial capacity is set to 16.

StringBuilder s1 = new StringBuilder(); // Capacity is 16

- Custom Capacity: You can specify a custom initial capacity while creating a StringBuilder.

StringBuilder s2 = new StringBuilder(20); // Capacity is 20

- With String Literal: If you create a StringBuilder with a string literal, the initial capacity is set to the length of the string plus 16.

StringBuilder s3 = new StringBuilder("LMS"); // Capacity is 19 (3 + 16)

Example of StringBuilder’s Mutable Behavior

Here’s a demonstration of how mutability works with the append() method:

class LMS {
public static void main(String arg[]) {
StringBuilder s1 = new StringBuilder("Hello");
StringBuilder s2 = s1.append("LMS");
System.out.println("s1 :- " + s1); // Output: HelloLMS
System.out.println("s2 :- " + s2); // Output: HelloLMS
System.out.println("s1 == s2 :- " + (s1 == s2)); // Output: true
}
}

In this example, both s1 and s2 reference the same object. Since StringBuilder objects are mutable, the append() operation modifies the original object itself, and s1 and s2 remain equal.

Capacity and Growth Mechanism

The capacity of a StringBuilder can grow dynamically when more characters are added. The formula used to calculate the new capacity when the current capacity is exceeded is:

New Capacity = (Current Capacity + 1)*2

Example of Capacity Growth

class LMS {
public static void main(String arg[]) {
StringBuilder s1 = new StringBuilder();
System.out.println("s1.capacity :- " + s1.capacity()); // 16
System.out.println("s1.length :- " + s1.length()); // 0
s1.append("1234567890111213");
System.out.println("s1.capacity :- " + s1.capacity()); // 16
System.out.println("s1.length :- " + s1.length()); // 16
s1.append("14");
System.out.println("s1.capacity :- " + s1.capacity()); // 34
System.out.println("s1.length :- " + s1.length()); // 18
s1.append("1516171819202122232425");
System.out.println("s1.capacity :- " + s1.capacity()); // 70
System.out.println("s1.length :- " + s1.length()); // 40
s1.append("1234567890123456789012345678901");
System.out.println("s1.capacity :- " + s1.capacity()); // 142
System.out.println("s1.length :- " + s1.length()); // 71
}
}

In this example, you can see how the capacity of the StringBuilder changes as its length exceeds the current capacity, using the formula for dynamic growth.

Important Methods of StringBuilder

1. length()

- Description: Returns the length of the string contained in the StringBuilder.

- Syntax: public int length();

2. capacity()

- Description: Returns the current capacity of the StringBuilder.

- Syntax: public int capacity();

3. append()

- Description: Appends the specified string to the existing StringBuilder and returns the same object.

- Syntax: public StringBuilder append(String str);

StringBuilder vs. StringBuffer

- Synchronization: The primary difference between StringBuilder and StringBuffer is that StringBuilder methods are not synchronized, whereas StringBuffer methods are synchronized. This makes StringBuilder faster but not thread-safe.

- Use Case: Use StringBuilder when you do not require thread safety and want better performance. Use StringBuffer when working in a multi-threaded environment where synchronization is required.

Use Cases

- Frequent String Modifications: StringBuilder is ideal when you need to perform frequent string manipulations without the overhead of creating multiple String objects.

- Performance: It is preferred in single-threaded applications due to its better performance compared to StringBuffer.

java.lang.System class

The java.lang.System class is a crucial part of the Java programming language, providing methods and fields that interact with the system environment, manage input and output streams, perform system operations, and more. It is a part of the java.lang package and is automatically imported into all Java programs, making its methods and fields readily available for use.

Key Characteristics of the System Class

- Final Class: The System class is declared as final, which means it cannot be subclassed.

- Private Constructor: It has a private constructor, which means you cannot create an instance of the System class directly.

- Static Members: Most of the methods and fields of the System class are static, allowing them to be accessed directly through the class name without creating an object.

Example of System Class Usage

Here’s a basic example to illustrate the use of the System class:

class LMS {
public static void main(String arg[]) {
StringBuilder s1 = new StringBuilder("Hello");
StringBuilder s2 = s1.append("LMS");
System.out.println("s1 :- " + s1);
System.out.println("s2 :- " + s2);
System.out.println("s1 == s2 :- " + (s1 == s2));
}
}

Important Members of the System Class

The System class includes several fields and methods that are frequently used in Java programming. Let’s go through some of the most important members:

1. public static final java.io.InputStream in

- Description: The in field represents the standard input stream, typically linked to the keyboard.

- Usage: This field is used to take input from the user via the console.

Example:

import java.util.Scanner;
class A {
public static void main(String arg[]) {
System.out.println("Enter Input: ");
Scanner s1 = new Scanner(System.in);
String input = s1.nextLine();
System.out.println("Input is: " + input);
}
}

2. public static void setIn(java.io.InputStream)

- Description: This method allows you to change the standard input stream to another source.

- Usage: It is used when you need to take input from a file or other source instead of the keyboard.

Example:

import java.io.FileInputStream;
class LMS {
public static void main(String[] args) throws Exception {
System.setIn(new FileInputStream("file.txt"));
char c1 = (char) System.in.read();
System.out.println(c1);
}
}

3. public static final java.io.PrintStream out

- Description: The out field represents the standard output stream, usually linked to the console.

- Usage: It is used to print output to the console using methods like print() and println().

Example:

System.out.println("This text will appear on the console.");

How System.out.println works?

System.out.println() is a commonly used method in Java for printing output to the console. Here’s how it works, step by step:

Breakdown of System.out.println()

1. System Class:

- System is a final class in the java.lang package that provides various system-related utilities and resources, including standard input and output.

2. Out Field:

- out is a static field of type PrintStream within the System class. It represents the standard output stream (typically the console).

3. PrintStream Class:

- PrintStream is a class that provides methods for printing data to various output streams. It has several overloaded println() methods that accept different types of arguments (e.g., String, int, double, etc.).

4. println() Method:

- The println() method in PrintStream prints the argument passed to it, followed by a newline character. This means that after the output, the cursor moves to the next line.

- If no argument is provided (i.e., println() with no parameters), it simply moves to the next line.

How It Works Internally

1. Method Call:

- When you call System.out.println(someValue), the method is invoked on the PrintStream object referenced by System.out.

2. Type Handling:

- The println() method can take various data types:

- If you pass a String, it prints the string.

- If you pass a primitive type (like int, double, etc.), it converts the value to its string representation using String.valueOf().

- If you pass an object, it calls the object’s toString() method to get its string representation.

3. Output to Console:

- The formatted string is sent to the output stream that PrintStream is connected to, which is typically the console.

- After printing the string, a newline character (\n) is automatically appended.

4. public static void setOut(java.io.PrintStream)

- Description: This method changes the destination of the output stream.

- Usage: It is used to redirect output to a file or another destination instead of the console.

Example:

import java.io.*;
class LMS {
public static void main(String[] args) throws Exception {
FileOutputStream f = new FileOutputStream("file.txt");
System.setOut(new PrintStream(f));
System.out.println("This text will be redirected to file.txt.");
}
}

5. public static final java.io.PrintStream err

- Description: The err field represents the standard error output stream, used to output error messages.

- Usage: It is often used to display error messages or debug information.

Example:

System.err.println("This is an error message.");

6. public static void setErr(java.io.PrintStream)

- Description: This method allows you to change the destination of the error output stream.

- Usage: It is used to redirect error messages to a file or another output stream.

Example:

import java.io.*;
class LMS {
public static void main(String[] args) throws Exception {
FileOutputStream f = new FileOutputStream("file.txt");
System.setErr(new PrintStream(f));
System.err.println("This error message will be written to file.txt.");
}
}

7. public static void exit(int)

- Description: This method terminates the currently running Java Virtual Machine (JVM).

- Usage: It is often used to exit a program when a certain condition is met.

Example:

class A {
public static void main(String arg[]) {
System.out.println("Before Exit()");
System.exit(0);
System.out.println("This line will not be executed.");
}
}

8. public static native void arraycopy(Object src, int srcPos, Object dest, int destPos, int length)

- Description: This method copies data from one array to another.

- Usage: It is used for quickly copying array elements.

Example:

class A {
public static void main(String arg[]) {
String source[] = {"Welcome", "To", "LMS", "Tutorial", "This", "Is", "System", "Class"};
String destination[] = new String[8];
System.arraycopy(source, 1, destination, 1, 4);
for (int i = 0; i < destination.length; i++) {
System.out.println("Index: " + i + " Value: " + destination[i]);
}
}
}

Other Notable Methods

- currentTimeMillis(): Returns the current time in milliseconds since the epoch (January 1, 1970, 00:00:00 GMT).

- nanoTime(): Returns the current time in nanoseconds for measuring time intervals.

- gc(): Requests the Java Virtual Machine to perform garbage collection.

- getProperty(String key): Retrieves system properties.

- load(String filename): Loads a specified file as a dynamic library.

Conclusion

The java.lang.System class is a powerful utility class in Java that provides essential methods and fields for interacting with the system environment, handling input and output streams, and performing other system-level operations. Its static fields and methods enable developers to control system I/O, manage properties, and handle system resources efficiently.

java.lang.Runtime

1. java.lang.Runtime Class Overview
The Runtime class is a built-in class provided by Java in the java.lang package. It allows Java applications to interface with the runtime environment in which they are executing. This class is based on the Singleton Design Pattern, which ensures that only one instance of the class can exist in the program.

Singleton Design Pattern: This design pattern restricts the instantiation of a class to a single instance. It ensures that no matter how many times you try to create an object, the same instance is returned.

Private Constructor
: The Runtime class has a private constructor to prevent the instantiation of objects outside the class. Instead, it provides a static method getRuntime() to return the same Runtime instance every time it is called.

Singleton Design Pattern Example
Let’s break down the example provided:

class LMS {
public static void main(String arg[]) {
A a1 = A.getA();
A a2 = A.getA();
A a3 = A.getA();
System.out.println("a1 hashcode :- " + a1.hashCode());
System.out.println("a2 hashcode :- " + a2.hashCode());
System.out.println("a3 hashcode :- " + a3.hashCode());
System.out.println("a1 == a2 :- " + (a1 == a2));
System.out.println("a2 == a3 :- " + (a2 == a3));
System.out.println("a1 == a3 :- " + (a1 == a3));
}
}
class A {
static A a1 = null;
// Private constructor prevents instantiation outside this class
private A() {}
// Static block to create a single instance
static {
a1 = new A();
}
// Static method to return the same instance
static A getA() {
return a1;
}
}

Explanation:
— The class A follows the Singleton Design Pattern. The constructor is private, so no new object can be created using new A().
— In the static block, a single instance of the class A is created and assigned to the static variable a1.
— The method getA() returns the same instance a1 every time it’s called.
— Since a1, a2, and a3 all point to the same object, their hashcodes are identical, and the comparison using == returns true.

Output:
a1 hashcode :- 1652149987
a2 hashcode :- 1652149987
a3 hashcode :- 1652149987
a1 == a2 :- true
a2 == a3 :- true
a1 == a3 :- true

2. Runtime.getRuntime() Method

The Runtime class is based on the Singleton pattern, and you can’t create its object using new. Instead, you call getRuntime() to get the single instance.

class LMS {
public static void main(String arg[]) {
Runtime r1 = Runtime.getRuntime();
System.out.println("r1 hashcode :- " + r1.hashCode());
Runtime r2 = Runtime.getRuntime();
System.out.println("r2 hashcode :- " + r2.hashCode());
System.out.println("r1 == r2 :- " + (r1 == r2));
}
}

Explanation:
— Runtime.getRuntime() is used to get the Runtime object. Every time you call it, it returns the same object (due to Singleton pattern).
— r1 and r2 refer to the same object, so the comparison using == returns true.

Output:
r1 hashcode :- 123456789
r2 hashcode :- 123456789
r1 == r2 :- true

3. Key Methods in java.lang.Runtime

a. availableProcessors()
This method returns the number of available processors for the current Java Virtual Machine (JVM).

class LMS {
public static void main(String arg[]) {
System.out.println("Number of processors allocated to the current JVM: ");
System.out.println(Runtime.getRuntime().availableProcessors());
}
}

Explanation:
— Runtime.getRuntime().availableProcessors() returns the number of processors available to the JVM.

Example Output:
Number of processors allocated to the current JVM: 8

b. freeMemory()
This method returns the amount of free memory available in the JVM’s heap, measured in bytes.

class LMS {
public static void main(String arg[]) {
System.out.println("Free heap memory: " + Runtime.getRuntime().freeMemory() + " bytes");
}
}

Explanation:
— Runtime.getRuntime().freeMemory() returns the amount of unused heap memory in the JVM, in bytes.

Example Output:

Free heap memory: 16548960 bytes

c. totalMemory()
This method returns the total memory allocated to the JVM’s heap in bytes.

class LMS {
public static void main(String arg[]) {
System.out.println("Total heap memory: " + Runtime.getRuntime().totalMemory() + " bytes");
}
}

Explanation:
— Runtime.getRuntime().totalMemory() returns the total memory allocated to the heap.

Example Output:
Total heap memory: 25690112 bytes

d. maxMemory()
This method returns the maximum memory the JVM will attempt to use for the heap.

class LMS {
public static void main(String arg[]) {
System.out.println("Maximum heap memory limit: " + Runtime.getRuntime().maxMemory() + " bytes");
}
}

Explanation:
— Runtime.getRuntime().maxMemory() returns the maximum amount of memory that the JVM can use.

Example Output:
Maximum heap memory limit: 536870912 bytes

e. exec(String command)
This method allows running system commands directly from Java.

class LMS {
public static void main(String arg[]) throws Exception {
Runtime.getRuntime().exec("notepad");
Runtime.getRuntime().exec("calc");
}
}

Explanation:
— Runtime.getRuntime().exec() can execute commands like opening Notepad or Calculator on Windows.

Conclusion

The java.lang package is an integral part of Java's core functionality. It provides the basic classes and interfaces that form the foundation of Java programming. Understanding the methods in the java.lang.Object class and other classes within this package is crucial for writing efficient and robust Java applications.

--

--

Pratik T
Pratik T

Written by Pratik T

Full Stack Developer skilled in Machine Learning, Deep Learning, Computer Vision, and NLP. Committed to making learning accessible to all.