将一组JNA结构传递给本地方法

huangapple go评论70阅读模式
英文:

Passing a collection of JNA structures to native method

问题

问题

我正在尝试将一组JNA结构传递给本地方法,但这证明是非常棘手的:

假设我们有一个结构:

class MyStructure extends Structure {
    // 字段...
}

以及一个在JNA接口中的方法:

void pass(MyStructure[] data);

它映射到本地方法:

void pass(const MYStructure* data);

现在的复杂性在于,应用程序正在动态构建这些结构的集合,即我们不是处理静态数组,而是类似这样的情况:

class Builder {
    private final Collection<MyStructure> list = new ArrayList<>();

    // 添加一些数据
    public void add(MyStructure entry) {
        list.add(entry);
    }

    // 将数据传递给本地库
    public void pass() {
        // 待办事项
    }
}

pass() 方法的一个天真的实现可能是:

MyStructure[] array = list.toArray(MyStructure[]::new);
api.pass(array);

(其中 api 是 JNA 库接口)。

当然,这不起作用,因为数组不是连续的内存块 - 这是合理的。

垃圾解决方案#1

一种解决方案是从结构实例分配一个JNA数组,并逐个字段地填充它:

MYStructure[] array = (MyStructure[]) new MyStructure().toArray(size);
for(int n = 0; n < array.length; ++n) {
    array[n].field = list.get(n).field;
    // 其他字段...
}

这确保数组由连续的内存组成。但我们不得不逐个字段地复制数据(我们已经在列表中填充了数据) - 这对于简单的结构来说是可以的,但我处理的一些数据有几十个字段,指向进一步嵌套数组的结构等。基本上,这种方法根本行不通。

垃圾解决方案#2

另一种选择是将数据集合转换为简单的JNA指针,大致如下:

MyStructure[] array = list.toArray(MyStructure[]::new);
int size = array[0].size();
Memory mem = new Memory(array.length * size);
for(int n = 0; n < array.length; ++n) {
    if(array[n] != null) {
        array[n].write();
        byte[] bytes = array[n].getPointer().getByteArray(0, size);
        mem.write(n * size, bytes, 0, bytes.length);
    }
}

这个解决方案是通用的,所以我们也可以将其应用于其他结构。但我们必须将方法签名更改为Pointer,而不是MyStructure[],这使得代码更加晦涩,不够自我描述,并且更难以测试。此外,我们可能在这里使用第三方库,甚至可能没有这个选项。

(注意,我以前问过类似的问题 在这里,但没有得到满意的答案,所以我想再试一次,我会删除旧的问题 / 回答两者)。

总结

基本上,我期望/希望有类似这样的东西:

MyStructure[] array = MyStructure.magicContiguousMemoryBlock(list.toArray());

类似于JNA助手类为字符串数组提供的StringArray

StringArray array = new StringArray(new String[]{...});

但据我所知,没有这样的“魔术”。是否还有其他更简单、更符合“JNA”的方法?这似乎非常愚蠢(而且可能是不正确的),必须分配一个字节对字节的数据副本,而我们本质上已经拥有了这些数据!

我还有其他选择吗?任何指针(双关语)都将不胜感激。

英文:

The Problem

I am attempting to pass a collection of JNA structures to a native method but it's proving very fiddly:

Let's say we have a structure:

class MyStructure extends Structure {
    // fields...
}

and a method in a JNA interface:

void pass(MyStructure[] data);

which maps to the native method:

void pass(const MYStructure* data);

Now the complication comes from the fact that the application is building a collection of these structures dynamically, i.e. we are NOT dealing with a static array but something like this:

class Builder {
    private final Collection&lt;MyStructure&gt; list = new ArrayList&lt;&gt;();

    // Add some data
    public void add(MyStructure entry) {
        list.add(entry);
    }

    // Pass the data to the native library
    public void pass() {
        // TODO
    }
}

