国产xxxx99真实实拍_久久不雅视频_高清韩国a级特黄毛片_嗯老师别我我受不了了小说

資訊專欄INFORMATION COLUMN

Java 中實現集合的 keep in order

gyl_coder / 2588人閱讀

摘要:此項禁止的一個特殊情況是不允許某個包含其自身作為元素。即使的順序與不一致,其行為也是定義良好的它只是違背了接口的常規協定。

原問題

Java 中怎樣實現一種即使元素改變依然有序的集合?

問題由來

起因是在公司做游戲項目的時候遇到一個需求需要實現:

服務器要維護一個幫派成員(Member)的集合,這個集合要按照在線狀態成員等級名稱依次有序排列

由于每時每刻都有玩家在不斷上下線,成員的經驗值也在不斷的改變。因此這個集合的有序狀態在不斷變化,起初的想法是維護一個有序的列表 ListCollections.sort() 加上一個定時結構,比如每十分鐘對列表排序一次。然后客戶端請求集合的時候返回這個列表即可。

這種方法的優點是排序是顯而易見的:定時執行、開銷相對小。類似的代碼如下:

class Member {
    // 幫派成員類
}

// 維護有序列表
List orderedMembers = Collections.synchronizedList(new ArrayList());

// 在服務器的刷幀循環中定時排序
@Override
public void onFrameUpdated(long currentTimeMillis) {
    // 十分鐘排序一次
    if (lastSorting + 10 * 60 * 1000 < currentTimeMillis) {
        // 排序 ....
        sort(orderedMembers);
    }
}
實時響應的排行榜

我想讓這個集合能實時響應玩家自身的變化,而不是定時掃描一次,于是一開始我是這么嘗試的,繼承 TreeSet 然后實現一個重新排序的回調 ReorderCallback,在任何玩家經驗值改變或是上線下線的時候調用回調的方法 reorder() 來使集合保持有序,代碼如下

interface ReorderCallback {

    // 回調方法,在任何影響排序的狀態值改變的時候被調用
    void reorder(T element);
}

// 自定義了一個 Set 用于監測成員類的變化
class AlwaysOrderedSet extends TreeSet implements ReorderCallback {

    // 實現回調
    @Override
    public void reorder(T element) {
        remove(element);
        add(element);
    }
}

然后在需要的地方回調:

// 幫派成員類
class Member {

    ReorderCallback rCallback;
    
    // 玩家上線
    @Override
    public void onOnline() {
        if (rCallback != null) {
            rCallback.reorder(this);
        }
    }
    
    // 玩家下線
    @Override
    public void onOffline() {
        if (rCallback != null) {
            rCallback.reorder(this);
        }
    }
}

然而,每次玩家狀態改變后調用 reorder() 并不能保持原集合的有序,反而會重復添加 member 對象。因為 TreeSet 無法追蹤元素的變化,就像以下的演示一樣:

public class Sorter {

    public static void main(String[] args) {

        class Student implements Comparable {

            int id;
            String name;
            int age;

            Student(int id, String name, int age) {
                this.id = id;
                this.name = name;
                this.age = age;
            }

            @Override
            public String toString() {
                return String.format("id=%d, name=%s, age=%d", id, name, age);
            }

            @Override
            public int compareTo(Student o) {
                return o.age - this.age;
            }
        }

        Set alwaysOrdered = new TreeSet<>();

        Student a = new Student(1, "Amy", 50);
        Student b = new Student(2, "Bob", 30);
        Student c = new Student(3, "Chris", 40);

        alwaysOrdered.add(a);
        alwaysOrdered.add(b);
        alwaysOrdered.add(c);

        System.out.println("-- before --");
        alwaysOrdered.forEach(System.out::println);

        // 集合元素發生改變
        b.age = 100;

        System.out.println("-- after --");
        alwaysOrdered.forEach(System.out::println);

        // 這里就相當于回調,簡單的 remove 再 add
        alwaysOrdered.remove(b);
        alwaysOrdered.add(b);

        System.out.println("-- after remove and add --");
        alwaysOrdered.forEach(System.out::println);
    }
}

上面的代碼運行結果如下:

-- before --
id=1, name=Amy, age=50
id=3, name=Chris, age=40
id=2, name=Bob, age=30
-- after --
id=1, name=Amy, age=50
id=3, name=Chris, age=40
id=2, name=Bob, age=100
-- after remove and add --
id=2, name=Bob, age=100
id=1, name=Amy, age=50
id=3, name=Chris, age=40
id=2, name=Bob, age=100

可以看出,我試圖改變 Bob 的年齡,然后先后調用集合的 removeadd 卻并不是我預想的那樣,remove 實際是返回了 false,因此 b 根本沒有從集合中移除。

百思不得其解后我試圖閱讀 Set 的源代碼,發現了一些線索:

 * 

Note: Great care must be exercised if mutable objects are used as set * elements. The behavior of a set is not specified if the value of an object * is changed in a manner that affects equals comparisons while the * object is an element in the set. A special case of this prohibition is * that it is not permissible for a set to contain itself as an element.

意思是:

注:如果將可變對象用作 set 元素,那么必須極其小心。如果對象是 set 中某個元素,以一種影響 equals 比較的方式改變對象的值,那么 set 的行為就是不確定的。此項禁止的一個特殊情況是不允許某個 set 包含其自身作為元素。

TreeSet 的源碼文檔里是這么寫的

 * 

Note that the ordering maintained by a set (whether or not an explicit * comparator is provided) must be consistent with equals if it is to * correctly implement the {@code Set} interface. (See {@code Comparable} * or {@code Comparator} for a precise definition of consistent with * equals.) This is so because the {@code Set} interface is defined in * terms of the {@code equals} operation, but a {@code TreeSet} instance * performs all element comparisons using its {@code compareTo} (or * {@code compare}) method, so two elements that are deemed equal by this method * are, from the standpoint of the set, equal. The behavior of a set * is well-defined even if its ordering is inconsistent with equals; it * just fails to obey the general contract of the {@code Set} interface.

大致意思是:

注意,如果要正確實現 Set 接口,則 set 維護的順序(無論是否提供了顯式比較器)必須與 equals 一致。(關于與 equals 一致 的精確定義,請參閱 ComparableComparator。)這是因為 Set 接口是按照 equals 操作定義的,但 TreeSet 實例使用它的 compareTo(或 compare)方法對所有元素進行比較,因此從 set 的觀點來看,此方法認為相等的兩個元素就是相等的。即使 set 的順序與 equals 不一致,其行為也是定義良好的;它只是違背了 Set 接口的常規協定。

總的來說就是,Set 的定義是不包含重復元素的集合,這個不重復的特性由它維護的元素的 equals() 方法來保證,這個好理解,關鍵是下面。TreeSet 并不完全遵守這個定義,它的不重復性由元素的 compareTo() 方法來保證。

因此,即便元素重寫了 equals() 方法和 hashCode() 方法,當元素 E 的值發生改變時,對于 TreeSet,對 contains(E) 的調用始終返回 false,因此對 remove(E) 的調用也始終返回 false,因為 TreeSet 只依據 compareTo 方法來判斷兩個元素是否相等并進行排序。

所以對于之前的 Student 示例,解決方法就是讓 Student 實現 Comparable 并重寫 compareTo 方法,用該方法代替 equals() 方法去定義對象是否相等的邏輯,同時加上排序:

class Student implements Comparable {

    int id;
    String name;
    int age;

    Student(int id, String name, int age) {
        this.id = id;
        this.name = name;
        this.age = age;
    }

    @Override
    public String toString() {
        return String.format("id=%d, name=%s, age=%d", id, name, age);
    }

    // compareTo 方法有兩個作用:1、代替 equals,2、compareTo
    @Override
    public int compareTo(Student o) {
        // 這里的判斷非常關鍵,代替了 equals() 方法
        if (id == o.id) {
        return 0;
    }
                
    // 這里再進行原始的 compareTo 判斷
    if (age == o.age) {
        if (name.equals(o.name)) {
            return id - o.id;
        }
        return name.compareTo(o.name);
    }
    return o.age - age;
    }
}

改寫后的 Student 的代碼運行結果如下:

id=1, name=Amy, age=50
id=3, name=Chris, age=40
id=2, name=Bob, age=30
-- after --
id=1, name=Amy, age=50
id=3, name=Chris, age=100
id=2, name=Bob, age=30
-- after remove and add --
id=3, name=Chris, age=100
id=1, name=Amy, age=50
id=2, name=Bob, age=30

文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。

轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/65775.html

相關文章

  • Java 中實集合 keep in order (后續)

    摘要:寫完上一篇中實現集合的后,自己又進行了一番探索,結合在公司項目的實際測試后,總結了一個更加有效地基于紅黑樹的結構來實現集合的,由于使用二叉樹來保存有序集合,因此對集合的增加刪除查找的時間復雜度均為。 寫完上一篇「Java 中實現集合的 keep in order」后,自己又進行了一番探索,結合在公司項目的實際測試后,總結了一個更加有效地、基于 TreeSet(紅黑樹)的結構來實現集合的...

    loostudy 評論0 收藏0
  • Spring核心接口之Ordered

    摘要:一接口介紹中提供了一個接口。從單詞意思就知道接口的作用就是用來排序的。類實現了接口的一個比較器。三中使用接口在的例子在配置文件中添加,那么默認會注入和這兩個類。 一、Ordered接口介紹Spring中提供了一個Ordered接口。從單詞意思就知道Ordered接口的作用就是用來排序的。Spring框架是一個大量使用策略設計模式的框架,這意味著有很多相同接口的實現類,那么必定會有優先級...

    cartoon 評論0 收藏0
  • 基于LinkedBlockingQueue實股票交易系統

    摘要:與基于數組的隊列相同,重載的構造函數可以接受集合指定的初始值。這種隊列比基于數組阻塞隊列具有更高的吞吐量。創建個交易者實例,將自己出售的訂單放入隊列中,每個出售訂單都將會有隨機的交易量。要使用基于優先級的隊列,需要提供適當的比較器。 阻塞隊列 在阻塞隊列的幫助下,許多同步問題都可以被公式化。阻塞隊列是隊列,當線程試圖對空隊列進行出列操作,或試圖向滿的隊列中插入條目時,隊列就會阻塞。直到...

    30e8336b8229 評論0 收藏0
  • 從零開始實一個簡易Java MVC框架(六)--加強AOP功能

    摘要:在前面的文章中實現的功能時,目標類都只能被一個切面代理,如果想要生成第二個代理類,就會把之前的代理類覆蓋。改裝原有功能現在要改裝原來的的實現代碼,讓的功能加入到框架中為了讓切面能夠排序,先添加一個注解,用于標記排序。 前言 在前面從零開始實現一個簡易的Java MVC框架(四)--實現AOP和從零開始實現一個簡易的Java MVC框架(五)--引入aspectj實現AOP切點這兩節文章...

    Loong_T 評論0 收藏0
  • python設計模式-抽象工廠模式

    摘要:源碼參考抽象工廠模式抽象工廠模式提供一個接口,用于創建相關或依賴對象的家族,而不需要指定具體類。工廠方法模式和抽象工廠模式如何選擇開始的時候,可以選擇工廠方法模式,因為他很簡單只需要繼承,并實現工廠方法即可。 問題:在上一篇 python設計模式:工廠方法模式我們嘗試使用工廠方法創建了披薩店,現在為了保證披薩加盟店也能有良好的聲譽,我們需要統一原材料,這個該如何做呢? 為了確保每家加盟...

    seal_de 評論0 收藏0

發表評論

0條評論

最新活動
閱讀需要支付1元查看
<