Java编程面试测试题

47

以下是一份用在面试中的编程测试题。我认为这个问题有一个非常奇怪的非面向对象角度,想知道为什么有人会从这个角度来考虑构造函数。作为经验丰富的Java程序员,我立刻对编写这段代码的个人能力和奇怪的问题视角产生了质疑。

我发现在面试中出现这种脱离上下文的问题很令人不安。我希望得到其他经验丰富的OO Java程序员的反馈。

请完成Solver构造函数,使调用solveAll返回一个包括传入参数的平方根和倒数在内的2个值的列表。

public interface MathFunction {
    double calculate(double x);
}

public class Solver {

    private List<MathFunction> functionList;

    public Solver() { 

        //Complete here

    }

    public List<Double> solveAll(double x) {
        List<Double> result = new ArrayList<Double>();
        for (MathFunction function : this.functionList) {
            result.add(new Double(function.calculate(x)));
        }

        return result;
    }
} 

2
我不认为这很奇怪或者不符合面向对象的编程思想。但是,我经验不够,期待得到一些答案。 ;) - brimborium
28
我们是否需要一个新的标签(目前我正在使用iPhone进行面试)来与“作业”竞争? :-) - Duncan Jones
3
我认为这个问题的重点是确保受访者不会在构造函数内尝试调用solveAll。他们只是想看到你是否理解代码,以及你是否能够找出需要创建两个MathFunction实现并将它们存储在列表中,以便稍后由solveAll方法使用的方法。 - Alderath
4
为什么这个问题说需要传递一个“整数”作为参数? - poitroae
2
这是一个警告,你的潜在雇主可能不会对这样的代码感到惊讶,并且这种代码在他们要让你维护的代码库中可能非常普遍。赶紧逃跑,趁还来得及! - choy
显示剩余2条评论
9个回答

38

这是通过使用最简单的方法来测试你的设计模式。我认为这可能是策略模式(或其他行为模式)。请参见以下链接:

http://en.wikipedia.org/wiki/Strategy_pattern

http://en.wikipedia.org/wiki/Behavioral_pattern

如果您要参加Java面试,您应该能够确定他们在暗示什么设计模式,并且这可以防止您过于不安!

为了回答这个问题,请创建两个实现MathFunction所需功能的类,然后创建两个实例并将它们存储在functionList中。

这里的重点不是“你是否能以这种奇怪的方式进行计算”,而是“你是否能识别设计模式”。


你只是给了他答案!;-) - Duncan Jones
不要啊 Joe!把 Strategy 放回去。它更像是一种策略而不是模板。 - jeff
@jeff - 我把策略放回去了,但它看起来像模板?我不知道,已经有几年没想过它们的名字了。 - Joe
@DuncanJones - 我猜面试已经结束了! - Joe
我曾经面试过一个人,问了他一个问题,但他没有完全理解。不解释这个问题是很残忍的,否则人们就无法进步。 - Joe
我不明白你在哪里看到了模板方法模式。 - gontard

31

我认为这段代码虽然有些复杂,但是还是相当符合面向对象的编程思想,实际上它是策略模式(strategy pattern)的一个实例。生成答案列表的代码并不关心如何计算答案,两个方面的职责被分离开来,而且可以通过应用不同的计算策略来改变计算方式,而无需修改生成答案列表的代码。

为了使该类更加有用,应该将这些函数从外部传递进来(即依赖注入),而不是在构造函数中实例化。

我猜你已经知道这个答案了,但说出来也无妨...

public Solver() {
    functionList = new ArrayList<MathFunction>();

    functionList.add(new MathFunction() {

        @Override
        public double calculate(double x) {
            return 1d/x;
        }
    });

    functionList.add(new MathFunction() {

        @Override
        public double calculate(double x) {
            return Math.sqrt(x);
        }
    });
}

一个程序员可能已经花费了过去的10年时间来使用和编码工厂模式、策略模式、模板模式,并实现了许多内部类和接口,出于各种原因。编程问题并不是为了找出这些,特别是如果它是一个计时问题。我需要一个理性的观点和上下文,否则这将是一个愚蠢的练习,让每个人都在猜测。 - user916115
如果我在面试中,我不会争论“这很愚蠢,在现实生活中永远不会出现”,我会继续做下去,然后事后再讨论。 - Joe
如果我在考试中出了这样的问题,我会寻找更好答案的认知和阐述,以及为什么这种方式比其他方式更好的解释。我总是觉得对问题和答案进行_讨论_比面试者回答的答案更有用。 - boatcoder
自从我上次接触Java代码以来已经有一段时间了,现在你能像这样实例化一个接口吗?如果可以的话那真的太酷了! - Sebastian Blask

