0
点赞
收藏
分享

微信扫一扫

lora-scripts 训练IP形象

拾光的Shelly 03-28 10:30 阅读 1

什么是泛型?

泛型其实就参数化类型,也就是说这个类型类似一个变量是可变的。

为什么会有泛型?

在没有泛型之前,java中是通过Object来实现泛型的功能。但是这样做有下面两个缺陷:
1 获取值的时候必须进行强转
2 没有错误检查

Object data[] = new Object[]{"one", "two", 1, new Object()};
String str = (String) data[0];

一般来说我们会把相同类型的数据放到一起,但是有没有发现如果使用object我们可以放入任意类型的数据,编译器也不会报错,这样在使用的时候就增加了类型转换异常的概率。
那么使用泛型呢?

List<String> strList = new ArrayList<>();
strList.add("one");
// 这句代码编译器就会提醒你不能这样使用
strList.add(1);

非static的内部类,在外部类加载的时候,并不会加载它,所以它里面不能有静态变量或者静态方法。

泛型擦除

泛型信息只存在于代码编译阶段,但是在java的运行期(已经生成字节码文件后)与泛型相关的信息会被擦除掉,专业术语叫做类型擦除。

// 这个例子 最后输出的结果为 true class的结果为:java.util.ArrayList
public class Pair<T> {
    public static void main(String[] args) {
        List<Integer> integerList = new ArrayList<>();
        List<String> stringList = new ArrayList<>();
        System.out.println(Objects.equals(integerList, stringList));
        System.out.println(integerList.getClass());
        System.out.println(stringList.getClass());
    }
}

再看下面这个例子:

public class Pair<T> {
    private T data;
    public static void main(String[] args) {
        Pair<String> stringPair = new Pair<>();
        System.out.println(stringPair.getClass());
        Field[] declaredFields = stringPair.getClass().getDeclaredFields();
        Arrays.stream(declaredFields).forEach(f -> System.out.println(String.valueOf(f.getName() + " " + f.getType())));
        System.out.println("========================");
        Pair<Integer> integerPair = new Pair<>();
        System.out.println(integerPair.getClass());
        Field[] fields = integerPair.getClass().getDeclaredFields();
        Arrays.stream(fields).forEach(f -> System.out.println(String.valueOf(f.getName() + " " + f.getType())));
    }
}

从运行结果我们可以证明在运行时类型已经被擦除为Object类型
在这里插入图片描述

在泛型类被类型擦除的时候,之前泛型类中的类型参数部分如果没有指定上限,如则会被转译成普通的Object 类型,如果指定了上限,如则类型参数就被替换成类型上限。
1 没有限定泛型的界限

public class Pair<T> {
    private T first;
    private T second;
}

擦除后,没有限定泛型的界限所以是Object类型:

public class Pair {
	private Object first;
	private Object second;
}

2 限定了泛型的界限

public class Pair<T extends Comparable> {
    private T data;
    public static void main(String[] args) {
        Pair<String> stringPair = new Pair<>();
        System.out.println(stringPair.getClass());
        Field[] declaredFields = stringPair.getClass().getDeclaredFields();
        Arrays.stream(declaredFields).forEach(f -> System.out.println(String.valueOf(f.getName() + " " + f.getType())));
        System.out.println("========================");
        Pair<Integer> integerPair = new Pair<>();
        System.out.println(integerPair.getClass());
        Field[] fields = integerPair.getClass().getDeclaredFields();
        Arrays.stream(fields).forEach(f -> System.out.println(String.valueOf(f.getName() + " " + f.getType())));
    }
}

在这里插入图片描述

这就证明在擦除后:

public class Pair<T extends Comparable & Serializable> {
    private Comparable first;
    private Comparable second;
}

如果交换泛型的顺序: Pair<T extends Serializable & Comparable > 那么擦除以后的类型为Serializable,这个时候编译器会插入强制类型转换(也就是说我们获取Comparable 类型时候会强制转换),为了提高效率一般将标记接口往末尾放。

