見出し画像

JavaSilver間違いやすかった部分

・アクセス修飾子
・基本
public : 全てのクラスからアクセス可能
protected : 同じパッケージ内あるいはサブクラスからのみアクセス可能
private : 同じクラス内からのみアクセス可能
無名 : 無名パッケージ内の全てのクラスからアクセス可能
・重要
異なるパッケージに属しているクラスがアクセス可能なのは、publicとprotectedのみ。
※protectedは、継承関係にある時のみ。
※アクセス修飾子なし(無名パッケージ)は、同パッケージ内からしかアクセスする事ができない。


・Javaという言語の根幹的な部分
・Javaとは
ソースコードは人間が読みやすい形にしたものであり、コンピュータにとっては分かりづらいものである。
そのため、コンパイルする事によって実行しやすい形に翻訳し、最適化を行う。
コンパイルでは、JVMが読み込んで処理できるようにコードが翻訳される。
したがって、別の言語等でコンパイルしたファイル等を読み込んだりする事はできない。

コンパイラはコンパイルする際に、コードを実行する対象のプラットフォームを考慮しない。
コンパイラは、JVMが読み込める形式に翻訳するだけである。
そして、JVMが各プラットフォームで実行できる形式にコードを翻訳する。

他の言語では、コンパイルしたコードを1つのファイルにまとめて、実行時にメモリに全て読込むものもある。
しかし、Javaでは、必要に応じて、必要なクラスファイルを読込む。

・モジュールの設定ファイル
モジュールの設定ファイル(module-info.java)に各設定を書く。
モジュールの利用を記載する際はrequiresを使う。モジュールの公開する際はexportsを記載する。
module A {
requires B;
}
module B {
requires A;
}
上記のように記載すると、どちらのモジュールも相互に利用しあう相互依存の関係になる。
このような相互依存の状態では、コンパイラがAとBを延々と確認し続ける事になるため、コンパイルエラーとなる。
ー java.base
java.baseはどのモジュールでも必ず読込まれるモジュール。
loggingモジュールやxmlモジュールもjava.baseを読込む。

・jdeps, jmodコマンド
クラスの依存関係を調べたい時はjdepsコマンド。モジュールを作成したい時はjmodコマンド。

・javaコマンド
クラスの依存関係を調べるためにはjdepsを使う。
ー モジュールの依存関係
モジュールの依存関係を調べるためにはjavaコマンドの以下のオプションを使う。
--show-module-resolution
ー クラスファイルの出力
-dオプションを付ける事でクラスファイルの出力先を指定できる。
javaコマンドでは、完全修飾クラス名を指定する必要がある。
カレントディレクトリから見て完全修飾クラス名にマッピングされるディレクトリ構造が一致していないとクラスファイルを実行できない。
また、カレントディレクトリ以外にあるクラスファイルを実行する時は、javaコマンドに-cpオプションを付与する。
javac -d build ex15/Sample.java ex15/Main.java
java -cp build ex15.Main
ー モジュールの設定
以下のオプションを使う事でモジュールの設定を調べる事ができる。
--describe-module
ー モジュール内のプログラムの実行
モジュール内のプログラムを実行するには以下のコマンドを実行する。
java —-module-path モジュールのルートディレクトリ -m モジュールのクラス
- ファイルの実行
Sampleというクラスがある場合のjavaコマンドでの実行法
javac Sample.java, java Sample
java Saple.java


・インターフェース
・インターフェースの特徴
- 公開すべき扱い方を定めるためのもの
- インスタンス化できず、実現したクラスを必要とする
- 抽象メソッドを定義する
ー インターフェースもabstract宣言できる。
- 定数は定義できるがフィールドは定義できない。
インターフェースは、abstractをつけて宣言する事ができるが、つけなくてもコンパイラによって自動的にabstractであると解釈される。


・抽象クラスと抽象メソッド
・抽象クラスの特徴
- インターフェースを実現したクラスの共通部分を定義するためのもの
- インスタンス化する事ができず、継承したサブクラスを必要とする
- 抽象メソッドと具象メソッドの両方を記述できる

・抽象クラスと抽象メソッド
以下の2つのルールに従う必要がある。
ー abstractで修飾する
ー 実装を持たない

