lambda菜鸟教程(机器学习的常用方法有哪些)
本文目录
机器学习的常用方法有哪些
梯度下降是非常常用的优化算法。作为机器学习的基础知识,这是一个必须要掌握的算法。借助本文,让我们来一起详细了解一下这个算法。
前言
本文的代码可以到我的Github上获取:
***隐藏网址***
本文的算法示例通过Python语言实现,在实现中使用到了numpy和matplotlib。如果你不熟悉这两个工具,请自行在网上搜索教程。
关于优化
大多数学习算法都涉及某种形式的优化。优化指的是改变x以最小化或者最大化某个函数的任务。
我们通常以最小化指代大多数最优化问题。最大化可经由最小化来实现。
我们把要最小化或最大化的函数成为目标函数(objective function)或准则(criterion)。
我们通常使用一个上标*表示最小化或最大化函数的x值,记做这样:
优化本身是一个非常大的话题。如果有兴趣,可以通过《数值优化》和《运筹学》的书籍进行学习。
模型与假设函数
所有的模型都是错误的,但其中有些是有用的。– George Edward Pelham Box
模型是我们对要分析的数据的一种假设,它是为解决某个具体问题从数据中学习到的,因此它是机器学习最核心的概念。
针对一个问题,通常有大量的模型可以选择。
本文不会深入讨论这方面的内容,关于各种模型请参阅机器学习的相关书籍。本文仅以最简单的线性模型为基础来讨论梯度下降算法。
这里我们先介绍一下在监督学习(supervised learning)中常见的三个符号:
m,描述训练样本的数量
x,描述输入变量或特征
y,描述输出变量或者叫目标值
- 请注意,一个样本可能有很多的特征,因此x和y通常是一个向量。不过在刚开始学习的时候,为了便于理解,你可以暂时理解为这就是一个具体的数值。
- 代价函数也叫损失函数。
- 不同的模型可能会用不同的损失函数。例如,logistic回归的假设函数是这样的:。其代价函数是这样的:
对于一个函数,怎么确定下行的方向?
每一步该往前走多远?
有没有可能停留在半山腰的平台上?
- 这里的下标i表示第i个参数。 上标k指的是第k步的计算结果,而非k次方。在能够理解的基础上,下文的公式中将省略上标k。
收敛是指函数的变化率很小。具体选择多少合适需要根据具体的项目来确定。在演示项目中我们可以选择0.01或者0.001这样的值。不同的值将影响算法的迭代次数,因为在梯度下降的最后,我们会越来越接近平坦的地方,这个时候函数的变化率也越来越小。如果选择一个很小的值,将可能导致算法迭代次数暴增。
公式中的 称作步长,也称作学习率(learning rate)。它决定了每一步往前走多远,关于这个值我们会在下文中详细讲解。你可以暂时人为它是一个类似0.01或0.001的固定值。
在具体的项目,我们不会让算法无休止的运行下去,所以通常会设置一个迭代次数的最大上限。
我们随机选择了 都为10作为起点
设置最多迭代1000次
收敛的范围设为0.001
学习步长设为0.01
如果样本数量较小(例如小于等于2000),选择BGD即可。
如果样本数量很大,选择 来进行MBGD,例如:64,128,256,512。
- 《深度学习》一书中是这样描述的:“与其说是科学,这更像是一门艺术,我们应该谨慎地参考关于这个问题的大部分指导。”。
对于凸函数或者凹函数来说,不存在局部极值的问题。其局部极值一定是全局极值。
最近的一些研究表明,某些局部极值并没有想象中的那么糟糕,它们已经非常的接近全局极值所带来的结果了。
Wikipeida: Gradient descent
Sebastian Ruder: An overview of gradient descent optimization algorithms
吴恩达:机器学习
吴恩达:深度学习
Peter Flach:机器学习
李宏毅 - ML Lecture 3-1: Gradient Descent
PDF: 李宏毅 - Gradient Descent
Intro to optimization in deep learning: Gradient Descent
Intro to optimization in deep learning: Momentum, RMSProp and Adam
Stochastic Gradient Descent – Mini-batch and more
刘建平Pinard - 梯度下降(Gradient Descent)小结
多元函数的偏导数、方向导数、梯度以及微分之间的关系思考
梯度下降法的三种形式BGD、SGD以及MBGD
- ***隐藏网址***
训练集会包含很多的样本,我们用 表示其中第i个样本。
x是数据样本的特征,y是其目标值。例如,在预测房价的模型中,x是房子的各种信息,例如:面积,楼层,位置等等,y是房子的价格。在图像识别的任务中,x是图形的所有像素点数据,y是图像中包含的目标对象。
我们是希望寻找一个函数,将x映射到y,这个函数要足够的好,以至于能够预测对应的y。由于历史原因,这个函数叫做假设函数(hypothesis function)。
学习的过程如下图所示。即:首先根据已有的数据(称之为训练集)训练我们的算法模型,然后根据模型的假设函数来进行新数据的预测。
线性模型(linear model)正如其名称那样:是希望通过一个直线的形式来描述模式。线性模型的假设函数如下所示:
这个公式对于大家来说应该都是非常简单的。如果把它绘制出来,其实就是一条直线。
下图是一个具体的例子,即: 的图形:
在实际的机器学习工程中,你会拥有大量的数据。这些数据会来自于某个数据源。它们存储在csv文件中,或者以其他的形式打包。
但是本文作为演示使用,我们通过一些简单的代码自动生成了需要的数据。为了便于计算,演示的数据量也很小。
import numpy as np
max_x = 10data_size = 10theta_0 = 5theta_1 = 2
def get_data:x = np.linspace(1, max_x, data_size)noise = np.random.normal(0, 0.2, len(x))y = theta_0 + theta_1 * x + noisereturn x, y
这段代码很简单,我们生成了x范围是 整数的10条数据。对应的y是以线性模型的形式计算得到,其函数是:。现实中的数据常常受到各种因素的干扰,所以对于y我们故意加上了一些高斯噪声。因此最终的y值为比原先会有轻微的偏离。
最后我们的数据如下所示:
x = y =
我们可以把这10条数据绘制出来这样就有一个直观的了解了,如下图所示:
虽然演示用的数据是我们通过公式计算得到的。但在实际的工程中,模型的参数是需要我们通过数据学习到的。所以下文我们假设我们不知道这里线性模式的两个参数是什么,而是通过算法的形式求得。
最后再跟已知的参数进行对比以验证我们的算法是否正确。
有了上面的数据,我们可以尝试画一条直线来描述我们的模型。
例如,像下面这样画一条水平的直线:
很显然,这条水平线离数据太远了,非常的不匹配。
那我们可以再画一条斜线。
我们初次画的斜线可能也不贴切,它可能像下面这样:
最后我们通过不断尝试,找到了最终最合适的那条,如下所示:
梯度下降算法的计算过程,就和这种本能式的试探是类似的,它就是不停的迭代,一步步的接近最终的结果。
代价函数
上面我们尝试了几次通过一条直线来拟合(fitting)已有的数据。
二维平面上的一条直线可以通过两个参数唯一的确定,两个参数的确定也即模型的确定。那如何描述模型与数据的拟合程度呢?答案就是代价函数。
代价函数(cost function)描述了学习到的模型与实际结果的偏差程度。以上面的三幅图为例,最后一幅图中的红线相比第一条水平的绿线,其偏离程度(代价)应该是更小的。
很显然,我们希望我们的假设函数与数据尽可能的贴近,也就是说:希望代价函数的结果尽可能的小。这就涉及到结果的优化,而梯度下降就是寻找最小值的方法之一。
对于每一个样本,假设函数会依据计算出一个估算值,我们常常用来表示。即 。
很自然的,我们会想到,通过下面这个公式来描述我们的模型与实际值的偏差程度:
请注意, 是实际数据的值, 是我们的模型的估算值。前者对应了上图中的离散点的y坐标,后者对应了离散点在直线上投影点的y坐标。
每一条数据都会存在一个偏差值,而代价函数就是对所有样本的偏差求平均值,其计算公式如下所示:
当损失函数的结果越小,则意味着通过我们的假设函数估算出的结果与真实值越接近。这也就是为什么我们要最小化损失函数的原因。
借助上面这个公式,我们可以写一个函数来实现代价函数:
def cost_function(x, y, t0, t1):cost_sum = 0for i in range(len(x)):cost_item = np.power(t0 + t1 * x, 2)cost_sum += cost_itemreturn cost_sum / len(x)
这个函数的代码应该不用多做解释,它就是根据上面的完成计算。
我们可以尝试选取不同的 和 组合来计算代价函数的值,然后将结果绘制出来:
import numpy as npimport matplotlib.pyplot as plt
from matplotlib import cmfrom mpl_toolkits.mplot3d import Axes3D
theta_0 = 5theta_1 = 2
def draw_cost(x, y):fig = plt.figure(figsize=(10, 8))ax = fig.gca(projection=’3d’)scatter_count = 100radius = 1t0_range = np.linspace(theta_0 - radius, theta_0 + radius, scatter_count)t1_range = np.linspace(theta_1 - radius, theta_1 + radius, scatter_count)cost = np.zeros((len(t0_range), len(t1_range)))for a in range(len(t0_range)):for b in range(len(t1_range)):cost)t0, t1 = np.meshgrid(t0_range, t1_range)
ax.set_xlabel(’theta_0’)ax.set_ylabel(’theta_1’)ax.plot_surface(t0, t1, cost, cmap=cm.hsv)
在这段代码中,我们对 和 各自指定了一个范围进行100次的采样,然后以不同的 组合对来计算代价函数的值。
如果我们将所有点的代价函数值绘制出来,其结果如下图所示:
从这个图形中我们可以看出,当 越接近 时其结果(偏差)越小。相反,离得越远,结果越大。
直观解释
从上面这幅图中我们可以看出,代价函数在不同的位置结果大小不同。
从三维的角度来看,这就和地面的高低起伏一样。最高的地方就好像是山顶。
而我们的目标就是:从任意一点作为起点,能够快速寻找到一条路径并以此到达图形最低点(代价值最小)的位置。
而梯度下降的算法过程就和我们从山顶想要快速下山的做法是一样的。
在生活中,我们很自然会想到沿着最陡峭的路往下行是下山速度最快的。如下面这幅图所示:
针对这幅图,细心的读者可能很快就会有很多的疑问,例如:
这些问题也就是本文接下来要讨论的内容。
算法描述
梯度下降算法最开始的一点就是需要确定下降的方向,即:梯度。
我们常常用 来表示梯度。
对于一个二维空间的曲线来说,梯度就是其切线的方向。如下图所示:
而对于更高维空间的函数来说,梯度由所有变量的偏导数决定。
其表达式如下所示:
在机器学习中,我们主要是用梯度下降算法来最小化代价函数,记做:
其中,L是代价函数,是参数。
梯度下降算法的主体逻辑很简单,就是沿着梯度的方向一直下降,直到参数收敛为止。
记做:
这里有几点需要说明:
线性回归的梯度下降
有了上面的知识,我们可以回到线性模型代价函数的梯度下降算法实现了。
首先,根据代价函数我们可以得到梯度向量如下:
接着,将每个偏导数带入迭代的公式中,得到:
由此就可以通过代码实现我们的梯度下降算法了,算法逻辑并不复杂:
learning_rate = 0.01
def gradient_descent(x, y):t0 = 10t1 = 10delta = 0.001for times in range(1000):sum1 = 0sum2 = 0for i in range(len(x)):sum1 += (t0 + t1 * x)sum2 += (t0 + t1 * xt0_ = t0 - 2 * learning_rate * sum1 / len(x)t1_ = t1 - 2 * learning_rate * sum2 / len(x)print(’Times: {}, gradient: ’.format(times, t0_, t1_))if (abs(t0 - t0_) 《 delta and abs(t1 - t1_) 《 delta):print(’Gradient descent finish’)return t0_, t1_t0 = t0_t1 = t1_print(’Gradient descent too many times’)return t0, t1
这段代码说明如下:
如果我们将算法迭代过程中求得的线性模式绘制出来,可以得到下面这幅动态图:
最后算法得到的结果如下:
Times: 657, gradient: Times: 658, gradient: Times: 659, gradient: Times: 660, gradient: Gradient descent finish
从输出中可以看出,算法迭代了660次就收敛了。这时的结果了。如果需要更高的精度,可以将delta的值调的更小,当然,此时会需要更多的迭代次数。
高维扩展
虽然我们举的例子是二维的,但是对于更高维的情况也是类似的。同样是根据迭代的公式进行运算即可:
这里的下标i表示第i个参数,上标k表示第k个数据。
梯度下降家族BGD
在上面的内容中我们看到,算法的每一次迭代都需要把所有样本进行遍历处理。这种做法称为之Batch Gradient Descent,简称BGD。作为演示示例只有10条数据,这是没有问题的。
但在实际的项目中,数据集的数量可能是几百万几千万条,这时候每一步迭代的计算量就会非常的大了。
于是就有了下面两个变种。
SGD
Stochastic Gradient Descent,简称SGD,这种算法是每次从样本集中仅仅选择一个样本来进行计算。很显然,这样做算法在每一步的计算量一下就少了很多。
其算法公式如下:
当然,减少算法计算量也是有代价的,那就是:算法结果会强依赖于随机取到的数据情况,这可能会导致算法的最终结果不太令人满意。
MBGD
以上两种做法其实是两个极端,一个是每次用到了所有数据,另一个是每次只用一个数据。
我们自然就会想到两者取其中的方法:每次选择一小部分数据进行迭代。这样既避免了数据集过大导致每次迭代计算量过大的问题,也避免了单个数据对算法的影响。
这种算法称之为Mini-batch Gradient Descent,简称MBGD。
其算法公式如下:
当然,我们可以认为SGD是Mini-batch为1的特例。
针对上面提到的算法变种,该如何选择呢?
下面是Andrew Ng给出的建议:
下表是 Optimization for Deep Learning 中对三种算法的对比
方法准确性更新速度内存占用在线学习BGD好慢高否SGD好(with annealing)快低是MBGD好中等中等是算法优化
式7是算法的基本形式,在这个基础上有很多人进行了更多的研究。接下来我们介绍几种梯度下降算法的优化方法。
Momentum
Momentum是动量的意思。这个算法的思想就是借助了动力学的模型:每次算法的迭代会使用到上一次的速度作为依据。
算法的公式如下:
对比式7可以看出,这个算法的主要区别就是引入了,并且,每个时刻的受前一个时刻的影响。
从形式上看,动量算法引入了变量 v 充当速度角色——它代表参数在参数空间移动的方向和速率。速度被设为负梯度的指数衰减平均。名称动量来自物理类比,根据牛顿运动定律,负梯度是移动参数空间中粒子的力。动量在物理学上定义为质量乘以速度。在动量学习算法中,我们假设是单位质量,因此速度向量 v 也可以看作是粒子的动量。
对于可以取值0,而是一个常量,设为0.9是一个比较好的选择。
下图是momentum算法的效果对比:
对原来的算法稍加修改就可以增加动量效果:
def gradient_descent_with_momentum(x, y):t0 = 10t1 = 10delta = 0.001v0 = 0v1 = 0gamma = 0.9for times in range(1000):sum1 = 0sum2 = 0for i in range(len(x)):sum1 += (t0 + t1 * x)sum2 += (t0 + t1 * xv0 = gamma * v0 + 2 * learning_rate * sum1 / len(x)v1 = gamma * v1 + 2 * learning_rate * sum2 / len(x)t0_ = t0 - v0t1_ = t1 - v1print(’Times: {}, gradient: ’.format(times, t0_, t1_))if (abs(t0 - t0_) 《 delta and abs(t1 - t1_) 《 delta):print(’Gradient descent finish’)return t0_, t1_t0 = t0_t1 = t1_print(’Gradient descent too many times’)return t0, t1
以下是该算法的输出:
Times: 125, gradient: Times: 126, gradient: Times: 127, gradient: Times: 128, gradient: Times: 129, gradient: Gradient descent finish
从结果可以看出,改进的算法只用了129次迭代就收敛了。速度比原来660次快了很多。
同样的,我们可以把算法计算的过程做成动态图:
对比原始的算法过程可以看出,改进算法最大的区别是:在寻找目标值时会在最终结果上下跳动,但是越往后跳动的幅度越小,这也就是动量所产生的效果。
Learning Rate 优化
至此,你可能还是好奇该如何设定学习率的值。
事实上,这个值的选取需要一定的经验或者反复尝试才能确定。
关键在于,这个值的选取不能过大也不能过小。
如果这个值过小,会导致每一次迭代的步长很小,其结果就是算法需要迭代非常多的次数。
那么,如果这个值过大会怎么样呢?其结果就是:算法可能在结果的周围来回震荡,却落不到目标的点上。下面这幅图描述了这个现象:
事实上,学习率的取值未必一定要是一个常数,关于这个值的设定有很多的研究。
下面是比较常见的一些改进算法。
AdaGrad
AdaGrad是Adaptive Gradient的简写,该算法会为每个参数设定不同的学习率。它使用历史梯度的平方和作为基础来进行计算。
其算法公式如下:
对比式7,这里的改动就在于分号下面的根号。
根号中有两个符号,第二个符号比较好理解,它就是为了避免除0而人为引入的一个很小的常数,例如可以设为:0.001。
第一个符号的表达式展开如下:
这个值其实是历史中每次梯度的平方的累加和。
AdaGrad算法能够在训练中自动的对learning rate进行调整,对于出现频率较低参数采用较大的学习率;相反,对于出现频率较高的参数采用较小的学习率。因此,Adagrad非常适合处理稀疏数据。
但该算法的缺点是它可能导致学习率非常小以至于算法收敛非常的慢。
关于这个算法的直观解释可以看李宏毅教授的视频课程:ML Lecture 3-1: Gradient Descent。
RMSProp
RMS是Root Mean Square的简写。RMSProp是AI教父Geoff Hinton提出的一种自适应学习率方法。AdaGrad会累加之前所有的梯度平方,而RMSProp仅仅是计算对应的平均值,因此可缓解Adagrad算法学习率下降较快的问题。
该算法的公式如下:
类似的,是为了避免除0而引入。 是衰退参数,通常设为0.9。
这里的 是t时刻梯度平方的平均值。
Adam
Adam是Adaptive Moment Estimation的简写。它利用梯度的一阶矩估计和二阶矩估计动态调整每个参数的学习率。
Adam的优点主要在于经过偏置校正后,每一次迭代学习率都有个确定范围,使得参数比较平稳。
该算法公式如下:
,分别是对梯度的一阶矩估计和二阶矩估计。, 是对,的校正,这样可以近似为对期望的无偏估计。
Adam算法的提出者建议 默认值为0.9,默认值为0.999,默认值为 。
在实际应用中 ,Adam较为常用,它可以比较快地得到一个预估结果。
优化小结
这里我们列举了几种优化算法。它们很难说哪种最好,不同的算法适合于不同的场景。在实际的工程中,可能需要逐个尝试一下才能确定选择哪一个,这个过程也是目前现阶段AI项目要经历的工序之一。
实际上,该方面的研究远不止于此,如果有兴趣,可以继续阅读 《Sebastian Ruder: An overview of gradient descent optimization algorithms》 这篇论文或者 Optimization for Deep Learning 这个Slides进行更多的研究。
由于篇幅所限,这里不再继续展开了。
算法限制
梯度下降算法存在一定的限制。首先,它要求函数必须是可微分的,对于不可微的函数,无法使用这种方法。
除此之外,在某些情况下,使用梯度下降算法在接近极值点的时候可能收敛速度很慢,或者产生Z字形的震荡。这一点需要通过调整学习率来回避。
另外,梯度下降还会遇到下面两类问题。
局部最小值
局部最小值(Local Minima)指的是,我们找到的最小值仅仅是一个区域内的最小值,而并非全局的。由于算法的起点是随意取的,以下面这个图形为例,我们很容易落到局部最小值的点里面。
这就是好像你从上顶往下走,你第一次走到的平台未必是山脚,它有可能只是半山腰的一个平台的而已。
算法的起点决定了算法收敛的速度以及是否会落到局部最小值上。
坏消息是,目前似乎没有特别好的方法来确定选取那个点作为起点是比较好的,这就有一点看运气的成分了。多次尝试不同的随机点或许是一个比较好的方法,这也就是为什么做算法的优化这项工作是特别消耗时间的了。
但好消息是:
鞍点
除了Local Minima,在梯度下降的过程中,还有可能遇到另外一种情况,即:鞍点(Saddle Point)。鞍点指的是我们找到点某个点确实是梯度为0,但它却不是函数的极值,它的周围既有比它小的值,也有比它大的值。这就好像马鞍一样。
如下图所示:
多类随机函数表现出以下性质:在低维空间中,局部极值很普遍。但在高维空间中,局部极值比较少见,而鞍点则很常见。
不过对于鞍点,可以通过数学方法Hessian矩阵来确定。关于这点,这里就不再展开了,有兴趣的读者可以以这里提供的几个链接继续探索。
参考资料与推荐读物
e的x减一次方的导数
e的x减一次方的导数是e^(x-1)。
具体解法如下:
e的x减一次方,即为e^(x-1)
e的x减一次方的导数,即为e^(x-1)的导数
e^(x-1)’=e^(x-1)*(1)=e^(x-1)
所以e的x减一次方的导数是e^(x-1)。
扩展资料
导数的求解注意点:
1、理解并牢记导数定义。导数定义中一定要出现这一点的函数值,如果已知告诉等于零,那极限表达式中就可以不出现,否就不能推出在这一点可导。
2、导数定义相关计算。这里有几种题型:1)已知某点处导数存在,计算极限,这需要掌握导数的广义化形式,还要注意是在这一点处导数存在的前提下,否则是不一定成立的。
3、导数、可微与连续的关系。函数在一点处可导与可微是等价的,可以推出在这一点处是连续的,反过来则是不成立的。
4、导数的计算。导数的计算可以说在每一年的考研数学中都会涉及到,而且形式不一,考查的方法也不同。
5、高阶导数计算。需要同学们记住几个常见的高阶导数公式,将其他函数都转化成我们这几种常见的函数,代入公式就可以了,也有通过求一阶导数,二阶,三阶的方法来找出他们之间关系的。
python 中 return lambda 怎么理解
lambda或者高阶函数是一种设计模式,是否适合应该要看更大范围的设计需要。脱离了需求场景,返回lambda根本是脱裤子放屁。
JAVA 8可以兼容JAVA 6么
Java 8的6个问题1. 并行Streams实际上可能会降低你的性能Java8带来了最让人期待的新特性之–并行。parallelStream() 方法在集合和流上实现了并行。它将它们分解成子问题,然后分配给不同的线程进行处理,这些任务可以分给不同的CPU核心处理,完成后再合并到一起。实现原理主要是使用了fork/join框架。好吧,听起来很酷对吧!那一定可以在多核环境下使得操作大数据集合速度加快咯,对吗?不,如果使用不正确的话实际上会使得你的代码运行的更慢。我们进行了一些基准测试,发现要慢15%,甚至可能更糟糕。假设我们已经运行了多个线程,然后使用.parallelStream() 来增加更多的线程到线程池中,这很容易就超过多核心CPU处理的上限,从而增加了上下文切换次数,使得整体都变慢了。基准测试将一个集合分成不同的组(主要/非主要的):12Map《Boolean, List《Integer》》 groupByPrimary = numbers.parallelStream().collect(Collectors.groupingBy(s -》 Utility.isPrime(s)));使得性能降低也有可能是其他的原因。假如我们分成多个任务来处理,其中一个任务可能因为某些原因使得处理时间比其他的任务长很多。.parallelStream() 将任务分解处理,可能要比作为一个完整的任务处理要慢。来看看这篇文章, Lukas Krecan给出的一些例子和代码 。提醒:并行带来了很多好处,但是同样也会有一些其他的问题需要考虑到。当你已经在多线程环境中运行了,记住这点,自己要熟悉背后的运行机制。2. Lambda 表达式的缺点lambda表达式。哦,lambda表达式。没有lambda表达式我们也能做到几乎一切事情,但是lambda是那么的优雅,摆脱了烦人的代码,所以很容易就爱上lambda。比如说早上起来我想遍历世界杯的球员名单并且知道具体的人数(有趣的事实:加起来有254个)。1234List lengths = new ArrayList();for (String countries : Arrays.asList(args)) { lengths.add(check(country));}现在我们用一个漂亮的lambda表达式来实现同样的功能:1Stream lengths = countries.stream().map(countries -《 check(country));哇塞!这真是超级厉害。增加一些像lambda表达式这样的新元素到Java当中,尽管看起来更像是一件好事,但是实际上却是偏离了Java原本的规范。字节码是完全面向对象的,伴随着lambda的加入 ,这使得实际的代码与运行时的字节码结构上差异变大。阅读更多关于lambda表达式的负面影响可以看Tal Weiss这篇文章。从更深层次来看,你写什么代码和调试什么代码是两码事。堆栈跟踪越来越大,使得难以调试代码。一些很简单的事情譬如添加一个空字符串到list中,本来是这样一个很短的堆栈跟踪12at LmbdaMain.check(LmbdaMain.java:19)at LmbdaMain.main(LmbdaMain.java:34)变成这样:12345678910111213at LmbdaMain.check(LmbdaMain.java:19)at LmbdaMain.lambda$0(LmbdaMain.java:37)at LmbdaMain$$Lambda$1/821270929.apply(Unknown Source)at java.util.stream.ReferencePipeline$3$1.accept(ReferencePipeline.java:193)at java.util.Spliterators$ArraySpliterator.forEachRemaining(Spliterators.java:948)at java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:512)at java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:502)at java.util.stream.ReduceOps$ReduceOp.evaluateSequential(ReduceOps.java:708)at java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234)at java.util.stream.LongPipeline.reduce(LongPipeline.java:438)at java.util.stream.LongPipeline.sum(LongPipeline.java:396)at java.util.stream.ReferencePipeline.count(ReferencePipeline.java:526)at LmbdaMain.main(LmbdaMain.java:39lambda表达式带来的另一个问题是关于重载:使用他们调用一个方法时会有一些传参,这些参数可能是多种类型的,这样会使得在某些情况下导致一些引起歧义的调用。Lukas Eder 用示例代码进行了说明。提醒:要意识到这一点,跟踪有时候可能会很痛苦,但是这不足以让我们远离宝贵的lambda表达式。3. Default方法令人分心Default方法允许一个功能接口中有一个默认实现,这无疑是Java8新特性中最酷的一个,但是它与我们之前使用的方式有些冲突。那么既然如此,为什么要引入default方法呢?如果不引入呢?Defalut方法背后的主要动机是,如果我们要给现有的接口增加一个方法,我们可以不用重写实现来达到这个目的,并且使它与旧版本兼容。例如,拿这段来自Oracle Java教程中 添加指定一个时区功能的代码来说:123456789101112131415public interface TimeClient {// ...static public ZoneId getZoneId (String zoneString) {try { return ZoneId.of(zoneString);} catch (DateTimeException e) { System.err.println("Invalid time zone: " + zoneString + "; using default time zone instead."); return ZoneId.systemDefault(); }}default public ZonedDateTime getZonedDateTime(String zoneString) { return ZonedDateTime.of(getLocalDateTime(), getZoneId(zoneString)); }}就是这样,问题迎刃而解了。是这样么?Default方法将接口和实现分离混合了。似乎我们不用再纠结他们本身的分层结构了,现在我们需要解决新的问题了。想要了解更多,阅读Oleg Shelajev在RebelLabs上发表的文章吧。提醒:当你手上有一把锤子的时候,看什么都像是钉子。记住它们原本的用法,保持原来的接口而重构引入新的抽象类是没有意义的。4. 该如何拯救你,Jagsaw?Jigsaw项目的目标是使Java模块化,将JRE分拆成可以相互操作的组件。这背后最主要的动机是渴望有一个更好、更快、更强大的Java嵌入式。我试图避免提及“物联网”,但我还是说了。减少JAR的体积,改进性能,增强安全性等等是这个雄心勃勃的项目所承诺的。但是,它在哪呢?Oracle的首席Java架构师, Mark Reinhold说: Jigsaw,通过了探索阶段 ,最近才进入第二阶段,现在开始进行产品的设计与实现。该项目原本计划在Java8完成。现在推迟到Java9,有可能成为其最主要的新特性。提醒:如果这正是你在等待的, Java9应该在2016年间发布。同时,想要密切关注甚至参与其中的话,你可以加入到这个邮件列表。5. 那些仍然存在的问题受检异常没有人喜欢繁琐的代码,那也是为什么lambdas表达式那么受欢迎的的原因。想想讨厌的异常,无论你是否需要在逻辑上catch或者要处理受检异常,你都需要catch它们。即使有些永远也不会发生,像下面这个异常就是永远也不会发生的:123try {***隐藏网址***} catch (ProtocolException pe) { /* Why don’t you call me anymore? */ }原始类型它们依然还在,想要正确使用它们是一件很痛苦的事情。原始类型导致Java没能够成为一种纯面向对象语言,而移除它们对性能也没有显著的影响。顺便提一句,新的JVM语言都没有包含原始类型。运算符重载James Gosling,Java之父,曾经在接受采访时说:“我抛弃运算符重载是因为我个人主观的原因,因为在C++中我见过太多的人在滥用它。”有道理,但是很多人持不同的观点。其他的JVM语言也提供这一功能,但是另一方面,它导致有些代码像下面这样:123javascriptEntryPoints 《《= (sourceDirectory in Compile)(base =》 ((base / "assets" ** "*.js") --- (base / "assets" ** "_*")).get)事实上这行代码来自Scala Play框架,我现在都有点晕了。提醒:这些是真正的问题么?我们都有自己的怪癖,而这些就是Java的怪癖。在未来的版本中可能有会发生一些意外,它将会改变,但向后兼容性等等使得它们现在还在使用。6. 函数式编程–为时尚早函数式编程出现在java之前,但是它相当的尴尬。Java8在这方面有所改善例如lambdas等等。这是让人受欢迎的,但却不如早期所描绘的那样变化巨大。肯定比Java7更优雅,但是仍需要努力增加一些真正需要的功能。其中一个在这个问题上最激烈的评论来自Pierre-yves Saumont,他写了一系列的文章详细的讲述了函数式编程规范和其在Java中实现的差异。所以,选择Java还是Scala呢?Java采用现代函数范式是对使用多年Lambda的Scala的一种肯定。Lambdas让我们觉得很迷惑,但是也有许多像traits,lazy evaluation和immutables等一些特性,使得它们相当的不同。提醒:不要为lambdas分心,在Java8中使用函数式编程仍然是比较麻烦的。
更多文章:
java和javaee有什么区别(javaEE和java有什么不同)
2024年8月22日 22:00
玳瑁辟邪真有那么厉害(玳瑁五行属什么,鬼魂怕什么东西什么东西辟邪)
2024年8月28日 04:10
showmodaldialog(showmodaldialog老出现滚动条是怎么回事直接用浏览器打开不会)
2024年7月18日 14:57
bc范式怎么判断(求数据库高手,解决有关范式的问题,谢谢!!)
2023年6月3日 22:20
svg图片是什么(ppt图标svg和eps和png有什么区别)
2024年7月9日 09:22
fedora歌剧(意大利歌曲《我亲爱的》的作者以及歌曲创作背景 是什么 急切求解 谢谢)
2024年6月27日 09:09
.net代码注释?请问这段.NET代码 每句话都是什么意思要详细的注释谢谢帮助!!
2024年7月4日 17:46