在一个类中,关键字“static”有什么作用?

472

具体来说,我正在尝试这段代码:

package hello;

public class Hello {

    Clock clock = new Clock();

    public static void main(String args[]) {
        clock.sayTime();
    }
}

但是它报错了:

无法在静态方法 main 中访问非静态字段

于是我将clock的声明更改为:

static Clock clock = new Clock();

它起作用了。在声明之前放置该关键字意味着什么?它会对该对象可做的事情进行什么限制或约束?


再次提醒,每个类加载器每个类只有一个静态实例。 - Javamann
什么是类加载器? - carloswm85
22个回答

653

static成员属于类而不是特定的实例。

这意味着,即使您创建了一百万个该类的实例或者您没有创建任何实例,只存在一个static字段的实例[1],它将被所有实例共享。

由于static方法也不属于特定的实例,因此它们无法引用实例成员。在给出的示例中,main不知道应该引用哪个Hello类的实例(因此也就是哪个Clock类的实例)。static成员只能引用static成员。当然,实例成员可以访问static成员。

附注:当然,static成员可以通过对象引用访问实例成员。

例如:

public class Example {
    private static boolean staticField;
    private boolean instanceField;
    public static void main(String[] args) {
        // a static method can access static fields
        staticField = true;

        // a static method can access instance fields through an object reference
        Example instance = new Example();
        instance.instanceField = true;
    }

[1]: 根据运行时的特性,它可以是每个ClassLoader或AppDomain或线程一个,但这并不重要。


5
在.NET中,您还可以使用[ThreadStatic]属性修改此行为,该属性使静态本地变量仅限于特定线程。 - TheSoftwareJedi
4
我知道这是一篇旧帖子,但对于像我这样的初学者来说,这可能会有所帮助。https://dev59.com/A2w05IYBdhLWcg3wyk3n - user3526905
你无法访问 instance.instanceField 吗?因为它是一个私有变量?或者说,因为你在它自己的类中实例化了对象,所以这是有效的?听起来对我来说像是递归的噩梦,但我是 Java 新手。 - Matt Corby
如果一个类的静态成员被2个不同的线程引用,那么该静态成员有多少实例?我觉得是2,但如果您希望在线程之间使用相同的实例,则必须使用volatile关键字。这正确吗? - slipperypete
如果类不存在任何实例,这个值会被保留吗? - mckenzm
@TheSoftwareJedi 嗯,就像 threadLocal 一样。 - z atef

133
这意味着在“Hello”中只有一个“clock”实例,而不是每个“Hello”类的独立实例都有一个“clock”实例,更进一步说,这意味着所有“Hello”类的实例将共享同一个“clock”引用。

因此,如果您在代码中的任何位置执行“new Hello”操作: A- 在第一种情况下(在未使用“static”之前),每次调用“new Hello”时都会创建一个新的“clock”,但是 B- 在第二种情况下(使用“static”后),每个“new Hello”实例仍将共享并使用最初创建的相同“clock”引用。

除非您需要在main之外的某个地方使用“clock”,否则以下内容也可以正常工作:

package hello;
public class Hello
{
    public static void main(String args[])
    {
      Clock clock=new Clock();
      clock.sayTime();    
    }
}

这是更常见的做法。main()例程应该是自包含的。 - Jason S
1
在第二个实例中,每次调用主方法时都会创建一个新的Clock实例,对吗? - Ali
2
在第二个实例中,静态时钟只会创建一次。在我的示例中,时钟位于主函数内部,那么是的,它将在每次调用主函数时重新创建。但通常情况下,主函数只在程序启动时调用一次,并且当其退出时,所有内容都被释放。 - Paul Tomblin
我不明白如何在主方法中创建一个新的时钟?因为你说每次调用main方法都会创建一个新的,但只有一个main方法。那么这个main方法如何引用不同的时钟实例呢?在主方法中创建一个新的时钟实例并使用它的sayTime()方法有点难理解,但是在主方法之外创建实例并使用sayTime()方法却不可能。当main方法被调用一次时,一切都是自由的吗?@PaulTomblin - ShakibaZar
@user5621266 我只使用了 main 方法,因为 OP 这样做了。如果它是一个公共方法,从其他地方调用,并且 Hello 类被实例化多次,那么它可以为每个 Hello 实例创建一个 Clock 实例,除非 clock 是静态的。 - Paul Tomblin

98
static关键字表示某个东西(字段、方法或嵌套类)与类型而非类型的任何特定实例相关联。例如,一个在不使用任何Math类的实例的情况下调用Math.sin(...),事实上,您无法创建Math类的实例。
有关更多信息,请参见Oracle的Java教程中的相关部分。

旁注

不幸的是,Java允许您像访问实例成员一样访问静态成员,例如:

// Bad code!
Thread.currentThread().sleep(5000);
someOtherThread.sleep(5000);

这使得sleep看起来像是一个实例方法,但实际上它是一个静态方法 - 它总是使当前线程睡眠。在调用代码中明确这一点是更好的做法:

// Clearer
Thread.sleep(5000);

1
另一个例子:System.out.println() 看起来像是一个类方法,但实际上它是一个实例方法。因为 out 是 System 类中的 PrintStream 实例。 - Jiahui Zhang
@LeslieCheung:不,对我来说它看起来不像是一个类方法,因为System.out对我来说不像是一个类型名称。 - Jon Skeet

42

static关键字在Java中表示变量或函数属于类型而不是实际对象,因此它被共享于所有该类的实例之间。

例如,如果你有一个变量:private static int i = 0; 并且在一个实例中增加它(i++),更改将反映在所有实例中。现在,在所有实例中,i 的值都为1。

静态方法可以在不创建对象的情况下使用。


4
“Shared between all instances”这个词汇给人以错误的印象,我认为它暗示你需要有该对象的一个实例。 - Jon Skeet
1
实际上并不需要任何实例,因为静态字段等属于类型。 - Jon Skeet
@Jon Skeet 静态属于类型,而不是对象?你能详细解释一下吗?类型像数据类型:int,double,...? - truongnm
@truongnm:类型应与声明变量/方法的类中的类型相同。 - Jon Skeet

29

静态成员的基本用法...

public class Hello
{
    // value / method
    public static String staticValue;
    public String nonStaticValue;
}

class A
{
    Hello hello = new Hello();
    hello.staticValue = "abc";
    hello.nonStaticValue = "xyz";
}

class B
{
    Hello hello2 = new Hello(); // here staticValue = "abc"
    hello2.staticValue; // will have value of "abc"
    hello2.nonStaticValue; // will have value of null
}

这就是你可以在所有类成员之间共享值而不必将类实例Hello发送到其他类中的方法。并且使用静态,您无需创建类实例。

Hello hello = new Hello();
hello.staticValue = "abc";
你可以通过类名直接调用静态值或方法:
Hello.staticValue = "abc";

23

静态意味着您不必创建类的实例来使用与该类关联的方法或变量。在您的示例中,您可以调用:

Hello.main(new String[]()) //main(...) is declared as a static function in the Hello class

直接使用,而不是:

Hello h = new Hello();
h.main(new String[]()); //main(...) is a non-static function linked with the "h" variable

在静态方法中(属于类的方法),您无法访问任何非静态成员,因为它们的值取决于类的实例化。非静态 Clock 对象是实例成员,每个 Hello 类的实例都有不同的值/引用,因此您无法从类的静态部分访问它。


静态上下文的解释很好 :) - Abdel-Raouf

