Java编程思想(Chapter2、4、6)
一切皆对象
用引用操纵对象
Java中操纵的标识符实际上是对象的“引用”。例如想要操纵一个字符串,则可以创建一个String引用。
String s;
此处s只是一个引用。
存储位置
基本类型/对象的引用:堆栈
对象: 堆
作用域
作用域由{}确定,决定了在其中定义的变量名的可见性以及生命周期。
1 { 2 int x =12; 3 { 4 int x = 96; //illegal 5 } 6 }
非法,不能隐藏。
{ String s = new String("str"); }
引用s在作用域终点消失,而s指向的String对象仍然占据着内存空间,直到gc销毁。
关于值传递还是引用传递的问题
Java中只存在值传递
1.基本类型的传递
没有任何疑问,传值
2.对象的传递
参数传递时,只存在传递基本类型和对象引用的问题,并不会直接传对象本身
一个方法不能修改一个基本数据类型的参数
一个方法可以改变一个对象参数的状态
一个方法不能让对象参数引用一个新的对象
参考知乎话题:java到底是值传递还是引用传递?传送门
对于参数是String类型,不修改原的原因在于 String 对象是final类型。
初始化与清理
初始化顺序
以一实例说明
public class Tag { public Tag(int marker) { System.out.println("tag"+marker); } }
public class Card { Tag tag = new Tag(1); public Card() { System.out.println("card()"); tag3 = new Tag(33); } Tag tag2 = new Tag(2); void f(){ System.out.println("f()"); } Tag tag3 = new Tag(3); }
public class Init01 { public static void main(String[] args) { Card card = new Card(); card.f(); } }
Result
tag1
tag2
tag3
card()
tag33
f()
可以看出先进行变量的初始化,再进行构造器调用。
在此基础上,如果还有静态数据时,顺序如何?
public class Bowl { public Bowl(int marker) { System.out.println("bowl "+marker); } protected void f(int marker){ System.out.println("f "+marker); } }
public class Table { static Bowl b1 = new Bowl(1); public Table(){ System.out.println("table()"); b2.f(1); } void f2(int marker){ System.out.println("f2 "+marker); } static Bowl b2 =new Bowl(2); }
public class Cupboard { Bowl b3 =new Bowl(3); static Bowl b4 = new Bowl(4); Cupboard(){ System.out.println("cup()"); b4.f(2); } void f3(int marker){ System.out.println("f3 "+marker); } static Bowl b5 = new Bowl(5); }
public class Test { public static void main(String[] args) { System.out.println("create cup in main"); new Cupboard(); System.out.println("create cup in main"); new Cupboard(); t2.f2(1); t3.f3(1); } static Table t2 = new Table(); static Cupboard t3 = new Cupboard(); }
Result
bowl 1 bowl 2 table() f 1 bowl 4 bowl 5 bowl 3 cup() f 2 create cup in main bowl 3 cup() f 2 create cup in main bowl 3 cup() f 2 f2 1 f3 1
可以看出,先进行static静态数据的初始化(只执行一次),再普通变量的初始化,最后再调用构造器。
数组的初始化
每次访问数组时,都将进行数组的边界检测。
复用类
带参数的构造器
如果类没有缺省的参数,或者想调用一个带参数的基类构造器,则必须用super显示地编写调用基类构造器的语句。
public class C3 extends C2{ public C3(int i) { super(i);// System.out.println("c3"); } public static void main(String[] args) { new C3(1); } } class C1{ public C1(int i) { System.out.println("c1"); } } class C2 extends C1{ public C2(int i) { super(i);// System.out.println("c2"); } }
此处,两个super(i)都是必须,不然会报错。
初始化及类的加载
public class Insect { protected int i = 9; protected int j; protected int x3 = print("static Insect.x3 init"); public Insect() { System.out.println("i="+i+" ,j="+j); j =39; } private static int x1 = print("static Insect.x1 init"); static int print(String s){ System.out.println(s); return 47; } }
public class Beetle extends Insect{ private int k = print("Beetle.k init"); public Beetle() { System.out.println("k="+k); System.out.println("j="+j); } private static int x2 = print("static Beetle.x2 init"); public static void main(String[] args) { System.out.println("Beetle construct"); Beetle beetle = new Beetle(); } }
Result
static Insect.x1 init static Beetle.x2 init Beetle construct static Insect.x3 init i=9 ,j=0 Beetle.k init k=47 j=39
可以看出,先进行类的加载,先基类再子类。static变量/块执行于类的初始化时期。new之后按照 先初始化再构造器的顺序执行。