Java全栈工程实践

浅谈计算机系统架构

读完本章节,你将收获以下几点

  1. 计算机硬件架构及其主要硬件的功能描述
  2. 推荐给全栈开发者的硬件
  3. 计算机软件架构及其常用软件

计算机硬件系统

现代计算机是由运算器、控制器、存储器、输入设备、输出设备五大部分组成,它们各司其职,完成了数据的计算、存储、传输任务,整体架构如下图所示
计算机系统架构

下面是它们各个组件的功能介绍:

  • CPU:也被称为中央处理器,由运算器和控制器组成,其主要作用是用来计算数据(从内存中获取指令并执行后将结果返回给内存或者写入到磁盘)和控制其他设备(声卡显卡,鼠标键盘)协同工作。目前主流的CPU架构有基于Intel的复杂指令集的X86架构(32位和64位)和手机(ARM指令集),服务器(SPARC指令集)的精简指令集。CPU通过总线(数据总线、地址总线、控制总线)和外部进行交互,数据的计算是在CPU内部的寄存器完成的。

  • 内存:采用编址存储,其主要作用是用来作为程序的工作区,程序运行时会被加载到内存,断电后数据会丢失。32位操作系统只能最多使用4G内存(编址的数量2的32次方,已经逐渐被淘汰),64位CPU通常可以使用4G以上的内存(编址可达4G*4G,但是受限于其他组件),单台服务器的内存根据不同的用途通常是16-128G。

  • 硬盘:其主要作用是永久性存储海量(TB或者PB级别)的数据,分为机械式硬盘(HDD)(7500rpm,15000rpm)和固态硬盘(SSD)两大类,门户网站通常是两种硬盘混合使用。

  • IO设备:其主要作用是用来数据的输入输出,常见的输入设备包括鼠标键盘,常见的输出设备包含声卡,显卡,打印机等等。还有个典型输入输出设备:网卡主要是负责数据在网络上的传输。

在程序故障诊断或者做程序性能优化是时除了考虑程序自身之外还需要考虑CPU,内存,磁盘和网卡的”健康状况“。

计算机硬件的IO各层次性能汇总

组件名称 延时、带宽
CPU
Cache L1,L2,L3 0.5-15ns、20-60GB/S
内存 30-100ns、2-12GB/S
SSD硬盘 10us-1ms、50MB-2GB/S
普通硬盘 5-20ms、50-200MB/S
网卡-网卡 100us-1ms、10MB-10GB/S

从该表格中可以看出CPU延时(最低)和带宽(最高),而普通硬盘是性能最差的,根据木桶效应,在性能优化时应该优先优化磁盘。

推荐给全栈开发者(前端+后端(服务端)+大数据/云计算)的硬件配置

目前(2018/01)为止能买到的最好的硬件配置的笔记本

Windows/Linux(Ubuntu):Razer/雷蛇 灵刃专业版 RZ09-0166 GTX1080 Pro游戏笔记本电脑

主要硬件配置如下:

硬件名称 规格
屏幕 17.3英寸16:9 4K屏幕
CPU 第七代Intel i7-7820HK 3.9GHz
内存 32GB DDR4-2667MHz
硬盘 512GSSD/1TSSD
显卡 NVIDA GeForce GTX1080显卡 8G显存

推荐先安装Windows10 x64企业版,然后通过安装的VMWareWorkStation14 Pro虚拟机上安装Linux/CentOS7.3、Linux/OpenSUSE42.2和Linux/Ubuntu16.04三个常见的Linux发行版。

而如果要从事MacOS、IOS和WatchOS的应用开发,首选当然是MacBook Pro,主要硬件配置如下(内容来自于苹果官网):

硬件名称 规格
屏幕 15.4英寸
CPU 第七代Intel i7 Turbo Boost 最高可达 4.1GHz
内存 16GB 2133MHz LPDDR3内存
硬盘 512GB SSD/1T SSD
显卡 显卡Radeon Pro 560 图形处理器,配备 4GB

MacBookPro则是安装了最新的MacOS10.13,由于缺少极少数必备软件(例如RedisDesktopManager,WPS),可以通过Mac平台上的虚拟机Parallels Desktop 13 安装了Windows10以及一些Mac平台上缺失的软件。

计算机软件系统

软件的出现实现了人和计算机之间更好的交互,软件是由一系列按照特定顺序组织的指令数据组成。
计算机软件分为系统软件和应用软件两大类,如下列表中包含常见的软件及其运行平台。从交互方式上看经历了字符(命令)交互,图形界面交互和语音手势、重力感应交互的发展过程。

  • 系统软件
人机交互方式 系统名称 平台
命令行界面 Unix(Solaris,HP Unix,AIX),Linux(CentOS) 服务器
图形界面 Windows10,MacOS10.13,Linux(Ubuntu17.10) 桌面
自然交互界面(语音、手势) Android8.0,IOS11 嵌入式(手机、汽车、电视)

* 应用软件

类型 名称 平台
通讯 微信、QQ、钉钉 Windows,MacOS,Android,IOS
购物 淘宝、天猫 Android,IOS
办公 WPS,Office Windows,Mac
支付 微信支付、支付宝 Android,IOS
外卖 美团外卖、饿了么 Android,IOS
邮件 Gmail、网易邮箱 PC,Android,IOS

系统软件主要实现和底层硬件交互,给应用软件提供运行平台。操作系统、编译器等都是属于系统软件,目前主流的桌面操作系统是Windows10,Linux(Ubuntu17),MacOS10.13,主流的移动端操作系统是Android8,IOS11,主流的服务端操作系统是Linux(CentOS7.4,Ubuntu17.10),Unix(HP Unix,Solaris,AIX)。

应用软件主要是运行在PC或者是移动终端的操作系统之上,用于解决工作和生活的各种需求,例如购物(淘宝、天猫、),聊天(微信、QQ),听音乐(QQ音乐、网易云音乐),看电影(爱奇艺、腾讯视频,优酷),收发邮件(网易邮箱,Gmail)和支付(支付宝,微信支付)等等。

而应用软分为BS(Broswer/Server)和CS(Client/Server)两种架构。

其中用于BS架构的Browser端应用开发的语言有JavaScript,Server端应用开发有Java,Python,Go,PHP。

而用于CS架构的Client端(Android,IOS,PC端)的开发语言有Java,Objective-C/Swift/C++,Server端的开发语言有Java。* *

MacOS日常开发中常用的软件列表

软件名称 功能描述
Chrome、Firefox、Safari 浏览器:Web开发必备
Tower、GitHub Desktop Git版本管理、GitHub桌面客户端
XCode MacOS/IOS/WatchOS开发必备
SecureCRT Linux SSH Client
Postman Web API 测试
AndroidStudio Android APP开发必备
IntelliJ IDEA Java开发必备
Pycharm Python开发必备
WebStorm 前端开发必备
PHPStorm PHP开发必备
GoLand Go程序开发必备
DataGrip、Navicat Permium Oracle/MySQL客户端
EdrawMax 画图
StartUML 建模
PDF Expert PDF书籍查看
微信、钉钉、QQ 即时通讯必备
有道云笔记、印象笔记 笔记
简书、GitBook、Mweb 写作
TeamViewer 远程连接
ParallelsDesktop 虚拟机(Mac运行Windows)
CleanMyMac 系统管理
WireShark、Charles 网络抓包
VPN Plus 免费VPN
Axure RP8 原型
百度网盘 资源云端存储
Dash 帮助文档查看器

编程语言发展史以及应用场景

读完本章节,你将收获以下几点

  1. 计算机编程语言的发展史
  2. 主流编程语言的应用场景
  3. 信息技术发展方向

编程语言的发展史

计算机程序设计语言经历了机器语言汇编语言高级程序设计语言的演进,其特点是让程序越来越容易开发、测试和部署,在了解编程语言发展史以及使用场景前需要了解程序到底是什么?

程序是指为了完成一项特定的任务(例如听音乐,看电影,购物)而用某种编程语言(C,C++,Java)编写的指令序列,指令是计算机进行程序控制的最小单位,由操作码(例如+ -)和操作数(例如010101)组成,所有指令的集合称为计算机的指令系统,常见的指令集包括运行于PC、服务器上基于Intel处理器的X86指令集和运行于手机的ARM指令集。不同的指令集系统是不兼容的,这也就导致PC上的程序不加修改,不能到手机上运行,反之亦然。

  • 机器语言
    计算机发展的最早期,程序员编写程序采用二进制的指令(010010101)来实现的,而每种CPU都有各自不同的指令系统(SPARC/Intel X86/ARM),因此在不同的机器上使用不同的机器语言实现。其特点是性能特别高效,而面向机器编程也就意味着不能移植,需要手动处理底层硬件的差异性,而且二进制的指令难以理解和维护。

  • 汇编语言
    随着时代和计算机技术的发展,汇编语言和编译器的出现解决了机器语言需要记住非常多的二进制机器指令的难题,但是还是没有从根本上解决移植性的问题,只是将机器指令转换为易懂的英文单词,然后由编译器编译成机器指令,因为计算机终归揭底只能识别0001110100110机器指令,而且汇编语言是面向机器的,不同机器(SPARC/Intel X86/ARM)的汇编指令是不相同的。

而机器语言和汇编语言的使用场景只有在追求绝对性能的场合(例如控制导弹的发射等等)中使用

如下所示案例演示C语言嵌套汇编语言的案例

#include <stdio.h>

