Java-Day-13(抽象类 + 接口 + 内部类)

发布时间 2023-04-21 19:55:27作者: 朱呀朱~

Java-Day-13

抽象类

( abstract )

  • 当父类的某些方法需要声明,但是又不确定如何实现时 ( 主要在于子类的重写时 ),可以将其声明为抽象方法,那么这个类就是抽象类

    • 所谓抽象方法就是没有实现的方法,而所谓没有实现就是指没有方法体
    • 当一个类中存在抽象方法时,需要将该类声明为 abstract 类
      • 一般来说,抽象类会被继承,由其子类来实现抽象方法
    abstract class Animal {
        private String name;
        public Animal(String name){
            this.name = name;
        }
    //    public void eat() {
    //        System.out.println("这是个动物,但不能确定是吃啥");
    //    }
        public abstract void eat();
    }
    
  • 抽象类简介

    • 用 abstract 关键字来修饰一个类时,这个类就叫抽象类
      • 访问修饰符 abstract 类名
    • 用 abstract 关键字来修饰一个方法时,这个方法就是抽象方法
      • 访问修饰符 abstract 返回类型 方法名 ( 参数列表 ); // 无 {} 方法体
    • 抽象类的价值更多作用是在于设计,是设计者设计好后,让子类继承并实现抽象类 ()
    • 抽象类在框架和设计模式使用较多
  • 注意细节

    • 抽象类不能被实例化
    • 抽象方法不能有主体,即不能实现
    • 抽象类不一定要包含 abstract 方法,还可以只有实现方法
    • 一旦类包含了 abstract 方法,那么此类就必须声明为 abstract
    • abstract 只能修饰类和方法,不能修饰属性或其它的
    • 抽象类可以有任意成员 ( 因为其本质还是类 ),如非抽象方法、构造器、静态属性等等
    • 如果一个类继承了抽象类,则它必须实现抽象类的所有抽象方法,除非子类它自己也声明为 abstract 类
    • 抽象方法不能用 private、final 和 static 来修饰,因为这些关键字都是和重写相违背的
  • 抽象类最佳实践 - 模板设计模式

    • 需求:

      • 有多个类,完成不同的任务 job
      • 要求统计得到各自完成任务的时间
      class A{
          public void job() {
      //        返回当前时间(毫秒为单位),用long类型接收
              long start = System.currentTimeMillis();
              long num = 0;
              for (long i = 1; i <= 1000000; i++) {
                  num += i;
              }
              long end = System.currentTimeMillis();
              System.out.println("A执行时间" + (end - start));
          }
      }
      
    • 代码改写

      class A{
          
          public void calculateTime(){
              long start = System.currentTimeMillis();
              job();
              long end = System.currentTimeMillis();
              System.out.println("A执行时间" + (end - start));
          }
          
          public void job() {
              long num = 0;
              for (long i = 1; i <= 1000000; i++) {
                  num += i;
              }
          }
      }
      
    • 模板设计模式

      • 设计一个抽象类 ( Template )

        编写方法 calculateTime()

        编写抽象方法 job()

      abstract public class Template {
          public abstract void job();
          public void calculateTime(){
              long start = System.currentTimeMillis();
              job();
              long end = System.currentTimeMillis();
              System.out.println("A执行时间" + (end - start));
          }
      }
      
      public class A extends Template{
          public void job() {
              long num = 0;
              for (long i = 1; i <= 1000000; i++) {
                  num += i;
              }
          }
      }
      
      // a对象没有calculateTime()方法,就到父类去找,父类此方法的job()进行动态绑定机制,找到对象是a,就去A里执行job()
      public class test {
          public static void main(String[] args){
              A a = new A();
              a.calculateTime();
          }
      }
      

接口

