自定义的 Java8 枚举

huangapple go评论52阅读模式
英文:

Customized Enum in Java8

问题

为了澄清我的目标,我重新表述了问题。这是我尝试解决的用例:目前在我的项目中有超过50个表格。在几个地方使用了这些表格(渲染、TableModel等)。该项目相当老,还包括一些今天可能不再必要的功能,不过那是另一个话题。一个示例中使用表格的地方如下:

exampleTable.getColumnModel().getColumn(0).setPreferredWidth(50);
exampleTable.getColumnModel().getColumn(1).setPreferredWidth(200);
exampleTable.getColumnModel().getColumn(2).setPreferredWidth(150);
exampleTable.getColumnModel().getColumn(3).setPreferredWidth(100);

以及在表格模型中:

public Class<?> getColumnClass(int columnIndex) {

    if (columnIndex == 0) {
        return Integer.class;
    } else if (columnIndex == 3) {
        return Date.class;
    }
    return String.class;
}

public int getColumnCount() {
    return 4;
}

public String getColumnName(int column) {
    int x = 0;
    if (column == x++) {
        return Texts.TableColumn.ID;
    } else if (column == x++) {
        return Texts.TableColumn.NAME;
    } else if (column == x++) {
        return Texts.TableColumn.CREATOR;
    } else if (column == x++) {
        return Texts.TableColumn.CREATED;
    } else {
        return "";
    }
}

现在,我的第一次尝试是使用索引的常量,以便我可以直接读取所指的列(无需宽度、文本等)。例如:

exampleTable.getColumnModel().getColumn(Constants.EXPL_TAB_COL_ID).setPreferredWidth(50);

然而,这会产生负面影响,因为我必须在常量名称中使用表名,例如,列ID可能会出现在不同位置的多个表格中。下一个尝试是使用Enums来解决这个问题,实际上适用于用“常量/Enums”替代索引的原始用例,以提高可读性:

exampleTable.getColumnModel().getColumn(TAB_EXPL.ID.ordinal()).setPreferredWidth(50);

然而,最佳解决方案应该一次性解决所有问题,我的目标是找到类似这样的解决方案:

for (TAB_EXPL col : TAB_EXPL.values()) {
    exampleTable.getColumnModel().getColumn(col.getIndex()).setPreferredWidth(col.getPreferredWidth());
}
public Class<?> getColumnClass(int columnIndex) {

    if (columnIndex == TAB_EXPL.ID.getIndex()) {
        return Integer.class;
    } else if (columnIndex == TAB_EXPL.CREATED.getIndex()) {
        return Date.class;
    }
    return String.class;
}

public int getColumnCount() {
    return TAB_EXPL.values().length;
}

public String getColumnName(int column) {
    for (TAB_EXPL col : TAB_EXPL.values()) {
        return col.getName();
    }
}

无论是类、枚举还是记录类型都可以解决问题。我还尝试了使用EnumMap类型与记录的组合,但当我尝试在EnumMap的put()方法中使用多个参数(记录使用int索引和名称定义)时失败了。此外,put()方法必须在构造函数或另一个方法中调用,这使得枚举/常量尝试无效。

eMap.put(TAB_EXPL.ID, (0, "Id"));

希望这有助于理解问题,并希望有人可以帮助我。

编辑2 解决方案: 我自己找到了解决方案,尽管它不是我寻找的确切解决方案,它仅适用于Java 9+。以下是一个示例,以防有人遇到类似的问题或有任何额外的想法:

import java.util.Map;
import java.util.EnumMap;

