java const string_深入研究Java String

开始写 Java 一年来,一直都是遇到什么问题再去解决,还没有主动的深入的去学习过 Java 语言的特性和深入阅读 JDK 的源码。既然决定今后靠 Java

吃饭,还是得花些心思在上面,放弃一些打游戏的时间,系统深入的去学习。

Java String 是 Java 编程中最常用的类之一,也是 JDK 提供的最基础的类。所以我决定先从 String 类入手,深入的研究一番来开个好头。

类定义与类成员

打开 JDK 中的 String 源码,最先应当关注 String 类的定义。

public final class String

implements java.io.Serializable, Comparable, CharSequence

不可继承与不可变

写过 Java 的人都知道, 当 final 关键字修饰类时,代表此类不可继承。所以 String 类是不能被外部继承。这时候我们可能会好奇,String 的设计者

为什么要把它设计成不可继承的呢。我在知乎上找到了相关的问题和讨论,

我觉得首位的回答已经说的很明白了。String 做为 Java 的最基础的引用数据类型,最重要的一点就是不可变性,所以使用 final 就是为了**禁止继承

破坏了 String 的不可变的性质**。

实现类的不可变性,不光是用 final 修饰类这么简单,从源码中可以看到,String 实际上是对一个字符数组的封装,而字符数组是私有的,并且没有提供

任何可以修改字符数组的方法,所以一旦初始化完成, String 对象便无法被修改。

序列化

从上面的类定义中我们看到了 String 实现了序列化的接口 Serializable,所以 String 是支持序列化和反序列化的。

什么是Java对象的序列化?相信很多和我一样的 Java 菜鸟都有这样疑问。深入分析Java的序列化与反序列化这篇文章中的这一段话

解释的很好。

Java平台允许我们在内存中创建可复用的Java对象,但一般情况下,

只有当JVM处于运行时,这些对象才可能存在,

即,这些对象的生命周期不会比JVM的生命周期更长。但在现实应用中,

就可能要求在JVM停止运行之后能够保存(持久化)指定的对象,并在将来重新读取被保存的对象。

Java对象序列化就能够帮助我们实现该功能。

使用Java对象序列化,在保存对象时,会把其状态保存为一组字节,在未来,再将这些字节组装成对象。

必须注意地是,对象序列化保存的是对象的”状态”,即它的成员变量。由此可知,对象序列化不会关注类中的静态变量。

除了在持久化对象时会用到对象序列化之外,当使用RMI(远程方法调用),或在网络中传递对象时,都会用到对象序列化。

Java序列化API为处理对象序列化提供了一个标准机制,该API简单易用。

在 String 源码中,我们也可以看到支持序列化的类成员定义。

/** use serialVersionUID from JDK 1.0.2 for interoperability */

private static final long serialVersionUID = -6849794470754667710L;

/**

* Class String is special cased within the Serialization Stream Protocol.

*

* A String instance is written into an ObjectOutputStream according to

*

* Object Serialization Specification, Section 6.2, "Stream Elements"

*/

private static final ObjectStreamField[] serialPersistentFields =

new ObjectStreamField[0];

serialVersionUID 是一个序列化版本号,Java 通过这个 UID 来判定反序列化时的字节流与本地类的一致性,如果相同则认为一致,

可以进行反序列化,如果不同就会抛出异常。

serialPersistentFields 这个定义则比上一个少见许多,大概猜到是与序列化时的类成员有关系。为了弄懂这个字段的意义,我 google 百度齐上,也

仅仅只找到了 JDK 文档对类 ObjectStreamField的一丁点描述, `A description of a Serializable field from a Serializable class.

An array of ObjectStreamFields is used to declare the Serializable fields of a class.` 大意是这个类用来描述序列化类的一个序列化字段,

如果定义一个此类的数组则可以声明类需要被序列化的字段。但是还是没有找到这个类的具体用法和作用是怎样的。后来我仔细看了一下这个字段的定义,

与 serialVersionUID 应该是同样通过具体字段名来定义各种规则的,然后我直接搜索了关键字 serialPersistentFields,终于找到了它的具体作用。

即,**默认序列化自定义包括关键字 transient 和静态字段名 serialPersistentFields,transient 用于指定哪个字段不被默认序列化,

