当前位置: 首页 / 技术分享 / 正文
民哥带你快速精通java泛型(一)

2022-10-18

泛型 类型   

  泛型由入门到精通

  Hi,小伙伴你好~欢迎进入泛型的学习,在学习之前友情提醒一下:学习泛型需要小伙伴们具备一定的javaSE基础,如果之前小伙伴们没有接触过java,大家可以移步到千锋北京java好程序员的javaSE课程进行学习。

  在正式开始学习之前,我们先来看一段经常书写的代码,分析一下代码存在那些问题?

  代码如下:

public class GenericsDemo {
public static void main(String[] args) {
//1.创建一个List对象
List list = new ArrayList();
//2.向List中添加数据
list.add("python");
list.add("java");
list.add(66);
//3.遍历集合
for (int i = 0; i <list.size() ; i++) {
//4.把集合中的每个元素转成String类型
String ele = (String) list.get(i);
//5.打印-测试结果
System.out.println("元素的值:"+ele);
}
}
}

  运行代码,会报如下图的异常:

a1

  那么小伙们,我们来分析一下原因,到底是因为什么报这个异常呢?

  在代码中我们定义了一个List类型的集合,先向其中加入了两个String类型的值,随后加入一个Integer类型的值。这是完全允许的,因为此时list默认的类型为Object类型,所以在代码编译期间没有任何问题。

  但是在运行代码时,由于list集合中既有String类型的值,又有Integer类型的值,致使list集合无法区分值是什么类型,很容易出现上图中的错误。因为编译阶段正常,而运行时会出现“java.lang.ClassCastException”异常。因此导致此类错误编码过程中不易发现。

  分析完了,小伙们现在明白了吧,通过分析我们发现上述代码主要存在两个问题:

  当我们将数据存入集合时,集合不会记住数据的类型,默认数据类型都是Object。

  当我们遍历集合中的数据时,人为进行强制类型转换,很容易报“java.lang.ClassCastException”。强制类型转换异常

  那么有没有什么办法可以使集合能够记住集合内元素各类型,且能够达到只要编译时不出现问题,运行时就

  不会出现“java.lang.ClassCastException”异常呢?

  答案就是使用泛型。

  那么什么是泛型呢?

  第一关 让我们一起走入泛型

  1.泛型的概述

  1.1 什么是泛型

  泛型,泛指任意类型,可以应用在接口上,类上,变量上,方法上,以及方法的参数中。

  百度百科介绍:

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

  在jdk1.5之前,没有泛型的情况的下,通过对类型Object的引用来实现参数的“任意化”,“任意化”带来的缺点是要做显式的强制类型转换,而这种转换是要求开发者对实际参数类型可以预知的情况下进行的。对于强制类型转换错误的情况 ,编译器可能不提示错误,在运行的时候才出现异常,这是一个安全隐患。

  泛型的好处:使用泛型,首先可以通过IDE进行代码类型初步检查,然后在编译阶段进行编译类型检查,以保证类型转换的安全性;并且所有的强制转换都是自动和隐式的,可以提高代码的重用率。

  个人理解:

  简单来说:泛型,即“参数化类型”,那么类型“参数化”到底怎么理解呢?

  顾名思义,类型“参数化”就是将类型由原来的具体类型,变成参数化的“类型”,有点类似于方法中的变量参数,不过此时是类型定义成参数形式(你可以理解为类型形参),然后在使用时传入具体的类型(也就是类型实参)。为什么这样操作呢?因为它能让类型"参数化",也就是在不创建新的类型的情况下,通过泛型可以指定不同类型来控制形参具体限制的类型。

  总结:

  泛型介绍完了, 小伙伴看完上述解释后,能理解泛型是什么了吗?我们可以用两句话来概括一下:

  泛型在声明时,用标记符表示,仅仅作为“参数化的类型”,可以理解为形式参数。

  比如:

  //定义List集合时,用标记符E表示任意类型,E可以理解为形式参数,没有具体的类型值public interface Listextends Collection{ ----------}

  泛型在使用时,需要指定具体的类型,也就是类型实际的参数。

  比如:

  //在使用List集合时,需要确定E的具体类型,String可以理解为具体的类型,也就是实际的参数值Listlist = new ArrayList();

  1.2 常用的泛型标记符

  E - Element (集合使用,因集合中存放元素)

  T - Type(Java 类)

  K - Key(键) V - Value(值)

  N - Number(数值类型)

  ? - 表示不确定的java类型

  S、U、V - 2nd、3rd、4th types

  你可能会有疑问,弄这么多标识符干嘛,直接使用万能的Object难道不香么?我们知道Object是所有类的基类(任何类如果没有指明其继承类,都默认继承于Object类),因此任何类的对象都可以设置Object的引用,只不过在使用的时候可能要类型强制转换。但是如果设置了泛型E、T等这些标识符,那么在实际使用之前类型就已经确定,因此不再需要类型强制转换

  2.泛型的语法使用

  2.1 泛型在集合中的使用

  单列集合中List中

