Steve.NayLinAung
Steve.NayLinAung

Reputation: 5155

Type cast to Generic

I fairly understand about the Generics. According to my understanding, you have to provide Type parameter when calling a Generic class or method. For example:

List<String> strings = new ArrayList<String>();

But I have some difficulties in understanding following code.

public static <T extends Employee> T findById(@NonNull factory, int employeeId) {
    return (T) factory.findEmployeeById(employeeId);
}

Employee has a lot of subtypes. For example:

public class Programmer extends Employee {}

public class Engineer extends Employee {}

By using this findById method, I can successfully implement like the following.

Programmer employee = EmployeeUtils.findById(factory, 2);

In the above method of findById, how would someone know what is the type of T? There is only hint about it is a subtype of Employee. It cannot know whether it is a Programmer or Engineer. How can Java compiler successfully do the type cast at the runtime to the concrete subclass (Programmer or Engineer)?

Please guide me any further reading if you want to recommend about Java Generics.

Upvotes: 0

Views: 146

Answers (3)

Andy Turner
Andy Turner

Reputation: 140309

How can Java compiler successfully do the type cast at the runtime to Programmer (or Engineer)?

The Java compiler doesn't do casts.

A cast is simply a way of saying to the compiler: "I know more than you; trust me." The compiler will still try to stop you doing casts that it can determine are definitely unsafe (like casting a String to an Integer), but by casting you are taking responsibility for type safety away from the compiler. You should only cast if you know - by the semantics of your code/system - that it will be safe.

This particular pattern is common for retrieving heterogeneous entities from a repository; but it is not type safe.

The onus is on callers of the method to only call the method with the "right kind of ID". The problem is that there is no indication in the code that it might fail.

The reason this pattern always irks me is that it hides the actual place the problem occurs. Recall that Java generics are basically just an elision of casts. This means that the method "really" looks like this:

public static Employee findById(@NonNull factory,int employeeId) {
  // There is actually no cast here!
  return factory.findEmployeeById(employeeId);
}

And the call site looks like this:

// The cast is here instead!
SpecificEmployeeType e = (SpecificEmployeeType) findById(someId);

(Try writing the code explicitly like this, and compare the bytecode with the generic version)

As such, whilst you can suppress the warning in the findById method, the actual exception occurs at the call site. So, the warning​ - the indication that a problem may occur there - is in the wrong place.

If you write it explicitly without the generics, you can see exactly where the problem actually occurs.

People often say that they want to use generics in this way, because it is "cleaner": you don't need the explicit cast or suppression at every call site. Personally, I want that extra stuff there: it makes it look like there is something dangerous going on there (which there is!) and so you know that you need to take extra care.


It is also worth pointing out that you should not add @SuppressWarnings("unchecked") to your code - either in the findById method, or at the call sites - unless you can be absolutely certain that the cast is safe.

Like casting, warning suppression is a way of taking responsibility for things that the compiler cannot prove. By suppressing warnings as a matter of course, you are just ignoring help the compiler is trying to offer. As with any cautionary advice, you are free to ignore it, but it is foolhardy to do so without fully understanding the consequences.

Upvotes: 1

Vladimir B.
Vladimir B.

Reputation: 41

The returning type T will be Employee. The following code confirms this.

public class GenericTest {
    public abstract static class Employee {
    }

    public static class Programmer extends Employee {
    }

    public static class Engineer extends Employee {
        void testMethod(){
            System.out.println("Engineer method");
        }
    }

    public static void main(String[] args) {
        getEmployee().testMethod();
    }

    public static <T extends Employee> T getEmployee() {
        return (T) new Engineer();
    }
}

If we try to run the code, we get a compilation error

Error:(28, 22) java: cannot find symbol symbol: method engineerMethod() location: class GenericTest.Employee

To fix the error, you need to add an "abstract method" or "method" to the abstract class like this:

public abstract static class Employee {
    void testMethod(){};
}

Upvotes: 0

SilverNak
SilverNak

Reputation: 3381

The compiler does not check the type cast in this case. You should get a warning telling you about an unchecked cast to T, when your compiler options are set accordingly.

The check will be executed at runtime. Consider the following code:

public class GenericTest {
    public abstract static class Employee {}

    public static class Programmer extends Employee {}

    public static class Engineer extends Employee {}

    public static void main(String[] args) {
        Programmer p = getEmployee();
    }

    public static <T extends Employee> T getEmployee() {
        return (T) new Engineer();
    }
}

This code will compile with a warning Type safety: Unchecked cast from GenericTest.Engineer to T. At runtime a ClassCastException will occur:

java.lang.ClassCastException: GenericTest$Engineer cannot be cast to GenericTest$Programmer

When you do this cast, you should be sure that the type is correct at runtime, otherwise ugly exceptions will fly.

Generics are type-erased at compile time. Thus, when you analyze the compiled bytecode, you will see that the method returns an Employee according to it's signature. Actually the compiled bytecode of above example is nearly identical to the bytecode compiled from the following code (having the same classes):

public static void main(String[] args) {
    Programmer p = (Programmer) getEmployee();
}

public static Employee getEmployee() {
    return new Engineer();
}

Hopefully, this will help you to understand what happens at runtime.

For further reading consider the Oracle Tutorial for Generics

Upvotes: 1

Related Questions