serialPersistentFields 用于指定哪些字段需要被默认序列化。如果同时定义了 serialPersistentFields 与 transient,transient 会被忽略。**

我自己也测试了一下,确实是这个效果。

知道了 serialPersistentFields 的作用以后,问题又来了,既然这个静态字段是用来定义参与序列化的类成员的,那为什么在 String 中这个数组的长度定义为0?

经过一番搜索查找资料以后,还是没有找到一个明确的解释,期待如果有大佬看到能解答一下。

可排序

String 类还实现了 Comparable 接口,Comparable接口只有一个方法 public int compareTo(T o),实现了这个接口就意味着该类支持排序,

即可用 Collections.sort 或 Arrays.sort 等方法对该类的对象列表或数组进行排序。

在 String 中我们还可以看到这样一个静态变量,

public static final Comparator CASE_INSENSITIVE_ORDER

= new CaseInsensitiveComparator();

private static class CaseInsensitiveComparator

implements Comparator, java.io.Serializable {

// use serialVersionUID from JDK 1.2.2 for interoperability

private static final long serialVersionUID = 8575799808933029326L;

public int compare(String s1, String s2) {

int n1 = s1.length();

int n2 = s2.length();

int min = Math.min(n1, n2);

for (int i = 0; i < min; i++) {

char c1 = s1.charAt(i);

char c2 = s2.charAt(i);

if (c1 != c2) {

c1 = Character.toUpperCase(c1);

c2 = Character.toUpperCase(c2);

if (c1 != c2) {

c1 = Character.toLowerCase(c1);

c2 = Character.toLowerCase(c2);

if (c1 != c2) {

// No overflow because of numeric promotion

return c1 - c2;

}

}

}

}

return n1 - n2;

}

/** Replaces the de-serialized object. */

private Object readResolve() { return CASE_INSENSITIVE_ORDER; }

}

从上面的源码中可以看出,这个静态成员是一个实现了 Comparator 接口的类的实例,而实现这个类的作用是比较两个忽略大小写的 String 的大小。

那么 Comparable 和 Comparator 有什么区别和联系呢?同时 String 又为什么要两个都实现一遍呢?

第一个问题这里就不展开了,总结一下就是,Comparable 是类的内部实现,一个类能且只能实现一次,而 Comparator 则是外部实现,可以通过不改变

类本身的情况下,为类增加更多的排序功能。

所以我们也可以为 String 实现一个 Comparator使用,具体可以参考Comparable与Comparator的区别这篇文章。

String 实现了两种比较方法的意图,实际上是一目了然的。实现 Comparable 接口为类提供了标准的排序方案,同时为了满足大多数排序需求的忽略大小写排序的情况,

String 再提供一个 Comparator 到公共静态类成员中。如果还有其他的需求,那就只能我们自己实现了。

类方法

String 的方法大致可以分为以下几类。

构造方法

功能方法

工厂方法

intern方法

关于 String 的方法的解析,这篇文章已经解析的够好了,所以我这里也不再重复的说一遍了。不过

最后的 intern 方法值得我们去研究。

intern方法

字符串常量池

String 做为 Java 的基础类型之一,可以使用字面量的形式去创建对象,例如 String s = "hello"。当然也可以使用 new 去创建 String 的对象,

但是几乎很少看到这样的写法,久而久之我便习惯了第一种写法,但是却不知道背后大有学问。下面一段代码可以看出他们的区别。

public class StringConstPool {

public static void main(String[] args) {

String s1 = "hello world";

String s2 = new String("hello world");

String s3 = "hello world";

String s4 = new String("hello world");

String s5 = "hello " + "world";

String s6 = "hel" + "lo world";

String s7 = "hello";

String s8 = s7 + " world";

System.out.println("s1 == s2: " + String.valueOf(s1 == s2) );

System.out.println("s1.equals(s2): " + String.valueOf(s1.equals(s2)));

System.out.println("s1 == s3: " + String.valueOf(s1 == s3));

System.out.println("s1.equals(s3): " + String.valueOf(s1.equals(s3)));

System.out.println("s2 == s4: " + String.valueOf(s2 == s4));

System.out.println("s2.equals(s4): " + String.valueOf(s2.equals(s4)));

System.out.println("s5 == s6: " + String.valueOf(s5 == s6));

System.out.println("s1 == s8: " + String.valueOf(s1 == s8));

}

}