6

在我看来,这确实是一种奇怪的方法。名称 Solver 是通用的,它不应该默认实现特定的操作。然而,也许这是面试的一部分?第一部分:简单地满足请求。第二部分:说这样做很奇怪。

我认为一个更好的方法是拥有一个 addMathFunction(MathFunction mf) 方法。如果需要,可以创建扩展 Solver 类并在构造函数中添加 MathFunctions 的子类。


3

好的,我已经编写了解决方案来回答我的问题。我的直觉是构造函数中不应该有任何内容,这似乎是正确的。functionList不是静态的,因此您需要一个实例来初始化它。它指定为整数,因此我将其舍入为整数。反函数在任何方面都不是高级数学。

import java.util.ArrayList;
import java.util.List;
import java.lang.Math;

public class Solver {

    private List<MathFunction> functionList = new ArrayList<MathFunction>();;

    public Solver() { 

// Complete here

    }

    public void initFunctionList() {

        MathFunction functionSquareRoot = new MathFunction(){

            @Override
            public double calculate(double x) {
                return (x<0 ? 0: Math.sqrt(x));  // maybe we need throw an exception here for negative numbers, but we'll just set it to 0
            }};

        MathFunction functionInverse = new MathFunction(){

            @Override
            public double calculate(double x) {
                return (x!=0.0 ? 1/x : 0);
            }

        };

        functionList.add(functionSquareRoot);
        functionList.add(functionInverse);

    }

    public List<Double> solveAll(double x) {
        List<Double> result = new ArrayList<Double>();

        for (MathFunction function : this.functionList) {
            result.add(new Double(function.calculate(x)));
        }

        return result;
    }

}


public interface MathFunction {
     double calculate(double x);
}


public class TestSolver {

    /**
     * @param args
     */
    public static void main(String[] args) {
        Solver s = new Solver();
        s.initFunctionList();
        System.out.println(s.solveAll(16.0));

    }

}

我误导了自己,构造函数可以是:
public Solver() { 

// Complete here
        MathFunction functionSquareRoot = new MathFunction(){

            @Override
            public double calculate(double x) {
                return (x<0 ? 0: Math.sqrt(x));  // maybe we need throw an exception here for negative numbers, but we'll just set it to 0
            }};

        MathFunction functionInverse = new MathFunction(){

            @Override
            public double calculate(double x) {
                return (x!=0.0 ? 1/x : 0);
            }

        };

        functionList.add(functionSquareRoot);
        functionList.add(functionInverse);

    }

为什么要四舍五入?只是想知道是否有特别的原因。 - MartyE
@bestsss 看起来你不想在此处四舍五入。如果需要的话,你应该将 double calculate(double x); 改为 double calculate(int x);,然后将 x 转换为 double - MartyE
只是询问,使用这种方法,您不会冒险将int除以long得到的结果舍入为0,从而导致1/x1出现问题吗?我这里没有Java编译器进行测试,所以我确实在询问。 - MartyE
@user916115 同样地,负数的倒数仍然是合法的 1/x - MartyE
@MartyE,long/long是整数除法,即1/3==0(余数为2)。结果被隐式转换为double以返回函数的结果。再次强调,如果声明为整数,则应该是整数除法。 - bestsss
显示剩余3条评论

3

虽然我认为这可能不是最好的方法,也不是最符合面向对象的方法,但我认为这个练习的目的是看看你对继承、接口和匿名内部类的理解程度。这是我唯一能想到的。


正如@Joe在他的答案中指出的那样,这个问题还想要看看你是否能够识别设计模式并正确地实现它。 - Jason Carter
请求完成构造函数以便方法 x 返回 y 一开始让我困惑不解。经过分析,您可以了解可以做什么。使用 MathFunction 接口通过内部类实现 2 个函数。它类似于策略模式/模板模式/工厂模式,但又不完全相同。 - user916115

