第十二天 接口、内部类

接口

概述

生活中大家每天都在用USB接口,那么USB接口与我们今天要学习的接口有什么相同点呢?

USB是通用串行总线的英文缩写,是Intel公司开发的总线架构,使得在计算机上添加串行设备(鼠标、键盘、打印机、扫描仪、摄像头、充电器、MP3机、手机、数码相机、移动硬盘等)非常容易。只须将设备插入计算机的USB端口中,系统会自动识别和配置。 有了USB,我们电脑需要提供的各种插槽的口越来越少,而能支持的其他设备的连接却越来越多。

那么我们平时看到的电脑上的USB插口、以及其他设备上的USB插口是什么呢?

其实,不管是电脑上的USB插口,还是其他设备上的USB插口都只是遵循了USB规范的一种具体设备而已。

根据时代发展,USB接口标准经历了一代USB、第二代USB 2.0和第三代USB 3.0 。

USB规格第一次是于1995年,由Intel、IBM、Compaq、Microsoft、NEC、Digital、North Telecom等七家公司组成的USBIF(USB Implement Forum)共同提出,USBIF于1996年1月正式提出USB1.0规格,频宽为1.5Mbps。

USB2.0技术规范是有由Compaq、Hewlett Packard、Intel、Lucent、Microsoft、NEC、Philips共同制定、发布的,规范把外设数据传输速度提高到了480Mbps,被称为USB 2.0的高速(High-speed)版本.

USB 3.0是最新的USB规范,该规范由英特尔等公司发起,USB3.0的最大传输带宽高达5.0Gbps(640MB/s),USB3.0 引入全双工数据传输。5根线路中2根用来发送数据,另2根用来接收数据,还有1根是地线。也就是说,USB 3.0可以同步全速地进行读写操作。

USB版本

最大传输速率

速率称号

最大输出电流

推出时间

USB1.0

1.5Mbps(192KB/s)

低速(Low-Speed)

5V/500mA

1996年1月

USB1.1

12Mbps(1.5MB/s)

全速(Full-Speed)

5V/500mA

1998年9月

USB2.0

480Mbps(60MB/s)

高速(High-Speed)

5V/500mA

2000年4月

USB3.0

5Gbps(500MB/s)

超高速(Super-Speed)

5V/900mA

2008年11月

USB 3.1

10Gbps(1280MB/s)

超高速+(Super-speed+)

20V/5A

2013年12月

下面是USB2.0和USB3.0标准下的各类接口示意图:

电脑边上提供了USB插槽,这个插槽遵循了USB的规范,只要其他设备也是遵循USB规范的,那么就可以互联,并正常通信。至于这个电脑、以及其他设备是哪个厂家制造的,内部是如何实现的,我们都无需关心。

这种设计是将规范和实现分离,这也正是Java接口的好处。Java的软件系统会有很多模块组成,那么各个模块之间也应该采用这种面相接口的低耦合,为系统提供更好的可扩展性和可维护性。

接口就是规范,定义的是一组规则,体现了现实世界中“如果你是/要...则必须能...”的思想。继承是一个"是不是"的is-a关系,而接口实现则是 "能不能"的has-a关系。

  • 例如:你能不能用USB进行连接,或是否具备USB通信功能,就看你是否遵循USB接口规范

  • 例如:Java程序是否能够连接使用某种数据库产品,那么要看该数据库产品有没有实现Java设计的JDBC规范

定义格式

接口的定义,它与定义类方式相似,但是使用 interface 关键字。它也会被编译成.class文件,但一定要明确它并不是类,而是另外一种引用数据类型。

引用数据类型:数组,类,接口。
接口的声明格式
【修饰符】 interface 接口名{
    //接口的成员列表:
    // 静态常量
    // 抽象方法
    // 默认方法
    // 静态方法
    // 私有方法
}
interface Usb3{
    //静态常量
    public static final long MAX_SPEED = 500*1024*1024;//500MB/s
    
    //抽象方法
    public abstract void read();
    public abstract void write();
    
    //默认方法
    public default void start(){
        System.out.println("开始");
    }
    public default void stop(){
        System.out.println("结束");
    }
    
    //静态方法
    public static void show(){
        System.out.println("USB 3.0可以同步全速地进行读写操作");
    }
}
接口的成员说明

接口定义的是多个类共同的公共行为规范,这些行为规范是与外部交流的通道,这就意味着接口里通常是定义一组公共方法。

在JDK8之前,接口中只允许出现:

(1)公共的静态的常量:其中public static final可以省略

