面向对象编程 —— java完成函数求导

率先声喜宝(Hipp)点,本文首要介绍的是面向对象(OO)的合计,顺便谈下函数式编程,而不是教您怎么着准确地、科学地用java求出函数在少数的导数。

 

一、引子

 

def d(f) :
    def calc(x) :
        dx = 0.000001  # 表示无穷小的Δx
        return (f(x+dx) - f(x)) / dx  # 计算斜率。注意,此处引用了外层作用域的变量 f
    return calc  # 此处用函数作为返回值(也就是函数 f 的导数)
# 计算二次函数 f(x) = x2 + x + 1的导数
f = lambda x : x**2 + x + 1  # 先把二次函数用代码表达出来
f1 = d(f)# 这个f1 就是 f 的一阶导数啦。注意,导数依然是个函数
# 计算x=3的斜率
f1(3)
# 二阶导数
f2 = d(f1)

率先,直接上一段python代码,请我们先分析下方面代码是用什么样方式求导的。请不要被那段代码吓到,你无需纠结它的语法,只要知道它的求导思路。

如上代码引用自《缘何笔者推荐 Python[4]:作为函数式编程语言的
Python
》,那篇博客是促使自个儿写篇文章的主要性缘由。

博主说“假设不用 FP,改用 OOP,上述需要该如何兑现?我觉得吧,用 OOP
来求导,那代码写起来多半是又丑又臭。”

作者将信将疑,于是就用面向对象的java试了试,最终也没多少代码。尽管用java8或之后版本,代码更少。

请我们想想2个题材,怎么着用面向对象的思路改写那一个程序。请先好好考虑,尝试编个程序再持续往下看。

设想到看到这一个标题进来的同桌大多是学过java的,下边小编用java,用面向对象的思绪一步步剖析那几个难题。

 

二、求导

 

文章初步小编已近申明过了,本文不是来研讨数学的,求导只是本人用来证实面向对象的七个例证。

比方你已经忘了起来那段代码的求导思路,请回头再看看,看看用python是哪些求导的。

相信您借使听旁人说过求导,肯定一眼就来看开首那段代码是用导数概念求导的。

C语言 1

代码中只是将无穷小Δx粗略地算做一个较小的值0.000001。

 

三、最初的想法

 

//自定义函数
public class Function {
    //函数:f(x) = 3x^3 + 2x^2 + x + 1
    public double f(double x) {
        return 3 * x * x * x + 2 * x * x + x + 1;
    }
}

//一元函数导函数
public class DerivedFunction {
    //表示无穷小的Δx
    private static final double DELTA_X = 0.000001;
    //待求导的函数
    private Function function;

    public DerivedFunction(Function function) {
        this.function = function;
    }

    /**
     * 获取function在点x处的导数
     * @param x 待求导的点
     * @return 导数
     */
    public double get(double x) {
        return (function.f(x + DELTA_X) - function.f(x)) / DELTA_X;
    }
}

public class Main {
    public static void main(String[] args) {
        //一阶导函数
        DerivedFunction derivative = new DerivedFunction(new Function());
        //打印函数在x=2处的一阶导数
        System.out.println(derivative.get(2));
    }
}

先声圣元点,考虑到博客篇幅,我利用了不僧不俗的代码注释,希望大家不用被自个儿误导。

本人想只要我们可以想想了,应该至少会想到那步吧。代码作者就不表明了,作者只是用java改写了作品先河的那段python代码,做了二个简约的翻译工作。再请大家着想下以上代码的题材。

刚早先,作者寻思这么些题材想开的是建2个名为Function的类,类中有3个名为f的法子。但考虑到要每趟须求新的函数导数时就得改变那一个f方法的兑现,分明不便民增加,那违背了开闭原则

预计有的同学没听过那几个词,作者就解释下:”对象(类,模块,函数等)应对扩展开放,但对修改封闭C语言,“。

于是乎小编就没继续写下去,但为了让我们直观的感想到那些想法,我写那篇博客时就贯彻了须臾间以此想法。

请我们想想一下什么重构代码以消除伸张性难点。

 

四、初阶的想法

 

揣测学过面向对象的同学会想到把Function类改成接口或抽象类,未来每回添加新的函数时一旦重写那一个接口或抽象类中的f方法,那就是面向接口编程,符合依靠反转原则,上边的代码就是这么做的。

再声称一点,考虑到篇幅的标题,前边的代码小编会省去与此前代码重复的注脚,有不明白的地点还请看看上多个设法中的代码。

//一元函数
public interface Function {
    double f(double x);
}

//自定义的函数
public class MyFunction implements Function {
    @Override
    public double f(double x) {
        return 3 * x * x * x + 2 * x * x + x + 1;
    }
}

public class DerivedFunction {
    private static final double DELTA_X = 0.000001;
    private Function function;

    public DerivedFunction(Function function) {
        this.function = function;
    }

    public double get(double x) {
        return (function.f(x + DELTA_X) - function.f(x)) / DELTA_X;
    }
}

public class Main {
    public static void main(String[] args) {
        //一阶导函数:f'(x) = 9x^2 + 4x + 1
        DerivedFunction derivative = new DerivedFunction(new MyFunction());
        System.out.println(derivative.get(2));
    }
}

自个儿想认真看的同窗大概会意识两个难题,我的翻译做的还不做到,开头那段python代码仍可以轻松地求出二阶导函数(导数的导数),而小编的代码却分外。

事实上只要稍加修改上述代码的贰个地方就可以轻松完结求二阶导,请再想想片刻。

 

五、后来的想法

 

当小编写出地点的代码时,小编深感完全能够矢口否认“用 OOP
来求导,那代码写起来多半是又丑又臭”的视角。但还无法求二阶导,作者有点不甘心。

于是乎小编就动笔,列了一晃用定义求一阶导和求二阶导的姿势,想了想五个姿态的分化与关系,突然想到导函数也是函数。

DerivedFunction的get方法和Function的f方法的参数和重临值一样,DerivedFunction可以达成Function接口,于是爆发了下边的代码。

public interface Function {
    double f(double x);
}

public class DerivedFunction implements Function {
    private static final double DELTA_X = 0.000001;
    private Function function;

    public DerivedFunction(Function function) {
        this.function = function;
    }

    @Override
    public double f(double x) {
        return (function.f(x + DELTA_X) - function.f(x)) / DELTA_X;
    }
}

public class Main {
    public static void main(String[] args) {
        Function f1 = new DerivedFunction(new Function() {
            @Override
            public double f(double x) {
                return 3 * x * x * x + 2 * x * x + x + 1;
            }
        });
        System.out.println(f1.f(2));
        //二阶导函数:f''(x) = 18x + 4
        Function f2 = new DerivedFunction(f1);
        //打印函数f(x) = 3x^3 + 2x^2 + x + 1在x=2处的二阶导数
        System.out.println(f2.f(2));
    }
}

设想到有的同学没学过java8或上述版本,以上代码没有运用java8函数式编程的新特征。 

若果您接触过java8,请考虑怎么着改写以上代码,使其更精简。

 

六、最终的想法

 

public class DerivedFunction implements Function<Double, Double> {
    private static final double DELTA_X = 0.000001;
    private Function<Double, Double> function;

    public DerivedFunction(Function<Double, Double> function) {
        this.function = function;
    }

    @Override
    public Double apply(Double x) {
        return (function.apply(x + DELTA_X) - function.apply(x)) / DELTA_X;
    }
}

public class Main {
    public static void main(String[] args) {
        //打印函数在x=2处的二阶导
        System.out.println(new DerivedFunction(new DerivedFunction(x -> 3 * x * x * x + 2 * x * x + x + 1)).apply(2.0));
    }
}

事先多少个想法为了增加Function接口,使用了外部类、匿名类的形式,其实也足以用其中类。而那在此地,小编用了lambda表明式,是否更不难了。

那里用的Function接口用的是jdk自带的,大家不须求团结定义了。因为那是三个函数式接口,我们得以用lambda方便地落实。后来发现,其实那里用UnaryOperator其一接口更方便。

未来大家有没有觉察,用java、用OOP也得以拾壹分简洁地贯彻求导,并不比开首的那段python代码麻烦很多。

 

七、编程范式

 

在我看来,编程范式简短来说就是编程的一种情势,一种风格。

本人先介绍其中的几个,你大概就清楚它的含义了。

 

7.1 面向对象程序设计(OOP)

观看那里的同窗应该对面向对象有了更直观的认识。在面向对象编程中,万物皆对象,抽象出类的定义。基特性格是包裹、继承、多态,认识不深的同桌能够再去本身事先的代码中找找那多个特点。

自家前边还介绍了面向对象的多少个条件:开闭原则依傍反转原则。其余还有纯净任务规范里氏替换原则接口隔离原则。这是面向对象的5个基本原则,合称SOLID)。

 

7.2 函数编程语言(FP)

正文先导那段代码用的就是python函数式编程的语法,后来本人又用java8函数式编程的语法翻译了那段代码。

信任你已经直观地感受到它的简练,以函数为主干,几行代码就化解了求导的难点。

 

7.3 进度式编程(Procedural programming)

大约学过编程都学过C,C语言就是一种进度式编程语言。在作者看来,进度式编程几乎就是为了成功三个必要,像记流水帐一样,平铺直叙下去。 

       

八、结尾

 

出于本身初学java,方今不得不想到这么多。若是我们有更好的想法依旧觉的自身上边说的卓殊,欢迎评论,望各位不吝赐教。

那是自小编的首先篇技术博客,但愿自身说知道了面向对象。假设对您有帮扶,请点个赞可能评论下,给小编点持续创作的引力。