ラベル Java の投稿を表示しています。 すべての投稿を表示
ラベル Java の投稿を表示しています。 すべての投稿を表示

2014年12月12日金曜日

Java継承、その他(12章~終わり)

継承

クラスのフィールドやメソッドを継承して、新しいクラスを派生させることができる

派生されたサブクラスは継承元のフィールド、メソッドを全て引き継ぎ、新しく追加することもできる

※Javaではクラスの継承時に直属の親クラスは1つしか持てない(多重継承不可)

class 派生クラス名 extents 継承元クラス名 {
     ・・・
}


//継承元クラス
class A {
  int a;
  void printA() {
    System.out.println(a);
  }
}

//Aを派生させたクラス
class B extends A {
  //新たなフィールドを追加
  int b;

  //新たなメソッドを追加
  void printB() {
    System.out.println(b);
  }

  void printAB() {
    //親クラスのメソッドを呼ぶことも可能
    printA();
    System.out.println(b);
  }

  void printAPlusB() {
    //親クラスのフィールドにもアクセス可能
    System.out.println(a + b);
  }
}

class Test {
  public static void main(String[] args) {
 
    //クラスAを生成
    A clsA = new A();
    clsA.a = 111;
 
    //printAメソッドを実行
    //「111」と表示
    clsA.printA();
 
 
    //Aを継承したクラスBを生成
    B clsB = new B();
 
    //親クラスのフィールドaを設定
    clsB.a = 222;
 
    //親クラスのprintAメソッドを実行
    //「222」と表示
    clsB.printA();
 
    //新たに定義したフィールドに設定
    clsB.b = 333;
 
    //新たに定義したメソッドを実行
    //「333」と表示
    clsB.printB();
 
    //「222」「333」と表示
    clsB.printAB();
 
    //「555」
    clsB.printAPlusB();
  }
}


呼称

派生元のクラス…親クラス、基底クラス、スーパークラス
派生したクラス…子クラス、派生クラス、サブクラス


メンバ

フィールドとメソッドをまとめて「メンバ」と呼ぶ


派生とコンストラクタ

クラスの派生において、コンストラクタ静的初期化子インスタンス初期化子は継承されない



「super(…)」によってスーパークラスのコンストラクタを呼び出せる
※ただし、コンストラクタの先頭のみで、「this(…)」とどちらかしか呼べない


サブクラス内のコンストラクタ内では、「super()」が自動的に挿入される


自動で作られたデフォルトコンストラクタには「super();」が追加されている
コンストラクタを定義しないクラスは、そのスーパークラスが引数なしのコンストラクタを持っていなければならない


「super.」によって、スーパークラスのメンバにアクセスできる
→スーパークラスと同じ名前のフィールドやメソッドを定義可能で、それらに名前のみでアクセスするとサブクラスの方となる



インスタンス生成時の処理の流れ

全てのフィールドをデフォルト値で初期化
上位クラスの初期化子
上位クラスの引数なしのコンストラクタ
下位クラスの初期化子
下位クラスのコンストラクタ



is-Aの関係

継承関係は「サブクラスは一種のスーパークラスである」と成り立つ、これを「is-Aの関係」という





継承の特徴

継承ではメンバを新たに追加することはできるが削除はできない

メンバの変更は「オーバーライド」と「隠蔽」によって行う




オーバーライド

スーパークラスのメソッドと同じシグネチャのメソッドをサブクラスで再定義して上書きすること



隠蔽

スーパークラスのフィールドと同じ名前のフィールド(型は違ってもよい)をサブクラスで定義し覆い隠してしまうこと
→隠蔽されたフィールドは「super.」でアクセスできる

※いろいろと紛らわしいので隠蔽を使うべきではない


アノテーション

オーバーライド時に「@Override」と記載しておくと、コーディングミスが防げる
→メソッド名や引数などが間違っている場合に警告が出るようになる

※「@Deplicate」アノテーションで推奨外と定義できる


class A {
  int a = 1;
  void printA() {
    System.out.println("sp.a=" + a);
  }
}

class B extends A {
  //フィールドを隠蔽
  int a = 2;

  //オーバーライド
  @Override
  void printA() {
    System.out.println("sub.a=" + a);
  }

  void printSP() {
    //スーパークラスのフィールドを表示
    System.out.println("super.a=" + super.a);
 
    //スーパークラスのメソッドを実行
    super.printA();
  }
}

class Test {
  public static void main(String[] args) {
 
    //クラスAを生成
    A clsA = new A();
 
    //printAメソッドを実行
    //「sp.a=1」と表示
    clsA.printA();
 
 
    //Aを継承したクラスBを生成
    B clsB = new B();
 
    //オーバーライドしたメソッドを実行
    //「sub.a=2」と表示(隠蔽した値を表示)
    clsB.printA();
 
    //スーパークラスの値を表示するメソッド
    //「super.a=1」「sp.a=1」と表示
    clsB.printSP();
  }
}


ポリモーフィズム

同じ名前のメソッドで異なる挙動を実現することが可能でやり方は以下のとおりである

継承関係にある型(クラス)同士であれば、上位の型を持つ変数に下位の型を持つインスタンスを代入できる

