【JavaSE 语法】-抽象类和接口

目录

一、抽象类

1、抽象类语法

2、抽象类特征

3、抽象类作用

二、接口

1、接口语法

2、接口使用

3、接口的特性

4、实现多个接口

5、接口使用实例

5.1、给对象数组排序

5.2、总结

6、Clonable接口和深拷贝

6.1、Clonable接口及浅拷贝

6.2、深拷贝

6.3、浅拷贝和深拷贝的区别

7、抽象类和接口的区别

7.1、相同点

7.2、不同点

8、object类以及相关知识

8.1、object类

8.2、toString()

8.3、equals()

8.4、hashcode()


一、抽象类

1、抽象类语法

抽象类:用abstract修饰的类。在抽象类中一般都有抽象方法(顺序和个数不限),普通成员属性,成员方法,构造方法可有可无abstract尽量放在抽象类或

抽象方法最前面,便于观察。

代码示例:

//抽象类(用abstract修饰)
abstract class Shape {
    //抽象方法(用abstract修饰)  一般都有
    abstract public void draw();

    //普通成员变量和方法    可有可无
    private String name;
    public void func() {

    }
    public Shape() {

    }
}

2、抽象类特征

一、抽象类不能直接实例化对象

Shape shape = new Shape() 

//编译错误
//Shape是抽象类,无法实例化

二、抽象方法不能被private的修饰

//抽象类
abstract class Shape {
    //抽象方法
    abstract private void draw();
}

//编译出错
//非法的修饰组合:abstract和private

注意抽象方法没有加限定符时,默认是public

三、抽象方法不能被final和static修饰,因为抽象方法要被子类重写

//抽象类
abstract class Shape {
    //抽象方法
    abstract public final void drawA();
    abstract public static void drawB();
}
//编译错误
//非法的修饰符组合: abstract和final
//非法的修饰符组合: abstract和static

四、抽象类必须被继承,并且继承后父类中的所有抽象方法,子类都要重写。若不重写,子类也是抽象类,必须使用abstract修饰。

//抽象类
abstract class Shape {
    //抽象方法
    abstract public void drawA();
    abstract public void drawB();
}

//子类继承父类并重写抽象方法
class Circle extends Shape {
    public void drawA() {
        
    }
    public void drawB() {
        
    }
}
//继承父类但仍为抽象方法,此时它是一个有三个抽象方法的抽象类
abstract class All extends Shape{
    abstract public void func();
}

抽象类特征汇总:

  1. 抽象类不能直接实例化对象
  2. 抽象类不能被private修饰
  3. 抽象方法不能被private的修饰
  4. 抽象方法不能被final和static修饰,因为抽象方法要被子类重写
  5. 抽象类必须被继承,并且继承后父类中的所有抽象方法,子类都要重写。若不重写,子类也是抽象类,必须使用abstract修饰。
  6. 抽象类中不一定包含抽象方法(这种抽象方法用不了),但有抽象方法的类一定是抽象类。
  7. 抽象类可以有构造方法,供子类创建对象时,初始化父类的成员变量

3、抽象类作用

说明:抽象类本身不能被实例化,要想使用只能创建该抽象类的子类,然后让子类重写该抽象类的方法。

优点:使用抽象类相当于多了一重编译器的校验。例如:

  • 使用抽象类的场景就如上面的代码, 实际工作不应该由父类完成,,而应由子类完成. 那么此时如果不小心误用成父类 了, 使用普通类编译器是不会报错的但是父类是抽象类就会在实例化的时候提示错误, 让我们尽早发现问题。

二、接口

1、接口语法

一、什么是接口?

在Java中,接口可以看成是:多个类的公共规范,是一种引用数据类型。

二、语法规则

格式:接口定义和类的定义相似,将class换成interface,就定义了一个接口。

//A为接口名称,同样用一个项目中只能有一个public类或public接口
public interface A { 
    //抽象方法
    abstract public void method1();
    void method2();//默认为abstract public(可交换位置),抽象类中则不能省略
    abstract void method3();
    public void method4();
    //以上四种都是抽象方法,但更推荐第二中,更为简洁
}

2、接口使用

说明:接口不能直接使用,必须要有一个类来实现该接口,在这个类中实现该接口的所有抽象方法

注意:

  1. 类和类之间接口和接口之间为extends接口和类之间implements实现关系
  2. 接口继承:可以用一个接口继承另外两个接口再用一个类去实现这个接口
//Flyable接口
interface Flyable {
    void method();
}
//实现接口的类
class Bird implements Flyable {
    public void method() {

    }
}

例子:USB接口+Mouse类+KeyBoard类+Computer类+TestUSB类