/*
   C语言嵌套汇编的应用案例
  * @author tony [email protected]
  * @date 2017/10/17 16:40
  * @website www.ittimeline.net

*/
void use_asm() {

    int a, b, c = 0; //初始化声明三个整数变量并赋值为0

    a = 4; 
    b = 6;

    printf("before a=%d\tb=%d\tc=%d\n", a, b, c);

    _asm {

        //将变量a的值赋值给eax寄存器
        mov eax, a;
        //将变量b的值和寄存器eax相加
        add eax, b;
        //将相加的结果赋值给c
        mov c, eax;
    }

    printf("after  a=%d\tb=%d\tc=%d\n", a, b, c);


}

/*主程序的入口
*     @author tony [email protected]
*     @date 2017/10/17 16:38
*     @website www.ittimeline.net
*/
int main() {

    use_asm();
    getchar();

}
  • 高级程序设计语言

    高级程序设计语言的高级之处体现在开发人员在编写程序时无需关心硬件差异只需要专注于业务模块实现即可。甚至是可以实现 一次编译,到处运行(**Java通过实现不同平台的JVM,编译生成的字节码文件可以在任意的JVM上运行)。高级语言通常都需要编译器或者是解释器将源码编译或者解释执行。高级语言主要分为面向过程和面向对象两种,其中典型的面向过程语言就是C,面向对象的编程语言有Java,C++等等。**

先看下目前(2018年1月)世界上主流语言的排行榜的前20位,数据来源于tiboe
tiboe

目前C语言排行第二, 同时C/C++的市场份额是超过Java的。

主流编程语言的应用场景介绍

目前主流的语言主要有Java/C/C++/C#,Pyhton,PHP,JavaScript,Swift,Objective-C,Go语言,它们拥有各种不同的使用场景。

编程语言主要是用来编写软件,而开发软件的类型主要分为系统软件和应用软件,系统软件主要有Windows,Linux(Ubuntu,CentOS,OpenSUSE),MacOS,Android,IOS,通常使用的系统软件开发语言为少量的汇编以及大量的C语言。

目前在典型的中小型互联网公司的应用软件的后台业务逻辑通常都是采用Java或者Python,C/C++/Go实现。前台展示通常包含PC,H5,Android,IOS和微信公众号、微信小程序。

信息技术发展方向

目前国内一线城市(北上广深)中的一线互联网企业主要有阿里巴巴、腾讯、百度、平安、滴滴、新美大等等,他们所涉及的领域包括电商、社交、游戏、人工智能、金融、出行、团购外卖等等。

而这些领域在技术上的实现通常会涵盖移动端、前端和服务端

移动端主要以Android,IOS为主,而开发Android App必须熟练掌握Java/Kotlin技术栈,开发IOS必须掌握Objective-C/Swift技术栈。

前端主要以HTML5+CSS3+JavaScript/ECMAScrip为基础的bootstrap,jQuery,AngularJS,VueJS,ReactJS,NodeJS,Webpack等框架的技术栈。

服务端主要以Linux(CentOS\Ubuntu)为主,如果想做服务端底层开发,必须熟练掌握C/C++技术栈,如果想做服务端应用层开发必须掌握Java/Python/Go技术栈。

游戏主要是Cocos2d-x和Unity3d,主要开发语言是C++和C#。

前言的方向是人工智能(Python)以及区块链(Go),物联网

大数据以及云计算因为是以海量数据为基础的,只有在巨头(Google,Amazon,Facebook,Apple,Microsoft,Alibaba,Tencent,Baidu)公司才会有用武之地

任何事物都是从无到有,可能逐渐发展壮大,也可能转瞬即逝

Java的起源

Java最初是20世纪90年代初期由SUN(Stanford University Network)公司的詹姆斯高斯林(Games Gosling)领导开发的Green项目,该项目最初的目的是为消费类电子产品(微波炉、机顶盒、电视机、电话等等)写一个通用的控制系统。

而90年代末期赶上欧美互联网爆发式的增长,得益于互联网环境的差异性(不同的操作系统,不同的硬件),Java语言开始逐渐流行起来,而且由于开源得到众多公司的支持,其开发平台也逐渐变得强大起来。

Java语言的特点

  • 跨平台:通过在不同的平台(Windows,Linux,MacOS)采用C/C++语言实现不同的JVM(Java Virtual Machine),使得源文件经过编译生成的字节码文件可以运行在在任意平台之上(实际上是运行在JVM之上),通常企业中都是在Windows上开发Java应用程序,然后不做任何修改直接可以部署到Linux(CentOS,Ubuntu)上运行。

  • 简单易用:Java是基于C++语言改编而来,作为一门纯粹的面向对象的编程语言,继承了C++的各种优点,也剔除了C++中难以理解的指针、虚基类、运算符重载等概念,同时有垃圾回收器(Java9默认是G1)来管理对象,不需要程序员手动释放内存。

  • 安全可靠:目前Java由于安全可靠的特性已经被广大的电商、P2P、金融、证券、保险、银行等公司根据自身的业务来构建分布式应用。

  • 开源:通过读取Java设计人员编写的源代码可以提高自身的编程功底,同时Java自从JDK1.4以后为了简化企业级开发,许多开源组织开发了例如MyBatis、Hibernate、Spring Framework,SpringBoot,SpringCloud等开源框架。

Java语言的应用场景

正是由于其跨平台、安全稳定等特性,Java在企业级和移动端应用开发占据广阔的市场。

企业级:银行(中国工商银行、中国建设银行、招商银行)、证券、电信(中国移动、中国联通)、互联网金融(平安、宜信)、电商(淘宝、京东、唯品会)、生活服务(美团点评、饿了么)

移动端:Android App

如果想知道哪些公司在招聘Java工程师,可以去访问拉勾网 或者是猎聘网,然后直接搜索Java即可。

JDK的发展史

JDK全称是Java Development Kit,用来开发和运行Java程序的工具集,自从Java语言在1995年第一次发布后,为了开发功能强大的应用,Sun公司陆续发布了JDK1.0-1.6,而2009年Oracle以74亿美金收购Sun公司,获得两项资产:Java和Solaris。

如下表格所示,列举了JDK的版本及其对应的年份以及主要的特性

版本 年份 主要特性
JDK1.0 1996年 运行和开发环境的工具集
JDK1.1 1997年 JIT(即使编译)编译器
JDK1.2 1998年 将Java分成三个版本,J2SE,J2ME,J2EE,同时发布JSP,Servlet,EJB规范
JDK1.4 2002年 著名的开源框架struts,spring,hibernate,应用服务器WebLogic,WebSphere诞生
JDK1.5 2004年 改名:J2SE,J2ME,J2EE变为JavaSE,JavaME,JavaEE,同时增加了注解、泛型、可变参数,自动拆装箱等功能
JDK1.6 2006年
JDK1.7 2011年 支持二进制整数,数值之间可以使用下划线,支持字符串的switch语句,try with resources语句
JDK1.8 2014年 lambda表达式,流式编程
JDK1.9 2017年 jshell,模块化系统,G1垃圾回收器

Java的三大版本介绍

Java分为三个版本,分别是JavaSE(Java Standard Edition),JavaEE(Java Enterprise Edition)和JavaME(Java Micro Edition),下面是不同版本的应用场景。

JavaSE主要是用于开发桌面应用,例如IntelliJ IDEA,Eclipse,Xmind等等。

JavaME主要用于开发嵌入式设备的应用

JavaEE主要是用于开发企业级应用,例如电商、证券、银行、互联网金融、物流等等都是基于JavaEE技术栈构建的

而JavaSE是Java技术体系的基础内容,那些流行的开源框架(SpringFramework,SpringBoot,SpringCloud,Dubbo,MyBatis,Hibernate)和Web容器(Tomcat,Jetty)都是基于JavaSE基础之上构建的。

JDK的下载安装和配置

JDK的介绍

如果想开发Java程序,必须先去Oracle官网下载和安装JDK,在这之前,先了解下什么是JDK?

JDK(Java Develpoment Kit)是Oracle公司(2009年SUN公司被其收购)推出的开发和**运行**Java程序的工具集,其整体架构(基于JDK8.0)如下图所示。
java8架构图
JDK包含了开发和运行Java程序所需要的编译(javac)和运行(java)环境,但是并没有提供类似于VisualStudio2017那样强大的集成开发环境。

而JRE包含了运行Java程序所需要的运行环境,从上面的架构图看出主要包含了JVM和核心类库、发布工具等等。

这里说明下Java能跨平台的本质原因

当我们去Oracle官网下载JDK时,会让我们选不同操作系统(MacOS,Linux,Windows,Solaris)版本的JDK下载,如下图所示:
java version
通过不同平台的JDK实现屏蔽了底层操作系统和硬件的差异性, 从而实现源码一次编译,到处运行。

JDK9的文档地址:https://docs.oracle.com/javase/9/index.html

JDK的下载

针对各自电脑的操作系统,去Oracle官方下载对应的JDK即可,目前最新的JDK版本为今年(2017/09/21)发布的JDK9。

下载地址为:http://www.oracle.com/technetwork/java/javase/downloads/jdk9-downloads-3848520.html

macOS直接复制http://download.oracle.com/otn-pub/java/jdk/9+181/jdk-9.0.1_osx-x64_bin.dmg该地址即可下载

MacOS10.13下JDK9的安装

在下载完成之后,可以根据JDK9图形化界面引导完成安装,默认的安装路径是/Library/Java/JavaVirtualMachines/jdk-9.0.1.jdk/,

然后配置环境变量JAVA_HOME和PATH即可,命令如下所示

然后配置环境变量JAVA_HOME和PATH即可,命令如下所示

MacBookPro:~ tony$ su root #切换到root用户 
sh-3.2# vim /etc/profile #编辑配置文件

然后按键盘i键,进入编辑模式,在profile文件中增加如下配置

export JAVA_HOME=/Library/Java/JavaVirtualMachines/jdk-9.0.1.jdk/Contents/Home
export PATH=$PATH:$JAVA_HOME/bin

JAVA_HOME环境变量是Maven/Gradle或者Tomcat等Java应用所需要的

然后按esc和:wq 保存并退出vim编辑器,接着 source /etc/profile 也就是让修改后的profile配置文件立即生效

MacBookPro:~ tony$ source /etc/profile

接下来就可以使用Java的javac和java命令来验证JDK是否安装成功了,如下所示

MacBookPro:jdk-9.0.1.jdk tony$ javac -version
javac 9.0.1
MacBookPro:jdk-9.0.1.jdk tony$ java -version
java version "9.0.1"
Java(TM) SE Runtime Environment (build 9.0.1+11)
Java HotSpot(TM) 64-Bit Server VM (build 9.0.1+11, mixed mode)
MacBookPro:jdk-9.0.1.jdk tony$

如果能够正确运行java和javac命令并且能够查看到对应的编译和运行的版本信息,证明JDK已经安装成功。

在公司开发Java项目时,提前询问运维的同事当前项目使用的JDK版本(例如JDK-8u144),然后在开发环境安装相同的JDK版本(JDK-8u144)来开发项目,这样能够屏蔽JDK的差异性,避免开发环境和生产环境因为JDK的版本问题引发程序的Bug。

Java企业级开发环境搭建

在公司开发企业级Java项目时需要用到哪些工具呢?

操作系统

目前主流的桌面系统包含Windows,MacOS,推荐使用MacBook Pro15.4 +MacOS10.13来作为Java开发的操作系统(它的好处后面会慢慢体会到),当然目前国内主流的Java开发的桌面系统是Windows。

集成开发环境

由于JDK虽然提供了开发和运行Java程序的工具集,但是没有提供一个类似于VisualStudio那样功能强大的集成开发环境,当然目前市场上也有像Eclipse的Java IDE,很多Java程序员都应该在项目开发中使用过。这里我推荐一个更加优秀的开发工具IntelliJ IDEA。

IntelliJ IDEA是来自捷克的Jetbrains公司开发的Java开发工具(完爆Eclipse/MyEclipse)。有旗舰版和社区版两大版本,相对社区版而言旗舰版的功能更加丰富。官网提供了两个版本之间差异的详细比较。擅长企业级应用、移动应用以及Web应用开发。

支持MacOS10.7+,Windows7+,Linux(Ubuntu14+)三大主流操系统,支持主流技术(Maven,Gradle,Git,Tomcat…)和框架(Spring,SpringBoot,SpringCloud…),同时支持Scala,Groovy等其他基于JVM的编程语言。

通过插件可以支持Python、Kotlin,PHP等编程语言以及数据库访问等等,还有日常开发中常用的GitHhub,MarkDown等等诸多你想要的好用的工具。

想了解更多信息,可以参考官网信息:http://www.jetbrains.com/idea/

官方文档地址http://www.jetbrains.com/idea/documentation/

中文文档:https://github.com/judasn/IntelliJ-IDEA-Tutorial

IntelliJ IDEA 下载安装

IntelliJ IDEA提供了主流操作系统(MacOS,Windows,Linux(Ubuntu))的下载,拥有收费的旗舰版和免费的社区版,目前(2017/12/14)最新的版本为2017.3.1版本。

macOS版的下载地址为https://download.jetbrains.8686c.com/idea/ideaIU-2017.3.1.dmg

而IntelliJ IDEA在MacOS下的安装过程也非常简单,只需要把下载完成的IntelliJ IDEA.app拖放到应用程序文件夹即可。

IntelliJ IDEA基本配置

安装完IntelliJ IDEA后,可以根据自身的硬件配置调整IntelliJ IDEA的VM Options通过菜单Help->Edit Custom VM Options,该文件可以调整IntelliJ IDEA的VM配置,若是16G以上的内存可以参考如下配置:

-Xms512m
-Xmx4096m
-XX:ReservedCodeCacheSize=480m
-XX:+UseCompressedOops
-Dfile.encoding=UTF-8
-XX:+UseConcMarkSweepGC
-XX:SoftRefLRUPolicyMSPerMB=100
-ea
-Dsun.io.useCanonCaches=false
-Djava.net.preferIPv4Stack=true
-XX:+HeapDumpOnOutOfMemoryError
-XX:-OmitStackTraceInFastThrow
-Xverify:none

-XX:ErrorFile=$USER_HOME/java_error_in_idea_%p.log
-XX:HeapDumpPath=$USER_HOME/java_error_in_idea.hprof
-Xbootclasspath/a:../lib/boot.jar

IntelliJ IDEA插件安装

在做项目开发之前,需要提前安装几款必备的插件,包括.gitignore,git flow integration,Alibaba Java Coding Guidelines,Key Promoter X,CheckStyle-IDEA,Lombok Plugin

IntelliJ IDEA插件的安装支持在线和离线两种方式安装,在线安装就是通过从IntelliJ IDEA自带的插件仓库搜索需要的插件安装即可,不过安装后需要重启后才能生效。

详细过程如下所示

start idea

plugins menu
plugins install type

这里以.ignore插件为例介绍在线插件的安装方式,如下图所示
install_plugins
IntelliJ IDEA会自动下载并安装插件,然后重启IDEA即可。

而离线安装就是通过http://plugins.jetbrains.com/网站查找对应的插件,然后在IntelliJ IDEA的离线插件安装导入插件即可。

项目构建工具

现在进行Java项目开发时,除了使用功能强大的IDE外,还需要用到项目构建工具(例如Maven,Gradle)用于项目构建的工作。

这里以maven为例,介绍其在项目中是如何完成构建工作的。

maven的下载地址:http://maven.apache.org/download.cgi,解压即可,无需要安装。

maven的配置也是非常简单:只需将MAVEN_HOME/bin目录添加到环境变量中即可。过程和JDK安装配置一样,完整配置过程如下:

MacBookPro:~ tony$ su root #切换到root用户 
sh-3.2# vim /etc/profile #编辑配置文件
export MAVEN_HOME=/Users/tony/Documents/Apache/apache-maven-3.5.0
export PATH=$PATH:$MAVEN_HOME/bin

验证Maven是否安装配置成功,使用mvn -v即可

MacBookPro:~ tony$ mvn -v
Apache Maven 3.5.0 (ff8f5e7444045639af65f6095c62210b5713f426; 2017-04-04T03:39:06+08:00)
Maven home: /Users/tony/Documents/Apache/apache-maven-3.5.0
Java version: 9.0.1, vendor: Oracle Corporation
Java home: /Library/Java/JavaVirtualMachines/jdk-9.0.1.jdk/Contents/Home
Default locale: zh_CN, platform encoding: UTF-8
OS name: "mac os x", version: "10.13.1", arch: "x86_64", family: "mac"

maven在线下载项目所依赖的第三方框架(例如spring,mybatis)的默认下载地址为https://repo1.maven.org/maven2/,鉴于国内的网络环境,建议修改M2_HOME所在目录的settings.xml配置,添加阿里云的私服地址。

<mirrors>
     <mirror>
       <id>nexus-aliyun</id>
       <mirrorOf>*</mirrorOf>
       <name>Nexus aliyun</name>
       <url>http://maven.aliyun.com/nexus/content/groups/public</url>
   </mirror>
  </mirrors>

如果想自定义本地仓库的路径,只需要修改setting.xml文件的localRepository节点的配置

  <localRepository>/Users/tony/Documents/Apache/maven_repository/</localRepository>

IntelliJ IDEA集成Maven

通常在公司开发项目时候,将Maven集成到IntelliJ IDEA中使用,而项目部署到了测试或者生产环境都是基于Maven的命令行使用。

而IntelliJ IDEA集成Maven前,首先需要更改IntelliJ IDEA中Maven的默认设置,通过File->Other Settings->Default Settings菜单找到 Maven设置,如下图示
idea_default_maven_setting
可以自定义Maven的安装路径,settings.xml和本地maven仓库路径。那样以后创建项目时默认就会使用这里的配置。

分布式版本控制系统

目前主流的分布式版本控制工具非Git莫属了,而绝大倒数开源项目都存储在Github,你可以在Github上面搜索到开源项目的源码,文档,如果能力足够强,还可以加入到开原组织,贡献自己的力量。

mac版git下载地址:https://jaist.dl.sourceforge.net/project/git-osx-installer/git-2.14.1-intel-universal-mavericks.dmg

git的安装也是双击dmg文件的pkg,跟着安装向导执行安装即可,验证是否安装成功可以使用git -v 查看

MacBookPro:tony root# git --version
git version 2.14.1

如果想使用Github的话,需要到官网去注册账号。为了能够上传代码,还需要使用git生成ssh key,命令如下:

MacBookPro:~ tony$ git config --global user.name "tony"
MacBookPro:~ tony$ git config --global user.email "[email protected]"
MacBookPro:~ tony$ ssh-keygen -t rsa -C "[email protected]"

然后将生成的ssh key(位于路径MacBookPro:~ tony$ /Users/tony/.ssh/id_rsa.pub)添加到github账号即可,如下图所示
github ssh key

IntelliJ IDEA集成Git和GitHub

