​

4.1 抽象类

如何定义图形类计算周长的通用方法?

Java面向对象笔记 • 【第4章 抽象类和接口】_接口Java面向对象笔记 • 【第4章 抽象类和接口】_设计模式_02Java面向对象笔记 • 【第4章 抽象类和接口】_java_03


  • 不同的图形子类图形计算周长的方式大相径庭,所以导致Shape类的calcPerimeter()方法无法运用某个固定的计算图形周长的公式。
  • 可以将calcPerimeter()方法定义为抽象方法,抽象方法没有具体的方法实现,该方法必须由其继承的子类重写,这样该方法就起到了约束规范的作用,又不影响类最初的设计思路。

4.1.1 抽象方法和抽象类

定义抽象类语法

[访问修饰符]   abstract  class  类名

定义抽象方法语法

[访问修饰符]   abstract  返回类型  方法名([参数列表])


抽象类和抽象方法规则:

一个抽象类中可以不定义抽象方法,但是只要类中有一个抽象方法,则该类一定是抽象类。

抽象类不能被实例化,即不能被new创建一个实例对象。

如果一个子类继承一个抽象类,则子类需要通过覆盖的方式来重写该抽象类中的所有抽象方法。如果子类没有完全重写抽象父类中所有的抽象方法,则子类仍是抽象的。

抽象方法可以与public、protected复合使用,但不能与final、private和static复合使用。


abstract 不能用于修饰属性,不能用于修饰局部变量,也不能用于修饰构造器。

示例:在图形继承关系中使用抽象类和抽象方法

// 抽象类 图形Shape :

public abstract class Shape { // 抽象类 图形
private String color;

// 定义一个计算周长的抽象方法
public abstract double calcPerimeter();
// 定一个返回图形子类型的方法
public abstract String getType();
// 有参构造方法
public Shape(String color){
this.color=color;
System.out.println("---执行了Shape类的构造方法---");
}
public String getColor() {
return color;
}
public void setColor(String color) {
this.color = color;
}
}

// 子类 三角形Triangle :

public class Triangle  extends Shape{ //子类 三角形
private double x,y,z; //定义三角形边

public Triangle(String color,double x,double y,double z) {
super(color); //调用父类构造器
this.setSide(x, y, z);
}
public void setSide(double x,double y,double z){//设置三角形三边
if(x>y+z || y>x+z || z>x+y){
System.out.println("三角形两边之和必须大于第三边");
return ;
}
this.x=x, this.y=y,this.z=z;
}
public double calcPerimeter() {//重写父类计算周长方法
return x+y+z;
}
public String getType() {//重写父类获取图形类型方法
return "三角形";
}
}

//子类 圆形Circle :

public class Circle extends Shape { //子类 圆形
private double r;//半径

public Circle(String color,double r) {
super(color);
this.r=r;
}
public double calcPerimeter() {//重写父类计算周长方法
return 2*Math.PI*r;
}
//重写父类获取图形类型方法
public String getType() {
return "圆形";
}
public double getR() {
return r;
}
public void setR(double r) {
this.r = r;
}

main方法

public static void main(String[] args) { // 测试
Shape triangle=new Triangle("红色", 3, 4, 5);
Shape circle=new Circle("红色", 4);
System.out.println(triangle.getType()+" 周长="+triangle.calcPerimeter());
System.out.println(circle.getType()+" 周长="+circle.calcPerimeter());
}

Java面向对象笔记 • 【第4章 抽象类和接口】_设计模式_04

Java面向对象笔记 • 【第4章 抽象类和接口】_java_05

4.1.2 抽象类的作用

避免子类设计的随意性:


  • 抽象类不能被实例化,只能作为父类继承。
  • 从多个具有相同特征的类中抽象出一个抽象类,以该抽象类作为其子类的模板。

体现了模板模式的设计理念:


  • 抽象类作为多个子类的通用模板。
  • 子类在抽象类的基础上进行扩展、改造,但子类总体上会保留抽象类的行为方式。
  • 编写一个抽象父类,父类提供多个子类的通用方法,并且将一个或多个方法留给其子类实现,这就是一种模板模式。

示例:在速度表应用上使用模板设计模式

// 抽象父类速度表 SpeedMeter ,该抽象类为模板类

public abstract  class SpeedMeter { //抽象父类速度表,该抽象类为模板类
private double trunRate; //转速
//将返回车轮半径的方法定义为抽象方法
public abstract double getRedius();

//定义计算速度的通用算法
public double getSpeed(){
//速度=车轮半径*2*π*转速
return 2*3.14*getRedius()*getTrunRate();
}
public double getTrunRate() {
return trunRate;
}
public void setTrunRate(double trunRate) {
this.trunRate = trunRate;
}
}

// 子类汽车速度表 CarSpeedMeter

public class CarSpeedMeter extends SpeedMeter{ //子类汽车速度表
//重写父类的获取半径的方法
public double getRedius() {
return 2.0;
}
public static void main(String[] args) {
//创建CarSpeedMeter对象
CarSpeedMeter csm=new CarSpeedMeter();
csm.setTrunRate(15);//设置转速
System.out.println("当前汽车时速="+csm.getSpeed()+"公里/小时");
}
}

Java面向对象笔记 • 【第4章 抽象类和接口】_设计模式_06

SpeedMeter类中提供了速度表的通用算法,但一些具体的实现细节则推迟到其子类CerSpeedMeter类中实现。这是一种典型的模板模式。

模板模式基本规则:

抽象父类仅定义使用的某些方法,将不能实现的部分抽象成抽象方法,留给其子类实现。

父类中包含需要调用其他依赖的方法,这些被调方法既可以由父类实现,也可以由其子类实现。父类中提供的方法仅定义了一个通用算法,需要具体子类辅助实现。

​4.1.3 实践练习​


4.2 final修饰符


  • final关键字可以用于修饰类、变量和方法。
  • final修饰变量时,表示该变量一旦获得了初始值就不可能被改变,final修饰的类不能被继承,final修饰的方法可以被继承但不能被重写。
  • final意味着终极。

4.2.1 final成员变量


  • 对于final修饰的成员变量而言,一旦赋初始值,就不能被重新赋值。
  • final修饰的类属性只能在静态初始化块或声明该属性时指定初始值。
  • final修饰的实例属性只能在构造方法或声明该属性时指定初始值。

示例:演示final成员变量使用

Java面向对象笔记 • 【第4章 抽象类和接口】_java_07

4.2.2 final局部变量


  • 系统不会对局部变量进行初始化,局部变量必须由程序员显式初始化。因此,使用final修饰局部变量时,既可以在定义时指定默认值,也可以不指定默认值。
  • final修饰的局部变量在定义时没有指定默认值,则应在后面的代码中对该final变量赋初始值,但只能赋值一次,不能重复赋值。
  • 如果final修饰的局部变量在定义时已经指定默认值,则后面代码中不能对该变量赋值。

示例:演示final局部变量使用

Java面向对象笔记 • 【第4章 抽象类和接口】_java_08

4.2.3 final方法


  • final修饰的方法不可以被重写。
  • 如果不允许子类重写父类的某个方法,则可以使用final修饰该方法。

示例:演示final方法使用

public class Bank { 
//将该方法定义为final,不允许子类重写该方法
public final void LowerInterestRates(){
System.out.println(“央行按照宏观经济情况进行统一降息,各银行按照国家标准统一执行");
}
}

public class ChinaBank extends Bank{
//如果子类重写父类的fianl()方法,程序在编译时将报错
public final void LowerInterestRates(){
}
}

4.2.4 final类


