面向对象编程 —— java实现函数求导
首先声明一点,本文主要介绍的是面向对象(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或以后版本,代码更少。
请大家思考一个问题,如何用面向对象的思路改写这个程序。请先好好思考,尝试编个程序再继续往下看。
考虑到看到这个标题进来的同学大多是学过java的,下面我用java,用面向对象的思路一步步分析这个问题。
二、求导
文章开头我已近声明过了,本文不是来讨论数学的,求导只是我用来说明面向对象的一个例子。
如果你已经忘了开头那段代码的求导思路,请回头再看看,看看用python是如何求导的。
相信你只要听说过求导,肯定一眼就看出开头那段代码是用导数定义求导的。

代码中只是将无穷小Δ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代码,做了一个简单的翻译工作。再请大家考虑下以上代码的问题。
刚开始,我思考这个问题想到的是建一个名为Function的类,类中有一个名为f的方法。但考虑到要每次要求新的函数导数时就得更改这个f方法的实现,明显不利于扩展,这违背了开闭原则。
估计有的同学没听过这个词,我就解释下:”对象(类,模块,函数等)应对扩展开放,但对修改封闭“。
于是我就没继续写下去,但为了让大家直观的感受到这个想法,我写这篇博客时就实现了一下这个想法。
请大家思考一下如何重构代码以解决扩展性问题。
四、初步的想法
估计学过面向对象的同学会想到把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,目前只能想到这么多。如果大家有更好的想法或者觉的我上面说的有问题,欢迎评论,望各位不吝赐教。
这是我的第一篇技术博客,但愿我说清楚了面向对象。如果对你有帮助,请点个赞或者评论下,给我点继续创作的动力。
面向对象编程 —— java实现函数求导的更多相关文章
- 面向对象第一单元总结:Java实现表达式求导
面向对象第一单元总结:Java实现表达式求导 题目要求 输入一个表达式:包含x,x**2,sin(),cos(),等形式,对x求导并输出结果 例:\(x+x**2+-2*x**2*(sin(x**2+ ...
- sigmod函数求导
sigmod函数: \[f(z)=\frac{1}{1+e^{-z}} \] 求导: \[\frac{\partial f(z)}{\partial z}=\frac{-1*-1*e^{-z}}{(1 ...
- JAVA实现表达式求导运算的分析总结
1第一次作业 1.1题目描述 对形如4*x+x^2+x的多项式求导. 1.2类图 1.3度量分析 在完成第一次作业时,我的写法没有特别的"面向对象".唯一封装起来的是Node,代表 ...
- LY.JAVA面向对象编程.包的概述、导包
2018-07-18 08:46:57 导包:
- hdu 5105 求函数极值 函数求导/三分法
http://acm.hdu.edu.cn/showproblem.php?pid=5105 给定a,b,c,d,l,r,表示有一个函数f(x)=|a∗x3+b∗x2+c∗x+d|(L≤x≤R),求函 ...
- javascript面向对象编程笔记(函数之闭包)
3 函数 3.5 闭包(closures) 3.5.1 作用域链 与很多程序设计语言不同,javascript不存在大括号级的作用域,但它有函数作用域,即在函数内定义的变量在函数外是不可见的.但如果该 ...
- JavaScript面向对象编程指南(三) 函数
第3章 函数 3.1 什么是函数 函数:本质是一种代码的分组形式.函数的声明如下: <script type="text/javascript"> /*函数的声明组成: ...
- javascript面向对象编程笔记(函数)
第三章 函数 3.1 什么是函数 一般来说,函数声明通常由以下几部分组成: function子句 函数名称 函数所需参数 函数体 return子句.如果某个函数没有显示的返回值,默认它的返回值为und ...
- 【机器学习】sigmoid函数求导 手写过程
随机推荐
- uva 10118,记忆化搜索
这个题debug了长达3个小时,acm我不能放弃,我又回来了的第一题! 一开始思路正确,写法不行,结果越改越乱 看了网上某神的代码,学习了一下 coding+debug:4小时左右,记忆化搜索+dp类 ...
- MySQL复制之理论篇
一.MySQL复制概述 MySQL支持两种复制方式:基于行的复制和基于语句的复制(逻辑复制).这两种方式都是通过在主库上记录 二进制日志.在备库重放日志的方式来实现异步的数据复制,其工作原理如下图: ...
- Msys2配置总结
一.MSYS2的MirrorList配置 1.修改msys2安装目录下的/etc/pacman.d文件夹里面的3个mirrorlist.*文件 [mirrorlist.mingw32] #中国科学技术 ...
- Connections between cities
Connections between cities Time Limit: 10000/5000 MS (Java/Others) Memory Limit: 32768/32768 K (Java ...
- 在SQL Server中实现关系模型
使用SQL Server的Transact-SQL(T-SQL)方言,此楼梯将为您提供如何使用SQL Server表中的数据的基本了解. DML是数据操作语言,是处理数据的语言的一个方面.它包括SEL ...
- 入我新美大的Java后台开发面试题总结
静儿最近在总结一些面试题,那是因为做什么事情都要认真.面试也一样,静儿作为新美大金融部门的面试官,负责任的告诉大家,下面的问题回答不上来,面试是过不了的.不过以下绝不是原题,你会发现自己实力不过硬,最 ...
- myeclipse自动保存修改代码
当你修改过代码后,myeclipse往往要你手动的保存代码才能运行这个修改后的代码,要是不保存就会一直运行修改前的代码.只要修改myeclipse中这两项,就可以让它编译运行修改后的代码: Windo ...
- 【每天半小时学框架】——React.js的模板语法与组件概念
[重点提前说:组件化与虚拟DOM是React.js的核心理念!] 先抛出一个论题:在React.js中,JSX语法提倡将 HTML 和 CSS 全都写入到JavaScrip ...
- Spring框架学习之注解配置与AOP思想
上篇我们介绍了Spring中有关高级依赖关系配置的内容,也可以调用任意方法的返回值作为属性注入的值,它解决了Spring配置文件的动态性不足的缺点.而本篇,我们将介绍Spring的又一大核心 ...
- linux-mv
linux-mv 主要用于文件或者目录的移动或者改动, 命令参数 -i:ruguo目标文件或者目录存在,提示是否覆盖目标文件或目录 -f:无论目标文件是否存在,直接覆盖,不提示, 有好多参数,自己可以 ...