抽象类与接口

抽象类

抽象类的概念

Java中使用类实例化对象描述现实生活中的实体,但是并不是所有的类都可以描述一个实体,如果一个类没有包含足够的信息来描述一个具体的类,那么这个类就是抽象类,或者说,将这样的类设计为抽象类。

我们来看下面的一段代码:

// Test.java
public class Test {
    public static void main(String[] args) {
        Flower flower = new Flower();
        Round round = new Round();

        Shape[] shapes = {flower,round};

        for (Shape shape:shapes) {
            shape.draw();
        }
    }
}

// Flower.java

public class Flower extends Shape{
    public void draw() {
        System.out.println("花一朵❀");
    }
}

// Round.java

public class Round extends Shape{
    public void draw() {
        System.out.println("画一个圆形");
    }
}

// Shape.java

public class Shape {
    public void draw(){
        System.out.println("打印一个图形");
    };
}

当我们实现Shape类的draw方法时,由于Shape的含义比较的宽泛,不是可以用具体的图形去描述,因此对应Shape来说,可以将它设置为抽象类,被其他类所继承。

抽象类的语法

1.抽象方法中不能有主体

在Java中被abstract修饰的类被称之为抽象类,抽象类中被abstract修饰方法称之为抽象方法。
在上述的代码中,执行的结果是花一朵❀,画一个圆形,并没有打印一个图形,那么如果我们将Shape写成一个抽象方法,那么内部还需要具体的实现吗?不需要,抽象类中并不需要具体的实现。将Shape写作抽象方法代码如下:

public abstract class Shape {
    public abstract void draw();
}

但是如果我们将抽象方法进行实现,那么就会出现如下的报错:

即抽象方法中不能有主体。
注意:抽象方法中可以有普通的成员变量和成员方法或者是构造方法。
           如果一个类中有抽象方法,这个类必须是抽象类。否则就会报错


抽象类和普通类的区别:抽象类有abstract修饰,并且可以存在抽象方法(也可以不存在)

2.抽象类不能被private,final,static修饰

  1. private表示类,方法或者变量是私有的,只能在定义的类中去访问,但是抽象类需要被其他的子类访问因此不可行。
  2. final修饰的类表示该类不能有子类,或者说不能被继承,这样抽象类相违背,因此不可行。
  3. 被static修饰表示不能被实例化,但是子类是抽象类的实例化,因此不可行。

3.抽象类不能实例化对象

由于抽象类去定义一类具体事物的抽象,只是提供了一些共有的属性,不包含像具体的类那样丰富的信息去实例化一个对象。就想是买一件衣服付款时,由于衣服可看作一个抽象类,不同的衣服的款式,尺寸,知名度都会影响它的价格,因此不能用一份这个抽象类去实例化一件具体衣服的价格。否则就会报如下错误:

但是抽象类的引用 可以引用其子类的对象。代码如下:
 

public abstract class Shape {
    public abstract void draw();
}

public class Cycle extends Shape{
    public void draw() {
        System.out.println("画一个圆形...");
    }
}

public class triangle extends Shape{
    public void draw () {
        System.out.println("画一个三角形");
    }
}

public class test {
    public static void main(String[] args) {
        Cycle cycle = new Cycle();
        triangle triangle = new triangle();
        Shape[] shapes = {cycle,triangle};

        for (Shape shape:shapes) {
            shape.draw();
        }
        // 向上转型
       Shape shape = new Cycle();
    }
}

4.继承抽象类的子类必须重新抽象方法,如果不想重新写,那么这个类必须是抽象类,如果下一个类继承了第二个抽象类,那么他必须重写两个类的抽象方法。例如:

public abstract class Shape {
    public abstract void draw();
}

abstract class A extends Shape {
    abstract void test();
}

class C extends A {
    void test() {
        System.out.println("这是重写的第二个抽象类的抽象方法");
    }

    public void draw() {
        System.out.println("这是重写的第一个抽象类的抽象方法");
    }
}

5.抽象类可不包含抽象方法,但是抽象方法必须在抽象类中

6.抽象类可以存在构造方法,用于子类实例化时初始化父类的成员

public abstract class Shape {
    public int a;
    // 抽象类构造方法 -- 构造方法必须与类目相同,并没有返回类型
    public Shape(int a) {
        this.a = a;
    }

    public abstract void draw();

}

public class Cycle extends Shape{
    public Cycle(int a) {
        // super调用父类的构造方法
        super(a);
    }
    public void draw() {
        System.out.println("画一个圆形...");
    }
}