(2)公共的抽象的方法:其中public abstract可以省略

理解:接口是从多个相似类中抽象出来的规范,不需要提供具体实现

在JDK1.8时,接口中允许声明默认方法和静态方法:

(3)公共的默认的方法:其中public 可以省略,建议保留,但是default不能省略

(4)公共的静态的方法:其中public 可以省略,建议保留,但是static不能省略

在JDK1.9时,接口又增加了:

(5)私有方法。

注意:接口中不能有其他成员,没有构造器,没有初始化块,因为接口中没有成员变量需要初始化。
因为接口中没有构造器,所以接口不能实例化,即不能创建对象
面试题拷问?

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

因为接口是标准规范,那么在规范中需要声明一些底线边界值,当实现者在实现这些规范时,不能去随意修改和触碰这些底线,否则就有“危险”。

例如:USB1.0规范中规定最大传输速率是1.5Mbps,最大输出电流是5V/500mA

USB3.0规范中规定最大传输速率是5Gbps(500MB/s),最大输出电流是5V/900mA

例如:尚硅谷学生行为规范中规定学员,早上8:25之前进班,晚上21:30之后离开等等。

2、为什么JDK1.8之后要允许接口定义静态方法和默认方法呢?因为它违反了接口作为一个抽象标准定义的概念。

静态方法:因为之前的标准类库设计中,有很多Collection/Colletions或者Path/Paths这样成对的接口和类,后面的类中都是静态方法,而这些静态方法都是为前面的接口服务的,那么这样设计一对API,不如把静态方法直接定义到接口中使用和维护更方便。

默认方法:(1)我们要在已有的老版接口中提供新方法时,如果添加抽象方法,就会涉及到原来使用这些接口的类就会有问题,那么为了保持与旧版本代码的兼容性,只能允许在接口中定义默认方法实现。比如:Java8中对Collection、List、Comparator等接口提供了丰富的默认方法。(2)当我们接口的某个抽象方法,在很多实现类中的实现代码是一样的,此时将这个抽象方法设计为默认方法更为合适,那么实现类就可以选择重写,也可以选择不重写。

3、为什么JDK1.9要允许接口定义私有方法呢?因为我们说接口是规范,规范时需要公开让大家遵守的

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

实现接口

接口的使用,它不能创建对象,但是可以被实现(implements ,类似于被继承)。

类与接口的关系为实现关系,即类实现接口,该类可以称为接口的实现类,也可以称为接口的子类。实现的动作类似继承,格式相仿,只是关键字不同,实现使用 implements关键字。

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

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

注意:

  1. 如果接口的实现类是非抽象类,那么必须重写接口中所有抽象方法。

  1. 默认方法可以选择保留,也可以重写。

重写时,default单词就不要再写了,它只用于在接口中表示默认方法,到类中就没有默认方法的概念了
  1. 不能重写静态方法。

示例代码:

class MobileHDD implements Usb3{

    //重写/实现接口的抽象方法,【必选】
    public void read() {
        System.out.println("读数据");
    }
    public void write(){
        System.out.println("写数据");
    }
    
    //重写接口的默认方法,【可选】
    //重写默认方法时,default单词去掉
    public void end(){
        System.out.println("清理硬盘中的隐藏回收站中的东西,再结束");
    }
}
如何接口中调用对应的方法
  • 对于接口的静态方法,直接使用“接口名.方法”进行调用即可

  • 也只能使用“接口名.方法"进行调用,不能通过实现类的对象进行调用

  • 对于接口的抽象方法、默认方法,只能通过实现类对象才可以调用

  • 接口不能直接创建对象,只能创建实现类的对象。

public class TestInteface {
    public static void main(String[] args) {
        //创建实现类对象
        MobileHDD b = new MobileHDD();
        
        //通过实现类对象调用重写的抽象方法,以及接口的默认方法,如果实现类重写了就执行重写的默认方法,如果没有重写,就执行接口中的默认方法
        b.start();
        b.read();
        b.stop();
        
        //通过接口名调用接口的静态方法
        MobileHDD.show();
    }
}
接口的多实现

之前学过,在继承体系中,一个类只能继承一个直接父类。而对于接口而言,一个类是可以实现多个接口的,这叫做接口的多实现。并且,一个类能继承一个父类,同时实现多个接口。

实现格式:

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

定义多个接口:

interface A {
    publicabstractvoidshowA();
    publicabstractvoidshow();
}
​
interface B {
    publicabstractvoidshowB();
    publicabstractvoidshow();
}

定义实现类:

public class C implements A,B{
    @Override
    public void showA() {
        System.out.println("showA");
    }

    @Override
    public void showB() {
        System.out.println("showB");
    }

    @Override
    public void show() {
        System.out.println("show");
    }
}
默认方法冲突问题
亲爹优先原则

当一个类,既继承一个父类,又实现若干个接口时,父类中的成员方法与接口中的抽象方法重名。如果子类重写了该方法就执行子类重写了的方法,如果子类没有重写该方法,子类就近选择执行父类的成员方法。代码如下:

定义接口:

interface A {
    public default void methodA(){
        System.out.println("AAAAAAAAAAAA");
    }
}

定义父类:

class D {
    public void methodA(){
        System.out.println("DDDDDDDDDDDD");
    }
}

定义子类:

class C extends D implements A {
  // 未重写methodA方法
}
class B extends D implements A{
    //当然也可以选择重写
    public void methodA(){
        System.out.println("BBBBBBBBBBBB");
    }
}

定义测试类:

public class Test {
    public static void main(String[] args) {
        C c=new C();
        c.methodA(); 
        
        B b=new B();
        b.methodA();
    }
}

输出结果:

DDDDDDDDDDDD

BBBBBBBBBBBB

必须做出选择(两种方式选一个)

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

无论你多难抉择,最终都是要做出选择的。代码如下:

声明接口:

interface A{
    public default void d(){
        System.out.println("今晚7点-8点陪我吃饭看电影");
    }
}
interface B{
    public default void d(){
        System.out.println("今晚7点-8点陪我逛街吃饭");
    }
}

方式一:选择自己想保留的那一个,然后通过“接口名.super.方法名"的方法选择保留哪个接口的默认方法。

class C implements A,B{

    @Override
    public void d() {
        A.super.d();
    }
    
}

方式二:选择自己完全重写

class D implements A,B{
@Override
public void d() {
System.out.println("自己待着");
}
}
接口的多继承

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

定义父接口:

interface A {
    void a();
    public default void methodA(){
        System.out.println("AAAAAAAAAAAAAAAAAAA");
    }
}

interface B {
    void b();
    public default void methodB(){
        System.out.println("BBBBBBBBBBBBBBBBBBB");
    }
}

定义子接口:

interface C extends A,B{
    @Override
    public default void methodB() {
        System.out.println("CCCCCCCCCCCCCCCCCCCC");
    }
}
小贴士:
子接口重写默认方法时,default关键字可以保留。
子类重写默认方法时,default关键字不可以保留。
class D implements C{

    @Override
    public void a() {
        System.out.println("xxxxx");
    }

    @Override
    public void b() {
        System.out.println("yyyyy");
    }
    
}
class E implements A,B,C{//效果和上面的D是等价的

    @Override
    public void b() {
        System.out.println("xxxxx");
    }

    @Override
    public void a() {
        System.out.println("yyyyy");
    }
    
}
接口与实现类对象的多态引用

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

public class TestInterface {
    public static void main(String[] args) {
        Flyable b = new Bird();
        b.fly();
        
        Flyable k = new Kite();
        k.fly();
    }
}
interface Flyable{
    //抽象方法
    void fly();
}
class Bird implements Flyable{

    @Override
    public void fly() {
        System.out.println("展翅高飞");
    }
    
}
class Kite implements Flyable{

    @Override
    public void fly() {
        System.out.println("别拽我,我要飞");
    }
    
}

内部类

概述

  1. 什么是内部类?

将一个类A定义在另一个类B里面,里面的那个类A就称为内部类,B则称为外部类

  1. 为什么要声明内部类呢?

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

而且内部类因为在外部类的里面,因此可以直接访问外部类的私有成员。

  1. 内部类都有哪些形式?

根据内部类声明的位置(如同变量的分类),我们可以分为:

(1)成员内部类:

  • 静态成员内部类

  • 非静态成员内部类

(2)局部内部类

  • 有名字的局部内部类

  • 匿名的内部类

静态内部类

语法格式:

【修饰符】 class 外部类{
    【其他修饰符】 static class 内部类{
    }
}

