java函数库学习 - CSDN
  • 译者注:作者介绍了神经网络的进化历史,总结了流行的Java神经网络,以及实现神经网络的方法 。作为开发人员,我们习惯从用命令或函数的角度来思考问题。程序由任务组成,每个任务都包含一些编程结构。神经网络...

    原文:Learning Neural Networks Using Java Libraries
    作者:
    Daniela Kolarova

    翻译:lloog

    译者注:作者介绍了神经网络的进化历史,总结了流行的Java神经网络库,以及实现神经网络的方法 。

    作为开发人员,我们习惯从用命令或函数的角度来思考问题。程序由任务组成,每个任务都包含一些编程结构。神经网络不同于这种编程方法,因为它加入了自动任务改进的概念,或者类似于大脑的学习和改进的能力。换句话说,神经网络在没有特定任务编程的情况下主动学习新的活动。

    本教程不是教你从头开始编写神经网络的教程,而是介绍结合Java代码的神经网络。本文主要内容是首先介绍神经网络的发展从McCulloch和Pitt的神经元开始,然后通过Hebb的发现,实现了Rosenblatt的感知器来增强它,并说明了它为什么不能解决XOR问题。其次介绍了通过连接神经元,生成一个多层感知器,并通过应用反向传播算法进行学习,从而实现XOR问题的解决方案。最后在演示神经网络实现、训练算法和测试之后,介绍利用一些致力于深度学习的开源Java ML框架如Neuroph、Encog和Deeplearning4j来快速实现神经网络:

    早期的人造神经元模型是由神经生理学家Warren McCulloch和逻辑学家Walter Pitts在1943年引入的。他们的论文名为“神经活动的逻辑微积分”,通常被认为是神经网络研究的开始。mcculloch - pitts神经元工作原理是对每一个输入输入1或0,其中1为真,0为假,然后给神经元分配一个二元阈值激活函数来计算神经元的输出。

    image

    该阈值给定一个实际值,比如1,如果阈值达到或超过阈值,则允许输出0或1。此外,为了表示AND函数,我们设置2.0的阈值,如下表所示:

    image

    如果我们将阈值切换到1,那么这个方法也可以应用于OR函数。到目前为止,我们具有如表所示的典型的线性可分离数据,我们可以使用直线来划分数据。 然而,McCulloch-Pitts神经元有一些严重的局限性。它既不能解决“异或”功能(XOR)也不能解决不是线性分离的“排他性”功能(XNOR)。 唐纳德·赫布(Donald Hebb)提出下一次革命,他以关于Hebbian学习的理论而闻名。在他1949年出版的《行为的组织》一书中这样写道:

    当细胞A的轴突足够接近细胞B,不断反复持续的激活细胞B,两个细胞之间就会产生生长过程或代谢变化,这样A的效率提高了,就像燃烧了细胞B一样”。

    换句话说,当一个神经元不停地触发另一个神经元时,第一个神经元的轴突/连接就会形成突触小结,如果它们已经与第二个神经元连接,就会放大它们。Hebb不仅提出,当两个神经元一起点燃神经元之间的连接时,神经元之间的联系就会增强——这被称为神经元之间连接的权重——还提出这个活动是学习和记忆所必需的基本操作之一。因此必须改变McCulloch-Pitts神经元,以便为每个输入分配权重。 此外,相对于总阀值量,1的输入或多或少都被被赋予了权重。

    后来,在1962年,由Frank Rosenblatt在他的《神经动力学原理》一书中定义并描述了感知器。这是一个神经元的模型,它可以通过输入的权重在Hebbean Sense 中学习,并为后来神经网络的发展奠定了基础。感知器学习使用随机权重初始化感知器,在激活后反复检查答案是否正确。如果不正确,神经网络可以从错误中学习并调整它的权重。

    image

    尽管感知器对原始McCulloch-Pitts神经元进行了许多改变,但感知器仍然局限于解决某些功能。1969年,Minsky与Seymour Papert,合著了” 感知器:计算几何的介绍”,在文章中攻击了感知器的局限性。文章中表明,感知器只能解决线性可分函数,并没有打破这点的限制。直到20世纪80年代以前,这方面的研究还很少。现在解决这些困难的方法之一就是建立神经网络。这些网络将人工神经元的输入与其他人工神经元的输出连接起来。因此,网络能够解决更困难的问题,但它们也会变得相当复杂。但感知器没有解决的XOR问题。如果我们仔细观察真值表,我们可以看到XOR函数变成等价于单个神经元可表示的OR和NOT AND函数。
    让我们再来看看真值表:

    image

    但是我们可以将代表NOT和AND的两个神经元结合起来,并构建一个神经网络来解决类似于下图所示的XOR问题:

    image

    这个图表示多层感知器,它有一个输入层,一个隐藏层和一个输出层。神经元之间的连接与图片中没有显示的权重有关。与单一感知器类似,每个处理单元都有一个summing和激活组件。它看起来很简单,但我们也需要一个训练算法来调整各个层次的权重,并让它学习。通过简单的感知器,我们可以很容易地根据误差计算变化权重。从而训练多层感知器实现计算神经网络的整体错误。

    1986年,Geoffrey Hinton,David Rumelhart和Ronald Williams发表了一篇论文,“通过反向传播错误学习表示”,描述了一个新的学习过程,反向传播。该过程反复调整网络中连接的权重,从而最小化网络实际输出向量和所需输出向量之间的差值。作为权重调整的结果,内部隐藏的单元(不是输入或输出的一部分)被用来表示重要的特征,并且这些单元通过交互捕获规律性任务。

    现在我们已经能够编写使用Java学习XOR函数的多层感知器。这需要创建一些类,比如名为ProcessingUnit的神经元接口,连接类,一些更活跃的函数,以及一个能够学习的单层神经网络。在我的GitHub存储库中你可以在项目中找到这些接口和类。

    其中NeuralNet类负责对层的构造和初始化。它还提供了训练和评估激活结果的功能。 如果运行NeuralNet类来解决典型的XOR问题,它将激活,评估结果,应用反向传播算法和输出训练结果。

    如果你仔细查看代码,你会发现它在可重用性方面并不是非常灵活。如果我们将NeuralNet结构从训练部分分离出来,变成把各种学习算法应用在各种神经网络结构上,那就更好了。此外,我们想要更深入的学习结构和各种激活函数,则必须改变数据结构。因为到目前为止,只有一个隐藏层被定义。为了确保不产生任何bug,反向传播计算必须经过仔细的测试。一旦完成了所有的重构,我们就必须开始考虑深层神经网络的性能。

    我想说的是,如果我们进行实际开发,那么首先需要看看现有的神经网络库。虽然从头开始实现神经网络有助于理解整个网络的细节,但是如果一个真实的解决方案必须从头开始实施,那么将要付出很大的努力。在本文中,我只选择了纯Java神经网络库。尽管Deeplearning4j得到了商业支持,但所有这些都是开源的。网上都有大量的参考文档和例子。其中Deeplearning4j也支持CUDA。维基百科也提供了一份关于各种语言的深度学习软件的综合列表。

    如何使用神经网络库的示例也被包含在关于使用XOR NeuralNet的GitHub中。很明显,在开发中利用现成的函数库与自己编写java代码相比工作量要少很多。其中Neuroph是为数据集提供的API,它更容易初始化训练数据,学习规则的层次结构,进行神经网络串行化/持久性和反序列化,并带有GUI。Encog是一种先进的机器学习框架,支持多种高级算法,也支持规范化类和处理数据。然而,它的主要优势在于神经网络算法。Encog包含创建各种网络的类,以及支持这些神经网络的规范化和处理数据的支持类。Deeplearning4j是一个非常强大的库,支持多种算法,包括与Apache Hadoop和Spark集成的分布式并行版本。对于有经验的开发人员和软件架构师来说,这绝对是正确的选择。这里作为神经网络库的一部分提供了一个XOR示例。

    想在要利用现有的众多神经网络库,开发人员需要熟悉各种各样的参数,从而让他们的神经网络进行学习。本文演示了一个非常简单的例子,它包含神经元和反向传播。然而,今天使用的许多人工神经网络仍然来使用mcculloch - pitts神经元和Rosenblatt感知器的早期版本。作为构建现代深层神经网络的基石,重要的是要理解神经元的根,并在库中利用好现成的神经元、层、激活函数和学习算法。

    展开全文
  • 第六章:认识Java的API-使用Java函数库 Java内置有数百个类:  如果你知道如何从统称Java API的Java的函数库中查找所需功能,那就不用再造轮子了;  核心Java函数库是由一堆等着被你当做组件使用的类集合而...

    该系列文章系个人读书笔记及总结性内容,任何组织和个人不得转载进行商业活动!

    第六章:认识Java的API-使用Java函数库


    Java内置有数百个类

        如果你知道如何从统称Java API的Java的函数库中查找所需功能,那就不用再造轮子了;
        核心Java函数库是由一堆等着被你当做组件使用的类集合而成的;
        你可以大量运用这些预设的组件来写程序;

    数组无法改变大小

        我们之前使用的数组,如String[] ss = new String[3];在初始化时就已经确定了数组大小;
        可以使用新的数组从原数组中copy值,变相实现;

    那么有没有能增加/删除元素自动缩放的、类似数组的结构呢?

    ArrayList

        他不是数组,它是Java函数库中的另一个类;
        Java SE(标准版本)带有很多创建好的类,类似我们写好的代码,只不过它们已经被编译过了;

    Methods:
        add(Object elem)
        remove(int index)
        remove(Object elem)
        contains(Object elem)
        isEmpty();
        indexOf(Object elem)//没有的话会返回-1
        size()
        get(int index)

    想知道API里有什么,确实需要花些时间,稍后介绍;

    再看ArrayList

        我是个对象;
        数组没办法删除元素,但我可以,并且动态改变我的大小;
        但对于primitive主数据类型,不能直接保存,包装起来就好了;这个在Java5.0,包装工作会自动进行;
        使用的时候,也是在运用ArrayList类型的对象,使用“.”运算符来调用它的方法;

    相比于数组,会以特殊的语法来操作,但是这样的特殊语法只能用在数组上;虽说数组也是对象,但是他有自己的规则,你无法调用它的方法,最多只能存取它的length实例变量;
    (其实可以看到java中的类都是将实例变量封装的,调用的一般都是方法)

    一般数组在创建时就必须确定大小;ArrayList则不需要;
    一般数组使用特殊的语法,[0]这种下标的方式;ArrayList只是个普通对象;
    存放对象一般数组必须指定位置,切不能越界;ArrayList可以用add(int,Object)或add(Object);


    ArrayList的参数化:(parameterized)

        Java5.0:参数化类型(类似Swift的泛型,用法和OC差不多)
        ArrayList<String>
            <String>是类型参数,ArrayList<String>表示字符串的集合;否则ArrayList将是异质对象的集合;

    使用ArrayList需要

        import java.util.ArrayList;
        或
        import java.util.*;
        (还记得之前用过一个import java.io.*;吗!)

    这里需要熟悉几个运算符

        与 和 或 运算符(&&,||);
        不等于 和 非 运算符(!= 和 !);
        短运算符(&&,||):
            使用&&时,JVM如果发现左边为false,就不会再去判断右边;||同理;
        长运算符(&,|):
            使用在boolean表达式时会强制JVM一定计算两边的算事;
            但这两个运算符通常是用来作位运算的;

    使用函数库(Java API)

        在Java的API中,类是被包装在包中的;
        要使用API中的类,必须知道他被放到哪个包中;

    Java函数库中的每个类都属于某个包:
        这些包都有名字,像是javax.swing;ArrayList是放在java.util这个包中的;(java.util放了很多工具类,后续会介绍包括如何自制包)

    使用来自API的类需要知名函数库类的完整名称:包的名称加上类的名称:
        像是System、String、Math都属于java.lang这个包;
        ArrayList的全名是java.util.ArrayLsit;

    指定使用类的方法(两种)

    1)import:
        放一个import述句在程序源文件最前边,使用的时候就可以直接使用ArrayList类型;
        import java.util.ArrayList;

    2)type:
        在程序代码中打出全名,无论哪里,只要用就打出全名;(除非是来自java.lang这个包中的类)
        如用作方法返回类型声明:public java.util.ArrayList<Dog> foo(){}
        

    包之所以重要的原因

    1)包可以帮助组织项目或函数库,相对于一大堆零散的类,以功能来组织会更好;
    2)包可以制造名称空间,以便错开相同名称的类;
    3)包可以限制 同一包之间的类才能相互存取,以维护安全性(访问控制权限);

    Java的命名传统能够防止发生包的名称冲突;

    javax开头的包代表什么?(javas.swing)

        包含在标准函数库中的包,都放在以java开头的包中;
        后来出现了一些没有包含在标准函数库中的包,被称为扩展的类;
        扩展的类有两种类型:即官方认可的标准扩展,其余beta版就是非标准的;
        标准版的扩展都是以javax作为包名称的开头,如Swing函数库,就是以javax.swing开头的;

    要点

        ArrayList是个Java API的类;
        使用add()来新增ArrayList的元素;
        使用remove()来删除ArrayList中的元素;
        要寻找某项元素的位置,使用indexOf();
        使用isEmpty()来判别ArrayList是否为空;
        获取ArrayList大小,使用size()方法;
        传统数组可以用length这个变量获取大小;
        ArrayList会自动地调整大小;
        可以使用参数类型来声明数组内容的类型;ArrayList<Button>;
        ArrayList只能携带对象而非primitive主数据类型,但编译器能够自动把primitive主数据类型包装成Object;
        类会用包来组织;
        类有完整的名:包名+类名;
        除java.lang之外,其他包的类都需要指定全名;也可以在源文件最开始import指令来说明所使用的包;

    知识小问答
    使用import会把程序变大吗?编译过程会把包或类包进去吗?
        这是C的思路,运用import知识帮助省下每个类前的包名称而已;程序不会因为用了import而变大或变慢;

    为何不必import java.lang?
        java.lang是个预先被引用的包;因为它是经常用的基础包;
        java.lang.String和java.lang.System是独一无二的class,Java会知道要去哪里找;

    如何查询API

        我们需要知道库中有哪些类,找到类之后,还需要知道他在做什么?
    方式1:查阅参考书;
    方式2:查阅HTML API文档

    如下图:
        左上是包;
        左下是包中的类;
        右侧是类的详情;

    展开全文
  • 虽然在机器学习中,Python是人工智能从业者使用最多的编程语言,但是,Java 在项目开发中仍然发挥着不可替代的作用,而且许多流行的机器学习框架本身就是 Java编写的。Python 的资料到处都是,而 Java 相关的资料就...

    导读:机器学习是目前盛行于世的技术之一,这几年一时风头无两。虽然在机器学习中,Python是人工智能从业者使用最多的编程语言,但是,Java 在项目开发中仍然发挥着不可替代的作用,而且许多流行的机器学习框架本身就是 Java编写的。Python 的资料到处都是,而 Java 相关的资料就相对少了很多。今天我们翻译了 Fatema Patrawala> 撰写的《六大最常用的 Java 机器学习库一览》。

    MLOSS.org 网站上,列出了 70 多个基于 Java 的开源机器学习项目,可能还有更多未列出的项目,存于大学里的服务器、GitHub 或 Bitbucket 中。我们将在本文中回顾 Java 中的主流机器学习库和平台,它们能够解决的问题类型,支持的算法以及可以使用的数据类型。

    本文节选自 Machine learning in Java,由 Bostjan Kaluza 编写,Packt Publishing Ltd. 出版

    Weka

    Weka 是 Waikato Environment for Knowledge Analysis(Waikato 智能分析环境)的缩写,是新西兰 Waikato 大学开发的机器学习库,也可能是最为有名的 Java 库。Weka 是一个通用的库,能够解决各种机器学习任务,如分类、回归和聚类。它具有丰富的图形用户界面、命令行界面和 Java API。有关 Weka 更多详情,请参阅:http://www.cs.waikato.ac.nz/ml/weka/

    截止到本书写作之时,Weka 总共包含 267 个算法,其中:数据预处理(82),属性选择(33),分类和回归(133),聚类(12),关联规则挖掘(7)。图形界面非常适合用于探索数据,而 Java API 可以让你开发新的机器学习方案并在应用中使用这些算法。
    在这里插入图片描述
    Weka 是在 GNU 通用公共许可证(GNU GPL)下发布的,这意味着你可以复制、分发和修改它,只要你跟踪源文件中的更改并将其保存在 GNU GPL 下。你甚至可以进行商业分发,但前提是你必须公开源代码或获得商业许可证。

    除了几种支持的文件格式外,Weka 还提供了自己的默认数据格式 ARFF,用于通过属性 - 数据对描述数据。它由两部分组成:第一部分包含标题头,它指定所有属性(即特性)及其类型;例如,标称、数字、日期和字符串。第二部分包含数据,其中每行对应于一个实例。标题头中的最后一个属性隐式地被视为目标变量,缺失的数据用问号标记。例如,用 ARFF 文件格式编写的 Bob 实例如下:

        @RELATION person_dataset
        
        @ATTRIBUTE `Name`  STRING
        @ATTRIBUTE `Height`  NUMERIC
        @ATTRIBUTE `Eye color`{blue, brown, green}
        @ATTRIBUTE `Hobbies`  STRING
        
        @DATA
        'Bob', 185.0, blue, 'climbing, sky diving'
        'Anna', 163.0, brown, 'reading'
        'Jane', 168.0, ?, ?
    

    该文件由三个部分组成。第一部分以 @relation 关键字开始,指定数据集名称。下一部分以 @ATTRIBUTE 关键字开始,后面是属性名和类型。可用的类型是 STRING(字符串)、NUMERIC(数字)、DATE(日期)和一组分类值。最后一个属性被隐式假设为我们想要预测的目标变量。最后一部分以 @DATA 关键字开始,每行后面跟着一个实例。实例值用逗号分隔,并且必须遵循与第二部分中的属性相同的顺序。

    Weka 的 Java API 由以下的顶层包组成:

    • weka.associations:这些是关联规则学习的数据结构和算法,包括 Apriori、 predictive apriori、FilteredAssociator、FP-Growth、Generalized Sequential Patterns (GSP)、Hotspot 和 Tertius。

    • weka.classifiers:这些是监督学习算法、评估期和数据结构。该包由以下几个部分组成:

      • weka.classifiers.bayes:它实现了贝叶斯(Bayesian)方法,包括朴素贝叶斯、贝式网络、贝叶斯逻辑回归等。

      • weka.classifiers.evaluation:这些是评价统计、混淆矩阵、ROC 曲线等标称和数值预测的监督评价算法。

      • weka.classifiers.functions:这些是回归算法,包括线性回归、保序回归、高斯过程、支持向量机、多层感知器、表决感知器等。

      • weka.classifiers.lazy:这些是基于实例的算法,比如 k- 最近邻、K*,惰性贝叶斯规则。

      • weka.classifiers.meta:这些是监督学习元算法,包括 AdaBoost、bagging、加性回归、随机委员会(random committee)等。

      • weka.classifiers.mi:这些是多实例算法,如 Citation-KNN、多样性密度、MI AdaBoost 等。

      • weka.classifiers.rules:这些是基于变治法(separate-and-conquer)、Ripper、Part、Prism 的决策表和决策规格。

      • weka.classifiers.trees:这些是各种决策树算法,包括 ID3、C4.5、M5、功能树、逻辑树、随机森林等。

    • weka.clusterers:这些是聚类算法,包括 k-means、Clope、Cobweb、DBSCAN 层次聚类、Farthest 等

    • weka.core:这些是各种实用类、数据表示、配置文件等。

    • weka.datagenerators:这些是用于分类、回归和聚类算法的数据生成器。

    • weka.estimators:这些是用于离散 / 标称域、条件概率估计等的各种数据分布估计。

    • weka.experiment:这是一组类,支持运行实验所需的配置、数据集、模型设置和统计信息。

    • weka.filters:这些是基于属性和基于实例的选择算法,用于监督和非监督数据预处理。

    • weka.gui:这些是实现 Explorer、Experimenter、和 Knowledge Flow 的图形界面。Explorer 允许你调查数据集、算法及其参数,并使用散点图和其他可视化的形式对数据集进行可视化。Experimenter 用于设计批量实验,但它只能用于分类和回归问题。Knowledge Flow 实现了可视化的拖放式用户界面来构建数据流,如:加载数据、应用过滤器、构建分类器和评估。

    用于机器学习的 Java-ML

    Java 机器学习库(Java-ML)是一组机器学习算法的集合,具备用于相同类型的算法的公共接口。它只提供 Java API,因此,它主要面向的是软件工程师和程序员。Java-ML 包含用于数据预处理、特征选择、分类和聚类的算法。此外,它还提供了几个 Weka 桥来直接通过 Java-ML API 访问 Weka 的算法。Java-ML 可从 http://java-ml.sourceforge.net 下载,截至本书完成之际,最近版本发布于 2012 年。
    在这里插入图片描述
    Java-ML 也是一个通用机器学习库。与 Weka 相比,它提供了更为一致的接口和最新算法的实现,在其他包中不存在这些算法,如一系列最先进的相似性度量和特征选择技术等,这些包含动态时间规整、随机森林属性评估等等。Java-ML 也可以在 GNU GPL 许可证下使用。

    Java-ML 支持任何类型的文件,只要它每行包含一个数据样本,并且特征用逗号、分号和制表符分隔。

    Java-ML 库由以下顶层包组成:

    • net.sf.javaml.classification:这些是分类算法,包括朴素贝叶斯、随机森林、Bagging、自组织映射、k- 最近邻等。

    • net.sf.javaml.clustering:这些是聚类算法,包括 kmeans、自组织映射、空间聚类、Cobweb、AQBC 等。

    • net.sf.javaml.core:这些表示实例和数据集。

    • net.sf.javaml.distance:这些是测量实例距离和相似度的算法,如切比雪夫距离(Chebyshev distance)、余弦距离 / 相似度、欧几里得距离(Euclidian distance)、杰卡德距离(Jaccard distance)/ 相似度、马氏距离(Mahalanobis distance)、曼哈顿距离(Manhattan distance)、明氏距离(Minkowski distance)、皮尔逊积矩相关系数(Pearson correlation coefficient)、斯皮尔曼简捷距离(Spearman’s footrule distance)、动态时间规整(dynamic time wrapping,DTW)等。

    • net.sf.javaml.featureselection:这些是用于特征评估、评分、选择和排名的算法,如增益比、ReliefF、Kullback-Liebler 散度、对称不确定性等。

    • net.sf.javaml.filter:这些是通过过滤、删除属性、设置类或属性值等操作实例的方法。

    • net.sf.javaml.matrix:实现内存或基于文件的数组。

    • net.sf.javaml.sampling:实现选择数据集子集的采样算法。net.sf.javaml.tools:这些是关于数据集、实例操作、序列化、Weka API 接口等的使用方法。

    • net.sf.javaml.utils:这些是算法的实用方法,如统计、数学方法、列联表等表等。

    Apache Mahout

    Apache Mahout 项目旨在构建可扩展的机器学习库。它是在可扩展分布式体系结构(如 Hadoop)上构建的,实用 MapReduce 范例,这是一种实用服务器集群处理和生成具有并行分布式算法的大型数据及的方法。
    在这里插入图片描述
    Mahout 提供了控制台界面和 Java API,可用于聚类、分类和写作过滤的可扩展算法。它可以解决这三个业务问题:项目推荐,如向喜欢某部电影的人推荐其他可能喜欢的电影;聚类,如将文本文档分组与主题相关的文档组中归档;分类,如学习将哪个主题分配给未标记的文档。

    Mahout 是在商业化的 Apache 许可证下分发的,这意味着只要你保留 Apache 许可证并将其显示在程序的版权声明中,你就可以使用它。

    Mahout 提供了以下库:
    org.apache.mahout.cf.taste:这些是基于用户和基于项目的协同过滤算法,及基于 ALS 的矩阵分解算法。

    • org.apache.mahout.classifier:这些是内存和分布式实现,包括逻辑回归、朴素贝叶斯、随机森林、隐马尔科夫模型(hidden Markov models,HMM)和多层感知器。

    • org.apache.mahout.clustering:这些是聚类算法,例如 Canopy 聚类、k-means、模糊 k-means、流式 K-means 和谱聚类。

    • org.apache.mahout.common:这些是算法的实用方法,包括距离、MapReduce 操作、迭代器等。

    • org.apache.mahout.driver:实现了通用驱动程序来运行其他类的主要方法。

    • org.apache.mahout.ep:这是使用记录步骤突变的进化优化。

    • org.apache.mahout.math:这些是 Hadoop 中的各种数据额实用方法和实现。

    • org.apache.mahout.vectorizer:这些是用于数据表示、操作和 MapReduce 任务的类。

    Apache Spark

    Apache Spark(或简称 Spark)是在 Hadoop 上构建大规模数据处理的平台,但与 Mahout 不同的是,它与 MapReduce 范式无关。相反,它使用内存缓存提取工作数据集,对其进行处理并重复查询。据报道,Spark 直接处理磁盘存储数据的速度是 Mahout 实现的十倍。可从 https://spark.apache.org 下载。
    在这里插入图片描述
    在 Spark 之上构建了许多模块,例如用于图形处理的 GraphX、用于处理实时数据流的 Spark Streaming 和用于机器学习库的 MLlib,这些模块具有分类、回归、协同过滤、聚类、降维和优化。

    Spark 的 MLlib 可以使用基于 Hadoop 的数据源,例如 Hadoop 分布式文件系统(HDFS)或 HBase,以及本地文件。支持的数据类型包括以下几种:

    • 局部向量存储在一台机器上。稠密向量表示为双类型值数组,如 (2.0,0.0,10.,0.0);而稀疏向量由向量的大小、索引数组和值数组表示,如 [4, (0, 2), (2.0, 1.0)]。

    • 标记点用于监督学习算法,由局部向量组成,用双类型的类值标记。标签可以是类索引、二进制结果或多个类索引的列表(多类分类)。例如,标记的稠密向量表示为 [1.0, (2.0, 0.0, 1.0, 0.0)]。

    • 局部矩阵在单台机器上存储稠密矩阵。它由矩阵维数和以列主序排列的单个双数组定义。

    • 分布式矩阵对存储在 Spark 的弹性分布式数据集(Resilient Distributed Dataset,RDD)中的数据进行操作,RDD 表示可以并行操作的元素集合。有三种表示:行矩阵,其中每一行都是可以存储在一台机器上的局部向量,但行索引没有意义;索引行矩阵,类似于行矩阵,但行索引是有意义的,即可以识别行并执行行连接;坐标矩阵,当行不能存储在一台机器上,且矩阵非常稀疏时才使用。

    Spark 的 MLlib API 库提供了各种学习算法和实用工具的接口,如下所示:

    • org.apache.spark.mllib.classification:这些是二元和多类分类算法,包括线性 SVM、逻辑回归、决策树和朴素贝叶斯。

    • org.apache.spark.mllib.clustering:这些是 k-means 聚类。

    • org.apache.spark.mllib.linalg:这些是数据表示,包括稠密向量、稀疏向量和矩阵。

    • org.apache.spark.mllib.optimization:这是 MLlib 中作为低级基元的各种优化算法,包括梯度下降、随机梯度下降、分布式 SGD 的更新方案和有限内存 BFGS。

    • org.apache.spark.mllib.recommendation:这些是基于模型的协同过滤,通过交替最小二乘矩阵分解来实现。

    • org.apache.spark.mllib.regression:这些是回归学习算法,如线性最小二乘、决策树、Lasso 和 Ridge 回归。

    • org.apache.spark.mllib.stat:这些是稀疏或稠密向量格式的样本的统计函数,用于计算均值、方差、最小值、最大值、计数和非零计数。org.apache.spark.mllib.tree:实现了分类和回归决策树的算法。

    • org.apache.spark.mllib.util:这些是用于加载、保存、预处理、生成和验证数据的方法的集合。

    Deeplearning4j

    DeepLearning4j(或称 DL4J),是一个用 Java 编写的深度学习库。它具有分布式和单机深度学习框架,包括并支持各种神经网络结构,如前馈神经网络、RBM(Restricted Boltzmann Machine,受限玻尔兹曼机)、卷积神经网络、深度信念网络、自动编码器等。DL4J 可以解决不同的问题,比如识别面孔、声音、垃圾邮件和电子商务欺诈。

    Deeplearning4j 也是在 Apache 2.0 许可下分发的,可从 http://deeplearning4j.org 下载。该库由以下组成:

    • org.deeplearning4j.base:这些是加载类。

    • org.deeplearning4j.berkeley:这些是数学使用方法。

    • org.deeplearning4j.clustering:k-means 的聚类实现。

    • org.deeplearning4j.datasets:这是数据集的操作,包括导入、创建、迭代等。

    • org.deeplearning4j.distributions:这是用于分发的实用方法。

    • org.deeplearning4j.eval:这些是评估类,包括混淆矩阵。

    • org.deeplearning4j.exceptions:实现异常处理程序。

    • org.deeplearning4j.models:这些是监督学习算法,包括深度信念网络、堆叠式自动编码器、堆叠去噪式自动编码器和 RBM。

    • org.deeplearning4j.nn:这些是基于神经网络的组件和算法的实现,例如神经网络、多层网络、卷积多层网络等。

    • org.deeplearning4j.optimize:这些是神经网络优化算法,包括反向传播、多层优化、输出层优化等。

    • org.deeplearning4j.plot:这些是用于呈现数据的各种方法。

    • org.deeplearning4j.rng:这是一个随机数据生成器。

    • org.deeplearning4j.util:这些是帮助和实用方法。

    MALLET

    机器学习语言工作包(Machine Learning for Language Toolkit,MALLET),是一个包含自然语言处理算法和实用程序的大型库。它可以用于各种任务,如文档分类、分档聚类、信息提取和主题建模。MALLET 提供了命令行界面和 Java API,适用于多种算法,如朴素贝叶斯、HMM(Hidden Markov Model,隐马尔可夫模型)、隐含狄利克主题模型(Latent Dirichlet topic model)、逻辑回归和条件随机域(conditional random fields)。
    在这里插入图片描述
    MALLET 可以在通用公共许可证 1.0 下使用,这意味着你甚至可以在商业应用程序中使用它。可以从 http://mallet.cs.umass.edu 下载。MALLET 实例由名称、标签、数据和源表示。但是,有两种方法可以将数据导入到 MALLET 格式中,如下所示:

    • Instance per file:每个文件(即文档)对应一个实例,MALLET 接受输入的目录名。

    • Instance per line:每行对应一个实例,假设使用以下格式:instance_name 标签令牌。数据将是一个特征向量,由作为标记出现的不同单词和它们出现次数组成。

    该库由以下包组成:

    • cc.mallet.classify:这些是用于训练和分类实例的算法,包括 AdaBoost、Bagging、C4.5、以及其他决策树模型、多元逻辑回归、朴素贝叶斯和 Winnow2。

    • cc.mallet.cluster:这些是无监督聚类算法,包括贪心凝聚( greedy agglomerative)、爬山算法(hill climbing)、k-best 和 k-means 聚类。

    • cc.mallet.extract:实现分词器(tokenizers)、文档提取器、文档查看器和清理器等。

    • cc.mallet.fst: 实现了序列模型,包括条件随机域、HMM、最大熵马尔科夫模型(maximum entropy Markov models),以及相应的算法和评估器。

    • cc.mallet.grmm:实现了如推理算法、学习和测试的图形模型和因子图。例如环状信念传播(loopy belief propagation)、吉布斯采样(Gibbs sampling)等。

    • cc.mallet.optimize:这些是用于寻找函数最大值的优化算法,例如梯度上升、有限内存 BFGS、随机元上升(stochastic meta ascent)等。

    • cc.mallet.pipe:这些方法是将数据处理为 MALLET 实例中的管道。cc.mallet.topics:这些是主题建模算法,例如隐含狄利克分布(Latent Dirichlet allocation)、四级弹球分布(four-level pachinko allocation)、分层 PAM、DMRT 等。

    • cc.mallet.types:实现了基本数据类型,如数据集、特征向量、实例和标签。

    • cc.mallet.util:这些是各种实用工具功能,如命令行处理、搜索、数学、测试等。

    如果你想利用关键的 Java 机器学习库进行设计、构建和部署你自己的机器学习应用,请查阅 Packt Publishing 出版社出版的《Java 机器学习》(Machine Learning in Java)一书。

    转载自:https://hub.packtpub.com/most-commonly-used-java-machine-learning-libraries/#

    下一步阅读:

    5 JavaScript machine learning libraries you need to know

    A non programmer’s guide to learning Machine learning

    Why use JavaScript for machine learning?

    展开全文
  • Java8函数式编程

    2019-06-13 15:18:48
    于是决心花点时间深入地去研究一下java8的函数式。 (想自学习编程的小伙伴请搜索圈T社区,更多行业相关资讯更有行业相关免费视频教程。完全免费哦!) 一、lambda表达式 先po一个最经典的例子——线程 public static...

    最近使用lambda表达式,感觉使用起来非常舒服,箭头函数极大增强了代码的表达能力。于是决心花点时间深入地去研究一下java8的函数式。

    (想自学习编程的小伙伴请搜索圈T社区,更多行业相关资讯更有行业相关免费视频教程。完全免费哦!)

    一、lambda表达式

    先po一个最经典的例子——线程

    public static void main(String[] args) {
      // Java7
      new Thread(new Runnable() {
        @Override
        public void run() {
          for (int i = 0; i < 100; i++) {
            System.out.println(i);
          }
        }
      }).start();
    
      // Java8
      new Thread(() -> {
        for (int i = 0; i < 100; i++) {
          System.out.println(i);
        }
      }).start();
    }
    

    第一次接触lambda表达式是在创建线程时,比较直观的感受就是lambda表达式相当于匿名类的语法糖,emm~,真甜。不过事实上,lambda表达式并不是匿名类的语法糖,而且经过一段时间的使用,感觉恰恰相反,在使用上匿名类更像是Java中lambda表达式的载体。

    使用场景

    下面的一些使用场景均为个人的一些体会,可能存在不当或遗漏之处。

    1. 简化匿名类的编码

    上面的创建线程就是一个很好简化编码的例子,此处就不再重复。

    2. 减少不必要的方法创建

    在Java中,我们经常会遇到这样一种场景,某个方法只会在某处使用且内部逻辑也很简单,在Java8之前我们通常都会创建一个方法,但是事实上我们经常会发现这样写着写着,一个类中的方法可能会变得非常庞杂,严重影响阅读体验,进而影响编码效率。但是如果使用lambda表达式,那么这个问题就可以很容易就解决掉了。

    一个简单的例子,如果我们需要在一个函数中多次打印时间。(这个例子可能有些牵强,但是实际上还是挺常遇见的)

    public class FunctionMain {
        
        public static void main(String[] args) {
            TimeDemo timeDemo = new TimeDemo();
            timeDemo.createTime = System.currentTimeMillis();
            timeDemo.updateTime = System.currentTimeMillis() + 10000;
            outputTimeDemo(timeDemo);
        }
    
        private static void outputTimeDemo(TimeDemo timeDemo) {
            Function timestampToDate = timestamp -> {
                DateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
                return df.format(new Date(timestamp));
            };
    
            System.out.println(timestampToDate.apply(timeDemo.createTime));
            System.out.println(timestampToDate.apply(timeDemo.updateTime));
        }
    
    
        interface Function {
            String apply(long timestamp);
        }
    }
    
    class TimeDemo {
        long createTime;
        long updateTime;
    }
    

    在这段代码的outputTimeDemo中我们可以看到,对于时间戳转换的内容,我们并没有额外创建一个方法,而是类似于创建了一个变量来表达。不过,这个时候出现了另一个问题,虽然我们少创建了一个方法,但是我们却多创建了一个接口Function,总有种因小失大的感觉, 不过这个问题,我们在后面的java.util.function包部分可以找到答案。

    3. 事件处理

    一个比较常见的例子就是回调。

    public static void main(String[] args) {
        execute("hello world", () -> System.out.println("callback"));
    }
    
    private static void execute(String s, Callback callback) {
        System.out.println(s);
        callback.callback();
    }
    
    @FunctionalInterface
    interface Callback {
        void callback();
    }
    

    在这里,可以发现一点小不同,就是Callback多了一个注解@FunctionalInterface,这个注解主要用于编译期检查,如果我们的接口不符合函数式接口的要求,那编译的时候就会报错。不加也是可以正常执行的。

    4. stream中使用

    这个在后面的stream中详解。

    java.util.function包

    在之前的例子中,我们发现使用lambda表达式的时候,经常需要定义一些接口用来辅助我们的编码,这样就会使得本应轻量级的lambda表达式又变得重量级。那是否存在解决方案呢?其实Java8本身已经为我们提供了一些常见的函数式接口,就在java.util.function包下面。

    接口 描述
    Function<T,R> 接受一个输入参数,返回一个结果
    Supplier 无参数,返回一个结果
    Consumer 接受一个输入参数,并且不返回任何结果
    BiFunction<T,U,R> 接受两个输入参数的方法,并且返回一个结果
    BiConsumer<T,U> 接受两个输入参数的操作,并且不返回任何结果

    此处列出最基本的几个,其他的都是在这些的基础上做了一些简单的封装,例如IntFunction就是对Function<T,R>的封装。上面的这些函数式接口已经可以帮助我们处理绝大多数场景了,如果有更复杂的情况,那就得我们自己定义接口了。不过遗憾的是在java.util.function下没找到无参数无返回结果的接口,目前我找到的方案就是自己定义一个接口或者直接使用Runnable接口。

    使用示例

    public static void main(String[] args) {
        Function<Integer, Integer> f = x -> x + 1;
        System.out.println(f.apply(1));
    
        BiFunction<Integer, Integer, Integer> g = (x, y) -> x + y;
        System.out.println(g.apply(1, 2));
    }
    

    lambda表达式和匿名类的区别

    lambda表达式虽然使用时和匿名类很相似,但是还是存在那么一些区别。

    1. this指向不同

    lambda表达式中使用this指向的是外部的类,而匿名类中使用this则指向的是匿名类本身。

    public class FunctionMain {
    
        private String test = "test-main";
    
        public static void main(String[] args) {
            new FunctionMain().output();
        }
    
        private void output() {
            Function f = () -> {
                System.out.println("1:-----------------");
                System.out.println(this);
                System.out.println(this.test);
            };
            f.outputThis();
    
            new Function() {
                @Override
                public void outputThis() {
                    System.out.println("2:-----------------");
                    System.out.println(this);
                    System.out.println(this.test);
                }
            }.outputThis();
        }
    
        interface Function {
            String test = "test-function";
    
            void outputThis();
        }
    }
    

    如上面这段代码,输出结果如下
    在这里插入图片描述
    所以如果想使用lambda表达式的同时去访问原类中的变量、方法的是做不到的。

    2. 底层实现不同

    编译

    从编译结果来看,两者的编译结果完全不同。

    首先是匿名类的方式,代码如下:

    import java.util.function.Function;
    
    public class ClassMain {
    
        public static void main(String[] args) {
            Function<Integer, Integer> f = new Function<Integer, Integer>() {
                @Override
                public Integer apply(Integer integer) {
                    return integer + 1;
                }
            };
            System.out.println(f.apply(1));
        }
    }
    

    编译后的结果如下:
    在这里插入图片描述
    可以看到ClassMain在编译后生成了两个class,其中ClassMain$1.class就是匿名类生成的class。

    那么接下来,我们再来编译一下lambda版本的。代码和编译结果如下:

    import java.util.function.Function;
    
    public class FunctionMain {
    
        public static void main(String[] args) {
            Function<Integer, Integer> f = x -> x + 1;
            System.out.println(f.apply(1));
        }
    }
    

    在这里插入图片描述
    在这里我们可以看到FunctionMain并没有生成第二个class文件。

    字节码

    更进一步,我们打开他们的字节码来寻找更多的细节。首先依然是匿名类的方式
    在这里插入图片描述
    在Code-0这一行,我们可以看到匿名类的方式是通过new一个类来实现的。

    接下来是lambda表达式生成的字节码,
    在这里插入图片描述
    在lambda表达式的字节码中,我们可以看到我们的lambda表达式被编译成了一个叫做lambda$main$0的静态方法,接着通过invokedynamic的方式进行了调用。

    3. lambda表达式只能替代部分匿名类

    lambda表达式想要替代匿名类是有条件的,即这个匿名类实现的接口必须是函数式接口,即只能有一个抽象方法的接口。

    性能

    由于没有实际测试过lambda表达式的性能,且我使用lambda更多是基于编码简洁度的考虑,因此本文就不探讨性能相关问题。
    关于lambda表达式和匿名类的性能对比可以参考官方ppt https://www.oracle.com/technetwork/java/jvmls2013kuksen-2014088.pdf

    二、Stream API

    Stream API是Java8对集合类的补充与增强。它主要用来对集合进行各种便利的聚合操作或者批量数据操作。

    1. 创建流

    在进行流操作的第一步是创建一个流,下面介绍几种常见的流的创建方式

    从集合类创建流

    如果已经我们已经有一个集合对象,那么我们可以直接通过调用其stream()方法得到对应的流。如下

    List<String> list = Arrays.asList("hello", "world", "la");
    list.stream();
    

    利用数组创建流

    String[] strArray = new String[]{"hello", "world", "la"};
    Stream.of(strArray);
    

    利用可变参数创建流

    Stream.of("hello", "world", "la");
    

    根据范围创建数值流

    IntStream.range(0, 100);         // 不包含最后一个数
    IntStream.rangeClosed(0, 99);    // 包含最后一个数
    

    BufferReader.lines()

    对于BufferReader而言,它的lines方法也同样可以创建一个流

    File file = new File("/Users/cayun/.m2/settings.xml");
    BufferedReader br = new BufferedReader(new InputStreamReader(new FileInputStream(file)));
    br.lines().forEach(System.out::println);
    br.close();
    

    2. 流操作

    在Stream API中,流的操作有两种:Intermediate和Terminal

    Intermediate:一个流可以后面跟随零个或多个 intermediate 操作。其目的主要是打开流,做出某种程度的数据映射/过滤,然后返回一个新的流,交给下一个操作使用。这类操作都是惰性化的(lazy),就是说,仅仅调用到这类方法,并没有真正开始流的遍历。
    Terminal:一个流只能有一个 terminal 操作,当这个操作执行后,流就被使用“光”了,无法再被操作。所以这必定是流的最后一个操作。Terminal 操作的执行,才会真正开始流的遍历,并且会生成一个结果,或者一个 side effect。

    除此以外,还有一种叫做short-circuiting的操作

    对于一个 intermediate 操作,如果它接受的是一个无限大(infinite/unbounded)的 Stream,但返回一个有限的新 Stream。
    对于一个 terminal 操作,如果它接受的是一个无限大的 Stream,但能在有限的时间计算出结果。

    常见的流操作可以如下归类:
    Intermediate
    map (mapToInt, flatMap 等)、 filter、 distinct、 sorted、 peek、 limit、 skip、 parallel、 sequential、 unordered

    Terminal
    forEach、 forEachOrdered、 toArray、 reduce、 collect、 min、 max、 count、 anyMatch、 allMatch、 noneMatch、 findFirst、 findAny、 iterator

    Short-circuiting
    anyMatch、 allMatch、 noneMatch、 findFirst、 findAny、 limit

    常见的流操作详解

    1. forEach

    forEach可以说是最常见的操作了,甚至对于List等实现了Collection接口的类可以不创建stream而直接使用forEach。简单地说,forEach就是遍历并执行某个操作。

    Stream.of("hello", "world", "a", "b").forEach(System.out::println);
    

    2. map

    map也同样是一个非常高频的流操作,用来将一个集合映射为另一个集合。下面代码展示了将[1,2,3,4]映射为[1,4,9,16]

    IntStream.rangeClosed(1, 4).map(x -> x * x).forEach(System.out::println);
    

    除此之外,还有一个叫做flatMap的操作,这个操作在映射的基础上又做了一层扁平化处理。这个概念可能比较难理解,那举个例子,我们需要将[“hello”, “world”]转换成[h,e,l,l,o,w,o,r,l,d],可以尝试一下使用map,那你会惊讶地发现,可能结果不是你想象中的那样。如果不信可以执行下面这段代码,就会发现map与flatMap之间的区别了,

    Stream.of("hello", "world").map(s -> s.split("")).forEach(System.out::println);
    System.out.println("--------------");
    Stream.of("hello", "world").flatMap(s -> Stream.of(s.split(""))).forEach(System.out::println);
    

    3. filter

    filter则实现了过滤的功能,如果只需要[1,2,3,4,5]中的奇数,可以如下,

    IntStream.rangeClosed(1, 5).filter(x -> x % 2 == 1).forEach(System.out::println);
    

    4. sorted和distinct

    其中sorted表示排序,distinct表示去重,简单的示例如下:

    Integer[] arr = new Integer[]{5, 1, 2, 1, 3, 1, 2, 4};    // 千万不要用int
    Stream.of(arr).sorted().forEach(System.out::println);
    Stream.of(arr).distinct().forEach(System.out::println);
    Stream.of(arr).distinct().sorted().forEach(System.out::println);
    

    5. collect

    在流操作中,我们往往需求是从一个List得到另一个List,而不是直接通过forEach来打印。那么这个时候就需要使用到collect了。依然是之前的例子,将[1,2,3,4]转换成[1,4,9,16]。

    List<Integer> list1= Stream.of(1, 2, 3, 4).map(x -> x * x).collect(Collectors.toList());
            
    // 对于IntStream生成的流需要使用mapToObj而不是map
    List<Integer> list2 = IntStream.rangeClosed(1, 4).mapToObj(x -> x * x).collect(Collectors.toList());
    

    3. 补充

    并行流

    除了普通的stream之外还有parallelStream,区别比较直观,就是stream是单线程执行,parallelStream为多线程执行。parallelStream的创建及使用基本与stream类似,

    List<Integer> list = Arrays.asList(1, 2, 3, 4);
    // 直接创建一个并行流
    list.parallelStream().map(x -> x * x).forEach(System.out::println);
    // 或者将一个普通流转换成并行流
    list.stream().parallel().map(x -> x * x).forEach(System.out::println);
    

    不过由于是并行执行,parallelStream并不保证结果顺序,同样由于这个特性,如果能使用findAny就尽量不要使用findFirst。

    使用parallelStream时需要注意的一点是,多个parallelStream之间默认使用的是同一个线程池,所以IO操作尽量不要放进parallelStream中,否则会阻塞其他parallelStream。

    三、Optional

    Optional的引入是为了解决空指针异常的问题,事实上在Java8之前,Optional在很多地方已经较为广泛使用了,例如scala、谷歌的Guava库等。

    在实际生产中我们经常会遇到如下这种情况,

    public class FunctionMain {
    
        public static void main(String[] args) {
            Person person = new Person();
            String result = null;
            if (person != null) {
                Address address = person.address;
                if (address != null) {
                    Country country = address.country;
                    if (country != null) {
                        result = country.name;
                    }
                }
            }
            System.out.println(result);
        }
    }
    
    class Person {
        Address address;
    }
    
    class Address {
        Country country;
    }
    
    class Country {
        String name;
    }
    

    每每写到这样的代码,作为编码者一定都会头皮发麻,满心地不想写,但是却不得不写。这个问题如果使用Optional,或许你就能找到你想要的答案了。

    Optional的基本操作

    1. 创建Optional

    Optional.empty();          // 创建一个空Optional
    Optional.of(T value);      // 不接受null,会报NullPointerException异常
    Optional.ofNullable(T value);     // 可以接受null
    

    2. 获取结果

    get();                                   // 返回里面的值,如果值为null,则抛异常
    orElse(T other);                         // 有值则返回值,null则返回other
    orElseGet(Supplier other);               // 有值则返回值,null则由提供的lambda表达式生成值
    orElseThrow(Supplier exceptionSupplier); // 有值则返回值,null则抛出异常
    

    3. 判断是否为空

    isPresent();       // 判断是否为空
    

    到这里,我们可能会开始考虑怎么用Optional解决引言中的问题了,于是思考半天,写出了这样一段代码,

    public static void main(String[] args) {
        Person person = new Person();
        String result = null;
        Optional<Person> per = Optional.ofNullable(person);
        if (per.isPresent()) {
            Optional<Address> address = Optional.ofNullable(per.get().address);
            if (address.isPresent()) {
                Optional<Country> country = Optional.ofNullable(address.get().country);
                if (country.isPresent()) {
                    result = Optional.ofNullable(country.get().name).orElse(null);
                }
            }
         }
         System.out.println(result);
    }
    

    啊嘞嘞,感觉不仅没有使得代码变得简单,反而变得更加复杂了。那么很显然这并不是Optional的正确使用方法。接下来的部分才是Optional的正确使用方式。

    4. 链式方法

    在Optional中也有类似于Stream API中的链式方法map、flatMap、filter、ifPresent。这些方法才是Optional的精髓。此处以最典型的map作为例子,可以看看map的源码

    public<U> Optional<U> map(Function<? super T, ? extends U> mapper) {
        Objects.requireNonNull(mapper);
        if (!isPresent())
            return empty();
        else {
            return Optional.ofNullable(mapper.apply(value));
        }
    }
    

    源码很简单,可以看到对于null情况仍然返回null,否则返回处理结果。那么此再来思考一下引言的问题,那就可以很简单地改写成如下的写法,

    public static void main(String[] args) {
        Person person = new Person();
        String result = Optional.ofNullable(person)
                .map(per -> per.address)
                .map(address -> address.country)
                .map(country -> country.name).orElse(null);
        System.out.println(result);
    }
    

    哇哇哇,相比原先的null写法真真是舒服太多了。

    map与flatMap的区别

    这两者的区别,同样使用一个简单的例子来解释一下吧,

    public class FunctionMain {
    
        public static void main(String[] args) {
            Person person = new Person();
            String name = Optional.ofNullable(person).flatMap(p -> p.name).orElse(null);
            System.out.println(name);
        }
    }
    
    class Person {
        Optional<String> name;
    }
    

    在这里使用的不是map而是flatMap,稍微观察一下,可以发现Person中的name不再是String类型,而是Optional类型了,如果使用map的话,那map的结果就是Optional<Optional>了,很显然不是我们想要的,flatMap就是用来将最终的结果扁平化(简单地描述,就是消除嵌套)的。
    至于filter和ifPresent用法类似,就不再叙述了。

    四、其他一些函数式概念在Java中的实现

    由于个人目前为止也只是初探函数式阶段,很多地方了解也不多,此处只列举两个。(注意:下面的部分应用函数与柯里化对应的是scala中的概念,其他语言中可能略有偏差)

    部分应用函数(偏应用函数)

    部分应用函数指的是对于一个有n个参数的函数f,但是我们只提供m个参数给它(m < n),那么我们就可以得到一个部分应用函数,简单地描述一下,如下
    在这里插入图片描述
    在这里插入图片描述
    在这里g就是f的一个部分应用函数。

    BiFunction<Integer, Integer, Integer> f = (x, y) -> x + y;
    Function<Integer, Integer> g = x -> f.apply(1, x);
    System.out.println(g.apply(2));
    

    柯里化

    柯里化就是把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,并且返回接受余下的参数而且返回结果的新函数的技术。换个描述,如下
    在这里插入图片描述
    Java中对柯里化的实现如下,

    Function<Integer, Function<Integer, Integer>> f = x -> y -> x + y;
    System.out.println(f.apply(1).apply(2));
    

    因为Java限制,我们不得不写成f.apply(1).apply(2)的形式,不过视觉上的体验与直接写成f(1)(2)相差就很大了。
    柯里化与部分应用函数感觉很相像,不过因为个人几乎未使用过这两者,因此此处就不发表更多见解。

    展开全文
  • C++,Java,C#,Python等各个编程语言早已经支持lambda表达式了,作为即将从业的大学生,现在学习Java函数式编程应该为时不晚。 FunctionalInterface函数式接口 四个最基本的函数式接口 基本数据类型的函数式...
  • 但是线上学习的劣势也很明显,没有线下那种学习氛围,碰到问题没法解决,在家学习很容易偷懒,极简JAVA学习营充分考虑到这些问题,通过每日实战编程练习,分队pk,助教答疑,作业点评,作业讲解,项目答辩等诸多环节...
  • 摘要: 你是一名希望开始或者正在学习机器学习Java程序员吗? 利用机器学习编写程序是最佳的学习方式。你可以从头开始编写算法,但是利用现有的开源,你可以取得更大的进步。 本文介绍了主要的平台和开放源码的...
  • 一门永不过时的编程语言——Java 软件开发。 Java编程语言占比: 据官方数据统计,在全球编程语言工程师的数量上,Java编程语言以1000万的程序员数量位居首位。 而且很多软件的开发都离不开Java编程,因此其程序员...
  • 他最主要的改进就是增加了函数式编程的功能(为了解决java程序总是冗长的问题),或许会感到奇怪,函数式编程和并发似乎没什么关系,但是java中与并发相关的API的实现,却是以函数式编程的范式来实现的。所以为了更...
  • Java POI使用学习

    2017-04-16 11:10:46
    主要学习用POI来进行excel的读取,jar包的下载地址http://poi.apache.org/。jar包使用哪个http://blog.csdn.net/szwangdf/article/details/39053859。 相关博客:...
  • 2016最新Java学习计划

    2018-04-02 23:05:53
    一、Java学习路线图 二、Java学习路线图——视频篇 六大阶段学完后目标知识点配套免费资源(视频+笔记+源码+模板)密码 第一阶段Java基础 入门学习周期:35天学完后目标:1.可进行小型应用程序开发2.对数据库进行...
  • Java(JavaEE)学习线路图

    2018-12-19 12:12:59
    Java学习线路图 Java教程 Java 教程 Java 简介 Java 开发环境配置 Java 基础语法 Java 对象和类 Java 基本数据类型 Java变量类型 Java修饰符 Java运算符 Java循环结构 Java分支结构 Java...
  • Java 学习路线

    2018-01-06 13:23:44
    对于入门java将近两年的时间,曾经迷惘过,一直想知道java的具体学习路线,看过了许许多多的java经验分享的帖子,评论,以及其他各种培训机构所谓的学习路线,发现没有一个符合我个人需求的学习路线,根据个人实际的...
  • 推荐一个学习资料群:java学习》+交流 523401738一、学习前的准备工作java语言一般用于大型的服务器程序开发,所有有必要了解如下内容:Unix开发环境Unix系统原理、Unix开发环境、Unix常用命令。熟练掌握Unix常用...
  • 一门永不过时的编程语言——Java 软件开发,虽然我是做C/C++开发的,但是也是学习过并且之后肯定还要继续学习Java的,原因就是5G时代,万物互联,更多的终端会使用安卓系统,随之而来的Java开发必然是会越来越火爆,...
  • java学习资源分享

    2018-03-18 21:20:49
    1.Java基础视频  《张孝祥JAVA视频教程》完整版...Java多线程与并发高级应用(传智播客) 尚学堂JAVA视频下载大全(持续更新中...请关注!)(尚学堂) 《动力节点,王勇JAVA系列视频教程》(东西网) 张孝祥Java高新技术
  • Javaweb系统学习路线:Java系统学习路线:第一阶段技术名称第一阶段: Java 基础,包括 Java 语法,面向对象特征,常见API,集合框架。(基础)第二阶段:Java API:输入输出,多线程,网络编程,反射注解等,Java 的...
  • 这几天一直在摸索java的...学习前请检查java环境配置,能在cmd.exe中输入java -version能显示环境信息,如: D:\workspace>java -version java version "1.7.0_05" Java(TM) SE Runtime Environment (build 1.7.0_05-b05
  • 1.Java语言中,只限子类或者同一包中的类的方法能访问的访问权限是( C ) A、public B、private C、protected D、无修饰 private: Java语言中对访问权限限制的最窄的修饰符,一般称之为“私有的”。被其修饰的...
  • 对于Java开发人员来说,最近几年的时间中,Java生态诞生了很多东西。...在2019年初,我认为Java 10还是比较新的,但是,在我学习完所有Java 10的特性之前,Java 11、Java 12、Java 12 已经接踵而至...
1 2 3 4 5 ... 20
收藏数 134,289
精华内容 53,715
热门标签
关键字:

java函数库学习