Java基础(十一):抽象类、接口、内部类


一、抽象类

由来

  • 随着继承层次中一个个新子类的定义,类变得越来越具体,而父类则更一般,更通用
  • 类的设计应该保证父类和子类能够共享特征
  • 有时将一个父类设计得非常抽象,以至于它没有具体的实例,这样的类叫做抽象类

1、语法格式

  • 抽象类被abstract修饰的类
  • 抽象方法被abstract修饰没有方法体的方法

抽象类的语法格式

[权限修饰符] abstract class 类名{
    
}
[权限修饰符] abstract class 类名 extends 父类{
    
}

抽象方法的语法格式

[其他修饰符] abstract 返回值类型 方法名([形参列表]);

举例:

public abstract class Animal {
    public abstract void eat();
}
public class Cat extends Animal {
    public void eat (){
      	System.out.println("小猫吃鱼和猫粮"); 
    }
}
public class CatTest {
 	 public static void main(String[] args) {
        // 创建子类对象
        Cat c = new Cat(); 
       
        // 调用eat方法
        c.eat();
  	}
}

2、abstract修饰类

  • 抽象类不能创建对象,如果创建,编译无法通过而报错。只能创建其非抽象子类的对象
    • 理解:假设创建了抽象类的对象,调用抽象的方法,而抽象方法没有具体的方法体,没有意义
    • 抽象类是用来被继承的,抽象类的子类必须重写父类的抽象方法,并提供方法体
    • 若没有重写全部的抽象方法,仍为抽象类
  • 抽象类中,也有构造方法,是供子类创建对象时,初始化父类成员变量使用的
    • 理解:子类的构造方法中,有默认的super()或手动的super(实参列表),需要访问父类构造方法
  • 抽象类中,不一定包含抽象方法,但是有抽象方法的类必定是抽象类
    • 理解:未包含抽象方法的抽象类,目的就是不想让调用者创建该类对象,通常用于某些特殊的类结构设计
  • 抽象类的子类,必须重写抽象父类中所有的抽象方法,否则,编译无法通过而报错。除非该子类也是抽象类
    • 理解:假设不重写所有抽象方法,则类中可能包含抽象方法。那么创建对象后,调用抽象的方法,没有意义

3、abstract修饰方法

  • 抽象方法只有方法的声明,没有方法体
  • 抽象方法其功能是确定的(通过方法的声明即可确定),只是不知道如何具体实现(体现为没有方法体)
  • 子类必须重写父类中的所有的抽象方法之后,方可实例化。否则,此子类仍然是一个抽象类

注意事项

  • 不能用abstract修饰变量、代码块、构造器
  • 不能用abstract修饰私有方法、静态方法、final的方法、final的类
    • 私有方法不能重写
    • 避免使用类调用静态方法(没实现没意义)
    • final方法不能被重写
    • final类不能有子类

二、接口

1、定义格式

  • 接口的定义,它与定义类方式相似,但是使用 interface 关键字
  • 它也会被编译成.class文件,但一定要明确它并不是类,而是另外一种引用数据类型
    • 引用数据类型:数组,类,枚举,接口,注解
[修饰符] interface 接口名{
    //接口的成员列表:
    // 公共的静态常量
    // 公共的抽象方法
    
    // 公共的默认方法(JDK1.8以上)
    // 公共的静态方法(JDK1.8以上)
    // 私有方法(JDK1.9以上)
}

举例:

public interface USB3{
    //静态常量
    long MAX_SPEED = 500*1024*1024;//500MB/s

    //抽象方法
    void in();
    void out();

    //默认方法
    default void start(){
        System.out.println("开始");
    }
    default void stop(){
        System.out.println("结束");
    }

    //静态方法
    static void show(){
        System.out.println("USB 3.0可以同步全速地进行读写操作");
    }
}

2、接口的说明

  • 在JDK8.0 之前,接口中只允许出现:
    • 公共的静态的常量:其中public static final可以省略
    • 公共的抽象的方法:其中public abstract可以省略
    • 理解:接口是从多个相似类中抽象出来的规范,不需要提供具体实现
  • 在JDK8.0 时,接口中允许声明默认方法静态方法
    • 公共的默认的方法:其中public 可以省略,建议保留,但是default不能省略
    • 公共的静态的方法:其中public 可以省略,建议保留,但是static不能省略
  • 在JDK9.0 时,接口又增加了:
    • 私有方法

