2016年度 発展プログラミング演習 第2講 Javaの基礎

概要

目標

C言語との比較

C言語と同じところ

C言語との違い

C言語とJava言語は,一見,似たような書き方を行いますが,考え方がまったく異なります. 考え方についてはこの講義の範囲外となりますので,詳しくは述べませんが, C言語とは別の言語である,という感覚は忘れずにこの講義に臨んでください.

制御構文

条件分岐

条件分岐は,ifもしくは,switchを用います. 文法はC言語と同じです. if文の例を次に示します.

if(条件式1){
    // 条件式1が成り立った時に実行される式.
}
else if(条件式2){
    // 条件式2が成り立った時に実行される式.
}
else{
    // 条件式1も,条件式2も成り立たなかった時に実行される式.
}

ただし,C言語と異なる点が1点だけ存在します.それは条件式に許される型です. Java言語では,条件式に許される型はBoolean型のみです. Boolean型はtrueもしくはfalse のみが値として許される型です.次のプログラムで確認してみましょう.

public class ConditionExample{
    public static void main(String[] args){
        Boolean value1 = true;
        Boolean value2 = false;
        if(value1){
            System.out.println("value1がtrueであるため,この部分が実行される.");
        }
        if(value2){
            System.out.println("value2はfalseであるため,実行されない.");
        }
        else{
            System.out.println("value2はfalseであるため,elseの方が実行される.");
        }
    }
}

Boolean型の導入により,=== に書き間違えるエラーはコンパイルの段階で指摘されるようになりました.

論理演算もC言語と同じです.条件式1と条件式2を両方満たす場合(ANDの場合)は, 条件式1 && 条件式2です. 一方のみが満たされていれば良い場合(ORの場合)は, 条件式1 || 条件式2です. また,条件式の結果は,Booleanになりますので, Boolean flag = 条件式1 && 条件式2;のような書き方も可能です.

例題

うるう年の判定

うるう年判定のフローチャート

与えられた年がうるう年であるか否かを判定しましょう. うるう年は,次のように判定します.

右図は,判定手順をフローチャートで表現した図です. この図や上記の条件を参考に,下のプログラムの判定部分を作成しましょう. leapYearと言うBoolean型の変数に, 判定結果をtrueもしくはfalseとして代入しましょう.

public class LeapYear{
    public static void main(String[] args){
        Integer year = 2016;
        Boolean leapYear = false;
        // ここに判定処理を書いていく.

        if(leapYear){ // leapYearがtrueの場合.
            System.out.printf("%d年はうるう年です.%n", year);
        }
        else{
            System.out.printf("%d年はうるう年ではありません.%n", year);
        }
    }
}

繰り返し

繰り返しも基本的には,C言語と同じく,forwhiledo whileが利用できます. forにおけるループ制御変数をfor文内でかけるようになりました. つまり,次のような書き方ができるようになりました.

for(Integer i = 0; i < max; i++){
    // 繰り返したい処理をここに書く.
}

if文の時と同じく,継続条件に入れられる型はBoolean型のみです. ですから,while(1)のような無限ループはコンパイルエラーとなります. 無限ループにしたい場合は,while(true)でなければいけません.

ループを途中で抜けたい場合は,breakが利用できます. 一方,ループ内の残りの処理を取りやめ,次のループを実行したい場合には, breakの代わりにcontinueが利用できます. breakcontinueのどちらも,実行されると, 即座に一番内側のループから抜けます.

2重,3重のループを抜けるには,ラベルを利用できますが,それよりも,ループ自体を関数に起き, returnで返すことが多いです.

例題

素数を判定する

素数判定のフローチャート

数値が与えられた時にその数値が素数であるか否かを判定しましょう. 素数とは,1とその値以外で割り切れない数のことです. 以下のプログラムを参考に素数を判定するプログラムを作成しましょう. 完成すれば,numberに代入される値を変更して,何度か試してみましょう.

