您的位置:首页 > 职场人生

黑马程序员——面向对象3:面向对象的特点之——封装(Encapsulation)

2014-09-28 21:45 309 查看
------- android培训java培训、期待与您交流! ---------

1.封装的定义
封装:指隐藏对象的属性和实现细节,仅对外提供公共访问方式。
对上述定义的理解:实际开发过程中,当我们使用某个对象的某个功能时,我们并不关心,这个对象在内部是如何实现这个功能的,换句话说,我们只需要指挥对象(或者面向对象)去完成我们的既定需求即可。这可以理解为“黑箱原理”。
2.类比举例
我们还是可以举前面“买电脑”的例子。当我们委托“懂电脑”的朋友进行挑选和砍价时,他是具体怎么做的我们不需要知道,换句话说,他进行这两个“操作”的过程,对于我是“隐藏”的。
再举一个“开车”的例子。在我们启动汽车以前,有没有必要去了解汽车内部运转的原理呢?如果真要弄懂,恐怕这辆车报废以前是开不上了。实际上,我们不需要了解汽车的运转原理,只需要学会如何开车就可以了。
也可以以“软件开发”为例。公司招聘软件开发工程师,进行软件开发,老板不需要知道这些工程师是如何写代码,也不需要知道,这些代码是如何运作的,他只需要“面向”这些工程师,并指挥他们,完成既定需求即可。
3.封装的优点
(1) 将变化隔离。即使某个类内部方法的实现原理发生改变(Java版本的升级),也并不影响我们调用这些类对应的对象,因为,这些方法的实现原理对于我们是不可见的,那么实现原理的改变同样是不可见的,我只管去调用就可以。比如,电脑发生故障拿到售后去维修,售后人员更换了某个电脑配件,这个更换动作,对于我们消费者而言是无所谓的,只要电脑能正常工作,满足我们的需求即可。
(2) 便于使用。不必了解对象内部方法的实现原理,就可以使用他们。
(3) 提高了重用性。每一次,需要使用某个对象的方法时,没有必要每次都重新定义(相比于面向过程)。只需要创建该对象,并调用方法即可。
(4) 提高安全性。由于,用户并不了解,对象内部方法的实现原理,也就不可能手动去进行修改,从而避免了人为修改而造成的代码的运行的不稳定或错误。
4.封装的原则
(1)­­ 将不需要对外提供的内容都隐藏起来。
(2) 将成员变量(或称为属性)都隐藏起来,只对外提供对公共方法的访问方法。
注意:我们既不能将对象的所有成员全部暴露,对外也不能一点也不提供访问的方式。就好比台式机主机。如果我们把机箱去掉,只是把各个配件组合在一起,并通过“短路”的方法启动电脑,也是可以使用的,但是麻烦且不安全;相反,如果我们使用完全封闭的机箱壳,用户也就无法使用电脑了。

参考之前学习的进制转换小程序(省略了方法的具体内容):
代码1:
class Demo
{
public void toBinary(int num){}
public void toOctal(intnum){}
public void toHex(intnum){}
private void trans(intnum, int index, int offset){}
}
上例中,方法trans其实是没有必要对外暴露的,因为,用户真正需要用到的方法只是获取进制转换后的数字,而其中的“算法”部分没有必要了解,并且出于安全考虑,也应该“隐藏”起来,避免人为修改造成的程序错误。这里修饰符private的意思是“私有”,也就是说这个方法只有这类的内部可以访问到(关于权限修饰符的详细讲解,参考后面的博客)。
小知识点1:
其实,每个方法本身就是最小单位的封装体。对于调用这个方法的用户来说,同样不需要知道方法的内部实现原理,只需要告诉用户,方法的返回值类型,方法名称,以及所需要传入的名义参数类型即可。以此类推,类就是更高一级的封装体。这个层次关系体现如下:
方法--类--包--框架
关于包将在后面的博客中介绍;而框架是一种高集成度的开发工具,可以帮助工程师完成一些组件的开发,开发人员不再需要自己搭建,直接拿来用即可。

