java第十一课——抽象类和接口

目录

一:抽象类

1.1抽象类的概念

1.2抽象类的语法

1.3抽象类的特性

二:接口

2.1接口概念

2.2接口的特点

 2.3接口的优势

2.4接口间的继承

2.5接口使用实例

2.5.1给对象数组排序

2.5.2Cloneable 接口和深拷贝

5.2.1浅拷贝

 5.2.2深拷贝

三:Object类

3.1toString()方法

 3.2equal()方法

 3.3hashCode()方法


一:抽象类

1.1抽象类的概念

        在面向对象的概念中,所有的对象都是通过类来描绘的,但是反过来,并不是所有的类都是用来描绘对象的,如果一个类中没有包含足够的信息来描绘一个具体的对象,这样的类就是抽象类。

        例如,这里有三个类,🐕类和🐱类分别继承Animal,并重写Animal中的bark()方法。对于Animal类而言,由于其并不会指向一个具体的动物,所以其中的bark()方法无需进行实现。即这个类中没有包含足够的信息来描绘一个具体的对象。这样的Animal类就是抽象类。

1.2抽象类的语法

        在Java中,一个类如果被abstract 修饰称为抽象类,抽象类中被abstract 修饰的方法称为抽象方法,抽象方法不用给出具体的实现。

1.3抽象类的特性

1.抽象类不能被实例化;(这个我们容易理解,抽象类的存在,本就不用于指定具体的对象)

2.抽象类中的成员变量和成员方法都与普通的类一样;(抽象类也是类)

3.抽象类存在的最大意义,就是被继承;

4.抽象类也可以发生向上转型,进一步发生多态;

5.当一个普通类,继承这个抽象类,这个普通类必须重写抽象类中的所有抽象方法;

6.当一个抽象类B继承了抽象类A,那么抽象类B可以不重写抽象类A当中的抽象方法;

7.当一个普通的类C继承了'6'中的抽象类B,就必须重写所有的抽象方法;

8.final不能修饰抽象方法和抽象类;(因为final修饰的不能重写,abstract修饰的必须重写)

9.抽象方法不能是private的;

10.抽象类当中不一定有抽象方法,但如果这个方法是抽象方法,那么这个类一定是抽象类。

二:接口

2.1接口概念

        接口是一种行为的规范和标准。

2.2接口的特点

1.接口使用关键字interface来修饰;

2.接口当中的成员方法,只能是抽象方法,所有的方法,默认都是public abstract类型;

3.接口当中的成员变量,默认是public static final类型;

4.如果要实现接口当中的方法,需要用default来修饰;

5.接口当中的静态方法,可以有具体的实现;

//1.接口使用关键字interface来修饰
interface IShape{

//2.接口当中的成员方法,只能是抽象方法,所有的方法,默认都是public abstract类型;
// public abstract void func()相当于void func()
    
    public abstract void func();

//3.成员变量,默认是public static final类型;
    public static final int a = 10;

//4.如果要实现接口当中的方法,需要用default来修饰;
    default void func1(){
        System.out.println("默认方法!");
    }
    
//5.接口当中的静态方法,可以有具体的实现。
    public static void staticFunc(){
        System.out.println("静态方法!");
    }
}

6.接口不能进行实例化;

7.一个普通类可以通过implements来实现一个接口 .

具体实例如下:

        这是一个画图的示例代码,父类为IShape类,子类则重写父类中的draw()方法,绘制不同的、具体的图形。

interface IShape {
    void draw();
}

class Cycle implements IShape {
    @Override
    public void draw() {
        System.out.println("●");
    }
}
class Rect implements IShape {
    @Override
    public void draw() {
        System.out.println("♦");
    }
}

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

public class TestDemo1 {

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

    public static void main(String[] args) {
        Cycle cycle = new Cycle();
        Rect rect = new Rect();
        Triangle triangle = new Triangle();
        drawMap(cycle);
        drawMap(rect);
        drawMap(triangle);
    }

}

运行结果如下:

8.一个类,可以继承抽象类,同时实现多个接口,每个接口之间使用逗号隔开。具体实例如下:

package Test1;

class Animal {
    public String name;

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

    public void eat() {
        System.out.println(this.name + " 吃饭!");
    }
}

    interface IFlying {
        void fly();
    }

    interface IRunning {
        void run();
    }

    interface ISwimming {
        void swim();
    }

    class Duck extends Animal implements IRunning,ISwimming,IFlying {

        public Duck(String name) {
            super(name);
        }

        @Override
        public void fly() {
            System.out.println(this.name+" 正在飞!");
        }

        @Override
        public void run() {
            System.out.println(this.name+" 正在跑!");
        }

        @Override
        public void swim() {
            System.out.println(this.name+" 正在游!");
        }
    }


public class TestDemo1 {
    public static void run(IRunning iRunning) {
        iRunning.run();
    }

    public static void swim(ISwimming iSwimming) {
        iSwimming.swim();
    }

    public static void fly(IFlying iFlying) {
        iFlying.fly();
    }

    public static void main(String[] args) {
        Duck duck = new Duck("唐老鸭");
        duck.fly();
        duck.run();
        duck.swim();
        System.out.println("=============");
        fly(duck);
        run(duck);
        swim(duck);
    }
}

运行结果如下:

具体分析:

 

 

注意:

  • IDEA中可以使用ctrl+i快速实现接口。 
  • IDEA中可以使用alt+回车快速实现接口。 

 2.3接口的优势

        有了接口之后, 类的使用者就不必关注具体类型,而只关注某个类是否具备某种能力即可。

        例如, 现在实现一个方法, 叫 "跑步"。

public static void walk(IRunning running) {
    System.out.println("跑步");
    running.run();
}

        在这个 walk 方法内部, 我们并不关注到底是哪种动物, 只要参数是会跑的, 就行。

Cat cat = new Cat("洗洗");
walk(cat);
Frog frog = new Frog("乔治");
walk(frog);
// 执行结果
跑步
洗洗正在用四条腿跑
跑步
乔治正在往前跳

甚至参数可以不是 "动物", 只要会跑!

class Robot implements IRunning {
        private String name;
        public Robot(String name) {
        this.name = name;
        }
       @Override
       public void run() {
       System.out.println(this.name + "正在用轮子跑");
       }
}
Robot robot = new Robot("阿尔法狗");
run(robot);
// 执行结果
阿尔法狗正在用轮子跑 

2.4接口间的继承

         我们只需知道两点,第一,接口间的继承表示的是一个接口拓展了另外一个接口的功能;第二,接口之间的继承用的是extends。

interface IRunning {
void run();
}
interface ISwimming {
void swim();
}
// 两栖动物, 既能跑, 也能游
interface IAmphibious extends IRunning, ISwimming {
}
class Frog implements IAmphibious {
...
}

        当然,你拓展了接口,自然要重写所有的抽象方法。

2.5接口使用实例

2.5.1给对象数组排序

        我们首先回忆一下如何对整型数组进行排序:

public class TestDemo2 {

    public static void main(String[] args) {
        int[] array = {1,21,4,15,6,17};
        Arrays.sort(array);
        System.out.println(Arrays.toString(array));
    }
}

 运行结果如下:

         显然,对整型数组排序,直接调用Arrays.sort方法即可。

        如果我们照猫画虎,对如下的学生数组进行排序,是否也可以直接调用Arrays.sort方法进行排序呢?代码实例及运行结果如下:

package Test1;

import java.util.Arrays;

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;
    }
 }
public class TestDemo2 {
    public static void main(String[] args) {
        Student[] students = new Student[3];
        students[0] = new Student("zhagnsan",98,58.9);
        students[1] = new Student("lisi",38,98.9);
        students[2] = new Student("aboluo",78,88.9);
        Arrays.sort(students);
        System.out.println("排序后的结果为:");
        System.out.println(Arrays.toString(students));

    }

}

        不好意思,抛出了异常;这是为何呢?原因是我们没有给出排序的依据,是根据姓名、年龄、还是分数呢?

        解决方案是,让我们的 Student 类实现 Comparable 接口, 并实现其中的 compareTo 方法。对于Comparable接口,我们需要了解:

 基于这种认识,我们需要在Student类当中重写Comparable接口中的抽象方法:

具体代码如下:

package Test1;

import java.util.Arrays;

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) {
        //return this.name.compareTo(o.name);
        //return o.age-this.age;
        return (int)(this.score-o.score);
    }
    }
public class TestDemo2 {



    public static void main(String[] args) {
        Student[] students = new Student[3];
        students[0] = new Student("zhagnsan",98,58.9);
        students[1] = new Student("lisi",38,98.9);
        students[2] = new Student("aboluo",78,88.9);
        Arrays.sort(students);
        System.out.println("按年龄排序后的结果为:");
        System.out.println(Arrays.toString(students));
    }
}

运行结果如下:

         这样的代码还有很大的缺陷,即对于比较的标准,在类中做了具体的规定。在这里我们通过年龄进行排序,一旦我们有一天需要根据分数进行排序,就需要对Student类中的代码进行修改,很不方便。一种明智的解决方案是通过比较器进行比较。

