ホームページ  >  記事  >  Java  >  Java 内部クラスの詳細な紹介

Java 内部クラスの詳細な紹介

黄舟
黄舟オリジナル
2017-03-14 11:51:301188ブラウズ


概要 :

複数の 継承 は、同時に複数の親クラスから の動作や特性を継承できるクラスを指します。データ セキュリティ 、それ 単一継承のみが許可されます。しかし、場合によっては多重継承を達成する必要があり、そのような状況は現実の相続などに実際に存在します。つまり、私たちは父親の行動と特徴と母親の行動と特徴の両方を受け継ぎます。幸いなことに、Java は、曲がりくねった方法で多重継承を実装するための 2 つの方法、インターフェース と内部クラスを提供します。 実際、多重継承の実装は、内部クラスの非常に重要なアプリケーションです。 さらに、内部クラスも適切に隠すことができます (たとえば、プライベートメンバーの内部クラス)。 内部クラスには、メンバー内部クラス、静的内部クラス、ローカル内部クラス、匿名内部クラスの 4 種類があります。特に、

  • メンバーの内部クラス: メンバーの内部クラスは、外部クラスのメンバーであり、外部クラスにアタッチされているため、内部クラスのオブジェクトは、外部クラスオブジェクトの場合にのみ作成できます。が最初に作成されます。このため、メンバーの内部クラスには static 変数 とメソッドを含めることができません。外部クラス オブジェクト では、内部クラス オブジェクトを直接作成できますが、外部クラスのすべての静的メンバーと静的メソッドにのみ直接アクセスできます。

  • 部分内部クラス:

    部分内部クラスは、スコープが変更されたことを除いて、メンバーの内部クラスと同じようにコンパイルされます。このメソッドと属性でのみ使用でき、メソッドと属性が

  • の外にある場合は無効になります。

    匿名内部クラス: 匿名内部クラスを定義するための前提条件は、内部クラスがクラスを継承するかインターフェイスを実装する必要があるということです。形式は new 親クラスまたはインターフェイス () {define the content of theサブクラス ( 関数

    wait など)}。
  • 言い換えれば、匿名内部クラスが最終的に提供するものは、匿名サブクラスのオブジェクトです。

    1.内部クラスの概要 内部クラスとは、クラス内で定義されたクラスのことを指します。クラス名はソースファイル名と同じである必要はありません。 内部クラスはコンパイル時の概念であり、コンパイルが成功すると、内部クラスと外部クラスは 2 つの完全に異なるクラスになります。 たとえば、Outer という名前の外部クラスと、その内部で定義された Inner という名前の内部クラスの場合、コンパイルが完了すると、Outer.

    class
  • と、Outer$inner.class という 2 つのクラスが表示されます。
したがって、内部クラスのメンバー変数/メソッド名は、外部クラスのメンバー変数/メソッド名と同じにすることができます。

内部クラスは静的にすることも、public、default、protected、private で変更することもできます。 特に、

Java ソース ファイル名とクラス名の関係について (Java ソース ファイル名の命名は内部クラスとは何の関係もありません。以下の 3 つのルールに含まれるクラスとインターフェイスはすべて外部クラス/インターフェイスを参照します) 、次の 3 つのルールに従う必要があります:

