现在我有一个排序算法,它以随机顺序获取一个对象数组,根据值属性将它们排序为一个新数组,并返回排序后的数组(特别是ArrayList,尽管我认为它与我的问题无关)。在我的任务中,我现在被指示根据这些不同对象自身的值属性,从高到低对另一个不同对象数组进行排序。我不想复制粘贴我已经实现的算法,也不想通过在几个地方更改对象和属性名称,或者直接按O(n^2)顺序对其他对象进行排序,而是想以更通用的方式重用我当前的算法。更清楚的是:
new Array<ClassA> a;
new Array<ClassB> b;
public Array<ClassB> sort(Array<ClassB> input){
//sort based on certain attribute in ClassB;
return sortedArray;
}
我想变成:
new Array<ClassA> a;
new Array<ClassB> b;
public Array<ClassB> sort(Array<Object> input, Attribute x){
//sort based on attribute of Object where which attribute is given as a parameter x;
return sortedArray;
}
我知道这可能比我的书面解释更令人困惑,所以为了更清楚(我希望),我希望将我的排序算法变成类似于python的内置函数:排序(可迭代,key=None,reverse=False)
我实现的算法是任务的一部分,所以我不能用库中的东西来代替它。如果你认为我的整个问题都很愚蠢,那么请考虑一下英语不是我的母语,不——我对编程术语一点也不熟悉,是的——我是一个非常糟糕的程序员。那就继续你的一天吧!如果你认为你能给我一个合理的答案,我将不胜感激:D
public Array<ClassB> sort(Array<Object> input, Attribute x){
这将任意对象的数组转换为包含ClassB
实例的数组,这显然不影响它的工作方式。
Array<Object> input
这并不意味着你认为它意味着什么。Array<ClassB>
是NOTArray<Object>
的有效值。毕竟,我可以向Array<Object>
添加String(String是一个对象!),但不能向Array<ClassB>
添加。在计算机技术术语中,java中的基本类型(如Number
和Integer
)是协变-任何作为X的子类型的实例也是X:整数也是数字,也是对象。但是类型args是不变量。任何属于X子类型的实例都与X不兼容:List<Integer>
不是List<Number>
。这是因为它就是这样工作的,正如我上面所展示的:您可以向List<Number>
添加doubles。
您可以选择不同的方差:List<? extends Number>
或List<? super Number>
这样做。不过,你不需要它来解决这个问题。
泛型将事物联系起来。这就是你想要的:接受一些。。thing,并返回相同事物的数组:
public <T> Array<T> sort(Array<T> input, Attribute x) {}
上面写着:有一些类型的T
。不知道它是什么。这个T用来连接事物:输入数组的类型是什么?输出数组具有相同的类型。
这就是你想要的。
Attribute x
不可能以编程方式引用属性。幸运的是,您实际上并不需要属性。您需要一个函数:给定一些T
,提供一个值;一个你现在如何内在地排序的。这个功能是如何工作的?你不在乎。只要它是一致的(对于任何给定的实例返回相同的值),就可以根据映射的值对其进行排序。所以,你不想要Attribute
,你想要一个函数,把t变成你可以排序的东西
尽情狂欢吧,列出你拥有的每一个可分类的概念
public <T> Array<T> sortByInt(Array<T> input, ToIntFunction<T> f) {}
public <T> Array<T> sortByString(Array<T> input, Function<T, String> f) {}
public <T> Array<T> sortByDouble(Array<T> input, ToDoubleFunction<T, String> f) {}
等等。这些方法确实需要不同的名称,您不希望在这里重载。
映射到对象并祈祷
你不知道如何对任何给定的对象进行排序。您大概只知道如何对integer、double和string进行排序。你可以写:
public <T> Array<T> sortByDouble(Array<T> input, Function<T, ?> f) {}
然后在运行时检查函数是否映射到您想要的。如果函数映射到InputStream
,这显然不是一个可以排序的东西,那么您必须检查它,并抛出异常。如果一个对象被映射到int,另一个对象映射到字符串,也会让人很困惑。这听起来很简单,但事实并非如此。不要这样做。
使用java内置的自定义内容概念
Java对此有一个类型:Comparable
。这变得有点复杂;在这里,你希望对象可以将自己与自己进行比较,所以:
public <T, V extends Comparable<V>> Array<T> sort(Array<T> input, Function<T, V> f) {}
这有点复杂,但这是最正确的版本。让我们来看看:
<T, V... >
部分:这说明这个方法有2个类型变量。它们就像普通变量,只是它们包含类型T
:它没有任何界限。因此,它可以是任何东西。任何调用此方法的代码都会显式地选择某个内容,或者编译器会找出它V
:这是有界的。V不可能只是任何东西。V必须是某种类型,它具有实现Comparable<V>
的属性——换句话说,它可以将自己与自己类型的另一个值进行比较。String
、Integer
和许多其他内置到java中的东西都是这样的。CCD_ 27是用于填写CCD_ 28的有效类型Array<T>
-方法返回的内容sort
-方法名称Array<T> input
—输入。请注意,此链接到返回类型Function<T, V>
-一个函数。这个函数将接收一个T类型的对象,并返回一个V类型的对象。记住,V不能只是任何东西——它需要是某种特定的类型,可以对自己与其他相同类型的对象进行排序,例如String
或Integer
有了这些,您可以编写一些代码。例如:
T first = input.get(0); // still no idea what T is. But `input.get(0)` returns it.
T second = input.get(1);
V firstAttribute = function.apply(first); // extracts the sortable attribute
V secondAttribute = function.apply(second);
int ordering = firstAttribute.compareTo(secondAttribute); // magic!
最后一行是一个奇迹。它编译并保证工作:firstAttribute是V
,虽然我们不知道V是什么(可以是String。也可以是Integer),但我们知道V有int compareTo(V other)
方法。因此,我们可以援引这一点。负数表示first
在second
之前,正数表示first
在第二之后,而0
响应表示first
和second
相等,或者就比较而言,是兄弟姐妹——两者都不"在"另一个之前。
这个函数就是编写排序算法所需要的全部功能。
注意,假设first
是T
,您可以写:
Array<T> output = new Array<T>();
output.add(first);
编译器不知道T
是什么,但它知道first
是T,所以这个调用是允许的。