接口、lambda表达式与内部类
- 接口用来描述类应该做什么,而不指定它们具体应该如何实现。
- 通过使用lambda表达式,可以用一种精妙而简洁的方式使用回调或可变行为的代码。
- 内部类定义在另一个类的内部,它们的方法可以访问包含它们的外部类的字段。
1. 接口
定义一个接口需要使用interface关键字
1 | public interface English { |
对于接口,有下面几点需要注意:
- 接口中所有的方法都自动是public方法(在实现接口时必须把方法声明为public)
- 接口不会有实例字段。
一个类可以实现一个或多个接口,使用implements关键字来实现一个接口,这个类还必须对接口中的所有方法提供定义。(默认方法可以不用)
1 | public class pingpongCoach extends coach implements English { |
从上面可以看见,一个类在实现接口的同时可以继承另一个类。
提供实例字段和方法实现的任务应该由实现接口的那个类来完成! 因此可以将接口看作没有实例字段的抽象类。
1.1. 接口的属性
接口不是类,不能用new运算符实例化一个接口。
1 | x = new English();//error |
但是可以声明接口的变量,对接口使用多态(引用实现了这个接口的类对象)
1 | English en = new pingpongCoach(...);//ok |
也可以用instanceof 检查一个对象是否实现了某个特定的接口。
1 | if(anObject instanceof English) |
接口可以继承另一个接口
1 | public interface Orallanguage extends English{ |
接口中虽然不能包含实例字段,却可以包含常量。接口中的字段会被自动设置为public static final
1 | public interface English { |
1.2. 接口与抽象类
- 抽象类和接口的成员区别
抽象类:变量、常量;有构造方法,抽象方法,非抽象方法
接口:常量,抽象方法
- 一个类只能扩展一个类,但是能够实现多个接口
- 类和类之间能够多层继承,而接口和接口之间能够多继承
1
2
3
4public interface English extends a,b{
//接口的多继承
}
public class English extends a,b//error - 抽象类是对事物的抽象,而接口是对行为的抽象。(如运动员类,说英语方法)
2. 接口中的其他方法
接口中还允许有静态方法、私有方法和默认方法
2.1. 静态方法
接口中的静态方法只能通过接口名调用,静态方法能够拥有方法体。
1 | static void ask(){ |
静态方法只能调用静态方法!
2.2. 私有方法
私有方法可以为静态方法或实例方法,仅限于在接口本身的方法中使用,只能用作其他方法的辅助方法。
2.3. 默认方法
可以为接口方法提供一个默认方法实现,用default修饰符标记这样一个方法
1 | default void studyen(){ |
接口的实现可以不用覆盖这个方法(很少),并且可以调用默认方法。
默认方法还可以调用其他方法(静态、私有)
2.4. 解决默认方法冲突
如果先在一个接口中将一个方法定义为默认方法,然后又在超类或另一个接口中定义同样的方法,在java中解决的方法如下:
- 超类优先,如果超类提供了一个具体方法,同名而且有相同参数类型的默认方法会被忽略。
- 接口冲突。如果一个接口中提供了一个默认方法,另一个接口提供了一个同名而且参数类型相同的方法,必须覆盖这个方法来解决冲突。
1 | interface Person{ |
如果至少有一个接口提供了实现,编译器就会报告错误,程序员必须解决这个二义性。
如果两个接口都没有提供默认实现,则只需实现这个方法或者都不实现(该类为抽象类)
3. 对象克隆
为一个包含对象引用的变量建立副本时,原变量和副本都是引用的同一个对象,任何一个变量改变都会影响另一个变量。
1 | public class demo { |
如果希望得到一个新对象,则需要使用clone方法,并且使用clone方法的类要实现Cloneable接口
1 | CloneClass c2 = (CloneClass) c1.clone(); |
默认的clone方法是浅拷贝(被复制对象的所有值属性都含有与原来对象的相同,而所有的对象引用属性仍然指向原来的对象)
如果需要,可以提供深拷贝。
要小心使用clone,尽量使用new
4. lambda表达式
lambda表达式是一个可传递的代码块,可以在以后执行一次或多次。
lambda运算符:所有的lambda表达式都是用新的lambda运算符 “ -> “,可以叫他,“转到”或者 “成为”。运算符将表达式分为两部分,左边指定输入参数,右边是lambda的主体。
1 | (String s) -> s.length() |
如果方法只有一个参数,而且这个参数的类型可以推倒得出,那么甚至还可以省略小括号:
1 | s -> s.length() |
即使lambda表达式没有参数,仍需提供空括号
1 | () -> System.out.println() |
无须指定返回值
4.1. 函数式接口
对于只有一个抽象方法的接口,需要这种接口的对象时,就可以提供一个lambda表达式,这种接口称为函数式接口。
1 | public interface lami {//只有一个抽象方法的接口为函数式接口 |
如何用lambda表达式来表示这个接口呢,看如下代码
1 | public class lamc { |
这时的lambda表达式实际为一个实现了该接口的类的实例对象,箭头右边是对接口中抽象方法的实现。
既然lambda表达式可以看做实现了接口的对象的话,那么以下方式也是可以的
1 | lami la = s->s.length(); |
5. 方法引用
当lambda表达式只是执行一个方法调用时,可直接使用方法引用
1 | lami la = s->s.length();//lambda表达式只是调用了String类的length方法 |
方法引用的格式为 类名::方法,方法也包括静态方法
方法引用和lambda表达式一样不能独立存在,总是转换为函数式接口的实例。
5.1. 变量作用域
lambda表达式有三个部分
- 一个代码块
- 参数
- 自由变量的值,这里指非参数而且不在代码中定义的变量
lambda表达式可以捕获外围变量的值,但是只能引用值不会改变的变量
1 | for(int i = 0;i<9;i++){ |
即lambda表达式中捕获的变量实际上是事实最终变量。
另外还需注意一点,在lambda表达式中声明与一个局部变量同名的参数或局部变量是不合法的
6. 内部类
把类定义在另一个类的内部,该类就被称为内部类。
1 | public class Outer { |
内部类方法可以访问定义在这个类作用域中的数据,包括私有数据,而外部类要访问内部类的成员要创建对象。
1 | public void method(){ |
- 对于成员内部类,外界可以访问,不过需要特殊的格式
格式:外部类名.内部类名 对象名 = 外部类对象.内部类对象
1 | public class demo { |
- 对于局部内部类,外界不能访问;局部内部类还可以访问局部变量!
1 | public class Outer { |
6.1. 匿名内部类
匿名内部类是一个继承了该类或者实现了该接口的子类匿名对象!
1 | new anonyClass(){ |
上述的匿名内部类实现的是一个函数式接口,可以用lambda表达式来代替
1 | anonyClass ano = ()-> System.out.println("匿名内部类!"); |
由于匿名内部类没有类名,所以不能有构造器
6.2. 静态内部类
只有内部类可以声明为static
特点:不能使用外部类的非static成员变量和成员方法。
非静态内部类编译后会默认的保存一个指向外部类的引用,而静态类却没有。即使没有外部类对象,也可以创建静态内部类对象
1 | public class Outer { |
注意:
- 静态内部类可以有静态字段和方法
- 接口中声明的内部类自动是static和public