3
我认为他们希望你在函数列表中添加两个项目。每个项目都将实现MathFunction接口,一个用于求平方根,另一个用于求倒数。问题在于细节:
1- 你有一个函数返回2个值,因为它做了两件不同的事情,这是不好的。
2- 如果你想要这个“万能”类,那么将MathFunctions作为参数传递进来会很有趣,这样你就可以进行任何类型的MathFunctions,并且MathFunctions是可参数化的。

3

这是我的解决方案。这是一个工厂类的简单示例。

public Solver() { 
    functionList = new ArrayList<MathFunction>();
    MathFunction sqrt = new MathFunction() {
        @Override
        public double calculate(double x) {
            return Math.sqrt(x);
        }

    };
    functionList.add(sqrt);
    MathFunction inverse = new MathFunction() {
        @Override
        public double calculate(double x) {
            return 1.0D / x;
        }

    };
    functionList.add(inverse);
}

这个问题展示了两个方面:
  • 编程人员是否理解数学术语,例如反函数。
  • 编程人员是否理解接口或类的实例可以存储在列表中,并稍后迭代。

1
又一个没读问题的人...楼主肯定知道怎么做。 - Martijn Courteaux
1
实际上,至少应该保留一个“解决方案”在这里。因为可能有一些人不知道答案,他们可能会在这里找到一个答案,从而更好地理解这个问题的“真正”答案... - brimborium
2
工厂方法模式在哪里? - gontard
@brimborium 可能应该在问题中提供一个解决方案。 - gontard
3
抱歉,但我没有看到工厂模式的使用。 "与其他创建型模式一样,它处理在不指定将要创建的对象的确切类的情况下创建对象(产品)的问题。”您只是实例化一个匿名类并使用它。在我看来,策略模式更适合这里。 - JSBach
显示剩余2条评论

2

这个有些牵强,对我来说更接近于装饰器模式。不确定在面试中应该说些什么,但以下是我的代码:

package math;

import java.util.ArrayList;
import java.util.List;

public class DecoratorMath 
{

    interface MathFunction 
    {
        double calculate(double x);
    }

    public static void main(String[] args) 
    {
        DecoratorMath decoratorMath =  new DecoratorMath();
        decoratorMath.go();
    }

    public void go() 
    {
        Solver solver = new Solver();
        decorate(solver);
        List<Double> results = solver.solveAll(02);
        for (Double d :results) 
        {
            System.out.println(d);
        }
    }

    public void decorate(Solver solver)
    {
        solver.addFunction(new MathFunction() 
        {
            @Override
            public double calculate(double x) 
            {
                return Math.sqrt(x);
            }
        });

        solver.addFunction(new MathFunction() 
        {
            @Override
            public double calculate(double x) 
            {
                return 1d/x;
            }
        });
    }

    class Solver
    {
        private List<MathFunction> mathFunctions = new ArrayList<MathFunction>();

        public void addFunction(MathFunction mathFunction)
        {
            mathFunctions.add(mathFunction);
        }

        public List<Double> solveAll(double x) 
        {
            List<Double> result = new ArrayList<Double>();
            for (MathFunction function : mathFunctions) 
            {
                result.add(new Double(function.calculate(x)));
            }
            return result;
        }
    }
}

0

在构造函数中做所有这些事情只是不好的实践。无论如何,这是我的全能解决方案。

import java.util.*;
import java.math.*;

//sqrt / inverse

public class Solver{

  private List<MathFunction> functionList;

  public interface MathFunction{
     double calculate(double x);
  }

  class X implements MathFunction {
    public double calculate(double x) {
      return Math.sqrt(x); 
  } 
  }

    class Y implements MathFunction {
    public double calculate(double y) {
      return 1/y; 
  } 
  }



  public Solver(){
  //here
    functionList = new ArrayList<MathFunction>();

    MathFunction f =  (MathFunction) new X();
    functionList.add(f);  

    MathFunction f2 =  (MathFunction) new Y();
    functionList.add(f2);

  }


  public List<Double> solveAll(double x){ 

  List<Double> result=new ArrayList<Double>();

    for (MathFunction function : this.functionList){

      result.add(new Double(function.calculate(x)));

    }

    return result;

  }

public static void main(String... args) {

    System.out.println("result="+new Solver().solveAll(123));

}

}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接