句柄和作用域:
虽然在Java中一切皆对象,但实际上我们不直接操纵对象,而是用句柄来操纵对象。
句柄和对象的关系就像电视机和遥控器的关系,我们通过遥控器来操纵电视机,但它们两个是可以脱离对方独立存在的。其外,我们平时是拿着遥控器到处走动而不是电视机。
//创建句柄
String s;
//创建对象并和句柄链接
s = new String("abcd");
既然对象和句柄是可以独立存在的,那么就有作用域问题了:
Java中主类型的作用域的有效范围和C++一样,除了下面这种情况:
int x = 10;
{
int x = 0;
}
C++中此语法合法,即C++可以将一个变量隐藏在一个更大的作用域中;而Java中会认为重复定义报错。
但对象的作用域和主类型不同:Java中用new创建对象,它会超出作用域的范围。例如:
{
String s = new String("a string");
}
句柄s会在括号外消失,但s指向的对象不会消失(两者是相互独立的)。这种情况若在C++中,不主动释放对象空间的话可能发生内存溢出,但Java有“垃圾收集器”,会在某个时间自动回收对象空间。
创建对象数组时,实际上是创建的句柄数组,每个句柄会初始化为一个特殊值并有自己的关键字null, 试图使用为null的句柄,则会报错。
类由数据成员(字段)和成员函数(方法)组成。
若某个主数据类型属于一个类成员,那么即使不明确(显式)进行初始化,也可以保证它们获得一个默认 值。
但这种保证不适用于变量不属于类成员的情况。若变量并非一个类的字段,如在一个方法中定义一个没有初始化的变量,它会随机获得一个值而非默认值,在一些编译器中会将这种情况报错(如eclipse)。
Java方法中参数传递是值传递,这意味着数组是传递的数组的引用,对象是传递的对象的句柄。如当把对象赋值给另一个对象或作为方法的参数传递时,都会产生别名现象,和数组一样。
static(静态)关键字:一旦将什么东西设为static,数据或方法就不会同那个类的任何对象实例联系到一 起。我们可以通过实例出来的对象访问该静态数据或方法,更重要的是也可以直接通过类名访问。对方法来说,static 一项重要的用途就是帮助我们在不必创建对象的前提下调用那个方法,这一点是至关重要的——特别是在定义程序运行入口方法 main()的时候。
几乎所有运算符都只能操作基本数据类型(主类型)。唯一的例外是“=”、“==”和“!=”,它们能操作所有对象。除此以外,String 类支持“+”和“+=”。
注意:“==”和“!=”比较对象时是比较对象的句柄,如下:
Integer l1 = new Integer(11);
Integer l2 = new Integer(11);
System.out.println(l1 == l2);
System.out.println(l1 != l2);
虽然两个对象的值完全相同,但因为是比较句柄,所以实际上输出的结果先false, 后true. 如果真的想比较对象是否相同,应该用equals()方法。System.out.println(l1.equals(l2));
但如果你创建了新类,必须重写equals()方法,因为equals()方法默认是比较句柄。大多数Java类库都实现了equals()方法用来比较对象的内容,如上述代码中的Integer类。
组合和继承:
继承中衍生类的初始化问题:
- 衍生类的构造器会自动调用基础类的无参构造器进行初始化;但如果你为基础类写了带参构造器而没有写无参构造器,又没有在衍生类的构造器中显式调用基础类的带参构造器,就会出错。
- 衍生类的构造器中要首先设置对基础类的构造器的调用,这意味着在它之前不能出现任何东西。这种设计能够保证基础类总是能在衍生类调用它之前得到正确的初始化。
继承中方法名的隐藏问题:
有一个方法名被“过载”使用多次,在衍生类里对那个方法名的重新定义就不会隐藏任何基础类的版本。所以无论方法在这一级还是在一个基础类中定义,过载都会生效(和C++不同)。效果如下:
组合 or 继承?
其实这涉及到设计模式的问题----组合模式。如果一个问题可以描述成整体和部分的关系,组合模式是最合适的,比如上面的汽车类和车门、轮子、发动机类之间的关系。而继承则适用于属于关系,如上面的动物类和狗类、猫类之间的关系。
因为组合有着极大的简洁性和灵活性,所以一般情况优先考虑利用组合。有一种情况必须使用继承--上溯造型。
上溯造型:取得 一个对象句柄,并将其作为基础类型句柄使用。
继承的一个好处是它支持“累积开发”,允许我们引入新的代码,同时不会为现有代码造成错误。这样可将 新错误隔离到新代码里。
final关键字:
Java中用final关键字来声明某个东西不能被改变。对于基本数据类型,final 会将值变成一个常数;但对于对象句柄,final 会将句柄变成一个常数。进行声明时,必须将句柄初始化到一个具体的对象。而且永远不能将句柄变成指向另一个对象。然而,对象本身是可以修改的。
final参数:方法的参数可以设置为final,这意味着在这个个方法的内部,我们不能改变参数句柄。
final方法:第一是方法“上锁”,防止任何继承类改变它的本来含义。第二是程序执行的效率。
只要编译器发现一个final 方法调用,就会忽略为执行方法调用机制而采取的常规代码插入方法(将自变量压入堆栈;跳至方法代码并执行它;跳回来;清除堆栈自变量;最后对返回值进行处理)。相反,它会用方法主体内实际代码的一个副本来替换方法调用。这 样做可避免方法调用时的系统开销。当然,若方法体积太大,那么程序也会变得雍肿,可能受到到不到嵌入代码所带来的任何性能提升。Java 编译器能自动侦测这些情况,并颇为“明智”地决定是否嵌入一个 final 方法。然而,最好还是不要完全相信编译器能正确地作出所 有判断。通常,只有在方法的代码量非常少,或者想明确禁止方法被覆盖的时候,才应考虑将一个方法设为 final。 类内所有private 方法都自动成为final。
final类:将类定义成 final 后,结果只是禁止进行继承——没有更多的限制。然而,由于它禁止了继承,所以一个 final 类中的所有方法都默认为 final。