スクロールしてページをめくる
Computer Science A · AP Computer Science A
値、参照、そしてそれらを使うメソッド
変数は、メモリの中の名前のついた箱です。基本型の変数は値を持ち、参照型の変数はオブジェクトの場所を持ちます。
int score = 42; int copy = score; // copy は独自の42を持つ copy = 7; // score はまだ42のまま String name = "Ada"; // 参照 String other = name; // 同じ参照
図ではこれらの行を実行します。copy = scoreは値42を2つ目の箱にコピーするので、copyが7になっても、scoreは変わりません。nameには文字がまったく入っていません。その箱が持つのは、メモリの別の場所に作られた String オブジェクトを指す矢印(赤)です。other = nameがコピーするのはオブジェクトではなく矢印です。矢印は2本、オブジェクトは1つです。
この違いは Java 全体に、そしてこの章全体にかかわります。変数が何を持つか、メソッドに何が渡され何が返ってくるか、newが何を作るか、そして String が一度作られると決して変わらない理由です。
この章は、AP Computer Science A の単元1「オブジェクトとメソッドの利用」で、選択問題の得点の15〜25%を占めます。内容は、コンパイルとエラー(1.1)、型・式・代入(1.2〜1.6)、ライブラリ・ドキュメント・メソッド(1.7〜1.11)、オブジェクトとその生成(1.12〜1.14)、String(1.15)です。試験の42問の選択問題には Java Quick Reference が付き、使える可能性のあるライブラリのメソッドがすべて載っています。自由記述問題の第1問では String のメソッドを呼び出します。
と3種類のエラー
アルゴリズムは、作業を完了したり問題を解いたりするための、段階的な手順です。言葉で書いても、図にしても、プログラムとして書いてもかまいません。
順次実行は、手順を順番に並べたもので、1つずつ実行されます。Java のメソッドは、流れを変えるものがない限り、文を上から下へ実行します。
コードはどんなテキストエディタでも書けますが、多くのプログラマは統合開発環境(IDE)を使います。IDE には、コードを書き、コンパイルし、実行するためのツールがあります。コンパイラはソースコードの一部のエラーを調べて変換します。Java のコンパイラはバイトコードを作り、それを Java 仮想マシン(JVM)が実行します。コンパイラが見つけたエラーは、プログラムを実行する前に必ず直さなければなりません。
図では、3つのプログラムを順に流します。それぞれに1種類のエラーがあります。
int total = 7 + 3 // ; がない System.out.println(total); 構文エラーは、言語の規則に違反しています。コンパイラが見つける(error: ';' expected)ので、何も実行されません。
int count = 0; System.out.println(10 / count);
実行時エラーは、プログラムの実行中に起こり、たいていはプログラムを異常終了させます。これは例外です。コンパイラが検出しなかったエラーで、通常の実行の流れを中断します(ArithmeticException: / by zero)。
double avg = (7 + 8) / 2; System.out.println(avg); // prints 7.0 論理エラーは、コンパイルも実行もできますが、動作が正しくありません。7 and 8の平均は7.5になるはずです。特定のデータでテストし、期待される結果と比べることでしか見つかりません。(7.0と出力される理由は、1.3のページで扱います。)
int、double、boolean、参照
データ型は、値の集合と、その値に対する演算です。どの型も基本型か参照型のどちらかです。
このコースでは、数とブール値のために、3つの基本型を使います。
| type | values | examples |
|---|---|---|
int | integers | 7, -40, 0 |
double | 実数 | 2.5, 7.0, -0.01 |
boolean | true または false | true |
Java にはほかに5つの基本型(long、short、byte、float、char)がありますが、コースと試験の範囲外です。String のように基本型でないものはすべてオブジェクトで、その型は参照型です。
変数は、値を保持する記憶場所で、その値はプログラムの実行中に変わることがあります。どの変数にも名前と型があり、基本型の変数は、その型の値を持ちます。
int n = 7; double d = 2.5; boolean ok = true; double w = 7; // stores 7.0 int m = 2.5; // コンパイルできない
int は double に収まります。double w = 7;は7.0を格納します。double は int に収まらないので、int m = 2.5;はコンパイルエラーになります。incompatible types: possible lossy conversion from double to int
型は、その値が取りうるものから選びます。生徒の人数は int、価格や平均は double、明かりがついているかどうかは boolean です。
print、println、エスケープシーケンス
System.out.print と System.out.println は、画面に情報を表示します。println はその後でカーソルを次の行に移し、print はカーソルをそのままにします。
リテラルは、固定された値をコードで表したものです。7、2.5、true がそうです。文字列リテラルは、二重引用符で囲んだ文字の並びです。"CSA" がそうです。
引用符の中には、そのまま入力できない文字があります。エスケープシーケンスはバックスラッシュで始まり、特別な意味を持ちます。このコースでは3つを使います。
| エスケープシーケンス | means |
|---|---|
\" | 二重引用符 |
\\ | バックスラッシュ |
\n | 改行 |
System.out.print("AP "); System.out.println("CSA"); System.out.println("say \"hi\""); System.out.print("a\\b\nc"); コンソールには次のように表示されます(図では1文字ずつ入力されます)。
AP CSA say "hi" a\b c 最初の print はカーソルを AP の後ろに残すので、CSA は同じ行に続きます。最後の行では、\\ がバックスラッシュを1つ出力し、\n が文字列の途中で新しい行を始めます。
int の除算、剰余、優先順位
算術式は、数値、変数、演算子 +、-、*、/、% を組み合わせたものです。int 同士なら結果は int、少なくとも1つが double なら結果は double になります。
System.out.println(17 / 5); // 3 System.out.println(17 % 5); // 2 System.out.println(17.0 / 5); // 3.4
int 同士の除算では、商の整数部分だけが残ります。17個のブロックは、5個ずつの組が3組できます。剰余演算子 % は、あまりを返します(2)。除算に double が入ると、結果は商そのものになります(3.4)。(このコースで % を使うのは、a ≥ 0 かつ b > 0 の場合だけです。)
優先順位:*、/、% は + と - より先に適用されます。優先順位が同じ演算子は左から右へ進み、括弧はまとまりを変えます。コンパイラは、コンパイル時にこのように値と演算子をまとめます。
| expression | value |
|---|---|
2 + 3 * 4 | 14 |
(2 + 3) * 4 | 20 |
10 - 4 - 3 | 3 |
7 + 9 % 4 | 8 |
1 / 2 * 4.0 | 0.0 |
1.0 / 2 * 4 | 2.0 |
1 / 2 * 4.0 では、1 / 2 は int の除算で、double が現れる前にすでに0になっています。
int を int の0で割ると、その行が実行されたときに ArithmeticException が投げられます。(double を0で割ることや、そこで生じる特別な double の値は、コースの範囲外です。)
右辺を計算して、左辺に格納する
代入演算子 = は、右側の式を計算して1つの値にし、左側の変数に格納します。
変数は、式で使う前に値を与えなければならず、その値は互換性のある型でなければなりません。最初の代入が変数を初期化します。その後の代入は値を置き換え、古い値は失われます。
int a = 3; // a 3 int b = 8; // a 3, b 8 int temp = a; // a 3, b 8, temp 3 a = b; // a 8, b 8, temp 3 b = temp; // a 8, b 3, temp 3
だから、2つの変数を交換するには3つ目の変数が必要です。temp がなければ、a = b によって、b が受け取る前に a の3が壊れてしまいます。図では、コピーを箱から箱へ動かします。コピー元は値を保ちます。
プログラムが実行されるとき、式は1つの値に評価され、その値には型があります。a + b は int、a + 0.5 は double です。count = count + 1 のような文は方程式ではありません。count を読み、1を加え、結果を書き戻します。
参照変数には、新しいオブジェクトか null を代入できます。リテラルnullは、その参照がどのオブジェクトにも結びついていないことを表します。
a = b = 4; のような、式の中での代入は、コースの範囲外です。
入力には、触覚、音声、視覚、テキストがあります。Scanner クラスは、キーボードから入力されたテキストを読む方法の1つです。試験では、特定の形式のユーザー入力は問われません。
切り捨て、拡張、四捨五入
キャスト演算子 (int) と (double) は、値を一方の基本型からもう一方へ変換します。
double を int にキャストすると切り捨てられます。小数点より後ろの桁が取り除かれ、符号にかかわらず値はゼロに近づきます。
| expression | value |
|---|---|
(int) 3.99 | 3 |
(int) -3.99 | -3 |
7 / 2 | 3 |
(double) 7 / 2 | 3.5 |
(double) (7 / 2) | 3.0 |
7 / 2.0 | 3.5 |
1 + 2.5 | 3.5 |
キャストは、そのすぐ後ろの値に適用されます。(double) 7 / 2 は7をキャストしてから double として割ります。(double) (7 / 2) は先に int の除算をして、その結果(3.0)をキャストします。
int を double に自動的に拡張するコードもあります。1 + 2.5 や、int を double 変数に格納するときです(1.2のページ)。
double を最も近い整数に四捨五入するには、ゼロから遠ざかる向きに半単位だけずらしてから切り捨てます。(int) (x + 0.5)(x が負でないとき)、(int) (x - 0.5)(x が負のとき)です。
| x | (int) x | rounded |
|---|---|---|
2.7 | 2 | 3 |
-2.7 | -2 | -3 |
3.2 | 3 | 3 |
-1.4 | -1 | -1 |
図では、これらをそれぞれキャストします。赤はゼロに向かって切り捨て、青は半分だけずらしてから切り捨てます。
整数のオーバーフローと丸め誤差
int は、決まった量のメモリ(4バイト)に格納されるので、Integer.MIN_VALUE から Integer.MAX_VALUE まで(両端を含む)という範囲があります。
| constant | value |
|---|---|
Integer.MAX_VALUE | 2147483647 |
Integer.MIN_VALUE | -2147483648 |
int n = Integer.MAX_VALUE - 2; // n は2147483645 n = n + 1; // n は2147483646 n = n + 1; // n は2147483647 n = n + 1; // n は-2147483648 n = n + 1; // n は-2147483647
式を評価した結果が範囲外の int になるときは、整数のオーバーフローが起こります。結果は範囲内の int になりますが、期待した値ではありません。MAX_VALUE を1つ超えると MIN_VALUE になります。図では、int を輪にして(縮尺は正確ではありません)、継ぎ目を上に置いています。エラーメッセージは出ません。プログラムは間違った数のまま進みます。
double の限界は別です。double も決まった量のメモリに格納されるので、保持できる精度より細かい結果は、表現できる最も近い値に丸められます。これが丸め誤差です。
System.out.println(0.1 + 0.2); // 0.30000000000000004 0.1も0.2も、2進数では正確に表せず、その小さな誤差が合計に現れます。丸め誤差を避けるには、できるだけ int を使います。お金は、ドルではなくセントで数えます。(それを避ける特別な10進数の型は、コースの範囲外です。)
+=、−=、*=、/=、%=、++、−−
複合代入演算子は、変数の値と右側の値のあいだで計算を行い、その結果を変数に代入します。
score += 5 は score = score + 5 と同じ働きをします。-=、*=、/=、%= も同様で、int に対する /= は整数の除算です。
後置インクリメント演算子 ++ は、数値の変数に1を加えて格納します。後置デクリメント演算子 -- は、1を引きます。
int score = 10; // score は10 score += 5; // score は15 score -= 3; // score は12 score *= 2; // score は24 score /= 5; // score は4 score %= 3; // score は1 score++; // score は2 score--; // score は1
図では各行を実行し、それぞれの値をテープに残していきます。紙の上でも同じようにたどりましょう。1行ごとに、新しい値が1つ決まります。
前置の形(++x)や、ほかの式の中でのインクリメント(arr[x++])は、コースの範囲外です。
自分で書いていないクラスを使う
ライブラリは、クラスの集まりです。アプリケーションプログラミングインターフェース(API)の仕様は、それらの使い方をプログラマに伝えます。
クラスは、参照型を定義します。ライブラリのクラスはパッケージにまとめられています。String と Math は java.lang に、Scanner と ArrayList は java.util にあります。既存のクラスは、コードを見なくてもオブジェクトを作るのに使え、それを可能にするのがドキュメントです。
クラスの属性は、そのクラスに関するデータで、変数に格納されます。String の文字がそうです。クラスの振る舞いは、インスタンスにできること、またはインスタンスに対してできることで、メソッドで定義されます。length() や substring がそうです。
コメントは、元のプログラマや、ほかの人にコードを説明します。コンパイラはコメントを無視し、実行されることもありません。Java のコメントには3種類あります。
// a comment to the end of the line /* a block of comment, over several lines */ /** a Javadoc comment, used to make API documentation */
図では、String を棚から取り出して、substring のドキュメントを開きます。
事前条件は、メソッドが期待どおりに働くために、実行の直前に真でなければならない条件です。メソッドがそれを確かめることは期待されていません。substring を、to が末尾を超える値で呼び出すと事前条件が破られ、丁寧な答えではなく、例外になります。
事後条件は、メソッドの実行後に常に真でなければならない条件です。返される値や、その後のオブジェクトの属性の値など、結果を表します。
パラメータ、引数、戻り値
メソッドは、名前のついたコードのまとまりで、呼び出されたときだけ実行されます。ブロックは、波括弧で囲まれたコードのことです。
手続きの抽象化:メソッドがどう書かれているかを知らなくても、何をするかさえ分かれば使えます。必要なことは、ヘッダーに書かれています。
public static double pow(double base, double exponent)
パラメータは、ヘッダーで宣言され、メソッドの中で使われる変数です(base、exponent)。メソッドシグネチャは、メソッド名と、パラメータの型を順に並べたリストです。pow(double, double) がそうです。パラメータのないメソッドは、リストが空になります。length() がそうです。
引数は、メソッドを呼び出すときに渡す値です。引数は、パラメータと数も順序も一致していなければなりません。引数は値渡しで渡され、各パラメータは引数のコピーとして始まります。図では、2と10が、コピーの2.0と10.0になります。
double r = Math.pow(2, 10); System.out.println(r); // 1024.0 void でないメソッドは、戻り値の型の値を返します。その値を使うには、変数に格納するか、式の中で使います。void メソッドは何も返さないので、式の一部にはできません。int x = System.out.println(5); はコンパイルできません。
同じ名前で、シグネチャの異なるメソッドが複数あるとき、メソッドはオーバーロードされています。Math.abs(int) と Math.abs(double) がそうです。どれが実行されるかは、引数で決まります。
呼び出しは、文の並びを中断します。まずメソッドの文が実行され、最後の文(または return)が実行されると、制御は呼び出しのすぐ後ろに戻ります。
static メソッドと Math.random()
クラスメソッドは、個々のオブジェクトではなく、クラスに属します。ヘッダーにキーワード static があり、クラス名とドット演算子で呼び出します。Math.sqrt(2.0) がそうです。それを定義しているクラスの内側では、クラス名を省略できます。
Math クラスは java.lang にあるので、import なしで使え、クラスメソッドだけを持ちます。Java Quick Reference には5つ載っています。
| method | returns |
|---|---|
static int abs(int x) | int の絶対値 |
static double abs(double x) | double の絶対値 |
static double pow(double base, double exponent) | base の exponent 乗 |
static double sqrt(double x) | 非負の平方根 |
static double random() | 0.0以上1.0未満の double |
Math.random() の範囲は、0.0を含み(含む端点)、1.0を含みません(含まない端点)。算術演算とキャストで、ほしい範囲に動かします。
int roll = (int) (Math.random() * 6) + 1;
図では、これを段階的に行います。* 6 は [0.0, 1.0) を [0.0, 6.0) に引き伸ばします。(int) は各部分を0、1、…、5に切り捨てます。6個の等しい部分なので、6個の値が等しい確率で出ます。+ 1 は、それを1、…、6にずらします。一般に、(int) (Math.random() * count) + low は、count 個の int のどれか1つ(low から始まる)を返します。
30回振った結果は5 on 1, 5 on 2, 6 on 3, 4 on 4, 6 on 5, 4 on 6でした。等しい確率で出ることは、短い試行で同じ回数ずつ出ることを意味しません。
設計図と、それが作るもの
クラスは、オブジェクトの属性と振る舞いを形式的に実装したもの、つまり設計図です。オブジェクトは、そのクラスの具体的なインスタンスで、それらの属性に対して独自の値を持ちます。
図では、仮想のクラス Book を使います。属性は2つ(タイトルとページ数)、振る舞いも2つ(getTitle() と getPages())です。そこから作られた Book は、それぞれ独自のタイトルとページ数を持ちます:"Maps", 120; "Birds", 340; "Stars", 96。
関連するクラスは、共通の属性と振る舞いを1つのクラス、つまりスーパークラスに置くことで共有できます。それを拡張するクラス、つまりサブクラスは、それらの属性と振る舞いをもう一度書かなくても利用できます。これが継承の関係です。(継承を設計して書くことは、コースの範囲外です。)
Java のすべてのクラスは Object クラスのサブクラスです。そのため、どのオブジェクトも toString() や equals のようなメソッドを持ちます。
参照型の変数はオブジェクト参照を持ちます。これはオブジェクトのメモリ上のアドレスと考えられます。図の矢印であり、オブジェクトそのものではありません。
Book b; // b は Book への参照を持てる String s; // s は String への参照を持てる
new、コンストラクタ、null
オブジェクトは、キーワードnewと、そのクラスのコンストラクタの1つの呼び出しで作ります。
コンストラクタは、クラスと同じ名前を持ちます。そのシグネチャは、その名前と、パラメータの型を順に並べたリストです。ヘッダーには、パラメータの型と名前が並びます。仮想の Book のドキュメントには、次のものが載っています。
Book(String title, int pages) Book(String title) String getTitle() int getPages()
シグネチャの異なる2つのコンストラクタがあります。コンストラクタはオーバーロードされていて、どちらが呼ばれるかは引数で決まります。new Book("Maps") は、2つ目を呼び出します。
Book b = new Book("Maps", 120); Book c = null; 図では、最初の行を順に実行します。new が Book のメモリを確保し、コンストラクタが実行され、そのパラメータが属性の初期値を設定します。その後、新しいオブジェクトへの参照が b に格納されます。メソッドの呼び出しと同様に、コンストラクタの呼び出しも文の並びを中断し、最後の文が実行されると、制御は呼び出しのすぐ後ろに戻ります。
コンストラクタの引数は、パラメータの型と数も順序も合っていなければならず、値渡しで渡されます。new Book(120, "Maps") は、int の次に String を取るコンストラクタがないため、コンパイルできません。
参照型の変数は、オブジェクト参照を持つか、オブジェクトがなければ null を持ちます。c は何も指していません。
ドット演算子と null
インスタンスメソッドは、そのクラスのオブジェクトに対して、オブジェクトの名前、ドット演算子、メソッドの順に書いて呼び出します。b.getPages() がそうです。
int p = b.getPages(); // p は120 int q = c.getPages(); // NullPointerException
この呼び出しは、b の参照をたどってそのオブジェクトに行き、そのオブジェクトに対して getPages() を実行し、返された int(120)を p に格納します。別の Book オブジェクトなら、そのオブジェクト自身のページ数を答えます。
c は null です。矢印をたどる先のオブジェクトがありません。null 参照に対してメソッドを呼び出すと、その行が実行されたときに NullPointerException が投げられます。コンパイルはできます。コンパイラが調べるのは、Book に getPages() があるかどうかだけだからです。
1.10のページのクラスメソッドと比べましょう。Math.sqrt(x) はクラスに対して呼び出します。s.length() は1つの String オブジェクトに対して呼び出し、その文字列だけについて答えます。
不変。連結すると新しいオブジェクトができる
String オブジェクトは、文字の並びを表します。文字列リテラル "cat" から作るか、コンストラクタ new String("cat") で作ります。String は java.lang にあり、最初から使えます。
String オブジェクトは不変です。一度作られると、その文字は決して変わりません。String に対して呼び出したメソッドは、それを変更せず、新しい値を返します。
String s = "cat"; // sは「cat」 s = s + "s"; // sは「cats」 String t = s + 1 + 2; // tは「cats12」 String v = 1 + 2 + s; // vは「3cats」
+ と += は String を連結し、新しい String オブジェクトを作ります。図では、s = s + "s" が「cats」を作り、s をそれに向けます。「cat」のオブジェクトは、まったく「cat」のままです。
基本型を String と連結すると、String に変換されます。+ は左から右へ働くので、String が最初にどこに現れるかが重要です。s + 1 + 2 では、各数がテキストとして順に連結され、1 + 2 + s では、先に1 + 2が int として加えられます。
どのオブジェクトも String と連結できます。Java は、そのオブジェクトの toString メソッドを呼び出します。これはすべてのクラスが Object から受け継いでいて、オブジェクトを表す文字列を返します。サブクラスは、これをしばしばオーバーライドします(スーパークラスと同じシグネチャを持つ public メソッドで、独自の動作を持つもの)。そのようなオーバーライドを書くことは、コースの範囲外です。
インデックス、length、substring、indexOf
String の文字には、0から長さより1小さい値までのインデックスがあります。その範囲外のインデックスを使うと、StringIndexOutOfBoundsException が投げられます。
Java Quick Reference には、次の String のメソッドが載っています(およびコンストラクタ。split はテキストファイルとともに、トピック4.6で扱います)。
| method | returns |
|---|---|
int length() | 文字数 |
String substring(int from, int to) | インデックス from からインデックス to - 1 までの文字 |
String substring(int from) | substring(from, length()) |
int indexOf(String str) | str が最初に現れるインデックス。見つからなければ −1 |
boolean equals(Object other) | 次のページ |
int compareTo(String other) | 次のページ |
String s = "BANANAS"; s.length(); // 7 s.substring(1, 4); // "ANA" s.substring(4); // "NAS" s.indexOf("NA"); // 2 s.indexOf("X"); // -1 s.substring(3, 4); // "A"
to は、含まれない最初のインデックスなので、substring(from, to) は to - from 文字になります。indexOf が見つけるのは最初の出現だけです。"NA" はインデックス4からも始まります。
index の位置の1文字だけを持つ文字列は、substring(index, index + 1) です。
equals、compareTo、== ではなく
equals は、2つの String が同じ文字の並びを持つかを調べます。compareTo は、どちらが先に来るかを調べます。
String a = "pear"; String b = new String("pear"); System.out.println(a == b); // false System.out.println(a.equals(b)); // true System.out.println(a.compareTo("peach") > 0); // true a と b は、同じ文字を持つ2つの別のオブジェクトを参照しています。a.equals(b) は文字を1組ずつ比べます:true。a == b は参照を比べ、同じオブジェクトを指しているかを調べます:false。String の比較には equals を使います。(== による参照の比較は、トピック2.6で再び出てきます。)
a.compareTo(other) は、a が other より前に来るなら0より小さい値、等しいなら0、a が後に来るなら0より大きい値を返します。String はアルファベット順に並べられ、最初に異なる文字で決まります。「pear」と「peach」はインデックス3まで一致し、そこでは「r」が「c」より後に来るので、結果は正になります。頼ってよいのは符号だけです。Quick Reference が保証しているのはそれだけです。
このコースでは、equals は String をほかの String と比べるときにしか使いません。