菜鸟笔记 -- Chapter 6.2.6 内部类

6.2.6  内部类

在权限修饰符中,我们已经见过内部类了,但我们看到的只是冰山一角,这节我们详细介绍一下内部类,内部类可以分为成员内部类,局部内部类,匿名内部类,静态内部类.下面我们来讲解一下,在讲解之前,先来看一道面试题:

    Public classTest{
           public static void main(String args[]){
               newB();
           }
        }
        public classA {
               public A(){
                      System.out.println("I am A" );
               }
               {System.out.println("A");}
               static{System.out.println("static A");}
        }
public class B extends A{
               public B(){
                      System.out.println("I am B" );
               }
               {System.out.println("B");}
               static{System.out.println("static B");}
        }

在我们不通过编译执行之前,是否可以猜测一下运行结果是什么呢?是会抛出异常吗?答案是代码不能编译通过提示:The public type A must be defined in its own file,即公共类型必须在自己的文件中定义

那么若要使一个文件中可以有多个类该怎么办呢?答案是只让一个类是公共的,这个公共的类的类名应该和类文件名称一致;如下代码便可编译通过了【此处说的是在一个类中编写多个类,并非说的是内部类】

package cn.yourick.innerclass;

class Test{
    public static void main(String[] args) {
        new B();
    }
}

class A{

    public A() {
        System.out.println("I am A");
    }
    
    {System.out.println("A");}
    
    static{System.out.println("static A");}
}

class B extends A{

    public B() {
        System.out.println("I am B");
    }
    {System.out.println("B");}
    static{System.out.println("static B");}
}

ClassB继承自classA,代码运行时会先运行静态代码块,然后运行构造代码块,然后是构造方法,有继承的话,就要先将父类中的运行.

  看到上面的代码我们发现一个类文件中可以定义多个类,那么我们就会心生疑问,”这会不会就是内部类呢?”,答案是这不是内部类,这些类都是一些单独存在的类,只是把多个类写在了一个类文件中罢了,我们想要验证这个很简单,只需要再写一个类和Test类文件中的某个类名称相同即可,前提是在一个包下面,那么此时就会提示”The type Test is already defined”,即这个类型已经被定义了.从这我们就可以看出虽然一个文件中定义了多个类,但他们仍然是独立的类.那么究竟是么是内部类呢?下面我们就来认识一下内部类.

简单来说,内部类就是在一个类的内部声明的类,相对于内部类,这个类称之为外部类.内部类分为成员内部类,局部内部类,匿名内部类,静态内部类.下面我们一一来认识一下.

6.2.6.1  成员内部类

6.2.6.1.1  成员内部类简介

在一个类中使用内部类,可以在内部类中直接访问其所在类的私有成员变量.同时外部类要访问内部类的所有成员变量/方法,则需要通过内部类的对象来获取。成员内部类的语法如下:

public class OuterClass {
    权限修饰符 class InnerClass{
        //......    
}
}

在内部类中可以随意使用外部类的成员方法以及成员变量,尽管这些类成员被修饰为private.下图充分说明了内部类的使用.尽管成员变量i以及成员方法g()都在外部类中被修饰为private.但在内部类中可以直接使用外部类中的类成员.

内部类的实例一定要绑定在外部类的实例上.如果在外部类中初识化一个内部类对象.那么内部类对象就会绑定在外部类对象上.内部类初始化方式与其他类初始化方式相同,都是使用new关键字.

【例1】创建OuterClass类,在类中定义一个innerClass内部类和doit()方法,在主方法中创建OuterClass类的实例对象并调用doit()方法.

package cn.yourick.innerclass;

public class OuterClass {
    innerClass in = new innerClass();//在外部类实例化内部类对象引用
    
    public void ouf(){
        in.inf();//在外部类方法中调用内部类方法 ,外部类不能直接调用内部类方法
    }
    class innerClass{

