2.1 面向对象

在目前的软件开发领域有两种主流的开发方法:结构化开发方法和面向对象开发方法。

早期的编程语言如C、Basic、Pascal等都是结构化编程语言;

随着软件开发技术的逐渐发展,人们发现面向对象可以提供更好的可重用性、可扩展性和可维护性,于是催生了大量的面向对象的编程语言,如C++、Java、C#和Ruby等。

2.1.1 结构化程序设计简介

结构化程序设计方法主张按功能来分析系统需求,其主要原则可概括为自顶向下、逐步求精、模块化等。

SA SD SP

结构化程序设计
首先采用结构化分析(Structured Analysis,SA)方法对系统进行需求分析,
然后使用结构化设计(Structured Design,SD)方法对系统进行概要设计、详细设计,
最后采用结构化编程(Structured Program,SP)方法来实现系统。

使用这种SA、SD和SP的方式可以较好地保证软件系统的开发进度和质量。

因为结构化程序设计方法主张按功能把软件系统逐步细分,因此这种方法也被称为面向功能的程序设计方法;结构化程序设计的每个功能都负责对数据进行一次处理,每个功能都接受一些数据,处理完后输出一些数据,这种处理方式也被称为面向数据流的处理方式

结构化程序设计的最小程序单元 函数

结构化程序设计里最小的程序单元是函数,每个函数都负责完成一个功能,用以接收一些输入数据,函数对这些输入数据进行处理,处理结束后输出一些数据。整个软件系统由一个个函数组成,其中作为程序入口的函数被称为主函数,主函数依次调用其他普通函数,普通函数之间依次调用,从而完成整个软件系统的功能。

自顶向下

结构化设计需要采用自顶向下的设计方式,在设计阶段就需要考虑每个模块应该分解成哪些子模块,每个子模块又分解成哪些更小的模块……依此类推,直至将模块细化成一个个函数。

每个函数都是具有输入、输出的子系统,函数的输入数据包括函数形参、全局变量和常量等,函数的输出数据包括函数返回值以及传出参数等。

结构化程序设计局限性

结构化程序设计方式有如下两个局限性。

  • 设计不够直观,与人类习惯思维不一致。采用结构化程序分析、设计时,开发者需要将客观世界模型分解成一个个功能,每个功能用以完成一定的数据处理。
  • 适应性差,可扩展性不强。由于结构化设计采用自顶向下的设计方式,所以当用户的需求发生改变,或需要修改现有的实现方式时,都需要自顶向下地修改模块结构,这种方式的维护成本相当高。

2.1.2 程序的三种基本结构

goto语句的问题

在过去的日子里,很多编程语言都提供了GOTO语句,GOTO语句非常灵活,可以让程序的控制流程任意流转——如果大量使用GOTO语句,程序完全不需要使用循环。但GOTO语句实在太随意了,如果程序随意使用GOTO语句,将会导致程序流程难以理解,并且容易出错。在实际软件开发过程中,更注重软件的可读性和可修改性,因此GOTO语句逐渐被抛弃了。

保留字goto

Java语言拒绝使用GOTO语句,但它将goto作为保留字,意思是目前Java版本还未使用GOTO语句,但也许在未来的日子里,当Java不得不使用GOTO语句时,Java还是可能使用GOTO语句的。

程序设计的三种基本结构

结构化程序设计非常强调实现某个功能的算法,而算法的实现过程是由一系列操作组成的,这些操作之间的执行次序就是程序的控制结构。1996年,计算机科学家Bohm和Jacopini证明了这样的事实:任何简单或复杂的算法都可以由顺序结构、选择结构和循环结构这三种基本结构组合而成。所以,这三种结构就被称为程序设计的三种基本结构,也是结构化程序设计必须采用的结构。

2.1.3 面向对象程序设计简介

面向对象程序设计的最小的程序单元是 类

采用面向对象方式开发的软件系统,其最小的程序单元是类,这些类可以生成系统中的多个对象,而这些对象则直接映像成客观世界的各种事物。

面向对象的软件系统由多个类组成,类代表了客观世界中具有某种特征的一类事物,这类事物往往有一些内部的状态数据,比如人有身高、体重、年龄、爱好等各种状态数据,当然程序没必要记录该事物所有的状态数据,程序只要记录业务关心的状态数据即可。

面向对象的语言不仅使用类来封装一类事物的内部状态数据,这种状态数据就对应于类的成员变量,而且类会提供操作这些状态数据的方法,还会为这类事物的行为特征提供相应的实现,这种实现也是方法。因此可以得到如下基本等式:

成员变量(状态数据) + 方法(行为) = 类定义

面向对象比面向过程编程粒度要大

从这个等式来看,面向对象比面向过程的编程粒度要大:面向对象的程序单位是类;而面向过程的程序单位是函数(相当于方法)。

2.1.4 面向对象的基本特征

面向对象的是三个基本特征 封装 继承 多态