public interface List<E> extends Collection<E> {
----
<T> T[] toArray(T[] a);
----
}

  双列集合Map中

public class HashMap<K,V> extends AbstractMap<K,V>
implements Map<K,V>, Cloneable, Serializable {
---
V get(Object key){---};
V put(K key, V value){---};
V remove(Object key){---};
void putAll(Map<? extends K, ? extends V> m){---};
-----
}

  小伙们可以看到:List,HashMap的源码,在声明集合时或者定义方法时,使用采用尖括号内加占位符的形式 ,这里的占位符就是我们上面说的泛型标记符,泛型标记符号E,K,V,T等用来表示任意类型(E,K,V,T也就是“泛型形参”,在实例化集合对象时需要明确的具体的类型(也就是“泛型的实际参数”))。

  通过观察集合的源码,那么我们自己也可以定义泛型接口,泛型类以及泛型方法,下面我们一起操作一下吧。

  2.2 声明泛型接口

  泛型应用于接口。例如生成器(GeneratorType),这是一种专门负责创建对象的类。当使用生成器创建新的对象时,它不需要任何参数,也就是说生成器无需额外的信息就知道如何创建新对象。

  一般而言,一个生成器只定义一个方法,该方法用以产生新的对象。

/**
* 定义一个泛型接口:生成任意对象
* @param <T>: 泛型形式参数,可以是任意类型
*/
public interface GeneratorType<T> {
T create();
}

/**
* 测试泛型接口
*/
class DemoGeneratorType{
public static void main(String[] args) {
//1.使用生成器:创建random对象
GeneratorType<Random> gt= new GeneratorType<Random>() {
@Override
public Random create() {
return new Random();
}
};
//2.使用 GeneratorType:创建对象
Random random = gt.create();
}
}

  来,小伙伴们,我们一起分析下上面的代码:

  我们声明了一个泛型接口 GeneratorType,目的用来生成任意类型的对象,在这里T可以表示任意类型。

  我们在测试类中,通过GeneratorType创建对象时,可以传递任意类型。

  比如 GeneratorType,那么就可以生成Random对象了

  注意: 在这里,我们通过匿名内部类的方式创建了Random对象,这种写法大家要慢慢熟悉喔。

  2.3 声明泛型类

  泛型应用于类上面。例如订单类(Order),这是一个专门负责封装订单里面商品的类,当我们购物生成订单时,订单里面可以包含任何商品信息。

  请注意,在类上定义的泛型,在类的变量、方法的参数以及方法中同样也能使用(静态方法除外)。

/**
* 定义一个订单类:封装任意类型的商品信息
* @param <T>
*/
public class Order<T> {
private T t ;//在变量中使用: T表示任意商品类型
public T get(){//在普通方法中使用:T表示任意商品类型
return t;
}
public void set(T t){//在方法的参数使用: T表示任意类型
this.t = t;
}
//测试:
public static void main(String[] args) {
Order<Phone> order = new Order<Phone>();//创建订单对象:封装Phone商品
order.set(new Phone("华为Mate20",3899.0));
System.out.println("商品名称:"+order.get().getPhoneName());
}
}
//定义手机商品类
class Phone{
private String phoneName;
private Double phonePrice;

public Phone(String phoneName, Double phonePrice) {
this.phoneName = phoneName;
this.phonePrice = phonePrice;
}

public Phone() {
}

public String getPhoneName() {
return phoneName;
}

public void setPhoneName(String phoneName) {
this.phoneName = phoneName;
}

public Double getPhonePrice() {
return phonePrice;
}

public void setPhonePrice(Double phonePrice) {
this.phonePrice = phonePrice;
}
}

  ok,泛型类我们声明完成了,大家看一下是不是和我们声明泛型接口很相似啊,确实是一样的。

  声明的语法就是:类名,在这里T可以表示任意类型。

  小伙伴也可以看到,我们定义了一个带泛型的Order类,在我们创建订单对象时,可以传入任意类型的商品对象,使我们的操作更加灵活

  2.4 声明泛型方法

  泛型应用于方法上面。前面说过在泛型类上定义的泛型,在类的方法中也能使用(静态方法除外)。但是有的时候我们只想在某个方法上使用泛型,而不是整个类,这也是被允许的,下面我和小伙们一起来体验一下。

  比如FactoryBean工厂类,通过泛型方法,创建任意类型的对象。

