当我想从树集中删除 object 时,为什么会出现不受支持的操作异常

why do i get an unsupported operation exception when i want to remove an object from a treeset

我有这个 3 class :

第一个

private List<Domino> pile = new ArrayList<Domino>();    
    
    public DominoPile(List<Domino> list) {
        this.pile = list;
            
    }
    public List<Domino> getList(){
        return List.copyOf(pile);
    }
    public void getOutLastOne() {
        pile.remove(this.getLast(0));
    }
    public Domino getLast(int nbr) {
        return nbr  == 0 || nbr > this.getSize() ? pile.get(pile.size() - 1) : pile.get(pile.size() - 1 - nbr);
    }
    public int getSize() {
        return pile.size();
    }
    
}

第二个

public class KingdominoGameFactory {
    
    private List<Player> pl;
    private List<Domino> dom;
    private int selected;
    private DominoPile pile;
    private int nbrPl;
    
    
    public KingdominoGameFactory(List<Player> p, List<Domino> d) {
        // TODO Auto-generated constructor stub
        this.setPl(List.copyOf(p));
        this.setDom(List.copyOf(d));
    }


    public List<Player> getPl() {
        return pl;
    }


    public void setPl(List<Player> pl) {
        this.pl = pl;
    }


    public List<Domino> getDom() {
        return dom;
    }


    public void setDom(List<Domino> dom) {
        this.dom = dom;
    }


    public int getSelected() {
        return selected;
    }


    public void setSelected(int selected) {
        this.selected = selected;
    }


    public int getNbrPl() {
        return nbrPl;
    }


    public void setNbrPl() {
        if(selected == 0) this.nbrPl = 2;
        else if(selected == 1) this.nbrPl = 3;
        else this.nbrPl = 4;
    }

    public DominoPile getPile() {
        return pile;
    }


    private void setPile(DominoPile pile) {
        this.pile = pile;
    }
    public void nbrDomFinal() {
        if(this.getNbrPl() == 2) this.setPile(new DominoPile(this.getDom().subList(0, 24)));
        else if(this.getNbrPl() == 3) this.setPile(new DominoPile(this.getDom().subList(0, 36)));
        else this.setPile(new DominoPile(this.getDom()));
    }
    public void nbrPlFinal() {
        if(this.getNbrPl() == 2) this.setPl(this.getPl().subList(0, 2));
        else if(this.getNbrPl() == 3) this.setPl(this.getPl().subList(0, 3));
        else this.setPl(this.getPl());
    }

第三个

public class Game {

    private List<Player> players;
    private DominoPile pile;
    private DrawLine actual = new DrawLine(new TreeSet<Domino>());
    private final int nbrDraw;
    
    public Game(KingdominoGameFactory kg){
        this.nbrDraw = kg.getNbrPl() == 3 ? 3 : 4;
        this.pile = new DominoPile(List.copyOf(kg.getPile().getList()));
        this.players = this.getListPlayers();
    }
    private void addActual(){
        actual.add(pile.getLast(0));
    }
    public int getNbrDraw() {
        return nbrDraw;
    }
    public List <Player> getListPlayers(){
        return players;
    }
    public void setDrawActual() {
        actual.clear();
        for(int i = 0; i < nbrDraw; ++i) {
            this.addActual();
            pile.getOutLastOne();
        }
    }
    public DrawLine getActual() {
        return actual;
    }
    
    public DominoPile getPile() {
        return pile;
    }
    
}

那我就做了这个测试

