java泛型学习

java泛型学习

1.为什么需要使用泛型

  • Java 5 添加了泛型,提供了编译时类型安全检测机制,该机制允许程序员在编译时检测到非法的类型。可以将运行时异常在编译时复现,方便处理。根据下面的例子可以看出如果不指定泛型那么list集合每添加的一个元素都是被当成object对象添加进去的,此时取出数据时就需要给每一种数据类型进行数据类型的转换。(我们不期望每次取出数据都进行强制类型转换,而且强制类型转换会带来很多错误,最适合的就是每个集合放的数据类型都一样)
    public void noFanXing()
    {
        List list =new ArrayList();
        list.add(2);
        list.add("a string");

        //String str = list.get(1); 错误写法,类型不匹配
       	String str = (String)list.get(1);
    }

2.与泛型相关的一些概念(使用泛型所引入的一些新的问题,需要新的技术解决)

Java 泛型从 Java 7 开始,编译器可以自动类型判断,可以省略构造器中的泛型,下面是一个Java 7 泛型例子:声明时指定泛型后,初始化对象可以省略。

List<String> strings = new ArrayList<>();
  • 泛型类 同时使用泛型可以提高代码复用,例如有一个需求对不同数据类型的数据进行同样的操作,如果使用泛型则相当于c++里面的模板类,可以通过泛型创造出不同的类,然后不同的类,从而达到对不同的数据类型起到同样的运算,增加代码的灵活性。
使用不同的模板类型初始化类,则生成的就是不同的类对象
public class NormalGeneric<K> {
    private K data;
 
    public NormalGeneric(K data) {
        this.data = data;
    }
}
NormalGeneric<String> normalGeneric = new NormalGeneric<>();
  • 泛型方法 泛型方法同泛型类一样,目的是方便代码复用,使用不同的**(数据类型)**参数调用泛型方法,则可以用同一套逻辑处理出不一样的逻辑结果。
尖括号<T> 这个表明这个方法是一个泛型方法,表示这个方法用到了T表示泛型
public static <T> T addAndReturn(T element, Collection<T> collection){
    collection.add(element);
    return element;
}
  • 泛型接口:泛型接口,就是接口中使用到了泛型,和泛型类是类似的,但是需要考虑一下泛型接口和继承的关系。两种情况。
    -接口也可以声明泛型。
泛型接口语法形式:Content<T>这个表示的是类型,就如同声明了一个类一样

public interface Content<T> {
    T text();
}

泛型接口有两种实现方式:

实现接口的子类明确声明泛型类型,此时的实现类,自己不是一个泛型类了,而是一个具体的类了。

public class GenericsInterfaceDemo01 implements Content<Integer> {
    private int text;

    public GenericsInterfaceDemo01(int text) {
        this.text = text;
    }

    @Override
    public Integer text() { return text; }

    public static void main(String[] args) {
        GenericsInterfaceDemo01 demo = new GenericsInterfaceDemo01(10);
        System.out.print(demo.text());
    }
}
// Output:
// 10

实现接口的子类不明确声明泛型类型,此时子类还是一个泛型类,可以通过赋予不用的类型参数而生成不同的泛型类对象。或者说不同的类。

public class GenericsInterfaceDemo02<T> implements Content<T> {
    private T text;

    public GenericsInterfaceDemo02(T text) {
        this.text = text;
    }

    @Override
    public T text() { return text; }

    public static void main(String[] args) {
        GenericsInterfaceDemo02<String> gen = new GenericsInterfaceDemo02<>("ABC");
        System.out.print(gen.text());
    }
}
// Output:
// ABC
  • 类型擦除
    Java 语言引入泛型是为了在编译时提供更严格的类型检查,并支持泛型编程。不同于 C++ 的模板机制,Java 泛型是使用类型擦除来实现的,使用泛型时,任何具体的类型信息都被擦除了。
    那么,类型擦除做了什么呢?它做了以下工作:

把泛型中的所有类型参数替换为 Object,如果指定类型边界,则使用类型边界来替换。因此,生成的字节码仅包含普通的类,接口和方法。
擦除出现的类型声明,即去掉 <> 的内容。比如 T get() 方法声明就变成了 Object get() ;List 就变成了 List。(List本来就是使用的object)如有必要,插入类型转换以保持类型安全。
生成桥接方法以保留扩展泛型类型中的多态性。类型擦除确保不为参数化类型创建新类;因此,泛型不会产生运行时开销。
让我们来看一个示例:

public class GenericsErasureTypeDemo {
    public static void main(String[] args) {
        List<Object> list1 = new ArrayList<Object>();
        List<String> list2 = new ArrayList<String>();
        System.out.println(list1.getClass());
        System.out.println(list2.getClass());
    }
}
// Output:
// class java.util.ArrayList
// class java.util.ArrayList

