Java(抽象类,接口)

目录

抽象类

认识抽象类

例子

注意事项

接口

认识接口

例子

注意事项

实现多个接口

三个特殊接口(Comparable,Comparator,Cloneable)

Comparable接口

Comparator接口

Cloneable接口

所以这种方法是深拷贝还是浅拷贝?


抽象类

认识抽象类

抽象类是指含有抽象方法的那种类,抽象类通常没有具体的实现;因为多个类中都需要重写同名方法,因此将这种方法定义为abstract修饰的抽象方法,存放在abstract修饰的抽象类当中,抽象类只能被继承。

例子

定义一个抽象方法方法draw且存放在存放在抽象类Shape当中,被多个实现画图的类继承。

//一个抽象类只能被继承
abstract class Shape {
     public abstract void draw();
}


//矩形类
class Rect extends Shape {
    @Override
    public void draw(){
        System.out.println("□");
    }
}
//花类
class Flower extends Shape {
    @Override
    public void draw() {
        System.out.println("❀");
    }
}
//三角形类
class Triangle extends Shape {
    @Override
    public void draw() {
        System.out.println("▲");
    }
}
//圆形类
class Circle extends Shape {
    @Override
    public void draw() {
        System.out.println("⚪");
    }
}

实例化这几个类并打印

public class Test {

    public static void drawMap(Shape shape){
        shape.draw();
    }

    public static void main(String[] args) {

    }

    public static void main1(String[] args) {

        //可以发生动态绑定
        Shape shape = new Circle();
        drawMap(shape);

        //也可发生多态
        Flower flower = new Flower();
        drawMap(flower);
    }
}

注意事项

1、抽象类不能被实例化,即关键字new修饰。

2、因为不能被实例化,因此这个类只能被继承。

3、抽象类当中也可以包含和普通类一样的成员和方法,能直接被继承或者调用。但是抽象类不能是privat的,private修饰则不能被继承。

4、一个普通类继承了抽象类,可以对抽象类当中普通方法或普通成员变量直接调用。也可调用在普通类中重写后的方法。

abstract class A {
    public int a = 10;
    public abstract void func1();
}
class D extends A {
    @Override
    public void func1() {

    }
}
public class TestDemo {
    public static void main(String[] args) {
        D d = new D();
        System.out.println(d.a);
    }
}

//此时打印结构为10

5、普通类继承了抽象类,必须重写抽象类同名的方法。

6、抽象类A继承了抽象类B,抽象A当中可以不重写抽象类B中的抽象方法。

abstract class A {
    public abstract void func1();
}
abstract class B extends A {
    public abstract void func2();
}

7、结合第六点,当普通类C继承了抽象类B,必须重写抽象类A和抽象类B当中的抽象方法。

abstract class A {
    public abstract void func1();
}
abstract class B extends A {
    public abstract void func2();
}

//普通类继承抽象类B
class C extends B {
    @Override
    public void func2() {

    }

    @Override
    public void func1() {

    }
}

8、抽象类不能被final修饰,抽象类只能被继承,而final修饰的类是不能被继承的,二者相互矛盾。

接口

认识接口

接口是抽象类的更进一步抽象,接口被interface修饰。接口名一般为I开头,如interface IA{}

例子

用接口定义抽象方法draw来实现画多个图形的类。

interface IShape {
    //抽象方法
    public abstract void draw();
}



class React implements IShape {
    @Override
    public void draw() {
        System.out.println("□");
    }
}

class Flower implements IShape {
    @Override
    public void draw() {
        System.out.println("❀");
    }
}

class Triangle implements IShape {
    @Override
    public void draw() {
        System.out.println("▲");
    }
}

class Circle implements IShape {
    @Override
    public void draw() {
        System.out.println("⚪");
    }
}

public class TestDemo2 {
    public static void main(String[] args) {
        public static void drawMap (IShape iShape) {
        iShape.draw();
    }

    public static void main(String[] args) {
        React react = new React();
        drawMap(react);
        drawMap(new Flower());
    }
}

注意事项

1、接口当中的普通方法,不能有具体的实现。如要实现,必须有default来修饰这个方法,即这个方法为接口默认方法

interface IShape {
    //抽象方法
    public abstract void draw();
    //default修饰实现普通方法
    default public void func1() {
        int a = 10;
    }
}

2、接口当中也可以有static方法。

3、接口当中所有方法都为public的(默认)。

4、接口当中的抽象方法是public abstract的。

5、接口不能被实例化,即关键字new修饰。

6、类和接口之间的关系是implements来实现的。

7、当一个普通类实现一个接口,普通类当中必须重写接口当中的抽象方法。

interface IShape {
    //抽象方法
    public abstract void draw();
}


class React implements IShape {
    @Override
    public void draw() {
        System.out.println("□");
    }
}

8、接口当中的成员变量默认为public static final 修饰的,方法默认为public abstract修饰的。

interface IShape {
    //抽象方法
    public abstract void draw();
    //默认写为
    void func1();

