【Java 进阶】匿名类(代码传递、回调、过滤器)、Lambda表达式(方法引用)、函数式接口(Supplier、Consumer、Predicate、Function)

Java笔记目录可以点这里:Java 强化笔记

匿名类(Anonymous Class)

  • 当接口、抽象类的实现类,在整个项目中只用过一次,可以考虑使用匿名类;

例如下面这个例子:

public interface Eatable {
	String name();
	int energy();
}
public class Person {
	public void eat(Eatable a) {
		System.out.println("eat - " + a.name() + " -" + a.energy());
	}
}
public static void main(String[] args) {
	Person person = new Person();
	person.eat(new Eatable() { // 匿名内部类
		@Override
		public String name() {
			return "Apple";
		}
		@Override
		public int energy() {
			return 100;
		}
	}); // eat - Apple -100
	
	Eatable beef = new Eatable() {
		@Override
		public String name() {
			return "Beef";
		}
		@Override
		public int energy() {
			return 200;
		}
	};
	person.eat(beef); // eat - beef -200
	
}

匿名类的使用注意

匿名类不能定义除 编译时常量(static int final a = 1) 以外的任何 static 成员;
在这里插入图片描述 在这里插入图片描述

匿名类只能访问 final 或者 有效final的局部变量
在这里插入图片描述 在这里插入图片描述
匿名类可以直接访问外部类中的所有成员(即使被声明为 private)

匿名类只有在实例相关的代码块中使用,才能直接访问外部类中的实例成员(实例变量、实例方法)

匿名类不能自定义构造方法,但可以有初始化块
在这里插入图片描述 在这里插入图片描述
匿名类的常见用途:

  • 代码传递
  • 过滤器
  • 回调

匿名类 - 代码传递 - 测试代码运行时间的工具类

public class Times {
	public interface Block {
		void execute();
	}
	
	public static void test(Block block) {
		long begin = System.currentTimeMillis();
		block.execute();
		long end = System.currentTimeMillis();
		long duration = end - begin;
		System.out.println("花费时间" + duration + "ms");
	}
	
}

插一句,字符串拼接其实相当耗时间,具体可见:【Java 基础】字符串(String、StringBuilder)

public static void main(String[] args) {
	Times.test(new Block() {
		@Override
		public void execute() {
			String str = "";
			for (int i = 0; i < 10000; i++) {
				// 字符串拼接极其耗时间, 因为每次都相当于创建新的对象
				str += i;
			}
		}
	});
}

匿名类 - 回调 - 简易网络请求

下面模拟一个简易网络请求代码来体现回调的过程:具体看 main 里的注释。

public class Networks {
	
	public interface Block {
		void success(Object response);
		void failure();
	}
	
	public static void get(String url, Block callBack) {
		// 1. 根据url发送一个异步请求(开启一条子线程)
		// ......
		// 2. 请求完毕后
		boolean result = url.contains("666") ? true : false;
		if (result) {
			Object response = null;
			callBack.success(response);
		} else {
			callBack.failure();
		}
	}
	
	public static void main(String[] args) {
		// 传入的网络请求只有满足某些条件时会返回成功, 其他情况会失败
		// 但是发送请求时将成功要做的事的和失败要做的事都写好了
		// 等到判断条件是否满足以后再回来调用, 这就是回调
		Networks.get("http://xxx.com?pwd=666", new Block() {
			@Override
			public void success(Object response) {
				System.out.println("请求成功");
			}
			@Override
			public void failure() {
				System.out.println("请求失败");
			}
		});
	}	
	
}

匿名类 - 过滤器 - 获取目录下的所有文件

通过一个获取目录下所有文件的小案例,体会下匿名类用作过滤器的遍历。

public class Files {
	public interface Filter {
		boolean accpet(String filename);
	}
	
	// 需要传入一个Filter过滤器, 用来自定义要返回满足哪些条件的文件名
	public static String[] getAllFileNames(String dir, Filter filter) {
	
		// 1.先获取 dir 文件夹下的所有文件名
		String[] allFilesnames = {};
		// 2.进行过滤
		for (String filename : allFilesnames) {
			if (filter.accpet(filename)) {
				// 将满足条件的文件名装起来
			}
		}
		// 返回所有装起来的文件名(满足条件的文件名)
		return null;
	}
	
