目次 | |

5. 変換及び昇格

すべてのJava式は,その式の構造,並びに,その式の中に記述された,リテラル,変数及びメソッドの型から演繹できる型をもつ。しかし,式の型が適切でない式を書いてしまうこともできるので,コンパイル時にエラーとなる場合もある。 例えば, if(14.8)内の式が boolean 以外の型をもてば,コンパイル時エラーが発生する。文脈によっては,式の型が受理可能な場合もある。Java言語では,プログラマに明示的型変換を要求するかわりに,指定された式の型から,式をとりまく文脈にとって受理可能な型への暗黙的な 変換(conversion) を実行することを図る。

S から型 T への特定の変換によって,型 S の式を,あたかも型 T をもつかのようにコンパイル時に扱うことができる。この変換の正当性を検査する実行時動作,又は式の実行時の値を新しい型 T にとって適切な形式に翻訳する実行時動作が,必要となる場合もある。その例を次に示す。

すべての変換の文脈で,決められた特定の変換だけが許される。 Javaで可能な変換を,記述の便宜を図るために,次の大分類に類別する。

Java式の変換が発生可能な 変換の文脈(conversion context) には5種類ある。個々の文脈で,可能な変換を定めた。"変換" という用語は,文脈に対して特定の変換を選択する過程を記述するためにも使用する。例えば,メソッド呼出しで実引数の式が,"メソッド呼出し変換" に従う,というが,これはメソッド呼出し実引数の文脈に対する規則によって,その式に合った特定の変換を暗黙的に選択することを意味する。

変換文脈の中には,+* などの数値演算子のオペランドとなる数値がある。この数値オペランドの変換過程を 数値昇格(numeric promotion) と呼ぶ。二項演算子の場合は,一方のオペランドに対する変換の選択が,他方のオペランド式の型に部分的に依存する可能性がある。

5.では,最初に,文字列の連結演算子 + に対して許される String への特殊な変換を含む,変換の六つの大分類 (5.1)を規定する。次に,五つの変換文脈を規定する。

変換に対する種々の文脈例を次に示す。


class Test {            

    public static void main(String[] args) {

        // Casting conversion (5.4) of a float literal to
        // type int. Without the cast operator, this would
        // be a compile-time error, because this is a
        // narrowing conversion (5.1.3):
        int i = (int)12.5f;

// String conversion (5.4) of i's int value: System.out.println("(int)12.5f==" + i);
// Assignment conversion (5.2) of i's value to type // float. This is a widening conversion (5.1.2): float f = i;
// String conversion of f's float value: System.out.println("after float widening: " + f);
// Numeric promotion (5.6) of i's value to type // float. This is a binary numeric promotion. // After promotion, the operation is float*float: System.out.print(f); f = f * i;
// Two string conversions of i and f: System.out.println("*" + i + "==" + f);
// Method invocation conversion (5.3) of f's value // to type double, needed because the method Math.sin // accepts only a double argument: double d = Math.sin(f);
// Two string conversions of f and d: System.out.println("Math.sin(" + f + ")==" + d);
}
これは,次の出力を生成する。


(int)12.5f==12
after float widening: 12.0
12.0*12==144.0
Math.sin(144.0)==-0.49102159389846934 

5.1 変換の種別

Javaにおける型変換を,次の六つに大分類する。

5.1.1 恒等変換

ある型からそれと同じ型への変換は,いかなる型に対しても許される。これは,二つの実用的な効果を持つ。第一に,すべての式は変換を受ける,と規則を簡潔に言明できる。第二に,明確化のために,プログラムが冗長なキャスト演算子を含むことを許可する。

boolean を含む唯一許される変換は,boolean から boolean への恒等変換とする。

5.1.2 プリミティブ型の拡大変換

プリミティブ型における次の19個の変換をプリミティブ型の拡大変換(widening primitive conversion) と呼ぶ。

プリミティブ型の拡大変換は,数値の全体としての大きさについての情報を失わない。実際,整数の型から他の整数の型への拡大変換及び型 float から型 double への拡大変換は,いかなる情報も失うことはない。つまり,数値を正確に保存する。int 若しくは long の値から float への変換,又は long の値から double への変換は,精度の損失(loss of precision),すなわち値の最小位の数ビットを失なうことがある。この場合,浮動小数点の結果値は,IEEE 754 直近へのまるめモード(4.2.4)を利用して,正しく丸めた整数値とする。

符号付き整数値の整数型 T への拡大変換は,単により長い桁を埋めるために整数値の2の補数表現について符号拡張をするだけとする。文字から整数型 T への拡大変換は,より長い桁を埋めるために文字値の表現をゼロ拡張する。

精度の損失が発生する可能性があるという事実にもかかわらず,プリミティブ型間の拡大変換は,実行時例外(11)を生じない。

精度を失う拡大変換の例を次に示す。


class Test {
    public static void main(String[] args) {
        int big = 1234567890;
        float approx = big;
        System.out.println(big - (int)approx);
    }
これは,次を印字する。

-46 
この例は,型 int から型 float に変換する過程で,型 float の値が9桁の有効数字の精度がないために,情報が失われたことを示している。

5.1.3 プリミティブ型の縮小変換

プリミティブに関する次の23個の変換をプリミティブ型の縮小変換(narrowing primitive conversion) と呼ぶ。

縮小変換は,数値全体の大きさの情報を失うことがあり,さらに精度を失うこともある。

符号付き整数の整数型 T への縮小変換は,単に n 個,最小位ビット以外のすべての情報を捨てる。ここで,n は,型 T を表現するのに使用するビット数とする。数値の大きさに関する情報の損失の可能性に加えて,結果値の符号が,入力値の符号と異なってしまうことがある。

同様にして,文字型から整数型 T への縮小変換は,単に n 個,最小位ビット以外のすべての情報を捨てる。ここで n は,型 T を表現するのに使用するビット数とする。数値の大きさに関する情報の損失の可能性に加えて,文字が16ビットの符号なし整数値を表わすにもかかわらず,結果値が,負の数になることがある。

浮動小数点数から整数型 T への縮小変換は,二つの段階を踏む。

