Java从零开始 第9讲 Object类,内部类
Object类
Object类是所有类的父类,即你创建一个没有继承的类,此类就默认继承Object类。
Object也可以用于接收数据,使用Object可以接收任意的应用数据类型
Object类是一个官方给出的,已经定义好的类,通过Ctrl + 左键 点击声明语句中的Object(如下)就可以查看Object类的源码
toString和equals方法
Object类中有一些十分重要的方法,其中就包括toString和equals,这两种方法都需要重写来对他们进行提升
toString方法
toString方法是在print类中默认调用的
package oop;
public class OOPSenior {
public static void main(String[] args) {
Father father = new Father();
System.out.println(father);
System.out.println(father.toString());
}
}
class Father{
void say(){
System.out.println("父类中say方法被调用了");
}
}
运行结果:
oop.Father@b4c966a
oop.Father@b4c966a
Process finished with exit code 0
注意在以上代码中,System.out.println(father);和System.out.println(father.toString());输出了相同的结果,即输出father时默认调用了toString方法
toString在Object类中的源码十分简单:
public String toString() {
return getClass().getName() + "@" + Integer.toHexString(hashCode());
}
所以为了给类一个更好的描述,推荐给每个类都重写toString方法,这样直接执行System.out.println(类名);的时候,就会运行你重写的toString内容
package oop;
public class OOPSenior {
public static void main(String[] args) {
Father father = new Father();
System.out.println(father);
System.out.println(father.toString());
}
}
class Father{
void say(){
System.out.println("父类中say方法被调用了");
}
@Override
public String toString(){
return "这是father类";// 可以写任何你想要输出的
}
}
运行结果:
这是father类
这是father类
Process finished with exit code 0
equals方法
在Object类源码中equals类也很简单
public boolean equals(Object obj) {
return (this == obj);
}
让我们同样看看equals能来代替什么:
public class OOPSenior {
public static void main(String[] args) {
Father father1 = new Father("大明");
Father father2 = new Father("大明");
System.out.println(father1 == father2);
System.out.println(father1.equals(father2));
}
}
class Father{
String name;
Father(String name){
this.name = name;
}
}
运行结果:
false
false
Process finished with exit code 0
在这里,两个对象拥有相同的名字,但是仍然不相等,因为他们有着不同的内存地址,这意味着无论两个对象属性多么一致,都会返回false,除非一个对象跟他自己比。
所以我们需要重写equals方法来改善这一点
public class OOPSenior {
public static void main(String[] args) {
Father father1 = new Father("大明");
Father father2 = new Father("大明");
System.out.println(father1 == father2);
System.out.println(father1.equals(father2));
}
}
class Father{
String name;
Father(String name){
this.name = name;
}
@Override
public boolean equals(Object obj){
if(this == obj)// 如果这是一个对象和自己比较
return true;
if(obj == null)// 如果两个对象都非空,当然this肯定非空可以省略
return false;
if(obj instanceof Father){// 如果传入的类也是Father类
if(this.name.equals(((Father) obj).name))// 这里写具体判断条件,这里我用名字相同作为判断条件
return true;
}
return false;
}
}
运行结果:
false
true
Process finished with exit code 0
可以看到通过equals已经能够正确的比较我们希望比较的值了
(特别注意:这里的 ==方法仍然返回的是false,和toString方法不一样,equals并不是完全和 ==符号等效)
内部类
内部类,即为定义在另一个类或者方法里面的类,所以包括内部类的类可以被称为外部类
内部类主要分为四种:成员内部类,局部内部类,匿名内部类,和静态内部类,让我们分别介绍他们
(特别注意:这四个类型并不是完全平行的,如匿名内部类是局部内部类的一种)
成员内部类
成员内部类即跟成员属性有相同位置,直接定义在另一个类下的类。成员内部类可以访问其外部类的所有属性和方法(即使是private修饰的),内部类通过如下方式创建对象(外部类名为Outter,内部类为Inner):
Outter outter = new Outter();
Outter.Inner inner = outter.new Inner();
在内部类和外部类有同样名字的属性或方法时,会优先访问内部类,如果要访问外部类,则需要:
外部类.this.成员变量
外部类.this.成员方法
局部内部类
局部内部类即被定义在一个方法中的类,局部内部类就像是一个局部变量,仅能作用于该方法内,因此不能用任何权限修饰符修饰
虽然看起来局部内部类作用很鸡肋,但是局部内部类的存在并不是毫无作用,而是为了让代码更适应于抽象的方法,如果一些方法在其被抽象定义的时候就要求传入一个类的对象时,你可以通过定义局部内部类的方法传入对象
匿名内部类
我们之前曾经讲过匿名对象,匿名内部类跟匿名对象很像,都只能使用一次,创建一次对象之后便不能再使用。匿名内部类的定义不需要class,只需要new:
new 父类构造器(参数列表) |实现接口() { //匿名内部类的类体部分
}
匿名内部类必须满足以下条件:
- 继承一个类或者实现一个接口(二选其一)
- 不能定义构造函数
- 不能存在静态成员变量或方法
- 不能是抽象的
- 只能访问final类型的局部变量
- 必须符合所有对局部内部类的限制
其他都很好理解,但是注意第四条,只能访问final类型的局部变量,这其实是因为匿名内部类在被JVM编译时会单独作为一份class的字节码文件存在,而在运行过程中,非final定义的变量可能会变化,但是该class字节码文件已经备份,无法更改其中的量,所以Java语言规定了匿名内部类中只能使用final修饰的变量
(特别注意:如果你在外定义了一个变量并且在匿名内部类中被使用,该变量自动视为被final修饰了)
静态内部类
静态内部类也是定义在另一个类里面的类,只不过加了一个static修饰,和静态成员属性一样,静态内部类也是不需要依赖于外部类对象的(直接创建静态内部类的对象),并且同样无法使用非static的变量或者方法