Java SE 学习笔记 :泛型篇

编译软件:IntelliJ IDEA 2019.2.4 x64

运行环境:win10 家庭中文版

jdk版本:1.8.0_361


文章目录

一、泛型是什么?

二、泛型相关名词解释

三、泛型可以在哪里声明?

3.1 在类或接口名后面,这样的类和接口称为泛型类或泛型接口,或者是参数化的类型

3.2 在方法的返回值类型前面,这样的方法称为泛型方法。

3.3 演示案例

四、如何自定义泛型类和泛型接口?

4.1 应用概述

4.2  用法小结

 五、泛型方法

 5.1 泛型方法的调用

 5.2  自定义泛型方法

六、类型变量的上限

6.1 定义泛型类的类型变量时指定上限

6.2  定义泛型方法的类型变量时指定上限

 七、泛型擦除

八、泛型通配符

8.1  为什么要使用泛型通配符?

8.2  类型通配符的三种使用形式

 8.3  使用类型通配符来指定类型参数的问题


提示:以下是本篇文章正文内容,下面案例可供参考

一、泛型是什么?

首先让我们看看百度百科中关于“泛型”的权威解释,如下所示。

Java泛型是J2 SE1.5中引入的一个新特性,其本质是参数化类型,也就是说所操作的数据类型被指定为一个参数(type parameter)这种参数类型可以用在接口和方法的创建中,分别称为泛型类、泛型接口、泛型方法。

                                                                                                     -------------------------百度百科

怎么说呢?解释的非常简洁高效准确。但对于初学者而言,可能过于专业化不好理解。不妨看看本文,也许能让你眼见一亮,对泛型的理解瞬间念头通达。

在我给各位阐释之前,先没看以下问题场景:

  1. 假设有一批空的塑料瓶,它被设计出来专门送给矿泉水公司去装矿泉水,但这些塑料瓶的瓶身没有贴标签【该标签是标注收货人的信息】。塑料瓶的生产厂家不知道这批塑料瓶要发给矿泉水公司,还是别的饮料公司。即厂家有可能会发错产品。
  2. 还是上面的问题场景中,假设塑料瓶的瓶身贴了标签,那么塑料瓶的生产厂家就不会犯发错产品的可能,矿泉水公司也能如期收到产品。

显而易见,贴标签的作用巨大,能够保证发货人与收货人可以如期准确的发出/收到产品。进而提高生产效率。

泛型,就有点像上述场景中的“标签”,可以在使用之前,表名某个xx的类型是啥,用的时候既方便又安全。

对于Java程序中则起到如下的作用。
(1)可以把类型检查提到“编译时”
(2)使用时,类型是安全的,不需要再强制类型转换。

泛型是如何传递类型的?

我们可以用之前的知识来辅助理解它。之前学习方法的时候,我们就有了形参和实参的概念。

为什么方法有形参?

因为方法体的功能实现,需要外界(使用者)给它提供数据,这个数据在编写方法体时,数据值是不确定的,  需要在调用方法时,才能确定。

例如:编写一个方法,可以求两个整数的和。

public static int add(int a, int b){
      int a;
      int b; 
      return a + b;
}

现在在设计某个类时,出现了某个成员的类型未知,需要在使用这个类的时候才能确定,就可以把这个类型作为参数传递。

写在类名后面,如下图代码和样例所示:

public class ArrayList<E> {//E就是代表未知的类型,又称为类型形参
    public void add(E e) //添加一个元素到当前集合中
        //...
    }
}
        

ArrayList<String> list = new ArrayList<String>();
//<E>就是对应<String>

总结:泛型就是参数化的类型,即泛型是传递类型的一种语法

格式:<泛型>

二、泛型相关名词解释

举例如下:

ArrayList<E>

E是类型变量,因为E是一种未知的类型,可能代表string,可能代表Integer等,可以变,所以称为类型变量。

<E> 是类型参数。

ArrayList<E>  是参数化的类型。

附注:类型变量也称泛型变量,类型参数也称 泛型实参

三、泛型可以在哪里声明?

3.1 在类或接口名后面,这样的类和接口称为泛型类泛型接口,或者是参数化的类型

代码如下(示例):

java.lang.Comparable接口
public interface Comparable<T> {
    public int compareTo(T o); //这个不是泛型方法
}

 

3.2 在方法的返回值类型前面,这样的方法称为泛型方法

代码如下(示例):

java.util.Arrays类:
publif static <T> List<T> asList(T... a) {
    return new ArrayList<>(a);
}

注意:<E>、<T>等泛型类型,只能指定为引用数据类型,不能指定为基本数据类型和void。

3.3 演示案例

案例演示代码:

@Test
public void test01(){
    Collection<Integer> coll=new ArrayList<Integer>();
    for (int i = 0; i <5 ; i++) {
        coll.add(new Random().nextInt(100));
    }
    for (Integer integer : coll) {
        System.out.print(integer+"\t");
    }
    System.out.println();
    coll.removeIf(new Predicate<Integer>() {
        @Override
        public boolean test(Integer integer) {
            return integer%2==0;
        }
    });

    Iterator<Integer> itr= coll.iterator();
    while (itr.hasNext()){
        System.out.print(itr.next()+"\t");
    }
}

