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

news/2024/7/7 9:59:07

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,我会就博文中有疑义的问题做出解答。同时希望博文中不正确的地方各位加以指正!


http://www.niftyadmin.cn/n/1982551.html

相关文章

idea goland 插件 struct to struct

go-struct-to-struct idea goland 插件。实现自动生成 struct 间 转换代码。 https://plugins.jetbrains.com/plugin/22196-struct-to-struct/ IntelliJ plugin that Automatically generate two struct transformations through function declarations Usage define func …

JavaSE基础知识(十七)--Java复用代码之组合与继承

Java SE 是什么&#xff0c;包括哪些内容(十七)&#xff1f; 本文内容参考自Java8标准 再次感谢Java编程思想对本文的启发&#xff01; 复用代码是Java众多引人注目的功能之一&#xff0c;但要想成为极具革命性的语言&#xff0c;仅仅能够复制代码并对之加以改变是不够的(也就…

eclipse HttpServlet 类会报错

2019独角兽企业重金招聘Python工程师标准>>> 以前的eclipse因为更换电脑的位数&#xff0c;启动时会出现Failed to load the JNI shared library的错误&#xff0c;好像是和jdk位数&#xff08;32位或64&#xff09;有关系 重新copy了一个eclipse导进去项目遇到以下…

JavaSE基础知识(十七)--Java复用代码之静态代理

Java SE 是什么&#xff0c;包括哪些内容(十七)&#xff1f; 本文内容参考自Java8标准 再次感谢Java编程思想对本文的启发&#xff01; Java中复用代码的方式&#xff0c;除了之前博文中提到过的组合和继承之外&#xff0c;还有第三种&#xff1a;代理&#xff0c;而代理又分为…

JavaSE基础知识(十七)--Java复用代码之动态代理

Java SE 是什么&#xff0c;包括哪些内容(十七)&#xff1f; 本文内容参考自Java8标准 再次感谢Java编程思想对本文的启发&#xff01; 上一篇博文中详细说明了静态代理的内容&#xff0c;也指出了静态代理只适合小范围的使用(使用和维护都很麻烦)&#xff0c;真正强大的是动态…

JavaSE基础知识(十七)--Java动态代理中InvocationHandler中Object类型参数proxy的作用

Java SE 是什么&#xff0c;包括哪些内容(十七)&#xff1f; 本文内容参考自Java8标准 再次感谢Java编程思想对本文的启发&#xff01; 上一篇博文中详细说明了动态代理的内容&#xff0c;但是在说到调用处理器InvocationHandler的时候&#xff0c;有一个Object类型的参数prox…

JavaSE基础知识(十七)--Java复用代码之结合使用组合与继承(正确的初始化与清理)

Java SE 是什么&#xff0c;包括哪些内容(十七)&#xff1f; 本文内容参考自Java8标准 再次感谢Java编程思想对本文的启发&#xff01; 在我们的日常编程工作中&#xff0c;同时使用组合与继承是很常见的事情&#xff0c;下面通过一个例子来说明&#xff1a; PS&#xff1a;同…

基于HTML5实现3D热图Heatmap应用

为什么80%的码农都做不了架构师&#xff1f;>>> Heatmap热图通过众多数据点信息&#xff0c;汇聚成直观可视化颜色效果&#xff0c;热图已广泛被应用于气象预报、医疗成像、机房温度监控等行业&#xff0c;甚至应用于竞技体育领域的数据分析。 http://www.hightopo…