String的解析
String作为Java中最常用的引用类型,相对来说基本上都比较熟悉,无论在平时的编码过程中还是在笔试面试中,String都很受到青睐,然而,在使用String过程中,又有较多需要注意的细节之处。
1.String是不可变类。
这句话其实大家都很熟悉了,那么具体什么是不可变类呢?一般认为:当对象一旦创建完成后,在正常情况下,对象的状态不会因外界的改变而改变(对象的状态是指对象的属性,包括属性的类型及属性值)。
首先看一个基本的例子:
1 String s = "abc";
2 System.out.println("s:" + s); // 输出s:abc
3 s = "def";
4 System.out.println("s:" + s); // 输出s:def
此时,初看上去,输出的结果变了,发现s的值发生了变化,那么这与上面的说法——String类是不可变类是否矛盾呢?答案是否定的,因为s只是指向堆内存中的引用,存储的是对象在堆中的地址,而非对象本身,s本身存储在栈内存中。
实际上,此时堆内存中依然存在着"abc"和"def"对象。对于"abc"对象本身而言,对象的状态是没有发生任何变化的。
那么为什么String类具有不可变性呢,显然,既然不可变说明String类中肯定没有提供对外可setters方法。接下来来具体看一下String类的定义。
下面是String类中主要属性的定义(Java 1.7源码):

1 public final class String implements java.io.Serializable, Comparable<String>, CharSequence{
2
3 /** The value is used for character storage. */
4 private final char value[];
5
6 /** Cache the hash code for the string */
7 private int hash; // Default to 0
8
9 }

与之前版本的Java String源码相比,String类减少了int offset 和 int count的定义。这样变化的结果主要体现在:
1.避免之前版本的String对象subString时可能引起的内存泄露问题;
2.新版本的subString时间复杂度将有O(1)变为O(n);
具体分析可见文章:
http://www.importnew.com/7656.html
http://www.importnew.com/14105.html
通过上面String类的定义,类名前面用了final class修饰,因此,String类不能被继承。对于其属性定义,可以看出,属性value[]和hash都是被定义成private类型,且由于没有 提供对外的public setters方法,String类属性不可被改变。
其中,需要重点关注属性value[],其被final char修饰,因此字符型数组value只会被赋值一次就不可修改。其存储内容正好是String中的单个字符内容。
2.String相关的 +
String中的 + 常用于字符串的连接。此处为了说明清楚这个问题,首先可以安装Eclipse 查看字节码插件ByteCode Outline。
在线安装网址: http://zipeditor.sourceforge.net/update/ Disabled。Help >> install new software >> 输入网址 >> 选择 bytecode outline >> ... ... 安装成功。
Window >> show view >> other >> java >> ByteCode即可在Eclipse下方面板栏中查看。
看下面一个简单的例子:

1 class D {
2
3 public static void main(String[] args) {
4
5 String a = "aa";
6 String b = "bb";
7 String c = "xx" + "yy " + a + "zz" + "mm" + b;
8 System.out.println(c);
9 }
10 }

编译运行后,点击ByteCode查看,主要字节码部分如下:

1 public static main([Ljava/lang/String;)V
2 L0
3 LINENUMBER 5 L0
4 LDC "aa"
5 ASTORE 1
6 L1
7 LINENUMBER 6 L1
8 LDC "bb"
9 ASTORE 2
10 L2
11 LINENUMBER 7 L2
12 NEW java/lang/StringBuilder
13 DUP
14 LDC "xxyy "
15 INVOKESPECIAL java/lang/StringBuilder.<init> (Ljava/lang/String;)V
16 ALOAD 1
17 INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder;
18 LDC "zz"
19 INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder;
20 LDC "mm"
21 INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder;
22 ALOAD 2
23 INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder;
24 INVOKEVIRTUAL java/lang/StringBuilder.toString ()Ljava/lang/String;
25 ASTORE 3
26 L3
27 LINENUMBER 8 L3
28 GETSTATIC java/lang/System.out : Ljava/io/PrintStream;
29 ALOAD 3
30 INVOKEVIRTUAL java/io/PrintStream.println (Ljava/lang/String;)V
31 L4
32 LINENUMBER 9 L4
33 RETURN
34 L5
35 LOCALVARIABLE args [Ljava/lang/String; L0 L5 0
36 LOCALVARIABLE a Ljava/lang/String; L1 L5 1
37 LOCALVARIABLE b Ljava/lang/String; L2 L5 2
38 LOCALVARIABLE c Ljava/lang/String; L3 L5 3
39 MAXSTACK = 3
40 MAXLOCALS = 4
41 }

显然,通过字节码我们可以得出如下几点结论:
1.String中使用 + 字符串连接符进行字符串连接时,连接操作最开始时如果都是字符串常量,编译后将尽可能多的直接将字符串常量连接起来,形成新的字符串常量参与后续连接(通过反编译工具jd-gui也可以方便的直接看出);
2.接下来的字符串连接是从左向右依次进行,对于不同的字符串,首先以最左边的字 符串为参数创建StringBuilder对象,然后依次对右边进行append操作,最后将StringBuilder对象通过toString()方 法转换成String对象(注意:中间的多个字符串常量不会自动拼接)。
也就是说
String c = "xx" + "yy " + a + "zz" + "mm" + b; 实质上的实现过程是: String c = new StringBuilder("xxyy").append(a).append("zz").append("mm").append(b).toString();
由于得出结论:当使用+进行多个字符串连接时,实际上是产生了一个StringBuilder对象和一个String对象。
3.String中的常用方法
1).与String类中value[]数组存储直接相关的有:
int length(); // 返回String长度,亦即value[]数组长度;
char charAt(int index); // 返回指定位置字符;
int indexOf(int ch, int fromIndex); //从fromIndex位置开始,查找ch字符在字符串中首次出现的位置。fromIndex默认为0,ch直接传入字符即可。如'C',区分大小写,未查找到返回-1;
char[] toCharArray() ; // 将字符串转换成一个新的字符数组
2).与其他字符串即字串相关的方法有:
int indexOf(String str, int fromIndex) ;
与indexOf含义相反有lastIndexOf(..),反向索引。
boolean contains(String str); //实际上 contains内部实现也是调用的indexOf,然后将其结果与-1相比较。
boolean startsWith(String str); // 判断字符串是否以str开头
boolean endsWith(String str); //.....是否以str结尾
String replace(CharSequence target, CharSequence replacement) ; // 替换
String substring(int beginIndex, int endIndex); //字符串截取,不传第二个参数则表示直接截取到字符串末尾
String[] split(String regex); // 字符串分割
4.String中的equals()与hashCode()
String类重写了Object类的equlas方法,使得比较字符串内容是否相等可以直接使用equlas方法。关于equals以及相应的hashCode方法参见博文《Java总结篇系列:java.lang.Object 》
5.String字符串常量池
JVM为了提高性能和减少内存开销,内部维护了一个字符串常量池,每当创建字符串常量时,JVM首先检查字符串常量池,如果常量池中已经存在,则返回池中的字符串对象引用,否则创建该字符串对象并放入池中。
因此下述结果返回true。
1 String a = "abc";
2 String b = "abc";
3 System.out.print(a == b); //true
但与创建字符串常量方式不同的是,当使用new String(String str)方式等创建字符串对象时,不管字符串常量池中是否有与此相同内容的字符串,都会在堆内存中创建新的字符串对象。
因此,下面代码片段有如下结果。
1 String a = "Hello";
2 String b = new String("Hello");
3 System.out.println(a == b); //false
4 System.out.println(a.equals(b)); //true
即使字符串内容相同,字符串常量池中的字符串与通过new String(..)等方式创建的字符串对象之间没有直接的关系,但是,可以通过字符串的intern()方法找到此种关联。intern()方法返回字符串对象在字符串常量池中的对象引用,若字符串常量池中尚未有此字符串,则创建一新的字符串常量放置于池中。
于是,很据如上理解,很自然的,可以得到如下结果。

1 String a = "Hello";
2 System.out.println(a == a.intern()); //true
3
4 String b = new String("corn");
5 String c = b.intern();
6
7 System.out.println(b == c); //false
8
9 String d = "corn";
10
11 System.out.println(c == d); //true

6.String/StringBuilder/StringBuffer区别
String是不可变字符串对象,StringBuilder和StringBuffer是可变字符串对象(其内部的字符数组长度可变),StringBuffer线程安全,StringBuilder非线程安全。
7.既然String是不可变字符串对象,如何才能改变让其可变?
既然String对象中没有对外提供可用的public setters等方法,因此只能通过Java中的反射机制实现。因此,前文中说到的String是不可变字符串对象只是针对“正常情况下”。而非必然。

1 public static void stringReflection() throws Exception {
2
3 String s = "Hello World";
4
5 System.out.println("s = " + s); //Hello World
6
7 //获取String类中的value字段
8 Field valueField = String.class.getDeclaredField("value");
9
10 //改变value属性的访问权限
11 valueField.setAccessible(true);
12
13 char[] value = (char[]) valueField.get(s);
14
15 //改变value所引用的数组中的第5个字符
16 value[5] = '_';
17
18 System.out.println("s = " + s); //Hello_World
19 }

