欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

详解Java编程中static关键字和final关键字的使用

程序员文章站 2024-03-06 20:08:02
java static关键字以及java静态变量和静态方法 static 修饰符能够与变量、方法一起使用,表示是“静态”的。 静态变量和静态方法能够通过类名来访问,不需...

java static关键字以及java静态变量和静态方法
static 修饰符能够与变量、方法一起使用,表示是“静态”的。

静态变量和静态方法能够通过类名来访问,不需要创建一个类的对象来访问该类的静态成员,所以static修饰的成员又称作类变量和类方法。静态变量与实例变量不同,实例变量总是通过对象来访问,因为它们的值在对象和对象之间有所不同。

请看下面的例子:

public class demo {
  static int i = 10;
  int j;
  demo() {
    this.j = 20;
  }
  public static void main(string[] args) {
    system.out.println("类变量 i=" + demo.i);
    demo obj = new demo();
    system.out.println("实例变量 j=" + obj.j);
  }
}

运行结果:

类变量 i=10
实例变量 j=20


static 的内存分配

静态变量属于类,不属于任何独立的对象,所以无需创建类的实例就可以访问静态变量。之所以会产生这样的结果,是因为编译器只为整个类创建了一个静态变量的副本,也就是只分配一个内存空间,虽然有多个实例,但这些实例共享该内存。实例变量则不同,每创建一个对象,都会分配一次内存空间,不同变量的内存相互独立,互不影响,改变 a 对象的实例变量不会影响 b 对象。

请看下面的代码:

public class demo {
  static int i;
  int j;
  public static void main(string[] args) {
    demo obj1 = new demo();
    obj1.i = 10;
    obj1.j = 20;
    
    demo obj2 = new demo();
    
    system.out.println("obj1.i=" + obj1.i + ", obj1.j=" + obj1.j);
    system.out.println("obj2.i=" + obj2.i + ", obj2.j=" + obj2.j);
  }
}

运行结果:

obj1.i=10, obj1.j=20
obj2.i=10, obj2.j=0

注意:静态变量虽然也可以通过对象来访问,但是不被提倡,编译器也会产生警告。

上面的代码中,i 是静态变量,通过 obj1 改变 i 的值,会影响到 obj2;j 是实例变量,通过 obj1 改变 j 的值,不会影响到 obj2。这是因为 obj1.i 和 obj2.i 指向同一个内存空间,而 obj1.j 和 obj2.j 指向不同的内存空间,请看下图:
详解Java编程中static关键字和final关键字的使用
注意:static 的变量是在类装载的时候就会被初始化。也就是说,只要类被装载,不管你是否使用了这个static 变量,它都会被初始化。

小结:类变量(class variables)用关键字 static 修饰,在类加载的时候,分配类变量的内存,以后再生成类的实例对象时,将共享这块内存(类变量),任何一个对象对类变量的修改,都会影响其它对象。外部有两种访问方式:通过对象来访问或通过类名来访问。
静态方法

静态方法是一种不能向对象实施操作的方法。例如,math 类的 pow() 方法就是一个静态方法,语法为 math.pow(x, a),用来计算 x 的 a 次幂,在使用时无需创建任何 math 对象。

因为静态方法不能操作对象,所以不能在静态方法中访问实例变量,只能访问自身类的静态变量。

以下情形可以使用静态方法:
一个方法不需要访问对象状态,其所需参数都是通过显式参数提供(例如 math.pow())。
一个方法只需要访问类的静态变量。

读者肯定注意到,main() 也是一个静态方法,不对任何对象进行操作。实际上,在程序启动时还没有任何对象,main() 方法是程序的入口,将被执行并创建程序所需的对象。

关于静态变量和静态方法的总结:
一个类的静态方法只能访问静态变量;
一个类的静态方法不能够直接调用非静态方法;
如访问控制权限允许,静态变量和静态方法也可以通过对象来访问,但是不被推荐;
静态方法中不存在当前对象,因而不能使用 this,当然也不能使用 super;
静态方法不能被非静态方法覆盖;
构造方法不允许声明为 static 的;
局部变量不能使用static修饰。

静态方法举例:

public class demo {
  static int sum(int x, int y){
    return x + y;
  }
  public static void main(string[] args) {
    int sum = demo.sum(10, 10);
    system.out.println("10+10=" + sum);
  }
}

运行结果:

10+10=20

static 方法不需它所属的类的任何实例就会被调用,因此没有 this 值,不能访问实例变量,否则会引起编译错误。

注意:实例变量只能通过对象来访问,不能通过类访问。
静态初始器(静态块)

块是由大括号包围的一段代码。静态初始器(static initializer)是一个存在于类中、方法外面的静态块。静态初始器仅仅在类装载的时候(第一次使用类的时候)执行一次,往往用来初始化静态变量。

示例代码:

