Integer等封装类理解

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档


封装类常量池

除了字符串常量池,Java的基本类型的封装类大部分也都实现了常量池。包括Byte,Short,Integer,Long,Character,Boolean

注意,浮点数据类型Float,Double是没有常量池的。


封装类的常量池是在各自内部类中实现的,比如IntegerCache(Integer的内部类)。要注意的是,这些常量池是有范围的:
Character:[0-127]
Byte,Short.Integer,Long,:[-128-127]
Boolean:[true,False]

测试代码如下:

public static void main(String[] args) {
  Character a=129;
  Character b=129;
  Character c=120;
  Character d=120;
  System.out.println(a==b);
  System.out.println(c==d);
  System.out.println("...integer...");
  Integer i=100;
  Integer n=100;
  Integer t=290;
  Integer e=290;
  System.out.println(i==n);
  System.out.println(t==e);
}

运行结果:

false
true
…integer…
true
false

封装类的常量池,其实就是在各个封装类里面自己实现的缓存实例(并不是JVM虚拟机层面的实现),如在Integer中,存在IntegerCache,提前缓存了-128~127之间的数据实例。意味着这个区间内的数据,都采用同样的数据对象。这也是为什么上面的程序中,通过==判断得到的结果为true。

示例:pandas 是基于NumPy 的一种工具,该工具是为了解决数据分析任务而创建的。

二、intern()方法

在Integer中的valueOf方法中,我们可以看到,如果传递的值i是在IntegerCache.low和IntegerCache.high范围以内,则直接从IntegerCache.cache中返回缓存的实例对象。

那么,在String类型中,既然存在字符串常量池,那么有没有方法能够实现类似于IntegerCache的功能呢?

答案是:intern()方法。由于字符串池是虚拟机层面的技术,所以在String的类定义中并没有类似IntegerCache这样的对象池,String类中提及缓存/池的概念只有intern() 这个方法。

这个方法的作用是:去拿String的内容去Stringtable里查表,如果存在,则返回引用,不存在,就把该对象的"引用"保存在Stringtable表里。

public static void main(String[] args) {
  String str = new String("Hello World");
  String str1=str.intern();
  String str2 = "Hello World";
  System.out.print(str1 == str2);
}

比如下面这段程序:

 String str = new String("Hello World");
        String str1=str.intern();
        String str2 = "Hello World";
        System.out.print(str1 == str2);
        System.out.print(str1 == str);

运行的结果为:true和false
实现逻辑如下图所示,str1通过调用str.intern()去常量池表中获取Hello World字符串的引用,接着str2通过字面量的形式声明一个字符串常量,由于此时Hello World已经存在于字符串常量池中,所以同样返回该字符串常量Hello World的引用,使得str1和str2具有相同的引用地址,从而运行结果为true。
在这里插入图片描述
总结:intern方法会从字符串常量池中查询当前字符串是否存在:

  • 若不存在就会将当前字符串放入常量池中,并返回当地字符串地址引用。
  • 如果存在就返回字符串常量池那个字符串地址。

注意,所有字符串字面量在初始化时,会默认调用intern()方法
这段程序,之所以a==b,是因为声明a时,会通过intern()方法去字符串常量池中查找是否存在字符串Hello,由于不存在,则会创建一个。同理,变量b也同样如此,所以b在声明时,发现字符常量池中已经存在Hello的字符串常量,所以直接返回该字符串常量的引用。
public static void main(String[] args) {
String a=“Hello”;
String b=“Hello”;
}

基本引用类型

Byte,Short,Integer,Long,Character,Boolean等基本类型封装类修饰的value值都是final修饰,因此需要通过构造方法来赋值
比如Integer:

    /**
     * The value of the {@code Integer}.
     *
     * @serial
     */
    private final int value;

    /**
     * Constructs a newly allocated {@code Integer} object that
     * represents the specified {@code int} value.
     *
     * @param   value   the value to be represented by the
     *                  {@code Integer} object.
     */
    public Integer(int value) {
        this.value = value;
    }

