jdk之java.lang.Integer源码理解
基本数据类型的包装类java.lang.Integer是我们频繁使用的一个系统类,那么通过一个示例反应出的几个问题来深入理解一下此类的源码。
需求:实现Integer类型的两个数值交换。
package cn.integer;
public class Demo {
public static void main(String[] args) {
Integer a = 1;
Integer b = 2;
System.out.println("bofore swap a:"+a+",b:"+b);
//交换a和b的值
swap(a,b);
System.out.println("after swap a:"+a+",b:"+b);
}
/**
* 数据交换
* @param a
* @param b
*/
private static void swap(Integer a, Integer b) {
/**
* 如何实现呢?尝试几种方法
*/
/*01.方式一(无法实现)
a = a^b;
b = a^b;
a = a^b;*/
/*02.方式二(无法实现)
int tmp = a;
a = b;
b = tmp;*/
/**
* 以上两种方式是因为java的值传递特性故无法实现数据交换。
*/
}
}
Java值传递的示意图如下:

当调用swap(..)方法时,在堆中会创建这两个值得副本,形参num1和num2指向副本的数据。原ab指向的数据不会改变。
那么如何通过修改swap()方法实现呢?
观察java.lang.Integer源码:
public final class Integer extends Number implements Comparable<Integer> {
...
}
可以发现 Integer和String一样,是final修饰的,这是因为jdk将这些系统类封装起来不希望被破坏。
继续看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;
}
这里的value也是使用final修饰的,那么如果想修改它的值,可以使用反射的方式获取value字段并进行修改。
修改swap()方法中代码:
public class Demo {
public static void main(String[] args) {
Integer a = 1;
Integer b = 2;
System.out.println("bofore swap a:"+a+",b:"+b);
//交换a和b的值
swap(a,b);
System.out.println("after swap a:"+a+",b:"+b);
}
/**
* 数据交换
* @param a
* @param b
*/
private static void swap(Integer a, Integer b) {
try {
//获取Integer类中私有字段value
Field field = Integer.class.getDeclaredField("value");
/**
* 开启访问权限
* public final class Field extends AccessibleObject implements Member {}
* 跟进AccessibleObject
* public void setAccessible(boolean flag) throws SecurityException {
SecurityManager sm = System.getSecurityManager();
if (sm != null) sm.checkPermission(ACCESS_PERMISSION);
setAccessible0(this, flag);
}
private static void setAccessible0(AccessibleObject obj, boolean flag)
throws SecurityException
{
if (obj instanceof Constructor && flag == true) {
Constructor<?> c = (Constructor<?>)obj;
if (c.getDeclaringClass() == Class.class) {
throw new SecurityException("Cannot make a java.lang.Class" +
" constructor accessible");
}
}
obj.override = flag;
}
*/
field.setAccessible(true);
//临时变量
int tmp = a.intValue();
//数据交换
field.set(a, b);
field.set(b, tmp);
} catch (Exception e) {
e.printStackTrace();
}
}
}
运行结果:

诶,我去。怎么只改变了一个值?咋成功了一半呢?
01.首先,Integer a = 1; 这里会自动装箱,将 int类型的1 转换成 Integer类型。
通过valueOf方法进行装箱
public static Integer valueOf(int i) {
if (i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)];
return new Integer(i);
}
这个方法非常重要,如果传入的参数在 -128-127之间,会返回一个缓存中的数据。否则就 new出一个Integer对象!
Integer.IntegerCache是Integer中的内部类:
private static class IntegerCache {
static final int low = -128;
static final int high;
static final Integer cache[];
static {
// high value may be configured by property
int h = 127;
String integerCacheHighPropValue =
sun.misc.VM.getSavedProperty("java.lang.Integer.IntegerCache.high");
if (integerCacheHighPropValue != null) {
try {
int i = parseInt(integerCacheHighPropValue);
i = Math.max(i, 127);
// Maximum array size is Integer.MAX_VALUE
h = Math.min(i, Integer.MAX_VALUE - (-low) -1);
} catch( NumberFormatException nfe) {
// If the property cannot be parsed into an int, ignore it.
}
}
high = h;
cache = new Integer[(high - low) + 1];
int j = low;
for(int k = 0; k < cache.length; k++)
cache[k] = new Integer(j++);
// range [-128, 127] must be interned (JLS7 5.1.7)
assert IntegerCache.high >= 127;
}
private IntegerCache() {}
}
02.当我们获取数值1时,
return IntegerCache.cache[i + (-IntegerCache.low)]
计算出IntegerCache.cache[129]的值(也就是1)返回。 cache这个数组存放着 -128-127这些数据。故index=129就是返回1。 03. 执行这行时 field.set(a, b);
将传入的2复制给a,此时a为2.
注意:这里通过set方法改变的的是缓存cache数组中的数据! 04. 当执行到 field.set(b, tmp);
tmp为1,而set方法的参数类型是Object,可以传入int类型的tmp
public void set(Object obj, Object value)
throws IllegalArgumentException, IllegalAccessException
{
if (!override) {
if (!Reflection.quickCheckMemberAccess(clazz, modifiers)) {
Class<?> caller = Reflection.getCallerClass();
checkAccess(caller, clazz, obj, modifiers);
}
}
getFieldAccessor(obj).set(obj, value);
}
所以这个tmp=1是从Integer的缓存数组中取得,因上一行操作已经将cache中index为129位置得原数值1改变为了2,故在cache中获取的是2! 那么把2赋值给了b自然是2!(b对应的下标[130],a对应的下标[129])
我们发现,问题的关键在于这个缓存cache!
如果说可以避开走缓存这一步,我们就能实现数据交换。除了传入 -128-127之外的数据,我们还可以:
001.将tmp转换成Integer对象后在传入Field的set方法:
private static void swap(Integer a, Integer b) {
try {
//获取Integer类中私有字段value
Field field = Integer.class.getDeclaredField("value");
field.setAccessible(true);
//临时变量
int tmp = a.intValue();
//数据交换
field.set(a, b);
field.set(b, new Integer(tmp));
} catch (Exception e) {
e.printStackTrace();
}
}
运行结果:

