static关键字:
静态的、全局的,一旦被static修饰说明在一定范围内是共享的,谁都可以访问,这时候就要注意并发读写问题。
可以修饰变量、方法、方法块
1、修饰变量
如果是public的,任何类都可以调用,不用初始化类就可以调用。调用方法:类名.static变量。
此时要注意如果多个线程同时操作该数据,可能出现并发问题。
例如:public static List list = new ArrayList();
此时,当有多个线程同时访问该变量时,就会有线程安全问题。此时有两个解决方案:
(1)把线程不安全的ArrayList换成线程安全的CopyOnWriteArrayList;
(2)每次访问时,手动加锁;
所以,使用static修饰变量时,线程安全是我们必须要考虑的。
对于public static int num = 1;变量num自增时,num++,多线程下是不安全的,可以使用AtomicInteger类,保证原子性,线程安全;
AtomicInteger num = new AtomicInteger(1);//初始值为1
自增使用函数:num.incrementAndGet();
volatile特性:
1)保证变量在线程间可见,对volatile变量所有的写操作都能立即反应到其他线程中,换句话说,volatile变量在各个线程中是一致的(得益于java内存模型—“先行发生原则”);
2)禁止指令的重排序优;
使用volatile修饰变量,可以保证可见性,但是不能保证原子性。多个线程操作num++会有线程安全问题,所以对于该变量的操作必须加锁,保证同一时刻只有一个线程在操作该变量。
2、修饰方法
pubic static修饰方法可以被任意类直接调用,类名.方法名。不需要初始化类。此时,没有线程安全问题,因为方法是在栈里执行的,每个线程都有自己独立的栈,相互隔离,互不影响。util包里经常会用到static修饰方法,调用方便。
3、静态块
在类启动之前,初始化一些数据
public staic List<String> list = new ArrayList<String>();
static{
list.add("abc");
}
静态块里的变量必须是静态的,而且需要写在静态块的前面,否则编译报错。
4、初始化时间
public class ParentStaticClass {
public static List<String> list = new ArrayList<String>(){{
System.out.println("父类静态变量初始化。。。");
}};
public ParentStaticClass(){
System.out.println("父类构造函数初始化。。。");
};
static{
System.out.println("父类静态块初始化。。。");
}
public static void test(){
System.out.println("父类静态方法被执行。。。");
}
}
public class StaticClass extends ParentStaticClass {
public static List<String> list = new ArrayList<String>(){{
System.out.println("子类静态变量初始化。。。");
}};
public StaticClass(){
System.out.println("子类构造函数初始化。。。");
}
static {
System.out.println("子类静态块初始化。。。");
}
public static void test(){
System.out.println("子类静态方法执行。。。");
}
public static void main(String[] args){
System.out.println("子类main方法执行。。。");
new StaticClass();
}
}
执行结果:
父类静态变量初始化。。。
父类静态块执行。。。
子类静态变量初始化。。。
子类静态块执行。。。
子类main方法执行。。。
父类构造函数初始化。。。
子类构造函数初始化。。。
结论:
(1)父类的静态变量和静态块比子类的先初始化;
(2)静态变量和静态块比构造函数先初始化;
静态方法在类初始化的时候并没有初始化,当被调用时才初始化。
final关键字
final的意思是不变的,通常有3中应用场景:
(1)修饰类,表示该类不能被继承;
(2)修饰方法,说明方法不能被覆写;
(3)修饰变量,说明变量只能初始化一次,初始化以后不能修改其内存地址。
对于第3中,只是不能修改内存地址,内部的值是可以改变的。
pubic static final List list = new ArrayList();
list可以先声明后初始化,修饰成员变量时,可以在构造函数中初始化,加上static也可以在静态块中初始化,修饰局部变量时,可以在具体的逻辑里初始化。
list.add("abc");
但是不能这样:
List<String> anotherList = new ArrayList<String>();
anotherList.add("abc");
list = anotherList;//这样list就指向了另外的内存地址。会报错。
try…catch…finally
try{
System.out.println("try is run...");
while (true){
throw new RuntimeException("try exception");
}
}catch (Exception e){
System.out.println("catch is run...");
throw new RuntimeException("catch exception");
}finally {
System.out.println("finally is run...");
}
运行结果:
try is run...
catch is run...
finally is run...
Exception in thread "main" java.lang.RuntimeException: catch exception
(1)finally执行后,才抛出catch的异常。
(2)最终捕获的异常是catch的异常,因为try的异常被catch吃掉了。这种情况可以打印下try的异常,这样在日志中就能看到try抛出的异常是什么了。