public class test {
    public static void main(String[] args) {
        // 发生向上转型,初始化父类成员a
        Shape shape = new Cycle(1);
        System.out.println(shape.a);

    }
}

7.抽象类可以发生动态绑定

public abstract class Shape {
    public abstract void draw();
}

public class Cycle extends Shape{
    public void draw() {
        System.out.println("画一个圆形...");
    }
}

public class Triangle extends Shape{
    public void draw () {
        System.out.println("画一个三角形");
    }
}

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

    public static void main(String[] args) {

        Triangle triangle = new Triangle();
        Cycle cycle = new Cycle();
        

        triangle.draw();
        cycle.draw();
//-------------------------------------
        new Triangle().draw();
        new Cycle().draw();
//-------------------------------------
        drawMap(triangle);
        drawMap(cycle);

    }
}
  1. 当您调用triangle.draw()cycle.draw()时,虽然trianglecycle都是Shape类型的引用,但是由于它们分别指向Triangle和Cycle类的实例,调用相应类的draw方法。发生动态绑定。

  2. 使用匿名对象调用方法,例如new Triangle().draw();和new Cycle().draw();发生动态绑定。在运行时,JVM会根据匿名对象实际类型调用相应的方法。

  3. 方法drawMap(Shape shape)接受一个Shape类型的参数,但实际传入的是triangle和cycle类的实例。在drawMap方法内部调用shape.draw();时,动态绑定确保调用的是传入对象实际类型的draw方法。

抽象类的意义

我们可能有这样的疑问,普通类可以被继承,普通的成员方法可以被重写,那么为什么使用抽象类和抽象方法呢?

抽象类中的抽象方法如果没有被重写,编译器会报错。 是的,使用抽象类和抽象方法的好处就是多了一层编译器的校验,能帮助我们检查抽象方法是否被重写。

抽象类不能被实例化。 很多引用场景,比如上面的画图形场景,其实际工作不应该由父类完成,而应由子类完成,此时误用父类编译器会报错。

接口

接口的概念

接口在Java中是一个抽象类型,是抽象方法的集合,一个类通过继承接口的方式,从而继承接口的抽象方法。例如我们可以将图形类写作图形接口。

接口可以看作多个类的公共规范,是一种引用类型

接口的语法

接口的定义与类的定义类似,只需要把class换成interface

接口能被public,abstract修饰,abstract默认不显性显示,如果省略public 则使用默认的权限

public interface 接口名 {
    // ...
}
  • 接口的命名一般以I开头
  • 接口的命名一般使用形容词性单词,这样就可以显示出接口的标准

接口的特性

1.接口不能被实例化

接口不能被实例化,但是接口可以引用实现该接口的类的对象(向上转型)

public class Test {
    public static void main(String[] args) {
        Animal animal = new Cat();
        animal.eat();
    }
}

public interface Animal {
    void eat();
}

public class Cat implements Animal{
    public void eat() {
        System.out.println(this.name+"正在吃🐟");
    }
}

打印的结果是小猫正在吃🐟。 如果写为Animal animal = new Animal();那么就会:

2.接口的成员变量

接口可以初始化变量,且接口的成员变量都是public static final修饰的,即使不写修饰符,成员变量默认被public static final修饰,这就意味着接口中的成员变量是静态(被static修饰)的,并且在声明的时候必须是初始化,因为它们被视为常量(被final修饰),这是可以将接口中的成员变量看作静态常量,只能读取,但是不能够被修改。

public interface Animal {
    int age = 10;
    public static final String name = "小动物";
}

public class Cat implements Animal{
    String name = "小猫";
    int age = 2;
}


public class Dog implements Animal{
    String name = "小狗";
    int age = 3;
}

public class Test {
    public static void main(String[] args) {
        Animal animal = new Cat();    // 向上转型
        animal.age = "5";    // 错误的,常量不可以被修改
    }    
}

         看到上述代码,发现name和agepublic static final 修饰,但是为什么在Cat和Dog类进行了“修改”,这是因为在Cat和Dog这两个类中,name和age不是接口Animal中常量的副本而是Cat和Dog类自己的实例,这些实例是可以被修改的
        但是在main函数中animal.age这个就是对于Animal接口中的age进行修改了,因此这句话是错误的。 

3.接口的方法

3.1接口中的抽象方法

接口定义的方法默认被public abstract修饰,属于抽象方法,所以它们没有方法体。抽象方法用来规定接口的类中必须实现的方法,任何实现这个接口的类必须实现其中的抽象方法

