0%

面向对象编程(高级部分)-2

面向对象编程(高级部分)

★抽象类

  • 由于父类方法可能存在不确定性的问题(即只知道需要这个方法,但是没有具体的方法体),所以就要考虑将该方法设计为抽象(abstract)方法,所谓抽象方法就是没有实现的方法,即该方法没有方法体

    当一个类中存在抽象方法时,需要将该类声明为抽象(abstract)类

    一般来说,抽象类会被继承,由子类来实现具体的抽象方法。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    package com.f.Chapter10.abstract_;

    public class Abstract {
    public static void main(String[] args) {

    }
    }

    //由于该类中存在抽象方法 eat,所以该类需要为抽象类
    abstract class Animal {
    public String name;

    //不同的动物吃的东西不一样,所以这里仅将 eat 方法定义为抽象方法
    public abstract void eat();
    }

    当父类的一些方法不能确定时,可以用 abstract 关键字来修饰该方法,这个方法就是抽象方法,用 abstract 来修饰该类就是抽象类。

  1. 用 abstract 关键字来修饰一个类时,这个类就叫抽象类。

    1
    2
    访问修饰符 abstract 类名{
    }
  2. 用 abstract 关键字来修饰一个方法时,这个方法就是抽象方法。

    访问修饰符 abstract 返回类型 方法名(参数列表); //没有方法体

  3. 抽象类的价值更多作用是在于设计,是设计者设计好后,让子类继承并实现抽象类。

★抽象类使用细节
  1. 抽象类不能被实例化

  2. 抽象类不一定要包含 abstract 方法。也就是说,抽象类可以没有 abstract 方法。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    package com.f.Chapter10.abstract_;

    public class AbstractDetail {
    public static void main(String[] args) {

    }
    }

    abstract class Animals {
    private String name;

    public void eat() {

    }
    }
  3. 一旦一个类包含了 abstract 方法,那么这个类必须声明为 abstract 类

  4. abstract 只能修饰类和方法,不能修饰属性和其他的。

  5. 抽象类可以有任意成员(因为抽象类还是类),比如:非抽象方法、构造器、静态属性等等。

  6. 抽象方法不能有主体,即不能实现。

  7. 如果一个类继承了抽象类,则它必须实现抽象类的所有抽象方法,除非它自己也声明为 abstract类

    考虑这么一种情况:抽象类 Animals 有一个抽象方法 eat,子类 Dogs 没有实现这个抽象方法,所以子类 Dogs 也为抽象类,那么 Dogs 的子类 Corgi 若要不为抽象类,则 Corgi 是否需要实现这个抽象方法呢?-> 答案是需要的。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    package com.f.Chapter10.abstract_;

    public class AbstractDetail {
    public static void main(String[] args) {
    }
    }

    abstract class Animals {
    private String name;
    public abstract void eat();
    }

    abstract class Dogs extends Animals {
    }

    class Corgi extends Dogs{
    @Override
    public void eat() {}
    }
  8. 抽象方法不能使用 private、final 和 static 来修饰,因为这些关键字都是和重写相违背的,即 “用 private、final 和 static 修饰的方法是不能被重写的”

