Home >Java >javaTutorial >Detailed introduction to Java internal classes

Detailed introduction to Java internal classes

黄舟
黄舟Original
2017-03-14 11:51:301248browse


Summary

Multiple Inheritance refers to a class that can inherit from more than one class at the same time A parent class inherits behaviors and characteristics. However, we know that Java only allows single inheritance in order to ensure data security. But sometimes, we do need to achieve multiple inheritance, and such situations really exist in real life, such as inheritance: we inherit both the behavior and characteristics of our father and those of our mother. Fortunately, Java provides two ways for us to implement multiple inheritance in a tortuous way: Interface and inner class. In fact, Implementing multiple inheritance is an extremely important application of inner classes. In addition, Inner classes can also be well hidden (for example, private member inner classes). There are four types of inner classes, namely member inner classes, static inner classes, local inner classes and anonymous inner classes. In particular,

  • Member inner class: Member inner class is a member of the outer class and is attached to the outer class , Therefore, the inner class object can be created only if the outer class object is created first. It is for this reason that member inner classes cannot contain staticvariables and methods;

  • static Inner class: Static inner class is an inner class modified as static. The inner class object does not depend on the outer class object, which means that we can directly create the inner class object, but its All static members and static methods of external classes can only be directly accessed;

  • #Local inner classes: Local inner classes are the same as member inner classes is compiled, but its scope has changed, it can only be used in this method and attribute, and it will become invalid outside this method and attribute;

  • Anonymous inner class: The premise for defining an anonymous inner class is that the inner class must inherit a class or implement an interface, in the format new Parent class or interface () {define the content of the subclass (such as function, etc.)}. In other words, what the anonymous inner class ultimately provides us is an object of an anonymous subclass.

1. Overview of internal classes

1. Basics of internal classes

 What internal classes refer to It is a class defined within a class. The class name does not need to be the same as the source file name. Inner class is a compile-time concept. Once compiled successfully, the inner class and the outer class will become two completely different classes. For example, for an external class named Outer and an internal class named Inner defined inside it, after the compilation is completed, Outer.class and Outer$inner will appear. class two classes. Therefore, the member variable/method names of the inner class can be the same as those of the outer class. Inner classes can be static or can be modified with public, default, protected and private. In particular, regarding the relationship between Java source file names and class names (the naming of java source file names has nothing to do with internal classes, the classes and interfaces involved in the following three rules all refer to external classes/interfaces ), need to comply with the following three rules:

  • If the java source file contains a public class (public interface), the source file name must be the same as The public class name (public interface name) is the same.
    In a java source file, if there is a public class or public interface, there can only be one public class or one public interface, and there cannot be multiple public classes or interfaces. Of course, a Java source file can have multiple classes or interfaces visible to the package, that is, the default access modifier (there is no access modifier before the class name). The order of public classes (interfaces) and package-visible classes (interfaces) in the file can be arbitrary, that is, the public class (interface) does not need to be in the first position.

  • If the java source file does not contain a public class (public interface), there is no limit on the java source file name.
    As long as it conforms to the naming convention of the file name, it does not need to have the same name as any class or interface in the file. Of course, it can also have the same name as one of them.

  • #The naming of classes and interfaces cannot conflict.
    Any class or interface in the same package cannot be named the same. Classes or interfaces in different packages can be named the same because they can be distinguished by packages.


2. The role of inner classes

Using inner classes can bring us the following advantages:

  • Inner classes can be well hidden (general non-inner classes are not allowed to have private and protected permissions, but inner classes can);

  • The inner class has access rights to all elements of the outer class;

  • Can achieve multiple inheritance;

  • #You can avoid modifying the interface and calling two methods with the same name in the same class.


#1) Internal classes can be well hidden

Usually our access rights to classes are modified by the access in front of the class Generally, non-inner classes are not allowed to have private and protected permissions, but inner classes can, so we can hide our information through inner classes. You can look at the following example:

//测试接口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.
 *///:~

 For the client, we can get an InterfaceTest instance through the getInnerInstance() method of Example, but we don’t know how this instance is implemented, and we don’t know how it is implemented. There is no corresponding concrete implementation class. Since InnerClass is private, if we don't look at the source code, we can't even see the name of the specific class that implements this interface, so inner classes can be well hidden.


2) The inner class has access rights to all elements of the outer class

//外部类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
 *///:~

Name This member variable is a private variable defined in Example. This variable is in the inner class can be accessed unconditionally.

 

3) Multiple inheritance can be achieved

 For multiple inheritance, it can be said that interfaces only solve part of the problem, while inner classes enable multiple inheritance The solution becomes more complete. Inner classes make Java's inheritance mechanism more complete and are the biggest reason for the existence of inner classes. A class in Java can only inherit one class, and its multiple inheritance is implemented using interfaces before we learn inner classes. But sometimes there are many inconveniences in using interfaces. For example, if we implement an interface, we must implement all the methods in it; and internal classes can make our class inherit multiple concrete classes or abstract classes, Avoid the limitations of interfaces. Look at the following example:

//父类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
 *///:~

Notice that the class MainExample, in this class, contains two inner classes Test1 and Test2. Among them, class Test1 inherits class Example1, and class Test2 inherits class Example2. In this way, class MainExample has methods of class Example1 and class Example2, thus indirectly realizing multiple inheritance.


4) Avoid modifying the interface to implement the call of two methods with the same name in the same class

Consider a situation where a class needs to inherit a class and implement an interface , but the classes and interfaces it inherits have two identical methods (method signatures are the same), so how do we distinguish them? This requires the use of inner classes. For example,

//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");
    }
}

At this time, does the test() method in Test belong to the test() method that covers MyTest, or does it implement the test() method in InterfaceTest? How can we adjust the method here in MyTest? Obviously this is difficult to distinguish. And if we use inner classes, this problem can be easily solved. Look at the following code:

//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() 方法
    }
}

By using an inner class to implement the interface, it will not conflict with the method of the same name inherited by the outer class.


3. Types of inner classes

In Java, there are two situations when using inner classes:

 (1) Define a class within a class ( Member inner class, static inner class);
 (2) Define a class (local inner class, anonymous inner class) in the method.


2. Member inner class

1. Definition and principle

 Member inner class is the most common inner class, it is the peripheral A member of a class, in actual use, generally sets its visibility to private. The member inner class is attached to the outer class, so the inner class object can only be created after the outer class object is created first. It is for this reason that member inner classes cannot contain static variables and methods, see the following example:

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 的内部类,该内部类对象不依赖于外部类对象,就是说我们可以直接创建内部类对象。看下面例子:
  
                Detailed introduction to Java internal classes


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,例如:
      Detailed introduction to Java internal classes


五. 匿名内部类

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

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

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

  • 定义匿名内部类的前提是,内部类必须是继承一个类或者实现接口,格式为 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 内部类。而这时候代码上要有点特别处理,具体看以下例子:
              Detailed introduction to Java internal classes

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


The above is the detailed content of Detailed introduction to Java internal classes. For more information, please follow other related articles on the PHP Chinese website!

Statement:
The content of this article is voluntarily contributed by netizens, and the copyright belongs to the original author. This site does not assume corresponding legal responsibility. If you find any content suspected of plagiarism or infringement, please contact admin@php.cn