很多使用泛型的小伙伴,都会有一个疑惑:为什么有的方法返回值前带T、K, V之类的标记,而有的方法返回值前又什么都不带呢?就像这样: // 实体基类 class Entity { public String toString
很多使用泛型的小伙伴,都会有一个疑惑:为什么有的方法返回值前带<T>、<K, V>之类的标记,而有的方法返回值前又什么都不带呢?就像这样:
// 实体基类class Entity {
public String toString() {
return "Entity";
}
}
// 用户类
class User extends Entity {
public String toString() {
return "User";
}
}
// 用户Dao
class UserDao {
public String toString() {
return "UserDao";
}
}
/**
* 带<E>标记与不带<E>标记的比较
*/
public class GenericsClass<T extends Entity> {
// 不带标记
public void notSign(T t) {
System.out.println("notSign " + t.toString());
}
// 带<T>标记
public <T> void hasSign(T e) {
System.out.println("hasSign " + e.toString());
}
public static void main(String[] args) {
GenericsClass<Entity> clazz = new GenericsClass<>();
Entity entity = new Entity();
User user = new User();
UserDao userDao = new UserDao();
System.out.println("不带标记的方法:");
clazz.notSign(entity);
clazz.notSign(user);
// 不能编译通过的
// 因为在GenericsClass<T extends Entity>中已经限定了全局的T为Entity及其子类,
// 所以不能再加入UserDao;
// clazz.notSign(userDao);
System.out.println("带标记的方法:");
clazz.hasSign(entity);
clazz.hasSign(user);
// 带上前缀<E>,就是在告诉编译器:这是新指定的一个类型,代表该方法自己独有的某个类,
// 跟GenericsClass<T extends Entity>中的Entity及其子类没有任何关系
// 或者说
// hasSign方法重新定义泛型T、隐藏或者代替了GenericsClass<T>中的T,不再受限于Entity及其子类
clazz.hasSign(userDao);
}
}
因此,返回值前面的<T>的作用是「重新定义泛型」,因此方法参数类型不受对象泛型类型的限制。在Java新的Stream API中有大量这种带前缀的用法,例如:
- ArrayList.java:public <T> T[] toArray(T[] a)
- Optional.java:public static <T> Optional<T> of(T value)
- Collectors.java:public static <T> Collector<T, ?, List<T>> toList()
泛型,作为Java的一个基础特性,并不是一点毛病都没有,「泛型擦除」就是至今还未解决的一个问题(这个问题其实对于大多数人来说可以不用知道,因为实际应用中极少出现这种场景,感兴趣的话稍稍了解一下,不喜可绕过)。
所谓泛型擦除,是这样一个问题:
class User {}class Product {}
class Shop<V> {}
class Particle<PPP, QQQ> {}
public class LostInformation<T> {
public static void main(String[] args) {
// ArrayList<String>和ArrayList<Integer>应该是不同的类型,但结果是它们完全相等
Class<?> c1 = new ArrayList<String>().getClass();
Class<?> c2 = new ArrayList<Integer>().getClass();
System.out.println(c1 == c2);
// 无法从泛型获得任何有关参数类型的信息
List<User> list = new ArrayList<>();
Map<User, Product> map = new HashMap<>();
Shop<User> shop = new Shop<>();
Particle<Long, Double> p = new Particle<>();
System.out.println(Arrays.toString(list.getClass().getTypeParameters()));
System.out.println(Arrays.toString(map.getClass().getTypeParameters()));
System.out.println(Arrays.toString(shop.getClass().getTypeParameters()));
System.out.println(Arrays.toString(p.getClass().getTypeParameters()));
}
}
可以看见,Particle<Long, Double>中关于Long和Double的信息已经完全丢失了,只剩下了最初的PPP和QQQ,真的应了那句话:历经千帆,归来仍是是少年!
但这是编程啊,不是文案啊~,不能这样丢掉了!