Java基础1-Java基本数据类型,以及自动拆装箱里隐藏的秘密(二)

简介: Java基础1-Java基本数据类型,以及自动拆装箱里隐藏的秘密(二)

Java基础1-Java基本数据类型,以及自动拆装箱里隐藏的秘密(一):https://developer.aliyun.com/article/1535596

了解基本类型缓存(常量池)的最佳实践

//基本数据类型的常量池是-128到127之间。
// 在这个范围中的基本数据类的包装类可以自动拆箱,比较时直接比较数值大小。
public static void main(String[] args) {
    //int的自动拆箱和装箱只在-128到127范围中进行,超过该范围的两个integer的 == 判断是会返回false的。
    Integer a1 = 128;
    Integer a2 = -128;
    Integer a3 = -128;
    Integer a4 = 128;
    System.out.println(a1 == a4);
    System.out.println(a2 == a3);

    Byte b1 = 127;
    Byte b2 = 127;
    Byte b3 = -128;
    Byte b4 = -128;
    //byte都是相等的,因为范围就在-128到127之间
    System.out.println(b1 == b2);
    System.out.println(b3 == b4);

    //
    Long c1 = 128L;
    Long c2 = 128L;
    Long c3 = -128L;
    Long c4 = -128L;
    System.out.println(c1 == c2);
    System.out.println(c3 == c4);

    //char没有负值
    //发现char也是在0到127之间自动拆箱
    Character d1 = 128;
    Character d2 = 128;
    Character d3 = 127;
    Character d4 = 127;
    System.out.println(d1 == d2);
    System.out.println(d3 == d4);


    `结果`
    
    `false`
    `true`
    `true`
    `true`
    `false`
    `true`
    `false`
    `true`
    
    

    Integer i = 10;
    Byte b = 10;
    //比较Byte和Integer.两个对象无法直接比较,报错
    //System.out.println(i == b);
    System.out.println("i == b " + i.equals(b));
    //答案是false,因为包装类的比较时先比较是否是同一个类,不是的话直接返回false.
    int ii = 128;
    short ss = 128;
    long ll = 128;
    char cc = 128;
    System.out.println("ii == bb " + (ii == ss));
    System.out.println("ii == ll " + (ii == ll));
    System.out.println("ii == cc " + (ii == cc));
    
    结果
    i == b false
    ii == bb true
    ii == ll true
    ii == cc true
    
    //这时候都是true,因为基本数据类型直接比较值,值一样就可以。
复制代码

总结:

通过上面的代码,我们分析一下自动装箱与拆箱发生的时机:

(1)当需要一个对象的时候会自动装箱,比如Integer a = 10;equals(Object o)方法的参数是Object对象,所以需要装箱。

(2)当需要一个基本类型时会自动拆箱,比如int a = new Integer(10);算术运算是在基本类型间进行的,所以当遇到算术运算时会自动拆箱,比如代码中的 c == (a + b);

(3) 包装类型 == 基本类型时,包装类型自动拆箱;

需要注意的是:“==”在没遇到算术运算时,不会自动拆箱;基本类型只会自动装箱为对应的包装类型,代码中最后一条说明的内容。

在JDK 1.5中提供了自动装箱与自动拆箱,这其实是Java 编译器的语法糖,编译器通过调用包装类型的valueOf()方法实现自动装箱,调用xxxValue()方法自动拆箱。自动装箱和拆箱会有一些陷阱,那就是包装类型复用了某些对象。

(1)Integer默认复用了[-128,127]这些对象,其中高位置可以修改;

(2)Byte复用了全部256个对象[-128,127];

(3)Short服用了[-128,127]这些对象;

(4)Long服用了[-128,127];

(5)Character复用了[0,127],Charater不能表示负数;

Double和Float是连续不可数的,所以没法复用对象,也就不存在自动装箱复用陷阱。

Boolean没有自动装箱与拆箱,它也复用了Boolean.TRUE和Boolean.FALSE,通过Boolean.valueOf(boolean b)返回的Blooean对象要么是TRUE,要么是FALSE,这点也要注意。

本文介绍了“真实的”自动装箱与拆箱,为了避免写出错误的代码,又从包装类型的源码入手,指出了各种包装类型在自动装箱和拆箱时存在的陷阱,同时指出了自动装箱与拆箱发生的时机。

基本数据类型的存储方式

上面自动拆箱和装箱的原理其实与常量池有关。

存在栈中

public void(int a){int i = 1;int j = 1;}方法中的i 存在虚拟机栈的局部变量表里,i是一个引用,j也是一个引用,它们都指向局部变量表里的整型值 1.int a是传值引用,所以a也会存在局部变量表。

存在堆里

class A{int i = 1;A a = new A();}i是类的成员变量。类实例化的对象存在堆中,所以成员变量也存在堆中,引用a存的是对象的地址,引用i存的是值,这个值1也会存在堆中。可以理解为引用i指向了这个值1。也可以理解为i就是1.

3 包装类对象怎么存其实我们说的常量池也可以叫对象池。比如String a= new String("a").intern()时会先在常量池找是否有“a"对象如果有的话直接返回“a"对象在常量池的地址,即让引用a指向常量”a"对象的内存地址。public native String intern();Integer也是同理。

下图是Integer类型在常量池中查找同值对象的方法。

public static Integer valueOf(int i) {
    if (i >= IntegerCache.low && i <= IntegerCache.high)
        return IntegerCache.cache[i + (-IntegerCache.low)];
    return new Integer(i);
}
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() {}
}
复制代码

所以基本数据类型的包装类型可以在常量池查找对应值的对象,找不到就会自动在常量池创建该值的对象。

而String类型可以通过intern来完成这个操作。

