Abstract约定
- 把公用的事情抽取出来变成一个骨架,Abstract代表一个骨架
- JDK中有很多Abstract类,比如AbstractHashMap之类的类
如何强制用户覆盖某个方法,但是不给默认方法
- 1.抛出异常,但这是运行时的,编译通过
public void doSomething() {
throw new UnsupportedOperationException();
}
- 2.使用抽象类,这样子整个类就要变成抽象类
public abstract void doSomething();
抽象类不能被实例化
- 抽象类用abstract去声明,除此之外它和其他类没有本质的区别
- 好处是如果别人忘了覆盖抽象类的方法,会报错
- 加入允许它能实例化,它里面没有方法的可以执行体,换句话而言,可以实例化的东西一定要补全所有的方法
- 如果把所有的方法都去掉,抽象类就变成了接口
- 抽象类可以包含抽象方法,可以包含成员变量
接口
- 接口不是类,它只代表一种功能
- 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("鸟儿飞");
}
}
- 继承体系图
- 鸟出了继承动物之外还有另一套继承体系,会飞,一个是蓝线一个是绿虚线
- 接口意味描述有这个功能,但是不给出具体的实现
- 一个东西必须是一个东西,但是可以有很多功能
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 {
}
-
接口里面的成员变量
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);
}
}
- 引入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
匿名类
- 可以定义在任何地方
- 字节码会有2
- 好处是和外围类非常紧密,直接访问外部作用域的变量,具体好处参考课后练习题把外部类变成匿名内部类的过程