• Java基础(二)


    目录

    1. 面向对象基础

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

    1.2 创建一个对象用什么运算符?对象实体与对象引用有何不同?

    1.3 对象相等和引用相等的区别

    1.4 如果一个类没有声明构造方法,该程序能正确执行吗?

    1.5 构造方法有哪些特点?是否可被 override?

    1.6 面向对象的三大特性

    1.7 接口和抽象类有什么共同点和区别?

    1.8 深拷贝和浅拷贝区别了解吗?什么是引用拷贝?

    2. Object

    2.1 Object 类的常见方法有哪些?

    2.2 == 和 equals() 的区别

    2.3 hashCode() 有什么用?

    2.4 为什么要有 hashCode?

    2.4.1 那为什么 JDK 还要同时提供这两个方法呢?

    2.4.2 那为什么不只提供 hashCode() 方法呢?

    2.4.3 那为什么两个对象有相同的 hashCode 值,它们也不一定是相等的?

    2.5 为什么重写 equals() 时必须重写 hashCode() 方法?

    3. String

    3.1 String、StringBuffer、StringBuilder 的区别?

    3.2 String 为什么是不可变的?

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

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

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

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

    3.7  String#intern 方法有什么作用?

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


    1. 面向对象基础

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

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

    面向对象开发的方式更容易维护和迭代升级、易复用、易扩展。

    1.2 创建一个对象用什么运算符?对象实体与对象引用有何不同?

    用new来创建一个对象。new 创建对象实例(对象实例在堆内存中),对象引用指向对象实例(对象引用存放在栈内存中)。

    Student s = new Student();

    new Student()为对象实例,存放在堆内存中。

    s为对象引用,存放在栈内存中

    • 一个对象引用可以指向 0 个或 1 个对象(一根绳子可以不系气球,也可以系一个气球);
    • 一个对象可以有 n 个引用指向它(可以用 n 条绳子系住一个气球)。

    1.3 对象相等和引用相等的区别

    • 对象的相等一般比较的是内存中存放的内容是否相等。
    • 引用相等一般比较的是他们指向的内存地址是否相等。

    1.4 如果一个类没有声明构造方法,该程序能正确执行吗?

    构造方法是一种特殊的方法,主要作用是完成对象的初始化工作。

    如果一个类没有声明构造方法,也可以执行!因为一个类即使没有声明构造方法也会有默认的不带参数的构造方法。如果我们自己添加了类的构造方法(无论是否有参),Java 就不会添加默认的无参数的构造方法了。

    1.5 构造方法有哪些特点?是否可被 override?

    • 名字与类名相同。
    • 没有返回值,但不能用 void 声明构造函数。
    • 生成类的对象时自动执行,无需调用。

    构造方法不能被 override(重写),但是可以 overload(重载),所以你可以看到一个类中有多个构造函数的情况。

    1.6 面向对象的三大特性

    封装

    封装是指把一个对象的状态信息(也就是属性)隐藏在对象内部,不允许外部对象直接访问对象的内部信息。但是可以提供一些可以被外界访问的方法来操作属性。就好像我们看不到挂在墙上的空调的内部的零件信息(也就是属性),但是可以通过遥控器(方法)来控制空调。

    继承

    不同类型的对象,相互之间经常有一定数量的共同点。例如,小明同学、小红同学、小李同学,都共享学生的特性(班级、学号等)。同时,每一个对象还定义了额外的特性使得他们与众不同。例如小明的数学比较好,小红的性格惹人喜爱;小李的力气比较大。继承是使用已存在的类的定义作为基础建立新类的技术,新类的定义可以增加新的数据或新的功能,也可以用父类的功能,但不能选择性地继承父类。通过使用继承,可以快速地创建新的类,可以提高代码的重用,程序的可维护性,节省大量创建新类的时间 ,提高我们的开发效率。

    关于继承如下 3 点请记住:

    1. 子类拥有父类对象所有的属性和方法(包括私有属性和私有方法),但是父类中的私有属性和方法子类是无法访问,只是拥有
    2. 子类可以拥有自己属性和方法,即子类可以对父类进行扩展。
    3. 子类可以用自己的方式实现父类的方法。就是重写。

    多态

    一个对象具有多种的状态,具体表现为父类的引用指向子类的实例。

    多态的特点:

    • 对象类型和引用类型之间具有继承(类)/实现(接口)的关系;
    • 引用类型变量发出的方法调用的到底是哪个类中的方法,必须在程序运行期间才能确定;
    • 多态不能调用“只在子类存在但在父类不存在”的方法;
    • 如果子类重写了父类的方法,真正执行的是子类覆盖的方法,如果子类没有覆盖父类的方法,执行的是父类的方法。

    1.7 接口和抽象类有什么共同点和区别?

    共同点

    • 都不能被实例化。
    • 都可以包含抽象方法。
    • 都可以有默认实现的方法(Java 8 可以用 default 关键字在接口中定义默认方法)。

    区别

    • 接口主要用于对类的行为进行约束,你实现了某个接口就具有了对应的行为。抽象类主要用于代码复用,强调的是所属关系。
    • 一个类只能继承一个类,但是可以实现多个接口。
    • 接口中的成员变量只能是 public static final 类型的,不能被修改且必须有初始值,而抽象类的成员变量默认 default,可在子类中被重新定义,也可被重新赋值。

    1.8 深拷贝和浅拷贝区别了解吗?什么是引用拷贝?

    • 浅拷贝:浅拷贝会在堆上创建一个新的对象(区别于引用拷贝的一点),不过,如果原对象内部的属性是引用类型的话,浅拷贝会直接复制内部对象的引用地址,也就是说拷贝对象和原对象共用同一个内部对象。
    • 深拷贝:深拷贝会完全复制整个对象,包括这个对象所包含的内部对象。

    这玩意用文字讲不明白,直接上代码

    浅拷贝

    浅拷贝的示例代码如下,我们这里实现了 Cloneable 接口,并重写了 clone() 方法。

    clone() 方法的实现很简单,直接调用的是父类 Object 的 clone() 方法。

    1. public class Address implements Cloneable{
    2. private String name;
    3. // 省略构造函数、Getter&Setter方法
    4. @Override
    5. public Address clone() {
    6. try {
    7. return (Address) super.clone();
    8. } catch (CloneNotSupportedException e) {
    9. throw new AssertionError();
    10. }
    11. }
    12. }
    13. public class Person implements Cloneable {
    14. private Address address;
    15. // 省略构造函数、Getter&Setter方法
    16. @Override
    17. public Person clone() {
    18. try {
    19. Person person = (Person) super.clone();
    20. return person;
    21. } catch (CloneNotSupportedException e) {
    22. throw new AssertionError();
    23. }
    24. }
    25. }

     测试:

    1. Person person1 = new Person(new Address("武汉"));
    2. Person person1Copy = person1.clone();
    3. // true
    4. System.out.println(person1.getAddress() == person1Copy.getAddress());

    从输出结构就可以看出, person1 的克隆对象和 person1 使用的仍然是同一个 Address 对象。 

    深拷贝

    这里我们简单对 Person 类的 clone() 方法进行修改,连带着要把 Person 对象内部的 Address 对象一起复制。

    1. @Override
    2. public Person clone() {
    3. try {
    4. Person person = (Person) super.clone();
    5. person.setAddress(person.getAddress().clone());
    6. return person;
    7. } catch (CloneNotSupportedException e) {
    8. throw new AssertionError();
    9. }
    10. }

    测试:

    1. Person person1 = new Person(new Address("武汉"));
    2. Person person1Copy = person1.clone();
    3. // false
    4. System.out.println(person1.getAddress() == person1Copy.getAddress());

    从输出结构就可以看出,显然 person1 的克隆对象和 person1 包含的 Address 对象已经是不同的了。

    那什么是引用拷贝呢? 简单来说,引用拷贝就是两个不同的引用指向同一个对象。

    我专门画了一张图来描述浅拷贝、深拷贝、引用拷贝:

    2. Object

    2.1 Object 类的常见方法有哪些?

     Object 类是一个特殊的类,是所有类的父类。它主要提供了以下 11 个方法:

    1. /**
    2. * native 方法,用于返回当前运行时对象的 Class 对象,使用了 final 关键字修饰,故不允许子类重写。
    3. */
    4. public final native Class getClass()
    5. /**
    6. * native 方法,用于返回对象的哈希码,主要使用在哈希表中,比如 JDK 中的HashMap。
    7. */
    8. public native int hashCode()
    9. /**
    10. * 用于比较 2 个对象的内存地址是否相等,String 类对该方法进行了重写以用于比较字符串的值是否相等。
    11. */
    12. public boolean equals(Object obj)
    13. /**
    14. * native 方法,用于创建并返回当前对象的一份拷贝。
    15. */
    16. protected native Object clone() throws CloneNotSupportedException
    17. /**
    18. * 返回类的名字实例的哈希码的 16 进制的字符串。建议 Object 所有的子类都重写这个方法。
    19. */
    20. public String toString()
    21. /**
    22. * native 方法,并且不能重写。唤醒一个在此对象监视器上等待的线程(监视器相当于就是锁的概念)。如果有多个线程在等待只会任意唤醒一个。
    23. */
    24. public final native void notify()
    25. /**
    26. * native 方法,并且不能重写。跟 notify 一样,唯一的区别就是会唤醒在此对象监视器上等待的所有线程,而不是一个线程。
    27. */
    28. public final native void notifyAll()
    29. /**
    30. * native方法,并且不能重写。暂停线程的执行。注意:sleep 方法没有释放锁,而 wait 方法释放了锁 ,timeout 是等待时间。
    31. */
    32. public final native void wait(long timeout) throws InterruptedException
    33. /**
    34. * 多了 nanos 参数,这个参数表示额外时间(以纳秒为单位,范围是 0-999999)。 所以超时的时间还需要加上 nanos 纳秒。。
    35. */
    36. public final void wait(long timeout, int nanos) throws InterruptedException
    37. /**
    38. * 跟之前的2个wait方法一样,只不过该方法一直等待,没有超时时间这个概念
    39. */
    40. public final void wait() throws InterruptedException
    41. /**
    42. * 实例被垃圾回收器回收的时候触发的操作
    43. */
    44. protected void finalize() throws Throwable { }

    2.2 == 和 equals() 的区别

    先说==

    • 对于基本数据类型,==比较值
    • 对于引用数据类型,==比较地址

    equals() 不能用于判断基本数据类型的变量,只能用来判断两个对象是否相等。

    • 类没有重写 equals()方法:通过equals()比较该类的两个对象时,等价于通过“==”比较这两个对象,使用的默认是 Objectequals()方法。
    • 类重写了 equals()方法:一般我们都重写 equals()方法来比较两个对象中的属性是否相等;若它们的属性相等,则返回 true(即,认为这两个对象相等)。

    String 中的 equals 方法是被重写过的,因为 Object 的 equals 方法是比较的对象的内存地址,而 String 的 equals 方法比较的是对象的值。

    1. public boolean equals(Object anObject) {
    2. if (this == anObject) {
    3. return true;
    4. }
    5. if (anObject instanceof String) {
    6. String anotherString = (String)anObject;
    7. int n = value.length;
    8. if (n == anotherString.value.length) {
    9. char v1[] = value;
    10. char v2[] = anotherString.value;
    11. int i = 0;
    12. while (n-- != 0) {
    13. if (v1[i] != v2[i])
    14. return false;
    15. i++;
    16. }
    17. return true;
    18. }
    19. }
    20. return false;
    21. }

    2.3 hashCode() 有什么用?

    hashCode() 的作用是获取哈希码(int 整数),也称为散列码。这个哈希码的作用是确定该对象在哈希表中的索引位置。

    hashCode() 定义在 JDK 的 Object 类中,这就意味着 Java 中的任何类都包含有 hashCode() 函数。另外需要注意的是:ObjecthashCode() 方法是本地方法,也就是用 C 语言或 C++ 实现的。

    散列表存储的是键值对(key-value),它的特点是:能根据“键”快速的检索出对应的“值”。这其中就利用到了散列码!(可以快速找到所需要的对象)

    2.4 为什么要有 hashCode?

    我们以“HashSet 如何检查重复”为例子来说明为什么要有 hashCode

    当你把对象加入 HashSet 时,HashSet 会先计算对象的 hashCode 值来判断对象加入的位置,同时也会与其他已经加入的对象的 hashCode 值作比较,如果没有相符的 hashCodeHashSet 会假设对象没有重复出现。但是如果发现有相同 hashCode 值的对象,这时会调用 equals() 方法来检查 hashCode 相等的对象是否真的相同。如果两者相同,HashSet 就不会让其加入操作成功。如果不同的话,就会重新散列到其他位置。这样我们就大大减少了 equals 的次数,相应就大大提高了执行速度

    其实, hashCode() 和 equals()都是用于比较两个对象是否相等。

    2.4.1 那为什么 JDK 还要同时提供这两个方法呢?

    这是因为在一些容器(比如 HashMapHashSet)中,有了 hashCode() 之后,判断元素是否在对应容器中的效率会更高(参考添加元素进HashSet的过程)!

    我们在前面也提到了添加元素进HashSet的过程,如果 HashSet 在对比的时候,同样的 hashCode 有多个对象,它会继续使用 equals() 来判断是否真的相同。也就是说 hashCode 帮助我们大大缩小了查找成本。

    2.4.2 那为什么不只提供 hashCode() 方法呢?

    这是因为两个对象的hashCode 值相等并不代表两个对象就相等。

    2.4.3 那为什么两个对象有相同的 hashCode 值,它们也不一定是相等的?

    因为 hashCode() 所使用的哈希算法也许刚好会让多个对象传回相同的哈希值。越糟糕的哈希算法越容易碰撞,但这也与数据值域分布的特性有关(所谓哈希碰撞也就是指的是不同的对象得到相同的 hashCode )。

    总结下来就是:

    • 如果两个对象的hashCode 值相等,那这两个对象不一定相等(哈希碰撞)。
    • 如果两个对象的hashCode 值相等并且equals()方法也返回 true,我们才认为这两个对象相等。
    • 如果两个对象的hashCode 值不相等,我们就可以直接认为这两个对象不相等。

    2.5 为什么重写 equals() 时必须重写 hashCode() 方法?

    因为两个相等的对象的 hashCode 值必须是相等。也就是说如果 equals 方法判断两个对象是相等的,那这两个对象的 hashCode 值也要相等。

    如果重写 equals() 时没有重写 hashCode() 方法的话就可能会导致 equals 方法判断是相等的两个对象,hashCode 值却不相等。

    3. String

    3.1 String、StringBuffer、StringBuilder 的区别?

    可变性

    String 是不可变的(后面会详细分析原因)。

    StringBuilderStringBuffer 都继承自 AbstractStringBuilder 类,在 AbstractStringBuilder 中也是使用字符数组保存字符串,不过没有使用 finalprivate 关键字修饰,最关键的是这个 AbstractStringBuilder 类还提供了很多修改字符串的方法比如 append 方法。

    1. abstract class AbstractStringBuilder implements Appendable, CharSequence {
    2. char[] value;
    3. public AbstractStringBuilder append(String str) {
    4. if (str == null)
    5. return appendNull();
    6. int len = str.length();
    7. ensureCapacityInternal(count + len);
    8. str.getChars(0, len, value, count);
    9. count += len;
    10. return this;
    11. }
    12. //...
    13. }

    线程安全性

    String 中的对象是不可变的,也就可以理解为常量,线程安全。AbstractStringBuilderStringBuilderStringBuffer 的公共父类,定义了一些字符串的基本操作,如 expandCapacityappendinsertindexOf 等公共方法。StringBuffer 对方法加了同步锁或者对调用的方法加了同步锁,所以是线程安全的。StringBuilder 并没有对方法进行加同步锁,所以是非线程安全的。

    性能

    每次对 String 类型进行改变的时候,都会生成一个新的 String 对象,然后将指针指向新的 String 对象。StringBuffer 每次都会对 StringBuffer 对象本身进行操作,而不是生成新的对象并改变对象引用。相同情况下使用 StringBuilder 相比使用 StringBuffer 仅能获得 10%~15% 左右的性能提升,但却要冒多线程不安全的风险。

    对于三者使用的总结:

    1. 操作少量的数据: 适用 String
    2. 单线程操作字符串缓冲区下操作大量数据: 适用 StringBuilder
    3. 多线程操作字符串缓冲区下操作大量数据: 适用 StringBuffer

    3.2 String 为什么是不可变的?

    String 类中使用 private  final 关键字修饰字符数组来保存字符串

    1. public final class String implements java.io.Serializable, Comparable, CharSequence {
    2. private final char value[];
    3. //...
    4. }

    我们知道被 final 关键字修饰的类不能被继承,修饰的方法不能被重写,修饰的变量是基本数据类型则值不能改变,修饰的变量是引用类型则不能再指向其他对象。因此,final 关键字修饰的数组保存字符串并不是 String 不可变的根本原因,因为这个数组保存的字符串是可变的(final 修饰引用类型变量的情况)。

    String 真正不可变有下面几点原因:

    1. 保存字符串的数组被 final 修饰且为私有的,并且String 类没有提供/暴露修改这个字符串的方法。
    2. String 类被 final 修饰导致其不能被继承,进而避免了子类破坏 String 不可变。

    在 Java 9 之后,StringStringBuilder 与 StringBuffer 的实现改用 byte 数组存储字符串。

    1. public final class String implements java.io.Serializable,Comparable, CharSequence {
    2. // @Stable 注解表示变量最多被修改一次,称为“稳定的”。
    3. @Stable
    4. private final byte[] value;
    5. }
    6. abstract class AbstractStringBuilder implements Appendable, CharSequence {
    7. byte[] value;
    8. }

    Java 9 为何要将 String 的底层实现由 char[] 改成了 byte[] ?

    新版的 String 其实支持两个编码方案:Latin-1 和 UTF-16。如果字符串中包含的汉字没有超过 Latin-1 可表示范围内的字符,那就会使用 Latin-1 作为编码方案。Latin-1 编码方案下,byte 占一个字节(8 位),char 占用 2 个字节(16),byte 相较 char 节省一半的内存空间。

    JDK 官方就说了绝大部分字符串对象只包含 Latin-1 可表示的字符。

    如果字符串中包含的汉字超过 Latin-1 可表示范围内的字符,byte 和 char 所占用的空间是一样的。

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

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

    1. String str1 = "he";
    2. String str2 = "llo";
    3. String str3 = "world";
    4. String str4 = str1 + str2 + str3;

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

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

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

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

     

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

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

    不过,使用 “+” 进行字符串拼接会产生大量的临时对象的问题在 JDK9 中得到了解决。在 JDK9 当中,字符串相加 “+” 改为了用动态方法 makeConcatWithConstants() 来实现,而不是大量的 StringBuilder 了。这个改进是 JDK9 的 JEP 280open in new window 提出的,这也意味着 JDK 9 之后,你可以放心使用“+” 进行字符串拼接了。关于这部分改进的详细介绍,推荐阅读这篇文章:还在无脑用 StringBuilder?来重温一下字符串拼接吧

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

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

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

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

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

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

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

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

    1、如果字符串常量池中不存在字符串对象“abc”的引用,那么它将首先在字符串常量池中创建,然后在堆空间中创建,因此将创建总共 2 个字符串对象。

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

    3.7  String#intern 方法有什么作用?

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

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

    示例代码(JDK 1.8) :

    1. // 在堆中创建字符串对象”Java“
    2. // 将字符串对象”Java“的引用保存在字符串常量池中
    3. String s1 = "Java";
    4. // 直接返回字符串常量池中字符串对象”Java“对应的引用
    5. String s2 = s1.intern();
    6. // 会在堆中在单独创建一个字符串对象
    7. String s3 = new String("Java");
    8. // 直接返回字符串常量池中字符串对象”Java“对应的引用
    9. String s4 = s3.intern();
    10. // s1 和 s2 指向的是堆中的同一个对象
    11. System.out.println(s1 == s2); // true
    12. // s3 和 s4 指向的是堆中不同的对象
    13. System.out.println(s3 == s4); // false
    14. // s1 和 s4 指向的是堆中的同一个对象
    15. System.out.println(s1 == s4); //true

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

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

    1. String str1 = "str";
    2. String str2 = "ing";
    3. String str3 = "str" + "ing";
    4. String str4 = str1 + str2;
    5. String str5 = "string";
    6. System.out.println(str3 == str4);//false
    7. System.out.println(str3 == str5);//true
    8. System.out.println(str4 == str5);//false

    注意:比较 String 字符串的值是否相等,可以使用 equals() 方法。 String 中的 equals 方法是被重写过的。 Objectequals 方法是比较的对象的内存地址,而 Stringequals 方法比较的是字符串的值是否相等。如果你使用 == 比较两个字符串是否相等的话,IDEA 还是提示你使用 equals() 方法替换

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

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

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

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

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

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

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

    对于String str4 = str1 + str2;

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

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

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

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

    示例代码:

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

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

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

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

    1. final String str1 = "str";
    2. final String str2 = getStr();
    3. String c = "str" + "ing";// 常量池中的对象
    4. String d = str1 + str2; // 在堆上创建的新的对象
    5. System.out.println(c == d);// false
    6. public static String getStr() {
    7. return "ing";
    8. }
  • 相关阅读:
    LinkedList与链表
    正统类加载器Tomcat(tomcat二探)
    每日一题(刷题记录)
    Centos7安装RabbitMQ
    【创新项目探索】大数据服务omnidata-hive-connector介绍
    C语言--数组的长度计算【详细解释】
    redis : 持久化
    C++ Reference: Standard C++ Library reference: C Library: cwchar: wcstod
    【深入浅出向】从自信息到熵、从相对熵到交叉熵,nn.CrossEntropyLoss, 交叉熵损失函数与softmax,多标签分类
    微服务中使用Maven BOM来管理你的版本依赖
  • 原文地址:https://blog.csdn.net/weixin_45734473/article/details/133638731