        public innerClass() {
            //内部类构造函数       
        }
        public void inf(){
            //内部类成员方法       
        }
        private int y = 0;//内部类成员变量
        public String str = "私有的只能在当前类中使用";
        public static final int x = 9;//内部类中如定义静态成员,那么内部类需要定义为静态内部类,或者内部静态定义为static final    
    }
    public innerClass doit(){//外部类方法,返回值为内部类引用//        y = 4;外部类不可以直接访问内部类成员方法
        int xx = OuterClass.innerClass.x;//内部类静态成员可以通过外部类调用内部类调用静态成员来调用
        in.y = 4;//调用内部类非静态成员,需要通过实例调用
        System.out.println("xx = "+xx+";in.y = "+in.y);
        return new innerClass();//返回内部类引用    
    }
    public static void main(String[] args) {
        OuterClass out = new OuterClass();//        innerClass ini = new innerClass();
        //内部类的对象实例化操作必须在外部类,或外部类的非静态方法中进行
        OuterClass.innerClass ini = out.doit();
    }
}

上例中的外部类创建内部类实例时与其他类创建对象引用时相同.内部类可以访问它的外部类的成员,但内部类的成员只有在内部类的范围之内是可知的,不能被外部类使用.例如,在上例中如果在外部类中将内部类的成员变量y再次赋值时将会出错,但是如果使用内部类对象引用调用成员变量y即可.下图说明了内部类innerClass对象与外部类OuterClass对象的关系.内部类的外部类可以访问内部类中的私有成员。

 

从上图可以看出,内部类对象与外部类对象关系非常紧密,内外可以交互使用彼此类中定义的变量.如果在外部类和非静态方法之外实例化内部类对象,需要使用”外部类.内部类”的形式指定该对象的类型.

在上例的主方法中如果不使用doit()方法返回内部类对象引用,可以直接使用内部类实例化内部类对象,但由于是在主方法中实例化内部类对象,必须在new操作符之前提供一个外部类引用.

[例 2] 在主方法中实例化一个内部类对象.

package cn.yourick.innerclass;

public class Demo1 {
    public static void main(String[] args) {
        Test1.fun1("学习内部类!");
        OuterClass out = new OuterClass();
        OuterClass.innerClass in = out.new innerClass();
        System.out.println(in.str);//注意内部类仍受权限修饰符约束
        OuterClass.innerClass class1 = new OuterClass().new innerClass();
        System.out.println(class1.str);
    }
}
class Test1{
    public static void fun1(String str){
        System.out.println(str);
    }
}

  在实例化内部类对象时,不能在new操作符之前使用外部类名称那种形式实例化内部类对象,而是应该使用外部类的对象来创建其内部类的对象.内部类对象会依赖于外部类对象,除非已经存在一个外部类对象,否则类中不会出现内部类对象.即类中如果没有外部类对象,那么断然不会有内部类对象,这适用于外部类外的其他的类.

  要注意的是,成员内部类不能含有static的变量和方法。因为成员内部类需要先创建了外部类,才能创建它自己的,这就是对成员内部类依赖于外部类的最好证明.

  非static的内部类,在外部类加载的时候,并不会加载它,所以它里面不能有静态变量或者静态方法。static类型的属性和方法,在类加载的时候就会存在于内存中。要使用某个类的static属性或者方法,那么这个类必须要加载到jvm中。
  基于以上两点,可以看出,如果一个非static的内部类如果具有static的属性或者方法,那么就会出现一种情况:内部类未加载,但是却试图在内存中创建static的属性和方法,这当然是错误的。原因:类还不存在,但却希望操作它的属性和方法。但是我们在例 1中却存在这么一句public static final int x = 9;//内部类中如定义静态成员,那么内部类需要定义为静态内部类,或者内部静态定义为static final

这条语句是可以编译通过的,这又是为什么呢?这是不是和上面我们所说的违背呢?