运行结果:


四、如何自定义泛型类和泛型接口?

4.1 应用概述

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

  • 当某个类/接口的非静态实例变量的类型不确定,需要在创建对象或子类继承时才能确定
  • 当某个(些)类/接口的非静态方法的形参类型不确定,需要在创建对象或子类继承时才能确定

语法格式:

注意

  1. <类型变量列表>:可以是一个或多个类型变量,一般都是使用单个的大写字母表示。例如:<T>、<K,V>等。
  2. <类型变量列表>中的类型变量不能用于静态成员上

举例:

例如:我们要声明一个学生类,该学生包含姓名、成绩,而此时学生的成绩类型不确定,为什么呢,因为,语文老师希望成绩是“优秀”、“良好”、“及格”、“不及格”,数学老师希望成绩是89.5, 65.0,英语老师希望成绩是'A','B','C','D','E'。那么我们在设计这个学生类时,就可以使用泛型。

样例演示代码:

  • 方式一:直接在类名旁+泛型的类型参数列表
public class Student<T> {
    private String name;
    private T score;

    public Student(String name, T score) {
        this.name = name;
        this.score = score;
    }

    @Override
    public String toString() {
        return "Student{" +
            "name='" + name + '\'' +
            ", score=" + score +
            '}';
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public T getScore() {
        return score;
    }

    public void setScore(T score) {
        this.score = score;
    }


}

  • 方式二:子类继承,注意,此时子类就不再是泛型类,它的父类才是

 注意上段中的泛型类或泛型接口声明的类型参数,即<E>,不可以用在静态成员上

why?

因为类或接口上面声明的泛型是未知的类型,需要再new对象时确定,而静态是早于对象创建的使用,或者说不依赖与对象的。

4.2  用法小结

IDK1.7支持简写形式: Student<string> stu1 = new Student<>("张三","良好”);

指定泛型实参时,必须左右两边一致,不存在多态现象

附注:

当String类型的元素进行自然排序时,就是按照字符的Unicode编码值进行排序


 五、泛型方法

 5.1 泛型方法的调用

例如:

在java.util.Arrays数组工具类中,有很多泛型方法

  • public static <T> List<T> asList(T... a):将实参对象依次添加到一个固定大小的List列表集合中。

  • public static <T> T[] copyOf(T[] original, int newLength):复制任意对象数组,新数组长度为newLength。

注意:

情形①

 

情形②

 5.2  自定义泛型方法

 附注:如何在IDEA中找出一个类的所有子类?


六、类型变量的上限

我们在声明<T>等类型变量时,可以给它限定“上限”。

语法格式:
<T extends 上限> : 表示T的类型必须是<=上限,即要么是上限本身,要是继承上限类或实现了上限接口的类型。

举例如下:

学生类,包含姓名和成绩。
现在成绩类型不确定,但是要求成绩必须是Number或Number的子类对象。
java.Lang.Number表示数值类型,例如它的子类有。

 代码如下:

在上面的案例需求中再加一个要求,成绩不仅要求是Number类或它的子类,还要求实现Comparable接口

一个类型变量的上限可以是1个,也可以是多个。但是要求如果有多个的话,类只能有一个,其他的都是接口类型。而且类在左边,其他的类型在右边。

如果自定义的方法实现了类型变量的下限,那么传参给方法的实参中的类也必须实现该接口。此举意在编译前提醒检测类型变量的类型转换问题

6.1 定义泛型类的类型变量时指定上限

代码举例如下:

6.2  定义泛型方法的类型变量时指定上限

编写一个数组工具类,包含可以给任意对象数组进行从小到大排序,调用元素对象的compareTo方法比较元素的大小关系。要求数组的元素类型必须是java.lang.Comparable<T>接口类型。

代码举例如下:

public class MyArrays {

    public static <T extends Comparable<T>> void sort(T[] arr){
        for (int i = 1; i < arr.length ; i++) {
            for (int j = 0; j < arr.length-i ; j++) {
                if (((Comparable)(arr[j])).compareTo(arr[j+1])>0){
                    T temp=arr[j];
                    arr[j]=arr[j+1];
                    arr[j+1]=temp;
                }
            }
        }
    }

}
import org.junit.Test;

public class TestArrays {
    @Test
    public void test01(){
        Integer[] nums={4,7,1,2,89,34,67};
        System.out.println("排序前:");
        for (Integer num : nums) {
            System.out.print(num+"\t");
        }
        System.out.println();
        MyArrays.sort(nums);
        System.out.println("排序后:");
        for (Integer num : nums) {
            System.out.print(num+"\t");
        }
    }
}

运行效果:


 七、泛型擦除

如果用户在使用泛型类或泛型接口时,没有主动指定泛型的类型,就会发生泛型的擦除。
泛型擦除后,类型变量按照哪个类型处理? 是统一按照object处理吗?
不是
答案:泛型擦除后,自动按照类型变量声明时的第一个上限处理,如果类型变量上面时没有指定上限,那么按照Object处理

代码举例如下: 

 


八、泛型通配符

8.1  为什么要使用泛型通配符?

当我们声明一个变量/形参时,这个变量/形参的类型是一个泛型类或泛型接口,例如:Comparator<T>类型,但是我们仍然无法确定这个泛型类或泛型接口的类型变量<T>的具体类型,此时我们考虑使用类型通配符 ?

代码举例如下:

import java.util.ArrayList;
import java.util.Collection;

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

    public static void main(String[] args) {
        //右边泛型指定为任意类型或不指定都可以
        m4(new ArrayList<Object>());//Collection<?> coll = new ArrayList<Object>();
        m4(new ArrayList<>());//Collection<?> coll = new ArrayList<>();
        m4(new ArrayList());//Collection<?> coll = new ArrayList();
        m4(new ArrayList<String>());//Collection<?> coll = new ArrayList<String>();
    }
}

在Java中,泛型的指定是必须左右两边一致的

8.2  类型通配符的三种使用形式

