我可以在 java 中将算法作为参数传递吗?

Can I pass an algorithm as a parameter in java?

我正在对排序算法进行一些测量测试。

我创建了这个方法来计算选择排序需要排序数组的时间

public static double timeToSelectionSort(Double[] arrayOfNumbers) {

      double timeToSelectionSort =0;
  Stopwatch stopwatch = new Stopwatch();

  Selection.sort(arrayOfNumbers);

  timeToSelectionSort = stopwatch.elapsedTime(); 

  return   timeToSelectionSort;
  }

问题是我需要为我的所有排序算法(插入、选择、快速排序、合并排序...)创建此方法

有没有办法将这些算法作为此方法的参数传递?

是的,绝对是。这就是所谓的 Strategy Pattern。基本上,创建一个接口,让每个算法都是实现接口的 class,并且参数是参数的类型(我在这里使用 C# 约定)

public interface SortingAlgo {
  void sort(...);
}

public class QuickSort implements SortingAlgo {
  public void sort(...) {
    ...
  }
}

public void methodYouWantToAcceptAlgo(SortingAlgo algo) {
  ...
}

等等

  1. 如果您的排序算法都是在不同的 类 中实现的,请使用要调用的方法创建一个接口,并让所有 类 实现该接口。
    例如。这就是 List 是一个具有多个具有不同性能特征的实现的接口。

    public interface Sort {
        void sort(Double[]);
    }
    public class Selection implements Sort {
        // code here
    }
    public static double timeToSort(Double[] input, Sort sort) {
        Stopwatch stopwatch = new Stopwatch();
        sort.sort(input);
        return stopwatch.elapsedTime();
    }
    public static void test(Double[] input) {
        System.out.println(timeToSort(input, new Selection()));
    }
    
  2. 如果您的排序算法都是静态方法,请使用 Consumer<Double[]> 接口,并使用 Java 8 个方法引用提供实现。

    public class Sorts {
        public static void selection(Double[] input) {
            // code here
        }
    }
    public static double timeToSort(Double[] input, Consumer<Double[]> sort) {
        Stopwatch stopwatch = new Stopwatch();
        sort.accept(input);
        return stopwatch.elapsedTime();
    }
    public static void test(Double[] input) {
        System.out.println(timeToSort(input, Sorts::selection));
    }
    
  3. 如果您的排序方法并非都共享相同的签名,请使用 lambda 表达式。这是#2 的变体。

    public static void test(Double[] input) {
        System.out.println(timeToSort(input, (a) -> Sorts.selection(a)));
    }
    

策略模式在这种情况下会非常有用。

Strategy Pattern:

  • 定义一族算法,封装每一个,并使它们 可互换。策略让算法独立于 使用它的客户

  • 这些算法可以互换使用以改变应用程序
    不改变其架构的行为。

  • 通过单独封装算法,新的算法符合
    具有相同的界面,可以很容易地引入。

其他简单的方法是使用Lambda Function

你问的是 strategy pattern,正如 EJoshuaS 指出的那样。策略模式的目的是准确解决您所描述的问题 - 您想要应用 some 操作,但您不一定知道 which 在编译时操作,因此您定义了一个 "strategy" 接口,允许您将 调用 策略的代码与 的代码分开]定义策略。

在您的情况下,您想要对数组进行排序,但希望将其与实际执行排序操作的算法分离。所以,首先你定义一个接口来描述排序:

/** Interface responsible for sorting an array of doubles. */
@FunctionalInterface // we'll come back to this
public interface Sorter {
  /** Sorts the input array in ascending order. */
  void sort(double[] arr);
}

现在我们有了一个接口,我们可以编写使用该接口的代码,尽管还不知道实现(例如冒泡排序、快速排序等)会是什么:

/** Given an array and a sorting strategy to apply, prints the sorted array. */
public static void printSorted(double[] arr, Sorter sorter) {
  sorter.sort(arr);
  System.out.println(Arrays.toString(arr));
}

太好了,我们完成了!大多数情况下...现在我们需要创建 Sorter 的一些实现以便调用 printSorted()。虽然我会将实现本身留给您,但样板文件看起来像:

/** Sorter strategy applying the bubble-sort algorithm. */
public class BubbleSorter implements Sorter {
  @Override
  public void sort(double[] arr) {
    // TODO
  }
}

一旦您拥有一个或多个 Sorter 实现,您就可以调用 printSorted():

printSorted(new double[] {5.0, 3.0, 10.0}, new BubbleSorter());

任务(打印排序数组)现在与要排序的数组要使用的排序策略分离,您可以轻松替换不同的策略。


等等,Java 8 呢?

Lambda 和一般的函数式编程本质上是应用策略模式的一种很好的语法(请原谅过于简单化),事实证明它们非常适合这类任务。

我将 Sorter 注释为 @FunctionalInterface above, which notes that the interface is intended to be used to construct lambda expressions. Any operation that takes a double[] and doesn't return anything can be passed as a lambda into our printSorted() function. For example, we can use Arrays.sort() 作为我们的策略:

printSorted(..., d -> Arrays.sort(d));

更好的方法是在此处使用 method reference (Arrays::sort),但为了清楚起见,我使用了 lambda。

您可能会注意到 Sorter 看起来很像 Consumer<double[]>。在实践中,它 一个 Consumer,因此我们可以删除该接口并让 printSorted() 采用 Consumer<double[]>。您应该更喜欢哪个(重用 Consumer 或定义您自己的界面)在很大程度上取决于偏好,但在 different contexts both are reasonable choices.