Bootstrap

泛型的介绍以及原理

目录

一、前言

二、什么泛型

三、为什么要使用泛型

3.1、保证了类型的安全性。

3.2、消除强制转换

3.3、提高程序的性能

3.4、 提高了代码的重用性

四、如何使用泛型

4.1、 泛型类

4.2、泛型接口

4.3、泛型方法

五、泛型通配符

5.1、无边界的通配符

5.2、固定上边界的通配符

5.3、固定下边界的通配符

六、泛型的实现原理

6.1、配置JAD

6.2、反编译分析


一、前言

泛型在java中有很重要的地位,无论是开源框架还是JDK源码都能看到它。

毫不夸张的说,泛型是通用设计上必不可少的元素,所以真正理解与正确使用泛型,是一门必修课

二、什么泛型

Java 泛型(generics)是 JDK 5 中引入的一个新特性, 泛型提供了编译时类型安全检测机制,该机制允许程序员在编译时检测到非法的类型。

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

三、为什么要使用泛型

泛型的好处是在编译的时候检查类型安全,并且所有的强制转换都是自动和隐式的,提高代码的重用率。

3.1、保证了类型的安全性。

比如:没有泛型的情况下使用集合:

public static void test1(){
        ArrayList arr = new ArrayList<>();
        arr.add(1);
        arr.add(new Object()); //编译正常
    }

在不指定泛型类型时候,arr可以add任何元素,但是显然这不是我们想要的结果,因为这样就导致在遍历arr的时候,我们不知道下一个元素会是什么类型。

当我们使用泛型指定类型时,编译不通过

 相当于告诉编译器每个集合接收的对象类型是什么,编译器在编译期就会做类型检查,使得程序更加安全,增强了程序的健壮性。

3.2、消除强制转换

泛型的一个附带好处是,消除源代码中的许多强制类型转换,这使得代码更加可读,并且减少了出错机会。

不使用泛型的时候:

public static void test1() {
        ArrayList arr = new ArrayList<>();
        arr.add(1);
        Object o = arr.get(0);//获取出来不知道是什么类型,就算你知道是int,在使用的时候需要进行转换 
        int o1 = (int) arr.get(0);

    }

使用泛型的时候:

3.3、提高程序的性能

在非泛型编程中,将简单类型作为Object传递时会引起Boxing(装箱)和Unboxing(拆箱)操作,这两个过程都是具有很大开销的。引入泛型后,就不必进行Boxing和Unboxing操作了,所以运行效率相对较高,特别在对集合操作非常频繁的系统中,这个特点带来的性能提升更加明显。

泛型变量固定了类型,使用的时候就已经知道是值类型还是引用类型,避免了不必要的装箱、拆箱操作:

object a = 1;//由于是object类型,会自动进行装箱操作。
 
int b = (int) a;//强制转换,拆箱操作。这样一去一来,当次数多了以后会影响程序的运行效率。

3.4、 提高了代码的重用性

如何提高的显而易见,假如ArrayList不使用泛型,你自己想吧~。~

四、如何使用泛型

泛型有三种使用方式,分别为:泛型类、泛型接口和泛型方法。

4.1、 泛型类

格式:public class 类名 <泛型类型1,...> { }

例如:public class GenericClass<ab,a,c> {}

当然,这个后面的参数类型也是有规范的,不能像上面一样随意,通常类型参数我们都使用大写的单个字母表示:

  • E - Element (在集合中使用,因为集合中存放的是元素)
  • T - Type(Java 类)
  • K - Key(键)
  • V - Value(值)
  • N - Number(数值类型)
  •  - 表示不确定的 java 类型
package com.cjian.generic;

/**
 * @Author: cjian
 * @Date: 2023/5/30 10:33
 * @Des:
 */
public class GenericClass <T> {
    private T type;

    public T getType() {
        return type;
    }

    public void setType(T type) {
        this.type = type;
    }

    public GenericClass(T type) {
        this.type = type;
    }

    public static void main(String[] args) {
        GenericClass<String> g1 = new GenericClass<>("string");
        System.out.println(g1.getType());
        GenericClass<Integer> g2 = new GenericClass<>(1);
        System.out.println(g2.getType());
    }
}

4.2、泛型接口

格式:public <泛型类型> 返回类型 方法名(泛型类型 变量名) { }

方法声明中定义的形参只能在该方法里使用,而接口、类声明中定义的类型形参则可以在整个接口、类中使用。当调用run()方法时,根据传入的实际对象,编译器就会判断出类型形参T所代表的实际类型。

package com.cjian.generic;

/**
 * @Author: cjian
 * @Date: 2023/5/30 10:39
 * @Des:
 */
