Java 是否像C++模板一样具有泛型推论?
Does Java have generics deduction like C++ templates?
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
实例的列表。您是说您希望列表在Integer
T
时具有不同的行为。
人们很容易把List<T>
解读为"T
的List
";但事实并非如此。这是一个List
,只是一个普通的旧List
,包含Object
s。<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);
}
这允许您使用特定类型的实例"执行"操作。您可以为Integer
s 设置一个Consumer
,为Object
s 设置一个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);
这里的重点是,虽然这里的泛型只是删除强制转换,但它也检查list
和consumer
的T
是否相同。你不能写
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<>() ;
}
- 错误处理.将系统错误代码映射到泛型
- 如果有一个模板构造函数只有一个泛型参数,为什么我必须有一个复制构造函数
- 链表的泛型函数remove()与成员函数remove)
- 给定一个类型,如何派生一个泛型更广泛的类型(例如,用于溢出安全求和)?
- 模板化接口 - 创建一个泛型模板类以返回任何容器
- 如何编写将要继承的泛型代码?
- C++17 如何保存泛型可调用对象以供以后使用
- 使用宏扩展的泛型:为什么指令缓存使用不当?
- C++泛型类错误,问题出在哪里?
- C++泛型类,单独实现?
- 将参数传递给泛型 lambda 时复制构造函数不正确
- 泛型枚举和其他类型的重载模板函数
- 使用泛型类型推送到堆栈时出现问题
- 可变参数泛型 lambda 和函数重载
- C++ 泛型和多态性:这种模式可行吗?
- 这些语句是否等效(静态变量、常量变量和泛型)
- Java 是否像C++模板一样具有泛型推论?
- 为堆栈实现泛型集合
- 以特征类型作为参数的泛型函数回调
- 如何在容器中指定模板化别名的泛型类型