public class PrimeCheck{
    public static void main(String[] args){
        // 素数判定を行いたい数値(n).
        Integer number = 133;
        Boolean prime = true;  

        // for文で繰り返す.
        // ・カウンタの初期値はいくつにすれば良いでしょうか.
        // ・カウンタの加算はいくつにすれば良いでしょうか.
        // iでnが割り切れるか判定する.

        if(prime){ // primeの値がtrueの場合.
            System.out.printf("%dは素数である.%n", number);
        }
        else{
            System.out.printf("%dは素数ではない.%n", number);
        }
    }
}

素数列を表示する

素数を判定するで作成したプログラムを変更し, 1から指定された数までのすべての素数を列挙するプログラムを作成しましょう. 基本的には,上のプログラムの処理全体をループで囲み,指定された値まで繰り返せば良いはずです.

変数とメソッド(関数)

変数とメソッド(関数)の定義場所

Javaでは,変数も関数も一番外側の class Xxxx{ } の波括弧の外側に書くことは許されていません. どのような変数であっても関数であっても,必ず クラス宣言の内側で宣言しなければいけません. 逆にいえば,グローバル変数はJavaでは存在できません. クラス宣言の外側で変数を定義できないためです.

クラス宣言の模式図 クラス宣言のすぐ内側にメソッドと変数の両方が定義できます. もちろん,変数もメソッドも複数個並べて定義できます. 右図のように,クラスには,複数のメソッド,複数の変数が含まれています. クラスに所属する変数のことをフィールドと呼びます. 一方,メソッド内にも変数を宣言できます.こちらの変数は, ローカル変数と呼ばれます.

フィールドとローカル変数は明確に区別されます.フィールドは初期値が指定されない場合, nullが暗黙的に代入されます. 一方のローカル変数は,値が代入されない場合は,コンパイルエラーが起こります. 次のプログラムをコンパイルして,エラーの内容を確認してみましょう.

public class NotAssignedVariables{
    // 値が代入されないフィールド.
    Integer notAssignedField;
    public void run(){
        // 値が代入されないローカル変数.
        Integer notAssignedVariable;

        System.out.println("フィールド: " + notAssignedField);
        System.out.println("ローカル変数: " + notAssignedVariable);
    }
}

変数,メソッドの名前

Java言語では,変数の名前のつけ方はキャメルケース が推奨されています.キャメルケースとは,英語の単語の区切りのスペースを削除し, 単語の先頭の文字を大文字にする命名方法です. 例えば,FileInputStreamは通常の書き方をすれば,file input streamです. これをつなげて書くことで,FileInputStreamという名前になります.

一方,一番先頭の単語の先頭文字が小文字の場合,大文字の場合があることに気づきましたか? fileInputStreamFileInputStreamの違いです. 名前を利用する場所でこれらを使い分けています. 変数名,メソッド名は小文字で始まるキャメルケース(lowerCamelCase), クラス名は大文字で始まるキャメルケース(UpperCamelCase)が推奨されています. また,Javaでは,countをcntのように略すのは,よくないとされています. 単語は略さずに使うようにしましょう.

変数

変数の型

JavaはC言語とは異なり,非常に多くの型が存在します. そのため,変数の型を意識しなければプログラムが理解できません. 変数には,必ず型と名前,値が存在し,型と名前は変数の定義時に必ず指定されます. 変数の定義は,C言語と同じく,型 名前のように型を指定して名前を指定します. このように定義した後でなければ変数は利用できない点もC言語と同じです. また,値は最初に代入された値が初期値となります.値が代入されない場合,基本的には nullが初期値として指定されたことになります(例外もあります).

public class Variables{
    Integer number = 133;
    String value = "124";
    Boolean flag = false;
    Double doubleValue;
    Random rand = new Random();
}

上記プログラムには,5つの変数が定義されています.それぞれの型,名前,値は何でしょうか.

変数の値

