【问题标题】:Sort ArrayList of Objects, WITHOUT Class Getters. Possible?对对象的 ArrayList 进行排序,没有类 Getter。可能的?
【发布时间】:2021-03-31 02:27:03
【问题描述】:

我正在尝试使用 lambda 表达式对对象的 ArrayList(即从 CSV 文件导入的行)、没有类(如“Employee”、“Person”或“Animal”等)进行排序,所以根本没有对象的Getter/Setter。所以我本质上是从 CSV 文件中逐行缓冲读取,将它们分配给对象,然后将这些对象添加到 ArrayList。然后我想按每个对象中的最后一个值/索引进行排序。比如……

这些是我的 CSV 行:

Mark,10,0,34
Tom,2,0,19
Billy,2,0,7

...我想按最右边的数字升序对它们进行排序,所以我排序后的 ArrayList 看起来像:

Billy,2,0,7
Tom,2,0,19
Mark,10,0,34

这甚至可以使用 lambda 表达式吗?到目前为止,我所拥有的代码如下所示:

// Import the csv file and assign each row to a list of objects
List<Object> olReturn = null;
try (BufferedReader br = new BufferedReader(new FileReader("stats_log.csv"))) {
                
    olReturn = br.lines().sorted((x,y) -> x.LASTVALUEHERE() - y.LASTVALUEHERE()).collect(Collectors.toList());
} catch (FileNotFoundException e) {
        e.printStackTrace();
} catch (IOException e) {
        e.printStackTrace();
}
            
for (Object obIndObject : olReturn) {
    System.out.println(obIndObject);
}

如果对象类有我想要排序的值的 getter,我知道该怎么做,但这不适用于这里,所以只是看看是否有潜在的解决方案,或者是否不可能?

【问题讨论】:

  • 如果您打算使用Object 基类的实例,您打算将数据放在哪里?
  • 所有数据将用于显示目的。我只是读入 CSV 行,将它们分配给(尽管是空的)对象,然后将它们放入 ArrayList。理想情况下,我只想按每个对象的最后一个索引 (-1) 对 ArrayList 进行排序,除此之外,我不会在程序的其他任何地方使用对象。这有帮助吗,还是我完全错过了这个问题? :')
  • 绝对不是ArrayList。你想以某种“自然”的顺序排列它们。 ArrayList 按顺序添加元素。
  • 重点是Object 是一种不能存储姓名或数字的对象。为了对信息进行排序和显示,您必须有某种方式来存储它。如果您不想定义自己的类,理论上可以只使用Strings 和Integerss,但与创建适当的数据类相比,这是一种笨拙的反模式。
  • 我认为你误解了一些东西。将字符串分配给Object 类型的变量不会自动更改该字符串的运行时类型。您可以随意将其称为Object string = "whatever",但这并不能改变它仍然是String 的事实。

标签: java


【解决方案1】:

tl;博士

List < String > sorted =
                List.of(
                        "Mark,10,0,34" ,
                        "Tom,2,7,19" ,
                        "Billy,2,0,7"
                )
                .stream()
                .sorted( ( String s1 , String s2 ) -> Integer.valueOf( s1.split( "," )[ 3 ] ).compareTo( Integer.valueOf( s2.split( "," )[ 3 ] ) ) )
                .toList(); 

你问:

使用 lambda 表达式对对象的 ArrayList(即从 CSV 文件导入的行)进行排序,

换句话说,您希望按以逗号 (,) 分隔的最后一部分对 String 对象列表进行排序。

对于每个字符串,我们需要调用String#split,返回一个部分数组。鉴于您的示例数据,我们预计恰好有四个部分。所以采取最后一部分。 Java 中的数组令人讨厌地使用索引,即从零开始的计数。所以第 4 项是索引 3,语法上是 [3]

此代码效率不高,因为排序涉及对compareTo 的多次重复调用。对于每个调用,我们都会重新进行拆分。效率不高,但有效。

List < String > inputs =
        new ArrayList <>(
                List.of(
                        "Mark,10,0,34" ,
                        "Tom,2,7,19" ,
                        "Billy,2,0,7"
                )
        );

