今天总结了抽象类的概念和使用方法及其,明白了其使用的环境。总结了抽象类的使用特点和优缺点。然后根据之前的内容做了相关的练习题,回顾了面向对象编程的三大特征。理解了其使用方法和环境
抽象类
面向对象编程的核心是面向抽象编程,一般依赖抽象不依赖具体。
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
public class A {
public static void main(String[] args) {
// Fa f1=new Fa(); //抽象类不允许直接实例化操作,这是语意决定的,是规则,和抽象类中是否包含抽象方法,是否有构造器无关
new Son(10).pp();
}
}
abstract class Fa {
// 定义抽象类时可以包含抽象方法,也可以没有抽象
public Fa() {
// 可以定义构造器
System.out.println("build Fa.");
}
// public abstract Fa(int k);没有抽象构造器的概念
public void pp() {
// 抽象类中可以定义成员
System.out.println("Fa...pp()");
}
public abstract void cc(); // 抽象方法
public static void dd() {
}// 在抽象类中可以定义静态成员,但是静态方法不能为抽象
}
class Son extends Fa {
// 子类必须直接或者间接的给父类中的所有抽象方法提供实现,否则子
类必须抽象
public Son(int k) {
// super(); 要么使用this()或者super()作为第一句,也可以不写,不写则默认为
super()
System.out.println("Son" + k);
}
@Override
public void cc() {
}
}
1.能定义抽象构造函数
- 抽象类中可以定义构造器,也可以不定义构造器,使用系统默认提供的无参构造器,但是自定义构
- 造器不能private抽象类不能是Õnal class,Õnal类不能被继承,从语法的角度上说不允许继承,不是构造器的原因
2.不能定义抽象静态方法
- 抽象类中可以有静态方法,但是必须有方法体,不能是抽象方法
- 允许抽象类直接调用静态方法
- 抽象类不能直接创建对象,只能通过继承的方式由子类实现对应的抽象方法;
3.所有抽象类的子类必须实现抽象父类中的所有抽象方法或者自己也声明成抽象类[没有实现所有的抽象方法]
4.抽象类除了可以有抽象方法,也可以有非抽象方法,允许静态方法【没有抽象静态方法的概念】没有任何限制,允许属性、方法,也允许抽象方法
抽象类不可以被实例化
是因为调用抽象方法没有意义?
抽象类必须有其子类覆盖了所有的抽象方法后,该子类才可以实例化,否则这个子类还是抽象类
强调:注意抽象类中可以包含构造器、析构器、抽象方法和方法以及静态方法等,也可以没有抽象方法
什么时候使用抽象类
- 当一个类的一个或多个方法为抽象方法时
- 当该类为一个抽象类的子类,并且没有为所有抽象方法提供实现细节或方法主体时
- 当一个类实现一个接口,并且没有为所有抽象方法提供实现细节或方法主体时
抽象类和普通类的区别
- 抽象类不能直接实例化,并且对抽象类使用 new 运算符是编译时错误
- 抽象类允许(但不要求)抽象类包含抽象成
- 抽象类不能被密封,简单说就是被Õnal修饰的类,密封类不能被继承,防止了恶意的派生
什么是模板模式
在模板模式Template Pattern中一个抽象类公开定义了总体的算法【算法骨架】,把没有办法在父类中实现的方法
延迟到子类中具体实现。这种类型的设计模式属于行为型模式
何时使用
有多个子类共有的方法,且逻辑相同
重要的、复杂的方法,可以考虑作为模板方法
注意事项
为防止恶意操作,一般模板方法都加上 Õnal 关键词
优点
封装不变部分,扩展可变部分
提取公共代码,便于维护
行为由父类控制,子类实现。
缺点
每一个不同的实现都需要一个子类来实现,导致类的个数增加,使得系统更加庞大
分析
冒泡排序算法固定可以定义在父类中,但是两个比较的算法不同确定,所以可以将具体比较算法延迟到子类中实现。抽象父类中定义算法模板,具体的比较延迟到子类中进行实现
public abstract class BubbleSorter {
// 在父类中固化算法实现,没有办法实现的比较方法延迟到子类中提供实现
public final void sort(Object[] arr) {
// final表示这个方法是最终方法,不允许子类覆盖
for (int i = 1; i < arr.length; i++) {
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];
arr[k + 1] = temp;
}
}
}
}
// 定义protected目的在于子类中提供实现
protected abstract boolean bigger(Object obj1, Object obj2);
}
具体的比较器,抽象父类的目的在于固化算法

