java: “abstract 抽象类” 与 “ interface 接口” 的妙用之道
每博一文案
有句很扎心的话,我对这世间唯一的不满就是这世间总是让更懂事的人承受的更多。
生活中,往往你越善解人意,就越没人在乎你的委屈,时间,让你学会了坚强,经历,让你学会了
成长,年龄,让你学会了迁就。
你不计较,你不动怒,你让步,你宽容,人生中充满了各种破事,你说最多的就是没事,然而有些人,却习惯了
这样的你,一次次的得寸进尺,丝毫不在乎你的感受,正如此生未完成所说:三角恋里每一次都是哪个主动波汗,
苦苦纠缠,步步相逼获胜,哪怕你有那么一点点心软,不忍看着,他左右为难,腹背受敌,那么兵败的终归是你。
有些人轻轻挥手就什么都有,而有些人拼尽了全力,却还是一无所有,会哭的孩子,有糖吃,不会哭的孩子,只能冷暖自知,其实你
不必那么懂事。一定要学会摆脱他人的期待,不再习惯取悦他人,找到真正的自己。
一路走来,跨过荆棘,是为了遇见春暖花开,而不是为了,去将就一个或是凑合一段生活,别人最
懂事的人受最多的委屈。
—————— 一禅心灵庙语
文章目录
- java: “abstract 抽象类” 与 “ interface 接口” 的妙用之道
- 每博一文案
- 1. 抽象类
- 1.2 抽象方法
- 2. 多态应用: 模板方法设计模式
- 3. 接口
- 3.1 JDK7 之前的接口特性
- 3.2 JDK8 之后的接口特性
- 4. 接口的应用:代理模式 (Proxy)
- 5. 面试题:
- 6. 总结:
- 7. 最后:
1. 抽象类
随着继承层次中一个个新子类的定义,类变得越来越具体,而父类则更一般,更通用。类的设计应该保证父类和子类能够共享特征,有时将一个父类设计得非常抽象,以至于它没有具体的实例,这样的类叫做抽象类。
用 abstract
关键字来修饰一个类,这个类叫做抽象类。
abstract
可以用来修饰 类,方法 。
abstract class Person{
}
- 抽象类是不可以 实例化对象的
new
。虽然我们的抽象类是不能 new ,但是却是必须要构造器(构造方法的)。
因为抽象类是用来 继承 使用的,而继承就涉及到了 子类实例化的全过程 :子类继承父类(抽象类)则就必须调用父类中的构造器,所以我们的抽象类是必须要有构造器的。这一点需要注意的。
我们为父类(抽象类)附加上 空参的构造器 就可以了,让子类实例化对象,有父类的无参构造器调用
1.2 抽象方法
被abstract
修饰的方法叫做 “抽象方法”。
abstract class Person{
// 抽象方法
abstract public void show();
}
抽象方法:没有方法体,只有方法的声明。
普通的类中是不能定义抽象方法的,抽象类中才可以定义抽象方法。所以就有了这么一句话:包含抽象方法的类,一定是抽象类(注意是类,除了接口) ,反之抽象类不一定就 有抽象方法。 因为抽象类中也可以没有抽象方法。
- 抽象类的子类必须重写 对应父类(抽象类)中所有 的抽象方法(注意是所有的不是单独的其中一个),若不重写其中的抽象方法,编译无法通过。若子类没有重写父类中的所有的抽象方法,则子类也是一个抽象类,需要使用
abstract
修饰类
或者该子类也定义为抽象类
abstract
修饰符的注意事项:
- abstract 在抽象类中不能用来,修饰: 变量,代码块,构造器的,因为对应变量,代码块,构造器这些都无法通过继承重写的。
- abstract 在抽象类中不能用来修饰:静态方法,private 修饰的方法,final 修饰的方法这些都无法继承来重写的。
2. 多态应用: 模板方法设计模式
抽象类体现的就是一种模板模式 的设计,抽象类作为多个子类的通用模板,子类在抽象类的基础上进行扩展,改造,但子类总体上会保留抽象类的行为方式。
解决问题的思路:
当功能内部一部分实现是确定的,一部分实现是不确定的。这时可以把不确定的部分暴露出去,让子类去实现,换句话说,在软件开发中实现一个算法时,整体步骤很固定,通用,这些步骤已经在父类中写好了。但是某些部分易变,易变部分可以抽象出来,供不同子类实现。这就是一种 模板模式 。
举例具体代码如下:
public class TemplateTest {
public static void main(String[] args) {
Bank b1 = new DrawMoneys();
b1.process(); // 多态:动态绑定;调用子类重写的方法transactBusiness()
System.out.println("**********************");
Bank b2 = new ManageMoneys(); // 多态
b2.process(); // 多态调用的是子类重写的方法transactBusiness()
}
}
abstract class Bank{
public void drawNumber(){
System.out.println("取号排队");
}
// 抽象方法: 不确定处理怎样的业务需求:让子类继承实现
abstract public void transactBusiness();
public void end(){
System.out.println("最后:业务评价");
}
// 重点
// final 无法被重写,最后将所有操作整合在一起处理,
public final void process() {
this.drawNumber(); // 取号
// 重点,实际调用的是: 子类重写的方法,多态
this.transactBusiness(); // 对应的业务处理,执行子类中重写的抽象方法
this.end(); // 最后业务评价
}
}
// 取钱,操作
class DrawMoneys extends Bank{
@Override
// 重写继承的抽象类的抽象方法(无法确定的部分)
public void transactBusiness(){
System.out.println("业务处理:取钱");
}
}
// 存钱操作。
class ManageMoneys extends Bank{
@Override
// 重写所继承的抽象类的抽象方法(无法确定的部分)
public void transactBusiness() {
System.out.println("业务处理: 存钱");
}
}
3. 接口
一方面,有时必须从几个类中派生出一个子类,继承它们所有的属性和方法。但是,Java不支持多重继承。有了接口,就可以得到多重继承的效果。
另一方面,有时必须从几个类中抽取出一些共同的行为特征,而它们之间又没有is-a的关系,仅仅是具有相同的行为特征而已。例如:鼠标、键盘、打印机、扫描仪、摄像头、充电器、MP3机、手机、数码相机、移动硬盘等都支持USB连接。
接口就是规范,定义的是一组规则,体现了现实世界中“如果你是/要…则必须能…”的思想。继承是一个"是不是"的关系,而接口实现则是"能不能"的关系。
接口的本质是契约,标准,规范,就像我们的法律一样。制定好后大家都要遵守。
接口使用 interface
关键字定义修饰类
interface Flyable{
}
Java中,接口 和 类 是并列的两个结构。
3.1 JDK7 之前的接口特性
-
接口是一种特殊的抽象类,这种抽象类中只包含常量和抽象方法的定义(JDK7.0及之前),而没有变量和方法的实现。
-
接口中的所有成员变量都默认是由
public static final
修饰的,对于编译器而言一般都是会省略的不写的。因为是被 final 修饰的所以是无法修改其中的变量的值的。被称为常量。
package blogs.blog3;
public class InterfaceTest {
public static void main(String[] args) {
System.out.println(Flyable.MAX_SPEED);
// 可以直接通过类名.的方式直接访问,省略的是 public static final;
System.out.println(Flyable.Min_SPEED);
}
}
interface Flyable{
public static final int MAX_SPEED = 7900;
int Min_SPEED = 1; // 默认省略了 public static final
}
- 接口中的所有抽象方法都默认是由
public abstract
修饰的,不过一般是会被省略不写的,但是默认是会附加上的
interface Flyable{
public static final int MAX_SPEED = 7900;
int Min_SPEED = 1; // 默认省略了 public static final
// 抽象方法
public abstract void fly();
// 省略了 public abstract
void stop();
}
class Plane implements Flyable {
@Override
public void fly() {
}
@Override
public void stop() {
}
}
- 接口是无需要 构造器(构造方法的),因为接口是不能 实例对象的 new 的,接口 与 类的关系是 :实现关系
implements
, 接口与接口之间的关系是 继承关系。
- Java开发中:接口通过让类去实现
implements
的方式来使用:
如果对应实现类,必须重写接口中所有的抽象方法(abstract) 默认省略的,不然编译报错;如果没有重写接口中的抽象方法,则将该类也定义为 接口 interface 才行。
- 接口 与 类的关系是 :
implements
实现关系- 一个接口可以被多个类 implements 实现
- 一个类可以 implements 实现多个 接口
interface AA{
// 抽象方法
public abstract void funAA();
}
interface BB{
// 抽象方法
public abstract void funBB();
}
// 一个类实现多个接口
class CC implements AA,BB{
@Override
public void funAA() {
}
@Override
public void funBB() {
}
}
- 接口 与 接口 之间是 多继承关系
interface AA{
// 抽象方法
public abstract void funAA();
}
interface BB{
// 抽象方法
public abstract void funBB();
}
// 接口与接口之间是: 多继承关系
interface DD extends AA,BB{
}
3.2 JDK8 之后的接口特性
Java 8中,你可以为接口添加静态方法和默认方法。从技术角度来说,这是完全合法的,只是它看起来违反了接口作为一个抽象定义的理念。
接口中的静态方法:
使用 static
关键字修饰,可以通过 “接口名.方法名” 的方式直接调用静态方法(因为是静态方法),并执行其方法体我们经常在相互一起使用的类中使用静态方发,你可以在标准库中找到像Collection/Collections或者 Path/Paths这样成对的接口和类。
interface CompareA{
// 接口中定义:静态方法
public static void method1(){
System.out.println("接口中的静态方法 method1");
}
}
package blogs.blog3;
public class InterfaceJDK8 {
public static void main(String[] args) {
// 调用接口中静态方法: 直接接口名.方法
CompareA.method1();
}
}
interface CompareA{
// 接口中定义:静态方法
public static void method1(){
System.out.println("接口中的静态方法 method1");
}
}
接口中的静态方法,不可以被重写,只能被接口自己是使用,通过,“接口名.方法名”调用。因为不是抽象方法,所以对于实现类来说是不需要重写的,也无法重写。
package blogs.blog3;
public class InterfaceJDK8 {
public static void main(String[] args) {
// 调用接口中静态方法
CompareA.method1();
SubClass subClass = new SubClass();
subClass.method1(); // jdk8中接口中定义的静态方法无法 实例对象调用,只能接口名.方法名的方式调用
}
}
interface CompareA{
// 接口中定义:静态方法
public static void method1(){
System.out.println("接口中的静态方法 method1");
}
}
class SubClass implements CompareA{
}
接口中的默认方法:
使用 default
关键字修饰,可以通过实现类对象来调用,我们在已有的接口中提供,新方法的同时保持了与旧版本代码的兼容性,比如:Java 8 API 中对 Collection, List, Comparator 等接口提供了丰富的默认方法。
接口中的默认方法的调用:通过实例化 new 实现类的对象。再通过对象的方式调用其中的默认的方法.
同样因为接口不是抽象方法,所以没有一定要重写的要求。
interface CompareA{
// 接口中定义: 默认方法
default void method2(){
System.out.println("接口中的默认方法");
}
}
package blogs.blog3;
public class InterfaceJDK8 {
public static void main(String[] args) {
SubClass subClass = new SubClass();
subClass.method2(); // 调用接口中的默认方法
}
}
interface CompareA{
// 接口中定义: 默认方法
default void method2(){
System.out.println("接口中的默认方法");
}
// 接口中定义:静态方法
public static void method1(){
System.out.println("接口中的静态方法 method1");
}
}
class SubClass implements CompareA{
}
接口中的默认方法:是可以重写的,重写默认方法和多态性中的动态绑定是一样的。实现运行的是调用重写以后的方法。
package blogs.blog3;
public class InterfaceJDK8 {
public static void main(String[] args) {
SubClass subClass = new SubClass();
subClass.method2(); // 调用接口中的默认方法
}
}
interface CompareA{
// 接口中定义: 默认方法
default void method2(){
System.out.println("接口中的默认方法");
}
// 接口中定义:静态方法
public static void method1(){
System.out.println("接口中的静态方法 method1");
}
}
class SubClass implements CompareA{
// 重写接口中的默认方法
@Override
public void method2(){
System.out.println("实现类重写的默认方法");
}
}
如果接口中和继承的父类中含有同名同参数的方法,而实现类又没有重写其中 同名同参数的方法的情况下,默认调用的是继承类中类父类中的方法,不是接口中的方法——> 类优先,如果重写了就是子类中的方法先调用
寻找的属性和方法的顺序是: 1.本类,2.父类,3.接口
package blogs.blog3;
public class InterfaceJDK8 {
public static void main(String[] args) {
SubClass subClass = new SubClass();
subClass.method2(); // 调用接口中的默认方法
}
}
interface CompareA{
// 接口中定义: 默认方法
default void method2(){
System.out.println("接口中的默认方法:同名同参数的方法");
}
// 接口中定义:静态方法
public static void method1(){
System.out.println("接口中的静态方法 method1");
}
}
class SuperClass{
public void method2(){
System.out.println("SuperClass 父类中的 同名同参数的方法");
}
}
class SubClass extends SuperClass implements CompareA{
// 重写接口中的默认方法
@Override
public void method2(){
System.out.println("实现类重写的默认方法,本类中的:同名同参数的方法");
}
}
在这里插入图片描述
注释掉:本类中重写的方法:重新运行看看。
如果实现类实现了多个接口,而这多个接口又定义同名同参数的默认方法:那么在实现类上,如果没有重写此(同名同参数同方法名)的情况下,是会报错的。:接口冲突。注意这是在 同名同参数同方法的接口中的 默认方法 才会,抽象方法是不会的。
解决方式:本实现类中重写其中的默认方法。
interface CompareA{
// 接口中定义: 默认方法
default void method2(){
System.out.println("CompareA接口中的: 默认方法");
}
// 接口中定义:静态方法
public static void method1(){
System.out.println("接口中的静态方法 method1");
}
}
interface CompareB{
// JDK8 接口: 默认方法
default void method2() {
System.out.println("CompareB: 接口中的默认方法");
}
}
class SubClass implements CompareA,CompareB{
}
解决方式:重写其中默认方法
interface CompareA{
// 接口中定义: 默认方法
default void method2(){
System.out.println("CompareA接口中的: 默认方法");
}
// 接口中定义:静态方法
public static void method1(){
System.out.println("接口中的静态方法 method1");
}
}
interface CompareB{
// JDK8 接口: 默认方法
default void method2() {
System.out.println("CompareB: 接口中的默认方法");
}
}
class SubClass implements CompareA,CompareB{
@Override
public void method2() {
System.out.println("实例类中重写的默认方法");
}
}
4. 接口的应用:代理模式 (Proxy)
代理模式是Java开发中使用较多的一种设计模式。代理设计就是为其他对象提供一种代理以控制对这个对象的访问。
应该场景:
- 安全代理: 屏蔽对真实角色的直接访问。
- 远程代理: 通过代理类处理远程方法调用 RMI
- 延迟加载: 先加载轻量级的代理对象,真正需要再加载真实对象
比如你要开发一个大文档查看软件,大文档中由大的图片,有可能一个图片有 100MB,在打开文件时,不可能将所有的图片都显示出来,这样就可以使用代理模式,当需要查看图片时,用 proxy
来进行大图片的打开。
- 分类
静态代理(静态定义代理类)
动态代理(动态生成代理类)
JDK自带的动态代理,需要反射等知识
具体实现代码如下:
package day16;
// 代理模式
public class StaticProxyTest {
public static void main(String[] args) {
Star s = new Proxy(new RealStar());
s.confer();
s.signContract();
s.bookTicket();
s.sing();
s.collectMoney();
}
}
interface Star {
void confer();// 面谈
void signContract();// 签合同
void bookTicket();// 订票
void sing();// 唱歌
void collectMoney();// 收钱
}
class RealStar implements Star {
public void confer() {
}
public void signContract() {
}
public void bookTicket() {
}
public void sing() {
System.out.println("明星:歌唱~~~");
}
public void collectMoney() {
}
}
class Proxy implements Star {
private Star real;
public Proxy(Star real) {
this.real = real;
}
public void confer() {
System.out.println("经纪人面谈");
}
public void signContract() {
System.out.println("经纪人签合同");
}
public void bookTicket() {
System.out.println("经纪人订票");
}
public void sing() {
// 调用的是: 对应 Star 多态重写的方法
real.sing();
}
public void collectMoney() {
System.out.println("经纪人收钱");
}
}
5. 面试题:
如下代码是否存在编译错误:
package blogs.blog3;
public class InterviewTest {
public static void main(String[] args) {
new C().px();
}
}
interface A{
int x = 0;
}
class B{
int x = 1;
}
class C extends B implements A{
public void px(){
System.out.println(x);
}
}
解析:
System.out.println(x); 其中的 x 变量是不明确的,因为该变量名 x ,对于 C 类来说,其中的父类 B 也有,其中的实现接口 A 其中也有 x,对于这两个 父类,接口中的同名变量,存在歧义,其中的编译器无法识别其要调用的是,哪个变量。所以报错。
解决方法:明确其中调用的是那边的变量即可,如下
package blogs.blog3; public class InterviewTest { public static void main(String[] args) { new C().px(); } } interface A{ int x = 0; } class B{ int x = 1; } class C extends B implements A{ public void px(){ System.out.println(super.x); // 调用父类中的变量 x System.out.println(A.x); // 调用接口中的变量 x ,因为接口中的变量默认是被public static final修饰的 } }
观察如下代码:是否存在错误
package blogs.blog3;
public class InterviewTest {
}
interface Playable{
public abstract void play();
}
interface Bounceable{
public abstract void play();
}
interface Rollable extends Playable,Bounceable {
Ball ball = new Ball("Pingpang");
}
class Ball implements Rollable {
private String name;
public Ball(String name) {
this.name = name;
}
public String getName() {
return name;
}
@Override
public void play() {
ball = new Ball("Football");
}
}
解析:
ball = new Ball(“Football”); 报错原因是: 因为对应的接口 Rollable 中已经定义了 Ball ball = new Ball(“Pingpang”); 全局变量,对于
接口中的属性,默认是被 public static final 修饰的,其中被 final 修饰的变量是无法被修改的,而这里你想要重新修改赋值的变量是 ball 同名的无法修改。
一个类继承了 类 ,实现了接口 , 重写了 func() 方法,父类,接口,本类中都有 func() 方法,如何调用执行其中对应父类,接口中的 func()方法
public class InterviewTest {
public static void main(String[] args) {
Students students = new Students();
students.play();
}
public static void main2(String[] args) {
new C().px();
}
}
interface People{
// 接口中的默认方法
default void func() {
System.out.println("People 接口中的默认方法 func()");
}
// 接口中的静态方法
public static void method(){
System.out.println("People接口中的静态方法");
}
}
class Persons{
public void func(){
System.out.println("父类 Persons中的 func()方法");
}
}
class Students extends Persons implements People{
@Override
public void func() {
System.out.println("本类中重写的 func()方法");
}
public void play(){
this.func(); // 调用执行本类中的 func()重写的方法
super.func(); // 调用执行父类中的 func()方法
People.super.func(); // 调用接口中的默认方法 func()
People.method(); // 调用接口中的静态方法 method();
}
}
6. 总结:
-
abstract
可以用来修饰 类,方法 -
抽象类不可以 实例化 new 对象,但必须要有构造器(构造方法) 用于继承中的子类的实例化全过程调用父类中的构造器。
-
abstract 不能修饰 属性,代码块,构造器,因为无法重写
-
abstract 不能修饰: private 修饰的方法,final修饰的方法,final 修饰的类,静态方法,都是无法重写的。
-
抽象类中的抽象方法必须被所
extends
继承的类全部重写,不然编译无法通过。 -
接口中的抽象方法必须被
implements
实现类全部重写,不然编译无法通过 -
接口 JDK7 之前的接口:只包含常量和方法的定义
- 接口中的所有成员变量都默认是由public static final修饰的
- 接口中的所有抽象方法都默认是由public abstract修饰的。
- 接口中没有构造器,也不能有构造器,接口不能实例化 new 对象
- 接口可以多继承。接口与类之间是 :
implements
实现关系,接口与接口之间是 :extends 继承关系
-
接口 JDK8 之后的接口:
- 接口中可以定义静态方法 被 static 修饰的方法,接口中的静态方法,只能接口中自己使用,无法重写,无法继承,访问方式是 “接口名.方法名”
- 接口中可以定义默认方法 使用
default
关键字修饰,可以通过实现类对象来调用 ”实例对象.方法名“,默认方法可以被重写。
-
接口的主要用途就是被实现类实现。(面向接口编程)
-
接口的本质是契约,标准,规范,就像我们的法律一样。制定好后大家都要遵守。
-
定义类的语法格式是:先写 extends ,后写 implements
class SubClass extends SuperClass implements InterfaceA{ }
7. 最后:
限于自身水平,其中存在的错误,希望大家给予指教,韩信点兵——多多益善,谢谢大家,后会有期,江湖再见!!!