corejava11(4.4 静态域和方法)

本文详细介绍了Java中的静态域和方法。静态域每个类只有一个,而每个对象都有自己的非静态实例字段副本;static常量很常见,如Math.PI;静态方法不在对象上操作,有特定使用场景;还介绍了静态方法的常见用法——工厂方法,以及main方法作为静态方法的原因。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

4.4 静态域和方法

在您看到的所有示例程序中,主方法都用static修饰符标记。我们现在准备讨论这个修饰语的含义。

4.4.1 静态域

如果将字段定义为静态字段,那么每个类只有一个这样的字段。相反,每个对象都有自己的非静态实例字段副本。例如,假设我们要为每个员工分配一个唯一的标识号。我们将实例字段id和静态字段nextId添加到Employee类中:

class Employee
{
    private static int nextId = 1;
    private int id;
    . . .
}

每个Employee对象现在都有自己的id字段,但类的所有实例之间只有一个共享的nextId字段。换个说法吧。如果Employee类有1000个对象,那么有1000个实例字段id,每个对象一个。但是只有一个静态字段nextId。即使没有Employee对象,也存在静态字段nextId。它属于类,而不是任何单个对象。

注意

在一些面向对象的编程语言中,静态字段称为类字段。“static”一词是C++的无意义的保留。

让我们实现一个简单的方法:

public void setId()
{
    id = nextId;
    nextId++;
}

假设您为harry设置了员工身份号码:

harry.setId();

然后,将harry的id字段设置为静态字段nextId的当前值,并递增静态字段的值:

harry.id = Employee.nextId;
Employee.nextId++;

4.4.2 static常量

静态变量非常罕见。然而,静态常量更常见。例如,Math类定义了一个静态常量:

public class Math
{
    . . .
    public static final double PI = 3.14159265358979323846;
    . . .
}

您可以在程序中以Math.PI的形式访问这个常量。

如果省略了关键字static,那么PI将是Math类的一个实例字段。也就是说,您需要这个类的一个对象来访问PI,并且每个Math对象都有自己的PI副本。

另一个您使用过多次的静态常量是System.out。它在System类中声明如下:

public class System
{
    . . .
    public static final PrintStream out = . . .;
    . . .
}

正如我们多次提到的,拥有public字段从来不是一个好主意,因为每个人都可以修改它们。但是,public常量(即最终字段)是可以的。由于out已声明为final,因此不能将另一个打印流重新分配给它:

System.out = new PrintStream(. . .); // ERROR--out is final

注意

如果查看System类,您会注意到一个方法setOut将System.out设置为不同的流。您可能想知道该方法如何更改final变量的值。但是,setOut方法是原生方法,而不是在Java编程语言中实现的。本地方法可以绕过Java语言的访问控制机制。这是一个非常不寻常的解决方案,您不应该在程序中模仿它。

4.4.3 static方法

静态方法是不在对象上操作的方法。例如,Math类的pow方法是静态方法。表达式

Math.pow(x, a)

计算乘方x^a。它不使用任何数学对象来执行它的任务。换句话说,它没有隐式参数。

可以将静态方法视为没有this参数的方法。(在非静态方法中,该参数指方法的隐式参数,见第150页第4.3.7节“隐式和显式参数”。)

Employee类的静态方法无法访问id实例字段,因为它不在对象上操作。但是,静态方法可以访问静态字段。下面是这样一个静态方法的示例:

public static int getNextId()
{
	return nextId; // returns static field
}

若要调用此方法,请提供类的名称:

int n = Employee.getNextId();

你能省略这个方法的关键字static吗?是的,但是您需要一个Employee类型的对象引用来调用该方法。

注意

使用对象调用静态方法是合法的。例如,如果harry是Employee对象,则可以调用harry.getNextId()而不是Employee.getNextId()。然而,我们发现这个符号令人困惑。getNextId方法根本不看Harry来计算结果。我们建议您使用类名而不是对象来调用静态方法。

在两种情况下使用静态方法:

  • 当一个方法不需要访问对象状态时,因为所有需要的参数都作为显式参数提供(例如:Math.pow)。
  • 当方法只需要访问类的静态字段时(例如:Employee.getNextId)。

C++注意

静态字段和方法在Java和C++中具有相同的功能。但是,语法略有不同。在C++中,使用::运算符访问其范围之外的静态字段或方法,如Math::PI

