java基础常见问题

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."":()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."":()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."":()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."":()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."":()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
}

可以看到

  1. 在编译期,s1直接等于abc,所以s1==s2 -> true
  2. s3实际上是通过StringBulider构造而来,所以s2==s3 -> false
  3. 对于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 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进制的,所以即使不序列化对象,依然可以传输。即结构对象可以进行跨网络传输和持久化存储。

  1. 筛选数据,防止重复存储
    序列化所做的工作除了将数据以二进制存入本地外,还要提供筛选数据,防止重复存储等功能。但是如果直接赋值内存中的数据,肯定达不到筛选数据,防止重复存储等功能。
  2. 跨平台、跨语言时
    将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方法,返回一个对象

你可能感兴趣的:(java基础常见问题)