当我们编写一个有参方法,形参就是使用时传入的参数类型。这个类型限制了传入的参数。
而有一种语法,可以将接口对象当做形参。
而我们知道,接口是不可以new对象的,所以这里的接口形参,实际上是规定一个大范围。
真正调用方法时应当传入一个接口的实现类作为方法的参数。
目前较新的JDK都已经可以允许接口、抽象类中存在各种访问权限等级的方法,包括私有。
并且这些方法,除了抽象方法,都可以有方法体存在。
首先我们要知道,在动态绑定(不懂的可以去看我另一篇介绍这个定义的文章)发生时,实际会执行子类中重写后的方法。
public class Test5 extends Father5 implements I5{
public void show() {
System.out.println("子类方法执行");
}
public static void main(String[] args) {
Test5 t1 = new Test5();
I5 t2 = new Test5();
Father5 t3 = new Test5();
t1.show(); //子类方法执行
t2.show(); //子类方法执行
t3.show(); //子类方法执行
}
}
interface I5 {
default void show(){
System.out.println("接口方法执行");
}
}
class Father5 {
public void show() {
System.out.println("父类方法执行");
}
}
但这仅仅是参数相同(无参也算相同)的情况下。
当我们把形参设成不同类型时,子类中的方法就不再是重写后的方法,所以会用声明类型中的方法来实现。
public class Test5 extends Father5 implements I5{
public void show(Test5 test5) {
System.out.println("子类方法执行");
}
public static void main(String[] args) {
Test5 t1 = new Test5();
I5 t2 = new Test5();
Father5 t3 = new Test5();
//即使Test5确实是I5的实现类,Father5的子类,但形参不同,就不会发生动态绑定
t1.show(t1); //子类方法执行
t2.show(t1); //接口方法执行
t3.show(t1); //父类方法执行
t1.show(t1); //子类方法执行
t2.show(t2); //接口方法执行
t3.show(t3); //父类方法执行
}
}
interface I5 {
default void show(I5 i5){
System.out.println("接口方法执行");
}
}
class Father5 {
public void show(Father5 father5) {
System.out.println("父类方法执行");
}
}
这样写也是可以的,因为t1是Test5类型,是子类同时也是实现类,所以符合另外两个show方法对形参类型的要求。
但如果我们把t3作为参数给到t2的show方法(反之亦然),那么就会报错,因为I5与Father5之间没有一定的关联。
至少在编译器看来它们俩是没关系的。
当然,我们程序员能够看出,t1,t2,t3的实际类型都是Test5类,所以进行强转是没有问题的,强转后程序也能够正常执行。
Test5 t1 = new Test5(); I5 t2 = new Test5(); Father5 t3 = new Test5(); t1.show(t1); //子类方法执行 t2.show((I5) t3); //接口方法执行 t3.show((Father5) t2); //父类方法执行综合案例(很重要!!)
package base;
//创建一个父类A
class A3 {
//给一个方法f
void f(A3 a){
System.out.println("A");
}
// void test() {
// System.out.println("父类");
// }
}
interface I2 {
//这里他使用了接口本身作为形参
default void f(I2 i) {
System.out.println("I");
}
//一个接口内的无参方法
default void test(){
System.out.println("接口");
}
default void test2(I2 i2){
System.out.println("接口");
}
}
public class Test3 extends A3 implements I2 {
//如果把f方法的参数换成I2 i,那么编译器会要求把访问权限设置成public
// 因为参数相同,变成重写接口方法了。这时调用同名方法,会发生动态绑定
//这样写的话,后面所有的I都会变成B
// public void f(I2 i2) {
// System.out.println("B");
// }
//当参数是Test3时,后面我们调用main方法时,main方法里的f,其实是接口的方法
//因为实现了接口,但因为f不是抽象方法,所以不要求一定重写
//那么实际上就相当于,Test3并没有实现I2接口中的这个方法,而是方法重载
//这里的形参是一个具体的类
//所以其实压根和接口I2里的不是一个方法,那个default void f是被继承、隐藏了,后续调用的相当于隐藏起来的方法
void f(Test3 test3) {
System.out.println("B");
}
//无参实验方法
public void test() {
System.out.println("子类");
}
//有参实验方法
public void test2(Test3 test3) {
System.out.println("子类");
}
public static void main(String[] args) {
I2 i = new Test3();
//test方法,如果父类、接口中都有(无论是哪个,编译都可以通过)
//那么该方法在参数相同的情况下,默认被当做重写后的父类方法
//所当我们用动态绑定来创建对象后,是正常的逻辑,调用子类中的重写方法
i.test(); //子类
//但如果参数不相同,接口内的方法形参是接口对象(不存在,所以其实相当于实现类)
//而实现类内部的方法形参是他本身,那么就相当于接口的方法没有真正被重写,而是重载了
//但当我们new实现类作为实参时,由于该类实现了I2接口,所以他相当于一个”接口对象”
//我们恰好是用i去调用,i是接口实现类对象。从参数类型上来看,没有问题。
//所以这里去实际调用的方法就是接口内的方法。
i.test2(new Test3()); //接口
Test3 tmp = new Test3();
//Test3实际上相当于没有实现接口内的方法,所以当我们用i去调用f方法时,I2中有f方法,不报错
//同时,只有接口I2中有这个方法(形参类型为I2),所以调用时也只能使用这个方法
i.f(tmp); // I
System.out.println("=========================");
i.f(new Test3()); // I
i.f(i);// I
}
}
注意事项
- 实现接口中的方法时,访问权限修饰符不能小于接口内方法本身。大多数情况下,重写时都需要用public修饰来确保。
- 形参不同时(类型、个数),同名方法不算做重写,而是重载。这时,什么类型的引用去调用,就会用这个类型内的方法去实现。
- 接口中的静态方法不会被继承,但默认方法会被继承,非抽象方法不要求实现。
public class Interface1 implements I6{
public static void main(String[] args) {
//默认方法可以被继承
Interface1 interface1 = new Interface1();
interface1.show();//方法被继承了
//静态方法不能被继承,会报错
//interface1.show2();
//但可以直接通过类名. 来调用
I6.show2(); //未被继承
}
}
interface I6 {
default void show(){
System.out.println("方法被继承了");
}
static void show2() {
System.out.println("未被继承");
}
}



