变量

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. SSH 学习笔记

    零.背景 在看 pm2 的 deploy 功能的时候,对 ssh 的不熟悉导致错误频出,包括之前对 github 的配置也用到了 SSH,所以找个机会整理一下. 一.介绍 SSH 是每一台 Linux ...

  2. 微信小程序实现给循环列表点击添加类(单项和多项)

    在微信小程序里面没有DOM对象, 不能操作DOM. 所有的操作通过数据来实现,下面主要实现了给循环列表点击添加类的操作 一.单项 目标需求:实现下图,给点击的view增加类,每次只能选择一个. 主要思 ...

  3. 关于SQL的常用操作(增、删、改、查)

    关于SQL的常见操作主要是增.删.改.查. 1.增,顾名思义就是新增数据(insert into).该语句用于向表中插入新纪录.insert into有两种用法. (1).无需指定要插入数据的列名,只 ...

  4. 【并发】1、关于线程的几种状态&关于yield的理解

    最近在看disruptor源码,在获取ringbuffer的下一个序列的时候,disruptor有几种等待策略,其中有YieldingWaitStrategy类,是使用java的Thread.yiel ...

  5. POJ 2665

    #include<iostream> #include<stdio.h> using namespace std; int main() { //freopen("a ...

  6. POJ 2491

    #include<iostream>#include<stdio.h>#include<string>#define MAXN 400using namespace ...

  7. Vue2.5开发去哪儿网App 城市列表开发之 兄弟组件间联动及列表性能优化

    一,  兄弟组件间联动 1.  点击城市字母,左侧对应显示 给遍历的 字母 添加一个点击事件: Alphabet.vue @click="handleLetterClick" ha ...

  8. 剑指offer十八之二叉树的镜像

    一.题目 操作给定的二叉树,将其变换为源二叉树的镜像.二叉树的镜像定义:        源二叉树 : 8 / \ 6 10 / \ / \ 5 7 9 11 镜像二叉树: 8 / \ 10 6 / \ ...

  9. Identity Server4学习系列三

    1.简介 在Identity Server4学习系列一和Identity Server4学习系列二之令牌(Token)的概念的基础上,了解了Identity Server4的由来,以及令牌的相关知识, ...

  10. Anaconda 科学计算环境与包的管理

    相信大多数 python 的初学者们都曾为开发环境问题折腾了很久,包管理和 python 不同版本的问题,特别是 window 环境安装个 scrapy 各种报错 ,使用 Anaconda 可以很好的 ...