( interface )

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

  • 语法

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

    • JDK 7.0 前,接口里的所有方法都没有方法体,即都是抽象方法

    • JDK 8.0 后,接口可以有静态方法、默认方法,也就是说接口中可以有方法的具体实现

    • 接口中,抽象方法可以省略 abstract 关键字

      public void f1();  // 即为抽象方法
      
    • JDK 8.0 后,可以有默认实现方法,但需要使用 default 关键字修饰

      default public void f2() {
          System.out.println("普通的默认方法的实现...");
      }
      
    • JDK 8.0 后的静态方法也可以实现

      public static void f3() {
          System.out.println("静态方法的具体实现")
      }
      
  • 应用场景

    • 可以通过接口来统一所需实现的方法名,否则五花八门
  • 注意细节

    • 接口不能被实例化

      • 接口本身就是希望别的类来实现它,然后再由实现了此接口的类进行实例化
    • 接口中所有的方法都是 public 方法,接口中抽象方法可以不用 abstract 修饰

      • 没写修饰符时的默认和先前的规则不同,默认是 public 了

      • 注意,没有方法体就不写 {}

    • 一个普通类实现接口,就必须将该接口的所有方法都实现

      • 可以 alt + enter 快捷键导入所有方法待实现
    • 抽象类实现接口,可以不用实现接口的方法

      interface AI {
          void f1(); // 默认的public类型
      }
      class D implements AI {
          // 不写方法会报错
          public void f1(){
              // 要写这个方法并public修饰
          }
      }
      abstract class D implements AI {
          // 就不会报错,抽象类就可以不去实现了
      }
      
    • 一个类同时可以实现多个接口

      interface A {}
      interface B {}
      class C implements A,B {}
      
    • 接口中的属性只能是 final 的,而且是 public static final 修饰符

      • 注意 final 了的属性是不能再修改,而无关乎接口,implements 相当于是实现了其接口,自然可以使用其属性 ( 只有 final 了的类不能继承 )
      interface A {
      //    接口中:
          int a = 1;
      //    实际上是:
      //    public static final int a = 1; 
      //    必须初始化
      }
      // main里因static可以直接调用,且因final不可以再修改
      System.out.println(A.a);
      // A.a = 10;  报错
      
    • 接口中属性的访问形式:接口名.属性名

    • 一个接口不能继承其他的类,但是可以继承多个别的接口

      interface A {}
      interface B {}
      interface C extends A,B{}
      
    • 接口的修饰符只能是 public 和默认,这点和类的修饰符是一样的

  • 小练习

    • 判断语法对否

      interface A {
          int a = 10;
      }
      class B implements A {}
      
      // main里:
      B b = new B();
      System.out.println(b.a); // 10
      System.out.println(A.a); // 10
      System.out.println(B.a); // 10
      
  • 接口 VS 继承

    • 动物小比喻

      • 四个动物,小猴子、老猴子、鸟、鱼

      • 小猴子和老猴子同种类,所以是小猴子继承老猴子

      • 但小猴子若是想飞,就得实现接口 implements 鸟,想游泳就得实现接口 implements 鱼

      • 继承是只能有一个,就像血脉只能有一条,能先天自动拥有,但接口就相当于拜师,可以拜多个,且学完要有自行领悟的结果 ( 血脉补充 )

        class LittleMonkey extends OldMonkey implements Fish,Bird { ... }
        
    • 接口和继承解决的问题不同

      • 继承的价值主要在于解决代码的复用性和可维护性
      • 接口的价值主要在于设计,设计好各种规范 ( 方法 ),让其它类去实现这些方法,更灵活
    • 接口比继承更加灵活

      • 接口比继承更加灵活,继承是满足 is - a 的是的关系,而接口只需满足 like - a 的像的关系
    • 接口在一定程度上实现代码解耦 ( 即:接口规范性 + 动态绑定机制 )

    • 接口、继承结合的练习 ( 切勿混淆 )

      interface A {
          int x = 0;
      //  相当于public static final int x = 0;
      }
      class B {
          int x = 1;
      }
      class C extends B implements A {
          public void px(){
      //        System.out.println(x); // ambiguous 不明确x
              System.out.println("接口中的x=" + A.x);
              System.out.println("父类中的x=" + super.x);
          }
      }
      
  • 接口的多态特性

    • 多态参数

      interface UsbInterface { ... }
      class Phone implements UsbInterface { ... }
      class Camera implements UsbInterface { ... }
      
      public class Computer {
      // 形参是接口类型,但凡是接收实现了UsbInterface接口的类的对象实例都可以作为此处的形参
          public void work(UsbInterface usbinterface) { }
      }
      
      // main内
      Phone phone = new Phone();
      Camera camera = new Camera();
      Computer computer = new Computer();
      // 多态体现1:既可以接收手机,也可以接收相机
      computer.work(phone);
      computer.work(camera);
      
      // main内,多态体现2:
      // 接口类型的变量 usb1 可以指向实现了UsbInterface接口的对象实例(参考父类引用指向子类对象)
      UsbInterface usb1 = new Phone();
      usb1 = new Camera();
      
    • 多态数组

      // main内
      Usb[] usbs = new Usb[2];
      usbs[0] = new Phone();
      usbs[1] = new Camera();
      
      for(int i = 0; i < usbs.length; i++){
          usbs[i].work();
          if(usbs[i] instanceof Phone){
              ((Phone) usbs[i]).call();
          }
      }
      
    • 接口存在多态传递现象

      • 就相当于你拜了师,不仅要实现师傅的愿望,还要完成宗门的愿景
      interface IH {}
      interface IG extends IH {}
      class Teacher implements IG {}
      
      // main内
      IG ig = new Teacher();
      // 如果IG继承了IH接口,而Teacher实现了IG接口
      // 那么,实际上就相当于Teacher类也实现了IH接口
      IH ih = new Teacher();
      