その場合、呼び出せるメンバは変数の型にあるもののみだが、実際に呼び出すのはインスタンスのメンバである(動的結合


class Base {
  void print() {
    System.out.println("base");
  }
}

class Sub1 extends Base {
  //オーバーライド
  void print() {
    System.out.println("sub1");
  }

  void printSub() {
    System.out.println("111");
  }
}

class Sub2 extends Base {
  //オーバーライド
  void print() {
    System.out.println("sub2");
  }

  void printSub() {
    System.out.println("222");
  }
}

class Test {
  public static void main(String[] args) {
 
    //Sub1のインスタンスを生成
    Sub1 s1 = new Sub1();
 
    //Sub2のインスタンスを生成
    Sub2 s2 = new Sub2();
 
    //Baseクラスの配列
    Base b[] = new Base[2];
 
    //Base配列にSub1のインスタンスを入れる
    b[0] = s1;
 
    //Base配列にSub2のインスタンスを入れる
    b[1] = s2;
 
    for (Base base : b) {
      //ポリモーフィズム
      //「sub1」「sub2」と表示される
      base.print();
   
      //Baseに無いメソッドは呼べない
      //コンパイルエラーとなる!!
      //base.printSub();
    }
 
  }
}



finalクラス

finalを頭に付けて宣言されたクラスの派生クラスを定義することはできない(継承できない)


finalメソッド

finalを頭に付けたメソッドはサブクラスでオーバーライドすることはできない



オーバーライドとメソッドのアクセス性

メソッドをオーバーライドする場合は上書きするメソッドのアクセス修飾子よりも、privateに近い方のアクセス修飾子をつけることはできない


class Base {
  //protectedアクセスのメソッド
  protected void print() {
    System.out.println("Print");
  }
}

class Sub1 extends Base {
  //同じアクセス修飾子はOK
  protected void print() {
    System.out.println("Print1");
  }
}

class Sub2 extends Base {
  //publicに近づけるのはOK
  public void print() {
    System.out.println("Print2");
  }
}

class Sub3 extends Base {
  //privateに近づけるのは×
  private void print() {
    System.out.println("Print3");
  }
}

class Sub4 extends Base {
  //privateに近づけるのは×
  void print() {
    System.out.println("Print3");
  }
}


抽象メソッド

具体的な機能を持たない空のメソッドで「abstract メソッド名(引数);」と宣言する


抽象クラス

abstract クラス名 {
    ・・・・
}

抽象メソッドを含んだクラスで、これを継承するサブクラスは抽象メソッドをオーバーライドする必要がある
→全ての抽象メソッドを実装しない場合はサブクラスも抽象クラスになる

※抽象クラスは継承されることを前提としたものであり、そのインスタンスを直接生成することはできない

※抽象メソッドを持たないクラスでも抽象クラスにすることはできる

※スーパークラスの非抽象メソッドを抽象メソッドとしてオーバーライドできる


//抽象クラス
abstract class Base {
  //抽象メソッド
  abstract void print();
}

//抽象クラスを継承
class Sub1 extends Base {
  //抽象メソッドを実装(オーバーライド)
  void print() {
    System.out.println("sub1");
  }
}

class Test {
  public static void main(String[] args) {
 
    //Sub1のインスタンスを生成
    Sub1 s1 = new Sub1();
 
    //「sub1」と表示される
    s1.print();
 
    //抽象クラスのインスタンスは生成できない
    //Base b = new Base();
  }
}



アクセス修飾子

public

どこからでもアクセス可能



protected

同一パッケージ内と、違うパッケージの継承先(サブクラス)がアクセス可能



デフォルト(無し)

同一パッケージ内のみアクセス可能


private

自分のクラス内のみアクセス可能(他のクラスからはアクセスできない)



※メソッドのオーバーライドや抽象メソッドの実装時に、元のメソッドのアクセス修飾子よりも厳しいアクセス制限(privateより)にすることはできない


class Base {
  //protectedアクセスのメソッド
  protected void printA() {
    System.out.println("A");
  }
}

class Sub1 extends Base {
  //制限を緩める(publicより)のはOK
  public void printA() {
    System.out.println("S1");
  }
}

class Sub2 extends Base {
  //同じアクセス修飾子はOK
  protected void printA() {
    System.out.println("S2");
  }
}

class Sub3 extends Base {
  //コンパイルエラー
  //制限を厳しくする(privateより)のは×
  void printA() {
    System.out.println("S3");
  }
}

class Sub4 extends Base {
  //コンパイルエラー
  //制限を厳しくする(privateより)のは×
  private void printA() {
    System.out.println("S3");
  }
}



インターフェイス


全てのメソッドが抽象メソッドのクラスのようなもので、複数実装することができる
→単一継承しか認めない抽象クラスの使用の欠点を補うモノである


※インターフェイス内のメソッドは全てpublicかつabstractとなる
メソッドを実装する場合はpublicにする必要がある



インターフェイスで宣言されたフィールドは「public static final」となる
→つまり、フィールドには定数しか宣言できない


interface インターフェイス名 {
    ・・・・
}




インターフェイスの実装

class クラス名 implements インターフェイス名(, インターフェイス名) {
    ・・・・
}

※複数実装する場合はカンマで区切る


//インターフェイス
interface If1 {
  void print1();
}

//インターフェイス
interface If2 {
  void print2();
}

//インターフェイスを実装
class Imp1 implements If1 {

  //オーバーライドしなければならない!
  @Override
  public void print1() {
    System.out.println("Imp1");
  }
}

//インターフェイスを複数実装
class Imp2 implements If1, If2 {

  //オーバーライドしなければならない!
  @Override
  public void print1() {
    System.out.println("Imp2-1");
  }

  //オーバーライドしなければならない!
  @Override
  public void print2() {
    System.out.println("Imp2-2");
  }
}

class Test {
  public static void main(String[] args) {
 
    //インターフェイスのインスタンスは作れない
    //コンパイルエラー!
    //If1 if0 = new If1();
 
    //インターフェイス型に実装クラスを入れるのはOK
    If1 if1 = new Imp1();
 
    //「Imp1」と表示される
    if1.print1();
 
    //多重実装したクラスを生成
    Imp2 imp2 = new Imp2();
 
    //「Imp2-1」「Imp2-2」と表示される
    imp2.print1();
    imp2.print2();
 
    //If1型に入れることもできる
    If1 if1a = imp2;
 
    //「Imp2-1」と表示される
    if1a.print1();
 
    //print2は呼べない(コンパイルエラー)
    //if1a.print2();
 
    //If2型に入れることもできる
    If2 if2a = imp2;
 
    //「Imp2-2」と表示される
    if2a.print2();
 
    //print1は呼べない(コンパイルエラー)
    //if2a.print1();
  }
}


extentsとimplementsの両方がある場合、extendsを先に書かなければならない


//インターフェイス
interface If1 {
  void print1();
}

//親クラス
class Base {
  void print2() {
    System.out.println("Print2");
  }
}

//継承かつ実装
class Sub extends Base implements If1 {
  @Override
  public void print1() {
    System.out.println("Print1");
  }
}



インターフェイスの派生

インターフェイスもクラスと同様に継承により派生することができる
→クラスと同様に多重継承はできない

interface If1 {
  void print1();
}

//If1を継承
interface If2 extends If1 {
  void print2();
}

//If2を実装
class Imp implements If2 {
  //If1のメソッドを実装
  public void print1() {
    System.out.println("If1");
  }

  //If2のメソッドを実装
  public void print2() {
    System.out.println("If2");
  }
}


class Test {
  public static void main(String[] args) {
 
    //インターフェイスの実装クラスを生成
    If2 if2 = new Imp();
 
    //「If1」「If2」と表示
    if2.print1();
    if2.print2();
  }
}



抽象クラス or インターフェイス

どちらを使うか迷ったらインターフェイスだが、共通の処理がある場合は抽象クラス



Objectクラス

全てのクラスの親となるクラスで、明示的に派生していないクラスは自動的にObjectクラスのサブクラスとなる


Objectクラスのメソッド

public String toString() … オブジェクトを文字列で表現する

public boolean equals(Object obj) … 等しいかどうか

public int hashCode() … equalsメソッドを修正する場合、こちらも要修正




例外処理

例外とはアプリケーション実行時に発生するエラーのことで、それを対処する必要がある


実行時エラー

リソースの状態によって発生するエラーなどで、ソースコードのミスではない場合のエラー
→エラーでプログラムが終了してしまわない為にも例外処理が必要



try~catch

例外が投げられた(スローされた)場合、その例外を捕捉(キャッチ)して、例外毎に対応する処理を行う

※catchは複数書くこともできるし無くてもよい

※catchは上から順番に捕捉できるか判定され、キャッチできない場合は次のcatchを判定する

※catchに書かれた例外クラスのサブクラスも捕捉される


finally

tryブロックの後に例外が発生する・しないに関わらず必ず実行される処理を記載する
→tryブロックの中で利用したリソースの後始末などに利用する

※finallyは無くてもよい


try {

  例外が発生する可能性のあるコード

} catch (例外クラス1 変数1) {

  例外1が発生した場合の処理

} catch (例外クラス2 変数2) {

  例外2が発生した場合の処理

} finally {
  tryブロックの後に必ず実行したい処理
}




try {

  //例外が発生する可能性がある処理
  FileReader reader = new FileReader("./test.txt");

} catch (FileNotFoundException e) {

  //ファイルが存在しない場合の処理
  e.printStackTrace();
  ・・・・

} catch (IOException e) {

  //入出力エラーの場合の処理
  e.printStackTrace();
  ・・・・

} catch (Exception e) {

  //その他の例外全てをここで捕捉
  e.printStackTrace();
  ・・・・

} finally {

  //最後に行いたい処理
  ・・・・
}



例外クラスの型


Error … このクラスのサブクラスは致命的なエラーで例外処理もできない
Exception … このクラスのサブクラス(RuntimeException系以外)は例外処理が必須
RuntimeException … このクラスのサブクラスの例外処理は任意




アップキャスト

親クラスの型にサブクラスのインスタンスを変換すること
暗黙的(自動的)に変換される(明示的なキャストは不要)


ダウンキャスト

親クラスの型からサブクラスの型に変換すること
明示的なキャストが必要(暗黙的に変換されない)

※ダウンキャストは失敗する可能性があるので「instanceof」で確認する


インスタンス変数 instanceof ダウンキャストしたいクラス型



class Base {
  void print() {
    System.out.println("base");
  }
}

class Sub extends Base {
  void printSub() {
    System.out.println("sub1");
  }
}

class Test {
  public static void main(String[] args) {
    //親クラス型
    Base b;
 
    //サブクラス型
    Sub s;
 
 
    s = new Sub();
 
    //アップキャスト
    b = s;
 
    //サブクラス型に子クラスを入れるのは×
    //コンパイルエラー!!
    //s = new Base();
 
    //明示的にキャストしてもこれは×
    //実行時エラー(変換できない)
    //Base b2 = new Base();
    //Sub s2 = (Sub)b2;
 
 
    //ベース型にサブのインスタンスを入れる
    Base b3 = new Sub();
 
    //b3に入っているインスタンスがSub型かどうか?
    if (b3 instanceof Sub) {
      //b3のインスタンスがSub型ならば、
      //このダウンキャストはOK
      Sub s3 = (Sub)b3;
      s3.printSub();
    }
  }
}




データの種類

CSV(comma-separated values)

データをカンマで区切った形式でExcelで読み込める

赤,red,#ff0000



プログラムで取得する場合はsplitメソッドを使うとよい

String str = "赤,red,#ff0000";

//カンマごとに分割し配列に格納
String[] ary = str.split(",");

for (String s : ary) {
  System.out.println(s);
}



TSV(tab-separated values)

データをタブで区切った形式

赤[\t]red[\t]#ff0000


xml(Extensible Markup Language)

xmlというマークアップ言語で記載した形式

<person>
  <name>yamada</name>
  <email>ym@xxx.xx</mail>
</person>


json(JavaScript Object Notation)

JavaScriptのオブジェクト記法を利用した形式

{
  person:[
    name:yamada,
    email:ym:xxx.xx
  ]
}


Perse(パース)

データを解析してプログラムで使える形式に変換する処理



スクレイピング

元のデータから必要な部分を抽出すること



ファイル書き込み

FileWriter→BufferedWriter→PrintWriterと徐々に便利なクラスに拡張していく


PrintWriter writer = null;

try {
  //sample.txtというファイルを開く(無ければ生成)
  writer = new PrintWriter(new BufferedWriter(new FileWriter("sample.txt")));

  //追記の場合は第二引数を付ける
  //writer = new PrintWriter(new BufferedWriter(new FileWriter("sample.txt", true)));

  //書き込み
  writer.write("Hello");

  //printlnも使える
  writer.printf("abcd");

  //printfも使える
  writer.printf("%d\n",123);

} catch (IOException e) {

  e.printStackTrace();

} finally {

  if (writer != null) {
    //ファイルを閉じる
    writer.close();
  }

}



ファイルの読み込み

FileReader→BufferedReaderと徐々に便利なクラスに拡張していく


BufferedReader reader = null;

try {

  //ファイルを開く
  reader = new BufferedReader(new FileReader("sample.txt"));
  String line;

  //ファイルを一行づつ読み込み
  while ((line = reader.readLine()) != null) {
    //一行分を出力
    System.out.println(line);
  }
} catch (FileNotFoundException e) {

  e.printStackTrace();

} catch (IOException e) {

  e.printStackTrace();

} finally {
  if (reader != null) {
    try {
      //ファイルを閉じる
      reader.close();
    } catch (IOException e) {
      e.printStackTrace();
    }
  }
}




文字

文字は整数値のコードで表され、JavaではUnicodeを採用しており、最初の128文字はASCIIコードと一致する



16進数

1~9abcdefで表し16で桁上がりする数値で頭に「0x」を付ける

0x32…10進数では50(16*3 + 2)
0xff…10進数では255(16*15 + 15)



文字リテラル

シングルクォート「'」で囲んだ文字で、char型の変数に入れられる



文字列とString


文字列リテラルはString型インスタンスへの参照であり、フィールドとしてchar型の配列を持っていてそこに1文字ずつ格納されている



文字列の比較

Stringクラスのインスタンスメソッドequalsを使用する

変数名.equals(比較する文字列);

s1 = "ABC";
s2 = "ABC";

if (s1.equals(s2)) {
  System.out.print("等しい");
} else {
  System.out.print("異なる");
}



文字列連結

StringBuilderクラスを使用すると効率的にメモリを使って連結できる

StringBuilder sb = new StringBuilder();
sb.append("abc");
sb.append("defg");

//「abcdefg」と表示される
System.out.println(sb);




コマンドライン引数


javaコマンドに引数を入力すると、mainメソッドのString[]型の引数として渡される


ソース

public class PrintArgs {
  public static void main(String[] args) {
    for (int i = 0; i < args.length; i++) {
      System.out.printf("args[%d]=%s\n", i, args[i]);
    }
  }
}


コマンドプロンプト

>java PrintArgs aaa bbb


実行結果

args[0]=aaa
args[1]=bbb



文字列から基本型への変換

基本型のラッパークラスのpase…メソッドで文字列から基本型への変換ができる


//文字列から数値に変換
int num = Integer.parseInt("5");

//文字列から浮動小数に変換
double d = Double.parseDouble("1.23");







スレッド(Thread)

複数の処理を並行して実行させるための機能


シングルスレッド

1つのプログラムで1つの処理だけを行う


マルチスレッド

1つのプログラムで複数の処理を同時に行う



スレッドの実装

Threadクラスを継承、Runnableインターフェイスを実装の2つの方法がある



Threadクラスを継承

1.Threadクラスをextends
2.runメソッドをオーバーライド
3.サブクラスのインスタンスを生成
4.startメソッドでスレッド開始(runが実行される)

public class ThreadBasic extends Thread {

  private String name;

  public ThreadBasic(String name) {
    this.name = name;
  }

  @Override
  public void run() {
    super.run();
 
    for (int i = 0; i < 50; i++) {
      System.out.printf("%s:%d,", name, i);
    }
  }

  public static void main(String[] args) {
 
    Thread t1 = new ThreadBasic("t1");
    Thread t2 = new ThreadBasic("t2");
    Thread t3 = new ThreadBasic("t3");
 
    t1.start();
    t2.start();
    t3.start();
 
  }
}


実行結果

t2:0,t3:0,t1:0,t1:1,t2:1,t3:1,t1:2,t2:2,t3:2,t2:3,t1…



Runnableインターフェイスを実装


1.Runnableインターフェイスをimplements
2.runメソッドを実装
3.実装クラスのインスタンスを引数にThreadクラスのインスタンスを生成
4.startメソッドでスレッド開始(runが実行される)

public class RunnableBasic implements Runnable {

  private String name;

  public RunnableBasic(String name) {
    this.name = name;
  }

  @Override
  public void run() {
    for (int i = 0; i < 50; i++) {
      System.out.printf("%s:%d,", name, i);
    }
  }

  public static void main(String[] args) {
 
    Thread t1 = new Thread(new RunnableBasic("t1"));
    Thread t2 = new Thread(new RunnableBasic("t2"));
    Thread t3 = new Thread(new RunnableBasic("t3"));
 
    t1.start();
    t2.start();
    t3.start();
  }
}


実行結果

t2:0,t3:0,t1:0,t1:1,t2:1,t3:1,t1:2,t2:2,t3:2,t2:3,t1…




匿名クラス(無名クラス)

名前を持たないクラスで以下のようなメリットがある


・関連するコードをシンプルに表現できる
・かたまりを把握しやすい
・式が許されている箇所であればどこにでも記述できる
・名前が無いので、名前空間を汚さない
※その性質上、定義したクラスを後から利用しないことがわかっている(=その場限りの)クラスを定義するのに利用する



匿名クラスを使ったスレッドの実装例

public class AnoymouseThread {

  public static void main(String[] args) {
 
    Thread t1 = new Thread() {
      @Override
      public void run() {
        for (int i = 0; i < 50; i++) {
          System.out.printf("%s:%d\n", "t1", i);
        }
      }
    };
 
    Thread t2 = new Thread(new Runnable() {
      @Override
      public void run() {
        for (int i = 0; i < 50; i++) {
          System.out.printf("%s:%d\n", "t2", i);
        }
      }
    });
 
    t1.start();
    t2.start();
  }
}


コレクション

配列よりもコレクションを優先して使うべきである
→配列よりも機能に乏しく、コレクションとの親和性も低いため



リスト(Listインターフェイス)

インデックスで管理、要素の重複OK

ArrayList:可変長配列クラス
LinkedList:リンク構造のリストクラス(要素の挿入・削除が高速)



セット(Setインターフェイス)

要素に順番なし、重複NG

HashSet:任意の順序で格納
LinkedHashSet:格納順序を保障
TreeSet:キーにより要素を自動的にソート



キュー(Queueインターフェイス)

待ち行列の機能を提供、先頭・末尾の要素だけを追加・削除できる

ArrayDeque:両端キュークラス
LinkedList:ListのLinkedListと同じ



マップ(Mapインターフェイス)

キーと値の組み合わせで管理、キーの重複はNG

HashMap:基本的なMap
TreeMap:キーにより要素を自動的にソートするMap



基本的な使い方

インターフェイス型に実装クラスのインスタンスを入れて使う

List<String> list = new ArrayList<String>();


インターフェイス型を用いる目的は、クラスへの依存を最小限にするため

ただし、Android開発においては以下の書き方の方がよい
→ポリモーフィズムはオーバーヘッドがあるため

ArrayList<String> list = new ArrayList<String>();



匿名クラスを使った初期化

List<String> list = new ArrayList<String>() {
  //インスタンス初期化子を定義
  {
    add("Java");
    add("C#");
    add("Ruby");
  }
};



配列→リスト変換


String[] str = {"Java", "C#", "Ruby"};

List<String> list1 = Arrays.asList(str);

//0番目の要素を更新
list1.set(0, "ジャバ");

//Listを表示
//[ジャバ, C#, Ruby]と表示される
System.out.println(list1);

//元の配列を表示
//[ジャバ, C#, Ruby]と表示される
System.out.println(Arrays.toString(str));

//エラー
list1.add("VB");

//エラー
list1.remove(0);

※この変換の仕方では要素の追加削除はできない




String[] str2 = {"Java", "C#", "Ruby"};
List<String> list2 = new ArrayList<String>();
Collections.addAll(list2, str2);

list2.set(0, "ジャバ");
list2.add("Perl");
list2.remove(1);

//[ジャバ, Ruby, Perl]と表示
System.out.println(list2);

//[Java, C#, Ruby]と表示
System.out.println(Arrays.toString(str2));

※この変換なら要素の追加、更新もOK



リスト→配列変換


List<String> list3 = new ArrayList<String>() {
  {
    add("Java");
    add("C#");
    add("Ruby");
  }
};

String[] str3 = new String[list3.size()];
list3.toArray(str3);
list3.set(0, "ジャバ");

//[Java, C#, Ruby]と表示
System.out.println(Arrays.toString(str3));

//[ジャバ, C#, Ruby]と表示
System.out.println(list3);



リストの順番処理

基本は拡張for文を使うがこれはイテレーターを利用したシンタックスシュガー(糖衣構文)
※糖衣構文:人間が読み書きしやすいように用意された構文


List<String> list = new ArrayList<String>() {
  {
    add("Java");
    add("C#");
    add("Ruby");
  }
};

for (String s : list) {
  System.out.println(s);
}



イテレーター

イテレーターはIteratorインターフェイスで定義され、コレクションからiteratorメソッドを利用することで、それぞれの実装に応じたiteratorオブジェクトを取得できる


Iteratorの主なメソッド

boolean hasNext():次の要素が存在するか?
E next():次の要素を取得
void remove():現在の要素を削除



List<String> list = new ArrayList<String>() {
  {
    add("Java");
    add("C#");
    add("Ruby");
  }
};


for (Iterator<String> ite = list.iterator(); ite.hasNext();) {
  System.out.println(ite.next());
}



Iteratorを使うメリット

拡張for文ではできないことができる場合がある


List<String> list = new ArrayList<String>() {
  {
    add("Java");
    add("C#");
    add("Ruby");
  }
};

for (String s : list) {
  System.out.println(s);

  //リストの要素を削除する
  //拡張for文では実行時エラーとなる
  //list.remove(s);
}

for (Iterator<String> ite = list.iterator(); ite.hasNext();) {
  System.out.println(ite.next());

  //リストの要素を削除する
  //iteratorを使用する場合はOK
  ite.remove();
}



ArrayList or LinkedList

要素の挿入削除が多い場合はLinkedList、そうではない場合はArrayList




Setの使い方

//初期値を匿名クラスの初期化子で追加
Set<String> s = new HashSet<String>() {
  {
    add("Java");
    add("C#");
    add("Ruby");
  }
};

//要素の追加
s.add("PHP");

//重複した要素なので何も起こらない
s.add("Java");

//ArrayListに入っている要素を追加
s.addAll(new ArrayList<String>() {
  {
    add("PHP");
    add("JavaScript");
  }
});

//[C#, PHP, Ruby, JavaScript, Java]と表示
System.out.println(s);

//要素を削除
s.remove("Perl");

//全要素を削除
s.clear();




Mapの使い方

//<キーの型,値の型>
Map<String, String> map = new HashMap<String, String>(){
  //匿名クラスの初期化子で初期値を追加
  {
    //追加
    put("red", "赤");
    put("green", "緑");
    put("blue", "青");
  }
};

//キーが含まれているか?
//「true」と表示
System.out.println(map.containsKey("red"));

//値が含まれているか?
//「false」と表示
System.out.println(map.containsValue("紫"));

//キーを順番に取り出す
for (String key : map.keySet()) {
  System.out.println(key);
}

//値を順番に取り出す
for (String val : map.values()) {
  System.out.println(val);
}

//キーと値の両方を取り出す
for (Map.Entry<String, String> entry : map.entrySet()) {
  System.out.printf("%s:%s\n", entry.getKey(), entry.getValue());
}



キュー(Queue)

先入れ先出し(FIFO:First in First Out)と呼ばれるデータ構造で「待ち行列」とも呼ばれる




スタック(Stack)

後入れ先出し(LIFO:Last in First Out)と呼ばれるデータ構造



//キュー(FIFO:First in First Out)
Deque<Integer> dq = new ArrayDeque<Integer>();


//追加
dq.addLast(1);
dq.addLast(2);
dq.addLast(3);

//[1, 2, 3]と表示
System.out.println(dq);

//最初の要素を取り出し、削除
//1と表示
System.out.println(dq.removeFirst());

//[2, 3]と表示
System.out.println(dq);


//スタック(LIFO:Last in First Out)
Deque<Integer> dq2 = new ArrayDeque<Integer>();

//追加
dq2.addLast(1);
dq2.addLast(2);
dq2.addLast(3);

//[1, 2, 3]と表示
System.out.println(dq2);

//最後の要素を取り出し削除
//3と表示
System.out.println(dq2.removeLast());

//[1, 2]と表示
System.out.println(dq2);

2014年12月11日木曜日

Javaクラス関係(8章~11章パッケージまで)

クラス

クラスとは変数(フィールド)とメソッドをまとめたもの


クラス宣言

「class クラス名」で宣言する


フィールド

クラス宣言の後の「{}」の中に宣言した変数



以下は3つのフィールドを持つAccountクラス

class Account {
  String name;
  String no;
  long balance;
}

※複数のクラスを1つのファイルに記載する場合、mainメソッドのあるクラス名をファイル名にする



インスタンス

「new クラス名()」で生成したクラス型の実体のこと

インスタンスを生成することをインスタンス化という

//Accountクラス型の変数xにインスタンスを代入
Account x = new Account();

※クラスの各フィールドは各々の規定値(0やnull)で初期化される



オブジェクト

クラスのインスタンスと配列の本体の総称



インスタンス変数

インスタンス化したフィールドのこと



フィールド(インスタンス変数)へのアクセス

メンバアクセス演算子(ドット演算子)「.」を使う

//Accountクラスのインスタンスをxに代入
Account x = new Account();

//インスタンス変数「name」に値を代入
x.name = "山田太郎";

//インスタンス変数「name」の値をstrに代入
String str = x.name;

//山田太郎と表示される
System.out.print(str);




コンストラクタ


インスタンス生成時に呼び出されるメソッドでクラス名と同じ
→主にフィールドの初期化などを行う


普通のメソッドと違い、値を返すことができない
→宣言時に戻り値の型を指定しない

//Dogクラス
class Dog {
  String name;
  double weight;

  //コンストラクタ
  //(クラス名と同じ、戻り値型の指定なし)
  Dog(String str) {
    name = str;
  }
}


class DogTester {
  public static void main(String[] args) {
 
    //コンストラクタで名前を設定
    Dog dog1 = Dog("Pochi");
 
  }
}



デフォルトコンストラクタ

コンストラクタを全く定義していない場合、引数なしのデフォルトコンストラクタが自動的に作られる


//コンストラクタが無いクラス
class Dog {
  String name;
  double weight;

}

この場合、以下のデフォルトコンストラクタが自動的に作られている

======================
  Dog() {
 
  }
======================


だから、定義していないコンストラクタを呼んでもエラーにならない

class DogTester {
  public static void main(String[] args) {
 
    //デフォルトコンストラクタでインスタンスを生成
    Dog dog1 = Dog();
 
  }
}



this

自分自身のインスタンスのことを「this」と表現する

クラスのフィールドと同じ変数や仮引数をつくることができる
→その場合でフィールドにアクセスするには「this.」を変数名の前につける


class Dog {

  String name;
  double weight;

  //仮引数とフィールドが同じ名前
  Dog(String name) {
 
    //this.nameでフィールドにアクセスできる
    this.name = name;
  }
}


自分のクラス内で自分のコンストラクタを呼ぶ場合「this」を使う

class Dog {
  String name;
  double weight;

  //コンストラクタ1
  Dog(String str) {
    name = str;
  }

  //コンストラクタ2
  Dog(String str, double wt) {

    //自分自身のコンストラクタ1をthisで呼ぶ
    this(str);

    weight = wt;
  }
}


メソッド


クラスにはメソッドを持たせることができる


インスタンスメソッド

インスタンスが実行するメソッドでstaticをつけない



class Dog {

  String name;

  Dog(String name) {
    this.name = name;
  }

  void bow() {
    System.out.print("Bow wow!");
  }
}


class DogTester {
  public static void main(String[] args) {
 
    Dog dog1 = new Dog("Pochi");
 
    //インスタンスメソッドを実行
    dog1.bow(); //Bow wow!と表示される
  }
}






クラスの配列

クラス型は基本型と同じように配列にできる

class Dog {
  void bow() {
    System.out.print("Bow wow!");
  }
}

class DogTester {
  public static void main(String[] args) {

    //Dogクラス型の配列(要素数3)
    Dog[] dogAry = new Dog[3];

    for (int i = 0; i < dogAry.length; i++) {
      //Dogクラス型の配列にインスタンスを格納
      dogAry[i] = new Dog();
    }

    //配列の各要素を変数dogに取り出す
    for (Dog dog : dogAry) {
      //各要素毎にインスタンスメソッドを実行
      dog.bow();
    }
  }
}


クラス変数(静的フィールド)


staticを付けて宣言したフィールドのことで、インスタンスがいくつ生成されても関係無く1つのクラスだけに属する

他のクラスがアクセスする場合は「クラス名.」でアクセスする(自分のクラスのクラス変数の場合は省略可)

class Student {

  //クラス変数
  static int count = 0;

  //コンストラクタ
  Student() {
    //インスタンス生成する毎にカウントアップ
    //自分のクラス変数は「クラス名.」がいらない
    count++;
  }
}

class StudentTester {
  public static void main(String[] args) {
 
    //クラス変数はインスタンスが無くても使える
 
    //カウントを表示(0と表示される)
    //他のクラスのクラス変数は「クラス名.」が要る
    System.out.println(Student.count);
 
    Student st1 = new Student();
 
    //カウントを表示(1と表示される)
    System.out.println(Student.count);
 
    Student st2 = new Student();
 
    //カウントを表示(2と表示される)
    System.out.println(Student.count);
  }
}




クラスメソッド(静的メソッド)

staticを付けて宣言したメソッドのことで、インスタンスがいくつ生成されても関係無1つのクラスだけに属する

他のクラスがクラスメソッドを呼ぶ場合は「クラス名.」で呼び出す(自分のクラスのクラスメソッドの場合は省略可)

クラスメソッドはインスタンス変数、インスタンスメソッドにアクセスすることはできない
(クラスからはどのインスタンスの変数、メソッドのことだか分からない為)

一方、インスタンスメソッドからは全部の変数、メソッドにアクセスできる

class Student {

  //クラス変数
  static int count = 0;

  //インスタンス変数
  int no;

  //コンストラクタ
  Student() {
    //インスタンスメソッドからstatic変数はアクセス可
    count++;

    //インスタンスメソッドからインスタンス変数もアクセス可
    no = count;
  }

  void showNo() {
    //インスタンスメソッドは両方アクセス可
    System.out.printf("%d/%d\n", no, count);
  }

  static void showCount() {

    //staticメソッドからはstatic変数はアクセス可
    System.out.printf("total:%d", count);

    //staticメソッドからはインスタンス変数は×
    //System.out.print(no);
  }

  static void showCount2() {
    System.out.print("***");
 
    //staticメソッドからstaticメソッドを呼ぶのはOK
    //(自分のクラス内なので「クラス名.」は省略)
    showCount();
 
    System.out.println("***");
 
 
    //staticメソッドからはインスタンスメソッドは×
    //showNo();
  }
}

class StudentTester {
  public static void main(String[] args) {

    Student st1 = new Student();

    //インスタンスメソッド(1/1と表示)
    st1.showNo();

    Student st2 = new Student();

    //インスタンスメソッド(2/2と表示)
    st2.showNo();

    //クラスメソッドは「クラス名.」で呼び出す
    //(***total:2***と表示)
    Student.showCount2();
  }
}



デフォルトコンストラクタの注意点

コンストラクタが1つでも定義されている場合、デフォルトコンストラクタは自動的に作成されない
→つまりコンストラクタが1つも無い場合のみデフォルトコンストラクタは作られる

class Dog {

  String name;

  //コンストラクタ
  Dog(String name) {
    this.name = name;
  }
}

class DogTester {
  public static void main(String[] args) {
 
    //定義されてるコンストラクタ
    Dog dog1 = new Dog("Poch");
 
    //コンパイルエラー!!
    //デフォルトコンストラクタは無い!
    Dog dog2 = new Dog();
 
  }
}



アクセス修飾子


フィールドやメソッドの前に「private」と付けると他のクラスからアクセスすることができなくなる
自分のクラスからアクセスすることはOK


フィールドやメソッドの前に「public」と付けると他のクラスにも公開され、全てのクラスがアクセス可能となる


何も付けないと同一パッケージのクラスからのみアクセスが可能となる
→異なるパッケージのクラスからはアクセスできない


class Dog {

  //プライベートな変数
  private String name;

  //コンストラクタ
  Dog(String name) {
 
    //同じクラス内ならアクセス可能
    this.name = name;
  }

  void showName() {
 
    //同じクラス内ならアクセス可能
    System.out.println(name);
  }
}

class DogTester {
  public static void main(String[] args) {
 
    //コンストラクタ
    Dog dog1 = new Dog("Pochi");
 
    dog1.showName();
 
    //コンパイルエラー
    //他のクラスからは書き換えできない
    dog1.name = "Taro";
 
    //コンパイルエラー
    //他のクラスからはアクセスできない
    System.out.print(dog1.name);
  }
}



アクセサ(アクセッサ)

フィールドの値を設定したり取得したりするメソッドのことで、主にプライベートなフィールドにアクセスするために作成する


セッタ(setter)

フィールドの値を設定するメソッドのことでフィールド名の前に「set」をつける



ゲッタ(getter)

フィールドの値を取得するメソッドのことでフィールド名の前に「get」をつける


class Dog {

  //フィールドname(プライベート)
  private String name;

  //セッタ(nameを設定するメソッド)
  void setName(String name) {
    this.name = name;
  }

  //ゲッタ(nameを取得するメソッド)
  String getName() {
    return name;
  }
}

class DogTester {
  public static void main(String[] args) {
 
    //コンストラクタ
    Dog dog1 = new Dog();
 
    //セッタでnameを設定
    dog1.setName("Pochi");
 
    //ゲッタでnameを取得
    String dogName = dog1.getName();
 
    //Pochiと表示される
    System.out.print(dogName);
  }
}



カプセル化

フィールドをprivateにして、その値の更新はアクセッサなどを使用(直接アクセスしない)すること



オブジェクト指向の三大要素

カプセル化継承ポリモーフィズム(多相性、多態性、多様性)



1クラス1ファイル

基本的に1つのファイルには1つのクラスのみを記述して、独立性を高めるべきである
→複数のクラスを1つのファイルに書くのはテスト的なもののみにする



4つのアクセス修飾子

public(公開),protected(パッケージ+継承先),無し(パッケージ),private(非公開)




コレクション

たくさんのインスタンスを管理する目的を持った、クラスやインターフェイスの総称(java.utilパッケージ)
→便利な配列のようなもの



ArrayList

コレクションの代表格


宣言

<型>(ジェネリクス)に入れたい型を書く

ArrayList<String> list=new ArrayList<String>();
→Stringを入れるArrayList



ジェネリクスには基本型が指定できないのでラッパークラスを使う

int→Integer, double→Double,・・・

ArrayList<Integer> list=new ArrayList<Integer>();
→Int(int)を入れるArrayList



要素の追加、取得

add(追加したい値)メソッドとget(取得したいindex)を使う


ArrayList<String> list=new ArrayList<String>();
list.add("Alice");
//「Alice」と表示
System.out.print(list.get(0));



要素数の取得

sizeメソッドで取得

ArrayList<String> list=new ArrayList<String>();

list.add("Alice");
list.add("Bob");
list.add("Chris");
list.add("Elmo");

for(int i=0;i<list.size();i++){
  System.out.println(i+":"+list.get(i));
}


素の削除、置き換え

remove(削除したい値)、remove(index)、
set(index, 置き換える値)を使う


//値が「Alice」の要素を削除
list.remove("Alice");

//インデックスが「3」の要素を削除
list.remove(3);

//インデックス「0」の要素を「Bob」に置換
list.set(0, "Bob");



要素にある値が含まれているかどうか

containsメソッドで判定


ArrayList<String> list=new ArrayList<String>();

list.add("Alice");
list.add("Bob");

if(list.contains("Alice")){
  System.out.println("含まれています");

}else{
  System.out.println("含まれていません");
}



オートボクシング、アンボクシング

ラッパークラスを使用時に自動変換してくれる


ArrayList<Integer> list=new ArrayList<Integer>();

//本来はこのように書く必要がある
list.add(new Integer(123));

//オートボクシング
list.add(456);


//本来はこのように書く必要がある
Integer num = list.get(0);


//アンボクシング
int num2 = list.add(1);




API

Application Programming Interface

システム同士がお互いにやり取りする決め事のこと





クラス型の変数の代入


クラス型の変数にインスタンスを代入すると、そのインスタンスへの参照(アドレス)がコピーされる
インスタンスの実体はコピーされない!!



同じ参照を持つクラス型変数の参照先を変更すると、他の参照先も変更される
→同じ1つの実体に複数の参照が紐づいているから


class ClassA {
  int num;
  ClassA(int num) {
    this.num = num;
  }
}

class Test1 {
  public static void main(String[] args) {
 
    //インスタンスを生成
    ClassA a = new ClassA(123);
 
    //123と表示される
    System.out.println(a.num);
 
    //クラス型変数を代入
    ClassA b = a;
 
    //bのnumを変更したのに…
    b.num = 567;
 
    //aの内容も変更されてしまう
    //567と表示される
    System.out.println(a.num);
 
  }
}



クラス型変数の比較


クラス型変数を「==」で比較しても同じ参照(アドレス)かどうかを比較するだけ
→同じクラスで全く同じフィールド値のインスタンス同士を比較してもtrueにはならない!




引数としてのクラス型変数


メソッドの引数にクラス型を渡す場合は、そのインスタンスの参照(アドレス)を渡すことになる
→インスタンスの実体が渡されるわけではない!



クラス型の配列

クラス型の配列を作ることができ、要素として格納されるのはインスタンスの参照(アドレス)である
→インスタンスの実体が格納されるわけではない!


class ClassA {
  int num;
  ClassA(int num) {
    this.num = num;
  }
}

class Test1 {
  public static void main(String[] args) {
 
    //要素数3のクラス型の配列を生成
    ClassA[] ary1 = new ClassA[3];
 
    //インスタンスを生成
    ClassA a = new ClassA(1);
 
    //クラス型の配列にインスタンスを格納
    ary1[0] = a;
 
    //生成と同時に格納もできる
    ary1[1] = new ClassA(2);
 
 
    //インスタンスを生成
    ClassA b = new ClassA(3);
    ClassA c = new ClassA(4);
    ClassA d = new ClassA(5);
 
    //配列を生成と同時に初期化もできる
    ClassA[] ary2 = {b, c, d};
 
    //インスタンスを生成しながら初期化もできる
    ClassA[] ary3 = {
      new ClassA(6),
      new ClassA(7),
      new ClassA(8),
    };
 
  }
}



同一クラス内のコンストラクタの呼び出し


this(・・・)という形式で同一クラスのコンストラクタを呼べる、ただしコンストラクタ内の先頭にしか書くことができない
→それ以外で呼ぼうとするとコンパイルエラー

class ClassA {
  int num1;
  int num2;
  ClassA(int num1) {
    this.num1 = num2;
  }

  ClassA(int num1, int num2) {
 
    //ここで呼ぶのはOK
    this(num1);
 
    this.num2 = num2;
 
    //ここで呼ぶとエラー!!
    this(num1);
  }

}


toStringメソッド

public String toString()」を定義すると、そのクラス型の変数をprintなどで表示しようとしたり、文字列を連結しようとするときに自動でこのメソッドが呼ばれる


class ClassA {
  int num;
  ClassA(int num) {
    this.num = num;
  }

  //toStringメソッドを実装
  public String toString() {
    return "-->" + num + "<--";
  }
}

class Test1 {
  public static void main(String[] args) {
 
    //インスタンスを生成
    ClassA a = new ClassA(1);
 
    //printしようとするとtoStringが自動で呼ばれる
    //「-->1<--」と表示される
    System.out.println(a);
 
    //文字列連結でもtoStringメソッドが自動で呼ばれる
    String str = "***" + a + "***";
 
    //「***-->1<--***」と表示される
    System.out.println(str);
  }
}



クラス型のフィールド


クラス型のフィールドに格納されるのはインスタンスの参照(アドレス)である
→複数のインスタンスに同じ参照を代入すると、1つ変更しただけで全て変更されてしまう!

class ClassA {
  int num;

  ClassA(int num) {
    this.num = num;
  }
}

class ClassB {

  //クラス型のフィールド
  ClassA a;

  //引数にクラス型
  ClassB(ClassA a) {
    this.a = a;
  }

  void printNum() {
    System.out.println(a.num);
  }

  void setNum(int num) {
    a.num = num;
  }
}

class Test1 {
  public static void main(String[] args) {
 
    //Aのインスタンスを生成
    ClassA a = new ClassA(1);
 
    //aを引数にBのインスタンスbを生成
    ClassB b = new ClassB(a);
 
    //「1」と表示される
    b.printNum();
 
    //aを引数にBのインスタンスcを生成
    ClassB c = new ClassB(a);
 
    //インスタンスcのnumを変更すると…
    c.setNum(22);
 
    //「22」と表示される
    c.printNum();
 
    //インスタンスbのnumも変更されてしまう
    //「22」と表示される
    b.printNum();
 
    //引数としたaのnumを変更すると…
    a.num = 333;
 
    //bとcのnumも変更されてしまう
 
    //「333」と表示される
    b.printNum();
 
    //「333」と表示される
    c.printNum();
 
    //このようにすれば問題ない
    ClassB d = new ClassB(new ClassA(555));
    ClassB e = new ClassB(new ClassA(666));
    d.setNum(777);
 
    //「777」と表示される
    d.printNum();
 
    //「666」と表示される
    e.printNum();
  }
}



クラス型を返すメソッド

クラス型のフィールドの値をメソッドの戻り値として返すと、そのフィールドへの参照(アドレス)を返すことになる
→その参照を通じてフィールドにアクセスできてしまうので、セキュリティ的に問題あり!


class ClassA {
  int num;

  ClassA(int num) {
    this.num = num;
  }
}

class ClassB {

  //クラス型のフィールド
  private ClassA a;

  ClassB(ClassA a) {
    this.a = a;
  }

  //クラス型のフィールド値を返す
  ClassA getClassA() {
    return a;
  }

  void printNum() {
    System.out.println(a.num);
  }

}

class Test1 {
  public static void main(String[] args) {
 
    //インスタンスを生成
    ClassB b = new ClassB(new ClassA(1));
 
    //「1」と表示される
    b.printNum();
 
    //クラス型のフィールドの値を取得
    ClassA a = b.getClassA();
 
    //aのnumを書き換えると…
    a.num = 22;
 
    //プライベートなbのnumが変更される
    //「22」と表示される
    b.printNum();
 
  }
}



============================


class ClassB {

  //このようにすれば問題なし
  ClassA getClassA() {
    return new ClassA(a.num);
  }


============================



has-A

あるクラスがその一部として別のクラスを持つこと
→クラス型のフィールドを定義することで実現できる


コンポジション

あるインスタンスが内部に別のインスタンスを持っている構造のこと
コンポジションはhas-Aの関係となる



コンポジションにより、あるクラスを拡張したクラスを作ることができる


class ClassA {

  void sayHello() {
    System.out.println("Hello");
  }
}

class ClassB {

  //ClassAのインスタンスを持つ
  private ClassA a = new ClassA();

  //ClassAのメソッドを呼ぶ
  void sayHello() {
    a.sayHello();
  }

  //ClassBだけのメソッドを追加
  void sayHoge() {
    System.out.println("hoge");
  }
}

class Test1 {
  public static void main(String[] args) {
 
    //インスタンスを生成
    ClassA a = new ClassA();
 
    //「Hello」と表示
    a.sayHello();
 
    //コンポジションでClassAを拡張したClassB
    ClassB b = new ClassB();
 
    //ClassAと同じメソッドを持っていて…
    //「Hello」と表示
    b.sayHello();
 
    //ClassB独自のメソッドを拡張できる
    //「hoge」と表示
    b.sayHoge();
 
  }
}



クラス変数

staticを付けて宣言されたフィールドで、そのクラスの全インスタンスで共有すべき変数
→クラス変数の実体は1つ宣言する毎に1個だけつくられる



クラス変数のアクセス


異なるクラスのクラス変数にアクセスする場合は「クラス名.」を頭に付ける
→自分のクラス内ならば変数名だけでOK

※ただし、ローカル変数や仮引数名としてクラス変数と同じ名前を宣言できてしまうので、その場合は「クラス名.」を頭に付けてアクセスする


「インスタンス変数名.」でもアクセスできるが意味合い的にも紛らわしいのでやらない!!


class ClassA {

  //クラス変数
  static int num = 123;

  static void display() {
    //自分のクラス内ならnumでOK
    System.out.println(num);
  }

  static void display(int num) {
    //仮引数に同じ名前が使われている
    System.out.println(ClassA.num + num);
  }

  static void display2() {
    //ローカル変数に同じ名前が使われている
    int num = 567;
    System.out.println(ClassA.num + num);
  }
}

class ClassB {

  public static void main(String[] args) {
 
    //他のクラスのクラス変数にアクセス
    //「123」と表示される
    System.out.println(ClassA.num);
 
    //インスタンスを生成
    ClassA a = new ClassA();
 
    //「インスタンス変数.」でも呼べる
    //しかし、紛らわしいのでやらない!!
    System.out.println(a.num);
  }
}



publicかつfinalなクラス変数の公開


クラスの利用者に公開すべき便利な定数はpublic static finalで宣言するとよい
→例)Math.PI…円周率




クラスメソッド

staticを付けたメソッドでクラス全体に関わる処理などを記載する
→呼び出し方はクラス変数と同じで「クラス名.」で呼べる、その他の注意点も同じ


オーバーロードはクラスメソッドとインスタンスメソッドにまたがって行える


クラスメソッド内ではインスタンス変数とインスタンスメソッドにはアクセスできない
→どのインスタンスの変数やメソッドにアクセスするのか分からない為


class ClassA {

  //クラス変数
  static int num = 123;

  //インスタンス変数
  int cnt = 999;

  //クラスメソッド
  static void display() {
 
    //クラスメソッド内でクラス変数はOK
    System.out.println(num);
 
    //クラスメソッド内でインスタンス変数は×
    //System.out.println(cnt);
 
    //クラスメソッド内でクラスメソッドはOK
    hoge();
 
    //クラスメソッド内でインスタンスメソッドは×
    //display(1);
  }

  //クラスメソッド
  static void hoge() {
    System.out.println("hoge");
  }


  //インスタンスメソッド
  //staticにまたがりオーバーロード
  void display(int n) {
    System.out.println(n);
  }

}

class ClassB {

  public static void main(String[] args) {
 
    //他のクラスのクラスメソッドを呼ぶ
    ClassA.display();
 
    //インスタンスを生成
    ClassA a = new ClassA();
 
    //「インスタンス変数.」でも呼べる
    //しかし、紛らわしいのでやらない!!
    a.display();
  }
}



ユーティリティクラス


インスタンス変数を持たずにクラスメソッドのみを提供するクラス
→代表例としてMathクラス



クラス初期化子(イニシャライザー)

クラス宣言内のstaticを付けたブロック「{・・・}」のことで、クラスを初めて利用する前に実行されている処理
→クラス変数を初期化するために使うとよい

※クラス初期化子は一度しか実行されない


class ClassA {

  //クラス変数
  static int num = 0;

  //クラス初期化子
  static {
    num += 123;
  }
}

class ClassB {

  public static void main(String[] args) {
 
    //クラス初期化子が実行される
    //123と表示される
    System.out.println(ClassA.num);
 
    //クラス初期化子は一度きり
    //123と表示される
    System.out.println(ClassA.num);
 
  }
}



インスタンス初期化子(イニシャライザー)

クラス宣言内のブロック「{・・・}」のことで、インスタンス生成時(コンストラクタの前)に実行される処理
→全てのコンストラクタで共通に行う処理を記載するとよい

class ClassA {

  //クラス変数
  static int count = 0;

  int num = 0;

  //インスタンス初期化子
  {
    count++;
    num = count;
  }

  //コンストラクタ
  ClassA() {
  }

  //コンストラクタ(引数あり)
  ClassA(int num) {
    this.num += num;
  }
}

class ClassB {

  public static void main(String[] args) {
 
    //インスタンス初期化子が実行
    ClassA a1 = new ClassA();
 
    //「1」と表示
    System.out.println(a1.num);
 
    //インスタンス初期化子が実行
    ClassA a2 = new ClassA(10);
 
    //「12」と表示
    System.out.println(a2.num);
 
  }
}



パッケージ


パッケージはクラスやインターフェイスをフォルダ階層にまとめたもので、名前の重複回避などの意味がある
→重複しないようにインターネットアドレスの逆順に並べたものを使うとよい(jp.co.yahooなど)


[java]
   |--[util]
   |     |--Scanner
   |     |--Random



単純名、完全限定名


クラス名のみが単純名、頭にパッケージ名を付けるのが完全限定名

java.utilパッケージのScannerクラスを例にすると…

単純名:Scanner
完全限定名:java.util.Scanner



単一型インポート宣言

「import 完全限定名」でインポートすると、その完全限定名で指定したモノがソース内で単純名で利用できる



オンデマンド型インポート宣言

「import パッケージ名.*」でインポートすると、そのパッケージ内のクラスやインターフェイスが単純名で利用できる

※オンデマンド型インポートはクラス名が重複する可能性があるので多用すべきではない!!

※必要に応じてインポートされるので、そのパッケージの全てをインポートするわけではない!



java.langパッケージの自動インポート

java.langパッケージはインポートしなくても、いきなり単純名で使用できる




静的インポート宣言

静的なメンバであるクラス変数、クラスメソッドをインポートでき、宣言の方法は2種類ある


単一インポート宣言

import static 型名.識別子名;


オンデマンド静的インポート宣言

import static 型名.*;




import java.util.Scanner;
import static java.lang.Math.PI;

class Test {
  public static void main(String[] args) {
 
    //インポートしているので単純名で使える
    Scanner stdIn = new Scanner(System.in);
 
    //静的インポートしているので変数名で使える
    System.out.printf("%.2f", PI);
  }
}



パッケージ宣言

「packege パッケージ名;」の形式で行い、ソースコードの先頭に書かなければならない

※パッケージ宣言は無くてもよい、2個以上書くことはできない

※パッケージ名は全て小文字とすべき(クラス名と被らないようにする為)


無名パッケージ

パッケージ宣言を書かない場合は、無名パッケージに所属することになる

※テスト的なモノ以外はパッケージに所属させるべきである



同一のパッケージ内のクラスは単純名で使える



パッケージとディレクトリ

パッケージを作成する場合は、パッケージ名と同一のディレクトリの中にファイルを置く



クラスのアクセス制御

publicクラス

public宣言されたクラスでパッケージとは無関係に利用できる

※publicクラスは1ファイルに1個



非publicクラス

publicを付けずに宣言されたクラスで同一パッケージ内以外は利用できない



メンバのアクセス制御


publicクラスの場合、publicを付けたメンバが公開アクセスとなる

非publicクラスの場合、publicを付けたメンバはパッケージアクセスとなる



Java基礎(1章~7章メソッドまで)

Javaの特徴

開発ツールが無料

オブジェクト指向

一度作ったプログラムはどこでも動く(Write Once, Run Anywhere)
※Javaを動かすにはJRE(Java Runtime Envelopement)が必要


開発準備

JDK(Java Development Kit)をインストール
→Java開発キット(JREが含まれている)

環境変数「Path」を設定


コマンドプロンプト

検索窓に「cmd」で起動

「cd フォルダ名」でフォルダ移動(相対or絶対パスで指定)

「dir」で現在のフォルダの中身を表示



プログラムの作成と実行

class クラス名 {
    public static void main(String[] args) {
        実行するプログラム(mainメソッド)
    }
}

「クラス名.java」で保存

「javac クラス名.java」でコンパイル
→「クラス名.class」が生成(バイトコード)

「java クラス名」で実行
→mainメソッドが実行される



コメント

「//コメント」一行コメント

「/* コメント */」複数行コメント

「/** コメント */」文書化(javadoc)コメント



画面出力

改行無し
System.out.print("○○○")

改行する(引数に何も指定しないと改行だけ)
System.out.println("○○○")



文字列

「"」(ダブルクォート)で囲むと文字列
→文字列リテラルと呼ぶ

「+」で文字列を連結

「\n」で改行



記述ルール

読みやすいように「インデント」を入れる

単語の途中でスペースを入れない

文字列の途中で改行を入れない



変数

変数を使うためには「宣言」が必要
→ int x;

変数宣言は2つ以上まとめても可能
→  int x, y;

int型には「整数」が入れられる
→「123」などの数字を「整数リテラル」と呼ぶ

整数÷整数の結果は、小数以下が切り捨てられる

変数宣言と同時に値を代入することを「初期化」という
→ int x = 2;

値の入っていない変数を使うとコンパイルエラー

使用できる文字はAからZ、aからz、0から9、アンダーバー(_)、$
(例) name, old, no32

先頭文字には数字の0から9は使用できない
(例) 12pointなどは×

大文字と小文字は区別される
(例) old と Oldは別の変数

予約語は使用できない
(例) char や classなどの予約語は使用できない

長さの制限は無い



基本型

整数型
→byte, short, int, long(左から順に大きく)

浮動小数点型
→float, double(左から順に大きく)

文字列(基本型ではなくクラス)
→String



定数

finalをつける(PHPのconst)
→final int PI = 3.14;



キーボードからの入力

「import java.util.Scanner;」をファイルの最初に記述

「Scanner stdIn = new Scanner(System.in);」でstdInを生成

「stdIn.nextInt()」で整数を読み込む

「stdIn.next()」「stdIn.nextLine()」で文字列を読み込む




乱数の生成

「import java.util.Random;」をファイルの最初に記述

「Random rand = new Random();」でrandを生成

「rand.next(○○)」で乱数を取得
→「3」を設定すると「0,1,2」を取得できる

※10~99の乱数を取得する場合「rand.next(90)+10」



if文

if (○○==○○) {
    ・・・
} else if (○○==○○) {
    ・・・
} else {
    ・・・
}

ifの処理が1行の場合は{}を省略できる
if (○○○)
    ・・・


switch文

switch (x) {
    case 0:
        ・・・
        break;
    case 1:
    case 2:
        ・・・(a)
        break;
    case 3:
        ・・・(b)
    default:
        ・・・(c)
}

フォールスルー

breakされるまで処理を続ける

※上の例は…

x=1だと(a)が実行される
x=2でも(a)が実行される
x=3だと(b)(c)が実行される



do文

do {
    ・・・
} (x < 10);

【セミコロンを忘れない!】

※この例ではxが10より小さい間ループを続ける
 
(最初からxが10以上でも1回はループする



While文

while (x < 10) {
    ・・・
}

※この例ではxが10より小さい間ループを続ける

(最初からxが10以上ならループしない




インクリメント


前置

x = 3;
y = ++x;

yには「4」が入る
(インクリメントしてから代入)



後置

x = 3;
y = x++;

yには「3」が入る
(代入してからインクリメント)




for文

for (int i = 0; i < 10; i++) {
    ・・・
}

※この例では10回ループが回る



break, continue

for (int i = 1; i <= 10; i++) {
    if (i == 3) {
        continue;
    }
 
    if (i == 5) {
        break;;
    }
}

この例では3回目のループをスキップ、5回目で抜ける



ラベル付ループ

abc:
for (int i = 1; i <= 10; i++) {
    for (int j = 1; j <= 10; j++) {
        if (j == 5) {
            break abc;
        }
    }
 
}

この例では5回目のループで、一番外側のループをまで抜ける



printf

出力したい値をフォーマットできる

「%」を出力したい場合は「%%」とする
→エスケープ処理

System.out.printf("%05f", 123);
→00123

System.out.printf("%6.2f", 12.34567);
→ 12.34



基本型


整数型

char, byte, short, int, long


浮動小数点型

float, double

論理型

boolean




演算と型


型の異なる数値の演算は大きい方の型に変換される

10 / 4 = 1 (int ÷ int = int)

10 / 4.0 = 2.5 (int ÷ double = double)
→int÷doubleなので左辺のintがdoubleに変換される



キャスト

double x = 1.234;
int y = (int)y;

double型の「1.234」がint型に変換され「1」になる



配列


宣言

int[] a;(int a[];でもOKだが昔のやり方であまり使われない)


生成

int[] a = new int[5];
→aは配列本体を参照する変数(参照型)



構成要素のアクセス

int x = a[2];
→配列「a」のインデックス「2」がxに代入される
(インデックスは0から始まる)
※存在しないインデックスを指定すると実行時にエラー



規定値

配列の各要素は各型の規定値で初期化される(intならば0)


要素数の取得

int arrayCount = a.length;
→配列aの要素数を取得



初期化

int[] a = {1, 2, 3, 4, 5};
→宣言と同時だったら初期化できる

int[] a;
a = {1, 2, 3, 4, 5};
→宣言の後に代入はコンパイルエラー

int[] a;
a = new int[]{1, 2, 3, 4, 5};
→これならOK



拡張for文(for-in,for-each)

int a[] = {1, 2, 3, 4};
for (int i : a) {
    System.out.print(i);
}
→配列aの中身が順番にi代入され「1234」と表示される
※phpのforeachと同じ(括弧の中は逆、asではなく:)



短絡評価


int month = 2;
if (month >= 3 && month <= 5) {
    System.out.print("春");
}
※この場合「month >= 3」がfalseでもうifの中を実行しないのが確定、その場合「month <= 5」は評価されない



シャッフルのアルゴリズム

偏らない方法を使うと良い(Fisher-Yatesシャッフル)

Random rand = new Random();
int[] ary = {1,2,3,4,5,6,7,8};
 
for(int i = ary.length; i > 1; --i){
    int index1 = i - 1;
    int index2 = rand.nextInt(i);
 
    //交換
    int temp = ary[index1];
    ary[index1] = ary[index2];
    ary[index2] = temp;
}

文字列の比較はs1.equals(s2)で行う
→(s1 == s2)はアドレスを比較しているだけで間違い



配列のコピー

配列変数を他の変数に代入しても参照先がコピーされるだけ。
→配列の要素1つ1つはコピーされない



空参照とガーベジコレクション

参照型にnullを代入すると、何も参照していない状態になる

String s = "abcd";
s = null; //何も参照していない


どこからも参照されなくなったオブジェクトはガーベジコレクションに回収され消える


finalな変数は参照先を変えることができないが、格要素は変更できる

int [] ary = {1, 2, 3};
ary = null        //エラー
ary = new int[4]; //エラー
ary[1] = 10;      //OK



多次元配列

宣言

//2x4の2次元配列の宣言
int[][] a = new int[2][4];


初期化

//2次元配列の初期化
int[][] b = { {1,2,3}, {4,5,6} };


多次元配列は以下のように動的に要素数を決めることができる

int[][] x = new int[2][];
x[0] = new int[4];
x[1] = new int[7];


※左詰めで最低1個は要素数を指定する必要がある

int[][][] x = new int[][][];   //エラー
int[][][] x = new int[][3][];  //エラー
int[][][] x = new int[4][][2]; //エラー
int[][][] x = new int[4][2][]; //OK


多次元配列も初期化子で初期化できる

int[][] a = {
    {1,2,3},
    {4,5,6},//最後のカンマは無くてもOK
}


バブルソート

int[] ary = {2, 5, 4, 1, 3};
for (int i = 0; i < ary.length - 1; i++) {
    for (int j = i + 1; j < ary.length; j++) {
        if (ary[i] > ary[j]) {
            int temp = ary[i]; //入れ替え
            ary[i] = ary[j];
            ary[j] = temp;
        }
    }
}



メソッド

ひとまとまりの処理はメソッドとしてまとめられる


メソッド宣言

static 戻り値の型※1 メソッド名(仮引数の型 仮引数名※2,…) {
    メソッド本体の処理
}
※1 戻り値が無い場合は「void」
※2 引数は無くても良いし、いくつでも良い
→引数が無い場合は「void method1() {…」のようになる

例)
static int max(int a, int b) {
    if (b > a) {
        return b;
    }
    return a;
}


メソッド呼び出し

//変数xにmaxメソッドの結果が入る
int x = max(1, 3);

→メソッドが呼ばれると、プログラムの流れはメソッド本体に移る



return文

returnでメソッドの戻り値を返し、メソッドの処理をそこで終了する
→戻り値が無い場合は「return;」としてもOK


値渡し

メソッドの引数は値渡しで行われる
→仮引数の値を書き換えても、実引数の引数は変わらない


voidメソッド

戻り値が無いメソッドは戻り値の型の所を「void」と書く
→return文を書かなくても良い、書く場合は「return;」とする


インポートのワイルドカード

java.util.*;
→java.utilパッケージの全てのクラスをインポートする


有効範囲(スコープ)

有効範囲外の変数は使うことができない

class Example {
 
  //フィールド
  static int x;
 
 //メソッド仮引数
  void method1(int y) {

    //フィールドの使い方
    Example.x = 100;
  }
 
  void method2() {
    int z;  // 局所
    if (z == 0) {
      int a; // 局所
    }
  }
}

x:フィールドなのでどこからでもアクセスできる(最強)

y:method1の仮引数なので、method1の中でしかアクセスできない(method2からはアクセスできない)

z:method2の中で宣言された変数なので、method2の中(かつ宣言された後)しかアクセスできない

a:ifブロックの中で宣言された変数なので、ifの中(かつ宣言された後)しかアクセスできない



フィールド

メソッドの外でstaticをつけて宣言する変数でどこからでもアクセスできる
→アクセスするときは「クラス名.フィールド名」とする

class Example {

  //フィールドx
  static int x;
 
  void method() {

    //フィールドに代入
    Example.x = 100;
 
    //表示(100と表示される)
    System.out.print(Example.x);
  }
}


※注意点

フィールドと同じ名前の変数を宣言できてしまう

→その場合は「クラス名.」をつけるとフィールドにアクセスできる

→変数名のみだとローカル変数の方をアクセスしてしまう

※フィールドにアクセスする場合は「クラス名.」をつけた方が間違いがない!



class Example {

  //フィールドx
  static int x;

  public static void main(String[] args) {
 
    //フィールドに代入
    Example.x = 1;
     
    //フィールドを表示(1と表示)
    System.out.print(Example.x);
     
    //単にxとしてもOKだが…(1と表示)
    System.out.print(x);
     
    //フィールドと同じ名前のローカル変数を宣言してもOK
    int x = 2;
     
    //変数名だけだとローカル変数が優先して表示される(2と表示)
    System.out.print(x);
     
    //クラス名.をつければフィールドが表示される(1と表示)
    System.out.print(Example.x);

  }
}


P240~247は授業でやらない



配列を扱うメソッド


メソッドの引数に配列を使うことができる

//引数にint型の配列を使う
static int maxOf(int[] a) {
    ・・・
}



メソッドの戻り値にも配列を使うことができる

//戻り値にint型の配列を使う
static int[] searchOf(int a) {
    ・・・
}



配列の引数

引数に配列を渡すと、配列の参照(アドレス)が渡される
→メソッド内で配列の要素を更新すると、呼び出し元の配列の要素も更新される

class Example {
  static void test1(int[] ary) {
    //引数で渡された配列の要素を書き換える
    ary[0] = 999;
  }
 
  public static void main(String[] args) {
    int[] a = {123, 456, 789};
 
    //123と表示される
    System.out.println(a[0]);
 
    //メソッド内でa[0]が書き換えられる
    test1(a);
 
    //999と表示される
    System.out.println(a[0]);
  }
}



三項演算子(条件演算子)

if-elseの代わりに使うことができる
→以下の処理は同じ結果となる

int x = 10;

//三項演算子
String s = x == 10 ? "YES" : "NO";

//YESと表示される
System.out.print(s);

==========================

int x = 10;
String s = null;

if (x == 10) {
s = "YES";
} else {
s = "NO";
}

//YESと表示される
System.out.print(s);



JavaDocコメント

特別なフォーマットでコメントを書くと、ドキュメントとして出力することができる

/**
 * 受験人数の桁数を取得
 *
 * @param cnt 受験人数
 * @return 受験人数の桁数
 */


1.「/**」で始まり「*/」で終わる
2.「@param 仮引数の変数名」の後にメソッドの引数の説明を記載
3.「@return」の後にメソッドの戻り値の説明を記載
4.「@see」の後に参照先を記載


※文字化けする場合は以下のオプションを追加

-encoding UTF-8
-charset UTF-8




オーバーロード(多重定義)


同じ名前のメソッドを作ることができる
ただし、同じシグネチャのメソッドは作れない


シグネチャ

メソッド名、仮引数の個数、仮引数の型



//このメソッドが既にあったとすると…
static int ave(int x, int y) {
・・・
}

//仮引数の個数が違うのでOK(オーバーロード)
static int ave(int x, int y, int z) {
・・・
}

//仮引数の型が違うのでOK(オーバーロード)
static int ave(String x, String y) {
・・・
}

//返却型が違うだけなので×
static double ave(int x, int y) {
・・・
}

//仮引数の変数名が違うだけなので×
static int ave(int abc, int xyz) {
・・・
}

2014年12月9日火曜日

配列が苦手な方へ

配列が苦手な方はとりあえず、以下の問題に答えられるようになっておけばOKです。

ポイントとしては『二次元配列とは配列の中に配列が入っている』ということです。




【問題】

二次元配列を使用して以下のように数字を出力せよ

ただし、初期化子を使って配列を初期化しないこと

表示時にはfor文と拡張for文と2種類使うこと




【出力結果】

1
1 2
1 2 3

1
1 2
1 2 3









【回答例】


class ArrayTest {
 
  public static void main(String[] args) {
   
    //二次元配列は配列の中に配列を格納
   
    //一次元目のみの要素を確保
    //(二次元目は後から動的確保)
    int[][] a = new int[3][];
   
    //0番目の要素の二次元目を動的確保
    //一次元目の要素には一次元配列を格納
    a[0] = new int[1];
    a[0][0] = 1;
   
    //1番目の要素の二次元目を動的確保
    a[1] = new int[2];
    a[1][0] = 1;
    a[1][1] = 2;
   
    //2番目の要素の二次元目を動的確保
    a[2] = new int[3];
    a[2][0] = 1;
    a[2][1] = 2;
    a[2][2] = 3;
   
   
   
    //for文でインデックスを使って表示
    for (int i = 0; i < a.length; i++) {
      for (int j = 0; j < a[i].length; j++) {
        System.out.printf("%d ", a[i][j]);
      }
      System.out.println();
    }
   
    System.out.println();
   
   
    //拡張for文で各要素を取得しながら表示
    //一次元目は配列を取得
    for (int[] t : a) {
      //二次元目でintの値を取得
      for (int num : t) {
        System.out.printf("%d ", num);
      }
      System.out.println();
    }
  }
}

2014年12月3日水曜日

参照型の引数は参照渡しではない

Javaのメソッドの引数は、参照型の場合は参照渡しになると思っていませんか?

そもそも、値渡しとはメソッドを呼ぶときに、引数にはその値のコピーを渡すもので、メソッド内でそのコピーされた値を書き換えても、元の引数は変更されないという動きになります。

一方、参照渡しとはメソッドの引数を呼ぶときに、引数にはその値の参照を渡すもので、メソッド内でその参照先を書き換えると、元の引数も書き換わってしまうという動きになります。

確かに、下のようなコードだと参照渡しのような動きをします。


class Ref1 {
  public static void main(String[] args) {
    // サンプル1
    int[] array = {10, 20, 30};
    System.out.println("呼び出し前:" + array[0]);
    method(array);
    System.out.println("呼び出し後:" + array[0]);
  }

  static void method(int[] array){
    //引数の配列のindex0を書き換える
    array[0] = 100;
  }
}


実行結果

呼び出し前:10
呼び出し後:100



しかし、以下のようなコードだとどうなるでしょう?

class Ref2 {
  public static void main(String[] args) {
    // サンプル2
    int[] array = {10, 20, 30};
    System.out.println("呼び出し前:" + array[0]);
    method(array);
    System.out.println("呼び出し後:" + array[0]);
  }

  static void method(int[] array){
    int[] nums = {100, 200, 300};
    //引数で渡された配列を書き換える
    array = nums;
  }
}


実行結果

呼び出し前:10
呼び出し後:10


メソッドの引数で渡された配列を書き換えているのに、メソッド実行後も配列の中身は変わっていません。

これはメソッドの引数が参照型の場合は、アドレス値をコピーしたものが引数として渡されているからです。
つまりは、基本型も参照型も値渡しをしているということです。

引数として渡されたアドレスの参照先である実体を操作することは可能です。
しかし、アドレス値そのものを書き換えても、コピーしたものを上書きしただけなので、元のアドレス値は何も変わらないというわけです。

カプセル化の意義をRPG風にたとえてみた

なぜクラスをカプセル化する必要があるのか疑問に感じる人も多いのではないでしょうか。

カプセル化とはインスタンス変数をprivateにして、直接アクセスできないようにし、メソッドを通じてのみ扱えるようにすることです。

では、これをRPG風に例えて説明してみましょう。

戦士のステータスとして「力」「武器の強さ」「攻撃力」を持っているとします。

これをクラスにするとこんな感じ

class Fighter {

  //力
  int strength;

  //武器の強さ
  int weapon;

  //攻撃力
  int attack;
}


大体のRPGでは「攻撃力」は「力」と「武器の強さ」の合計で決まるので、このクラスもそうしたい…

でも、現状の書き方では以下のように設定できてしまいます。


//戦士ジョンを生成
Fighter jon = new Fighter();

//ジョンの力を「10」に設定
jon.strength = 10;

//ジョンの武器の強さを「25」に設定
jon.weapon = 25;

//ジョンの攻撃力を「99」に設定??
//10 + 25で35じゃないの??
jon.attack = 99;



この場合は「攻撃力」を直接設定できてしまうので、ありえない値にできてしまう…

だったら、「攻撃力」は直接アクセスできないようにして、「力」または「武器の強さ」を設定したら自動的に計算してくれるようにしてしまえ!

それを踏まえたクラスがこちら

class Fighter {

  //力(直接アクセスできない)
  private int strength;

  //武器の強さ(直接アクセスできない)
  private int weapon;

  //攻撃力(直接アクセスできない)
  private int attack;


  //力を設定するメソッド
  void setStrength(int strength) {
 
    //力を設定
    this.strength = strength;
 
    //攻撃力を設定
    setAttack();
  }

  //武器の強さを設定するメソッド
  void setWeapon(int weapon) {
 
    //武器の強さを設定
    this.weapon = weapon;
 
    //攻撃力を設定
    setAttack();
 
  }

  //攻撃力を設定(他のクラスからは呼べない)
  private setAttack() {
 
    //攻撃力 = 力 + 武器の強さ
    attack = strength + weapon;
  }

  //攻撃力を取得
  int getAttack() {
    return attack;
  }
}


このクラスであれば、設定できるのは力と武器の強さだけで攻撃力は自動で計算してくれることになる

//戦士ジョンを生成
Fighter jon = new Fighter();

//ジョンの力を「10」に設定
jon.setStrength(10);

//ジョンの武器の強さを「25」に設定
jon.setWeapon(25);

//攻撃力は35と表示される
System.out.printf("攻撃力は%d", getAttack());





さらに、攻撃力の計算を「力」+「武器の強さ」の2倍に変更したい場合は、Fighterクラスの「setAttac()」を修正するだけで済みます。



//FighterクラスのsetAttackを修正
private setAttack() {

  //攻撃力 = (力 + 武器の強さ)の2倍に修正
  attack = (strength + weapon) * 2;
}



/*
  以下のコードはそのまま
*/

//戦士ジョンを生成
Fighter jon = new Fighter();

//ジョンの力を「10」に設定
jon.setStrength(10);

//ジョンの武器の強さを「25」に設定
jon.setWeapon(25);

//攻撃力は70と表示される
System.out.printf("攻撃力は%d", getAttack());


このようにあるステータスに対する約束事を必ず守らせるため、カプセル化しておくとそうすることしかできなくなるので、間違いが無くなる。

変更する場合でも、Fighterクラス側を修正するだけで良くなり保守性高まる。

つまり、カプセル化したほうが便利ですよね?

staticメソッドからインスタンス変数にアクセスできないたった一つの理由

なぜ、staticメソッドからインスタンス変数、インスタンスメソッドにアクセスできないのかと思う人が多いと思います。

例えば、以下のようなコードはコンパイルエラーになります。



class Student {

  //インスタンス変数
  String name;

  //コンストラクタ
  Student(String name) {
 
    this.name = name;
 
  }

  //クラスメソッド
  static void showName() {
 
    //インスタンス変数を表示???
    System.out.print(name);
  }

}

class StudentTester {
  public static void main(String[] args) {

    Student st1 = new Student("山田太郎");
 
    //クラスメソッドを呼ぶ
    Student.showName();
  }
}


これはStudentクラスのstaticメソッド「showName()」がインスタンス変数の「name」を表示しようとしているからです。

なぜこれがダメなのかは少し考えれば分かります。

StudentTesterのメインメソッドが以下のように記述されていたとします。



Student st1 = new Student("山田太郎");

//山田太郎と表示
System.out.print(st1.name);


Student st2 = new Student("田中花子");

//田中花子と表示
System.out.print(st2.name);



この後で、クラスメソッド「showName()」を呼ぼうとすると以下の処理を実行することになります。


//クラスメソッド
static void showName() {

  //nameを表示したいけど誰の名前??
  System.out.print(name);
}


インスタンス変数はnewでインスタンスが生成されてから、その中身を入れることができます。

仮にStudentクラスから1つのインスタンスしか生成できないならば、nameはそのインスタンス変数の中身で良いでしょう。

上の例で言えば、山田太郎しかインスタンスを生成できないならnameといえば「山田太郎」となります。

しかし、1つのクラスからはインスタンスはいくつでも生成することができます。

上の例でも、「山田太郎」「田中花子」と2つ生成しています。

その後でインスタンス変数のnameと言われても、「山田太郎」「田中花子」のどちらを指しているのか分かりませんよね?

なので、staticメソッドからインスタンス変数にアクセスできないようにしているのです。

staticメソッドからインスタンスメソッドを呼べないのも同様です。

山田太郎くんの持っているメソッドなのか、田中花子さんの持っているメソッドなのか誰にも分からないからです。

このように考えれば理解できますよね?

2014年12月2日火曜日

クラスとはなんぞやをRPGで例えてみた

クラスとは何なのか?
なぜ必要なのか?
そう思う人も多いでしょう。

そこで具体例として、RPGでモンスターとの戦闘を作る場合について考えてみましょう。

まず、モンスターにどのようなステータスが必要か決めなければならない。
例えば、「名前、HP、攻撃力、防御力、素早さ」が要るとする。

それをプログラムで書くとこんな感じ。

String  name = "スライム"; //名前
   int  hp = 5;            //HP
   int  attack = 12;       //攻撃
   int  defense = 8;       //防御
   int  agility = 10;      //素早さ

モンスターが複数現れた場合はこれをたくさん書かなければならない。

String  name2 = "ドラゴン"; //名前
   int  hp2 = 150;          //HP
   int  attack2 = 220;      //攻撃
   int  defense2 = 180;     //防御
   int  agility2 = 45;      //素早さ


はっきり言って超メンドクサイ!!

ならば、これらのステータスをモンスタークラスとしてまとめてしまおう!
各ステータスをインスタンス変数にして、そこに値を設定するコンストラクタを作成する。

class Monster {

  String  name;    //名前
     int  hp;      //HP
     int  attack;  //攻撃
     int  defense; //防御
     int  agility; //素早さ

  //コンストラクタ、引数は(名前,HP,攻撃力,防御力,素早さ)
  Monster(String name, int hp, attack, defense, agility) {
    this.name = name;       //名前を設定
    this.hp = hp;           //HPを設定
    this.attack = attack;   //攻撃を設定
    this.defense = defense; //防御を設定
    this.agility = agility; //素早さを設定
  }
}


このクラスを作って先ほどのモンスター2体を表現すると

Monster slime = new Monster("スライム", 5, 12, 8, 10);
Monster dragon = new Monster("ドラゴン", 150, 220, 180, 45);


モンスター2体を2つの変数で表現できてシンプルでわかり易い!
このようにクラスを使った方が便利ですねえ。

さらにMonsterクラスに攻撃された場合のメソッドをつくると…

int attacked(int hit) {

  //被弾値-防御=ダメージ
  int damage = hit - defense;

  //ダメージを受けた場合
  if (damege > 0) {
    //HPからダメージを引く
    hp -= damage - defense;
  } else {
    damege = 0;
  }
  return damege;
}


そうすると、モンスターを攻撃した場合の処理は…

//スライムを生成
Monster slime = new Monster("スライム", 5, 12, 8, 10);

//スライムに攻撃力40で攻撃
int damege = slime.attacked(40);

//32のダメージを与えた
System.out.printf("%dのダメージを与えた", damege);


と直感的にわかりやすくなる。



このようにクラスとはモンスターを作るのに決めなければならない項目の集まりである。

モンスターには攻撃力や防御力などのステータスが必要!
だから、フィールドにそれらを持たせる。

モンスターを攻撃したときにHPを減らしたい!
だから、メソッドとしてそういう機能を作る。

そうすることで、モンスターを生成し攻撃してダメージを与えるという処理が直感的で簡潔にコーディングすることができるわけです。
クラスって便利ですよね?

2014年11月30日日曜日

テスト対策、用語問題

javaはソースコードをコンパイルすると[バイトコード]が生成される




javaはコンパイルするのに[JDK]が必要である



javaを実行するには[JRE]がインストールされている必要がある



一行のみのコメントの場合は「//」から始める



複数行のコメントの場合は「/*」から「*/」の間に書く



文書化したいコメントは「/**」から「*/」の間に書く



文書化コメントを出力した文書を俗に「javadoc」という



変数ではなく「1」や「"abc"」などソースに直接記載した値を「リテラル」と呼ぶ



定数を宣言する場合は頭に「final」を付ける



キーボードから入力させる場合は「Scanner」クラスを使う



乱数を生成する場合は「Random」クラスを使う



キーボード入力や乱数生成をするクラスを使う場合はファイルの頭に「import」と書かなければならない



do-while文は最後に「;」を付ける必要がある



ループを途中で抜けたい場合は「break」と書く



ループの処理を途中でスキップしたい場合は「continue」と書く



ループに「ラベル」を付けることで多重ループを一度に抜けることができる



printfで整数値をフォーマットしたい場合は「%d」と書く



printfで小数値をフォーマットしたい場合は「%f」と書く



printfで文字列をフォーマットしたい場合は「%s」と書く



数値や変数などの型を違う型に変換することを「キャスト」という



変数には2種類あり、数値などの値を直接代入する「基本」型と、本体のアドレスが代入される「参照」型がある



配列やクラス型の変数に「null」を代入すると、どこのアドレスも指していない状態になる



配列やクラスで、実体のアドレスを持っている変数が一つも無くなった場合、「ガーベジコレクション」に回収され消滅する



メソッド内で宣言された変数などは他のメソッドからアクセスできない、これは「スコープ」が違うからである。



クラス内のメソッドの外で宣言する変数を「フィールド」という



同じ名前で、引数の型や個数が違うメソッドを定義できる、これを「オーバーロード」という



メソッド名、引数の型、個数を合わせたものを「シグネチャ」という



クラス型の実体で「new クラス名()」などで生成したものを「インスタンス」という



クラスや配列の実体を総称して「オブジェクト」という



クラスの実体を生成するときに呼ばれるメソッドを「コンストラクタ」という



クラスの実体の自分自身のことを「this」と表現する



クラスに属するメソッドやフィールドを定義する場合は頭に「static」と付ける



自分のメソッドやフィールドを他のクラスから隠したい場合は「private」、公開したい場合は「public」と書く、これらを「アクセス修飾子」という



他のクラスからアクセスできないフィールドから値を取得するメソッドを「ゲッタ」、設定するメソッドを「セッタ」、それらを合わせて「アクセサ」という。



フィールドを他のクラスからアクセスできない状態にして、その値の更新をメソッドで行いクラスの独立性を高めることを「カプセル化」と呼ぶ



配列をもっと便利にしたような使い方ができるクラスを「コレクション」と言い、代表格として「ArrayList」などがある



基本型が使えない場合は、それと同じ機能を持つクラスを使用するが、それらを「ラッパークラス」と言い、基本型とそれらを自動変換することを「オートボクシング」という




回答はこちら

12/11の試験範囲

1章~11章パッケージまで(継承は出ない!)


[☆=重要度]

☆☆☆☆☆☆

命名規則


使用できる文字はAからZ、aからz、0から9、アンダーバー(_)、$
(例) name, old, no32

先頭文字には数字の0から9は使用できない
(例) 12pointなどは×

大文字と小文字は区別される
(例) old と Oldは別の変数

予約語は使用できない
(例) char や classなどの予約語は使用できない

長さの制限は無い



☆☆☆☆☆☆

オーバーロードとは


同じ名前のメソッドを定義することが可能で、インスタンスメソッド、クラスメソッドにまたがり同じ名前をつけられる、ただし同じシグネチャのメソッドは定義できない

シグネチャとはメソッド名、仮引数の型、仮引数の数の組み合わせで、戻り値は含まれない



☆☆☆☆☆

乱数で範囲の値をすぐ作れる



☆☆☆

クラス
アルゴリズム
for



☆☆

基本配列
二次元配列
arrayList
メソッド





変数
四則演算
文字連結
if
基本型の代入
参照型の代入