抽象类与接口的难点解析

抽象类

1. 抽象类
  • 抽象类无法自身实例化,只能通过多态的形式,向上转型,创建父类引用指向子类对象。

  • 包含抽象方法的类必须使用abstract关键字声明为抽象类。有抽象方法的类一定是抽象类。

  • 抽象类的本质是普通类的“超集”,只是在普通类的基础上扩展抽象方法。故抽象类可以包含非抽象方法、抽象方法。

  • 抽象类也有构造方法。

2. 抽象方法

使用abstract关键字声明、无方法体的方法,称为抽象方法。

e.g. Java中,没有方法体的方法就是抽象方法。(×)

native关键字声明的本地方法也没有方法体。native方法的具体实现由JVM完成,使用JNDI实现Java调用C++中的同名方法。

3. 抽象类的子类

继承了抽象类的子类,若不重写其抽象父类的所有抽象方法,则该子类也会成为抽象类。以此来强制要求子类覆写抽象类中的所有抽象方法(若想要成功实例化的话)。

1)子类是普通类:必须重写抽象父类的所有抽象方法

2)子类是抽象类:可选择性重写抽象父类的抽象方法

4. 抽象类的构造方法

抽象类是在普通类的基础上扩展抽象方法,普通类有的,抽象类也有。故抽象类也有构造方法。

抽象类虽然无法自身实例化,但通过多态形式创建子类实例时,仍然遵循继承的规则,先调用父类的构造方法,再调用子类的构造方法。

e.g. 例题, 程序输出 “num = 0”。
在这里插入图片描述


接口

  • 接口表示一种规范或标准,表示具备某种能力或行为。

  • 接口中只有全局常量和抽象方法,没有其他任何非抽象方法、构造方法等。(JDK8以前)

  • 接口中的方法默认被public abstract修饰,均为抽象方法。

    接口中的变量默认被public static final修饰,均为全局常量。且定义时必须初始化常量。

    接口本身可以被abstract修饰,不可被private、protected、final修饰。

  • 接口的实现类必须覆写接口的所有抽象方法。

  • 一个类同时需要继承父类、实现接口时,先声明extends再声明implements。

JDK内置的常用接口
1)Comparable接口
//jdk源码:
public interface Comparable<T> {
    public int compareTo(T o); 
     /** this-o 返回值大小关系
    	>0  this > o
    	=0  this = o
    	<0  this < o
    */
}

若要根据自定义条件比较两个引用数据类型的对象的大小,可覆写java.lang.Comparable接口的compareTo()方法。

重写的思路模板:

public class Student implements Comparable{
    private String name;
    private int score;
    
    /** 比较两个学生的成绩是否相等(根据score值比较两个对象的大小)
        设定返回值int的三种情况:
            <0 当前学生成绩小于传入的学生成绩
            =0 当前学生成绩等于传入的学生成绩
            >0 当前学生成绩等于传入的学生成绩
     */
    @Override
    public int compareTo(Object o) {
        // 1.先判断传入的对象是否和当前对象就是同一个对象
        if(this == o) {
            return 0;  //相等
        }
        // 2.判断传入的对象是否具有可比性(是否属于同一类型)
        if(o instanceof Student) {
            // 3.具有可比性,则向下转型【规避传入的对象引用不是Student类型的风险】
            Student stu = (Student) o;
            // 4.比较传入的对象属性和当前对象属性的大小
            return this.score - stu.score;
            /* 返回值的三种情况:<0 当前对象小于传入的对象  =0 当前对象等于传入的对象  >0 当前对象大于传入的对象 */
        }
        // 5.不具有可比性,抛出异常,让程序止步于此
        throw new IllegalArgumentException("不是Student类型,无法比较!");
    }
}

仿照Arrays类中sort()方法的功能,自定义一个sort()方法,使得对象数组能够根据自定义的条件进行升序排序(使用冒泡排序)。

【思路】将方法形参中的对象数组定义为Comparable类型,实参传入Comparable接口的实现类的对象数组。即可根据自定条件进行大小比较。

public class Test {

    /** 自定义一个sort()方法,使用冒泡排序给对象数组进行升序排序。
	   【思路】形参设为Comparable类型的数组,实参传入Comparable接口的实现类的对象数组。
     */
    public static void sort(Comparable[] arr) {
        for(int i = 0; i < arr.length-1; i++) {
            for(int j = 0; j < arr.length-i-1; j++) {
                if(arr[j].compareTo(arr[j+1]) > 0) {  //arr[j]对象大于arr[j+1]对象
                    Comparable temp = arr[j];
                    arr[j] = arr[j+1];
                    arr[j+1] = temp;
                }
            }
        }
    }

    public static void main(String[] args) {
        //Student类已经实现了Comparable接口,且根据score属性值比较对象的大小
        Student[] students = new Student[] {
                new Student("vv", 20),
                new Student("ww", 18),
                new Student("xx", 12)
        };
        //自定义的sort()方法进行升序排序
        sort(students);
        //对Student类重写toString()方法后,即可打印出具体的属性值
        System.out.println(Arrays.toString(students));
    }
}

输出结果:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Cs4ewKpu-1653735055159)(C:/Users/Hong/AppData/Roaming/Typora/typora-user-images/image-20220508221448752.png)]

2)Comparator接口
//jdk源码:
public interface Comparator<T> {
	int compare(T o1, T o2);
    /** o1-o2 返回值大小关系
    	>0  o1 > o2
    	=0  o1 = o2
    	<0  o1 < o2
    */
    ...
}

实现了Comparator接口的类为一个比较器,可通过传入泛型来指定要比较的类型,重写compare()方法进行自定义条件比较。

为规避Comparable接口带来的弊端而生。想自定义比较的类必须自身实现Comparable接口,每次想要修改比较条件时,都要修改该类的 compareTo()方法,违背了开闭原则。

开闭原则:对扩展开放,对修改关闭。

当有新的需求到来时,不修改原先写好的代码,仅扩展新代码。

public class ComparatorTest {
    public static void main(String[] args) {
        Student[] students = new Student[] {
                new Student("小洪", 97),
                new Student("小陈", 91),
                new Student("oo", 100)
        };
        //根据学生成绩对学生进行升序排序
        Arrays.sort(students, new StudentSec());
        System.out.println(Arrays.toString(students));
        //根据学生成绩对学生进行降序排序
        Arrays.sort(students, new StudentDesc());
        System.out.println(Arrays.toString(students));
    }
}

//对学生成绩进行正序比较的比较器
class StudentSec implements Comparator<Student> {

    @Override
    public int compare(Student o1, Student o2) {
        return o1.getScore() - o2.getScore();
    }
}

//对学生成绩进行倒序比较的比较器
class StudentDesc implements Comparator<Student> {

    @Override
    public int compare(Student o1, Student o2) {
        return o2.getScore() - o1.getScore();
    }
}

输出结果:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-96LBHEeL-1653735055164)(C:/Users/Hong/AppData/Roaming/Typora/typora-user-images/image-20220528185015516.png)]
若想对一个类Student进行自定义条件的比较,则只需要创建一个比较器类,实现Comparator接口,指定比较的类型为Student。
想要几种自定义的条件比较,就创建几个比较器,每个比较器根据所需重写各自的compare()方法即可。

3)Cloneable接口
//jdk源码:
public interface Cloneable {
}

java.lang.Cloneable 接口内部没有任何抽象方法,仅用于标记其实现类具有可克隆的能力,“拷贝”出的新对象与原对象具有相同的属性值。实现了Cloneable接口的类才可以覆写Object类中的clone()方法,否则会抛出CloneNotSupportedException异常。

JVM运行时,会检查所有实现了Cloneable接口的实现类,赋予其克隆的能力,即允许其覆写Object类的clone()方法。

标记接口:接口中没有任何抽象方法,仅用于标记某个类拥有某种能力。

java.lang.Cloneable:表明Object.clone()方法可以合法地对该类实例进行按字段复制。实现此接口的类应该使用公共方法重写Object.clone()方法(它是受保护的)。如果在没有实现 Cloneable接口的实例上调用Object的clone()方法,则会导致抛出CloneNotSupportedException异常。