内部类

  • 一个类的内部又完整的嵌套了另一个类结果,被嵌套的类被称为内部类 ( inner class ),嵌套其他类的类被称为外部类 ( outer class ),内部类的最大特点就是可以直接访问私有属性,并且可以体现类与类之间的包含关系

    • 语法:

      class Outer{ // 外部类
          class Inner{
              // 内部类
          }
      }
      class Other{
          // 外部其他类
      }
      
  • 内部类分类

    • 定义在外部类局部位置上 ( 如方法中 )
      • 局部内部类 ( 有类名 )
      • 匿名内部类 ( 没有类名,重点 )
    • 定义在外部类的成员位置上
      • 成员内部类 ( 没有 static 修饰 )
      • 静态内部类 ( 使用 static 修饰 )

局部内部类

  • 局部内部类是定义在外部类的局部位置,通常在方法中 ( 代码块里也可以 )

    • 即:作用域 ——> 仅仅在定义了它的方法或代码块中
  • 局部内部类可以直接访问外部类的所有成员,包含私有的

  • 外部类在方法中,需要创建局部内部类的对象,就可调用其方法

  • 不能添加访问修饰符,因为它的地位就是一个局部变量,局部变量是不能使用修饰符的,但是可以使用 final 修饰

    • 因为一个局部内部类是可以被同一个外部类的另一个内部类所继承的
  • 外部其他类不能访问局部内部类

  • 如果外部类和局部内部类的成员重名时,默认遵循就近原则,如果想访问外部类的成员,则可以使用 ( 外部类名.this.成员 ) 去访问

    class OuterA{
        private int n1 = 100;
        public void o1(){
            class InnerA{
                private int n1 = 3;
                public void f1(){
                    System.out.println("n1=" + n1);
    //                输出为3
    
                    System.out.println("外部类的n1=" + OuterA.this.n1);
    //                输出为100,OuterA.this 本质是外部类的对象(定义了o1方法的对象)
                }
            }
        }
    }
    

匿名内部类

