Java 是否像C++模板一样具有泛型推论?

Does Java have generics deduction like C++ templates?

本文关键字:一样 泛型 C++ 是否 Java      更新时间:2023-10-16

Java有像C++模板一样的泛型推论吗?例如:

class Foo<T> {}
class Foo<T extends Foo> {}

使用哪个类取决于T实际上是什么。

如果Java没有这个特性,我该如何处理这个问题(也就是说,不同类型的T可能有不同的行为(?

我发现关于泛型类型的哲学问题往往有点模糊Foo<T>;让我们用熟悉的东西来重新定义它,一个简化的List界面:

interface List<T> {
int size();
T get(int i);
boolean add(T element);
}

List<T>是包含T实例的列表。您是说您希望列表在IntegerT时具有不同的行为。

人们很容易把List<T>解读为"TList";但事实并非如此。这是一个List,只是一个普通的旧List,包含Objects。<T>是对编译器的指令:

  • 每当我在此列表中add某些内容时,请确保我尝试添加的Object可以投射到T
  • 每当我从这个列表中get某些东西时,在我使用它做任何事情之前,先把它投到T

因此,像这样编写代码:

List<Integer> list = ...
Integer i = list.get(0);

由编译器脱糖以:

List list = ...
Integer i = (Integer) list.get(0);

并像这样编写代码:

list.add(anInteger);  // Fine.
Object object = ...
list.add(object);  // Oi!

由编译器检查,在第一种情况下说"很好",在第一种情况下说"oi!您无法将Object添加到此列表中!",并且编译失败。

这就是泛型的全部内容:这是一种省略强制转换的方法,并使编译器对代码进行健全检查。

这曾经在泛型代码中手动完成:您必须跟踪列表中应该包含哪种类型的元素,并确保只添加/获取该类型的内容。

对于简单的程序来说,这是可行的;但是对于一个人(或者更贴切地说,一群人(来说,它很快就会变得太多了。从字面上看,这是不必要的认知负担,如果编译器可以为您进行检查。

因此,您不能专门化泛型,因为它只是删除了一堆强制转换。如果你不能用强制转换来做到这一点,你就不能用泛型来做到这一点。


但是你可以用泛型做专门的事情;你只需要以不同的方式思考它们。

例如,考虑Consumer接口:

interface Consumer<T> {
void accept(T t);
}

这允许您使用特定类型的实例"执行"操作。您可以为Integers 设置一个Consumer,为Objects 设置一个Consumer

AtomicInteger atInt = new AtomicInteger();
Consumer<Integer> intConsumer = anInt::incrementAndGet;
Consumer<Object> objConsumer = System.out::println;

现在,您可以有一个泛型方法,该方法采用泛型List和相同类型的Consumer(*(:

<T> void doSomething(List<T> list, Consumer<T> consumer) {
for (int i = 0; i < list.size(); ++i) {
consumer.accept(list.get(i));
}
}

所以:

List<Integer> listOfInt = ...
doSomething(listOfInt, intConsumer);
List<Object> listOfObj = ...
doSomething(listOfObj, objConsumer);

这里的重点是,虽然这里的泛型只是删除强制转换,但它检查listconsumerT是否相同。你不能写

doSomething(listOfObj, intConsumer);  // Oi!

因此,专业化来自doSomething定义之外。


(*(实际上,最好将其定义为Consumer<? super T>;请参阅什么是PECS以获取解释。

Java泛型和C++模板虽然看起来相似并试图解决类似的问题,但非常不同。

在C++中,模板的工作原理是为每个唯一类型创建一个新类(因此它被称为"模板"(。因此,例如,如果您使用vector<foo>vector<bar>,编译器将为两个类生成不同的机器代码(即使它们共享相同的模板(。很明显,模板重载没有根本障碍,因此某些模板参数将根据某些条件(例如继承(生成看起来与其他类不同的类。

另一方面,在 Java 中,泛型按类型擦除工作。Java 中的泛型类只生成一次,但泛型类型被更通用的类型替换,类型检查改为在构建时完成。

所以这段代码:

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);
}
}

在构建时,本质上在内部成为此代码:

// 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);
}
}

因此,由于 Java 具有基于类型擦除的泛型,因此Foo类只有一个实现,因此不可能根据类型参数使用不同的行为进行覆盖。

我认为最好的方法是为每个情况提供一个具有基本接口的单独类

interface FooInterface {}
class FooGeneric<T> implements FooInterface {}
class FooForFoo<T extends Foo> implements FooInterface {}

并使用工厂返回正确的类型

public FooInterface getFoo(Object o) {
// you may want to cast the object to the correct type
return (o instanceof Foo) ? new FooForFoo<>() : new FooGeneric<>() ;
}