【问题标题】:Reusing code - Java重用代码 - Java
【发布时间】:2011-01-18 14:15:20
【问题描述】:

有没有办法通过这些函数中的数组代码重用迭代:

public static double[] ln(double[] z) {
    // returns  an array that consists of the natural logarithm of the values in array z
    int count = 0;

    for (double i : z){
        z[count] = Math.log(i);
        count += 1;
    }
    return z;
}


public static double[] inverse(double[] z) {
    //  returns  an array that consists of the inverse of the values in array z
    int count = 0;

    for (double i : z){
        z[count] = Math.pow(i,-1);
        count += 1;
    }
    return z;
}

【问题讨论】:

  • 不是真的...Java 不是 Lisp ;)
  • @Gugussee:是的,你可以。在 List 中,您可以通过语言结构来做到这一点,但在 Java 中,您可以使用 OO 设计模式来做到这一点。
  • 请注意,通过这种方式,更改也会反映在给定的数组参数中...换句话说,返回它是不必要的,或者您应该在更改之前创建给定数组参数的克隆.
  • @cherouvim:您说的是也使用反射或命名您的常用方法 "doIt"(而不是“log”或“inverse”),并且在任何如果是这样,那将是一种可能比疾病更糟的“治疗”。我说的更像是一个有趣的评论,而不是对 Java 的批评;)
  • @Gugussee:策略模式中没有使用反射。一切都是类型安全的,并使用可靠的 OO 原则。另外,我不是在批评你的评论,只是说这是可能的。

标签: java function operator-overloading code-reuse


【解决方案1】:

是的,使用http://en.wikipedia.org/wiki/Strategy_pattern 但这可能有点过头了。重复的迭代和 count++ 看起来还不错。

