slacr_

Just to record my life and thoughts.
笔记/编程/杂乱/极简

[Java笔记]接口&Lambda表达式

May 10, 2023Java3214 words in 21 min

Java 笔记

接口

接口 (interface)定义了一种可以被类层次中任何类实现行为的协议,是常量、抽象方法、默认方法和静态方法的集合。接口可以用来实现多重继承。
接口被看作是一种特殊的类型。与常规类一样,每个接口都被编译为独立的字节码文件。使用接口与使用抽象类相似。接口可以作为引用变量的数据类型或类型转换的结果等。与抽象类一样,不能用 new运算符创建接口的实例.
接口中的抽象方法只有声明,没有实现。抽象方法也可以省略修饰符,省略修饰符编译器自动加上
public abstract
接口通常表示某种能力,因此接口名后缀通常是 able

1
2
3
4
5
6
7
8
9
10
[public] interface InterfaceName [extends SuperInterfaces ]{
//1.常量的定义
//2.抽象方法的定义
//3.静态方法的定义
//4.默认方法的定义
}

[public] class ClassName implements InterfaceList{
//类体定义
}

如果实现接口的类不是 abstract类,则在类的定义部分必须实现接口中的所有抽象方法,即必须保证非 abstract类中不能存在 abstract方法。
接口方法的访问修饰符都是 public, 所以类在实现方法时,必须显式使用 public 修饰符,否则编译器警告缩小了访问控制范围.
定义在接口中的变量都自动加上public、final、static属性,因此它们都是常量,
常量的定义可以省略修饰符,下面三行代码效果相同.
int STATUS = 100;
public int STATUS = 100;
public final static int STATUS = 100;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
interface capable {

void work();
}

interface thinkable {

void desire();
}

class Person implements capable, thinkable {
public String capability, desire;
public void work() {
System.out.println("I am capable of " + capability);
}
public void desire() {
System.out.println("I wanna " + desire);
}
Person (String capability, String desire) {
this.desire = desire;
this.capability = capability;
}
public Person() {}
}

public class Main {
public static void main(String[] args) {
Person slacr = new Person("writing Java", "hava a rest");
slacr.work();
slacr.desire();
}
}

默认方法与接口继承

与类的继承类似,子接口继承父接口中的常量、抽象方法、默认方法, 除了静态方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
interface int01 {
int STATUS = 0;
void fn_1();
default void test() {
System.out.println("default method from int01"); // 默认方法, 可以被子接口和实现类继承, 接口中需要给出实现, 子类无需给出实现
}
}
interface int02 {
default void test() {
System.out.println("default method from int02"); // 默认方法, 接口中需要给出实现, 子类无需给出实现
}
}

interface int03 extends int01, int02 { // 接口可以多继承, 类只能单继承
@Override
default void test() { // 这里如果不覆盖默认方法会报错, 不能确定相同签名的默认方法, C++中就不会,
int01.super.test(); // 委托一个父接口的默认方法
}

}

class int04 {
public void test() {
System.out.println("test in class");
}
}
class int05 extends int04 implements int02 {
public void fn5() { // 同时实现一个接口和继承一个类, 类中有同名方法, 继承的优先级要比接口默认方法高,
test(); // 优先调用继承来的方法.
}
}

class Switch implements int01, int03, int02 {
public void fn_1() {
System.out.println("fn_1");
}
}

public class Main {
public static void main(String[] args) {
Switch s = new Switch();
System.out.println(s.STATUS);
s.fn_1();
s.test();
// 0
//fn_1
//default method from int01
}
}

接口是特殊的抽象类, 接口只能继承接口, 类只能实现接口(某种程度上的继承).

接口类型使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
interface A {
int a = 1;
static void test() {
System.out.println("test static interface method");
}
}
interface B {
int b = 2;

}

interface C extends A,B {
int c = 3;
}

class D implements A, B, C{
int d = 4;
}

public class Main {
public static void main(String[] args) {
A a = new D(); // 上转型对象,和C++中的隐式类型转换差不多
B b = new D();
C c = new D();
D d = new D();
System.out.println(a.a);
System.out.println(b.b);
System.out.println(c.a + " "+ c.b + " " + c.c);
System.out.println(d.a + " " + d.b + " " + d.c + " " + d.d);

A.test(); // 接口中静态方法和普通类中的差不多,只不过接口和抽象类不能实例化,只能类名调用
//1
//2
//1 2 3
//1 2 3 4
//test static interface method
}
}

Comparable接口