List < String > sorted =
        inputs
                .stream()
                .sorted( ( String s1 , String s2 ) -> Integer.valueOf( s1.split( "," )[ 3 ] ).compareTo( Integer.valueOf( s2.split( "," )[ 3 ] ) ) )
                .toList();  // Before Java 16: .collect( Collectors.toList() );

System.out.println( "sorted = " + sorted );

看到这个code run live at IdeOne.com

排序 = [Billy,2,0,7, Tom,2,7,19, Mark,10,0,34]

记录

您的问题确实没有意义,因为它明确避免使用类。 Java 是一种面向对象的语言。类是它的基础,它的存在理由

如果您只是因为必需的样板而不愿意定义一个类,我有个好消息要告诉您。 Java 16 带来了新的records 功能。

记录是声明一个类的一种简短方式,该类的主要目的是透明且不可变地传递数据。您只需声明所有类成员字段的类型和名称。编译器隐式创建构造函数、getter、equals & hashCodetoString

所以一个完整的类可以写在一个短行上。

record Person ( String name , int departmentId , int projectId , int employeeId ) {}

根据您的输入数据填充Person 对象的集合。

List < Person > persons = new ArrayList <>( inputs.size() );
for ( String input : inputs )
{
    String[] parts = input.split( "," );
    Person p = new Person( parts[ 0 ] , Integer.valueOf( parts[ 1 ] ) , Integer.valueOf( parts[ 2 ] )  , Integer.valueOf( parts[ 3 ] ) );
    persons.add( p );
}

按最后一个成员字段employeeId对该列表进行排序。

Collections.sort(
        persons ,
        new Comparator < Person >()
        {
            @Override
            public int compare ( Person p1 , Person p2 )
            {
                return Integer.compare( p1.employeeId() , p2.employeeId() );
            }
        }
);

这里是完整的例子。

package work.basil.demo;

import java.util.*;
import java.lang.*;

public class RecordDemo
{
    public static void main ( String[] args )
    {
        List < String > inputs =
                new ArrayList <>(
                        List.of(
                                "Mark,10,0,34" ,
                                "Tom,2,7,19" ,
                                "Billy,2,0,7"
                        )
                );

        record Person(String name , int departmentId , int projectId , int employeeId)
        {
        }

        List < Person > persons = new ArrayList <>( inputs.size() );
        for ( String input : inputs )
        {
            String[] parts = input.split( "," );
            Person p = new Person( parts[ 0 ] , Integer.valueOf( parts[ 1 ] ) , Integer.valueOf( parts[ 2 ] ) , Integer.valueOf( parts[ 3 ] ) );
            persons.add( p );
        }
        System.out.println( "persons = " + persons );

        Collections.sort(
                persons ,
                new Comparator < Person >()
                {
                    @Override
                    public int compare ( Person p1 , Person p2 )
                    {
                        return Integer.compare( p1.employeeId() , p2.employeeId() );
                    }
                }
        );
        System.out.println( "persons = " + persons );
    }
}

运行时。

persons = [Person[name=Mark, departmentId=10, projectId=0, employeeId=34], Person[name=Tom, departmentId=2, projectId=7, employeeId=19], Person[name=Billy, departmentId=2, projectId=0, employeeId=7]]
persons = [Person[name=Billy, departmentId=2, projectId=0, employeeId=7], Person[name=Tom, departmentId=2, projectId=7, employeeId=19], Person[name=Mark, departmentId=10, projectId=0, employeeId=34]]

在上面的代码中,请注意记录的另一个好处:您可以在本地声明记录,在方法内,以及嵌套在类或单独的类(.java 文件)中。在 Java 团队参与的同时,他们现在也允许本地枚举定义,以及本地接口定义。 Java 16 及更高版本中的所有内容。

