从零开始学Java-面向对象进阶(三)

124 阅读14分钟

在前篇文章中,我们学习了面向对象的多态、包、final关键字、权限修饰符、以及抽象类等,下面我们接着来学习第三部分吧!

目录

  • 接口
  • 内部类

接口

我们来看这张图片:

image.png

左边的三种动物是不是都有共同体系吃饭和喝水呀,但是狗类和青蛙类会游泳,而兔子类不会游泳呀,那怎么办,写在父类又不行,那写在狗类和青蛙类里面嘛?好像也可以是吧!但是这样子会出现不必要的重复性了。那这个时候是不是就可以定义一种游泳规则,谁要用的时候谁用呢?其实可以的,当我们需要给多个类定义规则的时候,这个时候就要用到接口了。这种规则我们可以叫他是接口。我们接着往下看:

  • 什么是接口呢?
    • 接口:就是一种规则,更侧重于行为

下面我们就来学习一下什么是接口吧:

如何定义接口以及使用接口

  • 接口的定义和使用

    • 接口用关键字interface来定义
      • public interface 接口名{}
    • 接口不能实例化,接口不能创建他的对象
    • 接口和类之间是实现关系,通过implements关键字表示
      • public class 类名 implements 接口名{}
    • 接口的子类(实现类)
      • 要么重写接口中所有抽象方法
      • 要么是抽象类
  • 接口的注意点

    • 接口和类的实现关系,可以单实现也可以多实现

      • public class 类名 implements 接口名1,接口名2{};
    • 实现类还可以在继承一个类的同时实现多个接口

      • public class 类名 extends 父类 implements 接口名1,接口名2{};

下面我们来做个案例练练手吧:

编写带有接口和抽象类的标准JavaBean类       接口
青蛙       属性:名字,年龄        行为:吃虫子,蛙泳
狗         属性:名字,年龄        行为:吃骨头,狗刨
兔子       属性:名字,年龄        行为:吃胡萝卜

定义一个共同属性的动物父类:

public abstract class Animal {
    private String name;
    private int age;
    
    // 吃行为
    public abstract void eat();
}

我们来定义一个游泳的接口,注意了,这个时候就不是创建class文件了,而是implements接口

image.png

public interface Swim{
    // 定义游泳规则的接口
    public abstract void swim();
}

定义一个青蛙类并继承父类和实现游泳的接口:

public class Frog extends Animal implements Swim{
    // 重写青蛙吃的行为
    @Override
    public void eat() {
        System.out.println(getAge() + "岁的" + getName() + "在吃虫子");
    }
    // 重写青蛙动作行为
    @Override
    public void swim() {
        System.out.println(",他还会游泳!");
    }
}

定义一个狗类并继承父类和实现游泳的接口:

public class Dog extends Animal implements Swim{
    // 重写狗吃的行为
    @Override
    public void eat() {
        System.out.println(getAge() + "岁的" + getName() + "在吃骨头");
    }
    // 重写狗动作行为
    @Override
    public void swim() {
        System.out.println(",他还会狗刨!");
    }
}

定义一个兔子类并继承父类:

public class Rabbit extends Animal {
    @Override
    public void eat() {
        System.out.println(getAge() + "岁的" + getName() + "在吃胡萝卜");
    }
}

然后在测试类中为他们创建对象并调用父类和接口方法:

Frog f = new Frog("小青蛙",1);
f.eat();    // 调用父类吃的方法
f.swim();   // 调用接口游泳的方法

Dog d = new Dog("小泰迪",4);
d.eat();    // 调用父类吃的方法
d.swim();   // 调用接口游泳的方法

Rabbit r = new Rabbit("小白兔",2);
r.eat();    // 调用父类吃的方法

我们来运行看一下吧:

image.png

接口里面成员的特点