public class HelloWorld {
    public enum TAB_EXPL { ID, DATE, USER };
    public enum TAB_ATT { ID, NAME, STATE };
    public static void main(String[] args) {

        class RecordTableColumn {

            private final int index;
            private final String name;
            private final int colWidth;

            public RecordTableColumn(int index, String name, int colWidth) {
                this.index = index;
                this.name = name;
                this.colWidth = colWidth;
            }

            public int getIndex() {
                return index;
            }

            public String getName() {
                return name;
            }

            public int getColWidth() {
                return colWidth;
            }
        }

        EnumMap<TAB_EXPL, RecordTableColumn> TabExpl = new EnumMap<>(Map.ofEntries(
            Map.entry(TAB_EXPL.ID, new RecordTableColumn(0, "Id", 10)),
            Map.entry(TAB_EXPL.DATE, new RecordTableColumn(1, "Date", 30)),
            Map.entry(TAB_EXPL.USER, new RecordTableColumn(2, "User", 100))
        ));

        EnumMap<TAB_ATT, RecordTableColumn> TabAtt = new EnumMap<>(Map.ofEntries(
            Map.entry(TAB_ATT.ID, new RecordTableColumn(0, "Id", 10)),
            Map.entry(TAB_ATT.NAME, new RecordTableColumn(1, "Name", 100)),
            Map.entry(TAB_ATT.STATE, new RecordTableColumn(2, "State", 50))
        ));

        System.out.println("Table Example:");
        for (TAB_EXPL col : TAB_EXPL.values()) {
            System.out.println("Index: " + TabExpl.get(col).getIndex());
            System.println("Name: " + TabExpl.get(col).getName());
            System.println("Column Width: " + TabExpl.get(col).getColWidth());
        }
        System.out.println("---------------------------------------------");
        System.println("Table Att:");
        for (TAB_ATT col : TAB_ATT.values()) {
            System.println("Index: " + TabAtt.get(col).getIndex());
            System.println("Name: " + TabAtt.get(col).getName());
            System.println("Column Width: " + TabAtt.get(col).getColWidth());
        }
    }
}
英文:

To clarify my goal I rephrased the question. Here the use-case I am trying to solve: At the moment there are more than 50 tables in my project. At several locations these tables are used(Rendering, TableModel, etc.) The project is quite old and also includes some features which might not be necessary today however that's a different topic. One example where a table is used is this one:

exampleTable.getColumnModel().getColumn(0).setPreferredWidth(50);          
exampleTable.getColumnModel().getColumn(1).setPreferredWidth(200);        
exampleTable.getColumnModel().getColumn(2).setPreferredWidth(150);         
exampleTable.getColumnModel().getColumn(3).setPreferredWidth(100);	

and in the table model:

public Class&lt;?&gt; getColumnClass(int columnIndex) {
if (columnIndex == 0) {
return Integer.class;
} else if (columnIndex == 3) {
return Date.class;
}
return String.class; 
}
public int getColumnCount() {
return 4;
}
public String getColumnName(int column) {
int x=0;
if (column == x++) {
return Texts.TableColumn.ID;
} else if (column == x++) {
return Texts.TableColumn.NAME;
} else if (column == x++) {
return Texts.TableColumn.CREATOR;
} else if (column == x++) {
return Texts.TableColumn.CREATED;
} else {
return &quot;&quot;;
}
}

Now my first attempt was to use constants for the index so I can directly read which column is meant (no widt, text, etc.) Example:

exampleTable.getColumnModel().getColumn(Constants.EXPL_TAB_COL_ID).setPreferredWidth(50);

However this has the negative side effect, that I have to use the Table name in the constant-name as for example the column ID can appear in multiple tables at different positions. The next attempt was to solve the problem by using Enums which actually worked for the original use-case of replacing the indeices with "Constants/Enums" for the readability:

exampleTable.getColumnModel().getColumn(TAB_EXPL.ID.ordinal()).setPreferredWidth(50);

However as the optimal solution would solve all the problems at once my goal is to find like this:

	for (TAB_EXPL col : TAB_EXPL.values()) {
exampleTable.getColumnModel().getColumn(col.getIndex()).setPreferredWidth(col.getPreferredWidth);
}
public Class&lt;?&gt; getColumnClass(int columnIndex) {
if (columnIndex == TAB_EXPL.ID.getIndex()) {
return Integer.class;
} else if (columnIndex == TAB_EXPL.CREATED.getIndex()) {
return Date.class;
}
return String.class; 
}
public int getColumnCount() {
return TAB_EXPL.values().length;
}
public String getColumnName(int column) {
for (TAB_EXPL col : TAB_EXPL.values()) {
return col.getName();
}
}

It doesn't matter if it's a class, enum or record it should just solve the problem. I also tried an approach with the EnumMap type in combination with a record however I failed when I tried to use multiple arguments(Record is defined with int Index, String name) in the put() method of the EnumMap. Also the put()-Method has to be called in a constructor or another method which makes the enum/constant attempt useless:

eMap.put(TAB_EXPL.ID, (0, &quot;Id&quot;));

Hope this helps to understand the problem and that someone can help me.

EDIT2 SOLUTION: I found a solution myself, however it is not exactly the one I searched and it does only work for Java version 9+. Here is an example in case someone has a similar problem or any additional ideas:

import java.util.Map;
import java.util.EnumMap;
public class HelloWorld {
public enum TAB_EXPL{ID,DATE,USER};
public enum TAB_ATT{ID,NAME,STATE};
public static void main(String[] args) {
class RecordTableColumn {
private final int index;
private final String name;
private final int colWidth;
public RecordTableColumn(int index, String name, int colWidth) {
this.index = index;
this.name = name;
this.colWidth = colWidth;
}
public int getIndex() {
return index;
}
public String getName() {
return name;
}
public int getColWidth() {
return colWidth;
}
}
EnumMap&lt;TAB_EXPL, RecordTableColumn&gt; TabExpl = new EnumMap&lt;&gt;(Map.ofEntries(
Map.entry(TAB_EXPL.ID, new RecordTableColumn(0, &quot;Id&quot;, 10)),
Map.entry(TAB_EXPL.DATE, new RecordTableColumn(1, &quot;Date&quot;, 30)),
Map.entry(TAB_EXPL.USER, new RecordTableColumn(2, &quot;User&quot;, 100))
));
EnumMap&lt;TAB_ATT, RecordTableColumn&gt; TabAtt = new EnumMap&lt;&gt;(Map.ofEntries(
Map.entry(TAB_ATT.ID, new RecordTableColumn(0, &quot;Id&quot;, 10)),
Map.entry(TAB_ATT.NAME, new RecordTableColumn(1, &quot;Name&quot;, 100)),
Map.entry(TAB_ATT.STATE, new RecordTableColumn(2, &quot;State&quot;, 50))
));
System.out.println(&quot;Table Example:&quot;);
for (TAB_EXPL col : TAB_EXPL.values()) {
System.out.println(&quot;Index: &quot; + TabExpl.get(col).getIndex());
System.out.println(&quot;Name: &quot; + TabExpl.get(col).getName());
System.out.println(&quot;Column Width: &quot; + TabExpl.get(col).getColWidth());
}  
System.out.println(&quot;---------------------------------------------&quot;);
System.out.println(&quot;Table Att:&quot;);
for (TAB_ATT col : TAB_ATT.values()) {
System.out.println(&quot;Index: &quot; + TabAtt.get(col).getIndex());
System.out.println(&quot;Name: &quot; + TabAtt.get(col).getName());
System.out.println(&quot;Column Width: &quot; + TabAtt.get(col).getColWidth());
}  
}

}

答案1

得分: 1

枚举的主要好处是类型安全的常量。如果将许多不同表的列混合到一个枚举中,就会失去类型安全性。

但是枚举可以实现接口。我们可以定义一个接口,所有我们的"表定义"枚举应该像这样实现:

/**
 * 用于定义表的枚举的接口。不应该由非枚举类实现。
 */
public interface TableDefinition<T extends Enum<T>> {
    int getPreferredWidth();
    Class<?> getColumnClass();
}

请注意,这个接口可以在枚举之外实现(据我所知,类型系统中没有一种方法可以强制只能使用enum来实现接口),但我们确实只想要枚举。

然后,我们将在某个地方定义一个类似这样的方法(这应该可能ColumnModel的非静态方法,但我将假装由于某些原因你不能更改该类):

static <T extends Enum<T> & TableDefinition<T>> void applyPreferredWidth(ColumnModel cm, Class<T> clazz) {
    for (T column : clazz.getEnumConstants()) {
        cm.getColumn(column.ordinal()).setPreferredWidth(column.getPreferredWidth());
    }
}

现在,这个方法(及其魔术类型参数)确保传递给它的类引用一个既是enum又实现了我们的接口的类。这意味着我们可以使用enum方法,比如ordinal()(我们将其解释为列索引)和我们自己的方法,比如getPreferredWidth()

现在,这个枚举的实现可能看起来像这样:

public enum ExampleTable implements TableDefinition<ExampleTable> {
    ID(Integer.class, 50),
    NAME(String.class, 200),
    CREATOR(String.class, 150),
    CREATED(Instant.class, 100);

