Java面向对象:抽象类的学习
一.什么是抽象类
在面向对象的概念中,所有的对象都是通过类来描绘的,但是反过来,并不是所有的类都是用来描绘对象的,如果一个类中没有包含足够的信息来描绘一个具体的对象,这样的类就是抽象类
比如:
狗类和猫类都是一个完整的对象,它们具备自己的行为和属性,同时也有相同的属性和行为,这些相同的属性行为也就是所有动物所具备的,
有这层关系后,便可以把这些共同具备的属性和行为抽取出来形成一个动物类
狗类和猫类都能具体描述出狗对象和猫对象,而动物类内部并没有足够的信息来描述一个准确的对象,因此,动物类可以被设置为抽象类…
当我们想描述一个图形时,能定义一个图形类,但是图形并不具体,没有完整的信息表示具体是什么图形,但所有具体的图形(如:三角形 圆形)都有图形共同具备的属性和行为
图形类派生出 圆形类和矩形类,图形类即是一种抽象的类它并不能具体表示哪种图形,它可以作为具体图形的基类给其提供图形本身具备的属性和行为,而具体图形具有图形的属性和行为和自身的属性和行为构成一个完整的类
在打印图形例子中, 我们发现, 父类 Shape 中的 draw 方法好像并没有什么实际工作, 主要的绘制图形都是由 Shape的各种子类的 draw 方法来完成的.
像这种没有实际工作的方法, 我们可以把它设计成一个 抽象方法(abstractmethod), 包含抽象方法的类我们称为 抽象类(abstract class).
动物类和图形类,本身都是一个普通的类,这些类通常都是具备其他类共有的属性而被抽取出来所形参的类,由于其并不能完整的描述对象 所以这些类都是抽象的, 在编写代码中一般也不会对其实例化,即抽象类存在的最大意义就是用来被继承的…
当一个类不能完整的描述一个对象时 可以选择将其属性方法补全能描述一个具体对象 或者最好在语法上将其设置为抽象类
二.抽象类语法
在Java中,一个类如果被 abstract 关键字修饰后即称为抽象类,抽象类中被 abstract 修饰的方法称为抽象方法,抽象方法不用给出具体的实现体。
示例:
abstract class Shape{ //抽象类中有抽象方法 也有非抽象方法 普通类只有非抽象方法 void draw(){ //抽象类中的非抽象方法 可以写方法体 System.out.println("画图"); } Shape(){ //构造方法 } String name; abstract void func(); //抽象类中方法 也可以不写方法体 不写方法体时要在前面加abstract 为抽象方法 方法后加分号 } class Circle extends Shape{ @Override void draw(){ //可以对抽象类 有方法体的方法进行重写 也可以不重写 System.out.println("画圆圈○"); } void func(){ // 必须对继承的抽象类的抽象方法进行(实现) 否则无法编译通过 //super.func(); 编译错误 在子类方法里不能通过super访问父类抽象方法 因为其没有方法体 System.out.println("实现父类的抽象方法"); } } class Flower extends Shape{ @Override void func(){ //即便方法体没内容也得重写抽象方法 } @Override void draw(){ System.out.println("画花❀"); } } class Rect extends Shape{ //按住ant加回车 回车 快捷出现 重写 父类的抽象方法 @Override void func() { } void draw(){ System.out.println("画矩形!"); } } public static void main(String[] args) { Circle circle=new Circle(); circle.draw(); //当子类没有重写 父抽象类的 有方法体的方法 会调用父类的 重写了则调用子类的 circle.func(); //子类必须实现父类抽象方法 否则编译不通过 因为父类抽象方法没有具体方法体不能被调用 Shape shape=circle; shape.func(); // 抽象类可以实现向上转型 动态绑定 多态 此时 func方法 被子类func实现重写了 调用的是这个子类方法 } } 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455
抽象类 它的目的就是为了派生出子类 子类有自己的成员和父类的成员负责具体描述对象
子类里可以调用子类对象的方法,没有重写抽象父类的非抽象方法则调用的是父类非抽象方法.而调用的抽象方法一定是子类重写的父类抽象方法(抽象方法一定要被重写)
注意:
抽象类也是类,内部可以包含普通方法和属性,甚至构造方法
抽象类中可以有抽象方法和非抽象方法 即被abstract修饰的方法为抽象方法
非抽象方法需要有方法体(具体的方法实现) 被子
后可以重写其方法或者不重写
抽象方法不能写方法体但是其子类一定要重写父类的抽象方法(除非子类也是个抽象类)
在子类对象内不能通过
访问父类的抽象方法(因为其没有具体方法体实现)
三.抽象类的特性
- 抽象类存在就是用来被继承的,因其不能完整描述对象,其抽象类是不能被实例化的
Shape shape = new Shape(); // 编译出错 //Error:(30, 23) java: Shape是抽象的; 无法实例化 123
2.抽象方法不能是 private 的
abstract class Shape { abstract private void draw(); // 编译出错 } //Error:(4, 27) java: 非法的修饰符组合: abstract和private 1234
被private修饰的方法被继承后 无法被重写,而此方法是抽象的无方法体因此没有被重写没有任何意义会编译报错,而非抽象方法可以被private修饰,其有自己的方法体,可以通过调用一些公开的方法访问不会报错
- 抽象方法不能被final和static修饰
public abstract class Shape { abstract final void methodA(); abstract public static void methodB(); } // 编译报错: // Error:(20, 25) java: 非法的修饰符组合: abstract和final // Error:(21, 33) java: 非法的修饰符组合: abstract和static 123456
被final修饰的方法不能被重写,而抽象方法其没有方法体不被重写没有意义
被static修饰的方法是类成员方法不属于对象,不会被子类继承即不能被重写没有意义
而抽象类里可以有static和final修饰的非抽象方法,
- 抽象类必须被继承,并且继承后子类要重写父类中的抽象方法,否则子类也是抽象类,必须要使用 abstract 修饰
abstract class Shape{ //抽象图形类 abstract public void draw(); } abstract class Triangle extends Shape {// 三角形抽象类: @Override public void draw() { //重写抽象方法 System.out.println("三角形""); } // 三角形:直角三角形、等腰三角形等,还可以继续细化 1234567891011
当一个抽象类被抽象类继承后即可以重写其抽象方法也可以不重写,因为其本身也是抽象的,而非抽象类继承抽象类后一定要重写抽象方法
- 抽象类中不一定包含抽象方法,但是有抽象方法的类一定是抽象类
- 抽象类中可以有构造方法,供子类创建对象时,初始化父类的成员变量
四.抽象类的作用
抽象类存在最大作用就是用来被继承的…
抽象类本身不能被实例化, 要想使用, 只能创建该抽象类的子类. 然后让子类重写抽象类中的抽象方法.
而一个普通类,其也可以被继承,内部的方法也可以被重写都具备类成员属性和方法,构造方法 ,而抽象类也就比普通类多了抽象方法,那为什么要有抽象类呢?
抽象类相比普通类来说,多了抽象这个概念,综合上面介绍的抽象类,实际上使用抽象类相当于比普通类多了一重编译器的校验.
使用抽象类其不能被实例化,其内的抽象方法不能被private static fina修饰 且一定要被非抽象的子类重写…
事实上 普通类可以作为父类被子类继承,但是在后续编写代码过程中,此父类也可以被实例化,但是其就无法在使用到任何子类的方法和行为,本身父类就是一些类共性抽取形成的一个类…
使用抽象类的场景就如上面所说, 实际工作不应该由父类完成, 而应由子类完成.
那么此时如果不小心误用成父类了, 使用普通类编译器是不会报错的. 但是父类是抽象类就会在实例化的时候提示错误, 让我们尽早发现问题.
很多语法存在的意义都是为了 “预防出错”, 例如我们曾经用过的 final 也是类似. 创建的变量用户不去修改, 不就可以相当于常量嘛?
但是加上 final 能够在不小心误修改的时候, 让编译器及时提醒我们.充分利用编译器的校验, 在实际开发中是非常有意义的.
所以在实际工作中,一个类本身就是作为父类存在,其不用被实例化,其内部的一些方法必须被重写,那么最好就设计此父类为抽象类,子类继承父类后,能够通过编译器校验提醒我们对需要重写的方法进行重写,也能避免不小心实例化的是父类对象而产生警告…
五. 抽象类实现多态
抽象类本身虽然不能实例化对象,和普通类没有很大的区别,即其类型可以创建引用变量接受子类对象地址(向上转型)
在发生向上转型后,子类重写了抽象类里的成员方法,即可以通过动态绑定实现多态
示例:
abstract class Shape{ void draw(){ System.out.println("画图"); } String name; abstract void func(); } class Circle extends Shape{ @Override void draw(){ //可以对抽象类 有方法体的方法进行重写 也可以不重写 System.out.println("画圆圈○"); } void func(){ System.out.println("实现父类的抽象方法"); } } class Flower extends Shape{ @Override void func(){ //即便方法体没内容也 得重写抽象方法 } @Override void draw(){ System.out.println("画花❀"); } } class Rect extends Shape{ //按住ant加回车 回车 快捷出现 重写 父类的抽象方法 @Override void func() { } void draw(){ System.out.println("画矩形!"); } } public class Note { public static void func(Shape shape){ // 在传之前已经是向上转型为shape类型 这里接受没有什么变化 shape.draw(); //通过父类引用接受不同子类对象地址 调用其被不同子类对象重写后的方法 发生动态绑定 实现不同行为 多态 } public static void main(String[] args) { Circle circle=new Circle(); Shape shape=circle; //func实现重写了 调用的是这个子类方法 Rect rect=new Rect(); Flower flower=new Flower(); Shape[] shapes={circle,rect,flower}; // shapes 为一维Shape类型数组 引用变量 每个元素是一个shape引用变量接受 子类对象地址 已经发生向上转型 for (Shape x:shapes) { //通过foreach 将每个转型后的地址传给方法 func(x); } } } 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657
可见抽象类能做的,普通类都能实现,抽象类比普通类多了抽象方法且不能实例化, 在实际开发中一个类要被其他类继承最好将这个类设计为抽象类…