【问题标题】:Java generics: Clean way to process two generic types using one function signatureJava 泛型:使用一个函数签名处理两种泛型类型的简洁方法
【发布时间】:2019-08-08 23:15:34
【问题描述】:

有一个类Utilities 提供公共静态函数,用于进行通用数据处理。该类主要用于创建FloatPointers 用于native code。

类如下所示:

class Utilities
{
    private Utilities(){}

    static FloatPointer toPointer( List< TypeA > list )
    {
        // do some magic
        return p
    }

    static FloatPointer toPointer( List< TypeB > list )
    {
        // do some different magic
        return p
    }
}

这个问题很明显。因为编译器将这些签名解析为FloatPointer toPointer( List list ),所以我遇到了擦除问题,所以这些函数不能这样定义。

现在我问自己,实现这一点的最干净的方法是什么。

首先,我非常想坚持使用名称toPointer,而不是使用不同的函数名称。这是因为在真正的类中有一大堆toPointer 函数处理各种类型。引入新名称会破坏计划,因为团队中的其他开发人员习惯于toPointer,这不是一个好主意。

我可以想象有两个函数 private static typeAToPointer( List&lt; TypeA &gt; list )private static typeBToPointer( List&lt; TypeB &gt; list ),但这两个函数需要从公共 toPointer 函数中调用,以免破坏函数名称的方案。

遗憾的是,我们没有实现TypeATypeB,所以我不能让这两者实现一个通用接口并接受这一点——通常我不能以任何方式更改TypeATypeB

目前我倾向于实现一个接受List&lt; T &gt; 的函数并简单地检查类型,然后处理它。这个例程看起来像这样:

static < T > FloatPointer toPointer( List< T > list )
{
    Class clazz;
    if( !list.isEmpty() ) clazz = list.get( 0 ).getClass();
    else throw new IllegalArgumentException( "Empty list will result in NP." );

    if( clazz == TypeA.class ) return typeAToPointer( list );
    else if( clazz == TypeB.class ) return typeBToPointer( list );
    else throw new IllegalArgumentException( "List entries of invalid type." );

}

对我来说,这看起来像是糟糕的代码。我错了吗?什么是实现我的目标的优雅/干净的方式,保持函数调用toPointer,但处理任一通用列表,同时无法调整TypeA或@的定义987654339@?

【问题讨论】:

  • 你需要做的“魔法”是什么?两者是否相同(ish)?
  • “对我来说,这看起来像糟糕的代码。我弄错了吗?”你的判断是正确的。
  • 是与否,代码是 samish,但是这两种类型确实有不同的公共功能,我需要使用它们。遗憾的是,我不能接受通用类型T,然后以同样的方式处理它。必须区分。
  • 请展示魔法。答案的形式取决于它实际需要做什么。
  • 我知道这不是你要找的,但我能看到的最干净的方法是 Java 只是更改方法名称而不是重载。我知道这不是理想,但它肯定比你那里的toPointer() 示例干净得多,并且还保持了泛型类型的安全性。

标签: java generics


【解决方案1】:

对我来说,这看起来像是糟糕的代码。我是不是搞错了?

不,你没有弄错。那里的示例混乱,不清楚,在运行时执行不必要的检查,也无法在编译时检查任何类型安全。

话虽如此:

什么是实现我的目标的优雅/干净的方式,即保留函数调用 toPointer,但处理任一通用列表,同时无法调整 TypeA 或 TypeB 的定义?

...你不能。

Java 泛型是通过擦除实现的,因此泛型信息在运行时会全部丢失(字节码中没有它的概念。)这意味着您不能拥有两个签名仅在泛型类型上有所不同的方法,如JVM 在运行时无法区分它们。

尽管您不情愿,但实现您所追求的最干净、最明显的方法就是放宽您对方法名称必须相同的要求。与上面的示例相比,这只需很少的更改即可为您提供编译时类型安全性和更简洁的代码。

您唯一的其他选择是创建List 的非泛型子类,它实现您要处理的每种泛型类型,并为每种类型使用不同的子类 - 但我真的看不到任何在这种情况下更可取。

【讨论】:

  • 我曾想过定义一个自定义的List 类型,但忽略了这个想法,因为引入一个与原始类型相同的全新类型似乎很荒谬,只是为了避免在调用时产生歧义一个奇异的函数。不完全是您在答案末尾的想法,而是沿着这些思路。好吧,似乎我别无选择,只能重命名该函数,尽管拥有一个带有一堆 toPointer 重载和一个 typeAToPointer 的干净类是很痛苦的。至少我没有忽略一些明显的事情,所以这是我的一线希望。谢谢!
  • 在其他语言(例如:Haskell)中,别名内置类型很常见。这可以提供清晰性并减少混淆具有不同语义但由相同基本类型表示的值的机会(例如:重量和高度都是浮点数,但您通常不想添加它们)。通过多态性处理这个问题有一些理论上的美,我肯定会考虑通过不同的非泛型 List 类型来模仿别名的想法,但我认为你是有权推迟污染你的代码库,直到你发现>1个区别。
【解决方案2】:

你可以这样做:

static FloatPointer typeAToPointer( List< TypeA > list )
{
    return toPointer( list, objectOfTypeA -> /*parse to FloatPointer*/ );
}

static FloatPointer typeBToPointer( List< TypeB > list )
{
    return toPointer( list, objectOfTypeB -> /*parse to FloatPointer*/ );
}

static < T > FloatPointer toPointer( List< T > list, Function< T, FloatPointer > parser ) {
   //do some common code here
   T object = /* extract object from list*/
   FloatPointer pointer = parser.apply( object );
   //do some other common code on pointer 
   return pointer ;
}

这里有一个模板方法 toPointer,它使用 Function parser 将某种类型 T 的对象映射到 FloatPinter。它的映射方式在具体的 toPointer( List list ) 方法中指定。例如,objectOfTypeA -> /解析为 FloatPointer/ 可能只是 objectOfTypeA -> objectOfTypeA.getFloatPointer()。

编辑:正如@AndyTurner 提到的,两个 toPointer 方法都会由于类型擦除而产生编译错误。如果您在编译时对具有正确类型的列表进行操作,重命名方法就可以解决问题。

【讨论】:

  • 除非你不能,因为前两个toPointer方法的擦除是一样的,所以不会编译。
  • 是的,可悲的是,正如@Andy Turner 提到的那样,这导致了我遇到的同样问题。编译器会理解前两个函数的两个签名是等价的。
  • @AndyTurner 哦,我没注意到。我会重命名方法 typeAToPointer() 和 typeBToPointer() 并解决问题,如果你知道你一直有什么类型的列表。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-12-18
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多