4.4 封装和隐藏

一、理解封装

  封装[Encapsulation]是面向对象的三大特征(封装、继承、多态)之一,它指的是将对象的状态信息隐藏在对象内部,不允许外部程序直接访问对象内部信息,而是通过该类所提供的方法来实现对内部信息的操作和访问。

  封装的两方法面含义:把该隐藏起来的隐藏起来,把该暴露的暴露出来:
1、把对象的成员变量和实现细节都隐藏起来,不允许外部直接访问
2、把方法暴露出来,让方法来控制对这些成员变量进行安全访问和操作。

  封装的目的:
1、隐藏类的实现细节。
2、让使用者只能通过预定的方法来访问数据,从而可以在该方法里加入控制逻辑,限制堆成员变量的不合理访问。
3、进行数据检查,从而有利于保证对象的完整性。
4、便于修改提高程序的可维护性。

二、使用访问控制符

  Java使用三个访问控制符:public、protected、private,分别代表3个访问控制级别,另外还有一个不加任何访问控制符的访问控制级别。4个访问级别有小到大如图所示:

★private(当前类访问权限):表示该成员只能在当前类的内部被访问。该访问控制符用于修饰成员变量最合适,使用它来修饰成员变量就可以把成员变量隐藏在该类的内部。
★default(不写,包访问权限):表示该成员或外部类就可以被相同包下的其他类访问。
★protected(子类访问权限):表示该成员既可以被同一个包类的其他类访问,也可以被不同包中的的子类访问。在通常情况下,使用protected来修饰一个方法,通常希望其他子类来重写这个方法。
★public(公共访问权限):表示该成员或外部类就可以被其他所有类访问,不管访问类和被访问类是否处于同一个包中,是否具有父子继承关系。

private default(不写) protected public
同一个类中
同一个包中
子类中
全局范围中
**  访问控制符用于控制一个类成员是否可以被其他类访问,对于局部变量而言,其作用域就是其所在的方法,不可能被其他类所访问,因此不能使用访问控制符。
  对于外部类而言,只有两种访问级别:public和默认,外部类不能使用private和protected修饰,因为外部类没有处于任何类的内部,也就没有其所在内的内部、所在类的子类两个范围,因此private和protected访问控制符对外部类没有意义**
注意:如果一个Java源文件定义的所有类都没有使用public修饰,则这个Java源文件的文件名可以是一切合法的文件名;但如果一个Java源文件里定义了一个public修饰的类,则这个源文件名必须与public修饰的类的类名同名。
下面通过合理访问控制符定义了一个Person类:
public class Person 
{
	//使用private修饰成员变量,将这些成员变量隐藏起来
	private String name;
	private int age;

	//提供方法操作name成员变量,name只能在当前类访问
	public void setName(String name)
	{
		//执行合理性检验,要求用户名必须在2-6位之间
		if(name.length()>6||name.length()<2)
		{
			System.out.println("你设置的人民不符合要求");
			return;//结束程序
		}
		else
		{
			this.name=name;
		}
	}
	public String getName()
	{
		return this.name;
	}
	
	//提供操作age成员变量
	public void setAge(int age)
	{
		//用户年龄在0-100之间
		if(age>100||age<0)
		{
			System.out.println("你设置的年龄不合法");
			return;
		}
		else
		{
			this.age=age;
		}
	}
	public int getAge()
	{
		return this.age;
	}
}

定义上面的Person类后,该类的name和age两个成员变量只有在Person类内才可以操作和访问,在Person类之外只能通过各自对应的setter和getter方法来操作和访问他们。
  Java类里的实例变量的setter和getter方法有着非常重要的意义。如果一个Java类里的每个实例变量都是以private修饰,并为每个实例变量都提供public修饰的setter和getter方法,那么这个类就是一个符合JavaBean规范的类。因此JavaBean总是一个封装良好的类。setter和getter方法合起来就变成属性,如果只有getter方法,则是只读属性。
下面程序示例访问Person类的成员变量

class PersonTest
{
	public static void main(String[] args) 
	{
		var p=new Person();
		//因为age变量已经被隐藏,所以下面语句将出现错误
		//p.age=1000;
		//PersonTest.java:7: 错误: age 在 Person 中是 private 访问控制

		p.setAge(1000);
		//你设置的年龄不合法,故并未对成员变量age设置成功
		System.out.println(p.getAge());

		//有效设置成员变量age后
		p.setAge(20);
		System.out.println(p.getAge());
		//同理对成员变量name也只能通过setter和getter方法进行访问

	}
}
---------- 运行Java捕获输出窗 ----------
你设置的年龄不合法
0
20

