java 泛型 万字详解(通俗易懂)

举报
Cyan_RA9 发表于 2023/05/04 16:25:44 2023/05/04
【摘要】 java 集合篇章——泛型 详解。

目录

一、前言

二、为什么需要泛型?

三、什么是泛型?

        1.泛型的定义 : 

        2.泛型的作用 : 

四、怎么用泛型?

        1.泛型的语法 : 

        2. 泛型的使用 : 

        3.自定义泛型类 : 

                1° 基本语法 : 

                2° 使用细节 : 

        4.自定义泛型接口 : 

                1° 基本语法 : 

                2° 使用细节 : 

        5.自定义泛型方法 : 

                1° 基本语法 : 

                2° 使用细节 : 

五、泛型内容延申

        1.关于继承性 : 

        2.关于通配符 : 

        3.关于JUnit框架 : 

                ①为什么需要JUnit?

                ②什么是JUnit?

                ③怎么使用JUnit?

六、完结撒❀


一、前言

        大家好,本篇博文是对java——集合篇章的内容补充,主要是给大家讲讲泛型。

        注意 : ①代码中的注释也很重要;不要眼高手低,自己跟着动手敲一遍代码;点击文章前面的目录或者侧边栏的目录可以进行跳转。良工不示人以朴,up所有文章都会进行适时改进。感谢阅读!

二、为什么需要泛型?

        1.用传统的方法创建集合,并对集合进行添加元素等操作时,无法对加入到集合中的元素的类型进行约束,导致有可能在类型转换时出现“类型转换异常”的情况,不安全。

        2.用传统方式遍历集合时,如果集合中的数据量较大,频繁的类型转换会降低程序运行的效率。

                eg :

                up以Intro类为演示类,我们设法向集合中添加几个苹果类对象,但是又意外地添加了一个梨类对象,我们假装自己不知道这个意外,仍然抱着“集合中都是苹果对象”的心态对集合中苹果对象的name属性进行遍历代码如下 : 

package csdn.knowledge.api_tools.gather.generic;
import java.util.ArrayList;
/**
 * @author : Cyan_RA9
 * @version : 21.0
 */
public class Intro {
    public static void main(String[] args) {
        //创建集合对象
        ArrayList arrayList = new ArrayList();
        //向集合中添加元素
        arrayList.add(new Apple("Green Apple"));
        arrayList.add(new Apple("Banana Apple"));
        arrayList.add(new Apple("Red Apple"));
        //意外发生了
        arrayList.add(new Pear());
        for (Object o : arrayList) {
            Apple apple = (Apple) o;
            System.out.println(apple.getName());
        }
    }
}
class Apple {
    private String name;
    public Apple(String name) {
        this.name = name;
    }
    public String getName() {
        return this.name;
    }
}
class Pear {}

image.gif

                运行结果 : 

image.png

                显然,IDEA不是格格巫,想把一个梨对象凭空转换成苹果对象是不现实的,果断给你报出“ClassCastException”(类型转换异常)。

三、什么是泛型?

        1.泛型的定义 : 

        1° 泛型,又称参数化类型(ParameterizedType),是一种可以“表示其他数据类型”的数据类型。泛型是JDK5.0中出现的新特性,解决数据类型的安全性问题,在类声明或实例化时只要指定好具体的类型即可。

        2° java泛型可以保证——如果程序在编译时没有发出警告,运行时就不会产生类型转换异常(ClassCastException),同时使得代码更加简洁和健壮。

        2.泛型的作用 : 

        1° 可以在类声明时通过一个标识来表示类中的某个属性的数据类型;

        2° 可以表示类中某个方法的返回值的数据类型;

        3° 可以表示某个方法或者构造器的形参的数据类型;

        PS : 可以理解为——将来会用指定的类型替换掉源代码中对应的“泛型”。

                eg : 

                up以Intro_2类为演示类,最终要实现——使用String类型“替换掉”Grape类中给出的泛型代码如下 : 

package csdn.knowledge.api_tools.gather.generic;
/**
    利用泛型创建对象时,就比如当前情况下,如果直接传入非String类型,会直接报错;
    而如果仅对编译类型使用了泛型,构造器没有给出泛型,即写成下面这样子——
        Grape<String> grape = new Grape(141);
    这时候就会造成——运行期异常—— “ClasCastException”。
 */
public class Intro_2 {
    public static void main(String[] args) {
        Grape<String> grape = new Grape<String>("grape");
        System.out.println("temp = " + grape.getTemp());
        System.out.println("temp's Class = " + grape.getTemp().getClass());
    }
}
class Grape<E> {
    /*
        1. E可以表示temp变量的数据类型
            该类型在定义Grape类对象时可以指定,即在编译期间确定E是什么类型。
     */
    private E temp;
    /*
        2. E也可以表示形参的数据类型,用法同上。
     */
    public Grape(E temp) {
        this.temp = temp;
    }
    /*
        3. E也可以表示函数的返回值的数据类型,用法同上。
     */
    public E getTemp() {
        return temp;
    }
}

