HOME/Java/

Javaの文法

Article Outline
TOC
Collection Outline

ブロック

ブロック内には、複数の文を記述することができます。変数の有効範囲(「スコープ」という)は、そのブロック内のみとなります。

public static void main(String[] args) {
    {
        // ブロック内で変数vを宣言
        int v = 31;
        // ブロック内では参照可能
        System.out.println("v = " + v);
    }
    // ブロックを抜けたため、変数vはここでは参照できない
    // System.out.println("v = " + v);
    // メソッド内のみで有効な変数xを宣言
    int x = 0;
    // for文のブロック内でのみ有効な変数yを宣言
    for (int y = 0; y < 2; y++) {
        // for文のブロック内でのみ有効な変数zを宣言
        int z = 0;
        System.out.println(
        "x = " + x++ + ", y = " + y + ", z = " + z++);
    }
    System.out.println("x = " + x); // 参照可
    // for文のブロックを抜けたため、変数y、変数zはここでは参照できない
    // System.out.println("y = " + y);
    // System.out.println("z = " + z);
}

ブロック内に記述できるのは、「文」と「局所変数宣言文」と「クラス宣言」だけです。
処理上、ブロックは通常の文とみなされます。そのため、「for」文や「if」文などでよく使われます。
メソッド内に「for」文や「if」文などと関係のないブロックを記述することも可能です。クラス宣言部の中、メソッドの外にブロックを記述すると初期化ブロックとなります。

識別子

コード中にクラス、メソッド、変数、パッケージ、ラベルなどを定義していきます。これらの名前を「識別子」といいます。 文字列をコンソールに表示する

System.out.println("Hello");
  • 「System」はクラス名
  • 「out」はフィールド名(変数名)
  • 「println」はメソッド名
    それぞれ識別子です。

識別子には、次の文字が使えます。

  • 半角アルファベットの大文字・小文字
  • 半角数字
  • 「$」「_ 」
  • 読点などの一部例外を除くUnicode文字(全角文字)

一般的にメソッド名、変数名は小文字+数字で単語の境目の部分だけを大文字にします。
例「getCode」「familyName」など。

クラス名は、それに加えて先頭の文字を大文字にします。
例「TranslateText」など。

ラベルの場合は、すべて大文字で定義します。
例「OUTER」など。

パッケージ名は、すべて小文字で定義します。
例「jp.sample.test」など。

変数

値や、インスタンスを入れる箱のことを「変数」といいます。
値を入れる変数のことをプリミティブ型変数(基本型変数)、インスタンスを入れる変数のことを参照型変数といいます。
どちらの場合も「型名 変数名」の形で宣言します。

int val; // プリミティブ型
String str; // 参照型

プリミティブ型変数

プリミティブ型とはデータだけを持つ型で、インスタンスではありません。
次の8つがJavaのプリミティブ型のすべてです。

  • boolean
  • char
  • byte
  • short
  • int
  • long
  • float
  • double

参照型変数

「インスタンスを入れる変数」の表現は正確ではありません。
実際には変数内にインスタンスが入っているわけではなく、メモリのどこかにあるインスタンスの場所(インスタンスの参照)を格納している変数です。 参照型変数の型は、Javaで最初から用意されているクラス、Webで公開されているクラス、これから自分で作るクラスなど膨大な数になります。

変数修飾子

変数の前には、次の修飾子を付けることができます。

  • アクセス指定子
  • final
  • volatile
  • transient
  • static

「final」以外の修飾子は、フィールドでのみ使うことができます。
「final」を付けて宣言した変数には、最初に値を代入した後は他の値を代入することはできません。

final StringBuilder buf = new StringBuilder();
// インスタンス内のデータの変更は可能
buf.append("ABC");
// 他のインスタンスを代入することはできない
// コンパイルエラーになる
buf = new StringBuilder();

代入

宣言した変数には、値を代入することができます。宣言と同時に代入することも可能です。
一度、宣言したら、基本的にその変数にはその型の値、または、インスタンスしか代入することはできません。
演算子「=」の左辺にこれから値を設定する変数、右辺に代入する値もしくは変数を記述します。
左辺にリテラルデータを記述することはできません。

