Java八股(object)
object类的方法
Java Object 类是所有类的超类,默认提供 11 个核心方法,核心用于对象比较、哈希、字符串表示、线程同步等。
所有类都默认继承与Object类,所以可以直接在所有的类中去重写Object类中的默认方法。
equals 方法
默认实现是比较两个对象的内存地址,和== 的效果一样。在实际开发中如果需要按照对象的具体内容进行比较,那么就需要重写equals方法。
1 | public class User{ |
- Obejct 想要取出 id 字段,需要先强转成 User 类型。
- getClass() 默认是 this.getClass()。
hashcode 方法
和 equals 配套的必须重写 hashCode 方法,因为 Java 的约定是如果两个对象 equals 返回 true,它们的 hashCode 必须相等;如果 hashCode 不相等,equals 一定返回 false。
如果只重写 equals 不重写 hashCode,会导致对象在 HashMap HashSet 等集合中无法正确存储。
比如两个 id 相同的 User 对象,重写过的equals()会返回 true,但由于 hashCode 不同,会被当成两个不同元素存入集合。重写示例:
1 |
|
toString 方法
默认返回类名加 @加对象的哈希码十六进制表示,比如 User@1b6d3586,可读性很差。实际开发中重写它是为了返回对象的具体信息,方便日志打印和调试
1 |
|
getClass 方法
返回对象运行时的实际类对象,和编译时类型可能不同,而且这个方法不能重写·
比如父类 Animal 的子类 Dog,Animal animal = new Dog() ,animal.getClass() 返回的是 Dog 的类对象,常用于反射场景,比如通过 getClass 获取类的属性和方法:
1 | Animal animal = new Dog(); |
本例子中涉及的Class类,是在运行时描述“某个类型本身”的信息。animal.getClass()返回的是一个对象,需要转成Class才能拿到属于它的本身信息。比如他的类名,他实现的父类和接口、类中的成员方法和成员字段等等。
可以利用 Class 完成反射过程。
Class类中的方法有:
- getName():拿到类名
- getSuperclass():拿到父类
- getInterfaces():拿实现的接口
- getDeclaredFields():拿到字段
- getDeclaredMethods():拿到方法
- getConstructor():拿到构造器
- newInstance() / getConstructor().newInstance() :通过反射创建对象
clone 方法
clone方法是默认的浅拷贝。如果对象中存在引用数据类型,拷贝对象会和原来的对象共用该引用类型。
在这里我重新搞清楚了浅拷贝的“引用数据类型”共用的意义。
假设,当前存在这样一个类:
1 | class User implements Cloneable { |
那么在将已存在的user1克隆一个user2的时候:
1 | User user2 = (User) user1.clone(); |
- 基本类型
int age
拷贝的是数值
两个对象完全独立 - 引用类型
String name,Double score
拷贝的是“对象引用地址”- 改引用对象内部的数据会影响彼此
- 但重新赋值引用变量不会共享
1 | User user1 = new User(); |
如果改动user2 的age和name:
1 | user2.age = 20; // user1.age 还是 18 ✔️ |
什么时候浅拷贝会“联动修改”?
当用可变对象 mutable object的时候会联动修改,比如List类型的对象。
1 | class User { |
结果在u1.tags中我们也能看到”A”。
对于以下类型的引用类型对象会发生联动修改:
StringBuilderCustomObjectArrayList/Map/SetObject[]- 任何自己 new 出来的 mutable 对象
为什么String不会跟着改变
String 确实是引用类型。但是它有两个特点:是引用类型,但 immutable(不可变)。
String重新赋值的时候实际上是在堆内存中重新开辟了一个空间,然后让clone的对象指向这一块堆内存。
深克隆的三种方法
可以参见文章:
notify 和 notifyAll 方法
它们都是用于多线程同步的,和 synchronized 配合使用,作用是唤醒等待当前对象锁的线程。notify 是随机唤醒一个等待线程,notifyAll 是唤醒所有等待线程,比如生产者消费者模式中,生产者生产完数据后调用 notifyAll,唤醒等待的消费者线程。
wait 方法
wait 方法有三个重载,作用是让当前持有对象锁的线程释放锁并进入等待状态,直到被 notify notifyAll 唤醒或者等待时间到期。同样需要在 synchronized 同步块或方法中使用。
== 与 equals 方法的区别
对字符串类型
对于字符串变量来说,使用”==”和”equals”比较字符串时,其比较方法是不同的。
“==”比较两个变量本身的值,即两个对象在内存中的首地址,**”equals”比较字符串包含内容是否相同**。
对非字符串类型
对于非字符串变量来说,如果没有对equals()进行重写的话,”==” 和 “equals”方法的作用是相同的。
用来比较对象在堆内存中的首地址,即用来比较两个引用变量是否指向同一个对象。
hashcode 和 equals 方法的关系
在 Java 中,对于重写 equals 方法的类,通常也需要重写 hashCode 方法,并且需要遵循以下规定:
- 一致性:如果两个对象使用
equals方法比较结果为true,那么它们的hashCode值必须相同。也就是说,如果obj1.equals(obj2)返回true,那么obj1.hashCode()必须等于obj2.hashCode()。 - 非一致性:如果两个对象的
hashCode值相同,它们使用equals方法比较的结果不一定为true。即obj1.hashCode() == obj2.hashCode()时,obj1.equals(obj2)可能为false,这种情况称为哈希冲突。
hashCode 和 equals 方法是紧密相关的,重写 equals 方法时必须重写 hashCode 方法,以保证在使用哈希表等数据结构时,对象的相等性判断和存储查找操作能够正常工作。而重写 hashCode 方法时,需要确保相等的对象具有相同的哈希码,但相同哈希码的对象不一定相等。
Java 中 String 类的常用方法
- 获取长度:
int length():返回字符串的长度(字符个数)。例:"abc".length()→ 3 - 判断内容:
boolean equals(Object obj):比较两个字符串的内容是否完全相同(区分大小写)。例:"abc".equals("ABC")→ false - 截取子串:
String substring(int beginIndex):从指定索引开始截取到末尾。例:"hello".substring(2)→ “llo” - 去除空格:
String trim():去除字符串首尾的空白字符(空格、制表符等)。例:" abc ".trim()→ “abc” - 替换内容:
String replace(char oldChar, char newChar):替换所有指定字符。例:"aaa".replace('a', 'b')→ “bbb” - 判断空字符串:
boolean isEmpty():判断字符串长度是否为 0(注意:null调用会报错,需先判空)。
String、StringBuffer、StringBuilder的区别和联系
1、可变性 :String 是不可变的(Immutable),一旦创建,内容无法修改,每次修改都会生成一个新的对象。StringBuilder 和 StringBuffer 是可变的(Mutable),可以直接对字符串内容进行修改而不会创建新对象。
2、线程安全性 :String 因为不可变,天然线程安全。StringBuilder 不是线程安全的,适用于单线程环境。StringBuffer 是线程安全的,其方法通过 synchronized 关键字实现同步,适用于多线程环境。
3、性能 :String 性能最低,尤其是在频繁修改字符串时会生成大量临时对象,增加内存开销和垃圾回收压力。StringBuilder 性能最高,因为它没有线程安全的开销,适合单线程下的字符串操作。StringBuffer 性能略低于 StringBuilder,因为它的线程安全机制引入了同步开销。
4、使用场景 :如果字符串内容固定或不常变化,优先使用 String。如果需要频繁修改字符串且在单线程环境下,使用 StringBuilder。如果需要频繁修改字符串且在多线程环境下,使用 StringBuffer。
| 特性 | String | StringBuilder | StringBuffer |
|---|---|---|---|
| 不可变性 | 不可变 | 可变 | 可变 |
| 线程安全 | 是(因不可变) | 否 | 是(同步方法) |
| 性能 | 低(频繁修改时) | 高(单线程) | 中(多线程安全) |
| 适用场景 | 静态字符串 | 单线程动态字符串 | 多线程动态字符串 |