接口和多态

Posted lf-637

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了接口和多态相关的知识,希望对你有一定的参考价值。

一、接口

1. 概述

接口是多个类的公共规范,是一种引用数据类型,最重要的内容是其中的抽象方法。接口是Java语言中一种引用类型,是方法的集合 ,内部主要封装了方法,包含抽象方法(JDK 7及以前),默认方法和静态方法(JDK 8),私有方法(JDK 9)

一个类通过继承接口的方式,从而来继承接口的抽象方法。接口并不是类,编写接口的方式和类很相似,但是它们属于不同的概念。类描述对象的属性和方法。接口则包含类要实现的方法。

2. 定义

接口的定义,与定义类方式相似,不同的是使用interface 关键字。它也会被编译成.class文件,但一定要明确它并不是类,而是另外一种引用数据类型。基本格式如下

public interface 接口名称 {
	// 抽象方法(任何版本都能定义)
	// 默认方法
	// 静态方法
	// 私有方法
}
① 含有抽象方法

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

public interface InterFaceName {
	public abstract void methodAbs1();
	public void methodAbs2();
	abstract void methodAbs3();
	void methodAbs4();
}
  • 注意:接口当中的抽象方法,修饰符必须是两个固定的关键字public abstract,也可以省略,但不能是别的关键字
② 含有默认方法和静态方法

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

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

代码如下:

public interface InterFaceName {
    public default void method() {
        // 执行语句
    }
    public static void method2() {
        // 执行语句
    }
}
③ 含有私有方法和私有静态方法

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

public interface InterFaceName {
	private void method() {
		// 执行语句
	}
}

3. 接口基本的使用步骤

  • 抽象方法的使用

① 接口不能直接使用,必须有一个“实现类”来“实现”接口

格式:

public class 实现类名称 implement 接口名称{
	//...
}
② 接口的实现类必须覆盖重写(实现)接口中所有的抽象方法
  • 实现:去掉abstract关键字,假设方法体大括号
③ 创建实现类的对象,进行使用

注意:实现类必须覆盖重写接口中所有的抽象方法,除非实现类是抽象类

  • 默认方法的使用

可以继承,可以重写,二选一,但是只能通过实现类的对象来调用

格式:

public default 返回值类型 方法名称(参数列表){
	方法体
}

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

① 继承默认方法,代码如下 :

定义接口:

public interface Liveable{
    public default void fly(){
        System.out.println("天上飞");
    }
}

定义实现类:

public class Animal implement Liveable{
    //什么都不用写,直接调用
}

定义测试类:

public class InterfaceDemo{
    public static void main(String[] args){
        //创建子类对象
        Animal a = new Animal();
        //调用默认方法
        a.fly();
    }
    //结果:天上飞
}
② 重写默认方法,代码如下 :

定义接口

public interface LiveAble {
	public default void fly(){
		System.out.println("天上飞");
	}
}

定义实现类

public class Animal implements LiveAble {
	@Override
	public void fly() {
		System.out.println("自由自在的飞");
	}
}

定义测试类

public class InterfaceDemo {
	public static void main(String[] args) {
		// 创建子类对象
		Animal a = new Animal();
		// 调用重写方法
		a.fly();
	}
} 
	输出结果:
	自由自在的飞
  • 静态方法的使用

静态与.class 文件相关,只能使用接口名调用,不可以通过实现类的类名或者实现类的对象调用

注意:不能通过接口实现类的对象来调用接口当中的静态方法。应这样用接口名称.静态方法名(参数)

定义接口:

public interface Liveable{
	public sataic void run(){
		systenm.out.println("这是接口静态方法");
	}
}

定义实现类

public class Animal implements LiveAble {
	// 无法重写静态方法
}

定义测试类

public class InterfaceDemo {
	public static void main(String[] args) {
		// Animal.run(); // 【错误】无法继承方法,也无法调用
		LiveAble.run(); //
	}
}
	输出结果:
	跑起来~~~
  • 私有方法的使用

    • 私有方法:只有默认方法可以调用
    • 私有静态方法:默认方法和静态方法可以调用

如果一个接口中有多个默认方法,并且方法中有重复的内容,那么可以抽取出来,封装到私有方法中,供默认方法去调用。从设计的角度讲,私有的方法是对默认方法和静态方法的辅助。

① 普通私有方法

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

private 返回值类型 方法名称(参数列表){
	方法体
}
② 静态私有方法

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

private taatic 返回值类型 方法名称(参数列表){
	方法体
}

  • 接口的常量定义和使用

接口中也可以定义“成员变量”,但必须使用public static final 三个关键字进行修饰。从效果上看就是接口的常量

格式:

public static final 数据类型 常量名称 = 数据值

如:public static final num = 10;//这是一个常量。一旦赋值不可修改

注意:

? ① 接口当中的常量,可以省略public static final ,不写也是这样。初学不省略。

? ② 接口当中的常量,必须进行赋值。因为有final关键字存在

? ③ 接口中常量名称,使用完全大写的字母,用下划线进行分隔。如NUM_OF_CLASS

