在一类元素的定义中,Java 的函数满足了多少个点?

How many of these points in the definition of a first-class element do Java's functions satisfy?

提问人:J. Mini 提问时间:8/22/2021 最后编辑:J. Mini 更新时间:8/27/2021 访问量:404

问:

计算机程序的结构和解释给出了以下条件,即编程语言的元素必须满足才能被认为是一流的:

  1. 它们可以按变量命名。

  2. 它们可以作为参数传递给过程。

  3. 它们可以作为程序的结果返回。

  4. 它们可能包含在数据结构中

Java 的函数满足了其中的多少个?如果有任何歧义,例如“将函数放在对象中是否算作 #4?”,请在您的回答中提及。

Java SICP 第一类函数

评论

1赞 Turing85 8/22/2021
事情是这样的:Java 中的函数没有定义的类型。我们可以写成 或 这样的东西,但从本质上讲,这只是 的句法糖。作为回报,这只是匿名接口实现的语法糖。如果我们将其与C++等语言进行比较,其中每个函数都有一个定义的类型,我们会看到Java lambda不满足任何这些标准。Objects::nonNullsomeObject.getClass()::isInstance(foo) -> Objects.nonNull(foo)
0赞 Clashsoft 8/22/2021
我想说的是匿名接口实现是一个实现细节,出于实际目的,所有这些都可以通过 lambda 完成。但是,由于我们显然不同意这一点,我实际上会以基于意见的方式结束这个问题。
0赞 Clashsoft 8/22/2021
“定义的类型”实际上也是无关紧要的,因为您必须为 Java 中的所有内容定义一个类型(忽略较新的类型),并且有用于各种函数的类型。var
0赞 Benjamin M 8/22/2021
@Turing85 它不仅仅是句法糖,至少在解释模式下是这样(在JIT优化它之前)。 将创建一个 lambda,而 ist 只是对现有 lambda 的引用。虽然理论上应该更快,内存效率更高。但当然,主要目标应该是编写可读的代码。因此,请选择最适合您需求的。JVM 将为您完成剩下的工作:)o -> Objects.nonNull(o)newObjects::nonNullObjects::nonNull
1赞 Clashsoft 8/22/2021
@Turing85但是,您在哪里或原始条件列表中找到了有关明确定义类型的任何信息?或者换句话说,为什么你需要一个来满足这些条件?

答:

1赞 Turing85 8/22/2021 #1

Java 中的函数没有定义的类型。我们可以写类似 的东西,但从本质上讲,这只是 或 的语法糖,它将产生一个函数接口的实例(分别参见 JLS,§15.13§15.27)。因此,这些方法始终包装在(可能是匿名的)接口实现中。Objects::nonNull(foo) -> Objects.nonNull(foo)

如果我们将其与C++等语言进行比较,其中每个函数都有一个定义的类型,我们会看到Java lambda不满足任何这些标准。

评论

0赞 JayC667 8/22/2021
而“匿名接口实现”只是“实现所述接口的匿名类”的语义糖:-D
1赞 Turing85 8/22/2021
这是“实施”这个词所暗示的,不是吗?=)
0赞 rzwitserloot 8/22/2021
作为回报,这只是匿名接口实现的语法糖。这是不正确的。在匿名内部类 def 与其中一个运行,您会看到差异。javap
1赞 kaya3 8/22/2021
JLS 是这样说的:“lambda 表达式的计算产生函数接口的实例 (§9.8)”,并且:“在运行时,lambda 表达式的计算类似于类实例创建表达式的计算,只要正常完成产生对对象的引用。命令行工具的权限并不比 Java 语言规范高。docs.oracle.com/javase/specs/jls/se8/html/jls-15.html#jls-15.27输出显示的只是编译器没有天真地实现它,而不是语义不同。javapjavap
1赞 Turing85 8/22/2021
@rzwitserloot 我删除了关于句法糖的部分,并包含了相关的 JLS 部分 kaya3 提供。Wrt. 到你说 C 是成为汇编的句法糖......问题是基于语言的(在本例中为:Java)。因此,我的论点是基于语言支持的内容,而不是编译器生成的内容。
4赞 kaya3 8/22/2021 #2

这是值得商榷的。当然,你可以写这样的东西:

Function<Integer, Integer> times2 = x -> x * 2;

这是用变量命名函数吗?嗯,是也不是。该变量包含对“函数”的引用,因此“函数”是一个值,由变量命名。另一方面,Java 语言规范是这样说的:times2

接口类型的变量可以保存 null 引用或对实现接口的任何类的任何实例的引用。

