开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第3天,点击查看活动详情
1.匿名类
- 当接口、抽象类的实现类,在整个项目中只用过一次,建议使用匿名类。
抽象类的实现类举例:
// 抽象类的实现类
public class Persion implements Runnable{
public void run(){
// xxx
}
}
public class Test{
public static void main(String[] args){
//Persion p = new Persion();
//p.run();
// 用匿名类
Runable persion = new Runable(){
public void run(){
// xxx
}
};
persion.run();
}
}
接口举例:
public interface Eatable{
String name();
}
public class Persion{
public void eat(Eatable e){
print(e.name());
}
}
Persion persion = new Persion();
调用Persion
的eating
方法的两种方式:
-
方式一:
persion.eating(new Eatable(){ public String name(){ return "匿名类"; } });
-
方式二:
Eatable e = new Eatable(){ public String name(){ return "匿名类"; } }; persion.eating(e);
1.1.匿名类的使用注意:
- 匿名类不能定义除编译时常量以外的任何
static
成员。 - 匿名类只能访问
final
或 有效final
的局部变量。 - 匿名类可以直接访问外部类中的所有成员(即使被声明为
private
) - 匿名类不能自定义构造方法,但可以有初始化块(无类名也就无法定义构造方法)
1.2.匿名类的常见用途:
- 代码传递
- 过滤器
- 回调
匿名类用用途1 - 代码传递
写个可统计下列代码执行时间的工具类
for(int i = 0; i < 1999; i++){
println("aaaa");
}
//工具类
public class TimeUtils{
public interface Block{
void execute();
}
public static void testUseTime(Block block){
long begin = System.currentTimeMillis(); //获取当前时间
block.execute();
long end = System.currentTimeMillis();
print("耗时:" + (end - begin)/1000.0 + "秒");
}
}
//调用
TimeUnits.testUseTime(new Block(){
//把想测试的代码防止execute()里
@Override
public void execute(){
for(int i=0; i<9999; i++){
println("aaaa");
}
}
});
匿名类用用途2 - 回调(请求一个链接,请求成功/失败 会调用对应的方法)
public class NetworkUtils{
public interface Block{
void success(Object response); //请求成功,返回数据
void failure(); //请求失败
}
public static void get(String url, Block callback){
// 1.根据url发送一个异步请求(开启一条子线程)
// 2.请求完毕之后
boolean result = true; //自定义个请求结果
if(result){
Object response = null; //自定义个服务器返回的数据
callback.success(response);
}else{
callback.failure();
}
}
}
//调用
NetworkUtils.get("http://xxx", new NetworkUtils.Block(){
// 请求成功 会调用此方法
@Override
public void success(Object response){
print("请求成功!");
}
// 请求失败 会调用此方法
@Override
public void failure(){
print("请求失败!");
}
});
匿名类用用途3 - 过滤器(传个文件夹,返回此文件夹下的所有文件)
public class FileUtils{
public interface Filter{
boolean accept(String filename); //表示接受此文件
}
public static String[] getAllFileNames(String dir, Filter filter){
// 1. 先获取dir 文件夹下的所有文件名
String[] allFileNames = {}; //假设这里已经获取到了
// 2.进行过滤
for(String filename : allFileNames){
if(filter.accept(filename)){
//将这个文件名装到集合中
}
}
// 3.返回集合
}
}
// 调用:搜索F盘下的 java文件
FileUtils.getAllFileNames("F:/", new filter(){
@Override
public boolean accept(String filename){
return filename.contains(".java"); // contains() 是否包含
}
});
Arrays.sort() 源码也使用了匿名类
数组排序:java.util.Arrays
Integer[] arr = {22, 55, 11, 44, 33};
// 默认是升序
Arrays.sort(arr);
System.out.println(Arrays.toString(arr));
// 降序 (Comparator: 比较器)
Arrays.sort(arr, new Comparator<Integer>(){
/**
* 返回值情况:
* > 0 : o1 > o2
* = 0 : o1 = o2
* < 0 : o1 < o2
* Arrays.sort() 默认是升序,小的在左边,大的在右边
* 例:当前传值(22, 33) 即o1=22,o2=33 -> 返回值会大于0 -> 根据上图 编译器会认为o1比o2大
* 根据大的放在右边 -> 结果为: 33, 22。实现了降序
*/
@Override
public int compara(Integer o1, Integer o2){
return o2 - o1; // 降序(o1-o2: 升序)
}
});
2.Lambda
-
函数式接口(
Functional Interface
): 只包含一个抽象方法的接口。 建议在接口上加@FunctionalInterface
注解,表示此接口是函数式接口。@FunctionalInterface public interface Filter{ boolean accept(String filename); }
-
当匿名类实现的是函数式接口时,可用
Lambda
表达式进行简化。 Lambda表达式写法(参数列表) -> { // 参数列表:函数式接口里面唯一的那个方法的参数列表 // 函数式接口里面唯一的那个方法 // 里面需要写的东西,都写在这里 // return xxx; 若唯一的那个方法有返回值,这里要写return // 如果花括号中只有一个语句,可以省略花括号 }
2.1.举例一
// 工具类
public class TimeUtils{
@FunctionalInterface
public interface Block{
void execute();
}
public static void test(Block block){
long begin = System.currentTimeMillis(); //获取当前时间
block.execute();
long end = System.currentTimeMillis();
print("耗时:" + (end - begin)/1000.0 + "秒");
}
}
// 调用: 传统方式
TimeUnits.test(new Block(){
//把想测试的代码防止execute()里
@Override
public void execute(){
for(int i=0; i<1000; i++){
println("1234");
}
}
});
// 调用:Lambda表达式
TimeUnits.test(() -> {
for(int i=0; i<1000; i++){
println("1234");
}
});
2.1.举例二
FileUtils.getAllFileNames("F:/", new filter(){
@Override
public boolean accept(String filename){
return filename.contains(".java"); // contains() 是否包含
}
});
FileUtils.getAllFileNames("F:/", (String filename) -> {
return filename.contains(".java");
});
// 甚至参数类型也可不写
FileUtils.getAllFileNames("F:/", (filename) -> {
return filename.contains(".java");
});
//当Lambda表达式里只有一条语句时,可省略大括号、分号、return
FileUtils.getAllFileNames("F:/", (filename) -> filename.contains(".java"));
3.lambda表达式推导:
// 1.定义一个接口
public interface ILike{
void lambda();
}
// 2.实现类
public class Like implements ILike{
@Override
public void lambda() {
System.out.println("My Lambda");
}
}
// 方法1
public class TestLambda {
public static void main(String[] args) {
ILike like = new Like();
like.lambda();
}
}
// 方法2 -- 静态内部类
public class TestLambda {
// 静态内部类
static Like implements ILike{
@Override
public void lambda() {
System.out.println("My Lambda");
}
}
public static void main(String[] args) {
ILike like = new Like();
like.lambda();
}
}
// 方法3 -- 局部内部类
public class TestLambda {
public static void main(String[] args) {
// 局部内部类
static Like implements ILike{
@Override
public void lambda() {
System.out.println("My Lambda");
}
}
ILike like = new Like();
like.lambda();
}
}
// 方法4 -- 匿名内部类(没有类的名称,必须借助接口或者父类)
public class TestLambda {
public static void main(String[] args) {
// 局部内部类
ILike like = new Like(){
@Override
public void lambda() {
System.out.println("My Lambda");
}
};
like.lambda();
}
}
// 方法5 -- lambda
public class TestLambda {
public static void main(String[] args) {
ILike like = () -> {
System.out.println("My Lambda");
};
like.lambda();
};
}
// 再来看看函数式接口
// 1.定义一个接口
public interface ILike{
void lambda();
}
// 2.实现类
public class Like implements ILike{ // 作用域、实现关系、方法名都可以不要
@Override
public void lambda() { // 方法名不需要、返回值类型可以推断出来、
System.out.println("My Lambda"); // 留下的只有形参和具体的执行逻辑
}
}
// 例子2
public interface IBird{
void fly(int a);
}
public class Test{
public static void main(String[] args){
IBird bird = (int a) -> {
System.out.println("飞" + a + "次");
};
bird.fly(23);
// 简化:参数类型也可去掉,即隐式声明参数(多个参数也可以,要去掉就都去掉)
IBird bird1 = (a) -> {
System.out.println("飞" + a + "次");
};
bird1.fly(23);
// 简化:括号也可以去掉 (只有一个参数时)
IBird bird2 = a -> {
System.out.println("飞" + a + "次");
};
bird2.fly(23);
// 简化:花括号也可以去掉(前提是只有一行代码)
IBird bird3 = a -> System.out.println("飞" + a + "次");
bird3.fly(23);
}
}
4.方法引用
方法引用是调用特定方法的
Lambda
表达式的一种快捷写法。
可以让你重复使用现有的方法定义,就像lambda一样传递他们。
Stu :: getStuName()
目标引用 双分号分隔符 方法名
4.1.指向 静态方法 的方法引用
(args) -> ClassName.staticMethod(args);
等价于
ClassName::staticMethod;
// 举例
(String number) -> Integer.parseInt(number);
等价于
Integer::parseInt;
4.2.指向 任意类型实例方法 的方法引用
(args) -> args.instanceMethod();
等价于
ClassName::instanceMethod;
// 举例
(String str) -> str.length();
等价于
String::length;
4.3.指向 现有对象的实例方法 的方法引用
(args) -> object.instanceMethod(args);
等价于
object::instanceMethod;
// 举例
StringBuilder stringBuilder = new StringBuilder();
(String str) -> stringBuilder.append(Str);
等价于
stringBuilder::append;
5.总结
从匿名类到lambda
,再到双冒号(::)表达式。理解其中的演变过程才是本次学习的重点。在实际编码过程中能够看懂以及灵活运用即可。本节课的内容在Stream
流中是非常常见的。
如果你学过js, 那么一定会非常熟悉函数式编程, 那你一定能快速理解java的函数式接口。
下一节讲学习枚举以及基本数据类型的包装类, 也是工作中比较常用到的基本知识。