Java学习(9)(3种向上转型的方式、重写、向下转型、多态的优缺点、抽象类【基础规则、抽象类的作用】接口【基础规则、 接口的使用】)

接上次博客:JAVA学习(8)继承 ( 继承的注意事项、Java的执行顺序、继承方式、限定词protcted、final关键词、多态、动态绑定和静态绑定 )_di-Dora的博客-CSDN博客

目录

向上转型(3种方式)

重写

向下转型

多态的优缺点

抽象类

基础规则:

抽象类的作用

接口

基础规则:

 接口的使用


上次我们提到过,要理解多态首先要做到:

1、向上转型(3种方式);

2、重写方法;

3、通过父类引用,调用这个父类和其子类对象重写的方法。

(如果是调用了子类的重写的方法,那么这个过程我们就叫做“运行时绑定”或者“动态绑定”——详情可见上次博客)

先来说说“向上转型”:

向上转型(3种方式)

向上转型实际上就是创建一个子类对象,将其赋值给一个父类类型的引用变量。

这是由于Java的继承机制,子类对象具有父类对象的所有特征,所以可以通过将子类对象赋值给父类类型的引用变量来实现向上转型(从小范围转向大范围)。这种转型的好处是可以实现代码的复用和多态性。

语法格式:父类类型 对象名 =  new  子类类型()

为了介绍向上转型,我们要先写几个生动形象的类来做基础以帮助理解:

class Animal {
    public String name;
    public int age;

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

    Animal eat() {
        System.out.println(name+" 正在吃东西,好吃!");
        return null;

    }