・抽象メソッド
インターフェースや抽象クラスに定義された抽象メソッドは、実現又は継承した具象クラスが実装しなければならない。
継承先で抽象メソッドが実装されていなければ、その継承先でコンパイルエラーとなる。

・抽象クラスと抽象メソッドの定義
抽象クラスには、実装を持たない抽象メソッドと実装を持つ具象メソッドを定義できる。
実装を持たない抽象メソッドを定義する際は、abstractで修飾する必要がある。
逆に、実装を持つ具象メソッドをabstractで修飾するとコンパイルエラーとなる。
public abstract class Sample {
int num;
public int calc() {
return num * 2;
}
public abstract int calculate() {
return num * 2;
}//コンパイルエラー
public abstract int samp()

・抽象メソッドの実装
インターフェースに定義されている抽象メソッドは、暗黙的に publicで修飾される。
スーパークラスで定義されたメソッドをオーバーライドする時に、より厳しいアクセス修飾子で修飾する事はできない。
interface Function {
void A();
}

class Sample implements Function {
protected void A() {~}
}

・interfaceとabstract
以下のように、AとBのinterfaceに、sampleメソッドとsample1メソッドの2つが抽象メソッドとして定義されている。
さらに、AとBのinterfaceを継承しているCという抽象クラスがあり、そのCを継承したDという実現クラスがある。
この場合、Dという実現クラスでは、AとBのinterfaceを定義しないと、Dクラスでコンパイルエラーとなる。
interface A {
public void sample();
}
interface B extends A {
public void sample1();
}
abstract class C extends B {
public void sample() {
System.out.println('a');
}
}
class D extends C {
public void sample() {
System.out.println('A');
}
/**
public void sample1() {
System.out.println('B');
}
**/
}

・インターフェースと抽象メソッド
インターフェースに定義されている抽象メソッドは、暗黙的にpublicで修飾されている。
そのため、以下のsampleメソッドは、publicで修飾されている事になる。
しかし、Aクラスでsampleメソッドをprotectedで修飾している。
より厳しい修飾子でオーバーライドすることはできないため、以下はコンパイルエラーとなる。
interface Function {
void sample();
}

class A implements Function {
protected void sample() {
}
}


・パッケージとアクセス修飾子
public class A {
int num = 1;
public Integer Sample() {
return 2;
}
}
public class B extends A {
public Integer Sample() {
return 3;
}
}
public class C extends B {
System.out.println(super.num);
}
Aクラスのnumはアクセス修飾子がない。
つまり、無名パッケージに属していることになる。
そのため、同パッケージ内であれば、上記はコンパイルエラーにならず、super.numで値が呼び出される。

・パッケージと無名クラス
クラスのアクセス修飾子に注目する。
以下のAクラスは無名クラスなので、以下のコードはコンパイルエラーとなる。
package a;
class A {
public String value;
public A(String value) {
this.value = value;
}
}

package b;
import a.A;
public class Main {
public static void main(String[] args) {
A a = new A(“Hello”);
System.out.println(a.value);
}
}


・リテラル
・整数リテラル
リテラルとは、ソースコード中に記述する値の事。
Javaには、整数・浮動小数点・真偽・文字の4つのリテラルがある。

long型である事を示したい時は数字の後ろに「L」や「l」、
float型である事を示したい時は数字の後ろに「F」や「f」をつける。
int型のリテラルは変数のデータ型に応じて自動的に変換されるため、byteやshortに対応した接尾辞はない。
整数リテラルは10進数の他に、16進数や8進数、2進数で記述する事もできる。
16進数であれば、接頭辞に「0x」をつける。
8進数であれば、接頭辞に「0」をつける。
2進数であれば、接頭辞に「0b」をつける。

・明示的な型変換
(基本)
1,大きな範囲の型を小さな型に変換する時は、明示的な型変換(キャスト)が必要。
2,数値を演算する時、演算子の両側のオペランドは同じ型でなければならない。
もし、オペランドの型が異なる場合には、小さい型は大きい方の型に自動的に変換される。
(例外)
byte型やshort型に整数リテラルを代入する場合、その値が型の範囲内であればコンパイルエラーにはならない。
short a = 10; //コンパイルエラーにはならない。

・暗黙的な型変換
小さなデータを大きなデータ型に代入する事
は可能。
→データに欠損が生ずる場合、NG
int a = 10;
float b = 10.0f;
a = b; //コンパイルエラー
a = (int)b; //コンパイルエラーにはならない。

・コンスタントプール
文字列リテラルは頻出する。
その度にインスタンスを生成していては、処理の負担が大きくなるし、メモリの消費は増える。
そのため、文字列リテラルはインスタンスとは異なるメモリ空間に作られ、そこへの参照がString型変数に代入される。
つまり、同じ文字列リテラルがプログラム内に出てくれば、そのメモリ空間にある文字列インスタンスへの参照が使い回しされる。
「str」という文字列リテラルを持つ変数aとbがあったとし、その変数aとbを==で比較するとtrueが返される。


・同一と同値
同一を確認する際には==を使う。
同値を確認する際にはequalsメソッドを使う。

・識別子
Javaでは、変数やメソッド、クラスなどの名前の事を「識別子」という。
基本的には自由に決める事ができるが、以下の規則に従う必要がある。
ー 予約語を識別子として使う事はできない。
ー 使える記号は、アンダースコア「_」と通貨記号のみ。
ー 数字から始めてはいけない。
*要約
識別子として使える記号は、アンダースコア(_)と通貨記号のみ。
!, (), [], {}などは記号であり、識別子としては使うことはできない。


・Stringクラス
・Stringクラスの扱い
Stringクラスのメソッドは、変更を保持しない。
そのため、メソッドの結果を代入しなければ、以下のように初期のまま変更が加えられない。
public class Sample {
public static void main(String[] args) {
String str = "abcde"
str.replace('a', 'x);
str.substring(2,3);
System.out.println(str); //abcde
}
}

・String nullの扱い方
String str = null;
str += null; //nullnull
String str = null;は、”null”となる。
*要約
String str = null;とすると、”null”となる。

・StringクラスのindexOf
String str = “abcde”;
System.out.println(str.indexOf(c)); //2
indexOfは、引数で渡された文字がどの位置に存在するかを調べるためのメソッド。
index値が返される。また、引数で指定した文字が存在しなければ-1が返される。
*要約
StringのindexOfメソッドは、引数として文字又は文字列を受け取る。
index値が返される。引数で渡された値が存在しなければ、-1が返される。

・Stringのsubstringメソッド
public String substring(int begin[,int end])
beginは開始位置、endは終了位置

public static void main(Sting[] args) {
String str = "Good Morning Everyone.";
System.out.println(str.substring(5); //Morning Everyone.
System.out.println(str.substring(5,12); //Morning
}

・java.lang.StringBuilderクラスのreplaceメソッド
第一引数と第二引数で指定した範囲の文字列を第三引数の文字に置き換える。


・Mathクラス
・roundメソッド
roundメソッドは、引数にdouble型、float型のいずれかを受け取り、四捨五入し整数にする。
roundメソッドは、小数点以下を四捨五入するためのメソッドであるため、引数は四捨五入される数字のみ、つまり1つしか受け取らない。


・例外
・throwableとException
Throwable
->Error
->Exception
->RuntimeException
上記からも分かるようにRuntimeExceptionはExceptionの子クラス。
クラスAとBを1つのクラスで実装する際には、throws Aの
みでよい。
public class A extends Exception{}
public class B extends RuntimeException{}

・検査例外と非検査例外
public class Main {
public static void main(String[] args) {
try {
sample(0);
} catch (IOException e) { }
}
private static void sample(int num) throws IOException, IndexOutOfBoundsException {
if (num < 10) {
throw new FileNotFoundException();
} else {
throw new IndexOutOfBoundsException();
}
}
}
FileNotFoundExceptionは、java.io.IOExceptionのサブクラスである。
IndexOutOfBoundsExceptionは、RuntimeExceptionのサブクラスであるため、catchブロックを書かなくても良い。

・無限ループと例外
無限ループとなるコードを実行すると、いずれメモリを使い切ってしまい、OutOfMemoryErrorという例外が発生する。
・LocalDateと例外
LocalDate a = LocalDate.of(2020, 0, 20);
とした際、java.time.DateTimeExceptionという例外が発生する。


・オーバーライド
・オーバーライドの条件
1、シグニクチャ(メソッド名と引数)が同じである事
2、戻り値型が同じ型かサブクラス型である事
3、アクセス修飾子が同じか、より緩いものである事
4、throwsで宣言する例外は、スローする例外と同じ型かサブクラス型である事。

・equalsメソッドのオーバーライド
equalsメソッドをオーバーライドする際には、色々な要件があるが一番重要なのは以下の一つ。
x.equals(null)はfalseを返す事。
ヌルポの例外は発生しない。

・オーバーライドとインスタンスの型
public class A {
public void sample(Collection a) {
System.out.pritnln("a");
}
}
public class B extends A {
public void sample(Collection a) {
System.out.println("b");
}
public void sample(List a) {
System.out.prinln("c");
}
}
public class Main {
public static void main(String[] args) {
A a1 = new A();
A a2 = new B();
B b1 = new B();
List<String> list = new ArrayList<>();
a1.sample(list); //a
a2.sample(list); //b
a3.sample(list); //c
}
}
a2ではA型で定義されているから、JVMはA型に定義されているsampleメソッドを探す。
しかし、オーバーライドされていればそちらを使うという決まりがあるため、a2はbを表示する。
また、listはList型でもありCollection型でもある。
そのため、Bクラスのどちらのメソッドにも対応している。
しかし、JVMはより厳密な方を選択するという特徴があるため、a3ではcが表示される。

・privateなメソッドのオーバーライド
privateなメソッドはオーバーライドできない。
privateなメソッドをオーバーライドしようとして、@Overrideアノテーションをつけて明示的にオーバーライドしようとするとコンパイルエラーとなる。


・オーバーロード
・複数のメソッドのオーバーロード
複数のメソッドがオーバーロードされている時、JVMは引数の型、順番、数で呼び出すメソッドを決定する。
以下では、String配列型を引数で渡している。
String配列は、ポリモーフィズムでObject配列としても扱う事ができる。
しかし、ポリモーフィズムが使われるのは、一致する型がない時である。
そのため、「B」が表示される。
public class Main {
public void test(Object[] array) {
System.out.println(“A”);
}
public void test(String[] array) {
System.out.println(“B”);
}
public static void main(String[] args) {
new Main().test(args);
}
}

・オーバーロードと呼びだし
オーバーロードされた複数のメソッドと完全一致しない引数を渡した場合、
コンパイラによって型変換され、対応するメソッドを呼び出すように最適化される。
また、配列はObjectクラスのサブクラスであるため、以下の場合、「test1」が出力される。
public class Sample {
public void test(Object[] val) {
System.out.println("test");
}
public void test(Object val) {
System.out.println("test1");
}
}

public class Main {
public static void main(String[] args) {
new Sample().test(new int[2]);
}
}


・オートボクシングとアンボクシング
プリミティブ型と参照型の2つがある。
プリミティブ型には8つの種類があり、それぞれに参照型であるラッパークラスが存在する。
プリミティブ型  ラッパークラス
boolean <=> Boolean
char <=> Character
byte <=> Byte
short <=> Short
int <=> Integer
long <=> Long
float <=> Float
double <=> Double
プリミティブ型からラッパークラスへの変換をオートボクシングという。
ラッパークラスからプリミティブ型への変換をアンボクシングという。
ー オートボクシング
int num = 10;
Integer numInt = num;
上記は、int型で宣言されたnumをInteger型へ変換される。
オートボクシングを使わずに上記を書くと以下のようになる。
int num = 10;
Integer numInt = new Integer(num);
//or
Integer numInt = Integer.valueOf(num);
ー アンボクシング
Integer numInt = new Integer(10);
int num = numInt;
アンボクシングを使わずに上記を書くと以下のようになる。
Integer numInt = new Integer(10);
int num = numInt.intValue();
*オートボクシングとアンボクシングの例
public static void main(String[] args) {
short s1 = 10;
Integer s2 = 20;
Long s3 = (long) s1 + s2;
//ここまでは実行される
String s4 = (String)(s3 + s2);
//Long型はStringへのキャストができないため、コンパイルエラーとなる
}


・文字と数字のキャスト
short a = (short) ‘A’;
//コンパイルエラーにならない
byte b = 10;
byte c = b;
//コンパイルエラーになる
文字は、文字番号で管理されているため、キャスト式を記述する事で、short型などに代入する事ができる。
しかし、キャスト式を記述しなければ、上記のbyte型のようにコンパイルエラーとなる。


・switch文
・switch文のcase値
switch文は式の値に対応するラベル(case値)へ処理を分岐させるもの。
case値として記載できる条件は以下となる。
1,条件式が戻す値と同じ型か互換性がある型である事
2,定数であるか、コンパイル時に値を決める事ができる事
3,nullでない事
※2の定数であるかについて
finalで宣言された変数かリテラルを指す。
public class Main {
public static void main(String[] args) {
final int NUM = 0;
int num = 5;
switch(num) {
case “200”: ~
case num:~
case NUM:~
}
}
}
上記の場合、条件式で渡されている型がint型であるので、case値もint型と互換性のある型である必要がある。
最初の「case “200”」でコンパイルエラーが発生する。
また、2つ目のcaseでは変数が渡されているのでコンパイルエラーとなる。
*要約
caseの条件
1,switch()の()の中と同じ型若しくは互換性のある型にする必要がある。
2,finalで宣言されている必要がある。

・switch文のdefault
switch文のdefaultをswitch文の最後に記載しなければならないというルールはない。
そのため、先頭に記載してもコンパイルエラーにはならない。
・continue文とbreak文
continue文は、ループ処理の途中で残りの処理をスキップして次のループ処理に移る。
break文は、ループ処理そのものを終わらせる。


・static
・staticフィールド
staticフィールドは、インスタンスを生成しなくても利用できる。
staticフィールドに対して、何らかの初期化処理をしたい時は、static初期化子を使う。
static初期化子は、複数指定でき、上から順に実行される。
public class Main {
private static int num;
static {
num = 10;
}
static {
num = 20;
}
public static void main(String[] args) {
System.out.println(num); //20
}
}

・staticフィールド
staticで修飾されたフィールドは、インスタンス毎ではなく、クラス毎に管理されるフィールドである。
そのため、countはインスタンス毎に0となり、valueはインスタンス内で継続される。
そのため、a.valueもb.valueも同じ値となる。
public class Sample {
Sample1 a = new Sample1();
Sample2 b = new Sample1();
a.test();
b.test();
System.out.println(a.value + ", " + b.value);
//10, 10
}

public class Sample1 {
static int value = 0;
int count = 0;
public void test() {
while(count < 5) {
count++;
value++;
}
}
}

・staticに関する問題
staticで修飾されたメソッドからは、staticで修飾されたもの以外にはアクセスできない。
以下では、staticなmainメソッドからstaticなtestメソッドにアクセスしているため、
ここではコンパイルエラーとならない。
しかし、sampleメソッドでは、staticではないnumフィールドをインクリメントしようとしているので、
コンパイルエラーが発生する。
public class Main {
int num;
private static void sample() {
num++
}
public static void main(String[] args) {
Main.test();
}
}


・配列
・配列と初期化子
配列で初期化子を使う時は、[]の中に数字を記入してはいけない。
・2次元配列
以下の一行目で、1次元配列が2つの枠で、2次元配列目が4つの枠が確保される。
int[][] array = new int[2][4];
array[0] = new int[] {1,2,3,4};
array[1] = new int[] {1,2};
for (int[] a : array) {
for (int b : a) {
System.out.println(b);
}
System.out.println();
}
//1234
12
・cloneメソッド
新しい配列を作り、その配列に同じ要素への参照をコピーする。
そのため、cloneメソッドで作られた配列と元の配列の参照先は同じである。
char[] array1 = {'a','b'}
char[] array2 = array1.clone();
array1 == array2; //true


・ローカル変数とフィールドの初期化
ローカル変数は初期化せずに参照すると、コンパイルエラーとなる。
フィールドは、初期化を明示的に行わずとも、自動的に初期化される。
int, byte, short, long型であれば0、float型は0.0F、double型は0.0D、char型は'¥u0000'、
boolean型はfalse、参照型はnull


・フィールドやメソッドの順番
通常、上から下に読込まれていく。
そのため、ローカル変数の宣言は、それを使う行よりも前に行う必要がある。
しかし、フィールドやメソッドは上で宣言していなくても、勝手に読込む。
public class Main {
public static void main(String[] args) {
System.out.println(num); //コンパイルエラーにならない
int a = b;//コンパイルエラー
int b;
}
static int num = 1;
}


・変数のスコープ
変数には、メソッド内で宣言するローカル変数とクラス内で宣言するフィールドの2種類ある。
ローカル変数内やフィールド内で同じ名前の変数名は使用する事ができない。
しかし、ローカル変数とフィールドでは同じ変数名でも問題はない。
以下では、ローカル変数とフィールドで同じ変数名が使用されている。
この場合、thisを使い、明示的にフィールドの変数名である事を示さない限り、ローカル変数が優先される。
そのため、以下では2,3と表示される
public class Test {
int a,b;
public Test(int a, int b) {
this.a = a * a;
this.b = b * b;
}
public static void main(String[] args) {
int a = 2, b = 3;
Test s = new Test(a,b);
System.out.println(a + "," + b);
//2,3
}
}


・varによるローカル変数の型推論
varが使えるのはローカル変数の宣言のみ。
フィールドの宣言や戻り値型に使うとコンパイルエラーとなる。


・フィールド
・finalで修飾されたフィールド
finalで修飾された変数は定数として扱われ、一度設定した値は変更する事ができない。
以下では、numフィールドがfinalで修飾されているが、初期化されていない。
そのため、コンストラクタで初期化する必要がある。
以下では、コンストラクタが3つあるが、最後のコンストラクタでは、numを初期化していない。
そのため、最後のコンストラクタでコンパイルエラーが発生する。
public class Test {
private final int num;
public Test(String str) {
this(Integer.parseInt(str);
}
public Test(int num) {
this.num = num;
}
public Test() {}
}


・ラムダ式
ー Consumer<T>
受け取る時はvoid accept(T)を使う。
引数を受け取って処理をするが、結果を戻さない。
ー Supplier<T>
受け取る時はget()を使う。
何も受け取らずに結果だけを戻す。
ー Predicate<T>
受け取る時はboolean test(T)を使う。
predicateは「断定する」という意味。
引数を受け取ってそれを評価する。
ー Function<T,R>
受け取る時はapply(T)を使う。
引数を受け取って、指定された型の結果を返す。


・try-with-resource
例外発生時に、自動的にcloseメソッドを呼び出し、リソースを開放する。
例外発生時は、以下の順で動作する。
①closeメソッドによるリソース開放
②catchブロック
③finallyブロック


・リストとMap
・java.util.Listインターフェースのofメソッド
ofメソッドを使用すると、引数で渡された要素を持つコレクションを作成できる。
引数に何も渡さなかった場合、要素数0のコレクションを作成できる。
java.util.Listインターフェースのofメソッドは変更できないコレクションを作る。
ofメソッドで作られたものを変更しようとすると、java.lang.UnsupportOperationExceptionという例外がスローされる。
・Mapインターフェース
Mapインターフェースは、key,valueの組み合わせで管理するコレクション。
keyを取り出すには、keySetメソッドを使用する。keySetメソッドはjava.util.Set型を戻す。
valueを取り出すにはvalueメソッドを使用する。valueメソッドはjava.util.Collection型のオブジェクトへの参照を戻す。


・拡張for文とnull
拡張for文では、次に取り出すものがない場合は、繰り返し処理を行わない。
そのため、以下のように、最初と最後がnullという場合は普通に実行できる。
String[] array = new String[3];
array[1] = 10;
for (String a : array) {
System.out.println(a);
//null a null
}


参照
java SE11 Silver問題集
https://www.javadrive.jp/start/if/index4.html
http://gi.ics.nara-wu.ac.jp/~takasu/lecture/programming/H26-gengo1-4.pdf
https://java-code.jp/795


この記事が気に入ったらサポートをしてみませんか?