Java中的关键字很多,本文选取常用的几个进行学习总结;
static
一、基本概念
静态,全局的,一旦被修饰,说明被修饰的东西在一定范围内是共享的,这时候要注意并发读写问题;
可以用来修饰类变量,方法,代码块;
1、static修饰类变量----类属性(静态属性)
static修饰的类变量无需初始化,通过 类名.变量 直接访问;这时候要注意线程安全问题;
当多个线程对共享变量进行读写时,会出现并发问题;譬如定义了 public static List<String> list = new ArrayList();这时候有两个解决办法,一个是把线程不安全的ArrayList换成线程安全的CopyOnWriteArrayList;另一种就是每次访问时手动加锁;
2、static修饰方法----类方法(静态方法)
通过 类名.方法名 直接访问;注意被static修饰的方法内部只能调用static修饰的方法,不能调用普通方法;
我们常用的Utils类中的方法习惯用static修饰,一方面是简单方便,另一方面是因为被static修饰的方法不用担心线程安全问题;
3、static修饰代码块
又称静态代码块;常用于在类启动前,初始化一些值;
需要注意的是,静态代码块只能调用被static修饰的变量,并且static变量需要写在静态代码块的前面,不然会报错;
二、加载时机
打印出来的结果如下:
父类的静态变量初始化
父类静态块初始化
子类静态变量初始化
子类静态块初始化
父类构造器初始化
子类构造器初始化
结论:
父类的静态变量和静态块比子类的静态变量和静态块优先加载;
静态块和静态变量比类构造器优先初始化;
静态方法在类初始化的时候不会初始化,只有在调用的时候才会初始化;
final
一般情况下,final用于以下场景:
- final修饰的类不能被继承;
- final修饰的方法不能被重写;
- final修饰的变量在声明的时候就必须初始化,而且不能再修改其内存地址;
try catch finally
这些关键字用于异常捕获流程中,其中:
try用来确定代码执行的范围,catch用来捕获可能发生的异常,finally用来执行无论如何一定要执行的代码;
特别需要注意的时,当try和catch都遇到异常时,代码的执行顺序:try-->catch-->finally:
public void testCatchFinally() {
try {
log.info("try is run");
if (true) {
throw new RuntimeException("try exception");
}
} catch (Exception e) {
log.info("catch is run");
if (true) {
throw new RuntimeException("catch exception");
}
} finally {
log.info("finally is run");
}
}
输出结果如下图:
可以看到,
finally执行完成后,才抛出catch异常;最终捕获的是catch的异常,try抛出来的异常会被catch吃掉, 所以当catch也有可能抛出异常时,先打印try的异常,从而try的异常可以再日志中有所体现;
volatile
原本意思是可见的,
当一个变量定义为volatile之后,它将具备两种特性——保持内存可见性和防止指令重排。
内存可见性
在Java中,用来修饰共享变量,当共享变量的值发生变化时,会及时通知其他线程,从而知道当前的共享变量值已经被修改;
这里要注意,随着技术的迭代,现在的计算机多为多核cpu,多核cpu情况下,为了提高效率,线程在获取值的时候,都是优先从cpu缓存区获取,若cpu缓存中没有,才去内存中获取,所以线程读的操作都是从cpu缓存区获取,由于cpu缓存区和内存的值并不总是同步的,这就引出了一个机制:当内存中共享变量的值发生变化时,内存会主动通知cpu缓存,当前共享变量的值已失效,需要重新从内存中拉取共享变量的值;volatile关键字就会触发这种机制;
被volatile修饰的变量,会被识别成共享变量,当内存中的值被修改后,会通知所有的cpu缓存,使cpu缓存中的值也对应修改,从而保证线程从cpu缓存中获取的值是最新的值;
防止指令重排
常用的单例模式就用到了volatile关键字:
public class Singleton {
private volatile static Singleton instance; //声明成 volatile
private Singleton (){}
public static Singleton getSingleton() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
代码中,instance=new Singleton();这条语句并不是一个原子操作:
1.分配内存给对象,在内存中开辟一段地址空间;// Singleton var = new Singleton();
2.对象的初始化;// var = init();
3.将分配好对象的地址指向instance变量,即instance变量的写;// instance = var;
如果不使用volatile关键字限制指令的重排序,1-3-2操作,获得到单例的线程可能拿到了一个空对象,后续操作会有影响!因此需要引入volatile对变量进行修饰;
default
一般用在接口的方法上,表示对于该方法,子类是无需强制实现的,但自己必须有默认实现;
this
this调用本类属性:只要在类中访问类的属性,一定要加上this关键字;
this表示当前对象;只要对象调用了本类中的方法,这个this就表示当前执行的对象;
this调用本类方法;
譬如简单的java pojo中,代码如下:
public class Person {
private String name;
private Long age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Long getAge() {
return age;
}
public void setAge(Long age) {
this.age = age;
}
public Person(String name, Long age) {
super();
this.name = name;
this.age = age;
}
}
super
使用super调用父类的属性和方法;具体代码如下:
class Person{
public String info = "爸爸!";
public void print(){
System.out.println("I am father");
}
}
class Student extends Person{
public String info = "儿子!" ;
public void print(){
//调用父类的方法
super.print();
System.out.println("I am child");
//调用父类中的属性
System.out.println(super.info);
System.out.println(this.info);
}
}
public class Test {
public static void main(String[] args) {
Student student= new Student();
student.print();
}
}
throws
用在方法上,表示方法可能会产生异常但是方法内部不处理,将异常抛回给调用处;
public class Demo {
public static void main(String[] args) {
try {
System.out.println(calculate(10, 0));
} catch (Exception e) {
e.printStackTrace();
}
}
public static int calculate(int x,int y) throws Exception {
return x/y ;
}
}
运行结果如下:
throw
用在方法中,表示异常对象由用户产生而非JVM产生,一般与自定义异常类搭配使用
public static void main(String[] args){
try {
throw new Exception("抛个异常玩玩!") ;
} catch (Exception e) {
e.printStackTrace();
}
}
synchronized
Java的一种同步锁机制;
修饰代码块
class SyncThread implements Runnable {
private static int count;
public SyncThread() {
count = 0;
}
public void run() {
//同步代码块
synchronized(this) {
for (int i = 0; i < 5; i++) {
try {
System.out.println(Thread.currentThread().getName() + ":" + (count++));
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public int getCount() {
return count;
}
}
public class Demo1{
public static void main(String[] args) {
//runnable
SyncThread syncThread = new SyncThread();
//thread1
Thread thread1 = new Thread(syncThread, "SyncThread1");
//thread2
Thread thread2 = new Thread(syncThread, "SyncThread2");
thread1.start();
thread2.start();
}
}
此时,Thread1和thread2是互斥的,因为在执行synchronized代码块时会锁定当前的对象,只有执行完该代码块才能释放该对象锁,下一个线程才能执行并锁定该对象,执行记过如下:
SyncThread1:0
SyncThread1:1
SyncThread1:2
SyncThread1:3
SyncThread1:4
SyncThread2:5
SyncThread2:6
SyncThread2:7
SyncThread2:8
SyncThread2:9
现在,把SyncThread的调用稍微改一下:
SyncThread syncThread1 = new SyncThread();
SyncThread syncThread2 = new SyncThread();
Thread thread1 = new Thread(syncThread1, "SyncThread1");
Thread thread2 = new Thread(syncThread2, "SyncThread2");
thread1.start();
thread2.start();
执行结果如下:
SyncThread1:0
SyncThread2:1
SyncThread1:2
SyncThread2:2
SyncThread2:4
SyncThread1:3
SyncThread2:5
SyncThread1:6
SyncThread1:7
SyncThread2:8
修饰普通方法
被synchronized修饰的方法称为同步方法,其作用的范围是整个方法,作用的对象是调用这个方法的对象;
public synchronized void run() {
for (int i = 0; i < 5; i ++) {
try {
System.out.println(Thread.currentThread().getName() + ":" + (count++));
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
修饰静态方法
修改一个静态的方法,其作用的范围是整个静态方法,作用的对象是这个类的所有对象;
class SyncThread implements Runnable {
private static int count;
public SyncThread() {
count = 0;
}
public synchronized static void method() {
for (int i = 0; i < 5; i ++) {
try {
System.out.println(Thread.currentThread().getName() + ":" + (count++));
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public synchronized void run() {
method();
}
}
public class Test{
public static void main(String[] args) {
SyncThread syncThread1 = new SyncThread();
SyncThread syncThread2 = new SyncThread();
Thread thread1 = new Thread(syncThread1, "SyncThread1");
Thread thread2 = new Thread(syncThread2, "SyncThread2");
thread1.start();
thread2.start();
}
}
修饰类
修饰类,其作用的范围是synchronized后面括号括起来的部分,作用主的对象是这个类的所有对象
class ClassName {
public void method() {
synchronized(ClassName.class) {
// todo
}
}
}
常见面试题:
1 常常看见变量和方法被final和static两个关键字修饰,为什么?
变量和方法与对象无关,属于类成员,从而可以直接类名.变量、类名.方法使用,简单方便;
强调变量内存地址不可变,方法不可被重写,强调方法内部的稳定性;
2 catch中发生了未知异常,finally还会执行吗?
catch中发生了异常,finally还是会执行,
并且当finally执行完成后,才会抛出catch异常
3 throw 和throws的区别
throw用于方法内部,主要表示手工抛出异常;
throws主要在方法声明上使用,明确告诉用户本方法可能产生的异常,同时该方法可能不处理
转载:https://blog.csdn.net/wuyundong123/article/details/100984407