    public String toString() {
        return "Animal{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}
class Dog extends Animal {
    public Dog(String name,int age) {
        super(name,age);

    }

    public void wangWang() {
        System.out.println(name+" 正在汪汪叫,可能有外人来了");
    }

    public Dog eat() {
        System.out.println(name+" 正在啃香喷喷的骨头!");
        return null;
    }
}
class Bird extends Animal {
    public Bird(String name, int age) {
        super(name, age);
    }

    public void fly() {
        System.out.println(name+" 正在自由翱翔");
    }
}

public class Test_animal {

    public static void main(String[] args) {
        Animal animal2 = new Bird("小鸟",10);
        Animal animal1 = new Dog("小狗",10);
    }

}

(1)、直接赋值

Dog dog1=new Dog;
Animal animal =dog1;

Animal animal=dog1;

(2)、方法传参

Dog dog = new Dog("小狗",10);
Bird bird = new Bird("小鸟",10);

func1(dog);
func1(bird);

public static void func1(Animal animal) {

}

(3)、返回值

//func2的返回值可以返回Animal和它的子类
Animal animal = func2();

public static Animal func2() {
        /*Bird bird = new Bird("小鸟",10);
        return bird;*/

        return new Bird("小鸟",10);
        return new Dog("小狗",2);
}

//当然,如下方法也可以,但是不太建议,因为只能返回Bird或它的子类
Animal animal11 = func3();

public static Bird func3() {
        return new  Bird("小鸟",10);
}

优点:

1、多态性:向上转型可以实现多态性,即一个变量可以引用不同类型的对象,从而使代码更加灵活和可扩展。

2、代码复用:向上转型可以实现代码的复用,即将一段代码应用到不同类型的对象上,从而减少代码冗余和提高代码的可维护性。

缺点:

1、限制功能:向上转型会限制使用子类特有的方法和属性,因为父类类型的引用变量只能访问父类的方法和属性。

如上,你调用的是Dog的 eat() 的方法,这个eat被重写了;

但是作为Animal ,不可以调用子类Dog独有的方法。

2、代码可读性:向上转型可能会降低代码的可读性,因为它将子类对象的类型信息隐藏在父类类型的引用变量中,导致代码阅读起来可能会更加困难。

 因此,在实际编程中,需要根据具体的需求和情况来考虑是否使用向上转型。如果需要实现多态性和代码复用,可以使用向上转型;如果需要使用子类特有的方法和属性,或者代码可读性更重要,可以不使用向上转型。

我们上面反复提到了“方法重写”这个概念,看来我们有必要去深入了解一下它:

重写

重写(override):也称为覆盖。重写是子类对父类非静态、非private修饰,非final修饰,非构造方法等的实现过程进行重新编写,返回值和形参都不能改变。即外壳不变,核心重写!

重写的好处在于子类可以根据需要,定义特定于自己的行为。也就是说子类能够根据需要重新实现父类的方法。子类中的该方法会覆盖父类中同名的方法,当子类对象调用该方法时,会优先调用子类中的方法实现,而不是父类中的方法实现。

重写方法的作用在于可以根据需要重新定义方法的实现,从而实现更加特定的功能。

例如,在父类中定义了一个通用的方法,而在子类中可以根据具体需要重写该方法,以实现更加特定的功能。

需要注意的是,在重写方法时,要确保子类的方法实现与父类的方法实现具有相同的语义。即使返回值类型和访问权限相同,但如果子类的方法实现与父类的方法实现不同,就可能导致意想不到的结果。因此,在重写方法时,必须仔细考虑方法的实现,确保其与父类的方法实现具有一致的语义。

重写方法的实现必须遵循以下规则:
子类在重写父类的方法时,一般必须与父类方法原型一致:返回值类型、方法名、(参数列表) 要完全一致;被重写的方法返回值类型其实也可以不同,但是必须是具有父子关系的(构成父子关系的时候,可以叫做“协变类型”);

访问权限不能比父类中被重写的方法的访问权限更低。例如:如果父类方法被public修饰,则子类中重写该方法就不能声明为protected。
注意:父类被final(密封方法)、static、private修饰的方法、构造方法都不能被重写。(静态方法不能被重写,但可以被子类中的同名静态方法所隐藏。)

静态方法和构造方法都不可以被重写是因为它们属于类级别的方法,而不是实例级别的方法。

静态方法是与类本身相关联的方法,它们不依赖于类的任何实例。因此,它们不能被子类重写或覆盖。子类可以定义与父类中静态方法同名的方法,但这只是在子类中创建了一个新的方法,而不是重写或覆盖了父类的静态方法。

构造方法是用于创建类的实例的特殊方法。它们的名称必须与类名完全相同,并且没有返回类型。因为构造方法用于创建对象实例,而每个类只能有一个默认的构造方法,它们不能被子类重写或覆盖。子类可以定义与父类中构造方法同名的方法,但这只是在子类中创建了一个新的方法,而不是重写或覆盖了父类的构造方法。

总之,静态方法和构造方法都是与类本身相关联的方法,它们的行为不依赖于类的任何实例。因此,它们不能被子类重写或覆盖。

重写的方法,可以使用 @Override注解来显示地指定。子类在实现抽象方法时,使用override关键字来覆盖父类的方法实现,从而使得子类对象能够调用该方法。有了这个注解能帮我们进行一些合法性校验。例如不小心将方法名拼写错了,那么编译器就会发现父类中并没有“aet”这个方法,从而编译报错,提示无法构成重写。

    @Override
    public Dog eat() {
        System.out.println(name+" 正在啃香喷喷的骨头!");
        return null;
    }

子类方法不能抛出比父类方法更宽泛的异常。

重写和重载的区别:

1、定义位置不同:重写发生在子类中,重载发生在同一个类中。

2、方法名、参数列表、返回类型和访问权限不同:在重载方法中,方法名相同,但参数列表必须不同,返回类型和访问权限可以不同;在重写方法中,方法名、参数列表必须与父类中的方法相同,返回类型和访问权限不能比父类的方法差,可以相同或更好。

3、实现方式不同:在重载中,每个重载方法实现的功能可以不同,但是方法名必须相同,方法的参数列表必须不同,返回类型和访问权限也可以不同。在重写中,子类中的方法必须与父类中的方法具有相同的方法名、参数列表、返回类型和访问权限,并且方法实现的语义也必须一致。

4、目的不同:重载是为了提供多个具有相同名称但参数列表不同的方法,以便在调用时选择合适的方法。而重写是为了在子类中修改或增强从父类继承来的方法,使得子类可以根据自己的需要实现特定的功能。

向下转型

将一个子类对象经过向上转型之后当成父类方法使用,再也无法调用子类的方法,但是如果这个时候我们需要调用子类独有的方法该怎么办呢?将父类应用引用在还原为子类对象即可,如下:

Bird bird = (Bird) animal2;
bird.fly();

但是!向下转型,不安全!!!我们如果这样写:

Bird bird = (Bird) animal2;
bird.fly();

Animal animal1 = new Dog("小狗",10);
    Bird bird2 = (Bird)animal1;
    bird2.fly();

类型转换异常: 

 所以我们这个时候可以引入"instanceof"关键字(返回一个布尔值)进行类型检查:

    public static void main(String[] args) {
        Animal animal2 = new Bird("小鸟", 10);
        //animal2.fly();
        //向下转型  但是 不安全
        if (animal2 instanceof Bird) {
            Bird bird = (Bird) animal2;
            bird.fly();
        }

        Animal animal1 = new Dog("小狗", 10);
        if (animal1 instanceof Bird) {
            Bird bird2 = (Bird) animal1;
            bird2.fly();
        } else {
            System.out.println("不能飞!");
        }
    }

让我们在回到我们最初讨论的问题核心——多态

多态的优缺点

多态是面向对象编程的重要概念,它提供了一种更加灵活和可扩展的代码实现方式,具有以下好处:

1、简化代码:多态可以使代码更加简洁、易于维护和扩展。在多态的实现中,不需要使用大量的if-else语句来判断对象类型,而是直接调用对象的方法,由Java虚拟机动态选择合适的方法。

2、降低耦合:多态可以降低代码的耦合度,因为它可以将方法的具体实现与调用该方法的对象解耦。这意味着可以在不影响程序其他部分的情况下修改方法的实现。

3、提高代码复用性:多态可以提高代码的复用性。一个方法可以被多个对象调用,因为不同的对象可以提供不同的实现。这样,不需要为每个对象都写一份相同的代码。

4、提高程序的可扩展性:多态可以提高程序的可扩展性。当需要添加新的类时,只需要让新类继承一个现有的类或实现一个接口,并实现相应的方法即可。

5、降低圈复杂度:多态可以降低圈复杂度。使用多态可以避免在程序中出现大量的switch或if-else语句,从而使程序更加简单、易于理解和维护。

圈复杂度(Cyclomatic Complexity)是一种软件度量指标,用于衡量代码的复杂程度,如果一段代码平铺直叙,那么就简单易懂,如果有很多条件分支或者循环语句,就复杂。圈复杂度的概念最初由Thomas J. McCabe在1976年提出,其基本思想是计算代码中的独立路径数,从而评估代码的复杂程度。

圈复杂度通常通过计算程序中不同路径的数量来进行测量(计算一段代码中条件语句和循环语句出现的个数)。每个独立的路径都代表着一组程序语句,它们的执行顺序不同于其他路径。因此,圈复杂度越高,代码的复杂度就越高。如果一段代码的圈复杂度太高,那么就需要考虑重构。

通常,圈复杂度与代码的可读性和可维护性成反比。高圈复杂度的代码通常更难理解、调试和维护。在软件开发中,圈复杂度通常被用作代码质量的评估标准之一,以帮助开发人员编写更清晰、更易于维护的代码。

一些工具可以自动计算圈复杂度,例如SonarQube、Checkstyle、PMD等,它们可以帮助开发人员评估代码质量,提供有用的反馈和建议。

总之,多态是一种重要的面向对象编程概念,它可以提高程序的可读性、可维护性和可扩展性,减少代码量并降低圈复杂度。

我们举个例子来讲解一下:

class Shape {

    public void draw() {
        System.out.println("画图形!");
    }
}
class Rect extends Shape {
    @Override
    public void draw() {
        System.out.println("矩形");
    }
}
class Cycle extends Shape {
    @Override
    public void draw() {
        System.out.println("圆");
    }
}

class Flower 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) {
        Cycle cycle = new Cycle();
        Rect rect = new Rect();
        Flower flower = new Flower();