静态内部类的特点:

  • 和其他类一样,它只是定义在外部类中的另一个完整的类结构

  • 可以继承自己的想要继承的父类,实现自己想要实现的父接口们,和外部类的父类和父接口无关

  • 可以在静态内部类中声明属性、方法、构造器等结构,包括静态成员

  • 可以使用abstract修饰,因此它也可以被其他类继承

  • 可以使用final修饰,表示不能被继承

  • 编译后有自己的独立的字节码文件,只不过在内部类名前面冠以外部类名和$符号。

  • 和外部类不同的是,它可以允许四种权限修饰符:public,protected,缺省,private

  • 外部类只允许public或缺省的

  • 可以在静态内部类中使用外部类的静态成员

  • 在静态内部类中不能使用外部类的非静态成员哦

  • 在外部类的外面不需要通过外部类的对象就可以创建静态内部类的对象

  • 如果在内部类中有变量与外部类的静态成员变量同名,可以使用“外部类名."进行区别

public class TestInner{
    public static void main(String[] args){
        Outer.Inner in= new Outer.Inner();
        in.inMethod();
        
        Outer.Inner.inTest();
        
        Outer.Inner.inFun(3);
    }
}

class Outer{
    private static int a = 1;
    private int b = 2;
    protected static class Inner{
        static int d = 4;//可以
        void inMethod(){
            System.out.println("out.a = " + a);
//            System.out.println("out.b = " + b);//错误的
        }
        static void inTest(){
            System.out.println("out.a = " + a);
        }
        static void inFun(int a){
            System.out.println("out.a = " + Outer.a);
            System.out.println("local.a = " + a);
        }
    }
}

非静态成员内部类

非静态内部类的特点:

  • 和其他类一样,它只是定义在外部类中的另一个完整的类结构

  • 可以继承自己的想要继承的父类,实现自己想要实现的父接口们,和外部类的父类和父接口无关

  • 可以在非静态内部类中声明属性、方法、构造器等结构,但是不允许声明静态成员,但是可以继承父类的静态成员,而且可以声明静态常量

  • 可以使用abstract修饰,因此它也可以被其他类继承

  • 可以使用final修饰,表示不能被继承

  • 编译后有自己的独立的字节码文件,只不过在内部类名前面冠以外部类名和$符号。

  • 和外部类不同的是,它可以允许四种权限修饰符:public,protected,缺省,private

  • 外部类只允许public或缺省的

  • 还可以在非静态内部类中使用外部类的所有成员,哪怕是私有的

  • 在外部类的静态成员中不可以使用非静态内部类哦

  • 就如同静态方法中不能访问本类的非静态成员变量和非静态方法一样

  • 在外部类的外面必须通过外部类的对象才能创建非静态内部类的对象

  • 因此在非静态内部类的方法中有两个this对象,一个是外部类的this对象,一个是内部类的this对象

示例代码:

public class TestInner{
    public static void main(String[] args){
        Outer out = new Outer();
        Outer.Inner in= out.new Inner();
        in.inMethod();
        
        Outer.Inner inner = out.getInner();
        inner.inMethod();
    }
}
class Father{
    protected static int c = 3;
}
class Outer{
    private static int a = 1;
    private int b = 2;
    protected class Inner extends Father{
//        static int d = 4;//错误
        int b = 5;
        void inMethod(){
            System.out.println("out.a = " + a);
            System.out.println("out.b = " + Outer.this.b);
            System.out.println("in.b = " + b);
            System.out.println("father.c = " + c);
        }
    }
    
    public static void outMethod(){
//        Inner in = new Inner();//错误的
    }
    public Inner getInner(){
        return new Inner();
    }
}

局部内部类

语法格式:

【修饰符】 class 外部类{
    【修饰符】 返回值类型  方法名(【形参列表】){
            【final/abstract】 class 内部类{
        }
    }    
}

局部内部类的特点:

  • 和外部类一样,它只是定义在外部类的某个方法中的另一个完整的类结构

  • 可以继承自己的想要继承的父类,实现自己想要实现的父接口们,和外部类的父类和父接口无关

  • 可以在局部内部类中声明属性、方法、构造器等结构,但不包括静态成员,除非是从父类继承的或静态常量

  • 可以使用abstract修饰,因此它也可以被同一个方法的在它后面的其他内部类继承

  • 可以使用final修饰,表示不能被继承

  • 编译后有自己的独立的字节码文件,只不过在内部类名前面冠以外部类名、$符号、编号。

  • 这里有编号是因为同一个外部类中,不同的方法中存在相同名称的局部内部类

  • 和成员内部类不同的是,它前面不能有权限修饰符等

  • 局部内部类如同局部变量一样,有作用域

  • 局部内部类中是否能访问外部类的静态还是非静态的成员,取决于所在的方法

  • 局部内部类中还可以使用所在方法的局部常量,即用final声明的局部变量

  • JDK1.8之后,如果某个局部变量在局部内部类中被使用了,自动加final

