enum_learn

本文深入探讨Java枚举的特性和用途,包括类型安全、自定义方法、实例化过程及序列化处理等内容。通过实例展示了如何定义枚举类型,并提供枚举在实际应用中的多种场景。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Java 枚举

Enum

参考《Effective Java》

Java1.5之后

  1. 枚举提够了编译时的类型安全
  2. 包含同名常量的多个枚举类型可以在一个系统中和平共处,因为每个类型都有自己的命名空间。
  3. 枚举类型允许添加任意的方法,并且实现任意的接口

Sample:

每个枚举常量后面括号中的数值,就是传递给构造器的参数。

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;
    private final double radius;
    private final double surfaceGravity;

    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
    }
}

  下面是一个简短的程序,根据某个物体在地球上的重量,打印出一张很棒的表格,显示出该物体在所有8颗行星上的重量。

  Planet就像所有的枚举一样,它有一个静态的values方法,按照声明顺序返回它的值数组
toString方法返回每一个枚举值的声明名称。

public class WeightTable {
    /**
     * @param args
     */
    public static void main(String[] args) {
        double earthWeight = Double.parseDouble("175");
        double mass = earthWeight / Planet.EARTH.surfaceGravity();
        for(Planet p : Planet.values()) {
            System.out.printf("Weight on %s is %f%n", p, p.surfaceWeight(mass));
        }
    }
}

输出结果为:

Weight on MERCURY is 66.133672
Weight on VENUS is 158.383926
Weight on EARTH is 175.000000
Weight on MARS is 66.430699
Weight on JUPITER is 442.693902
Weight on SATURN is 186.464970
Weight on URANUS is 158.349709
Weight on NEPTUNE is 198.846116

  与枚举常量相关的有些行为,可能只需要用在定义了枚举的类或者包中。这种行为最好被实现成私有的(private)或者包级私有(default)的方法。于是,每个枚举常量都带有一组隐蔽的行为,这使得包含枚举的类或者包在遇到这种常量时都可以做出适当的反应。就像其他的类一样,除非迫不得已要将枚举方法导出至它的客户端,否则都应该将它声明为私有的,如有必要,则声明为包级私有的。

  如果一个枚举具有普遍适用性,它就应该称为一个顶层类(top-level class);如果它只是被用在一个特定的顶层类中,它就应该成为该顶层类的一个成员类。

  可以在枚举类型中声明一个抽象的apply方法,并在特定于常量的类主体中,用具体的方法覆盖每个常量的抽象apply方法。这种方法被称作特定于常量的方法实现。

public enum Operation {

    PLUS("+") { double apply(double x, double y) { return x + y; } },
    MINUS("-") {double apply(double x, double y) { return x - y; }},
    TIMES("*") {double apply(double x, double y) { return x * y; }},
    DIVIDE("/") {double apply(double x, double y) { return x / y; }};

    private final String symbol;

    Operation(String symbol) {
        this.symbol = symbol;
    }
    @Override
    public String toString() {
        return symbol;
    }
    abstract double apply(double x, double y);
}

  永远不要根据枚举的序数导出与它关联的值,而是要将它保存在一个实例域中:

public enum Ensemble {
    SOLO(1),
    DUET(2),
    TRIO(3),
    QUARTET(4),
    QUINTET(5),
    SEXTET(6),
    SEPTET(7),
    OCTET(8),
    DOUBLE_QUARTET(8),
    NONET(9),
    DECTET(10),
    TRIPLE_QUARTET(12);

    private final int numberOfMusicians;
    Ensemble(int size) {
        this.numberOfMusicians = size;
    }
    public int numberOfMusicians() {
        return numberOfMusicians;
    }
}
枚举自己处理序列化

使用一个包含单个元素的枚举类型,可以用作单例模式:

public enum SingletonElvis {
    INTENCE(123);
    private SingletonElvis(int intValue) {
        this.intValue = intValue;
    }
    private final int intValue;
    public void testMethod() {
        System.out.println("intValue: " + intValue);
    }
}

public class SingletonEnumTest {
    public static void main(String[] args) {
        SingletonElvis.INTENCE.testMethod();
    }
}

枚举单例,JVM对序列化有保证

创建枚举默认就是线程安全

参考:
http://www.importnew.com/6461.html

