java回忆录——运行看泛型?1==1?200==200?1+1=2?(代码片段)

minigeek minigeek     2022-12-10     253

关键词:

大家是不是都是看到标题来的呢?好奇心?好吧,不管出于什么原因,都请大家看完这篇文章,让你对 java 的源代码和反射有更深的理解,哈哈,好像已经说出来了,这篇文章是继于上篇反射入门的补充。

第一个问题:泛型真的能够限制吗?

需求:我给你ArrayList<Integer>的一个对象,我想在这个集合中添加一个字符串数据,如何实现呢??

大家一看,这不明摆着不行吗,往集合中添加一个integer那行,当我往集合中添加一个字符串的时候,编译器就直接报错了。表面上是这样的,让我们来看看 ArrayList 中 add()方法的源码:

可以看到 add()方法传的参数为泛型,有就是说我们在创建 ArrayList 的对象时指定的泛型只在编译的时候起作用,这时我们就想到了反射,因为反射是在运行时起作用的。目标就是利用反射调用 add()方法,将字符串作为参数传进去,不就实现了吗?

public class Test 
    public static void main(String[] args)   
        ArrayList<Integer> list = new ArrayList<>();
        //添加一个Integer
        list.add(10);
        //获取ArrayList的Class对象
        Class c = list.getClass();
        //获取ArrayList的add方法,参数为泛型,所以传入个Object的类类型
        Method add = c.getMethod("add", Object.class);
        //调用add方法将字符串添加进去
        add.invoke(list, "qiuqing");
        //打印该ArrayList
        System.out.println(list);   
    

结果:

第二个问题:1 真的和 1 相等吗?

大家先来看看几个简单的例子:

    int a = 1;
    int b = 1;
    System.out.println(1==1);

这个结果肯定是 true ,因为这是基本类型的比较,==比较的是内容,所以为 true。

    Integer a = new Integer(1);
    Integer b = new Integer(1);
    System.out.println(a==b);

这个结果是 false,应该也不难,Integer 为包装器类型,属于引用类型,==比较的是地址,因为使用了 new 关键字,肯定要在堆中开辟一块内存,所以内存地址肯定不一样。

        Integer a = 1;
        Integer b = 1;
        System.out.println(a==b);

这个结果有的人不一定知道,结果为 true,为什么呢?我们都知道左边类型为 Integer ,右边为1是 int,并且在JDK1.5之后基本数据类型和包装器类型之间存在自动装箱和拆箱。

那么右边的自动装箱为 new Integer(1),那就和第二个例子一样,结果不应该是 false 吗?怎么为 true呢?

我们知道自动装箱的时候应该会调用 valueOf(int i)方法,那我们来看一下 Integer 中的 valueOf(int i)方法:

可以知道 low = -128,high = 127,当前 i = 1,满足判断条件,可以看到返回了一个从 cache 数组的值。这里返回 cache[129];

那么这个 cache 数组到底多大,到底存了哪些值呢?

可以看到 cache 数组是属于 Integer 的内部类 IntegerCache 的,而且可以算出 cache 数组大小为 256 ,并且存的数为 -128 到 127 。

这下我们知道当为 1 的时候返回的是 cache[129] = 1;当 == 比较的时候,两个 1 为 Integer 类型会自动拆箱,这时就和第一个例子一样了,基本数据类型比较,所以为 true。

        Integer a = 200;
        Integer b = 200;
        System.out.println(a==b);

第三个例子听懂了,这个应该很简单了,结果为 false,为什么?因为 200 不在 -128 到 127 之间,所以判断条件不满足,应该是走下面这条语句:

记住包装器的缓存:

Boolean:(全部缓存)

Byte:(全部缓存)

Character(<= 127缓存)

Short(-128 — 127缓存)

Long(-128 — 127缓存)

Integer(-128 — 127缓存)

Float(没有缓存)

Doulbe(没有缓存)

第三个问题:1 + 1 永远等于 2 吗?

相信大家看完上面这个例子,这个题应该不难吧,可以利用这个缓存数组来做点手脚,是的,就是结合上篇文章讲的反射将缓存数组反射出来。

需要利用反射更改Integer中的缓存,具体步骤:

1、 获取Integer中的内部静态类IntegerCache;

2、获取cache域;

3、myCache.setAccessible(true)是取消jvm的变量修饰检查,虽然cache数组是static final类型,我们仍然可以修改其数组元素;

4、获取数组对象;

5、数组范围默认是-128到127,阅读源码可知,-128到127之间的、并且通过Integer.valueOf得到的Integer值是直接使用缓存的;cache[130]对应的是Integer(2),可将其修改为Integer(3);

6、System.out.printf的函数接口是PrintStream.printf(String format, Object… args),由于入参为Object,1 + 1会被自动装箱,从而输出Integer.valueOf(2) = cache[130] = cache[131] = 3。

        Class cache = Integer.class.getDeclaredClasses()[0]; 
        Field myCache = cache.getDeclaredField("cache");
        myCache.setAccessible(true);
        Integer[] newCache = (Integer[]) myCache.get(cache);
        newCache[130] = newCache[131];
        int e = 1;
        int f = e + e;
        System.out.printf("%d + %d = %d",e,e,f);