  • <?>:完整形式为:类名<?> 或接口名<?>,此时?代表任意类型
  • <? extends 上限>:完整形式为:类名<? extends 上限类型> 或接口名<? extends 上限类型>,此时?代表上限类型本身或者上限的子类,即?代表 <= 上限的类型。
  • <? super 下限>:完整形式为:类名<? super 下限类型> 或接口名<? super 下限类型>,此时?代表下限类型本身或者下限的父类,即?代表>= 下限的类型。

案例:

声明一个集合工具类MyCollections,要求包含:

  • public static boolean different(Collection<?> c1, Collection<?> c2):比较两个Collection集合,此时两个Collection集合的泛型可以是任意类型,如果两个集合中没有相同的元素,则返回true,否则返回false。

  • public static <T> void addAll(Collection<? super T> c1, T... args):可以将任意类型的多个对象添加到一个Collection集合中,此时要求Collection集合的泛型指定必须>=元素类型。

  • public static <T> void copy(Collection<? super T> dest,Collection<? extends T> src):可以将一个Collection集合的元素复制到另一个Collection集合中,此时要求原Collection泛型的类型<=目标Collection的泛型类型。

代码演示如下:

import java.util.Collection;

public class MyCollections {
    public static boolean different(Collection<?> c1, Collection<?> c2){
        return c1.containsAll(c2) && c2.containsAll(c1);
    }

    public static <T> void addAll(Collection<? super T> c1, T... args){
        for (int i = 0; i < args.length; i++) {
            c1.add(args[i]);
        }
    }

    public static <T> void copy(Collection<? super T> dest,Collection<? extends T> src){
        for (T t : src) {
            dest.add(t);
        }
    }

}

public class MyCollectionsTest {
    public static void main(String[] args) {

        Collection<Integer> c1 = new ArrayList<Integer>();
        MyCollections.addAll(c1,1,2,3,4,5);
        System.out.println("c1 = " + c1);

        Collection<String> c2 = new ArrayList<String>();
        MyCollections.addAll(c2,"hello","java","world");
        System.out.println("c2 = " + c2);

        System.out.println("c1 != c2 " + MyCollections.different(c1, c2));

        Collection<Object> c3 = new ArrayList<>();
        MyCollections.copy(c3,c1);
        MyCollections.copy(c3,c2);
        System.out.println("c3 = " + c3);


    }
}

运行效果如下:

 8.3  使用类型通配符来指定类型参数的问题

(1)如果把"泛型类<T>"指定为"泛型类<?>";那么该泛型类中所有参数是T类型的方法或成员都无法正常使用。参数类型不是T类型的方法照常使用。

代码举例如下:

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;

public class TestProblem {
    @Test
    public void test01(){
        Collection<?> coll = new ArrayList<>();
//        coll.add("hello");
//        coll.add(1);
//        coll.add(1.0);
        /*
        上面所有添加操作都报错。
        为什么?
        因为<?>表示未知的类型,集合的元素是不确定的,那么添加任意类型对象都有风险。
        
        void add(E t)方法无法正常使用
        因为此时E由?表示,即表示直到add方法被调用时,E的类型仍然不确定,所以该方法无法正常使用
         */

        Collection<?> coll2 = Arrays.asList("hello","java","world");
        for (Object o : coll2) {
            System.out.println(o);
        }
    }
}

(2)如果把"泛型类<T>"指定为"泛型类<? extends 上限>":那么该泛型类中所有参数是T类型的方法或成员都无法正常使用。参数类型不是T类型的方法照常使用。

代码举例如下:

个人理解:如果把"泛型类<T>"指定为"泛型类<? extends 上限>",之所以无法正常使用,是因为

Java编译器在编译时无法确定添加到集合coll中的元素的类型是否<= Number,泛型类<? extends 上限> 中只说了要<=Number[上限],Number类以下的部分或者说它的子类,编译器是不知道的。

(3)如果把"泛型类<Tdd>"指定为"泛型类<? super 下限>":那么该泛型类中所有参数是T类型的方法或成员都可以使用,但是有要求。参数类型不是T类型的方法照常使用。


  • 3
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

陌上少年,且听这风吟

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值