上次通过上次上线时咱遇到的常量编译问题,下边有兴趣的话可以看看。
由于
java
interface
中声明的字段在编译时会自动加上
static final
的修饰符,即声明为常量。因而
interface
通常是存放常量的最佳地点。然而在
java
的实际应用时却会产生一些问题。
由于
java
interface
中声明的字段在编译时会自动加上
static final
的修饰符,即声明为常量。因而
interface
通常是存放常量的最佳地点。然而在
java
的实际应用时却会产生一些问题。
问题的起因有两个,第一,是我们所使用的常量并不是一成不变的,而是相对于变量不能赋值改变。例如我们在一个工程初期定义
常量
∏
=
3.14
,而由于计算精度的提高我们可能会重新定义
∏
=
3.14159
,此时整个项目对此常量的引用都应该做出改变。第二,
java
是动态语言。
与
c++
之类的静态语言不同
,java
对一些字段的引用可以在运行期动态进行,这种灵活性是
java
这样的动态语言的一大优势。也就使得我们在
java
工
程中有时部分内容的改变不用重新编译整个项目,而只需编译改变的部分重新发布就可以改变整个应用。
有一个
interface
A
,一个
class
B
,代码如下:
//file
A.java
public interface
A{
String name =
"bright";
}
//file
B.java
public class
B{
public static void
main(String[] args){
System.out.println("Class A's name = " + A.name);
}
}
|
够简单吧,好,编译
A.java
和
B.java
。
运行,输入
java
B
,显然结果如下:
我们现在修改
A.java
如下:
//file
A.java
public interface
A{
String name = "bright
sea";
}
|
编译
A.java
后重新运行
B
class
,输入
java
B
,注意:结果如下
为什么不是
"Class A's
name = bright sea"
?让我们使用
jdk
提供的反编译工具
javap
反编译
B.class
看个究竟,输入:
javap -c B
,结果如下:
Compiled from
B.java
public class B extends
java.lang.Object {
public
B();
public static void
main(java.lang.String[]);
}
Method
B()
0
aload_0
1 invokespecial #1 <Method
java.lang.Object()>
4
return
Method void
main(java.lang.String[])
0 getstatic #2 <Field
java.io.PrintStream out>
3 ldc #3 <String "Class A's
name = bright">
5 invokevirtual #4 <Method void
println(java.lang.String)>
8
return
|
注意到标号
3
的代码了吗?由于引用了一个
static final
的字段,编译器已经将
interface
A
中
name
的内容编译进了
class B
中,而不是对
interface A
中的
name
的引用。因此除非我们重新编译
class B
,
interface A
中
name
发生的变化无法在
class B
中反映。如果这样去做那么
java
的动态优势就消失殆尽。
解决方案,有两种解决方法。
第一种方法是不再使用常量,将所需字段放入
class
中声明,并去掉
final
修饰符。但这种方法存在一定的风险,由于不再是常量着因而在系统运行时有可能被其他类修改其值而发生错误,也就违背了我们设置它为常量的初衷,因而不推荐使用。
第二种方法,将常量放入
class
中声明,使用
class
方法来得到此常量的值。为了保持对此常量引用的简单性,我们可以使用一个静态方法。我们将
A.java
和
B.java
修改如下:
//file
A.java
public class
A{
private static final String
name = "bright";
public static String
getName(){
return
name;
}
}
//file
B.java
public class
B{
public static void
main(String[] args){
System.out.println("Class A's name = " +
A.getName());
}
}
|
同样我们编译
A.java
和
B.java
。运行
class B
,输入
java B
,显然结果如下:
Class A's name =
bright
现在我们修改
A.java
如下:
//file
A.java
public class
A{
private static final String
name = "bright";
public static String
getName(){
return
name;
}
}
|
我们再次编译
A.java
后重新运行
B
class
,输入
java
B
:结果如下
Class A's name = bright
sea
|
终于得到了我们想要的结果,我们可以再次反编译
B.class
看看
class B
的改变,输入:
javap -c B,
结果如下:
Compiled from
B.java
public class B extends
java.lang.Object {
public
B();
public static void
main(java.lang.String[]);
}
Method
B()
0
aload_0
1 invokespecial #1 <Method
java.lang.Object()>
4
return
Method void
main(java.lang.String[])
0 getstatic #2 <Field
java.io.PrintStream out>
3 new #3 <Class
java.lang.StringBuffer>
6 dup
7 invokespecial #4 <Method
java.lang.StringBuffer()>
10 ldc #5 <String "Class A's
name = ">
12 invokevirtual #6 <Method
java.lang.StringBuffer append(java.lang.String)>
15 invokestatic #7 <Method
java.lang.String getName()>
18 invokevirtual #6 <Method
java.lang.StringBuffer append(java.lang.String)>
21 invokevirtual #8 <Method
java.lang.String toString()>
24 invokevirtual #9 <Method void
println(java.lang.String)>
27
return
|
注意标号
10
至
15
行的代码,
class B
中已经变为对
A
class
的
getName()
方法的引用,当常量
name
的值改变时我们只需对
class
A
中的常量做修改并重新编译,无需编译整个项目工程我们就能改变整个应用对此常量的引用,即保持了
java
动态优势又保持了我们使用常量的初衷,因而方法
二是一个最佳解决方案。
分享到:
相关推荐
关于字符串常量池中存储的是引用还是对象的问题,实际上,Java的字符串常量池存放的是对象的引用,而不是对象本身。对象仍然在堆内存中创建。这一点可以通过实验验证,例如通过读取大文件并执行`intern()`操作,观察...
这个压缩包包含的"Java核心技术源代码"涵盖了多个章节,从基础到高级,帮助我们深入理解Java的内部机制和最佳实践。以下是对这些章节内容的详细解读: 1. **v1ch10**:这可能是关于"Java集合框架"的章节,包括...
在Java中,算法涉及到数据结构、排序和搜索等,而设计模式如单例、工厂、观察者、装饰器、适配器等则是解决常见问题的最佳实践,能够提高代码的可读性和可维护性。 总之,“JAVA核心面试知识梳理大全”是一份全面的...
### JavaClass反编译原理深度解析 #### 引言 在IT领域,特别是软件开发行业中,反编译技术是一项至关重要的技能,它不仅能够帮助开发者理解其他程序的内部逻辑,还能用于逆向工程、安全审计及版权分析等多个方面。...
《深入理解Java虚拟机:JVM高级特性与最佳实践》是周志明先生的著作,这本书深入剖析了JVM的工作原理和优化策略,是Java开发者的重要参考资料。 1. **JVM结构与工作原理**: JVM主要由类加载器、运行时数据区、...
1. **Heap Space与PermGen Space的区别**:Heap Space主要用于存储对象实例,而PermGen Space则主要存放类的信息、常量池等。两者虽然都是JVM内存管理的重要组成部分,但其用途不同,因此在配置时需要根据实际应用...
这份资料对于初学者来说是一份宝贵的资源,通过系统学习,不仅可以掌握JAVA编程的基本技能,还能逐步了解并熟悉JAVA开发的最佳实践。同时,对于有经验的开发者,回顾和巩固这些基础知识也是提升编程能力的有效途径。
- **永久代(Java 8之前的版本)/元空间(Java 8及以后的版本)**:用于存储类的元数据、常量池等信息。 #### 二、堆内存设置参数 在启动JVM时,可以通过一系列的命令行参数来控制堆内存的大小和行为,这些参数...
### JAVA面试题集知识点解析 #### 一、异常处理机制 **知识点概述:** 异常处理是Java编程语言中用于管理程序执行过程中可能出现的错误的重要机制。通过异常处理,程序员能够优雅地处理各种运行时错误,避免程序...
通过阅读《Core Java 7th 卷2》,读者不仅可以掌握Java的高级特性和最佳实践,还能提升解决问题和设计复杂系统的能力。源码分析则可以帮助读者更好地理解和应用书中的示例,加深对Java编程的理解。
20. **Java集合类框架的最佳实践**:例如使用适当的集合类型来存储数据,使用泛型来减少类型转换等。 #### 第二章 Java基础 21. **“==”与“equals”区别**:“==”比较的是两个对象的引用,而equals()比较的是两...
### Java基础知识总结大全 ...通过以上总结,我们不仅对Java的基础知识有了更深的理解,还掌握了一些关键概念和最佳实践。这对于后续深入学习Android开发或其他基于Java的应用开发具有重要的意义。
Java性能调优,特别是关于垃圾回收机制的分析和指导,是优化Java应用程序的关键环节。Java的垃圾回收(Garbage Collection, GC)是自动管理内存的一种机制,它负责识别并清理那些不再使用的对象,以释放内存资源。...
- 遵守Java语言的最佳实践。 - 使用异常处理机制。 - 代码格式化,如缩进、空格等。 - **测试用例**: - 编写单元测试,确保代码质量。 - 测试覆盖率达标。 - 维护测试用例,确保有效性。 综上所述,用友JAVA...
- **方法区**:用于存放已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码缓存等数据。 #### 执行引擎 执行引擎负责解释字节码指令或执行预先编译好的本地代码。现代JVM采用混合模式,即解释执行与...
- 方法区:存放类的信息、静态变量、常量池等内容。 - **垃圾回收机制**: - Java的垃圾回收机制自动管理内存,减少了内存泄漏的风险。 - 主要有分代收集、复制算法、标记-整理算法等不同的垃圾回收策略。 ####...
### Java常用内存配置知识 #### 一、Java内存计算方式 在Java中,内存管理是其核心特性之一,尤其对于大型应用来说,合理的内存配置能够显著提升应用的性能和稳定性。Java内存主要划分为以下几个区域: 1. **堆区...
Java虚拟机(JVM)是Java程序运行的基础,它负责解释和执行字节码,提供了一个平台无关的运行环境。JVM性能优化是提升Java应用效率的关键环节,涉及到内存管理、垃圾回收、线程调度等多个方面。以下是对"Java JVM及...
学习Java编程,特别是对于从C/C++转向Java的开发者来说,可能会遇到一些挑战。本文将主要探讨以下几个方面:程序流程、...随着经验的积累,你将更深入地理解Java的特性和最佳实践,从而编写出更加高效、可维护的代码。
4. **常量存储**:用于存放不变的常量数据,通常在ROM中,对于嵌入式系统尤为重要。 5. **不可随机访问的存储**:包括流对象和持久化对象,这些数据存储于程序外部,不受程序控制,支持对象在不同媒介间的转换,如...