なぜ内部クラスが必要なのか、なぜ内部クラスを使用するのかをお聞きしたいのですが?
内部クラスの使用方法は知っていますが、その理由はわかりません..
7 に答える
一部の内部クラスは (Java などで) 公開さMap.Entry
れていますが、それは標準ではなく例外です。
内部クラスは、基本的に実装の詳細です。
たとえば、Swing はイベント リスナの内部クラスを広範囲に使用します。それらがなければ、それ以外の場合は表示する必要のない一連のクラスでグローバル名前空間を汚染することになります (その目的を判断するのが難しくなる可能性があります)。
基本的に、内部クラスはスコープの形式です。パッケージ アクセスは、パッケージの外部からクラスを隠します。プライベート内部クラスは、そのクラスをそのクラスの外部から隠します。
Java の内部クラスは、関数ポインターまたはメソッド デリゲート (C# にある) またはクロージャーの欠如の代わりにもなります。それらは、関数を別の関数に渡す手段です。たとえば、次のようExecutor
なクラスがあります。
void execute(Runnable r);
そのため、メソッドを渡すことができます。C/C++ では、次の方法で実現できます。
void execute(void (*run)());
関数へのポインタです。
ウィキペディアのこの部分は、内部クラスが必要な理由を理解するのに役立つかもしれません:
通常または最上位クラスのインスタンスは、単独で存在できます。対照的に、内部クラスのインスタンスは、最上位クラスにバインドされていないとインスタンス化できません。
4 つの車輪を持つ自動車の抽象的な概念を考えてみましょう。当社のホイールには、車の一部であることに依存する特定の機能があります。この概念は、ホイールを車両の一部である可能性があるより一般的な形式のホイールとして表すものではありません。代わりに、これに固有のものとしてそれらを表します。次のように、内部クラスを使用してこの概念をモデル化できます。
トップレベルの車があります。クラス Car のインスタンスは、クラス Wheel の 4 つのインスタンスで構成されます。この Wheel の特定の実装は自動車に固有であるため、コードは、最上位クラスとしてより適切に表現される Wheel の一般的な概念をモデル化していません。したがって、これはクラス Car に意味的に接続されており、Wheel のコードは何らかの方法でその外部クラスに結合されています。
内部クラスは、この接続を正確にモデル化するメカニズムを提供します。ホイール クラスは Car.Wheel であり、Car がトップレベル クラスで、Wheel が内部クラスです。
したがって、内部クラスは、そうでなければクラスにカプセル化されないプログラムの特定の部分のオブジェクト指向を可能にします。
Java の匿名内部クラスは、アダプター パターンを使用する方法です。
interface Bar
{
public void bar();
}
class Foo
{
public void foo()
{
// do something relevant
}
// it happens that foo() defines the same contract (or a compatible one) as
// Bar.bar(); with an anonymous inner class we can adapt Foo to the Bar
// interface
public Bar asBar()
{
// return an instance of an anonymous inner class that implements
// the Bar inteface
return new Bar()
{
public void bar()
{
// from an inner class, we can access the enclosing class methods
// as the "this pointers" are "linked"
foo();
}
};
}
}
Java では、内部クラスとネストされたクラスの違いを理解していることを確認してください。
内部クラスは、それを囲むクラスのインスタンスに関連付けられており、そのオブジェクトのメソッドとフィールドに直接アクセスできます
C# には、Java の意味での内部クラスはなく、ネストされたクラスのみがあります。
このInner Class Exampleも参照してください。
ほとんどの場合、インナー クラスを使用するのは、インナー クラスが他の言語で利用可能なクロージャの概念に最も近いからです。これにより、外側のスコープの変数にアクセスできる内側のネストされたスコープのオブジェクトを作成して操作できます。Listener
これは、特にコールバックの作成 (たとえば、Swing でのさまざまな の定義) に役立ちます。
BetaやNewspeakのように、内部クラスをかなり異なるレベルに引き上げる言語があります。これらの言語では、クラスのネストはパッケージ化として機能します(つまり、パッケージはありません)。
このビジョンの詳細については、「モジュールの概念はいくつ必要ですか?」を参照してください。オブジェクトチームのブログ。彼のブログでGiladBrachaの作品も参照してください...
たとえば、クラス ebook があり、ebookPrice がある場合、ebook クラスの間に ebookPrice を囲みます。これは、ebook クラスに関連しており、(少なくとも概念的には) 内部でしか使用できないためです。
ebookPrice は、より上位のスコープにあり、他のすべてのクラスに関連する Price から継承できます。
(ちょうど私の2セント)。
オブジェクト指向の利点
私の謙虚な意見では、内部クラスの最も重要な機能は、通常はオブジェクトに変換できないものをオブジェクトに変換できることです。これにより、コードを内部クラスを使用しない場合よりもさらにオブジェクト指向にすることができます。
メンバークラスを見てみましょう。そのインスタンスは親インスタンスのメンバーであるため、親のすべてのメンバーとメソッドにアクセスできます。一見すると、これは大したことではないように思えるかもしれません。親クラスのメソッド内から、そのようなアクセスをすでに持っています。ただし、メンバー クラスを使用すると、親からロジックを取り出してオブジェクト化できます。たとえば、ツリー クラスには、ツリーの検索またはウォークを実行する 1 つのメソッドと多くのヘルパー メソッドが含まれる場合があります。オブジェクト指向の観点からは、ツリーはツリーであり、検索アルゴリズムではありません。ただし、検索を実行するには、ツリーのデータ構造に関する詳細な知識が必要です。
内部クラスを使用すると、そのロジックを削除して独自のクラスに配置できます。したがって、オブジェクト指向の観点から、私たちは機能が属していないところから機能を取り除き、それを独自のクラスに入れました。内部クラスを使用することで、検索アルゴリズムをツリーから分離することに成功しました。ここで、検索アルゴリズムを変更するには、単純に新しいクラスに交換できます。先に進むこともできますが、これにより、オブジェクト指向の手法によって提供される多くの利点がコードにもたらされます。
組織上の利点
オブジェクト指向設計は万人向けではありませんが、幸いなことに、内部クラスはそれ以上のものを提供します。組織的な観点から見ると、内部クラスを使用すると、名前空間を使用してパッケージ構造をさらに整理できます。フラットなパッケージにすべてをダンプする代わりに、クラスをクラス内にさらにネストできます。明らかに、内部クラスがないため、次の階層構造に制限されていました。
package1
class 1
class 2
...
class n
...
package n
内部クラスを使用すると、次のことができます。
package 1
class 1
class 2
class 1
class 2
...
class n
慎重に使用すると、内部クラスは、より自然にクラスに適合する構造階層を提供できます。
コールバックの利点
内部メンバー クラスと匿名クラスはどちらも、コールバックを定義するための便利な方法を提供します。最もわかりやすい例は、GUI コードに関するものです。ただし、コールバックの適用は多くのドメインに拡張できます。
ほとんどの Java GUI には、actionPerformed() メソッド呼び出しを引き起こす何らかのコンポーネントがあります。残念ながら、ほとんどの開発者はメイン ウィンドウに ActionListener を実装しているだけです。その結果、すべてのコンポーネントが同じ actionPerformed() メソッドを共有します。アクションを実行したコンポーネントを特定するために、通常は actionPerformed() メソッドに巨大で醜いスイッチがあります。
モノリシック実装の例を次に示します。
public class SomeGUI extends JFrame implements ActionListener {
protected JButton button1;
protected JButton button2;
//...
protected JButton buttonN;
public void actionPerformed(ActionEvent e) {
if (e.getSource() == button1) {
// do something
} else if (e.getSource() == button2) {
//... you get the picture
}
}
}
スイッチや大きな if/if else ブロックが表示されるたびに、大きな警報ベルが頭の中で鳴り始めます。一般に、このような構成はオブジェクト指向設計としては不適切です。コードの 1 つのセクションを変更すると、対応する switch ステートメントの変更が必要になる場合があるからです。内部メンバー クラスと匿名クラスにより、切り替えられた actionPerformed() メソッドから逃れることができます。
代わりに、リッスンするコンポーネントごとに ActionListener を実装する内部クラスを定義できます。その結果、多くの内部クラスが発生する可能性があります。ただし、大きな switch ステートメントを避けることができ、アクション ロジックをカプセル化するという追加のボーナスを得ることができます。さらに、そのアプローチにより、パフォーマンスが向上する可能性があります。n 回の比較があるスイッチでは、平均的なケースで n/2 回の比較が期待できます。内部クラスを使用すると、アクション実行者とアクション リスナーの間に 1 対 1 の対応を設定できます。大規模な GUI では、このような最適化はパフォーマンスに大きな影響を与える可能性があります。匿名のアプローチは次のようになります。
public class SomeGUI extends JFrame {
// ... button member declarations ...
protected void buildGUI() {
button1 = new JButton();
button2 = new JButton();
//...
button1.addActionListener(
new java.awt.event.ActionListener() {
public void actionPerformed(java.awt.event.ActionEvent e) {
// do something
}
});
// .. repeat for each button
}
}
内部メンバー クラスを使用すると、同じプログラムは次のようになります。
public class SomeGUI extends JFrame
{
... button member declarations ...
protected void buildGUI()
{
button1 = new JButton();
button2 = new JButton();
...
button1.addActionListener(
new java.awt.event.ActionListener()
{
public void actionPerformed(java.awt.event.ActionEvent e)
{
// do something
}
}
);
.. repeat for each button
内部クラスは親のすべてにアクセスできるため、モノリシックな actionPerformed() 実装に表示されるロジックを内部クラスに移動できます。
私はメンバー クラスをコールバックとして使用することを好みます。ただし、それは個人の好みの問題です。あまりにも多くの匿名クラスがコードを乱雑にしているように感じます。また、匿名クラスが 1 行または 2 行を超えると扱いにくくなる可能性があると感じています。
短所は?
他のことと同様に、良いことと悪いことを一緒に取らなければなりません。内部クラスには欠点があります。メンテナンスの観点から、経験の浅い Java 開発者は内部クラスを理解するのが難しいと感じるかもしれません。内部クラスを使用すると、コード内のクラスの総数も増加します。さらに、開発の観点から見ると、ほとんどの Java ツールは内部クラスのサポートが少し不足しています。たとえば、私は日々のコーディングに IBM の VisualAge for Java を使用しています。内部クラスは VisualAge 内でコンパイルされますが、内部クラスのブラウザーやテンプレートはありません。代わりに、内部クラスをクラス定義に直接入力するだけです。残念ながら、内部クラスの参照が困難になります。VisualAgeの多くを失うため、入力することも困難です。