重写和重载
不同点 | 重载 | 重写 |
---|---|---|
发生范围 | 同一个类 | 子类 |
参数列表 | 必须修改 | 不可修改 |
返回类型 | 随便修改 | 子类要比父类小(void 不可改) |
抛出异常 | 随便修改 | 子类要比父类小 |
修饰符 | 随便修改 | 可降低限制 |
发生阶段 | 编译器 | 运行期 |
泛型擦除
泛型提供了编译期间类型安全检测机制,该机制允许程序员在编译期间检测到非法的类型。
Java 的泛型是伪泛型,在编译后的字节码,所有的泛型信息都会被擦掉。但是为了反编译时识别泛型 LocalVariableTable 中的 Signature 会保留泛型信息。
List<Integer> l1 = new ArrayList<>();
List l2 = l1;
l2.add('a');
上述代码编译后再反编译可得下面的代码
List<Integer> l1 = new ArrayList();
l1.add('a');
扩展阅读
深入探索Java泛型的本质 | 泛型
== 和 equals
基本数据类型:== 比值
引用数据类型:== 比地址
对于没有重写的类,其 equals 继承自 Object,而 Object 的 equals 用的是 ==
hashCode 和 equals
对于 HashMap、HashSet 这类容器而言,需要先使用 hashCode 判断在容器中是否存在 hashCode 相同的元素,如果存在再使用 equals 比较是否是相同元素。
hashCode 相同,可能是因为 hash 碰撞导致的。所以 equals 判断相等的两个对象,其 hashCode 也相等。而 hashCode 相等的对象,equals 判断未必会相等。
hashCode 的作用是降低所需要比较的成本。如果不使用基于 Hash 的容器,无需重写 hashCode。
扩展阅读
Java hashCode() 和 equals()的若干问题解答
包装类常量池
Byte、Short、Integer、Long 常量池中缓存了 − 128 ~ 127 -128 ~ 127 −128~127 ,而 Character 缓存了 0 ~ 127 0~127 0~127。
Boolean、Float、Double 无常量池。
自动装拆箱
- 装箱:将基本类型用它们对应的引用类型包装起来。调用包装类的 valueOf 方法
- 拆箱:将包装类型转换为基本数据类型。调用了 xxxValue 方法
频繁的自动装拆箱会严重影响性能。
构造方法能否被重写(override)
构造方法不能重写。因为重写必须发生在继承的基础上,而构造方法又无法被继承,所以不能重写。
虽然构造方法不能重写,但可以重载。
String、Stirng Builder 和 String Buffer
String 不可变性
String 不可变指的是对 String 二次赋值不是在原内存地址上修改数据,而是重新指向一个新对象。
- 底层字符数组是 final,导致数组引用不可变,但数组元素是可变的。
- 底层字符数组是 private,也没提供暴露出去修改的方法。所以就杜绝了数组元素可变的情况。
- 上述两点足以保证 String 不可变,但是仍然在类上加了 final 完全杜绝被子类破坏。
不可变性带来的好处
- 多线程安全:在多线程环境下,只有写操作才会引起安全问题,然而 String 是不可写的所以在多线程下是安全的。
- 字符串常量池:字符串不可变,所以可以利用常量池公用一个串,从而节省内存空间。
扩展:反射改变 Stirng 底层字符数组
- 所有引用相同字符串常量池的 String,都会发生变化
- String 的 hashCode 只会计算一次,所以不会出现改变前后 hashCode 对不上的问题。
不可变性带来的弊端
由于 String 不可变,每次对 String
类型进行改变的时候,都会生成一个新的 String
对象,然后将指针指向新的 String
对象,开销较大。
从而引入了 StringBuilder 和 StringBuffer 来解决频繁修改字符串时带来的开销问题。StringBuilder 是线程不安全的,StringBuffer 是线程安全的。
综上所述:
- 对字符串修改不频繁,使用 String
- 对字符串修改频繁且在单线程环境下,使用 StirngBuilder
- 对字符串修改频繁且在多线程环境下,使用 StringBuffer
扩展阅读
如何理解 String 类型值的不可变? - 知乎提问
反射能否改变 final 字段
final 修饰的字段代表该字段引用不能改变,但引用对象本体可以改变,所以 final 能够被反射改变。
但对于基本数据类型和 String 来说,在编译时会做内联优化,所以从结果上来说就失效了。
public class A {
final String s = "a";
final int i = 1;
public String getS() {
return s;
}
public int getI() {
return i;
}
}
编译后再反编译可得
public class A {
final String s = "a";
final int i = 1;
public A() {
}
public String getS() {
return "a";
}
public int getI() {
return 1;
}
}
深拷贝、潜拷贝、引用拷贝
深拷贝:堆内新创建的对象里的所有字段都是新创建的。
潜拷贝:堆内新创建的对象里的所有字段都是引用旧对象的字段。
引用拷贝:压根没在堆内创建新对象,而是直接引用了老对象。