有关Java中的接口
学习目标
- 掌握接口语法
- 理解接口多态
- 熟练使用接口
- 了解接口新特性
- 掌握final关键字
- 了解lambda语法
1.接口语法
1.1 接口概念
-
从功能上看, 实现接口就意味着扩展了某些功能
-
接口与类之间不必满足
is-a
的关系结构 -
从抽象上看, 接口是特殊的抽象父类
-
从规则上看, 接口定义者和实现者都必须遵守特定规则
1.2 面向接口编程
● 接口是设计层面的概念,往往由设计师设计,将定义与实现分离.
● 程序员实现接口,实现具体方法.
● 面向接口编程和面向对象编程并不是平级的,它并不是比面向对象编程更先进的一种独立的编程思想,而是附属于面向对象思想体系,属于其一部分。或者说,它是面向对象编程体系中的思想精髓之一
● 面向接口编程的意思是指在面向对象的系统中所有的类或者模块之间的交互是由接口完成的
1.3 接口语法
[访问权限修饰符] interface 接口名 {
公开静态常量列表;
公开抽象方法列表;
}
在开发中:
接口的命名方式只有2种
XXXDao/DAO.java XXXDaoImpl.java
xxxService.java xxxServiceImpl.java
1.4 接口组成部分
目前我们使用的是JDK11。
//jdk1.8+ 接口声明为函数式接口 使用标记注解 @FunctionalInterface
//@FunctionalInterface //有且只有1个抽象方法
public interface MyInterface {
//组成部分----> 都是public
//1.常量 public static final 默认 final修饰的变量都是常量
//接口作为常量类使用---->维护模块中很多不可变的数据
//1.1 常量名称全部大写
//1.2 必须赋值
//1.3 值不允许被更改
String NAME = "admin";
int USER_PASS = 1234;
//2.方法 接口比抽象类还要抽象 jdk1.8之前 接口里面的方法全部都是抽象方法 public abstract 默认
//2.1 抽象方法 对于程序猿来说 使用量最多还是仅仅抽象方法 (封装行为)
void demo1();
int max(int num1, int num2);
//2.1 功能方法---->有方法体的方法 jdk1.8+ public default
default void demo2() {
System.out.println("MyInterface。。。。。demo2.。。。。");
}
//2.3 静态方法 jdk1.8+
static String staticMethod() {
System.out.println("MyInterface。。。。。staticMethod.。。。。");
return "static";
}
//2.4 私有的静态方法 jdk1.9+ 服务于其它public static的方法
private static void privateStaticMethod() {
System.out.println("MyInterface。。。。。。privateStaticMethod。。。。");
}
//2.5 接口没有构造方法----->不能直接实例化 其实也可以 创建了 匿名内部类
}
1.5 使用接口
1 接口继承接口(extends)
接口与接口属于多继承关系。一个接口可以继承多个接口。使用extends关键字实现。
public interface AInterface extends BInterface,CInterface,....{
}
public interface AInterface {
void a();
}
interface CInterface {
void c();
}
interface BInterface extends AInterface, CInterface {//子接口与父接口的关系
void b();
}
2 类实现接口(implements)
● 类与接口属于多实现关系。一个类实现多个接口。使用implements进行操作。
● 接口是比抽象类抽象层次更高的一个概念,因此和抽象类一样,它不能用于实例化对象,只能作为继承树的高层结构被子类实现(子类实现接口被称为implements(实现),其体现形式和继承类似)。
● 类实现接口,本质上与类继承类相似,区别在于“类最多只能继承一个类,即单继承,而一个类却可以同时实现多个接口”,多个接口用逗号隔开即可。实现类需要覆盖所有接口中的所有抽象方法,否则该类也必须声明为抽象类
public class 类名 extends Object implements AInter,BInter{
}
注意: 一个普通类实现接口有可能会报错?
public interface UserInterface {
//常量
String PASS = "1234";
String NAME = "admin";
//方法
boolean login(String name, String pass);
void register();
default void defaultMethod() {
System.out.println("UserInterface.....public....defaultMethod......");
}
static void staticMethod() {
System.out.println("UserInterface.....public....staticMethod......");
}
private void privateMethod() {
System.out.println("UserInterface.....private....privateMethod......");
}
}
//类实现接口称为接口的实现类
//接口就像 "招牌"
//只要类实现接口 就代表这个类中具有这些功能
//因此一个接口可以有多个实现类,有多套实现
public class UserInterfaceImpl implements UserInterface{
}
一个普通类 实现接口的时候 这个类可能会报错
解决方式:
1. 将这个类改为抽象类
2. 普通类重写/实现接口里面的所有的抽象方法
//类实现接口称为接口的实现类
//接口就像 "招牌"
//只要类实现接口 就代表这个类中具有这些功能
//因此一个接口可以有多个实现类,有多套实现
public class UserInterfaceImpl implements UserInterface{
@Override
public boolean login(String name, String pass) {
System.out.println("登录成功..........");
return true;
}
@Override
public void register() {
System.out.println("注册成功..........");
}
public void test(){
//在实现类中 依然可以调用父接口里面提供的静态以及default方法
//1. 实现类没有重写父接口default方法 执行的是父接口的逻辑
//2. 实现类重写父接口的default方法 执行的逻辑是子级重写过后的功能
defaultMethod();
this.defaultMethod();
//3.调用父接口的静态方法,静态都是所属于class的 因此调用方式使用: 接口名称.静态方法
UserInterface.staticMethod();
}
//可以选择重写父接口的default方法
@Override
public void defaultMethod() {
//假如有需求: 想再调用父接口的default方法逻辑. 需要使用super进行操作
//但是一个普通类既可以继承又可以实现: 单独使用super默认是父类
//如果是代表父接口: 接口名.super.方法
UserInterface.super.defaultMethod();
System.out.println("UserInterfaceImpl......重写的defaultMethod.........");
}
}
在实现类中 依然可以调用父接口里面提供的静态以及default方法
1. 实现类没有重写父接口default方法 执行的是父接口的逻辑
2. 实现类重写父接口的default方法 执行的逻辑是子级重写过后的功能
3. 调用父接口的静态方法,静态都是所属于class的 因此调用方式使用: 接口名称.静态方法
创建测试类: 测试实现类的方法
public static void main(String[] args) {
//类实现接口: 层级关系
//创建实现类对象 有2种方式编写
//1. 普通方式创建对象: 可以调用实现类中的所有的成员
UserInterfaceImpl userInterfaceImpl = new UserInterfaceImpl();
System.out.println(userInterfaceImpl.login("admin", "1234"));
userInterfaceImpl.register();
userInterfaceImpl.test();
//2.多态方式创建对象
//接口不能直接实例化 满足或者服务与实现类对象的创建
UserInterface userInterface = new UserInterfaceImpl();//这里就是父接口引用指向任意一个实现类对象
userInterface.register();
userInterface.login("admin","1234");
//userInterface.test(); //存在弊端 只能直接访问接口里面存在的成员 不能访问实现类独有的方法和属性
//目前而言: 以上2种方式都可以,看个人喜欢。在后期 建议使用第二种方式
}
2.多态(在实现的关系下)
接口也是引用数据类型,也可以作为形参,返回值进行使用。
因此接口也是体现多态的,可以更加良好的扩展程序。
2.1 需求
使用面向对象的思想,使用接口+多态实现以下需求:
需求:
● 通过下面案例,我们来掌握在接口实现的关系下,是如何实现多态的:
○ 为各学校开发这样一个小系统,包含类型:教员、学校、打印机。
○ 具体要求如下: 教员以及学校都具有 "输出详细信息"的方法。
○ 打印机类有方法,可以打印教员或学校的详细信息。
○ 系统要具备良好的可扩展性与可维护性
2.2 创建类
@Setter
@Getter
@Accessors(fluent = true)
public class Teacher{
private int id;
private String name;
private int age;
public void showInfo() {
System.out.println(name + "老师信息如下:");
System.out.println(name + "的id:" + id);
System.out.println(name + "的age:" + age);
}
}
@Setter
@Getter
@Accessors(fluent = true)
public class School{
private int id;
private String name;
private String address;
private String phone;
public void showInfo() {
System.out.println(name + "信息如下:");
System.out.println(name + "学校的id:" + id);
System.out.println(name + "学校的address:" + address);
System.out.println(name + "学校的phone:" + phone);
}
}
2.3 方法重载
@Setter
@Getter
@Accessors(fluent = true)
public class Printer {
private int id;
private String brand;
public void printInfo(Teacher a){
//打印老师+学校的信息
System.out.println(brand+"开始打印..........");
a.showInfo();
System.out.println(brand+"结束打印..........");
}
public void printInfo(School a){
//打印老师+学校的信息
System.out.println(brand+"开始打印..........");
a.showInfo();
System.out.println(brand+"结束打印..........");
}
}
2.4 问题
上述代码中,不考虑多态与扩展性的前提下,方法重载完全可以实现功能。
弊端:
针对于printInfo功能,假如还可以打印Student等信息,需要修改源码,新增一个新的方法。
没有满足程序设计中的 “开放封闭原则”,更没有提高程序的扩展性。
2.5 解决_父类
解决:
因此,我们要基于School与Teacher这两个类进行抽象。
想多态,必须要面向父级或者面向抽象进行开发。也就是必须有父级。
发现School与Teacher这两个类,共同继承了同一个父类Object。
@Setter
@Getter
@Accessors(fluent = true)
public abstract class Printer {
private int id;
private String brand;
public void printInfo(Object a){
//打印老师+学校的信息
System.out.println(brand+"开始打印..........");
//a.showInfo(); 根本无法通过a调用showInfo的方法
System.out.println(brand+"结束打印..........");
}
}
因此从父类考虑,无法实现多态。
2.6 解决_接口
//从接口进行设计,由于接口封装行为,发现Teacher与School类都有共同的行为showInfo,且实现逻辑不同。
//因此,我们可以来一个父接口,封装Teacher与School都具备的行为。
//等价于showInfo是重写InfoDao的方法
public interface InfoDao {
void showInfo();
}
@Setter
@Getter
@Accessors(fluent = true)
public class Teacher implements InfoDao{
private int id;
private String name;
private int age;
public void showInfo() {
System.out.println(name + "老师信息如下:");
System.out.println(name + "的id:" + id);
System.out.println(name + "的age:" + age);
}
}
@Setter
@Getter
@Accessors(fluent = true)
public class School implements InfoDao{
private int id;
private String name;
private String address;
private String phone;
public void showInfo() {
System.out.println(name + "信息如下:");
System.out.println(name + "学校的id:" + id);
System.out.println(name + "学校的address:" + address);
System.out.println(name + "学校的phone:" + phone);
}
}
@Setter
@Getter
@Accessors(fluent = true)
public abstract class Printer {
private int id;
private String brand;
public void printInfo(InfoDao a){
//打印老师+学校的信息
System.out.println(brand+"开始打印..........");
a.showInfo();
System.out.println(brand+"结束打印..........");
}
}
3.接口 VS 抽象类
● 在Java中接口是一个比抽象类更加抽象的概念,由于只声明行为,因此在接口中的方法均是抽象的,下表中罗列了接口和抽象类的差异:
● 成员变量方面,在接口中只存在公开静态常量(即便没有使用static final修饰,Java也会默认确定其性质)
● 成员方法方面,在接口中只存在公开抽象方法(即便没有abstract修饰,Java也会默认其抽象性质。但是JDK1.8之后,接口里面的方法更加丰富了。
相同点:
- 都不能直接实例化
- 都作为父级来使用
- 都是体现多态
不同点:
- 语法 abstract class 接口: interface
- 组成部分: 抽象类: 普通类+抽象方法 接口: jdk11 常量+抽象方法+default+static方法+private的功能方法
- 抽象类有构造—> 服务于子类对象创建 接口: 没有构造
- 抽象类: 定义子级都有的行为和属性。 接口: 定义子级要实现的行为。
- 使用场景: 子级有属性 这个时候 只能选择 类。 只有行为 其实抽象类或者接口 都可以。建议使用接口
类与类属于单继承。 类与接口属于多实现。 从设计角度 接口更加提高升序的扩展性。
4 @FunctionalInterface
● @FunctionalInterface: 是jdk1.8提供的新特性。 函数式接口。只能在接口上使用。
● 是一个标记注解。 jdk默认提供的。 类似于 @Override
● @FunctionalInterface 加与不加都可以。
● 如果在接口上使用了这个@FunctionalInterface注解
1.标志着接口中 有且只有1个抽象方法
2.可以使用lambda表达式简化开发
● 如果我们想使用lambda语法简化开发 标志着接口中 必须有且只有1个抽象方法 。
@FunctionalInterface
public interface MyInterface {
void demo();//有且只能有1个抽象方法
//其它功能方法与常量可以正常存在
}
5. lambda
5.1 语法
● 面向函数式编程-----> 面向方法编程-----> 面向重写的那个方法编程
● lambda: 拉姆达语法。jdk1.8提供的新特性。
● 基本语法1:
(形参类型 变量1…形参类型 变量n)->{
//方法体 核心逻辑
}
● 简化语法2:(方法引用)
引用/对象::方法名;
类名::方法名;
● 使用lambda语法开发 目的: 替换接口匿名内部类。
● 可以根据任意一个类/接口创建匿名内部类。
● 类的匿名内部类: 等价于创建了子类。 接口的匿名内部类: 等价于是接口实现类。
5.2 使用
1. 无参无返回值
public interface MyInterface {
//抽象方法
//1.无参无返回值
void demo();
}
class Test {
public static void main(String[] args) {
//接口不能直接实例化
//MyInterface myInterface = new MyInterface();
//接口: 创建匿名内部类
// new 类名/接口名(){};
//接口的匿名内部类-----> 接口的实现类
//lambda表达式----> 替换接口的匿名内部类 -----> 接口里面 有且只有1个抽象方法
//面向函数式编程----> 面向方法编程-----> 面向重写的那个方法编程
// (形参类型 变量名称,....)->{}
//1.调用接口里面的demo方法
MyInterface myInterface = new MyInterface() {
@Override
public void demo() {
System.out.println("demo.................");
}
};
//lambda简化
/*MyInterface myInterface1 = () -> {
System.out.println("demo1.................");
};*/
//前提: 方法体只有1行 {}可以省略 类似if else
MyInterface myInterface1 = () -> System.out.println("demo1.................");
myInterface.demo();
myInterface1.demo();
}
}
2. 有参无返回值
形参有1个的时候:
@FunctionalInterface
public interface MyInterface {
//抽象方法
//1.无参无返回值
//void demo();
//2.有参无返回值
//1个形参
void compareStr(String str);
}
public static void main(String[] args) {
//2.有参无返回值
MyInterface myInterface = new MyInterface() {
@Override
public void compareStr(String str) {
System.out.println("hello".equals(str));
//System.out.println(Objects.equals("hello",str));
}
};
myInterface.compareStr("hello");
//myInterface = (String abc)->System.out.println(Objects.equals("hello",abc));
//重写的方法只有1个形参类型 形参类型 与 () 都可以省略
myInterface = abc -> System.out.println(Objects.equals("hello", abc));
myInterface.compareStr("admin");
}
形参数量有多个的时候:
@FunctionalInterface
public interface MyInterface {
//抽象方法
//1.无参无返回值
//void demo();
//2.有参无返回值
//1个形参
//void compareStr(String str);
//多个形参类型
void max(int num1, int num2);
}
public static void main(String[] args) {
//调用max
MyInterface myInterface = new MyInterface() {
@Override
public void max(int num1, int num2) {
System.out.println(Math.max(num1,num2));
}
};
myInterface.max(1,10);
//方法的形参类型 有多个。可以省略数据类型
myInterface = (a, b)->System.out.println(Math.max(a,b));
myInterface.max(1,10);
}
3.有参有返回值
@FunctionalInterface
public interface MyInterface {
//抽象方法
//3.有参有返回值类型
int max(int num1, int num2);
}
public static void main(String[] args) {
MyInterface myInterface = new MyInterface() {
@Override
public int max(int num1, int num2) {
return Math.max(num1, num2);
}
};
//myInterface = (num1, num2) -> Math.max(num1, num2);
//前提: 有参有返回值 方法体只有1行
// 参数类型可以省略 {}可以省略 return 必须省略
myInterface = (num1, num2) -> Math.max(num1, num2);
//重写的方法的形参类型与数量 和 方法的返回值类型 与 调用的方法的形参类型+数量与返回值类型一致的时候
//引用/对象::方法名;
//类名::方法名;
myInterface = Math::max;
System.out.println(myInterface.max(1, 10));
}
4. 简单案例
Integer[] array = {1, 7, 8, 3, 2};
//对数组元素进行排序
// Arrays.sort(array);//升序
//想降序排列----> 自己指定对int数据的排序规则
// Arrays.sort(array,new Comparator<>(){
// @Override
// public int compare(Integer num1, Integer num2) {
// return num2-num1;
// }
// });
Arrays.sort(array, (num1, num2) -> num2.compareTo(num1));
// Arrays.sort(array,Comparator.reverseOrder());
Arrays.sort(array, Comparator.comparing(Integer::intValue).reversed());
System.out.println(Arrays.toString(array));
何时会使用lambda?
● 只要是方法的形参类型或者返回值类型是接口(接口中只有1个抽象方法)
● 而且逻辑只用一次,可以使用lambda语法简化匿名内部类。
6.final
可以理解final是一个关键字,也可以认为是修饰符。
6.1 修饰变量
final修饰变量就是常量。
● final修饰全局变量
1. 常量名称全部大写,多个单词使用_进行关联。
2. 必须赋值,且值不允许更改。
● final修饰局部变量
1. 常量名称依然遵循小写驼峰命名。
2. 值不允许更改。
//1.修饰变量
public class FinalDemo {
private FinalDemo() {
//NUM1 = 10;
}
//1. 修饰的全局变量 private static final / public static final
public static final String MY_MSG = "HELLO";
private static final int NUM = 100;
//一个类中 有很多常量 一般都是static 这个类要么是常量类
// com.baidu.x.x.constant.UserConstant
// 要么是我们工具类 com.baidu.x.x.util
public static final int NUM1;
static {
NUM1 = 200;
}
//2. final修饰局部变量 常量的名称不要全部大写 小写驼峰
public static void login(final String nameInfo, final String pass) {
// name = "aaaa";
final int num = 100;
}
public static void main(String[] args) {
login("admin", "1234");
}
}
6.2 修饰类
修饰类,会导致这些类不能能被继承,不能作为父类使用。
public final class MyClass {
}
public class Child /*extends MyClass*/{
}
6.3 修饰方法
在任何一个类中,都可以使用final修饰方法。
一般的场景用于父类: 不想让子类覆盖父类方法的时候,可以使用fina修饰方法。
public class MyClass{
public final void demo1(){
}
}
public class Child extends MyClass{
//在子类中 重写父类final修饰的方法 会出现问题
//@Override
//public void demo1(){
//}
}