在什么情况下调用类中static final修饰的属性时不需要初始化类

探讨Java中被final修饰的静态字段如何影响类的初始化过程,特别是在引用类型和基本类型上的不同表现。

被final修饰静态字段在操作使用时,不会使类进行初始化,因为在编译期已经将此常量放在常量池。
测试:

class ClassLoadTime{
  static{
    System.out.println("ClassLoadTime类初始化时就会被执行!");
  }
  public static final int MIN = 10; (防止测试类和此类不在一个包,使用public修饰符)
}
 
class ClassLoadDemo{
  public static void main(String[] args){
   System.out.println(ClassLoadTime.MIN);
  }
}
由于编译器编译优化,导致该变量在常量池中,当你用ClassLoadTime.MIN时,只会到常量池中查该数值,不会加载ClassLoaderTime类。

输出:

10
————————————————
版权声明:本文为优快云博主「冰凌其」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.youkuaiyun.com/xiao1_1bing/article/details/81139926

在看到上述文章的这段内容时有一个疑问,如果我访问的属性类型是一个引用类型的话也不会初始化 ClassLoaderTime类吗?于是用一下代码测试了一下:

public class HungrySingletonPattern {
	static{
		System.out.println("HungrySingletonPattern类初始化时就会被执行!");
	}
	
	public static final HungrySingletonPattern instance = new HungrySingletonPattern();
	public static final String str = "调我不用初始化类!";
	
	private HungrySingletonPattern() {
		super();
		// TODO Auto-generated constructor stub
	}
	
	public static final HungrySingletonPattern getInstance(){
		return instance;
	}
}

public class Test {
	public static void main(String[] args) throws SecurityException, NoSuchFieldException {
		HungrySingletonPattern i = HungrySingletonPattern.instance;
//		String s = HungrySingletonPattern.str;
	}
}

运行结果如下:

HungrySingletonPattern类初始化时就会被执行!

说明这个HungrySingletonPattern类被初始化了!为什么上边的例子不会初始化类,在把类中属性换成引用类型时类就会被初始化呢?上边的例子说是编译器的优化导致的,那么我们就来看看两段代码生成的字节码有什么区别吧:

1. main函数中调用HungrySingletonPattern.str的情况:

 Code:
   stack=1, locals=2, args_size=1
      0: ldc           #21                 // String 调我不用初始化类!
      2: astore_1
      3: return

2. main函数中调用HungrySingletonPattern.instance的情况:

    Code:
      stack=1, locals=2, args_size=1
         0: getstatic     #21                 // Field cn/edu/xidian/singleton/h
ungry/HungrySingletonPattern.instance:Lcn/edu/xidian/singleton/hungry/HungrySing
letonPattern;
         3: astore_1
         4: return

我们发现上述两个例子中生成的字节码唯一的区别就是1中生成的指令是ldc指令,而2中生成的指令是getstatic指令。那么这两个指令都是什么含义呢?

我们通过查询java虚拟机规范发现指令的解释如下:

0xb2  getstatic  获取指定类的静态域,并将其值压入栈顶。
0x12ldc将 int,float 或 String 型常量值从常量池中推送至栈顶。

所以我们可以大胆的推测:当类中的基本类型或String类型在被static final修饰时,编译器会将指令直接优化成ldc指令,从而在只获取该属性的值时,不需要初始化它所在的类;而当被static final修饰的属性为引用类型时,由于编译器无法直接将其转化为常量,故在获取该类型的属性时还是需要初始化类的。

