针对Android的Java基础学习


Java 基础第一讲:Java 的故事和 Java 编程环境搭建 13 Sep http://android.yaohuiji.com/about(转) 一、《Java 基础讲座》前言: 学习 Android 如果 Java 基础不好,势必举步维艰,所以从今天开始连载《Android 的 Java 基础讲座》。 本系列讲座假想对象是 Java 没有任何基础的朋友。Java 基础知识不是高科技,无数人都会,但是基础好 的并不多,如何用清晰、简练、生动有趣的方式把 Java 的基础概念讲清楚,也比较困难。我能承诺的是用 心去做、持续修订每一讲,本系列分 35 讲,具体目录可以看这里。 二、Java 的故事: 我们知道人可以使用程序软件来操控计算机的硬件来完成一些工作,而软件(程序)则是由某种编程 语言编写的。Java 就是这样一种正在广泛流行和使用的面向对象的编程语言,也是我们今后研究的重点语 言。 1、Java 的诞生 让我们把时空切换到 1982 年,那一年一个伟大的公司诞生于美国斯坦福大学校园,它的名字叫 Sun Microsystems,直译的话叫太阳微系统公司,事实上 Sun 是 Stanford University Network 的缩写。Sun 在 IT 行业中被认为是最具创造性的企业。是极少数几个同时拥有自己微处理器、电脑系统、操作系统的公 司。 1990 年的一天,Sun 的总裁麦克尼利(McNealy)听说他最好的一个工程师詹姆斯.高斯林(James Gosling)打算离职,他感觉事态很严重。直觉告诉他优秀的员工的离去意味着公司正在出大麻烦。麦克尼 利必须找高斯林和其他员工好好谈谈,看看问题出在哪里。 这些员工的意见很一致。Sun 公司本来是硅谷极为特殊的一个公司,以充满活力、富于创新著称。太 阳微系统公司一直很尊重员工,尽量发挥他们的创造力和热情。但是,近年来,太阳微系统公司却越来越 像成熟的大公司了。连哥斯林这样的人,公司也安排他去做一些为老系统写升级软件这种琐碎的工作。正 在扼杀着太阳微系统公司员工的创新思想和工作热情。高斯林他们想做一些伟大的、革命性的事情,但在 Sun 公司现在的状况中是不可能实现的。 Java 的创造者 詹姆斯.高斯林(James Gosling) 随后,麦克尼利采取了一个大胆的举动,他让高斯林自己组建一个完全独立于公司的小组,由小组成 员自己决定工作目标和进度。麦克尼利对哥斯林说:“我不管你们要做什么,要多少钱、多少人,也不管 你们花多长时间做出来,公司都无条件支持。” 这个后来取名为“绿色小组”所要研究的产品就是十年后风靡 IT 界的数字家电、后 PC 设备和家庭网。 事实证明,绿色小组的研究并不十分成功,直到 2001 年,Sun 在数字家电方面的业绩并不很突出。但是, 绿色小组的一个副产品,高斯林发明的 Java 程序设计语言,却深深改变了这个世界„„ 绿色小组成立之初只有 4 个人。他们有一个很模糊的想法,甚至连最终的目标产品是硬件还是软件也 不知道。但是他们知道必须发明一些技术或者产品让 Sun 公司赶上信息领域的下一波大浪潮。 当时人类已经发明了很多种消费类电子产品,包括微机、手机、手持电脑、录相机、电视机、洗衣机、 冰箱、微波炉等等。他们认为要将这些设备数字化并用网络互联讲是今后的方向(物联网?)。绿色小组 将这个需求归结成两个产品原型目标,即发明一种手持遥控设备来实现所有家电设备的互联(硬件);发 明一种程序设计语言,用它来编写能在这些设备上运行的小巧程序(软件)。 高斯林给当时设计了一种运行在虚拟机中的面向对象的语言,起名叫 Oak(橡树,高斯林窗外的一颗 树)。 但是申请注册上商标时,发现 Oak 被其他公司注册了,不得不重新起名。当时他们正在咖啡馆里喝着 印尼爪哇(Java)岛出产的咖啡,有一个人灵机一动说就叫 Java 怎么样,并得到了其他人的赞赏,于是他们 就将这种程序语言命名为 Java。 绿色小组的成员每周工作七天,平均每天工作 12 到 14 个小时,后期工程师们几乎住在实验室,没日 没夜地干,只是每隔几天回家洗澡换衣服。三年以后他们制作出了第一台样机,尽管实现了基本功能,但 造价在一万美元以上,尽管市场前景不明朗,技术上也还有很多问题,Sun 公司的管理层还是用奖金和股 权大大奖励了绿色小组的成员,并加大投入,努力实现产品化。 但是公司内外对其产品都不看好,市场也并不认可。绿色小组的成员在沮丧和失望中度过了整个 1993 年和 1994 年。在士气最低落的时候,大部分成员都离开了绿色小组,有的甚至离开了 Sin 公司。留下来的 人也失去了工作热情。不少人每天早上 11 点钟上班,下午 4 点钟就离开了。有些人一天到晚只是玩游戏, 还有的人则念学术论文。 在黑暗的日子里他们都期待着上天能眷顾他们这些苦命的人,期待着某种奇迹出现„„ 当时互联网已经出现了 20 年左右,但 Ftp 和 Telnet 的方式无法在科研人员之外的人群普及和应用, 1994 年一个名叫网景的公司推出了一种叫做 Netscape 浏览器的东西,加速了互联网的普及;高斯林他们 意识到互联网是一个今后的发展方向。开始制作针对互联网的 Java 应用,希望会有所斩获。 1995 年初的一天,高斯林和以往一样不停地参加各种会议以期让人们认可他们的产品,这次他参加的 是“硅谷-好莱坞”互联网及娱乐业的研讨会。演讲刚开始是,大家对高斯林的讲解意兴阑珊,直到他将鼠 标移向一个分子模型,这个分子模型动起来了,而且会随着鼠标的移动上下翻滚!场面立刻发生了逆转, 会场一下子沸腾起来,人们惊叹不已、啧啧称奇。刹那间,人们对互联网的潜力进行了一番新的审视!也 就在刹那间,这一批有影响力的人成了高斯林最忠实也是最有力的说客。 Java 活下来了,并且成了互联网时代最强势、最具代表性的语言。 2、微软和 Sun 针对 Java 的世纪之战 Java 特点是,一次编写,到处运行,可以适应于任何平台。而互联网就是这样一个可以是任意平台的 超大网络。所以 Java 借着互联网快速发展的东风,扶摇而上,迅速穿红。 面对 Java 金矿,大家都跃跃欲试„„ 这其中要数微软和 Sun 之间的斗争最为典型: 1996 年 9 月的某一天,微软浏览器部门的主管艾达姆·波茨瓦斯几经考量之后,提笔给时任微软 CEO 的比尔·盖茨写了一邮件,他非常恳切地提醒比尔·盖茨注意一个正在形成的威胁。他写到:"必须意识到 Java 不仅仅是一种语言,如果它只是一种语言,我们愿意并且能够容易地为它建立最佳的表现形式,事情 可以圆满解决了。但是事实上,Java 绝不仅仅是一种语言,它是 COM 的替代者!" 而 COM 恰恰是 Windows 的编程模型。而 Java 编程很多时候比 C++编程要容易的多,更致命的是他是跨平台的。波茨瓦斯也提出了 对抗 Java 的方法,就是悄悄地为 Java 提供某些扩展,使得用 Java 编写的程序能够在 Windows 中工作得 更好,但是在其它平台上却不能运行。 盖茨显然被这封信吓坏了,他第二天就回信了:"这可把我吓坏了。我不清楚微软的操作系统要为 Java 的客户应用程序代码提供什么样的东西,而这些东西将足够让它来取代我们的市场地位。了解这一点非常 重要,是应该最优先考虑的事情。"(没想到,这封信成为几年后司法部针对微软的反托拉斯案的呈堂证供。) 自此微软和 Sun 针对 Java 的世纪之战拉开了„„ 第一回合:微软推出 J++语言,并推出了 Visual j++集成编程工具,对 Java 进行了大量的修改。1997 年,Sun 公司以歧视使用 Java 软件,旨在维持其视窗操作系统的垄断地位,违反反垄断法为由起诉微软, 2001 年 1 月,SUN 胜诉,根据双方达成的和解协议,微软不得对 Windows 操作系统中包含的 Java 语言作任 何改动,并获赔 2000 万美元。 第二回合:2001 年年底,微软在推出新版操作系统 Windows XP 和新版 IE 时,故意不安装 Java 软件, 并且推出自己仿造 Java 创造的语言 C#和.net 框架。2002 年的 3 月 8 日,SUN 公司向美国加州地区法庭提 出起诉,称此举造成它直接经济损失高达 10 亿美元。2002 年 6 月,微软干脆称从 2004 年起,因为安全原 因微软的 Windows 操作系统将不再支持 Java 语言。 就在双方口水战日益升级之际,迎来了有关 SUN 诉微软案的第一次听证会。SUN 起诉微软的听证会被 安排在 2002 年 12 月的第一周,当时,在巴尔的摩市下了近三年来最大的一场雪。整个城市几乎都停止运 转。但是弗雷德里克·摩兹法官坚持要求开庭,并且要求几十位与案件有关的律师到场出席;据审判时一 位目击者说,为了保证早上能够到庭,法官他自己在会议室中睡了一晚。 几周后,也就是 2002 年 12 月 23 日,摩兹法官发布了那份长达 42 页的判决书,他裁定微软公司必须 在其 Windows 操作系统和 IE 中发布与其竞争的 Java 编程语言。摩兹法官的意见是:在微软的垄断下,Java 拥有一个并不健全的市场,比如说,大部分 PC 上所安装的 Java 软件要么就是旧版本,要么就是仅适用于 Windows 的版本,这使得其它软件开发者对 Java 平台产生了厌恶的情绪,这些都是因为微软反竞争行为的 结果,看来微软已经利用 Windows 的垄断地位来破坏 SUN 对 Java 的销售渠道。树立市场正义的唯一方法是 纠正微软的所作所为,"阻止微软从它过去的错误中获得将来的利益!" 针尖对麦芒的斗争一直在继续„„ 和解:2004 年 4 月 2 日,两者达成和解协议微软将向 Sun 赔付 20 亿美元以消解旧怨,他们开始共同 应对来自 IBM 和 Linux 的挑战。 从上面的故事中可能有同学认为 Sun 是正义的,微软是非正义的,是这样的吗? 我们可以再看看下面的故事„„ 3、Oracle 和 Google 针对 Java 的再次对决 事实上,不止微软一家意识到 Java 是座金矿。Oracle 是第二家从 Sun 手中购买 Java 许可证的公司, 而 IBM 甚至比 Sun 更早的意识到 Java 在企业级应用方面的价值,在对 Java 支持上投入了巨大的精力,我 们平时编写 Java 程序使用的 Eclipse IDE 集成编程环境,就是 IBM 主导开发、用以争夺 Java 领导权的重 大举措(有空可以细讲 IBM 和 Sun 的恩恩怨怨,从 Eclipse 这个名字就可以看到其中的火药味)。 “和谐”的阴影: IBM 和 Intel 为了争夺 Java 的话语权,向 Sun 发出了新一轮的挑战,2005 年他们支持 Apache 开源社 区发起了一个叫做 Harmony 的项目,Harmony 有个有趣的中文意思–和谐。 Harmony 的目的有两个: 1、在 Apache Licence v2 的许可之下,独立的(不阅读 Sun JDK 的源代码,仅仅根据 Java SE 5 specification)开发一个与 Java SE 兼容的 JDK。 2、通过 Harmony 的开发社区,创建一个模块化的架构(包括虚拟机和类库)。该架构允许所有的独立开 发项目可以共享运行时组件。 简单的说,Harmony 就是让其他公司可以使用它来绕开 SUN JDK 的商业限制。Sun 为了保持自己对 Java 的主导权,坚决不给 Harmony 颁发 JDK 认证。 这让开源社区 Apache 和 SUN 发生了决裂„„ “太阳”的终结: Sun 创造了 Sparc、Solaris、Java 等伟大的产品,Sun 曾经风光无限,市值估价 2000 亿美金。Sun 预测到网络就是计算机,可是真正的网络时代到来时,它却没有真正调整过来,不断的亏损和决策失误让 它举步维艰。 2009 年 4 月 Oracle 宣布以 74 亿美金收购 Sun,2010 年 1 月欧盟决定无条件同意这项收购,一个伟大 公司就这样走到生命的尽头。 Java 的创造者 James Gosling 在自己的博客贴出了一幅画,并写了一句话 So long, old friend„ (再 见了,老朋友) (墓前站的是 Linux 的吉祥物 Tux 塔克斯和 Java 的吉祥物 Duke 杜克) 虽然 Sun 已经离去,Java 还会继续前行„„ “机器人”的小伎俩: 1998 年Sun的共同创始人Andy Bechtolsheim 给了斯坦福大学的两个学生一笔10万美金的天使投资, 他们成立了一个小公司名字叫——Google。 2007 年 11 月 5 日,已经成长为互联网领域内巨人的 Google 发布了一个叫做 Android 的手机操作系统 平台。Android 采用我们上面提到的 Harmony 来作为 JDK(Java 开发工具包)的替代品,使用 Dalivk 虚拟 机来替代 JVM(Java 虚拟机),它这次从头至尾都没有说它用的是 Java,可是所有的 Java 程序员都懂这 就是 Java „„ Oracle 对 Google 的诉讼: 2010 年 8 月 12 日,Java 专利权的新主人 Oracle 指控 Google 在 Android 开发中“故意,直接并反复 侵犯 Oracle 的 Java 相关的知识产权”,新一轮的斗争正在继续。 三、Java 的版本演进 1995 年 5 月 23 日 Java 语言发布 1996 年 1 月,JDK1.0 诞生, 4 月,10 个最主要的操作系统供应商申明将在其产品中嵌入 JAVA 技术, 9 月,约 8.3 万个网页应用了 JAVA 技术来制作 1997 年 2 月 18 日 JDK1.1 发布 1998 年 12 月 4 日 JDK1.2(Java2)发布 随后,SUN 公司发布 Java 的三个版本:标准版(J2SE)、企业版(J2EE)和微型版(J2ME), 为 Java 今后的方向指明了道路。 2000 年 5 月 8 日 J2SE1.3 发布 2000 年 5 月 29 日 J2SE1.4 发布 2004 年 9 月 30 日 J2SE1.5 发布,成为 Java 语言发展史上的又一里程碑。 为了表示该版本的重要性,J2SE1.5 更名为 Java SE 5.0 2005 年 6 月 Java SE 6.0 发布。J2EE 更名为 Java EE,J2SE 更名为 Java SE,J2ME 更名为 Java ME 四、Java JDK 安装和编程环境搭建 接下来我们就开始学习这个改变了世界的编程语言。我们学打扑克牌需要先买一副扑克牌,然后打开 牌盒先认认牌。学习一门语言的第一步就是搭建它的编程环境,然后写一个简单的 Java 程序的例子,简单 了解一下。最后看看别人都用这个语言都弄出来些什么好玩的程序。 1、JDK 的下载(Windows 环境): JDK 的意思是 Java Development Kit ,直译就是 Java 开发套件。JDK 是我们学习 Java 必备工具。 我们可以从 Oracle 的网站可以下载最新版的 JDK。 a、访问 http://www.oracle.com/technetwork/java/javase/downloads/index.html 点击 Download JDK 按钮 b、在出来的页面中点击 Download 按钮 c、在下面的页面中选择 Platform 为 Windows,然后点击 Continue 按钮 安装过程我就不演示了,只需要建议的是,可以把 JDK 安装在类似 “C:\javasoft” 的目录中,而 不是 c:\program fileI 下。 d、在接下来的页面里点击 带下划线的下载链接即可 2、JDK 的安装: a、下面介绍一下安装步骤,双击下载回来的 jdk-6u21-windows-i586.exe,点击下一步 b、更改安装位置为 c:\javasoft\jdk1.6.0_21 c、稍等片刻 d、在选择 JRE(Java Runtime Environment Java 运行时环境)安装目录步骤时可以直接点下一步。 e、安装 jre 的过程中竟然有个 OOo(OpenOfficeOrganization)的广告 f、再稍等一下就可以看到成功安装的提示 g、最后打开命令行窗口敲一个命令检查是否运行正常 java –version ,这是个查看 Java 版本号的 命令,我们这次安装的是 JDK 1.6 的 第 21 个更新包,所以看到的反馈应该是这样: 好吧,恭喜你完成了最重要的一步 ^_^ 。 3、环境变量 环境变量一般是指在操作系统中用来指定操作系统运行环境的一些参数,比如临时文件夹位置和系统 文件夹位置等。Java 的运行环境需要配置如下几个参数,JAVA_HOME,classpath 和 Path,下面简单叙述一 下: 在“我的电脑”上点右键,在随后的菜单中选属性,可以看到系统属性选项卡,右下角就是“环境变 量”按钮。 点击“新建„” :变量名写 JAVA_HOME,变量值写 c:\javasoft\jdk1.6.0_21,这样就配置好了 JAVA_HOME,这个变量时其他 JAVA 程序约定俗称的寻找 Java 安装目录的依据。 找到变量 Path,点击“编辑„”, 在最前端加入“%JAVA_HOME%\bin;” 这样, 我们就可以在任何路径 下使用 java 和 javac 命令了。 找到 classpath,如果没有则新建一个。变量值为: “.;%JAVA_HOME%\lib\dt.jar;%JAVA_HOME%\lib\tools.jar” ,留意一下最前面的”.”,它代表当前目录。 classpath 的作用是让 java 虚拟机能够找到 class 文件的位置。 配置完成后需要检测一下,运行 cmd,敲命令 javac –version , 如果不报错,就说明配置成功了。 4、创建、编译和运行一个 Java 文件 a、我们建立一个目录,今后所有的实验都将在这里进行 “c:\workspace\java” b、在 java 目录下建立今天课程的目录 lesson01,记住目录名小写 c、创建 Lesson01.java :建立一个名字叫 Lesson01.java 的文件,右键用 Editplus 打开(不会使 用 EditPlus 的请自行搜索和学习使用,很简单不用怕)。在文件中写入: 1 class Lesson01{ 2 } 上面的代码你可以理解成:有个东西叫 Lesson01 d、编译 Lesson01.java : 在命令行窗口(不了解命令行窗口的同学可以自行搜索并学习)中敲入如下命令 javac Lesson01.java 如果成功的话会没有任何提示,并且会出现一个名字叫 Lesson01.class 的文件,生成 class 的过程 就是编译的过程。 e、运行 Lesson01.class : 在命令行窗口中敲入如下命令, 1 java Lesson01 提示如下图所示:意思是“在主线程中产生了一个叫做没有 main 这个方法的错误” 我们下面就来尝试修正这个错误,重新编辑 Lesson01.java ,内容如下: 1 class Lesson01{ 2 public static void main(String[] args){ 3 System.out.println("欢迎进入 Java 的世界!"); 4 } 5 } 再次编译、运行,这次看到将是: 恭喜你成功完成了一个 Java 程序从创建、编译到运行的全过程,Java 的世界就此向你展开。 六、Java 虚拟机和 Java 程序运行原理 我们在命令行中运行一下 java –version ,看看结果: 注意它的反馈信息实际上有 3 行,分别是 java sdk 的版本,JRE 的版本和 JVM 的版本, 而 Java HotSpot Client Virtual Machine 就是 Sun 公司开发的 Java 虚拟机。 那么什么是 Java 虚拟机? Java 虚拟机(JVM)是可运行 Java 代码的假想计算机。只要根据 JVM 规格描述将解释器移植到特定的计 算机上,就能保证经过编译的任何 Java 代码能够在该系统上运行。 Java 程序的运行过程大致就是先吧 Java 源文件(后缀是.java 的文件)编译成(.class)文件,然后 再运行 class 文件。 七、Java 程序演示(Demo) Java 可以做些什么,同学们可以先到 java 安装目录的 demo 子目录(C:\javasoft\jdk1.6.0_21\demo) 中看一看,这就是个本节课的作业,大家去寻宝吧。 最后送大家一架钢琴,JavaSoundDemo.jar 这个小程序是我从 Mac OS 自带的 JDK 包目录里发现的, 双击即可运行,鼠标放上去就会有音乐飘出,尽情的演奏吧^_^ 这个文件我打包源文件里了,不用向我单独要。 Java 基础第二讲:Java 基本语法(一) 14 Sep 本讲内容:对象、标识符、关键字、变量、常量、字面值、基本数据类型、整数、浮点数、布尔型、 字符型、赋值、注释 Java 作为一门语言,必然有他的语法规则。学习编程语言的关键之一就是学好语法规则,写作合乎语 法规则的语句,控制计算机完成各种任务。而按编程语言的语法规则写成的,完成某项功能的代码集合就 可以叫做程序。 一、初识对象(Object): “初识对象的时候我们还不知道什么是对象。” Java 的一个重要特点就是面向对象(Object Oriented), 面向对象是相对于面向过程(Process Oriented)来说的。 我们用一个从冰箱中取一杯牛奶的例子来说明面向过程和面向对象的区别。 先用面向过程的思路描述这个过程: 再用面向对象的思路描述这个过程: 我们把冰箱作为一个对象的时候,问题变得异常简单,冰箱有一个方法就是取牛奶的方法,你调用这 个方法,这个方法的返回值就是一杯牛奶。那么现实生活中有这样智能的冰箱吗?有的,找个人站冰箱旁 边就行了,把那个人和冰箱合起来包装成一个对象它就是智能冰箱对象了^_^ 。 面向对象的编程语言把所有事物都看成对象:万事万物皆对象。 Java 的程序就是一些对象的集合,这些对象通过调用彼此的方法与其他对象交互。每个对象都属于某 种有一个类或者接口定义的类型。 二、标识符(Indentifier) Java 语言中的类名、对象名、方法名、常量名等等这些 Java 组件都需要起个名字,在而这些组件的 名称就被称为标识符(Indentifier)。 合法的标识符具有一些命名规则: 1. 必须以字母、美元符号或下划线开头。数字不能开头 2. 第一个字符之后可以是任意长度的包含数字、字母、美元符号、下划线的任意组合。 3. 不能使用 Java 关键字和保留字做标识符 4. 标识符是大小写敏感的,Z 和 z 是两个不同的标识符。 5. Unicode 字符会被视为普通字母对待。 针对最后一条,连 SCJP(Sun Ceritified Java Programmer)考试也不会要求,但是你要了解,这样是可以 的: 1 public class Lesson02{ 2 public static void main(String[] args){ 3 String 世界 = "阿凡达!"; 4 System.out.println(世界); 5 } 6 } 上面的代码回成功的打印出“阿凡达”字样。 三、关键字(keywords) 和所有的编程语言一样,Java 具有一组内置的关键字,这些关键字绝对不能用来做为标识符。Java SE6 里一共有 50 个关键字(keywords): abstract continue for new switch assert default goto package synchronized boolean do if private this break double implements protected throw byte else import public throws case enum instanceof return transient 瞬间 catch extentds int short try char final interface static void class finally long strictfp volatile const float native super while 这些单词有共同的特点是:全是小写的,不能用作标识符。(如果非要用做标识符呢,会有什么事情 发生?自己去试试看)。其中 instanceof 是 instance of 的连写 ,strictfp 是 strict float point 的 连写 有三个看起来像是关键字,其实不是关键的东东,他们是字面值(literal),字面值的概念下面立刻 就会讲到。 true 布尔字面值 false 布尔字面值 null 空值字面值 四、变量(Variable)初识 所谓变量,就是值可以被改变的量。定义一个变量的时候不需要什么特殊的关键字修饰。 这个变量的概念很接近数学里变量的概念,举个例子: 1 public class Lesson02{ 2 public static void main(String[] args){ 3 String myName ="nabula"; 4 myName = "nebulayao"; 5 System.out.println(myName); 6 } 7 } 上述程序的打印结果大家想必能猜出来,是“nebulayao”。 五、常量(named constant、constant)初识 所谓常量,就是它的值不允许改变的量。要声明一个常量,就要用关键字 final 修饰,常量按照 Java 命名规范需要用全部大写,单词之间用下划线隔开: 1 // 游戏方向设定 北 南 东 西 2 final int NORTH = 1; 3 final int SOUTH = 2; 4 final int EAST = 3; 5 final int WEST = 4; 6 // 三种游戏元�� 7 final int RED_STAR = 1; 8 final int YELLOW_STAR = 2; 9 final int GREEN_STAR = 3; 那么我真改了会怎么样呢?我们一起看看 1 public class Lesson02{ 2 public static void main(String[] args){ 3 final int SOUTH =2; 4 SOUTH =1; 5 } 6 } 上面的代码,如果去编译的话会报下面的提示: 如果是在 Eclipse 等集成编程环境中写的话,会直接有红色错误标识出现: 六、字面值(literal、literal value) literal 这个单词被翻译了好多种说法、字面值、字面量、直接量、常值、文本符等等,我们需要知 道这些翻译都是一个意思,就是编程语言里一些内建数据类型的源代码表示。譬如: 1 42 //整数字面值 2 false //布尔字面值 3 3.1415 //double 字面值 4 'b' //char 字面值 七、基本数据类型(Primitive Type) Java 中数据类型(Data Type)分为基本数据类型(Primitive Type)和引用数据类型(Reference Data Type)。 Java 中基本数据类型有八种: 1、整数(integer data type) Java 语言中使用 3 中表示整数的方法,分别是十进制、八进制和十六进制,平时我们使用 10 进制, 有时候也会用 16 进制,仅仅会因为趣味才会去用八进制。下面演示一下三种整型表示法: 01 public class Lesson02{ 02 public static void main(String[] args){ 03 int i=10; //十进制直接写 04 int j=012; //八进制前面加 0,八进制用 0-7 表示 05 int k=0xa; //十六进制前面加 0x 或者 0X,16 进制用 0-9 a-f 表示,为什么用 a-f,因为我 们没有发明过 10-16 的数字符号,这里的 a-f x 大小写都可以 06 int cafe = 0xcafe; //你觉得这个咖啡会等于几? 07 System.out.println(i); 08 System.out.println(j); 09 System.out.println(k); 10 System.out.println(cafe); 11 } 12 } 类型 字 节 范围 举例 byte 1 -128 ~ 127 125 short 2 -32768 ~ 32767 20000 int 4 -2147483648 ~ 2147483647 123456789,2123456789 long 8 -9223372036854775808~9223372036854775807 9876543210L 整数里的默认类型是 int,也就是说你写一个整数字面值如果不明确指定他的类型,那么他一定是 int 类型。 想明确声明一个整数字面值是长整型的话,需要使用 l 或 L 做后缀。 01 public class Lesson02{ 02 public static void main(String[] args){ 03 byte a= 127; 04 short b= 128; 05 int c=2123456789; 06 long d=9876543210l; 07 long e=9876543210L; 08 System.out.println("a="+a+" b="+b +" c="+c+" d="+d+" e="+e); 09 } 10 } 2、浮点数(floating-point data type) 类型 字节 范围 举例 float 4 1.4E-45 ~ 3.4028235E38 3.1415f double 8 4.9E-324 ~ 1.7976931348623157E308 3.1415,3.1415d 01 public class Lesson02{ 02 public static void main(String[] args){ 03 float a= 3.1415926f; 04 double b= 3.1415926; 05 float c=2123456789; 06 float d=9876543210L; 07 double e=9876543210L; 08 System.out.println("a="+a+" b="+b +" c="+c+" d="+d+" e="+e); 09 System.out.println(Float.MIN_VALUE); 10 System.out.println(Float.MAX_VALUE); 11 System.out.println(Double.MIN_VALUE); 12 System.out.println(Double.MAX_VALUE); 13 } 14 } 如果你想定义一个单精度浮点数字面量,那么你必须加 f 或 F 做后缀。 浮点数当然也可以存整数在里面。 3、布尔型(boolean) 布尔型表达一个真或假,是或否的意思。在 Java 中使用 boolean 关键字来声明一个变量为布尔类型, 在 Java 中布尔字面值只有 2 个:true 和 false。注意是全小写的。 1 public class Lesson02{ 2 public static void main(String[] args){ 3 boolean flag =true; 4 flag= false; 5 System.out.println("flag="+flag); 6 } 7 } 4、字符型(char) Java 里用一个单引号内的单个字符来表示一个字符字面值。 类型 字节 范围 举例 char 2 字节,16 位 ‘\u0000’ ~ ‘\uFFFF’ ‘犇’,'\u004e’,’n’,23002 01 public class Lesson02{ 02 public static void main(String[] args){ 03 char a= 'a'; //ascii 字符可以 04 char b='1'; 05 char c= '犇'; //汉字也可以 06 char d='\u004e'; //其实上所有的 Unicode 字符都可以 07 char e='\n'; //转义符表示的字符也可以 08 char f=65535; //也可以,因为 char 存储的时候占两个字节,因为他不是整数,所以不需要 符号位,因此它的最大值就是 65535 了。 09 char g=23002; 10 char h=36745; 11 System.out.println("a="+a+" b="+b +" c="+c+" d="+d+" e="+e+" f="+f+" g="+g+" h="+h); 12 } 13 } 上面的例子,如果文件以 utf-8 编码存储,那么编译时会报错, 因为命令行方式它人默认编码集是 GBK,所以编译时请特别指出编码集为 utf-8,方法如下: 额外作业:有兴趣的同学可以写一个算式,把自己的名字打印出来,如果不会话可以再学几讲再做, 记住有这个事儿就行 八、赋值(assignment)初识 “=” 是个赋值运算符,我们讲运算符之前先把赋值运算符先行提出来讲一讲。他和数学里的用法一 样,都是把右侧的值赋予左侧的变量。 下面用例子演示一下三种赋值方式: 1 public class Lesson02{ 2 public static void main(String[] args){ 3 int a=1; 4 int b=1,c=1; 5 int d=a=b=c=2; 6 System.out.println("a="+a+" b="+b +" c="+c+" d="+d); 7 } 8 } 九、注释(annotation)初识 程序中的注释是程序设计者与程序阅读者之间沟通的手段,是写给程序员看的代码。通常情况下编译 器会忽略注释部分,不做语法检查。 1. 好的注释可以改善软件的可读性,可以让开发人员更快理解新的代码。 2. 好的注释可以最大限度的提高团队开发的合作效率。 3. 长期的注释习惯可以锻炼出更加严谨的思维能力。 Java 中的注释有三种: // 注释一行 /* 注释若干行 */ /** 注释若干行,并写入 javadoc 文档 */ 好了,本讲就到这里,下一讲我们讲基本类型的相互转换和赋值,Take your time and enjoy it. Java 基础第三讲:Java 基本语法(二) 15 Sep 本讲内容: 基本数据类型之间的转换、引用数据类型、字符串 一、基本数据类型之间的转换 所谓数据类型转换就是将变量从当前的数据类型转换为其它数据类型,类型转换在 Java 里是个很严肃 的事情^_^ ,我们现在讲解基本数据类型之间的转换。 先搞定布尔类型,布尔类型无法和其它基本数据类型之间发生任何方式的转换。 数字型的基本数据类型之间可以通过下面两种方式实现转换。 1、自动类型转换 我们把所有的基本数据按照取值范围从小到大排个序,就得到了下图: a、当把一个取值范围小的基本数据类型赋值给一个取值范围大的基本数据类型时,Java 编译器会帮你 完成类型转换。 b、还有一个秘密:“字面值的整数永远隐含着的意思是它是个 int”,当把一个在 byte 允许的取值范围 的 int 字面值赋予一个 byte 类型的变量时,编译器会自动帮你完成。 01 public class Lesson03_1{ 02 public static void main(String[] args){ 03 byte byte1=100; 04 short short1=10000; 05 char char1= 23002; 06 int int1= 1000000; 07 long long1= 9876543210L; 08 float float1= 9876543210.12345f; 09 double double1= 9876543210.123456789; 10 11 short short2=byte1; 12 // byte byte2=short1; 13 14 } 15 } 上面的例子中 byte 能转换成 short,short 不能转换成 byte,这就是自动类型转换,自动类型转换不会损 失精度。那么现在的问题是上面的程序如果强行编译会怎么样,大家看下图: 看到没,编译器说了,把 short 转成 byte 可能会损失精度!那么把 10000 存入 byte 会的确会损失精度, 那么把值是 100 的 short 类型数字存入 byte 肯定不会损失精度,那么编译器会怎么看待这件事情?我们问 去采访一下它,改程序成这样: 1 public class Lesson03_2{ 2 public static void main(String[] args){ 3 short short1=100; 4 byte byte1=short1; 5 } 6 } 运行之后: 我们看到编译器并不买账,我们看到编译器只对整数字面值的大小有感知,对这种从大到小的类型转 换是坚决不允许的,那么如何解决这个问题,就要用到后面讲的强制类型转换。 2、强制类型转换 我们把上面的程序再改一下: 1 public class Lesson03_2{ 2 public static void main(String[] args){ 3 short short1=100; 4 byte byte1=(byte)short1; 5 System.out.print(byte1); 6 } 7 } 我们发现这一次成功了,加一个括号,里面写一上要转换的类型,就搞定了。强制类型转换果然很强! 强制类型转换的副作用也是有的,那就是的确可能导致精度丢失(数字不准确): 1 public class Lesson03_2{ 2 public static void main(String[] args){ 3 short short1=250; 4 byte byte1=(byte)short1; 5 System.out.print(byte1); 6 } 7 } 这段代码的打印值是-6,我想这是大家都不愿意看到的情形。了解了强制类型转换的特点我们使用的 时候就会特别留意,自然就不会出问题^_^。 二、引用数据类型(Reference Data Type) 和基本数据类型对应的就是引用数据类型,引用数据类型包括类应用、接口引用和数组引用。因为这 三个东西大家还都没有学,所以这里只是让他们和大家见个面,混个脸熟,直接上代码: 01 import java.util.List; 02 public class Lesson03_3{ 03 04 //对象 05 static Object object; 06 //接口 07 static List list =null; 08 //数组 09 static int[] months; 10 11 public static void main(String[] args){ 12 System.out.println("object="+object); 13 System.out.println("list="+list); 14 System.out.println("months="+months); 15 } 16 } 查看运行结果: 我们看到引用数据类型中接口和对象的类型名都是首字母大写的,数组是带有方括号的。他们和基本 数据类型有着明显的不同。 用一句话来说就是基本数据类型变量里存储的是数据本身,而引用数据类型变量里存储的是指向数据 本身的地址。 三、字符串初识 字符串是程序开发中最常用的一种对象。关于对象我们有过一点点的了解,关于字符串我们也先啊和 他见见面,以后会慢慢熟悉起来的。下面我们用一个字符串来举例理解引用数据类型在内存中的存储情况: 1 public class Lesson03_4{ 2 public static void main(String[] args){ 3 String name1="nebulayao"; 4 String name2=null; 5 name2=name1; 6 System.out.println("name2="+name2); 7 } 8 } 查看运行结果,打印的结果是 name2=nebulayao 。上述程序用图形来表示的话就是这样: 好了,本讲就到这里,take your time and enjoy it 。 哦,回来说一句,除了整数字面值的默认类型 int 以外,你是不是也自己发现了一个小秘密?那就是浮 点数字面值的默认类型是 double 。如果你不知道的话,做题会吃亏的^_^,出题人太坏了。 Java 基础第四讲:Java 基本语法(三) 17 Sep 本讲内容: Java 中的运算符 前言:运算符 operator Java 运算符从一个或多个操作数产生新的值。而操作数指的是位于运算符左边或者右边的内容。 Java operators produce new values from one or more operands (just so we’re all clear, remember the operands are the things on the right or left side of the operator). 一、赋值运算符 Assignment Operators 其实 Java 里有 12 种赋值运算符,“=”只是最常见的一种,下面我们讲解的四个常用复合赋值运算符(+=、-=、 *=、/=) 01 public class Lesson04_1 { 02 public static void main(String[] args){ 03 int x=10; 04 int y=10; 05 y=y-6; 06 x=x+2*5; 07 System.out.println("x="+x); 08 System.out.println("y="+y); 09 10 x=10; 11 y=10; 12 y-=6; 13 x+=2*5; 14 System.out.println("x="+x); 15 System.out.println("y="+y); 16 17 x=10; 18 x*=2+5; 19 System.out.println("x="+x); 20 } 21 } 我们可以看到上面两段代码的效果是等同的,下面的代码的等同的代码是 x=x*(2+5),为什么不是乘法优先呢? 是因为*=还是赋值运算符,赋值运算符的优先级是最低的。关于运算符优先级我们最后会有个总结。 最后把 11 种复合赋值运算符都列出来: 运算符 举例 等效表达式 += a += b a = a+b -= a -= b a = a-b *= a *= b a = a*b /= a /= b a = a/b %= a %= b a = a%b &= a &= b a = a&b |= a |= b a = a|b ^= a ^= b a = a^b <<= a <<= b a = a<>= a >>= b a = a>>b >>>= a >>>= b a = a>>>b 二、关系运算符 Relational Operators 关系运算符用于比较操作数之间的关系,关系运算符总是产生一个布尔值(true 或 false)。 运算符 功能 举例 运算结果 可运算类型 > 大于 ‘a’>’b’ false 整数、浮点数、字符 < 小于 2<3.0 true 整数、浮点数、字符 == 等于 ‘x’==88 true 任意 != 不等于 true!=true flase 任意 >= 大于或等于 6.6>=8.9 flase 整数、浮点数、字符 <= 小于或者等于 ‘M’<=88 true 整数、浮点数、字符 当在两个字符类型数据之间比较或者把字符类型数据和整数浮点数类型比较时,Java 将字符的 Unicode 值 当做数值与其它数值相比较。 相等性运算符 “Equality” Operators == 和 != 这两个关系运算符比较的是两个相似事物的是否相等。注意他们无法比较两个不兼容的类型,什 么叫无法比较?就是编译器会直接报错。 对于整数和浮点数之间的相等性比较如果他们的值相等,那么就返回 true。对于引用类型的变量之间的比 较,是看他们是否引用了同一个对象,如果变量的位相等那么他们就是相等的。 01 import java.util.Date; 02 public class Lesson04_2 { 03 public static void main(String[] args){ 04 05 System.out.println("'a'=='a' ? " + ('a'=='a')); 06 System.out.println("'a'=='b' ? " + ('a'=='b')); 07 System.out.println("5.0==5L ? " + (5.0==5L)); 08 System.out.println("true==true ? " + (true==true)); 09 10 Date date1 = new Date(); 11 Date date2 = new Date(); 12 Date date3=date1; 13 boolean b1=date1==date2; 14 boolean b2=date1==date3; 15 System.out.println("date1="+date1.getTime()); 16 System.out.println("date2="+date2.getTime()); 17 System.out.println("b1="+b1); 18 System.out.println("b2="+b2); 19 } 20 } 看一下运行结果: 三、instanceof 运算符 instanceof Comparison 因为你们还没有正式学过对象,所以这一节,等学过对象之后请再回来看一遍。 instanceof 运算符只值能用于比较对象引用变量,可以检查对象是否是某种类型。这里的类型是指类、接 口类型数组。或者说 instanceof 检查的是运算符左边的变量引用的对象是否能通过右边类型或者接口的 IS-A 测试。 01 import java.util.Date; 02 public class Lesson04_3 { 03 04 public interface Foo{} 05 public class A implements Foo{} 06 public class B extends A{}; 07 08 public void instance(){ 09 A a = new A(); 10 B b= new B(); 11 Foo f= new A(); 12 System.out.println(a instanceof Foo); 13 System.out.println(b instanceof A); 14 System.out.println(b instanceof Foo); 15 System.out.println(f instanceof A); 16 System.out.println(f instanceof B); 17 18 } 19 public static void main(String[] args){ 20 Lesson04_3 lesson = new Lesson04_3(); 21 lesson.instance(); 22 } 23 } 运行结果如下: 四、算术运算符 Arithmetic Operators 基本算术运算符: +加 –减 *乘 /除 求余运算符: % 递增和递减运算符: ++ – ,可以作为缀或后缀 下面举例说明: 01 public class Lesson04_4 { 02 public static void main(String[] args){ 03 //求余运算 04 int a=10; 05 int b=7; 06 System.out.println(a%b); 07 08 //自增运算 09 double x = 2; 10 double y= 1.2; 11 double z= x++ + ++y; 12 System.out.println(z); 13 } 14 } 运行的结果是: 五、条件运算符 Conditional Operator 条件运算符根据条件来返回一个值。计算问号左边表达式的值,值为真是提供冒号左边的操作数为返回值, 值为假时提供冒号右边的操作数为返回值。这是 Java 里唯一的一个三元运算符,为了让你记忆深刻,我做 了一张图: 其中括号可以省略。 1 public class Lesson04_5 { 2 public static void main(String[] args){ 3 int numOfPets =3; 4 String status = (numOfPets < 4)?"宠物数量刚刚好":"宠物数量太多了"; 5 System.out.println(status); 6 } 7 } 编译和运行结果是: 六、逻辑运算符 Logical Operator 逻辑运算符只对布尔型操作数进行运算并返回一个布尔型数据。一共有 6 个逻辑运算符:&& , || ,& , | ,! 和 ^ 短路逻辑运算符 Short-Circuit Logical Operators: 01 public class Lesson04_6 { 02 public static void main(String[] args) { 03 int i = 5; 04 // 短路与运算符&&,要求左右两个表达式都为 true 时才返回 true,如果左边第一个表达式为 false 时,它立刻就返回 false,就好像短路了一样立刻返回,省去了一些无谓的计算时间。 05 boolean flag = (i < 3) && (i < 4); 06 System.out.println(flag); 07 08 // 短路或运算符||,要求左右两个表达式有一个为 true 时就返回 true,如果左边第一个表达式为 true 时,它立刻就返回 true,就好像短路了一样立刻返回,省去了一些无谓的计算时间。 09 flag = (i > 4) || (i > 3); 10 System.out.println(flag) ; 11 } 12 } 编译和运行结果是: 非短路逻辑运算符 Not Short-Circuit Operators: 01 public class Lesson04_6 { 02 public static void main(String[] args) { 03 int i = 5; 04 // 非短路与运算符&,要求左右两个表达式都为 true 时才返回 true,两个表达式他都会计算 05 boolean flag = (i < 3) & (i < 4); 06 System.out.println(flag); 07 08 // 非短路或运算符|,要求左右两个表达式有一个为 true 时就返回 true,两个表达式他都会计算 09 flag = (i > 4) | (i > 3); 10 System.out.println(flag); 11 } 12 } 编译和运行结果是: 逻辑运算符 Logical Operators ^ and ! : ^ 异或运算符 XOR : 要使一个^表达式为真,必须有且只有一个操作数为真。 1 public class Lesson04_6 { 2 public static void main(String[] args) { 3 4 System.out.println("xor ((2<3)^(3<4)) :" + ((2<3)^(3<4))); 5 System.out.println("xor ((2<3)^(3>4)) :" + ((2<3)^(3>4))); 6 System.out.println("xor ((2>3)^(3>4)) :" + ((2>3)^(3>4))); 7 } 8 } 编译和运行结果是: 七、位运算符 Bitwise Operators 位运算符是对整数操作数以二进制的每一位进行操作,返回结果也是一个整数。 逻辑位运算符分别是 按位取反 ~ 、按位与 & 、按位或 | 和 按位异或 ^ 移位运算符有 左移<< 、 右移>> 、 从 SCJP 考试的 5.0 版本以后不再对此项知识点有任何涉及,果断决定不放在讲座里讲,而是放在番外篇里 提一提。 八、运算符优先级 优先级 运算符 1 () [] . 2 ! +(正) -(负) ~ ++ – 3 * / % 4 +(加) -(减) 5 << >> >>> 6 < <= > >= instanceof 7 == != 8 &(按位与) 9 ^ 10 | 11 && 12 || 13 ?: 14 = += -= *= /= %= &= |= ^= ~= <<= >>= >>>= 优先级是上面的高下面的低,用括号来解决优先级不清晰的部分是个良好的编程习惯。 好了本讲就到这里,Take your time and enjoy it 。 Java 基础第五讲:流程控制(一) 20 Sep 本讲内容: 分支语句 流程控制 Flow Control : 流程控制语句是编程语言中的核心之一。可以分为 分支语句、循环语句和跳转语句。 本讲内容包括分支语句的 if-else 和 switch , 它们又被称为判决语句(decision statements),意思是根据某 种条件做出朝哪个方向前进的判断。 一、if-else 分支控制语句 ( if-else Branching ) 1、最简单的 if 语句 假设我到办公室里问黄文强在不在?如果他在的话会说在,不在的话一般情况是没人说话了。我们用程序 模拟一下: view source print? 01 public class Lesson06_1 { 02 public static void main(String[] args) { 03 //设置黄文强在 04 boolean flag = true; 05 System.out.println("开始"); 06 if (flag){ 07 System.out.println("在"); 08 } 09 System.out.println("结束"); 10 } 11 } 为了把分支语句的前后界定清楚,我加了开始和结束标识。上面的运行结果是: 2、最简单的 if-else 语句 假设我到办公室里问黄文强在不在?如果他在的话会说在,不在的时候有热心同事回答了一句“他不在”, 那我就不立刻明白了。我们用程序模拟一下: view source print? 01 public class Lesson06_1 { 02 public static void main(String[] args) { 03 //设置黄文强不在 04 boolean flag = false; 05 System.out.println("开始"); 06 if (flag){ 07 System.out.println("在"); 08 }else{ 09 System.out.println("他不在"); 10 } 11 System.out.println("结束"); 12 } 13 } 上面的运行结果是: 3、简单的 if – else if 语句 好吧,如果黄文强不在的话,我想问问刘克强在不在?恰好,刘克强在,那么用程序模拟是这样的: view source print? 01 public class Lesson06_1 { 02 public static void main(String[] args) { 03 // 设置黄文强不在 04 boolean flag1 = false; 05 // 设置刘克强在 06 boolean flag2 = true; 07 System.out.println("开始->"); 08 if (flag1) { 09 System.out.println("黄文强在"); 10 } else if (flag2) { 11 System.out.println("刘克强在"); 12 } 13 System.out.println("->结束"); 14 } 15 } 上面的运行结果是: 4、复合 if- else if – else 语句 如果刘克强也不在,那么用程序模拟是这样的: view source print? 01 public class Lesson06_1 { 02 public static void main(String[] args) { 03 // 设置黄文强不在 04 boolean flag1 = false; 05 // 设置刘克强在 06 boolean flag2 = true; 07 System.out.println("开始->"); 08 if (flag1) { 09 System.out.println("黄文强在"); 10 } else if (flag2) { 11 System.out.println("刘克强在"); 12 } else { 13 System.out.println("他们不在"); 14 } 15 System.out.println("->结束"); 16 } 17 } 上面的运行结果是: 5、if-else 语句规则: 1. if 后的括号不能省略,括号里表达式的值最终必须返回的是布尔值 2. 如果条件体内只有一条语句需要执行,那么 if 后面的大括号可以省略,但这是一种极为不好的编 程习惯。 3. 对于给定的 if,else 语句是可选的,else if 语句也是可选的 4. else 和 else if 同时出现时,else 必须出现在 else if 之后 5. 如果有多条 else if 语句同时出现,那么如果有一条 else if 语句的表达式测试成功,那么会忽略掉 其他所有 else if 和 else 分支。 6. 如果出现多个 if,只有一个 else 的情形,else 子句归属于最内层的 if 语句 6、实例练习: 下面我们看一个例子,(请注意这个例子的写法是不被推荐的,这里这么写是为了讲解 if-else 的规则) view source print? 01 public class Lesson06 { 02 public static void main(String[] args) { 03 boolean examIsDone = true; 04 int score = 65; 05 if (examIsDone) 06 if (score >= 90)System.out.println("A ,Excellent"); 07 else if (score >= 80) 08 System.out.println("B ,Good"); 09 else if (score >= 70) 10 System.out.println("C ,Middle"); 11 else if (score >= 60) 12 System.out.println("D ,Pass"); 13 else 14 System.out.println("E ,Fail"); 15 System.out.println("Done is Done"); 16 } 17 } 你认为 else 是属于哪个 if 语句的?System.out.println(“Done is Done”);是在哪一行代码之后执行的? 二、分支控制语句 switch Statement Java 中有一个和 if 语句比较相似的分支控制语句叫 switch ,它在有一系列固定值做分支时使用效率要比 if-else 方式效率高(别急,等一下再告诉你为什么效率高)。 先看一个例子:假设我们不考虑闰年的话,我们如何知道一个月有多少天?先用 if-else 的方式来实现: view source print? 01 public class Lesson06_2 { 02 public static void main(String[] args) { 03 int month=9; 04 if(month==1){ 05 System.out.println(month+"月有 31 天"); 06 }else if(month==2){ 07 System.out.println(month+"月有 28 天"); 08 }else if(month==3){ 09 System.out.println(month+"月有 31 天"); 10 }else if(month==4){ 11 System.out.println(month+"月有 30 天"); 12 }else if(month==5){ 13 System.out.println(month+"月有 31 天"); 14 }else if(month==6){ 15 System.out.println(month+"月有 30 天"); 16 }else if(month==7){ 17 System.out.println(month+"月有 31 天"); 18 }else if(month==8){ 19 System.out.println(month+"月有 31 天"); 20 }else if(month==9){ 21 System.out.println(month+"月有 30 天"); 22 }else if(month==10){ 23 System.out.println(month+"月有 31 天"); 24 }else if(month==11){ 25 System.out.println(month+"月有 30 天"); 26 }else if(month==12){ 27 System.out.println(month+"月有 31 天"); 28 }else{ 29 System.out.println("没有这个月份吧"); 30 } 31 } 32 } 接下来我们使用 switch 语句重新实现一次: view source print? 01 public class Lesson06_4 { 02 public static void main(String[] args) { 03 int month = 9; 04 switch (month) { 05 case 1: 06 System.out.println(month + "月有 31 天"); 07 break; 08 case 2: 09 System.out.println(month + "月有 28 天"); 10 break; 11 case 3: 12 System.out.println(month + "月有 31 天"); 13 break; 14 case 4: 15 System.out.println(month + "月有 30 天"); 16 break; 17 case 5: 18 System.out.println(month + "月有 31 天"); 19 break; 20 case 6: 21 System.out.println(month + "月有 30 天"); 22 break; 23 case 7: 24 System.out.println(month + "月有 31 天"); 25 break; 26 case 8: 27 System.out.println(month + "月有 31 天"); 28 break; 29 case 9: 30 System.out.println(month + "月有 30 天"); 31 break ; 32 case 10: 33 System.out.println(month + "月有 31 天"); 34 break; 35 case 11: 36 System.out.println(month + "月有 30 天"); 37 break ; 38 case 12: 39 System.out.println(month + "月有 31 天"); 40 break; 41 default: 42 System.out.println("没有这个月份吧"); 43 break; 44 } 45 } 46 } 运行 2 个程序,结果都是 9 月有 30 天。 从简洁程度和效率上,switch 都略胜一筹(上面的 2 个程序,把输出语句屏蔽掉,各自循环运行 10 亿次,switch 快了 6636 毫秒,也就是 6 秒钟)。 为什么会快一些呢,因为 switch 是在编译时优化的。运行时进行的不是变量的比较运算,而是直接跳转。 举个例子 7x9 你是反射式的出来的结果,9x7 你需要稍稍楞一小下,99x7 你就需要计算一下了。这就是他们 之间的区别。如果你觉得举例子不科学,改天我们再找虚拟机一起聊这个话题^_^ 好了,我们还是先学习一下 switch 都有什么使用规则吧: 1、留意 switch 格式的写法 标准且合法的格式: 非法格式说明: switch(x){ case 0 {} } switch(x){ 0:{} 1:{} } 能看到哪里错了吧,第一个没冒号,第二个没 case 关键字 2、switch 表达式必须能被求值成 char byte short int 或 enum 记忆的话可以记忆成非 lang 整形加枚举。请切记在 java 6 及以前版本中 string 是不允许用在 switch 表达式 中的(Java 7 可以用 string,现在正式版还没出来) 3、case 常量必须是编译时常量,因为 case 的参数必须在编译时解析,也就是说必须是字面量或者是在声 明时就赋值的 final 变量。 这个不太好理解,举个例子看看: view source print? 01 public class Lesson06_5 { 02 public static void main(String[] args) { 03 final int a = 2; 04 final int b; 05 b = 3; 06 int x =2; 07 switch(x){ 08 case 1: //编译 OK 09 case a: //编译 OK 10 case b: //无法通过编译 11 } 12 } 13 } 编译一下看看: 4、case 常量被会转换成 switch 表达式的类型,如果类型不匹配也会出错: view source print? 1 public class Lesson06_5 { 2 public static void main(String[] args) { 3 byte x =2; 4 switch(x){ 5 case 1: //编译 OK 6 case 128: //无法自动转换成 byte,编译器会报错 } 7 } 8 } 5、多个 case 常量重复也会出错: view source print? 1 public class Lesson06_5 { 2 public static void main(String[] args) { 3 byte x =2; 4 switch(x){ 5 case 1: //编译 OK 6 case 1: 7 } 8 } 9 } 编译一下看看: 6、匹配的 case 的语句是入口而不是独立分支: view source print? 01 public class Lesson06_5 { 02 public static void main(String[] args) { 03 int x = 1; 04 switch (x) { 05 case 1:System.out.println("周一"); 06 case 2:System.out.println("周二"); 07 case 3:System.out.println("周三"); 08 case 4:System.out.println("周四"); 09 case 5:System.out.println("周五"); 10 case 6:System.out.println("周六"); 11 case 7:System.out.println("周日"); 12 default:System.out.println("这个是星期几啊"); 13 } 14 } 15 } switch 代码是找到一个匹配的 case 常量入口,然后自顶向下执行的。也就是英文里的 Fall-Through,你可以 形象的理解他的执行方式是自由落体式的一股脑碰到啥就执行啥。 7、break:在 switch 语句块中,执行到 break 关键词时,立刻退出 switch 语句块,转到 switch 的下一条语 句。 我们结合 6、7 两点规则来重写一次,判断月份天数的例子: view source print? 01 public class Lesson06_3 { 02 public static void main(String[] args) { 03 int month = 9; 04 switch (month) { 05 case 1: 06 case 3: 07 case 5: 08 case 7: 09 case 8: 10 case 10: 11 case 12: 12 System.out.println(month + "月有 31 天"); 13 break; 14 case 2: 15 System.out.println(month + "月有 28 天"); 16 break; 17 case 4: 18 case 6: 19 case 9: 20 case 11: 21 System.out.println(month + "月有 30 天"); 22 break; 23 default: 24 System.out.println("没有这个月份吧"); 25 break; 26 } 27 } 28 } 运行程序: 8、default :当 switch 中所有的 case 常量都不匹配时,会执行 default 分支: 再把上面判断星期几的代码改一下: view source print? 01 public class Lesson06_5 { 02 public static void main(String[] args) { 03 int x = 8; 04 switch (x) { 05 case 1:System.out.println("周一"); 06 default:System.out.println("这个是星期几啊"); 07 case 2:System.out.println("周二"); 08 case 3:System.out.println("周三"); 09 case 4:System.out.println("周四"); 10 case 5:System.out.println("周五"); 11 break; 12 case 6:System.out.println("周六"); 13 case 7:System.out.println("周日"); 14 } 15 } 16 } 运行程序: 我们故意写了星期 8,所以触发了 default 分支,可是因为没有使用 break 语句,所以又直落执行了。 于是出现了这个看起来有点奇怪,其实很正常的输出。 好了,到这里我们就把 switch 讲完了。大家要记住的是当程序中有很多需要判断的成点状数据需要分 别处理时优先采用 switch,如果遇到区间判断时,不用思考,还是用 if-else 吧。 对编程基础知识的理解会随着你的编程经验不断加深,不要奢望一次理解有多到位,淡定,淡定。 Java 基础第六讲:流程控制(二) 21 Sep 本讲内容:循环、跳出循环、标签跳转 Java 中循环有三种形式 while 循环、do-while 循环 和 for 循环。其中从 Java 6 开始 for 循环又分 普通 for 循环 和 for-each 循环两种,我们接下来分别讲解。 一、while 循环 当条件为真时执行 while 循环,一直到条件为假时再退出循环体,如果第一次条件表达式就是假,那 么 while 循环将被忽略,如果条件表达式一直为真,那么 while 循环将一直执行。关于 while 括号后的表达 式,要求和 if 语句一样需要返回一个布尔值,用作判断是否进入循环的条件。 view source print? 1 public class Lesson06_6 { 2 public static void main(String[] args) { 3 int x = 8; 4 while (x > 0) { 5 System.out.println(x); 6 x--; 7 } 8 } 9 } 执行结果: 如果你把 x>0 改成大于 8 ,while 循环将一次都不执行 二、do-while 循环 好,如果你无论如何都想执行一次循环体内的代码,可以选择 do-while 循环,它的特点是做了再说。 view source print? 1 public class Lesson06_6 { 2 public static void main(String[] args) { 3 int x = 8; 4 do{ 5 System.out.println(x); 6 x--; 7 }while(x>8); 8 } 9 } x=8,条件是大于 8,查看运行结果,我们发现他总是会执行一次。 三、for 循环 当知道可以循环多少次时,是使用 for 循环的最佳时机。 1、基本 for 循环: 先举一个例子: view source print? 1 public class Lesson06_6 { 2 public static void main(String[] args) { 3 for (int i = 2, j = 1; j < 10; j++) { 4 if (j >= i) { 5 System.out.println(i + "x" + j + "=" + i * j); 6 } 7 } 8 } 9 } 这个例子打印了从九九乘法表的一部分: 原谅我没有从最常用的 for 循环开始写,你把 int i=2 写在 for 循环前面就变成最常用的 for 循环了。 下面说一下 for 循环的规则: 1. for 循环的三个部分任意部分都可以省略,最简单的 for 循环就是这样的 for(;;){ } 2. 中间的条件表达式必须返回一个布尔值,用来作为是否进行循环的判断依据 3. 初始化语句可以由初始化多个变量,多个变量之间可以用逗号隔开,这些在 for 循环中声明的变 量作用范围就只在 for 循环内部 4. 最后的迭代语句可以是 i++,j++ 这样的表达式,也可以是毫无干系的 System.out.println(“哈哈”) 之类的语句,它照样在循环体执行完毕之后被执行。 2、for-each 循环: for-each 循环又叫增强型 for 循环,它用来遍历数组和集合中的元素,因此我们会在数组一章和集合一 章里分别讲到,放心,你会掌握的很好。 这里举个例子给你看看先: view source print? 1 public class Lesson06_6 { 2 public static void main(String[] args) { 3 int[] a = { 6, 2, 3, 8 }; 4 for (int n : a) { 5 System.out.println(n); 6 } 7 } 8 } 运行结果如下: 四、跳出循环 break 、continue break 关键字用来终止循环或 switch 语句,continue 关键字用来终止循环的当前迭代。当存在多层循环 时,不带标签的 break 和 continue 只能终止离它所在的最内层循环,如果需要终止它所在的较外层的循环 则必须用,标签标注外层的循环,并使用 break 和 continue 带标签的形式予以明确标示。 先看一个不带标签的例子 BreakAndContinue.java: view source print? 01 public class BreakAndContinue { 02 03 public static void main(String[] args) { 04 05 int i =0; 06 while(true){ 07 System.out.println("i="+i); 08 if(i==12){ 09 i++; 10 i++; 11 continue; 12 } 13 i++; 14 if(i==20){ 15 break; 16 } 17 } 18 19 } 20 21 } 22 好例子自己会说话,这个例子打印了从 1 到 20 中除去 13 的数字。我们只需要看明白这个例子的输出 结果就能明白 break 和 continue 的区别了。 编译并运行代码,查看结果: 我们再看一个 break 带标签的例子: view source print? 01 public class BreakAndContinue { 02 03 public static void main(String[] args) { 04 05 boolean isTrue = true; 06 outer: 07 for(int i=0;i<5;i++){ 08 while(isTrue){ 09 System.out.println("Hello"); 10 break outer; 11 } 12 System.out.println("Outer loop."); 13 } 14 System.out.println("Good Bye!"); 15 16 } 17 18 } 19 编译并运行程序,查看结果: 把上面的例子中 break 替换成 continue,再次编译和运行,查看结果: ok,本讲就到这里,take your time and enjoy it. Java 基础第七讲:面向对象基础(一) 22 Sep 本讲内容:面向对象的概念和发展、面向对象的特征 一、面向对象(Object Oriented)编程语言的历史 1950 年有个叫做荷兰德的学生作为程序员进入 IBM 的时候,这个世界上的程序员只有几个而已。当时 计算机很少,计算机性能也差,程序员也少,加上程序员都是天才中的天才,智商超高,所以他们用十六 进制的机器编码来操纵计算机,似乎没有什么问题。 1960 年,计算机性能不断提升,应用领域也不断增多,程序员的人数也在增多,程序的复杂程度也不 断提高,很多程序需要好多人一起才能完成。而在此时在大型项目中由于软件的原因导致的大量问题也不 断暴露出来。由此催生了结构化程序设计方法。结构化程序设计思想采取了模块分解和功能抽象的方法, 把一个个复杂的问题,分解成一个个易于控制的子程序,便于开发和维护,因此结构化程序设计迅速走红, 并从 70 年代起逐渐占据统治地位。 70 年代末,随着计算机科学的发展,结构化程序设计方法也渐渐显得力不从心。于是面向对象设计思 路和语言慢慢浮出水面。 1967 年挪威两个科学家发布了 simula 语言(simulation 模拟、仿真),它引入了后来所有面向对象程序 设计语言都会遵循的几个基础概念:类、对象、继承。虽然因为 simula 比较难懂、难学,功能不完善而没 有流行开来,但是它的思想却指导着计算机这数十年的编程实践。 1972 年诞生的 smalltalk,被公认为是历史上第二个面向对象的程序设计语言,和第一个真正的集成开发 环境(IDE), smalltalk 对 Java 、Objective-C 、Ruby 的诞生都起到了极大的推动作用。90 年代的许多软件 开发思想,如设计模式、敏捷编程和重构等也都源自于 smalltalk。在 smalltalk 里所有的东西都是对象,15*19 会被理解成向 15 这个对象发送一个乘法的消息,参数是 19。 1985 年 c++商业版本的正式发布,标志着一个面向对象领域里的王者诞生了。C++在 c 语言的基础上, 借鉴了simula中类的概念、从 algol语言中继承了运算符重载、引用以及在任何地方都可以声明变量的能力, 从 BCPL 获得了//注释,从 Ada 语言中得到了模板,命名空间,从 Ada、Clu 和 ML 去取得了异常……C++是第 一个广泛流行起来的面向对象的编程语言,至今魅力不减。 1995 年 Java 诞生的故事大家都耳熟能详了,我们也知道 Java 是 C++的语法与 Smalltalk 语义的结合。由 此面向对象领域里又一个王者诞生了。 Java 里面向对象的概念六次讲座也就可以讲完。不要怕,这些概念很好理解;不要轻视,很多深层的 思想需要在实践中不断思考和分析才可以领悟。 二、类和对象的概念 1、类和对象的概念 人类自古就喜欢听故事,也喜欢写故事,我们从小也被要求写作文,为了帮助你写作文。老师还总结 了一些规律,譬如记叙文六要素:时间、地点、人物、起因、经过、结果。 有了这样指导性的东西,我们 写作文的时候就简单了许多。 面向对象程序语言的核心思想就是把一个事物的状态和行为封装起来作为一个整体看待。类描述的就 是对象知道知道什么和执行什么。 譬如我们用面向对象的思想来看待一架飞机: 如果我们站在顾客角度看飞机,那么它的状态是名字波音 777,座位数 380 人,飞行速度 940 公里每 小时,它的行为就是飞行,能把你从 A 地送到 B 地。 如果站在航空公司角度看飞机,那么它的状态是名字波音 777,资产编号 HNHK20100321,购买价格 18.7 亿人民币。它的行为就是能赚钱。 我们从不同角度去看待和抽象同一架飞机它的状态和行为不相同。 再从面向对象的角度看待一个家乐福超市的员工王丽: 她在上班的时候是个收银员,那么她的状态是编号 067,她的行为就是收银。她下班以后去家门口的 小店买菜,那么他的身份就是顾客,她的状态是有个购物商品清单,她的行为就是付款。 我们从不同的角度和时间去看待同一个人,她的状态和行为也是不相同的,甚至看起来是相反的。 好了,我们自己尝试分析一下,电脑的状态和行为,手机的状态和行为,桌子的状态和行为,QQ 的状 态和行为,小狗、小猫、老虎、大象、蚊子、苍蝇…… 有一个简单的方法区别什么是状态什么是行为:就 是状态是个名词,行为是个动词。 2、类和对象的关系 类是对象的蓝图,它告诉虚拟机如何创建某个类型的对象。对象是根据蓝图建造出来的实例。 譬如我们设计一个模拟 WOW 的格斗游戏,需要人或者怪兽来战斗吧,战斗需要武器吧。那么圣骑士 就是个类,人类圣骑士“锦马超”就是一个对象。如果双手剑件是个类,那么拿在“锦马超”手里的“霜之哀伤” 就是一个对象。 譬如我们要建立一个全班同学的通讯录,设计一个通讯录的格式,包括姓名、性别、手机号、QQ 号、 宿舍号。然后我们按照一定的格式印出来,交由每个同学填写,那么每个同学填写的那一份就叫对象,我 们填写的通讯录格式本身就是类。 譬如由一个寂寞的老人需要找个伴,要求:随时都可以陪着他,还不唠叨。有人带了一条狗。那么老 人提的需求就是蓝图,就是类。狗就是对类的实现,就是对象。 3、定义类,创建对象 下面我们学习如何用 Java 的程序代码来定义类、创建对象。 定义一个类的步骤是:定义类名,编写类的属性(状态),编写类的方法(行为) view source print? 01 public class Dog { 02 03 // 定义了狗的个头大小的属性 04 private int size; 05 06 // 定义设置个头的方法 07 public void setSize(int size) { 08 if (size > 0 && size < 10) { 09 this.size = size; 10 } else { 11 size = 1; 12 } 13 } 14 15 // 定义获取个头的方 法 16 public int getSize() { 17 return size; 18 } 19 20 // 定义狗叫的方法 21 public void bark(){ 22 if(size<5){ 23 System.out.println("汪汪汪!"); 24 }else{ 25 System.out.println("嗷!嗷!"); 26 } 27 } 28 29 //定义 main 方法 30 public static void main(String[] args) { 31 32 //创建了名字叫小黄的狗对象 33 Dog xiaoHang = new Dog(); 34 //设置它的大小属性 35 xiaoHang.setSize(3); 36 //调用它的叫方法 37 xiaoHang.bark(); 38 39 //创建了名字叫大黄的狗对象 40 Dog daHang = new Dog(); 41 //设置它的大小属性 42 daHang.setSize(7); 43 //调用它的叫方法 44 daHang.bark(); 45 } 46 } 运行程序查看运行结果: 三、面向对象的三大特性 封装、继承、多态是面向对象的三大特性。这里先让大家有个概念,通过今后漫长的学习过程不断加 深对它们的理解。 封装(encapsulation):就是把属性私有化,提供公共方法访问私有对象。 这里面有两层意思,第一隐 藏数据,第二把数据和对数据操作的方法绑定。 实现封装的步骤: 1 修改属性的可见性来限制对属性的访问。 2 为每个属性创建一对赋值方法和取值方法,用于对这些属性的访问。 3 在赋值和取值方法中,加入对属性的存取限制。 封装的优点: 1 隐藏类的实现细节; 2 可加入控制逻辑,限制对属性的不合理操作; 3 便于修改,增强代码的可维护性; 这里举一个智能冰箱的例子(可乐灌装工厂也可)。 view source print? 01 public class Lesson07 { 02 03 private String 牛奶="一瓶牛奶"; 04 05 public String 得到牛奶(){ 06 System.out.println("给出了"+牛奶); 07 return 牛奶; 08 } 09 10 public void 设置牛奶(String s){ 11 this.牛奶=s; 12 } 13 14 public static void main(String[] args) { 15 16 Lesson07 lesson = new Lesson07(); 17 lesson.得到牛奶(); 18 19 lesson.设置牛奶("一罐牛奶"); 20 lesson.得到牛奶(); 21 } 22 23 } 继承(inheritance):同类事物之间有它的共同性也有各自的独特性,我们把共同的部分抽离出来,就可 以得到使用这些事物的一般性的类,我们在把那些具有特殊性的共同点再次抽象就可到了一些具有特殊性 的类。而特殊类拥有一般类所具有的一般性属性和方法,也拥有自己特有的某些属性和方法。我们把特殊 类和一般类之间的关系叫做继承。 举个例子:马儿都有四条腿,马儿都有会跑,我们把这些共同性抽象出来就成了马类;而其中有一些 马是白色的马,还有一些是黑色的马,我们把这些特殊性也分别抽象出来,就成了白马类和黑马类。那么 白马类和马类之间的关系就是继承关系。它们是父子关系,马类是夫类、白马类是子类。 继承简化了人们对事物的认识和描述,清晰的体现了相关类间的层次关系。 继承达到了功能抽象、继承促进了代码复用、继承也带来了多态性。 这里先对继承有个概念,下面还会有详细的讲解。 多态(polymorphism):多态就是“一种定义、多种实现” 。Java 中可以把一个子类的对象赋给一个父类 的引用,这就出现了多态。这里先对多态有个概念,下面还会有详细的讲解。 这一讲里我们迈入了对象的世界。学习了类和对象的概念,并对面向对象的特点有了初步的了解,从 下一讲开始将近距离接触它们。 好,本讲就到这里。 Java 基础第八讲:面向对象基础(二) 23 Sep 本讲内容:成员变量、方法、方法的重载、构造函数 一、用程序讲解小白的故事 小白是一条狗,它心情好的时候会恭喜人发财,它心情差的时候会对路人撒野,吓得路人落荒而逃。下面 我们用面向对象的方式用程序讲述一下小白的故事。 view source print? 01 public class Dog { 02 03 //构造函数 04 public Dog(){ 05 size =3; 06 } 07 08 //定义叫声常量 09 final String BARK_NORMAL = "汪!汪汪!"; 10 final String BARK_HAPPY = "旺!旺旺!"; 11 final String BARK_SAD = "呜……嗷!"; 12 13 //定义心情常量 14 static final int NORMAL =0; 15 static final int HAPPY =1; 16 static final int SAD = 2; 17 18 // 定义了狗的个头大小的属性 19 private int size; 20 21 // 定义获取个头的方 法 22 public int getSize() { 23 return size; 24 } 25 26 // 定义狗叫的方法 27 public void bark(){ 28 if(size<5){ 29 System.out.println("汪汪汪!"); 30 }else{ 31 System.out.println("嗷!嗷!"); 32 } 33 } 34 35 //定义狗叫的方法,带心情参数 36 public void bark(int mood){ 37 switch(mood){ 38 case NORMAL: 39 System.out.println(BARK_NORMAL); 40 break; 41 case HAPPY: 42 System.out.println(BARK_HAPPY); 43 break; 44 case SAD: 45 System.out.println(BARK_SAD); 46 break; 47 } 48 } 49 50 //定义 main 方法 51 public static void main(String[] args) { 52 //创建了名字叫小白的狗对象 53 Dog xiaoBai = new Dog(); 54 //调用它叫的方法 55 xiaoBai.bark(); 56 57 //调用带参数的方 法 58 xiaoBai.bark(HAPPY); 59 } 60 61 } 运行程序,看一下输出结果: 二、类定义中的五个顶级成员(top-level member) 实体 static 修饰 没用 static 修饰 成员变量 类变量 实例变量 初始化块 静态初始化块 实例初始化块 构造方法 / 构造方法 方法 类方法 实例方法 接口 嵌套的接口 member interface / 类 嵌套顶层类 nested top-level class 内部成员类 inner member class 三、成员变量(类或对象的状态) 1、认识成员变量(类或对象的状态)、类变量、实例变量、局部变量、方法参数之间的区别 成员变量(field)是没有定义在代码块(包括初始化块、成员方法)中的变量。成员变量是类变量还 是实例变量取决于在其声明中是否使用了 static 关键字。 类变量在声明是用了 static 关键字,它的另一个名字叫静态变量、静态成员变量(static field) 。 实例变量是在声明时没有使用 static 关键字的成员变量,它的另一个名字叫非静态成员变量(non-static field)。 定义在代码块里的变量被称为局部变量(local variable)。 定义在方法声明中的变量叫方法参数。 view source print? 01 public class Lesson08 { 02 03 // 类变量 04 static String s1 = "类变量"; 05 06 // 实例变量 07 String s2 = "实例变量"; 08 09 // 初始化代码块里的局部变量 10 { 11 String s3 = "初始化代码块里的局部变量"; 12 System.out.println(s3); 13 } 14 15 // 静态初始化代码块里的局部变量 16 static { 17 String s4 = "静态初始化代码块里的局部变量"; 18 System.out.println(s4); 19 } 20 21 // 方法的参数和方法里的局部变 量 22 public void printString(String s5) { 23 String s6 = "方法里的局部变量"; 24 System.out.println("方法的参数:"+s5); 25 System.out.println(s6); 26 } 27 28 // 类方法 29 public static void printString() { 30 String s7="类方法里的局部变量"; 31 System.out.println(s7); 32 } 33 34 // main 方法 35 public static void main(String[] args) { 36 37 //调用类方法 38 Lesson08.printString(); 39 40 //打印类变量 41 System.out.println(s1); 42 43 //创建对象 44 Lesson08 lesson = new Lesson08(); 45 46 //打印实例变量 47 System.out.println(lesson.s2); 48 49 //调用实例方法 50 lesson.printString("参数的值"); 51 52 } 53 54 } 对于他们之间的区别,我们在以后的学习中你会越来越清晰的。 2、变量的初始化 实例变量一经定义就会有初始值,局部变量定义时不赋初值而直接使用,编译器会报错 view source print? 01 public class Lesson08_1 { 02 03 int i; 04 static int j; 05 { 06 int k; 07 System.out.println(k); 08 } 09 10 static { 11 int l; 12 System.out.println(l); 13 } 14 15 public void print(String m){ 16 17 System.out.println(m); 18 } 19 20 // main 方法 21 public static void main(String[] args) { 22 int n; 23 System.out.println(n); 24 25 Lesson08_1 lesson =new Lesson08_1(); 26 lesson.print("m"); 27 } 28 29 } 运行程序,查看结果: 然后我们再给局部变量都附上初值,再把实例变量和类变量都打印出来看看,代码如下: view source print? 01 public class Lesson08_1 { 02 03 int i; 04 static int j; 05 06 { 07 int k=2; 08 System.out.println(k); 09 } 10 11 static { 12 int l=2; 13 System.out.println(l); 14 } 15 16 public void print(String m){ 17 18 System.out.println(m); 19 } 20 21 // main 方法 22 public static void main(String[] args) { 23 24 System.out.println(j); 25 26 int n=2; 27 System.out.println(n); 28 29 Lesson08_1 lesson =new Lesson08_1(); 30 lesson.print("m"); 31 System.out.println(lesson.i); 32 } 33 34 } 运行程序,查看结果: 我们看到类变量和实例变量没赋值照样有值打印出来,我们也看到 int 的初始值是 0 。 实例变量和类变量的类型 初始值 整数 0 浮点类型 0.0 字符类型 ‘/u0000′ 布尔类型 boolean false 引用数据类型(譬如数组、接口、类) null 四、方法(类或对象的行为) 1、方法 Java 中类的行为由类的成员方法来实现。类的成员方法由方法的声明和方法体两部分组成。 修饰符,可选,用于指定谁有权限访问此方法。 返回值类型,必选,用于指定该方法的返回值数据类型;如果该方法没有返回值,则要用关键字 void 进 行标示。方法的返回值只能有一个。 参数列表,可以有 0 到多个,多个参数之间要用逗号隔开,参数的写法形如:String[] args, int age 这样。 方法名,必选,这个……,好吧命名规则是方法名和变量名的首字母要小写,别丢我人,弄个大写方法名出 来。 方法体,可选,这个……, 大括号,大括号不写的方法叫抽象方法。 2、属性和方法之间的关系 有句绕口令是这么说的:“状态影响行为,行为影响状态”。你有没有想过这问题,如果每个对象都是 从同一个类中生成出来,每个对象如果都一摸一样,那么这个世界是不是太无趣了。好在,我们看到前面 的例子中,小狗的大小属性影响了他叫的方式。通过设置狗大小的方法又改变了它的状态。这些属性和方 法的细节上的不同导致了,多姿多彩的对象,我们后面还会讲到更多的技术,也会导致更多的多样性。 五、方法重载 overload Java 里可以提供同一个方法的多个不同参数的版本供我们调用,譬如上面的小白,它叫 bark() 的方法 有两种,一种是很随意的叫,无拘无束的叫,还有一种是根据它心情的不同来叫,当然我还可以再定义一 个方法可以让他根据主人的脸色来叫,我们也可以再定义一个方法,穿的参数是食物,那么它的叫声可能 就是边吃边幸福的吼叫了…… 这样一个 bark 方法就带来了丰富多彩的变化。 在 Java 中允许类定义中多个方法的方法名相同,只要它们的参数声明不同即可。这种情况下,该方法 就被称为重载(overloaded ),这种方式就叫做方法重载(method overloading )。方法重载是实现程序多 样性的一个重要手段。也可以称作多态的一种表现方式。 重载规则: 1. 重载方法必须改变方法参数列表 2. 重载方法可以改变返回类型 3. 重载方法可以改变访问修饰符 4. 重载方法可以声明新的或更广的检验异常 5. 方法能够在同一个类或者一个子类中被重载 view source print? 01 public class lesson08_2 { 02 03 static long max(long a,long b){ 04 System.out.println("max(long a,long b)"); 05 return a>b?a:b; 06 } 07 08 static long max(long a,int b){ 09 System.out.println("max(long a,int b)"); 10 return a>b?a:b; 11 } 12 13 static int max(int a,int b){ 14 System.out.println("max(int a,int b)"); 15 return a>b?a:b; 16 } 17 18 static byte max(byte a,byte b){ 19 System.out.println("max(byte a,byte b)"); 20 return a>b?a:b; 21 } 22 23 public static void main(String[] args) { 24 byte byte1 = 125; 25 byte byte2 = 126; 26 int int1 = 1; 27 int int2 = 2; 28 long long1 = 1000; 29 long long2 = 2000; 30 31 System.out.println(max(byte1,byte2)); 32 System.out.println(max(int1,int2)); 33 System.out.println(max(byte1,int2)); 34 System.out.println(max(int1,long2)); 35 System.out.println(max(long1,int2)); 36 System.out.println(max(long1,long2)); 37 } 38 } 上面的例子说明了参数声明不同的含义,那就是只要参数的个数,类型和顺序任意一项不同就算不同 的参数声明,即使它们看起来很相似,甚至看起来可能会让虚拟机搞混。不过没关系,虚拟机很聪明,只 要你按照规则走他就能分清。 六、构造函数 在 Java 中,对象是构造出来的,特意用了一个 new 关键字来标示这个创建的过程。 我们把上一讲的例子修改一下,看看创建对象的过程发生了什么。 view source print? 01 public class Dog { 02 03 // 定义了狗的个头大小的属性 04 private int size=3; 05 06 public Dog(int size){ 07 System.out.println("带参数的构造函数"); 08 this.size = size; 09 } 10 11 public Dog(){ 12 System.out.println("不带参数的构造函数"); 13 this.size=2; 14 } 15 16 // 定义狗叫的方法 17 public void bark(){ 18 if(size<5){ 19 System.out.println("汪汪汪!"); 20 }else{ 21 System.out.println("嗷!嗷!"); 22 } 23 } 24 25 //定义 main 方法 26 public static void main(String[] args) { 27 28 //创建了名字叫小黄的狗对象 29 Dog xiaoHang = new Dog(4); 30 31 //调用它的叫方法 32 xiaoHang.bark(); 33 34 //创建了名字叫大黄的狗对象 35 Dog daHang = new Dog(6); 36 37 //调用它的叫方法 38 daHang.bark(); 39 40 //创建了名字叫小黑的狗对象 41 Dog xiaoHei = new Dog(); 42 43 //调用它的叫方法 44 xiaoHei.bark(); 45 } 46 } 我们看到创建对象的过程就是执行构造函数的过程,而且也看到构造方法也可以重载。 我们在这里明确的是说明构造函数或者说构造方法,它不是方法。它们之间的三大区别,为了让你记 清楚,我做了个图: 关于构造方法,还有一部分非常有趣的内容我们放在继承章节和大家一起分享。 本讲就到这里,各位再见,Take some time and enjoy it 。 Java 基础第九讲:面向对象基础(三) 24 Sep 本讲内容:继承、变量隐藏、方法重写、包、修饰符、this、super 一、继承 1、继承的概念 继承是面向对象的三大特性之一。在语义上继承的意思是照法律或遵照遗嘱接受死者的财产、头衔、 地位等,在 Java 程序中的继承也有这个意思,不过子类继承的是父类的属性和方法。 2、继承的语法结构(子类的定义方式) 3、继承的例子: 关于继承我们第七讲举了一个白马和马的例子,我们再举一个动物父类和鸟类子类、鱼类子类的例子,这 次我们用类图表示。 当我们写好了一个动物类,我们写鸟类的时候就可以继承动物类,自动获得动物类所拥有的属性和方 法,提高了代码重用性。 4、Object 类 Java 中的所有对象(类)都是 Object 类的子类。我们可以用 javap 查看一下一个最简单的类的字节码: view source print? 1 public class Lesson09 { 2 3 } 5、继承的原则: 1. 子类能够继承父类中被声明为 public 和 protected 的成员变量和成员方法。 2. 子类能够继承在同一个包中的默认修饰符修饰的成员变量和成员方法。 3. 如果子类声明了一个与父类变量同名的成员变量,则子类不能继承父类的成员变量,这种做法叫 做变量的隐藏。 4. 如果子类声明了一个与父类方法同名的成员方法,则子类不能继承父类的成员方法,这种做法方 法的重写。 二、包 package 1、编译单元(compilation unit) 在 Java 里,一个编译单元就是一个用来书写 Java 源代码的文本文件。我们前面讲类的定义的时候只关注了 类内部的东西,类外面是不是也有东西?答案是肯定的。编译单元有三个组成部分: 这三个部分都是可选的,包声明如果要有必须写在最前面,并且只能写一份。导入声明可以写多个, 类声明也可以写多个。 2、包的概念(package) 类名是类之间彼此区分的标示,一个程序中类数量增多是,必然会遇到类名冲突的情形。包提供了类的组 织和管理方式。包的用途有以下三种: 1. 将功能相近的类放在同一个包里,方便使用和查找 2. 类名相同的文件可以放在不同的包里而不会产生冲突 3. 可以依据包设定访问权限 3、包的声明 4、 包的例子: view source print? 01 //包的声明 02 package android.java.basic; 03 04 //导入声明 05 import java.util.Date; 06 07 //类声明 08 class Animal{ 09 long birthTime = new Date().getTime(); 10 11 void eat(){ 12 System.out.println("eating"); 13 } 14 } 15 16 //类声明 17 class Fish extends Animal { 18 void swim(){ 19 System.out.println("swimming"); 20 } 21 } 22 23 //类声明 24 public class Lesson09 { 25 public static void main(String[] args){ 26 27 //动物类 28 Animal a = new Animal(); 29 a.eat(); 30 System.out.println(a.birthTime); 31 32 //鱼类 33 Fish f = new Fish(); 34 f.eat(); 35 f.swim(); 36 System.out.println(f.birthTime); 37 } 38 } 运行程序,查看结果: 四、访问修饰符 public protected 默认的 private 在 Java 中可以用访问修饰符来控制类或者类成员对程序的其它部分的可见性,从而在语言级别实现访问控 制。当一个类无权访问另一个类或者类的成员时,编译器会提示你试图访问一些可能不存在的内容。 看见性 public protected 默认 private 从同一个类 是 是 是 是 从同一个包中的任何类 是 是 是 否 从同一个包中的子类 是 是 是 否 从包外的子类 是 是,通过继承 否 否 从包外的任何非子类的类 是 否 否 否 1. 对于类的修饰符,只能有 2 个选择,用 public 修饰或者不用(不用就是默认修饰符)。 2. 如果一个类本身对另一个类不可见,则即使将其成员声明为 public,也没有一个成员是可见的, 只有当你确类本身对你是可见的时,查看其各个成员的访问级别才有意义。 3. 对于类的成员(member, 包括属性和方法),可以用 public protected 默认的和private 4种修饰符。 4. 永远不要用访问修饰符修饰局部变量,编译器会毫不留情的报错。(记住:局部变量只有一个修饰 符可以用,那就是 final) 除了访问修饰符外,还有非访问修饰符 static、final、abstract、transient、synchronization、native、strictfy , 我们在今后的学习中逐步掌握。 五、变量隐藏(shadowing)、方法重写(Overiding) 当子类继承父类时,子类中一不小心就会定义出父类名字相同的成员变量,对于这种现象,规则里是 怎么说的,又是怎么应用的?用一句话说,就是子类成员会覆盖父类成员;对于变量就是变量隐藏,对于 方法就是方法重写(方法覆盖)。 1、变量隐藏 shadow 在做名词时意思是阴影,在做动词时意思是遮蔽,那么这里的意思 shadowing 更多的是遮蔽的 意思,不过我们翻译的时候大家已经习惯说这个叫变量的隐藏。 先看一个局部变量遮蔽成员变量的例子: view source print? 01 public class Lesson09_1 { 02 03 int i=1; 04 int j=1; 05 int k=1; 06 07 void test(int i){ 08 int j=2; 09 System.out.println("i="+i); 10 System.out.println("j="+j); 11 System.out.println("k="+k); 12 } 13 14 public static void main(String[] args){ 15 Lesson09_1 lesson = new Lesson09_1(); 16 lesson.test(2); 17 } 18 } 我们可以看到,当方法内的局部和成员变量名字相同时,在方法内,局部变量遮蔽住了成员变量,因此打 印出来的是 2,而不是 1。 再看一个子类成员变量遮蔽父类成员变量的例子。 view source print? 01 public class WhiteHorse extends Horse { 02 03 private static String color ="白色"; 04 05 public static int leg =4; 06 07 public static void main(String[] args){ 08 09 WhiteHorse xiaobai = new WhiteHorse(); 10 System.out.println(xiaobai.color); 11 System.out.println(xiaobai.leg); 12 13 //类变量是遮蔽不住的 14 System.out.println(Horse.color); 15 16 //强制转换后我们看到父类的实体 leg 变量还在,只是被隐藏了 17 Horse xiaobai1 = (Horse)xiaobai; 18 System.out.println(xiaobai1.leg); 19 20 } 21 22 } 运行程序,查看结果: 2、方法重写 Override 当子类继承父类时,如果子类方法的签名和父类方法的签名相同时,子类就无法继承父类的方法,此 时子类的方法就覆盖了父类的方法,我们称之为重写。重写可以定义子类某个行为的特殊性。 譬如动物会喝水,但是猫喝水和人喝水的具体行为就不同。 重写方法的规则如下: 1. 参数列表必须与重写的方法的参数列表完全匹配(方法签名相同)。如果不匹配,你得到的将是方 法重载。 2. 返回类型必须与超类中被重写方法中原先声明的返回类型或其子类型相同。 3. 访问级别的限制性可以比被重写方法弱,但是访问级别的限制性一定不能比被重写方法的更严格。 4. 仅当实例方法被子类继承时,它们才能被重写。子类和超类在同一个包内时,子类可以重写未标 示为 private 和 final 的任何超类方法。不同包的子类只能重写标示为 public 或 protected 的非 final 方法。 5. 无论父类的方法是否抛出某种运行时异常,子类的重写方法都可以抛出任意类型的运行时异常。 6. 重写方法一定不能抛出比被重写方法声明的检验异常更新或更广的检验异常,可以抛出更少或更 有限的异常。 7. 不能重写标示为 final 的方法。 8. 不能重写标示为 static 的方法。 9. 如果方法不能被继承,那么方法不能被重写。 我们举一个重写的例子: Horse.java view source print? 1 public class Horse { 2 //给马写个摆 Pose 的方法 3 public void pose(){ 4 //样子很酷 5 System.out.println("Cool!"); 6 } 7 } WhiteHorse.java view source print? 01 public class WhiteHorse extends Horse { 02 03 //白马重写了摆 pose 的方法 04 public void pose(){ 05 //白马更酷一点 06 System.out.println("Cool!!!!"); 07 } 08 09 public static void main(String[] args){ 10 WhiteHorse xiaobai = new WhiteHorse(); 11 xiaobai.pose(); 12 } 13 } 运行程序,查看结果: 我们再把白马类中 pose 方法的访问修饰符改成 private 试试看: 六、this 和 super 1、this.成员变量 当成员变量被局部变量隐藏时想使用成员变量,可以用 this 关键字来访问成员变量。 view source print? 01 public class Lesson09_1 { 02 03 int i=1; 04 int j=1; 05 int k=1; 06 static int l = 1; 07 08 void test(int i){ 09 int j=2; 10 int l=2; 11 System.out.println("i="+i); 12 System.out.println("j="+j); 13 System.out.println("k="+k); 14 System.out.println("l="+l); 15 16 System.out.println("this.i="+this.i); 17 System.out.println("this.j="+this.j); 18 System.out.println("this.k="+this.k); 19 System.out.println("this.l="+this.l); 20 21 } 22 23 public static void main(String[] args){ 24 Lesson09_1 lesson = new Lesson09_1(); 25 lesson.test(2); 26 27 } 28 } 运行程序,我们可以看到使用 this 关键字时可以看到被隐藏的成员变量可以正常访问了。 2、this() 构造函数 在构造方法中可以使用 this() 来引用另一个构造方法。 view source print? 01 public class Lesson { 02 03 private int minute=0; 04 05 Lesson(){ 06 this(45); 07 } 08 09 Lesson(int minute){ 10 this.minute = minute; 11 } 12 13 public static void main(String[] args){ 14 Lesson lesson = new Lesson(); 15 System.out.println(lesson.minute); 16 17 Lesson lesson2 = new Lesson(30); 18 System.out.println(lesson2.minute); 19 } 20 } 运行程序查看结果: 我们看到 this(45),的确调用了另外一个带参数的构造方法。需要注意的是 this()必须写在构造方法的第一 行。 3、super.成员 当父类的成员变量被隐藏、成员方法被重写(覆盖),此时想使用父类的这些成员时就要用 super 关键 字。我们改一下上面马和白马的例子: Horse.java view source print? 01 public class Horse { 02 03 public int height =120; 04 05 //给马写个摆 Pose 的方法 06 public void pose(){ 07 //样子很酷 08 System.out.println("Cool!"); 09 } 10 } WhiteHorse.java view source print? 01 public class WhiteHorse extends Horse { 02 03 public int height =150; 04 05 //白马重写了摆 pose 的方法 06 public void pose(){ 07 08 //先摆一个马的 pose 09 super.pose(); 10 11 //白马更酷一点 12 System.out.println("Cool!!!!"); 13 } 14 15 public void printHeight(){ 16 17 //打印父类被隐藏的变量 18 System.out.println(super.height); 19 20 //打印实例变量 21 System.out.println(height); 22 } 23 24 public static void main(String[] args){ 25 WhiteHorse xiaobai = new WhiteHorse(); 26 xiaobai.pose(); 27 xiaobai.printHeight(); 28 29 } 30 } 运行程序查看结果: 我们看到在子类的方法里可以使用 super 来引用被隐藏的父类变量,被覆盖(重写)的父类方法。 4、super() 父类构造函数 讲 super()之前,我们先看一下这个例子: Horse.java view source print? 1 public class Horse { 2 3 public Horse(){ 4 System.out.println("马类的构造函数"); 5 } 6 7 } WhiteHorse.java view source print? 01 public class WhiteHorse extends Horse { 02 03 public WhiteHorse(){ 04 System.out.println("白马类的构造函数"); 05 06 } 07 08 public static void main(String[] args){ 09 new WhiteHorse(); 10 } 11 } 运行程序查看结果: 我们看到,构造白马类之前,虚拟机先构造了它的父类马类,由此我们看到了白马类能继承马类的属 性和方法的根本原因,原来每一个白马类同时也是一个马类,还是一个 Object 类。在创建对象时,一个对 象的逐级父类自顶向下依次都创建了。 上图是一个白马对象在内存中的示意图,我们看到最外面的是 WhiteHorse,内层还有一个 Horse 对象, 更内层还有一个 Object 对象。 用下面的栈上的示意图可以更清晰的看到对象的创建过程。 首先调用的是 main 方法,main 方法调用 new WhiteHorse() ,WhiteHorse()构造函数调用了一个默认的 super(),super()方法就是父类的构造方法,以此类推最后调用了 Object()构造方法。 5、带参数的的 super()方法 在上面的例子里,我们看到编译器在你没有调用 super()方法的时候,插入了一个默认的 super()方法。 可惜的是编译器并不会自动插入带参数的 super(), 因此我们遇到这种情况就只能自己手工插入对 super()的 调用。 下面我们把上面的例子更改一下: Horse.java 的构造函数添加一个参数: view source print? 01 public class Horse { 02 03 protected int leg = 0; 04 05 public Horse(int leg){ 06 07 this.leg=4; 08 09 System.out.println("马类的构造函数"); 10 } 11 12 } 再次编译 WhiteHorse.java,出错提示如下: 标准 Java 编译器的提示有点故作神秘,这个提示几乎什么都没说;我们换个工具,在中文 Eclipse 上的提示 就明显多了: 我们按照它的提示更改一下 WhiteHorse 类: view source print? 01 public class WhiteHorse extends Horse { 02 03 public WhiteHorse(){ 04 super(4); 05 System.out.println("白马类的构造函数"); 06 } 07 08 public static void main(String[] args){ 09 new WhiteHorse(); 10 } 11 } 再次编译和运行程序,我们发现这次安然通过。 到这里,我们是不是可以小小总结一下,构造函数只能用 new、this() 和 super() 的方式来访问,是不 能像方法一样写方法名访问的。 本讲就到这里,下次再见。 Java 基础第十讲:面向对象基础(四) 13 Oct 本讲内容:抽象类、初始化块 一、抽象类 用 abstract 修饰的类定义,我们称之为抽象类,抽象类不能被实例化。 用 abstract 修饰的方法,我们称之为抽象方法,抽象方法不能有方法体。 面向对象中,所有的对象都是某一个类的实例,但是并不是每个类都可以实例化成一个对象。如果一 个类中没有足够的信息来描绘一个具体的对象,那么这个类就不能被实例化,我们称之为抽象类。抽象类 用来描述一系列看起来不同,但究其本质是相同的对象。譬如把苹果、橘子、梨等抽象出来一个概念叫水 果,我们把狗、老鼠、猫、狮子、大象、猪等抽象出来个概念叫动物。这时候我们把动物抽象成一个 Animal 类时,就最好不要让它直接初始化,创建出一个 Animal()实例对象的结果似乎难以想象。 抽象类被继承之外,没有用途,没有目的。 下面我们用一个 Test.java 的例子看一下什么叫抽象类: view source print? 01 abstract class Animal { 02 abstract void makenoise(); 03 } 04 05 class Lion extends Animal { 06 07 @Override 08 void makenoise() { 09 System.out.println("狮子吼!"); 10 } 11 } 12 13 class Dog extends Animal { 14 15 @Override 16 void makenoise() { 17 System.out.println("狗叫!"); 18 } 19 } 20 21 public class Test { 22 23 public static void main(String[] args){ 24 25 Animal a1 = new Dog(); 26 Animal a2 = new Lion(); 27 28 a1.makenoise(); 29 a2.makenoise(); 30 } 31 } 编译和运行程序,我们看看结果: 这个例子里,我们有这么几点需要留意: 1. 一个编译单元里是可以写多个顶级类的,只要 public 修饰的顶级类只有一个就行了。 2. 用 abstract 修饰的类是抽象类 3. 用 abstract 修饰的方法是抽象方法,抽象方法没有方法体,也就是说不能写大括号。 4. 抽象类实际上是定义了一个标准和规范,等着他的子类们去实现,譬如动物这个抽象类里定义了 一个发出声音的抽象方法,它就定义了一个规则,那就是谁要是动物类的子类,谁就要去实现这 个抽象方法。 5. 狗和狮子的类继承了动物这个抽象类,实现了发出声音的方法。 6. 一个对象除了被看成自身的类的实例,也可以被看成它的超类的实例。我们把一个对象看做超类 对象的做法叫做向上转型。譬如 Animal a1 = new Dog(); 7. 虽然都是动物类型,但是方法在运行时是按照它本身的实际类型来执行操作的。因此 a1.makenoise()执行的是狗叫,a2.makenoise()执行的是狮子吼,我们称之为运行时多态。 我们再看一下把一个类看做一个超类有什么样的损失或者不便,我们把上面的例子稍微改一下: view source print? 01 abstract class Animal { 02 abstract void makenoise(); 03 } 04 05 class Lion extends Animal { 06 07 @Override 08 void makenoise() { 09 System.out.println("狮子吼!"); 10 } 11 } 12 13 class Dog extends Animal { 14 15 @Override 16 void makenoise() { 17 System.out.println("狗叫!"); 18 } 19 20 void bark(){ 21 System.out.println("汪,汪!"); 22 } 23 } 24 25 public class Test { 26 27 public static void main(String[] args){ 28 29 Animal a1 = new Dog(); 30 Animal a2 = new Lion(); 31 32 a1.makenoise(); 33 a2.makenoise(); 34 35 ((Dog)a1).bark(); 36 } 37 } 运行程序,查看结果: 我们把焦点放在第 35 行,我们再 a1 前面加了一个(Dog),这个做法的意思是把 a1 强制转换为 Dog 对 象,只有转换为 Dog 对象后,才能使用 bark 方法,否则即使你知道他是一个 Dog 对象也不能调用 bark 方 法。这就是子类对象付给超类引用所带来的不便或者说是损失。 二、初始化块 我们已经知道在类中有两个位置可以放置执行操作的代码,这两个位置是方法和构造函数。初始化块 是第三个可以放置执行操作的位置。当首次加载类(静态初始化块)或者创建一个实例(实例初始化块) 时,就会运行初始化块。 我们看一个例子: view source print? 01 class SuperClass{ 02 SuperClass(){ 03 System.out.println("父类 SuperClass 的构造函数"); 04 } 05 } 06 07 public class Initialize extends SuperClass { 08 09 Initialize(int x){ 10 System.out.println("带参数的构造函数"); 11 } 12 13 Initialize(){ 14 System.out.println("不带参数的构造函数"); 15 } 16 17 static { 18 System.out.println("第一个静态初始化块"); 19 } 20 21 { System.out.println("第一个实例初始化块");} 22 23 { System.out.println("第二个实例初始化块");} 24 25 static { 26 System.out.println("第二个静态初始化块"); 27 } 28 29 public static void main(String[] args){ 30 new Initialize(1); 31 new Initialize(); 32 } 33 } 编译并运行程序,查看结果: 从上面的例子中我们需要留意如下几点: 1. 初始化块的语法相当简单,它没有名称,没有参数,也没有返回值,只有一个大括号。用 static 修 饰的初始化块就要静态初始化块,相对应的,没有 static 修饰的初始化块就叫实例初始化块。 2. 静态初始化块在首次加载类时会运行一次。 3. 实例初始化块在每次创建对象时会运行一次。 4. 实例初始化块在构造函数的 super()调用之后运行。 5. 初始化块之间的运行顺序取决于他们在类文件中出现的顺序,出现在前面的先执行。 6. 初始化块从书写惯例上应该写在靠近类文件的顶部,构造函数附近的某个位置。 好吧,本讲就到这里。 Java 基础第十一讲:面向对象基础(五) 14 Oct 本讲内容:接口 一、为什么要有接口 我们已经知道 Java 中只支持单继承,或者说不允许多重继承的出现,又可以说一个类只能有一个父类。 为了提供类似多重继承的功能,Java 提供了接口的功能,下面我们共同学习一下接口。 我们还是拿一个例子来引入接口的概念吧。 上面是一个高度浓缩过的例子,假设下面的子类数量远远不止 4 种,也假设方法数来那个也不止 2 个。 首先我们定义了一个动物的类,它有吃和叫的方法,接下来我们想增加一个玩耍的方法和亲近主人的 方法,如果把这两个方法定义在动物类里,看起来确实不合理,因为老虎对象也会继承到亲近主人的方法, 如果该方法默认实现是用舌头舔主人的脖子的话,就会产生老虎舔你脖子讨好你的场景,似乎有点太销魂 了,这种设计方式副作用太大。 如果把这两个方法定义在猫狗等需要的类里,这时候又会产生同样的内容重复写多次的情形,更不可 接受。 哎这时候你可能在想如果 Java 里可以多重继承多好啊,我再定义一个宠物类,在里面定义玩耍和亲近 主人的方法,然后让猫狗这些类也去继承宠物类问题不就解决了?可惜 Java 不允许这么干…… (关于为什 么 Java 中不允许多重继承,可以参见 Java 番外篇致命方块的诞生)。 好在 Java 里提供了接口的功能,你可以把宠物和动物都定义成接口,让猫狗去实现这两个接口,也可 以把动物定义成一个普通类或者抽象类,让猫狗去继承动物,再让猫狗去实现宠物接口。 下面我们用代码表达出来。在这里我用中文标识符是为了提高教学效果,请在实际编程中彻底断绝用 中文标识符的想法,别因为猎奇的原因今后开始用中文,还说是我教你的^_^ view source print? 01 class Animal { 02 03 public void 吃() { 04 System.out.println("吃"); 05 } 06 07 public void 叫() { 08 System.out.println("叫"); 09 } 10 11 } 12 13 interface Pet { 14 public void 玩耍(); 15 16 public void 讨好主人(); 17 } 18 19 class Lion extends Animal { 20 } 21 22 class Tiger extends Animal { 23 } 24 25 class Cat extends Animal implements Pet{ 26 27 @Override 28 public void 玩耍() { 29 System.out.println("玩耍"); 30 } 31 32 @Override 33 public void 讨好主人() { 34 System.out.println("舔你脖子(什么嗜好……)"); 35 } 36 } 37 38 class Dog extends Animal implements Pet{ 39 40 @Override 41 public void 玩耍() { 42 System.out.println("玩耍"); 43 } 44 45 @Override 46 public void 讨好主人() { 47 System.out.println("舔你脖子(狗也这样……)"); 48 } 49 } 50 51 public class XiaoBai{ 52 public static void main(String[] args){ 53 Dog xiaobai = new Dog(); 54 xiaobai.叫(); 55 xiaobai.吃(); 56 xiaobai.玩耍(); 57 xiaobai.讨好主人(); 58 } 59 } 编译并运行程序,查看结果: 我们看到使用接口完美的解决了上面的问题。 最后记住接口不仅仅是为了解决多重继承问题才出现的,要不然会被人笑话的^_^。 二、接口的几个规则 1. 接口名用 interface 修饰, 相对应的 类名用 class 修饰 2. 接口里定义的方法都是抽象方法,可以用 abstract 修饰,当然也可以不用它修饰 3. 接口只能被实现 ( implements ) 4. 可以用抽象类实现接口,也就是说虽然实现了,但是没有真正写接口的任何方法,它把责任推给 了抽象类的子类 5. 普通类实现接口,则必须按照接口的契约,实现接口所定义的所有方法。 6. 接口可以继承接口,或者说一个接口可以是另一个接口的父类 7. 一个接口可以继承多个父类,也就是说一个接口之间可以多重继承。 以上规则,有点超浓缩了,请同学们慢慢体会。 最后总结一下,当你实现接口时就表明你同意遵守定义在接口中的契约,也意味着你肯定实现了接口 定义的所有方法。那么任何了解该接口方法形式的人,都确信他们能够调用你所实现的类,去执行接口中 的方法。 这时候我们说接口是一个契约,是一个 like a 的关系(继承是 is a 关系)。 很多时候我们说不要滥用继承,要用接口,不要用继承,也是基于这样的思考。 好了本讲就到这里,Take your time and enjoy it 。 Java 基础第十二讲:面向对象基础(六) 15 Oct 本讲内容:内部类 Java 语言允许在类中再定义类,这种在其它类内部定义的类就叫内部类。内部类又分为:常规内部类、 局部内部类、匿名内部类和静态嵌套类四种。我们内部类的知识在 Android 手机开发中经常用到。 一、常规内部类 所谓常规内部类,或者说内部类,指的就是除去后面三种之外的内部类(这算什么解释。。。) 先写一个最简单的内部类的例子,大家感觉一下: view source print? 1 public class Outer { 2 public class Inner{ 3 } 4 } 编译一下,我们看到目录中出现了两个 class 文件,其中有一个文件名叫做 Outer$Inner.class,带了一 个$符号,这个特点让我们很容易的认出来这是内部类编译后的 class 文件。 再写一个稍微复杂一点的内部类: view source print? 01 public class Outer { 02 03 private int x=1; 04 05 public Outer(){ 06 System.out.println("Outer initial"); 07 } 08 09 public class Inner{ 10 11 public Inner(){ 12 System.out.println("Inner initial"); 13 } 14 15 private int x=2; 16 17 public void add(){ 18 int x=3; 19 System.out.println(x); 20 System.out.println(this.x); 21 System.out.println(Outer.this.x); 22 } 23 24 } 25 26 public static void main(String[] args){ 27 Inner inner = new Outer().new Inner(); 28 inner.add(); 29 } 30 } 我们编译以后,运行一下看看: 在上面的例子里我们可以清晰的看到: 1. 内部类就像一个实例成员一样存在于外部类中。 2. 内部类可以访问外部类的所有成员就想访问自己的成员一样没有限制。 3. 内部类中的 this 指的是内部类的实例对象本身,如果要用外部类的实例对象就可以用类名.this 的 方式获得。 4. 内部类对象中不能有静态成员,原因很简单,内部类的实例对象是外部类实例对象的一个成员。 下面我们再小结一下内部类的创建方法: 1. 在外部类的内部,可以用 Inner inner = new Inner(); 方法直接创建 2. 在外部类外部,必须先创建外部类实例,然后再创建内部类实例,除了上面 Inner inner = new Outer().new Inner()的写法以外,还有 Outer outer = new Outer(); Inner inner = outer.new Inner();的写 法 二、局部内部类 我们也可以把类定义在方法内部,这时候我们称这个类叫局部内部类。 我们再看一个例子: view source print? 01 public class Outer { 02 03 int x =1; 04 public void doSomething(){ 05 final int y=2; 06 class Inner{ 07 int x =3; 08 void print(){ 09 int x=4; 10 System.out.println(x); 11 System.out.println(this.x); 12 System.out.println(Outer.this.x); 13 System.out.println(y); 14 } 15 } 16 Inner inner = new Inner(); 17 inner.print(); 18 } 19 20 public static void main(String[] args){ 21 Outer outer = new Outer(); 22 outer.doSomething(); 23 } 24 } 运行程序,查看结果: 我们通过上面这里例子也可以看到下面几点: 1. 局部内部类的地位和方法内的局部变量的位置类似,因此不能修饰局部变量的修饰符也不能修饰 局部内部类,譬如 public、private、protected、static、transient 等 2. 局部内部类只能在声明的方法内是可见的,因此定义局部内部类之后,想用的话就要在方法内直 接实例化,记住这里顺序不能反了,一定是要先声明后使用,否则编译器会说找不到。 3. 局部内部类不能访问定义它的方法内的局部变量,除非这个变量被定义为 final 。 是不是有点不好理解?关于为什么用 final 修饰以后就可以用了,我打算专门在番外篇里专门写一篇博 客给你讲清楚,先记住吧。 三、匿名内部类 当我们把内部类的定义和声明写到一起时,就不用给这个类起个类名而是直接使用了,这种形式的内 部类根本就没有类名,因此我们叫它匿名内部类。 我们再看一个有趣的例子: view source print? 01 public class Dog { 02 03 public interface Pet { 04 05 public void beFriendly(); 06 public void play(); 07 08 } 09 10 public static void main(String[] args){ 11 12 Pet dog = new Pet(){ 13 @Override 14 public void beFriendly() { 15 System.out.println("蹭蹭你^_^"); 16 } 17 @Override 18 public void play() { 19 System.out.println("把飞盘叼给你,逼你把飞盘丢出去,然后它再捡回来让你继续扔,连续 500 次^_^"); 20 } 21 }; 22 23 dog.beFriendly(); 24 dog.play(); 25 26 } 27 } 编译和运行程序,查看结果: 竟然编译和运行都很正常,我们知道抽象类和接口肯定无法实例化的,因此刚才的例子肯定有点意思: 1. 第一匿名内部类可以是个接口,这个没什么好奇怪的哈。 2. 第 12 行到第 21 行是一个语句,就是定义了一个对象,因此 21 行大括号后面有个分号。 3. 匿名内部类用 new Pet(){ … } 的方式把声明类的过程和创建类的实例的过程合二为一。 4. 匿名内部类可以是某个类的继承子类也可以是某个接口的实现类。 好吧我们再看一个例子,方法参数内的匿名内部类: view source print? 01 public class Dog { 02 03 static abstract class Ball { 04 abstract String getName(); 05 } 06 07 void play(Ball b){ 08 System.out.println(b.getName()); 09 } 10 11 public static void main(String[] args){ 12 Dog dog = new Dog(); 13 14 dog.play(new Ball(){ 15 @Override 16 String getName() { 17 return "qiu qiu"; 18 }}); 19 } 20 } 编译和运行以后的截图我就不给你了,返回值就是“qiu qiu”。 从第 14 行到第 18 行是一句话,就是执行一个 play 方法,而这个方法的参数就由一个匿名内部类的实 例来提供。 四、静态嵌套类 为了让你感觉舒服一些,我们也把最简单的内部类放在最后讲。 当一个内部类前面用 static 修饰时,我们称之为静态嵌套类或者说静态内部类。 上面的例子里其实我们已经看到过静态嵌套类了,下面我们再举一个例子: view source print? 01 public class Outer { 02 03 static int x =1; 04 05 static class Nest { 06 07 void print(){ 08 System.out.println("Nest "+x); 09 } 10 } 11 12 public static void main(String[] args){ 13 Outer.Nest nest = new Outer.Nest(); 14 nest.print(); 15 } 16 } 因为静态嵌套类和其他静态方法一样只能访问其它静态的成员,而不能访问实例成员。因此静态嵌套 类和外部类(封装类)之间的联系就很少了,他们之间可能也就是命名空间上的一些关联。上面例子中你 需要注意的就是静态嵌套类的声明方法 new Outer.Nest() 连续写了两个类名,以至于我们都怀疑前面的 Outer 是个包名了,好在包名一般都小写的,要不还真分不清…… 再强调一遍,内部类在 Android 中应用的非常多,理解和使用好显得蛮重要。好了,本讲就到这里。 Java 基础第十三讲:数组 18 Oct 本讲内容:数组 数组是 Java 中的对象,它用来存储多个相同类型的基本数据类型或者对象引用。 一、声明数组 数组是通过说明它将要保存的元素类型来声明的,元素类型可以是对象或者基本类型。类型后面的方 括号可以在写在标识符的前面,也可以写在后面。当然我们推荐还是写在前面。 view source print? 1 int[] number1; 2 int number2[]; int[] number1 ; 把方括号紧贴着类型写,会明确的告诉我们声明的是一个对象他的名字是 number1,他 的类型是数组类型,而且是只能存储 int 类型的数组。 而后一种写法是 C 程序员更喜欢的写法。 可以声明多维数组,可以声明对象类型的数组: view source print? 1 String[][] s1; //二维数组 2 String[][][][] s2; //四维数组 3 String[] s3[]; //怪异写法的二维数组,这样写也是正确的,但是不建议这么干 Java 中的二维数组就是一维数组中的每一个元素都还是个数组,那么合起来就是二维数组了,以此类 推。 最后记住一句话在声明数组的时候不能在方括号中写数组的长度,因为声明数组的过程并没有创建数 组本身,只是定义了一个变量,但是变量并没被赋值。 二、构建数组 | 创建数组 | 实例化数组 构建数组意味着在堆上创建数组对象(所有的对象都存储在堆上,堆是一种内存存储结构,既然要存 储就设计空间分配问题,因此此时需要指定数组的大小)。而此时虽然有了数组对象,但数组对象里还没有 值。 view source print? 1 int[] scores; //声明数组 2 scores = new int[34]; //创建数组 3 int[] i = new int[22]; //声明并创建数组 构建数组意味着在堆上创建数组对象(所有的对象都存储在堆上,堆是一种内存存储结构,既然要存储就 设计空间分配问题,因此此时需要指定数组的大小)。 view source print? 1 int[][] xy= new int[2][3]; //声明并创建二维数组 2 int[][] mn= new int[2][]; //声明并创建二维数组,只创建第一级数组也是可以的。 3 mn[0]=int[4]; //分别定义第二级数组 4 mn[1]=int[5]; //他们的长度可以不同 三、初始化数组 | 给数组赋值 初始化数组就是把内容放在数组中。数组中的内容就是数组的元素。他们可以是基本数据类型也可以 是引用数据类型。如同引用类型的变量中保存的是指向对象的引用而不是对象本身一样。数组中保存的也 是对象的引用而不是对象本身。 view source print? 1 Pig[] pigs = new Pig[3]; //声明并创建猪数组 2 pigs[0] = new Pig(); //给每一个元素赋值,创建了三个猪对象,此时数组里才真正有了对象 3 pigs[1] = new Pig(); //数组用下标来赋值和访问,下标写在[]中,数组下标最大是声明数量减 1 4 pigs[2] = new Pig(); 下面我们再看一个例子: view source print? 1 int[] numbers={0,1,2,3,4,5,6,7,8,9}; 2 Pig[] pigs = {new Pig(),new Pig(),new Pig}; 3 int[][] xy={{2,3},{4,5},{6,7}}; 这种写法就是把声明、创建和初始化同时完成的快捷写法。注意这种写法不能被分拆: 1 int[] numbers; 2 numbers={0,1,2,3,4,5,6,7,8,9}; //这样的写法在 java 中是不允许的,这……很不幸 然而,下面的写法则是合法的: 1 int[] numbers; 2 numbers=new int[]{0,1,2,3,4,5,6,7,8,9}; //创建匿名数组并赋值 3 int[][] xy= new int[][]{{2,3},{4,5},{5,6}}; //创建二维匿名数组并赋值 4 int[] x=new int[3]{1,2,3}; //这样的写法是错误的 我们看到这样的写法多了个创建匿名数组的过程,记住创建匿名数组的时候不要在中括号中填写数组 的大小,否则会报错。 好了,本讲就到这里。 Java 基础第十四讲:字符串 19 Oct 本讲内容:字符串 程序开发的工作中 80%的操作都和字符串有关,这这句话请起来还是蛮有道理。 字符串成了串,就形成了一个类,这类就叫 String。 让我们留意一下 String 的源代码,第一,String 永远不可能有子类,它的实例也是无法改变的。第二,String 实现了 CharSequence 接口,而这个接口我们在 Android 开发中还是经常可以看到的。 一、创建字符串对象 view source print? 1 String s1 = new String("Milestone"); 2 String s2 = "String"; 以上就是创建字符串的两种方法,第一种是常规写法,创建一个对象当然就可以用 new 跟上个构造函数完 成。第二种是字符串对象的特殊写法,主要是字符串太常用了,所以 Java 在语言级别对其做了特殊照顾(作 弊?)。第二种写法,最常用,效率也高。(为什么说效率高,可以参见 Java 番外篇的相关文章) 二、字符串操作中的加号 我们经常要把两个或者更多的字符串拼接成一个字符串,除了普通的连接字符串的方法以外,Java 语言专 门为 String 提供了一个字符串连接符号“+” ,下面看一个例子: view source print? 01 public class StringTest { 02 public static void main(String[] args) { 03 04 String s1 = "abc"; 05 String s2= "xyz"; 06 String s3=s1.concat(s2); //第一种,用方法连接两个字符串 07 String s4=s1+s2; //第二种,用+号连接 08 System.out.println(s1); 09 System.out.println(s3); 10 System.out.println(s4); 11 12 int i = 1; 13 int j = 2; 14 String s5="3"; 15 System.out.println(i+j+s5); //第一个加号是数字和数字相加,是算数运算,第二个加号是数字和字符 串相加,就是连接操作了 16 System.out.println(""+i+j+s5); //为了保证都是字符串连接,我们再前面加一个空串。 17 } 18 } 编译并运行程序,查看结果: 三、字符串中的常用方法 charAt() 返回位于指定索引处的字符串 concat() 将一个字符串追加到另一个字符串的末尾 equalseIgnoseCase() 判断两个字符串的相等性,忽略大小写 length() 返回字符串中的字符个数 replace() 用新字符代替指定的字符 substring() 返回字符串的一部分 toLowerCase() 将字符串中的大写字符转换成小写字符返回 toString() 返回字符串的值 toUpperCase() 将字符串中的小写字符转换成大写字符返回。 trim() 删除字符串前后的空格 splite() 将字符串按照指定的规则拆分成字符串数组 (此处差一个例子) 好了,本讲就到这里。 Java 基础第十五讲:集合(一) 03 Nov 本讲内容:集合 collection 讲集合 collection 之前,我们先分清三个概念: 1. colection 集合,用来表示任何一种数据结构 2. Collection 集合接口,指的是 java.util.Collection 接口,是 Set、List 和 Queue 接口的超类接口 3. Collections 集合工具类,指的是 java.util.Collections 类。 SCJP 考试要求了解的接口有:Collection , Set , SortedSet , List , Map , SortedMap , Queue , NavigableSet , NavigableMap, 还有一个 Iterator 接口也是必须了解的。 SCJP 考试要求了解的类有: HashMap , Hashtable ,TreeMap , LinkedHashMap , HashSet , LinkedHashSet ,TreeSet , ArrayList , Vector , LinkedList , PriorityQueuee , Collections , Arrays 下面给出一个集合之间的关系图: 上图中加粗线的 ArrayList 和 HashMap 是我们重点讲解的对象。下面这张图看起来层级结构更清晰些。 我们这里说的集合指的是小写的 collection,集合有 4 种基本形式,其中前三种的父接口是 Collection。 1. List 关注事物的索引列表 2. Set 关注事物的唯一性 3. Queue 关注事物被处理时的顺序 4. Map 关注事物的映射和键值的唯一性 一、Collection 接口 Collection 接口是 Set 、List 和 Queue 接口的父接口,提供了多数集合常用的方法声明,包括 add()、 remove()、contains() 、size() 、iterator() 等。 add(E e) 将指定对象添加到集合中 remove(Object o) 将指定的对象从集合中移除,移除成功返回 true,不成功返回 false contains(Object o) 查看该集合中是否包含指定的对象,包含返回 true,不包含返回 flase size() 返回集合中存放的对象的个数。返回值为 int clear() 移除该集合中的所有对象,清空该集合。 iterator() 返回一个包含所有对象的 iterator 对象,用来循环遍历 toArray() 返回一个包含所有对象的数组,类型是 Object toArray(T[] t) 返回一个包含所有对象的指定类型的数组 我们在这里只举一个把集合转成数组的例子,因为 Collection 本身是个接口所以,我们用它的实现类 ArrayList 做这个例子: view source print? 01 import java.util.ArrayList; 02 import java.util.Collection; 03 04 public class CollectionTest { 05 06 public static void main(String[] args) { 07 08 String a = "a",b="b",c="c"; 09 Collection list = new ArrayList(); 10 list.add(a); 11 list.add(b); 12 list.add(c); 13 14 String[] array = list.toArray(new String[1]); 15 16 for(String s : array){ 17 System.out.println(s); 18 } 19 } 20 } 编译并运行程序,检查结果: 二、几个比较重要的接口和类简介 1、List 接口 List 关心的是索引,与其他集合相比,List 特有的就是和索引相关的一些方法:get(int index) 、 add(int index,Object o) 、 indexOf(Object o) 。 ArrayList 可以将它理解成一个可增长的数组,它提供快速迭代和快速随机访问的能力。 LinkedList 中的元素之间是双链接的,当需要快速插入和删除时 LinkedList 成为 List 中的不二选择。 Vector 是 ArrayList 的线程安全版本,性能比 ArrayList 要低,现在已经很少使用 2、Set 接口 Set 关心唯一性,它不允许重复。 HashSet 当不希望集合中有重复值,并且不关心元素之间的顺序时可以使用此类。 LinkedHashset 当不希望集合中有重复值,并且希望按照元素的插入顺序进行迭代遍历时可采用此类。 TreeSet 当不希望集合中有重复值,并且希望按照元素的自然顺序进行排序时可以采用此类。(自然顺 序意思是某种和插入顺序无关,而是和元素本身的内容和特质有关的排序方式,譬如“abc”排在“abd”前面。) 3、Queue 接口 Queue 用于保存将要执行的任务列表。 LinkedList 同样实现了 Queue 接口,可以实现先进先出的队列。 PriorityQueue 用来创建自然排序的优先级队列。番外篇中有个例子 http://android.yaohuiji.com/archives/3454 你可以看一下。 4、Map 接口 Map 关心的是唯一的标识符。他将唯一的键映射到某个元素。当然键和值都是对象。 HashMap 当需要键值对表示,又不关心顺序时可采用 HashMap。 Hashtable 注意 Hashtable 中的 t 是小写的,它是 HashMap 的线程安全版本,现在已经很少使用。 LinkedHashMap 当需要键值对,并且关心插入顺序时可采用它。 TreeMap 当需要键值对,并关心元素的自然排序时可采用它。 三、ArrayList 的使用 ArrayList 是一个可变长的数组实现,读取效率很高,是最常用的集合类型。 1、ArrayList 的创建 在 Java5 版本之前我们使用: view source print? 1 List list = new ArrayList(); 在 Java5 版本之后,我们使用带泛型的写法: view source print? 1 List list = new ArrayList(); 上面的代码定义了一个只允许保存字符串的列表,尖括号括住的类型就是参数类型,也成泛型。带泛 型的写法给了我们一个类型安全的集合。关于泛型的知识可以参见这里。 2、ArrayList 的使用: view source print? 01 List list = new ArrayList(); 02 list.add("nihao!"); 03 list.add("hi!"); 04 list.add("konikiwa!"); 05 list.add("hola"); 06 list.add("Bonjour"); 07 System.out.println(list.size()); 08 System.out.println(list.contains(21)); 09 System.out.println(list.remove("hi!")); 10 System.out.println(list.size()); 关于 List 接口中的方法和 ArrayList 中的方法,大家可以看看 JDK 中的帮助。 3、基本数据类型的的自动装箱: 我们知道集合中存放的是对象,而不能是基本数据类型,在 Java5 之后可以使用自动装箱功能,更方 便的导入基本数据类型。 view source print? 1 List list = new ArrayList(); 2 list.add(new Integer(42)); 3 list.add(43); 4、ArrayList 的排序: ArrayList 本身不具备排序能力,但是我们可以使用 Collections 类的 sort 方法使其排序。我们看一个例 子: view source print? 01 import java.util.ArrayList; 02 import java.util.Collections; 03 import java.util.List; 04 05 public class Test { 06 07 public static void main(String[] args) { 08 List list = new ArrayList(); 09 list.add("nihao!"); 10 list.add("hi!"); 11 list.add("konikiwa!"); 12 list.add("hola"); 13 list.add("Bonjour"); 14 15 System.out.println("排序前:"+ list); 16 17 Collections.sort(list); 18 19 System.out.println("排序后:"+ list); 20 } 21 22 } 编译并运行程序查看结果: 排序前:[nihao!, hi!, konikiwa!, hola, Bonjour] 排序后:[Bonjour, hi!, hola, konikiwa!, nihao!] 5、数组和 List 之间的转换 从数组转换成 list,可以使用 Arrays 类的 asList()方法: view source print? 01 import java.util.ArrayList; 02 import java.util.Collections; 03 import java.util.List; 04 05 public class Test { 06 07 public static void main(String[] args) { 08 09 String[] sa = {"one","two","three","four"}; 10 List list = Arrays.asList(sa); 11 System.out.println("list:"+list); 12 System.out.println("list.size()="+list.size()); 13 } 14 15 } 6、Iterator 和 for-each 在 for-each 出现之前,我们想遍历 ArrayList 中的每个元素我们会使用 Iterator 接口: view source print? 01 import java.util.Arrays; 02 import java.util.Iterator; 03 import java.util.List; 04 05 public class Test { 06 07 public static void main(String[] args) { 08 09 // Arrays 类为我们提供了一种 list 的便捷创建方式 10 List list = Arrays.asList("one", "two", "three", "four"); 11 12 // 转换成 Iterator 实例 13 Iterator it = list.iterator(); 14 15 //遍历 16 while (it.hasNext()) { 17 System.out.println(it.next()); 18 } 19 20 } 21 22 } 在 for-each 出现之后,遍历变得简单一些: view source print? 01 import java.util.Arrays; 02 import java.util.Iterator; 03 import java.util.List; 04 05 public class Test { 06 07 public static void main(String[] args) { 08 09 // Arrays 类为我们提供了一种 list 的便捷创建方式 10 List list = Arrays.asList("one", "two", "three", "four"); 11 12 for (String s : list) { 13 System.out.println(s); 14 } 15 16 } 17 18 } 好了,本讲就到这里,下次我们讲 HashMap。 Java 基础第十六讲:集合(二) 03 Nov 本讲内容:Map HashMap 前面课程中我们知道 Map 是个接口,它关心的是映射关系,它里面的元素是成对出现的,键和值都 是对象且键必须保持唯一。这一点上看它和 Collection 是很不相同的。 一、Map 接口 Map 接口的常用方法如下表所示: put(K key, V value) 向集合中添加指定的键值对 putAll(Map t) 把一个 Map 中的所有键值对添加到该集合 containsKey(Object key) 如果包含该键,则返回 true containsValue(Object value) 如果包含该值,则返回 true get(Object key) 根据键,返回相应的值对象 keySet() 将该集合中的所有键以 Set 集合形式返回 values() 将该集合中所有的值以 Collection 形式返回 remove(Object key) 如果存在指定的键,则移除该键值对,返回键所对应 的值,如果不存在则返回 null clear() 移除 Map 中的所有键值对,或者说就是清空集合 isEmpty() 查看 Map 中是否存在键值对 size() 查看集合中包含键值对的个数,返回 int 类型 因为 Map 中的键必须是唯一的,所以虽然键可以是 null,只能由一个键是 null,而 Map 中的值可没有 这种限制,值为 null 的情况经常出现,因此 get(Object key)方法返回 null,有两种情况一种是确实不存在该键 值对,二是该键对应的值对象为 null。为了确保某 Map 中确实有某个键,应该使用的方法是 containsKey(Object key) 。 二、HashMap HashMap 是最常用的 Map 集合,它的键值对在存储时要根据键的哈希码来确定值放在哪里。 1、HashMap 的基本使用: view source print? 01 import java.util.Collection; 02 import java.util.HashMap; 03 import java.util.Map; 04 import java.util.Set; 05 06 public class Test { 07 08 public static void main(String[] args) { 09 10 Map map = new HashMap(); 11 12 map.put(1, "白菜"); 13 map.put(2, "萝卜"); 14 map.put(3, "茄子"); 15 map.put(4, null); 16 map.put(null, null); 17 map.put(null, null); 18 19 System.out.println("map.size()="+map.size()); 20 System.out.println("map.containsKey(1)="+map.containsKey(2)); 21 System.out.println("map.containsKey(null)="+map.containsKey(null)); 22 System.out.println("map.get(null)="+map.get(null)); 23 24 System.out.println("map.get(2)="+map.get(2)); 25 map.put(null, "黄瓜"); 26 System.out.println("map.get(null)="+map.get(null)); 27 28 Set set = map.keySet(); 29 System.out.println("set="+set); 30 31 Collection c = map.values(); 32 33 System.out.println("Collection="+c); 34 35 } 36 37 } 编译并运行程序,查看结果: view source print? 1 map.size()=5 2 map.containsKey(1)=true 3 map.containsKey(null)=true 4 map.get(null)=null 5 map.get(2)=萝卜 6 map.get(null)=黄瓜 7 set=[null, 1, 2, 3, 4] 8 Collection=[黄瓜, 白菜, 萝卜, 茄子, null] 2、HashMap 中作为键的对象必须重写 Object 的 hashCode()方法和 equals()方法 下面看一个我花了 1 个小时构思的例子,熟悉龙枪的朋友看起来会比较亲切,设定了龙和龙的巢穴, 然后把它们用 Map 集合对应起来,我们可以根据龙查看它巢穴中的宝藏数量,例子只是为了说明 hashCode 这个知识点,所以未必有太强的故事性和合理性,凑合看吧: view source print? 01 import java.util.HashMap; 02 import java.util.Map; 03 04 public class Test { 05 06 public static void main(String[] args) { 07 08 // 龙和它的巢穴映射表 09 Map map = new HashMap(); 10 11 // 在 Map 中放入四只克莱恩大陆上的龙 12 map.put(new Dragon("锐刃", 98), new Nest(98)); 13 map.put(new Dragon("明镜", 95), new Nest(95)); 14 map.put(new Dragon("碧雷", 176), new Nest(176)); 15 map.put(new Dragon("玛烈", 255), new Nest(255)); 16 17 // 查看宝藏 18 System.out.println("碧雷巢穴中有多少宝藏:" + map.get(new Dragon("碧雷", 176)).getTreasure()); 19 } 20 21 } 22 23 // 龙 24 class Dragon { 25 26 Dragon(String name, int level) { 27 this.level = level; 28 this.name = name; 29 } 30 31 // 龙的名字 32 private String name; 33 34 // 龙的级别 35 private int level; 36 37 public int getLevel() { 38 return level; 39 } 40 41 public void setLevel(int level) { 42 this.level = level; 43 } 44 45 public String getName() { 46 return name; 47 } 48 49 public void setName(String name) { 50 this.name = name; 51 } 52 53 } 54 55 // 巢穴 56 class Nest { 57 58 //我研究的龙之常数 59 final int DRAGON_M = 4162; 60 61 // 宝藏 62 private int treasure; 63 64 // 居住的龙的级别 65 private int level; 66 67 Nest(int level) { 68 this.level = level; 69 this.treasure = level * level * DRAGON_M; 70 } 71 72 int getTreasure() { 73 return treasure; 74 } 75 76 public int getLevel() { 77 return level; 78 } 79 80 public void setLevel(int level) { 81 this.level = level; 82 this.treasure = level * level * DRAGON_M; 83 } 84 85 } 编译并运行查看结果: view source print? 1 Exception in thread "main" java.lang.NullPointerException 2 at Test.main(Test.java:18) 我们发现竟然报了错误,第 18 行出了空指针错误,也就是说 get 方法竟然没有拿到预期的巢穴对象。 在这里我们就要研究一下为什么取不到了。我们这里先解释一下 HashMap 的工作方式。 假设现在有个 6 张中奖彩票的存根,放在 5 个桶里(彩票首位只有 1-5,首位是 1 的就放在一号桶,是 2 的就放在 2 号桶,依次类推),现在你拿了 3 张彩票来兑奖,一个号码是 113,一个号码是 213,一个号 码是 313。那么现在先兑第一张,取出一号桶里的存根发现存根号码和你的号码不符,所以你第一张没中 奖。继续兑第二张,二号桶里就没存根所以就直接放弃了,把三号桶里的所有彩票存根都拿出来对应一番, 最后发现有一个存根恰好是 313,那么恭喜你中奖了。 HashMap 在确定一个键对象和另一个键对象是否是相同时用了同样的方法,每个桶就是一个键对象的 散列码值,桶里放的就是散列码相同的彩票存根,如果散列码不同,那么肯定没有相关元素存在,如果散 列码相同,那么还要用键的 equals()方法去比较是否相同,如果相同才认为是相同的键。简单的说就是 hashCode()相同 && equals()==true 时才算两者相同。 到了这里我们应该明白了,在没有重写一个对象的 hashcode()和 equals()方法之前,它们执行的是 Object 中对应的方法。而 Object 的 hashcode()是用对象在内存中存放的位置计算出来的,每个对象实例都不相同。 Object 的 equals()的实现更简单就是看两个对象是否==,也就是两个对象除非是同一个对象,否则根本不会 相同。因此上面的例子虽然都是名字叫碧雷的龙,但是 HashMap 中却无法认可它们是相同的。 因此我们只有重写 Key 对象的 hashCode()和 equals()方法,才能避免这种情形出现,好在 Eclipse 可以帮 我们自动生成一个类的 hashCode()和 equals(),我们把上面的例子加上这两个方法再试试看: view source print? 001 import java.util.HashMap; 002 import java.util.Map; 003 004 public class Test { 005 006 public static void main(String[] args) { 007 008 // 龙和它的巢穴映射表 009 Map map = new HashMap(); 010 011 // 在 Map 中放入四只克莱恩大陆上的龙 012 map.put(new Dragon("锐刃", 98), new Nest(98)); 013 map.put(new Dragon("明镜", 95), new Nest(95)); 014 map.put(new Dragon("碧雷", 176), new Nest(176)); 015 map.put(new Dragon("玛烈", 255), new Nest(255)); 016 017 // 查看宝藏 018 System.out.println("碧雷巢穴中有多少宝藏:" + map.get(new Dragon("碧雷", 176)).getTreasure()); 019 } 020 021 } 022 023 // 龙 024 class Dragon { 025 026 Dragon(String name, int level) { 027 this.level = level; 028 this.name = name; 029 } 030 031 // 龙的名字 032 private String name; 033 034 // 龙的级别 035 private int level; 036 037 public int getLevel() { 038 return level; 039 } 040 041 public void setLevel(int level) { 042 this.level = level; 043 } 044 045 public String getName() { 046 return name; 047 } 048 049 public void setName(String name) { 050 this.name = name; 051 } 052 053 @Override 054 public int hashCode() { 055 final int PRIME = 31; 056 int result = 1; 057 result = PRIME * result + level; 058 result = PRIME * result + ((name == null) ? 0 : name.hashCode()); 059 return result; 060 } 061 062 @Override 063 public boolean equals(Object obj) { 064 if (this == obj) 065 return true; 066 if (obj == null) 067 return false; 068 if (getClass() != obj.getClass()) 069 return false; 070 final Dragon other = (Dragon) obj; 071 if (level != other.level) 072 return false; 073 if (name == null) { 074 if (other.name != null) 075 return false; 076 } else if (!name.equals(other.name)) 077 return false; 078 return true; 079 } 080 081 } 082 083 // 巢穴 084 class Nest { 085 086 //我研究的龙之常数 087 final int DRAGON_M = 4162; 088 089 // 宝藏 090 private int treasure; 091 092 // 居住的龙的级别 093 private int level; 094 095 Nest(int level) { 096 this.level = level; 097 this.treasure = level * level * DRAGON_M; 098 } 099 100 int getTreasure() { 101 return treasure; 102 } 103 104 public int getLevel() { 105 return level; 106 } 107 108 public void setLevel(int level) { 109 this.level = level; 110 this.treasure = level * level * DRAGON_M; 111 } 112 113 } 编译并运行查看结果: view source print? 1 碧雷巢穴中有多少宝藏:128922112 这一次正常输出了,真不容易^_^ 好了本讲就到这里。 Java 基础的东西先告一段落,从明天开始继续 Android 之旅。 Java 基础第十七讲:异常处理(一) 22 Dec 本讲内容:异常 软件开发中有 80%的工作是用来检查和处理错误,而检查并处理错误很多时候是一件枯燥无趣的事情, 如果在语言级别提供一些帮助的话,会减轻一些程序员的负担。 而 Java 提供了一套比较优秀的异常处理机制: 1、使开发人员不必编写特殊代码来测试返回值就能发现问题, 2、在语法结构就把正常的代码和异常处理的代码清晰的分开来, 3、允许我们使用相同的异常处理代码来处理一定范围内的所有异常。 以期产生一种高效的、有组织的异常处理方式。 1、异常及异常的分类 异常是指在程序中出现的异常状况,在 Java 中异常被抽象成一个叫做 Throwable 的类。 其中如果程序出错并不是由程序本身引起的,而是硬件等其他原因引起的,我们称之为 Error,一般情 况下 Error 一旦产生,对程序来说都是致命的错误,程序本身无能为力,所以我们可以不对 Error 作出任何 处理和响应。 异常如果是由程序引起的我们称之为 Exception,Exception 又分两种,我们把运行时才会出现的异常叫 做 RuntimeException,RuntimeException 我们不好在程序编写阶段加以事先处理,而其他异常则可以在程序 编写和编译阶段加以事先检查和处理,我们把这种异常叫做检验异常。 程序只需要捕捉和处理检验异常。 相应的我们把除检验异常(可检查异常)之外的异常称之为非检验异常,包括 Error 和 RuntimeException , 非检验异常可以捕捉也可以不捕捉,更多的时候我们不捕捉,因为捕捉了我们也没办法处理,譬如程序运 行时发生了一个 VirtualMachineError 异常,虚拟机都出错了,作为运行在虚拟机内的程序又有什么办法处 理呢? 下面我们用图来表示一下上面讲述的内容: 2、异常的处理 try ^ catch ^ finally 异常相关的处理语法可以用下图概括一下: 异常处理的几条规则: 1. try 用于定义可能发生异常的代码段,这个代码块被称为监视区域,所有可能出现检验异常的代码 写在这里。 2. catch 代码段紧跟在 try 代码段后面,中间不能有任何其他代码。 3. try 后面可以没 catch 代码段,这实际上是放弃了捕捉异常,把异常捕捉的任务交给调用栈的上一 层代码。 4. try 后面可以有一个或者多个 catch 代码段,如果有多个 catch 代码段那么程序只会进入其中某一 个 catch。 5. catch 捕捉的多个异常之间有继承关系的话,要先捕捉子类后捕捉父类。 6. finally 代码段可以要也可以不要。 7. 如果 try 代码段没有产生异常,那么 finally 代码段会被立即执行,如果产生了异常,那么 finally 代码段会在 catch 代码段执行完成后立即执行。 8. 可以只有 try 和 finally 没有 catch。 下面提供一个使用异常的例子,请注意看相关注释: view source print? 001 import java.io.BufferedReader; 002 import java.io.BufferedWriter; 003 import java.io.FileNotFoundException; 004 import java.io.FileReader; 005 import java.io.FileWriter; 006 import java.io.IOException; 007 import java.util.ArrayList; 008 import java.util.List; 009 010 public class Bank02 { 011 012 //用 ArrayList 存储储户信息 013 List list = new ArrayList(); 014 015 //用初始化块准备一些测试数据 016 { 017 String[] account = { "10001", "aya", "111111", "60000" }; 018 list.add(account); 019 account = new String[] { "10002", "bean", "222222", "40000" }; 020 list.add(account); 021 } 022 023 //用 BufferedWriter 和 BufferedReader 读写文件 024 BufferedWriter bw; 025 BufferedReader br; 026 027 // 字符串数组转字符串,试图写一个通用方法 028 public static String arrayToString(String[] input, String space) { 029 String output = ""; 030 for (int i = 0; i < input.length; i++) { 031 output += input[i] + space; 032 } 033 return output.substring(0, output.length() - 1); 034 } 035 036 // 写文件 037 public void writeFile() { 038 try { 039 //try 代码块又称之为"监视区域",表示有可能出问题闹异常的代码写在这里 040 bw = new BufferedWriter(new FileWriter("bank.txt")); 041 for (String[] account : list) { 042 bw.write(arrayToString(account, " ")); 043 bw.newLine(); 044 } 045 bw.flush(); 046 } catch (IOException e) { 047 //可以使用一个 catch 来捕捉异常 048 //doSomething 049 050 //打印出异常的堆栈踪迹 051 e.printStackTrace(); 052 } finally { 053 //finally 提供了一个在正常时和异常时都需要执行的代码段,在这里可以进行一些资源的释放工 作,数据的清理工作 054 try { 055 bw.close(); 056 } catch (IOException e) { 057 e.printStackTrace(); 058 } 059 } 060 061 } 062 063 // 读文件 064 public void readFile() { 065 try { 066 //try 代码块又称之为"监视区域",表示有可能出问题闹异常的代码写在这里 067 br = new BufferedReader(new FileReader("bank.txt")); 068 String temp = ""; 069 list.clear(); 070 while ((temp = br.readLine()) != null) { 071 list.add(temp.split("")); 072 } 073 } catch (FileNotFoundException e) { 074 //可以使用一个 catch 来捕捉异常 075 //doSomething 076 077 //打印出异常的堆栈踪迹 078 e.printStackTrace(); 079 } catch (IOException e) { 080 //也可以用一组 catch 来捕捉多个异常,如果多个异常之间有继承关系,那么父类异常写下面 081 e.printStackTrace(); 082 }finally{ 083 //finally 提供了一个在正常时和异常时都需要执行的代码段,在这里可以进行一些资源的释放工 作,数据的清理工作 084 try { 085 br.close(); 086 } catch (IOException e) { 087 e.printStackTrace(); 088 } 089 } 090 } 091 092 public static void main(String[] args) { 093 //创建一个 bank 对象 094 Bank02 bank = new Bank02(); 095 //写入数据 096 bank.writeFile(); 097 //读取数据 098 bank.readFile(); 099 } 100 101 } 3、常见异常 ArrayIndexOfBoundsException 数组下标越界异常 ClassCastException 强制转换类失败异常 IllegalArgumentException 方法参数类型传入异常 IllegalStateException 非法的设备状态异常 NullPointException 传说中的空指针异常,如果一个对象不存在,你 有对这个对象使用点操作,那么就会出现该异常 NumberFormatException 把字符串转成数字失败时出现的数字格式异常 AssertionError 断言错误 ExceptionInInitializerError 试图初始化静态变量或者静态初始化块时抛出 StackOverflowError 栈溢出错误 NoClassDefFoundError 找不到指定的类错误 好了本讲就到这里,下节课我们讲自定义异常和调用栈。 Java 基础第十八讲:异常处理(二) 23 Dec 本讲内容预报:使用电影《盗梦空间》的情节,来讲述方法的调用栈、自定义异常、异常的抛出和传 播,看本讲的朋友可以多看两遍盗梦空间再来看本讲内容。 具体内容有空的时候再写哈^_^ 下面是一个备用图: Java 基础第十九讲:输入输出(一) 24 Dec 本讲内容: File、InputStream、OutputStream、Reader、Writer Java 基础第二十讲:输入输出(二) 25 Dec 本讲内容:示例《圣诞节的表白》 有一个害羞的男孩决定在圣诞节的这天向她表白……,因为奇怪的条件限制只能把表白的内容写在一台 公共的计算机上。为了只让她看到,而不会被其他人发现,男孩用他刚学习的输入输出知识写了一些代码, 他建立了一个文件,在文件里打印了一些内容,建立了 1 亿个文件夹,把自己的文件藏在混淆目录中,待 女孩子看完之后,又把这些代码和文件夹都删除了: view source print? 001 import java.io.BufferedReader; 002 import java.io.File; 003 import java.io.FileNotFoundException; 004 import java.io.FileReader; 005 import java.io.FileWriter; 006 import java.io.IOException; 007 import java.io.InputStreamReader; 008 009 public class HappyChristmas { 010 011 // 创建多层文件夹 012 public static boolean createDirectory(File parent, int level) { 013 014 File[] fa = new File[10]; 015 for (int i = 0; i < level; i++) { 016 for (int j = 0; j < 10; j++) { 017 fa[j] = new File(parent, "" + j); 018 fa[j].mkdir(); 019 createDirectory(fa[j], level - (i + 1)); 020 } 021 } 022 023 return true; 024 } 025 026 // 删除文件夹 027 public static boolean deleteDirectory(File file) { 028 if (file.delete()) { 029 return true; 030 } else { 031 File[] files = file.listFiles(); 032 for (int i = 0; i < files.length; i++) { 033 if (files[i].isFile()) { 034 files[i].delete() ; 035 } else { 036 deleteDirectory(files[i]); 037 } 038 } 039 if (file.delete()) { 040 return true; 041 } 042 } 043 return true; 044 045 } 046 047 public static void main(String[] args) { 048 049 // 创建一个目录 c:\happy 050 051 File file1 = new File("c:\\happy"); 052 053 if (file1.mkdir()) { 054 System.out.println("c:\\happy" + "目录创建成功"); 055 } else { 056 System.out.println("c:\\happy" + "目录创建失败"); 057 } 058 059 // 创建一个文件 c:\java\happyChristmas.txt 060 File file2 = new File("c:\\happy\\happyChristmas.txt"); 061 try { 062 if (file2.createNewFile()) { 063 System.out.println("c:\\happy\\happyChristmas.txt" + "文件创建成功"); 064 } else { 065 System.out.println("c:\\happy\\happyChristmas.txt" + "文件创建失败"); 066 } 067 } catch (IOException e) { 068 e.printStackTrace(); 069 } 070 071 // 创建一个多级目录 c:\java\2\0\1\0\1\2\2\4 072 073 File file3 = new File("c:\\happy\\2\\0\\1\\0\\1\\2\\2\\4"); 074 075 if (file3.mkdirs()) { 076 System.out 077 .println("c:\\happy\\2\\0\\1\\0\\1\\2\\2\\4" + "多级目录创建成功"); 078 } else { 079 System.out 080 .println("c:\\happy\\2\\0\\1\\0\\1\\2\\2\\4" + "多级目录创建失败"); 081 } 082 083 // 移动 happyChristmas.txt 到 c:\java\2\0\1\0\1\2\2\4 084 File file4 = new File( 085 "c:\\happy\\2\\0\\1\\0\\1\\2\\2\\4\\happyChristmas.txt"); 086 087 if (file2.renameTo(file4)) { 088 System.out 089 .println("c:\\happy\\2\\0\\1\\0\\1\\2\\2\\4\\happyChristmas.txt" 090 + "文件拷贝成功"); 091 } else { 092 System.out 093 .println("c:\\happy\\2\\0\\1\\0\\1\\2\\2\\4\\happyChristmas.txt" 094 + "文件拷贝失败"); 095 } 096 097 //从控制台输入内容存储到 happyChristmas.txt 中 098 BufferedReader in = new BufferedReader(new InputStreamReader(System.in)); 099 System.out.println("请输入内容:\n"); 100 try { 101 FileWriter writer = new FileWriter(file4.toString()); 102 103 while(!(in.readLine().equalsIgnoreCase("exit"))){ 104 String happyString = in.readLine(); 105 System.out.print("您输入的内容是: \""+happyString+"\",正在存储中\n"); 106 writer.write(happyString+System.getProperty("line.separator")); 107 } 108 writer.flush(); 109 writer.close(); 110 111 } catch (IOException e) { 112 e.printStackTrace(); 113 } 114 115 //从文件中读取 116 try { 117 FileReader fr = new FileReader(file4.toString()); 118 BufferedReader br = new BufferedReader(fr); 119 String ls = br.readLine(); 120 121 System.out.println("读取中,文件的内容如下:"); 122 123 while(ls!=null){ 124 System.out.println(ls); 125 ls = br.readLine(); 126 } 127 128 } catch (FileNotFoundException e) { 129 e.printStackTrace(); 130 } catch (IOException e) { 131 e.printStackTrace(); 132 } 133 134 // 创建 8 层混淆目录 135 if (createDirectory(file1, 8)) { 136 System.out.println("创建混淆目录成功"); 137 } else { 138 System.out.println("创建混淆目录失败"); 139 } 140 141 // 删除文件 c:\happy\2\0\1\0\1\2\2\4\happyChristmas.txt 142 if (file4.delete()) { 143 System.out 144 .println("c:\\happy\\2\\0\\1\\0\\1\\2\\2\\4\\happyChristmas.txt" 145 + "删除文件成功"); 146 } else { 147 System.out 148 .println("c:\\happy\\2\\0\\1\\0\\1\\2\\2\\4\\happyChristmas.txt" 149 + "删除文件失败"); 150 } 151 152 // 删除 c:\happy 目录 153 if (deleteDirectory(file1)) { 154 System.out.println(file1.toString() + "文件夹删除成功"); 155 } else { 156 System.out.println(file1.toString() + "文件夹删除失败"); 157 } 158 159 } 160 161 } 这里爆个小料,男孩子在文件里的内容我悄悄的看了一下,大致是这样的:“我想了很久,终于鼓起勇 气向你写这封信,你…… 你欠我的钱该还了吧!” …… 随后是欠债清单:“ 5 岁时,借了一块钱买了 2 只棒 棒糖,自己吃了一只,另外一只竟然给了另一个小男孩,6 岁时……,12 岁时…… 等等 ” 按照好莱坞式的结局…… 女孩子看完信之后,心里一片感动,神情的拉着男孩子的手说:我愿意拿一生来还债^_^,随后他们一 起过着幸福而快乐的生活。 第二十一讲:网络编程(一) 05 Jan 本讲内容: socket 只会装傻的”智能“机器人 套接字(socket)是代表计算机之间网络连接的对象。要进行计算机间的网络通讯,建立 TCP 连接, 那么就需要 Socket 编程,Java 提供了 ServerSocket 类和 Socket 类,可以利用他们很方便的实现,计算机间 的网络通讯。 因为 Java 把数据的传输已经抽象成 InputStream 和 OutputStream 的输入输出流,那么对网络通讯来说, 其实就是利用 ServerSocket 类和 Socket 类建立起来网络连接,进行字节流或者字符流的输入输出操作。 下面我们还是通过一个例子来学习 Socket 编程:只会装傻的”智能“机器人 直接上代码,服务器端 TalkServer.java : view source print? 01 import java.io.BufferedReader; 02 import java.io.IOException; 03 import java.io.InputStreamReader; 04 import java.io.PrintWriter; 05 import java.net.ServerSocket; 06 import java.net.Socket; 07 08 public class TalkServer { 09 10 public static void main(String[] args) throws IOException { 11 12 // 创建一个 ServerSocket 13 ServerSocket server = new ServerSocket(8080); 14 15 // 准备接受客户连接,如果有客户连接到,则创建一个 socket 对象,如果没有客户连接到,那么该 程序会停在本行一直等待,像是在睡眠或者假死,这种行为叫做“阻塞”,阻塞是网络通信中的术语,就是 你等别人响应,如果必须等到,等不到就一直等,就是阻塞,等不到也继续向前走的当然就是非阻塞。 16 Socket socket = server.accept(); 17 18 // 从 socket 中获取一个输入对象,以便接受从网络上来的数据 19 BufferedReader socketInput = new BufferedReader(new InputStreamReader(socket.getInputStream())); 20 21 // 从标准输入中获取一个输入对象,以便接受从键盘上传来的数据,如果想让服务器端和客户端自 由对话的话可以用此对象 22 //BufferedReader keyboardInput = new BufferedReader(new InputStreamReader(System.in)); 23 24 // 从 socket 中获取一个 25 PrintWriter socketOutput = new PrintWriter(socket.getOutputStream(),true); 26 27 // 定义一些变 量 28 String input; 29 30 // 一直执行,直到客户端表示要断开 31 while(true){ 32 //从客户端获取用户输入的字符串 33 input = socketInput.readLine(); 34 //发送到屏幕 35 System.out.print("客户:"); 36 System.out.println(input); 37 38 //准备好回复语句 39 String reply= reply(input); 40 //发送到客户端 41 socketOutput.println(reply); 42 //发送到屏幕 43 System.out.print("小瑶瑶:"); 44 System.out.println(reply); 45 46 //客户如果输入的是再见则退出循环 47 if(input.equals("再见")){ 48 break; 49 } 50 } 51 52 //关闭释放资源 53 socketInput.close(); 54 socketOutput.close(); 55 socket.close(); 56 57 } 58 59 //回复方法 60 private static String reply(String input) { 61 62 String output=replys[number%7]; 63 64 if(number%7==5){ 65 output = output+" "+input; 66 } 67 68 number++; 69 return output; 70 } 71 72 //回复方法用到的一些变量 73 static int number =0; 74 75 static String[] replys = new String[7]; 76 77 static{ 78 replys[0]= "你说的啥,我听不见?"; 79 replys[1]= "声音再大点行不?"; 80 replys[2]= "声音再大点一点点行不?"; 81 replys[3]= "还是听不清……"; 82 replys[4]= "看来是网络的问题,你要不说慢点?"; 83 replys[5]= "听到了!听到了!你说的是:"; 84 replys[6]= "完了,又听不清了……"; 85 } 86 87 } 88 客户端代码 TalkClient.java : view source print? 01 import java.io.BufferedReader; 02 import java.io.IOException; 03 import java.io.InputStreamReader; 04 import java.io.PrintWriter; 05 import java.net.Socket; 06 import java.net.UnknownHostException; 07 08 public class TalkClient { 09 10 public static void main(String[] args) throws UnknownHostException, IOException { 11 12 Socket socket = new Socket("127.0.0.1",8080); 13 14 // 从 socket 中获取一个输入对象,以便接受从网络上来的数据 15 BufferedReader socketInput = new BufferedReader(new InputStreamReader(socket.getInputStream())); 16 17 // 从标准输入中获取一个输入对象,以便接受从键盘上传来的数据 18 BufferedReader keyboardInput = new BufferedReader(new InputStreamReader(System.in)); 19 20 // 从 socket 中获取一个 21 PrintWriter socketOutput = new PrintWriter(socket.getOutputStream(),true); 22 23 // 定义一些变 量 24 String input; 25 26 // 一直执行 27 while(true){ 28 29 //从键盘获取数据,通过 socket 对象发送出去 30 input = keyboardInput.readLine(); 31 socketOutput.println(input); 32 33 //接收从 socket 中获取的服务器端发回的回应信息,并发送到屏幕上 34 input = socketInput.readLine(); 35 System.out.println(input); 36 } 37 38 } 39 40 } 编译两个文件,先运行 TalkServer,开始接收客户端的连接,然后运行客户端,在客户端上打字,服务器端 会有回应,运行截图如下,这里上一个服务器端的截图: 好了,本将就到这里。 待续 http://android.yaohuiji.com/about(转)
还剩148页未读

继续阅读

下载pdf到电脑,查找使用更方便

pdf的实际排版效果,会与网站的显示效果略有不同!!

需要 10 金币 [ 分享pdf获得金币 ] 0 人已下载

下载pdf

pdf贡献者

er74

贡献于2015-07-26

下载需要 10 金币 [金币充值 ]
亲,您也可以通过 分享原创pdf 来获得金币奖励!