/* output

s1 == s2: false

s1.equals(s2): true

s1 == s3: true

s1.equals(s3): true

s2 == s4: false

s2.equls(s4): true

s5 == s6: true

s1 == s8: false

*/

从这段代码的输出可以看到,equals 比较的结果都是 true,这是因为 String 的 equals 比较的值( Object 对象的默认 equals 实现是比较引用,

String 对此方法进行了重写)。== 比较的是两个对象的引用,如果引用相同则返回 true,否则返回 false。s1==s2: false和 s2==s4: false

说明了 new 一个对象一定会生成一个新的引用返回。s1==s3: true 则证明了使用字面量创建对象同样的字面量会得到同样的引用。

s5 == s6 实际上和 s1 == s3 在 JVM 眼里是一样的情况,因为早在编译阶段,这种常量的简单运算就已经完成了。我们可以使用 javap 反编译一下 class 文件去查看

编译后的情况。

➜ ~ javap -c StringConstPool.class

Compiled from "StringConstPool.java"

public class io.github.jshanet.thinkinginjava.constpool.StringConstPool {

public io.github.jshanet.thinkinginjava.constpool.StringConstPool();

Code:

0: aload_0

1: invokespecial #1 // Method java/lang/Object."":()V

4: return

public static void main(java.lang.String[]);

Code:

0: ldc #2 // String hello world

2: astore_1

3: return

}

看不懂汇编也没关系,因为注释已经很清楚了......

s1 == s8 的情况就略复杂,s8 是通过变量的运算而得,所以无法在编译时直接算出其值。而 Java 又不能重载运算符,所以我们在 JDK 的源码里也

找不到相关的线索。万事不绝反编译,我们再通过反编译看看实际上编译器对此是否有影响。

public class io.github.jshanet.thinkinginjava.constpool.StringConstPool {

public io.github.jshanet.thinkinginjava.constpool.StringConstPool();

Code:

0: aload_0

1: invokespecial #1 // Method java/lang/Object."":()V

4: return

public static void main(java.lang.String[]);

Code:

0: ldc #2 // String hello

2: astore_1

3: new #3 // class java/lang/StringBuilder

6: dup

7: invokespecial #4 // Method java/lang/StringBuilder."":()V

10: aload_1

11: invokevirtual #5 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;

14: ldc #6 // String world

16: invokevirtual #5 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;

19: invokevirtual #7 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;

22: astore_2

23: return

}

通过反编译的结果可以发现,String 的变量运算实际上在编译后是由 StringBuilder 实现的,s8 = s7 + " world" 的代码等价于

(new StringBuilder(s7)).append(" world").toString()。Stringbuilder 是可变的类,通过 append 方法 和 toString 将两个 String 对象聚合

成一个新的 String 对象,所以到这里就不难理解为什么 s1 == s8 : false 了。

之所以会有以上的效果,是因为有字符串常量池的存在。字符串对象的分配和其他对象一样是要付出时间和空间代价,而字符串又是程序中最常用的对象,JVM

为了提高性能和减少内存占用,引入了字符串的常量池,在使用字面量创建对象时, JVM 首先会去检查常量池,如果池中有现成的对象就直接返回它的引用,如果

没有就创建一个对象,并放到池里。因为字符串不可变的特性,所以 JVM 不用担心多个变量引用同一个对象会改变对象的状态。同时运行时实例创建的全局

字符串常量池中有一个表,总是为池中的每个字符串对象维护一个引用,所以这些对象不会被 GC 。

intern 方法的作用

上面说了很多都没有涉及到主题 intern 方法,那么 intern 方法到作用到底是什么呢?首先查看一下源码。

