1 第一章 Java开发环境搭建
1.1 章节目标与知识框架
1.1.1 章节目标
掌握Java的开发环境搭建,会编写HelloWorld程序,并能够准确的进行编译和运行;理解path和classpath环境变量并可以自行配置。
1.1.2 知识框架
1.2 Java语言概述(了解)
Java编程语言是SunMicrosystems公司的JamesGosling在1990年创建的,于1995年公布于世(一般说Java诞生于1995年)。Java是一门面向对象的编程语言,不仅吸收了C++语言的各种优点,还摒弃了C++里难以理解的多继承、指针等概念,因此Java语言具有功能强大和简单易用两个特征。Java语言作为面向对象编程语言的代表,极好地实现了面向对象理论,允许程序员以优雅的思维方式进行复杂的编程。
目前在世界各地都有Java程序员的存在,你走到哪里也不再孤单,因为有你的Java编程小伙伴在陪伴着你。我们一起来看看世界编程语言排行榜TIOBE(https://www.tiobe.com/tiobe-index/)给出的统计数据。
上图是TIOBE排行榜给出的2018年5月份的编程语言排行榜的统计数据,如果你一直在关注编程语言排行榜,那么你应该很容易发现Java的排名多年以来一直在前两名徘徊,并且多数情况下都是以排名第一的形式存在。
1.3 Java语言发展史(了解)
1990年末,Sun公司准备为下一代智能家电(电视机,微波炉,电话)编写一个通用的控制系统。该团队最初考虑使用C++语言,很多成员包括Sun公司的首席科学家BillJoy,发现C++语言在某些方面复杂,系统资源极其有限,缺少垃圾回收系统等,于是BillJoy决定开发一种新的语言:Oak。
1992年夏天,Green计划已经完成新平台的部分功能,包括Green操作系统,Oak的程序设计语言、类库等。同年11月,Green计划被转成“FirstPerson有限公司”,一个Sun公司的全资子公司。该团队致力于创建一种高度互动的设备。
1994年夏天,互联网和浏览器的出现不仅给广大互联网的用户带来了福音,也给Oak语言带来了新的生机。JamesGosling(Java之父)立即意识到,这是一个机会,于是对Oak进行了小规模的改造。
1994年秋,小组中的Naughton和Jonathanpayne完成了第一个Java语言的网页浏览器:
WebRunner。Sun公司实验室主任BertSutherland和技术总监EricSchmidt观看了该网页的演示并给予了高度的评价。当时Oak这个商标已经被注册了,于是将Oak改名为Java。
1995年初,Sun公司发布Java语言,Sun公司直接把Java放到互联网上,免费给大家使用,甚至连源代码也不保密,也放在互联网公开。几个月后,Java成了互联网上最热门的宝贝。各种各样的小程序层出不穷,Java终于扬眉吐气,成为了一种广为人知的编程语言。
1996年底,Flash问世了,这是一种更加简单的动画设计软件:使用Flash几乎无须任何编程语言知识,就可以做出丰富多彩的动画。Flash逐渐蚕食了Java在网页上的应用。
1997年2月18日,Sun公司发布了JDK1.1,增加了即时编译器JIT。
1995年Java诞生到1998年底,Java语言虽然成为了互联网上广泛使用的编程语言,但它没有找到一个准确的定位。
1998年12月,Sun发布了Java历史上最重要的JDK版本:JDK1.2。并将Java分成了J2EE(提供了企业应用开发相关的完整解决方案)、J2SE(整个Java技术的核心和基础)、J2ME(主要用于控制移动设备和信息家电等有限存储的设备)三个版本。
2002年2月,Sun发布了JDK历史上最为成熟的版本,JDK1.4。
2004年10月,Sun发布了万众期待的JDK1.5。JDK1.5增加了诸如泛型、增强的for语句、可变数量的形参、注释、自动拆箱和装箱等。
2005年,Java诞生十周年,J2SE/J2EE/J2ME分别改名为:JavaSE/JavaEE/JavaME。
2006年12月,Sun发布了JDK1.6。
2009年4月20日,Oracle甲骨文公司宣布将以每股9.5美元的价格收购Sun。Oracle通过收购Sun获得了两项资产:Java和Solaris。
2007年11月,Google宣布推出一款基于Linux平台的开源手机操作系统:Android。Android使用Java语言来开发应用程序。Android平台的流行,让Java语言获得了在客户端程序上大展拳脚的机会。
2011年7月28日,Oracle发布了JavaSE7,这次版本升级耗时将近5年时间。引入二进制整数、支持字符串的switch语句等。
2014年3月18日,Oracle发布了JavaSE8。
2017年7月,Oracle发布了JavaSE9。
2018年3月20日,Oracle发布了正式版JavaSE10。同一年9月25日发布了Java11。
2019年3月19日,Oracle发布了Java12。
以上的描述就是Java一路走来的发展历程,我们只能说:Java,你好坚挺啊!在这个发展的过程中一代语言的兴起又衰败,又兴起又衰败,但Java这24年来一直立于不败之地。并且渗透到每个行业,已然根深蒂固。
在以上的描述中,我们提到了Java包括三大块,分别是JavaSE、JavaEE、JavaME,这三者之间存在什么样的关系呢?请看下图你就明白了:
如上图所示,JavaEE和JavaME都包含JavaSE。实际上,这三大块就是Java的三大版本,JavaSE是Java的标准版,是学习JavaEE和JavaME的基础,JavaEE是企业版,JavaME是微型版。
JavaSE(JavaPlatform,StandardEdition)。JavaSE以前称为J2SE。它允许开发和部署在桌面、服务器、嵌入式环境和实时环境中使用的Java应用程序。JavaSE包含了支持JavaWeb服务开发的类,并为JavaPlatform,EnterpriseEdition(JavaEE)提供基础。
JavaEE(JavaPlatform,EnterpriseEdition)。这个版本以前称为J2EE。企业版本帮助开发和部署可移植、健壮、可伸缩且安全的服务器端Java应用程序。JavaEE是在JavaSE的基础上构建的,它提供Web服务、组件模型、管理和通信API,可以用来实现企业级的面向服务体系结构(service-orientedarchitecture,SOA)和Web2.0应用程序。
JavaME(JavaPlatform,MicroEdition)。这个版本以前称为J2ME。JavaME为在移动设备和嵌入式设备(比如手机、PDA、电视机顶盒和打印机)上运行的应用程序提供一个健壮且灵活的环境。JavaME包括灵活的用户界面、健壮的安全模型、许多内置的网络协议以及对可以动态下载的连网和离线应用程序的丰富支持。基于JavaME规范的应用程序只需编写一次,就可以用于许多设备,而且可以利用每个设备的本机功能。
综上所述,我们用几句话简单概括一下它们之间的区别。Java标准版,主要做一般的Java应用,比如应用软件/QQ之类的通信软件等等。Java企业版,主要做企业应用,比如公司网站,企业解决方案等。Java微型版,主要面向嵌入式等设备应用的开发,比如手机游戏等。
1.4 Java语言特性(了解)
sun公司对Java的描述:"Java is a simple, object-oriented, distributed, interpreted, robust, secure, architectureneutral, portable, high-performance, multihreaded, anddynamiclanguage"。翻译起来就是:“Java是一门简单的,面向对象,分布式,解释性,健壮的,安全的,结构中立的,便捷的,高性能的,多线程的,动态的语言”。那么,在学习Java编程语言之前,让我们一起来看一看它有哪些特性吧?
①简单性:Java语言底层采用C++语言实现,相对于C++来说,Java是简单的,在Java语言中程序员不需要再操作复杂的指针(指针的操作是很复杂的),继承方面也是只支持单继承(C++语言是一种半面向对象的编程语言,支持多继承,多继承会导致关系很复杂),在很多方面进行了简化。
②面向对象:Java中提供了封装、继承、多态等面向对象的机制。
③健壮性:在C++程序当中的无用数据/垃圾数据需要编程人员手动释放,当忘记释放内存的时候,会导致内存使用率降低,影响程序的执行;在Java语言当中这种问题得到了解决,因为Java语言引入了自动垃圾回收机制(GC机制),Java程序启动了一个单独的垃圾回收线程,时刻监测内存使用情况,在特定时机会回收/释放垃圾数据,这样会让内存时刻处于最好的状态。
④多线程:Java语言支持多个线程同时并发执行,同时也提供了多线程环境下的安全机制。
⑤可移植性/跨平台:可移植性/跨平台表示Java语言只需要编写/编译一次,即可处处运行。Java代码既可以运行在windows的环境下,又可以运行在Linux的环境下,而不需要修改Java源程序,那么它是怎么做到的呢?功劳全在于“Java虚拟机(JavaVirtualMachine,简称JVM)”这种机制,实际上Java程序运行的时候并不是直接运行在操作系统上面的,而是在操作系统上先安装了一个JVM,把Java程序放到了JVM当中运行,JVM屏蔽了各操作系统之间的差异,这样就完成了跨平台。但是,JVM的出现虽然搞定了跨平台,同时也带来了一些问题,比如要想运行Java程序就必须先安装JVM,没有JVM,Java程序是运行不了的,就像你要在网页上看视频,结果浏览器却提示你需要安装Flash插件,这一点你是不是感觉非常不爽呀!那么Java程序、Java虚拟机、操作系统之间是怎样的关系呢?请看下图:
通过上图我们可以看到不同的操作系统中安装的JVM肯定也是不同的,windows操作系统则必须安装windows版本的JVM,Linux操作系统则必须安装Linux版本的JVM。这是因为JVM是和操作系统直接打交道的,windows和Linux操作系统本身的执行原理不同,所以JVM肯定也必须是定制的,不能通用。但是Java程序放到windows的JVM上和放到Linux的JVM上最终执行效果是完全相同的。这是因为:虽然JVM版本不同,但是所有版本的JVM的实现都是遵守sun制定的JVM规范的,这样就可以达到编写一次到处运行的效果,有没有感觉很神奇呀!
当然,Java语言除了以上的特性之外还有很多其它的特性,我在这里就不再一一赘述了,大家对以上的特性来说重点知道Java的跨平台性以及垃圾回收机制即可,其它的作为了解。
1.5 JDK、JRE、JVM三者关系(理解)
在学习Java之前,我们需要对一些专业术语有一定的了解,在Java中常见的专业术语包括:JDK、JRE、JVM等,它们分别是什么,它们之间的关系又是怎样的呢,请看下图:
从上图中我们可以看到,JDK、JRE、JVM之间存在这样的包含关系:JDK包含JRE,JRE又包含JVM。换句话说,只要安装了JDK,JRE和JVM则自动就安装了。那么它们分别代表什么呢:
①JDK:JDK(JavaDevelopmentKit)是Java语言的软件开发工具包(SDK)。它是每一个Java软件开发人员必须安装的。JDK安装之后,它会自带一个JRE,因为软件开发人员编写完代码之后总是要运行的。注意:如果只是在这台机器上运行Java程序,则不需要安装JDK,只需要安装JRE即可(JRE是有独立安装包的,这个大家可以从Oracle官网上找一下)。
②JRE:JRE(JavaRuntimeEnvironment,Java运行环境),运行JAVA程序所必须的环境的集合,包含JVM标准实现及Java核心类库。
③JVM:JVM是JavaVirtualMachine(Java虚拟机)的缩写,JVM是一种用于计算设备的规范,它是一个虚构出来的计算机,是通过在实际的计算机上仿真模拟各种计算机功能来实现的。JVM是实现Java语言跨平台的法宝。
在这里我需要重点强调一下,不同的操作系统需要安装不同版本的JDK,有专门的windows版JDK,Linux版JDK,Mac版JDK等,当然不同版本的JDK安装之后会对应不同版本的JRE和JVM。
1.6 初步了解Java的加载与执行(了解)
在编写第一个Java程序之前,我们应当对Java的加载与执行提前有一个简单的了解,请看下图:
通过上图,我们可以看到Java程序从开发到最终运行是这样进行的:
第一步(写代码):在任意位置创建一个.java结尾的文件,程序员在该文件中编写符合Java语法的源代码,这个文件被称为Java源文件。
第二步(编译):使用“javac”命令对java源文件进行编译,如果Java源文件符合Java语法规则,则编译生成1个或者多个以“.class”结尾的文件。“.class”结尾的文件我们称之为字节码文件,注意字节码文件不是普通文本文件,使用记事本等文本编辑器是无法打开的,但该文件内容也不是二进制,如果是二进制形式的话,操作系统是可以直接执行的,这个文件的内容我们称之为字节码。注意:这里有一个“类名”的概念,假设生成的文件是A.class,则表示类名为A,B.class则表示类名为B。
第三步(运行):使用“java”命令运行已编译的Java程序,假设编译之后的字节码文件是A.class,则在dos命令窗口中执行javaA命令,这个时候Java虚拟机(JVM)就启动了,Java虚拟机通过“类装载器ClassLoader”从硬盘中找到A.class文件并装载,字节码文件被装载到Java虚拟机之后,Java虚拟机解释器对字节码进行解释,最终解释为二进制码,然后操作系统通过执行二进制指令来和硬件平台完成交互。
以上则是Java程序加载与执行的过程,接下来我给大家总结7条规则,大家可以理解并记忆一下:
①Java程序从开发到运行包括编译和运行两个阶段,这两个阶段可以在不同的操作系统中完成,例如在windows环境下进行编译,在Linux环境下运行,这是因为有JVM机制的存在,做到了一次编译到处运行(跨平台/可移植)。
②编译阶段需要使用javac.exe(安装JDK之后该命令就存在了)命令,运行阶段需要使用java.exe(安装JRE之后该命令就存在了)命令。
③一个Java源文件可能会编译生成多个class文件。
④Java源文件中的源代码如果不符合Java的语法机制则编译时编译器会提示错误信息,并且无法生成class文件。反之则生成class文件,而class文件才是最终要执行的程序,此时将Java源文件删除是不会影响Java程序运行的(当然,我们也不必删除java源文件,因为在运行class文件之后,如果没有达到预期的运行效果,这个时候还需要将Java源代码修改,重新编译,以达到最终的运行效果)。
⑤若生成的字节码文件名为A.class,那么我们称A为一个类的名字(这个先记住就行,后面的内容会讲)。
⑥当编译阶段完成之后,我们可以使用JRE中的java.exe命令运行程序,例如执行命令“javaA”,该命令执行后会启动类加载器,类加载器去硬盘上搜索A.class文件,找到该字节码文件之后,将其加载到JVM当中,JVM中的解释器会将A.class字节码文件解释为操作系统可以执行的二进制码,然后操作系统通过执行二进制码和硬件平台交互。
⑦运行Java程序的前提是当前操作系统上已经安装了对应版本的JVM(JVM不是单独安装的,安装JRE即可,不同的操作系统需要安装不同版本的JRE,不同版本的JRE对应不同版本的JVM)。
在了解了Java程序的加载与执行之后,我们开始动手实现第一个Java程序吧!
1.7 开发前的准备工作(掌握)
1.7.1 windows显示文件扩展名
Java源文件要求文件扩展名必须为.java,不能使用其他扩展名,有些操作系统默认不显示文件扩展名,大家可按照以下步骤,将文件的扩展名显示出来(以win7系统为例),请看以下步骤:
第一步:打开电脑桌面上的“计算机”,在菜单栏上找“工具”。
第二步:点击“工具”,下拉列表中点击“文件夹选项(O)…”。
第三步:在弹出的“文件夹选项”对话框中点击“查看”选项卡,在列表中找到“隐藏已知文件类型的扩展名”项,将该项前的“对勾”去掉,最后点击“应用”,点击“确定”即可。
以上是win7操作系统显示文件扩展名的步骤,其它windows系列版本的系统和以上操作类似,大家可以自己找一下,并设置好。
1.7.2 windows系统的常用DOS命令
初次学习Java编程最好不要一上来就使用集成开发环境IDE,不利于代码的掌握,建议使用文本编辑器,甚至可以使用记事本编写。在这种情况下,我们就需要熟悉一些dos命令,接下来我们来看几个常见的dos命令吧:
打开DOS命令窗口,使用快捷键:win+r,输入cmd,回车。
dir命令:查看当前目录下所有的子文件或子目录。
cd命令:切换路径,使用方法是:cd+目录路径,需要注意的是路径包括相对路径和绝对路径,对于windows来说从硬盘的根路径下开始的路径都是绝对路径,例如:C:\ProgramFiles、C:\ProgramFiles\Java等,所有的相对路径都是从当前所在目录作为起点开始查找的路径。另外cd..切换到上级目录,cd\切换到根目录。
切换盘符:直接输入c:,或者d:,然后回车即可。切换盘符不需要cd命令。
del命令:删除文件或目录,例如:del*.class,删除当前目录下所有的.class文件。
ipconfig命令:查看IP地址等信息,查看更详细的信息使用ipconfig/all。
ping命令:查看两台计算机是否可以正常通信,例如:ping192.168.1.100,正常情况下发送数据包和接收数据包数量相同并且没有延迟的时候表示通信正常,ping192.168.1.100-t表示一直发送数据包和接收数据包,pingwww.baidu.com可以查看电脑是否可以正常上网。
mkdir命令:创建目录,例如:mkdirabc表示在“当前目录”下新建abc目录。
cls命令:清屏。
exit命令:退出DOS命令窗口
以上的命令需要大家多敲多练才可以记住,那就辛苦大家敲一下吧!
1.7.3 文本编辑器的安装与配置
任何文本编辑器都可以进行Java代码的编写,例如:记事本、editplus、notepad++等,记事本的文本编辑能力稍弱,没有高亮显示,这里选择EditPlus。这里安装的EditPlus版本为:,双击之后,一直点击下一步就可以了。
在使用该工具之前,我们对该工具进行一个简单的配置,例如:取消自动备份、设置字体大小、设置缩进长度等。首先打开“首选项(P)…”,如下图所示:
打开首选项之后,从左边“类别”中可以找到“常规”,在常规下有字体的设置,这里就不再赘述了,另外除了常规之外,还有“文件”,选中“文件”之后,可以看到右边有“保存时创建备份文件”,将前面的对勾去掉,然后点击应用,这样就可以取消自动备份功能了。如下图所示:
接下来,我们一起来看一下怎么设置制表符的长度,默认是8个长度,太长了,代码看起来不是很协调,可以修改一下,点击类别“文件”下的“设置&语法”选项,然后可以看到右侧有“制表符/缩进”,如下图所示:
然后再点击“制表符/缩进”,弹出以下对话框,这时将长度修改为3,如下图所示:
到此为止,文本编辑器EditPlus安装并且配置完成了。当然,EditPlus还有更强悍的配置,比如EditPlus可以配置属于自己的Java环境,在这里我们就不再配置了,接下来的学习,Java程序的编译和运行,我们统一采用手工的方式进行,这样有利于大家熟练掌握开发步骤。
1.7.4 常用文本编辑快捷键
使用快捷键可以大大提高开发效率,从现在起我们应该尽可能使用快捷键来代替鼠标,接下来,我给大家列举一下通用的文本编辑快捷键。当然,如果大家想熟练掌握,还是需要后期不断的使用,请看下面列表:
以上是一些比较常见的快捷键,只要大家能够勤加练习,就能够提高操作速度,进而提高编程速度,最开始的时候可能会不太适应,只要坚持就对了。
1.8 开发第一个Java程序(掌握)
1.8.1 JDK的下载与安装
Java语言由Sun(SunMicrosystems)公司开发,2009年被Oracle(甲骨文)公司收购,所以目前JDK需要从Oracle下载,Oracle官网地址是https://www.oracle.com,这里我们学习Java10版本,所以下载JDK10,具体下载过程如下图所示:
注意:JDK10要求windows操作系统必须是64bit的。下载完成之后,直接双击进行安装,具体步骤如下所示:
到此,JDK的下载与安装就完成了。
1.8.2 JDK的bin目录
JDK安装结束之后,安装目录下有很多子目录,这里就不再一一赘述,后面用到的时候我们再看,这里重点看一下JDK的bin目录,bin目录下存放的都是运行Java程序相关的命令,在windows操作系统中,以exe后缀的文件都是可执行文件,或者叫做命令文件,请看下图:
通过上图我们可以看到,在bin目录下有javac和java两个命令,其中javac命令是用来编译Java源程序的,而java命令是用来运行Java程序的。其它命令目前我们还用不上,后期用上的话我们再看。
1.8.3 编写HelloWorld
以上所说的环境都准备好之后,大家就可以开始编写你的第一个Java程序了,由于Java程序的基础语法还没有讲解,第一个程序大家只能严格按照以下代码照抄了,以下程序为什么要这么写,大家可以先不需要去了解,因为这第一个程序的最主要目的是为了测试你机器上的Java环境是否搭建好了。我们首先新建文件,具体代码如下所示:
在这里大家需要注意的是,在照抄以上程序的时候,一定要注意标点符号都是半角形式,还需要注意字母的大小写。大家编写好以上程序之后,使用快捷键ctrl+s完成保存。
1.8.4 编译HelloWorld程序
编写完以上的Java程序并保存之后,接下来我们对以上的程序进行编译,编译java程序需要使用JDKbin目录下的javac.exe命令,我们先来看看这个命令是否可以在DOS命令窗口中使用。打开DOS命令窗口,输入javac,然后回车执行,如下图所示:
根据以上测试结果,可以看到javac命令是无法执行的,出现的错误提示信息是“'javac‟不是内部或外部命令,也不是可运行的程序或批处理文件”。这说明windows操作系统没有找到javac.exe文件。接下来我们将所在的“C:\Users\Administrator”目录切换到“C:\ProgramFiles\Java\jdk-10\bin”目录下再来测试,如下图所示:
通过上图我们可以看到,当我们将所在的当前目录切换到bin目录之后,查看了bin目录下确实存在javac.exe文件,然后执行javac命令,结果javac命令找到了并且也运行了,根据测试结果可以得出这样的结论:windows操作系统默认是从当前所在的路径下查找可执行命令文件的。换句话说,以后我们每一次使用javac这个命令的时候都需要先切换到bin目录下。这岂不是很麻烦,有什么解决办法吗?我们先来研究一下ipconfig命令的执行原理,请看下图:
通过上图我们可以看到,先将目录切换到“C:\Users\Administrator”下,并且使用dir命令查看ipconfig.exe文件,发现当前目录下ipconfig.exe文件并不存在,然后输入ipconfig命令并执行,发现该命令是可以执行的,换句话说windows操作系统可以找到ipconfig.exe文件,那么ipconfig.exe文件在哪个目录下呢,请看下图:
我们可以看到ipconfig.exe文件实际上是在“C:\Windows\System32”目录下。那么ipconfig命令为什么在命令窗口可以直接执行而不需要切换到命令所在目录呢?
实际上这是因为windows系统中的环境变量path在起作用。我们打开环境变量(在桌面上“计算机”图标上点击右键->属性->高级系统设置->环境变量),可以看到windows系统有以下的默认配置,请看下图:
我们可以看到windows系统有一个默认的系统变量path,path变量中有“C:\Windows\System32;”路径。
我们来做一个实验,把上图path环境变量当中的“C:\Windows\System32;”删掉(鼠标双击Path,将“C:\Windows\System32;”删除,注意:System32路径后面的半角分号也需要删除),然后点击确定。将所有的DOS命令窗口全部关闭(注意:修改了任何环境变量,DOS命令窗口都需要关闭之后重新打开,这样新的环境变量才会生效),打开一个新的DOS命令窗口,测试ipconfig,如下图所示:
根据以上测试,ipconfig命令无法执行了,windows操作系统已经找不到ipconfig.exe文件了,也就是说windows是从环境变量path中指定的路径下查找命令的。
接下来我们再重新恢复path环境变量的配置,配置环境变量path的步骤是这样的:在桌面计算机图标上点击鼠标右键,继续点击属性,在弹出的窗口上选择高级系统设置,然后在弹出的系统属性窗口上点击环境变量,此时会弹出以下窗口:
通过上图我们可以看到环境变量包括用户变量和系统变量两种,在这里简单说明一下,配置用户变量表示只对当前用户有效,例如在“Administrator的用户变量”中配置环境变量的话,这个环境变量只对“Administrator”用户有效。配置系统变量则表示对使用该计算机的所有用户有效。
我们继续配置环境变量path,在上图的系统变量当中找到path环境变量,点击“编辑”,将“C:\Windows\System32;”添加到path环境变量当中(注意:环境变量path当中有很多路径,路径和路径之间必须使用半角的英文分号进行分隔),然后点击确定,重新配置了环境变量需要关闭所有DOS命令窗口,打开一个新的DOS窗口测试ipconfig命令,如下图所示:
我们可以看到ipconfig命令又可以执行了。根据以上讲解,我们怎么让javac命令可以使用呢?非常简单,我们只要把“C:\ProgramFiles\Java\jdk-9.0.4\bin;”路径配置到环境变量path当中即可(配置过程可以参见ipconfig的配置过程),配置完成之后,再次测试javac命令,如下图所示:
到此为止,javac命令终于可以使用了。另外,在javac命令后面添加“-version”参数可以查看编译器的版本,如下图所示:
到这里,大家应该掌握环境变量path的作用以及配置了吧!
通过以上内容的学习,大家应该思考这样一个问题:path环境变量是隶属于java的吗,它和java有关系吗?答案是:path环境变量隶属于windows操作系统,不属于java范畴,是windows操作系统搜索某个命令文件的路径依据。
windows操作系统到底是如何搜索命令文件的呢?实际上它会先在当前路径下找,找不到的时候会自动去环境变量path的路径中查找,找到则执行该命令,找不到则在DOS窗口中提示错误信息。希望大家以后遇到类似的“在DOS命令窗口中输入某个命令时出现找不到命令”这样的问题都能够独立的解决。
通过以上的配置,javac命令已经可以使用了,那么它具体怎么使用才能编译java程序呢,接下来我们详细的讲解一下javac命令的具体使用办法。
javac命令的语法格式是:“javac源文件路径”,非常简单,就是javac命令后面加上java源文件的路径,之前我们讲解cd命令的时候说过路径包括绝对路径和相对路径。也就是说javac后面的源文件路径可以是绝对的也可以是相对的,如下图所示:
通过上图,我们可以看到,在java源程序编译之前,只有一个文件“HelloWorld.java”。继续看下图:
我们可以看到,javac命令后面使用了java源文件的绝对路径,编译之后,生成了一个新的文件“HelloWorld.class”,我们称为字节码文件。另外,这也说明java源文件没有语法错误,通过了编译。那么,除了这种绝对路径的方式,相对路径的方式应该怎么做呢?请看下图:
我们可以看到先使用cd命令将目录切换到chapter01目录下,为什么要切换到该目录下呢,这是因为HelloWorld.java文件就在chapter01目录下。这样一来,当前目录下存在HelloWorld.java文件,则直接使用“javacHelloWorld.java”进行编译。这种方式就表示使用了相对路径,记住:相对路径表示从当前所在的路径下作为起点开始找。细心的同学应该可以看到新生成的HelloWorld.class文件的最后修改时间变成了11:24,这说明编译通过了。
我们把HelloWorld.java文件中的程序故意修改让其出现错误,请看以下代码:
从以上的代码中可以看到main方法的参数应该是(String[]args),结果写成了(Strin[]args),这种情况下语法是错误的,那么此时编译会发生什么呢,请看下图:
我们可以看到,编译器报错了,字节码文件并没有生成,并且提示了很详细的错误信息,“错误:找不到符号”就表示某种类型未定义。以后开发中大家可能还会经常遇到这样的错误。
通过以上的测试,可以得出这样的结论:java源代码中存在语法错误,在编译的时候编译器会提示错误消息,并且不会生成class字节码文件。在以后的学习过程当中,编译错误的这些提示信息可以积累一下,有利于大家以后的开发。
小结:通过本小节的学习,每位同学必须掌握path环境变量的作用,以及它是怎么配置的,还有怎么使用javac编译java源程序。
1.8.5 运行HelloWorld程序
程序通过以上的编译之后,接下来我们就可以运行程序了。在这里先给大家普及一下,在Java中有一个“类名”的概念,什么是类名呢,假设字节码是A.class,则类名为A,字节码是B.class,则类名为B。之前生成的字节码文件是,那么类名则为HelloWorld。
程序怎么运行呢,这个时候就需要借助JDKbin目录下的java.exe命令了,我们先来测试这个命令是否可以在DOS窗口中使用,请看下图:
经过测试,我们看到java.exe命令是可以在DOS窗口中直接使用的,其实只要javac.exe可以使用,那么java.exe就一定可以使用,因为javac.exe和java.exe都在JDK的bin目录下,之前的课程中这个目录已经配置到环境变量path当中,所以这里java.exe肯定也是可以使用的。
那么java.exe具体怎么使用呢,这里需要大家记忆语法,语法格式为:“java类名”,需要注意的是java命令后面是类名,而不是class文件的名字,也不是class文件的路径,不能这样写javaHelloWorld.class,也不能这样写javaE:\course\JavaProjects\01-JavaSE\chapter01\HelloWorld.class,只能这样写:javaHelloWorld。
那么它的运行原理是什么呢?实际上是这样的:在命令窗口中输入“javaHelloWorld”回车之后,先启动的是类加载器(类加载器ClassLoader主要的作用是将类名所对应的class文件装载到JVM当中,这里不再赘述,以后再详细学习类加载器),类加载器从硬盘上查找HelloWorld.class字节码文件(为什么会查找HelloWorld.class呢?为什么不是查找A.class或者B.class文件呢?这是因为运行的命令是javaHelloWorld,命令中指定的是HelloWorld类,则类加载器查找的就是HelloWorld.class文件),默认情况下类加载器只从当前路径下查找,查找到之后则将HelloWorld.class文件加载到JVM并执行,如果没有查找到则会出现错误信息,请看下图:
我们可以看到,先使用cd命令将路径切换到HelloWorld.class字节码文件所在的目录,并使用dir命令查看一下,确认当前路径下存在HelloWorld.class文件,然后执行javaHelloWorld命令,执行结果是向控制台输出了“动力节点-口口相传的Java黄埔军校”。
由于默认情况下类加载器只从当前所在的路径下加载字节码文件,如果该字节码文件不存在会提示什么错误信息呢?请看下图:
我们可以看到上图中是在chapter01目录下使用dir命令查看A.class文件是否存在,结果是A.class文件在当前目录下是不存在的,我们使用“javaA”来执行,发现出错了,错误信息是:找不到或无法加载主类A。换句话说也就是类加载器在硬盘上找不到A.class文件导致的错误。
在上面我们已经说过了:类加载器默认从当前路径下加载字节码,那么可以让类加载器从指定的目录下加载class文件吗?答案是可以的,这个时候就需要借助classpath这个环境变量了(classpath环境变量隶属于java语言,专门给类加载器指路的),接下来我们来设置一下classpath环境变量,此处把classpath设置为E:\,如下图所示:
配置环境变量classpath的时候,这个变量不像path是已经存在的,我们只需要修改就行了,而classpath是不存在的,这里我们需要新建。在系统变量栏下点击新建即可,变量名填写classpath,变量值填写E:\,然后点击确定即可。设置完成后关闭所有DOS窗口,重新开启新窗口,接下来按照下图操作:
我们可以看到先使用cd命令将目录切换到chapter01下,并使用dir命令查看,确认HelloWorld.class文件在当前目录下是存在的,然后使用javaHelloWorld运行程序,结果出错了。错误信息之前我们已经见过了,表达的意思就是HelloWorld.class文件没找到。这是为何呢?这是因为当环境变量classpath设置为固定的E:\路径之后,类加载器只会从E:\路径下查找字节码文件,不再从当前路径下加载。我们来试试把HelloWorld.class文件放到E:\目录下呢?请看下图:
通过上图我们可以看到,将HelloWorld.class文件移动到了E:\目录下,然后再执行javaHelloWorld,我们可以看到程序正常执行了。
通过以上的测试可以得出,当设置环境变量classpath=E:\的时候,类加载器只去E:\目录下加载class文件了,不再从当前目录下加载,也不会去其它目录下加载。大家在这里思考一下,如果将classpath配置成一个指定的路径,例如classpath=E:\,这样我们以后的开发会不会很麻烦呢?
答案是非常麻烦,这是因为每一次编译生成的class文件都要放到E:\目录下类加载器才能找到,所以目前来说classpath环境变量是不需要配置的,因为classpath在没有配置的情况下,类加载器默认会从当前所在的目录下加载class,也就是说以后要想运行class首先要将DOS窗口的目录切换(cd命令)到class文件所在的位置,然后再运行。当然我们也可以把环境变量classpath配置为:classpath=.,因为.代表当前路径。
在本课中我们就不再设置环境变量classpath了,大家可以将classpath环境变量删除。或者您要是想配置classpath的话,就把classpath配置为.就行了。有同学认为,既然是这样我们为什么还要学习classpath环境变量呢,这是因为在java开发中有很多第三方的类库需要在我们的项目中引入,等需要引入其它类库的时候,我们就需要将这些类库的路径配置到classpath当中了。并且classpath可以配置多个路径,注意路径和路径之间采用半角分号分隔。例如以后学到java连接数据库JDBC的时候,环境变量就需要配置为classpath=.;xxxx.jar,它表示的含义是类加载器可以从当前路径下加载,也可以去指定的jar包中加载字节码文件了。这里不再赘述,学到JDBC的时候再说。
小结:通过以上内容的学习,我们知道classpath环境变量不属于windows操作系统,是java编程语言当中的一种机制,这种机制是专门为类加载器加载class文件时提供路径依据的。最终的结论是classpath环境变量目前是不需要配置!当然,随着后面内容的学习,大家会知道classpath环境变量总有一天是需要配置的,到那个时候大家可别忘了将“当前路径.”配置到classpath当中,例如:classpath=.;path1;path2;,如果没有把.配置到classpath当中,那么类加载器就不再从当前路径下加载class了。
到此为止大家必须掌握两个重要环境变量,一个是windows操作系统的path环境变量,另一个是java语言的classpath环境变量。至于有些参考资料上还提到了JAVA_HOME等环境变量,其实这些环境变量对于我们目前来说是不需要配置的,以后用到的时候再说吧。
1.9 对HelloWorld程序的解释(理解)
HelloWorld 程序的代码如下所示:
接下来,我们对这个代码进行简单的解释,这里只是一个简单的说明,要彻底弄明白还需要后面课程的铺垫,大家耐心等待。对于以上的程序我要说这么几点:
第一:public 表示公开的(关键字,固定写法)
第二:class 用来声明一个类(关键字,固定写法)
第三:HelloWorld 是一个类名(既然是一个名字,就可以改成其它的名字)
第四:public class HelloWorld 表示声明一个公共的类 HelloWorld
第五:在 java 编程中,一定要注意成对儿的符号要成对儿写,以上 HelloWorld 当中成对儿的符号包括:小括号(),中括号[],大括号{},双引号""。这些符号在编写的时候建议成对儿编写。
第六:最初学习 java 编程的时候一定要注意代码的格式,要有合理的缩进,什么时候缩进呢?大家需要记住:只要“我”这个大括号{}包含着“你”,那么“你”就应该比“我”低一级,此时“你”应缩进。
第七:类体的概念,在以上程序中 HelloWorld 后面的大括号{},这个大括号{}里被称为类体。如下所示:
public class HelloWorld {
// 类体
}
第八:程序入口,java 中规定程序的入口是一个固定的写法,必须像以下代码一样,不这样写,程序无法执行:
public static void main(String[] args) {
// 方法体
}
第九:以上程序的入口又叫做 main 方法,或者叫做主方法。大家记住固定写法即可。另外在 main 方法后面的大括号{}我们称之为方法体,方法体也是由大括号括起来的。
第十:方法体由一条一条 java 语句构成,每一条 java 语句必须以“;”结束。方法体当中的代码遵循自上而下的顺序依次逐行执行。
以上对 java 的入门程序 HelloWorld 进行了简单说明,有一些内容现在无法彻底搞明白,学习后面内容之后大家就理解了。
1.10 Java中的注释(掌握)
1.10.1 注释的作用
注释是对代码的解释和说明,其目的是让程序员能够更加快速的理解代码。它是编写程序时,写程序的人给一个语句、程序段等的解释或提示,能提高程序代码的可读性。我认为添加注释,是为了程序更容易理解与维护,特别是维护,更是对自己代码负责的一种体现。
注释在编译的时候不会生成到class字节码文件当中,它只在java源文件中保留。
1.10.2 注释的三种方式
Java 语言的注释包括三种方式,它们分别是:
第一种:单行注释,语法格式如下:
// 单行注释,两个正斜杠后面的内容被注释
第二种:多行注释,语法格式如下:
/*
* 这里的注释信息为多行注释:
* 第 1 行注释信息
* 第 2 行注释信息
*/
第三种:javadoc 注释。
/**
* 这里的信息是 javadoc 注释
* @author 作者名字
* @version 版本号
* @since 自从哪个版本号开始就存在了
*/
注意:对于javadoc注释来说,这里的注释会被JDKbin目录下的javadoc.exe命令解析并生成帮助文档(生成帮助文档后期做项目的时候大家会接触到的)。
1.10.3 注释应该怎么写
编写注释是程序员最基本的素质,养成编写注释的好习惯,要有编写注释的意识。当然,写注释也是有技巧的,不是所有位置都写,不是把写的代码原版翻译过来,老程序员往往在写注释的时候,不多不少,能够做到恰到好处,几句话就可以描述清楚程序的核心功能。
通常要在类和接口上写注释,这一部分注释是必须的。在这里,我们需要使用javadoc注释,需要标明:创建者,创建时间,版本,以及该类的作用。在方法中,我们需要对入参,出参,以及返回值,均要标明。对常量,我们需要使用多行注释,进行标明该常量的用途。在关键算法上,添加注释并且按照顺序依次标明,写明白该方法为什么这么做。
记住:注释的作用不在于表示代码的含义,而在于表示代码的功能。希望在以后的课程当中通过慢慢的培养,能够写一手漂亮的注释,当然,目前大家只需要掌握注释有哪几种,分别写到什么符号里就行了。
1.10.4 为HelloWorld提供注释
接下来我们为HelloWorld程序提供简单的注释信息,来练习一下注释的编写:
通过以上代码我们可以看到,HelloWorld类上写了一个javadoc注释,在javadoc注释中提供了对这个类的整体描述信息、作者信息、版本号信息等。在main方法上提供了javadoc注释,对这个方法进行了说明,对参数进行了说明等。在输出信息的那行代码上提供了单行注释,说明了这行代码的作用。
1.11 public class和class的区别(掌握)
在以上的程序中,我们看到HelloWorld类在定义的时候使用了关键字public,那么一个类声明的时候可以不使用public吗?我们一起来看看它们有什么区别?
我们先来进行一个实验,看看一个java源文件中是否可以定义多个class,请看下图:
我们可以看到创建了一个A.java源文件,在该文件中定义了三个类,分别是B类、C类和D类,使用javac命令编译之后生成了三个字节码,分别是B.class、C.class、D.class。
通过以上的测试可以得出:一个java源文件中可以定义多个class,并且在编译的时候一个class会对应编译生成一个class字节码文件。还有,public的class可以没有。
接下来,我们在A.java源代码中继续定义一个“公开的类E”,请看下图:
我们可以看到,定义公开的类E之后,再次编译,编译器报错了。并且提示的错误信息是:类E是公共的,应在名为E.java的文件中声明。换句话说在A.java文件中定义的公共的类的名字必须是A,不能是其它名称。也间接说明在同一个java文件中公共的类只能有一个(注意:在同一个java文件中类名不能重名)。
通过以上的测试可以得出:如果定义publicclass的类,只能定义一个,并且要求此类名必须和java源文件名保持一致。(这是规则记住就行,学计算机编程语言有很多知识点在学习的时候很难理解,只能靠记忆,随着后面内容的学习,大家会对以前困惑的知识点有所理解)。
接下来,我们在每一个类的类体当中都定义main方法,都写上程序的入口,看看是否可以编译和运行:
我们可以看到,在每一个class中都可以编写main方法,想让程序从哪个入口进去执行则加载该类即可。
通过以上的测试可以得出:任何一个class中都可以设定程序入口,也就是说任何一个class中都可以写main方法(主方法),想从哪个入口进去执行,则让类加载器先加载对应的类即可,例如:想让A类中的main方法执行,则执行:javaA,想让B类中的main方法执行,则执行:javaB。但实际上,对于一个完整的独立的应用来说,只需要提供一个入口,也就是说只需要定义一个main方法即可。
还有,在实际的开发中,虽然一个java源文件可以定义多个class,实际上这是不规范的,比较规范的写法是一个java源文件中只定义一个class。
1.12 章节小结
本章节的主要内容是带领大家搭建Java的开发环境,编写第一个Java程序。在这个过程当中经历了JDK的安装,环境变量path和classpath的配置,Java程序的编写、编译和运行。其中重点是需要大家理解path和classpath环境变量的作用以及如何配置。另外能够顺利的默写HelloWorld程序(不参考任何代码)。能够给HelloWorld程序提供简单的注释信息。
1.13 难点解惑
1.13.1 JDK常用基本组件
JDK常用的基本组件包括:javac(编译器)、java(运行java程序)、javadoc(提取java程序的注释信息并生成帮助文档)、jar(打jar包)、jdb(查错工具)、javap(反编译器)、jconsole(系统调试和内存监控工具)等。
以上有一些组件目前还没有接触到,随着后面内容的学习,大家会接触到的。
1.13.2 运行时出现"无法加载主类"
遇到这种情况,如下图所示:
可能有以下几方面原因:
第一:在运行java程序时,目录没有切换到class文件所在的路径下。运行时,先使用dir命令查看当前路径下是否存在xxx.class文件。
第二:如果切换到class文件所在的路径下,还是出现以上问题,说明手动配置了环境变量classpath,并且所配置的环境变量classpath当中没有当前路径“.”。要么将classpath删除,要么在classpath环境变量中添加当前路径“.”。
1.14 章节习题
第一题:编写Java程序,输出学生的基本信息,输出结果如下图所示:
第二题:编写Java程序,输出京东商城商品列表信息,输出结果如下图所示:
1.15 习题答案
第一题答案:
第二题答案:
1.16 day01课堂笔记
第一章 Java开发环境的搭建
1、常用的DOS命令
1.1、怎么打开DOS命令窗口
win键 + r (组合键):可以打开“运行”窗口
在运行窗口文本框中输入: cmd,然后回车
1.2、什么是DOS命令呢?
在DOS命令窗口中才可以输入并执行DOS命令。
在最初的windows计算机中没有图形界面的,只有DOS命令窗口。
也就是说通过执行DOS命令窗口可以完全完成文件的新建、编辑、保存、删除等一系列操作。
1.3、mkdir abc(这个命令不是必须掌握的)make directory(创建目录)
创建一个目录,起名abc
1.4、默认情况下DOS命令窗口打开之后,定位的位置是哪里?
C:\Users\Administrator 这是默认的当前路径
1.5、在DOS命令窗口中怎么复制内容?
win7:任意位置点击右键-->标记-->选中要复制的内容-->点击右键-->此时就到剪贴板里面了
win10:左键直接选中,然后右键单击一下就到剪贴板里面了。
1.6、切换盘符?
直接输入盘符就行:
c: 回车
d: 回车
e: 回车
f: 回车
就OK了。
当切换到D盘根下了,那么当前路径就是:D:\>
当前路径是当前所在的位置。
1.7、切换目录?(非常重要,必须掌握)
使用cd命令来完成目录的切换:cd是什么含义?change directory(改变目录)
cd命令怎么用,语法格式是什么?
cd 路径
路径在windows系统上包括:相对路径和绝对路径。
什么是相对路径呢?
一定要注意,从路径形式上来看,相对路径是一定不会以盘符开始的。
相对路径:相对路径一定是相对于当前所在“位置”而言的。相对路径是相对于当前而言,从当前所在的位置作为起点。
死记:相对路径一定是从当前位置作为起点开始找。
什么是绝对路径呢?
在windows操作系统中凡是路径起点是盘符的都是绝对路径,例如:
C:\Users\Administrator
C:\Users
C:\Users\Public\gakataka
C:\Windows\System32
D:\BaiduNetdiskDownload
D:\course\01-开课\OneNote
注意:
cd .. 回到上级路径。(..其实是一个目录,名字就叫..,可以从当前目录切换到上级目录)
cd \ 直接回到根路径。
. 一个点,代表当前路径。(cd命令用不着。以后配置环境变量的时候一个点有用处。)
1.8、cls 清屏
1.9、dir 查看当前目录下有啥东西。
1.10、exit 退出DOS命令窗口。
1.17 day02课堂笔记
1、常用的DOS命令(续)
1.1、del命令,删除一个或者多个文件
删除T1.class文件
C:\Users\Administrator>del T1.class
删除所有.class结尾的文件,支持模糊匹配
C:\Users\Administrator>del *.class
T1.class
T1.glass
del *ass 这个命令就会将T1.class和T1.glass都删除。
删除的一定是能匹配上的。
del *.class 这个命令中的那个“.”不要特殊化,这个“.”其实就是一个普通的字母
1.2、怎么查看本机的IP地址?
什么是IP地址?有什么用呢?
A计算机在网络当中要想定位到(连接到)B计算机,那么必须要先知道B计算机的IP地址,IP地址也可以看做计算机在同一个网络当中的身份证号(唯一标识)。
IP地址就相当于电话号码是一个意思。
ipconfig(ip地址的配置信息。)
ipconfig /all 该命令后面添加一个/all参数可以查看更详细的网络信息。 这个详细信息中包括网卡的物理地址,例如:70-8B-CD-A7-BA-25
这个物理地址具有全球唯一性。物理地址通常叫做MAC地址。
1.3、怎么查看两台计算机是否可以正常通信?
ping命令
语法格式:
ping IP地址
ping 域名
ping www.baidu.com
ping 61.135.169.121 (61.135.169.121是百度的IP地址)
ping 61.135.169.121 -t (-t参数表示一直ping)
一直ping的目的可以查看网络是否稳定。
在一个DOS命令窗口中如果有一个命令一直在执行,想强行终止怎么办?
ctrl + c 组合键
http://www.baidu.com 可以打开百度(这种方式比较方便,域名更容易记忆。)
http://61.135.169.121 也可以打开百度
域名底层最终还是会被解析成IP地址的形式。
2、文本编辑快捷键:
2.1、掌握常用的通用的文本编辑快捷键很重要,可以大大提升开发效率。
所以,必须熟练掌握,从此刻开始强迫自己少用鼠标,用组合键快捷键的方式。
2.2、常用的组合键都有哪些?
复制 ctrl + c
粘贴 ctrl + v
剪切 ctrl + x
保存 ctrl + s
撤销 ctrl + z
重做 ctrl + y
回到行首:home键
回到行尾:end键
当光标在行尾,怎么选中一行?
shift + home键
当光标在行首,怎么选中一行?
shift + end键
回到文件头:ctrl + home
回到文件尾:ctrl + end
全选:ctrl + a
查找:ctrl + f
---------------------------(以上必须会用)--------------------------
选中一个单词:鼠标双击
选中一行:鼠标连续击3次
不用鼠标选中一个单词:ctrl + shift + 右箭头/左箭头
3、计算机编程语言发展史?
第一代语言:机器语言
程序员直接编写二进制,一串二进制代码,例如:10010100010010001000....
计算机是由电流驱动的,电流只能表示两种状态:正、负。
而正可以对应1,负可以对应0.
10010010101010...这些二进制码正好和自然世界中的十进制存在转换关系。
所以很巧妙的是:计算机可以模拟现实世界当中的事物。
机器语言时期非常具有代表性的就是:打孔机。
缺点:
纸带不容易保存
另外打孔的时候是人为操作的,孔有可能打错了。孔打错了纸带就废了。
第二代语言:低级语言
非常具有代表性的:汇编语言。
汇编语言比机器语言更接近人类自然语言。
但是汇编语言还是需要专业人士进行开发,一般人拿到汇编语言也读不懂。
第三代语言:高级语言
高级语言完全接近人类自然语言,具有代表性的:
C语言:面向过程的
C++语言:一半面向过程,一半面向对象
Java语言:完全面向对象(java语言底层实际上是C++实现的。)
Python语言:面向对象
....
计算机编程语言是什么?
是一个团队,或者一个组织制定的一套固定的语法规则,你可以学习这套语法规则,然后通过这套语法规则和计算机交互。
我们为什么要学习汉语?
原因是我们学习了汉语之后,可以完成人和人的沟通。
我们为什么要学习日语?
因为我们要和日本人沟通。。。
4、Java语言的概述以及Java语言的发展史。
JDK(Java开发工具箱,做Java开发必须安装的,这是最根本的一个环境。)
JDK不是集成开发环境。
JDK这个开发工具箱中是Java最核心的库。
98年的时候:Java升级到JDK1.2,Java被分为三大块:
J2SE:标准版(基础,要学java,必须先学习SE。基础语法+基础库)
J2EE:企业版(专门为企业开发软件,为企业提供解决方案。例如:OA办公系统,保险行业的系统,金融行业的系统,医院系统....)
J2ME:微型版(专门为微型设备做嵌入式开发的。)
java诞生十周年改了名字:
JavaSE
JavaEE
JavaME
1.18 day03课堂笔记
1、Java语言的特性
1.1、简单性
在Java语言当中真正操作内存的是:JVM(Java虚拟机)
所有的java程序都是运行在Java虚拟机当中的。而Java虚拟机执行过程中再去操作内存。
对于C或者C++来说程序员都是可以直接通过指针操作内存的。
C或者C++更灵活,可以直接程序员操作内存,但是要求程序员技术精湛。
C语言或者C++更有驾驭感。
Java语言屏蔽了指针概念,程序员不能直接操作指针,或者说程序员不能直接操作内存。这种方式有优点也有缺点:
优点:不容易导致内存泄漏。(简单了。)
缺点:效率问题,包括驾驭感比较差。
飞机航行:
如果是C语言表示程序员是飞机驾驶员。
如果是Java语言表示程序员是飞机上的乘客。
Java语言底层是C++,所以JVM是用C++语言写好的一个虚拟的电脑。
JVM在哪里?告诉大家,安装了JDK之后,JVM就代表安装好了。
内存是什么?
对于计算机来说:最主要的几个部件是什么?
CPU:
中央处理器,相当于人类的大脑,负责发送并执行指令。是整个计算机的指挥官。CPU是负责计算的,负责运算的。
10 + 20 = 30
CPU负责将30这个结果计算出来。
但是在计算过程中有三个数据需要临时找个空间存储一下:
这三个数据分别是:10 20 30
内存:
程序运行过程当中的临时数据存储空间。
断电之后或者关机之后内存中的数据就消失了。
硬盘:
持久化设备,硬盘上的数据不会因断电而丢失。
主板:
相当于人类的躯干,是一个载体:
CPU、内存条、硬盘等主要的部件都是放在主板上的,主板上有很多线,将以上的部件链接起来。
.....
1.2、java是堪称完全面向对象的。
面向对象更容易让人理解,人类通常是以对象的方式认知世界的。
采用面向对象的方式可以让复杂问题简单化。
1.3、健壮性
主要是因为Java中有一种机制:自动垃圾回收机制(GC机制)。
java语言是健壮的,相对于C语言来说,C语言没有Java健壮。
Java不容易导致内存的泄漏。
C++或者C语言使用不当时很容易导致内存泄漏。
JVM负责调度GC机制。程序员不需要干涉。
以上讲解中又描述了这几个术语:
JVM(C++语言写的一个虚拟的计算机)、GC(垃圾回收机制)
1.4、java完全/完美支持多线程并发。
1.5、可移植性/跨平台
java语言只要编写一次,可以做到到处运行。
例如:java程序编写完之后,可以运行在windows操作系统上,不需要做任何改动可以直接运行在Linux操作系统上,同样也可以运行到MaC OS上面。
一次编写,到处运行。(平台改变了,程序不需要改。)
JVM这种机制实现了跨平台,那么这种机制优点和缺点分别是什么?
优点:一次编写到处运行,可以跨平台。
缺点:麻烦。对于运行java程序来说必须先有一个JVM。
就像你要想在网页上看视频,你必须先安装一个flash是一样的。
001-java怎么实现跨平台的
Java语言可以编写病毒吗?
可以,没问题。但是很难让用户中毒。
中毒的一般都是java程序员。所以很少有人编写java的病毒脚本。
2、JDK、JRE、JVM三者之间的关系?
JDK:Java开发工具箱
JRE:java运行环境
JVM:java虚拟机
JDK包括JRE,JRE包括JVM。
JVM是不能独立安装的。
JRE和JDK都是可以独立安装的。
有单独的JDK安装包,也有单独的JRE安装包,没有单独的JVM安装包。
安装JDK的时候:JRE就自动安装了,同时JRE内部的JVM也就自动安装了。
安装JRE的时候:JVM也就自动安装了。
问题:
假设你在软件公司开发了一个新的软件,现在要去客户那边给客户把项目部署一下,把项目跑起来,你需要安装JDK吗?
只需要安装JRE就行了。JRE体积很小,安装非常便捷快速。
问题:
为什么安装JDK的时候会自带一个JRE?
因为java程序员开发完程序之后,要测试这个程序,让这个程序运行起来,需要JRE。所以JDK安装的时候内部自带一个JRE。
3、到目前为止,我们接触过的重点术语,总结一下:
Java体系的技术被划分为三大块:
JavaSE:标准版
JavaEE:企业版
JavaME:微型版
安装JDK之后:
JDK:Java开发工具箱
JRE:Java运行环境
JVM:Java虚拟机
4、对Java的加载与执行的理解(理论比较重要)
java程序从编写到最终运行经历了哪些过程????
java程序非常重要的两个阶段:
编译阶段
运行阶段
注意:java程序员直接编写的java代码(普通文本)是无法直接被JVM识别的。java程序员编写的java代码这种普通文本必须经过一个编译,将这个“普通文本代码”变成“字节码”,JVM能够识别“字节码”。
java代码这种普通文本变成字节码的过程,被称为:编译。
java代码这种普通文本被称为:java源代码。(你编写的代码是源代码)
源代码不能直接执行,需要先进行编译,生成源代码对应的“字节码”
JVM可以识别的是字节码。
编译阶段和运行阶段可以在不同的操作系统上完成吗?
在windows上编译,编译之后生成了“字节码”,把“字节码”放到linux上运行?
完全可以,因为Java是跨平台的,可以做到一次编写到处运行。
java源代码一旦编译之后,源代码可以删除吗?只留下字节码可以执行吗?
完全可以执行,因为源代码不参与程序的执行过程,参与程序执行过程的是字节码。
但是最好不要删除源代码。因为有可能执行结果不是你需要的,当执行结果不是你需要的时候,你可以重新打开源代码进行修改,然后重新编译生成新的字节码,再重新执行。这样会有新的执行效果。
放源代码的文件扩展名必须是:xxx.java
并且需要注意的是:编译生成的字节码文件扩展名是:xxx.class
没有为什么,死记硬背吧!!!!
.java文件就是源文件,这个文件中编写源代码。
.class文件就是字节码文件,这个文件是编译源代码而得到的。
另外需要注意的是:
1个java源文件是可以编译生成多个class文件的,最终运行的是class文件。
问题:字节码文件是二进制文件吗?
字节码文件不是二进制文件,如果是二进制的话,就不需要JVM了。因为操作系统可以直接执行二进制。
java程序从开发到最终运行经历了什么?
编译期:(可以在windows上)
第一步:在硬盘的某个位置(随意),新建一个xxx.java文件
第二步:使用记事本或者其它文本编辑器例如EditPlus打开xxx.java文件
第三步:在xxx.java文件中编写“符合java语法规则的”源代码。
第四步:保存(一定要将xxx.java文件保存一下)
第五步:使用编译器(javac【JDK安装后自带】)对xxx.java文件进行编译。
第六步:如果xxx.java文件中编写的源代码是符合语法规则的,编译会通过;如果xxx.java文件中编写的源代码违背了语法规则,那么编译器会报错,编译器报错之后class文件是不会生成的,只有编译通过了才会生成class字节码文件。
并且一个java源文件是可以生成多个class文件的。(编译实质上是检查语法)
运行期(JRE在起作用):(可以在windows上,也可以在其他的OS上。)
第七步:如果是在Linux上运行,需要将windows上生成的class文件拷贝过去,不需要拷贝源代码,真正运行的是字节码。(但是源代码也不要删除,有用)
第八步:使用JDK自带的一个命令/工具:java(负责运行的命令/工具)执行字节码
第九步:往下的步骤就全部交给JVM了,就不需要程序员干涉了。
JVM会将字节码文件装载进去,然后JVM对字节码进行解释(解释器负责将字节码解释为1010101010..等的二进制)
第十步:JVM会将生成的二进制码交给OS操作系统,操作系统会执行二进制码和硬件进行交互。
注意:在以上的过程中,需要使用两个非常重要的命令?
javac 命令,负责编译
java 命令,负责运行
小插曲:
xxx.java源文件经过编译之后生成了A.class、B.class、C.class等文件,那么我们称A是一个类、B是一个类、C是一个类。其中A、B、C是类的名字。
没有为什么,死记硬背,SUN公司的java语法就是这么规定的。
A/B/C是类的名称。A类、B类、C类。
源文件中编写的代码叫做:源代码。
以上是一个复杂的过程,那么缩减一下,程序员到底要干啥?
新建java文件
打开java文件
写java源代码
保存
javac命令编译
java命令运行
编写、编译、运行
5、编写java中的第一个java程序:HelloWorld(你好世界:问世)
这个程序不需要大家理解,大家照抄就行,因为目前我也不会讲解这个程序为什么这么写。
主要是为了搭建java的开发环境,测试java的环境是否能用。
第一步:安装文本编辑器(EditPlus)
第二步:安装JDK(先下载JDK)
安装JDK13,直接下一步就行。
JDK13安装的时候内置了一个JRE,独立于JDK之外的JRE并没有生成。
对于java13来说,如果你希望生成一个独立于JDK之外的JRE的话需要执行特殊的命令。这里先不讲,后期用到的时候再说。
注意的是:
JDK8安装的时候,不仅JDK内置了一个JRE,而且还会在JDK目录之外,独立的生成一个单独的JRE。(以前低版本的时候,JRE实际上是有2个。)
一个是JDK内置的,一个是独立于JDK之外的。
JDK的bin目录下有:
javac.exe 负责编译
java.exe 负责运行
第三步:写代码
写一下第一个程序HelloWorld。
这个代码在此强调:
文件名照抄
文件内容代码严格照抄
照抄大小写
照抄任何一个环节
照抄标点符号
不要问为什么。
后面会解释。
括号:
[]
()
{}
都要成对写。
第四步:编译
第五步:运行
1.19 day04课堂笔记
1、开发第一个java程序:HelloWorld
1.1、程序写完之后,一定要ctrl + s 进行保存
第一个HelloWorld程序照抄就行了,不要问代码为什么这么写。另外,大家需要注意的是:java源代码只要修改,必须重新编译。重新编译生成新的class字节码文件。
1.2、编译阶段
怎么编译?使用什么命令?这个命令怎么用?
需要使用的命令是:C:\Program Files\Java\jdk-13.0.2\bin\javac.exe
这个命令需要先测试一下,打开DOS命令窗口,看看javac命令是否可用。
C:\Users\Administrator>javac
'javac' 不是内部或外部命令,也不是可运行的程序或批处理文件。
这说明:windows操作系统没有发现“javac.exe”命令在哪里。
windows操作系统没有找到javac.exe文件在哪。
为什么ipconfig、ping等命令可以使用呢?为什么javac用不了?
我们发现windows操作系统中有这样一个环境变量,名字叫做:Path,并且发现Path环境变量的值是: C:\Windows\system32;%SystemRoot%;%SystemRoot%\System32\Wbem;%SYSTEMROOT%\System32\WindowsPowerShell\v1.0\
我们还发现了在:C:\Windows\System32 这个目录下存在:ipconfig.exe
注意:修改完环境变量之后,DOS命令窗口必须关闭重新打开才会起作用。
将Path环境变量中的:C:\windows\system32; 删除之后
再测试:
C:\Users\Administrator>ipconfig
'ipconfig' 不是内部或外部命令,也不是可运行的程序或批处理文件。
配置环境变量Path的步骤:
桌面计算机上右键-->属性-->高级系统设置-->环境变量
怎么修改Path环境变量?
找到Path,鼠标双击!!!!
Path环境变量当中都是路径,路径和路径之间必须采用“半角的分号”分隔。
让javac.exe能用,我们配置哪个路径到Path中?
将C:\Program Files\Java\jdk-13.0.2\bin配置到Path当中。
注意:环境变量包括“系统变量”和“用户变量”
系统变量:范围比较大,系统变量会让计算机所有用户都起作用。
用户变量:范围比较小,这个变量只是作用于当前用户。
怎么查看编译器版本?
C:\Users\Administrator>javac -version
javac 13.0.2
怎么查看java虚拟机的版本?
C:\Users\Administrator>java -version
java version "13.0.2" 2020-01-14
Java(TM) SE Runtime Environment (build 13.0.2+8)
Java HotSpot(TM) 64-Bit Server VM (build 13.0.2+8, mixed mode, sharing)
问题1:path环境变量的作用是什么?
path环境变量的作用就是给windows操作系统指路的,告诉windows操作系统去哪里找这个命令文件。path环境变量中有很多很多的路径,路径和路径之间用半角分号分隔。
path=A;B;C;D......
path是环境变量的名字,A;B;C;D......是环境变量path的值。
问题2:path环境变量是java中的机制,还是windows操作系统中的机制?
path环境变量是隶属于java的吗?path环境变量和java有关系吗?
path环境变量本质上是隶属于windows操作系统的,和java没有关系。java只不过用了一下path环境变量。
要学会融会贯通,学一通百:
以后在安装其他软件之后,希望在DOS命令窗口中使用该软件的某个命令的时候,如果出现“命令找不到错误了”,这个时候希望大家能够想起来配置环境变量path。
path环境变量中的路径可以指定多个,没问题,多少个都行。
javac命令怎么用?
语法格式先背会:javac java源文件的路径
什么是java源文件?
java源文件的名字以“.java”结尾,该文件中写了java源代码。
java源文件的路径是什么意思?
注意:路径永远包括绝对路径和相对路径。
注意:神操作????
把java源文件直接拖进到DOS命令窗口,那么DOS命令窗口就有这个路径了。
C:\Users\Administrator>javac D:\course\JavaProjects\02-JavaSE\chapter01\HelloWorld.java
D:\>javac course\JavaProjects\02-JavaSE\chapter01\HelloWorld.java
D:\course\JavaProjects>javac 02-JavaSE\chapter01\HelloWorld.java
D:\course\JavaProjects\02-JavaSE\chapter01>javac HelloWorld.java
以上的四种方式都行,第一种方式是绝对路径
剩下三种方式都是相对路径。
C:\Users\Administrator>javac course\JavaProjects\02-JavaSE\chapter01\HelloWorld.java
错误: 找不到文件: course\JavaProjects\02-JavaSE\chapter01\HelloWorld.java
用法: javac <选项> <源文件>
使用 --help 可列出可能的选项
以上报错的原因是:java源文件的路径写错了。
C:\Users\Administrator>javac HelloWorld.java
错误: 找不到文件: HelloWorld.java
用法: javac <选项> <源文件>
使用 --help 可列出可能的选项
注意:神操作????
怎么在DOS命令窗口中快速定位到某个路径呢?
打开计算机-->打开一些文件夹-->在地址栏上直接输入cmd回车,这样直接就过去了。
编译报错的时候不会生成class字节码文件!
D:\course\JavaProjects\02-JavaSE\chapter01>javac HelloWorld.java
HelloWorld.java:3: 错误: 非法字符: '\uff1b'
System.out.println("Hello World");
^
1 个错误
1.3、运行阶段
运行的前提是:class文件(字节码)生成了。没有字节码文件程序是无法运行的。
重点重点重点重点重点重点重点重点重点!!!!!!!
假设该文件的名字叫做:HelloWorld.class
那么HelloWorld被称为??????????
HelloWorld 就是一个类名。
如果文件名是Test.class,那么:Test就是一个类名。
怎么运行,使用哪个命令?
使用JDK的bin目录下的:java.exe命令来运行。
先在DOS命令窗口中测试java.exe这个命令是否可用!!!
java -version
"java.exe"这个命令怎么用,语法格式是什么?
java 类名
java HelloWorld.class 对不对?????
不对!!!!
正确的写法是:java HelloWorld
千万千万要注意:java这个命令,后面跟的是“类名”,而绝对不能跟“文件路径”,因为java命令后面跟的不是文件,是一个“类名”。
对于这个字节:Test.class ,应该:java Test
对于这个字节码:A.class ,应该 java A
对于这个字节码:Hello.class,应该java Hello
.....
运行java程序需要哪些步骤呢?
第一步(必须这样做,这是必须的,先记住):
先使用cd命令切换到Test.class文件所在的路径。
第二步:执行java Test
切记:
java命令后面只要是跟路径,就一定不行。java命令后面只能跟类名。
2、到目前为止,大家告诉我,一共配置了哪些环境变量?
到目前为止,我们只配置了一个环境变量path,并且这个环境变量path和java实际上没关系,是人家windows操作系统的机制。
对于Java的JDK所属的环境变量,有一个叫做:JAVA_HOME
这个JAVA_HOME目前我们不需要,不配置这个环境变量也不会影响当前java程序的运行。
但是后期学习到JavaWEB的时候需要安装Tomcat服务器,那个时候JAVA_HOME就必须配置了。
那么除了JAVA_HOME环境变量之外,JDK相关的环境变量还有其他的吗?
答案:有的。
3、我们一起来研究一下:“java HelloWorld”的执行过程以及原理。
D:\course\JavaProjects\02-JavaSE\chapter01>java HelloWorld
敲完回车,都发生了什么?????
第一步:会先启动JVM(java虚拟机)
第二步:JVM启动之后,JVM会去启动“类加载器classloader”
类加载器的作用:加载类的。本质上类加载器负责去硬盘上找“类”对应的“字节码”文件。
假设是“java HelloWorld”,那么类加载器会去硬盘上搜索:HelloWorld.class文件。
假设是“java Test”,那么类加载器会去硬盘上搜索:Test.class文件。
.......
第三步:
类加载器如果在硬盘上找不到对应的字节码文件,会报错,报什么错?
错误: 找不到或无法加载主类
类加载器如果在硬盘上找到了对应的字节码文件,类加载器会将该字节码文件装载到JVM当中,JVM启动“解释器”将字节码解释为“101010000...”这种二进制码,操作系统执行二进制码和硬件交互。
问题?????
默认情况下,类加载器去硬盘上找“字节码”文件的时候,默认从哪找????
默认情况下类加载器(classloader)会从当前路径下找。
此处应该有疑问,你可以提出哪些问题????
能不能给类加载器指定一个路径,让类加载器去指定的路径下加载字节码文件。
答案:可以的。但是我们需要设置一个环境变量,叫做:classpath
classpath是一个环境变量,是给谁指路的?
答案:是给“类加载器”指路的。
classpath环境变量不属于windows操作系统,classpath环境变量隶属于java。
classpath环境变量是java特有的。
classpath=A路径;B路径;C路径.....
classpath是一个变量名
A路径;B路径;C路径.....是变量值
我们把classpath配置一下,这个环境变量在windows中没有,需要新建!!!!
计算机-->右键-->属性-->高级系统设置-->环境变量-->新建...
注意:变量名不能随意写:大小写无所谓,但必须叫做:classpath
CLASSPATH
ClassPath
Classpath
classpath
都行。
我目前是随意配置的:(重启CMD)
classpath=D:\course
非常重要的一个特点,必须记住:
配置了classpath=D:\course之后,类加载器只会去D:\course目录下找“xxx.class”文件,不再从当前路径下找了。
结论是:
到目前为止:classpath环境变量不需要配置。但你必须理解classpath环境变量是干什么的!!!!
你一定要理解classpath环境变量的作用是什么?
是给类加载器指路的。
在没有配置环境变量classpath的时候,默认从当前路径下加载。
如果配置了环境变量classpath的话,就只能从指定的路径下加载了。
path java_home classpath,这3个环境变量path需要配置,后面两个暂时不配置。
4、???????【让人困惑了!!!】(了解即可,不需要掌握,现阶段也不需要这样写)
在高版本的JDK当中,有这样的一个新特性,可以直接这样一步到位:
java x/y/z/xxx.java
java后面直接加java源文件的路径。
这个特性是为了简化开发而提出,但实际上底层的实现原理还是和以前一样的,以上命令在执行过程中,还是会先进行编译,然后再运行。并且以上的运行方式,编译生成的class文件在硬盘上不存在,看不到。
5、关于第一个java程序代码的解释说明!
// 单行注释
/*
多行注释
*/
/**
* javadoc注释:这里的注释信息可以自动被javadoc.exe命令解析提取并生成到帮助文档当中。
*/
/*
1、什么是注释,有什么用?
注释是对java源代码的解释说明。
注释可以帮程序员更好的理解程序。
2、注释信息只保存在java源文件当中,java源文件编译生成的字节码class文件,
这个class文件中是没有这些注释信息的。
3、在实际的开发中,一般项目组都要求积极地编写注释。这也是一个java软件工程师
的基本素养。
4、注释不是写的越多越好,精简,主线清晰,每个注释都应该是点睛之笔。(以后慢慢锻炼)
*/
// 这种注释属于单行注释,只注释两个斜杠后面的
/**
* 类的注释信息
* @version 1.0
* @author bjpowernode-dujubin
* ....
*/
public class HelloWorld{ // 这是一个类,类名叫做HelloWorld
public static void main(String[] args){
System.out.println("Hello World");
System.out.println("动力节点-口口相传的Java黄埔军校");
}
}
/*
在这里可以编写多行注释
这是一行注释
这是第二行注释
这是第三行注释
*/
1.19.1 day04代码
/*
1、在java中任何有效的代码必须写到“类体”当中,最外层必须是一个类的定义。
2、public表示公开的,class表示定义一个类,Test是一个类名。类名后面必须是
一对大括号,这一对大括号被称为“类体”
3、大括号必须是成对的。并且建议都要成对编写,这样才不会丢掉。
{}
[]
()
4、什么时候代码缩进?
我包着你,你就比我低一级。你就需要缩进。
没有合理的缩进,代码可读性很差。
或者也可以这样所,大括号里的都需要缩进。
缩进就是可读性问题,不缩进也不影响程序的编译和执行。
*/
public class Test{ // 声明/定义一个公开的类,起个名字叫Test
// 类体
// 整个这一块的代码被称为:main方法(程序的入口,SUN公司java语言规定的)
// 也就是说:JVM在执行程序的时候,会主动去找这样一个方法。没有这个规格的方法,程序是无法执行的。
// main方法也可以叫做主方法。
// 注意:方法必须放到“类体”中,不能放到“类体”外面。
// 任何一个程序都要有一个入口,没有入口进不来,无法执行。
public static void main(String[] args){ //这是一个入口方法。
// 方法体
// 注意:方法体由一行一行的“java语句”构成
// 并且非常重要的是:任何一条java语句必须以“;”结尾,并且这个分号还得是英文的,不能用中文分号。
// ";" 代表一条语句的结束。
// 非常非常重要的是:方法体中的代码遵循自上而下的顺序依次逐行执行。
System.out.println("Test1");
// System.out.println();这行代码的作用是向控制台输出一句话。就是这个作用。
// 注意:如果println后面小括号里的内容是一个“字符串”的话,必须使用英文双引号括起来。
// 双引号也要成对儿写。
System.out.println("Test2");
}
// 能再来一个一模一样的入口吗?
// 不行,有语法错误
/*
public static void main(String[] args){
}
*/
// 方法2
// 现在不执行不代表以后不执行,以后我们可以学习其它语法让他执行。
public static void main2(String[] args){
System.out.println("hehe");
}
// 方法3
// 方法4
}
/*
D:\course\JavaProjects\02-JavaSE\chapter01>java Test2
错误: 在类 Test2 中找不到 main 方法, 请将 main 方法定义为:
public static void main(String[] args)
以下程序可以编译通过,但是无法运行,符合语法规则。
*/
public class Test2{
}
/*
没有语法错误,能够编译通过,但是不能运行,因为没有main方法。
错误: 在类 Test3 中找不到 main 方法, 请将 main 方法定义为:
public static void main(String[] args)
*/
public class Test3{
static void main(String[] args){
}
}
public class Test4{
// 注意:args可以改名字,随意,对于主方法来说只有这个位置可以改,其它位置不能动
public static void main(String[] fdsafdsafdsafdsa){
System.out.println("hello world");
}
}
// 以下程序符合java语法规则吗?
// 不是不运行,是编译报错。编译过不去,运行肯定不行。
public class Test5{
// 类体当中应该是方法,而不是直接的java语句
// 这里可以写吗?
System.out.println("hello1");
// 主方法,入口
public static void main(String[] args){
}
// 这里可以写吗?
System.out.println("hello2");
}
// main方法中什么也不写行吗?
// 以下程序编译和运行可以吗?
// 完全没问题
public class Test6{
// 入口
public static void main(String[] args){
}
}
public class Test7{
public static void main(String[] args){
// 这个不加双引号行吗?
// 可以,因为它是数字。
System.out.println(100);
// 是数字,加双引号行吗?
System.out.println("100");
// 以上性质一样吗?
// 不一样:一个是字符串,一个是数字。
// 但最终输出到控制台上一个样子,没啥区别。
// 这里扩展一下:对于数字来说能进行加减乘除吗?
// + 能用吗?
// - 能用吗?
// / 能用吗?
// * 能用吗?
// 可以
System.out.println(100 + 200); // 300
System.out.println(200 - 100); // 100
System.out.println(200 * 100); // 20000
System.out.println(200 / 100); // 2
}
}
/*
1、这个内容没有为什么,只能经过测试,然后根据测试结果进行记忆。
2、第一个结论?
一个java源文件中可以定义多个class。
3、第二个结论?
public的类不是必须的。可以没有。
4、第三个结论?
在源文件中只要有一个class的定义,那么必然会对应生成一个class文件。
5、第四个结论?
public的类可以没有,但如果有的话,public修饰的类名必须和源文件名保持一致。
6、第五个结论?
public的类有也只能有1个。
*/
class A{
}
/*
Test8.java:20: 错误: 类 B 是公共的, 应在名为 B.java 的文件中声明
public class B{
^
1 个错误
*/
/*
public class B{
}
*/
// 如果定义public的类你只能这样写
public class Test8{
}
class C{
}
class D{
}
//错误: 类重复: Test8
/*
public class Test8{
}
*/
// 编译通过了
// 能执行吗?
// 想从哪个入口进去执行,你就加载哪个类就行了!!!
// 例如:java T1
// 例如:java T2
// 例如:java T3
// 测试不代表以后就这样写,一般一个软件的执行入口是一个。不会出现多个的。
// 以下只是一个测试罢了。
class T1{
// 想从这个入口进去执行怎么办?
public static void main(String[] args){
System.out.println("T1.....");
}
}
class T2{
// 想从这个入口进去执行怎么办?
public static void main(String[] args){
System.out.println("T2.....");
}
}
class T3{
// 想从这个入口进去执行怎么办?
public static void main(String[] args){
System.out.println("T3.....");
}
}
2 第二章 标识符与关键字
2.1 章节目标与知识框架
2.1.1 章节目标
了解构成java源程序的标识符和关键字都是什么,掌握标识符的命名规则以及规范。能够识别标识符是否合法。
2.1.2 知识框架
2.2 标识符概述(了解)
标识符(identifier)是指用来标识某个实体的一个符号,在不同的应用环境下有不同的含义。在计算机编程语言中,标识符是用户编程时使用的名字,用于给变量、常量、函数、语句块等命名,以建立起名称与使用之间的关系。标识符通常由字母和数字以及其它字符构成。
在编程语言中,标识符就是程序员自己规定的代表一定含义的单词(java源程序当中凡是程序员自己有权利修改的名字),比如类名,属性名,变量名等。如以下代码所示:
其中,Student是一个类名,表示学生类;age是一个属性名表示学生的年龄属性,setAge是一个方法名,a表示一个变量名。这些都是标识符。
2.3 标识符详解
2.3.1 标识符都可以标识什么(理解)
在java源程序当中,标识符可以用来标识:
类名,例如:Student学生类、User用户类、Product商品类、Order订单类等。
接口名,例如:Runable可运行的、Comparable可比较的等。
变量名,例如:name名字、age年龄、birth生日、length长度等。
方法名,例如:login登录、logout登出、eat吃、drink喝等。
常量名,例如:LOGIN_SUCCESS、ACCESS_TOKEN等。
除了标识以上之外,还可以标识其他的,这里就不再一一列举,大家主要先把以上的了解一下。总之标识符就是起名字。
2.3.2 标识符命名规则(掌握)
标识符主要用来起名字,那么可以随便起名吗,有没有什么命名规则呢,答案是:有的,而且还得必须遵守,当编写源程序的时候如果标识符违背命名规则,编译时会报错。那么java中的标识符命名规则有哪些呢?请看以下规则:
①标识符只能由数字、字母、下划线“_”、美元符号“$”组成,不能含有其它符号。
②标识符不能以数字开始。
③java关键字和保留字不能作为标识符。
④标识符严格区分大小写。
⑤标识符理论上没有长度限制。
以上几点需要大家在以后不断的练习中进行掌握,不需要死记硬背。
2.3.3 标识符命名规范(掌握)
遵守了标识符的命名规则之后,一起来看一看标识符有没有相关的命名规范呢,有同学可能问了:命名规则和命名规范有何不同呢?我在这里给大家解释一下,命名规则是一种语法上的要求,如果违背了,则表示语法错误,程序是无法正常编译的。而命名规范在一个团队中进行协同开发时尤为重要,如果大家都按照统一的命名规范书写代码,那么代码看起来就会像是同一个人编写的一样,能够很大程度上提高代码的可读性。换句话说,命名规范就是一个团队的编码约定。不过,当程序没能遵守命名规范的话,是不会影响程序的正常编译的。那么java中标识符的命名规范有哪些呢?请看以下通用的规范:
①见名知意:看到这个单词就知道它表示什么,增强程序的可读性,例如:Student则表示学生类型,User则表示用户类型;
②遵循驼峰命名方式:可以很好的分隔单词,每个单词之间会划清界限,同样也是增强程序的可读性,例如:getName则表示获取名字,UserService则表示用户业务类;
③类名、接口名首字母大写,后面每个单词首字母大写,这是遵守驼峰命名方式的;
④变量名、方法名首字母小写,后面每个单词首字母大写,这也是遵守驼峰命名方式的;
⑤常量名全部大写,单词和单词之间使用“_”衔接,为了表示清楚含义,不要怕单词长,例如:INT_MAX_VALUE则表示int类型最大值。
以上的命名规范是大部分java开发团队通用的,但有一些团队可能要求更严格,和大家分享一段阿里巴巴的开发规约:
通过上图,我们可以看到阿里巴巴的开发规约更严格一些,比如第一条:代码中的命名均不能以下划线或美元符号开始,也不能以下划线或美元符号结束。另外还有“POJO类中布尔类型的变量,都不要加is,否则部分框架解析会引起序列化错误……”。
在实际的开发中,每个团队都有自己的开发规约,大家写代码的时候一定要遵守自己团队的开发规约。这样才能增强程序的可读性。
2.4 关键字(理解)
Java关键字是编程语言里事先定义的,有特殊意义的单词,Java中所有的关键字都是小写的英语单词。
Java的关键字对Java的编译器有特殊的意义,它们用来表示一种数据类型,或者表示程序的结构等,关键字不能用作标识符。常见的关键字有哪些呢?请看以下表格:
接下来,我们一起看一下每个关键字代表的大致含义,请看以下表格:
以上关键字以及关键字的大致含义,大家目前先对其进行一个简单的了解,对于关键字不需要去死记硬背,随着后面内容的学习,每一天的积累,不断的敲代码,慢慢的就掌握了。接下来我们一起来看一下以下程序中有哪些单词是关键字,请看以下代码:
通过以上代码,我们可以看到其中public、class、static、void为关键字。有的同学说:我为什么没看出来哪些是关键字呢?我想说的是:别着急同学,后面慢慢的你就会找关键字了,也能很快的区分出哪些是标识符了,这需要一个过程,拭目以待吧。
2.5 章节小结
本章节主要是带领大家一起来看看Java程序的重要组成成分:标识符和关键字。其中标识符中重点掌握标识符的命名规则,以及命名规范,养成一个良好的编写规范,能够大大提高程序的可读性。另外,还有当给出相关的标识符,能够很快的判断其合法性。关键字这块重点要知道Java中的每一个关键字全部都是小写的英文单词,每个关键字都有特殊意义,并且不能拿关键字作为标识符。至于每个关键字所代表的含义,以及这个关键字如何编写,建议随着课程一边学习一边练习,然后一边再记忆。
2.6 难点解惑
之前我们在学习标识符的时候,其中有这样一条规则,那就是:标识符不能以数字开头。大家一起来思考一个这样的问题:新建一个java文件,起名“123Test.java”这样可以吗?接下来我们进行一个简单的测试,新建一个123Test.java,打开文件,编写代码,定义类T,如下图所示:
保存以上程序之后,进行编译,结果如下图所示:
我们可以看到123Test.java文件编译通过了,并且生成了T.class字节码文件。我相信有些同学看到这里的时候会恍然大悟。这是为什么呢?这是因为123Test.java中的“123Test”不是一个标识符,“123Test.java”只是一个普通的文件名而已。也就是说这里的“123Test”并不是作为一个类名的形式出现的,它还不是一个类的名字。
那么接下来大家再继续思考另一个问题,以上的“123Test.java”文件中能够定义一个公开的类吗?答案是:不能。这是为什么呢?因为我们之前学习过这样一条规则:公开的类的类名必须和Java源文件名保持一致,换句话说,如果我们这里定义公开的类的话,类名必须是123Test,这就尴尬了,123Test作为类名出现时,则是一个标识符,而标识符命名规则中规定不能以数字开始,所以是不能的。我们进行一个简单的测试,将以上123Test.java文件中的代码全部删除,然后定义一个公开的类,起名123Test,如下图所示:
接下来我们对以上程序进行编译,来看看会出现什么问题,请看下图:
通过以上的编译结果可以清楚的看到,“需要<标识符>”错误的出现,也就是说此时的123Test不是一个合法的标识符。
通过本难点的学习,你是否能够掌握到java源文件的名字并不是类名,并且也不需要符合标识符的命名规则呢。
2.7 章节习题
第一题:分析以下单词哪些是合法的标识符,哪些不合法,并说明原因:
myName,字,My_name,Points,$points,_sys_ta,OK,_23b,_3_,#name,25name,class,&time,if,HelloWorld
2.8 习题答案
第一题答案:
myName,字,My_name,Points,$points,_sys_ta,OK,_23b,_3_都是合法的标识符。
#name:不合法,标识符不能包含#
25name:不合法,标识符不能以数字开始
class:不合法,class是关键字,不能做标识符
&time:不合法,标识符不能包含&
if:不合法,if是关键字,不能做标识符
HelloWorld:不合法,标识符不能包含空格
2.9 day05课堂笔记
1、标识符
1.1、标识符可以标识什么,什么是标识符,怎么理解这个概念!
1.2、标识符的命名规则
1.3、标识符的命名规范
本小结最终的要求是:随意给出一个单词,判断它是否是合法的标识符。
2、每一天你会编写很多程序,你会遇到很多编译错误,也会遇到很多运行错误,你是否需要准备一个单独的文件来记录这些信息,以及记录这些信息是怎么导致的,原因是什么,怎么去解决的,解决办法是啥????
非常有必要的,要想成为一个调错高手,这个有必要进行一下。
3、关键字
3.1、什么是关键字?
在SUN公司开发Java语言的时候,提前定义好了一些具有特殊含义的单词,这些单词全部小写,具有特殊含义,不能用作标识符。
3.2、凡是在EditPlus中以蓝色字体形式存在的都是关键字,具有特殊含义。
3.3、切记:
java语言中的所有关键字都是全部小写。
注意:java语言中是严格区分大小写的。public和Public不一样。
Class和class不一样。static和Static也不一样。
3.4、那么关键字有哪些呢,我们需要背会吗?需要单独去记忆吗?
关键字:
public
static
void
class
byte
short
int
long
float
double
boolean
char
true
false
if
while
for
private
protected
........
对于这些关键字来说大家不需要单独花费时间去记忆,随着后面程序的积累,你会接触到所有的关键字。
2.9.1 day05代码
/*
1、在java程序当中,使用EditPlus工具进行代码编写的时候,
有一些单词是蓝色,有的是红色,有的绿色,有的是黑色,有
的是紫色,有的是粉色....
2、注意:在java源代码当中,在EditPlus工具中显示的高亮颜色为黑色时,
这个单词属于标识符。
3、标识符可以标识什么?
可以标识:
类名
方法名
变量名
接口名
常量名
......
4、到底什么是标识符呢?
一句话搞定:凡是程序员自己有权利命名的单词都是标识符。
5、标识符可以随意编写吗,有命名规则吗?有
什么是命名规则?
命名规则属于语法机制,必须遵守,不遵守命名规则表示不符合语法,
这样,编译器会报错。
规则1:标识符只能由数字、字母(包括中文)、下划线_、美元符号$组成,
不能含有其它符号。
规则2:标识符不能以数字开头
规则3:关键字不能做标识符。例如:public class static void 这些蓝色的字体
都是关键字,关键字是不能做标识符的。
规则4:标识符是严格区分大小写的。大写A和小写a不一样。
规则5:标识符理论上是没有长度限制的。
*/
public class BiaoShiFuTest{
// main是一个方法的名称,属于标识符
// 但是这个标识符不能修改,因为这个main是SUN固定死的。
public static void main(String[] args){
}
//doSome是一个方法名,可以改成其他的名字
public static void doSome(){
// k是一个变量名
int k = 100;
// nianLing 是一个变量名
int nianLing = 20;
}
}
/*
编译报错,错误信息是:
错误: 需要<标识符>
错误原因:编译器检测到class这个单词,那么编译器会从class这个
单词后面找类名,而类名是标识符,编译器找了半天没有找到标识符,
因为123ABC不是标识符,所以编译器提示的错误信息是:需要<标识符>
解决办法:
将123ABC修改为合法的标识符。
*/
class Y123ABC{
}
// 类名是标识符,标识符“中”不能有空格
/*
编译器错误信息是:
错误: 需要'{'
编译器检测到class,然后找class后面的标识符,编译器找到了一个合法的标识符
叫做“Hello”,然后编译器继续往后找“{”,结果没有找到“{”,所以报错了。
解决办法:
办法1:是把World删除
办法2:把空格删除
*/
/*
class Hello World{
}
*/
class Hello{
}
class HelloWorld {
}
class _A{
}
class _$1Aa你{
}
// 错误: 需要<标识符>
// 关键字不能做标识符
/*
class public {
}
*/
// 这个可以,因为 public1 不是关键字,可以用。
class public1 {
}
class b {
}
class B {
}
// 虽然java中的标识符严格区分大小写
// 但是对于类名来说,如果一个java源文件中同时出现了:A类和a类
// 那么谁在前就生成谁。大家以后最好不要让类名“相同”。
// 最好类名是不同的。
class HelloWorld2{
}
class helloWorld2{
}
class T{
}
// 在123.java文件中定义public的类可以吗?
// 因为之前有一条规则是这样说的:public的类可以没有
// 但如果有public的类,也只能有1个,并且public的类的
// 名字必须和源文件名保持一致。
//public class 123 { // 但是最终尴尬了,因为123不能做标识符。是错误的标识符。
//}
/*
题目:
创建一个java文件,起名 123.java可以吗?
可以,完全可以,在windows操作系统中文件名叫做:123.java没毛病。
123其实并不是标识符。只是一个文件名。
只不过在123.java文件中无法定义public的类。
标识符除了命名规则之外,还有命名规范:
1、命名规则和命名规范有什么区别?
命名规则是语法,不遵守就会编译报错。
命名规范只是说,大家尽量按照统一的规范来进行命名,不符合规范也行,
代码是可以编译通过的,但是你的代码风格和大家不一样,这个通常也是
不允许的。
规则类似于:现实世界中的法律。
规范类似于:现实世界中的道德。
统一按照规范进行的话,代码的可读性很好。
代码很容易让其它开发人员理解。
2、具体的命名规范是哪些?
规范1:见名知意(这个标识符在起名的时候,最好一看这个单词就知道啥意思。)
规范2:遵循驼峰命名方式,什么是驼峰(一高一低,一高一低...)
驼峰有利于单词与单词之间很好的进行分隔
BiaoShiFuTest,这个很好,一眼就能看出来是4个单词。
规范3:类名、接口名有特殊要求
类名和接口名首字母大写,后面每个单词首字母大写。
StudentTest、UserTest ,这是类名、接口名。
规范4:变量名、方法名有特殊要求
变量名和方法名首字母小写,后面每个单词首字母大写。
nianLing(NianLing这样就不符合了。)
mingZi(MingZi这样也不符合了。)
规范5:所有“常量”名:全部大写,并且单词和单词之间采用下划线衔接。
USER_AGE :用户年龄
MATH_PI:固定不变的常量3.1415926.....
*/
public class IdentifierTest{
public static void main(String[] args){
// 以下代码看不懂没关系,别着急。
// 主要看两个汉语拼音,可读性很强。
// nianLing和mingZi都是黑色字体的标识符。
int nianLing = 20;
String mingZi = "zhangsan";
}
}
3 第三章 变量
3.1 章节目标与知识框架
3.1.1 章节目标
理解变量本质是什么,在开发中有什么用?变量三要素是什么?怎么声明变量?怎么给变量赋值?变量是如何分类的?变量的作用域?
3.1.2 知识框架
3.2 字面量(理解)
字面量就是数据/数值,例如:1234,true,”abc”,‟中‟,3.14。在现实生活中每天都会接触到数据,例如:你今天的体重是86Kg,你今天花了500元,买了个西瓜重量是8.6Kg,外面明明是晴天,你却说狂风暴雨,你说的是假话(false),你明明喜欢她,却嘴上说不喜欢,撒谎(false)。
软件其实就是为了解决现实生活当中的问题,解决生活当中的问题其实就是处理生活当中的数据,一门编程语言首先要能够表示数据才可以处理数据,所以Java程序通过字面量来表示数据。
在编程语言中数据一般会被分门别类,所以每个数据都是有数据类型的,不同的数据类型会分配不同大小的内存空间去存储它。
数据被分为:整数型、浮点型、字符型、布尔型、字符串型等。
整数型(数字):1、2、100、-2
浮点型(数字,带小数):1.0、2.0、3.14
字符型(文字,单个字符):‟a‟、‟中‟
布尔型(真假):true、false
字符串型(文字,多个字符):”你好呀童鞋,欢迎来到动力节点!”
需要注意的是,java中规定字符型字面量必须采用半角的单引号括起来,而字符串型字面量必须使用半角双引号括起来。这是一个语法规定,不然编译器就报错了。
接下来,我们一起来找出以下代码中哪些是字面量吧?
通过以上的代码我们可以看到这些数据,或者说可以看到这些字面量:
"小明的体重=":字符串型字面量
86:整数型字面量
"kg":字符串型字面量
"圆周率=":字符串型字面量
3.1415926:浮点型字面量
true、false:都是布尔型字面量,表示真和假
'男'、'a':都是字符型字面量
"你的对手在看书!"、"你的闺蜜在减肥!"、"你的仇人在磨刀!"、"隔壁老王在练腰!"、"你还不赶紧抓紧时间,还在这愣着干啥呀!!!":这些都是字符串型字面量
以上程序的运行结果如下图所示:
接下来,我们再看一段代码,找出以下程序中字面量属于哪种类型,请看代码:
通过代码我们可以看到这些字面量:'9'、9、"100"、100、"3.1415926"、3.1415926、"true"、true,其中'9'由于带有单引号则属于字符型字面量,9属于整数型字面量;"100"由于带有双引号则属于字符串型字面量,100属于整数型字面量;"3.1415926"由于带有双引号则属于字符串型字面量,3.1415926属于浮点型字面量;"true"带有双引号则属于字符串型字面量,true属于布尔型字面量。
接下来,再看一段代码,分析以下程序存在什么问题?
以上程序的编译结果如下图所示:
以上程序为什么会编译报错呢,因为在程序中abc不是一个合法的字面量,abc不是数字,也不是布尔型,那我们只能将其看做字符串型,但Java中规定字符串型字面量必须使用双引号括起来,这里没有,所以程序是不符合语法规则的。故编译报错。
接下来,我们再一起来看一段程序,请看以下代码:
我们对以上的程序进行编译,编译结果请看下图:
通过上图我们可以看到编译报错了,错误信息的第一条是:未结束的字符文字,这是为什么呢,因为在java语言中字符型只能是单个字符,多个字符则是字符串,应该使用双引号括起来。以上程序中编译器检测到‟ab‟之后,发现以单引号开始,会认为后面是一个字符,于是去a后面找另一半单引号,结果未找到结束的单引号(因为结束的单引号在b后面),所以编译器报错了,并且错误信息是“未结束的字符文字”。
通过本小节的学习,大家需要理解的是:什么是字面量。能够知道字面量就是数据,我们软件处理的就是数据,不同类型的数据在程序中有不同的编写方式,例如:字符型字面量必须是单个字符,并且使用半角的单引号括起来。字符串型字面量必须是使用半角的双引号括起来。布尔类型字面量只有两个值,写法是true和false,true表示真,false表示假。而浮点型字面量则带有小数点。
3.3 变量
3.3.1 变量概述(理解)
变量是内存当中存储数据最基本的单元,将数据(字面量)放到内存当中,给这块内存空间起一个名字,这就是变量。所以变量就是内存当中的一块空间,这块空间有名字、有类型、有值,这也是变量必须具备的三要素。变量在内存中的抽象图形可以参考下图:
在上图当中每一个抽象的椭圆就代表一个变量,其中a、c、pi、sex是4个变量的名字(变量名只要是合法的标识符即可),13、‟好‟、3.14、true是4个变量中分别存储的数据(字面量),int、char、double、boolean是4个变量分别对应的数据类型(int、char、double、boolean等都是java的关键字,声明变量时用来指定变量的数据类型)。
数据类型在任何一门编程语言当中都很重要,因为程序在运行的过程中会通过不同的数据类型给数据分配不同大小的空间。有的数据类型占用的空间较小,但有的数据类型占用的空间就会很大。这也是符合现实的,在现实生活中有些数据较大,有些数据则较小。
变量要求“变量的数据类型”和变量中存储的“数据(字面量)”必须类型是一致的,换句话说,冰箱是用来存放小食品的,也就是说冰箱只能存放小食品,大象不能往冰箱里放,原因是放不下,空间不合适。例如:int类型就只能存下4个字节大小的整数,再长一点儿放不下,比如long类型的整数占有8个字节,这样的数据肯定是无法放到int类型的变量当中的。
所谓变量:可变化的量。它的意思是变量中存储的数据不是一成不变的,是可以被改变的,假设变量i中之前存储的数据是10,我们可以将10换成100,变量就是这个意思。
通过以上内容的学习,大家需要掌握一个变量是有三要素组成的,分别是:数据类型、变量名、存储的值。其中存储的值就是上一节讲到的字面量。
3.3.2 使用变量(掌握)
我们在使用变量之前需要先进行变量的声明,那么声明变量的语法格式是什么呢?请看:
数据类型变量名;
以上则是声明变量的语法格式,其中数据类型我们在下一章节会详细讲解,目前我们以“int”这种数据类型为例进行学习,int表示整数类型(注意:int是关键字,不能随意写,必须全部小写)。变量名只要符合标识符命名规则即可,当然也要见名知意,命名规范中还要求变量名首字母小写,后面每个单词首字母大写。请看以下代码则表示声明一个int类型的变量age用来存储年龄数据:
大家可以看到上面的代码中这个age变量的三要素当中只具备了两个要素:数据类型和变量名,此时的age变量并没有存储数据(或者说没有赋值),那么这个age变量可以访问吗,我们来试一下,请看代码:
我们对上面的程序进行编译,请看下图:
以上编译错误信息为:可能尚未初始化变量age,这句话的意思是age变量还没有初始化(没有赋值),也就是说变量age中还没有数据,空间还没有开辟出来,可见,java语言中的变量要求必须先声明,再赋值才能访问(这个规则大家一定要记住)。那么java语言中怎么给变量赋值呢?在Java语言中给变量赋值需要采用赋值运算符“=”,请看赋值的语法格式:
变量名=值;
在以上的语法当中,等号右边的值其实就是数据,我们之前所学的字面量就可以当做“值”。其中的等号“=”是一种运算符,它被称为赋值运算符,赋值运算符右边的表达式优先级较高,所以等号右边先执行,将执行结果赋给左边的变量。(注意:java语言中的单等号不是用来判断是否相等的,是为了完成赋值运算的。)接下来我们给age变量赋值,请看以下代码:
我们对以上的代码进行编译并运行,请看下图结果:
可以看到,以上程序的运行结果是在控制台输出了:20。这里需要注意的是,当在Java程序中输出某个变量的时候,会自动输出变量中所保存的值。以上的测试说明了我们的赋值是没有问题的。那么赋值运算还有其他注意事项吗?有的。在进行赋值运算的时候,Java中规定“值”的数据类型必须和“变量”的数据类型保持一致,也就是说int类型的变量只能存储int类型的数据,不能存储其他类型的数据,我们来进行一个简单的测试,请看以下代码:
我们对以上的程序进行编译,请看下图的编译结果:
通过以上的编译我们可以看出,程序的第四行出错了,错误信息是:类型不兼容,这是因为编译器检测到age变量是int类型,只能存储int类型的数据,结果赋给age变量的数据不是一个int类型的数据,而是一个带有双引号的字符串。通过以上的测试证实了在进行赋值运算的时候,“值”的数据类型必须和“变量”的数据类型一致才可以。
变量赋值之后可以再次重新赋值吗?当然可以,要不然怎么能叫变量呢。请看以下代码:
我们对以上的代码进行编译和运行,结果如下图所示:
通过测试可以看到变量是可以重新赋值的。在以上的程序当中,我们看到变量的声明和赋值是分两行代码完成的,那么变量的声明和赋值可以在一行上完成吗,我们再进行一个简单的测试,请看以下代码:
我们对以上的程序进行编译和运行,请看运行结果:
通过以上的测试,可以看出,变量的声明和赋值是可以在一行上完成的,声明变量的同时可以完成赋值运算。那么Java允许一次声明多个同类型的变量吗(C++是允许的,Java可以吗)?请看以下代码:
以上程序当中,我们看到第三行代码,一次声明了3个int类型的变量,并且分别进行了赋值。我们对以上的程序进行编译和运行,结果如下图所示:
通过运行结果可以看出,Java是允许一次声明多个同类型的变量的。我们将以上的代码进行一个简单的修改,请看修改之后的代码:
对以上的程序进行编译,请看以下编译结果:
我们可以看到程序编译报错了,而错误信息中显示a变量和b变量并没有赋值,这说明了代码“inta,b,c=400;”表示声明了三个int类型的变量,分别起名a、b、c,其中c变量赋值400,而a和b是没有赋值的,这里一定要注意,以上代码并不表示给a、b、c三个变量同时赋值400。
接下来我们再来研究一下,在同一个大括号当中,可以声明两个同名的变量吗?请看以下代码:
我们对以上的代码进行编译,请看下图结果:
通过以上的编译结果,可以看到第4行出错了,结果也证实了在同一个大括号当中不能同时声明多个同名的变量。这是因为在同一个大括号当中表示在内存的同一个域当中,在同一块区域上面有两个名字都叫做age的变量,这是不允许的,因为这样程序在运行的时候,java虚拟机也不知道该访问哪个变量了(好比说,你有两个朋友,他们的俩的名字都叫张三,当你们三个人同时在场的时候,你呼张三,其实他俩也不知道你在叫谁呢!)。所以,同一个域中变量名不能重名,但是记住:变量是可以重新赋值的。比如以下代码:
我们将以上代码编译并运行,请看下图结果:
最后我们再来看一下,在方法体当中的代码是否存在执行顺序,变量可以先访问,后声明吗?请看以下代码:
对以上程序进行编译,请看下图编译结果:
以上测试结果中显示第3行报错了,错误信息是找不到符号,换句话说age变量是不存在的,通过这个测试得知,方法中的代码是有执行顺序的,遵循自上而下的顺序依次逐行执行,也说明了变量必须先声明,然后才能使用。
通过本小节的学习,大家需要掌握的是在Java语言中变量如何声明,怎么赋值。另外也要知道变量是可以重新赋值的,还有声明和赋值可以分开完成,也可以一起完成,并且也可以一次声明多个变量。还有就是在同一个域当中变量名是不能重名的。方法当中的代码是有执行顺序的,遵循自上而下的顺序依次逐行执行。
3.3.3 变量分类(了解)
变量根据声明的位置不同可以分为:局部变量和成员变量。在方法体当中声明的变量以及方法的每一个参数都是局部变量。在方法体外,类体内声明的变量称为成员变量,成员变量声明时如果使用static关键字修饰的为静态成员变量(简称静态变量),如果没有static关键字修饰则称为实例成员变量(简称实例变量),请看以下代码:
在以上代码当中,sum是和main相似的方法,在sum方法当中a、b都是方法上的参数属于局部变量,在sum的方法体当中声明的firstNum属于局部变量。大家也可以看到在sum方法体外面声明了两个变量,一个是x,一个是y,这两个变量都属于成员变量,x变量没有使用static修饰属于实例变量,而y属于静态变量。
局部变量只在方法体当中有效,方法开始执行的时候局部变量的内存才会被分配,当方法执行结束之后,局部变量的内存就释放了。所以局部变量的生命周期非常短暂。
在本小节当中,需要大家掌握的是,看到程序能够找出哪些是局部变量,哪些是实例变量,哪些是静态变量即可,至于这个变量什么时候声明为局部的,什么时候声明为成员的,后面的课程当中会详细介绍。
3.3.4 变量作用域(理解)
所谓变量的作用域就是变量的有效范围。通过后面内容的学习大家会更加明白,实际上局部变量、实例变量、静态变量它们存储在Java虚拟机的不同内存区域上,所以变量是有作用域的。关于变量的有效范围,在这里我给大家总结了一个口诀:出了大括号就不认识了。虽然这句话属于大白话,但很实用。我们来测试一下,请看以下代码:
在以上代码中我们可以看到在main方法中声明了一个变量k,根据之前所学得知k变量是局部变量,只在本方法中有效(main方法),那么在m方法中是否可以访问局部变量k呢,我们对以上程序进行编译,请看下图结果:
通过以上的编译结果可以看到第6行出错了,错误信息找不到符号,也就是说k变量是不存在的无法访问的。这也印证了之前所说的那句话:出了大括号就不认识了。接下来我们对以上程序进行修改,如果将变量声明为静态变量呢(这里不要纠结为什么声明成静态变量,后面会讲),变量的作用域会发生改变吗,请看以下代码:
我们对以上代码进行编译,并运行,请看下图结果:
我们可以看到编译通过了,并且程序也正常运行了。有同学说,为什么只输出了main方法中的k,而m方法中的k没有输出呢?这是因为m方法并没有执行,程序的执行是从main方法作为入口进来执行的,在main方法中并没有手动调用m方法,所以m方法是不会执行的。不过这个测试也印证了那句话:出了大括号就不认识了,只要没有出大括号,变量就是有效的可访问的,以上程序声明k变量的位置在类体当中,方法体在类体当中包含,所以在以上的main方法和m方法中都是可以访问k变量的。接下来我们对以上的程序进一步修改,请看以下代码:
看到以上代码,我们发现在main方法当中声明了一个局部变量k,有同学可能会说,有两个同名的变量k,这个程序编译会报错的,因为变量名不能重名,如果你要是这么想的就错了,我们之前确实讲过变量名不能重名,但是我们指的是“在同一个域”当中变量名不能重名,以上程序声明的两个变量k处在不同的域当中,一个是类体域,一个是方法体域。那么我们对以上程序进行编译并运行,结果会是怎样呢,请看下图结果:
通过上图我们看到程序不但编译通过了,而且还运行了,并且运行的结果不是100,而是300,这说明了Java程序遵守“就近原则”,其实这个就近原则不止是Java语言中存在,大部分的编程语言都符合这个原则。也就是说以上程序在main方法中访问变量k的话,访问的是局部变量k,而不是静态变量k。那么有同学就要问了,m方法中的k访问的是哪个呢?我们尝试在main方法中调用m方法,让m方法执行,来测试一下输出结果,请看以下代码:
以上main方法当中调用m方法这段代码大家可以先大概理解一下,方法调用这块的知识点会在后续的内容当中讲解。我们对以上程序进行编译并运行,请看下图结果:
通过以上的测试我们可以看到在m方法中访问的是静态变量k,因为输出结果是100,而不是300。
在本小节当中需要大家掌握的是变量的有效范围,还是需要大家记住那句话:出了大括号就不认识了。另外Java遵循就近原则。
3.4 章节小结
通过本章内容的学习,大家需要掌握变量应该如何声明,如何赋值,如何使用。需要理解变量就是内存当中存储数据的一块空间,它包括三要素:数据类型、变量名、值。另外要知道变量根据声明的位置可以分为成员变量和局部变量,并且声明位置不同作用域也是不同的。还需要注意在同一个域当中变量名不能重名,不同的域,变量名可以相同,只不过Java遵循就近原则,会自动访问离它最近的数据。
3.5 难点解惑
本章节内容整体比较简单,没有太多的难点,不过对于初学者来说,还是有一定难度的,尤其是变量的作用域这一小节,关键是对变量作用域的理解。请看以下代码:
我们对以上程序进行编译,请看下图编译结果:
通过以上的编译结果可以看出,程序的第7行出现错误,错误信息表示变量i不存在,这是因为变量i的声明位置是if语句的大括号当中,还是我们之前所说的那句话:出了大括号就不认识了。这里的i变量在if语句的大括号执行结束之后,内存就会自动释放,它的作用域是语句块级别的,生命周期更短。也就是说if语句的大括号之外都不能访问到变量i。那么,如果想在后续的程序中继续使用变量i应该怎么办呢?我们可以将i变量的声明位置修改一下,请看代码:
我们对以上的代码进行编译并运行,请看下图结果:
以上程序之所以可以访问i变量,是因为i变量的作用域修改成了方法体域的级别。扩大了它的作用范围。
3.6 章节习题
第一题:通过变量来描述学生的信息,学生信息包括:学号、姓名、性别、身高。其中学号采用整数,姓名采用字符串,性别采用字符型,身高采用浮点型(提示:查阅相关资料,在java中如何定义字符串类型的变量,如何定义字符型的变量,还有如何定义浮点型的变量)。具体的学生数据有两份,第一个学生信息是:学号110,姓名张三,性别男,身高1.85米。第二个学生信息是:学号120,姓名李四,性别女,身高1.65米。要求最终将学生的信息输出到控制台。输出结果如下图所示:
3.7 习题答案
运行结果如下所示:
3.8 day05课堂笔记
4、变量(第三章 变量)
4.1、字面量
字面量就是数据,数据就是字面量,是一个东西。
10 100 123 :整型
1.34 3.14 2.0:浮点型
true false :布尔型
'a' '国':字符型
"a" "abc" "国" "中国":字符串型
10:整数,是一个数字
"10":它不是数字,是一个字符串,或者说,它属于“文字类”的。性质完全不同,在计算机中的对应的二进制码也是完全不同的。
4.2、变量
变量的理解
什么是变量?
变量就是一个存数据盒子。(盒子大小谁来决定啊?数据类型)
在内存中的最基本的存储单元。
存数据用的,而且这个数据是可变的,所以叫做变量。
变量的使用
变量的三要素?
数据类型、变量名、值 (值就是数据,就是字面量。)
int i = 100;
java中的变量必须先声明,再赋值才能访问(必须手动赋值。)
int k; System.out.println(k); 这样是不行的。
可以在一行上声明多个变量:
int a, b, c = 100;
c变量赋值100,a,b变量只声明了没有赋值。
int a = 10, b = 20, c = 100;
可以这样每个都赋值。
声明和赋值可以分开,也可以一起做!!!
int i;
i = 100; // 先声明再赋值
int k = 200; // 声明的同时赋值
在“同一个域”当中,变量名不能重名!!!!!!
但可以重新赋值!!!!!!
{
int i = 100;
//double i = 2.0; // 重名了编译器会报错,不允许。
i = 300; // 可以重新赋值。
}
到底什么叫做同一个域?????
这个目前不太好解释,大家记住吧:一个大括号代表一个域。
{A域
{B域
{C域
}
}
}
A域包括B域,B域包括C域。
变量的分类
根据位置进行分类:记住就行
在方法体当中声明的变量叫做局部变量。
public static void m1(){
//局部变量,方法执行结束之后内存释放。
int k = 100;
int i = 200;
}
在方法体外以及类体内声明的变量叫做成员变量。
public class T{
public static void x(){
}
// 成员变量
int i = 200;
}
变量的作用域
出了大括号就不认识了。别的先别管。
{
int i = 100;
{
在这里可以访问i
}
}
{
在这里是无法访问i变量。
}
3.8.1 day05代码
/*
变量的第一个测试程序:Var是变量
1、关于程序当中的数据?
开发软件是为了解决现实世界中的问题。
而现实世界当中,有很多问题都是使用数据进行描述的。
所以软件执行过程中最主要就是对数据的处理。
软件在处理数据之前需要能够表示数据,在java代码中
怎么去表示数据呢?在java中有这样的一个概念:字面量。
注意:在java语言中“数据”被称为“字面量”。
10
1.23
true
false
'a'
"abc"
以上这些都是数据,在程序中都被叫做“字面量”。
字面量可以分为很多种类:
整数型字面量:1 2 3 100 -100 -20 ....
浮点型字面量:1.3 1.2 3.14.....
布尔型字面量:true、false没有其它值了,表示真和假,true表示真,false表示假
字符型字面量:'a'、'b'、'中'
字符串型字面量:"abc"、"a"、"b"、"中国"
其中字符型和字符串型都是描述了现实世界中的文字:
需要注意的是:
所有的字符型只能使用单引号括起来。
所有的字符串型只能使用双引号括起来。
字符型一定是单个字符才能成为“字符型”
在语法级别上怎么区分字符型和字符串型?
主要看是双引号还是单引号。
单引号的一定是字符型。
双引号的一定是字符串型。
2、什么是变量?
*/
public class VarTest01{
public static void main(String[] args){
System.out.println(100);
System.out.println(3.14);
System.out.println(true);
System.out.println(false);
System.out.println('a');
System.out.println('中');
System.out.println("abc");
System.out.println("国"); // 这不属于字符型,因为使用双引号括起来了,所以它是字符串。
// 编译报错。ab是一个串,不是字符型,不能用单引号。
//System.out.println('ab');
System.out.println('好'); // 属于字符型
System.out.println("好"); // 属于字符串型
System.out.println("1"); //属于整数型吗?不是,是字符串。
System.out.println("true"); // 属于布尔型吗?不是,是字符串。
System.out.println("3.14"); // 字符串型
System.out.println(1); // 整数型
System.out.println(3.14); // 浮点型
System.out.println(true); // 布尔型
System.out.println(false); // 布尔型
//分析一下:如果只有字面量,没有变量机制的话,有什么问题?
// 10 是一个整数型数据,在内存中占有一定的空间(CPU 内存 硬盘)
// 10 + 20 = 30
// 在内存中找一块空间存储10,再找一块空间存储20,CPU负责“+”运算,算完
// 之后的结果是30,那么这个30也会在内存当中找一块临时的空间存储起来。
// 思考:以下的三个10在内存当中是一块空间,还是三块不同的空间呢?
// 以下虽然都是10,但是这3个10占用三块不同的内存空间。
System.out.println(10);
System.out.println(10);
System.out.println(10); // 只有“字面量”机制的话,是远远不够的,因为只有字面量内存是无法重复利用的。
// 定义/声明一个变量,起名i
int i = 1000;
// 以下这5次访问都是访问的同一块内存空间。(这样使用变量之后,内存空间就得到了复用。)
System.out.println(i);
System.out.println(i);
System.out.println(i);
System.out.println(i);
System.out.println(i);
// 以下程序表示访问的是字符i以及字符串i,以下的这两个i和以上的i变量没有任何关系。
System.out.println('i');
System.out.println("i");
}
}
/**
* 变量测试类2
* @author 杜聚宾
* @version 1.5
* @since 1.0
*/
/*
什么是变量?
变量其实就是内存当中存储数据的最基本的单元。
变量就是一个存储数据的盒子。
在java语言当中任何数据都是有数据类型的,其中整数型是:int
没有为什么,java中规定的,整数型就是:int
当然,在java中除了数据类型int之外,还有其它的类型,例如带小数的:double等。。。
数据类型有什么用呢?
记住:不同的数据类型,在内存中分配的空间大小不同。
也就是说,Java虚拟机到底给这个数据分配多大的空间,主要还是看这个变量的数据类型。
根据不同的类型,分配不同大小的空间。
对于int这种整数类型,JVM会自动给int分配4个字节大小的空间。
1个字节=8个比特位
1个比特位就是一个1或0. 注意:比特位是二进制位。
int是占用多少个二进制位?1个int占有32个二进制位(bit位)
int i = 1; 实际上在内存中是这样表示的:
00000000 00000000 00000000 00000001
int i = 2;
00000000 00000000 00000000 00000010
二进制位就是:满2进1位(0 1 10 11 100 101....)
十进制位就是:满10进1位(1 2 3 4 5 6 7 8 9 10)
对于一个变量来说,包括三要素:
变量的数据类型
变量的名字
变量中保存的值
类型+名字+值
类型决定空间的大小。
起个名字是为了以后方便访问。(以后在程序中访问这个数据是通过名称来访问的。)
值是变量保存的数据。
变量名属于标识符吗?(属于)
变量名命名规范中是怎么说的?
首字母小写,后面每个单词首字母大写,遵循驼峰命名方式,见名知意。
变量怎么声明/定义,语法格式是什么?
数据类型 变量名;
例如:
int nianLing;
在java语言中有一个规定,变量必须先声明,再赋值才能访问。(没有值相当于这个空间没有开辟。)
在java语言中怎么给一个变量赋值呢,语法格式是什么?
记住:使用一个运算符,叫做“=”,这个运算符被称为赋值运算符。
赋值运算符“=”的运算特点是:等号右边先执行,执行完之后赋值给左边的变量。
变量可以声明的时候赋值吗?可以的。
*/
public class VarTest02{
/**
* 这是一个程序的入口
* @param args是main方法的参数
*/
public static void main(String[] args){
// 定义一个int类型的变量,起名nianLing,该变量用来存储人的年龄。
int nianLing;
// 变量声明之后,没有手动赋值,可以直接访问吗?
// 编译报错:错误: 可能尚未初始化变量nianLing
//System.out.println(nianLing);
// 给变量赋值
nianLing = 45;
System.out.println(nianLing); // 这是访问变量。
System.out.println("nianLing"); // 这是访问字符串。
// 变量:可以变化的量。
// 重新赋值
nianLing = 80;
System.out.println(nianLing);
// 再次重新赋值
nianLing = 90;
System.out.println(nianLing);
// 体重80kg
int tiZhong = 80;
System.out.println(tiZhong);
}
}
public class VarTest03{
public static void main(String[] args){
// 在这里可以访问k变量吗?
// 注意:方法体当中的代码遵循自上而下的顺序依次逐行执行。
// 所以以下程序编译报错。
System.out.println(k); //错误: 找不到符号
// 只有执行了这一行代码,k变量在内存中才会开辟空间。
int k = 10;
}
}
同一个域中变量名不能重名 (重复声明)
// 重要的结论:在同一个域当中(这个域怎么理解,后面讲),变量名不能重名,不能重复声明。
// 变量可以重新赋值,但在同一个域当中,不能重复声明。
public class VarTest04{
public static void main(String[] args){
// 声明一个整数型的变量起名nianLing,存储值20
int nianLing = 20;
System.out.println(nianLing);
// 给变量重新赋值
nianLing = 30;
System.out.println(nianLing);
// 这个可以吗?不行
// 错误信息:错误: 已在方法 main(String[])中定义了变量 nianLing
/*
int nianLing = 100;
System.out.println(nianLing);
*/
}
}
与类型无关,变量名不能重名
// 编译报错:i变量重复定义了。(和类型没有关系。不能同名。)
public class VarTest05{
public static void main(String[] args){
// 整数型
int i = 100;
System.out.println(i);
// 浮点型(带小数的)
// 错误: 已在方法 main(String[])中定义了变量 i
double i = 1.2;
System.out.println(i);
}
}
// 一行上可以同时声明多个变量吗?
// 答案:可以一行声明多个变量。
public class VarTest06{
public static void main(String[] args){
// 声明三个变量,都是int类型,起名a,b,c
// 通过测试得出结论是:a,b没有赋值,c赋值100
int a, b, c = 100;
// 变量必须先声明,再赋值,才能访问,a,b两个变量赋值了吗?
//错误: 可能尚未初始化变量a
//System.out.println(a);
//错误: 可能尚未初始化变量b
//System.out.println(b);
System.out.println(c);
// 给a赋值
a = 200;
// 给b赋值
b = 300;
System.out.println(a);
System.out.println(b);
}
}
/*
1、关于变量的一个分类(这个需要“死记硬背”。)
变量根据出现的位置进行划分:
在方法体当中声明的变量:局部变量。
在方法体之外,类体内声明的变量:成员变量。
重点依据是:声明的位置。
2、注意:局部变量只在方法体当中有效,方法体执行结束该变量的内存就释放了。
*/
public class VarTest07{
// 这里可以定义变量吗?可以的
// 成员变量
int i = 100;
// 主方法
public static void main(String[] args){
// 局部变量
int k = 100; // main方法结束k内存空间释放。
}
}
/*
变量的作用域?
1、什么是作用域?
变量的有效范围。
2、关于变量的作用域,大家可以记住一句话:
出了大括号就不认识了。(死记这句话。)
3、java中有一个很重要的原则:
就近原则。(不仅java中是这样,其它编程语言都有这个原则。)
哪个离我近,就访问哪个。
*/
public class VarTest08{
// 成员变量
int i = 10000;
public static void main(String[] args){
// 局部变量
int i = 100; // 这个i的有效范围是main方法。
System.out.println(i); // 这个i是多少?
// 同一个域当中,这是不允许的。
//int i = 90;
// 考核一下:以下编写for循环你看不懂,没关系,后面会将。
for(int n = 0; n < 10; n++){ // 这里声明的n变量只属于for域。for结束后n释放没了。
// 这里没有编写代码。
}
// for循环执行结束之后,在这里访问n变量可以吗?
//System.out.println(n); //错误: 找不到符号
int k; // 属于main域。
for(k = 0; k < 10; k++){
}
// 能否继续访问k呢?
System.out.println(k);
}
// 这个方法怎么定义先不用管,后面会学习。
public static void x(){
// 在这个位置上能访问i吗?
// 错误: 找不到符号
// System.out.println(i); // i是无法访问的。
// 可以定义一个变量起名i吗?
// 这个i的有效范围是x方法。
// 局部变量
int i = 200; // 所以这个i和main方法中的i不在同一个域当中。不冲突。
}
}
4 第四章 数据类型
4.1 章节目标与知识框架
4.1.1 章节目标
本章节的目标是要求大家理解数据类型的作用,八种基本数据类型各是什么,常见数据类型的取值范围,怎么使用它们声明变量,各数据类型使用时的注意事项,另外要知道在实际开发中怎么选择合适的数据类型,还有这八种基本数据类型之间的相互转换。
4.1.2 知识框架
4.2 数据类型概述(理解)
在上一章节中我们学习了变量,我们知道任何一个变量都包括三要素,分别是:数据类型、变量名、值。其中数据类型尤为重要,目前我们已经接触过一种数据类型,那就是表示整数型的int。接下来我们一起来学习一下其他的数据类型。
在学习其他数据类型之前我们先来思考一个问题,数据类型在程序中起到什么作用呢?实际上是这样的,软件的存在主要是进行数据的处理,现实生活中的数据有很多,所以编程语言对其进行了分门别类,然后就产生了数据类型,不同数据类型的数据会给其分配不同大小的空间进行存储。也就是说,数据类型作用就是决定程序运行阶段给该变量分配多大的内存空间。这就是数据类型的主要作用。那么java中的数据类型都包括哪些呢?实际上Java中的数据类型就包括两大类,一类是基本数据类型,另一类是引用数据类型(引用数据类型后面学习),其中,基本数据类型又包括4类8种:
第1类:整数型(不带小数的数字):byte,short,int,long
第2类:浮点型(带小数的数字):float,double
第3类:字符型(文字,单个字符):char
第4类:布尔型(真和假):boolean
大家可以看到,在以上的基本数据类型范畴中未发现字符串类型(带双引号的是字符串),所以,在这里我要告诉大家,Java中的字符串属于引用数据类型,不属于基本数据类型的范畴。通过以上的学习,我们知道八种基本数据类型指的是:byte、short、int、long、float、double、boolean、char。接下来我们来看一下八种基本数据类型的详细信息,请看下表:
通过上表我们可以看出八种基本数据类型中byte占用1个字节,short占用2个字节,int占用4个字节,long占用8个字节,float占用4个字节,double占用8个字节,boolean占用1个字节,char占用2个字节。那么字节是什么呢,这个大家要知道1个字节是8个比特位,那么又有同学问了,1个比特位是什么,1个比特位就是一个1或0,或者说1个比特位就是一个二进制位。也就是说1个字节是由8个1和0组成的二进制数字串。
接下来我们先普及一下计算机基础知识吧,实际上计算机在任何情况下都只能识别二进制,什么是二进制呢?计算机毕竟是一台通电的机器,电流只有正极、负极,所以只能表示两种情况,也就是1和0。对于一串由1和0组成的数字来说就是二进制,所谓的二进制就是满2进1,请看以下十进制和二进制的对照表:
其实十进制和二进制之间是存在转换规则的,如下所示:
十进制转换成二进制:比方说十进制数65转换成二进制,我们可以使用短除法,65对2整除商32余数为1,把1写在旁边,接着32对2整除商16余数为0,把0写在旁边,用16整除2商0余数为0,把0写在旁边,这样进行下去直至商为0时为止。然后把余数逆序排列就得到了65的二进制。如下图所示:
二进制转换成十进制:比方说二进制代码为1000001的十进制数是多少呢?可以采用按权相加的方法,对于二进制代码1000001首先从右边第一位起对应2的零次方,第二位对应2的1次方,以此类推,把相应的数位与权值相乘得到的积相加即可,即1×2^0+0×2^1+0×2^2+0×2^3+0×2^4+0×2^5+1×2^6=65
好了,计算机二进制的小插曲我们就先说到这里,言归正传,接下来我们继续学习八种基本数据类型。
当我们对二进制有一定的了解之后,来看一下byte类型的取值范围,为什么最大值只能取到127呢:首先数字是有正负之分,在二进制位当中最左边的二进制位是符号位,0表示正数,1表示负数,byte属于字节型,占用空间大小是1个字节,1个字节是8个bit位,所以byte类型最大值是左边一个0右边七个1:01111111,这个二进制位实际上是27-1,也就是127。byte类型最小值是-128,那么,这也说明1个字节最多可以表示256种不同的情况(-128到127,中间有一个0,共256个不同的数字)。
接下来我再给大家普及一下计算机的容量单位换算:
1byte=8bit(1个字节是8个比特位)
1KB=1024byte
1MB=1024KB
1GB=1024MB
1TB=1024GB
到这里,我相信各位会去想,我自己的硬盘是多大空间,可以存储多少个二进制位,你可以算算哦!
对于以上的八种基本数据类型,其中byte、short、int、long属于整数型,代表现实世界中的整数,只不过容量大小不同,细分的话,byte叫做字节型,short叫做短整型,int叫做整型,long叫做长整型,在实际的开发中,为了兼顾到开发效率,选择数据类型的时候也不会太斤斤计较,这四种类型中int最为常用。
八种基本数据类型中,除了整数型可表示数字之外,浮点型也可以表示数字,并且浮点型表示的数字是带有小数的,其中包括float、double,float叫做单精度浮点数,double叫做双精度浮点数,根据容量大小我们要知道double可以表示更精确的数字。在实际的开发中,double更常用一些。不过对于财务系统来说,涉及到钱的问题,double的精度也是远远不够的,后期我们会学到Java基础库中的BigDecimal类,该类可表示超大精度的数据。比较适合财务系统的开发。不过,BigDecimal则不属于基本数据类型的范畴了。
八种基本数据类型中以上六种所描述的都是数字,除了数字之外,还可以表示文字,那就是基本数据类型char,char在Java中占用两个字节,一个汉字正好是两个字节,所以char类型完全可以存储一个汉字,char类型的字面量要求使用半角的单引号括起来,例如:'a'、'A'、'国'等。char类型和short类型都是占用2个字节,所以它们可表示的种类数量是相同的,不过由于char类型表示文字,没有负数这一说,所以相对来说char类型可以取到更大的正整数。通过上表我们也可以看出,short类型取值范围是[-32768~32767],而char类型取值范围是[0~65535],它们可表示的种类数量都是65536种,只不过char可以取到最大值为65535。
八种基本数据类型中有数字,还有文字,数字和文字占了七种,还有一种类型叫做布尔型,关键字是boolean,这种类型在Java语言中只有两个值:true和false,没有其他值,用来表示现实世界中的真和假。布尔类型的数据在实际的开发中,尤其在业务逻辑判断方面起到非常重要的作用。并且使用较为频繁。布尔类型占用1个字节,true和false其实在计算机底层是使用1和0来表示的。
通过本小节的学习,大家需要知道八种基本数据类型分别包括哪些,每种类型占用几个字节,取值范围是怎样的。对于二进制和十进制的转换,大家作为一个了解即可。
4.3 字符编码(理解)
对于以上的八种基本数据类型来说,其中七种类型byte,short,int,long,float,double,boolean计算机表示起来是很容易的,因为这七种类型底层直接就是数字,十进制的数字和二进制之间有固定的转换规则,所以计算机可直接表示和处理。但是大家别忘了,除了以上的七种数据类型之外,还有一种类型叫做字符型char,这个对于计算机来说表示起来就不是那么容易了,因为字符毕竟是现实世界当中的文字,而文字每个国家又是不同的,计算机是如何表示文字的呢?
实际上,起初的时候计算机只支持数字,因为计算机最初就是为了科学计算,随着计算机的发展,为了让计算机起到更大的作用,因此我们需要让计算机支持现实世界当中的文字,一些标准制定的协会就制定了字符编码(字符集),字符编码其实就是一张对照表,在这个对照表上描述了某个文字与二进制之间的对应关系。
最初的时候美国标准协会制定了ASCII码,ASCII(AmericanStandardCodeforInformationInterchange:美国信息交换标准代码)是基于拉丁字母的一套电脑编码系统,主要用于显示现代英语和其他西欧语言。它是现今最通用的信息交换标准,并等同于国际标准ISO/IEC646。ASCII码采用1个字节编码,1个字节可以表示256种不同的形式(前面说过了),对于英文来说这个足够了,因为英文单词就是由26个英文字母拼凑而成,大小写全部才52个,再加上数字和标点符号也不会超过256个。但ASCII码对于中文来说那就不够了,因为汉字不止256个。
常见的ASCII码需要大家能够记住几个,在ASCII码中规定'a'对应97,'b'对应98,以此类推,'A'对应65,'B'对应66,以此类推,'0'字符对应48,'1'字符对应49,以此类推,这些常见的编码还是需要大家记住的。
在字符编码当中,有这样两个常见的术语需要大家了解一下:编码和解码,它们都是什么,我们拿字符'a'来解释一下:'a'是97,97对应的二进制是01100001,那么从'a'到二进制01100001的转换过程称为编码,从二进制01100001到'a'的转换过程称为解码。大家一定要注意:编码和解码要采用同一种字符编码方式(要采用同一个对照表),不然会出现乱码。这也是乱码出现的本质原因。
随着计算机的不断发展,为了让计算机支持更多国家的语言,国际标准组织又制定了ISO-8859-1字符集,又被称为latin-1,向上兼容ASCII码,仍不支持中文,主要支持西欧语言。再后来,计算机慢慢的开始支持简体中文、繁体中文、日本语、朝鲜语等,其中支持简体中文的字符集包括:GB2312、GBK、GB18030,它们的容量大小不同,其中GB2312<GBK<GB18030。支持繁体中文的是大五码Big5等。后来,在上世纪90年代初,国际组织制定了一种字符编码方式,叫做Unicode编码,这种编码方式统一了全球所有国家的文字,具体的实现包括:UTF-8,UTF-16,UTF-32等。
Java为了国际化,为了支持所有国家的语言,所以Java采用的编码方式为Unicode编码。例如字符'中'对应的Unicode码是'\u4e2d'。在实际开发中几乎所有的团队都会使用Unicode编码方式,因为这种方式更通用,兼容性更好。
通过本小节的学习,大家需要理解字符编码是什么,有什么作用,常见的ASCII码要知道一些,另外要理解什么是编码,什么是解码,要知道编码和解码采用的字符编码方式不同时会出现乱码,还要知道国际通用的编码方式为ISO-8859-1,支持简体中文的编码方式包括GB2312、GBK、GB18030,而Java采用unicode编码,目前在实际的开发中大部分团队都会选择UTF-8的编码方式。
4.4 数据类型详解
4.4.1 字符型详解(理解)
字符型char在Java语言中占用2个字节,char类型的字面量必须使用半角的单引号括起来,取值范围为[0-65535],char和short都占用2个字节,但是char可以取到更大的正整数,因为char类型没有负数。
Java语言中的char类型变量可以容纳一个汉字。请看以下程序:
我们对以上的程序编译并运行,请看下图结果:
我们可以看到Java中的char类型确实可以存储一个汉字。我们再来看以下程序,假设字符我们采用双引号括起来会怎样:
我们对以上的程序进行编译,请看下图编译结果:
我们看到编译器报错了,并且提示的错误信息是“不兼容的类型”,这是因为双引号括起来不是char类型,而是String类型,其实String类型就是Java中的字符串类型,但大家要知道字符串不属于基本数据类型,而是引用数据类型。所以类型不兼容。接下来我们来测试一下两个或多个字符是否可以使用单引号括起来,请看以下代码:
我们对以上的程序进行编译,请看下图编译结果:
我们可以看出,编译器报错了,错误信息是“未结束的字符文字”,这是因为Java中有规定,字符型只能是单个字符,当编译器检测到'ab'的时候,左边以单引号开始,继续检测到a字符,然后编译器会继续检查下一个字符是否为另一半单引号,结果不是,而是b,所以编译器报错了。这也说明了Java中的字符只能是单个字符,不能是多个字符。
接下来,我们再来看一看关于转义字符,转义字符指用一些普通的字符组合代表一些特殊的字符,由于组合用的字符改变了原意,称为转义字符。Java中的转义字符以\开始,常见的转义字符有:\t、\n、\u、\\、\',\",其中\t代表制表符,\n是换行符,\\表示一个普通的\字符,\'表示一个普通的',\"表示一个普通的"。请看以下代码:
我们对以上的程序进行编译并运行,请看下图结果:
对于以上程序,表面看起来'\t'是由两个字符构成,按说应该编译报错,因为它毕竟是两个字符组成的字符串,可是最终的结果编译通过了,并且正常运行了,这说明了'\t'表示1个字符,所以\具有转义功能,根据以上输出结果可以看出\t是制表符(abc和def之间的空白就是制表符)。接下来我们来看一看其它的转义字符。请看以下程序:
我们对以上的程序进行编译并运行,请看下图运行结果:
通过以上代码的测试,hello和world之间换行了,所以\n代表一个换行符。
对于以上代码的第4行'\''来说,这里的\是不能去掉的,如果去掉之后代码就变成了''',那么此时编译器会报错,因为单引号'在Java中有特殊含义,在这个'''代码当中第一个单引号'会主动和第二个单引号'配对,此时最后的单引号'就是多余的了,不符合Java的语法,所以会导致编译错误,如果这个时候在第二个单引号'前面添加\进行转义,代码是'\'',那么此时第二个单引号\'就是一个不具备特殊含义的普通单引号字符,这个时候第一个单引号'会和最后一个单引号'配对。编译和运行就正常了。
对于以上代码第6行来说和第4行的原理是相同的,代码\"表示普通的双引号字符。
对于第5行代码来说,代码\\联合起来表示一个普通的\字符,在Java中1个\字符不是普通的\字符,具有特殊的作用就是转义,我们想让其变成一个普通的\字符需要使用两个\来表示,代码\\中,第一个\具有转义功能,第一个\将第二个\转换成普通的\字符,以此类推,如果代码是\\\\这样,则表示结果是\\。
对于第7行代码来说,如果代码修改为'u4e2d'必然报错,因为u4e2d在这个时候是一个普通的字符串,字符串是不能使用单引号括起来的,如果在u字符前添加转义字符\则表示的含义就不同了,\u转义之后表示后面的4e2d是一个unicode码,根据unicode字符集可以查询到汉字'中'的unicode码就是4e2d,所以最终输出结果是汉字'中'。
综上所述,\n表示换行符,\'表示普通的单引号字符,\\表示一个普通的\字符,\"表示一个普通的双引号字符,\u后面的十六进制是文字的unicode编码。
通过本小节的学习,我们需要掌握的是,在Java语言中,字符char类型的数据只能使用单引号括起来,并且在Java语言中char类型完全可以存储一个汉字,另外还需要知道在Java语言中\具有转义功能,常见的转义字符要知道代表什么含义。
最后再给大家留两行代码,思考一下它的运行结果,在后面我们再详细讲解该内容,请看以下代码:
我们对以上的程序进行编译并运行,请看下图运行结果:
在这里我简单提示一下,以上程序中的“+”运算符起到的作用是求和并不是字符串连接运算,剩下的大家思考一下吧,后面我们再看。
4.4.2 整数型详解(理解)
整数型数据在java中有4种表示方式,分别是十进制、八进制、十六进制、二进制。不过要注意的是二进制写法是在Java7中引入的,对于Java7之前的版本不支持该语法。默认为十进制,以0开始表示八进制,以0x开始表示十六进制,以0b开始表示二进制。十进制、八进制、十六进制有什么区别,请看:
十进制:0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17...
八进制:0,1,2,3,4,5,6,7,10,11,12,13,14,15,16,17,20,21...
十六进制:0,1,2,3,4,5,6,7,8,9,a,b,c,d,e,f,10,11...
接下来我们在代码中试验一下以上的几种写法,请看以下代码:
我们对以上的程序进行编译并运行,请看下图运行结果:
通过测试确实可以看到八进制10是8,十六进制的10是16,二进制的10是2。在实际的开发中大部分还是直接使用十进制的方式,因为十进制对于我们来说更直观,程序的可读性会更好一些。
在java语言当中,整数型字面量被当做int类型处理,也就是说在程序中只要遇到整数型的数字,该数字会默认被当做int类型来处理,如果想表示long类型则需要在字面量后面添加L/l,建议大写L,因为小写l和1不好区分。请看以下程序:
在以上的代码中inta=10;表示声明一个int类型的变量a,然后给a变量赋值10,其中10是一个整数型字面值,根据以上规则,10默认被当做int类型来处理,那么inta=10;就表示int类型的字面量10赋值给int类型的变量a,这个过程是不存在类型转换的。
另外在以上代码中longb=10L;表示声明一个long类型的变量b,然后给b变量赋值10L,由于10后面添加有L,则编译器会将10L当做long类型来处理,long类型的字面量10L赋值给long类型的变量b,这个过程也是不存在类型转换的。
接下来我们在以上代码的基础之上继续编写,请看以下代码:
我们可以看到在第5行新增了一行代码:longc=10;,这行代码是什么原理呢?我们先来编译,看看是否符合Java的语法规则,请看下图编译结果:
接下来我们再运行一下,请看下图运行结果:
通过以上的测试我们可以看到longc=10;这种编写方式符合语法规则,并且也可以正常运行,我们来分析一下,10是整数型字面量,按照以上规则,Java中会将10默认当做int类型处理,而c变量是long类型,int类型可以赋值给long类型的变量吗?答案是可以的,因为我们已经测试过了。这是因为int占用4个字节,而long占用8个字节,在Java中小容量可以直接赋值给大容量,这个过程被称为自动类型转换。
接下来我们对以上代码继续编写,请看以下代码:
我们可以看到在第7行新增了一行代码:intd=c;,我们先对以上代码进行编译,请看以下编译结果:
我们可以看到编译器报错了,也就是以上编写方式不符合Java语法规则,我们来看一看为什么,首先我们看到错误提示信息是:不兼容的类型,从long转换到int可能会有损失。这是因为c变量是long类型占用8个字节,而负责接收的d变量是int类型占用4个字节,很明显是大容量转换成小容量,好比一大玻璃杯中的水倒向小玻璃杯,最终的结果可能会使水溢出,因为小玻璃杯可能放不下。编译器检测到这种情况的时候就不会自作主张了,需要程序员来指定,因为数据的损失需要程序员知晓,毕竟数据损失是一件很严重的事情,而编译器自身是不会负责的,于是Java中规定大容量如果需要转换成小容量,则程序员必须手动添加强制类型转换符才能编译通过,这个过程我们称为强制类型转换。我们对以上代码进行修改,请看以下修改之后的代码:
我们可以看到将第7行的代码修改为:intd=(int)c;,这就是强制类型转换,语法格式是在需要强转的数据前添加小括号,小括号中写上要转换的类型,我们对以上的程序编译并运行,请看下图结果:
通过以上的测试我们得出这样一条结论:一个数据在赋值给一个变量的时候存在三种不同的情况,第一种情况是类型一致,不存在类型转换;第二种情况是小容量可以自动赋值给大容量,称为自动类型转换;第三种情况是大容量不能直接赋值给小容量,大容量如果一定要赋值给小容量的话,必须添加强制类型转换符进行强制类型转换操作。不过需要注意的是,强制类型转换在使用的时候一定要谨慎,因为可能会导致精度损失,因为大杯水倒入小杯中,可能会导致水的溢出,不过这也不全都是,也可能精度不会损失,如果大杯中的水很少,这个时候倒入小杯中也可能是不溢出的。就像以上的运行结果,虽然进行了强制类型转换,但并没有损失精度。接下来我们一起来看看精度损失是什么情况,请看以下代码:
我们可以看到在以上代码中,int类型的变量a强转为byte类型,我们对以上代码进行编译并运行,请看下图运行结果:
4个字节的int类型300强转为1个字节的byte类型,最终的结果是44,为什么呢?这是因为首先int类型的300对应的二进制码是:00000000000000000000000100101100,强制类型转换的时候会变成1个字节,这个时候底层是将前3个字节砍掉了,也就是最后的二进制码是:00101100,这个二进制码对应的是44。所以精度损失之后的结果就是44了。接下来我们再来看一下精度损失之后成为负数的情况,请看以下代码:
我们对以上的代码进行编译和运行,请看下图结果:
为什么以上的运行结果是-106呢?这是因为计算机在任何情况下都是采用二进制补码的形式存储数据的(为什么采用二进制补码形式存储数据,这里就不再赘述了,不做学术研究)。计算机二进制编码方式包括原码、反码、补码。对于正数来说原码、反码、补码是同一个。对于负数来说呢?负数的反码是在其原码的基础上,符号位不变,其余各个位取反,例如:-15的原码是:10001111,-15反码是:11110000。负数的补码是其反码再加1。例如:-15的补码是11110000加1:11110001。换句话说-15最终在计算机上会采用11110001二进制来表示。
我们再来看看以上的程序:inta=150。4个字节的150对应的二进制是:00000000000000000000000010010110,强转时前3个字节砍掉,最终计算机存储的二进制为:10010110,我们之前说过最终存储在计算机中的是二进制补码形式,也就是说10010110现在是二进制补码形式,我们通过补码推出原码,负数的补码是反码+1,所以10010110减1就是反码10010101,反码的符号位不变,其余位取反就能得出原码:11101010,而这个值就是-106。对于以上原理大家了解即可,在实际的开发中很少使用。
接下来我们再来看一段程序,分析以下程序错在哪里,为什么以及怎么解决?
我们对以上的程序进行编译,请看下图结果:
我们可以看到,编译报错了,为什么呢?原因是:java程序见到2147483648这个整数的时候,默认将其当做int类型来处理,但这个数字本身已经超出了int类型的取值范围(int类型最大值是2147483647),所以编译报错了,注意:这里编译报错的原因并不是说long类型存不下,long类型的变量完全可以存储这个数字,以上程序出现的错误是在赋值之前,还没有进行到赋值运算,数字本身已经超出int类型范围,自己崩掉了。怎么解决以上的问题呢?其实很简单,我们只要让java程序认为2147483648是一个long类型的数据就行了,也就是说在该数字后面添加L问题就解决了(longnum=2147483648L;)。
接下来,一起来看一下以下程序是否可以编译通过,请看代码:
我们来分析一下以上的代码:byteb=1;,1是整数型字面量,在java中默认被当做int类型来处理,int类型占用4个字节,b变量是byte类型占用1个字节,根据上面所学,大容量无法直接赋值给小容量,要想赋值需要进行强制类型转换,这里没有强转,所以按理说编译是报错的,接下来我们来看一下编译结果,请看下图:
编译结果让我们意外了,编译通过了,这是为什么呢?我来给大家解释一下,这是因为在java语言有这样一条规定,大家记住就行了,如果当一个整数型字面量没有超出byte类型取值范围时,可以直接赋值给byte类型变量。那么如果整数型字面量超出byte类型取值范围会怎样呢?我们来测试一下,请看以下代码:
我们对以上的代码进行编译,请看下图编译结果:
我们可以看到编译报错了,错误信息是第7行:不兼容的类型,从int转换到byte可能会有损失。对于以上程序的第5行并没有报错。针对这个错误信息我们之前在学习强制类型转换的时候接触过,也就是说以上程序要想编译通过必须进行强制类型转换,请看以下代码:
我们对以上的程序进行编译并运行,请看下图结果:
我们通过测试结果可以看出程序正常编译并运行了,这也印证了我们上面所说:当整数型字面量没有超出byte类型取值范围时,可以直接赋值。不过,如果超出了byte类型的取值范围,在使用时必须进行强制类型转换。但需要注意的是强制类型转换会导致精度的损失,例如以上代码中int类型的128强转为byte之后结果是-128(这是因为计算机以二进制补码形式存储数字),还是要谨慎使用。
其实除了byte类型有这样的规则之外,short和char也具有同样的规则,接下来我们先对short进行测试,代码如下所示:
我们对以上的代码进行编译,请看下图编译结果:
通过以上的结果可以看出第3行代码编译通过了,但是第5行编译报错了,这是因为short类型最大值是32767。对于第5行的32768已经超出了short类型取值范围,同样如果要使用的话需要进行强制类型转换,这里就不再演示了。接下来我们再来看一看char类型,char同样满足以上规则,当没有超出char类型取值范围时,可以直接赋值,请看以下代码:
我们对以上的程序进行编译并运行,请看下图结果:
通过以上的测试可以看出当没有超出char类型取值范围的时候,整数型字面量是可以直接赋值给char类型变量的,但结果为什么会是字符a和b呢?这是因为程序charc1=97;在实际执行的时候存在隐式的类型转换,会自动将int转换成char,由于char最终是一个字符,而97正好是字符a的ASCII码,所以最终结果是字符a和b。那么如果超出char类型取值范围会怎样呢(char最大值是65535)?请看以下代码:
我们对以上代码进行编译,请看下图编译结果:
通过以上测试我们同样看到一旦超出char类型取值范围时就不能直接赋值了,要修改以上的错误也是需要进行强制类型转换操作,这里就不再演示了。
综上所述,大家记住一个结论:当一个整数型的字面量没有超出byte,short,char的取值范围,可以将该字面量直接赋值给byte,short,char类型的变量,如果超出范围则需要添加强制类型转换符。
通过本小节的学习,我们需要掌握以下几个内容:第一,Java中的整数型字面量有四种表示方式,但最常用的还是十进制;第二,整数型字面量被当做int处理,如果想当做long处理,需要在后面添加L或l;第三,小容量转换为大容量被称为自动类型转换;第四,大容量转换成小容量称为强制类型转换,强转时需要添加强制类型转换符,但要注意强转可能损失精度;第五,当整数型字面量没有超出byte、short、char的取值范围,可直接赋值。
4.4.3 布尔型详解(理解)
在Java语言中布尔类型的值只包括true和false,没有其他值,不包括1和0,布尔类型的数据在开发中主要使用在逻辑判断方面,例如:如果外面在下雨,我出门带一把雨伞。如果明天休息,咱们就一起出去玩耍吧。请看一段程序(以下程序中使用了控制语句,后面会详细讲,先大概了解一下):
我们对以上程序进行编译并运行,请看下图运行结果:
接下来对以上程序进行一个简单的解释:其中第3行代码表示定义一个布尔类型的变量isRain来表示是否下雨了,给其赋值true,以下的判断逻辑是如果isRain为true则输出"外面下雨了,出门要带一把雨伞哦!",反之则输出"外面天气晴朗,走起吧!"。第9行代码表示定义一个布尔类型的变量sex来表示性别,判断逻辑是如果sex为true则输出"哥们你好",反之则输出"姐们你好"。
接下来,我们再来看一段代码,布尔类型变量的值是否可以使用1和0:
我们对以上的程序进行编译,请看下图结果:
通过以上的测试结果可以看出,在Java中布尔类型的变量值不能使用1和0,只能使用true和false。
通过本小节的学习,大家需要掌握的是在Java语言中boolean类型的数据只有两个值,分别是true和false,没有其他值,并且boolean类型在开发中主要使用在逻辑判断方面。
4.4.4 浮点型详解(理解)
浮点型数据实际上在内存中存储的时候大部分情况下都是存储了数据的近似值,为什么呢?这是因为在现实世界中存在无穷的数据,例如:3.333333333333333333..,数据是无穷的,但是内存是有限的,所以只能存储近似值,float单精度占4个字节,double双精度占8个字节,相对来说double精度要高一些。由于浮点型数据存储的是近似值,所以一般判断两个浮点型数据是否相等的操作很少。
在java语言中有这样的一条规定:只要是浮点型的字面量,例如1.0、3.14等默认会被当做double类型来处理,如果想让程序将其当做float类型来处理,需要在字面量后面添加f/F。请看以下代码:
编译报错了:
为什么会编译报错呢?那是因为3.0默认被当做double类型来处理,占用8个字节,前面的f变量是float类型占用4个字节,大容量无法直接赋值给小容量。怎么修改呢?请看代码:
运行结果如下图所示:
以上程序的第一种方案在3.0后面添加了F,3.0F被当做float类型来处理。第二种方案是进行了强制类型转换,第二种方案可能会存在精度损失。
4.5 基本数据类型转换(理解)
基本数据类型之间是存在固定的转换规则的,现总结出以下6条规则,无论是哪个程序,将这6个规则套用进去,问题迎刃而解:
八种基本数据类型中,除boolean类型不能转换,剩下七种类型之间都可以进行转换;
如果整数型字面量没有超出byte,short,char的取值范围,可以直接将其赋值给byte,short,char类型的变量;
小容量向大容量转换称为自动类型转换,容量从小到大的排序为:byte<short(char)<int<long<float<double,其中short和char都占用两个字节,但是char可以表示更大的正整数;
大容量转换成小容量,称为强制类型转换,编写时必须添加“强制类型转换符”,但运行时可能出现精度损失,谨慎使用;
byte,short,char类型混合运算时,先各自转换成int类型再做运算;
多种数据类型混合运算,各自先转换成容量最大的那一种再做运算;
接下来,根据以上的6条规则,我们来看一下以下代码,指出哪些代码编译报错,以及怎么解决(大家注意看代码的注释信息):
编译报错,错误信息如下所示:
如何修改,请看以下代码:
运行结果如下图所示:
通过本小节的学习,大家主要掌握基本数据类型转换的6条规则,以后遇到类似的问题直接套用规则即可。
public class TypeTransferTest{
public static void main(String[] args){
// 编译报错,因为1000已经超出范围了。
//byte b1 = 1000;
// 可以
byte b2 = 20;
// 可以
short s = 1000;
// 可以
int c = 1000;
// 可以
long d = c;
// 编译报错
//int e = d;
// 可以
int f = 10 / 3;
// 可以
long g = 10;
// 编译报错
//int h = g / 3;
// 可以
long m = g / 3;
// 编译报错
//byte x = (byte)g / 3;
// 可以
short y = (short)(g / 3);
// 可以
short i = 10;
// 可以
byte j = 5;
// 编译报错
//short k = i + j;
// 可以
int n = i + j;
// 可以
char cc = 'a';
System.out.println(cc); // a
System.out.println((byte)cc); // 97
// cc 会先自动转换成int类型,再做运算
int o = cc + 100;
System.out.println(o); // 197
}
}
4.6 章节小结
通过本章节内容的学习,需要理解数据类型在程序中的作用,需要掌握在Java语言中数据类型包括哪些,其中基本数据类型又包括哪些,每个基本数据类型所占用的字节数量,byte类型取值范围等。另外需要理解字符编码在程序中的作用。还有每一种数据类型特有的语法机制,包括整数型字面值默认当做int类型处理,如果以long形式表示,需要在字面值后添加L或l;浮点型字面量默认被当做double处理,后面添加F/f才可以被当做float类型;而布尔型在Java中只有true和false没有其他值;字符型变量完全可以存储1个汉字等。除了以上描述之外,还有相对来说难度较大的类型之间的相互转换,这个就需要大家记住相应的转换规则了。
4.7 难点解惑
本章节的难点主要还是数据类型相互转换这块,例如:bytea=3;这行代码为什么可以编译通过呢?按说3是int类型,而a变量是byte类型,大容量转换成小容量不是应该使用强制类型转换符吗,这里没有使用,也可以编译通过。这是因为Java中固定的语法规则规定的,当一个整数没有超出byte类型取值范围时,可以直接赋值给byte类型的变量,其实这一设计也是为了方便程序员写代码。
另外,我们如果基于以上代码再添加这行代码:byteb=a+4;这行代码为什么又编译报错了呢?按说a是3,3+4是7,这个7并没有超出byte取值范围,为什么编译报错呢?这是因为byte类型的a和int类型的4求和,结果为int类型,并且对于以上代码来说a是一个变量,变量就是一个不确定的值,所以编译器会认为a+4可能会超出byte取值范围,所以编译报错了。
如果我们程序是这样写的:byteb=3+4;对于这行代码来说,编译又通过了,这是因为3和4不是变量,都是确定的值,编译器会直接检测出3+4等于7,这个7并没有超出范围,所以编译又通过了。
4.8 章节习题
short s1 = 1; s1 = s1 + 1;有什么错?
char 类型变量能不能储存一个中文的汉字,为什么?
float f = 1.0 有什么错?
long a = 2147483648 有什么错?
int i = 0xffff 有问题吗?
char c = 65536 有问题吗,为什么?
4.9 习题答案
short s1 = 1; s1 = s1 + 1;有什么错?
s1是short类型,1是int类型,short和int混合运算的时候short会自动转换为int类型,所以s1+1编译器检测出是int类型,int类型无法赋值给short类型的变量s1。这样修改:s1=(short)(s1+1);
char类型变量能不能储存一个中文的汉字,为什么?
java中的文字采用unicode编码,一个中文占用2个字节,char类型在java中就是占用两个字节,所以java中的char类型完全可以容纳一个汉字。
float f = 1.0 有什么错?
1.0字面量被当做double类型处理,大容量double无法赋值给小容量float。这样修改,两种方案:第一种方案是1.0后面添加f/F。第二种方案是强制类型转换:floatf=(float)1.0;
long a = 2147483648 有什么错?
不是 long 类型存不下 2147483648,而是 java 把 2147483648 当做 int 类型来处理,但本身已经超出 int 类型范围。这样修改:long a = 2147483648L;
int i = 0xffff 有问题吗?
没有问题:0xffff 以 0x 开始表示十六进制表示方式,ffff 转换成十进制是:65535
char c = 65536 有问题吗,为什么?
65536 已经超出char 类型取值范围,不能直接赋值,这样修改:char c = (char)65536;
4.10 day06课堂笔记
1、数据类型
1.1、数据类型有什么用?
数据类型用来声明变量,程序在运行过程中根据不同的数据类型分配不同大小的空间。
int i = 10;
double d = 1.23;
i变量和d变量类型不同,空间大小不同。
1.2、数据类型在java语言中包括两种:
第一种:基本数据类型
基本数据类型又可以划分为4大类8小种:
第一类:整数型
byte,short,int,long (没有小数的)
第二类:浮点型
float,double (带有小数的)
第三类:布尔型
boolean:只有两个值true和false,true表示真,false表示假
第四类:字符型
char:java中规定字符型字面量必须使用单引号括起来。属于文字。
8小种:
byte,short,int,long
float,double
boolean
char
第二种:引用数据类型
字符串型String属于引用数据类型。
String字符串不属于基本数据类型范畴。
java中除了基本数据类型之外,剩下的都是引用数据类型。
引用数据类型后期面向对象的时候才会接触。
1.3、8种基本数据类型中
整数型:byte short int long有什么区别?
浮点型:float和double有什么区别?
区别:占用的空间大小不同。
关于计算机存储单位?
计算机只能识别二进制。(1001101100...)
1字节 = 8bit(8比特)--> 1byte = 8bit
1bit就是一个1或0.
1KB = 1024byte
1MB = 1024KB
1GB = 1024MB
1TB = 1024GB
byte b = 2; 在计算机中是这样表示的:00000010
short s = 2; 在计算机中是这样表示的:00000000 00000010
int i = 2;在计算机中是这样表示的:00000000 00000000 00000000 00000010
...
类型 占用字节数量(byte)
--------------------------------------------------------------------------------------------
byte 1
short 2
int 4
long 8
float 4
double 8
boolean 1 (1byte的1或0,00000001(true)或00000000(false))
char 2
关于二进制?
二进制???
1 2 3 4 5 6 7
1 10 11 100 101 110 111 ....
十进制转换成二进制
125 转换成二进制???
办法:除以2,然后余数逆序输出。
1111101
二进制转换成十进制
2的2次方 2的1次方 2的0次方
1 1 1
4 2 1
1*4 + 1*2 + 1*1 = 7
2的2次方 2的1次方 2的0次方
1 0 1
4 2 1
1*4 + 0*2 + 1*1 = 5
1.4、byte类型的取值范围?
byte是 [-128 ~ 127] 共可以标识256个不同的数字。
byte类型的最大值是怎么计算出来的?
byte是1个字节,是8个比特位,所以byte可以存储的最大值是:
01111111
注意:在计算机当中,一个二进制位最左边的是符号位,当为0时表示正数,当为1时表示负数。所以byte类型最大值是:0111 1111
那么是不是2的7次方-1呢?
是不是:1000 0000(前边是一个二进制) - 1
byte类型最大值是:2的7次方 - 1.
有几个取值范围需要大家记住:
(1个字节)byte: [-128 ~ 127]
(2个字节)short:[-32768 ~ 32767] 可以表示65536个不同的数字
(4个字节)int: [-2147483648 ~ 2147483647]
(2个字节)char: [0~65535] 可以表示65536个不同的数字
short和char实际上容量相同,不过char可以表示更大的数字。因为char表示的是文字,文字没有正负之分,所以char可以表示更大的数字。
1.5、对于8种基本数据类型来说:
其中byte,short,int,long,float,double,boolean,这7种类型计算机表示起来比较容易,因为他们都是数字。其中布尔类型只有两个值true和false,实际上true和false分别在C++中对应的是1和0,1为true,false为0。
对于char类型来说计算机表示起来比较麻烦,因为char对应的是文字,每一个国家的文字不一样,文字不能直接通过“自然算法”转换成二进制。这个时候怎么办?字符编码诞生了。
什么是字符编码?
字符编码是人为的定义的一套转换表。
在字符编码中规定了一系列的文字对应的二进制。
字符编码其实本质上就是一本字典,该字段中描述了文字与二进制之间的对照关系。
字符编码是人为规定的。(是某个计算机协会规定的。)
字符编码涉及到编码和解码两个过程,编码和解码的时候必须采用同一套字符编码方式,不然就会出现乱码。
关于字符编码的发展过程?
起初的时候计算机是不支持文字的,只支持科学计算。实际上计算机起初是为了战争而开发的,计算导弹的轨道....
后来随着计算机的发展,计算机开始支持文字,最先支持的文字是英文,英文对应的字符编码方式是:ASCII码。
ASCII码采用1byte进行存储,因为英文字母是26个。(键盘上所有的键全部算上也超不过256个。1byte可以表示256种不同的情况。所以英文本身在计算机方面就占优势。)
'a' --(采用ASCII码进行编码)-> 01100001
01100001 --(采用ASCII码进行解码)-> 'a'
如果编码和解码采用的不是同一个编码方式,会出现乱码。
'b' ---> 98
'c' ---> 99...
'a' ---> 97
'A' ---> 65
'B' ---> 66
...
'0' ---> 48 (这个'0'不是那个0,是文字'0')
'1' ---> 49
随着计算机语言的发展,后来国际标准组织制定了ISO-8859-1编码方式,又称为latin-1编码方式,向上兼容ASCII码。但不支持中文。
后来发展到亚洲,才支持中文,日文,韩文....
中文这块的编码方式:GB2312<GBK<GB18030 (容量的关系)
以上编码方式是简体中文。
繁体中文:big5(台湾使用的是大五码。)
在java中,java语言为了支持全球所有的文字,采用了一种字符编码方叫做unicode编码。unicode编码统一了全球所有的文字,支持所有文字。具体的实现包括:UTF-8 UTF-16 UTF-32....
需要记住:
ASCII('a'是97 'A'是65 '0'是48...)
ISO-8859-1(latin-1)
GB2312
GBK
GB18030
Big5
unicode(utf8 utf16 utf32)
2、八种基本数据类型详解
字符型 char
整数型 byte short int long
浮点型 float double
布尔型 boolean
4.10.1 day06代码
字符型 char
/*
字符型:
char
1、char占用2个字节。
2、char的取值范围:[0-65535]
3、char采用unicode编码方式。
4、char类型的字面量使用单引号括起来。
5、char可以存储一个汉字。
*/
public class CharTest01{
public static void main(String[] args){
// char可以存储1个汉字吗?
// 可以的,汉字占用2个字节,java中的char类型占用2个字节,正好。
char c1 = '中';
System.out.println(c1);
char c2 = 'a';
System.out.println(c2);
// 0如果加上单引号的话,0就不是数字0了,就是文字0,它是1个字符。
char c3 = '0';
System.out.println(c3);
// 错误: 不兼容的类型: String无法转换为char
//char c4 = "a";
// 错误: 未结束的字符文字
//char c5 = 'ab';
// 错误: 未结束的字符文字
//char c6 = '1.08';
}
}
/*
关于java中的转义字符
java语言中“\”负责转义。
\t 表示制表符tab
*/
public class CharTest02{
public static void main(String[] args){
// 普通的't'字符
char c1 = 't';
System.out.println(c1);
//char x = 'ab';
// 根据之前所学,以下代码应该报错。
// 经过测试以下代码 \t 实际上是1个字符,不属于字符串
// 两个字符合在一起表示一个字符,其中 \t 表示“制表符tab”
char c2 = '\t'; //相当于键盘上的tab键
System.out.println("abcdef");
System.out.println("abctdef");
// \的出现会将紧挨着的后面的字符进行转义。\碰到t表示tab键。
System.out.println("abc\tdef");
/*
System.out.println(); 换行
System.out.print(); 不换行
*/
System.out.print("HelloWorld");
System.out.println("123abcdef");
System.out.print("abc");
//char c3 = 'n'; // 普通的n字符
char c3 = '\n'; // 换行符
//System.out.print(c3);
System.out.println(c3);
System.out.println("def");
// 假设现在想在控制台输出一个 ' 字符怎么办?
// 错误: 空字符文字
//System.out.println(''');
// \' 表示一个普通不能再普通的单引号字符。(\'联合起来表示一个普通的 ')
System.out.println('\'');
// 假设现在想在控制台输出一个 \ 字符怎么办?
//错误: 未结束的字符文字
//System.out.println('\');
// 在java中两个反斜杠代表了一个“普通的反斜杠字符”
System.out.println('\\');
// 双引号括起来的是字符串
System.out.println("test");
// 希望输出的结果是:"test"
// 错误: 需要')'
//System.out.println(""test"");
System.out.println("“test”"); //内部的双引号我用中文的行吗?可以。
System.out.println("");
// 编译报错。
//System.out.println(""");
//System.out.println("\"");
System.out.println("\"test\"");
// 这个可以输出吗?
// 这个不需要专门进行转义。
// 这个 ' 在这里只是一个普通的字符,不具备特殊含义。
System.out.println("'");
//以下都有问题
//System.out.println(''');
//System.out.println(""");
// 可以的。
System.out.println("'这样呢'");
// 编译报错,因为:4e2d 是一个字符串
// 错误: 未结束的字符文字
//char x = '4e2d';
// 反斜杠u(\u)表示后面的是一个字符的unicode编码。
// unicode编码是十六进制的。
// jdk8的bin目录下有native2ascii.exe命令,可以将输入的文字翻译成unicode编码
char x = '\u4e2d';
System.out.println(x); // '中'
}
}
/*
十六进制,满16进1位:
1 2 3 4 5 6 7 8 9 a b c d e f 10
11 12 13 14 15 16 17 18 19 1a 1b 1c 1d 1e 1f 20...
八进制:
0 1 2 3 4 5 6 7 10 11 12 13 14 15 16 17 20.....
*/
整数型 int
/*
整数型在java语言中共4种类型:
byte 1个字节 最大值127
short 2个字节 最大值32767
int 4个字节 2147483647是int最大值,超了这个范围可以使用long类型。
long 8个字节
1个字节 = 8个二进制位
1byte = 8bit
对于以上的四个类型来说,最常用的是int。
开发的时候不用斤斤计较,直接选择使用int就行了。
在java语言中整数型字面量有4种表示形式:
十进制:最常用的。
二进制
八进制
十六进制
*/
public class IntTest01{
public static void main(String[] args){
// 十进制
int a = 10;
System.out.println(a); // 10
// 八进制
int b = 010;
System.out.println(b); // 8
// 十六进制
int c = 0x10;
System.out.println(c); // 16
int x = 16; //十进制方式
System.out.println(x);
// 二进制(JDK8的新特性,低版本不支持。)
int d = 0b10;
System.out.println(d); // 2
}
}
/*
在java中有一条非常重要的结论,必须记住:
在任何情况下,整数型的“字面量/数据”默认被当做int类型处理。(记住就行)
如果希望该“整数型字面量”被当做long类型来处理,需要在“字面量”后面添加L/l
建议使用大写L,因为小写l和1傻傻分不清。
*/
public class IntTest02{
public static void main(String[] args){
// 分析这个代码存在类型转换吗,以下代码什么意思?
// 不存在类型转换
// 100 这个字面量被当做int类型处理
// a变量是int类型,所以不存在类型的转换。
// int类型的字面量赋值给int类型的变量。
int a = 100;
System.out.println(a);
// 分析这个程序是否存在类型转换?
// 分析:200这个字面量默认被当做int类型来处理
// b变量是long类型,int类型占4个字节,long类型占8个字节
// 小容量可以自动转换成大容量,这种操作被称为:自动类型转换。
long b = 200;
System.out.println(b);
// 分析这个是否存在类型转换?
// 这个不存在类型转换。
// 在整数型字面量300后面添加一个L之后,300L联合起来就是一个long类型的字面量
// c变量是long类型,long类型赋值给long类型不存在类型转换。
long c = 300L;
System.out.println(c);
// 题目:
// 可以吗?存在类型转换吗?
// 2147483647默认被当做int来处理
// d变量是long类型,小容量可以自动赋值给大容量,自动类型转换
long d = 2147483647; // 2147483647是int最大值。
System.out.println(d);
// 编译器会报错吗?为什么?
// 在java中,整数型字面量一上来编译器就会将它看做int类型
// 而2147483648已经超出了int的范围,所以在没有赋值之前就出错了。
// 记住,不是e放不下2147483648,e是long类型,完全可以容纳2147483648
// 只不过2147483648本身已经超出了int范围。
// 错误: 整数太大
//long e = 2147483648;
// 怎么解决这个问题呢?
long e = 2147483648L;
System.out.println(e);
}
}
/*
1、小容量可以直接赋值给大容量,称为自动类型转换。
2、大容量不能直接赋值给小容量,需要使用强制类型转换符进行强转。
但需要注意的是:加强制类型转换符之后,虽然编译通过了,但是运行
的时候可能会损失精度。
*/
public class IntTest03{
public static void main(String[] args){
// 不存在类型转换
// 100L是long类型字面量,x是long类型字面量。
long x = 100L;
// x是long类型,占用8个字节,而y变量是int类型,占用4个字节
// 在java语言中,大容量可以“直接”赋值给小容量吗?不允许,没有这种语法。
// 编译错误信息:错误: 不兼容的类型: 从long转换到int可能会有损失
//int y = x;
// 大容量转换成小容量,要想编译通过,必须加强制类型转换符,进行强制类型转换。
// 底层是怎么进行强制类型转换的呢?
// long类型100L:00000000 00000000 00000000 00000000 00000000 00000000 00000000 01100100
// 以上的long类型100L强转为int类型,会自动将“前面”的4个字节砍掉:00000000 00000000 00000000 01100100
int y = (int)x; // 这个(int)就是强制类型转换符,加上去就能编译通过。
// 但是要记住:编译虽然过了,但是运行时可能损失精度。
System.out.println(y); // 100
// 定义变量a int类型,赋值100
int a = 100;
System.out.println(a);
int b = a; // 将变量a中保存的值100复制一份给b变量。
System.out.println(b);
}
}
java中有一个语法规则:
当这个整数型字面量没有超出byte的取值范围,那么这个整数型字面量可以直接赋值给byte类型的变量。这种语法机制是为了方便写代码,而存在的。
当整数型字面量没有超出short类型取值范围的时候,该字面量可以直接赋值给short
/*
java中有一个语法规则:
当这个整数型字面量没有超出byte的取值范围,那么这个
整数型字面量可以直接赋值给byte类型的变量。
这种语法机制是为了方便写代码,而存在的。
*/
public class IntTest04{
public static void main(String[] args){
// 分析:以下代码编译可以通过吗?
// 300 被默认当做int类型
// b变量是byte类型
// 大容量转换成小容量,要想编译通过,必须使用强制类型转换符
// 错误: 不兼容的类型: 从int转换到byte可能会有损失
//byte b = 300;
// 要想让以上的程序编译通过,必须加强制类型转换符
// 虽然编译通过了,但是可能精度损失。
// 300这个int类型对应的二进制:00000000 00000000 00000001 00101100
// byte占用1个字节,砍掉前3个字节,结果是:00101100 (44)
byte b = (byte)300;
System.out.println(b); // 44
// 这个编译能通过吗?
// 1是int类型,默认被当做int类型来看。
// x是byte类型,1个字节,大容量无法直接转换成小容量。
// 按说是编译报错的。
byte x = 1;
byte y = 127;
// 错误: 不兼容的类型: 从int转换到byte可能会有损失
byte z = 128;
// 当整数型字面量没有超出short类型取值范围的时候,该字面量可以直接赋值给short
// 类型的变量。
short s = 1;
short s1 = 32767;
// 错误: 不兼容的类型: 从int转换到short可能会有损失
//short s2 = 32768;
System.out.println(s);
}
}
4.11 day07课堂笔记
1、关于数据类型详解
字符型:char
整数型:byte short int long
byte b = 127; // 可以直接赋值
short s = 32767; // 可以直接赋值
char // 没有超出char的取值范围可以直接赋值给char变量吗?
浮点型:float double
布尔型:boolean
2、综合的看一下,在类型转换的时候需要遵循哪些规则?
第一条:八种基本数据类型中,除 boolean 类型不能转换,剩下七种类型之间都可以进行转换;
第二条:如果整数型字面量没有超出 byte,short,char 的取值范围,可以直接将其赋值给byte,short,char 类型的变量;
第三条:小容量向大容量转换称为自动类型转换,容量从小到大的排序为:
byte < short(char) < int < long < float < double,其中 short和 char 都占用两个字节,但是char 可以表示更大的正整数;
第四条:大容量转换成小容量,称为强制类型转换,编写时必须添加“强制类型转换符”,但运行时可能出现精度损失,谨慎使用;
第五条:byte,short,char 类型混合运算时,先各自转换成 int 类型再做运算;
第六条:多种数据类型混合运算,各自先转换成容量最大的那一种再做运算;
所有的笔试题都超不出以上的6条规则。死记硬背。
4.11.1 day07代码
第一个结论:当一个整数赋值给char类型变量的时候,会自动转换成char字符型,最终的结果是一个字符。
第二个结论:当一个整数没有超出byte short char的取值范围的时候,这个整数可以直接赋值给byte short char类型的变量。
/*
1、整数能否直接赋值给char
2、char x = 97;
这个java语句是允许的,并且输出的结果是'a'
经过这个测试得出两个结论:
第一个结论:当一个整数赋值给char类型变量的时候,会自动转换成char字符型,
最终的结果是一个字符。
第二个结论:当一个整数没有超出byte short char的取值范围的时候,
这个整数可以直接赋值给byte short char类型的变量。
*/
public class CharTest03{
public static void main(String[] args){
char c1 = 'a';
System.out.println(c1);
// 这里会做类型转换吗?
// 97是int类型(这是java中规定,默认当做int处理)
// c2是char类型
//char c2 = (char)97; // 不需要这样做。
char c2 = 97;
System.out.println(c2); // 'a'
// char类型取值范围:[0~65535]
char c3 = 65535; // 实际上最终是一个“看不懂”的字符。
System.out.println(c3);
//错误: 不兼容的类型: 从int转换到char可能会有损失
//char c4 = 65536;
// 怎么解决以上问题?
char c4 = (char)65536;
byte x = 1;
short s = 1;
char c = 1;
}
}
二进制原码反码补码
/*
1、计算机在任何情况下都只能识别二进制
2、计算机在底层存储数据的时候,一律存储的是“二进制的补码形式”
计算机采用补码形式存储数据的原因是:补码形式效率最高。
3、什么是补码呢?
实际上是这样的,二进制有:原码 反码 补码
4、记住:
对于一个正数来说:二进制原码、反码、补码是同一个,完全相同。
int i = 1;
对应的二进制原码:00000000 00000000 00000000 00000001
对应的二进制反码:00000000 00000000 00000000 00000001
对应的二进制补码:00000000 00000000 00000000 00000001
对于一个负数来说:二进制原码、反码、补码是什么关系呢?
byte i = -1;
对应的二进制原码:10000001
对应的二进制反码(符号位不变,其它位取反):11111110
对应的二进制补码(反码+1):11111111
5、分析 byte b = (byte)150;
这个b是多少?
int类型的4个字节的150的二进制码是什么?
00000000 00000000 00000000 10010110
将以上的int类型强制类型转为1个字节的byte,最终在计算机中的二进制码是:
10010110
千万要注意:计算机永远存储的都是二进制补码形式。也就是说上面
10010110 这个是一个二进制补码形式,你可以采用逆推导的方式推算出
这个二进制补码对应的原码是啥!!!!!!
10010110 ---> 二进制补码形式
10010101 ---> 二进制反码形式
11101010 ---> 二进制原码形式
*/
public class IntTest05{
public static void main(String[] args){
// 编译报错:因为150已经超出了byte取值范围,不能直接赋值,需要强转
//byte b = 150;
byte b = (byte)150;
// 这个结果会输出多少呢?
System.out.println(b); // -106
}
}
结论:byte、char、short做混合运算的时候,各自先转换成int再做运算。
/*
结论:byte、char、short做混合运算的时候,各自先转换成int再做运算。
*/
public class IntTest06{
public static void main(String[] args){
char c1 = 'a';
byte b = 1;
// 注意:这里的"+"是负责求和的
System.out.println(c1 + b); // 98
// 错误: 不兼容的类型: 从int转换到short可能会有损失
//short s = c1 + b; // 编译器不知道这个加法最后的结果是多少。只知道是int类型。
// 这样修改行吗?
//错误: 不兼容的类型: 从int转换到short可能会有损失
//short s = (short)c1 + b;
short s = (short)(c1 + b);
//short k = 98;
int a = 1;
//错误: 不兼容的类型: 从int转换到short可能会有损失
// short x = 1; 可以
short x = a; // 不可以,编译器只知道a是int类型,不知道a中存储的是哪个值。
System.out.println(x);
}
}
结论:多种数据类型做混合运算的时候,最终的结果类型是“最大容量”对应的类型。
char+short+byte 这个除外。
因为char + short + byte混合运算的时候,会各自先转换成int再做运算。
/*
结论:多种数据类型做混合运算的时候,最终的结果类型是“最大容量”对应的类型。
char+short+byte 这个除外。
因为char + short + byte混合运算的时候,会各自先转换成int再做运算。
*/
public class IntTest07{
public static void main(String[] args){
long a = 10L;
char c = 'a';
short s = 100;
int i = 30;
// 求和
System.out.println(a + c + s + i); //237
// 错误: 不兼容的类型: 从long转换到int可能会有损失
// 计算结果是long类型
//int x = a + c + s + i;
int x = (int)(a + c + s + i);
System.out.println(x);
// 以下程序执行结果是?
// java中规定,int类型和int类型最终的结果还是int类型。
int temp = 10 / 3; // / 是除号。(最终取整)
System.out.println(temp); // 3.33333吗?结果是:3
// 在java中计算结果不一定是精确的。
int temp2 = 1 / 2;
System.out.println(temp2); // 0
}
}
浮点型
java中规定,任何一个浮点型数据默认被当做double来处理。如果想让这个浮点型字面量被当做float类型来处理,那么请在字面量后面添加F/f。
/*
关于java语言中的浮点型数据
浮点型包括:
float 4个字节
double 8个字节
float是单精度
double是双精度
double更精确
比如说:
10.0 / 3 如果采用float来存储的话结果可能是:3.33333
10.0 / 3 如果采用double来存储的话结果可能是:3.3333333333333
但是需要注意的是,如果用在银行方面或者说使用在财务方面,double
也是远远不够的,在java中提供了一种精度更高的类型,这种类型专门
使用在财务软件方面:java.math.BigDecimal (不是基本数据类型,属于
引用数据类型。)
float和double存储数据的时候都是存储的近似值。
为什么?
因为现实世界中有这种无线循环的数据,例如:3.3333333333333....
数据实际上是无限循环,但是计算机的内存有限,用一个有限的资源
表示无限的数据,只能存储近似值。
long类型占用8个字节。
float类型占用4个字节。
哪个容量大?
注意:任意一个浮点型都比整数型空间大。
float容量 > long容量。
java中规定,任何一个浮点型数据默认被当做double来处理。
如果想让这个浮点型字面量被当做float类型来处理,那么
请在字面量后面添加F/f。
1.0 那么1.0默认被当做double类型处理。
1.0F 这才是float类型。(1.0f)
*/
public class FloatTest01{
public static void main(String[] args){
// 这个不存在类型转换
// 3.1415926是double类型
// pi是double类型
double pi = 3.1415926;
System.out.println(pi);
// 这个可以吗?
//错误: 不兼容的类型: 从double转换到float可能会有损失
//float f = 3.14;
// 怎么修改以上的代码呢?
// 第一种方式:在字面量后面添加F/f
//float f = 3.14f;
//float f = 3.14F;
// 第二种方式:强制类型转换,但可能损失精度。谨慎使用。
float f = (float)3.14;
System.out.println(f);
// 分析这个程序,可以编译通过吗?
// 错误: 不兼容的类型: 从double转换到int可能会有损失
// 原理:先将5转换成double类型,然后再做运算,结果是double
// 大容量无法直接赋值给小容量,需要强转。
//int i = 10.0 / 5;
// 怎么修改
int i = (int)10.0 / 5;
System.out.println(i); // 2
// 可以这样修改吗?强转的时候只留下整数位。
int x = (int)(10.0 / 5);
System.out.println(x); // 2
}
}
布尔型
/*
1、在java语言中boolean类型只有两个值,没有其他值:
true和false。
不像C或者C++,C语言中1和0也可以表示布尔类型。
2、boolean类型在实际开发中使用在哪里呢?
使用在逻辑判断当中,通常放到条件的位置上(充当条件)
*/
public class BooleanTest01{
public static void main(String[] args){
//错误: 不兼容的类型: int无法转换为boolean
//boolean xingBie = 1;
// 需求规定:如果为true则表示男,为false则表示女。
//boolean sex = true;
boolean sex = false;
int a = 10;
int b = 20;
System.out.println(a < b); // true
System.out.println(a > b); // false
//boolean flag = a < b;
boolean flag = (a < b); // 运算符是有优先级的,不确定可以加小括号。加了小括号就一定是先执行的。
System.out.println(flag); // true
// 后面我们会学习if语句
// if语句是一个条件语句
// 可以实现什么功能呢?例如:如果A账户的钱充足,才可以向B账户转账。
// 例如:如果这个布尔型值是true,则表示男性,为false则表示女性。
if(sex){
System.out.println("男");
}else{
System.out.println("女");
}
// 以上的if语句看不懂没关系。后面会学习。
}
}
5 第五章 运算符
5.1 章节目标与知识框架
5.1.1 章节目标
掌握常见的Java运算符的使用,包括算术运算符、关系运算符、逻辑运算符、赋值运算符、条件运算符、字符串连接运算符。
5.1.2 知识框架
5.2 运算符概述(了解)
运算符是指对操作数的运算方式。组成表达式的Java操作符有很多种(什么是操作数和操作符,例如1+2,其中1和2都是操作数,+是操作符,操作符和操作数联合起来构成表达式)。运算符按照其要求的操作数数目来分,可以有单目运算符(1个操作数)、双目运算符(2个操作数)和三目运算符(3个操作数)。运算符按其功能来分,有算术运算符、赋值运算符、关系运算符、逻辑运算符、位运算符、条件运算符、字符串连接运算符和其他运算符。常见的运算符如下所示:
每个编程语言当中都有运算符,基本上都是通用的,这么多的运算符,它们同时出现的时候有优先级吗?答案是有的。那么如果不确定它们的优先级怎么办,其实很简单,直接加小括号就可以了,添加有小括号优先级一定是高的,所以优先级不需要死记硬背,不确定就加小括号,例如:1+2*3,想确保先求和,你就需要这样写:(1+2)*3。
5.3 运算符详解
5.3.1 算术运算符(掌握)
算术运算符包括:+(两个数字求和)、-(两个数字相减)、*(两个数字乘积)、/(两个数字相除)、%(两个数字取模或者求余)、++(单目运算符,对数字自加1)、--(单目运算符,对数字自减1)。对于初学者来说,可能%、++、--比较生疏一些,我们重点来学习一下,请看以下代码(注意代码中的注释信息):
运行结果如下图所示:
通过以上代码的学习,我们得知,第一:两个int类型数据进行数学运算之后的结果一定是int类型;第二:++或--可以出现在变量前也可以是变量后;第三:++无论出现在变量前还是变量后,只要执行了++,那么变量必然会自加1;第四:++出现在变量后会先进行赋值运算,再自加1;第五:++出现在变量前会先自加1,再进行赋值运算。那么,大家思考一下以下代码的执行结果是什么?
5.3.2 关系运算符(掌握)
关系运算符主要是完成数据和数据之间的比较,比如:5>3,结果是true(真),5>10,结果是false(假),那么关系运算符都有哪些呢?>、>=、<、<=、==、!=。关系运算符是比较简单容易理解的,我们来看一段代码(注意代码中的注释):
通过以上代码可以看出,任何一个关系运算符的运算结果都是布尔类型,最后的结果不是true就是false,没有其他值,并且我们也看到a变量和b变量在比较的时候是拿着变量当中保存的值进行比较。也就是说a==b实际上是拿着a变量中保存的10和b变量中保存的10进行比较,看它们是否相等。
5.3.3 逻辑运算符(掌握)
逻辑运算符主要包括逻辑与(&),逻辑或(|),逻辑异或(^),短路与(&&),短路或(||)。所有逻辑运算符的特点是操作数都是布尔类型,并且最终的运算结果也是布尔类型。逻辑运算符的基本运算规则如下表所示:
接下来我们来研究一段代码,重点看看短路是如何发生的(注意看注释信息):
通过以上的测试,可以得出短路与(&&)在左边的表达式结果为false的时候,右边的表达式则不再执行,这种现象被称为短路现象,这种机制也显得短路与比较智能一些,效率更高一些,所以在实际开发中短路与(&&)的使用率要比逻辑与高一些。但这并不是绝对的,有的时候也可能会选择使用逻辑与(&),这取决于你是否期望右边的表达式一定执行。
大家思考一个问题,短路或(||)在什么情况下会发生短路现象呢?
5.3.4 赋值运算符(掌握)
赋值运算符目前也是只需要掌握=、+=、-=、*=、/=、%=,其它和二进制相关的内容也是到后面遇到的时候再详细学习。赋值类的运算符包括基本赋值运算符(=)和扩展的赋值运算符(+=、-=、*=、/=、%=)。我们来看一段代码:
x+=1和x=x+1真的是完全相同吗?我们来看下面的代码:
根据以上代码测试得出,对于扩展类的赋值运算符在运算的过程中不会改变运算的结果类型,也就是说byteb=100;b+=1000;b变量最初是byte类型,最后的运算结果还是一个byte类型。这是一条固定的语法规则,大家记住就行了,以后在使用扩展类赋值运算符的时候要谨慎,不小心就会精度损失的。
5.3.5 条件运算符(掌握)
条件运算符属于三目运算符,它的语法结构是:布尔表达式?表达式1:表达式2。它的运行原理是这样的,先判断布尔表达式的结果是true还是false,如果是true,则选择表达式1的结果作为整个表达式的结果,反之则选择表达式2的结果作为整个表达式的结果。来看一段代码:
实际上条件运算符和后期要学习的控制语句if可以达到同等效果。在实际开发中灵活运用条件运算符会让你的代码看起来更加简洁清凉,达到意想不到的效果。
5.3.6 字符串连接运算符(掌握)
在java语言中所有的字符串都使用半角双引号括起来的,字符串属于引用数据类型,不属于基本数据类型的范畴,怎么定义一个字符串的变量呢?例如:Stringname=“jack”;,这就类似于inti=10;是一样的,int是一种整数类型,i是变量,10是整数型字面量。那么String则是一种字符串类型,name是变量,”jack”是字符串型字面量。在java编程中对字符串的操作是非常频繁的,例如字符串的连接操作,此时就需要使用“+”字符串连接运算符了。
实际上“+”运算符在java语言中有两个作用,作用一是对数字进行求和运算,作用二就是字符串连接运算,那么它在什么时候是进行求和,什么时候又进行字符串连接呢?大家可以这样进行区分,当“+”运算的时候,两边的操作数都是数字的话,一定会进行求和运算,只要其中有一个操作数是字符串类型,那么一定会进行字符串拼接运算,字符串拼接之后的结果还是字符串类型。需要注意的是,当一个表达式当中有多个“+”,并且在没有小括号的前提下,遵循自左向右的顺序依次执行。我们来看一段程序:
对于程序System.out.println(a+"+"+b+"="+a+b);的分析见下图:
对于程序System.out.println(a+"+"+b+"="+(a+b));的分析见下图:
总之,使用“+”进行字符串拼接在开发中使用太频繁了,大家一定要将其掌握,尤其是怎么将一个变量放到字符串当中,你还记得以上程序的口诀吗?
除了以上所讲的运算符之外,实际上还有其它运算符,例如位运算符主要操作二进制位的,还有运算符instanceof、new等。这里就不再赘述了,当前使用较少,后期使用到的时候我们再看。
5.4 章节小结
本章节内容需要大家掌握开发中常用运算符的使用,包括算术运算符、关系运算符、逻辑运算符、赋值运算符、条件运算符、字符串连接运算符。实际上除了以上所讲运算符之外,还有其它运算符,比如位运算符、instanceof、new等运算符,在当下使用较少,后期用到的时候再进行学习。
本章节内容具有通用性,不仅仅是在Java中可以使用,在其他的编程语言中也可以使用。运算符在实际的开发中使用频率很高,因为软件存在的目的就是为了解决数据的处理。在这里想和大家说的是,学好每一个运算符的用法,为后面的学习打下扎实的基础。
5.5 难点疑惑
本章节的难点主要还是++运算符的使用,例如以下代码:
我们对以上程序进行分析,k变量等于10,输出k++,我们可以将System.out.println(k++)这行代码进行拆解,可以拆解为两行代码:inttemp=k++;System.out.println(temp);,也就是说实际上输出k++就是输出temp,根据之前所学规则,temp等于10,所以System.out.println(k++)会输出10。不过当这行代码执行结束之后k就不再是10了,变成了11。
我们继续再向下分析:System.out.println(++k)。这行代码同样也可以拆分为两行代码:inttemp2=++k;System.out.println(temp2);。其中k已经等于11了,根据以上所学规则temp2的值是12,所以最终输出12。
我们对以上的程序进行编译并运行,来看一下我们分析是否正确,请看下图:
通过以上程序的运行我们可以看出以上的分析是没有问题的,所以对于初学者来说,这块内容确实是一个不太好理解的地方。
5.6 章节习题
第一题:判断以下程序的输出结果。
第二题:判断以下程序的输出结果。
第三题:判断以下程序的输出结果。
第四题:判断以下程序的输出结果。
第五题:判断以下程序的输出结果。
第六题:判断以下程序的输出结果。
5.7 习题答案
第一题答案:
第二题答案:
第三题答案:
第四题答案:
第五题答案:
第六题答案:
// 大家讨论最多的一个问题。
// 如果只是针对于面试题的话,建议死记硬背。
public class Homework01{
public static void main(String[] args){
int i = 10;
i = i++;
// 大部分同学都会认为这个i一定是11
// 这个i变量最终的结果是10(惊讶)
// 首先,第一点:这种代码以后不会有人写。
// 其次:第二点:没必要讨论这个问题,因为在C++中运行结果确实是11.
// java中运行结果是10
// c++中运行结果是11
// 为什么?因为java和c++的编译器是不同的人开发的。原理不同。
System.out.println(i);
// 在java语言中i++,这种表达式在执行的时候,会提前先将i变量找一个临时
// 变量存储一下。(C++中并没有这样做。)
/*
int k = 10;
k = k++;
*/
int k = 10;
// k = k++;对应的是下面三行代码
int temp = k;
k++;
k = temp;
System.out.println(k);
}
}
public class Homework02{
public static void main(String[] args){
int x = 10;
int a = x + x++; // 该行代码只要结束,x就是11
System.out.println("a =" + a); // 20 (x走到这里就变成11了)
System.out.println("x =" + x); // 11
int b = x + ++x; // 23 该行代码结束之后,x就是12
System.out.println("b =" + b); // 23
System.out.println("x =" + x); // 12
int c = x + x--; // 该行代码执行结束之后,x就是11
System.out.println("c =" + c); // 24
System.out.println("x =" + x); // 11
int d = x + --x; // 该行代码只要执行结束,x就是10了。
System.out.println("d =" + d); // 21
System.out.println("x =" + x); // 10
}
}
public class Homework03{
public static void main(String[] args){
int i = 5;
int j = 5;
int m = 5;
int n = 5;
i++; // 这行代码执行结束之后,i是6
j = j + 1; // 这行代码执行结束之后,j是6
m--; // 这行代码执行结束之后,m是4
n = n - 1; // 这行代码执行结束之后,n是4
System.out.println(i); // 6
System.out.println(i++); // 6(虽然输出结果是6,但是你别忘了这行代码执行结束之后i就是7了。)
System.out.println(++i); // 8(这行代码执行结束之后,i本身的值变成了8)
System.out.println(i--); // 8(这行代码执行结束之后,i本身的值变成了7)
System.out.println(); // 小括号中什么也没有,空白,表示换行
System.out.println(j);
System.out.println(j++);
System.out.println(j--);
System.out.println(--j);
System.out.println();
System.out.println(m);
System.out.println(n);
}
}
5.8 接收用户键盘输入
/*
1、输出信息到控制台:
System.out.println(...);
2、在java中怎么接收键盘的输入呢?
先声明一下,这个代码看不懂很正常,因为这个代码是面向对象章节学习之后才能够理解。
这个代码以后复制粘贴就行。
前提:java.util.Scanner s = new java.util.Scanner(System.in);
接收一个整数怎么办?
int num = s.nextInt();
接收一个字符串怎么办?
String str = s.next();
*/
public class KeyInput{
public static void main(String[] args){
// 创建一个键盘扫描器对象
// s 变量名,可以修改。其它不能改。
java.util.Scanner s = new java.util.Scanner(System.in); //这行代码写一次就行了。
// 接收用户的输入,从键盘上接收一个int类型的数据
// 解释这行代码,尽量让大家明白:代码执行到这里的时候,会暂停下来
// 等待用户的输入,用户可以从键盘上输入一个整数,然后回车,回车之后
// i变量就有值了。并且i变量中保存的这个值是用户输入的数字。
// i变量就是接收键盘数据的。
int i = s.nextInt(); // i是变量名,s是上面的变量名
System.out.println("您输入的数字是:" + i);
// 代码执行到此处又会停下来,等待用户的输入。
// 敲完回车,s.nextInt();代码执行结束。
int j = s.nextInt();
System.out.println("您输入的数字是:" + j);
// 如果输入的不是数字,那么会出异常:InputMismatchException
int m = s.nextInt();
System.out.println("您输入的数字是:" + m);
// 我怎么从键盘上接收一个字符串呢?
// 程序执行到此处会停下来,等待用户的输入,用户可以输入字符串。
String str = s.next();
System.out.println("您输入了:" + str);
// 完整的。
System.out.print("请输入用户名:");
String name = s.next();
System.out.println("欢迎"+name+"回来");
}
}
import java.util.Scanner;
// 更有交互性
public class KeyInput2{
public static void main(String[] args){
// 创建键盘扫描器对象
Scanner s = new Scanner(System.in);
// 输出一个欢迎信息
System.out.println("欢迎使用小型迷你计算器");
System.out.print("请输入第1个数字:");
int num1 = s.nextInt();
System.out.print("请输入第2个数字:");
int num2 = s.nextInt();
System.out.println(num1 + "+" + num2 + "=" + (num1 + num2));
}
}
5.9 day07课堂笔记
3、运算符
算术运算符:
+ - * / % ++ --
关系运算符:
> >= < <= == !=
逻辑运算符:
& | ! && ||
赋值运算符:
= += -= *= /= %=
三目运算符:
布尔表达式 ? 表达式1 : 表达式2
字符串连接运算符:
+
5.9.1 day07代码
算术运算符
/*
算术运算符:
+ 求和
- 相减
* 乘积
/ 商
% 求余数(求模)
++ 自加1
-- 自减1
对于++运算符来说:
可以出现在变量前,也可以出现在变量后。
不管出现在变量前还是后,总之++执行结束之后,变量的值一定会自加1。
*/
public class OperatorTest01{
public static void main(String[] rags){
int a = 10;
int b = 3;
System.out.println(a + b); // 13
System.out.println(a - b); // 7
System.out.println(a * b); // 30
System.out.println(a / b); // 3
System.out.println(a % b); // 1
// 重点掌握 ++ 和 --
// 这里重点讲解 ++,至于-- 大家可以照葫芦画瓢。
// ++ 自加1(++可以出现在变量前,也可以出现在变量后。)
int i = 10;
// i变量自加1
i++;
System.out.println(i); //11
int k = 10;
// k变量自加1
++k;
System.out.println(k); //11
// 研究:++出现在变量前和变量后有什么区别?
// 先看++出现在变量后。
// 语法:当++出现在变量后,会先做赋值运算,再自加1
int m = 20;
int n = m++;
System.out.println(n); // 20
System.out.println(m); // 21
// ++出现在变量前呢?
// 语法规则:当++出现在变量前的时候,会先进行自加1的运算,然后再赋值。
int x = 100;
int y = ++x;
System.out.println(x); // 101
System.out.println(y); // 101
// 题目
int c = 90;
System.out.println(c++); // 传,这个“传”在这里有一个隐形的赋值运算。90
// 把上面代码拆解开
//int temp = c++;
//System.out.println(temp);
System.out.println(c); // 91
int d = 80;
System.out.println(++d); //81
// 拆解
//int temp2 = ++d;
//System.out.println(temp2);
System.out.println(d); // 81
/*
int e = 1;
int f = e; // e赋值给f,表示将e“传”给了f
*/
// 自己测试以下 -- 运算符。
}
}
关系运算符
/*
关系运算符:
>
>=
<
<=
==
!=
一定要记住一个规则:
所有的关系运算符的运算结果都是布尔类型,
不是true就是false,不可能是其他值。
在java语言中:
= : 赋值运算符
== :关系运算符,判断是否相等。
注意:关系运算符中如果有两个符号的话,两个符号之间不能有空格。
>= 这是对的, > = 这是不对的。
== 这是对的,= = 这是不对的。
*/
public class OperatorTest02{
public static void main(String[] args){
int a = 10;
int b = 10;
System.out.println(a > b); // false
System.out.println(a >= b); // true
System.out.println(a < b); // false
System.out.println(a <= b); // true
System.out.println(a == b); // true
System.out.println(a != b); // false
}
}
逻辑运算符
/*
逻辑运算符:
& 逻辑与(可以翻译成并且)
| 逻辑或(可以翻译成或者)
! 逻辑非(取反)
&& 短路与
|| 短路或
用普通话描述的话:100 大于 99 并且 100 大于 98 ,有道理
用代码描述的话:100 > 99 & 100 > 98 --> true
true & true --> true
非常重要:
逻辑运算符两边要求都是布尔类型,并且最终的运算结果也是布尔类型。
这是逻辑运算符的特点。
100 & true 不行,语法错误。
100 & 200 不行,没有这种语法。
true & false 这样可以。
100 > 90 & 100 > 101 --> false
& 两边都是true,结果才是true
| 有一边是true,结果就是true
*/
public class OperatorTest03{
public static void main(String[] args){
// 对于逻辑与&运算符来说,只要有一边是false,结果就是false。
// 只有两边同时为true,结果才是true。
System.out.println(true & true); // true
System.out.println(true & false); // false
System.out.println(false & false); // false
System.out.println(100 > 90 & 100 > 101); // false
System.out.println((100 > 90) & (100 > 101)); // false
int a = 100;
int b = 101;
int c = 90;
System.out.println(a < b & a > c); // true
// 对于逻辑或呢?
// 只要有一边是true,结果就是true。
System.out.println(a < b | c > b); // true
System.out.println(true | false); // true
System.out.println(true | true); // true
System.out.println(false | false); // false
System.out.println(!false); // true
System.out.println(!true); // false
// 注意:这里需要加一个小括号。
System.out.println(!(a > b)); // true
/*
关于短路与 &&,短路或 ||
其中重点学习短路与,短路或照葫芦画瓢。
短路与&& 和 逻辑与 &有什么区别?
首先这两个运算符的运算结果没有任何区别,完全相同。
只不过“短路与&&”会发生短路现象。
什么是短路现象呢?
右边表达式不执行,这种现象叫做短路现象。
什么时候使用&&,什么时候使用& ?
从效率方面来说,&&比&的效率高一些。
因为逻辑与&不管第一个表达式结果是什么,第二个表达式一定会执行。
以后的开发中,短路与&&和逻辑与还是需要同时并存的。
大部分情况下都建议使用短路与&&
只有当既需要左边表达式执行,又需要右边表达式执行的时候,才会
选择逻辑与&。
*/
System.out.println(true & true); // true
System.out.println(true & false); // false
System.out.println(false & false); // false
System.out.println(true && true); // true
System.out.println(true && false); // false
System.out.println(false && false); // false
// 接下来需要理解一下什么是短路现象,什么时候会发生“短路”。
int x = 10;
int y = 11;
// 逻辑与&什么时候结果为true(两边都是true,结果才是true)
// 左边的 x>y 表达式结果已经是false了,其实整个表达式的结
// 果已经确定是false了,按道理来说右边的表达式不应该执行。
System.out.println(x > y & x > y++);
// 通过这个测试得出:x > y++ 这个表达式执行了。
System.out.println(y); // 12
//测试短路与&&
int m = 10;
int n = 11;
// 使用短路与&&的时候,当左边的表达式为false的时候,右边的表达式不执行
// 这种现象被称为短路。
System.out.println(m > n && m > n++);
System.out.println(n); // 11
// 问题:什么时候发生短路或现象?
// || 短路或
// “或”的时候只要有一边是true,结果就是true。
// 所以,当左边的表达式结果是true的时候,右边的表达式不需要执行,此时会短路。
}
}
赋值运算符
很重要的语法机制:使用扩展赋值运算符的时候,永远都不会改变运算结果类型。
/*
赋值运算符:
1、赋值运算符包括“基本赋值运算符”和“扩展赋值运算符”:基本的、扩展的。
2、基本赋值运算符?
=
3、扩展的赋值运算符?
+=
-=
*=
/=
%=
注意:扩展赋值运算符在编写的时候,两个符号之间不能有空格。
+ = 错误的。
+= 正确的。
4、很重要的语法机制:
使用扩展赋值运算符的时候,永远都不会改变运算结果类型。
byte x = 100;
x += 1;
x自诞生以来是byte类型,那么x变量的类型永远都是byte。不会变。
不管后面是多大的数字。
*/
public class OperatorTest04{
public static void main(String[] args){
// 赋值运算符“=”右边优先级比较高,先执行右边的表达式
// 然后将表达式执行结束的结果放到左边的“盒子”当中。(赋值)
int i = 10;
// 重新赋值
i = 20;
byte b = 10;
b = 20;
/*
以 += 运算符作为代表,学习扩展赋值运算符。
其它的运算符,例如:-= *= /= %= 和 += 原理相似。
*/
int k = 10;
k += 20; // k变量追加20
System.out.println(k); // 30
int m = 10;
// += 运算符类似于下面的表达式
m = m + 20;
System.out.println(m); // 30
// 研究:
// i += 10 和 i = i + 10 真的是完全一样吗?
// 答案:不一样,只能说相似,其实本质上并不是完全相同。
byte x = 100; // 100没有超出byte类型取值范围,可以直接赋值
System.out.println(x); // 100
// 分析:这个代码是否能够编译通过?
// 错误: 不兼容的类型: 从int转换到byte可能会有损失
//x = x + 1; // 编译器检测到x + 1是int类型,int类型可以直接赋值给byte类型的变量x吗?
// 使用扩展赋值运算符可以吗?
// 可以的,所以得出结论:x += 1 和 x = x + 1不一样。
// 其实 x += 1 等同于:x = (byte)(x + 1);
x += 1;
System.out.println(x); // 101
// 早就超出byte的取值范围了。
x += 199; // x = (byte)(x + 199);
System.out.println(x); // 44 (当然会自动损失精度了。)
int y = 100;
y += 100;
System.out.println(y); // 200
y -= 100; // y = y - 100;
System.out.println(y); // 100
y *= 10; // y = y * 10;
System.out.println(y); // 1000
y /= 30; // y = y / 30;
System.out.println(y); // 33
y %= 10; // y = y % 10;
System.out.println(y); // 3
}
}
条件运算符(三目运算符 )
/*
条件运算符:(三目运算符。)
语法格式:
布尔表达式 ? 表达式1 : 表达式2
执行原理是什么?
布尔表达式的结果为true时,表达式1的执行结果作为整个表达式的结果。
布尔表达式的结果为false时,表达式2的执行结果作为整个表达式的结果。
*/
public class OperatorTest05{
public static void main(String[] args){
// 合法的java语句
// 表示声明一个变量,起名i
int i = 100;
// 这里会编译出错吗?
// 错误: 不是语句
// 100;
// 错误: 不是语句
//'男';
boolean sex = true;
// 分析以下代码是否存在语法错误?
// 错误: 不是语句
//sex ? '男' : '女';
// 前面的变量的c的类型不能随意编写。
// 最终的计算结果是字符型,所以变量也需要使用char类型。
char c = sex ? '男' : '女';
System.out.println(c);
// 实际开发中不会这样,故意的
//错误: 不兼容的类型: 条件表达式中的类型错误
//char x = sex ? '男' : "女";
//System.out.println(x);
// 这样可以吗?可以
sex = false;
System.out.println(sex ? '男' : "女");
//System.out.println(这里很牛,因为这里什么类型的数据都能放);
/*
System.out.println(100);
System.out.println("100");
System.out.println('a');
*/
}
}
字符串连接运算符
/*
+ 运算符:
1、+ 运算符在java语言中有两个作用。
作用1:求和
作用2:字符串拼接
2、什么时候求和?什么时候进行字符串的拼接呢?
当 + 运算符两边都是数字类型的时候,求和。
当 + 运算符两边的“任意一边”是字符串类型,那么这个+会进行字符串拼接操作。
3、一定要记住:字符串拼接完之后的结果还是一个字符串。
*/
public class OperatorTest06{
public static void main(String[] args){
// 定义一个年龄的变量。
int nianLing = 35;
// + 在这里会进行字符串的拼接操作。
System.out.println("年龄=" + nianLing); // "年龄=35"
int a = 100;
int b = 200;
// 这里的 + 两边都是数字,所以加法运算
int c = a + b;
System.out.println(c); // 300
// 注意:当一个表达式当中有多个加号的时候
// 遵循“自左向右”的顺序依次执行。(除非额外添加了小括号,小括号的优先级高)
// 第一个+先运算,由于第一个+左右两边都是数字,所以会进行求和。
// 求和之后结果是300,代码就变成了:System.out.println(300 + "110");
// 那么这个时候,由于+的右边是字符串"110",所以此时的+会进行字符串拼接。
System.out.println(a + b + "110"); // 最后一定是一个字符串:"300110"
// 先执行小括号当中的程序:b + "110",这里的+会进行字符串的拼接,
// 拼接之后的结果是:"200110",这个结果是一个字符串类型。
// 代码就变成了:System.out.println(a + "200110");
// 这个时候的+还是进行字符串的拼接。最终结果是:"100200110"
System.out.println(a + (b + "110"));
// 在控制台上输出"100+200=300"
System.out.println("100+200=300"); // 不是这个意思。
// 以下有效的运算符加号一共有4个,这4个加号都是字符串拼接操作。
System.out.println(a + "+" + b + "=" + c);
// 分析这个结果是多少?
// 以下表达式中没有小括号,所以遵循自左向右的顺序依次执行。
// 第1,2,3,4个加号都是进行字符串拼接,拼接之后的结果是:"100+200=100"
// 前4个加号运行之后是一个字符串"100+200=100"
// 然后这个字符串再和最后一个b变量进行字符串的拼接:"100+200=100200"
System.out.println(a + "+" + b + "=" + a + b);
// 和上面程序不一样的地方是:最后一个加号先执行,并且会先进行求和运算。
System.out.println(a + "+" + b + "=" + (a + b));
// 在java语言中怎么定义字符串类型的变量呢?
// int是整数型 i 是变量名 10是字面量
//int i = 10;
// String是字符串类型,并且String类型不属于基本数据类型范畴,属于引用类型。
// name是变量名,只要是合法的标识符就行。
// "jack" 是一个字符串型字面量。
String name = "李四"; // String类型是字符串类型,其中S是大写,不是:string
// 错误:类型不兼容。
//String name = 100;
// 会进行字符串的拼接
//System.out.println("登录成功欢迎" + name + "回来");
// 口诀:加一个双引号"",然后双引号之间加两个加号:"++",然后两个加号中间加变量名:"+name+"
System.out.println("登录成功欢迎"+name+"回来");
System.out.println(a + "+" + b + "=" + c);
}
}
6 第六章 控制语句
6.1 章节目标与知识框架
6.1.1 章节目标
记住所有控制语句的语法格式,理解不同的控制语句分别在哪些不同的情况下使用,并且能够熟练编写这些控制语句。
6.1.2 知识框架
6.2 控制语句概述(理解)
在大部分编程语言当中都会存在控制语句,控制语句是一个程序的灵魂,我们只依靠“标识符、关键字、变量、运算符”等零散的知识点是无法进行流程控制的,无法实现一个具体的功能或业务,所以控制语句这一章节非常重要。
现实生活中哪些业务需要使用控制语句呢?比如说“小孩身高如果高于1.2米,则乘坐交通工具就需要收费了,反之则免费”,再比如“A账户向B账户转账10000元,首先需要判断A账户的余额是否大于等于10000元,如果余额充足则可以转账,不足则无法转账”等等,这些业务当中都需要使用控制语句进行控制才能完成。
什么是控制语句,官方的解释是这样的:控制语句即用来实现对程序流程的选择、循环、转向和返回等进行控制。Java语言中共有8种控制语句,可以分为“选择语句”,“循环语句”,“转向语句”,“返回语句”四类。那么,这8种控制语句分别是什么呢?if语句、switch语句、for循环、while循环、do..while循环、break语句、continue语句、return语句。其中if和switch语句属于选择语句,for、while、do..while语句属于循环语句,break和continue语句属于转向语句,return属于返回语句。接下来让我们认真的学习每一个语句。
6.3 选择语句
选择语句又称为分支语句,它通过对给定的条件进行判断,从而决定执行两个或多个分支中的哪一支。因此,在编写选择语句之前,应该首先明确判断条件是什么,并确定当判断结果为“真”或“假”时应分别执行什么样的操作/算法。在Java语言中选择语句主要提供了两个,一个是if语句,另一个则是switch语句。
6.3.1 if(掌握)
我们先来看 if 语句,if 语句的编写方式归纳为以下四种,请看下图:
if语句是非常简单容易理解的,if被翻译为如果,else被翻译为其它,我们针对以上第二种写法进行说明:如果布尔表达式的结果是“真”,则执行它后面的分支,反之则执行else对应的分支。好比说“如果外面下雨了(真),出门则拿一把雨伞,反之则不带雨伞”。再比如“如果你吃饱了(真)就不需要再吃了,反之则需要继续吃东西”。那么以上两个业务用代码应该如何实现呢?请看下面程序:
上图中第一种写法对应的原理图:
上图中第二种写法对应的原理图:
上图中第三种写法对应的原理图:
上图中第四种写法对应的原理图:
通过以上原理的分析,我们可以得出if语句的任何一种形式在执行的时候只要有一个分支执行,则所对应的整个if语句就全部结束了,换句话说,对于一个独立的完整的if语句来说,最多只能有一个分支执行。
另外,我们可以得出“图6-1”中的四种写法,其中第二种和第四种都带有else分支,所以这两种写法我们可以确保肯定会有一个分支执行,另外两种写法则无法保证,也就是说第一种和第三种这两种写法可能一个分支都不会执行,这两种写法的分支是否执行取决于条件是否为true。
还有,Java语言中有这样一条规定:当分支中只有一条java语句话,大括号可以省略不写,如以下程序:
虽然大括号可以省略,但是大家编写程序的时候一定要小心,分析以下程序错在哪里?
显然if是没有任何问题的,输出“男”也是没问题的,输出“helloworld!”也是正常的,只不过负责输出“helloworld!”的那一行代码不在if语句的分支当中,那么下面的else则缺少了if语句而编译报错。换句话说,else以上的代码都没有问题,错误出现在else缺少了if。
虽然java规定当分支中只有一条java语句的话大括号可以省略,但是为了程序具有很强的可读性,所以建议在实际开发中还是不要省略大括号会比较好。
那么,接下来我们给大家列举几个if语句的案例,帮助大家更好的理解和运用if语句。
示例代码1:业务背景:根据用户输入不同的数字来判断用户不同性别,当输入1时性别为男,当输入0时性别为女,输入其它则不合法。
示例代码2:业务背景:键盘接收一个学生的成绩,范围在[0-100],根据成绩判断等级,[90-100]等级为“优秀”,[80-90)等级为“良好”,[70-80)等级为“中等”,[60-70)等级为“及格”,[0-60)等级为“大笨蛋”,输入其它数字表示成绩不合法。
对于初学者来说以上程序是可以的,但是对于老程序员来说,以上的代码就太冗余了,我们将其进行优化,看以下代码:
通过以上程序我们可以了解到一个功能的实现代码有多种编写形式,大家在学习的时候千万别机械性的一行一行对抄代码,实际上这种学习方法是非常痛苦,并且低效的,软件开发主要培养的是编程思想/编程思路,使用编程的思路解决现实生活中的问题。当然,对于初学者来说我们不可能刚开始就写出简单而高效的程序,这需要不断的模仿,不断的积累,尤其我们可以多参考高手写的程序(例如:JDK源代码),当你的代码达到一定量的时候,你就可以出山了。
示例代码3:业务背景:系统首先接收天气参数,1表示下雨天,0表示晴天,如果天气是晴天,输出“走起,一起出去玩耍喽!”,当天气是下雨天,则继续接收性别参数,1表示男,0表示女,当性别为女,则拿一把小花伞出去玩,当性别为男,则拿一把大黑伞出去玩。
运行结果如下图所示:
通过以上的程序我们得知,if语句是可以进行嵌套使用的,也就是说if语句的某个分支当中还可以继续编写其它if语句。实际上所有的控制语句都是可以进行嵌套使用的。语句嵌套这个知识点本来是不需要拿来进行特殊强调的,专门强调之后反而会让初学者特殊对待,我希望大家不要特殊对待嵌套。虽然被嵌套的是一个控制语句,但这个控制语句也是一段普通的java程序,你说呢!如下图:
总之,选择语句之if语句,在实际开发中使用频率非常高,大家一定要掌握if语句的编写方式以及运行原理,也可以自己想一些现实生活中的小例子加以练习。
6.3.2 switch(掌握)
switch语句和if语句一样,都属于选择语句(分支语句),不再赘述,我们直接来看一下一个比较完整的switch语句结构是怎样的,请看下图:
switch运行原理是怎样的呢,请看下图:
switch运行原理文字性描述是这样的(以上图为例):拿着c和表达式1进行比较,如果相等,则执行语句1,如果不相等,继续拿着c和表达式2进行比较,如果相等,则执行语句2,如果不相等,继续...,如果c和所有的分支表达式都不相等,此时default分支“语句n+1”则执行。
以上描述是switch语句的一般性执行流程,实际上在执行过程中还有很多因素的影响,接下来我将列出使用switch语句的一些注意事项:
①switch语句后面的小括号“()”当中都可以出现什么,在JDK1.6之前(包括1.6在内)switch只能探测int类型的数据,但是在JDK1.7之后(包括1.7在内),switch语句除了支持int类型之外,还支持String类型。也就是说从Java7开始switch后面小括号中可以出现字符串。
②switch虽然只能探测int类型,但是也可以将byte,short,char类型放到小括号当中,因为这些类型会自动转换成int类型(小容量向大容量转换称为自动类型转换)。
③switch语句当中case是可以进行合并的,例如以下代码:
④switch语句当中当某个分支匹配成功,则开始执行此分支当中的java语句,当遇到当前分支中的“break;”语句,switch语句就结束了,但是如果当前分支中没有“break;”语句,则会发生case穿透现象,也就是说下一个分支也不再进行匹配,直接进入下一个分支执行,直到遇到“break;”为止。例如以下代码:
执行结果如下所示:
接下来我们来看几个switch的经典案例,请看代码:
示例代码1:从键盘接收一个数字[1-7],当数字是1~5的时候,控制台输出工作日,当数字是6~7的时候,控制台输出休息日,其他数字提示错误信息。
示例代码2:业务背景:键盘接收一个学生的成绩(假设成绩是合法的),范围在[0-100],根据成绩判断等级,[90-100]等级为“优秀”,[80-90)等级为“良好”,[70-80)等级为“中等”,[60-70)等级为“及格”,[0-60)等级为“大笨蛋”。只允许使用switch,不允许使用if语句。
运行结果如下图所示:
以上程序在编写时候的核心技巧是:[90-100]分的所有数字,包括浮点型数字,让其除以10之后强转为int类型,结果一定是9或者10。[80-90)分的所有数字,除以10之后取整,结果一定是8,以此类推。没关系同学们,大家在起初学习的时候不一定会马上写出来以上程序,这个过程是需要积累的,下去之后先自己模仿我的程序,慢慢的你就会写了。
if和switch都是条件判断语句(分支语句/选择语句),在效果上是没有什么差别的。而不同,我们可以从效率上来看一看,举一个很简单的例子:if(a>b){a=b;}else{b=a;}如果将这个例子放在switch语句中,也并不是不能实现,但是实现起来可能会比较麻烦一些,因为在switch语句中case要求的是常量,一般是不能进行逻辑判断的,所以这也是if语句优于switch语句的地方。但是如果您判断的都是几个常量的数据,我建议您最好采用switch语句,虽然if语句也是能实现的,但是性能就比switch差的有点远了。
6.4 循环语句
在不少实际问题中有许多具有规律性的重复操作,因此在程序中就需要重复执行某些语句。一组被重复执行的语句称之为循环体,能否继续重复,取决于循环的终止条件。循环结构是在一定条件下反复执行某段程序的流程结构,被反复执行的程序被称为循环体。循环语句是由循环体及循环的终止条件两部分组成的。
为了帮助大家理解循环语句存在的意义,我们来看一段代码:
以上程序的业务背景是:输出100行“helloworld!”,如果我们像以上方式进行代码编写的话,代码将无法得到重复使用,大家也可以看到“System.out.println("helloworld!")”这行代码重复出现,直到输出100个为止。显然程序应该找一种更好的实现方式,这个时候我们就可以借助java语言中的循环语句来实现了。java中的循环语句共包括三个,分别是:for循环、while循环、do..while循环。
6.4.1 for(掌握)
接下来我们先来学习for循环。for循环的语法格式如下图所示:
对以上for循环语法结构进行说明:初始化表达式最先执行,并且在整个循环当中初始化表达式只执行一次,布尔表达式结果是true或者false,true表示循环可以继续执行,false表示循环结束,循环体当中的代码就是需要反复执行的代码,更新表达式一般是用来更新某个变量的值,更新操作之后可能布尔表达式的结果就不再是true了。那么for循环的执行顺序以及原理如下图所示:
对于for循环来说,初始化表达式、布尔表达式、更新表达式都不是必须的,当布尔表达式缺失的时候,没有条件控制的前提下,会形成死循环,我们来看一下最简单的for循环形式:
执行结果如下图所示:
最常见的for循环是怎样的呢?看以下代码:
运行结果如下图所示:
对以上代码进行分析:首先执行inti=1,并且只执行一次,定义初始化变量i,赋值1,然后判断i<=10结果为true,则执行循环体打印i=1,循环体结束之后,执行i++,然后i变成了2,继续判断i<=10结果为true,则执行循环体打印i=2,如此循环执行,直到打印i=10之后,再执行i++让i变成了11,然后判断i<=10结果为false,循环结束,这样就完成了1~10的输出,当然程序不是固定的,大家也可以把条件i<=10修改为i<11,这样也是可以的。
关于for循环的使用我们还需要注意初始化变量的作用域,在for循环当中声明的变量只在for循环中有效,当for循环结束之后,初始化变量的内存就释放了/消失了,所以在for循环之外是无法访问该变量的,例如以下代码在编译的时候就会报错了:
当然,这样写就不会报错了:
为什么这样写就可以了呢?那是因为i变量的作用域已经被扩大了,不再只是for循环中可以使用,for循环之外也可以使用,换句话说,以上的for循环结束之后i变量的内存并不会被释放。后续的程序可以继续使用。i变量的作用域是在整个test()方法中都有效,直到test()方法执行结束的时候才会释放i变量的内存空间。
接下来我们再来看看for循环还有哪些其它的写法:
以上程序运行结果,请看下图:
接下来,我们再使用for循环实现1~100所有数字的求和,实现思路是:定义变量i,初始值从1开始,每循环一次加1,这样就可以取到1~100的每一个整数了,那么求和怎么做呢?求和就是将1~100的每一个整数累加,这必然需要提前定义一个变量,使用变量实现累加,例如:a+=1,a+=2,a+=3...,代码如下所示:
运行结果如下所示:
通过以上程序我们可以学到什么?编程语言当中的累加可以使用扩展类赋值运算符+=来实现,另外sum变量为什么定义到for循环外面,而不是定义到循环体当中呢?那是因为当定义到循环体内之后,每一次循环体在执行的时候,都会重新定义sum变量,这样会让sum变量归0,无法达到累加的效果。
接下来,我们在以上程序的基础之上实现1~100所有奇数的和,编程思路是:在累加之前先判断变量i是否为奇数,如果是奇数则累加,这就需要在sum+=i外面套一个if语句进行控制,代码如下所示:
运行结果如下所示:
其实以上的方式是将每一个数字取出来,然后再判断是否为奇数,这种方式会导致循环次数达到100次,实际上可以换成另外一种解决方案,假设从1开始,每次累加2,那么每次取出的数字为1,3,5...,这样正好每次取出的数字都是奇数,可以直接累加了,这样循环的次数基本上会减半,效率则提高了,这种代码既优雅,又高效。请看以下代码:
运行结果如下所示:
以上演示的所有循环都是单层的,循环当中可以嵌套循环吗?答案是:当然可以,之前我们就说过所有控制语句都是可以嵌套使用的,当循环A中嵌套循环B的时候就等于在A循环体中的代码是B循环。其实大家在学习循环嵌套的时候完全没必要特殊化对待,完全可以把A循环体当中的B循环看做是一段普通的java代码。接下来我们看一段代码:
运行结果如下图所示:
分析以上for循环嵌套,请看下图:
分析循环嵌套的小窍门,如下图所示:
学习了for循环嵌套的使用,我们一起来写一下经典的九九乘法表,九九乘法表的结构大家还记得吗,我们一起来回顾一下小学的知识(嘿嘿):
通过观察九九乘法表结构我们可以看出来它有9行,所以可以得出一定需要以下代码:
运行结果如下:
观察上图我们可以得出i是行号,那么再次观察九九乘法表的规律得知,第1行1个式子,第2行2个式子,第3行3个式子...,第9行9个式子,那么结论是第i行有i个式子,以上循环确定为外层循环,共循环9次,外层循环每循环一次要保证能够输出九九乘法表的1整行。那么输出九九乘法表1整行的时候又需要一个循环进行控制,而且这个循环被放到内部,循环的次数随着行号的变化而变化。代码如下所示:
运行结果如下所示:
分析以上代码,请看下图:
接下来我们在每一个“结果”前添加“i*j=”,代码如下所示:
运行结果如下所示:
通过以上代码的学习,需要每位读者能够掌握什么是循环,为什么要使用循环,for循环的语法结构是什么,for循环的执行顺序以及原理是什么,嵌套的for循环又应该怎么理解,大家也可以挑战一下三层for循环嵌套,或者更多。总之for循环在实际开发中使用非常频繁,大家务必掌握。
6.4.2 while(掌握)
循环语句除了for循环之外还有while和do..while,接下来我们先来看一下while循环,首先学习while循环的语法结构,如下图所示:
通过上图可以看出while循环的语法结构非常简单,它的执行顺序以及原理是这样的,先判断布尔表达式的结果,如果是true,则执行循环体,循环体结束之后,再次判断布尔表达式的结果,如果是true,再执行循环体,循环体结束之后,再判断布尔表达式的结果,直到结果为false的时候,while循环结束。如果起初第一次判断布尔表达式结果的时候就是false,那么while循环体执行次数就为0了。它的执行顺序以及原理也可以参见下图:
通过while循环的执行原理可以得出while循环的循环体执行次数可能是0次,也可能是N次。那么要想使用while循环实现一个死循环,代码应该怎么写呢?
运行结果就不再给大家展示了,要使用while实现一个死循环是非常简单的,让判断条件一直为true就可以了。那么使用while循环实现输出1~10应该怎么做呢?
运行结果如下图所示:
其实使用while循环输出1~10不止有以上这一种方式,还有其他方式,例如:
运行结果如下图所示:
当然,大家还可以想想有没有其它的写法,可以自己写一写,例如:
再如:
使用while循环计算1~100所有偶数的和,应该怎么做呢?
运行结果如下图所示:
实际上while循环可以看做是for循环的另一种变形写法,本质上是一样的,执行效率上也是一样的,硬要说它们有什么不同的话,首先while循环语法结构比for更简单,for循环的计数器比while更清楚一些,另外for循环的计数器对应的变量可以在for循环结束之后就释放掉,但是while循环的计数器对应的变量声明在while循环外面,扩大了该变量的作用域。总之,不管是for还是while,大家都必须掌握,因为这两个循环使用最多。
6.4.3 do…while(掌握)
do..while循环是while循环的变形,它们的区别在于do..while循环可以保证循环体执行次数至少为1次,也就是说do..while循环的循环体执行次数是1~N次,它有点儿先斩后奏的意思,而while循环的循环体执行次数为0~N次。
为什么do..while循环可以保证至少执行一次呢,它和while循环的区别在哪里呢?实际上是因为在开始执行while循环的时候,最先执行的是条件判断,只有条件为true的时候才会执行循环体,既然是这样,那么条件就有可能为false,这个时候就会导致循环体执行次数为0次,俗话说,还没开始就结束了。而do..while循环最先执行的不是条件判断,它会先执行循环体,然后再进行条件判断,这样就可以保证循环体至少执行一次喽!
接下来我们一起来看看do..while循环的语法结构,以及执行顺序,如下图所示:
或者参见下图:
上图中清晰的描述了do..while循环执行顺序,这里就不再赘述,但需要注意的是do..while循环在最后的时候有一个半角的分号“;”,这个不能丢,丢掉之后编译器就报错了。接下来我们看一个do..while循环的典型案例。
示例代码:业务背景:我们通常在使用的一个系统的时候需要登录,假设用户名或者密码记不清楚了,你是不是需要不断的“反复的”输入用户名和密码,这就是一个非常典型的循环案例,而这个循环当中首先要做的第一件事儿不是判断用户名和密码是否正确,它的起点是先让用户输入用户名和密码,所以这个时候我们就需要使用do..while循环来完成。请看以下代码:
运行效果如下图所示:
解释以上程序:先提示用户输入用户名和密码,然后判断用户名和密码,当用户名不是admin或者密码不是123的时候继续提示用户输入用户名和密码,直到用户输入的用户名是admin并且密码是123的时候循环结束,循环结束之后输出登录成功的信息,只要循环没有结束就表示用户名和密码还是不对,当然,在现实的系统当中我们不可能允许用户无限制的输入用户名和密码,通常会给几次输入机会,当机会用完之后还是没有登录成功,通常该账户就被锁定了,你不妨试试这种业务又应该如何完成。
总之while和do..while循环的区别就在于do..while会先执行一次循环体,然后再判断条件,这样do..while的循环体至少执行一次。而while循环则是先判断条件是否合法,如果不合法,则循环体就没有任何执行的机会。while循环体执行次数是0~N次,但是do..while循环体执行次数则是1~N次。
6.5 转向语句
转向语句用于实现循环执行过程中程序流程的跳转,在Java中转向语句有break和continue语句。当然,还包括其它的,例如return语句,这里主要给大家说一下break和continue语句。
6.5.1 break(掌握)
使用break这一个单词就可以在java语言中自成一条java语句,break语句的编写很简单,例如“break;”,那么它可以用在哪里呢?首先它可以使用在switch语句当中,用来终止switch语句的执行,这个之前我们用过,这里不再赘述,break语句重点是使用在循环语句当中,用来终止/跳出循环。例如有这样一个业务:从键盘不断的接收用户输入的整数,只要用户输入的数字在[0~100]之间,则将输入的数字累加,一旦用户输入的整数不在[0-100]的范围,则终止循环的执行,并输出计算结果。请看下面的代码:
运行效果如下图所示:
通过以上程序我们得知当用户输入的数字不在[0-100]范围内的时候break语句执行,while循环结束了。那么,当循环语句多层嵌套的时候break语句终止的是哪个循环呢?我们来看以下的程序:
运行结果如下所示:
分析以上程序,请看下图:
通过上图的分析,可以得知break语句默认情况下只能终止离它“最近”的“一层”循环。以上的break语句则终止的是内部循环,不影响外部循环的执行。那么break语句可以用来终止指定的循环吗?请看以下代码:
运行结果如下图所示:
通过以上程序的测试,我们可以得知当多层循环嵌套的时候,可以给每个循环设置标识,例如:first:for...、second:for...,当某个条件成立时,想终止指定循环的话,可以这样做:breakfirst;或者breaksecond;,这样指定的循环就结束了。
总之,break语句出现在循环当中用来终止循环的执行。例如:运动场上的运动员跑圈儿,跑了一圈又一圈,这显然是循环机制,假设比赛要求跑10圈儿,或者20圈儿,那么当计数器等于10,或者20的时候,循环就结束了,如果中途发生意外呢,例如运动员晕倒了,那么此时在没有达到10圈儿或20圈儿的时候是不是也应该终止此循环的执行,要想让循环结束则执行break语句就可以了。
6.5.2 continue(掌握)
continue语句也是单个单词自成一条java语句,例如“continue;”,它和break语句都是用来控制循环的,break语句是用来终止循环的执行,而continue语句则是用来终止当前本次循环,直接进入下一次循环继续执行。请参照以下代码以及运行结果进行对比学习:
运行结果如下图所示:
对以上代码以及执行结果进行分析,请看下图:
对于以上程序,当“continue;”语句执行的时候,当前本次循环剩下的代码就不再执行了(不再执行下面的输出语句),直接执行“i++”去了,而break就不同了,当以上程序“break;”语句执行之后整个for循环就结束了。
对于break语句有“breakfirst;”这种写法,其实continue语句也是有这种语法机制的,这里就不再赘述了,大家可以自己编写程序测试一下。
对于break和continue语句的区别有这样一个小的现实情景,大家可以借鉴理解一下:某公司销售部销售经理要进行人员招聘,预约了10个应聘者,这10个应聘者在办公室门口长凳上按照一定的次序坐着等待,销售经理一个一个轮流进行面试,当轮到第3个应聘者面试的时候,销售经理突然接到了一通电话,说家里有事儿了,那么此时销售经理就不得不终止今天的面试,此时销售经理执行了break语句,循环结束了,剩下的就不再面试了。那么假设销售经理没有接到这通电话则会继续轮流面试,他为每一个应聘者准备了5个问题,假设轮到第6个应聘者面试,在面试过程中问完第1个问题之后销售经理就知道这人不适合这个岗位,那么此时剩下的4个问题就不再问了(当前本次循环结束),直接对着门口喊了一句:下一个应聘的进来。这个过程就相当于销售经理执行了continue语句。
总之,break用来终止循环,continue用来中断当前本次循环,直接进入下一次循环继续执行。
6.6 章节小结
本章节内容在以后的开发中会经常的使用,因为软件很多时候都需要处理业务逻辑,那么处理逻辑的过程是需要控制语句来完成的,所以本章节内容非常重要。
本章节中所讲到的控制语句都很常用,如果要说哪些使用频率最高,其中if语句、for循环、while循环使用最为频繁。对于这几个语句大家可以着重掌握。
6.7 难点解惑
本章节内容虽然很重要,但是每一个都不是很复杂,比较容易掌握,如果难点,对于初学者来说switch语句也不是那么容易掌握的。请看以下代码:
我们来分析一下以上程序,首先语法上没有错误,可以编译通过,有人可能认为default还可以写在这个位置?是的,default可以写在任何位置,但它的执行时机是不变的,永远都是在所有分支都没有匹配成功的时候才会执行,对于以上程序来说default分支是不会执行的,因为y最初等于3,与第一个分支会匹配成功。当第一个分支匹配成功之后,执行y++,此时y等于4。由于没有break语句,会发生case穿透现象,继续执行第二个分支y++,y的值最终是5。
我们对以上程序进行编译和运行,来看一下我们的分析是否是正确的,请看下图:
通过以上的测试结果,可以看出,我们的分析是正确的。
6.8 章节习题
第一题:编写java程序,用循环结构打印如下的数值列表:
1 10 100 1000
2 20 200 2000
3 30 300 3000
4 40 400 4000
5 50 500 5000
第二题:打印2到10000的所有素数,每行显示8个素数。
第三题:编写程序,计算5的阶乘。
第四题:控制台输入年龄,根据年龄输出不同的提示。
第五题:编写程序输出下图菱形。
第六题:篮球从5米高的地方掉下来,每次弹起的高度是原来的30%,经过几次弹起,篮球的高度是0.1米。
6.9 习题答案
第一题答案:
第二题答案:
第三题答案:
第四题答案:
第五题答案:
第六题答案:
6.10 day08课堂笔记
1、怎么接收用户键盘输入?
java.util.Scanner s = new java.util.Scanner(System.in);
// 接收整数
int i = s.nextInt()
// 接收字符串
String str = s.next();
2、控制语句
2.1、控制语句的出现可以让我们的程序具有逻辑性/条理性,可以使用控制语句
来实现一个“业务”了。
2.2、控制语句包括几类?
3类:
选择语句
循环语句
转向语句
2.3、选择语句也可以叫做分支语句(或者条件语句)
if语句
switch语句
2.4、循环语句:主要循环反复的去执行某段特定的代码块
for循环
while循环
do..while..循环
2.5、转向语句
break
continue
return(这个目前先不需要学习,后面讲方法的时候会使用。)
3、选择语句/分支语句if
4、选择语句switch
6.10.1 day08代码
if语句
/*
if语句的语法结构以及运行原理?
if语句是分支语句,也可以叫做条件语句。
if语句的语法格式:
第一种写法:
int a = 100;
int b = 200;
if(布尔表达式){
java语句;
java语句;
}
这里的一个大括号{} 叫做一个分支。
if 这个单词翻译为如果,所以又叫做条件语句。
该语法的执行原理是:
如果布尔表达式的结果是true,则执行大括
号中的程序,否则大括号中代码不执行。
第二种写法:
if(布尔表达式){ // 分支1
java语句;
}else{ // 分支2
java语句;
}
执行原理:如果布尔表达式的结果是true,则执行
分支1,分支2不执行。如果布尔表达式的结果是false,
分支1不执行,执行分支2.
以上的这个语句可以保证一定会有一个分支执行。
else表示其它。
第三种写法:
if(布尔表达式1){ // 分支1
java语句;
}else if(布尔表达式2){ // 分支2
java语句;
}else if(布尔表达式3){
java语句;
}else if(布尔表达式4){
java语句;
}....
以上if语句的执行原理?
先判断“布尔表达式1”,如果“布尔表达式1”为true,则执行分支1,
然后if语句结束了。
当“布尔表达式1”结果是false,那么会继续判断布尔表达式2的结果,
如果布尔表达式2的结果是true,则执行分支2,然后整个if就结束了。
从上往下依次判断,主要看第一个true发生在哪个分支上。
第一个true对应的分支执行,只要一个分支执行,整个if结束。
第四种写法:
if(布尔表达式1){ // 分支1
java语句;
}else if(布尔表达式2){ // 分支2
java语句;
}else if(布尔表达式3){
java语句;
}else if(布尔表达式4){
java语句;
}else{
java语句; // 以上条件没有一个成立的。这个else就执行了。
}
注意:
1、对于if语句来说,在任何情况下只能有1个分支执行,不可能
存在2个或者更多个分支执行。if语句中只要有1个分支执行了,
整个if语句就结束了。(对于1个完整的if语句来说的。)
2、以上4种语法机制中,凡是带有else分支的,一定可以保证会有
一个分支执行。以上4种当中,第一种和第三种没有else分支,这样
的语句可能会导致最后一个分支都不执行。第二种和第四种肯定会有
1个分支执行。
3、当分支当中“java语句;”只有1条,那么大括号{}可以省略,但为了
可读性,最好不要省略。(有的程序员在编写代码的时候,可能会故意
将大括号{}省略,你能看懂就行。)
4、控制语句和控制语句之间是可以嵌套的,但是嵌套的时候大家最好
一个语句一个语句进行分析,不要冗杂在一起分析。
if(true){
//窍门:分析外面if语句的时候,里面的这个if语句可以看做是普通的一堆java代码。
if(true){
if(false){
}else{
....最终走这里了。
}
}else{
}
}else{
}
if(){
// 窍门:分析外面if时,里面的for循环当做普通java代码来看。
for(){
if(){
for(){
}
}
}
}else{
while(){
if(){
for(){
}
}
}
}
*/
public class IfTest01{
public static void main(String[] args){
// 定义一个布尔类型的变量,表示性别。
//boolean sex = true;
boolean sex = false;
//业务:当sex为true时表示男,为false时表示女。
/*
if(sex == true){ // == 是关系运算符,不是赋值运算符,== 双等号是用来判断是否相等的。
System.out.println("男");
}else{
System.out.println("女");
}
*/
// 改良。
sex = true;
if(sex){
System.out.println("男");
}else{
System.out.println("女");
}
// 可以再进一步改良
// 可以使用三目运算符
sex = false;
System.out.println(sex ? "男" : "女");
// 代码可以这样写吗?
// ()小括号当中最终取的值是sex变量的值。
// 而sex是布尔类型,所以这个可以通过。
sex = false;
if(sex = true){ // 以前sex不管是true还是false,走到这一行sex一定是true。
System.out.println("男"); // 输出"男"
}else{
// 虽然这种语法可以,但是会导致else分支永远不能执行。
System.out.println("女");
}
int i = 100;
if(i == 100){
System.out.println("i是100");
}
/*
//错误: 不兼容的类型: int无法转换为boolean
if(i = 100){ // (i = 100)整体执行完之后是一个int类型,不是布尔类型。
System.out.println("i是100");
}
*/
// 当分支中只有一条java语句的话,大括号可以省略。
if(sex)
System.out.println("男");
else
System.out.println("女");
// 判断以下程序会出现问题吗?会出什么问题?第几行代码报错????
if(sex)
System.out.println("男");
System.out.println("HelloWorld!"); // 以上的这3行代码没有问题,合乎语法。
/*
else // 这一行编译报错,因为else缺少if
System.out.println("女");
*/
}
}
/*
业务要求:
1、从键盘上接收一个人的年龄。
2、年龄要求为[0-150],其它值表示非法,需要提示非法信息。
3、根据人的年龄来动态的判断这个人属于生命的哪个阶段?
[0-5] 婴幼儿
[6-10] 少儿
[11-18] 少年
[19-35] 青年
[36-55] 中年
[56-150] 老年
4、请使用if语句完成以上的业务逻辑。
*/
public class IfTest02{
public static void main(String[] args){
java.util.Scanner s = new java.util.Scanner(System.in);
System.out.print("请输入年龄:");
int age = s.nextInt();
//System.out.println("测试一下,您输入的年龄是:" + age);
/*
if(age < 0 || age > 150){
System.out.println("对不起,年龄值不合法");
} else {
// 能够走到这个分支当中,说明年龄是合法的。
// 可以进一步使用嵌套的if语句进行判断。
//if(age >= 0 && age <= 5){}
// 当前先使用if嵌套的方式,当然,嵌套不是必须的。可以有其它写法。
//System.out.println("年龄值合法");
// 年龄值合法的情况下,继续判断年龄属于哪个阶段的!!!!
//if(age >= 0 && age <= 5){} // 这样写代码比较啰嗦了。
if(age <= 5){
System.out.println("婴幼儿");
} else if(age <= 10){
System.out.println("少儿");
} else if(age <= 18){
System.out.println("少年");
} else if(age <= 35){
System.out.println("青年");
} else if(age <= 55){
System.out.println("中年");
} else {
System.out.println("老年");
}
}
*/
// 可以不嵌套吗?可以
/*
if(age < 0 || age > 150){
System.out.println("对不起,年龄值不合法");
} else if(age <= 5){
System.out.println("婴幼儿");
} else if(age <= 10){
System.out.println("少儿");
} else if(age <= 18){
System.out.println("少年");
} else if(age <= 35){
System.out.println("青年");
} else if(age <= 55){
System.out.println("中年");
} else {
System.out.println("老年");
}
*/
// 进一步改良
String str = "老年"; // 字符串变量默认值是“老年”
if(age < 0 || age > 150){
System.out.println("对不起,年龄值不合法");
// 既然不合法,你就别让程序往下继续执行了,怎么终止程序执行
//return;
} else if(age <= 5){
str = "婴幼儿";
} else if(age <= 10){
str = "少儿";
} else if(age <= 18){
str = "少年";
} else if(age <= 35){
str = "青年";
} else if(age <= 55){
str = "中年";
}
System.out.println(str);
// 对于初学者来说可能代码会写成这样,这是正常的。
// 代码的经验需要一步一步的积累,慢慢的代码就会越来越漂亮了。
// 需要时间,需要积累代码经验。最好的代码是:最少的代码量,最高的效率。
/*
if(age >= 0 && age <= 5){
}else if(age >= 6 && age <= 10){
}else if.....
*/
}
}
/*
题目:
1、系统接收一个学生的考试成绩,根据考试成绩输出成绩的等级。
2、等级:
优:[90~100]
良:[80~90)
中:[70-80)
及格:[60~70)
不及格:[0-60)
3、要求成绩是一个合法的数字,成绩必须在[0-100]之间,成绩可能带有小数。
*/
public class IfTest03{
public static void main(String[] args){
// 键盘扫描器对象
java.util.Scanner s = new java.util.Scanner(System.in);
System.out.print("请输入您的考试成绩:");
// 考试成绩带有小数
double score = s.nextDouble(); //程序到这里停下了,等待用户的输入。
// 判断考试成绩
String str = "优";
if(score < 0 || score > 100){
str = "成绩不合法!!!";
}else if(score < 60){
str = "不及格";
}else if(score < 70){
str = "及格";
}else if(score < 80){
str = "中";
}else if(score < 90){
str = "良";
}
System.out.println(str);
}
}
/*
题目:
业务:
从键盘上接收天气的信息:
1表示:雨天
0表示:晴天
同时从键盘上接收性别的信息:
1表示:男
0表示:女
业务要求:
当天气是雨天的时候:
男的:打一把大黑伞
女的:打一把小花伞
当天气是晴天的时候:
男的:直接走起,出去玩耍
女的:擦点防晒霜,出去玩耍
需要使用if语句以及嵌套的方式展现这个业务。
可以在程序的开始,接收两个数据,一个数据是天气,一个数据是性别。
然后将这两个数据保存到变量中。
*/
public class IfTest04{
public static void main(String[] args){
// 接收用户键盘输入
java.util.Scanner s = new java.util.Scanner(System.in);
// 提示信息
System.out.print("请输入您的性别,输入1表示男,输入0表示女:");
// 程序停下来等待用户的输入
int gender = s.nextInt();
//System.out.println(gender);
// 提示信息
System.out.print("请输入当前的天气,1表示雨天,0表示晴天:");
// 等待用户的输入
int weather = s.nextInt();
// 开发要不断的进行测试,不要期望一次把程序写好。
//System.out.println(weather);
if(weather == 1){
//System.out.println("雨天");
if(gender == 1){
// 男
System.out.println("下雨了,小哥哥,出门的时候记得拿一把大黑伞哦!");
}else if(gender == 0){
// 女
System.out.println("下雨了,小姐姐,出门的时候记得带一把小花伞哦!");
}
}else if(weather == 0){
//System.out.println("晴天");
if(gender == 1){
// 男
System.out.println("外面的天气不错,老铁们出去玩耍吧!");
}else if(gender == 0){
// 女
System.out.println("外面的天气晴朗,小姐姐要保护好皮肤哦,擦点防晒霜!");
}
}
}
}
switch语句
/*
switch语句:
1、switch语句也是选择语句,也可以叫做分支语句。
2、switch语句的语法格式
switch(值){
case 值1:
java语句;
java语句;...
break;
case 值2:
java语句;
java语句;...
break;
case 值3:
java语句;
java语句;...
break;
default:
java语句;
}
以上是一个完整的switch语句:
其中:break;语句不是必须的。default分支也不是必须的。
switch语句支持的值有哪些?
支持int类型以及String类型。
但一定要注意JDK的版本,JDK8之前不支持String类型,只支持int。
在JDK8之后,switch语句开始支持字符串String类型。
switch语句本质上是只支持int和String,但是byte,short,char也可以
使用在switch语句当中,因为byte short char可以进行自动类型转换。
switch语句中“值”与“值1”、“值2”比较的时候会使用“==”进行比较。
3、switch语句的执行原理
拿“值”与“值1”进行比较,如果相同,则执行该分支中的java语句,
然后遇到"break;"语句,switch语句就结束了。
如果“值”与“值1”不相等,会继续拿“值”与“值2”进行比较,如果相同,
则执行该分支中的java语句,然后遇到break;语句,switch结束。
注意:如果分支执行了,但是分支最后没有“break;”,此时会发生case
穿透现象。
所有的case都没有匹配成功,那么最后default分支会执行。
*/
public class SwitchTest01{
public static void main(String[] args){
// 分析这个程序是否能够编译通过?
// switch只支持int和String类型。
// 错误: 不兼容的类型: 从long转换到int可能会有损失
/*
long x = 100L;
switch(x){
}
*/
/*
long x = 100L;
switch((int)x){
}
*/
/*
byte b = 100;
switch(b){
}
*/
/*
short s = 100;
switch(s){
}
*/
/*
char c = 'a';
switch(c){
}
*/
//switch也支持字符串String类型。
//switch("abc"){}
// 写一个完整的switch语句
// 接收键盘输入,根据输入的数字来判断星期几。
// 0 星期日
// 1 星期一
// ....
// 假设输入的数字就是正确的。0到6
java.util.Scanner s = new java.util.Scanner(System.in);
System.out.print("请输入[0-6]的整数:");
int num = s.nextInt();
/*
switch(num){
case 0:
System.out.println("星期日");
break;
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("星期六");
}
*/
//case穿透现象
/*
switch(num){
case 0:
System.out.println("星期日");
break;
case 1:
System.out.println("星期一");
case 2:
System.out.println("星期二");
case 3:
System.out.println("星期三");
case 4:
System.out.println("星期四");
case 5:
System.out.println("星期五");
break;
case 6:
System.out.println("星期六");
}
*/
// 关于default语句,当所有的case都没有匹配上的时候,执行default语句。
/*
switch(num){
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;
default:
System.out.println("星期日");
}
*/
// 关于case合并的问题
switch(num){
case 1: case 2: 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;
default:
System.out.println("星期日");
}
}
}
/*
题目:
1、系统接收一个学生的考试成绩,根据考试成绩输出成绩的等级。
2、等级:
优:[90~100]
良:[80~90)
中:[70-80)
及格:[60~70)
不及格:[0-60)
3、要求成绩是一个合法的数字,成绩必须在[0-100]之间,成绩可能带有小数。
必须使用switch语句来完成,你该怎么办?
*/
public class SwitchTest02{
public static void main(String[] args){
// 提示用户输入学生成绩
java.util.Scanner s = new java.util.Scanner(System.in);
System.out.print("请输入学生成绩:");
double score = s.nextDouble();
//System.out.println(score);
if(score < 0 || score > 100){
System.out.println("您输入的学生成绩不合法,再见!");
return; // 这个代码的执行,会让main结束。后面会讲。
}
// 程序能够执行到这里说明成绩一定是合法的。
// grade的值可能是:0 1 2 3 4 5 6 7 8 9 10
// 0 1 2 3 4 5 不及格
// 6 及格
// 7 中
// 8 良
// 9 10 优
int grade = (int)(score / 10); // 95.5/10结果9.55,强转为int结果是9
String str = "不及格";
switch(grade){
case 10: case 9:
str = "优";
break;
case 8:
str = "良";
break;
case 7:
str = "中";
break;
case 6:
str = "及格";
}
System.out.println("该学生的成绩等级为:" + str);
}
}
6.11 day09课堂笔记
1、控制语句
1.1、关于循环语句
for循环
while循环
do..while循环
什么是循环语句,为什么要使用这种语句?
因为在现实世界当中,有很多事情都是需要反复/重复的去做。对应到程序当中,如果有一块代码需要重复执行,此时为了减少代码量,我们使用循环语句。
1.2、关于转向语句:
break;
continue;
return;(return语句后期讲到方法的时候再详细学习。目前先不用管。)
6.11.1 day09代码
for循环
// 演示一下:为什么要使用循环
// 循环语句的出现就是为了解决代码的复用性。
public class ForTest01{
public static void main(String[] args){
// 要求在控制台上输出100个100
/*
System.out.println(100);
System.out.println(100);
System.out.println(100);
System.out.println(100);
System.out.println(100);
System.out.println(100);
System.out.println(100);
System.out.println(100);
System.out.println(100);
System.out.println(100);
System.out.println(100);
System.out.println(100);
*/
// .... 重复的代码太多了
// 简化一下以上的代码,可以使用循环
// 什么时候可以考虑使用循环呢?相同的代码重复出现的时候,可以使用循环语句。
for(int i = 0; i < 100; i++){
System.out.println(100);
}
}
}
语法机制:
for(初始化表达式; 条件表达式; 更新表达式){
循环体; // 循环体由java语句构成
java语句;
java语句;
java语句;
java语句;
....
}
/*
1、for循环的语法机制以及运行原理?
语法机制:
for(初始化表达式; 条件表达式; 更新表达式){
循环体; // 循环体由java语句构成
java语句;
java语句;
java语句;
java语句;
....
}
注意:
第一:初始化表达式最先执行,并且在整个循环中只执行一次。
第二:条件表达式结果必须是一个布尔类型,也就是:true或false
执行原理:
先执行初始化表达式,并且初始化表达式只执行1次。
然后判断条件表达式的结果,如果条件表达式结果为true,
则执行循环体。
循环体结束之后,执行更新表达式。
更新完之后,再判断条件表达式的结果,
如果还是true,继续执行循环体。
直到更新表达式执行结束之后,再次判断条件时,条件为false,
for循环终止。
更新表达式的作用是:控制循环的次数,换句话说,更新表达式会更新
某个变量的值,这样条件表达式的结果才有可能从true变成false,从而
终止for循环的执行,如果缺失更新表达式,很有可能会导致死循环。
*/
public class ForTest02{
public static void main(String[] args){
// 最简练的for循环怎么写?
// 初始化表达式、条件表达式、更新表达式 其实都不是必须的!!!
/*
for(;;){
System.out.println("死循环");
}
*/
// 最常见的for循环
// 循环10次,输出0~9
/*
i = 0
i = 1
i = 2
i = 3
i = 4
i = 5
i = 6
i = 7
i = 8
i = 9
强调一下:对于以下的这个for循环,其中int i = 0;
最先执行,并且只执行一次,而且i变量属于for循环
的局部变量,for循环结束之后i的内存就释放了。
这个i变量只能在for循环中使用。
这个i变量属于for循环域。在main方法中没有办法直接使用。
*/
for(int i = 0;i < 10;i++){
System.out.println("i = " + i); // 0 1 2 3....9
}
//错误: 找不到符号
//System.out.println(i);
// i变量的作用域就扩大了。
int i = 0;
for(;i < 10;i++){
System.out.println("i --> " + i); // 0 1 2 3....9
}
System.out.println("这里的i可以访问吗?i = " + i); // 10
// 变形
for(int k = 1; k <= 10; k++){
System.out.println("k --> " + k); // 1 2 ..... 9 10
}
// 变形
for(int k = 1; k <= 10; ){
System.out.println("k --> " + k);
k++;
}
// 变形
for(int k = 1; k <= 10; ){
k++;
System.out.println("value --> " + k); // 2 3 4 5 6 7 8 9 10 11
}
}
}
public class ForTest03{
public static void main(String[] args){
// for的其它形式
for(int i = 10; i > 0; i--){
System.out.println("i = " + i); // 10 9 8 7 6 5 4 3 2 1
}
// 变形
for(int i = 0; i < 10; i += 2){
System.out.println("value1 = " + i); // 0 2 4 6 8
}
//注意:1对3求余数结果还是1
/*
for(int i = 100; i > 0; i %= 3){
System.out.println("value2 = " + i); // 100 1 1... 1
}
*/
}
}
/*
使用for循环,实现1~100所有奇数求和
至少给出两种解决方案。
*/
public class ForTest04{
public static void main(String[] args){
//第一种方案:
// 思路:先找出1~100所有的奇数,然后再考虑求和的事儿。
// 第一步:先从1取到100,一个数字一个数字取出来。
// 第二步:既然可以得到每一个数字,那么我们进一步判断这个数字是否为奇数
// 奇数对2求余数,结果都是1
int sum = 0; // 初始值给0
for(int i = 1; i <= 100; i++){
// int sum = 0; // 不能在这个循环体中声明,这样会导致“计算器归0”
//for循环中嵌套了if语句。
if(i % 2 == 1){ // i为奇数的条件
//System.out.println("i = " + i);
sum += i; // 累加 (sum = sum + i;)
}
}
// 一定是在for循环全部结束之后,输出的sum就是最终的结果。
System.out.println("1~100所有奇数求和结果是:" + sum); // 2500
//第二种方案:这种方案效率高,因为循环次数比较少。
// 之前的sum归0.重新求和。
sum = 0;
for(int i = 1; i < 100; i += 2){
//这样写可以保证每一次取出的值都是奇数。不需要判断。
//System.out.println(i);
sum += i;
}
System.out.println(sum);
}
}
for循环嵌套
/*
1、所有合法的“控制语句”都可以嵌套使用。
2、for循环嵌套一个for循环执行原理是什么?
提示一下:大家不要因为for循环中嵌套了一个for循环,就感觉
这个程序比较特殊,实际上大家可以这样看待:
for(){
//在分析外层for循环的时候,把里面的for就当做一段普通的java语句/代码.
for(){}
}
*/
public class ForTest05{
public static void main(String[] args){
// 单层for循环
for(int i = 0; i < 10; i++){
System.out.println("i = " + i);
}
for(int k = 0; k < 2; k++){ // 循环2次
System.out.println("k = " + k);
}
for(int k = 0; k < 2; k++){ // 循环2次
//System.out.println("k ==> " + k);
for(int i = 0; i < 10; i++){
System.out.println("i ---> " + i);
}
}
/*
int i = 0;
for(int k = 0; k < 2; k++){
//System.out.println("k's value = " + k);
for(; i < 10; i++){
System.out.println("k's value = " + k);
System.out.println("value ---> " + i);
}
}
*/
for(int k = 0; k < 2; k++){
for(int i = 0; i < 10; i++){
System.out.println("k's value = " + k);
System.out.println("value ---> " + i);
}
}
/*
// 第一遍
for(int i = 0; i < 10; i++){
System.out.println("i ==> " + i);
}
// 第二遍
for(int i = 0; i < 10; i++){
System.out.println("i ==> " + i);
}
*/
}
}
for嵌套实现九九乘法表
/*
九九乘法表
1*1=1
1*2=2 2*2=4
1*3=3 2*3=6 3*3=9
1*4=4 2*4=8 3*4=12 4*4=16
....
......
1*9=9 2*9=18.............................9*9=81
各位,请找一下以上九九乘法表的特点?????
第一个特点:共9行。
第二个特点:第1行1个。第2行2个。第n行n个。
最重要的是:不要慌,慢慢的把思路捋出来,再写代码。
*/
public class ForTest06{
public static void main(String[] args){
// 9行,循环9次。
for(int i = 1; i <= 9; i++){ // 纵向循环
//System.out.println(i); // i是行号(1~9)
// 负责输出一行的。(内部for循环负责将一行上的全部输出。)
for(int j = 1; j <= i; j++){ // i是行号
System.out.print(j + "*" + i + "=" + i * j + " ");
}
// 换行
System.out.println();
}
}
}
while循环
/*
while循环:
1、while循环的语法机制以及执行原理
语法机制:
while(布尔表达式){
循环体;
}
执行原理:
判断布尔表达式的结果,如果为true就执行循环体,
循环体结束之后,再次判断布尔表达式的结果,如果
还是true,继续执行循环体,直到布尔表达式结果
为false,while循环结束。
2、while循环有没有可能循环次数为0次?
可能。
while循环的循环次数是:0~n次。
*/
public class WhileTest01{
public static void main(String[] args){
// 死循环
/*
while(true){
System.out.println("死循环");
}
*/
// 本质上while循环和for循环原理是相同的。
/*
for(初始化表达式; 布尔表达式; 更新表达式){
循环体;
}
初始化表达式;
while(布尔表达式){
循环体;
更新表达式;
}
if switch属于分支语句属于选择语句。
for while do..while..这些都是循环语句。
可以正常互相嵌套。
*/
for(int i = 0; i < 10; i++){
System.out.println("i --->" + i);
}
/*
int i = 0;
while(i < 10){
System.out.println("i = " + i);
i++;
}
*/
// for和while完全可以互换,只不过就是语法格式不一样。
for(int i = 0; i < 10; ){
i++;
System.out.println("i --->" + i); // 1 2 3 .. 9 10
}
int i = 0;
while(i < 10){
i++;
System.out.println("i = " + i); // 1 2 3 .. 9 10
}
}
}
do while循环
/*
do..while循环语句的执行原理以及语法机制:
语法机制:
do {
循环体;
}while(布尔表达式);
注意:do..while循环最后的时候别漏掉“分号”
执行原理:
先执行循环体当中的代码,执行一次循环体之后,
判断布尔表达式的结果,如果为true,则继续执行
循环体,如果为false循环结束。
对于do..while循环来说,循环体至少执行1次。循环体的执行次数是:1~n次。
对于while循环来说,循环体执行次数是:0~n次。
*/
public class DoWhileTest01{
public static void main(String[] args){
//错误: 需要';'
/*
int i = 0;
do{
System.out.println(i);
i++;
}while(i < 10)
*/
/*
int i = 0;
do{
System.out.println(i); // 0 1 2 3 ... 8 9
i++;
}while(i < 10);
*/
/*
int i = 0;
do{
System.out.println(i++); // 0 1 2 3 ... 8 9
}while(i < 10);
*/
int i = 0;
do{
//System.out.println(++i); // 1 2 3 ... 8 9 10
// 把上面那一行代码拆分为以下的两行代码。
int temp = ++i;
System.out.println(temp); // 程序执行到此处的时候i是10
}while(i < 10);
System.out.println("-----------------------------");
int k = 100;
System.out.println(++k); // 101
System.out.println(k); // 101
int m = 10;
System.out.println(m++); // 10
System.out.println(m); // 11
// 至少执行1次循环体。
do{
System.out.println("Hello World!");
}while(false);
}
}
break语句
/*
break;语句:
1、break;语句比较特殊,特殊在:break语句是一个单词成为一个完整的java语句。
另外:continue也是这样,他俩都是一个单词成为一条语句。
2、break 翻译为折断、弄断。
3、break;语句可以用在哪里呢?
用在两个地方,其它位置不行
第一个位置:switch语句当中,用来终止switch语句的执行。
用在switch语句当中,防止case穿透现象,用来终止switch。
第二个位置:break;语句用在循环语句当中,用来终止循环的执行。
用在for当中
用在while当中
用在do....while..当中。
4、以下程序主要是以for循环为例学习break转向语句。
5、break;语句的执行并不会让整个方法结束,break;语句主要是用来终止离它最近
的那个循环语句。
6、怎么用break;语句终止指定的循环呢?
第一步:你需要给循环起一个名字,例如:
a: for(){
b:for(){
}
}
第二步:终止:break a;
*/
public class BreakTest01{
public static void main(String[] args){
// 输出0-9
/*
for(int i = 0; i < 10; i++){
System.out.println("i = " + i);
}
*/
for(int i = 0; i < 10; i++){
if(i == 5){
// break;语句会让离它最近的循环终止结束掉。
break; // break;终止的不是if,不是针对if的,而是针对离它最近的循环。
}
System.out.println("i = " + i); // 0 1 2 3 4
}
// 这里的代码照常执行。break;的执行并不会影响这里。
System.out.println("Hello World!");
// 这个for循环两次
for(int k = 0; k < 2; k++){ // 外层for
for(int i = 0; i < 10; i++){ // 内层for
if(i == 5){
break; // 这个break;语句只能终止离它最近的for
}
System.out.println("i ===> " + i);
}
}
System.out.println("------------------------------------------");
// 以下讲解的内容,以后开发很少用。不要紧张。
// 这种语法很少用,了解一下即可。
a:for(int k = 0; k < 2; k++){
b:for(int i = 0; i < 10; i++){
if(i == 5){
break a; // 终止指定的循环。
}
System.out.println("i ===> " + i);
}
}
System.out.println("呵呵");
}
}
continue语句
/*
continue;语句:
1、continue翻译为:继续
2、continue语句和break语句要对比着学习
3、continue语句的作用是:
终止当前"本次"循环,直接进入下一次循环继续执行。
for(){
if(){ // 当这个条件成立时,执行continue语句
continue; //当这个continue语句执行时,continue下面的代码不执行,直接进入下一次循环执行。
}
// 以上的continue一旦执行,以下代码不执行,直接执行更新表达式。
code1;
code2;
code3;
code4;
}
4、continue语句后面可以指定循环吗?
可以的。
这里就不再讲了,自己测试以下。
a:for(;;更新表达式1){
b:for(;;更新表达式2){
if(){
continue a;
}
code1;
code2;
code3;
}
}
*/
public class ContinueTest01{
public static void main(String[] args){
/*
i = 0
i = 1
i = 2
i = 3
i = 4
*/
for(int i = 0; i < 10; i++){
if(i == 5){
break;
}
System.out.println("i = " + i);
}
System.out.println("----------------------------");
/*
i = 0
i = 1
i = 2
i = 3
i = 4
i = 6
i = 7
i = 8
i = 9
*/
for(int i = 0; i < 10; i++){
if(i == 5){
continue;
}
System.out.println("i = " + i); // 输出i是4
}
}
}
6.12 day10课堂笔记(回顾前6章&讲解作业题)
1、今天三件事
第一:回顾之前所有的内容
第二:讲解昨天的所有作业题
第三:布置大量的练习题,坚决这个周末大家把之前所学习的所有内容全部掌握。
2、回顾
2.1、windows操作系统中,文件扩展名的展示。
2.2、安装EditPlus并且进行相关配置。
2.3、windows的dos命令
cls 清屏
exit 退出
ipconfig 查看IP地址
ping 查看两台计算机之间是否可以正常通信
d: 回车 切换盘符
cd 命令切换目录:相对路径和绝对路径
C:\Windows>cd System32 相对路径(相对于当前位置而言)
C:\>cd c:\windows\system32 绝对路径(路径以盘符开始)
cd .. 回到上级
cd \ 回到根
mkdir 创建目录
del 删除文件
dir 查看当前目录下有哪些子目录或者子文件
注意:
在DOS命令窗口中,可以使用tab键自动补全。
在DOS命令窗口中,使用上下箭头可以翻出历史命令。
2.4、快捷键:
补充两个windows系统的组合键:
win + r 打开运行窗口
win + d 显示桌面
win + l 锁屏(离开电脑的时候要锁屏)
alt + tab 切换应用
2.5、计算机语言的发展史
第一代
第二代
第三代
2.6、Java语言的发展史
JDK:java开发工具包
JavaSE JavaEE JavaME
SUN公司开发的,带头人:james gosling java之父
2.7、java语言的特点:
简单性
多线程
面向对象
可移植,跨平台:因为JVM的存在。(Java虚拟机屏蔽了操作系统之间的差异)
windows上安装的JDK和Linux上安装的JDK的版本不同。
JDK版本不同,最终JVM的版本也是不同的,每一个操作系统都有自己的JVM。
健壮性:自动GC垃圾回收机制。
2.8、java的加载与执行(java从开发到最终运行,经历了哪些过程)
要求:自己能够从头描述到最后。(不参考任何资料的前提下)
从编写到最终的运行,把过程描述出来。
第一步:
第二步:
.....
2.9、术语
JDK JRE JVM
JDK java开发工具包
JRE java运行时环境
JVM java虚拟机
JavaSE JavaEE JavaME
2.10、开始开发第一个java程序HelloWorld
第一:要会下载对应版本的JDK(http://www.oracle.com)
第二:要会安装JDK(双击,下一步)
第三:配置环境变量path(属于windows操作系统,与java无关)
path=C:\Program Files\Java\jdk-13.0.2\bin
这样javac和java命令就可以使用了。
第四:编写HelloWorld.java程序。
第五:javac进行编译:
javac命令在使用的时候
javac + java源文件的路径(注意:路径包括绝对和相对。)
第六:java命令进行运行
java 类名 (一定要注意:这里不是路径。是类名)
这里涉及到另一个环境变量叫做:classpath(属于java的,和windows操作系统无关)
classpath没有配置的情况下:从当前路径下找xxx.class文件
classpath设置了具体路径之后,不再从当前路径下找了。
重点掌握path和classpath两个环境变量。
2.11、java语言中的注释:
// 单行注释
/*
多行注释
*/
/**
* javadoc注释
*/
2.12、public class 和class的区别
一个java文件中可以定义多个class
一个class编译之后会生成1个class字节码文件,2个class编译之后会生成2个class文件
任何一个class中都可以编写main方法,每一个main方法都是一个入口
public的类可以没有
public的类如果有的话,只能有1个,并且public的类名要求和文件名一致。
class A{
main(){}
}
java A
class B{
main(){}
}
java B
....
2.13、标识符
标识符可以标识什么?
类名、接口名
变量名、方法名
常量名
标识符的命名规则?
标识符只能由数字 字母(可以有中文) 下划线 美元符号组成,不能有其它符号。
标识符不能以数字开始
标识符严格区分大小写
关键字不能做标识符
理论上没有长度限制
标识符的命名规范?
见名知意
驼峰命名方式,一高一低
类名、接口名:首字母大写,后面每个单词首字母大写。
变量名、方法名:首字母小写,后面每个单词首字母大写。
常量名:全部大写,每个单词之间使用下划线衔接。
标识符在editplus中是黑色字体。
2.14、关键字
关键字都是全部小写的,在editplus中以蓝色显示。
不需要特意记忆,一边学习一边记忆。
public
class
static
void
byte
short
int
long
float
double
boolean
char
true
false
if
else
switch
for
while
break
continue
........
2.15、变量
什么是变量,怎么理解的?
一个存储数据的盒子,
一个存储数据的单元。
int i = 100;
System.out.println(i);
什么是字面量,怎么理解的?
1 2 3 4 -100 100 整数型字面量
3.14 浮点型的字面量。
true false 布尔型字面量
'a' '中' 字符型字面量
"abc" "a" 字符串型的字面量
字面量其实本质上就是“数据”。
变量怎么声明,怎么赋值?
声明:
数据类型 变量名;
int i;
赋值:用=赋值
变量名 = 字面量;
i = 100;
重新赋值:i = 200;
变量在同一个域当中不能重名。
{
int i = 2;
double i = 2.0;
//报错了,重名了。
}
变量的分类?
在方法体当中声明的就是局部变量。
在方法体外面声明的就是成员变量。
变量的作用域?
出了大括号就不认识了。
每一个变量都有自己的有效范围。出了范围就不认识了,就不能用了。
2.16、数据类型
1. 什么是数据类型,有啥用?
数据类型决定了变量分配空间的大小,类型不同,空间大小不同。
(在内存中分配空间)
计算机的主要部件:CPU 内存 硬盘 主板。
2、数据类型分类?
基本数据类型:
byte short int long float double boolean char
引用数据类型:
String..........
3、要求要理解二进制
4、要求理解二进制和十进制之间的互相转换。
5、8种基本数据类型,每个占用空间大小。
类型 字节
------------------
byte 1
short 2
int 4
long 8
float 4
double 8
boolean 1
char 2
6、记忆byte short int char的取值范围:
byte -128 ~ 127
short -32768 ~ 32767
int -2147483648 ~ 2147483647
char 0~65535
7、理解字符编码?
什么时候会有乱码?编码和解码采用的不是同一套字符编码方式。
怎么理解字符编码?字符编码是人为制定的,一个字典表,字典中描述了转换关系。
常见的字符编码?
ASCII:
'a' 97
'A' 65
'0' 48
...
ISO-8859-1(latin-1)
GBK
GB2312
GB18030
Big5
unicode : java中采用的统一了全球所有的文字。
8、数据类型详细介绍
char
可以存储1个汉字
用单引号括起来
转义字符:
\t
\n
\'
\"
\\
\u
....
char c = 97;
System.out.println(c); //输出'a'
byte short int long
int最常用
任何一个数字,例如:1232 3 5 9,默认都是当做int处理,想当做long,必须加L或者l
123L这就是long类型
自动类型转换:小-->大
强制类型转换:大-->小,需要加强制类型转换符。另外运行可能损失精度。
当一个整数没有超出byte short char的取值范围,可以直接赋值给byte short char类型的变量。
在java中整数型字面量表示的时候有四种方式:
10 十进制
010 八进制
0x10 十六进制
0b10 二进制
float double
浮点型的数字默认被当做double来处理,想以float形式存在,数字后面添加F/f
float f = 1.0; //错误的
float f = 1.0f;
float f = (float)1.0;
要知道浮点型数据在java语言中存储的都是近似值。
还有一点:float和double的空间永远比整数型空间大,比long大。
boolean
boolean类型只有两个值:true false,没有其他值。
布尔类型使用在逻辑运算,条件判断当中。
9、基本数据类型转换的6条规则:
第一条:只有boolean不能转换,其它都行。
第二条:自动类型转换
byte < short(char) < int < long < float < double
char可以取到更大的正整数。
第三条:强制类型转换需要加强制类型转换符。可能损失精度。
第四条:当一个整数没有超出byte short char的取值范围时,可以直接赋值给byte short char类型的变量。
第五条:byte short char混合运算的时候,各自先转换成int再做运算。
第六条:多种数据类型混合运算的时候,先转换成容量最大的那一种再做运算。
2.17、运算符
算术运算符
+ - * / % ++ --
重点:++
++无论出现在变量前还是后,只要++运算结束,一定会自加1.
int i = 10;
i++;
System.out.println(i); // 11
int k = 10;
++k;
System.out.println(k); // 11
++出现在变量前:
int i = 10;
int k = ++i;
System.out.println(k); // 11
System.out.println(i); // 11
++出现在变量后:
int i = 10;
int k = i++;
System.out.println(k); // 10
System.out.println(i); // 11
int i = 10;
System.out.println(i++); // 10
解开以上题目的窍门是什么?拆代码
int temp = i++;
System.out.println(temp); // 10
System.out.println(i); // 11
int i = 10;
System.out.println(++i);
会拆代码:
int temp = ++i;
System.out.println(temp); // 11
System.out.println(i); // 11
关系运算符
>
>=
<
<=
==
!=
结果都是布尔类型。true/false
逻辑运算符
&
|
!
&&
||
逻辑运算符要求两边都是布尔类型,并且最终结果还是布尔类型。
左边是布尔类型 & 右边还是布尔类型 -->最终结果还是布尔类型。
& 两边都是true,结果才是true
| 一边是true,结果就是true
! 取反
&&实际上和&运算结果完全相同,区别在于:&&有短路现象。
左边的为false的时候:&& 短路了。
左边为true的时候:|| 短路了。
赋值运算符
=
+=
-=
*=
/=
%=
重要规则:
扩展赋值运算符在使用的时候要注意,不管怎么运算,最终的运算结果类型不会变。
byte x = 100; // byte最大值127
x += 1000; // 编译可以通过,x变量还是byte类型,只不过损失精度了。
x += 1000; 等同于: x = (byte)(x + 1000);
int i = 10;
i += 10; // 等同于:i = i + 10; 累加。
条件运算符
?:
三目
布尔表达式 ? 表达式1:表达式2
布尔表达式为true,选择表达式1作为结果。反之选择表达式2作为结果。
字符串连接运算符
+
+ 两边都是数字,进行求和。
+ 有一边是字符串,进行字符串的拼接。
+ 有多个的话,遵循自左向右依次执行:1 + 2 + 3
如果想让其中某个加号先执行,可以添加小括号:1 + (2 + 3)
注意:字符串拼接完之后的结果还是一个字符串。
技巧:怎么把一个变量塞到一个字符串当中。
String name = "jackson";
System.out.println("登录成功,欢迎"+name+"回来");
2.18、控制语句
选择语句
if
switch
循环语句
for
while
do..while
转向语句
break;
continue;
return;
1、选择语句/分支语句 if
四种写法。
语法机制:
if(布尔表达式){
}
if(布尔表达式){
}else{
}
if(布尔表达式){
}else if(布尔表达式){
}else if(布尔表达式){
}else if(布尔表达式){
}else if(布尔表达式){
}
if(布尔表达式){
}else if(布尔表达式){
}else if(布尔表达式){
}else if(布尔表达式){
}else if(布尔表达式){
}else{
}
if语句嵌套:
if(布尔表达式){ //前提条件
if(布尔表达式){
if(布尔表达式){
}else{
}
}
}else{
}
执行原理:
对于一个if语句来说,只要有1个分支执行,整个if语句结束。
当布尔表达式的结果为true时,分支才会执行。
分支当中只有一条java语句,大括号可以省略。
带有else的可以保证肯定会有一个分支执行。
2、选择语句/分支语句 switch
完整语法结构:
switch(值){ //值允许是String、int,(byte,short,char可以自动转换int)
case 值1: case 值x:
java语句;
break;
case 值2:
java语句;
break;
case 值3:
java语句;
break;
default:
java语句;
}
3、for循环
for循环语法机制:
for(初始化表达式;条件表达式;更新表达式){
循环体;
}
for(int i = 0; i < 10; i++){
System.out.println(i);
}
for循环执行原理:
1、先执行初始化表达式,并且只执行1次。
2、然后判断条件表达式
3、如果为true,则执行循环体。
4、循环体结束之后,执行更新表达式。
5、继续判断条件,如果条件还是true,继续循环。
6、直到条件为false,循环结束。
4、while循环
while(布尔表达式){
循环体;
}
执行次数:0~N次。
5、do..while循环
do{
循环体;
}while(布尔表达式);
执行次数:1~N次。
6、break;
默认情况下,终止离它最近的循环。
当然,也可以通过标识符的方式,终止指定的循环。
for(int i = 0; i < 10; i++){
if(i == 5){
break;
}
code1;
code2;
code3;
code4;
....
}
7、continue;
终止当前“本次”循环,直接跳入下一次循环继续执行。
for(int i = 0; i < 10; i++){
if(i == 5){
continue;
}
code1;
code2;
code3;
code4;
....
}
6.12.1 day10代码
/*
应该怎么去编程???????
计算1000以内所有不能被7整除的整数之和
1、解决一个问题的时候,可以先使用汉语描述思路。(养成好习惯)
2、复杂的问题可以“一步一步”去实现,没必要一下全部实现。
把上面的题目可以拆分成几步去完成:
第一步:从1开始循环,循环到1000,先保证每一个数字你都能取到。
第二步:在以上第一步的循环过程中,一个数字一个数字筛查,判断该数字是否
是“不能被7整除的整数”。
编程思想/思路,都是一步一步分析,积累出来的。
编程最主要的是写汉语思路。思路有了,代码就不远了。
把问题拆开,一个一个去解决一下。
不断写代码,不断的积累编程“模型”。
*/
public class Homework1{
public static void main(String[] args){
// 在外部准备一个变量,用来存储求和的结果
int sum = 0;
//循环的时候你能想起来for while do..while
for(int i = 1; i <= 1000; i++){ //第一步
// 判断的时候你能想起来使用if
if(i % 7 != 0){ // 第二步
// 此时的i是不能被7整除的,这个i应该此时纳入求和/累加。
//sum += i;
sum = sum + i;
}
}
// 在for循环结束之后,结果才计算完成,所以在这里输出。
System.out.println("sum = " + sum); //429429
}
}
// 计算 1+2-3+4-5+6-7....+100的结果
// 找规律:奇数时减法,偶数时加法。
// 第一种思路:(除1之外)所有的偶数求和,所有的奇数求和,然后偶数求和的结果减去奇数求和的结果。
// 第二种思路:循环过程中取出每个值,判断该数是偶数还是奇数,偶数就加,奇数就减。
//写代码养成好习惯是:写一步测试一步。
public class Homework2{
public static void main(String[] args){
// 第一步:先别着急着完成,先能从1取到100
int sum = 1; //sum的初始值不是0,而是1.
for(int i = 2; i <= 100; i++){ // i从2开始。
if(i % 2 == 0){ //偶数
sum += i;
}else{ // 奇数
sum -= i;
}
}
System.out.println("结果=" + sum); // 52
}
}
//从控制台输入一个正整数,计算该数的阶乘。例如输入5,阶乘为 5*4*3*2*1
public class Homework3{
public static void main(String[] args){
//第一步:怎么从键盘上接收一个正整数。
java.util.Scanner s = new java.util.Scanner(System.in);
// 等待用户输入一个正整数。
System.out.print("请输入一个正整数:");
int num = s.nextInt();
// 计算该数的阶乘
// 5的阶乘:5*4*3*2*1
// 8的阶乘:8*7*6*5*4*3*2*1
// 第二步:先不用管乘法的事儿,先实现从8取到1.(递减的方式取)
//int jieGuo = 0; //初始值不能是0,是0的时候,乘积最后是0.
int jieGuo = 1; //结果的初始值给1.
for(int i = num; i > 1; i--){
jieGuo *= i; // jieGuo = jieGuo * i;
}
System.out.println("计算结果 = " + jieGuo);
}
}
/*
从控制台接收一个正整数,判断该数字是否为质数
质数(质数是指在大于1的自然数中,除了1和它本身以外不再有其他因数的自然数)
因数是什么?
3 * 5 = 15
3和5都是15的因数。
2 3 4 5 6 7中:2 3 5 7都是质数。
重点题目:
主要练习,在外部打布尔标记。
*/
public class Homework4{
public static void main(String[] args){
// 从控制台接收一个正整数。
java.util.Scanner s = new java.util.Scanner(System.in);
System.out.print("请输入一个正整数:");
int num = s.nextInt(); // 假设输入的是8
// 判断该数字是否为质数
// 怎么判断num是不是质数
// 怎么判断8是不是质数?
// 思路:8除以2看看能不能整除、8除以3看看能不能整除、8除以4看看能不能整除
// 一直除下去,直到发现有能够整除的,表示该数一定不是质数。
/*
假设输入的是7:
7 / 1 不用判断
7 / 2 需要判断
7 / 3 需要判断
7 / 4 需要判断(假设这个判断,发现可以整除,就没必要往下判断了。)
7 / 5 需要判断
7 / 6 需要判断
7 / 7 不用判断
*/
//可以考虑在外边准备一个布尔类型的标记。
boolean zhiShu = true; // true表示是质数。
for(int i = 2; i < num; i++){ // 假设输入的是100
//System.out.println(i);
if(num % i == 0){
// 循环没必要执行了,为了效率,这里要终止循环
//System.out.println("该数字"+num+"不是质数!");
zhiShu = false;
break;
}
}
System.out.println(num + (zhiShu ? "是" : "不是") + "质数");
}
}
/*
从键盘接收一个正整数,该正整数作为行数,输出以下图形
*
***
*****
*******
*********
例如:输入5,则打印如上图5行。
空格的规律:
第1行4个空格(5-1)
第2行3个空格(5-2)
第3行2个空格(5-3)
第4行1个空格(5-4)
第5行0个空格(5-5)
星号的规律:
第1行1个
第2行3个
第3行5个
第4行7个
.....
行号 * 2 - 1
*/
public class Homework5{
public static void main(String[] args){
// 开发需要思路,实现这个功能需要一步一步来。
// 这个步骤是什么?
java.util.Scanner s = new java.util.Scanner(System.in);
System.out.print("请输入一个正整数作为行数:");
int rows = s.nextInt();
// 6行循环6次
// n行循环n次
for(int i = 1; i <= rows; i++){ // 外层循环控制的是总行数。
// 我在这里需要将一行全部输出
// 这里需要再使用循环,输出空格以及“*”
// 输出空格的循环
for(int j = 0; j < rows - i; j++){
System.out.print(" ");
}
// 输出星号*的循环
for(int k = 0; k < i * 2 - 1; k++){
System.out.print("*");
}
// 以上两个for循环都结束之后,表示一行就结束了
// 在这里换行
System.out.println();
}
}
}
/*
小芳的妈妈每天给她2.5元钱,她都会存起来,但是,每当这一天是存钱的第5天
或者5的倍数的话,她都会花去6元钱,请问,经过多少天,小芳才可以存到100元钱。
这个题目最主要练习是:
while循环 + 计数。
*/
public class Homework6{
public static void main(String[] args){
// 小芳每一天肯定会有2.5元的收入。
// 小芳即使有一天花了6元,但是2.5元的收入还是有的。
// 经过分析:总钱数肯定需要是double类型。int类型不行。
int day = 0; // 天数的默认初始值是0
double money = 0.0; // 钱的默认初始值是0
/*
while(true){
day++; // 天数加1
money += 2.5; // 钱加2.5元
// 如果天数是5的倍数,那么花去6元
if(day % 5 == 0){
money -= 6.0;
}
// 什么时候循环结束呢?
// 当money >= 100.0的时候,循环结束。
if(money >= 100){
break;
}
}
*/
// 改造之后的。
while(money < 100){
day++; // 天数加1
money += 2.5; // 钱加2.5元
// 如果天数是5的倍数,那么花去6元
if(day % 5 == 0){
money -= 6.0;
}
}
//小芳通过74天存到了101.0元钱
System.out.println("小芳通过" + day + "天存到了" + money + "元钱");
}
}
/*
一个数如果恰好等于它的因子之和,这个数就是完数,例如 6 = 1 + 2 + 3,编程
找出1000内所有的完数。
什么是完数?
一个数如果恰好等于它的因子之和,这个数就是完数。
那么因子怎么找?
10的因子?
10 % 1 == 0
10 % 2 == 0
10 % 5 == 0
不算10本身。
10的因子:
1 + 2 + 5 = 8
运行结果:
6
28
496
*/
public class Homework7{
public static void main(String[] args){
// 1不属于完数。从2开始判断
// 第一步:先从1到1000,每个数字都取出来
for(int i = 2; i <= 1000; i++){
// 第二步:在这里可以拿到i,那么此时应该判断i是否是一个完数。
// 这个数字有了,找这个数字的因子。
// 假设现在这个数字就是6,i等于6
int sum = 0;
for(int j = 1; j <= i / 2; j++){
//j取到的值是:1 2 3 4 5
//但实际上j取到哪儿就行了:1 2 3,取这几个数就行了。
//取到一半就行。
if(i % j == 0){
//此时j就是因子。
// 记得将因子j追加,累计。
sum += j;
}
}
// 以上for结束表示:所有因子求和完毕了。
if(i == sum){
//i是一个完数
System.out.println(i);
}
}
}
}
7 第七章 方法
7.1 章节目标与知识框架
7.1.1 章节目标
理解方法的本质以及作用;掌握方法的定义;掌握方法如何调用;理解栈数据结构;理解方法执行过程中内存是如何变化的;掌握方法的重载机制;掌握方法递归算法。
7.1.2 知识框架
7.2 方法(掌握)
7.2.1 方法的本质以及作用(理解)
我们先不讲方法是什么,先来看一段代码,分析以下程序存在哪些缺点,应该如何去改进:
以上代码完成了三个求和的功能,每一次求和的时候都把代码重新写了一遍,显然代码没有得到“重复利用”,表面上看是三个功能,但实际上只是“一个”求和功能,只不过每一次参与求和的实际数值不同。java中有没有一种方式或者语法,能让我们把功能性代码写一次,然后给这个功能性代码传递不同的数据,来完成对应的功能呢?答案是:当然有。这就需要我们掌握java语言中的方法机制,接下来大家看看改进之后的代码(这里先不需要掌握方法是怎么定义的以及怎么调用的,只要看以下代码就行,此小节是为了让大家理解方法的本质以及作用):
运行结果如下图所示:
通过以上程序我们可以看出,其实方法也没什么神秘的,方法其实就是一段普通的代码片段,并且这段代码可以完成某个特定的功能,而且可以被重复的调用/使用。java中的方法又叫做method,在C语言中叫做函数。
从现在开始大家以后在写代码的时候就要有定义方法的意识了,只要是可以独立出来的功能,我们都可以定义为单独的一个方法来完成,如果以后需要使用此功能时直接调用这个方法就行了,不要把所有的代码都扔到main方法当中,这样会导致程序的“复用性”很差。
7.2.2 方法的定义以及调用(掌握)
通过以上内容的学习,可以看出方法是一段可以完成某个特定功能的并且可以被重复利用的代码片段。接下来我们来学习一下方法应该怎么定义以及怎么调用。
定义/声明方法的语法格式如下所示:
接下来我将列出方法的相关规则,其中一些规则目前可能需要大家死记硬背,还有一些规则希望大家在理解的前提下进行记忆:
①[修饰符列表],此项是可选项,不是必须的,目前大家统一写成publicstatic,后面的课程会详细讲解。
②返回值类型,此项可以是java语言当中任何一种数据类型,包括基本数据类型,也包括所有的引用数据类型,当然,如果一个方法执行结束之后不准备返回任何数据,则返回值类型必须写void。返回值类型例如:byte,short,int,long,float,double,boolean,char,String,void等。
③方法名,此项需要是合法的标识符,开发规范中要求方法名首字母小写,后面每个单词首字母大写,遵循驼峰命名方式,见名知意,例如:login、getUsername、findAllUser等。
④形式参数列表(inta,intb),此项又被称为形参,其实每一个形参都是“局部变量”,形参的个数为0~N个,如果是多个参数,则采用半角“,”进行分隔,形参中起决定性作用的是参数的数据类型,参数名就是变量名,变量名是可以修改的,也就是说(inta,intb)也可以写成(intx,inty)。
⑤方法体,由一对儿大括号括起来,在形参的后面,这个大括号当中的是实现功能的核心代码,方法体由java语句构成,方法体当中的代码只能遵循自上而下的顺序依次逐行执行,不能跳行执行,核心代码在执行过程中如果需要外部提供数据,则通过形参进行获取。
整体来说方法的声明语法是很简单的,我相信每个人都能记住,其实我觉得方法的定义难度最大的不是语法,而是方法在定义的时候,返回值类型定为什么类型比较合适?方法的形式参数列表中定义几个参数合适?每个参数的数据类型定义为什么比较合适?以上的一系列问题实际上还是需要和具体的功能结合在一起才能决定,当然,这不是一天两天的事儿,不是说这一章节的内容学完之后就真正的会定义方法了,我们只能说语法会了,还需要后期不断的做项目,写代码才能找到感觉,找到编程思路。到那时,你自然就会定义返回值类型、形式参数列表了。
当一个方法声明之后,我们应该如何去让这个方法执行呢,当然,这个时候就需要亲自去调用这个方法了,调用方法的语法格式是(前提是方法的修饰符列表中带有static关键字):“类名.方法名(实际参数列表);”,例如以下代码:
运行结果如下图所示:
需要注意的是,方法在调用的时候,实际传给这个方法的数据被称为实际参数列表,简称实参,java语法中有这样的规定:实参和形参必须一一对应,所谓的一一对应就是,个数要一样,数据类型要对应相同。例如:实参(100,200)对应的形参(intx,inty),如果不是一一对应则编译器就会报错。当然也可能会存在自动类型转换,例如:实参(100,200)也可以传递给这样的形参(longa,longb),这里我们先不谈这个。
实际上方法在调用的时候,有的情况下“类名.”是可以省略的,我们来看看什么情况下它可以省略不写:
运行结果如下图所示:
通过以上程序的分析,我们得知,当在a()方法执行过程中调用b()方法的时候,并且a()方法和b()方法在同一个类当中,此时“类名.”可以省略不写,但如果a()方法和b()方法不在同一个类当中,“类名.”则不能省略。
7.2.3 方法返回值详解(掌握)
每个方法都是为了完成某个特定的功能,例如:登录功能、求和功能等,既然是功能,那么当这个功能完成之后,大多数情况下都会有一个结果的,比如,登录成功了或者失败了(true/false),求和之后最后的结果是100或者200,当然也有极少数的情况下是没有结果的。这个结果本质上就是一个数据,那么既然是一个数据,就一定会有对应的类型,所以在方法定义的时候需要指定该方法的返回值类型。
java语言中方法的返回值类型可以是任何一种数据类型,包括基本数据类型,也包括引用数据类型,例如:byte,short,int,long,float,double,boolean,char,String,Student(自定义类)等。当然,如果这个方法在执行完之后不需要返回任何数据,返回值类型必须写void关键字,不能空着不写。
关于方法的返回值在使用的时候有哪些注意事项呢,我们来看几段代码:
以上程序在编译的时候,报错了,错误信息是“缺少返回语句”,为什么呢?这是因为该方法在声明的时候指定了方法结束之后返回int类型的数据,但是以上方法体中并没有编写“返回数据”的代码。也就是说当一个方法在声明的时候返回值类型不是void的情况下,要求方法体中必须有负责“返回数据”的语句。这样的语句怎么写呢?答案是:“return数据;”(大家还记得第六章节控制语句中的返回语句吗?这个就是),并且要求这个“数据”的类型要和声明的返回值类型一致,要不然编译器就会报错了。代码这样写就没问题了:
如果代码这样写呢?
编译以上程序,我们可以看到编译器报错了,提示的错误信息是:“System.out.println("hello world!");”这行代码是无法访问的语句。这是为什么呢?因为在方法中如果一旦执行了带有“return”关键字的语句,此方法就会结束,所以以上的程序中“System.out.println("hello world!");”这行代码是永远无法执行到的,所以编译报错了。得到的结论是:带有return关键字的语句只要执行,所在的方法则执行结束。
那这样写呢?
还是编译报错,错误信息是“缺少返回语句”,为什么呢?实际上方法在声明的时候指定了返回值类型为int类型,java语法则要求方法必须能够“百分百的保证”在结束的时候返回int类型的数据,以上程序“return 1;”出现在if语句的分支当中,对于编译器来说,它只知道“return 1;”有可能执行,也有可能不执行,所以java编译器报错了,不允许程序员这样编写代码。来看以下代码:
这样就能编译通过了,为什么呢?这是因为编译器能够检测出以上的if..else..语句必然会有一个分支执行,这样就不缺少返回语句了。其实以上代码也可以这样写:
以上代码也可以达到同样的效果,因为“return 1;”如果不执行,则一定会执行“return 0;”,当然,如果执行了“return 1;”则方法一定会结束,“return 0;”也就没有执行的机会了。再看下面的代码:
以上程序还是编译报错,哪里出错了,为什么呢?其中“第1行”和“第3行”代码没有执行机会,编译报错,但“第2行”是有执行机会的。通过以上程序我们可以得出这样的结论:在同一个“域”中,“return”语句后面是不能写任何代码的,因为它无法执行到。
如果只是单纯的完成以上代码的功能我们也可以这样写:
所以,一个功能的实现,代码可以写很多种不同的方式,慢慢培养吧同学们,这需要一个过程,千万戒骄戒躁。我们接着看关于返回值还有哪些注意事项:
以上代码编译报错,为什么呢?这是因为一个方法在声明的时候返回值类型定义为void,这就表示该方法执行结束的时候不能返回任何数据,而以上程序中编写了“return10;”这样的代码,自然编译器会报错的。也就是说前后说法要一致,声明的时候有返回值,那么代码编写的时候就必须有“return值;”这样的语句。如果声明的时候没有返回值,那么方法结束的时候就不能编写“return值;”这样的语句。那么,大家再来看看以下的代码:
经过测试,以上的代码编译通过了,也就是说当一个方法的返回值类型是void的时候,方法体当中允许出现“return;”语句(注意:不允许出现“return值;”),这个语法的作用主要是用来终止方法的执行。当一个方法的返回值类型是void的时候,在方法执行过程中,如果满足了某个条件,则这个方法可能就没必要往下继续执行了,想终止这个方法的执行,此时执行“return;”就行了。有一些同学在最初的学习过程中,对break和return认识的不够清晰,我们来研究一下,请看以下代码:
运行结果如下图所示:
运行结果如下图所示:
通过以上的测试,我们可以得出break和return根本不是一个级别的,break用来终止循环,return用来终止一个方法的执行。接下来再看一段代码:
经过测试,以上代码编译报错,错误信息是:缺少返回值,为什么呢?这是因为方法在声明的时候规定了返回int类型的值,但是在方法体当中执行了“return;”语句,并没有返回具体的值,所以“return;”只能出现在返回值类型是void的方法中。
接下来我们来看看,当一个方法执行结束之后怎么接收这个返回值呢?请看下面代码:
运行结果如下图所示:
通过以上的测试我们得知,方法执行结束之后的返回值我们可以接收,也可以不接收,不是必须的,但大多数情况下方法的返回值还是需要接收的,要不然就没有意义了,另外方法的返回值在接收的时候采用变量的方式接收,要求这个变量的数据类型和返回值类型一致(当然,也可以遵守自动类型转换规则)。
7.2.4 栈数据结构(理解)
要想理解方法执行过程中内存的分配,我们需要先学习一下栈数据结构,那么什么是数据结构呢?其实数据结构是一门独立的学科,不仅是在java编程中需要使用,在其它编程语言中也会使用,在大学的计算机课程当中,数据结构和算法通常作为必修课出现,而且是在学习任何一门编程语言之前先进行数据结构和算法的学习。数据结构是计算机存储、组织数据的方式。数据结构是指相互之间存在一种或多种特定关系的数据元素的集合。通常情况下,精心选择的数据结构可以带来更高的运行或者存储效率。数据结构往往同高效的检索算法和索引技术有关。
常见的数据结构有哪些呢?例如:栈、队列、链表、数组、树、图、堆、散列表等。目前我们先来学习一下栈(stack)数据结构,这是一种非常简单的数据结构。如下图所示:
栈(stack)又名堆栈,它是一种运算受限的线性表。其限制是:仅允许在表的一端进行插入和删除运算。这一端被称为栈顶,相对地,把另一端称为栈底。向一个栈插入新元素又称作进栈、入栈或压栈(push),它是把新元素放到栈顶元素的上面,使之成为新的栈顶元素;从一个栈删除元素又称作出栈、退栈或弹栈(pop),它是把栈顶元素删除掉,使其相邻的元素成为新的栈顶元素。如下图所示:
通过以上的学习,我们可以得知栈数据结构存储数据有这样的特点:先进后出,或者后进先出原则。也就是说最先进去的元素一定是最后出去,最后进去的元素一定是最先出去,因为一端是开口的,另一端是封闭的。
对于栈数据结构,目前我们了解这么多就可以了,等学完“方法执行的时候内存是如何变化的”,到那个时候大家再思考一个问题,为什么方法执行过程的内存要采用栈这种数据结构呢,为什么不选择其它数据结构呢?
7.2.5 方法执行过程中内存的变化(理解)
以上内容中讲解了方法是什么,怎么定义,怎么调用,目前来说大家实际上掌握这些内容就行了,接下来的内容大家尽量去学,实在是掌握不了,也没有关系,后期的内容会对这一部分的知识点进行不断的讲解,慢慢的大家就会了,其实在学习编程的过程中会遇到很多这样的情况,没事,大家不要心急,学习后面内容的过程中你会对前面的内容豁然开朗。
以下要讲解的是程序的内存,例如:代码片段被存储在什么位置?方法调用的时候,在哪里开辟内存空间等等。所以这一部分内容还是很高端大气上档次的。不过话又说回来,要想真正掌握java,内存的分析是必要的。一旦掌握内存的分配,在程序没有运行之前我们就可以很精准的预测到程序的执行结果。
好了,接下来我们开始学习方法执行过程中内存是如何变化的?我们先来看一张图片:
上图是一张标准的java虚拟机内存结构图,目前我们只看其中的“栈”和“方法区”,其它的后期研究,方法区中存储类的信息,或者也可以理解为代码片段,方法在执行过程中需要的内存空间在栈中分配。java程序开始执行的时候先通过类加载器子系统找到硬盘上的字节码(class)文件,然后将其加载到java虚拟机的方法区当中,开始调用main方法,main方法被调用的瞬间,会给main方法在“栈”内存中分配所属的活动空间,此时发生压栈动作,main方法的活动空间处于栈底。
也就是说,方法只定义不去调用的话,只是把它的代码片段存储在方法区当中,java虚拟机是不会在栈内存当中给该方法分配活动空间的,只有在调用的瞬间,java虚拟机才会在“栈内存”当中给该方法分配活动空间,此时发生压栈动作,直到这个方法执行结束的时候,这个方法在栈内存中所对应的活动空间就会释放掉,此时发生弹栈动作。由于栈的特点是先进后出,所以最先调用的方法(最先压栈)一定是最后结束的(最后弹栈)。比如:main方法最先被调用,那么它一定是最后一个结束的。换句话说:main方法结束了,程序也就结束了(目前来说是这样)。
接下来我们来看一段代码,同时画出内存结构图,以及使用文字描述该程序的内存变化:
运行结果如下图所示:
通过上图的执行结果我们了解到,main方法最先被调用,但是它是最后结束的,其中m2方法最后被调用,但它是最先结束的。大家别忘了调用的时候分配内存是压栈,结束的时候是释放内存弹栈哦。为什么会是上图的结果呢,我们来看看它执行的内存变化,请看下图:
通过上图的分析,可以很快明白,为什么输出结果是这样的顺序,接下来我们再采用文字的方式描述它的内存变化:
①类加载器将class文件加载到方法区。
②开始调用main方法,在栈内存中给main方法分配空间,开始执行main方法,输出”mainbegin”。
③调用m1()方法,在栈内存中给m1()方法分配空间,m1()方法处于栈顶,具备活跃权,输出”m1begin”。
④调用m2()方法,在栈内存中给m2()方法分配空间,m2()方法处于栈顶,具备活跃权,输出”m2begin”,继续输出”m2over”。
⑤m2()方法执行结束,内存释放,弹栈。
⑥m1()方法这时处于栈顶,具备活跃权,输出”m1over”。
⑦m1()方法执行结束,内存释放,弹栈。
⑧main()方法这时处于栈顶,具备活跃权,输出”mainover”。
⑨main()方法执行结束,内存释放,弹栈。
⑩栈空了,程序结束。
大家是否还记得之前的课程中曾经提到方法体当中的代码是有执行顺序的,必须遵循自上而下的顺序依次逐行执行,当前行代码必须执行结束之后,下一行代码才能执行,不能跳行执行,还记得吗?现在再和栈数据结构一起联系起来思考一下,为什么要采用栈数据结构呢?是不是只有采用这种先进后出的栈数据结构才可以保证代码的执行顺序呢!此时,你是不是感觉程序的设计者在此处设计的非常巧妙呢!
7.3 方法重载/overload(掌握)
关于方法重载是什么,以及怎么进行重载,这些我们目前先不去研究,先来看看以下代码不使用方法重载机制,存在哪些缺点?
运行结果如下图所示:
我们可以看到以上三个方法功能“相似”,都是求和,只不过参与求和的数据类型不同,因此定义了三个方法,分别起了三个不同的方法名。这种方式会增加程序员编程的压力,因为程序员起码要记忆三个方法名,另外代码也不是很美观。怎么解决呢?我们来看看使用方法重载机制之后会是怎样,请看以下代码以及运行结果:
运行结果如下图所示:
以上代码使用了方法重载机制,我们可以看到,三个“功能相似”的方法名字都叫做sum,只不过方法的形参不同,这样对于程序员来说,调用方法时所需要记忆的方法名更少一些,代码更加美观一些。
接下来我们正式的研究一下方法的重载机制:什么是方法重载?什么情况下我们考虑使用方法重载?在代码角度来看,满足什么条件的时候构成方法重载?
那么,什么是方法重载呢?方法重载(overload)是指在一个类中定义多个同名的方法,但要求每个方法具有不同的参数的类型或参数的个数。调用重载方法时,Java编译器能通过检查调用的方法的参数类型和个数选择一个恰当的方法。方法重载通常用于创建完成一组任务相似但参数的类型或参数的个数不同的方法。调用方法时通过传递给它们的不同个数和类型的实参来决定具体使用哪个方法。
什么情况下我们考虑使用方法重载呢?在同一个类当中,如果多个功能是相似的,可以考虑将它们的方法名定义的一致,使用方法重载机制,这样便于程序员的调用,以及代码美观,但相反,如果两个方法所完成的功能完全不同,那么方法名也一定要不一样,这样才是合理的。
代码满足什么条件的时候构成方法重载呢?满足以下三个条件:
①在同一个类当中。
②方法名相同。
③参数列表不同:个数不同算不同,顺序不同算不同,类型不同也算不同。
接下来我们来看看以下程序哪些方法构成了方法重载,哪些没有:
编译结果如下图所示:
通过观察以上代码以及测试结果我们得知,方法5和方法4是一样的,这不是方法重载,这叫“方法重复(哈哈)”,因为之前我们就说过方法形参中起决定性作用的是参数的数据类型,参数的名字随意,因为每一个形参都是局部变量,变量名自然是随意的。其中方法6和方法1相同,显然方法的重载和方法的返回值类型没有关系,这也是合理的,因为之前我们提过,方法执行结束之后的返回值我们可以接收也可以不接收。另外方法7和方法1也无法构成重载,显然方法重载和修饰符无关。
总之,方法1和方法2要想构成方法重载,首先它们在同一个类当中,方法名一样,参数列表不同(类型、个数、顺序),这样java虚拟机在运行的时候就可以分清楚去调用哪个方法了。其实,最终要调用哪个方法,还是取决于调用的时候传递的实际参数列表。所以在java编程中要区分两个方法,首先看方法名,如果方法名一致,则继续看它们的形式参数列表。
接下来我们来看一下方法重载在实际开发中的应用,你有没有觉得每一次输出的时候“System.out.println();”这行代码很麻烦,我们来封装一个工具类,请看以下代码:
运行结果如下图所示:
看到以上的代码,你是不是感觉以后要打印数据到控制台就很方便了,代码再也不需要写这么多“System.out.println();”,你只需要“U.p();”,当然,你需要把U.java文件编译生成的U.class文件拷贝到你的硬盘当中,一直携带着,什么时候需要的话,把U.class文件放到classpath当中就可以使用了。
7.4 方法递归(理解)
什么是方法递归?我们先来看一段代码:
以上代码的执行结果如下图所示:
我们可以看到以上代码的执行过程中,一直输出“mbegin”,“mover”一次也没有输出,直到最终发生了错误:java.lang.StackOverflowError,这个错误是栈内存溢出错误,错误发生后,JVM退出了,程序结束了。
实际上以上代码在m()方法执行过程中又调用了m()方法,方法自身调用自身,这就是方法递归调用。以上程序实际上等同于以下的伪代码(说明问题,但是无法执行的代码):
通过伪代码我们可以看出,m()方法一直在被调用(方法中的代码必须遵循自上而下的顺序依次逐行执行,不能跳行执行),对于栈内存来说一直在进行压栈操作,m()方法从未结束过,所以没有弹栈操作,即使栈内存足够大(也是有限的内存),总有一天栈内存会不够用的,这个时候就会出现栈内存溢出错误。通过以上研究得出递归必须要有合法的结束条件,没有结束条件就一定会发生StackOverflowError。我们再来看看有结束条件的递归,例如以下代码:
综上所述,递归其实就是方法在执行的过程中调用了另一个方法,而另一个方法则是自己本身。在代码角度来看就是在a()方法中调用a()方法,使用递归须谨慎,因为递归在使用的时候必须有结束条件,没有结束条件就会导致无终止的压栈,栈内存最终必然会溢出,程序因错误的发生而终止。
大家再来思考一个问题,一个递归程序有合法有效的结束条件就一定不会发生栈内存溢出错误吗?在实际开发中遇到这个错误应该怎么办?
一个递归程序有的时候存在合法有效的终止条件,但由于递归的太深,在还没有等到条件成立的时候,栈内存已经发生了溢出,这种情况也是存在的,所以实际开发中我们尽可能使用循环来代替递归算法,原则是:能不用递归尽量不用,能用循环代替的尽可能使用循环。当然,如果在开发中遇到了由于使用递归导致栈内存溢出错误的发生,首先,我们要检查递归的终止条件是否合法,如果是合法的还是发生栈内存溢出错误,那么我们可以尝试调整堆栈空间的大小。怎么调整堆栈大小呢,大家可以研究一下下图中的一些参数,这里就不再讲解内存大小的调整了,这不是初级程序员应该掌握的。
接下来我们来研究一下在不使用递归的前提下,完成1~N的求和,这个应该很简单,请看下面代码:
运行结果如下图所示:
那么,使用递归应该怎么写呢?请看以下代码:
运行结果如下图所示:
我们来使用伪代码对以上代码的执行过程进行分析,请看以下伪代码:
以上程序的内存变化是这样的,请看下图:
为了加强大家对递归算法的理解,我们再来看一张图:
其实大家把上图逆时针旋转90度,你会看到一个栈数据结构对吗?
7.5 章节小结
本章节内容主要讲解了Java中方法相关的语法机制,其实这个在C语言中被称为函数,一般在开发中我们都会把独立的功能单独定义成一个方法,在需要的时候调用就行了,这样可以让写过的代码得到重复利用。
本章节中对于方法的作用是需要大家理解的,对于方法的定义与调用是需要大家必须掌握的,另外要求大家对方法的形参和返回值存在的意义有深刻的理解。还有大家要想彻底掌握Java程序中的方法,还需要对方法执行过程中内存的变化有深刻的理解。另外还要掌握方法的重载机制,以及方法的递归算法。尤其是递归时的内存变化有助于你对递归执行原理的理解。
7.6 难点疑惑
对于本章节内容来说,要求理解的内容较多,其中包括两个难点,第一个难点是:方法执行过程中内存的变化;第二个难点是:递归算法。其实只要对方法执行过程中内存的变化有很深入的理解,所有方法执行过程中内存变化都能画出来,递归算法也就理解了。对于画内存图在这里嘱咐大家几个关键点,以帮助你掌握方法的内存变化以及对递归的理解:第一个关键点是方法体当中的代码必须遵循自上而下的顺序依次逐行执行,当前行代码不结束,下一行代码是绝对不会执行的;第二个关键点是一定要理解栈数据结构的原理是遵循先进后出、后进先出原则,每当方法调用时分配空间,此时“进”栈,每当方法执行结束时释放空间,此时“出”栈。永远都是最后执行的方法最先结束,最先执行的方法最后结束。
7.7 章节习题
第一题:使用递归方式计算N的阶乘。
第二题:编写程序,模拟用户登录功能,程序开始运行时先在DOS命令窗口中初始化登录页面,提醒用户输入用户名和密码,当用户输入用户名为admin,密码为123的时候登录成功,打印欢迎信息,当用户输入的用户名和密码不正确打印错误提示信息并退出系统。对于以上的程序大家尽可能定义相关方法来完成,不要将所有代码都放到main方法当中。
第三题:通过方法重载、方法重复利用完成以下功能:
定义一个方法,该方法可以选出2个int类型较大的数据,返回值是较大的数据。
再定义一个方法,该方法可以选出3个int类型中较大的数据,返回值是较大的数据。
要求使用方法重载机制,要求代码体现出重复利用。
main方法中编写程序进行测试。
7.8 习题答案
第一题答案:
第二题答案:
第三题答案:
7.9 day11课堂笔记
1、方法
1.1、什么是方法,有什么用?
(可以先看一下一个程序如果没有方法,会出现什么问题?)
方法(英语单词:method)是可以完成某个特定功能的并且可以被重复利用的代码片段。
在C语言中,方法被称为“函数”。在java中不叫函数,叫做方法。
你定义了一个/抽取了一个方法出来,而这个方法确无法完成某个功能,那么你抽取的这个方法毫无意义。一般一个方法就是一个“功能单元”。假设在以后的开发中,某个功能是可以独立抽取出来的,建议定义为方法,这样以后只要需要这个功能,那么直接调用这个方法即可,而不需要重复编写业务逻辑代码。
1.2、方法怎么定义,语法机制是什么?
见MethodTest03
day11的回顾
1、方法是什么?
方法是一段可以完成某个特定功能的并且可以被重复利用的代码片段。
方法的出现,让代码具有了很强的复用性。
2、方法最难实现的是:
根据业务怎么进行方法的抽取。
方法的返回值类型定义为 什么?
方法的名字叫什么?
方法的形式参数列表定义为 什么?
....
一个方法就是一个独立的功能。
3、方法的定义
[修饰符列表] 返回值类型 方法名(形式参数列表){
方法体;
}
4、方法的每一个细节学习
4.1、修饰符列表:可选项,目前先写成:public static
4.2、怎么理解返回值?返回值是一个方法执行结束之后的结果。
4.3、返回值类型都可以指定哪些类型?
4.4、返回值和“return语句”的关系。
4.5、方法名只要是合法的标识符就行,首字母小写,后面每个单词首字母大写。见名知意。
4.6、形式参数列表
4.7、方法体:方法体当中的代码遵循自上而下的顺序依次逐行执行。
4.8、方法怎么调用?“类名.”什么时候可以省略?
实际参数列表,简称实参。(调用方法时传递的实际数据。)
实参和形参的关系是一一对应。
5、JVM的内存结构中三块比较重要的内存空间。
方法区:
存储代码片段,存储xxx.class字节码文件,这个空间是最先有数据的,类加载器首先将代码加载到这里。
堆内存:
后面讲(面向对象)
栈内存:
stack栈当中存储什么?
每个方法执行时所需要的内存空间(局部变量)。
6、关于数据结构中的栈数据结构
原则:
后进先出
先进后出
栈数据结构相关的术语:
栈帧:永远指向栈顶部的元素(栈顶元素具有活跃权。)
栈顶元素
栈底元素
压栈,入栈,进栈,push
弹栈,出栈,pop
昨天还聊了一些:什么是数据结构?什么是算法?
有一本书:数据结构与算法。
数据结构和算法的选择很重要,选择对了程序的执行效率大大提升。可以很好的优化程序。
7、分析程序运行过程中的内存变化
方法只定义不调用是不会执行的。
方法调用时:压栈 (在栈中给该方法分配空间)
方法执行结束时:弹栈(将该方法占用的空间释放,局部变量的内存也释放。)
7.9.1 day11代码
/*
对于一个java程序来说,如果没有“方法”,会存在什么问题?
代码无法得到复用。(怎么提高复用性,可以定义方法,然后需要
使用该功能的时候,直接调用一下方法即可。这样代码就得到复用了。)
*/
public class MethodTest01{
// 入口主方法。
public static void main(String[] args){
// 需求1:请编写程序,计算100和200的求和。
int x = 100;
int y = 200;
int z = x + y;
System.out.println(x + "+" + y + "=" + z);
// 需求2:请编写程序,计算666和888的求和。
// 这个需求2实际上和需求1是完全相同的,只不过具体求和时的“数据不同”
int a = 666;
int b = 888;
int c = a + b;
System.out.println(a + "+" + b + "=" + c);
// 需求3:请编写程序,计算111和222的和
int m = 111;
int n = 222;
int k = m + n;
System.out.println(m + "+" + n + "=" + k);
/*
需求1和需求2本质上相同,只不过参与运算的数值不同,
代码编写了两份,显然代码没有得到重复利用,专业术语
叫做“复用性”差。
功能/业务逻辑既然相同,为什么要重复编写代码,代码能不能
写一次,以后要是需要再次使用该“业务/需求”的时候,直接调用
就可以了。
如果想达到代码复用,那么需要学习java语言中的方法机制。
*/
}
}
/*
这个程序是一个体验程序,你看不懂,你只需要去体验就行了。
体验一下方法的好处。
注意:
程序开始执行的时候是先执行main方法。
因为main方法是一个入口。
在java语言中所有的方法体中的代码都必须遵循自上而下的顺序依次逐行执行。
这个必须记住。
main方法不需要程序员手动调用,是由JVM调用的。
但是除了main方法之外其他的方法,都需要程序员
手动调用,方法只有调用的时候才会执行,方法不调用
是不会执行的。
*/
public class MethodTest02{
// 方法定义在类体当中。
// 方法定义的先后顺序没有关系。都可以。
/*
public static void sumInt(int x, int y){ // 自上而下的顺序依次逐行执行。
int z = x + y;
System.out.println(x + "+" + y + "=" + z);
}
*/
// 主方法。入口。
public static void main(String[] args){ // 自上而下依次逐行执行。
// 需求1:请编写程序,计算100和200的求和。
sumInt(100, 200);
// 需求2:请编写程序,计算666和888的求和。
sumInt(666, 888);
// 需求3:请编写程序,计算111和222的和
sumInt(111, 222);
}
// 专门在这个类体当中定义一个方法,这个方法专门来完成求和。
// x y z在以下的sumInt方法中都属于局部变量
// 局部变量有一个特点:方法结束之后,局部变量占用的内存会自动释放。
public static void sumInt(int x, int y){ // 自上而下的顺序依次逐行执行。
int z = x + y;
System.out.println(x + "+" + y + "=" + z);
}
public static void sum(){
//System.out.println(x);
//System.out.println(y);
//错误: 找不到符号
//System.out.println(z);
}
}
// 这里并没有讲解方法的定义,以及方法的调用。
/*
1、方法怎么定义,语法机制是什么?
[修饰符列表] 返回值类型 方法名(形式参数列表){
方法体;
}
注意:
[] 符号叫做中括号,以上中括号[]里面的内容表示不是必须的,是可选的。
方法体由Java语句构成。
方法定义之后需要去调用,不调用是不会执行的。
1.1、关于修饰符列表:
修饰符列表不是必选项,是可选的。目前为止,大家统一写成:public static
后面你就理解应该怎么写了。
1.2、关于返回值类型:
第一:返回值类型可以是任何类型,只要是java中合法的数据类型就行,数据
类型包括基本数据类型和引用数据类型,也就是说返回值类型可以是:byte short
int long float double boolean char String......
第二:什么是返回值?
返回值一般指的是一个方法执行结束之后的结果。
结果通常是一个数据,所以被称为“值”,而且还叫
“返回值”。
方法就是为了完成某个特定的功能,方法结束之后
大部分情况下都是有一个结果的,而体现结果的一般
都是数据。数据得有类型。这就是返回值类型。
main{
// 调用a方法
a();..如果a方法执行结束之后有返回值,这个返回值返回给main了。
}
a(){}
方法执行结束之后的返回值实际上是给调用者了。谁调用就返回给谁。
第三:当一个方法执行结束不返回任何值的时候,返回值
类型也不能空白,必须写上void关键字。所以void表示该
方法执行结束后不返回任何结果。
第四:如果返回值类型“不是void”,那么你在方法体执行结束的时候必须使用
"return 值;"这样的语句来完成“值”的返回,如果没有“return 值;”这样的语句
那么编译器会报错。
return 值; 这样的语句作用是什么?作用是“返回值”,返回方法的执行结果。
第五:只要有“return”关键字的语句执行,当前方法必然结束。
return只要执行,当前所在的方法结束,记住:不是整个程序结束。
第六:如果返回值类型是void,那么在方法体当中不能有“return 值;”这样的
语句。但是可以有“return;”语句。这个语句“return;”的作用就是用来终止当前
方法的。
第七:除了void之外,剩下的都必须有“return 值;”这样的语句。
1.3、方法名
方法名要见名知意。(驼峰命名方式)
方法名在标识符命名规范当中,要求首字母小写,后面每个单词首字母大写。
只要是合法的标识符就行。
1.4、形式参数列表
简称:形参
注意:形式参数列表中的每一个参数都是“局部变量”,方法结束之后内存释放。
形参的个数是:0~N个。
public static void sumInt(){}
public static void sumInt(int x){}
public static void sumInt(int x, int y){}
public static void sum(int a, int b, double d, String s){}
形参有多个的话使用“逗号,”隔开。逗号是英文的。
形参的数据类型起决定性作用,形参对应的变量名是随意的。
1.5、方法体:
由Java语句构成。java语句以“;”结尾。
方法体当中编写的是业务逻辑代码,完成某个特定功能。
在方法体中的代码遵循自上而下的顺序依次逐行执行。
在方法体中处理业务逻辑代码的时候需要数据,数据来源就是这些形参。
2、方法定义之后怎么调用呢?
方法必须调用才能执行。
怎么调用,语法是什么?
类名.方法名(实际参数列表);
实参和形参的类型必须一一对应,另外个数也要一一对应。
*/
public class MethodTest03{
//方法定义在这里可以。
// main方法结束之后不需要给JVM返回任何执行结果。
public static void main(String[] args){
// 调用方法
//错误: 不兼容的类型: String无法转换为int
//MethodTest03.divide("abc", 200); // int a = "abc";
//错误原因: 实际参数列表和形式参数列表长度不同
//MethodTest03.divide();
// (10, 2)叫做实际参数列表,简称实参。
// 注意:实参和形参必须一一对应,类型要对应,个数要对应。
MethodTest03.divide(10, 2);
// 调用sum方法
// 怎么去接收这个方法的返回结果????
// 使用变量来接收这个方法的返回值。
// 注意:变量的定义需要指定变量的数据类型。
// 变量的数据类型是什么呢?
int jieGuo = MethodTest03.sum(100, 200);
System.out.println(jieGuo); //300
// jieGuo变量可以是double类型吗?
// double是大容量。int是小容量。自动类型转换。
double jieGuo2 = MethodTest03.sum(100, 200);
System.out.println(jieGuo2); //300.0
// 对于没有返回值的方法,变量能接收吗?
// divide方法结束没有返回值。不能接收。
// 错误: 不兼容的类型: void无法转换为int
//int i = MethodTest03.divide(100, 50);
// 当一个方法有返回值的时候,我可以选择不接收吗?
// 你可以返回值,但是我可以选择不要你这个值。这是允许的。
// 只不过这样没有意义,一般程序返回了执行结果,都是需要接收这个结果的。
// 我们可以不接收,但是这个返回值该返回还是会返回的。只不过不用变量接收。
// 以下代码虽然没有使用变量接收这个返回值,但是这个值还是返回了。
// 返回之后内存马上释放,因为没有使用变量接收。
MethodTest03.sum(100, 200);
byte b1 = 10;
//int a = b1;
byte b2 = 20;
int result = sum(b1, b2); // (b1,b2)是实参。自动类型转换。
System.out.println(result);
}
// 计算两个int类型数据的和
/*
public static String sum(int a, int b){
// 错误: 不兼容的类型: int无法转换为String
return a + b;
}
*/
public static int sum(int a, int b){
return a + b;
}
// 方法定义到这里也可以。没有顺序要求。
// 业务是什么?计算两个int类型数据的商
// 方法执行结束之后返回执行结果。
//错误: 缺少返回语句
/*
public static int divide(int x, int y){
int z = x / y;
}
*/
//错误: 不兼容的类型: String无法转换为int
/*
public static int divide(int x, int y){
int z = x / y;
return "abc";
}
*/
//可以
/*
public static int divide(int x, int y){
int z = x / y;
return z;
}
*/
//可以
/*
public static int divide(int x, int y){
return x / y;
}
*/
// 可以
/*
public static int divide(int a, int b){
return a / b;
}
*/
// 如果我不需要执行结束之后的返回值?
// 这个结果我希望直接输出。
// 错误: 不兼容的类型: 意外的返回值
/*
public static void divide(int a, int b){
return a / b;
}
*/
//可以
/*
public static void divide(int a, int b){
return; // 用来终止这个方法的
}
*/
// 可以
/*
public static void divide(int a, int b){
}
*/
// 可以
public static void divide(int a, int b){
System.out.println(a / b); // 5
}
}
/*
在方法调用的时候,什么时候“类名.”是可以省略的。什么时候不能省略?
a()方法调用b()方法的时候,a和b方法都在同一个类中,“类名.”可以
省略。如果不在同一个类中“类名.”不能省略。
*/
// 类1
public class MethodTest04{
public static void daYin3(){
System.out.println("动力节点-口口相传的Java黄埔军校!");
}
// 入口
public static void main(String[] args){
// 调用println()方法。
MethodTest04.daYin();
MethodTest04.daYin2();
MethodTest04.daYin3();
// “类名. ”可以省略吗?
daYin();
daYin2();
daYin3();
// 第一次跨类调用。
// 像这种情况下:“类名.”就不能省略了。
MyClass.daYin();
//daYin();
}
public static void daYin(){
System.out.println("hello world!");
}
public static void daYin2(){
System.out.println("hello world2!!!");
}
}
// 类2
class MyClass{
public static void daYin(){
System.out.println("打印1");
}
public static void daYin2(){
System.out.println("打印2");
}
public static void daYin3(){
System.out.println("打印3");
}
}
// 别自乱阵脚:任何一个方法体当中的代码都是遵循自上而下的顺序依次逐行执行的。
// 自上而下的顺序
/*
推测执行结果:
main begin
m1 begin
m2 begin
m3 begin
T's m3 method execute!
m3 over
m2 over
m1 over
main over
main方法最先执行,并且main方法是最后一个结束。
main结束,整个程序就结束了。
*/
public class MethodTest05{
public static void main(String[] args){
System.out.println("main begin");
// 调用m1方法
m1();
System.out.println("main over");
}
public static void m1(){
System.out.println("m1 begin");
// 调用程序不一定写到main方法中,不要把main方法特殊化。
// main方法也是一个普通方法。
m2();
System.out.println("m1 over");
}
// m2方法可以调用T类的m3()方法吗?
public static void m2(){
System.out.println("m2 begin");
T.m3();
System.out.println("m2 over");
}
}
class T{
public static void m3(){
System.out.println("m3 begin");
System.out.println("T's m3 method execute!");
System.out.println("m3 over");
}
}
/*
break;语句和return;语句有什么区别?
不是一个级别。
break;用来终止switch和离它最近的循环。
return;用来终止离它最近的一个方法。
*/
public class MethodTest06{
//main方法的返回值类型是void,表示没有返回值。
public static void main(String[] args){
for(int i = 0; i < 10; i++){
if(i == 5){
//break; // 终止for循环
return; // 终止当前的方法,和break;不是一个级别的。
//错误: 不兼容的类型: 意外的返回值
//return 10;
}
System.out.println("i = " + i);
}
System.out.println("Hello World!");
}
}
// 大家分析以下代码,编译器会报错吗?
public class MethodTest07{
public static void main(String[] args){
// 调用方法
int result = m();
System.out.println(result); // 1
// 调用x方法
int result1 = x(true);
System.out.println("result1 = " + result1);
// 再次调用x方法
int result2 = x(false);
System.out.println("result2 = " + result2);
}
//错误: 缺少返回语句
/*
public static int m(){
boolean flag = true; //编译器不负责运行程序,编译器只讲道理。
// 对于编译器来说,编译器只知道flag变量是boolean类型
// 编译器会认为flag有可能是false,有可能是true
if(flag){
// 编译器觉得:以下这行代码可能会执行,当然也可能不会执行
// 编译器为了确保程序不出现任何异常,所以编译器说:缺少返回语句
return 1;
}
}
*/
// 怎么修改这个程序呢?
// 第一种方案:带有else分支的可以保证一定会有一个分支执行。
/*
public static int m(){
boolean flag = true;
if(flag){
return 1;
}else{
return 0;
}
}
*/
// 第二种方案:该方案实际上是方案1的变形。
// return语句一旦执行,所在的方法就会结束。
/*
public static int m(){
boolean flag = true;
if(flag){
return 1;
}
return 0;
}
*/
/*
// 在同一个域当中,"return语句"下面不能再编写其它代码。编写之后编译报错。
public static int m(){
boolean flag = true;
if(flag){
return 1;
//错误: 无法访问的语句
//System.out.println("hello1");
}
// 这行代码和上面的代码hello1的区别是:不在同一个域当中。
//System.out.println("hello2");
return 0;
// 错误: 无法访问的语句
//System.out.println("hello3");
}
*/
// 三目运算符有的时候会让代码很简练。
public static int m(){
boolean flag = true;
return flag ? 1 : 0;
}
// 带有一个参数的方法。
public static int x(boolean flag){
return flag ? 1 : 0;
}
}
方法执行时内存变化
// 局部变量:只在方法体中有效,方法结束之后,局部变量的内存就释放了。
// JVM三块主要的内存:栈内存、堆内存、方法区内存。
// 方法区最先有数据:方法区中放代码片段。存放class字节码。
// 堆内存:后面讲。
// 栈内存:方法调用的时候,该方法需要的内存空间在栈中分配。
// 方法不调用是不会在栈中分配空间的。
// 方法只有在调用的时候才会在栈中分配空间,并且调用时就是压栈。
// 方法执行结束之后,该方法所需要的空间就会释放,此时发生弹栈动作。
// 方法调用叫做:压栈。分配空间
// 方法结束叫做:弹栈。释放空间
// 栈中存储什么?方法运行过程中需要的内存,以及栈中会存储方法的局部变量。
public class MethodTest08{
//主方法,入口
public static void main(String[] args){
//int a = 100;
// 这个赋值原理是:将a变量中保存的100这个数字复制一份传给b变量。
// 所以a和b是两个不同的内存空间,是两个局部变量。
//int b = a;
System.out.println("main begin");
int x = 100;
m1(x);
System.out.println("main over");
}
public static void m1(int i){ // i是局部变量
System.out.println("m1 begin");
m2(i);
System.out.println("m1 over");
}
public static void m2(int i){
System.out.println("m2 begin");
m3(i);
System.out.println("m2 over");
}
public static void m3(int i){
System.out.println("m3 begin");
System.out.println(i);
System.out.println("m3 over");
}
}
println(方法调用可以直接放到这里)
public class MethodTest09{
public static void main(String[] args){
//调用sum方法
int jieGuo = sum(10, 20);
System.out.println(jieGuo); // 30
// 上面两行代码能否合并为一行?
// 可以
System.out.println(sum(100, 200)); // 300
System.out.println(m()); // true
boolean flag = m();
if(flag){
System.out.println("真的。。。。。");
}
if(m()){
System.out.println("真的。。。。。");
}
}
public static boolean m(){
return true;
}
// 求和的方法
public static int sum(int a, int b){
return a + b;
}
}
008-JVM的主要内存空间(三块)
002-关于栈数据结构
003-方法执行过程中的内存变化
7.9.2 day11作业
1、编写一个方法,求整数n的阶乘,例如5的阶乘是1*2*3*4*5
思考:这个方法应该起什么名字,这个方法的形参是什么,方法的返回值类型是什么。
2、编写一个方法,输出大于某个正整数n的最小的质数。
思考:这个方法应该起什么名字,这个方法的形参是什么,方法的返回值类型是什么。
3、画出以下程序运行过程的内存图
public class Test{
public static void main(String[] args){
int a = 100;
int b = 200;
int retValue = m1(a, b);
System.out.println(retValue);
}
public static int m1(int x, int y){
int k = x * 10;
int m = y * 10;
int result = m2(k, m);
return result;
}
public static int m2(int c, int d){
int e = c / 2;
int f = d / 2;
int i = e + f;
return i;
}
}
public class Homework1{
public static void main(String[] args){
/*
// 计算5的阶乘
int n = 5;
int result = 1;
for(int i = n; i > 1; i--){
//System.out.println(i);
//result = result * i;
result *= i;
}
System.out.println(result);
*/
// 调用方法计算阶乘
int retValue1 = jieCheng(5);
System.out.println(retValue1); // 120
int retValue2 = jieCheng(6);
System.out.println(retValue2); // 720
}
// 提取一个方法出来,这个方法专门来计算某个数的阶乘
// 这个数不一定是5,可能是其他的值,可能是6,也可能是7,不确定
// 像这种不确定的数据,对于方法来说我们就可以定义为:形参。(形参类型定义为int)
// 该方法是为了完成阶乘的,最终是需要一个计算结果的,所以该方法应该有返回值。
// 将最后的结果返回给调用方。谁调用我,我就返回给谁。返回值类型定义为int
// 我这个方法可以计算任何数的阶乘。
public static int jieCheng(int n){
int result = 1;
for(int i = n; i > 1; i--){
result *= i;
}
return result;
}
}
/*
编写一个方法,输出大于某个正整数n的最小的质数
比如:这个正整数n是2
也就是要输出:大于2的最小的质数,结果就是3
比如:这个正整数n是9
也就是要输出:大于9的最小的质数,结果就是11
大于11的最小的质数是:13
思路:
首先,系统一定会先给你一个“正整数n”,然后你基于
这个n往后++,每加1得到的新数m判断一下是否为质数。
*/
public class Homework2{
public static void main(String[] args){
/*
// 假设目前系统给了一个正整数n,n为5
int n = 5;
// 请找出大于5的最小的质数
while(true){
n++; // n自加1
// 判断此时的n是否为质数
boolean flag = isZhiShu(n);
if(flag){
System.out.println(n);
break;
}
}
*/
// 对一个单独的方法进行测试
/*
boolean flag = isZhiShu(6);
System.out.println(flag ? "质数" : "非质数"); // true
*/
printZuiXiaoZhiShu(5);
printZuiXiaoZhiShu(10);
printZuiXiaoZhiShu(12);
printZuiXiaoZhiShu(100);
}
// 这方法就是用来打印最小质数的。
public static void printZuiXiaoZhiShu(int n){
while(true){
n++; // n自加1
// 判断此时的n是否为质数
boolean flag = isZhiShu(n);
if(flag){
System.out.println(n);
break;
}
}
}
// 定义一个专门的方法,来判断某个数字是否为质数
// 这个方法的形参是:被判断的数字num
// 这个方法的返回值类型是true表示是质数,是false表示非质数。
public static boolean isZhiShu(int num){
// 你怎么判断num是否是一个质数
// 质数只能被1和自身整除
for(int i = 2; i < num; i++){
if(num % i == 0){
return false;
}
}
//程序能够执行到此处说明num已经是质数了。
return true;
}
}
Homework2改造
/*
编写一个方法,输出大于某个正整数n的最小的质数
比如:这个正整数n是2
也就是要输出:大于2的最小的质数,结果就是3
比如:这个正整数n是9
也就是要输出:大于9的最小的质数,结果就是11
大于11的最小的质数是:13
思路:
首先,系统一定会先给你一个“正整数n”,然后你基于
这个n往后++,每加1得到的新数m判断一下是否为质数。
*/
public class Homework22{
public static void main(String[] args){
printZuiXiaoZhiShu(7);
}
public static void printZuiXiaoZhiShu(int n){
while(!isZhiShu(++n)){
}
System.out.println(n);
}
public static boolean isZhiShu(int num){
for(int i = 2; i < num; i++){
if(num % i == 0){
return false;
}
}
return true;
}
}
7.10 day12课堂笔记
1、方法重载overload
1.1、什么情况下我们考虑使用方法重载机制?
当功能相似的时候,建议将方法名定义为一致的,这样代码美观,又方便编程。
注意:如果功能不相似,坚决要让方法名不一致。
1.2、代码满足什么条件的时候构成了方法重载?
条件1:在同一个类当中
条件2:方法名相同
条件3:形式参数列表不同(类型、个数、顺序)
注意:
方法重载和返回值类型无关,和修饰符列表无关。
1.3、方法重载的优点?
代码美观
方便代码的编写
2、方法递归
2.1、需要理解什么是方法递归?
方法自身调用自身。
2.2、使用递归的时候,必须添加结束条件,没有结束条件,会发生栈内存溢出错误。
StackOverflowError
原因:一直压栈,没有弹栈,栈内存不够用。
2.3、会画出递归方法的内存结构图。
递归的过程当中可以将图画出来。
2.4、能够使用循环代替递归的尽量使用循环,循环的执行耗费内存少一些,递归耗费内存相对多一些,另外递归使用不当很容易内存溢出,JVM停止工作。
当然,只有极少数情况下,只能用递归,其它代码解决不了问题。
2.5、当递归有结束条件,并且结束条件合法的时候,就一定不会内存溢出吗?
也不一定。可能递归的太深了。
2.6、分享了一些递归方面的经验
在实际的开发中遇到递归导致的栈内存溢出错误是怎么办?
第一步:先检查结束条件是否正确。
第二步:如果正确,可以调整JVM的栈内存大小。(java -X)
3、我们要一味地将变量缩减吗?代码缩减吗?这样好吗?
public class Test{
public static void main(String[] args){
/*
int i = 100;
System.out.println(i);
*/
System.out.println(100);
boolean flag = test();
if(flag){
...
}
// 缩减之后的
if(test()){
....
}
}
public static boolean test(){
return true;
}
}
太计较变量的数量会有什么后果呢?(运行效率不会低)
后果1:代码的可读性差。
后果2:可读性差也可以会牵连到代码的开发效率。
其实计算机内存不差这个。。。。。。
注意:在编码过程中,有一些变量名是必须要定义的。
因为在后面代码中还需要访问这个数据。重复的访问这个
数据。
7.10.1 day12代码
方法重载
/*
方法重载机制?
1、以下程序先不使用方法重载机制,分析程序的缺点???
以下程序没有语法错误,运行也是正常的,你就分析一下代码风格存在什么缺点!
sumInt、sumLong、sumDouble不是功能“相同”,是功能“相似”。
三个方法功能不同,但是相似,分别起了三个不同的名字,有什么缺点?
缺点包括两个:
第一个:代码不美观(不好看、不整齐)。【这是次要的】
第二个:程序员需要记忆更多的方法名称,程序员比较累。
*/
public class OverloadTest01{
//主方法
public static void main(String[] args){
int x = sumInt(10, 20);
System.out.println(x);
long y = sumLong(10L, 20L);
System.out.println(y);
double z = sumDouble(10.0, 20.0);
System.out.println(z);
}
// 定义一个计算int类型数据的求和方法
public static int sumInt(int a, int b){
return a + b;
}
// 定义一个计算long类型数据的求和方法
public static long sumLong(long a, long b){
return a + b;
}
// 定义一个计算double类型数据的求和方法
public static double sumDouble(double a, double b){
return a + b;
}
}
/*
使用方法重载机制。解决之前的两个缺点。
优点1:代码整齐美观。
优点2:“功能相似”的,可以让“方法名相同”,更易于以后的代码编写。
在java语言中,是怎么进行方法区分的呢?
首先java编译器会通过方法名进行区分。
但是在java语言中允许方法名相同的情况出现。
如果方法名相同的情况下,编译器会通过方法的参数类型进行方法的区分。
*/
public class OverloadTest02{
public static void main(String[] args){
// 对于程序员来说,只需要记忆一个方法名即可。
System.out.println(sum(10, 20));
System.out.println(sum(10L, 20L));
System.out.println(sum(10.0, 20.0));
}
// 定义一个计算int类型数据的求和方法
public static int sum(int a, int b){
System.out.println("int求和");
return a + b;
}
// 定义一个计算long类型数据的求和方法
public static long sum(long a, long b){
System.out.println("long求和");
return a + b;
}
// 定义一个计算double类型数据的求和方法
public static double sum(double a, double b){
System.out.println("double求和");
return a + b;
}
}
/*
方法重载(overload):
什么时候需要考虑使用方法重载?
在同一个类当中,如果“功能1”和“功能2”它们的功能是相似的,
那么可以考虑将它们的方法名一致,这样代码既美观,又便于
后期的代码编写(容易记忆,方便使用)。
注意:方法重载overload不能随便使用,如果两个功能压根不相干,
不相似,根本没关系,此时两个方法使用重载机制的话,会导致
编码更麻烦。无法进行方法功能的区分。
什么时候代码会发生方法重载?
条件1:在同一个类当中
条件2:方法名相同
条件3:参数列表不同
参数的个数不同算不同
参数的类型不同算不同
参数的顺序不同算不同
只要同时满足以上3个条件,那么我们可以认定方法和方法之间发生了
重载机制。
注意:
不管代码怎么写,最终一定能让java编译器很好的区分开这两个方法。
方法重载和方法的“返回值类型”无关。
方法重载和方法的“修饰符列表”无关。
*/
public class OverloadTest03{
public static void main(String[] args){
m1();
m1(100);
m2(10, 3.14);
m2(3.14, 10);
m3(100);
m3(3.14);
}
public static void m1(){
System.out.println("m1无参数的执行!");
}
// 这个方法的参数个数和上面的方法的参数个数不同。
public static void m1(int a){
System.out.println("m1有一个int参数执行!");
}
public static void m2(int x, double y){
System.out.println("m2(int x, double y)");
}
// 参数的顺序不同,也算不同。
public static void m2(double y, int x){
System.out.println("m2(double y, int x)");
}
public static void m3(int x){
System.out.println("m3(int x)");
}
// 参数的类型不同。
public static void m3(double d){
System.out.println("m3(double d)");
}
//分析:以下两个方法有没有发生重载?
// 编译器报错了,不是重载,这是重复了:呵呵。
/*
public static void m4(int a, int b){
}
public static void m4(int x, int y){
}
*/
// 这两个方法有没有发生重载呢?
// 这不是重载,这是方法重复了。
/*
public static int m5(){
return 1;
}
public static double m5(){
return 1.0;
}
*/
//这两个方法重载了吗?
// 这个方法没有修饰符列表
// 这不是重载,是重复了。
/*
void m6(){
}
// 这个有修饰符列表
public static void m6(){
}
*/
}
class MyClass{
// 不在同一个类当中,不能叫做方法重载。
public static void m1(int x, int y){
}
}
public class OverloadTest04{
public static void main(String[] args){
// 大家是否承认:println是一个方法名。
// println我承认是方法名了,但是这个方法谁写的?SUN公司的java团队写的。
// 你直接用就行。
// println()方法肯定是重载了。(不信,你可以翻阅一下SUN公司写的源代码看看。)
// 对于println()方法来说,我们只需要记忆这一个方法名就行。
// 参数类型可以随便传。这说明println()方法重载了。
System.out.println(10);
System.out.println(3.14);
System.out.println(true);
System.out.println('a');
System.out.println("abc");
System.out.println(100L);
System.out.println(3.0F);
// 调用m方法
m(100);
}
public static void m(int i){
}
}
代码的封装
// 目前我们正在学习的一个内容是:方法重载机制(overload)
public class S{
// 以下所有的p()方法构成了方法的重载。
// 换行的方法
public static void p(){
System.out.println();
}
// 输出byte
public static void p(byte b){
System.out.println(b);
}
// 输出short
public static void p(short s){
System.out.println(s);
}
// 输出int
public static void p(int i){
System.out.println(i);
}
// 输出long
public static void p(long l){
System.out.println(l);
}
// 输出float
public static void p(float f){
System.out.println(f);
}
// 输出double
public static void p(double d){
System.out.println(d);
}
// 输出boolean
public static void p(boolean b){
System.out.println(b);
}
// 输出char
public static void p(char c){
System.out.println(c);
}
// 输出String
public static void p(String s){
System.out.println(s);
}
}
public class HelloWorld{
public static void main(String[] args){
//System.out.println("hello world!");
//错误: 找不到符号
//p("Hello World!");
S.p("Hello World!");
S.p(100);
S.p('a');
S.p(true);
S.p(100 + 200);
S.p(10 / 3);
// 调用hehe
HelloWorld.hehe();
hehe();
}
public static void hehe(){
System.out.println("呵呵");
}
}
方法递归
/*
方法递归?
1、什么是方法递归?
方法自己调用自己,这就是方法递归。
2、当递归时程序没有结束条件,一定会发生:
栈内存溢出错误:StackOverflowError
所以:递归必须要有结束条件。(这是一个非常重要的知识点。)
JVM发生错误之后只有一个结果,就是退出JVM。
3、递归假设是有结束条件的,就一定不会发生栈内存溢出错误吗?
假设这个结束条件是对的,是合法的,递归有的时候也会出现栈内存溢出错误。
因为有可能递归的太深,栈内存不够了。因为一直在压栈。
4、在实际的开发中,不建议轻易的选择递归,能用for循环while循环代替的,尽量
使用循环来做。因为循环的效率高,耗费的内存少。递归耗费的内存比较大,另外
递归的使用不当,会导致JVM死掉。
(但在极少数的情况下,不用递归,这个程序没法实现。)
所以:递归我们还是要认真学习的。
5、在实际的开发中,假设有一天你真正的遇到了:StackOverflowError
你怎么解决这个问题,可以谈一下你的思路吗?
我来谈一下我的个人思路:
首先第一步:
先检查递归的结束条件对不对。如果递归结束条件不对,
必须对条件进一步修改,直到正确为止。
第二步:假设递归条件没问题,怎么办?
这个时候需要手动的调整JVM的栈内存初始化大小。
可以将栈内存的空间调大点。(可以调整大一些。)
第三步:调整了大小,如果运行时还是出现这个错误,
没办法,只能继续扩大栈的内存大小。
(java -X)这个可以查看调整堆栈大小的参数
*/
public class RecursionTest01{
// 入口
public static void main(String[] args){
doSome();
}
public static void doSome(){
System.out.println("doSome begin");
// 调用方法:doSome()既然是一个方法,那么doSome方法可以调用吗?当然可以。
// 目前这个递归是没有结束条件的,会出现什么问题?
doSome();
// 这行代码永远执行不到。
System.out.println("doSome over");
}
/*
public static void doSome(){
System.out.println("doSome begin");
// 调用方法:doSome()既然是一个方法,那么doSome方法可以调用吗?当然可以。
doSome();
// 这行代码永远执行不到。
System.out.println("doSome over");
}
public static void doSome(){
System.out.println("doSome begin");
// 调用方法:doSome()既然是一个方法,那么doSome方法可以调用吗?当然可以。
doSome();
// 这行代码永远执行不到。
System.out.println("doSome over");
}
public static void doSome(){
System.out.println("doSome begin");
// 调用方法:doSome()既然是一个方法,那么doSome方法可以调用吗?当然可以。
doSome();
// 这行代码永远执行不到。
System.out.println("doSome over");
}
public static void doSome(){
System.out.println("doSome begin");
// 调用方法:doSome()既然是一个方法,那么doSome方法可以调用吗?当然可以。
doSome();
// 这行代码永远执行不到。
System.out.println("doSome over");
}
public static void doSome(){
System.out.println("doSome begin");
// 调用方法:doSome()既然是一个方法,那么doSome方法可以调用吗?当然可以。
doSome();
// 这行代码永远执行不到。
System.out.println("doSome over");
}
public static void doSome(){
System.out.println("doSome begin");
// 调用方法:doSome()既然是一个方法,那么doSome方法可以调用吗?当然可以。
doSome();
// 这行代码永远执行不到。
System.out.println("doSome over");
}
public static void doSome(){
// 假设突然有一天,一个条件成立了,这个doSome结束了
if(某个条件成立了){
return;
}
}
*/
}
005-递归没有结束条件的时候会发生栈内存溢出错误
// 先不使用递归,请编写程序,计算1~n的和。
public class RecursionTest02{
public static void main(String[] args){
// 1~10的和
int retValue1 = sum(10);
System.out.println(retValue1);
// 1~3的和
int retValue2 = sum(3);
System.out.println(retValue2); // 6 (1 + 2 + 3)
}
// 单独编写一个计算1~n和的方法
public static int sum(int n){
int result = 0;
for(int i = 1; i <= n; i++){
result += i;
}
return result;
}
}
// 使用递归,请编写程序,计算1~n的和。
public class RecursionTest03{
public static void main(String[] args){
// 1~3的和
int n = 3;
int r = sum(n);
System.out.println(r); // 6
}
// 大家努力的去看,去听,自己写不出来没关系,关键是能不能看懂。
// 单独编写一个计算1~n和的方法
// 这个代码修改为递归的方式。
// 3 + 2 + 1
public static int sum(int n){
//n最初等于3
// 3 + 2 (2是怎么的出来的:n - 1)
//sum(n - 1);
if(n == 1){
return 1;
}
// 程序能执行到此处说明n不是1
return n + sum(n-1);
}
}
004-递归原理
// 使用递归的方式计算N的阶乘
// 5的阶乘:5 * 4 * 3 * 2 * 1
// 用递归的方式实现一个。
// 使用for循环的方式实现一个。
public class RecursionTest04{
public static void main(String[] args){
int n = 5;
int jieGuo = jieCheng(n);
System.out.println(jieGuo); // 120
System.out.println(jieCheng2(5));
}
public static int jieCheng2(int n){
int result = 1;
for(int i = 2; i <= n; i++){
result *= i;
}
return result;
}
public static int jieCheng(int n){
// 5 * 4 * 3 * 2 * 1
if(n == 1){
return 1;
}
/*
int result = n * jieCheng(n - 1);
return result;
*/
return n * jieCheng(n - 1);
}
}
8 第八章 认识面向对象
8.1 章节目标与知识框架
8.1.1 章节目标
了解面向对象,知道类和对象的区别,会进行类的定义。
8.1.2 知识框架
8.2 面向过程和面向对象的区别(了解)
“面向过程”(ProcedureOriented)是一种以过程为中心的编程思想,简称OP。“面向过程”也可称之为“面向记录”编程思想,就是分析出解决问题所需要的步骤,然后用函数把这些步骤一步一步实现,使用的时候一个一个依次调用就可以了。所以面向过程的编程方式关注点不在“事物”上,而是做这件事分几步,先做什么,后做什么。例如:早晨起来:起床、穿衣、洗漱、上班,只要按照这个步骤来,就能实现“一天”的功能,整个这个过程中关注的是一步一步怎么做,并没有关注“人”这个事物。再例如:开门、调整座椅、系好安全带、踩离合、启动、挂档、给油,只要按照这个步骤来,车就走了,显然关注点还是在步骤上,只要实现每一步就行,整个过程并没有关注“汽车”这个事物。
“面向对象”(ObjectOriented)是一种以对象为中心的编程思想,简称OO。随着计算机技术的不断提高,计算机被用于解决越来越复杂的问题。一切事物皆对象,通过面向对象的方式,将现实世界的事物抽象成对象。通过面向对象的方法,更利于用人理解的方式对复杂系统进行分析、设计与编程。同时,面向对象能有效提高编程的效率,通过封装技术,可以像搭积木的一样快速开发出一个全新的系统。面向对象将对象作为程序的基本单元,将程序和数据封装其中,以提高软件的重用性、灵活性和扩展性。
使用面向对象编程思想开发系统,在现代开发中会将面向对象贯穿整个过程,一般包括:OOA/OOD/OOP:
①OOA:面向对象分析(Object-OrientedAnalysis)
②OOD:面向对象设计(Object-OrientedDesign)
③OOP:面向对象编程(Object-OrientedProgramming)
面向过程和面向对象有什么关系呢?面向过程其实是最为实际的一种思考方式,就算是面向对象的方法也是含有面向过程的思想。可以说面向过程是一种基础的方法。它考虑的是实际地实现。一般的面向过程是从上往下步步求精。面向对象主要是把事物给对象化,对象包括属性与行为。当程序规模不是很大时,面向过程的方法还会体现出一种优势。因为程序的流程很清楚,按着模块与函数的方法可以很好的组织。但对于复杂而庞大的系统来说,面向过程显得就很无力了。
为了帮助大家理解面向过程和面向对象,我们再来设想一个场景,假如说编写一段程序,模拟一个人抽烟的场景,采用面向过程的方式是这样的:买烟->买打火机->找能够抽烟的场合->点燃香烟->开抽,只要按照这个流程一步一步来,就可以实现抽烟场景,采用面向对象的方式关注点就不一样了,我们会想这个场景都有什么事物参与,每个事物应该有什么行为,然后将这些事物组合在一起,来描述这个场景,例如:一个会抽烟的人(对象)+香烟(对象)+打火机(对象)+允许抽烟的场所(对象),将以上4个对象组合在一起,就实现了抽烟场景,其中采用面向对象的方式开发具有很强的扩展力,例如:人这个对象是可以更换的,打火机也是可以更换的,香烟的品牌也是可以更换的,包括抽烟的场合也是可以更换的。如果采用面向过程方式开发,一步依赖另一步,任何一步都不能变化,变化其中一步则整个软件都会受到影响。
网上发现了一篇文章,说了一下OP与OO的不同,并且打了一个比喻,通俗易懂。有人这么形容OP和OO的不同:用面向过程的方法写出来的程序是一份蛋炒饭,而用面向对象写出来的程序是一份盖浇饭。所谓盖浇饭,北京叫盖饭,东北叫烩饭,广东叫碟头饭,就是在一碗白米饭上面浇上一份盖菜,你喜欢什么菜,你就浇上什么菜。我觉得这个比喻还是比较贴切的。蛋炒饭制作的细节,我不太清楚,因为我没当过厨师,也不会做饭,但最后的一道工序肯定是把米饭和鸡蛋混在一起炒匀。盖浇饭呢,则是把米饭和盖菜分别做好,你如果要一份红烧肉盖饭呢,就给你浇一份红烧肉;如果要一份青椒土豆盖浇饭,就给浇一份青椒土豆丝。蛋炒饭的好处就是入味均匀,吃起来香。如果恰巧你不爱吃鸡蛋,只爱吃青菜的话,那么唯一的办法就是全部倒掉,重新做一份青菜炒饭了。盖浇饭就没这么多麻烦,你只需要把上面的盖菜拨掉,更换一份盖菜就可以了。盖浇饭的缺点是入味不均,可能没有蛋炒饭那么香。到底是蛋炒饭好还是盖浇饭好呢?其实这类问题都很难回答,非要比个上下高低的话,就必须设定一个场景,否则只能说是各有所长。如果大家都不是美食家,没那么多讲究,那么从饭馆角度来讲的话,做盖浇饭显然比蛋炒饭更有优势,他可以组合出来任意多的组合,而且不会浪费。盖浇饭的好处就是"菜""饭"分离,从而提高了制作盖浇饭的灵活性。饭不满意就换饭,菜不满意换菜。用软件工程的专业术语就是"可维护性"比较好,"饭"和"菜"的耦合度比较低。蛋炒饭将"蛋""饭"搅和在一起,想换"蛋""饭"中任何一种都很困难,耦合度很高,以至于"可维护性"比较差。软件工程追求的目标之一就是可维护性,可维护性主要表现在3个方面:可理解性、可测试性和可修改性。面向对象的好处之一就是显著的改善了软件系统的可维护性。
对于编程语言来说,基于C语言的编程是面向过程的,C++只能说一半面向过程一半面向对象,java语言就是一门完全面向对象的编程语言。有C++基础的同学,学习java应该很快,因为java底层是C++语言实现的。当然,除了java语言之外,还有很多都是完全面向对象的编程语言,例如:C#、Python等。
对于面向过程和面向对象的理解,目前阶段来说还是很难的,毕竟大家现在还停留在只会定义变量,写个if语句阶段,慢慢来吧,我们需要不断的学习后面的内容,然后再加深对面向对象的理解。
8.3 面向对象三大特征(了解)
面向对象具有三大特征,这三大特征目前大家只需要记住,后面我会进行一一讲解:
①封装(Encapsulation)
②继承(Inheritance)
③多态(Polymorphism)
任何一门面向对象的编程语言都具备以上三大特征,例如:python、C#、java等。
8.4 类
8.4.1 类和对象的概念(理解)
面向对象之所以能够成为主流,那是因为人习惯以对象的方式认识现实世界,例如我说:老虎。那你大脑中马上呈现出一个老虎的样子,对吧。
软件存在的意义就是为了解决现实世界当中的问题,它必然模拟现实世界,也就是说现实世界中有什么,软件中就对应有什么。
面向对象编程思想中关注点是“对象”或者“事物”,那么在编程语言当中要想创建对象则必须先有类,那么类和对象分别是什么,它们的区别和联系是什么呢?
类是现实世界当中具有共同特征的事物进行抽象形成的模板或概念。而对象是实际存在的个体。例如:“汽车”就是一个类(所有的汽车都有方向盘、发动机、都能形式,这是它们的共同特征),“你家的那个汽车”就是一个真实存在的对象。或者说“明星”是一个类,“刘德华”就是一个对象。“沈腾”、“赵本山”、“宋丹丹”都是实际存在的对象,他们都属于“笑星”类,类描述事物的共同特征,那么“笑星”类都有哪些共同特征呢?笑星类都有姓名、性别、年龄等状态信息(属性),他们还有一个共同的行为就是“演出”(方法)。但当具体到某个对象上之后,我们发现姓名是不同的,性别是不同的,年龄也是不同的,演出的效果也是不同的。所以我们在访问姓名、性别、年龄的时候,必须先有笑星对象,通过真实存在的笑星对象去访问他的属性,包括“演出”的时候,只有“笑星”类是不行的,必须先有笑星对象,让笑星对象去执行“演出”这个动作。
通过类可以创建对象,对象又被称为实例(instance),这个过程也可以称为实例化。对象1、2、3具有共同特征,进行抽象形成了类,所以从对象到类称为抽象。如下图所示:
通过以上的描述,我们得知:类=属性+方法,而属性描述的是状态,方法描述的是行为动作。行为动作以方法的形式存在,那属性以什么形式存在呢?例如:姓名、性别、年龄,大家想起之前学习的变量了吗?变量用来存储数据。不错,对象的属性以变量形式存在,并且这里所说的变量是我们之前提过的“成员变量当中的实例变量”。为什么是实例变量呢,实例变量就是对象级别的变量,这样的变量要求必须先存在对象,通过对象才能访问。例如:“中国人”这个类,有一个属性是“身份证号”,每一个中国人的“身份证号”都是不一样的,所以身份证号必须使用一个真实存在的“中国人对象”来访问。不能使用“中国人”这个类去访问身份证号。一个类可以实例化N多个对象,假设通过“中国人”这个类创建了100个“中国人对象”,那么“身份证号”必然会有100个实例变量空间去存储。
8.4.2 类的发现和设计(了解)
理解了类和对象的概念之后,我们开始进行类的设计,那么,应该怎么在现实世界当中发现类呢?例如有这样的背景:开发学生选课系统,要求能够单独对学生信息、课程信息进行维护,还要求能够维护某学生选择某些课程。根据以上的描述,我们可以看到上面的描述中有很多名词,例如:学生、课程等。从这些名词当中就可以发现类,例如:学生类、课程类。所有的学生都有学号、姓名、性别、出生日期等属性,所有的课程都有课程编号、课程名字等属性。
如果我们发现了类,并且发现了类中的属性和方法,那么应该以什么形式展现出来呢,在团队协作开发中应该如何让其他项目组成员知晓你的设计呢,恐怕这个时候就需要使用UML了。UnifiedModelingLanguage(UML)又称统一建模语言或标准建模语言,是始于1997年一个OMG标准,它是一个支持模型化和软件系统开发的图形化语言,为软件开发的所有阶段提供模型化和可视化支持,包括由需求分析到规格,到构造和配置。面向对象的分析与设计(OOA&D,OOAD)方法的发展在80年代末至90年代中出现了一个高潮,UML是这个高潮的产物。它不仅统一了Booch、Rumbaugh和Jacobson的表示方法,而且对其作了进一步的发展,并最终统一为大众所接受的标准建模语言。UML规范用来描述建模的概念有:类、对象、关联、职责、行为、接口、用例、包、顺序、协作,以及状态。
其实软件开发和现实生活当中的建造大楼是一样的,在建造大楼之前需要进行前期的设计,这个时候就需要建筑工程师画图纸,图纸上画的都是一些符合某些标准的符号,负责建筑的人员一定是能够看懂这些标准符号的。而UML就是在软件开发方面的一种图标式语言,程序员在进行系统设计的时候,需要画出UML建模图,程序员根据UML建模图进行开发。
那么能够实现UML图的工具有哪些呢?例如:IBMRationalRose、PowerDesigner、StarUML、MSVisio等,我们接下来使用RationalRose工具画一个类出来。请看下图:
通过以上类图,我们可以看到一个学生有学号、姓名、年龄、性别属性,并且有一个考试的方法。其中学号采用整数型,姓名采用字符串类型,年龄采用整数型,性别采用布尔型,考试返回值类型设计为浮点型double。
请大家注意,该小节内容属于了解内容,目前还不需要掌握怎么画这些图,只要知道从名词中发现类,系统开发初期需要进行类的设计,而设计的时候就需要使用UML进行建模,了解几个常见的建模工具就行了。
8.4.3 类的定义(掌握)
以上所讲内容使用java语言完全可以实现,因为java语言是一门完全面向对象的编程语言,当然,使用其他面向对象的编程语言也可以实现。当进行了类的设计之后,接下来就可以根据UML图进行代码的编写了,在代码级别上实现一个类,类怎么定义呢?
以上为类的简单定义,实际上一个完整的类的定义要比以上语法复杂一些,以后再慢慢补充,先从简单的开始。
接下来,根据UML图,使用代码将“学生类”进行实现(只实现属性),代码如下图所示:
以上程序当中no、name、age、sex都是属性,它们都是成员变量中的实例变量,所谓实例变量就是对象级别的变量,这些属性要想访问,必须先创建对象才能访问,不能直接通过类去访问,因为每一个学生的学号都是不一样的。没有学生对象,谈何学号!
8.5 章节小结
通过本章节内容的学习,需要大家理解类是什么,对象是什么,类和对象有什么区别。必须要掌握的是要会定义一个类,代码要会写。
8.6 难点疑惑
本章节的难点主要还是在于概念的理解上,对于一个有Java开发经验的程序员来说也许很简单,但对于一个从未写过程序的人来说,简直是太抽象了,尤其是在开发中如何发现类,如何设计类,在这里我想说的是,大家不必过于急躁,类的发现和设计是需要大家以后不断的在实战中积累的,通过不断的写项目,慢慢的对面向对象的理解会越来越深入。
8.7 章节习题
第一题:设计日期类,每个日期对象都可以描述年月日信息。
第二题:设计男人类,每个男人都有身份证号、姓名、性别、女人。设计女人类,每个女人都有身份证号、姓名、性别、男人。
第三题:设计银行账户类,每个账户都有账号、密码、余额等信息。
第四题:设计微信账号类,每个微信账号都有微信号、手机号、昵称等信息。
8.8 习题答案
第一题答案:
第二题答案:
第三题答案:
第四题答案:
8.9 day13课堂笔记
1、面向过程和面向对象有什么区别?
从语言方面出发:
对于C语言来说,是完全面向过程的。
对于C++语言来说,是一半面向过程,一半是面向对象。(C++是半面向对象的)
对于Java语言来说,是完全面向对象的。
什么是面向过程的开发方式?
面向过程的开发方式主要的特点是:
注重步骤,注重的是实现这个功能的步骤。
第一步干什么
第二步干什么
....
另外面向过程也注重实现功能的因果关系。
因为A所有B
因为B所以C
因为C所以D
.....
面向过程中没有对象的概念。只是实现这个功能的步骤以及因果关系。
面向过程有什么缺点?(耦合度高,扩展力差。)
面向过程最主要是每一步与每一步的因果关系,其中A步骤因果关系到B步骤,A和B联合起来形成一个子模块,子模块和子模块之间又因为因果关系结合在一起,假设其中任何一个因果关系出现问题(错误),此时整个系统的运转都会出现问题。(代码和代码之间的耦合度太高,扩展力太差。)
螺栓螺母拧在一起:耦合度高吗?
这是耦合度低的,因为螺栓螺母可以再拧开。(它们之间是有接口的。)
螺栓螺母拧在一起之后,再用焊条焊接在一起,耦合度高吗?
这个耦合度就很高了。耦合度就是黏连程度。往往耦合度高的扩展力就差。
耦合度高导致扩展力差。(集成显卡:计算机显卡不是独立的,是集成到主板上的)
耦合度低导致扩展力强。(灯泡和灯口关系,螺栓螺母关系)
采用面向过程的方式开发一台计算机会是怎样?
这台计算机将没有任何一个部件,所有的都是融合在一起的。你的这台计算机是一个实心儿的,没有部件的。一体机。假设这台一体机的任何一个“部位”出问题,整个计算机就不能用了,必须扔掉了。(没有对象的概念。)
采用面向对象的方式开发一台计算机会是怎样?
内存条是一个对象,主板是一个对象,CPU是一个对象,硬盘是一个对象。然后这些对象组装在一起,形成一台计算机。假设其中CPU坏了,我们可以将CPU拆下来,换一个新的。
面向过程有什么优点?(快速开发)
对于小型项目(功能),采用面向过程的方式进行开发,效率较高。不需要前期进行对象的提取,模型的建立,采用面向过程方式可以直接开始干活。一上来直接写代码,编写因果关系。从而实现功能。
什么是面向对象的开发方式?
采用面向对象的方式进行开发,更符合人类的思维方式。(面向对象成为主流的原因)
人类就是以“对象”的方式去认识世界的。所以面向对象更容易让我们接受。
面向对象就是将现实世界分割成不同的单元,然后每一个单元都实现成对象,然后给一个环境驱动一下,让各个对象之间协作起来形成一个系统。
对象“张三”
对象“香烟”
对象“打火机”
对象“吸烟的场所”
然后将以上的4个对象组合在一起,就可以模拟一个人的抽烟场景。其中“张三”对象可以更换为“李四”;其中“香烟”也可以更换品牌;其中“打火机”也可以更换;其中“吸烟的场所”也可以更换。
采用面向对象的方式进行开发:耦合度低,扩展力强。
找一个合适的案例。说明一下面向对象和面向过程的区别?
蛋炒饭:
鸡蛋和米饭完全混合在一起。没有独立对象的概念。
假设客户提出新需求:我只想吃蛋炒饭中的米饭,怎么办?
客户提出需求,软件开发者必须满足这个需求,于是开始扩展,这个软件的扩展是一场噩梦。(很难扩展,耦合度太高了。)
盖饭:
老板,来一份:鱼香肉丝盖饭
鱼香肉丝是一道菜,可以看成一个独立的对象。米饭可以看成一个独立的对象。
两个对象准备好之后,只要有一个动作,叫做:“盖”,这样两个对象就组合在一起了。
假设客户提出新需求:我不想吃鱼香肉丝盖饭,想吃西红柿鸡蛋盖饭。这个扩展就很轻松了。直接把“鱼香肉丝”对象换成“西红柿鸡蛋”对象。
目前先听一下,需要三四年的时候才能彻底领悟面向对象。
面向过程主要关注的是:实现步骤以及整个过程。
面向对象主要关注的是:对象A,对象B,对象C,然后对象ABC组合,或者CBA组合.....
2、当我们采用面向对象的方式贯穿整个系统的话,涉及到三个术语:
OOA:面向对象分析(Object-Oriented Analysis)
OOD:面向对象设计(Object-Oriented Design)
OOP:面向对象编程(Object-Oriented Programming)
整个软件开发的过程,都是采用OO进行贯穿的。
实现一个软件的过程:
分析(A) --> 设计(D) --> 编程(P)
在软件公司当中,一般同事与同事之间聊天,有的时候会突然说出来一个英语单词。
这种情况是很常见的。所以一些术语还是要知道的,不然会闹出笑话。
leader 领导/经理/组长
team 团队
PM 项目经理(整个项目的监管人)Project Manager
3、面向对象包括三大特征
封装(Encapsulation)
继承(Inheritance)
多态(Polymorphism)
任何一个面向对象的编程语言都包括这三个特征
例如:
python也有封装 继承 多态。
java也有封装 继承 多态。
注意:java只是面向对象编程语言中的一种。
除了java之外,还有其它很多很多的编程语言也是面向对象的。
以上三个特征的名字先背会,后面一个一个进行学习。
4、类和对象的概念
007-类和对象的理解
面向对象当中最主要“一词”是:对象。
什么是类?
类实际上在现实世界当中是不存在的,是一个抽象的概念。是一个模板。是我们人类大脑进行“思考、总结、抽象”的一个结果。(主要是因为人类的大脑不一般才有了类的概念。)
类本质上是现实世界当中某些事物具有共同特征,将这些共同特征提取出来形成的概念就是一个“类”,“类”就是一个模板。
明星是一个类
什么是对象?
对象是实际存在的个体。(真实存在的个体)
宋小宝就是一个对象
姚明就是一个对象
刘德华就是一个对象
....
宋小宝、姚明、刘德华这3个对象都属于“明星”这个类。
在java语言中,要想得到“对象”,必须先定义“类”,“对象”是通过“类”这个模板创造出来的。
类就是一个模板:类中描述的是所有对象的“共同特征信息”
对象就是通过类创建出的个体。
这几个术语你需要自己能够阐述出来:
类:不存在的,人类大脑思考总结一个模板(这个模板当中描述了共同特征。)
对象:实际存在的个体。
实例:对象还有另一个名字叫做实例。
实例化:通过类这个模板创建对象的过程,叫做:实例化。
抽象:多个对象具有共同特征,进行思考总结抽取共同特征的过程。
类 --【实例化】--> 对象(实例)
对象 --【抽象】--> 类
类是一个模板,是描述共同特征的一个模板,那么共同特征包括什么呢?
潘长江对象:
名字:潘长江
身高:165cm
打篮球:非专业的,自己玩儿呢,无所谓了
学习:考试80分
姚明对象:
名字:姚明
身高:240cm
打篮球:NBA专业球员,打篮球非常棒
学习:考试100分
共同特征包括哪些?
名字、身高都属于名词(状态特征)
打篮球、学习都属于动词(动作特征)
类 = 属性 + 方法
属性来源于:状态
方法来源于:动作
public class 明星类{
//属性-->状态,多见于名词
名字属性;
身高属性;
//方法-->动作,多见于动词
打篮球方法(){
}
学习方法(){
}
}
张三同学、李四同学,他们俩有没有共同特征呢?
有共同特征,就可以抽象一个类模板出来。
可以定义一个学生类(Student)
public class Student {
// 属性
// 姓名
// 性别
// 身高
// 方法
public .... sing(){
}
public .... dance(){
}
public .... study(){
}
....
}
5、思考:“java软件工程师”在开发中起到的一个作用是什么?
我们为什么要做软件开发?
说的大一些是为了人民服务。解决现实生活当中的问题。
软件开发既然是为了解决现实世界当中的问题,那么首先java软件必须能够模拟现实世界。
其实软件是一个虚拟的世界。
这个虚拟的世界需要和现实世界一一对应,这才叫模拟。
006-java程序员是转换桥梁
6、类的定义
6.1、怎么定义一个类,语法格式是什么?
[修饰符列表] class 类名 {
//类体 = 属性 + 方法
// 属性在代码上以“变量”的形式存在(描述状态)
// 方法描述动作/行为
}
注意:修饰符列表可以省略。
6.2、为什么属性是“以”变量的形式存在的?
假设我们要描述一个学生:
学生包括哪些属性:
学号: 110
姓名:"张三"
性别:'男' (true/false)
住址:"深圳宝安区"
答案:是因为属性对应的是“数据”,数据在程序中只能放到变量中。
结论:属性其实就是变量。
变量的分类还记得吗?
变量根据出现位置进行划分:
方法体当中声明的变量:局部变量。
方法体外声明的变量:成员变量。(这里的成员变量就是“属性”)
6.3、请大家观察“学生对象1”和“学生对象2”的共同特征,然后再利用java语言将该“学生类”表述/表达出来。(这里只表达属性,不表达方法.)
7、关于编译的过程
按说应该先编译XueSheng.java,然后再编译XueShengTest.java
但是对于编译器来说,编译XueShengTest.java文件的时候,会自动找XueSheng.class,如果没有,会自动编译XueSheng.java文件,生成XueSheng.class文件。
第一种方式:
javac XueSheng.java
javac XueShengTest.java
第二种方式:
javac XueShengTest.java
第三种方式:
javac *.java
8、在语法级别上是怎么完成对象创建的呢?
类名 变量名 = new 类名();
这样就完成了对象的创建。
9、什么是实例变量?
对象又被称为实例。
实例变量实际上就是:对象级别的变量。
public class 明星类{
double height;
}
身高这个属性所有的明星对象都有,但是每一个对象都有“自己的身高值”。
假设创建10个明星对象,height变量应该有10份。
所以这种变量被称为对象级别的变量。属于实例变量。
实例变量在访问的时候,是不是必须先创建对象?(是)
10、对象和引用的区别?
对象是通过new出来的,在堆内存中存储。
引用是:但凡是变量,并且该变量中保存了内存地址指向了堆内存当中的对象的。
8.9.1 day13代码
/*
1、观察学生对象的共同特征(只观察属性)
有哪些共同特征:
学号:采用int类型
姓名:采用String类型
年龄:采用int类型
性别:采用char或者boolean类型
住址:采用String类型
注意:属性是成员变量。
2、以上是分析总结的结果,可以开始写代码了:
定义XueSheng类,编写成员变量作为属性。
3、变量有一个特点:
必须先声明,再赋值,才能访问。
成员变量可以不手动赋值??????????
XueSheng既是一个类名,同时又是一个“类型名”,属于引用数据类型。
*/
public class XueSheng{ // 这个程序编译之后,会生成XueSheng.class字节码文件。
// 属性
// 学号(成员变量)
int xueHao;
// 姓名
String xingMing;
// 年龄
int nianLing;
// 性别
boolean xingBie;
// 住址
String zhuZhi;
}
/*
对象的创建和使用。
*/
public class XueShengTest{
public static void main(String[] args){
int i = 100;
System.out.println("i = " + i);
// 在这里可以访问XueSheng类吗?
// 当然可以。
/*
创建对象的语法是什么?
目前死记硬背,先记住。后面你就理解了。
new 类名();
类是模板,通过一个类,是可以创建N多个对象的。
new是一个运算符。专门负责对象的创建。
XueSheng s1 = new XueSheng();
和
int i = 100;
解释一下:
i是变量名
int是变量的数据类型
100是具体的数据。
s1是变量名(s1不能叫做对象。s1只是一个变量名字。)
XueSheng是变量s1的数据类型(引用数据类型)
new XueSheng() 这是一个对象。(学生类创建出来的学生对象。)
数据类型包括两种:
基本数据类型:byte short int long float double boolean char
引用数据类型:String、XueSheng.....
java中所有的“类”都属于引用数据类型。
*/
XueSheng s1 = new XueSheng(); // 和 int i = 10;一个道理。
// 再通过该类创建一个全新的对象
XueSheng s2 = new XueSheng();
// 再创建一个呢?
XueSheng xsh = new XueSheng();
// 以上的这个程序就相当于通过XueSheng类实例化了3个XueSheng对象。
// 创建对象的个数没有限制,可以随意。只要有模板类就行。
// 3个对象都属于学生类型。
}
}
9 第九章 对象的创建和使用
9.1 章节目标与知识框架
9.1.1 章节目标
理解构造方法以及重载机制,通过构造方法可以完成对象的创建,并且能够通过引用访问对象的内存,了解Java虚拟机内存管理,能够画出程序执行过程的内存图,并了解空指针异常是如何发生的,以及方法调用时参数是如何传递的。
9.1.2 知识框架
9.2 对象的创建和使用(掌握)
9.2.1 对象的创建
类定义之后,就可以使用类这个“模板”来创造“对象”了,一个类是可以创建多个对象的哦!怎么创建呢,语法是什么?其实语法格式很简单:new类名(),这样就可以完成对象的创建了。俗话说,你想要什么java都可以给你,想要啥你就new啥。请看下面代码:
为了使用对象更加方便,建议使用变量接收一下?例如以下代码:
以上代码最初接触的时候,大家肯定会感觉非常陌生,这也是正常的,Students1=newStudent()实际上和inti=10是类似的,对于inti=10来说,int是一种基本数据类型,i是变量名,10是int类型的字面量。那对于Students1=newStudent()来说,其中Student是一种引用数据类型,s1是变量名,newStudent()执行之后是一个Student类型的对象。
大家要注意了,java语言当中凡是使用class关键字定义的类都属于引用数据类型,类名本身就是这种引用数据类型的类型名。
9.2.2 对象的使用
创建了对象之后怎么去访问这个对象的属性呢,或者说学生对象现在有了,怎么去访问他的学号、姓名、性别、年龄等信息呢。请看以下代码:
运行结果如下图所示:
接下来解释一下以上的输出结果,通过以上的Student类可以创建很多学生对象,假设通过Student类实例化了两个学生对象,那必然会有两个不同的学号,以上程序中并没有给学号赋值,但是获取了到的学号都是0,这是怎么回事呢?这是因为在java语言当中,当实例变量没有手动赋值,在创建对象的时候,也就是说在new的时候,系统会对实例变量默认赋值,它们的默认值请参考下表:
9.3 对象创建和使用的深层次解密
9.3.1 java虚拟机内存管理(理解)
为了更好的理解上面的程序,先来看看java虚拟机是如何管理它的内存的,请看下图:
①程序计数器:
1)概念:可以看做当前线程所执行的字节码的行号指示器。
2)特点:线程私有的内存
②java虚拟机栈(重点):
1)概念:描述的是java方法执行的内存模型。(每个方法在执行的时候会创建一个栈帧,用于存储局部变量表,操作数栈,动态链接,方法出口等信息。每个方法从调用直至完成的过程,就对应一个栈帧从入栈到出栈的过程。)
2)特点:线程私有,生命周期和线程相同。这个区域会出现两种异常:StackOverflowError异常:若线程请求的深度大于虚拟机所允许的深度。OutOfMemoryError异常:若虚拟机可以动态扩展,如果扩展是无法申请到足够的内存。
③本地方法栈:
1)概念:它与虚拟机栈所发挥的作用是相似的,区别是java虚拟机栈为执行java方法服务,而本地方法栈是为本地方法服务。
2)特点:线程私有,也会抛出两类异常:StackOverflowError和OutOfMemoryError。
④java堆(重点):
1)概念:是被所有线程共享的一块区域,在虚拟机启动时创建。
2)特点:线程共享,存放的是对象实例(所有的对象实例和数组),GC管理的主要区域。可以处于物理上不连续的内存空间。
⑤方法区(重点):
1)概念:存储已被虚拟机加载的类信息、常量、静态变量,即时编译器编译后的代码等数据。
2)特点:线程共享的区域,抛出异常OutOfMemory异常:当方法区无法满足内存分配需求的时候。
以上所描述内容,有看得懂的,也有看不懂的,例如:线程、本地方法等,这个需要大家在学习后面内容之后,返回来再看一看,那个时候你就全部明白了。针对于目前来说,大家必须要知道java虚拟机有三块主要的内存空间,分别是“虚拟机栈(后面简称栈)”、“方法区”、“堆区”,方法区存储类的信息,栈中存储方法执行时的栈帧以及局部变量,堆区中主要存储new出来的对象,以及对象内部的实例变量。其中垃圾回收器主要针对的是堆内存,方法区中最先有数据,因为程序执行之前会先进行类加载。栈内存活动最频繁,因为方法不断的执行并结束,不断的进行压栈弹栈操作。将目前阶段需要掌握的内存空间使用一张简单的图表示出来,这个图是大家需要掌握的:
大概了解了java虚拟机内存分配之后,来看看以下代码在执行过程中,内存是如何变化的:
以上代码在执行过程中内存的变化如下图所示:
注意:上图所描述内存图有些地方为了帮助大家更好的理解,有些位置画的不是很精确,随着后面内容的学习我们再进一步修改,目前上图已经够大家用了。
上图中i变量和s1变量都是局部变量,都在栈内存当中,只不过i变量是基本数据类型int,而s1变量是引用数据类型Student。
上图中堆区当中的称为“对象”,该“对象”内部no、name、age、sex都是实例变量/属性,这些变量在new对象的时候初始化,如果没有手动赋值,系统会赋默认值。
上图堆区中“对象”创建完成之后,该对象在堆区当中的内存地址是:0x1111,程序中的“=”将0x1111这个堆内存地址赋值给s1变量,也就是说s1变量保存了堆内存对象的内存地址,我们对于这种变量有一种特殊的称呼,叫做“引用”。也就是说对于Students1=newStudent()代码来说,s1不是对象,是一个引用,对象实际上是在堆区当中,s1变量持有这个对象的内存地址。
java中没有指针的概念(指针是C语言当中的机制),所以java程序员没有权利直接操作堆内存,只能通过“引用”去访问堆内存中的对象,例如:s1.no、s1.name、s1.sex、s1.age。访问一个对象的内存,其实就是访问该对象的实例变量,而访问实例变量通常包括两种形式,要么就是读取数据,要么就是修改数据,例如:System.out.println(s1.no)这就是读取数据,s1.no=100这就是修改数据。请看以下代码:
运行结果如下所示:
执行了以上程序之后,堆内存对象的实例变量发生了变化,如下图所示:
如果基于以上的代码再创建一个对象,内存图会是怎么的呢?先看代码:
JVM内存结构图如下所示:
通过上图的学习,可以看出假设new出100个学生对象,会有100个no,100个age...是这样吧。
通过以上内容的学习,需要每位同学掌握:局部变量存储在哪里?实例变量存储在哪里?实例变量在什么时候初始化?对象和引用有什么区别?在java中怎么访问堆内存当中的对象?这些你都掌握了吗。
9.3.2 构造方法Constructor(掌握)
什么是构造方法?构造方法怎么定义?构造方法怎么调用?构造方法有什么作用?构造方法可以重载吗?接下来学习一下。
构造方法是类中特殊的方法,通过调用构造方法来完成对象的创建,以及对象属性的初始化操作。
构造方法怎么定义,请看以下的语法格式:
[修饰符列表]构造方法名(形式参数列表){
构造方法体;
}
①构造方法名和类名一致。
②构造方法用来创建对象,以及完成属性初始化操作。
③构造方法返回值类型不需要写,写上就报错,包括void也不能写。
④构造方法的返回值类型实际上是当前类的类型。
⑤一个类中可以定义多个构造方法,这些构造方法构成方法重载。
怎么调用构造方法呢,语法格式是:new构造方法名(实际参数列表);接下来,看以下代码:
以上程序运行结果如下图所示:
以上程序的输出结果中虽然第一行看不懂,但起码说明程序是能够正常执行的。我们看到以上Date类当中的代码,并没有发现任何构造方法,为什么可以调用呢?接下来我们来测试一下,把构造方法显示的定义出来:
运行结果如下图所示:
通过以上程序执行结果确实看到了“newDate()”确实调用了Date类当中的无参数构造方法。再看以下程序:
编译报错了,错误信息如下图所示:
通过以上的测试,得出这样一个结论(这是java中语法的规定,记住就行):当一个类没有显示的定义任何构造方法的时候,系统默认提供无参数构造方法,当显示的定义构造方法之后,系统则不再提供无参数构造方法。无参数构造方法又叫做缺省构造器,或者默认构造方法。一般在开发中为了方便编程,建议程序员手动的将无参数构造方法写上,因为不写无参数构造方法的时候,这个默认的构造方法很有可能就不存在了,另外也是因为无参数构造方法使用的频率较高。例如以下代码:
运行结果如下图所示:
通过以上的测试可以看出一个类当中可以定义多个构造方法,构造方法是支持重载机制的,具体调用哪个构造方法,那要看调用的时候传递的实际参数列表符合哪个构造方法了。构造方法虽然在返回值类型方面不写任何类型,但它执行结束之后实际上会返回该对象在堆内存当中的内存地址,这个时候可以定义变量接收对象的内存地址,这个变量就是之前所学的“引用”,请看以下代码:
运行结果如下图所示:
以上程序中time1,time2,time3,time4都是引用,输出这些引用的结果是“Date@xxxxx”,对于这个结果目前可以把它等同看做是对象的内存地址(严格来说不是真实的对象内存地址)。通过这个引用就可以访问对象的内存了,例如以下代码:
运行结果如下图所示:
为什么无论通过哪个构造方法创建Date对象,最终的结果都是“0年0月0日”呢?这是因为所有的构造方法在执行过程中没有给对象的属性手动赋值,系统则自动赋默认值,实际上大部分情况下我们需要在构造方法中手动的给属性赋值,这本来就是构造方法的主要的职责,要不然重载多次构造方法就没有意义了,以上的代码应该这样写,请看:
运行结果如下图所示:
为什么第一个日期输出的是“0年0月0日”?这是因为调用的是无参数构造方法创建的第一个日期对象,在无参数构造方法中没有给属性赋值,则系统赋默认值,所以年月日都是0。那为什么第二个日期输出的是“2008年0月0日”呢?这是因为调用的是“public Date(intyear1){year = year1;}”构造方法创建的第二个日期对象,在这个构造方法当中只是显示的给Date对象的year属性赋值,month和day仍然是系统赋默认值,所以month和day都是0。第三个日期对象是“2008年8月8日”,这是因为在这个对应的构造方法中显示的为year,month,day三个属性都赋值了。在编写以上构造方法的时候需要注意变量名的问题,请看下图:
通过以上内容的学习得知,构造方法的作用是专门用来创建对象同时给属性赋值的,它的语法很简单,比普通方法还要简单,因为构造方法名和类名一致,还不需要写返回值类型,使用new就可以调用了。在一个类当中可以同时定义多个构造方法,它们之间构成重载关系。这样就做到了在java中你想要什么就new什么,每一次new都会在堆内存中创建对象,并且对象内部的实例变量被初始化了。
一定要注意,实例变量没有手动赋值的时候系统会默认赋值,但不管是手动赋值还是系统赋默认值,都是在构造方法执行的时候才会进行赋值操作,类加载的时候并不会初始化实例变量的空间,那是因为实例变量是对象级别的变量,没有对象,哪来实例变量,这也是为什么实例变量不能采用“类名”去访问的原因。
编译报错了:
9.3.3 空指针异常(掌握)
当一个空的引用去访问实例变量会出现什么问题吗?请看以下代码:
运行结果如下图所示:
java.lang.NullPointerException被称为空指针异常,在java编程当中属于很常见的异常,接下来研究一下以上程序执行过程的内存图是如何变化的。请看下图:
以上程序语法正确,编译通过,因为程序在编译阶段检测出“引用ball”属于Balloon类型,在Balloon类中有color属性,所以编译器允许通过ball引用去访问color属性,例如以上代码的ball.color。但是程序在运行阶段会通过ball引用查找堆内存当中的对象,因为color是实例变量,该变量存储在java对象内部,当ball=null执行之后表示“引用ball”不再保存java对象的内存地址,换句话说通过ball引用已经无法找到堆内存当中的java对象了,对于程序来说这个时候就没有办法正常访问了,这种情况下就会发生空指针异常。就好比一个小孩儿放风筝,通过拽线来操控风筝,结果线断了,再拽风筝线的时候,已经无法再操控风筝了,这对于小孩儿来说是一种异常。而java程序中把这种异常叫做NullPointerException。
总之,当一个“空的引用”去访问“对象相关/实例相关”数据的时候,此时一定会发生空指针异常。
9.3.4 当实例变量是一个引用(理解)
在以上内容学习的过程当中,其实大家已经接触过实例变量是引用的情况,不知道吧!例如在Student学生类当中有一个属性“Stringname;”,这个属性/实例变量name描述的是学生的姓名,name变量的数据类型是String类型,String类型不属于基本数据类型的范畴,也就是说String类型属于引用数据类型,换句话说String类型应该对应一个String.class文件才对,String是一个类,和我们自己定义的类没什么区别,是这样吗?一起来看看JDK的java源代码:
通过查看源代码得知,其实String是一个class,和我们定义的类没有区别,它和基本数据类型还是不一样的(inti=10,i变量是基本数据类型,i变量中存储的就是10),也就是说Stringname=“zhangsan”,实际上name变量中存储的并不是”zhangsan”这个字符串,因为name是一个引用,那name中必然存储的是”zhangsan”字符串对象的内存地址。因为之前我们说过引用的概念,什么是引用:引用就是一个变量,只不过该变量中存储的是java对象的内存地址。也就是说,以前所讲内容中使用内存图描述字符串的时候都是有偏差的。我们来修正一下,请看代码:
以上程序的运行结果请看下图:
将以上内存结构图画出来,请看:
通过上图可以看到,Student对象当中的name这个“属性/实例变量”是一个引用,保存的不是”zhangsan”字符串,而是字符串对象的内存地址。(按照实际来说,字符串”zhangsan”是在方法区的字符串常量池当中,这个后期再继续进行修正)。接下来,我们再来看看当属性是其他类型引用的时候,请看代码:
运行结果如下图所示:
以上程序main方法的内存结构图如下所示:
通过以上内容的学习大家掌握当对象的属性是一个引用的时候内存图是怎样的了吗?其实只要记住一点,任何“引用”当中存储一定是对象的内存地址,“引用”不一定只是以局部变量的形式存在,例如以上程序,其中Vip类当中的birth属性就是一个“引用”,它是一个实例变量。当一个对象的属性是引用的时候应该如何访问这个引用所指向的对象呢?这里其实有一个规律,大家记住就行:类当中有什么就可以“.”什么,例如:Vip类中有birth属性,那么就可以v.birth,那么v.birth是Date类型,Date类当中有year属性,那么就可以v.birth.year,你懂了吗?
9.3.5 方法调用时参数的传递问题(理解)
方法在调用的时候参数是如何传递的呢?其实在调用的时候参数传递给方法,这个过程就是赋值的过程,参数传递和“赋值规则”完全相同,只不过参数传递在代码上看不见“=”运算符。我们先来深入的研究一下“赋值规则”吧!
在以上程序当中,有两个疑问,第一个:a赋值给b,a把什么给了b?第二个:bird1赋值给bird2,bird1把什么给了bird2?
其实a,b,bird1,bird2就是4个普通的变量,唯一的区别只是a和b都是基本数据类型的变量,bird1和bird2都是引用数据类型的变量(或者说都是引用),a变量中保存的那个“值”是10,bird1变量中保存的那个“值”是0x8888(java对象内存地址),本质上来说10和0x8888都是“值”,只不过一个“值”是整数数字,另一个“值”是java对象的内存地址,大家不要把内存地址特殊化,它也是一个普通的值。
那么“赋值”是什么意思呢,顾名思义,赋值就是把“值”赋上去。a赋值给b,本质上不是把a给了b,而是把a变量中保存的“值10”复制了一份给了b。bird1赋值给bird2本质上不是把bird1给了bird2,而是把bird1变量中保存的“值0x8888”复制了一份给了bird2。请看以下内存图的变化:
通过以上内存图我们可以看出“赋值”运算的时候实际上和变量的数据类型无关,无论是基本数据类型还是引用数据类型,一律都是将变量中保存的“值”复制一份,然后将复制的这个“值”赋上去。他们的区别在于,如果是基本数据类型则和堆内存当中的对象无关,如果是引用数据类型由于传递的这个值是java对象的内存地址,所以会导致两个引用指向同一个堆内存中的java对象,通过任何一个引用去访问堆内存当中的对象,此对象内存都会受到影响。我们来验证一下,让a++,a应该变成了11,但是b不会变,让bird1.name=“波利”,然后输出bird2.name的结果肯定也是”波利”,请看代码以及运行结果:
运行结果如下图所示:
上面我就提到了,方法调用时参数的传递和赋值运算符的原理完全相同,那么请大家根据以上内容所学,画出以下程序的内存图,以及推算它们的执行结果:
9.4 章节小结
通过本章节内容的学习,需要大家掌握的是一个类定义好之后,怎么创建对象,对象创建之后怎么去使用这个对象,代码要会写。另外还需要掌握构造方法怎么定义、怎么调用。需要理解构造方法在开发中的作用。
在本章节内容中大家尤其要对Java虚拟机内存管理这块要有深入的理解。要知道Java虚拟机中的栈和堆各自存储什么,要能够画出程序执行过程中内存的变化。要知道空指针异常是如何发生的,为什么会出现空指针。
除以上所描述之外,大家还需要掌握方法调用时参数是如何传递的,一定要弄明白这个知识点,在以后的开发中会频繁的进行方法的调用,而方法调用时需要传递参数,参数传递时内存是如何变化的,弄明白这些有助于你对程序运行结果的把控。
9.5 难点疑惑
对于初学者来说,本章节内容是比较艰难的一个章节,主要是因为涉及到程序执行过程中内存的变化。本章节中要想搞明白所有的难点,只需要搞定一点就行,那就是亲手画出每个程序执行时的内存图。
在这里我要提醒大家的有两点:第一先要记住Java虚拟机内存管理这块有哪些内存空间,每一个空间中都存储什么;第二代码要遵循一行一行逐行执行,每执行一行则需要在内存方面发生一些变化。只要大家能把握以上两点,对于空指针异常、实例变量为引用类型、方法调用时参数传递问题等迎刃而解。
9.6 章节习题
第一题:定义丈夫类Husband和妻子类Wife,丈夫类的属性包括:身份证号,姓名,出生日期,妻子。妻子类的属性包括:身份证号,姓名,出生日期,丈夫。分别给这两个类提供构造方法(无参数构造方法和有参数构造方法都要提供),编写测试程序,创建丈夫对象,然后再创建妻子对象,丈夫对象关联妻子对象,妻子对象关联丈夫对象,要求能够输出这个“丈夫对象”的妻子的名字,或者能够输出这个“妻子对象”的丈夫的名字。要求能够画出程序执行过程的内存图。并且要求在程序中演示出空指针异常的效果。
9.7 习题答案
第一题答案:
执行结果如下图所示:
9.8 day14课堂笔记
上接day13
...
8、在语法级别上是怎么完成对象创建的呢?
类名 变量名 = new 类名();
这样就完成了对象的创建。
9、什么是实例变量?
对象又被称为实例。
实例变量实际上就是:对象级别的变量。
public class 明星类{
double height;
}
身高这个属性所有的明星对象都有,但是每一个对象都有“自己的身高值”。
假设创建10个明星对象,height变量应该有10份。
所以这种变量被称为对象级别的变量。属于实例变量。
实例变量在访问的时候,是不是必须先创建对象?
10、对象和引用的区别?
对象是通过new出来的,在堆内存中存储。
引用是:但凡是变量,并且该变量中保存了内存地址指向了堆内存当中的对象的。
009-对象和引用
1、画内存图注意事项:
第一:大家在内存图上不要体现出代码。内存上应该主要体现“数据”。
第二:大家画图的时候,图上的图形应该有先后顺序,先画什么,再画什么,必须是有顺序的,而不是想起来这个画这个,想起来那个画那个。
程序代码是有执行顺序的,程序执行到哪里你就画哪里就行了。
2、为什么要画内存图(非常重要)?
第一:有了内存图,程序不运行,我也知道结果。(可以推算出结果)
第二:有了内存图,有助于你调试程序。
画内存图是对Java运行机制的一种理解。不知道运行机制,以后复杂的程序出现错误之后你是不会调试的,调不明白。
3、程序在什么情况下会出现空指针异常呢?
空引用 访问 "对象相关"的数据时,会出现空指针异常。
垃圾回收器主要针对堆内存。
4、方法在调用的时候参数是如何传递的?
实际上,在java语言中,方法调用时参数传递,和类型无关,都是将变量中保存的那个“值”传过去,这个“值”可能是一个数字100,也可能是一个java对象的内存地址:0x1234
记住这句话:不管是哪一种数据类型的传递,都是将“变量中保存的那个值复制一份传递过去。”
5、构造方法。
5.1、当一个类中没有提供任何构造方法,系统默认提供一个无参数的构造方法。
这个无参数的构造方法叫做缺省构造器。
5.2、当一个类中手动的提供了构造方法,那么系统将不再默认提供无参数构造方法。
建议将无参数构造方法手动的写出来,这样一定不会出问题。
5.3、无参数构造方法和有参数的构造方法都可以调用。
Student x = new Student();
Student y = new Student(123);
5.4、构造方法支持方法重载吗?
构造方法是支持方法重载的。
在一个类当中构造方法可以有多个。
并且所有的构造方法名字都是一样的。
方法重载特点:
在同一个类中,方法名相同,参数列表不同。
5.5、对于实例变量来说,只要你在构造方法中没有手动给它赋值,统一都会默认赋值。默认赋系统值。
构造方法需要掌握的知识点:
1.构造方法有什么作用?
2.构造方法怎么定义,语法是什么?
3.构造方法怎么调用,使用哪个运算符?
4.什么是缺省构造器?
5.怎么防止缺省构造器丢失?
6.实例变量在类加载是初始化吗?实例变量在什么时候初始化?
6、封装
6.1、面向对象的三大特征:
封装
继承
多态
有了封装,才有继承,有了继承,才能说多态。
6.2、面向对象的首要特征:封装 。什么是封装?有什么用?
现实生活中有很多现实的例子都是封装的,例如:
手机,电视机,笔记本电脑,照相机,这些都是外部有一个坚硬的壳儿。
封装起来,保护内部的部件。保证内部的部件是安全的。另外封装了之后,对于我们使用者来说,我们是看不见内部的复杂结构的,我们也不需要关心内部有多么复杂,我们只需要操作外部壳儿上的几个按钮就可以完成操作。
那么封装,你觉得有什么用呢?
封装的作用有两个:
第一个作用:保证内部结构的安全。
第二个作用:屏蔽复杂,暴露简单。
在代码级别上,封装有什么用?
一个类体当中的数据,假设封装之后,对于代码的调用人员来说,不需要关心代码的复杂实现,只需要通过一个简单的入口就可以访问了。
另外,类体中安全级别较高的数据封装起来,外部人员不能随意访问,来保证数据的安全性。
6.3、怎么进行封装,代码怎么实现?
第一步:属性私有化(使用private关键字进行修饰。)
第二步:对外提供简单的操作入口。
9.8.1 day14代码
/*
学生类
学号:int
姓名:String
年龄:int
性别:boolean
住址:String
变量必须先声明,再赋值才能访问。
注意:对于成员变量来说,没有手动赋值时,系统默认赋值。
赋的值都是默认值,那么默认值是什么?
类型 默认值
---------------------
byte 0
short 0
int 0
long 0L
float 0.0F
double 0.0
boolean false
char \u0000
引用数据类型 null
null是一个java关键字,全部小写,表示空。是引用类型的默认值。
分析:对于成员变量来说,是不是应该一个对象有一份。
李四有李四的学号
张三有张三的学号
李四和张三的学号不一样。所以应该有两块不同的内存空间。
*/
public class Student{
// 属性(描述状态),在java程序中以“成员变量”的形式存在。
// 学号
// 一个对象一份。
int no; // 这种成员变量又被称为“实例变量”。
// 姓名
String name;
// 年龄
int age;
// 性别
boolean sex;
// 住址
String addr;
}
/*
对象的创建和使用。
*/
public class StudentTest{
public static void main(String[] args){
//局部变量
//错误: 可能尚未初始化变量k
/*
int k;
System.out.println(k);
*/
//访问学生姓名可以直接通过类名吗?
// 学生姓名是一个实例变量。实例变量是对象级别的变量。
// 是不是应该先有对象才能说姓名的事儿。
// 不能通过“类名”来直接访问“实例变量”。
//System.out.println(Student.name);
// i属于局部变量吗?当然是。
// 局部变量存储在栈内存当中。(栈主要存储局部变量。)
//int i = 100;
// 创建学生对象1
// s1属于局部变量吗?当然是。
// s1这个局部变量叫做引用
Student s1 = new Student();
// 怎么访问实例变量?
// 语法:引用.实例变量名
System.out.println(s1.no);
System.out.println(s1.name);
System.out.println(s1.age);
System.out.println(s1.sex);
System.out.println(s1.addr);
System.out.println("-----------------------------");
// 创建学生对象2
// s2也是局部变量。
// s2也叫做引用。
Student s2 = new Student();
System.out.println(s2.no);
System.out.println(s2.name);
System.out.println(s2.age);
System.out.println(s2.sex);
System.out.println(s2.addr);
// 程序执行到此处我可以修改s1这个学生的学号吗?
// 通过“=”赋值的方式将内存中实例变量的值修改一下。
s1.no = 110;
s1.name = "张三";
s1.age = 20;
s1.sex = true;
s1.addr = "深圳宝安区";
System.out.println("学号=" + s1.no);
System.out.println("姓名=" + s1.name);
System.out.println("年龄=" + s1.age);
System.out.println("性别=" + s1.sex);
System.out.println("住址=" + s1.addr);
// 再次赋值
s1.addr = "北京大兴区";
System.out.println("住址:" + s1.addr);
}
public static void method(){
// i s1 s2都是main方法中的局部变量,在这里是无法访问的。
/*
System.out.println(i);
System.out.println(s1);
System.out.println(s2);
*/
}
}
public class 学生{
int 学号;
String 姓名;
// 入口
public static void main(String[] args){
学生 张三 = new 学生();
System.out.println(张三.学号); // 0
System.out.println(张三.姓名); // null
张三.学号 = 1111;
张三.姓名 = "张三";
System.out.println(张三.学号);
System.out.println(张三.姓名);
}
}
// 和刚才的程序一样,只不过标识符采用了中文。
// 标识符可以有中文。
public class 学生测试{
// 程序入口
/*
public static void main(String[] args){
学生 张三 = new 学生();
System.out.println(张三.学号); // 0
System.out.println(张三.姓名); // null
张三.学号 = 1111;
张三.姓名 = "张三";
System.out.println(张三.学号);
System.out.println(张三.姓名);
}
*/
}
/*
User类:用户类
*/
public class User{
// 用户id
// 访问id不能这样:User.id (这是错误的,实例变量不能用类名访问。)
// id的访问必须先造对象,然后对象有了,才能访问对象的id
int id; //成员变量,实例变量(对象变量,一个对象一份。)
// 用户名
String username; // 成员变量可以不手动赋值,系统赋默认值。
// 密码
String password;
}
/*
class 人类{
// 成员变量,实例变量,对象级别变量,先造对象才能访问。
double 身高;
}
*/
// byte short int long float double boolean char :这些默认值偏向0,false也是0
// 引用类型:null
// 第一步:类加载
// 第二步:调用UserTest类的main方法(方法调用要压栈。)
public class UserTest{
// 方法体外声明的变量叫做成员变量。
//User u1; //成员变量。(实例变量)
public static void main(String[] args){
//int i = 100;
// 方法体当中声明的变量叫做局部变量
User u1 = new User();
// 实例变量怎么访问(属性怎么访问)?
// 语法是:“引用.属性名”
System.out.println(u1.id); //0
System.out.println(u1.username); //null
System.out.println(u1.password); //null
u1.id = 11111;
u1.username = "zhangsan";
u1.password = "123";
System.out.println(u1.id);
System.out.println(u1.username);
System.out.println(u1.password);
User u2 = new User();
u2.id = 22222;
u2.username = "lisi";
u2.password = "456";
System.out.println(u2.id);
System.out.println(u2.username);
System.out.println(u2.password);
}
}
011-UserTest程序执行内存图
难度较大
// 住址类
public class Address{
// 一个家庭住址有3个属性。
// 城市
String city; // 实例变量
// 街道
String street;
// 邮编
String zipcode;
}
public class User{
// 类=属性+方法
// 以下3个都是属性,都是实例变量。(对象变量。)
// 用户id
// int是一种基本数据类型
int id; // 实例变量
// 用户名
// String是一种引用数据类型
String username; // 实例变量
// 家庭住址
// Address是一种引用数据类型
// addr是成员变量并且还是一个实例变量
// addr是否是一个引用呢?是。addr是一个引用。
Address addr;
}
// 实例变量都存储在哪里?
// 实例变量都在堆内存的对象内部。
// 方法体外,类体内定义的变量叫做:成员变量。
/*
到目前为止,如果什么也没听懂,怎么写代码?
记住一个知识点就行,可以后期慢慢学习画图。
记住一句话:里面有什么就能“点”什么。
所有的实例变量(属性)都是通过“引用.”来访问的。
引用和对象怎么区分?
“引用”是啥?是存储对象内存地址的一个变量。
“对象”是啥?堆里new出来的。
通俗一点:
只要这个变量中保存的是一个对象的内存地址,那么这个变量就叫做“引用”。
思考:
引用一定是局部变量吗?
不一定。
*/
public class Test{
public static void main(String[] args){
//报错了。id是实例变量,必须先创建对象,通过“引用.”的方式访问。
/*
User u = new User();
u是引用。
*/
//System.out.println(User.id);
/*
int i = 100;
int j = i; // 原理:会将i中保存的100复制一份,传给j变量。
*/
// 家庭住址对象
Address a = new Address();
a.city = "北京";
a.street = "大兴区";
a.zipcode = "121221";
// 用户对象
User u = new User();
System.out.println(u.id); // 0
System.out.println(u.username); // null
System.out.println(u.addr); // null
u.id = 11111;
u.username = "zhangsan";
u.addr = a;
// 思考一个问题:
// 我想直到zhangsan他是哪个城市的,代码应该怎么写?
System.out.println(u.username + "是"+u.addr.city+"城市的!");
// u.addr.city 这行代码可否拆分呢?u.addr.city 节省变量。
// 拆分成以下代码和以上效果完全相同,原理完全相同,不同的是以下代码多了两个变量。
Address ad = u.addr;
String zhuZhi = ad.city;
System.out.println(zhuZhi);
//-----------------------是否理解以下代码---------------------------
int x = 100;
// = 代表赋值运算,“赋值”中有一个“值”
// x变量中的值是100. 将100复制一份给y
// 表示:将x变量中保存的值100复制一份给y
int y = x;
//-----------------------是否理解以下代码---------------------------
Address k = new Address(); // Address k = 0x1111;
Address m = k; // 这里表示将k变量中保存的0x1111复制了一份传给了m变量。
}
}
012-难度较大-内存图
// 把这个内存图画出来。一定要按照程序的执行顺序一步一步画。
public class T{
A o1; // 成员变量中的实例变量。必须先创建对象,通过“引用”来访问。
public static void main(String[] args){
D d = new D();
C c = new C();
B b = new B();
A a = new A();
T t = new T();
//这里不写代码会出现NullPointerException异常。(空指针异常。)
c.o4 = d;
b.o3 = c;
a.o2 = b;
t.o1 = a;
// 编写代码通过t来访问d中的i
//System.out.println(T.a); //错误的。
System.out.println(t.o1.o2.o3.o4.i);
}
}
class A{
B o2;
}
class B{
C o3;
}
class C{
D o4;
}
class D{
int i;
}
空指针异常
/*
空指针异常。(NullPointerException)
关于垃圾回收器:GC
在java语言中,垃圾回收器主要针对的是堆内存。
当一个java对象没有任何引用指向该对象的时候,
GC会考虑将该垃圾数据释放回收掉。
出现空指针异常的前提条件是?
"空引用"访问实例【对象相关】相关的数据时,都会出现空指针异常。
*/
public class NullPointerTest{
public static void main(String[] args){
// 创建客户对象
Customer c = new Customer();
// 访问这个客户的id
System.out.println(c.id); // 0
// 重新给id赋值
c.id = 9521; // 终身代号
System.out.println("客户的id是=" + c.id);
c = null;
// NullPointerException
// 编译器没问题,因为编译器只检查语法,编译器发现c是Customer类型,
// Customer类型中有id属性,所以可以:c.id。语法过了。
// 但是运行的时候需要对象的存在,但是对象没了,尴尬了,就只能出现一个异常。
System.out.println(c.id);
}
}
class Customer{
// 客户id
int id; // 成员变量中的实例变量,应该先创建对象,然后通过“引用.”的方式访问。
}
010-空指针异常是怎么发生的
方法调用时参数的传递问题
// 分析程序的输出结果。
// java中规定:参数传递的时候,和类型无关,不管是基本数据类型还是引用数据类型
// 统一都是将盒子中保存的那个“值”复制一份,传递下去。
// java中只有一个规定:参数传递的时候,一定是将“盒子”中的东西复制一份传递过去。
// 内存地址也是值,也是盒子中保存的一个东西。
public class Test1{
public static void main(String[] args){
int x = 100;
int y = x; // x赋值给y,是怎么传递的?将x变量中保存的100这个值复制一份传给y
// 局部变量,域是main
int i = 10;
// 将i变量中保存的10复制一份,传给add方法。
add(i);
System.out.println("main ---> " + i); //10
}
/*
public static void add(int i){ // i是局部变量,域是add
i++;
System.out.println("add ----> " + i); //11
}
*/
public static void add(int k){
k++;
System.out.println("add ----> " + k);
}
}
013-参数传递1
/*
java中关于方法调用时参数传递实际上只有一个规则:
不管你是基本数据类型,还是引用数据类型,实际上在传递的时候都是
将变量中保存的那个“值”复制一份,传过去。
int x = 1;
int y = x; 把x中保存1复制一份传给y
x和y都是两个局部变量。
Person p1 = 0x1234;
Person p2 = p1; 把p1中保存的0x1234复制一份传给p2
p1和p2都是两个局部变量。
你和你媳妇,都有你家大门上的钥匙,钥匙是两把。
但是都可以打开你家的大门。
*/
public class Test2{
public static void main(String[] args){
Person p = new Person();
p.age = 10;
add(p);
System.out.println("main--->" + p.age); //11
}
// 方法的参数可以是基本数据类型,也可以是引用数据类型,只要是合法的数据类型就行。
public static void add(Person p){ // p是add方法的局部变量。
p.age++;
System.out.println("add--->" + p.age); //11
}
}
class Person{
// 年龄属性,成员变量中的实例变量。
int age;
}
014-参数传递2
构造方法
/*
构造方法
1、什么是构造方法,有什么用?
构造方法是一个比较特殊的方法,通过构造方法可以完成对象的创建,
以及实例变量的初始化。换句话说:构造方法是用来创建对象,并且
同时给对象的属性赋值。(注意:实例变量没有手动赋值的时候,系统
会赋默认值。)
2、重点(需要记忆):当一个类没有提供任何构造方法,系统会默认提供
一个无参数的构造方法。(而这个构造方法被称为缺省构造器。)
3、调用构造方法怎么调用呢?
使用哪个运算符呢?
使用new运算符来调用构造方法。
语法格式:
new 构造方法名(实际参数列表);
4、构造方法的语法结构是?
[修饰符列表] 构造方法名(形式参数列表){
构造方法体;
通常在构造方法体当中给属性赋值,完成属性的初始化。
}
注意:
第一:修饰符列表目前统一写:public。千万不要写public static。
第二:构造方法名和类名必须一致。
第三:构造方法不需要指定返回值类型,也不能写void,写上void
表示普通方法,就不是构造方法了。
普通方法的语法结构是?
[修饰符列表] 返回值类型 方法名(形式参数列表){
方法体;
}
*/
public class ConstructorTest01{
public static void main(String[] args){
// 调用Student类的无参数构造方法
new Student();
// 调用普通方法
ConstructorTest01.doSome();
doSome();
// 创建Student类型的对象
Student s1 = new Student();
// 输出“引用”
//只要输出结果不是null,说明这个对象一定是创建完成了。
// 此处的输出结果大家目前是看不懂的,后期再说。
System.out.println(s1); //Student@54bedef2
// 这是调用另一个有参数的构造方法。
Student s3 = new Student(100);
System.out.println(s3); //Student@5caf905d
}
public static void doSome(){
System.out.println("do some!!!!");
}
}
public class Student{
// 学号
int no;
// 姓名
String name;
// 年龄
int age;
// 当前的Student这个类当中并没有定义任何构造方法。
// 但是系统实际上会自动给Student类提供一个无参数的构造方法。
// 将无参数的构造方法(缺省构造器)写出来
public Student(){
System.out.println("无参数的构造方法执行了!");
}
// 定义一个有参数的构造方法
public Student(int i){
}
/*
编译器检测到该方法名“Studen”,发现这个名字和类名不一致,
编译器会认为该方法是一个普通方法,普通方法应该有返回值
但是没有写返回值类型,所以报错了。
错误: 方法声明无效; 需要返回类型
*/
/*
public Studen(String name){
}
*/
// 第一种修改方式
//public void Studen(String name){}
// 第二种修改方式
public Student(String name){
}
}
/*
1、id,name,age都有默认值对吗?
对。
2、id的默认值是:0
name的默认值是:null
age的默认值是:0
3、思考:实例变量没有手动赋值的时候,实际上系统会默认赋值,
那么这个默认赋值操作是在什么时间进行的?
是在类加载的时候给这些实例变量赋值吗?
不是,实例变量是在构造方法执行的过程中完成初始化的,完成赋值的。
*/
public class User{
// 3个属性,3个实例变量【对象变量】
// 用户id
int id; //System.out.println(User.id);错误的。需要先new对象,只有对象有了才能谈id
// 用户名
String name;
// 年龄
int age;
// 手动定义有参数的构造方法,无参数构造方法将消失。
public User(int a){
}
public User(){
//这里实际上有三行代码你看不见。
// 无参数构造方法体当中虽然什么代码都没写,
// 但是实际上是在这个方法体里面进行的实例变量默认值初始化
/*
id = 0;
name = null;
age = 0;
*/
// 这就表示不再采用系统默认值,手动赋值了。
id = 111;
name = "lisi";
age = 30;
}
}
/*
1、构造方法对应的英语单词:Constructor【构造器】
2、构造方法作用:
创建对象,并且创建对象的过程中给属性赋值(初始化。)
*/
public class ConstructorTest02{
public static void main(String[] args){
User u = new User();
System.out.println(u.id); //111
System.out.println(u.name); //lisi
System.out.println(u.age); //30
User u2 = new User(1111111);
System.out.println(u2.id); //0
System.out.println(u2.name); // null
System.out.println(u2.age); // 0
}
}
public class Vip{
// 会员号
long no;
// 会员姓名
String name;
// 生日
String birth;
// 性别
boolean sex;
//无参数构造方法
public Vip(){
}
//有参数构造方法
public Vip(long huiYuanHao, String xingMing){
// 给实例变量赋值【初始化实例变量,初始化属性】
no = huiYuanHao;
name = xingMing;
// 实际上这里还有两行代码(没有手动赋值,系统都会默认赋值。)
//birth = null;
//sex = false;
}
//有参数构造方法
public Vip(long huiYuanHao,String xingMing, String shengRi){
no = huiYuanHao;
name = xingMing;
birth = shengRi;
// 实际上这里有一行默认的代码
//sex = false;
}
//有参数的构造方法
public Vip(long huiYuanHao,String xingMing,String shengRi,boolean xingBie){
no = huiYuanHao;
name = xingMing;
birth = shengRi;
sex = xingBie;
}
}
public class ConstructorTest03{
public static void main(String[] args){
//调用不同的构造方法创建对象
Vip v1 = new Vip();
System.out.println(v1.no); //0
System.out.println(v1.name); // null
System.out.println(v1.birth); // null
System.out.println(v1.sex); // false
Vip v2 = new Vip(11111L, "大灰狼");
System.out.println(v2.no); // 11111L
System.out.println(v2.name); // "大灰狼"
System.out.println(v2.birth); // null
System.out.println(v2.sex); // false
Vip v3 = new Vip(22222L, "小绵羊", "2000-10-10");
System.out.println(v3.no); // 22222L
System.out.println(v3.name); //"小绵羊"
System.out.println(v3.birth); // "2000-10-10"
System.out.println(v3.sex); // false
Vip v4 = new Vip(33333L, "钢铁侠", "1980-10-11", true);
System.out.println(v4.no); // 33333L
System.out.println(v4.name); //"钢铁侠"
System.out.println(v4.birth); //"1980-10-11"
System.out.println(v4.sex); //true
}
}
9.8.2 day14作业
第一题:设计日期类,每个日期对象都可以描述年月日信息。
第二题:设计男人类,每个男人都有身份证号、姓名、性别、女人。设计女人类,每个女人都有身份证号、姓名、性别、男人。
第三题:设计银行账户类,每个账户都有账号、密码、余额等信息。
第四题:设计微信账号类,每个微信账号都有微信号、手机号、昵称等信息。
第五题:定义丈夫类 Husband 和妻子类 Wife,
丈夫类的属性包括:身份证号,姓名,出生日期,妻子。
妻子类的属性包括:身份证号,姓名,出生日期,丈夫。
分别给这两个类提供构造方法(无参数构造方法和有参数构造方法都要提供),编写测试程序,创建丈夫对象,然后再创建妻子对象,丈夫对象关联妻子对象,妻子对象关联丈夫对象,要求能够输出这个“丈夫对象”的妻子的名字,或者能够输出这个“妻子对象”的丈夫的名字。要求能够画出程序执行过程的内存图。并且要求在程序中演示出空指针异常的效果。
//设计日期类,每个日期对象都可以描述年月日信息。
public class Date{
//年
int year;
//月
int month;
//日
int day;
}
//设计男人类,每个男人都有身份证号、姓名、性别、女人。
public class Man{
// 身份证号
String idCard;
// 姓名
String name;
// 性别
static boolean sex = true;
// 男人的女人
// 实例变量:woman是一个引用
Woman woman;
}
//设计女人类,每个女人都有身份证号、姓名、性别、男人。
public class Woman{
// 身份证号
String idCard;
// 姓名
String name;
// 性别
static boolean sex = false;
// 男人
Man man;
}
//设计银行账户类,每个账户都有账号、密码、余额等信息。
public class Account{
// 账号
String actno;
// 密码
String password;
// 余额
double balance;
}
//设计微信账号类,每个微信账号都有微信号、手机号、昵称等信息。
public class WebChat{
// 账号
String chatNo;
// 手机号
String mphone;
// 昵称
String nickname;
}
/*
定义丈夫类 Husband 和妻子类 Wife,
丈夫类的属性包括:身份证号,姓名,出生日期,妻子。
妻子类的属性包括:身份证号,姓名,出生日期,丈夫。
分别给这两个类提供构造方法(无参数构造方法和有参数构造方法都要提供),
编写测试程序,创建丈夫对象,然后再创建妻子对象,丈夫对象关联妻子对象,
妻子对象关联丈夫对象,要求能够输出这个“丈夫对象”的妻子的名字,
或者能够输出这个“妻子对象”的丈夫的名字。要求能够画出程序执行过程的内存图。
并且要求在程序中演示出空指针异常的效果。
别懵:
.后面只是一个单词,没有小括号,表示访问的是属性。
.后面有一个单词,单词后面有小括号,表示访问的是方法。
这个一定要画出内存图,很有研究价值。
*/
public class Last{
public static void main(String[] args){
// 创建丈夫对象(丈夫还没有妻子)
//Husband h = new Husband("1111111", "张三", "1999-10-11", null);
Husband h = new Husband("1111111", "张三", "1999-10-11");
// 创建妻子对象(妻子还没有丈夫)
//Wife w = new Wife("222222", "李四", "1989-11-10", null);
Wife w = new Wife("222222", "李四", "1989-11-10");
// 让他俩结婚(产生关系)
h.wife = w;
w.husband = h;
// 输出丈夫对象的妻子名字
//System.out.println(h.name + "的妻子是" + w.name); //这样写错误的。
//当没有结婚的时候,出现:java.lang.NullPointerException
System.out.println(h.name + "的妻子是" + h.wife.name);
System.out.println(w.name + "的丈夫是" + w.husband.name);
}
}
// 丈夫类
class Husband{
// 身份证号
String idCard;
// 姓名
String name;
// 生日
String birth;
// 妻子
Wife wife; //实例变量
// 无参数构造方法
public Husband(){
}
// 有参数的构造方法
public Husband(String s1,String s2,String s3){
idCard = s1;
name = s2;
birth = s3;
}
// 有参数的构造方法
public Husband(String s1,String s2,String s3, Wife w){
idCard = s1;
name = s2;
birth = s3;
wife = w;
}
}
// 妻子类
class Wife{
// 身份证号
String idCard;
// 姓名
String name;
// 生日
String birth;
// 丈夫
Husband husband;
// 无参数的构造方法
public Wife(){
}
// 有参数的构造方法
public Wife(String s1,String s2,String s3){
idCard = s1;
name = s2;
birth = s3;
}
// 有参数构造方法
public Wife(String s1,String s2,String s3,Husband h){
idCard = s1;
name = s2;
birth = s3;
husband = h;
}
}
10 第十章 封装
10.1 章节目标与知识框架
10.1.1 章节目标
理解为什么要进行封装?封装有什么好处?封装的代码怎么实现?
10.1.2 知识框架
10.2 封装(掌握)
10.2.1 封装的理解
封装是面向对象的三大特征之一,什么是封装?封装有什么好处?怎么封装,代码怎么写?这是大家这一章节要学习的内容。
封装从字面上来理解就是包装的意思,专业点就是信息隐藏,是指利用抽象数据类型将数据和基于数据的操作封装在一起,使其构成一个不可分割的独立实体,数据被保护在抽象数据类型的内部,尽可能地隐藏内部的细节,只保留一些对外接口使之与外部发生联系。系统的其他对象只能通过包裹在数据外面的已经授权的操作来与这个封装的对象进行交流和交互。也就是说用户是无需知道对象内部的细节,但可以通过该对象对外提供的接口来访问该对象。
在现实世界当中我们可以看到很多事物都是封装好的,比如“鼠标”,外部有一个壳,将内部的原件封装起来,至于鼠标内部的细节是什么,我们不需要关心,只需要知道鼠标对外提供了左键、右键、滚动滑轮这三个简单的操作。对于用户来说只要知道左键、右键、滚动滑轮都能完成什么功能就行了。为什么鼠标内部的原件要在外部包装一个“壳”呢,起码内部的原件是安全的,不是吗。再如“数码相机”,外部也有一个壳,将内部复杂的结构包装起来,对外提供简单的按键,这样每个人都可以很快的学会照相了,因为它的按键很简单,另外照相机内部精密的原件也受到了壳儿的保护,不容易坏掉。
根据以上的描述,可以得出封装有什么好处呢?封装之后就形成了独立实体,独立实体可以在不同的环境中重复使用,显然封装可以降低程序的耦合度,提高程序的扩展性,以及重用性或复用性,例如“鼠标”可以在A电脑上使用,也可以在B电脑上使用。另外封装可以隐藏内部实现细节,站在对象外部是看不到内部复杂结构的,对外只提供了简单的安全的操作入口,所以封装之后,实体更安全了。
10.2.2 不封装存在的问题
我们来看一段代码,在不进行封装的前提下,存在什么问题:
运行结果如下图所示:
以上程序MobilePhone类未进行封装,其中的电压属性voltage对外暴露,在外部程序当中可以对MobilePhone对象的电压voltage属性进行随意访问,导致了它的不安全,例如手机的正常电压是3~5V,但是以上程序已经将手机电压设置为100V,这个时候显然是要出问题的,但这个程序编译以及运行仍然是正常的,没有出现任何问题,这是不对的。
10.2.3 怎么封装
为了保证内部数据的安全,这个时候就需要进行封装了,封装的第一步就是将应该隐藏的数据隐藏起来,起码在外部是无法随意访问这些数据的,怎么隐藏呢?我们可以使用java语言中的private修饰符,private修饰的数据表示私有的,私有的数据只能在本类当中访问。请看程序:
以上程序编译报错了,请看下图:
通过以上的测试,手机对象的电压属性确实受到了保护,在外部程序中无法访问了。但从当前情况来看,voltage属性有点儿太安全了,一个对象的属性无法被外部程序访问,自然这个数据就没有存在的价值了。所以这个时候就需要进入封装的第二步了:对外提供公开的访问入口,让外部程序统一通过这个入口去访问数据,我们可以在这个入口处设立关卡,进行安全控制,这样对象内部的数据就安全了。
对于“一个”属性来说,我们对外应该提供几个访问入口呢?通常情况下我们访问对象的某个属性,不外乎读取(get)和修改(set),所以对外提供的访问入口应该有两个,这两个方法通常被称为set方法和get方法(请注意:set和get方法访问的都是某个具体对象的属性,不同的对象调用get方法获取的属性值不同,所以set和get方法必须有对象的存在才能调用,这样的方法定义的时候不能使用static关键字修饰,被称为实例方法。实例方法必须使用“引用”的方式调用。还记得之前我们接触的方法都是被static修饰的,这些方法直接采用“类名”的方式调用,而不需要创建对象,在这里显然是不行的)。请看以下代码:
运行结果如下图所示:
通过以上程序,可以看出MobilePhone的voltage属性不能在外部程序中随意访问了,只能调用MobilePhone的setVoltage()方法来修改电压,调用getVoltage()方法来读取电压,在setVoltage()方法中编写了安全控制代码,当电压低于3V,或者高于5V的时候,程序抛出了异常,不允许修改电压值,程序结束了。只有合法的时候,才允许程序修改电压值。(异常机制在后续的内容中会学到,不要着急。)
总之,在java语言中封装的步骤应该是这样的:需要被保护的属性使用private进行修饰,给这个私有的属性对外提供公开的set和get方法,其中set方法用来修改属性的值,get方法用来读取属性的值。并且set和get方法在命名上也是有规范的,规范中要求set方法名是set+属性名(属性名首字母大写),get方法名是get+属性名(属性名首字母大写)。其中set方法有一个参数,用来给属性赋值,set方法没有返回值,一般在set方法内部编写安全控制程序,因为毕竟set方法是修改内部数据的,而get方法不需要参数,返回值类型是该属性所属类型(先记住,以后讲:另外set方法和get方法都不带static关键字,不带static关键字的方法称为实例方法,这些方法调用的时候需要先创建对象,然后通过“引用”去调用这些方法,实例方法不能直接采用“类名”的方式调用。),例如以下代码:
运行结果如下图所示:
有的读者可能会有这样的疑问:构造方法中已经给属性赋值了,为什么还要提供set方法呢?注意了同学们,这是两个完全不同的时刻,构造方法中给属性赋值是在创建对象的时候完成的,当对象创建完毕之后,属性可能还是会被修改的,后期要想修改属性的值,这个时候就必须调用set方法了。
10.3 章节小结
通过本章节内容的学习,需要理解为什么要封装,封装有什么好处,以及掌握封装的代码应该如何编写。
10.4 难点解惑
对于本章节内容来说,比较难以理解的是在之前所写的程序中所有的方法都是带有static关键字的,为什么在进行封装时,提供的setter和getter方法就不能添加static关键字了呢?这块内容在后期学习static关键字之后自然就好理解了,这里我简单解释一下:带有static关键字的方法是静态方法,不需要创建对象,直接通过“类”来调用。对于没有static关键字的方法被称为实例方法,这些方法执行时要求必须先创建对象,然后通过“引用”的方式来调用。而对于封装来说,setter和getter方法都是访问对象内部的属性,所以setter和getter方法在定义时不能添加static关键字。
10.5 章节习题
第一题:定义“人”类,“人”类包括这些属性:姓名、性别、年龄等。使用封装的方式编写程序,要求对年龄进行安全控制,合法的年龄范围为[0~100],其他值表示不合法。
10.6 习题答案
第一题答案:
执行结果如下图所示:
10.7 day15课堂笔记
上接day14
...
6、封装
6.1、面向对象的三大特征:
封装
继承
多态
有了封装,才有继承,有了继承,才能说多态。
6.2、面向对象的首要特征:封装 。什么是封装?有什么用?
现实生活中有很多现实的例子都是封装的,例如:
手机,电视机,笔记本电脑,照相机,这些都是外部有一个坚硬的壳儿。
封装起来,保护内部的部件。保证内部的部件是安全的。另外封装了之后,对于我们使用者来说,我们是看不见内部的复杂结构的,我们也不需要关心内部有多么复杂,我们只需要操作外部壳儿上的几个按钮就可以完成操作。
那么封装,你觉得有什么用呢?
封装的作用有两个:
第一个作用:保证内部结构的安全。
第二个作用:屏蔽复杂,暴露简单。
在代码级别上,封装有什么用?
一个类体当中的数据,假设封装之后,对于代码的调用人员来说,不需要关心代码的复杂实现,只需要通过一个简单的入口就可以访问了。
另外,类体中安全级别较高的数据封装起来,外部人员不能随意访问,来保证数据的安全性。
6.3、怎么进行封装,代码怎么实现?
第一步:属性私有化(使用private关键字进行修饰。)
第二步:对外提供简单的操作入口。
1、封装的代码实现两步:
第一步:属性私有化
第二步:1个属性对外提供两个set和get方法。外部程序只能通过set方法修改,只能通过get方法读取,可以在set方法中设立关卡来保证数据的安全性。
再强调一下:
set和get方法都是实例方法,不能带static。
不带static的方法称为实例方法,实例方法的调用必须先new对象。
set和get方法写的时候有严格的规范要求:(大家要按照规矩来)
set方法长这个样子:
public void set+属性名首字母大写(1个参数){
xxx = 1个参数;
}
get方法长这个样子:
public 返回值类型 get+属性名首字母大写(无参){
return xxx;
}
2、static关键字
2.1、static修饰的统一都是静态的,都是类相关的,不需要new对象。直接采用“类名.”访问。
2.2、当一个属性是类级别的属性,所有对象的这个属性的值是一样的,建议定义为静态变量。
2.3、
10.7.1 day15代码
/*
Person表示人类:
每一个人都有年龄这样的属性。
年龄age,int类型。
我这里先不使用封装机制,分析程序存在什么缺点?
Person类的age属性对外暴露,可以在外部程序中随意访问,导致了不安全。
怎么解决这个问题?
封装。
*/
// 这是没有封装的Person。
/*
public class Person{
// 实例变量(属性)
int age; //age属性是暴露的,在外部程序中可以随意访问。导致了不安全。
}
*/
// 尝试封装一下
// 不再对外暴露复杂的数据,封装起来
// 对外只提供简单的操作入口。
// 优点:第一数据安全了。第二调用者也方便了。
public class Person{
// private 表示私有的,被这个关键字修饰之后,该数据只能在本类中访问。
// 出了这个类,age属性就无法访问了。私有的。
private int age; // 每一个人年龄值不同,对象级别的属性。
// 对外提供简单的访问入口(电视机的遥控器就相当于是电视机的访问入口,简单明了。)
// 外部程序只能通过调用以下的代码来完成访问
// 思考:你应该对外提供几个访问入口?
// 思考:这些操作入口是否应该是方法呢?
// 写一个方法专门来完成读。(get)
// 写一个方法专门来完成写。(set)
// get和set方法应该带有static,还是不应该有static,get和set方法应该定义为实例方法吗?
// get读年龄,set改年龄,这个读和改都是操作的一个对象的年龄。(没有对象何来年龄)
// 封装的第二步:对外提供公开的set方法和get方法作为操作入口。并且都不带static。都是实例方法。
/*
[修饰符列表] 返回值类型 方法名(形式参数列表){
}
注意:
java开发规范中有要求,set方法和get方法要满足以下格式。
get方法的要求:
public 返回值类型 get+属性名首字母大写(无参){
return xxx;
}
set方法的要求:
public void set+属性名首字母大写(有1个参数){
xxx = 参数;
}
大家尽量按照java规范中要求的格式提供set和get方法。
如果不按照这个规范格式来,那么你的程序将不是一个通用的程序。
*/
// get方法
public int getAge(){
return age;
}
// set方法
public void setAge(int nianLing){
// 能不能在这个位置上设置关卡!!!!
if(nianLing < 0 || nianLing > 150){
System.out.println("对不起,年龄值不合法,请重新赋值!");
return; //直接终止程序的执行。
}
//程序能够执行到这里,说明年龄一定是合法的。
age = nianLing;
}
}
// 在外部程序中访问Person这个类型中的数据。
public class PersonTest{
public static void main(String[] args){
// 创建Person对象
Person p1 = new Person();
// 访问人的年龄
// 访问一个对象的属性,通常包括两种操作,一种是读数据,一种是改数据。
// 读数据
System.out.println(p1.age); //读(get表示获取)
// 修改数据(set表示修改/设置)
p1.age = 50;
//再次读取
System.out.println(p1.age);
// 在PersonTest这个外部程序中目前是可以随意对age属性进行操作的。
// 一个人的年龄值不应该为负数。
// 程序中给年龄赋值了一个负数,按说是不符合业务要求的,但是程序目前还是让它通过了。
// 其实这就是一个程序的bug。
p1.age = -100; //改(随意在这里对Person内部的数据进行访问,导致了不安全。)
System.out.println("您的年龄值是:" + p1.age); //读
}
}
public class PersonTest02{
public static void main(String[] args){
// 创建对象
Person p1 = new Person();
// Person的age,彻底在外部不能访问了。但是这难免有点太安全了。
// age不能访问,这个程序就意义不大了。
/*
// 读age属性的值
System.out.println(p1.age);
// 修改age属性的值
p1.age = 20;
// 读age
System.out.println(p1.age);
*/
// 通过“类名.”可以调用set和get方法吗?不行。
// 只有方法修饰符列表中有static的时候,才能使用“类名.”的方式访问
// 错误的。
//Person.getAge();
//读调用getAge()方法
//int nianLing = p1.getAge();
//System.out.println(nianLing); //0
//以上代码联合
System.out.println(p1.getAge()); //0
//改调用setAge()方法
p1.setAge(20);
System.out.println(p1.getAge()); //20
// 你折腾半天了,这不是结果还是没控制住吗??????
p1.setAge(-100);
//System.out.println(p1.getAge()); // -100
System.out.println(p1.getAge()); // 20
}
}
//构造方法、构造器、Constructor【构造函数】
// 只能用new来调用构造方法。
public class Student{
String no;
String name;
// 无参数的构造方法
public Student(){
}
public Student(String s){
no = s;
}
/*
public Student(String mingZi){
name = mingZi;
}
*/
public Student(String s1, String s2){
no = s1;
name = s2;
}
}
//构造方法两个作用:创建对象 给属性赋值。
class StudentTest{
public static void main(String[] args){
Student s = new Student();
Student s1 = new Student("100");
}
}
//带有static的方法
//没有static的方法
//分别怎么调用?
//带有static的方法怎么调用?通过“类名.”的方式访问。
//对象被称为实例。
//实例相关的有:实例变量、实例方法。
//实例变量是对象变量。实例方法是对象方法。
//实例相关的都需要先new对象,通过“引用.”的方式去访问。
public class MethodTest{
/*
public MethodTest(){
}
*/
public static void main(String[] args){
MethodTest.doSome();
//类名. 可以省略(在同一个类中。)
doSome();
// 尝试使用“类名.”的方式访问“实例方法”
// 错误的
//MethodTest.doOther();
// 创建对象
MethodTest mt = new MethodTest();
// 通过"引用."的方式访问实例方法。
mt.doOther();
}
// 带有static
public static void doSome(){
System.out.println("do some!");
}
//这个方法没有static,这样的方法被称为:实例方法。(对象方法,对象级别的方法)
//这个没法解释,大家目前死记硬背。
public void doOther(){
System.out.println("do other....");
}
}
实例方法导致的空指针
/*
空指针异常导致的最本质的原因是?
空引用访问“实例相关的数据”,会出现空指针异常。
实例相关的包括:实例变量 + 实例方法。
*/
public class NullPointerTest{
public static void main(String[] args){
User u = new User();
System.out.println(u.id); // 0
u.doSome();
// 引用变成空null
u = null;
// id的访问,需要对象的存在。
//System.out.println(u.id); // 空指针异常
// 一个实例方法的调用也必须有对象的存在。
//u.doSome(); // 空指针异常。
}
}
// 类 = 属性 + 方法
// 属性描述状态
// 方法描述行为动作
class User{
// 实例变量
int id;
// 实例方法(对象相关的方法,对象级别的方法,应该是一个对象级别的行为。)
// 方法模拟的是对象的行为动作。
public void doSome(){
System.out.println("do some!");
}
// 考试的行为,由于每一个人考试之后的分数不一样,所以考试行为应该必须有对象的参与。
public void exam(){
}
}
10.7.2 day15作业
一、请通过代码封装,实现如下需求:
编写一个类Book,代表教材:
1.具有属性:名称(title)、页数(pageNum)
2.其中页数不能少于200页,否则输出错误信息,并赋予默认值200
3.为各属性提供赋值和取值方法
4.具有方法:detail,用来在控制台输出每本教材的名称和页数
5.编写测试类BookTest进行测试:为Book对象的属性赋予初始值,并调用Book对象的detail方法,看看输出是否正确
二、写一个名为Account的类模拟账户。
该类的属性和方法如下所示。
该类包括的属性:账户id,余额balance,年利率annualInterestRate;
包含的方法:各属性的set和get方法。取款方法withdraw(),存款方法deposit()
写一个测试程序
(1)创建一个Customer,名字叫Jane Smith,他有一个账号为1000,余额为2000,年利率为1.23%的账户
(2)对Jane Smith操作:
存入100元,再取出960元,再取出2000。
打印Jane Smith的基本信息
信息如下显示:
成功存入:100
成功取出:960
余额不足,取钱失败
Customer [Smith,Jane] has a account :id is 1000 annualInterestRate is 1.23% balance is 1140.0
三、(封装)已知一个类 Student 代码如下:
class Student{
String name;
int age;
String address;
String zipCode;
String mobile;
}
要求:
1、把Student 的属性都作为私有,并提供get/set 方法以及适当的构造方法。
2、为Student 类添加一个getPostAddress 方法,要求返回Student 对象的地址和邮编。
/*
请通过代码封装,实现如下需求:
编写一个类Book,代表教材:
1.具有属性:名称(title)、页数(pageNum)
2.其中页数不能少于200页,否则输出错误信息,并赋予默认值200
3.为各属性提供赋值和取值方法
4.具有方法:detail,用来在控制台输出每本教材的名称和页数
5.编写测试类BookTest进行测试:为Book对象的属性赋予初始值,并调用Book对象的detail方法,看看输出是否正确
*/
public class Homework1{
public static void main(String[] args){
// 创建Book对象
Book b1 = new Book("高三数学人教版", 250);
// 调用detail方法
b1.detail();
// 修改页数
b1.setPageNum(100);
b1.detail();
Book b2 = new Book();
b2.detail();
}
}
class Book{
// 名称
private String title; //实例变量,引用. 的方式访问
// 页数
private int pageNum;
// 构造方法,无参数
public Book(){
title = "未知";
pageNum = 200;
}
// 有参数构造器
public Book(String s, int i){
title = s;
if(i < 200){
pageNum = 200;
System.out.println("页数不能少于200,少于200时,页数默认为200页");
}else{
pageNum = i;
}
}
// setter and getter方法
public void setTitle(String s){
title = s;
}
public String getTitle(){
return title;
}
public void setPageNum(int i){
if(i < 200){
System.out.println("本书页数不能少于200页,默认赋值200");
// 默认赋值200
pageNum = 200;
return;
}
// 说明页数是大于等于200的。
pageNum = i;
}
public int getPageNum(){
return pageNum;
}
// 额外再提供一个detail方法
public void detail(){
//System.out.println("教材名称:" + this.title + ",总页数为" + this.pageNum);
//this.是可以省略的
System.out.println("教材名称:" + title + ",总页数为" + pageNum);
}
}
/*
写一个名为Account的类模拟账户。
该类的属性和方法如下所示。
该类包括的属性:账户id,余额balance,年利率annualInterestRate;
包含的方法:各属性的set和get方法。取款方法withdraw(),存款方法deposit()
写一个测试程序
(1)创建一个Customer,名字叫Jane Smith,他有一个账号为1000,余额为2000,年利率为1.23%的账户
(2)对Jane Smith操作:
存入100元,再取出960元,再取出2000。
打印Jane Smith的基本信息
信息如下显示:
成功存入:100
成功取出:960
余额不足,取钱失败
Customer [Smith,Jane] has a account :id is 1000 annualInterestRate is 1.23% balance is 1140.0
*/
public class Homework2{
public static void main(String[] args){
// 先创建一个账户对象
Account a = new Account("1000", 2000, 1.23);
// 创建客户对象
// 传给构造方法a是什么意思?让Customer对象和Account对象产生关系。
// 表示这个账户是Smith的。
Customer c = new Customer("Jane Smith", a);
/*
对Jane Smith操作:
存入100元,
再取出960元,
再取出2000。
*/
c.getAct().deposit(100);
c.getAct().withdraw(960);
c.getAct().withdraw(2000);
// 银行卡是不是应该有一个主人。
// 此程序最难的地方是:发现对象之间的关联。不好发现。
}
}
// 以后都是封装,所有的类都是属性私有化,对外提供setter and getter方法。
// 客户类
class Customer{
// 客户名字
private String name;
// 客户手里应该有银行账户
private Account act;
// 构造方法
public Customer(){
}
public Customer(String name, Account act){
this.name = name;
this.act = act;
}
// setter and getter方法
// 为什么要写set和get,用得着吗?用不着你也得写,因为这是“封装”规定的。
public void setName(String name){
this.name = name;
}
public String getName(){
return name;
}
public void setAct(Account act){
this.act = act;
}
public Account getAct(){
return act;
}
}
// 账户类
class Account{
// 账户id
private String id;
// 余额
private double balance;
// 年利率
private double annualInterestRate;
// 构造方法
public Account(){
}
public Account(String id, double balance, double annualInterestRate){
// 创建对象时需要的代码。
this.id = id;
this.balance = balance;
this.annualInterestRate = annualInterestRate;
}
// setter and getter
public void setId(String id){
// 对象创建完之后,想修改id属性调用此方法。
this.id = id;
}
public String getId(){
return id;
}
// 实例方法(需要“引用.”来调用。)
public void setBalance(double balance){ //修改余额的方法。
this.balance = balance;
}
public double getBalance(){
return balance;
}
public void setAnnualInterestRate(double annualInterestRate){
this.annualInterestRate = annualInterestRate;
}
public double getAnnualInterestRate(){
return annualInterestRate;
}
// 存款方法
// 参数表示存多少钱。
public void deposit(double money){
// this(向this指向的对象中存款)
//this.balance = this.balance + money;
//this.balance += money;
// 如果省略this.
//balance = balance + money;
//balance += money;
// 调用方法来进行修改余额
this.setBalance(this.getBalance() + money);
// this. 可以省略。
//setBalance(getBalance() + money);
System.out.println("成功存入:" + money);
}
// 取款方法
// 调用取钱的方法时,应该传递过来一个参数,告诉该方法要取多少钱
public void withdraw(double money){
// this(从this指向的对象中取款)
if(money > this.getBalance()){
System.out.println("余额不足,取钱失败");
return;
}
// 程序能够执行到此处说明余额充足
this.setBalance(this.getBalance() - money);
System.out.println("成功取出:" + money);
}
}
/*
(封装)已知一个类 Student 代码如下:
class Student{
String name;
int age;
String address;
String zipCode;
String mobile;
}
要求:
1、把Student 的属性都作为私有,并提供get/set 方法以及适当的构造方法。
2、为Student 类添加一个getPostAddress 方法,要求返回Student 对象的"地址"和"邮编"。
注意:这里所说的地址是:address,不是内存地址。
*/
public class Homework3{
public static void main(String[] args){
Student s1 = new Student();
System.out.println(s1.getName() + "," + s1.getPostAddress());
// 赋值
s1.setName("zhangsan");
s1.setAge(20);
s1.setAddress("北京朝阳区");
s1.setZipcode("122222");
s1.setMobile("12235224214");
System.out.println(s1.getName() + "," + s1.getPostAddress());
Student s2 = new Student("李四",18,"深圳宝安区","11111","456456456456465");
System.out.println(s2.getName() + "," + s2.getPostAddress());
}
}
class Student{
private String name;
private int age;
private String address;
private String zipcode;
private String mobile;
public Student(){
}
public Student(String name, int age, String address, String zipcode, String mobile){
this.name = name;
this.age = age;
this.address = address;
this.zipcode = zipcode;
this.mobile = mobile;
}
// 返回地址和邮编
public String getPostAddress(){
return "地址:" + this.getAddress() + ",邮编:" + this.getZipcode();
//return "地址:" + this.address + ",邮编:" + this.zipcode;
}
public void setName(String name){
this.name = name;
}
public String getName(){
return name;
}
public void setAge(int age){
this.age = age;
}
public int getAge(){
return age;
}
public void setAddress(String address){
this.address = address;
}
public String getAddress(){
return address;
}
public void setZipcode(String zipcode){
this.zipcode = zipcode;
}
public String getZipcode(){
return zipcode;
}
public void setMobile(String mobile){
this.mobile = mobile;
}
public String getMobile(){
return mobile;
}
}
11 第十一章 this和static
11.1 章节目标与知识框架
11.1.1 章节目标
理解this是什么,this能用在哪里,不能用在哪里,this什么时候可以省略,什么时候不能省略,以及怎么通过构造方法调用当前类中其它的构造方法。掌握静态代码块的执行时机,变量什么时候声明为静态变量,什么时候声明为实例变量,方法什么时候声明为实例方法,什么时候声明为静态方法,以及静态方法中为何不能直接访问实例变量和实例方法。
11.1.2 知识框架
11.2 this(掌握)
11.2.1 this是什么
this是java语言中的一个关键字,它存储在内存的什么地方呢,一起来看一段程序:
以上程序的内存结构图如下所示:
this可以看做一个变量,它是一个引用,存储在Java虚拟机堆内存的对象内部,this这个引用保存了当前对象的内存地址指向自身,任何一个堆内存的java对象都有一个this,也就是说创建100个java对象则分别对应100个this。通过以上的内存图,可以看出“jack引用”保存的内存地址是0x1111,对应的“this引用”保存的内存地址也是0x1111,所以“jack引用”和“this引用”是可以划等号的。也就是说访问对象的时候jack.name和this.name是一样的,都是访问该引用所指向对象的name属性。
this指向“当前对象”,也可以说this代表“当前对象”,this可以使用在实例方法中以及构造方法中,语法格式分别为“this.”和“this(..)”。this不能出现在带有static的方法当中。
11.2.2 this使用在实例方法中
我们来看看this是否可以出现在static的方法当中,请看以下代码以及编译结果:
编译报错,如下图所示:
通过以上的测试得知this不能出现在static的方法当中,这是为什么呢?首先static的方法,在调用的时候是不需要创建对象的,直接采用“类名”的方式调用,也就是说static方法执行的过程中是不需要“当前对象”参与的,所以static的方法中不能使用this,因为this代表的就是“当前对象”。
大家是否还记得在之前的“封装”过程中,曾编写属性相关的set和get方法,set和get方法在声明的时候不允许带static关键字,我们把这样的方法叫做实例方法,说到实例方法,大家肯定想到了实例变量,没错,实例变量和实例方法都是对象相关,必须有对象的存在,然后通过“引用”去访问。
为什么set和get方法设计为实例方法呢?那是因为set和get方法操作的是实例变量,“不同的对象”调用get方法最终得到的数据是不同的,例如zhangsan调用getName()方法得到的名字是zhangsan,lisi调用getName()方法得到的名字是lisi,显然get方法是一个对象级别的方法,不能直接采用“类名”调用,必须先创建对象,再通过“引用”去访问。
this可以出现在实例方法当中,因为实例方法在执行的时候一定是对象去触发的,实例方法一定是对象才能去调用的,而this恰巧又代表“当前对象”,所以“谁”去调用这个实例方法this就是“谁”。测试一下,请看以下代码:
运行结果如下图所示:
以上代码的输出结果具体是什么不重要,重要的是可以看出谁和谁是相等的。运行结果和代码结合起来分析一下this:
通过以上内容的学习得知,this可以使用在实例方法当中,它指向当前正在执行这个动作的对象。
大家是否还记得实例变量怎么访问?正规的访问方式是采用“引用.”去访问。请看下面的代码:
运行结果如下图所示:
将以上部分代码片段取出来进行分析:
把完整的代码拿过来:
运行结果如下图所示:
通过以上的测试我们得知:System.out.println(name + "is shopping!")和System.out.println(this.name + "is shopping!")是等效的。也就是说在shopping()这个“实例方法”当中直接访问“实例变量”name就表示访问当前对象的name。换句话说在实例方法中可以直接访问当前对象的实例变量,而“this.”是可以省略的。“this.”什么时候不能省略呢?请看以下代码:
你有没有看到name=_name这样的代码很丑陋,怎样可以优雅一些呢?请看:
以上代码当中this.name=name,其中this.name表示实例变量name,等号右边的name是局部变量name,此时如果省略“this.”,则变成name=name,这两个name都是局部变量(java遵守就近原则),和实例变量name无关了,显然是不可以省略“this.”的。
最终的结论是,this不能出现在static的方法中,可以出现在实例方法中,代表当前对象,大部分情况下this都是可以省略的,只有当在实例方法中区分局部变量和实例变量的时候不能省略。
接下来我们再来扩展一下this的使用,请看代码:
运行结果如下图所示:
通过以上的测试,可以看出在一个实例方法当中可以直接去访问其它的实例方法,方法是对象的一种行为描述,实例方法中直接调用其它的实例方法,就表示“当前对象”完成了一系列行为动作。例如在实例方法shopping()中调用另一个实例方法pay(),这个过程就表示jack在选购商品,选好商品之后,完成支付环节,其中选购商品是一个动作,完成支付是另一个动作。接下来继续扩展,请看以下代码:
以上代码编译报错了,请看:
为什么会编译报错,在main方法中为什么无法直接访问变量i?我们来分析一下,首先i变量是实例变量,实例变量要想访问必须先创建对象,然后通过“引用”去访问,main方法是static的方法,也就是说main方法是通过“类名”去调用的,在main方法中没有“当前对象”的概念,也就是说main方法中不能使用this,所以编译报错了。那应该怎么修改呢?请看:
运行结果如下图所示:
通过以上的测试得知,在static的方法中不能直接访问实例变量,要访问实例变量必须先自己创建一个对象,通过“引用”可以去访问,不能通过this访问,因为在static方法中是不能存在this的。其实这种设计也是有道理的,因为static的方法在执行的时候是采用“类名”去调用,没有对象的参与,自然也不会存在当前对象,所以static的方法执行过程中不存在this。那么在static方法中能够直接访问实例方法吗?请看以下代码:
编译报错了,请看下图:
为什么在main方法中无法直接调用实例方法doSome()呢?很简单,因为实例方法必须先创建对象,通过引用去调用,在以上的main方法中并没有创建对象,更没有this。所以在main方法中无法直接访问实例方法。结论就是:在static的方法中不能直接访问实例方法。怎么修改呢?同样需要先创建对象,请看:
运行结果如下图所示:
综上所述,我们需要记住这样的一个结论:this不能使用在static的方法中,可以使用在实例方法中,代表当前对象,多数情况下this是可以省略不写的,但是在区分局部变量和实例变量的时候不能省略,在实例方法中可以直接访问当前对象实例变量以及实例方法,在static方法中无法直接访问实例变量和实例方法。
11.2.3 this使用在构造方法中
this还有另外一种用法,使用在构造方法第一行(只能出现在第一行,这是规定,记住就行),通过当前构造方法调用本类当中其它的构造方法,其目的是为了代码复用。调用时的语法格式是:this(实际参数列表),请看以下代码:
运行结果如下图所示:
我们来看看以上程序的无参数构造方法和有参数构造方法:
通过上图可以看到无参数构造方法中的代码和有参数构造方法中的代码是一样的,按照以上方式编写,代码没有得到重复使用,这个时候就可以在无参数构造方法中使用“this(实际参数列表);”来调用有参数的构造方法,这样就可以让代码得到复用了,请看:
还是使用以上的main方法进行测试,运行结果如下:
在this()上一行尝试添加代码,请看代码以及编译结果:
通过以上测试得出:this()语法只能出现在构造方法第一行,这个大家记住就行了。
11.3 static(掌握)
11.3.1 static概述
static是java语言中的关键字,表示“静态的”,它可以用来修饰变量、方法、代码块等,修饰的变量叫做静态变量,修饰的方法叫做静态方法,修饰的代码块叫做静态代码块。在java语言中凡是用static修饰的都是类相关的,不需要创建对象,直接通过“类名”即可访问,即使使用“引用”去访问,在运行的时候也和堆内存当中的对象无关。
本小节要求大家主要掌握:什么时候将变量定义为静态变量?什么时候把方法定义为静态方法?静态代码块什么时候使用?类体中的代码有执行顺序要求吗?空引用访问静态变量和静态方法会出现空指针异常吗?
11.3.2 静态变量
java中的变量包括:局部变量和成员变量,在方法体中声明的变量为局部变量,有效范围很小,只能在方法体中访问,方法结束之后局部变量内存就释放了,在内存方面局部变量存储在栈当中。在类体中定义的变量为成员变量,而成员变量又包括实例变量和静态变量,当成员变量声明时使用了static关键字,那么这种变量称为静态变量,没有使用static关键字称为实例变量,实例变量是对象级别的,每个对象的实例变量值可能不同,所以实例变量必须先创建对象,通过“引用”去访问,而静态变量访问时不需要创建对象,直接通过“类名”访问。实例变量存储在堆内存当中,静态变量存储在方法区当中。实例变量在构造方法执行过程中初始化,静态变量在类加载时初始化。那么变量在什么情况下会声明为静态变量呢?请看以下代码,定义一个“男人”类:
运行结果如下图所示:
我们来看一下以上程序的内存结构图:
“男人类”创建的所有“男人对象”,每一个“男人对象”的身份证号都不一样,该属性应该每个对象持有一份,所以应该定义为实例变量,而每一个“男人对象”的性别都是“男”,不会随着对象的改变而变化,性别值永远都是“男”,这种情况下,性别这个变量还需要定义为实例变量吗,有必要让每一个“男人对象”持有一份吗,这样岂不是浪费了大量的堆内存空间,所以这个时候建议将“性别=男”属性定义为类级别的属性,声明为静态变量,上升为“整个族”的数据,这样的变量不需要创建对象直接使用“类名”即可访问。请看代码:
运行结果如下图所示:
我们来看一下以上程序的内存结构图:
通过以上内容的学习我们得知,当一个类的所有对象的某个“属性值”不会随着对象的改变而变化的时候,建议将该属性定义为静态属性(或者说把这个变量定义为静态变量),静态变量在类加载的时候初始化,存储在方法区当中,不需要创建对象,直接通过“类名”来访问。如果静态变量使用“引用”来访问,可以吗,如果可以的话,这个访问和具体的对象有关系吗?来看以下代码:
运行结果如下图所示:
通过以上代码以及运行结果可以看出,静态变量也可以使用“引用”去访问,但实际上在执行过程中,“引用”所指向的对象并没有参与,如果是空引用访问实例变量,程序一定会发生空指针异常,但是以上的程序编译通过了,并且运行的时候也没有出现任何异常,这说明虽然表面看起来是采用“引用”去访问,但实际上在运行的时候还是直接通过“类”去访问的。静态方法是这样吗?请看以下代码:
运行结果如下图所示:
通过以上代码测试得知,静态变量和静态方法比较正式的方式是直接采用“类名”访问,但实际上使用“引用”也可以访问,并且空引用访问静态变量和静态方法并不会出现空指针异常。实际上,在开发中并不建议使用“引用”去访问静态相关的成员,因为这样会让程序员困惑,因为采用“引用”方式访问的时候,程序员会认为你访问的是实例相关的成员。
总之,所有实例相关的,包括实例变量和实例方法,必须先创建对象,然后通过“引用”的方式去访问,如果空引用访问实例相关的成员,必然会出现空指针异常。所有静态相关的,包括静态变量和静态方法,直接使用“类名”去访问。虽然静态相关的成员也能使用“引用”去访问,但这种方式并不被主张。
11.3.3 静态代码块
静态代码块的语法格式是这样的:
静态代码块在类加载时执行,并且只执行一次。开发中使用不多,但离了它有的时候还真是没法写代码。静态代码块实际上是java语言为程序员准备的一个特殊的时刻,这个时刻就是类加载时刻,如果你想在类加载的时候执行一段代码,那么这段代码就有的放矢了。例如我们要在类加载的时候解析某个文件,并且要求该文件只解析一次,那么此时就可以把解析该文件的代码写到静态代码块当中了。我们来测试一下静态代码块:
运行结果如下图所示:
通过以上的测试可以得知一个类当中可以编写多个静态代码块(尽管大部分情况下只编写一个),并且静态代码块遵循自上而下的顺序依次执行,所以有的时候放在类体当中的代码是有执行顺序的(大部分情况下类体当中的代码没有顺序要求,方法体当中的代码是有顺序要求的,方法体当中的代码必须遵守自上而下的顺序依次逐行执行),另外静态代码块当中的代码在main方法执行之前执行,这是因为静态代码块在类加载时执行,并且只执行一次。再来看一下以下代码:
编译结果如下图所示:
为什么编译报错呢?那是因为i变量是实例变量,实例变量必须先创建对象才能访问,静态代码块在类加载时执行,这个时候对象还没有创建呢,所以i变量在这里是不能这样访问的。可以考虑在i变量前添加static,这样i变量就变成静态变量了,静态变量访问时不需要创建对象,直接通过“类”即可访问,例如以下代码:
运行结果如下图所示:
代码修改为这样呢?
编译报错了,请看下图:
通过测试,可以看到有的时候类体当中的代码也是有顺序要求的(类体当中定义两个独立的方法,这两个方法是没有先后顺序要求的),静态代码块在类加载时执行,静态变量在类加载时初始化,它们在同一时间发生,所以必然会有顺序要求,如果在静态代码块中要访问i变量,那么i变量必须放到静态代码块之前。
11.3.4 静态方法
方法在什么情况下会声明为静态的呢?方法实际上描述的是行为动作,我认为当某个动作在触发的时候需要对象的参与,这个方法应该定义为实例方法,例如:每个玩篮球的人都会打篮球,但是你打篮球和科比打篮球最终的效果是不一样的,显然打篮球这个动作存在对象差异化,该方法应该定义为实例方法。再如:每个高中生都有考试的行为,但是你考试和学霸考试最终的结果是不一样的,一个上了“家里蹲大学”,一个上了“清华大学”,显然这个动作也是需要对象参与才能完成的,所以考试这个方法应该定义为实例方法。
以上描述是从设计思想角度出发来进行选择,其实也可以从代码的角度来进行判断,当方法体中需要直接访问当前对象的实例变量或者实例方法的时候,该方法必须定义为实例方法,因为只有实例方法中才有this,静态方法中不存在this。请看代码:
运行结果如下图所示:
在以上的代码中,不同的客户购物,最终的效果都不同,另外在shopping()方法中访问了当前对象的实例变量name,以及调用了实例方法pay(),所以shopping()方法不能定义为静态方法,必须声明为实例方法。
另外,在实际的开发中,“工具类”当中的方法一般定义为静态方法,因为工具类就是为了方便大家的使用,将方法定义为静态方法,比较方便调用,不需要创建对象,直接使用类名就可以访问。请看以下工具类,为了简化“System.out.println();”代码而编写的工具类:
运行结果如下图所示:
11.4 章节小结
通过本章节内容的学习,要求大家掌握Java中两个关键字的用法,一个是this,一个是static。其中this要理解的是this是什么,内存中存储在哪里,要掌握this在实例方法和构造方法中的用法。对于static来说,要理解static代表什么含义,要掌握静态变量、静态代码块、静态方法的使用。
11.5 难点疑惑
在本章节中,对于static来说,代码的执行顺序是一个需要大家注意的地方,一般来说方法体当中的代码是有执行顺序要求的,之前所接触的程序中,类体当中的程序没有顺序要求,但自从认识了static之后,我们发现类体当中的代码也有执行顺序的要求了,尤其是static修饰的静态变量,以及static修饰的静态代码块他们是有先后顺序的,这里需要嘱咐大家的是static修饰的静态变量以及静态代码块都是在类加载时执行,并且遵循自上而下的顺序依次逐行执行。
11.6 章节习题
第一题:选择题
第二题:判断下面代码的输出结果,并说明原因
第三题:找出下面代码的错误,并说明为什么错了
11.7 习题答案
第一题答案:B
第二题答案:最终的输出结果是:null。原因:setName方法体当中的name=name是把局部变量name赋值给局部变量name,和实例变量name无关,所以getName()方法获取的实例变量值是null
第三题答案:第13,15行编译报错,因为静态方法中无法直接访问实例变量i和实例方法m2()。
11.8 day16课堂笔记
上接day15
...
2、static关键字
2.1、static修饰的统一都是静态的,都是类相关的,不需要new对象。直接采用“类名.”访问。
2.2、当一个属性是类级别的属性,所有对象的这个属性的值是一样的,建议定义为静态变量。
2.3、
018-java中的三大变量
1、this
1.1、this是一个关键字,是一个引用,保存内存地址指向自身。
1.2、this可以使用在实例方法中,也可以使用在构造方法中。
1.3、this出现在实例方法中其实代表的是当前对象。
1.4、this不能使用在静态方法中。
1.5、this. 大部分情况下可以省略,但是用来区分局部变量和实例变量的时候不能省略。
1.6、this() 这种语法只能出现在构造方法第一行,表示当前构造方法调用本类其他的构造方法,目的是代码复用。
2、总结所有的变量怎么访问,总结所有的方法怎么访问!!!!
总结一下到目前为止我们在一个类中都接触过什么了。
3、继承extends
3.1、什么是继承,有什么用?
继承:在现实世界当中也是存在的,例如:父亲很有钱,儿子不用努力也很有钱。
继承的作用:
基本作用:子类继承父类,代码可以得到复用。(这个不是重要的作用,是基本作用。)
主要(重要)作用:因为有了继承关系,才有了后期的方法覆盖和多态机制。
3.2、继承的相关特性
① B类继承A类,则称A类为超类(superclass)、父类、基类,
B类则称为子类(subclass)、派生类、扩展类。
class A{}
class B extends A{}
我们平时聊天说的比较多的是:父类和子类。
superclass 父类
subclass 子类
② java 中的继承只支持单继承,不支持多继承,C++中支持多继承,这也是 java 体现简单性的一点,换句话说,java 中不允许这样写代码:class B extends A,C{ } 这是错误的。
③ 虽然 java 中不支持多继承,但有的时候会产生间接继承的效果,
例如:class C extends B,class B extends A,也就是说,C 直接继承 B,其实 C 还间接继承 A。
④ java 中规定,子类继承父类,除构造方法不能继承之外,剩下都可以继承。但是私有的属性无法在子类中直接访问。(父类中private修饰的不能在子类中直接访问。可以通过间接的手段来访问。)
⑤ java 中的类没有显示的继承任何类,则默认继承 Object类,Object类是 java 语言提供的根类(老祖宗类),也就是说,一个对象与生俱来就有Object类型中所有的特征。
⑥ 继承也存在一些缺点,例如:CreditAccount 类继承 Account 类会导致它们之间的耦合度非常高,Account 类发生改变之后会马上影响到 CreditAccount 类
11.8.1 day16代码
/*
static:
1、static翻译为“静态”
2、所有static关键字修饰的都是类相关的,类级别的。
3、所有static修饰的,都是采用“类名.”的方式访问。
4、static修饰的变量:静态变量
5、static修饰的方法:静态方法
变量的分类:
变量根据声明的位置进行划分:
在方法体当中声明的变量叫做:局部变量。
在方法体外声明的变量叫做:成员变量。
成员变量又可以分为:
实例变量
静态变量
*/
class VarTest{
// 以下实例的,都是对象相关的,访问时采用“引用.”的方式访问。需要先new对象。
// 实例相关的,必须先有对象,才能访问,可能会出现空指针异常。
// 成员变量中的实例变量
int i;
// 实例方法
public void m2(){
// 局部变量
int x = 200;
}
// 以下静态的,都是类相关的,访问时采用“类名.”的方式访问。不需要new对象。
// 不需要对象的参与即可访问。没有空指针异常的发生。
// 成员变量中的静态变量
static int k;
// 静态方法
public static void m1(){
// 局部变量
int m = 100;
}
}
/*
什么时候变量声明为实例的,什么时候声明为静态的?
如果这个类型的所有对象的某个属性值都是一样的,
不建议定义为实例变量,浪费内存空间。建议定义
为类级别特征,定义为静态变量,在方法区中只保留
一份,节省内存开销。
一个对象一份的是实例变量。
所有对象一份的是静态变量。
*/
/*
public class StaticTest02{
public static void main(String[] args){
Chinese c1 = new Chinese("1231456456456456","张三","中国");
System.out.println(c1.idCard);
System.out.println(c1.name);
System.out.println(c1.country);
Chinese c2 = new Chinese("7897897896748564","李四","中国");
System.out.println(c2.idCard);
System.out.println(c2.name);
System.out.println(c2.country);
}
}
// 定义一个类:中国人
class Chinese{
// 身份证号
// 每一个人的身份证号不同,所以身份证号应该是实例变量,一个对象一份。
String idCard;
// 姓名
// 姓名也是一个人一个姓名,姓名也应该是实例变量。
String name;
// 国籍
// 对于“中国人”这个类来说,国籍都是“中国”,不会随着对象的改变而改变。
// 显然国籍并不是对象级别的特征。
// 国籍属于整个类的特征。整个族的特征。
// 假设声明为实例变量,内存图是怎样的?
// 假设声明为静态变量,内存图又是怎样的?
String country;
// 无参数
public Chinese(){
}
// 有参数
public Chinese(String s1,String s2, String s3){
idCard = s1;
name = s2;
country = s3;
}
}
*/
public class StaticTest02{
public static void main(String[] args){
// 访问中国人的国籍
// 静态变量应该使用类名.的方式访问
System.out.println(Chinese.country);
Chinese c1 = new Chinese("1231456456456456","张三");
System.out.println(c1.idCard);
System.out.println(c1.name);
Chinese c2 = new Chinese("7897897896748564","李四");
System.out.println(c2.idCard);
System.out.println(c2.name);
// idCard是实例变量,必须先new对象,通过“引用.” 访问
// 错误: 无法从静态上下文中引用非静态 变量 idCard
//System.out.println(Chinese.idCard);
}
}
// 定义一个类:中国人
class Chinese{
// 身份证号
// 每一个人的身份证号不同,所以身份证号应该是实例变量,一个对象一份。
String idCard;
// 姓名
// 姓名也是一个人一个姓名,姓名也应该是实例变量。
String name;
// 国籍
// 重点重点五颗星:加static的变量叫做静态变量
// 静态变量在类加载时初始化,不需要new对象,静态变量的空间就开出来了。
// 静态变量存储在方法区。
static String country = "中国";
// 无参数
public Chinese(){
}
// 有参数
public Chinese(String s1,String s2){
idCard = s1;
name = s2;
}
}
016-变量什么时候声明为静态变量1
017-变量什么时候声明为静态的2
空引用访问静态不会空指针
/*
实例的:一定需要使用“引用.”来访问。
静态的:
建议使用“类名.”来访问,但使用“引用.”也行(不建议使用"引用.")。
静态的如果使用“引用.”来访问会让程序员产生困惑:程序员以为是实例的呢。
结论:
空指针异常只有在什么情况下才会发生呢?
只有在“空引用”访问“实例”相关的,都会出现空指针异常。
*/
public class StaticTest03{
public static void main(String[] args){
// 通过"类名."的方式访问静态变量
System.out.println(Chinese.country);
// 创建对象
Chinese c1 = new Chinese("1111111", "张三");
System.out.println(c1.idCard); // 1111111
System.out.println(c1.name); // 张三
System.out.println(c1.country); // 中国
// c1是空引用
c1 = null;
// 分析这里会不会出现空指针异常?
// 不会出现空指针异常。
// 因为静态变量不需要对象的存在。
// 实际上以下的代码在运行的时候,还是:System.out.println(Chinese.country);
System.out.println(c1.country);
// 这个会出现空指针异常,因为name是实例变量。
//System.out.println(c1.name);
}
}
class Chinese{
// 实例变量
String idCard;
String name;
// 静态变量
static String country = "中国";
//构造方法
public Chinese(String x, String y){
idCard = x;
name = y;
}
}
public class StaticTest04{
public static void main(String[] args){
// 这是比较正规的方式,静态方法采用“类名.”
StaticTest04.doSome();
//对象
StaticTest04 st = new StaticTest04();
// 用“引用.”访问
st.doSome();
// 空引用
st = null;
// 不会出现空指针异常
st.doSome(); // 这个代码在最终执行的时候还是会转变为:StaticTest04.doSome();
// 实例方法doOther()
// 对象级别的方法(先new对象,通过“引用.”来访问)
//错误: 无法从静态上下文中引用非静态 方法 doOther()
//StaticTest04.doOther();
StaticTest04 st2 = new StaticTest04();
st2.doOther();
// 空引用
st2 = null;
// 空引用调用实例方法会出现什么问题?空指针异常。
//st2.doOther();
}
// 静态方法(静态方法不需要new对象,直接使用“类名.”来访问)
// 但是也可以使用“引用.”来访问,不建议用。(因为其他程序员会感到困惑。)
public static void doSome(){
System.out.println("静态方法doSome()执行了!");
}
// 实例方法(实例相关的都需要new对象,使用"引用."来访问。)
public void doOther(){
System.out.println("实例方法doOther执行了!");
}
}
// 从第一天开始讲解HelloWorld到目前为止,一个类当中一共就写过这些东西。
/*
类{
// 实例相关的都是需要new对象的,通过"引用."访问。
实例变量;
实例方法;
// 静态相关的都是采用“类名.”访问。也可以使用“引用.”,只不过不建议。
静态变量;
静态方法;
}
*/
参考标准:当这个方法体当中,直接访问了实例变量,这个方法一定是实例方法。
/*
关于方法来说,什么时候定义为实例方法?什么时候定义为静态方法?
有没有参考标准。
此方法一般都是描述了一个行为,如果说该行为必须由对象去触发。
那么该方法定义为实例方法。
参考标准:
当这个方法体当中,直接访问了实例变量,这个方法一定是实例方法。
我们以后开发中,大部分情况下,如果是工具类的话,工具类当中的方法
一般都是静态的。(静态方法有一个优点,是不需要new对象,直接采用类名
调用,极其方便。工具类就是为了方便,所以工具类中的方法一般都是static的。)
什么是工具类?????
以后讲。(工具类就是为了方便编程而开发的一些类。)
类 = 属性 + 方法
属性描述的是:状态
方法描述的是:行为动作
一个方法代表了一个动作。
什么时候方法定义为实例方法?
张三考试,得分90
李四考试,得分100
不同的对象参加考试的结果不同。
我们可以认定“考试”这个行为是与对象相关的行为。
建议将“考试”这个方法定义为实例方法。
*/
public class StaticTest05{
public static void main(String[] args){
User u = new User();
System.out.println(u.getId()); //0
//User.getId();
User.printName2();
User x = new User();
x.printName1();
// 访问T的id怎么访问
/*
T t = new T();
System.out.println(t.id);
*/
User y = new User();
y.printName1();
}
}
class T{
// 实例变量
int id;
}
// 我之前怎么说的?实例变量访问的语法机制是什么?
// 语法:引用.实例变量名
class User{
// 实例变量,需要对象
private int id;
// 实例变量
private String name; // 首先先分析的是,这个name是对象级别的,一个对象一份。
//分析这个方法应该定义为实例方法还是静态方法呢?
// 打印用户的名字这样的一个方法。
public void printName1(){
System.out.println(name);
}
public static void printName2(){
// 输出的是一个对象的name
//System.out.println(name);
}
public void setId(int i){
id = i;
}
public int getId(){
return id;
}
/*
public static int getId(){
return id;
}
*/
}
静态代码块
/*
1、使用static关键字可以定义:静态代码块
2、什么是静态代码块,语法是什么?
static {
java语句;
java语句;
}
3、static静态代码块在什么时候执行呢?
类加载时执行。并且只执行一次。
静态代码块有这样的特征/特点。
4、注意:静态代码块在类加载时执行,并且在main方法执行之前执行。
5、静态代码块一般是按照自上而下的顺序执行。
6、静态代码块有啥作用,有什么用?
第一:静态代码块不是那么常用。(不是每一个类当中都要写的东西。)
第二:静态代码块这种语法机制实际上是SUN公司给我们java程序员的一个特殊的时刻/时机。
这个时机叫做:类加载时机。
具体的业务:
项目经理说了:大家注意了,所有我们编写的程序中,只要是类加载了,请记录一下
类加载的日志信息(在哪年哪月哪日几时几分几秒,哪个类加载到JVM当中了)。
思考:这些记录日志的代码写到哪里呢?
写到静态代码块当中。
*/
public class StaticTest06{
// 静态代码块(特殊的时机:类加载时机。)
static {
System.out.println("A");
}
// 一个类当中可以编写多个静态代码块
static {
System.out.println("B");
}
// 入口
public static void main(String[] args){
System.out.println("Hello World!");
}
// 编写一个静态代码块
static{
System.out.println("C");
}
}
/*
A
B
C
Hello World!
*/
代码执行顺序
/*
栈:方法只要执行,会压栈。(局部变量)
堆:new出来的对象都在堆中。垃圾回收器主要针对。(实例变量)
方法区:类的信息,字节码信息,代码片段。(静态变量)
方法的代码片段放在方法区,但是方法执行过程当中需要的内存在栈中。
*/
public class StaticTest07{
// 静态变量在什么时候初始化?类加载时初始化。
// 静态变量存储在哪里?方法区
static int i = 100;
// 静态代码块什么时候执行?类加载时执行。
static {
// 这里可以访问i吗?
System.out.println("i = " + i);
}
// 实例变量
int k = 111; // k变量是实例变量,在构造方法执行时内存空间才会开辟。
static {
//k变量可以访问吗?
// static静态代码块在类加载时执行,并且只执行一次。
// 类加载时,k变量空间还没有开辟出来呢。
//错误: 无法从静态上下文中引用非静态 变量 k
//System.out.println("k = " + k);
// 这里可以访问name吗?
//错误: 非法前向引用
// 静态代码块和静态变量都在类加载的时候执行,时间相同,只能靠代码的顺序来决定谁先谁后。
//System.out.println("name = " + name);
}
// 静态变量在静态代码块下面。
static String name = "zhangsan";
//入口(main方法执行之前实际上执行了很多代码)
public static void main(String[] args){
System.out.println("main begin");
System.out.println("main over");
}
}
/*
总结:
到目前为止,你遇到的所有java程序,有顺序要求的是哪些?
第一:对于一个方法来说,方法体中的代码是有顺序的,遵循自上而下的顺序执行。
第二:静态代码块1和静态代码块2是有先后顺序的。
第三:静态代码块和静态变量是有先后顺序的。
*/
实例代码块
/*
1、除了静态代码块之外,还有一种语句块叫做:实例语句块
2、实例语句在类加载是并没有执行。
3、实例语句语法?
{
java语句;
java语句;
java语句;
}
4、实例语句块在什么时候执行?
只要是构造方法执行,必然在构造方法执行之前,自动执行“实例语句块”中的代码。
实际上这也是SUN公司为java程序员准备一个特殊的时机,叫做对象构建时机。
*/
public class InstanceCode{
//入口
public static void main(String[] args){
System.out.println("main begin");
new InstanceCode();
new InstanceCode();
new InstanceCode("abc");
new InstanceCode("xyz");
}
//实例语句块
{
System.out.println("实例语句块执行!");
}
// Constructor
public InstanceCode(){
System.out.println("无参数构造方法");
}
// Constructor
public InstanceCode(String name){
System.out.println("有参数的构造方法");
}
}
代码执行顺序
//判断以下程序的执行顺序
public class CodeOrder{
// 静态代码块
static{
System.out.println("A");
}
// 入口
// A X Y C B Z
public static void main(String[] args){
System.out.println("Y");
new CodeOrder();
System.out.println("Z");
}
// 构造方法
public CodeOrder(){
System.out.println("B");
}
// 实例语句块
{
System.out.println("C");
}
// 静态代码块
static {
System.out.println("X");
}
}
this
/*
this:
1、this是一个关键字,全部小写。
2、this是什么,在内存方面是怎样的?
一个对象一个this。
this是一个变量,是一个引用。this保存当前对象的内存地址,指向自身。
所以,严格意义上来说,this代表的就是“当前对象”
this存储在堆内存当中对象的内部。
3、this只能使用在实例方法中。谁调用这个实例方法,this就是谁。
所以this代表的是:当前对象。
4、“this.”大部分情况下是可以省略的。
5、为什么this不能使用在静态方法中??????
this代表当前对象,静态方法中不存在当前对象。
*/
public class ThisTest01{
public static void main(String[] args){
Customer c1 = new Customer("张三");
c1.shopping();
Customer c2 = new Customer("李四");
c2.shopping();
Customer.doSome();
}
}
// 顾客类
class Customer{
// 属性
// 实例变量(必须采用“引用.”的方式访问)
String name;
//构造方法
public Customer(){
}
public Customer(String s){
name = s;
}
// 顾客购物的方法
// 实例方法
public void shopping(){
// 这里的this是谁?this是当前对象。
// c1调用shopping(),this是c1
// c2调用shopping(),this是c2
//System.out.println(this.name + "正在购物!");
// this. 是可以省略的。
// this. 省略的话,还是默认访问“当前对象”的name。
System.out.println(name + "正在购物!");
}
// 静态方法
public static void doSome(){
// this代表的是当前对象,而静态方法的调用不需要对象。矛盾了。
// 错误: 无法从静态上下文中引用非静态 变量 this
//System.out.println(this);
}
}
class Student{
// 实例变量,怎么访问?必须先new对象,通过“引用.”来访问。
String name = "zhangsan";
// 静态方法
public static void m1(){
//System.out.println(name);
// this代表的是当前对象。
//System.out.println(this.name);
// 除非你这样
Student s = new Student();
System.out.println(s.name);
}
//为什么set和get方法是实例方法?
public static void setName(String s){
name = s;
}
public String getName(){
return name;
}
// 又回到上午的问题了?什么时候方法定义为实例方法,什么时候定义为静态方法?
// 如果方法中直接访问了实例变量,该方法必须是实例方法。
}
015-this关键字
// 分析:i变量在main方法中能不能访问????
public class ThisTest02{
// 实例变量
int i = 100; // 这个i变量是不是必须先new对象才能访问。
// 静态变量
static int k = 111;
// 静态方法
public static void main(String[] args){
// 错误: 无法从静态上下文中引用非静态 变量 i
// System.out.println(i);
// 怎么样访问i
ThisTest02 tt = new ThisTest02();
System.out.println(tt.i);
// 静态变量用“类名.”访问。
System.out.println(ThisTest02.k);
// 类名. 能不能省略?
// 可以
System.out.println(k);
}
}
this什么时候不能省略:在实例方法中,或者构造方法中,为了区分局部变量和实例变量,这种情况下:this. 是不能省略的。
this.no = no; // this. 的作用是:区分局部变量和实例变量。
/*
1、this可以使用在实例方法中,不能使用在静态方法中。
2、this关键字大部分情况下可以省略,什么时候不能省略呢?
在实例方法中,或者构造方法中,为了区分局部变量和实例变量,
这种情况下:this. 是不能省略的。
*/
public class ThisTest03{
public static void main(String[] args){
Student s = new Student();
s.setNo(111);
s.setName("张三");
System.out.println("学号:" + s.getNo());
System.out.println("姓名:" + s.getName());
Student s2 = new Student(2222, "李四");
System.out.println("学号:" + s2.getNo());
System.out.println("姓名:" + s2.getName());
}
}
// 分析一下:以下代码哪里写的不好。
// 学生类
class Student{
//学号
private int no;
//姓名
private String name;
//构造方法无参
public Student(){
}
//构造方法有参
/*
public Student(int i, String s){
no = i;
name = s;
}
*/
// 上面的构造方法也增强以下可读性
public Student(int no, String name){
this.no = no;
this.name = name;
}
// setter and getter方法
/*
public void setNo(int i){
no = i;
}
*/
/*
public void setNo(int no){ // 就近原则。
no = no; //这两个no都是局部变量no,和实例变量no没关系。
}
*/
public void setNo(int no){
//no是局部变量
//this.no 是指的实例变量。
this.no = no; // this. 的作用是:区分局部变量和实例变量。
}
public int getNo(){
return no;
//return this.no;
}
/*
public void setName(String s){
name = s;
}
*/
/*
public void setName(String name){ // 就近原则
name = name; //这两个name都是局部变量name,和实例变量name没关系。
}
*/
public void setName(String name){
this.name = name;
}
/*
public String getName(){
return name;
}
*/
public String getName(){ // getName实际上获取的是“当前对象”的名字。
//return this.name; // 严格来说,这里是有一个 this. 的。只不过这个 this. 是可以省略的。
return name;
}
}
this()
新语法:通过当前的构造方法去调用另一个本类的构造方法,可以使用以下语法格式:this(实际参数列表);
死记硬背:对于this()的调用只能出现在构造方法的第一行。
/*
1、this除了可以使用在实例方法中,还可以用在构造方法中。
2、新语法:通过当前的构造方法去调用另一个本类的构造方法,可以使用以下语法格式:
this(实际参数列表);
通过一个构造方法1去调用构造方法2,可以做到代码复用。
但需要注意的是:“构造方法1”和“构造方法2” 都是在同一个类当中。
3、this() 这个语法作用是什么?
代码复用。
4、死记硬背:
对于this()的调用只能出现在构造方法的第一行。
*/
public class ThisTest04{
public static void main(String[] args){
// 调用无参数构造方法
Date d1 = new Date();
d1.detail();
// 调用有参数构造方法
Date d2 = new Date(2008, 8, 8);
d2.detail();
}
}
/*
需求:
1、定义一个日期类,可以表示年月日信息。
2、需求中要求:
如果调用无参数构造方法,默认创建的日期为:1970年1月1日。
当然,除了调用无参数构造方法之外,也可以调用有参数的构造方法来创建日期对象。
*/
class Date{ // 以后写代码都要封装,属性私有化,对外提供setter and getter
//年
private int year;
//月
private int month;
//日
private int day;
// 构造方法无参
// 调用无参数构造方法,初始化的日期是固定值。
public Date(){
//错误: 对this的调用必须是构造器中的第一个语句
//System.out.println(11);
/*
this.year = 1970;
this.month = 1;
this.day = 1;
*/
this(1970, 1, 1);
}
// 构造方法有参数
public Date(int year, int month, int day){
this.year = year;
this.month = month;
this.day = day;
}
// 提供一个可以打印日期的方法
public void detail(){
//System.out.println(year + "年" + month + "月" + day + "日");
System.out.println(this.year + "年" + this.month + "月" + this.day + "日");
}
//setter and getter
public void setYear(int year){
// 设立关卡(有时间可以设立关卡)
this.year = year;
}
public int getYear(){
return year;
}
public void setMonth(int month){
// 设立关卡(有时间可以设立关卡)
this.month = month;
}
public int getMonth(){
return month;
}
public void setDay(int day){
// 设立关卡(有时间可以设立关卡)
this.day = day;
}
public int getDay(){
return day;
}
}
总结
/*
到目前为止一个类当中可以出现的:
类体{
实例变量;
实例方法;
静态变量;
静态方法;
构造方法;
静态代码块;
实例语句块;
方法(){
// 局部变量
int i = 100;
}
}
*/
public class Review{ // 类
// 类加载机制中,是这样的:在程序执行之前,凡是需要加载的类全部加载到JVM当中。
// 先完成加载才会执行main方法。
static{
System.out.println("Review类加载时执行!");
}
// 入口
// 静态方法
public static void main(String[] args){
// 局部变量
int i = 100;
// 完成一个对象的一连串动作。
// 一个学生在教室先学习,学习完成之后去餐厅吃饭。
Student s1 = new Student();
// 先学习,所有调用学习这个实例方法。
s1.study();
Student s2 = new Student();
}
}
// 学生类
class Student{
static{
System.out.println("Student类加载时执行!");
}
// 学号
private int no; // 实例变量
// 姓名
private String name;
// 学生有静态变量吗?
// 类级别的属性
static String job = "学习";
{
System.out.println("实例语句块,构造方法执行一次,这里就执行一次!");
}
// 构造方法
public Student(){
// 假设调用无参数的构造方法,默认创建的学生学号是100,名字是zhangsan
this(100, "zhangsan"); // this() 在这里也使用了。
}
public Student(int no, String name){
this.no = no; // 这里使用了this
this.name = name;
}
// 封装
// setter and getter方法
public void setName(String name){
this.name = name;
}
public String getName(){
return name;
}
public void setNo(int no){
this.no = no;
}
public int getNo(){
return no;
}
// 提供两个实例方法
public void study(){
// 私有的是可以在本类中访问的。在其它类中必须使用set和get方法。
//System.out.println(this.name + "正在努力的学习!");
//System.out.println(name + "正在努力的学习!");
// 在实例方法中调用本类其它的实例方法。
System.out.println(this.getName() + "正在努力的学习!");
//System.out.println(getName() + "正在努力的学习!");
// 方法执行到此处表示学习完成了,去吃饭。
//this.eat();
// this.可以省略
// 编译器检测到eat()方法是实例方法,会自动在eat()方法前添加 this.
eat();
}
public void eat(){ // 实例方法
System.out.println(this.getName() + "在餐厅吃饭呢!!!");
// 调用静态m1()方法
// 静态方法使用“类名.”的方式访问
// Student.m1();
// 类名. 可以省略吗?可以。
// java编译器会自动在m1()方法之前添加“类名.”,因为检测到m1()方法是一个静态方法。
m1();
}
// 提供两个静态方法
public static void m1(){
System.out.println("Student's m1 method execute!");
// 调用m2()方法
//Student.m2();
m2();
}
public static void m2(){
System.out.println("Student's m2 method execute!");
System.out.println("工作性质:" + job);
// 编译器检测到job是一个静态变量,所以这里会自动在job前添加:Student.
//System.out.println("工作性质:" + Student.job);
}
}
大结论:
只要负责调用的方法a和被调用的方法b在同一个类当中:
this. 可以省略
类名. 可以省略
/*
程序再怎么变化,万变不离其宗,有一个固定的规律:
所有的实例相关的都是先创建对象,通过“引用.”来访问。
所有的静态相关的都是直接采用“类名.”来访问。
你有发现一些问题吗?
总有一些是需要记忆的,在这些记忆的基础之上进行分析。
大结论:
只要负责调用的方法a和被调用的方法b在同一个类当中:
this. 可以省略
类名. 可以省略
*/
public class Review02{
int i = 100;
static int j = 1000;
public void m1(){
// 访问其他类的静态方法
T.t1();
// 访问其他类的实例方法
T t = new T();
t.t2();
}
public void m2(){}
// 实例方法
public void x(){ // 这个方法是实例方法,执行这个方法的过程中,当前对象是存在的。
m1();
m2();
m3();
m4();
System.out.println(i); // System.out.println(this.i);
System.out.println(j); // System.out.println(Review02.i);
}
public static void m3(){}
public static void m4(){}
// 问?你怎么分析这个程序?
/*
第一步:
main方法是静态的,JVM调用main方法的时候直接采用的是“类名.”的方式。
所以main方法中没有this。
第二步:
m1() 和 m2() 方法是实例方法,按照java语法规则来说,实例方法必须先
new对象,通过“引用.”的方式访问。
*/
public static void main(String[] args){
// 编译报错。
//m1();
//m2();
m3(); // 编译器会自动识别m3()静态方法,结果是:Review02.m3();
m4(); // Review02.m4();
//System.out.println(i); // 报错
System.out.println(j); // 可以
// 想访问m1() m2()还有i,你在static方法中只能自己new
Review02 r = new Review02();
System.out.println(r.i);
r.m1();
r.m2();
// 局部变量,局部变量访问的时候是不需要“xxx.”的
int k = 10000;
System.out.println(k);
}
}
class T{
// 静态方法
public static void t1(){
}
//实例方法
public void t2(){
}
}
12 第十二章 继承(Inheritance)
12.1 章节目标与知识框架
12.1.1 章节目标
了解继承在java中有什么作用,以及在代码上如何实现继承。
12.1.2 知识框
12.2 继承
12.2.1 继承概述(理解)
继承是面向对象三大特征之一,封装居首位,封装之后形成了独立体,独立体A和独立体B之间可能存在继承关系。其实程序中的继承灵感来自于现实生活,在现实生活中继承处处可见,例如,儿子继承了父亲的财产,儿子不需要努力就很有钱。
生活中的继承:
继承时子类继承父类的特征和行为,使得子类对象(实例)具有父类的属性,或子类从父类继承方法,使得子类具有与父类相同的行为。兔子和羊属于食草动物类,狮子和豹属于食肉动物类。食草动物和食肉动物又是属于动物类。所以继承需要符合的关系是:is-a(Birdis-aAnimal),父类更通用,子类更具体。虽然食草动物和食肉动物都是属于动物,但是两者的属性和行为上有差别,所以子类会具有父类的一般特性也会具有自身的特性。
为什么要使用继承机制?在不同的类中也可能会有共同的特征和动作,可以把这些共同的特征和动作放在一个类中,让其它类共享。因此可以定义一个通用类,然后将其扩展为其它多个特定类,这些特定类继承通用类中的特征和动作。继承是Java中实现软件重用的重要手段,避免重复,易于维护。
12.2.2 如何继承(掌握)
java中继承的语法格式:
为什么需要继承,接下来我们用以下这个需求来说明一下:
以上两个类分别描述了“银行账户类”和“信用账户类”,信用账户类除了具有银行账户类的特征之外还有自己的特性,按照以上代码的编写方式,程序将会非常的臃肿,我们将上面的程序修改为继承方式,请看代码:
运行结果如下图所示:
通过以上的代码,我们可以看到继承可以解决代码臃肿的问题。换句话说,继承解决了代码复用的问题(代码复用就是代码的重复利用),这是继承机制最基本的作用。
继承的作用中除了可以让代码复用之外,还有非常重要的两个作用,那就是有了继承之后才会衍生出方法的覆盖和多态机制。这两个作用我们在后续的课程中会详细讲解。目前先理解一下继承可以做到代码复用的效果。
12.2.3 继承的相关特性(掌握)
那么,对于Java的继承有哪些特性需要大家理解和记忆呢?我来罗列一下:
①B类继承A类,则称A类为超类(superclass)、父类、基类,B类则称为子类(subclass)、派生类、扩展类。
②java中的继承只支持单继承,不支持多继承,C++中支持多继承,这也是java体现简单性的一点,换句话说,java中不允许这样写代码:classBextendsA,C{}。
③虽然java中不支持多继承,但有的时候会产生间接继承的效果,例如:classCextendsB,classBextendsA,也就是说,C直接继承B,其实C还间接继承A。
④java中规定,子类继承父类,除构造方法和被private修饰的数据不能继承外,剩下都可以继承。
⑤java中的类没有显示的继承任何类,则默认继承Object类,Object类是java语言提供的根类(老祖宗类),也就是说,一个对象与生俱来就有Object类型中所有的特征。
⑥继承也存在一些缺点,例如:CreditAccount类继承Account类会导致它们之间的耦合度非常高,Account类发生改变之后会马上影响到CreditAccount类。
12.2.4 对继承自Object类的方法的测试(理解)
接下来我们来测试一下从Object类中继承过来的方法,先来看一下Object类的部分源代码:
我们来尝试调用这个方法,请看代码以及执行结果:
运行结果如下图所示:
虽然输出结果看不懂,但是起码看到这个字符串当中确实是有一个“@”符号的,toString()方法确实被ExtendsTest类继承过来了。
12.3 章节小结
通过本章节的学习,要求大家理解什么是继承,为什么要继承,继承有什么好处,继承的特性有哪些。另外要求大家掌握的是继承的代码如何实现。还有Java中任何一个类的定义都会自带Object类的特征,这是因为Object是所有类的超类。
12.4 难点疑惑
对于本章节内容来说,难点主要是类和类继承之后的代码执行顺序,请看程序:
我们对以上的程序进行分析,子类H2继承H1,newH2()执行的时候,会先进行类加载,先加载H2的父类H1,所以H1当中的静态代码块先执行,然后再执行H2中的静态代码块,静态代码块执行结束之后,不会马上执行构造方法,代码块会先执行,Java中有一条规则:子类构造方法执行前先执行父类的构造方法(学习super之后大家就知道了),所以父类H1的代码块先执行,再执行H1的构造方法,然后再执行H2的代码块,最后执行H2的构造方法。我们对以上程序进行编译并运行,请看下图结果:
通过以上的测试结果,可以看出以上我们的分析是正确的。
12.5 章节习题
第一题:定义猴子类,猴子有名字和性别等属性,并且定义猴子说话的方法,定义人类,人有名字和性别等属性,并且定义人说话的方法。使用继承,让代码具有复用性。
第二题:定义动物类,动物有名字属性,并且定义动物移动的方法,定义鱼类,鱼有名字属性,有颜色属性,并且定义鱼移动的方法。使用继承,让代码具有复用性。
12.6 习题答案
第一题答案:
第二题答案:
12.7 day17课堂笔记
上接day16
...
3、继承extends
3.1、什么是继承,有什么用?
继承:在现实世界当中也是存在的,例如:父亲很有钱,儿子不用努力也很有钱。
继承的作用:
基本作用:子类继承父类,代码可以得到复用。(这个不是重要的作用,是基本作用。)
主要(重要)作用:因为有了继承关系,才有了后期的方法覆盖和多态机制。
3.2、继承的相关特性
① B类继承A类,则称A类为超类(superclass)、父类、基类,B类则称为子类(subclass)、派生类、扩展类。
class A{}
class B extends A{}
我们平时聊天说的比较多的是:父类和子类。
superclass 父类
subclass 子类
② java 中的继承只支持单继承,不支持多继承,C++中支持多继承,这也是 java 体现简单性的一点,换句话说,java 中不允许这样写代码:class B extends A,C{ } 这是错误的。
③ 虽然 java 中不支持多继承,但有的时候会产生间接继承的效果,
例如:class C extends B,class B extends A,也就是说,C 直接继承 B,其实 C 还间接继承 A。
④ java 中规定,子类继承父类,除构造方法不能继承之外,剩下都可以继承。但是私有的属性无法在子类中直接访问。(父类中private修饰的不能在子类中直接访问。可以通过间接的手段来访问。)
⑤ java 中的类没有显示的继承任何类,则默认继承 Object类,Object类是 java 语言提供的根类(老祖宗类),也就是说,一个对象与生俱来就有Object类型中所有的特征。
⑥ 继承也存在一些缺点,例如:CreditAccount 类继承 Account 类会导致它们之间的耦合度非常高,Account 类发生改变之后会马上影响到 CreditAccount 类
1、继承extends
1.1、测试:子类继承父类之后,能使用子类对象调用父类方法吗?
可以,因为子类继承了父类之后,这个方法就属于子类了。
当然可以使用子类对象来调用。
1.2、在实际开发中,满足什么条件的时候,我可以使用继承呢?
凡是采用“is a”能描述的,都可以继承。
例如:
Cat is a Animal:猫是一个动物
Dog is a Animal:狗是一个动物
CreditAccount is a Account:信用卡账户是一个银行账户
....
假设以后的开发中有一个A类,有一个B类,A类和B类确实也有重复的代码,那么他们两个之间就可以继承吗?不一定,还是要看一看它们之间是否能够使用is a来描述。
class Customer{
String name; // 名字
// setter and getter
}
class Product{
String name; // 名字
// setter and getter
}
class Product extends Customer{
}
以上的继承就属于很失败的。因为:Product is a Customer,是有违伦理的。
1.3、任何一个类,没有显示继承任何类,默认继承Object,那么Object类当中有哪些方法呢?老祖宗为我们提供了哪些方法?
以后慢慢的大家一定要适应看JDK的源代码(多看看牛人写的程序自己才会变成牛人。)
先模仿后超越。
java为什么比较好学呢?
是因为Java内置了一套庞大的类库,程序员不需要从0开始写代码,程序员可以
基于这套庞大的类库进行“二次”开发。(开发速度较快,因为JDK内置的这套库
实现了很多基础的功能。)
例如:String是SUN编写的字符串类、System是SUN编写的系统类。
这些类都可以拿来直接使用。
JDK源代码在什么位置?
C:\Program Files\Java\jdk-13.0.2\lib\src.zip
你现在能看懂以下代码了吗?
System.out.println("Hello World!");
System.out 中,out后面没有小括号,说明out是变量名。
另外System是一个类名,直接使用类名System.out,说明out是一个静态变量。
System.out 返回一个对象,然后采用“对象.”的方式访问println()方法。
我们研究了一下Object类当中有很多方法,大部分看不懂,其中有一个叫做toString()的,我们进行了测试,发现:
System.out.println(引用);
当直接输出一个“引用”的时候,println()方法会先自动调用“引用.toString()”,然后输出toString()方法的执行结果。
// editPlus中蓝色是关键字
// 黑色是标识符
// System.out.println("Hello World!");
// 以上代码中:System、out、println都是标识符。
// 在 editplus中的红色字体,表示这个类是SUN的JDK写好的一个类。
public class Test{
// 静态变量
static Student stu = new Student();
// 入口
public static void main(String[] args){
//拆分为两行
Student s = Test.stu;
s.exam();
//合并代码
Test.stu.exam();
System.out.println("Hello World!");
}
}
class Student{
// 实例方法
public void exam(){
System.out.println("考试。。。。。");
}
}
12.7.1 day17代码
// 分析以下程序存在什么问题?代码臃肿。代码没有得到重复利用。
public class ExtendsTest01{
public static void main(String[] args){
// 创建普通账户
Account act = new Account();
act.setActno("1111111");
act.setBalance(10000);
System.out.println(act.getActno() + ",余额" + act.getBalance());
// 创建信用账户
CreditAccount ca = new CreditAccount();
ca.setActno("2222222");
ca.setBalance(-10000);
ca.setCredit(0.99);
System.out.println(ca.getActno() + ",余额" + ca.getBalance() + ",信誉度" + ca.getCredit());
}
}
// 银行账户类
// 账户的属性:账号、余额
class Account{
// 属性
private String actno;
private double balance;
// 构造方法
public Account(){
}
public Account(String actno, double balance){
this.actno = actno;
this.balance = balance;
}
// setter and getter
public void setActno(String actno){
this.actno = actno;
}
public String getActno(){
return actno;
}
public void setBalance(double balance){
this.balance = balance;
}
public double getBalance(){
return balance;
}
}
// 其它类型的账户:信用卡账户
// 账号、余额、信誉度
class CreditAccount{
// 属性
private String actno;
private double balance;
private double credit;
// 构造方法
public CreditAccount(){
}
// setter and getter方法
public void setActno(String actno){
this.actno = actno;
}
public String getActno(){
return actno;
}
public void setBalance(double balance){
this.balance = balance;
}
public double getBalance(){
return balance;
}
public void setCredit(double credit){
this.credit = credit;
}
public double getCredit(){
return credit;
}
}
// 使用继承机制来解决代码复用问题。
// 继承也是存在缺点的:耦合度高,父类修改,子类受牵连。
public class ExtendsTest02{
public static void main(String[] args){
// 创建普通账户
Account act = new Account();
act.setActno("1111111");
act.setBalance(10000);
System.out.println(act.getActno() + ",余额" + act.getBalance());
// 创建信用账户
CreditAccount ca = new CreditAccount();
ca.setActno("2222222");
ca.setBalance(-10000);
ca.setCredit(0.99);
System.out.println(ca.getActno() + ",余额" + ca.getBalance() + ",信誉度" + ca.getCredit());
}
}
// 银行账户类
// 账户的属性:账号、余额
class Account{ // 父类
// 属性
private String actno;
private double balance;
// 构造方法
public Account(){
}
public Account(String actno, double balance){
this.actno = actno;
this.balance = balance;
}
// setter and getter
public void setActno(String actno){
this.actno = actno;
}
public String getActno(){
return actno;
}
public void setBalance(double balance){
this.balance = balance;
}
public double getBalance(){
return balance;
}
}
// 其它类型的账户:信用卡账户
// 账号、余额、信誉度
class CreditAccount extends Account{ //子类
// 属性
private double credit;
// 构造方法
public CreditAccount(){
}
public void doSome(){
//错误: actno 在 Account 中是 private 访问控制
//System.out.println(actno);
// 间接访问
//System.out.println(this.getActno());
System.out.println(getActno());
}
// setter and getter方法
public void setCredit(double credit){
this.credit = credit;
}
public double getCredit(){
return credit;
}
}
class A
{
}
class B
{
}
class C extends A
{
}
class D extends B
{
}
// 语法错误
// java只允许单继承。不允许多继承。java是简单的。C++支持多重继承。
// C++更接近现实一些。因为在现实世界中儿子同时继承父母两方特征。
/*
class E extends A, B
{
}
*/
class X
{
}
class Y extends X
{
}
class M extends X
{
}
// 其实这也说明了Z是继承X和Y的。
// 这样描述:Z直接继承了Y,Z间接继承了X
class Z extends Y
{
}
/*
Z继承了Y
Y继承了X
X继承了Object
Z对象具有Object对象的特征(基因)。
Object是所有类的超类。老祖宗。类体系结构中的根。
java这么庞大的一个继承结构,最顶点是:Object
*/
/*
测试:子类继承父类之后,能使用子类对象调用父类方法吗
实际上以上的这个问题问的有点蹊跷!!!!!
哪里蹊跷?“能使用子类对象调用父类方法”
本质上,子类继承父类之后,是将父类继承过来的方法归为自己所有。
实际上调用的也不是父类的方法,是他子类自己的方法(因为已经继承过来了
就属于自己的。)。
*/
public class ExtendsTest04{
public static void main(String[] args){
// 创建子类对象
Cat c = new Cat();
// 调用方法
c.move();
// 通过子类对象访问name可以吗?
System.out.println(c.name);
}
}
// 父类
//class Animal extends Object {
class Animal{
// 名字(先不封装)
String name = "XiaoHua"; //默认值不是null,给一个XiaoHua
// 提供一个动物移动的方法
public void move(){
System.out.println(name + "正在移动!");
}
}
// Cat子类
// Cat继承Animal,会将Animal中所有的全部继承过来。
class Cat extends Animal{
}
//默认继承Object,Object类中有哪些方法呢?
/*
public class Object {
// 注意:当源码当中一个方法以“;”结尾,并且修饰符列表中有“native”关键字
// 表示底层调用C++写的dll程序(dll动态链接库文件)
private static native void registerNatives();
// 静态代码块
static {
// 调用registerNatives()方法。
registerNatives();
}
// 无参数构造方法
@HotSpotIntrinsicCandidate
public Object() {}
// 底层也是调用C++
@HotSpotIntrinsicCandidate
public final native Class<?> getClass();
// 底层也是调用C++
@HotSpotIntrinsicCandidate
public native int hashCode();
// equals方法你应该能看懂。
// public是公开的
// boolean 是方法的返回值类型
// equals 是一个方法名:相等
// (Object obj) 形参
// 只不过目前还不知道这个方法存在的意义。
public boolean equals(Object obj) {
//方法体
return (this == obj);
}
// 已有对象a,想创建一个和a一模一样的对象,你可以调用这个克隆方法。
// 底层也是调用C++
@HotSpotIntrinsicCandidate
protected native Object clone() throws CloneNotSupportedException;
// 一会我们可以测试一下toString()方法。
// public表示公共的
// String 是返回值类型,toString()方法执行结束之后返回一个字符串。
// toString 这是方法名。
// () 表示形参个数为0
public String toString() {
return getClass().getName() + "@" + Integer.toHexString(hashCode());
}
@HotSpotIntrinsicCandidate
public final native void notify();
@HotSpotIntrinsicCandidate
public final native void notifyAll();
public final void wait() throws InterruptedException {
wait(0L);
}
public final native void wait(long timeoutMillis) throws InterruptedException;
public final void wait(long timeoutMillis, int nanos) throws InterruptedException {
if (timeoutMillis < 0) {
throw new IllegalArgumentException("timeoutMillis value is negative");
}
if (nanos < 0 || nanos > 999999) {
throw new IllegalArgumentException(
"nanosecond timeout value out of range");
}
if (nanos > 0 && timeoutMillis < Long.MAX_VALUE) {
timeoutMillis++;
}
wait(timeoutMillis);
}
@Deprecated(since="9")
protected void finalize() throws Throwable { }
}
*/
public class ExtendsTest05 {
// ExtendsTest05默认继承Object
// ExtendsTest05类当中是有toString()方法
// 不过toString()方法是一个实例方法,需要创建对象才能调用。
/*
public String toString() {
return getClass().getName() + "@" + Integer.toHexString(hashCode());
}
*/
public static void main(String[] args){
// 分析这个代码可以执行吗?
//ExtendsTest05.toString();
// 先new对象
ExtendsTest05 et = new ExtendsTest05();
String retValue = et.toString();
// 2f92e0f4 可以“等同”看做对象在堆内存当中的内存地址。
// 实际上是内存地址经过“哈希算法”得出的十六进制结果。
System.out.println(retValue); // ExtendsTest05@2f92e0f4
// 创建对象
Product pro = new Product();
String retValue2 = pro.toString();
System.out.println(retValue2); // Product@5305068a
// 以上两行代码能否合并为一行!!!可以
System.out.println(pro.toString()); //Product@5305068a
// 如果直接输出“引用”呢???????
System.out.println(pro); //Product@5305068a
System.out.println(100);
System.out.println(true);
// Product@5305068a
System.out.println(pro); // println方法会自动调用pro的toString()方法。
}
}
class Product{
/*
public String toString() {
return getClass().getName() + "@" + Integer.toHexString(hashCode());
}
*/
}