	public static void main(String[] args) {
		// 获取F盘下所有.java文件
		Files.getAllFileNames("F:", new Filter() {
			@Override
			public boolean accpet(String filename) {
				if (filename.endsWith(".java")) return true;
				return false;
			}
		});
		// 获取F盘下所有名字中带hello的文件
		Files.getAllFileNames("F:", new Filter() {
			@Override
			public boolean accpet(String filename) {
				if (filename.contains("hello")) return true;
				return false;
			}
		});
	}

}

排序 Arrays.sort()

可以使用 JDK 自带的 java.util.Arrays 类对数组进行排序;

  • Arrays.sort 默认是升序排列,可以通过比较器 Comparator 改变次序;
Integer[] array = { 33, 22, 11, 77, 66, 99 };
Arrays.sort(args);
System.out.println(Arrays.toString(array));
// [33, 22, 11, 77, 66, 99]

Arrays.sort(array, new Comparator<Integer>() {
	@Override
	public int compare(Integer o1, Integer o2) {
		return o1 - o2;
	}
});

System.out.println(Arrays.toString(array));
// [11, 22, 33, 66, 77, 99]

Lambda 表达式(Lambda Expression)

  • Lambda 表达式是 Java 8 开始才有的语法

  • 函数式接口(Functional Interface):只包含 1 个抽象方法的接口
    可以在接口上面加上 @FunctionalInterface 注解,表示它是一个函数式接口

  • 当匿名类实现的是函数式接口时,可以使用 Lambda 表达式进行简化

@FunctionalInterface
public interface Testable {
	void test(int v);
}

一些注意点:

  • 参数列表可以省略参数类型;
  • 当只有一条语句时,可以省略大括号、分号、return
  • 当只有一个参数时:可以省略小括号
  • 没有参数时:不能省略小括号

Lambda 表达式示例:

public interface Caculator {
	int caculate(int v1, int v2);
}

static void execute(int v1, int v2, Caculator c) {
	System.out.println(c.caculate(v1, v2));
}

public static void main(String[] args) {	
	execute(10, 20, (int v1, int v2) -> {
		return v1 + v2; // 20
	});
	execute(11, 22, (v1, v2) -> v1 + v2); // 20
}

Lambda 的使用注意、匿名类 vs Lambda

  • Lambda 只能访问 final 或者 有效 final 的局部变量
  • Lambda 没有引入新的作用域
public class OuterClass {
	
	@FunctionalInterface
	public interface Testable {
		void test(int v);
	}
	
	private int age = 1;
	
	public class InnerClass {
		private int age = 2;
		void inner() {
			// int v = 4; // error, Lambda 没有引入新的作用域, 重复定义变量
			
			Testable t1 = v -> {
				System.out.println(v); // 3
				System.out.println(age); // 2
				System.out.println(this.age); // 2
				System.out.println(InnerClass.this.age); // 2
				System.out.println(OuterClass.this.age); // 1
			};
			t1.test(3);
			
			Testable t2 = new Testable() {
				@Override
				public void test(int v) {
					System.out.println(v); // 3
					System.out.println(age); // 2
					// System.out.println(this.age); // error
					System.out.println(InnerClass.this.age); // 2
					System.out.println(OuterClass.this.age); // 1
				}
			};
			t2.test(3);
			
		}
	}
	
	public static void main(String[] args) {
		new OuterClass().new InnerClass().inner();
	}
	
}

方法引用(Method Reference)

我觉得这一部分了解一下即可,见到认识就行,一般不太会这么写。。。。

如果 Lambda 中的内容仅仅是调用某个方法,可以使用方法引用(Method Reference)来简化;
在这里插入图片描述
引用静态方法

@FunctionalInterface
public interface Testable {
	int test(int v1, int v2);
}

public static void main(String[] args) {
	Testable t1 = (v1, v2) ->Math.max(v1, v2); 
	System.out.println(t1.test(10, 20)); // 20
	
	Testable t2 = Math::max; // 引用静态方法
	System.out.println(t2.test(10, 20)); // 20
}

引用特定对象的实例方法

@FunctionalInterface
public interface Testable {
	void test(int v);
}