除此之外,接口中没有构造器,没有初始化块,因为接口中没有成员变量需要动态初始化

3、接口的使用规则

3.1、类实现接口

  • 接口不能创建对象,但是可以被类实现(implements ,类似于被继承)
  • 类与接口的关系为实现关系,即类实现接口,该类可以称为接口的实现类
  • 实现的动作类似继承,格式相仿,只是关键字不同,实现使用 implements关键字
【修饰符】 class 实现类  implements 接口{
	// 重写接口中抽象方法【必须】,当然如果实现类是抽象类,那么可以不重写
  	// 重写接口中默认方法【可选】
}

【修饰符】 class 实现类 extends 父类 implements 接口{
    // 重写接口中抽象方法【必须】,当然如果实现类是抽象类,那么可以不重写
  	// 重写接口中默认方法【可选】
}

在这里插入图片描述

注意

  • 如果接口的实现类是非抽象类,那么必须重写接口中所有抽象方法
  • 默认方法可以选择保留,也可以重写
    • 重写时,default单词就不要再写了,它只用于在接口中表示默认方法,到类中就没有默认方法的概念了
  • 接口中的静态方法不能被继承也不能被重写

3.2、接口的多实现

  • 之前学过,在继承体系中,一个类只能继承一个父类
  • 而对于接口而言,一个类是可以实现多个接口的,这叫做接口的多实现
  • 并且,一个类能继承一个父类,同时实现多个接口
【修饰符】 class 实现类  implements 接口1,接口2,接口3。。。{
	// 重写接口中所有抽象方法【必须】,当然如果实现类是抽象类,那么可以不重写
  	// 重写接口中默认方法【可选】
}

【修饰符】 class 实现类 extends 父类 implements 接口1,接口2,接口3。。。{
    // 重写接口中所有抽象方法【必须】,当然如果实现类是抽象类,那么可以不重写
  	// 重写接口中默认方法【可选】
}
  • 接口中,有多个抽象方法时,实现类必须重写所有抽象方法。如果抽象方法有重名的,只需要重写一次

3.3、接口的多继承

  • 一个接口能继承另一个或者多个接口
  • 接口的继承也使用 extends 关键字,子接口继承父接口的方法

定义父接口:

public interface Chargeable {
    void charge();
    void in();
    void out();
}

定义子接口:

public interface UsbC extends Chargeable,USB3 {
    void reverse();
}

定义子接口的实现类:

public class TypeCConverter implements UsbC {
    @Override
    public void reverse() {
        System.out.println("正反面都支持");
    }

    @Override
    public void charge() {
        System.out.println("可充电");
    }

    @Override
    public void in() {
        System.out.println("接收数据");
    }

    @Override
    public void out() {
        System.out.println("输出数据");
    }
}
  • 所有父接口的抽象方法都有重写
  • 方法签名相同的抽象方法只需要实现一次

3.4、接口与实现类对象构成多态引用

  • 实现类实现接口,类似于子类继承父类
  • 因此,接口类型的变量与实现类的对象之间,也可以构成多态引用
  • 通过接口类型的变量调用方法,最终执行的是你new的实现类对象实现的方法体

接口的不同实现类:

public class Mouse implements USB3 {
    @Override
    public void out() {
        System.out.println("发送脉冲信号");
    }

    @Override
    public void in() {
        System.out.println("不接收信号");
    }
}
public class KeyBoard implements USB3{
    @Override
    public void in() {
        System.out.println("不接收信号");
    }

    @Override
    public void out() {
        System.out.println("发送按键信号");
    }
}

测试类:

public class TestComputer {
    public static void main(String[] args) {
        Computer computer = new Computer();
        USB3 usb = new Mouse();
        computer.setUsb(usb);
        usb.start();
        usb.out();
        usb.in();
        usb.stop();
        System.out.println("--------------------------");

        usb = new KeyBoard();
        computer.setUsb(usb);
        usb.start();
        usb.out();
        usb.in();
        usb.stop();
    }
}

3.5、使用接口的静态成员

  • 接口不能直接创建对象,但是可以通过接口名直接调用接口的静态方法静态常量
public class TestUSB3 {
    public static void main(String[] args) {
        //通过“接口名.”调用接口的静态方法 (JDK8.0才能开始使用)
        USB3.show();
        //通过“接口名.”直接使用接口的静态常量
        System.out.println(USB3.MAX_SPEED);
    }
}

