Java基础(二十):泛型

有时候,不是因为你没有能力,也不是因为你缺少勇气,只是因为你付出的努力还太少,所以,成功便不会走向你。而你所需要做的,就是坚定你的梦想,你的目标,你的未来,然后以不达目的誓不罢休的那股劲,去付出你的努力,成功就会慢慢向你靠近。

导读:本篇文章讲解 Java基础(二十):泛型,希望对大家有帮助,欢迎收藏,转发!站点地址:www.bmabk.com,来源:原文

Java基础系列文章

Java基础(一):语言概述

Java基础(二):原码、反码、补码及进制之间的运算

Java基础(三):数据类型与进制

Java基础(四):逻辑运算符和位运算符

Java基础(五):流程控制语句

Java基础(六):数组

Java基础(七):面向对象编程

Java基础(八):封装、继承、多态性

Java基础(九):Object 类的使用

Java基础(十):关键字static、代码块、关键字final

Java基础(十一):抽象类、接口、内部类

Java基础(十二):枚举类

Java基础(十三):注解(Annotation)

Java基础(十四):包装类

Java基础(十五):异常处理

Java基础(十六):String的常用API

Java基础(十七):日期时间API

Java基础(十八):java比较器、系统相关类、数学相关类

Java基础(十九):集合框架

Java基础(二十):泛型

Java基础(二十一):集合源码

Java基础(二十二):File类与IO流

Java基础(二十三):反射机制



一、泛型概述

  • 在JDK5.0之前只能把元素类型设计为Object
  • JDK5.0时Java引入了“参数化类型(Parameterized type)”的概念,允许我们在创建集合时指定集合元素的类型
  • 所谓泛型,就是允许在定义类、接口时通过一个标识表示类中某个属性的类型或者是某个方法的返回值或参数的类型

1、集合中使用泛型

  • 集合中没有使用泛型时

在这里插入图片描述

  • 集合中使用泛型时

在这里插入图片描述
举例:

//泛型在List中的使用
@Test
public void test1(){
    //举例:将学生成绩保存在ArrayList中
    //标准写法:
    //ArrayList<Integer> list = new ArrayList<Integer>();
    //jdk7的新特性:类型推断
    ArrayList<Integer> list = new ArrayList<>();

    list.add(56); //自动装箱
    list.add(76);
    list.add(88);
    list.add(89);
    //当添加非Integer类型数据时,编译不通过
    //list.add("Tom");//编译报错

    Iterator<Integer> iterator = list.iterator();
    while(iterator.hasNext()){
        //不需要强转,直接可以获取添加时的元素的数据类型
        Integer score = iterator.next();
        System.out.println(score);
    }
}

二、自定义泛型结构

1、泛型的基础说明

  • <类型>这种语法形式就叫泛型
  • <类型>的形式我们称为类型参数,这里的”类型”习惯上使用T表示,是Type的缩写。即:<T>
  • <T>:代表未知的数据类型,我们可以指定为<String>,<Integer>,<Circle>等
  • 类比方法的参数的概念,我们把<T>,称为类型形参,将<Circle>称为类型实参,有助于我们理解泛型
  • 这里的T,可以替换成K,V等任意字母
  • 声明类或接口时,在类名或接口名后面声明泛型类型,我们把这样的类或接口称为泛型类泛型接口
【修饰符】 class 类名<类型变量列表>extends 父类】 【implements 接口们】{
    
}
【修饰符】 interface 接口名<类型变量列表>implements 接口们】{
    
}

//例如:
public class ArrayList<E>    
public interface Map<K,V>{
    ....
} 
  • 声明方法时,在【修饰符】与【返回值类型】之间声明类型变量,我们把声明了类型变量的方法,称为泛型方法
[修饰符] <类型变量列表> 返回值类型 方法名([形参列表])[throws 异常列表]{
    //...
}

//例如:java.util.Arrays类中的
public static <T> List<T> asList(T... a){
    ....
}

2、自定义泛型类或泛型接口

  • 当我们在类或接口中定义某个成员时,该成员的相关类型是不确定的
  • 而这个类型需要在使用这个类或接口时才可以确定,那么我们可以使用泛型类、泛型接口