変数には,型に適した値しか入れられないことに注意しましょう. 例えば,上記の133Integer型の値であるため, 他の型の変数には代入できません.

一方,Random型の変数randに代入されている値を見てみましょう. new Random()のように,キーワードnewとともに記述されています. このnew Random()という記述は, Random型の実体(値)を生成することを表しています. C言語風に言えば,構造体のようなものをmallocを使ってメモリ領域を確保しています.

このように生成された実体(値)のことをRandom型のオブジェクト,略して Randomオブジェクトと呼びます. new ArrayList()と書かれていれば,ArrayListオブジェクト, new HashMap()であれば,HashMapオブジェクト,です.

Javaでは,複雑なデータ型を大量に扱います.そのため,new を用いてオブジェクトを作成することは今後頻繁に出てきますので,このような表現に慣れていきましょう.

変数のスコープ

また,変数には,それぞれ有効範囲が存在します.この有効範囲のことを 変数のスコープと呼びます. このスコープの範囲外で変数にアクセスしようとすると「シンボルが見つかりません」 というコンパイルエラーが発生します.

public class VariableScope{
    String scopeInObject;
    void method1(){
        String scopeInMethod1;
        for(Integer i = 0; i < 10; i++){
            String scopeInStatement1;
        }
    }
    void method2(){
        String scopeInMethod2;
        for(Integer i = 0; i < 10; i++){
            String scopeInStatement2;
        }
    }
}

例えば,左図のプログラムの各変数のスコープを確認しましょう. 2行目に定義されている変数scopeInObjectは, このプログラムのどこからでもアクセスできます. 一方,4行目の変数scopeInMethod1は,method1内であればアクセス可能です. しかし,method2からはアクセスできません. また,scopeInStatement1iは,for 文の中だけで有効な変数です.for文の外側でアクセスしようとしても, スコープが異なるためアクセスできません.

つまり,変数は,宣言した場所以降かつ,その波括弧内でしか有効ではありません. この有効範囲を即座に確認するためにも,しっかりと インデントを行いましょう.

一方で,変数名が重複していればどうなるでしょうか. スコープが重なっていなければ名前の重複は問題ありません. 左図のように,5行目と11行目で変数iが重複しています. しかし,2つのiの有効範囲は重なっていません.そのため,両方とも有効な変数となります.

メソッド

メソッドの定義

メソッド定義の意義

Java言語では,C言語以上にメソッド(関数)の役割が重要になってきています. 1〜3行程度の処理しかないメソッドもたくさんあります. それぞれのメソッドに名前をつけて,処理の内容をコメントに頼らずにわかりやすくする意図があるためです. 例えば,下の2つの処理を見比べてみてください.

public class Theorem1{
    void run(){
        Double x = 3.0, y = 4.0;
        Double z = Math.sqrt(x * x + y * y);
    }
}
public class Theorem2{
    void run(){
        Double x = 3.0, y = 4.0;
        Double z = pythagoreanTheorem(x, y);
    }
    Double pythagoreanTheorem(Double x, Double y){
        return Math.sqrt(x * x + y * y);
    }
}

両方とも,処理内容自体は同じです.Math.sqrt の一行をメソッドとして抜き出しているか否かの違いだけです. しかし,メソッドとして抜き出し,名前をつけることにより, 自然と,処理が説明できるようになりました. このようにプログラム自体にプログラムの説明をさせるためにも, メソッドを細かく分解することが,オブジェクト指向の流儀です.

メソッドの定義方法

public class DefineMethodExample{
    返り値の型 メソッド名(引数列){
    }
}

メソッドを定義するためには,メソッドの返り値引数,そして,メソッドの名前を決めなければいけません. また,すべてのメソッドは,必ずクラス宣言の内側で定義されます.例外はありません. それらが決まれば,左図のように書くことで,メソッドを定義できます.