由此可见Java中反射的强大之处。
String的解析的更多相关文章
- IEnumerable<IEnumerable<string>>结构解析通用解决方案(支持指定属性顺序)
一.前言 类似如下字符串 "ID", "NameValue", "CodeValue", "ExchangeTypeValue&q ...
- String深度解析
文章出处:http://my.oschina.net/xiaohui249/blog/170013 一.引题 String类型是比较特殊的一种类型,同时也是面试经常被问到的一个知识点,本文结合java ...
- JDK源码之String类解析
一 概述 String由final修饰,是不可变类,即String对象也是不可变对象.这意味着当修改一个String对象的内容时,JVM不会改变原来的对象,而是生成一个新的String对象 主要考虑以 ...
- Java之String重点解析
String s = new String("abc")这段代码创建了几个对象呢?s=="abc"这个判断的结果是什么?s.substring(0,2).int ...
- String全面解析
前言 public class Test { public static void main(String[] args) { String a = "abc"; String b ...
- Java基础(三) String深度解析
String可以说是Java中使用最多最频繁.最特殊的类,因为同时也是字面常量,而字面常量包括基本类型.String类型.空类型. 一. String的使用 1. String的不可变性 /** * ...
- Java String 类解析
I.构造函数: public String() {} 默认构造函数 public String(String original) {} 使用原有字符串构造 public String(char va ...
- java中的String要点解析
String类使我们经常使用的一个类,经常用来表示字符串常量. 字符串一旦被创建赋值,就不能被改变,因为String 底层是数组实现的,且被定义成final类型.我们可以看String源码. /** ...
- String相加解析
Java代码 package com.utils.test; public class TestObject { public static void main(String[] args) { St ...
随机推荐
- bzoj3382 [Usaco2004 Open]Cave Cows 3 洞穴里的牛之三
传送门:http://www.lydsy.com/JudgeOnline/problem.php?id=3382 [题解] 套路题. 首先我们会发现曼哈顿距离不好处理,难道要写kdtree??? (k ...
- [bzoj1568][JSOI2008]Blue Mary开公司——李超线段树
题目大意 题解 这道题需要用到一种叫做李超线段树的东西.我对于李超线段树,是这样理解的: 给节点打下的标记不进行下传,而是仅仅在需要的时候进行下传,这就是所谓永久化标记. 对于这道题,借用一张图, 这 ...
- canvas动态绘制饼状图,
当我们使用Echrts很Highcharts的时候,总是觉得各种统计图表是多么神奇,今天我就用现代浏览器支持的canvas来绘制饼状统计图,当然仅仅是画出图并没什么难度,但是统计图一般都有输入,根据不 ...
- SpringMvc基础知识(二) springmvc和mybatis整合
1 springmvc和mybatis整合 1.1 需求 使用springmvc和mybatis完成商品列表查询. 1.2 整合思路 springmvc+mybaits的系统架构: 第一步:整合dao ...
- Shell Script Basics
https://developer.apple.com/library/mac/documentation/OpenSource/Conceptual/ShellScripting/shell_scr ...
- Settings点击Location(位置)后右上角的开关button不会消失
MT8121/8382平台: 前几天又遇到一个源码的bug.在10寸平板上,进入设置界面,点击Location(位置)项,右上角Title处会显示一个开关button,用来开关定位服务.但点完Loca ...
- pymysql 线程安全pymysqlpool
# -*-coding: utf-8-*- # Author : Christopher Lee # License: Apache License # File : test_example.py ...
- spring 声明式事务中try catch捕获异常
原文:http://heroliuxun.iteye.com/blog/848122 今天遇到了一个这个问题 最近遇到这样的问题,使用spring时,在业务层需要捕获异常(特殊需要),当前一般情况下不 ...
- dubbo消费方超时处理
在我们分布式系统中,远程调用可能随时会出现调用超时,然后抛异常 在dubbo内部,默认设置的是500ms(好像是),所以,对于crud事物大的系统来讲肯定是要自定义超时时间咯,作为消费方,自然是优先级 ...
- 《Java编程思想》笔记 第五章 初始化与清理
1.构造器 因为创建一个类的对象构造器就会自动执行,故初始化某些东西特好 2.方法重载 方法名相同,参数列表不同. 2.1 区分重载方法 方法重载后区别不同方法的就是方法签名 -->参数类型和个 ...