强化基础-Java-泛型

什么是泛型?

泛型其实就参数化类型,也就是说这个类型类似一个变量是可变的。

为什么会有泛型?

在没有泛型之前,java中是通过Object来实现泛型的功能。但是这样做有下面两个缺陷:
1 获取值的时候必须进行强转
2 没有错误检查

Object data[] = new Object[]{"one", "two", 1, new Object()};
String str = (String) data[0];

一般来说我们会把相同类型的数据放到一起,但是有没有发现如果使用object我们可以放入任意类型的数据,编译器也不会报错,这样在使用的时候就增加了类型转换异常的概率。
那么使用泛型呢?

List<String> strList = new ArrayList<>();
strList.add("one");
// 这句代码编译器就会提醒你不能这样使用
strList.add(1);

非static的内部类,在外部类加载的时候,并不会加载它,所以它里面不能有静态变量或者静态方法。

泛型擦除

泛型信息只存在于代码编译阶段,但是在java的运行期(已经生成字节码文件后)与泛型相关的信息会被擦除掉,专业术语叫做类型擦除。

// 这个例子 最后输出的结果为 true class的结果为:java.util.ArrayList
public class Pair<T> {
    public static void main(String[] args) {
        List<Integer> integerList = new ArrayList<>();
        List<String> stringList = new ArrayList<>();
        System.out.println(Objects.equals(integerList, stringList));
        System.out.println(integerList.getClass());
        System.out.println(stringList.getClass());
    }
}

再看下面这个例子:

public class Pair<T> {
    private T data;
    public static void main(String[] args) {
        Pair<String> stringPair = new Pair<>();
        System.out.println(stringPair.getClass());
        Field[] declaredFields = stringPair.getClass().getDeclaredFields();
        Arrays.stream(declaredFields).forEach(f -> System.out.println(String.valueOf(f.getName() + " " + f.getType())));
        System.out.println("========================");
        Pair<Integer> integerPair = new Pair<>();
        System.out.println(integerPair.getClass());
        Field[] fields = integerPair.getClass().getDeclaredFields();
        Arrays.stream(fields).forEach(f -> System.out.println(String.valueOf(f.getName() + " " + f.getType())));
    }
}

从运行结果我们可以证明在运行时类型已经被擦除为Object类型
在这里插入图片描述

在泛型类被类型擦除的时候,之前泛型类中的类型参数部分如果没有指定上限,如则会被转译成普通的Object 类型,如果指定了上限,如则类型参数就被替换成类型上限。
1 没有限定泛型的界限

public class Pair<T> {
    private T first;
    private T second;
}

擦除后,没有限定泛型的界限所以是Object类型:

public class Pair {
	private Object first;
	private Object second;
}

2 限定了泛型的界限

public class Pair<T extends Comparable> {
    private T data;
    public static void main(String[] args) {
        Pair<String> stringPair = new Pair<>();
        System.out.println(stringPair.getClass());
        Field[] declaredFields = stringPair.getClass().getDeclaredFields();
        Arrays.stream(declaredFields).forEach(f -> System.out.println(String.valueOf(f.getName() + " " + f.getType())));
        System.out.println("========================");
        Pair<Integer> integerPair = new Pair<>();
        System.out.println(integerPair.getClass());
        Field[] fields = integerPair.getClass().getDeclaredFields();
        Arrays.stream(fields).forEach(f -> System.out.println(String.valueOf(f.getName() + " " + f.getType())));
    }
}

在这里插入图片描述

这就证明在擦除后:

public class Pair<T extends Comparable & Serializable> {
    private Comparable first;
    private Comparable second;
}

如果交换泛型的顺序: Pair<T extends Serializable & Comparable > 那么擦除以后的类型为Serializable,这个时候编译器会插入强制类型转换(也就是说我们获取Comparable 类型时候会强制转换),为了提高效率一般将标记接口往末尾放。

