第10章-内部类II


Think in java 读书笔记

pikzas

2019.05.05

第十章 内部类

知识点

1.什么是内部类

可以将一个类定义在另一个类的内部

class OuterClass{
    class InnerClass{
        
    }
}

2.内部类的主要特征是什么

内部类的主要特征是内部类可以随意访问外部类的属性和方法,不论其访问修饰符是什么

public interface Selector {
    boolean end();
    Object current();
    void next();
}

public class Seq {
    private Object[] items;
    private int next = 0;
    public Seq(int size){
        items = new Object[size];
    }
    public void add(Object item){
        if(next < items.length){
            items[next++] = item;
        }
    }

    private class SeqSelector implements Selector{
        private int i = 0;

        @Override
        public boolean end() {
            return i == items.length;
        }

        @Override
        public Object current() {
            return items[i];
        }

        @Override
        public void next() {
            if(i

3.适用于内部类的一些特殊语法

.new 由外部类对象新创建内部类对象

class Outer{
    class Inner{}
}

class Demo{
    public static void main(String[] args){
      Outer outer = new Outer();
      Outer.Inner inner = outer.new Inner();
    }
}

.this 在内部类如果想要获取外部类对象的引用

class DotThis{
    void f() {System.out.println("DotThis.f()");}
    class Inner{
        public DotThis getOuterObj(){
            return DotThis.this;
        }
    }
    
    public Inner getInner(){
        return new Inner();
    }
    
    public static void main(String[] args){
      DotThis dotThis = new DotThis();
      DotThis.Inner inner = dotThis.getInner();
      System.out.println(inner.getOuterObj().equals(dotThis));  // 输出为true
      inner.getOuterObj().f();  // inner.getOuterObj() 等同于 dotThis
      
    }
}

.new 语法也可已看出,必须要先有外部类实例,再由这个实例.new 出内部类。(静态内部类除外)

4.内部类与接口的配合使用

内部类实现接口,并将内部类访问限定为private或者protected,从而将实现隐藏在该内部类中。

public interface Dest {
    int value();
}

public class Demo {
    private class DestImp implements Dest{
        @Override
        public int value() {
            return 0;
        }
    }

    public Dest getDest(){
        return new DestImp();
    }
}


public class Test {
    public static void main(String[] args) {
        Demo demo = new Demo();
        Dest dest = demo.getDest();
        dest.value();
    }
}

5.局部内部类

以上看到的内部类都是直接定义在外部类的作用域内,如果让一个内部类定义在外部类的某个方法内部时,这就称作局部内部类。
并且当作用域超出了这个方法的时候,外部是访问不到该类的。

public interface Fruit {
    String desc();
}

public class Test {
    public Fruit getFruit(){
        class Apple implements Fruit{
            @Override
            public String desc() {
                return "i am apple";
            }
        }
        return new Apple();
    }
    //   getFruit方法之外,是访问不到Apple类的
    //   private Fruit fruit = new Apple();
}

6.匿名内部类

上面的局部内部类将Apple的定义和new 创建新对象的语法放在一起就是匿名内部类。如果返回的是具体的类,看起来也就没什么特殊了。

public interface Fruit {
    String desc();
}

public class Test2 {
    public Fruit getFruit(){
        return new Fruit(){
            @Override
            public String desc() {
                return "i am apple";
            }
        };
    }
    public static void main(String[] args) {
        Test2 test2 = new Test2();
        Fruit fruit = test2.getFruit();
        System.out.println(fruit.desc());
    }
}

6.1.匿名内部类可以是具体类,甚至可以对其方法进行override

public class Wrapping {
    private int i;
    public Wrapping(int x){
        this.i = x;
    }
    public int value(){
        return i;
    }
}

public class Outer {
    public Wrapping getItem(int x){
        return new Wrapping(x){
            @Override
            public int value(){
                return super.value() * 47;
            }
        };
    }

    public Wrapping getItem2(int x){
        return new Wrapping(x);
    }

    public static void main(String[] args) {
        Outer outer = new Outer();
        Wrapping wrapping = outer.getItem(10);
        Wrapping wrapping2 = outer.getItem2(10);
        System.out.println(wrapping.value());
        System.out.println(wrapping2.value());
    }
}

------输出结果------
470
10

6.2.对匿名内部类的字段进行初始化

  • 使用外部类传入的数据进行初始化,此时该数据必须为final的
public interface SampleInterface {
    int value();
}


public class InitDemo {
    public SampleInterface getSample(final int x){  //此处final必须为final的 可以理解为外部对象指向的数据不能变动,否则内部类对象会很困惑。
        return new SampleInterface() {
            private int i = x;
            @Override
            public int value() {
                return i;
            }
        };
    }
}

  • 通过初始化代码块完成类似于构造器的功能
public abstract class Base {
    public Base(int i){
        System.out.println("base constructor, i = " + i);
    }
    public abstract void f();
}

public class AnonymousConstructor {
    public static Base getBase(int i){ //此处的变量不用是final的,因为内部类并没用到i
        return new Base(i){
            {
                System.out.println("inside instance");  // 通过静态代码块可以实现初始化
            }
            public void f(){
                System.out.println("in inner f()");
            }
        };
    }

    public static void main(String[] args) {
        Base base = getBase(47);
        base.f();
    }
}

从上面对于传入内部类的参数是否要加上final的修饰规定是,如果内部类用到了外部传入的那个变量,则需要为final的。
内部类可以拿来扩展类,也可以拿来实现接口,但是不能两个都做到,也只能一次实现一个接口。

7.工厂方法的内部类实现方式

public interface Game {
    boolean move();
}

public interface GameFactory {
    Game getGame();
}

public class Chess implements Game {
    private Chess(){};
    private int moves = 0;
    private static final int MOVES = 4;
    @Override
    public boolean move() {
        System.out.println("Chess moves" + moves);
        return ++moves != MOVES;
    }

    public static GameFactory factory = new GameFactory(){
        public Game getGame(){
            return new Chess();
        }
    };

}

public class Checkers implements Game {
    private Checkers(){}
    private int moves = 0;
    private static final int MOVES = 3;

    @Override
    public boolean move() {
        System.out.println("Checker moves" + moves);
        return ++moves != MOVES;
    }

    public static GameFactory factory = new GameFactory() {
        @Override
        public Game getGame() {
            return new Checkers();
        }
    };
}

public class Test {
    public static void playGame(GameFactory factory) {
        Game game = factory.getGame();
        while(game.move());
    }
    public static void main(String[] args) {
        playGame(Chess.factory);
        playGame(Checkers.factory);
    }
}
-------输出结果----

Chess moves0
Chess moves1
Chess moves2
Chess moves3
Checker moves0
Checker moves1
Checker moves2

8.嵌套类(也成为静态内部类)

前面讲到的内部类的创建都依赖于外部类的对象,如果内部类定义的时候是static的,那么外部对象就不需要了。
同时因为内部类是static的,那么如果想要使用外部类中的属性或者方法,那么那些属性或者方法必须也是static的。


public class Outer {
    private int x = 123;
    private static int y = 999;
    public static class OneImpl implements OneInterface{
        public void f(){
//            System.out.println("inner class" + x); 此处因为x不是static的,会提示编译错误。
            System.out.println("inner class " + y);
        }
    }

    public static void main(String[] args) {
        OneInterface one = new OneImpl();
        one.f();
    }
}

8.1.嵌套类的应用

由于接口中的属性和方法默认都是public static的,所以其中可以来嵌套一些需要子类实现都通用的方法。

public interface DemoInterface {
    void fun();
    class DemoImpl implements DemoInterface{
        public void fun(){
            System.out.println("可以实现我自己的外部接口");
        }
    }
}

public class DemoTest implements DemoInterface {
    @Override
    public void fun() {
        System.out.println("DemoTest");
    }

    public static void main(String[] args) {
        DemoInterface demo = new DemoTest();
        DemoInterface demo2 = new DemoImpl();
        demo.fun();
        demo2.fun();
    }
}

-----输出结果-----
DemoTest
可以实现我自己的外部接口

嵌套类不同于普通的内部类的另一点在于,编译生成的.class文件名称都是形如Outer$Inner.class这个格式。
而且嵌套类内部还能有嵌套类,以及static声明的属性及方法。

9.内部类无论内部有多少层,内一层的类都可以无条件的访问外部类

10.内部类存在的意义

内部类存在的主要目的是为了解决java没有多继承导致的一些麻烦

  • java可以很好的解决多接口的实现问题,如下例子
public interface A {
}

public interface B {
}

public class X implements A,B {
}

public class Y implements A {
    B getB(){
        return new B() {};
    }
}

public class TestOne {
    public static void funA(A a) {

    }

    public static void funB(B b) {

    }
    
    public static void main(String[] args) {
        X x = new X();
        Y y = new Y();
        funA(x);
        funA(y);
        funB(x);
        funB(y.getB());
    }
}
  • 但是对于要求子类同时实现抽象类和父类问题时,只有内部类可以派上用场
public class M {
}

public abstract class N {
}

public class Z extends M {
    N getN(){
        return new N() {};
    }
}

public class TestTwo {
    public static void funM(M m) {

    }
    public static void funN(N n) {

    }
    public static void main(String[] args) {
        Z z = new Z();
        funM(z);
        funN(z.getN());
    }
}

11.内部类的继承

内部类与外部类紧紧联系在一起,那么如果想要继承一个外部类的内部类,就需要使用到特殊的语法 OutClass.super();

public class DemoOuter {
    class Inner{}
}

public class ExtInner extends DemoOuter.Inner {
    public ExtInner(DemoOuter demoOuter){
        demoOuter.super(); //必须添加如此这般的构造器并显示指定父类对象引用
    }
}

12.内部类初始化顺序

内部类的构造器会先调用,然后才会调用外部类构造器。
内部类不存在覆盖问题,Egg中的Yolk和BigEgg中的Yolk完全是两个不同的类。

public class Egg {
    class Yolk{
        public Yolk(){
            System.out.println("Egg York"); // 1 3
        }
        public void f(){
            System.out.println("Egg f()");
        }
    }
    private Yolk york = new Yolk();
    public Egg(){
        System.out.println("Egg"); // 2
    }
    public void insert(Yolk y){
        this.york = y;
    }
    public void g(){
        york.f();
    }
}

public class BigEgg extends Egg {
    class Yolk extends Egg.Yolk{
        public Yolk(){
            System.out.println("BigEgg Yolk"); // 4
        }
        public void f(){
            System.out.println("BigEgg f()"); // 5
        }
    }

    public BigEgg(){ 
        insert(new Yolk());
    }

    public static void main(String[] args) {
        BigEgg egg = new BigEgg(); // 0 
        egg.g();
    }
}

-----输出结果-----
Egg York
Egg
Egg York
BigEgg Yolk
BigEgg f()

13.局部内部类和匿名内部类的区别

前面讲过,将显示的类声明写出来,在返回一个对象的方式叫做局部内部类,但是如果直接new 接口并返回,那么他就是匿名内部类。
如果某个场景下,需要返回一个接口的多种不同实现,那么只有局部内部类做得到,匿名内部类是做不到的。

14.内部类标识符

匿名内部类 --> LocalInnerClass$1.class
局部内部类 --> LocalInnerClass$Inner.class

相关