public interface Animal {
    int age = 10;
    public static final String name = "小动物";

    void sleep();
    public abstract void eat();

}

public class Cat implements Animal{
    String name = "小猫";
    int age = 2;
    public void eat() {
        System.out.println(this.name+"正在吃🐟");
    }

    public void sleep() {
        System.out.println(this.name+"正在睡觉");
    }
}

public class Dog implements Animal{
    String name = "小狗";
    int age = 3;

    public void eat() {
        System.out.println(this.name+"正在吃骨头");
    }

    public void sleep() {
        System.out.println(this.name+"正在睡觉");
    }
}

public class Test {
    public static void main(String[] args) {
        Cat cat = new Cat();
        cat.eat();
        cat.sleep();
    }
}

 结果:

如何不实现就会报错:

3.2接口中的默认方法

如果是要接口中的方法有具体的实现,可以使用默认方法,默认方法用default关键词定义,如果一个类实现了包含默认方法的接口,如果在这个类中没有覆盖这个方法,那么就会继承这个默认的实现。如果进行了覆盖,那么使用覆盖的方法。
例子,Cat类进行了覆盖以及不覆盖测试:

public interface Animal {
    default void sound() {
        System.out.println("小动物在叫");
    }
}

public class Cat1 implements Animal{
    // 没有实现sound()
}

public class Cat2 implements Animal{
    // 实现sound()
    public void sound() {
        System.out.println(this.name+"正在咪咪叫");
    }
}

public class Test {
    public static void main(String[] args) {
        Cat1 cat1 = new Cat1();
        cat1.sound();
        
        Cat2 cat2 = new Cat2();
        cat2.sound();
    }
}

结果如下:

注意,这组代码是分开运行的,当其中一个实现的了sound()时,另外一个调用时被实现的sound(),而不是animal中的sound();

3.3接口中的静态方法
  • 静态方法可以直接被调用,不需要实例化
  • 接口中的静态方法不能被实现该接口的类重写
public interface Animal {
    static void run() {
        System.out.println("小动物正在跑");
    }
}

package demo9;

public class Cat implements Animal{
    public void run() {
        System.out.println("小猫正在跑");
    }

}

public class Test {
    public static void main(String[] args) {
        Animal.run();
        Cat cat = new Cat();
        cat.run();
    }
}

run()方法在Cat类中并不是重写接口的静态方法,而是Cat类的一个实例方法。在Java中,接口的静态方法不是抽象方法,因此不需要也不可能在实现类中被重写。

4.抽象类继承抽象接口

当抽象类继承了抽象接口,抽象类可以不重写抽象接口中的方法,但是如果一个非抽象类继承了该抽象方法,那么该抽象类需要重写上面所有的抽象方法。例如:

// 猫科
public interface feline {
    void eat();
    void sleep();
}


// 猫属
abstract class Felis implements feline{
    abstract void Fsleep();
    public void MyFeils() {
        System.out.println("猫属中的特有方法");
    }
}

// 猫
public class Cat extends Felis{
    public void sleep() {
        System.out.println("实现猫科的sleep()");
    }

    public void eat() {
        System.out.println("实现猫科的eat()");
    }

    void Fsleep() {
        System.out.println("实现猫属的Fsleep()");
    }
}

public static void main(String[] args) {
     Cat myCat = new Cat();
     myCat.eat();
     myCat.sleep();
     myCat.Fsleep();
     myCat.MyFeils();
}

 5.重写后的权限

抽象方法被实例化后一定要重写,由于接口的成员方法默认都有public修饰,所有重写后的方法必须时public修饰(重写后的访问权限大于等于父类)

6.接口中不能有静态代码块,构造代码块和构造方法

  • 静态代码块: 静态代码块是在类加载时执行的代码块,通常用于初始化静态变量。接口中不能有静态代码块,因为接口不涉及对象的实例化过程,所以不需要在加载时执行特定的代码。

  • 构造代码块: 构造代码块在类的每个实例创建时执行,用于初始化实例变量。接口中不能有构造代码块,因为接口不能被实例化。

  • 构造方法: 构造方法用于创建类的新实例。接口中不能有构造方法,因为接口只定义了可以由实现它的类来实现的方法,本身并不实现这些方法,也不需要创建实例。

注意:接口文件的文件名必须与接口名相同,且接口文件编译后也是.class为后缀的字节码文件

接口的使用

