I'm always excited to take on new projects and collaborate with innovative minds.

Email

contact@niteshsynergy.com

Website

https://www.niteshsynergy.com/

Java->OOP->FC->WC->IC->GC->String->EH->

OOP→

 

FC→

✅ Java.lang Package – Cleaned & Modernized Study Notes

🌟 Introduction to the java.lang Package in Java

In Java, some of the most essential and frequently used classes and interfaces are grouped under the java.lang package. This package is fundamental to Java development, as it provides core functionalities that are crucial for any Java program, including object manipulation, string operations, memory management, and number handling.

✅ Key Points:

  • The java.lang package is automatically available to all Java programs, so there's no need to explicitly import it.
  • It contains the building blocks of Java programming, and you’ll find yourself using its classes in almost every project you work on.

📘 Major Classes in java.lang

Below are some of the most important and commonly used classes in the java.lang package:

1️⃣ Object Class

  • The root of the class hierarchy in Java.
  • Every class in Java implicitly inherits from Object.
  • Commonly used methods: equals(), hashCode(), toString(), clone(), getClass(), and finalize().

Read below details as each method then follow next step as below:-

public Object() {} – The Default Constructor

This is the only constructor defined in the Object class in Java.

  • It is a zero-argument (no-arg) constructor.
  • It is public, so any subclass or other class can call it (either explicitly or via super()).
  • This constructor does not do anything directly, but it enables the creation of a new object of the class or its subclasses.

🤔 Why is there only one constructor in the Object class?

The Object class is the root class of the Java class hierarchy. Every class in Java, whether directly or indirectly, inherits from Object.

Reasons for having only a default constructor:

  1. Simplicity & Universality:
    • Object doesn’t need any initial state or fields.
    • All other classes can inherit from it and define their own constructors.
  2. Inheritance Chain Requirement:
    • If a subclass doesn’t explicitly call super(...), the Java compiler automatically inserts a call to super() (i.e., the no-arg constructor).
    • Having a no-arg constructor ensures every class can safely inherit and compile.
  3. Design Philosophy:
    • Object is designed to be lightweight and abstract.
    • It doesn't assume any specific data; it's meant to be extended.

🔍 What is @IntrinsicCandidate?