public class Pair<T extends Serializable & Comparable> {
    private T data;
    public static void main(String[] args) {
        Pair<String> stringPair = new Pair<>();
        System.out.println(stringPair.getClass());
        Field[] declaredFields = stringPair.getClass().getDeclaredFields();
        Arrays.stream(declaredFields).forEach(f -> System.out.println(String.valueOf(f.getName() + " " + f.getType())));
        System.out.println("========================");
        Pair<Integer> integerPair = new Pair<>();
        System.out.println(integerPair.getClass());
        Field[] fields = integerPair.getClass().getDeclaredFields();
        Arrays.stream(fields).forEach(f -> System.out.println(String.valueOf(f.getName() + " " + f.getType())));
    }
}

在这里插入图片描述

所谓的插入强制类型转换,就是编译器在编译泛型表达式的时候会转化为两条指令:

  • 对原始方法的调用得到Object
  • 将返回的Object类型强制转换为具体的类型。

3 泛型方法的擦除
首先我们要区分一下泛型方法,泛型方法。只有声明了的方法才是泛型方法,泛型类中的使用了泛型的成员方法并不是泛型方法。如果单纯的在方法中使用了泛型它不是泛型方法。
泛型方法:

public <E> E convert(T t) { 
}

非泛型方法:

public T getT(T t) {
}
public static <T extends Comparable> T min(T[] data) {
   return null;
}

泛型方法擦除后:

public static Comparable min(Comparable [] data) {
   return null;
}

需要注意的是泛型方法的多态是通过桥方法实现的

public class Pair<T> {
    private T time;
    public void setTime(T time) {
       this.time = time;
    }
}
// 被擦除以后
public void setTime(Object time) {
}

如果这个时候子类继承Pair,并指定了类型:

class DateInterVal extends Pair<LocalDate> {
    @Override
    public void setTime(LocalDate time) {
    }
}

这个时候如果调用Pair的setTime方法,由于多态其实底层是这样来实现的:

setTime(setTime((LocalDate) time));

总结:1 虚拟机中没有类型,只有普通的类和方法 2 所有的类型参数都会替换为他们的限定类型 3 合成桥方法来保持多态 4为保证类型安全,必要时会插入强制类型转换

泛型方法

1 在类中的泛型方法
首先我们来区分几个定义方式,看注释部分。

public class Pair<T extends Comparable & Serializable> {
    private T data;
    // 编译无法通过 因为这个方法是静态方法,所以我们不能使用T类型,但是我们可以使用E类型,因为E类型是申明的
    public static <E> E convert2E(T t) {
        return null;
    }
    // 在非静态方法的情况下 可以使用上面的类中定义的泛型T
    public <E> E convert2E(T t) {
        return null;
    }
    // 注意这里我们在静态方方法申明了一个T类型,这个T和类上的T类型是没有关联的,是一个全新的类型
    // 这个T可以和类的T是同一个类型,也可以不是同一个类型
    public static  <T> T accept(T t) {
        return null;
    }
}

2 泛型方法中的可变参数

public class Pair<T> {
    static class Dot {
        private int x;
        private int y;
        public Dot(int x, int y) {
            this.x = x;
            this.y = y;
        }
        @Override
        public String toString() {
            return "Dot{" +
                    "x=" + x +
                    ", y=" + y +
                    '}';
        }
    }
    // 泛型方法可变参数
    private static <T> void print(T ...es) {
        for (T t : es) {
            System.out.println(t  + "");
        }
    }
    public static void main(String[] args) {
        Dot dot = new Dot(1, 1);
        print(15000, "15000", dot);
    }
}

2 静态方法与泛型
一个基本原则,如果要在静态方法使用泛型的话,这个方法必须为泛型方法。

// 也就是说必须申明
public static  <T> T accept(T t) {
   return null;
}

泛型缺陷

1 不能使用基本类型实例化类型参数
也就是说没有

Pair<int> Pair<double> 类型

因为泛型在擦除以后为object类型,但是原生类型不能直接赋值为object,而是要使用包装类。
2 不能实例化类型参数
本质也是因为类型擦除导致的

