2024 年 Clojure 状态调查中分享您的想法!

欢迎!请参阅关于页面以获取有关该平台如何工作的更多详细信息。

+3
Java 互操作

Clojure 编译器无法解析从包私有基类继承的公共泛型方法。

重复说明

  • 在包 P1 中
    • 定义一个带有泛型类型参数的包私有类 A
    • 在 A 中定义一个使用泛型类型(参数或返回类型)的公共方法 M
      **** 定义一个扩展 A 的公共类 B
  • 在包 P2 中
    • 构造 B 的一个实例
      **** 调用 B.M()

这在 Java 中是有效的。但在 Clojure 中,调用 B.M 会导致反射警告,随后是错误 "java.lang.IllegalArgumentException: Can't call public method of non-public class." 无论使用多少类型提示都无法阻止警告或错误。

附件 clj-1243-demo1.tar.gz 包含演示问题的示例代码和脚本。

使用包私有类中的公共方法的 Java 项目的示例

14 个答案

0

评论由:stuart.sierra 提出

也无法从 Java 中反射性调用该方法。

这可能是Java反射中的一个bug:(链接:http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4283544 文本:JDK-4283544)

但是为什么这种情况只发生在泛型方法中呢?

by
不正确。这是一个合法的方法调用,因此使用Java反射调用它是完全可行的。你甚至不需要设置setAccessible。请参阅我的回答以获取示例:https://ask.clojure.org/index.php/4255/cannot-resolve-public-generic-method-package-private-class?show=12483#a12483
0
by

评论由:stuart.sierra 提出

根据Rich Hickey的说法,桥接方法的存在是不确定的,并且在不同版本的JDK之间不一致。

一个可能的解决方案是使用ASM检查第三方Java类的字节码,而不是反射API。这样,Clojure编译器将能够访问与Java编译器相同的信息。

0
by

评论者:jafingerhut

CLJ-1183作为本问题的重复问题已被关闭。在此提及,以防任何在此条目上工作的同事需要跟随链接阅读其中的讨论或测试用例。

0
by

评论者:noamb

我目前使用的一种权宜之计是定义一个新的Java类,添加一个静态方法来完成我需要的操作,然后从Clojure中调用它。

0
by

评论者:noamb

此外,我还在1.6和1.7(alpha5)版本中看到了这个问题,但是问题提到的问题只提到了1.5版本。

0
by

评论者:adamtait

