简体   繁体   English

Java中泛型的静态多态性

[英]Static polymorphism with generics in Java

I was hoping to create some clean code that can recursively dig into a collection and print the first Integer it finds. 我希望创建一些干净的代码,可以递归地挖掘集合并打印它找到的第一个Integer。 Coming from a C++ background that approach looked like this: 来自C ++背景的方法看起来像这样:

class Test {
static void print(Integer i) {
  System.out.println(i);
}

static <T> void print(ArrayList<T> arr) {
  T entry= arr.at(0);
  Test.print (entry);
}

static void Test() {
    ArrayList<ArrayList<Integer>> arrayOfArrayOfInt= create();
    print( arrayOfArrayOfInt );
}
}

Unfortunately this doesn't work. 不幸的是,这不起作用。

One alternative is to give up on static polymorphism and create a function print(Object o) and then do a number of instanceof checks to branch to the right behavior. 一种替代方法是放弃静态多态并创建函数print(Object o),然后执行一些instanceof检查以分支到正确的行为。 Is that the only way it can be done due to Java's type erasure or is there a more elegant approach? 这是由于Java的类型擦除是否可以完成的唯一方法,还是有更优雅的方法?

You're right in assuming that you can't treat java generics as you would a c++ template. 你是正确的假设你不能像处理c ++模板一样对待java泛型。

So, without instanceof , this will not guess that entry is an Integer (neither in runtime nor in compile time): 因此,如果没有instanceof ,这将不会猜测条目是一个整数(既不在运行时也不在编译时):

static <T> void print(ArrayList<T> arr) {
  T entry= arr.get(0);
  Test.print (entry);
}

So to "to create some clean code that can recursively dig into a collection and print the first Integer it find" (or rather the first non list element at index 0) in java: 所以要“创建一些干净的代码,可以递归地挖掘集合并打印它找到的第一个整数” (或者更确切地说是索引0处的第一个非列表元素):

static void print(Object obj) {
    System.out.println(obj);
}

static <T> void print(List<T> arr) {
    T entry = arr.get(0);
    if (entry instanceof List)
        print((List<?>) entry);
    else 
        print(entry);
}

static void test() {
    List<List<Integer>> arrayOfArrayOfInt = create();
    print(arrayOfArrayOfInt);
}

Not the elegant solution you'd like but the feasible solution the generic type system requires. 不是您想要的优雅解决方案,而是泛型系统所需的可行解决方案。

The following method will recursively dig into an Object and return an Optional containing the first Integer it finds, or Optional.empty() if it can't find one. 下面的方法将递归挖成一个Object ,并返回一个Optional含有第一Integer它找到,或Optional.empty()如果不能找到一个。

static Optional<Integer> firstInt(Object o) {
    if (o instanceof Integer)
        return Optional.of((Integer) o);
    if (o instanceof int[]) {
        int[] array = (int[]) o;
        return array.length > 0 ? Optional.of(array[0]) : Optional.empty();
    }
    if (o instanceof Object[])
        return firstInt(Arrays.asList((Object[]) o));
    if (o instanceof Iterable) {
        for (Object o2 : (Iterable<?>) o) {
            Optional<Integer> result = firstInt(o2);
            if (result.isPresent())
                return result;
        }
    }
    return Optional.empty();
}

You can do this using polymorphism, but you would have to create an interface Searchable like this 可以使用多态做到这一点,但是你必须创建一个像这样的Searchable接口

interface Searchable {
    Optional<Integer> search();
}

and then create wrapper classes for all the concrete types you want to be able to search. 然后为您希望能够搜索的所有具体类型创建包装类。 For example: 例如:

public final class SearchableList<T extends Searchable> extends AbstractList<T> implements Searchable {

    private final List<T> list;

    SearchableList(List<T> list) {
        this.list = list;
    }

    // rest omitted
}

However, this would be such a convoluted mess, and I would avoid it, preferring the instanceof checks instead. 然而,这将是一个令人费解的混乱,我会避免它,而不是更喜欢检查instanceof