内部类的对象:脱离了其外围类的对象就不会存在。
静态变量:作用是让该类的所有对象共享一个状态。
原因分析:
  因为你的内部类是非静态的,除了要依靠外部类实例外,还要依赖内部类实例。而静态变量是不需要构建类实例的,两者是相矛盾的。而final类型修饰的变量(即常量)可以离开类实例存活一段时间的。所以final修饰过的编译不出错。你如果把内部类修饰成静态的就更不会报错了。【内部类要持有外部类的实例指针,换句话说,有外部实例才有内部类,内部类依附于外部类实例。静态成员和方法是什么意思?是独立于类实例存在的,这样就和内部类的特点有矛盾之处了:内部类是一个依附,有内部类就要有外部实例,然后静态成员是独立于实例存在的,所以两个概念不能放在一起。外部类加载的时候不会加载内部类,但是会加载静态,此时内部类还没有加载,所以存在问题!】
综上所述:出现这种情况的主要原因,是外部类实例与内部类实例依赖问题

6.2.6.1.2  内部类向上转型为接口

  如果将一个权限修饰符为private的内部类向上转型为其父类对象,或者直接向上转型为一个接口,在程序中就可以完全隐藏内部类的具体实现过程.可以在外部提供一个接口,在接口中声明一个方法.如果在实现该接口的内部类中实现该接口的方法,就可以定义多个内部类(在同一个外部类中定义多个内部类)以不同的方式实现接口中的同一个方法,而在一般的类中是不能多次实现接口中同一个方法的,这种技巧经常被应用在Swing编程中,可以在一个类中做出多个不同的响应事件.

[例 6.2.6.1.2]创建InterfaceInner类,并定义接口OutInterface,使内部类InnerClass实现这个接口,最后使doit()方法返回值类型为该接口.

package cn.yourick.innerclass;

public interface OutInterface {
    public void f();
}
package cn.yourick.innerclass;

class OutClass2{
    //定义一个内部类实现OutInterface接口,一个包中独立的类名称不能相同,但是内部类不在此例,内部类只是作为外部类的附属存在的,依赖于外部类,但是不能和外部类名称一样
    private class InnerClass implements OutInterface{
        //内部类有参构造
        public InnerClass(String s) {
            System.out.println(s);
        }
        @Override
        public void f() {//实现接口中的方法
            System.out.println("InnerClass--访问内部类中的f()方法");
        }
    }
    //定义第二个内部类,对接口方法进行不同实现
    public class InnerClassDemo implements OutInterface{
        public InnerClassDemo() {
            System.out.println("InnerClassDemo的构造函数!");
        }
        @Override
        public void f() {
            System.out.println("InnerClassDemo--访问内部类中的f()方法");
        }
    }
    public OutInterface doit1(){
        return new InnerClass("访问内部类构造方法");
    }
    public OutInterface doit2(){
        return new InnerClassDemo();
    }
}
package cn.yourick.innerclass;

public class InterfaceInner {
    public static void main(String[] args) {
        OutClass2 outClass2 = new OutClass2();
        //调用doit()返回一个OutInterface接口
        OutInterface outInterface1 = outClass2.doit1();
        outInterface1.f();
        OutInterface outInterface2 = outClass2.doit2();
        outInterface2.f();
        //内部类设置为公有访问方式
        test1();
    }
    public static void test1(){
        OutInterface innerClassDemo = new OutClass2().new InnerClassDemo();
        innerClassDemo.f();
    }
}

  从上例可以看出,OutClass2中定义了一个权限修饰符为private的内部类,这个内部类实现了OutInterface接口,然后修改doit()方法,使该方法返回一个OutInterface接口.由于内部类InnerClass修饰权限为private,所以除了OutClass2类可以访问该内部类之外,其他类都不能访问,而可以访问doit()方法,由于该方法返回一个外部接口类型,这个接口可以作为外部使用的接口.它包含一个f()方法,在继承该接口的内部类中实现了该方法,如果某个类继承了外部类,由于内部的权限不可以向下转型为内部类InnerClass,同时也不能访问f()方法,但是却可以访问接口中的f()方法.例如,InterfaceInner类中最后一条语句,接口引用调用f()方法,从执行结果可以看出,这条语句执行的是内部类中的f()方法,很好的对继承该类的子类隐藏了实现细节,仅为编写子类的人留下一个接口和一个外部类,同时也可以调用f()方法,但是f()方法的具体实现过程全部很好的隐藏了,这就是内部类最基本的用途.

