1. 用构造器初始化
Java中通过提供构造器,确保每个类的对象都可以得到初始化,构造器的形式为:
className(){
//---
}
可以看见程序在初始化对象的时候自动执行了构造方法。
如果类中只有唯一的一个带参数的构造器,那么默认的无参构造器将不可用。
2. 方法重载
假如我们有多种多样的需求,想要初始化不同形态的对象,这时候我们可能需要多个构造器才能满足需求。而构造器的名字又是固定的,所以这个时候变化的就是参数类型或者是参数个数。我们把这种方法名不变,只改变参数类型或者参数个数的操作叫做方法重载。方法重载不仅支持构造器方法重载,也支持普通方法的重载。
public class ConstructorTest {
private int age;
private String name;
ConstructorTest(){
}
ConstructorTest(int i){
age=i;
}
ConstructorTest(String j){
name=j;
}
ConstructorTest(int i,String j){
age=i;
name=j;
}
public static void main(String[] args) {
ConstructorTest ct1 = new ConstructorTest();
ConstructorTest ct2 = new ConstructorTest(22);
ConstructorTest ct3 = new ConstructorTest("张三");
ConstructorTest ct4 = new ConstructorTest(23,"李四");
System.out.println("ct1 age=" + ct1.age + "---name=" + ct1.name);
System.out.println("ct2 age=" + ct2.age + "---name=" + ct2.name);
System.out.println("ct3 age=" + ct3.age + "---name=" + ct3.name);
System.out.println("ct4 age=" + ct4.age + "---name=" + ct4.name);
}
}
运行结果:
ct1 age=0---name=null
ct2 age=22---name=null
ct3 age=0---name=张三
ct4 age=23---name=李四
重载方法中如果传入了低类型的参数,那么如果找不到合适的方法,会被隐式的提升成高类型的数据。如果传入了高类型的参数,如果不进行类型转换,那么编译器会报错。Java中区分重载方法的依据是参数类型和参数个数,参数顺序区分会造成程序易读性较差,在一些情况下,也可以使用返回值区分,当然前提是如果你关心方法的返回值。
3. 缺省构造器
如前文所述,默认的构造器,就是没有形参的构造器,作为一个类的缺省构造器。如果程序员没有显示的在代码中创建一个构造器,那么Java会自动帮你创建一个无参构造器来完成初始化。当然如果程序员显示的创建了构造函数,那么Java就不会给你创建缺省构造器了。
4. this关键字
this关键字也是Java中尤为重要的一个关键字,主要有三个作用:
- 表示当前对象的引用,并返回当前对象.
- 表示类的成员变量,在有的构造函数中,形参与成员变量使用同一个字符串,这时候可以用this来区分,带有this的表示成员变量,用法如下:
- 可以在构造器中调用另一个构造器,使用this带参数代替构造器的方法名,用法如下:
5. 清理:终结处理和垃圾回收
Java中的内存清理使用的是Java自带的垃圾回收机制,但是不管怎么来说,这种方式都不是绝对安全的。Java的垃圾回收机制清理的是通过new创建的对象,而在某些特殊情况下,可能有些对象不是通过new创建的,这些对象如果不使用的时候,垃圾回收器是不能准确的清理他们的从而造成了这块特殊的内存区域一直得不到释放。Java中提供了finalize()方法来处理这一部分特殊的内存区域。它的处理流程是这样的,在Java垃圾回收器启用之前,会先调用这个方法进行一些必要的回收操作。但是针对这一块特殊的区域,或者是new创建的需要被回收的对象,一般情况下只有当Java虚拟机内存快要消耗殆尽的时候,垃圾回收器才会启动,毕竟启动垃圾回收器也是需要消耗资源的,所以不可能说实时存在。所说的特殊的创建对象方式,一般是指“本地方法”使用时,也就是在Java中调用非Java代码的时候发生的。所以一般情况下是不需要使用finalize()方法的。
finalize()通常还有另一个用法,由于它是在Java垃圾回收器启动之前执行的,所以可以用它来判断终结状态,即判断一个对象是否满足回收条件。
6. 成员初始化
Java尽量保证每个变量在使用之前都进行了初始化操作,变量分为局部变量和成员变量,局部变量如果没有显示的初始化,在使用它的时候会报错,而成员变量不会,如果没有显示的初始化一个成员变量,那么它会被默认的分配一个指定的值。
public class InitialValues {
boolean t;
char c;
byte b;
short s;
int i;
long l;
float f;
double d;
InitialValues reference;
void printInitialValues() {
System.out.println("Data type Initial value");
System.out.println("boolean " + t);
System.out.println("char [" + c + "]");
System.out.println("byte " + b);
System.out.println("short " + s);
System.out.println("int " + i);
System.out.println("long " + l);
System.out.println("float " + f);
System.out.println("double " + d);
System.out.println("reference " + reference);
}
public static void main(String[] args) {
InitialValues iv = new InitialValues();
iv.printInitialValues();
}
}
运行结果:
Data type Initial value
boolean false
char [ ]
byte 0
short 0
int 0
long 0
float 0.0
double 0.0
reference null
7. 构造器初始化
如前边对构造器的阐述,可以使用构造器来初始化类的成员变量,当对象被实例化之后,对象的成员变量会被初始化。静态成员变量只有在第一次使用它是才会被初始化,后边再次用到时不会被初始化。初始化顺序为创建对象时,先初始化这个类的静态变量,然后在堆上为这个对象分配内存,最后执行构造函数。
总结一下对象的创建过程,假设有一个名字为Dog的类:
- 当首次创建类型为Dog的对象时(构造器可以看做是静态方法),或者Dog类的静态方法/静态域被首次访问时,Java解释器必须查找类路径,以定位到Dog.class文件。
- 然后载入Dog.class,有关静态初始化的所有动作都会被执行。包括静态方法和静态代码块,具体执行顺序为静态方法和静态代码块的定义顺序。因此静态初始化在Class对象首次被加载的时候进行一次。
- 当用new Dog()创建对象的时候,首先将在堆上为Dog对象分配足够的存储空间。
- 这快存储空间会被请零,这就自动地将Dog对象中所有基本数据类型都设置称为了默认值,并且引用类型被设置为null。
- 执行所有出现字段定义的初始化工作。
- 执行构造器。
8. 数组初始化
数据是一系列相同数据类型封装起来的序列,它的初始化可以发生在任何时候,int[] a1表示一个int类型的数组,这个数组内部所有的值都是int类型,a1只是这个数组的一个引用,可以显示的通过如int[] a1={1,2,3};的形式进行初始化。如果不能确定数组的内容或者是长度,则可以通过new的形式来创建一个数组。int[] a = new int[20];这种创建也只是创建了一个引用数组,直到数组中的每一个字段都有确切的值了,初始化才真正的完成。如a[1]=3;
使用数组我们可以构建一个变参的函数,就是当方法的参数类型和个数都不确定的时候,我们可以使用一个数组作为形参。因为Object类是所有类型的父类,所以这个形参数组的类型就是Obejct类,对于基本数据类型,因为都有对应的包装类,所以也可以转换成Obejct类进行使用。
public class DifArgTest {
public static void printArray(Object...args){
for(Object obj:args){
System.out.print(obj + "");
}
}
public static void main(String[] args) {
DifArgTest.printArray("我","今年",24,"岁");
}
}
9. 枚举类型
创建一个枚举类型,类型内部的实例值是常量,因此按照通用的命名规范进行大写。同时需要使用枚举时,可以初始化一个引用然后进行赋值。
当我们创建枚举的时候编译器会自动为我们添加一些有用的特性,我觉得这是与其它语言相比更加完备的地方,比如它会创建一个toString()方法,这也就是为什么我们上边可以使用syso打印出来。编译器还会创建ordinal()方法,用来表示特定enum常量的声明顺序,以及一个static values()方法,该方法是一个静态的方法可以通过enum名字进行访问,方法的作用是按照enmu的声明顺序,产生一个由enum常量值组成的数组。
public class EnumOrder {
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
euum看起来像是一种新的数据类型,但是实际上enum是一个类,并且具有自己的方法。除了上边的特性之外,enum还有个更加实用的特性,由于它是一个常量集,因此可以和switch语句完美匹配。
public class EnumTest{
public EnumSet es;
EnumTest(EnumSet es){
this.es = es;
}
public enum EnumSet{
FIRST,SECOND,THIRD
}
public void doSwitchPrint(){
switch(es){
case FIRST:
System.out.println("This is First");
break;
case SECOND:
System.out.println("This is Second");
break;
case THIRD:
System.out.println("This is Third");
break;
default:
System.out.println("This is Error");
}
}
public static void main(String[] args) {
EnumTest et1 = new EnumTest(EnumSet.FIRST);
EnumTest et2 = new EnumTest(EnumSet.SECOND);
EnumTest et3 = new EnumTest(EnumSet.THIRD);
et1.doSwitchPrint();
et2.doSwitchPrint();
et3.doSwitchPrint();
}
}
运行结果:
This is First
This is Second
This is Third