USB接口:

package interface_USB;

public interface USB {
    //抽象方法
    void openDevice();
    void closeDevice();
}

Mouse类:

package interface_USB;

public class Mouse implements USB{
    //重写USB接口中的方法
    @Override
    public void openDevice() {
        System.out.println("打开鼠标");
    }
    @Override
    public void closeDevice() {
        System.out.println("关闭鼠标");
    }
    //Mouse特有的方法
    public void click() {
        System.out.println("点击鼠标");
    }
}

KeyBoard类:

package interface_USB;

public class KeyBoard implements USB {
    @Override
    public void openDevice() {
        System.out.println("打开键盘");
    }
    @Override
    public void closeDevice() {
        System.out.println("关闭键盘");
    }
    public void input() {
        System.out.println("键盘输入");
    }
}

Computer类:

package interface_USB;

public class Computer {//在Computer内使用KeyBoard和Mouse两个类
    public void powerOn() {
        System.out.println("打开笔记本电脑");
    }
    public void powerOff() {
        System.out.println("关闭笔记本电脑");
    }

    public void useDevice(USB usb) { //向上转型
        usb.openDevice(); //无论是鼠标还是键盘都需要打开和关闭,因此放在if-else外
        if(usb instanceof Mouse) {
            Mouse mouse = (Mouse) usb;//向下转型
            mouse.click();
        }else if(usb instanceof KeyBoard) {
            KeyBoard keyBoard = (KeyBoard) usb;
            keyBoard.input();
        }
        usb.closeDevice();
    }
}

TestUSB类:

package interface_USB;

public class TestUSB {
    public static void main(String[] args) {
        Computer computer = new Computer();
        computer.powerOn();

        //使用鼠标设备
        computer.useDevice(new Mouse());
        //使用键盘设备
        computer.useDevice(new KeyBoard());

        computer.powerOff();
    }
}

结果演示:

打开笔记本电脑
打开鼠标
点击鼠标
关闭鼠标
打开键盘
键盘输入
关闭键盘
关闭笔记本电脑

3、接口的特性

  1. 接口类型是一种引用类型,但不能直接new接口的对象
  2. 接口的成员变量只能是常量(默认:public static final),成员方法只能是抽象方法(默认:public abstract)。和抽象类不同。
  3. 接口中的方法不能在接口中实现,只能由实现接口的类来实现
  4. 接口中的抽象方法只能被public修饰。(默认为public)
  5. 接口中不能有静态代码块和构造方法(在类中实现初始化)
  6. 接口虽然不是类,但是经过编译后字节码文件后缀也是.class
  7. jdk8中,接口还包含default方法

4、实现多个接口

在Java中,没有多继承,即一个类只能有一个父类。但是一个类可以实现多个接口。如下

1、先给出Flying、Running、Swimming三个接口:

Flying:

package interface_;

public interface IFlying {
    void flying();
}

Running:

package interface_;

public interface IRunning {
    void running();
}

Swimming:

package interface_;

public interface ISwimming {
    void swimming();
}

2、给出父类Animal,子类Dog、Chicken

Animal:

package interface_;

public class Animal {
    public String name;

    //构造方法,便于初始化Animal中的name
    public Animal(String name){
        this.name=name;
    }
    
    public void bark(){
        System.out.println("叫");
    }
}

Dog:

package interface_;

//要先extends(继承),后implements(实现)
public class Dog extends Animal implements ISwimming,IRunning{
    public int age;

    //重写接口中的抽象方法
    @Override
    public void swimming() {
        System.out.println(super.name + this.age + "swimming");
    }
    @Override
    public void running() {
        System.out.println(super.name + this.age + "running");
    }

    public Dog(String name,int age){
        super(name); //调用父类构造完成初始化初始化
        this.age=age;
    }

    //重写从父类继承的bark()方法,以发生多态
    @Override
    public void bark() {
        System.out.println(this.name + "汪汪叫");
    }
}

Chicken:

package interface_;

public class Chicken extends Animal implements IRunning,IFlying{
    //重写接口中的抽象方法
    @Override
    public void running() {
        System.out.println(super.name + "在疾跑中");
    }
    @Override
    public void flying() {
        System.out.println(super.name + "在飞翔");
    }

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

    //重写从父类继承的方法
    @Override
    public void bark() {
        System.out.println(super.name + "在报晓");
    }
}

3、最后给测试的类Interface

Interface:

package interface_;

