by shihang.mai
1. String的intern()
1.1 字符串的拼接
先来看看字符串的拼接
public static void main(String[] args) {
String s1 = "a"+"b"+"c";
String s2 = "abc";
String s3 = s2+"";
final String s4 = "abc";
String s5 = s4+"";
//true
System.out.println("s1==s2:" + (s1 == s2));
//false
System.out.println("s2==s3:"+ (s2 == s3));
//true
System.out.println("s4==s5:"+ (s4 == s5));
}
我们用javac
编译文件,然后javap -c class
得到下面内容
Compiled from "Test.java"
public class com.qdama.intl.common.service.listen.Test {
public com.qdama.intl.common.service.listen.Test();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
public static void main(java.lang.String[]);
Code:
0: ldc #2 // String abc
2: astore_1
3: ldc #2 // String abc
5: astore_2
6: new #3 // class java/lang/StringBuilder
9: dup
10: invokespecial #4 // Method java/lang/StringBuilder."<init>":()V
13: aload_2
14: invokevirtual #5 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
17: ldc #6 // String
19: invokevirtual #5 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
22: invokevirtual #7 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
25: astore_3
26: ldc #2 // String abc
28: astore 5
30: getstatic #8 // Field java/lang/System.out:Ljava/io/PrintStream;
33: new #3 // class java/lang/StringBuilder
36: dup
37: invokespecial #4 // Method java/lang/StringBuilder."<init>":()V
40: ldc #9 // String s1==s2:
42: invokevirtual #5 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
45: aload_1
46: aload_2
47: if_acmpne 54
50: iconst_1
51: goto 55
54: iconst_0
55: invokevirtual #10 // Method java/lang/StringBuilder.append:(Z)Ljava/lang/StringBuilder;
58: invokevirtual #7 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
61: invokevirtual #11 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
64: getstatic #8 // Field java/lang/System.out:Ljava/io/PrintStream;
67: new #3 // class java/lang/StringBuilder
70: dup
71: invokespecial #4 // Method java/lang/StringBuilder."<init>":()V
74: ldc #12 // String s2==s3:
76: invokevirtual #5 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
79: aload_2
80: aload_3
81: if_acmpne 88
84: iconst_1
85: goto 89
88: iconst_0
89: invokevirtual #10 // Method java/lang/StringBuilder.append:(Z)Ljava/lang/StringBuilder;
92: invokevirtual #7 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
95: invokevirtual #11 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
98: getstatic #8 // Field java/lang/System.out:Ljava/io/PrintStream;
101: new #3 // class java/lang/StringBuilder
104: dup
105: invokespecial #4 // Method java/lang/StringBuilder."<init>":()V
108: ldc #13 // String s4==s5:
110: invokevirtual #5 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
113: ldc #2 // String abc
115: aload 5
117: if_acmpne 124
120: iconst_1
121: goto 125
124: iconst_0
125: invokevirtual #10 // Method java/lang/StringBuilder.append:(Z)Ljava/lang/StringBuilder;
128: invokevirtual #7 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
131: invokevirtual #11 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
134: return
}
可以看到
- 在编译期,s1直接等于abc,所以s1==s2 -> true
- s3实际上是通过StringBulider构造而来,所以s2==s3 -> false
- 对于s4加上了final,表明是常量,s5在编译期直接赋值abc,所以s4==f5 -> true
1.2 String常量池
底层实际就是一个hash表,数组加链表.可通过-XX:StringTableSize=数值
改变数组的大小
1.3 intern()
对于intern(),需要分jdk 1.6和1.7以上描述
- jdk1.6
当字符串.intern()
如果常量池不存在该字符串常量,那么就会把值
复制一份到常量池,并返回常量池中的对象
如果常量池存在该字符串常量,直接返回常量池中的对象 - jdk 1.7以上
当字符串.intern()
如果常量池不存在该字符串常量,那么就会把地址
复制一份到常量池,并返回常量池中的对象地址
如果常量池存在该字符串常量,直接返回常量池中的对象
举例说明A
public static void main(String[] args) {
//常量池会有一个a,堆中有s1(a对象)
String s1 = new String("a");
//执行该语句,因为常量池已经有a,故返回的是常量池的对象,但这里没变量接收
s1.intern();
//这里s2直接取常量池中的a
String s2 = "a";
//s1是堆中的a对象,s2是常量池的a对象,故结果为false
System.out.println(s1 == s2);
}
举例说明B
public static void main(String[] args) {
//常量池b、c,堆s3(bc对象)
String s3 = new String("b") + new String("c");
//执行该语句,这里分不同的版本
//当为1.6时,直接将bc值复制一份到常量池,形成常量池中有b、c、bc,堆中还是s3(bc对象)
//当为1.7时,将s3的地址放到常量池,形成常量池中有b、c、s3地址
s3.intern();
//当为1.6时,那么s4直接取得常量池的bc
//当为1.7时,bc在常量池实际是s3的地址
String s4 = "bc";
//当为1.6时,s3是堆中的s3(bc对象),s4时常量池的bc,故结果为false
//当为1.7时,s4即为s3,故结果为true
System.out.println(s3 == s4);
}
举例说明C
public static void main(String[] args) {
//常量池d、e,堆s5(de对象)
String s5 = new String("d") + new String("e");
//常量池放入de
String s6 = "de";
/*执行此方法,将de放入常量池,但是上一步常量池已经有de,
故这里返回常量池的de,但是没变量接收,等于没做任何事*/
s5.intern();
//s5是堆对象,s6是常量池对象,故结构为false
System.out.println(s5 == s6);
}
2. ==和equals
看看Object类的equals源码
public boolean equals(Object obj) {
return (this == obj);
}
其实它于==一样,都直接是比较两个对象地址是否一样
对于String的equals,实际上是重写了Object的equals方法的
public boolean equals(Object anObject) {
if (this == anObject) {
return true;
}
if (anObject instanceof String) {
String anotherString = (String)anObject;
int n = value.length;
if (n == anotherString.value.length) {
char v1[] = value;
char v2[] = anotherString.value;
int i = 0;
while (n-- != 0) {
if (v1[i] != v2[I])
return false;
I++;
}
return true;
}
}
return false;
}
3. equals与hashcode
equals相同,那么它们的hashcode必须相等
我们重写equals都必须重写hashcode,我们用反证法说明。
当我们如果只重写equals时,那么在使用集合时,会出现逻辑性错误。
- HashSet
它在加入元素时,先会判断hashcode,如果hashcode相等再判断equals。当只重写equals的话,那么Set中就可能出现相同的元素了
4. 序列化和反序列化
序列化: 对象->字节流
反序列化: 字节流->对象
java实现序列化两种方式:实现Serializable
接口或者实现Exteranlizable
接口
对于Serializable
static修饰和被transient修饰的属性不会参加序列化
,除了自身的static serialVersionUID。
下面是找到的源码过滤的地方
private static ObjectStreamField[] getDefaultSerialFields(Class<?> cl) {
Field[] clFields = cl.getDeclaredFields();
ArrayList<ObjectStreamField> list = new ArrayList<>();
//重点
int mask = Modifier.STATIC | Modifier.TRANSIENT;
for (int i = 0; i < clFields.length; i++) {
//重点
if ((clFields[i].getModifiers() & mask) == 0) {
list.add(new ObjectStreamField(clFields[i], false, true));
}
}
int size = list.size();
return (size == 0) ? NO_FIELDS :
list.toArray(new ObjectStreamField[size]);
}
4.1 需要序列化的原因
首先明确,其实不用序列化,一样可以存储数据的
。任何数据在计算机中的存储都是0,1进制的,所以即使不序列化对象,依然可以传输。即结构对象可以进行跨网络传输和持久化存储。
- 筛选数据,防止重复存储
序列化所做的工作除了将数据以二进制存入本地外,还要提供筛选数据,防止重复存储等功能。但是如果直接赋值内存中的数据,肯定达不到筛选数据,防止重复存储等功能。 - 跨平台、跨语言时
将java 对象序列化成 xml 或者 json 形式。这样即使是 python 等非java语言都可以直接使用这个xml 或者json 对象得到自己需要的信息了
序列化使得对象信息更加普通化,可读化
。这样就可以使得别的进程,别的语言,别的平台都能够知道这个对象信息,从而保证了对象信息的持久化
博主:https://blog.csdn.net/liu16659/article/details/85793686
4.2 serialVersionUID作用
- 当一个对象实现Serializable接口,但是没指定serialVersionUID,那么java在序列化时,根据属性生成一个serialVersionUID。当修改对象属性后,再将原本序列化的对象反序列化,
会报错
。 - 当一个对象实现Serializable接口,指定serialVersionUID,当修改对象属性后,再将原本序列化的对象反序列化,不会报错。
在开发代码时,不指定这个,旧数据就无法反序列化,会出很大问题
4.3 序列化和单例
直接看-设计模式之单例,写得很清楚
4.4 反序列化安全
JWT,待完善
5. 异常
6. 克隆
快速获取一个对象的副本。实现Cloneable
标记接口,重写Object类的clone()方法
6.1 浅克隆
创建一个新对象,新对象的属性和原来对象完全相同,对于非基本类型属性,仍指向原有属性所指向的对象的内存地址
6.2 深克隆
创建一个新对象,属性中引用的其他对象也会被克隆,不再指向原有对象地址
6.2.1 改写引用类型的clone
引用类型也实现Cloneable,并且持有该类型的类的clone()将该引用类型重新set进去即可
public Object clone() {
Cat clone = null;
try {
clone = (Cat) super.clone();
clone.setSkill(this.getSkill().clone());
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return clone;
}
6.2.2 序列化克隆
实现Cloneable, Serializable
public Object clone() {
ByteArrayOutputStream bos = null ;
ObjectOutputStream oos = null ;
ByteArrayInputStream bis = null ;
ObjectInputStream ois = null ;
try {
//序列化
bos = new ByteArrayOutputStream();
oos = new ObjectOutputStream(bos);
oos.writeObject(this);
//反序列化
bis = new ByteArrayInputStream( bos.toByteArray() );
ois = new ObjectInputStream( bis );
Cat copy = (Cat) ois.readObject();
return copy;
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
return null;
}finally{
try {
bos.close();
oos.close();
bis.close();
ois.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
正因为对象可序列化克隆,所以写单例时必须考虑防止序列化影响。所以在单例中加入方法readResolve()即可,这是因为在反序列化的源码中,如果目标类有readResolve方法,那就通过反射的方式调用要被反序列化的类中的readResolve方法,返回一个对象