image.gif

                运行结果 : 

image.png

                其实,当我们创建葡萄类对象给出<String>的泛型时, 达到的效果如下——

class Grape<String> {
    private String temp;
    public Grape(String temp) {
        this.temp = temp;
    }
    public String getTemp() {
        return temp;
    }
}

image.gif

                即,在所有泛型E出现的地方,E都被替换为了String。

四、怎么用泛型?

        1.泛型的语法 : 

        interface 接口名<T> 或者 interface 接口名<K, V>

        class 类名<E> 或者 class 类名<K, V>

        Δ注意 : 

        ①尖括号<>中可以填写任意字母作为泛型的标识符,一般均为大写,常用T,K和V,分别表示Type,Key和Value。

        字母本身不代表任何值,而代表类型,即程序员手动指定的数据类型。

        在指定泛型时,必须要求最终确定的数据类型为引用类型,不可以是基本数据类型。

        ④实际传入的类型可以是泛型指定类型的子类型

        ⑤若在定义类时使用了泛型,实例化该类时却什么都没有传入,默认使用Object类型。

        2. 泛型的使用 : 

        从JDK7开始,等号后边的泛型可以不用写了,称为“菱形泛型”。     

        以上文“泛型的作用”中的代码为例,我们可以将其改写为如下的形式——

                Grape<String> grape = new Grape<>("grape"); //菱形泛型

        在实际开发中,菱形泛型的使用非常广泛,因此,推荐这种写法

                up以Generic_Demo1类为演示类,代码如下 : 

package csdn.knowledge.api_tools.gather.generic;
import java.util.ArrayList;
import java.util.Iterator;
/**
 * @author : Cyan_RA9
 * @version : 21.0
 */
public class Generic_Demo1 {
    public static void main(String[] args) {
        //1.创建集合对象(使用泛型)
        ArrayList<Integer> arrayList = new ArrayList<>();
        //2.向集合中添加元素
        arrayList.add(141);
        arrayList.add(141);
        arrayList.add(5);
        arrayList.add(11);
        arrayList.add(233);
        arrayList.add(233);
        //3.遍历集合
        Iterator<Integer> iterator = arrayList.iterator();
        while (iterator.hasNext())
        {
            System.out.println(iterator.next());
        }   //不再需要进行类型转换
    }
}

image.gif

                运行结果 : 

image.png

        3.自定义泛型类 : 

                其实,我们在上文中举过的例子———

                class Grape<E> {
                }

                ———就是自定义泛型的应用。

                1° 基本语法 : 

        class 类名<T, R, E...> {        //可以同时定义多个泛型

                //类体

        }

                2° 使用细节 : 

        类的非静态成员可以使用泛型(属性,方法,构造器等)。

        ②静态成员不可以使用类的泛型。

           原因也很简单,我们在“代码块”一文中讲过,static修饰的成员的初始化——在类加载时就会执行完毕,而泛型最终代表的数据类型是在创建对象时才确定的,所以,jvm在对静态成员初始化时,无法得知它们的实际类型,也就没法儿初始化了

        ③在自定义泛型类中,使用了泛型的数组只可以定义,不可以被初始化

            因为jvm无法确定数组的实际类型,也就没法在内存中开辟空间

        自定义泛型类中,泛型最后代表的数据类型在创建对象时确定的

        如果在创建泛型类对象时没有给出指定类型,默认以Object替代

                eg : 

                up以Generic_Demo2类为演示类,代码如下 :

package csdn.knowledge.api_tools.gather.generic;
import java.util.ArrayList;
/**
 * @author : Cyan_RA9
 * @version : 21.0
 */
public class Generic_Demo2 {
    public static void main(String[] args) {
        ArrayList<Phone> phones = new ArrayList<>();
        phones.add(new Phone<String, Integer>("Huawei", "mate50", 6000));
        phones.add(new Phone<String, Integer>("Huawei", "mate40", 4000));
        phones.add(new Phone<String, Integer>("Huawei", "mate30", 3500));
        System.out.println("phones = " + phones);
        Phone<String, Integer> apple = new Phone<>("Apple", "iphone 14", 6000);
        /*
            泛型方法中,泛型最终表示的实际的数据类型————即传入的形参的类型。(引用类型)
         */
        apple.charge(Integer.valueOf(100));
        apple.charge(Long.valueOf(2333));
    }
}
class Phone<T, E> {
    //使用了泛型的成员变量
    private T brand;
    private T model;
    private E price;
    //使用了泛型的构造器
    public Phone() {}
    public Phone(T brand, T model, E price) {
        this.brand = brand;
        this.model = model;
        this.price = price;
    }
    //使用了泛型的成员方法
    public T getBrand() {
        return brand;
    }
    public void setBrand(T brand) {
        this.brand = brand;
    }
    public T getModel() {
        return model;
    }
    public void setModel(T model) {
        this.model = model;
    }
    public E getPrice() {
        return price;
    }
    public void setPrice(E price) {
        this.price = price;
    }
    //使用了泛型的成员方法的第二种形式
    public<M> void charge(M m) {
        /*
            getClass()方法可以获取当前类的正名(包名 + 类名);
            而后面再加上getSimpleName()方法就可以直接获取到类的类名。
         */
        System.out.println("传入的引用类型 = " + m.getClass().getSimpleName());
        System.out.println("当前的" + getModel() + "手机已经充电了 " + m + " 分钟。");
    }
    @Override
    public String toString() {
        return "\nPhone{" +
                "brand=" + brand +
                ", model=" + model +
                ", price=" + price +
                '}';
    }
}

