对于封装性,实际上之前只详细讲解了 private
, 而封装性如果要想讲解完整,必须结合全部4种访问权限来看,这4种访问权限的定义如下表所示。
范围 | private | default | protected | public |
---|---|---|---|---|
同包的同类中 | √ | √ | √ | √ |
同包的不同类 | √ | √ | ||
不同包的子类 | √ | √ | ||
不同包的非子类 | √ |
对于上表可以简单理解为: private
只能在本类中访问;default
只能在同一个包中访问;protected
可以在不同包的子类中访问;public
为所有类都可以访问。
前面文章中对于 private
、default
、public
都已经有所了解,所以本节主要讲解 protected
。
// 范例 1: 定义 com.xiaoshan.demoa.A 类
package com.xiaoshan.demoa;
public class A{
protected String info="Hello";//使用 protected权限定义
}
// 范例 2: 定义 com.xiaoshan.demob.B 类,此类继承A 类
package com.xiaoshan.demob;
import com.xiaoshan.demoa.A;
public class B extends A{ // B是A 不同包的子类
public void print(){ //直接访问父类中的protected属性
System.out.println("A 类的 info = "+ super.info);
}
}
由于B
类是A
的子类,所以在B
类中可以直接访问父类中的 protected
权限属性。
// 范例 3: 代码测试
package com.xiaoshan.test;
import com.xiaoshan.demob.B;
public class Test {
public static void main(String args[]){
new B().print();
}
}
程序执行结果:
A 类的 info = Hello
此程序直接导入了B类 , 而后实例化对象调用 print()
方法,而在 print()
方法中利用“super.info
” 直接访问了父类中的 protected
权限属性。
而如果要在 com.xiaoshan.test
包中直接利用 Test
主类访问A
中的属性,由于它们不在同一个包下,也不存在继承关系,所以将无法访问。
// 范例 4: 错误的访问
package com.xiaoshan.test;
import com.xiaoshan.demoa.A;
public class Test {
public static void main(String args[]){
A a = new A();
System.out.println(a.info); //错误:无法访问
}
}
此程序在进行编译时会直接提示用户,info
是protected
权限,所以无法被直接访问。
实际上在给出的4种权限中,有3种权限 ( private
、default
、protected
) 都是对封装的描述,也就是说面向对象的封装性现在才算是真正讲解完整。从实际的开发使用来讲,几乎不会使用到 default
权限,所以真正会使用到的封装概念只有两个权限 private
、protected
。
对于访问权限,初学者要把握以下两个基本使用原则即可。
private
权限;public
权限。命名规范的主要特点就是保证程序中类名称或方法等名称的标记明显一些,可是对于 Java 而言,有如下一些固定的命名规范还是需要遵守的。
TestDemo
;studentName
;printInfo()
;FLAG
;com.xiaoshanjava.util
。需要注意以上所给出的5种命名规范,是所有开发人员都应该遵守的,而不同的开发团队也可能会有属于自己的命名规范,对于这些命名规范,在日后从事软件开发的过程中,都应该仔细遵守。
在之前大部分的属性定义时都使用了 private
进行声明,而对于构造方法其实也可以使用 private
声明,则此时的构造方法就被私有化。而构造方法私有化之后会带来哪些问题,以及有什么作用呢?下面就来进行简单的分析。
首先在讲解私有化构造方法操作之前,来观察如下的程序。
// 范例 5: 构造方法非私有化
class Singleton { //定义一个类,此类默认提供无参构造方法
public void print(){
System.out.println("Hello World.");
}
}
public class TestDemo{
public static void main(String args[])(
Singleton inst = null; //声明对象
inst = new Singleton(; //实例化对象
inst.print(); //调用方法
}
}
程序运行结果:
Hello World.
在本程序中, Singleton
类里面存在构造方法 ( 因为如果一个类中没有明确地定义一个构造方法,则会自动生成一个无参的、什么都不做的构造方法) , 所以可以先直接实例化对象,再调用类中提供的 print()
方法。下面将构造方法改变一下,即使用 private
封装。
// 范例 5: 私有化构造方法
class Singleton{ //定义一个类
private Singleton(){ //构造方法私有化
}
public void print(){
System.out.println("Hello World.");
}
}
public class TestDemo {
public static void main(String args[]){
Singleton inst = null; //声明对象
inst = hew Singletono: //错误:The constructor Singleton() is not visible
inst.print(); //调用方法
}
}
此程序在实例化 Singleton
类对象时,程序出现了编译错误,因为构造方法被私有化了,无法在外部调用,即无法在外部实例化 Singleton
类的对象。
那么现在在保证 Singleton
类中的构造方法不修改不增加,以及 print()
方法不修改的情况下,如何操作才可以让类的外部通过实例化对象去调用 print()
方法呢?
思考一:使用 private
访问权限定义的操作只能被本类所访问,外部无法调用,现在既然构造方法被私有化,就证明这个类的构造方法只能被本类所调用,即只能在本类中产生本类实例化对象。
// 范例 6: 第一步思考
class Singleton { //定义一个类
Singleton instance = new Singleton(); //在内部实例化本类对象
private Singleton(){ //构造方法私有化
}
public void print(){
System.out.println("Hello World.");
}
}
思考二: 对于一个类中的普通属性,默认情况下一定要在本类存在实例化对象后才可以进行调用,可是此程序在 Singleton
类的外部无法产生实例化对象,就必须想一个办法,让 Singleton
类中的 instance
属性可以在没有 Singleton
类实例化对象时来进行调用。因此可以使用 static
完成,static
定义的属性特点是由类名称直接调用,并且在没有实例化对象时候可以调用。
// 范例 7: 第二步思考
class Singleton { //定义一个类
static Singleton instance = new Singleton(); //可以由类名称直接访问
private Singleton(){ //构造方法私有化
}
public void print(){
System.out.println("Hello World.");
}
}
public class TestDemo {
public static void main(String args[]){
Singleton inst = null; //声明对象
inst = Singleton.instance; //利用“类.static属性”方式取得实例化对象
inst.print(); //调用方法
}
}
程序运行结果:
Hello World.
思考三: 类中的全部属性都应该封装,所以上边范例中的 instance
属性也应该进行封装,而封装之后要想取得属性,则要编写 getter
方法,只不过这时的 getter
方法应该也由类名称直接调用,定义为 static
型。
// 范例 8: 第三步思考
class Singleton { //定义一个类
private static Singleton instance = new Singleton();
private Singleton(){ //构造方法私有化
}
public void print(){
System.out.println("Hello World.");
}
public static Singleton getInstance(){ //取得本类对象
return instance;
}
}
public class TestDemo(
public static void main(String args[]){
Singleton inst = null; //声明对象
inst = Singleton.getInstance(); //利用“类.static方法()”取得实例化对象
inst.print(); //调用方法
}
}
程序运行结果:
Hello World.
思考四: 这样做的目的是什么?程序中的 instance
属性属于 static
定义,就表示所有 Singleton
类的对象不管有多少个对象声明,其本质都会共同拥有同一个 instance
属性引用,那么既然是同一个,又有什么意义呢?
如果要控制一个类中实例化对象的产生个数,首先要锁定的就是类中的构造方法(使用 private
定义构造方法), 因为在实例化任何新对象时都要使用构造方法,如果构造方法被锁,就自然就无法产生新的实例化对象。
如果要调用类中定义的操作,那么很显然需要一个实例化对象,这时就可以在类的内部使用 static
方式来定义一个公共的对象,并且每一次通过 static
方法返回唯一的一个对象,这样外部不管有多少次调用,最终一个类只能够产生唯一的一个对象,这样的设计就属于单例设计模式 (Singleton
)。
不过本程序依然有一个问题,那就是以下代码也可以使用。
// 范例 9: 程序出现的问题
class Singleton { //定义一个类
private static Singleton instance = new Singleton();
private Singleton(){ //构造方法私有化
}
public void print(){
System.out.println("Hello World.");
}
public static Singleton getInstance(){ // 取得本类对象
instance = new Singleton(); //重新实例化对象
return instance;
}
}
此程序中操作语法没有错误,也不需要考虑是否有意义,现在的代码也允许这样做,而这样做会发现之前表示唯一一个实例化对象的所有努力就白费了。因此,必须想办法废除这种做法,于是需要在定义 instance
的时候增加一个 final
关键字。
// 范例 10: 一个完整的单例模式的程序
class Singleton { //定义一个类
private final static Singleton instance = new Singleton();
private Singleton(){ //构造方法私有化
}
public void print(){
System.out.println("Hello World.");
}
public static Singleton getInstance(){ //取得本类对象
return instance;
}
}
public class TestDemo{
public static void main(String args[]){
Singleton inst = null; //声明对象
inst = Singleton.getInstance(); //利用“类.static方法()”取得实例化对象
inst.print(); //调用方法
}
}
程序运行结果:
Hello World.
在使用 Singleton
类时,不管代码如何操作,也永远只会存在唯一的一个 Singleton
类的实例化对象,而这样的代码,在设计模式上就称为单例设计模式(Singleton
)。
单例设计模式只留下一个类的一个实例化对象,而多例设计模式,会定义出多个对象。
例如:定义一个表示星期几的操作类,这个类的对象只能有7个实例化对象(星期一 ~星期日
);定义一个表示性别的类,只能有2个实例化对象(男、女
);定义一个表示颜色基色的操作类,只能有3个实例化对象(红、绿、蓝
)。
这些情况下,这样的类就不应该由用户无限制地去创造实例化对象,应该只使用有限的几个,这个就属于多例设计。不管是单例设计还是多例设计,有一个核心不可动摇,即构造方法私有化。
// 范例 11: 定义一个表示性别的类
package com.xiaoshan.demo;
class Sex{
private String title;
private static final Sex MALE = new Sex("男");
private static final Sex FEMALE = new Sex("女");
private Sex(String title){ //构造私有化
this.title = title;
}
public String toString(){
return this.title;
}
public static Sex getInstance(int ch){ //返回实例化对象
switch (ch){
case 1:
return MALE;
case 2:
return FEMALE;
default:
return;
}
}
}
public class TestDemo {
public static void main(String args[]){
Sex sex = Sex.getInstance(2);
System.out.println(sex);
}
}
程序执行结果:
女
此程序首先定义了一个描述性别的多例程序类,并且将其构造方法封装,然后利用 getInstance()
方法,接收指定编号后返回一个实例化好的Sex
类对象。
范例11的代码利用数字编号来取得了一个 Sex
类的对象,有朋友可能觉得这样做表示的概念不明确,那么为了更加明确要取得对象类型,可以引入一个接口进行说明。
// 范例 12: 利用接口标记对象内容
interface Choose {
public int MAN = 1; //描述数字
public int WOMAN = 2; //描述数字
}
public class TestDemo {
public static void main(String args[]){ //利用接口标记内容取得对象
Sex sex = Sex.getInstance(Choose.MAM);
System.out.println(sex);
}
}
此程序如果要取得指定的 Sex
类对象,可以利用接口中定义的全局常量(实际上也可以在Sex
类中定义一些全局常量)来进行判断。这样的做法是一种标准做法,但是这样做有一些复杂,所以利用字符串直接判断会更加简单一些。
在 JDK 1.7 之前, switch
只能支持对 int
或 char
类型进行判断,正因为如果纯粹是数字或字符意义不明确,所以增加了String
的支持。
// 范例 13: 对取得Sex 类对象进行修改
package com.xiaoshan.demo;
class Sex{
private String title;
private static final Sex MALE = new Sex("男");
private static final Sex FEMALE = new Sex("女");
private Sex(String title){ //构造私有化
this.title = title;
}
public String toString(){
return this.title;
}
public static Sex getInstance(String ch){
switch (ch){ //利用字符串判断
case "man":
return MALE;
case "woman":
return FEMALE;
default:
return null;
}
}
}
public class TestDemo {
public static void main(String args[]){
Sex sex = Sex.getInstance("man");
System.out.println(sex);
}
}
程序执行结果:
男
此程序直接使用 String
作为 switch
的判断条件,这样在取得实例化对象时就可以利用字符串来描述对象名字,这一点要比直接使用数字更加方便。
⏪ 温习回顾上一篇(点击跳转): 《【Java基础教程】(十七)包及访问权限篇 · 上:包的定义及导入、常用系统包概览,javac、java和jar命令的作用,package和 import关键字的应用~》
⏩ 继续阅读下一篇(点击跳转): 《【Java基础教程】(十九)异常处理篇 · 上:~》