Java采用了构造器,并提供了“垃圾回收器”。
用构造器确保初始化
构造器采用与类完全相同的名称。调用构造器是编译器的责任。
不接受任何参数的构造器叫做默认构造器,或无参构造器。
在Java中,初始化和创建捆绑在一起,两者不能分离。
构造器是一种特殊类型的方法,因为它没有返回值。这与返回值为空(void)不同。构造器不返回任何东西。
方法重载
为了让方法名相同而形式参数不同的方法同时存在,需要用到方法重载 。
区分重载方法
对于方法相同的方法,Java确定调用哪一个的是依据:每个重载的方法都必须有一个独一无二的参数类型列表。
涉及基本类型的重载
基本类型能从一个较小的类型自动提升至一个较大的类型,此过程一旦牵扯到重载,可能会造成一些混淆。
复制 public class PrimitiveOverloading {
static void f1(char x) { System.out.print("f1(char) "); }
static void f1(byte x) { System.out.print("f1(byte) "); }
static void f1(short x) { System.out.print("f1(short) "); }
static void f1(int x) { System.out.print("f1(int) "); }
static void f1(long x) { System.out.print("f1(long) "); }
static void f1(float x) { System.out.print("f1(float) "); }
static void f1(double x) { System.out.print("f1(double) "); }
static void f2(byte x) { System.out.print("f2(byte) "); }
static void f2(short x) { System.out.print("f2(short) "); }
static void f2(int x) { System.out.print("f2(int) "); }
static void f2(long x) { System.out.print("f2(long) "); }
static void f2(float x) { System.out.print("f2(float) "); }
static void f2(double x) { System.out.print("f2(double) "); }
static void f3(short x) { System.out.print("f3(short) "); }
static void f3(int x) { System.out.print("f3(int) "); }
static void f3(long x) { System.out.print("f3(long) "); }
static void f3(float x) { System.out.print("f3(float) "); }
static void f3(double x) { System.out.print("f3(double) "); }
static void f4(int x) { System.out.print("f4(int) "); }
static void f4(long x) { System.out.print("f4(long) "); }
static void f4(float x) { System.out.print("f4(float) "); }
static void f4(double x) { System.out.print("f4(double) "); }
static void f5(long x) { System.out.print("f5(long) "); }
static void f5(float x) { System.out.print("f5(float) "); }
static void f5(double x) { System.out.print("f5(double) "); }
static void f6(float x) { System.out.print("f6(float) "); }
static void f6(double x) { System.out.print("f6(double) "); }
static void f7(double x) { System.out.print("f7(double) "); }
public static void main(String[] args) {
System.out.print(" 5: ");
f1(5); f2(5); f3(5); f4(5); f5(5); f6(5); f7(5); System.out.println();
char x1 = 'x';
System.out.print(" char: ");
f1(x1); f2(x1); f3(x1); f4(x1); f5(x1); f6(x1); f7(x1); System.out.println();
byte x2 = 0;
System.out.print(" byte: ");
f1(x2); f2(x2); f3(x2); f4(x2); f5(x2); f6(x2); f7(x2); System.out.println();
short x3 = 0;
System.out.print(" short: ");
f1(x3); f2(x3); f3(x3); f4(x3); f5(x3); f6(x3); f7(x3); System.out.println();
int x4 = 0;
System.out.print(" int: ");
f1(x4); f2(x4); f3(x4); f4(x4); f5(x4); f6(x4); f7(x4); System.out.println();
long x5 = 0;
System.out.print(" long: ");
f1(x5); f2(x5); f3(x5); f4(x5); f5(x5); f6(x5); f7(x5); System.out.println();
long x6 = 0;
System.out.print(" float: ");
f1(x6); f2(x6); f3(x6); f4(x6); f5(x6); f6(x6); f7(x6); System.out.println();
double x7 = 0;
System.out.print("double: ");
f1(x7); f2(x7); f3(x7); f4(x7); f5(x7); f6(x7); f7(x7); System.out.println();
}
} /** Output:
5: f1(int) f2(int) f3(int) f4(int) f5(long) f6(float) f7(double)
char: f1(char) f2(int) f3(int) f4(int) f5(long) f6(float) f7(double)
byte: f1(byte) f2(byte) f3(short) f4(int) f5(long) f6(float) f7(double)
short: f1(short) f2(short) f3(short) f4(int) f5(long) f6(float) f7(double)
int: f1(int) f2(int) f3(int) f4(int) f5(long) f6(float) f7(double)
long: f1(long) f2(long) f3(long) f4(long) f5(long) f6(float) f7(double)
float: f1(long) f2(long) f3(long) f4(long) f5(long) f6(float) f7(double)
double: f1(double) f2(double) f3(double) f4(double) f5(double) f6(double) f7(double)
*/
常数5被当做int处理。如果传入的数据类型小于方法中声明的形式参数类型,实际数据类型就会被提升。char类型略有不同,如果无法找到恰好接受char参数的方法,就会把char直接提升至int型。
如果传入的实际参数大于重载方法声明的形式参数:
复制 public class Demotion {
static void f1(char x) { System.out.print("f1(char) "); }
static void f1(byte x) { System.out.print("f1(byte) "); }
static void f1(short x) { System.out.print("f1(short) "); }
static void f1(int x) { System.out.print("f1(int) "); }
static void f1(long x) { System.out.print("f1(long) "); }
static void f1(float x) { System.out.print("f1(float) "); }
static void f1(double x) { System.out.print("f1(double) "); }
static void f2(char x) { System.out.print("f2(char) "); }
static void f2(byte x) { System.out.print("f2(byte) "); }
static void f2(short x) { System.out.print("f2(short) "); }
static void f2(int x) { System.out.print("f2(int) "); }
static void f2(long x) { System.out.print("f2(long) "); }
static void f2(float x) { System.out.print("f2(float) "); }
static void f3(char x) { System.out.print("f3(char) "); }
static void f3(byte x) { System.out.print("f3(byte) "); }
static void f3(short x) { System.out.print("f3(short) "); }
static void f3(int x) { System.out.print("f3(int) "); }
static void f3(long x) { System.out.print("f3(long) "); }
static void f4(char x) { System.out.print("f4(char) "); }
static void f4(byte x) { System.out.print("f4(byte) "); }
static void f4(short x) { System.out.print("f4(short) "); }
static void f4(int x) { System.out.print("f4(int) "); }
static void f5(char x) { System.out.print("f5(char) "); }
static void f5(byte x) { System.out.print("f5(byte) "); }
static void f5(short x) { System.out.print("f5(short) "); }
static void f6(char x) { System.out.print("f6(char) "); }
static void f6(byte x) { System.out.print("f6(byte) "); }
static void f7(char x) { System.out.print("f7(char) "); }
public static void main(String args[]) {
System.out.print("Double: ");
double x = 0;
// f1(x); f2(x); f3(x); f4(x); f5(x); f6(x); f7(x); error: no suitable method found for \
// f2(double) f3(double) f4(double) f5double) f6(double)
f1(x); f2((float)x); f3((long)x); f4((int)x); f5((short)x); f6((byte)x); f7((char)x);
}
} /** Output
Double: f1(double) f2(float) f3(long) f4(int) f5(short) f6(byte) f7(char)
*/
如果传入的参数较大,就需要显式进行强制转换(窄化转换)。
Java不允许方法只有返回值类型不同(即,不能通过返回值区分方法)。
默认构造器
如果没有定义构造器,编译器会默认创建一个默认构造器(无参构造器);如果提供了一个构造器,那么编辑器不再自动创建默认构造器。
this关键字
如果希望在方法内部获得对当前对象的引用,可以使用this关键字。this关键字只能在方法内部使用,表示对调用方法的那个对象的引用。
如果在方法内部调用同一个类的另一个方法,就不必使用this,直接调用即可。
在构造器中调用构造器
复制 public class ST {
int a = 0;
String s = "initial value";
public ST(int a) {
this.a = a;
System.out.println("Constructor w/ int arg only, a = " + a);
}
public ST(String s) {
this.s = s;
System.out.println("Constructor w/ String arg only, s = " + s);
}
public ST(int a, String s) {
this(a);
//! this(s); // Can't call two
System.out.println("String & int arg");
}
public ST() {
this(10, "hi");
System.out.println("Constructor without args");
//! this(10, "hi"); // 构造器必须置于最起始处
}
public void printST() {
System.out.println("a=" + a + " s=" + s);
}
public static void main(String args[]) {
ST x = new ST();
x.printST();
}
} /** Output
Constructor w/ int arg only, a = 10
String & int arg
Constructor without args
a=10 s=initial value
*/
注意:
在一个构造器中,调用另一个构造器,必须置于最起始处
static
static方法就是没有this的方法。在static方法的内部不能调用非静态方法;在非静态方法中,可以调用静态方法。
清理:终结处理和垃圾回收
如果对象(并未使用new)获得一块“特殊”的内存区域,由于垃圾回收器只知道释放那些经由new分配内存,所以它不知道如何释放该对象的这块特殊内存。为了应对这种情况,Java允许在类中定义一个名为finalize()的方法。它的工作原理假定是这样的:一旦垃圾回收器准备好释放对象占用的存储空间,将首先调用其finalize()方法,并且在下一次垃圾回收动作发生时,才会真正回收对象占用的内存。
但是finalize()和C++中的析构函数是不一样的。在C++中,正常情况下对象一定会被销毁,而Java中的对象却并非总是被回收。
有时,只要程序没有濒临存储空间用完的那一刻,对象占用的空间就总也得不到释放。如果程序执行结束,并且垃圾回收器一直都没释放创建的对象的存储空间,则随着程序的退出,那些资源也会全部交还给操作系统。
finalize()的用途
垃圾回收只与内存有关。
finalize()的需要限制到一种特殊情况,即通过某种创建对象方式以外的方式为对象分配了存储空间。这种情况主要发生在使用本地方法 的情况下。本地方法是一种在Java中调用非Java代码的方式。
实施清理
无论是垃圾回收还是终结,都不保证一定会发生(垃圾回收或终结)。如果Java虚拟机并未面临内存耗尽的情形,它不会浪费时间去执行垃圾回收以恢复内存。
垃圾回收器如何工作
自适应的、分代的、停止-复制、标记-清扫式垃圾回收器。
成员初始化
Java尽力保证:所有变量在使用前都能得到恰当的初始化。对于方法的局部变量,Java以编译时错误的形式来保证变量初始化。
复制 public void f() {
int i;
//! i++; // error: variable i might not have been initialized
}
类的每个基本类型数据成员保证都会有一个初始值(可以不用显式初始化),这种初始化要早于调用构造器函数 。
在类中定义一个对象引用时,如果不将其初始化,此引用就会获得一个特殊值null。
初始化顺序
在类的内部,变量定义的先后顺序决定了初始化的顺序。即使变量定义散布于方法定义间,它们仍旧会在任何方法(包括构造器)被调用前得到初始化。
静态数据的初始化
复制 class Bowl {
Bowl(int marker) {
System.out.println("Bowl(" + marker + ")"); // A1/1/1; A1/2/1; A2/1/1; A2/2/1; A2/3/1
}
void f1(int marker) {
System.out.println("f1(" + marker + ")"); // A1/4/1; A2/5/1; A4/3/1
}
}
class Table {
static Bowl bowl1 = new Bowl(1); // A1/1
Table() {
System.out.println("Table()"); // A1/3
bowl2.f1(1); // A1/4
}
void f2(int marker) {
System.out.println("f2(" + marker + ")"); // A7/1
}
static Bowl bowl2 = new Bowl(2); // A1/2
}
class Cupboard {
Bowl bowl3 = new Bowl(3); // A2/3; A4/1; A6/1
static Bowl bowl4 = new Bowl(4); // A2/1
Cupboard() {
System.out.println("Cupboard()"); // A2/4; A4/2; A6/2
bowl4.f1(2); // A2/5; A4/3; A6/3
}
void f3(int marker) {
System.out.println("f3(" + marker + ")"); // A8/1
}
static Bowl bowl5 = new Bowl(5); // A2/2
}
public class StaticInitialization {
public static void main(String args[]) {
System.out.println("Creating new Cupboard() in main"); // A3
new Cupboard(); // A4
System.out.println("Creating new Cupboard() in main"); // A5
new Cupboard(); // A6
table.f2(1); // A7
cupboard.f3(1); // A8
}
static Table table = new Table(); // A1
static Cupboard cupboard = new Cupboard(); // A2
} /** Output
Bowl(1)
Bowl(2)
Table()
f1(1)
Bowl(4)
Bowl(5)
Bowl(3)
Cupboard()
f1(2)
Creating new Cupboard() in main
Bowl(3)
Cupboard()
f1(2)
Creating new Cupboard() in main
Bowl(3)
Cupboard()
f1(2)
f2(1)
f3(1)
*/
无论创建多少个对象,静态数据都只占用一份存储区域。
static关键字不能应用于局部变量,因此它只能作用于域。
复制 void f1(int marker) {
//! static int i = 0; // error: illegal start of expression
System.out.println("f1(" + marker + ")");
}
如果一个域是静态的基本类型域,且也没有对它进行初始化,那么它就会获得基本类型的标准初值;如果它是一个对象引用,那么它的默认初始化值就是null。
显式的静态初始化
复制 class Cup {
Cup(int marker) {
System.out.println("Cup(" + marker + ")");
}
void f(int marker) {
System.out.println("f(" + marker + ")");
}
}
class Cups {
static Cup cup1;
static Cup cup2;
static {
cup1 = new Cup(1);
cup2 = new Cup(2);
}
static {
Cup cup3 = new Cup(3);
}
Cups() {
System.out.println("Cups()");
}
}
复制 public class ExplicitStatic {
public static void main(String args[]) {
System.out.println("Inside main()");
// Cups.cup1.f(99);
}
// static Cups cups1 = new Cups();
// static Cups cups2 = new Cups();
// static Cups cups3;
} /** Output
Inside main()
*/
复制 public class ExplicitStatic {
public static void main(String args[]) {
System.out.println("Inside main()");
Cups.cup1.f(99);
}
static Cups cups1 = new Cups();
static Cups cups2 = new Cups();
static Cups cups3;
} /** Output
Cup(1)
Cup(2)
Cup(3)
Cups()
Cups()
Inside main()
f(99)
*/
复制 public class ExplicitStatic {
public static void main(String args[]) {
System.out.println("Inside main()");
Cups.cup1.f(99);
}
// static Cups cups1 = new Cups();
// static Cups cups2 = new Cups();
// static Cups cups3;
} /** Output
Inside main()
Cup(1)
Cup(2)
Cup(3)
f(99)
*/
非静态实例初始化
实例初始化是在两个构造器之前执行的。
数组初始化
数组只是相同类型的,用一个标识符名称封装在一起的一个对象序列或基本类型序列。
两种定义方式(只是定义,并未创建对象):
在定义时,不允许指定数组大小。
所有数组都有一个不可更改的成员:length,通过它获取数组内元素个数。
为数组变量分配空间:
复制 a1 = new int[10];
Random rand = new Random();
a2 = new int[rand.nextInt(20)];
数组元素中的基本数据类型(不包括基本类型的包装类)会自动初始化为空值。
如果创建了一个非基本类型的数组,那么就创建了一个数组引用;如果忘记了创建对象,并试图使用数组中的引用,就会在运行时产生异常。
可以用花括号包起来的列表来初始化对象数组:
复制 Integer[] a = {
new Integer(1),
new Integer(2),
3, // 自动包装,最后的逗号可选
};
可变参数列表
Object[] args
复制 class A { }
public class VarArgs {
static void printArray(Object[] args) {
for (Object obj: args) {
System.out.print(obj + " ");
}
System.out.println();
}
public static void main(String args[]) {
printArray(new Object[] {
new Integer(47), new Float(3.14), new Double(11.11)
});
printArray(new Object[] {"one", "two", "three"});
printArray(new Object[] {new A(), new A(), new A()});
}
} /** Output
47 3.14 11.11
one two three
A@4aa298b7 A@7d4991ad A@28d93b30
*/
Java SE5之后,添加了新特性Object... args
:
复制 class A {}
public class NewVarArgs {
static void printArray(Object... args) {
for (Object obj: args) {
System.out.print(obj + " ");
}
System.out.println();
}
public static void main(String[] args) {
printArray(new Integer(47), new Float(3.14), new Double(11.11));
printArray(47, 3.14F, 11.11D);
printArray("one", "two", "three", "four");
printArray(new A(), new A(), new A(), new A());
printArray((Object[])new Integer[]{ 1, 2, 3, 4 });
printArray();
}
} /** Output
Args
47 3.14 11.11
47 3.14 11.11
one two three four
A@4aa298b7 A@7d4991ad A@28d93b30 A@1b6d3586
1 2 3 4
*/
可以使用具有Object之外类型的可变参数列表:
复制 void f(int required, String... trailing) {
...
}
在参数列表中可以使用任何类型的参数,包括基本类型。
但是这种是不可以的:
复制 //! static void f(int... ints, String... strs) {
//! ...
//! }
可变参数列表与自动包装机制可以和谐共处。
枚举类型
复制 //: Spiciness.java
public enum Spiciness { // 定义
NOT, MILD, MEDIUM, HOT, FLAMING
}
//: SimpleEnumUse.java
public class SimpleEnumUse { // 使用
public static void main(String args[]) {
Spiciness howHot = Spiciness.MEDIUM;
System.out.println(howHot);
}
} /**
MEDIUM
*/
这里创建一个名为Spiciness的枚举类型,它具有5个具名值。由于每个枚举类型的实例是常量,因此按照命名惯例它们都用大写字母表示(如果一个名字中有多个单词,用下划线将它们隔开)。
ordinal()和values
复制 enum Spiciness {
NOT, MILD, MEDIUM, HOT, FLAMING
}
public class EnumSample {
public static void main(String args[]) {
for (Spiciness s: Spiciness.values()) {
System.out.println(s + ", ordinal " + s.ordinal());
}
}
} /**
NOT, ordinal 0
MILD, ordinal 1
MEDIUM, ordinal 2
HOT, ordinal 3
FLAMING, ordinal 4
*/
enum类型对象可以结合switch使用。
复制 enum Spiciness {
NOT, MILD, MEDIUM, HOT, FLAMING
}
public class EnumSample {
public static void main(String args[]) {
Spiciness s = Spiciness.HOT;
switch(s) {
case NOT:
System.out.println("NOT");
break;
case MILD:
System.out.println("MILD");
break;
case MEDIUM:
System.out.println("MEDIUM");
break;
case HOT:
System.out.println("HOT");
break;
case FLAMING:
System.out.println("FLAMING");
break;
default:
System.out.println("UNKONWN");
}
}
} /**
HOT
*/