也就是说,变量实际上并不直接保存对函数的引用;它包含对具有方法的对象的引用。JLS 不允许它保存“函数”或“对函数的引用”,只允许“对实例的引用”,这满足接口。该实例有一个名为 的方法,该方法在调用时执行该函数。applyapply

我们能说对象就是函数吗?嗯,是也不是,这是一个解释问题。但我倾向于“否”,因为方法的名称特定于接口,如果您在不同的上下文中编写相同的 lambda 函数,那么“函数对象”可以具有不同名称的方法。如果同一个 lambda 函数是在两个不同的上下文中编写的,它是“同一个函数”吗?我认为是的,但显然它们不是相同的对象,因为它们具有不同的方法名称。所以我得出结论,如果它们是相同的函数但不是同一个对象,那么函数和对象就是不同的东西。applyFunction


请注意,@rzwitserloot为相反的解释辩护的答案也是正确的,除非他们声称他们的解释是唯一可能的解释。正如我所说,这是值得商榷的。我应该认为我们正在辩论的事实足以证明这个命题......

评论

0赞 rzwitserloot 8/22/2021
不直接保存对函数的引用;它保存对具有 apply 方法的对象的引用。事实并非如此; 这里没有标识,或多或少是一个方法句柄。如果你向后弯腰并试图将其视为对象(例如,您尝试锁定它,或将其传递给 ,那么 JVM 将为您创建一个代理对象,尽管您在用作对象时得到的答案将没有用处。times2times2times2System.identityHashCodetimes2
0赞 kaya3 8/22/2021
@rzwitserloot 这是一个实现细节。从语义上讲,类型的变量包含对实现接口(或)的某个类的实例的引用,就像类型变量包含对实现接口(或)的某个类的实例的引用一样。Function<Integer, Integer>Function<Integer, Integer>nullList<Integer>List<Integer>null
0赞 kaya3 8/22/2021
类似地,Java 可以通过存储指向实例的指针(标记为指针)或基元值(标记为基元值)来实现盒装类型,并且仅在实例用于算术以外的其他用途时才动态创建实例。这种优化不会改变语言的语义,即类型的变量包含对 (或 ) 实例的引用。IntegerIntegerIntegerIntegernull
0赞 rzwitserloot 8/22/2021
不,你在这里是不正确的。它不保存任何类的实例。 句点。运行调试器,查看 VM 实际执行的操作。java lang 规范解释了如果您尝试将 Function<Integer, Integer> 类型的表达式视为对象会发生什么,例如,如果您去:,但这并不意味着如此。如果它确实如此,那么我可以说在 javascript 中,所有变量都是布尔值。毕竟,我可以为任何表达式编写,这不是语法错误,QED。Function<Integer, Integer> x = ...; synchronized (x) {}if (x)
1赞 kaya3 8/22/2021
JLS 明确表示“接口类型的变量可以保存 null 引用或对实现接口的任何类的任何实例的引用。因此,根据 JLS,类型的变量不能包含实现该接口的类的实例或 .这是没有办法的。“但实施”不是解决它的方法。我们谈论的是语言的语义。Function<Integer, Integer>null
2赞 rzwitserloot 8/22/2021 #3
  1. 它们可以按变量命名。

是的,很明显:

Runnable r = () -> System.out.println("Hello");
Runnable r2 = System.out::println;

我有函数/方法引用,我可以有引用它们的命名变量。

多义性:

这些变量也可以指向实际对象:

r = new Runnable() {
    public void run() {
        System.out.println("Goodbye!");
    }
};

与函数语法示例不同,上述 means 实际上是指向具有定义的类似对象特征的确定对象。内存中有一个真实的对象,java lang 规范保证了这一点。r

但是,这并不重要。在像 javascript 或 python 这样的语言中,我可以将任何东西分配给变量(变量在这些语言中是非类型的)。这并不能仅仅因为我可以将 和 分配给任何变量而“使所有变量都布尔”。truefalse

同样,在 java 中,要调用函数,例如实际打印给定的:,我必须编写而不是 .HelloRunnable r = () -> System.out.println("Hello");r.run()r()

这是关于语法的争论,它不是基本的东西。在 python 中,将接受变量 ,将取消引用它(跟随指针),尝试将它在那里找到的内容解释为函数,然后执行它,不传递任何参数。如果无法强制执行函数,则会发生错误。r()r

Java 也不例外。 将接受变量,将取消引用它(跟随指针),尝试将它在那里找到的内容解释为 Runnable 类型的函数,并执行它,不传递任何参数。如果它发现那里没有 Runnable(例如,它是 ),则会发生错误。r.run()rnull

看?相同。试图用其他语言的做事方式来定义事物会错误地导致人们说,不知何故,上面是“不算数”的“语法糖”,这是一个错误的结论。

  1. 它们可以作为参数传递给过程。

是的,毫不含糊。

public void runTwice(Runnable r) {
    r.run();
    r.run();
}

Runnable r = () -> System.out.println("Hello");
runTwice(r);
runTwice(System.out::println);
runTwice(() -> System.out.println("Goodbye!"));

我正在将这些函数传递给 java。

  1. 它们可以作为程序的结果返回。

是的,毫不含糊:

public Runnable printMeTwice(String text) {
    return () -> {
        System.out.println(text);
        System.out.println(text);
    };
}

Runnable r = printMeTwice();
r.run();
  1. 它们可能包含在数据结构中

是的,毫不含糊:

class Animal {
    String name;
    Function<Food, Excrement> eat;
}

Animal animal = new Animal();
animal.eat = food -> return food.extractNutrients();

如果有任何歧义。

使用粘贴时的特定措辞,根本没有歧义,这有点有趣,考虑到这个问题已经有答案,要么被误解,要么对这些词使用特别奇怪的解释。

与大多数其他语言相比,java 有点不同的一个“奇怪”之处在于,在大多数语言中,如果变量包含一个函数,要完成“取消引用指针,然后执行你这样做时找到的函数”的工作,你会写 .在 java 中,你不会这样做;相反,在 Java 中,你做 or ;在 Java 中,函数具有命名类型,而在大多数语言中则没有。例如,在 java 中,我可以有“仅整数计算器操作”的概念:rr()r.run()r.apply(t)

IntCalcOp plusButton = (a, b) -> a + b;

我可以有一个“整数比较函数”的概念:

IntComparator highestIsEarlier = (a, b) -> b - a;

这两件事根本不同。我无法将类型的变量传递给需要 IntComparator 的方法,即使签名在功能上完全相同 - 两者都接受 2 个 int 并返回一个 int。IntCalcOp

请注意,这并非天生就低人一等;事实上,它可能天生就优越。这是语言设计中的意见分歧。为了展示名义类型和完全没有隐式转换的好处,这里有 2 个非常简单的对象定义,它们在功能上是相同的:

interface Camera {
    void shoot(Person p);
}

interface Gun {
    void shoot(Person p);
}

在一些语言中,比如 python 和 javascript,如果我给你一把枪,而你期望有一台相机,你就会杀人。在 Java 中,这是不可能的。

再多的喋喋不休的“但它是语法糖”也改变了上面 4 个片段中的任何一个,这些片段清楚地表明,根据计算机程序的结构和解释的定义,Java 的函数是第一类元素

我不认为那本书有一点脚注说:“语法糖不算数”。如果你愿意在有这个警告的情况下进行操作,我们首先需要定义语法糖的含义。*

例如,java 语言规范并没有这些被转换为匿名的内部类文本。实现甚至不再这样做(其他答案提到了这一点,并且在这方面是不正确的)。

C 编译器的工作方式可能是首先发出汇编程序代码,然后通过汇编程序运行该代码以生成可执行文件。这是否使所有 C 代码都只是“语法糖”?如果这是真的,那么 C 甚至没有循环。

这种推理很有趣,但我认为,为了确定一种语言能做什么,完全没有意义。从本质上讲,如果你挖掘得足够深入,所有编程语言都是 100% 的语法糖。

评论

0赞 kaya3 8/22/2021
使用粘贴时的特定措辞,根本没有歧义,这有点有趣,考虑到这个问题已经有答案,要么被误解,要么对这些词使用特别奇怪的解释。- 我认为你完全错过了我回答的重点。歧义只在于“函数”一词,例如函数是值还是函数只是值的属性。你所有的观点都是正确的,除非你断言它们是明确正确的,即你否认甚至有可能......
0赞 kaya3 8/22/2021
...将其解释为“函数即值”以外的任何内容。我认为很明显,还有另一种可行的解释,“函数是值的属性”,我回答的目的是证明这是一种可能的解释。
0赞 rzwitserloot 8/22/2021
@kaya3 我补充了几句话,说明在采用 javascript/python 式的世界观来讨论编程语言应该如何工作时,如何得出这个结论。希望这能解决您的问题。
0赞 kaya3 8/22/2021
它没有解决我的担忧,我认为你关于语法糖的观点是不诚实的,因为语法糖的目的是根据同一语言的其他特征来定义语言特征的语义。所以 Python 代码不能是汇编程序或 C 的语法糖,因为 Python 不是汇编程序或 C。
1赞 kaya3 8/22/2021
@BenjaminM 为了澄清这一点,函数是参考值,因此函数值变量的值是对函数的引用,这与 SICP 对函数作为值的含义的定义完全兼容。例如,没有人会说 Python 没有一流的函数,因为 Python 的变量总是包含引用。