接口和lambda表达式
接口
几口不是类,是对类的一组需求描述,类负责实现接口。
接口中所有的方法自动属于public
,因此在接口中声明方法时,不需要提供public
. 接口只负责定义方法,不能有实例域。 Java SE 8 前,接口不能实现方法,8及以后可以定义默认方法。
语法
Java中使用interface
关键字定义接口
public intface TestInterface
{
}
类实现接口,使用implements
关键字
Class Test implements TestInterface
{
...
}
接口中方法自动为public
,接口中的域自动设置为public static final
一个类可以实现多个接口,接口可以继承多个接口。
Class Test implements TestInterface,TestInterface2
{
...
}
Comparable 接口
Comparable
是一个泛型接口,实现这个接口要提供泛型参数。
用于两个对象的比较.
Arrays.sort()
中用来比较实现了Comparable
的 类 的数组
Integer
和Double
含有静态方法compare
,用于比较相应类型的两个基本类型数值。
静态方法
Java 8
中,运行在接口中添加静态方法。
但通常的做法是将静态方法放到伴随类中,例如Collection/Collections
,Path/Paths
例如在接口中定义静态方法。
public interface Path {
static java.nio.file.Path get(String first, String... more) {
return FileSystems.getDefault().getPath(first, more);
}
}
默认方法
可以给接口提供一个默认实现,必须用default
修饰符修饰
public interface Path {
static java.nio.file.Path get(String first, String... more) {
return FileSystems.getDefault().getPath(first, more);
}
default java.nio.file.Path get(String path) {
return get(path, null);
}
}
默认方法冲突
如果接口中将一个方法定义为默认方法,又在超类或另一个类定义了同样的方法,Java根据以下规程处理:
超类优先,如果超类提供了一个具体方法,接口中的默认方法会被忽略。
接口冲突,如果多个超接口提供了同名同参的方法,则程序员必须手动覆盖这个方法解决这个冲突
interface Named{ default String getName(){ return getClass.getName(); } } interface Person{ default String getName(){ return getClass.getName(); } } class Student implements Person,Named{ ... }
Student必须重新实现getName()
方法,如果要调用Named
接口的默认方法
public class Student implements Named, Person {
@Override
public String getName() {
return Named.super.getName();
}
}
不要在接口中定义
Object
类中方法的默认方法,例如不能为toString``equals
定义默认方法,由于类优先规则,这类方法无法超越Object.toString()
或Object.equals
接口实例
回调接口
回调(callback)是一种程序设计默认,在某个特定事件发生时调用我们指定的动作。例如按下鼠标,或接受到某个事件系统应该做什么。
java.swing
中有一个Timer
类,它可以在到达指定时间间隔后发出通知,并调用我们传入的回调方法。
start
方法开启。
public class TestTimer {
public static void main(String[] args) {
Timer timer = new Timer(1000, e -> {
System.out.printf("%1$ta %1$tb %1$td %1$tT %1$tZ %1$tY \n", new Date());
});
timer.start();
JOptionPane.showMessageDialog(null, "Quit program");
System.exit(0);
}
}
Comparator
String 类实现了Comparable<String>
接口,实现是根据字典顺序排序。如果我们要使用自定义排序方式,不可能继承String
,我们可以使用Arrays.sort
的另一个版本,一个数组和一个比较器compartor
public interface Comparator<T> {
int compare(T o1, T o2);
}
实例
package top.himcs.basic.inter;
import java.util.Arrays;
import java.util.Comparator;
public class TestComparator {
public static void main(String[] args) {
Comparator<String> stringComparable = new Comparator<String>() {
@Override
public int compare(String o1, String o2) {
return o1.length() - o2.length();
}
};
String s1 = "33123";
String s2 = "1333";
String[] ss = new String[]{s1, s2};
Arrays.sort(ss, stringComparable);
System.out.println(Arrays.toString(ss));
}
}
对象克隆
clone
是Object
的procted
方法,默认的克隆方式是浅拷贝,即不会克隆对象中的引用对象,只会克隆基本类型。
克隆前三问?
默认的
clone
方法是否满足要求是否在子对象上使用克隆
是否不应该使用克隆
优先考虑不使用克隆,如果使用克隆,类必须:
实现
Cloneable
接口重新定义
clone
方法
Cloneable 接口是Java 提供的一组标记接口( tagging interface) 之一。(有些程序员称之为记号接口( marker interface))。应该记得,Comparable 等接口的通常用途是确保一个类实现一个或一组特定的方法。标记接口不包含任何方法; 它唯一的作用就是允许在类型查询中使用
instanceof
:if(obj instanceof Cloneable)...
数组也可使使用clone()
规则与类克隆一直,基本类型是值克隆,互不影响,类还是会引用同一份对象。
public static void main(String[] args) {
String[] strings = new String[]{"a", "b", "c"};
String[] strings2 = strings.clone();
strings[0] = "aa";
System.out.println(Arrays.toString(strings2));
System.out.println(strings);
System.out.println(strings[1].hashCode());
System.out.println(strings2[1].hashCode());
Employee[] employees = new Employee[]{ new Employee()};
Employee[] employees2 = employees.clone();
System.out.println(employees[0].hashCode());
System.out.println(employees2[0].hashCode());
System.out.println(employees[0] == employees2[0]);
}
lambda 表达式
Java 8
引入,用简洁的语法定义一个函数接口。
与javascript
中的箭头函数类似()=>{}
语法
Java
中 用->
表示lamdba
表达式
(String frist,String second)->{
return first+second;
}
如果没有参数,要提供一个空括号
()->{return ''}
如果可以推到出参数类型,类型参数也可以省略
(first,second)->{
return first+second;
}
如果只有一个参数,小括号也可以省略
first->{}
如果表达式函数只有一行,可以无需写return
也可以不加括号
(first,second)->first+second;
函数式接口
对于只有一个抽象方法的接口,当需要这种接口的对象时,可以提供太一个lambda
表达式。这种接口成为函数式接口functional interface
.
接口可以提供非抽象方法,例如提供默认方法,静态方法,或者重新声明
Object
类对象的方法,都不会影响唯一的抽象方法,这个接口依然是函数式接口。
例如Compartor
接口是一个函数式接口
可以如下调用:
Arrays.sort(employees, (employee1, employee2) -> {
return employee1.hashCode() - employee2.hashCode();
});
Java会将声明的lambda
表达式转换为实际的函数接口。
直接声明一个函数式接口
Comparator comparator = (Comparator<String>) (String s1, String s2) -> {
return s1.length() - s2.length();
};
方法引用
将一个方法传递给需要所需函数式接口的地方,如下所示。
语法
Timer timer1 = new Timer(1000, System.out::println);
timer1.start();
等价于
event->{System.out.println(event)}
使用::
操作符有以下三种情况:
- object::instanceMethod
- Class::staticMethod
- Class::instanceMethod
前两种情况,等价于提供方法参数的lambda
,即将参数传入方法。 例如System.out::println
等价于x->System.out.println(x)
Math::pow
等价于(x,y)->Math.pow(x,y)
第三种情况下,第一个参数会成为方法的调用者。例如String::compareToIgnoreCase
等价于(x,y)->x.compareToIgnoreCase(y)
如果有多个同名的重载方法,编译器会尝试从上下文中寻找需要的方法。
类似于
lambda
表达式,方法引用不能独立存在,总是会转化为函数式接口的实例。
可以在方法中使用this
参数,例如this::equals
等价于x->this.equals(x)
,使用super
也是合法的。
例如super::toString
package top.himcs.basic.inter;
import java.util.Arrays;
public class TestLambda {
public static void main(String[] args) {
TestLambda.test(TestLambda::printV2, new Object[]{"Hello", "World"});
}
public static void test(test t, Object... objects) {
t.print(objects);
}
public static void printV2(Object... objects) {
System.out.println(Arrays.toString(objects));
}
@FunctionalInterface
interface test {
void print(Object... objects);
}
}
存储函数表达式的接口
java.util.function
下定义了很多通用的函数式接口。
例如BiFunction<T, U, R>
接受参数类型T
和U
,返回类型为R
的函数,可以将我们的函数保存到这中类型的变量中。
BiFunction<String, String, Integer> function = (s1, s2) -> {
return s1.length() - s2.length();
};
Predicate<T>
是一个保存参数类型为T
,返回结果为boolean
的j函数接口,可以用来测试
例如
Predicate<String> predicate = s -> {
return s.isEmpty();
};
ArrayList
有个removeIf
方法,参数为一个Predicate
。
常用函数式表达式接口
接口 | 参数 | 返回 | 方法名 | 描述 |
---|---|---|---|---|
Runnable | void | run | 作为无参数或返回值的动作运行 | |
Supplier<T> | T | get | 返回一个T类型的值 | |
Consumer<T> | T | void | accept | 处理一个T类型的值 |
BiConsumer<T, U> | T, U | void | accept | 处理T和U类型 |
BiFunction<T, U, R> | T,U | R | apply | 处理T和U类型,返回一个R类型 |
Function<T, R> | T | R | apply | 处理T类型,返回一个R类型 |
UnaryOperator<T> extends Function<T, T> | T | T | apply | T类型的一元操作 |
BinaryOperator<T> extends BiFunction<T,T,T> | T,T | T | apply | T类型的二元操作 |
Predicate<T> | T | boolean | test | 测试T的布尔值 |
BiPredicate<T, U> | T,U | boolean | test | 两个参数的布尔值 |
由于泛型只能为Object
引用类型,基础类型要最好用专门的标准接口,这样可以减少装箱操作提高性能,例如处理int
接口
@FunctionalInterface
public interface IntConsumer {
void accept(int value);
}
如果设计你自己的接口,其中只有一个抽象方法, 可以用
@FunctionalInterface
注解来标记这个接口。这样做有两个优点。
如果你无意中增加了另一个非抽象方法, 编译器会产生一个错误消息。
另外javadoc 页里会指出你的接口是一个函数式接口。 并不是必须使用注解根据定义, 任何有一个抽象方法的接口都是函数式接口。不过使用
@FunctionalInterface
注解确实是一个很好的做法。
构造器引用
构造器引用与方法引用类似,方法名为new
例如Person::new
是Person
构造器的一个引用,具体调用哪个构造器,取决于上下文.
例如
ArrayList<String> names = ...;
Stream<Person> stream == names.streams().map(Person::new);
List<Person> people = stream.collect(Collectors.toList());
数组也可以建立构造器引用,例如int[]::new
,有一个参数,即数组的长度
等价于x->new int[x]
Java有一个验证,无法构造泛型T
的数组,表达式new T[n]
会报错,并会改为new Object[n]
.
获取一个Person
引用数组
Person[] people = stream.toArray(Person[]::new)
下面这种方式不可以:
T[] ts = Arrays.asList(t1).stream().toArray(T[]::new);
只能通过数组反射来构造泛型数组。
如下
public static <T> T[] get(Class<T> type,int length) {
T[] ts = (T[])Array.newInstance(type, length);
return ts;
}
变量作用域
在lamdba
表达式中可以直接访问外部的变量。
可以理解为lamdba
表达式转换为包含方法的一个对象,外部变量的值会复制到这个对象的实例变量中。
在lambda
中,只能引用值不会改变的变量。
lamdba
中变量名不能和外部已有变量名的突。
在lamdba
中的this
是值创建这个lamdba
表达式的外部对象。
例如
public class Appliction(){
public void int(){
ActionListener listener = event -> {
System.out.println(this.toString());
}
}
}
这里的this.toString()
会调用Appliction
对象的toString
方法。
处理lambda表达式
lambda
的精髓在于延迟执行deferred exection
,如果要立即执行,可以直接执行,无需包装在lamdba
表达式中。
可能要用到lamdba
的原因:
- 单独线程执行
- 多次运行
- 算法的适当位置(例如排序中的比较操作)
- 事件回调(按钮点击,事件触发)
- 必要时运行的代码