public interface GenericInterface<T> {
    void run(T value);
}

class StringImpl implements GenericInterface<String> {

    @Override
    public void run(String value) {
        System.out.println(value);
    }
}

class IntegerImpl implements GenericInterface<Integer> {

    @Override
    public void run(Integer value) {
        System.out.println(value);
    }
}

4.3、泛型方法

定义格式:修饰符 <代表泛型的变量> 返回值类型 方法名(参数){ }

package com.cjian.generic;

import java.util.ArrayList;

/**
 * @Author: cjian
 * @Date: 2023/5/30 9:51
 * @Des: 使用泛型打印不同类型的数组
 */
public class Demo {
    public static void main(String[] args) {
        String[] strings = {"a", "b", "c"};
        Integer[] integers = {1, 2, 3};
        Double[] doubles = {1.1, 2.2, 3.3};

        printArr(strings);
        printArr(integers);
        printArr(doubles);
        test1();
    }

    public static <E> void printArr(E[] arr) {
        for (E e : arr) {
            System.out.printf("%s ", e);
        }
        System.out.println();
    }

    public static void test1() {
        ArrayList<Integer> arr = new ArrayList<>();
        arr.add(1);
        int o1 = arr.get(0);
    }
}

五、泛型通配符

//表示类型参数可以是任何类型
public class GenericClass<?>{}
 
//表示类型参数必须是A或者是A的子类
public class GenericClass<T extends A>{}
 
//表示类型参数必须是A或者是A的超类型
public class GenericClass<T supers A>{}

5.1、无边界的通配符

        就是<?>, 比如List<?>

       无边界的通配符的主要作用就是让泛型能够接受未知类型的数据.

package com.cjian.generic;

import java.util.ArrayList;
import java.util.List;

/**
 * @Author: cjian
 * @Date: 2023/5/30 10:47
 * @Des:
 */
public class GenericTest {
    public static void main(String[] args) {
        List<String> name = new ArrayList<String>();
        List<Integer> age = new ArrayList<Integer>();
        List<Number> number = new ArrayList<Number>();

        name.add("icon");
        age.add(18);
        number.add(314);

        getData(name);
        getData(age);
        getData(number);

    }

    public static void getData(List<?> data) {
        System.out.println("data :" + data.get(0));
    }
}

5.2、固定上边界的通配符

        采用<? extends E>的形式

        使用固定上边界的通配符的泛型, 就能够接受指定类及其子类类型的数据。

要声明使用该类通配符, 采用<? extends E>的形式, 这里的E就是该泛型的上边界。

注意: 这里虽然用的是extends关键字, 却不仅限于继承了父类E的子类, 也可以代指显现了接口E的类

package com.cjian.generic;

/**
 * @Author: cjian
 * @Date: 2023/5/30 10:33
 * @Des:
 */
public class GenericClass<T> {
    private T type;

    public T getType() {
        return type;
    }

    public void setType(T type) {
        this.type = type;
    }

    public GenericClass(T type) {
        this.type = type;
    }

    // 只能接收Number及其Number的子类
    public static void printNum(GenericClass<? extends Number> temp) {
        System.out.print(temp + "、");
    }

    public static void main(String[] args) {
        GenericClass<String> g1 = new GenericClass<>("string");
        System.out.println(g1.getType());
        GenericClass<Integer> g2 = new GenericClass<>(1);
        System.out.println(g2.getType());
    }
}

5.3、固定下边界的通配符

        采用<? super E>的形式

        使用固定下边界的通配符的泛型, 就能够接受指定类及其父类类型的数据.。

要声明使用该类通配符, 采用<? super E>的形式, 这里的E就是该泛型的下边界.。

注意: 你可以为一个泛型指定上边界或下边界, 但是不能同时指定上下边界。

 

package com.cjian.generic;

/**
 * @Author: cjian
 * @Date: 2023/5/30 10:33
 * @Des:
 */
public class GenericClass<T> {
    private T type;

    public T getType() {
        return type;
    }

    public void setType(T type) {
        this.type = type;
    }

    public GenericClass(T type) {
        this.type = type;
    }

    // 只能接收String或Object类型的泛型,String类的父类只有Object类
    public void printString(GenericClass<? super String> value) {
        System.out.print(value);
    }

    public static void main(String[] args) {
        GenericClass<String> g1 = new GenericClass<>("string");
        System.out.println(g1.getType());
        GenericClass<Integer> g2 = new GenericClass<>(1);
        System.out.println(g2.getType());
    }
}

六、泛型的实现原理

泛型本质是将数据类型参数化,它通过擦除的方式来实现,即编译器会在编译期间「擦除」泛型语法并相应的做出一些类型转换动作。

