基本数据类型的包装类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源码理解的更多相关文章

  1. 【JDK】:java.lang.Integer源码解析

    本文对JDK8中的java.lang.Integer包装类的部分数值缓存技术.valueOf().stringSize().toString().getChars().parseInt()等进行简要分 ...

  2. java.lang.Integer源码浅析

    Integer定义,final不可修改的类 public final class Integer extends Number implements Comparable<Integer> ...

  3. 如何查看JDK以及JAVA框架的源码

    如何查看JDK以及JAVA框架的源码 设置步骤如下: 1.点 “window”-> "Preferences" -> "Java" -> &q ...

  4. 结合java.util.TreeMap源码理解红黑树

    前言 本篇将结合JDK1.6的TreeMap源码,来一起探索红-黑树的奥秘.红黑树是解决二叉搜索树的非平衡问题. 当插入(或者删除)一个新节点时,为了使树保持平衡,必须遵循一定的规则,这个规则就是红- ...

  5. java.lang.StringBuffer源码分析

    StringBuffer是一个线程安全的可变序列的字符数组对象,它与StringBuilder一样,继承父类AbstractStringBuilder.在多线程环境中,当方法操作是必须被同步,Stri ...

  6. java中Object源码理解

    java阅读笔记 1.object getClass() 返回是的此object运行时的类,返回的对象是被object锁定的对象,调用这个方法不需要进行强转 public static void ma ...

  7. 如何在Eclipse中查看JDK以及JAVA框架的源码(转载)

    原文链接:http://www.cnblogs.com/outlooking/p/5243415.html 设置步骤如下: 1.点 “window”-> "Preferences&qu ...

  8. 如何在Eclipse中查看JDK以及Java框架的源码

    方法一:快速简单 第一步: 打开你的Eclipse,然后随便找一个Java文件,随便找一个Java类库,比如String什么的,然后按住Ctrl,再点击它,你会发现跳到如下界面: 你会发现报错了:So ...

  9. Java之Integer源码

    1.为什么Java中1000==1000为false而100==100为true? 这是一个挺有意思的讨论话题. 如果你运行下面的代码 Integer a = 1000, b = 1000; Syst ...

随机推荐

  1. 【WPF学习】第一章 XAML介绍

    XAML(Extensible Application Markup Language的简写,发音为“zammel”)是用于实例化.NET对象的标记语言.尽管XAML是一种应用于诸多不同问题领域的技术 ...

  2. Python机器学习及实践 课后小题

    目录 第二章 2.3章末小结 @(Python机器学习及实践-----从零开始通往Kaggle竞赛之路) 第二章 2.3章末小结 1 机器学习模型按照使用的数据类型,可分为监督学习和无监督学习两大类. ...

  3. web端常见测试

    一.登录注册功能 1.页面调转 2.tab键与enter键 3.密码加密显示,是否支持复制粘贴 4.账号密码校验 5.刷新页面,更新验证码 二.界面测试 1.样式.颜色.整体布局风格 2.最大化.最小 ...

  4. Docker常用命令和功能介绍

    可以搜索 dockerfile 定制创建一个redis镜像image 表示镜像docker search 搜索镜像的名称和标签docker 所在目录/var/lib/dockerdocker的镜像文件 ...

  5. vue报错There are multiple modules with names that only differ in casing. This can lead to unexpected behavior when compiling on a filesystem with other case-semantic. Use equal casing. Compare these mod

    今天在开发一个新项目时,当安装完依赖包启动项目后报了一个这个错 There are multiple modules with names that only differ in casing.Thi ...

  6. centos7上python3.6.5的安装及卸载

    前言 最近开始安装配置公司给我的台式机,加上刚刚购买的ECS,处女座的我,环境前前后后大概装了有十来次吧,之前装总是临时网上找教程,但是装下来总是感觉有点别扭,当时服务器装的是3.6.5,虚拟机装的是 ...

  7. C++->List的使用注释

    List容器的应用: //----------单链队列-------队列的链式存储结构--------------- typedef struct QNode{                     ...

  8. open_basedir的配置

    .user.ini的使用 1.限制目录访问 解锁: chattr -i .user.ini 加锁: chattr +i .user.ini .user.ini配置 open_basedir=/项目路径 ...

  9. spring boot MySQL Public Key Retrieval is not allowed

    建议在链接url处添加对应的属性 jdbc:mysql://localhost:3306/book?allowPublicKeyRetrieval=true&useSSL=false 

  10. path('<int:question_id>/vote/', views.vote, name='vote')中的<int:question_id>的含义

    path('<int:question_id>/vote/', views.vote, name='vote')<int:question_id>用于匹配URL的值,并将扑捉到 ...