9.1 为什么需要封装?
我要用洗衣机,只需要按一下开关和洗涤模式就可以了。有必要了解洗衣机内部的结构吗?有必要
碰电动机吗?
我要开车,我不需要懂离合、油门、制动等原理和维修也可以驾驶。
客观世界里每一个事物的内部信息都隐藏在其内部,外界无法直接操作和修改,只能通过指定的方
式进行访问和修改。
随着我们系统越来越复杂,类会越来越多,那么类之间的访问边界必须把握好,面向对象的开发原则要
遵循 “ 高内聚、低耦合 ” 。
高内聚、低耦合是软件工程中的概念,也是 UNIX 操作系统设计的经典原则。内聚,指一个模块内各个元素彼此结合的紧密程度;耦合指一个软件结构内不同模块之间互连程度的度量。内聚意味着重用和独立,耦合意味着多米诺效应牵一发动全身。
而 “ 高内聚,低耦合 ” 的体现之一:
高内聚 :类的内部数据操作细节自己完成,不允许外部干涉;
低耦合 :仅暴露少量的方法给外部使用,尽量方便外部调用。
9.2 何为封装性?
所谓封装,就是把客观事物封装成抽象概念的类,并且类可以把自己的数据和方法只向可信的类或者对
象开放,向没必要开放的类或者对象隐藏信息。
通俗的讲,把该隐藏的隐藏起来,该暴露的暴露出来。这就是封装性的设计思想。
9.3 Java 如何实现数据封装
实现封装就是控制类或成员的可见性范围。这就需要依赖访问控制修饰符,也称为权限修饰符来控
制。
权限修饰符: public 、 protected 、 缺省 、 private 。具体访问范围如下:
具体修饰的结构:
外部类: public 、缺省
成员变量、成员方法、构造器、成员内部类: public 、 protected 、缺省、 private
9.4 封装性的体现
9.4.1 成员变量 / 属性私有化
概述:私有化类的成员变量,提供公共的 get 和 set 方法,对外暴露获取和修改属性的功能。
实现步骤:
① 使用 private 修饰成员变量
private 数据类型 变量名 ;
代码如下:
public class Person {private String name ;private int age ;private boolean marry ;}
② 提供 getXxx 方法 / setXxx 方法,可以访问成员变量,代码如下:
public class Person {private String name ;private int age ;private boolean marry ;public void setName ( String n ) {name = n ;}public String getName () {return name ;}public void setAge ( int a ) {age = a ;}public int getAge () {return age ;}public void setMarry ( boolean m ){marry = m ;}public boolean isMarry (){return marry ;}}
③ 测试:
public class PersonTest {public static void main ( String [] args ) {Person p = new Person ();// 实例变量私有化,跨类是无法直接使用的/* p.name = " 张三 ";p.age = 23;p.marry = true;*/p . setName ( " 张三 " );System . out . println ( "p.name = " + p . getName ());p . setAge ( 23 );System . out . println ( "p.age = " + p . getAge ());p . setMarry ( true );System . out . println ( "p.marry = " + p . isMarry ());}}
成员变量封装的好处:
让使用者只能通过事先预定的方法来 访问数据 ,从而可以在该方法里面加入控制逻辑,限制对成员 变量的不合理访问。还可以进行数据检查,从而有利于保证对象信息的完整性。
便于修改 ,提高代码的可维护性。主要说的是隐藏的部分,在内部修改了,如果其对外可以的访问 方式不变的话,外部根本感觉不到它的修改。例如:Java8->Java9 , String 从 char[] 转为 byte[] 内部实 现,而对外的方法不变,我们使用者根本感觉不到它内部的修改。
9.4.2 私有化方法
/**** @Description 自定义的操作数组的工具类* @author 尚硅谷 - 宋红康 Email:shkstart@126.com* @version**/public class ArrayUtil {/**** @Description 求 int 型数组的最大值* @author 尚硅谷 - 宋红康* @param arr* @return*/public int max ( int [] arr ) {int maxValue = arr [ 0 ];for ( int i = 1 ; i < arr . length ; i ++ ){if ( maxValue < arr [ i ]){maxValue = arr [ i ];}}return maxValue ;}/**** @Description 求 int 型数组的最小值* @author 尚硅谷 - 宋红康* @param arr* @return*/public int min ( int [] arr ){int minValue = arr [ 0 ];for ( int i = 1 ; i < arr . length ; i ++ ){if ( minValue > arr [ i ]){minValue = arr [ i ];}}return minValue ;}/**** @Description 求 int 型数组的总和* @author 尚硅谷 - 宋红康* @param arr* @return*/public int sum ( int [] arr ) {int sum = 0 ;for ( int i = 0 ; i < arr . length ; i ++ ){sum += arr [ i ];}return sum ;}/**** @Description 求 int 型数组的元素的平均值* @author 尚硅谷 - 宋红康* @param arr* @return*/public int avg ( int [] arr ) {int sumValue = sum ( arr );return sumValue / arr . length ;}// 创建一系列重载的上述方法// public double max(double[] arr){}// public float max(float[] arr){}// public byte max(byte[] arr){} /**** @Description 遍历数组* @author 尚硅谷 - 宋红康* @param arr*/public void print ( int [] arr ) {for ( int i = 0 ; i < arr . length ; i ++ ){System . out . print ( arr [ i ] + " " );}System . out . println ();}/**** @Description 复制数组 arr* @author 尚硅谷 - 宋红康* @param arr* @return*/public int [] copy ( int [] arr ) {int [] arr1 = new int [ arr . length ];for ( int i = 0 ; i < arr . length ; i ++ ){arr1 [ i ] = arr [ i ];}return arr1 ;}/**** @Description 反转数组* @author 尚硅谷 - 宋红康* @param arr*/public void reverse ( int [] arr ) {for ( int i = 0 , j = arr . length - 1 ; i < j ; i ++ , j -- ){int temp = arr [ i ];arr [ i ] = arr [ j ];arr [ j ] = temp ;}}/**** @Description 数组的排序* @author 尚硅谷 - 宋红康* @param arr* @param desc 指明排序的方式。 ascend: 升序 descend: 降序*/public void sort ( int [] arr , String desc ) {if ( "ascend" . equals ( desc )){ //if(desc.equals("ascend")){for ( int i = 0 ; i < arr . length - 1 ; i ++ ) {for ( int j = 0 ; j < arr . length - 1 - i ; j ++ ) {if ( arr [ j ] > arr [ j + 1 ]) {// int temp = arr[j];// arr[j] = arr[j + 1]; // arr[j + 1] = temp;swap ( arr , j , j + 1 );}}}} else if ( "descend" . equals ( desc )){for ( int i = 0 ; i < arr . length - 1 ; i ++ ) {for ( int j = 0 ; j < arr . length - 1 - i ; j ++ ) {if ( arr [ j ] < arr [ j + 1 ]) {// int temp = arr[j];// arr[j] = arr[j + 1];// arr[j + 1] = temp;swap ( arr , j , j + 1 );}}}} else {System . out . println ( " 您输入的排序方式有误! " );}}private void swap ( int [] arr , int i , int j ){int temp = arr [ i ];arr [ i ] = arr [ j ];arr [ j ] = temp ;}/**** @Description 查找指定的 value 值在 arr 数组中出现的位置* @author 尚硅谷 - 宋红康* @param arr* @param value* @return 返回 value 值出现的位置 或 -1 :未找到*/public int getValue ( int [] arr , int value ) {// 方法:线性查找for ( int i = 0 ; i < arr . length ; i ++ ){if ( value == arr [ i ]){return i ;}}return - 1 ;}}
注意:开发中,一般成员实例变量都习惯使用 private 修饰,再提供相应的 public 权限的 get/set 方法访问。对于 final 的实例变量,不提供 set() 方法。(后面 final 关键字的时候讲)对于 static final 的成员变量,习惯上使用 public 修饰。
总结:
面向对象特征之一:封装性1. 为什么需要封装性? 理论上:-`高内聚`:类的内部数据操作细节自己完成,不允许外部干涉;-`低耦合`:仅暴露少量的方法给外部使用,尽量方便外部调用。通俗的说:把该隐藏的隐藏起来,该暴露的暴露出来。这就是封装性的设计思想。2. 如何实现数据封装?2.1 权限修饰符Java规定了4种权限修饰,分别是:private、缺省、protected、public2.2 作用我们可以使用4种权限修饰来修饰类及类的内部成员。当这些成员被调用时,体现可见性的大小。2.3 实际案例: 在题目中,我们给Animal的对象的legs属性赋值。在实际的常识中,我们知道legs不能赋值为负数的。但是如果 直接调用属性legs,是不能加入判断逻辑的。那怎么办呢? > 将legs属性私有化(private),禁止在Animal类的外部直接调用此属性 > 提供给legs属性赋值的setLegs()方法,在此方法中加入legs赋值的判断逻辑if(legs >= 0 && legs % 2 ==0)将此方法暴露出去,使得在Animal类的外部调用此方法,对legs属性赋值。 > 提供给legs属性获取的getLegs()方法,此方法对外暴露。使得在Animal类的外部还可以调用此属性的值。2.4 4种权限具体使用 《见课件》> 类:只能使用public、缺省修饰 > 类的内部成员:可以使用4种权限修饰进行修饰。2.5 开发中4种权限使用频率的情况:比较高:public、private比较低:缺省、protected3. 封装性的体现 > 场景1:私有化(private)类的属性,提供公共(public)的get和set方法,对此属性进行获取或修改 > 场景2:将类中不需要对外暴露的方法,设置为private. > 场景3:单例模式中构造器private的了,避免在类的外部创建实例。(放到static关键字后讲)