6.2.6.1.3  使用this关键字获取内部类与外部类的引用

如果在外部类中定义的成员变量与内部类的成员变量名称相同,可以使用this关键字.

[例 6.2.6.1.3]创建TheSameName类,在类中定义成员变量x,定义一个内部类Inner,并在内部类中也创建x变量,在内部类的doit()方法中分别操作两个x变量.

package cn.yourick.innerclass;

import keyword.ThisTest;

public class TheSameName {
    private int x;
    private class inner{
        private int x = 9;
        public int doit(int x){
            x++;//调用的是形参
            this.x++;//调用的是内部类的变量x
            TheSameName.this.x++;//调用的是外部类的变量x
            return x;
        }
    }
    public static void main(String[] args) {
        TheSameName name = new TheSameName();
        TheSameName.inner inner = name.new inner();
        System.out.println("外部类成员变量x="+name.x);
        System.out.println("内部类局部变量x="+inner.doit(3));
        System.out.println("外部类成员变量x="+name.x);
        System.out.println("内部类成员变量x="+inner.x);
    }
}

  在类中如果内部类与外部类遇到成员变量重名的情况可以使用this关键字进行处理,例如在内部类中使用this.x语句可以调用内部类的成员变量x,而使用TheSameName.this.x语句可以调用外部类的成员变量x,即使用外部类名称后跟一个点操作符和this关键字便可获取外部类的一个引用.应该明确一点,在内存中所有对象被放在堆中,将方法以及方法中的形参或局部变量放置在栈中,如下图所示.栈中的doit()方法指向内部类的对象,而内部类的对象与外部类的对象时相互依赖的,outer.this对象指向外部类对象.

 

6.2.6.2  局部内部类

内部类不仅可以在类中进行定义,也可以在类的局部位置定义,如在类的方法或任意的作用域中均可以定义内部类.

[例 6.2.6.2]在外部类的sell()方法中创建Apple局部内部类,然后创建该内部类的实例,并调用其定义的price()方法输出单价信息.

package cn.yourick.innerclass;

public class SellOutClass {
    private String name;

    public SellOutClass(String name) {
        this.name = name;
    }
    public void sell(int price){
        class Apple{
            int innerPrice = 0;
            public Apple(int price){
                innerPrice = price;
            }
            public void price(){
                System.out.println("现在开始销售"+name);
                System.out.println("单价为:"+innerPrice+"元");
            }
        }
        Apple apple = new Apple(price);
        apple.price();
    }
    public static void main(String[] args) {
        SellOutClass sellOutClass = new SellOutClass("苹果");
        sellOutClass.sell(100);
    }
}

在上述代码中可以看到,将内部类定义在sell()方法内部.但是有一点值得注意,内部类Apple是sell()方法的一部分,并非SellOutClass类的一部分(所以前面

TheSameName name = new TheSameName();

TheSameName.inner inner = name.new inner();

的声明方式已不适用,编译也不会通过),所以在sell()方法的外部不能访问该内部类,但是该内部类可以访问当前代码块的常量以及此外部类的所有成员.

      局部内部类也像别的类一样进行编译,但只是作用域不同而已,只在该方法或条件的作用域内才能使用,退出这些作用域后无法引用的。

6.2.6.3  匿名内部类

  在编写程序代码时,不一定要给内部类一个名字,可以直接以对象名来代替.匿名内部类的所有实现代码都需要在大括号之间进行编写.在图形化编程的事件监控器代码中,会大量使用匿名内部类,这样可以大大简化代码,并增强代码的可读性.