// int型に整数はそのまま代入できる
// 宣言と同時に代入も可能
int val = 123;
// int型に真偽型を代入することはできない
// コンパイルエラーになる
val = false;

プリミティブ型の場合、変数の中に値を直接、格納します。

int val = 780;
char ch = 'あ';

参照型変数へ代入するときには、リテラルではなく、インスタンスを作成する必要があります。
ただし、String型やClass型のように、リテラルを使って代入することができるクラスもあります。

// staticメソッドを使ってインスタンスを作成
Integer val = Integer.valueOf(398);
// new演算子を使ってインスタンスを作成
StringBuilder buf = new StringBuilder();
// 文字列リテラルによる初期化
String str = "ABCD";

変数に格納されるのはインスタンスそのものではなく、インスタンスの場所です。
もし、次のようなコードを実行した場合、変数「buf1」と「buf2」は同じインスタンスを指すことになります。

StringBuilder buf1 = new StringBuilder();
StringBuilder buf2 = buf1;

そのため、一方の変数にだけ変更を加えたつもりでも、他方の変数にも影響を与えてしまうので気を付けてください。
次のコードを実行すると、「ABCXYZ」と表示されてしまいます。
これは、参照型の変数の中身は他の変数への参照値が入っていて、参照型の変数を代入することはその参照値をコピーするためです。

StringBuilder buf1 = new StringBuilder();
StringBuilder buf2 = buf1;
buf1.append("ABC");
buf2.append("XYZ");
System.out.println(buf1.toString());

null

参照型の変数は、インスタンスのある場所を示す変数です。
変数を宣言しただけでインスタンスを代入しなかった場合は、何もないことを示す特別な値「null」が入っています。
宣言しただけで初期化(何らかのインスタンスの代入)を行っていない変数を使おうとすると、コンパイルエラーが発生します。

String str;
// 初期化していない変数strを使おうとしているため
// コンパイルエラーになる
System.out.println(str.substring(1, 3));

「null」は、すべての参照型変数に代入可能です。

String str = null;
Integer val = null;
Object obj = null;

「null」は何もないことを表すので、「null」が代入された変数を使うと「NullPointerException」という例外が発生します。

String str = null;
// NullPointerExceptionが発生する
System.out.println(str.substring(1, 3));

ただし、staticフィールドとstaticメソッドはクラスに紐付くので、例外は発生しません。

// 以下は正常に動作する
Integer var = null;
System.out.println(var.valueOf(100));
System.out.println(var.MAX_VALUE);

異なる型への代入

プリミティブ型変数の場合は、宣言した変数の型が代入する値の型より広い型であれば代入することができます。
広い方から順に、double型、float型、long型、int型、short型、byte型となります。
ただし、int型の値をfloat型に、long型の値をfloat型やdouble型に代入すると桁落ちする可能性があります。
また、char型は文字型ですが、実際は、その文字のUnicodeの文字コードの数値(32bit)を保持しているため、int型と互換があります。

// 広い型への代入
byte b = 3;
short s = b;
int i = s;
long l = i;
float f = i;
double d = f;
// char型をint型に代入
char ch = 'A';
i = ch;

参照型変数の場合は、その型のクラスのサブクラスや、型のインタフェースの実装クラスのインスタンスを代入することができます。

SuperClass.java

// スーパークラス
public class SuperClass {
}

Interface.java

// インタフェース
public interface Interface {
}

SubClass.java

// SuperClassのサブクラス
// Interfaceの実装クラス
public class SubClass extends SuperClass implements Interface {
    public static void main(String[] args) {
        // スーパークラス型の変数にサブクラスのインスタンスを代入
        SuperClass obj1 = new SubClass();
        // インタフェース型の変数に実装クラスのインスタンスを代入
        Interface obj2 = new SubClass();
    }
}

プリミティブ型のキャスト

プリミティブ型では、狭い型から広い型に代入できる。

byte b = 100;
int i = b; // 狭い型の変数を広い型に代入するのはOK

それとは逆に、広い型から狭い型への代入を行いたい場合は、キャストを使います。
キャストは、値の前に変換したい型をカッコで囲んで記述します。

int i = 100;
byte b = (byte) i; // int型をbyte型に変換(キャスト)する