public class Interface {
    //实现多态
    public static void show(Animal animal) { //向上转型,实现多态
        animal.bark(); //动态绑定,实现多态
    }
//    法一
    //实现接口重写方法的多态
    public static void run(IRunning irunning) {
        irunning.running();
    }
    public static void fly(IFlying iflying) {
        iflying.flying();
    }
    public static void swim(ISwimming iswimming) {
        iswimming.swimming();
    }

//    法二
    //访问接口抽象类的重写方法
    public static void scan(Animal animal) {//注意static
        if(animal instanceof Dog) {
            ((Dog) animal).swimming();
            ((Dog) animal).running();//重写后,Dog的running和Chicken的running不一样
        }else if(animal instanceof Chicken){
            ((Chicken)animal).flying();
            ((Chicken)animal).running();
        }
    }

    public static void main(String[] args) {
        Dog dog = new Dog("油条",3);
        Chicken chicken = new Chicken("鸡你太美");

        //dog实现接口抽象方法重写,实现多态
        System.out.println("法一");
        System.out.println("dog实现接口抽象方法重写,实现多态");
        run(dog);
        //fly(dog);  Dog没有继承Flying这个接口,所以该行会报错
        swim(dog);
        System.out.println("----------------");

        //chicken实现接口抽象方法重写,实现多态
        System.out.println("chicken实现接口抽象方法重写,实现多态");
        run(chicken);
        fly(chicken);
        //swim(chicken);  Chicken没有继承Swimming这个接口,所以该行会报错
        System.out.println("----------------");

        //调用方法从而访问接口中抽象方法的重写方法
        System.out.println("法二");
        System.out.println("调用方法从而访问接口中抽象方法的重写方法");
        scan(dog);
        scan(chicken);
        System.out.println("----------------");

        //重写从父类继承的方法,实现多态
        System.out.println("重写从父类继承的方法,实现多态");
        dog.bark();
        chicken.bark();

    }
}

4、结果演示:

法一
dog实现接口抽象方法重写,实现多态
油条3running
油条3swimming
----------------
chicken实现接口抽象方法重写,实现多态
鸡你太美在疾跑中
鸡你太美在飞翔
----------------
法二
调用方法从而访问接口中抽象方法的重写方法
油条3swimming
油条3running
鸡你太美在飞翔
鸡你太美在疾跑中
----------------
重写从父类继承的方法,实现多态
油条汪汪叫
鸡你太美在报晓

5、接口使用实例

5.1、给对象数组排序

法一法二核心:让Person类实现Comparable接口

法一:重写compareTo,toString利用Arrays下的sort,toStringage排序

package object_class;

import java.util.Arrays;

class Person implements Comparable{
    private String name;
    private int age;

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

    //重写Comparable中抽象方法
    @Override
    public int compareTo(Object object) {
        if(object instanceof Person) {
            Person person = (Person) object;
            if(this.age>person.age) { 
                return -1;
            }else if(this.age<person.age) {
                return 1;
            }else {
                return 0;
            }
        }
        return 0;
    }

    //重写toString,和Arrays下的toString配合使用
    @Override
    public String toString() { //如果不重写toString,返回值是一个地址
        return "{"+this.name+","+this.age+"}";
    }
}
//测试类
public class SortTest {
    public static void main(String[] args) {
        Person[] peoples = {
                new Person("zhangsan",19),
                new Person("wangwu",17),
                new Person("alan",21)
        };
        //利用Arrays下的sort,toString
        Arrays.sort(peoples);
        System.out.println(Arrays.toString(peoples));
    }
}

结果演示:

[{alan,21}, {zhangsan,19}, {wangwu,17}]

解释:

  1. Person类中:重写接口Comparable下的抽象方法compareTo,重写从Object类继承的方法toString
  2. 利用Arrays下的sort排序,调用sort时,会自动调用重写的compareTo方法
  3. 利用Arrays下的toString输出,实现把Person型的对象中的成员转变成字符串输出(但输出的是地址),重写的toString保证输出的是对象成员

法二:重写compareTo,toString,在测试类中重新实现sort,利用Arrays下的toStringname排序

package object_class;

import java.util.Arrays;

//Person类实现Comparable
class Person implements Comparable{
    private String name;
    private int age;

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

    //重写Comparable中抽象方法compareTo
    @Override
    public int compareTo(Object object) {
        if(object instanceof Person) {
            Person person = (Person) object;
            if(this.name.compareTo(person.name)>0) { //compareTo在这里是源码重写的
                return -1;
            }else if(this.name.compareTo(person.name)<0) {
                return 1;
            }else {
                return 0;
            }
        }
        return 0;
    }

