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

简介: 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属性进行遍历代码如下 :

packagecsdn.knowledge.api_tools.gather.generic;
importjava.util.ArrayList;
/*** @author : Cyan_RA9* @version : 21.0*/publicclassIntro {
publicstaticvoidmain(String[] args) {
//创建集合对象ArrayListarrayList=newArrayList();
//向集合中添加元素arrayList.add(newApple("Green Apple"));
arrayList.add(newApple("Banana Apple"));
arrayList.add(newApple("Red Apple"));
//意外发生了arrayList.add(newPear());
for (Objecto : arrayList) {
Appleapple= (Apple) o;
System.out.println(apple.getName());
        }
    }
}
classApple {
privateStringname;
publicApple(Stringname) {
this.name=name;
    }
publicStringgetName() {
returnthis.name;
    }
}
classPear {}

image.gif

               运行结果 :

image.png

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

三、什么是泛型?

       1.泛型的定义 :

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

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

       2.泛型的作用 :

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

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

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

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

               eg :

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

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

image.gif

               运行结果 :

image.png

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

classGrape<String> {
privateStringtemp;
publicGrape(Stringtemp) {
this.temp=temp;
    }
publicStringgetTemp() {
returntemp;
    }
}

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类为演示类,代码如下 :

packagecsdn.knowledge.api_tools.gather.generic;
importjava.util.ArrayList;
importjava.util.Iterator;
/*** @author : Cyan_RA9* @version : 21.0*/publicclassGeneric_Demo1 {
publicstaticvoidmain(String[] args) {
//1.创建集合对象(使用泛型)ArrayList<Integer>arrayList=newArrayList<>();
//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类为演示类,代码如下 :

packagecsdn.knowledge.api_tools.gather.generic;
importjava.util.ArrayList;
/*** @author : Cyan_RA9* @version : 21.0*/publicclassGeneric_Demo2 {
publicstaticvoidmain(String[] args) {
ArrayList<Phone>phones=newArrayList<>();
phones.add(newPhone<String, Integer>("Huawei", "mate50", 6000));
phones.add(newPhone<String, Integer>("Huawei", "mate40", 4000));
phones.add(newPhone<String, Integer>("Huawei", "mate30", 3500));
System.out.println("phones = "+phones);
Phone<String, Integer>apple=newPhone<>("Apple", "iphone 14", 6000);
/*泛型方法中,泛型最终表示的实际的数据类型————即传入的形参的类型。(引用类型)*/apple.charge(Integer.valueOf(100));
apple.charge(Long.valueOf(2333));
    }
}
classPhone<T, E> {
//使用了泛型的成员变量privateTbrand;
privateTmodel;
privateEprice;
//使用了泛型的构造器publicPhone() {}
publicPhone(Tbrand, Tmodel, Eprice) {
this.brand=brand;
this.model=model;
this.price=price;
    }
//使用了泛型的成员方法publicTgetBrand() {
returnbrand;
    }
publicvoidsetBrand(Tbrand) {
this.brand=brand;
    }
publicTgetModel() {
returnmodel;
    }
publicvoidsetModel(Tmodel) {
this.model=model;
    }
publicEgetPrice() {
returnprice;
    }
publicvoidsetPrice(Eprice) {
this.price=price;
    }
//使用了泛型的成员方法的第二种形式public<M>voidcharge(Mm) {
/*getClass()方法可以获取当前类的正名(包名 + 类名);而后面再加上getSimpleName()方法就可以直接获取到类的类名。*/System.out.println("传入的引用类型 = "+m.getClass().getSimpleName());
System.out.println("当前的"+getModel() +"手机已经充电了 "+m+" 分钟。");
    }
@OverridepublicStringtoString() {
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类为演示类,代码如下 :

packagecsdn.knowledge.api_tools.gather.generic;
/*** @author : Cyan_RA9* @version : 21.0*/publicclassGeneric_Demo3 {
/*PS : 重点不再main方法,请往下看。*/publicstaticvoidmain(String[] args) {
Ipad_Airipad_air=newIpad_Air();
ipad_air.charge("iPad Air 5", Long.valueOf(100));
Ipad_Proipad_pro=newIpad_Pro();
ipad_pro.charge("iPad Pro 2022", Integer.valueOf(40));
    }
}
//定义Usb接口interfaceUsb<T, E> {
//在接口的(公有抽象)方法中使用泛型publicabstractvoidcharge(Tt, Ee);
}
//演示1 : 继承接口时确定泛型interfaceIPadextendsUsb<String, Long> {
}
classIpad_AirimplementsIPad {
/*注意!当我们利用快捷键重写Usb接口中的charge方法时,IDEA会自动用String和Long替换掉T和E.*/@Overridepublicvoidcharge(Strings, LongaLong) {
System.out.println("给"+s+"充电 "+aLong+" 分钟吧!");
    }
}
//演示2 : 实现接口时确定泛型classIpad_ProimplementsUsb<String, Integer> {
@Overridepublicvoidcharge(Strings, Integerinteger) {
System.out.println(s+"设备"+"已充电 "+integer+" 分钟。");
    }
}