模板设计模式

  • 考虑下面的场景:我们有多个类,各自完成不同的任务 job,要求计算不同任务的耗时。

    为了方便起见,假设只有两个类 AA 和 BB,则它们的任务 job 和耗时可以用下面的代码来实现。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    package com.f.Chapter10.abstract_;

    public class TemplateDesignMode {
    public static void main(String[] args) {
    AA aa = new AA();
    aa.job();
    BB bb = new BB();
    bb.job();
    }
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    package com.f.Chapter10.abstract_;

    public class AA {
    public void job() {
    long start = System.currentTimeMillis();
    long sum = 0;

    for (int i = 0; i < 800000; i++) {
    sum += i;
    }

    long end = System.currentTimeMillis();
    System.out.println("任务耗时" + (end - start) + "ms");
    }
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    package com.f.Chapter10.abstract_;

    public class BB {
    public void job() {
    long start = System.currentTimeMillis();
    long sum = 0;

    for (int i = 0; i < 80000; i++) {
    sum *= i;
    }

    long end = System.currentTimeMillis();
    System.out.println("任务耗时" + (end - start) + "ms");
    }
    }
  • 但是若类似的类有好多个,则它们的代码都有相同的重叠部分,造成代码的重复和冗余,为了提高代码的复用性,可以考虑把代码相同的部分抽取出来,做成一个公共的父类模板 Template,这些类都继承这个父类

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    package com.f.Chapter10.abstract_;

    public abstract class Template {
    public void calculateTime() {
    long start = System.currentTimeMillis();
    long sum = 0;
    job(); //涉及到动态绑定:当调用对象方法的时候,该方法会和该对象内存地址/运行类型绑定;
    long end = System.currentTimeMillis();
    System.out.println("任务耗时" + (end - start) + "ms");
    }

    //这里,为了让 job 各不相同,应为抽象方法。实际操作发现不为抽象方法也无伤大雅
    public abstract void job();
    }

    这里的抽象类 job 由继承的子类来重写。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    package com.f.Chapter10.abstract_;

    public class AA extends Template{
    @Override
    public void job() {
    long sum = 0;

    for (int i = 0; i < 800000; i++) {
    sum += i;
    }
    }
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    package com.f.Chapter10.abstract_;

    public class BB extends Template{
    @Override
    public void job() {
    long sum = 0;

    for (int i = 0; i < 80000; i++) {
    sum *= i;
    }
    }
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    package com.f.Chapter10.abstract_;

    public class TemplateDesignMode {
    public static void main(String[] args) {
    AA aa = new AA();
    aa.calculateTime();
    BB bb = new BB();
    bb.calculateTime();
    }
    }
  • 由此,将相同的代码部分提取出来,做成公共的模板,提高了代码的复用性,称为模板设计模式。

★接口

  • 接口就是给出一些没有实现的方法,封装到一起,到某个类要使用的时候,再根据具体情况把这些方法写出来。

  • 接口的定义语法:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    interface 接口名{
    //属性;
    //方法(1.抽象方法;2.默认实现方法;3.静态方法);
    }

    class 类名 implements 接口{
    //自己的属性;
    //自己的方法;
    //必须实现的接口的抽象方法;
    }

    注意:

    1. 在 jdk 7.0 之前,接口里的所有方法都没有方法体;

    2. 在 jdk 8.0 之后,接口里可以有静态方法,默认方法,也就是说接口中可以有方法的具体体现;

      • 注意:接口的默认方法要通过 “实现了接口的类的对象” 来调用,且默认方法可以被重写。

        默认方法可以被重写,实现类可以直接使用接口默认方法,也可以重写接口默认方法。

    3. 在接口中,抽象方法,可以省略 abstract

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    interface MyInterface {
    //默认方法,jdk 8.0 之后
    default public void myMethod(){
    System.out.println("MyInterface...");
    }
    //静态方法,jdk 8.0 之后
    public static void hi(){
    System.out.println("hi...");
    }
    //在接口中,省略了 abstract 修饰的抽象方法
    public void hello();
    }

    class A implements MyInterface {
    //引用接口的类需要实现接口的抽象方法
    @Override
    public void hello() {
    System.out.println("hello...");
    }
    }
★接口使用细节
  1. 接口不能被实例化;

  2. 接口中所有的方法都是 public 方法,接口中的抽象方法可以不用 abstract 修饰

  3. 一个普通类实现(implements)接口,就必须将该接口的所有方法都实现,如果方法很多,则可以使用 alt+enter 来快速实现;

  4. 抽象类实现接口时,可以不用实现接口的方法;

  5. 一个类可以同时实现多个接口

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    interface IA{
    void iaHi(); //修饰符public和abstract可以不写
    }

    interface IB{
    void ibHello();
    }

    class AA implements IA,IB {
    @Override
    public void iaHi() {
    }
    @Override
    public void ibHello() {
    }
    }
  6. 接口中的属性,只能是 final 的,而且是 public static final 修饰符,例如,int a = 1;,实际上是 public static final int a = 1;(必须初始化);

    1
    2
    3
    interface IA{
    int a = 1; //等价于 public static final int a = 1;
    }
  7. **接口中属性的访问形式为:接口名.属性名**;

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    interface IA{
    int a = 1;
    void iaHi();
    }

    class AA implements IA {
    private int aa = IA.a; //接口名.属性名
    @Override
    public void iaHi() {
    }
    }
  8. 接口不能继承其他的类,但是可以继承多个别的接口,如 interface IA extends IB,IC{}

  9. 接口的修饰符只能是 public 和默认,这点和类的修饰符是一样的。

  10. 对于接口中的属性,当类实现接口时,以下几种访问形式都是可以的

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    public class InterfaceTest {
    public static void main(String[] args) {
    AA aa = new AA();
    System.out.println(IA.a); //接口名.属性名
    System.out.println(aa.a); //对象名.属性名
    System.out.println(AA.a); //类名.属性名
    }
    }

    interface IA{
    int a = 1;
    }

    class AA implements IA {
    }
★接口vs继承
  • 当子类继承了父类时,就自动的拥有了父类的”功能“(属性和方法),如果子类需要扩展功能,就可以通过实现接口的方法来扩展。

    实现接口是对 java 单继承机制的一种补充

    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
    public class ExtendsVsInterface {
    public static void main(String[] args) {
    LittleMonkey littleMonkey = new LittleMonkey("小猕");
    littleMonkey.climbing(); //猴子小猕会爬树...
    littleMonkey.swimming(); //小猕通过学习学会了游泳...
    }
    }

    class Monkey {
    private String name;

    public Monkey(String name) {
    this.name = name;
    }

    public String getName() {
    return name;
    }

    public void climbing() {
    System.out.println("猴子" + this.name + "会爬树...");
    }
    }

    interface Fishable {
    void swimming();
    }

    class LittleMonkey extends Monkey implements Fishable {
    public LittleMonkey(String name) {
    super(name);
    }

    @Override
    public void swimming() {
    System.out.println(getName() + "通过学习学会了游泳...");
    }
    }
  • 接口和继承解决的问题不同:

    接口的价值主要在于:设计,设计好各种规范(方法),让其他类去实现这些方法

    继承的价值主要在于:解决代码的复用性和可维护性问题。

  • 接口比继承更加灵活,继承是满足 is-a 的关系,而接口只需满足 like-a 的关系。

  • 接口在一定程度上实现了代码的解耦(接口规范性 + 动态绑定机制)。

★接口多态特性
  1. 多态参数。在下面的代码中,对于 work 函数,形参是接口类型 UsbInterface,而实参第一次传入的是 phone,第二次传入的是 camera,即为接口的多态参数特性,即可以接收 “实现了 UsbInterface 接口的类的对象实例“。

    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
    package com.f.Chapter10.interface_;

    public class Computer {
    public static void work(UsbInterface usbInterface){
    usbInterface.start();
    usbInterface.stop();
    }

    public static void main(String[] args) {
    Phone phone = new Phone();
    Camera camera = new Camera();
    work(phone);
    work(camera);
    }
    }

    interface UsbInterface{
    void start();
    void stop();
    }

    class Phone implements UsbInterface{
    @Override
    public void start() {
    System.out.println("手机接入usb接口...");
    }

    @Override
    public void stop() {
    System.out.println("手机拔出usb接口...");
    }
    }

    class Camera implements UsbInterface{
    @Override
    public void start() {
    System.out.println("相机接入usb接口...");
    }

    @Override
    public void stop() {
    System.out.println("相机拔出usb接口...");
    }
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    package com.f.Chapter10.interface_;

    public class InterfacePolyParameter {
    public static void main(String[] args) {
    //接口的多态体现
    //接口类型的变量 ia 可以指向 实现了IA接口的类 的对象实例
    IA ia = new Bicycle();
    ia = new Car();

    //继承的多态体现
    //父类的变量 base 可以指向继承了父类的子类 Sub 的对象实例
    Base base = new Sub();
    }
    }

    interface IA { }

    class Bicycle implements IA { }

    class Car implements IA { }

    class Base { }

    class Sub extends Base { }
  2. 多态传递

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    package com.f.Chapter10.interface_;

    public class InterfacePolyPass {
    public static void main(String[] args) {
    //接口类型的变量可以指向 实现了该接口的类 的对象实例
    IB ib = new Teacher();
    //如果接口 IB 继承了 IA,那么 IA 的变量也可以指向 实现了接口IB的类 的对象实例
    //亦即,实际上就相当于 Teacher 类也要实现 IA 接口
    IA ia = new Teacher();
    }
    }

    interface IA {}
    interface IB extends IA {}
    class Teacher implements IB {}

★内部类

  • 一个类的内部又完整地嵌套了另一个类结构,被嵌套的类称为内部类(inter class),嵌套其他类的类称为外部类(outer class)。

  • 内部类的基本语法:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    class Outer{		//外部类
    class Inner{ //内部类

    }
    }

    class Other{ //外部其他类

    }

    内部类的最大特点就是可以直接访问外部类的私有属性,并且可以体现类与类之间的包含关系。

  • 内部类可以分为四种:

    • 定义在外部类的局部位置上(比如方法内):
      1. 局部内部类(有类名);
      2. 匿名内部类(没有类名);
    • 定义在外部类的成员位置上:
      1. 成员内部类(没用 static 修饰);
      2. 静态内部类(用 static 修饰)。
局部内部类
  • 局部内部类是定义在外部类的局部位置上,比如方法中代码块中,并且有类名

    1. 可以直接访问外部类的所有成员,包含私有的;
    2. 局部内部类不能添加访问修饰符,因为它的地位就是一个局部变量,局部变量是不能使用修饰符的,但是可以使用 final 修饰,因为局部变量也可以使用 final
    3. 作用域:仅仅在定义它的方法或代码块中;
    4. 局部内部类 —-访问—-> 外部类的成员,访问方式是直接访问;
    5. 外部类 —-访问—-> 局部内部类的成员,访问方式是先创建对象,再访问(注意:必须在作用域内);
    6. 外部其他类不能访问局部内部类,因为局部内部类的地位就是一个局部变量;
    7. 如果外部类和局部内部类的成员重名,那么局部内部类想访问该成员的话,默认遵循就近原则,如果想访问外部类的成员,则可以使用(外部类名.this.成员)去访问。
    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
    package com.f.Chapter10.innerclass;

    public class LocalInnerClass {
    public static void main(String[] args) {
    Outer outer = new Outer();
    outer.foo();
    System.out.println("outer的地址为:" + outer);
    }
    }

    class Outer {
    private int n1 = 100;

    public void foo() {
    //局部内部类定义在外部类的局部位置上,比如方法中
    final class Inner {
    private int n1 = 200;

    public void f1() {
    //局部内部类可以直接访问外部类的所有成员,包含私有的
    //Outer.this 本质就是外部类的对象,即哪个Outer类的对象调用了foo方法,那么这个Outer.this就是哪个对象
    System.out.println("类Outer的私有成员n1的值为:" + Outer.this.n1);
    System.out.println("Outer.this的地址为:" + Outer.this);
    System.out.println("类Inner的私有成员n1的值为:" + n1);
    System.out.println("类Inner的私有成员n1的值为:" + this.n1);
    }
    }
    //外部类访问局部内部类的成员的方式是先创建对象,再访问(注意:必须在作用域内)
    Inner inner = new Inner();
    inner.f1();
    }
    }
★★★匿名内部类
  • 匿名内部类是定义在外部类的局部位置上,比如方法中代码块中,并且没有类名

    1. 本质是类;

    2. 是内部类;

    3. 该类没有名字(是系统分配名字);

    4. 同时还是一个对象。

    5. 语法:

      1
      2
      3
      new 类/接口(参数列表){
      //......
      };
    • 基于接口的匿名内部类:

      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
      46
      package com.f.Chapter10.innerclass;

      public class AnonymousInnerClass {
      public static void main(String[] args) {
      Outer outer = new Outer();
      outer.method();
      }
      }

      class Outer {
      private int n1 = 100;

      public void method() {
      //基于接口的匿名内部类
      //1. 需求:想使用 IA 接口,并创建对象
      //2. 传统方式是写一个类,实现该接口,再创建这个类的对象
      //3. 但需求是 Tiger 类只使用一次,后面不再使用
      //4. 为了简化开发,可以使用匿名内部类
      //5. tiger 的编译类型? -> IA
      //6. tiger 的运行类型? -> Outer$1
      /*
      我们看底层,会发现系统分配有类名 Outer$1
      class Outer$1 implements IA {
      @Override
      public void cry(){
      System.out.println("老虎叫唤...");
      }
      }
      */
      //7. jdk底层在创建匿名内部类 Outer$1 后,就立马创建了 Outer$1 实例,并且把地址返回给了 tiger 变量
      //8. 匿名内部类只使用一次,就不再使用(注意这是对匿名内部类来说的,而不是对创建好的实例来说的)
      IA tiger = new IA() {
      @Override
      public void cry() {
      System.out.println("老虎叫唤...");
      }
      };
      tiger.cry();
      tiger.cry();
      System.out.println("tiger的运行类型是:" + tiger.getClass());
      }
      }

      interface IA {
      void cry();
      }
    • 基于类的匿名内部类:

      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
      46
      47
      48
      49
      50
      51
      52
      package com.f.Chapter10.innerclass;

      public class AnonymousInnerClass_ {
      public static void main(String[] args) {
      Outer outer = new Outer();
      outer.method();
      }
      }

      class Outer {
      private int n1 = 100;

      public void method() {
      //基于类的匿名内部类
      //1. father01 的编译类型? -> Father
      // father02 的编译类型? -> Father
      //2. father01 的运行类型? -> Outer$1
      // father02 的运行类型? -> Father
      /*
      我们看底层,会发现系统分配有类名 Outer$1
      class Outer$1 extends Father {
      @Override
      public void test() {
      System.out.println("匿名内部类重写Father类的test方法...");
      }
      }
      */
      //3. jdk底层在创建匿名内部类 Outer$1 后,就立马创建了 Outer$1 实例,并且把地址返回给了 father01 变量
      //4. 匿名内部类只使用一次,就不再使用(注意这是对匿名内部类来说的,而不是对创建好的实例来说的)
      //5. 注意:("jack") 这个参数列表会传递给 Father 类的构造器
      Father father01 = new Father("jack") {
      @Override
      public void test() {
      System.out.println("匿名内部类重写Father类的test方法...");
      }
      };
      System.out.println("father01的运行类型是:" + father01.getClass());
      //Father father02 = new Father("tom");
      //System.out.println("father02的运行类型是:" + father02.getClass());
      father01.test();
      }
      }

      class Father {
      public Father(String name) {
      System.out.println("Father类的构造器,接收到参数name=" + name);
      }

      public void test() {
      System.out.println("Father类的test方法...");
      }
      }
★匿名内部类使用细节
  1. 匿名内部类的语法比较奇特,请注意,因为匿名内部类既是一个类的定义,同时它本身也是一个对象,因此从语法上看,它既有定义类的特征,也有创建对象的特征
  2. 匿名内部类可以访问外部类的所有成员,包括私有的;
  3. 不能添加访问修饰符,因为它的地位就是一个局部变量;
  4. 作用域:仅仅在定义它的方法或代码块中;
  5. 匿名内部类 —-访问—-> 外部类的成员,访问方式是直接访问;
  6. 外部其他类不能访问匿名内部类;
  7. 如果外部类和匿名内部类的成员重名,那么匿名内部类想访问该成员的话,默认遵循就近原则,如果想访问外部类的成员,则可以使用(外部类名.this.成员)去访问;
  8. 匿名内部类涉及到:继承、多态、动态绑定、内部类
★★★匿名内部类实践
  • 当作实参直接传递,代码简洁高效。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    package com.f.Chapter10.innerclass;

    public class AnonymousInnerClassExercise {
    public static void main(String[] args) {
    //匿名当作实参直接传递,代码简洁高效。
    f1(new IA(){
    @Override
    public void show() {
    System.out.println("匿名内部类的 show 方法...");
    }
    });
    }

    //静态方法,形参是接口类型 IA
    public static void f1(IA ia) {
    ia.show();
    }
    }

    interface IA {
    void show();
    }

    如果不使用匿名内部类,则要先创建一个实现了 IA 接口的类,然后在 main 函数中创建该类的对象,再把该对象作为参数传入 f1,如果只使用一次 f1,代码就会显得很繁琐。

成员内部类
  • 成员内部类是定义在外部类的成员位置,并且没有 static 修饰

    1. 可以直接访问外部类的所有成员,包括私有的;

    2. 可以添加任意访问修饰符(public、protected、默认、private),因为它的地位就是一个成员;

      注意:对于一个外部类,其访问修饰符只能是默认或者 public;

    3. 作用域:和外部类的其他成员一样,为整个外部类的类体;

    4. 成员内部类 —-访问—-> 外部类的成员,访问方式是直接访问;

    5. 外部类 —-访问—-> 成员内部类的成员,访问方式是先创建对象,再访问(注意:必须在外部类的类体内);

    6. “ 在成员内部类中,say 方法的修饰符是 private,所以在外部其他类中,无法调用该方法,但是在外部类中是可以调用该方法的,如 f1 函数所示 ”。

    7. 外部其他类创建成员内部类的实例对象的方式有以下两种:

      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
      package com.f.Chapter10.innerclass;

      public class MemberInnerClass {
      public static void main(String[] args) {
      Outer outer = new Outer();
      outer.f1();

      //外部其他类访问成员内部类的成员的两种方式:
      //第一种方式:outer.new Inner():相当于把new Inner()当作是outer成员
      Outer.Inner inner01 = outer.new Inner();
      Outer.Inner inner02 = new Outer().new Inner();

      //第二种方式:在外部类中,编写一个方法,返回Inner对象实例
      Outer.Inner inner03 = outer.getInnerInstance();

      //注意:因为在成员内部类中,say 方法的修饰符是 private,所以在外部其他类中,无法调用该方法,
      // 但是在外部类中是可以调用该方法的,如 f1 函数所示
      }
      }

      class Outer { //外部类
      private int n1 = 10;
      public String name = "张三";

      class Inner { //成员内部类
      private void say() {
      //可以直接访问外部类的所有成员,包括私有的
      System.out.println("外部类Outer的n1 = " + Outer.this.n1);
      System.out.println("外部类Outer的name = " + Outer.this.name);
      }
      }

      public void f1(){
      //使用成员内部类:创建成员内部类的对象,然后使用
      Inner inner = new Inner();
      inner.say();
      }

      //该方法返回成员内部类的实例对象
      public Inner getInnerInstance(){
      return new Inner();
      }
      }
    8. 如果外部类和成员内部类的成员重名,那么成员内部类想访问该成员的话,默认遵循就近原则,如果想访问外部类的成员,则可以使用(外部类名.this.成员)去访问。

静态内部类
  • 静态内部类是定义在外部类的成员位置,并且static 修饰

    1. 可以直接访问外部类的所有静态成员,包括私有的,但不能直接访问非静态成员

    2. 可以添加任意访问修饰符(public、protected、默认、private),因为它的地位就是一个成员;

    3. 作用域:和外部类的其他成员一样,为整个外部类的类体;

    4. 静态内部类 —-访问—-> 外部类的静态成员,访问方式是直接访问;

    5. 外部类 —-访问—-> 静态内部类的成员,访问方式是先创建对象,再访问(注意:必须在外部类的类体内);

    6. 外部其他类创建静态内部类的实例对象的方式有以下两种:

      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
      package com.f.Chapter10.innerclass;

      public class StaticInnerClass {
      public static void main(String[] args) {
      Outer outer = new Outer();
      outer.hi();

      //外部其他类访问静态内部类的成员的两种方式:
      //第一种方式:new Outer.Inner():因为是静态内部类,所以可以直接用 Outer.Inner
      Outer.Inner inner01 = new Outer.Inner();
      inner01.say();

      //第二种方式:在外部类中,编写一个方法,返回Inner对象实例
      Outer.Inner inner02 = new Outer().getInnerInstance();
      inner02.say();
      }
      }

      class Outer {
      private int n1 = 10;
      private static String name = "张三";

      public static class Inner {
      private String name = "李四";

      public void say() {
      System.out.println("静态内部类的非静态变量name的值为:" + name);
      System.out.println("外部类的静态变量name的值为:" + Outer.name);
      System.out.println("=====================================");
      //不能直接访问外部类的非静态成员
      //System.out.println(n1); //无法访问
      }
      }

      public void hi() {
      System.out.print("我是类Outer的hi方法...\t");
      Inner inner = new Inner();
      inner.say();
      }

      public Inner getInnerInstance() {
      return new Inner();
      }
      }
    7. 如果外部类和静态内部类的成员重名,那么静态内部类想访问该成员的话,默认遵循就近原则,如果想访问外部类的成员,则可以使用(外部类名.成员)去访问。

      注意:上面所说的成员,在外部类中一定是静态成员,但在静态内部类中,可以是静态的,也可以是非静态的。

---------------The End---------------