示例代码:

class Outer{
    private static int a = 1;
    private int b = 2;
    
    public static void outMethod(){
        final int c = 3;
        class Inner{
            public void inMethod(){
                System.out.println("out.a = " + a);
//                System.out.println("out.b = " + b);//错误的,因为outMethod是静态的
                System.out.println("out.local.c = " + c);
            }
        }
        
        Inner in = new Inner();
        in.inMethod();
    }
    
    public void outTest(){
        final int c = 3;
        class Inner{
            public void inMethod(){
                System.out.println("out.a = " + a);
                System.out.println("out.b = " + b);//可以,因为outTest是飞静态的
                System.out.println("method.c = " + c);
            }
        }
        
        Inner in = new Inner();
        in.inMethod();
    }
    
}

为什么在局部内部类中使用外部类方法的局部变量要加final呢?

public class TestInner{
    public static void main(String[] args) {
        A obj = Outer.method();
        //因为如果c不是final的,那么method方法执行完,method的栈空间就释放了,那么c也就消失了
        obj.a();//这里打印c就没有中可取了,所以把c声明为常量,存储在方法区中
    }
}

interface A{
    void a();
}
class Outer{
    public static A method(){
        final int c = 3;
        class Sub implements A{
            @Override
            public void a() {
                System.out.println("method.c = " + c);
            }
        }
        return new Sub();
    }
}

匿名内部类

引入

当我们在开发过程中,需要用到一个抽象类的子类的对象或一个接口的实现类的对象,而且只创建一个对象,而且逻辑代码也不复杂。那么我们原先怎么做的呢?

  1. 编写类,继承这个父类或实现这个接口

  1. 重写父类或父接口的方法

  1. 创建这个子类或实现类的对象

例如:

public interface Runnable{
    public abstract void run();
}
//声明接口实现类
public class MyRunnable implements Runnable{
    public void run(){
        while(true){
            System.out.println("大家注意安全");
            try
                Thread.sleep(1000);
            }catch(Exception e){                
            }
        }
    }
}
public class Test{
    public static void main(String[] args){
        //如果MyRunnable类只是在这里使用一次,并且只创建它的一个对象
        //分开两个.java源文件,反而不好维护
        Runnable target = new MyRunnable();
        Thread t = new Thread("安全提示线程",target);
        t.start();
    }
}

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

可以修改为如下形式:

public class Test{
    public static void main(String[] args){
        //MyRunnable类只是在这里使用一次,并且只创建它的一个对象,那么这些写代码更紧凑,更好维护
        Runnable target = new Runnable(){
            public void run(){
                while(true){
                    System.out.println("大家注意安全");
                    try
                        Thread.sleep(1000);
                    }catch(Exception e){                
                    }
                }
            }
        };
        Thread t = new Thread("安全提示线程",target);
        t.start();
    }
}
语法格式
new 父类(【实参列表】){
    重写方法...
}
//()中是否需要【实参列表】,看你想要让这个匿名内部类调用父类的哪个构造器,如果调用父类的无参构造,那么()中就不用写参数,如果调用父类的有参构造,那么()中需要传入实参
new 父接口(){
    重写方法...
}
//()中没有参数,因为此时匿名内部类的父类是Object类,它只有一个无参构造
匿名内部类是没有名字的类,因此在声明类的同时就创建好了唯一的对象。

注意:

匿名内部类是一种特殊的局部内部类,只不过没有名称而已。所有局部内部类的限制都适用于匿名内部类。例如:

  • 在匿名内部类中是否可以使用外部类的非静态成员变量,看所在方法是否静态

  • 在匿名内部类中如果需要访问当前方法的局部变量,该局部变量需要加final

思考:这个对象能做什么呢?

答:(1)调用某个方法(2)赋值给父类/父接口的变量,通过多态引用使用这个对象(3)作为某个方法调用的实参

static关键字

static是一个修饰符,可以修饰:

  • 成员变量,我们称为类变量,或静态变量,表示某个类的所有对象共享的数据

  • 成员方法,我们称为类方法,或静态方法,表示不需要实例对象就可以调用的方法,使用“类名."进行调用

  • 代码块,我们称为静态代码块,或静态初始化块,用于为静态变量初始化,每一个类的静态代码块只会执行一次,在类第一次初始化时执行

  • 成员内部类,我们称为静态成员内部类,简称静态内部类,不需要外部类实例对象就可以使用的内部类,在静态内部类中只能使用外部类的静态成员

容易错误:

static修饰外部类(错误)

static的方法被重写(错误)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值