002. 使用 Field类的 setInt方法
public void setInt(Object obj, int i)
throws IllegalArgumentException, IllegalAccessException
{
if (!override) {
if (!Reflection.quickCheckMemberAccess(clazz, modifiers)) {
Class<?> caller = Reflection.getCallerClass();
checkAccess(caller, clazz, obj, modifiers);
}
}
getFieldAccessor(obj).setInt(obj, i);
}
修改代码 field.setInt(b,tmp); 也可以实现。
003. 非要实现打印效果的话,使用非常规手短,直接在swap中打印结果...
private static void swap(Integer a, Integer b) {
System.out.println("after swap a:2,b:1");
System.exit(0);
}
这个示例中涉及到技能点:

jdk之java.lang.Integer源码理解的更多相关文章
- 【JDK】:java.lang.Integer源码解析
本文对JDK8中的java.lang.Integer包装类的部分数值缓存技术.valueOf().stringSize().toString().getChars().parseInt()等进行简要分 ...
- java.lang.Integer源码浅析
Integer定义,final不可修改的类 public final class Integer extends Number implements Comparable<Integer> ...
- 如何查看JDK以及JAVA框架的源码
如何查看JDK以及JAVA框架的源码 设置步骤如下: 1.点 “window”-> "Preferences" -> "Java" -> &q ...
- 结合java.util.TreeMap源码理解红黑树
前言 本篇将结合JDK1.6的TreeMap源码,来一起探索红-黑树的奥秘.红黑树是解决二叉搜索树的非平衡问题. 当插入(或者删除)一个新节点时,为了使树保持平衡,必须遵循一定的规则,这个规则就是红- ...
- java.lang.StringBuffer源码分析
StringBuffer是一个线程安全的可变序列的字符数组对象,它与StringBuilder一样,继承父类AbstractStringBuilder.在多线程环境中,当方法操作是必须被同步,Stri ...
- java中Object源码理解
java阅读笔记 1.object getClass() 返回是的此object运行时的类,返回的对象是被object锁定的对象,调用这个方法不需要进行强转 public static void ma ...
- 如何在Eclipse中查看JDK以及JAVA框架的源码(转载)
原文链接:http://www.cnblogs.com/outlooking/p/5243415.html 设置步骤如下: 1.点 “window”-> "Preferences&qu ...
- 如何在Eclipse中查看JDK以及Java框架的源码
方法一:快速简单 第一步: 打开你的Eclipse,然后随便找一个Java文件,随便找一个Java类库,比如String什么的,然后按住Ctrl,再点击它,你会发现跳到如下界面: 你会发现报错了:So ...
- Java之Integer源码
1.为什么Java中1000==1000为false而100==100为true? 这是一个挺有意思的讨论话题. 如果你运行下面的代码 Integer a = 1000, b = 1000; Syst ...
随机推荐
- .net core3.0 webapi搭建(一)
一.创建WebApi项目: 生成项目之后,控制器默认生成了一个WeatherForecastController 我们可以直接启动项目,F5调试,默认进入WeatherForecastControll ...
- oracle add_month函数
本文转自:https://blog.csdn.net/lanchengxiaoxiao/article/details/7695057 add_months 函数主要是对日期函数进行操作,举例子进行说 ...
- urllib模块提供的urlretrieve()函数使用
urllib模块提供的urlretrieve()函数,urlretrieve()方法直接将远程的数据下载到本地 注意:若是网站有反爬虫的话这个函数会返回 403 Forbidden 参数url:传入的 ...
- 【终端使用】常用Linux命令的基本使用
常用Linux命令的基本使用: 命令 对应英文 作用 ls list 查看当前文件夹下的内容 pwd print work directory 查看当前所在的文件夹 cd [目录名] change d ...
- vue必须掌握之组件通信(7种方法)
方法一:$emit / props 父组件通过props的方式向子组件传递,子组件通过$emit触发父组件中v-on绑定的自定义事件 <!--父组件--> <template> ...
- python开发第二篇 :python基础
python基础a.Python基础 -基础1. 第一句python -python后缀名可以任意? -导入模块时如果不是.py文件,以后的文件后缀名是.py.2.两种 ...
- 如何在任意文件下启动jupyter notebook,而不用担心环境配置问题
网上看了很多帖子,说可以写一个bat文件,将bat文件放在你想启动jupyter notebook的地方.可是不行,不能解决我的问题!!!!!!!!!!! 网上是这样说的: ######这为引用### ...
- VNote笔记本和画图
VNote笔记本 跨平台的,以markdown标记语言记录的文本文档.从sourceforget.org开源网址下载即可. 画图集成: 1.集成graphviz http://www.graphviz ...
- Python-Django学习笔记(二)-创建一个Django项目与应用
1.创建一个Django项目 打开cmd命令行,设置好工作目录(cd 目录路径),然后输入 django-admin startproject projectname #必须安装好Django才可以执 ...
- Linux运维---16. Kolla部署OpenStack外接rabbit集群
# 前提时rabbit集群已经搭建完成 vim /etc/kolla/globals.yml enable_rabbitmq: "no" rpc_transport_url: &q ...