ホームページ  >  記事  >  Java  >  Java 列挙型の使用方法の詳細な説明

Java 列挙型の使用方法の詳細な説明

黄舟
黄舟オリジナル
2017-09-09 11:23:181951ブラウズ

この記事では、Java 列挙型の使用方法の詳細な説明に関する情報を主に紹介します。この記事を通じて列挙型の使用方法をマスターできるようになることを願っています。

序文: コード内のフラグとステータスは列挙型に置き換える必要があります

多くの人は、列挙型は実際の開発ではめったに使用されないか、まったく使用されないと言います。なぜなら、彼らのコードは次のようになっていることがよくあります:


public class Constant { 
  /* 
   * 以下几个变量表示英雄的状态 
   */ 
  public final static int STATUS_WALKING = 0;//走  
  public final static int STATUS_RUNNINGING = 1;//跑 
  public final static int STATUS_ATTACKING = 2;//攻击  
  public final static int STATUS_DEFENDING = 3;//防御  
  public final static int STATUS_DEAD = 4;//挂了  
   
  /* 
   * 以下几个变量表示英雄的等级 
   */ 
  //此处略去N行代码 
}

そして、このクラスを次のように使用します:


hero.setStatus(Contant.STATUS_ATTACKING);

さて、その後、彼らは「実際の開発の経験はほとんどないので列挙型を使用してください」と言います。

もちろん、列挙クラス Enum がほとんど使用されないことを意味します。

ただし、私が言いたいのは、上記のコードはすべて Enum を使用して実装する必要があるということです。

なぜですか?

なぜなら、彼らのコードは完全にチームメイトへの信頼に基づいているからです。見知らぬチームメイトがやって来て、次のことをしたとします:


hero.setStatus(666);

画面上の主人公に何が起こると思いますか?


つまり、実際のプログラミングでこのようなコードを頻繁に使用する場合は、Enum を学習する時期が来ています。


列挙型についての予備調査なぜ列挙型を使用するのか

惑星や曜日などの「自然な列挙型」や、csdn タブ ラベルなどの私たちが設計した列挙型など、生活のいたるところに列挙型があります。 、メニューなど。

Java コードで列挙を表現するには通常 2 つの方法があります。1 つは int 列挙ですが、Enum 列挙が Java によって提供される実際の列挙であることは誰もが知っています。


それでは、なぜ Enum 列挙型を使用するのでしょうか?まず、Java 1.5 より前、列挙型がなかったときに列挙をどのように表現したかを見てみましょう。


8 つの惑星を例に挙げると、それぞれの惑星は int 値に対応します


public class PlanetWithoutEnum { 
  public static final int PLANET_MERCURY = 0; 
  public static final int PLANET_VENUS = 1; 
  public static final int PLANET_EARTH = 2; 
  public static final int PLANET_MARS = 3; 
  public static final int PLANET_JUPITER = 4; 
  public static final int PLANET_SATURN = 5; 
  public static final int PLANET_URANUS = 6; 
  public static final int PLANET_NEPTUNE = 7; 
}

これは int 列挙モードと呼ばれます。 もちろん、文字列列挙モードを使用することもできます。このアプローチは、型の安全性と使いやすさの点で劣っています。

変数 Planet が惑星を表す場合、ユーザーはこの値を、planet = 9 などの列挙値にない値に割り当てることができます。さらに、これがどの惑星であるかは神のみが知っており、私たちにはそれを計算するのは困難です。 . 惑星はいくつありますか? 惑星を横断するのは困難です。


今度は列挙型を使用して惑星を作成します。



public enum Planet { 
  MERCURY, VENUS, EARTH, MARS, JUPITER, SATURN, URANUS, NEPTUNE; 
}

上記は最も単純な列挙です。これを Planet 1.0 と呼びます。このバージョンの Planet 列挙では、Planet 型の任意の変数がコンパイラによって保証される関数が実装されました。パラメータに渡される null 以外のオブジェクトは、これら 8 つの惑星のいずれかに属している必要があります。


次に、Planet をアップグレードすると、列挙型に任意のメソッドを追加できるようになります。入門書のコードは、誰でも列挙コンストラクター、パブリック メソッド、列挙トラバーサルなどの知識ポイントを体験できます。


public enum Planet { 
  MERCURY(3.302e+23, 2.439e6),  
  VENUS(4.869e+24, 6.052e6),  
  EARTH(5.975e+24,6.378e6),  
  MARS(6.419e+23, 3.393e6),  
  JUPITER(1.899e+27, 7.149e7),  
  SATURN(5.685e+26, 6.027e7),  
  URANUS(8.683e+25, 2.556e7),  
  NEPTUNE(1.024e+26,2.477e7); 
  private final double mass; // In kilograms 
  private final double radius; // In meters 
  private final double surfaceGravity; // In m / s^2 
 
