JAVA-基础篇

第01章_Java语言概述


1. Java知识脉络图

1.1 Java基础全程脉络图

Java基础全程脉络图

1.2 本章专题与脉络

第1阶段:Java基本语法-第01章

2. 抽丝剥茧话Java

2.1 当前大学生就业形势

  • 麦可思研究院发布了《2022年中国大学生就业报告》,针对2021届毕业生收入较高的本科专业排行榜:
img
  • 麦可思研究院发布过《2021年中国大学生就业报告》,应届本科毕业生就业数量较大的前十位行业类的就业质量:

    jiuyezhiliang1
  • 报告还对毕业三年后的2017届毕业生所在十大行业进行了统计:

jiuyezhiliang2
  • 从国家统计局发布的2021年全国平均工资来看,不管在城镇非私营单位还是私营单位,IT业均为最高

2.2 IT互联网是否依旧靠谱

过去不能代表未来!互联网是否依旧靠谱?!

image-20220523002428542

2014 年至 2018 年间,我国网民规模从 6.49 亿增长为 8.29 亿,增幅为 27.5%。同一时间段,全国移动互联网接入的流量却从 20.6EB 增长到了 711.1EB,增幅达 3352%(获取和处理的信息量大幅增加)。

随着 5G 技术进一步拓宽移动互联网的速度和容量,产业互联网将在消费型流量的基础上创造生产型流量,根据报告的预测,至 2024 年,全国移动互联网的接入流量将达到 127663.8EB, 流量规模达到2018年的179.5倍

当下,5G、物联网、人工智能、产业互联网都是国家政策大方向,需要大量能与机器对话的中高端人才。

2.3 IT行业岗位分析

image-20220522234544537

软件开发,是进入互联网IT圈最好的选择之一!

  • 起始薪资高
  • 工作环境好
  • 涨薪幅度高
  • 行业更公平

2.4 软件开发之Java开发

image-20220504101615999

image-20220608160444947

  • 移动应用领域(集成Android平台):Java在Android端是主要开发的语言,占有重要的地位。

    image-20220511092649122 20220608_160310
  • 企业级应用领域(JavaEE后台):用来开发企业级的应用程序,大型网站如淘宝、京东、12306,以及各大物流、银行、金融、社交、医疗、交通、各种OA系统等都是用JavaEE技术开发的。

    image-20220511090528043
  • 大数据分析、人工智能领域:流行的大数据框架,如Hadoop、Flink都是用Java编写的。Spark使用Scala编写,但可以用Java开发应用。

    image-20220511091647358

    image-20220513233529610

    Eversoft公司在提到2022年Java发展趋势时写道:

    Java 是用于开发大数据项目的最主流的语言。我们可以轻松地预测它也将在之后继续主导大数据

  • 游戏领域、桌面应用、嵌入式领域:很多大型游戏的后台、桌面应用等也是Java开发的。

2.5 到底多少人在用Java

2020年,根据 IDC 的报告“Java Turns 25”显示,超过 900 万名开发人员(全球专职开发人员中的 69%)在使用 Java——比其他任何语言都多。该报告指出,大多数企业业务服务都依靠 Java 来实现。

… Java 一直是开发人员中最流行的编程语言,被誉为“宇宙第一语言”。

我想告诉大家:

“市场的需求比较大,市场的供给比较大”

“如果你在Java领域里持续积累5-7年以上,那么你至少会成为这个行业的一个专家!”

2.6 八卦一下程序员

image-20220602105134523

还可以是:

image-20220602105213557

2.7 Java系列课程体系

  • 见02_学习路线图之《Java中高级程序员全程学习路线图.xmind》

2.8 Java职业晋升路线图

技术发展路线

薪资数据统计来源:拉勾网

3. 计算机的硬件与软件

3.1 计算机组成:硬件+软件

image-20220524152902812

3.2 CPU、内存与硬盘

  • CPU(Central Processing Unit,中央处理器)

    • 人靠大脑思考,电脑靠CPU来运算、控制。
  • 硬盘(Hard Disk Drive)

    • 计算机最主要的存储设备,容量大,断电数据不丢失。
    • 正常分类:机械硬盘(HDD)固态硬盘(SSD)以及混合硬盘(SSHD)
    • 固态硬盘在开机速度和程序加载速度远远高于机械硬盘,缺点就是贵,所有无法完全取代机械硬盘。
  • 内存(Memory)

    • 负责硬盘上的数据与CPU之间数据交换处理
    • 具体的:保存从硬盘读取的数据,提供给CPU使用;保存CPU的一些临时执行结果,以便CPU下次使用或保存到硬盘。
    • 断电后数据丢失。

image-20220731234604511

3.3 输入设备:键盘输入

  • 熟悉指法
image-20220517100223784

不熟悉键盘的小伙伴,可以“金山打字通”走起了。坚决杜绝二指禅!!

image-20220517100543176

4. 软件相关介绍

4.1 什么是软件

软件,即一系列按照特定顺序组织的计算机数据指令的集合。有系统软件应用软件之分。

Pascal之父Nicklaus Wirth: “Programs = Data Structures + Algorithms”

系统软件:

image-20220522215226273

应用软件:

macdesk

4.2 人机交互方式

  • 图形化界面(Graphical User Interface,GUI),这种方式简单直观,使用者易于接受,容易上手操作。

  • 命令行方式(Command Line Interface,CLI),需要在控制台输入特定的指令,让计算机完成一些操作。需要记忆一些指令,较为麻烦。

    img

4.3 常用的DOS命令

DOS(Disk Operating System,磁盘操作系统)是Microsoft公司在Windows之前推出的一个操作系统,是单用户、单任务(即只能执行一个任务)的操作系统。现在被Windows系统取代。

对于Java初学者,学习一些DOS命令,会非常有帮助。

进入DOS操作窗口:

  • 按下Windows+R键盘,打开运行窗口,输入cmd回车,进入到DOS的操作窗口。

    image-20220520100110104

常用指令:

  • 操作1:进入和回退
操作 说明
盘符名称: 盘符切换。E:回车,表示切换到E盘。
dir 列出当前目录下的文件以及文件夹
cd 目录 进入指定单级目录。
cd 目录1\目录2\… 进入指定多级目录。cd atguigu\JavaSE
cd .. 回退到上一级目录。
cd \ 或 cd / 回退到盘符目录。
  • 操作2:增、删
操作 说明
md 文件目录名 创建指定的文件目录。
rd 文件目录名 删除指定的文件目录(如文件目录内有数据,删除失败)
  • 操作3:其它
操作 说明
cls 清屏。
exit 退出命令提示符窗口。
← → 移动光标
↑ ↓ 调阅历史操作命令
Delete和Backspace 删除字符

5. 计算机编程语言

5.1 计算机语言是什么

  • 语言:是人与人之间用于沟通的一种方式。例如:中国人与中国人用普通话沟通。而中国人要和英国人交流,可以使用英语或普通话。

  • 计算机编程语言,就是人与计算机交流的方式。人们可以使用编程语言对计算机下达命令,让计算机完成人们需要的功能。

  • 计算机语言有很多种。如:C 、C++、Java、Go、JavaScript、Python,Scala等。

体会:语言 = 语法 + 逻辑

5.2 计算机语言简史

  • 第一代:机器语言(相当于人类的石器时代)

    • 1946年2月14日,世界上第一台计算机ENAC诞生,使用的是最原始的穿孔卡片。这种卡片上使用的是用二进制代码表示的语言,与人类语言差别极大,这种语言就称为机器语言。比如一段典型的机器码:

      1
      2
      3
      1.	0000,0000,000000010000 代表 LOAD A, 16
      2. 0000,0001,000000000001 代表 LOAD B, 1
      3. 0001,0001,000000010000 代表 STORE B, 16
    • 这种语言本质上是计算机能识别的唯一语言,人类很难理解。可以大胆想象”此时的程序员99.9%都是异类!

      image-20220309223406537

  • 第二代:汇编语言(相当于人类的青铜&铁器时代)

    • 使用英文缩写的助记符来表示基本的操作,这些助记符构成了汇编语言的基础。比如:LOADMOVE等,使人更容易使用。因此,汇编语言也称为符号语言

    • 优点:能编写高效率的程序

    • 缺点:汇编语言是面向机器的,不同计算机机型特点不同,因此会有不同的汇编语言,彼此之间不能通用。程序不易移植,较难调试。

      image-20220309223725671
    • 比起机器语言,汇编大大进步了,是机器语言向更高级的语言进化的桥梁。目前仍然应用于工业电子编程领域、软件的加密解密、计算机病毒分析等。

  • 第三代:高级语言(相当于人类的信息时代)

    • 高级语言发展于20世纪50年代中叶到70年代,是一种接近于人们使用习惯的程序设计语言。它允许程序员使用接近日常英语的指令来编写程序,程序中的符号和算式也与日常用的数学式子差不多,接近于自然语言和数学语言,容易为人们掌握。比如:

      image-20211218092541175

    • 高级语言独立于机器,有一定的通用性;计算机不能直接识别和执行用高级语言编写的程序,需要使用编译器或者解释器,转换为机器语言才能被识别和执行。

image-20211218092630678

image-20211218092541075

此外,高级语言按照程序设计方法的不同,又分为:面向过程的语言面向对象的语言

  • C、Pascal、Fortran面向过程的语言
  • C++面向过程/面向对象
  • Java跨平台的纯面向对象的语言
  • C#、Python、JavaScript、Scala…

目前以及可预见的将来,计算机语言仍然处于“第三代高级语言”阶段。但是不管是什么语言,最后都要向机器语言靠近,因为CPU只认识0和1。

5.3 计算机语言排行榜