public class demo {
  public static int i;
  static{
    i = 10;
    system.out.println("now in static block.");
  }
  public void test() {
    system.out.println("test method: i=" + i);
  }
  public static void main(string[] args) {
    system.out.println("demo.i=" + demo.i);
    new demo().test();
  }
}

运行结果是:

now in static block.
demo.i=10
test method: i=10


静态导入

静态导入是 java 5 的新增特性,用来导入类的静态变量和静态方法。

一般我们导入类都这样写:

import packagename.classname; // 导入某个特定的类




import packagename.*; // 导入包中的所有类

而静态导入可以这样写:

import static packagename.classname.methonname; // 导入某个特定的静态方法



import static packagename.classname.*; // 导入类中的所有静态成员

导入后,可以在当前类中直接用方法名调用静态方法,不必再用 classname.methodname 来访问。

对于使用频繁的静态变量和静态方法,可以将其静态导入。静态导入的好处是可以简化一些操作,例如输出语句 system.out.println(); 中的 out 就是 system 类的静态变量,可以通过 import static java.lang.system.*; 将其导入,下次直接调用 out.println() 就可以了。

请看下面的代码:

import static java.lang.system.*;
import static java.lang.math.random;
public class demo {
  public static void main(string[] args) {
    out.println("产生的一个随机数:" + random());
  }
}

运行结果:

产生的一个随机数:0.05800891549018705

java final关键字:阻止继承和多态
在 java 中,声明类、变量和方法时,可使用关键字 final 来修饰。final 所修饰的数据具有“终态”的特征,表示“最终的”意思。具体规定如下:
final 修饰的类不能被继承。
final 修饰的方法不能被子类重写。
final 修饰的变量(成员变量或局部变量)即成为常量,只能赋值一次。
final 修饰的成员变量必须在声明的同时赋值,如果在声明的时候没有赋值,那么只有 一次赋值的机会,而且只能在构造方法中显式赋值,然后才能使用。
final 修饰的局部变量可以只声明不赋值,然后再进行一次性的赋值。

final 一般用于修饰那些通用性的功能、实现方式或取值不能随意被改变的数据,以避免被误用,例如实现数学三角方法、幂运算等功能的方法,以及数学常量π=3.141593、e=2.71828 等。

事实上,为确保终态性,提供了上述方法和常量的 java.lang.math 类也已被定义为final 的。

需要注意的是,如果将引用类型(任何类的类型)的变量标记为 final,那么该变量不能指向任何其它对象。但可以改变对象的内容,因为只有引用本身是 final 的。

如果变量被标记为 final,其结果是使它成为常数。想改变 final 变量的值会导致一个编译错误。下面是一个正确定义 final 变量的例子:

public final int max_array_size = 25; // 常量名一般大写


常量因为有 final 修饰,所以不能被继承。

请看下面的代码:

public final class demo{
  public static final int total_number = 5;
  public int id;
  public demo() {
    // 非法,对final变量total_number进行二次赋值了
    // 因为++total_number相当于 total_number=total_number+1
    id = ++total_number;
  }
  public static void main(string[] args) {
    final demo t = new demo();
    final int i = 10;
    final int j;
    j = 20;
    j = 30; // 非法,对final变量进行二次赋值
  }
}

final 也可以用来修饰类(放在 class 关键字前面),阻止该类再派生出子类,例如 java.lang.string 就是一个 final 类。这样做是出于安全原因,因为要保证一旦有字符串的引用,就必须是类 string 的字符串,而不是某个其它类的字符串(string 类可能被恶意继承并篡改)。

方法也可以被 final 修饰,被 final 修饰的方法不能被覆盖;变量也可以被 final 修饰,被 final 修饰的变量在创建对象以后就不允许改变它们的值了。一旦将一个类声明为 final,那么该类包含的方法也将被隐式地声明为 final,但是变量不是。

被 final 修饰的方法为静态绑定,不会产生多态(动态绑定),程序在运行时不需要再检索方法表,能够提高代码的执行效率。在java中,被 static 或 private 修饰的方法会被隐式的声明为 final,因为动态绑定没有意义。

由于动态绑定会消耗资源并且很多时候没有必要,所以有一些程序员认为:除非有足够的理由使用多态性,否则应该将所有的方法都用 final 修饰。

这样的认识未免有些偏激,因为 jvm 中的即时编译器能够实时监控程序的运行信息,可以准确的知道类之间的继承关系。如果一个方法没有被覆盖并且很短,编译器就能够对它进行优化处理,这个过程为称为内联(inlining)。例如,内联调用 e.getname() 将被替换为访问 e.name 变量。这是一项很有意义的改进,这是由于cpu在处理调用方法的指令时,使用的分支转移会扰乱预取指令的策略,所以,这被视为不受欢迎的。然而,如果 getname() 在另外一个类中被覆盖,那么编译器就无法知道覆盖的代码将会做什么操作,因此也就不能对它进行内联处理了。