我对范型的掌握

177 阅读3分钟

我的好友们, 希望当你们看到我的无知的时候, 将我的懵懂愚昧知无不言的, 或通过结构化知识, 或通过零碎的忠告告诉我. 让我能斧正自己错误的观念以及逻辑. 我想你们的点拨, 也许才是我未来三年成长的开始.

类定义:

public class GenericType<T> {
}
public interface CommonType {
    <K, V> V getItem(K t);
}
public class Food {
}
public class Fruit extends Food {
}
public class Apple extends Fruit {
}
public class Orange extends Fruit {
}

范型不能用作 instanceof:

为什么呢?

我也不知道为什么编译器要特别的强调这个. 如果 List<? extends Fruit> 这种写法不代表新定义的数据类型, 降级为 List 判断不可以吗?

请拯救我于黑暗之中吧.
if (fruits instanceof List<? extends Fruit>) { 
    // Error1: Illegal generic type for instanceof
}
if (fruits instanceof List<Fruit>) { // Error: 同 Error1
}
if (fruits instanceof List) {
}

通配符 ‘?’ 不能作用于类上:

为什么呢?

请拯救我于黑暗之中吧.
public class GenericType<? extends Food> { // Error: Unexpected wildcard
}

范型的使用范围:

作用于类上(循环范型):

注意这里的循环范型, 用在从父类返回子类类型很好用
public class GenericType<T extends GenericType<T>> { 

    private T t;

    public void setData(T t) {
        this.t = t;
    }

    public T getData() {
        return t;
    }
}

作用于函数:

public interface CommonType {
    <K, V> V getItem(K t);
}

作用于属性:

List<? extends Fruit> fruits = new ArrayList<Apple>();

范型转型归纳:

  1. 不能将涉及 TypeA 的范型赋值给涉及 TypeB 的范型.
List<Fruit> fruits = new ArrayList<Apple>(); // Error: 不能向上转型
  1. 容易混淆的异常: 对于"? extends Class" 的形式: 可以向上转型

正因为这里可以向上转型, 所以造成了对 fruits 中的 add 方法, 无法判断其范型的具体类型.

核心在于右侧实例的类型, 要能确定右侧实例的范型类型下限, 才知道添加哪些类是安全的.

(PS: add 方法在编译期间, 可以看成 “add(? extends Fruit))”

List<? extends Fruit> fruits = new ArrayList<Apple>(); 
fruits.add(new Food());// Error: 不能向上转型, 如果说确定了 Fruit  为上界
                       // 因此 List<? extends Fruit> 接收的实例, 你很难说他到底接收的是什么类型.
                       // 我们假设存在一种运行时检查机制, 可以判断出实例对象的范型.
                       // 那么 fruits.add(new xx()) 就都存在类型转换的运行时异常.

fruits.add(new Fruit());// Error: 不能向上转型
fruits.add(new Apple());  // Error: 不能向上转型
                          //   Required type: capture of ? extends Fruit
                          //   Provided: Apple
  1. 容易混淆的异常: 对于"? super Class" 的形式: 可以向上转型
List<? super Fruit> fruits = new ArrayList<Fruit>();
fruits.add(new Food());// Error: 确定了 Fruit 为下界
                       //        因此 List<? super Fruit> 接收的实例, 至少可以接受 Fruit
                       //        因此可以添加 Fruit 以及 Fruit 的子类.
fruits.add(new Fruit());
fruits.add(new Apple());
  1. 很符合逻辑的类型转换异常.
public class MyMain {
    public static void main(String[] args) {
        extendsGeneric(new GenericType<Fruit>());
        extendsGeneric(new GenericType<Food>()); // Error:  Food 不是 Fruit 的子类.
        extendsGeneric(new GenericType<Apple>());
    }

    private static <T> void extendsGeneric(GenericType<? extends Fruit> foods) {
    }
}
public class MyMain {
    public static void main(String[] args) {
        superGeneric(new GenericType<Food>());
        superGeneric(new GenericType<Fruit>());
        superGeneric(new GenericType<Apple>()); // Error: Apple 不是 Fruit 的父类
    }

    private static <T> void superGeneric(GenericType<? super Fruit> fruits) {
    }
}

使用反射实现无类别函数传递:

嗯... 我也没机会写框架. 那很难.

范型擦除后, 在运行时期还可以获取到范型类型吗, 为什么能拿到呢?

范型擦除后通过 Signature 保留住了范型的信息. 但是方法签名以及方法体中的的范型信息已经被擦除.

希望好心人能指导 Signature 相关的逻辑.
public class MyMain {
    private static class MyGenericeType extends GenericType<Integer, Food> {
    }

    public static void main(String[] args) {
        MyGenericeType myGenericeType = new MyGenericeType();
        Class myGenericeTypeClass = myGenericeType.getClass();
        System.out.println("myGenericeType class: " + myGenericeTypeClass);
        System.out.println("myGenericeType getSuperclass: " +  myGenericeTypeClass.getSuperclass());

        Type type = myGenericeTypeClass.getGenericSuperclass();
        System.out.println("myGenericeType getGenericSuperclass: " + type);
        Type[] types = ((ParameterizedType) type).getActualTypeArguments();
        System.out.println("myGenericeType Type.getActualTypeArguments: " + types.length);
        for (int i = 0; i < types.length; i++) {
            System.out.println("types[" + i + "] " + types[i] + " " + ((Class)types[i]).getCanonicalName());
        }

    Method[] methods = myGenericeType.getClass().getMethods();
    for (int i = 0; i < methods.length; i++) {
        if (methods[i].getName().contains("setData") || methods[i].getName().contains("getData")) {
            System.out.println(methods[i]);
        }
    }
}

// 1. myGenericeType.getClass(): class com.moon_rock.javalib.MyMain$MyGenericeType
// 2. myGenericeType.getSuperclass(): class com.moon_rock.javalib.generic.GenericType
// 3. 通过 getGenericSuperclass() 返回的是: sun.reflect.generics.reflectiveObjects.ParameterizedTypeImpl
// myGenericeType.getGenericSuperclass(): com.moon_rock.javalib.generic.GenericType<java.lang.Integer, com.moon_rock.javalib.bean.Food>
// 4. myGenericeType Type.getActualTypeArguments length: 2
// getActualTypeArguments 返回的数组中, 数组元素的类型: types[0] instanceof Class: true
// types[0] class java.lang.Integer java.lang.Integer 
// types[1] class com.moon_rock.javalib.bean.Food com.moon_rock.javalib.bean.Food
// 5. 下面为范型擦除
// public void com.moon_rock.javalib.generic.GenericType.setData(java.lang.Object) 
// public java.lang.Object com.moon_rock.javalib.generic.GenericType.getData()
// 下面为范型擦除
public class MyMain {
    public static void main(String[] args) {
        methods = CommonType.class.getMethods();
        for (int i = 0; i < methods.length; i++) {
            System.out.println(methods[i]);
        }
    }
} 
// public abstract java.lang.Object com.moon_rock.javalib.generic.CommonType.getItem(java.lang.Object)

也是是时候看看 java 虚拟机的实现规范了.