こんにちは!侍エンジニア インストラクターの本多です。
みなさんは、ある型から別の型に変換するキャストという言葉は聞いたことがありますか?
この記事では、キャストについて
- キャストとは何か
- キャストの使い方
- クラスをアップキャストする方法
- クラスをダウンキャストする方法
といった基本的な内容から、応用的な内容に関しても解説していきます。今回はキャストについて、使い方をわかりやすく解説します!
なお、Javaの記事については、こちらにまとめています。
キャストとは
ある型から別の型に変換することをキャストといいます。利用シーンとしては「型は変えたくないけど、一時的に別の型として扱いたい」といったことが考えられます。例えば、整数型のテストの点数から、小数点以下第1位までの平均点を求めたい、などです。
キャストを使うためには、下記のように記述するのが基本パターンとなります。
(型)変数名
基本型(プリミティブ型)のキャストから解説します。
キャストの使い方
キャストの使い方について、実際にコードを確認していきましょう。
暗黙的なキャストについて
まずは変数が自動的に型変換される例についてみてきましょう。
これからご紹介する例はint型の2つの変数で割り算を行う場合に、計算結果が自動的にint型になる例です。自動的に型変換されることを暗黙的なキャストといいます。
public class Main { public static void main(String[] args) { int i = 10; int j = 3; float result = i / j; System.out.println("result = " + result); } }
実行結果:
result = 3.0
このサンプルコードの実行結果を確認すると、3.3333…となってほしいところが、きれいに3.0となってしまいました。確かに結果を代入する変数resultはfloat型になっているので、小数点以下も代入可能のようにみえます。
しかしi / j の計算をした時に、両者がint型なので結果も自動的にint型になります。よって、小数点以下が切り捨てられて3となります。その結果をfloat型に代入しても、整数3をfloat型に代入することになるので3.0となるだけです。小数点以下まで計算するとすれば、最初から変数をfloat型で宣言するのが一番簡単です。
割り算でキャストが必要な場合
先ほどのサンプルコードでは、int型の2つの変数で割り算を行い結果が自動的にint型になりました。小数点以下を含むfloat型の結果を得たい場合は、キャストを使って変更することが可能です。サンプルコードで確認していきましょう。
public class Main { public static void main(String[] args) { int i = 10; int j = 3; float result = (float)i / (float)j; System.out.println("result = " + result); } }
実行結果:
result = 3.3333333
このサンプルコードの実行結果を確認すると、想定通りの結果が得られました。このように、元の変数の型はそのままで、一時的に宣言時とは別の型に置き換えることをキャストといいます。
今回はint型をfloat型にしましたが、それ以外にも基本型であれば基本的にキャストは可能です。
キャストができない場合の注意点
先ほどのサンプルコードでは、キャストについて確認しました。
ただし、以下のようなキャストはできません。
public class Main { public static void main(String[] args) { int i = 10; int j = 3; float result = (double)i / (double)j; System.out.println("result = " + result); } }
実行結果:
Main.java:6: error: incompatible types: possible lossy conversion from double to float
float型とdouble型では、double型の方が表現できる桁数が大きいです。ということは、floatの最大桁数より大きい数値がiやjに入っていた場合は、より小さい桁数をもつ変数resultに押し込めようとしているので、エラーになります。この例では、あえて大きい型にキャストして小さい型に代入しようとしました。
一方、桁数が大きい型で宣言した変数をより小さい型の変数に代入する際、キャストして型変換すると代入が可能になります。
精度保証の注意点
キャストする際は、元の基本型のデータ範囲とキャスト後の基本型のデータ範囲に注意する必要があります。
データ範囲が大きい基本型から小さい基本型へキャストすると精度が保証されません。ご注意ください。
サンプルコードで確認しましょう。
public class Main { public static void main(String[] args) { double doubleMax = Double.MAX_VALUE; int intResult = (int) doubleMax; System.out.println(doubleMax); System.out.println(intResult); } }
実行結果:
1.7976931348623157E308 2147483647
このサンプルコードでは、double型の最大値をint型にキャストして、int型の変数に代入しています。
エラーにはなりませんが、int型の最大値に置き換わってしまって精度が保証さず、意図しない値となっています。
クラスのアップキャスト
スーパークラスを継承するサブクラスがあるとします。スーパークラスのオブジェクトにサブクラスのインスタンスを格納することをアップキャストといいます。
アップキャストはサブクラスからスーパークラスへ型変換されることになります。アップキャストは自動的に行われる暗黙的型変換ということになります。
自動的に型変換が行われるのは、サブクラスがスーパークラスのすべてのメンバーを保証できるためです。
サブクラスからスーパークラスにアップキャスト
サブクラスからスーパークラスにアップキャストする場合についてサンプルコードで確認していきましょう。
class SuperClass { public void method1() { System.out.println("スーパークラスのメソッドを実行"); } } class SubClass extends SuperClass { public void method2() { System.out.println("サブクラスのメソッドを実行"); } } public class Main { public static void main(String[] args) { SubClass sb = new SubClass(); // SubClassのメソッド sb.method1(); sb.method2(); // アップキャスト(暗黙的) SuperClass sc = sb; sc.method1(); //sc.method2(); // コンパイルエラー } }
実行結果:
スーパークラスのメソッドを実行 サブクラスのメソッドを実行 スーパークラスのメソッドを実行
このサンプルコードでは、SubClassのインスタンスsbをSuperClass型のオブジェクトscに格納しています。scの型はSuperClassの型で、SubClassのメソッドをメンバに持つわけではありません。したがって、method2メソッドを実行するとコンパイルエラーとなるのでコメントアウトしています。
どのフィールド変数、メソッドが使えるかはオブジェクトの型で決まります。インスタンスの型がサブクラスでも、オブジェクトの型がスーパークラスなら、スーパークラスのフィールド変数およびメソッドしか使えません。
アップキャストの注意点
サブクラスのオブジェクトをスーパークラス型のオブジェクトでアップキャストする際、注意点があります。スーパークラスとサブクラスに、同じ名前のフィールド変数やメソッドがある場合には、フィールド変数はスーパークラス、メソッドはサブクラスが優先される、という点です。
サンプルコードで確認しましょう。
class SuperClass { int intField = 2; public int calc() { return intField * 5; } } class SubClass extends SuperClass { int intField = 3; public int calc() { return intField * 6; } } public class Main { public static void main(String[] args) { SubClass sb = new SubClass(); // (1)SubClassのフィールド変数とメソッド System.out.println("sbのフィールド変数:" + sb.intField); System.out.println("sbのcalc:" + sb.calc()); // (2)キャストした後のフィールド変数とメソッド SuperClass sc = sb; System.out.println("scのフィールド変数:" + sc.intField); System.out.println("scのcalc:" + sc.calc()); } }
実行結果:
sbのフィールド変数:3 sbのcalc:18 scのフィールド変数:2 scのcalc:18
このサンプルコードでは、SubClassはSuperClassを継承しています。(1)は参照変数とsbともにSubClassなので、3と18が表示されたのは分かりますが、(2)は2と18と表示されています。
このことから、フィールド変数はスーパークラス、メソッドはサブクラスが優先されたのが分かります。継承しているので、SubClassはSuperClassのメンバーを内包していますが、メソッドはオーバーライドして、フィールド変数は同じ名前のフィールド変数を宣言して隠蔽しています。
このような場合、アップキャストしたあとはどちらが優先されるのか、注意が必要です。
クラスのダウンキャスト
アップキャストとは逆に、サブクラスのオブジェクトにスーパークラスのオブジェクトを格納することをダウンキャストといいます。
スーパークラスからサブクラスにダウンキャスト
スーパークラスからサブクラスにダウンキャストする場合は明示的にダウンキャストを行う必要があります。
これはスーパークラスはサブクラスのすべてのメンバーを保証できないためです。明示的にダウンキャストするには基本型のキャストの場合と同じように「()」を使って記述します。
(クラス名)オブジェクト名
また、ダウンキャストは一度アップキャストしたオブジェクトに対してのみ使用することができます。
アップキャストされていないオブジェクトをダウンキャストすると、ClassCastExceptionの例外が発生するので注意しましょう。
それではダウンキャストについてサンプルコードで確認しましょう。
class SuperClass { public void method1() { System.out.println("スーパークラスのメソッドを実行"); } } class SubClass extends SuperClass { public void method2() { System.out.println("サブクラスのメソッドを実行"); } } public class Main { public static void main(String[] args) { // アップキャスト(暗黙的) SuperClass sc = new SubClass(); // ダウンキャスト(明示的) SubClass sb = (SubClass)sc; // SubClassのメソッド sb.method1(); sb.method2(); } }
実行結果:
スーパークラスのメソッドを実行 サブクラスのメソッドを実行
このサンプルコードでは、まずサブクラスのインスタンスをスーパークラス型のオブジェクトに格納してアップキャストしています。その後、明示的にダウンキャストしています。
ダウンキャストの注意点
先ほどアップキャストされていないオブジェクトをダウンキャストすると、ClassCastExceptionの例外が発生すると説明しました。ダウンキャストができるか確認する方法があります。それはinstanceof演算子を使う方法です。
instanceof演算子はオブジェクトが指定したクラス型もしくは指定したクラスを継承しているか比較します。サンプルコードで確認していきましょう。
class SuperClass { public void method1() { System.out.println("スーパークラスのメソッドを実行"); } } class SubClass extends SuperClass { public void method2() { System.out.println("サブクラスのメソッドを実行"); } } public class Main { public static void main(String[] args) { // アップキャスト(暗黙的) SuperClass sc1 = new SubClass(); // ダウンキャストできるかの確認 if(sc1 instanceof SubClass) { // ダウンキャスト(明示的) SubClass sb1 = (SubClass)sc1; // SubClassのメソッド sb1.method1(); sb1.method2(); } else { System.out.println("sc1をダウンキャストできません"); } SuperClass sc2 = new SuperClass(); // ダウンキャストできるかの確認 if(sc2 instanceof SubClass) { // ダウンキャスト(明示的) SubClass sb2 = (SubClass)sc2; // SubClassのメソッド sb2.method1(); sb2.method2(); } else { System.out.println("sc2をダウンキャストできません"); } } }
実行結果:
スーパークラスのメソッドを実行 サブクラスのメソッドを実行 sc2をダウンキャストできません
このサンプルコードでは、instanceof演算子を使ってダウンキャストできるか確認しています。SuperClassのオブジェクトsc1にはSubClassのインスタンスを格納し、暗黙的にアップキャストされています。
そのためinstanceof演算子でサブクラスの型と比較するとtrueを返し、ダウンキャストが実行されています。SuperClassのオブジェクトsc2はSubClassのインスタンスでもSubClassを継承したクラスのインスタンスでもないため、ダウンキャストは実行されていません。
このようにダウンキャストを使用する場合はClassCastExceptionの例外が発生しないように、instanceof演算子を使って確認するようにしましょう!
String型の型変換について知りたい方へ
この記事では基本型の型変換について説明しています。
String型の型変換については、こちらで詳しく説明していますので、ぜひ参考にしてくださいね
まとめ
この記事では、基本型やクラスのキャストを説明しました。
キャストは、一時的に宣言した型とは異なる型として扱いたいときに使われます。基本型の場合は理解しやすいですが、クラスの場合はいろいろとルールがあって、最初は理解が難しいかもしれません。
使い方を忘れた場合、本記事を参考にしてくださいね!