【讨论】:

    【解决方案2】:

    您的代码与您的 cmets 不匹配。

    public static double[] ln(double[] z) {
        // returns  an array that consists of the natural logarithm of the values in array z
        double[] r = new double[z.length];
        for(int i=0;i<z.length;i++) r[i] = Math.log(z[i]);
        return r;
    }
    
    public static double[] inverse(double[] z) {
        //  returns  an array that consists of the inverse of the values in array z
        double[] r = new double[z.length];
        for(int i=0;i<z.length;i++) r[i] = 1/z[i];
        return r;
    }
    

    您可以使用策略模式来使循环通用,但这有三个缺点。

    • 代码更难阅读。
    • 代码更长更复杂。
    • 执行速度要慢很多倍。

    Math.pow(x, -1) 比 1/x 贵很多倍,并且会产生更多舍入误差。

    【讨论】:

    • 你的实现非常好。谢谢
    • 因为它返回一个副本,所以它不会改变原件,使您的编码更简单。这是返回另一个数组的点的一部分。如果您只想更改原始值,则不需要返回值。
    【解决方案3】:

    这是一种重用循环的方法。这是一个例子:

    public interface MathOperation {
       public double f(double value);
    }
    

    public class Invert implements MathOperation {
       public double f(double value) {
         return Math.pow(value, -1);
       }
    }
    

    public class Log implements MathOperation {
       public double f(double value) {    
         return Math.log(value);
       }
    }
    

    private static void calculateOnArray(double[] doubles, MathOperation operation) {
      int count = 0;
    
      for (double i : doubles){
        doubles[count] = operation.f(i);
        count += 1;
      }
    }
    
    public static double[] ln(double[] z) {
      calculateOnArray(z, new Log());
      return z;
    }
    
    public static double[] inverse(double[] z) {
      calculateOnArray(z, new Invert());
      return z;
    }
    

    注意 - 这不是 command 模式,但也不是 real strategy pattern 的实现。它接近于策略,但为了避免进一步的反对,我保持模式未命名 ;-)

    【讨论】:

    • 这是策略模式。您正在使用传入的参数,而不是实现自己的状态。如果您将z 传递给MathOperation 的构造函数,这将是命令模式。无论如何,很好的例子。虽然我可能会将 new Log()new Invert() 分配为静态字段,而不是每次都重新创建它们。
    • @BalusC - 愚蠢的我,当然,你是对的!我会改一下文字。
    【解决方案4】:

    没有那么多的重用,但这里有两点 - 首先,永远不要使用 i 作为数组值,它是迭代器的约定,如果你这样做你会迷惑人们。其次,您最好在此处使用 for 循环而不是 for each 循环,这将带走您的手动“计数”变量。

    我还会在返回之前创建数组的副本,您实际上是在更改此处传递的参数,如果其他人看到您的方法签名可能不是他们所期望的。我希望一个具有类似返回类型的方法保持原始参数不变(因为返回我已经引用过的相同值是没有意义的!)

    【讨论】:

      【解决方案5】:

      在支持闭包、lambda 表达式或可以传递的块(例如 Ruby)的语言中,您可以简洁地执行此操作。在 Java 中,您可以通过定义一个接口来模拟这一点,该接口带有一个要作为回调调用的方法。在您使用它的地方,您使用该接口创建一个匿名类。看起来有点麻烦:

      public interface Calculation {
          double calculate(double x);
      }
      
      public static double[] calcArray(double[] z, Calculcation calc) {
          int count = 0;
      
          for (double i : z){
              z[count] = calc.calculate(i);
              count += 1;
          }
          return z;
      }
      
      public static double[] ln(double[] z) {
          return calcArray(z, new Calculation() {
              double calculate(double x) {
                  return Math.log(x);
              }
          });
      }
      
      public static double[] inverse(double[] z) {
          return calcArray(z, new Calculation() {
              double calculate(double x) {
                  return Math.pow(x, -1);
              }
          });
      }
      

      【讨论】:

      • 顺便说一句 - Java 8 中的语言支持(不仅仅是单方法接口)中的 Java 闭包即将到来,但这还需要一段时间(2012 年!)
      【解决方案6】:

      如果您使用函数式编程,您可以重复使用它。有很多图书馆为你写了很多这样的东西。对于这个例子,我只使用了 Guava (http://code.google.com/p/guava-libraries/),但函数式 Java (http://functionaljava.org/) 等库也可以正常工作。

      函数式编程的主要优点之一是能够抽象出算法,以便您可以在任何需要的地方重用该算法。对于您的情况,您的算法本质上是这样的:

      1. 对于列表中的每个元素
      2. 对该元素执行 x 操作
      3. 返回新列表

      诀窍是能够传入一个新的操作来代替“x”,这取决于你想要做什么。在函数式编程的世界中,我们创建了一个称为“函子”的对象,这实际上只是将函数封装在对象中的一种方式。 (如果 Java 有闭包,这会更容易,但这就是我们所拥有的。)

      无论如何,这里有一些代码可以满足你的需求:

      类:逆

      
      import com.google.common.base.Function;
      
      public class Inverse implements Function
      {
          @Override
          public Double apply(Double arg0)
          {
              return Math.pow(arg0, -1);
          }
      }
      

      类:对数

      
      import com.google.common.base.Function;
      
      public class Logarithm implements Function
      {
          @Override
          public Double apply(Double arg0)
          {
              return Math.log(arg0);
          }
      }
      

      类:驱动程序

      
      import static org.junit.Assert.*;
      
      import java.util.ArrayList;
      import java.util.Arrays;
      import java.util.List;
      
      import org.junit.Test;
      
      import com.google.common.collect.Collections2;
      
      public class Driver
      {
          @Test
          public void testInverse()
          {
              List initialValues = Arrays.asList(new Double[] {1.0, 2.0, 3.0});
      
              List logValues = new ArrayList(Collections2.transform(initialValues, new Inverse()));
      
              assertEquals(3, logValues.size());
      
              assertEquals(Double.valueOf(1.0), logValues.get(0), 0.01);
              assertEquals(Double.valueOf(0.5), logValues.get(1), 0.01);
              assertEquals(Double.valueOf(0.333), logValues.get(2), 0.01);
      
          }
      }
      

      Driver 类只包含一个测试用例,但它确实说明了上面定义的 Functor 的用法。您会注意到在上面的 testInverse 方法中使用了一个名为 Collections2.transform 的方法。您可以在此处找到此方法的文档:

      http://guava-libraries.googlecode.com/svn/trunk/javadoc/com/google/common/collect/Collections2.html#transform%28java.util.Collection,%20com.google.common.base.Function%29

      本质上,此方法的作用是抽象出遍历列表并将某些函数应用于该列表的每个元素的算法 - 您所要做的就是应用要执行的函数。因此,在这种情况下,我将一个 Doubles 列表和一个仿函数(Inverse)传递给该方法。它给了我一个新列表,其中包含初始列表中每个值的倒数。如果您希望记录每个值,请将不同的函子传递给 transform 方法。

      这种类型的编程确实有一点学习曲线,但对于您想要执行的代码重用类型来说,它绝对很棒。通过利用现有的库(如 Guava),您可以使您的代码更容易编写。请注意,我编写的代码实际上比您编写的代码要容易一些,因为在执行数学函数时我不必处理列表/数组——我只在一个元素上执行它;转换函数负责将该函数应用于整个列表。

      【讨论】:

        【解决方案7】:

        是的,正如这里的几个示例所示。但是,如果这是真正的问题而不是极端简化,那么在我看来,创建三个具有继承和重载的类以节省编写一个 FOR 语句的时间似乎是朝着错误的方向前进。您正在编写二十行代码以节省重复一行代码。有什么收获?

        如果在现实生活中,由于某种原因,遍历数据的过程要复杂得多——如果它不仅仅是循环遍历一个数组,而且我不知道,还有一些在数据库中查找内容的复杂过程以及提交 Web 服务请求并执行一个充满复杂计算的页面以找到下一个元素——那么我的答案会有所不同。

        【讨论】:

          猜你喜欢
          • 2011-02-03
          • 1970-01-01
          • 1970-01-01
          • 2016-07-08
          • 2012-01-12
          • 2013-07-24
          • 2013-02-21
          • 2015-01-28
          • 2012-01-13
          相关资源
          最近更新 更多