<think>好的,我现在需要解决用户遇到的编译错误:将函数指针类型`void (*)(void *, enum scan_balance *)`传递给参数类型为`void (*)(void *, char *)`导致的不兼容问题。这个问题通常是由于函数指针的参数类型不匹配引起的。我需要仔细分析可能的原因,并找出解决方法。 首先,用户提供的错误信息明确指出,函数指针的参数类型不一致。函数期望的是`char *`类型的第二个参数,而实际传递的是`enum scan_balance *`类型。这可能发生在回调函数或函数指针作为参数传递的时候。例如,当调用某个库函数或API时,用户提供的回调函数签名与库期望的不一致。 接下来,我需要考虑几种可能的解决方案。首先,检查函数指针的定义和使用是否一致。用户可能在定义回调函数时使用了错误的参数类型,或者在调用时传入了不匹配的函数指针。例如,库函数可能要求一个接受`char *`作为第二个参数的函数指针,但用户定义了一个使用`enum scan_balance *`的函数,导致类型不匹配。 解决方案一:修改回调函数的参数类型。如果用户有权限修改回调函数的定义,可以尝试将第二个参数改为`char *`,然后在函数内部进行类型转换。例如: ```c void callback(void *arg1, char *arg2) { enum scan_balance *balance = (enum scan_balance *)arg2; // 使用balance进行操作 } ``` 这样,当传递该回调函数时,参数类型就匹配了。不过,这种方法需要确保在调用时传入的`arg2`确实是`enum scan_balance *`类型的指针,转换为`char *`是安全的。 解决方案二:调整函数指针的声明。如果用户能够修改接受函数指针的函数的参数类型,可以将其更改为匹配实际的函数指针类型。例如,将参数类型从`void (*)(void *, char *)`改为`void (*)(void *, enum scan_balance *)`。但这种方法可能不适用,特别是当该函数是第三方库的一部分时,用户无法修改其声明。 解决方案三:使用类型转换强制匹配。在传递函数指针时,显式地进行类型转换。例如: ```c // 假设原函数需要这样的函数指针类型 void library_function(void (*callback)(void *, char *)); // 用户定义的函数 void my_callback(void *arg1, enum scan_balance *arg2); // 调用时进行强制转换 library_function((void (*)(void *, char *))my_callback); ``` 但这种方法存在风险,因为强制转换可能掩盖类型不匹配的问题,导致未定义行为,尤其是在不同指针类型的大小或表示方式不同的平台上。因此,这种方法需要谨慎使用,并确保实际使用时参数的类型确实兼容。 接下来,我需要考虑用户提供的引用内容是否对解决问题有帮助。引用[1]提到了变长整数打包和函数`PutLengthPrefixedSlice`及`GetLengthPrefixedSlice`,这可能涉及数据序列化和指针操作,但暂时看不出直接关联。引用[2]提到修复枚举类型在委托中的使用,这可能与枚举类型在函数指针中的使用相关。例如,如果`enum scan_balance`在某个头文件中定义,而函数指针的声明可能未能正确识别该枚举类型,导致类型不匹配。这种情况下,确保枚举类型的正确定义和头文件包含顺序可能有助于解决问题。引用[3]涉及图形驱动的恢复函数,可能与函数指针的使用场景不同,但提到平台特定的实现,可能提示需要考虑平台兼容性,但当前问题更可能是类型不匹配而非平台问题。 综合上述分析,最可行的解决方案是修改回调函数的参数类型并进行类型转换,或者调整函数指针的声明。如果用户无法修改库函数的声明,那么解决方案一可能是最直接的。同时,需要检查`enum scan_balance`的定义是否正确定义,并且在使用它的地方可见,以避免因类型未定义导致的隐式转换为其他类型。 另外,可能需要注意枚举类型的大小和指针的兼容性。虽然枚举类型的大小通常由编译器决定,但在某些情况下,将其指针强制转换为`char *`可能会引发对齐或大小的问题,但现代编译器中通常指针的大小是一致的,因此这种转换在大多数情况下是安全的,但需要确保实际操作的数据是正确的类型。 最后,总结可能的步骤:检查函数指针的声明和定义是否一致,必要时进行类型转换;确保所有相关的类型定义正确且可见;如果使用第三方库,查阅文档确认正确的函数指针签名。</think>要解决函数指针类型不兼容的编译错误`incompatible function pointer types passing 'void (*)(void *, enum scan_balance *)' to parameter of type 'void (*)(void *, char *)'`,需确保函数指针的参数类型严格匹配。以下是分步解决方案: --- ### **1. 检查函数指针声明与实现** - **问题根源**:调用函数时传递的第二个参数类型为`enum scan_balance *`,而函数期望的类型是`char *`。 - **解决方法**: 1. **修改回调函数参数类型**:将回调函数的第二个参数改为`char *`,并在内部进行类型转换。 ```c // 原函数定义(错误) void your_callback(void *arg1, enum scan_balance *arg2) { ... } // 修改后 void your_callback(void *arg1, char *arg2) { enum scan_balance *balance = (enum scan_balance *)arg2; // 显式类型转换 // 后续操作 } ``` 2. **调用时匹配类型**:确保调用此回调函数时,传入的第二个参数是`char *`类型,例如: ```c enum scan_balance data; your_callback(arg1, (char *)&data); // 强制类型转换 ``` --- ### **2. 调整函数指针声明(若可能)** 如果目标函数允许修改参数类型(例如是自定义函数),可直接将参数类型声明为`enum scan_balance *`: ```c // 原声明(错误) void target_function(void (*callback)(void *, char *)); // 修改后 void target_function(void (*callback)(void *, enum scan_balance *)); ``` --- ### **3. 显式强制类型转换** 若无法修改函数声明或回调实现,可在传递函数指针时强制转换类型: ```c // 假设目标函数要求 void (*)(void *, char *) target_function((void (*)(void *, char *))your_callback); ``` - **注意**:此方法可能掩盖潜在的类型安全问题,需确保实际使用时参数类型正确[^2]。 --- ### **4. 检查枚举类型的可见性** 确保`enum scan_balance`的定义在使用前已正确声明: ```c // 在头文件中正确定义枚举 typedef enum scan_balance { BALANCE_A, BALANCE_B } scan_balance; ``` 若枚举定义在另一个文件中,需包含相关头文件。 --- ### **示例场景** 假设调用链如下: ```c // 库函数声明(不可修改) void register_callback(void (*cb)(void *, char *)); // 用户自定义回调 void my_callback(void *ctx, enum scan_balance *balance) { ... } // 注册回调时需强制转换 register_callback((void (*)(void *, char *))my_callback); ``` --- ### **根本原因** - 函数指针类型严格匹配要求参数类型完全一致,包括指针类型。 - `enum scan_balance *`与`char *`在编译器看来是不同类型,即使它们的内存布局相同[^1]。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值