まえがき
取得した知識と試験準備の印象を引き続き共有したいと思います。 このシリーズのゼロパートについて勧告を行ったすべての人に感謝します! 今日は、アクセス修飾子と、継承およびパッケージとの関係、可変引数と列挙、および配列とそれらを初期化する方法について説明します。 harazhiteliが再び応答し、私が言及するのを忘れた、または単に知らなかったことを補完することを願っています。
引き続き、試験の準備を継続しています。
シリーズ全体のコンテンツ
メソッド、フィールド、ローカル変数およびそれらの修飾子
前述したように、Javaには4つのアクセス修飾子があります:public、private、protected、および修飾子の欠如(デフォルトの修飾子です)。 ネストされていないクラスとインターフェースに適用されるのは、パブリックとデフォルト修飾子の2つだけです。 セット全体は、クラスのメソッドとフィールドに適用できます。
- メソッドまたはフィールドにパブリック修飾子がある場合、それらはユニバース全体で潜在的に利用可能です。
- メソッドまたはフィールドにプライベートアクセス修飾子がある場合、それらはクラス内でのみ使用できます。 このようなクラスのメンバーは継承されないため、サブクラスで置き換えることはできません。 これを覚えておいてください。
- メソッドまたはフィールドにデフォルトのアクセス修飾子がある場合、それらはパッケージ内でのみ使用可能です。
- メソッドまたはフィールドに保護されたアクセス修飾子がある場合、それらは主にクラス自体とその子孫にアクセスできます。 さらに、クラスのこれらのメンバーにアクセスすると、兄弟をパッケージに入れることができます。
試験の一環として提案されたコードを確認するには、注意が必要です。 メソッドまたはフィールドアクセス修飾子とクラスアクセス修飾子の両方に常に注意を払ってください。 多くの場合、メソッドにはpublic修飾子があり、それを含むクラスにはパッケージからのみアクセスできるという状況があります。 この状況では、パッケージ外部からのメソッドは使用できません。 この詳細に注意を払うことなく、簡単にマイナスを取得できます。
また、デフォルトのアクセスと保護された修飾子を使用するときに生じるいくつかの機能にも注意を払いたいと思います。 次の例を考えてみましょう。 テストパッケージで宣言された基本クラスがあるとします。 このクラスには2つのフィールドがあります。 最初はデフォルトアクセスで宣言され、2番目は保護されています。
package org.kimrgrey.scjp.test; public class BaseClass { int defaultValue; protected int protectedValue; public BaseClass() { this.defaultValue = 1; this.protectedValue = 1; } }
package org.kimrgrey.scjp.test; public class BaseClass { int defaultValue; protected int protectedValue; public BaseClass() { this.defaultValue = 1; this.protectedValue = 1; } }
BaseClassを継承しないこのパッケージでSamePackageAccessクラスを宣言した場合、defaultValueフィールドとprotectedValueフィールドの両方にアクセスできます。 protected修飾子のこの機能について覚えておく価値があります。パッケージ内で保護されていると宣言されたクラスメンバーは、継承とリンクの両方で利用できます。 例:
package org.kimrgrey.scjp.test; public class SamePackageAccess { public SamePackageAccess() { BaseClass a = new BaseClass(); a.defaultValue = 2; a.protectedValue = 2; } }
package org.kimrgrey.scjp.test; public class SamePackageAccess { public SamePackageAccess() { BaseClass a = new BaseClass(); a.defaultValue = 2; a.protectedValue = 2; } }
このパッケージの継承の場合、アクセスは参照と継承の両方で両方のフィールドに保存されます。
package org.kimrgrey.scjp.test; public class SamePackageSubclass extends BaseClass { public SamePackageSubclass() { this.defaultValue = 3; this.protectedValue = 3; BaseClass a = new BaseClass(); a.defaultValue = 3; a.protectedValue = 3; } }
package org.kimrgrey.scjp.test; public class SamePackageSubclass extends BaseClass { public SamePackageSubclass() { this.defaultValue = 3; this.protectedValue = 3; BaseClass a = new BaseClass(); a.defaultValue = 3; a.protectedValue = 3; } }
次に、パッケージを超えた場合に何が起こるかを見てみましょう。 最初に起こることは、アクセス修飾子を明示的に指定せずに宣言されたフィールドへのアクセスを失うことです。 BaseClassの直接の子孫を含む、ネイティブパッケージの外部のすべてのクラスは、それをまったく認識しません。 protected修飾子を持つフィールドは、そのすべてのサブクラスへの継承を通じて利用可能になります。 ただし、継承者でさえ、リンクを介して使用することはできません。 さらに、クラスがパッケージの外部に継承されると、フィールドはすべてのクラスに対して閉じられますが、それ以外の子孫は例外です。
package org.kimrgrey.scjp.main; import org.kimrgrey.scjp.test.BaseClass; public class OtherPackageSubclass extends BaseClass { public OtherPackageSubclass() { this.defaultValue = 10; // Line 8: , this.protectedValue = 10; BaseClass a = new BaseClass(); a.protectedValue = 10; // Line 12: BaseClass } }
package org.kimrgrey.scjp.main; import org.kimrgrey.scjp.test.BaseClass; public class OtherPackageSubclass extends BaseClass { public OtherPackageSubclass() { this.defaultValue = 10; // Line 8: , this.protectedValue = 10; BaseClass a = new BaseClass(); a.protectedValue = 10; // Line 12: BaseClass } }
この例には、別の重要な詳細も含まれています。 上記のコードをコンパイルするとどうなるのかと聞かれたとしますか? また、次の回答オプションを提供します。
- コードは正常にコンパイルされます
- 行番号8でコンパイルエラーが発生する
- 行番号12でコンパイルエラーが発生します。
この場合、特に明記されていない限り、すべての正解オプション(2および3)を選択する必要があります。最初の適切なオプションで停止しないでください。 すべての回答を注意深く読み、正しさを確認してください。 このアプローチが最も効果的です。 質問の本質を読んで理解した後、文言で与えられたコードではなく、答えを確認して分析します。
継承に関連する修飾子の中で、finalも考慮する必要があります。 Finalは、クラスと同じ方法でメソッドに作用します。継承者による再定義を禁止します。 同時に、finalメソッドが配置されているクラス自体を拡張することもできます。
final修飾子は、フィールド、メソッド引数、およびローカル変数に適用できます。 プリミティブ型の場合、初期化を除き、変数の値の変更は禁止されます。 ローカル変数の初期化の瞬間は、メソッドのフレームワーク内でのローカル変数への最初の値の割り当てと見なされることに注意してください。 これより前は、変数を使用できません。コンパイルエラーが発生します。 マークされた最終フィールドも明示的に初期化する必要があります。 これは、宣言中、初期化ブロック内、または宣言されているクラスのコンストラクターで直接実行できます。 最終フィールドの初期化を相続人の良心に任せることは許可されていません。 リンクの場合、最後の修飾子はリンクの再割り当てを禁止します。 リンクが指すオブジェクト自体は引き続き変更できます。状態を変更するメソッドを呼び出し、フィールドに新しい値を割り当てます。
final以外の修飾子はローカル変数に適用できないことに注意してください。 したがって、ローカル変数宣言に
private int a
などが表示された場合、これはコンパイルされないと安全に言えます。 しかし、フィールドはどうですか?
- 前述したように、4つのアクセスレベルはすべてフィールドに適用できます。
- フィールドは最終としてマークできます。
- フィールドは一時的としてマークされる場合があります。
- フィールドは静的としてマークできます。
- フィールドはvolatileとしてマークされている場合があります。
- フィールドを抽象としてマークすることはできません。
- フィールドを同期済みとしてマークすることはできません。
- フィールドをstrictfpとしてマークすることはできません。
- フィールドをネイティブとしてマークすることはできません。
上記の修飾子の一部については、これまで説明していません。 これらについては、関連するトピックで後ほど検討します(一時的はシリアル化の一部と見なされ、マルチスレッドでは同期化され揮発性になります)。
可変引数メソッド
- varargパラメーターを指定する場合、基本型は任意の型(プリミティブ型または非型)にできます。
- このようなパラメーターを宣言するには、型、3つのピリオド、スペース、メソッド内で使用される配列の名前を記述します:
void f (int... a)
。 また、次のように、タイプ、3つのドット、および識別子をスペースで区切ることもできvoid f(int ... a)
。 ドットを注意深く見てください。 試験の著者は、それらを識別子で転送することを好みます。 このアプローチは機能しません。 - 他のパラメーターをメソッドに渡すこともできますが、この場合、varargパラメーターは最後でなければなりません:
void f (double x, int... a)
- メソッドには、ただ1つのvarargパラメーターを含めることができます。
わかりやすくするために、このトピックに関する質問の良い例を挙げます。 doSomething()メソッドの宣言を選択して、以下のコードが正常にコンパイルされるようにしますか?
package org.kimrgrey.scjp.main; public class Application { public static void main(String[] args) { doSomething(1); doSomething(1, 2); } }
package org.kimrgrey.scjp.main; public class Application { public static void main(String[] args) { doSomething(1); doSomething(1, 2); } }
-
static void doSomething(int... values) {}
-
static void doSomething(int[] values) {}
-
static void doSomething(int x, int... values) {}
最初と3番目のオプションは正しいです。 それと他の両方は、呼び出しのセマンティクスの観点と構文の観点の両方から正しいです。 配列を型として使用していくつかのパラメーターを渡すのはとても簡単です。 しかし、その反対は真実ではありません。 例:
package org.kimrgrey.scjp.main; public class Application { private static void f (int... a) { for (int i = 0; i < a.length; ++i) { System.out.println(a[i]); } } public static void main(String[] args) { f(new int[] {1, 2 ,3}); } }
package org.kimrgrey.scjp.main; public class Application { private static void f (int... a) { for (int i = 0; i < a.length; ++i) { System.out.println(a[i]); } } public static void main(String[] args) { f(new int[] {1, 2 ,3}); } }
すべてが素晴らしく組み立てられ、機能します。 これについての正式な説明はわかりませんが、これはvarargパラメーターが単なる構文上の糖であり、コンパイラーによって配列への参照として認識されるという事実によると思われるため、問題はありません。
乗り換え
- 列挙にはコンストラクタを含めることができます。
- 列挙にはフィールドを含めることができます。
- 列挙にはメソッドを含めることができます。
- 列挙がクラスの外部で宣言されている場合、パブリックまたはデフォルトの2つのアクセスレベルしか取得できません。
- 列挙には静的メソッドの
values()
があり、列挙の可能なすべての値を含む配列を、厳密に宣言された順序で返します。
列挙内の値ごとに、独自の「本文」、つまりその具体的な説明を宣言できます。 さらに、メソッドの値固有のバージョンは、列挙全体全体に使用されるバージョンをオーバーロードします。 これにより、アプリケーションのニーズに応じて列挙型メンバーの動作を変更できます。 例:
package org.kimrgrey.scjp.main; import static java.lang.System.*; enum Currency { UNKNOWN, USD { public String getStringCode() { return "USD"; } public int getSomethingElse() { return 10; } }, UAH { public String getStringCode() { return "UAH"; } }, RUR { public String getStringCode() { return "RUR"; } }; public String getStringCode() { return ""; } } public class Application { private static void f (int... a) { for (int i = 0; i < a.length; ++i) { out.println(a[i]); } } public static void main(String[] args) { out.println(Currency.USD.getStringCode()); // out.println(Currency.USD.getSomethingElse()); } }
package org.kimrgrey.scjp.main; import static java.lang.System.*; enum Currency { UNKNOWN, USD { public String getStringCode() { return "USD"; } public int getSomethingElse() { return 10; } }, UAH { public String getStringCode() { return "UAH"; } }, RUR { public String getStringCode() { return "RUR"; } }; public String getStringCode() { return ""; } } public class Application { private static void f (int... a) { for (int i = 0; i < a.length; ++i) { out.println(a[i]); } } public static void main(String[] args) { out.println(Currency.USD.getStringCode()); // out.println(Currency.USD.getSomethingElse()); } }
このコードを実行した結果、文字列「USD」が標準出力ストリームに配置されます。 getSomethingElse()メソッドに注意してください。 USDの値に対して宣言されていますが、リスト全体については言及されていません。 広告は公開されているにもかかわらず、外部から誰もこのメソッドにアクセスできません。 44行目がコメント解除されている場合、コードはコンパイルされません。
配列について少し
Javaで配列を宣言するには、2つのオプションがあります。 角かっこは、型名の後に
int[] a
、または識別子の後に
int a[]
ように配置できます。 最初の方法をお勧めしますが、両方の方法は構文の点で完全に等しいことを理解することが重要です。 したがって、
String[] s[]
は、文字列の2次元配列にすぎません。 疑いなくコンパイルします。
メモリは配列が作成されたときにのみ割り当てられるため、配列を宣言するとき、サイズを指定することはできません:
int[] a = new int [4]
。 したがって、
int a[4]
コードはコンパイルエラーの原因になります。 オブジェクト参照の配列の場合、配列を作成するとき、オブジェクト自体は作成されないことに注意することが重要です。 たとえば、
Thread threads = new Thread [20]
というコードは、20個のnullの配列を作成し、コンストラクターは呼び出されません。
多次元配列を作成する場合、それらを配列と考える必要があり、その各要素は再び配列を参照します。 マトリックスやキューブなどの抽象的な構成はプログラミングを簡素化しますが、試験の合格を難しくする可能性があります。 たとえば、構成
int [][]a = new int [10][]
は非常に受け入れられ、2次元配列を作成します。その要素は後で初期化できます。a
a[0] = new int [100]
、必ずしも配列と等しいわけではありません長さ:
a[1] = new int [200]
。
(要素ごとではなく)配列をすばやく初期化するには、次のような構文を使用できます
int[] x ={1, 2, 3}
。 中括弧には、定数だけでなく、変数や式も含めることができます。 匿名配列を作成することもできます。これは、厳密に定義された配列を関数に渡す必要がある場合によく使用されます:
f(new int [] {2, 4, 8})
。 試験でそのようなデザインが見られる場合は、必ずよく見てください。 このような何かが書かれる可能性があります:
f(new int[3] {2, 4, 8})
。 匿名配列のサイズは宣言に基づいて計算され、明示的に指定されるべきではないため、このようなコードはコンパイルされません。
これで私は今日で終わります。 近い将来、Javaのいくつかの重要な操作(割り当て、比較、instanceOf、算術)の機能、および暗黙のクラスとストリームについて明確に話します。