Effective Java (枚举)

系统 2149 0

 

三十、用enum代替int常量:

      枚举类型是指由一组固定的常量组成合法值的类型,该特征是在Java 1.5 中开始被支持的,之前的Java代码都是通过“公有静态常量域字段”的方法来简单模拟枚举的,如:
      public static final int APPLE_FUJI = 0;
      public static final int APPLE_PIPPIN = 1;
      public static final int APPLE_GRANNY_SMITH = 2;
      ... ...
      public static final int ORANGE_NAVEL = 0;
      public static final int ORANGE_TEMPLE = 1;
      public static final int ORANGE_BLOOD = 2;
      这样的写法是比较脆弱的。首先是没有提供相应的类型安全性,如两个逻辑上不相关的常量值之间可以进行比较或运算(APPLE_FUJI - ORANGE_TEMPLE),再有就是常量int是编译时常量,被直接编译到使用他们的客户端中。如果与该常量关联的int发生了变化,客户端就必须重新编译。如果没有重新编译,程序还是可以执行,但是他们的行为将不确定。
      下面我们来看一下Java 1.5 中提供的枚举的声明方式:
      public enum Apple { FUJI, PIPPIN, GRANNY_SMITH }
      public enum Orange { NAVEL, TEMPLE, BLOOD }
      和“公有静态常量域字段”不同的是,如果函数的参数是枚举类型,如Apple,那么他的实际值只能来自于该枚举所声明的枚举值,即FUJI, PIPPIN, GRANNY_SMITH。如果试图将Apple和Orange中的枚举值进行比较,将会导致编译错误。
      和C/C++中提供的枚举不同的是,Java中允许在枚举中添加任意的方法和域,并实现任意的接口。下面先给出一个带有域方法和域字段的枚举声明:

        
           1
        
        
          public
        
        
          enum
        
         Planet {

        
           2
        
                 MERCURY(3.302e+23,2.439e6),

        
           3
        
                 VENUS(4.869e+24,6.052e6),

        
           4
        
                 EARTH(5.975e+24,6.378e6),

        
           5
        
                 MARS(6.419e+23,3.393e6),

        
           6
        
                 JUPITER(1.899e+27,7.149e7),

        
           7
        
                 SATURN(5.685e+26,6.027e7),

        
           8
        
                 URANUS(8.683e+25,2.556e7),

        
           9
        
                 NEPTUNE(1.024e+26,2.477e7);

        
          10
        
        
          private
        
        
          final
        
        
          double
        
         mass;   
        
          //
        
        
          千克
        
        
        
        
          11
        
        
          private
        
        
          final
        
        
          double
        
         radius; 
        
          //
        
        
        
        
        
          12
        
        
          private
        
        
          final
        
        
          double
        
         surfaceGravity;

        
          13
        
        
          private
        
        
          static
        
        
          final
        
        
          double
        
         G = 6.67300E-11;

        
          14
        
                 Planet(
        
          double
        
         mass,
        
          double
        
         radius) {

        
          15
        
        
          this
        
        .mass = mass;

        
          16
        
        
          this
        
        .radius = radius;

        
          17
        
                     surfaceGravity = G * mass / (radius * radius);

        
          18
        
                 }

        
          19
        
        
          public
        
        
          double
        
         mass() { 

        
          20
        
        
          return
        
         mass;

        
          21
        
                 }

        
          22
        
        
          public
        
        
          double
        
         radius() {

        
          23
        
        
          return
        
         radius;

        
          24
        
                 }

        
          25
        
        
          public
        
        
          double
        
         surfaceGravity() {

        
          26
        
        
          return
        
         surfaceGravity;

        
          27
        
                 }

        
          28
        
        
          public
        
        
          double
        
         surfaceWeight(
        
          double
        
         mass) {

        
          29
        
        
          return
        
         mass * surfaceGravity;

        
          30
        
                 }

        
          31
        
             }
      

      在上面的枚举示例代码中,已经将数据和枚举常量关联起来了,因此需要声明实例域字段,同时编写一个带有数据并将数据保存在域中的构造器。枚举天生就是不可变的,因此所有的域字段都应该为final的。下面看一下该枚举的应用示例:

        
           1
        
        
          public
        
        
          class
        
         WeightTable {

        
           2
        
        
          public
        
        
          static
        
        
          void
        
         main(String[] args) {

        
           3
        
        
          double
        
         earthWeight = Double.parseDouble(args[0]);

        
           4
        
        
          double
        
         mass = earthWeight/Planet.EARTH.surfaceGravity();

        
           5
        
        
          for
        
         (Planet p : Planet.values())

        
           6
        
                         System.out.printf("Weight on %s is %f%n",p,p.surfaceWeight(mass));

        
           7
        
                 }

        
           8
        
             }

        
           9
        
        
          //
        
        
           Weight on MERCURY is 66.133672

        
        
          10
        
        
              //
        
        
           Weight on VENUS is 158.383926

        
        
          11
        
        
              //
        
        
           Weight on EARTH is 175.000000

        
        
          12
        
        
          //
        
        
           Weight on MARS is 66.430699

        
        
          13
        
        
              //
        
        
           Weight on JUPITER is 442.693902

        
        
          14
        
        
              //
        
        
           Weight on SATURN is 186.464970

        
        
          15
        
        
              //
        
        
           Weight on URANUS is 158.349709

        
        
          16
        
        
              //
        
        
           Weight on NEPTUNE is 198.846116
        
      

      枚举的静态方法values()将按照声明顺序返回他的值数组。枚举的toString方法返回每个枚举值的声明名称。
      在实际的编程中,我们常常需要针对不同的枚举常量提供不同的数据操作行为,见如下代码:

        
           1
        
        
          public
        
        
          enum
        
         Operation {

        
           2
        
                 PLUS,MINUS,TIMES,DIVIDE;

        
           3
        
        
          double
        
         apply(
        
          double
        
         x,
        
          double
        
         y) {

        
           4
        
        
          switch
        
         (
        
          this
        
        ) {

        
           5
        
        
          case
        
         PLUS: 
        
          return
        
         x + y;

        
           6
        
        
          case
        
         MINUS: 
        
          return
        
         x - y;

        
           7
        
        
          case
        
         TIMES: 
        
          return
        
         x * y;

        
           8
        
        
          case
        
         DIVIDE: 
        
          return
        
         x / y;

        
           9
        
                     }

        
          10
        
        
          throw
        
        
          new
        
         AssertionError("Unknown op: " + 
        
          this
        
        );

        
          11
        
                 }

        
          12
        
             }
      

      上面的代码已经表达出这种根据不同的枚举值,执行不同的操作。但是上面的代码在设计方面确实存在一定的缺陷,或者说漏洞,如果我们新增枚举值的时候,所有和apply类似的域函数,都需要进行相应的修改,如有遗漏将会导致异常的抛出。幸运的是,Java的枚举提供了一种更好的方法可以将不同的行为与每个枚举常量关联起来:在枚举类型中声明一个抽象的apply方法,并在特定于常量的类主体中,用具体的方法覆盖每个常量的抽象apply方法,如:

        
          1
        
        
          public
        
        
          enum
        
         Operation {

        
          2
        
                 PLUS { 
        
          double
        
         apply(
        
          double
        
         x,
        
          double
        
         y) { 
        
          return
        
         x + y;} },

        
          3
        
                 MINUS { 
        
          double
        
         apply(
        
          double
        
         x,
        
          double
        
         y) { 
        
          return
        
         x - y;} },

        
          4
        
                 TIMES { 
        
          double
        
         apply(
        
          double
        
         x,
        
          double
        
         y) { 
        
          return
        
         x * y;} },

        
          5
        
                 DIVIDE { 
        
          double
        
         apply(
        
          double
        
         x,
        
          double
        
         y) { 
        
          return
        
         x / y;} };

        
          6
        
        
          abstract
        
        
          double
        
         apply(
        
          double
        
         x, 
        
          double
        
         y);

        
          7
        
             }
      

      这样在添加新枚举常量时就不会轻易忘记提供相应的apply方法了。我们在进一步看一下如何将枚举常量和特定的数据进行关联,见如下代码:

        
           1
        
        
          public
        
        
          enum
        
         Operation {

        
           2
        
                 PLUS("+") { 
        
          double
        
         apply(
        
          double
        
         x,
        
          double
        
         y) { 
        
          return
        
         x + y;} },

        
           3
        
                 MINUS("-") { 
        
          double
        
         apply(
        
          double
        
         x,
        
          double
        
         y) { 
        
          return
        
         x - y;} },

        
           4
        
                 TIMES("*") { 
        
          double
        
         apply(
        
          double
        
         x,
        
          double
        
         y) { 
        
          return
        
         x * y;} },

        
           5
        
                 DIVIDE("/") { 
        
          double
        
         apply(
        
          double
        
         x,
        
          double
        
         y) { 
        
          return
        
         x / y;} };

        
           6
        
        
          private
        
        
          final
        
         String symbol;

        
           7
        
                 Operation(String symbol) {

        
           8
        
        
          this
        
        .symbol = symbol;

        
           9
        
                 }

        
          10
        
                 @Override 
        
          public
        
         String toString() {

        
          11
        
        
          return
        
         symbol;

        
          12
        
                 }

        
          13
        
        
          abstract
        
        
          double
        
         apply(
        
          double
        
         x, 
        
          double
        
         y);

        
          14
        
             }
      

      下面给出以上代码的应用示例:

        
           1
        
        
          public
        
        
          static
        
        
          void
        
         main(String[] args) {

        
           2
        
        
          double
        
         x = Double.parseDouble(args[0]);

        
           3
        
        
          double
        
         y = Double.parseDouble(args[1]);

        
           4
        
        
          for
        
         (Operation op : Operation.values())

        
           5
        
                     System.out.printf("%f %s %f = %f%n",x,op,y,op.apply(x,y));

        
           6
        
                 }

        
           7
        
             }

        
           8
        
        
          //
        
        
           2.000000 + 4.000000 = 6.000000

        
        
           9
        
        
              //
        
        
           2.000000 - 4.000000 = -2.000000

        
        
          10
        
        
              //
        
        
           2.000000 * 4.000000 = 8.000000

        
        
          11
        
        
          //
        
        
           2.000000 / 4.000000 = 0.500000
        
      

      没有类型有一个自动产生的valueOf(String)方法,他将常量的名字转变为枚举常量本身,如果在枚举中覆盖了toString方法(如上例),就需要考虑编写一个fromString方法,将定制的字符串表示法变回相应的枚举,见如下代码:

        
           1
        
        
          public
        
        
          enum
        
         Operation {

        
           2
        
                 PLUS("+") { 
        
          double
        
         apply(
        
          double
        
         x,
        
          double
        
         y) { 
        
          return
        
         x + y;} },

        
           3
        
                 MINUS("-") { 
        
          double
        
         apply(
        
          double
        
         x,
        
          double
        
         y) { 
        
          return
        
         x - y;} },

        
           4
        
                 TIMES("*") { 
        
          double
        
         apply(
        
          double
        
         x,
        
          double
        
         y) { 
        
          return
        
         x * y;} },

        
           5
        
                 DIVIDE("/") { 
        
          double
        
         apply(
        
          double
        
         x,
        
          double
        
         y) { 
        
          return
        
         x / y;} };

        
           6
        
        
          private
        
        
          final
        
         String symbol;

        
           7
        
                 Operation(String symbol) {

        
           8
        
        
          this
        
        .symbol = symbol;

        
           9
        
                 }

        
          10
        
                 @Override 
        
          public
        
         String toString() {

        
          11
        
        
          return
        
         symbol;

        
          12
        
                 }

        
          13
        
        
          abstract
        
        
          double
        
         apply(
        
          double
        
         x, 
        
          double
        
         y);

        
          14
        
        
          //
        
        
          新增代码
        
        
        
        
          15
        
        
          private
        
        
          static
        
        
          final
        
         Map<String,Operation> stringToEnum = 
        
          new
        
         HashMap<String,Operation>();

        
          16
        
        
          static
        
         {

        
          17
        
        
          for
        
         (Operation op : values())

        
          18
        
                         stringToEnum.put(op.toString(),op);

        
          19
        
                 }

        
          20
        
        
          public
        
        
          static
        
         Operation fromString(String symbol) {

        
          21
        
        
          return
        
         stringToEnum.get(symbol);

        
          22
        
                 }

        
          23
        
             }
      

      需要注意的是,我们无法在枚举常量构造的时候将自身放入到Map中,这样会导致编译错误。与此同时,枚举构造器不可以访问枚举的静态域,除了编译时的常量域之外。
    
