我正在尝试在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
这是正确的方法还是我做错了?
答案 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接口,而不是扩展这个类。