2.1、说明

  • 我们在声明完自定义泛型类以后,可以在类的内部(比如:属性、方法、构造器中)使用类的泛型
  • 我们在创建自定义泛型类的对象时,可以指明泛型参数类型
    • 一旦指明,内部凡是使用类的泛型参数的位置,都具体化为指定的类的泛型类型
  • 如果在创建自定义泛型类的对象时,没有指明泛型参数类型
    • 那么泛型将被擦除,泛型对应的类型均按照Object处理,但不等价于Object
    • 经验:泛型要使用一路都用。要不用,一路都不要用
  • 泛型的指定中必须使用引用数据类型。不能使用基本数据类型,此时只能使用包装类替换
  • 除创建泛型类对象外,子类继承泛型类时、实现类实现泛型接口时,也可以确定泛型结构中的泛型参数
    • 如果我们在给泛型类提供子类时,子类也不确定泛型的类型,则可以继续使用泛型参数
    • 我们还可以在现有的父类的泛型参数的基础上,新增泛型参数

2.2、注意

  • 泛型类可能有多个参数,此时应将多个参数一起放在尖括号内。比如:<E1,E2,E3>
  • JDK7.0 开始,泛型的简化操作:ArrayList<Fruit> flist = new ArrayList<>()
  • 不能使用new E[]。但是可以:E[] elements = (E[])new Object[capacity]
    • 参考:ArrayList源码中声明:Object[] elementData,而非泛型参数类型数组
  • 在类/接口上声明的泛型,在本类或本接口中即代表某种类型,但不可以在静态方法中使用类的泛型
    • 静态方法需要在类加载初始化默认值,类泛型在创建对象时候才能确定类型,所以自洽
  • 异常类不能是带泛型的

举例1:

class Person<T> {
    // 使用T类型定义变量
    private T info;
    // 使用T类型定义一般方法
    public T getInfo() {
        return info;
    }
    public void setInfo(T info) {
        this.info = info;
    }
    // 使用T类型定义构造器
    public Person() {
    }
    public Person(T info) {
        this.info = info;
    }
    // static的方法中不能声明泛型
    //public static void show(T t) {
    //
    //}
    // 不能在try-catch中使用泛型定义
    //public void test() {
        //try {
        //
        //} catch (MyException<T> ex) {
        //
        //}
    //}
}

举例2:

class Father<T1, T2> {
}
// 子类不保留父类的泛型
// 1)没有类型 擦除
class Son1 extends Father {// 等价于class Son extends Father<Object,Object>{
}
// 2)具体类型
class Son2 extends Father<Integer, String> {
}
// 子类保留父类的泛型
// 1)全部保留
class Son3<T1, T2> extends Father<T1, T2> {
}
// 2)部分保留
class Son4<T2> extends Father<Integer, T2> {
}

3、自定义泛型方法

  • 如果我们定义类、接口时没有使用<泛型参数>
  • 但是某个方法形参类型不确定时,这个方法可以单独定义<泛型参数>

3.1、说明

  • 泛型方法的格式:
[访问权限]  <泛型>  返回值类型  方法名([泛型标识 参数名称])  [抛出的异常]{
    
}
  • 方法,也可以被泛型化,与其所在的类是否是泛型类没有关系
  • 泛型方法中的泛型参数在方法被调用时确定
  • 泛型方法可以根据需要,声明为static

举例1:

public class DAO {
    public <E> E get(int id, E e) {
        E result = null;
        return result;
    }
}

举例2:

public static <T> void fromArrayToCollection(T[] a, Collection<T> c) {
    for (T o : a) {
        c.add(o);
    }
}

public static void main(String[] args) {
    Object[] ao = new Object[100];
    Collection<Object> co = new ArrayList<Object>();
    fromArrayToCollection(ao, co);

    String[] sa = new String[20];
    Collection<String> cs = new ArrayList<>();
    fromArrayToCollection(sa, cs);
}

三、泛型在继承上的体现

  • 如果B是A的一个子类型(子类或者子接口)
    • 而G是具有泛型声明的类或接口
    • G<B>并不是G<A>的子类型!
  • 比如:String是Object的子类,但是List<String>并不是List<Object>的子类

在这里插入图片描述

public void testGenericAndSubClass() {
    Person[] persons = null;
    Man[] mans = null;
    //Person[] 是 Man[] 的父类
    persons = mans;

    Person p = mans[0];

    // 在泛型的集合上
    List<Person> personList = null;
    List<Man> manList = null;
    //personList = manList;(报错)
}

四、通配符的使用

  • ? 为泛型非限定通配符,表示类型未知,不用声明,可以匹配任意的类
  • 比如:List<?>Map<?,?>
    • List<?>List<String>List<Object>等各种泛型List的父类