  • final修饰符修饰的类称为最终类,最终类不能有子类。
  • final类通常具有固定作用,用于完成某种标准功能。
  • final不能被继承以达到不能被修改的目的。

示例:演示final类使用

public final class Bank {  // final类
public void LowerInterestRates(){
System.out.println(“央行按照宏观经济情况进行统一降息,各银行按照国家标准统一执行");
}
}

public class ChinaBank extends Bank{ //报错,final类不能有子类

}
}

​4.2.5 实践练习​


4.3 接口

举例:生活中的接口

Java面向对象笔记 • 【第4章 抽象类和接口】_设计模式_09

Java中的接口同生活中的接口一样,体现的是一种能力。

4.3.1 接口的定义

语法:

[访问修饰符] interface  接口名  extends  父接口1,[父接口2,...]


说明:

接口的访问修饰符可以是public和缺省访问修饰符,如果省略public修饰符,系统默认使用缺省访问修饰符。

接口中只能定义公有的、静态的常量,并且这些常量默认都是公有的、静态的。

接口中的方法只能是公有的抽象方法,并且这些方法默认都是公有的、抽象的。

接口只能继承多个接口,接口不能继承类,也不能实现其他接口。


4.3.2 接口的实现

语法:

[访问修饰符]  class  类名  implements  接口1,[接口2,...]


说明:

接口是一种标准的体现。

接口不能用于创建实例,接口的主要作用是在设计程序时对其实现类进行规范和约束。

接口的主要用途就是被实现类实现。

一个类可以实现多个接口,从而实现多继承。


示例:设计一个具有输入功能,并且防水、防尘功能的键盘,以及一个防水、防尘和防盗功能的防盗门

public interface  Input { //输入接口
//定义输入的标准,由其实现类实现具体的实现细节
public abstract void input(); // abstract 可省略
}

public interface Function { //功能接口
//接口只能定义常量, 而且必须是静态常量
public static final String DEEP="30米";//防水深度
//防尘指数
int INDEX=5; //接口中的常量默认是公有的、静态、终极的
//防水功能
public abstract void waterproof();
//防尘功能
void dust(); //接口中的方法默认是公有的、抽象的
}


public interface ExtendsFunction extends Function { //扩展功能
void antiTheft(); //防盗
}
public class AntitheftDoor implements ExtendsFunction { //防盗门实现扩展功能接口
//实现防水功能
public void waterproof() {
System.out.println("我是防盗门采用高科技的防水技术,防水深度:"+DEEP);
}
//实现防尘功能
public void dust() {
System.out.println("我是防盗门采用纳米防尘技术防尘,防尘指数:"+INDEX+"颗星");
}
//实现防盗功能
public void antiTheft() {
System.out.println("我是防盗门采用密码匹配防盗技术");
}
}


public class Keyboard implements Function,Input{ //键盘类实现功能接口和输入接口
//实现防水功能
public void waterproof() {
System.out.println("我是键盘采用特殊的密封屏蔽技术实现防水,防水深度:"+DEEP);
}
//实现防尘功能
public void dust() {
System.out.println("我是键盘采用全硅胶材料实现防尘功能,防尘指数:"+INDEX+"颗星 ");
}
//实现Input接口中的输入功能
public void input() {
System.out.println("我是键盘可以将敲击键盘上的数据输入到计算机中");
}
}


public class Tset{
public static void main(String[] args) {
//创建键盘对象
Keyboard keyboard=new Keyboard();
keyboard.dust();//调用键盘的防尘方法
keyboard.waterproof();//调用键盘的防水方法
keyboard.input();//调用输入方法
System.out.println("------------------------------------");
//创建防盗门对象
AntitheftDoor antitheftDoor=new AntitheftDoor();
antitheftDoor.antiTheft();//调用防盗门的防盗方法
antitheftDoor.dust();//调用防盗门的防尘方法
antitheftDoor.waterproof();//调用防盗门的防水方法
}
}

Java面向对象笔记 • 【第4章 抽象类和接口】_编程语言_10

4.3.3 抽象类和接口的区别

相同点:

接口和抽象类都不能被实例化,它们都位于继承树的顶端,用于被其他类实现和继承。

接口和抽象类都可以包含抽像方法,实现接口或继承抽象类的普通子类都必须实现这些抽象方法。

不同点:

接口中只能包含抽象方法,而抽象类则完全可以包含普通方法。

接口中不能定义静态方法,而抽象类中可以定义静态方法。

接口中只能定义静态常量,不能定义普通变量,或非静态的常量,而抽象类中则可以定义不同的属性,也可以定义静态的属性。

接口中不包含构造器,而抽象类中可以包含构造器,抽象类中的构造器并不用于创建对象,而是让其子类调用这些构造器来完成抽象类的初始化操作。

一个类最多只能有一个直接父类,包括抽象类,而一个类可以实现多个接口。通过实现多个接口可以弥补Java单继承的不足。

​4.3.4 实践练习​


4.4 接口编程案例

什么是面向接口编程?

面向接口编程就是先把客户的业务逻辑线提取出来作为接口,接口业务的具体实现通过该接口的实现类来完成。

当客户需求变化时,只需编写该业务逻辑的新的实现类,通过更改配置文件中该接口的实现类就可以完成需求,而不需要改写现有代码,从而减少对系统的影响。

面向接口编程的优点:

降低程序的耦合性。在程序中紧密的联系并不是一件好的事情,因为两种事物之间联系越紧密,更换其中之一的难度就越大。

易于系统的扩展。

易于系统的维护。

4.4.1 接口编程实例

使用面向接口编程实现一个简易的用户管理系统

要求用户按照系统在控制台中的提示信息,做出相应的选择。

系统接收到用户输入的指令后,给出相应的提示信息。

Java面向对象笔记 • 【第4章 抽象类和接口】_java_11

创建dto包,在该包中定义类UserInfo用于封装用户信息。

public class UserInfo { //用户实体类
private String name;
private int age;
private String birthday;
private String address;

//省略属性的setter和getter方法
}

创建dao包,在该包中定义一个维护用户信息的接口UserInfoDao,在该接口中定义3个抽象方法,它们的功能分别是删除用户、更新用户信息和保存用户。

public interface UserInfoDao { //维护用户信息接口
public abstract void deleteUser(UserInfo user);
public abstract void updateUser(UserInfo user);
public abstract void saveUser(UserInfo user);
}

在该dao包中定义一个实现UserInfoDao的用户信息维护UserInfoDaoImpl,该类分别实现接口中所有的抽象方法。

public class UserInfoDaoImpl implements UserInfoDao { //实现用户信息维护接口
public void saveUser(UserInfo user) {
System.out.println("执行新增方法");
System.out.println("姓名:"+ user.getName() +" 年龄:"+ user.getAge() + " 生日:"+ user.getBirthday() + " 家庭地址:"+ user.getAddress());
}
public void updateUser(UserInfo user) {
System.out.println("执行更新方法");
}
public void deleteUser(UserInfo user) {
System.out.println("执行删除操作");
}
}

创建business包,在该包中定义UserInfoAccess类,在该类定义UserInfoDao对象属性,该值为实现UserInfoDao接口的具体对象。

在UserInfoAccess类定义服务方法service()实现主体业务。

public class UserInfoAccess {
private UserInfoDao userInfoDao=new UserInfoDaoImpl();

public void service(){
System.out.println("请选择操作【1】添加【2】修改【3】删除");
Scanner input=new Scanner(System.in);
String state=input.next();
if("1".equals(state)){
userInfoDao.saveUser();
}else if("2".equals(state)){
userInfoDao.updateUser();
}else if("3".equals(state)){
userInfoDao.deleteUser();
}
}
}

public class UserMain{
public static void main(String[] args) {
new UserInfoAccess().service();
}
}

​4.4.2 实践练习​


总结:

一个抽象类中可以不定义抽象方法,但是只要类中有一个抽象方法,则该类一定是抽象类。

抽象类不能被实例化,只能作为父类继承。

final关键字可以用于修饰类、变量和方法。final修饰变量时,表示该变量一旦获得了初始值就不可能被改变,final修饰的类不能被继承,final修饰的方法可以被继承但不能被重写。

接口中只能定义公有的、静态的常量,并且这些常量默认都是公有的、静态的;接口中的方法只能是公有的抽象方法,并且这些方法默认都是公有的、抽象的。

接口只能继承多个接口,接口不能继承类,也不能实现其他接口。