java 内部类 嵌套类

概述

java允许我们把一个类a定义在另一个类b里面,那么这个类a就叫做内部类。例如如下面:

  • 如果内部类似Non-static的那么被称作内部类

    1
    2
    3
    4
    5
    6
    class OuterClass {
    ...
    class NestedClass {
    ...
    }
    }
  • 如果内部类是static 的那么被称作嵌套类或者内部静态类

    1
    2
    3
    4
    5
    6
    7
    class OuterClass {
    ...
    static class StaticNestedClass {
    ...
    }
    }

使用方法

普通的内部类

1
2
Outer o = new Outer();
Outer.Inner i = o.new Inner();
  • 内部类的对象只能在与其外部类相关联的情况下才能被创建。
  • 内部类访问外部类实例时可以使用Outer.this,创建内部类实例时使用外部类的实例对象 .new Inner()
  • 内部类对象隐含地保存了一个指向外部类对象的引用,每一个非静态内部类的实例都链接到一个外部类的实例上

    静态嵌套类

1
Outer.Inner i = new Outer.Inner();
  • 如果不需要内部类与其外部类对象之间有联系,可以将内部类声明为static.
  • 不能访问非静态的外围成员

    注意

  • 如果一个类要被声明为static的,只有一种情况,就是静态内部类。
  • 静态内部类跟静态方法一样,只能访问静态的成员变量和方法,不能访问非静态的方法和属性,但是普通内部类可以访问任意外部类的成员变量和方法
  • 静态内部类可以声明普通成员变量和方法以及static成员变量和方法,而普通内部类不能声明static成员变量和方法。

    为什么需要内部类

    内部类的使用场景

    我们来看一个例子:

    先定义一个抽象类
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    /**
    * Selector迭代器
    */
    public abstract class Selector<T> {
    public void ff(){
    System.out.println(this.getClass().getName());
    }
    abstract boolean hasNext();
    abstract T next();
    }

    再定义一个Sequence类

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    /**
    * 用类包装一个Integer的数组,并实现添加 通过继承Selector迭代遍历
    */
    public class Sequence extends Selector<Integer>{
    private Object [] items;
    private int next =0;
    public Sequence(int size){
    items=new Object[size];
    }
    public void add(int x){
    if(next <items.length){
    items[next]=x;
    next++;
    }
    }
    /************************************实现抽象类*/
    private int index=0;
    @Override
    boolean hasNext() {
    return !(index==items.length);
    }
    @Override
    Integer next() {
    Integer value=null;
    if( hasNext()){
    value=Integer.parseInt(items[index].toString()) ;
    index++;
    }
    return value;
    }
    }

    我们定义一个Sequence类来包装了一个Object的数组,里面封装了添加初始化操作,通过继承了Selector 来实现了迭代遍历功能。
    这时如果我们需要Sequence再继承其他类怎么办?比如现在有一个类SequenceBiz

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    /**
    * Sequence需要继承的业务
    */
    public class SequenceBiz {
    public void log()
    {
    //dosomestring 一些需要Sequence继承的业务
    System.out.println(this.getClass().getName()+"我记录了日志");
    }
    }

    这个时候可以使用内部类来解决

    使用内部类
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    /**
    * 用类包装一个Object的数组,并使用内部类通过继承Selector迭代遍历
    * 继承SequenceBiz 来处理其他业务
    */
    public class Sequence1 extends SequenceBiz{
    private Object [] items;
    private int next =0;
    public Sequence1(int size){
    items=new Object[size];
    }
    public void add(int x){
    if(next <items.length){
    items[next]=x;
    next++;
    }
    }
    private class SequenceSelector extends Selector<Integer>{
    private int index=0;
    @Override
    public boolean hasNext() {
    return !(index==items.length);
    }
    @Override
    public Integer next() {
    Integer value=null;
    if( hasNext()){
    value=Integer.parseInt(items[index].toString()) ;
    index++;
    }
    return value;
    }
    }
    /**
    * 返回迭代器
    * @return
    */
    public Selector getSelector()
    {
    return new SequenceSelector();
    }
    }

    我们来测试一下

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    public class TestInnerClass {
    public static void main(String[] args)
    {
    // Sequence sequence=new Sequence(5);
    // for (int i=0;i<5;i++){
    // sequence.add(i);
    // }
    // while (sequence.hasNext()){
    // System.out.println(sequence.next());
    // }
    Sequence1 sequence1=new Sequence1(5);
    for (int i=0;i<5;i++){
    sequence1.add(i);
    }
    Selector selector=sequence1.getSelector();
    while (selector.hasNext()){
    System.out.println(selector.next());
    }
    sequence1.log();
    }
    }
我们来看内部类的好处:
  • 使用内部类解决了java中不能继承多个类的问题
  • Sequence1创建的private内部类来实现Selector 隐藏了Selector的具体实现。(jdk源码中ArrayList的迭代器就是这种方法)
  • 嵌套小类会使代码更靠近使用位置,使代码更加利于维护、

    静态内部类的场景

普通类中创建一个静态类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
public class Outer {
private String name;
private int age;
public static class Builder {
private String name;
private int age;
public Builder(int age) {
this.age = age;
}
public Builder withName(String name) {
this.name = name;
return this;
}
public Builder withAge(int age) {
this.age = age;
return this;
}
public Outer build() {
return new Outer(this);
}
}
private Outer(Builder b) {
this.age = b.age;
this.name = b.name;
}
}

静态内部类调用外部类的构造函数,来构造外部类,由于静态内部类可以被单独初始化说有在外部就有以下实现。即Builder design pattern(生成器设计模式)
我们可以在main中使用

1
调用 Outer outer = new Outer.Builder(2).withName("Yang Liu").build();

接口中的内部类

在接口中我们定义的内部类只能是静态内部类。关于使用场景可以参考shiro框架中Subject接口。

1
2
3
4
5
6
7
8
9
10
/**
*生成器设计模式实现用于以简化的方式创建实例
Builder design pattern implementation for creating {@link Subject} instances in a simplified way without requiring knowledge of Shiro's construction techniques.
*
/
public interface Subject {
public static class Builder {
}
}

关于这个设计模式可以参考

- https://www.cnblogs.com/zhuyuliang/p/5212746.html
- [Builder Design Pattern](https://dev.to/nishparadox/builder-design-pattern?utm_source=tuicool&utm_medium=referral)
说明
- 如果类的构造器或者静态工厂中有多个参数,设计这样类时,最好使用builder模式,特别是大多数参数都是可选的时候。
- 如果现在不能确定参数的个数,使用构造器即builder模式。
- 静态内部类提高了封装性,和代码的可读性。 

总结