第六章 访问权限控制
-
包:库单元
包:包内包含有一组类,它们在单一的名字空间之下被组织在一起。
编译:当编译一个.java文件时,在.java文件中的每个类都会有一个输出文件,而该输出文件的名称与.java文件中的每个类的名称相同,只是多了一个.class后缀名。 Java可运行程序是一组可以打包并压缩为一个Java文档文件(JAR,使用Java的jar文档生成器)的.class文件。Java解释器负责这些文件的查找,装载和解释。
代码组织:类库实际上是一组类文件,其中每个文件都有一个public类(此类必须与文件名相同),以及任意数量的非public类,因此每个文件都有一个构件。如果希望这些构件(每一个都有它们自己的独立的.java和.class文件)从属于同一个群组,就可以使用关键字package。如果使用package语句,它必须是文件中除注释以外的第一句程序代码。在文件起始处写://这表示你在声明该编译单元是名为access的类库的一部分 package access;
牢记 package和import关键字允许你做的,是将单一的全局名字空间分开,使得无论多少人使用Internet以及Java开始编写类,都不会出现名称冲突问题。
独一无二的包名:package名称的第一部分是类的创建者的反顺序的Internet域名,第二部分是把package名称分解为你的机器上的一个目录。
路径:放置在环境变量CLASSPATH可以找到的地方。 -
import static
对于自制工具库,可以使用静态import语句import static来导入,以在你的系统上使用静态的自己定义的方法。静态import语句简化了方法的使用:import static java.lang.System.out; import static java.lang.Integer.*; public class TestStaticImport { public static void main(String[] args) { out.println(MAX_VALUE); out.println(toHexString(42)); } } /*Output: 2147483647 2a */
-
Java访问权限修饰词
在Thinking in Java读书笔记(一)的第一章的第1条笔记就提到了public、protected、private这几个修饰词。修饰词 本类 同一个包的类 子类 其他类 public √ √ √ √ protected √ √ √ × default √ √ × × private √ × × × 默认权限是包访问权限。
第七章 复用类
-
复用类
通过创建新类来复用代码,而不必从头开始编写。有两种方法:组合语法和继承语法。
组合语法通常用于想在新类中使用现有类的功能而非它的接口这种情形。
继承语法是使用某个现有类,并开发一个它的特殊版本,通常这意味着你在使用一个通用类,并为了某种特殊需要而将其特殊化。 -
组合语法
只需将对象引用置于新类中即可。对于非基本类型的对象,必须将其引用置于新类中,但可以直接定义基本类型数据。class WaterSource { private String s; WaterSource() { System.out.println("WaterSource()"); s = "Constructed"; } public String toString() {return s;} } public class SprinklerSystem { private String value1, value2, value3, value4; private WaterSource source = new WaterSource(); private int i; private float f; public String toString() { return "value1 = " + value1 + " " + "value2 = " + value2 + " " + "value3 = " + value3 + " " + "value4 = " + value4 + "\n" + "i = " + i + " " + "f = " + f + " " + "source = " + source; } public static void main(String[] args) { SprinklerSystem sprinklers = new SprinklerSystem(); System.out.println(sprinklers); } } /*Output: WaterSource() value1 = null value2 = null value3 = null value4 = null i = 0 f = 0.0 source = Constructed */
这里的toString()方法很特别,每一个非基本类型的对象都有一个toString()方法,而且当编译器需要一个String而你却只有一个对象时,该方法会被自动调用。
-
初始化引用
下列位置可进行引用的初始化:
①在定义对象的地方,这意味着他们总是能够在构造器被调用之前被初始化。
②在类的构造器中。
③就在正要使用这些对象之前,这种方法称为惰性初始化。在生成对象不值得及不必每次都生成对象的情况下,这种方式可以减少额外的负担。
④使用实例初始化。
示例:class Soap { private String s; Soap() { System.out.println("Soap()"); s = "Constructed"; } public String toString() {return s;} } public class Bath { //定义的同时初始化 private String s1 = "Happy", s2 = "Happy", s3, s4; private Soap castille; private int i; private float toy; public Bath() { System.out.println("Inside Bath()"); //构造器内初始化 s3 = "Joy"; toy = 3.14f; castille = new Soap(); } { i = 47; } //实例初始化 public String toString() { if(s4 == null) //惰性初始化 s4 = "Joy"; return "s1 = " + s1 + "\n" + "s2 = " + s2 + "\n" + "s3 = " + s3 + "\n" + "s4 = " + s4 + "\n" + "i = " + i + "\n" + "toy" + toy + "\n" + "castlle = " + castille; } public static void main(String[] args) { Bath b = new Bath(); System.out.print(b); } } /*Output: Inside Bath() Soap() s1 = Happy s2 = Happy s3 = Joy s4 = Joy i = 47 toy3.14 castlle = Constructed */
-
继承语法
声明语法:class SubClass extends SuperClass {/*...*/}
一般情况下,为了方便继承,SuperClass的所有数据成员都指定为private,所有的方法指定为public。
super关键字表示超类,可以帮助子类调用父类的方法。 -
main()
可以为每个类都创建一个main()方法。即使是一个程序中含有多个类,也只有命令行所调用的那个类的main()方法会被调用(即使被调用的类不是public类)。 -
初始化基类
子类会在构造器中调用基类构造器来执行初始化,而基类构造器具有执行基类初始化所需要的所有知识和能力。
不带参数的构造器:即使你不为子类创建构造器,编译器也会为你合成一个默认构造器,该构造器将调用基类的构造器。
带参数的构造器:如果没有默认的基类构造器,或者想调用一个带参数的基类构造器,就必须使用关键字super显式地编写调用基类构造器的语句,并且配以适当的参数列表。不然编译器会抱怨找不到这个基类构造器。 -
代理
组合和继承的中庸之道。我们将一个成员对象置于所要构造的类中(就像组合),但与此同时我们在新类中暴露了该成员对象的所有方法(就像继承)。示例://: SpaceShipControls.java public class SpaceShipControls { void up(int velocity) {} void down(int velocity) {} void left(int velocity) {} void right(int velocity) {} void forward(int velocity) {} void back(int velocity) {} void turboBoost() {} } =================================== //: SpaceShipDelegation.java public class SpaceShipDelegation { private String name; private SpaceShipControls controls = new SpaceShipControls(); //这里像是组合 public SpaceShipDelegation(String name) { this.name = name; } //Delegated methods 就像继承一样暴露所有的方法 public void back(int velocity) { controls.back(velocity); } public void down(int velocity) { controls.down(velocity); } public void forward(int velocity) { controls.forward(velocity); } public void left(int velocity) { controls.left(velocity); } public void right(int velocity) { controls.right(velocity); } public void up(int velocity) { controls.up(velocity); } public void turboBoost() { controls.turboBoost(); } public static void main(String[] args) { SpaceShipDelegation protector = new SpaceShipDelegation("NSEA Protector"); protector.forward(100); } }
-
@Override注解
Java SE5新增加@Override注解,它并不是关键字,但是可以把它当作关键字使用。当你想要覆写(重写)某个方法时,可以选择添加这个注解,在你不留心重载而并非覆写了该方法时,编译器就会生成一条错误信息。当子类重写父类方法时,编译器会检查该注解下方法是否正确。 -
向上转型
子类和父类的关系:子类是父类的一种类型。子类可能比父类含有更多的方法,但它必须至少具备父类中所含有的方法。在向上转型的过程中,类接口中唯一可能发生的事情是丢失方法,而不是获取它们。 -
final关键字
使用final关键字的三种情况:数据,方法,类。(1)final数据
常用情况:一个永不改变的编译时常量或一个在运行时被初始化的而并不想被改变的值。
针对编译期常量,可以在编译时执行计算式,减轻运行时的负担。在Java中,这类常量必须是基本数据类型,并且以final关键字表示。
一个既是static又是final的域(即编译时常量)只占据一段不能改变的储存空间。 按照惯例,既是static又是final的域将用大写表示,并使用下划线分隔各个单词。
当对引用使用final时,只确保该引用指向这个对象,但对象本身可以改变。
空白final:被声明为final但又未给定初值的域。只要在使用前初始化即可。
final参数:Java允许在参数列表中以声明的方式将参数指明为final,这意味着你无法在方法中更改参数引用所指向的对象。对于基本数据类型参数,可以理解为只可读参数不可改参数。(2)final方法
使用final方法的原因有两个:①锁定方法,以防任何继承类修改它的含义。②效率(Java早期版本的final会同意编译器针对该方法的所有调用都转为内嵌调用,不过在Java SE5/6时,虚拟机(特别是Hotspot技术)会优化这个问题,所以其实不用考虑②)。
类中所有的private方法都隐式地指定为是final的。(3)final类
当将某个类的整体定义为final时(通过将关键字final置于它的定义之前),就表明你不打算继承该类。 -
类的加载
每个类的编译代码都在它自己的独立、文件中,该文件只在需要使用代码程序时才会被加载。
第八章 多态
-
方法调用绑定
绑定:将一个方法调用同一个方法主体关联起来。
前期绑定:在程序执行前进行绑定(由编译器和连接程序实现)。
后期绑定:在运行时根据对象的类型进行绑定,后期绑定也叫动态绑定或运行时绑定。
Java中除了static方法和final方法(private方法属于final方法)之外,其他所有的方法都是后期绑定。
Java中所有方法都是通过动态绑定实现多态。 -
多态
指向子类的父类引用由于向上转型了,它只能访问父类中拥有的方法和属性,而对于子类中存在而父类中不存在的方法,该引用是不能使用的,尽管是重载该方法。若子类重写了父类中的某些方法,在调用该些方法的时候,必定是使用子类中定义的这些方法(动态连接、动态调用)。 -
多态的缺陷
(1) "覆盖"私有方法public class PrivateOverride { private void f() { System.out.println("private f()"); } public static void main(String[] args) { PrivateOverride po = new Derived(); po.f(); } } class Derived extends PrivateOverride { public void f() { System.out.println("public f()"); } } /*Output: private f() */ /*由于继承也会把基类的main方法继承下来,所以在eclipse中运行时编译器会询问执行 哪一个main,这里选择PrivateOverride即可。*/
上例中我们想输出的是public f(),但是由于private方法被自动认为是final方法,而且对子类是屏蔽的,所以Derived类中的f()是一个全新的方法。这里可得出结论:只有非private方法才能被覆盖,但是还需要密切注意覆盖private方法的现象。在导出类中,对于基类中的private方法,最好采用不同的名字。
(2) 域和静态方法
如果某个方法是静态的,它的行为就不具有多态性。静态方法是与类相关联的而非对象。 -
构造器和多态
构造器本身是static方法,只不过该static声明是隐式的,因此构造器并不具有多态性。
构造器的调用顺序:
(1) 调用基类构造器。这个步骤会不断地反复递归下去,首先是构造这种层次结构的根,然后是下一层的导出类,等等,直到最低层的导出类。
(2) 按声明顺序调用成员的初始化方法。
(3) 调用导出类构造器的主体。
例子:class Meal { Meal() {System.out.println("Meal()");} } class Bread { Bread() {System.out.println("Bread()");} } class Cheese { Cheese() {System.out.println("Cheese()");} } class Lettuce { Lettuce() {System.out.println("Lettuce()");} } class Lunch extends Meal { Lunch() {System.out.println("Lunch()");} } class PortableLunch extends Lunch { PortableLunch() {System.out.println("PortableLunch()");} } public class Sandwich extends PortableLunch { private Bread b = new Bread(); private Cheese c = new Cheese(); private Lettuce l = new Lettuce(); public Sandwich() {System.out.println("Sandwich()");} public static void main(String[] args) { new Sandwich(); } } /*Output: Meal() Lunch() PortableLunch() Bread() Cheese() Lettuce() Sandwich() */
由于构造器负责创建对象,这里会遇到一个细节问题,就是子类的成员变量还没初始化就被父类构造器调用。例子:
class Glyph { void draw() {System.out.println("Glyph.draw()");} Glyph() { System.out.println("Glyph() before draw()"); draw();//由于动态绑定为RoundGlyph对象,所以这里执行的是RoundGlyph的draw方法 System.out.println("Glyph() after draw()"); } } class RoundGlyph extends Glyph { private int radius = 1; RoundGlyph(int r) { radius = r; System.out.println("RoundGlyph.RoundGlyph().radius = " + radius); } void draw() { System.out.println("RoundGlyph.RoundGlyph().radius = " + radius); } } public class PolyConstructors { public static void main(String[] args) { new RoundGlyph(5); } } /*Output: Glyph() before draw() RoundGlyph.RoundGlyph().radius = 0 Glyph() after draw() RoundGlyph.RoundGlyph().radius = 5 */
所以在上述的构造器调用顺序之前,编译器会在其他任何事物发生之前,将分配给对象的存储空间初始化为二进制的零。优点是所有的东西都至少初始化为零(或是某些特殊数据类型中与“零”等价的值),而不仅仅留作垃圾。为避免错误,在构造器内唯一能够安全调用的那些方法是基类中的final方法(也适用于private方法,它们自动属于final方法)。
-
协变返回类型
Java SE5中添加了协变返回类型,它表示在导出类中的被覆盖方法可以返回基类方法的返回类型的某种导出类型:class Grain { public String toString() { return "Grain"; } } class Wheat extends Grain { public String toString() { return "Wheat"; } } class Mill { Grain process() { return new Grain(); } } class WheatMill extends Mill { Wheat process() { return new Wheat(); } } public class CovariantReturn { public static void main(String[] args) { Mill m = new Mill(); Grain g = m.process(); System.out.println(g); m = new WheatMill(); g = m.process(); System.out.println(g); } } /*Output: Grain Wheat */
Java SE5之前会强制process()的覆盖版本必须返回Grain而不能返回Wheat,而协变会允许返回更加具体的Wheat。