(anonymous )

  • 匿名内部类 ( 重点!!!! )

    • 本质还是类
    • 仍属于内部类,定义于外部类的局部位置
    • 该类没有名字 ( 不能见,且是系统取的名字 )
    • 同时还是一个对象
  • 基本语法

    new 类或接口(参数列表){
        类体
    };
    
  • 基于接口的匿名内部类

    • 在一个 class 里想要使用 IA 接口并使用其方法

    • 传统方式是写一个类实现该接口,再于 class 里创建此类对象来实现要求

    • 可若是只想使用此方法那么一次,这样做不仅麻烦还为此创建了对象 ( 浪费 )

      // main内:
      OuterB outerB = new OuterB();
              outerB.method();
      
      class OuterB{
          private int n1 = 100;
          public void method(){
              IA tiger = new Tiger();
              tiger.cry();
          }
      }
      
      interface IA {
          public void cry();
      }
      class Tiger implements IA {
          @Override
          public void cry() {
              System.out.println("老虎wa");
          }
      }
      
    • 使用匿名内部类

      class OuterB{
          private int n1 = 100;
          public void method(){
              /*
              此处编译类型是 IA,运行类型是:匿名内部类
              看底层
                  class XXX implements IA { // XXX: OuterB$1(系统分配的,用一次就没了)
                    @Override
                    public void cry() {
                        System.out.println("老虎叫唤");
                    }
                }
               */
      //        JDK底层在创建匿名内部类OuterB$1,立即马上就创建了 OuterB$1 实例,
      //        并且把地址返回给了 tiger
      //        ( 看到 new IA(){} 就辨别出了这是一个匿名类,自动转换成实现接口的形式并创建实例,并返回了地址 )
              IA tiger = new IA() {
                  @Override
                  public void cry() {
                      System.out.println("老虎叫唤");
                  }
              };
              System.out.println("tiger用getClass方法查看运行类型:" + tiger.getClass());
              tiger.cry();
          }
      }
      
      interface IA {
          public void cry();
      }
      
    • 匿名内部类 OuterB$1 使用一次就不能再使用了 ( new OuterB$1是不存在的 ),但借其创建的对象 tiger 还在

  • 基于类的匿名内部类

    class OuterB{
        private int n1 = 100;
        /* 
        public void method(){
            IA tiger = new IA() {
                @Override
                public void cry() {
                    System.out.println("老虎叫唤");
                }
            };
            System.out.println("tiger用getClass方法查看运行类型:" + tiger.getClass());
            这里不注释的话这里就是$1,下面的就是$2
        */ 
    
    //        就是创建了一个Father对象,编译运行类型都是Father
    //        Father father = new Father("tanker");
    
    //        加上大括号就变成一个匿名内部类了
            /*
            编译类型:Father  运行类型:OuterB$2
                class OuterB$2 extends Father{
                    但如果这里什么都没有写,就表示下面的Father father = new Father("tanker"){}里也是什么都没有写
                    @Override
                    public void test() {
                        System.out.println("匿名内部类重写了test方法");
                    }
                }
             */
    
            Father father = new Father("tanker"){
                @Override
                public void test() {
                    System.out.println("匿名内部类重写了test方法");
                }
            };
    //        运行类型变成了OuterB$2 (自行递增制的,如果在此之前没有任何匿名内部类,即把上面的接口匿名内部类去掉了,那这里就是OuterB$1)
            System.out.println("father用getClass方法查看运行类型:" + father.getClass());
            father.test();
        }
    }
    
    class Father {
        public Father(String name) {
        }
        public void test(){
        }
    }
    
    • 但如果是 abstract class Father { abstract void test(); } 抽象类的话, Father father = new Father("tanker")
    • 一般此匿名就是为了重写方法或者再添方法等
  • 匿名内部类的方法调用

    • 匿名内部类既是一个类的定义,同时它本身也是一个对象,所以从语法上来看,它既有定义类的特征,也有创建对象的特征

    • 第一种调用方法

      public class test {
          public static void main(String[] args){
              OuterC outerC = new OuterC();
              outerC.f1();
          }
      }
      
      class OuterC{
          private int n1 = 100;
          public void f1(){
              Person p = new Person(){
                  @Override
                  public void hello() {
                      super.hello();
                      System.out.println("匿名内部类重写hello方法");
                  }
              };
              p.hello(); // 动态绑定
          }
      }
      
      class Person {
          public void hello(){
              System.out.println("被super调用了父类的hello");
          }
      }
      
    • 第二种调用方法

      // 使用方式一
      class OuterC{
          private int n1 = 100;
          public void f1(){
              new Person(){
                  @Override
                  public void hello() {
                      super.hello();
                      System.out.println("匿名内部类重写hello方法的F2");
                  }
              }.hello(); // 认定了只会调用这么一次,所以都不用写接收的对象名p
          }
      }
      
      // 使用方式二
      class OuterC{
          private int n1 = 100;
          public void f1(){
              new Person(){
                  @Override
                  public void hello() {
                      super.hello();
                      System.out.println("匿名内部类重写hello方法的F2");
                  }
      
                  @Override
                  public void hi(String name) {
                      super.hi(name);
                  }
              }.hi("其名为duang"); // 认定了只会调用这么一次,所以都不用写接收的对象名p
          }
      }
      
      class Person {
          public void hello(){
              System.out.println("被super调用了父类的hello");
          }
          public void hi(String name){
              System.out.println("这里是父类的有参数的方法");
          }
      }
      
  • 可以直接访问外部类的所有成员,包含私有的

    class OuterC{
        private int n1 = 100;
        public void f1(){
            new Person(){
                @Override
                public void hello() {
                    System.out.println("直接访问外部类的成员,例如私有的n1=" + n1);
                }
            }.hello(); // 认定了只会调用这么一次,所以都不用写接收的对象名p
        }
    }
    
  • 不能添加访问修饰符,因为它的地位就是一个局部变量

  • 作用域也就仅仅在定义它的方法或代码块中

  • 匿名内部类对外部类成员是直接访问

  • 外部其他类不能访问匿名内部类,因为匿名内部类地位是一个局部变量 ( 在你想找这个类时,它就已自我销毁了 )

  • 如果外部类和内部类的成员重名时内部类访问的话,就默认遵循就近原则,如果想访问外部类的成员,则可以使用 ( 外部类名.this.成员 ) 去访问

    ( 很多使用原则同局部内部类 )

  • 匿名内部类的最佳实践

    • 当作实参直接传递,简洁高效

      public class test {
          public static void main(String[] args){
              f1(new IL() {
                  @Override
                  public void show() {
                      System.out.println("main这里直接传的是一个匿名内部类(适合那种只用一次的方法),其整体可以看成一个对象");
      //                要是用传统的方法,就得新建一个类去实现接口方法,然后f1(new 新建的类名) ——> 硬编码
                  }
              });
          }
          public static void f1(IL il){
      //        此处的show方法是什么,要表现为什么样子看main里f1(new ...)
              il.show();
          }
      }
      
      interface IL{
          void show();
      }
      
    • 已知有一个铃声接口 Bell,里面有个 ring 方法;有一个手机类 Cellphone,具有闹钟功能 alarmclock,参数是 Bell 类型

      • 测试手机类的闹钟功能,通过匿名内部类 ( 对象 ) 作为参数,打印:嘿,起床了兄嘚
      • 再传入另一个匿名内部类 ( 对象 ),打印:上早八了喽
      public class test {
          public static void main(String[] args){
              CellPhone cellPhone = new CellPhone();
      
      //        传递的是实现了 Bell 接口的匿名内部类 XXX$1
      //        重写了 ring
      //        Bell bell = new Bell(){
      //              ( Bell bell 就是方法形参列表的部分,虽然编译类型定下来了,但是运行类型是变化的:匿名内部类)
      //              重写内容
      //        }
              cellPhone.alarmClock(new Bell() {
                  @Override
                  public void ring() {
                      System.out.println("嘿,起床了兄嘚");
                  }
              });
              cellPhone.alarmClock(new Bell() {
                  @Override
                  public void ring() {
                      System.out.println("上早八了喽");
                  }
              });
          }
      }
      
      interface Bell{ // 接口
          void ring();
      }
      class CellPhone{
          public void alarmClock(Bell bell){ // 形参是Bell接口类型
              bell.ring();
          }
      }
      

