第1講 Java言語の基礎2

この講で利用するプログラム

第1講 Java言語の基礎2のサブセクション

コメント

Javaのコメントは3種類の書き方ができます.

一行コメント

// 以降改行までがコメントとして扱われます.

Integer answer = 1 + 1; // この部分がコメント.
// 改行すると別のコメントとして扱われるため再度コメント記号が必要

通常コメント

/**/ で囲まれた部分がコメントとして扱われます. C言語と同じスタイルです.

このコメントを入れ子にすることはできません.

/* この部分がコメントです. */
/* この部分がコメントです.
   複数行に分かれていてもOK! */
Integer answer = 1 + 1;
/* この部分がコメントです.
   Integer answer = 1 + 1; // 途中に1行コメントが含まれていてもOK
*/

Javadoc コメント

/***/ で囲まれた部分がコメントとして扱われます. 通常コメントと同じような書き方ですが,このコメントが書ける場所は次の3つに限られています. 次の3つ以外の場所にJavadocコメントが書かれると通常コメントとして扱われます.

  • クラス宣言の前,
  • メソッド宣言の前.
  • フィールド宣言の前.
    • フィールド宣言とは,クラス宣言の内側,かつ,メソッド宣言の外側で定義された変数を指します.

javadoc コマンドにより,ソースファイルからドキュメントを生成できますが,その時にドキュメントに掲載する文章をこのコメントで記します. この授業では扱いません.

/**
 * クラスに対するJavadocコメント.
 * このクラスの役割などを書く.
 */
public class SampleClass{
    /**
     * フィールドに対するJavadocコメント.
     * このフィールドの役割などを書く.
     */
    String stringField = "string"; // フィールドの宣言

    /**
     * メソッドに対するJavadocコメント.
     * このメソッドの役割などを書く.
     */
    public static void main(String[] args){
        // 何らかの処理.
    }
}

クラス定義

クラス定義の基本形

まずは,以下のクラスファイルの基本形を覚えておきましょう. GivenClassName は与えられたクラス名に置き換えてください. run メソッド内に指定されたプログラムを書いてください. main メソッドは返り値の型の前にpublic staticというキーワードがついていますが,これらのキーワードは今のところは mainのみにつけるものと思ってください.

public class GivenClassName{
  void run(){
    // ここに課題内容のプログラムを書く.
  }
  public static void main(String[] args){
    GivenClassName application = new GivenClassName();
    application.run();
  }
}

ここで注目してもらいたいのは,6行目,GivenClassName application = new GivenClassName();と, 7行目のapplication.run(); です.

6行目のnew GivenClassName()GivenClassName という型の実体を作成し, GivenClassName 型の変数application に代入しています. そして,7行目のapplication.run() は,変数application に対して, run メソッドを呼び出しています.

C言語とは異なり,Java言語では,メソッドはどこかの型の実体に必ず所属しています. そのため,メソッドを呼び出すときは,どの実体に対して呼び出すのかを明示しなくてはいけません.

なお,publicについては,FAQ可視性とは何ですかを参照してください.

new演算子

先ほど,new で実体を作成したと言いました. 次のプログラムは今まで書いてきたプログラムの例です.

Integer value1 = 3;
Integer value2 = 5;
Integer result = value1 + value2;
System.out.println(result);

実は,このプログラムは,様々なものが省略されています.省略せずに書くと,次のようなプログラムになります.

Integer value1 = Integer.valueOf(3);
Integer value2 = Integer.valueOf(5);
Integer result = Integer.valueOf(value1 + value2);
System.out.println(result);

Integer.valueOfnew Integerと同じような処理と思ってください. このように,実は,実体を作成して変数に代入しています. Java言語では,ほぼ全てこのように実体を作成しなければ変数に代入できないことを覚えておいてください.

ヒント

実体を作成するには,基本的には new演算子を利用し,new 型名とします. しかし,Integer型は,Java 9 からはnew演算子での実体の作成が非推奨となりました(コンパイル時に警告が出るようになります). 代わりに Integer.valueOfメソッドを利用します. 上記のプログラムは,以下のように書き換えることで,将来的にも利用できるプログラムとなります.

Integer value1 = Integer.valueOf(3);
Integer value2 = Integer.valueOf(5);
Integer result = Integer.valueOf(value1 + value2);
System.out.println(result);

変数

宣言場所

