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

    基本数据类型

    Java基本数据按类型可以分为四大类:布尔型,整数型,浮点型,字符型,这四大类包含8中基本数据类型。

    • 布尔型:boolean
    • 整数型:byte、short、int、long
    • 浮点型:float、double
    • 字符型:char

    8种基本类型取值如下:

    数据类型 代表含义 默认值 取值 包装类
    boolean 布尔型 false 0(false)/1(true) Boolean
    byte 字节型 (byte)0 -128到127 Byte
    char 字符型 'u0000'(空) 'u0000'到'uFFFF' Character
    short 短整数型 (short)0 -2^15到2^15 Short
    int  整数型 0 -2^31到2^31-1 Integer
    long 长整数型 0L -2^63到2^63-1 Long
    float 单浮点型 0.0f 1.4e-45到3.4e+38 Float
    double 双浮点型 0.0d 4.9e-324到1.798e+308 Double

     除char的包装类Character和int的包装类Integer之外,其他基本数据类型的包装类只需要首字母大写即可。包装类的作用和特点,本文下半部分详细讲解。

    我们可以在代码中,查看某种类型的取值范围,代码如下:

        public static void main(String[] args) {
            // Byte 取值:-128 ~ 127
            System.out.println(String.format("Byte 取值: %d ~ %d", Byte.MIN_VALUE,Byte.MAX_VALUE));
            // Integer 取值: -2147483648 ~ 2147483647
            System.out.println(String.format("Integer 取值: %s ~ %s", Integer.MIN_VALUE,Integer.MAX_VALUE));
        }

    包装类型

    我们知道8种基本数据类型都有其对应的包装类,因为Java的设计思想是万物既对象,有很多时候我们需要以对象的形式操作器某项功能,比如说获取哈希值(hashcode)或获取类(getClass)等。

    那包装类特性有哪些呢?

    1.功能丰富

    包装类本质上是一个对象,对象就包含属性和方法,比如hashCode、getClass、max、min等。

    2.可定义泛型类型参数

    包装类可以定义泛型,而基本类型不行。

    比如使用Integer定义泛型,代码:

    List<Integer> list = new ArrayList<>();

    如果使用int定义就会报错,代码:

    3.序列化

    因为包装类都实现了Serializable接口,所以包装类天然支持序列化和反序列化。比如Integer的类图如下:

    4.类型转换

    包装类提供了类型转换的方法,可以很方便的实现类型之间的转换,比如Integer类型转换代码:

            String age = "18";
            int ageInt = Integer.parseInt(age) + 2;
            //输出结果:20
            System.out.println(ageInt);

    5.高频区间的数据缓存

    此特性为包装类很重要的用途之一,用于高频区间的数据缓存,以Integer为例来说,在数值区间为-128~127时,会直接复用已有对象,在这区间之外的数字才会在堆上产生。

    我们使用 == 对 Integer 进行验证,代码如下:

        public static void main(String[] args) {
            // Integer 高频区缓存范围 -128 ~ 127
            Integer num1 = 127;
            Integer num2 = 127;
            // Integer 取值127 == 结果为 true (值127 num1==num2 =>true)
            System.out.println("值127 num1==num2 =>" + (num1 == num2));
    
            Integer num3 = 128;
            Integer num4 = 128;
            // Integer 取值128 == 结果为 false (值128 num3==num4 =>false)
            System.out.println("值128 num3==num4 =>" + (num3 == num4));
        }

    从上面的代码很明显可以看出,Integer为127时复用了已有的对象,当值为128时,重新在堆上生成了新对象。

    为什么会产生高频区域数据缓存呢?我们查看源码就能发现“线索”,源码版本 JDK8,源码如下:

        public static Integer valueOf(int i) {
            if (i >= IntegerCache.low && i <= IntegerCache.high)
                return IntegerCache.cache[i + (-IntegerCache.low)];
            return new Integer(i);
        }

    由此可见,高频区域的数值会直接使用已有对象,非高频区域的数值会重新new一个新的对象。

    各包装类高频区域的取值范围:

    • Boolean: 使用静态final定义,就会返回静态值
    • Byte:缓冲区 -128~127
    • Short:缓冲区 -128~127
    • Character:缓冲区 0~127
    • Long:缓冲区 -128~127
    • Integer:缓冲区 -128~127

    包装类的注意事项

    • int默认值是0,而Integer的默认值是null
    • 推荐所有包装类对象之间的值比较实用equals()方法,因为包装类的非高频去数据会在堆上产生,而高频区又会复用已有对象,这样会导致同样的代码,因为取值不同,而产生两种截然不同的结果。代码示例:
      public static void main(String[] args) {
              // Integer 高频区缓存范围 -128 ~ 127
              Integer num1 = 127;
              Integer num2 = 127;
              // Integer 取值127 == 结果为 true (值127 num1==num2 =>true)
              System.out.println("值127 num1==num2 =>" + (num1 == num2));
      
              Integer num3 = 128;
              Integer num4 = 128;
              // Integer 取值128 == 结果为 false (值128 num3==num4 =>false)
              System.out.println("值128 num3==num4 =>" + (num3 == num4));
              // Integer 取值128 equals 结果为 true (值128 num3.equals(num4) =>true)
              System.out.println("值128 num3.equals(num4) =>" + (num3.equals(num4)));
          }
    • Float 和 Double不会有缓存,其他包装类都有缓存。
    • Integer是唯一一个可以修改缓存范围的包装类,在VM options加入参数:
      -XX:AutoBoxCacheMax=666 即修改缓存最大值为666。

      示例代码如下:

          public static void main(String[] args) {
              Integer num1 = 128;
              Integer num2 = 128;
              System.out.println("值为-128 =>" + (num1 == num2));
              Integer num3 = 666;
              Integer num4 = 666;
              System.out.println("值666 num3==num4 =>" + (num3 == num4));
              Integer num5 = 667;
              Integer num6 = 667;
              System.out.println("值为667 =>" + (num5 == num6));
          }

      执行结果如下:

      值为-128 =>true
      值为666 =>true
      值为667 =>false

      由此可见将Integer最大缓存修改为666之后,667不会被缓存,而-128~666之间的数都被缓存了。

    小测验哦~

    1.以下Integer代码输出的结果是?

            Integer age = 10;
            Integer age2 = 10;
            Integer age3 = 133;
            Integer age4 = 133;
            System.out.println((age == age2) + "," + (age3 == age4));

    答: true, false 

    2.以下Double代码输出的结果是?

            Double num = 10d;
            Double num2 = 10d;
            Double num3 = 133d;
            Double num4 = 133d;
            System.out.println((num == num2) + "," + (num3 == num4));

    答: false, false 

    3.以下程序输出结果是?

            int i = 100;
            Integer j = new Integer(100);
            System.out.println(i == j);
            System.out.println(j.equals(i));

    A: true,true

    B: true,false

    C: false,true

    D: false,false

    答: A

    题目分析:有人认为这和Integer高速缓存有关系,但你发现把值改为10000结果也是 true,true ,这是因为  Integer  和  int  比较时,会自动拆箱为  int  ,相当于两个  int  比较,值一定是  true, true .

    4.以下程序执行的结果是?

            final int iMax = Integer.MAX_VALUE;
            System.out.println(iMax + 1);

    A:  2147483648

    B:  -2147483648

    C:  程序报错

    D:  以上都不是

    答:  B

    题目解析:  这是因为整数在内存中使用的是补码的形式表示,最高位是符号位 0 表示正数, 1 表示负数, 当执行 +1 时, 最高位就变成了 1, 结果就成了  -2147483648.

    5.以下程序执行的结果是?

            Set<Short> set = new HashSet<>();
            for(short i = 0; i < 5; i++){
                set.add(i);
                set.remove(i - 1);
            }
            System.out.println(set.size());

    A:  1

    B:  0

    C:  5

    D:  以上都不是

    答:  C

    题目解析:  Short 类型 -1 之后转换成了Int 类型, remove() 的时候在集合中找不到 Int 类型的数据, 所以就没有删除任何元素, 执行的结果就是 5 .

    6. short s = 2; s = s + 1; 会报错吗? short s = 2; s+=1; 会报错吗?

    答:  s=s+1 会报错, s+=1 不会报错, 因为 s = s + 1 会导致short 类型升级为 int 类型,所以会报错, s+=1 还是原来的short 类型, 所以不会报错.  

    其实对此我是有疑问的,继续探究.分别从源码和底层原理两个方面来了解一下这个问题.

    从源码角度:

    (1) s = s+1报错,这句先执行s+1然后把结果赋给s,由于1为int类型,所以s+1的返回值是int,编译器自动进行了隐式类型转换。所以将一个int类型赋给short就会出错

    (2) s += 1这句不报错

    通过反编译可以看到源码为

    当jvm识别+=且原值为整型时,会先忽略原值的具体数据类型,先用int计算后,如果计算结果是int就直接转为原来的数据类型,如果不是int就强转为int然后再转回原数据类型,例如

    反编译后为

    输出结果为2,损失了精度.

    从底层原理:

    而i+1 是将heap中数据直接送到寄存器中进行运算,运算结果会直接存放在heap中。

    i+=1 运行的底层Heap申请一个区域存放i,在数据区域开辟一个区域存放1,2个内存段的数据被送入到寄存器中进行运算,运算结果被放到heap中,数据区域运算后被自动释放后由GC回收 。

    7. float f = 3.4; 会报错吗? 为什么?

    答: 会报错,因为值3.4是double类型,float类型级别小于double类型,所以会报错.

    8.为什么需要包装类?

    答: 需要包装类的原因有两个.

    1. Java的设计思想是万物既对象,包装类体现了面向对象的设计理念;
    2. 包装类包含了很多属性和方法,比基本数据类型功能多,比如提供的获取哈希值(hashCode)或获取类(getClass)的方法等.

    9.基本类 int 和包装类 Integer ,在 -128 ~ 127 之间都会复用已有的缓存对象,这种说法正确吗?

    答: 不正确,只有包装类高频区域数据才有缓存.

    10.包装类 Double 和 Integer 一样都有高频区域数据缓存,这种说法正确吗?

    答: 不正确,基本数据类型的包装类只有Double和Float没有高频区域的缓存.

    11.包装类的值比较要是用什么方法?

    答: 包装类因为有高频区域数据缓存,所以推荐使用equals()方法进行值比较.

    12.包装类有哪些功能?

    答:包装类提供的功能有以下几个.

    • 功能丰富 : 包装类包含了有 hashCode, getClass, max, min等方法
    • 可以定义泛型类型参数 :  例如List<Integer> list = new ArrayList<>(); ;
    • 序列化 : 包装类实现了Serializable接口,所以包装类天然支持序列化和反序列化.
    • 类型转换 : 包装类提供了方便的类型转换方法,比如Integer的parseInt()方法.
    • 高频区域数据缓存 : 高频区域可使用已有的缓存对象;

    详见正文"包装类型"部分内容.

     13.泛型可以为基本数据类型吗? 为什么?

    答: 泛型不能使用基本数据类型.泛型在JVM(Java 虚拟机)编译的时候会类型擦除,比如代码 List<Integer> list 在JVM编译的时候会转换为List list, 因为泛型是在JDK5时提供的,而JVM的类型擦除是为了兼容以前代码的一个这种方案,类型擦除之后就变成了Object, 而Object不能存储基本数据类型,但可以使用基本数据类型对应的包装类,所以像 List<int> list 这样的代码是不被允许的,编译器阶段会检查报错,而List<Integer> list是被允许的.

    14.选择包装类还是基本类的原则有哪些?

    答: 我们知道正确的使用包装类,可以提供程序的执行效率,看可以使用已有的缓存,一般情况下选择基本数据类型还是选择包装类原则有以下几个.

    1. 所有POJO类属性必须使用包装类;
    2. RPC方法返回值和参数必须使用包装类;
    3. 所有局部变量推荐使用基本数据类型;

    15.基本数据类型在JVM中一定存储在栈中吗? 为什么?

    答: 基本数据类型不一定存储在栈中,因为基本类型的存储位置取决于声明的作用域,来看具体的解释.

    • 当基本数据类型为局部变量的时候,比如在方法中声明的变量,则存放在方法栈中,当方法结束,系统会释放方法栈,在该方法中的变量也会随着栈的销毁而结束,这也是局部变量只能在方法中使用的原因;
    • 当基本数据类型为全局变量的时候,比如类中的声明的变量,则存储在堆上,因为全局变量不会随着某个方法的执行结束而销毁.

    16.以下程序执行的结果是?

            Integer i1 = new Integer(10);
            Integer i2 = new Integer(10);
            Integer i3 = Integer.valueOf(10);
            Integer i4 = Integer.valueOf(10);
            System.out.println(i1 == i2);
            System.out.println(i2 == i3);
            System.out.println(i3 == i4);

    答案: false;false;false

    题目解析: new Integer(10) 每次都会创建一个新对象,Integer.valueOf(10)则会使用缓存池中的对象.

    17.  3*0.1==0.3  返回值是多少?

    答: 返回值为:  false.

    题目解析: 因为有些浮点数不能完全精确的表示出来,如下代码:

     System.out.println(3 * 0.1); 

    返回的结果是:   0.30000000000000004

  • 相关阅读:
    leetcode------Rotate Array
    leetcode------Validate Binary Search Tree
    leetcode------Unique Binary Search Trees II
    [错误]集合已修改;可能无法执行枚举操作
    [转载]如何申请淘宝app_key、app_secret、SessionKey?
    [转载]JS中如何定义全局变量
    [转载]C# Double toString保留小数点方法
    jquery easyui datagrid 获取选中多行
    MongoDB { code: 18, ok: 0.0, errmsg: "auth fails" } 原因
    C# WinForm开发系列
  • 原文地址:https://www.cnblogs.com/Night-Watch/p/11661188.html
Copyright © 2011-2022 走看看