说到成员呢,还是我们的那三个老常客了:成员变量、构造方法、成员方法。下面我们先来说说成员变量吧:

  • 成员变量
    • 只能是常量
    • 默认修饰符:public static final
      • public:表示公共的,在什么地方都可以去调用接口方法
      • static:表示静态的,可以方便的进行调用(接口名.常量名称)
      • final:表示是常量,会用final修饰
  • 构造方法
    • 没有(接口中没有构造方法,因为在接口不能创建对象,接口当中也不需要给子类去赋值)
  • 成员方法
    • 只能是抽象方法
      • 默认修饰符:public abstract

接口和类之间的关系

  • 类和类的关系

    • 继承关系只能单继承,不能多继承,但是可以多层继承
  • 类和接口的关系

    • 实现关系可以单实现也可以多实现还可以在继承一个类的同时实现多个接口。(如果出现了重名的接口方法,只需要重写一次就可以了)。
  • 接口和接口的关系

    • 继承关系,可以单继承,也可以多继承

下面就有个问题了,如果我过几天要在接口增加新的方法,那是不是都要在每个类里面去重写呀,是的,在接口添加新的方法都要在继承的类中去重写。那我不重写行不行呀,不行,代码会报错的。

image.png

其实是有办法的,我们来看一下:

image.png

下面我们就来说一下在接口中添加方法的解决方式吧:

接口中新增默认方法

  • 允许在接口中定义默认方法,需要使用关键字default修饰。
    • 作用:解决接口升级的问题
  • 接口中默认方法的定义格式:
    • 格式:public default 返回值类型 方法名(参数列表){}
    • 格式:public default void show(){}
  • 接口中默认方法的注意事项:
    • 默认方法不是抽象方法,所以不强制被重写,但是如果被重写,重写的时候就需要去掉default关键字
    • public可以省略,default不能省略
    • 如果实现多个接口,多个接口中存在相同名字的默认方法,子类就必须对该方法进行重写。

下面我们来实操一下吧:

在接口里面定义两个方法:

image.png

定义一个类来继承接口:

image.png

我明明在接口定义两个方法呀,为什么我只重写了一个method方法就不会被报错呢?其实是不是因为method方法加了强制abstract是被强制重写的方法呀,show方法是加了default的默认方法,所以说什么时候使用在重写就可以啦。但是我们需要重写的时候需要注意去掉default关键字。我们来看重写方法:

@Override
public void show() {
    System.out.println("重写show方法");
}

好啦,新增默认方法就学到这里,下面我们来学一下新增静态方法吧

接口中新增静态方法

  • 允许在接口中定义静态方法,需要使用关键字static修饰。
  • 接口中静态方法的定义格式:
    • 格式:public static 返回值类型 方法名(参数列表){}
    • 格式:public static void show(){}
  • 接口中默认方法的注意事项:
    • 静态方法只能通过接口名调用,不能通过实现类名或者对象名调用
    • public可以省略static不能省略

怎么样,是不是和默认方法是一样的呢!下面我们来学习一下私有方法吧:

接口中新增私有方法

  • 接口中私有方法的定义格式:
    • 默认的私有方法
      • 格式1:private 返回值类型 方法名(参数列表){}
      • 格式1:private void show(){}
    • 静态的私有方法
    • 格式2:private static 返回值类型 方法名(参数列表){}
    • 格式2:private static void show(){} 我们来看段代码吧:
public default void show1(){
    System.out.println("show3方法开始执行!");
    show3();
}

public static void show2(){
    System.out.println("show4方法开始执行!");
    show4();
}
// 默认的私有方法,给默认方法服务的
private void show3(){
    System.out.println("忽略一百行代码");
}
//静态的私有方法,给静态方法服务的
private static void show4(){
    System.out.println("忽略一百行代码");
}