image.gif

                运行结果 :  

image.png

                如果我们在静态成员中使用泛型,IDEA会报错,如下图所示 : 

image.png

        4.自定义泛型接口 : 

                1° 基本语法 : 

        interface 接口名<T, R, E...> {        //可以同时定义多个泛型

                //body

        }

                2° 使用细节 : 

        同自定义泛型类一个道理,自定义泛型接口的静态成员不能使用泛型

        自定义泛型接口中,泛型最终代表的数据类型是在继承该接口或者实现该接口时确定的。

        若在使用时没有给出具体泛型,默认使用Object类型替代
            PS : 尽管默认以Object类型替换,但是建议——在不指定泛型的情况下,手动添加<Object>泛型标识符。这样在开发中,不管是领导还是你的同事,别人一看你的代码就知道是怎么回事

                eg : 

                up以Generic_Demo3类为演示类,代码如下 : 

package csdn.knowledge.api_tools.gather.generic;
/**
 * @author : Cyan_RA9
 * @version : 21.0
 */
public class Generic_Demo3 {
    /*
        PS : 重点不再main方法,请往下看。
     */
    public static void main(String[] args) {
        Ipad_Air ipad_air = new Ipad_Air();
        ipad_air.charge("iPad Air 5", Long.valueOf(100));
        Ipad_Pro ipad_pro = new Ipad_Pro();
        ipad_pro.charge("iPad Pro 2022", Integer.valueOf(40));
    }
}
//定义Usb接口
interface Usb<T, E> {
    //在接口的(公有抽象)方法中使用泛型
    public abstract void charge(T t, E e);
}
//演示1 : 继承接口时确定泛型
interface IPad extends Usb<String, Long> {
}
class Ipad_Air implements IPad {
    /*
        注意!
        当我们利用快捷键重写Usb接口中的charge方法时,IDEA会自动用String和Long替换掉T和E.
     */
    @Override
    public void charge(String s, Long aLong) {
        System.out.println("给" + s + "充电 " + aLong + " 分钟吧!");
    }
}
//演示2 : 实现接口时确定泛型
class Ipad_Pro implements Usb<String, Integer> {
    @Override
    public void charge(String s, Integer integer) {
        System.out.println(s + "设备" + "已充电 " + integer + " 分钟。");
    }
}

image.gif

                运行结果 : 

image.png

        5.自定义泛型方法 : 

                1° 基本语法 : 

        修饰符<T, R...> 返回值类型 方法名(形参列表) {    //形参列表往往会使用定义好的泛型

                //body

        }

                2° 使用细节 : 

        ①自定义泛型方法,既可以定义在普通类中,也可以定义在泛型类中。

        ②泛型最终调用的数据类型是在调用方法时确定的

        ③每次调用泛型方法,都可以指定不同的泛型类型。

        注意区分自定义泛型方法 和 泛型在方法上的应用。

        eg : 

//以下代码仅作为演示,无实际意义
class<T, U> Watermelon {
    public<K> void taste(T t, U u, K k) {
        System.out.println("T和U代表泛型在方法上的应用;而K则是自定义泛型方法的使用。");
    }
}
image.gif

五、泛型内容延申

        1.关于继承性 : 

                举个例子,如下图所示 : 

image.png

                up在创建ArrayList对象时使用了泛型,但是没有采用“菱形泛型”的形式,而是在编译类型中给出了<Object>的泛型,在运行类型中给出了<Interger>。

                这时,IDEA报错,显示所需的类型和提供的类型不一致这说明什么?

                不会因为Integer类型是Object类型的子类就通过编译,即编译类型的泛型和运行类型的泛型必须统一。泛型本身不存在继承性

        2.关于通配符 : 

                其实up在之前的“反射”一文中已经提到过通配符。通配符是一个问号,有以下三种使用场景——

                <?> : 单独使用,表示支持任意泛型类型

                <? extends A> : 表示支持A类以及A类的子类,规定了泛型的上限

                <? super A> : 表示支持A类以及A类的父类,规定了泛型的下限

                eg : 

                up以Generic_Demo4类为演示类,代码如下 : 

