03_データ型・変数

対象:C言語経験者 / C言語との差分を中心に理解します


■ 1. Javaの型の全体像

Javaの型は大きく2種類に分かれます。C言語にはなかった区別。

基本型(プリミティブ型) 値そのものを変数に格納 byte short int long float double char boolean ← Javaで追加 変数 42 ← 値を直接保持 参照型 データの場所(アドレス)を格納 String 文字列 配列 int[] など クラス全般 後の単元で詳しく 変数 0x1a4 "Java" 実体
基本型は値を直接持ちます。参照型はデータの場所(参照)を持ちます。
「String は基本型ではない」ということを頭に入れておきましょう。
試験問1・問2で「基本データ型に該当するもの」を問われたとき、String は基本型ではないので注意。

● 参照型とは何か

プリミティブ型は変数の中に値そのものが入る。参照型は少し違います。

C言語のポインタを思い出してください。ポインタ変数はデータそのものではなく「データがある場所(アドレス)」を持っていた。参照型はこれに近い考え方です。

プリミティブ型 int n = 42; 変数 n 42 変数の中に値 42 が入っています 参照型(String) String s = "Java"; 0x3f2 "Java" 変数 s (アドレスを持つ) 文字列の実体 (別の場所にある)
プリミティブ型は値を直接保持します。参照型は実体がある場所(参照)を保持します。

この仕組みから、参照型には C言語のポインタにはなかった null という概念があります。
null は「どこも指していない(実体がない)」状態を表す。

String s = null;  // 実体なし。これはOK
System.out.println(s);  // → null と表示される

// ただし null のまま操作しようとするとエラー(NullPointerException)
// System.out.println(s.length());  // ← 実行時エラー

● String の比較:==.equals() の違い

== は「同じ場所(アドレス)を指しているか」を調べます。.equals() は「中身の文字列が同じか」を調べます。

参照型の仕組みを思い出してください。変数が持っているのはアドレスです。== はそのアドレスを比べるので、中身が同じでも別のオブジェクトなら false になります。

ケース1:new で作った場合(== は false) String a = new String("Java"); String b = new String("Java"); 0x1a0 変数 a 0x2b8 変数 b "Java" オブジェクト① "Java" オブジェクト② a == b → false アドレスが違う (0x1a0 ≠ 0x2b8) a.equals(b) → true ケース2:文字列リテラルの場合(== も true になることがある) String a = "Java"; String b = "Java"; 0x3c1 変数 a 0x3c1 変数 b "Java" 文字列プール (同じ実体を共有) a == b → true たまたまアドレスが同じ ※ 常にこうなるとは限らない! a.equals(b) → true 常に中身で比較
== はアドレスを比べます。.equals() は中身を比べます。
文字列の中身を比べたいときは常に .equals() を使いましょう
ケース2が「たまたまtrue」になる理由
Javaは同じ文字列リテラルを「文字列プール」という場所に1つだけ保存して再利用します。 そのため "Java" と書いた変数が同じアドレスを持つことがあり、==true になることがあります。
しかし これは実装の都合であり、保証された動作ではありませんnew String("Java") で作った場合は別のオブジェクトになります。
→ 文字列の比較には常に .equals() を使いましょう。
String a = new String("Java");
String b = new String("Java");
String c = "Java";
String d = "Java";

System.out.println(a == b);        // false  (別オブジェクト)
System.out.println(a.equals(b));   // true   (中身が同じ)
System.out.println(c == d);        // true   (文字列プールで同じ実体)
System.out.println(c.equals(d));   // true   (中身が同じ)

// 結論:中身を比べたいなら常に .equals() を使う

● String はイミュータブル(不変)

参照型を理解する上でもう1つ重要な性質があります。String オブジェクトは一度作ったら中身を変えられません。これを「イミュータブル(immutable)」と言います。

では str = str + "efg" のように連結したとき、何が起きているのでしょうか。

実行前 変数 str 0xa1 0xa1 のオブジェクト "abcd" 変更不可(イミュータブル) str + "efg" を評価すると… ① 新オブジェクト "abcdefg" を生成 ② str のアドレスを更新 ("abcd" 自体は変えない) 実行する操作 str = str + "efg" 実行後 変数 str 0xb2 ↑ アドレスが変わった 0xb2(新規生成) "abcdefg" 0xa1 "abcd" × 参照なし GC 対象(自動解放)
str = str + "efg" は "abcd" を書き換えるのではなく、新しい "abcdefg" オブジェクトを生成して str を付け替えます
古い "abcd" は参照がなくなり、ガベージコレクションによって自動解放されます。