Java 编译器通过如下方式实现擦除:

  • 用 Object 或者界定类型替代泛型,产生的字节码中只包含了原始的类,接口和方法;
  • 在恰当的位置插入强制转换代码来确保类型安全;
  • 在继承了泛型类或接口的类中插入桥接方法来保留多态性。

6.1、配置JAD

使用jad反编译Generic.classs

下载地址:JAD Java Decompiler Download Mirror

下载完毕后,新建一个系统变量

 然后添加到Path:

 cmd输入jad出现下面的代表安装成功:


 

6.2、反编译分析

 来到class文件所在目录,进行反编译:

package com.cjian.generic;

/**
 * @Author: cjian
 * @Date: 2023/5/30 10:33
 * @Des:
 */
public class GenericClass<T> {
    private T type;

    public T getType() {
        return type;
    }

    public void setType(T type) {
        this.type = type;
    }

    public GenericClass(T type) {
        this.type = type;
    }

    // 只能接收Number及其Number的子类
    public static void printNum(GenericClass<? extends Number> generic) {
        System.out.print(generic.getType());
    }

    // 只能接收String或Object类型的泛型,String类的父类只有Object类
    public void printString(GenericClass<? super String> generic) {
        System.out.print(generic.getType());
    }

    public static void main(String[] args) {
        GenericClass<String> g1 = new GenericClass<>("string");
        System.out.println(g1.getType());
        GenericClass<Integer> g2 = new GenericClass<>(1);
        System.out.println(g2.getType());
    }
}

 提示不用管,当前路径下已经生成了Generic.jad文件,打开:

// Decompiled by Jad v1.5.8e2. Copyright 2001 Pavel Kouznetsov.
// Jad home page: http://kpdus.tripod.com/jad.html
// Decompiler options: packimports(3) 
// Source File Name:   GenericClass.java

package com.cjian.generic;

import java.io.PrintStream;

public class GenericClass
{

    public Object getType()
    {
        return type;
    }

    public void setType(Object type)
    {
        this.type = type;
    }

    public GenericClass(Object type)
    {
        this.type = type;
    }

    public static void printNum(GenericClass generic)
    {
        System.out.print(generic.getType());
    }

    public void printString(GenericClass generic)
    {
        System.out.print(generic.getType());
    }

    public static void main(String args[])
    {   
        GenericClass g1 = new GenericClass("string");
        // 合适的地方静心类型强转
        System.out.println((String)g1.getType());
        GenericClass g2 = new GenericClass(Integer.valueOf(1));
        System.out.println(g2.getType());
    }

    // 使用Object替代泛型
    private Object type;
}

发现编译器擦除 GenericClass类后面的两个尖括号,并且将 type的类型定义为 Object 类型,且在使用的地方进行了类型强转

那么是不是所有的泛型类型都以 Object 进行擦除呢?大部分情况下,泛型类型都会以 Object 进行替换,而有一种情况则不是。那就是使用到了extends和super语法的有界类型,如:
 

public class Generic<T extends String> {
    private T num;
}

这种情况的泛型类型,num 会被替换为 String 而不再是 Object。

再看下插入桥接方法的场景:

 Comparable.java:

package com.cjian.generic;

/**
 * @Author: cjian
 * @Date: 2023/5/30 13:54
 * @Des:
 */
public interface Comparable<T> {
    int compareTo(T obj);
}

 User.java

package com.cjian.generic;

/**
 * @Author: cjian
 * @Date: 2023/5/30 13:58
 * @Des:
 */
public class User implements Comparable<User> {
    private String name;

    @Override
    public int compareTo(User other) {
        return this.name.compareTo(other.name);
    }
}

Comparable.jad

// Decompiled by Jad v1.5.8e2. Copyright 2001 Pavel Kouznetsov.
// Jad home page: http://kpdus.tripod.com/jad.html
// Decompiler options: packimports(3) 
// Source File Name:   Comparable.java

package com.cjian.generic;


public interface Comparable
{

    public abstract int compareTo(Object obj);
}

 User.jad

// Decompiled by Jad v1.5.8e2. Copyright 2001 Pavel Kouznetsov.
// Jad home page: http://kpdus.tripod.com/jad.html
// Decompiler options: packimports(3) 
// Source File Name:   User.java

package com.cjian.generic;


// Referenced classes of package com.cjian.generic:
//            Comparable

public class User
    implements Comparable
{

    public User()
    {
    }

    public int compareTo(User other)
    {
        return name.compareTo(other.name);
    }

    // 插入桥接方法来保留多态性
    public volatile int compareTo(Object obj)
    {
        return compareTo((User)obj);
    }

    private String name;
}

;