22

Java中的静态:

静态是一种非访问修饰符。 static关键字属于类,而不是类的实例。 可以用于将变量或方法附加到类。

静态关键字可以与以下内容一起使用:

方法

变量

嵌套在另一个类中的类

初始化块

不能与以下内容一起使用:

类(非嵌套)

构造函数

接口

方法局部内部类(与嵌套类不同)

内部类方法

实例变量

局部变量

示例:

想象一下以下示例,其中有一个名为count的实例变量,该变量在构造函数中递增:

package pkg;

class StaticExample {
    int count = 0;// will get memory when instance is created

    StaticExample() {
        count++;
        System.out.println(count);
    }

    public static void main(String args[]) {

        StaticExample c1 = new StaticExample();
        StaticExample c2 = new StaticExample();
        StaticExample c3 = new StaticExample();

    }
}

输出:

1 1 1

由于实例变量在对象创建时获取内存,因此每个对象都将拥有实例变量的副本,如果它被增加,它不会反映到其他对象中。

现在,如果我们将实例变量count更改为静态变量,则程序将产生不同的输出:

package pkg;

class StaticExample {
    static int count = 0;// will get memory when instance is created

    StaticExample() {
        count++;
        System.out.println(count);
    }

    public static void main(String args[]) {

        StaticExample c1 = new StaticExample();
        StaticExample c2 = new StaticExample();
        StaticExample c3 = new StaticExample();

    }
}

输出:

1 2 3

在这种情况下,静态变量只会获得一次内存,如果任何对象更改了静态变量的值,则它将保留其值。

带有final的静态变量:

声明为final和静态的全局变量在整个执行过程中都不会改变。因为静态成员存储在类内存中,它们只会在整个执行过程中加载一次。它们对类的所有对象都是相同的。如果将静态变量声明为final,则任何对象都无法更改它们的值,因为它是final的。因此,声明为final和static的变量有时被称为常量。接口的所有字段都被称为常量,因为它们默认为final和static。

输入图像描述

图片来源:Final Static


17

除了现有的回答之外,让我用一张图片来解释:

所有储蓄账户都适用2%的利率。因此它是静态的

账户余额应该是个人的,因此不是静态的(即可变的)

enter image description here


13

到目前为止,这次讨论忽略了类加载器的考虑。严格来说,Java静态字段在给定类加载器的所有类实例之间共享。


1
这是Apocalisp在Merhdad的回答评论中提到的。 - Zach Langley
1
好观点。很多人不知道这一点,但一旦你开始搞类加载器,它就变得非常重要。 - sleske
3
这些都是真的,但它并没有回答问题。应该发布为评论。 - user207421

8

一个字段可以分配给类或类的实例。默认情况下,字段是实例变量。通过使用 static 关键字,该字段成为类变量,因此只有一个 clock。如果您在一个地方进行更改,则它将在所有地方可见。实例变量是彼此独立地更改的。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接