        drawMap(cycle);
        drawMap(rect);
        drawMap(flower);
    }
}

 上面这个代码就体现了多态这个思想:一个引用调用一个方法,引用的对象不一样,调用同一个方法,该方法被这些对象的类重写过,表现的行为不一样。

再比如:

如果没有多态,我们想要实现一个循环打印不同图形的代码就会需要很多的条件语句:

    public static void drawMaps1() {
        Cycle cycle = new Cycle();
        Rect rect = new Rect();
        Flower flower = new Flower();

        String[] strings = {"cycle", "rect", "cycle", "rect", "flower"};
        for (String s : strings) {
            if(s.equals("cycle")) {
                cycle.draw();
            }else if(s.equals("rect")) {
                rect.draw();
            }else {
                flower.draw();
            }
        }
    }

那用多态怎么做呢?

    public static void drawMaps() {
        Cycle cycle = new Cycle();
        Rect rect = new Rect();
        Flower flower = new Flower();
        Triangle triangle = new Triangle();

        Shape[] shapes = {cycle,rect,cycle,rect,flower,triangle};//发生了向上转型
        for(Shape shape : shapes) {
            shape.draw();
        }

    }

但是,看似完美的多态也存在缺点:

1、属性没有多态性:当父类和子类都有同名的属性的时候,通过父类引用,只能引用父类自己的成员属性。