  1. 第一段階では,浮動小数点数を,Tlong ならば long に変換し,Tbyteshortchar 又は int ならば int に変換する。
  2. 第二段階は,次のとおりとする。
次に例を示す。


class Test {
    public static void main(String[] args) {
        float fmin = Float.NEGATIVE_INFINITY;
        float fmax = Float.POSITIVE_INFINITY;
        System.out.println("long: " + (long)fmin +
                            ".." + (long)fmax);
        System.out.println("int: " + (int)fmin +
                            ".." + (int)fmax);
        System.out.println("short: " + (short)fmin +
                            ".." + (short)fmax);
        System.out.println("char: " + (int)(char)fmin +
                            ".." + (int)(char)fmax);
        System.out.println("byte: " + (byte)fmin +
                            ".." + (byte)fmax);
    }
これは,次を出力する。


long: -9223372036854775808..9223372036854775807
int: -2147483648..2147483647
short: 0..-1
char: 0..65535
byte: 0..-1 
charint 及び long に対する結果は,その型の最小及び最大表現可能値を生成する。

byte 及び short に対する結果は,符号及び数値の大きさに関する情報を失い,精度も失う。この結果は,int の最小数及び最大数の下位ビットを調べることにより理解できる。 最小の int は16進で 0x80000000 であって,最大の int0x7fffffff である。これから,short の結果が値の下位16ビット,つまり 0x0000 及び 0xffff となることが説明される。また char の結果もまた,これらの値の下位16ビット,つまり '\u0000' 及び '\uffff' となることで説明される。更に byte の結果が,これらの値の下位8ビット,つまり 0x00 及び 0xff となることで説明される。

double から float への縮小変換は, IEEE 754に準拠して実行される。この結果は, IEEE 754の直近への丸めモードを使用して正確に丸められる。float として表現するには小さすぎる値は,正又は負のゼロに変換する。float として表現するには大きすぎる値は,(正又は負の)無限大に変換する。double NaN は,常に floatNaN に変換する。

オーバフロー,アンダフロー又はその他の情報の損失が生ずる可能性があるという事実にもかかわらず,プリミティブ型間の縮小変換は,実行時例外(11.)を生じない。

情報を損失する多くの縮小変換を例示する,小さなテストプログラムを次に示す。


class Test {