String string = new T();
// 类型擦除以后,很显然是存在问题的
String string = new Object();

但是我们可以通过反射来创建一个实例:

 Pair<String> pair = Pair.class.newInstance();

3 运行时类型查询只适用于原始类型
下面这三条语句都是编译会报错的,因为虚拟中的对象总是一个特定的非泛型类型,所以类查询只能查询原始类型。

pair instanceof String;
pair instanceof Pair<String>;
pair instanceof Pair<T>;
// 这条语句是可以的 原始类型的查询
pair instanceof Pair

并且下面的语句也会返回true:

Pair<String> ps = new Pair<>();
Pair<Double> pd = new Pair<>();
System.out.println(ps.getClass() == pd.getClass());

4 不能创建参数化类型的数组
类型擦除以后变为Pair[] pars = new Pair[10]; 然后我们可以赋予pairs[0] = new Pair(); 没有编译错误,但存在运行时错误。

// 可以申明
Pair<String> [] pairs;
// 不可以实例化 也是一样的如果把String擦除 为Object 可能会导致运行时异常 不安全
Pair[] pairs = new Pair<String>[10];
// 如果是通配类型的 则可以 但是这样的话不是很安全因为里面可以存 Pair<String> 也可以存 Pair<Double> 
// 在使用的时候可能类型转换异常
Pair[] pairs = new Pair<?>[10];

5 Varargs 警告

public static <T> void addAll(Collection<T> coll, T... ts){
   // 这里其实创建了数组就违背了不能创建数组
   for(T t : ts) {
       coll.add(t);
   }
}
public static void main(String[] args) {
   Collection<Pair<String>> table = new ArrayList<>();
   Pair<String> pair1 = new Pair<>();
   Pair<String> pair2 = new Pair<>();
   addAll(table, pair1, pair2);
}
static class Pair<T> {

}

6 不能实例化类型变量
T[] array = new T[10]
类型擦除后上述定义变为Object[] array = new Object[10]; 这样一来我们可以将任何类型赋予array[0], 比如array[0] = “1”; 编译器不会报错,但运行时在使用的时候就有可能会出错了。

// 编译不会通过
T t = new T();
// 编译不会通过
T[] array = new T[10]

这里也可以通过反射来进行:

public T[] demo() {
    T data[] = (T[]) Array.newInstance(Pair.class, 2);
    return data;
}

7 泛型类的静态上下文中类型变量无效
也就是静态不能和泛型一起使用,如果一定要一起使用的话,必须申明。

// 通不过
public static T t;
// 如果一定要使用则需要申明
public static <T> void addAll(Collection<T> coll, T... ts){
     for(T t : ts) {
         coll.add(t);
     }
 }

8 不能抛出或捕获泛型类的实例

public static <T extends Throwable> void doWork() {
  try {
	// 会产生编译错误
  } catch (T e) {

  }
}

泛型中的继承

继承泛型类时,必须对父类中的类型参数进行初始化
1 使用泛型初始化父类的泛型

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

2 使用具体的类型

public class Bar extends Foo<String> {

}

特别注意:

// 这里的继承关系是 Integer 和 Double 继承
Box<Number> box = new Box<Number>();
box.add(new Integer(10)); 
box.add(new Double(10.1));
// Box<Integer> Box<Number> 他们并不是继承关系 这一定要注意 

原文链接
以 Collections 类为例,ArrayList 实现 List ,List 继承 Collection 。 所以 ArrayList 是 List 的一个子类型,它是 Collection 的一个子类型。 只要不改变类型参数,子类型关系在类型之间保留。
在这里插入图片描述

泛型中的限定

在通配符类型中,允许类型参数发生变化。
1 子类限定

// 类上
class Son<T extends Foo> {
}
// 方法上
public <T> T demo2(Bar<? extends Number> data) {
}
// 方法的申明
public <T extends Integer> T demo3(Bar<? super Integer> data) {
}

2 超类限定

// 方法上
public <T> T demo1(Bar<? super Integer> data) {
}
举报

相关推荐

0 条评论