方法重载:
- 区分重载方法的规则很简单,每个重载的方法都必须有一个独一无二的参数类型列表。参数顺序的不同,也足以区分两个方法,但是一般情况下别这么做,这样会使代码难以维护。
- 关于参数是基本类型(char,byte,short,int,long,float,double)的重载,如果传入的数据类型小于方法中声明的形式参数类型,实际数据类型就会被提升。char略有不同,如果无法找到恰好接受char参数的方法,就会把char直接提升到int型。如果传入的实际参数大于重载方法声明的形式参数,就得通过类型转化来执行窄化转换,如果不这样做,编译器就会报错。
在构造器中调用构造器:
- 尽管可以用this调用一个构造器,但却不能调用两个
- 必须将构造器调用置于最起始处,否则编译器会报错
- 除构造器外,编译器禁止在其他任何方法中调用构造器。
static的含义:
在static方法内不能调用非静态方法(这不是完全不可能,如果传递一个对象的引用到静态方法里(静态方法可以创建其自身的对象),然后通过这个引用,你就可以调用非静态方法和访问非静态数据成员了,但通常要达到这样的效果,你只需写一个非静态方法即可),反过来倒是可以的。
终结处理和垃圾回收:
- Java允许在类中定义一个名为finalize()的方法。它的工作原理“假定”是这样的:一旦垃圾回收器准备好释放对象占用的存储空间,将首先调用其finalize()方法,并且在下一次垃圾回收动作发生时,才会真正回收对象占用的内存。所以要是你打算用finalize(),就能在垃圾回收时刻做一些重要的清理工作。
- 对象可能不被垃圾回收;垃圾回收并不等于“析构”(c++中销毁对象必须用到这个函数);垃圾回收只与内存有关。
- 如果程序执行结束,并且垃圾回收器一直都没有释放你创建的任何对象的存储空间,则随着程序的退出,那些资源也会全部交还给操作系统。
- 为什么要使用finalize()方法呢?使用垃圾回收器的唯一原因就是回收程序不再使用的内存,对于与垃圾回收有关的任何行为来说(尤其是finalize()方法),它们也必须同内存及其回收有关。也就是说finalize()方法也是与内存及其回收有关。而无论对象是怎么创建的,垃圾回收器都会负责释放对象占据的所有内存。而java中一切皆为对象,那也就是说垃圾回收器可以释放所有被占据的内存。那为什么还需要finalize方法来释放呢?那就只有是某些特殊的情况,就是当通过某种创建对象方式以外的方式为对象分配了存储空间时,这时分配的内存是垃圾回收器无法释放的,所以需要finalize来进行释放(具体的例子在书本88页最上面有讲)。
- 无论是“垃圾回收”还是终结,都不保证一定会发生。如果Java虚拟机(JVM)并未面临内存耗尽的情景,它是不会浪费时间去执行垃圾回收以及恢复内存的。
- 对象的交互自引用(这种情况是怎么个引用法我还不是很明白)。
成员初始化:
Java尽力保证:所有变量在使用前都能得到恰当的初始化。但是对于局部变量,Java以编译时错误的形式来贯彻这种保证。
构造器初始化:
- 初始化顺序:在类的内部,变量定义的先后顺序决定了初始化的顺序。即使变量定义散布与方法定义之间,它们仍然会在任何方法(包括构造器)被调用之前得到初始化。如下例:
- 静态数据的初始化:无论创建多少个对象,静态数据都只占用一份存储区域。static静态关键字不能应用于局部变量,因此它只能作用于域。如果一个域是静态的基本类型域,且没有对它进行初始化,那么它就会获得基本类型的标准初值;如果它是一个对象引用,那么它的默认初始化值就是null。含有静态数据的类的初始化顺序是:先静态对象,而后是非静态对象。
- 非静态实例初始化:Java中也有被称为实例初始化的类似语法,用来初始化每一个对象的非静态变量,如下例:
class Mug { Mug(int marker) { print("Mug(" + marker + ")"); } void f(int marker) { print("f(" + marker + ")"); } } public class Mugs { Mug mug1; Mug mug2; { mug1 = new Mug(1); mug2 = new Mug(2); print("mug1 & mug2 initialized"); } Mugs() { print("Mugs()"); } Mugs(int i) { print("Mugs(int)"); } public static void main(String[] args) { print("Inside main()"); new Mugs(); print("new Mugs() completed"); new Mugs(1); print("new Mugs(1) completed"); } } /* Output: Inside main() Mug(1) Mug(2) mug1 & mug2 initialized Mugs() new Mugs() completed Mug(1) Mug(2) mug1 & mug2 initialized Mugs(int) new Mugs(1) completed *///:~
看起来与静态初始化子句一样,只不过少了静态关键字。这种语法对于支持“匿名内部类”的初始化是必须得,但它也使得你可以保证无论调用了哪个显示构造器,某些操作都会发生。从输出来看,实例初始化子句是在两个构造器之前执行的。
数组的初始化:
- 编译器不允许指定数组的大小。如果在编写程序时,并不确定数组里需要多少个元素,可以直接用new在数组里创建元素。看下例:
//: initialization/ArrayNew.java // Creating arrays with new. import java.util.*; import static net.mindview.util.Print.*; public class ArrayNew { public static void main(String[] args) { int[] a; Random rand = new Random(47); a = new int[rand.nextInt(20)]; print("length of a = " + a.length); print(Arrays.toString(a)); } } /* Output: length of a = 18 [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] *///:~
当然,在本例中,也可以在定义的同时初始化:
int[] a = new int[rand.nextInt(20)];
如果可能的话,请尽量这么做。
-
如果你创建了一个非基础类型的数组,那么你就创建了一个引用数组,而引用数组直到通过创建新的对象,并把对象赋值给引用,初始化进程才算结束。而如果忘记了创建对象,并且试图使用数组中的空引用,就会在运行时产生异常。对于引用数组的创建,可以先创建引用数组,再创建对象赋值给引用,如下例:
//: initialization/ArrayClassObj.java // Creating an array of nonprimitive objects. import java.util.*; import static net.mindview.util.Print.*; public class ArrayClassObj { public static void main(String[] args) { Random rand = new Random(47); Integer[] a = new Integer[rand.nextInt(20)]; print("length of a = " + a.length); for(int i = 0; i < a.length; i++) a[i] = rand.nextInt(500); // Autoboxing print(Arrays.toString(a)); } } /* Output: (Sample) length of a = 18 [55, 193, 361, 461, 429, 368, 200, 22, 207, 288, 128, 51, 89, 309, 278, 498, 361, 20] *///:~
先创建引用数组:
Integer[] a = new Integer[rand.nextInt(20)];
然后再创建对象赋值给引用:
a[i] = rand.nextInt(500);
也可以用花括号括起来的列表来初始化对象数组。有两种形式:
//: initialization/ArrayInit.java // Array initialization. import java.util.*; public class ArrayInit { public static void main(String[] args) { Integer[] a = { new Integer(1), new Integer(2), 3, // Autoboxing }; Integer[] b = new Integer[]{ new Integer(1), new Integer(2), 3, // Autoboxing }; System.out.println(Arrays.toString(a)); System.out.println(Arrays.toString(b)); } } /* Output: [1, 2, 3] [1, 2, 3] *///:~
在这两种形式中,初始化列表的最后一个逗号都是可选的。
-
可变参数列表:可变参数列表使得重载过程变得复杂了。你应该总是只在重载方法的一个版本上使用可变参数列表,或者压根就不用它。这里放一个可变参数列表的例子,可以看一次可变参数是怎么定义的:
//: initialization/OverloadingVarargs.java public class OverloadingVarargs { static void f(Character... args) { System.out.print("first"); for(Character c : args) System.out.print(" " + c); System.out.println(); } static void f(Integer... args) { System.out.print("second"); for(Integer i : args) System.out.print(" " + i); System.out.println(); } static void f(Long... args) { System.out.println("third"); } public static void main(String[] args) { f('a', 'b', 'c'); f(1); f(2, 1); f(0); f(0L); //! f(); // Won't compile -- ambiguous } } /* Output: first a b c second 1 second 2 1 second 0 third *///:~
枚举类型:
enum关键字,使得我们在需要群组并使用枚举类型集时,可以很方便的处理。enum有一个特别实用的特性,它可以在switch语句内使用。大体上,你可以将enum用作另外一种创建数据类型的方式,然后直接将所得到的类型拿来使用。下面是enum的一个实例:
//: initialization/Spiciness.java
public enum Spiciness {
NOT, MILD, MEDIUM, HOT, FLAMING
} ///:~
//: initialization/Burrito.java
public class Burrito {
Spiciness degree;
public Burrito(Spiciness degree) { this.degree = degree;}
public void describe() {
System.out.print("This burrito is ");
switch(degree) {
case NOT: System.out.println("not spicy at all.");
break;
case MILD:
case MEDIUM: System.out.println("a little hot.");
break;
case HOT:
case FLAMING:
default: System.out.println("maybe too hot.");
}
}
public static void main(String[] args) {
Burrito
plain = new Burrito(Spiciness.NOT),
greenChile = new Burrito(Spiciness.MEDIUM),
jalapeno = new Burrito(Spiciness.HOT);
plain.describe();
greenChile.describe();
jalapeno.describe();
}
} /* Output:
This burrito is not spicy at all.
This burrito is a little hot.
This burrito is maybe too hot.
*///:~