14初级 - 面向对象:接口与抽象类

338 阅读7分钟

Abstract约定

  • 把公用的事情抽取出来变成一个骨架,Abstract代表一个骨架
  • JDK中有很多Abstract类,比如AbstractHashMap之类的类

如何强制用户覆盖某个方法,但是不给默认方法

  • 1.抛出异常,但这是运行时的,编译通过
public void doSomething() {
    throw new UnsupportedOperationException();
}
  • 2.使用抽象类,这样子整个类就要变成抽象类
public abstract void doSomething();

抽象类不能被实例化

  • 抽象类用abstract去声明,除此之外它和其他类没有本质的区别
  • 好处是如果别人忘了覆盖抽象类的方法,会报错
  • 加入允许它能实例化,它里面没有方法的可以执行体,换句话而言,可以实例化的东西一定要补全所有的方法
  • 如果把所有的方法都去掉,抽象类就变成了接口
  • 抽象类可以包含抽象方法,可以包含成员变量

接口

  • 接口不是类,它只代表一种功能

1.png

  • main.java
package com.github.hcsp.inheritance;

public class Main {
    public static void main(String[] args) {
        // 接口不是类,它只代表一种功能
        会飞 会飞的东西 = new 鸟();
    }
}
  • 会飞.java
package com.github.hcsp.inheritance;

public interface 会飞 {
    void 飞();
}
  • 鸟.java
package com.github.hcsp.inheritance;

// 鸟实现了这个功能
public class 鸟 implements 会飞 {

    @Override
    public void 飞() {
        System.out.println("鸟儿飞");
    }
}

实现接口的同时继承其他的类

  • 鸟.java
package com.github.hcsp.inheritance;

// 鸟实现了这个功能
public class 鸟 extends 动物 implements 会飞 {

    public 鸟(String name) {
        super(name);
    }

    @Override
    public void 飞() {
        System.out.println("鸟儿飞");
    }
}
  • 继承体系图

2.png

  • 鸟出了继承动物之外还有另一套继承体系,会飞,一个是蓝线一个是绿虚线
  • 接口意味描述有这个功能,但是不给出具体的实现
  • 一个东西必须是一个东西,但是可以有很多功能
package com.github.hcsp.inheritance;

// 鸟实现了这个功能
public class 鸟 extends 动物 implements 会飞, 会呼吸, 会新陈代谢 {

    public 鸟(String name) {
        super(name);
    }

    @Override
    public void 飞() {
        System.out.println("鸟儿飞");
    }
}

什么时候该用抽象类,什么时候该用接口

  • 接口的抽象度更高,因为不需要实现

抽象类与接口详解与实战

  • 抽象类不可实例化
  • 可以实例化的东西一定要补全所有的方法体
  • 可以包含抽象方法 - 非private/static
  • 可以包含普通类的任何东西
  • abstractXXXX.java
package com.github.hcsp.inheritance;

public abstract class AbstractXXXX {
    abstract void f();
}

接口继承另外一个接口

  • 任何类继承了MyInterface接口就要把接口继承的接口包括其本身的所有方法实现
package com.github.hcsp.inheritance;

import java.util.List;

public interface MyInterface extends List {
}

3.png

  • 接口里面的成员变量int a = 1等价public static final int A = 1这些是多余的。

  • 接口可以理解成一种能力,和类的区别可以理解成,类找几个人来干活,接口有能力的人来干活

  • 接口避免了多重继承的二义性

  • 接口很大的限制就是一旦发布出去不能做修改,但是这是不可能的

package com.github.hcsp.inheritance;

import java.util.List;

public interface MyInterface {
    void f();
    // 向后兼容性
    // backward compatibility
    void g();
}

class MyClass implements MyInterface {

    @Override
    public void f() {
        // balabala
    }
}
  • list的例子,有序列表缺少sort方法,按control + H查看所有的实现
  • java8之后默认的接口方法
default void sort(Comparator<? super E> c) {
        Object[] a = this.toArray();
        Arrays.sort(a, (Comparator) c);
        ListIterator<E> i = this.listIterator();
        for (Object e : a) {
            i.next();
            i.set((E) e);
        }
    }

4.png

5.png

  • 引入default之后又引起的二义性
interface A {
    default void f() {
        System.out.println("I'm A");
    }
}
interface B {
    default void f() {
        System.out.println("I'm A");
    }
}

class C implements A, B {
    {
        f();
    }
}

总结接口和抽象类的差异和共性

  • 相同点:都是抽象的不可实例化
  • 都可以包含抽象方法,抽象方法就是没有方法体,非static/private/final
  • 不同点:抽象类是类可以包含类的一切东西,接口只能包含受限制的成员(所有都是public static final的常量)和方法
  • 方法在经典的接口定义中默认是public的没有方法体的抽象方法,java八之后可以加default的默认方法
  • 抽象类只能单一继承,一条路径,而接口可以多继承,甚至可以继承多次

java的设计

  • 最大程度的灵活性
  • 和最大程度的复用
  • 接口也可以用isInstance,代表一种能力,一种约定,API-application program Interface,数据交互的约定,user Interface,和用户的约定,点x表示关闭

接口和抽象类实战:实现一个文件过滤器

  • command+alt+b,查看最简单的实现接口
  • command+f12查看整个文件的结构
  • 匿名内部类
    • 直接访问外部的变量
  • 第一版实现功能
  • FileFIlterVisitor.java