java.io.Serializable:未实现此接口的类将无法使其任何状态序列化或反序列化。为保证serialVersionUID值跨不同Java编译器实现的一致性,序列化类必须声明一个明确的 serialVersionUID值。

java.util.RandomAccess:用来表明其支持快速(通常是固定时间)随机访问。此接口的主要目的是允许一般的算法更改其行为,从而在将其应用到随机或连续访问列表时能提供良好的性能。

java.rmi.Remote:用于标识其方法可以从非本地虚拟机上调用的接口。任何远程对象都必须直接或间接实现此接口。只有在远程接口(扩展java.rmi.Remote的接口)中指定的这些方法才可远程使用。

//jdk源码:Object类中的clone()方法
protected native Object clone() throws CloneNotSupportedException;

克隆测试:(浅拷贝)

public class CloneTest {
    public static void main(String[] args) {
        A a = new A(10);
        B b1 = new B("b1", a);
        //B类对象b1具有可克隆的能力,通过b1调用clone()方法,克隆出一个新对象
        B b2 = b1.clone();
        System.out.println(b2 == b1);  //输出false,克隆出来的对象和原对象是两个独立的对象
        System.out.println(b2.name == b1.name);  //输出true,克隆对象的name与原对象相同
        System.out.println(b2.a == b1.a);  //输出true,克隆对象b2的a引用与原对象b1的a引用指向同一个地址值,指向同一个A类对象 ==> 浅拷贝
        //修改原对象a引用,克隆对象的a引用也会同步修改
        b1.a.num = 100;
        System.out.println(b2.a.num);  //输出100,再次证实B类实现的是浅拷贝
    }
}

class A {
    int num;
    public A(int num) {
        this.num = num;
    }
}

class B implements Cloneable {

    String name;
    A a;  //B类对象包含了A类对象的引用

    public B(String name, A a) {
        this.name = name;
        this.a = a;
    }

    //重写Object类中的clone()方法,返回值为向上转型(B->Object)
    public B clone() {
        B newB = null;
        try {
            newB = (B) super.clone();
            //此处调用的是Object类的clone()方法
        } catch (CloneNotSupportedException e) {
            throw new RuntimeException(e);
        }
        return newB;
    }
}

深浅拷贝

深浅拷贝都会拷贝出一个新的对象,如B类对象b1经过拷贝后,产生一个新对象b2。

但若B类对象的属性中包含了对A类对象的引用A a; 则在拷贝时,此属性值(指向的A类对象)分为两种情况:

浅拷贝:拷贝后新对象内部的A类引用和原对象的A类引用指向同一个A类对象 b2.a == b1.a(地址值相同)

深拷贝:拷贝后新对象内部的A类引用和原对象的A类引用指向不同的A类对象 b2.a != b1.a(地址值不同)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-gijasZEG-1652068037566)(C:/Users/Hong/AppData/Roaming/Typora/typora-user-images/image-20220509105826620.png)]
Java中实现深拷贝的两种方式

1)递归使用clone()方法

2)序列化(把对象转换为json字符串)

调用clone()方法产生新对象不会调用该类的构造方法。

Java中产生新对象的两种方式:

1)使用new关键字在堆区为该对象属性开辟内存空间,调用构造方法给属性初始化赋值。

2)实现Comparable接口,重写Object类的clone()方法,通过原对象调用clone()方法,克隆出新对象。


接口与抽象类的区别

  • 接口较抽象类是更加纯粹的“抽象”概念。

    抽象类是在普通类的基础上扩展抽象方法,普通类有的它都有; 而JDK8以前,接口中只有全局常量和抽象方法,没有普通类中的其他方法、变量等。

  • 抽象类和接口都参照多态的形式进行实例化,但抽象类的多态基于继承,子类和父类之间具有is a关系,多个子类之间存在相同的行为特性。

    而接口的多态与继承无关,接口和实现类之间不具有is a关系,多个实现类之间没有特定的关系。

  • 一个类可以实现多个接口,但只能继承一个父类。故通常情况下优先选择使用接口,避免抽象类单继承的局限性。


接口和接口的关系

  • 接口只能继承接口,且允许多继承,即一个接口可继承多个接口。
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值