public class DefineMethodExample{
    // 引数なし,何も返さないメソッドの定義方法
    void method(){
    }
    // 下の2つの書き方は許されておらず,
    // コンパイルエラーとなる.
    method(){
    }
    Integer method(void){
    }
}

何も返さないメソッドの返り値の型はvoidであり,省略できません. また,逆に,受け取る引数のみを記載し,引数を受け取らない場合は,引数列には何も書いてはいけません. 例えば,引数も返り値もないメソッドmethodは,左図のような定義になります. コメントにもあるように下の2つの書き方はJavaでは許されておらず, 「無効なメソッド宣言」というコンパイルエラーとなります.

返り値の型に制限はなく,どんな型であっても返せます.たとえ,自分で定義した型であってもです.

メソッドの呼び出し

メソッドを呼び出すとき,C言語と大きく異なる点があります. Javaではすべてのメソッドを,実体を経由して呼び出す必要がある点です. すべてのメソッドはクラス宣言の内側に定義しなければいけないと上で述べました. そして,newというキーワードを利用し,実体を作成しました. その実体を経由してしかメソッドは呼び出せません.

Random rand = new Random();
Integer value = rand.nextInt(100);

例えば,前回の練習問題で乱数を発生させました(左図を参照).乱数を発生させるには,まず Randomオブジェクトをnew Random()を実行して作成します. その後,Randomオブジェクトが代入されている変数rand を経由してnextIntメソッドを呼び出しています.

Javaではこのように,ほぼすべてのメソッドがオブジェクトを経由して実行されます. 例えば,System.out.printlnも,Systemクラスのフィールド変数 outを経由してprintlnメソッドを呼び出しています. outは変数名であり,その型はPrintStreamです (APIドキュメントより).

public class MethodCallExample{
    void caller(){ // 呼び出し元.
        this.callee();
    }
    void callee(){ // 呼び出し先.
        // なんらかの処理.
    }
}

では,変数として表せないようなメソッドを呼び出したい場合は,どうやって呼び出せば良いのでしょう. 例えば,左のプログラムにおいて,callerメソッド内でcallee メソッドを呼び出したいとします.そのような時は,thisというキーワードを用いて, this.callee();と書くことで,calleeメソッドが呼び出されます. では,なぜMethodCallExampleオブジェクトを作成してcallee メソッドを呼び出さないのでしょうか(MethodCallExample example = new MethodCallExample(); example.callee();).

callerが実行されているということは,すでに実体が作成されています. 上記のように,MethodCallExampleオブジェクトを作成して calleeメソッドを呼び出スト,別の実体を作成して呼び出すことになります. このような処理も時には必要ですが,単純に,今使われている実体をそのまま使いたい場合の方が多いでしょう. そのような時,今使われている実体を参照する変数として,thisが用意されています. thisは宣言不要で,メソッド内部で利用できます. ただし,mainメソッドでは利用できません.

その他

コメント

Javaも,C言語と同じように,コメントが書けます. コメントの記述方法は,3種類存在します.

1行コメント

// これ以降改行までがコメントとして扱われる.
// 途中で//が入っていても,改行までがコメント.

1行コメントと呼ばれるコメントは,左図のように,スラッシュを2つ重ねます. このコメントは,左図のように,開始から改行までがコメントとして扱われます. 途中でどのような内容が含まれていたとしても,コメントとして扱われます.

多くの場合,メソッド中の処理の簡単なメモとして利用されます.

通常のコメント

/* 開始から終了までがコメントとして扱われる.
C言語と同じ形式. */

標準的なコメントの書き方です.C言語の書き方と同じです. このコメントの中には,通常コメント,Javadocコメントを入れ子にはできません.

メソッド中の複数行に渡る説明の場合,用いられることが多いです.

Javadocコメント

/**
 このように,最初の通常のコメントの開始のスターが
 2つ重なった場合,Javadocコメントとして扱われます.
 */

