极*Java速成教程 - (3)

Java语言基础

访问权限控制

Java是一个面向对象的语言,当你不是它所设计的要面向的对象时,它就不会给你看你不该看到的东西,也就是“访问权限控制”。

亲疏有别,才能权限控制

包的概念

正如现实世界中有不同的一个个家族,家族中的每个成员都是由其先祖生育而来(仅考虑父系或者是母系一系),Java中的所有类也都是归属于其“家族”,或者用术语来说就是“包(package)”。
包就像Windows系统下的文件夹,一个套着一个,而包中的类就像文件夹中的文件(事实上类和包的物理储存的确是这么进行的,包就是文件夹,类就是文件)。那么就像文件在操作系统中的路径形如C:\Program Files\Common Files\System一样一层一层地展开,Java的包和类的结构也是形如java.util.Vector这样的层级形式。这样就起到了命名空间隔离的作用,不用太过担心起名的时候重名的问题了。

包的使用

在代码的第一行,可以使用package xxx.xxx.xxx这样的形式声明自己属于哪个包。当在其他地方使用的时候,只需要使用import xxx.xxx.*这样的形式,将包的整体或者是包中的一个类进行导入,就可以在代码中使用了。
在进行包和类的导入的时候,Java编译器会首先在Java安装目录中进行寻找,在安装目录中可以找到官方包,然后会去系统中设定的CLASSPATH中寻找,CLASSPATH顾名思义,就是自己专门设置的用来放类的地方,最后会在开发路径下进行寻找。当每个地方都找不到的时候,编译器就会报错。
通过import包和类,就可以起到方便开发的作用,在导入之前如果要使用一个类,就要使用它的全名,就像喊别人“张三的儿子张十三的儿子张二十二的儿子张二十七”一样繁琐。当导入了“张三的儿子张十三的儿子张二十二的儿子”这个命名空间以后,直接喊“张二十七”就可以直接喊到这个人了。

一些问题和麻烦

如果引用的两个包中的不同类重名的时候,就会出现冲突,就像“张三的儿子张十六的儿子张二十七”和“张壹的儿子张二十七”不是一个人一样,直接喊张二十七会带来困扰。Java中如果有同名的类,编译器就会报错,这时就必须使用全名才可以准确地定位到这个类。

如何控制权限

当前用包的方法区分开Java中类的组织结构以后,我们就可以来聊一聊Java的访问控制了

public

这是最为“开放”的权限,被public关键词修饰的成员,将对所有人开放,所有人都可以对其进行访问。

默认权限

当没有添加权限控制关键词是,成员就默认是这个访问权限,只有跟这个成员所属的类同属于同一个包的类,才可以访问到这个对象。相当于是包内的public,包外的不可访问。当类没有描述自己属于哪一个包时,与其同一文件夹下的类将视其为同一个包的类。

protected

被保护的成员,在包内所有其他类都可以访问,在包外只有这个类的子孙才可以访问,就像父亲继承给子女的东西子女都可以使用一样,但是如果没有继承,而是父类的对象的protected成员,那相当于是父亲的东西,子女是不可以使用的。

private

被保护的成员。对于面向对象来说,我们关注的是如何用对象构建程序,也就是对象的属性和行为。至于对象内部是如何运作的,我们是不需要去了解甚至是避免去触碰的,因为那是负责开发那个类的人(或者是精分出的另一个自己)所负责的,不恰当的访问可能会带来难以预料的问题甚至崩溃。而对于负责开发类的人来说,避免让外界的人触碰到自己的成员也是很重要的,所以就有了private关键词。
被这个关键词修饰的成员,将不能被外界所访问。

不给你看,只能我主动说

类的开发者将类内部具体的行为和对象通过权限管理隐藏起来,可以达到保护的作用,不让别人看到内部的实现,也方便了开发者后期对类的内容进行修改,不会给类的使用者带来重新修改代码的麻烦,这就是“封装”。
封装过后,大多数情况下,就只剩下了public关键词修饰的代码可供类的使用者进行访问,这就是类暴露给使用者的“接口”,就仿佛是一个小姑娘,你不能冒昧地直接从人家身上摸出来身份证看看人家多少岁,但是如果小姑娘说你可以问我多大了,那就可以问哪个小姑娘多少岁,虽然年龄这个数据在小姑娘那里是否经过了什么修饰和处理你是不知道的,但是你最后是可以拿到一个年龄的数据的(手斜)。

访问权限对于类也是有效的

  • 类只能是public的或者是默认的。
  • 对于一个文件来说,里面可以有好几个类,但是只能有一个类是public的,而且这个类必须和文件名同名。
  • 如果文件中没有public的类,也就是说文件中所有的类都是默认的权限,那么文件和类的命名是随意的,但是同样的,这个类是只有包内可见的。
  • 其他的类可以给这个类提供服务,但是不能是public的。

private的构造器

虽然一般来说,类的构造器得是public的,才能让别人正常创建类的对象,但是在一些特殊的时候,类的构造器是可以是private的。或许一个隐藏起来的构造器比较让人费解,但是有时候,这可以带来一些有趣的用法。

  1. 提供一个static的方法,在这个方法的内部new一个本类的对象,那么这个类所有的对象就都是通过这个static方法获得的,这可以带来一些比如统计对象数量之类的需求的一些便利。
  2. 搭配一个private的static的本类的属性变量,然后初始化这个类,并提供一个接口,这个接口返回一个本类的引用,使用者通过接口获取引用并调用其他public的方法,这个在设计模式中叫单例模式,可保证一个类只有一个对象。

