Java的面向对象
19-抽象类
2021-07-06 564 1
简介 抽象类、抽象方法、接口、代理模式等相关介绍
1. 抽象类和抽象方法
随着继承层次中一个个新子类的定义,子类变得越来越具体,而父类则更一般更通用。类的设计应该保证父类和子类能够共享特征。有时将一个父类设计得非常抽象,以至于它没有具体的实例,这样的类叫做抽象类。
abstract关键字的使用
abstract英文翻译抽象的
abstract可以用来修饰的结构有类和方法
抽象类
abstract修饰类
此类不能实例化
抽象类中一定有构造器,子类实例化时调用(涉及:子类对象实例化的全过程)
开发中,都会提供抽象类的子类,让子类实例化,完成相关的操作
抽象方法
abstract修饰方法
抽象方法只有方法的声明,没有方法体(例如 public abstract void eat();)
包含抽象方法的类,一定是一个抽象类。反之,抽象类中可以没有抽象方法。
若子类重写了父类中的所有的抽象方法后,此子类方可实例化
若子类没有重写父类(包含间接父类)中的所有的抽象方法,则此子类需要声明为抽象类,需要使用abstract修饰。(类中的方法没有方法体,就不能实例化)
abstract使用上的注意点
abstract不能用来修饰:属性、构造器等结构
abstract不能用来修饰私有方法、静态方法、final的方法、final的类
私有方法不能再被重写(子类对父类的private方法不可见,不能重写,如果声明为抽象的,就无法重写了),与abstract冲突
abstract修饰的方法是希望子类被重写
静态方法:静态方法是属于类的,随着类加载而加载,类可以直接调用静态方法(如果声明为抽象的,就不能调用了,那就与静态方法矛盾了)
final的方法、final的类,
final与abstract功能完全是冲突的。final类不可再继承、final方法不可再重写,而abstract类希望被继承、abstract方法希望被重写。
//AbstractTest.java package com.ylaihui.oop11; abstract class Creature{ public abstract void breath(); } abstract class Person extends Creature{ String name; int age; Person(){} Person(String name, int age){ this.name = name; this.age = age; } public abstract void eat(); } class Student extends Person{ String major; Student(){} Student(String name, int age, String major){ super(name,age); this.major = major; } public void eat() { System.out.println("Student eat()..."); } public void breath() { System.out.println("Student breath()..."); } } public class AbstractTest { // Cannot instantiate the type Person // Person p = new Person(); Student s = new Student(); }
2. 抽象类的匿名子类
抽象类的匿名子类(有对象名的形式)
抽象类匿名子类的匿名对象(类名没有,对象名也没有的情况)
//OrderTest.java package com.ylaihui.oop11; abstract class Order{ public abstract void show(); } public class OrderTest { public static void method(Order o){ o.show(); } public static void main(String[] args) { // Cannot instantiate the type Order // Order o = new Order(); // Order o = new Order(){}; -- {} 中 提供 抽象类 Order 需要重写的方法 //创建了一匿名子类的对象:o Order o = new Order(){ @Override public void show() { System.out.println("this is book order."); } }; System.out.println(o.getClass()); System.out.println(o.getClass().getSuperclass()); // 多态, 形参是 Order类型,实参是 Order类的子类对象, Order类的子类是匿名的,对象名是 o method(o); //----------------------------------------- //创建匿名子类的匿名对象 //method(new Order()); // Cannot instantiate the type Order //method(new Order(){}); // {} 中提供需要重写的方法 method(new Order(){ @Override public void show() { System.out.println("this is computer order."); } }); } }
class com.ylaihui.oop11.OrderTest$1
class com.ylaihui.oop11.Order
this is book order.
this is computer order.
3. 模板方法设计模式
抽象类体现的就是一种模板模式的设计,抽象类作为多个子类的通用模板,子类在抽象类的基础上进行扩展、改造,但子类总体上会保留抽象类的行为方式。
解决的问题:
当功能内部一部分实现是确定的, 一部分实现是不确定的。这时可以把不确定的部分暴露出去,让子类去实现。
例如在软件开发中实现一个算法时,整体步骤很固定、通用,这些步骤已经在父类中写好了。但是某些部分易变,易变部分可以抽象出来,供不同子类实现。这就是一种模板模式。提供给你一个模板,你按模板“干活”
各个抽象类以及抽象方法就是模板, 子类需要提供具体的实现
模板方法设计模式是编程中经常用得到的模式。 各个框架、 类库中都有他的影子, 比如常见的有:
数据库访问的封装
Junit单元测试
JavaWeb的Servlet中关于doGet/doPost方法调用
Hibernate中模板程序
Spring中JDBCTemlate、 HibernateTemplate等
代码示例:
//TemplateTest.java package com.ylaihui.oop11; //抽象类的应用:模板方法的设计模式 public class TemplateMethodTest { public static void main(String[] args) { System.out.println("----customer A----"); BankTemplateMethod btm = new DrawMoney(); btm.process(); System.out.println("----customer B----"); BankTemplateMethod btm2 = new ManageMoney(); btm2.process(); } } abstract class BankTemplateMethod { // 具体方法 public void takeNumber() { System.out.println("取号排队"); } public abstract void transact(); // 办理具体的业务 //钩子方法 public void evaluate() { System.out.println("反馈评分"); } // 模板方法,把基本操作组合到一起,子类一般不能重写 public final void process() { this.takeNumber(); this.transact();// 像个钩子,具体执行时,挂哪个子类,就执行哪个子类的实现代码(回调方法) this.evaluate(); } } class DrawMoney extends BankTemplateMethod { public void transact() { System.out.println("我要取款!!!"); } } class ManageMoney extends BankTemplateMethod { public void transact() { System.out.println("我要理财!我这里有2000万美元!!"); } }
4. 接口(interface)
接口出现的背景
一方面, 有时必须从几个类中派生出一个子类, 继承它们所有的属性和方法。 但是, Java不支持多重继承。 有了接口, 就可以得到多重继承的效果。
另一方面, 有时必须从几个类中抽取出一些共同的行为特征,而它们之间又没有is-a的关系,仅仅是具有相同的行为特征而已。例如:鼠标、键盘、打印机、扫描仪、摄像头、充电器、 MP3机、手机、数码相机、移动硬盘等都支持USB连接。
关联知识解释:
is - a 衡量的是子父类的关系,比如学生继承人, 学生是一个人
继承是一个"是不是"的关系,而接口实现则是 "能不能"的关系。
has-a 表示组合,包含关系。比如兔子有腿、头等,不能说兔子腿是属于一种兔子(不能说是继承关系)
接口就是规范,定义的是一组规则,继承是一个"是不是"的关系,而接口实现则是 "能不能"的关系。
接口的本质是契约,标准,规范,就像我们的法律一样,制定好后大家都要遵守。
接口和类是并列关系
接口的使用
1.接口使用interface来定义
2.Java中,接口和类是并列的两个结构
3.接口中只能定义全局常量和抽象方法(JDK7及以前)
全局常量:public static final修饰的的,但是书写时,可以省略不写
抽象方法:public abstract修饰的
JDK8开始,接口中可以定义静态方法和默认方法
静态方法: 使用 static 关键字修饰。 可以通过接口直接调用静态方法,并执行其方法体。我们经常在相互一起使用的类中使用静态方法。可以在标准库中找到像Collection/Collections或者Path/Paths这样成对的接口和类。
默认方法: 默认方法使用 default 关键字修饰。可以通过实现类对象来调用。在已有的接口中提供新方法的同时,还保持了与旧版本代码的兼容性。
4. 接口中不能定义构造器的,意味着接口不可以实例化
5. Java开发中,接口通过让类去实现(implements)的方式来使用.
如果实现类覆盖了接口中的所有抽象方法,则此实现类就可以实例化
如果实现类没有覆盖接口中所有的抽象方法,则此实现类仍为一个抽象类
//InterfaceTest.java package com.ylaihui.oop12; interface Moveable{ public static final int MIN_SPEED = 0; int MAX_SPEED = 150; // 省略了 public static final public abstract void move(); void stop(); // 省略 public abstract // Interfaces cannot have constructors // Moveable(){ // // } } class Person implements Moveable{ @Override public void move() { System.out.println("Person walk..."); } @Override public void stop() { System.out.println("Person stop..."); } } // 只实现了接口中的部分抽象方法,那么本类要定义为 abstract abstract class Animal implements Moveable{ @Override public void move() { } } public class InterfaceTest { public static void main(String[] args) { System.out.println(Moveable.MAX_SPEED); System.out.println(Moveable.MIN_SPEED); // Moveable.MIN_SPEED = 100; // The final field Moveable.MIN_SPEED cannot be assigned Person person = new Person(); } }
5. 接口的多继承
1. 接口与接口之间可以继承,而且可以多继承
1. 接口的具体使用,体现多态性
3. 接口实际上可以看做是一种规范
* 面试题:抽象类与接口有哪些异同?
定义Java类的语法格式: 先写extends,后写implements
例如: class SubClass extends SuperClass implements InterfaceA, InterfaceB, InterfaceC{ }
public class LinkedList<E>
extends AbstractSequentialList<E>
implements List<E>, Deque<E>, Cloneable, java.io.Serializable
一个类可以实现多个接口, 接口也可以继承其它接口。
实现接口的类中必须提供接口中所有方法的具体实现内容,方可实例化。否则,仍为抽象类。
接口的主要用途就是被实现类实现。(面向接口编程)
与继承关系类似,接口与实现类之间存在多态性
接口和类是并列关系, 或者可以理解为一种特殊的类。 从本质上讲,接口是一种特殊的抽象类,这种抽象类中只包含常量和方法的定义(JDK7.0及之前), 没有变量和方法的实现。JDK8之后,接口中支持静态方法和默认方法,随着JDK版本的迭代,接口设计的越来越像类了。
代码示例:
类图
//USBTest.java package com.ylaihui.oop12; interface USB{ void start(); void stop(); } class FlashMemory implements USB{ @Override public void start() { System.out.println("flash memory start!"); } @Override public void stop() { System.out.println("flash memory stop!"); } } class Printer implements USB{ @Override public void start() { System.out.println("Printer stop!"); } @Override public void stop() { System.out.println("Printer stop!"); } } class Computer{ public void transdata(USB usb){ usb.start(); System.out.println("computer trans data..."); usb.stop(); } } public class USBTest { public static void main(String[] args) { Computer c = new Computer(); c.transdata(new Printer()); c.transdata(new FlashMemory()); } }
Printer stop!
computer trans data...
Printer stop!
flash memory start!
computer trans data...
flash memory stop!
6. JDK8中的接口-静态方法和默认方法
Java8中,可以为接口添加静态方法和默认方法。从技术角度来说,这是完全合法的,只是它看起来违反了接口作为一个抽象定义的理念。
接口中的静态方法: 使用 static 关键字修饰。 可以通过接口直接调用静态方法,并执行其方法体。我们经常在相互一起使用的类中使用静态方法。可以在标准库中找到像Collection/Collections或者Path/Paths这样成对的接口和类。
接口中的默认方法: 默认方法使用 default 关键字修饰。可以通过实现类对象来调用。在已有的接口中提供新方法的同时,还保持了与旧版本代码的兼容性。
比如: java 8 API中对Collection、 List、 Comparator等接口提供了丰富的默认方法。
接口中定义的静态方法,只能通过接口来调用,不能通过实现类或其对象来调用,说白了,接口中的静态方法不是给实现类用的
通过实现类的对象,可以调用接口中的默认方法
如果实现类重写了接口中的默认方法,调用时,仍然调用的是重写以后的方法
类优先原则:
如果子类(或实现类)继承的父类和实现的接口中声明了同名同参数的默认方法,那么子类在没有重写此方法的情况下,默认调用的是父类中的同名同参数的方法,而非调用接口中的方法。
接口冲突:
如果实现类实现了多个接口,而这多个接口中定义了同名同参数的默认方法,那么在实现类没有重写此方法的情况下报错。这就需要我们必须在实现类中重写此方法
---- Duplicate default methods named defaultMethod3 with the parameters () and () are inherited from the types InterfaceB and InterfaceA
如何在子类(或实现类)的方法中调用父类、接口中被重写的方法 --- 例如 CompareA.super.method3();
代码示例
//InterfaceA.java package com.ylaihui.oop13; public interface InterfaceA { // 静态方法 public static void staticMethod(){ System.out.println("interface staticMethod..."); } // 默认方法 public default void defaultMethod(){ System.out.println("interface defaultMethod..."); } public default void defaultMethod1(){ System.out.println("interface defaultMethod1..."); } public default void defaultMethod2(){ System.out.println("interface defaultMethod2..."); } public default void defaultMethod3(){ System.out.println("interface defaultMethod3..."); } }
//InterfaceB.java package com.ylaihui.oop13; public interface InterfaceB { public default void defaultMethod3(){ System.out.println("interface defaultMethod3..."); } }
//SuperClass.java package com.ylaihui.oop13; public class SuperClass { public void defaultMethod2(){ System.out.println("SuperClass defaultMethod..."); } }
//SubClassTest.java package com.ylaihui.oop13; class subClass extends SuperClass implements InterfaceA,InterfaceB{ public void defaultMethod1(){ System.out.println("subClass defaultMethod1..."); } // 如果实现类实现了多个接口,而这多个接口中定义了同名同参数的默认方法,那么在实现类没有重写此方法的情况下,报错。-->接口冲突。 // 这就需要我们必须在实现类中重写此方法 public void defaultMethod3() { System.out.println("subClass defaultMethod3..."); } public void Method(){ // 在子类(或实现类)的方法中调用父类、接口中被重写的方法 InterfaceA.super.defaultMethod3(); } } public class SubClassTest { public static void main(String[] args) { subClass sc = new subClass(); // 接口中定义的静态方法,只能通过接口来调用,不能通过实现类或其对象来调用 // sc.staticMethod(); // The method staticMethod() is undefined for the type subClass InterfaceA.staticMethod(); // 通过实现类的对象,可以调用接口中的默认方法 sc.defaultMethod(); // 如果实现类重写了接口中的默认方法,调用时,仍然调用的是重写以后的方法 sc.defaultMethod1(); // subClass defaultMethod1... // 如果子类(或实现类)继承的父类和实现的接口中声明了同名同参数的默认方法,那么子类在没有重写此方法的情况下, // 默认调用的是父类中的同名同参数的方法,而非调用的接口中的默认方法 sc.defaultMethod2(); // SuperClass defaultMethod... } }
代码输出
interface staticMethod...
interface defaultMethod...
subClass defaultMethod1...
SuperClass defaultMethod...