11.似て非なるもの
~オーバーロード~

 メソッドのそっくりさんが登場します。分身の術ではありません。だまされないようによーく目を凝らしてください。

失敗作ならば乗り換えよう

 名前、住所、メールアドレス...個人データだけでも文字列はたくさん使われています。 これらをJavaで扱うにはStringというクラスを利用します。 標準ライブラリ中のパッケージjava.langにあります。

 次はStringクラスの変数strを宣言しています。



 まず①でstrの宣言、②で文字列「abc」をオブジェクトとして生成し、その参照をstrに代入しています。 今までオブジェクトの生成はnewを使っていましたが、Stringではあらかじめわかっている文字列はダブルクォートで囲んで、直接代入できます。 "abc"のような文字列の定数を文字列リテラルといいます。
 ここででてきた水色の部分のオブジェクトは、中身を変えることができません。 文字abcをdefに変えたくてもできないのです。 「いったん作ると変えられない」を覚えておきましょう。
 ただしこれはオブジェクトの方の話で、変数strの値が変えられない、というわけではないのです。 よって③のように、新しい文字列リテラルを代入すれば、 そのオブジェクトの参照がstrに入りなおすので、図のようにstrは文字列defを表すことになります。

見てるだけー

 クラスStringには、文字列を扱う便利なメソッドがたくさん用意されています。 文字列オブジェクトの中身は変えられませんから、 どれも、文字列から特定の文字を探したり、オリジナルの文字列のコピーを作ってそこに新しい文字列を作ったりします。 もしどうしても文字列を直接いじりたい方は、パッケージjava.langの中のクラスStringBufferを使ってください。

 次にいくつかのメソッドを載せます。 右には文字列オブジェクトの中身が変わるのではないことを示すために、しつこく図を載せておきました。

上から
使用例
説明図
出力結果
String str; str = "abcde";
System.out.println(str.length()  +" 3文字目="+str.charAt(2));
5 3文字目=c
String str1 = "abcd", str2 = "abce";
System.out.println(str1.  compareTo(str2));
-1
String str1 = "abc", str2;
str2 = str1.concat("de");
System.out.println(str2);
abcde
String str = "abcde";
System.out.println(str.  startsWith("ab") +  " " + str.endsWith("dd"));
true false
String str = "abcde";
System.out.println(str.substring(3));
de
String str1 = "abcbcdb", str2;
str2 = str1.replace('b', 'x');
System.out.println(str2);
axcxcdx
String str1 = "abcde", str2;
str2 = str1.toUpperCase();
System.out.println("str1=" + str1);
System.out.println("str2=" + str2);
str1=abcde
str2=ABCDE
⑦´ String str1 = "abcde";
str1 = str1.toUpperCase();
System.out.println("str1=" + str1);
str1=ABCDE
String str = "oyaoya";
System.out.println(str.  indexOf('a') + " " +   str.indexOf("ya") +  " " + str.indexOf('a', 3));
2 1 5


 まず一番の基本は①のlengthです。配列の長さは配列名.lengthであったのに、文字列の場合はlengthはメソッドなので( )が付きます。 間違えないでください。
 charAtは、実引数の位置の文字を取り出します。2文字目なのかと思ったら大間違え。 1文字目の位置は0なので、charAt(2)で3文字目が取り出されます。配列の添字のつけ方と同じですね。

 ②は比較です。左端から1文字ずつ比較していって違った文字のコードの差を求めます。同じ文字列ならば結果は0になります。

 ③は連結です。実引数の文字列を後ろにつなげますが、このときオリジナルの文字列をコピーしたものが使われます。

 ④はstartsWithが左端、endsWithが右端について、実引数と比較し、同じか違うかをtrueかfalseで出します。 startとWithの間に「s」が入ります。endとWithの間も。

 ⑤は実引数の位置以降の文字列を求めます。

 ⑥は一番目の実引数の文字を二番目ので置き換えます。 置き換えるといってもオリジナルの文字列ではなく、コピーしたものについてで、その参照を返してくれる、というわけです。

 ⑦は文字列を大文字に変換します。⑥と同じで、オリジナルはそのままです。⑦´のようにすれば、文字列str1はABCDEになります。

 ⑧は実引数の文字、または文字列を左端から探して、その位置を返します。実引数が二つあるものは、二番目の実引数以降を探す対象とします。

同じ顔した別人

 メソッドindexOfについて「おやっ!」と思われた方はいませんか。 例ではなんと3通りの呼び出し方があるではないですか。 実引数を見ると、最初は1文字、次は文字列、最後は1文字と開始位置、どれも同じindexOfです。

 実はStringクラスにはメソッドindexOfは何通りも定義してあって、呼ばれたときの実引数の型や数でどれを使うかが決められているのです。 どう呼ぶかが大切なのです。

 クラスStringのindexOfは使う一方なので、次は実際どのように同名のメソッドがいくつも宣言することができるのかを見ましょう。
 クラスPersonは最初の方でやったクラスの書き換えです。メソッドwhoであやしい自己紹介をします。

[ファイルPerson.java]
public class Person {
 String name;
  int age;

  public Person(String name, int age) {
   this.name = name;
   this.age = age;
  }

  public void who() {//①
   System.out.println("私は"+this.name+
    " 年齢は"+this.age+"才");
  }

  public void who(String name) {//②
   System.out.println("私は"+name+
    " 年齢は"+this.age+"才");
  }

  public void who(int age) {//③
   System.out.println("私は"+this.name+
    " 年齢は"+age+"才");
  }