/**

* Returns a canonical representation for the string object.

*

* A pool of strings, initially empty, is maintained privately by the

* class {@code String}.

*

* When the intern method is invoked, if the pool already contains a

* string equal to this {@code String} object as determined by

* the {@link #equals(Object)} method, then the string from the pool is

* returned. Otherwise, this {@code String} object is added to the

* pool and a reference to this {@code String} object is returned.

*

* It follows that for any two strings {@code s} and {@code t},

* {@code s.intern() == t.intern()} is {@code true}

* if and only if {@code s.equals(t)} is {@code true}.

*

* All literal strings and string-valued constant expressions are

* interned. String literals are defined in section 3.10.5 of the

* The Java™ Language Specification.

*

* @return a string that has the same contents as this string, but is

* guaranteed to be from a pool of unique strings.

*/

public native String intern();

Oracle JDK 中,intern 方法被 native 关键字修饰并且没有实现,这意味着这部分到实现是隐藏起来了。从注释中看到,这个方法的作用是如果常量池

中存在当前字符串,就会直接返回当前字符串,如果常量池中没有此字符串,会将此字符串放入常量池中后再返回。通过注释的介绍已经可以明白这个方法的作用了,

再用几个例子证明一下。

public class StringConstPool {

public static void main(String[] args) {

String s1 = "hello";

String s2 = new String("hello");

String s3 = s2.intern();

System.out.println("s1 == s2: " + String.valueOf(s1 == s2));

System.out.println("s1 == s3: " + String.valueOf(s1 == s3));

}

}

/* output

s1 == s2: false

s1 == s3: true

*/

这里就很容易的了解 intern 实际上就是把普通的字符串对象也关联到常量池中。

当然 intern 的实现原理和最佳实践等也是需要理解学习的,美团技术团队的这篇深入解析String#intern

很深入也很详细,推荐阅读。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/378794.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

python 示例_带有示例的Python字典update()方法

python 示例字典update()方法 (Dictionary update() Method) update() method is used to update the dictionary by inserting new items to the dictionary. update()方法用于通过将新项目插入字典来更新字典。 Syntax: 句法&#xff1a; dictionary_name.setdefault(itera…

Rsync 使用指南

Rsync是个相当棒的同步工具&#xff0c;比如&#xff1a;1. 如何做本地两个目录之间的同步&#xff1f;rsync -av --delete --force ~/Desktop/Miscs/ /media/disk/DesktopMiscs 这样就可以做~/Desktop/Miscs目录的镜像了。/media/disk是我的移动硬盘的挂载点。这里关键有个问题…

C++——统计多行单个字符类型个数

键盘输入n个字符&#xff0c;请分别统计大写字母、小写字母、数字、其他字符的个数并输出&#xff1b;还需要输出所有数字字符之和 【输入形式】 第一行为一个整数n(100 > n > 0)&#xff0c;接下来n行每行一个字符 【输出形式】 输出第1行为4个整数&#xff0c;分别…

安卓项目4

经历两天的琢磨&#xff0c;终于把android连接服务器端php&#xff0c;读取mysql这一块弄好了。 先说说这几天遇到的问题。 http://wenku.baidu.com/view/87ca3bfa700abb68a982fbca.html 这是我参照的资料&#xff0c;原先我一度认为是不能实例化ServiceLink类&#xff0c;后来…

system getenv_Java System类getenv()方法及示例

system getenv系统类getenv()方法 (System class getenv() method) getenv() method is available in java.lang package. getenv()方法在java.lang包中可用。 getenv() method is used to return an unmodifiable Map of the current environment variable in key-value pairs…

用ASP获取客户端IP地址的方法

要想透过代理服务器取得客户端的真实IP地址&#xff0c;就要使用 Request.ServerVariables("HTTP_X_FORWARDED_FOR") 来读取。不过要注意的事&#xff0c;并不是每个代理服务器都能用 Request.ServerVariables("HTTP_X_FORWARDED_FOR") 来读取客户端的真实…

C++——已知a+b、 a+c、b+c、 a+b+c,求a、b、 c

有三个非负整数a、b、 C,现按随机顺序给出它们的两两和以及总和4个整数&#xff0c;即ab、 ac、bc、 abc, 注意,给出的4个数的顺序是随机的&#xff0c;请根据这四个数求出a、b、c是多少? [输入形式] 输入为一-行4个正整数, x1、 x2、x3、 x4 (0≤xi≤10^9) &#xff0c;表示…

DDD:DomainEvent、ApplicationEvent、Command

