美文网首页
java基础常见问题

java基础常见问题

作者: 麦大大吃不胖 | 来源:发表于2021-08-24 21:59 被阅读0次

    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
    }
    

    可以看到

    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<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进制的,所以即使不序列化对象,依然可以传输。即结构对象可以进行跨网络传输和持久化存储。

    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基础常见问题

          本文链接:https://www.haomeiwen.com/subject/yzcriltx.html