第五章 初始化与清理

初始化

成员初始化

  • 对于类的数据成员(基本数据类型),Java为不同的基本数据类型,分别设置不同的初始值
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class People {
String name;
int age;
boolean married;
char ch;
float score;
byte b;

void out(){
System.out.println("name "+name);
System.out.println("age "+age);
System.out.println("married "+married);
System.out.println("ch ["+ch+"]");
System.out.println("score "+score);
System.out.println("b "+b);
}
public static void main(String args[]){
People people = new People();
people.out();

}

}

输出结果

1
2
3
4
5
6
name  null
age 0
married false
ch [ ] //ch的默认值为空格
score 0.0
b 0

  • 对于类方法里的变量,Java不做处理,即如果,某个变量未初始化,则无法使用该变量,否则会报错.
1
2
3
4
5
6
7
8
public class People {
//...
void out(){
int i;
i++; //会报错,i没有被初始化(i没有值)
}
//...
}
  • 对于引用,若未被初始化,则默认值为NULL

利用函数的返回值初始化

只有方法和类。可以消除向前引用.当某个函数/方法还没声明时,可提前使用.

1
2
3
4
5
6
7
8
public class People {
//...
int i = f();
int j = g(i);
int f(){return 11;}
int g(int k){return k+1;}
//...
}

最终可知

1
2
i=11
j=12

但是对于变量/类成员,无法进行前向引用,不能使用还未被初始化的变量/类成员.
如下

1
2
3
4
5
6
7
8
9
public class People {
//...
int j = g(i);//g(i)提前使用下个语句定义的i,程序会报错
int i = f();
//int j = g(i);
int f(){return 11;}
int g(int k){return k+1;}
//...
}

类的初始化顺序

静态初始化

Java对初始化采用的是惰性方式,只有用到的时候才会去初始化它,如果一个类中有若干方法,在没有调用它时,Java是不会去执行方法内部的 代码(如果方法内部新new了对象).

静态初始化也是惰性的,只有在必要时才会初始化,若初始化一个类,只有在调用了类的静态方法时,Java才会去初始化该静态方法.对于类属性,无论是否是静态的都会初始化,区别只在于,静态属性只初始化一次.

对于不含有main函数的类而言

初始化顺序为:静态初始化块,构造代码块,构造函数代码块

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class te{
{
System.out.println("普通代码块/构造代码快");
int b =1;
}
static{
System.out.println("静态代码块");
int a = 1;
}
public te() {
// TODO Auto-generated constructor stub
System.out.println("构造函数代码块");
}
}

public class test {
public static void main(String args[]){
new te();
}

}

结果

1
2
3
静态代码块
普通代码块/构造代码快
构造函数代码块


对于含有main函数的类

初始化顺序为:静态初始化块,main()函数块,构造函数块

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34

class te{
{
System.out.println("te-------普通代码块/构造快");
int b =1;
}
static{
System.out.println("te-------静态代码块");
int a = 1;
}
public te() {
// TODO Auto-generated constructor stub
System.out.println("te---------构造代码块");
}
}

public class test {
{
System.out.println("test构造代码块");
}
static{
System.out.println("test静态代码块");
te t = new te();
}
public test() {
// TODO Auto-generated constructor stub
System.out.println("test构造函数");
}
public static void main(String args[]){
System.out.println("main()函数块");
new te();
}

}

结果

1
2
3
4
5
6
7
8
9
10
//静态代码块
test静态代码块
te-------静态代码块
te-------普通代码块/构造块
//普通代码块
te---------构造代码块
//main函数块
main()函数块
test构造代码块
test构造函数

我们来分析一下Java初始化顺序.

  • 由于类test具有入口函数main(),于是先对test类进行初始化.
  • 初始化静态代码快,执行main函数,执行构造函数
  • 其中有一点值得注意:如果test类中,有普通的构造代码块,如下