  // Universal gravitational constant in m^3 / kg s^2 
  private static final double G = 6.67300E-11; 
 
  // Constructor 
  Planet(double mass, double radius) { 
    this.mass = mass; 
    this.radius = radius; 
    surfaceGravity = G * mass / (radius * radius); 
  } 
 
  public double mass() { 
    return mass; 
  } 
 
  public double radius() { 
    return radius; 
  } 
 
  public double surfaceGravity() { 
    return surfaceGravity; 
  } 
 
  public double surfaceWeight(double mass) { 
    return mass * surfaceGravity; // F = ma 
  } 
}

//注:这里对书中的代码做了微调 
public class WeightTable { 
  public static void main(String[] args) { 
    printfWeightOnAllPlanets(8d); 
  } 
 
  public static void printfWeightOnAllPlanets(double earthWeight) { 
    double mass = earthWeight / Planet.EARTH.surfaceGravity(); 
    for (Planet p : Planet.values()) 
      System.out.printf("Weight on %s is %f%n", p, p.surfaceWeight(mass)); 
  } 
}

WeightTable を実行すると、出力結果は次のようになります:


Weight on MERCURY is 3.023254
Weight on VENUS is 7.240408
Weight on EARTH is 8.000000
Weight on MARS is 3.036832
Weight on JUPITER is 20.237436
Weight on SATURN is 8.524113
Weight on URANUS is 7.238844
Weight on NEPTUNE is 9.090108

この小さなプログラムでは、列挙型 A を返す列挙型の value() メソッドを使用します。列挙型変数のコレクション。非常に実用的です。

高度な列挙型電卓演算子列挙クラス

前のセクションの例では、列挙型クラスのパブリック メソッドを使用しました。このセクションでは、さまざまな操作を実装する方法を確認するために、電卓演算子列挙クラスを例として取り上げます。列挙オブジェクトごとに。


まず、public メソッドで switch を使用して列挙型を決定し、その後、さまざまな操作を実行する方法を簡単に考えます。コードは次のとおりです。必要なものを実装していますが、2 つの欠点があります。

まず、最後に例外をスローするか、スイッチにデフォルトを追加する必要があります。そうしないとコンパイルできません。しかし、明らかに、プログラムのブランチは例外やデフォルトに入りません。


第二に、このコードは非常に脆弱です。新しい操作タイプを追加しても、対応する処理ロジックをスイッチに追加するのを忘れた場合、新しい操作を実行するときに問題が発生します。


幸いなことに、Java 列挙型には、定数固有のメソッド実装と呼ばれる機能が用意されています。


列挙型で抽象メソッドを宣言し、各列挙定数でこのメソッドをオーバーライドするだけです。実装は次のとおりです。



public enum OperationUseSwitch { 
  PLUS, MINUS, TIMES, pIDE; 
 
  double apply(double x, double y) { 
    switch (this) { 
      case PLUS: 
        return x + y; 
      case MINUS: 
        return x + y; 
      case TIMES: 
        return x + y; 
      case pIDE: 
        return x + y; 
    } 
    // 如果this不属于上面四种操作符,抛出异常 
    throw new AssertionError("Unknown operation: " + this); 
  } 
}

この方法では、新しい操作は発生しません。 added シンボルの後に対応する処理ロジックを追加するのを忘れた場合、コンパイラは apply メソッドをオーバーライドする必要があることを要求します。


ただし、この定数固有のメソッド実装には欠点があります。それは、列挙定数間でコードを共有することが難しいということです。


週を列挙してみましょう


如果还是使用 特定于常量的方法实现,写出来的代码可能就是这样的:


public enum DayUseAbstractMethod { 
  MONDAY { 
    @Override 
    void apply() { 
      dealWithWeekDays();//伪代码 
    } 
  }, 
  TUESDAY { 
    @Override 
    void apply() { 
      dealWithWeekDays();//伪代码 
    } 
  }, 
  WEDNESDAY { 
    @Override 
    void apply() { 
      dealWithWeekDays();//伪代码 
    } 
  }, 
  THURSDAY { 
    @Override 
    void apply() { 
      dealWithWeekDays();//伪代码 
    } 
  }, 
  FRIDAY { 
    @Override 
    void apply() { 
      dealWithWeekDays();//伪代码 
    } 
  }, 
  SATURDAY { 
    @Override 
    void apply() { 
      dealWithWeekEnds();//伪代码 
    } 
  }, 
  SUNDAY { 
    @Override 
    void apply() { 
      dealWithWeekEnds();//伪代码 
    } 
  }; 
 
  abstract void apply(); 
}

