netty系列之:我有一個可擴充套件的Enum你要不要看一下?

flydean發表於2022-06-06

簡介

很多人都用過java中的列舉,列舉是JAVA 1.5中引用的一個新的型別,用來表示可以列舉的範圍,但是可能很少有人知道java中的enum到底是怎麼工作的,enum和Enum有什麼關係?Enum可不可以擴充套件?

一起來看看吧。

enum和Enum

JAVA1.5中引入了列舉類,我們通常使用enum關鍵字來定義一個列舉類:

public enum StatusEnum {
    START(1,"start"),
    INPROCESS(2,"inprocess"),
    END(3,"end");

    private int code;
    private String desc;

    StatusEnum(int code, String desc){
        this.code=code;
        this.desc=desc;
    }
}

上面的列舉類中,我們自定義了建構函式,並且定義了3個列舉物件。

接下來看下怎麼來使用這個列舉類:

    public static void main(String[] args) {
        StatusEnum start = START;
        System.out.println(start.name());
        System.out.println(start.ordinal());
        System.out.println(start.code);
        System.out.println(start.desc);
    }

可以輸出code和desc很好理解,因為這是我們自定義的列舉類中的屬性,但是name和ordinal是什麼呢?他們是哪裡來的呢?

這裡就要介紹java.lang.Enum類了,它是JAVA中所有enum列舉類的父類,name()和ordinal()方法就是在這個類中定義的:

public final int ordinal() {
        return ordinal;
    }

public final String name() {
        return name;
    }

其中ordinal表示的是列舉類中列舉的位置,那麼就是列舉類中列舉的名字。在上面的例子中,START的兩個值分別是1和START。

我們來看下Enum類的定義:

public abstract class Enum<E extends Enum<E>>
        implements Comparable<E>, Serializable

輸入它是一個抽象類,但是編譯器是不允許你繼承這個類的。如果你強行繼承,則會拋錯:

Classes cannot directly extend 'java.lang.Enum'

所以說,強扭的瓜不甜,大家一定要記住。

事實上,不僅僅Enum類本身不能被繼承,上面建立的enum類StatusEnum也是不能被繼承的。

這會造成一個什麼問題呢?

如果這個enum是包含在一個外部jar包中的時候,你就沒法對該enum進行擴充套件,在某些特定的情況下,這樣的限制可能會帶來一些不便。

還好,netty也意識到了這個問題,接下來,我們看下netty是怎麼解決的。

netty中可擴充套件的Enum:ConstantPool

netty中的表示常量的類叫做Constant,它有兩個屬性,分別是ID和name:

public interface Constant<T extends Constant<T>> extends Comparable<T> {

    int id();

    String name();
}

儲存這些Constant的就叫做ConstantPool。ConstantPool中有一個ConcurrentMap用來儲存具體的Constant。 我們看一下ConstantPool
的工廠類方法valueOf:

public T valueOf(String name) {
        return getOrCreate(checkNonEmpty(name, "name"));
    }

valueOf方法傳入建立的Constant的名字。然後呼叫getOrCreate方法來建立新的Constant:

    private T getOrCreate(String name) {
        T constant = constants.get(name);
        if (constant == null) {
            final T tempConstant = newConstant(nextId(), name);
            constant = constants.putIfAbsent(name, tempConstant);
            if (constant == null) {
                return tempConstant;
            }
        }

        return constant;
    }

可以看到getOrCreate就是向constants Map中建立和獲取新建立的constant物件。

使用ConstantPool

ConstantPool是一個抽象類,如果我們需要新建一個列舉類池,可以直接繼承ConstantPool,然後實現其中的newConstant方法。下面是一個使用的具體例子:

public final class Foo extends AbstractConstant<Foo> {
  Foo(int id, String name) {
    super(id, name);
  }
}

public final class MyConstants {

  private static final ConstantPool<Foo> pool = new ConstantPool<Foo>() {
    @Override
    protected Foo newConstant(int id, String name) {
      return new Foo(id, name);
    }
  };

  public static Foo valueOf(String name) {
    return pool.valueOf(name);
  }

  public static final Foo A = valueOf("A");
  public static final Foo B = valueOf("B");
}

private final class YourConstants {
  public static final Foo C = MyConstants.valueOf("C");
  public static final Foo D = MyConstants.valueOf("D");
}

在上面的例子中,我們建立的列舉類繼承自AbstractConstant,然後自定義了ConstantPool,從pool中可以返回新建立的Foo物件。

實時上,在netty channel中經常使用的ChannelOption就是AbstractConstant的子類,我們簡單來看下其中的實現:

public class ChannelOption<T> extends AbstractConstant<ChannelOption<T>> {

    private static final ConstantPool<ChannelOption<Object>> pool = new ConstantPool<ChannelOption<Object>>() {
        @Override
        protected ChannelOption<Object> newConstant(int id, String name) {
            return new ChannelOption<Object>(id, name);
        }
    };
    public static <T> ChannelOption<T> valueOf(String name) {
        return (ChannelOption<T>) pool.valueOf(name);
    }
    public static <T> ChannelOption<T> valueOf(Class<?> firstNameComponent, String secondNameComponent) {
        return (ChannelOption<T>) pool.valueOf(firstNameComponent, secondNameComponent);
    }
    public static boolean exists(String name) {
        return pool.exists(name);
    }
    public static <T> ChannelOption<T> newInstance(String name) {
        return (ChannelOption<T>) pool.newInstance(name);
    }

可以看到,ChannelOption中定義了ConstantPool,然後通過ConstantPool的valueOf和newInstance方法來建立新的ChannelOption物件。

總結

如果你也想要對列舉類進行擴充套件,不妨使用Constant和ConstantPool試試。

本文的例子可以參考:learn-netty4

更多內容請參考 http://www.flydean.com/49-netty-extensible-enum/

最通俗的解讀,最深刻的乾貨,最簡潔的教程,眾多你不知道的小技巧等你來發現!

歡迎關注我的公眾號:「程式那些事」,懂技術,更懂你!

相關文章