1、通配符的读与写

写操作

  • 将任意元素加入到其中不是类型安全的
Collection<?> c = new ArrayList<String>();

c.add(new Object()); // 编译时错误
  • 因为我们不知道c的元素类型,我们不能向其中添加对象
  • 唯一可以插入的元素是null,因为它是所有引用类型的默认值

读操作

  • 读取List<?>的对象list中的元素时,永远是安全的
  • 因为不管 list 的真实类型是什么,它包含的都是Object

举例1:

public class TestWildcard {
    public static void m4(Collection<?> coll){
        for (Object o : coll) {
            System.out.println(o);
        }
    }
}

举例2:

public static void main(String[] args) {
    List<?> list = null;
    list = new ArrayList<String>();
    list = new ArrayList<Double>();
    // list.add(3);//编译不通过
    list.add(null);

    List<String> l1 = new ArrayList<String>();
    List<Integer> l2 = new ArrayList<Integer>();
    l1.add("尚硅谷");
    l2.add(15);
    read(l1);
    read(l2);
}

public static void read(List<?> list) {
    for (Object o : list) {
        System.out.println(o);
    }
}

2、使用注意点

  • 注意点1:编译错误:不能用在泛型方法声明上,返回值类型前面<>不能使用?
public static <?> void test(ArrayList<?> list){
}
  • 注意点2:编译错误:不能用在泛型类的声明上
class GenericTypeClass<?>{
}
  • 注意点3:编译错误:不能用在创建对象上,右边属于创建集合对象
ArrayList<?> list2 = new ArrayList<?>();

3、有限制的通配符

  • <?>:允许所有泛型的引用调用
  • 通配符指定上限:<? extends 类/接口 >:允许使用的类型必须是指定类/接口,或继承指定类、实现指定接口,即(子类)<=
  • 通配符指定下限:<? super 类/接口 >:允许使用的类型必须是指定类/接口,或指定类的父类、指定接口的父接口,即(父类)>=
  • 说明:
<? extends Number>     //(无穷小 , Number]
//只允许泛型为Number及Number子类的引用调用

<? super Number>      //[Number , 无穷大)
//只允许泛型为Number及Number父类的引用调用

<? extends Comparable>
//只允许泛型为实现Comparable接口的实现类的引用调用

举例1

class Creature{}
class Person extends Creature{}
class Man extends Person{}

class PersonTest {
    public static <T extends Person> void test(T t){
        System.out.println(t);
    }

    public static void main(String[] args) {
        test(new Person());
        test(new Man());
        test(new Creature());// 编译报错
    }
}

举例2:

public static void main(String[] args) {
    Collection<Integer> list1 = new ArrayList<Integer>();
    Collection<String> list2 = new ArrayList<String>();
    Collection<Number> list3 = new ArrayList<Number>();
    Collection<Object> list4 = new ArrayList<Object>();
    
    getElement1(list1);
    getElement1(list2);//报错
    getElement1(list3);
    getElement1(list4);//报错
  
    getElement2(list1);//报错
    getElement2(list2);//报错
    getElement2(list3);
    getElement2(list4);
  
}
// 泛型的上限:此时的泛型?,必须是Number类型或者Number类型的子类
public static void getElement1(Collection<? extends Number> coll){}
// 泛型的下限:此时的泛型?,必须是Number类型或者Number类型的父类
public static void getElement2(Collection<? super Number> coll){}

举例3:

@Test
public void test1(){
    //List<Object> list1 = null;
    List<Person> list2 = new ArrayList<Person>();
    //List<Student> list3 = null;

    List<? extends Person> list4 = null;

    list2.add(new Person());
    list4 = list2;

    //读取:可以读
    Person p1 = list4.get(0);

    //写入:除了null之外,不能写入
    list4.add(null);
    //        list4.add(new Person());
    //        list4.add(new Student());

}

@Test
public void test2(){
    //List<Object> list1 = null;
    List<Person> list2 = new ArrayList<Person>();
    //List<Student> list3 = null;

    List<? super Person> list5 = null;
    list2.add(new Person());

    list5 = list2;

    //读取:可以实现
    Object obj = list5.get(0);

    //写入:可以写入Person及Person子类的对象
    list5.add(new Person());
    list5.add(new Student());
}

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/148553.html

(0)
飞熊的头像飞熊bm

相关推荐

发表回复

登录后才能评论
极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!