文章目录
PPT链接:点击这里
5、抽象类与抽象方法
随着继承层次中一个个新子类的定义,类变得越来越具体,而父类则更一般,更通用。类的设计应该保证父类和子类能够共享特征。有时将一个父类设计得非常抽象,以至于它没有具体的实例,这样的类叫做抽象类
5.1、abstract的使用
- abstract的理解:抽象的
- abstract可以用来修饰的结构:类、方法
abstract修饰类:抽象类
- 此类不能实例化
- 抽象类中一定有构造器,便于子类实例化时调用(涉及:子类对象实例化的全过程)
- 开发中,都会提供抽象类的子类,让子类对象实例化,完成相关的操作
abstract修饰方法:抽象方法
-
抽象方法只有方法的声明,没有方法体
-
包含抽象方法的类,一定是一个抽象类。反之,抽象类中可以没有抽象方法
-
若子类重写了父类中的所有的抽象方法后,此子类方可实例化
若子类没有重写父类中的所有的抽象方法,则此子类也是一个抽象类,需要使用abstract修饰
注意点
- abstract不能用来修饰:属性、构造器等结构
- abstract不能用来修饰私有方法、静态方法、final的方法、final的类 ----- 因为这些方法都不能被重写
public class AbstractTest {
public static void main(String[] args) {
//一旦Person类抽象了,就不可实例化
// Person p1 = new Person();
}
}
abstract class Creature{
public abstract void breath();
}
abstract class Person extends Creature{
String name;
int age;
public Person(){//子类对象实例化会用到
}
public Person(String name,int age){
this.name = name;
this.age = age;
}
//抽象方法:只有方法的声明,没有方法体
public abstract void eat();
public void walk(){
System.out.println("人走路");
}
}
class Student extends Person{
public Student(String name,int age){
super(name,age);
}
public Student(){
}
//重写抽象方法
public void eat(){
System.out.println("学生多吃有营养的食物");
}
@Override//重写抽象方法
public void breath() {
System.out.println("学生应该呼吸新鲜的没有雾霾的空气");
}
}
5.2、抽象类应用
抽象类是用来模型化那些父类无法确定全部实现,而是由其子类提供具体实现的对象的类
5.3、抽象类的匿名子类
-
涉及到匿名的东西,都是只用一次,目的就是为了省事。有时候不一定用,但是为了看得懂代码,这个还是要有所了解的
-
匿名分为以下 4 种情况:
- 非匿名类的非匿名对象
- 非匿名类的匿名对象
- 匿名子类的非匿名对象
- 匿名子类的匿名对象
public class PersonTest {
public static void main(String[] args) {
//********1.非匿名类的匿名对象********(对象没有名,类有名:Worker)
method(new Worker());
System.out.println("************************************");
//********2.非匿名类的非匿名对象w********(对象有名:w,类有名:worker)
Worker w = new Worker();
method(w);
System.out.println("************************************");
//********3.匿名子类的非匿名对象********
//是new了一个对象,但不是new了一个抽象类的对象,是创建了一个匿名(这个类没有名)子类(Person的子类)对象,对象名为p
//因为我们暂时还不知道子类的名字是什么,所以借助子类重写方法来实现方法的调用
Person p = new Person() {
@Override //这里说明了创建的类是Person的子类,注意重写相关的方法
public void eat() {
System.out.println("吃东西");
}
@Override
public void breath() {
System.out.println("好好呼吸");
}
};
method(p);//会调用对象重写的方法,还涉及到了多态的使用
System.out.println("************************************");
//********4.匿名子类的匿名对象(连对象名也省去)********
method(new Person(){
@Override//这里也说明了创建的类是Person的子类,注意重写相关的方法
public void eat() {
System.out.println("吃好吃东西");
}
@Override
public void breath() {
System.out.println("好好呼吸新鲜空气");
}
});
}
public static void method(Person p){
p.breath();
p.eat();
}
}
abstract class Person {//抽象类也可以继承,比如Person是可以继承别的类的
public Person() {
}
public abstract void eat();
public abstract void breath();
}
class Worker extends Person{
@Override
public void eat() {
}
@Override
public void breath() {
}
}
5.4、练习
5.4.1、练习1
编写一个 Employee 类,声明为抽象类,包含如下三个属性:name, id, salary
提供必要的构造器和抽象方法:work()
对于 Manager 类来说,他既是员工,还具有奖金(bonus)的属性
请使用继承的思想,设计 CommonEmployee 类和 Manager 类,要求类中提供必要的方法进行属性访问
【代码实现】
public abstract class Employee {
private String name;
private int id;
private double salary;
public Employee(){
super();
}
public Employee(String name, int id, double salary) {
super();
this.name = name;
this.id = id;
this.salary = salary;
}
public abstract void work();
}
public class Manager extends Employee{
private double bonus; //奖金
public Manager(double bonus) {
super();
this.bonus = bonus;
}
public Manager(String name, int id, double salary, double bonus) {
super(name, id, salary);
this.bonus = bonus;
}
@Override
public void work() {
System.out.println("管理员工,提高公司运行效率");
}
}
public class CommonEmployee extends Employee {
@Override
public void work() {
System.out.println("员工在一线车间生产产品");
}
}
public class EmployeeTest {
public static void main(String[] args) {
//多态
Employee manager = new Manager("库克",1001,5000,50000);
manager.work();
CommonEmployee commonEmployee = new CommonEmployee();
commonEmployee.work();
}
}
5.4.2、练习2
编写工资系统,实现不同类型员工(多态)的按月发放工资。如果当月出现某个Employee对象的生日,则将该雇员的工资增加100元
实验说明:
1.定义一个Employee类,该类包含:
private成员变量name,number,birthday,其中birthday为MyDate类的对象
abstract方法earnings()
toString()方法输出对象的name,number和birthday
2.MyDate类包含:
private成员变量year,month,day
toDateString()方法返回日期对应的字符串:xxxx年xx月xx日
3.定义SalariedEmployee类继承Employee类,实现按月计算工资的员工处理。该类包括:
private成员变量monthlySalary
实现父类的抽象方法earnings(),该方法返回monthlySalary值;toString()方法输出员工类型信息及员工的name,number,birthday
4.参照SalariedEmployee类定义HourlyEmployee类,实现按小时计算工资的员工处理。该类包括:
private成员变量wage和hour
实现父类的抽象方法earnings(),该方法返回wage * hour值
toString()方法输出员工类型信息及员工的name,number,birthday
5.定义PayrollSystem类,创建Employee变量数组并初始化,该数组存放各类雇员对象的引用
利用循环结构遍历数组元素,输出各个对象的类型,name,number,birthday
当键盘输入本月月份值时,如果本月是某个Employee对象的生日,还要输出增加工资信息
提示:
定义People类型的数组People c1[]=new People[10];
数组元素赋值:
c1[0]=new People("John","0001",20);
c1[1]=new People("Bob","0002",19);
若People有两个子类Student和Officer,则数组元素赋值时,可以使父类类型的数组元素指向子类
c1[0]=new Student("John","0001",20,85.0);
c1[1]=new Officer("Bob","0002",19,90.5);
【代码实现】
public abstract class Employee {
private String name;
private int number;
private MyDate birthday;
public Employee(String name, int number, MyDate birthday) {
super();
this.name = name;
this.number = number;
this.birthday = birthday;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getNumber() {
return number;
}
public void setNumber(int number) {
this.number = number;
}
public MyDate getBirthday() {
return birthday;
}
public void setBirthday(MyDate birthday) {
this.birthday = birthday;
}
public abstract double earnings();//抽象方法
@Override
public String toString() {
return "name=" + name + ", number=" + number + ", birthday=" + birthday.toDateString();
//不写toDateString(),则birthday会默认调用toString(),是地址值
}
}
public class MyDate {
private int year;
private int month;
private int day;
public MyDate(int year, int month, int day) {
super();
this.year = year;
this.month = month;
this.day = day;
}
public int getYear() {
return year;
}
public void setYear(int year) {
this.year = year;
}
public int getMonth() {
return month;
}
public void setMonth(int month) {
this.month = month;
}
public int getDay() {
return day;
}
public void setDay(int day) {
this.day = day;
}
public String toDateString(){
return year + "年" + month + "月" + day + "日";
}
}
public class SalariedEmployee extends Employee{
private double monthlySalary;//月工资
public SalariedEmployee(String name,int number,MyDate birthday) {
super(name,number,birthday);
}
public SalariedEmployee(String name, int number, MyDate birthday, double monthlySalary) {
super(name, number, birthday);
this.monthlySalary = monthlySalary;
}
@Override
public double earnings() {//重写earnings()抽象方法
return monthlySalary;
}
@Override
public String toString() {
return "SalariedEmployee [" + super.toString() + "]";
}
}
public class HourlyEmployee extends Employee{
private int wage;//每小时的工资
private int hour;//月工作的小时数
public HourlyEmployee(String name, int number, MyDate birthday) {
super(name, number, birthday);
}
public HourlyEmployee(String name, int number, MyDate birthday,int wage,int hour) {
super(name, number, birthday);
this.wage = wage;
this.hour = hour;
}
public int getWage() {
return wage;
}
public void setWage(int wage) {
this.wage = wage;
}
public int getHour() {
return hour;
}
public void setHour(int hour) {
this.hour = hour;
}
@Override
public double earnings() {
return wage * hour;
}
public String toString(){
return "HourlyEmployee[" + super.toString() + "]";
}
}
import java.util.Scanner;
public class PayrollSystem {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
System.out.println("请输入当月的月份:");
int month = scanner.nextInt();
Employee[] emps = new Employee[2];
emps[0] = new SalariedEmployee("马森", 1002,new MyDate(1992, 2, 28),10000);
emps[1] = new HourlyEmployee("潘雨生", 2001, new MyDate(1991, 1, 6),60,240);
for(int i = 0;i < emps.length;i++){
System.out.println(emps[i]);
double salary = emps[i].earnings();
System.out.println("月工资为:" + salary);
if(month == emps[i].getBirthday().getMonth()){
System.out.println("生日快乐!奖励 100 元");
}
}
}
}
6、接口(interface)
- 一方面,有时必须从几个类中派生出一个子类,继承它们所有的属性和方法。但是Java 不支持多重继承。有了接口,就可以得到多重继承的效果
- 另一方面,有时必须从几个类中抽取出一些共同的行为特征,而它们之间又没有 is-a 的关系,仅仅是具有相同的行为特征而已。例如:鼠标、键盘、打印机、扫描仪、摄像头、充电器、MP3 机、手机、数码相机、移动硬盘等都支持 USB 连接
- 接口就是规范,定义的是一组规则,体现了现实世界中 “如果你是/要…则必须能…” 的思想。继承是一个"是不是"的关系,而接口实现则是"能不能"的关系
- 接口的本质是契约,标准,规范,就像我们的法律一样。制定好后大家都要遵守
6.1、接口的使用
-
接口使用
interface
来定义 -
Java 中,接口和类是并列的两个结构
-
如何定义两个接口:定义接口中的成员
-
JDK7 及以前:只能定义全局常量和抽象方法
全局常量:public static final 的,书写时可以省略不写
抽象方法:public abstract 的,书写时可以省略不写
-
JDK8:除了全局常量和抽象方法之外,还可以定义静态方法、默认方法
-
-
接口中不能定义构造器,意味着接口不可以实例化
-
Java 开发中,接口通过让类去实现(
implements
)的方式来使用如果实现类覆盖了接口中的所有方法,则此实现类就可以实例化
如果实现类没有覆盖接口中所有的抽象方法,则此实现类仍为一个抽象类
-
Java类可以实现多个接口,弥补了Java 单继承性的局限性。格式:
class AA extends BB implementd CC,DD,EE
-
接口与接口之间可以继承,而且可以多继承
-
接口的具体使用,体现多态性
接口的主要用途就是被实现类实现(面向接口编程)
-
接口,实际可以看作是一种规范
public class InterfaceTest {
public static void main(String[] args) {
System.out.println(Flyable.MAX_SPEED);
System.out.println(Flyable.MIN_SPEED);
// Flyable.MIN_SPEED = 2;//编译错误,不能赋值
Plane plane = new Plane();
plane.fly();
}
}
interface Flyable{
//全局常量
public static final int MAX_SPEED = 7900;//第一宇宙速度
int MIN_SPEED = 1;//省略了public static final
//抽象方法
public abstract void fly();
void stop();//省略了public abstract
//接口中不能定义构造器
// public Flyable(){
//
// }
}
interface Attackable{
void attack();
}
class Plane implements Flyable{
@Override
public void fly() {
System.out.println("通过引擎起飞");
}
@Override
public void stop() {
System.out.println("驾驶员减速停止");
}
}
abstract class Kite implements Flyable{//实现类没有覆盖接口中所有的抽象方法,则此实现类仍为一个抽象类
@Override
public void fly() {
}
}
class Bullet extends Object implements Flyable,Attackable,CC{
@Override
public void attack() {
}
@Override
public void fly() {
}
@Override
public void stop() {
}
@Override
public void method1() {
}
@Override
public void method2() {
}
}
//************************************
interface AA{
void method1();
}
interface BB{
void method2();
}
interface CC extends AA,BB{//继承
}
6.2、接口的应用举例
-
接口使用上也满足多态性
-
接口,实际上就是定义了一种规范
-
开发中,体会面向接口编程
【举例1】
【举例2】
【举例3】
public class USBTest {
public static void main(String[] args) {
Computer com = new Computer();
//1.创建了接口的 非匿名实现类的非匿名对象
Flash flash = new Flash();
com.transferData(flash);
//2.创建了接口的 非匿名实现类的匿名对象
com.transferData(new Printer());
//3.创建了接口的 匿名实现类的非匿名对象
USB phone = new USB(){
@Override
public void start() {
System.out.println("手机开始工作");
}
@Override
public void stop() {
System.out.println("手机结束工作");
}
};
com.transferData(phone);
//4.创建了接口的 匿名实现类的匿名对象
com.transferData(new USB(){
@Override
public void start() {
System.out.println("mp3开始工作");
}
@Override
public void stop() {
System.out.println("mp3结束工作");
}
});
}
}
class Computer{
//电脑传输数据
public void transferData(USB usb){//USB usb = new Flash();
usb.start();
System.out.println("具体传输数据的细节");
usb.stop();
}
}
interface USB{
//常量:定义了长、宽、最大最小的传输速度等
void start();
void stop();
}
class Flash implements USB{
@Override
public void start() {
System.out.println("U盘开启工作");
}
@Override
public void stop() {
System.out.println("U盘结束工作");
}
}
class Printer implements USB{
@Override
public void start() {
System.out.println("打印机开启工作");
}
@Override
public void stop() {
System.out.println("打印机结束工作");
}
}
6.3、抽象类和接口之间的对比
面试题:抽象类与接口有哪些异同
在开发中,常看到一个类不是去继承一个已经实现好的类,而是要么继承抽象类,要么实现接口
6.4、练习
6.4.1、练习1
判断下列哪条有错误,如有错误应该怎么改?
interface A {
int x = 0;
}
class B {
int x = 1;
}
class C extends B implements A {
public void pX() {
System.out.println(x);//编译不通过,x不明确
//改
// System.out.println(super.x); //1 ——调用父类B
// System.out.println(A.x);//0 ——调用接口A
}
public static void main(String[] args) {
new C().pX();
}
}
6.4.2、练习2
判断下列哪条有错误,如有错误应该怎么改?
interface Playable {
void play();
}
interface Bounceable {
void play();
}
interface Rollable extends Playable, Bounceable {
Ball ball = new Ball("PingPang");
//ball是全局常量,这里省略了public static final,应该是public static final Ball ball = new Ball("PingPang");
}
public class Ball implements Rollable {
private String name;
public String getName() {
return name;
}
public Ball(String name) {
this.name= name;
}
public void play() {//可以看做上面两个接口中的play()都被重写了
ball = new Ball("Football"); //ball不能重新赋值,ball是全局常量
System.out.println(ball.getName());
}
}
6.4.3、练习3
1.定义一个接口用来实现两个对象的比较
interface CompareObject{
public int compareTo(Object o);//若返回值是0,代表相等;若为正数,代表当前对象大;负数代表当前对象小
}
2.定义一个Circle类,声明radius属性,提供getter和setter方法
3.定义一个ComparableCircle类,继承Circle类并且实现CompareObject接口。在ComparableCircle类中给出接口中方法compareTo的实现体,用来比较两个圆的半径大小
4.定义一个测试类InterfaceTest,创建两个ComparableCircle对象,调用compareTo方法比较两个类的半径大小
5.思考:参照上述做法定义矩形类Rectangle和ComparableRectangle类,在ComparableRectangle类中给出compareTo方法的实现,比较两个矩形的面积大小
【代码实现】
public interface CompareObject {
public int compareTo(Object o); //若返回值是0 , 代表相等; 若为正数,代表当前对象大;负数代表当前对象小
}
public class Circle {
private double radius;
public double getRadius() {
return radius;
}
public void setRadius(double radius) {
this.radius = radius;
}
public Circle() {
super();
}
public Circle(double radius) {
super();
this.radius = radius;
}
}
public class ComparableCircle extends Circle implements CompareObject{
public ComparableCircle(double radius) {//也可以用ComparableCircle造圆
super(radius);
}
@Override
public int compareTo(Object o) {
if(this == o){//是同一个引用
return 0;
}
if(o instanceof ComparableCircle){
ComparableCircle c = (ComparableCircle)o;
//错误的:强转会丢失精度
// return (int) (this.getRadius() - c.getRadius());
//正确的:
if(this.getRadius() > c.getRadius()){
return 1;
}else if(this.getRadius() < c.getRadius()){
return -1;
}else{
return 0;
}
//当属性radius声明为Double类型时,可以调用包装类的方法
// return this.getRadius().compareTo(c.getRadius());//Double类实现Comparable接口,Comparable接口中有compareTo()
}else{
throw new RuntimeException("传入的数据类型不匹配");
}
}
}
public class ComparableCircleTest {
public static void main(String[] args) {
ComparableCircle c1 = new ComparableCircle(3.4);
ComparableCircle c2 = new ComparableCircle(3.6);
int compareValue = c1.compareTo(c2);
if(compareValue > 0){
System.out.println("c1对象大");
}else if(compareValue < 0){
System.out.println("c2对象大");
}else{
System.out.println("c1与c2一样大");
}
int compareValue1 = c1.compareTo(new String("AA"));//若两个对象的类型不一样
System.out.println(compareValue1);
}
}
6.5、Java 8 中关于接口的改进
Java8 中,可以为接口添加静态方法和默认方法。从技术角度来说,这是完全合法的,只是它看起来违反了接口作为一个抽象定义的理念
- 静态方法:使用 static 关键字修饰。可以通过接口直接调用静态方法,并执行其方法体。我们经常在相互一起使用的类中使用静态方法。你可以在标准库中找到像 Collection/Collections 或者 Path/Paths 这样成对的接口和类
- 默认方法:默认方法使用 default 关键字修饰。可以通过实现类对象来调用。我们在已有的接口中提供新方法的同时,还保持了与旧版本代码的兼容性。比如:java 8 API 中对 Collection、List、Comparator 等接口提供了丰富的默认方法
public interface CompareA {
//静态方法
public static void method1() {
System.out.println("CompareA:北京");
}
//默认方法
public default void method2(){
System.out.println("CompareA:上海");
}
//public可以省略
default void method3(){
System.out.println("CompareA:上海");
}
}
public interface CompareB {
default void method3(){
System.out.println("CompareB:上海");
}
}
public class SuperClass {
public void method3(){
System.out.println("SuperClass:北京");
}
}
public class SubClassTest {
public static void main(String[] args) {
SubClass s = new SubClass();
// s.method1();//错误
// SubClass.method1();//错误
// 知识点1:接口中定义的静态方法,只能通过接口来调用,不能通过实现类或实现类的对象调用
CompareA.method1();
// 知识点2:通过实现类的对象,可以调用接口中的默认方法。如果实现类重写了接口中的默认方法,调用时,仍然调用的是重写以后的方法
s.method2();//SubClass:上海
// 知识点3:如果子类(或实现类)继承的父类和实现的接口中声明了同名同参数的默认方法,
// 那么子类在没有重写此方法的情况下,默认调用的是父类中的同名同参数的方法。-->类优先原则
// 知识点4:如果实现类实现了多个接口(此时没有继承父类),而这多个接口中定义了同名同参数的默认方法,
// 那么在实现类没有重写此方法的情况下,报错。-->接口冲突
// 这就需要我们必须在实现类中重写此方法
s.method3();//SubClass:深圳
}
}
class SubClass extends SuperClass implements CompareA,CompareB{
//重写
public void method2(){//在类中重写接口的默认方法,default要去掉
System.out.println("SubClass:上海");
}
public void method3(){
System.out.println("SubClass:深圳");
}
// 知识点 5:如何在子类(或实现类)的方法中调用父类、接口中被重写的方法
public void myMethod(){
method3(); //调用自己定义的重写的方法
super.method3(); //调用的是父类中声明的
// 调用接口中的默认方法
CompareA.super.method3();
CompareB.super.method3();
}
}
6.6、默认方法接口冲突问题
-
若一个接口中定义了一个默认方法,而另外一个接口中也定义了一个同名同参数的方法(不管此方法是否是默认方法),在实现类同时实现了这两个接口时,会出现:接口冲突
-
解决1:调用其中一个接口中的方法 ----
接口名.super.方法名();
-
解决2:在实现类中覆盖接口中的同名同参数的方法,用实现类的对象调用
-
-
若一个接口中定义了一个默认方法,而父类中也定义了一个同名同参数的非抽象方法,则不会出现冲突问题。因为此时遵守:类优先原则,接口中具有相同名称和参数的默认方法会被忽略
-
解决1:默认调用父类的方法,类优先原则
-
解决2:重写该方法,用对象调用方法
-
解决3:调用接口中的方法 ----
接口名.super.方法名();
-
interface Filial {// 孝顺的
default void help() {
System.out.println("老妈,我来救你了");
}
}
interface Spoony {// 痴情的
default void help() {
System.out.println("媳妇,我来救你了");
}
}
class Father{
public void help(){
System.out.println("儿子,就我媳妇!");
}
}
//********************发生接口冲突********************
class Man implements Filial, Spoony {
public static void main(String[] args) {
Man man = new Man();
man.impHelp();//解决1:调用其中一个接口中的方法
man.help(); //解决2:实现类覆盖接口中同名同参数的方法,用实现类的对象调用
}
@Override
public void help() {//覆盖
System.out.println("我该就谁呢?");
}
public void impHelp() {
Filial.super.help();
Spoony.super.help();
}
}
//********************类优先原则********************
class Man extends Father implements Filial, Spoony {
public static void main(String[] args) {
Man man = new Man();
man.help(); //解决1:重写前默认调用父类的方法,类优先原则
//解决2:重写后,调用重写后的方法
man.impHelp();//解决3:调用接口中的方法
}
@Override
public void help() {//覆盖重写
System.out.println("我该就谁呢?");
}
public void impHelp() {
Filial.super.help();
Spoony.super.help();
}
}
物竞天择,适者生存,加油吧!!!