変数は宣言する場所によって2種類の呼び方があり,特徴も少し異なります. 一つは,メソッド内で宣言する変数,ローカル変数(local variable), もう一つは,メソッドの外で宣言する変数,フィールド(field)です. メソッドもフィールドもどちらも,クラス宣言の直下になければいけません(クラス宣言の括弧({ })のすぐ内側).

  • フィールド(field)
    • 初期値は null(何も参照していないことを表す予約語).
    • 変数名.フィールド名でアクセスできる.
    • 有効範囲は,そのフィールドが宣言されているクラスの実体が有効な範囲.
      • クラスの実体の有効範囲と同じ.
      • クラスの実体の有効範囲は,そのクラスがフィールド,ローカル変数のどちらで宣言されたかによって異なる.
  • ローカル変数(local variable)
    • 初期化されなければコンパイルエラーとなる.
    • 有効な時間は,そのメソッド内のスコープ内のみ.
      • その変数が宣言された箇所以降かつ,宣言された場所の一番内側の括弧内({ })のみ.

プログラムを書くとき,変数の有効範囲はできるだけ狭く,というのが定石です. そのため,基本的には,ローカル変数を使い,ローカル変数で対応できない場合に,フィールドを利用しましょう.

実体

実体を確認するために,次のプログラムを考えてみましょう.

Date は日付を表す型です.new Date(), もしくは,new Date(year, month, day, hour, minute) という命令で実体を作成します.与える引数の型は全て Integer です. ただし,年月日,時間を指定する場合,年は1900年からの経過年数, 月は0から始まる(0が1月を表す)点に注意してください. 古い時代のもので,互換性のために残されているもので,変な仕様ではありますが,これで確認してみましょう.

また,コンパイル時に「非推奨のAPIを使用または...」という注意が出ますが,無視してください. これはコンパイルエラーではなく,注意(Warning)ですので,コンパイルはできています. お勧めできない古い時代のライブラリを使っている時に出ます.基本的には使わない方が良いのですが, 今回は説明のために用います.

なお,Date型を利用するときは,import java.util.Dateクラス宣言の前に必要になります.import文が必要な理由は, FAQ import文とは何かを参照してください.

実際に,入力して,どのような実行結果になるかを考えてから,動かしてみましょう. そして,予想との違いを考えてください.

import java.util.Date;

public class DateExample{
  void run(){
    // year は 1900年からの経過年数.
    // month は 0 から始まる.
    Date date1 = new Date(115, 8, 29, 9, 0);
    Date date2 = date1;
    System.out.printf("date1: %s%n", date1);
    System.out.printf("date2: %s%n", date2);

    // Year を +1 している.
    date1.setYear(date1.getYear() + 1);
    System.out.printf("date1: %s%n", date1);
    System.out.printf("date2: %s%n", date2);
  }
  public static void main(String[] args){
    DateExample example = new DateExample();
    example.run();
  }
}

Javaの変数はほぼ全てがポインタとなっています. ポインタとは,メモリ領域の特定の場所を指し示すだけの変数です. 上のプログラムの date2 = date1 という命令が実行されると, date2date1 の参照先が代入されたことを表します. その結果,date1date2 は同じ参照先の実体を指し示すようになり, 一方を変更すると(date1.setYear(...);), もう一方も変更されることになります. 下の図をクリックすると図が更新されます.何度かクリックして見て動作を確認しましょう.

値の一致性

基礎プログラミング演習で学習したものとは変数の扱いが異なる場合がありますので,注意してください. Java言語では,全ての変数は,メモリ上のどこかにある実体を参照しているものと思ってください.

参照の一致性

先のプログラムrun メソッドに次のコードを追加して, 実行結果を確認してみましょう.

void run(){
    Date date1 = new Date(115, 8, 29, 9, 0);
    Date date2 = date1;
    date1.setYear(date1.getYear() + 1);
    // 以下のプログラムを追加する.
    Date date3 = new Date(116, 8, 29, 9, 0);

    System.out.println(date1 == date2);
    System.out.println(date1 == date3);
    System.out.println(date2 == date3);
}

各変数が指し示す Date 型変数の値は全て,2016-09-29 9:00 になっているはずですが, なぜ truefalse という違いが現れるのでしょうか. 実は,== は参照先が同じであるかを判定する演算子であり,値が一致するか否かは判定されません. 以下の画像をクリックし,動作を確認してみましょう.

参照の一致性

値の一致性

では,2つの変数の値が一致するかどうかを判定するにはどうすれば良いのでしょうか. それには,Objects型のequalsメソッドを利用します. 先ほどと同じく,runメソッドに次の処理を追加し,実行結果を確認しましょう.