5.如何实现封装
上面说了这么多,我们来看一下具体如何实现封装。
代码2:
//定义Person类
class Person
{
int age;
void speak()
{
System.out.println(“age= ”+age);
}
}
class PersonDemo
{
public static void main(String[] args)
{
//创建一个Person对象
Person p = new Perons();

//对Person的成员变量(属性)age赋值
p.age= 20;
p.speak();
}
}
注意:上述代码中,对Person对象的age属性进行赋值时,我们可以对其赋值负数,例如:


p.age = -20;


这虽然在程序中是允许的,但在现实生活中是不可能的。经过思考,我们发现产生这种问题的原因是因为我们可以直接访问到Person对象的成员变量(属性)age。因此,为了避免类似问题的发生,解决方法是将类的成员变量私有化,在类型修饰符前加上private访问权限修饰符,代码如下:

private int age;


知识点1:

private,表示私有,是访问权限修饰符的一种(访问权限最小),用于修饰类中的成员(包括成员变量和成员方法)。注意:被private修饰的成员,只能在本类内部访问到。如果从某个类的外部去访问类内的私有成员,编译器就会报错。
既然被private修饰的成员不能被直接访问,并且如果这个成员有被访问的需求,那就应该对外提供某种方法,以实现对该成员的调用,例如在Person类的内部添加如下方法:

public void setAge(int newAge)
{
age = newAge;
}
public int getAge()
{
return age;
}
上述两个方法,分别实现对age这个属性的赋值和获取动作。

知识点2:
对于一个成员变量,只能对其进行两个动作——赋值和获取。那么对于这两种方法,有一个命名规范,对于赋值方法命名为setXxx,对于获取方法命名为getXxx,其中Xxx表示属性名。对于方法名,第一个单词的首字母要小写,从第二个单词开始首字母大写。注意,只要这个属性需要在类外部调用,就一定要使其私有化,并提供对应的赋值和获取方法。需要记住的是,set方法返回值类型一定是void,并且需要传入参数;get方法的返回值类型与对应的成员变量一致,并且方法的参数列表为空。
在这里,要注意区别一个概念。通过private对成员进行私有,仅仅是封装的一种表现形式,千万不能说封装就是私有。实际上,只要类的成员不能被你访问到,那么相对你而言这个成员就是被封装的。
虽然,我们将类的成员变量进行了私有化,但是,我们还是可以通过set方法将负数赋给age属性。那么属性私有化,并对外提供访问方法到底有什么用呢?实际上,我们可以通过在set方法中添加逻辑判断语句来控制传入的参数,提高代码的健壮性。将前文代码进行修改以后最终体现为:
代码3:

class Person
{
private int age;

public void setAge(int newAge)
{
if(newAge > 0 && newAge < 130)
{
age= newAge;
}
else
System.out.println(“所传参数非法!”);
}
public void speak()
{
System.out.println(“age= ”+age);
}
}
class PersonDemo
{
public static void main(String[] args)
{
//创建一个Person对象
Person p = new Perons();

//对Person的成员变量(属性)age赋值
p.setAge(20);
p.speak();
}
}


下面我们来描述一下,这一段代码在内存中的运行过程。首先还是要从主函数开始运行,在栈内存中为主函数分配一个空间,在其中创建一个Person类类型变量p。再在堆内存中为Person对象开辟空间,同时为其内部的私有成员变量age赋予默认初始化值0,并为这块空间其分配一个内存地址值。将这个地址值赋给栈内存的变量p,p就指向了堆内存中的Person对象。接着调用变量p所指向的Person对象的setAge方法,并将整型数20作为参数传入。此时,又在栈内存为setAge方法开辟一块空间,其内部有个整型变量newAge,并被赋值为20。其中,setAge方法内部的赋值动作,是将newAge的值赋给调用这个方法的对象的成员变量(属性)——age,而不是其他变量。然后调用变量p所指向的Person对象的speak方法,那么就又在栈内存中为speak方法开辟一块空间,并将堆内存中Person对象的age通过输出语句进行输出。这里,speak方法的age变量,同样是调用speak方法的对象所属的属性。那么为什么调用这两个方法时涉及的age变量都是堆内存中Person对象的呢?可以这样理解:既然这两个方法都是由Person对象调用,那么访问到的属性当然就是属于该对象的。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: