目录
一、如何理解根父类
- 类
java.lang.Object
是类层次结构的根类,即所有其它类的父类 - 每个类都使用
Object
作为超类
- 所有对象(包括数组)都实现这个类的方法
- 如果一个类没有特别指定父类,那么默认则继承自Object类
例如:
public class Person {
...
}
//等价于:
public class Person extends Object {
...
}
二、Object类的方法
1、(重点)equals()
1.1、= =
- 基本数据类型比较值:只要两个变量的
值相等
,即为true
int a = 5;
if(a == 6){…}
- 引用类型比较引用(是否指向同一个对象):只有
指向同一个对象
时,==才返回true
Person p1 = new Person();
Person p2 = new Person();
if (p1==p2){…}
1.2、equals()
- 所有类都继承了Object,也就获得了equals()方法。还可以重写
Object类源码中equals()的作用与“==”相同
:比较是否指向同一个对象
- 对于File、String、Date,用equals()方法进行比较时
- 是比较类型及内容而不考虑引用的是否是同一个对象
- 原因:在这些类中重写了Object类的equals()方法
- 当自定义对象使用equals()时,需要重写。用于比较两个对象的
“内容”
是否都相等
自定义类重写equal方法
public class User {
private Integer id;
private String username;
private String password;
@Override
public boolean equals(Object o) {
// 如果是同一个对象,直接返回true
if (this == o) {
return true;
}
// 如果传入的对象是null或者不是同一个类,直接返回false
if (o == null || getClass() != o.getClass()) {
return false;
}
// 如果传入的对象是同一个类,所有属性的做比较
User user = (User) o;
return Objects.equals(id, user.id)
&& Objects.equals(username, user.username)
&& Objects.equals(password, user.password);
}
}
练习
int it = 65;
float fl = 65.0f;
System.out.println(“65和65.0f是否相等?” + (it == fl)); // true
char ch1 = 'A'; char ch2 = 12;
System.out.println("65和'A'是否相等?" + (it == ch1)); // true
System.out.println("12和ch2是否相等?" + (12 == ch2)); // true
String str1 = new String("hello");
String str2 = new String("hello");
System.out.println("str1和str2是否相等?"+ (str1 == str2)); // false
System.out.println("str1是否equals str2?"+(str1.equals(str2))); // true
1.3、==和equals的区别
- == 既可以比较基本类型也可以比较引用类型
- 对于基本类型就是比较值
- 对于引用类型就是比较内存地址
- equals是java.lang.Object类里面的方法
- 如果该方法没有被重写过默认也是==
- 一般情况重写equals方法,会比较类中的相应属性是否都相等
2、(重点)hashCode()
2.1、hashCode作用及与equal的区别
- hashCode()和equals()的作用都是用来
比较两个对象是否相等
- hashCode()是通过将对象的内部地址(物理地址)转换成一个整数
- 然后将这个整数通过hash函数的算法返回一个hashcode
- 再比较时通过比较 hashCode 来判断对象是否相等
- 因此在效率上,hashCode()的效率是大于equals()的
- 但是hashCode()可能会发生
hash冲突
,导致有时候值不同,而hashCode是相同的- 因此在准确度上,equals()的准确度大于hashCode()
总结:
- 如果两个对象通过equals()判断相等,那么这两个对象的hashCode一定也相等
- 如果两个对象的hashCode相等,这两个对象不一定相等
实际应用:
- 我们可以先用hashCode()去进行对比
- 如果hashCode不等,则两个对象不相等
- 如果hashCode相等,我们再调用 equals() 进行深度对比
- 这样既能保证
效率
,又能保证精准
2.2、常用的哈希码的算法
- Object类的hashCode,返回对象的
内存地址转换为整数
,再通过hash函数计算出一个hashCode,由于每个对象的内存地址都不一样,所以哈希码也不一样 - String类的hashCode,计算字符串中
每个字符的ASCII码的总和
- Integer类的hashCode,返回的就是Integer对象里所包含的那个
整数的数值
2.3、举例-HashSet插入对象
- User实体类,重写equal方法和hashCode方法
@Data
public class User {
private Integer id;
private String username;
private String password;
public User(Integer id, String username) {
this.id = id;
this.username = username;
}
@Override
public boolean equals(Object o) {
System.out.println("equals...");
// 如果是同一个对象,直接返回true
if (this == o) {
return true;
}
// 如果传入的对象是null或者不是同一个类,直接返回false
if (o == null || getClass() != o.getClass()) {
return false;
}
// 如果传入的对象是同一个类,所有属性的做比较
User user = (User) o;
return Objects.equals(id, user.id)
&& Objects.equals(username, user.username)
&& Objects.equals(password, user.password);
}
@Override
public int hashCode() {
System.out.println("hashCode...");
return Objects.hash(id, username, password);
}
}
// Objects类方法
public static int hash(Object... values) {
return Arrays.hashCode(values);
}
// Arrays类方法
public static int hashCode(Object a[]) {
if (a == null){
return 0;
}
int result = 1;
for (Object element : a){
// 31这个数字是为了尽量的减少hash冲突
result = 31 * result + (element == null ? 0 : element.hashCode());
}
return result;
}
public class Test {
public static void main(String[] args) {
Set<User> set = new HashSet<>();
User u1 = new User(3,"张三");
User u2 = new User(3,"张三");
set.add(u1);
set.add(u2);
System.out.println("set size:"+ set.size());
}
}
输出结果:
- 插入u1只需要调用hashCode获取对应下标位置
- 插入u2调用hashCode发现对应位置有数据也就是u1,然后会去调用equal比较,内容相同则表示同一个数据不插入,则集合数据只有一个
hashCode...
hashCode...
equals...
set size:1
3、toString()
- 默认情况下,toString()返回的是
“对象的运行时类型 @ 对象的hashCode值的十六进制形式"
- 在进行String与其它类型数据的连接操作时,自动调用toString()方法
- 如果我们直接System.out.println(对象),方法内会调用这个对象的toString()
Date now = new Date();
System.out.println(“now=” + now); //相当于
System.out.println(“now=” + now.toString());
- 如String 类重写了toString()方法,返回字符串的值
s1 = "hello";
System.out.println(s1);//相当于System.out.println(s1.toString());
自定义类重写toString方法:
public class Person {
private String name;
private int age;
@Override
public String toString() {
return "Person{" + "name='" + name + '\'' + ", age=" + age + '}';
}
}
4、clone()
- 实现了
Cloneable
接口的类 - 可以通过clone()方法克隆对象(
包括赋值的属性及方法
)
//Object类的clone()的使用
public class CloneTest {
public static void main(String[] args) {
Animal a1 = new Animal("花花");
try {
Animal a2 = (Animal) a1.clone();
// 原始对象:Animal [name=花花]
System.out.println("原始对象:" + a1);
// clone之后的对象:Animal [name=花花]
System.out.println("clone之后的对象:" + a2);
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
}
}
class Animal implements Cloneable{
private String name;
public Animal() {
super();
}
public Animal(String name) {
super();
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "Animal [name=" + name + "]";
}
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
5、finalize()
- 当对象被回收时,系统自动调用该对象的 finalize() 方法
(不是垃圾回收器调用的,是本类对象调用的)
- 永远不要主动调用某个对象的finalize方法,应该交给垃圾回收机制调用
- 什么时候被回收?
- 当某个对象没有任何引用时,JVM就认为这个对象是垃圾对象
- 就会在之后不确定的时间使用垃圾回收机制来销毁该对象
- 在销毁该对象前,会先调用 finalize()方法
- 子类可以重写finalize方法,目的是在对象被清理之前执行必要的清理操作
- 比如,在方法内断开相关连接资源
- 如果重写该方法,让一个新的引用变量重新引用该对象,则会重新激活对象
- 在JDK 9中此方法已经被
标记为过时
的
public class FinalizeTest {
public static void main(String[] args) {
Person p = new Person("Peter", 12);
System.out.println(p);
p = null;//此时对象实体就是垃圾对象,等待被回收。但时间不确定。
System.gc();//强制性释放空间
}
}
class Person{
private String name;
private int age;
public Person(String name, int age) {
super();
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
//子类重写此方法,可在释放对象前进行某些操作
@Override
protected void finalize() throws Throwable {
System.out.println("对象被释放--->" + this);
}
@Override
public String toString() {
return "Person [name=" + name + ", age=" + age + "]";
}
}
6、getClass()
- public final Class<?> getClass():获取对象的运行时类型
- 因为Java有多态现象,所以一个引用数据类型的变量的编译时类型与运行时类型可能
不一致
- 因此如果需要查看这个变量实际指向的对象的类型,需要用getClass()方法
public static void main(String[] args) {
Object obj = new Person();
System.out.println(obj.getClass());//运行时类型
}
结果:
class com.atguigu.java.Person
三、native关键字的理解
使用native关键字说明这个方法是原生函数,也就是这个方法是用C/C++
等非Java语言实现的,并且被编译成了DLL
,由Java去调用
- 本地方法是有方法体的,用c语言编写。由于本地方法的方法体源码没有对我们开源,所以我们看不到方法体
- 在Java中定义一个native方法时,并不提供实现体
为什么要用native方法
- Java使用起来非常方便,然而有些层次的任务用java实现起来不容易,或者我们对程序的效率很在意时
- 例如:Java需要与一些底层操作系统或某些硬件交换信息时的情况
- native方法正是这样一种交流机制:它为我们提供了一个非常简洁的接口,而且我们无需去了解Java应用之外的繁琐的细节
native声明的方法,对于调用者,可以当做和其他Java方法一样使用
- native method的存在并不会对其他类调用这些本地方法产生任何影响
- 实际上调用这些方法的其他类甚至不知道它所调用的是一个本地方法
- JVM将控制调用本地方法的所有细节