你好,我是 Guide。秋招即将到来,我对 JavaGuide 的内容进行了重构完善,公众号同步一下最新更新,希望能够帮助你。

上篇:Java 基础常见知识点&面试题总结(上),2022 最新版!

原文地址: https://javaguide.cn/java/basis/java-basic-questions-02.html(如果文章图片显示异常,请点此链接阅读)

面向对象基础

面向对象和面向过程的区别

两者的主要区别在于解决问题的方式不同:

  • 面向过程把解决问题的过程拆成一个个方法,通过一个个方法的执行解决问题。
  • 面向对象会先抽象出对象,然后用对象执行方法的方式解决问题。

另外,面向对象开发的程序一般更易维护、易复用、易扩展。

相关 issue : 面向过程 :面向过程性能比面向对象高??

成员变量与局部变量的区别

  • public final class String implements java.io.Serializable, Comparable<String>, CharSequence {
        private final char value[];
    	//...
    }
    

    字符串拼接用“+” 还是 StringBuilder?

    Java 语言本身并不支持运算符重载,“+”和“+=”是专门为 String 类重载过的运算符,也是 Java 中仅有的两个重载过的元素符。

    String str1 = "he";
    String str2 = "llo";
    String str3 = "world";
    String str4 = str1 + str2 + str3;
    

    上面的代码对应的字节码如下:

    Java 基础常见知识点&amp;面试题总结(中),2022 最新版!| JavaGuide-LMLPHP

    可以看出,字符串对象通过“+”的字符串拼接方式,实际上是通过 StringBuilder 调用 append() 方法实现的,拼接完成之后调用 toString() 得到一个 String 对象 。

    不过,在循环内使用“+”进行字符串的拼接的话,存在比较明显的缺陷:编译器不会创建单个 StringBuilder 以复用,会导致创建过多的 StringBuilder 对象。

    String[] arr = {"he", "llo", "world"};
    String s = "";
    for (int i = 0; i < arr.length; i++) {
        s += arr[i];
    }
    System.out.println(s);
    

    StringBuilder 对象是在循环内部被创建的,这意味着每循环一次就会创建一个 StringBuilder 对象。

    Java 基础常见知识点&amp;面试题总结(中),2022 最新版!| JavaGuide-LMLPHP

    如果直接使用 StringBuilder 对象进行字符串拼接的话,就不会存在这个问题了。

    String[] arr = {"he", "llo", "world"};
    StringBuilder s = new StringBuilder();
    for (String value : arr) {
        s.append(value);
    }
    System.out.println(s);
    

    Java 基础常见知识点&amp;面试题总结(中),2022 最新版!| JavaGuide-LMLPHP

    如果你使用 IDEA 的话,IDEA 自带的代码检查机制也会提示你修改代码。

    String#equals() 和 Object#equals() 有何区别?

    String 中的 equals 方法是被重写过的,比较的是 String 字符串的值是否相等。 Objectequals 方法是比较的对象的内存地址。

    字符串常量池的作用了解吗?

    字符串常量池 是 JVM 为了提升性能和减少内存消耗针对字符串(String 类)专门开辟的一块区域,主要目的是为了避免字符串的重复创建。

    // 在堆中创建字符串对象”ab“
    // 将字符串对象”ab“的引用保存在字符串常量池中
    String aa = "ab";
    // 直接返回字符串常量池中字符串对象”ab“的引用
    String bb = "ab";
    System.out.println(aa==bb);// true
    

    更多关于字符串常量池的介绍可以看一下 Java 内存区域详解 这篇文章。

    String s1 = new String("abc");这句话创建了几个字符串对象?

    会创建 1 或 2 个字符串对象。

    1、如果字符串常量池中不存在字符串对象“abc”的引用,那么会在堆中创建 2 个字符串对象“abc”。

    示例代码(JDK 1.8):

    String s1 = new String("abc");
    

    对应的字节码:

    Java 基础常见知识点&amp;面试题总结(中),2022 最新版!| JavaGuide-LMLPHP

    ldc 命令用于判断字符串常量池中是否保存了对应的字符串对象的引用,如果保存了的话直接返回,如果没有保存的话,会在堆中创建对应的字符串对象并将该字符串对象的引用保存到字符串常量池中。

    2、如果字符串常量池中已存在字符串对象“abc”的引用,则只会在堆中创建 1 个字符串对象“abc”。

    示例代码(JDK 1.8):

    // 字符串常量池中已存在字符串对象“abc”的引用
    String s1 = "abc";
    // 下面这段代码只会在堆中创建 1 个字符串对象“abc”
    String s2 = new String("abc");
    

    对应的字节码:

    Java 基础常见知识点&amp;面试题总结(中),2022 最新版!| JavaGuide-LMLPHP

    这里就不对上面的字节码进行详细注释了,7 这个位置的 ldc 命令不会在堆中创建新的字符串对象“abc”,这是因为 0 这个位置已经执行了一次 ldc 命令,已经在堆中创建过一次字符串对象“abc”了。7 这个位置执行 ldc 命令会直接返回字符串常量池中字符串对象“abc”对应的引用。

    intern 方法有什么作用?

    String.intern() 是一个 native(本地)方法,其作用是将指定的字符串对象的引用保存在字符串常量池中,可以简单分为两种情况:

    • 如果字符串常量池中保存了对应的字符串对象的引用,就直接返回该引用。
    • 如果字符串常量池中没有保存了对应的字符串对象的引用,那就在常量池中创建一个指向该字符串对象的引用并返回。

    示例代码(JDK 1.8) :

    // 在堆中创建字符串对象”Java“
    // 将字符串对象”Java“的引用保存在字符串常量池中
    String s1 = "Java";
    // 直接返回字符串常量池中字符串对象”Java“对应的引用
    String s2 = s1.intern();
    // 会在堆中在单独创建一个字符串对象
    String s3 = new String("Java");
    // 直接返回字符串常量池中字符串对象”Java“对应的引用
    String s4 = s3.intern();
    // s1 和 s2 指向的是堆中的同一个对象
    System.out.println(s1 == s2); // true
    // s3 和 s4 指向的是堆中不同的对象
    System.out.println(s3 == s4); // false
    // s1 和 s4 指向的是堆中的同一个对象
    System.out.println(s1 == s4); //true
    

    String 类型的变量和常量做“+”运算时发生了什么?

    先来看字符串不加 final 关键字拼接的情况(JDK1.8):

    String str1 = "str";
    String str2 = "ing";
    String str3 = "str" + "ing";
    String str4 = str1 + str2;
    String str5 = "string";
    System.out.println(str3 == str4);//false
    System.out.println(str3 == str5);//true
    System.out.println(str4 == str5);//false
    

    Java 基础常见知识点&amp;面试题总结(中),2022 最新版!| JavaGuide-LMLPHP

    对于编译期可以确定值的字符串,也就是常量字符串 ,jvm 会将其存入字符串常量池。并且,字符串常量拼接得到的字符串常量在编译阶段就已经被存放字符串常量池,这个得益于编译器的优化。

    在编译过程中,Javac 编译器(下文中统称为编译器)会进行一个叫做 常量折叠(Constant Folding) 的代码优化。《深入理解 Java 虚拟机》中是也有介绍到:

    Java 基础常见知识点&amp;面试题总结(中),2022 最新版!| JavaGuide-LMLPHP

    常量折叠会把常量表达式的值求出来作为常量嵌在最终生成的代码中,这是 Javac 编译器会对源代码做的极少量优化措施之一(代码优化几乎都在即时编译器中进行)。

    对于 String str3 = "str" + "ing"; 编译器会给你优化成 String str3 = "string";

    并不是所有的常量都会进行折叠,只有编译器在程序编译期就可以确定值的常量才可以:

    • 基本数据类型( bytebooleanshortcharintfloatlongdouble)以及字符串常量。
    • final 修饰的基本数据类型和字符串变量
    • 字符串通过 “+”拼接得到的字符串、基本数据类型之间算数运算(加减乘除)、基本数据类型的位运算(<<、>>、>>> )

    引用的值在程序编译期是无法确定的,编译器无法对其进行优化。

    对象引用和“+”的字符串拼接方式,实际上是通过 StringBuilder 调用 append() 方法实现的,拼接完成之后调用 toString() 得到一个 String 对象 。

    String str4 = new StringBuilder().append(str1).append(str2).toString();
    

    我们在平时写代码的时候,尽量避免多个字符串对象拼接,因为这样会重新创建对象。如果需要改变字符串的话,可以使用 StringBuilder 或者 StringBuffer

    不过,字符串使用 final 关键字声明之后,可以让编译器当做常量来处理。

    示例代码:

    final String str1 = "str";
    final String str2 = "ing";
    // 下面两个表达式其实是等价的
    String c = "str" + "ing";// 常量池中的对象
    String d = str1 + str2; // 常量池中的对象
    System.out.println(c == d);// true
    

    final 关键字修改之后的 String 会被编译器当做常量来处理,编译器在程序编译期就可以确定它的值,其效果就相当于访问常量。

    如果 ,编译器在运行时才能知道其确切值的话,就无法对其优化。

    示例代码(str2 在运行时才能确定其值):

    final String str1 = "str";
    final String str2 = getStr();
    String c = "str" + "ing";// 常量池中的对象
    String d = str1 + str2; // 在堆上创建的新的对象
    System.out.println(c == d);// false
    public static String getStr() {
          return "ing";
    }
    

    参考

    后记

    近期文章精选 :

    走近作者 :

    如果本文对你有帮助的话,欢迎点赞&在看&分享,这对我继续分享&创作优质文章非常重要。感谢🙏🏻

06-14 12:39