    //成员变量
    public static final int a = 10;
    //默认写为
    int a = 10;
}

9、当一个类实现了一个接口,这个类当中的必须重写接口当中的抽象方法,且只能用public修饰方法。

interface IA {
    void func1();
}
class BClass implements IA {
    @Override
    public void func1() {
        
    }
}

此时B类相当于IA接口的子类,重写的条件是:子类方法的访问权限必须大于父类方法的访问权限。

10、一个普通类只能继承一个抽象类,可以同时实现多个接口。且必须重写所有抽象方法。

interface IB {
    
}
interface IA {
    void func1();
}
abstract class CClass {
    abstract public void func2();
}

class Class extends CClass implements IA,IB {
    @Override
    public void func2() {
        
    }

    @Override
    public void func1() {
        
    }
}

11、接口和接口之间可以通过extends关键字来操作,意为拓展。A接口拓展了B接口,即A接口具有了B接口的功能。

interface IC {
    void func5();
}
interface ID extends IC{
    void func6();
}
class E implements ID {
    @Override
    public void func5() {
        
    }

    @Override
    public void func6() {

    }
}

实现多个接口

  • 有的时候我们需要让一个类同时继承自多个父类. 这件事情在有些编程语言通过 多继承 的方式来实现的.
  • 然而 Java 中只支持单继承, 一个类只能 extends 一个父类. 但是可以同时实现多个接口, 也能达到多继承类似的效果.

定义一个动物Animal类

class Animal {
    protected String name;

    public Animal(String name) {
        this.name = name;
    }
}

动物能力用接口实现

interface IFlying {
    void fly();
}
interface IRunning {
    void run();
}
interface ISwimming {
    void swim();
}

猫能跑

class Cat extends Animal implements IRunning {
    public Cat(String name) {
        super(name);
    }

    @Override
    public void run() {
        System.out.println(super.name + "会飞");
    }
}

鱼会游

class Fish extends Animal implements ISwimming {
    public Fish(String name) {
        super(name);
    }

    @Override
    public void swim() {
        System.out.println(super.name + "会游");
    }
}

青蛙会跑也会游

class Flog extends Animal implements IRunning,ISwimming {
    public Flog(String name) {
        super(name);
    }

    @Override
    public void run() {
        System.out.println(super.name + "会跑");
    }

    @Override
    public void swim() {
        System.out.println(super.name + "会游");
    }
}

实现这些类

    public static void main(String[] args) {
        Fish fish = new Fish("🐟");
        fish.swim();

        Cat cat = new Cat("🐱");
        cat.run();

        Flog flog = new Flog("🐸");
        flog.run();
        flog.swim();
    }

三个特殊接口(Comparable,Comparator,Cloneable)

当我们对一个无序数组进行排序时,会用Arrays.sort函数。

public class TestDemo4 {
    public static void main(String[] args) {
        int[] array = {1,3,4,67,8,34};
        System.out.println(Arrays.toString(array));
        Arrays.sort(array);
        System.out.println(Arrays.toString(array));
    }
}

排序前后打印结果为:

 当将类实例化了对象,如何将对象当中的数据进行排序?

如:定义一个学生类,有包括姓名,年龄,成绩字段

class Student {
    public String name;
    public int age;
    public double score;

    public Student(String name, int age, double score) {
        this.name = name;
        this.age = age;
        this.score = score;
    }

    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", score=" + score +
                '}';
    }
}

初始化一个数组,数组类型为Student类.

    public static void main(String[] args) {
        Student[] students = new Student[3];
        students[0] = new Student("zhang san",18,95.5);
        students[1] = new Student("li si",19,93.4);
        students[2] = new Student("wang wu",20,96.5);
    }

用数组的方法,将Student类数组排序并打印代码会报错。Arrays.sort不确定根据students哪个数据进行排序。

 正确的做法应该是让类实现一个Comparable接口,接着重写其中的compareTo方法。

Comparable接口

class Student implements Comparable<Student>{
    public String name;
    public int age;
    public double score;

    public Student(String name, int age, double score) {
        this.name = name;
        this.age = age;
        this.score = score;
    }

    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", score=" + score +
                '}';
    }

    @Override
    public int compareTo(Student o) {
        /*if (this.age > o.age) {
            return 1;
        } else if (this.age == o.age) {
            return 0;
        } else {
            return -1;
        }*/
        return (int)(this.score - o.score);
    }
}

其中Comparable后面的尖括号内容为要比较的数据类型;
重写的compareTo方法当中this代表的是调用这个方法的对象。

 这时可以正常使用Arrays.sort了,再用其按分数排序。

    public static void main(String[] args) {
        Student[] students = new Student[3];
        students[0] = new Student("zhang san",18,95.5);
        students[1] = new Student("li si",19,93.4);
        students[2] = new Student("wang wu",20,96.5);

        Arrays.sort(students);
        System.out.println(Arrays.toString(students));


    }