面向对象方法具有三个基本特征:封装(Encapsulation)、继承(Inheritance)和多态(Polymorphism).

  • 封装指的是将对象的实现细节隐藏起来,然后通过一些公用方法来暴露该对象的功能;
  • 继承是面向对象实现软件复用的重要手段,当子类继承父类后,子类作为一种特殊的父类,将直接获得父类的属性和方法;
  • 多态指的是子类对象可以直接赋给父类变量,但运行时依然表现出子类的行为特征,这意味着同一个类型的对象在执行同一个方法时,可能表现出多种行为特征。

抽象

除此之外,抽象也是面向对象的重要部分,抽象就是忽略一个主题中与当前目标无关的那些方面,以便更充分地注意与当前目标有关的方面。抽象并不打算了解全部问题,而只是考虑部分问题。例如,需要考察Person对象时,不可能在程序中把Person的所有细节都定义出来,通常只能定义Person的部分数据、部分行为特征——而这些数据、行为特征是软件系统所关心的部分。

虽然抽象是面向对象的重要部分,但抽象不是面向对象的特征之一,因为所有的编程语言都需要抽象。当开发者进行抽象时应该考虑哪些特征是软件系统所需要的,那么这些特征就应该使用程序记录并表现出来。因此,需要抽象哪些特征没有必然的规定,而是取决于软件系统的功能需求。

面向对象的其他特征

面向对象还支持如下几个功能。

  • 对象是面向对象方法中最基本的概念,它的基本特点有:标识唯一性、分类性、多态性、封装性、模块独立性好。
  • 类是具有共同属性、共同方法的一类事物。类是对象的抽象:对象则是类的实例。而类是整个软件系统最小的程序单元,类的封装性将各种信息细节隐藏起来,并通过公用方法来暴露该类对外所提供的功能,从而提高了类的内聚性,降低了对象之间的耦合性。
  • 对象间的这种相互合作需要一个机制协助进行,这样的机制称为“消息”。消息是一个实例与另个实例之间相互通信的机制。
  • 在面向对象方法中,类之间共享属性和操作的机制称为继承。继承具有传递性。继承可分为单继承(一个继承只允许有一个直接父类,即类等级为树形结构)与多继承(一个类允许有多个直接父类)。

Java不支持多继承

由于多继承可能引起继承结构的混乱,而且会大大降低程序的可理解性,所以Java不支持多继承。

面向对象和基于对象的区别

判断一门语言是否是面向对象的,通常可以使用继承和多态来加以判断。

  • “面向对象”和“基于对象”都实现了“封装”的概念,
  • 但是面向对象实现了“继承和多态”,
  • 而“基于对象”没有实现实现“继承和多态”。

2.0 疯狂java讲义 4 第2章 本章概述

本章要点

  • 结构化程序设计
  • 顺序结构
  • 分支结构
  • 循环结构
  • 面向对象程序设计
  • 继承、封装、多态
  • UML简介
  • 掌握常用的UML图形
  • 理解Java的面向对象特征

Java是变相对象的编程语言

Java语言是纯粹的面向对象的程序设计语言,这主要表现为Java完全支持面向对象的三种基本特征:封装、继承和多态。Java语言完全以对象为中心,**Java程序的最小程序单位是类**,整个Java程序由一个一个的类组成。

Java完全支持使用对象、类、继承、封装、消息等基本概念来进行程序设计,允许从现实世界中客观存在的事物(即对象)出发来构造软件系统,在系统构造中尽可能运用人类的自然思维方式。实际上,这些优势是所有面向对象编程语言的共同特征。

OOA OOD OOP

面向对象的方式实际上由OOA(面向对象分析)、OOD(面向对象设计)和OOP(面向对象编程)三个部分有机组成,其中,OOA和OOD的结构需要使用一种方式来描述并记录,目前业界统一采用UML(统一建模语言)来描述并记录OOA和OOD的结果。

UML

目前UML的最新版本是2.0,它一共包括13种类型的图形,使用这13种图形中的某些就可以很好地描述并记录软件分析、设计的结果。通常而言,没有必要为软件系统绘制13种UML图形。

7种常用的UML图

常用的UML图形有用例图、类图、组件图、部署图、顺序图、活动图和状态机图。本章将会介绍UML图的相关概念,也会详细介绍这7种常用的UML图的绘制方法。

1.9 本章小结

  • 本章简单介绍了Java语言的发展历史,并详细介绍了Java语言的编译、解释运行机制,也大致讲解了Java语言的垃圾回收机制。
  • 本章的重点是讲解如何搭建Java开发环境,包括安装JDK,设置PATH环境变量,并详细阐述了CLASSPATH环境变量的作用,并向读者指出应该如何处理CLASSPATH环境变量。
  • 本章还详细介绍了如何开发和运行第一个Java程序,并总结出了初学者容易出现的几个错误。
  • 此外,本章详细介绍了Java9新增的jshell工具,这个工具对于Java学习者和新功能测试都非常方便,希望读者好好掌握它。
  • 本章最后针对Java学习者是否应该使用IDE工具给出了一些过来人的建议。

