Search code examples
javajava.lang.class

Why do these two way to compare Class objects result differently?


I've defined two classes : Employee and Doctor, They're father and son. The code like this:

class Employee {
  // ....
}

class Doctor extends Employee {
  // ....
}

And then I wrote a main method like this:

public static void main(String[] args) {
        Doctor doctor = new Doctor();
        Employee employee = new Employee();
        System.out.println(doctor.getClass() == new Employee().getClass());  // code 1
        System.out.println(employee.getClass() == Employee.class);  // code 2
        System.out.println(doctor.getClass() == Employee.class); // code 3
    }

but only code 1 and code 2 is correct and code 3 calls exception :

Error:(33, 46) Java: uncomparable type: java.lang.Class<capture#1 extends Doctor> and java.lang.Class<Employee>

and I know I can use equals to deal with this problem, but I want to know why I can't use operator == to compare them.


Solution

  • The behaviour you observe is correct, but to explain why, we need to refer to the Java Language Specification.

    §15.8.2. Class Literals says

    The type of C.class, where C is the name of a class, interface, or array type (§4.3), is Class<C>.

    §4.3.2. The Class Object says:

    The type of a method invocation expression of getClass is Class<? extends |T|>, where T is the class or interface that was searched for getClass (§15.12.1) and |T| denotes the erasure of T (§4.6).

    §15.21.3. Reference Equality Operators == and != says:

    It is a compile-time error if it is impossible to convert the type of either operand to the type of the other by a casting conversion (§5.5). The run-time values of the two operands would necessarily be unequal (ignoring the case where both values are null).

    So, we can write down the compile-time types of each expression:

    • Employee.class is of type Class<Employee>.
    • Doctor.class is of type Class<Doctor>.
    • employee.getClass() and new Employee().getClass() are both of type Class<? extends Employee>. This means a Class representing either Employee or a subclass of it, including Doctor.
    • doctor.getClass() and new Doctor().getClass() are both of type Class<? extends Doctor>. This means a Class representing either Doctor or a subclass of it, e.g. Surgeon perhaps.

    Now we can explain all three behaviours:

    1. doctor.getClass() == new Employee().getClass() compares a Class<? extends Doctor> with Class<? extends Employee>. The first type can be converted to the second type by a casting conversion, so this is allowed.
    2. employee.getClass() == Employee.class compares a Class<? extends Employee> with a Class<Employee>. The second type can be converted to the first type by a casting conversion, so this is allowed.
    3. doctor.getClass() == Employee.class compares a Class<? extends Doctor> with a Class<Employee>. Neither type can be converted to the other by a casting conversion, so this is a compile-time error (not an exception).

    Going into bit more detail on 3., a Class<? extends Doctor> could be satisfied by Class<Doctor> or Class<Surgeon>, but not by Class<Employee> because Employee is not a subtype of Doctor. However, you could write a similar expression which has the intended result if you upcast doctor before calling getClass:

    1. ((Employee) doctor).getClass() == Employee.class is similar to case 2., so it is allowed.

    It is admittedly quite unusual to fix a type error by upcasting instead of downcasting.