Java static静态变量和静态方法(非常详细,附带实例)
对于初学者,看到的所有 Java 程序中,main() 方法都用 static 修饰符标记,本节带大家学习 static 的用法。
例如,假设我们想给每个雇员一个不同的 ID 号,那么我们可以共享最后一个分配的 ID。
构建新的 Employee 对象时,共享的 lastId 变量将递增,id 实例变量将被设置为该值。因此,每个 Employee 都有一个不同的 id 值。
例如,Math 类就声明了一个静态常量:
下面是一个静态 final 变量的例子。这个变量是一个对象,而不是一个数值。每次你需要一个随机数时,都要构造一个新的随机数生成器,这样做既浪费又不安全。最好在这个类的所有实例中共享一个随机数生成器。
静态常量的另一个例子是 System.out。它在 System 类中声明如下:
静态方法要使用 static 修饰符声明:
使用静态方法的另一个常见原因是,为了不拥有的类提供附加功能。例如,如果有一个方法能够产生给定范围内的随机整数,是不是很好?不能向标准库中的 Random 类添加方法,但可以提供一个静态方法:
注意,调用对象的静态方法是合法的。例如,为了获取今天的日期,可以不通过 LocalDate.now() 的形式调用,而是在 LocalDate 类的对象 date 上调用 date.now(),但是这样并没有多大的意义。now() 方法并不依赖 date 对象来计算结果。因此,大多数 Java 编程人员都认为这是不好的编程风格。
由于静态方法并不依赖对象进行操作,因此无法从静态方法访问实例变量。然而,静态方法可以访问类中的静态变量。
例如,在 RandomNumbers.nextInt() 方法中,我们可以将随机数生成器设置为静态变量:
例如,NumberFormat 类就使用了很多的工厂方法,来生成不同样式的格式化对象:
此外,构造方法 new Numberformat(...) 将生成 NumberFormat 对象;但是,工厂方法可以返回子类的对象。事实上,上面的那些工厂方法返回的是 DecimalFormat 类的实例。
工厂方法也可以返回共享对象,而不是不必要地构造新对象。例如,调用 Collections.emptyList() 将返回一个共享的不可变的空列表。
Java静态变量
如果将类中的变量声明为 static,则整个类中将只有一个该变量。然而,每个对象都有自己的实例变量副本。例如,假设我们想给每个雇员一个不同的 ID 号,那么我们可以共享最后一个分配的 ID。
public class Employee { private static int lastId = 0; private int id; ... public Employee() { lastId++; id = lastId; } }这样,每个 Employee 对象都有自己的实例变量 id,但只有 lastId 变量属于该类,而不属于该类的任何特定实例。
构建新的 Employee 对象时,共享的 lastId 变量将递增,id 实例变量将被设置为该值。因此,每个 Employee 都有一个不同的 id 值。
你可能会想知道,为什么这样一个属于类但不属于单个实例的变量会被命名为“static”。因为这个意义含混的词是 C++ 保留的一个关键字,C++ 起初也没有想出更合适的词,它只是从C语言中一个不相关的用法中借用了这个关键字。一个更加贴切的描述性的术语是“类变量”(class variable)。
Java静态常量
可变的静态变量其实很少,但静态常量(即 static final 的变量)却非常常见。例如,Math 类就声明了一个静态常量:
public class Math { ... public static final double PI = 3.14159265358979323846; ... }可以在程序中以 Math.PI 的形式访问此常量。如果省略 static 关键字,那么 PI 将是 Math 类的实例变量。也就是说,需要一个类的对象来访问 PI,并且每个 Math 对象都有自己的 PI 副本。
下面是一个静态 final 变量的例子。这个变量是一个对象,而不是一个数值。每次你需要一个随机数时,都要构造一个新的随机数生成器,这样做既浪费又不安全。最好在这个类的所有实例中共享一个随机数生成器。
public class Employee { private static final Random generator = new Random(); private int id; ... public Employee() { id = 1 + generator.nextInt(1_000_000); } }
静态常量的另一个例子是 System.out。它在 System 类中声明如下:
public class System { public static final PrintStream out; ... }注意,即使 out 在 System 类中被声明为 final,System 类中也有一个 setOut() 方法来将 System.out 设置为不同的流。这个方法是一种“原生”方法,并未在 Java 中实现,它可以绕过 Java 语言的访问控制机制。这是 Java 早期出现的一种非常罕见的情况,你在其他的地方可能不会遇到。
Java静态初始化块
在前面的讲解中,静态变量是在声明时初始化的。有时,需要执行一些额外的初始化工作。这时,可以将它们放入一个静态初始化块(static initialization block)中。static { // 静态初始化代码 }这样,在类首次被加载时就会执行静态初始化块。与实例变量一样,如果没有显式的赋值,那么静态变量会被设置为 0、false 或 null。所有静态变量初始化和静态初始化块都按照它们在类声明中出现的顺序执行。
Java静态方法
静态方法是不依赖对象就能被调用的方法。例如,Math 类的 pow() 方法是一个静态方法。以下表达式:Math.pow(x, a)计算幂 x^a。它不需要使用任何 Math 对象来执行任务。
静态方法要使用 static 修饰符声明:
public class Math { public static double pow(double base, double exponent) { ... } }那么是否可以将 pow() 方法转换为普通的实例方法呢?由于在 Java 中,基本类型并不是类,因此它不能作为 double 类型的实例方法。所以,一旦将其作为 Math 类的一个实例方法,就需要构造一个 Math 对象来调用它。
使用静态方法的另一个常见原因是,为了不拥有的类提供附加功能。例如,如果有一个方法能够产生给定范围内的随机整数,是不是很好?不能向标准库中的 Random 类添加方法,但可以提供一个静态方法:
public class RandomNumbers { public static int nextInt(Random generator, int low, int high) { return low + generator.nextInt(high - low + 1); } }可以这样调用该方法:
int dieToss = RandomNumbers.nextInt(1, 6); System.out.println(dieToss);
注意,调用对象的静态方法是合法的。例如,为了获取今天的日期,可以不通过 LocalDate.now() 的形式调用,而是在 LocalDate 类的对象 date 上调用 date.now(),但是这样并没有多大的意义。now() 方法并不依赖 date 对象来计算结果。因此,大多数 Java 编程人员都认为这是不好的编程风格。
由于静态方法并不依赖对象进行操作,因此无法从静态方法访问实例变量。然而,静态方法可以访问类中的静态变量。
例如,在 RandomNumbers.nextInt() 方法中,我们可以将随机数生成器设置为静态变量:
public class RandomNumbers { private static final Random generator = new Random(); public static int nextInt(int low, int high) { return low + generator.nextInt(high - low + 1); // OK to access the static generator variable } }
Java工厂方法
静态方法的常见用法是工厂方法(factory method),即返回类的新实例的静态方法。例如,NumberFormat 类就使用了很多的工厂方法,来生成不同样式的格式化对象:
NumberFormat currencyFormatter = NumberFormat.getCurrencyInstance(); NumberFormat percentFormatter = NumberFormat.getPercentInstance(); double x = 0.1; System.out.println(currencyFormatter.format(x)); // Prints $0.10 System.out.println(percentFormatter.format(x)); // Prints 10%这里为什么不使用构造方法呢?区分两个构造方法的唯一方法是通过它们的参数类型来区分。因此,不能同时有两个没有参数的构造方法。
此外,构造方法 new Numberformat(...) 将生成 NumberFormat 对象;但是,工厂方法可以返回子类的对象。事实上,上面的那些工厂方法返回的是 DecimalFormat 类的实例。
工厂方法也可以返回共享对象,而不是不必要地构造新对象。例如,调用 Collections.emptyList() 将返回一个共享的不可变的空列表。