Java ソース ファイルにパブリック クラス (パブリック インターフェイス) が含まれる場合、ソース ファイル名はパブリック クラス名 (パブリック インターフェイス名) と同じである必要があります。 )。 Javaソースファイル内にパブリッククラスやパブリックインターフェースがある場合、パブリッククラスやパブリックインターフェースは1つだけ存在でき、複数のパブリッククラスやパブリックインターフェースは存在できません。もちろん、Java ソース ファイルには、パッケージから見える複数のクラスまたはインターフェイス、つまりデフォルトのアクセス修飾子を含めることができます (クラス名の前にアクセス修飾子はありません)。ファイル内のパブリック クラス (インターフェイス) とパッケージ可視クラス (インターフェイス) の順序は任意です。つまり、パブリック クラス (インターフェイス) が最初の位置にある必要はありません。

  • Javaソースファイルにパブリッククラス(パブリックインターフェース)が含まれていない場合、Javaソースファイル名に制限はありません。
    ファイル名の命名規則に従っている限り、ファイル内のクラスやインターフェイスと同じ名前である必要はありません。もちろん、それらのいずれかと同じ名前であってもかまいません。

  • クラスとインターフェースの名前が競合することはできません。
    同じパッケージ内のクラスまたはインターフェイスに同じ名前を付けることはできません。異なるパッケージ内のクラスまたはインターフェイスはパッケージによって区別できるため、同じ名前を付けることができます。


  • 2. 内部クラスの役割

    内部クラスを使用すると、次の利点がもたらされます:

    • 内部クラスを適切に隠すことができます (一般的な非内部クラスは protected でプライベートにすることはできません)権限はありますが、内部クラスは可能です);

    • 内部クラスは外部クラスのすべての要素へのアクセス権を持ちます。

    • インターフェイスを変更して、同じクラス内の同じ名前の 2 つのメソッドへの呼び出しを実装します。

    • 1) 内部クラスはうまく隠すことができます

      通常、クラスへのアクセス権は、クラスの前にあるアクセス修飾子によって制限されます。一般に、非内部クラスは、保護されたアクセス許可を使用してプライベートにすることはできません。 、しかし内部クラスはできるので、内部クラスを通じて情報を隠すことができます。次の例を見てください:

      //测试接口public interface InterfaceTest {
          public void test();
      }//外部类public class Example {
      
          //内部类
          private class InnerClass implements InterfaceTest{
              @Override
              public void test() {
                  System.out.println("I am Rico.");
              }
          }    //外部类方法
          public InterfaceTest getInnerInstance(){        return new InnerClass();
          }
      }//客户端public class Client {
          public static void main(String[] args) {
              Example ex = new Example();
              InterfaceTest test = ex.getInnerInstance();
              test.test();
          }
      }/* Output:
              I am Rico.
       *///:~

    • クライアントの場合、Example の getInnerInstance() メソッドを通じて InterfaceTest インスタンスを取得できますが、このインスタンスがどのように実装されているかはわかりません。対応する特定のクラスの実装。 InnerClass はプライベートであるため、ソース コードを見なければ、このインターフェイスを実装する特定のクラスの名前さえ確認できないため、内部クラスをうまく隠すことができます。

    2) 内部クラスは外部クラスのすべての要素へのアクセス権を持っています

    //外部类public class Example {
        private String name = "example";    //内部类
        private class Inner{
            public Inner(){
                System.out.println(name);   // 访问外部类的私有属性
            }
        }    //外部类方法
        public Inner getInnerInstance() {        return new Inner();
        }
    }//客户端public class Client {
        public static void main(String[] args) {
            Example ex = new Example();
            ex.getInnerInstance();
        }
    }/* Output:
            example
     *///:~

    名前 このメンバ変数は例で定義したプライベート変数です この変数は内部クラス内で無条件にアクセスできます。

    3)多重継承が実現できる

    多重継承に関しては、インターフェースは問題の一部しか解決しないが、内部クラスは多重継承の解決をより完全なものにすると言える。内部クラスは Java の継承メカニズムをより完全なものにし、内部クラスが存在する最大の理由です。 Java のクラスは 1 つのクラスのみを継承でき、その多重継承は内部クラスを学習する前にインターフェイスを使用して実装されます。しかし、インターフェイスの使用には多くの不便な点がある場合があります。たとえば、インターフェイスを実装する場合、そのインターフェイス内のすべてのメソッドを実装する必要があり、内部クラスがインターフェイスの制限を回避して、クラスに複数の具象クラスや

    抽象クラス

    を継承させることができます。 .セックス。

    以下の例を見てください:

    //父类Example1public class Example1 {
        public String name() {        return "rico";
        }
    }//父类Example2public class Example2 {
        public int age() {        return 25;
        }
    }//实现多重继承的效果public class MainExample {
    
        //内部类Test1继承类Example1
        private class Test1 extends Example1 {
            public String name() {            return super.name();
            }
        }    //内部类Test2继承类Example2
        private class Test2 extends Example2 {
            public int age() {            return super.age();
            }
        }    public String name() {        return new Test1().name();
        }    public int age() {        return new Test2().age();
        }    public static void main(String args[]) {
            MainExample mexam = new MainExample();
            System.out.println("姓名:" + mexam.name());
            System.out.println("年龄:" + mexam.age());
        }
    }/* Output:
            姓名:rico
            年龄:25
     *///:~

    このクラスのクラス MainExample には 2 つの内部クラス Test1 と Test2 が含まれていることに注意してください。このうち、クラス Test1 はクラス Example1 を継承し、クラス Test2 はクラス Example2 を継承します。このように、クラスMainExampleはクラスExample1とクラスExample2のメソッドを持ち、間接的に多重継承を実現している。

    4) 同じクラス内で同じ名前の 2 つのメソッドの呼び出しを実装するためにインターフェースを変更することは避けてください クラスがクラスを継承してインターフェースを実装するが、継承するクラスとインターフェース内では次のような状況を考えてみましょう。 2 つの同一のメソッド (メソッド シグネチャが同じ) では、どうやってそれらを区別すればよいでしょうか?これには内部クラスを使用する必要があります。例えば

    //Test 所实现的接口public interface InterfaceTest {
        public void test();
    }//Test 所实现的类public class MyTest {
        public void test(){
            System.out.println("MyTest");
        }
    }//不使用内部类的情形public class Test extends MyTest implements InterfaceTest{
        public void test(){
            System.out.println("Test");
        }
    }

    このとき、Testのtest()メソッドはMyTestをカバーするtest()メソッドに属するのか、それともInterfaceTestのtest()メソッドを実装しているのか? MyTest でメソッドを調整するにはどうすればよいでしょうか?明らかに、これを区別するのは困難です。そして、内部クラスを使用すれば、この問題は簡単に解決できます。次のコードを見てください:

    //Test 所实现的接口public interface InterfaceTest {
        public void test();
    }//Test 所实现的类public class MyTest {
        public void test(){
            System.out.println("MyTest");
        }
    }//使用内部类的情形public class AnotherTest extends MyTest {
    
        private class InnerTest implements InterfaceTest {
            @Override
            public void test() {
                System.out.println("InterfaceTest");
            }
        }    public InterfaceTest getCallbackReference() {        return new InnerTest();
        }    public static void main(String[] args) {
            AnotherTest aTest = new AnotherTest();
            aTest.test(); // 调用类MyTest 的 test() 方法
            aTest.getCallbackReference().test(); // 调用InterfaceTest接口中的 test() 方法
        }
    }

    内部クラスを使用してインターフェースを実装することにより、外部クラスによって継承された同じ名前のメソッドと競合することはありません。

    3. 内部クラスの種類

    Javaでは、内部クラスが使用される状況は2つあります:

    (1) クラス内にクラスを定義する(メンバー内部クラス、静的内部クラス)

    (2)メソッド内にクラス(ローカル内部クラス、匿名内部クラス)を定義します。


    2. メンバー内部クラス

    1. 定義と原理

    最も一般的な内部クラスであり、実際の使用では、その可視性は private に設定されます。


    メンバーの内部クラスは外部クラスにアタッチされるため、内部クラスのオブジェクトは、外部クラスのオブジェクトが最初に作成された場合にのみ作成できます。このため、メンバーの内部クラスには静的変数とメソッドを含めることができません

    。次の例を参照してください。

    public class Outter {
        private class Inner {
    
            private final static int x=1;   // OK
    
            /* compile errors for below declaration
            * "The field x cannot be declared static in a non-static inner type, 
            * unless initialized with a constant expression" */
            final static Inner a = new Inner();     // Error  
    
            static Inner a1=new Inner();     // Error  
    
            static int y;     // Error  
        }
    }

      如果上面的代码编译无误, 那么我们就可以直接通过 Outter.Inner.a 拿到内部类Inner的实例。 由于内部类的实例一定要绑定到一个外部类的实例的,所以矛盾。因此,成员内部类不能含有 static 变量/方法。此外,成员内部类与 static 的关系还包括:

    • 包含 static final 域,但该域的初始化必须是一个常量表达式

    • 内部类可以继承含有static成员的类。


    2、交互

     成员内部类与外部类的交互关系为:

    • 成员内部类可以直接访问外部类的所有成员和方法,即使是 private 的;

    • 外部类需要通过内部类的对象访问内部类的所有成员变量/方法。

    //外部类class Out {    private int age = 12;    private String name = "rico"; 
    
        //内部类
        class In {        private String name = "livia"; 
            public void print() {
                String name = "tom"; 
                System.out.println(age);
                System.out.println(Out.this.name);
                System.out.println(this.name);
                System.out.println(name);
            }
        }    // 推荐使用getxxx()来获取成员内部类的对象 
        public In getInnerClass(){        return new In();
        }
    }public class Demo {
        public static void main(String[] args) {
    
            Out.In in = new Out().new In();   // 片段 1
            in.print();        //或者采用注释内两种方式访问
            /* 
             * 片段 2
            Out out = new Out();
    
            out.getInnerClass().print();  // 推荐使用外部类getxxx()获取成员内部类对象 
    
            Out.In in = out.new In();
            in.print();
    
            */
        }
    }/* Output:
            12
            rico
            livia
            tom
     *///:~

     对于代码片段 1和2,可以用来生成内部类的对象,这种方法存在两个小知识点需要注意:

    1) 开头的 Out 是为了标明需要生成的内部类对象在哪个外部类当中;
    2) 必须先有外部类的对象才能生成内部类的对象。


    因此,成员内部类,外部类和客户端之间的交互关系为:

    • 在成员内部类使用外部类对象时,使用 outer.this 来表示外部类对象;

    • 在外部类中使用内部类对象时,需要先进行创建内部类对象;

    • 在客户端创建内部类对象时,需要先创建外部类对象。

    特别地,对于成员内部类对象的获取,外部类一般应提供相应的 getxxx() 方法。


    3、私有成员内部类

      如果一个成员内部类只希望被外部类操作,那么可以使用 private 将其声明私有内部类。例如,

    class Out {    private int age = 12;    private class In {
            public void print() {
                System.out.println(age);
            }
        }    public void outPrint() {        new In().print();
        }
    }public class Demo {
        public static void main(String[] args) {        /*
            * 此方法无效
            Out.In in = new Out().new In();
            in.print();
            */
    
            Out out = new Out();
            out.outPrint();
        }
    }/* Output:
            12
     *///:~

      在上面的代码中,我们必须在Out类里面生成In类的对象进行操作,而无法再使用Out.In in = new Out().new In() 生成内部类的对象。也就是说,此时的内部类只对外部类是可见的,其他类根本不知道该内部类的存在。


    三. 静态内部类

    1、定义与原理

        静态内部类,就是修饰为 static 的内部类,该内部类对象不依赖于外部类对象,就是说我们可以直接创建内部类对象。看下面例子:
      
                    Java 内部クラスの詳細な紹介


    2、交互

     静态内部类与外部类的交互关系为:

    • 静态内部类可以直接访问外部类的所有静态成员和静态方法,即使是 private 的;

    • 外部类可以通过内部类对象访问内部类的实例成员变量/方法;对于内部类的静态域/方法,外部类可以通过内部类类名访问。


    3、成员内部类和静态内部类的区别

     成员内部类和静态内部类之间的不同点包括:

    • 静态内部类对象的创建不依赖外部类的实例,但成员内部类对象的创建需要依赖外部类的实例;

    • 成员内部类能够访问外部类的静态和非静态成员,静态内部类不能访问外部类的非静态成员;


    四. 局部内部类

    1、定义与原理

       有这样一种内部类,它是嵌套在方法和作用域内的,对于这个类的使用主要是应用与解决比较复杂的问题,想创建一个类来辅助我们的解决方案,但又不希望这个类是公共可用的,所以就产生了局部内部类。局部内部类和成员内部类一样被编译,只是它的作用域发生了改变,它只能在该方法和属性中被使用,出了该方法和属性就会失效。

    // 例 1:定义于方法内部public class Parcel4 { 
        public Destination destination(String s) { 
            class PDestination implements Destination { 
                private String label; 
    
                private PDestination(String whereTo) { 
                    label = whereTo; 
                } 
    
                public String readLabel() { 
                    return label; 
                } 
            } 
            return new PDestination(s); 
        } 
    
        public static void main(String[] args) { 
            Parcel4 p = new Parcel4(); 
            Destination d = p.destination("Tasmania"); 
        } 
    }
    // 例 2:定义于作用域内部public class Parcel5 { 
        private void internalTracking(boolean b) { 
            if (b) { 
                class TrackingSlip { 
                    private String id; 
                    TrackingSlip(String s) { 
                        id = s; 
                    } 
                    String getSlip() { 
                        return id; 
                    } 
                } 
                TrackingSlip ts = new TrackingSlip("slip"); 
                String s = ts.getSlip(); 
            } 
        } 
    
        public void track() { 
            internalTracking(true); 
        } 
    
        public static void main(String[] args) { 
            Parcel5 p = new Parcel5(); 
            p.track(); 
        } 
    }

    2、final 参数

       对于final参数,若是将引用类型参数声明为final,我们无法在方法中更改参数引用所指向的对象;若是将基本类型参数声明为final,我们可以读参数,但却无法修改参数(这一特性主要用来向局部内部类和匿名内部类传递数据)。

      如果定义一个局部内部类,并且希望它的方法可以直接使用外部定义的数据,那么我们必须将这些数据设为是 final 的;特别地,如果只是局部内部类的构造器需要使用外部参数,那么这些外部参数就没必要设置为 final,例如:
          Java 内部クラスの詳細な紹介


    五. 匿名内部类

       有时候我为了免去给内部类命名,便倾向于使用匿名内部类,因为它没有名字。匿名内部类的使用需要注意以下几个地方:

    • 匿名内部类是没有访问修饰符的;

    • 匿名内部类是没有构造方法 (因为匿名内部类连名字都没有)

    • 定义匿名内部类的前提是,内部类必须是继承一个类或者实现接口,格式为 new 父类或者接口(){子类的内容(如函数等)}。也就是说,匿名内部类最终提供给我们的是一个匿名子类的对象,例如:

    // 例 1abstract class AbsDemo
    {    abstract void show();
    }public class Outer{
        int x=3;    public void function()//可调用函数
        {        new AbsDwmo()//匿名内部类
            {            void show()
                {
                    System.out.println("x==="+x);
                }            void abc()
                {
                    System.out.println("haha");
                }
            }.abc();  //匿名内部类调用函数,匿名内部类方法只能调用一次
        }   
    }
    // 例 2interface Inner {    //注释后,编译时提示类Inner找不到 
        String getName(); 
    }public class Outer { 
    
        public Inner getInner(final String name, String city) { 
            return new Inner() { 
                private String nameStr = name; 
    
                public String getName() { 
                    return nameStr; 
                } 
            }; 
        } 
    
        public static void main(String[] args) { 
            Outer outer = new Outer(); 
            Inner inner = outer.getInner("Inner", "gz"); 
            System.out.println(inner.getName()); 
    
            System.out.println(inner instanceof Inner);  //匿名内部类实质上是一个匿名子类的对象
        } /* Output:
            Inner
            true
          *///:~ }

    • 若匿名内部类 (匿名内部类没有构造方法) 需要直接使用其所在的外部类方法的参数时,该形参必须为 final 的;如果匿名内部类没有直接使用其所在的外部类方法的参数时,那么该参数就不必为final 的,例如:

    // 情形 1:匿名内部类直接使用其所在的外部类方法的参数 namepublic class Outer { 
        public static void main(String[] args) { 
            Outer outer = new Outer(); 
            Inner inner = outer.getInner("Inner", "gz"); 
            System.out.println(inner.getName()); 
        } 
    
        public Inner getInner(final String name, String city) {  // 形参 name 被设为 final
            return new Inner() { 
                private String nameStr = name;       // OK
                private String cityStr = city;       // Error: 形参 city 未被设为 final
    
                public String getName() { 
                    return nameStr; 
                } 
            }; 
        } 
    } 
    
    
    
    // 情形 2:匿名内部类没有直接使用其所在的外部类方法的参数public class Outer { 
        public static void main(String[] args) { 
            Outer outer = new Outer(); 
            Inner inner = outer.getInner("Inner", "gz"); 
            System.out.println(inner.getName()); 
        } 
    
        //注意这里的形参city,由于它没有被匿名内部类直接使用,而是被抽象类Inner的构造函数所使用,所以不必定义为final
        public Inner getInner(String name, String city) { 
            return new Inner(name, city) {     // OK,形参 name 和 city 没有被匿名内部类直接使用
                private String nameStr = name; 
    
                public String getName() { 
                    return nameStr; 
                } 
            }; 
        } 
    } 
    
    abstract class Inner { 
        Inner(String name, String city) { 
            System.out.println(city); 
        } 
    
        abstract String getName(); 
    }

      从上述代码中可以看到,当匿名内部类直接使用其所在的外部类方法的参数时,那么这些参数必须被设为 final的。为什么呢?本文所引用到的一篇文章是这样解释的:

      “这是一个编译器设计的问题,如果你了解java的编译原理的话很容易理解。首先,内部类被编译的时候会生成一个单独的内部类的.class文件,这个文件并不与外部类在同一class文件中。当外部类传的参数被内部类调用时,从java程序的角度来看是直接的调用,例如:

    public void dosome(final String a,final int b){  
      class Dosome{       public void dosome(){
                System.out.println(a+b)
           }
      }; 
    
      Dosome some=new Dosome();  
      some.dosome();  
    }

      从代码来看,好像是内部类直接调用的a参数和b参数,但是实际上不是,在java编译器编译以后实际的操作代码是:

    class Outer$Dosome{  
      public Dosome(final String a,final int b){  
          this.Dosome$a=a;  
          this.Dosome$b=b;  
      }  
      public void dosome(){  
          System.out.println(this.Dosome$a+this.Dosome$b);  
      }  
    }

      从以上代码来看,内部类并不是直接调用方法传进来的参数,而是内部类将传进来的参数通过自己的构造器备份到了自己的内部,自己内部的方法调用的实际是自己的属性而不是外部类方法的参数。这样就很容易理解为什么要用final了,因为两者从外表看起来是同一个东西,实际上却不是这样,如果内部类改掉了这些参数的值也不可能影响到原参数,然而这样却失去了参数的一致性,因为从编程人员的角度来看他们是同一个东西,如果编程人员在程序设计的时候在内部类中改掉参数的值,但是外部调用的时候又发现值其实没有被改掉,这就让人非常的难以理解和接受,为了避免这种尴尬的问题存在,所以编译器设计人员把内部类能够使用的参数设定为必须是final来规避这种莫名其妙错误的存在。”


      以上关于匿名内部类的每个例子使用的都是默认无参构造函数,下面我们介绍 带参数构造函数的匿名内部类

    public class Outer { 
        public static void main(String[] args) { 
            Outer outer = new Outer(); 
            Inner inner = outer.getInner("Inner", "gz"); 
            System.out.println(inner.getName()); 
        } 
    
        public Inner getInner(final String name, String city) { 
            return new Inner(name, city) {   //匿名内部类
                private String nameStr = name; 
                public String getName() { 
                    return nameStr; 
                } 
            }; 
        } 
    } 
    
    abstract class Inner { 
        Inner(String name, String city) {   // 带有参数的构造函数
            System.out.println(city); 
        } 
    
        abstract String getName(); 
    }

      特别地,匿名内部类通过实例初始化 (实例语句块主要用于匿名内部类中),可以达到类似构造器的效果,如下:

    public class Outer { 
        public static void main(String[] args) { 
            Outer outer = new Outer(); 
            Inner inner = outer.getInner("Inner", "gz"); 
            System.out.println(inner.getName()); 
            System.out.println(inner.getProvince()); 
        } 
    
        public Inner getInner(final String name, final String city) { 
            return new Inner() { 
                private String nameStr = name; 
                private String province; 
    
                // 实例初始化 
                { 
                    if (city.equals("gz")) { 
                        province = "gd"; 
                    }else { 
                        province = ""; 
                    } 
                } 
    
                public String getName() { 
                    return nameStr; 
                } 
    
                public String getProvince() { 
                    return province; 
                } 
            }; 
        } 
    }

    六. 内部类的继承

      内部类的继承,是指内部类被继承,普通类 extents 内部类。而这时候代码上要有点特别处理,具体看以下例子:
                  Java 内部クラスの詳細な紹介

      可以看到,子类的构造函数里面要使用父类的外部类对象.super() [成员内部类对象的创建依赖于外部类对象];而这个外部类对象需要从外面创建并传给形参。


    以上がJava 内部クラスの詳細な紹介の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

    声明:
    この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。