如何在 Java 中测试类是否正确实现了 Serializable(不仅仅是 Serializable 的实例)

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/3840356/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-14 05:42:14  来源:igfitidea点击:

how to test in Java that a class implements Serializable correctly (not just is an instance of Serializable)

javaserialization

提问by Jason S

I am implementing a class to be Serializable (so it's a value object for use w/ RMI). But I need to test it. Is there a way to do this easily?

我正在实现一个可序列化的类(因此它是一个使用 RMI 的值对象)。但我需要测试一下。有没有办法轻松做到这一点?

clarification: I'm implementing the class, so it's trivial to stick Serializable in the class definition. I need to manually serialize/deserialize it to see if it works.

澄清:我正在实现这个类,所以在类定义中坚持 Serializable 是微不足道的。我需要手动序列化/反序列化它以查看它是否有效。

I found this C# question, is there a similar answer for Java?

我发现了这个C# 问题,Java 有类似的答案吗?

采纳答案by skaffman

The easy way is to check that the object is an instance of java.io.Serializableor java.io.Externalizable, but that doesn't really prove that the object really is serializable.

简单的方法是检查对象是否是java.io.Serializableor的实例java.io.Externalizable,但这并不能真正证明该对象确实是可序列化的。

The only way to be sure is to try it for real. The simplest test is something like:

唯一可以确定的方法是实际尝试。最简单的测试是这样的:

new ObjectOutputStream(new ByteArrayOutputStream()).writeObject(myObject);

and check it doesn't throw an exception.

并检查它没有抛出异常。

Apache Commons Langprovides a rather more brief version:

Apache Commons Lang提供了一个更简洁的版本:

SerializationUtils.serialize(myObject);

and again, check for the exception.

再次检查异常。

You can be more rigourous still, and check that it deserializes back into something equal to the original:

您还可以更严格,并检查它是否反序列化回与原始内容相同的内容:

Serializable original = ...
Serializable copy = SerializationUtils.clone(original);
assertEquals(original, copy);

and so on.

等等。

回答by YoK

You can do following test:

您可以进行以下测试:

  • Serialize object to file and make sure no exception is thrown.
  • Additionally, deserialize object back and compare with original object.
  • 将对象序列化到文件并确保没有抛出异常。
  • 此外,反序列化对象并与原始对象进行比较。

Here is example for serializing and deserializing object to file:

这是将对象序列化和反序列化到文件的示例:

http://www.rgagnon.com/javadetails/java-0075.html

http://www.rgagnon.com/javadetails/java-0075.html

http://www.javapractices.com/topic/TopicAction.do?Id=57

http://www.javapractices.com/topic/TopicAction.do?Id=57

回答by Andrzej Doyle

The short answer is, you can come up with some candidate objects and actually try to serialize them using the mechanism of your choice. The test here is that no errors are encountered during marshalling/unmarshalling, and that the resulting "rehydrated" object is equal to the original.

简短的回答是,您可以提出一些候选对象并实际尝试使用您选择的机制序列化它们。这里的测试是在编组/解组过程中没有遇到错误,并且生成的“再水化”对象与原始对象相同。

Alternatively, if you don't have any candidate objects, you could implement a reflection-based test that introspects the (non-static, non-transient) fields of your class to ensure that they too are Serializable. Speaking from experience, this gets surprisingly complex surprisingly quickly, but it can be done to a reasonable extent.

或者,如果您没有任何候选对象,您可以实现一个基于反射的测试,该测试对您的类的(非静态、非瞬态)字段进行自省,以确保它们也是可序列化的。从经验来看,这会变得非常复杂,出奇地快,但可以在合理的范围内完成。

The downside to this latter approach is that if a field is e.g. List<String>, then you can either fail the class for not having a strictly serializable field, or simply assume that a serializable implementation of List will be used. Neither is perfect. (Mind you, the latter problem exists for examples too; if every example used in the test uses serializable Lists, there's nothing to prevent a non-serializable version being used by some other code in practice).

后一种方法的缺点是,如果一个字段是 eg List<String>,那么您可以因为没有严格的可序列化字段而使类失败,或者只是假设将使用 List 的可序列化实现。两者都不完美。(请注意,后一个问题也存在于示例中;如果测试中使用的每个示例都使用可序列化列表,则在实践中没有什么可以阻止其他代码使用不可序列化版本)。

回答by TofuBeer

This code should do it...

这段代码应该可以...

import java.io.ByteArrayOutputStream;
import java.io.Externalizable;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.io.OutputStream;
import java.io.Serializable;

public class Main
{
    public static void main(String[] args)
    {
        System.out.println(isSerializable("Hello"));
        System.out.println(isSerializable(new Main()));
    }

    public static boolean isSerializable(final Object o)
    {
        final boolean retVal;

        if(implementsInterface(o))
        {
            retVal = attemptToSerialize(o);
        }
        else
        {
            retVal = false;
        }

        return (retVal);
    }

    private static boolean implementsInterface(final Object o)
    {
        final boolean retVal;

        retVal = ((o instanceof Serializable) || (o instanceof Externalizable));

        return (retVal);
    }

    private static boolean attemptToSerialize(final Object o)
    {
        final OutputStream sink;
        ObjectOutputStream stream;

        stream = null;

        try
        {
            sink   = new ByteArrayOutputStream();
            stream = new ObjectOutputStream(sink);
            stream.writeObject(o);
            // could also re-serilalize at this point too
        }
        catch(final IOException ex)
        {
            return (false);
        }
        finally
        {
            if(stream != null)
            {
                try
                {
                    stream.close();
                }
                catch(final IOException ex)
                {
                    // should not be able to happen
                }
            }
        }

        return (true);
    }
}

回答by Jason S

utility methods based on skaffman's answer:

基于 skaffman 答案的实用方法:

private static <T extends Serializable> byte[] pickle(T obj) 
       throws IOException 
{
    ByteArrayOutputStream baos = new ByteArrayOutputStream();
    ObjectOutputStream oos = new ObjectOutputStream(baos);
    oos.writeObject(obj);
    oos.close();
    return baos.toByteArray();
}

private static <T extends Serializable> T unpickle(byte[] b, Class<T> cl)
       throws IOException, ClassNotFoundException 
{
    ByteArrayInputStream bais = new ByteArrayInputStream(b);
    ObjectInputStream ois = new ObjectInputStream(bais);
    Object o = ois.readObject();
    return cl.cast(o);
}

回答by ouster

This only works for fully populated objects, if you require that any objects composed in your top level object are also serializable then they cannot be null for this test to be valid as serialization/deserialization skips the null objects

这仅适用于完全填充的对象,如果您要求在顶级对象中组成的任何对象也是可序列化的,那么它们不能为 null 以便此测试有效,因为序列化/反序列化跳过空对象

回答by wu-lee

I've attempted to write a unit test (in Groovy using Spock) which can check that a given interface for use with RMI is in fact fully serializable - all the parameters, exceptions, and possible implementations of types defined in the methods.

我试图编写一个单元测试(在 Groovy 中使用 Spock),它可以检查用于 RMI 的给定接口实际上是完全可序列化的——所有参数、异常和方法中定义的类型的可能实现。

It seems to work for me so far, however, this is a bit fiddly to do and there may be cases this doesn't cover, so use at your own risk!

到目前为止,它似乎对我有用,但是,这有点麻烦,而且可能有些情况不包括在内,所以使用风险自负!

You will need to replace the example interfaces Notificationetc. with your own. The example includes one unserializable field as an illustration.

您需要Notification用自己的接口替换示例接口等。该示例包括一个不可序列化的字段作为说明。

package example

import groovy.transform.CompileDynamic
import groovy.transform.CompileStatic
import spock.lang.Specification

import java.lang.reflect.*
import java.rmi.Remote
import java.rmi.RemoteException

/** This checks that the a remoting API NotifierServer is safe
 *
 * It attempts to flush out any parameter classes which are
 * not Serializable. This isn't checked at compile time!
 *
 */
@CompileStatic
class RemotableInterfaceTest extends Specification {
    static class NotificationException extends RuntimeException {
        Object unserializable
    }

    static interface Notification {
        String getMessage()

        Date getDate()
    }

    static interface Notifier extends Remote {
        void accept(Notification notification) throws RemoteException, NotificationException
    }


    static interface NotifierServer extends Remote {
        void subscribe(Notification notifier) throws RemoteException
        void notify(Notification message) throws RemoteException
    }

    // From https://www.javaworld.com/article/2077477/learn-java/java-tip-113--identify-subclasses-at-runtime.html
    /**
     * Scans all classes accessible from the context class loader which belong to the given package and subpackages.
     *
     * @param packageName The base package
     * @return The classes
     * @throws ClassNotFoundException
     * @throws IOException
     */
    static Class[] getClasses(String packageName)
            throws ClassNotFoundException, IOException {
        ClassLoader classLoader = Thread.currentThread().getContextClassLoader()
        assert classLoader != null
        String path = packageName.replace('.', '/')
        Enumeration resources = classLoader.getResources(path)
        List<File> dirs = new ArrayList()
        while (resources.hasMoreElements()) {
            URL resource = resources.nextElement()
            dirs.add(new File(resource.getFile()))
        }
        ArrayList classes = new ArrayList()
        for (File directory : dirs) {
            classes.addAll(findClasses(directory, packageName))
        }
        return classes.toArray(new Class[classes.size()])
    }

    /**
     * Recursive method used to find all classes in a given directory and subdirs.
     *
     * @param directory   The base directory
     * @param packageName The package name for classes found inside the base directory
     * @return The classes
     * @throws ClassNotFoundException
     */
    static List<Class> findClasses(File directory, String packageName) throws ClassNotFoundException {
        List<Class> classes = new ArrayList()
        if (!directory.exists()) {
            return classes
        }
        File[] files = directory.listFiles()
        for (File file : files) {
            if (file.isDirectory()) {
                //assert !file.getName().contains(".");
                classes.addAll(findClasses(file, packageName + "." + file.getName()))
            } else if (file.getName().endsWith(".class")) {
                classes.add(Class.forName(packageName + '.' + file.getName().substring(0, file.getName().length() - 6)))
            }
        }
        return classes
    }

    /** Finds all known subclasses of a class */
    @CompileDynamic
    static List<Class> getSubclasses(Class type) {
        allClasses
            .findAll { Class it ->
                !Modifier.isAbstract(it.modifiers) &&
                it != type &&
                type.isAssignableFrom(it)
            }
    }

    /** Checks if a type is nominally serializable or remotable.
     *
     * Notes:
     * <ul>
     * <li> primitives are implicitly serializable
     * <li> interfaces are serializable or remotable by themselves, but we
     * assume that since #getSerializedTypes checks derived types of interfaces,
     * we can safely assume that all implementations will be checked
     *</ul>
     *
     * @param it
     * @return
     */
    static boolean isSerializableOrRemotable(Class<?> it) {
        return it.primitive || it.interface || Serializable.isAssignableFrom(it) || Remote.isAssignableFrom(it)
    }

    /** Recursively finds all (new) types associated with a given type 
     * which need to be serialized because they are fields, parameterized
     * types, implementations, etc. */
    static void getSerializedTypes(final Set<Class<?>> types, Type... it) {
        for(Type type in it) {
            println "type: $type.typeName"

            if (type instanceof GenericArrayType) {
                type = ((GenericArrayType)type).genericComponentType
            }

            if (type instanceof ParameterizedType) {
                ParameterizedType ptype = (ParameterizedType)type
                getSerializedTypes(types, ptype.actualTypeArguments)
                break
            }


            if (type instanceof Class) {
                Class ctype = (Class)type

                if (ctype == Object)
                    break

                if (types.contains(type))
                    break

                types << ctype
                for (Field field : ctype.declaredFields) {
                    println "${ctype.simpleName}.${field.name}: ${field.type.simpleName}"
                    if (Modifier.isVolatile(field.modifiers) ||
                        Modifier.isTransient(field.modifiers) ||
                        Modifier.isStatic(field.modifiers))
                        continue

                    Class<?> fieldType = field.type
                    if (fieldType.array)
                        fieldType = fieldType.componentType

                    if (types.contains(fieldType))
                        continue

                    types << fieldType
                    if (!fieldType.primitive)
                        getSerializedTypes(types, fieldType)
                }

                if (ctype.genericSuperclass) {
                    getSerializedTypes(types, ctype.genericSuperclass)
                }

                getSubclasses(ctype).each { Class c -> getSerializedTypes(types, c) }

                break
            }
        }
    }

    /** Recursively checks a type's methods for related classes which
     * need to be serializable if the type is remoted */
    static Set<Class<?>> getMethodTypes(Class<?> it) {
        Set<Class<?>> types = []
        for(Method method: it.methods) {
            println "method: ${it.simpleName}.$method.name"
            getSerializedTypes(types, method.genericParameterTypes)
            getSerializedTypes(types, method.genericReturnType)
            getSerializedTypes(types, method.genericExceptionTypes)
        }
        return types
    }

    /** All the known defined classes */
    static List<Class> allClasses = Package.packages.collectMany { Package p -> getClasses(p.name) as Collection<Class> }


    @CompileDynamic
    def "NotifierServer interface should only expose serializable or remotable types"() {
        given:
        Set<Class> types = getMethodTypes(NotifierServer)

        Set<Class> nonSerializableTypes = types.findAll { !isSerializableOrRemotable(it) }

        expect:
        nonSerializableTypes.empty
    }

}