import java.util.Date;
import java.util.Objects;

public class DateExample{
    void run(){
        // ...
        // 次の処理を追加する.
        System.out.println(Objects.equals(date1, date2));
        System.out.println(Objects.equals(date1, date3));
        System.out.println(Objects.equals(date2, date3));
    }
    // ...
}

このようにすることで,値の一致性を確認できます. Javaで比較する時は,参照の一致か,値の一致か,どちらを判定したいのかを区別して比較しましょう.

なお,Objects型を利用するときは,クラス宣言の前に import 文が必要です. import java.util.Objects と書いてください.

import java.util.Date;
import java.util.Objects;

public class DateExample{
    void run(){
        Date date1 = new Date(115, 8, 29, 9, 0);
        Date date2 = date1;
        date1.setYear(date1.getYear() + 1);
        System.out.printf("date1: %s%n", date1);
        System.out.printf("date2: %s%n", date2);

        Date date3 = new Date(116, 8, 29, 9, 0);
        System.out.printf("date3: %s%n", date3);

        System.out.println("参照の一致性");
        System.out.println(date1 == date2);
        System.out.println(date1 == date3);
        System.out.println(date2 == date3);

        System.out.println("値の一致性");
        System.out.println(Objects.equals(date1, date2));
        System.out.println(Objects.equals(date1, date3));
        System.out.println(Objects.equals(date2, date3));
    }

    public static void main(String[] args){
        DateExample app = new DateExample();
        app.run();
    }
}

何も参照していない値null

Java言語における変数は基本的に全て参照であり,メモリ上のどこを指し示しているかを表しています. では,何も指し示していない場合,すなわち,何も代入されていない場合,どのように扱われるのでしょうか. この場合,nullという値が代入されていることになります.

nullは何も参照していない値を表しています. そして,nullが代入されている変数に対して,メソッド呼び出しやフィールドを参照しようとすると NullPointerExceptionという実行時エラーが発生します.

例題

例えば,次のNullPointerExceptionDemoで確認してみましょう.

public class NullPointerExceptionDemo {
    void run() {
        String emptyString  = "";
        String stringString = "string";
        String nullString   = null;

        System.out.println(emptyString.toString());  // => 空文字が出力される.
        System.out.println(stringString.toString()); // => stringという文字列が出力される.
        System.out.println(nullString.toString());   // => NullPointerException
    }

    public static void main(String[] args) {
        NullPointerExceptionDemo demo = new NullPointerExceptionDemo();
        demo.run();
    }
}

実行結果(実行時のエラーメッセージの読み方)

このプログラムを実行すると,次のような出力が得られます(行頭の番号は説明のために追加したものであり,出力されません).

  1: 
  2: string
  3: Exception in thread "main" java.lang.NullPointerException
  4: 	at NullPointerExceptionDemo.run(NullPointerExceptionDemo.java:9)
  5: 	at NullPointerExceptionDemo.main(NullPointerExceptionDemo.java:14)

出力結果の1行目がプログラムの7行目により出力された空文字列(長さ0の文字列)です. そして,出力結果の2行目が,"string"という文字列が出力されています. 出力結果の3行目以降が NullPointerException という実行時エラーの情報です. 3行目が,どのようなエラーが発生したかを表しています(エラーの種類). 続く4行目以降で,どのメソッドを実行している時に,そのエラーが起きたのかを示しています(エラーの発生場所). ここでは,NullPointerExceptionDemo.javaの9行目(runメソッド内)で発生しました(実行結果の4行目). そして,そのrunメソッドはNullPointerExceptionDemo.javaの14行目(mainメソッド内)で呼び出されました,のように読みます.

なお,Java でクラス名やメソッド名などはキャメルケースで書かれます. つまりNullPointerExceptionは null pointer exception(ヌル・ポインタ・エクスセプション)と読み,null参照の例外という意味です.

配列

宣言方法

Java言語にもC言語と同じように,配列が存在します. ただし,宣言の方法はC言語とは異なります.

// C言語は以下のように,変数名に配列を表す記号をつけていた.
int array[];
// Java言語では,型名の方に配列を表す記号をつける.
// Integerの配列型である array を宣言する,と考えてください.
Integer[] array;

配列の長さ

Java言語で配列の長さを取得するには,配列型の変数に .length をつけることで取得できます.

Integer[] array = // 配列の初期化は省略
System.out.println(array.length); // => arrayの長さが出力される

配列の各要素

配列の各要素にアクセスするには,C言語と同じく,array[0]のように書きます. この array[0] の 0 をインデックスと呼びます. 配列の要素は,0から始まり,array.length - 1 のインデックス(配列の長さ - 1)までが配列の有効な範囲です.

配列の範囲外の要素にアクセスしようとすると,ArrayIndexOutOfBoundsException というエラーが発生します.

配列の例

では,次のように,String型の配列であるarray の各要素から図のように文字列が参照されている例を考えましょう.

配列の例

この時,以下の処理を行うと,どのような結果になるか考えましょう.

  1. System.out.println(array.length);
  2. System.out.println(array[1]);
  3. System.out.println(array[5]);
  4. System.out.println(array[6]);

配列の作成方法

この授業では扱いません.基本的に自分で配列を作成することはなく, ライブラリの返り値など,与えられたものの利用のみとします. 配列が必要な場合は,4回目で扱う Listを利用するようにしましょう.

なぜなら,配列は近年のプログラミングからすると機能が乏しく,使いにくいためです. それよりは,配列と同じように扱えながらも,より高機能な機能が提供されている List の使い方を学ぶ方が優意義であるためです.

コマンドライン引数

今まで,main メソッドの引数に String型の配列がついていました. この変数を使うと,コマンドライン引数で受け取った値をプログラムから利用できます.

つまり,ターミナル上でプログラムを実行するとき,後ろに指定した文字列をプログラムで受け取れます.

$ java HogeHoge command line arguments
    # => 上記の command, line, arguments がコマンドライン引数

例題1 ArgsPrinter

次のプログラムで,コマンドライン引数で与えられた値を全て出力してみましょう.

public class ArgsPrinter{
    void run(String[] args){
        // args のインデックスの0番目から最後までを繰り返して,
        // args の各要素を出力してみましょう.
        for(...) {
            // 何番目の要素として,何が配列の要素として格納されているかを確認しましょう.
            System.out.printf("%d: %s%n", ...);
        }
    }
    public static void main(String[] args){
        ArgsPrinter printer = new ArgsPrinter();
        printer.run(args);
    }
}

実行例

入力できれば,上記のプログラムを実行して実行結果を確認してください. 実行する時,コマンドライン引数をいくつか与えてみてください.

$ java ArgsPrinter
$ java ArgsPrinter abc def ghi
$ java ArgsPrinter abcdefg
$ java ArgsPrinter 0 1 2 3 4 5 6 7 8 9

このように,run メソッドが引数を受け取るようにすることもできます. 必要に応じてコマンドライン引数を run メソッドでアクセスできるようにしましょう.

public class ArgsPrinter{
    void run(String[] args){
        // args のインデックスの0番目から最後までを繰り返して,
        // args の各要素を出力してみましょう.
        for(Integer i = 0; i < args.length; i++){
            // 何番目の要素として,何が配列の要素として格納されているかを確認しましょう.
            System.out.printf("%d: %s%n", i, args[i]);
        }
    }
    public static void main(String[] args){
        ArgsPrinter printer = new ArgsPrinter();
        printer.run(args);
    }
}

例題2 コマンドライン引数とデフォルト値

コマンドライン引数に何か値が与えられた場合はその値を出力し,そうでない場合は"no arguments"を出力しましょう.

public class FindValueInArgs {
    void run(String[] args) {
        String value = // デフォルト値を代入しておく.
        // もしコマンドライン引数に値が存在すれば
        if(...) {
            // コマンドライン引数で与えられた値を value に代入する.
        }
        System.out.printf("args[0]: %s%n", value); 
    }

    public static void main(String[] args) {
        FindValueInArgs app = new FindValueInArgs();
        app.run(args);
    }
}

デフォルト値を args[0] = "no arguments" とできない点に注意してください. 配列は長さを変更できません. つまり,コマンドライン引数が与えられなかった場合,args の長さは 0 ですので,args[0] は範囲を超えたアクセスになります. そのため,別の変数(value)を用意し,そこにデフォルト値もしくは,指定された値を代入しましょう.

実行例

入力できれば,上記のプログラムを実行して実行結果を確認してください. 実行する時,コマンドライン引数をいくつか与えてみてください.

$ java FindValueInArgs
$ java FindValueInArgs hello
$ java FindValueInArgs world
$ java FindValueInArgs hello world

このようにすることで,コマンドライン引数で指定された場合はその値を利用し, そうでない場合はデフォルト値を利用することが可能になります.