也可以对姓名排序。又因为姓名为引用类型,所以需将campareTo方法改写:

    @Override
    public int compareTo(Student o) {
        /*if (this.age > o.age) {
            return 1;
        } else if (this.age == o.age) {
            return 0;
        } else {
            return -1;
        }*/
        return this.name.compareTo(o.name);
    }

虽然comparable接口可以让数组元素按照自己的意愿进行排序,但这种写法对类的侵入性较强,如果要更改排序方式,就需要在Student类中修改compareTo()方法,这并不是我们想看到的,因此,我们引入了下一种接口:

Comparator接口

comparator接口也叫比较器,是根据给自定义比较实现的一个接口,实现这个接口需要重写compare方法。

定义一个ScoreComparator实现Comparator接口:

class ScoreComparator implements Comparator<Student> {
    @Override
    public int compare(Student o1, Student o2) {
        return (int)(o1.score - o2.score);
    }
}

将这个类实例化后使用:

 通过sort的源码可以确定Arrays.sort此时的参数:

 也可以定义一个NameCamparator来排序名字:

class NameComparator implements Comparator<Student> {
    @Override
    public int compare(Student o1, Student o2) {
        return o1.name.compareTo(o2.name);
    }
}
    public static void main(String[] args) {
        Student[] students = new Student[3];
        students[0] = new Student("zhang san",18,95.5);
        students[1] = new Student("li si",19,93.4);
        students[2] = new Student("wang wu",20,96.5);

        NameComparator nameComparator = new NameComparator();
        Arrays.sort(students,nameComparator);
        System.out.println(students);

    }

Cloneable接口

Cloneable接口是Java内置的接口,也较为常用的接口。Object类当中存在一个clone方法,调用这个方法可以创建一个对象的拷贝。

这时person1和person2在内存的存储为:

1、实例化person1对象后,将其字段该值,person2的同名字段为person1字段的拷贝。

    public static void main(String[] args) throws CloneNotSupportedException {
        Person person1 = new Person();
        person1.age = 99;
        Person person2 = (Person) person1.clone();
        System.out.println(person2);
    }

 person2的打印结果也为99.

2、当实例化对象person1的拷贝person2后,修改person2字段的值,perso1字段的值不变。


    public static void main(String[] args) throws CloneNotSupportedException {
        Person person1 = new Person();
        Person person2 = (Person) person1.clone();
        person2.age = 199;
        System.out.println(person1);
        System.out.println(person2);
    }

打印结果为99,199

所以这种方法是深拷贝还是浅拷贝?

需要注意的是,确定深拷贝还是浅拷贝的依据不是调用的方法,而是拷贝的过程属于什么拷贝、拷贝了什么内容。

以下情况为浅拷贝:

1.定义了一个Money类,同时在Person类当中实例化这个类。

class Money {
    public double m = 12.5;
}
class Person implements Cloneable{
    public int age;
    public Money money = new Money();
    public void eat() {
        System.out.println("吃!");
    }

    @Override
    public String toString() {
        return "Person{" +
                "age=" + age +
                '}';
    }

    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
}

其在内存的存储为:

1、分别通过person1对象和person2对象引用money对象打印m:

    public static void main(String[] args) throws CloneNotSupportedException {
        Person person1 = new Person();
        Person person2 = (Person) person1.clone();
        System.out.println(person1.money.m);
        System.out.println(person2.money.m);
    }
    
    //此时打印结果都为12.5

2、通过person2对象引用money再修改m的值:

    public static void main(String[] args) throws CloneNotSupportedException {
        Person person1 = new Person();
        Person person2 = (Person) person1.clone();
        person1.money.m = 99.9;
        System.out.println(person1.money.m);
        System.out.println(person2.money.m);
    }
    
    //此时打印结果都为99.9

如何理解这种拷贝为浅拷贝?

这种拷贝没有将money引用指向的对象拷贝一份,而是使person2对象当中money引用指向已经实例的对象。

如何达到深拷贝效果?需将money引用所指的对象也再拷贝一份,这时Money类需要实现克隆接口,Person类需重新定义克隆方法。

此时在内存的存储为:

1、分别通过person1对象和person2对象引用money对象打印m:

    public static void main(String[] args) throws CloneNotSupportedException {
        Person person1 = new Person();
        Person person2 = (Person) person1.clone();
        System.out.println(person1.money.m);
        System.out.println(person2.money.m);
    }
    
    //此时打印结果都为12.5

 2、通过person2对象引用money再修改m的值:

    public static void main(String[] args) throws CloneNotSupportedException {
        Person person1 = new Person();
        Person person2 = (Person) person1.clone();
        person1.money.m = 99.9;
        System.out.println(person1.money.m);
        System.out.println(person2.money.m);
    }
   
    //这时person1.money.m打印结果为12.5;person2.money.m打印结果为99.9

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

爆裂突破手

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值