Types vs. Classes

Andrew (he/him) - Aug 9 '20 - - Dev Community

Some languages (Java in particular) blur the line between types and classes. You may even see them used interchangeably in tutorials, documentation, etc. While they are related concepts, they are not synonymous.

Here's a 30-second explanation.

Consider the Vector class

jshell> var a = new Vector<Integer>(List.of(1, 2, 3))
a ==> [1, 2, 3]
Enter fullscreen mode Exit fullscreen mode

Vector a is an instance of the Vector class. But its type is not Vector, but rather Vector<Integer>. A Vector<Integer> and (for example) a Vector<String> may be instances of the same class (Vector), but they are clearly not the same type -- they hold totally different kinds of data!

In addition, Vector cannot be a type, because it takes a type parameter itself. When we write Vector, we actually mean Vector<E>, where E is a type parameter.

We can draw a parallel with constructor arguments. Consider this simple class

jshell> public class Dog {
   ...>   private String _name;
   ...>   public Dog(String name) {
   ...>     _name = name;
   ...>   }
   ...>   public String name() { return _name; }
   ...> }
|  modified class Dog

jshell> var Fido = new Dog("Fido")
Fido ==> Dog@68de145

jshell> var Spot = new Dog("Spot")
Spot ==> Dog@46f7f36a
Enter fullscreen mode Exit fullscreen mode

You can think of class Dog as a sort of parameterized object constructor. It takes a single parameter (or "argument"), name, and uses that name to construct an object, a Dog.

Similarly, generic classes like Vector<E> can be thought of as parameterized type constructors. They take a single parameter, which in this case is another type, and use that type to construct a new (parameterized) type.

So why is this so confusing in Java?

Because Java uses raw types, which you may occasionally see in compiler or jshell warnings

jshell> var b = new Vector(List.of(1, 2, 3))
|  Warning:
|  unchecked call to Vector(java.util.Collection<? extends E>) as a member of the raw type java.util.Vector
|  var b = new Vector(List.of(1, 2, 3));
|          ^--------------------------^
b ==> [1, 2, 3]
Enter fullscreen mode Exit fullscreen mode

Java will grudgingly compile this code, but since you didn't tell it what type E is, it assumes it to be Object. Look what happens when I try to add a String to b

jshell> b.add("hey")
|  Warning:
|  unchecked call to add(E) as a member of the raw type java.util.Vector
|  b.add("hey")
|  ^----------^
$23 ==> true

jshell> b
b ==> [1, 2, 3, hey]
Enter fullscreen mode Exit fullscreen mode

Java can't narrow the type of the elements within the Vector at all. All it knows is that they must be some kind of Objects. Since String is an Object, it has no problem adding it to this Vector.

If you try the same with a, you'll get an error

jshell> a.add("hey")
|  Error:
|  incompatible types: java.lang.String cannot be converted to java.lang.Integer
|  a.add("hey")
|        ^---^
Enter fullscreen mode Exit fullscreen mode

Raw types are a holdover from a time before Java had these type parameterizations (nearly two decades ago now). But they still regularly cause confusion among the uninitiated as to the difference between a type and a class in Java.

Remember that a Vector<String> and a Vector<Integer> are both Vectors (they are instances of the same class), but they do not hold the same kind of data, so they do not have the same type.

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .