QA@IT

【Java】ArrayList内の要素の重複を除きユニークにするには?

21558 PV

JavaのArrayListについて質問です。

ArrayList内の要素の重複を除きユニークにするにはどうしたらいいですか?

List → ["a","b","b","c","a"]
上記のように重複した要素を削除して、
List → ["a","b","c"]
このような形にしたいです。

ArrayListのように要素がString型などの場合はSetにaddAllすれば重複がのぞけると思いますが、
ArrayListのように要素が自身で作成したオブジェクト(複数のフィールドを持ちます)の場合に上手くいかず困っています。

Beanの中身としては、

public class Bean {
  private String id;
  private String name;
  private Integer age;

  //Constructor...
  //Setter and Getter...
}

のような感じになります。

public class Main {
  public static void main(String[] args) {
    List<Bean> beans = new ArrayList<>();

    Bean b1 = new Bean("1","hoge","23");
    Bean b2 = new Bean("2","hoge","23");
    Bean b3 = new Bean("3","pojo","24");
    Bean b4 = new Bean("1","hoge","23");

    beans.add(b1);
    beans.add(b2);
    beans.add(b3);
    beans.add(b4);
  }
}

上記の状態でb1~b4のインスタンスがリストの要素として入りますが、
b1とb4はフィールドの内容が全て重複しているため除きたいのです。
重複データが入っているListから重複を削除したb1,b2,b3だけがのこるListを生成するにはどのようにしたらよろしいでしょうか?

ご教授いただけると助かります。
よろしくお願い致します。

回答

Beanでequalsとhashcodeをオーバーライドすれば可能です。
ただし、何をもって等しいとするかは設計次第ですので用途に合わせて適切に実装する必要があります。

示された Beanの場合は以下のような感じでしょうか。前述の通り等価とみなす仕様次第なのでそのまま使えるかはわかりません。
(コンストラクタに渡しているageの値がStringなのがちょっとひっかかりますが。)

package qait9842;

public class Bean {
    private String id;
    private String name;
    private Integer age;

    @Override
    public boolean equals(Object obj){

        if (obj==null) return false;

        if (! (obj instanceof Bean) ) return false;
        if (this==obj) return true;

        Bean other = (Bean)obj;
        if(age != other.age) return false;
        if(id == null && other.id != null || id != null && other.id == null) return false;
        if(name == null && other.name != null || name != null && other.name == null ) return false;

        if(age == other.age && id.equals(other.id) && name.equals(name)) return true;

        return false;
    }

    @Override
    public int hashCode() {
        final int oddPrime = 31;
        int result = oddPrime;
        result += age;
        result *= oddPrime;
        result += (name == null) ? 0 : name.hashCode();
        result *= oddPrime;
        result += (id == null) ? 0 : id.hashCode();
        return result;
    }

// constructor
// getter, setter
}

適当ですけどこんなテストはパスします。

package qait9842;

import static org.hamcrest.CoreMatchers.*;
import static org.hamcrest.MatcherAssert.*;

import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;

import org.junit.Test;

public class BeanTest {

    @Test
    public void testEqualsObject() {
        Bean a = new Bean(){{
            this.setAge(0);
            this.setId("a");
            this.setName("b");
        }};

        Bean b = new Bean(){{
            this.setAge(0);
            this.setId("a");
            this.setName("b");
        }};

        // 行数節約のため複数ケース書いてます。
        assertThat(b, is(a));
        assertThat(new Bean(), is(a));
        assertThat(new Bean(){{
            setAge(0);
            setId("a");
            setName(null);
        }}, is(a));
    }

    @Test
    public void testMakeUniqueWithAddAllMethod(){

        List<Bean> beans = new LinkedList<>();
        beans.add(new Bean(){{
            setAge(0); setId("a"); setName("b");
        }});
        beans.add(new Bean(){{
            setAge(0); setId("a"); setName("b");
        }});
        beans.add(new Bean(){{
            setAge(0); setId("a"); setName("c");
        }});
        beans.add(new Bean(){{
            setAge(0); setId("a"); setName("b");
        }});
        beans.add(new Bean(){{
            setAge(0); setId("a"); setName("c");
        }});
        beans.add(new Bean(){{
            setAge(0); setId("a"); setName("b");
        }});


        assertThat(beans.size(), is(6));

        Set<Bean> work = new LinkedHashSet<>();
        work.addAll(beans);

        beans.clear();
        beans.addAll(work);

        assertThat(beans.size(), is(2));
        assertThat(beans.get(0).getName(), is("b"));
        assertThat(beans.get(1).getName(), is("c"));

    }

    @Test
    public void testQ9842(){

        List<Bean> beans = new LinkedList<>();
        beans.add(new Bean(){{
            setId("1"); setName("hoge"); setAge(23);
        }});
        beans.add(new Bean(){{
            setId("2"); setName("hoge"); setAge(23);
        }});
        beans.add(new Bean(){{
            setId("3"); setName("pojo"); setAge(24);
        }});
        beans.add(new Bean(){{
            setId("1"); setName("hoge"); setAge(23);
        }});

        Set<Bean> work = new LinkedHashSet<>();
        work.addAll(beans);

        beans.clear();
        beans.addAll(work);

        assertThat(beans.size(), is(3));
        assertThat(beans.get(0).getId(), is("1"));
        assertThat(beans.get(1).getId(), is("2"));
        assertThat(beans.get(2).getName(), is("pojo"));

    }}

ダイアモンド演算子つかってるので 1.7以降だろうということで、java1.7でコンパイルしてます。
junitは4です。

編集 履歴 (3)
  • 回答のほどありがとうございます!
    equalsだけでなくhashCodeもオーバーライドしなければならないのですね!
    hashCodeをオーバーライドしたところ上手くいきましたしとても勉強になりました!
    ありがとうございます!
    -
  • サンプルのequalsではinstanceofを使っている部分が継承クラスでは期待通りに動かないかもしれませんので注意してください。 -
ウォッチ

この質問への回答やコメントをメールでお知らせします。