复用,拼乐高的艺术

复用,就是一次定义多次使用,Java中的复用包括类的组合和继承。通过复用,可以减少开发的复杂性,在不破坏现有代码的前提下实现代码的重用,增加新的功能。

组合

类是Java的概念组成单元,对象是类的实体,从需求出发,将基本类型等多个对象拼合到一起,就像小块的积木拼成新的形状,构造成新的类,就是类的组合。

继承

继承(extend)就像是你现在有了一个积木变形金刚,但是你觉得这个变形金刚不够威武霸气,所以对它进行修改,扣下来它的外壳拼上新的外壳(重写方法,完全一样的方法名和参数列表,需要在方法名上面一行注明:@Override),并给他增加新的武器(添加新成员,包括重载成员方法)。
继承同样也受访问控制关键限制,被继承来的父类成员属性和父类成员方法将自动添加到子类中。在子类中可以对继承来的方法和属性进行覆盖(final关键词修饰的除外),重新定义他们的属性,重新描述他们的行为。
如果想在子类继承来的方法中调用父类的方法,可以使用super关键词,就像this指的是本类,super指的是父类。

用继承表达行为间的差异,并用字段表达状态上的变化

初始化

对于继承来说,子类会继承父类的所有接口,兴许还会带来新的属性和方法,而子类的对象,实际上是由一个父类的对象和子类新增的部分结构构成的。而构造器和初始化过程,也是由父类的和子类的两部分构成的。因此,开发者需要做一个新的工作,就是在子类构造时对父类进行构造,当父类使用默认构造器时,Java会自动调用父类的构造方法,但当开发者定义了父类构造器时,就要在子类构造器中使用super关键词调用父类的构造器,完成由深及浅,由父到子的构造过程。
类,是被惰性加载的,只有在初次使用(包括使用static方法)时,它才会被加载,而static对象和代码是在加载时按照定义顺序初始化的。因此,类的初始化的顺序可能是父类静态成员,子类静态成员,父类成员变量,父类构造器,子类成员变量,子类构造器。调用未完全初始化的对象容易出现问题。
对象的清理顺序与初始化顺序相反。

向上转型

这是个历史遗留的名称,有点拗口和令人费解,记住就好。
子类是父类的超集,子类是父类的特例。那么可以这么理解:子类满足父类的一切要求,子类就是一个父类。那么,父类类型的引用名自然可以指向子类,父类类型的参数列表也可以接受子类类型的参数,这些用父类的东西去控制子类的应用,这就是“向上转型”。

final

final表示最终,表示某种意义上的不可修改,因此是要被慎重使用的。

final在数据

被final修饰的数据不可被改变,对于一个基本类型的变量来说,就是这个变量的值不可以被改变,而对于一个对象的引用来说,则是这个对象不可以被改变,这个引用不可以引用到其他的对象,但是这个对象的内容是可以被改变的。
被final修饰的没有对象的空白变量名,必须在声明时或者构造器中被初始化。
在方法的参数列表中出现的final意味着在方法中不可以对这个引用进行修改,对于基本类型来说就是值,对于对象来说就是引用指向的对象。

final在方法

final用来修饰方法时,表示这个方法是最终版本,不能再重写覆盖了。同时final还有一个作用就是将函数嵌入到调用这个函数的地方,省去了调用函数的开销,然而现在的虚拟机已经足够高级学会自动优化了,所以这个用法基本上也不用了。
private方法是隐含final意味的。但在子类中定义一个与父类重名的private方法是被允许的,这样并非覆盖了父类的方法,因为对于子类来说,父类的private方法是不可见不存在的,定义一个同名的方法也是与父类方法毫无关系的。

final在类

用final修饰类,就表明这个类是不可以变动的,是不能有子类不能被继承的。当不希望别人对类的设计进行修改,或者处于安全等考虑时,才考虑将类修饰为final。

何时使用组合,何时使用继承,是一个需要认真思考的问题。

is-a和has-a是大有不同的。当一个类是另一个类的特例,并具有完全一样的方法时,就是is-a,就比如青砖红砖都是砖,那么继承可能是一个更合适的选择(但是绝大多数时候is-like-a是更常见的情况,长得像就可以,防火砖也是砖,虽然它增加了防火的属性和相关方法)。当一个类使用了另一个类的功能而非接口的时候,就是has-a,比如汽车都有方向盘,但是汽车并非是方向盘的继承和扩展,就该使用组合(这时候思考一下本文第一篇举的例子,是不是有一些不合适)。至于真实代码中具体结构的设计,就要根据实际情况进行判断和抉择了。
组合不可以向上转型,继承可以向上转型,在编写程序的时候,应该对此进行思考。

代理

如果说组合太过浅表,继承又太过深入,低层的类与上层的类并没有父子间的密切关系的话,那么还有一种叫“代理”的模式可以选择,其含义就是通过在类中创建低层类的对象,然后在高层类中包装低层类的方法提供调用。

posted @ 2019-11-22 20:28  很懒的虫  阅读(504)  评论(1编辑  收藏  举报