Command&#xff1a;纵向传递&#xff0c;跨分层&#xff0c;在控制器层和应用层之间传递。 DomainEvent&#xff1a;横向传递&#xff0c;跨聚合&#xff0c;在一个DLL中。 ApplicationEvent&#xff1a;横向传递&#xff0c;跨模块&#xff0c;在不同的DLL中。转载于:https:/…

表示和描述-边界追踪

边界追踪目标&#xff1a; 输入&#xff1a;某一区域的点 输出&#xff1a;这一区域的点的坐标序列&#xff08;顺时针或逆时针&#xff09; Moore边界追踪法&#xff1a; 两个前提条件&#xff1a; 1、图像为二值化后的图像&#xff08;目标为1&#xff0c;背景为0&#xff0…

视频的读取与处理

读取本地视频&#xff0c;以灰度视频输出 import cv2vc cv2.VideoCapture(E:\Jupyter_workspace\study\data/a.mp4)#视频路径根据实际情况而定#检查是否打开正确 if vc.isOpened():open,fream vc.read()#read()返回两个参数&#xff0c;第一个参数为打开成功与否True or Fal…

更灵活的定位内存地址的方法05 - 零基础入门学习汇编语言36

第七章&#xff1a;更灵活的定位内存地址的方法05 让编程改变世界 Change the world by program 问题7.8 [codesyntax lang"asm"] assume cs:codesg,ds:datasg datasg segment db ibm db dec db dos db vax …

nextgaussian_Java Random nextGaussian()方法与示例

nextgaussian随机类nextGaussian()方法 (Random Class nextGaussian() method) nextGaussian() method is available in java.util package. nextGaussian()方法在java.util包中可用。 nextGaussian() method is used to generate the next pseudo-random Gaussian double valu…

Java PriorityQueue clear()方法与示例

PriorityQueue类clear()方法 (PriorityQueue Class clear() method) clear() method is available in java.util package. clear()方法在java.util包中可用。 clear() method is used to remove all the objects from this PriorityQueue. clear()方法用于从此PriorityQueue中删…

图像分割-边缘连接

三种基本方法&#xff1a; 1&#xff1a;局部处理 2&#xff1a;区域处理 3&#xff1a;使用霍夫变换的全局处理 局部处理 根据预定的规则&#xff0c;将所有相似点连接起来。 用于确定边缘像素相似性的两个主要性质&#xff1a;1、梯度向量的幅度2、梯度向量的角度 由于要…

01-图像ROI区域获取

截取部分图像数据 import cv2 def cv_show(name,img):cv2.imshow(name,img)cv2.waitKey(0)cv2.destroyAllWindows()img2 cv2.imread("E:\Jupyter_workspace\study\data/cat.png")#读取照片&#xff0c;第二个参数若为0&#xff0c;则灰度图&#xff1b;若不填或者1…

如何编写测试计划

有以下几个方面需要作考虑&#xff1a; 1. 测试的范围。要测试什么&#xff0c;这是肯定要明确的&#xff0c;即使你知道&#xff0c;你也要写出来&#xff0c;让看这份文档的人知道测试的范围。在确定测试内容的时候&#xff0c;还可以做一个优先级的区分&#xff0c;这样能保…

java clone 序列化_关于Java对象深度Clone以及序列化与反序列化的使用

‍ 我们可以利用clone方法来实现对象只见的复制&#xff0c;但对于比较复杂的对象(比如对象中包含其他对象&#xff0c;其他对象又包含别的对象.....)这样我们必须进行层层深度clone&#xff0c;每个对象需要实现 cloneable接口&#xff0c;比较麻烦&#xff0c;那就继续…

java enummap_Java EnumMap containsKey()方法与示例

java enummapEnumMap类containsKey()方法 (EnumMap Class containsKey() method) containsKey() method is available in java.util package. containsKey()方法在java.util包中可用。 containsKey() method is used to check whether this map has values for the given key e…

02-对图像进行边界填充

import cv2 import matplotlib.pyplot as pltimg2 cv2.imread("E:\Jupyter_workspace\study\data/cat.png")#读取照片&#xff0c;第二个参数若为0&#xff0c;则灰度图&#xff1b;若不填或者1则彩色图或本身图top_size,bottom_size,left_size,right_size (50,50,…