泛型的介绍以及原理

news/2024/10/19 9:37:09/

目录

一、前言

二、什么泛型

三、为什么要使用泛型

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> {@Overridepublic void run(String value) {System.out.println(value);}
}class IntegerImpl implements GenericInterface<Integer> {@Overridepublic 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.javapackage 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;@Overridepublic 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.javapackage 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.javapackage com.cjian.generic;// Referenced classes of package com.cjian.generic:
//            Comparablepublic class Userimplements 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;
}


http://www.ppmy.cn/news/107154.html

相关文章

「接口汇总」APISpace 常用的免费API 整理

空气质量查询&#xff1a;支持国内3400个城市的整点观测&#xff0c;并附带空气质量监测点&#xff08;全国共2335个&#xff09;的整点观测数据。天气预报查询&#xff1a;支持全国以及全球多个城市的天气查询&#xff0c;包含国内3400个城市以及国际4万个城市的实况数据&…

TensorFlow、PyTorch分布式训练

要在两台主机之间使用分布式训练&#xff0c;您可以使用一些深度学习框架提供的工具和库来实现。 这里以TensorFlow为例&#xff0c;介绍一下如何在两台主机之间使用分布式训练。 首先&#xff0c;您需要安装TensorFlow和CUDA等相关软件&#xff0c;并确保两台主机都可以访问…

Python - Pycharm 配置 autopep8 并设置快捷键

什么是 PEP8 官方&#xff1a;PEP 8 – Style Guide for Python Code | peps.python.org 中文翻译博客&#xff1a;https://www.cnblogs.com/ajianbeyourself/p/4377933.html PEP8 是 Python 官方推出的一套编码的规范&#xff0c;只要代码不符合它的规范&#xff0c;就会有…

vulnhub dc-8

1.信息搜集 端口 22,80,31337 存活ip 192.168.85.136 2.访问网站&#xff0c;进行信息搜集 在欢迎页面发现sql注入 sqlmap进行跑数据 python sqlmap.py -u "http://192.168.85.136/?nid1" --batch -D d7db -T users -C name,pass --dump尝试robots.txt,发现后他登…

android DatePicker 和 TimePicker 样式

这种 DatePicker 样式&#xff0c;对应的 xml 这里 year, month, day 都支持双向绑定 <DatePickerandroid:id"id/datePicker"android:layout_width"match_parent"android:layout_height"wrap_content"app:layout_constraintTop_toBottomOf&…

算法27:最长回文子序列长度——范围模型

目录 题目&#xff1a; 样本模型&#xff1a; 递归版本的范围模型 分析过程 动态规划版本 优化动态规划&#xff1a; 题目&#xff1a; 给定一个字符串str&#xff0c;返回这个字符串的最长回文子序列长度 比如 str “a12b3c43def2ghi1kpm” * 最长回文子序列是“123…

孪生诱捕网络在欺骗防御领域的应用

随着以数字化、网络化和智能化为特征的信息化浪潮的蓬勃兴起&#xff0c;信息已经成为重要的战略资源与重要生产要素&#xff0c;在国家的发展和人们的生产生活中起到至关重要的作用。信息化在给人们带来便利的同时&#xff0c;网络信息安全问题也日益凸显。经过多年的网络安全…

C++ new delete

可执行程序(进程) 的虚拟地址空间: 内核: 操作系统 栈区:函数的形参&#xff0c;非静态的局部变量&#xff0c;函数现场保护数据等等&#xff0c;栈是向下增长的。 共享库的内存映射区域:用于装载一个共享的动态内存库。用户可使用系统接口创建共享内存&#xff0c;做进程间通…