成员变量和方法变量
成员变量定义在方法外即全局变量(包括静态变量和实例变量),方法变量定义在方法内,也叫局部变量。
静态变量(类变量)
静态变量是static修饰的类成员变量,也叫类变量,可以不设置初始值,静态变量的值存储在方法区的常量池中。
可以直接通过类名调用,也可以通过实例调用,但一般不推荐此种方式。
在类加载的准备阶段进行分配空间以及初始化为该类型的默认值,在类加载的初始化阶段赋值为初始值。如下:String类型的variable2没有设置初始值,在类加载的准备阶段初始化为null,在初始化阶段因为没有设置初始值,就没有赋值过程;int类型的variable0设置了初始值为100,在准备阶段初始化为0,在初始化阶段赋为100。
public class TestClass {
public static String variable2;
public static int variable0 = 100;
public static void main(String[] args) {
//静态变量没有设置初始值时,类加载的准备阶段会将String类型初始化为null
System.out.println(TestClass.variable2);//null
//虽然通过实例也可以调用静态,但一般不推荐此种方式
System.out.println(new TestClass().variable2);//null
//静态变量设置了初始值时,类加载的准备阶段会将int类型初始化为0,然后在类加载的初始化阶段赋为初始值100
System.out.println(TestClass.variable0);//100
}
}
静态变量与实例无关,被所有对象共享,可以一处修改多处使用,如下:
public class TestClass {
public static int variable0 = 100;
public static void main(String[] args) {
TestClass test1 = new TestClass();
test1.variable0 = 200;
System.out.println(test1.variable0);//200
TestClass test2 = new TestClass();
System.out.println(test2.variable0);//200
}
}
在多线程情况下,被所有线程共享,如下:
public static void main(String[] args) {
new Thread(()->{
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"---"+TestClass.variable0);
},"thread1").start();
new Thread(()->{
TestClass.variable0 = 200;
System.out.println(Thread.currentThread().getName()+"---"+TestClass.variable0);
},"thread2").start();
}
常量
常量是final修饰的类静态成员量,必须要设置初始值并且该值不可修改,常量除了值不可修改之外其他属性同静态变量类似。常量的值也是存储在方法区的常量池中。可以直接通过类名调用,也可以通过实例调用,但一般不推荐此种方式。
在类加载的准备阶段进行分配空间以及直接初始化为初始值,在初始化阶段没有赋值过程。被所有线程所有对象共享。
实例变量
实例变量即为实例对象的变量(属性),是类的非静态成员变量,可以不设置初始值,只能通过实例调用。
实例变量作为实例对象的属性存储在堆内存中,生命周期和实例对象一样,随着实例一起创建、使用和销毁。在类实例化的时候初始化为默认值,随后设置为初始值(有待验证)。
实例变量和实例直接挂钩,自然不存在跨对象共享问题。
在多线程环境下操作同一实例对象的实例变量会存在安全性问题。如下:
public class TestClass {
public int variable1 = 20;
public static void main(String[] args) {
TestClass test1 = new TestClass();
new Thread(()->{
System.out.println(Thread.currentThread().getName()+"---"+test1.variable1);
for (int i = 0; i < 1000000; i++) {
test1.setVariable1(test1.getVariable1()+1);
}
System.out.println(Thread.currentThread().getName()+"---"+test1.variable1);
},"thread1").start();
new Thread(()->{
System.out.println(Thread.currentThread().getName()+"---"+test1.variable1);
for (int i = 0; i < 1000000; i++) {
test1.setVariable1(test1.getVariable1()+1);
}
System.out.println(Thread.currentThread().getName()+"---"+test1.variable1);
},"thread2").start();
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(test1.getVariable1());
}
}
上述代码的想得到的数据为2000020,实际打印的数据如下:
解决:在循环读取和赋值时给实例加锁,代码如下:
for (int i = 0; i < 1000000; i++) {
synchronized (test1) {
test1.setVariable1(test1.getVariable1() + 1);
}
}
方法变量(局部变量)
方法变量即为方法内部定义的变量或者是方法被调用时传递的参数,存储在虚拟机栈的栈帧中的局部变量表里(具体参看JVM系列之栈帧(Stack Frame)结构)。
虚拟机没有给局部变量初始化为默认值的过程(因为局部变量一般比较多,生命周期短,虚拟机做变量初始化开销会很大),所以一般在定义时就赋值为初始值,如果没有设置初始值,那么在被使用前一定要进行赋值,否则会抛异常,如下:
public String method(boolean flag){
String methodVariable;
if(flag){
methodVariable = "test1";
}else{
methodVariable = "test2";
}
return methodVariable;
}