当前位置: 主页 > JAVA语言

java函数的编写-编写函数把华氏温度转换为摄氏温度,

发布时间:2023-02-08 11:13   浏览次数:次   作者:佚名

出品 | CSDN博客

本文将介绍Java 8中新增的Lambda表达式,包括Lambda表达式的常见用法和方法引用的用法,分析Lambda表达式的原理,最后总结Lambda表达式的优缺点。

编写函数实现value左右循环移位_编写函数把华氏温度转换为摄氏温度,_java函数的编写

概述

Java 8引入的Lambda表达式的主要作用是简化部分匿名内部类的编写。

能够使用Lambda表达式的一个重要基础是必须有相应的函数式接口。 所谓函数式接口,是指内部只有一个抽象方法的接口。

lambda 表达式的另一个基础是类型推断机制。 给定足够的上下文信息,编译器可以在不显式命名的情况下推断参数列表的类型。

java函数的编写_编写函数实现value左右循环移位_编写函数把华氏温度转换为摄氏温度,

常见用法

2.1 无参数函数的简写

无参函数就是没有参数的函数,比如Runnable接口的run方法,定义如下:

@FunctionalInterface

public interface Runnable {

public abstract void run;

}

在Java 7及更早的版本中,我们一般可以这样使用:

new Thread(new Runnable {

@Override

public void run {

System.out.println("Hello");

System.out.println("Jimmy");

}

}).start;

从Java 8开始,无参函数的匿名内部类可以简写如下:

-> {

执行语句

}

这样接口名和函数名就可以省略了。 那么,上面的例子可以简写为:

new Thread( -> {

System.out.println("Hello");

System.out.println("Jimmy");

}).start;

当只有一条语句时,我们也可以将代码块简写,格式如下:

-> 表达式

注意这里用的是表达式,不是语句,也就是说不需要在末尾加分号。

那么,当上面例子中只有一条语句执行时,可以简写为:

new Thread( -> System.out.println("Hello")).start;

2.2 单参数函数的简写

单参数函数是只有一个参数的函数。 例如View内部的OnClickListener接口的onClick(View v)方法定义如下:

编写函数把华氏温度转换为摄氏温度,_java函数的编写_编写函数实现value左右循环移位

public interface OnClickListener {

/**

* Called when a view has been clicked.

*

* @param v The view that was clicked.

*/

void onClick(View v);

}

在Java 7及更早的版本中,我们通常可能会这样使用:

view.setOnClickListener(new View.OnClickListener {

@Override

public void onClick(View v) {

v.setVisibility(View.GONE);

}

});

从Java 8开始,单参数函数的匿名内部类可以简写如下:

([类名 ]变量名) -> {

执行语句

}

类名可以省略,因为Lambda表达式可以自己推断。 那么上面的例子可以简写为:

view.setOnClickListener((View v) -> {

v.setVisibility(View.GONE);

});

view.setOnClickListener((v) -> {

v.setVisibility(View.GONE);

});

单参数函数甚至可以去掉括号,官方推荐使用这种方法:

变量名 -> {

执行语句

}

那么,上面的例子可以简写为:

view.setOnClickListener(v -> {

v.setVisibility(View.GONE);

});

当只有一条语句时,代码块仍然可以简写,格式如下:

([类名 ]变量名) -> 表达式

类名和括号仍然可以省略,如下:

变量名 -> 表达式

那么,上面的例子可以进一步简写为:

view.setOnClickListener(v -> v.setVisibility(View.GONE));

2.3 多参数函数的简写

多参数函数是具有两个或多个参数的函数。 例如Comparator接口的compare(T o1, To o2)方法有两个参数,定义如下:

@FunctionalInterface

public interface Comparator {

int compare(T o1, T o2);

}

在Java 7及更早的版本中,当我们对一个集合进行排序时,通常可以这样写:

List list = Arrays.asList(1, 2, 3);

Collections.sort(list, new Comparator {

@Override

public int compare(Integer o1, Integer o2) {

return o1.compareTo(o2);

}

});

从Java 8开始,多参数函数的匿名内部类可以简写如下:

([类名1 ]变量名1, [类名2 ]变量名2[, ...]) -> {

执行语句

}

相同的类名可以省略,所以上面的例子可以简写为:

编写函数把华氏温度转换为摄氏温度,_编写函数实现value左右循环移位_java函数的编写

Collections.sort(list, (Integer o1, Integer o2) -> {

return o1.compareTo(o2);

});