2、构造方法没有多态性。这通常会造成意想不到的错误:

class B {
    public B() {
        // do nothing
        func(); // 会调用子类的func
    }
    public void func() {
        System.out.println("B.func()");
    }
}


class D extends B {

    private int num = 1;

    D() {
        super();
    }
    @Override
    public void func() {
        // num = 0  -------D这个对象还没有完全构造完成,所以它还没有初始化被赋值为1
        System.out.println("D.func() " + num);
    }
}
public class Test3 {
    public static void main(String[] args) {
        D d = new D();
    }
}

构造D对象的同时,会调用B的构造方法;

B的构造方法中调用了func方法,此时会触发动态绑定,会调用到D中的func;

此时D对象自身还没有构造,此时num处在未初始化的状态,值为0。按理来说,num的值应该是1。

综上,在构造函数内,尽量避免使用实例方法,除了final和private方法(它们都不能被重写)。
总之就是要用尽量简单的方式使对象进入可工作状态",尽量不要在构造器中调用方法(如果这个方法被子类重写,就会触发动态绑定,但是此时子类对象还没构造完成,可能会出现一些隐藏的但是又极难发现的问题)。

3、运行时效率低:多态需要在运行时才能确定调用哪个方法,这增加了程序的运行时开销,可能导致程序运行缓慢。

4、隐藏错误:多态有时可能会隐藏某些错误。例如,如果一个父类定义了一个方法,但该方法被其子类重写并实现了不同的功能,那么如果程序员在调用该方法时忘记了子类的具体实现,就可能会导致程序运行出错。

5、可控性:多态有时可能会降低程序的可控性。当程序出现错误时,由于方法的具体实现是在运行时动态选择的,因此可能难以确定错误的来源。

6、需要合适的继承和抽象设计:实现多态需要合适的继承和抽象设计。如果类的层次结构设计不合理,可能会导致代码过于复杂、难以理解和维护。

综上所述,多态虽然提高了代码的灵活性和可扩展性,但也存在一些缺点,我们还是需要在实践中根据具体情况权衡利弊。

抽象类

抽象类 在面向对象的概念中,所有的对象都是通过来描绘的,但是反过来,并不是所有的类都是用来描绘对象的,如果 一个类中没有包含足够的信息来描绘一个具体的对象,这样的类就是抽象类比如我们之前举的例子——Animal类,它就可以设计成 抽象类Animal。你能想象一个Animal是什么样子的吗?长着狗头狮身?有着猫咪轻盈的步伐?还加上一团兔子尾巴?Oh ! No ! 简直无法想象。

这么说来,

抽象类其实是一种特殊的类,它不能被实例化,只能被继承,并且可以包含抽象方法和非抽象方法。抽象类用于定义一些通用的方法和属性,而具体的实现留给其子类去实现,

抽象类的概念是面向对象编程中的一种基本概念,它提供了一种模板化的设计方法,可以帮助程序员更好地组织和管理代码。抽象类通过抽象方法的声明来定义接口,子类需要实现这些抽象方法才能使用该类。抽象类通常用于将具有相似行为和属性的类归为一类,这有助于提高代码的可重用性和可维护性。

基础规则:

1.抽象类是被 abstract 关键字修饰的。它不能被实例化,只能被用作其他类的基类。

2.抽象类中可以包含抽象方法和非抽象方法,抽象方法用abstract关键字声明,并且没有方法体,子类必须实现这些抽象方法才能使用抽象类,否则子类也是抽象类,这个时候就必须要使用 abstract修饰子类。

3.除了定义抽象方法,抽象类还可以定义普通方法,这些方法可以有具体的实现。抽象类也可以包含变量、构造函数等其他类成员。即抽象类当中可以有和普通类一样的成员变量和一样的成员员方法。注意,抽象类中不一定包含抽象方法,但是有抽象方法的类一定是抽象类,需要用abstract修饰。                                                                                                                                                   

4.final 和 abstract 是不可以同时存在的,因为它们代表的含义是相反的,他们一个代表不可以被继承,一个代表可以被继承。抽象方法也不能够被private修饰,在这里,private 和 abstract 也是互相矛盾的,他们一个代表不能够被重写,一个代表可以被重写。

5.抽象方法不能被static修饰。抽象方法不能被static修饰的原因是,抽象方法是为了被具体的子类实现而存在的,而static方法是属于类的方法,不依赖于具体的实例对象。如果把抽象方法声明为static方法,就会失去抽象方法的本意,也就是无法在子类中实现抽象方法的具体实现,从而失去了抽象类和抽象方法的作用。还有,子类在实现抽象方法时,需要使用override关键字来覆盖父类的方法实现,从而使得子类对象能够调用该方法。如果抽象方法可以被static修饰,那么子类就无法通过override关键字来覆盖该方法的实现,因为static方法是属于类的方法,而不是属于实例对象的方法。

6.抽象类中可以有构造方法,供子类创建对象时,初始化父类的成员变量。


那么,问题来了,抽象类既然不能实例化对象 那么我们还要它干什么?  -------就是为了被继承。
当一个普通的类 继承了这个抽象类之后,这个普通类一定要要重写这个抽象类当中所有的抽象方法,在这里,如果你没有在子类里面重写抽象方法,编译器甚至会给你报错。
 

综上,抽象类是一种特殊的类,它不能被实例化,而是被设计成用作其他类的基类,通过继承和实现抽象方法来使用。在抽象类的里面定义了一些抽象方法,这些方法没有实现体,也就是没有方法体的代码,它们只有声明,而没有具体的实现。这些抽象方法留给具体的子类去实现,因为抽象类本身没有足够的信息来实现它们。抽象类只是规定了子类需要实现哪些方法,以及方法的参数和返回值。

这么说来,抽象类的主要作用是提供一种基础设计,使得具有相似行为和属性的类可以归为一类,从而提高代码的可重用性和可维护性。此外,抽象类还可以提供一些默认实现或者共用的方法(定义在其中的普通方法),方便子类继承和调用。

抽象类的优点包括:

  • 提高代码的可重用性和可维护性;
  • 强制子类实现抽象方法,确保了代码的正确性;
  • 提供了一种面向对象编程的抽象层次,方便代码的组织和管理。

需要注意的是,抽象类也有一些限制,如我们刚刚提到的不能被实例化、不能被final修饰等。在实际应用中,我们还是需要根据具体情况选择是否使用抽象类来实现代码设计。

总之,抽象类是一种重要的面向对象编程概念,可以帮助程序员更好地组织和管理代码,并且提高代码的可重用性和可维护性。

让我们来看个抽象类的例子:

abstract class Shape {

    /*public int a = 10;*/

    public Shape() {
        //用来让子类调用 帮助这个抽象类初始化自己的成员
    }

    public abstract void draw();

    /*public void test() {

    }*/
}

//这里class A 就是一个继承了Shape父类的抽象子类
//但是要记住,出来混总是要还的。它的子类要替父还债。
abstract class A extends Shape {
    public abstract void test();
}

class B extends A {
    @Override
    public void draw() {

    }

    @Override
    public void test() {

    }
}
class Rect extends Shape {
    @Override
    public void draw() {
        System.out.println("矩形");
    }
}
class Flower 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) {
        //Shape shape = new Shape();
        Shape shape = new Rect();//向上转型
        drawMap(new Rect());
        drawMap(new Flower());
        //new Rect() ---> 匿名对象 没有名字的对象
        //匿名对象的缺点是什么 ?  缺点就是,每次使用都得重新实例化
        Rect rect = new Rect();
        rect.draw();
        rect.draw();

        new Rect().draw();
        new Rect().draw();

    }
}

在这里,我们重点解释一下:

在抽象类里面是可以包括构造方法的,可是抽象类它本身并不能够被实例化,那么这个抽象方法其实是用来让子类继承并调用,以帮助这个抽象类初始化成员的。

当一个抽象类A不想被一个普通类B继承,此时可以把B这个类变成抽象类,然后,当一个普通类C去继承这个抽象类B的时候,C需要重写B和A里面的所有的抽象类,单说来就是抽象类C需要“替父还债”、“父债子偿”。

抽象类的作用

使用抽象类相当于多了一重编译器的校验,这是因为抽象类会强制子类必须实现抽象方法,否则编译器会报错。这种机制可以保证子类在实现抽象方法时遵循了父类的规范和约束,从而避免了潜在的错误和不一致性。

抽象类存在的意义是为了提供一种接口和规范,使得不同的具体子类能够遵循相同的规则和行为,从而提高代码的可读性、可维护性和可扩展性。使用抽象类可以避免重复编写相似的代码,提高代码的复用性和模块化程度,同时也有利于代码的管理和维护。

所以,我们现在就可以回答这个问题了——普通的类也可以被继承,普通的方法也可以被重写,为什么还需要我们的抽象类呢?

普通的类虽然也可以被继承,普通的方法虽然也可以被重写,但是抽象类和抽象方法的作用是强制子类必须实现某些方法。使用普通的类和方法来实现类似的功能,可能需要在每个子类中都重复编写相似的代码,造成代码冗余和可维护性差的问题。而使用抽象类和抽象方法,可以在父类中定义通用的接口和行为规范,让子类去实现具体的方法,从而避免了代码的冗余和不一致性。

接口

在Java中,接口(interface)是一种抽象类型,它定义了一组抽象方法和常量,但没有实现方法体。接口中定义的方法必须由实现了该接口的类去实现,实现类需要提供方法的具体实现,以满足接口的规范。在Java中,接口是一种非常重要的概念,它广泛应用于各种场合,如Java集合框架中的Iterator接口、Comparable接口、Runnable接口等等。学习和掌握接口的使用方法,可以帮助我们更好地设计和实现高质量的Java程序。

接口的作用是提供一种约定,规定了实现类需要提供哪些方法,以便在程序中使用。它可以使程序的结构更加清晰,降低代码的耦合度。通过接口,我们可以将程序中的不同部分解耦,使得系统更加灵活和易于扩展。接口还可以用来实现多重继承,一个类可以实现多个接口(多态的体现),并提供相应的实现方法。

接口的语法如下:

[public] interface 接口名称 [extends 其他接口名称] {
    // 常量定义
    // 抽象方法定义
    // 默认方法定义
    // 静态方法定义
    // 私有方法定义
}

其中,常量定义和抽象方法定义是接口的必要组成部分,而默认方法、静态方法和私有方法是在Java 8 中新增的特性。

新建接口:

 基础规则:

1.接口当中的成员,成员变量默认都是 public static final 的,成员方法默认是 public abstact 的,所以一般情况下我们就不用写了。接口中的变量必须全部是常变量——即使用final和static关键字修饰的变量。变量的值在定义时就已经确定,不可更改。

