03. Java基础之初始化
一. 用构造器确保初始化
构造器是一种特殊类型的方法,因为它没有返回值。这与返回值为空(void)明显不同。对于空返回值,尽管方法本身不会自动返回什么,但仍然可以选择让它返回别的东西。构造器则不会返回任何东西,你别无选择(new表达式确实返回了对新建对象的引用,但构造器本身并没有任何返回值)。假如构造器具有返回值,并且允许人们自行选择返回类型,那么势必得让编译器知道该如何处理此返回值。
1. 一个类中,可以变量、方法名和构造器都同名
1 public class Test { 2 public String Test = "hello"; 3 4 public Test() { 5 System.out.println("no param constructor"); 6 } 7 8 public Test(String name) { 9 System.out.println("param constructor"); 10 } 11 12 public void Test() { 13 System.out.println("Test.Test()"); 14 } 15 16 public static void main(String args[]) { 17 Test test = new Test(); 18 Test test2 = new Test("name"); 19 System.out.println(test.Test); 20 System.out.println(test2.Test); 21 test.Test(); 22 test2.Test(); 23 } 24 }
1 no param constructor 2 param constructor 3 hello 4 hello 5 Test.Test() 6 Test.Test()
2. 方法重载
每个重载的方法都必须有一个独一无二的参数类型列表。
如果传入的数据类型(实际参数类型)小于方法中声明的形式参数类型,实际数据类型就会提升。char类型不同,如果无法找到正好接受char参数的方法,就会直接提升为int型。
大小参数传入举例:
1 public class Test { 2 public void f(int a) { 3 System.out.println(a); 4 } 5 public static void main(String args[]) { 6 Test test=new Test(); 7 test.f('a');//print 97 8 test.f(2.0);//compile error, double can't treat as int 9 } 10 }
note:如果传入的实际参数较大,就得通过类型转换来执行窄化转换。如果不这样做,编译器就会报错。
note:不能用返回值区分重载方法,因为void f()和int f()在调用的时候,我们会认为模糊,不知道调用了哪一个,那么机器就更加的糊涂了。java不知道调用哪一个f().
3. 默认构造器
如果已经定义了一个构造器(无论是否有参数),编译器就不会帮你自动创建默认构造器。
1 public class Test { 2 public Test(int a) 3 { 4 5 } 6 public static void main(String args[]) { 7 Test test=new Test(1); 8 Test test2=new Test();//compile error 9 } 10 }
4. this关键字
(1)方法调用
1 public class Banana { 2 void peel(int i) { 3 4 } 5 6 } 7 8 9 public class BananaPeel { 10 public static void main(String args[]) { 11 Banana a=new Banana(), 12 b=new Banana(); 13 a.peel(1); 14 b.peel(2); 15 } 16 17 }
上面的peel是如何知道是被a还是b调用呢?原来是编译器做了一些幕后工作。它暗自把“所操作对象的引用”作为第一个参数传递给了peel()。所以上述两个方法的调用变成了这样:
Bannana.peel(a,1);
Bannana.peel(b,1);
这是内部的表示形式,我们并不可以这么写。由于这个引用是编译器偷偷传入的,所以没有标识符可以用。但是,为此专门有了this. this关键字只能在方法内部使用,表示“调用方法的哪个对象”的引用。如果在方法内部调用同一个类的另一个方法,就不必加this,直接调用就可以。
(2)构造器调用
1 public class Test { 2 public Test() { 3 System.out.println("no parm"); 4 } 5 public Test(int i) 6 { 7 this();//直接调用无参构造器 8 System.out.println("parm"); 9 } 10 public static void main(String args[]) { 11 Test test=new Test(1); 12 13 } 14 } 15 16 no parm 17 parm 18 19 note:调用有参构造器时,采用this(2);即可
(3)vs static
static方法就是没有this的方法。在static方法的内部不能调用非static方法,反过来可以。使用static方法时,由于不存在this,所以不是通过“向对象发送消息”的方式来完成的。
5. 成员初始化
对于方法的局部变量,Java以编译错误的形式来保证它得到初始化。成员变量,即使没有显示初始化,也会默认给个指定值。
1 public class Test { 2 public String aString; 3 public void f() 4 { 5 int a; 6 int c;//只是定义,但没使用,因此不会报错 7 int b; 8 b=4; 9 System.out.println(b);//4 10 System.out.println(a);//compile error 11 } 12 public static void main(String args[]) { 13 Test test=new Test(); 14 System.out.println(test.aString);//null 15 16 } 17 }
6. 构造器初始化
无法阻止自动化初始化的执行,它将在构造器被调用之前发生。比如:
public class Counter{
int i;
Counter() {i=7;}
}
那么i首先会被置0,然后变成7。对于所有基本类型和对象引用,包括在定义时已经指定初值的变量,这种情况都是成立的;因此,编译器不会强制你一定要在构造器的某个地方或在使用它们之前对元素进行初始化-----因为初始化早已经得到了保证。
(1)初始化顺序
在类的内部,变量定义的先后顺序决定了初始化顺序。即使变量定义散布于方法定义之间,它们仍旧会在任何方法(包括构造器)被调用之前得到初始化。比如:
1 public class Window { 2 public Window(int marker) { 3 System.out.println("Window("+marker+")"); 4 } 5 }
1 public class House { 2 Window w1=new Window(1); 3 public House() { 4 System.out.println("House()"); 5 w3=new Window(33); 6 } 7 Window w2=new Window(2); 8 void f(){ 9 System.out.println("f()"); 10 } 11 Window w3=new Window(3); 12 }
1 public class Test { 2 public static void main(String args[]) { 3 House house=new House(); 4 house.f(); 5 } 6 }
1 Window(1) 2 Window(2) 3 Window(3) 4 House() 5 Window(33) 6 f()
(2)静态数据的初始化
无论创建多少个对象,静态数据都只占用一份存储区域。static关键字不能应用于局部变量,因此它只能作用于域。
1 public class Bowl { 2 public Bowl(int marker) { 3 System.out.println("Bowl("+marker+")"); 4 } 5 void f1(int marker) { 6 System.out.println("f1("+marker+")"); 7 } 8 }
1 public class Table { 2 static Bowl bowl1=new Bowl(1); 3 public Table() { 4 System.out.println("Table()"); 5 bowl1.f1(1); 6 } 7 void f2(int marker) { 8 System.out.println("f2("+marker+")"); 9 } 10 static Bowl bowl2=new Bowl(2); 11 }
1 public class Cupboard { 2 Bowl bowl3=new Bowl(3); 3 static Bowl bowl4=new Bowl(4); 4 public Cupboard() { 5 System.out.println("Cupboard()"); 6 bowl4.f1(2); 7 } 8 void f3(int marker) { 9 System.out.println("f3("+marker+")"); 10 } 11 static Bowl bowl5=new Bowl(5); 12 }
1 public class Test { 2 public static void main(String args[]) { 3 System.out.println("Creating new Cupboard() in main"); 4 new Cupboard(); 5 System.out.println("Creating new Cupboard() in main"); 6 new Cupboard(); 7 table.f2(1); 8 cupboard.f3(1); 9 } 10 static Table table=new Table(); 11 static Cupboard cupboard=new Cupboard(); 12 }
1 Bowl(1) 2 Bowl(2) 3 Table() 4 f1(1) 5 Bowl(4) 6 Bowl(5) 7 Bowl(3) 8 Cupboard() 9 f1(2) 10 Creating new Cupboard() in main 11 Bowl(3) 12 Cupboard() 13 f1(2) 14 Creating new Cupboard() in main 15 Bowl(3) 16 Cupboard() 17 f1(2) 18 f2(1) 19 f3(1)
(3)静态代码块
和其它静态初始化动作一样,静态代码块只执行一次:当首次生成这个类的一个对象时,或者首次访问属于那个类的静态数据成员时(即便从未生成过那个类的对象)。比如:
1 public class Cup { 2 public Cup(int marker) { 3 System.out.println("Cup("+marker+")"); 4 } 5 void f(int marker) { 6 System.out.println("f("+marker+")"); 7 } 8 9 } 10 11 public class Cups { 12 static Cup cup1; 13 static Cup cup2; 14 static { 15 cup1=new Cup(1); 16 cup2=new Cup(2); 17 } 18 public Cups() { 19 System.out.println("Cups()"); 20 } 21 22 } 23 24 25 public class Test { 26 public static void main(String args[]) { 27 System.out.println("Inside main"); 28 Cups.cup1.f(99); 29 } 30 static Cups cups1=new Cups(); 31 static Cups cups2=new Cups(); 32 }
1 Cup(1) 2 Cup(2) 3 Cups() 4 Cups() 5 Inside main 6 f(99)
(4)普通代码块 和 静态代码块
相同点:都是在JVM加载类时且在构造方法执行之前执行,在类中都可以定义多个,
一般在代码块中对一些static变量进行赋值。
不同点:静态代码块在非静态代码块之前执行(静态代码块—>非静态代码块—>构造方法)。
静态代码块只在第一次new执行一次,之后不再执行,而非静态代码块在每new
一次就执行一次。非静态代码块可在普通方法中定义(不过作用不大);而静态代码块不行。
1 package b; 2 3 public class Cups { 4 public Cups() { 5 System.out.print("默认构造方法!-->"); 6 } 7 8 // 非静态代码块 9 { 10 System.out.print("非静态代码块!-->"); 11 } 12 13 // 静态代码块 14 static { 15 System.out.print("静态代码块!-->"); 16 } 17 18 public static void test() { 19 { 20 System.out.println("普通方法中的代码块!"); 21 } 22 } 23 }
1 public class Test { 2 /** 3 * 区别两次new静态与非静态代码块执行情况 4 */ 5 public static void main(String[] args) { 6 Cups c1 = new Cups(); 7 c1.test(); 8 9 Cups c2 = new Cups(); 10 c2.test(); 11 } 12 }
1 静态代码块!-->非静态代码块!-->默认构造方法!-->普通方法中的代码块! 2 非静态代码块!-->默认构造方法!-->普通方法中的代码块!
7.继承与初始化
1 public class Insect { 2 private int i=9; 3 protected int j; 4 public Insect() { 5 System.out.println("i="+i+",j="+j); 6 j=39; 7 } 8 private static int x1=printInit("static Insect.x1 initialized"); 9 static int printInit(String s) { 10 System.out.println(s); 11 return 47; 12 } 13 }
1 public class Beetle extends Insect{ 2 private int k=printInit("Beetle.k initialized"); 3 public Beetle() { 4 System.out.println("k="+k); 5 System.out.println("j="+j); 6 } 7 private static int x2=printInit("static Beetle.x2 intialized"); 8 public static void main(String args[]) { 9 System.out.println("Beetle Constructor"); 10 Beetle beetle=new Beetle(); 11 } 12 13 }
1 static Insect.x1 initialized 2 static Beetle.x2 intialized 3 Beetle Constructor 4 i=9,j=0 5 Beetle.k initialized 6 k=47 7 j=39
二. 在构造器中调用方法----易混淆
在构造器没有完成初始化,而这个构造器里面又调用了方法,这会造成一些不好的设计现象。应该尽量避免此类发生。
先看以下实例,从而更好的理解:
1 package com.test.c; 2 3 public abstract class A { 4 public A() 5 { 6 print(); 7 } 8 public abstract void print(); 9 10 }
1 package com.test.c; 2 3 public class B extends A{ 4 public int a=3; 5 6 public void print() { 7 System.out.println(a); 8 9 } 10 public static void main(String args[]) 11 { 12 B b=new B(); 13 b.print(); 14 } 15 16 17 }
输出:
0
3
分析:
在类的初始化时,jvm会先给对象分配一块空间,所有的成员都被暂时赋值为0,对象引用赋值为null,这时int值还是0,然后从基类开始调用构造方法,在基类的构造方法中,调用了print()方法,注意此时调用的print()是导出类的方法。
看似是调用了基类的print()的方法,其实是调用了自身的方法,调用某类的方法是需要该类的实例,但导出类中根本没有基类的实例成员。
在完成基类构造方法的调用之前,导出类的其他成员都不会初始化,此时int值是0,所以最终先输出:0
完成基类构造方法调用后,然后进行自身的成员变量的初始化,这时int值被赋为3。然后调用自身的构造方法,在构造方法里调用了重写的抽象方法print(),所以此时再输出:3
三. 重要知识点
1. 为何基类构造器总会被先调用?
构造器是为了初始化才会有的。
什么是初始化?就是说一些准备工作,我有那些属性、方法,
子类继承父类就是要使用父类里的东西(属性、方法),但是怎么才能知道父类里有什么呢?
那就要先把父类初始化一下,然后父类就具有了相应的属性和方法。
父类初始化完成了,表示具有一些东西了,这时候,子类才会初始化自己。
2. static final变量和static变量的区别?
前者不会引起类加载,后者会引起类加载
1 public class Person { 2 public int a=1; 3 public static int b=2; 4 public final static int c=3; 5 static { 6 System.out.println("hello"); 7 } 8 9 } 10 11 12 public class Test2 { 13 public static void main(String args[]) { 14 // System.out.println(Person.b);//print hello, 2,static导致了类加载 15 System.out.println(Person.c);//print 3,final staitc不会导致类加载 16 17 } 18 19 }
3. 哪些操作会造成类加载初始化?
4. 总结所有的初始化顺序:
静态变量-->静态代码块-->普通代码块-->构造器
1 public class Base { 2 public Base() { 3 System.out.println("this is constructor"); 4 } 5 public int a=1; 6 public static int b=2; 7 public final static int c=3; 8 { 9 System.out.println("ha"+b); 10 System.out.println("ha"+c); 11 12 } 13 static { 14 System.out.println("hello"+b);//静态代码块里面不能调用非静态变量或方法 15 System.out.println("hello"+c); 16 } 17 18 19 }
1 public class Test { 2 public static void main(String args[]) { 3 Base base=new Base(); 4 Base base2=new Base(); 5 } 6 }
1 //静态变量-->静态代码块-->普通代码块-->构造器 2 //hello2 3 //hello3 4 //ha2 5 //ha3 6 //this is constructor 7 //ha2 8 //ha3 9 //this is constructor
参考文献:
thinking in java