匿名内部类的语法格式如下:

Return new A(){//内部类代码

}

  其中A表示对象名.由于匿名内部类没有名称,所以匿名内部类使用默认构造方法来生成匿名内部类的对象.在匿名内部类定义结束后,需要加分号标识,这个分号并不代表定义内部类结束的标识,而代表创建匿名内部类的引用表达式的标识.

匿名内部类编译以后,会产生以”外部类名$序号”为名称的.class文件,序号以1~n排列,分别代表1~n个匿名内部类.

[例6.2.6.3]在main()方法中编写匿名内部类去除字符串中的全部空格.

package cn.yourick.innerclass;

public interface IStringDeal {
    public String filterBlankChar();//声明过滤字符串中空格的方法
}
package cn.yourick.innerclass;

public class OutString {
    public static void main(String[] args) {
        final String sourceStr = "吉林省 明日 科技有限公司----编程    字典! ";
        //实现接口
        IStringDeal s = new IStringDeal(){
            @Override
            public String filterBlankChar() {
                String converStr = sourceStr;
                converStr = converStr.replaceAll(" ", "");
                return converStr;
            }
        };
        System.out.println("源字符串:"+sourceStr);
        System.out.println("转换后的字符串:"+s.filterBlankChar());
    }
}

匿名内部类是不能加访问修饰符的。要注意的是,new 匿名类,这个类是要先定义的,看下面例子:

package cn.yourick.innerclass;

public interface Inner {
    String getName();
}
package cn.yourick.innerclass;

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() {
            @Override
            public String getName() {
//                city = name;不能使用,提示要将city定义为final
                return name;
            }
        };
    }
}
反编译匿名内部类
// Decompiled by DJ v3.12.12.100 Copyright 2015 Atanas Neshkov  Date: 2017/12/16 星期六 上午 5:46:29
// Home Page:  http://www.neshkov.com/dj.html - Check often for new version!
// Decompiler options: packimports(3) 
// Source File Name:   Outer.java

package cn.yourick.innerclass;


// Referenced classes of package cn.yourick.innerclass:
//            Inner, Outer

class Outer$1
    implements Inner
{

    public String getName()
    {
        return val$name;
    }

    final Outer this$0;
    private final String val$name;

    Outer$1()
    {
        this$0 = final_outer;
        val$name = String.this;
        super();
    }
}

  同时在这个例子,留意外部类的方法的形参,当所在的方法的形参需要被内部类里面使用时,该形参必须为final。这里可以看到形参name已经定义为final了,而形参city 没有被使用则不用定义为final。为什么要定义为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来规避这种莫名其妙错误的存在。”

 (简单理解就是,拷贝引用,为了避免引用值发生改变,例如被外部类的方法修改等,而导致内部类得到的值不一致,于是用final来让该引用不可改变)

      因为匿名内部类,没名字,是用默认的构造函数的,无参数的,那如果需要参数呢?则需要该类有带参数的构造函数:

package cn.yourick.innerclass;

public abstract class InnerTest {
//    String name;
//    String city;此处是可以省略的
    public InnerTest(String name, String city) {
        super();
//        this.name = name;
//        this.city = city;
        System.out.println(city);
    }
    public abstract String getName();
}
package cn.yourick.innerclass;

public class OuterTest {
    public static void main(String[] args) {
        OuterTest outerTest = new OuterTest();
        InnerTest innerTest = outerTest.getInnerTest("InnerTest", "京");
        System.out.println(innerTest.getName());
    }
    public InnerTest getInnerTest(final String name,String city){
        return new InnerTest(name, city) {
            
            @Override
            public String getName() {
                return name;
            }
        };
    }
}
package cn.yourick.innerclass;
class OuterTest$1 extends InnerTest
{

    public String getName()
    {
        return val$name;
    }

    final OuterTest this$0;
    private final String val$name;