    private final Class<?> columnClass;
    private final int preferredWidth;

    ExampleTable(Class<?> columnClass, int preferredWidth) {
        this.columnClass = columnClass;
        this.preferredWidth = preferredWidth;
    }

    @Override
    public int getPreferredWidth() {
        return preferredWidth;
    }

    @Override
    public Class<?> getColumnClass() {
        return columnClass;
    }
}
英文:

The primary benefit of enums is type safe constants. If you have columns of many different tables mixed into one enum, you're losing out on type safety.

But enums can implement interfaces. And we can define an interface that all our "table-definition" enums should implement like this:

/**
* Interface for enums that define a table. Should not be implemented by non-enum classes.
*/
public interface TableDefinition&lt;T extends Enum&lt;T&gt;&gt; {
int getPreferredWidth();
Class&lt;?&gt; getColumnClass();
}

Note that this interface could be implemented outside of an enum (there's no way in the type system that I know of to force an interface to only be implemented using enum), but we really only want enums.

Then we'll define a method like this somewhere (it should probably be a non-static method of ColumnModel, but I'll pretend you can't change that class for some reason):

static &lt;T extends Enum&lt;T&gt; &amp; TableDefinition&lt;T&gt;&gt; void applyPreferredWidth(ColumnModel cm, Class&lt;T&gt; clazz) {
for (T column : clazz.getEnumConstants()) {
cm.getColumn(column.ordinal()).setPreferredWidth(column.getPreferredWidth());
}
}

Now this method (and it's magic type parameter) ensures that the class passed to it references a class that's both and enum and implements our interface. This means we can use enum methods like ordinal() (which we'll just interpret as the column index) and our own methods like getPreferredWidth().

Now an implementation of this enum could simply look like this:

public enum ExampleTable implements TableDefinition&lt;ExampleTable&gt; {
ID(Integer.class, 50),
NAME(String.class, 200),
CREATOR(String.class, 150),
CREATED(Instant.class, 100);
private final Class&lt;?&gt; columnClass;
private final int preferredWidth;
ExampleTable(Class&lt;?&gt; columnClass, int preferredWidth) {
this.columnClass = columnClass;
this.preferredWidth = preferredWidth;
}
@Override
public int getPreferredWidth() {
return preferredWidth;
}
@Override
public Class&lt;?&gt; getColumnClass() {
return columnClass;
}
}

答案2

得分: 0

如果您不想编写构造函数和获取方法,您可以尝试使用Lombok。

        &lt;dependency&gt;
            &lt;groupId&gt;org.projectlombok&lt;/groupId&gt;
            &lt;artifactId&gt;lombok&lt;/artifactId&gt;
            &lt;version&gt;1.18.26&lt;/version&gt;
        &lt;/dependency&gt;

而且您可以像这样编写枚举。

package com.example;

import lombok.AllArgsConstructor;
import lombok.Getter;

public class EnumClass {

    @AllArgsConstructor
    @Getter
    public enum TableA {
        ID(Integer.class, 50);

        private final Class&lt;?&gt; aClass;
        private final Integer width;
    }

    public static void main(String[] args) {
        Class&lt;?&gt; aClass = TableA.ID.getAClass();
        Integer width = TableA.ID.getWidth();
    }
}

但您仍然需要编写字段...

英文:

If you don't want to write constructer and getter method. You can try lombok.

        &lt;dependency&gt;
&lt;groupId&gt;org.projectlombok&lt;/groupId&gt;
&lt;artifactId&gt;lombok&lt;/artifactId&gt;
&lt;version&gt;1.18.26&lt;/version&gt;
&lt;/dependency&gt;

And you can write enum like this.

package com.example;
import lombok.AllArgsConstructor;
import lombok.Getter;
public class EnumClass {
@AllArgsConstructor
@Getter
public enum TableA {
ID(Integer.class, 50);
private final Class&lt;?&gt; aClass;
private final Integer width;
}
public static void main(String[] args) {
Class&lt;?&gt; aClass = TableA.ID.getAClass();
Integer width = TableA.ID.getWidth();
}
}

But you still need to write fields...

huangapple
  • 本文由 发表于 2023年3月15日 20:14:42
  • 转载请务必保留本文链接:https://go.coder-hub.com/75744555.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定