变量

Java中没有初始化的变量是不能直接使用的

局部变量

String msg;
System.out.print(msg);

就会提示错误,我们必须显式的为变量指定一个初值如null。刚开始学Java的时候写出过这样的代码:

Scanner scan = new Scanner(System.in);
String msg;
int x = scan.nextInt();
if (x > 0) {
msg = "positive";
} else if (x < 0) {
msg = "negative";
} else if (x == 0) {
msg = "zero";
}
System.out.print(msg);

乍一看没什么问题,但这个片段放到一般的main方法里是编译不过的。会提示变量msg可能未初始化。虽然我们在if-else语句中已经把可能x取值都考虑完整了,但是编译器不能进行这样一个智能的检测,如果没有最后的else字句,它任务这个if-else语句中的所有字句都可能不会进入。这样的情况下有没有额外的语句对msg进行初始化,那么在最后输出的时候msg可能就是处于未初始化的状态。

类成员变量

类成员变量和局部变量不同,它们可以不进行显式的初始化而直接使用。因为类实例创建时首先会进行一个零值初始化(数值变量都为0值,引用变量都为null,boolean为false)。因而可以像如下这样使用:

class Box {
public int width;
public int height;
public String name;
public Box() {}
public Box(String name) {this.name = name} public static void main(String[] args) {
Box b = new Box();
System.out.println(b.width);
System.out.println(b.height);
System.out.println(b.name);
}
}

以上会输出:

0
0
null

成员变量初始化顺序

初始化顺序以声明顺序为准,构造函数后于(不论是实例还是静态的)初始化块执行。基类初始化先于子类进行。一般来说按照这样的规则分析都可以顺利的推断出成员变量初始化的结果。但是有一些奇葩的情况需要注意,如初始化块中对后面才声明的变量进行赋值操作

public class InitVar {

	{
x = 3;
} int x = 2; public static void main(String[] args) {
InitVar v = new InitVar(); System.out.println(v.x);
} }

上例中会输出:

2

当我们调换初始化块和声明语句的位置时输出为3,不过需要注意当初始化块中使用的变量比声明要靠前时,只能对其进行赋值操作而不能进行变量值读取操作。如下情况是不允许的:

public class InitVar {
{
System.out.println(x);
} int x = 2; public static void main(String[] args) { }
}

将会报

InitVar.java:5: error: illegal forward reference

System.out.println(x);

^

这样约束是为了防止循环初始化即读取b的值来初始化a但是b又在a后才声明。而声明b的地方又用b来初始化a。等同于下面的情况:

public class InitVar {
int x = y;
int y = x; public static void main(String[] args) {}
}

字符串

字符串初始化可以使用字面常量形式直接初始化,也可以使用new搞出一个对象来

String a = "a word";
String b = new String("b word");

常量池

字符串变量一个很特殊的地方就是它的字面常量一般会进入常量池中。那么它们是什么时候进入常量池的呢?并不是在执行那条语句的时候,而是包含该语句(含有字面字符串常量)的类文件加载的时候就已经加入常量池了。这些常量在Java类文件中有专门的区段进行存储,可以通过命令行javap -v进行查看(编译后的.class文件),其中的Constant Pool一段就是其中包含的常量值。比如如下的Java代码

public class StringConstant {
public static void main(String[] args) {
String a = "a word";
String b = new String("b word");
System.out.println(a);
System.out.println(b);
}
}

编译后执行javap -v StringConstant.class查看其类文件内容截取如下

Constant pool:
#1 = Methodref #9.#18 // java/lang/Object."<init>":()V
#2 = String #19 // a word
#3 = Class #20 // java/lang/String
#4 = String #21 // b word

main方法的字节码如下:

  public static void main(java.lang.String[]);
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=3, locals=3, args_size=1
0: ldc #2 // String a word
2: astore_1
3: new #3 // class java/lang/String
6: dup
7: ldc #4 // String b word
9: invokespecial #5 // Method java/lang/String."<init>":(Ljava/lang/String;)V
12: astore_2
13: getstatic #6 // Field java/lang/System.out:Ljava/io/PrintStream;
16: aload_1
17: invokevirtual #7 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
20: getstatic #6 // Field java/lang/System.out:Ljava/io/PrintStream;
23: aload_2
24: invokevirtual #7 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
27: return

