从json返回对象会产生java.lang.RuntimeException:无法调用没有args

时间:2017-03-16 05:50:15

标签: java android json gson

我正在尝试在android上开发一个SMS分类器应用程序。我正在使用https://github.com/ptnplanet/Java-Naive-Bayes-Classifier foo中的Java朴素贝叶斯分类器。

在使用Netbeans学习数据集后,我将Classifier对象保存为json并将其复制到android studio中的assets文件夹中。当我将json转换回Classifier对象时,我得到以下异常 java.lang.RuntimeException:无法调用没有args的公共java.util.Dictionary()。 我只是想将消息输入编辑文本并找到其类别。

这是我的 MainActivity.java

package com.example.sidyeti.smsclassifier;

import android.content.res.AssetManager;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;

import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;

import org.json.JSONException;
import org.json.JSONObject;

import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Type;
import java.util.Arrays;

import de.daslaboratorium.machinelearning.classifier.Classifier;
import de.daslaboratorium.machinelearning.classifier.bayes.BayesClassifier;

public class MainActivity extends AppCompatActivity {

    EditText editText;
    Button submitButton;
    TextView categoryTextView;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        editText= (EditText) findViewById(R.id.message_edit_text);
        categoryTextView= (TextView) findViewById(R.id.category_text_view);
        submitButton= (Button) findViewById(R.id.submit_button);
        submitButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                    String message=editText.getText().toString().trim();
                    Gson gson=new Gson();
                    Classifier<String,String> bayes;//=new BayesClassifier<String, String>();
                    Type bayesType=new TypeToken<BayesClassifier<String,String>>(){}.getType();
                    bayes=gson.fromJson(loadJSONFromAsset(),bayesType);
                    String words[]=message.split("\\s");
                    String categoryResult=bayes.classify(Arrays.asList(words)).getCategory();
                    categoryTextView.setText(categoryResult);
            }
        });

    }
    public String loadJSONFromAsset() {
        String json = null;
        try {
                InputStream is = getAssets().open("bayesObject.json");
                int size = is.available();
                byte[] buffer = new byte[size];
                is.read(buffer);
                is.close();
                json = new String(buffer, "UTF-8");

        } catch (IOException ex) {
            ex.printStackTrace();
            return null;
        }
        return json;
        }
}

这是我的布局:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/activity_main"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    tools:context="com.example.sidyeti.smsclassifier.MainActivity">

    <EditText
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:hint="Message here"
        android:id="@+id/message_edit_text"
        />
    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Submit"
        android:layout_below="@id/message_edit_text"
        android:layout_centerHorizontal="true"
        android:id="@+id/submit_button"
        />
    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Category"
        android:textSize="20sp"
        android:textStyle="bold"
        android:layout_below="@id/submit_button"
        android:layout_centerHorizontal="true"
        android:id="@+id/category_text_view"
        />
</RelativeLayout>

这是错误日志:

FATAL EXCEPTION: main
Process: com.example.sidyeti.smsclassifier, PID: 22953
java.lang.RuntimeException: Failed to invoke public java.util.Dictionary() with no args
at com.google.gson.internal.ConstructorConstructor$3.construct(ConstructorConstructor.java:111)
at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$Adapter.read(ReflectiveTypeAdapterFactory.java:210)
at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$1.read(ReflectiveTypeAdapterFactory.java:129)
at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$Adapter.read(ReflectiveTypeAdapterFactory.java:220)
at com.google.gson.Gson.fromJson(Gson.java:887)
at com.google.gson.Gson.fromJson(Gson.java:852)
at com.google.gson.Gson.fromJson(Gson.java:801)
at com.example.sidyeti.smsclassifier.MainActivity$1.onClick(MainActivity.java:44)
at android.view.View.performClick(View.java:5207)
at android.view.View$PerformClick.run(View.java:21177)
at android.os.Handler.handleCallback(Handler.java:739)
at android.os.Handler.dispatchMessage(Handler.java:95)
at android.os.Looper.loop(Looper.java:148)
at android.app.ActivityThread.main(ActivityThread.java:5441)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:738)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:628)
Caused by: java.lang.InstantiationException: Can't instantiate abstract class java.util.Dictionary
at java.lang.reflect.Constructor.newInstance(Native Method)
at com.google.gson.internal.ConstructorConstructor$3.construct(ConstructorConstructor.java:108)
at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$Adapter.read(ReflectiveTypeAdapterFactory.java:210) 
at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$1.read(ReflectiveTypeAdapterFactory.java:129) 
at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$Adapter.read(ReflectiveTypeAdapterFactory.java:220) 
at com.google.gson.Gson.fromJson(Gson.java:887) 
at com.google.gson.Gson.fromJson(Gson.java:852) 
at com.google.gson.Gson.fromJson(Gson.java:801) 
at com.example.sidyeti.smsclassifier.MainActivity$1.onClick(MainActivity.java:44) 
at android.view.View.performClick(View.java:5207) 
]at android.view.View$PerformClick.run(View.java:21177) 
at android.os.Handler.handleCallback(Handler.java:739) 
]at android.os.Handler.dispatchMessage(Handler.java:95) 
at android.os.Looper.loop(Looper.java:148) 
at android.app.ActivityThread.main(ActivityThread.java:5441) 
at java.lang.reflect.Method.invoke(Native Method) 
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:738) 
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:628) 
03-16 10:56:46.511 22953-22953/com.example.sidyeti.smsclassifier I/Process: Sending signal. PID: 22953 SIG: 9

