[Java]学習メモ - 5月

Javaの学習をやっていて、わからなかったこと、初めて知ったこと、覚えておきたい知識などを日ごとに分けてメモしておきたいなと思ったのでここに記しておきます。

## 目次

- [2025.05.28(WED)](#20250528wed)
	- [プリミティブ型について](#プリミティブ型について)
- [2025.05.29(THU)](#20250529thu)
	- [新知識](#新知識)
- [2025.05.30(FRI)](#20250530fri)
	- [実行に手こずった](#実行に手こずった)
	- [結論](#結論)
	- [toStringのオーバーライド](#tostringのオーバーライド)
- [2025.05.31(SAT)](#20250531sat)
	- [引数にオブジェクトを渡すメソッド](#引数にオブジェクトを渡すメソッド)
	- [オーバーロード](#オーバーロード)
	- [インスタンスフィールドとクラスフィールド](#インスタンスフィールドとクラスフィールド)


## 2025.05.28(WED)

### プリミティブ型について

short, byte, charなどのプリミティブ型について少々手こずったため、詳細を残しておく。

#### 手こずった問題

[【Java】基本構文に関する問題](https://phoeducation.work/JavaPracticeBasic1#%E5%95%8F%E9%A1%8C1-3)

ここの問題1-3。
以下のような問題である。

``` Java
public class Main {
	public static void main(String[] args) {
		// short型の変数 s1 を宣言し、65 を代入してください。
		// s1 の値を出力してください。

		// byte型の変数 b を宣言し、変数 s1 の値を代入してください。
		// b の値を出力してください。

		// char型の変数 c を宣言し、変数 b の値を代入してください。
		// c の値を出力してください。

		// short型の変数 s2 を宣言し、変数 s1 と b を足した値を代入してください。
		// s2 の値を出力してください。
	}
}
```

具体的には最後のs2の部分で引っかかった。
まず全体的にどのようなコードを書いたかと言うと：

``` Java
public class Main {
	public static void main(String[] args) {
		// short型の変数 s1 を宣言し、65 を代入してください。
		short s1;
		s1 = 65;
		// s1 の値を出力してください。
		System.out.println(s1);
		// byte型の変数 b を宣言し、変数 s1 の値を代入してください。
		byte b;
		b = (byte)s1;
		// b の値を出力してください。
		System.out.println(b);
		// char型の変数 c を宣言し、変数 b の値を代入してください。
		char c;
		c = (char)b;
		// c の値を出力してください。
		System.out.println(c);
		// short型の変数 s2 を宣言し、変数 s1 と b を足した値を代入してください。
		short s2;
		s2 = s1 + (short)b;
		// s2 の値を出力してください。
		System.out.println(s2);
	}
}
```

以上のように最初は書いていて、s1, b, cまでは問題なく出力させることができた。
しかしs2でどうしても`incompatible types:`エラーが出てしまう。
互換性のない型と言われているのだが、s1をshortにキャストしてみても何をしてみてもエラーは解消されなかった。そのため色々と調べていくと...。

#### short同士の足し算では結果としてintが返るらしい

[基本データ型での型変換について - teratail](https://teratail.com/questions/183332)

teratailにそれらしき質問と回答があったので見てみると、

> short + short の返り値は、実はint型なのです。

とあった。
「確かに、short + short で足し算をやって結果もshortで返ってくるとオーバーフローの可能性があるなあ」
と得心はいったものの、調べるまで頭に思い浮かばなかった。なので、詰まっていた部分は正しくは以下のように：

``` Java
// ...
s2 = (short)(s1 + b); 
// ...
```

というふうに、**計算結果をshortにキャストしてやる必要があった**のだ。
それと最初の解答では`s2 = s1 + (short)b`とbyte型の方をshortにキャストしていたが、これは逆にbyte → shortであふれる必要がないので、**こっちは明示的にキャストする必要がない。**

というふうに分かれば簡単なエラーだったが、頭に思い浮かばなかったためにそこそこ手こずったという手痛い詰まり方をした。

#### ちなみに

じゃあ「byte + byteの結果もshortかintで返ってくるのか？」と思ったので調べてみた。

``` Java
public class Main {
	public static void main(String[] args) {
		byte b1 = 5;
		byte b2 = 5;

		byte b3 = b1 + b2; // エラー
		short s = b1 + b2; // エラー
		int i = b1 + b2; // OK
	}
}
```

`b1 + b2`をbyteで受けるのもshortで受けるのもダメだったので、どうやらbyte + byteもintで帰ってくるようだ。

こういうシンプルな記述・エラーで1つでもなかなか考え込んでしまうことがよくあるので、今後もプログラム記述→エラー発生→修正の流れを経験したらメモとして投稿しておこうと思った。

## 2025.05.29(THU)

### 新知識

- Mathクラスはjava.langにあるので特段何かライブラリをインポートする必要はない。

例：
``` Java
public class Main {
	public static void main(String[] args) {
		int value = -15;
		int absoluteNum = Math.abs(value);
	}
}
```

## 2025.05.30(FRI)

### 実行に手こずった

まず、今回実行に手こずったjavaファイルのある場所のディレクトリ構造は下記のようになっていた。

```
\JavaPractice
├- \AbstractBasicPractice
├- \AbstractNormalPractice
		├- AbstractNormalPractice.java
		├- AbstractNormalPractice.class
		⋮
```

で、VSCodeの拡張機能のサジェスト昨日のようなもので、該当ファイルにはパッケージ名が自動で宣言されていた。

``` Java
package AbstractNormalPractice;

public class AbstractNormalPractice {
	public static void main(String[] args) {
		// ⋮
```

こうなっているとどうも`\(ファイルのあるディレクトリ名)> java (ファイル名)`では動かないらしかった。
色々と調べた末にパッケージ（javaやらclassやらをまとめているファイル）のある上の階層、上に示したように今回の場合はJavaPracticeまで移動して、かつコマンドは
`\JavaPractice> java AbstractNormalPractice.AbstractNormalPractice`とすることで無事起動した。

----

### 結論

パッケージの依存関係がある場合は、
`\(パッケージフォルダのある階層)> java (パッケージ名).(ファイル名)`
として起動する必要があるようだ。

参考：[Java：「メインクラスを検出およびロードできませんでした」の解決法 - Qiita](https://qiita.com/inahu_ono/items/ddd6985eb02ca694c58c)

----

### toStringのオーバーライド

参考：[【Java】toString()メソッドの使い方 - Qiita](https://qiita.com/mzmz__02/items/6480a2ba316e4cfbea0f#tostring%E3%83%A1%E3%82%BD%E3%83%83%E3%83%89%E3%81%A8%E3%81%AF)

``` Java
public class Hoge {
	String name;
	int age;
	String affiliate;
	
	@Override
	public String toString() {
		return "name: " + name + "age: " + age + "affiliate: " + affiliate;
	}
}
```

**オーバーライド**ということは、スーパークラスの同名メソッドを書き換えているのだが、一見スーパークラスなどはないように見える。
しかし全てのクラスは定義と同時に、暗黙的に`Object`クラスのサブクラスとして定義される。
そして`toString()`は**Object**クラスで定義されているメソッドであるため、上記のようなオーバーライドが可能なのである。
ちなみに`toString()`は必要に応じてOverrideされることが前提のメソッドらしいので、必要なときはどんどん上書きしていくのが良さそう。


## 2025.05.31(SAT)

### 引数にオブジェクトを渡すメソッド

参考：[【Java】オブジェクト指向の基本問題1-1 - 問題2-3](https://phoeducation.work/JavaOOP1#%E5%95%8F%E9%A1%8C2-3)

まずはソースコード全文。

``` Java
public class ClassNormalPractice {
	public static void main(String[] args) {
		// Person型配列の作成
		Person[] persons = {new Person(), new Person(), new Person()};

		// 姓・名・年齢データを設定
		persons[0].setData("生塩", "ノア", 16);
		persons[1].setData("明星", "ヒマリ", 17);
		persons[2].setData("久枝", "佐一", 28);

			// 自己紹介メソッドを呼び出す
		for (int i = 0; i < persons.length; i++) {
			persons[i].introduce();
		}

		System.out.println();

		// 年齢差の比較
		persons[0].compare(persons[2]);
		persons[1].compare(persons[0]);
		persons[2].compare(persons[1]);
	}
}
class Person {
	private String familyName;
	private String firstName;
	private int age;

	public void setData(String familyName, String firstName, int age) {
		this.familyName = familyName;
		this.firstName = firstName;
		this.age = age;
	}

	public String getFamilyName() {
		return familyName;
	}
	public String getfirstName() {
		return firstName;
	}
	public int getAge() {
		return age;
	}

	public void introduce() {
		System.out.println("私の名前は" + familyName + " " 
		+ firstName + "、年齢は" + age + "歳です。");
	}

	public void compare(Person p) {
        if(age == p.getAge()) {
            System.out.println("わたくし" + familyName + "は、" 
             + p.getFamilyName() + "さんと同じ年齢です。");
        } else if (age < p.getAge()) {
            System.out.println("わたくし" + familyName + "は、" 
             + p.getFamilyName() + "さんより" + (p.getAge() - age) 
             + "歳年下です。");
        } else if (age > p.getAge()) {
            System.out.println("わたくし" + familyName + "は、" 
             + p.getFamilyName() + "さんより" + (age - p.getAge()) 
             + "歳年上です。");
        }
	}
}
```

で、今回の焦点は`Person`クラスの最後にある`compare`メソッドになる。
これは他の`Person`クラスオブジェクトの年齢と自身の年齢を比較する必要があるので、比較対象の年齢を引っ張ってくる必要がある。
そうすると引数にオブジェクトを取るように定義するというのはすぐに思いついたが具体的な記述に迷った。
しかし調べてみると単純なことで、`String`, `int`型の引数も`public void method(Type arg)`のように定義しているので、その形式に則って`public void method(Class arg)`としてやればよいわけである。
このように簡単な話ではあるが、次回同じことをやろうとするとまた迷ってしまう可能性があるのでここに記す。

----

### オーバーロード

参考：[【Java】オブジェクト指向の基本問題1-1 - 問題2-4](https://phoeducation.work/JavaOOP1#%E5%95%8F%E9%A1%8C2-4)

``` Java
public class OverloadBasicPractice {
	public static void main(String[] args) {
		Chef chef = new Chef();

		Egg e = new Egg();
		Rice r = new Rice();
		Milk m = new Milk();
		Cheese c = new Cheese();

		System.out.println("1品目は" + chef.cook(e, c) + "です。");
		System.out.println("2品目は" + chef.cook(r, e) + "です。");
		System.out.println("3品目は" + chef.cook(r, c) + "です。");
		System.out.println("4品目は" + chef.cook(m, e) + "です。");
	}
}
// 各食材クラス
class Egg {}
class Rice {}
class Milk {}
class Cheese {}
// cookメソッドを持つchefクラス
class Chef {
	public String cook(Egg e, Cheese c) {
		return "スクランブルエッグ";
	}
	public String cook(Rice r, Egg e) {
		return "オムライス";
	}
	public String cook(Rice r, Cheese c) {
		return "リゾット";
	}
	public String cook(Milk m, Egg e) {
		return "プディング";
	}
}
```

このソースでは**オーバーロード**を用いている。
オーバーロードとは、**同名のメソッドを複数立てるテクニック**であるようだ。
ただし重要なのは、同名メソッドの各々の**シグネチャ**が異なっていなければならない、ということ。
メソッドにおけるシグネチャとは、**メソッド名+引数の数+引数の型**の組み合わせのこと。
今回の例ではメソッド名と引数の数は同じだが、引数の型が各々異なるため別のシグネチャとなり、オーバーロードを行うことができている。
また、次のような記述も可能。

``` Java
class Sample {
	public void test(String s, int i) { }
	public void test(int i, String s) { }
}
```

一見すると不可能なように見えるが、
- １つ目の`test`メソッドはString型が第一引数に、int型が第二引数に来ている。
- ２つ目の`test`メソッドはint型が第一引数に、String型が第二引数に来ている。
つまり引数の数+引数の型の組み合わせが異なり、この場合も**シグネチャが異なる**ため、オーバーロードが可能。

また、オーバーロードはコンストラクタでも使うことができる。

``` Java
class OverloadTest {
	private String sfield;
	private int ifield;
	private double dfield;

	OverloadTest(String sfield, int ifield, double dfield) {
		this.sfield = sfield;
		this.ifield = ifield;
		this.dfield = dfield;
	}

	OverloadTest(String sfield, int ifield) {
		this(sfield, ifield, 3.0); // 先に定義したコンストラクタを使う
	}
}
```

上記のようにコンストラクタをオーバーロードできる。
また、このような場合、後に定義する方のコンストラクタでは**先に定義したコンストラクタをthisで呼び出す**というテクニックもあるようだ。

----

### インスタンスフィールドとクラスフィールド

参考：[【Java】オブジェクト指向の基本問題1-1 - 問題2-7](https://phoeducation.work/JavaOOP1#%E5%95%8F%E9%A1%8C2-7)

インスタンスフィールドとクラスフィールドの違いについて残しておく（メソッドも）。

（理解できた限りで）簡潔に述べると、インスタンスフィールドが**個々のオブジェクトが持つ変数**で、クラスフィールドが**クラス全体に共通の変数**ということらしい。

``` Java
class Test {
	private int instancefield1;
	private String instancefield2;
	private static int classfield = 0; // これがクラスフィールド
}
```

以上のような記述でインスタンスフィールドとクラスフィールドを定義する。
具体的には`static`が付いている変数やメソッドは**すべてのインスタンスが共有することになる**ということのようだ。
今まで`static`は、「インスタンス化なしでメソッドにアクセスするために付ける修飾子」と理解していたが、どちらかというと「**`static`が付いてクラス共有になった結果としてインスタンス化しなくてもアクセスできるようになる**」という方が正しいのかも（視座が違うだけでどっちも正しいとも言えそうだが）。
とにかく、インスタンスフィールドとクラスフィールドを使い分けることで、下記のように「Personクラスインスタンスの名前と年齢はインスタンスフィールドとして、作成したPersonクラスインスタンスの数をクラスフィールドとして管理する」というような使い方ができる。

``` Java
public class Main {
	public static void main(String[] args) {
		// Personクラスオブジェクト作成の前に人数を表示
		System.out.println("現在の人数：" + Person.getCount() + "人"); 
		// 現在の人数：0人　と表示

		Person[] persons = {
			new Person("ノア", 16),
			new Person("ウタハ", 17),
			new Person("チヒロ", 17),
			new Person("ヒマリ", 17),
			null
		};

		System.out.println("現在の人数：" + Person.getCount() + "人");
		// 現在の人数：4人　と表示

		persons[4] = new Person("ユウカ", 16);

		System.out.println("現在の人数：" + Person.getCount() + "人");
		// 現在の人数：5人　と表示

		for (int i = 0; i < persons.length; i++) {
			persons[i].getInformation();
		}
		// 各インスタンスの名前と年齢を表示
	}
}
class Person {
	// インスタンスフィールド
	private String name;
	private int age;
	// クラスフィールド
	private static int count = 0;

	// コンストラクタ
	Person(String name, int age) {
		this.name = name;
		this.age = age;
		count++;
	}

	// クラスメソッド
	public static int getCount() {
		return count;
	}

	// インスタンスメソッド
	public String getName() {
		return name;
	}
	public int getAge() {
		return age;
	}
	public void getInformation() {
		System.out.println("名前：" + name + " " + "年齢：" + age);
	}
}
```

今まで`static`の役割を若干誤解していたフシがあったが、今回の学習で捉え直すことができたのではないかと思う。