    //重写toString,和Arrays下的toString配合使用
    @Override
    public String toString() { //如果不重写toString,返回值是一个地址
        return "{"+this.name+","+this.age+"}";
    }
}
//测试类
public class SortTest {
    //我们自己重新实现sort
    public static void bubbleSort(Comparable[] peoples) {
        for (int i = 0; i < peoples.length-1; i++) {
            for (int j = 0; j < peoples.length-1-i; j++) {
                if(peoples[j].compareTo(peoples[j+1])>0) {
                    Comparable tmp = peoples[j];
                    peoples[j] = peoples[j+1];
                    peoples[j+1] = tmp;
                }
            }
        }
    }
    public static void main(String[] args) {
        Person[] peoples = {
                new Person("zhangsan",19),
                new Person("wangwu",17),
                new Person("alan",21)
        };
        //重新实现sort,利用Arrays下的toString
        bubbleSort(peoples);
        System.out.println(Arrays.toString(peoples));
    }
}

结果演示:

[{zhangsan,19}, {wangwu,17}, {alan,21}]

解释:

  1. Person类中:重写接口Comparable下的抽象方法compareTo,重写从Object类继承的方法toString
  2. 在测试类SortTest中自己写一个bubbleSort方法,作用和Arrays下的sort一致
  3. 利用Arrays下的toString输出,重写从Object类继承的方法toString保证:输出的是对象成员,而不是对象地址

法三:创建新类成为比较的类(泛型,用<>),传类名

法三核心:创建以年龄(姓名)比较的类,来实现Comparator接口

package compare_class;

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

class Person {
    public String name; 
    public int age;

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

    //重写toString,和Arrays下的toString配合使用
    @Override
    public String toString() { //如果不重写toString,报错
        return "{"+this.name+","+this.age+"}";
    }
}

//Comparator中有compare和equals,但是equals是从object类继承的,不算是Comparator的抽象方法
// 所以用到Comparator不需要重写equals

//成员变量非private都可以用这种类实现比较
//以年龄比较
class AgeComparator implements Comparator<Person> {
    @Override
    public int compare(Person p1,Person p2) {
        return p1.age-p2.age;
    }
}
//以姓名比较
class NameComparator implements Comparator<Person> {

    public int compare(Person p1,Person p2) {
        //这里的compareTo是Arrays下的,
        // 所以不需要Person去实现Comparable,再重写compareTo
        return p1.name.compareTo(p2.name); 
    }
}
//测试类
public class Test {
    public static void main(String[] args) {
        Person[] peoples = {
                new Person("zhangsan",19),
                new Person("wangwu",17),
                new Person("alan",21)
        };

        System.out.println("以年龄排序:");
        AgeComparator ageComparator = new AgeComparator();//创建年龄的类
        Arrays.sort(peoples,ageComparator);
        System.out.println(Arrays.toString(peoples));

        System.out.println("--------------------------------------");

        System.out.println("以姓名排序:");
        NameComparator nameComparator = new NameComparator();
        Arrays.sort(peoples,nameComparator);
        System.out.println(Arrays.toString(peoples));
    }
}

结果演示:

以年龄排序:
[{wangwu,17}, {zhangsan,19}, {alan,21}]
--------------------------------------
以姓名排序:
[{alan,21}, {wangwu,17}, {zhangsan,19}]

解释:

  1. Comparator接口中有compare和equals两个方法,但是equals是从object类继承的,不算是Comparator的抽象方法 。所以用到Comparator不需要重写equals
  2. 成员变量非private就可以用这种类实现比较,如Penson类中成员变量是public
  3. 以姓名排序时,用到的compareTo可以通过Comparator找到,所以不需要Person去实现Comparable,再重写compareTo。
  4. 在Test中,AgeComparator和NameComparator要先new对象,然后再把对象引用传给Arrays下的sort。

5.2、总结

以后使用自定义类型比较大小,那么必须让这个类具备可比较的功能。

  • 可以让目标类实现接口Comparable再重写compareTo实现比较大小
  • 或者新建类,在实现泛型Comparator接口的类中,再重写compare来实现比较大小

6、Clonable接口和深拷贝

6.1、Clonable接口及浅拷贝

package clonable_deepcopy;

class Student implements Cloneable{
    public String id;

    public Student(String name) {
        this.id = name;
    }

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

    //重写Cloneable接口的克隆方法
    //throws CloneNotSupportedException表示异常
    @Override
    protected Object clone() throws CloneNotSupportedException { //返回Object类型
        return super.clone();//protected修饰,不同包中需要用super访问
    }
}

public class Clon{
    public static void main(String[] args) throws CloneNotSupportedException {
        Student person1 = new Student("1234");
        Student person2 = (Student) person1.clone();//向下转型

        System.out.println(person2);
    }
}

总结克隆:

1、实现Cloneable  2、重写clone  3、声明异常  4、向下转型

6.2、深拷贝


package clonable_deepcopy;

class Money implements Cloneable {
    public int m=100;

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

class Student implements Cloneable{
    public Money money = new Money();


    //深拷贝
    @Override
    protected Object clone() throws CloneNotSupportedException { //返回Object类型
        Student tmp = (Student) super.clone();//克隆了student1,使用Cloneable中的克隆   这里必须是super才行
        tmp.money = (Money) this.money.clone();//克隆了money,使用Money中的克隆
        return tmp;
    }
}

public class Clon{
    public static void main(String[] args) throws CloneNotSupportedException {
        Student student1 = new Student();
        System.out.println(student1.money.m);
        Student student2 = (Student) student1.clone();

        student1.money.m=1234;
        System.out.println(student1.money.m);
        System.out.println(student2.money.m);
    }
}
100   //Student类中Money类型money初始值
1234  //改变对象student1中money类型中money值
100   //student2不受影响,说明student1和student2中money指向的不是同一块空间,即完成了深拷贝

解释:一个类作为另一个类的成员,此时需要深拷贝

1、实现Cloneable 2、重写clone  3、声明异常(这三部分都需要对Student和Money类进行操作 )4、向下转型

注意:深拷贝时第一步利用Cloneable中的clone,用super.colone()拷贝student1对象,第二步利用Money中的重写的clone,用this.clone克隆这个对象(student1)的成员(money),最后返回克隆对象的引用

6.3、浅拷贝和深拷贝的区别

一、浅拷贝:

二、深拷贝:

7、抽象类和接口的区别

7.1、相同点

  1. 都不能实例化对象
  2. 若要使用,都需要重写抽象方法

7.2、不同点

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

其它区别

  1. 抽象类不能由private修饰,而接口只能由public修饰
  2. 子类通过extends继承抽象类子类通过implements实现接口,并且都需要实现抽象方法
  3. 抽象类中子类只能继承一个抽象类,但接口中子类能实现多个接口
  4. 抽象类可以有构造方法,而接口不能有构造方法。
  5. 抽象类可以有成员变量,而接口只有常量,默认为public static final。

8、object类以及相关知识

8.1、object类

object类是所有类的父类。我们写的类默认继承object类。所以所以类的对象都可以用object的引用进行接收

8.2、toString()

Object类中的toString()方法实现:

// Object类中的toString()方法实现:
public String toString() { 
    return getClass().getName() + "@" + Integer.toHexString(hashCode()); 
}

8.3、equals()

Java 中, == 进行比较时:
  1. 如果==左右两侧是基本类型变量,比较的是变量值是否相同
  2. 如果==左右两侧是引用类型变量,比较的是引用变量的地址是否相同
  3. 若要比较对象中的内容,必须重写Object类的equals()方法,因为equals()方法也是默认按地址比较的

1、Object类中的equals方法实现(比较地址):

// Object类中的equals方法
public boolean equals(Object obj) { 
    return (this == obj); // 使用引用中的地址直接来进行比较 
}
//this(person1): person@483
//obj(person2):  person@484

2、重写equals(),使之可以比较引用类型(比较对象具体内容):

@Override
public boolean equals(Object object) {
    if(object==null) {
        return false;
    }
    if(this==object){ //比较二者引用在同一个地址  //调用equals的this不可能是null
        return true;
    }
    if(!(object instanceof Person)) { //子类不是Person类,就返回false
        return false;
    }
    Person person = (Person) object; //向下转型
    //这里的equals比较String类型,已经被系统重写,不再是object的equals
    return this.name.equals(person.name) && this.age==person.age;
}

8.4、hashcode()

Object类中hashCode()源码:

解释:该方法是一个native方法,底层是由C/C++写的。

public native int hashCode();

1、但如果不重写hashCode,相同内容的对象hashCode值不一样

package object_class;

class Person {
    private String name;
    private int age;

    public Person(String name,int age) {
        this.name=name;
        this.age=age;
    }
}
public class Test {
    public static void main(String[] args) {
        Person person1 = new Person("zhangsan",18);
        Person person2 = new Person("zhangsan",18);
 
        System.out.println(person1.hashCode());
        System.out.println(person2.hashCode());
    }
}

结果演示:

460141958
1163157884

2、重写相同内容的对象hashCode相同(在数据结构哈希表中):

//重写hashCode
@Override
public int hashCode() {
    //计算对象的位置
     return Objects.hash(name,age); 
}

结果演示:

-1461067297
-1461067297
  • 4
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 5
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

学Java的冬瓜

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

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

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

打赏作者

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

抵扣说明:

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

余额充值