本章练习

  • 搭建自己的Java开发环境。
  • 编写Java语言的HelloWorld

1.7 Java9的G1垃圾回收器

内存泄漏

传统的C,C++等编程语言,需要程序员负责回收已经分配的内存。显式进行垃圾回收是一件比较困难的事情,因为程序员并不总是知道内存应该何时被释放。如果一些分配出去的内存得不到及时回收,就会引起系统运行速度下降,甚至导致程序瘫痪,这种现象被称为内存泄漏。

显示垃圾回收缺点

总体而言,显式进行垃圾回收主要有如下两个缺点:

  • 程序忘记及时回收无用内存,从而导致内存泄漏,降低系统性能。
  • 程序错误地回收程序核心类库的内存,从而导致系统崩溃。

Java垃圾回收机制

与C,C++程序不同,Java语言不需要程序员直接控制内存回收,Java程序的内存分配和回收都是由JRE在后台自动进行的。JRE会负责回收那些不再使用的内存,这种机制被称为垃圾回收(Garbage Collection,GC)。通常JRE会提供一个后台线程来进行检测和控制,一般都是在CPU空闲或内存不足时自动进行垃圾回收,而程序员无法精确控制垃圾回收的时间和顺序。

垃圾回收器负责回收堆内存中的对象

Java的堆内存是一个运行时数据区,用以保存类的实例(对象),Java虚拟机的堆内存中存储着正在运行的应用程序所建立的所有对象,这些对象不需要程序通过代码来显式地释放。一般来说,堆内存的回收由垃圾回收器来负责,所有的JVM实现都有一个由垃圾回收器管理的堆内存。垃圾回收是一种动态存储管理技术,它自动释放不再被程序引用的对象,按照特定的垃圾回收算法来实现内存资源的自动回收功能。

在C,C++中,对象所占用的内存不会被自动释放,如果程序没有显式释放对象所占用的内存,对象所占用的内存就不能分配给其他对象,该内存在程序结束运行之前将一直被占用;

什么是垃圾

而在Java中,当没有引用变量指向原先分配给某个对象的内存时,该内存便成为垃圾。JVM的一个超级线程会自动释放该内存区。垃圾回收意味着程序不再需要的对象是“垃圾信息”,这些信息将被丢弃。

当一个对象不再被引用时,内存回收它占领的空间,以便空间被后来的新对象使用。事实上,除释放没用的对象外,垃圾回收也可以清除内存记录碎片。由于创建对象和垃圾回收器释放丢弃对象所占的内存空间,内存会出现碎片。碎片是分配给对象的内存块之间的空闲内存区,碎片整理将所占用的堆内存移到堆的一端,JVM将整理出的内存分配给新的对象。

垃圾回收机制优缺点

优点

垃圾回收能自动释放内存空间,减轻编程的负担。这使Java虚拟机具有两个显著的优点。

  • 垃圾回收机制可以很好地提高编程效率。在没有垃圾冋收机制时,可能要花许多时间来解决一个难懂的存储器问题。在用Java语言编程时,依靠垃圾回收机制可大大缩短时间。
  • 垃圾回收机制保护程序的完整性,垃圾回收是Java语言安全性策略的一个重要部分

缺点

  • 垃圾回收的一个潜在缺点是它的开销影响程序性能。Java虚拟机必须跟踪程序中有用的对象,才可以确定哪些对象是无用的对象,并最终释放这些无用的对象。这个过程需要花费处理器的时间。
  • 其次是垃圾回收算法的不完备性,早先采用的某些垃圾回收算法就不能保证100%收集到所有的废弃内存。

当然,随着垃圾回收算法的不断改进,以及软硬件运行效率的不断提升,这些问题都可以迎刃而解。

Java语言规范没有明确地说明JVM使用哪种垃圾回收算法,但是仼何一种垃圾回收算法一般要做两件基本的事情:发现无用的对象;回收被无用对象占用的内存空间,使该空间可被程序再次使用。

垃圾回收特点

通常,垃圾回收具有如下几个特点:

  • 垃圾回收器的工作目标是回收无用对象的内存空间,这些内存空间都是JVM堆内存里的内存空间,垃圾回收器只能回收内存资源,对其他物理资源,如数据库连接、磁盘IO等资源则无能为力。
  • 为了更快地让垃圾回收器回收那些不再使用的对象,可以将该对象的引用变量设置为null,通过这种方式暗示垃圾回收器可以回收该对象
  • 垃圾回收发生的不可预知性。由于不同JVM采用了不同的垃圾回收机制和不同的垃圾回收算法,因此它有可能是定时发生的,有可能是当CPU空闲时发生的,也有可能和原始的垃圾回收一样,等到内存消耗出现极限时发生,这和垃圾回收实现机制的选择及具体的设置都有关系。虽然程序员可以通过调用Runtime对象的gc()或System.gc()等方法来建议系统进行垃圾回收,但这种调用仅仅是建议,依然不能精确控制垃圾回收机制的执行。
  • 垃圾回收的精确性主要包括两个方面:一是垃圾回收机制能够精确地标记活着的对象;二是垃圾回收器能够精确地定位对象之间的引用关系。前者是完全回收所有废弃对象的前提,否则就可能造成内存泄漏;而后者则是实现归并和复制等算法的必要条件,通过这种引用关系,可以保证所有对象都能被可靠地回收,所有对象都能被重新分配,从而有效地减少内存碎片的产生。
  • 现在的JVM有多种不同的垃圾回收实现,每种回收机制因其算法差异可能表现各异,有的当垃圾回收开始时就停止应用程序的运行,有的当垃圾回收运行时允许应用程序的线程运行,还有的在同一时间允许垃圾回收多线程运行

当编写Java程序时,一个基本原则是:对于不再需要的对象,不要引用它们。如果保持对这些对象的引用,垃圾回收机制暂时不会回收该对象,则会导致系统可用内存越来越少;当系统可用内存越来越少时,垃圾回收执行的频率就越来越高,从而导致系统的性能下降。

Java7 新增的G1垃圾回收器

2011年7月发布的Java7提供了G1垃圾回收器来代替原有的并行标记清除垃圾回收器(简称CMS)

Java8 使用本地内存 代替 永生代内存

2014年3月发布的Java 8删除了HotSpot JVM中的永生代内存(PermGen,永生代内存主要用于存储一些需要常驻内存、通常不会被回收的信息),而是改为使用本地内存来存储类的元数据信息,并将之称为:元空间(Metaspace),这意味着以后不会再遇到java.Lang.OutOfMemoryError:PermGen错误

Java 9后无效的GC相关的JVM设置

2017年9月发布的Java 9彻底删除了传统的CMS垃圾回收器,因此运行JVM的DefNew+CMSParDew+Serialold、IncrementalCMS等组合全部失效。java命令(该命令负责启用JVM运行Java程序)以前支持的以下GC相关选项全部被删除。

  • -Xincgc
  • -XX:+CMSIncrementalMode
  • -XX:+UseCMSCompactAtFullCollection
  • -XX:+CMSFullGCsBeforeCompaction
  • -XX:+UseCMSCollectionPassing

此外,-XX:UseGC选项也被标记为过时,将来也会被删除。

Java 9默认采用低暂停(low-Pause)的G1垃圾回收器,并为Gl垃圾回收器自动确定了几个重要的参数设置,从而保证G1垃圾回收器的可用性、确定性和性能。如果部署项目时为java命令指定了XX:+UseConcMarkSweepGC选项希望启用CMS垃圾回收器,系统会显示警告信息。

1.6 JDK9新增的jshell具

JDK9工具的一大改进就是提供了jshell工具,它是一个REPL(Read-Eval-Print Loop)工具,该工具是一个交互式的命令行界面,可用于执行Java语言的变量声明、语句和表达式,而且可以立即看到执行结果。因此,我们可以使用该工具来快速学习Java或测试Java的新API。

在没有jshell时,开发者想要测试某个新功能或新API,通常要先打开IDE工具,然后新建一个测试项目,再新建一个类,最后才可以开始写代码来测试新功能或新API。这显得很麻烦,而jshell的出现解决了这个痛点。

jshell位于JDK安装目录的bin路径下,如果读者按前面介绍的方式配置了PATH环境变量,那么输入shell命令应该即可进入shell交互模式;如果系统提示找不到jshell命令,那么肯定是环境变量配置错误。

查看jshell帮助信息

进入shell交互模式后,可执行/help来查看帮助信息:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
jshell> /help
| 键入 Java 语言表达式, 语句或声明。
| 或者键入以下命令之一:
| /list [<名称或 id>|-all|-start]
| 列出您键入的源
| /edit <名称或 id>
| 编辑按名称或 id 引用的源条目
| /drop <名称或 id>
| 删除按名称或 id 引用的源条目
| /save [-all|-history|-start] <文件>
| 将片段源保存到文件。
| /open <file>
| 打开文件作为源输入
| /vars [<名称或 id>|-all|-start]
| 列出已声明变量及其值
| /methods [<名称或 id>|-all|-start]
| 列出已声明方法及其签名
| /types [<名称或 id>|-all|-start]
| 列出已声明的类型
| /imports
| 列出导入的项
| /exit
| 退出 jshell
| /env [-class-path <路径>] [-module-path <路径>] [-add-modules <模块>] ...
| 查看或更改评估上下文
| /reset [-class-path <路径>] [-module-path <路径>] [-add-modules <模块>]...
| 重启 jshell
| /reload [-restore] [-quiet] [-class-path <路径>] [-module-path <路径>]...
| 重置和重放相关历史记录 -- 当前历史记录或上一个历史记录 (-restore)
| /history
| 您键入的内容的历史记录
| /help [<command>|<subject>]
| 获取 jshell 的相关信息
| /set editor|start|feedback|mode|prompt|truncation|format ...
| 设置 jshell 配置信息
| /? [<command>|<subject>]
| 获取 jshell 的相关信息
| /!
| 重新运行上一个片段
| /<id>
| 按 id 重新运行片段
| /-<n>
| 重新运行前面的第 n 个片段
|
| 有关详细信息, 请键入 '/help', 后跟
| 命令或主题的名称。
| 例如 '/help /list' 或 '/help intro'。主题:
|
| intro
| jshell 工具的简介
| shortcuts
| 片段和命令输入提示, 信息访问以及
| 自动代码生成的按键说明
| context
| /env /reload 和 /reset 的评估上下文选项