4. 接口内容小结

  • 成员变量其实是常量,格式:

    • [public] [static] [final] 数据类型 常量名称 = 数据中
      • 常量必须进行赋值,且一旦进行赋值就不能改变
      • 常量名称字母完全大写,用下划线进行分隔
  • 接口中最重要的就是抽象方法,格式:

    • [public] [abstruct] 返回值类型 方法名称(参数列表)
      • 注意:实现类必须覆盖重写接口中所有的抽象方法,除非实现类是抽象类
  • 从Java 8开始,接口里允许定义默认方法,格式:

    • public default 返回值类型 方法名称(参数列表){方法体}
    • 注意:默认方法也可以被覆盖重写
  • 从Java 8开始,接口里允许定义静态方法,格式:

    • [public] static 返回值类型 方法名称(参数列表){方法体}
    • 注意:应通过接口名称进行调用,不能通过实现类对象调用接口静态方法
  • 从Java 9开始,接口里允许定义私有方法,格式:

    • 普通私有方法:private 返回值类型 方法名称(参数列表){方法体}
    • 静态私有方法:private static 返回值类型 方法名称(参数列表){方法体}
    • 注意:private的方法只有接口自己才能调用,不能被实现类或别人使用

5. 接口注意事项

  • 接口没有静态代码块和构造方法

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

    • 格式:

    • public class MyInterface implements InterfaceA,InterfaceB{
      	//覆盖重写所有抽象方法
      }
      
  • 如果实现类所实现的多个接口中,存在重复的抽象方法,那么只需覆盖重写一次即可

  • 如果实现类所实现的多个接口中,存在充分的默认方法,那么实现类一定要对冲突的默认方法进行覆盖重写

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

  • 一个类如果其直接父类当中的方法和接口当中的默认方法产生了冲突,那么优先使用父类中的方法

7. 接口的多继承

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

注意:

  • 多个父接口中的抽象方法可以重复
  • 多个父接口在的默认方法如果重复,那么子接口必须进行默认方法的覆盖重写,且带着default关键字

二、多态

1. 概述

多态是继封装、继承之后,面向对象的第三大特性 。多态是同一个行为具有多个不同表现形式。多态性是对象多种表现形式的体现。

如学生A是一名学生,同时也是一个人。学生A这个对象既有学生形态,也有人类形态。一个对象拥有多个形态这就是对象的多态性

2. 定义

  • 前提

    • 继承或者实现【二选一】
    • 方法的重写【意义体现:不重写,无意义】
    • 父类引用指向子类对象【格式体现】
  • 代码中体现多态性,就是一句话:父类引用指向子类对象

3. 多态的格式与体现

① 格式:父类引用指向子类对象
父类名称 对象名 = new 子类名称();
Fu obj = new Zi();
//或者
接口名称 对象名 = new 实现类名称();
② 多态中成员方法的使用特点

在多态的代码中,成员方法的访问规则是:

  • 看new的是谁,就优先用谁
  • 编译看左边,运行看右边
    • 用左边父类没有的方法,就会编译报错
    • 而成员变量的规则是:编译看左边,运行还看左边

4. 多态的好处

实际开发的过程中,父类类型作为方法形式参数,传递子类对象给方法,进行方法的调用,更能体现出多态的扩展性与便利 ,多态的好处体现在可以使程序编写的更简单,并有良好的扩展

5. 引用类型转换

多态的转型分为向上转型与向下转型两种

① 向上转型
  • 对象的向上转型,其实就是多态写法。多态本身是子类类型向父类类型向上转换的过程,这个过程是默认的。当父类引用指向一个子类对象时,便是向上转型
  • 格式:父类名称 对象名 = new 子类名称();
  • 含义:右侧创建一个子类对象,把它当作父类来使用
    • Animal animal = new Cat();//创建一只猫,当作动物看待
  • 注意:向上转型一定是安全的。从小范围转向了大范围
② 向下转型
  • 向下转型其实是一个还原的动作。是父类类型向子类类型向下转换的过程,这个过程是强制的
  • 向上转型一定是安全的,正确的。当也有一个弊端:对象一旦向上转型为父类,那么就无法调用原本子类特有的内容。解决方法边上向下转型。
  • 格式:子类类型 变量名 = (子类类型) 父类变量名;
  • 含义:将父类对象还原为原本的子类对象
③ 代码演示

定义类

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 Animal {
		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.catchMouse(); // 调用的是 Cat 的catchMouse
        }
}
④ 注意:以上面的例子为例
  • 必须保证对象原本创建的时候,就是猫,后面才能向下转型为猫

  • 如果对象原本创建的时候不是猫,若非要向下转型为猫就会报错。这段代码可以通过编译,但是运行时,却报出了 ClassCastException 类型转换异常!这是因为,明明创建了Cat类型对象,运行时,当然不能转换成Dog对象的。这两个类型并没有任何继承关系,不符合类型转换的定义。

    • 类似于int num = (int)10.0; //可以int unm = (int)10.8; 不行,会损失精度
  • 为了避免上面这种情况的发生,Java提供了 instanceof 关键字,给引用变量做类型的校验,格式如下

    • 变量名 instanceof 数据类型 ,如果变量属于该数据类型,返回true,否则返回false ```

    • public class Test {
      	public static void main(String[] args) {
      	// 向上转型
      	Animal a = new Cat();
      	a.eat(); // 调用的是 Cat 的 eat
      	// 向下转型
      	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
      		}
      	}
      }
      

以上是关于接口和多态的主要内容,如果未能解决你的问题,请参考以下文章

Java面向对象多态和接口

多态与抽象以及接口

java讨论:啥是多态和接口,为啥需要?

封装,继承,多态,接口

每个人单核苷酸多态性(SNP)形成的原因是啥?

ObjectOrientedProgramming - 面向对象的编程(多态抽象类接口)- Java - 细节狂魔