    public static void main(String[] args) {

        // A narrowing of int to short loses high bits:
        System.out.println("(short)0x12345678==0x" +
                    Integer.toHexString((short)0x12345678));

// A int value not fitting in byte changes sign and magnitude: System.out.println("(byte)255==" + (byte)255);
// A float value too big to fit gives largest int value: System.out.println("(int)1e20f==" + (int)1e20f);
// A NaN converted to int yields zero: System.out.println("(int)NaN==" + (int)Float.NaN);
// A double value too large for float yields infinity: System.out.println("(float)-1e100==" + (float)-1e100);
// A double value too small for float underflows to zero: System.out.println("(float)1e-50==" + (float)1e-50);
}
このテストプログラムは次の出力を生成する。


(short)0x12345678==0x5678
(byte)255==-1
(int)1e20f==2147483647
(int)NaN==0
(float)-1e100==-Infinity
(float)1e-50==0.0 

5.1.4 参照型の拡大変換

次の変換を 参照型の拡大変換(widening reference conversion) と呼ぶ。

これらの変換は,実行時に特別な動作を要求せず,従って実行時に例外を投げない。これは,参照を単に他の型をもつと見なし,コンパイル時には正しいとすることによる。

クラスの詳細な規定については 8を,インタフェースについては9を,配列については10を参照のこと。

5.1.5 参照型の縮小変換

次の変換を 参照型の縮小変換(narrowing reference conversion) と呼ぶ。

これらの変換は,実参照値が新しい型に適切な値かどうか調べるために,実行時に検査を要求する。適切な値でなければ,ClassCastException が投げられる。

5.1.6 文字列変換

空型を含めてすべての型から型 String への文字列変換が存在する。

5.1.7 禁止される変換

5.2 代入変換

代入変換(assignment conversion) は,式の値を変数に代入する(15.25)ときに発生する。式の型は変数の型に変換しなければならない。代入文脈では,恒等変換(5.1.1),プリミティブ型の拡大変換(5.1.2)又は参照型の拡大変換(5.1.4)の使用が許される。更に,次の条件をすべて満足すれば,プリミティブ型の縮小変換を使用してもよい。

式の型が,代入文脈で許される変換によって変数の型へ変換できなければ,コンパイル時エラーが発生する。

式の型が代入変換によって変数の型に変換可能ならば,その式(又はその式の値)は,その変数に対して 代入可能(assignable to) 又は等価だが,式の型は変数の型と 代入互換(assignment compatible with) とする。

代入変換は,例外を発生しない。(しかし,配列の要素を含む特別な場合には,代入が例外を生じることがあるので注意すること。10.10及び15.25.1を参照のこと。)

コンパイル時の定数の縮小とは,次のようなコードを許すことを意味する。

byte theAnswer = 42; 
縮小がなければ,整数リテラル 42 は型 int をもつという事実は,byte へのキャストを要求することになる。

byte theAnswer = (byte)42;    
プリミティブ型の値は,参照型の変数に代入してはならない。これを実行しようとすると,コンパイル時エラーを生じる。型 boolean の値は,型 boolean の変数にのみ代入できる。

次のテストプログラムは,プリミティブ型の値の代入変換に関する例を含む。


class Test {
    public static void main(String[] args) {
        short s = 12;                 // narrow 12 to short
        float f = s;                  // widen short to float
        System.out.println("f=" + f);
        char c = '\u0123';
        long l = c;                   // widen char to long
        System.out.println("l=0x" + Long.toString(l,16));
        f = 1.23f;
        double d = f;                 // widen float to double
        System.out.println("d=" + d);
    }
このプログラムは,次の出力を生成する。

f=12.0
i=0x123
d=1.2300000190734863 
しかし,次のテストプログラムは,コンパイル時エラーとなる。


class Test {
    public static void main(String[] args) {
        short s = 123;
        char c = s;                   // error: would require cast
        s = c;                        // error: would require cast
    }
その理由は,すべての short 値が char 値ではないし,すべての char 値が short 値でもないからとする。

参照型の値は,プリミティブ型の変数に代入してはならない。これを実行しようとするとコンパイル時エラーを生じる。

空型の値(空参照が唯一,その値)は,あらゆる参照型に代入可能で,その結果は,その型の空参照とする。

参照の代入を例示する例題を次に示す。


public class Point { int x, y; }

public class Point3D extends Point { int z; }
public interface Colorable { void setColor(int color); }
public class ColoredPoint extends Point implements Colorable { int color; public void setColor(int color) { this.color = color; } }
class Test { public static void main(String[] args) { // Assignments to variables of class type: Point p = new Point(); p = new Point3D(); // ok: because Point3d is a // subclass of Point Point3D p3d = p; // error: will require a cast because a // Point might not be a Point3D // (even though it is, dynamically, // in this example.) // Assignments to variables of type Object: Object o = p; // ok: any object to Object int[] a = new int[3]; Object o2 = a; // ok: an array to Object
// Assignments to variables of interface type: ColoredPoint cp = new ColoredPoint(); Colorable c = cp; // ok: ColoredPoint implements // Colorable
// Assignments to variables of array type: byte[] b = new byte[4]; a = b; // error: these are not arrays // of the same primitive type Point3D[] p3da = new Point3D[3]; Point[] pa = p3da; // ok: since we can assign a // Point3D to a Point p3da = pa; // error: (cast needed) since a Point // can't be assigned to a Point3D
}
コンパイル時参照型 S(代入元)の値のコンパイル時参照型 T(代入先)への代入は,次のように検査する。 クラスの詳細な規定については8を,インタフェースについては9を,配列については10を参照のこと。

次のテストプログラムは,参照値についての代入変換を例示するが,注釈で記述するとおり,これは,優先順位規則に違反しているためにコンパイルに失敗する。この例を一つ前の例と比較することが望ましい。


public class Point { int x, y; }

public interface Colorable { void setColor(int color); }
public class ColoredPoint extends Point implements Colorable { int color; public void setColor(int color) { this.color = color; } }
class Test { public static void main(String[] args) {
Point p = new Point();
ColoredPoint cp = new ColoredPoint();
// Okay because ColoredPoint is a subclass of Point: p = cp;
// Okay because ColoredPoint implements Colorable: Colorable c = cp;
// The following cause compile-time errors because // we cannot be sure they will succeed, depending on // the run-time type of p; a run-time check will be // necessary for the needed narrowing conversion and // must be indicated by including a cast: cp = p; // p might be neither a ColoredPoint // nor a subclass of ColoredPoint c = p; // p might not implement Colorable
}
配列オブジェクトの代入を含む例をもう一つ次に示す。


class Point { int x, y; }

class ColoredPoint extends Point { int color; }
class Test { public static void main(String[] args) { long[] veclong = new long[100]; Object o = veclong; // okay Long l = veclong; // compile-time error short[] vecshort = veclong; // compile-time error Point[] pvec = new Point[100]; ColoredPoint[] cpvec = new ColoredPoint[100]; pvec = cpvec; // okay pvec[0] = new Point(); // okay at compile time, // but would throw an // exception at run time cpvec = pvec; // compile-time error }
この例では,

