接口(interface)明确了描述类被授权了哪些能力,但不会指定具体的方式。实现类(implement)一个或多个接口。–>使类完成了实现,是一种对于行为规范的准则的抽象。
个体的方法可以在子类中自写展现,当多个子类均拥有相同的需求的时候就需要凭借接口来快速的高效的完成。
接口是一种规范或契约,它只包含方法的声明,没有方法的实现。一个类可以实现一个或多个接口,并提供接口中定义的所有方法的具体实现。
Java接口(interface)
- < 1 >明确接口中的三个关系
- < 2 >接口中的成员:
- [ 1 ] 成员变量:
- ~~[ 2 ] 构造方法~~ --- >是不存在的
- [ 3 ] 成员方法:
- 【 1 】JDK7 :接口只能定义抽象方法
- 【 2 】JDK8:接口可以有定义的方法体(默认,静态)
- (1)允许接口定义默认方法,default修饰
- Java类和多接口的时候,若多接口中存在重名的方法,而优先类中没有复写,用eclisp的快捷方式为什么只有靠近implement的接口中的方法+解决办法
- 解决办法:
- (2)允许接口中定义定义静态方法,需要用static修饰
- 【 3 】JDK9:接口有私有方法:
- (1)为什么showB需创建对象,而A的却不用
- (2)普通的私有方法,给default默认方法服务(普通的私有方法,给默认方法服务的。default删去)
- < 3 >对比代码
- [ 1 ] 没有抽象方法的抽象实现类:
- [ 2 ] 实现类是抽象类的类
- < 4 >InterApply:
- 代码举例[ 1 ]:
- 分析代码:
- 为什么在main方法中必须强转circle从Shape到Circle的时候setRadius才能用:
- 代码举例[ 2 ]:
- 分析:
- < 5 > 适配器模式(Adapter Pattern):
- 代码1
- 代码2:
接口不同于类,其在Java是一种抽象类型的体现,将抽象的方法集中到一起。以关键字interface来声明,一个类通过继承接口的方式+关键字implement来实现我们所需的方法。
实现接口的类是抽象类,否则该类要定义接口中的所有方法。
< 1 >明确接口中的三个关系
1.> 类和类之间的继承关系:
继承可单不可多,但可多层。让子类继承父类的属性和方法
继承是面向对象编程中一种重要的关系,它允许一个类(子类/派生类)继承另一个类(父类/基类)的属性(数据成员)和方法(函数成员)。子类可以获得父类的属性和方法,并且可以在此基础上添加新的属性和方法,或者重写父类的方法。继承可以是单继承(一个子类只能有一个父类)或多层继承(一个子类可以继承自另一个子类,形成继承链)。
package oop_useInterface;public abstract class Animal {private String name;private int age;public Animal() {}public Animal(String name, int age) {this.name = name;this.age = age;}/*此处省略getter() setter()方法*/public abstract void eat(String age);
}
Dog继承了Animal的字段并重写了eat这个抽象方法实现了具体的能力:
package oop_useInterface;public class Dog extends Animal implements Swim{public Dog() {super();}public Dog(String name, int age) {super(name, age);}public void swim(int age) {System.out.println("DOg Swim");}@Overridepublic void eat(String a) {System.out.println("DOG EAT");}}
2.> 类和接口之间的实现关系:
实现关系,可单多实现,还可以在继承一个类的同时实现多个接口。使类必须实现接口中定义的方法
接口是一种规范,它定义了一组方法的签名(方法名、参数类型和返回类型),但没有具体的实现。类可以实现一个或多个接口,这意味着类需要提供接口中定义的所有方法的具体实现。实现关系是类与接口之间的关系,它强制类实现接口中定义的方法。这种关系使得类可以符合特定的接口标准,从而可以更容易地交互和替换。
public interface Swim {public abstract void swim(int age);
}
按照上方Dog类的implement的关键字,证明Dog接入了Swim这个接口中的swim方法,将其里面的抽象方法重写,实现了Dog类拥有swim方法。
3>. 接口和接口之间的继承关系:
继承关系:可单可多;实现类需要重写所有的抽象方法;新接口继承已有接口的方法签名:
接口之间也可以存在继承关系,类似于类之间的继承关系。一个接口可以继承自一个或多个接口,这样新的接口就会继承已有接口的方法签名。实现类需要重写所有的抽象方法。这种继承关系允许你在新接口中定义更多的方法,并且同时继承了父接口中的方法签名。如果一个类实现了一个继承自其他接口的接口,那么它需要提供所有继承的接口方法的具体实现。
// 定义基础接口 Shape
interface Shape {void draw();
}// 定义继承自 Shape 的 Resizable 接口
interface Resizable extends Shape {void resize(int percentage);
}// 定义继承自 Resizable 的 Drawable 接口
interface Drawable extends Resizable {void colorize(String color);
}
< 2 >接口中的成员:
[ 1 ] 成员变量:
接口中有常量,且只能是常量。这就意味着常量在接口中只能被默认的修饰为public static final
这意味着它们是全局可访问的常量。这些常量通常用于定义接口相关的常量值,且不可被更改。
[ 2 ] 构造方法 — >是不存在的
接口是不存在构造方法的
[ 3 ] 成员方法:
【 1 】JDK7 :接口只能定义抽象方法
当接口内的规则(指新增抽象方法)一改增,导致下面的实现类需要随之改增。
原因:实现类需要重写所有的抽象方法
在 JDK 8 之后的版本中,Java 引入了默认方法(default methods)和静态方法(static methods)的概念,允许在接口中提供方法的默认实现,但这也是有限制的,而且默认方法引入了一些新的问题和考虑事项。
【 2 】JDK8:接口可以有定义的方法体(默认,静态)
接口的能力升级:实现类无需立即进行修改,只需遵循某些规则并进行重写即可。
(1)允许接口定义默认方法,default修饰
格式L:public default 返回值类型 方法名(参数列表){
实例L:public default void show(){}**注意事项[1]默认方法不是抽象方法,不强制被重写;如果被重写,徐去掉default关键字[2]public可以省略,default不能省略,如果省略Java会将其当做抽象方法[3]如果实现多个接口,多个接口存在相同名字的默认方法,子类就必须对改方法进行重写
Java类和多接口的时候,若多接口中存在重名的方法,而优先类中没有复写,用eclisp的快捷方式为什么只有靠近implement的接口中的方法+解决办法
如果一个类实现了多个接口,而这些接口中有重名方法,但类本身没有复写这个方法,Eclipse通常会根据"implement"关键字后面的接口的顺序,选择第一个接口中的方法作为默认方法。
解决办法:
(1)手写自己想要的方式
(2)放在波浪线上,表示有一个或多个建议的操作。- ->这个仅仅是继承了接口中的default默认方法
(2)允许接口中定义定义静态方法,需要用static修饰
格式L:public static 返回值类型 方法名(参数列表){}
实例L:public static void show(){}**注意事项[1]静态方法只能通过接口名调用,不能通过实现类名或者对象名调用[2]public可以省略,static不能省略。如果省略Java会将其当做抽象方法[3]静态方法不可重写
再次理解重写:子类把父类继承下来的虚方法表里面的方法覆盖的过程。静态的,私有的,最终的不会添加到方法表的。
Inter
package oop_useInterface5_JDK8_NewMethod2;public interface Inter {public abstract void method();public static void show() {System.out.println("Inter-static Method");}
}
InterImpl
package oop_useInterface5_JDK8_NewMethod2;public class InterImpl implements Inter{@Overridepublic void method() {System.out.println("InterImpl-overideMethod");}//实现类不可被重写,需要去掉Override//不叫重写,只是一个重名的方法public static void show() {System.out.println("InterImpl-static Method");}
}
Test
package oop_useInterface5_JDK8_NewMethod2;public class Test {public static void main(String[] args) {
//下面只是重名的方法: //接口中静态方法Inter.show();//实现了的静态方法InterImpl.show();}//再次理解重写: 子类把父类继承下来的虚方法表里面的方法覆盖的过程。//静态的,私有的,最终的不会添加到方法表的。
}
【 3 】JDK9:接口有私有方法:
JDK9以前,一个接口在两个default方法,如果有相同的表述,可以采用在写一个default方法抽取出来,如何调用对应方法但抽取是为本代码访问,不想被外类使用,所以要变访问修饰符 -->抽离方法
[1]普通的私有方法,给default默认方法服务(普通的私有方法,给默认方法服务的。default删去)格式1L:private 返回值类型 方法名(参数列表){}实例1L:private void show(){}[2]静态的使用方法,给static静态方法服务(静态的私有方法,给静态方法服务的)格式2L:private static 返回值类型 方法名(参数列表){}实例2L:private static void method(){}
Inter1
package oop_useInterface5_JDK9_New_PrivateMethod;public interface Inter1 {public static void showA() {System.out.println("ShowA--run");RunB();}public default void showB() {System.out.println("ShowB--run");RunA();}// public default void Run() {
// System.out.println("Running");
// }//普通的私有方法,给默认方法服务的。default删去private void RunA() {System.out.println("Running");}//静态的私有方法,给静态方法服务的private static void RunB () {System.out.println("Running");}
}
main方法
package oop_useInterface5_JDK9_New_PrivateMethod;public class Testsss {public static void main(String[] args) {Inter1.showA(); // 调用静态的接口方法Inter1 inter1 = new Inter1Impl();inter1.showB(); // 调用默认的接口方法}
}
class Inter1Impl implements Inter1 {@Overridepublic void showB() {// TODO 自动生成的方法存根Inter1.super.showB();}// 实现类不需要实现私有方法
}
(1)为什么showB需创建对象,而A的却不用
静态方法是与接口本身关联的,与实现类无关,因此可以通过接口名称来直接调用。这就是为什么您可以使用 Inter1.showA() 这样的方式调用 showA 方法,无需创建实现类的对象。
默认方法是为了向已有的接口添加新方法而引入的,这样可以保证已有的类库仍然能够正常工作。因此,默认方法在实现类中必须被实现,而且需要通过实现类的对象来调用,因为默认方法是实例方法,与特定的实例相关联。
(2)普通的私有方法,给default默认方法服务(普通的私有方法,给默认方法服务的。default删去)
如果不去掉default:
Illegal combination of modifiers for the private interface method RunA; additionally only one of static and strictfp is permitted
当去除default就可以为其服务
< 3 >对比代码
[ 1 ] 没有抽象方法的抽象实现类:
虽然是抽象实现类:接口中的没有声明是默认的、静态的、私有的就会被认为是抽象的方法,抽象的方法一定要去重写,赋予具体的实现过程。
interface MyInterface {void method1(); // 接口中的方法void method2();
}class MyConcreteClass implements MyInterface {public void method1() {System.out.println("执行 method1");}public void method2() {System.out.println("执行 method2");}
}public class sadasd {public static void main(String[] args) {MyConcreteClass myObject = new MyConcreteClass();myObject.method1(); // 输出:执行 method1myObject.method2(); // 输出:执行 method2}
}
[ 2 ] 实现类是抽象类的类
public interface MyInterface {void method1(); // 接口中的方法void method2();
}public abstract class MyAbstractClass implements MyInterface {public void method1() {System.out.println("执行 method1");}// method2没有提供具体实现
}public class MyConcreteClass extends MyAbstractClass {public void method2() {System.out.println("执行 method2");}
}public class Main {public static void main(String[] args) {MyConcreteClass myObject = new MyConcreteClass();myObject.method1(); // 输出:执行 method1myObject.method2(); // 输出:执行 method2}
}
一定要实现其中的抽象方法
The type MyConcreteClass must implement the inherited abstract method MyInterface.method2()
< 4 >InterApply:
public interface Method{}public void show(Method的接口 c){}即:接口类型 x = new 实现类对象(编译看左边,运行看右边)
-
接口代表规则,是行为的抽象:
接口是 Java 中用于定义一组方法签名(即方法名称、参数列表和返回类型)的规范。它们不包含方法的具体实现,只声明了方法的签名。类可以实现一个或多个接口,从而表明它们将遵循这些接口所定义的规则,也就是行为。 -
方法的参数是接口时,可以传递接口所有实现类的对象:–>接口多态
当一个方法的参数是接口类型时,可以传递实现了该接口的任何类的对象作为参数。这体现了多态的概念,即你可以在运行时传递不同的对象,实现不同的行为,但是方法的定义只关心接口所规定的方法签名。 -
接口和抽象类不可以实例化:
创建实现了接口的类的对象,或者从抽象类派生出具体的子类,然后通过这些对象进行实例化。这也是多态的体现。
是指不能直接通过
new
关键字实例化接口或抽象类,但是可以传递引用,实现多态。意思是不能直接使用new 接口名
或new 抽象类名
·来创建对象。
但可以通过类实现接口或继承抽象类,并创建实现类的对象。这些实现类可以通过接口引用来实现多态,从而在不同的上下文中使用不同的实现。 -
编译时和运行时
在Java 中,有一个重要的原则是“编译看左边,运行看右边”。这指的是,在编译阶段编译器只关注左边(变量的声明类型),而在运行时,实际执行的方法是根据右边(对象的实际类型)确定的。
代码举例[ 1 ]:
package com.example.shapes;public interface Shape {double calculateArea();
}
package com.example.shapes;public class Circle implements Shape {private double radius;public Circle(double radius) {this.radius = radius;}public double getRadius() {return this.radius;}public void setRadius(double radius) {this.radius = radius;}@Overridepublic double calculateArea() {return Math.PI * radius * radius;}
}
package com.example.shapes;public class Rectangle implements Shape {private double width;private double height;public Rectangle(double width, double height) {this.width = width;this.height = height;}@Overridepublic double calculateArea() {return width * height;}
}
package com.example.shapes;public class AreaCalculator {public void printArea(Shape shape) {double area = shape.calculateArea();System.out.println("Area: " + area);}
}
package com.example.shapes;public class Main {public static void main(String[] args) {Shape circle = new Circle(5);Shape rectangle = new Rectangle(4, 6);if (circle instanceof Circle) {((Circle) circle).setRadius(123);double radius = ((Circle) circle).getRadius();System.out.println("Circle Radius: " + radius);} else {System.out.println("circle is not an instance of Circle");}AreaCalculator calculator = new AreaCalculator();calculator.printArea(circle); // Output: Area: 78.53981633974483calculator.printArea(rectangle); // Output: Area: 24.0}
}
分析代码:
-
接口代表规则,是行为的抽象:
在代码中,Shape
接口就是一个很好的示例。它定义了一个名为calculateArea()
的方法,表达了所有实现了该接口的类应该提供计算面积的行为。Circle
和Rectangle
类实现了Shape
接口,表明它们都遵循了这一规则,并提供了自己的面积计算方法。 -
方法的参数是接口时,可以传递接口所有实现类的对象:
在AreaCalculator
类中的printArea(Shape shape)
方法就是这个例子。它的参数是一个接口类型Shape
,但你可以传递实现了Shape
接口的任何类的对象,如在main
方法中所示。这允许你传递不同类型的图形(比如Circle
和Rectangle
)作为参数,但方法的定义只关心Shape
接口的规范。 -
接口和抽象类不可以实例化:
接口Shape
是无法直接实例化的,而Circle
和Rectangle
作为它的实现类,可以通过new
关键字实例化。在main
方法中,你使用了多态来创建一个Circle
对象和一个Rectangle
对象,并将它们赋给了Shape
类型的引用变量circle
和rectangle
。
为什么在main方法中必须强转circle从Shape到Circle的时候setRadius才能用:
在代码中,在main
方法中创建了一个Shape
类型的对象 circle
,并且将它实际实例化为了一个Circle
对象。然而,由于你将其声明为Shape
类型,编译器只知道它是一个实现了Shape
接口的对象,并没有直接访问Circle
类中特有的方法和属性的权限。
当你尝试在circle
对象上调用setRadius
方法时,编译器无法确定这个方法是否存在于Shape
接口中,因为在Shape
接口中并没有定义这个方法。因此,编译器会报错,提示Shape
接口中没有setRadius
方法。
为了解决这个问题,可以使用强制类型转换 (Circle) circle
将 circle
对象转换为 Circle
类型。这样编译器就知道你希望将它视为 Circle
类型,可以调用 Circle
类中定义的方法和属性。
然而,需要注意的是,强制类型转换存在一些风险,因为你需要确保对象实际上是你期望的类型。如果你尝试将一个不是 Circle
类型的对象进行强制转换,会在运行时抛出 ClassCastException
。
在你的代码中,由于你在 main
方法中已经创建了一个 Circle
对象,并将其赋给了 circle
变量,因此强制类型转换是安全的,但是通常最好在进行强制类型转换之前使用 instanceof
进行类型检查,以避免可能的异常情况。你在代码中使用了 instanceof
来检查 circle
是否是 Circle
类型的实例,然后才进行强制类型转换,这是一个良好的实践。
总结一下,在 main
方法中使用强制类型转换是为了获得更多关于对象的类型信息,从而让编译器识别并允许访问特定类型的方法和属性。但是要注意类型转换的安全性,避免出现运行时异常。
代码举例[ 2 ]:
// 定义接口
public interface Animal {void makeSound();
}// 实现接口的 Dog 类
public class Dog implements Animal {@Overridepublic void makeSound() {System.out.println("Dog barks");}
}// 实现接口的 Cat 类
public class Cat implements Animal {@Overridepublic void makeSound() {System.out.println("Cat meows");}
}// 主类
public class Main {// 方法参数是接口类型,可以接受实现了该接口的任何类的对象public void animalSound(Animal animal) {animal.makeSound();}public static void main(String[] args) {Main main = new Main();Animal dog = new Dog(); // 实现类对象通过接口引用Animal cat = new Cat(); // 实现类对象通过接口引用main.animalSound(dog); // 编译时 Animal,运行时 Dog 的 makeSoundmain.animalSound(cat); // 编译时 Animal,运行时 Cat 的 makeSound}
}
分析:
多态是面向对象编程的一个重要特性,它允许你使用基类或接口类型的引用来引用派生类或实现类的对象,从而在运行时根据对象的实际类型来决定调用哪个方法。在这个示例中,Animal
是一个接口,Dog
和 Cat
是实现了 Animal
接口的类。通过将 Dog
和 Cat
的实例赋值给 Animal
类型的引用,你可以在 animalSound
方法中使用多态,根据实际的对象类型来调用正确的 makeSound
方法。
这种方法使得代码更加灵活和可扩展,因为你可以添加更多实现了 Animal
接口的类,而不需要修改 animalSound
方法的代码,从而实现了代码的开闭原则。
< 5 > 适配器模式(Adapter Pattern):
当一个接口中的抽象方法过多,但只需要使用其中一部分时候采用【相当于一个方法表】步骤: 编写中间类: 接口名Adapter 实现对应接口对接口中的抽象方法进行空实现让真正的实现类继承中间类,并重写需要的方法为了避免其他类创建适配器类的对象,中间的适配器类用abstract修饰
细节:继承的实现类还有多个继承,可以采取间接继承【因为Java没有多继承的】
代码1
下面是一个简单的示例代码来演示适配器模式的用法。在这个示例中,我们使用适配器模式来适配一个已有的LegacyRectangle
类(旧接口)到一个新的Rectangle
接口(新接口),只需要使用Rectangle
接口的一部分方法。
// 定义新的目标接口
interface Rectangle {void draw();void resize();
}// 旧的LegacyRectangle类(旧接口)
class LegacyRectangle {void oldDraw() {System.out.println("LegacyRectangle: Drawing");}void oldResize() {System.out.println("LegacyRectangle: Resizing");}
}// 适配器类,将LegacyRectangle适配到Rectangle接口
abstract class RectangleAdapter implements Rectangle {@Overridepublic void draw() {// 空实现,可以在子类中选择实现}@Overridepublic void resize() {// 空实现,可以在子类中选择实现}
}// 实际的适配器,继承RectangleAdapter并实现需要的方法
class LegacyRectangleAdapter extends RectangleAdapter {private LegacyRectangle legacyRectangle;public LegacyRectangleAdapter(LegacyRectangle legacyRectangle) {this.legacyRectangle = legacyRectangle;}@Overridepublic void draw() {legacyRectangle.oldDraw();}@Overridepublic void resize() {legacyRectangle.oldResize();}
}public class AdapterPatternExample {public static void main(String[] args) {// 使用适配器来调用LegacyRectangle的方法,看起来像是调用Rectangle接口的方法LegacyRectangle legacyRectangle = new LegacyRectangle();Rectangle adapter = new LegacyRectangleAdapter(legacyRectangle);adapter.draw(); // 实际调用LegacyRectangle的oldDraw方法adapter.resize(); // 实际调用LegacyRectangle的oldResize方法}
}
在这个示例中,LegacyRectangle
是旧接口,Rectangle
是新接口。我们创建了一个RectangleAdapter
作为适配器抽象类,然后创建了一个具体的适配器类LegacyRectangleAdapter
,它继承自RectangleAdapter
并将LegacyRectangle
适配到了Rectangle
接口。在main
方法中,我们可以看到通过适配器来调用LegacyRectangle
的方法,实际上调用的是旧接口的方法,但通过适配器,它们看起来像是在调用新接口的方法。
代码2:
// 定义原始接口
interface Shape {void draw();
}// 定义更新后的接口
interface UpdatedShape {void draw();void resize();
}// 中间适配器类,用 abstract 修饰,提供空实现
abstract class ShapeAdapter implements UpdatedShape {@Overridepublic void draw() {// 空实现}@Overridepublic void resize() {// 空实现}
}// 实现类继承适配器类,只需要重写需要的方法
class CircleAdapter extends ShapeAdapter {private Shape adaptedShape;public CircleAdapter(Shape shape) {this.adaptedShape = shape;}@Overridepublic void draw() {adaptedShape.draw();}
}// 圆形类实现原始接口
class Circle implements Shape {@Overridepublic void draw() {System.out.println("Drawing a circle");}
}public class Main {public static void main(String[] args) {Shape circle = new Circle();UpdatedShape updatedCircle = new CircleAdapter(circle);updatedCircle.draw(); // 调用原始方法updatedCircle.resize(); // 调用新增方法,空实现}
}
这个新的接口就是说是这个旧的就接口的一种接口升级。然后又因为这个接口中的抽象方法必须在实现类中进行重写,使用需要一个适配器重写这个新的接口中的方法。
其中的自己适配器类是为了扩展新的方法,最后到达这个继承下来的真正的适配器,就完成了新的接口内部方法的复写
Shape circle = new Circle();
UpdatedShape updatedCircle = new CircleAdapter(circle);
创建实现了接口的类的对象,或者从抽象类派生出具体的子类,然后通过这些对象进行实例化。这也是多态的体现。