内部类有哪些?它们存在的意义是什么?

一、内部类的分类及区别

内部类的表现形式为一个类可以在另一个类的内部存在,其中,内部包含其它类的类称为外部类,被包含的类称为内部类。在如下的示例代码中,Outer就是外部类,Inner就是内部类。

public class Outer {
    public class Inner {
    }
}

常见的内部类主要分为以下四种形式:

  • 成员内部类
  • 静态内部类
  • 局部内部类
  • 匿名内部类

值得说明的是,包含内部类的外部类在编译过后会成为两个完全不同的类,分别是Outer.classOuter$Inner.class

1.1 成员内部类

此时的内部类相当于是外部类的一个普通成员,只要当外部类被实例化成一个对象后,借用该对象就可以对内部类进行操作。

通常成员内部类表现为如下的代码形式:

public class Outer {    
    private String outerValue = "outer";
    // 成员内部类,相当于外部类的一个成员变量
    public class Inner {
        private String innerValue = "inner";    
        public void printInner(){
            System.out.println("访问外部类的私有属性:" + outerValue);
            System.out.println("访问内部类的私有属性:" + innerValue);
        }
    }
}
public class Test {
    public static void main(String[] args) {
        // 01使用成员内部类
        Outer outer = new Outer();
        Outer.Inner inner = outer.new Inner();
        inner.printInner();
    }
}

在使用成员内部类的时候,需要记住以下几点:

  • 内部类可以直接使用外部类的任何属性和方法,即使是private的。
  • 外部类不能直接使用内部类的属性及方法,只能通过内部类的对象来使用。
  • 内部类中不能包含static的内容。
  • 内部类中需要使用外部类的对象时,使用outer.this来指代。

1.2 静态内部类

静态内部类又称为嵌套内部类,是在外部类中具有static关键字声明的内部类,相当于是外部类的类变量。

通常静态内部类表现为如下的代码形式:

public class Outer2 {
    private String outerValue1 = "outerValue1";
    private static String outerValue2 = "outerValue2";
    // 静态内部类/嵌套类
    public static class Inner2{
        private String innerValue1 = "innerValue1";
        public void printInfo(){
            System.out.println("访问外部类中的私有属性:" + new Outer2().outerValue1);
            System.out.println("访问外部类中的静态变量:" + Outer2.outerValue2);
            System.out.println("访问内部类中的私有属性:" + innerValue1);
        }
    }
}
public class Test {
    public static void main(String[] args) {    
        // 02使用静态内部类/嵌套类
        Outer2.Inner2 inner2 = new Outer2.Inner2();
        inner2.printInfo();
}

在使用静态内部类的时候,需要记住以下几点:

  • 静态内部类的内部不能直接访问外部类的非静态成员,只能通过实例化外部类的对象,然后通过该对象进行访问。
  • 创建静态内部类的对象时,不需要像成员内部类一样需要先行创建外部类的对象,而是直接使用内部类的名称即可创建。

1.3 局部内部类

局部内部类是出现在外部类的方法或者某个代码块的作用域范围内的内部类,通常表现为如下的代码形式:

public class Outer3 {
    private String outerValue = "outerValue";
    // 在方法内部定义的局部内部类
    public void outerInfo(){
        final String methodValue = "methodValue";
        class Inner3 {
            String innerValue = "innerValue";
            public void printInner3(){
                System.out.println("访问外部类的属性:" + outerValue);
                System.out.println("访问外部类的方法的常量:" + methodValue);
                System.out.println("访问内部类的属性:" + innerValue);
            }
        }
        Inner3 inner3 = new Inner3();
        inner3.printInner3();
    }
    
    // 在作用域内部定义的局部内部类
    public void outerInfo2(boolean flag){
        if(flag){
            final String methodValue2 = "methodValue2";
            class Inner4{
                String innerValue2 = "innerValue2";
                public void printInner4(){
                    System.out.println("访问外部类的属性:" + outerValue);
                    System.out.println("访问外部类的方法的常量:" + methodValue2);
                    System.out.println("访问内部类的属性:" + innerValue2);
                }
            }
            Inner4 inner4 = new Inner4();
            inner4.printInner4();
        }else{
            System.out.println("outerInfo2:else");
        }
    }
}
public class Test {
    public static void main(String[] args) {
        // 03使用局部内部类
        Outer3 outer3 = new Outer3();
        outer3.outerInfo();
        outer3.outerInfo2(true);
}

在使用局部内部类的时候,需要记住以下几点:

  • 局部内部类仅在作用域范围内有效,超出范围则不再可使用。
  • 局部内部类中可以使用方法或者代码块级别的变量,但是要求它们必须是final类型的常量。

1.4 匿名内部类

匿名内部类没有类名,通常被设计为只使用一次,用以简化代码的书写,通常表现为如下的代码形式:

public class Outer4 {
    private String outerValue = "outerValue";
    // 在方法内部使用匿名内部类
    public Inner5 getInnerInfo(final String name, String value){
        final String methodValue = "methodValue";
        return new Inner5(){
            private String innerValue = name;
            @Override
            public String getInner5() {
                System.out.println("访问外部类的私有属性:" + outerValue);
                System.out.println("访问外部类的方法的常量" + methodValue);
                return innerValue;
            }
        };
    }
}
interface Inner5 {
    String getInner5();
}
public class Test {
    public static void main(String[] args) {
        // 04使用匿名内部类
        Outer4 outer4 = new Outer4();
        Inner5 inner5 = outer4.getInnerInfo("zhangsan", "lisi");
        System.out.println(inner5.getInner5());
    }
}

在使用匿名内部类的时候,需要记住以下几点:

  • 匿名内部类必须继承一个父类或者实现一个接口。
  • 在匿名内部类中需要使用外部类的方法的属性时,要求该属性必须为final类型的常量。
  • 在匿名内部类中需要使用外部类的方法的形参时,要求该形参必须为final类型的常量。

二、内部类的作用及意义

学会使用内部类,是掌握Java高级编程的一部分,能让你更优雅地设计自己的程序结构。

2.1 为了实现封装特性

我们在另一篇文章《面向对象三大特性的总结》中有详细介绍关于封装的常用方法,其中就有使用内部类的方法。

外部类对内部类的封装主要表现为在其它地方想要使用内部类的话需要受到外部类的限制。

public class Animal {
    public class Dog {
    }
    public Dog getDog(){
        return new Dog();
    }
}
public class Test {
    public static void main(String[] args) {
        Animal a = new Animal();
        Dog d = a.new Dog();
        Dog o = a.getDog();
        // 无法直接实例化内部类的对象,如下语句无法编译通过
        Dog g = new Dog();
    }
}

在如上的例子中,内部类虽然是public的,但是想要使用内部类必须借助外部类的对象才能做到,这就是封装。更进一步,如果你只希望在外部类中才能使用内部类,而在其它地方都不能使用的话,只需要将内部类改为private即可。

public class Animal {
    private class Dog {
    }
    public Dog getDog(){
        return new Dog();
    }
    public static void main(String[] args) {
        Animal a = new Animal();
        // 在外部类中可以使用private的内部类,但是仍然只能通过外部类的对象才能访问
        Dog d = a.new Dog();
        Dog o = a.getDog();
    }
}
public class Test {
    public static void main(String[] args) {
        Animal a = new Animal();
        // 下面两行语句都无法编译通过,因为Dog是private的内部类,无法在外部类之外的其它任何地方使用
        Dog d = a.new Dog();
        Dog o = a.getDog();
    }
}

2.2 为了完善多重继承

在Java中一个子类只能继承一个父类,如果想继承多个父类,只能将这些父类改为接口,然后子类实现多个接口。然而接口中的方法都是抽象的,是必须在子类中予以实现的,这就带来了很大的不便,那有没有什么方法能让子类同时继承多个父类中的已经实现好了的方法呢?当然是通过内部类,具体示例如下:

public class Animal {
    public String bark(){
        return "WOW";
    }
}
public class Creature {
    public String drink(){
        return "water";
    }
}
public class Mammal {
    public class Dog extends Creature {
    }
    public class Cat extends Animal {
    }
    public String getDogAction(){
        return new Dog().drink();
    }
    public String getCatAction(){
        return new Cat().bark();
    }
}
public class Test {
    public static void main(String[] args) {
        Mammal m = new Mammal();
        // 对于Mammal来说,其通过内部类Dog和Cat间接地分别继承了Creature和Animal中的方法
        System.out.println(m.getCatAction());
        System.out.println(m.getDogAction());
    }
}

2.3 让Java也有闭包

首先我们需要先理解什么是闭包?

闭包就是能够读取其他函数内部变量的函数。——百度百科

那么映射到Java里面,所谓的闭包就可以指在内部类中可以访问并使用外部类中的所有变量和方法的特性。那么这样的闭包究竟有什么用处呢?别急,我们先来看下面这样一个例子:

public class Fruit {
    private Integer num = 10;
    private void addOneFruit(){
        num++;
        System.out.println("fruit增加到" + num + "个");
    }
    public class Child {
        public void getOneFruit(){
            num--;
            System.out.println("fruit减少到" + num + "个");
        }   
        public void putOneFruit(){
            addOneFruit();
        }
        
    }
}
public class Test {
    public static void main(String[] args) {
        Fruit fruit = new Fruit();
        Child child = fruit.new Child();
        // 通过内部类对象引用来操作外部类的私有属性和方法(称为闭包)
        child.getOneFruit();
        child.putOneFruit();
    }
}

在上面的例子中,main方法中原来是无论如何都不能访问Fruit的私有属性numaddOneFruit的,但是通过内部类Child,这一切就成了可能,从而实现了闭包。下面是列举的关于闭包的好处:

  • 使得环境外部(类、函数)可以有一种途径访问环境内的私有成员。
  • 使得当前环境的对象得以一直保留在内存中,即使将该对象的引用置为null。

关于上述第一点,你是否会有这样的疑问:为什么不使用setter和getter方法对环境内的私有成员进行访问呢?

如果单从能否实现角度看,我们要访问环境内的私有成员,当然可以通过setter和getter方法,但是从面向对象编程角度看,则不能依靠这种方法。比如Fruit的减少只能通过Child的操作来进行,这是模拟现实世界中的操作“吃”,只有Child才能吃掉Fruit达到让Fruit减少的目的,总不能让Fruit自己吃自己吧。

关于上述的第二点,我们可以改下main方法进行说明:

public class Test {
    public static void main(String[] args) {
        Fruit fruit = new Fruit();
        Child child = fruit.new Child();
        // 通过内部类对象引用来操作外部类的私有熟悉和方法(称为闭包)
        child.getOneFruit();
        child.putOneFruit();
        // 即使将外部类的引用置为null,但因其内部类引用仍然存在,内部类需要依赖外部类,所以外部类的对象仍然得以保留,不会被GC回收
        fruit = null;
        child.putOneFruit();
        child.getOneFruit();
        child = null;
        // 内部类的引用被置为null后,内部类的对象和外部类的对象从此就没有引用指向它们,很快会被GC回收
        // 下面两行编译无法通过
        child.getOneFruit();
        child.putOneFruit();
    }
}

三、常见的使用内部类的例子

待补充......

你可能感兴趣的:(内部类有哪些?它们存在的意义是什么?)