ldc表示从常量池中取出常量引用放入操作数栈顶,astore_1表示把当前操作数栈顶的值取出并存入本地变量区域的1号位置中,即完成了String a = "a word"的执行过程。后续的语句是用于new出一个初始的String对象,然后把常量池中的字符串作为构造函数的参数调用构造函数对初始的String对象进行构造。由于invokespecial(用于调用构造函数)和astore_2都要消耗操作数栈上的String对象引用值,因此在new出String对象后调用了dup指令复制了一份在操作数栈上。

由此可见字符常量并非在new时才加入常量池中,而是在类加载时(不过类具有延时加载机制,可能要到第一次真正使用类时才会去解析类文件中的常量并加入常量池)。

String.intern()方法

String 的intern方法可以手工的把程序中通过拼接得到的字符串加入常量池(直接使用常量初始化或者常量定义的话该字面常量就已经存在于常量池中了)。那么String.intern()方法除了炫技之外有什么其他用途呢?应该是用在可能会产生大量重复字符串对象且这些对象还会长期存在的情况下,比如要在内存中记录100w册的图书以供长期查询,然后其中的出版社名称是通过某种方式动态提取生成(比如从其他的RPC接口反序列化得到的),那么虽然有许多出版社名称是一样的对于hashmap之类的使用不造成丝毫影响,但是反序列化时都是动态new出String对象,就造成了资源的浪费(原本可以使用常量池的一个对象,现在有许多重复对象)。此时应该使用字符串的intern方法进行检测,使用一致的字符串对象。

当然一般常量池所在的空间都比较小,如果大量对一些短生命周期的字符串使用intern操作是不明智的。

另外intern方法在1.7中与1.6中过程是不同的,1.7会将调用intern的对象引用放入常量池(如果当前没有),而1.6则会复制一份并将其拷贝的引用放入(参见深入理解JVM)。

类间常量引用

当一个类引用另一个类中的常量时会把常量值之间复制过来,当做一种优化手段这在C++里也是存在的。如果A依赖B,而B修改了常量值,A没有进行更新编译那么它使用的任然是老的。可以通过如下代码进行说明,设有两个文件Box.java和Limits.java,都在默认包下

Limits.java

public class Limits {
public static final int MAX_WIDTH = 10000;
}

Box.java

public class Box {
public static void main(String[] args) {
int width = 1000;
if (width < Limits.MAX_WIDTH) {
System.out.println("valid");
} else {
System.out.println("invalid");
}
}
}

那么当第一次Limits.java、Box.java编译后,如果再仅仅修改Limits.java调整MAX_WIDTH的值然后运行java Box并不能使结果有任何改变。使用javap看Box.class文件即可,其中100就是原来Limits.MAX_WIDTH的值,以直接量的形式包含在了代码中。

 public static void main(java.lang.String[]);
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=2, locals=2, args_size=1
0: sipush 1000
3: istore_1
4: iload_1
5: bipush 100
7: if_icmpge 21
...

虽然当跟新一些部署应用时需要要考虑到,有可能仅仅更新依赖jar主程序中的常量并没有改变,必须重新编译主程序。