接口不能直接实例化对象,接口的使用需要一个“实现类”来实现接口中的方法,用到关键字implement

接口与类之间时实现关系,父类和子类时继承关系。继承表达的时is A的关系,例如猫是动物,接口表达的时具有xxx含义,例如猫会爬树。下面用键盘和鼠标的例子来演示:

public interface USB {
    void openDevice();
    void closeDevice();
}

public class Mouse implements USB{
    public void openDevice() {
        System.out.println("打开鼠标");
    }

    public void closeDevice() {
        System.out.println("关闭鼠标");
    }

    public void click() {
        System.out.println("点击鼠标");
    }
}

public class KeyBoard implements USB{
    public void openDevice() {
        System.out.println("打开键盘");
    }

    public void closeDevice() {
        System.out.println("关闭键盘");
    }

    public void input() {
        System.out.println("打字");
    }
}

public class Computer {
    public void open() {
        System.out.println("开机");
    }

    public void close() {
        System.out.println("关机");
    }

    public void useUSBDevice(USB usb) {
        usb.openDevice();
        if(usb instanceof Mouse) {
            Mouse mouse = (Mouse) usb;
            mouse.click();
        }else {
            KeyBoard keyBoard = (KeyBoard) usb;
            keyBoard.input();
        }
        usb.closeDevice();
    }

}

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

        computer.useUSBDevice(new KeyBoard());
        computer.useUSBDevice(new Mouse());

        computer.close();
    }
}

多个接口的实现

在Java中,类和类之间时单继承的,一个类只有一个父类,即Java中不支持多继承。看一个例子:对于一个动物来说,不仅仅只有走路,有的还会游泳,甚至一些还会飞,我们应该如何表示,这时就要引出Java中的一个类实现多个接口,这个类要实现所有接口的抽象方法。

一个类可以继承一个父类并实现多个接口,当继承和接口都存在时,先使用extend进行继承,然后使用implements实现多个接口

public interface IWalk {
    void walk();
}

public interface ISwimming {
    void swimming();
}

public class Animal {
    public String name;
    public int age;
}

public class Dog extends Animal implements IWalk,ISwimming{
    public Dog(String name,int age) {
        this.age = age;
        this.name = name;
    }

    public void walk() {
        System.out.println(this.name+"正在跑");
    }

    public void swimming() {
        System.out.println(this.name+"正在刨水");
    }
}


public class Test {
    public static void main(String[] args) {
        Dog dog = new Dog("旺财",'4');
        dog.swimming();
        dog.walk();
    }
}

接口与多态

看下面一组代码:
 

public interface IFly {
    void fly();
}

public interface IWalk {
    void walk();
}

public interface ISwimming {
    void swimming();
}

public abstract class Animal {
    public String name;
    public int age;
    public Animal(String name,int age) {
        this.name = name;
        this.age = age;
    }

    public abstract void eat();
}

public class Duck extends Animal implements IWalk,ISwimming,IFly{
    public Duck(String name,int age) {
        super(name,age);
    }

    @Override
    public void eat() {
        System.out.println(this.name+"正在吃鸭粮");
    }


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

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

    @Override
    public void walk() {
        System.out.println(this.name+"正在走路");
    }
}

public class Dog extends Animal implements ISwimming,IWalk{
    // 子类继承父类先实现父类的构造方法
    public Dog(String name,int age) {
        super(name,age);
    }

    @Override
    public void eat() {
        System.out.println(this.name+"吃狗粮");
    }

    public void bark() {
        System.out.println(this.name+"正在汪汪叫...");
    }

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

    public void walk() {
        System.out.println(this.name+"正在跑");
    }
}


public class Fish extends Animal implements ISwimming{

    public Fish(String name, int age) {
        super(name, age);
    }

    @Override
    public void eat() {
        System.out.println(this.name+"正在吃🐟粮");
    }

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


public class Test {
    public static void func1(Animal animal) {
        animal.eat();
    }

    public static void walk(IWalk iWalk) {
        iWalk.walk();
    }

    public static void fly(IFly iFly) {
        iFly.fly();
    }

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

    public static void main(String[] args) {
        walk(new Duck("小鸭子",3));
        walk(new Dog("旺财",4));
    }

