2016年度 発展プログラミング演習 第2講 Javaの基礎
概要
目標
- C言語との違いを知る.
- 変数の型の存在を知る.
- 変数の型を区別できる.
- 制御構文(if文,for文)が書けるようになる.
- 変数のスコープがわかる.
- メソッドが定義できる.
- メソッドを呼び出せる.
- コメントが書ける.
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;
のような書き方も可能です.
例題
うるう年の判定
与えられた年がうるう年であるか否かを判定しましょう. うるう年は,次のように判定します.
- 西暦で表される年が 4 で割り切れたらうるう年である.
- ただし,そのうち,100で割り切れる年はうるう年ではない.
- ただし,さらに,400で割り切れる年はうるう年である.
右図は,判定手順をフローチャートで表現した図です.
この図や上記の条件を参考に,下のプログラムの判定部分を作成しましょう.
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言語と同じく,for
とwhile
,
do while
が利用できます.
for
におけるループ制御変数をfor
文内でかけるようになりました.
つまり,次のような書き方ができるようになりました.
for(Integer i = 0; i < max; i++){ // 繰り返したい処理をここに書く. }
if
文の時と同じく,継続条件に入れられる型はBoolean
型のみです.
ですから,while(1)
のような無限ループはコンパイルエラーとなります.
無限ループにしたい場合は,while(true)
でなければいけません.
ループを途中で抜けたい場合は,break
が利用できます.
一方,ループ内の残りの処理を取りやめ,次のループを実行したい場合には,
break
の代わりにcontinue
が利用できます.
break
,continue
のどちらも,実行されると,
即座に一番内側のループから抜けます.
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
という名前になります.
一方,一番先頭の単語の先頭文字が小文字の場合,大文字の場合があることに気づきましたか?
fileInputStream
とFileInputStream
の違いです.
名前を利用する場所でこれらを使い分けています.
変数名,メソッド名は小文字で始まるキャメルケース(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つの変数が定義されています.それぞれの型,名前,値は何でしょうか.
変数の値
変数には,型に適した値しか入れられないことに注意しましょう.
例えば,上記の133
はInteger
型の値であるため,
他の型の変数には代入できません.
一方,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
からはアクセスできません.
また,scopeInStatement1
やi
は,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
のみにつけるものと思ってください.
この段落はこの授業の範囲外ですが,public
とstatic
の説明を行います.この説明は今のところは理解できなくても問題ありません.
public
は可視性
を表しており,誰がそのメソッド,フィールドにアクセスできるのかの権限を表しています.
一方のstatic
は静的なメソッドを表しており,
オブジェクトを作成せずにメソッドを呼び出せてしまいます.
どのオブジェクトに対してメソッドを呼び出すのかという関係を壊してしまいます.
使いどころはありますが,本講義の範囲を大幅に超えますので,
本講義においては,static
を付けたメソッドは,
main
以外には使わないと思ってください.
1. フィールドの定義
次の指示に従ったプログラムを作成しましょう.
- クラス名を
FieldAccess
とします. -
クラス定義の基本形を参考に,
main
メソッドからrun
メソッドを呼び出します. String
型のフィールド変数name
を定義します.run
メソッド内で次の処理を行ってください.-
name
に値を代入してください.代入する値は自分の名前とします. 代入する値が文字列であることに注意してください. name
をSystem.out.println
を利用して出力してください.
-
2. メソッドの定義
次の指示に従ったプログラムを作成しましょう.
- クラス名を
MethodAccess
とします. -
クラス定義の基本形を参考に,
main
メソッドからrun
メソッドを呼び出します. -
greet
メソッドを作成し,run
メソッドからgreet
メソッドを呼び出しましょう.greet
メソッドの返り値はありません(void
です).- 引数は
String
型の値を受け取ります. - 引数として渡す値は自分の名前としてください.
greet
メソッド内では,次の処理を行ってください.- 文字列
Hello
をSystem.out.print
を利用して出力しましょう. - 引数として受け取った名前を
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} \]
- クラス名は
Fibonacci
とします. -
クラス定義の基本形を参考に,
main
メソッドからrun
メソッドを呼び出します. -
printFibonacciSeries
メソッドを用意します. 返り値はvoid
,引数はInteger
型の変数max
とします. -
run
メソッドからprintFibonacciSeries
メソッドを呼び出しましょう. 引数は,上にあるように20を渡してください. -
printFibonacciSeries
メソッド内で0
からmax
までのフィボナッチ数列を計算して出力してください. -
$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までの間で上記のようになるようにプログラムを書いていきましょう.
- クラス名は
FizzBuzz
とする. -
クラス定義の基本形を参考に,
main
メソッドからrun
メソッドを呼び出します. -
printFizzBuzz
メソッドを用意します. 返り値はvoid
,引数はInteger
型の変数max
とします. -
run
メソッドからprintFizzBuzz
メソッドを呼び出しましょう. 引数は上にあるように100とします. -
printFizzBuzz
メソッド内でループ制御変数i
を用いて1からmax
まで繰り返す. -
繰り返し内で,
i
が次の条件のときに適切な表示を行う.i
が3で割り切れるとき,Fizz
と表示する.i
が5で割り切れるとき,Buzz
と表示する.i
が3でも5でも割り切れるとき,FizzBuzz
と表示する.- それ以外のとき,
i
の値を表示する.
完成すれば,最大値を変更して再度実行してみましょう. また,特定の範囲の項のみを出力するにはどのようにすれば良いかを考えてみましょう.
6. 基数変換
10進数で与えられた数値を特定の進数で表現してみましょう.
基数radix
と値value
は正数が与えられるものとし,
radix
の最大値は16とします.
- クラス名を
Conversion
とする. -
クラス定義の基本形を参考に,
main
メソッドからrun
メソッドを呼び出します. -
基数
radix
をInteger
型としてフィールドとして定義してください. 初期値は16
とします. -
convert
メソッドを用意してください. 引数はInteger
型のvalue
, 返り値の型はString
とします. -
run
メソッドの処理を次のようにしてください.convert
メソッドを呼び出します.引数は適当な値としてください.convert
メソッドの返り値を適当な変数を定義して,代入してください.convert
メソッドの返り値を出力してください.
-
convert
メソッドの処理を次のようにしてください.-
String
型の変数result
を宣言します. また,初期値として空文字(""
)を代入します. -
value
が0より大きい場合に繰り返すループを作成します.value
をradix
で割った余りをInteger
型の変数remainder
に代入してください.-
remainder
を元にresult
を更新します. 文字列は+
で連結できます.result = remainder + result;
とすることで, 下の桁から順に計算できるようになります. -
ただし,
radix
が10より大きい場合,remainder
が10を超えることもあります.そのため,remainder
が10以上の場合は,"a"
から"f"
になるようにしましょう.switch
でremainder
の値の場合分けを行います.remainder
が15の時はresult = "f" + result;
, 14の時はresult = "e" + result;
,...のようにします.default
をどのように使うかを考えること. -
ループ処理の最後に
value
にradix
で割った値を代入します.value = value / radix;
とし,ループの先頭に戻ってください.
-
完成すれば,変換する数値や基数を変えて実行してみましょう.
まとめ
本日学んだ内容は次の通りです.
-
制御構文の書き方を学んだ.
- 条件分岐を書いた.
- 繰り返しを書いた.
- フィールドとメソッドの違いを学んだ.
- 変数の型と値,スコープについて学んだ.
- メソッドの定義方法を学んだ.
- メソッドの呼び出し方法を学んだ.
- 暗黙の変数
this
について学んだ. - コメントの書き方を知った.
- クラス定義の基本形を知った.