public class FindValueInArgs {
    void run(String[] args) {
        String value = "no arguments"; // デフォルト値を代入しておく.
        // もしコマンドライン引数に値が存在すれば
        if(args.length > 0) {
            // コマンドライン引数で与えられた値を value に代入する.
            value = args[0];
        }
        System.out.printf("args[0]: %s%n", value);
    }

    public static void main(String[] args) {
        FindValueInArgs app = new FindValueInArgs();
        app.run(args);
    }
}

練習問題

1. HelloWorld 改

コマンドライン引数で与えられた人に,挨拶しましょう. もし,誰も指定されない場合は,"Hello, World"と出力してください. クラス名は,HelloWorld2 とし,クラス定義の基本形に従ってプログラムを書いてください. コマンドライン引数も参考になるでしょう.

出力例

$ java HelloWorld2 Tamada
Hello, Tamada
$ java HelloWorld2 Sagisaka
Hello, Sagisaka
$ java HelloWorld2
Hello, World

2. HelloWorld 改2

コマンドライン引数で与えられた人に,挨拶しましょう. 基本的には,HelloWorld 改と同じですが,もし, "World"が指定されたら,"Hi, World"と気さくに挨拶してください. クラス名は,HelloWorld3 とし,クラス定義の基本形 に従ってプログラムを書いてください. コマンドライン引数も参考になるでしょう.

出力例

$ java HelloWorld3 Miyamori
Hello, Miyamori
$ java HelloWorld3 Tamada
Hello, Tamada
$ java HelloWorld3 World
Hi, World
$ java HelloWorld3
Hello, World

3. 階乗

コマンドライン引数であたえられた数値の階乗を計算するプログラムを作成してください. $n$の階乗は,$n! = n \times (n - 1) \times (n - 2) … 2 \times 1$ で求められます.

public class Factorial{
    void run(String[] args){
        Integer number = Integer.valueOf(args[0]);
        Integer factorial;
        // number の階乗を計算する.
        System.out.printf("%d! = %d%n", number, factorial);
    }
    // mainメソッドは省略
}

String型からInteger型への変換

なお,String型の変数からInteger型に変換するには,以下のように行います. Factorial の3行目で同じことを行っています.

// Integer max = "15"; // コンパイルエラー.
    // "15"という文字列はそのまま Integer 型に代入できない.
String numberString = "15";
Integer max = Integer.valueOf(numberString);
    // => max には 15 という数値が代入される

従来,String型からInteger型に変換する方法は2つありました.

  • Integer.valueOfメソッドを使う方法.
  • new Integer で実体を作成する方法.

しかし,Java 9 から後者の方法(new Integerで実体を作成する方法)が非推奨のAPIとして指定されました. そのため,Integer.valueOfで行う方法に書き換えました(2018年4月18日).

出力例

$ java Factorial 3
3! = 6
$ java Factorial 4
4! = 24
$ java Factorial 5
5! = 120

4. FizzBuzz

FizzBuzz は1から特定の値までの数値を順に出力します. ただし,3の倍数の時は,数値の代わりに Fizz という文字列を, 5の倍数の時は,数値の代わりに Buzz という文字列を, 3と5の公倍数の時は,数値の代わりに FizzBuzz という文字列を出力します. クラス名は,FizzBuzzとしてください.

コマンドラインから受け取った値まで,上記のルールに従って値を出力してみましょう. もし,コマンドラインで値が指定されなかったときは,15が指定されたものとして処理を進めてください.

出力例

$ java FizzBuzz 3
1
2
Fizz
$ java FizzBuzz
1
2
Fizz
4
Buzz
... 途中省略
13
14
FizzBuzz

5. Fibonacci数列

Fibonacci数列とは,次の漸化式で表される数列です. このFibonacci数列を初項からコマンドライン引数で指定された項まで出力してみましょう. コマンドライン引数が指定されなかった場合は,20項目(20項も出力結果に含む)までを出力するようにしてください. クラス名は,Fibonacciとします.

$$ F_i = \begin{cases} 1 & (i = 1) \\\\\ 1 & (i = 2) \\\\\ F_{i-2}+F_{i-1} & (i \geq 3)\ \end{cases} $$

出力例

$ java Fibonacci 3
1 1 2
$ java Fibonacci 8
1 1 2 3 5 8 13 21
$ java Fibonacci 15
1 1 2 3 5 8 13 21 34 55 89 144 233 377 610
$ java Fibonacci
1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987 1597 2584 4181 6765

まとめ

まとめ