Short answer: Yep, give up on static polymorphism and use instanceof . 简答:是的,放弃静态多态并使用instanceof Or just write a local loop to do your work. 或者只是写一个本地循环来完成你的工作。

Java's generics are much weaker than C++'s templates. Java的泛型比C ++的模板弱得多。 In particular, the compiler will not produce new specializations for you. 特别是,编译器不会为您生成新的特化。 All you can do with them is do more restrictive static type checking on classes and methods that are already defined through normal means (eg you wrote them out by hand or generated them with a tool). 您可以用它们做的就是对已经通过常规方法定义的类和方法进行更严格的静态类型检查(例如,您手动编写它们或使用工具生成它们)。 As you suspected, type erasure is relevant. 如您所料,类型擦除是相关的。

With Java generics, this code: 使用Java泛型,此代码:

static <T> void print(ArrayList<T> arr) {
  T entry= arr.at(0);
  Test.print (entry);
}

always effectively turns in to this, due to type erasure: 由于类型擦除,总是有效地转入此:

static Object void print(ArrayList<Object> arr) {
  Object entry= arr.at(0);
  Test.print (entry);
}

It will not produce separate specializations as C++ templates would. 它不会像C ++模板那样产生单独的特化。

Your particular piece of code isn't working because it's missing a couple particular methods, which you'd have to supply by hand. 您的特定代码无法正常工作,因为它缺少一些特定的方法,您必须手动提供这些方法。 But I think I can see where you're trying to go. 但我想我能看到你想要去的地方。 (I assume you want to end up with code that's going to work on a broader range of types than just List<List<Integer>> .) And you're not going to get a satisfactory solution for that using Java generics. (我假设您希望最终得到的代码可以在更广泛的类型上工作,而不仅仅是List<List<Integer>> 。)并且您不会使用Java泛型获得满意的解决方案。 (You can make it work, but you'll have to write a whole lot of code by hand, which templates won't help you with. And there would be some grody edge cases.) Switch to dynamic type checking and instanceof ; (你可以使它工作,但你必须手工编写大量的代码,哪些模板对你没用。而且会有一些grody edge例。)切换到动态类型检查和instanceof ; that's the Java way to do this stuff. 这是Java方法来做这些事情。

First of all, the function to get an element is "get" not "at." 首先,获取元素的函数是“get”而不是“at”。 However, your main problem is that you are trying to call the function "print" which takes an integer, but passing it a value of type "T." 但是,你的主要问题是你试图调用带有整数的函数“print”,但是传递一个类型为“T”的值。

If you are going to call print and pass it a value of unknown type, the function must either take an argument of type Object or take a generic argument, such as 如果要调用print并将其传递给未知类型的值,则该函数必须采用Object类型的参数或采用泛型参数,例如

public <T> void print(T item)

or 要么

public void print (Object item)

Java will only match function calls to the most specific overload at compile time, if you're trying to find the correct overload at runtime you will have to use instanceof. Java只会在编译时将函数调用与最具体的重载进行匹配,如果您在运行时尝试查找正确的重载,则必须使用instanceof。 Alternatively, you could build a HashMap that points from Class objects to Consumer objects. 或者,您可以构建一个从Class对象指向Consumer对象的HashMap。

HashMap<Class<?>, Consumer<?>> prints = new HashMap<>();
void setup() {
    prints.put(ArrayList.class, (list) -> {
        for (Object o : list) {
            print(o);
        }
    }

    prints.put(Integer.class, (integer) -> System.out.println(integer));
}

void print(Object o) {
    Consumer<?> func = prints.get(o.getClass());
    func.accept(o);
}

If you want to keep this code the way it is, you'll have to add another print-method for objects. 如果要保持此代码的方式,则必须为对象添加另一种打印方法。

public static void print(Object o){}

But there are better approaches for your example anyways. 但无论如何,你的例子有更好的方法。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM