提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
封装类常量池
除了字符串常量池,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