本章主要讲解java的构造器(构造方法)和“垃圾处理器”。
5.1 用构造器确保初始化
首先,构造器的方法名与类名相同,保证了构造器名称不会与类中成员命名冲突,同时编译器在初始化期间能够自动明确调用该构造器。在java中,“初始化”与“创建”捆绑在一起,不能分离。例如,在创建对象时,new Rock();将会为对象分配存储空间,并调用相应的构造器,以确保在你能操作对象前,它已经被恰当的初始化。(***)
其次,不接受任何参数的构造器叫做默认构造器,也是无参构造器。当然,也可以带有形式参数,例如,
package init;
public class Construstor {
public static void main(String[] args){
for(int i=0;i<8;i++){
new Rock(i); //因为构造方法是带有形式参数的,所以在初始化对象时就提供实际参数
}
}
}
class Rock{
Rock(int i){ //构造方法含参
System.out.println("Rock"+i+"");
}
}
构造器是一种特殊类型的方法,因为没有返回值,不会返回任何东西,不同于void。
package init;
public class UseThis {
String s;
void print(){
System.out.println(s);
}
public static void main(String[] args) {
UseThis u=new UseThis(); //静态方法必须通过对象的引用才能调用非静态方法
u.print(); //用于测试未初始化的string变量的默认值 ,结果输出null
}
}
5.2方法重载
为了让方法名相同而形式参数不同的构造器同时存在,必须用到方法重载。每个重载的方法都必须有一个独一无二的参数形式列表。
5.2.1涉及基本类型的重载
基本类型能从一个“较小”的类型自动提升为一个“较大”的类型。
如果传入的数据类型(实际参数类型)小于方法中声明的形式参数类型,实际数据类型会被提升;
char类型不同,如果无法找到恰好接收char参数的方法,就会把char直接提升为int类型;
如果传入的实际参数大于重载方法声明的形式参数,会发生窄化转换,如果不这样做,编译器会报错。
5.2.2不能用返回值来区分重载方法
5.3默认构造器
它的作用是创建一个“默认对象”。
public class Construstor {
public static void main(String[] args){
<span style="white-space:pre"> </span>//Rock r=new Rock(); //(1)
Rock r=new Rock(1);
}
}
//class{} //如果你写的类中没有构造器,编译器会自动创建一个默认构造器,(1)可以运行
class Rock{
Rock(int i){ //如果已经定义了构造器,无论是否有参数,编译器就不会帮你创建默认构造器,此时(1)报错
System.out.println("Rock"+i+"");
}
}
5.4 this 关键字
this关键字只能在方法内部使用,表示对“调用方法的那个对象的引用”。this的用法和其他对象引用并无不同。但要注意,如果在方法内部调用同一个类的另一个方法,不需要使用this,直接调用即可。当前方法的this引用会自动应用于同一类的其他方法。
只有当需要明确指出对当前对象的引用时,才需要使用this关键字。例如,当需要返回对当前对象的引用时,return this;
this关键字对于将当前对象传递给其他方法很有用。(????书上例子没看懂)
package init;
public class UseThis {
static String s="hello"; //在静态的main函数输出s,必须是静态的数据成员,不能调用非静态方法和非静态数据成员,除非在静态方法里创建对象的
UseThis(){ //引用,用这个引用去调用非静态的数据成员和成员方法
this("hi",47);
}
UseThis(String s,int i){
//this.s=s; //注释这行后,输出hi,hello;
System.out.println(s);
}
public static void main(String[] args) {
UseThis u=new UseThis();
System.out.println(s);
}
} //但是,非静态方法可以访问静态数据成员和成员方法。
this.s=s;表示形式参数的复制给了数据成员的引用,因为这二者指向了同一个值。所以,程序中加上这句话后,输出结果相同,都是“hi”。因为参数s的名称与数据成员名字相同,所以使用this.s代替数据成员,this 代表的当前对象的引用。
5.4.1在构造器中调用构造器
可以为一个类写多个构造器,在一个构造器中调用另一个构造器(只能调用一个,且调用必须置于最起始处),以避免重复代码。在构造器中,如果为this添加了参数列表,将产生符合此参数列表的某个构造器的调用。
除构造器外,编译器禁止在其他任何地方调用构造器。
5.5 清理:终结处理和垃圾回收(此部分内容会在以后 单独提出,整理成文************************************)
(1)对象可能不被垃圾回收
(2)垃圾回收并不等于“析构”
(3)垃圾回收只与内存有关:垃圾回收器的唯一目的是回收程序不再使用的内存,无论对象时怎样创建的。
java的垃圾回收机制并不是万能的。假定你的对象获得了一块“特殊”的内存区域,并非使用new,由于垃圾回收器只知道释放那些经由new分配的内存,所以它不知道该如何释放这块“特殊”内存。为了应对这种情况,java允许在类中定义finalize()方法,就能在垃圾回收时做一些重要的清理工作。
5.6成员初始化
package init;
public class UseThis {
String s;
UseThis ut;
void print(){
System.out.println(s);
System.out.println(ut);
}
public static void main(String[] args) {
UseThis u=new UseThis(); //静态方法必须通过对象的引用才能调用非静态方法
u.print(); //用于测试为初始化的string变量的默认值
}
}
在类里定义了一个对象引用,如果不将其初始化,此引用就会获得一个特殊值null。在上述程序中,对象引用指的是ut,还是u????
5.6.1指定初始化
对于基本或非基本类型的对象,都可以在定义时初始化,int i=99;
也可以创建对象时初始化,
UseThis u=new UseThis(1); //如果没有为u指定初始值就尝试使用它,就会出现运行时错误。
也可以通过某个方法初始化,int i=f( a);但 a 必须已经初始化才可以使用.
5.7 构造器初始化
牢记:无法阻止自动初始化的执行,它将在构造器被调用之前发生。
5.7.1初始化顺序
package init;
class Window{
Window(int i){
System.out.println("window"+i);
}
}
class House{
Window w1=new Window(1);
House(){
System.out.println("house()");
w3=new Window(33); //w3会被初始化两次,第一次在构造器前,即(1)处,第二次在构造器内,也就是这里。第一次引用的对象被丢弃,并作为垃圾回收
}
Window w2=new Window(2);
void f(){
System.out.println("f()");//
}
Window w3=new Window(3); //该定义也先于方法被调用前(1)
}
public class OrderOfInit {
public static void main(String[] args) {
House h=new House();
h.f(); //调用house的f方法,但是house类中的变量会先于方法调用之前被定义并初始化
}
}/*输出顺序:window1
window2
window3
house()
window33
f()*/
5.7.2静态数据初始化无论创建多少个对象,静态数据的都只占用一份存储区域。
static关键字不能应用于局部变量,只能作用于域。
如果一个域是基本类型,在没有初始化前,会获得默认初值,如果是对象引用,默认初始值是null。
static int i=47;
House h2=new House(2);
static House h1=new House(1); //静态成员初始化与非静态没有什么区别,但是要先于上面的非静态初始化(*****)
同时,静态初始化只在class对象首次加载时执行一次。而非静态初始化在每次class加载时都会执行。
5.7.3显式的静态初始化
“静态程序块”:
class Spoon{
static int i;
static { //表示这段代码只会被执行一次:当首次生成这个类的对象时,或者首次访问属于这个类的静态数据成员时。
i=47;
}
}
5.7.4非静态实例的初始化
package init;
class Mug{
Mug(int i){
System.out.println("Mug"+i);
}
}
public class Mugs {
Mug mug1;
Mug mug2;
{
mug1=new Mug(1);
mug2=new Mug(2); //实例初始化子句,相对于
}
// static Mug mug1; //如果程序改成这样,输出也会改变,因为静态代码段,也会先于构造器执行,但是只执行一次
// static Mug mug2; /*Mug1
// static{ Mug2
// mug1=new Mug(1); Mugs
// mug2=new Mug(2); Mugs(int)
// }
Mugs(){
System.out.println("Mugs");
}
Mugs(int i){
System.out.println("Mugs(int)");
}
public static void main(String[] args) {
new Mugs();
new Mugs(1);
}
}/*Mug1
Mug2
Mugs
Mug1
Mug2
Mugs()*///由输出可以看出,无论调用那个显式构造器,实例初始化子句都会先于构造器被调用
5.8数组初始化
int [] a;与int a[];含义相同,但是前一种 更合理,表明类型是“一个int型数组”。
package init;
public class ArrayOfPrimitives {
public static void main(String[] args) {
int[] a1={1,2,3,4,5}; //直接在定义时初始化,此时存储空间的分配将由编译器负责
int[] a2; //编译器不允许指定数组大小,现在拥有的只是对数组的引用,而且已经为该引用分配了足够多的内存空间,而且也没有给数组对象本身分配任何空间
a2=a1; //此时传递的是引用,a1,a2指向了同一个数组
for(int i=0;i<a2.length;i++){ //数组的初始化动作可以在代码任何地方
a2[i]=a2[i]+1; //a2指向的值改变了,对应的a1指向的值也会改变
}
for(int i=0;i<a1.length;i++){ //所有数组,无论是对象还是基本类型,都有一个固定成员length
System.out.print(a1[i]+" ");
}
}
}/*output:2 3 4 5 6*/
分别创建基本类型的数组和非基本类型的数组:
package init;
import java.util.*;
public class ArrayOfPrimitives {
public static void main(String[] args) {
Random rand=new Random(47);
int[] a=new int[rand.nextInt(20)]; //定义随机大小的int型数组
//应该尽量在数组定义时将其初始化
Integer[] b=new Integer[rand.nextInt(20)];//定义随机大小的非基本类型数组,此时还没有初始化,还是引用数组
Integer[] c={
new Integer(1), //也可以用花括号括起来的列表初始化对象数组
new Integer(2),
3, //逗号是可选的
};
System.out.println(Arrays.toString(a));//打印未初始化的a数组
//System.out.println(Arrays.toString(b));//运行异常,因为b数组未初始化,是空引用
System.out.println(Arrays.toString(c));
for(int i=0;i<b.length;i++){
b[i]=rand.nextInt(50);//只有当把对象赋值给引用后,初始化进程才算结束
}
System.out.println(Arrays.toString(b));//此时可以打印出b数组
}
}/*output:[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
[1, 2, 3]
[43, 11, 11, 29, 18, 0, 22, 7, 38, 28, 1, 39, 9, 28, 48]*/
5.8.1可变参数列表
应用于参数个数或类型位置未知的场合
以Object数组为参数的方法:
package init;
class A{}
public class VarArgs {
static void printArray1(Object[] args){ //定义Object数组,与下面的方法进行比较
for(Object obj:args){ //用foreach语法
System.out.print(obj+" ");
}
System.out.println();
}
static void printArray2(Object... args){ //定义Object,可变参数列表,参数个数和类型不限
for(Object obj:args){ //用foreach语法
System.out.print(obj+" ");
}
System.out.println();
}
static void printArray3(int i,Object... args){ //也可以在可变参数列表中指定一个object之外的类型
for(Object obj:args){ //用foreach语法
System.out.print(obj+" ");
}
System.out.println();
}
// static void printArray3(Object... args){ //与上面的参数列表不同,方法名相同,但是这样做是不安全的
// for(Object obj:args){ //会导致编译器运行错误,要用参数列表严格分开
// System.out.print(obj+" ");
// }
// System.out.println();
// }
static void printArray3(char i,Object... args){ //将上面参数列表的int改为char就可以避免错误
for(Object obj:args){
System.out.print("four");
}
System.out.println();
}
public static void main(String[] args) {
printArray1(new Object[] //对于第一种方法,需要创建object对象引用
{new A(),new Integer(47),"one",1});//创建了四种不同的对象,其中new A输出的是类的名字和对象的地址
//printArray1(1,2,3,4); 运行错误,必须创建object对象
printArray1(new Object[]{new Integer(47),new Float(3.14),new Double(11.11)});
printArray2(1,2,3,4); //可以是任何类型,可以混合使用
printArray2(new A(),new Integer(48),"two",2);
printArray3(3,"three"); //参数列表中指定了一个int型
printArray3('a',"three"); //调用的是第四个方法,因为a是char型的
}
}/*output:init.A@139a55 47 one 1
47 3.14 11.11
1 2 3 4
init.A@1db9742 48 two 2
three
four*/
5.9 枚举类型--------
enum关键字
package init;
public enum Week {
MON,TEU,WED,THU,FRI; //创建枚举类型集,因为枚举类型的实例是整型常量,要求都大写
}
package init;
public class EnumUse {
public static void main(String[] args) {
Week w1=Week.MON; //给枚举类型实例化,引用w1指向某个实例
System.out.println(w1);
for(Week w:Week.values()){ //产生Week中常量值
System.out.println(w+",ordinal "+w.ordinal());//表示enum常量的声明顺序
}
switch(w1){ //枚举可应用于switch语句中
case MON:System.out.println("周一");
break;
default:System.out.println("hello");
}
}
}/*output:MON
MON,ordinal 0
TEU,ordinal 1
WED,ordinal 2
THU,ordinal 3
FRI,ordinal 4
周一*/