一、接口

1.1 概述

接口,是Java语言中一种引用类型,是方法的集合,如果说类的内部封装了成员变量、构造方法和成员方法,那么 接口的内部主要就是封装了方法,包含抽象方法(JDK 7及以前),默认方法和静态方法(JDK 8),私有方法 (JDK 9)。

接口的定义和类的定义差不多,但是使用interface关键字。他是不同于类的另一种引用类型。

引用数据类型:数组、类、接口

接口的使用步骤:

1.接口不能直接使用,必须有一个“实现类”来“实现”该接口 -> implements

格式:

  1. public class 实现类名称 implements 接口名称{
  2. //....
  3. }

2.接口的实现类必须覆盖重写(实现)接口中所有的抽象方法。

实现:去掉abstract关键字,加上方法体大括号。

3.创建实现类的对象,进行使用。

需要实现接口中所有的抽象方法,创建该类对象,就可以调用方法了,否则它必须是一个抽象类。

  1. public class Demo01Interface {
  2. public static void main(String[] args) {
  3. //错误写法!不能直接new接口对象使用
  4. //MyInterfaceAbstract inter=new MyInterfaceAbstract();
  5. //要使用接口的实现类的对象使用
  6. MyInterfaceAbstractImpl impl=new MyInterfaceAbstractImpl();
  7. impl.methodAbs();
  8. impl.methodAbs1();
  9. }
  10. }

1.2 定义格式

  1. public interface 接口名称{
  2. //抽象方法
  3. //默认方法
  4. //静态方法
  5. //私有方法
  6. }

含有抽象方法

抽象方法:使用abstract关键字修饰,可以省略,没有方法体,该方法供子类实现使用。

注意事项:
1.接口当中的抽象方法,修饰符必须是两个固定的关键字:public abstract
2.这两个关键字修饰符,可以选择性地省略。
3.方法的三要素可以随意定义。

  1. public interface InterFaceName{
  2. //这是抽象方法
  3. public abstract void method();
  4. //这些也是抽象方法
  5. abstract void methodAbs1();
  6. public void methodAbs2();
  7. void methodAbs3();
  8. }

含有默认方法和静态方法

默认方法:使用default修饰,不可省略,供子类调用或者子类重写。

接口当中的默认方法,可以解决接口升级的问题。

默认方法例子:
  1. 当接口中有一个抽象方法,然后有了两个实现类,他们覆盖重写了接口的抽象方法,那么现在想要在接口中再添加几个方法,并且能让已存在的实现类也能调用方法,如果是抽象方法的话,需要重新将实现类进行覆盖重写新的抽象方法,所以不可取,这里我们运用默认方法,定义默认方法后,接口的实现类对象可以直接调用`b.methodDefault();`,**默认方法被调用时,如果实现类对象中没有该方法,他会向上到接口中去调用该方法**
  • 接口的默认方法,可以通过接口实现类对象,直接调用
  • 接口的默认方法,也可以被接口实现类进行覆盖重写
public class Demo02Interface {
    public static void main(String[] args) {
        MyInterfaceDefaultA a=new MyInterfaceDefaultA();
        a.methodAbs();//调用抽象方法,实际运行的是右侧的实现类

        //调用默认方法,如果实现类中没有,会向上找接口的默认方法
        a.methodDefault();

        MyInterfaceDefaultB b=new MyInterfaceDefaultB();

        b.methodAbs();
        b.methodDefault();//实现类B覆盖重写了接口的默认方法
    }
}
输出结果:
实现了抽象方法,AAA
这是新添加的默认方法

实现了接口,BBB
实现类B覆盖重写了接口的默认方法

静态 :使用static修饰,供接口直接调用。

静态方法使用:

注意:不能通过接口实现类的对象来调用接口当中的静态方法

正确用法:通过接口名称,直接调用其中的静态方法

格式: 接口名称.静态方法名(参数);

public class Demo03Interface {
    public static void main(String[] args) {

        //创建实现类对象(静态方法不需要对象)
        MyInterfaceStaticImpl impl=new MyInterfaceStaticImpl();

        //错误写法
        //impl.methodStatic();

        //直接通过接口名称调用静态方法(不需要new 对象)
        MyInterfaceStatic.methodStatic();
    }
}

含有私有方法和私有静态方法

私有方法:使用 private 修饰,供接口中的默认方法或者静态方法调用。

例子引入:

public interface MyInterfacePrivateA {
    public default void methodDefault1(){
        System.out.println("默认方法1");
//        System.out.println("AAA");
//        System.out.println("BBB");
        methodCommon();
    }

    public default void methodDefault2(){
        System.out.println("默认方法2");
//        System.out.println("AAA");
//        System.out.println("BBB");
        methodCommon();
    }
    //发现默认方法1和默认方法2中有重复代码->输出AAA和BBB