● ループ内の連結は非効率になる

イミュータブルの性質から、ループの中で + を繰り返すたびに新しいオブジェクトが生成されて捨てられ続けます。これは大量の文字列を扱うときに性能の問題になります。

非効率(String の + 連結)
String result = "";
for (int i = 0; i < 1000; i++) {
    result = result + i;
    // 毎回新しいオブジェクトが生成される
    // 1000個作って1000個捨てる
}
効率的(StringBuilder)
StringBuilder sb = new StringBuilder();
for (int i = 0; i < 1000; i++) {
    sb.append(i);
    // 同じオブジェクトに追記するだけ
}
String result = sb.toString();
StringStringBuilder
変更可否不変(イミュータブル)変更可能(ミュータブル)
+ 連結毎回新しいオブジェクトを生成append() で同じオブジェクトに追記
最終的に String が必要なときそのまま使える.toString() で String に変換
試験での出題よく出るクラスライブラリの単元で出る
数回の連結なら + で問題ありません。ループの中で大量に連結するときは StringBuilder を使うのが定石です。
StringBuilder の詳細はクラスライブラリの単元で改めて学びます。

■ 2. 基本型(プリミティブ型)一覧

● 「プリミティブ」とは

primitive は英語で「原始的・基本的なもの」という意味。

プログラミングでは「それ以上分解できない、一番シンプルな値」を指す。42 という整数や 3.14 という小数は、それ自体が値そのもの。これがプリミティブ型。

対して "Java" という文字列は、内部に文字が並んだ複雑な構造を持ちます。こういうものは参照型として扱われます。

種類型名サイズ値の範囲(目安)C言語との比較
整数型 byte8ビット-128 〜 127C の char(符号あり)に近い
short16ビット-32,768 〜 32,767C の short と同じ
int32ビット約 ±21億C の int と同じ(Javaでは常に32ビット)
long64ビット約 ±922京C の long long に相当
浮動小数点型 float32ビット単精度C の float と同じ
double64ビット倍精度C の double と同じ
文字型 char16ビットUnicode 1文字C の char(1バイト)と異なります。Javaは2バイト
論理型 boolean Java追加true または falseC言語にはない。C では 0/1 で代替していた
C言語では int のサイズは環境によって変わる(16ビット・32ビットなど)。
Javaでは型のサイズはどの環境でも固定されています。これもWORAを支える仕組みの1つ。

■ 3. リテラルの書き方(試験頻出)

リテラルとは「ソースコードに直接書いた値」のこと。型によって書き方のルールがあります。

書き方注意点
int そのまま int a = 100; 整数リテラルのデフォルトは int
long 末尾に L long b = 100L; l(小文字)でも動くが 1 と紛らわしいので L が慣習
double そのまま(小数点あり) double d = 3.14; 小数リテラルのデフォルトは double
float 末尾に f float f = 3.14f; f を付けないと double と見なされてコンパイルエラー
boolean true / false boolean b = true; 数値(0/1)は使えない。boolean b = 1; はエラー
char シングルクォート char c = 'A'; C言語と同じ。ただし内部は Unicode(2バイト)
String ダブルクォート String s = "Java"; 参照型。シングルクォートはエラー

● 数値リテラルのセパレータ(Java 7以降)

桁数が多い数値は _(アンダースコア)で区切って書ける。

int million = 1_000_000;   // 1,000,000 と同じ意味
long big    = 1_543_900L;  // 試験第67回 問3 (21) で登場
_ は見やすさのためだけ。プログラムの動作には影響しない。
試験でも出題されています(「3行目でコンパイルエラー」という誤りの選択肢に注意)。

■ 実習1:いろいろな型を使ってみる

実習1

03_DataTypes フォルダに DataTypes.java を作って実行しましょう。