退出jshell

也可执行/exit退出jshell

执行你希望测试的Java代码,比如执行System.out.println("HelloWorld"),jshell不要求以分号结尾。

jshell常用命令

  • /list:列出用户输入的所有源代码。
  • /edit:编辑用户输入的第几条源代码。比如/edit2表示编辑用户输入的第2条源代码。jshell会启动一个文本编辑界面让用户来编辑第2条源代码
  • /drop:删除用户输入的第几条源代码。
  • /save:保存用户输入的源代码。
  • /vars:列出用户定义的所有变量。
  • /methods:列出用户定义的全部方法
  • /types:列出用户定义的全部类型。

测试jshell

在jshell界面中输入如下语句:

定义变量

1
int a = 20

上面语句用于定义一个变量。接下来输入/vars命令,即可看到shell列出了用户定义的全部变量。系统生成如下输出:

查看变量

1
2
jshell> /vars
| int a = 20

输出语句

在shell界面中输入如下语句:

1
System.out.println("HelloWorld!")

运行效果如下:

1
2
jshell> System.out.println("HelloWorld!")
HelloWorld!

显示所有代码

1
/list
1
2
3
4
jshell> /list

1 : int a = 20;
2 : System.out.println("HelloWorld!")

这个输出结果又两个,前面的数字是id,这个id是自增的,新增的命令或者修改的命令都会加1

修改之前的代码

1
/edit id

这里的id例如修改第2行

1
/edit 2

jshell会启动一个图形化的编辑器让你来编辑这一行,如下图所示:
这里有一张图片
在这个弹出的编辑器中,将上面的代码

1
System.out.println("HelloWorld!")

修改为:

1
System.out.println(a);

然后点击Accept按钮,然后点击Exit退出.可以看到如下运行结果:

1
2
jshell> /edit 2
20

删除某行代码

1
/drop id

例如删除上面的第二行代码:

1
/drop 2

运行结果:

1
2
3
4
5
6
jshell> /drop 2

jshell> /list

1 : int a = 20;
3 : System.out.println(a);

可以看到,现在第二行代码,已经被删除掉了,但是前面的id不会

执行某行代码

1
/id

例如

cmd中临时使用JDK9

需求

我在使用旧版本的java8,但是如果我想使用Java9的新特性,例如jshell.

使用set命令设置临时的path环境变量

cmd中的set命令用于设置临时的环境变量,所以在path环境变量的前面添加JDK9安装路径下的bin路径即可使用jshell:

1
set path=E:\java\java9\jdk-9\bin;%path%

效果如下:

1
2
3
4
5
6
7
G:\Desktop>echo %path%
E:\java\java8\jdk-8u221\bin;C:\WINDOWS\system32;C:\WINDOWS;C:\WINDOWS\System32\Wbem;.........;E:\Program Files\JetBrains\IntelliJ IDEA 2019.3.1\bin;

G:\Desktop>set path=E:\java\java9\jdk-9\bin;%path%

G:\Desktop>echo %path%
E:\java\java9\jdk-9\bin;E:\java\java8\jdk-8u221\bin;C:\WINDOWS\system32;C:\WINDOWS;C:\WINDOWS\System32\Wbem;.........;E:\Program Files\JetBrains\IntelliJ IDEA 2019.3.1\bin;

这样就可以在当前cmd中使用jshell,设置成功效果:

1
2
3
G:\Desktop>jshell
| 欢迎使用 JShell -- 版本 9
| 要大致了解该版本, 请键入: /help intro

1.4 第一个Java程序

1.4.1 编辑Java源代码

编辑Java源代码可以使用任何无格式的文本编辑器,在Windows操作系统上可使用记事本(NotePad)、Editplus等程序,在Linux平台上可使用Vi工具等。

编写Java程序不要使用写字板,更不可使用word等文档编辑器。因为写字板、Word等工具是有格式的编辑器,当使用它们编辑一份文档时,这个文档中会包含一些隐藏的格式化字符,这些隐藏字符会导致程序无法正常编译、运行。

在记事本中新建一个文本文件,并在该文件中输入如下代码

1
2
3
4
5
6
7
public class HelloWorld {
// Java程序的入口方法,程序将从这里开始执行
public static void main(String[] args) {
// 向控制台打印一条语句
System.out.println("Hello World!");
}
}