    //如果多添加一个默认方法来调用重复代码则会影响实现类
    //所以创建一个私有方法来调用
    private void methodCommon(){
        System.out.println("AAA");
        System.out.println("BBB");
    }
}

1.普通私有方法:

解决多个默认方法之间重复代码问题

2.静态私有方法:

解决多个静态方法之间重复代码问题

//私有静态方法,只能在接口类中访问
private static void methodCommon(){
    System.out.println("AAA");
    System.out.println("BBB");
}

接口中定义常量

接口当中也可以定义“成员变量”,但是必须使用public static final三个关键字进行修饰
从效果上看,这骑士就是接口的【常量】
格式
public static final 数据类型 常量名称 = 数据值;

注意:
一旦使用final关键字进行修饰,说明不可改变。

接口的当中的常量可以省略 public static final,但是默认有这三个关键字

接口中常量的名称,使用完全大写的字母,并且用下划线分隔(推荐命名)

public interface MyInterfaceConst {

    //这就是一个常量,一旦赋值,不可修改
    public static final int NUM=10;
}

1.3 接口小结

1.4 接口使用过程的注意事项!

使用接口时,需注意:

1.接口是没有静态代码块或者构造方法的。

2.一个类的直接父类是唯一的,但是一个类可以同时实现多个接口

3.如果实现类所实现的多个接口当中,存在重复的抽象方法那么只需要覆盖重写一次即可

重复抽象方法需一次覆盖

public class MyInterfaceImpl implements MyInterfaceA,MyInterfaceB{


    @Override
    public void methodA() {

    }

    @Override
    public void methodB() {

    }


    //覆盖重写了AB两个接口重复的方法(只需一次)
    @Override
    public void methodAbs() {

    }
}

4.如果实现类没有覆盖重写所有接口当中的抽象方法,那么实现类就必须是一个抽象类

5.如果实现类所实现的多个接口当中,存在重复的默认方法,那么实现类一定要对冲突的默认方法进行覆盖重写(不管实现类是不是抽象类)

冲突默认方法必重写

public interface MyInterfaceA {

    public default void methodDefault(){
        System.out.println("默认方法AAA");
    }
}

public interface MyInterfaceB {

    public default void methodDefault(){
        System.out.println("默认方法BBB");
    }
}


public class MyInterfaceImpl implements MyInterfaceA,MyInterfaceB{

    //必须覆盖重写冲突的默认方法
    @Override
    public void methodDefault(){
        System.out.println("对多个接口当中冲突的默认方法进行了覆盖重写");
    }
}

6.一个类如果直接父类当中的方法,和接口当中的默认方法产生了冲突优先用父类当中的方法

父类优先

public class Fu {
    public void method1(){
        System.out.println("父类方法");
    }
}

public interface MyInterface {
    public default void method1(){
        System.out.println("接口的默认方法");
    }
}

public class Zi extends Fu implements MyInterface{

}

public class Demo01Interface {
    public static void main(String[] args) {
        Zi zi=new Zi();
        zi.method1();
        //输出结果:父类方法
    }
}

1.5 多继承[了解]

1.类与类之间是单继承的,直接父类只有一个。

2.类和接口之间是多实现的,一个类可以实现多个接口

3.接口和接口之间是多继承的

一个接口能继承另一个或者多个接口,这和类之间的继承比较相似。接口的继承使用 extends 关键字,子接口继 承父接口的方法。如果父接口中的默认方法有重名的,那么子接口需要重写一次。

必须进行默认方法的覆盖重写【而且带着default关键字】

interface A{
    public default void method(){
        System.out.println("AAAAAAAA");
    }
}

interface B{
    public default void method(){
        System.out.println("BBBBBBBB");
    }
}

定义子接口:

interface D extends A,B{
    @Override
    public default void method()
    {
        System.out.println("DDDDDDDDDD");
    }
}

小贴士: 子接口重写默认方法时,default关键字可以保留。 子类重写默认方法时,default关键字不可以保留

接口中,无法定义成员变量,但是可以定义常量,其值不可以改变,默认使用public static final修饰。

接口中,没有构造方法,不能创建对象。

接口中,没有静态代码块

二、多态

2.1 概述

多态是继封装、继承之后,面向对象的第三大特性。

生活中,比如跑的动作,小猫、小狗和大象,跑起来是不一样的。再比如飞的动作,昆虫、鸟类和飞机,飞起来也 是不一样的。可见,同一行为,通过不同的事物,可以体现出来的不同的形态。多态,描述的就是这样的状态。

多态性指的是对象—>一个对象又是子类又是父类(小明是一个学生也是一个人类)

  • 多态:是指同一种行为,具有多个不同表现形式

前提【重点】

1.继承或者实现【要产生上下关系】

2.方法的重写【意义体现:不重写,无意义】

3.父类引用指向子类对象【格式体现】

2.2 多态的体现

格式:

父类名称 对象名 = new 子类名称();
//或者:
接口名称 对象名 = new 实现类名称();
Fu f = new Zi();
f.method();