このコメントは,クラス宣言の前,メソッド,フィールド宣言の前に書かれます. javadocというプログラムから利用されるためのコメントです. Javadocとは,プログラムからドキュメントを生成するためのコマンドです. このコメントには,一定の書き方がありますが,書き方については, この講義の範囲外ですので,興味のある人は調べてみてください. この授業では,このような書き方がある,という理解で十分です.

演習問題

クラス定義の基本形

練習問題の前に,クラス定義の基本形を覚えておきましょう. これから毎回様々なプログラムを書いていきます. クラス名が指定された時に基本的な形が即座に書けるかどうかで習熟度が格段に変わってきます. そのため,以下の基本形をしっかりと覚えておきましょう.

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

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

この段落はこの授業の範囲外ですが,publicstatic の説明を行います.この説明は今のところは理解できなくても問題ありません. public可視性 を表しており,誰がそのメソッド,フィールドにアクセスできるのかの権限を表しています. 一方のstaticは静的なメソッドを表しており, オブジェクトを作成せずにメソッドを呼び出せてしまいます. どのオブジェクトに対してメソッドを呼び出すのかという関係を壊してしまいます. 使いどころはありますが,本講義の範囲を大幅に超えますので, 本講義においては,staticを付けたメソッドは, main以外には使わないと思ってください.

1. フィールドの定義

次の指示に従ったプログラムを作成しましょう.

  1. クラス名をFieldAccessとします.
  2. クラス定義の基本形を参考に, mainメソッドからrunメソッドを呼び出します.
  3. String型のフィールド変数nameを定義します.
  4. runメソッド内で次の処理を行ってください.
    1. nameに値を代入してください.代入する値は自分の名前とします. 代入する値が文字列であることに注意してください.
    2. nameSystem.out.printlnを利用して出力してください.

2. メソッドの定義

次の指示に従ったプログラムを作成しましょう.

  1. クラス名をMethodAccessとします.
  2. クラス定義の基本形を参考に, mainメソッドからrunメソッドを呼び出します.
  3. greetメソッドを作成し,runメソッドから greetメソッドを呼び出しましょう.
    • greetメソッドの返り値はありません(voidです).
    • 引数はString型の値を受け取ります.
    • 引数として渡す値は自分の名前としてください.
  4. greetメソッド内では,次の処理を行ってください.
    1. 文字列HelloSystem.out.printを利用して出力しましょう.
    2. 引数として受け取った名前をSystem.out.printlnを利用して出力しましょう.

3. 階乗

public class Factorial{
    void run(){
        Integer number = 10;
        Integer factorialValue = // factorialメソッドの呼び出し.
        System.out.printf(
            "%d! = %d%n", number, factorialValue
        );
    }
    Integer factorial(Integer number){
        // numberの階乗を計算して返す.
    }
    // mainメソッドは省略.
}

左図のプログラムは階乗を求めるプログラムです. 左図のコメント部分のプログラムを書きましょう. $n$の階乗の計算方法は次の通りです.

\[ n! = n \times (n - 1) \times (n - 2) \cdots 2 \times 1 \]

factorialメソッド内では,繰り返しを用いて,階乗の値を求めましょう. まず,計算結果を保持する変数resultを用意しましょう. 次に,1からnumberまで繰り返します. 繰り返しの中では,resultに対して,iを乗算すれば良いですね. resultの初期値は何が良いかは考えてみましょう. 先週の練習問題,総和を求めるが参考になるでしょう.

完成すれば,別の値の階乗を求めましょう.

4. Fibonacci数列.