成员内部类

  • 是定义在外部类的成员位置上,并且无 static 修饰

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

    // main中:
    OuterA outerA = new OuterA();
    outerA.f2();
    
    class OuterA {
        private int n1 = 10;
        class InnerB {
            public void f1() {
                System.out.println("成员内部类访问外部类的属性n1=" + n1);
            }
        }
    
        public void f2(){
            System.out.println("外部类的方法内调用成员内部类的属性方法");
            InnerB b = new InnerB();
            b.f1();
        }
    }
    
  • 可以添加任意访问修饰符 ( public、protected、默认、private ),因为其地位就是一个成员

  • 作用域和外部类的其他成员一样,为整个类体

    • 如前面代码中在外部类的成员方法中创建成员内部类对象,再调用方法,InnerB 的使用范围在整个 OuterA 中
  • 成员内部类访问外部类是直接访问

  • 外部类访问内部类是先创建对象,再访问

  • 外部其他类访问成员内部类

    • F1,平常用写法 new B(),但此处 B 是外部类 OuterA 的一个内部类

      new B() 相当于外部类的一个属性,所以要用实例 outerA.new B()

      // 外部其他类中,如main里
      OuterA outerA = new OuterA();
      
      OuterA.B b = outerA.new B();
      b.f1();
      
    • F2,在外部类 ( 想访问的内部类 B 所在的外部类 ) 中编写一个方法,可以返回 B 对象 ( 返回一个 new B )

      // 外部其他类中,如main里
      OuterA outerA = new OuterA();
      
      OuterA.B BInstance = outerA.getBInstance();
      BInstance.f1();
      
      // 外部类 + 内部类
      class OuterA {
          private int n1 = 10;
          class B {
              public void f1() {
                  System.out.println("成员内部类访问外部类的属性n1=" + n1);
              }
          }
      //   返回一个对象(外部类的一个方法)
          public B getBInstance(){
              return new B();
          }
      }
      
    • F3,省去 OuterA outerA = new OuterA(); 用两个 new ( 实际上就是 F2 的简写 )

      OuterA.B BInstance = new OuterA().getBInstance();
      
  • 如果外部类和内部类的成员重名时,内部类访问的话,默认遵循就近原则,如果想访问外部类的成员,则可以使用 ( 外部类名.this.成员 ) 去访问

    // 成员内部类里
    System.out.println("成员内部类自己的属性=" + n1 + ",此内部类的外部类的重名属性=" + OuterA.this.n1);
    

