Reputation:
Does Java has generics deduction like C++ templates? For example:
class Foo<T> {}
class Foo<T extends Foo> {}
Which class is used depends on what T
is actually is.
If Java do not have this feature, how I can deal with this problem(that is, different type of T
may have different behaviours)?
Upvotes: 1
Views: 570
Reputation: 140494
I find that philosophical questions about generics types like Foo<T>
tend to be a bit vague; let's reframe this in terms of something familiar, a simplified List
interface:
interface List<T> {
int size();
T get(int i);
boolean add(T element);
}
A List<T>
is a list which contains instances of T
. You are saying you want the list to have different behavior when, say, T
is Integer
.
It's tempting to read List<T>
as "a List
of T
s"; but it's not. It's a List
, just a plain old List
, containing Object
s. The <T>
is an instruction to the compiler:
add
something to this list, make sure the Object
I try to add can be cast to a T
.get
something from this list, cast it to a T
before I do anything with it.So, code like this:
List<Integer> list = ...
Integer i = list.get(0);
is desugared by the compiler to:
List list = ...
Integer i = (Integer) list.get(0);
And code like this:
list.add(anInteger); // Fine.
Object object = ...
list.add(object); // Oi!
is checked by the compiler, which says "fine" in the first case, and "oi! You can't add an Object
to this list!", and compilation fails.
That's really all generics is: it's a way of eliding casts, and getting the compiler to sanity check your code.
This used to be done by hand in pre-generics code: you had to keep track of what type of element should be in the list, and make sure you only add/get things of that type out.
For simple programs, that's feasible; but it quickly becomes too much for one person (or, more pertinently, a team of people) to hold in their heads. And it's - literally - unnecessary cognitive burden, if the compiler can do that checking for you.
So, you can't specialize generics, because it's simply removing a bunch of casts. If you can't do it with a cast, you can't do it with generics.
But you can do specialized things with generics; you just have to think about them in a different way.
For example, consider the Consumer
interface:
interface Consumer<T> {
void accept(T t);
}
This allows you to "do" things with instances of a particular type. You could have a Consumer
for Integer
s, and a Consumer
for Object
s:
AtomicInteger atInt = new AtomicInteger();
Consumer<Integer> intConsumer = anInt::incrementAndGet;
Consumer<Object> objConsumer = System.out::println;
Now, you can have a generic method which takes a generic List
and a Consumer
of the same type (*):
<T> void doSomething(List<T> list, Consumer<T> consumer) {
for (int i = 0; i < list.size(); ++i) {
consumer.accept(list.get(i));
}
}
So:
List<Integer> listOfInt = ...
doSomething(listOfInt, intConsumer);
List<Object> listOfObj = ...
doSomething(listOfObj, objConsumer);
The point here is that while generics here are simply removing casts, it's also checking that the T
is the same for list
and consumer
. You can't write
doSomething(listOfObj, intConsumer); // Oi!
So, the specialization comes from outside the definition of doSomething
.
(*) Actually, it's better to define this as Consumer<? super T>
; see What is PECS for an explanation.
Upvotes: 2
Reputation: 40884
Java generics and C++ template, while they look similar and attempt to solve similar problems, are very different.
In C++, templates work by creating a new class for each unique type it is unsed for (hence why it's called "templates"). So if you use vector<foo>
and vector<bar>
, for instance, the compiler will generate different machine code for the two classes (even though they share the same template). It's clear to see then that there's no fundamental obstacle to having template overloading, so that some template arguments will generate classes that look different from others, based on certain conditions (such as inheritance).
In Java, on the other hand, generics work by type erasure. A generic class in Java is only generated once, but the generic types are replaced by more general types, and the type checking is done at build time instead.
So this code:
public class Foo<T extends Bar> {
public static T do_something(T value) {
// ...
}
}
public class Bar { }
public class Baz extends Bar { }
public class UseFoo {
void use_foo() {
Baz baz = new Baz();
baz = Foo.do_something(baz);
}
}
Essentially becomes this code internally when it's being built:
// note how the generic type is gone and replaced by `Bar`
public class Foo {
public static Bar do_something(Bar value) {
// ...
}
}
public class Bar { }
public class Baz extends Bar { }
public class UseFoo {
void use_foo() {
Baz baz = new Baz();
// note the cast here, since we know (because of generics)
// that this type must be of type `Baz` when given the input of type `Baz`
baz = (Baz) Foo.do_something(baz);
}
}
So because Java has generics based on type erasure, there will only ever be one implementation of the Foo
class, so it's not possible to override with different behavior based on the type arguments.
Upvotes: 1
Reputation: 401
I think the best way to do this is to have a separate class for each case with a basic interface
interface FooInterface {}
class FooGeneric<T> implements FooInterface {}
class FooForFoo<T extends Foo> implements FooInterface {}
And use a Factory to return the correct type
public FooInterface getFoo(Object o) {
// you may want to cast the object to the correct type
return (o instanceof Foo) ? new FooForFoo<>() : new FooGeneric<>() ;
}
Upvotes: 0