• 技术文章 >Java >java教程

    深入理解Java原生类型与包装器类型

    黄舟黄舟2017-03-14 11:44:09原创781


    摘要

      本文对 Java 原生类型与包装器类型进行深度剖析,主要涉及以下四个方面:原生类型与包装器类型基础、字面值概念和种类、 基本类型的自动转型与强制转型和自动装箱与拆箱机制。


    要点:


    一. 原生类型与包装器类型

    在Java中有八种基本数据类型

              基本类型.png-111.6kB

    特别需要注意以下几点:


    二. 字面值

      在Java源代码中,字面值用于表示固定的值(fixed value)。数值型的字面值是最常见的,字符串字面值也很常见可以算是一种,当然也可以把特殊的null当做字面值。字面值大体上可以分为 整型字面值(int型字面值 、 long型字面值 和 字符字面值)整型字面值、浮点型字面值(double型字面值 和 float型字面值)字符串字面值特殊字面值 四种。


    (1) 整型字面值

      从形式上看是整数的值归类为整型字面值。例如: 10, 100000L, ‘B’‘B’、0XFF 这些都可以称为整型字面值。在使用时,需要注意以下几点:

    byte b0 = 128;      
     // Type mismatch: cannot convert from int to bytebyte b1 = 12;       
     // OKbyte b2 = (byte)300;
     // OK 需要强制转换,但表示的数字是截取后的数字(300二进制为100101100,截取后为00101100,即44)char c0 = 5;       
     // OKchar c1 = char(-3);       
     // 编译通过,但打印值为 ?(char 为无符号的)System.out.println(c1);   
     // ?

    (2) 浮点字面值

      浮点字面值简单的理解可以理解为小数,分为float字面值double字面值。如果在小数后面加上F或者f,则表示这是个float字面值,如11.8F。如果小数后面不加F/f(如10.4),或者小数后面加上D/d,则表示这是个double字面值。

    double d1 = 10;     
    // OK,自动类型转换double d2 = 11.4;    
    // OKdouble d3 = 1.23E3;    
    // OK  double d4 = 10D;    
    // OK  double d5 = 0.4D;     
    // OK float f1 = 10;      
    // OK,自动类型转换float f2 = 11.1F;      
    // OKfloat f3 = 11.1;      
    // Type mismatch: cannot convert from double to float

    (3) 字符及字符串字面值

      Java中字符字面值用单引号括起来,如‘@’,‘1’。所有的UTF-16字符集都包含在字符字面值中。不能直接输入的字符,可以使用转义字符,如‘\n’为换行字符。也可以使用八进制或者十六进制表示字符,八进制使用反斜杠加3位数字表示,例如’\141’表示字母a。十六进制使用\u加上4为十六进制的数表示,如’\u0061’表示字符a。也就是说,通过使用转义字符,可以表示键盘上的有的或者没有的所有字符。常见的转义字符序列有:
    \ddd(八进制) 、 \uxxxx(十六进制Unicode字符)、\’(单引号)、\”(双引号)、\ (反斜杠)\r(回车符) \n(换行符) \f(换页符) \t(制表符) \b(回格符)

      字符串字面值则使用双引号,字符串字面值中同样可以包含字符字面值中的转义字符序列


    (4) 特殊字面值

      null 是一种特殊的类型(type),可以将它赋给任何引用类型变量,表示这个变量不引用任何东西。如果一个引用类型变量为null,表示这个变量不可用。

      还有一种特殊的 class literal,用 type name 加上 .class 表示,例如 String.class。 首先,String是类Class(java.lang.Class)的一个实例(对象),而”This is a string”是类String的一个对象。然后,class literal用于表示类Class的一个对象,比如String.class用于表示类Class的对象String。简单地说,类字面值(class literal)就是诸如String.class 、Integer.class这样的字面值,它所表示的就是类String、类Integer。如果输出Integer.class,你会得到class java.lang.Integer。List.class的输出为interface java.util.List。总之,class literal 用于表示类型本身


    特别需要注意:

    int x = 123_456_789;  
    // 在编译的时候,下划线会自动去掉。float f = 1.22_3344;  
     //可以连续使用下划线int = _123;  
     // Errorlong = 1_L;  
     // Error

    三. 自动转型与强制转型

    1、 自动转型

    自动转型总原则:byte,short,char(同级)-> int -> long -> float -> double (由低精度到高精度)


    (1) 由低精度到高精度的自动转换

    具体可分为以下两种情形:

        byte b = 1;    char c = 1;    short s = 1;    int i = 1;
    
        c = b;  // Error,同级
        c = s;  // Error,同级
        s = c;  // Error,同级
        i = c;  // OK
        int i = 1;    long t = 1;    float f = 1;    double d = 1;
    
        f = i;  //  Ok
        f = t;  //  Ok
        d = f;  // Ok

    (2) 运算符对基本类型的影响

    具体可分为以下两种情形:

    1) 当使用 +、-、*、/、%、==、>、< 等 等运算符对基本类型进行运算时,遵循如下规则:

      两个操作数中,先考虑是否有一个是double类型的。如果有,另一个操作数和结果 将会被转换成double类型。再依次考虑float,long。除此之外,两个操作数(包括byte、short、int、char)都将会被转换成int类型。

    byte b1 = 10 ;  
    //OK,会检查发现10没有超过byte类型的最大值byte b2 = 12;   
    //OK,会检查发现12没有超过byte类型的最大值byte b = b1 + b2; 
    //Error,byte类型在计算时会自动提升为int类型,此时就会报错,因为b1+b2实际上是int类型,但是左侧的变量为byte类型。short s1=1; 
    //OK,会检查发现1没有超过short类型的最大值s1=s1+1;    
    //Error,因为s1+1 结果int,但左侧变量为 short,报错s1++;      
    //OK,不会报错,与s1=s1+1不同!!!,会检查发现2没有超过short类型的最大值s1=1+1;   
    //OK,1+1 是个编译时可以确定的常量,'+'运算在编译时就被执行了,而不是在程序执行的时候,这个语句的效果等同于s1=2

    2) 当使用 +=、-=、*=、/=、%= i++、 ++i 运算符对基本类型进行运算时,遵循如下规则:

      运算符右边的数值将首先被强制转换成与运算符左边数值相同的类型,然后再执行运算,且运算结果与运算符左边数值类型相同。自增(减)运算也类似。

    short s1=1; // OK,会检查发现1没有超过short类型的最大值short s2;
    
    s1+=1;    // OK,正确,1首先被强制转换为short型,然后再参与运算,并且结果也是short类型s2 = ++s1;     
    // OK,正确,s2的值为2

    2、强制转型
      强制转换的格式是在需要转型的数据前加上 “( )”, 然后在括号内加入需要转化的数据类型。主要发生于以下两种情形:

     byte b = 3; int i = 3; long t = 3; float f = 3; char c = 3; short s = 3;
    
     i = (int) f;  // OK,由高精度向低精度转换
     t = (long) f;  // OK,由高精度向低精度转换
     b = (byte) i;  // OK,由高精度向低精度转换
    
     i = b; // OK,由低精度向高精度转换,自动转型
     System.out.println(c==s);  // OK,true,c 和 s 自动转型为int,然后比较
    
     b = (byte) s;  // OK,一种类型到另一种类型转换
     c = (char) b;  // OK,一种类型到另一种类型转换
     c = (char) s;   // OK,一种类型到另一种类型转换

      特别需要注意的是,强制转换常常会导致二进制位的截取,甚至会导致意想不到的结果:

     int i = 128; byte b = (byte)i;
     System.out.println(b);           // -128(即-0)

    四. 自动装箱与拆箱(Autoboxing and Unboxing)

    1、什么是装箱?什么是拆箱?

      Java为每种基本数据类型都提供了对应的包装器类型。在 Java SE5 之前,如果要 创建一个数值为10的Integer对象,必须这样进行:

    Integer i = new Integer(10);

      而从 Java SE5 之后就提供了自动装箱的特性,如果要 创建一个数值为10的Integer对象,只需要这样就可以了:

    Integer i = 10;

      这个过程中会自动根据数值创建对应的 Integer对象,这就是装箱

      那什么是拆箱呢?顾名思义,跟装箱对应,就是自动将包装器类型转换为基本数据类型:

    Integer i = 10;  //装箱int n = i;   //拆箱

      简单一点说,装箱就是自动将基本数据类型转换为包装器类型拆箱就是自动将包装器类型转换为基本数据类型


    2、装箱和拆箱是如何实现的

     上一小节了解装箱的基本概念之后,这一小节来了解一下装箱和拆箱是如何实现的。我们就以Interger类为例,下面看一段代码:

    public class Main {
        public static void main(String[] args) {
    
            Integer i = 10;        int n = i;
        }
    }

    反编译class文件之后得到如下内容: 

            装箱拆箱反编译.jpg-59.5kB
               
      从反编译得到的字节码内容可以看出,在装箱的时候自动调用的是Integer的valueOf(int)方法。而在拆箱的时候自动调用的是Integer的intValue方法

      对于其他的包装器类,比如Double、Character,也同样适用。

      因此,可以用一句话总结装箱和拆箱的实现过程:

      装箱过程是通过调用包装器的valueOf方法实现的,而拆箱过程是通过调用包装器的 xxxValue方法实现的(xxx代表对应的基本数据类型)。


    3、valueOf、xxxValue 方法在JDK中的实现

    (1) 在 Byte,Character,Short,Integer,Long 中的实现(以 Integer 为例)

    public static Integer valueOf(int i) {        
    if(i >= -128 && i <= IntegerCache.high)            
    return IntegerCache.cache[i + 128];        
    else
                return new Integer(i);
                
        }    private static class IntegerCache {
            static final int high;        
            static final Integer cache[];        
            static {                                         
            // 静态代码块  
                final int low = -128;            
                // high value may be configured by property
                int h = 127;            
                if (integerCacheHighPropValue != null) {                
                // Use Long.decode here to avoid invoking methods that
                    // require Integer's autoboxing cache to be initialized
                    int i = Long.decode(integerCacheHighPropValue).intValue();
                    i = Math.max(i, 127);                
                    // Maximum array size is Integer.MAX_VALUE
                    h = Math.min(i, Integer.MAX_VALUE - -low);
                }
                high = h;
    
                cache = new Integer[(high - low) + 1];            
                int j = low;            
                for(int k = 0; k < cache.length; k++)                      
                // 初始化  
                    cache[k] = new Integer(j++);
            }        private IntegerCache() {}
        }

      在装箱时,valueOf方法会被自动调用:如果整型字面值在[-128,127]之间,便返回 IntegerCache.cache(在类加载时就自动创建) 中已经存在的对象的引用;否则,创建一个新的Integer对象并返回。

    public int intValue() {    return value;
    }

     从这段代码可以看出,在拆箱时,Integer对象会自动调用其 intValue 方法,返回该对象对应的 int 值。
     
    下面代码可以很好说明这一点:

    public class Main {
        public static void main(String[] args) {
    
            Integer i1 = 100;
            Integer i2 = 100;
            Integer i3 = 200;
            Integer i4 = 200;
    
            System.out.println(i1==i2);          // true
            System.out.println(i3==i4);          // false
        }
    }

    (2) 在 Float, Double 中的实现(以 Double 为例)

       public static Double valueOf(double d) {        return new Double(d);
        }

      在装箱时,valueOf方法会被自动调用,从而创建相应的Double对象并返回。

     public double doubleValue() {    return value;
    }

      从这段代码可以看出,在拆箱时,Double对象会自动调用其 doubleValue 方法,返回该对象对应的 double 值。
      
      下面代码可以很好说明这一点:

    public class Main {
        public static void main(String[] args) {
    
            Double i1 = 100.0;
            Double i2 = 100.0;
            Double i3 = 200.0;
            Double i4 = 200.0;
    
            System.out.println(i1==i2);          // false
            System.out.println(i3==i4);          // false
        }
    }

      为什么Double类的valueOf方法会采用与Integer类的valueOf方法不同的实现呢?原因很简单,在某个范围内的整型数值的个数是有限的,而浮点数却不是


    (3) 在 Boolean 中的实现

    public static final Boolean TRUE = new Boolean(true);
    public static final Boolean FALSE = new Boolean(false);
    public static Boolean valueOf(boolean b) {        
    return (b ? TRUE : FALSE);
    }

      在装箱时,valueOf方法会被自动调用,从而创建相应的Boolean对象并返回。

     public boolean booleanValue() {    return value;
        }
    }

      从这段代码可以看出,在拆箱时,Boolean对象会自动调用其 booleanValue 方法,返回该对象对应的 boolean 值。
      
      下面代码可以很好说明这一点:

    public class Main {
        public static void main(String[] args) {
    
            Boolean i1 = false;
            Boolean i2 = false;
            Boolean i3 = true;
            Boolean i4 = true;
    
            System.out.println(i1==i2);          // true
            System.out.println(i3==i4);          // true
        }
    }

     总之,


    4、Integer i = new Integer(xxx)Integer i =xxx;的区别


    5、“==”运算符

      当使用“==”运算符在基本类型和其包装类对象之间比较时,涉及到自动装箱、拆箱机制,遵循如下规则:

       1). 只要两个操作数中有一个是基本类型或表达式(即包含算术运算符),就是比较它们的数值是否相等。
       2). 否则,就是判断这两个对象的内存地址是否相等,即是否是同一个对象。


    // 代码片段1public class Main {    public static void main(String[] args) {
    
            Integer i01=59;        int i02=59;
            Integer i03=Integer.valueOf(59);
            Integer i04=new Integer(59);
    
            System.out.println(i01==i02);  // true,拆箱
            System.out.println(i01==i03);  // true,同一对象
            System.out.println(i03==i04);  // false,不同对象
            System.out.println(i02==i04);  // true,拆箱
        }
    }

    // 代码片段2public class Main {    public static void main(String[] args) {
    
            Integer a = 1;
            Integer b = 2;
            Integer c = 3;
            Integer d = 3;
            Integer e = 321;
            Integer f = 321;
            Long g = 3L;
            Long h = 2L;
    
            System.out.println(c==d);  // true
            System.out.println(e==f);  // false
            System.out.println(c==(a+b));  // true
            System.out.println(c.equals(a+b));  // true
            System.out.println(g==(a+b));  // true
            System.out.println(g.equals(a+b));  // false
            System.out.println(g.equals(a+h));  // true
        }
    }

    6、小结

    以上就是深入理解Java原生类型与包装器类型的详细内容,更多请关注php中文网其它相关文章!

    声明:本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn核实处理。
    上一篇:Java String综述(下篇) 下一篇:Java继承、多态与类复用的详细介绍和代码实例
    PHP编程就业班

    相关文章推荐

    • JAVA学习IO操作之字节流和字符流(总结分享)• 完全掌握JAVA流程控制• Java学习总结之数组(整理分享)• Java工厂方法模式详解• 详细整理java枚举的使用总结

    全部评论我要评论

  • 取消发布评论发送
  • 1/1

    PHP中文网