public class Pair<T extends Serializable & Comparable> {
    private T data;
    public static void main(String[] args) {
        Pair<String> stringPair = new Pair<>();
        System.out.println(stringPair.getClass());
        Field[] declaredFields = stringPair.getClass().getDeclaredFields();
        Arrays.stream(declaredFields).forEach(f -> System.out.println(String.valueOf(f.getName() + " " + f.getType())));
        System.out.println("========================");
        Pair<Integer> integerPair = new Pair<>();
        System.out.println(integerPair.getClass());
        Field[] fields = integerPair.getClass().getDeclaredFields();
        Arrays.stream(fields).forEach(f -> System.out.println(String.valueOf(f.getName() + " " + f.getType())));
    }
}

在这里插入图片描述

所谓的插入强制类型转换,就是编译器在编译泛型表达式的时候会转化为两条指令:

  • 对原始方法的调用得到Object
  • 将返回的Object类型强制转换为具体的类型。

3 泛型方法的擦除
首先我们要区分一下泛型方法,泛型方法。只有声明了的方法才是泛型方法,泛型类中的使用了泛型的成员方法并不是泛型方法。如果单纯的在方法中使用了泛型它不是泛型方法。
泛型方法:

public <E> E convert(T t) { 
}

非泛型方法:

public T getT(T t) {
}
public static <T extends Comparable> T min(T[] data) {
   return null;
}

泛型方法擦除后:

public static Comparable min(Comparable [] data) {
   return null;
}

需要注意的是泛型方法的多态是通过桥方法实现的

public class Pair<T> {
    private T time;
    public void setTime(T time) {
       this.time = time;
    }
}
// 被擦除以后
public void setTime(Object time) {
}

如果这个时候子类继承Pair,并指定了类型:

class DateInterVal extends Pair<LocalDate> {
    @Override
    public void setTime(LocalDate time) {
    }
}

这个时候如果调用Pair的setTime方法,由于多态其实底层是这样来实现的:

setTime(setTime((LocalDate) time));

总结:1 虚拟机中没有类型,只有普通的类和方法 2 所有的类型参数都会替换为他们的限定类型 3 合成桥方法来保持多态 4为保证类型安全,必要时会插入强制类型转换

泛型方法

1 在类中的泛型方法
首先我们来区分几个定义方式,看注释部分。

public class Pair<T extends Comparable & Serializable> {
    private T data;
    // 编译无法通过 因为这个方法是静态方法,所以我们不能使用T类型,但是我们可以使用E类型,因为E类型是申明的
    public static <E> E convert2E(T t) {
        return null;
    }
    // 在非静态方法的情况下 可以使用上面的类中定义的泛型T
    public <E> E convert2E(T t) {
        return null;
    }
    // 注意这里我们在静态方方法申明了一个T类型,这个T和类上的T类型是没有关联的,是一个全新的类型
    // 这个T可以和类的T是同一个类型,也可以不是同一个类型
    public static  <T> T accept(T t) {
        return null;
    }
}

2 泛型方法中的可变参数

public class Pair<T> {
    static class Dot {
        private int x;
        private int y;
        public Dot(int x, int y) {
            this.x = x;
            this.y = y;
        }
        @Override
        public String toString() {
            return "Dot{" +
                    "x=" + x +
                    ", y=" + y +
                    '}';
        }
    }
    // 泛型方法可变参数
    private static <T> void print(T ...es) {
        for (T t : es) {
            System.out.println(t  + "");
        }
    }
    public static void main(String[] args) {
        Dot dot = new Dot(1, 1);
        print(15000, "15000", dot);
    }
}

2 静态方法与泛型
一个基本原则,如果要在静态方法使用泛型的话,这个方法必须为泛型方法。

// 也就是说必须申明
public static  <T> T accept(T t) {
   return null;
}

泛型缺陷

1 不能使用基本类型实例化类型参数
也就是说没有

Pair<int> Pair<double> 类型

因为泛型在擦除以后为object类型,但是原生类型不能直接赋值为object,而是要使用包装类。
2 不能实例化类型参数
本质也是因为类型擦除导致的

String string = new T();
// 类型擦除以后,很显然是存在问题的
String string = new Object();