public class Person {
	public void setAge(int age) {
		System.out.println("Person - setAge - " + age);
	}
	static void execute(Testable t, int v) {
		t.test(v);
	}
	public static void main(String[] args) {
		// 10
		execute(v -> System.out.println(v), 10);
		// 20
		execute(System.out::println, 20); // 引用特定对象的实例方法
		
		// Person - setAge - 10
		execute(v -> new Person().setAge(v), 10);
		// Person - setAge - 20
		execute(new Person()::setAge, 20); // 引用特定对象的实例方法
	}
}

引用特定类型的任意对象的实例方法

String[] strings = { "Jack", "james", "Apple", "abort" };
// [abort, Apple, Jack, james]
Arrays.sort(strings, (s1, s2) -> s1.compareToIgnoreCase(s2));

// [abort, Apple, Jack, james]
Arrays.sort(strings, String::compareToIgnoreCase); // 引用特定类型的任意对象的实例方法

引用构造方法

public class Person {
	@FunctionalInterface
	public interface Testable {
		Object test(int v);
	}
	public Person(int age) { // 构造方法
		System.out.println("Person - " + age);
	}
	public static void main(String[] args) {
		Testable t1 = v -> new Person(v);
		// Person - 18
		// com.mj.Person@816f27d
		System.out.println(t1.test(18)); 
		
		// Person - 18
		// com.mj.Person@6ce253f1
		Testable t2 = Person::new; // 引用构造方法
		System.out.println(t2.test(18));
	}
}

引用数组的构造方法

@FunctionalInterface
public interface Testable {
	Object test(int v);
}

