使用Java对象模仿Matlab结构

时间:2013-03-20 17:00:49

标签: java matlab interop

这是我的问题(大图)。我有一个使用大而复杂的项目(我的意思是包含多层嵌套结构)Matlab结构。这可以预见得很慢(特别是在尝试加载/保存时)。我试图通过将其中一些结构转换为Java对象来改善运行时。问题在于,这些Matlab结构中的数据是在很多地方访问的,因此任何需要重写访问语法的内容都是令人望而却步的。因此,我需要Java对象尽可能地模仿Matlab结构的行为,特别是在访问存储在其中的值时(这些值只设置在一个地方,因此java中缺少运算符重载以进行设置)不是考虑的因素。)

我遇到的问题(小图片)在于从这些结构的数组中访问数据。例如,

person(1)
  .age = 20
  .name
    .first = 'John'
    .last = 'Smith

person(2)
  .age = 25
  .name
    .first = 'Jane'
    .last = 'Doe'

Matlab将允许您执行以下操作,

>>age = [person(1:2).age]
age = 
    20    25

试图用Java完成同样的事情,

>>jperson = javaArray('myMatlab.Person', 2);
>>jperson(1) = Person(20, Name('John', 'Smith'));
>>jperson(2) = Person(25, Name('Jane', 'Doe'));
>>age = [jperson(1:2).age]
??? No appropriate method or public field age for class myMatlab.Person[]

有什么方法可以让Java对象模仿这种行为? 我的第一个想法是简单地扩展Person[] class,但this doesn't appear to be possible because it is final.我的第二种方法是创建一个包含PersonList的包装类,但是我不相信这会因为调用< / p>

wrappedPerson(1:2)

将被解释为对wrappedPerson类的构造函数调用或尝试访问不存在的WrappedPerson数组的元素(因为java不会让我覆盖“()”运算符)。任何见解将不胜感激。

我用于java类的代码是

public class Person {
  int _age;
  ArrayList<Name> _names;

  public Person(int age, Name name) {
    _age = age;
    _names.add(name);
  }

  public int age() {return _age;}
  public void age(int age) {_age = age;}
  public Name[] name() {return _names.toArray(new Name[0]);}
  public void name(Name name) { _names.add(name);}
}

public class Name {
  String _first;
  String _last;

  public Name(String first, String last) {
    _first = first;
    _last = last;
  }

  public int first() {return _first;}
  public void first(String firstName) {_first = firstName;}
  public int last() {return _last;}
  public void last(String lastName) {_last = lastName;}
}

1 个答案:

答案 0 :(得分:6)

TL; DR:这是可能的,有一些花哨的OOP M代码技巧。改变().的行为可以使用Matlab包装器类来完成,该类在Java包装器类之上定义subsref。但是由于Matlab到Java的固有开销,它可能不会比普通的Matlab代码快得多,只是更加复杂和繁琐。除非你将逻辑移到Java中,否则这种方法可能不会为你加速。

我提前为啰嗦道歉。