当使用多态方式调用方法时,首先检查父类中是否有该方法,如果没有,则编译错误;如果有,执行的是子类重写 后方法

代码如下:

定义父类

public abstract class Animal{
    public abstract void eat();
}

定义子类

class Cat extends Animal{
    public void eat(){
        System.out.println("吃鱼");
    }
}

class Dog extends Animal{
    public void eat(){
        System.out.println("吃骨头");
    }
}

定义测试类

public class Test{
    public static void main(String[] args){
        //多态形式,创建对象
        Animal a1=new Cat();
        //调用的Cat 的eat
        a1.eat();      //吃鱼

        //多态形式,创建对象
        Animal a2=new Dog();
        //调用的Dog的eat
        a2.eat();     //吃骨头
    }
}

2.3 多态的好处

测试代码:

public class Test{
    public static void main(String[] args){
        //多态形式,创建对象
        Cat c=new Cat();
        Dog d=new Dog();

        //调用showCatEat
        showCatEat();
        //调用showDogEat
        showDogEat();

        /*
        以上两个方法,均可以被showAnimalEat(Animal a)方法所替代
        而执行效果一致
        */

        showAnimalEat(c);
        showAnimalEat(d);

    }
    public static void showCatEat (Cat c){        
            c.eat();      
            }       
    public static void showDogEat (Dog d){         
            d.eat();     
            }       
    public static void showAnimalEat (Animal a){         
            a.eat();     
            }
}

由于多态特性的支持,showAnimalEat方法的Animal类型,是Cat和Dog的父类类型,父类类型接收子类对象,当 然可以把Cat对象和Dog对象,传递给方法

当eat方法执行时,多态规定,执行的是子类重写的方法,那么效果自然与showCatEat、showDogEat方法一致, 所以showAnimalEat完全可以替代以上两方法。

所以,多态的好处,体现在,可以使程序编写的更简单,并有良好的扩展

2.4 引用类型转换

多态的转换分为向上转换和向下转换两种

向上转换

多态本身是子类类型向父类类型向上转换的过程,这个过程是默认的
当父类引用指向一个子类对象时,便是向上转型。

类似于 double num=100;//正确,int—>double,自动类型转换

向上转型一定是安全的

使用格式:

父类类型  变量名 = new 子类类型(); 
如:Animal a = new Cat();

向下转型

多态本身是父类类型向子类类型向下转型的过程,这个过程是强制的

一个已经向上转型的子类对象,将父类引用转为子类引用,可以使用强制类型转换的格式,便是向下转型。
使用格式:

子类类型 变量名 = (子类类型) 父类变量名;
如:Cat c=(Cat) a;

对象的向下转型其实是一个还原的动作 将(向上转型后的)父类对象【还原】为本来的子类对象

为什么要转型

当使用多态方式调用方法时,首先检查父类中是否有该方法,如果没有,则编译错误。也就是说,不能调用子类拥 有,而父类没有的方法。编译都错误,更别说运行了。这也是多态给我们带来的一点”小麻烦”。所以,想要调用子 类特有的方法,必须做向下转型(还原)。

abstract class Animal{
    abstract void eat();
}

class Cat extends Animal{
    public void eat(){
        System.out.println("吃鱼");
    }
    public void catchMouse(){
        System.out.println("抓老鼠");
    }
}

class Dog extends Animals{
    public void eat(){
        System.out.println("吃骨头");
    }
    public void watchHouse(){
        System.out.println("看家");
    }
}

定义测试类:

public class Test{
    public static void main(String[] args){
        //向上转型
        Animal a= new Cat();
        a.eat();         //调用的是Cat的eat

        //向下转型
        Cat c=(Cat)a;
        c.cathMouse();
        //这样才能调用Cat类独有的catchMouse方法(Animal类中没有)

    }
}

image.png

转型的异常

转型的过程中,一不小心就会遇到这样的问题,请看如下代码

public class Test{
    public static void main(String[] args){
            //向上转型
            Animal a= new Cat();
            a.eat();

            //向下转型
            Dog d=(Dog) a;
            d.watchHouse();  //调用的是Dog的watchHouse【运行报错】

    }
}

所以说向下转型是一个还原的过程,原来是Cat只能还原为Cat;

报出ClassCastException异常,

为了避免ClassCastException的发生,Java提供了 instanceof 关键字,给引用变量做类型的校验,格式如下:

变量名 instanceof 数据类型
如果变量属于该数据类型,返回true。 
如果变量不属于该数据类型,返回false。

转型前先做判断

  if (a instanceof Cat){            
          Cat c = (Cat)a;                    
          c.catchMouse();        // 调用的是 Cat 的 catchMouse         
  } 
  else if (a instanceof Dog){             
          Dog d = (Dog)a;                    
          d.watchHouse();       // 调用的是 Dog 的 watchHouse         
  }