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を減らしたい!
だから、メソッドとしてそういう機能を作る。

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