2.接口中的方法必须全部是抽象方法,即没有实现的方法,只有方法名、参数列表和返回类型。接口当中不可有普通的方法。如果一定要有普通方法,那么就在方法名前加一个 default ,从Java 8 开始,允许在接口中定义 default 修饰的方法,它是可以有具体的实现的。这也就是说,接口中可以定义默认方法(默认实现),默认方法可以在不破坏实现类原有结构的情况下,为接口添加新的方法。

3.static修饰的静态方法,也是可以有具体的实现的。接口中定义的静态方法可以通过接口名直接调用,而不需要实例化接口。

4.接口中可以定义私有方法(但是这个私有方法不可以是抽象的),使用private关键字。私有方法只能在接口内部被调用,不允许在实现类中被调用。

5.接口是不可以被实例化的。

6.接口可以继承多个接口,使用extends关键字。

7.类可以实现一个或多个接口,使用implements关键字,一定要实现接口的方法。

8.实现接口的类必须实现接口中的所有抽象方法,否则需要将自己也声明为抽象类。

9.接口也可以发生向上转型和动态绑定。

10.当一个类实现了接口中的所有方法之后,当前类中的重写方法不能使用默认访问权限。因为在父类中那个方法默认就是一个 public ,所以重写方法的权限要大于等于父类中的方法,只能是 public 。

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

12.一个接口也会产生独立的字节码文件。创建接口的时候,接口名称一般以大写字母 I 开头,一般使用“形容词”词性的单词。阿里编码规范中约定,接口中的方法和属性不要加任何修饰符号,保持代码的简洁性。

13.接口的修饰符:可以加 public 和 abstract (其实编译器会默认接口是 abstract 的)。

总之,接口的主要作用是定义一组规范,让实现类按照规范进行实现,并且可以扩展和修改接口的规范,而不会影响实现类的结构。接口还可以用于实现多态和减少代码的耦合度。

好了,明白了书写接口的基本原则,让我们来看一个相关例子:

interface IShape {
    //int a = 10;
    void draw();
   /*
   //default修饰的方法,可以有具体的实现
   default public void test() {
        System.out.println("test()");
    }
    //被static修饰的静态方法,可以有具体的实现
    public static void test2() {
        System.out.println("static ");
    }*/
}
abstract class A implements IShape {

}


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

public class TestDemo {
    public static void drawMap(IShape iShape) {
        iShape.draw();
    }
    public static void main(String[] args) {
        //IShape iShape = new IShape();
        //向上转型
        IShape iShape1 = new Rect();
        IShape iShape2 = new Flower();
        drawMap(iShape1);
        drawMap(iShape2);
        /*drawMap(new Rect());
        drawMap(new Flower());*/
    }
}

 接口的使用

接口不可以直接被使用,必须有一个“实现类”来“实现”该接口,实现接口中的所有的抽象方法。

public class 类名称 implements 接口名称{
//...
}

子类可以继承父类、接口也可以继承多个接口,这里都是继承关系,所以使用extends关键字。

类可以实现一个或多个接口,使用implements关键字。

最后,下面是一个帮助理解的例子:

 interface IUSB {
    void openDevice();//打开服务
    void closeDevice();//关闭服务
}

 class KeyBoard implements IUSB{

    @Override
    public void openDevice() {
        System.out.println("打开键盘");
    }

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

    public void inPut(){
        System.out.println("键盘输入");
    }
}


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

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

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



 class Computer {
    public void powerOn() {
        System.out.println("打开笔记本电脑");
    }

    public void powerOff() {
        System.out.println("关闭笔记本电脑");
    }


    public void useDevice(IUSB usb) {
        usb.openDevice();
        if (usb instanceof Mouse) {
            //click是Mouse独有的方法,要调用需要先向下转型
            Mouse mouse = (Mouse) usb;
            mouse.click();
        } else if (usb instanceof KeyBoard) {
            KeyBoard keyBoard = (KeyBoard) usb;
            keyBoard.inPut();
        }
        usb.closeDevice();
    }


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

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

        computer.powerOff();

    }
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值