1
2
3
4
5
6
7
8
9
public class test {
{
//普通构造代码块
System.out.println("test构造代码块");
}
static{
System.out.println("test静态代码块");
te t = new te();
}

在对test类初始化时,当初始化静态代码块初始化完毕后,并不会执行普通代码块内部的代码.
那么何时才会执行普通构造代码块?
只有当main函数new一个test对象时,Java才会执行它.
类分有两种,一种是带有main()执行入口函数的类,一种是没有main函数的类,两中初始化顺序有细微的差别.在main()函数内部new一个新的对象时,该对象的初始化与没有main()函数的类的初始化顺序相同.

数组的初始化

Java不允许即指定数组大小,又指定数组元素的值

1
2
3
int[] a = new int[5];//指定数组大小
int[] b = new int[]{1,2,3,4,5};//指定数组元素的值
int[] b = new int[6]{1,2,3,4,5,6};//程序会报错,不能同时指定数组大小和数组元素的值

静态初始化

第一种:

1
int[] a = {1,2,3,4,5}; //只能在创建数组的同时,进行初始化

第二种:

1
int[] a = new int[]{1,2,3,4,5};//静态初始化

动态初始化

第三种:

1
2
3
4
int[] a = new int[6];
a[0] = 1;
a[2] = new Integer(1);
//a[2] = new int(1)是错误的,对于单个的基本数据类型,不能用new

细节过程

1
2
3
4
int[] a;//创建一个数组引用
a = new int[10];//Java在栈中为其分配足够的内存空间,a是一个引用数组,a[0]~a[9],都是引用
a[0] = new Integer(1);//Java在堆中为对象(Integer(1)分配内存空间,引用a[0]指向该对象)
int[] b=a;//定义一个数组引用指向a,于是b与a指向的是同一个对象,当对这个对象进行修改时,a,b的值同时会改变.

可变参数列表

在Java函数中,形参如果是可变参数,则,这些可变参数会以数组的形式传入

1
2
3
4
5
void f(Object ...args){
for(Object k:args){
System.out.println(k);
}
}

如果调用f(1,2,3,4)结果为:

1
2
3
4
1
2
3
4

可变参数机制,会将形参聚合成为一个数组,此处会将1,2,3,4转型成为一个Object类型的数组.
如果传入的是一个数组,则java不会对其进行任何的转换.对于传入的数组类型不同,结果可能也会不同.

对于java库中类

比如基本数据类型的包装器(是一个类):Integer,Float,Double,Boolean,Character等利用foreach可以输出数组内容,
例如

1
2
3
4
5
6
7
8
9
10
11
12
public class Animal {

static void print(Object ...objects){
for(Object tt:objects){
System.out.println(tt);
}
}
public static void main(String args[]){
Integer[] integer = new Integer[]{new Integer(1),new Integer(2),new Integer(3)};
Animal.print(integer);
}
}

结果

1
2
3
1
2
3

对于非Java库中的类

比如基本数据类型(非包装器),自己构造的类,则会输出[I@123456类型的数据,[代表是数组,123456(16进制数)代表对象的地址.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package chapter2;
class People{
String name="you";
public People(String name) {
// TODO Auto-generated constructor stub
this.name = name;
}
}
public class Animal {

static void print(Object ...objects){
for(Object tt:objects){
System.out.println(tt);
}
}
public static void main(String args[]){
People[] people = new People[]{new People("you"),new People("java")};
int[] a = new int[]{1,2,3,4};
System.out.println("People---");
Animal.print(people);
System.out.println("基本数据类型---");
Animal.print(a);
}
}

结果

1
2
3
4
5
People---
chapter2.People@2a139a55
chapter2.People@15db9742
基本数据类型---
[I@6d06d69c

如果要输出一个类时,java默认是输出类名称@类地址形式,如果要输出特定内容,需要重载toString()方法.每个类,在创建时都有toString()方法,由于Java库中的类,toString()已经包装过,所以可以直接输出类的信息.
以下是经过修改的代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
package chapter2;

class People{
String name="you";
public People(String name) {
// TODO Auto-generated constructor stub
this.name = name;
}
public String toString(){
return "people`s String:"+name;
}
}
public class Animal {

static void print(Object ...objects){
for(Object tt:objects){
System.out.println(tt);
}
}
public static void main(String args[]){
People[] people = new People[]{new People("you"),new People("java")};
int[] a = new int[]{1,2,3,4};
Integer[] bIntegers = new Integer[]{new Integer(2),new Integer(22),new Integer(22)};
System.out.println("People---");
Animal.print(people);
System.out.println("基本数据类型---");
Animal.print(a);
System.out.println("Java内部类Integer-----");
Animal.print(bIntegers);
}
}

输出结果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
People---
people`s String:you
people`s String:java
基本数据类型---
[I@2a139a55
Java内部类Integer-----
2
22
22
``
## enum用法
`enum`用法类似于`class`的用法,
声明
```java
public class Animal {

public enum Month{
January ,
February ,
March ,
April ,
May ,
June ,
July ,
August ,
September ,
October ,
November ,
December ,
}
public static void main(String args[]){
//输出Month的所有元素
for(Month a:Month.values()){
System.out.println(a);
}
Month ts = Month.August;
System.out.println("ts的属性:"+ts);
}
}

结果

1
2
3
4
5
6
7
8
9
10
11
12
13
January 该常量的声明顺序为:0
February 该常量的声明顺序为:1
March 该常量的声明顺序为:2
April 该常量的声明顺序为:3
May 该常量的声明顺序为:4
June 该常量的声明顺序为:5
July 该常量的声明顺序为:6
August 该常量的声明顺序为:7
September 该常量的声明顺序为:8
October 该常量的声明顺序为:9
November 该常量的声明顺序为:10
December 该常量的声明顺序为:11
ts的属性:August

在建立enum对象时,编译器会自动添加一些方法和属性,例如,下图是Month的方法和属性
Month的方法
enum的另一个应用是用于switch中,

1
2
switch( A ){
}

A中只可以是int,char,和enum类型.

再议内存管理

其实,对这方面细节还不是很懂,先将自己理解的东西记下来.
又称堆栈(stack)和(heap),他们最主要的区别就是:栈中的元素需要知道元素的生命周期,故栈主要是存储那些知道何时创建,何时结束的数据,比如引用,方法中的形参,变量.而在堆中,无需关注这些,每次new一个对象,就会在堆中开辟一个内存空间,堆中的对象何时被销毁也是无法确定的. java的垃圾回收器是惰性的,只有在万不得已的情况下,才会去回收垃圾,因此,当某个对象的引用已经不存在时,并不代表这个对象将会被销毁.

本文标题:第五章 初始化与清理

文章作者:定。

发布时间:2017年5月5日 - 15时05分

本文字数:8,432字

原始链接:http://cocofe.cn/2017/05/05/第五章 初始化与清理/

许可协议: Attribution-NonCommercial 4.0

转载请保留以上信息。