结果:

java常用基础知识回忆1

...器检查出错误,只在编译阶段被处理成了普通类和方法,在运行阶段会被擦除,在处理泛型类型时,会有一个原始类型被自动提供,原始类型的名字就是擦除类型参数的泛型类型的名字.(如果没有jvm会默认为obj 查看详情

从装箱拆箱看泛型

.NET很容易把值类型转换为引用类型,所以可以在需要对象的任意地方使用值类型。例如int可以赋予一个对象,从值类型转换为引用类型称为装箱。如果方法需要把一个对象作为参数,同时传递一个值类型,装箱操作就会自动进... 查看详情

day15java语言中的-------泛型

...间的关系,对你在逻辑架构上有很大的帮助。下面就来看看泛型究竟是怎么一会事儿。二、泛型【Gener 查看详情

java之泛型

...检查和类型转换的方法。也就是说,泛型信息不会进入到运行时阶段。3、泛型的使用1..泛型类2.泛型方法3.泛型接口3.1泛型类publicclassPerson{publicstaticvoidmain(String[]arg 查看详情

类和对象

...字是构造类型泛型,我太急了,能用C#写点小东西后继续看泛型。继承遇到的困难:pub 查看详情

java泛型概述与应用(代码片段)

文章目录泛型1.1泛型概述1.2泛型类1.2.1示例代码1.3泛型方法1.3.1示例代码1.4泛型接口1.4.1示例代码1.5类型通配符1.5.1类型通配符:<?>1.5.2类型通配符上限:<?extends类型>1.5.3类型通配符下限:<?super类型>1.5.4泛型通配符的使用... 查看详情

java5特性泛型

1.泛型类2.泛型方法3.泛型子类型4.泛型通配符5.类型擦除泛型在编译时会擦除,不会进入运行阶段 查看详情

201771010120苏浪浪面向对象程序设计(java)第10周

...试程序并进行代码注释。测试程序1:l 编辑、调试、运行教材311、312页代码,结合程序运行结果理解程序;l 在泛型类定义及使用代码处添加注释;l&nb 查看详情

java_集合之二

...的特性,它提供了编译时类型安全检测机制泛型的好处把运行时期的问题提前到了编译期间避免了强制类型转换泛型的定义格式<类型>:指定一种类型的格式.尖括号里面可以任意书写,一般只写一个字母.例如:<E><T><... 查看详情

java基础

...,属于一种参数化类型,可以作为参数传递.泛型的好处:1)将运行时期异常提前到了编译时期2)优化了设计,解决了×××警告线问题3)避免了强制类型转换泛型的 查看详情

java反射和泛型

反射在计算机科学中,反射是指计算机程序在运行时(Runtime)可以访问、检测和修改它本身状态或行为的一种能力。[1]用比喻来说,反射就是程序在运行的时候能够“观察”并且修改自己的行为。要注意术语“反射”和“内省... 查看详情

java的泛型

...型,但是对象的类型不一致,会导致ClassCassException异常;为了运行时期不出现类型异常,可以在定义容器时,就明确容器中的元素类型,在向容器存储对象时就进行检查,一旦不一致编译就报错.是一项运行在编译时期的安全机制注意结合... 查看详情

java泛型

...出现的,用于解决安全问题,是一个安全机制2.好处是将运行时期的异常classCastException转移到了编译时期3.在java中应用最广泛的地方就是集合4.在java的api中有<E>和<t>都是泛型e代表elementt代表type5.什么时候使用泛型呢?当... 查看详情

java编译时注解和运行时注解有啥区别

...件,在编译期间将被丢弃,不能通过JVM获取注解信息;2)运行时注解,编译时被存储在.class字节码文件,可以通过JVM运行时获取注解信息(且只限于被RUNTIME注解的注解)。参考技术A重写,重载,泛型,分别是在运行时还是编译时执... 查看详情

java的泛型

...化,模板化编程,简化编程2泛型的原理java的泛型参数在运行时会进行类型擦除,擦除后就是object,编译器也会转成object进行编译那么为啥使用泛型呢?更加安全更好的可读性3泛型的使用最常用的容器类比如List泛型类classPair2<... 查看详情

关于java泛型1

publicclassA<T>extendsB<List<T>>{privateintb; }publicclassB<T>{ privateTdate; publicTgetDate(){ returndate; } publicvoidsetDate(Tdate){ this.date=date; }}publicclassC{publics 查看详情

尚硅谷_java零基础教程(泛型generics)--学习笔记(代码片段)

Java泛型一、为什么要有泛型1、为什么要有泛型(Generic)2、泛型的概念二、在集合中使用泛型三、自定义泛型结构1、自定义泛型结构1.1、泛型类、泛型接口1.2、泛型方法2、自定义泛型demo四、泛型在继承上的体现五、通配符的使用... 查看详情

java泛型学习

package com.fish.genrictiry;import java.util.ArrayList;/* 泛型是jdk1.5使用的新特性。    泛型的好处:  1. 将运行时的异常提前至了编译时。  2. 避免了无谓的强制类型转换 。 & 查看详情