这是正确的方法还是我做错了?

1 个答案:

答案 0 :(得分:1)

您收到该错误的原因是Gson没有java.util.Dictionary的类型适配器。它是一个抽象类,因此它无法实例化为Gson报告,显然是最常见的异常原因:

  

引起:java.lang.InstantiationException:无法实例化抽象类java.util.Dictionary

这是根本原因。为什么会发生这种情况:基类分类器类Classifier具有以下字段,使Gson无法选择:

private Dictionary<K, Dictionary<T, Integer>> featureCountPerCategory;
private Dictionary<T, Integer> totalFeatureCount;
private Dictionary<K, Integer> totalCategoryCount;

如果您发布JSON而不是所有Android内容会更好,因为后者与您的问题无关。所以,首先,我已经通过反射直接创建了一个分类器模拟,以强调它在引擎盖下的工作方式:

final class ClassifierMocks {

    private ClassifierMocks() {
    }

    static Classifier<String, String> createBayesClassifierMock() {
        try {
            final Classifier<String, String> classifier = new BayesClassifier<>();
            final Dictionary<String, Integer> totalFeatureCount = new Hashtable<>();
            totalFeatureCount.put("foo-feature", 1);
            totalFeatureCount.put("bar-feature", 2);
            final Dictionary<String, Integer> totalCategoryCount = new Hashtable<>();
            totalCategoryCount.put("foo-category", 1);
            totalCategoryCount.put("bar-category", 2);
            final Collection<Classification<String, String>> memoryQueue = new LinkedList<>();
            memoryQueue.add(new Classification<>(asList("foo", "bar"), "FOO/BAR", 0.1f));
            memoryQueue.add(new Classification<>(asList("baz", "qux"), "BAZ/QUX", 0.2f));
            assign(classifier, "totalFeatureCount", totalFeatureCount);
            assign(classifier, "totalCategoryCount", totalCategoryCount);
            assign(classifier, "memoryQueue", memoryQueue);
            return classifier;
        } catch ( final NoSuchFieldException | IllegalAccessException ex ) {
            throw new RuntimeException(ex);
        }
    }

    private static void assign(final Classifier<?, ?> classifier, final String name, final Object value)
            throws NoSuchFieldException, IllegalAccessException {
        final Field field = Classifier.class.getDeclaredField(name);
        field.setAccessible(true);
        field.set(classifier, value);
    }

}