狭い型から広い型への変換は問題ないのですが、その逆は型の範囲を超える値が代入される可能性があります。
その場合は桁あふれを起こしてしまい、エラーにはなりません。
次のコードでは、byte型の範囲を超える値を代入しているため、桁あふれが起きます。

int i = 200;
byte b = (byte) i; // int型をbyte型に変換(キャスト)する
System.out.println(b); // 「-56」と表示される

小数型を整数型に変換すると、端数が切り捨てられます。

int i = (int) 4.99;
System.out.println(i); // 「4」と表示される
i = (int) -4.99;
System.out.println(i); // 「-4」と表示される

また、boolean型とint型のように互換のない型の間でのキャストはできません。

int i = (int) true; // コンパイルエラーになる

オートボクシングを使った広い型から狭い型へのキャストもできません。

byte b = (byte) Integer.valueOf(9); // コンパイルエラーになる
int i = Byte.valueOf((byte) 9); // こちらはOK

参照型のキャスト

参照型のキャストも基本的には同じです。スーパークラスからサブクラスへのキャストが可能です。
まったく関係のないクラスへのキャストはできません。

// SuperClassのサブクラス
public class SubClass extends SuperClass {
    public static void main(String[] args) {
        // スーパークラスからサブクラスへのキャストはOK
        SubClass obj1 = (SubClass) new SuperClass();
        // 全く関係の無いクラスへのキャストはコンパイルエラーになる
        SubClass obj2 = (SubClass) Integer.valueOf(9);
    }
}

「ClassCastException」例外

キャストが明らかに不正な場合はコンパイルエラーとなりますが、コンパイル時に判断が付かないようなケースではプログラムの実行時に「ClassCastException」例外が発生します。
どうしてもキャストを使用しなければならないケースもありますが、できるだけジェネリックスを使うなど、キャストを使わないほうが思わぬミスの防止になります。

Object x = new Integer(0);
System.out.println((String)x); // ClassCastExceptionが発生する

定数の定義

Javaで定数を定義するには、フィールドを宣言するときに「static final」を付けて宣言します。
「final」を付けるとフィールドは値を変更できなくなるため、定数として扱うことができます。
「static」はなくてもかまいませんが、インスタンスごとに同じ値のフィールドが作成される無駄を省きます。

Constant.java

// 定数を定義するクラス
public class Constant {
    // 定数フィールド
    public static final String APP_NAME = "特製アプリケーション";
}

ConstantSample.java

// 定数を使うクラス
public class ConstantSample {
    public static void main(String[] args) {
        System.out.println(Constant.APP_NAME + "を開始します。");
    }
}

ConstantSample.javaを実行すると、次のように表示されます。

特製アプリケーションを開始します。

厳密には、「Constant.APP_NAME」は定数ではなく、定数フィールドです。
Javaでは定数を定義することはできません。ただし、慣習的に定数フィールドのことを「定数」といいます。

定数を使う場合の注意点

コンパイラによりますが、final宣言されたメソッドやフィールドは、それを使っている場所を定数の値に置換してからコンパイルします。 これをインライン展開といいます。ConstantSample.javaの「main」メソッドは、次の形で書き換えられてからコンパイルしています。

public static void main(String[] args) {
    System.out.println("特製アプリケーションを開始します。");
}

そのため、Constant.javaの定数フィールドの値を変えて、Constant.javaだけをコンパイルしても、ConstantSample.javaの実行結果は変わりません。 定数を変更した場合は全クラスをコンパイルし直すようにしてください。

定数とならないケース

static finalのフィールドがすべて定数になるわけではなく、コンパイル時に値が決定するものだけが定数となります。 たとえば、次のような例を考えてみます。

public static final int X = 1 + 1; // 定数
public static final int Y = new Integer(2).intValue(); // 定数ではない

この例では、フィールド「X」「Y」ともに値は「2」となります。
「X」はコンパイル時に「1+ 1」の算出が行われて定数値となります。
しかし、「Y」は「Integer」クラスのインスタンスが作成されてからでないと値が取得できないため、コンパイル時ではなく、プログラムの実行時に値が代入されるので定数ではありません。
両者の違いとしては、前者は先に述べたようにインライン展開が行われますが、後者は実行時にしか値がわからないため、インライン展開されません。
そのため、後者は「switch」文の「case」の値として使用できません。「switch」文内の「case」の値は重複してはならないルールがあり、この重複チェックがコンパイル時に行われるためです。

