JavaSE基础知识(十六)--Java的类的访问权限(单例模式初步)

本文介绍了Java类的访问权限控制,强调了接口和实现分离的重要性,以及封装的概念。讲解了如何通过访问权限修饰词控制类的可见性和对象创建,特别是通过单例模式实现类的唯一实例。文章还讨论了类浏览器和JDK文档在理解类接口中的作用,并提醒开发者注意类的命名和访问权限规则。
摘要由CSDN通过智能技术生成

Java SE 是什么,包括哪些内容(十六)?

本文内容参考自Java8标准
在此特别感谢Java编程思想一书对本文的启发!

1、接口和实现

看到这里,那么在你以后的编程生涯中,你都需要牢记一点,尽量将接口和实现分离,下面的内容将会初步涉及到这种思想,往后会越加的深入,你会逐渐理解为什么接口和实现分离很重要!
访问权限的控制常被称作是具体实现的隐藏,把数据和方法包装进类中,以及具体实现的隐藏,被称为是封装,其结果是一个带有特征和行为的数据类型
出于两个很重要的原因,访问权限控制将权限的边界划在了数据类型的内部,第一个原因是要设定客户端程序员可以使用和不可以使用的界限,可以在结构中建立自己的机制,而不必担心客户端程序员偶然地将内部机制当做是他们可以使用的接口的一部分。
这个原因直接引出了第二个原因,即将接口和具体实现分离,如果结构(这个结构你可以单纯的理解成数据类型的内部结构,再简单一些就是数据类型所有的方法)是用于一组程序之中,而客户端程序员除了可以向接口(数据类型的方法,之前说过,调用方法就是向对象发送消息)发送信息之外而什么都不能做的话,那么开大类库的程序员就可以随意修改任何不是public修饰的内容(例如包访问权限,protected、private等),而不会破坏客户端代码。
为了清楚起见,可能会采用一种将public成员置于开头,后面跟着protected、包访问权限和private成员的创建类的形式,这样做的好处是类的使用者可以从头读起,首先阅读对他们而言最为重要的部分(即public成员,可以在.java文件的外部调用它们),等到遇见作为内部实现细节的非public成员停止阅读。
上面描述的排布就像下面这样。
代码示例:

// 理想中的代码排布
   public class OrganizedByAccess{
      public void pub1(){}
      public void pub2(){}
      public void pub3(){}
      private void pri1(){}
      private void pri2(){}
      private void pri3(){}
      private int i;
   }

这样做仅能使程序阅读起来稍微容易一些,但是接口和实现仍然是混在一起的。也就是说,仍能看到源代码的一部分,因为它就在类中(这段话的意思是,如果这么看,你仍然能看到源代码,能看到每个方法具体是怎么实现的),具体来说,就是看到如下图这样:
以下图示中的源码是java.util包中的Collections类:
你能在源码里面找到它的实现!
这个是源码,注释稍微有点多。
在上图中,你会发现,所有的方法的实现源代码你都能看到,图中总共有如下几个方法:

// 上图中的方法名称
   public static <T> void sort(List<T> list,Comparator<? super T> c){}
   public static <T> int binarySearch(List<? extends Comparable <? super T>> list,T key){}
   private static <T> int indexedbinarySearch(List<? extends Comparable<? super T>> list,T key){}
   private static <T> int iteratorbinarySearch(List<? extends Comparable<? super T>> list,T key){}
   private static <T> T get(ListIterator<? extends T> i,int index){}

幸好有JDK帮助文档,它的注释在某种程度上降低了程序代码的可读性对客户端程序员的重要性。
这句话怎么理解呢?还是通过截图来看吧:
以下是java.util包中的Collections类的JDK文档内容:
文档中只注明了类的方法(接口),你看不到任何的代码实现!
JDK文档的内容!
实际上,你只需要看这个文档就行了,里面包含了所有java.util.Collections类的方法(接口),每种方法都带了注释,简明扼要的告诉了你,每个方法都是干什么的,你没有必要再去通过源码来了解每个方法都是干什么的了。换句话说,就算你的源码简直不可读,没人看得懂,都没关系,有文档注释就行!注释能看懂,就能用!
将类的接口(方法)展现给类的使用者实际上是类浏览器的任务,类浏览器是一种以非常有用的方式来查阅所有可用的类,并告诉你它们可以做些什么(也就是显示出可用成员)的工具,在Java中,用Web浏览器浏览JDK文档可以达到使用类浏览器相同的效果。