静态内部类

  • 同成员内部类定义在外部类的成员位置,但是要有 static 来修饰

  • 可以直接访问外部类的所有静态成员,包含私有的,但不能直接访问非静态成员 ( 静态访静态 )

    // 外部类中:
    private static String name = "dangduang";
    
    static class InnerC {
        public void hi(){
            System.out.println(name);
        }
    }
    
  • 可以添加任意访问修饰符 ( public、protected、默认、private ),因为其地位就是一个成员

  • 作用域和外部类的其他成员一样,为整个类体

  • 成员内部类访问外部类是直接访问

  • 外部类访问内部类是先创建对象,再访问

  • 外部其他类访问成员内部类

    • F1,InnerC 是静态的,可以直接 外部类名.静态成员

      但注意,如果静态内部类是 private 修饰的话是取不到的,要满足访问权限

      // 外部其他类,如 main 中
      OuterA.InnerC innerC = new OuterA.InnerC();
      innerC.hi();
      
    • F2,编写一个方法,可以返回静态内部类的对象实例

      // 外部其他类,如 main 中
      OuterA outerA = new OuterA();
      OuterA.InnerC innerC1 = outerA.getInnerCInstance();
      innerC1.hi();
      
      // 外部类 + 内部类
      class OuterA {
          private static String name = "dangduang";
          static class InnerC {
              public void hi(){
                  System.out.println(name);
              }
          }
      //   返回一个对象(外部类的一个方法)
          public InnerC getInnerCInstance(){
              return new InnerC();
          }
      }
      
    • F3,static 静态化返回实例的方法,就无需如 F2 那样再 new 创建了

      // 外部其他类,如 main 中
      OuterA.InnerC innerC2 = OuterA.getInnerCInstance_();
      innerC2.hi();
      
      // 外部类 + 内部类
      class OuterA {
          private static String name = "dangduang";
          static class InnerC {
              public void hi(){
                  System.out.println(name);
              }
          }
          
      //    返回一个对象(外部类的一个方法)
      //    因为返回的 InnerC 是静态的,这个 get 方法就也可以是静态 static 的
          public static InnerC getInnerCInstance_(){
              return new InnerC();
          }
      }
      
  • 如果外部类和静态内部类的成员重名时,静态内部类访问的话,默认遵循就近原则,如果想访问外部类的成员,则可以使用 ( 外部类名.成员 ) 去访问

    // 外部类 OuterA 里
    private static String name = "..";
    // 静态内部类里
    System.out.println("成员内部类自己的属性=" + name + ",此内部类的外部类的重名属性=" + OuterA.name);