下面我们来给接口做个总结吧:

  1. JDK7以前:接口中只能定义抽象方法。
  2. JDK8:接口中可以定义有方法体的方法。(静态、默认
  3. JDK9:接口中可以定义私有方法
  4. 私有方法分两种:普通的私有方法静态的私有方法
  5. 接口代表规则,是行为的抽象。想要让哪个类拥有一个行为,就让这个类实现对应的接口就可以啦。
  6. 当一个方法参数是接口时,可以传递接口所有实现类的对象,这种方式称之为接口的多态。

适配器

什么是适配器呢?我们往下看:

假设我们接口有10个方法,而我又只要使用第五个方法怎么办呢?

public interface Inter {
    // 定义10个方法
    public abstract void method1();
    public abstract void method2();
    public abstract void method3();
    public abstract void method4();
    public abstract void method5();
    public abstract void method6();
    public abstract void method7();
    public abstract void method8();
    public abstract void method9();
    public abstract void method10();
}

像我们之前是不是要重写接口里面的10个方法呀,那样不是很麻烦嘛?

image.png

这个时候我们就可以创建他们的第三者:适配器

image.png

image.png

image.png

看这个时候就不会出现全部方法了,需要用到哪个方法在重写即可。下面我们来总结一下吧:

  1. 当一个接口中抽象方法过多,但是我只要使用其他一部分的时候,就可以去设计一个适配器。

  2. 书写步骤:

    1. 编写中间类以XXXAdapter起名,XXX(接口名),实现对应的接口。
    2. 对接口中的抽象方法进行空实现。
    3. 让真正的实现类继承中间类,并重写需要调用的方法。
    4. 为了避免其他类创建适配器的对象,中间的适配器类用abstract进行修饰

好啦,接口我们就学习到这里,下面我们来学习一下内部类吧:

内部类

内部类介绍

内部类是类的五大成员:属性、方法、构造方法、代码块、内部类

什么是内部类:在一个类的里面,在定义一个类,这就叫内部类

我们来看一段案例了解一下吧:

需求:写一个javabean类描述汽车

属性:汽车的品牌,车龄,颜色,发动机的品牌,使用年限

public class Cat {      // 外部类
    String carName;     // 汽车颜色
    int carAge;         // 汽车年龄
    String carColor;    // 汽车颜色

    class Engine{       // 内部类
        String engineName;  // 发动机品牌
        int engineAge;      // 使用年限
    }
}

在定义内部类的时候我们要遵守这些规则:

  • 内部类表示的事物是外部类的一部分。
  • 内部类单独出现没有任何意义。

内部类访问的特点:

  • 内部类可以直接访问外部类的成员,包括私有。
  • 外部类要访问内部类的成员,必须要创建对象,否则访问不了。

内部类分类

内部类分为:成员内部类、静态内部类、局部内部类、匿名内部类其中前面三个我们只需要了解就可以啦,而匿名内部类需要重点掌握的。下面我们一起来学一下:

成员内部类

  • 成员内部类书写:

    • 写在成员位置的,属于外部类的成员
    • 成员内部类可以被一些修饰符修饰:private、默认、protected、public、static等...

    image.png

  • 获取成员内部类对象:

    • 方法1:当成员内部类被private修饰时。在外部类中编写方法,对外提供内部类对象。
    • 方法2:直接创建格式:外部类名.内部类名 对象名 = 外部类对象.内部类对象;
  • 成员内部类获取外部类的成员变量:外部类名.变量名

我们来看下面这段代码吧:

image.png

Outer.this.a是什么意思呢?Outer.this.表示的就是外部类的地址值,直接在外部类的地址值中去找变量a的值。

静态内部类

什么是静态内部类呢?我们还是来看这段代码:

public class Cat {      // 外部类
    String carName;     // 汽车颜色
    int carAge;         // 汽车年龄
    String carColor;    // 汽车颜色

    static class Engine{       // 静态内部类
        String engineName;  // 发动机品牌
        int engineAge;      // 使用年限
    }
}

被static修饰的类叫做静态内部类。静态内部类我们需要注意一点:

  • 静态内部类只能访问外部类中的静态变量和静态方法,如果想要访问非静态的需要创建对象。

创建静态内部类对象的格式:外部类名.内部类名. 对象名 = new 外部类名.内部类名();

调用静态方法的格式:外部类名.内部类名.方法名();

调用非静态方法的格式:先创建对象,用对象名调用。

我们来看一下调用的两种格式吧: image.png

局部内部类

  • 局部内部类:
    • 将内部类定义在方法里面就叫做局部内部类,类似于方法里面的局部变量。
    • 外界是无法使用的,需要在方法内部创建对象并调用。
    • 该类可以直接访问外部类的成员,也可以访问方法内的局部变量。

下面我们来看段代码:

image.png

由于这个不常用,使所以只需要知道就可以啦!下面我们来学最最最最最最重要的匿名内部类吧!

匿名内部类

匿名内部类定义格式:

new 类名或者接口名(){
    重写方法;
}

例:

new Inter(){
    public void show(){
        
    }
}

匿名内部类整体格式包含了三部分:继承\实现、方法重写、创建对象

下面我们来实操一下吧:

定义一个接口,接口里面编写游泳方法:

public interface Swim {
    public abstract void swim();
}

我们用之前的方法来对接口的方法重写看一下格式:

public class Student implements Swim{     // Swim接口名
    @Override
    public void swim() {    // 重写接口游泳的方法
        System.out.println("重写了游泳的方法!");
    }
};

用匿名内部类编写接口的重写方法格式:

// 编写匿名内部类的代码
new Swim(){     // Swim接口名
    @Override
    public void swim() {    // 重写接口游泳的方法
        System.out.println("重写了游泳的方法!");
    }
};

怎么样,是不是匿名方法的反而更简单一点呢?那是什么意思呢?我们来看下面这张图片:

image.png

下面我们再来看一个匿名类的:

定义一个类:

public abstract class Outer {
    public abstract void eat();
}

用匿名重写类里面的eat方法:

// 编写匿名内部类的代码
new Outer(){     // Outer类名
    @Override
    public void eat() {     // 重写类吃的方法
        System.out.println("重写了吃的方法!");
    }
};

怎么样,是不是也是和接口是一样的呢,下面我们也来说一下是什么意思吧:

image.png

那怎么使用呢?我们来看一下:

首先定义一个方法:

public static void method(Outer a){
    a.eat();
}

我们想要调用method方法,之前是不是要先写一个子类继承父类,然后在创建子类对象传递给method方法呀。那好麻烦呀,我们来看一下内部类的使用方式:

// 在测试类中调用下面的method方法
method(
        // 直接改写eat方法
        new Outer() {
            @Override
            public void eat() {
                System.out.println("狗在吃骨头");
            }
        }
);

怎么样,是不是用匿名内部类就简单多啦!是的。下面我们再来扩展一些知识吧:

Outer d = new Outer() {
    @Override
    public void eat() {
        System.out.println("狗在吃骨头");
    }
};
d.eat();

image.png

怎么样,是不是和上面一样的呀!这个整体我们可以理解为Outer类的继承对象,继承的多态。

下面我们来总结一下吧:

  • 什么是匿名内部类:

    • 匿名内部类本质上就是隐藏了名字的内部类。可以写在成员位置,也可以写在局部位置。
  • 匿名内部类的格式:

    image.png

  • 格式的细节:

    • 包含了继承或者实现方法重写创建对象。整体就是一个类的子类对象或者接口的实现类对象
  • 使用场景:

    • 当方法的参数是接口或者类时,(以接口为例:可以传递这个接口的实现类对象,如果实现类只要使用一次,就可以用匿名内部类简化代码)

好啦,这篇文章就学到这里,有什么不懂的可以在评论区评论一起探讨哟,我们下期不见不散!!!

==最后非常感谢您的阅读,也希望能得到您的反馈  ==