在您全面了解之前,您可以根据Matlab代码调用Java结构的性能。虽然Java字段访问和方法调用本身比Matlab快得多,但是从M代码调用它们需要大量的开销,所以除非你将大量逻辑推入Java,否则最终可能会结束速度净损失。每次将M代码交叉到Java层时,都需要付费。在这个答案上看看基准测试:Is MATLAB OOP slow or am I doing something wrong?以了解规模。 (完全披露:这是我的一个答案。)它不包括Java字段访问,但由于自动装箱开销,它可能是方法调用的顺序。如果您正在编写Java类,就像在您的示例中一样,使用getter和setter方法而不是公共字段(即&#34; good&#34; Java风格),那么您将承担Java方法调用的成本与纯Matlab结构相比,每次访问都会 bad

所有这一切,如果你想让x = [foo(1:2).bar]语法在M代码中工作,其中foo是一个Java数组,它基本上是可能的。在调用Java之前,().都在Matlab中进行了评估。你可以做的是在Matlab OOP中定义你自己的自定义JavaArrayWrapper类,对应于你的Java数组包装器类,并将你的(可能包装的)Java数组包装在其中。让它覆盖subsrefsubsasgn以同时处理().。对于(),执行数组的正常子集化,将其包装在JavaArrayWrapper中。对于.案例:

  • 如果包装的对象是标量,则正常调用Java方法。
  • 如果包装对象是一个数组,则循环遍历它,在每个元素上调用Java方法,并收集结果。如果结果是Java对象,则将它们包装在JavaArrayWrapper中。

但是。由于跨越Matlab / Java屏障的开销,这将是缓慢的,可能比纯Matlab代码慢一个数量级。

为了使其快速工作,您可以提供相应的自定义Java类来包装Java数组,并使用Java Reflection API提取每个选定数组成员对象的属性并将其收集到数组中。关键是当你做一个&#34;链接&#34; Matlab中的引用如x = foo(1:3).a.b.cfoo是一个对象,它不进行逐步评估,评估foo(1:3),然后对结果调用.a,等等。它实际上解析整个(1:3).a.b.c引用,将其转换为结构化参数,并将整个事物传递给subsref foo方法,该方法负责解释整个链。隐式调用看起来像这样。

x = subsref(foo, [ struct('type','()','subs',{{[1 2 3]}}), ...
                   struct('type','.', 'subs','a'), ...
                   struct('type','.', 'subs','b'), ... 
                   struct('type','.', 'subs','c') ] )

因此,鉴于您可以访问整个参考链接&#34;在前面,如果foo是定义subsasgn的M代码包装类,则可以将整个引用转换为Java参数,并将其传递给Java包装器类的单个方法调用,然后使用它Java反射动态地遍历包装数组,选择引用元素,并在Java层内部执行链接引用。例如。它会像这样在Java类中调用getNestedFields()

public class DynamicFieldAccessArrayWrapper {
    private ArrayList _wrappedArray;

    public Object getNestedFields(int[] selectedIndexes, String[] fieldPath) {
        // Pseudo-code:
        ArrayList result = new ArrayList();
        if (selectedIndexes == null) {
            selectedIndexes = 1:_wrappedArray.length();
        }
        for (ix in selectedIndexes) {
            Object obj = _wrappedArray.get(ix-1);
            Object val = obj;
            for (fieldName in fieldPath) {
                java.lang.reflect.Field field = val.getClass().getField(fieldName);
                val = field.getValue(val);
            }
            result.add(val);
        }
        return result.toArray(); // Return as array so Matlab can auto-unbox it; will need more type detection to get array type right
    }
}

然后你的M代码包装器类将检查结果并决定它是否是原始的,并且应该作为Matlab数组或逗号分隔列表返回(即多个argouts,它们被[...]收集) ,或者应该包装在另一个JavaArrayWrapper M代码对象中。

M代码包装器类看起来像这样。

classdef MyMJavaArrayWrapper < handle
    % Inherit from handle because Java objects are reference-y
    properties
        jWrappedArray  % holds a DynamicFieldAccessArrayWrapper
    end
    methods
        function varargout = subsref(obj, s)
            if isequal(s(1).type, '()')
                indices = s(1).subs;
                s(1) = [];
            else
                indices = [];
            end
            % TODO: check for unsupported indexing types in remaining s
            fieldNameChain = parseFieldNamesFromArgs(s);
            out = getNestedFields( jWrappedArray, indices, fieldNameChain );
            varargout = unpackResultsAndConvertIfNeeded(out);
        end
    end
end

编组和解组subsasgn调用的值所涉及的开销可能会超过Java位的任何速度增益。

您可以通过将subsasgn的M代码实现替换为在C中执行结构编组和解组的MEX实现来消除这种开销,使用JNI构建Java对象,调用getNestedFields并转换结果是Matlab结构。这超出了我能举例说明的范围。

如果这对你来说有点可怕,我完全同意。你在这里碰到语言的边缘,并试图从用户区扩展语言(特别是提供新的句法行为)真的很难。我不会在生产代码中认真做这样的事情;只是试图勾勒出你正在四处寻找的问题区域。

您是在处理这些深层嵌套结构的同构数组吗?也许有可能将它们转换为&#34;平面有组织的&#34;结构,而不是带有标量字段的结构数组,你有一个带数组字段的标量结构。然后你可以用纯M代码对它们进行矢量化操作。这会使事情变得更快,特别是对于saveload,其中每个mxarray的开销比例。