There are two main categories of objects in Java: Primitive Types and Reference Types. This page will give a brief overview of both, and close off with some info about the mystical Object class.
Primitive types are built in to Java and have fixed memory sizes. Different types require different amounts of memory.
If you remember environment diagrams, you may recall that some variables are put straight into the boxes, while others have an arrow pointing to them. The reason for this is that it actually denotes primitive vs. reference types! Primitive types go straight in the box because they aren't mutable (i.e. you can't change the objects contained in the box since they're just constant literals like numbers).
A quick aside on Strings 🧵 You may have noticed that strings are not on this list. That is because unlike in Python, they aren't a primitive type! Under the hood, Strings are a reference type that are very similar to a char array.
Java will automatically convert between primitive types if no information is lost ( from byte to int).
Conversion in the other direction (from a larger to smaller container) requires an explicit cast (e.g.,
(char) int). The compiler will treat a cast object as though its static type is the cast type, but this will only work if the cast type is the same as or a parent of the dynamic type. However, relative to the assigned static type, the cast type could be a child of the static type or a parent of the static type.
Assignment statements are an exception to this:
aByte = 10is fine even though 10 is an int literal. This is because arithmetic operations (+, *, ...) automatically promote operands (e.g.,
'A' + 2is equivalent to
(int)'A' + 2)
However, this doesn't work if you are trying to add a larger type to a smaller type (e.g.,
aByte = aByte + 1since operands become an int type which cannot be set equal to a byte type. But += works!
A reference type refers to basically anything that's not primitive 😅
Here are some major differences that set them apart from primitive types:
- Reference types can take an arbitrary amount of memory. Unlike primitives which have a fixed memory for each type, objects like arrays can expand to hold lots of things inside it.
- Reference types are referred to using addresses. When you say something like
int arr = new int,
arronly stores a 64-bit memory address which points to the real object, a 5-length integer array. Again, think back to the arrow in environment diagrams, and how those work.
- By default, reference types can be set to null which is represented as an address of all zeroes. Or, the new keyword can be used to set it to a specific address.
- Reference objects can be lost if all pointers to it are reassigned. For example, if I now enter
arr = null;, the original 5-length array still exists, but just has nothing to refer to it.
The assignment operator (
=) has different behaviors for primitive types and references types.
For primitive types,
y = xmeans "copy the bits from y into a new location, then call them x". Here, the entire object is copied- this means that changing y will NOT change x even though they are set "equal".
For reference types,
obj1 = obj2means "copy the address stored in obj1 to obj2". Here,
obj2are referring to the exact same object, and mutating one will change the other.
A clarification on reference type assignment
By mutating, I mean changing the internals of an object (for example, accessing an array index or doing something like
obj1.value = 1. If you change the actual address of
obj2, as in
obj2 = obj3, this does not change
obj2is now referring to a completely different object!
In Java, all objects inherit from the master Object class. Here are some important properties of Object that will be useful to know:
String toString(): By default, this prints out the class name followed by the memory address (e.g.,
Object@192c38f). This can be overridden to make more user-friendly names for objects.
boolean equals(Object obj): By default, this checks if the two objects are actually the same object (same memory address). This can be overridden to check if specific contents of objects are the same, rather than checking if they are literally the same object. (Like
int hashCode(): Returns a numeric hash code for the object that should differentiate it from other objects. This should be overridden if
equals()is overridden since
Class<?> getClass(): Returns the class of this object.