image.gif

              运行结果 :

image.png

       5.自定义泛型方法 :

              1° 基本语法 :

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

               //body

       }

              2° 使用细节 :

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

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

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

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

       eg :

//以下代码仅作为演示,无实际意义class<T, U>Watermelon {
public<K>voidtaste(Tt, Uu, Kk) {
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类为演示类,代码如下 :

packagecsdn.knowledge.api_tools.gather.generic;
importjava.util.ArrayList;
importjava.util.List;
/*** @author : Cyan_RA9* @version : 21.0*/publicclassGeneric_Demo4 {
publicstaticvoidmain(String[] args) {
nutrition1(newArrayList<Object>());
nutrition1(newArrayList<Fruit>());
nutrition1(newArrayList<Banana>());
nutrition1(newArrayList<GreenBanana>());
/*不报错,因为没有进行类型约束。*/System.out.println("=====================================s");
//nutrition2(new ArrayList<Object>());/*报错,因为Object不属于“Fruit类及其子类”的范畴。*/nutrition2(newArrayList<Fruit>());
nutrition2(newArrayList<Banana>());
nutrition2(newArrayList<GreenBanana>());
System.out.println("=====================================s");
nutrition3(newArrayList<Object>());
nutrition3(newArrayList<Fruit>());
//nutrition3(new ArrayList<Banana>());/*报错,因为Banana类不属于“Fruit类及其父类”的范畴。*///nutrition3(new ArrayList<GreenBanana>());/*同样报错,因为GreenBanana类不属于“Fruit类及其父类”的范畴。*/    }
//通配符使用情况一 :publicstaticvoidnutrition1(List<?>fruit) {
System.out.println("水果营养丰富,富含维生素!");
    }
//通配符使用情况二 :publicstaticvoidnutrition2(List<?extendsFruit>fruit) {
System.out.println("水果营养丰富,富含维生素!");
    }
//通配符使用情况三 :publicstaticvoidnutrition3(List<?superFruit>fruit) {
System.out.println("水果营养丰富,富含维生素!");
    }
}
classFruit {
}
classBananaextendsFruit {
}
classGreenBananaextendsBanana {
}

image.gif

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

       3.关于JUnit框架 :

               ①为什么需要JUnit?

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

               ②什么是JUnit?

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

               ③怎么使用JUnit?

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

packagecsdn.knowledge.api_tools.gather.generic;
publicclassGeneric_Demo5 {
publicstaticvoidmain(String[] args) {
    }
publicvoidf1() {
System.out.println("f1方法被调用");
    }
publicvoidf2() {
System.out.println("f2方法被调用");
    }
publicvoidf3() {
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分享过的知识。感谢阅读!

目录
相关文章
|
19小时前
|
安全 Java 程序员
Java 泛型
Java 泛型
14 0
|
19小时前
|
安全 Java 编译器
java泛型浅谈
java泛型浅谈
6 1
|
19小时前
|
存储 安全 Java
掌握8条泛型规则,打造优雅通用的Java代码
掌握8条泛型规则,打造优雅通用的Java代码
掌握8条泛型规则,打造优雅通用的Java代码
|
19小时前
|
Java
JAVA难点包括异常处理、多线程、泛型和反射,以及复杂的分布式系统知识
【5月更文挑战第2天】JAVA难点包括异常处理、多线程、泛型和反射,以及复杂的分布式系统知识。入坑JAVA因它的面向对象特性、平台无关性、强大的标准库和活跃的社区支持。
40 2
|
19小时前
|
安全 Java 编译器
【JAVA】泛型和Object的区别
【JAVA】泛型和Object的区别
|
19小时前
|
存储 算法 Java
滚雪球学Java(20):Java泛型与枚举:提升代码灵活性与可读性
【4月更文挑战第9天】🏆本文收录于「滚雪球学Java」专栏,专业攻坚指数级提升,希望能够助你一臂之力,帮你早日登顶实现财富自由🚀;同时,欢迎大家关注&&收藏&&订阅!持续更新中,up!up!up!!
28 1
滚雪球学Java(20):Java泛型与枚举:提升代码灵活性与可读性
|
19小时前
|
Java
|
19小时前
|
安全 Java 机器人
|
19小时前
|
存储 安全 Java
每日一道Java面试题:说一说Java中的泛型?
今天的每日一道Java面试题聊的是Java中的泛型,泛型在面试的时候偶尔会被提及,频率不是特别高,但在日后的开发工作中,却是是个高频词汇,因此,我们有必要去认真的学习它。
21 0
|
19小时前
|
安全 Java 编译器
Java基础教程(14)-Java中的枚举类,泛型和注解
【4月更文挑战第14天】枚举类型(enum)是固定常量集合,Java中用`enum`定义。特点包括:使用enum关键字,定义类型名和值,可独立或嵌入定义,可实现接口,定义变量和方法。枚举在switch语句中适用,每个枚举值在JVM中唯一,不能继承Enum类。