三十一、用实例域代替序数:

      Java中的枚举提供了ordinal()方法,他返回每个枚举常量在类型中的数字位置,如:

        
          1
        
        
          public
        
        
          enum
        
         Color {

        
          2
        
                 WHITE,RED,GREEN,BLUE,ORANGE,BLACK;

        
          3
        
        
          public
        
        
          int
        
         indexOfColor() {

        
          4
        
        
          return
        
         ordinal() + 1;

        
          5
        
                 }

        
          6
        
             }
      

      上面的枚举中提供了一个获取颜色索引的方法(indexOfColor),该方法将返回颜色值在枚举类型中的声明位置,如果我们的外部程序依赖了该顺序值,那么这将会是非常危险和脆弱的,因为一旦这些枚举值的位置出现变化,或者在已有枚举值的中间加入新的枚举值时,都将导致该索引值的变化。该条目推荐使用实例域的方式来代替枚举提供的序数值,见如下修改后的代码:

        
           1
        
        
          public
        
        
          enum
        
         Color {

        
           2
        
                 WHITE(1),RED(2),GREEN(3),ORANGE(4),BLACK(5);

        
           3
        
        
          private
        
        
          final
        
        
          int
        
         indexOfColor;

        
           4
        
                 Color(
        
          int
        
         index) {

        
           5
        
        
          this
        
        .indexOfColor = index;

        
           6
        
                 }

        
           7
        
        
          public
        
        
          int
        
         indexOfColor() {

        
           8
        
        
          return
        
         indexOfColor;

        
           9
        
                 }

        
          10
        
             }
      

      Enum规范中谈到ordinal时这么写道:“大多数程序员都不需要这个方法。它是设计成用于像EnumSet和EnumMap这种基于枚举的通用数据结构的。”除非你在编写的是这种数据结构,否则最好避免使用ordinal()方法。
    
三十二、用EnumSet代替位域:

      下面的代码给出了位域的实现方式:

        
          1
        
        
          public
        
        
          class
        
         Text {

        
          2
        
        
          public
        
        
          static
        
        
          final
        
        
          int
        
         STYLE_BOLD = 1 << 0;

        
          3
        
        
          public
        
        
          static
        
        
          final
        
        
          int
        
         STYLE_ITALIC = 1 << 1;

        
          4
        
        
          public
        
        
          static
        
        
          final
        
        
          int
        
         STYLE_UNDERLINE = 1 << 2;

        
          5
        
        
          public
        
        
          static
        
        
          final
        
        
          int
        
         STYLE_STRIKETHROUGH = 1 << 3;

        
          6
        
        
          public
        
        
          void
        
         applyStyles(
        
          int
        
         styles) { ... }

        
          7
        
             }
      

      这种表示法让你用OR位运算将几个常量合并到一个集合中,使用方式如下:
      text.applyStyles(Text.STYLE_BOLD | Text.STYLE_ITALIC);
      Java中提供了EnumSet类,该类继承自Set接口,同时也提供了丰富的功能,类型安全性,以及可以从任何其他Set实现中得到的互用性。但是在内部具体实现上,没有EnumSet内容都表示为位矢量。如果底层的枚举类型有64个或者更少的元素,整个EnumSet就用单个long来表示,因此他的性能也是可以比肩位域的。与此同时,他提供了大量的操作方法,其实现也是基于位操作的,但是相比于手工位操作,由于EnumSet替我们承担了这部分的开发,从而也避免了一些容易出现的低级错误,代码的美观程度也会有所提升,见如下修改的代码:

        
          1
        
        
          public
        
        
          class
        
         Text {

        
          2
        
        
          public
        
        
          enum
        
         Style { BOLD, ITALIC, UNDERLINE, STRIKETHROUGH }

        
          3
        
        
          public
        
        
          void
        
         applyStyles(Set<Style> styles) { ... }

        
          4
        
             }
      

      新的使用方式如下:
      text.applyStyles(EnumSet.of(Style.BOLD,Style.ITALIC));
      需要说明的是,EnumSet提供了丰富的静态工厂来轻松创建集合。

 