【讨论】:

  • *public record Person(...
  • 这很棒,类似于我发布的解决方案,但我喜欢它设置 lambda 的方式。我知道 java 喜欢对象:P 不幸的是,这正是我这次设置这个项目的方式。感谢您抽出宝贵时间!
  • @onkarruikar 是的! record,而不是 class。旧习惯很难改掉。
  • 为什么不使用Comparator.comparingInt()
【解决方案2】:

我根据 OP 的评论更新了

public static void main (String[] args) {
    List<String> list =
        Arrays.asList("Mark,10,0,34", "Wyle,2,0,19", "Tom,2,0,19", "Billy,2,0,7");
    
    Comparator<String> cmp = new Comparator<String>() {
        public int compare (String o1, String o2) {
            int diff = (Integer.valueOf(o1.substring(o1.lastIndexOf(',')+1))).compareTo(Integer.valueOf(o2.substring(o2.lastIndexOf(',')+1)));
            return (diff == 0)
                ? o1.compareTo(o2)
                : diff;
        }
    };
    Collections.sort(list, cmp);
    System.out.println(list);
}   

这输出: [Billy,2,0,7, Tom,2,0,19, Wyle,2,0,19, Mark,10,0,34]

此示例将根据最后一组数字或按字母顺序排列最后一组数字相同的情况进行比较。

【讨论】:

  • 我知道这会按字母顺序对列表进行排序,但我不希望这样。我需要按字符串中的最后一个数字排序,所以即使其中一个以“a”开头,如果字符串的最后一个数字大于后面的数字,我也不希望它显示在顶部它。我想出了一个解决方案,我很快就会发布...谢谢!
  • 这与我实现的非常接近,只是将字母顺序作为第二个排序依据。感谢您的帮助!
  • @MattWilson 我纠正了逻辑中的一个小错误,并在最后添加了一个 dup 值的案例,这样您就可以看到它如何像数值一样解析。
【解决方案3】:

好的,它没有使用 lambda 表达式,但是我能够修改找到的解决方案 here 以满足我的需要。看看:

这是自定义的比较器类:

private class MyObjectComparator<MyObject> implements Comparator<MyObject> {
      /**
       * {@inheritDoc}
       */
      @Override
      public int compare(MyObject o1, MyObject o2) {
         return Integer.parseInt(o1.toString().split(",")[3]) - Integer.parseInt(o2.toString().split(",")[3]);
      }
    }

如您所见,我在拆分字符串后解析第三个索引值的整数。换句话说,我取每个字符串“对象”,拆分它们,然后引用我想要引用的值。然后,我只是调用这个类:

    // Import the csv file and assign each row to a list of objects
    List<Object> olReturn = null;
    try (BufferedReader br = new BufferedReader(new FileReader("stats_log.csv"))) {
        olReturn = br.lines().collect(Collectors.toList());
    // call to comparator here
        Collections.sort(olReturn, new MyObjectComparator());
    } catch (FileNotFoundException e) {
        e.printStackTrace();
    } catch (IOException e) {
        e.printStackTrace();
    }

这让我得到了我需要的东西。感谢您与我一起解决这个问题!

【讨论】:

    【解决方案4】:

    br.lines() 返回 String 的 Stream,所以底层对象是 String,因此你只需要做一些 String 索引来得到你需要的东西

    olReturn = br.lines().sorted(Comparator.comparing(x -> Long.valueOf(x.substring(x.lastIndexOf(",") + 1)))).collect(Collectors.toList());
    

    【讨论】:

    • 我无法让这段代码工作。也许你可以在 IdeOne.com 上 fork 我的代码版本,然后进行调整。 ideone.com/JTLhu1
    • @Basil 在 OP 的代码中使用 BufferredReader,因此 br.lines() 是 Stream,所以您的伪代码应该类似于:ideone.com/J1txTV
    【解决方案5】:

    您应该避免在Comparator 中计算splitInteger.parseInt,因为它是低效的。

    试试这个。

    List<String> list =
        Arrays.asList("Mark,10,0,34", "Wyle,2,0,19", "Tom,2,0,19", "Billy,2,0,7");
    List<String> result = list.stream()
        .map(line -> new Object() {
            int sortKey = Integer.parseInt(line.split(",")[3]);
            String self = line;
        })
        .sorted(Comparator.comparingInt(object -> object.sortKey))
        .map(object -> object.self)
        .collect(Collectors.toList());
    System.out.println(result);
    

    输出:

    [Billy,2,0,7, Wyle,2,0,19, Tom,2,0,19, Mark,10,0,34]
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-11-24
      • 2015-12-17
      • 2014-11-09
      • 1970-01-01
      • 2016-04-15
      • 2023-03-22
      相关资源
      最近更新 更多