package com.github.hcsp.polymorphism;

import java.io.IOException;
import java.nio.file.FileVisitResult;
import java.nio.file.Path;
import java.nio.file.SimpleFileVisitor;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.ArrayList;
import java.util.List;

public class FileFilterVisitor extends SimpleFileVisitor<Path> {

    private String extension;
    private List<String> filteredNames = new ArrayList<>();

    public FileFilterVisitor(String extension) {
        this.extension = extension;
    }

    public List<String> getFilteredNames() {
        return filteredNames;
    }

    @Override
    public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
        System.out.println(file);
        if (file.getFileName().toString().endsWith(extension)) {
            filteredNames.add(file.getFileName().toString());
        }
        return FileVisitResult.CONTINUE;
    }
}

  • FileFilter.java
package com.github.hcsp.polymorphism;

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.List;

public class FileFilter {
    public static void main(String[] args) throws IOException {
        Path projectDir = Paths.get(System.getProperty("user.dir"));
        Path testRootDir = projectDir.resolve("test-root");
        if (!testRootDir.toFile().isDirectory()) {
            throw new IllegalStateException(testRootDir.toAbsolutePath().toString() + "不存在!");
        }

        List<String> filteredFileNames = filter(testRootDir, ".csv");
        System.out.println(filteredFileNames);
    }

    /**
     * 实现一个按照扩展名过滤文件的功能
     *
     * @param rootDirectory 要过滤的文件夹
     * @param extension     要过滤的文件扩展名,例如 .txt
     * @return 所有该文件夹(及其后代子文件夹中)匹配指定扩展名的文件的名字
     */
    public static List<String> filter(Path rootDirectory, String extension) throws IOException {
        FileFilterVisitor visitor = new FileFilterVisitor(extension);
        Files.walkFileTree(rootDirectory, visitor);
        return visitor.getFilteredNames();
    }
}
  • 第一版实现还不完美,逻辑应该紧密相连现在分为两个文件,另外参数需要反复传递
  • FileFilter.java匿名内部类去解决这个问题,粉红色代表内部的变量捕获了外部的类
package com.github.hcsp.polymorphism;

import java.io.IOException;
import java.nio.file.*;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.ArrayList;
import java.util.List;

public class FileFilter {
    public static void main(String[] args) throws IOException {
        Path projectDir = Paths.get(System.getProperty("user.dir"));
        Path testRootDir = projectDir.resolve("test-root");
        if (!testRootDir.toFile().isDirectory()) {
            throw new IllegalStateException(testRootDir.toAbsolutePath().toString() + "不存在!");
        }

        List<String> filteredFileNames = filter(testRootDir, ".csv");
        System.out.println(filteredFileNames);
    }

    /**
     * 实现一个按照扩展名过滤文件的功能
     *
     * @param rootDirectory 要过滤的文件夹
     * @param extension     要过滤的文件扩展名,例如 .txt
     * @return 所有该文件夹(及其后代子文件夹中)匹配指定扩展名的文件的名字
     */
    public static List<String> filter(Path rootDirectory, String extension) throws IOException {
        List<String> names = new ArrayList<>();
        Files.walkFileTree(rootDirectory, new SimpleFileVisitor<Path>() {
            @Override
            public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
                System.out.println(file);
                if (file.getFileName().toString().endsWith(extension)) {
                    names.add(file.getFileName().toString());
                }
                return FileVisitResult.CONTINUE;
            }
        });
        return names;
    }
}

接口实战:Comparable接口

  • 可比较性,对象的年龄比较,Comparable就是可比较的意思
  • Comparable约定,自然顺序(从小到大),A<B返回-,否则反之
  • 本质上比较接口Comparable就是一种策略模式,排序函数sort完全不变,根据大小策略完成不同的功能

set集合的坑

  • 不想等的两个元素错误的判断成了相等,导致数据丢失
  • TreeSet通过Comparable接口比较两个元素是否相等,且不能容纳重复元素而compareTo返回0则表示相等,所以当我们用错误的compareTo时候TreeSet就丢弃了其中的一个,修复办法,哪怕名字相等也不能返回0。总结:compare to对于两个不同的元素,绝对不能返回0

接口与抽象类实战:实现一个通用的过滤器

  • 一个类里面所有方法都是抽象的,可以变成接口

JDK内置了判断是否是某个东西的接口,predicate

  • predicate可以理解成判定

内部类详解

  • 内部类和静态内部类的区别
  • 内部类的好处可以提供更加精细的控制,静态类就是在一个类内部的类,包裹它的类称为外围类
  • 面试经常会问 private class 内部类和 private static class的区别,private class会和外围类的实例绑定,隐式的调用this,而private static class不和外围类绑定,因此不能调用外围类的实例方法
  • 静态内部类强行调用外围类实例的案例
private class A {
    {
        log(null);
    }
}
private static class B {
    public B(Home home) {
        this.home = home;
    }
    Home home;
    {
        home.log(null);
    }
}

永远使用静态内部类,除非编译报错

  • 加static

匿名类

  • 可以定义在任何地方
  • 字节码会有1,1,2
  • 好处是和外围类非常紧密,直接访问外部作用域的变量,具体好处参考课后练习题把外部类变成匿名内部类的过程