java-接口、lambda表达式
接口(interface)
接口是一个不含任何成员变量的抽象类
java8
中接口中所有方法必须为public
(实现该接口时,重写的方法必须声明为public)- 接口中不能定义成员变量,但是可以定义为一个常量(类型为
public static final
) - 不能用
new
来实例化一个接口,但是允许定义一个接口变量 - 接口变量必须引用实现了该接口的类对象
- 同一个类可以同时实现多个接口
- 接口可以定义
default
方法,需要在方法名前添加default
关键字,该方法提供了一个默认的实现,实现该接口时可以按需重写该方法 - 接口中的默认方法可以调用其他任意方法
java8
之后允许接口定义静态方法- 接口中的方法不能使用
final
关键字修饰 - 当Java抽象类实现某个接口后没必要实现所有的方法。普通类必须实现所有抽象方法。
public class InterfaceTest implements Test{
public static void main(String[] args) {
InterfaceTest test = new InterfaceTest();
// 调用接口的默认方法
test.getMessage();
System.out.println(InterfaceTest.info);
// 调用接口的静态方法
System.out.println(Test.staticMethod());
}
@Override
public void getHint() {
System.out.println("call getHint");
}
}
interface Test {
// 常量
String info = "test interface";
// 默认方法
default void getMessage() {
getHint();
System.out.println("test message");
}
// 一般方法
void getHint();
// 静态方法
static String staticMethod() {
return "this is static method";
}
}
默认方法冲突
1.超类优先:如果超类提供了一个具体方法, 同名而且有相同参数类型的默认方法会被忽略
假设Test
接口的默认方法getMessage
和Test1
类的方法getMessage
重名
只实现Test
接口时不需要重写默认方法getMessage
public class InterfaceTest implements Test{
public static void main(String[] args) {
InterfaceTest test = new InterfaceTest();
// 调用接口的默认方法
test.getMessage();
System.out.println(InterfaceTest.info);
// 调用接口的静态方法
System.out.println(Test.staticMethod());
System.out.println();
}
@Override
public void getHint() {
}
}
interface Test {
// 常量
String info = "test interface";
// 默认方法
default void getMessage() {
getHint();
System.out.println("Test: message");
}
// 一般方法
void getHint();
static String staticMethod() {
return "Test: this is static method";
}
}
class Test1 {
void getMessage() {
System.out.println("Test1: message");
}
}
如果InterfaceTest
同时继承了Test1
类并实现了Test
接口,那么从Test
接口的默认方法会被忽略,必须进行重写
public class InterfaceTest extends Test1 implements Test{
public static void main(String[] args) {
InterfaceTest test = new InterfaceTest();
// 调用接口的默认方法
test.getMessage();
System.out.println(InterfaceTest.info);
// 调用接口的静态方法
System.out.println(Test.staticMethod());
System.out.println();
}
@Override
public void getMessage() {
Test.super.getMessage();
}
@Override
public void getHint() {
}
}
interface Test {
// 常量
String info = "test interface";
// 默认方法
default void getMessage() {
getHint();
System.out.println("Test: message");
}
// 一般方法
void getHint();
static String staticMethod() {
return "Test: this is static method";
}
}
class Test1 {
void getMessage() {
System.out.println("Test1: message");
}
}
2.接口冲突:如果一个类同时实现了多个接口,并且这些接口中包含相同的默认方法
实现类必须重写重名方法,手动选择使用某个接口的默认方法
这里假设同时实现了Test
和Test1
接口,两个接口都有getMessage()
public class InterfaceTest implements Test, Test1 {
public static void main(String[] args) {
InterfaceTest test = new InterfaceTest();
// 调用接口的默认方法
test.getMessage();
}
@Override
public void getMessage() {
// 使用Test1的getMessage
// Test1.super.getMessage();
// 或者使用Test的getMessage
Test.super.getMessage();
}
@Override
public void getHint() {
}
}
interface Test {
// 常量
String info = "test interface";
// 默认方法
default void getMessage() {
getHint();
System.out.println("Test: message");
}
// 一般方法
void getHint();
static String staticMethod() {
return "Test: this is static method";
}
}
interface Test1 {
default void getMessage() {
System.out.println("Test1: message");
}
}
有这样一种情况,此时Test1
接口的getMessage
不是默认方法,InterfaceTest
并不会默认使用Test
的默认方法。必须重写getMessage
方法(可以指定使用 Test 的默认方法)
也就是说: 如果至少有一个接口提供了一个同名的默认实现, 编译器就会报告错误, 而程序员就必须解决这个二义性。
public class InterfaceTest implements Test, Test1 {
public static void main(String[] args) {
InterfaceTest test = new InterfaceTest();
// 调用接口的默认方法
test.getMessage();
}
@Override
public void getMessage() {
Test.super.getMessage();
}
@Override
public void getHint() {
}
}
interface Test {
// 常量
String info = "test interface";
// 默认方法
default void getMessage() {
getHint();
System.out.println("Test: message");
}
// 一般方法
void getHint();
static String staticMethod() {
return "Test: this is static method";
}
}
interface Test1 {
void getMessage();
}
Comparator 接口
String
类实现了Comparable<String>
接口,其中的String.compareTo()
实现了按字典序排序
需求: 将字符串按照长度从小到大排序,如果长度相等,则按字典序排序
这里需要重新实现comparator
接口的compare
方法
public class InterfaceTest {
public static void main(String[] args) {
String[] strs = {"Happy", "new", "year"};
Comparator<String> cmp = new LengthComparator();
for (int i = 0; i < strs.length - 1; i++) {
if (cmp.compare(strs[i],strs[i+1]) > 0) {
String tmp = strs[i];
strs[i]=strs[i+1];
strs[i+1] = tmp;
}
}
System.out.println(Arrays.toString(strs));
}
}
class LengthComparator implements Comparator<String> {
// 返回正值的时候说明需要交换位置
@Override
public int compare(String o1, String o2) {
// 长度相等,按字典序排序
if (o1.length() == o2.length()) {
return o1.compareTo(o2);
}
// 按长度从小到大排序
return o1.length() - o2.length();
}
}
简写方式:
import java.util.Arrays;
import java.util.Comparator;
public class InterfaceTest {
public static void main(String[] args) {
String[] strs = {"Happy", "new", "year"};
Arrays.sort(strs, new Comparator<String>() {
@Override
public int compare(String o1, String o2) {
if (o1.length() == o2.length()) {
return o1.compareTo(o2);
}
return o1.length() - o2.length();
}
});
System.out.println(Arrays.toString(strs));
}
}
Cloneable 接口
Object.clone()
默认的克隆操作是浅拷贝
,并没有克隆对象中引用的其他对象。
浅拷贝会有什么影响吗?如果原对象和浅克隆对象共享的子对象是不可变的(如 String 类),那么这种共享就是安全的。或者在对象的生命期中,子对象一直包含不变的常量,没有更改器方法会改变它,也没有方法会生成它的引用,这种情况下同样是安全的。
下面的图给出了一个浅拷贝的例子,Date
是一个可变类
clone
方法来实现对象的深拷贝要使用Object.clone()
方法(浅拷贝)或者重写来实现深拷贝方法,都必须完成下面的两步:
- 让所在类实现
Cloneable
接口 - 将
clone()
方法的权限改为public
(这样这个方法在所有类中都可以调用)
public class Main {
public static void main(String[]args) throws CloneNotSupportedException {
Test t = new Test();
Test other = (Test) t.clone();
System.out.println(t.hashCode());
System.out.println(other.hashCode());
}
}
class Test implements Cloneable{
@Override
public Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
让图6.2
的实现深拷贝
package CloneTest;
import java.util.Date;
import java.util.GregorianCalendar;
public class CloneTest
{
public static void main(String[] args)
{
try
{
Employee original = new Employee("John Q. Public", 50000);
original.setHireDay(2000, 1, 1);
Employee copy = original.clone();
copy.raiseSalary(100);
copy.setHireDay(2022, 11, 17);
System.out.println("original=" + original);
System.out.println("copy=" + copy);
}
catch (CloneNotSupportedException e)
{
e.printStackTrace();
}
}
}
class Employee implements Cloneable
{
private String name;
private double salary;
private Date hireDay;
public Employee(String n, double s)
{
name = n;
salary = s;
hireDay = new Date();
}
public Employee clone() throws CloneNotSupportedException
{
// 先调用一次浅拷贝
// Object.clone()返回的是Object对象
// call Object.clone()
Employee cloned = (Employee) super.clone();
// 对可变对象进行克隆
// clone mutable fields
cloned.hireDay = (Date) hireDay.clone();
return cloned;
}
public void setHireDay(int year, int month, int day)
{
Date newHireDay = new GregorianCalendar(year, month - 1, day).getTime();
// Example of instance field mutation
hireDay.setTime(newHireDay.getTime());
}
public void raiseSalary(double byPercent)
{
double raise = salary * byPercent / 100;
salary += raise;
}
public String toString()
{
return "Employee[name=" + name + ",salary=" + salary + ",hireDay=" + hireDay + "]";
}
}
Object 类的 clone()方法为什么是protected
修饰的
用protected
修饰clone
方法,主要是为了让子类去重写它,实现深拷贝,以防在其他任何地方随意调用后修改了对象的属性对原来的对象造成影响。
lambda 表达式
lambda
表达式形式:(参数)->
一个表达式或{代码块}
- 无需指定表达式的返回类型,可通过上下文进行推断
- 代码块需要显式的指定返回值
- 即使没有参数也要提供一个空括号
()
- 如果可以推导出
lambda
表达式的参数类型,可以忽略其类型
package LambdaTest;
import java.util.*;
public class LambdaTest {
public static void main(String[] args) {
Runnable hello = () -> {
System.out.println("hello");
};
hello.run();
String[] strs = new String[] {"Hello","Year","New"};
// 按字符串长度升序排序
Comparator<String> stringComparator = (first, second) -> {
if (first.length() < second.length()) return -1;
else if (first.length() > second.length()) return 1;
else return 0;
};
Arrays.sort(strs,stringComparator);
System.out.println(Arrays.toString(strs));
// 按字符串长度降序排序
Comparator<String> stringComparator1 = (first,second) -> second.length() - first.length();
Arrays.sort(strs,stringComparator1);
System.out.println(Arrays.toString(strs));
}
}
函数式接口
- 函数式接口只有一个抽象方法
- 接口重写了 Object 的公共方法也不算入内
Comparator
就是一个函数式接口,接口之前有@FunctionalInterface
注解最好把 lambda 表达式看作是一个函数而不是一个对象,另外lambda
表达式可以传递到函数式接口。
Object
不是一个函数式接口,不能将lambda表达式
赋给类型为Object
变量Predicate 接口
Predicate
函数式接口的主要作用就是提供一个test
方法,接受一个参数返回一个布尔类型,Predicate
接口在stream api
中进行一些判断的时候非常常用。
方法引用(method refrence)
在 Java 8 中,我们会使用lambda
表达式创建匿名方法,但是有时候,我们的lambda
表达式可能仅仅调用一个已存在的方法,而不做任何其它事,对于这种情况,通过一个方法名字来引用这个已存在的方法会更加清晰,Java 8 的方法引用允许我们这样做。方法引用是一个更加紧凑,易读的lambda
表达式,注意方法引用是一个lambda
表达式,其中方法引用的操作符是双冒号"::"
。
lamda 表达式的代码可以改写为方法引用的形式
public class LambdaTest {
public static void main(String[] args) {
Runnable hello = () -> {
System.out.println("hello");
};
hello.run();
String[] strs = new String[] {"Hello","Year","New"};
// 按字符串长度升序排序
Arrays.sort(strs, LambdaTest::compare2);
System.out.println(Arrays.toString(strs));
// 按字符串长度降序排序
Arrays.sort(strs, LambdaTest::compare);
System.out.println(Arrays.toString(strs));
}
private static int compare(String first, String second) {
return second.length() - first.length();
}
private static int compare2(String first, String second) {
if (first.length() < second.length()) return -1;
else if (first.length() > second.length()) return 1;
else return 0;
}
方法引用主要有以下三种形式:
object::instanceMethod
对象 + 实例方法Class::staticMethod
类名+ 静态方法Class::instanceMethod
类名 + 实例方法
// 第一种
System.out::println // 等价于x -> System.out.println(x)
// 第二种
Math::pow // 等价于(x,y)-> Math.pow(x,y)
// 第三种,第一个参数将作为object
String::compareToIgnoreCase // 等价于(x, y) -> x.compareToIgnoreCase(y)
对于第三种方法,显然不能用类调用实例方法,这样编译都会报错!
当一个对象调用一个方法,方法的参数中包含一个函数式接口,该函数式接口的第一个参数类型是这个对象的类,那么这个函数式接口可用方法引用代替,并且替换用的方法可以不包含函数式接口的第一个参数
@FunctionalInterface
interface TestInterface<T> {
String handleString(T a, String b);
}
class TestClass {
private final String oneString;
TestClass(String oneString) {
this.oneString = oneString;
}
public String concatString(String a) {
return this.oneString + a;
}
public String startHandleString(TestInterface<TestClass> testInterface, String str) {
return testInterface.handleString(this, str);
}
}
public class Test {
public static void main(String[] args) {
TestClass testClass = new TestClass("abc");
String result = testClass.startHandleString(TestClass::concatString, "123");
System.out.println(result);
//相当于以下效果
TestClass testClass2 = new TestClass("abc");
TestInterface testInterface = (a, b) -> testClass2.concatString(b);
String result2 = testInterface.handleString(testClass2, "123");
System.out.println(result2);
}
}
构造方法引用
1.构造器引用
语法形式:className::new
构造函数本质上是静态方法,只是方法名字比较特殊,使用的是new 关键字
public class ConstructorRefDemo {
public static void main(String[] args) {
// 利用MyClass的构造方法实现了MyFunc接口
MyInterface myInterface = MyClass :: new;
MyClass mc = myInterface.func(100);
System.out.println("val in mc is: " + mc.getValue());
}
}
interface MyInterface {
MyClass func(int n);
}
class MyClass {
private final int val;
MyClass(int v) {
val = v;
}
public int getValue() {
return val;
}
}
还有一个更难理解的例子
package interfaceTest;
import java.util.Arrays;
import java.util.List;
@FunctionalInterface
interface Supplier<T>
{
T get();
}
class Car
{
public static Car create(final Supplier<Car> supplier)
{
return supplier.get();
}
public static void collide(final Car car)
{
System.out.println("静态方法形式调用 " + car.toString());
}
public void follow(final Car another)
{
System.out.println("对象方法形式调用 " + another.toString());
}
public void repair()
{
System.out.println("任意对象方法引用 " + this.toString());
}
@Override
public String toString()
{
return "just a Car " + this.hashCode();
}
}
public class CRacer
{
public static void main(String[] args)
{
//构造器引用:它的语法是Class::new
Car car0 = Car.create(Car::new);
Car car1 = Car.create(Car::new);
System.out.println(car0.hashCode());
System.out.println(car1.hashCode());
final List<Car> cars = Arrays.asList(car0,car1);
//静态方法引用:Class::static_method
cars.forEach(Car::collide);
//特定类的任意对象的方法引用Class::method
cars.forEach(Car::repair);
cars.forEach(System.out::println);
final Car police = Car.create(Car::new);
System.out.println(police.hashCode());
cars.forEach(police::follow);
}
}
2.构造数组引用
语法形式:Typename[]::new
import java.util.Arrays;
import java.util.function.Function;
public class InterfaceTest {
public static void main(String[] args) {
Function<Integer, String[]> func1 = length -> new String[length];
String[] arr1 = func1.apply(5);
for (int i = 0; i < arr1.length; i++) {
arr1[i] = "arr1_" + i;
}
System.out.println(Arrays.toString(arr1));
Function<Integer, String[]> func2 = String[]::new;
String[] arr2 = func2.apply(10);
for (int i = 0; i < arr2.length; i++) {
arr2[i] = "arr2 _" + i;
}
System.out.println(Arrays.toString(arr2));
}
}
自由变量和 this 关键字
自由变量的值,这是指非参数而且不在代码中定义的变量。
package interfaceTest;
import javax.swing.*;
import java.awt.event.ActionListener;
public class InterfaceTest {
public static void main(String[] args) throws InterruptedException {
countDown("Hello",1000);
Thread.sleep(2000);
System.exit(0);
}
public static void countDown(String message,int delay) {
ActionListener listener = event -> {
System.out.println(message);
};
new Timer(delay,listener).start();
}
}
比如countDown
中的message
就是自由变量,也可以说message
被lambda
表达式捕获的。
注意:lambda
表达式中只能引用值不会改变的变量。因为在lambda
表达式中改变变量,并发执行多个操作时可能会不安全
lambda
表达式捕获的变量也称为最终变量,也就是该变量在初始化后不会被赋新值。
在一个lambda
表达式中使用this
关键字时,是指创建这个lambda
表达式的方法的this
参数。this
关键字的含义不会随lambda
表达式而改变。
import javax.swing.*;
import java.awt.event.ActionListener;
public class InterfaceTest {
public static void main(String[] args) throws InterruptedException {
new Application().init();
Thread.sleep(2000);
System.exit(0);
}
}
class Application {
public void init() {
ActionListener listener = e -> {
// 调用的是Application的toString()方法
System.out.println(this.toString());
};
new Timer(1000,listener).start();
}
@Override
public String toString() {
return "hello";
}
}
Runnable 接口和 IntConsumer 接口
Runnable
: 无参数,返回值类型为void
,抽象方法名为run
IntConsumer
: 接受参数类型为int
,返回值类型为void
,抽象方法名为accept
import java.util.function.IntConsumer;
public class InterfaceTest {
public static void main(String[] args) {
repeat(10,() -> System.out.println("hello"));
repeatAndNumber(10,i -> System.out.println("hello_" + i));
}
public static void repeat(int n, Runnable runnable) {
for (int i = 0; i < n; i++) {
runnable.run();
}
}
public static void repeatAndNumber(int n, IntConsumer consumer) {
for (int i = 0; i < n; i++) {
consumer.accept(i);
}
}
}