public class DataTypes {
    public static void main(String[] args) {

        // 整数型
        int    i = 100;
        long   l = 1_000_000L;   // アンダースコアで区切れる

        // 浮動小数点型
        double d = 3.14159;
        float  f = 3.14f;        // float は末尾に f が必要

        // 文字型(Javaの char は Unicode = 2バイト)
        char c = 'A';

        // 論理型(C言語にはない)
        boolean flag = true;

        // 文字列(参照型)
        String name = "Java";

        // 出力
        System.out.println("int    : " + i);
        System.out.println("long   : " + l);
        System.out.println("double : " + d);
        System.out.println("float  : " + f);
        System.out.println("char   : " + c);
        System.out.println("boolean: " + flag);
        System.out.println("String : " + name);
    }
}

実行結果:

int : 100 long : 1000000 double : 3.14159 float : 3.14 boolean: true String : Java

※ float の出力は実行環境によって末尾の桁が変わることがあります(例:3.14 → 3.1400001 など)。これは浮動小数点の精度の問題。

char c = 'A'; を println で出力すると何が表示されるか確認しましょう

System.out.println(c)A と表示されます。
System.out.println("char: " + c)char: A になります。

C言語と同じ感覚で使える。ただし内部は Unicode(2バイト)なので、日本語の1文字も char に入れられる:char k = '日';

● 動いたら変えてみましょう

#変えてみること予想される結果
1 float f = 3.14f;f を消して float f = 3.14; にします コンパイルエラーになります。エラーメッセージを読んでみよう
2 char c = 'A';char c = '日'; に変える と表示されます。Javaのcharは日本語1文字も扱える(Unicode)
3 boolean flag = true;boolean flag = 1; に変える コンパイルエラー。booleanに数値は入れられない(C言語との違い)
4 long l = 1_000_000L;L を消す この値はintの範囲内なのでエラーにならない。では long l = 10_000_000_000; はどうなる?

■ 4. 型変換(キャスト)

異なる型の間で値を代入するとき、Javaは厳しくチェックします。

byte short int long float double → この方向は自動変換OK(値の範囲が広くなる) ← 逆方向はキャスト(明示的な変換)が必要
値の範囲が広い型への変換は自動。狭い型への変換はキャストが必要。

● 自動変換(暗黙のキャスト)

int    i = 100;
long   l = i;      // int → long は自動OK(範囲が広くなる)
double d = i;      // int → double も自動OK

● キャスト(明示的な変換)

double d = 3.99;
int    i = (int) d;   // 小数点以下を切り捨て → i = 3
System.out.println(i); // → 3
「四捨五入」ではなく「切り捨て」。3.99 を int にキャストすると 3 になります。
C言語でも同じルールだが試験でよく問われます。

● コンパイルエラーになるケース(試験頻出)

int   x = 35;
short y = x + 10000;  // コンパイルエラー!

// なぜ?
// x + 10000 の計算結果は int 型になる
// int を short に代入しようとするとコンパイルエラー
// → キャストが必要
short y = (short)(x + 10000);  // これはOK(ただし値がshortの範囲を超える場合は壊れる)
試験第67回 問3 (18) がまさにこのパターン。
「int型の式の値をshort型の変数に代入しようとするとコンパイルエラー」という正答。

■ 実習2:型変換の動きを確認します

実習2

04_TypeConv フォルダに TypeConv.java を作って実行しましょう。

public class TypeConv {
    public static void main(String[] args) {

        // ① 自動変換(広い型へは自動OK)
        int    a = 100;
        double d = a;          // int → double 自動変換
        System.out.println("int → double: " + d);  // 100.0

        // ② キャスト(狭い型へは明示が必要)
        double x = 3.99;
        int    i = (int) x;    // 小数点以下切り捨て
        System.out.println("double → int: " + i);  // 3

        // ③ 整数どうしの割り算と型
        int p = 3;
        int q = 8;
        int    r1 = p / q;           // 整数除算 → 0
        double r2 = p / q;           // 整数除算の結果(0)をdoubleに → 0.0
        double r3 = (double) p / q;  // pをdoubleに変換してから割る → 0.375
        System.out.println("int/int       : " + r1);
        System.out.println("int/int→double: " + r2);
        System.out.println("double/int    : " + r3);

        // ④ float と double の比較(試験頻出)
        float  f = 5.9f;
        int    n = 5;
        boolean result = (f == n);   // float と int を比較
        System.out.println("5.9f == 5: " + result);  // false
    }
}