很明显,我们这段代码里面有相当多的重复代码。

那么要怎么优化呢,我们不妨这样想,星期一星期二等等是一种枚举,那么工作日和休息日,难道不也是一种枚举吗,我们能不能给Day的构造函数传入一个工作日休息日的DayType枚举呢?这也就是书中给出的一种叫策略枚举 的方法,代码如下:


public enum Day { 
  MONDAY(DayType.WEEKDAY), TUESDAY(DayType.WEEKDAY), WEDNESDAY( 
      DayType.WEEKDAY), THURSDAY(DayType.WEEKDAY), FRIDAY(DayType.WEEKDAY), SATURDAY( 
      DayType.WEEKDAY), SUNDAY(DayType.WEEKDAY); 
  private final DayType dayType; 
 
  Day(DayType daytype) { 
    this.dayType = daytype; 
  } 
 
  void apply() { 
    dayType.apply(); 
  } 
 
  private enum DayType { 
    WEEKDAY { 
      @Override 
      void apply() { 
        System.out.println("hi, weekday"); 
      } 
    }, 
    WEEKEND { 
      @Override 
      void apply() { 
        System.out.println("hi, weekend"); 
      } 
    }; 
    abstract void apply(); 
  } 
}

通过策略枚举的方式,我们把Day的处理逻辑委托给了DayType,个中奥妙,读者可以细细体会。

枚举集合   EnumSet的使用

EnumSet提供了非常方便的方法来创建枚举集合,下面这段代码,感受一下


public class Text { 
  public enum Style { 
    BOLD, ITALIC, UNDERLINE, STRIKETHROUGH 
  } 
 
  // Any Set could be passed in, but EnumSet is clearly best 
  public void applyStyles(Set<Style> styles) { 
    // Body goes here 
    for(Style style : styles){ 
      System.out.println(style); 
    } 
  } 
 
  // Sample use 
  public static void main(String[] args) { 
    Text text = new Text(); 
    text.applyStyles(EnumSet.of(Style.BOLD, Style.ITALIC)); 
  } 
}

这个例子里,我们使用了EnumSet.of方法,轻松创建了枚举集合。

枚举Map   EnumMap的使用

假设对于香草(Herb),有一个枚举属性Type(一年生、多年生、两年生)

Herb:


public class Herb { 
  public enum Type { ANNUAL, PERENNIAL, BIENNIAL } 
 
  private final String name; 
  private final Type type; 
   
  Herb(String name, Type type) { 
    this.name = name; 
    this.type = type; 
  } 
 
  @Override public String toString() { 
    return name; 
  } 
}

现在,假设我们有一个Herb数组,我们需要对这个Herb数组按照Type进行分类存放。

所以接下来,我们需要创建一个Map,value肯定是Herb的集合了,那么用什么作为key呢?

有的人会使用枚举类型的ordinal()方法,这个函数返回int类型,表示枚举遍历在枚举类里的位置,这样做,缺点很明显,由于你的key的类型是int,不能保证传入的int一定能和枚举类里的变量对应上。

所以,在key的选择上,毫无疑问,只能使用枚举类型,也即Herb.Type。

最后还有一个问题,要使用什么Map? Java为枚举类型专门提供了一种Map,叫EnumMap,相比较与其他Map,这种Map在处理枚举类型上更快,有兴趣的同学可以研究一下这个map的内部实现。

下面让我们看看怎么使用EnumMap:


public static void main(String[] args) { 
  Herb[] garden = { new Herb("Basil", Type.ANNUAL), 
      new Herb("Carroway", Type.BIENNIAL), 
      new Herb("Dill", Type.ANNUAL), 
      new Herb("Lavendar", Type.PERENNIAL), 
      new Herb("Parsley", Type.BIENNIAL), 
      new Herb("Rosemary", Type.PERENNIAL) }; 
 
  // Using an EnumMap to associate data with an enum - Page 162 
  Map<Herb.Type, Set<Herb>> herbsByType = new EnumMap<Herb.Type, Set<Herb>>( 
      Herb.Type.class); 
  for (Herb.Type t : Herb.Type.values()) 
    herbsByType.put(t, new HashSet<Herb>()); 
  for (Herb h : garden) 
    herbsByType.get(h.type).add(h); 
  System.out.println(herbsByType); 
 
}

总结

和int枚举相比,Enum枚举的在类型安全和使用便利上的优势是不言而喻的。
Enum为枚举提供了丰富的功能,如文章中提到的特定于常量的方法实现和策略枚举。
EnumSet和EnumMap是两个为枚举而设计的集合,在实际开发中,用到枚举集合时,请优先考虑这两个。

以上がJava 列挙型の使用方法の詳細な説明の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

声明:
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。