面向对象

       面向对象,是对现实世界的模拟,下图简单模拟了一个动物世界。

面向对象对动物世界的模拟

       面向对象的三个基本特征之一继承,这里Primat继承了Animal,Person继承了Primat,继承很简单,看以下代码实现:

1
2
3
4
5
6
7
//动物这个类比较抽象,没有具体的属性,写成接口比较合理
public interface Animal {
//所有的动物都离不开eat(吃)这个行为,把吃放在动物这个类(接口也是一种特殊的类)是合理的,写一个方法来模拟吃这个行为。
public void eat();
//move(行走)这个行为不能放在这儿,有些动物不能行走,比如鱼类
// void move();
}
1
2
3
4
5
6
7
/**
* 灵长类继承了动物这个接口,所以也继承了动物吃的行为(这就是继承带来的好处,不用再重复写一次吃这个行为的代码)
*/
public interface Primat extends Animal {
//这儿扩展了一个move(行走)行为方法
public void move();
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
/**
* 人类这一层已经有了属性(名字,年龄),再抽象成接口就不合理了,这儿声明成了类
*/
public class Person implements Primat {
//构造函数
public Person(String name, String sex, Integer age) {
this.name = name;
this.sex = sex;
this.age = age;
}

//添加了属性,未加private修饰,同包类能访问
String name;
String sex;

//年龄这个属性用了private来修饰,外部类看不到,这就是封装
private Integer age;

@Override
public void eat() {//已经有了具体的行为,需要实现该行为。
System.out.println(name + "拿起碗开始吃饭");
}

@Override
public void move() {//已经有了具体的行为,需要实现该行为。
System.out.println(name + "站起来开始走路");
}

public void printAge() {
if (isLady(sex)) {//isLady这个方法其它类访问不到,但该类自身是可以访问的
System.out.println(name + "女士的年龄可是秘密,不告诉你");
return;
}
System.out.println(name + age + "岁了");
}

//这个方法也用了private来修饰,外部类看不到,也无法调用该方法,这就是封装
private boolean isLady(String sex) {
if (sex.equals("女")) {
return true;
}
return false;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/**
* 声明一个猴子类
*/
public class Monkey implements Primat {

//添加了属性,未加private修饰,同包类能访问
String name;
//构造函数
public Monkey(String name) {
this.name = name;
}

@Override
public void eat() {//已经有了具体的行为,需要实现该行为。
System.out.println(name + "吃起了香蕉");
}

@Override
public void move() {//已经有了具体的行为,需要实现该行为。
System.out.println(name + "开始在树上跳来跳去");
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public static void main(String[] args) {
Animal animal1 = new Monkey("小猴1");//小猴1以Animal形态出现
Animal animal2 = new Person("张三", "男", 21);//张三以Animal形态出现

Primat primat = new Person("李四", "男", 21);//李四以primat形态出现
Person person3 = new Person("王小姐", "女", 18);//王小姐以Person形态出现
Person person4 = new Person("王五", "男", 26);//王五以Person形态出现
animal1.eat();//以Animal形态出现,只能调用Animal里的eat()方法,该方法能打印出相应结果
animal2.eat();//以Animal形态出现,只能调用Animal里的eat()方法,该方法能打印出相应结果
// animal1.move();//报错,编译不过,以Animal形态出现时只能调用Animal里的方法
// animal1.getAge();//报错,编译不过,以Animal形态出现时只能调用Animal里的方法
primat.eat();//以Primat形态出现,由于继承了Animal类,可以调用Animal里的eat()方法
primat.move();//以Primat形态出现,可以调用自身的move()方法
// primat.getAge();//报错,编译不过,以Primat形态出现只能调用Primat及其父类里的方法

person3.eat();//以Person类形态出现,可以调用继承的方法
person3.move();//以Person类形态出现,可以调用继承的方法
person3.printAge();//以Person类形态出现,调用自身方法
person4.printAge();//以Person类形态出现,调用自身方法

((Person) animal2).printAge();//把形态强制转型成Person形态,可以调用Person里的方法
}

       打印结果如下:

1
2
3
4
5
6
7
8
9
小猴1吃起了香蕉
张三拿起碗开始吃饭
李四拿起碗开始吃饭
李四站起来开始走路
王小姐拿起碗开始吃饭
王小姐站起来开始走路
王小姐女士的年龄可是秘密,不告诉你
王五26岁了
张三21岁了

       在代码中,不管是动物,鸟类,人类,猴子,我们都可以抽象成类,类是对象的模板,通过new关键字,可以创建一个个对象。看上面代码中的animal1和animal2,虽然都是同一个形态(Animal),由于指向的是子类对象,当调用同一个eat()方法,运行时会智能匹配到子类的实现,最后得到的结果也不一样,这种形为,我们称之为多态。

       多态要满足三个条件。

  1. 要有继承;(Person继承了Animal,Monkey也继承了Animal)
  2. 要有重写;(都重写了父类的eat方法)
  3. 父类引用指向子类对象。如上面的代码:
1
2
3
Animal animal1 = new Monkey("小猴1");
Animal animal2 = new Person("张三", "男", 21);
Primat primat = new Person("李四", "男", 21);

       person4这个对象能访问到name、sex属性,eat()、printAge()方法,但无法访问age属性,isLady()方法。是因为我们在该属性和方法前面加了private关键字。隐藏了不想对客户端暴露的age属性和isLady()方法(这里的客户端是main方法),但是我们对客户端提供了一个printAge方法来打印年龄,但在打印年龄前,我们对年龄做了一系列处理(不打印女士年龄)。

       对于这种隐藏对象属性和实现细节,仅对外公开指定方法来控制程序中属性的访问和修改,我们称之为封装。(这儿我们没有对age提供set方法,提供了一个printAge()方法供外部访问)。

       假如有这样一个需求,当Person对象的名字、年龄、性别都一致,就当成同一人处理,在Java中的==与equals可知,我们不会用==或equals直接去做比较,以下是结果:

1
2
3
4
5
6
7
public static void main(String[] args) {
Person person1 = new Person("张三", "男", 26);
Person person2 = new Person("张三", "男", 26);

System.out.println(person1 == person2);
System.out.println(person1.equals(person2));
}
1
2
false
false

       当用==和equals失败后,开发者可能会写下以下代码去做判断:

1
2
//如果名字相同,年龄相同,性别相同
if(person1.name.equals(person2.name)&&person1.sex.equals(person2.sex)&&person1.)

       由于age属性是私有的,又没有提供getAge()方法,获取不到age属性,可能会在Person这个类里提供一个getAge()方法或把age的private关键字去掉,这样虽然也能完成逻辑,但会导致后续使用该类的人再也不调printAge去打印age了,而是直接访问age属性或getAge()方法去打印,女士的age也就暴露了出去。很显然,这种做法打破了我们之前对age的封装,不建议这么做。

       比较两个对象是否相等,不应该由客户端来决定,而是由对象本身来决定。这也是面向对象的技巧之一。

       在Person里扩展如下方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
/**
* 内置了比较方法
*
* @param person
* @return
*/
public boolean isSame(Person person) {
//如果名字相同,年龄相同,性别相同
if (this.name.equals(person.name) && this.sex.equals(person.sex) && this.age.equals(person.age)) {
return true;
}
return false;
}
1
2
3
4
5
6
public static void main(String[] args) {
Person person1 = new Person("张三", "男", 26);
Person person2 = new Person("张三", "男", 26);

System.out.println(person1.isSame(person2));
}
1
true

       事实上这样比较还是有问题的,在Java中的==与equals中做了更进一步的说明。

参考资料:
清浅池塘 面向对象

Fork me on GitHub