【问题标题】:Even Though using of Generics I had distinguish the T type即使使用泛型我也区分了 T 类型
【发布时间】:2017-06-12 13:44:29
【问题描述】:

我喜欢检查 Excel .csv 列,该列可能是 String、Int oder Double 类型。我实现了一个经典的泛型 Pair 类:

public class PairT<K,V> implements Comparable<PairT<K,V>>

如果是整数列,列值存储在:

ArrayList<PairT<Integer,Integer>> column_list = new ArrayList<>();

V 值保存 excel 行索引的位置。

下面的代码 sn-p 显示了令人讨厌的解决方案 - 我会改进:

// Add a cell value of type T to the column list
@SuppressWarnings("unchecked")
public static <T> void addCell(
        String excelcell,
        int row_idx,
        boolean ignorecase,
        T defkey,
        /*IO*/ArrayList<PairT<T,Integer>> column_list) throws RuntimeException
{   
    //Class<?> classtype = defkey.getClass();  String typename = classtype.getSimpleName();
    char type;
    if (defkey      instanceof String) type = 'S';
    else if (defkey instanceof Integer) type = 'I';
    else if (defkey instanceof Double) type = 'D';
    else type = 'O'; // other

    T key;
try
{
    switch(type)
    {
    case 'I': 
        try 
        {  key = (T)new Integer(excelcell);
        } catch (NumberFormatException e) { key = defkey; }  

        column_list.add(new PairT<T,Integer>(key,row_idx));
        break;         
    case 'D':
        try 
        {   key = (T)new Double(excelcell);          
        } catch (NumberFormatException e) { key = defkey; } 

        column_list.add(new PairT<T,Integer>(key,row_idx));
        break;
    case 'S':
        if (ignorecase) excelcell = excelcell.toUpperCase();
        column_list.add(new PairT<T,Integer>((T)excelcell,row_idx));
        break;
    default:  // Other take the .toString() output as key 
        column_list.add(new PairT<T,Integer>((T)excelcell.toString(),row_idx));
    }
}catch (Exception ex) // possibly a ClassCastException
{
    throw new RuntimeException("addCell(): Problems using PairT<K,V>",ex);
}
} //----- end of addCell()

为了测试我使用:

ArrayList<PairT<Integer,Integer>> column_list = new ArrayList<>();

int row_idx = 0; 

boolean ic = true; // for String values only;
Integer defval = new Integer("0");

String cell = "12";
addCell(cell,row_idx,ic,defval,column_list);

cell = "17.34"; // leads to def val
addCell(cell,++row_idx,ic,defval,column_list);

cell = "456";
addCell(cell,++row_idx,ic,defval,column_list);

cell = "foo"; // lead to def avlue
addCell(cell,++row_idx,ic,defval,column_list);

System.out.println("result: " + column_list); 
// [12;0, 0;1, 456;2, 0;3]

java.util.Collections.sort(column_list);  
System.out.println("Sorted: " + column_list); 

//Sorted: [0;1, 0;3, 12;0, 456;2]

它按预期工作,但是 - 正如我所说 - 我不想区分 addCell() 中的 Type T。 我更喜欢简短的解决方案,例如:

if (ignorecase) column_list.add(new PairT<T,Integer>((T)excelcell.toUpperCase(),row_idx));
else            column_list.add(new PairT<T,Integer>((T)excelcell,row_idx));

【问题讨论】:

  • 仅供参考,K 和 V 作为映射中的类型代表键和值。它们不是配对类中真正合适的标识符,因为没有键或值。
  • 你应该可以逃脱钻石运算符:list.add(new PairT&lt;&gt;(foo,foo))
  • 默认的 switch case 通过将String 转换为T 导致堆污染。你应该扔例如IllegalArgumentException 代替。
  • 我能想到的最简洁的解决方案是创建三个独立的addCell 方法,分别使用Integer defKeyDouble defKeyString defKey 参数。这使您可以完全取消 T,这应该会大大简化代码。

标签: java generics


【解决方案1】:

您的代码中存在一些问题;

首先,您将column_list 定义为Integer 值对的列表。您想将其定义为 ArrayList&lt;PairT&lt;T, Integer&gt;&gt; column_list = new ArrayList&lt;&gt;(); 以允许您存储 String、Double 或 Integer 数据。

其次,在addCell() 中检查整数defval 的类型,它的值始终为 0。然后在下面的 switch 语句中使用从该变量推断出的类型,这意味着您执行代码Integer不管excelCell是什么类型。

考虑到这些因素,我已经使用泛型类型参数清理了编写 addCell() 的代码。

public class Main {

