第15篇 抽象类与接口
1.抽象类格式 关键字: abstract
1.1抽象类:
abstract class 类名{} public abstract class ClassName{ int a; public abstract void fun(); }
-
除非该继承的子类也是抽象类,否则继承了抽象类的所有类都要重写抽象类的抽象方法
-
不能 new 一个 java 抽象类,有点像c++中的抽象类,即含纯虚函数的抽象类,需要子类完全重载(java中的重写)后才能生成实例
-
抽象类内可以有变量,变量写法与普通类一致
-
抽象类中有构造方法
1.2 抽象类内的抽象方法
修饰符 abstract 返回值类型 方法名(参数列表); public abstract void fun();//不能写函数体代码块
-
抽象类不一定有抽象方法,但是有抽象方法的类必须定义成抽象类
-
不包含抽象方法的抽象类,目的就是不想让调用者实例化该对象,通常用于某些特殊的类的结构设计
-
抽象方法的修饰符可以是 public 或者是 protected, 省缺情况下默认是 public
-
抽象类的构造方法能不能是抽象方法?
public abstract class Student { String name; abstract Student();//直接报错! } /*抽象类的构造方法不能是抽象方法 原因很简单:执行子类构造函数之前需要执行父类的构造函数,如果父类的构造函数可以是抽象方法的话,需要在子类重写,但是调用父类构造函数时,子类的构造函数尚未执行,哪来的重写? */
1.3 抽象类中静态方法的调用
- 直接 [抽象类].[静态方法] !!!
public abstract class Student { public static void fun(){}; public abstract void fun2(); } public class main { public static void main(String[] args) { Student.fun(); } }
2.接口
**接口的本质是规范,定义的是一组规则 **
-
类的关键字是class, 接口的关键字是 interface
class Person{}//类 interface Person{}//接口 -
接口内的所有方法都是抽象的,不能写函数体代码块
-
接口内的方法都是 (public abstract) 类型,不写的话编译器自动补全
-
接口与抽象类一样,其内同样可以定义变量
-
接口内定义的变量都是 (public static final) 类型的全局静态常量,所有比较少用
interface Person{//接口 public static final String name= “name”; void getName(); public abstract void getName() }
2.1 实现类
-
实现了接口的类简称实现类
-
接口都需要有实现类, 用法类似于类继承,把关键字 extends 换成 implements
public class [实现类名 = "接口名" + Impl] implements [接口名],[接口名]{} 例如: public interface Person{}//接口 public interface Man{}//接口 public class PersonImpl implements Person,Man{}//实现类
注意:
- 实现类还没有把继承的所有接口里的所有抽象方法全重写之前,编译器会一直报错,这是正常现象
- 正如上面格式中所写,一个实现类是可以继承多个接口的,也就是伪多继承!!!
- 接口的多继承,在 implements 后可跟多个接口名,用逗号隔开,且都不需要在接口名字面前写关键字interface
- 实现类推荐的命名方式为 对应接口名 + Impl
2.2 JDK8、JDK9的新变化
- JDK 8 之前接口只能定义抽象方法,JDK8中接口也可以定义默认方法 default 和静态方法 static。
- 默认方法允许接口提供默认实现,从而减少实现类的工作量。当接口的实现类没有提供该方法的具体实现时,将使用默认方法
- 静态方法可以为接口提供与接口相关的工具方法,静态方法可以直接通过接口名来调用,而不需要创建实现类的实例。因为静态方法无法被实现类覆盖或继承。此外,不能通过 实现类.[方法] 的形式调用静态方法,这点与 class继承 不同。
- JDK9可以定义私有方法 private。只能接口自己用,也就是被默认方法调用,所有相当于是JDK8的补充。
public interface MyInterface { default void fun1() { // 默认方法的实现代码,即实现代码是在接口中定义的 }//default 需要明写,因为不写默认是public static void fun2(){ //同上,实现代码是在接口中定义的 } } public class main { public static void main(String[] args) { MyInterface.fun2();//用接口名直接调用静态方法 //此处假设MyInterfaceImpl是MyInterface的实现类 MyInterfaceImpl.fun2();//报错,不能通过实现类调用父类接口的静态方法。只能通过接口名来调用。 } }
2.3 JDK8 默认方法冲突问题,(也叫接口冲突)
当一个实现类继承了多个接口(或者即继承类又继承接口),且接口(类)中有同名同参的默认方法时,实现类调用的是哪一个类的方法?冲突如何解决呢?
2.3.1 显式调用接口的方法
显式调用 某个接口的默认方法(注意是显式调用!!!不是必须重写!,在不重写的情况下显式调用就不报错!)。
格式:[接口名] . super . [默认方法名]
interface A { default void doSomething() { System.out.println("Do something in A"); } } interface B { default void doSomething() { System.out.println("Do something in B"); } } class MyClass implements A, B { @Override public void doSomething() { A.super.doSomething(); // 显式调用接口A的默认方法 } }
2.3.2 实现类重写
如果实现类中重写就相当于同时对所有同名同参的默认方法方法都重写了。调用时也是用的实现类自己的重写方法。
2.3.3 类优先原则
即继承了类又继承了接口,且有同名同参的方法。在子类没有重写方法的情况下,遵从类优先原则,实现类调用父类的方法。
interface A { default void doSomething() { System.out.println("Do something in A"); } } Class B { void doSomething() {//注意此处没有default System.out.println("Do something in B"); } } class MyClass extends class B implements A { //此时即继承了类又继承了接口。且有同名同参的方法 doSomething();//调用类B的方法 }
2.3.4 JDK8 接口冲突的总结
注意这里 父类、与父类接口有区别,父类是指class,父类接口指 interface。
- 问:子类即继承了类又继承了接口,且有同名同参方法。直接以方法名的形式,调用的是哪个方法呢?
- 子类若是重写了,则调用子类的方法;
- 若是子类没重写,则调用父类的方法(类优先原则:无论有多少个父类接口有同名同参方法,默认调用父类方法);
- 若是子类没重写,且单个父类接口有同名同参方法时,与父类的操作逻辑一致。
- 多个父类接口都有同名同参方法时,不会调用接口的方法,直接报错;
- 问:子类、继承的父类与接口中都有同名同参方法,想要直接调用各自的方法又如何写呢?
interface A { default void doSomething() {//注意此处是default,可以有代码块 System.out.println("Do something in interface A"); } } interface B { default void doSomething() {//注意此处是default,可以有代码块 System.out.println("Do something in interface B"); } } class SuperC { void doSomething() {//注意此处没有default System.out.println("Do something in class SuperC"); } } class C extends class SuperC implements A,B { void doSomething() {//注意此处没有default System.out.println("Do something in class C"); } //此时C即继承了类SuperC又继承了接口A,B。且有同名同参的方法. void fun(){ doSomething();//调用C自己的同名同参的方法 super.doSomething();//调用父类SuperC的同名同参的方法 A.super.doSomething();//调用指定父类接口A的同名同参的方法 } }
3.抽象类与接口的异同
异:
- 接口中没有构造方法,抽象类中有构造方法
- 接口的关键字为interface 和 implements,抽象类的关键字为 class 和 extends
- 接口可以多继承,抽象类只有单继承
- 接口中的方法必须全是抽象方法,抽象类中可以有非抽象方法(此条不适用于 JDK8及其往后的版本)
- 接口中的变量都是 public static final 类型(全局静态常量),抽象类没有要求
- 接口的抽象方法一定是 public abstract, 抽象类的抽象方法可以是public abstract 或者是 protected abstract
- 接口的抽象方法省缺时默认为 public abstract, 抽象类的抽象方法只可以省缺public ,因为省缺默认是public, 而 abstract 必须写,不写就不是抽象方法了。
同:
- 接口和抽象类的抽象函数都是用abstract
- 接口和抽象类都是为了定义一些约束
- 接口和抽象类都不能直接实例化,只有继承其的子类(接口为实现类)在完全重写抽象方法后,才能生成子类实例
**补充:**
4.函数式接口(可以用lambda 表达式简化)
-
定义:只包含一个抽象方法(可以包含其他非抽象方法)的接口叫做函数式接口。正如上面所说,JDK8之后接口也可以定义默认方法和静态方法。
注意:
- 可以用 lambda 表达式进行创建该接口的实现类。注意若lambda表达式抛出一个非运行时异常,那么该异常须在该接口的抽象方法上声明。
- 与@Override 注解的作用类似,Java 8中专门为函数式接口引入了一个新的注解:@FunctionalInterface 。该注解可用于一个接口的定义上,一旦使用该注解来定义接口,编译器将会强制检查该接口是否确实有且仅有一个抽象方法(equal和hashcode方法不算),否则将会报错。使用@FunctionalInterface 同时 javadoc 也会包含一条声明,说明这个接口是一个函数式接口。但是这个注解不是必须的,只要符合函数式接口的定义,那么这个接口就是函数式接口。
-
可以用lambda 表达式简化实例化函数式接口
- 函数式接口适用于函数式编程的场景,Lambda就是Java中函数式编程的体现,可以使用Lambda表达式创建一个函数式接口的对象,一定要确保接口中有且只有一个抽象方法,这样Lambda才能顺利的进行推导。
5. Lambda表达式
- 定义:lambda表达式即函数式编程,也被称为闭包,它将允许把函数作为一个方法的参数;即行为参数化,把函数作为参数传递进方法中。
- Java 8提出了lambda表达式,在Java 8之前,如果想将行为传入函数,仅有的选择就是匿名类。
- Lambda表达式可以取代大部分的匿名内部类
- Lambda 表达式类似于方法,但它们不需要名称,可以有 return 返回值
lambda表达式格式: (params) -> expression (params) -> statement (params) -> { statements }
// Java 8之前: new Thread(new Runnable() { @Override public void run() { System.out.println("A lot code to write!"); } }).start(); //Java 8方式: new Thread( () -> System.out.println("write in one line!!!") ).start();
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享4款.NET开源、免费、实用的商城系统
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
· 上周热点回顾(2.24-3.2)