在使用谷歌云API时遇到了这个问题。
要使用谷歌云数据存储,您需要访问保护泛型子类(BaseKey)上的方法(链接:[https://github.com/GoogleCloudPlatform/gcloud-java/blob/v0.1.6/gcloud-java-datastore/src/main/java/com/google/gcloud/datastore/BaseKey.java#L96](https://github.com/GoogleCloudPlatform/gcloud-java/blob/v0.1.6/gcloud-java-datastore/src/main/java/com/google/gcloud/datastore/BaseKey.java#L96) 文本:.kind),这个方法扩展了(链接:[https://github.com/GoogleCloudPlatform/gcloud-java/blob/v0.1.6/gcloud-java-datastore/src/main/java/com/google/gcloud/datastore/KeyFactory.java#L25](https://github.com/GoogleCloudPlatform/gcloud-java/blob/v0.1.6/gcloud-java-datastore/src/main/java/com/google/gcloud/datastore/KeyFactory.java#L25) 文本:KeyFactory)。

在1.7和1.8的Clojure版本中运行测试后,以下异常依然存在;
{quote}
IllegalArgumentException:不能调用非公共类中的公共方法:公共方法 未能在 中找到匹配的方法。
{quote}

0
jira

评论者:kstrempel

我在使用谷歌云API时遇到了相同的问题。

在1.8和1.9.0-alpha7上测试过,问题依旧。

0
jira

评论者:kstrempel

我在使用谷歌云API时遇到了相同的问题。

在1.8和1.9.0-alpha7上测试过,问题依旧。

0
jira

评论者:mruza

我也遇到了相同的问题。所附的补丁(链接:^invocation_target_selection.patch)为我解决了该问题。
项目中的所有测试仍然通过,但确实需要专业知识人员对该变量进行审查。

0
jira

评论者:alexs miller

嗨,Michal,

谢谢您关注此事。

  1. 请按照以下链接中创建补丁的正确格式说明:[http://dev.clojure.org/display/community/Developing+Patches](http://dev.clojure.org/display/community/Developing+Patches)
  2. 如果您能提供一些解释来帮助审查,那就太有帮助了。否则,审查员需要从头开始重构您的思想过程。
  3. 在审查之前,这个变化也需要一些测试(虽然编写起来可能不太有趣,但我想这是必要的)。
0

评论者:mruza

我已经根据说明添加了测试并更新了补丁。

以下是涉及到的逻辑。以下是 src/jvm/clojure/lang/Compiler.java 文件的摘录

1462: if(target.hasJavaClass() && target.getJavaClass() != null) 1463: { 1464: List methods = Reflector.getMethods(target.getJavaClass(), args.count(), methodName, false); 1465: if(methods.isEmpty()) 1466: { 1467: method = null; 1468: if(RT.booleanCast(RT.WARN_ON_REFLECTION.deref())) 1469: { 1470: RT.errPrintWriter() 1471: .format("Reflection warning, %s:%d:%d - call to method %s on %s can't be resolved (no such method).\n", 1472: SOURCE_PATH.deref(), line, column, methodName, target.getJavaClass().getName()); 1473: } 1474: } 1475: else 1476: { 1477: int methodidx = 0; 1478: if(methods.size() > 1) 1479: { 1480: ArrayList<Class[]> params = new ArrayList(); 1481: ArrayList<Class> rets = new ArrayList(); 1482: for(int i = 0; i < methods.size(); i++) 1483: { 1484: java.lang.reflect.Method m = (java.lang.reflect.Method) methods.get(i); 1485: params.add(m.getParameterTypes()); 1486: rets.add(m.getReturnType()); 1487: } 1488: methodidx = getMatchingParams(methodName, params, args, rets); 1489: } 1490: java.lang.reflect.Method m = 1491: (java.lang.reflect.Method) (methodidx >= 0 ? methods.get(methodidx) : null); 1492: if(m != null && !Modifier.isPublic(m.getDeclaringClass().getModifiers())) 1493: { 1494: //public method of non-public class, try to find a public descendant 1495: if((type=Reflector.getDeepestPublicDescendant(m.getDeclaringClass(), target.getJavaClass())) == null) 1496: //if descendant not found, try to find an ancestor 1497: m = Reflector.getAsMethodOfPublicBase(m.getDeclaringClass(), m); 1498: } 1499: method = m; 1500: if(method == null && RT.booleanCast(RT.WARN_ON_REFLECTION.deref())) 1501: { 1502: RT.errPrintWriter() 1503: .format("Reflection warning, %s:%d:%d - call to method %s on %s can't be resolved (argument types: %s).\n", 1504: SOURCE_PATH.deref(), line, column, methodName, target.getJavaClass().getName(), getTypeStringForArgs(args)); 1505: } 1506: } 1507: }

  • 第1462行的条件确保目标类型/类的类型已知
  • 第1464行的 {{clojure.lang.Reflector.getMethods()}} 方法返回给定名称在目标类型中定义的所有 public 方法的列表
  • 然后选择最佳方法调用位于第1477-1491行
  • 如果选择的方法的声明类不是 public,则尝试找到一个既是目标类型的超类又是声明所选方法的类的子类的 public 类 - 这是在 {{clojure.lang.Reflector.getDeepestPublicDescendant()}} 方法中实现的
  • 如果找到了这样的类,则用它代替方法声明类来生成调用方法的字节码
  • 如果没有找到这样的类,则尝试在声明所选方法的类的 public 祖先中找到兼容的方法

请注意,这种更改可能导致调用与更改之前不同的方法,正如在 {{选择非公共接口上的方法}} 测试中所示。我认为这种更改是可以接受的,因为它
产生更好的匹配(有关参数类型),调用的方法是
让 clojure 中的方法选择更像 java

0

评论由:stuart.sierra 提出

CLJ-126 描述了在 Java 5 上的一个类似问题。

0
参考: https://clojure.atlassian.net/browse/CLJ-1243 (由 stuart.sierra 报告)
0

以下是一个在 Java 上工作但在 Clojure 上失败的视频代码的例子。示例类来自 [org.opensearch.client/opensearch-java "2.1.0"] 库。

此 Java 代码没有问题地运行

package example;

import org.opensearch.client.opensearch.core.bulk.IndexOperation;

import java.io.IOException;
import java.util.Map;

public class Example {
    public static void main(String[] args) throws IOException {
        new IndexOperation.Builder<Map<?, ?>>()
                .index("test-index")
                .id("test-id")
                .document(Map.of("test-document", 123))
                .build();
    }
}

但 Clojure 版本无法使用 Java 互操作编写。绕过方法是手动使用反射调用这些方法

(ns example
  (:import (org.opensearch.client.opensearch.core.bulk IndexOperation$Builder)))

(defn bug []
  (-> (IndexOperation$Builder.)
      (.index "test-index")
      (.id "test-id")
      (.document {"test-document" 123})
      (.build)))

(defn workaround []
  (let [bulk-builder-index (-> (Class/forName "org.opensearch.client.opensearch.core.bulk.BulkOperationBase$AbstractBuilder")
                               (.getDeclaredMethod "index" (into-array Class [String])))
        bulk-builder-id (-> (Class/forName "org.opensearch.client.opensearch.core.bulk.BulkOperationBase$AbstractBuilder")
                            (.getDeclaredMethod "id" (into-array Class [String])))]
    (-> (IndexOperation$Builder.)
        ^IndexOperation$Builder ((fn [builder index]
                                   (.invoke bulk-builder-index builder (into-array Object [index])))
                                 "test-index")
        ^IndexOperation$Builder ((fn [builder id]
                                   (.invoke bulk-builder-id builder (into-array Object [id])))
                                 "test-id")
        (.document {"test-document" 123})
        (.build))))

(comment
  (bug)
  (workaround))

当使用 Clojure 1.11.1 加载此命名空间时,它会给出以下反射警告

Reflection warning, .../example.clj:6:7 - call to method index on org.opensearch.client.opensearch.core.bulk.IndexOperation$Builder can't be resolved (argument types: java.lang.String).
Reflection warning, .../example.clj:7:7 - call to method id can't be resolved (target class is unknown).
Reflection warning, .../example.clj:8:7 - call to method document can't be resolved (target class is unknown).
Reflection warning, .../example.clj:9:7 - reference to field build can't be resolved.

当执行有问题的功能时,它会因为异常而失败

Execution error (IllegalArgumentException) at example/bug (example.clj:6).
Can't call public method of non-public class: public final org.opensearch.client.opensearch.core.bulk.BulkOperationBase$AbstractBuilder org.opensearch.client.opensearch.core.bulk.BulkOperationBase$AbstractBuilder.index(java.lang.String)

IndexOperation$Builder 是一个公开的类,它扩展了受保护的抽象类 WriteOperation$AbstractBuilder,该类又扩展了受保护的抽象类 BulkOperationBase$AbstractBuilder。`index` 和 `id` 方法返回类型是类级别类型参数,但方法参数不是泛型的。

以下是库代码中的相关位置

...