Java类库中也定义了许多接口,有些接口中没有定义任何方法,这些接口称为标识接口,如java.lang包中定义的 Cloneable接口、java.io包中的 Serializable接口。有些接口中定义了若干方法,如java.lang包中Comparable接口中定义的comapreTo()方法、AutoClosable接口定义的 close()方法、Runnable 接口中定义的 run()方法.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
class Circle implements  Comparable<Circle>{   // 实现Comparable接口
public double r;
public double area;
public Circle (double r) {
this.r = r;
this.area = Math.PI*r*r;
}
@Override
public int compareTo(Circle c) { // 实现compareTo方法, 使得两个对象能够比较
// return this.area > c.area ? 1 : (this.area == c.area ? 0 : -1 );
return Double.compare(this.area, c.area);
// 或者使用Double类的compare方法比较浮点数
}
}


public class Main {
public static void main(String[] args) {
Circle c1 = new Circle(1);
Circle c2 = new Circle(2);
Circle c11 = new Circle(1);
System.out.println(c1.compareTo(c2)); // -1
System.out.println(c2.compareTo(c1)); // 1
System.out.println(c1.compareTo(c11)); // 0
}
}

JavaAPI中许多类实现了 Comparable接口,如基本数据类型包装类 (Byte、Short、Integer、Long、Float、Double、Character、Boolean)。File类、String类、LocalDate 类、BigInteger类和 BigDecimal类也实现了 Comparable接口,这些类的对象都可按自然顺序排序.

Comparator 接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
class Circle {   // 实现Comparable接口
public double r;
public double area;
public Circle (double r) {
this.r = r;
this.area = Math.PI*r*r;
}
public String toString() {
return String.format( "%.2f", this.area) + " ";
}
}

class CircleComparator implements Comparator<Circle>{ // 实现Comparator接口
public int compare(Circle l, Circle r){
return (int)(r.area - l.area); // 按面积倒序排序
}
}


public class Main {
public static void main(String[] args) {

Circle[] circles = new Circle[5];
for (int i = 0; i < circles.length; i++) {
circles[i] = new Circle(i);
}
// public static <T>void sort(T[] a, Comparator<? super T>c)
Arrays.sort(circles, new CircleComparator()); // Arrays.sort() 传一个Comparator接口实例

for (Circle circle : circles) {
System.out.print(circle); // 50.27 28.27 12.57 3.14 0.00
}
}
}

Lambda表达式

Lambda表达式是Java SE 8 新增的一个语言特征。它将 Java的面向对象编程范式与函数式编程结合起来,可以增强Java在并发编程和事件驱动编程中的优势。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class Main {
public static void main(String[] args) {
// 老版本Java倒序排列字符串数组, 创建匿名接口实现类对象, 实现Comparator接口的compare方法
String[] names = {"andy", "bible", "candy", "david"};
Arrays.sort(names, new Comparator<String>() {
@Override
public int compare(String o1, String o2) {
return o2.compareTo(o1);
}
});
for (String name : names) {
System.out.print(name + " ");
}
System.out.println();
// 使用lambda表达式, js里面也有
String[] names_2 = {"andy", "bible", "candy", "david"};
Arrays.sort(names_2, (String a, String b) -> {return b.compareTo(a);});
for (String name : names_2) {
System.out.print(name + " ");
}
}
}

函数式接口

函数式接口 (function interface)是指仅包含一个抽象方法的接口,因此也称为单抽象方法 (single abstract method,SAM) 接口。每一个Lambda 表达式都对应一个函数式接口类型。可以将 Lambda 表达式看作实现函数式接口的类的一个实例。默认方法不是抽象方法,所以在函数式接口中可以定义默认方法。
可以根据需要定义函数式接口,只要接口只包含一个抽象方法即可。在定义函数式接口时可以给接口添加@FunctionalInterface注解,如果接口定义多于一个的抽象方法,编译器会报错.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
interface Converter<F, T> {     //函数式接口
T convert(F from);
default void convertInfo() {
System.out.println(this.getClass());
}
}

public class Main {
public static void main(String[] args) {
// 用Lambda表达式创建函数式接口对象
Converter<String, Integer> converter = (from -> Integer.valueOf(from)); // 省略{}与return
// 等同于匿名接口实现类的对象
Converter<String, Integer> converter1 = new Converter<String, Integer>() {
@Override
public Integer convert(String from) {
return Integer.valueOf(from);
}
};

Integer converted = converter.convert("382938");
Integer converted1 = converter1.convert("21234321");
System.out.println(converted);
System.out.println(converted1);
converter.convertInfo();
// don't think that much
}
}

在Java API中有些接口就只含有一个抽象方法,如 Runnable 接口、AutoCloseable 接口、Comparable 接口和 Comparator 接口等。此外,java.util.function中包含几十个函数式接口。函数式接口之所以重要是因为可以使用 Lambda 表达式创建一个与匿名内部类等价的对象。

预定义的函数式接口

java.util.function包中定义了大量的函数式接口,它们使编写Lambda表达式变得容易。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
import java.util.Random;
import java.util.function.*;

