To me, the easiest way to understand int.class
and Integer.class
is to stop thinking Integer
is a wrapper (which kind of implies it is "special"). In fact, it is easier, and probably more appropriate, to think of int as a special case.
Integer is just a normal Java class, nothing different from e.g. String. It derives from Object
, operates like Object
, At runtime you can create an instance of Integer
, the instance takes an object-like memory layout, e.g. with a pointer at the beginning pointing to the Integer.class
, which is what enables the polymorphic runtime behavior of java.
There is really nothing special yet about Integer
. if you imagine a Java without boolean, int, long these primitives, but only with Integer, Boolean, Long etc, the type system is actually very consistent.
Conceptually, you can think of int
as a special class introduced later for performance reasons. Initially, it has nothing to do with Integer. At the time when Java was created, maintaining an object-like memory layout for plain numbers is very expensive for arithmetic heavy programs. And most of the arithmetic operations do not even involve polymorphic dynamic dispatching at all. E.g. it is a lot less common to invoke methods such as toString on a number.
int is special in the sense that it is a class whose "instances" are laid out in memory with the common object structure stripped off - just four consecutive bytes with no extra meta data.
As a result, you cannot do 123.getClass()
because the runtime memory layout of int 123 does not have a class pointer. int.class
does exist, it is completely unrelated to Integer.class
(yet). In a sense, int is more similar to Void, as in Void.class does exist, but you can never have object o where o.class == Void.class
.
Java could just settle here, int is int, Integer is Integer. But what people realize is that although less common, it is still very useful to be able treat int as a normal Java object otherwise you will always have to maintain two sets of your methods at least - one that takes normal objects, and another one that takes primitives - even though performance is not your concern. In these scenarios, int is actually behaving like Integer. Java allows this conversion to automatically happen through the processes of auto-boxing, hence effectively making int and Integer related.
But int is still int, and Integer is still Integer (e.g. int.class != Integer.class
). But one extra field is introduced to Integer.class to indicate it relates to int.class, that is Integer.TYPE
. So Integer.TYPE == int.class
. To me Integer.TYPE
is just to capture the notional that Integer and int are related. How to use it, or whether it is even useful, is up to you.
In practice, int and Integer are still separate types where it matters. For example:
void accept (int value);
void accept (Integer value);
are still considered two overloads. Hence when working with reflection, you can use int.class and Integer.class to differentiate between the two.
When not working with reflection or some form of meta programming, because you cannot navigate to int.class from an object, it is relatively rare to see int.class to be used in your code. Sometimes it feels confusing because auto-boxing syntax seems to suggest you should be getting int.class:
int value = 1;
Class c = ((Object)value).getClass();
But that is just an illusion, as the moment you do ((Object)value)
, you are actually creating a new Integer instance with the same value.
The only time I need to explicitly work with int.class and Integer.class is to build a generics api <T> T getValue(String name, Class<T> type);
where I need to differentiate if the api is allowed to return null:
int value = getValue("key", Integer.class); // will potentially throw NPE
int value = getValue("key", int.class); // will never throw NPE
int.class
,boolean.class
etc. I don't know when that functionality came in, so maybe the docs are just out of date there? – Callous