抽象类
面向对象编程的核心是面向抽象编程,一般依赖抽象不依赖具体
public class A{
public void pp(Pig pig){}
}
//-------------
public class A{
//如果进行切换类型,则必须进行修改源代码
public void pp(Animal animal){} //这里可以任意更换Animal的子类
}
class Animal{}
class Pig extends Animal{}
class Cat extends Animal{}
public class 游戏 {
public void 玩() {
//定义父类时,有些方法必须声明,因为不做声明则无法正常调用
//但是在游戏类中由于具体不够明确,所以没有办法具体定义
游戏 a=new 篮球();
}
}
包含了抽象方法的类叫作“抽象类”
所谓的抽象方法是指没有函数体的方法,抽象方法必须在子类中给出具体实现,而抽象类本身不能创建对象
public abstract class 游戏 { //如果一个类中直接或者间接的包含抽象方法,则类必须为抽象类
public abstract void 玩();//定义抽象方法,
//【public abstract void 玩(){}】语法错误,因为{}不是表示没有实现,而是有具体的实现,支持实现为空
}
抽象类的特点
方法只有声明没有实现时,该方法就是抽象方法【不是空实现方法体,没有{}】,需要被abstract修饰,否则语法报错。抽象方法必须定义在抽象类中,该类必须也被abstract修饰
public abstract class 游戏 {
//如果一个类中直接或者间接的包含抽象方法,则类必须为抽象类
public abstract void 玩();
//定义抽象方法,【public abstract void 玩(){}】语法错误,
//因为{}不是表示没有实现,而是有具体的实现,支持实现为空
}
- public void pp(){}不是抽象方法,只是方法的实现为空,有方法体
- public void pp(); 没有{}才是没有方法体,才是抽象方法,当然需要添加关键字abstract
不能定义抽象构造函数
- 抽象类中可以定义构造器,也可以不定义构造器,使用系统默认提供的无参构造器,但是自定义构造器不能private
- 抽象类不能是 nal class, nal类不能被继承,从语法的角度上说不允许继承,不是构造器的原因。
不能定义抽象静态方法
- 抽象类中可以有静态方法,但是必须有方法体,不能是抽象方法
- 允许抽象类直接调用静态方法
抽象类不能直接创建对象,只能通过继承的方式由子类实现对应的抽象方法;
- 一般的使用方法为【动物 x=new 人();】
所有抽象类的子类必须实现抽象父类中的所有抽象方法或者自己也声明成抽象类[没有实现所有的抽象方法]
抽象类除了可以有抽象方法,也可以有非抽象方法,允许静态方法【没有抽象静态方法的概念】
- 没有任何限制,允许属性、方法,也允许抽象方法
抽象类不可以被实例化
抽象类必须有其子类覆盖了所有的抽象方法后,该子类才可以实例化,否则这个子类还是抽象类
(抽象类中可以包含构造器、析构器、抽象方法和方法以及静态方法等,也可以没有抽象方法)
什么时候使用抽象类
- 当一个类的一个或多个方法为抽象方法时
- 当该类为一个抽象类的子类,并且没有为所有抽象方法提供实现细节或方法主体时
- 当一个类实现一个接口,并且没有为所有抽象方法提供实现细节或方法主体时
抽象类和普通类的区别
-
抽象类不能直接实例化,并且对抽象类使用 new 运算符是编译时错误
-
抽象类允许(但不要求)抽象类包含抽象成员
public abstract class A{
private int age; // 可以定义属性、常量
public void pp(){}//允许定义非抽象方法,允许定义静态方法,没有抽象构造器的概念
public abstract int pp(int k); //抽象方法,抽象类中可以定义抽象方法,也可以没有抽象方法
}
抽象类不能被密封
- 简单说就是被 nal修饰的类,密封类不能被继承,防止了恶意的派生
抽象类的作用
在面向对象方法中,抽象类主要用来进行类型隐藏。构造出一个固定的一组行为的抽象描述,但是这组行为却能够有任意个可能的具体实现方式。这个抽象描述就是抽象类,而这一组任意个可能的具体实现则表现为所有可能的派生类。模块可以操作一个抽象体。由于模块依赖于一个固定的抽象体,因此它可以是不允许修改的
通过从这个抽象体派生,也可扩展此模块的行为功能。为了能够实现面向对象设计的一个最核心的原则开闭原则OCP,抽象类是其中的关键所在
抽象类往往用来表征对问题领域进行分析、设计中得出的抽象概念,是对一系列看上去不同,但是本质上相同的具体概念的抽象
OOP的核心是面向抽象编程
- 在父类中固化算法骨架,在子类中实现特殊的方法
- 依赖抽象
模板模式
定义抽象类的目的是提供可由其子类共享的一般形式,子类可以根据自身需要扩展抽象类
什么是模板模式
在模板模式Template Pattern中一个抽象类公开定义了总体的算法【算法骨架】,把没有办法在父类中实现的方法
延迟到子类中具体实现。这种类型的设计模式属于行为型模式
何时使用
有多个子类共有的方法,且逻辑相同
重要的、复杂的方法,可以考虑作为模板方法
(为防止恶意操作,一般模板方法都加上 nal 关键词)
优点
封装不变部分,扩展可变部分
提取公共代码,便于维护
行为由父类控制,子类实现。
缺点
每一个不同的实现都需要一个子类来实现,导致类的个数增加,使得系统更加庞大。
分析
冒泡排序算法固定可以定义在父类中,但是两个比较的算法不同确定,所以可以将具体比较算法延迟到子类中实现。抽象父类中定义算法模板,具体的比较延迟到子类中进行实现
public abstract class BubbleSorter {
//在父类中固化算法实现,没有办法实现的比较方法延迟到子类中提供实现
public final void sort(Object[] arr){
for(int i=1;i<arr.length;i++){
//final表示这个方法是最终方法,不允许子类覆盖
for(int k=0;k<arr.length-i;k++){
if(bigger(arr[k], arr[k+1])){
Object temp=arr[k];
arr[k]=arr[k+1];
9
arr[k+1]=temp;
}
}
}
}
//定义protected目的在于子类中提供实现
protected abstract boolean bigger(Object obj1,Object obj2);
}
具体的比较器,抽象父类的目的在于固化算法实现 nal
//专门用于对int类型数据进行冒泡排序
public class IntSorter extends BubbleSorter {
@Override
protected boolean bigger(Object obj1, Object obj2) {
if(obj1!=null && obj2!=null) {
if(obj1 instanceof Integer && obj2 instanceof Integer) {
//不用简单写法
int k1=(Integer)obj1;
int k2=(Integer)obj2;
return k1>k2;
}
}
return false;
}
}
测试调用
public class A {
public static void main(String[] args) {
Object[] arr=new Integer[10];
Random r=new Random();
for(int i=0;i<arr.length;i++)
arr[i]=r.nextInt(100);
for(Object temp:arr)
System.out.print(temp+"\t");
System.out.println();
IntSorter is=new IntSorter();//是否可以使用BubbleSorter定义is类型? 可以。抽象类只是不允许直接new,但是仍旧可以用于声明变量类型。例如前面一直使用的Number就是抽象类
is.sort(arr);
for(Object temp:arr)
System.out.print(temp+"\t");
System.out.println();
}
}
类和类之间的耦合问题
OOP要求类内高内聚、类间弱耦合—客户需求变动
如何实现类和类之间松耦合---使用抽象不用具体
public class A{
private BubbleSorter bs;//PigSorter
}
1、设计一个能细分为矩形、三角形、圆形和椭圆形的图形类。使用继承将这些图形分类,找出能作为基类部分的
共同特征(如校准点)和方法(如画法、初始化) 并看看这些图形是否能进一步划分为子类。
2、创建一个Vehicle类并将它声明为抽象类。在Vehicle类中声明一个NoOfWheels方法使它返回一个字符串值。创建两个类Car和Motorbike从Vehicle类继承,并在这两个类中实现NoOfWheels方法。
在Car类中应当显示“四轮车”信息,而在Motorbike类中应当显示“双轮车”信息。创建另一个带main方法的类,在该类中创建Car和Motorbike的实例,并在控制台中显示消息
接口
使用关键字interface定义接口
- 抽象类中的抽象方法不能省略abstract关键字,但是接口中的方法声明可以添加abstract,也可以不写
- 接口不能直接使用,必须有对应的实现类
public interface 会飞的 {
public void 起飞();
public void 巡航飞行();
//没有具体实现,也无法定义具体实现,这里是抽象方法
public void 降落();
}
public class 飞机类 implements 会飞的 {
//共性是通过实现接口来表示的
private String name; //名称,这是类属的属性,这里可以定义字节类的成员,和接口无关
//如果当前类不是抽象类,则必须实现接口中的所有抽象方法
@Override
public void 起飞() {
System.out.println("使劲跑,一抬头就飞起来了");
}
@Override
public void 巡航飞行() {
System.out.println("使劲烧油...");
}
@Override
public void 降落() {
System.out.println("对准...");
}
}
通过接口定义变量,使用具体类的实例,进行调用
会飞的 obj = new 飞机类();
obj.起飞();
//飞机类中必须提供接口中的所有抽象方法的实现,否则类只能是抽象类
//因为变量是通过接口进行声明的,所以只能调用接口中声明的方法,不能直接调用实现类中的没有在接口中声明过的方法
obj.巡航飞行();
obj.降落();
引入接口的目的在于隔离实现
public void 出差(飞机 obj){}
这种写法当前类和飞机是耦合的,如果需要坐超人出差,则必须修改源代码
public void 出差(会飞的 obj){}
当前类只是和接口耦合,任何实现了接口的对象都可以作为参数进行传入
使用接口而不是使用具体的类,则可以实现在实现接口的多个具体实现类之间进行更换,例如定义出超人类
什么是接口
在Java中不直接支持多继承,因为会出现调用的不确定性,所以Java将多继承机制进行改良,在Java中变成了多实现。一个类可以实现多个接口,一个接口可以继承多个接口
interface IA{}
interface IB{}
interface IC extends IA,IB{}
正确的,其中IC中的方法等于IA+IB
class A implements IA,IB{}
IA a=new A();
IB b=new A();
-
没有构造器方法
-
没有属性,只能定义常量
public class Test {
public static void main(String[] args) {
System.out.println(IA.name);
System.out.println(B.name);
//常量可以直接使用
//实现类也可以直接访问常量
}
}
interface IA {
String name = "lhz";// 接口中只能定义常量,不能定义属性,默认限定词public static
//final,自定义限定词不能冲突
}
class B implements IA{}
-
可以包含抽象方法,也可以没有抽象方法
-
抽象方法必须在实现类中提供实现
class A2Impl implements IA2{
//在实现类中必须实现接口中的所有抽象方法,否则当前类只能是抽象类
public int ee() {
return 0;
}
public void pp() {
System.out.println("A2Impl....");
}
public void cc() {
System.out.println("A2Impl.....");
}
//dd方法由于在接口中有默认实现,所以可以不用重新定义,也允许重新定义
}
- 可以使用default关键字给抽象方法提供默认实现,有默认实现的方法在实现类中可以重新定义,也可以不重新定义