user2269707
user2269707

Reputation:

Does Java have generics deduction like C++ templates?

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

Answers (3)

Andy Turner
Andy Turner

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 Ts"; but it's not. It's a List, just a plain old List, containing Objects. The <T> is an instruction to the compiler:

  • Whenever I add something to this list, make sure the Object I try to add can be cast to a T.
  • Whenever I 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 Integers, and a Consumer for Objects:

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

Freyja
Freyja

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

A.Oubidar
A.Oubidar

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

Related Questions