次の漸化式で表されるFibonacci数列を20項目(20項目も出力項目に含む)まで表示してみましょう. \[ F_i = \begin{cases} 0 & i=0\\ 1 & i=1\\ F_{i-2}+F_{i-1} & i\geq2 \end{cases} \]

  1. クラス名は Fibonacciとします.
  2. クラス定義の基本形を参考に,mainメソッドからrunメソッドを呼び出します.
  3. printFibonacciSeriesメソッドを用意します. 返り値はvoid,引数はInteger型の変数maxとします.
  4. runメソッドからprintFibonacciSeriesメソッドを呼び出しましょう. 引数は,上にあるように20を渡してください.
  5. printFibonacciSeriesメソッド内で0からmax までのフィボナッチ数列を計算して出力してください.
  6. $n$項目の値を$n$とともに1行で出力してください. System.out.println(i + ": " + value);のように 文字列を+で連結できます. もちろん,System.out.printf("%d: %d%n", i, value);のようにprintfを使っても構いません.

完成すれば,最大値を変更して再度実行してみましょう. また,特定の範囲の項のみを出力するにはどのようにすれば良いかを考えてみましょう.

5. FizzBuzz

FizzBuzzというプログラムを作成します. FizzBuzzは1から特定の値までの数値を順に出力します. ただし,3の倍数の場合は数値の代わりにFizzという文字列を,5の倍数の場合はBuzzという文字列を, 3と5の公倍数の場合はFizzBuzzという文字列を数値の代わりに出力します. ここでは,1から100までの間で上記のようになるようにプログラムを書いていきましょう.

  1. クラス名は FizzBuzzとする.
  2. クラス定義の基本形を参考に, mainメソッドからrunメソッドを呼び出します.
  3. printFizzBuzzメソッドを用意します. 返り値はvoid,引数はInteger型の変数maxとします.
  4. runメソッドからprintFizzBuzzメソッドを呼び出しましょう. 引数は上にあるように100とします.
  5. printFizzBuzzメソッド内でループ制御変数i を用いて1からmaxまで繰り返す.
  6. 繰り返し内で,iが次の条件のときに適切な表示を行う.
    1. iが3で割り切れるとき,Fizzと表示する.
    2. iが5で割り切れるとき,Buzzと表示する.
    3. iが3でも5でも割り切れるとき,FizzBuzzと表示する.
    4. それ以外のとき,iの値を表示する.

完成すれば,最大値を変更して再度実行してみましょう. また,特定の範囲の項のみを出力するにはどのようにすれば良いかを考えてみましょう.

6. 基数変換

10進数で与えられた数値を特定の進数で表現してみましょう. 基数radixと値valueは正数が与えられるものとし, radixの最大値は16とします.

  1. クラス名をConversionとする.
  2. クラス定義の基本形を参考に, mainメソッドからrunメソッドを呼び出します.
  3. 基数radixInteger型としてフィールドとして定義してください. 初期値は16とします.
  4. convertメソッドを用意してください. 引数はInteger型のvalue, 返り値の型はStringとします.
  5. runメソッドの処理を次のようにしてください.
    1. convertメソッドを呼び出します.引数は適当な値としてください.
    2. convertメソッドの返り値を適当な変数を定義して,代入してください.
    3. convertメソッドの返り値を出力してください.
  6. convertメソッドの処理を次のようにしてください.
    1. String型の変数resultを宣言します. また,初期値として空文字("")を代入します.
    2. valueが0より大きい場合に繰り返すループを作成します.
      1. valueradixで割った余りを Integer型の変数remainderに代入してください.
      2. remainderを元にresultを更新します. 文字列は+で連結できます. result = remainder + result;とすることで, 下の桁から順に計算できるようになります.
      3. ただし,radixが10より大きい場合,remainder が10を超えることもあります.そのため,remainderが10以上の場合は, "a"から"f"になるようにしましょう. switchremainderの値の場合分けを行います. remainderが15の時はresult = "f" + result;, 14の時はresult = "e" + result;,...のようにします. defaultをどのように使うかを考えること.
      4. ループ処理の最後にvalueradixで割った値を代入します. value = value / radix;とし,ループの先頭に戻ってください.

完成すれば,変換する数値や基数を変えて実行してみましょう.

まとめ

本日学んだ内容は次の通りです.