输出完成 (耗时 0 秒) - 正常终止

关于访问控制符的使用原则:
1、类里的绝大部分成员变量都应该使用private修饰,只有一些static修饰的、类似全局变量的成员变量,才有可能考虑用public修饰。除此之外,有些方法只用于辅助实现该类的其他方法,这些方法被称为工具方法,工具方法也应该使用private修饰。
2、如果某个类主要用于其他类的父类,该类里包含的大部分方法可能仅希望被其他子类重写,而不想被外界直接调用,则应该使用protected修饰这些方法。
3、希望暴露出来给其他类自由调用方法,应该使用public修饰。因此类的构造器通常使用public修饰,从而允许在其他地方创建该类的实例。因为外部类通常希望被其他类自由调用,所以大部分外类都使用public修饰。

三、package、import和import static

3.1 package包引用

Oracle也允许在类名前增加一个前缀来限定这个类。Java引入包机制,提供类的多层命名空间,用于解决类的命名冲突、类文件管理的冲突。
Java允许将一组功能相关的类,放到同一个package下。如果希望把一个类放到指定的包结构下,应该在Java源程序的第一个非注释行放置如下格式代码:
package packageName;
一旦在Java源文件中使用了package语句,就意味者该源文件里定义的所有类都属于这个包。位于包中的每个类的完整类名都应该是包名和类名的组合,如果其他人使用该包下的类,也应该使用包名加类名的组合。
下面在lee包定义一个简单的Java类

package lee;
public class Hello 
{
	public static void main(String[] args) 
	{
		System.out.println("Hello World!");
	}
}

Hello.java源文件存储在:

进入Hello.java的源文件的路径下,在该路径下用javac来编译这个源文件。

-d选项用于设置编译生成的class文件的保存位置,这里指定将生成的class文件放在当前路径下(.就代表当前路径)。使用该命令后,发现当前路径下并没有class文件,而是在当前路径下多了一个lee文件夹,文件夹里有一个Hello.class文件。

package这样的用法是为了,避免同名类引起的冲突。Java规定:位于包中的类,在文件系统中也必须有与包名层次相同的目录结果。所以对于上面的Hello.calss它必须放在lee文件夹中才有效
注意:如果直接使用javac Hello.java命令来编译这个文件,将会在当前路径下生成一个Hello.class文件,而不会生成lee文件。也就是说,如果编译Java文件不使用-d选项,编译器不会为Java源文件生成相同的目录结果。鉴于此,推荐编译Java源文件时总是使用-d选项,即使想把生成的calss文件放在当前路径下,也应该使用-d .选项,
进入编译器生成的lee文件夹所在路径,执行如下命令
java lee.Hello
将看到程序正常输出:

E:\Java\第五章 面向对象\5.4 隐藏和封装>java lee.Hello
Hello World!

当时如果直接进入lee文件下.class文件所在路径下执行java Hello命令来运行Hello类,系统将出现错误。

当虚拟机要加载lee.Hello类时,它会依次搜索CLASSPATH环境变量所指定的系列路径,查找这些路径下是否包含lee路径,并在lee路径下查找是否包含Hello.java文件。虚拟机在装载带包名的类时,会先搜索CLASSPATH环境变量指定的目录,然后在这些目录中按与包层次对应的目录结构去查找class文件。
注:将源文件和class文件统一存放,有可能造成混乱,通常建议将源文件和class文件分开存放,以便管理。
Java包机制需要两方面的保证:(1)源文件使用package语句指定包名;(2)class文件必须放在对应的路径下

3.2 import省略写类名

我们再在lee包的子包sub下定义一个Apple类

package lee.sub;
public class Apple 
{
	public static void show() 
	{
		System.out.println("这是一个Aplle类");
	}
}

这里在lee文件下定义一个Java源文件对Hello类和Apple类进行调用:

class PackageTest 
{
	//调用lee文件夹下的Hello类
	public static void main(String[] args) 
	{
		var h=new lee.Hello();
		var a=new lee.sub.Apple();
		a.show();
	}
}
---------- 运行Java捕获输出窗 ----------
这是一个Aplle类

