一:==和equals区别
在初学Java时,可能会经常碰到下面的代码:
1 String str1 = new String("hello");
2 String str2 = new String("hello");
3
4 System.out.println(str1==str2);
5 System.out.println(str1.equals(str2));
为什么第4行和第5行的输出结果不一样?==和equals方法之间的区别是什么?如果在初学Java的时候这个问题不弄清楚,就会导致自己在以后编写代码时出现一些低级的错误。今天就来一起了解一下==和equals方法的区别之处。
一.关系操作符“==”到底比较的是什么?
下面这个句话是摘自《Java编程思想》一书中的原话:
“关系操作符生成的是一个boolean结果,它们计算的是操作数的值之间的关系”。
这句话看似简单,理解起来还是需要细细体会的。说的简单点,==就是用来比较值是否相等。下面先看几个例子:
public class Main {
/**
* @param args
*/
public static void main(String[] args) {
// TODO Auto-generated method stub
int n=3;
int m=3;
System.out.println(n==m);
String str = new String("hello");
String str1 = new String("hello");
String str2 = new String("hello");
System.out.println(str1==str2);
str1 = str;
str2 = str;
System.out.println(str1==str2);
}
}
输出结果为 true false true
n==m结果为true,这个很容易理解,变量n和变量m存储的值都为3,肯定是相等的。而为什么str1和str2两次比较的结果不同?要理解这个其实只需要理解基本数据类型变量和非基本数据类型变量的区别。
在Java中游8种基本数据类型:
浮点型:float(4 byte), double(8 byte)
整型:byte(1 byte), short(2 byte), int(4 byte) , long(8 byte)
字符型: char(2 byte)
布尔型: boolean(JVM规范没有明确规定其所占的空间大小,仅规定其只能够取字面值"true"和"false")
对于这8种基本数据类型的变量,变量直接存储的是“值”,因此在用关系操作符==来进行比较时,比较的就是 “值” 本身。要注意浮点型和整型都是有符号类型的,而char是无符号类型的(char类型取值范围为0~2^16-1).
也就是说比如:
int n=3;
int m=3;
变量n和变量m都是直接存储的"3"这个数值,所以用==比较的时候结果是true。
而对于非基本数据类型的变量,在一些书籍中称作为 引用类型的变量。比如上面的str1就是引用类型的变量,引用类型的变量存储的并不是 “值”本身,而是于其关联的对象在内存中的地址。比如下面这行代码:
String str1;
这句话声明了一个引用类型的变量,此时它并没有和任何对象关联。
而 通过new String("hello")来产生一个对象(也称作为类String的一个实例),并将这个对象和str1进行绑定:
str1= new String("hello");
那么str1指向了一个对象(很多地方也把str1称作为对象的引用),此时变量str1中存储的是它指向的对象在内存中的存储地址,并不是“值”本身,也就是说并不是直接存储的字符串"hello"。这里面的引用和C/C++中的指针很类似。
因此在用==对str1和str2进行第一次比较时,得到的结果是false。因此它们分别指向的是不同的对象,也就是说它们实际存储的内存地址不同。
而在第二次比较时,都让str1和str2指向了str指向的对象,那么得到的结果毫无疑问是true。
二.equals比较的又是什么?
equals方法是基类Object中的方法,因此对于所有的继承于Object的类都会有该方法。为了更直观地理解equals方法的作用,直接看Object类中equals方法的实现。
该类的源码路径为:C:\Program Files\Java\jdk1.6.0_14的src.zip 的java.lang路径下的Object.java(视个人jdk安装路径而定)。
下面是Object类中equals方法的实现:
很显然,在Object类中,equals方法是用来比较两个对象的引用是否相等,即是否指向同一个对象。
但是有些朋友又会有疑问了,为什么下面一段代码的输出结果是true?
public class Main {
/**
* @param args
*/
public static void main(String[] args) {
// TODO Auto-generated method stub
String str1 = new String("hello");
String str2 = new String("hello");
System.out.println(str1.equals(str2));
}
}
要知道究竟,可以看一下String类的equals方法的具体实现,同样在该路径下,String.java为String类的实现。
下面是String类中equals方法的具体实现:
可以看出,String类对equals方法进行了重写,用来比较指向的字符串对象所存储的字符串是否相等。
其他的一些类诸如Double,Date,Integer等,都对equals方法进行了重写用来比较指向的对象所存储的内容是否相等。
总结来说:
对于==,如果作用于基本数据类型的变量,则直接比较其存储的 “值”是否相等;
如果作用于引用类型的变量,则比较的是所指向的对象的地址
对于equals方法,注意:equals方法不能作用于基本数据类型的变量
如果没有对equals方法进行重写,则比较的是引用类型的变量所指向的对象的地址;
诸如String、Date等类对equals方法进行了重写的话,比较的是所指向的对象的内容。
二:try catch finally return 执行顺序
1、try块中没有抛出异常,try、catch和finally块中都有return语句
return
运行代码:
1 public static void main(String[] args) {
2 System.out.println("=============NoException==================");
3 System.out.println(NoException());
4 System.out.println("===============================");
5 }
运行结果:
1 =============NoException==================
2 i in try block is:10
3 i in finally - from try or catch block is:9
4 8
5 ===============================
执行顺序:
执行try块,执行到return语句时,先执行return的语句,--i,但是不返回到main方法,执行finally块,遇到finally块中的return语句,执行--i,并将值返回到main方法,这里就不会再回去返回try块中计算得到的值。
结论:try-catch-finally都有return语句时,没有异常时,返回值是finally中的return返回的。
2.try块中没有抛出异常,仅try和catch中有return语句
return
运行结果:
1 =============NoException1==================
2 i in try block is:10
3 i in finally - from try or catch block is:9
4 i in finally block is:8
5 9
6 ===============================
执行顺序:
try中执行完return的语句后,不返回,执行finally块,finally块执行结束后,返回到try块中,返回i在try块中最后的值。
结论:try-catch都有return语句时,没有异常时,返回值是try中的return返回的。
3.try块中抛出异常,try、catch和finally中都有return语句
return
执行结果:
1 =============WithException==================
2 i in try block is:10
3 i in catch - form try block is:10
4 i in catch block is:9
5 i in finally - from try or catch block is--8
6 i in finally block is--7
7 6
8 ===============================
执行顺序:
抛出异常后,执行catch块,在catch块的return的--i执行完后,并不直接返回而是执行finally,因finally中有return语句,所以,执行,返回结果6。
结论:
try块中抛出异常,try、catch和finally中都有return语句,返回值是finally中的return,但是try,catch后的代码也要执行。
4.try块中抛出异常,try和catch中都有return语句
return
执行结果:
1 =============WithException1==================
2 i in try block is:10
3 i in catch - form try block is:10
4 i in finally - from try or catch block is:9
5 i in finally block is:8
6 9
7 ===============================
执行顺序:
抛出异常后,执行catch块,执行完finally语句后,依旧返回catch中的执行return语句后的值,而不是finally中修改的值。
结论:
返回的catch中return值。
5.try、catch中都出现异常,在finally中有返回
1 public static int WithException2(){
2 int i=10;
3 try{
4 System.out.println("i in try block is:"+i);
5 i=i/0;
6 return --i;
7 }
8 catch(Exception e){
9 System.out.println("i in catch - form try block is:"+i);
10 int j = i/0;
11 return --i;
12 }
13 finally{
14
15 System.out.println("i in finally - from try or catch block is:"+i);
16 --i;
17 --i;
18 System.out.println("i in finally block is:"+i);
19 return --i;
20 }
执行结果:
1 =============WithException2==================
2 i in try block is:10
3 i in catch - form try block is:10
4 i in finally - from try or catch block is:10
5 i in finally block is:8
6 7
7 ===============================
执行顺序:
try块中出现异常到catch,catch中出现异常到finally,finally中执行到return语句返回,不检查异常。
结论:
返回finally中return值。
6、只在函数最后出现return语句
1 public static int WithException3(){
2 int i=10;
3 try{
4 System.out.println("i in try block is:"+i);
5 i=i/0;
6 //return --i;
7 }
8 catch(Exception e){
9 System.out.println("i in catch - form try block is:"+i);
10 //int j = i/0;
11 //return --i;
12 }
13 finally{
14
15 System.out.println("i in finally - from try or catch block is:"+i);
16 --i;
17 --i;
18 System.out.println("i in finally block is:"+i);
19 //return --i;
20 }
21 return --i;
22 }
执行结果:
1 =============WithException3==================
2 i in try block is:10
3 i in catch - form try block is:10
4 i in finally - from try or catch block is:10
5 i in finally block is:8
6 7
7 ===============================
总体结论:
结论一:
return语句并不是函数的最终出口,如果有finally语句,这在return之后还会执行finally(return的值会暂存在栈里面,等待finally执行后再返回)
结论二:
finally里面不建议放return语句,根据需要,return语句可以放在try和catch里面和函数的最后。可行的做法有四:
(1)return语句只在函数最后出现一次。
(2)return语句仅在try和catch里面都出现。
(3)return语句仅在try和函数的最后都出现。
(4)return语句仅在catch和函数的最后都出现。
注意,除此之外的其他做法都是不可行的,编译器会报错。
Java基础知识之字符串
一、理解内存
了解字符串之前我们得先理解什么是内存?
通常我们所说的内存就是暂时存储程序以及数据的地方,包括随机存储器(RAM)(掉电丢失),只读存储器(ROM)(掉电不丢失),以及高速缓存(CACHE)(速度最快)。只不过因为RAM是其中最重要的存储器。
我们来看RAM组成:
寄存器:速度最快的存储场所,因为寄存器位于处理器内部,所以在程序中我们无法控制。
栈(Stack) :存放基本类型的对象和引用,但是对象本身不存放在栈中,而是存放在堆中。
Java中存在8大基本类型,他们的变量值中存放的就是具体的数值,而其他的类型都叫做引用类型(对象也是引用类型,你只要记住除了基本类型,都是引用类型)他们的变量值中存放的是他们在堆中的引用(内存地址)。
堆(Heap):在堆上分配内存的过程称作 内存动态分配过程。在java中堆用于存放由new创建的对象和数组。堆中分配的内存
静态存储区/方法区(Static Field):是指在固定的位置上存放应用程序运行时一直存在的数据,java在内存中专门划分了一个静态存储区域来管理一些特殊的数据变量如静态的数据变量。
常量池(Constant Pool):顾名思义专门存放常量的。常量池就是该类型所有用到地常量的一个有序集合包括直接常量(基本类型,String)和对其他类型、字段和方法的符号引用。,用于存放编译期生成的各种字面量和符号引用,这部分内容将在类加载后存放到常量池中。
Java语言并不要求常量一定只能在编译期产生,运行期间也可能产生新的常量,这些常量被放在运行时常量池中。这里所说的常量包括:基本类型包装类(包装类不管理浮点型(浮点型在堆里面,因为Double,Float 是new生成的),整形只会管理-128到127(范围之外也是new出来的))和String(也可以通过String.intern()方法可以强制将String放入常量池)。
总结:
定义一个局部变量的时候,java虚拟机就会在栈中为其分配内存空间,局部变量的基本数据类型和引用存储于栈中,引用的对象实体存储于堆中。因为它们属于方法中的变量,生命周期随方法而结束。
成员变量全部存储与堆中(包括基本数据类型,引用和引用的对象实体),因为它们属于类,类对象终究是要被new出来使用的。当堆中对象的作用域结束的时候,这部分内存也不会立刻被回收,而是等待系统GC进行回收。
二、字符串常量池
我们知道字符串的分配和其他对象分配一样,是需要消耗高昂的时间和空间的,而且字符串我们使用的非常多。JVM为了提高性能和减少内存的开销,在实例化字符串的时候进行了一些优化:使用字符串常量池。每当我们创建字符串常量时,JVM会首先检查字符串常量池,如果该字符串已经存在常量池中,那么就直接返回常量池中的实例引用。如果字符串不存在常量池中,就会实例化该字符串并且将其放到常量池中。
优缺点
字符串常量池的好处就是减少相同内容字符串的创建,节省内存空间。
如果硬要说弊端的话,就是牺牲了CPU计算时间来换空间。CPU计算时间主要用于在字符串常量池中查找是否有内容相同对象的引用。不过其内部实现为HashTable,所以计算成本较低。
创建字符串的两种方式:
字面量形式:如:String s = “hello”;
JVM检测这个字面量,如果JVM通过字符串常量池查找不到内容为hello的字符串对象存在,那么会创建这个字符串对象,然后将刚创建的对象的引用放入到字符串常量池中,并且将引用返回给变量s。如果发现内容为“hello”的字符串存在字符串常量池中,那么直接将已经存在的字符串引用返回给变量s。
使用new创建:如:String s = new String(“hello”);
new创建字符串时首先查看池中是否有相同值的字符串,如果有,则拷贝一份到堆中,然后返回堆中的地址;如果池中没有,则在堆中创建一份,然后返回堆中的地址(注意,此时不需要从堆中复制到池中,否则导致浪费池的空间)
四、字符串实战
实例1:
String str = "ABC";
String str1 = new String("ABC");
System.out.println(str == str1); //false
System.out.println(str.equals(str1)); //true
分析:第一句:创建一个常量,放于字符串常量池中。
第二句:创建一个对象,将字符串常量池中的”ABC”赋值到堆中。
第三句:两个对象不是同一个对象,所以输出false。
第四句:因String的equals方法重写过,两个对象的内容相等,所以true。
实例2:
String str1 = "123";
System.out.println("123" == str1.substring(0)); //true
System.out.println("23" == str1.substring(1)); //false
补充:substring源码:
public String substring(int start) {
if (start == 0) {
return this;
}
if (start >= 0 && start <= count) {
return fastSubstring(start, count - start);
}
throw indexAndLength(start);
}
分析:第二句:由substring源码可知,如果start==0,就返回当前对象,所以为true
第三句:由substring源码可知,如果start不等于0,则创建了新的对象,所以为false
实例3:
String str3 = new String("ijk");
String str4 = str3.substring(0);
System.out.println(str3 == str4); //true
System.out.println((new String("ijk") == str4)); //false
分析:第三句:两个相同的对象,输出true
第四句:新创建了一个对象,两个不同的对象,输出false
实例4
String str5 = "NPM";
String str6 = "npm".toUpperCase();
System.out.println(str5 == str6); //false
System.out.println(str5.equals(str6)); //true
补充:toUpperCase源码:
public String toUpperCase() {
return CaseMapper.toUpperCase(Locale.getDefault(), this, count);
}
public static String toUpperCase(Locale locale, String s, int count) {
String languageCode = locale.getLanguage();
if (languageCode.equals("tr") || languageCode.equals("az") || languageCode.equals("lt")) {
return ICU.toUpperCase(s, locale);
}
if (languageCode.equals("el")) {
return EL_UPPER.get().transliterate(s);
}
.......
.......
.......
.......
if (output == null) {
if (newString != null) {
return newString;
} else {
return s;
}
}
return output.length == i || output.length - i < 8 ? new String(0, i, output) : new
注意1:toUpperCase()和toLowerCase()只对英文字母有效,对除了A~Z和a~z的其余字符无任何效果
注意2:toUpperCase()和toLowerCase()都创建了新的对象。
分析:第三句:两个不同的对象,输出false
第四句:虽然对象不同但是内容相同,输出true
实例5:
String str9 = "a1";
String str10 = "a" + 1;
System.out.println(str9 == str10); //true
分析:当两个字符串常量连接时(相加)得到的新字符串依然是字符串常量且保存在常量池中。
实例6:
String str11 = "ab";
String str12 = "b";
String str13 = "a" + str12;
System.out.println(str11 == str13); //false
分析:当字符串常量与 String 类型变量连接时得到的新字符串不再保存在常量池中,而是在堆中新建一个 String 对象来存放,很明显常量池中要求的存放的是常量,有String类型变量当然不能存在常量池中了。str11 是字符串常量池中的对象,str13 是指向堆中的对象,不是同一个对象,所以输出false。
实例7:
String str14 = "ab";
final String str15 = "b";
String str16 = "a" + str15;
System.out.println(str14 == str16); //true
分析:字符串常量与 String 类型常量连接,得到的新字符串依然保存在常量池中。
实例8:
private static String getBB() {
return "b";
}
String str17 = "ab";
final String str18 = getBB();
String str19 = "a" + str18;
System.out.println(str17 == str19); //false
分析:final String str18 = getBB()其实与final String str18 = new String(“b”)是一样的,也就是说 return “b” 会在堆中创建一个 String 对象保存 ”b”,虽然 str18 被定义成了 final,所以可见看见,并非定义为 final 的就保存在常量池中,很明显此处 str18 常量引用的 String 对象保存在堆中,因为 getBB() 得到的 String 已经保存在堆中了,final 的 String 引用并不会改变 String 已经保存在堆中这个事实。
实例9:
String str20 = "ab";
String str21 = "a";
String str22 = "b";
String str23 = str21 + str22;
System.out.println(str23 == str20); //false
System.out.println(str23.intern() == str20); //true
System.out.println(str23 == str20.intern()); //false
System.out.println(str23.intern() == str20.intern()); //true
分析:而对于调用 intern 方法如果字符串常量池中已经包含一个等于此 String 对象的字符串(用 equals(Object) 方法确定)则返回字符串常量池中的字符串,否则将此 String 对象添加到字符串常量池中,并返回此 String 对象的引用,所以str23.intern() == str20实质是常量比较返回 true,str23 == str20.intern()中 str23 就是上面说的堆中新对象,相当于一个新对象和一个常量比较,所以返回 false,str23.intern() == str20.intern() 就没啥说的了,指定相等。
实例10:
String s1 = "abc";
StringBuffer s2 = new StringBuffer(s1);
System.out.println(s1.equals(s2)); //false
补充:
equals源码:
@Override public boolean equals(Object other) {
if (other == this) {
return true;
}
if (other instanceof String) {
String s = (String)other;
int count = this.count;
if (s.count != count) {
return false;
}
// TODO: we want to avoid many boundchecks in the loop below
// for long Strings until we have array equality intrinsic.
// Bad benchmarks just push .equals without first getting a
// hashCode hit (unlike real world use in a Hashtable). Filter
// out these long strings here. When we get the array equality
// intrinsic then remove this use of hashCode.
if (hashCode() != s.hashCode()) {
return false;
}
for (int i = 0; i < count; ++i) {
if (charAt(i) != s.charAt(i)) {
return false;
}
}
return true;
} else {
return false;
}
}
分析:instanceof运算符用法:左面的操作元是一个对象实例,右面是一个类.当 左面的对象是右面的类创建的对象时,该运算符运算的结果是true,否则是false(一个类的实例包括本身的实例,以及所有直接或间接子类的实例 )
我们再看StringBuffer 并不继承String类,所以到第五行判断不成立,直接返回了false。也就是equal比较的是相同类型的引用变量才行。
总结:要玩明白 Java String 对象的核心其实就是玩明白字符串的堆栈和常量池,虚拟机为每个被装载的类型维护一个常量池,常量池就是该类型所用常量的一个有序集合,包括直接常量(String、Integer 和 Floating Point常量)和对其他类型、字段和方法的符号引用,池中的数据项就像数组一样是通过索引访问的;由于常量池存储了相应类型所用到的所有类型、字段和方法的符号引用,所以它在 Java 程序的动态链接中起着核心的作用。
作者:你的雷哥
本文版权归作者所有,欢迎转载,但未经作者同意必须在文章页面给出原文连接,否则保留追究法律责任的权利。
1、try块中没有抛出异常,try、catch和finally块中都有return语句
return
运行代码:
1 public static void main(String[] args) {
2 System.out.println("=============NoException==================");
3 System.out.println(NoException());
4 System.out.println("===============================");
5 }
运行结果:
1 =============NoException==================
2 i in try block is:10
3 i in finally - from try or catch block is:9
4 8
5 ===============================
执行顺序:
执行try块,执行到return语句时,先执行return的语句,--i,但是不返回到main方法,执行finally块,遇到finally块中的return语句,执行--i,并将值返回到main方法,这里就不会再回去返回try块中计算得到的值。
结论:try-catch-finally都有return语句时,没有异常时,返回值是finally中的return返回的。
2.try块中没有抛出异常,仅try和catch中有return语句
return
运行结果:
1 =============NoException1==================
2 i in try block is:10
3 i in finally - from try or catch block is:9
4 i in finally block is:8
5 9
6 ===============================
执行顺序:
try中执行完return的语句后,不返回,执行finally块,finally块执行结束后,返回到try块中,返回i在try块中最后的值。
结论:try-catch都有return语句时,没有异常时,返回值是try中的return返回的。
3.try块中抛出异常,try、catch和finally中都有return语句
return
执行结果:
1 =============WithException==================
2 i in try block is:10
3 i in catch - form try block is:10
4 i in catch block is:9
5 i in finally - from try or catch block is--8
6 i in finally block is--7
7 6
8 ===============================
执行顺序:
抛出异常后,执行catch块,在catch块的return的--i执行完后,并不直接返回而是执行finally,因finally中有return语句,所以,执行,返回结果6。
结论:
try块中抛出异常,try、catch和finally中都有return语句,返回值是finally中的return,但是try,catch后的代码也要执行。
4.try块中抛出异常,try和catch中都有return语句
return
执行结果:
1 =============WithException1==================
2 i in try block is:10
3 i in catch - form try block is:10
4 i in finally - from try or catch block is:9
5 i in finally block is:8
6 9
7 ===============================
执行顺序:
抛出异常后,执行catch块,执行完finally语句后,依旧返回catch中的执行return语句后的值,而不是finally中修改的值。
结论:
返回的catch中return值。
5.try、catch中都出现异常,在finally中有返回
1 public static int WithException2(){
2 int i=10;
3 try{
4 System.out.println("i in try block is:"+i);
5 i=i/0;
6 return --i;
7 }
8 catch(Exception e){
9 System.out.println("i in catch - form try block is:"+i);
10 int j = i/0;
11 return --i;
12 }
13 finally{
14
15 System.out.println("i in finally - from try or catch block is:"+i);
16 --i;
17 --i;
18 System.out.println("i in finally block is:"+i);
19 return --i;
20 }
执行结果:
1 =============WithException2==================
2 i in try block is:10
3 i in catch - form try block is:10
4 i in finally - from try or catch block is:10
5 i in finally block is:8
6 7
7 ===============================
执行顺序:
try块中出现异常到catch,catch中出现异常到finally,finally中执行到return语句返回,不检查异常。
结论:
返回finally中return值。
6、只在函数最后出现return语句
1 public static int WithException3(){
2 int i=10;
3 try{
4 System.out.println("i in try block is:"+i);
5 i=i/0;
6 //return --i;
7 }
8 catch(Exception e){
9 System.out.println("i in catch - form try block is:"+i);
10 //int j = i/0;
11 //return --i;
12 }
13 finally{
14
15 System.out.println("i in finally - from try or catch block is:"+i);
16 --i;
17 --i;
18 System.out.println("i in finally block is:"+i);
19 //return --i;
20 }
21 return --i;
22 }
执行结果:
1 =============WithException3==================
2 i in try block is:10
3 i in catch - form try block is:10
4 i in finally - from try or catch block is:10
5 i in finally block is:8
6 7
7 ===============================
总体结论:
结论一:
return语句并不是函数的最终出口,如果有finally语句,这在return之后还会执行finally(return的值会暂存在栈里面,等待finally执行后再返回)
结论二:
finally里面不建议放return语句,根据需要,return语句可以放在try和catch里面和函数的最后。可行的做法有四:
(1)return语句只在函数最后出现一次。
(2)return语句仅在try和catch里面都出现。
(3)return语句仅在try和函数的最后都出现。
(4)return语句仅在catch和函数的最后都出现。
注意,除此之外的其他做法都是不可行的,编译器会报错。
Java基础知识之字符串
一、理解内存
了解字符串之前我们得先理解什么是内存?
通常我们所说的内存就是暂时存储程序以及数据的地方,包括随机存储器(RAM)(掉电丢失),只读存储器(ROM)(掉电不丢失),以及高速缓存(CACHE)(速度最快)。只不过因为RAM是其中最重要的存储器。
我们来看RAM组成:
寄存器:速度最快的存储场所,因为寄存器位于处理器内部,所以在程序中我们无法控制。
栈(Stack) :存放基本类型的对象和引用,但是对象本身不存放在栈中,而是存放在堆中。
Java中存在8大基本类型,他们的变量值中存放的就是具体的数值,而其他的类型都叫做引用类型(对象也是引用类型,你只要记住除了基本类型,都是引用类型)他们的变量值中存放的是他们在堆中的引用(内存地址)。
堆(Heap):在堆上分配内存的过程称作 内存动态分配过程。在java中堆用于存放由new创建的对象和数组。堆中分配的内存
静态存储区/方法区(Static Field):是指在固定的位置上存放应用程序运行时一直存在的数据,java在内存中专门划分了一个静态存储区域来管理一些特殊的数据变量如静态的数据变量。
常量池(Constant Pool):顾名思义专门存放常量的。常量池就是该类型所有用到地常量的一个有序集合包括直接常量(基本类型,String)和对其他类型、字段和方法的符号引用。,用于存放编译期生成的各种字面量和符号引用,这部分内容将在类加载后存放到常量池中。
Java语言并不要求常量一定只能在编译期产生,运行期间也可能产生新的常量,这些常量被放在运行时常量池中。这里所说的常量包括:基本类型包装类(包装类不管理浮点型(浮点型在堆里面,因为Double,Float 是new生成的),整形只会管理-128到127(范围之外也是new出来的))和String(也可以通过String.intern()方法可以强制将String放入常量池)。
总结:
定义一个局部变量的时候,java虚拟机就会在栈中为其分配内存空间,局部变量的基本数据类型和引用存储于栈中,引用的对象实体存储于堆中。因为它们属于方法中的变量,生命周期随方法而结束。
成员变量全部存储与堆中(包括基本数据类型,引用和引用的对象实体),因为它们属于类,类对象终究是要被new出来使用的。当堆中对象的作用域结束的时候,这部分内存也不会立刻被回收,而是等待系统GC进行回收。
二、字符串常量池
我们知道字符串的分配和其他对象分配一样,是需要消耗高昂的时间和空间的,而且字符串我们使用的非常多。JVM为了提高性能和减少内存的开销,在实例化字符串的时候进行了一些优化:使用字符串常量池。每当我们创建字符串常量时,JVM会首先检查字符串常量池,如果该字符串已经存在常量池中,那么就直接返回常量池中的实例引用。如果字符串不存在常量池中,就会实例化该字符串并且将其放到常量池中。
优缺点
字符串常量池的好处就是减少相同内容字符串的创建,节省内存空间。
如果硬要说弊端的话,就是牺牲了CPU计算时间来换空间。CPU计算时间主要用于在字符串常量池中查找是否有内容相同对象的引用。不过其内部实现为HashTable,所以计算成本较低。
创建字符串的两种方式:
字面量形式:如:String s = “hello”;
JVM检测这个字面量,如果JVM通过字符串常量池查找不到内容为hello的字符串对象存在,那么会创建这个字符串对象,然后将刚创建的对象的引用放入到字符串常量池中,并且将引用返回给变量s。如果发现内容为“hello”的字符串存在字符串常量池中,那么直接将已经存在的字符串引用返回给变量s。
使用new创建:如:String s = new String(“hello”);
new创建字符串时首先查看池中是否有相同值的字符串,如果有,则拷贝一份到堆中,然后返回堆中的地址;如果池中没有,则在堆中创建一份,然后返回堆中的地址(注意,此时不需要从堆中复制到池中,否则导致浪费池的空间)
四、字符串实战
实例1:
String str = "ABC";
String str1 = new String("ABC");
System.out.println(str == str1); //false
System.out.println(str.equals(str1)); //true
分析:第一句:创建一个常量,放于字符串常量池中。
第二句:创建一个对象,将字符串常量池中的”ABC”赋值到堆中。
第三句:两个对象不是同一个对象,所以输出false。
第四句:因String的equals方法重写过,两个对象的内容相等,所以true。
实例2:
String str1 = "123";
System.out.println("123" == str1.substring(0)); //true
System.out.println("23" == str1.substring(1)); //false
补充:substring源码:
public String substring(int start) {
if (start == 0) {
return this;
}
if (start >= 0 && start <= count) {
return fastSubstring(start, count - start);
}
throw indexAndLength(start);
}
分析:第二句:由substring源码可知,如果start==0,就返回当前对象,所以为true
第三句:由substring源码可知,如果start不等于0,则创建了新的对象,所以为false
实例3:
String str3 = new String("ijk");
String str4 = str3.substring(0);
System.out.println(str3 == str4); //true
System.out.println((new String("ijk") == str4)); //false
分析:第三句:两个相同的对象,输出true
第四句:新创建了一个对象,两个不同的对象,输出false
实例4
String str5 = "NPM";
String str6 = "npm".toUpperCase();
System.out.println(str5 == str6); //false
System.out.println(str5.equals(str6)); //true
补充:toUpperCase源码:
public String toUpperCase() {
return CaseMapper.toUpperCase(Locale.getDefault(), this, count);
}
public static String toUpperCase(Locale locale, String s, int count) {
String languageCode = locale.getLanguage();
if (languageCode.equals("tr") || languageCode.equals("az") || languageCode.equals("lt")) {
return ICU.toUpperCase(s, locale);
}
if (languageCode.equals("el")) {
return EL_UPPER.get().transliterate(s);
}
.......
.......
.......
.......
if (output == null) {
if (newString != null) {
return newString;
} else {
return s;
}
}
return output.length == i || output.length - i < 8 ? new String(0, i, output) : new
注意1:toUpperCase()和toLowerCase()只对英文字母有效,对除了A~Z和a~z的其余字符无任何效果
注意2:toUpperCase()和toLowerCase()都创建了新的对象。
分析:第三句:两个不同的对象,输出false
第四句:虽然对象不同但是内容相同,输出true
实例5:
String str9 = "a1";
String str10 = "a" + 1;
System.out.println(str9 == str10); //true
分析:当两个字符串常量连接时(相加)得到的新字符串依然是字符串常量且保存在常量池中。
实例6:
String str11 = "ab";
String str12 = "b";
String str13 = "a" + str12;
System.out.println(str11 == str13); //false
分析:当字符串常量与 String 类型变量连接时得到的新字符串不再保存在常量池中,而是在堆中新建一个 String 对象来存放,很明显常量池中要求的存放的是常量,有String类型变量当然不能存在常量池中了。str11 是字符串常量池中的对象,str13 是指向堆中的对象,不是同一个对象,所以输出false。
实例7:
String str14 = "ab";
final String str15 = "b";
String str16 = "a" + str15;
System.out.println(str14 == str16); //true
分析:字符串常量与 String 类型常量连接,得到的新字符串依然保存在常量池中。
实例8:
private static String getBB() {
return "b";
}
String str17 = "ab";
final String str18 = getBB();
String str19 = "a" + str18;
System.out.println(str17 == str19); //false
分析:final String str18 = getBB()其实与final String str18 = new String(“b”)是一样的,也就是说 return “b” 会在堆中创建一个 String 对象保存 ”b”,虽然 str18 被定义成了 final,所以可见看见,并非定义为 final 的就保存在常量池中,很明显此处 str18 常量引用的 String 对象保存在堆中,因为 getBB() 得到的 String 已经保存在堆中了,final 的 String 引用并不会改变 String 已经保存在堆中这个事实。
实例9:
String str20 = "ab";
String str21 = "a";
String str22 = "b";
String str23 = str21 + str22;
System.out.println(str23 == str20); //false
System.out.println(str23.intern() == str20); //true
System.out.println(str23 == str20.intern()); //false
System.out.println(str23.intern() == str20.intern()); //true
分析:而对于调用 intern 方法如果字符串常量池中已经包含一个等于此 String 对象的字符串(用 equals(Object) 方法确定)则返回字符串常量池中的字符串,否则将此 String 对象添加到字符串常量池中,并返回此 String 对象的引用,所以str23.intern() == str20实质是常量比较返回 true,str23 == str20.intern()中 str23 就是上面说的堆中新对象,相当于一个新对象和一个常量比较,所以返回 false,str23.intern() == str20.intern() 就没啥说的了,指定相等。
实例10:
String s1 = "abc";
StringBuffer s2 = new StringBuffer(s1);
System.out.println(s1.equals(s2)); //false
补充:
equals源码:
@Override public boolean equals(Object other) {
if (other == this) {
return true;
}
if (other instanceof String) {
String s = (String)other;
int count = this.count;
if (s.count != count) {
return false;
}
// TODO: we want to avoid many boundchecks in the loop below
// for long Strings until we have array equality intrinsic.
// Bad benchmarks just push .equals without first getting a
// hashCode hit (unlike real world use in a Hashtable). Filter
// out these long strings here. When we get the array equality
// intrinsic then remove this use of hashCode.
if (hashCode() != s.hashCode()) {
return false;
}
for (int i = 0; i < count; ++i) {
if (charAt(i) != s.charAt(i)) {
return false;
}
}
return true;
} else {
return false;
}
}
分析:instanceof运算符用法:左面的操作元是一个对象实例,右面是一个类.当 左面的对象是右面的类创建的对象时,该运算符运算的结果是true,否则是false(一个类的实例包括本身的实例,以及所有直接或间接子类的实例 )
我们再看StringBuffer 并不继承String类,所以到第五行判断不成立,直接返回了false。也就是equal比较的是相同类型的引用变量才行。
总结:要玩明白 Java String 对象的核心其实就是玩明白字符串的堆栈和常量池,虚拟机为每个被装载的类型维护一个常量池,常量池就是该类型所用常量的一个有序集合,包括直接常量(String、Integer 和 Floating Point常量)和对其他类型、字段和方法的符号引用,池中的数据项就像数组一样是通过索引访问的;由于常量池存储了相应类型所用到的所有类型、字段和方法的符号引用,所以它在 Java 程序的动态链接中起着核心的作用。