编辑上面的Java文件时,Java程序严格区分大小写。将上面文本文件保存为HelloWorld.java文件,这个文件就是Java程序的源程序。编写好Java程序的源代码后,接下来就应该编译该Java源文件来生成字节码了。

1.4.2 编译Java程序

编译Java程序需要使用javac命令,因为前面已经把javac命令所在的路径添加到了系统的PATH环境变量中,因此现在可以使用javac命令来编译Java程序了。

如果直接在命令行窗口里输入javac,不跟任何选项和参数,系统将会输出大量提示信息,用以提示javac命令的用法,读者可以参考该提示信息来使用javac命令。
对于初学者而言,先掌握javac命令的如下用法:

1
javac -d destDir srcFile

在上面命令中:

  • -d destDir是javac命令的选项,用以指定编译生成的字节码文件的存放路径,destDir只需是本地磁盘上的一个有效路径即可;而
  • srcFile是Java源文件所在的位置,这个位置既可以是绝对路径,也可以是相对路径。

通常,总是将生成的字节码文件放在当前路径下,当前路径可以用点(.)来表示。在命令行窗口进入HelloWorld.java文件所在路径,在该路径下输入如下命令:

1
javac -d . HelloWorld.java

运行该命令后,在该路径下生成一个HelloWorld.Class文件。

使用javac编译文件只需要指定存放目标文件的位置即可,无须指定字节码文件的文件名。因为javac编译后生成的字节码文件有默认的文件名:文件名总是以源文件所定义类的类名作为主文件名,以.class作为扩展名。这意味看如果一个源文件里定义了多个类,将编译生成多个字节码文件。事实上,指定目标文件存放位置的-d选项也是可省略的,如果省略-d选项,则意味着将生威的字节码文件放在当前路径下。

1.4.3 运行Java程序

运行Java程序使用java命令,启动命令行窗口,进入HelloWorld.class所在的位置,在命令行窗口里直接输入java命令,不带任何参数或选项,将看到系统输出大量提示,告诉开发者如何使用java命令.

对于初学者而言,当前只要掌握java命令的如下用法即可:

1
java Java类名

值得注意的是,java命令后的参数是Java类名,而不是字节码文件的文件名,也不是Java源文件名.

通过命令行窗口进入HelloWorld.class所在的路径,输入如下命令:

1
java HelloWorld

运行上面命令,将看到如下输出:

1
Hello World!

这表明Java程序运行成功

1.4.4 根据CLASSPATH环境变量定位类

JDK1.4之前需要设置CLASSPATH环境变量

实际上,如果使用1.5以上版本的JDK,完全可以不用设置CLASSPATH环境变量————正如上面编译、运行Java程序所见到的,即使不设置CLASSPATH环境变量,完全可以正常编译和运行Java程序

那么CLASSPATH环境变量的作用是什么呢?当使用java Java类名命令来运行Java程序时,JRE到哪里去搜索Java类呢?可能有读者会回答,在当前路径下搜索啊。这个回答很聪明,但1.4以前版本的JDK都没有设计这个功能,这意味着即使当前路径已经包含了HelloWorld.class,并在当前路径下执行”java HelloWorld“,系统将一样提示找不到HelloWorld类。

除此之外,编译和运行Java程序还需要JDK的lib路径下dt.jar和tools.jar文件中的java类,因此还需要把这两个文件添加到CLASSPATH环境变量里。

JDK9不包含dt.jar和tools.jar

JDK9的lib目录已经不再包含dt.jar和tools.jar文件。

因此,如果使用1.4以前版本的JDK来编译和运行Java程序,常常需要设置CLASSPATH环境变量的值为

1
.;%JAVA_HOME%\lib\dt.jar;%JAVA_HOME%\lib\tools.jar

(其中%JAVA_HOME%代表JDK的安装目录)。
只有使用早期版本的JDK时,才需要设置CLASSPATH环境变量

CLASSPATH加载第三方库

当然,即使使用JDK1.5以上版本的JDK,也可以设置CLASSPATH环境变量(通常用于加载第三方类库),一旦设置了该环境变量,JRE将会按该环境变量指定的路径来搜索Java类。这意味着如果CLASSPATH环境变量中不包括点(.),也就是没有包含当前路径,JRE不会在当前路径下搜索Java类。

使用java命令的-cp选项临时指定JRE搜索路径

如果想在运行Java程序时临时指定JRE搜索Java类的路径,则可以使用-classpath选项(或用-cp选项,cp是简写,作用完全相同),即按如下格式来运行java命令:

1
java -classpath %CLASSPATH%;.;dir1;dir2;dir3;......;dirN Java类名

-classpath选项的值可以是一系列的路径,多个路径之间在Windows平台上以分号(;)隔开,在Linux平台上则以冒号(:)隔开。
上面命令通过%CLASSPATH%来引用CLASSPATH环境变量的值,并在-classpath选项的值里添加了一个点,强制JRE在当前路径下搜索Java类。

1.3.1 下载和安装Java9的JDK