输出完成 (耗时 0 秒) - 正常终止

可以看出,创建其他包下的实例,则在调用构造器时也必须使用包的前缀,例如lee.Hello()、lee.sub.show()。为了简化编程,省略写包名,Java引入了import关键字,import可以向某个Java文件中导入指定包层次下的某个类或全部类,import语句应该出现在package语句之后,类定义之前。
import导入类的语句:

import package.subpackage...classname;//导入单个类
import package.subpackage...*;//导入指定包下的全部类

一旦在Java源文件使用import语句来导入指定类,在该源文件中使用这些类就可以使用包前缀,不在需要类全名。使用import语句简化上面的PackageTest源程序

import lee.Hello;
import lee.sub.Apple;
class PackageTestplus 
{
	//调用lee文件夹下的Hello类
	public static void main(String[] args) 
	{
		var h=new Hello();
		var a=new Apple();
		a.show();
	}
}
---------- 运行Java捕获输出窗 ----------
这是一个Aplle类

输出完成 (耗时 0 秒) - 正常终止

关键点:
★Java默认为所有源文件导入java.lang(lang是language的简写,保存的是语法相关的语句)包下的所有类,因此前面在Java程序中使用String、System类时都无须使用import语句导入这些类。
★在一些特殊情况下也必须使用类全名:
例如在同一个程序中使用java.sql包下的类和java.util下的类,则可以使用下面两行import语句:

import java.util.*;
import java.sql.*;

如果在接下来的程序中需要使用Date类,则会引起编译错误,原因在于java.sql和java.util包下都包含Date类,引起了系统混乱。在这种情况下需要使用类全名,如:

java.sql.Date d=new java.sql.Date();

3.3 import static静态导入

JDK1.5以后增加了一种静态导入的语法,它用于导入指定类的某个静态成员变量、方法或全部的静态成员变量或方法。
导入指定类的单个静态成员变量、方法的语法格式:

import static package.subpackage...ClassName.FieldName|MethodName;

导入指定类的全部静态成员变量、方法的语法格式:

import static package.subpackage...ClassName.*;

所谓静态成员变量、静态方法就是类变量和类方法,他们都需要static修饰。使用import可以省略包名;import static则可以连类名都可以省略
下面的程序使用import static语句来导入java.lang.System类下的全部静态成员变量,从而可以将程序简化为:

import static java.lang.System.*;
import static java.lang.Math.*;
class StaticImportTest 
{
	public static void main(String[] args) 
	{
		//out是java.lang.System类的静态方法,代表标准输出
		//PI是Math类的静态成员变量,代表圆周率
		System.out.println("Hello World!");
		out.println("Hello World");
		System.out.println(Math.PI);
		out.println(PI);
	}
}
---------- 运行Java捕获输出窗 ----------
Hello World!
Hello World
3.141592653589793
3.141592653589793

输出完成 (耗时 0 秒) - 正常终止

3.4 java常用包

Java核心类都放在java包以及其子包下,java扩展的许多类都放在javax包以及其子包下。
Java语言常用的几个核心库:
★java.lang包。
该包提供了Java语言进行程序设计的基础类,它是默认导入的包。该包里面的Runnable接口和Object、Math、String、StringBuffer、System、Thread以及Throwable类需要重点掌握,因为它们应用很广。
★java.util包。
该包提供了包含集合框架、遗留的集合类、事件模型、日期和时间实施、国际化和各种实用工具类(字符串标记生成器、随机数生成器和位数组)。
★java.io包。
该包通过文件系统、数据流和序列化提供系统的输入与输出。
★java.net包。
该包提供实现网络应用与开发的类。
★java.sql包。
该包提供了使用Java语言访问并处理存储在数据源(通常是一个关系型数据库)中的数据API。
★java.awt包
该包包含了构成抽象窗口工具集(abstract window toolkits)的多个类,这些类被用来构建和管理应用程序的图形用户界面(GUI)。
★javax.swing包。
这两个包提供了GUI设计与开发的类。
java.awt包提供了创建界面和绘制图形图像的所有类,而javax.swing包提供了一组“轻量级”的组件,尽量让这些组件在所有平台上的工作方式相同。

posted @ 2020-02-25 14:25  小新和风间  阅读(304)  评论(0编辑  收藏  举报