Thinking in Java 初始化、数组、枚举、可变参数列表

本文详细介绍了Java中对象的创建过程,包括静态数据初始化、对象的构造过程。接着讨论了数组的特性,如初始化、长度及赋值操作。还探讨了枚举类型,包括其默认方法和特性。最后提到了可变参数列表的使用和便利性。
摘要由CSDN通过智能技术生成

静态数据初始化

无论创建了多少个对象,静态数据都只占用一份存储区域。static关键字不能应用于局部变量,因此他只作用于域。如果一个域时基本的静态类型域,且也没有对他进行初始化,那么它就会获得基本类型的标准初值:如果它是一个对象引用,那么它的默认初始化值就是null。
想要了解静态存储区域是何时初始化的,看下面这个例子:

class Bowl{
    Bowl(int marker){
        System.out.println("Bowl( "+marker+" )");
    }
    void f1(int marker){
        System.out.println("f1( "+marker+" )");
    }
}
class Table{
    static Bowl bowl1=new Bowl(1);
    Table(){
        System.out.println("table()");
        bowl2.f1(1);
    }
    void f2(int marker){
        System.out.println("f2( "+marker+" )");
    }
    static Bowl bowl2=new Bowl(2);
}
class Cupboard{
    Bowl bowl3=new Bowl(3);
    static Bowl bowl4=new Bowl(4);
    Cupboard(){
        System.out.println("cupboard");
        bowl4.f1(2);
    }
    void f3(int marker){
        System.out.println("f3( "+marker+" )");
    }
    static Bowl bowl5=new Bowl(5);
}
/**
 * Staticinitialization
 */
public class Staticinitialization {

    public static void main(String[] args) {
        System.out.println("creating new cupboard() in main");
        new Cupboard();
        System.out.println("creating new cupboard() in main");
        new Cupboard();
        table.f2(1);
        cupboard.f3(1);
    }
    static Table table=new Table();
    static Cupboard cupboard=new Cupboard();
}

结果

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 )

Bowl类使得看到类的创建,而Table类和Cupboard类再它们的类定义中加入了Bowl类型的静态数据成员。注意,在静态数据成员定义之前,Cupboard类先定义了一个Bowl类型的非静态数据成员b3
由输出可见,静态初始化只要在必要时刻才会进行。如果不创建Table对象,也不引用Table.b1或Table.b2,那么静态的Bowl b1和b2永远不会被创建。只有在第一个Table对象被创建的时候,它们才会被初始化,此后,静态对象不会再次被初始化。

初始化的顺序是先静态对象,而后是非静态对象。

总结一下对象的创建过程,假设有个名为Dog的类

  1. 即使没有显式的使用static关键词,构造器实际上也是静态方法。因此,当首次创建类型为Dog的对象时,或者Dog类的静态方法被访问时,Java解释器必须查找类路径,定位Dog.class文件
  2. 然后载入Dog.class,有关静态初始化的所有动作都会执行。因此,静态初始化只在Class对象首次加载的时候进行一次。
  3. 当new Dog()创建对象的时候,首先将在Dog对象分配足够的存储空间。
  4. 这块存储空间会被清零,这就自动地将Dog对象中的所有基本类型数据都设置成了默认值,而引用则被设置成了null
  5. 执行所有出现字段定义处的初始化动作
  6. 执行构造器。

数组

编译器不允许指定数组的大小。这就又把我们带回有关“引用”的问题上。现在拥有的只是对数组的一个引用,而且也没给数组对象分配任何空间。为了给数组创建相应的存储空间,必须写初始化表达式。对于数组,初始化动作可以出现在代码的任何地方。但也可以使用一种特殊的初始化表达式,它必须在创建数组的地方出现。这种特殊的初始化是由一对花括号括起来的值组成的。在这种情况写,存储空间的分配(相当于new)将由编译器负责。例如

int [] a1{1,2,3,4,5,6};

那么,为什么还要在没有数组的时候定义一个数组引用呢?

int[] a2;

在Java总,可以将一个数组赋值给另一个数组,所以可以这样

a2=a1

其实真正做的只是复制了一个引用,就像下面

public class Arr {

    public static void main(String[] args) {
        int []a1={1,2,3,4,5};
        int []a2;
        a2=a1;
        for(int i=0;i<a2.length;i++){
            a2[i]+=1;
        }
        for(int i=0;i<a1.length;i++){
            System.out.println("a["+i+"] = "+a1[i]);
        }
    }
}

输出结果

a[0] = 2
a[1] = 3
a[2] = 4
a[3] = 5
a[4] = 6