JDK1.7后,常量池被放入到堆空间中,这导致intern()函数的功能不同,具体怎么个不同法,且看看下面代码,这个例子是网上流传较广的一个例子,分析图也是直接粘贴过来的,这里我会用自己的理解去解释这个例子:

[java] view plain copy
String s = new String("1");  
s.intern();  
String s2 = "1";  
System.out.println(s == s2);  
  
String s3 = new String("1") + new String("1");  
s3.intern();  
String s4 = "11";  
System.out.println(s3 == s4);  
输出结果为:

[java] view plain copy
JDK1.6以及以下:false false  
JDK1.7以及以上:false true
复制代码

JDK1.6查找到常量池存在相同值的对象时会直接返回该对象的地址。

JDK 1.7后,intern方法还是会先去查询常量池中是否有已经存在,如果存在,则返回常量池中的引用,这一点与之前没有区别,区别在于,如果在常量池找不到对应的字符串,则不会再将字符串拷贝到常量池,而只是在常量池中生成一个对原字符串的引用。

那么其他字符串在常量池找值时就会返回另一个堆中对象的地址。

下一节详细介绍String以及相关包装类。

具体请见:https://blog.csdn.net/a724888/article/details/80042298

关于Java面向对象三大特性,请参考:

https://blog.csdn.net/a724888/article/details/80033043


目录
打赏
0
0
0
0
93
分享
相关文章
Java基础(三):八种基本数据类型
Java基础(三):八种基本数据类型
87 12
Java基础(三):八种基本数据类型
Java中的数据类型
Java语言提供了八种基本类型,分为4类8种:六个数值型(四个整数型byte、short、int、long,两个浮点型float、double)、一个字符型char和一个布尔型boolean。每种基本类型有固定的位数、取值范围及默认值。此外,还存在`void`类型,但无法直接操作。基本类型支持隐式和显式类型转换,并有对应的包装类如`Integer`、`Double`等,用于在需要对象的场景中使用。包装类支持自动装箱与拆箱机制,简化了基本类型与引用类型的转换,但需要注意性能和空指针异常等问题。
Java中的数据类型
【潜意识Java】Java基础教程:从零开始的学习之旅
本文介绍了 Java 编程语言的基础知识,涵盖从简介、程序结构到面向对象编程的核心概念。首先,Java 是一种高级、跨平台的面向对象语言,支持“一次编写,到处运行”。接着,文章详细讲解了 Java 程序的基本结构,包括包声明、导入语句、类声明和 main 方法。随后,深入探讨了基础语法,如数据类型、变量、控制结构、方法和数组。此外,还介绍了面向对象编程的关键概念,例如类与对象、继承和多态。最后,针对常见的编程错误提供了调试技巧,并总结了学习 Java 的重要性和方法。适合初学者逐步掌握 Java 编程。
54 1
|
3月前
|
Java基础之数据类型
Java基础之数据类型
33 6
|
3月前
|
在Java中如何将基本数据类型转换为String
在Java中,可使用多种方法将基本数据类型(如int、char等)转换为String:1. 使用String.valueOf()方法;2. 利用+运算符与空字符串连接;3. 对于数字类型,也可使用Integer.toString()等特定类型的方法。这些方法简单高效,适用于不同场景。
167 7
大厂面试必看!Java基本数据类型和包装类的那些坑
本文介绍了Java中的基本数据类型和包装类,包括整数类型、浮点数类型、字符类型和布尔类型。详细讲解了每种类型的特性和应用场景,并探讨了包装类的引入原因、装箱与拆箱机制以及缓存机制。最后总结了面试中常见的相关考点,帮助读者更好地理解和应对面试中的问题。
106 4
使用Java操作Redis数据类型的详解指南
通过使用Jedis库,可以在Java中方便地操作Redis的各种数据类型。本文详细介绍了字符串、哈希、列表、集合和有序集合的基本操作及其对应的Java实现。这些示例展示了如何使用Java与Redis进行交互,为开发高效的Redis客户端应用程序提供了基础。希望本文的指南能帮助您更好地理解和使用Redis,提升应用程序的性能和可靠性。
65 1
新手学JAVA(五)----深入剖析JAVA拆装箱
<div class="markdown_views"> <p><font size="4">先来看一段代码:</font></p> <pre class="prettyprint"><code class=" hljs cs"><span class="hljs-keyword">public</span> <span class="hljs-keyword">clas
1201 0
Java社招面试题:一个线程运行时发生异常会怎样?
大家好,我是小米。今天分享一个经典的 Java 面试题:线程运行时发生异常,程序会怎样处理?此问题考察 Java 线程和异常处理机制的理解。线程发生异常,默认会导致线程终止,但可以通过 try-catch 捕获并处理,避免影响其他线程。未捕获的异常可通过 Thread.UncaughtExceptionHandler 处理。线程池中的异常会被自动处理,不影响任务执行。希望这篇文章能帮助你深入理解 Java 线程异常处理机制,为面试做好准备。如果你觉得有帮助,欢迎收藏、转发!
60 14
Java 面试必问!线程构造方法和静态块的执行线程到底是谁?
大家好,我是小米。今天聊聊Java多线程面试题:线程类的构造方法和静态块是由哪个线程调用的?构造方法由创建线程实例的主线程调用,静态块在类加载时由主线程调用。理解这些细节有助于掌握Java多线程机制。下期再见! 简介: 本文通过一个常见的Java多线程面试题,详细讲解了线程类的构造方法和静态块是由哪个线程调用的。构造方法由创建线程实例的主线程调用,静态块在类加载时由主线程调用。理解这些细节对掌握Java多线程编程至关重要。
44 13

热门文章

最新文章

AI助理

你好,我是AI助理

可以解答问题、推荐解决方案等