Java 基础:变量 与 字符串的更多相关文章

  1. Java基础-变量的定义以及作用域详解

    Java基础-变量的定义以及作用域详解 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 一.字面量 常量(字面量)表示不能改变的数值(程序中直接出现的值).字面量有时也称为直接量,包 ...

  2. Java基础-处理json字符串解析案例

    Java基础-处理json字符串解析案例 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 作为一名开发人员,想必大家或多或少都有接触到XML文件,XML全称为“extensible ...

  3. Java 基础 变量和运算符

    Java基础语法   第1章 变量 1.1 变量概述 1.2 计算机存储单元 1.3 基本类型之4类8种 1.4 常量与类型 1.5 定义变量(创建变量) 1.6 变量使用的注意事项 1.7 数据类型 ...

  4. JAVA基础——变量和常量

    JAVA的变量和常量知识总结 一.认识java标识符 标识符就是用于给 Java 程序中变量.类.方法等命名的符号. 使用标识符时,需要遵守几条规则: 1.  标识符可以由字母.数字.下划线(_).美 ...

  5. Java基础 - 变量转换

    在java中变量转发分为两种,隐式转换和强制转换 隐式转换: byte a = 10; int b = 20; byte c = a + b; // 该方法会报错,转换过程中字节数只能从小变大,不能从 ...

  6. Java基础——变量、数据类型

    一 .变量 1.计算机的内存类似于人的大脑,计算机使用内存来记忆大量运算时要使用数据.内存是一个物理设备,如何来存储一个数据呢?很简单,把内存想象成一间旅馆,要存储的数据就好比要住宿的客人. 首先,旅 ...

  7. Java 基础 变量介绍

    变量的声明和使用 概念: 变量是指内存中的一个存储区域,该区域要有自己的名称(变量名).类型(数据类型),该区域的数据可以在同一数据类型的范围内不断变化值: 变量的使用注意事项: Java中的变量必须 ...

  8. Java基础-变量常量

    变量 内存中的一小块区域,需要变量名来访问 变量的命名: 变量类型 变量名=变量值 例:String stuName= "wangwei"; java中的所有标点符号都是英文的 变 ...

  9. java基础18 String字符串和Object类(以及“equals” 和 “==”的解析)

    一.String字符串 问:笔试题:new String("abc")创建了几个对象?答:两个对象,一个对象是 位于堆内存,一个对象位于字符串常量池 class Demo17 { ...

  10. Java基础 变量和数据类型及相关操作

    Java基本语法: 1):Java语言严格区分大小写,好比main和Main是完全不同的概念. 2):一个Java源文件里可以定义多个Java类,但其中最多只能有一个类被定义成public类.若源文件 ...

随机推荐

  1. win7 docker 挂载共享目录

    在 win7 下用 docker 不像 win10 那样方便,安装包都不一样. 在 win7 下共享一个目录的方法如下: 1. 先设置 win7 到 VirtualBox 中 docker 用的那个虚 ...

  2. Git-管理和撤销修改

    一.管理修改 为什么说Git管理的是修改,而不是文件呢?我们还是做实验.第一步,对readme.txt做一个修改,比如加一行内容: Git is a distributed version contr ...

  3. 从工程化角度讨论如何快速构建可靠React组件

    前言 React 的开发也已经有2年时间了,先从QQ的家校群,转成做互动直播,主要是花样直播这一块.切换过来的时候,业务非常繁忙,接手过来的业务比较凌乱,也没有任何组件复用可言. 为了提高开发效率,去 ...

  4. 跟踪spring MVC的请求

    当我们点击一个超链接时,spring MVC在后台都做了些什么呢,今天就来看看后台都干了啥 首先需要在web.xml里配置一下:

  5. D3.js(v3)+react框架 基础部分之数据绑定及其工作过程与绑定顺序

    数据绑定: 将数据绑定到Dom上,是D3最大的特色.d3.select和d3.selectAll返回的元素的选择集.选择集上是没有数据的. 数据绑定就是使被选择元素里“含有”数据. 相关函数有两个: ...

  6. Smart/400开发上手4: 调试Cobol代码 (DEBUG with QBATCH)

    Step1:Compile Cobol source CB TIM07 using *SRCDBG option例如:CB MEMBER(TIM07) OPTION(*SRCDBG) WORKU(TI ...

  7. d3.js在vue项目中的安装及案例

    1. 安装: npm i d3 --save 2. 引入:main.js import * as d3 from "d3"; Vue.prototype.$d3 = d3; win ...

  8. redis缓存存在的隐患及其解决方案

    redis缓存1.缓存穿透 1>.什么是缓存穿透? 业务系统需要查训的数据根本不存在,当业务系统查询时, 首先会在缓存中查训,由于缓存中不存在,然后在往数据 库中查,由于该数据在数据库中也不存在 ...

  9. B2C电商项目

    经历四个月的自学. 结合所学的知识(HTML,CSS,javascript,jQuery,Mysql,Redis,Django,celery,fastDfs,haystack,whoosh,uWSGI ...

  10. [Umbraco] xslt语言介绍及与umbraco的关系

    XSLT是扩展样式表转换语言(Extensible Stylesheet Language Transformations)的简称,这是一种对XML文档进行转化的语言,XSLT中的T代表英语中的“转换 ...