用构造器确保初始化
在java中通过构造器,类的设计者可以确保每个对象都会得到初始化。
在创建对象时,如果类具有构造器,java就会在用户又能了操作对象之前自动调用构造器,从而保证了初始化的进行。
构造器命名与类具有相同的名称。
来看一个简单的构造器
class Rock {
Rock() {
System.out.print("Rock ");
}
public static void main(String[] args) {
Rock rock = new Rock();
}
}
在上面代码中new Rock();时,程序将会为对象分配存储空间,并且调用相应的构造器。这就确保了在你对这个对象操作对象之前,它就已经被恰当的初始化了。
不接受任何参数的构造器叫做默认构造器,或者叫做无参构造器。和其他的方法一样,构造器也能带有形式参数,以便指定如何创建对象。对上面修改一下:
class Rock {
Rock(int i) {
System.out.print("Rock " + i + " ");
}
public static void main(String[] args) {
Rock rock = new Rock(3);
}
}
从概念上来说,“初始化“和“创建“是独立的。但是在java中两者被捆绑在一起,不能分离。
方法的重载
当创建一个对象时,也就给此对象分配到的存储空间取了一个名字。所谓方法则是给某个动作取名字。通过使用名字,你可以引用所有的对象和方法。
方法重载
下面这个例子示范了重载的构造器和重载的方法
class Tree {
int height;
Tree() {
height = 0;
System.out.print("Plant a seed");
}
Tree(int initHeight) {
height = initHeight;
System.out.print("Create Tree height is " + height + " feet tall" );
}
void info() {
System.out.print("Tree height is " + height + " feet tall");
}
void info(String s) {
System.out.print(s + " Tree height is " + height + " feet tall");
}
}
public class Overloading {
public static void main(String[] args) {
for(int i = 0; i < 5; i++) {
Tree t = new Tree(i);
t.info();
t.info("Over");
}
new Tree();
}
}
区分重载的方法
要是有几个方法具有相同的名字,那么我们可以通过这些方法参数进行区分。
涉及基本类型的重载
基本类型能从一个“较小“的类型自动提升至一个“较大“的类型,此过程一旦牵涉到重载,可能会造成一下混淆。
以返回值区分重载方法
例如 int x = f()
void f()
int f() {return 1;}
默认构造器
默认构造器(无参构造器),它的作用是用来创建一个“默认对象“。如果你写的类中没有构造器,则编译器会自动帮组你创建一个默认构造器,例如:
class Bird {}
public class DefaultConstructor {
public static void main(String[] s) {
Bird b = new Bird();
}
}
默认的构造器是Bird()
this关键字
this关键字只能在方法内部使用,表示对“调用方法的那个对象“的引用。
如果在方法内部调用同一个类的另一个方法,就不必使用this,直接调用即可。
public class Apricot {
void pick() {}
void pit() { pick(); }
}
只有当需要明确指出对当前对象的引用时,才需要使用this关键字。例如,当需要返回对当前对象的引用时,就使用return返回this。
public class HelloWorld {
int i = 0;
HelloWorld increment() {
i++;
return this;
}
void print() {
System.out.println("i = " + i);
}
public static void main(String[] args) {
HelloWorld x = new HelloWorld();
x.increment().increment().print();
}
}
由于increment()通过this关键字返回了对当前对象的引用,所以很容易在同一条语句里对同一个对象执行多次操作。
this关键字对于将当前对象传递给其他方法也很有用:
class Person {
public void eat(Apple apple) {
Apple peeled = apple.getPeeled();
System.out.println("Yummy");
}
}
class Peeler {
static Apple peel(Apple apple) {
return apple;
}
}
class Apple {
Apple getPeeled() {
return Peeler.peel(this);
}
}
public class HelloWorld {
public static void main(String[] args) {
new Person().eat(new Apple());
}
}
在构造器中调用构造器
当你在写一个类时,为这个类写了多个构造器,又是可能想在一个构造器中调用另一个构造器,以避免重复代码。可以使用this关键字做到这一点。
通常写this的时候,都是指“这个对象“或者“当前对象“,而且它本省表示对当前对象的引用。
在构造器中,如果为this添加了参数列表,将会产生对符合此参数列表的某个构造器的明确调用。
public class HelloWorld {
int petalCount = 0;
String s = "initial value";
HelloWorld(int petals) {
petalCount = petals;
System.out.println("Constructor w/ int arg only, petalCount = " + petalCount);
}
HelloWorld(String ss) {
s = ss;
System.out.println("Constructor w/ string arg only, s = " + ss);
}
HelloWorld(String s, int petals) {
this(s);
//this(petals); Can not call two!
this.petalCount = petals;
System.out.println("String & int args");
}
HelloWorld() {
this("hi", 47);
System.out.println("Default Constructor no args");
}
void printPetalCount() {
//!this(11); Not inside non-constructor
System.out.println("petalCount = " + petalCount + " s = " + s);
}
public static void main(String[] args) {
HelloWorld h = new HelloWorld();
}
}
这里的例子表明,尽管可以使用this调用一个构造器,但是不能调用两个。
另外,必须将构造器调用于最起始处,否则编译器会报错。
本例中,由于参数s和类中的成员变量s的名字相同。所以使用了this.s来表示成员变量,避免歧义。在printPetalCount()方法中也体现了,除了构造器之外,编译器禁止在其他任何方法中调用构造器。
static含义
这里讨论一下static(静态)方法。static方法就是没有this的方法,怎么理解呢?首先我们知道this表示调用方法的那个对象的引用。
static方法,是在类没有创建任何对象,可以通过类来调用static方法。通过static修饰的成员或者是方法,在编译时就已经分配好了内存,直到程序退出这块内存才被释放。这样做的意义是?在Java的世界里,一切都是对象,而对象是类的实体,对于一个类而言,如果需要使用它的成员变量或者是方法时,必须先将这个类实例化成对象后,才能通过对象的引用使用这些变量或者是方法。但是通过static修饰后,可以直接通过类名.*来直接访问。
终结处理和垃圾回收
程序员都理解初始化的重要性,但是清理工作同样重要。
垃圾回收只与内存有关,也就是说使用垃圾回收器的唯一原因是为了回收不再使用的内存。
数组
java中数组的概念:数组只是相同类型的、用一个标识符名称封装到一起的一个对象序列或基本类型数据序列。
数组的定义:只要在类型名后面添加一对方括号即可:
int[] a1; or int a1[];
String[] s;
需要说明的是,编译器不允许指定数组的大小。比如在c语言中,定义一个数组:int a[10];
。
现在我们在java中定义一个数组:int[] a;
到此为止,我们只是拥有了这个类型数组的引用(并且你已经为该引用分配了足够的存储空间),但是并没有给这个数组对象本身分配任何空间。
所以我们需要对这个数组进行初始化。
对于数组,初始化动作可以出现在代码的任何地方;
但是,还有一种特殊的初始化表达式,这种初始化表达式必须在创建数组的地方出现。通过一对花括号括起来的值来组成。
例如:int[] a = {1, 2, 3, 4, 5};
数组赋值给另一个数组:
int[] a = {1, 2, 3, 4, 5};
int[] b;
b = a;
在编程过程中,如果不能确定数组中元素的个数,怎么办?
可以直接用new在数组里创建元素。
例如:
public class HelloWorld {
public static void main(String[] args) {
int[] array;
array = new int[10];
System.out.println(array.length);
System.out.println(Arrays.toString(array));
}
}
并且数组中的每个元素都被初始化成空值(对于数字和字符是0)
当然数组也可以在定义是同时进行初始化。
改写上面程序:
public class HelloWorld {
public static void main(String[] args) {
int[] array = new int[10];
System.out.println(array.length);
System.out.println(Arrays.toString(array));
}
}
上面创建的数组都是基本类型的数据,如果需要创建一个非基本类型的数组,那么你需要创建一个引用数组。
例如以整型的包装容器类Integer为例,这是一个类,不是基本类型。
Integer[] a = new Integer[10];
但是我们创建的只是一个引用的数组,所以还要对每一个元素进行初始化(创建对象)。如果忘记创建对象,并且试图使用数组中的空引用,在运行时出现异常。
所以:
Integer[] a = new Integer[10];
for(int i = 0; i < 10; i++) {
a[i] = i;
}
也可以用花括号来初始化对象数组。
Integer[] a = {
new Integer(1),
new Integer(9),
3, //Autoboxing
}
Integer[] b = new Integer[]{
new Integer(1),
new Integer(9),
3, //Autoboxing
}
但是第一种的形式,只能在数组定义的时候使用。
可变参数列表
由于所有的类都直接或者是间接的继承于Object类(暂时还没有深刻体会,是不是类似于c中的void *呢),所以可以创建以Object数组为参数的方法。
public class HelloWorld {
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[] {"one", "two"});
printArray(new Object[] {new Integer(1), new Integer(2)});
}
}
在java se5中
public class HelloWorld {
static void printArray(Object... args) {
for(Object obj : args)
System.out.print(obj + " ");
System.out.println();
}
public static void main(String[] args) {
printArray(47, 11.11, 3.14F);
printArray("ONE", "TWO", "THREE");
}
}
枚举类型
枚举类型使用关键字enum
看一个列子:public enum Spiciness {NOT, MILD, HOT, FLAMING}
这里创建了一个名为Spiciness的枚举类型,它具有5个具名值。枚举类型的实例是常量。
为了使用这个enum,我们需要创建一个该类型的引用,并初始化。
public enum Spiciness {
NOT, MILD, HOT, FLAMING
};
public class HelloWorld {
public static void main(String[] args) {
Spiciness howHot = Spiciness.MILD;
System.out.println(howHot);
}
}
可以将enum作为类来看,事实上enum确实是类,并且有着自己的方法。