    @BeforeEach
    void setup() {
        List<Domino> liste = new ArrayList<Domino>();
        liste.add(new Domino(1, new Tile(Terrain.CASTLE, 2), new Tile(Terrain.CASTLE, 1)));
        liste.add(new Domino(2, new Tile(Terrain.CASTLE, 2), new Tile(Terrain.CASTLE, 1)));
        liste.add(new Domino(3, new Tile(Terrain.CASTLE, 2), new Tile(Terrain.CASTLE, 1)));
        liste.add(new Domino(4, new Tile(Terrain.EMPTY, 2), new Tile(Terrain.CASTLE, 1)));
        liste.add(new Domino(5, new Tile(Terrain.EMPTY, 2), new Tile(Terrain.CASTLE, 1)));
        liste.add(new Domino(6, new Tile(Terrain.EMPTY, 2), new Tile(Terrain.CASTLE, 1)));
        liste.add(new Domino(7, new Tile(Terrain.EMPTY, 2), new Tile(Terrain.CASTLE, 1)));
        liste.add(new Domino(8, new Tile(Terrain.EMPTY, 2), new Tile(Terrain.CASTLE, 1)));
        liste.add(new Domino(9, new Tile(Terrain.EMPTY, 2), new Tile(Terrain.CASTLE, 1)));
        liste.add(new Domino(10, new Tile(Terrain.EMPTY, 2), new Tile(Terrain.CASTLE, 1)));
        liste.add(new Domino(11, new Tile(Terrain.EMPTY, 2), new Tile(Terrain.CASTLE, 1)));
        liste.add(new Domino(12, new Tile(Terrain.EMPTY, 2), new Tile(Terrain.CASTLE, 1)));
        liste.add(new Domino(13, new Tile(Terrain.EMPTY, 2), new Tile(Terrain.CASTLE, 1)));
        liste.add(new Domino(14, new Tile(Terrain.EMPTY, 2), new Tile(Terrain.CASTLE, 1)));
        liste.add(new Domino(15, new Tile(Terrain.EMPTY, 2), new Tile(Terrain.CASTLE, 1)));
        liste.add(new Domino(16, new Tile(Terrain.EMPTY, 2), new Tile(Terrain.CASTLE, 1)));
        liste.add(new Domino(17, new Tile(Terrain.EMPTY, 2), new Tile(Terrain.CASTLE, 1)));
        liste.add(new Domino(18, new Tile(Terrain.EMPTY, 2), new Tile(Terrain.CASTLE, 1)));
        liste.add(new Domino(19, new Tile(Terrain.EMPTY, 2), new Tile(Terrain.CASTLE, 1)));
        liste.add(new Domino(20, new Tile(Terrain.EMPTY, 2), new Tile(Terrain.CASTLE, 1)));
        liste.add(new Domino(21, new Tile(Terrain.EMPTY, 2), new Tile(Terrain.CASTLE, 1)));
        liste.add(new Domino(22, new Tile(Terrain.EMPTY, 2), new Tile(Terrain.CASTLE, 1)));
        liste.add(new Domino(23, new Tile(Terrain.EMPTY, 2), new Tile(Terrain.CASTLE, 1)));
        liste.add(new Domino(24, new Tile(Terrain.EMPTY, 2), new Tile(Terrain.CASTLE, 1)));
        liste.add(new Domino(25, new Tile(Terrain.EMPTY, 2), new Tile(Terrain.CASTLE, 1)));
        List<Player> liste2 = new ArrayList<Player>();
        liste2.add(new Player("Jeff", "366"));
        liste2.add(new Player("Jeff", "366"));
        liste2.add(new Player("Jeff", "366"));
        liste2.add(new Player("Jeff", "366"));
        
        kg = new KingdominoGameFactory(liste2, liste);
        kg.setSelected(0);
        kg.setNbrPl();
        kg.nbrDomFinal();
        kg.nbrPlFinal();
        
        game = new Game(kg);
    }


    @Test
    void setActual() {
        game.setDrawActual();
        assertEquals(game.getActual().getSize(), 4);
    }

然后我有这个错误

java.lang.UnsupportedOperationException
    at java.base/java.util.ImmutableCollections.uoe(ImmutableCollections.java:72)
    at java.base/java.util.ImmutableCollections$AbstractImmutableCollection.remove(ImmutableCollections.java:79)
    at kingdomino.domains.DominoPile.getOutLastOne(DominoPile.java:23)
    at kingdomino.domains.Game.setDrawActual(Game.java:35)
    at kingdomino.domains.GameTest.setActual(GameTest.java:87)
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.base/java.lang.reflect.Method.invoke(Method.java:566)

我不知道为什么我不能从游戏中移除 class 因为 DominoPile 的测试都成功了,这是唯一有问题的测试,我不知道如何解决这个问题我试图改变我的 collection 类型,我也试图改变我的方法“getOutLastOne”

List.copyOf

这会生成一个 不可变(即:只读)副本,无论您传递给它什么。复制这个没有进一步的意义(事实上,我相信如果你在其中传递一个 already-immutable 列表,它只是逐字地 returns ;复制无法修改的东西没有意义).

创建 DominoPile 对象时,将 List.copyOf 的结果传递给其构造函数,这意味着 DominoPile 中的 pile 字段现在是这些不可变列表之一。稍后您的代码调用 getOutLastOne 修改(变异)由 pile 字段引用的列表,这不起作用,因为它无法修改。如例外情况所述。

这就是问题所在。您可以使用 3 种不同的策略来解决问题。概括地说:

  1. DominoPile 的构造函数中的文档,passed-in 列表被逐字记录并将按原样修改,意思是:它不能是不可变的(现在调用者有责任阅读文档并按照它说的去做),以及 DominoPile 对列表造成的任何影响(例如删除最后一个)回显到用于首先创建 DominoPile 对象的列表。

完成后(基本上,您需要做的就是添加一些 javadoc),然后是 caller(您当前有 new DominoPile(List.copyOf(kg....)))需要停止传递不可变列表。这很简单 - 将 List.copyOf(X) 替换为 new ArrayList<>(X) - 这也制作了一个副本,但是 mutable 一个。

  1. 或者,您也可以选择稍微不同的设计:让 DominoPile 仅将提供的列表视为模板,从而让您的 DominoPile 构造函数创建一个新的(可变的!)列表,该列表是一个副本它的。换句话说,将构造函数和 set 方法中的 this.pile = pile 替换为 this.pile = new ArrayList<>(pile)。但我有点不喜欢这个名字 setPile 如果是这样的话。

  2. DominoPile 变得不可变,所有设置方法都需要消失,所有数据结构都变得不可变,并且像 .getOutLastOne 这样的方法实际上不会删除任何东西(它不能 - 一切无法修改),而是创建一个全新的 DominoPile 对象,该对象是自身的克隆,只是删除了一个多米诺骨牌。现在,您的列表是不可变的这一事实(如果有的话)是一个好处。这是... over-engineering 我认为有相当大的差距。

这些选项是按照我会怎么做的顺序列出的 - 所以,除非你有一个紧迫的理由为什么第一个选项对你来说不好,否则我会选择第一个,在这里。基于你的 API 设计(比如有一个 set 方法,这意味着你似乎并不特别渴望不变性)。