public static void main(String[] args) {
	Testable t1 = v -> new int[v];
	System.out.println(((int[])t1.test(3)).length);
	
	Testable t2 = int[]::new; // 引用数组的构造方法
	System.out.println(((int[])t2.test(3)).length);

引用当前类中定义的实例方法

public class Person {
	public void setAge(int age) {
		System.out.println("Person - setAge - " + age);
	}
	public void show() {
		Testable t1 = v1 -> setAge(v);
		// Person - setAge - 10
		t1.test(10);
		
		Testable t2 = this::setAge; // 引用当前类中定义的实例方法
		// Person - setAge - 10
		t2.test(10);
	}
}

引用父类中定义的实例方法

@FunctionalInterface
public interface Testable {
	void test(int v);
}
class Person {
	public void setAge(int age) {
		System.out.println("Person - setAge - " + age);
	}
}
public class Studen extends Person {
	public void setAge(int age) {
		System.out.println("Student - setAge - " + age);
	}
	public void show() {
		Testable t1 = v -> super.setAge(v);
		// Student - setAge - 10
		t1.test(10);
		
		Testable t2 = super::setAge; // 引用父类中定义的实例方法
		// Student - setAge - 10
		t2.test(10);
	}
}

常用函数式接口

java.util.function 包中提供了很多常用的函数式接口

  • Supplier
  • Consumer
  • Predicate
  • Function

Supplier:优化不一定执行的代码

  • 有时使用 Supplier 传参,可以避免代码的浪费执行(有必要的时候再执行)
    在这里插入图片描述

首先看这段例子,getFirstNotEmptyString 返回传入的第一个不为空的字符串,但是有时候会浪费性能,比如第二个字符串是由某个函数构造的,虽然第一个字符串已经确定不为空,但依旧执行了构造第二个字符串的函数。

public static void main(String[] args) {
	String s1 = "Jack";
	String s2 = "Rose";
	System.out.println(getFirstNotEmptyString(s1, makeString()));
}

static String makeString() {
	System.out.println("makeString");
	return String.format("%d %d %d", 1, 2, 3);
}

// 获取第一个不为空的字符串, 正常写法
static String getFirstNotEmptyString(String s1, String s2) {
	if (s1 != null || s1.length() > 0) return s1;
	if (s2 != null || s2.length() > 0) return s2;
	return null;
}
makeString
Jack

这时候可以用函数式接口 Supplier 来优化:

public static void main(String[] args) {
	String s1 = "Jack";
	// 使用了函数式接口, 不会执行 makeString()
	System.out.println(getFirstNotEmptyString(s1, () -> makeString()));
}

static String makeString() {
	System.out.println("makeString");
	return String.format("%d %d %d", 1, 2, 3); // 1 2 3
}

// 获取第一个不为空的字符串, 函数式接口
static String getFirstNotEmptyString(String s1, Supplier<String> supplier) {
	if (s1 != null || s1.length() > 0) return s1;
	String s2 = supplier.get();
	if (s2 != null || s2.length() > 0) return s2;
	return null;
}
jack

Supplier 并不神秘,我们自己也可以写一个接口达到类似效果,这里只是演示一下,实际中类似需求用 Supplier 即可,不需要自己写。

public static void main(String[] args) {
	String s1 = "Jack";
	String s2 = "Rose";
	// 使用了函数式接口, 不会执行s1 + s2
	System.out.println(getFirstNotEmptyString3(s1, () -> s1 + s2));
}

@FunctionalInterface
public interface GetString {
	String get();
}

static String getFirstNotEmptyString3(String s1, GetString supplier) {
	if (s1 != null || s1.length() > 0) return s1;
	String s2 = supplier.get();
	if (s2 != null || s2.length() > 0) return s2;
	return null;
}

Consumer:接收一个值决定要做什么

在这里插入图片描述
Consumer 应用:接收一个值,来决定要做什么

public static void main(String[] args) {
	int[] nums = { 11, 33, 44, 88, 77, 66 };
	forEach(nums, (n) -> {
		String result = ((n & 1) == 0) ? "偶数" : "奇数";
		System.out.println(n + "是" + result);
	});	
}

static void forEach(int[] nums, Consumer<Integer> c) {
	if (nums == null || c == null) return;
	for (int n : nums) {
		c.accept(n);
	}
}
11是奇数
33是奇数
44是偶数
88是偶数
77是奇数
66是偶数

andThen 应用:

public static void main(String[] args) {
	int[] nums = { 11, 33, 44, 88, 77, 66 };
	forEach(nums, (n) -> {
		String result = ((n & 1) == 0) ? "偶数" : "奇数";
		System.out.println(n + "是" + result);
	}, (n) -> {
		String result = ((n % 3) == 0) ? "能" : "不能";
		System.out.println(n + result + "被3整除");
	});
}

static void forEach(int[] nums, Consumer<Integer> c1, Consumer<Integer> c2) {
	if (nums == null || c1 == null || c2 == null) return;
	for (int n : nums) {
		// 相当于先执行c1的任务, 然后执行c2的任务, 执行完后进入下一轮循环
		c1.andThen(c2).accept(n);
	}
}
11是奇数
11不能被3整除
33是奇数
33能被3整除
44是偶数
44不能被3整除
88是偶数
88不能被3整除
77是奇数
77不能被3整除
66是偶数
66能被3整除

Predicate:让过滤条件更灵活

在这里插入图片描述
Predicate应用:可以不将过滤条件写死,由外面传入更加灵活。

public static void main(String[] args) {
	int[] nums = { 11, 33, 44, 88, 77, 66 };
	// 过滤条件: 偶数
	String str = join(nums, (n) -> (n & 1) == 0);
	// 44_88_66
	System.out.println(str);
}

// 将数组元素用_拼接成字符串, 并且满足外面传入的过滤条件
static String join(int[] nums, Predicate<Integer> p) {
	if (nums == null || p == null) return null;
	StringBuilder sb = new StringBuilder();
	for (int n : nums) {
		if (p.test(n)) {
			sb.append(n).append("_");
		}
	}
	sb.deleteCharAt(sb.length() - 1);
	return sb.toString();
}

andornegate 应用:

public static void main(String[] args) {
int[] nums = { 11, 33, 44, 88, 77, 66 };
	// 过滤条件: 偶数且能被3整除
	String str1 = join1(nums, (n) -> (n & 1) == 0, (n) -> (n % 3) == 0);
	// 过滤条件: 偶数或能被3整除
	String str2 = join2(nums, (n) -> (n & 1) == 0, (n) -> (n % 3) == 0);
	// 过滤条件: 偶数条件取反, 即奇数
	String str3 = join3(nums, (n) -> (n & 1) == 0);

	System.out.println(str1); // 66
	System.out.println(str2); // 33_44_88_66
	System.out.println(str3); // 11_33_77
}
// and
static String join1(int[] nums, Predicate<Integer> p1, Predicate<Integer> p2) {
	if (nums == null || p1 == null || p2 == null) return null;
	StringBuilder sb = new StringBuilder();
	for (int n : nums) {
		if (p1.and(p2).test(n)) {
			sb.append(n).append("_");
		}
	}
	sb.deleteCharAt(sb.length() - 1);
	return sb.toString();
}
// or
static String join2(int[] nums, Predicate<Integer> p1, Predicate<Integer> p2) {
	if (nums == null || p1 == null || p2 == null) return null;
	StringBuilder sb = new StringBuilder();
	for (int n : nums) {
		if (p1.or(p2).test(n)) {
			sb.append(n).append("_");
		}
	}
	sb.deleteCharAt(sb.length() - 1);
	return sb.toString();
}
// negate
static String join3(int[] nums, Predicate<Integer> p) {
	if (nums == null || p == null) return null;
	StringBuilder sb = new StringBuilder();
	for (int n : nums) {
		if (p.negate().test(n)) {
			sb.append(n).append("_");
		}
	}
	sb.deleteCharAt(sb.length() - 1);
	return sb.toString();
}

Function:类型转换

在这里插入图片描述
Function 应用:类型转换

public static void main(String[] args) {
	String[] strs = { "10", "20", "30" };
	// 将字符串转为int
	int result = sum(strs, Integer::valueOf);
	System.out.println(result);	 // 60
}

static int sum(String[] strs, Function<String, Integer> f) {
	if (strs == null || f == null);
	int result = 0;
	for (String str : strs) {
		result += f.apply(str);
	}
	return result;
}
public static void main(String[] args) {
	String[] strs = { "10", "20", "30" };
	// 将字符串转为int并模10, 相加
	int result = sum(strs, Integer::valueOf, (i) -> i % 10);
	System.out.println(result); // 0
}

static int sum(String[] strs, Function<String, Integer> f1, Function<Integer, Integer> f2) {
	if (strs == null || f1 == null || f2 == null) return 0;
	int result = 0;
	for (String str : strs) {
		result += f1.andThen(f2).apply(str);
	}
	return result;
}

综合实例(Iterable、Predicate、Function、Consumer、Stream)

public class Person {
	private String name;
	private int age;
	public Person(String name, int age) {
		this.name = name;
		this.age = age;
	}
	public int getAge() {
		return age;
	}
	@Override
	public String toString() {
		return "Person [name=" + name + ", age=" + age + "]";
	}
	
}

是不是有点恐怖。。。

public class Main {
	static <X, Y> void process(Iterable<X> eles,
			Predicate<X> tester, 
			Function<X, Y> mapper, 
			Consumer<Y> block) {
		if (eles == null || tester == null
		|| mapper == null || block == null) return;
		for (X ele : eles) {
			if (!tester.test(ele)) continue;
			Y data = mapper.apply(ele);
			block.accept(data);
		}
	}
	
	public static void main(String[] args) {
		
		List<Person> ps = new ArrayList<>();
		ps.add(new Person("Jack", 20));
		ps.add(new Person("Rose", 10));
		ps.add(new Person("Kate", 15));
		ps.add(new Person("Larry", 40));
		
		process(ps, (p) -> p.getAge() >= 15 && p.getAge() <= 25, 
				Person::toString, System.out::println);
		// Person [name=Jack, age=20]
		// Person [name=Kate, age=15]
		
		List<Integer> is = new ArrayList<>();
		is.add(11);
		is.add(22);
		is.add(33);
		is.add(44);
		process(is, (i) -> (i & 1) == 0, (i) -> "test_" + i, System.out::println);
		// test_22
		// test_44
		
		// Stream初体验, 有点牛批啊
		ps.stream()
		.filter((p) -> p.getAge() >= 15 && p.getAge() <= 25)
		.map(Person::toString)
		.forEach(System.out::println);
		// Person [name=Jack, age=20]
		// Person [name=Kate, age=15]
		
		is.stream()
		.filter((i) -> (i & 1) == 0)
		.map((i) -> "test_" + i)
		.forEach(System.out::println);
		// test_22
		// test_44
	}
}

猜你喜欢

转载自blog.csdn.net/weixin_43734095/article/details/105593899