接下来,尽管序列化Classifier实例会导致某个JSON(但本身无效),因此需要自定义类型适配器工厂才能为java.util.Dictionary实例创建类型适配器。 (InstanceCreator无济于事,因为它可以将java.util.Dictionary实例化为Hashtable,但不能描述如何解析字典

在实施类型适配器工厂之前,我会对泛型做一个侧面说明。 Classifier个实例是通用的,可以包含任何类型,而JSON对象只能有字符串键(假设JSON对象完全匹配java.util.Dictionary键/值结构),所以应该有某种策略允许将Classfier<T, ...映射到T的字符串,反之亦然。

interface INameMapper<K> {

    String toName(K key);

    K fromName(String name);

}

真正简单的转换器将任何键类型转换为字符串并返回。应该还有一个工厂,因为Gson通过类型适配器工厂传播实际类型(例如,可以为List<Integer>List<String>创建完全不同的类型适配器 - 它是类型适配器工厂的强大功能)。这是:

interface INameMapperFactory {

    <K> INameMapper<K> createNameMapper(Type type)
            throws IllegalArgumentException;

}

让我们创建默认名称映射器工厂以处理字符串分类器。请注意,它可以自行增强:

final class DefaultNameMapperFactory
        implements INameMapperFactory {

    // The factory holds no state, so it can be a global singleton
    private static final INameMapperFactory defaultNameMapperFactory = new DefaultNameMapperFactory();

    private DefaultNameMapperFactory() {
    }

    // But a call-site won't know if the singleton is returned since the latter is encapsulated
    static INameMapperFactory getDefaultNameMapperFactory() {
        return defaultNameMapperFactory;
    }

    @Override
    public <K> INameMapper<K> createNameMapper(final Type type)
            throws IllegalArgumentException {
        final INameMapper<?> nameMapper;
        // Are we asked for java.lang.String keys?
        if ( String.class.equals(type) ) {
            nameMapper = stringNameMapper;
        // ... Enhance if necessary ...
        } else {
            throw new IllegalArgumentException("Unknown type: " + type);
        }
        // Some Java generics boilerplate...
        @SuppressWarnings("unchecked")
        final INameMapper<K> castNameMapper = (INameMapper<K>) nameMapper;
        return castNameMapper;
    }

    // The simplest name mapper ever: strings are always convertible to strings and can return the same instances
    private static final INameMapper<String> stringNameMapper = new INameMapper<String>() {
        @Override
        public String toName(final String key) {
            return key;
        }

        @Override
        public String fromName(final String name) {
            return name;
        }
    };

}

接下来,让我们让Gson感知Dictionary

final class DictionaryTypeAdapterFactory
        implements TypeAdapterFactory {

    // If a dictionary type has no enough type information, let's assume it's raw and holds no type parameters (thus they are objects)
    private static final Type[] objectToObject = { Object.class, Object.class };

    private final INameMapperFactory nameMapperFactory;

    private DictionaryTypeAdapterFactory(final INameMapperFactory nameMapperFactory) {
        this.nameMapperFactory = nameMapperFactory;
    }

    static TypeAdapterFactory getDictionaryTypeAdapterFactory(final INameMapperFactory nameMapperFactory) {
        return new DictionaryTypeAdapterFactory(nameMapperFactory);
    }

    @Override
    public <T> TypeAdapter<T> create(final Gson gson, final TypeToken<T> typeToken) {
        // Is it a dictionary or any its subclass?
        if ( typeToken.getRawType().isAssignableFrom(Dictionary.class) ) {
            // Try to get the given type type parameters to be accurate as much as possible
            final Type[] typeParameters = get2TypeParameters(typeToken.getType());
            // typeParameters is constructed privately, so we know it's always a 2-element array
            final Type keyType = typeParameters[0];
            final Type valueType = typeParameters[1];
            // Ask Gson for a type adapter for the given value type (there can be dozens already)
            final TypeAdapter<?> valueTypeAdapter = gson.getAdapter(TypeToken.get(valueType));
            // And request ourselves for a name mapper since the dictionary key type is now known
            final INameMapper<Object> nameMapper = nameMapperFactory.createNameMapper(keyType);
            // Another Java generics boilerplate along with a dictionary type adapter for a concrete parameterized type
            @SuppressWarnings({ "unchecked", "rawtypes" })
            final TypeAdapter<T> typeAdapter = (TypeAdapter) new DictionaryTypeAdapter<>(nameMapper, valueTypeAdapter);
            return typeAdapter;
        }
        // If it's not a Dictionary instance, just tell Gson to pick up the best downstream parser
        return null;
    }

    private static Type[] get2TypeParameters(final Type type) {
        // Any parameterization?
        if ( !(type instanceof ParameterizedType) ) {
            return objectToObject;
        }
        // Just return the parameterized type actual type parameters
        final ParameterizedType parameterizedType = (ParameterizedType) type;
        final Type[] actualTypeArguments = parameterizedType.getActualTypeArguments();
        // Self-documented assertion
        assert actualTypeArguments.length == 2;
        return actualTypeArguments;
    }

    private static final class DictionaryTypeAdapter<K, V>
            extends TypeAdapter<Dictionary<K, V>> {

        private final INameMapper<K> keyMapper;
        private final TypeAdapter<V> valueTypeAdapter;

        private DictionaryTypeAdapter(final INameMapper<K> keyMapper, final TypeAdapter<V> valueTypeAdapter) {
            this.keyMapper = keyMapper;
            this.valueTypeAdapter = valueTypeAdapter;
        }

        @Override
        @SuppressWarnings("resource")
        public void write(final JsonWriter out, final Dictionary<K, V> dictionary)
                throws IOException {
            if ( dictionary == null ) {
                // If no dictionary is provided, then just write a `null` token to the output (it's a must)
                out.nullValue();
            } else {
                // Otherwise generate `{`, `k1`, `v1`, ... `}` tokens to the output token by token
                out.beginObject();
                final Enumeration<K> keys = dictionary.keys();
                while ( keys.hasMoreElements() ) {
                    final K key = keys.nextElement();
                    final V value = dictionary.get(key);
                    out.name(keyMapper.toName(key));
                    valueTypeAdapter.write(out, value);
                }
                out.endObject();
            }
        }

        @Override
        public Dictionary<K, V> read(final JsonReader in)
                throws IOException {
            // Peek the next token and dispatch a proper reader strategy
            final JsonToken token = in.peek();
            switch ( token ) {
            case NULL:
                return parseAsNull(in);
            case BEGIN_OBJECT:
                return parseAsObject(in);
            case BEGIN_ARRAY:
            case END_ARRAY:
            case END_OBJECT:
            case NAME:
            case STRING:
            case NUMBER:
            case BOOLEAN:
            case END_DOCUMENT:
                throw new MalformedJsonException("Unexpected token " + token + " at " + in);
            default:
                // This actually must never happen unless Gson adds a new token type
                throw new AssertionError(token);
            }
        }

        private static <K, V> Dictionary<K, V> parseAsNull(final JsonReader in)
                throws IOException {
            // Any token must be consumed
            in.nextNull();
            return null;
        }

        private Dictionary<K, V> parseAsObject(final JsonReader in)
                throws IOException {
            // Now just read the token input back and create a dictionary
            final Dictionary<K, V> dictionary = new Hashtable<>();
            in.beginObject();
            // Are there more elements in the object?
            while ( in.hasNext() ) {
                final K key = keyMapper.fromName(in.nextName());
                final V value = valueTypeAdapter.read(in);
                dictionary.put(key, value);
            }
            in.endObject();
            return dictionary;
        }

    }

}

现在让我们把它们放在一起:

private static final Type stringToStringBayesClassifierType = new TypeToken<BayesClassifier<String, String>>() {
}.getType();

// Building a custom Gson instance that is aware of the type adapter factory above
private static final Gson gson = new GsonBuilder()
        .registerTypeAdapterFactory(getDictionaryTypeAdapterFactory(getDefaultNameMapperFactory()))
        .create();

public static void main(final String... args) {
    final Classifier<String, String> before = createBayesClassifierMock();
    dump(before);
    final String json = gson.toJson(before, stringToStringBayesClassifierType);
    System.out.println(json);
    final Classifier<String, String> after = gson.fromJson(json, stringToStringBayesClassifierType);
    dump(after);
}

private static void dump(final Classifier<?, ?> classifier) {
    System.out.println(classifier);
    System.out.println(classifier.getFeatures());
    System.out.println(classifier.getCategories());
}

输出:

  

de.daslaboratorium.machinelearning.classifier.bayes.BayesClassifier@5b37e0d2
  [foo-feature,bar-feature]
  [bar-category,foo-category] ​​
  { “memoryCapacity”:1000, “featureCountPerCategory”:{}, “totalFeatureCount”:{ “富特征”:1, “巴特征”:2}, “totalCategoryCount”:{ “棒类”:2“, FOO类别 “:1},” memoryQueue “:[{” FEATURESET “:[” foo”的, “酒吧”], “类别”: “FOO / BAR”, “概率”:0.1},{ “FEATURESET”: [ “巴兹”, “qux”], “类别”: “BAZ / qUX”, “概率”:0.2}]}
  de.daslaboratorium.machinelearning.classifier.bayes.BayesClassifier@64a294a6
  [foo-feature,bar-feature]
  [bar-category,foo-category] ​​

顺便说一句,这可能是一个很好的机会,要求开发团队将该库迁移到java.util.Map,因为java.util.Dictionary已过时且不建议使用(来自其Javadoc,大胆而不是我的):

  

注意:此类已过时。新实现应该实现Map接口,而不是扩展这个类。