    // animal调用对象不同,发生的行为不一样,发生动态绑定,发生了多态
    public static void main1(String[] args) {
        func1(new Duck("小鸭子",3));
        func1(new Dog("旺财",4));
        func1(new Fish("金鱼",2));
    }
}

上面的代码中不同的子类继承了父类并实现了接口,在animal进行调用时,调用的对象不同,发生的行为不同,发生了动态绑定,发生了多态;在main方法中,我们创建了Duck,Dog的实例,并将它们作为参数传递给对应的方法。由于这些类实现了相应的接口,所以它们可以被传递给这些方法,并调用它们实现的接口方法。
        当有了接口之后,类的使用者就不必关注具体类型,而是关注某个类是否具备某种能力。

接口间的扩展

一个接口继承(扩展)另一个接口,子接口不实现父接口的抽象方法,而当一个非抽象类实现了子接口,那么这里类要重写父接口的所有抽象方法接口之间的继承相当于合并接口。例如:

interface IA {
    void testA();
}

interface IB{
    void testB();
}

interface IC extends IA,IB {
    void testC();
}

class D implements IC {

    @Override
    public void testA() {
        
    }

    @Override
    public void testB() {

    }

    @Override
    public void testC() {

    }
}

接口之间可以实现多继承,例如两栖动物可以行走可以游泳:


public interface IRunning {
    void run();
}


public interface ISwimming {
    void swim();
}

public interface IAmphibious extends ISwimming, IRunning {
    void func();
}

抽象类和接口的区别

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

  • 抽象类可以有方法体,但是接口中不行,除非用default和static进行修饰。
  • 一个类只能继承一个抽象类,但是一个类可以有多个接口
  • 抽象类中成员变量可以是各种类型,但是接口中public static final 类型
  • 抽象类中可以有静态代码块和静态方法,接口中不能有静态代码块和静态方法(Java8开始有静态方法)。
区别抽象类接口
1结构组成普通类,抽象方法抽象方法,全局常量
2权限各种权限public
3子类使用extends关键字继承抽象类implements关键字实现接口
4关系一个抽象类可以实现多个接口接口不能继承抽象类,但是接口可以使用extends关键字继承多个父接口
5子类限制一个子类只能继承一个抽象类一个子类可以实现多个接口

 实现接口和继承的区别:

  • 接口中的抽象方法必须重写,而继承中的子类可以不重写父类中的方法
  • 一个类只能继承一个父类,一个方法可以实现多个接口
  • 接口中只有抽象方法和被public static final修饰的成员变量,继承中可以定义普通的属性和方法
  • 接口--implement,继承--extend

Object类:

在Java中,object类时所有类的父类。每个Java类都是直接或间接的继承object类,如果没有明确指定父类,则默认继承自object类。

toString():

返回对象的字符串表示形式,每个对象都有一个toString(),方法,默认情况下,toString()返回的字符串包含类的名称+@+对象的哈希码无符号的十六进制。例如:

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

public class Test {
    public static void main(String[] args) {
        Person person = new Person("小明",16);
        System.out.println(person);
    }
}

 打印:

但是如果我们重写了toString(),就会打印出正确的值。

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

equals():

public static void main(String[] args) {
    Dog obj1 = new Dog("旺财", 3);
    Dog obj2 = new Dog("旺财", 3);

    System.out.println(obj1 == obj2); // false,因为比较的是引用
    System.out.println(obj1.equals(obj2)); // false
}

明明两个对象相同,但是为什么时false呢,对于第一个比较,==是比较的是两个对象的引用,而不是比较的对象的内容,即使它们的内容相同,但是它们在内存中的地址不同,因此obj1==obj2结果是false,对于第二个比较,还是比较两个对象的引用是否相同。如果我们需要比较内容相同,就必须重写equals方法。

public boolean equals(Object obj) {
        if (this == obj) return true;
        if(obj == null) {
            return false;
        }
        if(!(obj instanceof Dog)) {
            return false;
        }
        // 传入的对象转化为dog类型
        Dog tmp = (Dog) obj;
        // 字符串比较equals
        return tmp.name.equals(this.name) && tmp.age == this.age;
    }

由于Dog是Object的子类,子类重写父类的方法,子类调用时调用自己的方法,即调用自己重写的方法

hashCode():

    public static void main(String[] args) {
        Dog dog1 = new Dog("旺财",3);
        Dog dog2 = new Dog("旺财",3);

        System.out.println(dog1.hashCode());
        System.out.println(dog2.hashCode());

    }

hashCode()可以提高数据的检索效率,并保证数据结构的重要性,由于并不是所有的类都需要被当作哈希表的间,因此在Java中没有实现。需要自己去实现hashCode()
我们这里需要输出的相同,因为name和age相同

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值