zoukankan      html  css  js  c++  java
  • Java数据类型——基本类型/包装类型

    Java数据类型(基本类型/引用类型)

    基本类型

    Java语言提供了八种基本类型。六种数字类型(四个整数型,两个浮点型),一种字符类型,还有一种布尔型;Java是一种面向对象语言,为了让基本类型具备对象特性(比如方法调用),Java为每种基本类型提供了一个包装类。

    类型 默认值 占用储存空间 存储范围 包装类
    byte 0 1*byte -128(-2^7)~ 127(2^7-1) Byte
    short 0 2*byte -32768(-2^15)~ 32767(2^15 - 1) Short
    int 0 4*byte -2,147,483,648(-2^31)~ 2,147,483,647(2^31 - 1) Integer
    long 0L 8*byte -9,223,372,036,854,775,808(-2^63)~ 9,223,372,036,854,775,807(2^63 -1) Long
    float 0.0f 4*byte 1.4E-45 ~ 3.4028235E38 Float
    double 0.0d 8*byte 4.9E-324 ~ 1.7976931348623157E308 Double
    boolean false 1*bit false、true Boolean
    char false 2*byte u0000 ~ uffff Character

    因为Java为每种基本类型提供了包装类型,实际工作中,更多的是使用包装类型;那对于包装类型的比较应该是大家都会遇到的一些坑;

    • 装箱与拆箱
    package com.sjw.box;
    
    public class DateType {
        public static void main(String[] args) {
            int i = 100;
            // 自动装箱(autoboxing) 等价于 Integer a=Integer.valueOf(100);
            Integer a = 100;
            Integer b = 100;
            // 包装类型与基本类型通过==比较,比较值大小
            // 拆箱(unboxing) 等价于 a.intValue()==i
            System.out.println(a == i);
            // 包装类型通过==比较,比较地址
            System.out.println(a == b);
            // 包装类型通过equals比较,比较值大小
            System.out.println(a.equals(b));
        }
    }
    

    为了增强说服力,下面我们通过JDK自带的javap反解析工具,分析上述Java代码编译生成的Class文件。

    // javap -c -l DateType.class
    Compiled from "DateType.java"
    public class com.sjw.box.DateType {
      public com.sjw.box.DateType();
        Code:
           0: aload_0
           1: invokespecial #1                  // Method java/lang/Object."<init>":()V
           4: return
        LineNumberTable:
          line 3: 0
        LocalVariableTable:
          Start  Length  Slot  Name   Signature
              0       5     0  this   Lcom/sjw/box/DateType;
    
      public static void main(java.lang.String[]);
        Code:
           // 将int 100 push压入操作数栈
           0: bipush        100
           // pop出栈(int 100),赋值给局部变量表索引为1的变量 int i
           2: istore_1
           // 将int 100 push压入操作数栈
           3: bipush        100
           // pop出栈(int 100),调用Integer.valueOf,并将方法返回值(引用)push压入操作数栈;装箱(autoboxing)
           5: invokestatic  #2                  // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
           // pop出栈(引用地址),赋值给局部变量表索引为2的变量 Integer a
           8: astore_2
           // 将int 100 push压入操作数栈
           9: bipush        100
          // pop出栈(int 100),调用Integer.valueOf,并将方法返回值(引用)push压入操作数栈;装箱(autoboxing)
          11: invokestatic  #2                  // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
          // pop出栈(引用地址),赋值给局部变量表索引为3的变量 Integer b
          14: astore_3
          // 将静态属性PrintStream System.out push压入操作数栈
          15: getstatic     #3                  // Field java/lang/System.out:Ljava/io/PrintStream;
          // 将局部变量表索引为2的引用类型变量a push压入操作数栈
          18: aload_2
          // pop出栈a(引用)调用其intValue方法,并将返回值push压入操作数栈;拆箱(unboxing)
          19: invokevirtual #4                  // Method java/lang/Integer.intValue:()I
          // 将局部变量表索引为1的int类型变量i push压入操作数栈
          22: iload_1
          // pop操作数栈栈顶2个元素,进行int比较,不相等跳转到Code 30,相等继续
          23: if_icmpne     30
          // 将int常量1压入操作数栈
          26: iconst_1
          // 跳转Code 31
          27: goto          31
          // 将int常量0压入操作数栈
          30: iconst_0
          // pop操作数栈栈顶2个元素(out,1或0),以栈顶元素(1或0)作为参数调用out引用的方法println
          31: invokevirtual #5                  // Method java/io/PrintStream.println:(Z)V
          34: getstatic     #3                  // Field java/lang/System.out:Ljava/io/PrintStream;
          // 将局部变量表索引为2的引用类型变量a push压入操作数栈
          37: aload_2
          // 将局部变量表索引为3的引用类型变量b push压入操作数栈
          38: aload_3
          // pop操作数栈栈顶2个元素,进行引用对象比较(比较地址),不相等跳转到Code 46,相等继续
          39: if_acmpne     46
          42: iconst_1
          43: goto          47
          46: iconst_0
          47: invokevirtual #5                  // Method java/io/PrintStream.println:(Z)V
          50: getstatic     #3                  // Field java/lang/System.out:Ljava/io/PrintStream;
          53: aload_2
          54: aload_3
          55: invokevirtual #6                  // Method java/lang/Integer.equals:(Ljava/lang/Object;)Z
          58: invokevirtual #5                  // Method java/io/PrintStream.println:(Z)V
          // 将局部变量表索引为2的引用类型变量a push压入操作数栈
          61: aload_2
          // pop出栈a(引用)调用其intValue方法,并将返回值push压入操作数栈;拆箱(unboxing)
          62: invokevirtual #4                  // Method java/lang/Integer.intValue:()I
          // pop出栈(int 100),赋值给局部变量表索引为1的变量 int i
          65: istore_1
          66: return
        // Java代码行与Code汇编指令行偏移量映射表
        LineNumberTable:
          // Java代码第5行代码对用Code汇编指令0-2
          line 5: 0
          // Java代码第7行代码对用Code汇编指令3-8
          line 7: 3
          line 8: 9
          line 11: 15
          line 13: 34
          line 15: 50
          line 17: 61
          line 18: 66
        // 局部变量表
        LocalVariableTable:
          Start  Length  Slot  Name   Signature
              // 函数参数args
              0      62     0  args   [Ljava/lang/String;
              // int变量 i
              3      59     1     i   I
              // Integer变量 a  
              9      53     2     a   Ljava/lang/Integer;
              // Integer变量 b  
             15      47     3     b   Ljava/lang/Integer;
    }
    

    注本文重点不是分析javap反解析class字节码文件的具体信息,具体如何分析、汇编指令等参考官方文档

    经过上面简单分析,了解int包装类型Integer的装箱(Integer.valueOf)、拆箱(Integer.intValue)的过程;对于常见的面试题

        public void compare(Integer arg) {
            int a = 0;
            Integer b = 1;
            // 拆箱(unboxing),arg.intValue();如果arg为null,会出现NPE(NullPointerException)
            if (arg == a) ;
            // 不需要拆箱(unboxing),比较引用对象,即地址的比较
            if (arg == b) ;
            // 如果arg为null,会出现NPE(NullPointerException)
            if (arg.equals(b)) ;
            // 如果arg为null,不会出现NPE(NullPointerException),
            if (b.equals(arg)) ;
        }
    
        // Integer.java源码
        public boolean equals(Object obj) {
            // 如果obj为null,直接返回false
            if (obj instanceof Integer) {
                return value == ((Integer)obj).intValue();
            }
            return false;
        }
    
    • 包装对象的缓存
      正如前面介绍,包装对象的值大小比较需要使用equals,在一定范围内包装类型==比较是可以的,注意不是比较值,依然比较地址
        public void compare() {
            // 装箱(autoboxinxg)  Integer a = Integer.valueOf(100);
            Integer a = 100;
            Integer b = 100;
            Integer c = new Integer(100);
            Integer e = 1000;
            Integer f = 1000;
            // ture
            if (a == b) ;
            // false
            if (a == c) ;
            // ?
            if (e == f) ;
        }
    

    上述代码ef是ture还是false?不要急向下看,Java如何缓存对象的?如何分析,我们知道new出来的对象用比较,只要没用引用同一个对象就为false;对于通过装箱来的包装对象可能用的是缓存对象,我们知道装箱的过程是通过valueOf()方法的;

        // valueOf源码
        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;
                // 可通过jvm参数-XX:AutoBoxCacheMax进行配置
                String integerCacheHighPropValue =
                    sun.misc.VM.getSavedProperty("java.lang.Integer.IntegerCache.high");
                if (integerCacheHighPropValue != null) {
                    try {
                        int i = parseInt(integerCacheHighPropValue);
                        // 取127与配置值的最大值
                        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() {}
        }
    

    通过int的包装类型Integer分析了一下装箱(autoboxing)、拆箱(unboxing),以及缓存IntegerCache是如何实现的,并且IntegerCache缓存范围默认是-128~127,max value可以通过jvm参数-XX:AutoBoxCacheMax进行配置;假设jvm参数加上-XX:AutoBoxCacheMax=2000;会缓存value值为-127~2000的封装类型,以供通过装箱生成的对象实例;到这里可以回答e==f是true还是false。

    对于其他基本类型long、float、double对应的包装类型Long、Float、Double装箱、拆箱以及是否缓存,感兴趣的可以通过Java源码学习了解一下;本文代码样例采用JDK1.8。

    知其然,更要知其所以然。。。

  • 相关阅读:
    GMA Round 1 数列求单项
    GMA Round 1 双曲线与面积
    多线程环境中安全使用集合API(含代码)
    使用synchronized获取互斥锁的几点说明
    ThreadPoolExecutor线程池
    线程状态转换
    volatile关键字
    守护线程与线程阻塞的四种情况
    线程挂起,恢复与终止
    线程中断
  • 原文地址:https://www.cnblogs.com/sunjingwu/p/12283867.html
Copyright © 2011-2022 走看看