        cpvec = (ColoredPoint[])pvec;    // okay, but may throw an
                                         // exception at run time 

5.3 メソッド呼出し変換

メソッド呼出し変換(method invocation conversion) は,メソッド又はコンストラクタの呼出し(15.815.11)の各実引数値に適用する。実引数式の型は,対応する仮引数の型に変換しなければならない。メソッド呼出し文脈は,恒等変換 (5.1.1),プリミティブ型拡大変換(5.1.2),又は参照型拡大変換(5.1.4)を可能とする。

メソッド呼出し変換は,オーバロードされたメソッドの照合解決過程(15.11.2)が複雑になるため,代入変換(5.2)の一部となる暗黙の整数定数の縮小変換を含まない。次の例は,コンパイル時にエラーを引き起こす。


class Test {

static int m(byte a, int b) { return a+b; }

static int m(short a, short b) { return a-b; }
public static void main(String[] args) { System.out.println(m(12, 2)); // compile-time error }
整数リテラル 12 及び 2 は,型 int をもつのでいずれのメソッド m(15.11.2)の規則のもとでは一致しないからである。整数定数の暗黙縮小を含む言語では,この例のような場合を解決する追加規則を必要とする。

5.4 文字列変換

文字列変換は,実引数の一方が String のとき,二項演算子 + のオペランドにだけ適用する。この場合,+ の他方の実引数を String に変換し,二つの文字列の連結である新しい String+ の結果となる。文字列変換は,文字列の連結演算子 + (15.17.1)の記述で詳細に規定する。

5.5 キャスト変換

キャスト変換(casting conversion) は,キャスト演算子(15.15)のオペランドに適用する。このときオペランド式の型は,キャスト演算子で明示的に名前を与えた型に変換しなければならない。キャストの文脈では,恒等変換(5.1.1),プリミティブ型の拡大変換 (5.1.2),プリミティブ型の縮小変換(5.1.3),参照型の拡大変換(5.1.4)又は参照型の縮小変換(5.1.5)の利用を可能とする。従って,キャスト変換は,代入変換又はメソッド呼出し変換よりも包括的となる。すなわち,キャストは文字列変換以外の任意の変換が許される。

コンパイル時に不正と証明されるキャストもある。このようなキャストは,コンパイル時エラーを生じる。

プリミティブ型の値は,型が同じならば恒等変換によって,そうでないときにはプリミティブ型の拡大変換又はプリミティブ型の縮小変換によって,他のプリミティブ型にキャストできる。

プリミティブ型の値を,キャスト変換によって参照型にキャストすることはできないし,参照型の値をプリミティブ型にキャストすることもできない。

残りの場合は,参照型間の変換に関係する。コンパイル時参照型 S(変換元)の値から,コンパイル時参照型 T(変換先)へのキャスト変換の,コンパイル時正当性検査に関する詳細規則は,次のとおりとする。

クラスの詳しい規定は 8,インタフェースは9,及び配列は 10 を参照のこと。

参照型へのキャストがコンパイル時エラーでなければ,次の二つの場合が存在する。

実行時例外が投げられる場合,それは, ClassCastException (11.5.1.120.22)とする。

5.2の例と同様の参照型へのキャスト変換の例を次に示す。


public class Point { int x, y; }

public interface Colorable { void setColor(int color); }
public class ColoredPoint extends Point implements Colorable { int color; public void setColor(int color) { this.color = color; } }

final class EndPoint extends Point { }
class Test { public static void main(String[] args) { Point p = new Point(); ColoredPoint cp = new ColoredPoint(); Colorable c;
// The following may cause errors at run time because // we cannot be sure they will succeed; this possibility // is suggested by the casts: cp = (ColoredPoint)p; // p might not reference an // object which is a ColoredPoint // or a subclass of ColoredPoint c = (Colorable)p; // p might not be Colorable
// The following are incorrect at compile time because // they can never succeed as explained in the text: Long l = (Long)p; // compile-time error #1 EndPoint e = new EndPoint(); c = (Colorable)e; // compile-time error #2
}
ここで,クラス型 Long 及び Point は関係しない(つまり,これらは同じでないし,相手のサブクラスでもない)ために,最初のコンパイル時エラーが生じる。従ってこれらの間のキャストは,常に失敗する。

EndPoint の変数はインタフェース Colorable を実装する値を参照できないために,第二のコンパイル時エラーが生じる。これは,EndPointfinalな型であって,final な型の変数は常にコンパイル時の型と同じ実行時の型の値を保持することによる。そこで変数 e の実行時の型は正確に型 EndPoint でなければならず,型 EndPointColorable を実装しない。

次に,配列(10)を含む例を示す。


class Point {
    int x, y;

Point(int x, int y) { this.x = x; this.y = y; }
public String toString() { return "("+x+","+y+")"; }
}
public interface Colorable { void setColor(int color); }
public class ColoredPoint extends Point implements Colorable { int color; ColoredPoint(int x, int y, int color) { super(x, y); setColor(color); }

public void setColor(int color) { this.color = color; }
public String toString() { return super.toString() + "@" + color; }
}
class Test { public static void main(String[] args) { Point[] pa = new ColoredPoint[4]; pa[0] = new ColoredPoint(2, 2, 12); pa[1] = new ColoredPoint(4, 5, 24); ColoredPoint[] cpa = (ColoredPoint[])pa; System.out.print("cpa: {"); for (int i = 0; i < cpa.length; i++) System.out.print((i == 0 ? " " : ", ") + cpa[i]); System.out.println(" }"); }
この例は,エラーなしにコンパイルされ,次の出力を生成する。

cpa: { (2,2)@12, (4,5)@24, null, null } 
次の例は,コンパイルのためにキャストを使用しているが,型に互換性がないために実行時に例外を投げる。


public class Point { int x, y; }

public interface Colorable { void setColor(int color); }
public class ColoredPoint extends Point implements Colorable {
int color;

public void setColor(int color) { this.color = color; }
}
class Test { public static void main(String[] args) {
Point[] pa = new Point[100];
// The following line will throw a ClassCastException: ColoredPoint[] cpa = (ColoredPoint[])pa;

System.out.println(cpa[0]);

int[] shortvec = new int[2];

Object o = shortvec;
// The following line will throw a ClassCastException: Colorable c = (Colorable)o;

c.setColor(0);
}

5.6 数値昇格

数値昇格(numeric promotion) は,算術演算のオペランドに対して適用する。数値昇格の文脈では,恒等変換 (5.1.1) 又はプリミティブ型の拡大変換 (5.1.2) が使用できる。

数値昇格は,数値演算子のオペランドを共通の型に変換するために使用する。演算が実行可能となるように,数値昇格には,単項数値昇格(5.6.1)及び二項数値昇格(5.6.2)の二種類が存在する。C言語での類似の変換は,"通常単項変換"及び"通常二項変換"と呼ぶ。

数値昇格は,Javaの全体的な特徴ではなく,組込み演算の特性である。

5.6.1 単項数値昇格

演算子には, 単項数値昇格(unary numeric promotion) を単一オペランドに適用するものがあり,次のように数値型の値を生成しなければならない。

単項数値昇格は,次の状況の式に関して実行する。

単項数値昇格の例を含むテストプログラムを次に示す。


class Test {
    public static void main(String[] args) {
        byte b = 2;
        int a[] = new int[b];    // dimension expression promotion
        char c = '\u0001';
        a[c] = 1;                // index expression promotion
        a[0] = -c;               // unary - promotion
        System.out.println("a: " + a[0] + "," + a[1]);
        b = -1;
        int i = ~b;              // bitwise complement promotion
        System.out.println("~0x" + Integer.toHexString(b)
                    + "==0x" + Integer.toHexString(i));
        i = b << 4L;             // shift promotion (left operand)
        System.out.println("0x" + Integer.toHexString(b)
             + "<<4L==0x" + Integer.toHexString(i));
    }
このテストプログラムは,次の出力を生成する。


a: -1,1
~0xffffffff==0x0
0xffffffff<<4L==0xfffffff0 

5.6.2 二項数値昇格

ある演算子が 二項数値昇格(binary numeric promotion) をオペランドの対に適用するときは,個々のオペランドが,数値型の値を表さなければならないが,必要に応じてオベランドを変換するための拡大変換(5.1.2)を用いて,順番に次の規則を適用する。

二項数値昇格は,特定の演算子のオペランドに関して実行する。

二項数値昇格の例は,上記の5.1に存在する。ここでは別の例を示す。


class Test {
    public static void main(String[] args) {
        int i = 0;
        float f = 1.0f;
        double d = 2.0;

// First i*f promoted to float*float, then // float==double is promoted to double==double: if (i * f == d) System.out.println("oops");
// A char&byte is promoted to int&int: byte b = 0x1f; char c = 'G'; int control = c & b; System.out.println(Integer.toHexString(control));
// A int:float promoted to float:float: f = (b==0) ? f : 4.0f; System.out.println(1.0/f);
}
これは,次の出力を生成する。


7
0.25 
この例は,ASCII文字 G の下位5ビットを除く全体にゼロのマスクをかけることによって,この文字をASCII文字の “control-G (BEL)” に変換する。7 は,この制御文字の数値である。


目次 | |