如下图所示,通过查找菜单File->Other Settings->Default Settings ,IntelliJ IDEA会在git的默认安装目录下查找执行程序,用户在安装Git后点击Test按钮即可验证是否集成成功
idea_default_git_settings
如下图所示,通过查找菜单File->Other Settings->Default Settings ,IntelliJ IDEA中已经集成了GitHub的服务, 只需要输入用户名和密码即可。
[idea_default_github_settings

Java模板项目创建

在正式进入开发之前,先熟悉JDK,IntelliJ IDEA,Maven,Git在项目中的基本使用
这里采用MacOS10.13+JDK9.01+IntelliJ IDEA 2017.3.1+Maven3.5.0+Git2.1.14的环境,从零开始搭建一个完整的工程,同时介绍他们的基本使用。

IntelliJ IDEA创建基于Maven的多模块项目

1.创建一个新项目
create_project

2 设置项目类型,JDK
new maven project

3 设置maven项目信息
maven project settings

4 设置项目路径和名称
project_settings

5 创建一个模块
new_module

6 设置模块的JDK和项目类型
module_jdk_project——type

7 设置模块的ArtifactId
module_artifactId

  1. 设置模块的路径,项目名称
    module_name_location

9 项目概览
project_info

javacore作为项目父工程,因此无需src目录,手动删除即可。而javacore的pom.xml可以用来定义各个子模块需要的公共依赖
以及子模块的管理。
javacore-object作为子模块,依赖父工程javacore

添加项目公共组件

添加log4j2,testng以及commons-lang3至javacore的pom.xml文件中,完整的内容如下

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>net.ittimeline</groupId>
    <artifactId>javacore</artifactId>
    <packaging>pom</packaging>
    <version>3.0-SNAPSHOT</version>
    <modules>
        <module>javacore-object</module>
    </modules>


    <!-- 定义依赖类库的版本信息-->

    <properties>
        <log4j2.version>2.9.1</log4j2.version>
        <testng.version>6.11</testng.version>
        <guava.version>23.0</guava.version>
        <commons-lang3.version>3.5</commons-lang3.version>
    </properties>



    <dependencies>

        <dependency>
            <groupId>org.apache.logging.log4j</groupId>
            <artifactId>log4j-api</artifactId>
            <version>${log4j2.version}</version>
        </dependency>

        <dependency>
            <groupId>org.apache.logging.log4j</groupId>
            <artifactId>log4j-core</artifactId>
            <version>${log4j2.version}</version>
        </dependency>

        <dependency>
            <groupId>org.testng</groupId>
            <artifactId>testng</artifactId>
            <version>${testng.version}</version>
        </dependency>

        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-lang3</artifactId>
            <version>${commons-lang3.version}</version>
        </dependency>

    </dependencies>


</project>

添加.gitignore

在使用版本控制系统管理项目时,由于IntelliJ IDEA在创建项目时生成了其自身需要的目录和文件,例如javacore.iml,.idea等,它们不应该被提交,可以通过IntelliJ IDEA提供的.ignore插件,通过将项目中不需要版本控制的文件添加到.gitignore中即可,

创建.gitignore文件
gitignore

选择Java模板
选择Java模板
选中文件右键添加到.gitignore文件中
add gitignore

完整的.gitignore文件可以参考以下模板或者自行增加和删减

/.idea/

*.idea/

*.iml

*logs/

*.log

*.classpath

*.project

*.settings/

*.metadata/

RemoteSystemsTempFiles/

Servers/

*/target/

*.DS_Store

/itfinls/src/main/webapp/js/config.js

/itfinls/logs/

/*/*.iml

添加log4j2配置文件

日志的重要性不言而喻,目前流行的日志组件有logback,log4j2等等,这里采用log4j2作为项目各个模块(目前是javacore-object)的日志组件,记录程序的运行信息,完整的配置参考如下

<?xml version="1.0" encoding="UTF-8"?>
<!--设置log4j2的自身log级别为warn -->
<configuration status="warn">
    <properties>

        <Property name="app_name">javacore-object</Property>
        <Property name="log_path">logs/${app_name}</Property>
        <Property name="log_file_size">100MB</Property>

    </properties>
    <appenders>
        <console name="Console" target="SYSTEM_OUT">
            <PatternLayout pattern="[%d][%t][%p][%l] %m%n" />
        </console>

        <RollingFile name="RollingFileInfo" fileName="${log_path}/info.log"
                     filePattern="${log_path}/$${date:yyyy-MM}/info-%d{yyyy-MM-dd}-%i.log.gz">
            <Filters>
                <ThresholdFilter level="INFO" />
                <ThresholdFilter level="WARN" onMatch="DENY"
                                 onMismatch="NEUTRAL" />
            </Filters>
            <PatternLayout pattern="[%d][%t][%p][%c:%L] %m%n" />
            <Policies>
                <!-- 归档每天的文件 -->
                <TimeBasedTriggeringPolicy interval="1" modulate="true" />
                <!-- 限制单个文件大小 -->
                <SizeBasedTriggeringPolicy size="${log_file_size}" />
            </Policies>
            <!-- 限制每天文件个数 -->
            <DefaultRolloverStrategy compressionLevel="0" max="10"/>
        </RollingFile>

        <RollingFile name="RollingFileWarn" fileName="${log_path}/warn.log"
                     filePattern="${log_path}/$${date:yyyy-MM}/warn-%d{yyyy-MM-dd}-%i.log.gz">
            <Filters>
                <ThresholdFilter level="WARN" />
                <ThresholdFilter level="ERROR" onMatch="DENY"
                                 onMismatch="NEUTRAL" />
            </Filters>
            <PatternLayout pattern="[%d][%t][%p][%c:%L] %m%n" />
            <Policies>
                <!-- 归档每天的文件 -->
                <TimeBasedTriggeringPolicy interval="1" modulate="true" />
                <!-- 限制单个文件大小 -->
                <SizeBasedTriggeringPolicy size="${log_file_size}" />
            </Policies>
            <!-- 限制每天文件个数 -->
            <DefaultRolloverStrategy compressionLevel="0" max="10"/>
        </RollingFile>

        <RollingFile name="RollingFileError" fileName="${log_path}/error.log"
                     filePattern="${log_path}/$${date:yyyy-MM}/error-%d{yyyy-MM-dd}-%i.log.gz">
            <ThresholdFilter level="ERROR" />
            <PatternLayout pattern="[%d][%t][%p][%c:%L] %m%n" />
            <Policies>
                <!-- 归档每天的文件 -->
                <TimeBasedTriggeringPolicy interval="1" modulate="true" />
                <!-- 限制单个文件大小 -->
                <SizeBasedTriggeringPolicy size="${log_file_size}" />
            </Policies>
            <!-- 限制每天文件个数 -->
            <DefaultRolloverStrategy compressionLevel="0" max="10"/>
        </RollingFile>

    </appenders>

    <loggers>
        <logger name="org.springframework" level="INFO"/>
        <logger name ="org.ibatis" level="info"/>

        <root level="info">
            <appender-ref ref="Console" />
            <appender-ref ref="RollingFileInfo" />
            <appender-ref ref="RollingFileWarn" />
            <appender-ref ref="RollingFileError" />
        </root>

    </loggers>

</configuration>

修改项目以及模块的默认编译级别

在使用IntelliJ IDEA创建基于Maven的项目时,默认的编译级别是JDK1.5,这里我们在IDEA中将项目及其模块的编译级别改为目前的最新(1.9)编译级别,否则后续代码如果使用了1.5以后的新特性,代码将会无法编译通过。MacOS中的IntelliJ IDEA选中项目后可以使用command+;快键键定位到修改编译级别的菜单,如下图所示。
project_compile_level
如果想要修改模块(例如javacore-object)的编译级别,只需要选中模块修改即可。在IDEA中,同一个项目的不同模块可以使用不同版本的JDK来编译和运行。

后期所有的代码都会存放在这个javacore工程的不同模块中后
这里先将项目模板上传至GitHub,具体流程如下图所示

1 Share Project on GitHub
share project on github.png

2 设置项目描述信息
javacore_project_github_desc
3 添加首次需要提交的文件
javacore_first_commit
点击OK后,IDEA会自动在GitHub中创建javacore的代码仓库,同时将本地的代码push到远程的master分支上,此时可以访问https://github.com/ittimeline/javacore,就可以在远程查看仓库的代码,如下图所示。
github_javacore

Java版HelloWorld实现

HelloWorld通常是一门计算机语言的入门程序,如果你能编写出HelloWorld程序,并且能够运行,那就意味着已经迈向了计算机程序设计的大门。

Java是面向对象的程序设计语言,最小的程序单元是类(Class),而类如果想独立运行,必须包含main方法,

固定格式为public static void main(String[]args){}

HelloWorld的本质功能是打印输出一段字符串到控制台,完整的实现内容如下所示

package net.javacore.object;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

/**
 * 
 * Hello World Application
 * @author tony [email protected]
 * @date 2017-12-15-下午4:23
 * @website wwww.ittimeline.net
 * @see
 * @since JDK9.0.1
 */
public class HelloWorld {

    /**
     * 日志记录器
     */
    private static final Logger LOGGER= LogManager.getLogger();


    /**
     * Java程序的主入口,程序从main方法的第一行开始执行到最后一行
     * @param args
     */
    public static void main(String[]args){
        /*
        * 此程序运行将会输出 Hello World in MacOS10.13 With IntelliJ IDEA 2017.3.1
        * 然后还会写入到javacore/logs/javacore-object的info.log文件中
         */
        LOGGER.info("Hello World in MacOS10.13 With IntelliJ IDEA 2017.3.1");


    }

}

程序的运行结果如下所示:
helloworld_run_result

HelloWorld解析

1 java使用package来存放Java类,作用类似于windows的文件夹,通常情况下包名小写,互联网公司的包名通常都是公司网站的域名倒置,例如com.alibaba和这里的net.ittimeline

2 如果想使用第三方类库的某个类(例如这里使用的Logger),可以使用import full package.ClassName来导入某个包下的类(例如这里的org.apache.logging.log4j.LogManager)

3 Java使用Class来组织代码(例如这里的public class HelloWorld),类中可以存放成员变量,方法(例如这里的main方法),代码块和静态代码块

4 Java的注释分为单行注释(//)、多行注释和文档(/***/)注释三种,如上应用程序所示。注释的功能是增加程序的说明信息,便于后期的维护,编译器在编译程序时会删除注释的内容。

使用IntelliJ IDEA生成JavaDoc

IntelliJ IDEA提供了生成JavaDoc的工具,找到菜单Tools->Generate JavaDoc…

然后做如下图的设置
idea_generator_javadoc

之后访问/Users/tony/Document/javadoc目录的index.html,便可看到如下效果
helloworld_javadoc
可以双击HelloWorld类,查看该类的构造器方法等信息,如下图所示
helloworld_info

使用IntelliJ IDEA提交代码至GitHub

程序员的工作生涯中编码只是占据了很少的一部分工作任务,而后期的项目构建,合并代码,本地调试,系统测试会占据大部分时间,为了后期的版本管理,这里使用IntelliJ IDEA将现有的代码推送至GitHub,实现代码的分布式版本管理。

在使用IntelliJ IDEA提交代码时只需要使用菜单Git Add和Git->Commit Directory即可,如下所示
Git add

这里是将文件添加到Git的暂存区,接下来使用Git->Commit Directory提交代码,如下图所示 。
git commit directory

push commits
push commits

查看远程推送的结果
git push hello world

Java程序的运行机制

首先聊下编译型语言的运行机制(以C语言为例子)

C语言在运行之前通常需要进行预编译-编译-转汇编-链接后运行,这种语言的特点是由编译器将源代码直接编译成对应平台的机器码,同时编译器可以针对特定平台进行优化,因此性能较高,但是编译后的程序通常不能够跨平台使用。

目前流行的Python语言是解释型的编程语言,其特点是每次运行时利用解释器将源代码解释成对应平台的机器码后运行,因此效率相对于编译型的语言效率较低,但是比较容易实现移植(不用修改就可以运行在各大操作系统之上),只要在各个平台实现对应的解释器即可。

Java源程序(例如HelloWorld.java)**经过编译器编译(javac HelloWorld.java)后变成字节码(例如HelloWorld.class),字节码由虚拟机(JVM)解释执行,虚拟机将每一条要执行的字节码送给解释器,解释器将其翻译成特定机器上的机器码(0110二进制指令),然后在特定的机器上运行。**

一切都是对象

Java的书籍,如果是高级进阶的推荐,自当首推Java编程思想第四版,如果是英文基础良好的同学建议阅读英文版

这里主要解读第二章Everything Is an Object的源码和练习题

原有HelloDate采用的是java.util.包中的Date实现获取当前日期,而JDK8提供了全新的日期API,源程序如下所示

package net.ittimeline.javacore.object.code;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

import java.time.LocalDateTime;

/**
 * 显示当前日期
 * @author tony [email protected]
 * @date 2017-12-20-下午2:10
 * @website wwww.ittimeline.net
 * @see
 * @since JDK9.0.1
 */
public class HelloDate {

    private static final Logger LOGGER = LogManager.getLogger();

    public static void main(String[]args){

        //使用JDK8的API实现在指定的时区从系统时钟获取当前的日期
        LocalDateTime currentDateTime =LocalDateTime.now();
        LOGGER.info("current date time is "+currentDateTime);
    }
}

原有ShowProperties类中使用JDK中的System.getProperty(String keyName)方法获取环境变量信息,推荐使用commons-lang3组件提供的SystemUtils类获取系统环境信息,源程序如下所示

package net.ittimeline.javacore.object.code;

import org.apache.commons.lang3.SystemUtils;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

/**
 * 使用Commons-lang3组件的SystemUtils获取系统环境变量信息
 * @author tony [email protected]
 * @date 2017-12-20-下午3:21
 * @website wwww.ittimeline.net
 * @see
 * @since JDK9.0.1
 */
public class ShowProperties {

    private static final Logger LOGGER = LogManager.getLogger();

    public static void main(String[]args){

        //获取Java Home
        LOGGER.info("Java Home is "+SystemUtils.getJavaHome());

        //获取用户名称
        LOGGER.info("UserName is "+SystemUtils.USER_NAME);

        //获取操作系统信息
        LOGGER.info("OS info :"+SystemUtils.OS_NAME+"\t : "+SystemUtils.OS_VERSION);

        //获取Java Library Path
        LOGGER.info("java library path is "+SystemUtils.JAVA_LIBRARY_PATH);
    }
}

Java会对类中的成员变量根据不同的数据类型在实例化对象时赋不同的初始值,如下图所示

数据类型 初始值
byte 0
short 0
int 0
long 0
float 0.0
double 0.0
char []
package net.ittimeline.javacore.object.exercises;

import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.testng.annotations.Test;

/**
 * Java对成员变量的默认初始化
 * byte 0
 * int 0
 * long 0
 * char []
 * boolean false
 * double 0.0
 * @author tony [email protected]
 * @date 2017-12-25-下午2:36
 * @website wwww.ittimeline.net
 * @see
 * @since JDK9.0.1
 */
public class DefaultInitializationTest {

    //采用testng作为单元测试框架
    @Test
    public void defaultInitializationTest(){

        //实例化对象
        DefaultInitialization defaultInitialization=new DefaultInitialization();


    }
}


/**
 * 自定义类DefaultInitialization,声明开发中最常用的基本数据类型
 *
 */
class  DefaultInitialization{
    private static final Logger LOGGER = LogManager.getLogger();

    /**
     * byte默认为0
     */
    byte byteVal;
    /**
     * int默认为0
     */
    int intVal;

    /**
     * long默认为0
     */
    long longVal;
    /**
     * 字符默认为[]
     */
    char charVal;
    /**
     * boolean默认为false
     */
    boolean flagVal;
    /**
     * double默认为0.0
     */
    double dbVal;

    /**
     * 构造器会在实例化对象时被调用
     * this表示当前对象的引用
     */
    public DefaultInitialization(){

        //这里的this会调用该对象的toString()方法
        LOGGER.info("成员变量初始化对应的默认值为 "+this);

    }

    /**
     * 重写父类java.lang.Object的toString()方法
     * @return
     */
    @Override
    public String toString() {
        return new ToStringBuilder(this)
                .append("intVal", intVal)
                .append("longVal", longVal)
                .append("charVal", charVal)
                .append("flagVal", flagVal)
                .append("dbVal", dbVal)
                .toString();
    }
}

如果一个Java类想独立运行,必须要包含一个main方法,源程序如下所示

package net.ittimeline.javacore.object.exercises;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

/**
 *
 * 如果一个类想独立运行,必须包含一个main方法,格式如下
 * @author tony [email protected]
 * @date 2017-12-26-下午1:15
 * @website wwww.ittimeline.net
 * @see
 * @since JDK9.0.1
 */
public class ATypeName {

    private static final Logger LOGGER = LogManager.getLogger();

    public static void main(String[]args){
        //实例化对象
        ATypeName aTypeName=new ATypeName();

        //这里会调用父类java.lang.Object的toString()方法
        LOGGER.info(aTypeName);
    }
}

在日常企业级开发中,还可以使用JUnit或者TestNG框架作为测试框架,通过其提供的注解标注在指定的方法上便可独立运行,源码如下所示:

package net.ittimeline.javacore.object.exercises;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.testng.annotations.Test;

/**
 * 使用TestNG框架编写测试用例
 * @author tony [email protected]
 * @date 2017-12-26-下午5:11
 * @website wwww.ittimeline.net
 * @see
 * @since JDK9.0.1
 */
public class TestNGCase {


    private static final Logger LOGGER= LogManager.getLogger();


    /**
     *  使用TestNG框架的@Test注解标注在指定的方法上,就可以单独运行
     */

    @Test
    public void run(){

        LOGGER.info("execute run method");

    }
}

在日常企业级开发中,最常用的就是根据业务逻辑封装类,在类中定义(非静态)属性和方法,然后通过实例化对象后访问对象的成员变量,给其赋值,或者调用成员变量的方法实现对应的业务逻辑,源程序如下所示:

package net.ittimeline.javacore.object.exercises;

import net.ittimeline.javacore.util.LoggerUtils;
import org.apache.commons.lang3.RandomUtils;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.testng.annotations.Test;

/**
 * 成员变量的实例化以及赋值和显示赋值的结果
 * @author tony [email protected]
 * @date 2017-12-26-下午1:18
 * @website wwww.ittimeline.net
 * @see
 * @since JDK9.0.1
 */
public class DataOnly {


    //声明3个基本数据类型的成员变量
    int index;
    long id;
    boolean flag;


    /**
     * 采用commons-lang3组件的ToStringBuilder类来构建DataOnly对象的toString()方法
     * @return
     */
    @Override
    public String toString() {
        return new ToStringBuilder(this)
                .append("index", index)
                .append("id", id)
                .append("flag", flag)
                .toString();
    }

    @Test
    public void testVarriableAssignment(){
        //实例化对象
        DataOnly dataOnly=new DataOnly();
        //访问对象的成员变量,并赋值
        dataOnly.id= RandomUtils.nextLong();
        dataOnly.index=0;
        dataOnly.flag=Boolean.TRUE;

        //这里会调用dataOnly对象的toString()方法,可以观察赋值后的结果
        LoggerUtils.loggerInfo(dataOnly);
    }

}
package net.ittimeline.javacore.object.exercises;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

/**
 * @author tony [email protected]
 * @date 2017-12-27-上午12:09
 * @website wwww.ittimeline.net
 * @see
 * @since JDK9.0.1
 */
public class Storage {

    private static final Logger LOGGER= LogManager.getLogger();

    /**
     * 初始化一个字符串成员变量并赋值为Hello World
     */
    String str="Hello World";

    /**
     * 定义一个方法返回传递字符串长度的2倍
     * @param str
     * @return
     */
    int storage(String str){
        return str.length()*2;
    }


    /**
     * 打印调用storage方法的结果
     */
    void print(){

        LOGGER.info("storage(str) = " + storage(str));

    }


    public static void main(String[]args){

        Storage storage =new Storage();

        storage.print();
    }
}

static是java的关键字(赋予了特殊的含义),用来修饰类中的成员变量和方法,静态成员变量可以直接通过类名.方法名的方式来访问,方法也可以通过类名.方法名的方式来访问,应用案例如下所示

package net.ittimeline.javacore.object.exercises;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.testng.annotations.Test;

/**
 * @author tony [email protected]
 * @date 2017-12-27-上午12:17
 * @website wwww.ittimeline.net
 * @see
 * @since JDK9.0.1
 */
public class StaticTest {

    private static final Logger LOGGER= LogManager.getLogger();


    static int salary=400000;

    /**
     * 将CustomClass的静态成员变量val自增1并返回
     * @return
     */
    static int  increment(){

        //静态成员变量直接通过类名.成员变量名访问
        CustomClass.val++;

        return CustomClass.val;
    }


    /**
     * static修饰成员变量和方法的特性说姚明
     * static修饰成员变量可以直接通过类名.成员变量名
     * static修饰方法名时可以直接通过类名.方法名
     */
    @Test
    public void testStaticModifier (){


        LOGGER.info("CustomClass.val= " + CustomClass.val);
        //静态方法在当前类中可以直接使用方法名调用
        increment();
        LOGGER.info("CustomClass.val= " + CustomClass.val);
        //如果在外部类中想使用increment()方法,可以通过StaticTest.increment()调用
        StaticTest.increment();

    }


}


class CustomClass{

    /**
     * 定义一个静态的整型成员变量,并初始化值为99
     */
    static int val =99;
}

JDK5.0以后提供了一个自动拆箱/装箱的功能,所谓的自动拆箱就是就按将引用类型的值赋值给对应的基本类型的变量,自动装箱就是能够将基本类型的数值赋值给对应包装类型的变量,应用案例如下所示:

package net.ittimeline.javacore.object.exercises;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.testng.annotations.Test;

/**
 * 基本数据类型与对应的包装类型的自动拆装箱特性
 * @author tony [email protected]
 * @date 2017-12-27-上午10:08
 * @website wwww.ittimeline.net
 * @see
 * @since JDK9.0.1
 */
public class AutoboxingTest {

    private static final Logger LOGGER= LogManager.getLogger();

    /**
     * 自动装箱
     */
    @Test
    public void autoboxing(){
        //将基本数据类型的变量赋值给对应的包装类型,这里使用了JDK5提供改的自动装箱功能
        Byte byteRef=1; 
        Integer integerRef=123456789;
        Long longRef=12345689101112L;

        LOGGER.info("byteRef = "+byteRef+" integerRef = "+integerRef+" longRef = "+longRef);
    }

    /**
     * 自动拆箱
     */

    @Test
    public void unboxing(){
        Byte byteRef=1;
        byte  byteVal=byteRef;

        //Boolean类型的自动拆箱
        boolean booleanVal=Boolean.valueOf(true); 


        Double doubleRef=3.14;
        double doubleVal=doubleRef;

        LOGGER.info("byteVal = "+byteVal+" booleanVal = "+booleanVal+" doubleVal = "+doubleVal);

    }
}

数组是一组相同数据类型的集合,在日常使用中最常用的应用就是遍历数组,应用案例如下所示:

package net.ittimeline.javacore.object.exercises;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

/**
 * @author tony [email protected]
 * @date 2017-12-27-上午10:49
 * @website wwww.ittimeline.net
 * @see
 * @since JDK9.0.1
 */
public class Welcome {

    private static final Logger LOGGER= LogManager.getLogger();

    public static void main(String[]args){

        //初始化一个字符串数组
        String[]books =new String[]{"《《Java编程思想第四版》》","《《Java核心技术第十版》》","Java8实战"};


        for(int i=0;i<books.length;i++){
            //循环遍历数组的元素
            LOGGER.info("boks["+i+"]"+books[i]);
        }
    }
}

变量

程序运行时数据都会被加载到内存,而内存实际上就是有地址编号并且是连续的存储空间,高级语言为了方便操作内存中的数据,引入了数据类型和变量的概念。

变量故名思议就是变化的数据,生活中无处不在的变量,例如股市的上升下降,人民币升值贬值,游戏中人物的生命值等等。

而变量的本质就是容器,这个容器用于存储计算机根据不同的逻辑执行对应的运算后的结果。

命名规范

遵守命名规范可以提高代码的可读性和维护性

以下命名规范节选自阿里巴巴Java开发手册(终极版),请在后期开发中务必遵守,具体可以使用阿里巴巴提供的基于IntelliJ IDEA插件来做代码规约检查。

1 【强制】代码中的命名不能以下划线(_)和美元符号($)开始和结束

反例: _name、 namename 、name_

2 【强制】 代码中的命名严禁使用拼音与英文混合的方式,更不允许直接使用中文

说明:正确的英文拼写和语法可以让阅读者易于理解,避免歧义。

正解: aliaba、taobao

反解:DaZhePromotion、getPingfenByName()

3 【强制】类名使用UpperCamelCase风格,但是以下情形除外:DO/BO/DTO/VO/AO/PO等

正解:MarcoPolo/UserDO/XmlService/TcpUdpDeal

反解:marcoPolo/UsrDo/XMLService/TCPUDPDeal

4【强制】方法名、参数名、成员变量名、局部变量都统一采用lowerCamelCase的风格,必须采用骆驼峰的格式

正解:localValue/getHttpMessage()/inputUserId

声明和初始化

Java是强类型的语言,这就意味着在使用变量类型之前必须声明变量的类型,不同变量的数据类型在占据的内存空间,存放数据的极限值以及能够执行的运算是不尽相同的。

Java语言与C的不同之处在于无法直接获取变量的地址,因为Java语言没有提供直接操作内存的API

变量声明的格式:数据类型 变量名=值; 如下应用所示

package net.ittimeline.javacore.varriable;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.testng.annotations.Test;

/**
 * @author tony [email protected]
 * @date 2017-12-27-上午11:15
 * @website wwww.ittimeline.net
 * @see
 * @since JDK9.0.1
 */
public class VarriableTest {


    private static final Logger LOGGER= LogManager.getLogger();

    /**
     * 变量声明的格式: 数据类型 变量名=值
     */
    @Test
    public void varriableDeclare(){

        //数据类型 变量名=值;
        int age =28;
        LOGGER.info("age = "+age);

        Double value=3.14;

        LOGGER.info("value = "+value);
    }
}

变量的交换

在后续的排序算法中会经常使用到变量的交换,常用的变量交换有如下三种方式实现

通过中间变量实现两个变量的交换

特点:中间变量实现交换需要开辟临时的内存空间,以空间换取时间

通过算术运算符实现两个变量的交换

特点:算术运算不需要开辟临时的内存空间,但是需要注意交换变量的算术运算结果的数据溢出

通过亦或运算符实现两个变量的交换

特点:不需要开辟内存空间,也不需要考虑算术运算的数据溢出,是变量交换的最佳实现

/**
 * 自定义类实现变量交换的三种方法
 * @author tony [email protected]
 * @date 2017-12-27-上午11:15
 * @website wwww.ittimeline.net
 * @see
 * @since JDK9.0.1
 */
class VarriableSwap{

    private static final Logger LOGGER= LogManager.getLogger();



    /**
     * 通过中间变量实现两个变量的交换
     * @param sources 交换变量的数组
     * @return
     */
    public int[] varriableSwapWithTemp(int[] sources){
        int[]target= new int[2];

        if(checkSources(sources)){
            LOGGER.info("swap with temp before sources is "+ Arrays.toString(sources));

            int tmp=sources[0]; //tmp=5 sources[0]=5
            sources[0]=sources[1]; //sources[1]=10 sources[0]=10
            sources[1]=tmp;


            //把交换位置后的数组元素赋值给新的target数组
            target=sources;
            LOGGER.info("swap with temp after target is "+Arrays.toString(target));

        }

        return target;

    }

    /**
     * 通过算术运算实现两个变量的交换
     * @param sources 交换变量的数组
     */
    public int[] varriableSwapWithAlg(int[]sources){

        //遍历交换之前的数组元素
        int[]target=new int[2];
        if(checkSources(sources)){
            LOGGER.info("swap with alg before sources is "+ Arrays.toString(sources));

            sources[0]=sources[0]+sources[1];
            sources[1]=sources[0]-sources[1];
            sources[0]=sources[0]-sources[1];

            target=sources;
            LOGGER.info("swap with alg after target is "+Arrays.toString(target));

        }
        return target;
    }

    /**
     * 使用亦或运算符实现两个变量的交换
     * @param sources 交换变量的数组
     * @return
     */
    public int[] varriableSwapWithXOR(int[]sources){
        int[]target=new int[2];

        if(checkSources(sources)){
            LOGGER.info("swap with xor before sources is "+ Arrays.toString(sources));

            sources[0]=sources[0]^sources[1];
            sources[1]=sources[0]^sources[1];
            sources[0]=sources[0]^sources[1];

            target=sources;
            LOGGER.info("swap with xor after target is "+Arrays.toString(target));

        }

        return target;

    }


    /**
     * 校验数组的元素是否为2个长度
     * @param soures
     * @return
     */
    public boolean checkSources(int[]soures){

        boolean flag=true;

        if(soures.length!=2){
            LOGGER.info("数组的元素不合法");
            flag=false;
        }

        return flag;
    }

}

完整的变量交换测试用例如下所示

采用了TestNG框架,其中initData()方法主要用于初始化测试数据,由于该方法标注了@BeforeClass注解,因此该方法会在当前类的第一个测试方法之前运行

package net.ittimeline.javacore.varriable;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.testng.annotations.*;

import java.util.Arrays;

/**
 * @author tony [email protected]
 * @date 2017-12-27-上午11:15
 * @website wwww.ittimeline.net
 * @see
 * @since JDK9.0.1
 */
public class VarriableTest {


    private static final Logger LOGGER= LogManager.getLogger();


    /**
     * 该数组用于存储待交换的2个变量元素
     */
    int[]sources;

    /**
     * 该数组用于存储交换后的2个变量元素
     */
    int[] target;

    /**
     * 引用封装了3种变量交换实现的对象
     */
    VarriableSwap varriableSwap;

    /**
     * 初始化测试数据
     */
    @BeforeClass
    public void initData(){
        sources=new int[]{5,10};
        varriableSwap=new VarriableSwap();
        target=new int[2];
    }


    /**
     * 测试使用临时变量实现交换变量
     */
    @Test
    public void testVarriableSwapWithTemp(){


         varriableSwap=new VarriableSwap();
         target=varriableSwap.varriableSwapWithTemp(sources);
    }

    /**
     * 测试使用算术运算符实现变量交换
     */
    @Test
    public void testVarriableSwapWithAlg(){
        target =varriableSwap.varriableSwapWithAlg(sources);

    }

    /**
     * 测试使用亦或运算符实现变量交换
     */

    @Test
    public void testVarriableSwapWithXOR(){
        target =varriableSwap.varriableSwapWithXOR(sources);


    }

}

数据类型

计算机的核心任务就是各种数据的运算,其中最常用的数据类型有字符串(String),小数(BigDecimal),整数(Integer)和日期(LocalDate)。

Java提供了8种基本数据类型,还提供了对应的包装类型,它们的对应类型如下表格所示。

基本数据类型 包装数据类型
byte Byte
short Short
int Integer
long Long
float Float
double Double
char Character
boolean Boolean

其中byte,short,int,long分别用于存储不同大小的整数(后面分析包装类源码时会明确大小)。

float,double用于存储小数,但是它们只能用于科学计算和工程计算,如果涉及到金融业务,推荐使用java.math.BigDecimal类来代替,具体原因后面会详细解释。

char表示存储字符,Java支持Unicode(啥是Unicode?后面也会详细解释),因此可以存储字母、中文以及Unicode字符,日常开发中char使用的较少,但是String类的底层实现实际上就是封装了字符数组(char[])

boolean的结果只能是true或者false,它们通常用于逻辑、关系运算的计算结果。

通过JDK1.5以后新增的自动拆装箱特性,可以将基本数据类型当作Java中的对象来处理,如下应用案例所示

package net.ittimeline.javacore.datatype;

import org.testng.annotations.Test;

/**
 * 基本数据类型和包装类
 * @author tony [email protected]
 * @date 2017-12-27-下午3:10
 * @website wwww.ittimeline.net
 * @see
 * @since JDK9.0.1
 */
public class DataTypeTest {

    @Test
    public void primitiveTypeWrapperType(){
        int num=100;
        Integer value=new Integer(100);
        //JDK5自动装箱
        value=num;
    }
}

基本数据类型和包装类的对比

这里以最常用的基本数据类型int为例

相等性判断:基本数据类型使用双等号(==)判断相等性,包装类型使用equals()方法(来自于java.lang.Object)来实现相等性判断

内存存储:基本数据类型存储于JVM的栈内存中,而包装类存储于JVM的堆内存中,栈内存只是存储包装类的引用,例如Integer value=new Integer(100),value就是对象的引用地址,存储在栈内存,而new Integer(100)则是存在JVM的堆空间中。

因此一个int占据4个字节,而一个Integer占据3个4个字节,分别是对象的引用占据4个字节,对象的int值占据4个字节,对象中对Integer对象的引用占据4个字节。此外Java需要使用额外的内存来支持对象的垃圾回收,但是基本数据类型不需要。

初始化:对象中的基本数据类型的成员变量初始化值为0,而包装类通常是null

性能:基本数据类型的运算性能高于包装类。

整型

Java语言提供了四种不同大小的整型类型,按照占据字节大小升序排序分别是byte,short,int,long类型,它们与C语言的不同之处在于同一个类型在各个平台上(无论是16位、32位和64位系统)占据的内存大小和表示的数值极限是一样的。

整型中最常使用的是int,long和byte

其中int类型的典型应用场景就是控制循环的次数,默认的整数值类型就是int,如下应用所示

private static final Logger LOGGER= LogManager.getLogger();
/**
     * int类型的应用场景
     * @author tony [email protected]
     * @date 2018-01-01-下午6:50
     * @website wwww.ittimeline.net
     * @see
     * @since JDK9.0.1
     */
    @Test
    public void testIntegerType(){

        for(int i =0;i<Integer.MAX_VALUE;i++){

            LOGGER.info("i = "+i);
        }
    }

long类型通常用于映射数据库的主键ID,long类型的值后面通常需要加L后缀(千万不要加小写的l,容易混淆),如下案例所示

    private static final Logger LOGGER= LogManager.getLogger();
    /**
     * long类型的值后添加L后缀,如果是小写的,容易产生混淆
     * @author tony [email protected]
     * @date 2018-01-01-下午6:45
     * @website wwww.ittimeline.net
     * @see
     * @since JDK9.0.1
     */

    @Test
    public void testLongTypeError(){

        //推荐使用大写的L后缀
        long val=1l;
        LOGGER.info("val*2 = "+(val*2));

    }

byte类型通常用于底层文件处理(后期学习IO操作时会使用到)。

Java的整型支持八进制、十进制和十六进制,其中八进制以0开头,包含0-7之间的八个整数,而十六进制以0X开头,包含0-9,a-f之间的十个整数和六个字母。日常开发中最常使用的十进制,而在阅读JDK的底层实现会使用八进制或者十六进制的整数。应用案例如下所示:

 private static final Logger LOGGER= LogManager.getLogger();

  /**
     * 整数的四种进制类型
     *
     * 默认就是十进制类型 包含0-9之间的十个数字组成
     *
     * 计算机底层以二进制的补码存储数据,以0B开头,由0和1组成
     *
     * 八进制由0-7之间的八个数字组成,以0开头,Linux系统的权限系统就是典型的八进制应用场景
     *
     * 十六进制是由0-9和a-f的十六个元素组成,以0X开头,计算机的内存地址都是十六进制的。
     * 
     * @author tony [email protected]
     * @date 2018-01-01-下午6:45
     * @website wwww.ittimeline.net
     * @see
     * @since JDK9.0.1
     */
    @Test
    public void testIntegerRadix(){

        //二进制 以0b开头的数值
        byte binaryVal=0B11; //3
        LOGGER.info("binary val is "+binaryVal);//输出十进制的结果

        //这里会发生编译错误,因为八进制只能由0-7之间的八个数字组成
        // short octVal =018;

        //计算结果为十进制的15
        short octVal=017;

        LOGGER.info("oct val is "+octVal);
        int height =632;
        LOGGER.info("height val is "+height);

        //计算结果是74514
        long hexVal =0x123_12;
        LOGGER.info("hex val is "+hexVal);

    }

JDK中的java.lang.Long类中就是使用了十六进制的整数定义了long类型能够存储的最大值和最小值,源码片段如下

public final class Long extends Number implements Comparable<Long> {
    /**
     * A constant holding the minimum value a {@code long} can
     * have, -2<sup>63</sup>.
     */
    @Native public static final long MIN_VALUE = 0x8000000000000000L;

    /**
     * A constant holding the maximum value a {@code long} can
     * have, 2<sup>63</sup>-1.
     */
    @Native public static final long MAX_VALUE = 0x7fffffffffffffffL;

在JDK7以后支持二进制的字面量值(在数值前面加上0B前缀),同时也是在JDK1.7以后数值之间可以增加下划线,提高数据的可阅读性,而在编译器编译时会去掉数值之间的下划线。如下应用程序所示

 private static final Logger LOGGER= LogManager.getLogger();

 /**
     * JDK7新特性
     * 1 支持使用二进制的直接量 以0b开头
     * 2 数值(无论是整数还是浮点数)之间可以使用下划线,不过需要注意的是下划线只能出现在数字中间,也就是说前后必须都是数字。
     * @author tony [email protected]
     * @date 2018-01-01-下午6:30
     * @website wwww.ittimeline.net
     * @see
     * @since JDK9.0.1
     */
    @Test
    public void testJDK7Feature(){

        byte byteTypeBinary =0b011;
        LOGGER.info("byte type binary "+byteTypeBinary);

        long longTypeBinary =0b010_101_010_100_011_01L;

        LOGGER.info("long type binary:"+longTypeBinary);
    }

在byte,short,int,long类型对应的包装类Byte,Short,Integer,Long中以常量的方式声明了它们占据的字节大小以及存储数据的取值范围,如下应用程序所示:

private static final Logger LOGGER= LogManager.getLogger();



    /**
     * 获取Java基本数据类型(整数)的大小信息
     * 从整数对应的包装类Byte,Short,Integer,Long中定义的常量后获取
     * @author tony [email protected]
     * @date 2018-01-01-下午6:27
     * @website wwww.ittimeline.net
     * @see
     * @since JDK9.0.1
     */
    @Test
    public void testIntegerInfo(){

        LOGGER.info("byte占据的字节大小为"+Byte.BYTES+"\tbyte存储的最小值为"+Byte.MIN_VALUE+"\tbyte存储的最大值为"+Byte.MAX_VALUE); //8bit
        LOGGER.info("short占据的字节大小为"+Short.BYTES+"\tshort存储的最小值为"+Short.MIN_VALUE+"\tshort存储的最大值为"+Short.MAX_VALUE); //64K
        LOGGER.info("int占据的字节大小为"+Integer.BYTES+"\tint存储的最小值为"+Integer.MIN_VALUE+"\tint存储的最大值为"+Integer.MAX_VALUE); //4G
        LOGGER.info("long占据的字节大小为"+Long.BYTES+"\tlong存储的最小值为"+Long.MIN_VALUE+"\tlong存储的最大值为"+Long.MAX_VALUE);

    }

浮点型

Java的浮点型数据有两种,分别是占据4个字节的单精度浮点型float和占据8个字节内存的双精度浮点型double,如下应用所示,

float和double的包装类Float,Double同样以常量的方式定义了它们的基本类型所占据的内存大小以及存储数据的范围。

private static final Logger LOGGER= LogManager.getLogger();

    /**
     *
     * 浮点数存储数据的内存大小以及极限
     * @author tony [email protected]
     * @date 2018-01-02-下午6:27
     * @website wwww.ittimeline.net
     * @see
     * @since JDK9.0.1
     */

    @Test
    public void testFloatInfo(){

        LOGGER.info("float占据的字节大小为"+Float.BYTES+"\tfloat存储的最小值为"+Float.MIN_VALUE+"\tfloat存储的最大值为"+Float.MAX_VALUE); //3.4028235E38 E38表示10的38次方

        LOGGER.info("double占据的字节大小为"+Double.BYTES+"\tdouble存储的最小值为"+Double.MIN_VALUE+"\tdouble存储的最大值为"+Double.MAX_VALUE); //

    }

浮点数在内存中的存储

任何数据类型在计算机的最底层都是采用二进制的方式存储的,然而二进制无法精确的表示浮点数,就好像十进制无法精确的表示1/3一样。

Java的浮点型数据遵循IEEE754标准,即采用二进制数据的科学计数法来存储浮点数。

对于float类型的数值(32位二进制数据),第一位是符号位,接下来八位表示指数,再接下来23位表示位数,

而对于double类型的数值(64位二进制数据),第一位也是符号位,接下来11位数表示指数,再接下来52位表示尾数。

Java浮点数的特点以及应用案例

Java中的浮点型数据的类型默认是double类型,如果想使用float类型,数值后面必须添加f后缀来表示。浮点数据可以使用十进制数据或者是科学计数法两种方式来表示,如下应用案例所示

     private static final Logger LOGGER= LogManager.getLogger();

 /**
     *
     * 浮点数的两种表示方法
     * @author tony [email protected]
     * @date 2018-01-02-下午6:29
     * @website wwww.ittimeline.net
     * @see
     * @since JDK9.0.1
     */
    @Test
    public void testFloatValueType(){

        //12.3默认是float类型,不能将表示范围大的值直接赋值给表示范围小的变量 添加f后缀后12.3可以使用float存储
        float fltVal=12.3f;
        LOGGER.info("fltVal = "+fltVal);


        //十进制
        double dbVal=Math.PI;
        LOGGER.info("圆周率是"+dbVal);


        //科学计数法表示法 e10表示10的10次方
        double exponentVal=123.45e10;

        LOGGER.info("exponentVal = "+exponentVal);


    }

通常情况下float的数值有效位数为6位,double类型的数值有效位数是15位数,如果数值超过有效位数则会得到一个四舍五入的结果。

浮点数中还有三个特殊的值

当使用正数除以0.0得到的结果是正无穷大,通过Double或者Float的POSITIVE_INFINTY表示

当使用负数除以0.0得到的结果是负无穷大,通过Double或者Float的NEGATIVE_INFINITY表示

当使用0.0除以0.0或者对一个负数开方将得到一个非数,通过Double或者Float的NaN表示

如下应用程序所示

private static final Logger LOGGER= LogManager.getLogger(); 
 /**
     *
     * 浮点数的数值精确度
     * 三个特殊的浮点数
     * @author tony [email protected]
     * @date 2018-01-02-下午6:39
     * @website wwww.ittimeline.net
     * @see
     * @since JDK9.0.1
     */
    @Test
    public void testFloatValue(){
        //java的浮点数默认是是double类型,但是如果想要声明float类型的变量,需要在变量值后面加上f后缀
        float floatVal=5.234_555_56f;
        double doubleVal=floatVal;
        /**
         *  输出结果为5.234557 因为float只能精确到小数点后六位 输出结果做了四舍五入的处理
         */
        LOGGER.info(" float val is "+floatVal);
        /**
         * 输出结果为234555721282959 因为double能精确到小数点后15位数,输出结果做了四舍五入的处理
         */
        LOGGER.info("double val is "+doubleVal);//5.234555721282959

        final double POSITIVE_INFINITY=1.0/0.0;
        final double NEGATIVE_INFINITY=-1.0/0.0;


        LOGGER.info("double的正无穷大的结果是"+POSITIVE_INFINITY);
        LOGGER.info("float的正无穷大的结果是"+NEGATIVE_INFINITY);

        final double DOUBLE_NEGATIVE_INFINITY=Double.NEGATIVE_INFINITY; //-1.0 / 0.0
        final float FLOAT_NEGAIVE_INFINITY=Float.NEGATIVE_INFINITY;// -1.0f / 0.0f
        //输出结果表示double和float的正无穷大是相等的
        LOGGER.info(DOUBLE_NEGATIVE_INFINITY==FLOAT_NEGAIVE_INFINITY);

        double NANVal=0.0/0.0;
        LOGGER.info("0.0除以0.0的结果是一个非数,其结果是 = "+NANVal);

    }

在Effecitve Java书籍中指出,float和double类型主要是为了科学计算和工程计算而设计的,它们没有提供完全精确的计算结果,因此不适合用于货币计算,应用程序如下所示

private static final Logger LOGGER= LogManager.getLogger(); 

/**
     * 浮点数计算精度的问题
     * @author tony [email protected]
     * @date 2018-01-02-下午6:39
     * @website wwww.ittimeline.net
     * @see
     * @since JDK9.0.1
     */
    @Test
    public void testFloatCalcutorPrecision(){
        /*
        * 浮点数默认都是double类型,而double的有效位数是小数点后15位
        * 而在内部的存储采用的是二进制指数存储
        * */
        double dbVal=2.0-1.1;
        LOGGER.info("浮点数计算精度的问题:2.0-1.1 = "+dbVal); //2.0-1.1 = 0.8999999999999999
    }

为了解决float和double的运算结果的精度问题,java提供了java.math.BigDecimal类,它能提供任意精度的浮点运算,金融理财等项目(单位是元为单位)的浮点数计算时,推荐使用BigDecimal类来完成浮点数的计算,以保证获取正确的计算结果。

如果在金融理财等项目不想使用BigDecimal类获取精确地结果,那么可以使用分作为账户金额的计算单位,数据库(以MySQL为例)中对应以bigint存储金额即可。而在页面上显示时再将分转换成元即可。

 private static final Logger LOGGER= LogManager.getLogger(); 

 /**
     * 使用BigDecimal来解决浮点数运算精度不准的问题
     * @author tony [email protected]
     * @date 2018-01-02-下午6:49
     * @website wwww.ittimeline.net
     * @see
     * @since JDK9.0.1
     */
    @Test
    public void testBigDecimal(){

        /**
         * 使用字符串参数来构造BigDecimal对象
         */
        BigDecimal constructorOfString1= new BigDecimal("2.0");
        BigDecimal constructorOfString2= new BigDecimal("1.1");

        BigDecimal constructorOfStringResult =constructorOfString1.subtract(constructorOfString2);

        LOGGER.info("constructor of string result  is "+constructorOfStringResult);


        /**
         * 不能使用double来构造BigDecimal对象,不然即使使用BigDecimal,计算时也会产生精度不准的问题
         */

        BigDecimal constructorOfDouble1=new BigDecimal(2.0);
        BigDecimal constructorOfDouble2 =new BigDecimal(1.1);

        BigDecimal constructorOfDoubleResult =constructorOfDouble1.subtract(constructorOfDouble2);

        LOGGER.info("constructor of double result is "+constructorOfDoubleResult);
    }

在使用BigDecimal时需要注意两点:

1: 不要使用double类型的参数来创建BigDecimal对象,那样即使使用BigDecimal来运算,运算结果也会产生精度不准的问题。

2: 同时BigDecimal是不可变的对象,这就意味着每次运算的结果都会产生一个新的BigDecimal对象。

猜你喜欢

转载自blog.csdn.net/ittechnologyhome/article/details/79916258
今日推荐