実行結果:

int → double: 100.0 double → int: 3 int/int : 0 int/int→double: 0.0 double/int : 0.375 5.9f == 5: false

● ③ の r2 と r3 の違いを確認しましょう

なぜ r2 は 0.0 で r3 は 0.375 なのか

int / int の計算はまず整数どうしの割り算が行われ、結果は整数の 0 になります。

その後 double r2 = 0 と代入されるので 0.0。小数の情報はすでに失われています。

一方 r3(double) p で先に p3.0 に変換してから / q を行うので、3.0 / 8 = 0.375 になります。

試験第67回 問3 (20) がこのパターン。

④ で 5.9f == 5 が false になる理由

異なるデータ型どうしの比較では、値の範囲が広い型に合わせられます。

floatint の比較では intfloat に変換され、5.9f == 5.0f と比較されます。

5.9f ≠ 5.0f なので結果は false。試験第67回 問3 (19) のパターン。

● 動いたら変えてみましょう

#変えてみること予想・確認ポイント
1 double x = 3.99; の値を 3.013.5-3.9 に変えてキャスト結果を確認します すべて「切り捨て」であることを確かめよう。-3.9 → -3(0方向への切り捨て)になります
2 double r3 = (double) p / q;(double)q 側に移して p / (double) q にします 同じ 0.375 になるはず。どちらかを double にすれば割り算が double で行われます
3 float f = 5.9f; の比較対象を int n = 5; から float n2 = 5.9f; に変えて f == n2 を確認します true になります。同じ型・同じ値なら == が成立します
4 次のコードをプログラムに追加して実行してみる:
int n = -6; System.out.println((int)(n / 2.0));
まず自分で答えを予想してから実行しましょう

■ 5. 変数宣言のルール

ルール
宣言と同時に代入できますint a = 10;
宣言と代入を分けられますint a; a = 10;
同じ型を1行でまとめられますint a = 10, b = 20;
使う前に必ず初期化が必要初期化しないとコンパイルエラー(C言語では未定義動作だったがJavaはエラーにする)
C言語では未初期化変数を使ってもコンパイルが通ることがあった(警告止まり)。
Javaは未初期化変数の使用をコンパイルエラーにします。これも安全設計の一部。

■ 試験(問3)との対応

頻出ポイント

よく問われること正しい答え
基本データ型に含まれるものbyte short int long float double char boolean の8種類。String は含まない
float のリテラル末尾に f が必要。float f = 3.14; はコンパイルエラー
int 同士の割り算小数点以下は切り捨て。3 / 8 = 0
異なる型の演算範囲が広い型に自動変換されます。int + double → double
narrow な型への代入コンパイルエラー。キャストが必要。
_ セパレータJava 7以降で使える。1_000_000 は有効。
boolean に数値を代入エラー。boolean b = 1; は不可。
練習問題:次のプログラムの出力を答えよ(第67回 問3(20) 類似)
public class Q {
    public static void main(String[] args) {
        int    a = 3 / 8;
        double b = 3 / 8.0;
        System.out.println(a + " " + b);
    }
}

答え:

0 0.375

理由:3 / 8 は整数どうしなので 0(切り捨て)。3 / 8.0 は 8.0 が double なので 3 が double に変換されてから計算され 0.375。

練習問題2:コンパイル結果を答えよ(第67回 問3(18) 類似)
public class Q2 {
    public static void main(String[] args) {
        int   x = 35;
        short y = x + 10000;
        System.out.println(y);
    }
}

答え:コンパイルエラー(4行目)

理由:x + 10000 の結果は int 型。int を short に代入しようとするとキャストが必要なためコンパイルエラー。
値が short の範囲(-32768〜32767)に収まっていてもエラーになります。これがJavaの型の厳格さ。


■ まとめ

ポイント内容
基本型は8種類byte short int long float double char boolean
String は基本型ではない参照型。試験の「基本データ型」の問いで注意
booleanC言語にはない。true/false のみ。数値不可
float のリテラル末尾に f が必要
int / int整数除算。小数点以下は切り捨て
型の自動変換範囲が広い方向(byte→short→int→long→float→double)は自動
キャスト狭い方向への代入には (型名) が必要
未初期化変数使おうとするとコンパイルエラー(C言語より厳格)