但是我们可以通过反射来创建一个实例:

 Pair<String> pair = Pair.class.newInstance();

3 运行时类型查询只适用于原始类型
下面这三条语句都是编译会报错的,因为虚拟中的对象总是一个特定的非泛型类型,所以类查询只能查询原始类型。

pair instanceof String;
pair instanceof Pair<String>;
pair instanceof Pair<T>;
// 这条语句是可以的 原始类型的查询
pair instanceof Pair

并且下面的语句也会返回true:

Pair<String> ps = new Pair<>();
Pair<Double> pd = new Pair<>();
System.out.println(ps.getClass() == pd.getClass());

4 不能创建参数化类型的数组
类型擦除以后变为Pair[] pars = new Pair[10]; 然后我们可以赋予pairs[0] = new Pair(); 没有编译错误,但存在运行时错误。

// 可以申明
Pair<String> [] pairs;
// 不可以实例化 也是一样的如果把String擦除 为Object 可能会导致运行时异常 不安全
Pair[] pairs = new Pair<String>[10];
// 如果是通配类型的 则可以 但是这样的话不是很安全因为里面可以存 Pair<String> 也可以存 Pair<Double> 
// 在使用的时候可能类型转换异常
Pair[] pairs = new Pair<?>[10];

5 Varargs 警告

public static <T> void addAll(Collection<T> coll, T... ts){
   // 这里其实创建了数组就违背了不能创建数组
   for(T t : ts) {
       coll.add(t);
   }
}
public static void main(String[] args) {
   Collection<Pair<String>> table = new ArrayList<>();
   Pair<String> pair1 = new Pair<>();
   Pair<String> pair2 = new Pair<>();
   addAll(table, pair1, pair2);
}
static class Pair<T> {

}

6 不能实例化类型变量
T[] array = new T[10]
类型擦除后上述定义变为Object[] array = new Object[10]; 这样一来我们可以将任何类型赋予array[0], 比如array[0] = “1”; 编译器不会报错,但运行时在使用的时候就有可能会出错了。

// 编译不会通过
T t = new T();
// 编译不会通过
T[] array = new T[10]

这里也可以通过反射来进行:

public T[] demo() {
    T data[] = (T[]) Array.newInstance(Pair.class, 2);
    return data;
}

7 泛型类的静态上下文中类型变量无效
也就是静态不能和泛型一起使用,如果一定要一起使用的话,必须申明。

// 通不过
public static T t;
// 如果一定要使用则需要申明
public static <T> void addAll(Collection<T> coll, T... ts){
     for(T t : ts) {
         coll.add(t);
     }
 }

8 不能抛出或捕获泛型类的实例

public static <T extends Throwable> void doWork() {
  try {
	// 会产生编译错误
  } catch (T e) {

  }
}

泛型中的继承

继承泛型类时,必须对父类中的类型参数进行初始化
1 使用泛型初始化父类的泛型

public class Bar<T> extends Foo<T> {
    
}

2 使用具体的类型

public class Bar extends Foo<String> {

}

特别注意:

// 这里的继承关系是 Integer 和 Double 继承
Box<Number> box = new Box<Number>();
box.add(new Integer(10)); 
box.add(new Double(10.1));
// Box<Integer> Box<Number> 他们并不是继承关系 这一定要注意 

原文链接
以 Collections 类为例,ArrayList 实现 List ,List 继承 Collection 。 所以 ArrayList 是 List 的一个子类型,它是 Collection 的一个子类型。 只要不改变类型参数,子类型关系在类型之间保留。
在这里插入图片描述

泛型中的限定

在通配符类型中,允许类型参数发生变化。
1 子类限定

// 类上
class Son<T extends Foo> {
}
// 方法上
public <T> T demo2(Bar<? extends Number> data) {
}
// 方法的申明
public <T extends Integer> T demo3(Bar<? super Integer> data) {
}

2 超类限定