A naive implementation of the pass() method could be:

MyStructure[] array = list.toArray(MyStucture[]::new);
api.pass(array);

(where lib is the JNA library interface).

Of course this doesn't work because the array is not a contiguous block of memory - fair enough.

Rubbish Solution #1

One solution is to allocate a JNA array from a structure instance and populate it field-by-field:

MYStructure[] array = (MyStructure[]) new MyStructure().toArray(size);
for(int n = 0; n &lt; array.length; ++n) {
    array[n].field = list.get(n).field;
    // other fields...
}

This guarantees the array consist of contiguous memory. But we have had to implement a field-by-field copy of the data (which we've already populated in the list) - this is OK for a simple structure, but some of the data I am dealing with has dozens of fields, structures that point to further nested arrays, etc. Basically this approach is just not viable.

Rubbish Solution #2

Another alternative is to convert the collection of data to a simple JNA pointer, something along these lines:

MyStructure[] array = list.toArray(MyStructure[]::new);
int size = array[0].size();
Memory mem = new Memory(array.length * size);
for(int n = 0; n &lt; array.length; ++n) {
	if(array[n] != null) {
        array[n].write();
		byte[] bytes = array[n].getPointer().getByteArray(0, size);
		mem.write(n * size, bytes, 0, bytes.length);
	}
}

This solution is generic so we can apply it to other structure as well. But we have to change the method signatures to be Pointer instead of MyStructure[] which makes the code more obtuse, less self-documenting and harder to test. Also we could be using a third-party library where this might not even be an option.

(Note I asked a similar question a while ago here but didn't get a satisfactory answer, thought I'd try again and I'll delete the old one / answer both).

Summary

Basically I was expecting/hoping to have something like this:

MyStructure[] array = MyStructure.magicContiguousMemoryBlock(list.toArray());

similar to how the JNA helper class provides StringArray for an array-of-string:

StringArray array = new StringArray(new String[]{...});

But no such 'magic' exists as far as I can tell. Is there another, simpler and more 'JNA' way of doing it? It seems really dumb (and probably incorrect) to have to allocate a byte-by-byte copy of the data that we essentially already have!

Do I have any other options? Any pointers (pun intended) gratefully accepted.

答案1

得分: 1

如果您能够创建一个连续的内存块,为什么不简单地将列表反序列化到其中呢?

例如:

MyStructure[] array = list.get(0).toArray(list.size());
list.toArray(array);
pass(array);

无论如何,最好不要在列表或任何其他集合中存储Structure。更好的做法是在内部保存一个POJO,然后使用bean映射库直接重新映射为结构数组,或者手动进行映射。

使用MapStruct bean映射库可能如下所示:

@Mapper
public interface FooStructMapper {
    FooStructMapper INSTANCE = Mappers.getMapper(FooStructMapper.class);
    void update(FooBean src, @MappingTarget MyStruct dst);
}

MyStrucure[] block = new MyStructure().toArray(list.size());
for(int i=0; i < block.length; i++) {
   FooStructMapper.INSTANCE.update(list.get(i), block[i]);
}

要点是 - Structure 构造函数使用Memory分配内存块,这是一个非常慢的操作。同样,分配的内存位于Java堆空间之外。尽可能避免在可以的情况下进行这种分配,这总是更好的做法。

英文:

If you able to create a continues block of memory, why don't you simply de-serialize your list into it.

I.e. something like:

MyStructure[] array = list.get(0).toArray(list.size());
list.toArray(array);
pass(array);

In any case you'd better not to store Structure in your List or any another collection. It is better idea to hold a POJO inside, and then remap it to array of structures directly using a bean mapping library or manually.

With MapStruct bean mapping library it may looks like:

@Mapper
public interface FooStructMapper {
    FooStructMapper INSTANCE = Mappers.getMapper( FooStructMapper.class );
    void update(FooBean src, @MappingTarget MyStruct dst);
}

MyStrucure[] block = new MyStructure().toArray(list.size());
for(int i=0; i &lt; block.length; i++) {
   FooStructMapper.INSTANCE.update(list.get(i), block[i]);
}

What the point - Structure constructor allocates memory block using Memory, it is really slow operation. As well as memory allocated outside of java heap space. It is always better to avoid this allocate whenever you can.

答案2

得分: 1

以下是您要求的翻译内容:

作为前一个回答的作者,我意识到很多混淆是在朝着一种方式进行处理,然后意识到了一种更好的解决方案,我们主要在对您的答案的评论中讨论了这种更好的解决方案。我将尝试通过在该答案中实际演示我的建议,这是我认为最好的方法。简单来说,如果您有一个非连续的结构并且需要一个连续的结构,您必须将连续的内存带到结构中,或者将结构复制到连续的内存中。我将在下面概述这两种方法。

> 是否有另一种更简单、更“JNA”的方法来做这件事?不必(可能不正确地)必须分配字节对字节的数据副本,因为我们基本上已经有了这些数据!

我在回答另一个问题时提到过,在这种情况下,您可以在这种情况下使用useMemory()方法。这是一个protected方法,但是如果您已经扩展了Structure,则可以从子类(即您的结构)中访问该方法,方式与扩展子类的Pointer构造函数相同(目的完全相同)。

因此,您可以将集合中的现有结构的本机后备内存更改为连续的内存。这里有一个可工作的示例:

public class Test {

    @FieldOrder({ "a", "b" })
    public static class Foo extends Structure {
        public int a;
        public int b;

        // 您可以选择覆盖或创建一个单独的辅助方法
        @Override
        public void useMemory(Pointer m) {
            super.useMemory(m);
        }
    }

    public static void main(String[] args) {
        List<Foo> list = new ArrayList<>();
        for (int i = 1; i < 6; i += 2) {
            Foo x = new Foo();
            x.a = i;
            x.b = i + 1;
            list.add(x);
        }

        Foo[] array = (Foo[]) list.get(0).toArray(list.size());
        // 索引 0 在 toArray() 中复制
        System.out.println(array[0].toString());
        // 但是我们仍然需要将其后备内存更改为副本
        list.get(0).useMemory(array[0].getPointer());
        // 迭代以更改后备并写入其余部分
        for (int i = 1; i < array.length; i++) {
            list.get(i).useMemory(array[i].getPointer());
            list.get(i).write();
            // 由于将结构数组作为参数发送将自动写入,
            // 在此需要同步它。
            array[1].read(); 
        }
        // 此时,您可以将连续的结构数组发送到本机。
        // list.get(n) 和 array[n] 都指向相同的内存,例如:
        System.out.println(list.get(1).toString());
        System.out.println(array[1].toString());
    }
}

输出(注意连续分配)。后两个输出是相同的,可以从列表或数组中获得。

如果您不想在每个结构定义中放置useMemory,您仍然可以将其放在扩展Structure的中间类中,然后扩展该中间类而不是Structure

<hr>

如果您不想在结构定义中(或其超类中)覆盖useMemory(),您仍然可以通过在代码中简单地进行一些效率较低的内存复制来实现。

为了从内存中“获取”该内存以便在其他地方进行写入,您必须从Java端内存中读取它(通过反射,这就是JNA将结构转换为本机内存块的方式),或者从本机端内存中读取它(即使您只想读取它,也需要将其写入)。在幕后,JNA是通过一个简单的write()调用在API下隐藏地逐字段写入本机字节。

您的“垃圾解决方案#2”似乎接近于在这种情况下所需的解决方案。以下是我们必须处理的约束以及任何解决方案:

  • 在现有的Structure列表或数组中,本机内存不是连续的(除非您自己预先分配连续的内存,并以受控的方式使用该内存,或者像上面示范的那样覆盖useMemory()),而且大小是可变的。
  • 接受数组参数的本机函数期望一个连续内存块。

以下是处理结构和内存的“JNA方式”:

  • 结构在通过Structure.getPointer()访问的指针值处具有本机分配的内存,大小至少为Structure.size()
  • 结构本机内存可以使用Structure.getByteArray()批量读取。
  • 可以使用new Structure(Pointer p)构造函数从指向本机内存的指针构造结构。
  • Structure.toArray()方法创建由大的连续本机内存块支持的结构数组。

我认为您的解决方案#2是一种相当高效的方法,但是您的问题表明您希望有更多的类型安全性,或者至少是自说明的代码,在这种情况下,我要指出一种在代码中以稍微低效的方式复制内存的“JNA方式”。

为了“获取”内存以便在其他地方进行写入,您必须从Java端内存中读取它(通过反射,这就是JNA将结构转换为本机内存块的方式),或者从本机端内存中读取它(即使您只

英文:

As the author of the previous answer, I realize a lot of the confusion was approaching it one way before realizing a better solution that we discussed primarily in comments to your answer. I will try to answer this additional clarification with an actual demonstration of my suggestion on that answer which I think is the best approach. Simply, if you have a non-contiguous structure and need a contiguous structure, you must either bring the contiguous memory to the structure, or copy the structure to the contiguous memory. I'll outline both approaches below.

> Is there another, simpler and more 'JNA' way of doing it? It seems really dumb (and probably incorrect) to have to allocate a byte-by-byte copy of the data that we essentially already have!

I did mention in my answer on the other question that you could use useMemory() in this situation. It is a protected method but if you are already extending a Structure you have access to that method from the subclass (your structure), in much the same way (and for precisely the same purpose) as you would extend the Pointer constructor of a subclass.

You could therefore take an existing structure in your collection and change its native backing memory to be the contiguous memory. Here is a working example:

public class Test {

    @FieldOrder({ &quot;a&quot;, &quot;b&quot; })
    public static class Foo extends Structure {
        public int a;
        public int b;

        // You can either override or create a separate helper method
        @Override
        public void useMemory(Pointer m) {
            super.useMemory(m);
        }
    }

    public static void main(String[] args) {
        List&lt;Foo&gt; list = new ArrayList&lt;&gt;();
        for (int i = 1; i &lt; 6; i += 2) {
            Foo x = new Foo();
            x.a = i;
            x.b = i + 1;
            list.add(x);
        }

        Foo[] array = (Foo[]) list.get(0).toArray(list.size());
        // Index 0 copied on toArray()
        System.out.println(array[0].toString());
        // but we still need to change backing memory for it to the copy
        list.get(0).useMemory(array[0].getPointer());
        // iterate to change backing and write the rest
        for (int i = 1; i &lt; array.length; i++) {
            list.get(i).useMemory(array[i].getPointer());
            list.get(i).write();
            // Since sending the structure array as an argument will auto-write,
            // it&#39;s necessary to sync it here.
            array[1].read(); 
        }
        // At this point you could send the contiguous structure array to native.
        // Both list.get(n) and array[n] point to the same memory, for example:
        System.out.println(list.get(1).toString());
        System.out.println(array[1].toString());
    }

Output (note the contiguous allocation). The second two outputs are the same, from either the list or the array.

Test$Foo(allocated@0x7fb687f0d550 (8 bytes) (shared from auto-allocated@0x7fb687f0d550 (24 bytes))) {
int a@0x0=0x0001
int b@0x4=0x0002
}
Test$Foo(allocated@0x7fb687f0d558 (8 bytes) (shared from allocated@0x7fb687f0d558 (8 bytes) (shared from allocated@0x7fb687f0d558 (8 bytes) (shared from allocated@0x7fb687f0d550 (8 bytes) (shared from auto-allocated@0x7fb687f0d550 (24 bytes)))))) {
int a@0x0=0x0003
int b@0x4=0x0004
}
Test$Foo(allocated@0x7fb687f0d558 (8 bytes) (shared from allocated@0x7fb687f0d558 (8 bytes) (shared from allocated@0x7fb687f0d550 (8 bytes) (shared from auto-allocated@0x7fb687f0d550 (24 bytes))))) {
int a@0x0=0x0003
int b@0x4=0x0004
}

If you don't want to put useMemory in every one of your structure definitions you can still put it in an intermediate class that extends Structure and then extend that intermediate class instead of Structure.

<hr>

If you don't want to override useMemory() in your structure definitions (or a superclass of them), you can still do it "simply" in code with a little bit of inefficiency by copying over the memory.

In order to "get" that memory to write it elsewhere, you have to either read it from the Java-side memory (via reflection, which is what JNA does to convert the structure to the native memory block), or read it from Native-side memory (which requires writing it there, even if all you want to do is read it). Under-the-hood, JNA is writing the native bytes field-by-field, all hidden under a simple write() call in the API.

Your "Rubbish Solution #2" seems close to what's desired in this case. Here are the constraints that we have to deal with, with whatever solution:

  • In the existing list or array of Structure, the native memory is not contiguous (unless you pre-allocate contiguous memory yourself, and use that memory in a controlled manner, or override useMemory() as demonstrated above), and the size is variable.
  • The native function taking an array argument expects a block of contiguous memory.

Here are the "JNA ways" of dealing with structures and memory:

  • Structures have native-allocated memory at a pointer value accessible via Structure.getPointer() with a size of (at least) Structure.size().
  • Structure native memory can be read in bulk using Structure.getByteArray().
  • Structures can be constructed from a pointer to native memory using the new Structure(Pointer p) constructor.
  • The Structure.toArray() method creates an array of structures backed by a large, contiguous block of native memory.

I think your solution #2 is a rather efficient way of doing it, but your question indicates you'd like more type safety, or at least self-documenting code, in which case I'd point out a more "JNA way" of modifying #2 with two steps:

  • Replace the new Memory(array.length * size) native allocation with the Structure.toArray() allocation from your solution #1.
    • You still have a length * size block of contiguous native memory and a pointer to it (array[0].getPointer()).
    • You additionally have pointers to the offsets, so you could replace mem.write(n * size, ... ) with array[n].getPointer().write(0, ... ).
  • There is no getting around the memory copying, but having two well-commented lines which call getByteArray() and immediately write() that byte array seem clear enough to me.
    • You could even one-line it... write(0, getByteArray(0, size), 0, size), although one might argue if that's more or less clear.

So, adapting your method #2, I'd suggest:

// Make your collection an array as you do, but you could just keep it in the list 
// using `size()` and `list.get(n)` rather than `length` and `array[n]`.
MyStructure[] array = list.toArray(MyStructure[]::new);

// Allocate a contiguous block of memory of the needed size
// This actually writes the native memory for index 0, 
// so you can start the below iteration from 1
MyStructure[] structureArray = (MyStructure[]) array[0].toArray(array.length);

// Iterate the contiguous memory and copy over bytes from the array/list
int size = array[0].size();
for(int n = 1; n &lt; array.length; ++n) {
    if(array[n] != null) {
        // sync local structure to native (using reflection on fields)
        array[n].write();
        // read bytes from the non-contiguous native memory
        byte[] bytes = array[n].getPointer().getByteArray(0, size);
        // write bytes into the contiguous native memory
        structureArray[n].getPointer().write(0, bytes, 0, bytes.length);
        // sync native to local (using reflection on fields)
        structureArray[n].read();
    }
}

From a "clean code" standpoint I think this rather effectively accomplishes your goal. The one "ugly" part of the above method is that JNA doesn't provide an easy way to copy fields between Structures without writing them to native memory in the process. Unfortunately that's the "JNA way" of "serializing" and "deserializing" objects, and it's not designed with any "magic" for your use case. Strings include built-in methods to convert to bytes, making such "magic" methods easier.

It is also possible to avoid writing the structure to native memory just to read it back again if you do the field-by-field copy as you implied in your Method #1. However, you could use JNA's field accessors to make it a lot easier to access the reflection under the hood. The field methods are protected so you'd have to extend Structure to do this -- which if you're doing that, the useMemory() approach is probably better! But you could then pull this iteration out of write():

for (StructField sf : fields().values()) {
    // do stuff with sf 
}

My initial thought would be to iterate over the non-contiguous Structure fields using the above loop, storing a Field.copy() in a HashMap with sf.name as the key. Then, perform that same iteration on the other (contiguous) Structure object's fields, reading from the HashMap and setting their values.

答案3

得分: 1

以下是翻译好的内容:

Daniel Widdis 提供的解决方案将解决这个“问题”,如果确实需要对 JNA 结构执行逐字节复制。

然而,我已经转向了一些其他帖子中表达的思路 - JNA 结构纯粹用于在本地层与原生层之间进行编组,不应真正用作“数据”。我们应该定义领域 POJO(普通的Java对象)并根据需要将它们转换为 JNA 结构 - 这可能需要更多的工作,但我想应该可以处理。

编辑:这是我最终使用自定义流收集器实现的解决方案:

public class StructureCollector <T, R extends Structure> implements Collector<T, List<T>, R[]> {
	/**
	 * 辅助方法 - 将给定集合转换为由<b>第一个</b>元素引用的连续数组。
	 * @param <T> 数据类型
	 * @param <R> 结果的 JNA 结构类型
	 * @param data			数据
	 * @param identity		身份构造函数
	 * @param populate		填充函数
	 * @return 数组的<b>第一个</b>元素
	 */
	public static <T, R extends Structure> R toArray(Collection<T> data, Supplier<R> identity, BiConsumer<T, R> populate) {
		final R[] array = data.stream().collect(new StructureCollector<>(identity, populate));

		if (array == null) {
			return null;
		} else {
			return array[0];
		}
	}

	private final Supplier<R> identity;
	private final BiConsumer<T, R> populate;
	private final Set<Characteristics> chars;

	/**
	 * 构造函数。
	 * @param identity		身份结构
	 * @param populate		填充函数
	 * @param chars			流特性
	 */
	public StructureCollector(Supplier<R> identity, BiConsumer<T, R> populate, Characteristics... chars) {
		this.identity = notNull(identity);
		this.populate = notNull(populate);
		this.chars = Set.copyOf(Arrays.asList(chars));
	}

	@Override
	public Supplier<List<T>> supplier() {
		return ArrayList::new;
	}

	@Override
	public BiConsumer<List<T>, T> accumulator() {
		return List::add;
	}

	@Override
	public BinaryOperator<List<T>> combiner() {
		return (left, right) -> {
			left.addAll(right);
			return left;
		};
	}

	@Override
	public Function<List<T>, R[]> finisher() {
		return this::finish;
	}

	@SuppressWarnings("unchecked")
	private R[] finish(List<T> list) {
		// 检查空数据
		if (list.isEmpty()) {
			return null;
		}

		// 分配连续数组
		final R[] array = (R[]) identity.get().toArray(list.size());

		// 填充数组
		final Iterator<T> itr = list.iterator();
		for (final R element : array) {
			populate.accept(itr.next(), element);
		}
		assert !itr.hasNext();

		return array;
	}

	@Override
	public Set<Characteristics> characteristics() {
		return chars;
	}
}

这很好地封装了分配和填充连续数组的代码,示例用法:

class SomeDomainObject {
    private void populate(SomeStructure struct) {
        ...
    }
}

class SomeStructure extends Structure {
    ...
}

Collection<SomeDomainObject> collection = ...

SomeStructure[] array = collection
    .stream()
    .collect(new StructureCollector<>(SomeStructure::new, SomeStructure::populate));

希望这对于正在做类似工作的人有所帮助。

英文:

The solutions offered by Daniel Widdis will solve this 'problem' if one really needs to perform a byte-by-byte copy of a JNA structure.

However I have come round to the way of thinking expressed by some of the other posters - JNA structures are intended purely for marshalling to/from the native layer and should not really be used as 'data'. We should be defining domain POJOs and transforming those to JNA structures as required - a bit more work but deal with I guess.

EDIT: Here is the solution that I eventually implemented using a custom stream collector:

public class StructureCollector &lt;T, R extends Structure&gt; implements Collector&lt;T, List&lt;T&gt;, R[]&gt; {
	/**
	 * Helper - Converts the given collection to a contiguous array referenced by the &lt;b&gt;first&lt;/b&gt; element.
	 * @param &lt;T&gt; Data type
	 * @param &lt;R&gt; Resultant JNA structure type
	 * @param data			Data
	 * @param identity		Identity constructor
	 * @param populate		Population function
	 * @return &lt;b&gt;First&lt;/b&gt; element of the array
	 */
	public static &lt;T, R extends Structure&gt; R toArray(Collection&lt;T&gt; data, Supplier&lt;R&gt; identity, BiConsumer&lt;T, R&gt; populate) {
		final R[] array = data.stream().collect(new StructureCollector&lt;&gt;(identity, populate));

		if(array == null) {
			return null;
		}
		else {
			return array[0];
		}
	}

	private final Supplier&lt;R&gt; identity;
	private final BiConsumer&lt;T, R&gt; populate;
	private final Set&lt;Characteristics&gt; chars;

	/**
	 * Constructor.
	 * @param identity		Identity structure
	 * @param populate		Population function
	 * @param chars			Stream characteristics
	 */
	public StructureCollector(Supplier&lt;R&gt; identity, BiConsumer&lt;T, R&gt; populate, Characteristics... chars) {
		this.identity = notNull(identity);
		this.populate = notNull(populate);
		this.chars = Set.copyOf(Arrays.asList(chars));
	}

	@Override
	public Supplier&lt;List&lt;T&gt;&gt; supplier() {
		return ArrayList::new;
	}

	@Override
	public BiConsumer&lt;List&lt;T&gt;, T&gt; accumulator() {
		return List::add;
	}

	@Override
	public BinaryOperator&lt;List&lt;T&gt;&gt; combiner() {
        return (left, right) -&gt; {
            left.addAll(right);
            return left;
        };
	}

	@Override
	public Function&lt;List&lt;T&gt;, R[]&gt; finisher() {
		return this::finish;
	}

	@SuppressWarnings(&quot;unchecked&quot;)
	private R[] finish(List&lt;T&gt; list) {
		// Check for empty data
		if(list.isEmpty()) {
			return null;
		}

		// Allocate contiguous array
		final R[] array = (R[]) identity.get().toArray(list.size());

		// Populate array
		final Iterator&lt;T&gt; itr = list.iterator();
		for(final R element : array) {
			populate.accept(itr.next(), element);
		}
		assert !itr.hasNext();

		return array;
	}

	@Override
	public Set&lt;Characteristics&gt; characteristics() {
		return chars;
	}
}

This nicely wraps up the code that allocates and populates a contiguous array, example usage:

class SomeDomainObject {
    private void populate(SomeStructure struct) {
        ...
    }
}

class SomeStructure extends Structure {
    ...
}

Collection&lt;SomeDomainObject&gt; collection = ...

SomeStructure[] array = collection
    .stream()
    .collect(new StructureCollector&lt;&gt;(SomeStructure::new, SomeStructure::populate));

Hopefully this might help anyone that's doing something similar.

huangapple
  • 本文由 发表于 2020年10月13日 00:39:59
  • 转载请务必保留本文链接:https://go.coder-hub.com/64321876.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定