ホームページ  >  記事  >  Java  >  Java ネイティブ型とラッパー型についての深い理解

Java ネイティブ型とラッパー型についての深い理解

黄舟
黄舟オリジナル
2017-03-14 11:44:091665ブラウズ


要約:

この記事では、主に次の 4 つの側面を含む、Java ネイティブ型とラッパー型の詳細な分析を行います: ネイティブ型とラッパー型の基本、リテラル値の概念と型、および自動基本タイプ 鋳造と鋳造、自動箱詰めと箱から出す機構。


キーポイント:

  • プライマリ型とラッパー型

  • リテラル値の概念と型

  • 基本型、自動変換と強制変換

  • 自動ボックス化とアンボックス化メカニズム (オートボクシングとアンボックス化)


1. プリミティブ型とラッパー型

Java には 8 つの基本的なデータ型があります。

文字サフィックス識別子のない

整数Java ネイティブ型とラッパー型についての深い理解はデフォルトでint型

、文字サフィックス識別子のない浮動小数点数はデフォルトでdouble型

    のみ
  • byte,char,short,int の4つの基本型およびそのラッパー クラス (Java5.0/1.5 以降のサポートが必要) は switch ステートメント (switch に適用できるのは enum 型と int 型のみ)

  • 他の

    のコンパイル型はエラーを報告します。char (2 バイト) は単一引用符を使用して単一の文字 (漢字も可) を表すことができます。例: 'Liang'、'A'; 整数 2進数(0B/0bで始まる)、8進数(0B/0bで始まる整数)、10進数、16進数(0xまたは0Xで始まる

  • 整数
  • )で表すことができます。 2.リテラル値

    Javaのソースコードでは、固定値を表すためにリテラル値が使用されます。最も一般的なのは数値リテラル値です。
  • String
  • リテラル値も非常に一般的であり、特殊な

    null もリテラル値とみなすことができます。リテラル値は整数リテラル値(int型リテラル値、long型リテラル値、文字リテラル値)整数リテラル値、浮動小数点型リテラル値(double型)に大別できます。リテラル値) および float 型リテラル)

文字列リテラル

特殊リテラル(1) 整数リテラル 形式的には整数である値は、整数リテラルとして分類されます。例: 10、100000L、‘B’‘B’、0XFF、これらはすべて整数リテラル値と呼ぶことができます。使用する場合は、次の点に注意する必要があります。 整数リテラルは、10進16進(0X/0x

で始まる)、
8進

(で始まる)で使用できます。 0

で始まる) と binary (0B/0b で始まる )。もちろん、

    基数は基数系
  • の範囲を超えることはできません。たとえば、09 は不正であり、8 進数の基数は 7 までしか到達できません。 一般的にリテラル値はint型で作成されますが、int型リテラル値はbyte、short、char、long、intに代入することができます。 もちろん、 リテラル値がターゲット範囲内にあることが前提です (Java は 自動的に変換を完了します) 範囲外のリテラル値を特定の型 (そのような型) に代入しようとすると、バイト型に 128 を割り当てる場合)、 コンパイルが失敗し、強制的な 型変換 が必要です。

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);   
 // ?
  • int型で表現できないlong型を作成したい場合は、リテラル値の末尾にLまたはlを付ける必要があります(通常は区別しやすいLを使用することをお勧めします)。したがって、整数リテラルには、int リテラルとlongリテラルが含まれます。

  • Javaでは、「@」、「1」などの文字リテラルは一重引用符で囲まれます(整数リテラルにも属します)。すべての UTF-16 文字セット は文字リテラルに含まれます。直接入力できない文字については、改行文字「n」などのエスケープ文字を使用できます。 8 進数または 16 進数を使用して文字を表すこともできます。たとえば、「141」は文字 a を表します。 16 進数は、u に 4 を加えた 16 進数で表されます。たとえば、「u0061」は文字 a を表します。つまり、エスケープ文字を使用すると、存在するかどうかに関係なく、キーボード上のすべての文字を表すことができます。一般的なエスケープ文字シーケンスは次のとおりです:
    ddd (8 進数)、uxxxx (16 進数の Unicode 文字)、' (一重引用符)、" (二重引用符)、(バックスラッシュ) r (キャリッジ リターン) n (ライン フィード) 文字) f (フォーム フィード)文字) t (タブ文字) b (バックスペース文字)


(2) 浮動小数点リテラル値

浮動小数点リテラル値は、単純に10進数として理解でき、浮動小数点リテラル値浮動小数点リテラル値に分けられます。

double リテラル値
。F または f が小数点の後に追加される場合、それは 11.8F などの浮動小数点数のリテラル値であることを意味します (10.4 など)。 10 進数、D/d を追加すると、これが二重リテラル値であることを意味します

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 桁の文字は、文字 a を表します。16 進数では、u と 4 を使用して 16 進数を表します。「u0061」は、文字 a を表します。存在するかどうかに関係なく、キーボード上のすべての文字を表します。エスケープ文字シーケンスは次のとおりです:

