如何使用JAVA过滤器lambda对Arraylist元素进行分组



我正在尝试按JAVA数组列表元素进行分组。

我有一个JSONObjects数组,如下所示,我想按字段priceside分组,然后用新的文件buySharesandsellShares1对股份进行合计

[{"shares":20,"side":"B","orderId":"001","price":"500"}, 
{"shares":20,"side":"S","orderId":"002","price":"501"}, 
{"shares":25,"side":"B","orderId":"003","price":"500"}, 
{"shares":10,"side":"S","orderId":"004","price":"501"}, 
{"shares":30,"side":"B","orderId":"005","price":"505"}, 
{"shares":35,"side":"B","orderId":"006","price":"505"}, 
{"shares":35,"side":"S","orderId":"007","price":"500"}]

我想按价格和侧面分组,得到如下内容:

[{"price":"500","buyShares":45, "sellShares":35}, {"sellShares":30,"price":"501"}, {"price":"505","buyShares":65}]

我使用以下java代码:

ArrayList<JSONObject> aOrdersArray = new ArrayList<>(aOrders.values());
System.out.println(aOrdersArray);
List<JSONObject> test = aOrdersArray.stream()
.distinct()
.collect(Collectors.groupingBy(jsonObject -> jsonObject.getInt("price")))
.entrySet().stream()
.map(e -> e.getValue().stream()
.reduce((f1,f2) -> {
JSONObject h = new JSONObject();
h.put("price",f1.get("price"));
System.out.println(f1);
if (f1.get("side").equals("B") && f2.get("side").equals("B")) {
h.put("buyShares", f1.getInt("shares") + f2.getInt("shares"));
}
else if (f1.get("side").equals("S") && f2.get("side").equals("S")) {
h.put("sellShares", f1.getInt("shares") + f2.getInt("shares"));
}
else if (f1.get("side").equals("S")) {
h.put("sellShares", f1.get("shares"));
}
else if (f1.get("side").equals("B")) {
h.put("buyShares",f1.get("shares"));
}
else if (f2.get("side").equals("S")) {
h.put("sellShares", f2.get("shares"));
}
else if (f2.get("side").equals("B")) {
h.put("buyShares",f2.get("shares"));
}
return h;
}))
.map(f -> f.get())
.collect(Collectors.toList());
System.out.println(test);

有时我会得到以下错误

Exception in thread "main" org.json.JSONException: JSONObject["side"] not found.

您尝试做的事情很复杂,而且容易出错,因为您的起点不正确。

你一开始就不应该有JSONObject。JSON输入中的数据是常规的,您希望对其内容进行操作。

正确的开始方法是创建一个表示这样一个记录的java对象,然后将JSON输入转换为一个合适的、惯用的java版本。您想要:

@Value class Record {
int shares;
RecordKind kind;
int orderId;
int price;
public PriceGroup toGroup() {
return new PriceGroup(price,
kind == BUY ? shares : 0,
kind == SELL ? shares : 0);
}
}
@Value class PriceGroup {
int price;
int buyShares;
int sellShares;
public PriceGroup merge(PriceGroup other) {
if (other.price != this.price) throw new IllegalArgumentException();
return new PriceGroup(price,
this.buyShares + other.buyShares,
this.sellShares + other.sellShares);
}
}

注意:使用龙目的@Value。假设这个东西有一个构造函数,所有字段都是final,toString和equals以及hashCode都在正确的位置,等等

一旦有了以上两种类型,就可以将输入的JSON转换为List<Record>。有了这个List<Record>,那么,也只有到那时,你才能开始映射、分组等。

当然,这个错误永远不会发生,而且你的地图/组码会更容易阅读。任何打字错误都会导致编译时出错。自动完成会很好地工作,等等:所有的优点。

