Java 中这些常用关键字,总有那么些被你遗忘的

我们知道 Java 中是有保留关键字的,到目前为止大约有 50+ 关键字,具体如下:

Java 关键字大全

在命名上我们不能与这些关键字冲突,有冲突的话,编译器会报错。每个关键字都有特定的场景。当然 Java 现在有 50+ 关键字,我们不可能每个都用的到, 所以这里我挑出了 8 个常用的或者比较重要的关键字,一起来学习或者复习一下。

1、static

static 翻译成静态的、全局的,一旦被修饰,说明被修饰的东西在一定范围内是共享的。

static 只能修饰类变量、方法和方法块,你没见过 static 修饰类的吧。

「当 static 修饰类变量时」,如果该变量是 public 的话,表示该类变量可以被任何类直接访问,而且无需初始化类,直接使用 类名.变量名 这种形式直接访问。比如:

public class StaticDemo {
    // 被 static 修饰的变量
    static String str = "我被 static 修饰了";

    public static void main(String[] args) {
        System.out.println(StaticDemo.str);
    }
}

「static 修饰变量时,需要需要考虑线程安全问题」,因为多个线程同时操作一个共享变量时,极有可能出现并发问题。如我们定义了: public static List list = new ArrayLis t(); 这样的共享变量。这个 list 如果同时被多个线程访问的话,就有线程安全的问题。

「当 static 修饰方法时」,那就是说,无需本类的对象即可调用此方法,参考项目中的 utils 类。声明为 static 的方法有以下几条限制:

  • 仅能调用其他的 static 方法.

  • 它们只能访问 static 成员.

  • 它们不能以任何方式引用this或super.

  • static方法独立于任何实例,因此static方法必须被实现,而不能是抽象的abstract.

public class StaticDemo {

    static void fun(){
        System.out.println("我是被 static 修饰的方法");
    }

    public static void main(String[] args) {
        StaticDemo.fun();
    }
}

「static 方法内部的变量在执行时是没有线程安全问题的」。方法执行时,数据运行在栈里面,栈的 数据每个线程都是隔离开的,所以不会有线程安全的问题。

「当 static 修饰方法块时」,我们叫做静态块,静态块常常用于在类启动之前,初始化一些值,静态代码块语法格式:

    static {
        
    }

静态代码块跟普通代码块最大的不同是加载时间。静态代码块的内容是在类加载的时候执行的, 「而且只会加载一次」

2、final

final 的意思是不变的,一般来说用于以下三种场景:

「1.修饰类」,被 final 修饰的类,表明该类是无法继承的;比如我们常用的 string 类。

「2.修饰方法」,被 final 修饰的方法,表明该方法是无法覆写的;

「3.修饰变量」, 被 final 修饰的变量,说明该变量在声明的时候,就必须初始化完成,而且以后也不能修改其内存地址。

对于第三点需要注意一下,说的是无法修改其内存地址,并没有说无法修改其值。因为对于 List、 Map 这些集合类来说,被 final 修饰后,是可以修改其内部值的,但却无法修改其初始化时的内存地址。

3、this

在 Java 程序中遍地都是 this 的使用,this 使得程序设计变得规范、简单、灵活。但是在使用过程中,在不同场合它的含义并不完全相同,使用不当还会出现错误。

「this 就是指针/引用」,在Java语言中,当创建一个对象后,Java虚拟机就会为其分配一个指向对象本身的指针,这个指针就是“this”。

看看 this 在不同场合下的使用:

「1、this 调用本类中的成员变量」

public class ThisDemo {
 String name;
 public void fun(String name) {
  this.name = name;
 }
}

此时this指当前对象的引用,既然是对象的引用,我们也可以通过this调用成员方法.

「2、this 调用构造方法」

public class ThisDemo {
 public ThisDemo(String s) {
  System.out.println(s);
 }
 
 public ThisDemo() {
  this("调用构造方法");
 }
 public static void main(String[] args) {
  new ThisDemo();
 }
}

在一个Java类中,构造方法是一个与类同名的方法,必须与类的名字相同。而且在Java类中必须存在一个构造方法。如果在代码中没有显示的体现构造方法的话,那么编译器在编译的时候会自动添加.(如果你手动添加了一个构造方法,那么编译器就不会在为你自动添加无参的构造方法) .我们在实际编程的时候有时候需要在一个构造方法中对另一个构造方法进行调用。因此我们可以使用this()代表构造方法,但是,在使用this关键字调用其他构造方法的时候,this()调用构造方法只能放在构造方法的首行,为的是能够为类中的属性初始化;而且至少有一个构造方法是不用this调用,否则程序会出现错误。

「3、this 引用当前对象」

this 最重要的特定就是表示当前对象(注意是当前对象和当前对象的引用区分开),那什么叫当前对象呢?在Java中当前对象就是指当前正在调用类中方法的对象。使用this引用当前对象是指如果在类的方法中需要返回一个对象,并且该对象是方法所在的类的当前对象,可以使用this关键字作为方法的返回值。例如:

public class ThisDemo {
 public ThisDemo() {}
 public ThisDemo getThis() {
  return this;
 }
 public static void main(String[] args) {
  ThisDemo t1 = new ThisDemo();
  ThisDemo t2 = t1.getThis();
  System.out.println(t1 == t2);
  System.out.println(t1.equals(t2));
  //输出
  //true
  //true
 }
}

4、synchronized

synchronized 常用于并发编程中,用来控制线程对变量、方法的访问,从而解决并发问题。

在 java 代码中使用 synchronized 可是使用在代码块和方法中,根据Synchronized 用的位置可以有这些使用场景:

图片描述

synchronized 的位置不同,锁住的对象也不一样,有以下几种情况:

  • 当synchronized作用在方法上时,锁住的便是对象实例(this);

  • 当作用在静态方法时锁住的便是对象对应的Class实例,因为 Class数据存在于永久代,因此静态方法锁相当于该类的一个全局锁;

  • 当synchronized作用于某一代码域时,锁住的便是对应的代码块。

5、volatile

volatile 关键字也是常用在并发编程中,用来保证共享变量的可见性。因为在 JVM 和系统内存交互中,存在一个缓存层,如果变量没有被 volatile 修饰的话,修改后的变量值会存入到缓存中,等待系统刷新到内存中。这样会导致一些并发问题,被 volatile 修饰的变量,如果对变量进行修改的话,会被 CPU 强制刷新到内存中,其他使用该变量的线程可以第一时间看到变量最新的值。

「volatile 还有一个作用就是阻止重排序」,有时候编译器会对代码进行优化,选择一个它认为最高效的执行方式,但是有时候会带来一些问题,可以研究单例模式的双重检测锁写法。

6、try、catch、finally

其实这是三个关键字,我把它组合在一起,因为在使用时我们也是组合使用,try、catch、finally 常用于我们捕捉异常的一整套流程,先来看看三个关键字的职责:

  • 「try」:用来确定代码执行的范围;

  • 「catch」:捕捉可能会发生的异常;

  • 「finally」:用来执行一定要执行的代码块,比如关闭连接;

这个关键字组合使用到没什么难的,但是这里会有一个面试中常用的面试题: 「如果发生了异常,finally 会不会执行」

对于这个问题,我们用事实说话,看下面这个例子:

public static void main(String[] args) {
    try {
        System.out.println("try 运行中");
        int n = 1 / 0;
    } catch (Exception e) {
        System.out.println("catch 运行中");
        throw new RuntimeException("报错了");
    } finally {
        System.out.println("finally 运行中");
    }
}

运行结果如下:

从结果中可以看出, 「先执行了 finally 中的内容,在抛出 catch 中的异常」

7、transient

这个关键字你咋一看可能会觉得陌生,但是它在很多类中应用广泛。

「transient 关键字是用来阻止序列化的」,被 transient 关键字修饰的变量,在序列化时会被忽略掉。

我们可以在 ArrayList 的源码中找到 transient 的身影,ArrayList 中的 elementData 变量就是被 transient 修饰的;

transient Object[] elementData; // non-private to simplify nested class access

思考题:「为什么 ArrayList 要用 transient 修饰 elementData 变量?」

8、 default

default 在代码中也是比较常见的,比如 switch 分支一句中,用来表示默认实现或者操作。

default 也会用在接口的方法上。我们知道接口的实现类必须实现接口的所有方法,但是有时候我们一个接口类中,有那么几个方法是大部分实现类公用的,那么我们就可以加上 default ,在接口中实现该方法,子类就可以不用实现了。

public class StaticDemo {

    public static void main(String[] args) {
        Test test = new Test();
        test.test();
    }

}

interface TestApi{
    default void test(){
        System.out.println("我是默认实现,子类类可以不用实现了.....");
    }
}
class Test implements TestApi{

}