问题解答

面试题:new Integer(112)和Integer.valueOf(112)的区别

  • new Integer,创建一个Integer实例对象
  • Integer.ValueOf(112),在-128~127之间的数据,之间从缓存中获取即可。

有两个Integer变量a,b,通过swap方法之后,交换a,b的值,请写出swap的方法。

public class SwapExample {
 
    public static void main(String[] args){
        Integer a=1;
        Integer b=2;
        System.out.println("交换前:a="+a+",b="+b);
        swap(a,b);
        System.out.println("交换后:a="+a+",b="+b);
    }
 
    private static void swap(Integer a,Integer b){
       //doSomething
    }
}

基础不是很好的同学,可能会很直接的按照”正确的逻辑“来编写程序,可能的代码如下。

int temp = a;
a=b;
b =temp;

程序逻辑,理论上是没问题,定义一个临时变量存储a的值,然后再对a和b进行交换。而实际运行结果如下

交换前:a=1,b=2
交换后:a=1,b=2

问题解答:
Java中有两种参数传递类型。

  • 值传递,形参不会改变实参
  • 地址传递,传递的是内存地址的引用,对于数据的修改,会影响地址中变量

在Java中,只有一种参数传递方式,就是值传递。但是,当参数传的是基本类型时,传的是值的拷贝,对拷贝变量的修改不影响原变量;当传的是引用类型时,传的是引用地址的拷贝,但是拷贝的地址和真实地址指向的都是同一个真实数据,因此可以修改原变量中的值;当传的是Integer类型时,虽然拷贝的也是引用地址,指向的是同一个数据,但是Integer的值不能被修改,因此无法修改原变量中的值。

我们来看一下Integer中value值得定义,可以发现该属性是final修饰,意味着是不可更改。

    /**
     * The value of the {@code Integer}.
     *
     * @serial
     */
    private final int value;

因此,上述代码之所以没有交换成功,是因为传递到swap方法中的a和b,会创建一个变量副本,这个副本中的值虽然发生了交换,但不影响原始值。

了解了这块知识之后,我们的问题就变成了,如何对一个修饰了final关键字的属性进行数据修改。那就是通过反射来实现,实现代码如下.

public class SwapExample  {

    public static void main(String[] args){
        Integer a=1;
        Integer b=2;
        System.out.println("交换前:a="+a+",b="+b);
        swap(a,b);
        System.out.println("交换后:a="+a+",b="+b);
    }

    private static void swap(Integer a,Integer b){
        try {
            Field field=Integer.class.getDeclaredField("value");
            Integer temp= a;
            field.setAccessible(true); //针对private修饰的变量,需要通过该方法设置。
            field.set(a,b);
            field.set(b,temp);
        } catch (NoSuchFieldException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
    }
}

那这段代码运行完是否能达到预期呢? 上述程序运行结果如下:

交换前:a=1,b=2
交换后:a=2,b=2

从结果来看,确实是发生了变化,但是变化并不完整,因为b=1这个预期值并没有出现。为什么呢?其实还是和今天分享得主题有关系,我们来逐步看一下。
1.Integer temp =a这个地方,基于IntegerCache的原理,这里并不会产生一个新的temp实例,意味着temp变量和a变量指向的内存地址是同一个。
2.当通过field.set方法,把a内存地址的值通过反射修改成b以后,那么此时a的值就变成2,注意:由于内存地址变成了2的地址,而temp这个变量又指向该内存地址,因此temp的值自然就变成了2.
3.接着使用filed.set(b,temp)修改b属性的值,此时temp的值时2,所以得到的结果b也变成了2.

如何修改:
把Integer temp = a;改成Integer temp = new Integer(a);

参考:

https://blog.csdn.net/q331464542/article/details/121194130

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值