3.5、使用接口的静态方法

  • 对于接口的静态方法,直接使用“接口名.”进行调用即可
    • 也只能使用“接口名."进行调用,不能通过实现类的对象进行调用
  • 对于接口的抽象方法、默认方法,只能通过实现类对象才可以调用
    • 接口不能直接创建对象,只能创建实现类的对象
public class TestMobileHDD {
    public static void main(String[] args) {
        //创建实现类对象
        MobileHDD b = new MobileHDD();

        //通过实现类对象调用重写的抽象方法,以及接口的默认方法,如果实现类重写了就执行重写的默认方法,如果没有重写,就执行接口中的默认方法
        b.start();
        b.in();
        b.stop();

        //通过接口名调用接口的静态方法
//        MobileHDD.show();
//        b.show();
        Usb3.show();
    }
}

4、JDK8中相关冲突问题

4.1、默认方法冲突问题

类优先原则

  • 当一个类,既继承一个父类,又实现若干个接口时
  • 父类中的成员方法与接口中的抽象方法重名
  • 子类就近选择执行父类的成员方法

定义接口:

public interface Friend {
    default void date(){//约会
        System.out.println("吃喝玩乐");
    }
}

定义父类:

public class Father {
    public void date(){//约会
        System.out.println("爸爸约吃饭");
    }
}

定义子类:

public class Son extends Father implements Friend {
    @Override
    public void date() {
        //(1)不重写默认保留父类的
        //(2)调用父类被重写的
//        super.date();
        //(3)保留父接口的
//        Friend.super.date();
        //(4)完全重写
        System.out.println("跟康师傅学Java");
    }
}

定义测试类:

public class TestSon {
    public static void main(String[] args) {
        Son s = new Son();
        s.date();
    }
}
  • 调用父类方法:super.方法名()
  • 调用父接口默认方法:类名.super.方法名()

接口冲突(左右为难)

  • 当一个类同时实现了多个父接口,而多个父接口中包含方法签名相同的默认方法时,怎么办呢?

声明接口:

public interface BoyFriend {
    default void date(){//约会
        System.out.println("神秘约会");
    }
}

选择保留其中一个,通过“接口名.super.方法名"的方法选择保留哪个接口的默认方法

public class Girl implements Friend,BoyFriend{

    @Override
    public void date() {
        //(1)保留其中一个父接口的
//        Friend.super.date();
//        BoyFriend.super.date();
        //(2)完全重写
        System.out.println("跟康师傅学Java");
    }
}
  • 当一个子接口同时继承了多个接口,而多个父接口中包含方法签名相同的默认方法时,怎么办呢?

另一个父接口:

public interface USB2 {
    //静态常量
    long MAX_SPEED = 60*1024*1024;//60MB/s

    //抽象方法
    void in();
    void out();

    //默认方法
    public default void start(){
        System.out.println("开始");
    }
    public default void stop(){
        System.out.println("结束");
    }

    //静态方法
    public static void show(){
        System.out.println("USB 2.0可以高速地进行读写操作");
    }
}

子接口:

public interface USB extends USB2,USB3 {
    @Override
    default void start() {
        System.out.println("Usb.start");
    }

    @Override
    default void stop() {
        System.out.println("Usb.stop");
    }
}
  • 子接口重写默认方法时,default关键字可以保留
  • 子类重写默认方法时,default关键字不可以保留

4.2、常量冲突问题

  • 当子类继承父类又实现父接口,而父类中存在与父接口常量同名的成员变量,并且该成员变量名在子类中仍然可见
  • 当子类同时实现多个接口,而多个接口存在相同同名常量

此时在子类中想要引用父类或父接口的同名的常量或成员变量时,就会有冲突问题

父类和父接口:

public class SuperClass {
    int x = 1;
}
public interface SuperInterface {
    int x = 2;
    int y = 2;
}
public interface MotherInterface {
    int x = 3;
}

子类:

public class SubClass extends SuperClass implements SuperInterface,MotherInterface {
    public void method(){
//        System.out.println("x = " + x);//模糊不清
        System.out.println("super.x = " + super.x);
        System.out.println("SuperInterface.x = " + SuperInterface.x);
        System.out.println("MotherInterface.x = " + MotherInterface.x);
        System.out.println("y = " + y);//没有重名问题,可以直接访问
    }
}

5、接口与抽象类之间的对比