    OuterTest$1(String $anonymous1, String s)
    {
        this$0 = final_outertest;
        val$name = s;
        super(String.this, $anonymous1);
    }
}
package cn.yourick.innerclass;
class OuterTest$1 extends InnerTest
{

    public String getName()
    {
        return name;
    }

    final OuterTest this$0;

    OuterTest$1(String $anonymous0, String $anonymous1)
    {
        this$0 = OuterTest.this;
        super($anonymous0, $anonymous1);
    }
}

  上述为编译后的代码,前者为抽象类中已注释,后者为未注释,【注释的那么匿名内部类操作的变量是会在自己由编译器创建一个成员变量,而未注释的操作的是抽象类的成员变量,属于继承了父类抽象类的成员变量,但是形参name的final还是不能去掉,仍然要保证数据的一致性】但结果都是一样的,如下:

 

  注意这里的形参city,由于它没有被匿名内部类直接使用,而是被抽象类Inner的构造函数所使用,所以不必定义为final。

      而匿名内部类通过实例初始化,可以达到类似构造器的效果:

package cn.yourick.innerclass;

public interface InnerDemo {
    String getProvince();
    String getName();
}
package cn.yourick.innerclass;
import keyword.ThisTest;
public class OutDemo {
    public static void main(String[] args) {
        OutDemo outDemo = new OutDemo();
        InnerDemo innerDemo = outDemo.getInner("InnerDemo", "gz");
        System.out.println("province:"+innerDemo.getProvince());
        System.out.println("name:"+innerDemo.getName());
    }
    public InnerDemo getInner(final String name,final String city){
        return new InnerDemo() {
//            private String name ;//可以和形参名称一样,因为两个一个是实例变量,一个只是局部变量,但是尽量不要取一样名称,要不局部变量没办法区别使用
            private String innerName = name;
            private String innerCity = city;
            @Override
            public String getProvince() {
                if(city.equals("gz")) return innerCity;
                else return "null";
            }
            
            @Override
            public String getName() {
                return innerName;//根据就近原则,此时返回的是实例成员字段,如果要返回局部,那么就不要在匿名内部类中取和局部一样的名字
            }
        };
    }
}

仔细观察上面的代码我们发现getInner与一个有参构造函数很类似,这也是匿名内部类的一个特色了。

6.2.6.4  静态内部类

  在内部类前添加修饰符static,这个内部类就变为静态内部类..一个静态内部类中可以声明static成员,但是在非静态内部类中不可以声明静态成员.静态内部类有一个最大的特点,就是不可以使用外部类的非静态成员,所以静态内部类在程序开发中比较少见.可以这样认为,普通的内部类对象隐式地在外部保存了一个引用,指向创建它的外部类对象,但如果内部类被定义为static时,它应该具有更多的限制,静态内部类具有以下两个特点:

  • l 创建静态内部类的对象,不需要其外部类的对象;
  • l 不能从静态内部类的对象中访问非静态外部类的对象.

[例 6.2.6.4]定义一个静态内部类StaticInnerClass.

package cn.oop.innerclass;

  在上例中,在内部类的doitInner()方法中调用成员变量x,由于Inner被修饰为static形式,而成员变量x却是非static类型的,所以在doitInner()方法中不能调用x变量.

  进行程序测试时,如果在每一个Java文件中都设置一个主方法,将出现很多额外代码,而程序本身并不需要这些主方法,为了解决这个问题,可以将主方法写入静态内部类中.

package cn.yourick.innerclass;

public class StaticInnerClass {
    int x = 100;
    static int y = 101;
    static class Inner{
        void doitInner(){           
            //System.out.println("外部类x="+x);
            System.out.println("外部类y="+y);
        }
    }
}

  如果编译上例中的类,将编译生成一个名称为StaticInnerClass$Inner的独立类和一个StaticInnerClass类,只要使用java StaticInnerClass$Inner就可以运行主方法中的内存.这样当测试完成需要将所有的.class文件打包时,只要删除StaticInnerClass$Inner独立类即可;

