提示

Java 中有50个关键字,这些关键字支撑起 Java 的底层操作。@ermo

# Java 关键字

Java 中的关键字 JDK 内部使用,因此开发人员不可以使用关键字进行声明变量或者实例化对象。

强制使用时编译器会提示异常。

public class KeywordDemo {
    public static void main(String[] args) {
        // Compile error
        int this = "Don't use java keyword to define variable.";
        System.out.println(this);
    }
}

Java 中有50个关键字,其中 constgoto 作为保留字,目前没有使用到。

简单看下 Java 中的关键字。

Java 关键字

# strictfp

strictfp 是 strict float point 的缩写,即精确浮点数。浮点数计算在不同的平台上会有不同的结果。为解决这个问题,strictfp 在 JDK1.2 被提出,并遵循 IEEE 754 (opens new window) 标准去计算。

strictfp 可以作用到接口、类和方向上。

public class Person {
    strictfp void sum() {}
}
public strictfp interface StrictfpInterface {
    float sum();
}
public strictfp class Point {
}

strictfp 不能作用于抽象方法上,但是可以用于抽象类和抽象接口上。

# transient

transient 关键字只能用来修饰成员变量,被修饰的成员变量不参与序列化过程。

参考 Java 序列化与反序列化

# final

final 关键词作为不可访问的修饰符,可以作用到类、方法和变量上。

# 变量

final 修饰的基础变量不可更改,可以将 final 作用到成员变量上,这样的变量必须在构建对象的时候初始化,并且一经初始化后不可更改。

初始化有三种方式:

  • 构造器
  • 代码块
  • 静态代码块
public class FinalDemo {

    private final int j;
    // 方式1
    public FinalDemo() {
        this.j = 1;
    }
    
    // 方式2
    private final int num;
    {
        num = 5;
    }
    
    private static final int i;
    // 方式3
    static {
        i = 1;
    }
}

实际开发过程中应该将不可变常量与不可变参数用 final 修饰,是一种很好的开发习惯。

public class FinalDemo {

    private final int num;

    public FinalDemo () {
        this.num = 1;
    }

    public static void main(String[] args) {
        FinalDemo finalDemo = new FinalDemo();
        // Cannot assign a value to final variable 'num'
        finalDemo.num = 3;
    }
}
public class FinalDemo {

    private final int age = 18;

    public static final double PI = 3.14;

    public static void sum(final int num) {
        final int appleNum = 5;
    }
}

final 修饰的引用对象不可变,这里指的是变量引用的内存地址不可变。对象的内部变量依然可以更改。

public class FinalDemo {

    private int num = 5;

    public static void main(String[] args) {
        final FinalDemo finalDemo = new FinalDemo();
        finalDemo.num = 6;
        System.out.println(finalDemo.num); // 6

        // 编译错误
        finalDemo = new FinalDemo();
    }
}

# 方法

final 修饰的方法不能被重写。下面代码编译会报错。

public class FinalDemo {

    class Parent {
        public final void sum() {
            System.out.println("sum method.");
        }
    }
    
    class Children extends Parent {
        // Compile error
        public void sum() {
            System.out.println("children sum method.");
        }
    }
}

父类方法使用 private 声明,该方法默认加有 final 修饰符。

这时子类可以定义同名方法,编译器认为这是两个独立的方法。

public class FinalDemo {

    static class Parent {
        private void sum() {
            System.out.println("sum method.");
        }
    }

    static class Children extends Parent {
        private void sum() {
            System.out.println("children sum method.");
        }
    }

    public static void main(String[] args) {
        Parent parent = new Parent();
        parent.sum(); // sum method.

        Children children = new Children();
        children.sum(); // children sum method.
    }
}

#

final 修饰的类不可变,同时也不能被继承。

JDK 中基础类型的包装类 IntegerFloat 以及 String 都使用了 final 进行定义。

public final class Integer extends Number implements Comparable<Integer> {
    // 代码省略
}
public final class String
    implements java.io.Serializable, Comparable<String>, CharSequence {
        // 代码省略
}

# static

static 修饰符主要用于内存管理,使用 static 修饰的方法和变量不依赖类的实例,它们在内存中使用的是同一个内存地址。

static 修饰符可以在以下场景使用。

# 静态变量(Static Variables)

静态变量和成员变量的区别是,成员变量依赖于实例,每一个类都有一个成员变量副本。

而静态变量在内存地址中只有一份,所有类共享这一份内存地址。

使用成员变量前需要实例化当前类,静态变量可以直接通过类名调用。

静态变量不常使用,开发中经常使用的是 final 修饰的静态常量。

public class StaticVariables {

    private static final double PI = 3.14;
    private static j = 2;
    private int num = 1;

    public static void main(String[] args) {
        StaticVariables staticVariables = new StaticVariables();
        System.out.println(staticVariables.num); // 1

        System.out.println(StaticVariables.PI); // 3.14
        System.out.println(StaticVariables.j); // 2
    }
}

# 静态代码块(Static Block)

初始化静态变量的一种常见方法就是在变量声明的时候直接赋值。

另一个方法就是使用静态代码块进行静态变量的初始化。静态代码块在类加载时就会执行。

关于静态代码块有一个高频考点,那就是类的执行顺序:

  • 父类静态变量
  • 父类静态代码块
  • 子类静态变量
  • 子类静态代码块
  • 父类实例变量
  • 父类普通代码块
  • 子类实例变量
  • 子类普通代码块
  • 子类构造器

# 静态方法(Static Methods)

静态方法不是在对象上执行的方法,使用静态方法有以下三种限制:

  • 静态方法只能访问所属类的静态字段和静态方法,不能访问成员变量
  • 静态方法不能使用 abstract 抽象关键字,该方法必须要有实现
  • 静态方法不能使用 thissuper 关键字

我们最常用的主方法就是静态方法。

    public static void main(String[] args) {
        
    }

静态方法另一种常见的用途就是工厂方法。比如日期类 LocalDate.now()

    public static LocalDate now() {
        return now(Clock.systemDefaultZone());
    }

Java8 中的 Objects 判空方法也是静态方法。

    public static <T> T requireNonNull(T obj, String message) {
        if (obj == null)
            throw new NullPointerException(message);
        return obj;
    }

# 静态内部类(Nested Class)

在 Java 中,每个 .java 文件称为一个类,在这个类内部再定义一个类称之为内部类,相对而言,包含内部类的类称之为外部类。

内外部类只是针对开发人员来说的,编译器会将内部类也编译为一个独立的字节码文件。

静态内部类和普通的类差别不大,它可以有自己独立的静态变量、静态方法、成员变量、成员方法和构造器等。

静态内部类可以直接访问外部类的私有静态变量和方法,不能访问外部类的成员变量和成员方法。

为什么静态内部类可以访问外部类的静态私有变量和静态私有方法?

因为在字节码编译的过程中,编译器自动为外部类新增一个非私有的静态方法,方法内返回静态私有变量,然后内部类通过调用该静态方法,从而访问到了外部的静态私有变量。

除此之外,静态内部类的创建不依赖于外部类的实例,而非静态内部类需要依赖外部类的实例。

public class Outer {
    private static int count = 1;

    public static class StaticInner {

        public void share() {
            System.out.println("share=" + count);
        }
    }
    
    public class Inner {
        
    }

    public static void main(String[] args) {
        StaticInner staticInner = new StaticInner();
        
        Outer outer = new Outer();
        Inner inner = outer.new Inner();
    }
}

# 参考资料

  • Java 编程的逻辑,马昌俊
  • Java 核心技术卷一,[美] 凯·S·霍斯特曼