Java基础4 接口、lambda表达式和内部类
接口
基本概念
接口不是类,是对类的一组需求描述。这些类要遵从接口描述的统一格式来进行定义
接口不含有实例域,实现实例域和方法实现的任务应该由实现接口的那个类来完成
类对接口的实现(注意接口中的方法和常量的访问属性)
首先要 implements 接口名,其次要在类内对接口的所有方法进行定义(实现)
接口中的方法都默认为public abstract,(因为接口中只定义方法,接口也像祖先类一样充当被派生的祖先,所以每个方法在接口中都是抽象的)
在类中实现这些方法时也必须声明方法为public。如果不声明为public,编译器会认为访问属性为default,即类的默认访问属性(default:只能在 同包 的类中调用。protected:可以在 任意包 的 子类 中调用。public:可在 任意包 中调用。private: 只能在自身类中调用。)
接口中常量默认为public static final:接口不能被实例化,没有构造方法,没有实例变量只有静态变量,故选择static表示变量是属于接口的。如果不是static则表示变量属于对象,只有创建对象才会有这个变量。final为保证所有的变量都是不可变的,必须都在接口中赋初值,只有这样接口内定义的域才不会被实现类改变,这个域才有意义。
Java SE8中可有静态方法,这样不必再给每个接口提供伴随类(用来定义静态方法)了
接口特性
- 接口不是类,不能使用new实例化接口
- 可声明接口的变量
- 接口变量必须引用实现了接口的类对象
- 可使用instance检查一个对象是否实现了某个接口
- 接口也可以被拓展public interface Lion extends Animals
- 接口不可包含实例域或者静态方法,但可以包含常量
- 接口中的域被自动设为public static final
抽象类和接口的区别
一个类只能extends一个类,但是却可以implements多个接口 。
C++中一个类可extends多个类,称为多重继承,接口提供多重继承大多好处,又可以避免其复杂和低效性。
接口中的方法
静态方法
Java SE8加入了静态方法, 在jdk1.8中,接口可有static方法,但必须有内容且能自己实现,而不需要被实现。
输出
123
me
print in static method in interface
默认方法
Java SE8中增加了默认方法
default标记的方法,为接口的方法提供了默认实现
-
在Java API中,被default的方法不必在继承类中实现
-
接口演化
为了代码向后兼容,即“源代码兼容”:
如果之前的接口A被B类实现,A类中想新加方法,又想让原来的B类编译通过,就要将A中新方法设为默认方法。默认方法冲突
-
接口和接口命名冲突
一个类L同时继承了两个接口A,B,其中有两个相同名称的方法F
如果至少有一个接口提供了一个默认实现,程序员就必须在类L中提供一个F方法,从A和B的F方法中选择一个
public class Student implements Named,Per{
//覆盖getName方法,而且用public(重写的方法权限要更大)
//这是必须的否则编译出错
public String getName() {
return Per.super.getName();
}
public static void main(String[] args) {
Student a=new Student();
System.out.println(a.getName());//选择Per的方法
}
}
interface Named{
default String getName() {
return "yes";
}
}
interface Per{
default String getName() {
return "no";
}
}
- 接口和类冲突
如果一个类拓展了一个超类,同时实现了一个接口,从超类和接口继承了相同的方法,那么只考虑超类的方法,接口的所有默认方法都被忽略,称为类优先原则
lambda表达式
简述
为何用lambda表达式:
Java的局限,只能用传递对象的方式传递代码块。
lambda的格式:
参数 -> 表达式
如果可从上下文推断出参数的类型,可以忽略不写;参数可有一至多个
public static void Main(string[] args)
{
GuangChaoshi gwl = (p, z) =>
{
int zuidixiaofei = 10;
if (p < zuidixiaofei)
{
return 100;
}
else
{
return z - p - 10;
}
};
System.out.println(gwl(10,100) + ""); //打印80,z对应参数b,p对应参数a
使用lambda的接口——函数式接口
函数式接口只有一个抽象方法,当需要使用这种接口的对象时,可以提供一个lambda表达式。
Arrays.sort(a[10],(first,second)->first.length()-second.length());
//第二个参数是Comparator实例,接口Comparator只有一个抽象方法
//故该函数式接口的实例可换为lambda表达式
Array.sort方法会接收实现了函数式接口的Comparator类的对象。在这个对象上调用lambda表达式。对象和类的管理全由具体的方法实现。这比内联类高效。
最好将lambda表达式看成一个函数。可以传递这个表达式到专门的函数式接口。
与lambda表达式有关的操作——方法引用
方法引用不会独立存在,总会转换为函数式接口的实例。
3种情况:
对象名::具体方法名
类名::静态方法名
类名::具体方法名
前两种等价于lambda表达式
System.out::println(x)等价于x->System.out::println(x)
//System.out是调用了System类的静态数据对象out
Math::pow等价于(x,y)->Math.pow(w,y)
第三种中,第一个参数将会成为方法的对象,后面的参数成为方法的参数
String::compareToIgnoreCase等价于(x,y)->x.compareToIgnoreCase(y)
方法引用中参数也可以为this,super
类似的,构造器引用
方法名为new
Person::new//Person构造函数的一个引用,选择哪个构造器取决于上下文
特殊的:泛型构造
Java无法构造泛型类型T的数组,因为当new T[n]会变为new Object[n]
(T为类名,如Person)
不可以用Person[] people=stream.toArray(new Person[]);
但可用Person[] people=stream.toArray(Person[]::new);来传入Person引用数组
在lambda表达式中的变量
在lambda表达式中只能引用值不改变的参数(最终变量):
如果在lambda表达式中改变变量值,在并发环境下就会不安全,同样的,如果参数在lambda表达式外改变,也不合法。故引用的变量必须从初始化后就不改变值。
局部变量的同名也不能出现。
在使用this时,指的是lambda表达式的方法的this参数。
public class Application() {
public void doWork() {
Runnable runner = () -> {
...;
System.out.println(this.toString());
...
};
}
}
this.toString()调用的是Application对象的toString(),而不是Runnable对象的
使用lambda表达式
lambda表达式的重点是延迟执行,使用的环境如:
多次运行代码
在算法适当位置运行