// 方法上
public <T> T demo1(Bar<? super Integer> data) {
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/493773.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Learn OpenGL 26 视差贴图

什么是视差贴图 视差贴图(Parallax Mapping)技术和法线贴图差不多&#xff0c;但它有着不同的原则。和法线贴图一样视差贴图能够极大提升表面细节&#xff0c;使之具有深度感。它也是利用了视错觉&#xff0c;然而对深度有着更好的表达&#xff0c;与法线贴图一起用能够产生难…

商标跨类异议与跨类保护!

有个朋友对普推知产老杨说收到某邮件&#xff0c;名下商标让某公司抢注了现在公告期&#xff0c;让赶紧提出来异议去处理下&#xff0c;怎么会有这样的事&#xff0c;相同的名称基本上在同类别相关产品是无法公告和获得初审的。 经详细检索分析后&#xff0c;发现不是这样一回…

【Linux】理解父子进程(系统调用创建进程,fork函数,写时拷贝)

目录 fork函数 返回值 内存分配 父子进程是操作系统一个重要的概念&#xff0c;特别是在多任务处理和并发编程中&#xff0c;在Linux中&#xff0c;每个进程都有一个唯一的进程ID&#xff0c;并且每个进程都有可能创建其他进程。当一个进程创建了一个新的进程时&#xff0c;…

【Linux】进程>环境变量地址空间进程调度

主页&#xff1a;醋溜马桶圈-CSDN博客 专栏&#xff1a;Linux_醋溜马桶圈的博客-CSDN博客 gitee&#xff1a;mnxcc (mnxcc) - Gitee.com 目录 1.环境变量 1.1 基本概念 1.2 常见环境变量 1.3 查看环境变量方法 1.4 和环境变量相关的命令 1.5 环境变量的组织方式 1.6 通…

最“原始”的收音机长啥样?

同学们大家好&#xff0c;今天我们继续学习杨欣的《电子设计从零开始》&#xff0c;这本书从基本原理出发&#xff0c;知识点遍及无线电通讯、仪器设计、三极管电路、集成电路、传感器、数字电路基础、单片机及应用实例&#xff0c;可以说是全面系统地介绍了电子设计所需的知识…

vs code

vs code 下载安装 https://code.visualstudio.com/https://code.visualstudio.com/ 下载完后&#xff0c;下一步下一步就安装完了&#xff0c;安装好后可以下载各种好用的插件

【前端面试3+1】03深拷贝浅拷贝、let和var、css盒模型、【有效括号】

一、深拷贝浅拷贝 深拷贝和浅拷贝都是用于复制对象或数组的概念&#xff0c;但它们之间有着重要的区别&#xff1a; 1. 浅拷贝&#xff1a; 浅拷贝是指在拷贝对象或数组时&#xff0c;只会复制一层对象的属性或元素&#xff0c;而不会递归地复制嵌套的对象或数组。因此&#xf…

2024年第十二届计算机与通信管理国际会议(ICCCM 2024)即将召开!

2024年第十二届计算机与通信管理国际会议&#xff08;ICCCM 2024&#xff09;将2024年7月19-21日在日本鹿儿岛召开。会议由鹿儿岛大学主办。此次会议旨在为业界建立一个广泛、有效的交流合作平台&#xff0c;让我们及时了解行业发展动态、掌握最新技术&#xff0c;拓宽研究视野…

修改Jupyter Notebook的默认路径,以及在PowerShell中自定义其启动路径

修改Jupyter Notebook的默认路径&#xff0c;以及在PowerShell中自定义其启动路径 设置 Jupyter Notebook 配置文件&#xff0c;修改默认路径要在PowerShell中设置自定义的启动脚本&#xff0c;以确保Jupyter Notebook能够自动定位到当前路径设置后的效果 在使用Jupyter Notebo…

【C语言】编译和链接

文章目录 一、编译环境和运行环境二、翻译环境2.1 预处理2.2 编译2.2.1 词法分析2.2.2 语法分析2.2.3 语义分析 2.3 汇编2.4 链接 三、运行环境 一、编译环境和运行环境 在ANSIC的任何一种实现中&#xff0c;存在两个不同的环境。 第1种是翻译环境&#xff0c;在这个环境中源代…

C++ STL教程

C STL教程 文章目录 C STL教程1.1 std::vector1.1.1vector的定义1.1.2vector容器的初始化1.1.3vector容器内元素的访问和修改1.1.4vector中的常用函数 1.2 std::string1.2.1string的定义1.2.2string的初始化1.2.3string中元素的访问和修改1.2.4string中连接字符串1.2.5string中…

阿里云服务器安装MySQL(宝塔面板)

只写关键步骤 1. 创建一个云服务器实例 2 修改密码&#xff0c;登录服务器 3. 安装宝塔面板 进入https://www.bt.cn/new/index.html 进入宝塔面板地址 4. 安装Mysql 5. 创建数据库&#xff08;可导入数据库&#xff09; 6. 测试连接数据库 打开Navicat&#xff08;或其他数据…

[Qt] QString::fromLocal8Bit 的使用误区

QString::fromLocal8Bit 是一个平台相关的函数。默认情况下在 Windows 下 就是 gbk 转 utf-8 ,在 Linux就应该是无事发生。因为Linux平台默认的编码方式就是 utf-8 可以通过 void QTextCodec::setCodecForLocale(QTextCodec *c)来修改 Qt默认的编码方式。如下 第一输出乱码的…

DreamPolisher、InternLM2 、AniArtAvatar、PlainMamba、AniPortrait

本文首发于公众号&#xff1a;机器感知 DreamPolisher、InternLM2 、AniArtAvatar、PlainMamba、AniPortrait DreamPolisher: Towards High-Quality Text-to-3D Generation via Geometric Diffusion We present DreamPolisher, a novel Gaussian Splatting based method wit…

kubernetes负载均衡资源-Ingress

一、Ingress概念 1.1 Ingress概念 使用NodePort类型的Service可以将集群内部服务暴露给集群外部客广端,但使用这种类型Service存在如下几个问题。 1、一个端口只能一个服务使用,所有通过NodePort暴露的端口都需要提前规划;2、如果通过NodePort暴露端口过多,后期维护成本太…

中国土壤厚度空间分布数据

土壤层次分为覆盖层 林溶层 淀积层 母质层&#xff0c;其中在林溶层中的最上面那层就是我们通常说的土壤厚度在这一层中&#xff0c;这一层也被称为腐殖层&#xff0c;是肥力性质最好的一层&#xff0c;植物根系和微生物也集中在这一层。至于覆盖层在森林土壤中比较常见&#x…

【LeetCode: 2580. 统计将重叠区间合并成组的方案数 + 合并区间】

&#x1f680; 算法题 &#x1f680; &#x1f332; 算法刷题专栏 | 面试必备算法 | 面试高频算法 &#x1f340; &#x1f332; 越难的东西,越要努力坚持&#xff0c;因为它具有很高的价值&#xff0c;算法就是这样✨ &#x1f332; 作者简介&#xff1a;硕风和炜&#xff0c;…

计算机组成原理 3 运算器

定点补码加/减法运算 补码加减法的实现 补码加法 &#xff1a; [X &#xff0b; Y] 补 [X] 补 &#xff0b; [Y] 补 和的补码 补码的和 补码减法 &#xff1a; [X−Y] 补 [X] 补 &#xff0b; [−Y] 补 [X] 补 −[Y] 补 差的补码 补码的差 求补公式 &#xff1a; [−…

qemu快速入门

1.环境 win10系统上 -》 通过vmware装 -》 CentOS 7.4 -》装qemu虚拟出一台指定cpu的CentOS 7.9 2.安装基本命令 yum install -y net-tools yum install -y wget 3.安装基础依赖 yum groupinstall Development Tools -y yum groupinstall "Virtualization Host"…

MySQL数据库高级语句

文章目录 MySQL高级语句older by 排序区间判断查询或与且&#xff08;or 与and&#xff09;嵌套查询&#xff08;多条件&#xff09;查询不重复记录distinctcount 计数限制结果条目limit别名as常用通配符嵌套查询&#xff08;子查询&#xff09;同表不同表嵌套查询还能用于删除…