@IntrinsicCandidate is a JVM internal annotation, introduced in newer Java versions (starting from Java 15+), used mainly by the HotSpot VM (Java's runtime engine).

  • It marks methods that the JVM may optimize using intrinsic (native) instructions.
  • In this case, the JVM may recognize the Object() constructor as a candidate for low-level optimization when objects are created.

➡️ You can ignore this annotation in normal Java development—it’s not intended for use in user code.

 

🧠 Summary:

AspectDescription
public Object()The only constructor in Object class – no arguments, does nothing directly.
Why only one constructor?Simplicity, support for inheritance, and because Object has no state.
@IntrinsicCandidateJVM hint for optimizing object creation.

✅ 1. @IntrinsicCandidate public native int hashCode();

🔍 What is hashCode()?

  • hashCode() is a native method in Java that returns an integer hash code for the object.
  • It's defined in Object class as:

    public native int hashCode ()
  • native keyword means:
    → Its implementation is not in Java, but in C/C++ and is handled by the JVM.

💡 Why is it needed?

  • It is primarily used in hash-based collections like:
    • HashMap
    • HashSet
    • Hashtable
  • These collections use the hash code to quickly find or store objects.

🔧 How does it work (default behavior)?

  • By default, hashCode() returns a number that is typically derived from the object’s memory address.

    System.out.println( new Object ().hashCode()); // some int like 12345678   
  • But you can override it in your class to provide a custom logic, especially when overriding equals().

✅ 2. public boolean equals(Object obj) { return (this == obj); }

🔍 What is equals()?

  • The equals(Object obj) method is used to compare two objects for equality.
  • This is the default implementation in the Object class.

💡 What does this == obj mean?

  • == checks whether both references point to the exact same object in memory.
  • So, equals() by default is reference comparison, not content comparison.

Example:

String a = new String ( "test" ); String b = new String ( "test" ); System.out.println(a == b);         // false (different objects) System.out.println(a.equals(b));    // true (overridden in String to compare content)   
  • If you don’t override equals(), your class will compare by reference.
  • If you override equals(), you can compare based on content or business logic.

 

🔄 hashCode() & equals() – Together

These two are tightly coupled in Java, especially in hash-based collections.

✅ Contract:

  • If two objects are equal (equals() returns true), then they must have the same hash code.
  • But the reverse is not always true — two objects can have the same hash code but not be equal (called a hash collision).

🧪 Example:

class Employee {     int id;    String name;     public Employee ( int id, String name) {         this .id = id;         this .name = name;    }     @Override      public boolean equals (Object obj) {         if ( this == obj) return true ;         if (obj == null || getClass() != obj.getClass()) return false ;         Employee e = (Employee) obj;         return id == e.id && name.equals(e.name);    }     @Override      public int hashCode () {         return id + name.hashCode();    } } 

Now, new Employee(1, "Nitesh") and new Employee(1, "Nitesh") will be treated as equal and will have the same hash code — perfect for use in HashSet.


🧠 Summary

MethodTypeDefault BehaviorCan be Overridden?Used In
hashCode()NativeReturns int based on memory address✅ YesHashMap, HashSet
equals()JavaCompares object references (this == obj)✅ YesComparing objects

 

🚫 When You Don't Override hashCode() and equals() in a Child Class

📍 By default:

If your custom class does not override equals() and hashCode(), it inherits them from the Object class, where:

  • equals() → compares object references (memory addresses)
    i.e. obj1 == obj2
  • hashCode() → returns a hash based on memory address, via a native implementation in the JVM.

 

📦 Example:

class Product {     int id;    String name;     public Product ( int id, String name) {         this .id = id;         this .name = name;    } } public class Main {     public static void main (String[] args) {         Product p1 = new Product ( 101 , "Laptop" );         Product p2 = new Product ( 101 , "Laptop" );        System.out.println(p1 == p2);             // false (different objects)         System.out.println(p1.equals(p2));        // false (reference comparison)         System.out.println(p1.hashCode());        // e.g. 31245678         System.out.println(p2.hashCode());        // e.g. 87654321     } } 

Even though p1 and p2 have the same content, they are not considered equal and have different hash codes, because we didn’t override equals() and hashCode().

 

🔗 Relationship (Context) Between Them When Not Overridden:

Conditionequals() BehaviorhashCode() BehaviorOutcome
Not OverriddenCompares memory address (==)Returns address-based hash code❌ Not suitable for logical content comparison
In Collections like HashMapObjects with same content are not matched unless same referenceKeys will be missed or duplicated unexpectedly❌ Broken behavior

 

📉 Real-World Problem Without Overriding:

 
Set<Product> set = new HashSet <>(); set.add( new Product ( 101 , "Laptop" )); set.add( new Product ( 101 , "Laptop" )); System.out.println(set.size()); // Output: 2 (even though same content!)   

Why?
HashSet calls:

  1. hashCode() to find the bucket
  2. equals() to check equality

But both are comparing reference, so it thinks they are different objects.


✅ When You Should Override

To make objects compare by logical content, override both:

  • equals() → define logical equality
  • hashCode() → generate hash code based on significant fields
@Override public boolean equals (Object o) {     if ( this == o) return true ;     if (o == null || getClass() != o.getClass()) return false ;     Product product = (Product) o;     return id == product.id && name.equals(product.name); } @Override public int hashCode () {     return Objects.hash(id, name); } 

Now:

Product p1 = new Product ( 101 , "Laptop" ); Product p2 = new Product ( 101 , "Laptop" ); System.out.println(p1.equals(p2)); // true System.out.println(p1.hashCode() == p2.hashCode()); // true   

🧠 Final Summary: Context When Not Overridden

AspectWithout Overriding
equals() behaviorCompares by reference only (==)
hashCode() behaviorJVM-generated hash based on object memory
Logical comparison❌ Fails — even if content is same
Hash-based collections❌ Unreliable behavior — duplicates possible
When to override✅ Always override both when using custom objects in collections or comparing by content

 

 

✅ 1. What is getClass()?

🔍 Definition:

  • getClass() is a final native method defined in the Object class.
  • It returns a Class<?> object that represents the runtime class of the current object.

🔁 Syntax:

Class<?> cls = obj.getClass(); 

🔬 Breakdown of the Declaration

KeywordMeaning
@IntrinsicCandidateJVM-level hint for intrinsic/native optimization (low-level feature)
publicAccessible from anywhere
finalCannot be overridden by any subclass
nativeImplemented in native code (like C/C++), not Java
Class<?>Generic return type indicating it returns a Class object

 

💡 Why is getClass() useful?

✅ It gives runtime class information — even in polymorphic code.

java
 
Object obj = new String ( "Hello" ); System.out.println(obj.getClass()); // class java.lang.String   

Even though obj is declared as Object, getClass() still knows the real type.


📘 Example Usage:

🧪 Simple usage:

String name = "Nitesh" ; Class<?> cls = name.getClass(); System.out.println(cls.getName());         // java.lang.String System.out.println(cls.getSimpleName());   // String   

🔍 Practical uses:

  • Reflection API (used by frameworks like Spring, Hibernate)
  • Debugging/logging (identify object types)
  • Serialization/deserialization
  • Dynamic loading (e.g., Class.forName("..."))

 

🤔 Why final and native?

🔐 final

  • Because allowing subclasses to override getClass() would break the JVM's type safety and behavior.
  • It must always return the actual runtime class — overriding would allow faking it.

⚙️ native

  • The JVM needs to know the object's class internally, and this info is stored in the object's header.
  • So, getClass() simply fetches this info directly from the memory structure using native code.

 

🧠 Summary Table:

FeatureDescription
Method NamegetClass()
Return TypeClass<?> (Java Reflection class)
Defined Injava.lang.Object
Native✅ Yes (implemented in C/C++ inside JVM)
Final✅ Yes (cannot be overridden)
UseGet the actual class of the object at runtime
Common Use CasesReflection, debugging, dynamic loading, frameworks

 

📦 Real World Example:

 
public class Test {     public static void main (String[] args) {         Number num = new Integer ( 10 ); // Polymorphic reference         System.out.println( "Runtime class: " + num.getClass().getName());    } } 

🔁 Output:

Runtime class : java.lang. Integer   

Even though num is of type Number, getClass() returns Integer.

 

 

 

🔎 Method Declaration:

@IntrinsicCandidate protected native Object clone () throws CloneNotSupportedException;

 

✅ Purpose of clone()

The clone() method is used to create and return a copy of the current object. It's a shallow copy by default, unless you manually implement deep copying.

 

🧠 Why clone() is needed?

When you assign an object like this:

Person p1 = new Person("Nitesh");
Person p2 = p1;
 

You are not copying the object. You are copying the reference (both point to the same memory).

But in some cases (e.g., undo operations, version control, simulation), you need a copy of the object with independent memory — that's where clone() comes in.

 

🧩 clone() Keyword Explanation

KeywordMeaning
@IntrinsicCandidateJVM optimization hint
protectedOnly accessible within the same package or subclass
nativeImplemented in JVM native code (not Java)
ObjectReturn type is an Object — must cast manually
throws CloneNotSupportedExceptionYou must explicitly handle this if the class is not Cloneable

⚠️ Exception: CloneNotSupportedException

This is a checked exception, which is thrown when:

Your class does not implement the Cloneable interface, but you're trying to call clone().

 

📦 How to use clone() properly?

🔧 Step-by-Step:

  1. Implement Cloneable interface (marker interface — no methods).
  2. Override the clone() method from Object.
  3. Call super.clone() inside it.
  4. Catch or declare CloneNotSupportedException.

🔀 Shallow vs Deep Cloning

TypeMeaning
Shallow CloneCreates a new object, but references the same internal objects.
Deep CloneCreates a new object and recursively clones all internal objects.

 

✅ Example: Shallow Cloning

 
 
class Person implements Cloneable {     int age;    String name;     public Person ( int age, String name) {         this .age = age;         this .name = name;    }     @Override      public Object clone () throws CloneNotSupportedException {         return super .clone(); // shallow copy     } } public class Main {     public static void main (String[] args) throws CloneNotSupportedException {         Person p1 = new Person ( 25 , "Nitesh" );         Person p2 = (Person) p1.clone();        System.out.println(p1 == p2);           // false (different objects)         System.out.println(p1.name == p2.name); // true (same string object in shallow copy)     } } 

🌊 Deep Cloning Example

If the object contains references (like a List, or nested objects), you must manually deep clone them.

class Address {    String city;    Address(String city) {         this .city = city;    } } class Employee implements Cloneable {    String name;    Address address;    Employee(String name, Address address) {         this .name = name;         this .address = address;    }     @Override      public Object clone () throws CloneNotSupportedException {         Employee cloned = (Employee) super .clone();        cloned.address = new Address ( this .address.city); // deep copy          return cloned;    } } 

 

🧠 Summary Table

FeatureDescription
PurposeCreates a copy of the object (not just reference)
Default Copy TypeShallow (fields copied, but objects referenced are not cloned)
Need to implementCloneable interface
Throws ExceptionCloneNotSupportedException if not Cloneable
Return typeObject — needs casting
Access modifierprotected — must override to access from outside class

 

❓Why only protected?

  • So it’s not publicly accessible by default — it enforces controlled cloning.
  • You must explicitly override and expose it if you want it to be used outside.

🔄 Alternative to clone()

Some developers avoid clone() due to complexity and prefer:

  • Copy constructors
  • Builder patterns
  • Serialization cloning (deep copy via byte streams)

 

✅ Original toString() in Object class

public String toString () {     return getClass().getName() + "@" + Integer.toHexString(hashCode()); }

🧠 What does this return?

It gives a string representation of the object in this format:

ClassName @HashCodeInHex

Example:

Person p = new Person (); System.out.println(p.toString()); // e.g., com.demo.Person@1a2b3c

 

🔍 Breakdown of parts

1️⃣ getClass().getName()

  • Gets the fully-qualified class name at runtime.
  • Example: com.mycompany.model.Employee

2️⃣ hashCode()

  • A 32-bit integer that usually represents the object's memory address (by default).
  • Can be overridden for logical equality.

3️⃣ Integer.toHexString(int i)

  • Converts the int (from hashCode()) to a hexadecimal string.
  • Makes it compact and looks like memory representation (like C/C++ pointer).

 

🚀 Benefits of toString()

PurposeBenefit
Debugging/loggingSee object info in logs or exceptions
Default representationAvoids nulls in string conversions
IDE/Debugger toolsDisplays concise object info

 

🔁 Edge Case Analysis (Your Main Question)

Let’s explore all combinations of overriding:

 

✅ Case 1: Neither toString() nor hashCode() overridden

Output:

ClassName @HexOfDefaultHashCode   

Example:

MyClass obj = new MyClass (); System.out.println(obj);   // e.g., MyClass@4e25154f   

→ Default behavior, shows technical object info.

 

✅ Case 2: Only hashCode() overridden

@Override public int hashCode () {     return 42 ; } 

Output:

ClassName@2a  ( 42 in hex = 2 a) 

→ You changed how the hash is calculated, so it affects the toString() output.

🔴 But still not human-friendly — just custom number in hex.

 

✅ Case 3: Only toString() overridden

@Override public String toString () {     return "Employee{name='Nitesh'}" ; } 

Output:

 
Employee{ name = 'Nitesh'

→ Much more readable and meaningful for logs, UIs, etc.

Hashcode is still default, but unused unless you call hashCode() explicitly.

 

✅ Case 4: Both toString() and hashCode() overridden

 
@Override public int hashCode () {     return Objects.hash(id, name); } @Override public String toString () {     return "Employee{id=" + id + ", name='" + name + "'}" ; } 

Output:

Employee{ id =101, name= 'Nitesh'

→ Full control over both:

  • hashCode() for hashing, collections, equality.
  • toString() for display, logging.

 

🤔 Why Override toString()?

➕ Pros:

  • Easier debugging (System.out.println(obj) gives readable output)
  • Clearer logs
  • Better error messages
  • Frameworks (like Spring, Hibernate) use it for logging objects

 

🧱 Best Practice for toString():

 
@Override public String toString () {     return "Person{name='" + name + "', age=" + age + "}" ; } 

 

💣 What Happens If You Don’t Override?

If you don’t override:

  • Logs and debug messages are hard to interpret
  • Framework logs become cryptic
  • You see object references like MyClass@7852e922 (useless for business logic)

 

🧠 Summary Table

CasetoString() OutputHuman Readable?Controlled Behavior
No overrideClass@HexHash
Only hashCode() overriddenClass@CustomHex⚠️ (affects Map/Set)
Only toString() overriddenMeaningful string
Both overriddenYour string with custom logic

 

✅ Conclusion:

  • Always override toString() when your class is used in logs, UI, or debugging.
  • Override hashCode() when you also override equals() (important for collections like HashSet, HashMap).
  • They both serve different purposes:
    • toString() = display
    • hashCode() = identity & hashing

 

✅ Original Method in Object Class: how it works, why it was deprecated, real use cases, and modern alternatives.

@Deprecated(since="9") protected void finalize () throws Throwable { }

 

💡 What is finalize()?

  • finalize() is a method in java.lang.Object that was intended to be called by the Garbage Collector (GC) before reclaiming memory from an object.
  • You could override it to release system resources, close streams, or do cleanup.

 

🧠 How It Used to Work

  1. When an object became eligible for garbage collection, the JVM would:
    • Check if the object overrides finalize()
    • Call finalize() once only before destroying the object
    • Then collect (free) the object’s memory
  2. You could override it like this:
@Override protected void finalize () throws Throwable {     try {        System.out.println( "Cleanup before GC" );    } finally {         super .finalize(); // important to call     } } 

 

⚠️ Why finalize() is Deprecated (Since Java 9)

❌ Unreliable:

  • No guarantee finalize() will be called at all.
  • The timing is unpredictable.

🐢 Performance Overhead:

  • Finalized objects take longer to collect — they go through an extra phase.

😨 Risky Behavior:

  • If finalize() throws an exception and doesn’t call super.finalize(), GC can break.
  • Holding onto objects in finalize() can resurrect them, delaying collection (memory leak risk).

 

🔥 Example of Dangerous Use

@Override protected void finalize () throws Throwable {    System.out.println( "Object resurrected" );    globalList.add( this ); // resurrects the object

This object won’t be garbage collected, leading to memory leaks!


✅ Modern Replacement (Post-Java 9+)

Use try-with-resources or AutoCloseable:

Instead of relying on finalize(), use this:

public class MyResource implements AutoCloseable {     @Override      public void close () {        System.out.println( "Closed properly!" );    } } 
try ( MyResource r = new MyResource ()) {     // use resource } // Automatically calls r.close()   

This ensures deterministic cleanup of resources like files, DB connections, etc.

 

🧪 Example: Before vs After

✅ Old (Deprecated):

@Override protected void finalize () throws Throwable {    System.out.println( "Cleaning up..." );     super .finalize(); } 

✅ Modern (Recommended):

 
public class FileHandler implements AutoCloseable {     private FileInputStream fis;     public FileHandler (String fileName) throws IOException {        fis = new FileInputStream (fileName);    }     @Override      public void close () throws IOException {         if (fis != null ) fis.close();    } } 
try ( FileHandler fh = new FileHandler ( "data.txt" )) {     // use the file

 

🔚 Summary

Featurefinalize()
Introduced InJava 1.0
Deprecated SinceJava 9
Unreliable?✅ Yes – GC timing is unpredictable
Replaced ByAutoCloseable + try-with-resources
Can throw?Yes – Throwable (broadest exception)
Dangerous?✅ Yes – possible resurrection & leaks

 

🧠 Final Thought

Never use finalize() in new code.

If you're maintaining legacy code that still uses it:

  • Remove it if it's not essential.
  • Migrate cleanup logic to AutoCloseable.

 

 

→ Rest Other method of Object class will take in multithreading→ 

 

2️⃣ String Class

  • Represents immutable sequences of characters.
  • Widely used for storing and manipulating textual data.
  • Example:

    String s = "Hello World"

    ✅ 1. Why is String a Special Class?

    • It is immutable and final.
    • Widely used in Java programs.
    • Java treats string literals as interned objects (stored in a pool for memory efficiency).

    ✅ 2. How String Works Internally – SCP (String Constant Pool)

    String s1 = "Java";
    String s2 = "Java";
    String s3 = new String("Java");
     

    • s1 and s2 point to the same SCP object.
    • s3 creates a new object in heap + references "Java" in SCP.

    ➡️ SCP is part of the Method Area, and literals are stored only once.

     

    ✅ 3. How Many Objects Are Created?

    String s = new String ( "Java" ); 
    • "Java"1 object in SCP
    • new String(...)1 object in heap

    ✔️ So: 2 objects created.

     

    ✅ 4. Is String Thread-Safe?

    • Yes, because it’s immutable. Once created, it cannot be changed.
    • Multiple threads can safely share it without synchronization.

    ✅ 5. Is String a Singleton?

    • ❌ No, but SCP makes it behave like a singleton for literals.
    • You can use String.intern() to get the pooled version.
    java
    String a = new String ( "Nitesh" ).intern(); String b = "Nitesh" ; System.out.println(a == b); // true   

    ✅ 6. Can You Create Your Own Singleton like String?

    Yes:

    public class MySingleton {     private static final MySingleton instance = new MySingleton ();     private MySingleton () {}     public static MySingleton getInstance () {         return instance;    } } 

    But remember: String is not a Singleton class; it just interns values.

     

    ✅ 7. Are equals() and hashCode() Overridden in String?

    ✅ YES — here’s why:

    String a = "Java" ; String b = new String ( "Java" ); System.out.println(a == b);            // false → checks reference System.out.println(a.equals(b));       // true → checks content System.out.println(a.hashCode());      // based on content   
    • equals() → compares character-by-character
    • hashCode() → overridden to produce consistent hash for same content

    ➡️ Ensures String works properly in HashMap, HashSet, etc.

     

3️⃣ StringBuffer Class

  • A mutable sequence of characters (unlike String).
  • Thread-safe (synchronized), suitable for multi-threaded environments.
  • Good when frequent modifications to string content are needed.

StringBuffer – Thread-safe Mutable Strings

✅ Overview

  • Mutable (you can change the content)
  • Thread-safe via synchronized methods
  • Slower in single-threaded apps

→ StringBuffer is not 100% synchronized remember. Most of StringBuffer methods are synchronized  not all. If someone say directly like StringBuffer all synchronized methods then this is not valid.

As below methods are not synchronized.

private void 
	
	
	
	
	
	
	
	
	
	
	
	readObject
	
	
	
	
	
	
	
	
	
	
	
	(ObjectInputStream s
	
	
	
	
	
	
	
	
	
	
	
	)
 
private static final 
	
	
	
	
	
	
	
	
	
	
	
	ObjectStreamField
	
	
	
	
	
	
	
	
	
	
	
	[] serialPersistentFields 
	
	
	
	
	
	
	
	
	
	
	
	=
 
public int 
	
	
	
	
	
	
	
	
	
	
	
	lastIndexOf
	
	
	
	
	
	
	
	
	
	
	
	(String str
	
	
	
	
	
	
	
	
	
	
	
	) {
 
public int 
	
	
	
	
	
	
	
	
	
	
	
	indexOf
	
	
	
	
	
	
	
	
	
	
	
	(String str
	
	
	
	
	
	
	
	
	
	
	
	) {
 
public 
	
	
	
	
	
	
	
	
	
	
	
	StringBuffer 
	
	
	
	
	
	
	
	
	
	
	
	insert
	
	
	
	
	
	
	
	
	
	
	
	(int 
	
	
	
	
	
	
	
	
	
	
	
	offset
	
	
	
	
	
	
	
	
	
	
	
	, double 
	
	
	
	
	
	
	
	
	
	
	
	d
	
	
	
	
	
	
	
	
	
	
	
	) {
 
public 
	
	
	
	
	
	
	
	
	
	
	
	StringBuffer 
	
	
	
	
	
	
	
	
	
	
	
	insert
	
	
	
	
	
	
	
	
	
	
	
	(int 
	
	
	
	
	
	
	
	
	
	
	
	offset
	
	
	
	
	
	
	
	
	
	
	
	, float 
	
	
	
	
	
	
	
	
	
	
	
	f
	
	
	
	
	
	
	
	
	
	
	
	) {
 

Ex:-

StringBuffer sb = new StringBuffer("Hi");
sb.append(" Java");
System.out.println(sb); // Hi Java
 

✅ Use Case

  • When you need mutable + thread-safe string operations.

 

 

4️⃣ StringBuilder Class (Introduced in Java 1.5)

  • Also a mutable sequence of characters.
  • Not synchronized, hence faster than StringBuffer in single-threaded environments.
  • Ideal for high-performance string manipulations.

StringBuilder – High Performance, Not Thread-Safe

✅ Overview

  • Same as StringBuffer but not thread-safe
  • Much faster in single-threaded environments

StringBuilder sb = new StringBuilder("Hi");
sb.append(" Java");
System.out.println(sb); // Hi Java
 

✅ Use Case

  • Prefer StringBuilder over StringBuffer when no multithreading is involved.

🔄 Comparison Table

FeatureStringStringBufferStringBuilder
MutabilityImmutableMutableMutable
Thread-safeYes (because immutable)Yes (synchronized)❌ No
PerformanceMediumSlow (due to sync)Fast (no sync)
Use CaseConstants, KeysMulti-threaded opsHigh-perf single thread
Equals/hashCode✅ Overridden❌ Inherited❌ Inherited

 

✅ Real-World Example: Why String is Immutable

String a = "password" ; a.replace( "pass" , "****" ); System.out.println(a); // password — original unchanged   

🔒 Prevents:

  • Accidental/unauthorized changes
  • Security issues (like password tampering)

 

✅ Bonus: How Interning Helps Memory

String x = "Spring" ; String y = new String ( "Spring" ).intern(); System.out.println(x == y); // true   
  • Helps deduplicate memory usage.
  • Makes memory management more efficient for string-heavy apps.

 

🔍 Do String, StringBuffer, and StringBuilder override equals() and hashCode()?

Classequals() Overridden?hashCode() Overridden?Why / Why Not?
String✅ YES✅ YESTo compare contents, support HashMap, Set, etc.
StringBuffer❌ NO (inherits Object)❌ NO (inherits Object)Uses reference equality, not designed for content comparison
StringBuilder❌ NO (inherits Object)❌ NO (inherits Object)Same as StringBuffer, avoids overhead

 

🔹 1. String → ✅ OVERRIDDEN

✔️ equals():

@Override public boolean equals (Object anObject) {     if ( this == anObject) return true ;     if (anObject instanceof String aString) {         return this .length() == aString.length() && this .compareTo(aString) == 0 ;    }     return false ; } 

✔️ hashCode():

@Override public int hashCode () {     int h = 0 ;     for ( char c : value) {        h = 31 * h + c;    }     return h; } 
  • 🧠 Reason: String is heavily used in collections like HashMap, HashSet.
  • ✅ Must have content-based equality for keys to work correctly.

 

🔹 2. StringBuffer → ❌ NOT OVERRIDDEN

  • Inherits both from Object.
  • Compares only reference, not contents.
StringBuffer sb1 = new StringBuffer ( "Java" ); StringBuffer sb2 = new StringBuffer ( "Java" ); System.out.println(sb1.equals(sb2));     // ❌ false System.out.println(sb1.hashCode());      // Identity-based   
  • ❌ Not overridden because:
    • It’s mutable, and content can change — which breaks consistency in HashMap.
    • Immutability is required for reliable hashing → not guaranteed here.

 

🔹 3. StringBuilder → ❌ NOT OVERRIDDEN

  • Same behavior as StringBuffer.
  • Reference-based comparison, not content-based.
  • Not thread-safe but more performant.

🔄 Why Only String Overrides?

FeatureStringStringBuffer/Builder
Immutable✅ Yes❌ No
Thread-safe✅ YesBuffer: ✅, Builder: ❌
Used as Key in HashMap✅ Common❌ Rare
Consistent Content✅ Fixed❌ Can change anytime
Hashing is Safe✅ Yes❌ No

 

💡 Summary

  • String overrides both equals() and hashCode() → for content comparison, safe to use in HashMap.
  • StringBuffer & StringBuilder do NOT override → because they're mutable, and using them in hash-based collections would cause unexpected behavior if the content changes after insertion.
  • String is immutable, pooled, and equals/hashCode are overridden.
  • It's not a true singleton but behaves like one in SCP.
  • StringBuffer is thread-safe but slower.
  • StringBuilder is fast but not thread-safe.

 

 

5️⃣ Wrapper Classes

Java provides object representations for all primitive data types via wrapper classes:

Primitive TypeWrapper Class
intInteger
floatFloat
charCharacter
booleanBoolean
doubleDouble
byteByte
shortShort
longLong

These classes allow primitives to be used in collections and support utility methods for conversions and parsing.

6️⃣ Autoboxing and Unboxing (Introduced in Java 1.5)

  • Autoboxing: Automatic conversion of a primitive type to its corresponding wrapper class.

    int x = 10 ; Integer y = x; // Autoboxing   
  • Unboxing: Automatic conversion of a wrapper class object back to a primitive.

    Integer z = 20 ; int a = z; // Unboxing   

 

🧠 Conclusion

The java.lang package forms the backbone of Java's core libraries. With classes like Object, String, and utility types such as wrappers and builders, it equips developers with powerful tools to build efficient and robust Java applications. Since it's implicitly available in every Java program, you can start using its classes and interfaces without worrying about imports—making development quicker and more streamlined.

22 min read
Jun 04, 2025
By Nitesh Synergy
Share

Leave a comment

Your email address will not be published. Required fields are marked *