注:面向对象的三大特征:封装、继承、多态。
一、封装的作用和含义
1、封装:把对象的属性和操作结合为一个独立的整体,并尽可能隐藏对象的内部实现细节。
如:看电视。我们只需要知道如何使用电视机和遥控器,会开关机和换台即可,不需要触碰电视机内部。制造厂家为了方便我们使用电视,把复杂的内部细节全部封装起来,只给我们暴露简单的接口,比如:电源开关。具体内部是怎么实现的,我们不需要操心。
需要让用户知道的才暴露出来,不需要用户知道的全部隐藏起来。
程序的设计要追求“高内聚、低耦合”。(把复杂性封装起来,用的时候尽可能的简单)
高内聚就是类的内部数据操作细节自己完成,不允许外部干涉;
低耦合是仅暴露少量的方法给外部使用,尽量方便外部调用。
2、编程中封装的具体优点:
1、提高代码的安全性;
2、提高代码的复用性;
3、“高内聚”:封装细节,便于修改内部代码,提高可维护性;
4、“低耦合”:简化外部调用,便于调用者使用,便于扩展和协作。
3、示例:没有封装的代码会出现一些问题
/**
*
* @Title: TestPotting.java
* @Package test
* @Description: TODO(测试封装:没有被封装,有时会导致一些问题)
* @author Charlie
* @date 2019年1月23日
* @version V1.0
*/
package test;
/**
* @ClassName: TestPotting
* @Description: TODO(测试封装:没有被封装,有时会导致一些问题)
* @author Charlie
* @date 2019年1月23日 下午11:04:48
*
*/
public class TestPotting {
/**
* @Title: main
* @Description: TODO(测试封装的main方法)
* @param @param args 参数
* @return void 返回类型
* @throws
*/
public static void main(String[] args) {
Person p = new Person();
p.name = "Charlie";
p.age = -28; //年龄可以通过这种方式随意赋值,没有任何限制
System.out.println(p);
}
}
/**
*
* @ClassName: Person
* @Description: TODO(定义一个人类)
* @author Charlie
* @date 2019年1月23日 下午11:09:05
*
*/
class Person{
String name;
int age;
public String toString() {
return "Person [name=" + name + ", age=" + age + "]";
}
}
运行结果:
年龄不可能是负数,但是如果没有使用封装的话,便可以给年龄赋值成任意的整数,这显然不符合我们的正常逻辑思维。
我们可以使用一些特殊的关键词声明一些属性和方法,不让别的类看到,加访问控制符,可以控制访问权限。(通过访问控制符实现封装)
如果哪天我们需要将Person类中的age属性修改为String类型的,你会怎么办?你只有一处使用了这个类的话那还比较幸运,但如果你有几十处甚至上百处都用到了,那你岂不是要改到崩溃。而封装恰恰能解决这样的问题。如果使用封装,我们只需要稍微修改下Person类的setAge()方法即可,而无需修改使用了该类的客户代码。
二、封装的实现----使用访问控制符
1、Java是使用“访问控制符”来控制哪些细节需要封装,哪些细节需要暴露的。
Java中4种“访问控制符”分别为private、default、protected、public,它们说明了面向对象的封装性,所以我们要利用它们尽可能的让访问权限降到最低,从而提高安全性。
2、访问权限修饰符(不仅可以修饰属性和方法,还可以修饰类)
权限范围如下:
1、private 表示私有,只有自己类能访问;
注:子类继承父类,但无法使用父类的私有属性和方法。
2、default表示没有修饰符修饰,只有同一个包的类能访问;
注:不写访问修饰符,即默认为default。
3、protected表示可以被同一个包的类以及其他包中的子类访问
4、public表示可以被该项目的所有包中的所有类访问
3、进一步说明Java中4种访问权限修饰符的区别:
首先我们创建4个类:Person类、Student类、Animal类和Computer类,分别比较本类、本包、子类、其他包的区别。
public访问权限修饰符:
图1 public访问权限—本类中访问public属性
图2 public访问权限—本包中访问public属性
图3 public访问权限—不同包中的子类访问public属性
图4 public访问权限—不同包中的非子类访问public属性
通过图1 ~ 图4可以说明,public修饰符的访问权限为:该项目的所有包中的所有类。
protected访问权限修饰符:将Person类中属性改为protected,其他类不修改。
图5 protected访问权限—修改后的Person类
图6 protected访问权限—不同包中的非子类不能访问protected属性
通过图5和图6可以说明,protected修饰符的访问权限为:同一个包中的类以及其他包中的子类。
默认访问权限修饰符:将Person类中属性改为默认的,其他类不修改。
图7 默认访问权限—修改后的Person类
通过图7可以说明,默认修饰符的访问权限为:同一个包中的类。
private访问权限修饰符:将Person类中属性改为private,其他类不修改。
图8 private访问权限—修改后的Person类
通过图8可以说明,private修饰符的访问权限为:同一个类。
三、封装的使用细节
1、类的属性的处理
1、一般使用private访问权限;
(一般属性全部私有)
2、提供相应的get/set方法来访问相关属性,这些方法通常是public修饰的,以提供对属性的赋值与读取操作;
(注意:boolean变量的get方法是is开头!)
3、一些只用于本类的辅助性方法可以用private修饰,希望其他类调用的方法用public修饰。
2、示例:JavaBean的封装实例(JavaBean只提供属性和相应的set/get方法)
/**
*
* @Title: Person.java
* @Package test
* @Description: TODO(JavaBean的封装实例)
* @author Charlie
* @date 2019年1月27日
* @version V1.0
*/
package test;
/**
* @ClassName: Person
* @Description: TODO(JavaBean的封装实例)
* @author Charlie
* @date 2019年1月27日 下午9:42:49
*
*/
public class Person {
//属性一般使用private修饰
private String name;
private int age;
private boolean flag;
//为属性提供public修饰的set/get方法
//鼠标右键单击,点击Source→Generate Getters and Setters...,选择需要get/set相关的属性
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public boolean isFlag() {
return flag;
}
public void setFlag(boolean flag) {
this.flag = flag;
}
}
3、封装的使用
示例:使用封装来解决上提到的年龄非法赋值的问题
/**
*
* @Title: Test.java
* @Package test
* @Description: TODO(封装的使用:解决年龄非法赋值的问题)
* @author Charlie
* @date 2019年1月27日
* @version V1.0
*/
package test;
/**
*
* @ClassName: Person
* @Description: TODO(设置人类)
* @author Charlie
* @date 2019年1月27日 下午9:59:17
*
*/
class Person{
private String name;
private int age;
public Person() {
}
public Person(String name, int age) {
this.name = name;
// this.age = age;//构造方法中不能直接赋值,应该调用setAge方法
setAge(age);
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
//在赋值之前先判断年龄是否合法
if(age > 130 || age < 0) {
this.age = 18; //年龄不合法则默认为18
}else {
this.age = age; //年龄合法才能赋值给属性age
}
}
@Override
public String toString() {
return "Person [name=" + name + ", age=" + age + "]";
}
}
/**
* @ClassName: Test
* @Description: TODO(测试年龄是否被正确封装)
* @author Charlie
* @date 2019年1月27日 下午9:56:55
*
*/
public class Test {
/**
* @Title: main
* @Description: TODO(这里用一句话描述这个方法的作用)
* @param @param args 参数
* @return void 返回类型
* @throws
*/
public static void main(String[] args) {
Person p1 = new Person();
//p1.name = "小红"; //编译失败
//p1.age = 18; //编译失败
p1.setName("小芳");
p1.setAge(18);
System.out.println(p1);
Person p2 = new Person("小玉", 22);
System.out.println(p2);
}
}
运行结果: