Object
我们上一篇文章说了继承,那么每一个类都是有自己的父类,就算你没有使用继承的关键字,那也是有默认的隐性继承类,它就是超父类Object,那在编写代码的时候为什么不在创建的类后面extends Object呢?
如果没有显式继承自其它非Object类,编译器默认会让这个类继承成Object类。这一点可以通过反编译.class文件确认。
那Object类有哪些属性和方法呢?
那属性真的是一个都没有,全都是方法,今天就简单说一些我们经常用的方法
getClass
getClass()
方法用于获取对象的运行时类,在反射的时候用的比较多。
注意到这个方法被final和native修饰,那说明
- 不能覆写
- 实现在C/C++层
返回的Class对象就是表示类静态同步方法锁定的对象。
hashCode
源码如下:
public native int hashCode();
hashCod()
方法用户获取对象的hash值。 在强烈依赖hashCode的地方是必须的。比如HashMap中对key进行hash计算,其实是利用key的hashCode。
这个方法的实现依然在native层,具体实现暂时不得而知。
equals
源码如下:
public boolean equals(Object obj) {
return (this == obj);
}
equals方法用于比较两个对象是否相等。
默认的实现就是通过比较引用来判断是不是相等,但是经过重写后比较的就是值,比如String方法里面源码如下:
public boolean equals(Object anObject) {
if (this == anObject) {
return true;
}
if (anObject instanceof String) {
String anotherString = (String)anObject;
int n = value.length;
if (n == anotherString.value.length) {
char v1[] = value;
char v2[] = anotherString.value;
int i = 0;
while (n-- != 0) {
if (v1[i] != v2[i])
return false;
i++;
}
return true;
}
}
return false;
}
在String中重写了Object中的equals,那么比较的是值,首先第一个if判断的是当前对象(谁调用equals方法就是谁)的地址和后面值(equals(这里的值))的地址是否一样,如果一样,既然地址都一样,那么值就一定相等。
如果不一样就要判断这个对象是不是和后面的对象是同一种类型,通过instanceof这个关键字来判断,比较的是前面的类型是否和后面的类型一样。如果一样就去比值,如果不一样就返回false了。
注意:地址相同,值一定相同;值相同的,地址不一定相同。
注意:重写equals方法之后,一定要重写hashCode!!!
那么什么一定要重写hashCode呢?
首先这个是Object规范中要求的,两个相等的实例必须有相同的hashCode。那我们探究一下根本原因。
以HashMap为例,上面提到对key进行hash,其实是你用了key的hashCode。HashMap中判断两个key是否相同,除了equals还要判断hashCode是否一致。如果没有覆写,本来我们本意认为是同一个key的,现在变成了两个,这个影响可能很小但也可能是灾难性的。
toString
源码如下:
public String toString() {
return getClass().getName() + "@" + Integer.toHexString(hashCode());
}
toString方法就是把对象用字符串的形式表示,这个在日志中使用较多,当然也可以是类型转换,比如Integer类。
默认的实现其实没有什么用,“类名@hashCode”,直接打印对象输出就是这样的格式。
官方文档建议最好覆写这个方法,返回一些有价值的、可读性强的信息。
还有几个方法:
- notify
- notifyAll
- wait
都是多线程里面的知识点了,等到讲多线程的时候,在详细讲解。
还有一个方法是:finalize,这个方法是GC准备回收对象的时候调用来执行清理工作的。很遗憾的是,Java语言不能保证finalize及时执行,也不能保证它会执行。如何能保证的话,大家可以去百度一下,这里就不进行过多的讲解了
多态
多态有一个前提,就是两个类之间产生继承关系才能够产生多态,继承在上篇文章就讲解到。
那什么叫做多态呢?
就是一个对象有多种表现形式或形态的能力叫做多态。
多态的优点
- 消除类型之间的耦合关系
- 可替换性
- 可扩充性
- 接口性
- 灵活性
- 简化性
多态存在的三个必要条件
- 继承
- 重写
- 父类引用指向子类对象
比如:
Parent p = new Son();
当使用多态方式调用方法时,首先检查父类中是否有该方法,如果没有,则编译错误;如果有,再去调用子类的同名方法。
多态的好处:可以使程序有良好的扩展,并可以对所有类的对象进行通用处理。举例说明一下:
public class Test {
public static void main(String[] args) {
show(new Cat()); // 以 Cat 对象调用 show 方法
show(new Dog()); // 以 Dog 对象调用 show 方法
Animal a = new Cat(); // 向上转型
a.eat(); // 调用的是 Cat 的 eat
Cat c = (Cat)a; // 向下转型
c.work(); // 调用的是 Cat 的 work
}
public static void show(Animal a) {
a.eat();
// 类型判断
if (a instanceof Cat) { // 猫做的事情
Cat c = (Cat)a;
c.work();
} else if (a instanceof Dog) { // 狗做的事情
Dog c = (Dog)a;
c.work();
}
}
}
abstract class Animal {
abstract void eat();
}
class Cat extends Animal {
public void eat() {
System.out.println("吃鱼");
}
public void work() {
System.out.println("抓老鼠");
}
}
class Dog extends Animal {
public void eat() {
System.out.println("吃骨头");
}
public void work() {
System.out.println("看家");
}
}
注意:当一个父类有两个子类的是,在我们多态状态下一个子类转换成父类的对象时,在通过变量名转换成另外一个子类的时候,编译是通过的,但是运行会报错。ClassCastException强制类型转换的时候产生的异常叫做类型不匹配。代码如下:
Peson p = new Son();
Son1 s = (son1)p;
多态的实现方式
- 重写
- 接口
- 抽象类和抽象方法
抽象类
在面向对象的概念中,所有的对象都是通过类来描绘的,但是反过来,并不是所有的类都是用来描绘对象的,如果一个类中没有包含足够的信息来描绘一个具体的对象,这样的类就是抽象类。
抽象类除了不能实例化对象之外,类的其它功能依然存在,成员变量、成员方法和构造方法的访问方式和普通类一样。
由于抽象类不能实例化对象,所以抽象类必须被继承,才能被使用。也是因为这个原因,通常在设计阶段决定要不要设计抽象类。
父类包含了子类集合的常见的方法,但是由于父类本身是抽象的,所以不能使用这些方法。
在Java中抽象类表示的是一种继承关系,一个类只能继承一个抽象类,而一个类却可以实现多个接口。
抽象类的关键字是:abstract
public abstract class Develop {
abstract void work();
abstract void sleep();
void test(){
System.out.println("哈哈哈,马上可以吃饭了...");
}
}
/*
* 具体子类
*/
public class Java extends Develop{
@Override
void work() {
System.out.println("边敲代码边睡觉");
}
void haha(){
System.out.println("聊天止于微笑");
}
@Override
void sleep() {
// TODO Auto-generated method stub
}
}
//抽象子类
abstract class Web extends Develop{
@Override
void work() {
System.out.println("网页开发");
}
//abstract void sleep();
void hehe(){
System.out.println("聊天止于呵呵");
}
}
//具体子类:
class Son extends Web{
@Override
void sleep() {
System.out.println("上课睡觉可真香!!!");
}
}
当一个类是抽象类的时候,如果子类继承了抽象类,就要把抽象父类中的抽象方法进行具体实现,如果没有把所有的方法具体实现, 编译时报错的,除非把子类也变成一个抽象类,指导所有的方法都被子类具体实现后才可以。
注意:抽象类是单继承的。
总结:
- 抽象类可以直接继承抽象类
- 抽象类也可以直接继承具体类
- 具体类不能直接继承抽象类,除非把抽象类中的抽象方法都具体实现才可以。
- 抽象类不能被重写
- abstract与static,private,final,native不能一起使用,因为一个是静态的只加载一次,一个是私有的不能被访问,一个是最终的不能被重写,一个是原生函数,是C++/C语言实现的,是访问不了的。
接口
接口与类相似点
- 一个接口可以有多个方法。
- 接口文件保存在 .java 结尾的文件中,文件名使用接口名。
- 接口的字节码文件保存在 .class 结尾的文件中。
- 接口相应的字节码文件必须在与包名称相匹配的目录结构中。
接口与类的区别
- 接口不能用于实例化对象。
- 接口没有构造方法。
- 接口中所有的方法必须是抽象方法。
- 接口不能包含成员变量,除了 static 和 final 变量。
- 接口不是被类继承了,而是要被类实现。
- 接口支持多继承。
接口特性
- 接口中每一个方法也是隐式抽象的,接口中的方法会被隐式的指定为 public abstract(只能是 public abstract,其他修饰符都会报错)。
- 接口中可以含有变量,但是接口中的变量会被隐式的指定为 public static final 变量(并且只能是 public,用 private 修饰会报编译错误)。
- 接口中的方法是不能在接口中实现的,只能由实现接口的类来实现接口中的方法。
抽象类和接口的区别
- 抽象类中的方法可以有方法体,就是能实现方法的具体功能,但是接口中的方法不行。
- 抽象类中的成员变量可以是各种类型的,而接口中的成员变量只能是 public static final 类型的。
- 接口中不能含有静态代码块以及静态方法(用 static 修饰的方法),而抽象类是可以有静态代码块和静态方法。
- 一个类只能继承一个抽象类,而一个类却可以实现多个接口。
JDK 1.8 以后,接口里可以有静态方法和方法体了。代码如下:
interface InterfaceDemo{
//默认方法
public default void test(){
System.out.println("default方法");
}
//静态方法
public static void testStatic(){
System.out.println("静态方法");
}
}
接口的声明
语法如下:
public interface 接口名称{}
接口是多继承的,代码如下:
public interface A{}
public interface B{}
public interface 接口名称 extends A,B{}
接口有以下特性:
- 接口是隐式抽象的,当声明一个接口的时候,不必使用abstract关键字。
- 接口中每一个方法也是隐式抽象的,声明时同样不需要abstract关键字。
- 接口中的方法都是公有的。
重写接口中声明的方法时,需要注意以下规则:
- 类在实现接口的方法时,不能抛出强制性异常,只能在接口中,或者继承接口的抽象类中抛出该强制性异常。
- 类在重写方法时要保持一致的方法名,并且应该保持相同或者相兼容的返回值类型。
- 如果实现接口的类是抽象类,那么就没必要实现该接口的方法。
在实现接口的时候,也要注意一些规则:
- 一个类可以同时实现多个接口。
- 一个类只能继承一个类,但是能实现多个接口。
- 一个接口能继承另一个接口,这和类之间的继承比较相似。