# 抽象方法
# 抽象方法格式和作用
抽象方法场景:父类知道子类一定要完成某个功能,但是每个子类实现的情况都不一样,父类的该功能就可以定义成抽象方法。
抽象方法:没有方法体的方法
抽象方法的格式:
public abstract 返回值类型 方法名(参数列表);
注意:抽象方法必须放在抽象类中。
父类中抽象方法有必要存在吗?
从设计的角度看,父类不写抽象方法,子类也可以不写这个方法,导致功能缺失,也容易造成扯皮。比如下面的例子中,如果我们不在 Card 中声明抽象类 pay,GoldCard 和 SliverCard 可能就没实现 pay 方法,Demo1 中调用 pay 就有会有问题,这个时候写 Card 类的人就有可能背锅,因为你没有强制要求写 GoldCard 和 SliverCard 类的人实现 pay 方法:
public class Demo1 {
public static void main(String[] args) {
GoldCard goldCard = new GoldCard("金卡", 1000);
goldCard.pay();
SliverCard sliverCard = new SliverCard("银卡", 1000);
sliverCard.pay();
}
}
public abstract class Card {
private String name;
private double balance;
public Card() {
}
public Card(String name, double balance) {
this.name = name;
this.balance = balance;
}
public abstract void pay();
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public double getBalance() {
return balance;
}
public void setBalance(double balance) {
this.balance = balance;
}
}
// 定义类继承抽象类 alt + 回车: 万能键,IDEA会给我们各种提示
public class GoldCard extends Card{
public GoldCard() {
}
public GoldCard(String name, double balance) {
super(name, balance);
}
@Override
public void pay() {
System.out.println("金卡打八折");
}
}
public class SliverCard extends Card {
public SliverCard() {
}
public SliverCard(String name, double balance) {
super(name, balance);
}
@Override
public void pay() {
System.out.println("银卡打八五折");
}
}
# 抽象类
# 抽象类格式
public abstract class 类名 {}
抽象类的使用步骤:
- 定义类继承抽象类
- 重写抽象方法
- 使用子类对象
抽象类设计目的:抽象类不能创建对象,让子类继承抽象类,重写抽象方法。
# 抽象类注意事项
- 抽象类不能创建对象
public class Demo1 {
public static void main(String[] args) {
// 'Card' is abstract; cannot be instantiated
Card card = new Card();
}
}
- 抽象类中有构造方法, 让构造方法给成员变量赋值
public class GoldCard extends Card{
public GoldCard() {
}
public GoldCard(String name, double balance) {
super(name, balance);
}
@Override
public void pay() {
System.out.println("金卡打八折");
}
}
- 抽象方法必须放在抽象类中, 抽象类中可以没有抽象方法
// 抽象方法必须放在抽象类中
// Class 'Parent' must either be declared abstract or implement abstract method 'test()' in 'Parent'
public class Test1 {
// Abstract method in non-abstract class
public abstract void test();
}
// 抽象类中可以没有抽象方法
abstract class Test2 {}
- 子类必须重写抽象类中的所有抽象方法.如果子类只重写一部分抽象方法,子类还是抽象类
abstract class Parent {
public abstract void fn1();
public abstract void fn2();
}
// Class 'Child' must either be declared abstract or implement abstract method 'fn1()' in 'Parent'
//class Child extends Parent {
//
//}
//Class 'Child' must either be declared abstract or implement abstract method 'fn2()' in 'Parent'
//class Child extends Parent{
// @Override
// public void fn1() {
//
// }
//}
// 如果子类只重写一部分抽象方法,子类还是抽象类
abstract class Child extends Parent {}
class Child extends Parent {
@Override
public void fn1() {
}
@Override
public void fn2() {
}
}
# 模板方法模式
什么是设计模式(Design pattern)
- 软件设计模式是一套被前人反复使用、多数人知晓的、经过分类编目的、代码设计经验的总结。
- 使用设计模式是为了可重用代码、让代码更容易被他人理解、保证代码可靠性。
现在学习 Java 中常用的设计模式之一:模板方法模式
生活中的模板:
什么时候使用模板方法模式?当项目中多个子类有相同功能,而且大部分代码是一样的,只有部分不同的时候。
需求
某加油站推出了2种支付卡,一种是金卡,后续加油享受8折优惠。
另一种是银卡 ,后续加油享受8.5折优惠。
请分别实现2种卡片进入收银系统后的逻辑,卡片需要包含主人名称,余额,支付功能。
支付功能:1.输入用户名和密码、2.登录成功、3.扣款、4.打印消费额。
分析:
1. 创建一个抽象的卡片类Card作为父类模板
2. 在父类Card中提供一个模板方法实现支付要先输入用户名和密码、登录成功、扣款、打印消费额。
3. 具体的扣款定义成抽象方法,交给子类实现
4. 定义金卡(GoldCard)类,让子类重写实现具体的扣款方法
5. 定义银卡(SilverCard)类,让子类重写实现具体的扣款方法
直接设计完成该案例,存在什么问题?代码重复度过高。
使用模板方法模式设计将 2 种卡片通用的代码抽成一个父类模板。
public class Demo {
public static void main(String[] args) {
GoldCard goldCard = new GoldCard();
goldCard.pay(500);
System.out.println("--------------------");
SilverCard silverCard = new SilverCard();
silverCard.pay(400);
}
}
public abstract class Card {
private String name;
private double balance;
public Card() {
}
public Card(String name, double balance) {
this.name = name;
this.balance = balance;
}
public abstract double payment(double money);
public void pay(double money) {
System.out.println("请输入账号密码");
System.out.println("登陆成功");
double payment = payment(money);
System.out.println("总共花费了" + payment + "元");
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public double getBalance() {
return balance;
}
public void setBalance(double balance) {
this.balance = balance;
}
}
public class GoldCard extends Card {
public GoldCard() {
}
public GoldCard(String name, double balance) {
super(name, balance);
}
@Override
public double payment(double money) {
return money * 0.8;
}
}
public class SilverCard extends Card {
public SilverCard() {
}
public SilverCard(String name, double balance) {
super(name, balance);
}
@Override
public double payment(double money) {
return money * 0.85;
}
}
抽象类中调用自己的抽象方法,调用的是相应子类的实现的抽象方法。
总结:
模板设计模式思想总结:
# 接口
# 接口的概念和格式
什么是接口?
接口可以理解为比抽象类还抽象的类,接口中全是抽象方法。接口体现的是规范,接口中的抽象方法定义的一组行为规范,体现了现实世界中“事物遵守某种规范就必须拥有规定的行为”的思想。
接口存在的两个重要意义:
- 定义统一规范,约束一类事物的功能设计
- 为后续软件设计,提高程序的扩展性(后续了解)
接口的使用:接口不能直接创建对象。接口是用来被类实现(implements)的,实现接口的类称为实现类。实现类可以理解成所谓的子类。
实现类的格式
class 实现类 implements 接口1, 接口2 {
}
接口实现的注意事项:一个类实现接口,必须重写完全部接口的全部抽象方法,否则这个类需要定义成抽象类。
# 案例-电脑 USB 接口
笔记本电脑会提供USB接口,只要符合USB接口的设备都能连接电脑。定义USB鼠标和USB键盘。
实现步骤
1. 定义USB接口包含(连接电脑, 传输数据, 断开连接)
2. 定义USB鼠标实现USB接口
3. 定义USB键盘实现USB接口
public class Demo {
public static void main(String[] args) {
UsbKeyboard usbKeyboard = new UsbKeyboard();
usbKeyboard.connect();
usbKeyboard.sendData();
usbKeyboard.disconnect();
System.out.println("-----------");
UsbMouse usbMouse = new UsbMouse();
usbMouse.connect();
usbMouse.sendData();
usbMouse.disconnect();
}
}
public interface USB {
public abstract void connect();
public abstract void disconnect();
public abstract void sendData();
}
public class UsbKeyboard implements USB {
@Override
public void connect() {
System.out.println("键盘连接了");
}
@Override
public void disconnect() {
System.out.println("键盘断开连接了");
}
@Override
public void sendData() {
System.out.println("键盘传输数据了");
}
}
public class UsbMouse implements USB {
@Override
public void connect() {
System.out.println("鼠标连接了");
}
@Override
public void disconnect() {
System.out.println("鼠标断开连接了");
}
@Override
public void sendData() {
System.out.println("鼠标传输数据了");
}
}
# 接口中成员的特点
- 接口中的方法默认就是抽象方法,会自动添加: public abstract
public interface USB {
public abstract void connect();
// 可以看到,不加 public abstract 也不会报错
void disconnect();
}
- 接口中是常量,会自动添加: public static final
public interface USB {
public static final int money = 100;
// 接口中是常量,会自动添加: public static final
String name = "usb";
}
# 实现多个接口
Java 中类只能单继承,一个类只有一个父类:
class 子类 extends 父类 {}
一个类可以同时实现多个接口:
class 实现类 implements 接口1, 接口2 {}
# 接口与接口的多继承
一个接口可以同时继承多个接口:
interface 接口名 extends 接口1, 接口2 {}
接口多继承的作用:规范合并,整合多个接口为同一个接口,便于子类实现。
# JDK8 后接口新增方法
JDK8 版本开始后,Java 只对接口的成员方法进行了改进:
- 默认方法(就是类似之前写的普通实例方法):必须用 default 修饰。需要用接口的实现类的对象来调用。
// public default 返回值类型 方法名(参数列表) {}
public interface USB {
public default void fn1() {}
}
- 静态方法: 默认会 public 修饰。注意:接口的静态方法必须用本身的接口名来调用。
// public static 返回值类型 方法名() {}
public interface USB {
public static void fn2() {};
}
- 私有方法(就是私有的实例方法): JDK 9 才开始有的。只能在本类中被其他的默认方法或者私有方法访问。
// private 返回值类型 方法名() {}
public interface USB {
private void fn3() {}
}
# 接口和抽象类的区别
# 包
# 包的概念和作用
包的概念:包类似于 Windows 系统中的文件夹、通过包可以分门别类的管理各种不同的类和资源。
新建包:
声明包的语法格式:package 包名; 报名建议全部英文小写,且具备意义。
package demo05接口的定义和使用;
public interface USB {}
# 包的概念和导入包
怎么导包?相同包下的类可以直接访问。不同包下的类必须导包,才可以使用!
导包格式:
// import 包名.类名;
import java.util.Random;
# 权限修饰符
# 四个权限修饰符的作用
权限修饰符概述:是用来控制一个类中成员能够被访问的范围。可以修饰成员变量,成员方法,构造器,内部类。
权限修饰符分类和作用:权限修饰符有四种,作用范围由小到大(private -> 缺省 -> protected - > public)
| 修饰符 | 同一个类中 | 同一个包中 其他类 | 不同包下的 子类 | 不同包下的 无关类 |
|---|---|---|---|---|
| public | √ | √ | √ | √ |
| protected | √ | √ | √ |
| | 缺省 | √ | √ |
|
| | private | √ |
|
|
|
# 总结
- 常用的
- private: 私有的,只有本类可以使用
- public: 公开的,所有地方都能使用
- 不常用的
- 缺省的(默认的),给本包的类使用
- protected: 受保护的,给子类使用
- 自己定义成员(方法,成员变量,构造器等)一般满足如下要求
# final
# final 关键字修饰类和方法的特点
final 的作用:final 关键字是最终、不可变的意思,可以修饰类,方法,变量。
final 修饰的特点:
- 修饰类:表明该类是最终类,不能被继承。
- 修饰方法:表明该方法是最终方法,不能被重写。
- 修饰变量:表明该变量是常量,表示该变量第一次赋值后,不能多次被赋值。
final 修饰变量的注意:
# 总结
final 关键字修饰的特点
# final 关键字变量的特点
final 的作用:final 关键字是最终、不可变的意思,可以修饰类,方法,变量。
# 代码块
# 代码块格式和特点
代码块介绍:代码块是类的 5 大成分之一(成员变量、构造器,方法,代码块,内部类),定义在类中方法外。在 Java 类下,使用 { } 括起来的代码被称为代码块 。
代码块分类:
- 静态代码块
- 格式:static{}
- 特点:需要通过 static 关键字修饰,随着类的加载而加载,并且自动触发、只执行一次
- 使用场景:在类加载的时候做一些静态数据初始化的操作,以便后续使用。
public class Student {
static {
System.out.println("我是静态代码块!");
}
}
- 构造代码块(了解,几乎不用)
- 格式:{}
- 特点:每次创建对象,调用构造器执行前都会执行该代码块中的代码
- 使用场景:初始化实例资源。放在类中方法外(成员位置)
public class Student {
{
System.out.println("我是构造代码块!");
}
}
# 案例-斗地主
需求
在启动游戏房间的时候,应该提前准备好54张牌,后续才可以直接使用这些牌数据。
分析
1. 当系统启动的同时需要准备好数据的时候,就可以用静态代码块了。
2. 该房间只需要一副牌。
3. 定义一个静态的数组存储54张牌对象,静态的数组只会加载一份。
4. 在启动游戏房间前,应该将54张牌初始化好。
5. 使用静态代码块进行静态数组的数据初始化,将54张牌存入到静态数组中去。