好久没有思考过什么是面向对象这个问题了,就好像好久没有吃过烤红薯同样,那股香味到底是什么,已经很难准确地形容出来了。脑海中只浮现出这样一幅动图:java
前两天,读者秋秋问我:面试
二哥,究竟什么是面向对象呢?还有,什么是面向过程。今天去面试的时候,面试官让我用面向对象的思想谈一谈此次面试的过程。编程
看到这个问题后,我思考了好一下子,总以为面试官的问法有点问题:为何要用面向对象的思想谈一谈面试的“过程”?设计模式
有点矛盾,有没有?先无论这么多了,且来看看什么是面向对象吧。安全
一开始的时候,并无面向对象,只有面向过程的概念。咱们回到秋秋面试的话题上,把面试前(能够下降需求的复杂性)的过程简单地拆解一下。并发
为了实现这 3 个步骤,咱们定义 3 个方法,并依次调用:this
可是,假如参加面试的不是秋秋,这 3 个方法就要从新定义了(莫抬杠),尽管步骤并无变。面向对象从另外一个角度来解决这个问题,它把对象(对事物的一种抽象描述)做为程序的基本单元。spa
回到秋秋面试的例子,用面向对象的思想来实现,就须要先定义 2 个类(类是构建对象的蓝图,里面包含若干的数据和操做这些数据的方法),分别是应聘者和面试官。设计
应聘者能够投递简历;面试官能够接收应聘者的简历和通知应聘者前来面试。而后再经过类建立两个对象,分别是秋秋和他的面试官;对象建立成功后,就能够依次调用对应的方法完成上述的 3 个步骤。code
面向对象(英语:Object Oriented,缩写:OO)思想是一种试图下降代码间的依赖,应对复杂性,从而解决代码重用的软件设计思想——刚好解决了面向过程带来的问题。
面向对象有不少重要的特性,好比说封装、继承和多态。这些概念又该怎么理解呢?所谓一图胜千言,我给你来一张有趣的、形象的。
了解了面向对象的思想后,咱们来经过具体的代码完成秋秋面试前的 3 个步骤。并对类和对象的相关知识点进行概括和总结。
先来细致地看一下应聘者类——Candidate.java。
package com.cmower
class Candidate {
private String name;
public Candidate(String name) {
this.name = name;
}
public void deliverResume() {
System.out.println(getName() + "发简历");
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
Candidate 包含了类的 4 个重要概念:
name
;getter/setter
)getName()
和 setName()
;Candidate()
;deliverResume()
。Candidate 类虽然简单,但却大有学问。
1)为了保证包名的绝对惟一,Sun 公司建议将域名(绝对是独一无二的)以逆序的形式做为包名——这也是为何包名常常以 org
、com
开头的缘由(是否是有一种豁然开朗的感受)。我曾申请过一个域名,叫 cmower.com,因此我我的编写的绝大多数代码都是在 com.cmower
包下。
2)类的方法定义顺序依次是:构造方法 > 公有(public
)方法或保护(protected
)方法 > 私有(private
)方法 > getter/setter 方法。
构造方法是建立对象的必经之路,放在首位是必须的。若是只有系统默认的无参构造方法,可忽略。
公有方法是类的调用者和维护者最关心的方法,应该在比较靠前的位置展现;保护方法虽然只有子类关心,也多是“模板设计模式”下的核心方法,因此也要靠前;私有方法只对本类可见,通常不须要特别关心,因此日后放;getter/setter
方法承载的信息价值较低,因此放在类的最后面。
3)setter 方法中,参数名称与成员变量名称保持一致,采用 this.成员名 = 参数名
的形式。
4)成员变量不要用 public
修饰,尽可能用 private
修饰;若是须要被子类继承,能够用 protected
修饰。
在初学 Java 编程的时候,我常常产生一个疑惑:为何不使用 public
修饰成员变量呢?这样作不是比 getter/setter
更方便吗?
我最早想到的答案是这样的:
解释:若是只有 private String name
而没有 getter/setter
的话,Eclipse 会提示 The value of the field Candidate.name is not used
的警告。
固然了,这样的答案过于牵强。那能不能来个靠谱点的答案呢?
能,为了体现封装的思想:将数据与行为进行分离。封装有什么好处呢?
getter/setter
)来访问数据,能够方便地加入控制方法,限制对成员变量的不合理操做;不过,我对这些严肃的词汇和科学用语实在是提不起半点兴致。那就再换一个答案吧。
套用《Java 开发实战经典》中举过的一个例子,咱们增长一个应聘者年龄的共有成员变量 age。
class Candidate {
public int age;
}
而后在建立应聘者对象的时候,直接经过类成员变量赋值:new Candidate().age = -99;
这样赋值是没有任何问题的,但没有实际的意义,年龄是不可能为负数的。为了防止出现这样的错误,能够对它进行封装,也就是私有化,而后在 setter
方法中对年龄进行判断,代码以下:
class Candidate {
private int age;
public void setAge(int age) {
if (age >= 0) {
this.age = age;
}
}
}
这个答案你以为满意吗?我最开始看到这个答案的时候以为很满意。但看了《阿里巴巴 Java 开发手册》后(详情截图以下),就以为不满意了。
第一,类成员变量使用基本类型很容易形成NullPointException
的错误;第二,在 getter/setter
增长业务逻辑的确很容易把实际的问题隐藏起来。
那,好的答案到底是什么呢?
若是设置成员变量为 public
,那么每一个调用者均可以读写它,但若是以 private
配合 getter/setter
的形式访问时,就能够达到“不许访问”、“只读访问”、“读写访问”以及“只写访问”的目的。由于不是每一个成员变量都须要 getter/setter
。
5)每一个类都至少会有一个构造方法。初学者可能会很是疑惑:个人那个类真的没有构造方法啊!
若是在编写一个类的时候没有编写构造方法,那么系统就会提供一个无参的构造方法,就好像是这样:
class Candidate {
private String name;
public Candidate() {
}
}
当执行 new Candidate()
的时候,成员变量 name 就会被初始化为 null
。通常状况下,咱们会为类设置它必须的构造方法,而后在建立对象的时候对成员变量进行赋值。
再来粗略地看一下面试官类——Interviewer.java。
class Interviewer {
private Candidate candidate;
public Interviewer (Candidate candidate) {
this.candidate = candidate;
}
public void receviveResume() {
System.out.println("收到" + getCandidate().getName() + "简历");
}
public void notifyInterview() {
System.out.println("通知" + getCandidate().getName() + "面试");
}
public Candidate getCandidate() {
return candidate;
}
public void setCandidate(Candidate candidate) {
this.candidate = candidate;
}
}
Interviewer 有一个成员变量 Candidate,一个构造方法,两个共有方法,以及成员变量对应的 getter/setter
。
(这段代码存在一个严重的问题,你注意到了吗?)
而后,咱们让应聘者发送简历,让面试官接收简历并发送通知。
Candidate qiuqiu = new Candidate("秋秋");
// 发送简历
qiuqiu.deliverResume();
Interviewer interviewer = new Interviewer(qiuqiu);
// 面试官接收到简历
interviewer.receviveResume();
// 面试官通知应聘者来面试
interviewer.notifyInterview();
在初学 Java 的很长一段时间里,我老是搞不清楚什么是“对象”,什么是“引用”,差点所以放弃个人程序生涯。后来,在网上认识了一个大佬,人称老王,是他挽救了个人程序生涯。
他解释说。
Candidate qiuqiu = new Candidate("秋秋");
能够拆分为两行代码:
Candidate qiuqiu;
qiuqiu = new Candidate("秋秋");
第一行代码 Candidate qiuqiu;
中的 qiuqiu 这时候能够称做是对象变量,它暂时尚未引用任何对象,严格意义上,它也不能称为 null
。
第二行代码 qiuqiu = new Candidate("秋秋");
能够拆分为两个部分,= 号左侧和 = 号右侧。
右侧的表达式 new Candidate("秋秋")
先执行,执行完后,会在堆上建立了一个 name 为“秋秋”的对象,类型为 Candidate,表达式 new Candidate("秋秋")
的值是新建立对象的引用。
而后再把这个引用经过 = 操做符赋值给左侧的对象变量 qiuqiu
,赋值后,qiuqiu
就再也不是对象变量了,应该称为对象引用。
看完老王的解释,你会不会不由自主地“哦,原来如此啊!”反正我当时顿悟的时候是这样的。
前面提到,Interviewer 类的设计存在一个严重的问题,是什么呢?
Candidate qiuqiu = new Candidate("秋秋");
Interviewer interviewer = new Interviewer(qiuqiu);
interviewer.getCandidate().setName("夏夏");
System.out.println(qiuqiu.getName());
这段代码执行完后,你会发现秋秋变成了夏夏,应聘者的私有成员变量 name 居然被改变了!问题的缘由也很简单,qiuqiu 和 interviewer.getCandidate()
引用了同一个对象。
那怎么解决呢?当 getter
须要返回一个可变对象的引用时,应该先进行克隆(clone)。如下展现了一个很是简单的克隆方案。
class Interviewer {
private Candidate candidate;
public Interviewer (Candidate candidate) {
this.candidate = candidate;
}
public Candidate getCandidate() {
Candidate candidate = new Candidate(this.candidate.getName());
return candidate;
}
}
这篇文章花了 5 个多小时才写完,此刻个人感受只有一个字——饿,我要出去吃饭了。吃饭以前,我决定先买个烤红薯吃,重温一下那种久违的香。