//专门用于对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();
}
}
定义猪的比较器 实现猪的排序
描述猪—定义猪这个类
public class PigSorter extends BubbleSorter {
@Override
protected boolean bigger(Object obj1, Object obj2) {
if (obj1 != null && obj2 != null) {
if (obj1 instanceof Pig && obj2 instanceof Pig) {
Pig p1 = (Pig) obj1;
Pig p2 = (Pig) obj2;
if (p1.getColor().equals(p2.getColor())) {
return p1.getWeight() - p2.getWeight() > 1e-6;
} else {
String col1 = p1.getColor();
String col2 = p2.getColor();
if ("黑色".equals(col1) && "白色".equals(col2))
return true;
}
}
}
return false;
}
}
public class Test1 {
public static void main(String[] args) {
Pig[] arr=new Pig[10];
Random r=new Random();
for(int i=0;i<arr.length;i++) {
//第一种写法比较繁琐
// Pig tmp=new Pig();
// tmp.setColor(r.nextBoolean()?"黑色":"白色");
// tmp.setWeight(r.nextInt(200)+200);
Pig tmp=new Pig(r.nextBoolean()?"黑色":"白色",r.nextInt(200)+200);
arr[i]=tmp;
}
int counter=0;
for(Pig temp:arr) {
counter++;
System.out.print(temp+"\t");
if(counter%3==0)
System.out.println();
}
System.out.println("\n===============================");
BubbleSorter bs=new PigSorter();
bs.sort(arr);
counter=0;
for(Pig temp:arr) {
counter++;
System.out.print(temp+"\t");
if(counter%3==0)
System.out.println();
}
}
}
抽象类的作用
在面向对象方法中,抽象类主要用来进行类型隐藏。构造出一个固定的一组行为的抽象描述,但是这组行为却能够有任意个可能的具体实现方式。这个抽象描述就是抽象类,而这一组任意个可能的具体实现则表现为所有可能的派生类。模块可以操作一个抽象体。由于模块依赖于一个固定的抽象体,因此它可以是不允许修改的通过从这个抽象体派生,也可扩展此模块的行为功能。为了能够实现面向对象设计的一个最核心的原则开闭原则
OCP,抽象类是其中的关键所在
抽象类往往用来表征对问题领域进行分析、设计中得出的抽象概念,是对一系列看上去不同,但是本质上相同的具体概念的抽象
OOP的核心是面向抽象编程
在父类中固化算法骨架,在子类中实现特殊的方法依赖抽象
类和类之间的耦合问题
OOP要求类内高内聚、类间弱耦合—客户需求变动
如何实现类和类之间松耦合—使用抽象不用具体
public class A{
private BubbleSorter bs;//PigSorter
}
练习题
1、为“无名的粉”写一个类class WuMingFen
要求: 1.有三个属性 面码:String theMa 粉的分量(两) int quantity 是否带汤 boolean likeSoup
//一般情况下,不允许定义类在default包中,因为default包中的类其它包无法访问
//包名称的命名规则:全小写,采用域名反转
//类名称,一般名词,采用大写字母开头,大写字母分词
public class WuMingFen {
private String theMa;
private int quantity;// 采用简单类型定义属性,最大的问题是很难判定是默认值还是赋的值
private boolean likeSoup = true;// 按照一般的业务规则,可以一般有汤
//一般在具体开发中,建议采用私有属性,公有的get/set方法的形式定义属性
public String getTheMa() {
return theMa;
}
//业务规则:面码一旦告知则不能修改,所以可以不提供这个set方法,则面码属性就是只读属性
public void setTheMa(String theMa) {
this.theMa = theMa;
}
public int getQuantity() {
return quantity;
}
public void setQuantity(int quantity) {
//通过get/set方法可以进行一些参数的合法性验证
if (quantity < 1)
quantity = 1; // 也可以通过抛出异常终程序运行 throw new RuntimeException()
this.quantity = quantity;
}
public boolean isLikeSoup() {
//如果针对boolean类型的属性,默认get方法的命名规则是isXxxx不
//是getXxx;但是如果将属性修改为Boolean,则还是getxxx
return likeSoup;
}
public void setLikeSoup(boolean likeSoup) {
this.likeSoup = likeSoup;
}
}
2.写一个构造方法 以便于简化初始化过程 如 WuMingFen f1 = new WuMingFen(“牛肉”,3,true);
public class Fen {
public Fen(int k) {
}
}
//可以看到class WuMingFen extends Fen则super()报错,因为Fen类中没有无参构造器
public WuMingFen(String theMa, int quantity, boolean likeSoup) {
super(10); //如果父类中没有无参构造器则调用父类的带参构造器则是必须的
this.theMa = theMa;
this.quantity = quantity;
this.likeSoup = likeSoup;
}
3.重载构造方法 使得初始化过程可以多样化 WuMingFen f2 = new WuMingFen(“牛肉”,2);
//方法的重载
//方法名称相同,参数不同[个数、顺序、类型],和返回值类型无关,和参数名称无关
public WuMingFen(String theMa,int quantity){
this(theMa,quantity,true);//调用上面定义的构造器,并传入相关的参数
}
4.如何使得下列语句构造出来的粉对象是酸辣面码、2两、带汤的 WuMingFen f3 = new WuMingFen();
//方法的重在,但是按照业务规则,给属性赋默认值
public WuMingFen(){
//方法1:酸辣面码、2两、带汤
this.likeSoup=true;
this.theMa="麻辣";
this.quantity=2;
//方法2 this("麻辣",2,true);
}
5.写一个普通方法 check() 用于查看粉是否符合要求。即将对象的三个属性打印在控制台上
/public void check() {
System.out.println("无名粉:面码" + this.theMa + ",重量" + quantity + "," + (likeSoup
? "有汤" : "无汤"));
}