public class Main {
public static void main(String[] args) {
// public interface Function<T, R> {
// R apply(T argument);
// }

Function<Integer, Double> getPerimeter = (Integer r) -> {return 2*Math.PI*r;};
System.out.println(getPerimeter.apply(2));

BiFunction<Float, Float, Float> calcArea = (w, l) -> w*l;
System.out.println(calcArea.apply(3f, 5f));

// UnaryOperator, Function子接口; BinaryOperator, BiFunction子接口; 参数类型只有一个
UnaryOperator<Integer> getfactorial = (Integer i) -> {
int res = 1;
for (int j = 2; j <= i; j++) {
res*=j;
}
return res;
};
System.out.println(getfactorial.apply(5));

// public interface Predicate<T> {
// boolean test(T t);
// }
Predicate<String> isAllDigital = (String s) -> {
for (int i = 0; i < s.length(); i++) {
if(!Character.isDigit(s.charAt(i))) {
return false;
}
}
return true;
};
System.out.println(isAllDigital.test("12345") + " " + isAllDigital.test("123ad22"));

// public interface Supplier<T> {
// T get();
// }
Supplier<Integer> randomDigit = () -> new Random().nextInt(10);
System.out.println(randomDigit.get());
//Java API 还提供了 Supplier 接口的各种变体,如 DoubleSupplier (返回 Double)、
//IntSupplier以及LongSupplier等。

// public interface Consumer<T> {
// void accept(T t);
// }

Consumer<char[]> printcharArr = (chars -> {
for (int i = 0; i < chars.length; i++) {
System.out.print(chars[i]);
}
});
char[] chars = ("goodbye world").toCharArray();
printcharArr.accept(chars);
//Java API 还提供了 Consumer 接口的各种变体,如 DoubleConsumer (返回
//Double)、IntConsumer 以及LongConsumer等
}
}

方法引用与构造方法引用

Java中有许多方法带一个函数式接口对象作为参数。如果传递的表达式有实现的方法,可以使用一种特殊的语法,方法引用 (method referencing) 代替Lambda 表达式。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public class Main {
public static void main(String[] args) {
ArrayList<Integer> AL = new ArrayList<>(5);
AL.add(1);
AL.add(2);
AL.add(3);
AL.add(4);

AL.forEach(new Consumer<Integer>() {
@Override
public void accept(Integer integer) {
System.out.println(integer+1);
}
});
AL.forEach( i -> System.out.println(i+2));
AL.forEach(System.out::println); // 方法引用

// public static T[] sort(T[] array, Comparator<? super T> comparator)
String arr[] = {"Ali", "Baidu", "Tencent", "ByteDance"};
Arrays.sort(arr, String::compareToIgnoreCase); // 方法引用
for (int i = 0; i < arr.length; i++) {
System.out.print(arr[i] + " ");
}
System.out.println();
}
}

方法引用是类名或对象引用,后跟:: 然后是方法名.
双冒号(:)是Java SE8 引进的一种新运算符,可以引用静态方法、实例方法甚至构造方法。方法引用有以下三种使用方式:
对象::实例方法名
类名::静态方法名
类名::实例方法名

使用第一种方式,在对象上调用实例方法,将给定的参数传递给实例方法,因此,System.out::println 等同于 x->System.out.println(x)
使用第二种方式,用类名调用静态方法,将给定的参数传递给静态方法。例如,java.util.Objects类定义了 isNull()静态方法,调用 Objects.isNull(x)直接返回x=null 的值。使用 list.removeif(Objects::isNull)将从列表中删除所有的 null值。
使用第三种方式,用类名调用实例方法,第一个参数作为方法的调用者,其他参数传递给方法。例如, String::compareToIgnoreCase 等同于(x,y)->x.compareToIgnoreCase(y)

构造方法引用中需要使用 new 运算符。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
class Person {
private String name;
private int age;

public Person(String name, int age) {
this.name = name;
this.age = age;
}

public String getName() {
return name;
}

public int getAge() {
return age;
}
}

public class Main {
public static void main(String[] args) {
// 使用构造方法引用创建Person对象
BiFunction<String, Integer, Person> personFactory = Person::new;
Person person = personFactory.apply("John Doe", 30);

System.out.println("Name: " + person.getName());
System.out.println("Age: " + person.getAge());

}
}

可以使用数组类型编写构造方法引用。例如, int[]::new 是一个含有一个参数的构造方法引用,该参数为数组长度。它等同于 Lambda 表达式 n->new int[n]

  1. 《Java程序设计(第3版)》 IBSN 9787302485520
  2. Java API 文档
  • Author:

    slacr_

  • Copyright:

  • Published:

    May 10, 2023

  • Updated:

    May 10, 2023

Buy me a cup of coffee ☕.

1000000