“static”术语有着奇怪的历史。首先,在C语言中引入了关键字static来表示退出块时不会消失的局部变量。在这种情况下,术语“static”是有意义的:当再次输入块时,变量仍然存在,并且仍然存在。然后,static在c中得到了第二个含义,表示不能从其他文件访问的全局变量和函数。关键字static被简单地重用以避免引入新的关键字。最后,C++重用关键字作为第三个无关的解释,表示属于类的变量和函数,而不是类的任何特定对象。这就是java关键词的意思。

4.4.4 工厂方法

下面是静态方法的另一个常见用法。诸如LocalDateNumberFormat之类的类使用构造对象的静态工厂方法。您已经看到工厂方法LocalDate.nowLocalDate.of。下面是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%

为什么NumberFormat类不使用构造函数?有两个原因:

  • 不能给构造函数命名。构造函数名称始终与类名相同。但我们需要两个不同的名称来获取货币实例和百分比实例。
  • 使用构造函数时,不能更改所构造对象的类型。但是工厂方法实际上返回DecimalFormat类的对象,DecimalFormat是继承自NumberFormat的子类。(更多关于继承的信息,请参见第5章。)

4.4.5 main方法

注意,您可以调用静态方法而不需要任何对象。例如,您从不构造Math类的任何对象来调用Math.pow。

出于同样的原因,main方法是静态方法。

public class Application
{
    public static void main(String[] args)
    {
        // construct objects here
        . . .
    }
}

main方法不在任何对象上操作。事实上,当程序启动时,还没有任何对象。静态main方法执行,并构造程序需要的对象。

提示

每个类都有一个main方法。对于类的单元测试来说,这是一个方便的技巧。例如,可以向Employee类添加一个main方法:

class Employee
{
    public Employee(String n, double s, int year, int month, int day)
    {
        name = n;
        salary = s;
        hireDay = LocalDate.of(year, month, day);
    }
    . . .
    public static void main(String[] args) // unit test
    {
        var e = new Employee("Romeo", 50000, 2003, 3, 31);
        e.raiseSalary(10);
        System.out.println(e.getName() + " " + e.getSalary());
    }
    . . .
}

如果要单独测试Employee类,只需执行

java Employee

如果Employee类是较大应用程序的一部分,则使用

java Application

并且永远不会执行Employee类的main方法。

清单4.3中的程序包含一个具有静态字段nextId和静态方法getNextId的Employee类的简单版本。我们用三个雇员对象填充一个数组,然后打印雇员信息。最后,我们打印下一个可用的标识号,以演示静态方法。

请注意,Employee类还具有用于单元测试的静态main方法。试着运行

java Employee

java StaticTest

来执行main方法。

清单4.3 StaticTest/StaticTest.java

/**
 * This program demonstrates static methods.
 * @version 1.02 2008-04-10
 * @author Cay Horstmann
 */
public class StaticTest
{
   public static void main(String[] args)
   {
      // fill the staff array with three Employee objects
      var staff = new Employee[3];

      staff[0] = new Employee("Tom", 40000);
      staff[1] = new Employee("Dick", 60000);
      staff[2] = new Employee("Harry", 65000);

      // print out information about all Employee objects
      for (Employee e : staff)
      {
         e.setId();
         System.out.println("name=" + e.getName() + ",id=" + e.getId() + ",salary="
            + e.getSalary());
      }

      int n = Employee.getNextId(); // calls static method
      System.out.println("Next available id=" + n);
   }
}

class Employee
{
   private static int nextId = 1;

   private String name;
   private double salary;
   private int id;

   public Employee(String n, double s)
   {
      name = n;
      salary = s;
      id = 0;
   }

   public String getName()
   {
      return name;
   }

   public double getSalary()
   {
      return salary;
   }

   public int getId()
   {
      return id;
   }

   public void setId()
   {
      id = nextId; // set id to next available id
      nextId++;
   }

   public static int getNextId()
   {
      return nextId; // returns static field
   }

   public static void main(String[] args) // unit test
   {
      var e = new Employee("Harry", 50000);
      System.out.println(e.getName() + " " + e.getSalary());
   }
}

java.util.Object 7

  • static <T> void requireNonNull(T obj)
  • static <T> void requireNonNull(T obj, String message)
  • static <T> void requireNonNull(T obj, Supplier<String> messageSupplier) 8
    如果obj为空,则这些方法抛出一个无消息或给定消息的NullPointerException。(第6章解释了如何延时从供应者地方获取一个值。第8章解释了<T>语法。)
  • static <T> T requireNonNullElse(T obj, T defaultObj)
  • static <T> T requireNonNullElseGet(T obj, Supplier<T> defaultSupplier)
    如果obj不为空,则返回obj;如果obj为空,则返回默认对象。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值