我怎样才能优雅地序列化lambda?
例如,下面的代码抛出一个NotSerializableException.如何在不创建SerializableRunnable"虚拟"界面的情况下修复它?
public static void main(String[] args) throws Exception {
File file = Files.createTempFile("lambda", "ser").toFile();
try (ObjectOutput oo = new ObjectOutputStream(new FileOutputStream(file))) {
Runnable r = () -> System.out.println("Can I be serialized?");
oo.writeObject(r);
}
try (ObjectInput oi = new ObjectInputStream(new FileInputStream(file))) {
Runnable r = (Runnable) oi.readObject();
r.run();
}
}
Run Code Online (Sandbox Code Playgroud) 假设我有一个使用lambda表达式(闭包)定义的对象列表.有没有办法检查它们,以便可以进行比较?
我最感兴趣的代码是
List<Strategy> strategies = getStrategies();
Strategy a = (Strategy) this::a;
if (strategies.contains(a)) { // ...
Run Code Online (Sandbox Code Playgroud)
完整的代码是
import java.util.Arrays;
import java.util.List;
public class ClosureEqualsMain {
interface Strategy {
void invoke(/*args*/);
default boolean equals(Object o) { // doesn't compile
return Closures.equals(this, o);
}
}
public void a() { }
public void b() { }
public void c() { }
public List<Strategy> getStrategies() {
return Arrays.asList(this::a, this::b, this::c);
}
private void testStrategies() {
List<Strategy> strategies = getStrategies();
System.out.println(strategies);
Strategy a = (Strategy) …Run Code Online (Sandbox Code Playgroud) 我注意到在执行中有些奇怪HashMap.clear().这就是它在OpenJDK 7u40中的表现:
public void clear() {
modCount++;
Arrays.fill(table, null);
size = 0;
}
Run Code Online (Sandbox Code Playgroud)
这就是OpenJDK 8u40的外观:
public void clear() {
Node<K,V>[] tab;
modCount++;
if ((tab = table) != null && size > 0) {
size = 0;
for (int i = 0; i < tab.length; ++i)
tab[i] = null;
}
}
Run Code Online (Sandbox Code Playgroud)
我知道现在table为空地图可以为null,因此需要在局部变量中进行额外的检查和缓存.但为什么被Arrays.fill()for-loop取代?
似乎在此提交中引入了更改.不幸的是,我没有找到解释为什么普通for循环可能比更好Arrays.fill().它更快吗?还是更安全?
我可以使用以下语法序列化lambda:
Runnable r = (Runnable & Serializable) () -> System.out.println("");
try (ObjectOutput oo = new ObjectOutputStream(new ByteArrayOutputStream())) {
oo.writeObject(r);
}
Run Code Online (Sandbox Code Playgroud)
但是,如果我从客户端代码收到lambda并且它没有被正确转换,我就无法序列化它.
如何r在不更改其定义的情况下序列化以下内容:
Runnable r = () -> System.out.println("");
Run Code Online (Sandbox Code Playgroud)
我试图序列化"派生"对象:
Runnable r1 = (Runnable & Serializable) r::run;
Runnable r2 = (Runnable & Serializable) () -> r.run();
Run Code Online (Sandbox Code Playgroud)
但在每种情况下,都oo.writeObject(rxxx);失败了NotSerializableException.
就像一个小项目一样,我一直试图做一个小东西,读取序列化的lambdas(本地或从FTP)并调用它们的运行函数作为测试的一部分来试验Windows中的文件关联(即打开某些文件类型)用某个程序打开它们等等,但无论我尝试什么,它似乎都没有正确地反序列化.
lambda被宣布为这样
Runnable r = (Runnable & Serializable) () -> {
// blah blah
// made sure not to capture anything
};
Run Code Online (Sandbox Code Playgroud)
并使用由ObjectOutputStream包装的[n optional] BufferedOutputStream包装的FileOutputStream进行序列化,没有问题.但是,当[在不同的项目中]反序列化时,它会失败,说它无法找到包含序列化代码的封闭类.我已经尝试过各种各样的东西,比如将它们包装在一个可序列化的类中(用于测试目的的是用serialVersionUID = 0L)或者定义一个扩展Runnable和Serializable的接口,但无济于事.
是的,我知道序列化lambda不是很好的做法(或者我们被告知),但我不知道如何将函数和子程序转换成我可以存储为文件或FTP的东西.如果这根本不是正确的方法,请告诉我们.
哦,我正在使用最新版本的Eclipse Luna.
编辑:
像这样反序列化
File f = new File(somePath);
FileInputStream fish = new FileInputStream(f);
BufferedInputStream bos = new BufferedInputStream(fish); // not really necessary
ObjectInputStream ois = new ObjectInputStream(bos);
Runnable r = (Runnable) ois.readObject();
ois.close();
r.run();
Run Code Online (Sandbox Code Playgroud) 尽管可以在Java 8中序列化lambda,但强烈建议不要这样做.甚至不鼓励对内部类进行序列化.给出的原因是lambdas可能不会在另一个JRE上正确地反序列化.然而,这并不意味着这有是一个办法可以安全的序列化拉姆达?
例如,假设我将类定义为:
public class MyClass {
private String value;
private Predicate<String> validateValue;
public MyClass(String value, Predicate<String> validate) {
this.value = value;
this.validateValue = validate;
}
public void setValue(String value) {
if (!validateValue(value)) throw new IllegalArgumentException();
this.value = value;
}
public void setValidation(Predicate<String> validate) {
this.validateValue = validate;
}
}
Run Code Online (Sandbox Code Playgroud)
如果我像这样声明了类的实例,我不应该序列化它:
MyClass obj = new MyClass("some value", (s) -> !s.isEmpty());
Run Code Online (Sandbox Code Playgroud)
但是如果我像这样创建了一个类的实例呢:
// Could even be a static nested class
public class IsNonEmpty …Run Code Online (Sandbox Code Playgroud) 刚刚介绍Streams和Java 8 Lambda功能,以及对其他不言自明的Oracle doc Lambda Expressions的最后评论:
如果lambda表达式的目标类型及其捕获的参数是可序列化的,则可以序列化它.但是,与内部类一样,强烈建议不要对lambda表达式进行序列化.
检查这一点我发现了SO问题
OP处理来自客户端代码的序列化lambda表达式.
如果我有一个互联网服务和参数之一是一个lambda表达式,现在看来,这可能包含恶意代码,可以做这样的事情的文件系统访问权限,或导致堆栈溢出 - 所以这将是非常愚蠢的信任它.
我是否过度夸大了安全风险,或者序列化表达式可以包含什么限制?
我正在尝试学习序列化如何与Java及其最新版本一起使用.我正在尝试序列化这样的lambda:
Runnable r = (Runnable & Serializable)() -> {System.out.println("This is a test");};
Run Code Online (Sandbox Code Playgroud)
但我注意到我没有关于缺少serialVersionUID变量的警告.这是正常的吗?
我知道它将在运行时生成,但强烈建议您定义它:https://docs.oracle.com/javase/8/docs/api/java/io/Serializable.html
如果可序列化类未显式声明serialVersionUID,则序列化运行时将基于类的各个方面计算该类的默认serialVersionUID值,如Java(TM)对象序列化规范中所述.但是,强烈建议所有可序列化类显式声明serialVersionUID值,因为默认的serialVersionUID计算对类细节高度敏感,这些细节可能因编译器实现而异,因此在反序列化期间可能导致意外的InvalidClassExceptions.因此,为了保证跨不同java编译器实现的一致的serialVersionUID值,可序列化类必须声明显式的serialVersionUID值.强烈建议显式serialVersionUID声明尽可能使用private修饰符,因为此类声明仅适用于立即声明的类 - serialVersionUID字段作为继承成员无用.数组类不能声明显式的serialVersionUID,因此它们始终具有默认的计算值,但是对于数组类,不需要匹配serialVersionUID值.
我该怎么办 ?如何在Lambda中定义它?
谢谢