1.数据存储的位置
在程序中,数据可以存在于下面的位置:
上图展示了运行时数据分区。程序计数器只占用少量的内存。堆内存区域和方法区是所有线程共享的,每个线程都有自己的程序计数器,虚拟机栈,本地方法栈,每个线程在执行的时候,每次进出一个方法,就对应这虚拟机栈或本地方法栈的一个栈帧的入栈和出栈。这里的程序计数器负责指示字节码的地址。
2.通过引用访问对象
public class Light { private int brightness; public Light(int brightness) { this.brightness = brightness; } public void on(){ System.out.println("the light is on" + " " + brightness); } public void off(){ System.out.println("the light is off" + " " + brightness); } public static void main(String[] args){ Light light = new Light(10); light.on(); light.off(); } }
看一段代码。从这段代码里分析一下一个简单的java程序在运行中具体发生了什么。编写完这个类后,将这个类编译为字节码,实际中在集成开发环境,eclipse或idea中运行这个类的时候已经帮我们做好了编译的工作。编译后,jvm的类加载器会将这个字节代码加载到内存中的方法区,现在可以将字节码理解为某些“描述性”的东西,jvm会自动寻找main函数,程序的入口就在这里。
在这个时候在栈内存中有一个栈针开始入栈,栈针存储的局部变量表会记录light这个对象的引用,在堆中开始创建对象,然后调用对象的方法,
由上表可以看出,基本数据类型存储在栈的本地变量表中,对象中的数据存储在堆中的对象内存区域,本例中就是brightness的值。类的方法存储在方法区中,调用一个对象的方法时,通过对象的引用找到对象的实例数据,和要调用的方法,完成调用。
4.默认值
为了保证安全,java会给没有显示初始化的对象数据成员一个默认值,无论它是对象的引用还是基本数据类型。
public class Light { private int brightness; private List<String> list; public void on(){ System.out.println(brightness); } public void off(){ System.out.println(list.toString()); } public static void main(String[] args){ Light light = new Light(); light.on(); light.off(); } }
0 Exception in thread "main" java.lang.NullPointerException at com.zhangjun.study.Light.off(Light.java:17) at com.zhangjun.study.Light.main(Light.java:23) Process finished with exit code 1
上面代码的执行展示了这一点。创建light对象的时候,虚拟机在堆中分配一块区域,同时“清除”这块区域,如果没有显式的构造函数或者初始化,那么对象的数据成员将会被分配给“默认值”,例如基本数据类型int默认值是0,对象类型默认为null,所以当试图调用对象的时候会产生空指针错误。当然其它数据类型也有其基本的默认值。
如果程序中对数据成员进行了初始化,或在构造函数中进行初始化,那创建的对象包含的数据成员的值就是初始化的值。默认的顺序是 分配默认值——>在定义处初始化——>构造函数初始化。 所以构造函数相当于创建一个对象的最后“把关者”,当然也可以在创建对象后再对数据成员赋值,如果你能保证你不会在调用数据成员之前忘掉的话。
5.传值调用还是传引用调用
在java中,调用一个方法传参过程中,基本数据类型是传值,对象是传引用。
public class Light { private int weight; public int getWeight() { return weight; } public Light(int weight) { this.weight = weight; } public void setWeight(int weight) { this.weight = weight; } public void change(int a, Light light){ a++; light.setWeight(1000); } public static void main(String[] args){ Light light = new Light(10); int a = 199; System.out.println("before change a"+ " " + a); System.out.println("before change light.weight"+ " " + light.getWeight()); light.change(a, light); System.out.println("after change a"+ " " + a); System.out.println("after change light.weight"+ " " + light.getWeight()); } }
before change a 199 before change light.weight 10 after change a 199 after change light.weight 1000 Process finished with exit code 0
调用change的时候,相当于把局部变量a的值复制了一份,传给了change方法里的a,而change方法的a是存储在change这个方法对应的栈帧中,与main方法中的a是两个变量,所以改变change方法里的a不会对main方法中的a造成影响。
而对应light对象而言,相当于把这个对象的引用,复制一份到change方法中的栈帧中,实际上,对于这个引用来讲,相当于传“值”,对整个对象来说,相当与传“引用”,通俗一点就是二者指向的是一个对象。(可以参照上图理解)
6.static关键字
static翻译过来是静态的,静态的就是不随着对象变化而变化,如果一个数据成员或方法被标记为static,那么这个成员和方法就是这个类所有对象所共享的。可以在不用创建对象的情况下直接调用静态成员或方法。
public class Light { public static int weight = 1; public Light(int weight) { this.weight = weight; } public static int getWeight() { return weight; } public static void main(String[] args){ System.out.println(weight); Light light = new Light(10); System.out.println(weight); } }
1 10 Process finished with exit code 0
可以看到,第一次调用weight是在没有创建对象的情况下产生的。main函数没什么特殊的,只是这个类中的一个静态方法,作为程序的入口。静态方法的内部不能引用非静态成员或方法,非静态的成员或方法只有在创建对象后可以被调用,这从getWeight中也可以看出。
可以看到,当创建一个light对象后,这个“破坏者”把大家的公共财产“weight”改成了它自己想要的结果,这样做的后果就是后面的所有light对象都要接受这个结果。
总结:static成员是类级别的,非static成员是对象级别的。
原文:http://www.cnblogs.com/mercurys/p/7658770.html