Java Non-Primitive Types - Complete Guide
Introduction to Non-Primitive Types
Non-primitive types (also called reference types) in Java represent objects rather than raw values. Instead of holding the actual data, they hold a reference (memory address) to the object in the heap.
Examples include Strings, arrays, classes, interfaces, enums, and collections. They are fundamental to object-oriented programming and allow features like methods, inheritance, and polymorphism.
Key Differences: Primitive vs Reference Types
The table below summarizes the major differences between primitive and reference types:
Aspect | Primitive Types | Reference Types |
---|---|---|
Storage | Directly stores actual values | Stores references to objects |
Memory | Typically allocated on stack | Objects stored in heap; reference on stack |
Default Value | Type-specific (0, false, etc.) | null |
Size | Fixed (1–8 bytes depending on type) | Varies by object structure |
Operations | Arithmetic, logical | Method calls, field access |
Assignment | Copies the value | Copies the reference (points to same object) |
Comparison with == | Compares values | Compares object references |
Null Value | Cannot be null | Can be null |
String Class - The Most Common Reference Type
Strings are objects that represent sequences of characters. They are immutable and heavily used in Java.
String literals are stored in a special pool to optimize memory, while `new String()` creates a distinct object.
public class StringExamples {
public static void main(String[] args) {
String str1 = "Hello";
String str2 = new String("Hello");
String str3 = "Hello";
System.out.println("str1 == str2: " + (str1 == str2));
System.out.println("str1 == str3: " + (str1 == str3));
System.out.println("str1.equals(str2): " + str1.equals(str2));
String message = " Hello World! ";
System.out.println("Original: '" + message + "'");
System.out.println("Trimmed: '" + message.trim() + "'");
System.out.println("Uppercase: " + message.toUpperCase());
System.out.println("Lowercase: " + message.toLowerCase());
System.out.println("Length: " + message.length());
System.out.println("Substring: " + message.substring(1, 6));
System.out.println("Replace: " + message.replace("World", "Java"));
System.out.println("Contains 'Hello': " + message.contains("Hello"));
String fullName = "John" + " " + "Doe";
System.out.println("Full name: " + fullName);
StringBuilder sb = new StringBuilder();
sb.append("Hello").append(" ").append("World");
System.out.println("StringBuilder result: " + sb.toString());
}
}
str1 == str2: false str1 == str3: true str1.equals(str2): true Original: ' Hello World! ' Trimmed: 'Hello World!' Uppercase: HELLO WORLD! Lowercase: hello world! Length: 14 Substring: Hello Replace: Hello Java! Contains 'Hello': true Full name: John Doe StringBuilder result: Hello World
Arrays as Reference Types
Arrays in Java are objects, even if they hold primitives. They store elements in contiguous memory and provide indexed access.
public class ArrayExamples {
public static void main(String[] args) {
int[] numbers = {1, 2, 3, 4, 5};
String[] names = {"Alice", "Bob", "Charlie"};
System.out.println("First number: " + numbers[0]);
System.out.println("Second name: " + names[1]);
numbers[0] = 10;
names[1] = "Robert";
System.out.println("Numbers length: " + numbers.length);
System.out.println("Names length: " + names.length);
System.out.print("Numbers: ");
for (int num : numbers) {
System.out.print(num + " ");
}
System.out.println();
System.out.print("Names: ");
for (String name : names) {
System.out.print(name + " ");
}
System.out.println();
int[][] matrix = {
{1, 2, 3},
{4, 5, 6},
{7, 8, 9}
};
System.out.println("Matrix[1][2]: " + matrix[1][2]);
int[] arr1 = {1, 2, 3};
int[] arr2 = arr1;
arr2[0] = 100;
System.out.println("arr1[0] after modification: " + arr1[0]);
}
}
First number: 1 Second name: Bob Numbers length: 5 Names length: 3 Numbers: 10 2 3 4 5 Names: Alice Robert Charlie Matrix[1][2]: 6 arr1[0] after modification: 100
Custom Classes and Objects
Defining custom classes is a core part of object-oriented programming. Objects created from classes are reference types.
class Person {
String name;
int age;
String email;
public Person(String name, int age, String email) {
this.name = name;
this.age = age;
this.email = email;
}
public void introduce() {
System.out.println("Hello, my name is " + name + " and I'm " + age + " years old.");
}
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public int getAge() { return age; }
public void setAge(int age) { if (age > 0) this.age = age; }
}
public class CustomClassExample {
public static void main(String[] args) {
Person person1 = new Person("Alice", 25, "alice@email.com");
Person person2 = new Person("Bob", 30, "bob@email.com");
person1.introduce();
person2.introduce();
System.out.println("Original age: " + person1.getAge());
person1.setAge(26);
System.out.println("Updated age: " + person1.getAge());
Person person3 = person1;
person3.setName("Alicia");
System.out.println("person1 name after modification: " + person1.getName());
Person person4 = null;
if (person4 != null) {
person4.introduce();
} else {
System.out.println("person4 is null");
}
}
}
Hello, my name is Alice and I'm 25 years old. Hello, my name is Bob and I'm 30 years old. Original age: 25 Updated age: 26 person1 name after modification: Alicia person4 is null
Wrapper Classes
Wrapper classes provide object representations for primitive types. They are used in collections and support autoboxing and unboxing.
public class WrapperClasses {
public static void main(String[] args) {
Integer intObj = Integer.valueOf(100);
Double doubleObj = Double.valueOf(3.14);
Boolean boolObj = Boolean.valueOf(true);
Character charObj = Character.valueOf('A');
Integer autoInt = 200;
int primitiveInt = intObj;
System.out.println("Integer object: " + intObj);
System.out.println("Double object: " + doubleObj);
System.out.println("Boolean object: " + boolObj);
System.out.println("Character object: " + charObj);
System.out.println("Autoboxed integer: " + autoInt);
System.out.println("Unboxed primitive: " + primitiveInt);
String numStr = "123";
int parsedInt = Integer.parseInt(numStr);
System.out.println("Parsed integer: " + parsedInt);
System.out.println("Max integer value: " + Integer.MAX_VALUE);
System.out.println("Min integer value: " + Integer.MIN_VALUE);
System.out.println("Integer size in bits: " + Integer.SIZE);
String formatted = String.format(",%d", 1000000);
System.out.println("Formatted number: " + formatted);
}
}
Integer object: 100 Double object: 3.14 Boolean object: true Character object: A Autoboxed integer: 200 Unboxed primitive: 100 Parsed integer: 123 Max integer value: 2147483647 Min integer value: -2147483648 Integer size in bits: 32 Formatted number: 1,000,000
Collections Framework
Java's Collections Framework provides advanced data structures like List, Set, and Map for handling groups of objects efficiently.
import java.util.*;
public class CollectionsExample {
public static void main(String[] args) {
List<String> namesList = new ArrayList<>();
namesList.add("Alice");
namesList.add("Bob");
namesList.add("Alice");
namesList.add("Charlie");
System.out.println("List: " + namesList);
Set<String> namesSet = new HashSet<>();
namesSet.add("Alice");
namesSet.add("Bob");
namesSet.add("Alice");
namesSet.add("Charlie");
System.out.println("Set: " + namesSet);
Map<String, Integer> ageMap = new HashMap<>();
ageMap.put("Alice", 25);
ageMap.put("Bob", 30);
ageMap.put("Charlie", 35);
System.out.println("Map: " + ageMap);
System.out.println("Alice's age: " + ageMap.get("Alice"));
System.out.println("\nList elements:");
for (String name : namesList) {
System.out.println(" " + name);
}
System.out.println("\nMap entries:");
for (Map.Entry<String, Integer> entry : ageMap.entrySet()) {
System.out.println(" " + entry.getKey() + ": " + entry.getValue());
}
}
}
List: [Alice, Bob, Alice, Charlie] Set: [Alice, Bob, Charlie] Map: {Alice=25, Charlie=35, Bob=30} Alice's age: 25 List elements: Alice Bob Alice Charlie Map entries: Alice: 25 Charlie: 35 Bob: 30