封装
在C语言中可以通过结构体封装多个变量,表示一个事物的多个属性。而Java是一种面向对象的编程语言,它更进一步,将变量和函数封装在一个类中,使用对象的形式模拟一个事物的多个方面,变量对应着对象中的域,表示对象的多个属性,而函数则对应着对象的方法,模拟对象的功能。因此我们可以通过对象域的值表示对象的状态,对象的方法表示对象所具备的功能,即通过方法向对象传递数据并改变它的状态或者让其执行某种行为。这是对象对变量和函数的封装。
从另一个角度讲,Java中的类还对实现和接口进行了封装。一个类封装了其内部的运行机制,还向外提供一系列接口。类的使用者可以实例化该类的对象,并使用这些接口改变对象的状态或使其执行一些行为,完成一定的功能,亦可以使用该对象组建更为复杂的对象。而类的实现者则负责实现该类的内部运行机制,并向外提供接口,因此这些接口就是使用者与实现者的通信协议。而在开发过程中,代码并不是一成不变的,实现者需要不断更新类的内部运行机制优化代码等,因此需要一种机制将实现与接口分离,避免实现者的改动对使用者的代码造成影响。所以,Java中使用了private, default, protected, public四种访问权限机制。
1. 代码组织
在介绍Java的访问权限机制之前,需要首先了解一下Java的代码组织形式。Java作为面向对象的语言,类是代码构成的基本单位,但是另外它还有另外两种层次的基本单位,一个是库的基本单位:包,即库可以由多个包组成,另一个是文件,即一个包可以由多个文件组成,Java文件是编译的基本单位,而一个文件中可以有多个类,类就是Java代码的基本构成单位了。Java还规定,每个文件中最多只能有一个public类(可以没有),且该类的名称应该与文件名一致。编译之后,每个类(包括内部类)会生成一个字节码文件,即.class文件,文件名与类名相同,内部类稍有不同。
在Java中使用package关键字控制.java文件和.class文件的路径,与类名共同构成该类的全限定名,并且在加载类的时候,通过搜寻classpath+(package控制的该类的路径)确定该类的.class文件并加载到虚拟机。
所以在使用一个类时,需要该类的全限定名才可以在classpath中搜寻到该类对应的.calss文件并使用,为了减少在代码编写的复杂性,Java使用import关键字,引入该类,在该文件的其他地方只需要某类的简单名称即可。
2. 访问权限控制
前面说需要将类的实现与接口分离,为了防止实现的改动对使用者代码的影响,Java中采用了private, default, protected, public四个关键字限制类的域和方法的访问范围。它们依次为类内,包内,继承结构,全局内的访问范围,范围依次扩大,这里需要注意的的是,protected修饰的域或方法,在包内而不在继承结构内的也具有访问权限,即protected具有更宽的权限范围。
在类的实现过程中,对访问权限控制关键词的使用原则就是使用私有化的关键词控制类的域,即属性,以及为实现类的运行机制而需要的方法,使用private将其控制在类的内部使用,也可以使用default(即不加关键字)和protected,扩大到包内,继承结构内使用,将运行机制的代码控制在一定的使用范围以内。而对于接口部分,则使用public关键字,这部分主要是方法,也是实现者和使用者协商好的使用协议。因此,类的实现与接口就被很好的分离开,实现可以变化,接口则相对固定,避免了改动对使用者的影响。变化部分和不变的部分分离是软件设计模式的核心思想,Java通过四个访问权限控制关键字的机制一定程度上支持了这种思想。
最后需要说明访问权限关键字public和default可以修饰class,但是一个文件中只能有一个public class,而default class 只有在同一个包内的代码可以使用该类。而private不能修饰类,因为类的外部不能使用该类则没有任何意义,而protected是为继承结构设计,对于类同样没有任何意义。
代码复用
1. 复用方式
合理复用已存在的代码可以很大程度上提升开发效率,复用代码是Java众多引人注目的功能之一。首先需要明白,Java中所有的事物都是围绕类展开,类是一切的基本单元,代码复用也不例外。代码复用就可以理解为类的复用,即使用现有的类去构建自己需要的新的类。那么构建方式就有两种类型,一种是堆积木方式,就是使用现有类的对象作为新类的域,该对象作为新类的一个属性,表达它所处的状态,并利用该对象的功能构建自己需要的功能等,这种方式称为组合。另一种复用方式就是改造的方式,就在现有类的基础上对类进行扩展,既保留了原有类的功能,又可以扩展新的功能,这种方式称为继承。在两种方式之间还有一种折中的方式称为代理,代理构建的新类为代理类,在代理类中包含一个现有类(即被代理类)的对象作为它的属性,而代理类向外暴露的接口与被代理类的接口一直,而在功能实现方面由代理负责调用被代理类对应方法,即功能由被代理类的对象实现,而代理类的使用者只有对代理类的依赖,不用管具体由谁实现。代理是一种设计模式,可以很好地解耦,这里只是简单介绍,个人的一点小小的理解。
2. 基本语法
组合和继承的语法都比较简单,这里需要注意一下对于继承关系,对象初始化的过程,可以理解为在子类中有一个隐士的父类对象的初始化。另外,对于父类和子类中方法的问题,对于同方法名,涉及到覆写和重载的概念。对于覆写是指子类中重新定义了父类中的相同方法,方法名和参数列表完全相同。而重载则是在一个类中相同方法名,不同参数列表,他们是完全不同的方法,只是因为功能相似而取的相同的方法名。而对于父类和子类中相同方法名,不同参数列表的情况,则完全是子类中接接口的扩展,与父类完全没有关系。为了防止想覆写父类方法,而方法签名写错而以外扩展接口的状况,Java中使用@Override注解标明某个方法是对父类的覆写。如果因为书写错误,而有@Override注解时,会有编译期的错误提醒。此外,重载与返回类型无关,但是对于覆写,返回类型应该一致,或者子类的方法中至少返回父类方法返回类型或者其导出类,对于返回导出类的情况,称之为协变返回类型。
3. 方式选择
组合和继承的关键问题在于如何选择组合还是继承。组合技术通常用于想在新类中使用现有类的功能而非它的接口,即新类中嵌入某个对象,让其实现某个功能,但新类的使用者看到的只是为新类所定义的接口,而非嵌入对象的接口。而继承是使用现有类并开发一个它的新版本,扩展了它的功能和接口。Java中是使用对象模拟现实世界中的事物,而对于组合和继承的选择一定程度上取决于事物之间的关系。组合的方式,其对象间的关系为"has-a",继承的方式,其对象间的关系为"is-a"。使用组合方式,使得对象间的关系没有约束,复用类更加灵活,但是缺点是新类是一个全新的类,在外部与现有的类没有任何关系,无法使用向上转型,利用多态的优势。而继承的方式则是对现有类的扩展,是现有类的一个特殊版本,可以自动向上转型,可以利用多态的优势,降低对具体实现的依赖,但是缺点在于现有类与已有类之间的关系约束,使用不再那么灵活。
final关键字
final表示不可变的类型,它可以修饰域,参数,方法和类。
1.final域
final域包括两种,一是静态的final域,是类的域,属于编译期常量,需要在定义时初始化,public修饰的final静态域,变量名通常大写,第二种是非静态的final域,即属于对象的不可变的属性,这一类要么在定义时初始化,要么在构造器中初始化,否则会有编译期错误。
2. final参数
final参数表示在该方法中不能对该对象修改。
注意,final修饰变量,无论是域还是参数,对于基本类型,是其值不能在运行时发生改变,而对于引用类型而言则是指该变量指向的对象不能发生改变,而对象本身的状态是可以变化的,至于不可变对象的定义Java中没有提供机制支持。
3. final方法
final方法原本有两个使用场景,一个是设计目的,避免类的此方法在子类中被覆写,第二个是优化性能,final方法可以自动变为内联函数,且不是动态绑定,没有多态特性,特高运行效率。但是随着虚拟机的优化机制越来越好,第二个目的已经基本不存在了。因此在考虑是否使用final方法时,只需要考虑这个是不是设计需要。
此外需要注意,private方法默认是final的,子类不能覆写private方法,因为子类并没有访问权限。对于定义方法名和参数列表完全与父类相同的情况,则视为定义另外完全没有关系的方法。
4. final类
final类,很简单,就是设计目的,即表示这个类不想被使用者扩展,即该类不能被继承。