JDK包含JRE JRE包含JVM

JDK

JDK的全称是Java SE Development Kit,即Java标准版开发包,是Oracle提供的一套用于开发Java应用程序的开发包,它提供了编译、运行Java程序所需的各种工具和资源,包括Java编译器、Java运行时环境,以及常用的Java类库等。

JRE

Java运行时环境,它的全称是Java Runtime Environment,因此也被称为JRE,它是运行Java程序的必需条件。

JRE包含JVM。JVM是运行Jaa程序的核心虚拟机,而运行Java程序不仅需要核心虚拟机,还需要其他的类加载器、字节码极验器以及大量的基础类库。JRE除包含JVM之外,还包含运行Java程序的其他环境支持。

般而言,如果只是运行Java程序,可以只安装JRE,无须安装JDK。

开发JDK 运行JRE

  • 如果需要开发Java程序,则应该选择安装JDK;当然,安装了JDK之后,就包含了JRE,也可以运行Java程序。
  • 如果只是运行Java程序,则需要在计算机上安装JRE,仅安装JVM是不够的。

实际上,Oracle网站上提供的就是JRE的下载,并不提供单独JVM的下载。

Java SE Java EE和Java ME

Oracle把Java分为Java SE、Java EE和Java ME三个部分,而且为Java SE和Java EE分别提供了JDK和Java EE SDK( Software Development Kit)两个开发包。

如果读者只需要学习Java SE的编程知识,则可以下载标准的JDK;

如果读者学完Java SE之后,还需要继续学习Java EE相关内容,也可以选择下载Java EE SDK。

Java EE SDK包含JDK

一个Java EE SDK版本里已经包含了最新版的JDK,安装Java EE SDK就包含了JDK。

下载JDK 安装JDK

省略…

JDK安装目录结构

安装完成后,可以在JDK安装路径下看到如下的文件路径

  • bin:该路径下存放了JDK的各种工具命令,常用的javac、java等命令就放在该路径下
  • conf:该路径下存放了JDK的相关配置文件。
  • include:存放一些平台特定的头文件。
  • jmods:该目录下存放了JDK的各种模块
  • legal:该目录下包含了JDK各模块的授权文档。
  • lib:该路径下存放的是JDK工具的一些补充JAR包。比如src.zip文件中保存了Java的源代码。
  • README和COPYRIGHT等说明性文档。

模块化系统

模块化系统是JDK9的重大更新,随着Java语言的功能越来越强大,Java语言也越来越庞大。很多时候,一个基于Java的软件并不会用到Java的全部功能,因此该软件也不需要加载全部的Java功能,而模块化系统则允许发布Java软件系统时根据需要只加载必要的模块。

为此,JDK专门引入了一种新的JMOD格式,它近似于JAR格式,但JMOD格式更强大,它可以包含本地代码和配置文件。该目录下包含了JDK各种模块的JMOD文件,比如使用WinRar打开java.base.jmod文件,将会看到如图1.4所示的文件结构

图1.4

从图1.4可以看岀,java.base.jmod是JDK的最基础模块,该模块包含了Java的lang、util、math等模块,这些都是Java最核心的功能,是其他所有模块的基础。

此外,上面提到的bin路径是一个非常有用的路径,在这个路径下包含了编译和运行Java程序的avac和java两个命令。除此之外,还包含了jlink、jar等大量工具命令。

1.3.2 设置PATH环境变量

前面已经介绍过了,编译和运行Java程序必须经过两个步骤

  • 将源文件编译成字节码。
  • 解释执行平台无关的字节码程序。

上面这两个步骤分别需要使用java和javac两个命令。启动Windows操作系统的命令行窗口(在开始”菜单里运行cmd命令即可),在命令行窗口里依次输入java和javac命令,将看到如下输出:

1
2
'java'不是内部或外部命令,也不是可运行的程序或批处理文件。
'javac'不是内部或外部命令,也不是可运行的程序或批处理文件。

这意味着还不能使用java和javac两个命令。这是因为:虽然已经在计算机里安装了JDK,而JDK的安装路径下也包含了java和javac两个命令,但计算机不知道到哪里去找这两个命令。

计算机根据PATH环境变量中的路径来查找命名

计算机如何查找命令呢?Windows操作系统根据Path环境变量来查找命令。Path环境变量的值是一系列路径,Windows操作系统将在这一系列的路径中依次查找命令,如果能找到这个命令,则该命令是可执行的;否则将出现:”xxx不是内部或外部命令,也不是可运行的程序或批处理文件”的提示。

而Linux操作系统则根据PATH环境变量米查找命令,PAIH环境变量的值也是一系列路径。

因为Windows操作系统不区分大小写,设置Path和PATH并没有区别;而Linux系统是区分大小写的,设置Path和PATH是有区别的。
因此不管是Windows还是Linux,统一设置PATH环境变量即可。

不管是Linux平台还是Windows平台,只需把java和javac两个命令所在的路径添加到PATH环境变量中,就可以编译和运行java程序了。