<think> 首先,用户的问题是关于Java成员内部属性是否可以用static修饰以及原因。我需要基于提供的引用内容来回答。 回顾引用内容: - 引用[1]:明确指出成员内部能含有static成员(包括属性和方法)。只有内部本身可以使用static修饰修饰。 - 引用[2]:解释了为什么普通内部能定义static修饰的方法,因为static成员是成员,依赖实例,而普通成员需要实例。 - 引用[3]:详细说明成员内部为什么能定义静态属性和静态方法。核心点是成员内部的实例必须先有外部的实例,而static变量在加载初始化,这违反了成员内部的依赖关系。 - 引用[4]:讨论了成员内部能存在任何static的方法和变量,提供了jvm加载的角度,但指出有些矛盾,最终归结为设计选择。 现在,用户的问题是:成员内部属性是否能使用static修饰及原因。 关键点: - 成员内部(也叫非静态内部)是定义在外部内部但没有static修饰的内部。 - 根据引用,成员内部能有static成员。 回答结构: 1. **明确回答**:成员内部属性能用static修饰。 2. **解释原因**:基于引用内容解释原因。 - 依赖关系:成员内部的实例必须绑定到一个外部的实例上。static成员独立于实例,在加载初始化,但成员内部本身依赖于外部的实例存在。 - 加载顺序:static成员在加载初始化,而成员内部在没有外部实例能被加载或实例化。 - 设计原因:Java语言规范允许,以避免逻辑矛盾。 具体原因: - 引用[3]:成员内部的对象必须先有外部的对象才能创建。static变量在加载初始化,而此成员内部还没有实例化,导致矛盾。 - 引用[4]:虽然有些解释矛盾,但最终指出是语言设计选择。 额外点: - 成员内部可以有静态常量:引用[4]提到,如果属性是基本数据型或String,并且同finalstatic修饰(即静态常量),是允许的。这是因为常量在编译确定,需要运行初始化。 - "属性使用static的前提有两点:1)该属性是基本数据型或者String;2)该属性必须被final + static修饰;" - 所以,成员内部可以有static final常量。 回答要求: - 使用中文。 - 结构清晰。 - 生成相关问题(根据系统指令)。 - 添加引用标识。 生成相关问题:基于主题,生成几个相关问题。例如: 1. 成员内部和静态内部的区别是什么? 2. 如何在成员内部中定义常量? 3. 为什么Java这样设计成员内部? 确保回答真实可靠:基于提供的引用,综合回答。 最终回答结构: - 先直接回答问题。 - 详细解释原因。 - 如果有例外(如常量),提到。 - 最后生成相关问题。 </think> ### 成员内部属性是否能用static修饰及原因 在Java中,**成员内部(非静态内部)的普通属性能用static修饰**。这是由于成员内部的本质特性和Java语言的设计规范决定的。只有一种特殊情况例外:**属性可以被`static`和`final`同修饰(即静态常量),但这仅限于基本数据型或String型**。下面我将逐步解释原因,并基于底层机制进行分析。 #### 1. **为什么普通属性能用static修饰?** - **依赖关系冲突**:成员内部是定义在外部内部的非静态(即没有`static`修饰符),它的实例必须绑定到一个外部的实例上。这意味着,创建成员内部的对象前,必须先创建外部的对象。而`static`属性(或方法)是级别的成员,它们在加载就被初始化,独立于任何实例。如果成员内部允许有`static`属性,会导致逻辑矛盾: - static属性需要在成员内部加载初始化。 - 但成员内部本身依赖于外部实例才能加载(因为它包含了对外部实例的隐式引用)。 - 如果没有外部实例,成员内部无法被正确加载,static属性也就无法初始化。这违反了成员内部的实例绑定规则,可能导致未定义行为或编译错误[^3][^4]。 - **加载顺序问题**:Java虚拟机(JVM)的加载顺序是: - 外部加载(但未实例化)。 - 静态成员(static域)初始化(先执行)。 - 实例创建后,非静态成员(如成员内部)才被加载和初始化。 如果成员内部static属性,它的初始化必须在成员内部加载完成,但这个加载过程依赖于外部的实例化。这会造成循环依赖:static属性需要优先初始化,但成员内部又需要外部实例才能存在。结果就是编译器禁止这种语法,以避免运行错误[^2][^3]。 - **语言设计限制**:Java规范明确禁止成员内部拥有非finalstatic成员(属性和方法)。这是为了避免语义混乱。例如,如果static属性存在,它理论上可以被独立访问(如`InnerClass.staticVar`),但成员内部的定义要求所有访问都必须通过外部实例。这种设计确保了内部的封装性和一致性[^1][^4]。 #### 2. **例外情况:static final常量** - 成员内部允许声明**static final常量**(例如`public static final int MAX_SIZE = 100;`),但必须满足两个条件: - 属性是基本数据型(如`int`、`double`)或`String`。 - 属性被`static`和`final`同修饰。 - **允许的原因**: - `final`确保属性可变的常量,在编译就能确定值(编译器会直接内联替换)。 - 这避免了运行初始化依赖,因为常量的值在加载前就已固定,涉及外部实例的绑定问题。 - 例如,在成员内部中定义`public static final String TAG = "DEBUG";`是合法的,因为它本质上是一个编译期常量,会破坏内部的实例模型[^4]。 #### 3. **示例说明** - 错误示例:成员内部能有普通static属性。 ```java public class OuterClass { // 成员内部(非静态) public class InnerClass { // 编译错误:成员内部能有static属性 static int count = 0; // 非法! } } ``` 编译会报错:`Inner classes cannot have static declarations`。 - 正确示例:成员内部中可以定义static final常量。 ```java public class OuterClass { public class InnerClass { // 合法:static final常量(基本型或String) public static final int MAX_VALUE = 100; // 基本数据型 public static final String NAME = "Inner"; // String型 } } ``` #### 4. **根本原因总结** - **核心问题**:成员内部的实例生命周期绑定到外部实例上,而static成员要求独立于实例存在。这种设计冲突使得非finalstatic属性无法在成员内部中使用。Java通过语法限制来保证型安全,避免运行错误[^3]. - **设计哲学**:这反映了Java的封装原则——成员内部用于封装与外部紧密耦合的逻辑,而静态内部(用`static`修饰的内部)则用于独立场景。如果需要static成员,应该使用静态内部[^1][^4]。 如果您有其他具体场景的问题,欢迎进一步讨论! ### 相关问题 1. 成员内部和静态内部的主要区别是什么? 2. 在成员内部中如何正确定义和使用常量? 3. 为什么Java允许静态内部拥有static成员,而成员内部能? 4. 成员内部的实例化过程是怎样的?
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值