在这里插入图片描述

6、面试题

1、为什么接口中只能声明公共的静态的常量?

  • 因为接口是标准规范,那么在规范中需要声明一些底线边界值
  • 当实现者在实现这些规范时,不能去随意修改和触碰这些底线,否则就有“危险”
  • 例如
    • USB1.0规范中规定最大传输速率是1.5Mbps,最大输出电流是5V/500mA
    • USB3.0规范中规定最大传输速率是5Gbps(500MB/s),最大输出电流是5V/900mA

2、为什么JDK8.0 之后允许接口定义静态方法和默认方法呢?

  • 静态方法
    • 因为之前的标准类库设计中,有很多Collection/Colletions或者Path/Paths这样成对的接口和类
    • 后面的类中都是静态方法,而这些静态方法都是为前面的接口服务的
    • 那么这样设计一对API,不如把静态方法直接定义到接口中使用和维护更方便
  • 默认方法
    • 我们要在已有的老版接口中提供新方法时
    • 如果添加抽象方法,就会涉及到原来使用这些接口的类就会有问题
    • 那么为了保持与旧版本代码的兼容性,只能允许在接口中定义默认方法实现
    • 比如:Java8中对Collection、List、Comparator等接口提供了丰富的默认方法

3、为什么JDK1.9要允许接口定义私有方法呢?

  • 因为有了默认方法和静态方法这样具有具体实现的方法
  • 那么就可能出现多个方法由共同的代码可以抽取
  • 而这些共同的代码抽取出来的方法又只希望在接口内部使用,所以就增加了私有方法

三、内部类

1、概述

什么是内部类

  • 将一个类A定义在另一个类B里面
    • 里面的那个类A就称为内部类(InnerClass)
    • 类B则称为外部类(OuterClass)

为什么要声明内部类呢?

  • 具体来说,当一个事物A的内部,还有一个部分需要一个完整的结构B进行描述
  • 而这个内部的完整的结构B又只为外部事物A提供服务,不在其他地方单独使用
  • 那么整个内部的完整结构B最好使用内部类

内部类的分类

在这里插入图片描述

2、成员内部类

概述

  • 如果成员内部类中不使用外部类的非静态成员,那么通常将内部类声明为静态内部类,否则声明为非静态内部类

语法格式:

[修饰符] class 外部类{
    [其他修饰符] [static] class 内部类{
    }
}

成员内部类的使用特征,概括来讲有如下两种角色:

  • 成员内部类作为类的成员的角色
    • 和外部类不同,Inner class还可以声明为private或protected(四种都可以)
    • 可以调用外部类的结构。(注意:在静态内部类中不能使用外部类的非静态成员)
    • Inner class 可以声明为static的,但此时就不能再使用外层类的非static的成员变量
  • 成员内部类作为类的角色
    • 可以在内部定义属性、方法、构造器等结构
    • 可以继承自己的想要继承的父类,实现自己想要实现的父接口们,和外部类的父类和父接口无关
    • 可以声明为abstract类 ,因此可以被其它的内部类继承
    • 可以声明为final的,表示不能被继承
    • 编译以后生成OuterClass$InnerClass.class字节码文件(也适用于局部内部类)

注意点:

  • 外部类访问成员内部类的成员,需要“内部类.成员”或“内部类对象.成员”的方式
  • 成员内部类可以直接使用外部类的所有成员,包括私有的数据
  • 当想要在外部类的静态成员部分使用内部类时,可以考虑内部类声明为静态的

创建成员内部类对象

  • 实例化静态内部类
外部类名.静态内部类名 变量 = 外部类名.静态内部类名();
变量.非静态方法();
  • 实例化非静态内部类
外部类名 变量1 = new 外部类();
外部类名.非静态内部类名 变量2 = 变量1.new 非静态内部类名();
变量2.非静态方法();

举例:

public class TestMemberInnerClass {
    public static void main(String[] args) {
        //创建静态内部类实例,并调用方法
        Outer.StaticInner inner = new Outer.StaticInner();
        inner.inFun();
        //调用静态内部类静态方法
        Outer.StaticInner.inMethod();

        System.out.println("*****************************");
        
        //创建非静态内部类实例(方式1),并调用方法
        Outer outer = new Outer();
        Outer.NoStaticInner inner1 = outer.new NoStaticInner();
        inner1.inFun();

        //创建非静态内部类实例(方式2)
        Outer.NoStaticInner inner2 = outer.getNoStaticInner();
        inner1.inFun();
    }
}
class Outer{
    private static String a = "外部类的静态a";
    private static String b  = "外部类的静态b";
    private String c = "外部类对象的非静态c";
    private String d = "外部类对象的非静态d";