Collections.sort(list, (o1, o2) -> {

return o1.compareTo(o2);

});

当只有一条语句时,代码块仍然可以简写,格式如下:

([类名1 ]变量名1, [类名2 ]变量名2[, ...]) -> 表达式

这时候类名也可以省略,但是括号不能省略。 如果这条语句需要返回一个值,那么return关键字就不用写了。

因此,上面的例子可以进一步简写为:

Collections.sort(list, (o1, o2) -> o1.compareTo(o2));

最后,这个例子也可以简写如下:

Collections.sort(list, Integer::compareTo);

咦,这是什么功能? 这就是我们接下来要讲的:方法引用。

java函数的编写_编写函数把华氏温度转换为摄氏温度,_编写函数实现value左右循环移位

方法参考

方法引用也是一种语法糖,可用于简化开发。

我们在使用Lambda表达式的时候,如果“->”右边要执行的表达式只是调用一个类已有的方法,那么可以用“方法引用”代替Lambda表达式。

方法引用可分为4类:

下面分别介绍这四类。

3.1 引用静态方法

当我们要执行的表达式是调用某个类的静态方法,而这个静态方法的参数列表对应接口中抽象函数的参数列表时,我们可以使用引用静态方法的格式。

如果 Lambda 表达式符合以下格式:

([变量1, 变量2, ...]) -> 类名.静态方法名([变量1, 变量2, ...])

我们可以简写如下:

类名::静态方法名

注意静态方法名后不需要加括号或参数,因为编译器可以推断出来。 下面我们继续以2.3节中的例子进行说明。

首先创建一个工具类,代码如下:

public class Utils {

public static int compare(Integer o1, Integer o2) {

return o1.compareTo(o2);

}

}

注意比较函数的参数和Comparable接口的比较函数的参数是一一对应的。 那么一个通用的Lambda表达式可以这样写:

编写函数实现value左右循环移位_编写函数把华氏温度转换为摄氏温度,_java函数的编写

Collections.sort(list, (o1, o2) -> Utils.compare(o1, o2));

如果你使用方法引用,你可以像这样缩写它:

Collections.sort(list, Utils::compare);

3.2 引用对象的方法

当我们要执行的表达式是调用一个对象的方法,而这个方法的参数列表对应接口中抽象函数的参数列表时,我们可以使用引用该对象的方法的格式。

如果 Lambda 表达式符合以下格式:

([变量1, 变量2, ...]) -> 对象引用.方法名([变量1, 变量2, ...])

我们可以简写如下:

对象引用::方法名

下面我们继续以2.3节中的例子进行说明。 首先创建一个类,代码如下:

public class MyClass {

public int compare(Integer o1, Integer o2) {

return o1.compareTo(o2);

}

}

当我们创建这个类的对象java函数的编写,并在Lambda表达式中使用该对象的方法时,一般可以这样写:

MyClass myClass = new MyClass;

Collections.sort(list, (o1, o2) -> myClass.compare(o1, o2));

注意这里函数的参数也是一一对应的,所以方法引用的方式可以简写如下:

MyClass myClass = new MyClass;

Collections.sort(list, myClass::compare);

另外,当我们要执行的表达式是调用Lambda表达式所在类的方法时,也可以使用如下格式:

this::方法名

比如我在Lambda表达式所在的类中添加如下方法:

private int compare(Integer o1, Integer o2) {

return o1.compareTo(o2);

}

在Lambda表达式中使用该方法时,一般可以这样写:

Collections.sort(list, (o1, o2) -> compare(o1, o2));

如果你使用方法引用,你可以像这样缩写它:

Collections.sort(list, this::compare);

3.3 引用类的方法

引用类的方法所采用的相应参数形式与上述两种略有不同。 如果Lambda表达式的“->”右边要执行的表达式是被调用的“->”左边第一个参数的实例方法,第二个参数(或者没有参数)对应给实例方法当实例方法的参数列表时可以使用此方法。

java函数的编写_编写函数把华氏温度转换为摄氏温度,_编写函数实现value左右循环移位

这可能有点令人困惑,假设我们的 Lambda 表达式符合以下格式:

(变量1[, 变量2, ...]) -> 变量1.实例方法([变量2, ...])

那么我们的代码可以简写为:

变量1对应的类名::实例方法名

还是用2.3节的例子,当我们使用的Lambda表达式是这样的:

Collections.sort(list, (o1, o2) -> o1.compareTo(o2));

根据上面的说法,可以简写为:

Collections.sort(list, Integer::compareTo);

3.4 引用构造函数

当我们要执行的表达式是创建一个新的对象,而对象的构造方法的参数列表与接口中函数的参数列表相对应时,我们可以使用“引用构造方法”的格式。

假设我们的 Lambda 表达式符合以下格式:

([变量1, 变量2, ...]) -> new 类名([变量1, 变量2, ...])

我们可以简写如下:

类名::new

我们举个例子来说明。 Java 8引入了Function接口,也就是函数式接口。 部分代码如下:

@FunctionalInterface

public interface Function {

/**

* Applies this function to the given argument.

*

* @param t the function argument

* @return the function result

*/

R apply(T t);

// 省略部分代码

}

我们使用这个接口来实现一个函数来创建一个指定大小的ArrayList。 一般我们可以这样做:

Function function = new Function {

@Override

public ArrayList apply(Integer n) {

return new ArrayList(n);

}

};

List list = function.apply(10);

使用 Lambda 表达式,我们通常可以这样写:

Function function = n -> new ArrayList(n);

使用“引用构造函数”,我们可以将其缩写为:

Function function = ArrayList::new;

编写函数实现value左右循环移位_java函数的编写_编写函数把华氏温度转换为摄氏温度,

自定义功能接口

自定义函数接口很容易,写一个只有一个抽象方法的接口即可,示例代码:

编写函数把华氏温度转换为摄氏温度,_编写函数实现value左右循环移位_java函数的编写

@FunctionalInterface

public interface MyInterface {

void function(T t);

}

上面代码中的@FunctionalInterface是可选的,但是有了这个注解,编译器会帮你检查接口是否符合函数式接口规范。 就像添加@Override注解会检查函数是否被重写一样。

java函数的编写_编写函数实现value左右循环移位_编写函数把华氏温度转换为摄氏温度,

实现原理

经过上面的介绍,我们看到Lambda表达式只是为了简化匿名内部类的写法。 似乎在编译阶段将所有的Lambda表达式替换为匿名内部类就足够了。 但这种情况并非如此。 在JVM层面,Lambda表达式和匿名内部类其实有着明显的区别。

5.1 匿名内部类的实现

匿名内部类仍然是一个类,但我们不需要显式指定类名,编译器会自动为类命名。 例如,有如下形式的代码:

public class LambdaTest {

public static void main(String[] args) {

new Thread(new Runnable {

@Override

public void run {

System.out.println("Hello World");

}

}).start;

}

}

编译后会生成两个class文件:

LambdaTest.class

LambdaTest$1.class

使用javap -c LambdaTest.class进一步分析LambdaTest.class的字节码,部分结果如下:

public static void main(java.lang.String[]);

Code:

0: new #2 // class java/lang/Thread

3: dup

4: new #3 // class com/example/myapplication/lambda/LambdaTest$1

7: dup

8: invokespecial #4 // Method com/example/myapplication/lambda/LambdaTest$1."":V

11: invokespecial #5 // Method java/lang/Thread."":(Ljava/lang/Runnable;)V

14: invokevirtual #6 // Method java/lang/Thread.start:V

17: return

可以发现匿名内部类的对象是在第4行:new #3创建的。

5.2 Lambda表达式的实现

接下来,我们将使用Lambda表达式来实现上面的示例代码,代码如下:

public class LambdaTest {

public static void main(String[] args) {

new Thread( -> System.out.println("Hello World")).start;

}

}

此时编译后只会生成一个文件LambdaTest.class。 我们看一下通过javap反编译文件的结果:

public static void main(java.lang.String[]);

Code:

0: new #2 // class java/lang/Thread

3: dup

4: invokedynamic #3, 0 // InvokeDynamic #0:run:Ljava/lang/Runnable;

9: invokespecial #4 // Method java/lang/Thread."":(Ljava/lang/Runnable;)V

12: invokevirtual #5 // Method java/lang/Thread.start:V

15: return

从上面的结果我们发现java函数的编写,Lambda表达式被封装到主类的私有方法中,通过invokedynamic指令调用。

因此,我们可以得出结论,Lambda表达式是通过invokedynamic指令实现的,编写Lambda表达式并不会产生新的类。

由于Lambda表达式不会创建匿名内部类,因此在Lambda表达式中使用this关键字时,指向外部类的引用。

java函数的编写_编写函数把华氏温度转换为摄氏温度,_编写函数实现value左右循环移位

的优点和缺点

优势:

缺点: