Java SE基础巩固(三):常用的关键字

394 阅读6分钟

Java中的关键字很多,至少有50个左右,常见的new,final,try,catch等等,其中大多数关键字的意义都很简单,基本上根据英文意思就能知道其功能,本文不会对那些简单的关键字做介绍,仅挑选了几个使用频率较高,但又可能导致“迷惑”的关键字来讨论。

1 transient

transient关键字可修饰于类成员变量,作用是当类的对象发生序列化的时候,最终的序列化内容不包括被修饰的成员变量。下面的代码演示了transient的功能:

public class User implements Serializable {

    private String username;

    private transient String password;
    
    @Override
    public String toString() {
        return "User{" +
                "username='" + username + '\'' +
                ", password='" + password + '\'' +
                '}';
    }
    //setter and getter
}
public class TransientTest {

    public static void main(String[] args) {
        User user = new User();

        user.setUsername("yeonon");
        user.setPassword("admin");

        System.out.println("在序列化之前:");
        System.out.println(user);
        System.out.println("----------------------");

        try (ObjectOutputStream oos = new ObjectOutputStream(
                new FileOutputStream(
                        "E:\\Java_project\\effective-java\\src\\top\\yeonon\\ch11\\user.txt"))){
            oos.writeObject(user);
            oos.flush();
        } catch (IOException e) {
            e.printStackTrace();
        }

        try (ObjectInputStream ois = new ObjectInputStream(
                new FileInputStream("E:\\Java_project\\effective-java\\src\\top\\yeonon\\ch11\\user.txt"))){
            User user1 = (User) ois.readObject();
            System.out.println("序列化之后读取到的:");
            System.out.println(user1);
        } catch (IOException  | ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
}

User类首先得实现Serializable接口,然后我在password成员变量上增加了transient关键字,在主类中,用ObjectOutputStream和ObjectInputStream来做序列化和反序列化。

执行后的输出结果如下:

在序列化之前:
User{username='yeonon', password='admin'}
----------------------
序列化之后读取到的:
User{username='yeonon', password='null'}

发现序列化之后,再读取时,password是null,这说明序列化的内容没有包括password。

顺便说一下,在实际写代码的时候最好不要使用user1这种变量名,这里只是为了方便,随手写的。

2 instanceof

这是一个二元操作符,也算是关键字,作用是判断左边的对象是否是右边类的实例。如下所示:

User user = new User();
if (user instanceof User) {
    System.out.println("user object is instance of User class");
} else {
    System.out.println("user object is'n instance of User class");
}

这里肯定会输出user object is instance of User class。如果我们将User替换成Object,也一样会输出这段话,这说明instanceof还可以用于继承体系,即可以用来判断继承体系中子类的实例是否是父类的实现。

3 volatile

这个多用于并发环境下,功能有两个:

  • 禁止指令重排
  • 保证可见性

关于volatile的介绍,在我之前的文章 Java虚拟机(二):Java内存模型 有详细解释,在此不再赘述。

4 synchronized

和volatile一样,多用于并发环境下,其作用就是给某个代码段或者方法加上内置锁。拿并发编程中的“Hello,World”来举个例子:

public class SyncTest {

    private int count = 0;

    public void add() {
        count += 1;
    }

    public int getCount() {
        return count;
    }

    public static void main(String[] args) throws InterruptedException, TimeoutException, ExecutionException {
        ExecutorService service = Executors.newFixedThreadPool(4);
        SyncTest syncTest = new SyncTest();
        for (int j = 0; j < 4; j++) {
            service.execute(() -> {
                for (int i = 0; i < 10000; i++) {
                    syncTest.add();
                }
            });
        }
        service.shutdown();
        service.awaitTermination(1000, TimeUnit.SECONDS);
        System.out.println(syncTest.getCount());
    }
}

这里输出的结果会是什么呢?40000?答案是不一定,可能是40000,也可能是比40000小的数,但肯定不会比40000大,因为这里至少有4个线程在并发的对count进行修改,而又没有什么同步措施,故答案不对。如果在add方法上加入synchronized关键字,就可以保证线程安全了,如下所示:

public synchronized void add() {
    count += 1;
}

这样之后,无论运行多少次代码,结果都会是40000。因为synchronized实际上是内置锁,同一时刻仅有一个线程能获取到锁,并对其进行修改,最后执行完毕释放锁,其他线程可再次竞争锁,然后如此往复,知道任务完成。

那synchronized除了作用在方法还能作用在哪呢?下面是synchronized的使用方式:

  • 作用在实例方法上(没有static修饰的方法),相当于给对应的对象加锁,即也不能访问该对象其他的有synchronized修饰的方法,其他实例对象不受影响。

  • 作用静态方法上,相当于给类加锁,此时的作用范围就是该类的所有实例对象,即该类的所有对象同一时刻只能访问有一个对象能访问到synchronized静态方法,而且不能访问该类的其他synchronized静态方法。

  • 作用在代码块中,如下所示:

    private String lock = "lock";
    synchronized(lock) {
        //do something
    }
    //或者
    synchronized(Test.class) {
        //do something
    }
    

    这又有两种情况,一种是括号里的是对象实例,这种情况是对对象实例加锁,对其他对象没有影响。另一种是括号里的是类对象,这种情况是对类加锁,该类的其他对象都会受到影响。

关于synchronized的其他内容(例如在虚拟机里是如何实现的?有哪些相应的指令?)就不多说了,比较本文不是专门讲并发的。

5 final

final最容易让人记住的功能就是将一个变量声明成常量了,但实际上它的作用不仅仅是这个,还可以防止指令重排,在我之前的文章Java虚拟机(二):Java内存模型有比较详细的介绍,在此不再赘述。

6 static

static的作用也比较明显,就是将类、方法、成员变量声明成静态的。

  • 作用在类上。不能作用在外部类,只能作用在内部类上。表明该类是外部类所拥有的,而不是外部类的对象实例拥有的。可以在类里定义静态成员,而非静态内部类则不行。
  • 作用在方法上,表明该方法是一个类方法,和实例对象没有关系,可以直接通过类.方法的形式调用。在静态方法内部,能直接调用静态方法,但不能直接调用实例方法。
  • 作用在成员变量上,表明该成员变量是一个类成员变量,访问规则同静态方法。

静态类作用就是方便使用,如果一个类不依赖外部类的成员变量、方法等,那么最好将其声明成静态类。

静态方法其实也是为了方便使用,在调用的时候可以直接通过类名.方法的形式调用而不需要创建一个新的实例对象,静态工厂模式就非常依赖这个特性。

静态成员变量还是为了方便使用,我们经常能在程序源代码中看到类似public static final String XXX = "YYY"的声明,这是因为静态变量可以直接访问,无论是在实例方法里,还是静态方法里都一样,这样就能避免通过参数传递了。

7 小结

本文简单的介绍了几个常用的关键字,但实际上它们的功能或者原理都远远不止于此,如果想深入了解,建议到网上搜索资料进行学习。