静态内部类的使用不需如局部内部类一样依赖于外部类实例,如下

StaticInnerClass.Inner.doitInner();

   静态内部类也叫嵌套内部类,就是修饰为static的内部类。声明为static的内部类,不需要内部类对象和外部类对象之间的联系,就是说我们可以直接引用Outer.Inner,即不需要创建外部类,也不需要创建内部类。

      嵌套类和普通的内部类还有一个区别:普通内部类不能有static数据和static属性,也不能包含嵌套类,但嵌套类可以。而嵌套类不能声明为private,一般声明为public,方便调用。

6.2.6.5  内部类的继承

  学习Java都知道Java的三大特性封装,多态和继承;封装是隐藏对象的属性和实现细节,仅对外提供公共访问方式.多态是某一类事物具备的多种表现形态(猫、狗和动物)java中多态的表现形式如下:

  • l 父类的引用指向自己的子类对象
  1. l 父类的引用接收自己的子类对象

  继承是从已有的类中派生出新的类,新的类能吸收已有类的数据属性和行为,并能扩展新的功能,或者说将事物的共性的功能和属性抽取出来定义成一个父类,让后来具有更多样化实现的子类继承父类。知道了继承,那么我们是不是有这么一个考虑,那就是内部类同样也是类,那么内部类的继承是什么样子的呢?内部类也和其他普通类一样可以被继承.但是继承内部类比继承普通类复杂一些,需要设置专门的语法来完成.

[例6.2.6.5]创建OutputInnerClass类,使OutputInnerClass类继承ClassA类中的内部类ClassB.

package cn.yourick.innerclass;

public class ClassA{
    
    public ClassA() {
        System.out.println("继承内部类是需要在构造函数中以外部类作为参数构造一个有参构造的!");
    }

    class ClassB extends TheSameName{
        @Override
        public void fun1() {
            super.fun1();
        }
    }
}
package cn.yourick.innerclass;
/**
 * 继承内部类ClassB
 * @author Yourick
 *
 */
public class OutputInnerClass extends ClassA.ClassB{

    public OutputInnerClass(ClassA a) {
        a.super();
    }
    public static void main(String[] args) {
        OutputInnerClass outputInnerClass = new OutputInnerClass(new ClassA());
        outputInnerClass.fun1();
    }
    @Override
    public void fun1() {
        super.fun1();
    }
}

  在某个类继承内部类时,必须硬性给予这个类一个带参数的构造方法,并且该构造方法的参数为需要继承内部类的外部类的引用,同时在构造方法中使用a.super();语句,这样才能为继承提供了必要的对象引用.另外,内部类继承则和平常继承一样,没有特殊性.

6.2.6.6  内部类小结

  • l 内部类是指在一个外部类的内部再定义一个类。类名不能和外部类名称相同,可以和同包其他类名称相同。
  • l 内部类可以是静态static的,也可用public,default,protected和private修饰。(而外部顶级类即类名和文件名相同的只能使用public和default)。
  • l 内部类是一个编译时的概念,一旦编译成功,就会成为完全不同的两类。对于一个名为outer的外部类和其内部定义的名为inner的内部类。编译完成后出现outer.class和outer$inner.class两类。所以内部类的成员变量/方法名可以和外部类的相同。

6.2.6.7  内部类在实际开发中的运用及好处

1.内部类可以很好的实现隐藏

 一般的非内部类,是不允许有 private 与protected权限的,但内部类可以

2.内部类拥有外围类的所有元素的访问权限

3.可实现多重继承,在外部类中定义多个内部类,分别继承不同类,通过外部类来访问内部类,看起来好像是多重继承了。

4.可以避免修改接口而实现同一个类中两种同名方法的调用。也是通过多个内部类来实现接口的方法的。

posted @ 2017-12-18 10:46  十月十四  阅读(405)  评论(0编辑  收藏  举报