示例说明:
上面的例子中,虽然指定了不同的类型参数,但是 list1 和 list2 的类信息却是一样的。

这是因为:使用泛型时,任何具体的类型信息都被擦除了。这意味着:ArrayList 和 ArrayList 在运行时,JVM 将它们视为同一类型。
此处的核心观点表达的就是,泛型类是不带来额外开销了,虽然看起来使用不同的类型参数对泛型类进行初始化,得到的是不一样的类,进而new出来的也是不一样的对象,但是java的类型擦除机制将所有的泛型参数都当做object进行处理,从而使所有的泛型类实例对象都是同一对象。但是我们使用时理解时是不应该将其当成同一个类的。

  1. 泛型和继承
    泛型不能用于显式地引用运行时类型的操作之中,例如:转型、instanceof 操作和 new 表达式。因为所有关于参数的类型信息都丢失了。当你在编写泛型代码时,必须时刻提醒自己,你只是看起来好像拥有有关参数的类型信息而已。

正是由于泛型时基于类型擦除实现的,所以,泛型类型无法向上转型。
向上转型是指用子类实例去初始化父类,这是面向对象中多态的重要表现。

初始化的意思就是赋值。
在这里插入图片描述
Integer 继承了 Object;ArrayList 继承了 List;但是 List 却并非继承了 List。

这是因为,泛型类并没有自己独有的 Class 类对象。比如:并不存在 List.class 或是 List.class,Java 编译器会将二者都视为 List.class。(此处涉及到了类型擦除的知识点,但是按照类型擦除的观点同样的类型应该是可以相互赋值的,所以我们需要按c++的模板来理解泛型类,每个泛型类用不同的参数初始化出来的实例对象都是不同的类,而这些类又没有继承关系,所以不能相互转化,也级不能相互赋值)

List<Integer> list = new ArrayList<>();
List<Object> list2 = list; // Erorr
  • 类型边界
    有时您可能希望限制可在参数化类型中用作类型参数的类型。类型边界可以对泛型的类型参数设置限制条件。例如,对数字进行操作的方法可能只想接受 Number 或其子类的实例。

要声明有界类型参数,请列出类型参数的名称,然后是 extends 关键字,后跟其限制类或接口。

类型边界的语法形式如下:

<T extends XXX>

public class GenericsExtendsDemo01 {
    static <T extends Comparable<T>> T max(T x, T y, T z) {
    //上面的示例声明了一个泛型方法,类型参数 T extends Comparable<T> 表明传入方法中的类型必须实现了 Comparable 接口。
        T max = x; // 假设x是初始最大值
        if (y.compareTo(max) > 0) {
            max = y; //y 更大
        }
        if (z.compareTo(max) > 0) {
            max = z; // 现在 z 更大
        }
        return max; // 返回最大对象
    }

    public static void main(String[] args) {
        System.out.println(max(3, 4, 5));
        System.out.println(max(6.6, 8.8, 7.7));
        System.out.println(max("pear", "apple", "orange"));
    }
}
// Output:
// 5
// 8.8
// pear

  • 类型通配符
    类型通配符一般是使用 ? 代替具体的类型参数。例如 List<?> 在逻辑上是 List ,List 等所有 List<具体类型实参> 的父类。
  • 上界通配符
    可以使用上界通配符来缩小类型参数的类型范围。

它的语法形式为:<? extends Number>

  • 下界通配符
    下界通配符将未知类型限制为该类型的特定类型或超类类型。

🔔 注意:上界通配符和下界通配符不能同时使用。
它的语法形式为:<? super Number>
无界通配符有两种应用场景

  • 可以使用 Object 类中提供的功能来实现的方法。
  • 使用不依赖于类型参数的泛型类中的方法。
  • 方法和泛型类没有关系的时候使用,同时需要对多个不同的泛型对象进行处理
    语法形式:<?>
public class GenericsUnboundedWildcardDemo {
    public static void printList(List<?> list) {
        for (Object elem : list) {
            System.out.print(elem + " ");
        }
        System.out.println();
    }

    public static void main(String[] args) {
        List<Integer> li = Arrays.asList(1, 2, 3);
        List<String> ls = Arrays.asList("one", "two", "three");
        printList(li);
        printList(ls);
    }
}
// Output:
// 1 2 3
// one two three
  • 通配符和向上转型
    前面,我们提到:泛型不能向上转型。但是,我们可以通过使用通配符来向上转型。
public class GenericsWildcardDemo {
    public static void main(String[] args) {
        List<Integer> intList = new ArrayList<>();
        List<Number> numList = intList;  // Error

        List<? extends Integer> intList2 = new ArrayList<>();
        List<? extends Number> numList2 = intList2;  // OK
    }

参考链接深入理解java泛型来自博客园
java泛型学习总结
深入理解Java泛型

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值