1. 在Windows7等平台上设置环境变量

省略…

2. 在Linux上设置环境变量

省略…..

1.2 Java程序运行机制

Java语言是一种特殊的高级语言,它既具有解释型语言的特征,也具有编译型语言的特征,因为Java程序要经过先编译,后解释两个步骤。

1.2.1 高级语言的运行机制

计算机高级语言按程序的执行方式可以分为编译型和解释型两种。

编译型语言

编译型语言是指使用专门的编译器,针对特定平台〔操作系统)将某种髙级语言源代码一次性“翻译”成可被该平台硬件执行的机器码(包括机器指令和操作数),并包装成该平台所能识别的可执行性程序的格式,这个转换过程称为编译(Compile)。编译生成的可执行性程序可以脱离开发环境,在特定的平台上独立运行。

链接

有些程序编译结束后,还可能需要对其他编译好的目标代码进行链接,即组装两个以上的目标代码模块生成最终的可执行性程序,通过这种方式实现低层次的代码复用。

因为编译型语言是一次性地编译成机器码,所以可以脱离开发环境独立运行,而且通常运行效率较高;但因为编译型语言的程序被编译成特定平台上的机器码,因此编译生成的可执行性程序通常无法移植到其他平台上;如果需要移植,则必须将源代码复制到特定平台上,再针对特定平台进行修改,然后采用特定平台上的编译器重新编译。

常见编译型语言

现有的C、C++、Objective-C、Swit、Kotlin等高级语言都属于编译型语言。

解释型语言

是指使用专门的解释器对源程序逐行解释成特定平台的机器码并立即执行的语言。

解释型语言通常不会进行整体性的编译和链接处理,解释型语言相当于把编译型语言中的编译和解释过程混合到一起同时完成。

可以认为:每次执行解释型语言的程序都需要进行一次编译,因此解释型语言的程序运行效率通常较低,而且不能脱离解释器独立运行。

但解释型语言跨平台比较容易,只需提供特定平台的解释器即可,每个特定平台上的解释器负责将源程序解释成特定平台的机器指令即可。解释型语言可以方便地实现源程序级的移植,但这是以牺牲程序执行效率为代价的。

常见解释型语言

现有的JavaScript、Ruby、Python等语言都属于解释型语言。

伪编译型语言

除此之外,还有一种伪编译型语言,如Visual Basic,它属于半编译型语言,并不是真正的编译型语言。它首先被编译成P-代码,并将解释引擎封装在可执行性程序内,当运行程序时,P-代码会被解析成真正的二进制代码。表面上看起来,Visual Basic可以编译生成可执行的EXE文件,而且这个EXE文件也可以脱离开发环境,在特定平台上运行,非常像编译型语言。实际上,在这个EXE文件中,既有程序的启动代码,也有链接解释程序的代码,而这部分代码负责启动Visual Basic解释程序,再对Visual Basic代码进行解释并执行。

1.2.2 Java程序的运行机制和JVM

Java语言比较特殊,因为Java语言编写的程序需要经过编译步骤,但这个编译步骤并不会生成特定平台的机器码,而是编译生成一种与平台无关的字节码(也就是*.class文件)。当然,这种字节码不是可执行的,必须使用Java解释器来解释执行。因此可以认为:Java语言既是编译型语言,也是解释型语言。或者说,Java语言既不是纯粹的编译型语言,也不是纯粹的解释型语言。

Java程序的执行过程必须经过先编译、后解释两个步骤,如图1.1所示。

图1.1

JVM

Java语言里负责解释执行字节码文件的是Java虚拟机,即JVM(Java Virtual Machine)。JVM是可运行Java字节码文件的虚拟计算机。所有平台上的JVM向编译器提供相同的编程接口,而编译器只需要面向虚拟机,生成虚拟机能理解的代码,然后由虚拟机来解释执行。在一些虚拟机的实现中,还会将虚拟机代码转换成特定系统的机器码执行,从而提高执行效率。

当使用Java编译器编译Java程序时,生成的是与平台无关的字节码,这些字节码不面向任何具体平台,只面向JVM。不同平台上的JVM都是不同的,但它们都提供了相同的接口。JVM是Java程序跨平台的关键部分,只要为不同平台实现了相应的虚拟机,编译后的Java字节码就可以在该平台上运行。显然,相同的字节码程序需要在不同的平台上运行,这几乎是“不可能的”,只有通过中间的转换器才可以实现,JVM就是这个转换器。

JVM是一个抽象的计算机,和实际的计算机一样,它具有指令集并使用不同的存储区域。它负责执行指令,还要管理数据、内存和寄存器。

Oracle公司制定的Java虚拟机规范在技术上规定了JVM的统一标准,具体定义了JVM的如下细节:

  • 指令集
  • 寄存器
  • 类文件的格式
  • 栈
  • 垃圾回收堆
  • 存储区

Oracle公司制定这些规范的目的是为了提供统一的标准,最终实现Java程序的平台无关性。