《java基础》-胖菜鸟说抽象类
先扯两句
好吧,不知不觉又过了好些天了,之前类说完了,这抽象类就一直没开始,这拖延症的毛病是好不了了啊!不说了,直接开始吧。
正文
上一篇说了老头子我找女朋友的事,可厚着脸皮说了这么多,根本就没有人来啊,所以之前的那些种种的要求,不过是我自己的梦中情人罢了,根本就没办法在现实中new出来一个可触控的女朋友。
梦中情人而为了找到这个传说中的女朋友,就只能将之前的所谓“要求”做一些修改,可能是降低要求,或者干脆就直接去掉某条要求。为了不让自己注孤生,这个时候就不能用之前创建的老头子我自己的实体类了,而是要改成抽象类。那么什么是抽象类呢?
说人话抽象类往往用来表征对问题领域进行分析、设计中得出的抽象概念,是对一系列看上去不同,但是本质上相同的具体概念的抽象。
好吧,强行借由找女朋友的我来翻译一波就是,看一个女生,第一反应肯定是看相貌、之后交流看谈吐、然后一起出去玩时看爱好、最后同居看是不是打呼噜(说我老流氓的自己默默自己的良心,你想看的能写吗!)。
/**
* 女朋友
*/
public class Girl {
/**
* 是否长得好看
*/
private boolean isLookOk;
/**
* 是否谈吐让人舒服
*/
private boolean isTalkOk;
/**
* 爱好是否相合
*/
private boolean isHobbyOk;
/**
* 晚上睡觉不打呼噜
*/
private boolean isNotSnoring;
public Girl(boolean isLookOk, boolean isTalkOk, boolean isHobbyOk, boolean isNotSnoring) {
this.isLookOk = isLookOk;
this.isTalkOk = isTalkOk;
this.isHobbyOk = isHobbyOk;
this.isNotSnoring = isNotSnoring;
}
public boolean isLookOk() {
return isLookOk;
}
public boolean isTalkOk() {
return isTalkOk;
}
public boolean isHobbyOk() {
return isHobbyOk;
}
public boolean isNotSnoring() {
return isNotSnoring;
}
}
有了女孩这个类后,再来看看我们的半寿翁的抽象类,可以看到其实抽象类其实很好创建,不过是在正常创建类的基础上添加一个abstract属性罢了,你猜这个词翻译成汉语是什么意思?
public abstract class ABanShouWeng {
/**
* 姓名
*/
private static String name;
/**
* 构造方法
*
* @param name 姓名
*/
public ABanShouWeng(String name) {
openEyes();
if (null == name || name.isEmpty()) {
ABanShouWeng.name = name;
}
}
/**
* 睁开眼睛
*/
private void openEyes() {
System.out.println("睡醒了,又该相亲去了");
}
protected String getName() {
return name;
}
/**
* 不满足女朋友条件的程度
*/
protected boolean meetTheRequirements = true;
/**
* 判断是否符合条件
*
* @param girl 被判断的女孩
*/
protected abstract void judge(Girl girl);
/**
* 相貌判断
*
* @param girl 女孩
* @return 是否可以
*/
protected final boolean isLookOk(Girl girl) {
return null != girl && girl.isLookOk();
}
/**
* 谈吐判断
*
* @param girl 女孩
* @return 是否可以
*/
protected final boolean isTalkOk(Girl girl) {
return null != girl && girl.isTalkOk();
}
/**
* 爱好判断
*
* @param girl 女孩
* @return 是否可以
*/
protected boolean isHobbyOk(Girl girl) {
return null != girl && girl.isHobbyOk();
}
/**
* 打呼噜判断
*
* @param girl 女孩
* @return 是否可以
*/
protected final boolean isNotSnoring(Girl girl) {
return null != girl && girl.isNotSnoring();
}
}
包含
类的特性
抽象类、抽象类、很明显它就是一个抽象的类。
少说废话 好吧,别开枪,我的意思是抽象类就是抽象的类,那就必然都有类的特性。而关于类的介绍,请大家移步上一篇胖菜鸟说类
中有相应介绍,这里就不多占用各位的时间了,我们来进入抽象类特有的特性。
抽象类的构造方法
/**
* 构造方法
*
* @param name 姓名
*/
public ABanShouWeng(String name) {
openEyes();
if (null == name || name.isEmpty()) {
ABanShouWeng.name = name;
}
}
在类的介绍中有说到过构造方法,就是在构造这个类的时候用到的,而抽象方法,因为一般情况下不会直接构造,而是通过其他的子类继承(Java 继承)的方式来具体实现相应的功能,也就是说构造的时候基本都是构造的子类,那抽象类的构造方法一般都没什么用啊,那为什么还需要构造方法呢?我们先上图:
构造方法提示显而易见,当我们的子类继承了抽象类以后,会提示我们“在当前的BanShouWeng子类中没有实现抽象方法ABanShouWeng中的个非默认的构造方法”错误信息,这样就可以提示子类,在构造的时候需要录入哪些必要参数。就好像这里,老头子我肯定是有名字的,所以就需要初始化赋值,这样在后面的getName()方法调用的时候就不会出现异常,否则你不给我取名字凭啥问我叫啥啊。
当然,可以看到这里使用的姓名是静态方法,虽然正常情况下我们应该能不用静态属性和静态方法尽量不要使用,毕竟这东西还是耗内存的(如果不考虑性能,那就随便了),这里主要是阐述一种情况,大家在正常使用的时候可以采用相应的缓存机制实现这个姓名(或相类似的东西)存储。那就是老头子我的名字叫半寿翁,但是我不能每天早起第一件事就是给自己取名字叫“半寿翁”吧,毕竟取名字这个事,如果没有特殊的需求的情况下,一般人一辈子一次就可以了,所以这里加了一个判断,当每天起来的时候,发现自己出生的时候有名字了,那就不需要取名字了。当然如果你真的非要中间改名字,大不了构造方法做一下调整呗,更复杂的初始化大家就根据自己的心情随便玩吧:
/**
* 构造方法
*
* @param name 姓名
* @param isChangeName 是否改名
*/
public ABanShouWeng(String name, boolean isChangeName) {
if (null == name || name.isEmpty() || isChangeName) {
ABanShouWeng.name = name;
}
openEyes();
}
除此之外,因为在创建类的时候,我们总会需要初始化一些信息,比如在这个例子中,我们人(好吧,也有先摸到手机再睁眼的,还好这里我举例的抽象类是:半寿翁)每天醒来做的第一件事就是睁开眼睛,所以对于这种在类构造的第一时间就需要执行的方法,也可以放在抽象类的构造方法中,这样就可以保证无论老头子我哪天起床第一件事都是睁眼睛了。
PS:上面说的主要是正常的编程思维的操作流程,我们需要依照抽象类的构造方法在子类中重写,以保证能够正常初始化,从而使抽象类中的方法能够正常运行。但是如果非要作死,自己额外创建一些构造方法,从而避过抽象类中的构造方法,在编译的时候也不会提示错误,但是当程序运行后会带来什么样的惊喜就看你的运气了,这里是不建议大家这么做的。
再PS:如果抽象方法中的构造方法真的不够我们子类中使用,而需要添加一些参数的时候,由于重载了构造得到,也会导致我们的抽象类的构造方法无法执行,但是这也是在开发过程中最最常见的情况了。比如奇葩说中一位伟大的哲人杨奇函曾经说过,他会每晚问自己,“今天你比昨天更博学了吗?”
比昨天更博学 /**
* 构造方法
*
* @param name 姓名
* @param knowledge 知识
*/
public BanShouWeng(String name, byte[] knowledge) {
super(name);
}
也就是将我们让需要传递到抽象类中参数,使用 字段传递过去即可,至于其他我们学到了什么指示,那是子类自己的事,抽象方法才懒得去管。当然,如果这个实现的子类比较淘气,非要好多个构造方法呢,这个时候,有两种解决方法:
方法1
/**
* 构造方法
*
* @param name 姓名
*/
public BanShouWeng(String name) {
super(name, null);
init();
}
/**
* 构造方法
*
* @param name 姓名
* @param knowledge 知识
*/
public BanShouWeng(String name, byte[] knowledge) {
super(name);
init();
}
方法1实际上就是基于之前说的多参情况的一种拓展。可以更加方便每种构造方法的独立执行,相对方法2会少一步构造方法之间相互调用的操作,相对而言会速度快一些,但是相对的缺点就是当我们有一些初始化方法的时候,这里就需要在每一个构造方法中都添加一个init()方法。
- 优点:每种构造方法的独立执行,相对而言速度会快一些
- 缺点:当需要在子类中调用一些初始化方法的时候,需要为每一个构造方法添加对应的方法调用,相对较麻烦
方法2
/**
* 构造方法
*
* @param name 姓名
*/
public BanShouWeng(String name) {
this(name, null);
}
/**
* 构造方法
*
* @param name 姓名
* @param knowledge 知识
*/
public BanShouWeng(String name, byte[] knowledge) {
super(name);
init();
}
方法2中的 字段浅显的解释就是调用自身的全局属性,类似于"this.name = name;"就是将局部变量的name赋值给全部变量name,而构造方法同样,这里会优先执行BanShouWeng(String name)构造方法,然后因为this方法,会自动再调用复核两个参数条件(knowledge复制为null了)的构造方法 BanShouWeng(String name, byte[] knowledge)。由于这里存在方法
抽象方法
/**
* 判断是否符合条件
*
* @param girl 被判断的女孩
*/
protected abstract void judge(Girl girl);
- 优点:构造方法都关联在一起,因此只需要在参数最多的构造方法中添加上初始化方法就可以了,更符合封装的思想
- 缺点:各个构造方法之间相互调用,相对而言速度会慢一些
前面在介绍抽象类的时候,背景就是原本我们明确的需求发生了变化(找不到女朋友,所以降低要求),为了兼容这种变化,而进行的一种对象的封装(封装相同的属性),而这种变化的具体实现就需要我们的抽象方法提供相应的提示:
抽象方法提示可以看到,万一哪天老头子我起床的后,在外面遇到女孩忘了看看是否符合自己择偶标准的时候,由于继承了构造方法,它会在当天的我起床之前主动提醒我“今天的BanShouWeng要么就必须也生命成抽象的方法,不然就得事项抽象方法ABanShouWeng的judge方法”,如果我不按照这个要求来调整的话,那就永远别想起床了(编译无法通过,程序永远别想运行)。
说到底抽象方法,其存在的目的就是为了提供个方法用于在子类中重写,以使得我们的程序在使用过程中能够更加的灵活,免得产品让改个需求,我们就要重构一次低层,较小风险。
final方法
/**
* 打呼噜判断
*
* @param girl 女孩
* @return 是否可以
*/
protected final boolean isNotSnoring(Girl girl) {
return null != girl && girl.isNotSnoring();
}
刚刚说到抽象方法,其目的是为了在子类中修改,既然这样有提供来改的,就肯定有提供出来不允许改的,刚找工作的时候住的床位,三个打呼噜的,晚上睡觉的时候,呼噜能直接连起来,从那以后对呼噜就产生了心理阴影,所以无论这个女孩的任何情况我都能忍,就是打呼噜这项坚决不行。而调整的方法就是在方法中添加 final 字段。
final 的意思就是最后,所以这个方法前面添加了final之后,就是意味着,这个方法就是这样了,谁都不允许改,所以在子类中想要重写方法的时候可以看到,ABanShouWeng的四个用于判断是否可以做女朋友的条件中,只有isHobbyOk(Girl girl)没有添加final字段,因此在子类中,想要重写判断条件的时候,就只有这一个方法可以联想出来:
重写联想那么我们不用联想,直接重写会怎么样呢?
禁止重写显而易见报错了:“isNotSnoring(Girl)方法不能重写,因为在ABanShouWeng抽象类中被重写的isNotSnoring(Girl)方法是以final修饰的”
抽象类实例
第一轮相亲(默认情况)
我们的抽象类半寿翁创建好了,那么就该出去相亲了。4月1日,很好,这个日期好像预示着什么,还是先看看不怕死的老头子我愚人节相亲,对女孩的要求都有哪些:
public class BanShouWeng_4_1 extends ABanShouWeng {
public BanShouWeng_4_1(String name) {
super(name);
}
@Override
protected void judge(Girl girl) {
boolean isLookOk = isLookOk(girl);
meetTheRequirements = meetTheRequirements & isLookOk;
System.out.println(MessageFormat.format("{0}长得好{1}", girl.hashCode(), isLookOk ? "漂亮" : "一般"));
boolean isTalkOk = isTalkOk(girl);
meetTheRequirements = meetTheRequirements & isTalkOk;
System.out.println(MessageFormat.format("{0}聊{1}", girl.hashCode(), isTalkOk ? "不够啊!" : "到啥时候结束啊!"));
boolean isHobbyOk = isHobbyOk(girl);
meetTheRequirements = meetTheRequirements & isHobbyOk;
System.out.println(MessageFormat.format("{0}爱好{1}", girl.hashCode(), isHobbyOk ? "跟我一样啊!" : "真活久见!"));
boolean isNotSnoring = isNotSnoring(girl);
meetTheRequirements = meetTheRequirements & isNotSnoring;
System.out.println(MessageFormat.format("{0}呼噜{1}", girl.hashCode(), isNotSnoring ? "都不打一个,真好" : "响彻天际啊!"));
System.out.println(MessageFormat.format("[0]看{1}", getName(), meetTheRequirements ? "自己终于可以脱单了" : "还是算了吧"));
}
}
很好,成年人不做选择,所有的条件都必须满足!让我们来看看年轻的结果吧:
@Test
public void makeFriend() {
new BanShouWeng_4_1("半寿翁")
.judge(
new Girl(true
, true
, false
, true));
}
爱好不合
有一点爱好不合,显然已失败告终。
第二轮相亲(普通方法重写)
经过第一天相亲失败的教训,4月2号感觉爱好不合其实也可以慢慢培养嘛,所以无论什么爱好都好,只要其他满足就可以。
/**
* 4月2日的半寿翁
*/
public class BanShouWeng_4_2 extends ABanShouWeng {
public BanShouWeng_4_2(String name) {
super(name);
}
@Override
protected void judge(Girl girl) {
boolean isLookOk = isLookOk(girl);
meetTheRequirements = meetTheRequirements & isLookOk;
System.out.println(MessageFormat.format("{0}长得好{1}", girl.hashCode(), isLookOk ? "漂亮" : "一般"));
boolean isTalkOk = isTalkOk(girl);
meetTheRequirements = meetTheRequirements & isTalkOk;
System.out.println(MessageFormat.format("{0}聊{1}", girl.hashCode(), isTalkOk ? "不够啊!" : "到啥时候结束啊!"));
boolean isHobbyOk = isHobbyOk(girl);
meetTheRequirements = meetTheRequirements & isHobbyOk;
System.out.println(MessageFormat.format("{0}爱好{1}", girl.hashCode(), isHobbyOk ? "跟我一样啊!" : "真活久见!"));
boolean isNotSnoring = isNotSnoring(girl);
meetTheRequirements = meetTheRequirements & isNotSnoring;
System.out.println(MessageFormat.format("{0}呼噜{1}", girl.hashCode(), isNotSnoring ? "都不打一个,真好" : "响彻天际啊!"));
System.out.println(MessageFormat.format("[0]看{1}", getName(),meetTheRequirements ? "自己终于可以脱单了" : "还是算了吧"));
}
/**
* 放弃思考,妹子爱好啥我都喜欢
*
* @param girl 女孩
* @return 是否可以
*/
@Override
protected boolean isHobbyOk(Girl girl) {
return true;
}
}
那么放弃了爱好这个条件后,老头子我运气怎么样呢?
@Test
public void makeFriend() {
new BanShouWeng_4_2("半寿翁")
.judge(
new Girl(false
, false
, false
, true));
}
相貌和谈吐也不行
虽然放弃了爱好这一项,结果相貌和谈吐也不行,于是又失败了。
第三轮相亲(抽象方法调整)
经过连续两天的大家,老头子我终于幡然悔悟,自己都这样的,还奢求啥啊,也别要求那么多了,只要来满足两点就可以:女的、活的。
/**
* 4月3日的半寿翁
*/
public class BanShouWeng_4_3 extends ABanShouWeng {
public BanShouWeng_4_3(String name) {
super(name);
}
@Override
protected void judge(Girl girl) {
// Girl肯定是女孩,不为空就是活的
meetTheRequirements = null != girl;
System.out.println(MessageFormat.format("[0]看{1}", getName(), meetTheRequirements ? "自己终于可以脱单了" : "还是算了吧"));
}
}
这次大家可以提前祝福我脱单了,但是还是希望大家处于出于礼貌和同情心来看一下结果吧
@Test
public void makeFriend() {
new BanShouWeng_4_3("半寿翁")
.judge(
new Girl(false
, false
, false
, false));
}
脱单了
好吧,真不知道这算不算一个happy ending。只希望有不熟悉抽象类的看了这篇文章后能知道抽象类究竟是个什么、怎么用吧。在最后送大家一个祝福,遇到合适的她就赶快牵手吧。好了,不说了,让老头子我先去哭会,希望自己别因为这篇文章就找个完全不合自己心意的女孩,童言无忌啊!
鸣谢
- 梦中情人:图片取自Pixabay的Stefan Keller
- 说人话:图片取自Pixabay的Gerd Altmann
- 少说废话:图片取自Unsplash的Markus Spiske