要将JSON转换为给定的类型,请使用Jackson。如果您不想添加依赖项(实际上应该添加(,那么在Record类中编写一个public static Record fromJson(JSONObject)方法,并使用.map(Record::fromJson)立即访问Record对象流,永远不要返回到JSONObject。

List<Record> orders = ...;
List<PriceGroup> test = orders.stream()
.distinct()
.map(Record::toGroup)
.collect(Collectors.groupingBy(PriceGroup::getPrice))
.entrySet().stream()
.map(e -> e.getValue().stream()
.reduce((f1, f2) -> f1.merge(f2))
.collect(Collectors.toList());

您的代码现在读起来简单多了,所有方法都有正确的名称(它是getBuyShares(),而不是.getInt("buyShares")(,并且可以通过例如自动完成来发现,例如,您可以单独测试merge的功能。

要使用gson,可以创建一个类:

public class Share{
private int shares;
private String side;
private String orderId;
private String price;

//getters and setters
public int getShares() {
return shares;
}
public void setShares(int shares) {
this.shares = shares;
}
public String getSide() {
return side;
}
public void setSide(String side) {
this.side = side;
}
public String getOrderId() {
return orderId;
}
public void setOrderId(String orderId) {
this.orderId = orderId;
}
public String getPrice() {
return price;
}
public void setPrice(String price) {
this.price = price;
}
}

然后您可以使用这个来映射json:中的对象

String yourJson = "[{"shares":20,"side":"B","orderId":"001","price":"500"},{"shares":35,"side":"S","orderId":"007","price":"500"}]";
Gson gson = new Gson();
Type shareListType = new TypeToken<ArrayList<Share>>(){}.getType();
ArrayList<Share> userArray = gson.fromJson(yourJson, shareListType);  

虽然每个人都建议使用对象映射值来简化计算,但这本身并不是必须的

  • 您需要为每一种合理类型的JSON节点创建一个映射(尽管它可以在一定程度上自动化(——如果您的映射在代码库中广泛使用,这是完全可以的,但可能(!(不值得为一次性使用的特定对象做这件事
  • 直接操作JSON树可以保证绑定(反序列化(不会影响原始JSON树结构(即,在Gson中(很可能在Jackson中(,可以实现一个不能保证数据映射完美往返的类型适配器:在JSON->映射->out JSON=>在JSON中!=输出JSON;对于到JSON和返回的映射也是如此(
  • 有些JSON库(就像您的库一样?(根本不提供对象映射/绑定功能
@SuppressWarnings("unchecked")
final Iterable<JSONObject> input = (Iterable<JSONObject>) new JSONTokener(inputReader).nextValue();
final JSONArray actual = StreamSupport.stream(input.spliterator(), false)
.collect(Collectors.groupingBy(jsonObject -> jsonObject.getString("price")))
.entrySet()
.stream()
.map(entry -> entry.getValue()
.stream()
.collect(Collector.of(
() -> {
final JSONObject jsonObject = new JSONObject();
jsonObject.put("price", entry.getKey());
return jsonObject;
},
(a, e) -> {
final String side = e.getString("side");
final String key;
final BigDecimal shares;
switch ( side ) {
case "B":
key = "buyShares";
shares = e.getBigDecimal("shares");
break;
case "S":
key = "sellShares";
shares = e.getBigDecimal("shares");
break;
default:
throw new AssertionError(side);
}
if ( !a.has(key) ) {
a.put(key, shares);
} else {
a.put(key, a.getBigDecimal(key).add(shares));
}
},
(a1, a2) -> {
throw new UnsupportedOperationException();
},
Function.identity()
))
)
.collect(Collector.of(
JSONArray::new,
JSONArray::put,
(a1, a2) -> {
throw new UnsupportedOperationException();
},
Function.identity()
));
final JSONArray expected = (JSONArray) new JSONTokener(expectedReader).nextValue();
Assertions.assertEquals(expected.toString(), actual.toString()); // why? because neither JSONArray nor JSONObject implement equals/hashCode

最新更新