リテラル

コード中に記述する値そのものを「リテラル」といいます。
「a = 100」の「100」、「str ="ABC"」の「"ABC"」がリテラルです。
型によって次のような種類があります。

  • ブーリアンリテラル boolean型のリテラル表記です。「true」と「false」の2つだけです。

  • 文字リテラル 文字1字を表します。char型になります。「'A'」「'あ'」などのように、文字を「'」(シングルクォーテーション)で囲んで表します。

  • 文字列リテラル 文字列を表します。String型になります。「"ABC"」「"あいうえお"」などのように、文字列を「"」(ダブルクォーテーション)で囲んで表します。
    なお、リテラルもインスタンスなので、メソッドを呼び出すことも可能です。

// 「BC」と表示される
System.out.println("ABC".substring(1));
  • 整数リテラル 整数を表します。int型になります。
    「123L」のように、数値の最後に「L」を付けると、long型になります。
    「0x12ab」のように、数値の先頭に「0x」を付けると16進数表記になります。
    「0123」のように、数値の先頭に「0」を付けると8進数表記になります。
    int型は符号付き32bit整数なので、表せる範囲は−2147483648~2147483647です。

  • 浮動小数点数リテラル 小数を表します。double型になります。
    「12.3F」のように、数値の最後に「F」を付けるとfloat型になります。
    「e」の前に仮数部を、後ろに指数部を記述します。「1.234e2」と「123.4」は同じ値になります。

  • クラスリテラル Class型のインスタンスを表します。「String.class」のように、クラス名の後ろに「.class」を付けると、そのクラスのClass型のインスタンスになります。
    「Class」クラスは、そのクラスのメタ情報(どのようなフィールド、メソッドを持っているかなどの情報)を保持するクラスです。

  • 参照リテラル 参照リテラルは、「null」だけです。

新しいリテラル表記

Java7では新しい数値のリテラル表記が追加されました。
「0b」を先頭に付けることで、2進数をリテラル表記できるようになりました。「0b010101」のように記述します。
もう1つ、数値の中にアンダースコアがあっても無視して、普通に数値として扱えるようになりました。
桁の大きい数値を見やすく記述することが可能になります。ただし、次の場所には記述することができません。

  • 「0x」「0b」の直後
  • 「L」「F」などの型を示す文字の直前
  • 小数点の直前、直後
int a = 1_000; // 1000 と同じ
int b = 0x12__abc; // 0x12abc と同じ
int c = 0_1234; // 01234 (8進数)と同じ
double d = 1.234_56; // 1.23456 と同じ
int e = 0b_1101; // エラー
long f = 1234_L; // エラー

補助文字

Java5で、Unicode4.0に含まれる補助文字がサポートされるようになりました。
この補助文字は、16進数5桁で表されます。2byteのchar型では表現できなくなったため、これを「コードポイント」と呼び、int型で表すようになりました。
文字列の中の任意の文字の文字コードを取得するために「String#charAt」メソッドを使っていましたが、補助文字を含む場合は「String#codePointAt」メソッドを使う必要があります。
補助文字の文字コードは、特別な変換式で4byteに変換して上位2byteと下位2byteをUnicodeの未使用領域に割り当てるようにしています。
上位2byteを「上位サロゲート」、下位2byteを「下位サロゲート」と呼びます。これは「String#toCharArray」メソッドで取得できます。
補助文字の例として「𠮷」(上の部分が「士」ではなく「土」になっている)がありますが、これをchar型の変数に代入しようとするとコンパイルエラーになります。

char ch = '𠮷'; // 4byteデータなのでコンパイルエラーになる

算術演算子

被演算数の型が異なるもの同士で演算を行った場合、演算結果は広い型になります。
なお、同じ型同士の演算でも、byte型、short型での演算結果は、int型になります。

long a = 12 + 234L; // intとlongの演算結果はlong型になる
byte b = 100;
short c = 200;
int d = b + c; // byte, shortのみでの演算結果はint型になる

