【问题标题】:Clojure: working with a java.util.HashMap in an idiomatic Clojure fashionClojure:以惯用的 Clojure 方式使用 java.util.HashMap
【发布时间】:2009-11-03 03:43:07
【问题描述】:

我有一个 java.util.HashMap 对象 m(调用 Java 代码的返回值),我想获得一个带有附加键值对的新映射。

如果 m 是 Clojure 映射,我可以使用:

(assoc m "key" "value")

但是在HashMap 上尝试会得到:

java.lang.ClassCastException:java.util.HashMap 无法转换为 clojure.lang.Associative

seq 也没有运气:

(assoc (seq m) "key" "value")

java.lang.ClassCastException: clojure.lang.IteratorSeq 无法转换为 clojure.lang.Associative

我设法做到这一点的唯一方法是使用HashMap 自己的put,但这会返回void,所以我必须明确返回m

(do (. m put "key" "value") m)

这不是惯用的 Clojure 代码,而且我正在修改 m 而不是创建新地图。

如何以更接近 Clojure 的方式使用 HashMap

【问题讨论】:

  • 我认为这不可能。 Java 的 HashMap 不是持久数据结构,所以要么修改它,要么克隆它并修改克隆版本。

标签: clojure interop hashmap purely-functional clojure-java-interop


【解决方案1】:

Clojure 使 java Collections 可序列化,因此您可以直接在 java.util.HashMap 上使用 Clojure 序列函数。

但是 assoc 需要一个 clojure.lang.Associative 所以你必须首先将 java.util.HashMap 转换为:

(assoc (zipmap (.keySet m) (.values m)) "key" "value")

编辑:更简单的解决方案:

(assoc (into {} m) "key" "value")

【讨论】:

  • 但是你没有 hashmap,你有一个 clojure map,这似乎违背了他的目标。
  • 所以 (assoc (into {} m) "key" "value") 而不是 (assoc m "key" "value")。美丽的!谢谢。
  • 我相信,如果您再次需要 HashMap,您可以创建一个新的。 (java.util.HashMap.m)
  • 我最近在大型数据上使用了这种方法,我发现它对于嵌套地图等非常慢。我最终只是找到了一个 clojure 库,这样我就不必处理 Java 地图了。跨度>
【解决方案2】:

如果您正在与 Java 代码交互,您可能不得不硬着头皮使用 Java 方式,使用 .put。这不一定是大罪; Clojure 专门为您提供了 do. 之类的东西,因此您可以轻松地使用 Java 代码。

assoc 仅适用于 Clojure 数据结构,因为已经进行了大量工作以降低创建新的(不可变的)副本的成本,只需稍作改动即可。 Java HashMap 并非旨在以相同的方式工作。每次进行更改时都必须继续克隆它们,这可能会很昂贵。

如果您真的想摆脱 Java 变异领域(例如,也许您将这些 HashMaps 保留很长时间并且不希望 Java 调用到处都是,或者您需要通过 @987654325 序列化它们@ 和 read,或者您想使用 Clojure STM 以线程安全的方式使用它们)您可以轻松地在 Java HashMap 和 Clojure 哈希映射之间进行转换,因为 Clojure 数据结构实现了正确的 Java 接口,因此它们可以互相交谈。

user> (java.util.HashMap. {:foo :bar})
#<HashMap {:foo=:bar}>

user> (into {} (java.util.HashMap. {:foo :bar}))
{:foo :bar}

如果你想要一个类似do 的东西,一旦你完成了它就会返回你正在处理的对象,你可以使用doto。事实上,Java HashMap 在官方文档中用作该函数的示例,这再次表明,如果您(明智地)使用 Java 对象,这并不是世界末日。

clojure.core/doto
([x & forms])
Macro
  Evaluates x then calls all of the methods and functions with the
  value of x supplied at the front of the given arguments.  The forms
  are evaluated in order.  Returns x.

  (doto (new java.util.HashMap) (.put "a" 1) (.put "b" 2))

一些可能的策略:

  1. 如果可以,请将您的突变和副作用限制在单个函数中。如果您的函数在给定相同输入的情况下始终返回相同的值,则它可以在内部做任何想做的事情。有时,改变数组或映射是实现算法的最有效或最简单的方法。只要您不向世界其他地方“泄露”副作用,您仍然可以享受函数式编程的好处。

  2. 1234563最后一秒(将它们反馈给 Java 时)。

【讨论】:

  • 感谢您的详细解释。非常有用和有趣。我学到了关于“doto”而不是“do”的技巧,这是在 Java 对象上调用 void 方法并在最后取回它们的更优雅的方式。你会认为我会意识到这一点,因为我正在阅读 Stu 的书 ;-)
  • btw 在 Clojure 1.7 (2015) 和一些早期版本中,java.util.HashMap 现在甚至以 Clojurely 方式打印出来:(java.util.HashMap. {:a 1 :b 2}) 在回复中打印为“{:a 1, : b 2}",即使它仍然是 java.util.HashMap。例如assoc 不会处理它。
【解决方案3】:

传统方式使用java hash map完全没问题。
(do (. m put "key" "value") m)
This is not idiomatic Clojure code, plus I'm modifying m instead of creating a new map.

您正在修改一个真正打算修改的数据结构。 Java 的哈希映射缺乏结构共享,这使得 Clojures 映射可以被有效地复制。通常惯用的方法是使用 java-interop 函数以典型的 java 方式处理 java 结构,或者将它们干净地转换为 Clojure 结构并以函数式 Clojure 方式使用它们。当然,除非它使生活更轻松并产生更好的代码;那么所有的赌注都被取消了。

【讨论】:

    【解决方案4】:

    这是我在尝试比较 clojure 版本与 java 的内存特性时使用 hashmaps 编写的一些代码(但从 clojure 使用)

    (import '(java.util Hashtable))
    (定义频率2 [coll]
        (让[mydict(新哈希表)]
          (减少 (fn [计数 x]
                (让 [y (.toLowerCase x)]
                  (如果(.get mydict y)
                (.put mydict y (+ (.get mydict y) 1))
                (.put mydict y 1)))) coll) mydict))
    

    这是获取一些集合并返回每个不同的事物(比如字符串中的一个词)被重复使用的次数。

    【讨论】:

      猜你喜欢
      • 2014-03-12
      • 1970-01-01
      • 1970-01-01
      • 2014-02-21
      • 2010-12-08
      • 2013-01-28
      • 1970-01-01
      • 2011-08-13
      • 2011-07-22
      相关资源
      最近更新 更多