目录
一、抽象类
1、什么是抽象类
2、抽象类的特点
3、抽象类的作用
4、抽象类示例代码
二、接口
1、什么是接口
2、接口的书写建议
3、接口的特点
4、实现多个接口
5、接口能“忘记类型”
6、接口间的继承
7、接口的应用
7.1、引用类型的比较--Comparable 和 Comparator 接口
7.2、引用类型数组排序--Comparable 和 Comparator 接口
7.3、引用类型的克隆--Clonable 接口
7.3.1、浅拷贝
7.3.2、深拷贝
三、抽象类和接口的区别
四、Object类
1、equals
2、hashCode
一、抽象类
1、什么是抽象类
抽象类就是不能描述一个具体对象的类,比如类 Shape,它的子类可以有 Cycle、Squre等。Shape 的 draw 并不能确定子类对象的描绘,此时 Shape 就需要定义为抽象类。抽象类由 abstract 修饰。抽象类中,不能准确描述子类对象的方法 draw 叫做抽象方法,同样用 abstract 修饰。
这样,抽象类只负责声明抽象方法,而子类负责重写来实现抽象方法,子类就能准确描绘自己的 draw 了。
因为抽象类的抽象方法并没有具体实现,所以抽象类不可以用 new 实例化。
因为抽象类要求子类必须重写抽象方法,所以子类要么重写抽象方法,要么子类也为抽象类,但最后一个子类必须要重写抽象方法。
因为抽象方法必须被重写,就不能用 private、static、final 修饰了。
抽象方法必须在抽象类中,但抽象类不一定有抽象方法。
2、抽象类的特点
首先,它与普通类的相似之处:都可以有属性、普通方法、构造方法。
然后,它的独特性:不能实例化、可以有抽象方法。
3、抽象类的作用
有人会说,普通类的子类也能重写,为什么非要声明抽象?实际上,abstract 起了一个编译器检查的作用,避免应该用子类(比如 Cycle)描述 draw,却误用父类(Shape)描述 draw的情况。有了 abstract ,实例化抽象父类会报错,而实例化普通父类却不会。
4、抽象类示例代码
二、接口
1、什么是接口
接口就是通用的规范,实现规范的要求,就能使用了。接口的修饰词 interface 替代类 class 的位置。
2、接口的书写建议
- 接口名以 I 开头。
- 接口名最好是形容词。
- 接口里边的成员不要加修饰符,用默认的,更简洁。
3、接口的特点
接口不能实例化。
一个类要实现接口,用 implememts 。
一个类实现接口,要重写抽象方法,方法的修饰词只能是 public,因为重写方法的访问权限不能低于被重写方法的访问权限。如果没重写,类要用 abstract 修饰。
接口中的成员变量,默认且必须修饰为 public static final。
接口中的成员方法,如果不实现,默认且必须为 public abstract。
接口中的成员方法,要实现,必须被 static 或者 default 修饰。默认是 public 修饰。
接口中不能有静态代码块和构造方法。
接口不是类,编译完的字节码也单独为一个 .class 文件。
4、实现多个接口
一个类不能继承多个父类,但是能实现多个接口,这可以弥补继承的缺陷。
示例代码:比如不是所有的动物都会游泳,因此不能把跑、游全部写在 Animal 类里。如果把跑、游分别写在两个类里,又不能继承多个类。这种情况,就使用接口,接口可以实现多个。
5、接口能“忘记类型”
接口相对于继承,还有一个优势,就是接口可以“忘记类型”,而类只能“半忘记类型”。
比如,在继承实现多态时,Dog、Cat 同属 Animal 的子类,那么只能由 Animal 的子类向上转型,而不能是其它类的子类,或者没有继承的类。
在接口实现多态时,不管属不属于 Animal ,只要是实现了 IRunable 的类,都能够向 IRunable 转型。
示例代码:Dog、Dock、Robot 都能跑。
6、接口间的继承
相当于把接口合并。比如鸭子是又会跑,又会游的两栖动物,那么可以把 IRunnable 和 ISwimable 合并成一个接口。类和类单继承,类和接口多实现,接口和接口能多继承。
示例代码:
7、接口的应用
7.1、引用类型的比较--Comparable 和 Comparator 接口
基本数据类型,可以直接比较大小。引用类型,比如自定义的 Student 不能直接比较大小,因为没有比较的标准,姓名?年龄?分数?
因此,可以实现 Comparable 接口,并实现 compareTo 方法:参数接收一个。
但这样又有个问题,固定了 age 作为比较的标准。如果一开始用的 age,过了几天业务变了,又想用 score,直接在重写的 compareTo 方法里改,是万万不可的。之前用你写的类,以 age 为标准的同事怎么办?直接改会全部乱套。对类具有侵入性(引入该组件后,其它代码要做相应的更改,以适应组件)。
比较好的办法是,给每一种比较标准实现一个比较器,它们都实现 Comparator 接口,重写 compare 方法:参数接收两个。
注意,name的类型 String 也是引用类型,得用 String 类重写的 CompareTo 。
7.2、引用类型数组排序--Comparable 和 Comparator 接口
引用类型有了比较标准,就能进行排序了。
用 Arrays.sort(默认升序),可以用 Student 内默认的 compareTo 比较:
也可以指定比较器(按 name)比较:
降序,将重写 compareTo 或者 compare 的两个值反过来:
原理,比如自定义一个冒泡排序:改动前,o1-o2>0 才交换,即升序;改动后,o2-o1>0才交换,即降序。
降序,如果不改动 compareTo 或者 compare,也可以改动自定义冒泡排序的 > 为 <。
7.3、引用类型的克隆--Clonable 接口
Object 类是所有类的祖先,因此其它的引用的对象可以使用父类 Object 实现的方法 Clone。但是,需要满足3个条件:
(1)Object 类中的 Clone 方法的访问权限范围是 protected,也就是如果在不同包,只能被子类使用。
现在有一个 Person 类:
在另一个 Test 类中创建 Person 的对象,并直接克隆,不可行。因为用 Object 类的方法克隆 Person 的对象,那么 Person 才是 Object 的子类,而不是 Test。又由于 protected 修饰,Person 的对象的克隆,就只能在 Person 类中实现,而不能在其它类中实现。
Person 类重写 Clone 方法,让其他类间接克隆 Person 的对象:
(2)Clone 方法的返回值是 Object 类型,需要向下转型,才能实现赋值给 Person 对象:
但仍然报错:不支持克隆。
(3)Person 类必须实现 Cloneable 接口。Cloneable 接口是一个空接口,主要是标记作用,标记这个类能克隆。
7.3.1、浅拷贝
现在,我们实现了 Person 对象的克隆,给 Person 加一个 Money 对象作为成员变量:
测试代码:
运行结果:
代码只改变了 person2 的 money,但事实上 person1 的 money 也改变了。
绘制数据存储图:
m 也是引用类型,存的是对象的地址。只创建了新的 Person 对象空间,并复制内容;而没有创建新的 Money 对象空间,并复制内容。这就是浅拷贝。
7.3.2、深拷贝
深拷贝,必须把 Person 类里面的所有引用类型成员变量也拷贝一份。
再运行上面的测试代码,person1的 m 的 money就不会跟着改值了:
三、抽象类和接口的区别
抽象类其实有普通类的功能。
- 抽象类可以有普通成员,构造方法;接口只能有抽象方法,想有成员变量和方法,得有修饰符的限制。
- 抽象类是被继承,接口是被实现。
- 一个类只能继承一个抽象类,但能实现多个接口。
- 一个接口不能继承普通类,但是能继承多个接口,相当于把继承的接口合并。
- 抽象类可以的访问权限跟普通类一样,默认和 public;但接口只能是 public。
四、Object类
Object类是所有类的爸爸,很重要。
1、equals
它重点是比较相不相等(返回值是 boolean);compare 和 compareTo 是比较哪个大(返回值是 int)。
不是我们想要的比较值,所以重写:
2、hashCode
作用是算一个对象在内存中的位置,详细的后续学。源码是 native 方法,看不到具体实现,
两个地址不同的对象,就算赋值相同,分别直接用 hashCode,hashcode值也不一样。
这样改了是相同的。