ddd (8 進数)、uxxxx (16 進数の Unicode 文字)、' (一重引用符)、" (二重引用符)。)、(バックスラッシュ) r (キャリッジ リターン) n (ライン フィード) f (フォーム フィード) t (タブ) b (バックスペース)

文字列リテラルには二重引用符を使用します。文字リテラルにはエスケープ文字シーケンス

を含めることもできます。

(4) 特殊なリテラル値

null は特殊な型 (型) であり、任意の referencetypevariable に割り当てることができ、この変数が何も参照しないことを示します。

参照型変数がnullの場合、その変数は利用できないことを意味します。

String.class のように、型名に .class を加えたもので表される特別な class リテラルもあります。 まず、StringはクラスClass(java.lang.Class)のインスタンス(object)であり、「This is a string」はクラスStringのオブジェクトです。次に、クラス リテラルは、クラス Class のオブジェクトを表すために使用されます。たとえば、String.class は、クラス Class のオブジェクト String を表すために使用されます。簡単に言うと、クラスリテラルは String.class や Integer.class などのリテラル値であり、それらが表すのはクラス String とクラス Integer です。 Integer.class を出力すると、クラス java.lang.Integer が得られます。 List.class の出力はインターフェイス java.util.List です。要約すると、クラス リテラルは、型自体を表すために使用されます


特別な注意を払う必要があります:

  • は数値リテラルで使用できます とアンダースコアは数値リテラルで使用できます(整数リテラルと浮動小数点リテラル値を含む) 1つ以上のアンダースコアを挿入します。ただし、アンダースコアは数字を区切るためにのみ使用でき、文字と文字を区切ることはできません。

    int x = 123_456_789;  
    // 在编译的时候,下划线会自动去掉。float f = 1.22_3344;  
     //可以连续使用下划线int = _123;  
     // Errorlong = 1_L;  
     // Error
    3.そして強制変形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,short,char)

     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文件之后得到如下内容: 

            Java ネイティブ型とラッパー型についての深い理解
               
      从反编译得到的字节码内容可以看出,在装箱的时候自动调用的是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)类方法

    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&#39;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()实例方法

    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) 类方法

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

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

    • public int intValue()实例方法

     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 Boolean valueOf(boolean b) 类方法

    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()实例方法

     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
        }
    }

     总之,

    • Integer、Short、Byte、Character、Long 这几个类的valueOf方法的实现是类似的,有限可列举,共享[-128,127]

    • Double、Float的valueOf方法的实现是类似的,无限不可列举,不共享

    • Boolean的valueOf方法的实现不同于以上的整型和浮点型,只有两个值,有限可列举,共享


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

    • 第一种方式不会触发自动装箱的过程,而第二种方式会触发;

    • 在执行效率和资源占用上的区别。第二种方式的执行效率和资源占用在一般性情况下([-128,127])要优于第一种情况(注意这并不是绝对的)。


    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
        }
    }
    • 第一个和第二个输出结果没有什么疑问;

    • 第三个打印语句由于 a+b 包含了算术运算,因此会触发自动拆箱过程(会调用intValue方法),因此它们比较的是数值是否相等;

    • 对于c.equals(a+b)会先触发自动拆箱过程,再触发自动装箱过程,也就是说a+b,会先各自调用intValue方法,得到了加法运算后的数值之后,便调用Integer.valueOf方法,再进行equals比较;

    • 对于g==(a+b),会分别触发 Integer 和 Long 的自动拆箱过程,然后 int 自动转为 long,进行比较;

    • 对于g.equals(a+b),最终会归结于 Long对象与Integer对象的比较,由于二者不为同一类型,直接返回 false ;

    • 对于g.equals(a+h),最终会归结于 Long对象与Long对象的比较,由于 -128


    6、小结

    • 使用“==”运算符在基本类型和其包装类对象之间比较时,只要两个操作数中有一个是 基本类型 表达式(即包含算术运算符),就是比较它们的数值是否相等。否则,就是判断这两个对象的内存地址是否相等,即是否是同一个对象

    • 如果一个 方法中参数类型为原生数据类型 ,所传入的参数类型为其封装类对象,则会自动对其进行 拆箱 ;相应地,如果一个方法中 参数类型为封装类型对象 ,所传入的参数类型为其原始数据类型,则会自动对其进行 装箱 ,例如上述的equals方法。

        至于什么时候装箱,什么时候拆箱主要取决于:在当前场景下,你需要的是引用类型还是原生类型。若需要引用类型,但传进来的值是原生类型,则自动装箱(例如,使用equals方法时传进来原生类型的值);若需要的是原生类型,但传进来的值是引用类型,则自动拆箱(例如,使用运算符进行运算时,操作数是包装类型)。

以上がJava ネイティブ型とラッパー型についての深い理解の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

声明:
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。