    static class StaticInner{
        private static String a ="静态内部类的静态a";
        private String c = "静态内部类对象的非静态c";
        public static void inMethod(){
            System.out.println("Inner.a = " + a);
            System.out.println("Outer.a = " + Outer.a);
            System.out.println("b = " + b);
        }
        public void inFun(){
            System.out.println("Inner.inFun");
            System.out.println("Outer.a = " + Outer.a);
            System.out.println("Inner.a = " + a);
            System.out.println("b = " + b);
            System.out.println("c = " + c);
//            System.out.println("d = " + d);//不能访问外部类的非静态成员
        }
    }

    class NoStaticInner{
        private String a = "非静态内部类对象的非静态a";
        private String c = "非静态内部类对象的非静态c";

        public void inFun(){
            System.out.println("NoStaticInner.inFun");
            System.out.println("Outer.a = " + Outer.a);
            System.out.println("a = " + a);
            System.out.println("b = " + b);
            System.out.println("Outer.c = " + Outer.this.c);
            System.out.println("c = " + c);
            System.out.println("d = " + d);
        }
    }


    public NoStaticInner getNoStaticInner(){
        return new NoStaticInner();
    }
}

3、局部内部类

3.1、非匿名局部内部类

语法格式:

[修饰符] class 外部类{
    [修饰符] 返回值类型  方法名(形参列表){
            [final/abstract] class 内部类{
    	}
    }    
}
  • 编译后有自己的独立的字节码文件,只不过在内部类名前面冠以外部类名、$符号、编号
    • 这里有编号是因为同一个外部类中,不同的方法中存在相同名称的局部内部类
  • 和成员内部类不同的是,它前面不能有权限修饰符等
  • 局部内部类如同局部变量一样,有作用域
  • 局部内部类中是否能访问外部类的非静态的成员,取决于所在的方法

举例:

public class TestLocalInner {
    public static void main(String[] args) {
        Outer.outMethod();
        System.out.println("-------------------");

        Outer out = new Outer();
        out.outTest();
        System.out.println("-------------------");

        Runner runner = Outer.getRunner();
        runner.run();

    }
}
class Outer{

    public static void outMethod(){
        System.out.println("Outer.outMethod");
        final String c = "局部变量c";
        class Inner{
            public void inMethod(){
                System.out.println("Inner.inMethod");
                System.out.println(c);
            }
        }

        Inner in = new Inner();
        in.inMethod();
    }

    public void outTest(){
        class Inner{
            public void inMethod1(){
                System.out.println("Inner.inMethod1");
            }
        }

        Inner in = new Inner();
        in.inMethod1();
    }

    public static Runner getRunner(){
        class LocalRunner implements Runner{
            @Override
            public void run() {
                System.out.println("LocalRunner.run");
            }
        }
        return new LocalRunner();
    }

}
interface Runner{
    void run();
}

3.2、匿名内部类

  • 因为考虑到这个子类或实现类是一次性的,那么我们“费尽心机”的给它取名字,就显得多余
  • 那么我们完全可以使用匿名内部类的方式来实现,避免给类命名的问题

语法格式:

new 父类([实参列表]){
    重写方法...
}
new 父接口(){
    重写方法...
}

举例1:使用匿名内部类的对象直接调用方法:

interface A{
	void a();
}
public class Test{
    public static void main(String[] args){
    	new A(){
			@Override
			public void a() {
				System.out.println("aaaa");
			}
    	}.a();
    }
}

举例2:通过父类或父接口的变量多态引用匿名内部类的对象

interface A{
	void a();
}
public class Test{
    public static void main(String[] args){
    	A obj = new A(){
			@Override
			public void a() {
				System.out.println("aaaa");
			}
    	};
    	obj.a();
    }
}

举例3:匿名内部类的对象作为实参

interface A{
	void method();
}
public class Test{
    public static void test(A a){
    	a.method();
    }
    
    public static void main(String[] args){
    	test(new A(){

			@Override
			public void method() {
				System.out.println("aaaa");
			}
    	});
    }   
}
  • 28
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 6
    评论
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值