2、类的访问权限

在Java中,访问权限修饰词也可以用于确定库中哪些类对于该库的使用者是可用的,如果希望某个类可以为客户端程序员使用,就可以通过把关键字public作为整个类的定义来达到目的,这样做甚至能控制客户端程序员是否能创建这个类的对象。
为了控制某个类的访问权限,修饰词必须出现在关键字class之前。因此可以像下面这样声明:
public class Widget{ /*/}
现在如果库的名字是:access
那么任何的客户端程序员都可以通过下面的声明访问Widget:
**improt access.Widget;或import access.

然而,这里还有一些额外的限制:
⑴、每个编译单元(
.java文件
)都只能有一个public类,这表示,每个编译单元都有单一的公共接口,用public类来表现。该接口可以按要求包含众多的支持包访问权限的类。如果在某个编译单元含有一个以上的public类,编译器就会报错。
⑵、public类的名称必须完全与含有该编译单元的文件名完全一致,包括大小写,所以对于Widget而言,文件的名称必须是Widget.java。而不能是widget.java或者是WIDGET.java、如果不一致,编译器将会报错。
⑶、虽然不是很常用,但是编译单元内不带public类也是完全可以的。在这种情况下,可以随意对文件命名(尽管随意这个词可能会给后期维护带来可能存在的麻烦)。
在创建一个包访问权限的类时,仍旧是在将该类的域声明为private时才有意义(应该尽可能的总是将域指定为私有的),但通常来说,将与类(包访问权限)相同的访问权限赋予方法也是合理的。
这里有个问题需要注意,类即不可以是private的(这样其它的类将不能使用它的对象提供的服务,因为根本就看不见,内部类除外,后面的博文会说到),也不能是protected的(内部类除外,后面博文会说到),所以对于类的访问权限,仅有两个选择:包访问权限或者是public。如果不希望其他任何类对该类拥有访问权限,可以将该类的所有构造方法设置为private,从而阻止任何人创建该类的对象但是有一个例外,就是你在该类的static成员内部可以创建对象再返回,下面是一个示例。
代码示例:

// static成员
   //类Soup1
   class Soup1{
      //private权限的构造器,意味着在这个类范围之外的其它类中不能创建对象。
      //在这个类的内部还是可以创建对象的。
      private Soup1(){}
      //类方法(static).
      public static Soup1 makeSoup1(){
         //返回一个匿名的对象。
         return new Soup1();
      }
   } 
   //类Soup2
   class Soup2{
      //private权限的构造器,意味着在这个类范围之外的其它类中不能创建对象。
      //在这个类的内部还是可以创建对象的。
      private Soup2(){}
      //创建private权限的对象。
      private static Soup2 ps1 = new Soup2();
      //返回静态的对象引用,这里千万要注意了,返回的是引用,不再是一个对象了。
      //这个是单例模式的简洁版本。
      //也是单例模式的核心代码,因为始终只有一个对象,也就是单例。
      public static Soup2 access(){
         return ps1;
      }
      //方法f()
      public void f(){}
   } 
   //类Lunch
   public class Lunch{
      //方法testPrivate()
      void testPrivate(){
         //不能通过new关键字创建对象,因为类Soup1的构造方法是private的。
         //Soup1 s1 = new Soup1();
      }
      //方法testStatic()
      void testStatic(){
         //返回一个类Soup1的匿名对象。
         //因为是类方法,所以直接用类的引用名称调用
         //Soup1.makeSoup1();
         Soup1 s1 = Soup1.makeSoup1();
      }
      //方法testSingleton()
      void testSingleton(){
         //返回一个对象的引用,调用方法f()
         Soup2.access().f();
      }
   }
   //到目前为止,绝大多数方法返回void或者基本类型.所以定义
   public static Soup1 makeSoup1(){
      return new Soup1();
   }

可能上面的代码会让人看起来有点迷惑,方法名称(makeSoup1)前面的词Soup1告知了该方法返回的东西,这里经常是void,意味着方法不返回任何东西,但是在这里,你能看到,它返回一个对象引用,示例中的这种情况,它返回的是一个对Soup1类的对象的引用。
类Soup1和类Soup2展示了如何通过将所有的构造方法指定为private来阻止直接创建某个类的实例,请一定要牢记,如果你没有明确地创建一个构造方法的话,Java编译器会为你创建一个默认的构造方法(不带任何形式参数),它的权限不是private!如果我们自己创建了构造方法,那Java编译器就不会创建了!
如果把构造方法的权限指定为private,那么谁也无法使用它创建该类的对象,但是,应该怎么样使用这个类呢?上面的代码示例给出了两个选择:
①、在Soup1中,创建一个static方法,它会创建一个新的Soup1对象,并返回它的引用。如果你想在返回引用之前做一些额外的工作,这种方式是被优先考虑的:比如限制创建对象的数量。
这种方式的问题是,会创建多个对象,每次调用方法都会创建一个全新的对象
②、Soup2用到了所谓的设计模式,这种特定的涉及模式叫"单例模式",这是因为在这种模式下,你始终只能创建一个类的唯一一个对象,Soup2的对象是作为private static成员创建的,并且有且只有一个,你只能通过public access()方法访问,再没有其他的任何方式可以访问到它。
总结:
正如前面提到过的,如果你没有为类指定一个访问权限修饰词的话,它就会默认得到包访问权限,这就意味着包内的任何其它类都能创建该类的对象,但是在包外是不行的(一定要记住,相同目录下的不具有明确的package的声明的文件,都被视作是该目录下的默认包的一部分),然而,如果该类的某个static成员是public的话,则客户端程序员依旧可以调用该static成员(可以将创建对象的代码放在这里)。
无论是在什么样的关系之中,设立一些为各成员所遵守的边界限始终是很重要的,当创建了一个类库,也就与该类库的用户建立了某种关系,这些用户就是客户端程序员,他们是另外一些程序员,他们将可能使用你的类库聚合成为一个新的应用程序,或是运用你的类库构建一个更大的类库!
如果不制定规则,客户端程序员就可以对类的所有成员随心而为,即使你可能并不希望他们直接复制其中的一些成员,但是,在这种情况下,所有的事物都是公开的。
本博文介绍了一组类是如何被打包到一个类库中的,其次,类是如何控制对其成员的访问的。
据说,用C语言开发项目,在50千行至100千行代码之间就会出现问题,这是因为C语言仅有单一的"名字空间",并且名称开始发生冲突,会引发额外的管理开销,但是在Java中,关键字package、包的命名模式,和关键字import,可以使你对名称进行完全的限制,因此名称冲突的问题是很容易避免的。
控制对成员的访问权限有两个原因,第一是为了是用户不要触碰那些他们不该触碰的部分,这些部分对于类内部的操作是必要的,但是它并不属于客户端程序员所需接口的一部分,因此,将方法和域指定成private,对客户端程序员而言是一种服务。因为这样他们可以很清楚地看到什么对他们重要,什么是他们可以忽略的,这样简化了他们对类的理解。
第二个原因,也是最重要的原因,是为了让类库的设计者可以更改类内部的工作方式,而不必担心这样会对客户端程序员产生重要影响。例如,最初可能会以某种方式创建一个类,然后发现如果更改程序结构,可以大大提高运行速度。如果接口和实现可以被明确地隔离和加以保护,那么就可以实现这一目的,而不必强制客户端重新编写代码,访问权限控制可以确保不会有任何客户端程序员依赖于某个类的底层实现的任何部分。
当具备了改变底层实施细节的能力时,不仅可以随意地改善设计,还可能会随意地犯错误,同时也增加了犯错的可能性,无论如何精心地计划并设计,都有可能犯错。当了解到你所犯错误是相对安全的时候,就可以更加放心地进行试验,也可以更快地学会、更快地完成任务。
类的公共接口是用户真正能够看到的,所以这一部分是在分析和设计过程中决定该类是否正确的最重要的部分。尽管如此,你仍然有进行改变的空间。如果在最初无法创建出正确的接口,那么只要不删除任何客户端程序员在他们的程序中已经用到的东西,就可以在后面添加更多的方法。
注意,访问权限控制专注于类库创建者和该类库的外部使用者之间的关系。这种关系就是一种通信方式。然而,在许多情况下事情并非如此。例如,你自己编写了所有的代码,或者你在一个组员聚集在一起的项目组工作,所有的东西都放在同一个包中,这些情况是另外一种不同的通信方式,因此严格的遵循访问权限规则并不一定是最佳的选择,默认(包)访问权限也许只是可行而已。
PS:时间有限,有关Java SE的内容会持续更新!今天就先写这么多,如果有疑问或者有兴趣,可以加QQ:2649160693,并注明CSDN,我会就博文中有疑义的问题做出解答。同时希望博文中不正确的地方各位加以指正!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值