三十三、用EnumMap代替序数索引:

      前面的条目已经给出了尽量不要直接使用枚举的ordinal()方法的原因,这里就不在做过多的赘述了。在这个条目中,只是再一次给出了ordinal()的典型用法,与此同时也再一次提供了一个更为合理的解决方案用于替换ordinal()方法,从而进一步证明我们在编码过程中应该尽可能减少对枚举中ordinal()函数的依赖。见如下代码:

        
           1
        
        
          public
        
        
          class
        
         Herb {

        
           2
        
        
          public
        
        
          enum
        
         Type { ANNUAL, PERENNIAL, BIENNIAL }

        
           3
        
        
          private
        
        
          final
        
         String name;

        
           4
        
        
          private
        
        
          final
        
         Type type;

        
           5
        
                 Herb(String name, Type type) {

        
           6
        
        
          this
        
        .name = name;

        
           7
        
        
          this
        
        .type = type;

        
           8
        
                 }

        
           9
        
                 @Override 
        
          public
        
         String toString() {

        
          10
        
        
          return
        
         name;

        
          11
        
                 }

        
          12
        
             }

        
          13
        
        
          public
        
        
          static
        
        
          void
        
         main(String[] args) {

        
          14
        
                 Herb[] garden = getAllHerbsFromGarden();

        
          15
        
                 Set<Herb> herbsByType = (Set<Herb>[])
        
          new
        
         Set[Herb.Type.values().length];

        
          16
        
        
          for
        
         (
        
          int
        
         i = 0; i < herbsByType.length; ++i) {

        
          17
        
                     herbsByType[i] = 
        
          new
        
         HashSet<Herb>();

        
          18
        
                 }

        
          19
        
        
          for
        
         (Herb h : garden) {

        
          20
        
                     herbsByType[h.type.ordinal()].add(h);

        
          21
        
                 }

        
          22
        
        
          for
        
         (
        
          int
        
         i = 0; i < herbsByType.length; ++i) {

        
          23
        
                     System.out.printf("%s: %s%n",Herb.Type.values()[i],herbByType[i]);

        
          24
        
                 }

        
          25
        
             }
      

      这里我需要简单描述一下上面代码的应用场景:在一个花园里面有很多的植物,它们被分成3类,分别是一年生(ANNUAL)、多年生(PERENNIAL)和两年生(BIENNIAL),正好对应着Herb.Type中的枚举值。现在我们需要做的是遍历花园中的每一个植物,并将这些植物分为3类,最后再将分类后的植物分类打印出来。下面将提供另外一种方法,即通过EnumMap来实现和上面代码相同的逻辑:

        
           1
        
        
          public
        
        
          static
        
        
          void
        
         main(String[] args) {

        
           2
        
                 Herb[] garden = getAllHerbsFromGarden();

        
           3
        
                 Map<Herb.Type,Set<Herb>> herbsByType = 

        
           4
        
        
          new
        
         EnumMap<Herb.Type,Set<Herb>>(Herb.Type.
        
          class
        
        );

        
           5
        
        
          for
        
         (Herb.Type t : Herb.Type.values()) {

        
           6
        
                     herbssByType.put(t,
        
          new
        
         HashSet<Herb>());

        
           7
        
                 }

        
           8
        
        
          for
        
         (Herb h : garden) {

        
           9
        
                     herbsByType.get(h.type).add(h);

        
          10
        
                 }

        
          11
        
                 System.out.println(herbsByType);

        
          12
        
             }
      

      和之前的代码相比,这段代码更加清晰,也更加安全,运行效率方面也是可以与使用ordinal()的方式想媲美的。