  public void who(String name, int age) {//④
   System.out.println("私は"+name+
    " 年齢は"+age+"才");
  }
}

 メソッドwhoが4通りあります。 仮引数のない①、String型の仮引数の②、int型の仮引数の③、String型とint型の仮引数二つの④です。

 さあ、このクラスを使っていろいろ試してみましょう。

[ファイルMain.java]
public class Main {
 public static void main(String[] args) {
  Person x = new Person("太郎", 5);
  x.who(); //①´
  x.who("次郎"); //②´
  x.who(10); //③´
  x.who("次郎", 10);//④´
 }
}

 結果はこうです。 おやおや正直に答えているのは最初だけですね。後は、実引数に指定された名前や年齢を騙っています。

私は太郎 年齢は5才 ←①´の出力
私は次郎 年齢は5才 ←②´の出力
私は太郎 年齢は10才 ←③´の出力
私は次郎 年齢は10才 ←④´の出力

①´は実引数なしなので、仮引数なしの①を呼び出します。 ②´は文字列リテラルが実引数、これはString型なので②を呼び出します。 ③´はint型の定数なので、③を呼び出します。 ④´はString型とint型の実引数なので、④を呼び出します。呼び出された後の処理は大丈夫ですね。

 このようにひとつのクラス(後でやるサブクラスも含む)で同じメソッド名をいくつもの形で宣言することを、 メソッド名のオーバーロード(overloading)といいます。実引数の型や個数でどれを呼び出すかが区別されるわけです。
 トラックのオーバーロードは困りますが、メソッド名のオーバーロードは大いに利用しましょう。
 例のように自己紹介する、というメソッドがwhoHontou、whoNamaeUso、whoTosiUso、whoDottimoUsoなどと 一々別のメソッド名でなければならないとしたら、メソッド名を考えるのも大変、呼ぶのも大変ですよね。

 クラスPersonのソースを見ると、同じような出力処理が並んでいますね、これはかっこ悪い。ちょっと書き換えておきましょう。

public class Person {
 String name;
 int age;

 public Person(String name, int age) {
  this.name = name;
  this.age = age;
 }

 public void who() {//①
  this.who(this.name, this.age);
 }

 public void who(String name) {//②
  this.who(name, this.age);
 }

 public void who(int age) {//③
  this.who(this.name, age);
 }

 public void who(String name, int age) {//④
  System.out.println("私は" + name + " 年齢は" + age + "才");
 }
}

どこで区別?

 古くて恐縮ですが、おそ松くん兄弟は6つ子、やはり親はちゃんと区別できるのでしょうね。
 メソッドの区別は、メソッドのシグネチャ(signature)で行います。 メソッドのシグネチャとは、メソッド名、引数の型や個数、並び順のことを言います。 これがすべて同じであれば、同一メソッドとみなされます。

 もし次のようなメソッドが同じクラスに定義されていれば、これは同一メソッドと見なされ、重複してます、とエラーになります。 戻り値の値が違っても、シグネチャが同じであれば同一メソッドと見なされるので注意してください。

public class Person {
 String name;
 int age;

 public Person(String name, int age) {
  this.name = name;
  this.age = age;
 }

 public void who() {//①
  this.who(this.name, this.age);
 }

 public int who() {//①´´
  this.who(this.name, this.age);
  return -1;
 }

 public void who(String name, int age) {//④
  System.out.println("私は" + name + " 年齢は" + age + "才");
 }

 public void who(String n, int a) {//④´´
  System.out.println("拙者" + n + "と申す、年は" + a + "才でござる");
 }
}

 ①と①´´は戻り値の型が違いますが、メソッド名と引数の並びは同じなので、「重複するメソッド名」とコンパイルエラーになります。 ④と④´´は、仮引数の名前は違いますが、これは本質的な違いではないのでやはりエラーになります。

作り方はお好みで

 オーバーロードはメソッドだけでなく、コンストラクタでも可能です。考え方は同じです。
 次の例では、クラスPersonのコンストラクタをいろいろ用意してみました。

[ファイルPerson.java]
public class Person {
 String name;
 int age;

 public Person() {//②
  this("", 0);
 }

 public Person(String name) {//③
  this(name, 0);
 }

 public Person(String name, int age) {//①
  this.name = name;
  this.age = age;
 }

 public void who() {
  this.who(this.name, this.age);
 }

 public void who(String name) {
  this.who(name, this.age);
 }

 public void who(int age) {
  this.who(this.name, age);
 }

 public void who(String name, int age) {
  System.out.println("私は" + name + " 年齢は" + age + "才");
 }
}

 ②の中の

   this("", 0);

はthisの後にメソッド名がありません。 これはクラス内の別のコンストラクタを呼び出しているのです。 実引数が二つなので①のコンストラクタですね。this.Personでないことに注目しましょう。 0歳の名無しのゴンベさんを作るときにはこれが使えます。生まれたばかりの赤ちゃんかな。 ③は名前だけ。やっと名前が決まった赤ちゃんはこれです。

 ではいろいろな方法でオブジェクトを作成してみましょう。

[ファイルMain.java]
public class Main {
 public static void main(String[] args) {
  Person x = new Person("太郎", 5);//①´
  x.who();

  x = new Person();//②´
  x.who();

  x = new Person("花子");//③´
  x.who();
 }
}

 出力は次のようになります。

私は太郎 年齢は5才
私は 年齢は0才
私は花子 年齢は0才