「++」「−−」は、数値型変数のみで使うことができます。
変数の前についている場合は値を1増やしてから(または1減らしてから)、その変数の中身を参照します。
変数の後ろについている場合は、その変数の中身を参照してから値を変えます。被演算数が1つなので単項演算子といいます。

int a = 3;
int b = a++; // bには3が代入され、aの値は4になる
int c = 3;
int d = ++c; // cの値は4になり、dには4が代入される

ビット演算子・シフト演算子

Javaでは、数値をすべて2進数で処理しています。
普通の四則演算以外にも、「0」と「1」を使って数値の組み合わせによる演算も行えます。それを「ビット演算」といいます。
ビット演算の対象は、整数のみです。
シフト演算子は2進数で表した値を右、または、左にずらします。
右にずらした場合は最も左に、左にずらした場合は最も右に「0」を追加し、はみ出した部分は切り捨てます。
「>>」は一番左のビット(最上位ビット)が「1」だった場合、最も左に「1」を追加します。

// 「41」は2進数で「101001」
int a = 41;
// 「12」(1010)が表示される
System.out.println(a >> 2);
// 「164」(10100100)が表示される
System.out.println(a << 2);
// 「-41」は2進数で「11111111111111111111111111010111」
a = -41;
// 「-11」(11111111111111111111111111110101)が表示される
System.out.println(a >> 2);
// 「1073741813」(111111111111111111111111110101)が表示される
System.out.println(a >>> 2);

比較演算子

比較演算子は、2つの値を比較します。演算結果は、必ずboolean型です。 判定が成り立つ場合は「true」、成り立たない場合は「false」を戻します。
「<」「>」を含む演算子は、数値のみに使えます。「==」「!=」は、すべての型で使えます。
《代入について》(p.65)で説明したように、参照型変数にはインスタンスの場所が格納されているだけです。
「null」かどうかの判定以外でインスタンスの比較する場合は、「Object#equals」メソッドや、そのクラスで実装されている比較用のメソッド(「compareTo」メソッドなど)を使ってください。
次のコードを実行すると、「等しくない」と表示されます。

// aとbは同じ「ABC」という文字列だが異なるインスタンス
String a = "ABC";
String b = new String("ABC");
if (a == b) {
    System.out.println("等しい");
} else {
    System.out.println("等しくない");
}

論理演算子

論理演算は、boolean型のみ使います。
「&」と「&&」、「|」と「||」は、結果は同じです。
これらの違いは、「&&」と「||」は左辺の評価で「true」か「false」かが決まった場合、右辺の評価を行わないということです。
文字列が「null」かどうかを判定して、「null」でなければ「ABC」という文字列かを判定する場合、次のような違いがあります。

String str = null;
// 「null != str」の判定時点でfalseになるので
// 「str.equals("ABC")」は実行されない
if (null != str && str.equals("ABC")) {
    System.out.println("OK");
} else {
    System.out.println("NG");
}
// 「null != str」と「str.equals("ABC")」を判定してから
// 論理積をとるのでNullPointerExceptionが発生する
if (null != str & str.equals("ABC")) {
    System.out.println("OK");
} else {
    System.out.println("NG");
}

代入演算子

代入演算子は、変数に値を代入するのに使います。
単に値を代入するだけでなく、演算結果を代入することもできます。

条件演算子

条件演算子は、「a ? b : c」の形で記述します。
「a」が「true」の場合は「b」を、「false」の場合は「c」を戻します。
「if」文を使うと、次のように書き換えることができます。

int x;
// 条件演算子を使った場合
x = a ? 100 : 200;
// if文を使った場合
if (a) {
    x = 100;
} else {
    x = 200;
}

被演算数が3つあるのはこの演算子だけなので、三項演算子ともいいます。

文字列の演算子

String型は「+」演算子を使うと、文字列を結合することができます。

String str1 = "ABC";
String str2 = str1 + "XYZ"; // "ABCXYZ"がstr2に代入される

String型とString型以外を「+」で演算すると、String型以外の被演算数は文字列表現に置き換えられて、文字列同士の結合となります。
プリミティブ型であれば、値がそのまま文字列になります。
参照型変数であれば、「toString」メソッドの結果が結合されます。
「null」だった場合は、「null」という文字列になります(「NullPointerException」例外は発生しない)。
また、「+=」演算子も同様に使うことができます。

