KTV和泛型(2)

83 阅读2分钟

您好,我是湘王,这是我的掘金小站,欢迎您来,欢迎您再来~

很多使用泛型的小伙伴,都会有一个疑惑:为什么有的方法返回值前带、<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);
    }
}

 

因此,返回值前面的的作用是「重新定义泛型」,因此方法参数类型不受对象泛型类型的限制。在Java新的Stream API中有大量这种带前缀的用法,例如:

  • ArrayList.java:public T[] toArray(T[] a)
  • Optional.java:public static Optional of(T value)
  • Collectors.java:public static Collector<T, ?, List> 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,真的应了那句话:历经千帆,归来仍是是少年!

但这是编程啊,不是文案啊~,不能这样丢掉了!

感谢您的大驾光临!咨询技术、产品、运营和管理相关问题,请关注后留言。欢迎骚扰,不胜荣幸~