1.如何理解根父类
类 java.lang.Object
是类层次结构的根类,即所有类的父类。每个类都使用 Object
作为超类。
-
Object类型的变量与除Object以外的任意引用数据类型的对象都多态引用
-
所有对象(包括数组)都实现这个类的方法。
-
如果一个类没有特别指定父类,那么默认则继承自Object类。例如:
public class MyClass /*extends Object*/ {
// ...
}
2.Object类的其中5个方法
API(Application Programming Interface),应用程序编程接口。Java API是一本程序员的字典
,是JDK中提供给我们使用的类的说明文档。所以我们可以通过查询API的方式,来学习Java提供的类,并得知如何使用它们。在API文档中是无法得知这些类具体是如何实现的,如果要查看具体实现代码,那么我们需要查看src源码。
根据JDK源代码及Object类的API文档,Object类当中包含的方法有11个。今天我们主要学习其中的5个:
(1)toString()
方法签名:public String toString()
①默认情况下,toString()返回的是“对象的运行时类型 @ 对象的hashCode值的十六进制形式"
②通常是建议重写
③如果我们直接System.out.println(对象),默认会自动调用这个对象的toString()
因为Java的引用数据类型的变量中存储的实际上时对象的内存地址,但是Java对程序员隐藏内存地址信息,所以不能直接将内存地址显示出来,所以当你打印对象时,JVM帮你调用了对象的toString()。
例如自定义的Person类:
public class Person {
private String name;
private int age;
@Override
public String toString() {
return "Person{" + "name='" + name + '\'' + ", age=" + age + '}';
}
}
(2)getClass()
public final Class<?> getClass():获取对象的运行时类型
因为Java有多态现象,所以一个引用数据类型的变量的编译时类型与运行时类型可能不一致,因此如果需要查看这个变量实际指向的对象的类型,需要用getClass()方法
public static void main(String[] args) {
Object obj = new Person();
System.out.println(obj.getClass());//运行时类型
}
(3)equals()
public boolean equals(Object obj):用于判断当前对象this与指定对象obj是否“相等”
①默认情况下,equals方法的实现等价于与“==”,比较的是对象的地址值
②我们可以选择重写,重写有些要求:
A:
B:如果重写equals,那么一定要遵循如下几个原则:
a:自反性:x.equals(x)返回true
b:传递性:x.equals(y)为true, y.equals(z)为true,然后x.equals(z)也应该为true
c:一致性:只要参与equals比较的属性值没有修改,那么无论何时调用结果应该一致
d:对称性:x.equals(y)与y.equals(x)结果应该一样
e:非空对象与null的equals一定是false
class User{
private String host;
private String username;
private String password;
public User(String host, String username, String password) {
super();
this.host = host;
this.username = username;
this.password = password;
}
public User() {
super();
}
public String getHost() {
return host;
}
public void setHost(String host) {
this.host = host;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
@Override
public String toString() {
return "User [host=" + host + ", username=" + username + ", password=" + password + "]";
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((host == null) ? 0 : host.hashCode());
result = prime * result + ((password == null) ? 0 : password.hashCode());
result = prime * result + ((username == null) ? 0 : username.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
User other = (User) obj;
if (host == null) {
if (other.host != null)
return false;
} else if (!host.equals(other.host))
return false;
if (password == null) {
if (other.password != null)
return false;
} else if (!password.equals(other.password))
return false;
if (username == null) {
if (other.username != null)
return false;
} else if (!username.equals(other.username))
return false;
return true;
}
}
(4)hashCode()
public int hashCode():返回每个对象的hash值。
如果重写equals,那么通常会一起重写hashCode()方法,hashCode()方法主要是为了当对象存储到哈希表(后面集合章节学习)等容器中时提高存储和查询性能用的,这是因为关于hashCode有两个常规协定:
-
①如果两个对象的hash值是不同的,那么这两个对象一定不相等;
-
②如果两个对象的hash值是相同的,那么这两个对象不一定相等。
重写equals和hashCode方法时,要保证满足如下要求:
-
①如果两个对象调用equals返回true,那么要求这两个对象的hashCode值一定是相等的;
-
②如果两个对象的hashCode值不同的,那么要求这个两个对象调用equals方法一定是false;
-
③如果两个对象的hashCode值相同的,那么这个两个对象调用equals可能是true,也可能是false
public static void main(String[] args) {
System.out.println("Aa".hashCode());//2112
System.out.println("BB".hashCode());//2112
}
(5)finalize()
protected void finalize():用于最终清理内存的方法
演示finalize()方法被调用:
public class TestFinalize {
public static void main(String[] args) throws Throwable{
for (int i=1; i <=10; i++){
MyDemo my = new MyDemo(i);
//每一次循环my就会指向新的对象,那么上次的对象就没有变量引用它了,就成垃圾对象
}
//为了看到垃圾回收器工作,我要加下面的代码,让main方法不那么快结束,因为main结束就会导致JVM退出,GC也会跟着结束。
System.gc();//如果不调用这句代码,GC可能不工作,因为当前内存很充足,GC就觉得不着急回收垃圾对象。
//调用这句代码,会让GC尽快来工作。
Thread.sleep(5000);//单位是毫秒,让当前程序休眠5秒再结束
}
}
class MyDemo{
private int value;
public MyDemo(int value) {
this.value = value;
}
@Override
public String toString() {
return "MyDemo{" + "value=" + value + '}';
}
//重写finalize方法,让大家看一下它的调用效果
@Override
protected void finalize() throws Throwable {
// 正常重写,这里是编写清理系统内存的代码
// 这里写输出语句是为了看到finalize()方法被调用的效果
System.out.println(this+ "轻轻的走了,不带走一段代码....");
}
}
每一个对象的finalize()只会被调用一次,哪怕它多次被标记为垃圾对象。当一个对象没有有效的引用/变量指向它,那么这个对象就是垃圾对象。GC(垃圾回收器)通常会在第一次回收某个垃圾对象之前,先调用一下它的finalize()方法,然后再彻底回收它。但是如果在finalize()方法,这个垃圾对象“复活”了(即在finalize()方法中意外的又有某个引用指向了当前对象,这是要避免的),被“复活”的对象如果再次称为垃圾对象,GC就不再调用它的finalize方法了,避免这个对象称为“僵尸”。
public class TestFinalize {
private static MyDemo[] arr = new MyDemo[10];
private static int total;
public static void add(MyDemo demo){
arr[total++] = demo;
}
public static void main(String[] args) throws Throwable{
for (int i=1; i <=10; i++){
MyDemo my = new MyDemo(i);
//每一次循环my就会指向新的对象,那么上次的对象就没有变量引用它了,就成垃圾对象
}
//为了看到垃圾回收器工作,我要加下面的代码,让main方法不那么快结束,因为main结束就会导致JVM退出,GC也会跟着结束。
System.gc();//如果不调用这句代码,GC可能不工作,因为当前内存很充足,GC就觉得不着急回收垃圾对象。
//调用这句代码,会让GC尽快来工作。
Thread.sleep(5000);//单位是毫秒,让当前程序休眠5秒再结束
for (int i = 0; i < arr.length; i++) {
System.out.println(arr[i]);//MyDemo的对象还在,没有被回收掉,因为在回收过程中被复活了
}
for (int i = 0; i < arr.length; i++) {
arr[i] = null;//让这些元素不引用MyDemo的对象,这些对象再次称为垃圾对象
System.out.println(arr[i]);
}
arr = null;
System.gc();//再次让GC工作,使得MyDemo的对象再次被回收
Thread.sleep(5000);//单位是毫秒,让当前程序休眠5秒再结束
}
}
class MyDemo{
private int value;
public MyDemo(int value) {
this.value = value;
}
@Override
public String toString() {
return "MyDemo{" + "value=" + value + '}';
}
//重写finalize方法,让大家看一下它的调用效果
@Override
protected void finalize() throws Throwable {
// 正常重写,这里是编写清理系统内存的代码
// 这里写输出语句是为了看到finalize()方法被调用的效果
System.out.println("我轻轻的走了,不带走一段代码....");
TestFinalize.add(this);
//把当前对象this放到一个数组中,这样就有变量引用它,当前对象就不能被回收了
//当下次this对象再次称为垃圾对象之后,GC就不会调用它的finalize()方法了
}
}
面试题:对finalize()的理解?
-
当对象被GC确定为要被回收的垃圾,在回收之前由GC帮你调用这个方法,不是由程序员手动调用。
-
这个方法与C语言的析构函数不同,C语言的析构函数被调用,那么对象一定被销毁,内存被回收,而finalize方法的调用不一定会销毁当前对象,因为可能在finalize()中出现了让当前对象“复活”的代码
-
每一个对象的finalize方法只会被调用一次。
-
子类可以选择重写,一般用于彻底释放一些资源对象,而且这些资源对象往往时通过C/C++等代码申请的资源内存
(6)重写toString、equals和hashCode方法(Alt+Insert)
建议使用IDEA中的Alt + Insert快捷键,而不是Ctrl + O快捷键。
3.标准JavaBean
JavaBean
是 Java语言编写类的一种标准规范。符合JavaBean
的类,要求:
(1)类必须是具体的和公共的,
(2)并且具有无参数的构造方法,
(3)成员变量私有化,并提供用来操作成员变量的set
和get
方法。
(4)重写toString方法
public class ClassName{
//成员变量
//构造方法
//无参构造方法【必须】
//有参构造方法【建议】
//getXxx()
//setXxx()
//其他成员方法
}
编写符合JavaBean
规范的类,以学生类为例,标准代码如下:
public class Student {
// 成员变量
private String name;
private int age;
// 构造方法
public Student() {
}
public Student(String name, int age) {
this.name = name;
this.age = age;
}
// get/set成员方法
public void setName(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setAge(int age) {
this.age = age;
}
public int getAge() {
return age;
}
//其他成员方法列表
public String toString(){
return "姓名:" + name + ",年龄:" + age;
}
}
测试类,代码如下:
public class TestStudent {
public static void main(String[] args) {
// 无参构造使用
Student s = new Student();
s.setName("柳岩");
s.setAge(18);
System.out.println(s.getName() + "---" + s.getAge());
System.out.println(s);
// 带参构造使用
Student s2 = new Student("赵丽颖", 18);
System.out.println(s2.getName() + "---" + s2.getAge());
System.out.println(s2);
}
}