可以看到代码中给出a1的初始值,但a2却没有;在本例子中,a2是在后面被赋给另一个数组的。由于a2和a1都是相同数组的别名,因此通过a2所做的修改在a1中可以看到。
所以数组都有一个固定成员,可以通过它获知数组内包含了多少个元素,但不能对其修改,这个成员就是length。

如果在编写程序时,并不能确定数组里需要多少个元素,该怎么办,可以直接用new在数组里面创建元素。尽管创建的是基本类型数组,new仍然可以工作。

import java.util.Arrays;
import java.util.Random;

/**
 * Arr
 */
public class Arr {
    public static void main(String[] args) {
        int[] a;
        Random rand=new Random();
        a=new int[rand.nextInt(20)];
        System.out.println("length of a = "+a.length);
        System.out.println(Arrays.toString(a));
    }
}

数组的大小通过Random.nextInt()方法随机决定的,这个方法会返回0到输入参数之间的一个值。这表面数组的创建确实是在运行时刻进行的。此外,程序输出表面:数组元素中的基本数据类型值会自动初始化为空值(对于数字和字符,就是0;对于布尔型,是false)
Arrays.toString()方法属于Java.util标准类库,它将产生一维数组的可打印版本。
数组下标范围越界,编译器不会报错,但会抛出异常
本例中,数组也可以在定义的同时进行初始化:

int[] a=new int[rand.nextInt(20)];

如果你创建一个非基本类型的数组,那么你就创建了一个引用数组。以整型的包装器类Integer为例,它是一个类而不是基本类型

import java.util.Arrays;
import java.util.Random;
public class Arr {

    public static void main(String[] args) {
        Random rand =new Random(47);
        Integer[] a=new Integer[rand.nextInt(20)];
        System.out.println(a.length);
        for(int i=0;i<a.length;i++){
            a[i]=rand.nextInt(100);
        }
        System.out.println(Arrays.toString(a));
    }
}

这里,即便使用new创建数组之后

Integer[] a=new Integer[rand.nextInt(20)];

它还只是一个引用数组,并且直到通过创建新的Integer对象,并把对象赋值给引用,初始化进程才算结束:

a[i]=rand.nextInt(100);

可以创建一个String对象数组,将其传递给另一个main()方法,以提供参数,用来替换传递给该main()方法的命令行参数。

public class Arr {
    public static void main(String[] args) {
        Other.main(new String[] {"fiddle","de","dum"});
    }
}
class Other{
    public static void main(String[] args) {
        for(String s:args){
            System.out.println(s+" ");
        }
    }
}
fiddle
de
dum

可变参数列表
上面那种形式提供了一种方便的语法来创建对象并调用方法。由于所有的类都是直接或间接继承于object类,所以可以创建Object数组为参数的方法。

class A{}
public class VarArgs {
    static void printArray(Object[] args){
        for(Object obj : args){
            System.out.println(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()});
    }
}

结果

47
3.14
11.11

one
two
three

A@15db9742
A@6d06d69c
A@7852e922

也可以这样写(新版本)

public class VarArgs {

    static void printArray(Object... args){
        for(Object obj:args){
            System.out.println(obj+" ");
        }
        System.out.println("");
    }
    public static void main(String[] args) {
        printArray(47,3.14f,11.11);
        printArray("one","two","three");
    }
}

有了可变参数,就再也不用显式地编写数组语法了,当你指定参数时,编译器实际上会为你取填充数组。你获取的仍旧是一个数组。因此,如果你由一组事物,可以把它们当作列表传递,而且如果你已经有了一个数组,该方法可以把它们当作可变参数列表来接受。

enum

Java程序员再需要使用枚举类型时,必须了解很多细节并需要格外仔细,以正确地产生enum的效果。

public enum Spiciness{
    NOT,
    MILD,
    MEDIUM,
    HOT,
    FLAMING
}

为了使用enum,需要创建一个该类型的引用,并将其赋值给某个实例

public enum Spiciness{
    NOT,
    MILD,
    MEDIUM,
    HOT,
    FLAMING
}

public class EnumOrder {

    public static void main(String[] args) {
        Spiciness howHot=Spiciness.MEDIUM;
        System.out.println(howHot);
    }
}

在你创建enum时,编译器会自动添加一些有用的特性。例如,他会创建toString()方法,以便你可以很方便地显示某个enum实例的名字,编译器还会创建ordinal()方法,用来表示某个特定enum常量的声明顺序,已经static values()方法,用来按照enum常量的声明顺序。

enum Spicy{
    NOT,
    MILD,
    MEDIUM,
    HOT
}

public class enum2 {
    public static void main(String[] args) {
        for(Spicy s:Spicy.values()){
            System.out.println(s+" ordinal "+s.ordinal());
        }
    }
}
NOT ordinal 0
MILD ordinal 1
MEDIUM ordinal 2
HOT ordinal 3
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值