package cn.qf;
/**
* 定义一个工厂Bean:
*/
public class FactoryBean {
/*
定义不带泛型的方法
*/
public static Object createObject0(String className) throws Exception{
return Class.forName(className).newInstance();
}
/*
定义一个普通的泛型方法:className表示类的全路径
*/
public <T> T createObject1(String className) throws Exception{
return (T) Class.forName(className).newInstance();
}
/*
定义一个静态的泛型方法:className表示类的全路径
*/
public static <T> T createObject2(String className)throws Exception{
return (E) Class.forName(className).newInstance();
}
//测试:
public static void main(String[] args) throws Exception {
//创建一个Phone对象 : 不使用泛型方法,需要类型强转
Phone p1 = (Phone) FactoryBean.createObject0("cn.qf.Phone");
//创建一个Phone对象 :泛型方法,不需要类型强转
Phone p2 = FactoryBean.createObject2("cn.qf.Phone");
}
}

class Phone{
private String phoneName;
private Double phonePrice;
----
}

  在这里我们使用工厂模式来创建对象,为了在我们获取对象时,不用类型强转,我们也使用了泛型。小伙伴通过代码可以看到,不使用泛型的方法,在获取对象时,需要类型强转(可能会引起类型强转异常)。

  在使用泛型方法获取对象时,不需要类型强转(可以避免引起类型强转异常)。

  2.5 泛型方法、泛型接口、泛型类小结

  从上面的介绍小伙伴也看到了,泛型类的好处就是在泛型类上定义的泛型,在类的方法中也能使用(普通静态方法除外)。而泛型方法的最大优点就是能独立于类,不受类是否是泛型类的限制。因此当你考虑使用泛型的时候,优先考虑定义泛型方法。如果非要定义泛型类,

  个人建议通过使用泛型方法来将整个类泛型化,因为这样就不用担心静态方法的事,如果有静态方法那必然是泛型方法。这样就能避免普通静态方法无法获取泛型类泛型的尴尬局面。

  你以为这就把泛型介绍完了吗?并没有,小伙伴们先休息片刻,稍后我们继续喔。

  闯关练习

  需求:

  定义一个泛型类:

  包含与类的泛型一样的变量,

  包含与类的泛型一样的方法,参数也使用泛型

  同时定义一个类的泛型不相同的泛型方法

  答案:

/**
* 定义泛型类:
* @param <T>: 泛型T
*/
public class GenericDemo4<T> {
//1.定义一个与T 一样的变量
private T t;
//2.定义一个与T一样的方法
public T test1(T outer){
System.out.println(outer);
return outer;
}
//3.定义一个与T不一样的方法
public <E> E test2(E e){
System.out.println("自定义泛型的方法:"+e);
return e;
}
//测试:
public static void main(String[] args) {
//1.创建对象:指定T的泛型为 String
GenericDemo4<String> gt = new GenericDemo4<String>();
//2.调用 与T 一样的泛型方法
gt.test1("hello world");
//3.调用 与T 不一样的泛型方法
gt.test2(10);
}
}

好程序员公众号

  • · 剖析行业发展趋势
  • · 汇聚企业项目源码

好程序员开班动态

More+
  • HTML5大前端 <高端班>

    开班时间:2021-04-12(深圳)

    开班盛况

    开班时间:2021-05-17(北京)

    开班盛况
  • 大数据+人工智能 <高端班>

    开班时间:2021-03-22(杭州)

    开班盛况

    开班时间:2021-04-26(北京)

    开班盛况
  • JavaEE分布式开发 <高端班>

    开班时间:2021-05-10(北京)

    开班盛况

    开班时间:2021-02-22(北京)

    开班盛况
  • Python人工智能+数据分析 <高端班>

    开班时间:2021-07-12(北京)

    预约报名

    开班时间:2020-09-21(上海)

    开班盛况
  • 云计算开发 <高端班>

    开班时间:2021-07-12(北京)

    预约报名

    开班时间:2019-07-22(北京)

    开班盛况
在线咨询
试听
入学教程
立即报名

Copyright 2011-2020 北京千锋互联科技有限公司 .All Right 京ICP备12003911号-5 京公网安备 11010802035720号