TIOBE (https://www.tiobe.com/tiobe-index/)是一个流行编程语言排行,每月更新。排名权重基于世界范围内 工程师数量,Google、Bing、Yahoo! 、Wikipedia、Amazon、Youtube和百度这些主流的搜索引擎,也将作为排名权重的参考指标。

image-20221027112824724

计算机语言走势

image-20221027113511832

5.4 编程语言,该学哪个?

image-20220310151657860

网传的编程语言鄙视链:

image-20220310151750665

  • C语言:万物之源
  • 诞生于1972年,由AT&T公司旗下贝尔实验室创建完成,用于构建Unix操作系统。

  • 偏向计算机底层操作(操作系统、网络、硬件驱动等)。

  • 优势:几乎所有现代编程语言都脱胎于C

  • 劣势:相当陡的学习曲线;不支持面向对象编程

  • C++语言:难学的语言
  • 诞生于1983年,作为C语言的增强方案、升级版本。C++是C语言的超集,C语言的大部分知识也适用于C++。
  • 用途:windows 或 MacOS UI、office全家桶、主流的浏览器、Oracle、MySQL、著名的游戏引擎(如星际争霸、魔兽世界)等
  • 优势:很多公司都用 C++ 开发核心架构,如 Google、腾讯、百度、阿里云等;面向对象性
  • 劣势:体系极为庞大,这是世界目前来说最复杂也是最难的编程语言。
  • C#语言:背靠大树的语言
  • 诞生于2000年,一款强大而灵活的编程语言。靠着微软这棵大树,是多年来windows平台的一门主流编程语言。
  • 用途:windows桌面应用开发、Windows Azure、游戏开发
  • PHP语言:最好的语言?
  • 诞生于1994年,一款服务器端脚本语言。最初表示个人主页(Personal Home Page)
  • PHP语法和C类似,有很多的模板和框架,简单易懂,也许你可以在短短几天做出web app。它主要用于web端,快速建站网络开发
  • 劣势:学习门槛太低;其代码运行速度低于大部分编程语言党争对手
  • Python:易学的语言
  • 诞生于1991年,一种面向对象的语言,虽然运行效率不高,但是开发效率非常高
  • Python被称为胶水语言,哪里都可以用。
  • JavaScript语言:前端为王
  • 诞生于1995年,网景公司开发完成。
  • JavaScript是JavaScript 是目前所有主流浏览器上唯一支持的脚本语言。在前端开发中,占有不可替代的地位。
  • Java语言:需求旺盛
  • 创建于1995年,Java是一种面向对象、基于类的编程语言。
  • Java可能是目前运用最广的项目语言。代码稳定性超过 C 和 C++,生产力远超 C 和 C++。有 JVM 在,可以轻松地跨平台。
  • 具有强大的开源开发工具,大量的开源共享库。
  • Java拥有世界上数量最多的程序员,最不缺人。
  • Go语言:夹缝中生存
  • Go 语言现在很受关注,它是取代 C 和 C++ 的另一门有潜力的语言。
    • C 语言太原始了,C++ 太复杂了,Java 太高级了,所以 Go 语言就在这个夹缝中出现了。
  • Go语言已成为云计算领域事实上的标准语言,尤其是在 Docker/Kubernetes 等项目中。
  • Go 语言语法特别简单,你有了 C 和 C++ 的基础,学习 Go 的学习成本基本为零。
  • Go社区从Java社区移植了各种优秀的框架或库。

总结:

  • 程序设计语言有很多种,每种语言都是为了实现某个特定的目的而发明的。
  • 没有“最好”的语言,只有在特定场景下相对来说,最适合的语言而已。
  • 如果你掌握了一种编程语言,也会更容易上手其它的编程语言。关键是学习如何使用程序设计方法来解决问题。这也是本套课程的主旨。
  • Talk is cheap,Show me the code

6. Java语言概述

6.1 Java概述

  • SUN(Stanford University Network,斯坦福大学网络公司 ) 1995年推出的一门高级编程语言。

  • 是一种面向Internet的编程语言。Java一开始富有吸引力是因为Java程序可以在Web浏览器中运行。这些Java程序被称为Java小程序(applet),内嵌在HTML代码中。

  • 伴随着互联网的迅猛发展,以及Java技术在web方面的不断成熟,已经成为Web应用程序的首选开发语言。

6.2 Java语言简史

起步阶段:

1991年,Sun公司的工程师小组想要设计一种语言,应用在电视机电话闹钟烤面包机等家用电器的控制和通信。由于这些设备的处理能力内存都很有限,并且不同的厂商会选择不同的中央处理器(CPU),因此这种语言的关键是代码短小紧凑与平台无关(即不能与任何特定的体系结构捆绑在一起)。

Gosling团队率先创造了这个语言,并命名为“Oak“(起名的原因是因为他非常喜欢自己办公室外的橡树)。后因智能化家电的市场需求没有预期的高,Sun公司放弃了该项计划。

随着20世纪90年代互联网的发展,Sun公司发现该语言在互联网上应用的前景,于是改造了Oak,于1995年5月以Java的名称正式发布。(Java是印度尼西亚爪哇岛的英文名称,因盛产咖啡而闻名。)

image-20220309230839100

发展阶段:

发行版本 发行时间 备注
Java 1.0 1996.01.23 Sun公司发布了Java的第一个开发工具包
Java 1.1 1997.02.19 JavaOne会议召开,创当时全球同类会议规模之最。
Java 1.2 1998.12.08 Java拆分成:J2SE(标准版)、J2EE(企业版)、J2ME(小型版)
Java 1.3 2000.05.08
Java1.4 2004.02.06
Java 5.0 2004.09.30 ①版本号从1.4直接更新至5.0;②平台更名为JavaSE、JavaEE、JavaME
Java 6.0 2006.12.11 2009.04.20 Oracle宣布收购SUN公司
2009.04.20 Oracle公司收购SUN,交易价格74亿美元。
Java 7.0 2011.07.02
Java 8.0 2014.03.18 此版本是继Java 5.0以来变化最大的版本。是长期支持版本(LTS)
Java 9.0 2017.09.22 ①此版本开始,每半年更新一次;②Java 9.0开始不再支持windows 32位系统
Java 10.0 2018.03.21
Java 11.0 2018.09.25 JDK安装包取消独立JRE安装包,是长期支持版本(LTS)
Java 12.0 2019.03.19
Java17.0 2021.09 发布Java 17.0,版本号也称为21.9,是长期支持版本。
Java19.0 2022.09 发布Java19.0,版本号也称为22.9。

6.3 Java之父

image-20220310152923961
  • 詹姆斯·高斯林(James Gosling)先生以“Java 技术之父”而闻名于世。他是Java 技术的创始人,他亲手设计了Java语言,并开发了Java编译器和Java虚拟机,使Java成为了世界上最流行的开发语言。
  • James Gosling于1984 年加入Sun公司,并一直服务于Sun公司,直至2010年前后,Sun被Oracle并购而加入Oracle,担任客户端软件集团的首席技术官; 2010年4月从Oracle离职。

6.4 公司八卦

  • SUN 与 Oracle

SUN是一家极具创新能力的公司,2001年 “9.11”以前,SUN公司市值超过1000亿美元。 但是没能利用Java构建一个强有力、可变现的生态系统,没打好Java这张牌。此后,互联网泡沫破裂,硬件需求大幅减少,它的市值在一个月之内跌幅超过90%。SUN公司的成长用了20年,而衰落只用了1年!

image-20220309230923533
  • Oracle 与 Google

Google和Oracle的侵权事件:

2010 年 8 月,Oracle 起诉 Google 的 Android 系统侵权,要求赔偿 26 亿美元。

  • Oracle认为Google的代码中使用了Java的37个API,并且认为Google是故意为之,因为这样做的好处是可以让更多的Java程序员更容易接受Android的代码。
  • Oracle认为Android 中有9行代码直接抄袭了Java的实现。这9行牛气哄哄的代码都出自一人之手,他就是Java 大牛——Joshua Bloch

2018 年 3 月,美国联邦巡回上诉法院裁决,谷歌侵犯了甲骨文的版权,支付高达88亿美元的赔偿金。

2021 年 4 月,美国最高法院给出了最终裁决:谷歌胜诉,其代码属于“合理使用”的范畴。为期十多年的软件行业“第一版权案”落幕。

1
2
3
4
5
6
7
8
9
10
//Code In OpenJDK / Android :
1. private static void rangeCheck(int arrayLen, int fromIndex, int toIndex) {
2. if (fromIndex > toIndex)
3. throw new IllegalArgumentException("fromIndex(" + fromIndex +
4. ") > toIndex(" + toIndex+")");
5. if (fromIndex < 0)
6. throw new ArrayIndexOutOfBoundsException(fromIndex);
7. if (toIndex > arrayLen)
8. throw new ArrayIndexOutOfBoundsException(toIndex);
9. }

image-20221027145908490

6.5 Java技术体系平台

  • Java SE(Java Standard Edition)标准版
    • 支持面向桌面级应用(如Windows下的应用程序)的Java平台,即定位个人计算机的应用开发。
    • 包括用户界面接口AWT及Swing,网络功能与国际化、图像处理能力以及输入输出支持等。
    • 此版本以前称为J2SE
  • Java EE(Java Enterprise Edition)企业版
    • 为开发企业环境下的应用程序提供的一套解决方案,即定位在服务器端的Web应用开发
    • JavaEE是JavaSE的扩展,增加了用于服务器开发的类库。如:Servlet能够延伸服务器的功能,通过请求-响应的模式来处理客户端的请求;JSP是一种可以将Java程序代码内嵌在网页内的技术。
    • 版本以前称为J2EE
  • Java ME(Java Micro Edition)小型版
    • 支持Java程序运行在移动终端(手机、机顶盒)上的平台,即定位在消费性电子产品的应用开发
    • JavaME是JavaSE的内伸,精简了JavaSE 的核心类库,同时也提供自己的扩展类。增加了适合微小装置的类库:javax.microedition.io.*等。
    • 此版本以前称为J2ME

image-20211218093009884

注意:

Android开发不等同于Java ME的开发

7. Java开发环境搭建(掌握)

7.1 什么是JDK、JRE

  • JDK (Java Development Kit):是Java程序开发工具包,包含JRE 和开发人员使用的工具。
  • *JRE * (Java Runtime Environment) :是Java程序的运行时环境,包含JVM 和运行时所需要的核心类库

如下是Java 8.0 Platform:

image-20220310200731185

小结:

JDK = JRE + 开发工具集(例如Javac编译工具等)

JRE = JVM + Java SE标准类库

7.2 JDK版本选择

image-20221108221057540

image-20220310201541841

  • 自Java 8版本发布以来,其后的每次更新,都会有小伙伴高呼:Java8 YYDS!
  • 论坛的声音:“你发任你发,我用Java 8!

数据说话1:

JRebel 于2022年4月前后发布了《2022 年Java生态系统状况报告》,报告中提到使用Java11 的占比最多,Java 8 紧随其后,如下图。而此前2020年的报告显示,Java8占比达到了84.48%

d315

我的分析:

G1是最受欢迎的GC算法。Java 11及更高版本的G1收集器是默认的GC,而Java 8中并不是。出于对G1的喜爱,很多开发者才会选择抛弃Java 8。

数据说话2:

此外,某美国软件开发商在对近千名专业的Java开发者调研后,发布的《2022年Java开发者生产力报告》称:八年前发布的Java 8依然是Java中应用最广泛的版本,占比 37%,其次是 Java 11,占比29%

高斯林说话:


image-20220428151952049

Spring框架说话:

在Java 17正式发布之前,Java开发框架Spring率先在官博宣布,Spring Framework 6和Spring Boot 3计划在2022年第四季度实现总体可用性的高端基线:

  • Java 17+(来自 Spring Framework 5.3.x 线中的 Java 8-17)
  • Jakarta EE 9+(来自Spring框架5.3.x 线中的 Java EE 7-8)

Spring 官方说明:https://spring.io/blog/2022/01/20/spring-boot-3-0-0-m1-is-now-available

image-20220527155439683

意味着:springboot3.0 是需要用java17和spring6.0为基础建设。如果从企业选型最新springboot3.0作为架构来说,它搭配jdk17肯定是标配了。

7.3 JDK的下载

  • 下载网址(Oracle公司官网):
  • 下载步骤:如图所示,在官网底部选择Developers开发者

1572254490435

(1)在Developers页面中间的技术分类部分,选择Java,单击进入,如图所示:

image-20211019110551338

(2)这里展示的是最新Java版本,例如Java17。单击Download Java,然后选择具体的版本下载。

image-20211019110843394

(3)选择Download Java按钮后

image-20211019111110242

(4)如果想安装Java8 可以选择如下位置:

image-20211019111150970

(5)选择Accept License Agreement,

image-20211019111252989

(6)注册或登录后下载:

image-20211019111922387

(7)如果需要安装其它版本,可以选择Java archive:

image-20220801004606762

接着进行选择下载即可:

image-20220801004648997

7.4 JDK的安装

  • 安装说明
    • 傻瓜式安装,下一步即可。
    • 建议:安装路径不要有中文或者空格等特殊符号。
    • 本套课程会同时安装JDK8 和 JDK17,并以JDK17为默认版本进行讲解。
  • 安装步骤:

(1)双击jdk-17_windows-x64_bin.exe文件,并单击下一步,如图所示:

image-20220303083718546

(2)修改安装路径,单击更改,如图所示:

image-20220303083759546

(3)将安装路径修改为D:\develop_tools\jdk\jdk-17.0.2\,并单击下一步,如图所示:

image-20220303083845794

(4)稍后几秒,安装完成,如图所示:

image-20220303083905277

image-20220303083931309

7.5 配置path环境变量

7.5.1 理解path环境变量

什么是path环境变量?

答:window操作系统执行命令时,所要搜寻的路径。

为什么配置path?

答:希望在命令行使用javac.exe等工具时,任意目录下都可以找到这个工具所在的目录。

以JDK为例演示

我们在C:\Users\songhk目录下使用javac命令,结果如下:

image-20220310223852720

我们在JDK的安装目录的bin目录下使用javac命令,结果如下:

image-20220607113708022

我们不可能每次使用java.exe,javac.exe等工具的时候都进入到JDK的安装目录下,太麻烦了。这时就需要配置path环境变量。

7.5.2 JDK8配置方案1:只配置path

  • 步骤:

(1)打开桌面上的计算机,进入后在左侧找到此电脑,单击鼠标右键,选择属性,如图所示:

image-20220310224844837

(2)选择高级系统设置,如图所示:

image-20220310224912273

(3)在高级选项卡,单击环境变量,如图所示:

image-20220310224952745

(4)在系统变量中,选中Path 环境变量,双击或者点击编辑 ,如图所示:

image-20220310225023208

(5)点击新建,填入D:\develop_tools\jdk\jdk1.8.0_271\bin ,并将此值上移到变量列表的首位。如图所示:

  • 编辑模式1:
image-20220607114102135
  • 编辑模式2:(注意,结尾需要有英文模式下的;)

image-20220929104547925

(6)环境变量配置完成,重新开启DOS命令行,在任意目录下输入javacjava命令或java -version,运行成功。

image-20220607114432286 image-20220607114517652

7.5.3 JDK8配置方案2:配置JAVA_HOME+path(推荐)

  • 步骤:

(1)打开桌面上的计算机,进入后在左侧找到计算机,单击鼠标右键,选择属性,如图所示:

image-20220310224844837

(2)选择高级系统设置,如图所示:

image-20220310224912273

(3)在高级选项卡,单击环境变量,如图所示:

image-20220310224952745

(4)在系统变量中,单击新建 ,创建新的环境变量,如图所示:

image-20220310225245208

(5)变量名输入JAVA_HOME,变量值输入 D:\develop_tools\jdk\jdk1.8.0_271 ,单击确定,如图所示:

image-20220607114800372

(6)选中Path 环境变量,双击或者点击编辑 ,如图所示:

image-20220310225023208

(7)在变量值的最前面,键入%JAVA_HOME%\bin。如图所示:

image-20220607114953758

注意:强烈建议将%JAVA_HOME%\bin声明在path环境变量中所有变量的最前面!

(8)环境变量配置完成,重启DOS命令行,在任意目录下输入javacjava命令或java -version,运行成功。

image-20220607114432286 image-20220607114517652

我想说:

有的书籍、论坛、视频上还提到配置classpath,用于指名class文件识别的路径。其实是没必要的,反而建议大家如果配置了classpath环境变量,要删除。对于初学者,反而不友好。

小结如下:

image-20220607115255562

7.5.4 JDK17配置方案:自动配置

不管大家有没有提前安装JDK8或其它版本JDK,在我们安装完JDK17之后,理应按JDK8的方式配置path环境变量。但是,我们发现在安装完JDK17以后,配置环境变量之前,执行CMD指令:

image-20220607115700870

竟然成功了!而且是17.0.2版本。因为JDK17在安装之后,自动进行了环境变量的配置。如下:

image-20220607115840388

这里建议,将JDK17安装的路径,设置为JAVA_HOME,并将%JAVA_HOME%\bin上移到首位。

image-20221110142710972 image-20221110142734279

思考:如果你仍然希望在JDK8下开发Java程序?如何做呢?

8. 开发体验:HelloWorld(掌握)

JDK安装完毕,我们就可以开发第一个Java程序了,习惯性的称为:HelloWorld。

8.1 开发步骤

Java程序开发三步骤:编写编译运行

  • 将 Java 代码编写到扩展名为 .java 的源文件中
  • 通过 javac.exe 命令对该 java 文件进行编译,生成一个或多个字节码文件
  • 通过 java.exe 命令对生成的 class 文件进行运行

image-20220310230210728

8.2 编写

(1)在D:\JavaSE\chapter01 目录下新建文本文件,完整的文件名修改为HelloWorld.java,其中文件名为HelloWorld,后缀名必须为.java

image-20220801010222222

(2)用记事本或editplus等文本编辑器打开(虽然记事本也可以,但是没有关键字颜色标识,不利于初学者学习)

(3)在文件中输入如下代码,并且保存:

1
2
3
4
5
class HelloChina {
public static void main(String[] args) {
System.out.println("HelloWorld!!");
}
}
  • 友情提示1:每个字母和符号必须与示例代码一模一样,包括大小写在内。

  • 友情提示2:

image-20220310230618659

image-20220521173215013

第一个HelloWord 源程序就编写完成了,但是这个文件是程序员编写的,JVM是看不懂的,也就不能运行,因此我们必须将编写好的Java源文件 编译成JVM可以看懂的字节码文件 ,也就是.class文件。

8.3 编译

在DOS命令行中,进入D:\JavaSE\chapter01目录,使用javac 命令进行编译。

使用文件资源管理器打开D:\JavaSE\chapter01目录,然后在地址栏输入cmd。

image-20220801010435144

命令:

1
javac Java源文件名.后缀名java

举例:

1
javac HelloWorld.java
image-20220801010840640

编译成功后,命令行没有任何提示。打开D:\JavaSE\chapter01目录,发现产生了一个新的文件 HelloChina.class,该文件就是编译后的文件,是Java的可运行文件,称为字节码文件,有了字节码文件,就可以运行程序了。

8.4 运行

在DOS命令行中,在字节码文件目录下,使用java 命令进行运行。

命令:

1
java 主类名字

主类是指包含main方法的类,main方法是Java程序的入口:

1
2
3
public static void main(String[] args){

}

举例:

1
java HelloChina

错误演示:

java HelloChina.class

image-20220801011104381

9. Java开发工具

9.1 都有哪些开发Java的工具

  • 级别一:文本开发工具
image-20220513185907022
  • 级别二:集成开发环境(Integrated Development Environment,IDE)

把代码编写,编译,执行,调试等多种功能综合到一起的开发工具。

image-20220513190655773

9.2 如何选择

前期我们先使用文本开发工具,培养代码感,利于公司笔、面试。

后期我们使用IDE,提供更强大的功能支持

10. HelloWorld案例常见错误

10.1 拼写问题

  • 单词拼写问题
    • 正确:class 错误:Class
    • 正确:String 错误:string
    • 正确:System 错误:system
    • 正确:main 错误:mian
  • Java语言是一门严格区分大小写的语言
  • 标点符号使用问题
    • 不能用中文符号,英文半角的标点符号(正确)
    • 括号问题,成对出现

10.2 编译、运行路径问题

举例1:

image-20220310231657692
  • 源文件名不存在或者写错
  • 当前路径错误
  • 后缀名隐藏问题

举例2:

image-20220310231717893
  • 类文件名写错,尤其文件名与类名不一致时,要小心
  • 类文件不在当前路径下,或者不在classpath指定路径下

10.3 语法问题

举例1:

image-20220310231746581

声明为public的类应与文件名一致,否知编译失败。

举例2:

image-20220310231815405

编译失败,注意错误出现的行数,再到源代码中指定位置改错

10.4 字符编码问题

当cmd命令行窗口的字符编码与.java源文件的字符编码不一致,如何解决?

1557881223916

解决方案一:

  • 在Notepad++等编辑器中,修改源文件的字符编码:
1557881271819
  • 在EditPlus中可以将Java源文件另存为ANSI编码方式(中文操作系统下即为GBK字符集)
1658810752424

解决方案二:

在使用javac命令式,可以指定源文件的字符编码
1
javac -encoding utf-8 Review01.java

10.5 建议

  • 注意缩进!

    • 一定要有缩进。缩进就像人得体的衣着一样!

    • 只要遇到{}就缩进,缩进的快捷键tab键。

  • 必要的空格

    • 变量类型、变量、赋值符号、变量值之间填充相应空格,更美观。比如: int num = 10;

11. HelloWorld小结

11.1 Java程序的结构与格式

结构:

1
2
3
4
5
类{
方法{
语句;
}
}

格式:

(1)每一级缩进一个Tab键

(2){}的左半部分在行尾,右半部分单独一行,与和它成对的”{“的行首对齐

11.2 Java程序的入口

Java程序的入口是main方法

1
2
3
public static void main(String[] args){

}

11.3 两种常见的输出语句

  • 换行输出语句:输出内容,完毕后进行换行,格式如下:

    1
    System.out.println(输出内容);
  • 直接输出语句:输出内容,完毕后不做任何处理,格式如下

    1
    System.out.print(输出内容);

注意事项:

​ 换行输出语句,括号内可以什么都不写,只做换行处理

​ 直接输出语句,括号内什么都不写的话,编译报错

11.4 源文件名与类名

(1)源文件名是否必须与类名一致?public呢?

1
2
3
4
5
如果这个类不是public,那么源文件名可以和类名不一致。但是不便于代码维护。

如果这个类是public,那么要求源文件名必须与类名一致。否则编译报错。

我们建议大家,不管是否是public,都与源文件名保持一致,而且一个源文件尽量只写一个类,目的是为了好维护。

(2)一个源文件中是否可以有多个类?public呢?

1
2
3
一个源文件中可以有多个类,编译后会生成多个.class字节码文件。

但是一个源文件只能有一个public的类。

12. 注释(comment)

  • 什么是注释?

    • 源文件中用于解释、说明程序的文字就是注释。
  • 注释是一个程序员必须要具有的良好编程习惯。实际开发中,程序员可以先将自己的思想通过注释整理出来,再用代码去体现。

    程序员最讨厌两件事:

    一件是自己写代码被要求加注释

    另一件是接手别人代码,发现没有注释

  • 不加注释的危害

  • Java中的注释类型:

    • 单行注释

      1
      //注释文字
    • 多行注释

      1
      2
      3
      4
      5
      /* 
      注释文字1
      注释文字2
      注释文字3
      */
    • 文档注释 (Java特有)

      1
      2
      3
      4
      /**
      @author 指定java程序的作者
      @version 指定源文件的版本
      */
  • 注释的作用

    • 它提升了程序的可阅读性。(不加注释的危害性,见图。)
    • 调试程序的重要方法。
  • 具体使用1:单行注释、多行注释

    • 对于单行和多行注释,被注释的文字,不会出现在字节码文件中,进而不会被JVM(java虚拟机)解释执行。

    • 多行注释里面不允许有多行注释嵌套。

    • 一个段子

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      A:嘿 //是什么意思啊?
      B:嘿.
      A:呃 我问你//是什么意思?
      B:问吧.
      A:我刚才不是问了么?
      B:啊?
      A:你再看看记录...
      B:看完了.
      A:......所以//是啥?
      B:所以什么?
      A:你存心耍我呢吧?
      B:没有啊 你想问什么?
      ……
      不断循环之后,A一气之下和B绝交,自己苦学程序。
      N年之后,A终于修成正果,回想起B,又把聊天记录翻出来看,这时,他突然发现B没有耍他……
      而他自己也不知道当年他问B的究竟是什么问题……
  • 具体使用2:文档注释(Java特有)

    • 文档注释内容可以被JDK提供的工具 javadoc 所解析,生成一套以网页文件形式体现的该程序的说明文档。

    • 操作方式。比如:

      1
      javadoc -d mydoc -author -version HelloWorld.java
image-20220310232527578

案例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
//单行注释
/*
多行注释
*/
/**
文档注释演示。这是我的第一个Java程序!^_^
@author songhk
@version 1.0
*/
public class HelloWorld{

/**
Java程序的入口
@param args main方法的命令参数
*/
public static void main(String[] args){
System.out.println("hello");
}
}

13. Java API文档

  • API (Application Programming Interface,应用程序编程接口)是 Java 提供的基本编程接口。
  • Java语言提供了大量的基础类,因此 Oracle 也为这些基础类提供了相应的说明文档,用于告诉开发者如何使用这些类,以及这些类里包含的方法。大多数Java书籍中的类的介绍都要参照它来完成,它是编程者经常查阅的资料。
  • Java API文档,即为JDK使用说明书、帮助文档。类似于:
新华字典

14. Java核心机制:JVM

14.1 Java语言的优缺点

Java确实是从C语言和C++语言继承了许多成份,甚至可以将Java看成是类C语言发展和衍生的产物。“青出于蓝,而胜于蓝”。

14.1.1 优点

  • 跨平台性:这是Java的核心优势。Java在最初设计时就很注重移植和跨平台性。比如:Java的int永远都是32位。不像C++可能是16,32,可能是根据编译器厂商规定的变化。
  • 通过Java语言编写的应用程序在不同的系统平台上都可以运行。“Write once , Run Anywhere”。
  • 原理:只要在需要运行 java 应用程序的操作系统上,先安装一个Java虚拟机 (JVM ,Java Virtual Machine) 即可。由JVM来负责Java程序在该系统中的运行。
JVM的跨平台性

image-20211217111217831

  • 面向对象性:

    面向对象是一种程序设计技术,非常适合大型软件的设计和开发。面向对象编程支持封装、继承、多态等特性,让程序更好达到高内聚低耦合的标准。

  • 健壮性:吸收了C/C++语言的优点,但去掉了其影响程序健壮性的部分(如指针、内存的申请与释放等),提供了一个相对安全的内存管理和访问机制。

  • 安全性高:

    Java适合于网络/分布式环境,需要提供一个安全机制以防恶意代码的攻击。如:安全防范机制(ClassLoader类加载器),可以分配不同的命名空间以防替代本地的同名类、字节代码检查。

  • 简单性:

    Java就是C++语法的简化版,我们也可以将Java称之为“C++--”。比如:头文件,指针运算,结构,联合,操作符重载,虚基类等。

  • 高性能:

    • Java最初发展阶段,总是被人诟病“性能低”;客观上,高级语言运行效率总是低于低级语言的,这个无法避免。Java语言本身发展中通过虚拟机的优化提升了几十倍运行效率。比如,通过JIT(JUST IN TIME)即时编译技术提高运行效率。

    • Java低性能的短腿,已经被完全解决了。业界发展上,我们也看到很多C++应用转到Java开发,很多C++程序员转型为Java程序员。

14.1.2 缺点

  • 语法过于复杂、严谨,对程序员的约束比较多,与python、php等相比入门较难。但是一旦学会了,就业岗位需求量大,而且薪资待遇节节攀升
  • 一般适用于大型网站开发,整个架构会比较重,对于初创公司开发和维护人员的成本比较高(即薪资高),选择用Java语言开发网站或应用系统的需要一定的经济实力。
  • 并非适用于所有领域。比如,Objective C、Swift在iOS设备上就有着无可取代的地位。浏览器中的处理几乎完全由JavaScript掌控。Windows程序通常都用C++或C#编写。Java在服务器端编程和跨平台客户端应用领域则很有优势。

14.2 JVM功能说明

JVMJava Virtual Machine ,Java虚拟机):是一个虚拟的计算机,是Java程序的运行环境。JVM具有指令集并使用不同的存储区域,负责执行指令,管理数据、内存、寄存器。

image-20220310165805739

14.2.1 功能1:实现Java程序的跨平台性

我们编写的Java代码,都运行在JVM 之上。正是因为有了JVM,才使得Java程序具备了跨平台性。

image-20220513191856727

使用JVM前后对比:

image-20220514151716805

14.2.2 功能2:自动内存管理(内存分配、内存回收)

  • Java程序在运行过程中,涉及到运算的数据的分配存储等都由JVM来完成
  • Java消除了程序员回收无用内存空间的职责。提供了一种系统级线程跟踪存储空间的分配情况,在内存空间达到相应阈值时,检查并释放可被释放的存储器空间。
  • GC的自动回收,提高了内存空间的利用效率,也提高了编程人员的效率,很大程度上减少了因为没有释放空间而导致的内存泄漏

面试题:

Java程序还会出现内存溢出和内存泄漏问题吗? Yes!

15. 章节案例

案例1:个人信息输出

image-20220315221336038

1
2
3
4
5
6
7
8
class Exercise1{
public static void main(String[] args){
System.out.println("姓名:康师傅");
System.out.println();//换行操作
System.out.println("性别:男");
System.out.println("家庭住址:北京程序员聚集地:回龙观");
}
}

案例2:输出:心形

结合\n(换行),\t(制表符),空格等在控制台打印出如下图所示的效果。

image-20220309004152219

方式一:

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
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
//方式一:
class Exercise2{
public static void main(String[] args){
System.out.print("\t");
System.out.print("*");
System.out.print("\t");
System.out.print("\t");
System.out.print("\t");
System.out.print("\t");
System.out.print("\t");
System.out.print("\t");
System.out.print("\t");

System.out.println("*");


System.out.print("*");
System.out.print("\t");
//System.out.print("\t");
System.out.print("\t");
System.out.print("\t");
System.out.print("I love java");
System.out.print("\t");
System.out.print("\t");
System.out.print("\t");
System.out.print("\t");
System.out.print("\t");
System.out.println("*");


System.out.print("\t");
System.out.print("*");
System.out.print("\t");
System.out.print("\t");
System.out.print("\t");
System.out.print("\t");
System.out.print("\t");
System.out.print("\t");
System.out.print("\t");

System.out.println("*");


System.out.print("\t");
System.out.print("\t");
System.out.print("*");
System.out.print("\t");
System.out.print("\t");
System.out.print("\t");
System.out.print("\t");
System.out.print("\t");

System.out.println("*");


System.out.print("\t");
System.out.print("\t");
System.out.print("\t");
System.out.print("*");
System.out.print("\t");
System.out.print("\t");
System.out.print("\t");

System.out.println("*");


System.out.print("\t");
System.out.print("\t");
System.out.print("\t");
System.out.print("\t");
System.out.print("*");
System.out.print("\t");

System.out.println("*");


System.out.print("\t");
System.out.print("\t");
System.out.print("\t");
System.out.print("\t");
System.out.print(" ");
System.out.print("*");

}

}

方式二:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Exercise3{
public static void main(String[] args){

System.out.print("\t"+"*"+"\t\t\t\t\t\t\t\t\t\t\t\t"+"*"+"\t"+"\n");
System.out.print("*"+"\t\t"+"*"+"\t\t\t\t"+"I love Java"+"\t\t\t\t"+"*"+"\t\t\t"+"*"+"\n");
System.out.print("\t"+"*"+"\t\t\t\t\t\t\t\t\t\t\t\t"+"*"+"\t"+"\n");
System.out.print("\t\t"+"*"+"\t\t\t\t\t\t\t\t\t\t"+"*"+"\t\t"+"\n");
System.out.print("\t\t\t"+"*"+"\t\t\t\t\t\t\t\t"+"*"+"\t"+"\n");
System.out.print("\t\t\t\t"+"*"+"\t\t\t\t\t\t"+"*"+""+"\t"+"\n");
System.out.print("\t\t\t\t\t"+"*"+"\t\t\t\t"+"*"+""+"\t\t"+"\n");
System.out.print("\t\t\t\t\t\t"+"*"+"\t\t"+"*"+""+"\t\t"+"\n");
System.out.print("\t\t\t\t\t\t\t"+"*"+"\n");


}

}

第02章_变量与运算符


本章专题与脉络

第1阶段:Java基本语法-第02章

1. 关键字(keyword)

  • 定义:被Java语言赋予了特殊含义,用做专门用途的字符串(或单词)
    • HelloWorld案例中,出现的关键字有 classpublicstaticvoid 等,这些单词已经被Java定义好了。
  • 特点:全部关键字都是小写字母
  • 关键字比较多,不需要死记硬背,学到哪里记到哪里即可。
  • 官方地址: https://docs.oracle.com/javase/tutorial/java/nutsandbolts/_keywords.html

image-20220310234414849

说明:

  1. 关键字一共50个,其中constgoto保留字(reserved word)。
  2. truefalsenull不在其中,它们看起来像关键字,其实是字面量,表示特殊的布尔值和空值。
image-20220310234557454 image-20220801143635356

2. 标识符( identifier)

Java中变量、方法、类等要素命名时使用的字符序列,称为标识符。

技巧:凡是自己可以起名字的地方都叫标识符。(自己定义类名/自己定义变量名/自己定义方法名)

标识符的命名规则(必须遵守的硬性规定):

1
2
3
4
5
> 由26个英文字母大小写,0-9 ,_或 $ 组成  
> 数字不可以开头。
> 不可以使用关键字和保留字,但能包含关键字和保留字。
> Java中严格区分大小写,长度无限制。
> 标识符不能包含空格。

练习:miles、Test、a++、 –a、4#R、$4、 #44、apps、class、public、int、x、y、radius

标识符的命名规范(建议遵守的软性要求,否则工作时容易被鄙视):

1
2
3
4
5
6
7
8
9
10
11
> 包名:多单词组成时所有字母都小写:xxxyyyzzz。
例如:java.lang、com.atguigu.bean

> 类名、接口名:多单词组成时,所有单词的首字母大写:XxxYyyZzz
例如:HelloWorld,String,System等

> 变量名、方法名:多单词组成时,第一个单词首字母小写,第二个单词开始每个单词首字母大写:xxxYyyZzz
例如:age,name,bookName,main,binarySearch,getName

> 常量名:所有字母都大写。多单词时每个单词用下划线连接:XXX_YYY_ZZZ
例如:MAX_VALUE,PI,DEFAULT_CAPACITY

注意:在起名字时,为了提高阅读性,要尽量有意义,“见名知意”。

更多细节详见《代码整洁之道_关于标识符.txt》《阿里巴巴Java开发手册-1.7.1-黄山版》

3. 变量

3.1 为什么需要变量

image-20220513235020527

一花一世界,如果把一个程序看做一个世界或一个社会的话,那么变量就是程序世界的花花草草、万事万物。即,变量是程序中不可或缺的组成单位,最基本的存储单元

image-20220513235828042

3.2 初识变量

  • 变量的概念:

    • 内存中的一个存储区域,该区域的数据可以在同一类型范围内不断变化

    • 变量的构成包含三个要素:数据类型变量名存储的值

    • Java中变量声明的格式:数据类型 变量名 = 变量值

      image-20220311000744867
  • 变量的作用:用于在内存中保存数据。

  • 使用变量注意:

    • Java中每个变量必须先声明,后使用。 ①先声明后赋值 int age; age=10; ②声明同时赋值 int age=25;
    • 使用变量名来访问这块区域的数据。
    • 变量的作用域:其定义所在的一对{ }内。
    • 变量只有在其作用域内才有效。出了作用域,变量不可以再被调用。
    • 同一个作用域内,不能定义重名的变量。(不同域可以同名)

3.3 Java中变量的数据类型

Java中变量的数据类型分为两大类:

  • 基本数据类型:包括 整数类型浮点数类型字符类型布尔类型

  • 引用数据类型:包括数组接口枚举注解记录

    Java的数据类型

3.4 变量的使用

3.4.1 步骤1:变量的声明

1
格式:数据类型  变量名;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//例如:
//存储一个整数类型的年龄
int age;

//存储一个小数类型的体重
double weight;

//存储一个单字符类型的性别
char gender;

//存储一个布尔类型的婚姻状态
boolean marry;

//存储一个字符串类型的姓名
String name;

//声明多个同类型的变量
int a,b,c; //表示a,b,c三个变量都是int类型。

注意:变量的数据类型可以是基本数据类型,也可以是引用数据类型。

3.4.2 步骤2:变量的赋值

给变量赋值,就是把“值”存到该变量代表的内存空间中。同时,给变量赋的值类型必须与变量声明的类型一致或兼容。

变量赋值的语法格式:

1
变量名 = 值;

举例1:可以使用合适类型的常量值给已经声明的变量赋值

1
2
3
age = 18;
weight = 109;
gender = '女';

举例2:可以使用其他变量或者表达式给变量赋值

1
2
3
4
5
6
int m = 1;
int n = m;

int x = 1;
int y = 2;
int z = 2 * x + y;

3:变量可以反复赋值

1
2
3
4
5
6
7
//先声明,后初始化
char gender;
gender = '女';

//给变量重新赋值,修改gender变量的值
gender = '男';
System.out.println("gender = " + gender);//gender = 男

举例4:也可以将变量的声明和赋值一并执行

1
2
boolean isBeauty = true;
String name = "迪丽热巴";

内存结构如图:

image-20220514152216148

4. 基本数据类型介绍

4.1 整数类型:byte、short、int(默认)、long

image-20220520111756274
  • Java各整数类型有固定的表数范围和字段长度,不受具体操作系统的影响,以保证Java程序的可移植性。
image-20220311001553945
  • 定义long类型的变量,赋值时需要以”l“或”L“作为后缀。 – 例如:long age=12l
  • Java程序中变量通常声明为int型,除非不足以表示较大的数,才使用long。
  • Java的整型常量默认为 int 型

4.1.1 补充:计算机存储单位

  • 字节(Byte):是计算机用于计量存储容量基本单位,一个字节等于8 bit。

  • 位(bit):是数据存储的最小单位。二进制数系统中,每个0或1就是一个位,叫做bit(比特),其中8 bit 就称为一个字节(Byte)。

  • 转换关系:

    • 8 bit = 1 Byte
    • 1024 Byte = 1 KB
    • 1024 KB = 1 MB
    • 1024 MB = 1 GB
    • 1024 GB = 1 TB

4.2 浮点类型:float、double(默认)

  • 与整数类型类似,Java 浮点类型也有固定的表数范围和字段长度,不受具体操作系统的影响。
image-20220311001749699
  • 浮点型常量有两种表示形式:
    • 十进制数形式。如:5.12 512.0f .512 (必须有小数点)
    • 科学计数法形式。如:5.12e2 512E2 100E-2
  • float:单精度,尾数可以精确到7位有效数字。很多情况下,精度很难满足需求。
  • double(通常采用此类型):双精度,精度是float的两倍。
  • 定义float类型的变量,赋值时需要以”f“或”F“作为后缀。 – 例如:float age=12f
  • Java 的浮点型常量默认为double型

4.2.1 关于浮点型精度的说明

  • 并不是所有的小数都能可以精确的用二进制浮点数表示。二进制浮点数不能精确的表示0.1、0.01、0.001这样10的负次幂。

  • 浮点类型float、double的数据不适合在不容许舍入误差的金融计算领域。如果需要精确数字计算或保留指定位数的精度,需要使用BigDecimal类。(算法题经常出现)

  • 测试用例:

1
2
3
4
5
6
7
8
9
//测试1:(解释见章末企业真题:为什么0.1 + 0.2不等于0.3)
System.out.println(0.1 + 0.2);//0.30000000000000004

//测试2:
float ff1 = 123123123f;
float ff2 = ff1 + 1;
System.out.println(ff1);
System.out.println(ff2);
System.out.println(ff1 == ff2); #输出是True 因为精度太差所以竟然是相等的!!!!!

4.2.2 应用举例

案例1:定义圆周率并赋值为3.14,现有3个圆的半径分别为1.2、2.5、6,求它们的面积。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/**
* @author 尚硅谷-宋红康
* @create 12:36
*/
public class Exercise1 {
public static void main(String[] args) {
double PI = 3.14; //圆周率

double radius1 = 1.2;
double radius2 = 2.5;
int radius3 = 6;

System.out.println("第1个圆的面积:" + PI * radius1 * radius1);
System.out.println("第2个圆的面积:" + PI * radius2 * radius2);
System.out.println("第3个圆的面积:" + PI * radius3 * radius3);
}
}

案例2:小明要到美国旅游,可是那里的温度是以华氏度为单位记录的。
它需要一个程序将华氏温度(80度)转换为摄氏度,并以华氏度和摄氏度为单位分别显示该温度。

1
℃ = (℉ - 32) / 1.8
1
2
3
4
5
6
7
8
9
10
11
/**
* @author 尚硅谷-宋红康
* @create 12:51
*/
public class Exercise2 {
public static void main(String[] args) {
double hua = 80;
double she = (hua-32)/1.8;
System.out.println("华氏度" + hua+"℉转为摄氏度是" +she+"℃");
}
}

4.3 字符类型:char

  • char 型数据用来表示通常意义上“字符”(占2字节)

  • Java中的所有字符都使用Unicode编码,故一个字符可以存储一个字母,一个汉字,或其他书面语的一个字符。

  • 字符型变量的三种表现形式:

    • 形式1:使用单引号(‘ ‘)括起来的单个字符

      例如:char c1 = ‘a’; char c2 = ‘中’; char c3 = ‘9’;

    • 形式2:直接使用 Unicode值来表示字符型常量:‘\uXXXX’。其中,XXXX代表一个十六进制整数。

      例如:\u0023 表示 ‘#’。

    • 形式3:Java中还允许使用转义字符‘\’来将其后的字符转变为特殊字符型常量。

      例如:char c3 = ‘\n’; // ‘\n’表示换行符

    转义字符 说明 Unicode表示方式
    \n 换行符 \u000a
    \t 制表符 \u0009
    \" 双引号 \u0022
    \' 单引号 \u0027
    \\ 反斜线 \u005c
    \b 退格符 \u0008
    \r 回车符 \u000d
  • char类型是可以进行运算的。因为它都对应有Unicode码,可以看做是一个数值。

  • 举例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class IdentifierTest {
public static void main(String[] args){
//形式1 使用单引号(' ')括起来的单个字符
char bl='a';
System.out.println(bl); // a
char zg='中';
System.out.println(zg); // 中

//形式2 直接使用 `Unicode值`来表示字符型常量:
char qiguai='\u0036';
System.out.println(qiguai); // 6


//形式3 使用`转义字符‘\’` 来将其后的字符转变为特殊字符型常量。
char c9='\n';
char c10='\t';
// hello
// world
System.out.println("hello"+c9+"world");
// hello world
System.out.println("hello"+c10+"world");
}
}

4.4 布尔类型:boolean

  • boolean 类型用来判断逻辑条件,一般用于流程控制语句中:

    • if条件控制语句;
    • while循环控制语句;
    • for循环控制语句;
    • do-while循环控制语句;
  • boolean类型数据只有两个值:true、false,无其它。

    • 不可以使用0或非 0 的整数替代false和true,这点和C语言不同。
    • 拓展:Java虚拟机中没有任何供boolean值专用的字节码指令,Java语言表达所操作的boolean值,在编译之后都使用java虚拟机中的int数据类型来代替:true用1表示,false用0表示。——《java虚拟机规范 8版》
  • 举例:

1
2
3
4
5
6
7
boolean isFlag = true;

if(isFlag){
//true分支
}else{
//false分支
}

经验之谈:

Less is More!建议不要这样写:if ( isFlag = = true ),只有新手才如此。关键也很容易写错成if(isFlag = true),这样就变成赋值isFlag为true而不是判断!老鸟的写法是if (isFlag)或者if ( !isFlag)。

5. 基本数据类型变量间运算规则

在Java程序中,不同的基本数据类型(只有7种,不包含boolean类型)变量的值经常需要进行相互转换。

转换的方式有两种:①自动类型提升和②强制类型转换

5.1 自动类型提升

规则:将取值范围小(或容量小)的类型自动提升为取值范围大(或容量大)的类型 。

image-20220523162200891

基本数据类型的转换规则如图所示:

image-20220311002543688

(1)当把存储范围小的值(常量值、变量的值、表达式计算的结果值)赋值给了存储范围大的变量时

1
2
3
4
5
6
int i = 'A';//char自动升级为int,其实就是把字符的编码值赋值给i变量了
double d = 10;//int自动升级为double
long num = 1234567; //右边的整数常量值如果在int范围呢,编译和运行都可以通过,这里涉及到数据类型转换

//byte bigB = 130;//错误,右边的整数常量值超过byte范围
long bigNum = 12345678912L;//右边的整数常量值如果超过int范围,必须加L,显式表示long类型。否则编译不通过

(2)当存储范围小的数据类型与存储范围大的数据类型变量一起混合运算时,会按照其中最大的类型运算(自动转换为最大类型)。

1
2
3
4
5
int i = 1;
byte b = 1;
double d = 1.0;

double sum = i + b + d;//混合运算,升级为double 输出为3.0

(3)当byte,short,char(三个在int之前的不分前后的类型)数据类型的变量进行算术运算时,按照int类型处理。

1
2
3
4
5
6
7
8
byte b1 = 1;
byte b2 = 2;
byte b3 = b1 + b2;// byte编译报错,b1 + b2自动升级为int,所以是需要int来接结果!!!!!

char c1 = '0'; // int值为48
char c2 = 'A'; // int值为65
int i = c1 + c2; //至少需要使用int类型来接收
System.out.println(c1 + c2); //48+65=113

练习:

1
2
3
4
5
6
7
8
9
10
11
12
设 x 为float型变量,y为double型变量,a为int型变量,b为long型变量,c为char型变量,则表达式
x + y * a / x + b / y + c的值类型为:
代码如下:
float x=1.0f;
double y=2.0;
int a=1;
long b=3l;
char c='a'; // int值为97
double sum = x + y * a / x + b / y + c;
System.out.println(x+y*a/x+b/y+c); //101.5

A. int B. long C. double D. char

5.2 强制类型转换

3.14 赋值到int 类型变量会发生什么?产生编译失败,肯定无法赋值。

1
int i = 3.14; // 编译报错

想要赋值成功,只有通过强制类型转换,将double 类型强制转换成int 类型才能赋值。

image-20220523162407722

规则:将取值范围大(或容量大)的类型强制转换成取值范围小(或容量小)的类型。

自动类型提升是Java自动执行的,而强制类型转换是自动类型提升的逆运算,需要我们自己手动执行。

转换格式:

1
数据类型1 变量名 = (数据类型1)被强转数据值;  //()中的数据类型必须<=变量值的数据类型

(1)当把存储范围大的值(常量值、变量的值、表达式计算的结果值)强制转换为存储范围小的变量时,可能会损失精度溢出

1
2
3
4
5
6
7
int i = (int)3.14;//损失精度  输出为3 截断了后面的0.14

double d = 1.2;
int num = (int)d;//损失精度 输出为1 截断了后面的0.2

int i = 200;
byte b = (byte)i;//溢出 输出负56 因为原码补码问题导致

(2)当某个值想要提升数据类型时(本身可以通过自动类型提升完成),也可以使用强制类型转换。这种情况的强制类型转换是没有风险的,通常省略。

1
2
3
4
int i = 1;
int j = 2;
double bigger = (double)(i/j); // 0.0
int bigger2=i/j; // 0 可能想要一个double类型 所以使用强制类型转换才可以做到成为0.0

(3)声明变量时,long(可以√省略),float(不可以×省略)。

1
2
3
4
5
6
7
8
9
10
11
12
13
//记忆方法:  可以看作long定义的时候可能是int(整数型默认)自动类型转换为long   所以可以省略
// 可以看做float定义的时候可能是double(浮点数默认)没办法自动类型转换为double 所有不可以省略

long l1 = 123L;
long l2 = 123;//如何理解呢? 此时可以看做是int类型的123自动类型提升为long类型

//long l3 = 123123123123; //报错,因为123123123123超出了int的范围。
long l4 = 123123123123L;


//float f1 = 12.3; //报错,因为12.3看做是double,不能自动转换为float类型
float f2 = 12.3F;
float f3 = (float)12.3;

练习:判断是否能通过编译

1
2
3
4
5
6
7
8
9
10
11
12
13
1short  s = 5;
s = s-2; //判断:no 因为short-int的结果是一个int(往上自动类型转换) 所以需要强制类型转换
2byte b = 3;
b = b + 4; //判断:no 因为byte+int的结果是一个int(往上自动类型转换) 所以需要强制类型转换
b = (byte)(b+4); //判断:yes 要强制类型转换
3char c = ‘a’;
int i = 5;
float d = .314F;
double result = c+i+d; //判断:yes 因为是char+int+float 结果是一个float 可以自动类型转换为double
4byte b = 5;
short s = 3;
short t = s + b; //判断:no 因为当byte,short数据类型进行算术运算时,按照int类型
//可以改成short t = (short) (s + b);

*问答:为什么标识符的声明规则里要求不能数字开头? * — 是本身的数值 / 他是一个变量所对应的值

1
2
3
4
//如果允许数字开头,则如下的声明编译就可以通过:
int 123L = 12;
//进而,如下的声明中l的值到底是123?还是变量123L对应的取值12呢? 出现歧义了。
long l = 123L; // 123还是12

5.3 基本数据类型与String的运算

5.3.1 字符串类型:String

  • String不是基本数据类型,属于引用数据类型 【所以不能够进行基本数据类型的自动类型转换和强制类型转换】
  • 使用一对""来表示一个字符串,内部可以包含0-n个字符
  • 声明方式与基本数据类型类似。例如:String str = “尚硅谷”;

5.3.2 运算规则

1、任意八种基本数据类型的数据与String类型只能进行连接“+”运算,且结果一定也是String类型

1
2
3
4
5
6
7
8
9
10
11
System.out.println("   " + 1 + 2); //空格空格空格12  

int num = 10;
boolean b1 = true;
String s1 = "abc";

String s2 = s1 + num + b1;
System.out.println(s2); //abc10true

//String s3 = num + b1 + s1; //编译不通过,因为int类型不能与boolean运算
String s4 = num + (b1 + s1); //编译通过 10trueabc [b1+s1先成为字符串tureabc,然后int+string成为string]

2、String类型不能通过强制类型()转换,转为其他的类型

1
2
3
4
String str = "123";
int num = (int)str;//错误的

int num = Integer.parseInt(str);//借助包装类的方法才能转

5.3.3 案例与练习

案例:公安局身份登记

要求填写自己的姓名、年龄、性别、体重、婚姻状况(已婚用true表示,单身用false表示)、联系方式等等。

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
/**
* @author 尚硅谷-宋红康
* @create 12:34
*/
public class Info {
public static void main(String[] args) {
String name = "康师傅";
int age = 37;
char gender = '男';
double weight = 145.6;
boolean isMarried = true;
String phoneNumber = "13112341234";

System.out.println("姓名:" + name);
System.out.println("年龄:" + age);
System.out.println("性别:" + gender);
System.out.println("体重:" + weight);
System.out.println("婚否:" + isMarried);
System.out.println("电话:" + phoneNumber);
//或者
System.out.println("name = " + name + ",age = " + age + ",gender = " +
gender + ",weight = " + weight + ",isMarried = " + isMarried +
",phoneNumber = " + phoneNumber);
}
}

练习:

练习1:

1
2
3
4
5
6
7
String str1 = 4;                       //判断对错: × 可以改为String str1=String.valueOf(4);
String str2 = 3.5f + ""; //判断str2对错:对 float+string=string类型
System.out.println(str2); //输出:3.5
System.out .println(3+4+"Hello!"); //输出:7Hello! 注意从左往右先算最后和string变为字符串
System.out.println("Hello!"+3+4); //输出:Hello!34
System.out.println('a'+1+"Hello!"); //输出:98Hello! 注意从左往右先算最后和string变为字符串
System.out.println("Hello"+'a'+1); //输出:Helloa1

练习2:

1
2
3
4
5
6
7
8
System.out.println("*    *");				//输出: *    *
System.out.println("*\t*"); //输出: * *
System.out.println("*" + "\t" + "*"); //输出: * *
System.out.println('*' + "\t" + "*"); //输出: * *
System.out.println('*' + '\t' + "*"); //输出: 51* '*'是char类型在进行算数运算时转为int类型计算!!!
System.out.println('*' + "\t" + '*'); //输出: * *
System.out.println("*" + '\t' + '*'); //输出: * *
System.out.println('*' + '\t' + '*'); //输出: 93 '*'是char类型在进行算数运算时转为int类型计算!!!

6. 计算机底层如何存储数据

计算机世界中只有二进制,所以计算机中存储和运算的所有数据都要转为二进制。包括数字、字符、图片、声音、视频等。

010101

世界上有10种人 ,认识和不认识二进制的。

6.1 进制的分类

  • 十进制(decimal)

    • 数字组成:0-9
    • 进位规则:满十进一
  • 二进制(binary)

    • 数字组成:0-1
    • 进位规则:满二进一,以0b0B开头
  • 八进制(octal):很少使用

    • 数字组成:0-7
    • 进位规则:满八进一,以数字0开头表示
  • 十六进制

    • 数字组成:0-9,a-f
    • 进位规则:满十六进一,以0x0X开头表示。此处的 a-f 不区分大小写

代码演示:

1
2
3
4
5
6
7
8
9
class BinaryTest {
public static void main(String[] args) {
int n1=123; //十进制 1*10^2+2*10^1+3*10^0=100+20+3=
int n2=0b11; //二进制 1*2^1+1*2^0=2+1=3
int n3=071; //八进制 7*8^1+1*8^0=56+1=57
int n4=0x12f;//十六进制 1*16^2+2*16^1+15*16^0=256+32+15=303
System.out.println("n1:"+n1+"\nn2:"+n2+"\nn3:"+n3+"\nn4:"+n4);
}
}

6.2 进制的换算举例

十进制 二进制 八进制 十六进制
0 0 0 0
1 1 1 1
2 10 2 2
3 11 3 3
4 100 4 4
5 101 5 5
6 110 6 6
7 111 7 7
8 1000 10 8
9 1001 11 9
10 1010 12 a或A
11 1011 13 b或B
12 1100 14 c或C
13 1101 15 d或D
14 1110 16 e或E
15 1111 17 f或F
16 10000 20 10

6.3 二进制的由来

二进制,是计算技术中广泛采用的一种数制,由德国数理哲学大师莱布尼茨于1679年发明。

二进制数据是用0和1两个数码来表示的数。它的基数为2,进位规则是“逢二进一”。

二进制广泛应用于我们生活的方方面面。比如,广泛使用的摩尔斯电码(Morse Code),它由两种基本信号组成:短促的点信号“·”,读“”;保持一定时间的长信号“”,读“”。然后,组成了26个字母,从而拼写出相应的单词。

image-20220520105721126

记忆技巧:

morsecode

我们偶尔会看到的:SOS,即为:

image-20220520110206899

6.4 二进制转十进制

二进制如何表示整数?

  • 计算机数据的存储使用二进制补码形式存储,并且最高位是符号位
    • 正数:最高位是0
    • 负数:最高位是1
  • 规 定
    • 正数:原码=反码=补码
    • 负数:
      • 负数的原码:把十进制转为二进制,然后最高位设置为1
      • 负数的反码:在原码的基础上,最高位不变,其余位取反(0变1,1变0)
      • 负数的补码:反码+1

二进制转十进制:权相加法

针对于byte数据举例来说:

image-20220523114701881

  • 例如:byte类型(1个字节,8位)

    25 ==> 原码 0001 1001 ==> 反码 0001 1001 –>补码 0001 1001

    -25 ==>原码 1001 1001 ==> 反码1110 0110 ==>补码 1110 0111

1
2
3
4
5
6
7
8
整数:
正数:25 00000000 00000000 000000000 00011001(原码)
正数:25 00000000 00000000 000000000 00011001(反码)
正数:25 00000000 00000000 000000000 00011001(补码)

负数:-25 10000000 00000000 000000000 00011001(原码)
负数:-25 11111111 11111111 111111111 11100110(反码)
负数:-25 11111111 11111111 111111111 11100111(补码)

一个字节可以存储的整数范围是多少?

1
2
3
4
5
6
7
8
9
//1个字节:8位

0000 0001 ~ 0111 111 ==> 1~127

1000 0001 ~ 1111 1111 ==> -127 ~ -1

0000 0000 ==>0

1000 0000 ==> -128(特殊规定)=-127-1

6.5 十进制转二进制

十进制转二进制:除2取余的逆

image-20220312000042595

6.6 二进制与八进制、十六进制间的转换

二进制转八进制(三合一)

image-20220312000233863

二进制转十六进制(四合一)

image-20220312000251113

八进制、十六进制转二进制(一拆多)

image-20220312000341297

6.7 各进制间的转换

image-20220312000143438

练习:

1
2
3
4
5
6
7
8
9
1.将以下十进制数转换为十六进制和二进制
十进制: 123 256 87 62
十六进制: 7b 100 57 3e 从走往左四合一,不够补0
二进制: 1111011 100000000 1010111 111110 除2取余反向读

2.将以下十六进制数转换为十进制和二进制
十六进制: 0x123 0x25F 0x38 0x62
十进制: 291 607 56 98 拆开按照16^算
二进制: 100100011 1001011111 111000 1100010 一拆四 到时候最前面的0记得删除!!!

7. 运算符(Operator)(掌握)

运算符是一种特殊的符号,用以表示数据的运算、赋值和比较等。

运算符的分类:

  • 按照功能分为:算术运算符、赋值运算符、比较(或关系)运算符、逻辑运算符、位运算符、条件运算符、Lambda运算符
分类 运算符
算术运算符(7个) +、-、*、/、%、++、–
赋值运算符(12个) =、+=、-=、*=、/=、%=、>>=、<<=、>>>=、&=、|=、^=等
比较(或关系)运算符(6个) >、>=、<、<=、==、!=
逻辑运算符(6个) &、|、^、!、&&、||
位运算符(7个) &、|、^、~、<<、>>、>>>
条件运算符(1个) (条件表达式)?结果1:结果2
Lambda运算符(1个) ->(第18章时讲解)
  • 按照操作数个数分为:一元运算符(单目运算符)、二元运算符(双目运算符)、三元运算符 (三目运算符)
分类 运算符
一元运算符(单目运算符) 正号(+)、负号(-)、++、–、!、~
二元运算符(双目运算符) 除了一元和三元运算符剩下的都是二元运算符
三元运算符 (三目运算符) (条件表达式)?结果1:结果2

7.1 算术运算符

7.1.1 基本语法

image-20220312000848332

举例1:加减乘除模

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class ArithmeticTest1 {
public static void main(String[] args) {
int a = 3;
int b = 4;

System.out.println(a + b);// 3+4=7
System.out.println(a - b);// 3-4=-1
System.out.println(a * b);// 3*4=12
System.out.println(a / b);// 3/4=0
System.out.println(a % b);// 3%4=3 取余为3

//重点:结果与被模数符号相同
System.out.println(5%2);//1 被模数为5
System.out.println(5%-2);//1 被模数为5
System.out.println(-5%2);//-1 被模数为-5
System.out.println(-5%-2);//-1 被模数为-5
//商*除数 + 余数 = 被除数
//5%-2 ==>商是-2,余数时1 (-2)*(-2)+1 = 5
//-5%2 ==>商是-2,余数是-1 (-2)*2+(-1) = -4-1=-5
}
}

举例2:“+”号的两种用法

  • 第一种:对于+两边都是数值的话,+就是加法的意思 —– 数值+数值=数值
  • 第二种:对于+两边至少有一边是字符串的话,+就是拼接的意思 —– 字符串+数值=字符串
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class ArithmeticTest2 {
public static void main(String[] args) {
// 字符串类型的变量基本使用
// 数据类型 变量名称 = 数据值;
String str1 = "Hello";
System.out.println(str1); // Hello

System.out.println("Hello" + "World"); // HelloWorld

String str2 = "Java";
// String + int --> String
System.out.println(str2 + 520); // Java520
// String + int + int
// String + int
// String
System.out.println(str2 + 5 + 20); // Java520
}
}

举例3:自加自减运算

理解:++ 运算,表示自增1。同理,-- 运算,表示自减1,用法与++ 一致。

1、单独使用

  • 变量在单独运算的时候,变量前++和变量后++,是没有区别的。
  • 变量前++ :例如 ++a
  • 变量后++ :例如 a++
1
2
3
4
5
6
7
8
9
10
public class ArithmeticTest3 {
public static void main(String[] args) {
// 定义一个int类型的变量a
int a = 3;
//++a;
a++;
// 无论是变量前++还是变量后++,结果都是4
System.out.println(a);
}
}

2、复合使用

  • 其他变量放在一起使用或者和输出语句放在一起使用前++后++就产生了不同。
  • 变量前++ :变量先自增1,然后再运算。
  • 变量后++ :变量先运算,然后再自增1。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class ArithmeticTest4 {
public static void main(String[] args) {
// 其他变量放在一起使用
int x = 3;
//int y = ++x; // y的值是4,x的值是4,
int y = x++; // y的值是3,x的值是4

System.out.println(x);
System.out.println(y);
System.out.println("==========");

// 和输出语句一起
int z = 5;
//System.out.println(++z);// 输出结果是6,z的值也是6
System.out.println(z++);// 输出结果是5,z的值是6
System.out.println(z);

}
}

7.1.2 案例与练习

案例1:

1
2
3
4
5
6
7
8
9
10
11
12
随意给出一个整数,打印显示它的个位数,十位数,百位数的值。
格式如下:
数字xxx的情况如下:
个位数:
十位数:
百位数:

例如:
数字153的情况如下:
个位数:3
十位数:5
百位数:1
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/**
* @author 尚硅谷-宋红康
* @create 12:20
*/
class ArithmeticExer1 {
public static void main(String[] args) {
//随意给出一个整数,打印显示它的个位数,十位数,百位数的值。
// 主打一个缩小范围然后取余确定最后一位
int n=1234;
int qian=n/1000;
int bai=n/100%10; // 提示:n/100=12
int shi=n/10%10; //提示:n/100=123
int ge=n%10;
System.out.println("个位数:"+ge);
System.out.println("十位数:"+shi);
System.out.println("百位数:"+bai);
System.out.println("千位数:"+qian);

}
}

拓展:获取一个四位数的个位,十位,百位,千位

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/**
* @author 尚硅谷-宋红康
* @create 12:39
*/
public class ArithmeticExer01 {
public static void main (String [] args) {
// 获取一个四位数的个位,十位,百位,千位
// 主打一个缩小范围然后取余确定最后一位
int n=1234;
int qian=n/1000;
int bai=n/100%10; // 提示:n/100=12
int shi=n/10%10; //提示:n/100=123
int ge=n%10;
System.out.println("个位数:"+ge);
System.out.println("十位数:"+shi);
System.out.println("百位数:"+bai);
System.out.println("千位数:"+qian);

}
}

案例2:为抵抗洪水,战士连续作战89小时,编程计算共多少天零多少小时?

1
2
3
4
5
6
7
8
9
10
11
12
13
/**
* @author 尚硅谷-宋红康
* @create 17:47
*/
public class ArithmeticExer2 {
public static void main(String[] args){
//为抵抗洪水,战士连续作战89小时,编程计算共多少天零多少小时?
int sum=89;
int day=89/24;
int hour=89%24;
System.out.println("一共"+day+"天"+hour+"小时");
}
}

练习1:算术运算符:自加、自减

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class ArithmeticExer3{
public static void main(String[] args){
int i1 = 10;
int i2 = 20;
int i = i1++; // 此时 i=10 i1=11
System.out.print("i="+i); // i=10
System.out.println("i1="+i1);// i1=11
i = ++i1; // 此时i1=12 i=12
System.out.print("i="+i);// i=12
System.out.println("i1="+i1);// i1=12
i = i2--; // 此时i2=19 i=20
System.out.print("i="+i);// i=20
System.out.println("i2="+i2);// i2=19
i = --i2; // 此时i2=18 i=18
System.out.print("i="+i);// i=18
System.out.println("i2="+i2);// i2=18
}
}

练习2:

1
System.out.println("5+5=" + 5 + 5); //打印结果是? 5+5=55   因为string+int+int => string+int => string

练习3:

1
2
3
byte bb1 = 127;
bb1++;
System.out.println("bb1 = " + bb1);//-128

练习4:

1
2
3
int i = 1;
int j = i++ + ++i * i++; //从左往右 1+3*3=10 最后i等于4
System.out.println("j = " + j);

练习5:(企业真题)写出下列程序的输出结果

1
2
3
4
5
6
7
int i = 2;
int j = i++;
System.out.println(j);

int m = 2;
m = m++; //(1)先取b的值“2”放操作数栈 (2)m再自增,m=3 (3)再把操作数栈中的"2"赋值给m,m=2
System.out.println(m); // 是2!!!!

7.2 赋值运算符

7.2.1 基本语法

  • 符号:=

    • 当“=”两侧数据类型不一致时,可以使用自动类型转换或使用强制类型转换原则进行处理。
    • 支持连续赋值(先定义好之后才能连续赋值)。
  • 扩展赋值运算符: +=、 -=、*=、 /=、%=

    赋值运算符 符号解释
    += 将符号左边的值右边的值进行相加操作,最后将结果赋值给左边的变量
    -= 将符号左边的值右边的值进行相减操作,最后将结果赋值给左边的变量
    *= 将符号左边的值右边的值进行相乘操作,最后将结果赋值给左边的变量
    /= 将符号左边的值右边的值进行相除操作,最后将结果赋值给左边的变量
    %= 将符号左边的值右边的值进行取余操作,最后将结果赋值给左边的变量
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
56
public class SetValueTest1 {
public static void main(String[] args) {
int i1 = 10;
long l1 = i1; //自动类型转换

byte bb1 = (byte)i1; //强制类型转换

int i2 = i1;

//连续赋值的测试
//以前的写法
int a1 = 10;
int b1 = 10;

//连续赋值的写法
int a2,b2;
a2 = b2 = 10;

int a3 = 10,b3 = 20;

//举例说明+= -= *= /= %=
int m1 = 10;
m1 += 5; //类似于 m1 = m1 + 5的操作,但不等同于。
System.out.println(m1);//15

//练习1:开发中,如何实现一个变量+2的操作呢?
// += 的操作不会改变变量本身的数据类型。其他拓展的运算符也如此!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
//写法1:推荐
short s1 = 10;
s1 += 2; //编译通过,因为在得到int类型的结果后,JVM自动完成一步强制类型转换,将int类型强转成short
System.out.println(s1);//12
//写法2:
short s2 = 10;
//s2 = s2 + 2;//编译报错,因为将int类型的结果赋值给short类型的变量s时,可能损失精度
s2 = (short)(s2 + 2);
System.out.println(s2);


//练习2:开发中,如何实现一个变量+1的操作呢?
//写法1:推荐
int num1 = 10;
num1++;
System.out.println(num1);

//写法2:
int num2 = 10;
num2 += 1;
System.out.println(num2);

//写法3:
int num3 = 10;
num3 = num3 + 1;
System.out.println(num3);

}
}

7.2.2 练习

练习1:

1
2
3
short s = 3; 
s = s+2; //① 编译报错 s是一个short short+int=int不能直接给short 需要强制类型转换 s=(short)(s+2);
s += 2; //② 正常执行 short+int=int 自动完成了强制类型转换(+=不改变值的数据类型)

练习2:

1
2
3
4
5
int i = 1;
i *= 0.1;
System.out.println(i);// 因为*=不改变值的数据类型 0.1就截断为0 [脑子短路]
i++;
System.out.println(i);//1

练习3:

1
2
3
4
5
int m = 2;
int n = 3;
n *= m++; // 可以看作是 n = n * m++; n=3*2=6 m=3
System.out.println("m=" + m);//3
System.out.println("n=" + n);//6

练习4:

1
2
3
int n = 10;
n += (n++) + (++n); // 可以看作是 n = n + (n++) + (++n)=10+10+12=32
System.out.println(n);//32

练习5:你有几种办法实现变量值减1?变量值减2呢?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/**
* @author 尚硅谷-宋红康
* @create 16:55
*/
public class MinusTest {
public static void main(String[] args) {
//练习①:变量值减1
short s = 10;
//方式1:
//s = (short)(s - 1);
//方式2:推荐
s--; //或者 --s
//方式3:
s -= 1;

//练习②:变量值减2
short s1 = 10;
//方式1:
//s1 = (short)(s1 - 2);
//方式2:推荐
s1 -= 2;
}
}

7.3 比较(关系)运算符

image-20220312001742263

  • 比较运算符的结果都是boolean型,结果只有true/false。

  • > < >= <= :只适用于基本数据类型(除boolean类型之外)

    == != :适用于基本数据类型和引用数据类型

  • 比较运算符“==”不能误写成“=

举例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class CompareTest {
public static void main(String[] args) {
int i1 = 10;
int i2 = 20;

System.out.println(i1 == i2);//false
System.out.println(i1 != i2);//true
System.out.println(i1 >= i2);//false


int m = 10;
int n = 20;
System.out.println(m == n);//false
System.out.println(m = n);//20

boolean b1 = false;
boolean b2 = true;
System.out.println(b1 == b2);//false
System.out.println(b1 = b2);//true
}
}

思考:

1
2
3
4
5
6
boolean b1 = false;
//区分好==和=的区别。
if(b1 == true) //if(b1 = true)
System.out.println("结果为真");
else
System.out.println("结果为假");

7.4 逻辑运算符

7.4.1 基本语法

image-20220312001943403
  • 逻辑运算符,boolean 逻辑运算符 boolean =boolean 。

  • 运算符说明:

    • & 和 &&:表示”且”关系,当符号左右两边布尔值都是true时,结果才能为true。否则,为false。
    • | 和 || :表示”或”关系,当符号两边布尔值有一边为true时,结果为true。当两边都为false时,结果为false。
    • ! :表示”非”关系,当变量布尔值为true时,结果为false。当变量布尔值为false时,结果为true。
    • ^ :当符号左右两边布尔值不同时,结果为true。当两边布尔值相同时,结果为false。
      • 理解:异或,追求的是“异”!
  • 逻辑运算符用于连接布尔型表达式,在Java中不可以写成 3 < x < 6,应该写成x > 3 & x < 6 。

  • 区分“&”和“&&”:

    • 相同点:如果符号左边是true,则二者都执行符号右边的操作

    • 不同点:& : 如果符号左边是false,则继续执行符号右边的操作

    ​ && :如果符号左边是false,则不再继续执行符号右边的操作(有一个不符合可以跳过)

    • 建议:开发中,推荐使用 &&
  • 区分“|”和“||”:

- 相同点:如果符号左边是false,则二者都执行符号右边的操作



- 不同点:| : 如果符号左边是true,则继续执行符号右边的操作

  ​               || :如果符号左边是true,则不再继续执行符号右边的操作(有一个符合可以跳过)



- 建议:开发中,推荐使用 ||

代码举例:

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
public class LoginTest {
public static void main(String[] args) {
int a = 3;
int b = 4;
int c = 5;

// & 与,且;有false则false
System.out.println((a > b) & (a > c));
System.out.println((a > b) & (a < c));
System.out.println((a < b) & (a > c));
System.out.println((a < b) & (a < c));
System.out.println("===============");
// | 或;有true则true
System.out.println((a > b) | (a > c));
System.out.println((a > b) | (a < c));
System.out.println((a < b) | (a > c));
System.out.println((a < b) | (a < c));
System.out.println("===============");
// ^ 异或;相同为false,不同为true 追求异!!!
System.out.println((a > b) ^ (a > c));
System.out.println((a > b) ^ (a < c));
System.out.println((a < b) ^ (a > c));
System.out.println((a < b) ^ (a < c));
System.out.println("===============");
// ! 非;非false则true,非true则false
System.out.println(!false);
System.out.println(!true);

//&和&&的区别
System.out.println((a > b) & (a++ > c));
System.out.println("a = " + a);
System.out.println((a > b) && (a++ > c));
System.out.println("a = " + a);
System.out.println((a == b) && (a++ > c));
System.out.println("a = " + a);

//|和||的区别
System.out.println((a > b) | (a++ > c));
System.out.println("a = " + a);
System.out.println((a > b) || (a++ > c));
System.out.println("a = " + a);
System.out.println((a == b) || (a++ > c));
System.out.println("a = " + a);
}
}

7.4.2 案例与练习

案例:

1
2
3
4
5
6
7
1. 定义类 CompareLogicExer
2. 定义 main方法
3. 定义一个int类型变量a,变量b,都赋值为20
4. 定义boolean类型变量bo1 , 判断++a 是否被3整除,并且a++ 是否被7整除,将结果赋值给bo1
5. 输出a的值,bo1的值
6. 定义boolean类型变量bo2 , 判断b++ 是否被3整除,并且++b 是否被7整除,将结果赋值给bo2
7. 输出b的值,bo2的值
1
2
3
4
5
6
7
8
9
10
11
12
public class CompareLogicExer {
public static void main(String[] args) {
int a=20;
int b=20;
boolean b01=(++a%3==0)&&(a++%7==0);
System.out.println(a); // a=22 先是++a之后成为21 最后a++成为22
System.out.println(b01); // true
boolean b02=(b++%3==0)&&(++b%7==0); //一定要记得&&是可以跳出来循环的!
System.out.println(b); // b=21 先是b++之后成为21 直接&&不成立直接跳出来了!!!!
System.out.println(b02); // false
}
}

练习1:区分 & 和 && (&&被推荐使用,因为有一个不符合就跳出判断了)

1
2
3
4
5
6
7
int x = 1;
int y = 1;

if(x++ == 2 & ++y == 2){ // 1==2 不成立但是x=2 然后&不能跳过循环继续y=2 2==2成立 但是最终是false&true=false
x = 7;
}
System.out.println("x=" + x + ",y=" + y); // x=2 y=2
1
2
3
4
5
6
int x = 1,y = 1;

if(x++ == 2 && ++y == 2){ // 1==2不成立 x=2 因为是&&直接跳出循环
x =7;
}
System.out.println("x="+x+",y="+y); // x=2 y=1

练习2:区分 | 和 ||(||被推荐使用,因为有一个符合就跳出判断了)

1
2
3
4
5
6
int x = 1,y = 1;

if(x++==1 | ++y==1){ // 1==1 x=2 左边成立 y=2 2==1不成立 true|false=true
x =7;
}
System.out.println("x="+x+",y="+y); // x=7 y=2
1
2
3
4
5
6
int x = 1,y = 1;

if(x++==1 || ++y==1){ // 1==1 左边成立 x=2 直接成功
x =7;
}
System.out.println("x="+x+",y="+y); // x=7 y=1

练习3:程序输出

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class  Test  {
public static void main (String [] args) {
boolean x = true;
boolean y = false;
short z = 42;

if ((z++ == 42) && (y = true)) { // 42==42 左边成立 z=43 y=true
z++; // z=44
}
if ((x = false) || (++z == 45)) { // x=false 左边成立 z=45 45==45 不成立
z++; // z=46
}

System.out.println("z=" + z); // z=46
}
}
1
2
//结果为:
//z= 46

7.5 位运算符(难点、非重点)

7.5.1 基本语法

image-20220313174721111 image-20220312002506339
  • 位运算符的运算过程都是基于二进制的补码运算

(1)左移:<<

运算规则:在一定范围内,数据每向左移动n位,相当于原数据*2^n。(正数、负数都适用)

【注意】当左移的位数n超过该数据类型的总位数时,相当于左移(n-总位数)位

1
3<<4  类似于  3*2^4 => 3*16 => 48

image-20200225113651675

1
-3<<4  类似于  -3*2^4 => -3*16 => -48

image-20200225114707524

(2)右移:>>

运算规则:在一定范围内,数据每向右移动n位,相当于原数据/(2^n)。(正数、负数都适用)

【注意】如果不能整除,向下取整

1
69>>4  类似于  69/(2^4) = 69/16 =4

image-20200225115636844

1
-69>>4  类似于  -69/(2^4) = -69/16 = -5

image-20200225120112188

(3)无符号右移:>>>

运算规则:往右移动后,左边空出来的位直接补0。(正数、负数都适用)

1
69>>>4  类似于  69/2的4次 = 69/16 =4

image-20200225121104734

1
-69>>>4   结果:268435451

image-20200225121244290

(4)按位与:&

运算规则:对应位都是1才为1,否则为0

  • 1 & 1 结果为1

  • 1 & 0 结果为0

  • 0 & 1 结果为0

  • 0 & 0 结果为0
1
9 & 7 = 1

image-20200225122440953

1
-9 & 7 = 7

image-20200225122221616

(5)按位或:|

运算规则:对应位只要有1即为1,否则为0

  • 1 | 1 结果为1

  • 1 | 0 结果为1

  • 0 | 1 结果为1

  • 0 & 0 结果为0

1
9 | 7  //结果: 15

image-20200225122758851

1
-9 | 7 //结果: -9

image-20200225123409130

(6)按位异或:^

运算规则:对应位一个为1一个为0,才为1,否则为0(找异!)

  • 1 ^ 1 结果为0

  • 1 ^ 0 结果为1

  • 0 ^ 1 结果为1

  • 0 ^ 0 结果为0
1
9 ^ 7  //结果为14

image-20200225123445305

1
-9 ^ 7 //结果为-16

image-20200225133145727

(7)按位取反:~

运算规则:对应位为1,则结果为0;对应位为0,则结果为1

  • ~0就是1

  • ~1就是0

1
~9  //结果:-10

image-20200225124112662

1
~-9  //结果:8

image-20200225124156862

7.5.2 举例

举例1:

snipaste_20220312_002549

举例2:体会 m = k ^ n = (m ^ n) ^ n

image-20220312002736157

7.5.3 案例

案例1:高效的方式计算2 * 8的值(经典面试题)

1
答案:2 << 3 、  8  << 1

案例2:如何交换两个int型变量的值?String呢?

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
/**
* @author 尚硅谷-宋红康
* @create 16:58
*/
public class BitExer {
public static void main(String[] args) {
int m = 10;
int n = 5;

System.out.println("m = " + m + ", n = " + n);

//(推荐)实现方式1:优点:容易理解,适用于不同数据类型 缺点:需要额外定义变量
//int temp = m;
//m = n;
//n = temp;

//实现方式2:优点:没有额外定义变量 缺点:可能超出int的范围;只能适用于数值类型
m=m+n; //20+10=30 全上来了m+n
n=m-n; //30-20=10 n=10 全部的值-右边原来的=剩下的就是左边的(新的右边的)
m=m-n; //30-20=20 m=20 全部-新的右边的=新的左边的

//实现方式3:优点:没有额外定义变量 缺点:不易理解;只能适用于数值类型
m = m ^ n;
n = m ^ n; //(m ^ n) ^ n
m = m ^ n;

System.out.println("m = " + m + ", n = " + n);
}
}

7.6 条件运算符

7.6.1 基本语法

  • 条件运算符格式:
1
(条件表达式)? 表达式1:表达式2
  • 说明:条件表达式是boolean类型的结果,根据boolean的值选择表达式1或表达式2

    image-20220312002841945
  • 如果运算后的结果赋给新的变量,要求表达式1和表达式2为同种或兼容的类型

1
2
3
4
5
6
7
8
9
10
11
12
13
public static void main(String[] args) {
int i = (1==2 ? 100 : 200);
System.out.println(i);//200

boolean marry = false;
System.out.println(marry ? "已婚" : "未婚" );

double d1 = (m1 > m2)? 1 : 2.0;
System.out.println(d1);

int num = 12;
System.out.println(num > 0? true : "num非正数");
}

7.6.2 案例

案例1:获取两个数中的较大值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/**
* @author 尚硅谷-宋红康
* @create 12:40
*/
public class ConditionExer1 {
public static void main(String[] args) {
//获取两个数的较大值
int m1 = 10;
int m2 = 20;

int max1 = (m1 > m2)? m1 : m2;
System.out.println("m1和m2中的较大值为" + max1);
}
}

案例2:获取三个数中的最大值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/**
* @author 尚硅谷-宋红康
* @create 12:43
*/
public class ConditionExer2 {
public static void main(String[] args) {
int a=13;
int b=4442;
int c=54;
int secondmax=a>b?a:b; //a和b里面的最大数
int max=secondmax>c?secondmax:c; //和c再比较一下
System.out.println(max);
}
}

案例3:今天是周2,10天以后是周几?

要求:控制台输出”今天是周2,10天以后是周x”。

1
2
3
4
5
6
7
8
9
10
11
12
13
/**
* @author 尚硅谷-宋红康
* @create 12:46
*/
public class ConditionExer3 {

public static void main(String[] args) {
int day=2;
int tomorrow=2+10%7; // 10/7相当于过了几周 现在意思就是10%7就是整周以外过了几天 不用管整7!!
System.out.println(tomorrow);
}

}

7.6.3 与if-else的转换关系

  • 条件运算符——->if-else结构。
  • 开发中,如果既可以使用条件运算符(执行效率高,推荐!),又可以使用if-else。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
//if-else实现获取两个数的较大值

int i1 = 10;
int i2 = 20;

int max;//声明变量max,用于记录i1和i2的较大值

if(i1 > i2){
max = i1;
}else{
max = i2;
}

System.out.println(max);

7.7 运算符优先级

运算符有不同的优先级,所谓优先级就是在表达式运算中的运算符顺序。

上一行中的运算符总是优先于下一行的。

优先级 运算符说明 Java运算符
1 括号 ()[]{}
2 正负号 +-
3 单元运算符 ++--~
4 乘法、除法、求余 */%
5 加法、减法 +-
6 移位运算符 <<>>>>>
7 关系运算符 <<=>=>instanceof
8 等价运算符 ==!=
9 按位与 &
10 按位异或 ^
11 按位或 `
12 条件与 &&
13 条件或 `
14 三元运算符 ? :
15 赋值运算符 =+=-=*=/=%=
16 位赋值运算符 &=、`

开发建议:

  1. 不要过多的依赖运算的优先级来控制表达式的执行顺序,这样可读性太差,尽量使用()来控制表达式的执行顺序。
  2. 不要把一个表达式写得过于复杂,如果一个表达式过于复杂,则把它分成几步来完成。例如:
    ​ (num1 + num2) * 2 > num3 && num2 > num3 ? num3 : num1 + num2;

8. 【拓展】关于字符集

8.1 字符集

  • 编码与解码

计算机中储存的信息都是用二进制数表示的,而我们在屏幕上看到的数字、英文、标点符号、汉字等字符是二进制数转换之后的结果。按照某种规则,将字符存储到计算机中,称为编码 。反之,将存储在计算机中的二进制数按照某种规则解析显示出来,称为解码

  • 字符编码(Character Encoding) : 就是一套自然语言的字符与二进制数之间的对应规则。

  • 字符集:也叫编码表。是一个系统支持的所有字符的集合,包括各国家文字、标点符号、图形符号、数字等。

8.2 ASCII码

  • ASCII码(American Standard Code for Information Interchange,美国信息交换标准代码):上个世纪60年代,美国制定了一套字符编码,对英语字符与二进制位之间的关系,做了统一规定。这被称为ASCII码。
  • ASCII码用于显示现代英语,主要包括控制字符(回车键、退格、换行键等)和可显示字符(英文大小写字符、阿拉伯数字和西文符号)。
  • 基本的ASCII字符集,使用7位(bits)表示一个字符(最前面的1位统一规定为0),共128个字符。比如:空格“SPACE”是32(二进制00100000),大写的字母A是65(二进制01000001)。
  • 缺点:不能表示所有字符。

image-20220513095907601

8.3 ISO-8859-1字符集

  • 拉丁码表,别名Latin-1,用于显示欧洲使用的语言,包括荷兰语、德语、意大利语、葡萄牙语等
  • ISO-8859-1使用单字节编码,兼容ASCII编码。

8.4 GBxxx字符集

  • GB就是国标的意思,是为了显示中文而设计的一套字符集。
  • GB2312:简体中文码表。一个小于127的字符的意义与原来相同,即向下兼容ASCII码。但两个大于127的字符连在一起时,就表示一个汉字,这样大约可以组合了包含7000多个简体汉字,此外数学符号、罗马希腊的字母、日文的假名们都编进去了,这就是常说的”全角”字符,而原来在127号以下的那些符号就叫”半角”字符了。
  • GBK:最常用的中文码表。是在GB2312标准基础上的扩展规范,使用了双字节编码方案,共收录了21003个汉字,完全兼容GB2312标准,同时支持繁体汉字以及日韩汉字等。
  • GB18030:最新的中文码表。收录汉字70244个,采用多字节编码,每个字可以由1个、2个或4个字节组成。支持中国国内少数民族的文字,同时支持繁体汉字以及日韩汉字等。

8.5 Unicode码

  • Unicode编码为表达任意语言的任意字符而设计,也称为统一码、标准万国码。Unicode 将世界上所有的文字用2个字节统一进行编码,为每个字符设定唯一的二进制编码,以满足跨语言、跨平台进行文本处理的要求。

  • Unicode 的缺点:这里有三个问题:

    • 第一,英文字母只用一个字节表示就够了,如果用更多的字节存储是极大的浪费
    • 第二,如何才能区别Unicode和ASCII?计算机怎么知道两个字节表示一个符号,而不是分别表示两个符号呢?
    • 第三,如果和GBK等双字节编码方式一样,用最高位是1或0表示两个字节和一个字节,就少了很多值无法用于表示字符,不够表示所有字符
  • Unicode在很长一段时间内无法推广,直到互联网的出现,为解决Unicode如何在网络上传输的问题,于是面向传输的众多 UTF(UCS Transfer Format)标准出现。具体来说,有三种编码方案,UTF-8、UTF-16和UTF-32。

8.6 UTF-8

  • Unicode是字符集,UTF-8、UTF-16、UTF-32是三种将数字转换到程序数据的编码方案。顾名思义,UTF-8就是每次8个位传输数据,而UTF-16就是每次16个位。其中,UTF-8 是在互联网上使用最广的一种 Unicode 的实现方式。
  • 互联网工程工作小组(IETF)要求所有互联网协议都必须支持UTF-8编码。所以,我们开发Web应用,也要使用UTF-8编码。UTF-8 是一种变长的编码方式。它可以使用 1-4 个字节表示一个符号它使用一至四个字节为每个字符编码,编码规则:
    1. 128个US-ASCII字符,只需一个字节编码。
    2. 拉丁文等字符,需要二个字节编码。
    3. 大部分常用字(含中文),使用三个字节编码。
    4. 其他极少使用的Unicode辅助字符,使用四字节编码。
  • 举例

Unicode符号范围 | UTF-8编码方式

1
2
3
4
5
6
7
8
9
10
11
(十六进制)           | (二进制)

————————————————————|—–—–—–—–—–—–—–—–—–—–—–—–—–—–

0000 0000-0000 007F | 0xxxxxxx(兼容原来的ASCII)

0000 0080-0000 07FF | 110xxxxx 10xxxxxx

0000 0800-0000 FFFF | 1110xxxx 10xxxxxx 10xxxxxx

0001 0000-0010 FFFF | 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx

image-20220525164636164

8.7 小结

字符集

注意:在中文操作系统上,ANSI(美国国家标准学会、AMERICAN NATIONAL STANDARDS INSTITUTE: ANSI)编码即为GBK;在英文操作系统上,ANSI编码即为ISO-8859-1。

第03章_流程控制语句


本章专题与脉络

第1阶段:Java基本语法-第03章
  • 流程控制语句是用来控制程序中各语句执行顺序的语句,可以把语句组合成能完成一定功能的小逻辑模块。

  • 程序设计中规定的三种流程结构,即:

    • 顺序结构
      • 程序从上到下逐行地执行,中间没有任何判断和跳转。
    • 分支结构
      • 根据条件,选择性地执行某段代码。
      • if…elseswitch-case两种分支语句。
    • 循环结构
      • 根据循环条件,重复性的执行某段代码。
      • forwhiledo-while三种循环语句。
      • 补充:JDK5.0 提供了foreach循环,方便的遍历集合、数组元素。(第12章集合中讲解)
  • 生活中、工业生产中流程控制举例

洗衣流程

1. 顺序结构

顺序结构就是程序从上到下逐行地执行。表达式语句都是顺序执行的。并且上一行对某个变量的修改对下一行会产生影响。

image-20211218093256771

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class StatementTest{
public static void main(String[] args){
int x = 1;
int y = 2;
System.out.println("x = " + x);
System.out.println("y = " + y);
//对x、y的值进行修改
x++;
y = 2 * x + y;
x = x * 10;
System.out.println("x = " + x);
System.out.println("y = " + y);
}
}

Java中定义变量时采用合法的前向引用。如:

1
2
3
4
public static void main(String[] args) {
int num1 = 12;
int num2 = num1 + 2;
}

错误形式:

1
2
3
4
public static void main(String[] args) {
int num2 = num1 + 2;
int num1 = 12;
}

2. 分支语句

2.1 if-else条件判断结构

2.1.1 基本语法

结构1:单分支条件判断:if

格式:

1
2
3
if(条件表达式){
语句块;

说明:条件表达式必须是布尔表达式(关系表达式或逻辑表达式)/ 布尔变量。

执行流程:

  1. 首先判断条件表达式是true/false
  2. 如果是true就执行语句块
  3. 如果是false就不执行语句块

image-20220514160139926

结构2:双分支条件判断:if…else

格式:

1
2
3
4
5
6
if(条件表达式) { 
语句块1;
}
else {
语句块2;
}

执行流程:

  1. 首先判断条件表达式看其结果是true还是false
  2. 如果是true就执行语句块1
  3. 如果是false就执行语句块2

image-20220514160243421

结构3:多分支条件判断:if…else if…else

格式:

1
2
3
4
5
6
7
8
9
10
11
if (条件表达式1) {
语句块1;
} else if (条件表达式2) {
语句块2;
}
...
}else if (条件表达式n) {
语句块n;
} else {
语句块n+1;
}

说明:一旦条件表达式为true,则进入执行相应的语句块。执行完对应的语句块之后,就跳出当前结构。

执行流程:

  1. 首先判断关系表达式1看其结果是true还是false
  2. 如果是true就执行语句块1,然后结束当前多分支
  3. 如果是false就继续判断关系表达式2看其结果是true还是false
  4. 如果是true就执行语句块2,然后结束当前多分支
  5. 如果是false就继续判断关系表达式…看其结果是true还是false

​ …

n. 如果没有任何关系表达式为true,就执行语句块n+1,然后结束当前多分支。

image-20220514160538651

2.1.2 应用举例

案例1:成年人心率的正常范围是每分钟60-100次。体检时,如果心率不在此范围内,则提示需要做进一步的检查。

1
2
3
4
5
6
7
8
9
10
11
public class IfElseTest1 {
public static void main(String[] args){
int heartBeats = 89;

if(heartBeats < 60 || heartBeats > 100){
System.out.println("你需要做进一步的检查");
}

System.out.println("体检结束");
}
}

案例2:定义一个整数,判定是偶数还是奇数

1
2
3
4
5
6
7
8
9
10
11
public class IfElseTest2 {
public static void main(String[] args){
int a = 10;

if(a % 2 == 0) {
System.out.println(a + "是偶数");
} else{
System.out.println(a + "是奇数");
}
}
}

案例3:

1
2
3
4
5
6
7
8
岳小鹏参加Java考试,他和父亲岳不群达成承诺:
如果:
成绩为100分时,奖励一辆跑车;
成绩为(80,99]时,奖励一辆山地自行车;
当成绩为[60,80]时,奖励环球影城一日游;
其它时,胖揍一顿。

说明:默认成绩是在[0,100]范围内
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
public class IfElseTest3 {
public static void main(String[] args) {

int score = 67;//岳小鹏的期末成绩
//写法一:默认成绩范围为[0,100]
if(score == 100){
System.out.println("奖励一辆跑车");
}else if(score > 80 && score <= 99){ //错误的写法:}else if(80 < score <= 99){
System.out.println("奖励一辆山地自行车");
}else if(score >= 60 && score <= 80){
System.out.println("奖励环球影城玩一日游");
}
//else{
// System.out.println("胖揍一顿");
//}


//写法二:
// 默认成绩范围为[0,100]
if(score == 100){
System.out.println("奖励一辆跑车");
}else if(score > 80){
System.out.println("奖励一辆山地自行车");
}else if(score >= 60){
System.out.println("奖励环球影城玩一日游");
}else{
System.out.println("胖揍一顿");
}
}
}

image-20221027185234087

image-20221027185500389

当条件表达式之间是“互斥”关系时(即彼此没有交集),条件判断语句及执行语句间顺序无所谓。

当条件表达式之间是“包含”关系时,“小上大下 / 子上父下”,否则范围小的条件表达式将不可能被执行。

2.1.3 if…else嵌套

在 if 的语句块中,或者是在else语句块中,又包含了另外一个条件判断(可以是单分支、双分支、多分支),就构成了嵌套结构

执行的特点:
(1)如果是嵌套在if语句块中的,只有当外部的if条件满足,才会去判断内部的条件
(2)如果是嵌套在else语句块中的,只有当外部的if条件不满足,进入else后,才会去判断内部的条件

案例4:由键盘输入三个整数分别存入变量num1、num2、num3,对它们进行排序(使用 if-else if-else),并且从小到大输出。

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
class IfElseTest4 {
public static void main(String[] args) {

//声明num1,num2,num3三个变量并赋值
int num1 = 23,num2 = 32,num3 = 12;

if(num1 >= num2){

if(num3 >= num1)
System.out.println(num2 + "-" + num1 + "-" + num3);
else if(num3 <= num2)
System.out.println(num3 + "-" + num2 + "-" + num1);
else
System.out.println(num2 + "-" + num3 + "-" + num1);
}else{ //num1 < num2

if(num3 >= num2){
System.out.println(num1 + "-" + num2 + "-" + num3);
}else if(num3 <= num1){
System.out.println(num3 + "-" + num1 + "-" + num2);
}else{
System.out.println(num1 + "-" + num3 + "-" + num2);
}
}
}
}

2.1.4 其它说明

  • 语句块只有一条执行语句时,一对{}可以省略,但建议保留
  • 当if-else结构是“多选一”时,最后的else是可选的,根据需要可以省略

2.1.5 练习

练习1:

1
2
3
4
5
6
7
8
9
//1)对下列代码,若有输出,指出输出结果。
int x = 4;
int y = 1;
if (x > 2) {
if (y > 2)
System.out.println(x + y);
System.out.println("atguigu");
} else
System.out.println("x is " + x);

练习2:

1
2
3
4
5
6
7
8
9
10
boolean b = true;
//如果写成if(b=false)能编译通过吗?如果能,结果是?
if(b == false) //建议:if(!b)
System.out.println("a");
else if(b)
System.out.println("b");
else if(!b)
System.out.println("c");
else
System.out.println("d");

练习3:

定义两个整数,分别为small 和 big,如果第一个整数small大于第二个整数big,就交换。输出显示small和big变量的值。

1
2
3
4
5
6
7
8
9
10
11
12
13
public class IfElseExer3 {
public static void main(String[] args) {
int small = 10;
int big = 9;

if (small > big) {
int temp = small;
small = big;
big = temp;
}
System.out.println("small=" + small + ",big=" + big);
}
}

练习4:小明参加期末Java考试,通过考试成绩,判断其Java等级,成绩范围[0,100]

  • 90-100 优秀
  • 80-89 好
  • 70-79 良
  • 60-69 及格
  • 60以下 不及格
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import java.util.Scanner;
//写法一:
public class IfElseExer4 {
public static void main(String[] args) {
System.out.print("小明的期末Java成绩是:[0,100]");
int score = 89;

if (score < 0 || score > 100) {
System.out.println("你的成绩是错误的");
} else if (score >= 90 && score <= 100) {
System.out.println("你的成绩属于优秀");
} else if (score >= 80 && score < 90) {
System.out.println("你的成绩属于好");
} else if (score >= 70 && score < 80) {
System.out.println("你的成绩属于良");
} else if (score >= 60 && score < 70) {
System.out.println("你的成绩属于及格");
} else {
System.out.println("你的成绩属于不及格");
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import java.util.Scanner;
//写法二:
public class IfElseExer4 {
public static void main(String[] args) {
System.out.print("小明的期末Java成绩是:[0,100]");
int score = 89;

if (score < 0 || score > 100) {
System.out.println("你的成绩是错误的");
} else if (score >= 90) {
System.out.println("你的成绩属于优秀");
} else if (score >= 80) {
System.out.println("你的成绩属于好");
} else if (score >= 70) {
System.out.println("你的成绩属于良");
} else if (score >= 60) {
System.out.println("你的成绩属于及格");
} else {
System.out.println("你的成绩属于不及格");
}

}
}

练习5:

1
编写程序,声明2个int型变量并赋值。判断两数之和,如果大于等于50,打印“hello world!”
1
2
3
4
5
6
7
8
9
10
public class IfElseExer5 {

public static void main(String[] args) {
int num1 = 12, num2 = 32;

if (num1 + num2 >= 50) {
System.out.println("hello world!");
}
}
}

练习6:

1
编写程序,声明2个double型变量并赋值。判断第一个数大于10.0,且第2个数小于20.0,打印两数之和。否则,打印两数的乘积。
1
2
3
4
5
6
7
8
9
10
11
12
13
public class IfElseExer6 {

public static void main(String[] args) {
double d1 = 21.2,d2 = 12.3;

if(d1 > 10.0 && d2 < 20.0){
System.out.println("两数之和为:" + (d1 + d2));
}else{
System.out.println("两数乘积为:" + (d1 * d2));
}
}

}

练习7:判断水的温度

1
2
3
4
5
6
7
如果大于95℃,则打印“开水”;

如果大于70℃且小于等于95℃,则打印“热水”;

如果大于40℃且小于等于70℃,则打印“温水”;

如果小于等于40℃,则打印“凉水”。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class IfElseExer7 {

public static void main(String[] args) {
int waterTemperature = 85;

if(waterTemperature > 95){
System.out.println("开水");
}else if(waterTemperature > 70 && waterTemperature <= 95){
System.out.println("热水");
}else if(waterTemperature > 40 && waterTemperature <= 70){
System.out.println("温水");
}else{
System.out.println("凉水");
}
}

}

2.2 switch-case选择结构

2.2.1 基本语法

语法格式:

1
2
3
4
5
6
7
8
9
10
11
12
13
switch(表达式){
case 常量值1:
语句块1;
//break;
case 常量值2:
语句块2;
//break;
// ...
[default:
语句块n+1;
break;
]
}

执行流程图:

image-20220514101841710

执行过程:

第1步:根据switch中表达式的值,依次匹配各个case。如果表达式的值等于某个case中的常量值,则执行对应case中的执行语句。

第2步:执行完此case的执行语句以后,
​ 情况1:如果遇到break,则执行break并跳出当前的switch-case结构
​ 情况2:如果没有遇到break,则会继续执行当前case之后的其它case中的执行语句。—>case穿透
​ …
​ 直到遇到break关键字或执行完所有的case及default的执行语句,跳出当前的switch-case结构

使用注意点:

  • switch(表达式)中表达式的值必须是下述几种类型之一:byte,short,char,int,枚举 (jdk 5.0),String (jdk 7.0);

  • case子句中的值必须是常量,不能是变量名或不确定的表达式值或范围;

  • 同一个switch语句,所有case子句中的常量值互不相同;

  • break语句用来在执行完一个case分支后使程序跳出switch语句块;

    如果没有break,程序会顺序执行到switch结尾;

  • default子句是可选的。同时,位置也是灵活的。当没有匹配的case时,执行default语句。

2.2.2 应用举例

案例1:

1
2
3
4
5
6
7
8
9
10
11
public class SwitchCaseTest1 {
public static void main(String args[]) {
int num = 1;
switch (num){
case 0:System.out.println("0");break;
case 1:System.out.println("1");break;
case 2:System.out.println("2");break;
default:System.out.println("other");break;
}
}
}

案例2:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class SwitchCaseTest2 {
public static void main(String args[]) {
String season = "summer";
switch (season) {
case "spring":
System.out.println("春暖花开");
break;
case "summer":
System.out.println("夏日炎炎");
break;
case "autumn":
System.out.println("秋高气爽");
break;
case "winter":
System.out.println("冬雪皑皑");
break;
default:
System.out.println("季节输入有误");
break;
}
}
}

错误举例:

1
2
3
4
5
6
7
8
9
10
11
12
int key = 10;
switch(key){
case key > 0 :
System.out.println("正数");
break;
case key < 0:
System.out.println("负数");
break;
default:
System.out.println("零");
break;
}

案例3:使用switch-case实现:对学生成绩大于60分的,输出“合格”。低于60分的,输出“不合格”。

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
56
57
58
59
class SwitchCaseTest3 {
public static void main(String[] args) {

int score = 67;
/*
写法1:极不推荐
switch(score){
case 0:
System.out.println("不及格");
break;
case 1:
System.out.println("不及格");
break;
//...

case 60:
System.out.println("及格");
break;
//...略...

}
*/

//写法2:
switch(score / 10){
case 0:
case 1:
case 2:
case 3:
case 4:
case 5:
System.out.println("不及格");
break;
case 6:
case 7:
case 8:
case 9:
case 10:
System.out.println("及格");
break;
default:
System.out.println("输入的成绩有误");
break;
}

//写法3:
switch(score / 60){
case 0:
System.out.println("不及格");
break;
case 1:
System.out.println("及格");
break;
default:
System.out.println("输入的成绩有误");
break;
}
}
}

2.2.3 利用case的穿透性

在switch语句中,如果case的后面不写break,将出现穿透现象,也就是一旦匹配成功,不会在判断下一个case的值,直接向后运行,直到遇到break或者整个switch语句结束,执行终止。

案例4:编写程序:从键盘上输入2023年的“month”和“day”,要求通过程序输出输入的日期为2023年的第几天。

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
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
import java.util.Scanner;

class SwitchCaseTest4 {
public static void main(String[] args) {

Scanner scan = new Scanner(System.in);

System.out.println("请输入2023年的month:");
int month = scan.nextInt();

System.out.println("请输入2023年的day:");
int day = scan.nextInt();

//这里就不针对month和day进行合法性的判断了,以后可以使用正则表达式进行校验。

int sumDays = 0;//记录总天数

//写法1 :不推荐(存在冗余的数据)
/*
switch(month){
case 1:
sumDays = day;
break;
case 2:
sumDays = 31 + day;
break;
case 3:
sumDays = 31 + 28 + day;
break;
//....

case 12:
//sumDays = 31 + 28 + ... + 30 + day;
break;
}
*/

//写法2:推荐
switch(month){
case 12:
sumDays += 30;//这个30是代表11月份的满月天数
case 11:
sumDays += 31;//这个31是代表10月份的满月天数
case 10:
sumDays += 30;//这个30是代表9月份的满月天数
case 9:
sumDays += 31;//这个31是代表8月份的满月天数
case 8:
sumDays += 31;//这个31是代表7月份的满月天数
case 7:
sumDays += 30;//这个30是代表6月份的满月天数
case 6:
sumDays += 31;//这个31是代表5月份的满月天数
case 5:
sumDays += 30;//这个30是代表4月份的满月天数
case 4:
sumDays += 31;//这个31是代表3月份的满月天数
case 3:
sumDays += 28;//这个28是代表2月份的满月天数
case 2:
sumDays += 31;//这个31是代表1月份的满月天数
case 1:
sumDays += day;//这个day是代表当月的第几天
}

System.out.println(month + "月" + day + "日是2023年的第" + sumDays + "天");
//关闭资源
scan.close();
}
}

拓展:

1
2
3
4
5
6
7
8
从键盘分别输入年、月、日,判断这一天是当年的第几天

注:判断一年是否是闰年的标准:
1)可以被4整除,但不可被100整除

2)可以被400整除

例如:1900,2200等能被4整除,但同时能被100整除,但不能被400整除,不是闰年
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
56
57
58
59
60
61
62
63
64
65
66
import java.util.Scanner;

public class SwitchCaseTest04 {

public static void main(String[] args) {

Scanner scanner = new Scanner(System.in);
System.out.print("请输入year:");
int year = scanner.nextInt();

System.out.print("请输入month:");
int month = scanner.nextInt();

System.out.print("请输入day:");
int day = scanner.nextInt();

//判断这一天是当年的第几天==>从1月1日开始,累加到xx月xx日这一天
//(1)[1,month-1]个月满月天数
//(2)单独考虑2月份是否是29天(依据是看year是否是闰年)
//(3)第month个月的day天

//声明一个变量days,用来存储总天数
int sumDays = 0;

//累加[1,month-1]个月满月天数
switch (month) {
case 12:
//累加的1-11月
sumDays += 30;//这个30是代表11月份的满月天数
//这里没有break,继续往下走
case 11:
//累加的1-10月
sumDays += 31;//这个31是代表10月的满月天数
//这里没有break,继续往下走
case 10:
sumDays += 30;//9月
case 9:
sumDays += 31;//8月
case 8:
sumDays += 31;//7月
case 7:
sumDays += 30;//6月
case 6:
sumDays += 31;//5月
case 5:
sumDays += 30;//4月
case 4:
sumDays += 31;//3月
case 3:
sumDays += 28;//2月
//在这里考虑是否可能是29天
if (year % 4 == 0 && year % 100 != 0 || year % 400 == 0) {
sumDays++;//多加1天
}
case 2:
sumDays += 31;//1月
case 1:
sumDays += day;//第month月的day天
}

//输出结果
System.out.println(year + "年" + month + "月" + day + "日是这一年的第" + sumDays + "天");

scanner.close();
}
}

案例5:根据指定的月份输出对应季节

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
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
import java.util.Scanner;

/*
* 需求:指定一个月份,输出该月份对应的季节。一年有四季:
* 3,4,5 春季
* 6,7,8 夏季
* 9,10,11 秋季
* 12,1,2 冬季
*/
public class SwitchCaseTest5 {
public static void main(String[] args) {
Scanner input = new Scanner(System.in);
System.out.print("请输入月份:");
int month = input.nextInt();

/*
switch(month) {
case 1:
System.out.println("冬季");
break;
case 2:
System.out.println("冬季");
break;
case 3:
System.out.println("春季");
break;
case 4:
System.out.println("春季");
break;
case 5:
System.out.println("春季");
break;
case 6:
System.out.println("夏季");
break;
case 7:
System.out.println("夏季");
break;
case 8:
System.out.println("夏季");
break;
case 9:
System.out.println("秋季");
break;
case 10:
System.out.println("秋季");
break;
case 11:
System.out.println("秋季");
break;
case 12:
System.out.println("冬季");
break;
default:
System.out.println("你输入的月份有误");
break;
}
*/

// 改进版
switch(month) {
case 1:
case 2:
case 12:
System.out.println("冬季");
break;
case 3:
case 4:
case 5:
System.out.println("春季");
break;
case 6:
case 7:
case 8:
System.out.println("夏季");
break;
case 9:
case 10:
case 11:
System.out.println("秋季");
break;
default:
System.out.println("你输入的月份有误");
break;
}

input.close();
}
}

常见错误实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
switch(month){
case 3|4|5://3|4|5 用了位运算符,11 | 100 | 101结果是 111是7
System.out.println("春季");
break;
case 6|7|8://6|7|8用了位运算符,110 | 111 | 1000结果是1111是15
System.out.println("夏季");
break;
case 9|10|11://9|10|11用了位运算符,1001 | 1010 | 1011结果是1011是11
System.out.println("秋季");
break;
case 12|1|2://12|1|2 用了位运算符,1100 | 1 | 10 结果是1111,是15
System.out.println("冬季");
break;
default:
System.out.println("输入有误");
}

使用if-else实现:

1
2
3
4
5
6
7
8
9
10
11
if ((month == 1) || (month == 2) || (month == 12)) {
System.out.println("冬季");
} else if ((month == 3) || (month == 4) || (month == 5)) {
System.out.println("春季");
} else if ((month == 6) || (month == 7) || (month == 8)) {
System.out.println("夏季");
} else if ((month == 9) || (month == 10) || (month == 11)) {
System.out.println("秋季");
} else {
System.out.println("你输入的月份有误");
}

2.2.4 if-else语句与switch-case语句比较

  • 结论:凡是使用switch-case的结构都可以转换为if-else结构。反之,不成立。

  • 开发经验:如果既可以使用switch-case,又可以使用if-else,建议使用switch-case。因为效率稍高。

  • 细节对比:

    • if-else语句优势
      • if语句的条件是一个布尔类型值,if条件表达式为true则进入分支,可以用于范围的判断,也可以用于等值的判断,使用范围更广
      • switch语句的条件是一个常量值(byte,short,int,char,枚举,String),只能判断某个变量或表达式的结果是否等于某个常量值,使用场景较狭窄
    • switch语句优势
      • 当条件是判断某个变量或表达式是否等于某个固定的常量值时,使用if和switch都可以,习惯上使用switch更多。因为效率稍高。当条件是区间范围的判断时,只能使用if语句。
      • 使用switch可以利用穿透性,同时执行多个分支,而if…else没有穿透性。
  • 案例:只能使用 if-else

    从键盘输入一个整数,判断是正数、负数、还是零。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import java.util.Scanner;

public class IfOrSwitchDemo {
public static void main(String[] args) {
Scanner input = new Scanner(System.in);

System.out.print("请输入整数:");
int num = input.nextInt();

if (num > 0) {
System.out.println(num + "是正整数");
} else if (num < 0) {
System.out.println(num + "是负整数");
} else {
System.out.println(num + "是零");
}

input.close();
}
}

2.2.5 练习

练习1:从键盘输入星期的整数值,输出星期的英文单词

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
import java.util.Scanner;

public class SwitchCaseExer1 {
public static void main(String[] args) {
//定义指定的星期
Scanner input = new Scanner(System.in);
System.out.print("请输入星期值:");
int weekday = input.nextInt();

//switch语句实现选择
switch(weekday) {
case 1:
System.out.println("Monday");
break;
case 2:
System.out.println("Tuesday");
break;
case 3:
System.out.println("Wednesday");
break;
case 4:
System.out.println("Thursday");
break;
case 5:
System.out.println("Friday");
break;
case 6:
System.out.println("Saturday");
break;
case 7:
System.out.println("Sunday");
break;
default:
System.out.println("你输入的星期值有误!");
break;
}

input.close();
}
}

练习2:

1
使用 switch 把小写类型的 char型转为大写。只转换 a, b, c, d, e. 其它的输出 “other”。
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
public class SwitchCaseExer2 {

public static void main(String[] args) {

char word = 'c';
switch (word) {
case 'a':
System.out.println("A");
break;
case 'b':
System.out.println("B");
break;
case 'c':
System.out.println("C");
break;
case 'd':
System.out.println("D");
break;
case 'e':
System.out.println("E");
break;
default :
System.out.println("other");
}
}
}

练习3:

1
2
3
4
5
6
7
8
9
编写程序:从键盘上读入一个学生成绩,存放在变量score中,根据score的值输出其对应的成绩等级:

score>=90 等级: A
70<=score<90 等级: B
60<=score<70 等级: C
score<60 等级: D

方式一:使用if-else
方式二:使用switch-case: score / 10: 0 - 10
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
public class SwitchCaseExer3 {

public static void main(String[] args) {

Scanner scan = new Scanner(System.in);
System.out.println("请输入学生成绩:");
int score = scan.nextInt();

char grade;//记录学生等级
//方式1:
// if(score >= 90){
// grade = 'A';
// }else if(score >= 70 && score < 90){
// grade = 'B';
// }else if(score >= 60 && score < 70){
// grade = 'C';
// }else{
// grade = 'D';
// }

//方式2:
switch(score / 10){
case 10:
case 9:
grade = 'A';
break;
case 8:
case 7:
grade = 'B';
break;
case 6:
grade = 'C';
break;
default :
grade = 'D';
}

System.out.println("学生成绩为" + score + ",对应的等级为" + grade);

scan.close();
}
}

练习4:

1
2
3
编写一个程序,为一个给定的年份找出其对应的中国生肖。中国的生肖基于12年一个周期,每年用一个动物代表:rat、ox、tiger、rabbit、dragon、snake、horse、sheep、monkey、rooster、dog、pig。

提示:2022年:虎 2022 % 12 == 6

image-20220314005350344

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
/**
* @author 尚硅谷-宋红康
* @create 18:55
*/
public class SwitchCaseExer4 {
public static void main(String[] args){
//从键盘输入一个年份
Scanner input = new Scanner(System.in);
System.out.print("请输入年份:");
int year = input.nextInt();
input.close();

//判断
switch(year % 12){
case 0:
System.out.println(year + "是猴年");
break;
case 1:
System.out.println(year + "是鸡年");
break;
case 2:
System.out.println(year + "是狗年");
break;
case 3:
System.out.println(year + "是猪年");
break;
case 4:
System.out.println(year + "是鼠年");
break;
case 5:
System.out.println(year + "是牛年");
break;
case 6:
System.out.println(year + "是虎年");
break;
case 7:
System.out.println(year + "是兔年");
break;
case 8:
System.out.println(year + "是龙年");
break;
case 9:
System.out.println(year + "是蛇年");
break;
case 10:
System.out.println(year + "是马年");
break;
case 11:
System.out.println(year + "是羊年");
break;
default:
System.out.println(year + "输入错误");
}
}
}

练习5:押宝游戏

1
2
3
4
随机产生3个1-6的整数,如果三个数相等,那么称为“豹子”,如果三个数之和大于9,称为“大”,如果三个数之和小于等于9,称为“小”,用户从键盘输入押的是“豹子”、“大”、“小”,并判断是否猜对了

提示:随机数 Math.random()产生 [0,1)范围内的小数
如何获取[a,b]范围内的随机整数呢?(int)(Math.random() * (b - a + 1)) + a

1659112038716

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
import java.util.Scanner;

public class SwitchCaseExer5 {
public static void main(String[] args) {
//1、随机产生3个1-6的整数
int a = (int)(Math.random()*6 + 1);
int b = (int)(Math.random()*6 + 1);
int c = (int)(Math.random()*6 + 1);

//2、押宝
Scanner input = new Scanner(System.in);
System.out.print("请押宝(豹子、大、小):");
String ya = input.next();
input.close();

//3、判断结果
boolean result = false;
//switch支持String类型
switch (ya){
case "豹子": result = a == b && b == c; break;
case "大": result = a + b + c > 9; break;
case "小": result = a + b + c <= 9; break;
default:System.out.println("输入有误!");
}

System.out.println("a,b,c分别是:" + a +"," + b +"," + c );
System.out.println(result ? "猜中了" : "猜错了");
}
}

练习6:

1
2
3
4
5
6
7
8
9
10
11
12
使用switch语句改写下列if语句:

int a = 3;
int x = 100;
if(a==1)
x+=5;
else if(a==2)
x+=10;
else if(a==3)
x+=16;
else
x+=34;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
int a = 3;
int x = 100;

switch(a){
case 1:
x += 5;
break;
case 2:
x += 10;
break;
case 3:
x += 16;
break;
default :
x += 34;

}

3. 循环语句

  • 理解:循环语句具有在某些条件满足的情况下,反复执行特定代码的功能。

  • 循环结构分类:

    • for 循环
    • while 循环
    • do-while 循环
  • 循环结构四要素

    • 初始化部分
    • 循环条件部分
    • 循环体部分
    • 迭代部分

3.1 for循环

3.1.1 基本语法

语法格式:

1
2
3
for (①初始化部分; ②循环条件部分; ④迭代部分){
③循环体部分;

执行过程:①-[②-③-④]-[②-③-④]-[②-③-④]-…..-直到②不满足

图示:

image-20220315013023236

说明:

  • for(;;)中的两个;不能多也不能少
  • ①初始化部分可以声明多个变量,但必须是同一个类型,用逗号分隔 –例如: int i=10,j=12,k=13;
  • ②循环条件部分为boolean类型表达式,当值为false时,退出循环
  • ④可以有多个变量更新,用逗号分隔 –例如: ;i++,j++,k++;

3.1.2 应用举例

案例1:使用for循环重复执行某些语句

题目:输出5行HelloWorld

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class ForTest1 {
public static void main(String[] args) {
//需求1:控制台输出5行Hello World!
//写法1:
//System.out.println("Hello World!");
//System.out.println("Hello World!");
//System.out.println("Hello World!");
//System.out.println("Hello World!");
//System.out.println("Hello World!");

//写法2:
for(int i = 1;i <= 5;i++){
System.out.println("Hello World!");
}
}
}

案例2:格式的多样性

题目:写出输出的结果

1
2
3
4
5
6
7
8
9
public class ForTest2 {
public static void main(String[] args) {
int num = 1;
for(System.out.print("a");num < 3;System.out.print("c"),num++){
System.out.print("b");

}
}
}

案例3:累加的思想

题目:遍历1-100以内的偶数,并获取偶数的个数,获取所有的偶数的和

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class ForTest3 {
public static void main(String[] args) {
int count = 0;//记录偶数的个数
int sum = 0;//记录偶数的和

for(int i = 1;i <= 100;i++){

if(i % 2 == 0){
System.out.println(i);
count++;
sum += i;
}

//System.out.println("偶数的个数为:" + count);
}

System.out.println("偶数的个数为:" + count);
System.out.println("偶数的总和为:" + sum);
}
}

案例4:结合分支结构使用

题目:输出所有的水仙花数,所谓水仙花数是指一个3位数,其各个位上数字立方和等于其本身。例如: 153 = 1*1*1 + 3*3*3 + 5*5*5

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class ForTest4 {
public static void main(String[] args) {
int sum=0;
for(int i=100;i<1000;i++){
//取出每一位
int ge=i%10;
int shi=i/10%10;
int bai=i/100;
//System.out.println("此轮的每位数字分别是:"+bai+" "+shi+" "+ge);
if((ge*ge*ge+shi*shi*shi+bai*bai*bai)==i){
System.out.println(i);
sum++;
}
}
System.out.println(sum);
}
}

拓展:

1
打印出四位数字中“个位+百位”等于“十位+千位”并且个位数为偶数,千位数为奇数的数字,并打印符合条件的数字的个数。

案例5:结合break的使用

说明:输入两个正整数m和n,求其最大公约数和最小公倍数。

比如:12和20的最大公约数是4,最小公倍数是60。

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
public class ForTest5 {
public static void main(String[] args) {
//输入两个正整数m和n,求其最大公约数和最小公倍数
//公约数: 1 2 3 4 6 / 1 2 4 5 10 --最大公约数是4
//公倍数: 12 24 36 48 60 / 20 40 60 80 --最小公倍数是60
int m=12,n=20;
int maxgy=0; // 最大公约数
int mingb=0; // 最小公倍数
int maxgy2=0;

//1.考虑找最大公约数的话就要看min 如果min的到头了那就最大了
int min=n>m?m:n;
for(int i=min;i>=0;i--){
if(m%i==0&&n%i==0){ //只要同时能被整除那就是公约数
maxgy=i;
break; //第一个满足的就是最大的
}
}
//1.2.笨办法就是从1开始到min范围内找
for(int i=1;i<=min;i++){
if(m%i==0&&n%i==0){ //只要同时能被整除那就是公约数
maxgy2=i;
}
}

//2.考虑找最小公倍数的话就要看max
int max=n>m?n:m;
for(int i=max;i<=m*n;i++){ //从max-m*n范围内 最差就是m*n
if(i%m==0&&i%n==0){ //i同时能整除m和n就是公倍数
mingb=i;
break; //找到第一个就是最小的
}
}
System.out.println("最大公约数1方法:"+maxgy);
System.out.println("最大公约数2方法:"+maxgy2);
System.out.println("最小公倍数:"+mingb);
}
}

说明:

1、我们可以在循环中使用break。一旦执行break,就跳出当前循环结构。

2、小结:如何结束一个循环结构?

​ 结束情况1:循环结构中的循环条件部分返回false

​ 结束情况2:循环结构中执行了break。

3、如果一个循环结构不能结束,那就是一个死循环!我们开发中要避免出现死循环。

3.1.3 练习

练习1:打印1~100之间所有奇数的和

1
2
3
4
5
6
7
8
9
10
11
12
13
public class ForExer1 {

public static void main(String[] args) {

int sum = 0;//记录奇数的和
for (int i = 1; i < 100; i++) {
if(i % 2 != 0){ //不能被2整除就是奇数
sum += i;
}
}
System.out.println("奇数总和为:" + sum);
}
}

练习2:打印1~100之间所有是7的倍数的整数的个数及总和(体会设置计数器的思想)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class ForExer2 {

public static void main(String[] args) {

int sum = 0;//记录总和
int count = 0;//记录个数
for (int i = 1; i < 100; i++) {
if(i % 7 == 0){
sum += i;
count++;
}
}
System.out.println("1~100之间所有是7的倍数的整数的和为:" + sum);
System.out.println("1~100之间所有是7的倍数的整数的个数为:" + count);
}
}

练习3:

编写程序从1循环到150,并在每行打印一个值,另外在每个3的倍数行上打印出“foo”,在每个5的倍数行上打印“biz”,在每个7的倍数行上打印输出“baz”。

image-20220315013722962
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class ForExer3 {
public static void main(String[] args) {
for (int i = 1; i < =150; i++) {
if(i%3==0){
System.out.println(i+" foo");
}else if(i%5==0){
System.out.println(i+" biz");
}else if(i%7==0){
System.out.println(i+" baz");
}else{
System.out.println(i);
}
}
}
}

3.2 while循环

3.2.1 基本语法

语法格式:

1
2
3
4
5
①初始化部分
while(②循环条件部分){
③循环体部分;
④迭代部分;
}

执行过程:①-(②-③-④)-(②-③-④)-(②-③-④)-…-直到②不符合结束

图示:

image-20220315013023236

说明:

  • while(循环条件)中循环条件必须是boolean类型。
  • 注意不要忘记声明④迭代部分。否则,循环将不能结束,变成死循环。
  • for循环和while循环可以相互转换。二者没有性能上的差别。实际开发中,根据具体结构的情况,选择哪个格式更合适、美观。
  • for循环与while循环的区别:初始化条件部分的作用域不同。

3.2.2 应用举例

案例1:输出5行HelloWorld!

1
2
3
4
5
6
7
8
9
10
class WhileTest1 {
public static void main(String[] args) {

int i = 1;
while(i <= 5){
System.out.println("Hello World!");
i++;
}
}
}

案例2:遍历1-100的偶数,并计算所有偶数的和、偶数的个数(累加的思想)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class WhileTest2 {
public static void main(String[] args) {
//遍历1-100的偶数,并计算所有偶数的和、偶数的个数(累加的思想)
int num = 1;

int sum = 0;//记录1-100所有的偶数的和
int count = 0;//记录1-100之间偶数的个数

while(num <= 100){

if(num % 2 == 0){
System.out.println(num);
sum += num;
count++;
}

//迭代条件
num++;
}

System.out.println("偶数的总和为:" + sum);
System.out.println("偶数的个数为:" + count);
}
}

案例3:猜数字游戏

1
2
3
4
5
随机生成一个100以内的数,猜这个随机数是多少?

从键盘输入数,如果大了,提示大了;如果小了,提示小了;如果对了,就不再猜了,并统计一共猜了多少次。

提示:生成一个[a,b] 范围的随机数的方式:(int)(Math.random() * (b - a + 1) + a)
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
/**
* @author 尚硅谷-宋红康
* @create 16:42
*/
public class GuessNumber {
public static void main(String[] args) {
//获取一个随机数
int random = (int) (Math.random() * 100) + 1;

//记录猜的次数
int count = 1;

//实例化Scanner
Scanner scan = new Scanner(System.in);
System.out.println("请输入一个整数(1-100):");
int guess = scan.nextInt();

while (guess != random) {

if (guess > random) {
System.out.println("猜大了");
} else if (guess < random) {
System.out.println("猜小了");
}

System.out.println("请输入一个整数(1-100):");
guess = scan.nextInt();
//累加猜的次数
count++;

}

System.out.println("猜中了!");
System.out.println("一共猜了" + count + "次");
}
}

案例4:折纸珠穆朗玛峰

1
2
世界最高山峰是珠穆朗玛峰,它的高度是8848.86米,假如我有一张足够大的纸,它的厚度是0.1毫米。
请问,我折叠多少次,可以折成珠穆朗玛峰的高度?
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
/**
* @author 尚硅谷-宋红康
* @create 19:08
*/
public class ZFTest {
public static void main(String[] args) {
//定义一个计数器,初始值为0
int count = 0;

//定义珠穆朗玛峰的高度
int zf = 8848860;//单位:毫米

double paper = 0.1;//单位:毫米

while(paper < zf){
//在循环中执行累加,对应折叠了多少次
count++;
paper *= 2;//循环的执行过程中每次纸张折叠,纸张的厚度要加倍
}

//打印计数器的值
System.out.println("需要折叠:" + count + "次");
System.out.println("折纸的高度为" + paper/1000 + "米,超过了珠峰的高度");
}
}

3.2.3 练习

练习:从键盘输入整数,输入0结束,统计输入的正数、负数的个数。

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
import java.util.Scanner;

public class Test05While {
public static void main(String[] args) {
Scanner input = new Scanner(System.in);

int positive = 0; //记录正数的个数
int negative = 0; //记录负数的个数
int num = 1; //初始化为特殊值,使得第一次循环条件成立
while(num != 0){
System.out.print("请输入整数(0表示结束):");
num = input.nextInt();

if(num > 0){
positive++;
}else if(num < 0){
negative++;
}
}
System.out.println("正数个数:" + positive);
System.out.println("负数个数:" + negative);

input.close();
}
}

3.3 do-while循环

3.3.1 基本语法

语法格式:

1
2
3
4
5
①初始化部分;
do{
③循环体部分
④迭代部分
}while(②循环条件部分);

执行过程:①-③-④-②-③-④-②-③-④-…-②

图示:

image-20220512165558698

说明:

  • 结尾while(循环条件)中循环条件必须是boolean类型
  • do{}while();最后有一个分号
  • do-while结构的循环体语句是至少会执行一次,这个和for和while是不一样的
  • 循环的三个结构for、while、do-while三者是可以相互转换的。

3.3.2 应用举例

案例1:遍历1-100的偶数,并计算所有偶数的和、偶数的个数(累加的思想)

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
class DoWhileTest1 {
public static void main(String[] args) {

//遍历1-100的偶数,并计算所有偶数的和、偶数的个数(累加的思想)
//初始化部分
int num = 1;

int sum = 0;//记录1-100所有的偶数的和
int count = 0;//记录1-100之间偶数的个数

do{
//循环体部分
if(num % 2 == 0){
System.out.println(num);
sum += num;
count++;
}

num++;//迭代部分


}while(num <= 100); //循环条件部分


System.out.println("偶数的总和为:" + sum);
System.out.println("偶数的个数为:" + count);
}
}

案例2:体会do-while至少会执行一次循环体

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class DoWhileTest2 {
public static void main(String[] args) {
//while循环:
int num1 = 10;
while(num1 > 10){
System.out.println("hello:while");
num1--;
}

//do-while循环:
int num2 = 10;
do{
System.out.println("hello:do-while");
num2--;
}while(num2 > 10);

}
}

案例3:ATM取款

1
2
3
4
5
6
7
8
声明变量balance并初始化为0,用以表示银行账户的余额,下面通过ATM机程序实现存款,取款等功能。

=========ATM========
1、存款
2、取款
3、显示余额
4、退出
请选择(1-4):
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
56
57
58
59
60
import java.util.Scanner;

/**
* @author 尚硅谷-宋红康
* @create 19:12
*/
public class ATM {
public static void main(String[] args) {

//初始化条件
double balance = 0.0;//表示银行账户的余额
Scanner scan = new Scanner(System.in);
boolean isFlag = true;//用于控制循环的结束

do{
System.out.println("=========ATM========");
System.out.println("\t1、存款");
System.out.println("\t2、取款");
System.out.println("\t3、显示余额");
System.out.println("\t4、退出");
System.out.print("请选择(1-4):");

int selection = scan.nextInt();

switch(selection){
case 1:
System.out.print("要存款的额度为:");
double addMoney = scan.nextDouble();
if(addMoney > 0){
balance += addMoney;
}
break;
case 2:
System.out.print("要取款的额度为:");
double minusMoney = scan.nextDouble();
if(minusMoney > 0 && balance >= minusMoney){
balance -= minusMoney;
}else{
System.out.println("您输入的数据非法或余额不足");
}
break;
case 3:
System.out.println("当前的余额为:" + balance);
break;
case 4:
System.out.println("欢迎下次进入此系统。^_^");
isFlag = false;
break;
default:
System.out.println("请重新选择!");
break;
}

}while(isFlag);

//资源关闭
scan.close();

}
}

3.3.3 练习

练习1:随机生成一个100以内的数,猜这个随机数是多少?

从键盘输入数,如果大了提示,大了;如果小了,提示小了;如果对了,就不再猜了,并统计一共猜了多少次。

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
import java.util.Scanner;

public class DoWhileExer {
public static void main(String[] args) {
//随机生成一个100以内的整数
/*
Math.random() ==> [0,1)的小数
Math.random()* 100 ==> [0,100)的小数
(int)(Math.random()* 100) ==> [0,100)的整数
*/
int num = (int)(Math.random()* 100);
//System.out.println(num);

//声明一个变量,用来存储猜的次数
int count = 0;

Scanner input = new Scanner(System.in);
int guess;//提升作用域
do{
System.out.print("请输入100以内的整数:");
guess = input.nextInt();

//输入一次,就表示猜了一次
count++;

if(guess > num){
System.out.println("大了");
}else if(guess < num){
System.out.println("小了");
}
}while(num != guess);

System.out.println("一共猜了:" + count+"次");

input.close();
}
}

3.4 对比三种循环结构

  • 三种循环结构都具有四个要素:
    • 循环变量的初始化条件
    • 循环条件
    • 循环体语句块
    • 循环变量的修改的迭代表达式
  • 从循环次数角度分析
    • do-while循环至少执行一次循环体语句。
    • for和while循环先判断循环条件语句是否成立,然后决定是否执行循环体。
  • 如何选择
    • 遍历有明显的循环次数(范围)的需求,选择for循环
    • 遍历没有明显的循环次数(范围)的需求,选择while循环
    • 如果循环体语句块至少执行一次,可以考虑使用do-while循环
    • 本质上:三种循环之间完全可以互相转换,都能实现循环的功能

3.5 “无限”循环

类似:死循环

3.5.1 基本语法

语法格式:

  • 最简单”无限”循环格式:while(true) , for(;;)

适用场景:

  • 开发中,有时并不确定需要循环多少次,需要根据循环体内部某些条件,来控制循环的结束(使用break)。
  • 如果此循环结构不能终止,则构成了死循环!开发中要避免出现死循环。

3.5.2 应用举例

案例1:实现爱你到永远…

1
2
3
4
5
6
7
8
public class EndlessFor1 {
public static void main(String[] args) {
for (;;){
System.out.println("我爱你!");
}
// System.out.println("end");//永远无法到达的语句,编译报错
}
}
1
2
3
4
5
6
7
public class EndlessFor2 {
public static void main(String[] args) {
for (; true;){ //条件永远成立,死循环
System.out.println("我爱你!");
}
}
}
1
2
3
4
5
6
7
public class EndlessFor3 {
public static void main(String[] args) {
for (int i=1; i<=10; ){ //循环变量没有修改,条件永远成立,死循环
System.out.println("我爱你!");
}
}
}

思考:如下代码执行效果

1
2
3
4
5
6
7
public class EndlessFor4 {
public static void main(String[] args) {
for (int i=1; i>=10; ){ //一次都不执行
System.out.println("我爱你!");
}
}
}

案例2:从键盘读入个数不确定的整数,并判断读入的正数和负数的个数,输入为0时结束程序。

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
import java.util.Scanner;

class PositiveNegative {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);

int positiveNumber = 0;//统计正数的个数
int negativeNumber = 0;//统计负数的个数
for(;;){ //while(true){
System.out.println("请输入一个整数:(输入为0时结束程序)");
int num = scanner.nextInt();
if(num > 0){
positiveNumber++;
}else if(num < 0){
negativeNumber++;
}else{
System.out.println("程序结束");
break;
}
}
System.out.println("正数的个数为:"+ positiveNumber);
System.out.println("负数的个数为:"+ negativeNumber);

scanner.close();
}
}

3.6 嵌套循环(或多重循环)

3.6.1 使用说明

  • 所谓嵌套循环,是指一个循环结构A的循环体是另一个循环结构B。比如,for循环里面还有一个for循环,就是嵌套循环。其中,for ,while ,do-while均可以作为外层循环或内层循环。
    • 外层循环:循环结构A
    • 内层循环:循环结构B
  • 实质上,嵌套循环就是把内层循环当成外层循环的循环体。只有当内层循环的循环条件为false时,才会完全跳出内层循环,才可结束外层的当次循环,开始下一次的外层循环。
  • 设外层循环次数为m次,内层为n次,则内层循环体实际上需要执行m*n次。
  • 技巧:从二维图形的角度看,外层循环控制行数,内层循环控制列数
  • 开发经验:实际开发中,我们最多见到的嵌套循环是两层。一般不会出现超过三层的嵌套循环。如果将要出现,一定要停下来重新梳理业务逻辑,重新思考算法的实现,控制在三层以内。否则,可读性会很差。

例如:两个for嵌套循环格式

1
2
3
4
5
6
7
for(初始化语句①; 循环条件语句②; 迭代语句⑦) {
for(初始化语句③; 循环条件语句④; 迭代语句⑥) {
循环体语句⑤;
}
}

//执行过程:① - ② - ③ - ④ - ⑤ - ⑥ - ④ - ⑤ - ⑥ - ... - ④ - ⑦ - ② - ③ - ④ - ⑤ - ⑥ - ④..

执行特点:外层循环执行一次,内层循环执行一轮。

3.6.2 应用举例

案例1:打印5行6个*

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class ForForTest1 {
public static void main(String[] args) {
/*

******
******
******
******
******

*/

for(int j = 1;j <= 5;j++){

for(int i = 1;i <= 6;i++){
System.out.print("*");
}

System.out.println();
}
}
}

案例2:打印5行直角三角形

1
2
3
4
5
*
**
***
****
*****
1
2
3
4
5
6
7
8
9
10
public class ForForTest2 {
public static void main(String[] args){
for (int i = 1; i <= 5; i++) {
for (int j = 1; j <= i; j++) {
System.out.print("*");
}
System.out.println();
}
}
}

案例3:打印5行倒直角三角形

1
2
3
4
5
*****
****
***
**
*
1
2
3
4
5
6
7
8
9
10
11
12
public class ForForTest3 {
public static void main(String[] args){
for(int i = 1;i <= 5;i++){
for(int j = 1;j <= 6 - i;j++){
System.out.print("*");

}
System.out.println();

}
}
}

案例4:打印”菱形”形状的图案

1
2
3
4
5
6
7
8
9
        * 
* * *
* * * * *
* * * * * * *
* * * * * * * * *
* * * * * * *
* * * * *
* * *
*
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
public class ForForTest4 {

public static void main(String[] args) {
/*
上半部分 i m(表示-的个数) n(表示*的个数)关系式:2*i + m = 10 --> m = 10 - 2*i
--------* 1 8 1 n = 2 * i - 1
------* * * 2 6 3
----* * * * * 3 4 5
--* * * * * * * 4 2 7
* * * * * * * * * 5 0 9

下半部分 i m n 关系式: m = 2 * i
--* * * * * * * 1 2 7 n = 9 - 2 * i
----* * * * * 2 4 5
------* * * 3 6 3
--------* 4 8 1

*/
//上半部分
for (int i = 1; i <= 5; i++) {
//-
for (int j = 1; j <= 10 - 2 * i; j++) {
System.out.print(" ");
}
//*
for (int k = 1; k <= 2 * i - 1; k++) {
System.out.print("* ");
}
System.out.println();
}
//下半部分
for (int i = 1; i <= 4; i++) {
//-
for (int j = 1; j <= 2 * i; j++) {
System.out.print(" ");
}

//*
for (int k = 1; k <= 9 - 2 * i; k++) {
System.out.print("* ");
}
System.out.println();
}
}

}

案例5:九九乘法表

image-20221113193013204

1
2
3
4
5
6
7
8
9
10
public class ForForTest5 {
public static void main(String[] args) {
for (int i = 1; i <= 9; i++) {
for (int j = 1; j <= i; j++) {
System.out.print(i + "*" + j + "=" + (i * j) + "\t");
}
System.out.println();
}
}
}

3.6.3 练习

练习1:将一天中的时间打印到控制台

1
2
3
4
5
6
7
8
9
public class ForForDemo {
public static void main (String[] args) {
for (int hour = 0;hour < 24 ;hour++ ) {
for (int min = 0; min < 60 ; min++) {
System.out.println(hour + "时" + min +"分");
}
}
}
}

4. 关键字break和continue的使用

4.1 break和continue的说明

1
2
3
4
5
6
			  适用范围			      在循环结构中使用的作用						相同点

break switch-case
循环结构 一旦执行,就结束(或跳出)当前循环结构 此关键字的后面,不能声明语句

continue 循环结构 一旦执行,就结束(或跳出)当次循环结构 此关键字的后面,不能声明语句

此外,很多语言都有goto语句,goto语句可以随意将控制转移到程序中的任意一条语句上,然后执行它,但使程序容易出错。Java中的break和continue是不同于goto的。

4.2 应用举例

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
class BreakContinueTest1 {
public static void main(String[] args) {

for(int i = 1;i <= 10;i++){

if(i % 4 == 0){
//break;//123
continue;//123567910
//如下的语句不可能被执行,编译不通过
//System.out.println("今晚迪丽热巴要约我吃饭");
}

System.out.print(i);
}

System.out.println("####");

//嵌套循环中的使用
for(int i = 1;i <= 4;i++){

for(int j = 1;j <= 10;j++){
if(j % 4 == 0){
//break; //结束的是包裹break关键字的最近的一层循环!
continue;//结束的是包裹break关键字的最近的一层循环的当次!
}
System.out.print(j);
}
System.out.println();
}

}
}

4.3 带标签的使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
break语句用于终止某个语句块的执行
{ ……
break;
……
}

break语句出现在多层嵌套的语句块中时,可以通过标签指明要终止的是哪一层语句块
label1: { ……
label2: { ……
label3: { ……
break label2;
……
}
}
}
  • continue语句出现在多层嵌套的循环语句体中时,也可以通过标签指明要跳过的是哪一层循环。

  • 标号语句必须紧接在循环的头部。标号语句不能用在非循环语句的前面。

  • 举例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class BreakContinueTest2 {
public static void main(String[] args) {
l:for(int i = 1;i <= 4;i++){

for(int j = 1;j <= 10;j++){
if(j % 4 == 0){
//break l;
continue l;
}
System.out.print(j);
}
System.out.println();
}
}
}

4.4 经典案例

题目:找出100以内所有的素数(质数)?100000以内的呢?

目的:不同的代码的实现方式,可以效率差别很大。

分析:素数(质数):只能被1和它本身整除的自然数。 —> 从2开始,到这个数-1为止,此范围内没有这个数的约数。则此数是一个质数。
比如:2、3、5、7、11、13、17、19、23、…

实现方式1:

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
class PrimeNumberTest {
public static void main(String[] args) {


//boolean isFlag = true; //用于标识i是否被除尽过

long start = System.currentTimeMillis(); //记录当前时间距离1970-1-1 00:00:00的毫秒数

int count = 0;//记录质数的个数


for(int i = 2;i <= 100000;i++){ //i

boolean isFlag = true; //用于标识i是否被除尽过

for(int j = 2;j <= i - 1;j++){

if(i % j == 0){ //表明i有约数
isFlag = false;
}

}

//判断i是否是质数
if(isFlag){ //如果isFlag变量没有给修改过值,就意味着i没有被j除尽过。则i是一个质数
//System.out.println(i);
count++;
}

//重置isFlag
//isFlag = true;

}

long end = System.currentTimeMillis();
System.out.println("质数的个数为:" + count);
System.out.println("执行此程序花费的毫秒数为:" + (end - start)); //16628

}
}

实现方式2:针对实现方式1进行优化

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
class PrimeNumberTest1 {
public static void main(String[] args) {

long start = System.currentTimeMillis(); //记录当前时间距离1970-1-1 00:00:00的毫秒数

int count = 0;//记录质数的个数

for(int i = 2;i <= 100000;i++){ //i

boolean isFlag = true; //用于标识i是否被除尽过

for(int j = 2;j <= Math.sqrt(i);j++){ //优化2:将循环条件中的i改为Math.sqrt(i)

if(i % j == 0){ //表明i有约数
isFlag = false;
break;//优化1:主要针对非质数起作用
}

}

//判断i是否是质数
if(isFlag){ //如果isFlag变量没有给修改过值,就意味着i没有被j除尽过。则i是一个质数
//System.out.println(i);
count++;
}

}

long end = System.currentTimeMillis();
System.out.println("质数的个数为:" + count);
System.out.println("执行此程序花费的毫秒数为:" + (end - start));//1062

}
}

实现方式3(选做):使用continue + 标签

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
class PrimeNumberTest2 {
public static void main(String[] args) {

long start = System.currentTimeMillis(); //记录当前时间距离1970-1-1 00:00:00的毫秒数

int count = 0;//记录质数的个数

label:for(int i = 2;i <= 100000;i++){ //i

for(int j = 2;j <= Math.sqrt(i);j++){ //优化2:将循环条件中的i改为Math.sqrt(i)

if(i % j == 0){ //表明i有约数
continue label;
}

}
//一旦程序能执行到此位置,说明i就是一个质数
System.out.println(i);
count++;
}


long end = System.currentTimeMillis();
System.out.println("质数的个数为:" + count);
System.out.println("执行此程序花费的毫秒数为:" + (end - start));//1062

}
}

4.5 练习

练习1:

1
2
3
生成 1-100 之间的随机数,直到生成了 97 这个数,看看一共用了几次?

提示:使用 (int)(Math.random() * 100) + 1
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class NumberGuessTest {
public static void main(String[] args) {
int count = 0;//记录循环的次数(或生成随机数进行比较的次数)
while(true){
int random = (int)(Math.random() * 100) + 1;
count++;
if(random == 97){
break;
}
}

System.out.println("直到生成随机数97,一共比较了" + count + "次");

}
}

5. Scanner:键盘输入功能的实现

  • 如何从键盘获取不同类型(基本数据类型、String类型)的变量:使用Scanner类。

  • 键盘输入代码的四个步骤:

    1. 导包:import java.util.Scanner;
    2. 创建Scanner类型的对象:Scanner scan = new Scanner(System.in);
    3. 调用Scanner类的相关方法(next() / nextXxx()),来获取指定类型的变量
    4. 释放资源:scan.close();
  • 注意:需要根据相应的方法,来输入指定类型的值。如果输入的数据类型与要求的类型不匹配时,会报异常导致程序终止。

5.1 各种类型的数据输入

案例:小明注册某交友网站,要求录入个人相关信息。如下:

请输入你的网名、你的年龄、你的体重、你是否单身、你的性别等情况。

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
//① 导包
import java.util.Scanner;

public class ScannerTest1 {

public static void main(String[] args) {
//② 创建Scanner的对象
//Scanner是一个引用数据类型,它的全名称是java.util.Scanner
//scanner就是一个引用数据类型的变量了,赋给它的值是一个对象(对象的概念我们后面学习,暂时先这么叫)
//new Scanner(System.in)是一个new表达式,该表达式的结果是一个对象
//引用数据类型 变量 = 对象;
//这个等式的意思可以理解为用一个引用数据类型的变量代表一个对象,所以这个变量的名称又称为对象名
//我们也把scanner变量叫做scanner对象
Scanner scanner = new Scanner(System.in);//System.in默认代表键盘输入

Scanner input=new Scanner(System.in);
//网名 string
String name=input.next();
//年龄 int
int age=input.nextInt();
//体重 double
double weight=input.nextDouble();
//单身 string
String ismarried=input.next();
//性别 string
String sex=input.next();
System.out.println("网名:" + name + "\n年龄:" + age + "\n体重:" + weight +"\n单身:" + ismarried + "\n性别:" + sex);

//④ 关闭资源
scanner.close();
}
}

5.2 练习

练习1:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
大家都知道,男大当婚,女大当嫁。那么女方家长要嫁女儿,当然要提出一定的条件:高:180cm以上;富:财富1千万以上;帅:是。

如果这三个条件同时满足,则:“我一定要嫁给他!!!”
如果三个条件有为真的情况,则:“嫁吧,比上不足,比下有余。”
如果三个条件都不满足,则:“不嫁!”

提示:
System.out.println(“身高: (cm));
scanner.nextInt();

System.out.println(“财富: (千万));
scanner.nextDouble();

System.out.println(“帅否: (true/false));
scanner.nextBoolean();



System.out.println(“帅否: (是/否));
scanner.next(); "是".equals(str)
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
import java.util.Scanner;

class ScannerExer1 {
public static void main(String[] args) {

Scanner scan = new Scanner(System.in);

System.out.println("请输入你的身高:(cm)");
int height = scan.nextInt();

System.out.println("请输入你的财富:(以千万为单位)");
double wealth = scan.nextDouble();

/*

方式1:关于是否帅问题,我们使用boolean类型接收

System.out.println("帅否?(true/false)");
boolean isHandsome = scan.nextBoolean();

//判断
if(height >= 180 && wealth >= 1.0 && isHandsome){ //不建议isHandsome == true
System.out.println("我一定要嫁给他!!!");
}else if(height >= 180 || wealth >= 1.0 || isHandsome){
System.out.println("嫁吧,比上不足,比下有余。");
}else{
System.out.println("不嫁");
}

*/

//方式2:关于是否帅问题,我们使用String类型接收
System.out.println("帅否?(是/否)");
String isHandsome = scan.next();

//判断
if(height >= 180 && wealth >= 1.0 && isHandsome == "是"){ //知识点:判断两个字符串是否相等,使用String的equals()
System.out.println("我一定要嫁给他!!!");
}else if(height >= 180 || wealth >= 1.0 || isHandsome == "是"){
System.out.println("嫁吧,比上不足,比下有余。");
}else{
System.out.println("不嫁");
}

//关闭资源
scan.close();
}
}

练习2:

1
2
3
我家的狗5岁了,5岁的狗相当于人类多大呢?其实,狗的前两年每一年相当于人类的10.5岁,之后每增加一年就增加四岁。那么5岁的狗相当于人类多少年龄呢?应该是:10.5 + 10.5 + 4 + 4 + 4 = 33岁。

编写一个程序,获取用户输入的狗的年龄,通过程序显示其相当于人类的年龄。如果用户输入负数,请显示一个提示信息。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import java.util.Scanner;

class ScannerExer2 {
public static void main(String[] args) {

Scanner scan = new Scanner(System.in);

System.out.println("请输入狗狗的年龄:");
int dogAge = scan.nextInt();

//通过分支语句,判断狗狗相当于人的年龄
if(dogAge < 0){
System.out.println("你输入的狗狗的年龄不合法");
}else if(dogAge <= 2){
System.out.println("相当于人的年龄:" + (dogAge * 10.5));
}else{
System.out.println("相当于人的年龄:" + (2 * 10.5 + (dogAge - 2) * 4));
}

//关闭资源
scan.close();

}
}

6. 如何获取一个随机数(Math.random())

如何产生一个指定范围的随机整数?

1、Math类的random()的调用,会返回一个[0,1)范围的一个double型值

2、Math.random() * 100 —> [0,100)
(int)(Math.random() * 100) —> [0,99]
(int)(Math.random() * 100) + 5 —-> [5,104]

3、如何获取[a,b]范围内的随机整数呢?(int)(Math.random() * (b - a + 1)) + a

4、举例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class MathRandomTest {
public static void main(String[] args) {
double value1=Math.random(); // [0,1)随机一个double值
System.out.println(value1);

double value100d=Math.random()*100; // [0,100)随机一个double值
System.out.println(value100d);

int value100i=(int)(Math.random()*100); // [0,100)随机一个int值
System.out.println(value100i);

int a=4;
int b=10;
int valueab=(int)(Math.random()*(b-a+1))+a; // [a,b]范围内的随机整数
System.out.println(valueab);
}
}

第05章_数组


本章专题与脉络

第1阶段:Java基本语法-第05章

1. 数组的概述

1.1 为什么需要数组

需求分析1:

需要统计某公司50个员工的工资情况,例如计算平均工资、找到最高工资等。用之前知识,首先需要声明50个变量来分别记录每位员工的工资,这样会很麻烦。因此我们可以将所有的数据全部存储到一个容器中统一管理,并使用容器进行计算。

需求分析2:

snipaste_20220317_000101

容器的概念:

  • 生活中的容器:水杯(装水等液体),衣柜(装衣服等物品),集装箱(装货物等)。
  • 程序中的容器:将多个数据存储到一起,每个数据称为该容器的元素。

1.2 数组的概念

  • 数组(Array),是多个相同类型数据按一定顺序排列的集合,并使用一个名字命名,并通过编号的方式对这些数据进行统一管理。

  • 数组中的概念

    • 数组名
    • 下标(或索引)
    • 元素
    • 数组的长度

image-20220317000952499

数组的特点:

  • 数组本身是引用数据类型,而数组中的元素可以是任何数据类型,包括基本数据类型和引用数据类型。
  • 创建数组对象会在内存中开辟一整块连续的空间。占据的空间的大小,取决于数组的长度和数组中元素的类型。
  • 数组中的元素在内存中是依次紧密排列的,有序的。
  • 数组,一旦初始化完成,其长度就是确定的。数组的长度一旦确定,就不能修改
  • 我们可以直接通过下标(或索引)的方式调用指定位置的元素,速度很快。
  • 数组名中引用的是这块连续空间的首地址。

1.3 数组的分类

1、按照元素类型分:

  • 基本数据类型元素的数组:每个元素位置存储基本数据类型的值
  • 引用数据类型元素的数组:每个元素位置存储对象(本质是存储对象的首地址)(在面向对象部分讲解)

2、按照维度分:

  • 一维数组:存储一组数据
  • 二维数组:存储多组数据,相当于二维表,一行代表一组数据,只是这里的二维表每一行长度不要求一样。

image-20211221164709624

2. 一维数组的使用

2.1 一维数组的声明

格式:

1
2
3
4
5
//推荐
元素的数据类型[] 一维数组的名称;

//不推荐
元素的数据类型 一维数组名[];

举例:

1
2
3
4
int[] arr;
int arr1[];
double[] arr2;
String[] arr3; //引用类型变量数组

数组的声明,需要明确:

(1)数组的维度:在Java中数组的符号是[],[]表示一维,[][]表示二维。

(2)数组的元素类型:即创建的数组容器可以存储什么数据类型的数据。元素的类型可以是任意的Java的数据类型。例如:int、String、Student等。

(3)数组名:就是代表某个数组的标识符,数组名其实也是变量名,按照变量的命名规范来命名。数组名是个引用数据类型的变量,因为它代表一组数据。

举例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class ArrayTest1 {
public static void main(String[] args) {
//比如,要存储一个小组的成绩
int[] scores;
int grades[];
// System.out.println(scores);//未初始化不能使用

//比如,要存储一组字母
char[] letters;

//比如,要存储一组姓名
String[] names;

//比如,要存储一组价格
double[] prices;

}
}

注意:Java语言中声明数组时不能指定其长度(数组中元素的个数)。 例如: int a[5]; //非法

2.2 一维数组的初始化

2.2.1 静态初始化

  • 如果数组变量的初始化和数组元素的赋值操作同时进行,那就称为静态初始化。

  • 静态初始化,本质是用静态数据(编译时已知)为数组初始化。此时数组的长度=sum(静态数据)。

  • 一维数组声明和静态初始化格式1:

    1
    2
    3
    4
    5
    6
    数据类型[] 数组名 = new 数据类型[]{元素1,元 素2,元素3,...};



    数据类型[] 数组名;
    数组名 = new 数据类型[]{元素1,元素2,元素3,...};
    • new:关键字,创建数组使用的关键字。因为数组本身是引用数据类型,所以要用new创建数组实体。

例如,定义存储1,2,3,4,5整数的数组容器。

1
2
3
4
int[] arr = new int[]{1,2,3,4,5};//正确
//或
int[] arr;
arr = new int[]{1,2,3,4,5};//正确
  • 一维数组声明和静态初始化格式2:
1
数据类型[] 数组名 = {元素1,元素2,元素3...};//必须在一个语句中完成,不能分成两个语句写

例如,定义存储1,2,3,4,5整数的数组容器

1
2
3
4
int[] arr = {1,2,3,4,5};//正确

int[] arr;
arr = {1,2,3,4,5};//错误

举例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class ArrayTest2 {
public static void main(String[] args) {
int[] arr = {1,2,3,4,5};//右边不需要写new int[]

int[] nums;
nums = new int[]{10,20,30,40}; //声明和初始化在两个语句完成,就不能使用new int[]

char[] word = {'h','e','l','l','o'};

String[] heros = {"袁隆平","邓稼先","钱学森"};

System.out.println("arr数组:" + arr);//arr数组:[I@1b6d3586
System.out.println("nums数组:" + nums);//nums数组:[I@4554617c
System.out.println("word数组:" + word);//word数组:[C@74a14482
System.out.println("heros数组:" + heros);//heros数组:[Ljava.lang.String;@1540e19d
}
}

2.2.2 动态初始化

数组变量的初始化和数组元素的赋值操作分开进行,即为动态初始化。

动态初始化中,只确定了元素的个数(即数组的长度),而元素值此时只是默认值,还并未真正赋自己期望的值。真正期望的数据需要后续单独一个一个赋值。

格式:

1
2
3
4
5
6
数组存储的元素的数据类型[] 数组名字 = new 数组存储的元素的数据类型[长度];



数组存储的数据类型[] 数组名字;
数组名字 = new 数组存储的数据类型[长度];
  • [长度]:数组的长度,表示数组容器中可以最多存储多少个元素。

  • 注意:数组有定长特性,长度一旦指定,不可更改。和水杯道理相同,买了一个2升的水杯,总容量就是2升是固定的。

举例1:正确写法

1
2
3
4
int[] arr = new int[5];

int[] arr;
arr = new int[5];

举例2:错误写法

1
int[] arr = new int[5]{1,2,3,4,5};//错误的,后面有{}指定元素列表,就不需要在[]中指定元素个数了。

2.3 一维数组的使用

2.3.1 数组的长度

  • 数组的元素总个数,即数组的长度
  • 每个数组都有一个属性length指明它的长度,例如:arr.length 指明数组arr的长度(即元素个数)
  • 每个数组都具有长度,而且一旦初始化,其长度就是确定,且是不可变的。

2.3.2 数组元素的引用

如何表示数组中的一个元素?

每一个存储到数组的元素,都会自动的拥有一个编号,从0开始,这个自动编号称为数组索引(index)或下标,可以通过数组的索引/下标访问到数组中的元素。

1
数组名[索引/下标]

数组的下标范围?

Java中数组的下标从[0]开始,下标范围是[0, 数组的长度-1],即[0, 数组名.length-1]

数组元素下标可以是整型常量或整型表达式。如a[3] , b[i] , c[6*i];

举例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class ArrayTest3 {
public static void main(String[] args) {
int[] arr = {1,2,3,4,5};

System.out.println("arr数组的长度:" + arr.length);
System.out.println("arr数组的第1个元素:" + arr[0]);//下标从0开始
System.out.println("arr数组的第2个元素:" + arr[1]);
System.out.println("arr数组的第3个元素:" + arr[2]);
System.out.println("arr数组的第4个元素:" + arr[3]);
System.out.println("arr数组的第5个元素:" + arr[4]);

//修改第1个元素的值
//此处arr[0]相当于一个int类型的变量
arr[0] = 100;
System.out.println("arr数组的第1个元素:" + arr[0]);
}
}

2.4 一维数组的遍历

将数组中的每个元素分别获取出来,就是遍历。for循环与数组的遍历是绝配。

举例1

1
2
3
4
5
6
7
8
9
10
11
12
13
public class ArrayTest4 {
public static void main(String[] args) {
int[] arr = new int[]{1,2,3,4,5};
//打印数组的属性,输出结果是5
System.out.println("数组的长度:" + arr.length);

//遍历输出数组中的元素
System.out.println("数组的元素有:");
for(int i=0; i<arr.length; i++){
System.out.println(arr[i]);
}
}
}

举例2

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
public class ArrayTest5 {
public static void main(String[] args) {
int[] arr = new int[5];

System.out.println("arr数组的长度:" + arr.length);
System.out.print("存储数据到arr数组之前:[");
for (int i = 0; i < arr.length; i++) {
if(i==0){
System.out.print(arr[i]);
}else{
System.out.print("," + arr[i]);
}
}
System.out.println("]");

//初始化
/*
arr[0] = 2;
arr[1] = 4;
arr[2] = 6;
arr[3] = 8;
arr[4] = 10;
*/

for (int i = 0; i < arr.length; i++) {
arr[i] = (i+1) * 2;
}

System.out.print("存储数据到arr数组之后:[");
for (int i = 0; i < arr.length; i++) {
if(i==0){
System.out.print(arr[i]);
}else{
System.out.print("," + arr[i]);
}
}
System.out.println("]");
}
}

2.5 数组元素的默认值

数组是引用类型,当我们使用动态初始化方式创建数组时,元素值只是默认值。例如:

1
2
3
4
5
6
public class ArrayTest6 {
public static void main(String argv[]){
int a[]= new int[5];
System.out.println(a[3]); //a[3]的默认值为0
}
}

对于基本数据类型而言,默认初始化值各有不同。

对于引用数据类型而言,默认初始化值为null(注意与0不同!)

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
public class ArrayTest7 {
public static void main(String[] args) {
//存储26个字母
char[] letters = new char[26];
System.out.println("letters数组的长度:" + letters.length);
System.out.print("存储字母到letters数组之前:[");
for (int i = 0; i < letters.length; i++) {
if(i==0){
System.out.print(letters[i]);
}else{
System.out.print("," + letters[i]);
}
}
System.out.println("]");

//存储5个姓名
String[] names = new String[5];
System.out.println("names数组的长度:" + names.length);
System.out.print("存储姓名到names数组之前:[");
for (int i = 0; i < names.length; i++) {
if(i==0){
System.out.print(names[i]);
}else{
System.out.print("," + names[i]);
}
}
System.out.println("]");
}
}

3. 一维数组内存分析

3.1 Java虚拟机的内存划分

为了提高运算效率,就对空间进行了不同区域的划分,因为每一片区域都有特定的处理数据方式和内存管理方式。

JVM架构-简图

区域名称 作用
虚拟机栈 用于存储正在执行的每个Java方法的局部变量表等。局部变量表存放了编译期可知长度
的各种基本数据类型、对象引用,方法执行完,自动释放。
堆内存 存储对象(包括数组对象),new来创建的,都存储在堆内存
方法区 存储已被虚拟机加载的类信息、常量、(静态变量)、即时编译器编译后的代码等数据。
本地方法栈 当程序中调用了native的本地方法时,本地方法执行期间的内存区域
程序计数器 程序计数器是CPU中的寄存器,它包含每一个线程下一条要执行的指令的地址

3.2 一维数组在内存中的存储

1、一个一维数组内存图

1
2
3
4
public static void main(String[] args) {
int[] arr = new int[3];
System.out.println(arr);//[I@5f150435
}

2、数组下标为什么是0开始

因为第一个元素距离数组首地址间隔0个单元格。

3、两个一维数组内存图

两个数组独立

1
2
3
4
5
6
public static void main(String[] args) {
int[] arr = new int[3];
int[] arr2 = new int[2];
System.out.println(arr);
System.out.println(arr2);
}

4、两个变量指向一个一维数组

两个数组变量本质上代表同一个数组。

1
2
3
4
5
6
7
8
9
10
11
12
public static void main(String[] args) {
// 定义数组,存储3个元素
int[] arr = new int[3];
//数组索引进行赋值
arr[0] = 5;
arr[1] = 6;
arr[2] = 7
//定义数组变量arr2,将arr的地址赋值给arr2
int[] arr2 = arr;
arr2[1] = 9;
System.out.println(arr[1]); // 输出9
}

4. 一维数组的应用

案例1:升景坊单间短期出租4个月,550元/月(水电煤公摊,网费35元/月),空调、卫生间、厨房齐全。屋内均是IT行业人士,喜欢安静。所以要求来租者最好是同行或者刚毕业的年轻人,爱干净、安静。

1
2
3
4
5
6
7
8
9
10
11
public class ArrayTest {
public static void main(String[] args) {
int[] arr = new int[]{8,2,1,0,3};
int[] index = new int[]{2,0,3,2,4,0,1,3,2,3,3};
String tel = "";
for(int i = 0;i < index.length;i++){
tel += arr[index[i]];
}
System.out.println("联系方式:" + tel);
}
}

案例2:输出英文星期几

用一个数组,保存星期一到星期天的7个英语单词,从键盘输入1-7,显示对应的单词
{“Monday”,”Tuesday”,”Wednesday”,”Thursday”,”Friday”,”Saturday”,”Sunday”}

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
import java.util.Scanner;

/**
* @author 尚硅谷-宋红康
* @create 14:37
*/
public class WeekArrayTest {
public static void main(String[] args) {

//1. 声明并初始化星期的数组
String[] weeks = {"Monday","Tuesday","Wednesday","Thursday","Friday","Saturday","Sunday"};

//2. 使用Scanner从键盘获取1-7范围的整数
Scanner scanner = new Scanner(System.in);
System.out.println("请输入[1-7]范围的整数:");
int number = scanner.nextInt();

if(number < 1 || number > 7){
System.out.println("你输入的输入非法");
}else{

//3. 根据输入的整数,到数组中相应的索引位置获取指定的元素(即:星期几)
System.out.println("对应的星期为:" + weeks[number - 1]);

}

scanner.close();

}
}

案例3:从键盘读入学生成绩,找出最高分,并输出学生成绩等级。

  • 成绩>=最高分-10 等级为’A’

  • 成绩>=最高分-20 等级为’B’

  • 成绩>=最高分-30 等级为’C’

  • 其余 等级为’D’

提示:先读入学生人数,根据人数创建int数组,存放学生成绩。

image-20220317004637748
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
/**
* @author 尚硅谷-宋红康
* @create 14:55
*/
public class ScoreTest1 {
public static void main(String[] args) {

//1. 根据提示,获取学生人数
System.out.print("请输入学生人数:");
Scanner scanner = new Scanner(System.in);
int count = scanner.nextInt();

//2. 根据学生人数,创建指定长度的数组 (使用动态初始化)
int[] scores = new int[count];

//3. 使用循环,依次给数组的元素赋值
int maxScore = 0; //记录最高分
System.out.println("请输入" + count + "个成绩");
for (int i = 0; i < scores.length; i++) {
scores[i] = scanner.nextInt();
//4. 获取数组中元素的最大值,即为最高分
if(maxScore < scores[i]){
maxScore = scores[i];
}
}

System.out.println("最高分是:" + maxScore);

//5. 遍历数组元素,输出各自的分数,并根据其分数与最高分的差值,获取各自的等级
char grade;
for (int i = 0; i < scores.length; i++) {

if(scores[i] >= maxScore - 10){
grade = 'A';
}else if(scores[i] >= maxScore - 20){
grade = 'B';
}else if(scores[i] >= maxScore - 30){
grade = 'C';
}else{
grade = 'D';
}
System.out.println("student " + i + " socre is " + scores[i] + ", grade is " + grade);
}
//关闭资源
scanner.close();

}
}

5. 多维数组的使用

5.1 概述

  • Java 语言里提供了支持多维数组的语法。
  • 如果说可以把一维数组当成几何中的线性图形,那么二维数组就相当于是一个表格,像Excel中的表格、围棋棋盘一样。

    image-20220317004810263
  • 应用举例1:

    某公司2022年全年各个月份的销售额进行登记。按月份存储,可以使用一维数组。如下:

    1
    int[] monthData = new int[]{23,43,22,34,55,65,44,67,45,78,67,66};

    如果改写为按季度为单位存储怎么办呢?

    1
    int[][] quarterData = new int[][]{{23,43,22},{34,55,65},{44,67,45},{78,67,66}};
  • 应用举例2:

高一年级三个班级均由多个学生姓名构成一个个数组。如下:

1
2
3
4
5
String[] class1 = new String[]{"段誉","令狐冲","任我行"};

String[] class2 = new String[]{"张三丰","周芷若"};

String[] class3 = new String[]{"赵敏","张无忌","韦小宝","杨过"};

那从整个年级看,我们可以声明一个二维数组。如下:

1
String[][] grade = new String[][]{{"段誉","令狐冲","任我行"},{"张三丰","周芷若"},{"赵敏","张无忌","韦小宝","杨过"}};
  • 应用举例3:

    image-20220516095701345

蓝框的几个元素,可以使用一维数组来存储。但现在发现每个元素下还有下拉框,其内部还有元素,那就需要使用二维数组来存储:

image-20220516095829526

  • 使用说明

    1561524724397

  • 对于二维数组的理解,可以看成是一维数组array1又作为另一个一维数组array2的元素而存在。
  • 其实,从数组底层的运行机制来看,其实没有多维数组。

5.2 声明与初始化

5.2.1 声明

二维数组声明的语法格式:

1
2
3
4
5
6
7
//推荐
元素的数据类型[][] 二维数组的名称;

//不推荐
元素的数据类型 二维数组名[][];
//不推荐
元素的数据类型[] 二维数组名[];

例如:

1
2
3
4
5
6
7
8
9
public class Test20TwoDimensionalArrayDefine {
public static void main(String[] args) {
//存储多组成绩
int[][] grades;

//存储多组姓名
String[][] names;
}
}

面试:

1
2
int[] x, y[];
//x是一维数组,y是二维数组 相当于 int[]x int[] y[];

5.2.2 静态初始化

格式:

1
int[][] arr = new int[][]{{3,8,2},{2,7},{9,0,1,6}};

定义一个名称为arr的二维数组,二维数组中有三个一维数组

  • 每一个一维数组中具体元素也都已初始化
    • 第一个一维数组 arr[0] = {3,8,2};
    • 第二个一维数组 arr[1] = {2,7};
    • 第三个一维数组 arr[2] = {9,0,1,6};
  • 第三个一维数组的长度表示方式:arr[2].length;
  • 注意特殊写法情况:int[] x,y[]; x是一维数组,y是二维数组。
  • 举例1:
1
2
3
4
5
6
7
8
int[][] arr = {{1,2,3},{4,5,6},{7,8,9,10}};//声明与初始化必须在一句完成

int[][] arr = new int[][]{{1,2,3},{4,5,6},{7,8,9,10}};

int[][] arr;
arr = new int[][]{{1,2,3},{4,5,6},{7,8,9,10}};

arr = new int[3][3]{{1,2,3},{4,5,6},{7,8,9,10}};//错误,静态初始化右边new 数据类型[][]中不能写数字
  • 举例2:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class TwoDimensionalArrayInitialize {
public static void main(String[] args) {
//存储多组成绩
int[][] grades = {
{89,75,99,100},
{88,96,78,63,100,86},
{56,63,58},
{99,66,77,88}
};

//存储多组姓名
String[][] names = {
{"张三","李四", "王五", "赵六"},
{"刘备","关羽","张飞","诸葛亮","赵云","马超"},
{"曹丕","曹植","曹冲"},
{"孙权","周瑜","鲁肃","黄盖"}
};
}
}

5.2.3 动态初始化

如果二维数组的每一个数据,甚至是每一行的列数,需要后期单独确定,那么就只能使用动态初始化方式了。动态初始化方式分为两种格式:

格式1:规则二维表:每一行的列数是相同的

1
2
3
4
5
6
7
8
9
//(1)确定行数和列数
元素的数据类型[][] 二维数组名 = new 元素的数据类型[m][n];
//其中,m:表示这个二维数组有多少个一维数组。或者说一共二维表有几行
//其中,n:表示每一个一维数组的元素有多少个。或者说每一行共有一个单元格

//此时创建完数组,行数、列数确定,而且元素也都有默认值

//(2)再为元素赋新值
二维数组名[行下标][列下标] = 值;

举例:

1
int[][] arr = new int[3][2];
  • 定义了名称为arr的二维数组

  • 二维数组中有3个一维数组

  • 每一个一维数组中有2个元素

  • 一维数组的名称分别为arr[0], arr[1], arr[2]

  • 给第一个一维数组1脚标位赋值为78写法是:arr[0][1] = 78;

格式2:不规则:每一行的列数不一样

1
2
3
4
5
6
7
8
9
10
11
12
//(1)先确定总行数
元素的数据类型[][] 二维数组名 = new 元素的数据类型[总行数][]; //行一定知道 但是列不一定知道

//此时只是确定了总行数,每一行里面现在是null

//(2)再确定每一行的列数,创建每一行的一维数组
二维数组名[行下标] = new 元素的数据类型[该行的总列数];

//此时已经new完的行的元素就有默认值了,没有new的行还是null

//(3)再为元素赋值
二维数组名[行下标][列下标] = 值;

举例:

1
int[][] arr = new int[3][];
  • 二维数组中有3个一维数组。
  • 每个一维数组都是默认初始化值null (注意:区别于格式1)
  • 可以对这个三个一维数组分别进行初始化:arr[0] = new int[3]; arr[1] = new int[1]; arr[2] = new int[2];
  • 注:int[][]arr = new int[][3]; //非法

练习:

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
/*
1
2 2
3 3 3
4 4 4 4
5 5 5 5 5
*/
public class Test25DifferentElementCount {
public static void main(String[] args){
//1、声明一个二维数组,并且确定行数
//因为每一行的列数不同,这里无法直接确定列数
int[][] arr = new int[5][];

//2、确定每一行的列数
for(int i=0; i<arr.length; i++){
/*
arr[0] 的列数是1
arr[1] 的列数是2
arr[2] 的列数是3
arr[3] 的列数是4
arr[4] 的列数是5
*/
arr[i] = new int[i+1];
}

//3、确定元素的值
for(int i=0; i<arr.length; i++){
for(int j=0; j<arr[i].length; j++){
arr[i][j] = i+1;
}
}

//4、遍历显示
for(int i=0; i<arr.length; i++){
for(int j=0; j<arr[i].length; j++){
System.out.print(arr[i][j] + " ");
}
System.out.println();
}

}
}

5.3 数组的长度和角标

  • 二维数组的长度/行数:二维数组名.length
  • 二维数组的某一行:二维数组名[行下标],此时相当于获取其中一组数据。它本质上是一个一维数组。行下标的范围:[0, 二维数组名.length-1]。此时把二维数组看成一维数组的话,元素是行对象。
  • 某一行的列数:二维数组名[行下标].length,因为二维数组的每一行是一个一维数组。
  • 某一个元素:二维数组名[行下标][列下标],即先确定行/组,再确定列。
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
public class Test22TwoDimensionalArrayUse {
public static void main(String[] args){
//存储3个小组的学员的成绩,分开存储,使用二维数组。
/*
int[][] scores1;
int scores2[][];
int[] scores3[];*/

int[][] scores = {
{85,96,85,75},
{99,96,74,72,75},
{52,42,56,75}
};

System.out.println(scores);//[[I@15db9742
System.out.println("一共有" + scores.length +"组成绩.");

//[[:代表二维数组,I代表元素类型是int
System.out.println(scores[0]);//[I@6d06d69c
//[:代表一维数组,I代表元素类型是int
System.out.println(scores[1]);//[I@7852e922
System.out.println(scores[2]);//[I@4e25154f
//System.out.println(scores[3]);//ArrayIndexOutOfBoundsException: 3

System.out.println("第1组有" + scores[0].length +"个学员.");
System.out.println("第2组有" + scores[1].length +"个学员.");
System.out.println("第3组有" + scores[2].length +"个学员.");

System.out.println("第1组的每一个学员成绩如下:");
//第一行的元素
System.out.println(scores[0][0]);//85
System.out.println(scores[0][1]);//96
System.out.println(scores[0][2]);//85
System.out.println(scores[0][3]);//75
//System.out.println(scores[0][4]);//java.lang.ArrayIndexOutOfBoundsException: 4
}
}

5.4 二维数组的遍历

  • 格式:
1
2
3
4
5
6
for(int i=0; i<二维数组名.length; i++){ //二维数组对象.length
for(int j=0; j<二维数组名[i].length; j++){//二维数组行对象.length
System.out.print(二维数组名[i][j]);
}
System.out.println();
}
  • 举例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class Test23TwoDimensionalArrayIterate {
public static void main(String[] args) {
//存储3个小组的学员的成绩,分开存储,使用二维数组。
int[][] scores = {
{85,96,85,75},
{99,96,74,72,75},
{52,42,56,75}
};

System.out.println("一共有" + scores.length +"组成绩.");
for (int i = 0; i < scores.length; i++) {
System.out.print("第" + (i+1) +"组有" + scores[i].length + "个学员,成绩如下:");
for (int j = 0; j < scores[i].length; j++) {
System.out.print(scores[i][j]+"\t");
}
System.out.println();
}
}
}

5.5 内存解析

二维数组本质上是元素类型是一维数组的一维数组。

1
2
3
4
5
6
7
int[][] arr = {
{1},
{2,2},
{3,3,3},
{4,4,4,4},
{5,5,5,5,5}
};

1562112672215

1
2
3
4
5
6
7
8
9
//1、声明二维数组,并确定行数和列数
int[][] arr = new int[4][5];

//2、确定元素的值
for (int i = 0; i < arr.length; i++) {
for (int j = 0; j < arr.length; j++) {
arr[i][j] = i + 1;
}
}

1562113179785

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
//1、声明一个二维数组,并且确定行数
//因为每一行的列数不同,这里无法直接确定列数
int[][] arr = new int[5][];

//2、确定每一行的列数
for(int i=0; i<arr.length; i++){
/*
arr[0] 的列数是1
arr[1] 的列数是2
arr[2] 的列数是3
arr[3] 的列数是4
arr[4] 的列数是5
*/
arr[i] = new int[i+1];
}

//3、确定元素的值
for(int i=0; i<arr.length; i++){
for(int j=0; j<arr[i].length; j++){
arr[i][j] = i+1;
}
}

1562113981079

5.6 应用举例

案例1:获取arr数组中所有元素的和。

提示:使用for的嵌套循环即可。

image-20220317005436209

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class Exercise3 {
public static void main(String[] args){
int[][] arr=new int[][]{{3,5,8,0},{12,9,0,0},{7,0,6,4}};
int sum=0;
System.out.println(arr.length); //行的长度
System.out.println(arr[0].length); //列的长度
//两层循环
for(int i=0;i<arr.length;i++){
for(int j=0;j<arr[0].length;j++){
sum+=arr[i][j];
}
}
System.out.println(sum);
}
}

案例2:声明:int[] x,y[]; 在给x,y变量赋值以后,以下选项允许通过编译的是:

1
2
3
4
5
6
7
8
9
10
11
声明:int[] x,y[]; 在给x,y变量赋值以后,以下选项允许通过编译的是:
a) x[0] = y; //no
b) y[0] = x; //yes
c) y[0][0] = x; //no
d) x[0][0] = y; //no
e) y[0][0] = x[0]; //yes
f) x = y; //no

提示:
一维数组:int[] x 或者int x[]
二维数组:int[][] y 或者 int[] y[] 或者 int y[][]

案例3:使用二维数组打印一个 10 行杨辉三角。

提示:

  1. 第一行有 1 个元素, 第 n 行有 n 个元素

  2. 每一行的第一个元素和最后一个元素都是 1

  3. 从第三行开始, 对于非第一个元素和最后一个元素的元素。即:

    1
    yanghui[i][j] = yanghui[i-1][j-1] + yanghui[i-1][j];

image-20220317005549522

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
/**
* @author 尚硅谷-宋红康
* @create 10:11
*/
public class YangHuiTest {
public static void main(String[] args) {

int[][] arr = new int[10][];
for (int i = 0; i < arr.length; i++) {
arr[i] = new int[i+1]; //每一行都多一列
//每一行的第一个元素和对角线元素都是1
arr[i][0] = arr[i][i] = 1;
for (int j = 1; j < arr[i].length - 1; j++) {
arr[i][j] = arr[i - 1][j - 1] + arr[i - 1][j];
}
}

//输出二维数组
for (int i = 0; i < arr.length; i++) {
for (int j = 0; j < arr[i].length; j++) {
System.out.print(arr[i][j] + "\t");
}
System.out.println(); //每一行换行
}

}
}

6. 数组的常见算法

6.1 数值型数组特征值统计

  • 这里的特征值涉及到:平均值、最大值、最小值、总和等

举例1:数组统计:求总和、均值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class TestArrayElementSum {
public static void main(String[] args) {
int[] arr = {4,5,6,1,9};
//求总和、均值
int sum = 0;//因为0加上任何数都不影响结果
for(int i=0; i<arr.length; i++){
sum += arr[i];
}
double avg = (double)sum/arr.length;

System.out.println("sum = " + sum);
System.out.println("avg = " + avg);
}
}

举例2:求数组元素的总乘积

1
2
3
4
5
6
7
8
9
10
11
12
13
public class TestArrayElementMul {
public static void main(String[] args) {
int[] arr = {4,5,6,1,9};

//求总乘积
long result = 1;//因为1乘以任何数都不影响结果
for(int i=0; i<arr.length; i++){
result *= arr[i];
}

System.out.println("result = " + result);
}
}

举例3:求数组元素中偶数的个数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class TestArrayElementEvenCount {
public static void main(String[] args) {
int[] arr = {4,5,6,1,9};
//统计偶数个数
int evenCount = 0;
for(int i=0; i<arr.length; i++){
if(arr[i]%2==0){
evenCount++;
}
}

System.out.println("evenCount = " + evenCount);
}
}

举例4:求数组元素的最大值

1574577970893

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class TestArrayMax {
public static void main(String[] args) {
int[] arr = {4,5,6,1,9};
//找最大值
int max = arr[0];
for(int i=1; i<arr.length; i++){//此处i从1开始,是max不需要与arr[0]再比较一次了
if(arr[i] > max){
max = arr[i];
}
}

System.out.println("max = " + max);
}
}

举例5:找最值及其第一次出现的下标

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class TestMaxIndex {
public static void main(String[] args) {
int[] arr = {4,5,6,1,9};
//找最大值以及第一个最大值下标
int max = arr[0];
int index = 0;
for(int i=1; i<arr.length; i++){
if(arr[i] > max){
max = arr[i];
index = i;
}
}

System.out.println("max = " + max);
System.out.println("index = " + index);
}
}

举例6:找最值及其所有最值的下标

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class Test13AllMaxIndex {
public static void main(String[] args) {
int[] arr = {4,5,6,1,9,9,3};
//找最大值
int max = arr[0];
for(int i=1; i<arr.length; i++){
if(arr[i] > max){
max = arr[i];
}
}
System.out.println("最大值是:" + max);
System.out.print("最大值的下标有:");

//遍历数组,看哪些元素和最大值是一样的
for(int i=0; i<arr.length; i++){
if(max == arr[i]){
System.out.print(i+"\t");
}
}
System.out.println();
}
}

优化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class Test13AllMaxIndex2 {
public static void main(String[] args) {
int[] arr = {4,5,6,1,9,9,3};
//找最大值
int max = arr[0];
String index = "0";
for(int i=1; i<arr.length; i++){
if(arr[i] > max){
max = arr[i];
index = i + "";
}else if(arr[i] == max){
index += "," + i;
}
}

System.out.println("最大值是" + max);
System.out.println("最大值的下标是[" + index+"]");
}
}

举例7(难):输入一个整形数组,数组里有正数也有负数。数组中连续的一个或多个整数组成一个子数组,每个子数组都有一个和。求所有子数组的和的最大值。要求时间复杂度为O(n)。
例如:输入的数组为1, -2, 3, -10, -4, 7, 2, -5,和最大的子数组为3, 10, -4, 7, 2,因此输出为该子数组的和18。

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
public class Test5 {
public static void main(String[] args) {
int[] arr = new int[]{1, -2, 3, 10, -4, 7, 2, -5};
int i = getGreatestSum(arr);
System.out.println(i);
}

public static int getGreatestSum(int[] arr){
int greatestSum = 0;
if(arr == null || arr.length == 0){
return 0;
}
int temp = greatestSum;
for(int i = 0;i < arr.length;i++){
temp += arr[i];

if(temp < 0){
temp = 0;
}

if(temp > greatestSum){
greatestSum = temp;
}
}
if(greatestSum == 0){
greatestSum = arr[0];
for(int i = 1;i < arr.length;i++){
if(greatestSum < arr[i]){
greatestSum = arr[i];
}
}
}
return greatestSum;
}
}

举例8:评委打分

分析以下需求,并用代码实现:

(1)在编程竞赛中,有10位评委为参赛的选手打分,分数分别为:5,4,6,8,9,0,1,2,7,3

(2)求选手的最后得分(去掉一个最高分和一个最低分后其余8位评委打分的平均值)

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
/**
* @author 尚硅谷-宋红康
* @create 10:03
*/
public class ArrayExer {
public static void main(String[] args) {
int[] arr={5,4,6,8,9,0,1,2,7,3};
//最高分
int max=0;
for(int i=0;i<arr.length;i++){
if(arr[i]>max){
max=arr[i];
}
}
//最低分
int min=arr[0];
for(int i=0;i<arr.length;i++){
if(arr[i]<min)
min=arr[i];
}
//算平均分 [所有的-MAX-MIN再除以总数-2]
int average=0;
for(int i=0;i<arr.length;i++){
average+=arr[i];
}
System.out.println((double)(average-max-min)/(arr.length-2)); //要考虑最终结果可能是一个浮点数
}
}

6.2 数组元素的赋值与数组复制

举例1:杨辉三角(见二维数组课后案例)

举例2:使用简单数组

(1)创建一个名为ArrayTest的类,在main()方法中声明array1和array2两个变量,他们是int[]类型的数组。

(2)使用大括号{},把array1初始化为8个素数:2,3,5,7,11,13,17,19。

(3)显示array1的内容。

(4)赋值array2变量等于array1,修改array2中的偶索引元素,使其等于索引值(如array[0]=0,array[2]=2)。打印出array1。 array2 = array1;

思考:array1和array2是什么关系?

拓展:修改题目,实现array2对array1数组的复制

snipaste_20220317_225346snipaste_20220317_225359

举例3:一个数组,让数组的每个元素去除第一个元素,得到的商作为被除数所在位置的新值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class Test3 {
public static void main(String[] args) {
int[] arr = new int[]{12,43,65,3,-8,64,2};

// for(int i = 0;i < arr.length;i++){
// arr[i] = arr[i] / arr[0];
// }
for(int i = arr.length -1;i >= 0;i--){
arr[i] = arr[i] / arr[0];
}
//遍历arr
for(int i = 0;i < arr.length;i++){
System.out.print(arr[i] + " ");
}
}
}

举例4:创建一个长度为6的int型数组,要求数组元素的值都在1-30之间,且是随机赋值。同时,要求元素的值各不相同。

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
public class Test4 {
// 5-67 Math.random() * 63 + 5;
@Test
public void test1() {
int[] arr = new int[6];
for (int i = 0; i < arr.length; i++) {// [0,1) [0,30) [1,31)
arr[i] = (int) (Math.random() * 30) + 1;

boolean flag = false;
while (true) {
for (int j = 0; j < i; j++) {
if (arr[i] == arr[j]) {
flag = true;
break;
}
}
if (flag) {
arr[i] = (int) (Math.random() * 30) + 1;
flag = false;
continue;
}
break;
}
}

for (int i = 0; i < arr.length; i++) {
System.out.println(arr[i]);
}
}
//更优的方法
@Test
public void test2(){
int[] arr = new int[6];
for (int i = 0; i < arr.length; i++) {// [0,1) [0,30) [1,31)
arr[i] = (int) (Math.random() * 30) + 1;

for (int j = 0; j < i; j++) {
if (arr[i] == arr[j]) {
i--;
break;
}
}
}

for (int i = 0; i < arr.length; i++) {
System.out.println(arr[i]);
}
}
}

举例5:扑克牌

案例:遍历扑克牌

遍历扑克牌,效果如图所示:

1659199523932

提示:使用两个字符串数组,分别保存花色和点数,再用一个字符串数组保存最后的扑克牌。
String[] hua = {“黑桃”,”红桃”,”梅花”,”方片”};
String[] dian = {“A”,”2”,”3”,”4”,”5”,”6”,”7”,”8”,”9”,”10”,”J”,”Q”,”K”};

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
package com.atguigu3.common_algorithm.exer5;

/**
* @author 尚硅谷-宋红康
* @create 17:16
*/
public class ArrayExer05 {
public static void main(String[] args) {
String[] hua = {"黑桃","红桃","梅花","方片"};
String[] dian = {"A","2","3","4","5","6","7","8","9","10","J","Q","K"};


String[] pai = new String[hua.length * dian.length];
int k = 0;
for(int i = 0;i < hua.length;i++){
for(int j = 0;j < dian.length;j++){
pai[k++] = hua[i] + dian[j]; //添加花色和字母
}
}

for (int i = 0; i < pai.length; i++) {
System.out.print(pai[i] + " ");
if(i % 13 == 12){
System.out.println(); //12个为一行
}
}

}
}

拓展:在上述基础上,增加大王、小王。

举例6:回形数

从键盘输入一个整数(1~20) ,则以该数字为矩阵的大小,把1,2,3…n*n 的数字按照顺时针螺旋的形式填入其中。

例如: 输入数字2,则程序输出:
1 2
4 3

输入数字3,则程序输出:
1 2 3
8 9 4
7 6 5
输入数字4, 则程序输出:
1 2 3 4
12 13 14 5
11 16 15 6
10 9 8 7

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
56
57
58
59
60
61
62
63
64
65
66
//方式1
public class RectangleTest {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
System.out.println("输入一个数字");
int len = scanner.nextInt();
int[][] arr = new int[len][len];

int s = len * len;
/*
* k = 1:向右
* k = 2:向下
* k = 3:向左
* k = 4:向上
*/
int k = 1;
int i = 0,j = 0;
for(int m = 1;m <= s;m++){
if(k == 1){
if(j < len && arr[i][j] == 0){
arr[i][j++] = m;
}else{
k = 2;
i++;
j--;
m--;
}
}else if(k == 2){
if(i < len && arr[i][j] == 0){
arr[i++][j] = m;
}else{
k = 3;
i--;
j--;
m--;
}
}else if(k == 3){
if(j >= 0 && arr[i][j] == 0){
arr[i][j--] = m;
}else{
k = 4;
i--;
j++;
m--;
}
}else if(k == 4){
if(i >= 0 && arr[i][j] == 0){
arr[i--][j] = m;
}else{
k = 1;
i++;
j++;
m--;
}
}
}

//遍历
for(int m = 0;m < arr.length;m++){
for(int n = 0;n < arr[m].length;n++){
System.out.print(arr[m][n] + "\t");
}
System.out.println();
}
}
}
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
//方式2
/*
01 02 03 04 05 06 07
24 25 26 27 28 29 08
23 40 41 42 43 30 09
22 39 48 49 44 31 10
21 38 47 46 45 32 11
20 37 36 35 34 33 12
19 18 17 16 15 14 13
*/
public class RectangleTest1 {

public static void main(String[] args) {
int n = 7;
int[][] arr = new int[n][n];

int count = 0; //要显示的数据
int maxX = n-1; //x轴的最大下标
int maxY = n-1; //Y轴的最大下标
int minX = 0; //x轴的最小下标
int minY = 0; //Y轴的最小下标
while(minX<=maxX){
for(int x=minX;x<=maxX;x++) {
arr[minY][x] = ++count; //横着从左往右 01开始
}
minY++; //下一循环就是下一行的横
for(int y=minY;y<=maxY;y++) {
arr[y][maxX] = ++count; //竖着从上往下 08开始
}
maxX--; //往前挪一个
for(int x=maxX;x>=minX;x--) {
arr[maxY][x] = ++count; //横着从右往左 14开始
}
maxY--;
for(int y=maxY;y>=minY;y--) {
arr[y][minX] = ++count; //竖着从下往上 20开始
}
minX++; //下一个循环就是下一行
}


for(int i=0;i<arr.length;i++) {
for(int j=0;j<arr.length;j++) {
String space = (arr[i][j]+"").length()==1 ? "0":"";
System.out.print(space+arr[i][j]+" ");
}
System.out.println();
}
}
}

6.3 数组元素的反转

实现思想:数组对称位置的元素互换。

image-20221117195931777
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
public class TestArrayReverse1 {
public static void main(String[] args) {
int[] arr = {1,2,3,4,5};
System.out.println("反转之前:");
for (int i = 0; i < arr.length; i++) {
System.out.println(arr[i]);
}

//反转
/*
思路:首尾对应位置的元素交换
(1)确定交换几次
次数 = 数组.length / 2
(2)谁和谁交换
for(int i=0; i<次数; i++){
int temp = arr[i];
arr[i] = arr[arr.length-1-i];
arr[arr.length-1-i] = temp;
}
*/
for(int i=0; i<arr.length/2; i++){
int temp = arr[i];
arr[i] = arr[arr.length-1-i];
arr[arr.length-1-i] = temp;
}

System.out.println("反转之后:");
for (int i = 0; i < arr.length; i++) {
System.out.println(arr[i]);
}
}

}

1561469087319

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class TestArrayReverse2 {
public static void main(String[] args) {
int[] arr = {1,2,3,4,5,6,7,8,9};
System.out.println("反转之前:");
for (int i = 0; i < arr.length; i++) {
System.out.print(arr[i]+" ");
}
System.out.println();

for(int left=0,right=arr.length-1;left<=right;left++,right--){
//两个数做交换
int temp=0;
temp=arr[left];
arr[left]=arr[right];
arr[right]=temp;
}

System.out.println("反转之后:");
for (int i = 0; i < arr.length; i++) {
System.out.print(arr[i]+" ");
}
}
}

6.4 数组的扩容与缩容

数组的扩容

题目:现有数组 int[] arr = new int[]{1,2,3,4,5}; ,现将数组长度扩容1倍,并将10,20,30三个数据添加到arr数组中,如何操作?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class ArrTest1 {
public static void main(String[] args) {

int[] arr = new int[]{1,2,3,4,5};
int[] newArr = new int[arr.length << 1]; //位运算符相当于乘2

for(int i = 0;i < arr.length;i++){
newArr[i] = arr[i];
}

newArr[arr.length] = 10;
newArr[arr.length + 1] = 20;
newArr[arr.length + 2] = 30;

arr = newArr;

//遍历arr
for (int i = 0; i < arr.length; i++) {
System.out.println(arr[i]);
}
}
}

数组的缩容

题目:现有数组 int[] arr={1,2,3,4,5,6,7}。现需删除数组中索引为4的元素。

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
public class ArrTest2 {
public static void main(String[] args) {

int[] arr = {1, 2, 3, 4, 5, 6, 7};
//删除数组中索引为4的元素
int delIndex = 4;
//方案1:
/*//创建新数组
int[] newArr = new int[arr.length - 1];

for (int i = 0; i < delIndex; i++) {
newArr[i] = arr[i];
}
for (int i = delIndex + 1; i < arr.length; i++) {
newArr[i - 1] = arr[i];
}

arr = newArr;
for (int i = 0; i < arr.length; i++) {
System.out.println(arr[i]);
}*/

//方案2:
for(int i=delIndex-1;i<arr.length-1;i++){
System.out.println("本轮要替换的是"+arr[i]+" "+arr[i+1]);
arr[i]=arr[i+1];
}
//只遍历前6个
for (int i = 0; i < arr.length-1; i++) {
System.out.println(arr[i]);
}
}
}

6.5 数组的元素查找

1、顺序查找

顺序查找:挨个查看

要求:对数组元素的顺序没要求

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class TestArrayOrderSearch {
//查找value第一次在数组中出现的index
public static void main(String[] args){
int[] arr = {4,5,6,1,9};
int value = 1;
int index = -1;

for(int i=0; i<arr.length; i++){
if(arr[i] == value){
index = i;
break;
}
}

if(index==-1){
System.out.println(value + "不存在");
}else{
System.out.println(value + "的下标是" + index);
}
}
}

2、二分查找

举例:

image-20220317230955644

实现步骤:

image-20220623210601915
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
//二分法查找:要求此数组必须是有序的。
int[] arr3 = new int[]{-99,-54,-2,0,2,33,43,256,999};
boolean isFlag = true;
int value = 256;
//int value = 25;
int head = 0;//首索引位置
int end = arr3.length - 1;//尾索引位置
while(head <= end){
int middle = (head + end) / 2;
if(arr3[middle] == value){
System.out.println("找到指定的元素,索引为:" + middle);
isFlag = false;
break;
}else if(arr3[middle] > value){
end = middle - 1;
}else{//arr3[middle] < value
head = middle + 1;
}
}

if(isFlag){
System.out.println("未找打指定的元素");
}

6.6 数组元素排序

6.6.1 算法概述

  • 定义

    • 排序:假设含有n个记录的序列为{R1,R2,…,Rn},其相应的关键字序列为{K1,K2,…,Kn}。将这些记录重新排序为{Ri1,Ri2,…,Rin},使得相应的关键字值满足条Ki1<=Ki2<=…<=Kin,这样的一种操作称为排序。
    • 通常来说,排序的目的是快速查找。
  • 衡量排序算法的优劣:

    • 时间复杂度:分析关键字的比较次数和记录的移动次数

    • 常见的算法时间复杂度由小到大依次为:Ο(1)<Ο(log2n)<Ο(n)<Ο(nlog2n)<Ο(n2)<Ο(n3)<…<Ο(2n)<Ο(n!)<O(nn)

    • 空间复杂度:分析排序算法中需要多少辅助内存

      1
      一个算法的空间复杂度S(n)定义为该算法所耗费的存储空间,它也是问题规模n的函数。
    • 稳定性:若两个记录A和B的关键字值相等,但排序后A、B的先后次序保持不变,则称这种排序算法是稳定的。

      image-20211222113701365

6.6.2 排序算法概述

  • 排序算法分类:内部排序和外部排序

    • 内部排序:整个排序过程不需要借助于外部存储器(如磁盘等),所有排序操作都在内存中完成。
    • 外部排序:参与排序的数据非常多,数据量非常大,计算机无法把整个排序过程放在内存中完成,必须借助于外部存储器(如磁盘)。外部排序最常见的是多路归并排序。可以认为外部排序是由多次内部排序组成。
  • 十大内部排序算法

​ 数组的排序算法很多,实现方式各不相同,时间复杂度、空间复杂度、稳定性也各不相同:

image-20211222111142684

常见时间复杂度所消耗的时间从小到大排序:

O(1) < O(logn) < O(n) < O(nlogn) < O(n^2) < O(n^3) < O(2^n) < O(n!) < O(n^n)

注意,经常将以2为底n的对数简写成logn。

image-20220824003440106

6.6.3 冒泡排序(Bubble Sort)

image-20220516094637228

排序思想:

  1. 比较相邻的元素。如果第一个比第二个大(升序),就交换他们两个。

  2. 对每一对相邻元素作同样的工作,从开始第一对到结尾的最后一对。这步做完后,最后的元素会是最大的数。

  3. 针对所有的元素重复以上的步骤,除了最后一个。

  4. 持续每次对越来越少的元素重复上面的步骤,直到没有任何一对数字需要比较为止。

BubbleSort

动态演示:https://visualgo.net/zh/sorting

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
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
/*
1、冒泡排序(最经典)
思想:每一次比较“相邻(位置相邻)”元素,如果它们不符合目标顺序(例如:从小到大),
就交换它们,经过多轮比较,最终实现排序。
(例如:从小到大) 每一轮可以把最大的沉底,或最小的冒顶。

过程:arr{6,9,2,9,1} 目标:从小到大

第一轮:
第1次,arr[0]与arr[1],6>9不成立,满足目标要求,不交换
第2次,arr[1]与arr[2],9>2成立,不满足目标要求,交换arr[1]与arr[2] {6,2,9,9,1}
第3次,arr[2]与arr[3],9>9不成立,满足目标要求,不交换
第4次,arr[3]与arr[4],9>1成立,不满足目标要求,交换arr[3]与arr[4] {6,2,9,1,9}
第一轮所有元素{6,9,2,9,1}已经都参与了比较,结束。
第一轮的结果:第“一”最大值9沉底(本次是后面的9沉底),即到{6,2,9,1,9}元素的最右边

第二轮:
第1次,arr[0]与arr[1],6>2成立,不满足目标要求,交换arr[0]与arr[1] {2,6,9,1,9}
第2次,arr[1]与arr[2],6>9不成立,满足目标要求,不交换
第3次:arr[2]与arr[3],9>1成立,不满足目标要求,交换arr[2]与arr[3] {2,6,1,9,9}
第二轮未排序的所有元素 {6,2,9,1}已经都参与了比较,结束。
第二轮的结果:第“二”最大值9沉底(本次是前面的9沉底),即到{2,6,1,9}元素的最右边
第三轮:
第1次,arr[0]与arr[1],2>6不成立,满足目标要求,不交换
第2次,arr[1]与arr[2],6>1成立,不满足目标要求,交换arr[1]与arr[2] {2,1,6,9,9}
第三轮未排序的所有元素{2,6,1}已经都参与了比较,结束。
第三轮的结果:第三最大值6沉底,即到 {2,1,6}元素的最右边
第四轮:
第1次,arr[0]与arr[1],2>1成立,不满足目标要求,交换arr[0]与arr[1] {1,2,6,9,9}
第四轮未排序的所有元素{2,1}已经都参与了比较,结束。
第四轮的结果:第四最大值2沉底,即到{1,2}元素的最右边

*/
public class Test19BubbleSort{
public static void main(String[] args){
int[] arr = {6,9,2,9,1};

//目标:从小到大
//冒泡排序的轮数 = 元素的总个数 - 1
//轮数是多轮,每一轮比较的次数是多次,需要用到双重循环,即循环嵌套
//外循环控制 轮数,内循环控制每一轮的比较次数和过程
for(int i=1; i<arr.length; i++){ //循环次数是arr.length-1次/轮
/*
假设arr.length=5
i=1,第1轮,比较4次
arr[0]与arr[1]
arr[1]与arr[2]
arr[2]与arr[3]
arr[3]与arr[4]

arr[j]与arr[j+1],int j=0;j<4; j++

i=2,第2轮,比较3次
arr[0]与arr[1]
arr[1]与arr[2]
arr[2]与arr[3]

arr[j]与arr[j+1],int j=0;j<3; j++

i=3,第3轮,比较2次
arr[0]与arr[1]
arr[1]与arr[2]

arr[j]与arr[j+1],int j=0;j<2; j++
i=4,第4轮,比较1次
arr[0]与arr[1]

arr[j]与arr[j+1],int j=0;j<1; j++

int j=0; j<arr.length-i; j++
*/
for(int j=0; j<arr.length-i; j++){
//希望的是arr[j] < arr[j+1]
if(arr[j] > arr[j+1]){
//交换arr[j]与arr[j+1]
int temp = arr[j];
arr[j] = arr[j+1];
arr[j+1] = temp;
}
}
}

//完成排序,遍历结果
for(int i=0; i<arr.length; i++){
System.out.print(arr[i]+" ");
}
}
}

冒泡排序优化(选讲)

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
/*
思考:冒泡排序是否可以优化
*/
class Test19BubbleSort2{
public static void main(String[] args) {
int[] arr = {1, 3, 5, 7, 9};

//从小到大排序
for (int i = 0; i < arr.length - 1; i++) {
boolean flag = true;//假设数组已经是有序的
for (int j = 0; j < arr.length - 1 - i; j++) {
//希望的是arr[j] < arr[j+1]
if (arr[j] > arr[j + 1]) {
//交换arr[j]与arr[j+1]
int temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;

flag = false;//如果元素发生了交换,那么说明数组还没有排好序
}
}
if (flag) {
break;
}
}

//完成排序,遍历结果
for (int i = 0; i < arr.length; i++) {
System.out.print(arr[i] + " ");
}
}
}

6.6.4 快速排序

快速排序(Quick Sort)由图灵奖获得者Tony Hoare发明,被列为20世纪十大算法之一,是迄今为止所有内排序算法中速度最快的一种,快速排序的时间复杂度为O(nlog(n))。

快速排序通常明显比同为O(nlogn)的其他算法更快,因此常被采用,而且快排采用了分治法的思想,所以在很多笔试面试中能经常看到快排的影子。

排序思想:

  1. 从数列中挑出一个元素,称为”基准”(pivot),

  2. 重新排序数列,所有元素比基准值小的摆放在基准前面,所有元素比基准值大的摆在基准的后面(相同的数可以到任一边)。在这个分区结束之后,该基准就处于数列的中间位置。这个称为分区(partition)操作。

  3. 递归地(recursive)把小于基准值元素的子数列和大于基准值元素的子数列排序。

  4. 递归的最底部情形,是数列的大小是零或一,也就是永远都已经被排序好了。虽然一直递归下去,但是这个算法总会结束,因为在每次的迭代(iteration)中,它至少会把一个元素摆到它最后的位置去。

动态演示:https://visualgo.net/zh/sorting

图示1:

image-20220317235922776

图示2:

第一轮操作:

image-20221117205612230

第二轮操作:

image-20221117205719427

6.6.5 内部排序性能比较与选择

  • 性能比较

    • 从平均时间而言:快速排序最佳。但在最坏情况下时间性能不如堆排序和归并排序。
    • 从算法简单性看:由于直接选择排序、直接插入排序和冒泡排序的算法比较简单,将其认为是简单算法。对于Shell排序、堆排序、快速排序和归并排序算法,其算法比较复杂,认为是复杂排序。
    • 从稳定性看:直接插入排序、冒泡排序和归并排序时稳定的;而直接选择排序、快速排序、 Shell排序和堆排序是不稳定排序
    • 从待排序的记录数n的大小看,n较小时,宜采用简单排序;而n较大时宜采用改进排序。
  • 选择

    • 若n较小(如n≤50),可采用直接插入或直接选择排序。
      当记录规模较小时,直接插入排序较好;否则因为直接选择移动的记录数少于直接插入,应选直接选择排序为宜。
    • 若文件初始状态基本有序(指正序),则应选用直接插入、冒泡或随机的快速排序为宜;
    • 若n较大,则应采用时间复杂度为O(nlgn)的排序方法:快速排序、堆排序或归并排序。

7. Arrays工具类的使用

java.util.Arrays类即为操作数组的工具类,包含了用来操作数组(比如排序和搜索)的各种方法。 比如:

  • 数组元素拼接
    • static String toString(int[] a) :字符串表示形式由数组的元素列表组成,括在方括号(”[]”)中。相邻元素用字符 “, “(逗号加空格)分隔。形式为:[元素1,元素2,元素3。。。]
    • static String toString(Object[] a) :字符串表示形式由数组的元素列表组成,括在方括号(”[]”)中。相邻元素用字符 “, “(逗号加空格)分隔。元素将自动调用自己从Object继承的toString方法将对象转为字符串进行拼接,如果没有重写,则返回类型@hash值,如果重写则按重写返回的字符串进行拼接。
  • 数组排序 sort
    • static void sort(int[] a) :将a数组按照从小到大进行排序
    • static void sort(int[] a, int fromIndex, int toIndex) :将a数组的[fromIndex, toIndex)部分按照升序排列
    • static void sort(Object[] a) :根据元素的自然顺序对指定对象数组按升序进行排序。
    • static void sort(T[] a, Comparator<? super T> c) :根据指定比较器产生的顺序对指定对象数组进行排序。
  • 数组元素的二分查找 binarySearch
    • static int binarySearch(int[] a, int key) 、static int binarySearch(Object[] a, Object key) :要求数组有序,在数组中查找key是否存在,如果存在返回第一次找到的下标,不存在返回负数。
  • 数组的复制 copyof
    • static int[] copyOf(int[] original, int newLength) :根据original原数组复制一个长度为newLength的新数组,并返回新数组
    • static T[] copyOf(T[] original,int newLength):根据original原数组复制一个长度为newLength的新数组,并返回新数组
    • static int[] copyOfRange(int[] original, int from, int to) :复制original原数组的[from,to)构成新数组,并返回新数组
    • static T[] copyOfRange(T[] original,int from,int to):复制original原数组的[from,to)构成新数组,并返回新数组
  • 比较两个数组是否相等 equals
    • static boolean equals(int[] a, int[] a2) :比较两个数组的长度、元素是否完全相同
    • static boolean equals(Object[] a,Object[] a2):比较两个数组的长度、元素是否完全相同
  • 填充数组 fill
    • static void fill(int[] a, int val) :用val值填充整个a数组
    • static void fill(Object[] a,Object val):用val对象填充整个a数组
    • static void fill(int[] a, int fromIndex, int toIndex, int val):将a数组[fromIndex,toIndex)部分填充为val值
    • static void fill(Object[] a, int fromIndex, int toIndex, Object val) :将a数组[fromIndex,toIndex)部分填充为val对象

举例:java.util.Arrays类的sort()方法提供了数组元素排序功能:

1
2
3
4
5
6
7
8
9
import java.util.Arrays;
public class SortTest {
public static void main(String[] args) {
int[] arr = {3, 2, 5, 1, 6};
System.out.println("排序前" + Arrays.toString(arr));
Arrays.sort(arr);
System.out.println("排序后" + Arrays.toString(arr));
}
}

8. 数组中的常见异常

8.1 数组角标越界异常

当访问数组元素时,下标指定超出[0, 数组名.length-1]的范围时,就会报数组下标越界异常:ArrayIndexOutOfBoundsException。

1
2
3
4
5
6
7
8
public class TestArrayIndexOutOfBoundsException {
public static void main(String[] args) {
int[] arr = {1,2,3};
// System.out.println("最后一个元素:" + arr[3]);//错误,下标越界
// System.out.println("最后一个元素:" + arr[arr.length]);//错误,下标越界
System.out.println("最后一个元素:" + arr[arr.length-1]);//对
}
}

创建数组,赋值3个元素,数组的索引就是0,1,2,没有3索引,因此我们不能访问数组中不存在的索引,程序运行后,将会抛出 ArrayIndexOutOfBoundsException 数组越界异常。在开发中,数组的越界异常是不能出现的,一旦出现了,就必须要修改我们编写的代码。

8.2 空指针异常

观察一下代码,运行后会出现什么结果。

1
2
3
4
5
6
7
8
public class TestNullPointerException {
public static void main(String[] args) {
//定义数组
int[][] arr = new int[3][];

System.out.println(arr[0][0]);//NullPointerException
}
}

因为此时数组的每一行还未分配具体存储元素的空间,此时arr[0]是null,此时访问arr[0][0]会抛出NullPointerException 空指针异常。

空指针异常在内存图中的表现

1572338767825

小结:空指针异常情况

1
2
3
4
5
6
7
8
9
10
11
12
13
		//举例一:
// int[] arr1 = new int[10];
// arr1 = null;
// System.out.println(arr1[9]);

//举例二:
// int[][] arr2 = new int[5][];
// //arr2[3] = new int[10];
// System.out.println(arr2[3][3]);

//举例三:
String[] arr3 = new String[10];
System.out.println(arr3[2].toString());

×

纯属好玩

扫码支持
扫码打赏,你说多少就多少

打开支付宝扫一扫,即可进行扫码打赏哦

文章目录
  1. 1. 第01章_Java语言概述
    1. 1.1. 1. Java知识脉络图
      1. 1.1.1. 1.1 Java基础全程脉络图
      2. 1.1.2. 1.2 本章专题与脉络
    2. 1.2. 2. 抽丝剥茧话Java
      1. 1.2.1. 2.1 当前大学生就业形势
      2. 1.2.2. 2.2 IT互联网是否依旧靠谱
      3. 1.2.3. 2.3 IT行业岗位分析
      4. 1.2.4. 2.4 软件开发之Java开发
      5. 1.2.5. 2.5 到底多少人在用Java
      6. 1.2.6. 2.6 八卦一下程序员
      7. 1.2.7. 2.7 Java系列课程体系
      8. 1.2.8. 2.8 Java职业晋升路线图
    3. 1.3. 3. 计算机的硬件与软件
      1. 1.3.1. 3.1 计算机组成:硬件+软件
      2. 1.3.2. 3.2 CPU、内存与硬盘
      3. 1.3.3. 3.3 输入设备:键盘输入
    4. 1.4. 4. 软件相关介绍
      1. 1.4.1. 4.1 什么是软件
      2. 1.4.2. 4.2 人机交互方式
      3. 1.4.3. 4.3 常用的DOS命令
    5. 1.5. 5. 计算机编程语言
      1. 1.5.1. 5.1 计算机语言是什么
      2. 1.5.2. 5.2 计算机语言简史
      3. 1.5.3. 5.3 计算机语言排行榜
      4. 1.5.4. 5.4 编程语言,该学哪个?
    6. 1.6. 6. Java语言概述
      1. 1.6.1. 6.1 Java概述
      2. 1.6.2. 6.2 Java语言简史
      3. 1.6.3. 6.3 Java之父
      4. 1.6.4. 6.4 公司八卦
      5. 1.6.5. 6.5 Java技术体系平台
    7. 1.7. 7. Java开发环境搭建(掌握)
      1. 1.7.1. 7.1 什么是JDK、JRE
      2. 1.7.2. 7.2 JDK版本选择
      3. 1.7.3. 7.3 JDK的下载
      4. 1.7.4. 7.4 JDK的安装
      5. 1.7.5. 7.5 配置path环境变量
        1. 1.7.5.1. 7.5.1 理解path环境变量
        2. 1.7.5.2. 7.5.2 JDK8配置方案1:只配置path
        3. 1.7.5.3. 7.5.3 JDK8配置方案2:配置JAVA_HOME+path(推荐)
        4. 1.7.5.4. 7.5.4 JDK17配置方案:自动配置
    8. 1.8. 8. 开发体验:HelloWorld(掌握)
      1. 1.8.1. 8.1 开发步骤
      2. 1.8.2. 8.2 编写
      3. 1.8.3. 8.3 编译
      4. 1.8.4. 8.4 运行
    9. 1.9. 9. Java开发工具
      1. 1.9.1. 9.1 都有哪些开发Java的工具
      2. 1.9.2. 9.2 如何选择
    10. 1.10. 10. HelloWorld案例常见错误
      1. 1.10.1. 10.1 拼写问题
      2. 1.10.2. 10.2 编译、运行路径问题
      3. 1.10.3. 10.3 语法问题
      4. 1.10.4. 10.4 字符编码问题
      5. 1.10.5. 10.5 建议
    11. 1.11. 11. HelloWorld小结
      1. 1.11.1. 11.1 Java程序的结构与格式
      2. 1.11.2. 11.2 Java程序的入口
      3. 1.11.3. 11.3 两种常见的输出语句
      4. 1.11.4. 11.4 源文件名与类名
    12. 1.12. 12. 注释(comment)
    13. 1.13. 13. Java API文档
    14. 1.14. 14. Java核心机制:JVM
      1. 1.14.1. 14.1 Java语言的优缺点
        1. 1.14.1.1. 14.1.1 优点
        2. 1.14.1.2. 14.1.2 缺点
      2. 1.14.2. 14.2 JVM功能说明
        1. 1.14.2.1. 14.2.1 功能1:实现Java程序的跨平台性
        2. 1.14.2.2. 14.2.2 功能2:自动内存管理(内存分配、内存回收)
    15. 1.15. 15. 章节案例
  2. 2. 第02章_变量与运算符
    1. 2.1. 本章专题与脉络
    2. 2.2. 1. 关键字(keyword)
    3. 2.3. 2. 标识符( identifier)
    4. 2.4. 3. 变量
      1. 2.4.1. 3.1 为什么需要变量
      2. 2.4.2. 3.2 初识变量
      3. 2.4.3. 3.3 Java中变量的数据类型
      4. 2.4.4. 3.4 变量的使用
        1. 2.4.4.1. 3.4.1 步骤1:变量的声明
        2. 2.4.4.2. 3.4.2 步骤2:变量的赋值
    5. 2.5. 4. 基本数据类型介绍
      1. 2.5.1. 4.1 整数类型:byte、short、int(默认)、long
        1. 2.5.1.1. 4.1.1 补充:计算机存储单位
      2. 2.5.2. 4.2 浮点类型:float、double(默认)
        1. 2.5.2.1. 4.2.1 关于浮点型精度的说明
        2. 2.5.2.2. 4.2.2 应用举例
      3. 2.5.3. 4.3 字符类型:char
      4. 2.5.4. 4.4 布尔类型:boolean
    6. 2.6. 5. 基本数据类型变量间运算规则
      1. 2.6.1. 5.1 自动类型提升
      2. 2.6.2. 5.2 强制类型转换
      3. 2.6.3. 5.3 基本数据类型与String的运算
        1. 2.6.3.1. 5.3.1 字符串类型:String
        2. 2.6.3.2. 5.3.2 运算规则
        3. 2.6.3.3. 5.3.3 案例与练习
    7. 2.7. 6. 计算机底层如何存储数据
      1. 2.7.1. 6.1 进制的分类
      2. 2.7.2. 6.2 进制的换算举例
      3. 2.7.3. 6.3 二进制的由来
      4. 2.7.4. 6.4 二进制转十进制
      5. 2.7.5. 6.5 十进制转二进制
      6. 2.7.6. 6.6 二进制与八进制、十六进制间的转换
      7. 2.7.7. 6.7 各进制间的转换
    8. 2.8. 7. 运算符(Operator)(掌握)
      1. 2.8.1. 7.1 算术运算符
        1. 2.8.1.1. 7.1.1 基本语法
        2. 2.8.1.2. 7.1.2 案例与练习
      2. 2.8.2. 7.2 赋值运算符
        1. 2.8.2.1. 7.2.1 基本语法
        2. 2.8.2.2. 7.2.2 练习
      3. 2.8.3. 7.3 比较(关系)运算符
      4. 2.8.4. 7.4 逻辑运算符
        1. 2.8.4.1. 7.4.1 基本语法
        2. 2.8.4.2. 7.4.2 案例与练习
      5. 2.8.5. 7.5 位运算符(难点、非重点)
        1. 2.8.5.1. 7.5.1 基本语法
        2. 2.8.5.2. 7.5.2 举例
        3. 2.8.5.3. 7.5.3 案例
      6. 2.8.6. 7.6 条件运算符
        1. 2.8.6.1. 7.6.1 基本语法
        2. 2.8.6.2. 7.6.2 案例
        3. 2.8.6.3. 7.6.3 与if-else的转换关系
      7. 2.8.7. 7.7 运算符优先级
    9. 2.9. 8. 【拓展】关于字符集
      1. 2.9.1. 8.1 字符集
      2. 2.9.2. 8.2 ASCII码
      3. 2.9.3. 8.3 ISO-8859-1字符集
      4. 2.9.4. 8.4 GBxxx字符集
      5. 2.9.5. 8.5 Unicode码
      6. 2.9.6. 8.6 UTF-8
      7. 2.9.7. 8.7 小结
  3. 3. 第03章_流程控制语句
    1. 3.1. 本章专题与脉络
    2. 3.2. 1. 顺序结构
    3. 3.3. 2. 分支语句
      1. 3.3.1. 2.1 if-else条件判断结构
        1. 3.3.1.1. 2.1.1 基本语法
        2. 3.3.1.2. 2.1.2 应用举例
        3. 3.3.1.3. 2.1.3 if…else嵌套
        4. 3.3.1.4. 2.1.4 其它说明
        5. 3.3.1.5. 2.1.5 练习
      2. 3.3.2. 2.2 switch-case选择结构
        1. 3.3.2.1. 2.2.1 基本语法
        2. 3.3.2.2. 2.2.2 应用举例
        3. 3.3.2.3. 2.2.3 利用case的穿透性
        4. 3.3.2.4. 2.2.4 if-else语句与switch-case语句比较
        5. 3.3.2.5. 2.2.5 练习
    4. 3.4. 3. 循环语句
      1. 3.4.1. 3.1 for循环
        1. 3.4.1.1. 3.1.1 基本语法
        2. 3.4.1.2. 3.1.2 应用举例
        3. 3.4.1.3. 3.1.3 练习
      2. 3.4.2. 3.2 while循环
        1. 3.4.2.1. 3.2.1 基本语法
        2. 3.4.2.2. 3.2.2 应用举例
        3. 3.4.2.3. 3.2.3 练习
      3. 3.4.3. 3.3 do-while循环
        1. 3.4.3.1. 3.3.1 基本语法
        2. 3.4.3.2. 3.3.2 应用举例
        3. 3.4.3.3. 3.3.3 练习
      4. 3.4.4. 3.4 对比三种循环结构
      5. 3.4.5. 3.5 “无限”循环
        1. 3.4.5.1. 3.5.1 基本语法
        2. 3.4.5.2. 3.5.2 应用举例
      6. 3.4.6. 3.6 嵌套循环(或多重循环)
        1. 3.4.6.1. 3.6.1 使用说明
        2. 3.4.6.2. 3.6.2 应用举例
        3. 3.4.6.3. 3.6.3 练习
    5. 3.5. 4. 关键字break和continue的使用
      1. 3.5.1. 4.1 break和continue的说明
      2. 3.5.2. 4.2 应用举例
      3. 3.5.3. 4.3 带标签的使用
      4. 3.5.4. 4.4 经典案例
      5. 3.5.5. 4.5 练习
    6. 3.6. 5. Scanner:键盘输入功能的实现
      1. 3.6.1. 5.1 各种类型的数据输入
      2. 3.6.2. 5.2 练习
    7. 3.7. 6. 如何获取一个随机数(Math.random())
  4. 4. 第05章_数组
    1. 4.1. 本章专题与脉络
    2. 4.2. 1. 数组的概述
      1. 4.2.1. 1.1 为什么需要数组
      2. 4.2.2. 1.2 数组的概念
      3. 4.2.3. 1.3 数组的分类
    3. 4.3. 2. 一维数组的使用
      1. 4.3.1. 2.1 一维数组的声明
      2. 4.3.2. 2.2 一维数组的初始化
        1. 4.3.2.1. 2.2.1 静态初始化
        2. 4.3.2.2. 2.2.2 动态初始化
      3. 4.3.3. 2.3 一维数组的使用
        1. 4.3.3.1. 2.3.1 数组的长度
        2. 4.3.3.2. 2.3.2 数组元素的引用
      4. 4.3.4. 2.4 一维数组的遍历
      5. 4.3.5. 2.5 数组元素的默认值
    4. 4.4. 3. 一维数组内存分析
      1. 4.4.1. 3.1 Java虚拟机的内存划分
      2. 4.4.2. 3.2 一维数组在内存中的存储
        1. 4.4.2.1. 1、一个一维数组内存图
        2. 4.4.2.2. 2、数组下标为什么是0开始
        3. 4.4.2.3. 3、两个一维数组内存图
        4. 4.4.2.4. 4、两个变量指向一个一维数组
    5. 4.5. 4. 一维数组的应用
    6. 4.6. 5. 多维数组的使用
      1. 4.6.1. 5.1 概述
      2. 4.6.2. 5.2 声明与初始化
        1. 4.6.2.1. 5.2.1 声明
        2. 4.6.2.2. 5.2.2 静态初始化
        3. 4.6.2.3. 5.2.3 动态初始化
      3. 4.6.3. 5.3 数组的长度和角标
      4. 4.6.4. 5.4 二维数组的遍历
      5. 4.6.5. 5.5 内存解析
      6. 4.6.6. 5.6 应用举例
    7. 4.7. 6. 数组的常见算法
      1. 4.7.1. 6.1 数值型数组特征值统计
      2. 4.7.2. 6.2 数组元素的赋值与数组复制
      3. 4.7.3. 6.3 数组元素的反转
      4. 4.7.4. 6.4 数组的扩容与缩容
      5. 4.7.5. 6.5 数组的元素查找
      6. 4.7.6. 6.6 数组元素排序
        1. 4.7.6.1. 6.6.1 算法概述
        2. 4.7.6.2. 6.6.2 排序算法概述
        3. 4.7.6.3. 6.6.3 冒泡排序(Bubble Sort)
        4. 4.7.6.4. 6.6.4 快速排序
        5. 4.7.6.5. 6.6.5 内部排序性能比较与选择
    8. 4.8. 7. Arrays工具类的使用
    9. 4.9. 8. 数组中的常见异常
      1. 4.9.1. 8.1 数组角标越界异常
      2. 4.9.2. 8.2 空指针异常
,