目录
1. 抽象类
2. 接口
接口实现多态的例子:
2.1 实现多个接口
2.2 接口间的继承
2.3 接口使用实例
2.3.1 Comparable接口 和 Comparator接口(第二个也叫比较器)
1、实现Comparable接口,重写compareTo方法
2、实现Comparator接口,重写compare方法
2.3.2 Cloneable接口
2.4 Cloneable接口 与 浅拷贝和深拷贝
2.4.1 浅拷贝
图解:
代码:
结论:
2.4.2 深拷贝
图解:
代码:
结论:
3. 面试题:抽象类和接口的区别
1. 被abstract修饰的类是抽象类,抽象类不能实例化对象,即不能new对象【abstract class】
2. 被abstract修饰的方法是抽象方法,抽象方法没有具体的实现,抽象方法不能被private修饰,也不能被final和static修饰,要满足重写的规则。【因为抽象方法需要被子类重写,而private,final,static修饰的不能被子类重写】【public abstract】
3. 一个类中有抽象方法,那这个类一定得是抽象类,抽象类中才能有抽象方法。
4. 抽象类和普通类中成员的区别就是:抽象类中可以有抽象方法【不放抽象方法也不报错】而普通类中不能有,其余和普通类一模一样,也可以有属性和方法。
5. 抽象类中也可以有构造方法,为了方便子类能调用,来初始化抽象类中的成员。
6. 抽象类存在的最大意义 - 就是为了被继承
7. 抽象类的最完美表现:抽象类中包含抽象方法,抽象类被子类继承,子类中重写抽象方法,实例化子类然后向上转型,实现多态。
比如下面这个例子:【这个例子就是抽象类存在的意义和表现】
abstract class Shape{//【!!!】
public abstract void draw();//【!!!】
}
class Rectangle extends Shape{
@Override
public void draw() {
System.out.println("画矩形");
}
}
class Circle extends Shape{
@Override
public void draw() {
System.out.println("画圆");
}
}
class Triangle extends Shape{
@Override
public void draw() {
System.out.println("画三角形");
}
}
public class Test {
public static void drawFuc(Shape shape){
shape.draw();
}
public static void main(String[] args) {
Shape shape = new Rectangle();
drawFuc(shape);
drawFuc(new Circle());
drawFuc(new Triangle());
}
}
8. 如果一个普通类,继承了一个抽象类,如果抽象类中有抽象方法,此时必须重写这个抽象方法。
9. 如果一个抽象类B,继承了一个抽象类Shape,此时B当中不需要重写Shape中的抽象方法;但是如果B再被普通类C继承,那么C中就需要重写Shape中的抽象方法。
在Java中,接口可以看成是:多个类的公共规范,是一种引用数据类型。
接口是对行为的一种规范或标准
可以将接口看做抽象类的进一步,但接口不是类
定义接口:interface+接口名(大驼峰)
1. 将class关键字换成 interface 关键字,就定义了一个接口。
2. 接口当中的成员方法不能有具体的实现,默认是抽象方法。如果此方法有具体实现,要么是default修饰的方法,要么是static修饰的方法。且接口中的所有方法默认都是public的,不写默认也有public,所以一般就直接不写,保持代码简洁性。
接口中的成员方法可以是:
3. 成员变量默认是public static final 修饰的,我们一般直接省略不写(加或者不加默认都会有,写成这样就错了【protected static final】,要与默认的保持一致。)
4. 接口不能被实例化,即不能new对象
5. 接口中不能有静态代码块和构造方法
6. 类和接口之间采用implements 来实现多个接口,若接口中有抽象方法,必须重写所有的抽象方法
7. 如果一个抽象类B,implements实现一个接口,此时B当中不需要重写接口中的抽象方法;但是如果B再被普通类C继承,那么C中就需要重写接口中的抽象方法。
也就是说如果你这个类不想重写接口中的抽象方法,你就加个abstract,变成抽象类。但出来混迟早要还的,要是这个抽象类被普通类继承了,你还是得重写接口中的抽象方法。
8. 接口虽然不是类,但是接口编译完成后字节码文件的后缀格式也是.class;所以,可以把接口看做是特殊的类,一般来说一个接口就是一个Java文件。
9. 接口也可以发生多态
10. 接口的存在就是为了重写其中的抽象方法
11. 接口的修饰符可以是abstract,不写其实默认也有。(不能是private,protected,final
抽象方法和default修饰的方法,不能被super调用。
接口中的静态方法,只能用接口名去调用,不能用实现接口的子类名去调用。
像抽象类或普通类中的静态方法,是可以用继承该类的子类名去调用的。接口很特殊。
interface IShape{
void draw();
}
class Rectangle implements IShape{
@Override
public void draw() {
System.out.println("矩形");
}
}
class Circle implements IShape{
@Override
public void draw() {
System.out.println("圆");
}
}
class Triangle implements IShape{
@Override
public void draw() {
System.out.println("三角形");
}
}
public class Test {
public static void drawFuc(IShape shape){
shape.draw();
}
public static void main(String[] args) {
drawFuc(new Rectangle());
drawFuc(new Circle());
drawFuc(new Triangle());
}
}
/**
* 请实现笔记本电脑使用USB鼠标、USB键盘的例子
*
* 1. USB接口:包含打开设备、关闭设备功能
* 2. 笔记本类:包含开机功能、关机功能、使用USB设备功能
* 3. 鼠标类:实现USB接口,并具备点击功能
* 4. 键盘类:实现USB接口,并具备输入功能
*/
interface USB{
void openDevice();
void closeDevice();
}
class Mouse implements USB{
@Override
public void openDevice() {
System.out.println("打开鼠标");
}
@Override
public void closeDevice() {
System.out.println("关闭鼠标");
}
public void click(){
System.out.println("鼠标点击");
}
}
class KeyBoard implements USB{
@Override
public void openDevice() {
System.out.println("打开键盘");
}
@Override
public void closeDevice() {
System.out.println("关闭键盘");
}
public void inPut(){
System.out.println("键盘输入");
}
}
class Computer{
public void powerOn(){
System.out.println("打开笔记本电脑");
}
public void powerOff(){
System.out.println("关闭笔记本电脑");
}
public void useDevice(USB usb){
usb.openDevice();
if(usb instanceof Mouse){
((Mouse) usb).click();//向下转型
}else if(usb instanceof KeyBoard){
((KeyBoard) usb).inPut();//向下转型
}
usb.closeDevice();
}
}
public class Test {
public static void main(String[] args) {
/**
* 调用类中的非静态方法,需要先实例化那个类,【引用.方法】才行。
*/
//打开电脑
Computer computer = new Computer();
computer.powerOn();
//使用鼠标设备
computer.useDevice(new Mouse());//向上转型
//使用键盘设备
computer.useDevice(new KeyBoard());//向上转型
//关闭电脑
computer.powerOff();
}
}
一个类可以实现多个接口,使用implements,用逗号(,)隔开,可以解决多继承的问题。
Java当中只能继承一个类,不支持多继承,但可以实现多个接口。
继承表达的含义是 is - a 语义, 而接口表达的含义是 具有 xxx 特性。
有了接口之后, 类的使用者就不必关注具体类型, 而只关注某个类是否具备某种能力。
实现多个接口的例子:
//接口
interface IRunning{
void run();
}
interface ISwimming{
void swim();
}
interface IFlying{
void fly();
}
//类
class Animals{
public String name;
public Animals(String name) {
this.name = name;
}
public void eat(){
System.out.println("正在吃饭");
}
}
//重写抽象方法快捷键:alt+enter
class Dog extends Animals implements IRunning,ISwimming{
public Dog(String name) {
super(name);
}
@Override
public void run() {
System.out.println(name+"正在跑");
}
@Override
public void swim() {
System.out.println(name+"正在游泳");
}
public void eat(){
System.out.println(name+"正在吃狗粮");
}
}
class Duck extends Animals implements IRunning,ISwimming,IFlying{
public Duck(String name) {
super(name);
}
@Override
public void run() {
System.out.println(name+"正在跑");
}
@Override
public void swim() {
System.out.println(name+"正在游泳");
}
@Override
public void fly() {
System.out.println(name+"正在飞");
}
public void eat(){
System.out.println(name+"正在吃鸭粮");
}
}
class Robert implements IRunning{
@Override
public void run() {
System.out.println("机器人正在跑");
}
}
public class Test {
//跑
public static void isRun(IRunning running){
running.run();
}
//游泳
public static void isSwim(ISwimming swimming){
swimming.swim();
}
//吃
public static void isEat(Animals animals){
animals.eat();
}
public static void main(String[] args) {
//跑:
isRun(new Dog("小狗"));
isRun(new Duck("小鸭子"));
isRun(new Robert());
System.out.println("-------------------");
//游泳:
isSwim(new Dog("小狗"));
isSwim(new Duck("小鸭子"));
System.out.println("-------------------");
//吃
isEat(new Dog("小狗"));
isEat(new Duck("小鸭子"));
}
}
接口间的继承相当于把多个接口合并在一起,一个接口可以继承多个接口。当类实现这个接口时,它继承的接口中的抽象类也需要重写。
接口间的继承用 extends关键字,类实现接口用 implements
接口是对行为的一种规范或标准,
实现这个接口,就具备了这个接口的行为或能力。
自定义类型的对象之间比较,需要实现接口,使此类具备比较的能力,这样对象之间才能进行比较
如下图,想要比较两个学生的大小,需要Student类实现Comparable
谁调用compareTo这个方法,谁就是this,传入的参数就是o,所以这里,this就是student1,o就是student2
这样就实现了学生大小的比较。
那么如果我想通过学生的年龄比较大小,就得重新编写compareTo方法中的代码,可能会带来影响。Comparable接口对类的侵入性非常强,确定通过什么比较了,一般就不能更换了。
Comparator接口中,不止一个抽象方法,为什么只需要实现一个compare方法就好了 ?
因为Object类中已经实现了equals方法,而所有类都会继承Object类,所以此类不需要再实现equals方法了。
Comparator接口对类的侵入性比较弱。例如,想要比较两个学生的大小,可以根据学生的年龄比较大小,也可以根据学生的名字比较大小。分别定义两个类继承Comparator接口就行。如下:
这是学生类:
根据年龄比较大小:
根据名字比较大小:
自定义类型的对象想要实现克隆,需要如下几步:
1. 此类实现Cloneable接口,使其具备可克隆的能力
2. 重写Object类中的clone方法
3. 声明异常,强转类型
举个例子:
自定义类型Person的一个对象想要实现克隆,我们分别来看一下这几步:
第一步,实现Cloneable接口
我们进入到Cloneable接口中,发现里面什么都没有。
这种接口叫做 空接口/标记接口,它的作用就是:实现了这个接口,当前类就拥有了可以被克隆的能力。
第二步,重写clone方法
因为所有的类都继承Object类,Object类中的clone方法是protected限定符修饰的。Object类在java.lang包里,和子类Person一定不在同一个包里。在不同包中时,只有在子类中可以访问。也就是说,只能在Person类中才能直接访问到Object类中的clone方法,在其它类中都不行。
如下图:
成功运行出来了。
但是,我们一般不直接在Person类里使用Person的实例化对象,会在另一个类(如Test类)中实例化Person对象,然后调用clone方法。这时,就是不同包的非子类了,于是Object类中的clone方法就无法被访问了。
那么,我们该怎么办呢?很简单,在子类Person中重写clone方法就可以啦。
子类重写clone方法,在方法中调用Object类的clone方法,那么我们通过调用子类的clone方法,就可以实现克隆啦,本质上调用的还是Object的clone方法。
第三步,声明异常,强转类型,如上。因为clone方法的返回值是Object类型的,所以需要强转成Person类型。
浅拷贝只会拷贝Person类中的对象,不会拷贝这个对象中的Money的对象,Money的对象只有一份,一变全变。
深拷贝会拷贝Person类中的对象,也会拷贝这个对象中的Money的对象,所以不会一变全变。
1. 抽象类和接口都是 Java 中多态的常见使用方式,抽象类和接口都不能实例化对象。
2. 对于成员变量,抽象类中成员变量没有要求,接口中的成员变量默认是public static final修饰的。
3. 对于成员方法,
相同点是:抽象类和接口中都可以有抽象方法,抽象方法没有具体的实现。而且当抽象类被子类继承,或者子类实现了此接口,抽象类和接口中的所有抽象方法都必须被重写。
不同的是:抽象类中的抽象方法可以被除了private以外的其他三个访问修饰限定符修饰,而且抽象类中还可以有普通的成员方法。而接口的所有方法默认都是public修饰的,其他三个访问修饰限定符都不可以。接口中默认就是抽象方法。如果存在有具体实现的方法,要么是default修饰的,要么是静态方法。
4. 抽象类中可以有构造方法,接口中不能有构造方法
5. 子类使用extends关键字继承抽象类,使用implements关键字实现接口
6. 一个子类只能继承一个抽象类,但一个子类可以实现多个接口
7. 一个抽象类可以实现若干个接口,接口不能继承抽象类,但是接口可以使用extends关键字继承多个父接口