具体代码如下:

package Test1;

import java.util.Arrays;
import java.util.Comparator;

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 +
                '}';
    }
}
    //年龄比较器
    class AgeComparator implements Comparator<Student> {

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

    //姓名比较器
    class StringComparator implements Comparator<Student> {
        @Override
        public int compare(Student o1, Student o2) {
            return o1.name.compareTo(o2.name);
        }
    }

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


public class TestDemo3 {

    public static void main(String[] args) {
        Student[] students = new Student[3];
        students[0] = new Student("zhagnsan",98,58.9);
        students[1] = new Student("lisi",38,98.9);
        students[2] = new Student("aboluo",78,88.9);
        ScoreComparator scoreComparator = new ScoreComparator();
        Arrays.sort(students,scoreComparator);
        System.out.println("按分数排序后的结果为:");
        System.out.println(Arrays.toString(students));
    }
}

运行结果如下:

 分析:

Q:为什么不重写Comparator接口中的equals等方法呢?

Comparator接口中的方法

 A:如果一个类既有继承又有实现,并且接口中的方法与父类接口中的方法相同,那么该类不用重写接口中的方法也可以。这就要说起java的祖宗Object了,Object是所有java类的父类,定义了一些方法,其中就有equals方法,让我们来看一下。所以为什么不用重写呢?因为父类已经有该方法且是具体的方法。

2.5.2Cloneable 接口和深浅拷贝

5.2.1浅拷贝

        浅拷贝是会将对象的每个属性进行依次复制,但是当对象的属性值是引用类型时,实质复制的是其引用,当引用指向的值改变时也会跟着变化。我们先来看一个例子:

        首先,我们创建一个Person类,实例化一个person1对象,并尝试进行拷贝。

         显然代码出现了错误,此时不能直接进行拷贝。那么拷贝的要求是什么呢?一个对象可以被克隆,必须满足两个条件:

 具体步骤如下:

S1:在Person类中实现Cloneable接口。

S2:重写Cloneable接口中的clone()方法。将鼠标放在空白处,按下ctrl+O,选择clone():Object方法,点击OK。

这里的意思,即调用父类Object类中的clone()方法。

 S3.将person.clone()强转为Person,此为最特殊的一步。

        三步完成,大局已定,直接运行即可,具体代码如下: 

package Test;

class Person implements Cloneable{
    public int id;

    public Person(int id) {
        this.id = id;
    }

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

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


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

运行结果如下:

分析:

         我们现在讲了半天,其实这个Person类中只有一个简单的成员变量,并没有引用类型。我们再回顾浅拷贝的定义,是会将对象的每个属性进行依次复制,但是当对象的属性值是引用类型时,实质复制的是其引用,当引用指向的值改变时也会跟着变化。所以现在,我会在Person类中添加一个引用类型的对象,我们来看看会发生什么。

package Test;


class Money{
    public double money = 19.9;
}


class Person implements Cloneable{
    public int id;
    public Money m = new Money();

    public Person(int id) {
        this.id = id;
    }

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

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


}
public class TestDemo {
    public static void main(String[] args) throws CloneNotSupportedException {
        Person person1 = new Person(1234);
        Person person2 = (Person)person1.clone();
        System.out.println(person1.m.money);
        System.out.println(person2.m.money);
        System.out.println("=========================");
        person2.m.money = 99.99;
        System.out.println(person1.m.money);
        System.out.println(person2.m.money);
    }
}

 运行结果如下:

        显然,当我使用person2对象改变money值后,再通过person1对象访问money值,同样发生了变化。 即当对象的属性值是引用类型时,实质复制的是其引用,当引用指向的值改变时也会跟着变化。这种拷贝,我们称之为浅拷贝。

分析:

 5.2.2深拷贝

        深拷贝复制变量值,对于引用数据,则递归至基本类型后,再复制。深拷贝后的对象与原来的对象是完全隔离的,互不影响,对一个对象的修改并不会影响另一个对象。

        对于我们上面的例子,如何实现深拷贝呢?简单,既然调用clone()方法能够实现克隆,那我再克隆一份person1所指向的m所指向的money,然后让person2所指向的m指向这份新的money,不就好了吗?具体步骤如下:

S1:在Money类中实现Cloneable接口,并重写Cloneable接口中的clone()方法。

S2: 在Person类中的clone()方法也要发生相应的改变。在“浅拷贝”中,我们直接return super.clone()即可,而在“深拷贝”中,需要对引用进行拷贝。

分析:

         

        此时,当我使用person2对象改变money值后,再通过person1对象访问money值,不再发生变化。具体代码如下:

package Test;


class Money implements Cloneable{
    public double money = 19.9;

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


class Person implements Cloneable{
    public int id;
    public Money m = new Money();

    public Person(int id) {
        this.id = id;
    }

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

    @Override
    protected Object clone() throws CloneNotSupportedException {
        Person tmp = (Person)super.clone();
        tmp.m = (Money)this.m.clone();
        return tmp;
    }


}
public class TestDemo {
    public static void main(String[] args) throws CloneNotSupportedException {
        Person person1 = new Person(1234);
        Person person2 = (Person)person1.clone();
        System.out.println(person1.m.money);
        System.out.println(person2.m.money);
        System.out.println("=========================");
        person2.m.money = 99.99;
        System.out.println(person1.m.money);
        System.out.println(person2.m.money);
    }
}

运行结果如下:

 注意:
1.实现深拷贝,是从代码层次上进行的,不是说某个方法是深拷贝,是从代码的实现来看的;
2.要达到深拷贝,如果对象中有引用,这个对象的引用所指的对象也要进行拷贝;
3.拷贝完成之后,通过这个引用修改其指向的数据,原来的不会发生改变。

补充:抽象类和接口的区别

核心区别: 抽象类中可以包含普通方法和普通字段, 这样的普通方法和字段可以被子类直接使用(不必重写), 而接口中不能包含普通方法, 子类必须重写所有的抽象方法。 

三:Object类

        Object是Java默认提供的一个类。Java里面除了Object类,所有的类都是存在继承关系的。默认会继承Object父类。即所有类的对象都可以使用Object的引用进行接收。

        整个Object类中的方法如下图所示:

        在本课中,我会主要介绍toString()方法,equals()方法,hashcode()方法。

3.1toString()方法

        如果要打印对象中的内容,可以直接重写Object类中的toString()方法。具体实例如下:

package Test;


class Player{
    public int age;
    String name;
    public int salary;

    public Player(int age, String name, int salary) {
        this.age = age;
        this.name = name;
        this.salary = salary;
    }

    @Override
    public String toString() {
        return "Player{" +
                "age=" + age +
                ", name='" + name + '\'' +
                ", salary=" + salary +
                '}';
    }
}
public class TestDemo3 {
    public static void main(String[] args) {
        Player p1 = new Player(37,"勒布朗詹姆斯",37800000);
        System.out.println(p1);
    }
}

运行结果如下:

 3.2equal()方法

        作用:用于比较两个对象中内容是否相同。首先我们要了解,什么情况下认为两个对象中内容相同?

        如何利用编译器生成equals()方法?

S1:

 S2:

 S3:直接按默认设置next,最后finish即可。

分析:

具体代码如下:

package Test;

import java.util.Objects;

class Student {
    public String name;
    public int age;

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

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

    //用来判断 两个引用 所引用的对象 是不是一样的


    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Student student = (Student) o;
        return age == student.age &&
                Objects.equals(name, student.name);
    }

}


public class TestDemo4 {

    public static void main(String[] args) {
        Student student1 = new Student("gaobo",18);
        Student student2 = new Student("gaobo",18);
        System.out.println(student1.equals(student2));
    }
}

 运行结果如下:

 3.3hashCode()方法

        hashCode()这个方法用于计算一个具体的对象位置,我们可以初步理解为计算了内存地址所在。

        内容完全一样的两个对象,我们认为它们应该放在同一个地址,所以这时需要重写hashCode()方法以达到我们的目的。具体代码如下:

package Test;

import java.util.Objects;

class Student {
    public String name;
    public int age;

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

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

    //用来判断 两个引用 所引用的对象 是不是一样的

    @Override
    public int hashCode() {
        return Objects.hash(name, age);
    }
}


public class TestDemo4 {

    public static void main(String[] args) {
        Student student1 = new Student("gaobo",18);
        Student student2 = new Student("gaobo",18);
        System.out.println("对象位置:");
        System.out.println(student1.hashCode());
        System.out.println(student2.hashCode());
    }
}

运行结果如下:

         显然,结果确实如我们所预想的那样。

结论:

1、hashcode方法用来确定对象在内存中存储的位置是否相同 ;
2、事实上hashCode() 在散列表中才有用,在其它情况下没什么用。在散列表中hashCode() 的作用是获取对象的散列码,进而确定该对象在散列表中的位置。

        最后说一点,在之前已经分析了Object可以接收任意的对象,因为Object是所有类的父类,但是Obejct并不局限于此,它可以接收所有数据类型,包括:类、数组、接口。不过坦白讲,这些内容并不会经常用到,也没必要去纠结。


本课内容完! 

  • 4
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值