    @SuppressWarnings("unchecked")
    public static <T> void main(String[] args) {
        ArrayList<PairT<T, Integer>> list = new ArrayList<>();

        int row_idx = 0;

        String cell = "12";
        addCell((T)cell, row_idx, list);

        cell = "17.34";
        addCell((T)cell, ++row_idx, list);

        cell = "456";
        addCell((T)cell, ++row_idx, list);

        cell = "foo";
        addCell((T)cell, ++row_idx, list);

        System.out.println("result: " + list);
        java.util.Collections.sort(list);
        System.out.println("Sorted: " + list);

    }

    @SuppressWarnings("unchecked")
    public static <T> void addCell(T excelCell, int row_idx, ArrayList<PairT<T, Integer>> list){
        if(excelCell instanceof String) list.add((PairT<T, Integer>) new PairT<>(((String) excelCell).toUpperCase(), row_idx));
        else list.add(new PairT<>(excelCell, row_idx));
    }

我已删除 ignoreCasedefval,因为它们与本示例无关。

【讨论】:

  • 你好卢克,猜测你铸造的 (T)cell 是一个简单的 cell.toString()
  • 是的,我目前正在检查这个。铸造 (T)anything 只需使用 toString()。可能是通用概念在实现 InitFromString 方法中受到影响。例如。泛型必须实现这样的方法。例如
  • 抱歉,上一篇文章中缺少示例:通用类 Pig 必须实现 initFromString() 才能初始化 Pig。例如。 initFromTring("name_of_the_pig; weight;age_of_the_pig",';');这与常用的 toString() 正好相反。
  • @Norbert 抱歉,我不完全确定您在问什么?您不必将单元格的类型更改为 String,因为泛型的要点是它们可以采用在编译时定义的任何类。
【解决方案2】:

您可以使用我在my answer here 中描述的内容并将Class&lt;T&gt; 映射到Function&lt;String, T&gt;,但我倾向于同意VGR 的评论,即您应该重新考虑在这里使用泛型的方式。

我认为 VGR 建议的重构代码的一种简单方法如下:

public static void addCell
   (String  excelCell,
    int     rowIdx,
    boolean ignoreCase,
    Integer defCellValue,
    List<PairT<Integer, Integer>> columnList)
{
    Integer cellValue;
    try {
        cellValue = Integer.valueOf(excelCell);
    } catch (NumberFormatException x) {
        cellValue = defCellValue;
    }
    columnList.add(new PairT<>(cellValue, rowIdx));
}

// addCell overload with Double defCellValue
// addCell overload with String defCellValue

可以概括一下并做这样的事情,尽管如果你只有几个重载,它不会给你带来太多好处:

public static void addCell
   (String  excelCell,
    int     rowIdx,
    boolean ignoreCase,
    Integer defCellValue,
    List<PairT<Integer, Integer>> columnList)
{
    addCell(excellCell,
            rowIdx,
            ignoreCase,
            defCellValue,
            columnList,
            Integer::valueOf);
}

public static void addCell(..., Double defCellValue, ...) {
    addCell(..., Double::valueOf);
}

public static void addCell(..., String defCellValue, ...) {
    addCell(..., Function.identity());
}

private static <T> void addCell
   (String  excelCell,
    int     rowIdx,
    boolean ignoreCase,
    T       defCellValue,
    List<PairT<T, Integer>> columnList,
    Function<String, T>     cellParser)
{
    if (ignoreCase) {
        excelCell = excelCell.toUpperCase();
    }
    T cellValue;
    try {
        cellValue = cellParser.apply(excelCell);
    } catch (IllegalArgumentException x) {
        cellValue = defValue;
    }
    columnList.add(new PairT<>(cellValue, rowIdx));
}

其他几件事:

  • 这样写会更有意义,例如class Cell&lt;T&gt; { int row, col; T value; } 而不是像你正在做的那样尝试使用 pair 类。 pair 类非常嘈杂,难以使用,而且您不能在 pair 类中编写特定于单元格的实用方法。

  • 您应该遵守 Java 命名约定。静态常量为大写,空格下划线 (static final int FOO_COUNT;),所有其他变量均为驼峰式,第一个字母为小写 (int fooCount;)。

【讨论】:

  • 是的,感谢使用这种“Function cellParser”技术(如 C 中的函数指针)的好主意。我会试试这个。第二个是的:我过去使用过 Cell 类,但由于泛型的受赞誉以及简单单元的可能“矫枉过正”而否决了这一点
猜你喜欢
  • 2011-08-13
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-09-25
  • 1970-01-01
  • 2018-09-19
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多