接口、多态
接口
基础性质
特征
- 只要符合接口的规范,任何类都可以使用;
- 是一种引用类型;
构成
- JDK8中可包含 常量 抽象方法 默认方法 静态方法;最重要的是其中的抽象方法;
- JDK9中还可包含私有方法 ;
- 不能有构造方法!
构成内容 | 声明格式 | 注意事项 | 调用方式 | 备注 |
---|---|---|---|---|
成员变量/常量 | 修饰符可省略,默认且必须为 public static final | 必须赋值且值不可变 | 通过接口名直接调用 | JDK8可用 |
抽象方法 | 修饰符可省略,默认且必须为 public abstract(返回值和参数列表随业务需求);不可以有方法体 | 必须被实现类重写 | 通过实现类.方法名调用; | JDK8可用 |
默认方法 | 修饰符可省略,默认且必须为 public default(返回值和参数列表随业务需求);必须有方法体 | 可以被(可以不被)实现类重写(若被重写则调用的是重写后的) | 通过实现类.方法名调用; | JDK8可用 |
静态方法 | 修饰符可省略,默认且必须为 public static(返回值和参数列表随业务需求);必须有方法体 | 不可以被实现类重写 | 通过接口名直接调用 | JDK8可用 |
私有方法 | 修饰符可省略,默认且必须为 private (返回值和参数列表随业务需求);必须有方法体 | 不可以被实现类重写 | 实现类和实例不可调用;接口内部直接通过方法名调用 | 仅JDK9 |
静态私有方法 | 修饰符可省略,默认且必须为 private static(返回值和参数列表随业务需求);必须有方法体 | 不可以被实现类重写 | 实现类和实例不可调用;接口内部直接通过方法名调用 | 仅JDK9 |
定义方式
以下几种方式均可定义接口和其中的抽象方法
package com.heima.study;
public interface demo {
public abstract void abs1();
public void abs2();
abstract void abs3();
void abs4();
String abs5();
}
- 接口中的抽象方法默认且只能为public abstract,这两个关键字均可以省略;
- 抽象方法必须没有方法体,但是返回值可以为任意类型;参数列表可以为任意类型和个数(根据业务需要);
基本使用步骤
接口不能直接使用,必须有实现类 实现接口(重写覆盖其全部抽象方法),然后实例化实现类的对象去使用;
-
步骤1:定义接口类
public interface 接口名称{ //接口内容 public abstract void abs1(); public abstract void abs2(); }
-
步骤2:定义实现类
public class 实现类名称 implements 接口名称{ //重写接口中的 所有 抽象方法 @override public void abs1(){ 填入方法体; } public void abs2(){ 填入方法体; } }
如果不重写所有抽象方法,实现类本身也必须为抽象类;
-
步骤3:测试类 将实现类实例化
public class 测试类名称{ //不能调用接口!! 实现类名 实例名称 =new 实现类(); //用实现类调用重写的接口的方法; 实例名称.abs1(); 实例名称.abs2(); }
默认方法
- 默认方法必须有方法体
package com.heima.study;
public interface demo1 {
public abstract void abs1();
public default void absA(){
//方法体
}
}
- 默认方法可以解决接口升级的问题:
接口升级时添加的新方法如果定义为 public default,则可以自动添加至实现该接口的所有实现类,可以直接被实现类的实例对象调用,避免因为新增的方法没被实现类重写而报错; - 默认方法可以不被重写(也可以被重写);
- 使用方式:
直接从实现类的实例调用默认方法,会向上→实现类重写的方法→接口的默认方法 层层查找去调用
package com.heima.study;
public class imp implements demo1 {
public void abs1() {
//重写
}
}
//无需重写默认方法absA
package com.heima.study;
public class testImp {
imp test1= new imp();
test1.abs1();
test2.absA();//通过实现类的实例直接调用默认方法
}
静态方法
- 静态方法必须有方法体
package com.heima.study;
public interface demo2 {
public abstract void abs1();
public static void absB(){
//方法体
}
}
- 默认方法不可以被重写;
- 使用方式:
直接 接口名.静态方法名
package com.heima.study;
public class imp2 implements demo2 {
public abstract void abs1(){
//重写抽象类,增加方法体
}
}
//不可重写默认方法absB
package com.heima.study;
public class testImp2 {
imp2 test1= new imp2();
test1.abs1();
demo2.absB();//通过实现类的实例直接调用默认方法
}
私有方法
仅限JAVA9
问题场景:接口中的多个方法ABC间可能存在重复代码,需要抽取出来一个方法,直接在ABC中调用即可;但是如果直接定义成默认方法或者静态方法,会出现被实现类直接使用的问题;
解决方案:在接口中定义私有方法,仅供默认/静态方法调用使用;
-
普通私有(解决默认方法间的重复)
private 返回值类型 方法名称(参数列表){ 方法体 }
-
静态私有(解决静态方法间的重复)
private static 返回值类型 方法名称(参数列表){ 方法体 }
成员变量(=接口的常量)
-
没有默认值,必须赋值,且值不可变( final );
-
使用 接口名.名称 调用;
-
默认且只能是public static final,可以不写,但默认仍是;
private interface 接口名称{ public static final 数据类型 名称 =数据值; public abstract void abs1(); public abstract void abs2(); }
形式!!!(不可变的变量命名都如此)
应用问题
实现多个接口
ublic class MyImpl implements MyInterfaceA,MyInterfaceB{
@Override
public void absA() {
System.out.println("实现A");
}
@Override
public void absB() {
System.out.println("实现B");
}
}
- 多个接口或接口与父类中存在重名方法
- 若需要实现的多个接口中有重名 抽象 方法都需要被重写,重写一次即可;
- 若需要实现的多个接口中有重名 默认 方法,则该默认方法必须被重写,重写一次即可;
- 若继承的**父类的方法 和 实现的接口的默认方法**存在重名,则使用实现类.该名字调用的是父类方法!!即继承和接口之间优先级:继承优先。无需重写!
- 如果实现类没有重写所有接口中的全部抽象方法,实现类本身也必须为抽象类;
接口继承接口
-
接口与接口间可以多继承
-
多个父接口之间抽象方法重名,没关系,因为没有方法体,不存在冲突;
-
默认方法如果与默认方法、默认方法与抽象方法间重名,子接口必须对默认方法重写,并且声明为default;
public interface MyInterface extends MyInterfaceB,MyInterfaceA { public abstract void abs(); } //此时MyInterface这个子接口中包含MyInterfaceB,MyInterfaceA 中的所有抽象方法;
多态性
extends 类继承类、接口继承接口;implements 类实现接口等 关系的产生,是多态性的前提;
释义
如小红,既是女人(有女人的形态),也是教师(有教师的形态),也是人(有人的形态);—— 一个对象有多种形态,即对象的多态性!
代码体现多态性
父类引用(等号左侧)指向子类对象(等号右侧);
父类名称 对象名 =new 子类名称();
接口名称 对象名 = new 实现类名称();
public class Fu {
int num=10;
public void shownum() { //父类方法
System.out.println(num);
}
public void shownumFu() { //父类专有方法
System.out.println(num);
}
}
public class zi extends Fu{
int num=20;
int age=20;
@Override //子类重写父类方法
public void shownum() {
System.out.println(num);
}
public void shownumZi() { //子类专有方法
System.out.println(age);
}
}
public class Multi {
public static void main(String[] args) {
Fu test=new zi(); //多态性写法
test.shownumZi(); //编译失败!访问不到子类专有方法(2.2)
test.shownumFu(); //结果是10,运行看右边,从子类向上查找,在父类找到并使用(2.1.1)
test.shownum(); //结果是20,运行看右边,使用子类的成员方法和变量(2.1.2)
test.num; //是10,编译运行都看等号左边,访问的是Fu类
test.age; //编译失败!编译运行都看左边,访问不到子类变量,父类向上都没有该变量;
}
}
访问成员方法
父类与子类有 重名方法 时,优先 从子类向父类 查找调用;
访问成员变量
父类与子类有 重名成员变量 时:
① 通过对象名访问成员变量 :
编译运行都看左边
则从 等号左边(父类)再向上 查找该变量;(即多态写法下,直接通过对象名称是访问不到子类专有的成员变量的!!!)
② 通过成员方法访问成员变量:
编译看左边,运行看右边
2.1 重名方法:
2.1.1子类没有覆盖重写过方法,则查找方法时子类没有,向上查到父类,使用的也是父类的成员变量;
2.1.2子类覆盖重写过该方法,则从子类向上查找,子类有则调用子类的方法,使用的自然是子类的成员变量;
2.2 不重名子类方法
不可能存在通过子类专有方法访问;该实例根本访问不到子类的成员方法!编译失败!(编译看左,运行看右,左边父类里没有子类专有方法,故编译失败)
应用场景
????
对象的转型
对象的向上转型
-
即多态写法:右侧创建一个子类对象把它当做父类看待使用;父类引用(等号左侧)指向子类对象(等号右侧);
父类名称 对象名 =new 子类名称();
-
类似于:基本数据类型的自动类型转换;(也可以自动转换:可以直接将实现类/子类 作为 接口类/父类类型的参数传递 )
-
是否安全:是。因为是从小范围转向大范围!
-
是否全面:否;因为无法调用子类的特有方法或变量!
对象的向下转型(还原)
子类名称 对象名 =(子类名称)父类对象名;
public class animal{
public void hunt(){
System.out.println("捕猎");
}
}
public class cat extends animal{
@override
public void hunt(){
System.out.println("捕鱼");
}
public void eat(){
System.out.println("吃鱼");
}
}
public class test{
public static void main(String[] args){
animal a1= new cat();
a1.hunt();//正常编译运行 “捕鱼”
a1.eat();//编译失败
cat c1=(cat) a1;
c1.eat();//正常编译运行 “吃鱼”
}
}
-
类似于:引用类型的强制类型转换;
-
应用场景:需要调用子类的专有方法和变量时;向下转型回去即可。
-
是否安全:否,可能会出现转型错误(比如 dog d1=(dog) a1;强行把猫动物转成狗)编译可以通过但运行报错;
-
判断还原是否安全:instanceof
父类对象 instanceof 子类名称 //返回布尔值,true:是子类
public static void main(String[] args) {
Fu test=new zi();
if(test instanceof zi) {
zi test2 = (zi) test;
test2.shownum2();
}else{
System.out.println("不要转,原类型不是zi");
}
}
应用场景
举例
宠物公司提供了animal类,其中具体有cat类和dog类(定义animal类,定义cat和dog类继承animal类)
我家想有个宠物(ourpet类),随便什么宠物都行,于是让宠物公司送来“随便什么宠物”(传参父类即可animal 类型 的animal);
送来之后根据具体类型可以陪我做不同的事(需要调用子类方法);
所以送来之后我先看看是啥(instanceof),然后让他做该做的事(向下转型,调用子类方法)。
* 即由于参数是个父类,接收到参数必须判断才知道参数所属的子类类型;参数可能是用户或其他人提供的~
public class animal{
public void hunt(){
System.out.println("捕猎");
}
}
public class cat extends animal{
@override
public void hunt(){
System.out.println("捕鱼");
}
public void play(){
System.out.println("陪玩");
}
}
public class dog extends animal{
@override
public void hunt(){
System.out.println("捕鼠");
}
public void watch(){
System.out.println("看家");
}
}
public class ourPet(Animal animal){
public static void main(String[] args){
if(animal instanceof cat) {
cat c = (cat) animal;
c.play();
}
if(animal instanceof dog) {
dog d = (dog) animal;
d.watch();
}
}
}
举例2
笔记本电脑:有开机功能、关机功能、使用USB设备功能
USB设备:有插入使用、移除关闭功能
键盘:是一种USB设备,除了USB设备都有的功能外,还有输入功能
鼠标:是一种USB设备,除了USB设备都有的功能外,还有点击功能
* 向上转型可以自动转换:可以直接将实现类/子类 作为 接口类/父类类型的参数传递
//定义电脑类
public class laptop {
//定义开关机方法
public void poweron(){
System.out.println("开机");
}
public void poweroff(){
System.out.println("关机");
}
//定义使用usb设备方法
public void usedevice(usb u){
//调用usb设备的通用方法(被子类重写过,执行的时候执行子类方法)
u.open();
//由于点击和输入是实现类的方法,所以需要判断、向下转型、然后调用
if(u instanceof mouse ){
((mouse) u).clickmouse();
//不建议直接用else,因为实际应用中可能有很多种,不是非A即B;
}else if(u instanceof keyboard ){
((keyboard) u).input();
}
//调用usb设备的通用方法(被子类重写过,执行的时候执行子类方法)
u.close();
}
}
//定义接口,包含打开关闭2种抽象方法
public interface usb {
public abstract void open();
public abstract void close();
}
//定义鼠标类,实现usb接口
public class mouse implements usb {
//重写打开关闭设备方法
@Override
public void open() {
System.out.println("插入鼠标");
}
@Override
public void close() {
System.out.println("移除鼠标");
}
//定义子类专有方法
public void clickmouse(){
System.out.println("点击鼠标");
}
}
//定义键盘类,实现usb接口
public class keyboard implements usb {
//重写打开关闭设备方法
@Override
public void open() {
System.out.println("打开键盘");
}
@Override
public void close() {
System.out.println("关闭键盘");
}
//定义子类专有方法
public void input() {
System.out.println("键盘输入");
}
}
//测试使用
public class testUse {
//准备一个电脑
public static void main(String[] args) {
laptop lap1= new laptop();
//开机
lap1.poweron();
//***********
//准备一个鼠标和键盘,同时向上转型,多态写法
usb m=new mouse();
usb k =new keyboard();
//准备一个鼠标和键盘,也可以使用下方写法,直接创建接口的实现类的实例
// mouse m=new mouse();
// keyboard k =new keyboard();
//使用设备,传USB类型参数
lap1.usedevice(m);
lap1.usedevice(k);
lap1.poweroff();//关机
}
}
结果:
开机
打开鼠标
点击鼠标
关闭鼠标
打开键盘
键盘输入
关闭键盘
关机