三十四、用接口模拟可伸缩的枚举:

      枚举是无法被扩展(extends)的,这是一个无法回避的事实。如果我们的操作中存在一些基础操作,如计算器中的基本运算类型(加减乘除)。然而对于有些用户来讲,他们也可以使用更高级的操作,如求幂和求余等。针对这样的需求,该条目提出了一种非常巧妙的设计方案,即利用枚举可以实现接口这一事实,我们将API的参数定义为该接口,而不是具体的枚举类型,见如下代码:

        
           1
        
        
          public
        
        
          interface
        
         Operation {

        
           2
        
        
          double
        
         apply(
        
          double
        
         x,
        
          double
        
         y);

        
           3
        
             }

        
           4
        
        
          public
        
        
          enum
        
         BasicOperation 
        
          implements
        
         Operation {

        
           5
        
                 PLUS("+") {

        
           6
        
        
          public
        
        
          double
        
         apply(
        
          double
        
         x,
        
          double
        
         y) { 
        
          return
        
         x + y; }

        
           7
        
                 },

        
           8
        
                 MINUS("-") {

        
           9
        
        
          public
        
        
          double
        
         apply(
        
          double
        
         x,
        
          double
        
         y) { 
        
          return
        
         x - y; }

        
          10
        
                 },

        
          11
        
                 TIMES("*") {

        
          12
        
        
          public
        
        
          double
        
         apply(
        
          double
        
         x,
        
          double
        
         y) { 
        
          return
        
         x * y; }

        
          13
        
                 },

        
          14
        
                 DIVIDE("/") {

        
          15
        
        
          public
        
        
          double
        
         apply(
        
          double
        
         x,
        
          double
        
         y) { 
        
          return
        
         x / y; }

        
          16
        
                 };

        
          17
        
        
          private
        
        
          final
        
         String symbol;

        
          18
        
                 BasicOperation(String symbol) {

        
          19
        
        
          this
        
        .symbol = symbol;

        
          20
        
                 }

        
          21
        
                 @Override 
        
          public
        
         String toString() {

        
          22
        
        
          return
        
         symbol;

        
          23
        
                 }

        
          24
        
             }

        
          25
        
        
          public
        
        
          enum
        
         ExtendedOperation 
        
          implements
        
         Operation {

        
          26
        
                 EXP("^") {

        
          27
        
        
          public
        
        
          double
        
         apply(
        
          double
        
         x,
        
          double
        
         y) {

        
          28
        
        
          return
        
         Math.pow(x,y);

        
          29
        
                     }

        
          30
        
                 },

        
          31
        
                 REMAINDER("%") {

        
          32
        
        
          public
        
        
          double
        
         apply(
        
          double
        
         x,
        
          double
        
         y) {

        
          33
        
        
          return
        
         x % y;

        
          34
        
                     }

        
          35
        
                 };

        
          36
        
        
          private
        
        
          final
        
         String symbol;

        
          37
        
                 ExtendedOperation(String symbol) {

        
          38
        
        
          this
        
        .symbol = symbol;

        
          39
        
                 }

        
          40
        
                 @Override 
        
          public
        
         String toString() {

        
          41
        
        
          return
        
         symbol;

        
          42
        
                 }

        
          43
        
             }
      

      通过以上的代码可以看出,在任何可以使用BasicOperation的地方,我们也同样可以使用ExtendedOperation,只要我们的API是基于Operation接口的,而非BasicOperation或ExtendedOperation。下面为以上代码的应用示例:

        
           1
        
        
          public
        
        
          static
        
        
          void
        
         main(String[] args) {

        
           2
        
        
          double
        
         x = Double.parseDouble(args[0]);

        
           3
        
        
          double
        
         y = Double.parseDouble(args[1]);

        
           4
        
                 test(ExtendedOperation.
        
          class
        
        ,x,y);

        
           5
        
             }

        
           6
        
        
          private
        
        
          static
        
         <T 
        
          extends
        
         Enum<T> & Operation> 
        
          void
        
         test(

        
           7
        
                 Class<T> opSet,
        
          double
        
         x,
        
          double
        
         y) {

        
           8
        
        
          for
        
         (Operation op : opSet.getEnumConstants()) {

        
           9
        
                     System.out.printf("%f %s %f = %f%n",x,op,y,op.apply(x,y));

        
          10
        
                 }

        
          11
        
             }
      

      注意,参数Class<T> opSet将推演出类型参数的实际类型,即上例中的ExtendedOperation。与此同时,test函数的参数类型限定确保了类型参数既是枚举类型又是Operation的实现类,这正是遍历元素和执行每个元素相关联的操作所必须的。

 

Effective Java (枚举)


更多文章、技术交流、商务合作、联系博主

微信扫码或搜索:z360901061

微信扫一扫加我为好友

QQ号联系: 360901061

您的支持是博主写作最大的动力,如果您喜欢我的文章,感觉我的文章对您有帮助,请用微信扫描下面二维码支持博主2元、5元、10元、20元等您想捐的金额吧,狠狠点击下面给点支持吧,站长非常感激您!手机微信长按不能支付解决办法:请将微信支付二维码保存到相册,切换到微信,然后点击微信右上角扫一扫功能,选择支付二维码完成支付。

【本文对您有帮助就好】

您的支持是博主写作最大的动力,如果您喜欢我的文章,感觉我的文章对您有帮助,请用微信扫描上面二维码支持博主2元、5元、10元、自定义金额等您想捐的金额吧,站长会非常 感谢您的哦!!!

发表我的评论
最新评论 总共0条评论