String str = "ABC" + 123; // "ABC123"という文字列になる
Integer val = null;
str += val; // "ABC123null"という文字列になる

その他の演算子

カッコは、優先順位の低い演算子による演算を優先して行う場合に使います。

int a = 3 + 5 * 2; // aには13が代入される
int b = (3 + 5) * 2; // bには16が代入される

また、メソッドやコンストラクタを呼び出すときにも使います。

Date d = new Date();
long l = d.getTime();

「.」演算子は、メソッドの呼び出し元やフィールドを保持するクラス、インスタンスを指定するために使います。

String str = "ABC";
System.out.println(str.substring(1));
System.out.println(Integer.MAX_VALUE);

「[]」は、配列の何番目のデータを使うかを指定したり、配列のサイズを指定したりするために使います。

int[] values = new int[3];
values[0] = 100;

「instanceof」演算子は、インスタンスの型を判定するのに使います。
左オペランドのインスタンスが右オペランドのクラスにキャスト可能な場合、「true」を返します。

void castMethod(Object obj) {
    if (obj instanceof Integer) {
        Integer val = (Integer) obj;
        System.out.println(val);
    }
}

演算子の優先順位

Javaでは、複数の演算子を一度に使うことができます。基本的に左側から順に処理していきますが、演算子には優先順位があり順序が入れ替わることがあります。
演算子の優先順位は、次のようになっています(記載順で優先順位が高い)。

  • ()、[ ]、 .
  • ++、 −−、 〜、 !
  • *、 /、 %
  • +、 −
  • <<、 >>、 >>>
  • 、 >=、 <、 <=、 instanceof

  • ==、 !=
  • &
  • ^
  • &&
  • ||
  • ?:(条件演算子)
  • =(代入演算子)

なお、代入演算子だけは、右側から処理していきます。

int a = b = c = 100; // a, b, c すべてに100が代入される

定数の計算式は実行時ではなく、コンパイル時に計算されます。文字列を「+」演算子で結合したときも同様です。

int i = 1 + 2 + 3 * 4;
String s = "Hello" + " " + "World";

上記の例は、次の記述と同じことです。

int i = 15;
String s = "Hello World";

if文

処理を分岐させたいときには、「if」文を使います。

if (評価式)
    (評価式の値がtrueだった場合に行う文);
if (評価式)
    (評価式の値がtrueだった場合に行う文);
else
    (評価式の値がfalseだった場合に行う文);

評価式の値は、必ず、boolean型でなければいけません。
boolean型の変数、戻り値がboolean型のメソッド呼び出し式、論理演算式などを記述します。
評価結果が「true」の場合は評価式の次の文を実行し、「false」の場合はその文をスキップして次の処理に移ります。
「else」を使うと評価結果が「false」だった場合の文も記述できます。
複数の式の評価を行いたい場合は、「かつ」を表す「&&」演算子や、「または」を表す「||」演算子を使います。
複数の処理を記述したい場合は、文をブロックにします。

if (評価式) {
    (評価式の値がtrueだった場合に行う文1);
    (評価式の値がtrueだった場合に行う文2);
} else {
    (評価式の値がfalseだった場合に行う文1);
    (評価式の値がfalseだった場合に行う文2);
}

処理が1つの場合にブロックを使わずに記述することを、「ぶら下がり文」といいます。
ただし、ぶら下がり文を使うと、少し複雑な「if」文で勘違いを起こしやすくなるので注意してください。
たとえば、次のコードで「c = a + b」の処理が行われるのは「a >= 0」の場合ではなく、「a< 0」かつ「b >= 0」の場合です。

if (a < 0)
    if (b < 0)
        c = 0;
else
    c = a + b;

else if

「ぶら下がり文」を使ってelseの後ろに、さらに「if」文を記述することもできます。
次の2つの「if」文は、同じ処理を行います。

// elseの中にif文を記述
if (a >= 100) {
    System.out.println("100以上");
} else {
    if (a >= 50) {
        System.out.println("50以上100未満");
    } else {
        System.out.println("50未満");
    }
}
// 「else if」を使って記述
if (a >= 100) {
    System.out.println("100以上");
} else if (a >= 50) {
    System.out.println("50以上100未満");
} else {
    System.out.println("50未満");
}