package csdn.knowledge.api_tools.gather.generic;
import java.util.ArrayList;
import java.util.List;
/**
 * @author : Cyan_RA9
 * @version : 21.0
 */
public class Generic_Demo4 {
    public static void main(String[] args) {
        nutrition1(new ArrayList<Object>());
        nutrition1(new ArrayList<Fruit>());
        nutrition1(new ArrayList<Banana>());
        nutrition1(new ArrayList<GreenBanana>());
        /*
            不报错,因为没有进行类型约束。
         */
        System.out.println("=====================================s");
        //nutrition2(new ArrayList<Object>());
        /*
            报错,因为Object不属于“Fruit类及其子类”的范畴。
         */
        nutrition2(new ArrayList<Fruit>());
        nutrition2(new ArrayList<Banana>());
        nutrition2(new ArrayList<GreenBanana>());
        System.out.println("=====================================s");
        nutrition3(new ArrayList<Object>());
        nutrition3(new ArrayList<Fruit>());
        //nutrition3(new ArrayList<Banana>());
        /*
            报错,因为Banana类不属于“Fruit类及其父类”的范畴。
         */
        //nutrition3(new ArrayList<GreenBanana>());
        /*
            同样报错,因为GreenBanana类不属于“Fruit类及其父类”的范畴。
         */
    }
    //通配符使用情况一 :
    public static void nutrition1(List<?> fruit) {
        System.out.println("水果营养丰富,富含维生素!");
    }
    //通配符使用情况二 :
    public static void nutrition2(List<? extends Fruit> fruit) {
        System.out.println("水果营养丰富,富含维生素!");
    }
    //通配符使用情况三 :
    public static void nutrition3(List<? super Fruit> fruit) {
        System.out.println("水果营养丰富,富含维生素!");
    }
}
class Fruit {
}
class Banana extends Fruit {
}
class GreenBanana extends Banana {
}

image.gif

                注意看上面代码中的注释,大家也可以把代码复制下来自己去试试。 

        3.关于JUnit框架 : 

                ①为什么需要JUnit?

                平时我们在写程序时,往往一个main方法中会测试很多功能代码(比如说很多方法),所以我们经常会注释掉某部分已经测试完毕的功能代码,以便于测试其他的功能代码。但是,假如一个类中有很多功能代码要测试,我们就不得不频繁地注释与反注释,非常麻烦

                ②什么是JUnit?

                JUnit是一个java语言提供的单元测试框架,目前多数的java开发环境中,都已集成了JUnit作为单元测试的工具

                ③怎么使用JUnit?

                up以Generic_Demo5类为演示类,代码如下 : 

package csdn.knowledge.api_tools.gather.generic;
public class Generic_Demo5 {
    public static void main(String[] args) {
    }
    public void f1() {
        System.out.println("f1方法被调用");
    }
    public void f2() {
        System.out.println("f2方法被调用");
    }
    public void f3() {
        System.out.println("f3方法被调用");
    }
}

image.gif

                我们先在要测试的方法前输入@Test,如下图所示 : 

image.png

                这时候IDEA是会报错的,因为我们需要引入相应的组件,使用Alt + Enter快捷键,如下图所示 : 

image.png

                选择导入'JUnit5.8'(一般都使用5.8,而不是4的版本)。进入以后点击OK,首次导入会等待一段时间,如下图所示 : 

image.png

                大概几十秒后,就会显示导入成功了,如下图所示 : 

image.png

                导入成功后,我们会发现——在原先@Test标注的方法上,出现了一个绿色的小箭头的标志,如下图所示 : 

image.png

                点击绿色小箭头我们就可以实现对该方法进行单独地运行或者Debug,如下图所示 : 

image.png

                运行结果 : 

image.png

                并且,当我们已经导入JUnit后,为下一个方法标注“@Test”时就不需要重新导入和等待了,直接就可以标注,如下GIF图演示 : 

image.png

                发现没有,在JUnit框架的帮助下,我们既不是通过设置静态方法来调用,也没有通过创建对象来调用,而是直接指定的可以运行或者Debug某个方法,并且一个方法的调用不会影响其他的方法。这么一来就可以轻松解决我们上文中提出的问题。 

六、完结撒❀

        🆗,以上就是java——泛型的全部内容了。泛型与集合配合使用的频率非常高,所以up将泛型作为了集合篇章的内容补充。而至此,整个集合篇章的内容已全部讲完。之后,up会单独为集合篇章出一篇总结性质的博文,为大家整理一下集合框架中涉及到的up分享过的知识。感谢阅读!

【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。