《JAVA语言程序设计》上课笔记

教学目标:

1.使学生了解JAVA课程的性质、定位、作用;为什么要学习JAVA?让学生知道如何学好JAVA;

教学内容:

一、       问几个问题

1、            你们到这里来干什么的?

来学习JAVA程序设计

      为什么要来学习JAVA呢?

              找个好工作,拿到高薪水

2、            怎么样才能达到你们的目标呢?

有的同学说学好JAVA就可以了,但是如何才能学好呢?

学好JAVA的几个关键

1、            认认真真上课

2、            按时按质量完成作业

3、            多动手练习

4、            多动脑思考

5、            多问多讨论

在课堂上学习什么?

1、            系统学习JAVA有关的知识

2、            全真企业项目实训

3、            更为重要的是:好的学习方法和学习技能的训练,好的编程思维和编程思想的训练(决定你发展的高度)

4、            超级重要的提示:软件开发是一个特别讲究实力的行业,你技术好、开发实力强,好工作和高新自然不是问题。

所以,我们的任务就是:传授最实用的开发技术给大家,并实实在在培养大家的开发实力,从而真正的解决就业问题。

部分学习方法:

认识事物的一般过程是怎么样的

1、            是什么à有什么—>能干什么à怎么做à为什么这么做

2、            从粗到精 ,分解的概念,从大处着手,再到细节,比如看书先看目录再看内容

如何又快又好的掌握某种知识

   类比法,当你学第一点的时候没有比较,学第二点的时候就可以和第一点类比,相同点就不用学了,相似的找出不同点,完全不同的就重新来,新知识更新太快,每半年到一年更新一次,采用正确的类比方法非常有利于我们的学习,基础一定要打扎实

   预测法:拿到一个新知识,不要急着去看,前提是你要会一些东西,从已经会的着手进行分析,这个是需要持续去训练的

如何深入理解和掌握概念:语句分析法,通过后面的概念来示范。

几个重要嘱咐:一定要投入,要充分发挥主观能动性(我们不是师生,是战友,目标一致对外,即公司老板,打倒他,让她认可你,需要多发挥主观能动性,不是糊弄老师;一定要听老师的话,你们现在都没有入门,入门的衡量是你是否具备了JAVA的编程思想,能够用java的思考方式去分析解决问题才叫入门,不是说你会写几个程序你就入门了,以中国人学习英语为例,不是说你会了几个单词你就会英语了,你不具备英语的思考能力,你不会用他的思维去思考,你用汉语的方式思考用英语表达就是中国式的英语,JAVA也是一样,你必须要用JAVA的思维方式去思考,java有它自己的特点,有它自己解决问题的套路,你不用它的思维方式来思考,根本就不算入门,就不叫会Java,一定要照老师的方法去练,按照自己的方式去练,效果肯定好不了,练的越多就离java越远;一定要理论和实践并重,很多同学都说直接教我们写程序吧,不是说整代码最实在,代码怎么来的,手敲出来的,脑子想的才能敲出来,头脑解决手,要先有思维,先有解决方法,然后再敲代码,理论指导实践,实践是用来加深对理论的体会,没有理论绝对发展不高;一定要重视构建自己的知识结构体系,学完的东西要往脑袋里装,不是说听完就忘,也不是体现在笔记本上。基础差不是借口,都是从零开始,大家水平是一样的,关键是看你想不想学,横下一条心,想学就一定要学好。能否学好就两个原因:1、理解能力,即吸收能力,说白了就是上课的质量,就是老师讲的你能不能吸收消化,勤能补拙2、要持续努力

几个不建议:

不建议光听不练,自己不练,没法消化

不建议光练不想,这个行业不可能照搬,要举一反三

不建议盲目上手,手比脑先动

不建议钻牛角尖,不要在某个细节里

不建议依赖视频或拷贝

不建议忽视基础

上课建议:

跟思路,跳过暂时不明了的某个细节,听课的时候有不明白的地方怎么办,跳过,而不要在这个地方死钻,要把握上课整体思路,可以先记下来

积极跟着老师去思考

积极回答老师的问题,形成互动,一个是上课的气氛,二是可以帮助对知识的理解,我会知道你理解到什么程度,这是现场把握

多动手记笔记

有问题的时候可以随时举手示意

刚才讲的感觉比较空洞,但是在后面你会体会到。

课程概览:

学习JAVA开发初级部分的知识

第一章:JAVA入门

第二章:JAVA基础语法

第三章:java类和对象

第四章:java高级类特性

第五章:数组和枚举

第六章:常见类的使用

第七章:抽象类和接口

第八章:异常和断言

最后通过一个小项目,跟实际有用的或者相关的,来巩固所学知识

第一章           JAVA入门

教学目标:

理解Java 是什么

了解Java 能干什么

理解Java 有什么

了解Java 的历史、现状和特点

1.1java是什么?

   通常我们认为,Java 是:

一种计算机编程语言

一种软件开发平台

一种软件运行平台

一种软件部署环境

什么意思呢,分别解释一下。

1Java是一种计算机编程语言

1.1:语言

要准确地给语言下一个定义很困难,对我们来说也没有必要,但是大家都知道汉语、英

语等是语言。语言是用来干什么的呢?很明显语言是用来交流的,比如大家现在看这些文字,

其实就是我们通过这些文字在交流。

1.2:计算机编程

所谓计算机编程就是:把我们的要求和想法,按照能够让计算机看懂的规则和约定,编

写出来的过程,就是编程。编程的结果就是一些计算机能够看懂并能够执行和处理的东西,

我们把它叫做软件或者程序。事实上,程序就是我们对计算机发出的命令集(指令集)。

1.3Java是一种计算机编程语言

我们说Java 是一种计算机编程语言,首先是说:Java 是一种语言,也就是Java 是用来

交流的,那么用来谁和谁交流呢?很明显就是人和计算机交流了,换句话说把我们的要求和

想法用Java 表达出来,那么计算机能看懂,就能够按照我们要求运行,而这个过程就是我

们说的使用Java 编程,所以我们讲Java 是一种计算机编程语言。

为了让计算机看懂,Java 会有一系列的规则和约定,这些就是Java 的语法。

2Java是一种软件开发平台

2.1:什么是软件开发

可以简单地理解为:编程的结果是软件或者程序,而编程的过程就是软件开发。软件开

发的基本步骤包括:需求分析、概要设计、详细设计、编码、测试、维护等阶段。

2.2:什么是开发平台

在软件开发的过程中,我们需要很多的工具来辅助我们的工作,不可能什么都从头自己

做。我们把编程的环境和相应的辅助工具统称为开发环境,开发平台就是用来提供这个开发

环境的。

2.3Java是一种开发平台

Java 不单纯是一个编程的语言,它自身提供了一系列开发Java 所需要的环境和工具,

来进行编译、解释、文档生成、打包等,比如:javac.exe、java.exe 等等,这些我们后面会

讲到,现在不明白也没有关系。所以我们讲Java是一个开发平台

PDF 文件使用 "pdfFactoryPro" 试用版本创建www.fineprint.cn

Java私塾跟我学系列——JAVA 篇网址:http://www.javass.cn 电话:010-68434236

3Java是一种软件运行平台

3.1:什么是软件的运行平台

如同我们需要阳光、空气、水和食物才能正常存活一样,软件最终要能够运行,也需要

一系列的外部环境,来为软件的运行提供支持,而提供这些支持的就是运行平台。

3.2Java是一种运行平台

Java 本身提供Java 软件所需要的运行环境,Java 应用可运行在安装了JRE(Java

Runtime Environment)的机器上,所以我们说Java是一个运行平台。

JRE:Java RuntimeEnvironment,Java运行环境。

4Java是一种软件部署环境

4.1:什么是软件的部署

简单地讲,部署就是安装,就是把软件放置到相应的地方,并且进行相应的配置(一般

称作部署描述),让软件能够正常运行起来。

4.2Java是一种软件部署环境

Java 本身是一个开发的平台,开发后的Java 程序也是运行在Java 平台上的。也就是说,

开发后的Java 程序也是部署在Java 平台上的,这个尤其在后面学习JEE(Java 的企业版)

的时候,体现更为明显。

二:Java 能干什么

Java 能做的事情很多,涉及到编程领域的各个方面。

1:桌面级应用:尤其是需要跨平台的桌面级应用程序。

先解释一下桌面级应用:简单的说就是主要功能都在我们本机上运行的程序,比如

word、excel等运行在本机上的应用就属于桌面应用。

2:企业级应用

先解释一下企业级应用:简单的说就是大规模的应用,一般使用人数较多,数据量较大,

对系统的稳定性、安全性、可扩展性和可装配性等都有比较高的要求。

这是目前Java 应用最广泛的一个领域,几乎一枝独秀。包括各种行业应用,企业信息

化,也包括电子政务等,领域涉及:办公自动化OA,客户关系管理CRM,人力资源HR,

企业资源计划ERP、知识管理KM、供应链管理SCM、企业设备管理系统EAM、产品生命

周期管理PLM、面向服务体系架构SOA、商业智能BI、项目管理PM、营销管理、流程管

理WorkFlow、财务管理……等等几乎所有你能想到的应用。

3:嵌入式设备及消费类电子产品

包括无线手持设备、智能卡、通信终端、医疗设备、信息家电(如数字电视、机顶盒、

电冰箱)、汽车电子设备等都是近年以来热门的Java应用领域,尤其是手机上的Java 应用

程序和Java 游戏,更是普及。

4:除了上面提到的,Java还有很多功能:如进行数学运算、显示图形界面、进行网络操作、

进行数据库操作、进行文件的操作等等。

PDF 文件使用 "pdfFactoryPro" 试用版本创建www.fineprint.cn

Java私塾跟我学系列——JAVA 篇网址:http://www.javass.cn 电话:010-68434236

三:Java 有什么

Java 体系比较庞杂,功能繁多,这也导致很多人在自学Java 的时候总是感觉无法建立

全面的知识体系,无法从整体上把握Java的原因。在这里我们先简单了解一下Java 的版本。

具体的Java 体系知识结构,将在后面详细讲述。

Java 分成三种版本,分别是Java 标准版(JSE)、Java 微缩版(JME)和Java企业版(JEE),

每一种版本都有自己的功能和应用方向。

1Java标准版: JSE(Java Standard Edition

JSE(Java Standard Edition)是Sun 公司针对桌面开发以及低端商务计算解决方案而开

发的版本,例如:我们平常熟悉的Application 桌面应用程序。这个版本是个基础,它也是

我们平常开发和使用最多的技术,Java 的主要的技术将在这个版本中体现。本书主要讲的

就是JSE。

2Java微缩版:JME(Java Micro Edition

JME(Java ,Micro Edition) 是对标准版JSE 进行功能缩减后的版本,于1999 年6月

由SunMicrosystems 第一次推向Java 团体。它是一项能更好满足Java开发人员不同需求

的广泛倡议的一部分。Sun Microsystems 将JME 定义为“一种以广泛的消费性产品为目标

的高度优化的Java 运行时环境,包括寻呼机、移动电话、可视电话、数字机顶盒和汽车导

航系统。”

JME 是致力于消费产品和嵌入式设备的开发人员的最佳选择。尽管早期人们对它看好而

且Java开发人员团体中的热衷人士也不少,然而,JME 最近才开始从其影响更大的同属产

品JEE 和JSE 的阴影中走出其不成熟期。

JME 在开发面向内存有限的移动终端(例如寻呼机、移动电话)的应用时,显得尤其实用。

因为它是建立在操作系统之上的,使得应用的开发无须考虑太多特殊的硬件配置类型或操作

系统。因此,开发商也无须为不同的终端建立特殊的应用,制造商也只需要简单地使它们的

操作平台可以支持JME 便可。

3Java企业版:JEE(Java Enterprise Edition

JEE(Java Enterprise Edition)是一种利用Java 平台来简化企业解决方案的开发、部

署和管理相关的复杂问题的体系结构。JEE技术的基础就是核心Java平台或Java平台的标

准版,JEE 不仅巩固了标准版中的许多优点,例如“编写一次、随处运行”的特性、方便存

取数据库的JDBC API、CORBA 技术以及能够在Internet 应用中保护数据的安全模式等等,

同时还提供了对EJB(Enterprise Java Beans)、JavaServlets API、JSP(Java ServerPages)

以及XML 技术的全面支持。其最终目的就是成为一个能够使企业开发者大幅缩短投放市场时

间的体系结构。

JEE 体系结构提供中间层集成框架来满足无需太多费用而又需要高可用性、高可靠性以

及可扩展性的应用的需求。通过提供统一的开发平台,JEE 降低了开发多层应用的费用和复

杂性,同时提供对现有应用程序集成强有力支持,完全支持Enterprise Java Beans,有良

好的向导支持打包和部署应用,添加了目录支持,增强了安全机制,提高了性能。

JEE 是对标准版进行功能扩展,提供一系列功能,用来解决进行企业应用开发中所面临

的复杂的问题。具体的我们会放到后面JEE 的课程去讲。

4:三个版本之间的关系

JEE 几乎完全包含JSE 的功能,然后在JSE 的基础上添加了很多新的功能。

JME主要是JSE的功能子集,然后再加上一部分额外添加的功能。

四:闲话Java

1Java历史

在上世纪90 年代初,sun 公司有一个叫做Green 的项目,目的是为家用消费电子产品

开发一个分布式代码系统,这样就可以对家用电器进行控制,和它们进行信息交流。詹姆

斯·高斯林(James Gosling)等人基于C++开发一种新的语言Oak(Java 的前身)。Oak是一

种用于网络的精巧而安全的语言。Sun 公司曾依此投标一个交互式电视项目,但结果是被SGI

打败,Sun 打算抛弃Oak。随___________着互联网的发展,Sun 看到了Oak 在计算机网络上的广阔应用

前景,于是改造Oak,在1995 年5 月以“Java”的名称正式发布,从此Java走上繁荣之路。

当然提到Java 历史,不得不提的一个故事就是Java 的命名。开始“Oak”的命名是以

项目小组办公室外的树而得名,但是Oak 商标被其他公司注册了,必须另外取一个名字,

传说有一天,几位Java 成员组的会员正在讨论给这个新的语言取什么名字,当时他们正在

咖啡馆喝着Java(爪哇)咖啡,有一个人灵机一动说就叫Java 怎样,得到了其他人的赞同,

于是,Java这个名字就这样传开了。当然对于传说,了解一下就好了,不必过于认真。

2Java大事记

作为学习Java 的人士,对Java历史上发生的大事件有一个了解是应该的。

JDKJava Software Develop Kit):Java 软件开发工具包。JDK 是Java的核心,包

括了Java运行环境,一系列Java 开发工具和Java基础的类库。目前主流的JDK 是Sun 公

司发布的JDK,除了Sun 之外,还有很多公司和组织都开发了自己的JDK,例如IBM公司开

发的JDK,BEA 公司的Jrocket,还有GNU 组织开发的JDK 等等。

时间 事件

1995 年5 月23 日Java 语言诞生

1996 年1 月第一个JDK—JDK1.0诞生

1997 年2 月18 日JDK1.1发布

1998 年12月8 日Java2企业平台J2EE发布

1999 年6 月Sun发布Java三个版本:标准版J2SE,企业

版J2EE,微型版J2ME

2004 年9 月30 日JavaSE5.0发布

2006 年12 月JavaSE6.0发布

3Java特点

简单地说,Java 具有如下特点:简单的、面向对象、平台无关、多线程、分布式、安全、

高性能、可靠的、解释型、自动垃圾回收等特点。

这里只解释一下平台无关和分布式,其余的在后面会逐步接触到。

3.1:平台无关

所谓平台无关指的是:用Java 写的程序不用修改就可在不同的软硬件平台上运行。这

样就能实现同样的程序既可以在Windows 下运行,到了Unix 或者Linux 环境不用修改就直

接可以运行了。Java主要靠Java 虚拟机(JVM)实现平台无关性。

平台无关性就是一次编写,到处运行:Write Once, RunAnywhere

3.2:分布式

分布式指的是:软件由很多个可以独立执行的模块组成,这些模块被分布在多台计算机

上,可以同时运行,对外看起来还是一个整体。也就是说,分布式能够把多台计算机集合起

来就像一台计算机一样,从而提供更好的性能。

4Java标准组织——JCP

JCP(Java Community Process) 是一个开放的国际组织,成立于1995 年,主要职能

是发展和更新Java 技术规范、参考实现(RI)、技术兼容包(TCK)。Java 技术和JCP 两者

的原创者都是SUN 计算机公司。组织成员可以提交JSR(Java Specification Requests),

通过讨论、认可、审核以后,将进入到下一版本的规范里面。

也就是说 JCP 是目前Java 技术发展事实上的控制者和领导者。


七:构建JSE 开发环境

学习Java 开发的第一步就是构建开发环境,下面以JDK6.0在Windows XP上的安装配

置为例来讲述:

第一步:下载JDK

从SUN 网站下载JDK6或以上版本,这里以jdk-6u2-windows-i589-p版为例,

第二步:安装JDK

(1):双击jdk-6u2-windows-i589-p.exe 文件,出现安装界面如下图:

(2):然后出现下面的界面

(3):点击“接受”按钮,然后出现下列界面

(4):点击界面上的“更改”按钮,出现如下界面:

(5):在这个界面设置需要安装到的路径,可以安装到任何你想要安装的路径,如下图:

(6):然后点击确定,返回到上一个界面,如下图所示:

(7):然后点击下一步,会进行JDK 的安装,请耐心等待,直到出现JRE的安装,如下图:

(8):选择更改,然后在弹出的界面进行安装路径的设置,跟前面的方式一样,然后点击确

定回来,如下图所示:

(9):然后点击下一步,直到出现下面的界面,表示安装完成。

(10):自述文件可看可不看,不想看,把前面的勾点掉即可,然后点击完成按钮。

(11):安装完成过后,JDK 文件夹如下图:

D:\common\Java\jdk1.6.0_02:是JDK的安装路径

bin:binary的简写,下面存放的是Java 的各种可执行文件

db:JDK6 新加入的Apache 的Derby 数据库,支持JDBC4.0的规范。

include:需要引入的一些头文件,主要是c和c++的,JDK 本身是通过C和C++实现的。

jre:Java 运行环境。

lib:library 的简写,JDK 所需要的一些资源文件和资源包。

第三步:配置环境变量

安装完成后,还要进行Java 环境的配置,才能正常使用,步骤如下:

(1):在我的电脑点击右键——〉选择属性,如下图所示:

(2):在弹出界面上: 选择高级——〉环境变量,如下图所示:

(3):弹出如下界面,我们的设置就在这个界面上:

(4):在系统变量里面找到“Path”这一项,然后双击它,在弹出的界面上,在变量值

开头添加如下语句“D:\common\Java\jdk1.6.0_02\bin;”,注意不要忘了后面的分号,如下

图所示:

(5):然后点击编辑系统变量界面的确定按钮,然后点击环境变量界面的“新建”,弹

出界面如下图:

(6):在上面填写变量_________{名为:JAVA_HOME ,变量值为:D:\common\Java\jdk1.6.0_02,

如下图所示:

(7):然后点击新建系统变量界面的确定按钮,然后点击环境变量界面的“新建”,弹

出新建系统变量界面,在上面填写变量名为:classpath , 变量值为:.; ,注意是点和分

号,如下图所示:

(8):然后点击新建系统变量界面的确定按钮

(9):然后点击环境变量界面的确定按钮

(10):点击系统属性界面的确定按钮

到此设置就完成了。

(11):那么为何要设置这些环境变量呢,如何设置呢:

PATH:

提供给操作系统寻找到Java 命令工具的路径。通常是配置到JDK 安装路径\bin

JAVA_HOME:

提供给其它基于Java 的程序使用,让它们能够找到JDK 的位置。通常配置到JDK 安装

路径。注意:这个必须书写正确,全部大写,中间用下划线。

CLASSPATH:

提供程序在运行期寻找所需资源的路径,比如:类、文件、图片等等。

注意:在windows 操作系统上,最好在classpath的配置里面,始终在前面保持“.;”的配置,

在windows 里面“.”表示当前路径。

第四步:检测安装配置是否成功

进行完上面的步骤,基本的安装和配置就好了,怎么知道安装成功没有呢?

(1):点击开始——〉点击运行,在弹出的对话框中输入“cmd”,如下图示:

(2)然后点击确定,在弹出的dos窗口里面,输入“javac”,然后回车,出现如下界面则表

示安装配置成功。

好了,现在Java 的开发环境就配置好了,有些人已经跃跃欲试的想要马上开始学习如

何编写Java 程序了, 下面先来体会第一个Java 程序。

第一个java程序——HelloWorld

第一步:编写Java文件,文件名为HelloWorld.java,源代码如下:

  public class HelloWorld{

      publicstatic void main(String[] args){

        System.out.println(“你好,java!我来了!!!”)

         }

   }

第二步:编译Java源文件成为class文件

   在cmd窗口:javac HelloWorld.java

第三步:运行class文件

   在cmd窗口:java HelloWorld

第四步:查看控制台,看看输出什么

   在控制台(console),输出:你好,java!我来了!!!

Java是区分大小写的,所以要注意书写正确

要注意源代码的规范性

接下来,你们难道不觉得神奇吗?java可以按照我们的要求将结果输出出来

下面就让我们学习一下HelloWorld背后的到底是怎么运行起来的?

HelloWorld背后的故事

问大家一个问题:Java是如何让计算机理解、并且完成我们要做的事情呢?

当然我们并不深入到最细节

1、            编写代码

首先把我们想要计算机做的事情,通过Java表达出来,写成Java文件,这个过程就是编写代码的过程,就是编程,就是开发

2、            编译

写完java代码后,机器并不认识我们写的java代码,需要进行编译成字节码,编译后的文件叫做class文件(类文件)。编译后的类文件计算机同样也是不能识别的,那怎么办呢,那编译干吗呢,编译的实际目的是为了跨平台。面向的是虚拟机

3、            类装载ClassLoader

类装载的功能是为执行程序寻找和装载所需要的类。类文件如何才能运行,就需要ClassLoader将它找到,然后装载进来

4、            字节码校验

类文件装载过后,就进行字节码校验,对class文件的代码进行校验,保证代码的安全性

5、解释(Interpreter)或者JIT(Just In Time,Java即时编译)

可是机器也不能认识class文件,还需要被解释器进行解释,或者是通过即时编译成本机机器码,机器才能最终理解我们所要表达的东西。

6、运行

   最后由运行环境对代码进行运行,真正实现我们想要机器完成的工作。

现在我们来看下一个问题,Java的三大特性,刚才我们已经知道怎么去写、怎么去编译、怎么去跑程序,现在程序跑起来了吧,对java程序也有了个初步认识了,最后,大家知道,尤其刚开始学习一个东西,肯定是大吹特吹它的好处和优点,前面我已经说过了java有一个特点,跨平台性,技术上它还号称它有三大特性,第一个就是虚拟机,这个我们一定要很好的认识,java之所以能够跨平台,主要就靠虚拟机技术了。

Java技术三大特性——虚拟机

Java虚拟机JVM(java vitual machine)在java编程里面具有非常重要的地位,约相当于JRE,其实就是一个运行的平台。

是什么?

   Java虚拟机是在真实机器中用软件模拟实现的一种想象机器。为什么要有这样一个虚拟机呢,就涉及到了跨平台的问题,Java虚拟机规范为不同的硬件平台提供了一种编译Java技术代码的规范,该规范使Java软件独立于平台,因为编译是针对作为虚拟机的“一般机器”而做。

有什么?

JVM为下列各项做出了定义(仅限了解,简单知道就好)

---指令集(相当于CPU

---寄存器

---类文件格式

---

---垃圾收集堆

---存储区

能干什么?也就是虚拟机的功能:屏蔽底层软硬件结构的不同

(1):通过ClassLoader寻找和装载class文件

(2):解释字节码成为指令并执行,提供class文件的运行环境

(3):进行运行期间的垃圾回收,垃圾回收也是三大特性之一,过会儿再讲,也是虚拟机的功能

(4):提供与硬件交互的平台,软硬件的不同由虚拟机屏蔽掉了

虚拟机是java平台无关的保障

正是因为有虚拟机这个中间层,java才能够实现与平台无关。虚拟机就好比是一个java运行的基本平台,所有的java程序都运行在虚拟机上,所有与平台有关的东西都是由虚拟机去处理。

Java技术三大特性——垃圾回收

什么是垃圾?对于计算机而言,所有东西都是数据,在运行过程中,存在被分配了的内存块不再被需要的情况,那么这些内存块对程序来讲就是垃圾。

产生了垃圾,自然就需要清理这些垃圾,更为重要的是需要把这些垃圾所占用的内存资源,回收回来,加以再利用,从而节省资源,提高系统性能。程序资源耗得越少越好。

什么是垃圾回收?

对不再需要的已分配内存应取消分配,也就是释放内存,这个过程就是垃圾回收。

Java的垃圾回收

在其他语言中,取消分配是程序员的责任:在java编程语言中提供了一种系统级线程以跟踪内存分配,从而可以自动检查和释放不需要的内存,这是java一个很大的改进。这是一个自动的过程,不需要人工干预。你想干预也干预不了。这个功能在虚拟机里。那么到底怎么做的呢?不会告诉你的,这是虚拟机的核心技术之一,它是不开源的。先讲一个概念:

什么是内存泄露?

内存泄露就是程序运行期间,所占用的内存一直往上涨,很容易造成系统资源耗尽而降低性能或崩溃。为什么会一直往上涨呢?

几点提示:

1、            在java里面,垃圾回收是一个自动的系统行为,程序员不能控制垃圾回收的功能和行为。比如垃圾回收什么时候开始,什么时候结束,还有到底哪些资源需要回收等,都是程序员不能控制的。线程优先级问题导致。垃圾回收的级别低,就无法进行。我们只需看看,不需要知道怎么做的。

2、            有一些跟垃圾回收相关的方法,比如:System.gc(),记住一点,调用这些方法,仅仅是在通知垃圾回收程序,至于垃圾回收程序运不运行,什么时候运行,都是无法控制的。

3、            程序员可以通过设置对象为null(后面会讲到)来标识某个对象不再被需要了,这只是标示这个对象可以被回收了,并不是马上被回收。

Java技术三大特性——代码安全

Java如何保证编写的代码是安全可靠的呢?

编写代码——编译——运行

1、            第一关:编写的代码首先要被编译成class文件,如果代码写的有问题,编译期间就会发现,然后提示有编译错误,无法编译通过。

2、            第二关:通过编译关后,在类装载的时候,还会进行类装载检查,把本机上的类和网络资源类相分离,在调入类的时候进行检查,因而可以限制任何木马的应用。

3、            第三关:类装载后,在运行前,还会进行字节码校验,以判断你的程序是安全的。

4、            第四关:如果你的程序在网络上运行,还有沙箱(sand box)的保护,什么是沙箱呢?就是如果你的程序没有获得授权,只能在沙箱限定的范围内运行,是不能访问本地资源的,从而保证安全性。Applet是跑在浏览器上的,如果applet能访问你的本地资源,就不需要木马等了,想那什么就能拿到什么。万一你想突破沙箱,使用jarsigner,就可以了,签名就是说明你的程序是安全的,等到客户端看到这个签名了就可以,相当于免检产品一样。

第一章           小结提纲

第二章JAVA基础语法

尽量采用实例来结合讲解

为什么不讲别的,这就是学习方式。Java是一个编程语言,将我们想要做的事情让计算机表达出来,表达的时候不能乱表达,必需遵循一定的语法,不能乱写,就是编程的规则

什么是关键字?

关键字是由java语言保留地(只许java用,不许其他的用),java的开发和运行平台(jdk和jre)认识,并能正确处理的一些单词。关键字是单词,并不是所有的单词都是关键字。(语句分析法)

换句话说,关键字是在java语言和java的开发和运行平台之间的约定,只要按照这个约定使用了某个关键字,java的开发和运行平台就能够认识它,并正确的处理。

Java中有哪些关键字?以及关键字的基本含义?

书上有,瞟两眼就好,不用记,学到了自然就会,没学到看了也白搭。

提示几点:

1、            java的关键字也是随着新的版本发布在不断变动中的,不是一成不变的。基本没有被推出去的,加一个关键字代价很高,编译器要改,运行环境要改,语法规则也要改。要从最底层改到最高层。

2、            所有关键字都是小写的。

3、            Goto和const不是java编程语言中使用的关键字,但是是java的保留字,也就是说java保留了它们,但是没有使用它们。True和false不是关键字,而是boolean类型直接量

4、            表示类的关键字是class

什么是标识符?

在java语言中,标识符是赋予变量、类或方法的名称。透过标识符可以操作它背后所代表的类、方法等。

标识符命名规则?

1、            首字母只能以字母、下划线、$开头、其后可以跟字母、下划线、$和数字

2、            标识符区分大小写(事实上整个java编程里面都是区分大小写的)abc和Abc是两个不同的标识符。

3、            标识符不能是关键字

4、            标识符长度没有限制

标识符命名建议:

1、            如果标识符由多个单词构成,那么从第二个单词开始,首字母大写,示例:isText

2、            标识符尽量命名的有意义,让人能够望文知义

3、            尽量少用带$符号的标识符,主要是习惯问题,大家都不是很习惯使用带$符号的标识符:还有在某些特定的场合,$具有特殊的含义(在java内部类学习的时候)

4、            Java语言使用Unicode字符集包括:

(1)‘A’-‘Z’和‘a’——‘z’

(2)Unicode字符集中序号大于0xC0的所有字符

(3)Unicode字符集支持多种看起来相同的字母;

(4)建议标识符中最好使用ASCII字母

5、标识符不应该使用中文,虽然中文标识符也能够正常编译和运行,其原因如上一点讲到的:是把中文当做unicode字符集中的符号来对待了。(Unicode即万国码,由4位16进制组成,只能接受一部分中文字符,所有的中文字符都看做是字符集中的符号)

例如下列程序是可以正常编译和运行的,但是不建议这么做:

Public class Test{

   Publicstatic void main(String[] args){

      Stringjava学习=”中文标识符测试”;

      System.out.println(“java学习==”+java学习);

 

 

}

}

小测验:

下列哪些是正确的标识符:

你好,可用,但不建议

Javass正确

22a b,A+b,A-b,A_b,A*b,A/b,$1,A$b,_Ab

数据类型

在把我们的数据表达成为计算机能认识的东西的时候,除了基本的关键字和标识符,我们要为每个数据分个类型,为什么要分类型呢?分类的好处是把相同的或者相似的放在一起,计算机在操作这些数据的时候,为了方便操作,就将数据进行分类

什么是数据类型?

数据类型简单的说就是对数据的分类,对数据各自的特点进行类别的划分,划分的每种类型都具有区别于其他类型的特征,每一类数据都有相应的特点和操作功能。

Java基本数据类型——整数型

Java中的基本数据类型

java中的基本数据类型分为八个原始数据类型,原始类型可分为四种:

(1)   整数型:byte,short,int,long(都是小写的)

(2)   浮点型:float,double

(3)   字符型:char

(4)   逻辑型:boolean

整数型——byte,short,int,long

在java中,整数型的值都是带符号的数字,可以用十进制、八进制和十六进制(java只提供这三种进制的直接表达)来表示,所谓多少进制,就是满多少就进位的意思,如十进制表示逢十进位,八进制就表示逢八进位。

Java中默认是十进制的,示例:

15:十进制的15

015:八进制的15,相当于十进制的13

0x15:十六进制的15,相当于十进制的21

Java中,表示数据的最小单位是byte,就是最小的数据单元,8位

Short是16位的,int是32位的,long是64位

实际上它们表达的都是一类数:整数,只是表达的长度不同而已。

举例:

Public class Test{

   Publicstatic void main(String[] args){

      inta = 0123;

      System.out.println(“java学习==”+a);

 

 

}

}

 

输出结果为:83(十进制值)

进制换算

在Java中的定义示例:

Public class Test{

   Publicstatic void main(String[] args){

      inta = 0123;

      System.out.println(“java学习==”+a);

 

 

}

}

如果要明确为long型,在数据后面加上L;

整数类型的值,如果没有特别指明,默认是Int型。

取值范围和默认值

Byte     8位    -27-27-1    默认值均为0

Short    16位   -215-215-1

浮点型(float,double)

Java用浮点数来表示实数,简单地说就是带小数的数据,默认是double类型,double是64位,float是32位的,直接将double给float,就会出现精度不够的现象。

用float或double来定义浮点数类型,如果一个数字包括小数点或指数部分,或者在数字后带有F或f(float)、D或d(double),则该数字文字为浮点型的。

示例如下:

12.3  //简单的浮点型数据

12.3E10  //数据很大的一个浮点数据,这里采用科学技术法(如果不想用科学技术法,可以采用BigDecimal或者DecimalFormat)

在java中的定义示例:

Float abc =5.6F;

Float abc = 5.6f

Double abc =5.6;

Double abc = 5.6D;

Double abc = 5.6d;

浮点型的值,如果没有特别指明,默认是double型的。

Java基本数据类型——字符型

Char类型用来表示单个字符。一个char代表一个16bit无符号(不分正负的)Unicode字符,一个char字符必须包含在单引号内。示例如下:

‘a’  //表示简单的字符

‘1’   //用数字也可以表示字符

下面就错了,只能使用单个字符

‘ab’    //错误

‘12’    //错误

什么是Unicode编码?

Unicode编码又叫统一码、万国码或者单一码,是一种在计算机上使用的字符编码。它为每种语言中的每个字符设定了统一并且唯一的二进制码,以满足跨语言、跨平台进行文本转换、处理的要求。1990年开始研发,1994年正式公布。随着计算机工作能力的增强,Unicode也在面世以来的十多年里得到普及。

Unicode表达如下:

‘\u????’     一个Unicode字符。????应严格按照四个16进制数进行替换。Char a = ‘\uaaaa’

Char a =’a’

Char a =65    //输出A

所以将char归到整数类型

取值范围和默认值

名称      长度        范围                默认值

Char     16位    Unicode   216-1       Unicode  0

Java里面的转义字符

在java中有些字符不好表达,比如说回车,在程序中如何表达呢?

在java中用转义字符来表示这些难以表达的字符。

转义字符是指,用一些普通字符的组合来代替一些特殊字符,由于其组合改变了原来字符表示的含义,因此称为“转义”。常见的转义字符有:

\n  换行(\u000a)      \t  水平制表符,就是tab键(\u0009)

\b  空格(\u0008)      \r   回车(\u000d)

\f  换页(\u000c)         \’   单引号(\u0027)

\”  双引号(\u0022)         \\   反斜杠(\u005c)

举例:

Java基本数据类型——逻辑型

逻辑型——Boolean型数据

逻辑值有两种状态,用两个文字值表示,即true和false

在java中的定义示例:

Boolean truth = true;  //声明变量truth

注意:java中不可以直接将整数类型和逻辑类型转换

java在整数类型和boolean类型之间无转换计算。有些语言(C和C++)允许将数字值转换成逻辑值(所谓非零即真),这在java编程语言中是不允许的;boolean类型只允许使用boolean值(true或false)

八种基本数据类型就学完了,刚才是单独定义,现在相互换换试试。

如:long a = 123L;

Double b = a;

进制的转换在后面我们会学到。

接下来看两个概念

声明和赋值

什么是声明?

声明为java程序实体引入标识符,能够使用这些名字访问到这些实体,声明实体包括:类名、属性名、方法名、变量名、参数名、接口名等等。其实简单点说就是定义某个东西并且对外宣称它。

就好比你给某个东西取了个名,然后你以后说这个名字别人就知道你所指的那个东西。

什么是赋值

   赋值就是为一个声明的变量或者常量赋予具体的值,也就是赋予值的意思。使用一个等号“=”来表示。

   如:int  a =   5

这句话的意思不是说a等于5,而是声明一个类型为int的变量a,并将它赋值为5。

a就代表了5,操作a就相当于操作5。

接下来看另一个类型——字符串类型

字符型只能表示一个字符,那么多个字符如何表示呢?

Java中使用String这个类来表示多个字符,表示方式是用双引号把要表示的字符串引起来,字符串里面的字符数量是任意多个。

String的文字应用双引号封闭,如前面HelloWorld里面写的:

在java中定义示例:

String greeting = “Good Morning !! \n”;

String err_msg =”Record Not Found !”;

String str1,str2;//声明两个字符串变量

String s = “12abc”; //基本的字符串型

String s = “”;//表示空串

几点重要的提示:

1、        String 不是原始的数据类型,而是一个类(class),类的概念不懂没关系,后面会学到。

2、        String 是不可变的,也就是说一旦定义并赋值了,String将不会改变。修改String的值其实是新生成了一个String。

比如说:

String s1=”www”;

S1 = “123”;//创造了两个String

这个时候输出s1的时候是出123还是www?

3、        String包含的字符数量是任意多个,而字符类型只能是一个。

要注意:”a”表示的是字符串,而’a’表示的是字符类型,它们具有不同的功能。

4、        String的默认值是null

这个东西现在理解起来可能还有些困难,大家可以先记着,等到讲到后面常用类的时候,就会派上用场了,刚开始你们总是要记点东西的。

好了,看下这些题,作个小测验,马上做一下:

1、        哪些数据类型能存储21?(选出所有正确答案)

A int    B Boolean  C  double     D float    E char

可以将小范围的值放到大范围里面

2、        哪些数据类型能存储21.21(选出所有正确答案)

A int B Boolean  Cdouble    D float

21.21后面没有加f,所以它是double型的

 

3、        哪些数据是合法的char型值(选出所有正确答案)

A a     B ‘a’    C 8     D ‘8’    E “a”

4、        哪些数据是合法的boolean型值(选出所有正确答案)

A true      B ‘true’    C1      D false    E0

5、        int(5.6)的值是多少?

A 5    B 6     C5.6    D 都不对

虽然还没有学到,马上就会学。Int函数取整函数

学完了数据类型,接下来就来学习在java中如何使用这些数据类型了。

常量和变量

什么是常量?

常量是值不可以改变的标识符。说白了就是个名字

对常量的定义规则:建议大家尽量全部大写,并用下划线将词分隔。

如JAVA_CLASS_NUMBER,FILE_PATH等

什么是变量?

变量是值可以改变的标识符,听起来好像是有一定道理,但是我们要麻烦点,虽然是值可以改变的标识符,但是通常是变量用来引用一个存储单元,用标识符来表示,可以通过操作变量来操作变量所对应的内存区域或值块的值。简单点说,变量就是要么指向一个值,要么指向一个空间,我可以通过操作这个变量来操作这个值或者这个空间,这个就叫做变量。

1、        变量是标识符

变量本质上是标识符,但不是所有的标识符都是变量,它所指向的东西是可以改变的标识符才叫做变量。

2、        值可以改变

一定是值可以改变的这些标识符才被称为变量,注意是可以改变,不是一定要改变。

变量的定义规则:

1、        遵从所有标识符的规则

2、        所有变量都可以大小写混写,一般来说首字母应小写

3、        尽量不要使用下划线和$符号

4、        可以先声明再赋值,也可以声明赋值同时进行

几点说明

(1)   变量在计算机内部对应一个存储单元,而且总是具有某种数据类型:基本数据类型或引用数据类型(变量必须有类型)

(2)   变量总是具有与其数据类型相对应的值;

(3)   每个变量均具有:名字、类型、一定大小的存储单元以及值。

类型   变量名  =  值;

好,储备到现在,我们算是把单个的元素(如关键字、标识符、类型等)准备完了,现在就可以把单个元素应用到java代码里去进行组合,组合成小的片段,来,看看java里的小的片段

语句

用分号;结尾的一行代码就是语句,java中语句必须以;结尾。

块(block

一个块就是以{}作为边界的语句的集合,块可以嵌套。

空格

在一个java程序中任何数量的空格都是允许的。但是在SCJD考试中空格的数量是严格的,主要是用于使程序显得规范。

注释

什么是注释呢?简单来说,就是标注解释的意思,主要用来对java代码进行说明。Java中有三种注释方式,写注释一定要写出背后的含义,用来解释代码背后的含义。

(1)    //:注释单行语句,示例如下:

//定义一个值为10的int变量,用来干什么?为什么是10

Int  a =  10;

(2)   /*    */  多行注释,示例如下:

/*

这是一个注释,不会被java用来运行

这是第二行注释,可以有任意多行

*/

(3) /**     */  文档注释

紧放在变量、方法或类的声明之前的文档注释,表示该注释应该被放在自动生成的文档中(由javadoc命令生成的html文件)以当作对声明项的描述。

示例:

/**

*@version

*@author

*/

/**

*这是一个文档注释的测试

*它会通过javadoc生成标准的java接口文档

*/

常常在javadoc注释中加入一个以@开头的标记,结合javadoc指令的参数,可以在生成的API文档中产生特定的标记。

什么是javadoc?

一层含义是指java里面的Javadoc命令,可以使用该命令来生成文档;另一层含义就是指符合javadoc规则而生成的文档

常用的javadoc标记

@author:作者       @version:版本      @deprecated:不推荐使用的方法        @param:方法的参数类型

@return:方法的返回类型

@see:参见,用于指定参考的内容

@exception:抛出的异常

@throws:抛出的异常,与exception同义

先了解一下,肯定不能让你们现在就写。

Java编程基本的编码约定

有些还没有学到,没有关系,先了解一下。

类——类名应该是名词,大小写可混用,但首字母应大写。例如:

Class   AccountBook

Class   ComplexVariable

接口——接口名大小写规则与类名相同。

        Interface  Account

方法——方法名应该是动词,大小写可混用,但首字母应小写。在每个方法名内,大写字母将词分隔并限制使用下划线。例如;

        balanceAccount()

        addComplex()

变量——所有变量都可大小写混用,但首字母应小写。词由大写字母分隔,限制用下划线,限制使用$,因为这个字符对内部类有特殊的含义。

        currentCustomer

变量应该代表一定的含义,通过它可传达给读者使用它的意图。尽量避免使用单个字符,除非是临时“即用即扔“的变量(例如,用I,j,k作为循环控制变量)

常量——全部大写并用下划线将词分隔

        HEAD_COUNT

        MAXIMUM_SIZE

控制结构——当语句是控制结构的一部分时,即使是单个语句也应适用括号({})将语句封闭。例如;

If (condition){

   Dosomething

} else  {

Dosomething  else

}

语句行——每行只写一个语句并使用四个缩进的空格使你的代码更易读。

逢块缩格

好,这些讲过之后呢,接下来就是非常麻烦的东西了,就是运算符。

运算符

Java运算符很多,下面按优先顺序列出了各种运算符

又结合就是先算该符号的右边,然后再向左运算

算术运算:+、-、*、/、%

Int a = (5+3-4)*5/6+3;

实际上就是四则运算

对于该例,大家注意一个问题,就是整数型除以整数型,只取整数部分,如int a=4/5 结果 a=0

     Int a= 5%3 结果 a=2

要想判断一个数是否能被另一个数整除,我们就可以用%运算,如果余数为0,则整除

If(a % 2 ==0){

}

比较运算符

比较运算符包括>、<、>=、<=、==、!=等类似运算,运算结果为boolean型,需要注意的是:

字符可以比较大小;(用它们的ascii码,化为整数)

小心浮点数的相等比较

逻辑运算符:

运算符&&(定义为“与”)两个条件都要满足和||(定义为“或”)满足条件之一即可,执行布尔逻辑表达式。参与运算的都是boolean类型的值,运算结果也是boolean类型的。

比如有如下的语句,怎么表达呢?

   如果  你 是男生  而且  你的身高超过了1.8米

   那么  你  可以加入  男子篮球队

  String sexOfYou =”男生”;

   DoubleheightOfYou=1.83;

   If(sexOfYou== “男生” && heightOfYou > 1.8{

      System.out.println(“请你加入男子篮球队”);

   }

在&&中,如果前面的是false,后面的条件就不用判断了

在||中,如果前面的是true,后面的条件就不用判断了

不要小看这种规律,如果条件用&&连接时,只要把条件最严格的条件放在最前面就可以了,出现false的几率就大,后面就无需判断了

用||连接时,只要把条件最容易为true的条件放在最前面就可以了,后面就无需判断了,目的就是减少后面的运算。

赋值预算

自加、自减运算符(++、--)

++运算相当于:运算的变量加1,如x++等同于x=x+1

--运算恰好相反,相当于运算的变量减1

注意:x++和++x是不同的。X++是先使用,然后再加;++x是先加然后再使用。实例

Int a=5;

System.out.println(“a=”a++);

输出结果为5

如果System.out.println(“a=”+a);

输出结果为6

如果System.out.println(“a=”+++a);

则输出结果为6

位运算

位运算符&(与)、|(或)、~(非、取反)、^(异或)

移位运算符

Java语言提供了两种右移位运算符和一种左移位运算符,右移一位(>>)相对于除以2,左移(<<)相对于乘以2。

(1)  运算符>>进行算术或符号右移位。移位的结果是第一个操作数被2的幂来除,而指数的值是由第二个数给出的。例如:

128>>1  gives 128/2=64

256>>4  gives 256/24=16

-256 >>4gives -256/24=-16

(2)逻辑或非符号右移位运算符>>>主要作用于位图,而不是一个值的算术意义:它总是将零置于符号位上。例如:

1010…>> 2 gives 111010…

1010…>>>2 gives 001010…

在移位的过程中,>>运算符使符号位被拷贝

(3)运算符<<执行一个左移位。移位的结果是:第一个操作数乘以2的幂,指数的值是由第二个数给出的。例如:

128<<1 gives 128*2=256

16<<2 gives 16*2*2=64

(4)负数等于整数取反加一

例如:

1357      =   00000000000000000000010101001101

-1357     =   11111111111111111111101010110011

1357>>5   =   00000000000000000000000000101010

-1357>>5  =   11111111111111111111111111010101

1357>>>5  =   00000000000000000000000000101010

-1357>>>5  =  00000111111111111111111111010101

1357<<5   =   00000000000000001010100110100000

-1357<<5  =   11111111111111110101011001100000

移位运算在常规开发中一般用不上。了解即可

说明:

(1)   移位运算符将它们右侧的操作数模32简化为int类型左侧操作数,模64简化为long类型右侧操作数。因而,任何int x, x>>>32都会导致不变的x值,而不是你可能预计的零。

(2)   值得称赞的一点是:>>>运算符仅被允许用在整数类型,并且仅对int和long值有效。如果用在short或byte值上,则在应用>>>之前,该值将通过带符号的向上类型转换被升级为一个int。有鉴于此,无符号移位通常已成为符号转移。

用加号(+)进行串连接

运算符+能够进行String对象的连接并生成一个新的String

String schoolName =”无锡城市职业技术学院”;

String className=”NIIT20102班”

String result = schoolName+className

输出结果为:无锡城市职业技术学院NIIT20102

共生成了3个String对象

如果+运算符中有一个自变量为String对象,则其他自变量将被转换成String。所有对象都可被自动转换成String,尽管这样做的结果可能是意义含糊的。不是串的对象是通过使用toString()成员方法而转换成串的等价物的。只要是字符串加的东西都转换成字符串

三目运算符? :  (条件表达式)?result1:result2

三目运算符?:表达的是:判断问号前面的条件表达式是否为真,如果为真,返回冒号前面的值,否则返回冒号后面的值。例如:

    int  i  = (5>3) ?  6  :  7

    System.out.println(“the i =”+i);

输出结果为:the  i = 6

其实三目运算符的基本功能相当于if-else(马上就要学到了),使用三目运算符是因为它的表达式比相同功能的if-else更简洁。上面的例子改称用if-else表示如下:

      int   i = 0;

      if(5>3){

           i=6;

      }else  {

           i=7;

      }

      System.out.println(“thei =”+i);

运行结果为the i = 6

好,讲完了所有的运算符号,还有一些运算符没有讲到,咱们后面用到再说,下面学习表达式

表达式

什么是表达式

其实就是综合使用变量、常量还有各种运算符来组合成的式子

最容易理解的就是四则运算的表达式,如下:

int  a  =  10;

int  b  =  10;

((a+b)*a-b)/5;     //这就是一个表达式

同样的道理,如下都是表达式

a>b、a<=b

表达式都有返回值。

好,差不多了,到这个地方又前进了一步,虽然我们刚才讲的这些语法比较零散,但是还是有思路在里面的,我们先学的是单个字(关键字),然后再学到的一些词,然后再学的运算符实际上就是将单个字组合起来,形成表达式,相当于词组,一个一个小片段,再把单个字单个次组合起来就是块,就是java的控制语句,那么Java有几类控制语句呢?只有2类,一类叫分支,一类叫循环。分支就是分叉,如果怎么样则会怎么样,否则会怎么样;循环实际上就是重复做。分支有两种,一种是if-else,另一种就是switch

 

java分支控制语句

分支语句又称条件语句。条件语句使部分程序可根据某些表达式的值被有选择地执行。Java语言支持双路和多路switch分支语句。

if ,else语句

if,else语句的基本语法是:

        if (布尔表达式){

           语句或块;

        }else  {

           语句或块;

        }

表达的是:

           如果(布尔表达式){

              执行语句;

           }   否则  {

              执行语句;

           }

语法本身很简单,我们继续进行扩展

说明:

(1)   if 块和else块可以包含任意的java代码,自然也可以包含新的if-else,也就是说,if-else是可以嵌套的,嵌套的规则是不可以交叉,必须完全包含。比如:

if (a1>a2){

    if(a1>a3){

      //包含在里面的块必须先结束

    } else {

      //同样可以包含if- else

    }

}

注意:凡是嵌套的东西要正确结尾

(2)   else部分是选择性的,并且当测试条件为假时如不需做任何事,else部分可被省略。也就是说if块可以独立存在,但是else块不能独立存在,必须要有if块才能有else块。else匹配规则就是匹配到离它最近的那个if。

(3)   如果if块和else块的语句只有一句时,可以省略{},例如:

   if (a1>a2)

      System.out.println(“这是if块”);

  Else

      System.out.println(“这是else块”);

上面的代码从语法角度是完全正确的,但是从代码的可阅读性上,容易让人产生迷惑,所以不建议大家这么写。

(4)   还可以把else和if组合使用,就是使用else  if,表达“否则如果”的意思,示例如下:

   if (a1>a2){

      System.out.println(“a1>a2”);

  } elseif(a1>a3){

      System.out.println(“a1>a3”);

   }else if (a1>a4){

      System.out.println(“a1>a4”);

   }else{

      System.out.println(“now is else”);

   }

示例:

  

public   class  test2{

   public static  void  main(String[] args){

         int score = 90;

         if(score>90){

            System.out.println(“优秀”);

         }else if(score>70 &&score<=90){

            System.out.println(“良好”);

         }else{

            System.out.println(“及格”);

         }

    }

}

要注意这个例子,其实隐含有一个判断逻辑在里面

比如运行:else if(score>=70)这句话的时候,隐含一个逻辑是score<90,如果单独表达的话,跟如下等价:

If(score<90  && score>=70)

前面我们学到了选择分支语句if-else和else-if,分支语句是用来选择分支,选择不同的路径,下面学习另外一个switch。Switch表示多路选择

If-else刚开始叫做双路选择,但是也可以表达很多分支,即多路分支。

Switch用来表示多路选择分支的情况,switch语句的句法是:

        Switch(expr1){

        Case expr2:

              Statements;

              Break;

        Case expr3:

           Statements;

           Break;

        Default:

           Statements:

           Break;

}

仍然以上面的例子为例:

public   class  test2{

   public static  void  main(String[] args){

         int score = 90;

         if(score>90){

            System.out.println(“优秀”);

         }else if(score>70 &&score<=90){

            System.out.println(“良好”);

         }else{

            System.out.println(“及格”);

         }

    }

}

如果想改成switch来写。

      Switch(score){

           Case 90:

           System.out.println(“90分”);

           Break;

           Case 89:

              System.out.println(“90分”);

           Break;

           Default

              System.out.println(“60分”);

      }

相比较来说if-else比较灵活。

说明:

(1)   switch的表达式expr1只能是整数型的值或者enum枚举类型的常量值,包含byte、short、int和char,不能是字符串或对象,也不能是long型的值。(用char举个例子)

(2)   当变量或表达式的值不能与任何case值相匹配时,可选缺省符(default)指出了应该执行的程序代码。

示例:

    IntcolorNum = 5;

    Switch(colorNum){

            Case0:

               System.out.println(Color.red);

               Break;

            Case1:

               System.out.println(Color.green);

               Break;

            Default:

               System.out.println(Color.black);

               Break;

}

(3)   如果没有break语句作为某一个case代码段的结束句,则程序的执行将继续到下一个case,而不检查case表达式的值。因为case子句只是起到了一个标号的作用,用来查找匹配的入口并从此处开始执行,对后面的case子句不再进行匹配,而是直接执行其后的语句序列。

示例:

   IntcolorNum = 0;

   Switch(colorNum){

Case 0:

   System.out.println(Color.red);

Case 1:

   System.out.println(Color.green);

Default:

   System.out.println(Color.black);

   Break;

}

      运行结果:

        Java.awt.Color[r=255,g=0,b=0]

        Java.awt.Color[r=0,g=255,b=0]

        Java.awt.Color[r=0,g=0,b=0]

Switch就这么点东西,表达的是多路分支,遇到break才会终止,否则就会向下运行。下面该看看循环了

Java循环控制语句——for

循环语句使语句或块的执行得以重复进行。

For循环语句

      句法:

      For(初值表达式;boolean测试表达式;改变量表达式或者步长){

语句或语句块

}

Int sum =0;

For(int i=0;i<100;i++){

      Sum+=I;即sum = sum+I;/即完成了从0加到99

     

}

      System.out.println(“sum==”+sum);

板书演示程序执行过程。Int=0其实只执行一回。I<=100其实执行了102,每判断一回就执行循环体一次。I++执行100回。

说明:

1、        如果循环体只有一条语句,那么可以不适用{}。但是我们不建议这么做。

2、        for 语句里面的3个部分都可以省略,但是我们不建议这么做

示例1:就是一个无限循环,其实就是死循环

        For(;;){

           System.out.println(“java学习”);

        }

示例2:可以省略部分,无限循环

      For(int i=0;;){

        System.out.println(“java学习”+i);

}

示例3:可以省略部分,没有改变表达式的值,所以条件判断永远为真,无限循环

      For(int i=0;i<3;){

        System.out.println(“java学习”+i);

}

示例4:可以省略部分,没有条件判断,无限循环

        For(int i=0;;i++){

   System.out.println(“java学习”+i);

}

当然还有其他组合方式,都是可以的。没有任何意义。

多重循环

   所谓多重循环就是在循环体内嵌套循环,示例如下:

        For(int i=0;i<2;i++){

   System.out.println(“我在学习java”);

   For(int j=0;j<20;j++){

   System.out.println(“目前正在学习=”+j);

}

}

运行顺序还是通过画图来说吧

可以使用以下示例进行讲解:

   Public class test4{

   Public static void main(String[] args){

   Int sum=0;

   For(int i=0;i<=2;i++){

   For(int j=0;j<=2;j++){

      System.out.println(“i==”+i+”,j==”+j);

}

   }

}

}

通过再加一层循环来看学生是否理解。

接下来学习while循环

While循环语句

句法:

   while(布尔表达式){

   语句或块;

}

示例:

   Inti=0;

   Intsum=0;

   While(i<=100){

   Sum+=I;

   I++;

}

System.out.println(“sum=”+sum);

说明:

1、        如果循环体只有一条语句,那么可以不适用{}。但是不建议这么做

2、        While循环可以和for循环互换,一般建议知道循环次数的情况下使用for循环,而不知道循环次数的时候使用while循环。

Java循环控制语句——do while

Do while循环语句:

   句法:

   Do {

   语句或块;

} while(布尔测试);

示例:

      Inti=0;

      Intsum=0;

        Do {

           Sum+=I;

           I++;

        } while(i<=100);

System.out.println(“sum=”+sum);

说明:do循环与while循环的不同之处在于,前者至少执行一次,而后者可能一次都不执行。

好了,3个循环语句我们都学完了,接下来我们来看最后一个问题,循环里面还有特殊循环流程控制,什么是特殊循环控制呢?就是在循环里面我们可能想要对循环去控制他。

下列语句可被用在更深层次的控制循环语句中:

Break

   Break语句被用来从switch语句、循环语句和预先给定了label的块中退出,跳出离break最近的循环。

例如:

       

   Public class test5{

   Publicstatic void main(String[] args){

   Intsum=0;

   For(inti=0;i<2;i++){

      For(intj=0;j<2;j++){

         For(int k=0;k<2;k++){

           System.out.println(“i==”+i+”,j==”+j+”,k==”+k);

           Break/continue;

}

}

}

 

}

 

 

}

输出结果为:000

           010

           100

           110

Continue

   Continue语句被用来略过并跳到循环体的结尾,终止本次循环,继续下一次循环。将上例的break改为continue看看程序执行情况。是没有区别的。但是如果将continue放在System.out语句上面,则没有任何输出。上例换成continue时,无输出。如果将程序改为

For(int k=0;k<2;k++){

   If(k==0){

Continue;

}

}

System.out.println();

则输出结果为:

           001

           011

           101

           111

如果将上例的continue换成break,则不会输出任何结果。

Label语句

      Label可标识控制需要操作的任何有效语句,它被用来标识循环构造的复合语句。不允许乱写。不能叫break到,而是break谁。或者continue谁

以三重循环为例

Public class Test{

   Public static void main(String[]args){

   Aa:   Int sum = 0;

   Aa:   For(int i=0;i<2;i++){

   Aa:     For (int j=0;j<2;j++){

           For(int k=0;k<2;k++){

                 Aa:System.out.println(“i=”+i+”,j=”+j+”,k=”+k);

              Aa:   If(k==0){

                 Break aa;

Java里的Label不能随便乱写,配合break使用时,只能用在循环及其循环里面的判断语句上。

到这里,break就和label配合起来了。一般,continue不会和Lable配合。本来这种就不是正常的控制,所以叫做特殊控制。你写个几个月的程序都可能不会用上。第二章到此就讲完了,然后我们来复习一下。

第二章总结:

 

 

 

 

 

 

                    是什么?

           关键字  有什么?

                    能干什么?(最重要的作用就是相对我们的程序或者虚拟机或者运行平台的一个约定)

                     是什么?

           标识符    干什么?

                     定义规则

                       是什么?        基本数据类型

            数据类型   有什么?分类

                       用来做什么      引用数据类型

                       杂项    转义

                               声明

                               赋值

Java基础语法                          是什么

            常量和变量:都是标识符     干什么

                                      定义规则

 

            Java代码的基本知识:语句、块、注释、空格、编码约定

                         运算符:左结合、右结合、优先级

                         算术、比较、逻辑、自加自减、赋值、 位、

          基础语法       移位、字符串串接、三目

                         表达式、控制语句、循环

第二章学完了,轻松的日子也就过完了。

编程题:

输出图案:

*

**

***

****

*

**

***

****

Public  class   star{

   Publicstatic void main(String [] args){

      //控制段落

      For(int k=0;k<2;k++){

//控制行数

        For(int i=0;i<4;i++){

           //控制每行输出的*

           For(int j=0;j<i+1;j++){

              System.out.print (“*”);

           }

           System.out.print(“\n”);

        }

      }

   }

}

 

作业讲评

1、  int x=6,y=8;

Boolean b;

B=x<y|++x=--y

2、  x=10;

x+=x-=x-x;

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

第三章类和对象

这一章内容相对来说比较多,而且很多东西是需要理解的,我们除了要讲理论性的,还要做一些练习,我们先看理论性的东西。面向对象是java编程第一个比较重要的思想或者说转变,你们之前学的c是面向过程的,从现在开始要培养对象的概念。

面向对象初步

什么是对象?

大家可能对它有点意思,但是无法准确的将它表达出来,那么我们来看什么是对象。

对象是真实世界中的物体在人脑中的映像,包括实体对象和逻辑对象。比如说一瓶矿泉水,你怎么知道它是一瓶矿泉水,为什么不说它是一个鼠标,而是矿泉水,大家想想,这就是在大家头脑里的影像,你在判断它是一瓶矿泉水的时候,你是如何得出这个结论的,是不是从你的头脑里把你以前对所有物体的认知调出来,包括大小、形状、外观、颜色功能等全都储藏在你的脑袋里的知识库里的,然后把它调出来,调出来过后再和你所看到的物体进行比较匹配,匹配上了以后,你就知道它就是一瓶矿泉水了,所以说这个储藏在你头脑里的知识就是你头脑里的影像,它已经储藏在你头脑里了,虽然你看不见摸不着但你不能否认它确实储藏在你的头脑里,但是如果说你以前从来没见过矿泉水,你知不知道它是矿泉水?肯定不知道,因为你头脑里没有这部分知识的储备,也就是没有这个东西的映像,所以说你现在无从判断,这就是给大家解释一下什么叫做物体在人脑中的映像,也就是说我们现实生活当中各种我们能识别的东西都是因为我们已经建立好了这个映像,这个就是对象,还算比较广义的定义,一个虚拟的假象,包括实体对象和逻辑对象。

平时我们遇到的多数都是实体对象,实际存在的对象,还有虚拟存在的对象,比如我们之间的关系是师生关系,这个关系是看不见摸不着的,就是逻辑对象。

实体对象指的是我们在现实生活中能看得见、摸得着,实际存在的东西,比如:人,桌子,椅子等。逻辑对象是针对非具体物体,但是在逻辑上存在的东西的反映,比如:人与人之间的关系,这个看不见摸不着,但是是逻辑存在的。为了简单,这里先讨论实体对象。

对象的基本构成

分析实体对象的构成,大家有没有发现有这样一些共同点,这些实体对象都有自己的组成,比如说电脑,它是由机箱、显示器、CPU、主板等,我们可以这么讲,电脑就相当于上述物件的组合,组成这个对象的东西我们称为属性。这些属性决定了对象的具体表现,比如:人的身高、体重等。

刚才讲的那些属性是静态的,除了静态的,用来描述实体对象的基本情况外,实体对象还有自己的动作,通过这些动作能够完成一定的功能,我们称之为方法,比如:人的手能动,能够写字,能够刷牙等,比如电脑能开机、关机、跑游戏、存储数据、做运算等等,这些都是电脑所拥有的动作,这些动作我们就称它为方法。

对象的基本构成实际有两类,一类就是静态的属性,还有一类就是动态的功能。

到此,我们讲了两个内容,一个对象是个什么东西,第二就是对象由什么构成。

空调的属性:颜色、体积等。空调的动态功能:(方法):制冷、制热等。

任何一个对象都有这两个东西,但是,大家想想,拿这些实际的对象跟我们的编程有什么关系呢?你看这个空调在这儿实实在在的摆着,但是跟编程有什么关系啊,是不是没有啊,那么讲了这么多对象的概念,跟我们编程有什么瓜葛呢?现在我们就来找他们之间的联系,那么接下来我们要学的就是,因为我们的程序是用java来写,所以就要看对象和java有什么联系呢,就是先把对象抽象出来。

什么是抽象?

抽象是在思想上把各种对象或现象之间的共同的本质属性抽取出来而舍去个别的非本质的属性的思维方法[WHB1] 。也就是说把一系列相同或类似的实体对象的特点抽取出来,采用一个统一的表达方式,这就是抽象。

如何对对象进行抽象?

先告诉大家方法,用什么方法来进行对象的抽象呢?

第一步:分析各种车对象的静态属性

第二步:分析各种车对象的动态功能

第三步:抽取出公共的属性和功能

第四步:考虑是否加入一些个性化的属性和功能

这里我们用车来做个示例。

车{

   //静态属性(所有的车都有哪些静态属性呢,你们说,我来写)

      颜色、排量、大小、形状、品牌、类型、方向盘

      轮胎

      发动机

      制动系统

      电瓶

      等等

   //动态功能

      开动、刹车、转弯、鸣笛、

}这些都是通用的

我的宝来车{

   颜色=灰色;排量=1.6;品牌=宝来;类型=家用轿车;//公共的

   气囊=2;

}

找到共同点,描述出来就是抽象

抽象对象和实体对象之间的关系

仔细观察上面的抽象对象和具体的实体对象。

你会发现,抽象对象只有一个,而实体对象却是无数个,通过对抽象对象设置不同的属性,赋予不同的功能,那么就能够表示不同的实体对象。为什么要抽象出来呢?

这样就大大简述了对象的描述工作,使用一个对象就可以统一地描述某一类实体了,在需要具体实体的时候,分别设置不同的值就可以表示具体对象了。比方说楼下这停的一排车,我们就叫一个对象,都可以称之为车,每一辆都包含在内了,我们用统一的描述,就可以把这些车都描述出来了,正是因为有这样一个抽象的过程,才使得用程序去描述这些对象成为可能。如果说我们就用某个具体的对象来进行编程的话,大家想想会出现什么问题?一辆车就是一个对象,那就没有尽头了,有多少个就写多少个,我们可以用一个对象就可以把所有的车全部包括在内了,这就是抽象的意义,就是把具体的对象演变成一个公共的描述,正是因为这样,我们才有可能用程序描述出来。

到这儿,可能很多同学还是觉得比较虚幻。可以再以电脑进行举例。

抽象对象和类型

根据前面的描述,抽象对象可以代表一类的东西,这就是类型。比如前面提到的“车”这个抽象对象,就代表了所有的车,具体的对象“宝来车”属于车,具体的对象“马六”也属于车,车就是一个类型。

大家要反复去练习,要把具体的变成抽象的。凡是能看到的东西都可以去抽象提炼,做到你满眼看过去都不是实体的东西,而是抽象的对象。抽象的目的是要用java来表达。

Java中如何表达抽象对象和实体对象呢?

Java中的类

把抽象对象使用java表达出来,那就是类class。类在java编程语言中作为定义新类型的一种途径,类声明可定义新类型并描述这个类型是如何实现的。

抽象对象中的属性就对应为java类的属性

抽象对象中的功能就对应为java类的方法

由此我们也就可以看到了,一个类包含属性和方法

Java中的对象

Java中的对象是在java中一个类的实例,也称为实例对象。就是具体的实体对象。

类和实例的关系:一个类多个实例。

类可被认为是一个模板——你正在描述的一个对象模型。一个对象就是你每次使用的时候创建的一个类的实例的结果。

Java类的基本构成

刚才已经学到用类表达抽象对象了,知道了抽象出来的对象被java表达成为类,那么java类到底如何写呢?如何用java表达类,现在就正儿八经学类的写法了。

Java类的定义形式

一个完整的java类通常由下面六个部分组成:

包定义语句(package

   Import语句

   类定义class{

      成员变量(属性)

      构造方法

      成员方法(方法)

   }

我们现在学写java类,不外乎就是把这六个学会了,你就会写类了。刚才的抽象就是告诉大家java类用来干什么的。

其中:只有类定义和{}是不可或缺的,其余部分都可以根据需要来定义。

也就是说最简单的java类怎么写?

Class t1{

}

编译可以通过,但运行时会报错,找不到main()方法。

接下来我们就分别学习这六个部分

什么是包?

在java中,包是类、接口或其他包的集合,也就是说包里头放类、接口或者其他的包,大家听起来好像挺玄乎的,那么我简单来告诉你,比方说文件夹和子文件夹的关系,方便分门别类的管理,同样道理,源代码也是这样,我们不会把它们乱放在一起,而是用包来组织,所以说包就类似于文件夹,包主要用来将类组织起来成为组,从而对类进行管理。也就是说把一些相同的东西放在一个包内就好管理了。

包能干什么?

(1)  包允许您将包含类代码的文件组织起来,易于查找和使用适当的类。

(2)  包不止是包含类和接口,还能够包含其他包。形成层次的包空间。

(3)  它有助于避免命名冲突。当您使用很多类时,确保类和方法名称的唯一性是非常困难的。包能够形成层次命名空间,缩小了名称冲突的范围,易于管理名称。

所以我们以后再说类的时候不能只说类名,而是要带包说才对,不能说今天作业是test1,结果每个里面都有test1,你得告诉我是哪里面的test1。简言之,包是组织和管理类的。

JDK中常用的包:

有java.lang包:就是我们之前所写的,不用引入可以直接使用的包,它是java的核心包

Java.util包:在java里面统一都叫工具包

Java.awt包:画图形界面的,抽象窗口工具包

Java.swing包:

Java.io包:文件、输入输出流

Java.net包等等。

了解一下即可,以后会逐个学习。

Java中如何表达包——package语句

Package语句必须作为java源文件的第一条语句,指明该文件中定义的类所在的包。若缺省该语句,则指定为无名包,其语法格式为:

  Packagepkg1[.pkg2[.pkg3…]];           //【】表示可选

例如:

      Packageaa.bb.cc.dd;

      Class t1{

      }

      说明t1类在dd包里。

写就写完了,那么如何去编译呢?此时,应该将T1.java放在/aa/bb/cc/dd目录下面。

如何编译和生成包

Java 包的完整路径 XXX.java

如上例:javac aa/bb/cc/dd/t1.java

-d指的是编译后的文件的生成路径

如何运行:

运行带包的程序,需要使用类的全路径,也就是带包的路径;

Java aa.bb.cc.dd.t1

说明:

1、            java编译器把包对应于文件系统的目录管理,也就是说同包即同目录,包的目录就是文件夹的目录

2、            每个源文件只有一个包的声明,如在上例再写一个package aa;则出错。

3、            包名应该全部小写,多个包名中间用点来分隔(是编码规范不是语法规则),用大写也可以,但是不符合规范,人家一看,就显得你想当的业余,不要说你程序写的可好了,但是人家一看就不感兴趣了。

在java中怎么写包,就是在源程序最前面写上package语句,而且只能写在开头,即第一条。前面可以有空格有空行,就是不能有代码。

注意:编译的时候,包间隔用/,运行的时候,包间隔用.

Java类的基本构成——import

Import的功能

为了能够使用某一个包的成员,我们需要在java程序中明确导入该包。使用”import”语句可以完成此功能。比如,我们在a包里面有一个A的类,现在在b包里面的B类里面想要使用它,该怎么办呢?这就需要使用import来引入了。换句话说import语句为编译器指明了寻找类的途径。(讲完例子讲红色字体)举个例子,刚才在aa/bb/cc/dd里定义了一个类T1,此时在/javastudy里面新建一个包d2,在d2里写一个java类,叫t2.java,现在我想在t2.java里头去用这个t1,怎么办呢?先写T2

    package d2;

    public class T2{

      public staticvoid main(String[] args){

        //在这里使用T1

        T1 t = newT1[];

System.out.println(“t1===”+t.s)

      }

   }

在T1里面我追加一个属性如下:

package aa.bb.cc.dd;

class T1{

   public String s=”123”;

   public staticvoid main(String[] args){

System.out.println(“okok”);

}

}

由于T1和T2目前同一个路径下,那么怎么办呢?我们在T2中使用上例中的T1 t = new T1();

编译T2类,javac d2/T2.java,则系统报错,找不到T1,当然找不到了,因为我们默认是在当前路径下找,那么在当前路径下能不能找到呢。我们在T1中class前加上public,否则也会报错的,为什么要加public,我们在后面会讲,在这个时候,我们想要在一个类里面使用另一个类,这个时候就需要使用import,大家看,import怎么写,第一,import语句的位置一定写在package语句之后,类定义之前,写上import aa.bb.cc.dd.*(引入这个包下的所有的类)/importaa.bb.cc.dd.T1;(指名道姓写上要导入的包名),重新编译,则通过。

Import的语法

Import package1[.package2…].(classname|*);

1、        import语句应位于package语句之后,类的定义之前

2、        使用*号表示引入包下所有的类。当然也可以指明要引入的具体的类,过去常采用的是引入具体的类,

使用*会影响性能,但是从1.4版本后完全可以使用*,性能基本没有什么差别,尽量使用*

3、        一个类可以有0~多条import语句。

可以在上例中增加importjava.util.*; 和import java.io.*;

好了,到这里,这些讲完了过后,这是import大家基本了解了,接下来我们补充一个东西就是

Java类搜寻方式

   Import其实就是告诉编译器到哪儿去找所需要的类,那么对于这个大家注意一下,java是按照什么顺序去搜寻呢?程序中的import语句标明要引入p1包中的Test类,假定环境变量classpath的值为”.;c:\jdk\lib;d:\ex”,java运行环境将依次到下述可能的位置寻找并载入该字节码文件Test.class:

.p1\Test.class   //.表示当前路径

C:\jdk\lib\p1\Test.class

D:\ex\p1\Test.class

如果在第一个路径下就找到了所需的类文件,则停止搜索。否则依次搜索后续路径,如果在所有的路径中都未找到所需的类文件,则编译或运行出错。

注意:java搜寻类的时候是找到一个符合的就停止搜索,即找到第一个就停。在引用别人的包的时候很容易出现版本问题。(1.1、1.2等)引用外部资源包的时候往往有多个版本,但是各个版本之间是有差异的。

也就是说它的基本寻找方式就是按照classpath再加上包的结构再加上类名一个一个去找。

接下来看看,在讲类定义之前,我们先看看另外一个非常重要的东西。

就是类的访问修饰符

访问修饰符在java里引入它的目的就是两个字:权限,说白了就是能不能让你访问,我们刚才就在T1中加了一个public。

Java语言允许对类和类中定义的各种属性和方法进行访问控制,即规定不同的保护等级来限制对她们的使用。

Java引入访问修饰符的目的:

在于实现信息的封装和隐藏,就是两个字:权限,说白了就是能不能让你访问,我们刚才就在T1中加了一个public。

Java访问修饰符的具体规则

可否直接访问

控制等级

同一个类中

同一个包中

不同包中的子类的对象

任何场合

Private

Yes

 

 

 

无修饰符

Yes

Yes

 

 

protected

Yes

Yes

Yes

 

public

Yes

Yes

Yes

Yes

在java里面有四类访问修饰符,分别是上表所示。下面给大家演示一下:

Public的含义就是任何地方都可以访问,现在将上例中的public改成private String s,看看还能跑吗?

无修饰符就是在同一个包,同一个类中,把Private  去掉一样无法访问

Protected:同类、同包、不同包的子类,我们现在没有子类,如果在

Public class T2 后面加上extend T1,大家再看看。报错,再将T1 t =new T1()改为T2 t = new T2(),这样就走父子类了。

Public权限最低,没有限制,Private权限最高。

这就java的访问修饰符

讲完这个,我们下面就可以把类写出来了,下面就看看类的定义

类定义

类的定义如下:

<访问修饰符>[一般修饰符]  class <类名>{

      [<属性定义>]

      [<构造方法定义>]

      [<方法定义>]

}

一般修饰符目前还没学到,后面会讲

类的定义就这么简单,下面该看类定义里的东西,先看第一个构造方法

Java类的基本构成——构造方法

什么是构造方法

这也是一个很重要的功能,什么是构造方法呢

类有一个特殊的成员方法叫做构造方法,它的作用是在创建对象时候初始化成员变量。在创建对象时,会自动调用类的构造方法,也就是说构造方法其实是个回调方法。

它怎么个特殊法,接红色字体。

什么叫回调方法呢?意思就是由我们来实现,但是不由我们来调用,而是由jdk调用。

构造方法定义规则

构造方法必须与该类具有相同的名字,并且没有方法的返回类型(包括没有void)。通常都是public的,当然也可以使用其他的访问修饰符。

构造方法可以有参数,也可以没有参数。

举例:

Public class T2 extends T1{

   Public staticvoid main(String [] args){

      T2 t = newT2();

      System.out.println(“t1==”+t.s);

}
T2(){//前面修饰符不限,()内参数不限

}

}

这里补充说明几点:

1、        定义类的时候,类名必须和文件名字保持一致

2、        一个文件只能有一个public class,但是一个文件可以定义多个类

说明:每个类至少有一个构造方法。如果不写一个构造方法,java编程语言将提供一个默认的,该构造方法没有参数,而且方法体为空。

注意:如果一个类中已经定义了构造方法则系统不再提供默认的构造方法。

就像我们上例中没有写构造方法,但是程序里也有构造方法,就在

T2 t = new T2();体现

就相当于在程序中增加一段

Public T2(){

}

虽然不写,但是还是有的。但是如果写了一个,如:

Public T2(int a){

}

那么参数为空的就没有了,如果还需要空参,那就必须自己再写一个。

下面看一下析构方法

Java类的基本构成——析构方法

析构方法finalize的功能是:当对象被从内存中删除时,该成员方法将会被自动调用。实际上,这个方法是没有任何意义的,也就是调了白调。咱们学过的调了白调的还有谁,咱们学过的垃圾回收就是这样的。所以这些东西你看,现在学知识,我一再强调,大家现在学的时候就要踏踏实实的学,知识学一个就是一个,就要真的会,不要说看了书我都懂,我会把它写出来,我告诉你,这没有意义,所以我们学习的时候踏实就是这样,其实很多人都是这样,这个东西看了好像都懂了,但是实际上并没有真的连起来,类比就是学到了一个点,马上就会把以前学过的相关的进行拉通思考,这种就叫做类比,时间长了以后,你学到的会越来越多,类比拉通学习的东西也会越来越多,这就是好的知识架构,所以说好的知识架构是成形的成骨架的,而不是一个一个孤立的点,不成体系的。通常,在析构方法内,可以写用来回收对象内部的动态空间的代码。

特别注意:当我们去调用析构方法的时候,并不会引起该对象实例从内存中删除,而是不会起到任何作用。

在java编程里面,一般不需要我们去写析构方法,这里只是了解一下就可以了。

好,还有最后两个

Java类的基本构成——属性

什么是属性?

简单点说,类的属性就是对象所具有的静态属性。怎么描述就是定义规则

定义规则:

   访问修饰符  修饰符  类型  属性名称 = 初始值;

比如T1类中

Public String s=”123”;这个就是属性

修饰符这里没有。

一般写属性都遵循这个规矩,要么把属性写在开头要么写在结尾,有些老外习惯把属性写在结尾

访问修饰符:可以使用四种中的任意一种

修饰符:是对属性特性的描述,这里先不说,后面会学到,如static、final等等

类型:属性的数据类型,可以是任意的类型

属性名称:任何合法的标识符,当变量来看,因此首字母小写

初始值:赋值给属性的初始值,可以写,可以不写,如果不设置,那么会自动进行初始化,基本类型使用缺省值,对象类型自动初始化为null。

 

package aa.bb.cc.dd;

public class T1{

   public String s=”123”;

   public staticvoid main(String [] args){

      System.out.println(“okok”);

}

   Public inta=0;  //=0可以写也可以不写,效果一样,可以通过T2进行验证。

   Public voidt1(){

   }

}

说明:属性有时候也被称为成员变量、实例变量、域,它们经常被互换使用。但是大家用的比较多的都叫属性,最早叫域,后来叫成员变量,现在基本都叫属性了。

看看最后一个

Java类的基本构成——方法

什么是方法?

方法就是对象所具有的动态功能

定义规则

访问修饰符  修饰符  返回值类型  方法名称(参数列表) throws 异常列表 {方法体}

有的同学说,这一串特别是后面都没有接触到,没关系,你先记住。不用死记硬背,只要拿个例子做对比就好记了。最好的例子就是我们以前常写的那个。

返回值类型:表示方法返回值的类型。如果方法不返回任何值,它必须声明为void(空),就是没有返回值。Java技术对返回值是很严格的,例如,如果声明某方法返回一个int值,那么方法必须从所有可能的返回路径中返回一个int值。

例如:上例T1中

Public int t1(){

   Renturn 8;//必须要有return,只有这么写才是对的

}

方法名称:可以是任何合法标识符,应该小写。

参数列表:包括形参、实参,这个我们下节课再讲。允许将参数值传递到方法中。列举的元素由逗号分开,而每一个元素包含一个类型和一个标识符。不理解形参、实参,你是理解不了方法的。

Throws 异常列表:throws子句导致一个运行时错误或异常被报告到调用的方法中,以便以合适的方式处理它。异常在后面的课程中介绍

花括号内是方法体,即方法的具体语句序列。

形参

比如我们之前讲过的车的例子,车能跑,我们就定义这个方法

Run(给油,有人驾驶)

什么叫做参数呢?当你去定义这个方法的时候,比如让车能跑起来,我们就需要给车原材料,给它一个初始的值,这个就叫做参数。

比如人会做饭,那么要有原材料,比如油盐酱醋等,功能是做饭,返回值是饭菜,就是结果。

饭菜 做饭(做饭的原材料),饭菜就是返回值,做饭是功能,原材料就是参数。

速度 run  (给油,有人驾驶){

具体运行

}

我们定义:

Public double run(double oil,boolean drive){

}

Public int cook(double rice){

}

这就是定义方法,这就是从具体到抽象

接下来我们看的就是参数列表

什么是形参?

形参:就是形式参数的意思。是在定义方法名的时候使用的参数,用来标识方法接收的参数类型,在调用方法时传入。定义它有什么作用呢?直白点说就是占位,就是把这个位子先占上,占位有什么作用呢?第一就是标识这个参数的类型,就是告诉系统我需要这个,比如车要跑需要油,但是有没有说需要多少油啊,没有吧,就是说有油才能跑,有人驾驶才能跑,具体多少油,具体谁来驾驶没有说吧,所以称它为形式参数。

什么是实参?

实参:就是实际参数的意思。直白点说,就是在调用方法时传递给该方法的实际参数。也举个例子。

Public static void main(String [] args){

   System.out.println(“ok”);

   T1 t = new T1();

   t.run(5,true);

}

这里面5代表5升油,true代表是有人驾驶。

5和true就是实参,就是运行的时候实际往里传的参数就是实参

基本规则:

(1)  形参和实参的类型必需要一致,或者符合隐含转换规则。

(2)  形参类型不是引用类型时,在调用该方法时,是按值传递的。在该方法运行时,形参和实参是不同的变量,它们在内存中位于不同的位置,形参将实参的值复制一份,在该方法运行结束的时候形参被释放,而实参内容不会改变。

(3):形参类型是引用类型时,在调用该方法时,是按引用传递的。运行时,传给方法的是实参的地址,在方法体内部使用的也是实参的地址,即使用的就是实参本身对应的内存空间。所以在函数体内部可以改变实参的值。

Package chap3;

Public class Car{//车这个类

PrivateString make;

Private inttyre;

Private intwheel;

Public car(){

     Make =”bmw”;

     Tyre= 4;

     Wheel =1;

}

Public doublerun(int oil,boolean drive)

  Return 200.0

}

}

 

最后再讲一个东西,就是参数可变的方法

Java类的基本构成——参数可变的方法

从 JDK5.0 开始,提供了参数可变的方法。

当不能确定一个方法的入口参数的个数时,5.0以前版本的Java 中,如果要传递多个同类型的值时,如

Public intt1(int a,int b,int c,int d)

通常的做法是将多个参数放在一个数组或者对象集合中作为参数来传递,5.0 版本以前的写法是:

intsum(Integer[] numbers){…}

public intt1(integer … abc){

return 8

}

在方法中可以写成

Public classT1{

  Public String s = “123”;

  Public static void main(String[] args){

     System.out.println(“ok”);

     T1 t = new T1();

     t.t1(1);

     t.t1(1,2)

     t.t1(1,2,3);

}

//在别处调用该方法

sum(newInteger[] {12,13,20});

而在5.0 版本中可以写为:

intsum(Integer... numbers){//方法内的操作}

注意:方法定义中是三个点

//在别处调用该方法

sum(12,13,20);//正确

sum(10,11);//正确

也就是说,传入参数的个数并不确定。但请注意:传入参数的类型必须是一致的,究其本质,就是一个数组。

显然,JDK5.0 版本的写法更为简易,也更为直观,尤其是方法的调用语句,不仅简化

很多,而且更符合通常的思维方式,更易于理解。

好,到现在为止,我们终于把Java的类写出来了,下面学习如何使用java类。刚才大家一直看我在说看我再写,但是大家实际上还不知道怎么用java类。那么怎么用呢?学会写再学会用就ok了。

new 关键字?

前面定义了一个车的类,那么怎么来使用这个类呢:在java里头对象必须先初始化,然后呢?必须要new来做这项工作。在你可以使用变量之前,实际内存必须被分配。这个工作是通过使用关键字new来实现的。如下:

Car car;//声明一个car的变量

car = newCar();//赋值,先New一个car,然后赋值给它。即分配给它具体的内存。

这里new关键字有两个具体的作用,大家记一下:

第一个作用:就是初始化对象;第二个作用即为这个对象分配内存空间。这就是New关键字的作用。那么有这两点过后,大家看一下,在这儿分析一下,这两条语句实际什么含义呢?

首先是先定义了一个car,也就是声明了一个变量,这时候有对象没有呢?没有,只是声明。然后再调new Car(),先运行new Car(),new Car()运行完,大家注意一下,new Car()是一个什么运行机制呢?它运行时候,它的顺序是怎么样的呢,我现在告诉大家,new Car()是这么运行的,new Car()先创建一个内存空间,把这个类实例创建出来,然后再去回调这个构造方法。分配内存空间来干什么呢?放什么?放实例是不对的,应该放的是实例的属性的值。也就是属性的值放在New的空间里。不同的对象值也是不一样,但是结构是一样的。

画图演示:

 

    car变量       =          newCar()

       内存分配示意图

第一个语句(声明)仅为引用分配了足够的空间,而第二个语句则通过调用对象的构造方法为构成Car类的属性分配了空间。对象的赋值使变量car重新正确的引用新的对象。这两个操作完成后,Car对象的内容则可通过car 进行访问。

new意味着内存的分配和初始化,new调用的方法就是类的构造方法。也可以使用一个语句为引用car和由引用car所指的对象分配空间。即 Car car = new Car();

所以new关键字的功能:

1、        初始化对象实例

2、        为对象实例分配内存空间;

你要想使用一个车,使用的是虚幻的车还是真实的车呢,当然是真实的车了。

 

如何使用对象中的属性和方法呢?

要调用对象中的属性和方法,使用.操作符,如下:

car.make=”BMW”;

car.tyre=4;

car.wheel=1;

.操作符的权限是最高的,先运行。

this 关键字:用来指向当前对象或类实例的。怎么用呢

this关键字的功能

1、        点取成员

就是在编写类的时候,通过使用this来点取类的属性。java编程语言自动将所有实例变量和方法引用与this关键字联系在一起,因此,使用关键字在某些情况下是多余的,也就是说写不写this都是一样的。举个例子:

public classT1{

   public String s = "123";

  

   public static void main(String[] args){

      System.out.println("ok");

 

      T1 t = new T1();

     

 

 

      t.run(5,true);

      t.t1(1);

      t.t1(1,2);

      t.t1(1,2,3);

   }

   public int a;

   public int t1(Integer ... abc){

  

      return 8;

   }

   public int b=4;

 

   public doublerun(double oil,boolean drive){

      System.out.println("a=="+this.a);//也可以写成"a=="+this.a

 

      ruturn 0.0

   }

}

2、区分同名变量

   就是在类属性上定义的变量和方法内部定义的变量相同的时候,到底是调用谁呢?如果使用this 就表示调用属性,否则就调用方法内部的变量。举个例子,接上例

public doublerun(double oil,boolean drive){

   int a= 5;

System.out.println("a=="+a);//输出a==5,如果写成this.a,则输出结果为a==0。原因就是this代表的是当前实例,this.a表示当前实例的一个属性。

 

      ruturn 0.0

   }

3、作为方法名来初始化对象

   public classTest{

      publicTest(){

        this(3);//在这里调用本类的另外的构造方法

      }

      public Test(inta){}

   }

接上例:

public class T1{

   public String s= "123";

  

   public T1(){

      this(3);//在这个构造方法中调用下一个构造方法

   }

   publicT1(int a){

   a = a;

   }//如果这样写,输出结果是3吗?应该是0,要想输出3,必须写成this.a =a,因为不加this,这个a就代表离他最近的那个变量,加上this,就表示引用这个类的a这个属性。

  

  

   public staticvoid main(String[] args){

      System.out.println("ok");

 

      T1 t = newT1();

     

 

 

      t.run(5,true);

      t.t1(1);

      t.t1(1,2);

      t.t1(1,2,3);

   }

   public int a;

   public intt1(Integer ... abc){

  

      return 8;

   }

   public int b=4;

 

   public doublerun(double oil,boolean drive){

      System.out.println("a=="+a);//也可以写成"a=="+this.a

 

      ruturn 0.0

   }

}

到这里,如何使用类也就差不多了。使用类其实是使用类的实例。下面我们就来看到底这个程序是如何运行起来的。

java类的基本运行顺序

这个对于Java程序员来讲是必备的一个知识点。这是什么概念呢?就是我们现在甭管这个程序大、小,你每写一个程序,同学们,你一定要搞清楚,以后有没有虚拟机呢?你应该当成是没有虚拟机,你的脑袋就是虚拟机,也就是说,这个程序写完,谁先跑,谁后跑,怎么跑,你必须在你的脑袋里面给它盘算出来,你应该知道它是怎么走的,那么这里我要告诉大家,作为程序员,这就叫做可控,这个大家不要觉得真搞笑,直接运行一下不就完了,如果你要抱这种思想,我告诉你,你走不了多远,你的水平不会有提高,为什么呢?做程序现在要讲究一个控制力,意思就是写了一段代码,你要能够保证它是正确的,能够完成你的功能,那么你要能够掌控它,做一个类是这样,慢慢地,做十个类八个类,甚至于做一个小工程也是这样,你能掌控一个小工程,慢慢你就能掌控一个大工程。你要把整个工程都要处于你的掌控之下,而不要出现失控的状况,所以说,现在刚开始做,我们就要开始练,你只有现在能够掌控小的东西,以后才能掌控大的东西,你不能把自己定位为永远只做程序员吧,你要开始慢慢做项目经理,做设计,越往上,它就越虚,你做设计,它不写代码,那么你怎么保证你的设计是对的,你是不是要有足够的经验和能力才能保证你做的设计,人家照你的设计写下来是对的,那时候就不要你写代码了,但事实上,这个能力就是你平时要锻炼的,如果要做到项目经理的话,那就更多了,你整个工程,从怎么开始配置,到最后怎么部署运行都要掌控,这个东西就是这样的,所以说训练就是要从小开始,小的不愿做,大的做不来,这是最可怕,所以我也不希望你们出现这种想法,这个简单,不用搞,都能掌控,一到掌控不了啦,他倒是想来练,发现不行了,这个东西就是这样,来,现在看看这个类是怎么运行的。我们通过一个例子来给大家讲讲,

第1行public class Test{

   第2行private String name ="java 私塾";

   第3行private int age = 2;

   第4行public Test(){

      第5行age = 1000;//期望能到1000年

   第6行}

   第7行public static void main(String[] args){

      第8行Test t = new Test();

      第9行System.out.println(t.name+"的年龄是"+t.age+"年");

   第10行}

第11行}

将程序放到画图里来讲。再举个例子说明运行顺序。

这个程序第一句,首先运行到第七行,肯定是这样,为什么?大家看一下,main方法是不是固定的,相当于程序运行的和虚拟机的约定,虚拟机在跑你这个类的时候,main方法是不是虚拟机来调,固定的上来就找你这个public static voidmain(),就是固定调这个方法,从这个角度这个main方法按照我们前面讲的,它是不是也是一个回调方法啊。它其实不是主要不是我们来调,是你运行的时候虚拟机来调这个方法,然后就进来了,就到第8行了,第8行谁先运行?这是一个右结合的表达式,它一定是new Test();然后new Test()完了才进行赋值操作,明白这个概念吧,这个赋值符号是不是右结合的,先把右边算完了然后再赋值,好,先运行new Test(),这个时候,程序就跑到第4行了,就是调用Test构造方法去了,到了这里,java是怎么走的呢?它不是马上就往里走到第5行,有些同学肯定想,都调到这个方法了,还不往里走吗?不是,那么大家注意一下,new Test()这个如果用内存分配图来画的话,大家就知道,到了这一步,实际上只是系统分配了一个内存空间,里面是空的,Test()它不会进去,紧接着它先调第2行,这就是我们之前讲的,初始化一个类,必先初始化属性,也就是说age = 1000不是这时候运行,它运行完第4行,就到第2行,也就是空间里就填充了name = “java 私塾”,再运行第3行,空间里就出现age =2,所以说,等你把所有的属性都初始化完,构造方法调用没有?没有,构造方法还没有运行,那么new出来的内存空间里属性就放上去了,然后等你第3行运行完了再运行第5行,这个时候,age就会修改成age= 1000,第5行走完,然后走第6行,好,这个时候,内存空间里就是name=”java私塾”,age =1000,我们回忆一下前面讲的new关键字,你new 完了,是一个内存空间,里面有属性及其值,它的过程是什么样呢,当你调构造方法的时候,实际上只分配了一个内存空间,没有值,然后,初始化,那么初始化一个类,必先初始化它的属性,好,初始化一个属性,在内存空间里就写上一个,初始化一个就写一个,所以说,等你初始化完了,空间里的属性也就出来了,然后才去真正的调构造方法,那么大家想一想,调构造方法的时候,内存里的实例有还是没有,就是这个实例造出来了没有,就是真正执行构造方法里面的时候,这个实例出来了没有,出来了,所以我给大家讲的,为什么很多人在这个地方,他其实不懂这个运行顺序,都觉得这个构造方法就是调这个方法,构造一个实例,表面上好像没错,但实际上,这个构造方法严格上讲,只是一个回调方法,应该是等实例初始化了过后,然后给你个机会说你还想干点什么吗?你想干点什么就在这里头写,你不想干什么,你就不写,你有没有发现一个问题,你不写,是不是类实例照样有啊,为什么呢?不是说因为你写了代码才有类实例,而是类实例人家本来就创建好了,然后才调这个方法啊,明白这个道理吗?所以我们刚才讲它是回调方法,就是这个道理,构造方法你什么都不写,类实例照样创建,如果真的是是像有人想的一样,我调第5行来构造类实例,那完蛋了,你一句话都没写,那人家怎么构建呢?所以说,通常情况下,大家想一想,道理弄明白了,构造方法里面应该写些什么是不是应该很清楚了,直白点说就是什么,就是当这个类初始化的时候,你想干什么,就写在构造方法里,比方说,这个类初始化的时候,我想设置点初始值,或者类初始化的时候,我想输出一条语句,就写在构造方法里头,其实说白了吧,它不好意思把你完全撇开,人家把事情办完了,再回过来问问你,你还有什么要写的没,要是还有的话,你就写这儿吧,你要不写的话,它是不是就不管你了,不写,它就直接跑了对吧,这个大家看到,这个运行顺序就是这样,好,总结,再回头说一遍,先走第7行,然后到第8行,第8行后半截就蹦到第2行,然后第3行,第4行,第5、6行,第6行完了以后就回到哪里呢?就回到第8行,然后来做赋值,赋值给t,这个实际上就结合刚才讲的new关键字,第8行走完了,然后再走第9行,这个时候t.age=1000了,好,下面我稍微改一改,就拿T1类来练吧

public class T1{

   public String s= "123";

   {

      System.out.println("111=="+s);    //先不加块的写,问能不能这么写,对还是不对,肯定不对,为什么?这句话是一个语句,语句只能放在块内,有同学会说,我不是放在了类的那个块内了吗,大家注意下,你不能放在类体里面的,你只能放在内部块或者方法里面,那有同学说,为什么上一句话就可以呢,它是属性的声明,是声明和赋值语句,而这句话是功能语句,java里面功能语句必需放在块里头,为了保证它能放在这里,我就加上{},这样就对了。不要看的怪异,但是代码是对的,这就叫做属性块,和属性放在一起,说明他的地位和属性一样,既然和属性一样了,那么属性运行的时候,他也就运行了,如果运行的话,输出应该是什么呢?先输出ok,然后呢,我们分析一下,然后到new T1(),转到属性,输出111=123,他执行完了,然后到构造方法里,输出s===123,然后回到T1 t = new T1(),最后输出t.s==333。对不对呢,跑一下。今后大家就要像这样,也就是说你写完一个小的程序,现在小的时候你就要这么练,就是什么呢,写完了过后,你不要先编译运行,而是凭自己的分析,它应该怎么运行,推出来它是什么结果,实际上这就是培养自己写代码的能力,那么写代码的能力,不是说你看我能够给它做出来,而是你脑子里想要完成什么,那么做出来你想要完成什么功能,它就能做出什么,就是让他忠实你的意愿执行什么,然后再编译运行。就跟我们想的一模一样,这就对了。现在我们就知道程序大概怎么跑起来的了。

   }

   public T1(){

      System.out.println("s=="+s);      //第二个写

      this.s="333";                   //先写

   }

   public staticvoid main(String[] args){

      System.out.println("ok");

      T1 t = newT1();

      System.out.println("t.s=="+t.s);    //第三写

   }

}

到现在,我们就完成了3个问题,第一,怎么写这个类,第二,怎么用这个类,第三, 不但知道怎么用,而且知道它是怎么跑起来的。就像虚拟机一样,如果你每个程序写出来都像这样知道它是怎么跑的,这么门清的话,这个程序就算出了错,你要去调试,也就简单了,我就知道大概在什么地方可能有问题了。好,这个是java类的三个问题,主干也就差不多了。这儿我们说明一下,这里只是说明了一个基本的运行过程,java程序并不是只有这些,还有很多其他的东西,并不是完整的,我们的小程序基本没有问题了。没有考虑更多复杂的情况。

Package、import、public等4个、class、this、void、static、new,学就是这样,你就不要专门去学,学到哪儿记到哪儿,没有学到你记了也没有什么用,好,最后我们再学习两个东西,一个是变量,一个是类的三大特性。

再谈变量

刚才我们已经看到了,有些变量是定义到属性上了,有些变量定义到方法、类里的,这就是实例变量和局部变量。什么叫做实例变量呢?

实例变量:实例变量其实就是类的属性。

它们在使用new ***()创建一个对象时被分配内存空间。每当创建一个对象时,系统就为该类的所有实例变量分配存储空间,创建多个对象就有多份实例变量,当然了,一个空间是不是就有一份儿啊。通过对象的引用就可以访问实例变量。

局部变量:在方法内部(在块内定义算不算呢?也算局部变量)或块内定义的变量或方法的参数被称为局部(local)变量,有时也被称为自动(automatic)、临时(temporary)或栈(stack)变量。至于叫什么名字无所谓,关键是这地方需要大家理解的是有几点:

1、局部变量它的作用域,后面我马上就会讲到,作用域就是作用范围。在这儿先简单说一下,就是这个作用域,局部变量的作用域就是包含它的块,比如说

public class T1{

   public String s= "123";//属性的访问范围是整个类的所有地方都能访问

   {

      int a = 0;//局部变量

System.out.println("111=="+s);

   }//如果将该块放在s定义前面就不对了,初始化属性的时候是从上到下的,如果放在前面,那么s还没有被定义,因此编译出错。

   public T1(){

      System.out.println("s=="+s);

      this.s="333";

   }

   public staticvoid main(String[] args){

      System.out.println("ok");

      T1 t = newT1();

      System.out.println("t.s=="+t.s);

   }

}

局部变量就是块内才能访问,而属性是整个类都能访问,属于全局变量,这个地方又有一个问题,如果块内又有块呢?接上例

{

      int a = 0;//局部变量

System.out.println("111=="+s);

{

   //这个块还是可以访问变量a的,当然更能访问s

}

   }

局部变量除了方法内和块内,还有什么地方可以算局部变量呢?就是方法的参数。方法的参数实际上就是定义在方法的定义那句话的,所以它也是局部变量。都是局部变量。最后再看一点,刚才大家看到了,局部变量和属性(全局变量、实例变量)可不可以名称相同,可不可以类型和名称都相同,可以的。再给大家写两个,顺手写,大家看看这句话合法吗?

{

      int s = 0;//局部变量

System.out.println("111=="+s);

}

合法的,虽然同样一个s,它的类型不同,但是一般情况下,我们一般不会这样写,如果让人迷惑,我们通常可以写成:

String s = “3333”;

用这个本地变量去覆盖属性,但是刚才那样写并不是不可以,事实摆在眼前啊,编译通过了嘛,但是虽然合法,但是程序员一般不会这么写的,太怪异了,我只是刚才想到了,就随手写了。好,那么像这种问题,今后大家怎么练呢?我不可能把所有可能的情况全部给大家演示到吧,那么这个时候,大家看书啊或者看到某个问题的时候呢,就是先去想,想完过后你去运行,去推,推完了以后记着整个程序试着跑一下,来验证一下你的想法,这种实际就是略微带点试验性质的或者说略微带点小小研究性质的学习,这个对于学Java,学IT,学软件开发这是一个很好的能力,我把各种情况都来试试,看看怎么样对怎么样不对,说白了就是尝试啊,然后谁说了算,当然最终运行结果了,它会告诉你对还是不对,像这样,它就有助于大家把每个概念理解的非常透彻,而不是表面的东西,好,下面再看一个实例变量和局部变量的一个示例,看看这个例子,

public class Test{

   private int I;//Test类的实例变量

   public intfirstMethod(){

      int j = 1;//局部变量

      //这里能够访问i和j

      System.out.println(“firstMethod中 i = ”+i+”,j=”+j);

      return 1;

   }//firstMethod()方法结束

   public intsecondMethod(float f){//method parameter

      int j =2;//局部变量,跟firstMethod中的j是不同的

      //这个j的范围是限制在secondMethod()中的

      //在这个地方,可以同时访问i,j,f

      System.out.println(“secondMethod中 i=”+i+”,j=”+j+”,f=”+f);

      return 2;

   }

   public staticvoid main(String[] args){

      Test t = newTest();

      t.firstMethod();

      t.secondMethod(3);

   }

}

变量初始化

在java程序中,任何变量都必须经过初始化后才能被使用。当一个对象被创建时,实例变量在分配内存空间时按程序员指定的初始化值赋值,否则系统将按下列默认值进行初始化:

byte

0

short

0

int

0

Long

0L

float

0.0f

double

0.0d

char

‘\u0000’

boolean

False

所有引用类型

null

好,说到这个地方,可以讲一下null是什么意思了,好,大家注意一下,我们前面讲的new关键字是怎么讲的,我们前面用了一个内存分配示意图,那个连线意思就是将初始值赋值给car变量,那么知道什么叫null吗?如果提前写一个car = null,它的意思就是把这个变量和这个内存空间之间的联系给它断掉,这就叫做null,如果把连线去掉,那么右边空间就是独立空间了,它里的内容就没有人用了,分配的内存空间没人用,那就是垃圾,那么垃圾回收机制就起作用了,那么这里面的东西就被干掉了。所以null关键字表达的含义就是把变量和内存空间之间的关系断掉,那么在程序里该怎么写啊,比如t我们用完了,我想给它清掉,那么我们可以在程序最后加上一行t= null;

public class T1{

   public String s= "123";

   {

      System.out.println("111=="+s);

   }

   public T1(){

      System.out.println("s=="+s);

      this.s="333";

   }

   public staticvoid main(String[] args){

      System.out.println("ok");

      T1 t = newT1();

      System.out.println("t.s=="+t.s);

      t =null;//表示t用完了,可以清掉了,就是把null值赋给t就可以了。

   }

}

说明:

1、一个具有null的值的引用不引用任何对象。试图使用它引用的对象将会引起一个异常。

2、实例变量能够自动初始化。局部变量必须在使用之前“手工”做初始化。如果编译器能够确认一个变量在初始化之前可能被使的情形,编译器将报错。如:

public class Test{

   private int i;//Test类的实例变量

   public voidtest1(){

      int x =(int)(Math.random() * 100);

      int y;

      int z;

      if ( x >50){

        y = 9;//这里不会出错,为什么呢?实际上就是做初始化,给y赋值

      }

      z = y + x;//将会引起错误,错误不是z,而是y可能还没有被初始化就使用了,因为这取决于上面的条件判断,如果x>50,那么y才会被赋值,如果x<=50,那么y就不会被赋值,也就不会被初始化,所以就还没被初始化就被使用了。

   }

}

y = 9,y放在=左边,就叫做赋值

z=y+x,y放在右边,就是使用y。

变量的范围:

java变量的范围有四个级别:类级、对象实例级、方法级、块级

类级变量又称全局级变量,在对象产生之前就已经存在,就是后面会学到的static变量。这个我们在前面还没有讲到。

对象实例级,就是前面学到的实例变量,它和类级变量都是类的属性,差别就在于一个带static修饰,一个不带。

方法级:就是在方法内部定义的变量,就是前面学到的局部变量

块级:就是定义在一个块内部的变量,变量的生存周期就是这个块,出了这个块就消失了。

访问说明:

1、        方法内部除了能访问方法级的变量,还可以访问类级和实例级的变量

2、        块内部能够访问类级、实例级变量,如果块被包含在方法内部,还可以访问方法级变量

3、        变量当然是要在被访问前被定义和初始化,不能访问后面才定义的变量。

最后再写个小例子大家可以看一下

public class Test{

   private staticString name = “中国人民”;//类级

   private int i;//对象实例级,Test类的实例变量

   {//属性块,在类初始化属性时候运行

      int j = 2;//块级

   }

   public voidtest1(){

      int j = 3;//方法级

      if(j==3){

        int k =5;//块级

      }

      //这里不能访问块级的变量,块级变量只能在块内部访问

      System.out.println(“name=”+name+”,i=”+i+”,j=”+j);

   }

   public staticvoid main(String[] args){

      Test t = newTest();

      t.test1();

   }

}

好了,到现在为止,我们终于把类怎么写、怎么用、怎么跑基本这套路子看下来了啊,其实思路还是很简单的,第三章就围绕着这些东西在讲。

好了,最后来看看面向对象三大特征

封装

第一个问题:什么叫封装呢?

什么是封装?

封装这个词听起来好像是将什么东西包裹起来不要别人看见一样,就好像是把东西装进箱子里面,这样别人就不知道箱子里面装的是什么东西了。其实java中的封装这个概念也就和这个是差不多的意思。就是把很多很多的数据包装起来,不让你看到,那么大家想想,我们前面写的类有没有封装的体现,哪些算是封装的体现,大家想想。其实很多,比方说我们写的类的属性,为什么这个属性放在这个类里而不放在别的地方呢,这个属性是不是就等于被封装在这个类里头了,算不算封装啊,当然算了,其实类就是把一堆的属性还有一堆的方法包在一块,这个东西就叫做封装。也就是类包含属性,包含方法,本身这个行为就叫做封装。这是第一个体现,封装其实还有一个体现,还有什么体现呢?最典型的就是权限,大家想想,我包完了过后,比如private,private什么意思,私有的,就是我不让别人看见,只有本类才能使用,这也是封装的体现啊,所以权限实际上就是隔离级别,private的隔离级别很高,而public隔离级别很低,总之,封装在前面我们写的程序里面两大体现,一个是类的属性和方法被包在一起,第二个就是权限。

java中的封装:封装是java面向对象的特点的表现,封装是一种信息隐蔽技术。

它有两个含义:一是把对象的全部属性和全部功能结合在一起,形成一个不可分割的独立单位;二是尽可能隐藏对象的内部结构,实际上就相当于private。

也就是说,如果我们使用了封装技术的话,别人只能用我们作出来的东西而看不见我们做的这个东西的内部结构了。

大家想想,封装有什么好处呢?其实很简单,就是能够让别人能够享受到这个封装类的功能,就是能使用这个封装类的功能,但是她需不需要关心这个封装类的内部结构,她是不需要的,所以说好处就是你可以使用功能而又不用去关心实现细节,是这个道理吧,那有同学说,你就让我关心关心又怎么了呢,大家想想看啊,如果一个类让你去用,让你去关心它的实现细节,这会造成一个什么样的麻烦呢,对你来讲,麻不麻烦,对使用者来讲,麻不麻烦,你去用一个类,现在又让你关心它的细节,你麻不麻烦,当然麻烦了,这就意味着,本来原来你只需要知道个大概就行了,现在是不是具体的细节你都要知道啊,所以说对使用者来讲麻烦,那么对于开发者来讲,麻不麻烦,也麻烦,他一旦把细节暴露给你了,就会出现什么情况呢,一旦当开发者需要进行改动的时候,就需要两边都要知道怎么改,使用者也要知道怎么改,如果他不改,那就乱套了,所以说封装的好处就是:

封装的功能:

1、隐藏对象的实现细节

   数据隐藏通常指的就是封装。它将对象的外部界面与对象的实现区分开来,隐藏实现细节。隐藏实现细节靠什么来隐藏啊,就是靠权限。说白了就是不让你看到内容是怎么实现的,那么这么做有什么好处呢,可以看第二点(接2)

2、使代码更好维护

   因为别人不知道你怎么实现的,他只知道这个大概,就是他只知道这个方法的定义,而不知道这个方法的内部是怎么实现,这就意味着以后如果我想到好的算法,我是不是可以随时改啊,因为我就算改了,对你来讲,你什么也不知道,跟你没有什么关系,比如还拿车子这个东西来说,车对于我们普通消费者使用者来讲,是不是封装了的,我们关不关心比如说它的发动机怎么造的,方向盘怎么造的我们管不管,我们不管,我们只知道给我车,我能开起来就行了,那么这些真正的实现细节是不是只有工厂才知道啊,工厂它会告诉你它是怎么做这件事情的吗,当然不会了,人家管这个东西叫商业机密,是不是这个概念啊,其实这就是一个封装,也就是说这些事情我们工厂知道就可以了,你不需要知道,你只管拿着用就好了,那么这样就会造成一个什么现象呢,比方说,这一款车和下一款车,它把发动机变了,对你来讲有没有影响,没有什么太大影响,你不知道,除非它明确告诉你,这个东西变掉了,否则的话,你根本不知道它变了,封装能使代码更好的维护就是这样,比方说我们写段代码,我不告诉你我怎么写的,反正你知道这个功能就行了,那么这样有什么好处呢,我里头的代码我就可以随时改,这样的话,我一个人就可以做主了,反正我写的我就可以改,这样就使得代码可以更好的维护,程序开发都是这样一个特点,不会把细节暴露出来,一句话,知道的人越多,那么你就越麻烦,你就越不能改,为什么,因为你改了,就要通知所有的人,对不对,道理就是这样,好,这就是第二个好处,下面我们看第三点

3、迫使用户去使用一个界面访问数据,这个地方要先给大家说说这个界面二字,界面二字不好理解,可能有的同学都会觉得界面不就是图形界面嘛,就是一个让人操作的地方,都认为这个就是界面,那么你就理解错了,现在我们这里所说的界面指的是一个设计概念,设计概念就是什么啊,就是你去看任何一个东西,你所能够看到的或者你能得到的就叫界面,这有时候就是一个抽象的概念,而不是图形化的,比方说,我们去看一个类,这个类公布给你的界面就是什么呢?举个例子来说吧。

public class Test{

   public String s= “”;

   private int a =0;

   public int t1(){

      return 1;

   }

   private voidt2(){

   }

}

像上面这个类有没有界面,有,那它的界面是什么?就是所有从你这个地方能够访问的所有的属性或者方法,就是所有你能看到的就是界面,那么大家想想,比方说,我要从另外一个类里头,不如说,我们在T1.java里头,从它的眼光看这个Test类,那么它看到的Test是什么样子的呢?所以说这个界面是指的就是这个意思,它能看到什么?第一,它能看到这个类名叫Test,第二,能看到有个String叫s,即String s,第三,它能看到有个方法叫t1,而且它能返回一个int类型的值,即public int t1(),别的它还能不能看到,别的它就不能看到了,为什么,因为另一个属性和另一个方法是私有的,那么这个时候,大家看看,什么叫做界面呢,这个东西就叫做界面,就是我从一个类里来看另一个类,我所能够看到的能访问到的,组合起来,从T1的角度,Test的界面就是什么,就是

Test

String s

public int t1()

这个样子,明白这个意思吗,这就叫界面,好,回过头来再来理解这句话,迫使用户去使用一个界面访问数据,由于它封装了,那么封装了过后,对T1这个类里头去使用Test,我关不关心它t1()方法怎么实现的啊,我不关心,我只关心我能不能调t1(),我能调就完了,至于怎么实现那是Test的事情,所以它说强迫用户去使用一个界面访问数据,那么这个时候我们就通过这个界面只能访问这个功能,这就是封装体现的一个功能,这样的话,你看,直接就体现了两点,一,实现细节不知道,二,你只能通过它去访问,第三呢,代码如何实现的我们就不管了,我们写的这个例子应该就能体现出这么几点来,在T1类里,看不出实现细节,我只能看到这个界面,然后我只管操作界面,t1()方法的代码你可以在Test里随便改,你爱咋地咋的,反正我不知道,我也不想知道,

所谓一个界面就像是房间的门一样,你应该通过门进入房间。

   封装迫使用户通过方法访问数据能保护对象的数据不被误修改,还能使对象的重用变的简单。

好,这个东西就叫做封装,能理解了吧,事实上,封装在面向对象里面,前面之所以没有先讲面向对象的特点,原因就在于,如果刚开始讲,是空对空,纯理论的,大家不见得能理解,等我们会写类了,反过来看,我们就容易理解了,好,这就是封装的特点,接下来再看第二个特征

面向对象三大特征——继承

前面其实我们也简单写到了一个,T2继承T1,在讲protected的时候用到的。先看看什么是继承

什么是继承?

大家平时听的最多的就是子承父业,还有继承父辈的财产或者债务,别光想着财产,有责任也有义务,所以继承就是从父辈那儿把东西接下来就完了啊,看看java里头继承什么概念

继承是面向对象软件技术当中的一个概念。如果一个对象A 继承自另一个对象B,我们就把这个A称为“B的子对象”,而把B称为“A的父对象”。

像这种从一个对象派生出子对象,就会让子对象可以拥有父对象的属性和功能,这种现象就称为对象的继承。A继承了B,A就具有了B的功能和属性。那么大家想想,这有什么好处呢?最大的好处就是节省代码,对吧,用两个字来形容,就叫做复用,重复使用。什么意思呢,就是说,比方说,刚开始没有继承的时候,比如我们讲提炼车的功能,我们发现提炼出来的车的功能是公共的吧,对不对,我们又提到一点,比如宝马车有自己的特点,奔驰车也有它自己的特点,他们两个可能各有特色,那么就出现什么呢?就是他们既有公共的功能,又有自己的特点,那么这个时候怎么办,如果说宝马车,我从头到尾全部定义一遍,那奔驰车又从头到尾定义一遍,你就会发现他们两可能有80%的功能是一样的,但是肯定也有差异吧,如果完全没有差异的话,那就分不出来了,不同品牌的车它们都有各自的特点,所以这个地方我该怎么办,我们就把那80%的就提炼出来公用,只做一份就好了,你总不能一辆车我就做一份,那重复的就太多了,所以我们怎么干呢,就把公共提炼出来的部分作为父类——车,宝马车去继承车,奔驰车也去继承车,这样一来,他们就都拥有了这个公共的功能,这就是继承。接着我们看继承的功能

继承的功能?

继承可以使得子对象具有父对象的各种属性和方法,这个地方虽然说有各种属性和方法,但前提是,子类要能看得到,什么概念,如果说父类都是一个私有的属性,子类能不能继承,从程序本身来讲,子类是继承不了的,什么意思呢?我们讲能够继承是指子类能够直接操作和访问的东西,那有同学说,子类可以调父类的方法,通过父类去操作,其实这个东西是不是还是只有父类有啊,所以说如果它是私有的,子类是继承不了的,子类只能继承哪些?简单的说就是能够访问的,有哪些是能够访问的呢?就是protected、default、public的,这里举个例子来说,当然这个继承呢,后面我们还要具体去讲,这个地方先简单说一下。例子:

public class T1 extends Test{

   private void test(){//自定一个方法,先写

      t2();//接下面部分,也可以写成this.t2();和调属性是一样的。

   }

   private voidt2(){//后写,如果要在test()方法里调t2方法,该怎么调呢,可以直接调用,接到上面的test中。

   }

   public staticvoid main(String[] args){//在这里要调test方法,应该先new一个对象,为什么不能直接调呢?因为方法定义的时候有个static,static后面我们再来说

      T1 t = newT1();

      t.test();

   }

}

我们看到上例它是继承了,然后我们在这里做个示例,你看在Test.java中是不是有个private int a = 0 ;如果说我在T1中,

   private voidtest(){

      System.out.println(“s==”+this.s);//对不对,回到Test中,看到s是不是public的,是不是被继承下来的啊,先编译执行一下,今天只是大概介绍一下,后面extents我们再慢慢去学。输出s==,为什么是空的呢,是不是因为在Test中s=””,没有值,赋予一个值,就有值了,但是private int a = 0,同样道理,同样是属性,我们将s替换成a就不对了,编译以后,我们就看到不能访问了。这就意味着T1就没有把a继承下来,所以说继承如果是private的,那么也无法访问的。说白了,捞不着访问就是你没有继承下来的东西,也就是说这是父类独有的不能给你继承的,比方说你父亲画画画的很好,这个是他的一个能力,他个人独有的,也许他是大师级的,到你这儿就是小孩级别的了。这是很正常的。

这里还有两个概念,大家理解一下。

is-a的关系

什么是什么,比方说a is b,意思就是a是b,比方说“我是一个人”,人就是一个抽象的对象,我表示一个具体的实体,一个具体的实体对象,如果把我看成一个类的话,就是我这个类就继承人这个类,表达的就是is a 的关系,比方说宝马车是车,奔驰车也是车,如果从对象上来讲,可以有如下三个对象,Car、BMWCar、BenzCar。很明显BenzCar和BMWCar都是Car,都继承自Car。所以可以说BMWCar is a Car,BenzCar is a Car。Car就是一个父类,BMWCar和BenzCar就是子类。这就是is – a ,大家看到is –a 描述的就是父子类,有继承关系的叫做is-a。那么什么叫has –a呢

has-a的关系

从英语来讲,has是拥有的意思,has-a就是拥有什么,那么事实上,has-a就是表示一个类拥有什么属性,比方说,人有两只胳膊两条腿,这就叫做has-a,这个对象拥有什么属性和功能,但是一般来讲,has拥有的不提方法,一般都是提的是属性,在java里面has-a 是用来描述对象组合关系(一个人有一个脑袋两只胳膊两条腿,哪个都不能 少)比如说宝马车有一个方向盘,奔驰车也有一个方向盘,而宝马车和奔驰车的方向盘是不一样的,那么这种对象拥有属性或者其他对象的这种关系就用has-a来描述。
好,继承也差不多了,下面我们看最后一个特征,多态性

面向对象三大特征——多态

态表示形态、姿态,各种各样的表现形式。

什么是多态?

同一行为的多种不同表达或者同一行为的多种不同实现就叫做多态。

可能光看这个有点拗口,我具体点来说,比方还拿车这个例子来说,我们笼统说车这个对象,那么这个时候,多态就是说车这个对象它有很多不同的表达,比方它可以表达成宝马,可以表达成奔驰,这就意味着,同一种东西,具有不同的表达或者不同的实现方式,描述的就是这种关系,这种现象总体就叫做多态,归根结底,他们都叫做车。

按照刚才这种描述的话,我们就可以形成一个什么关系呢?刚才我们说BMWCar is a car,反过来可以怎么说呢?就是车可以是宝马,或者车可以是奔驰,就是车的不同的表现形式。

板书:public class Car{

      }

      public classBMW extends Car{}

      public classBenz extends Car{}

这里,在给大家引申一下,事实上,java里头多态是不多的,只有两种情况,一种就是继承的时候会出现多态,还有一种就是后面第七章要讲的接口,就是实现接口的时候那里会出现多态,这里大家先了解一下,不太理解也没关系,学完了一总结你就知道了,java里头多态只有这两种。这个就是多态的定义,那么多态有什么功能呢?

多态的功能?

主要是方便我们进行定义,进行什么定义呢,最典型的就是方法的定义,比方说,我现在要去趟梦之岛,我先决条件是什么,你得给我辆车我才去,这就会出现什么状况呢,就好比描述刚才这个现象,板书

public class Me extends Person{

   public booleanrunMZD(Car car){//买电脑,需要传进一个参数,就是条件我要有车,然后保证买到电脑。

   return true;}

}

这个参数定义了一个Car,这个Car就是一个父类,就是你要给我辆车我才去,至于你给我辆什么车,不管宝马还是奔驰我都不介意,随便什么车都可以,这就意味着当你在调用的时候,比方说接上例,我继续板书,在main方法中调用的时候,

public static void main(String [] args){

   Me t = newMe();//先写

   Car car1 = new BMW();//后写,car我要具体的传一个实参进去

   Car car2 = newBenz();//后写

   t.runMZD(car1);//先写,car1最后加,也可以替换成car2

   }

这里,我到底传谁进去呢,接红色的car1,我这里传car1或者car2都是对的,只说给个车,又没说给个什么车,你给我什么车我就用什么车,所以可以传car1,也可以传car2,这就是多态,从这里大家有没有体会到多态有一个很重要的功能,就是在public boolean runMZD(Car car)里定义形参的时候方便定义,我不需要定义具体的值,不是说我到底要个什么车,而是一个笼统的说法,是个车就行,具体的等到真正实现的时候才具体的往里头传,这就是多态的使用方法,它的功能就是方便对类或者方法的定义,

所以说,多态的功能就是可以简化行为的描述,可以让同一个行为最后有多种不同的实现。例子里同一个行为就是给车,最后不同的实现就是你可以给我宝马,也可以给我奔驰,随便你,其他的也行。

好,到这个地方,多态就结束了。简单回忆一下,面向对象三大特征:封装、继承和多态,这些东西实际上理解完概念,大家好像会觉得感觉三大特征并没有多大的作用,作用都是要落实到后面的代码里去,比方说我们刚才所说的多态,如果没有这个东西,实际上代码是很难写的,就是这个概念,后面随着我们学习的深入,你们就会发现他们的作用,后面我们就会深入的学习继承。

下面我们开始讲下一个重要的东西,引用类型

引用类型

什么是引用类型

我们先回忆一下,学过数据类型没有,那什么是数据类型,就是数据分类,为什么分类,是不是不同的分类具有自身的功能,就是分门别类,这样好处理,谁好处理呢?开发者好处理,虚拟机也好处理,你就好描述了。已经学了8+1(String),那么当时还有引用类型没有介绍,今天我们就来学习引用类型。那么什么是引用类型呢?在Java里头,

引用类型(reference type)是指向一个对象而不是原始值的类型,简单点说,就是指向一个对象的类型就是引用类型,指向对象的变量是引用变量,这句话的意思就是把对象和原始值就分开了,也就是说如果指向原始值的叫基本类型,指向对象的就叫引用类型。有的同学会讲,感觉还是不是很清晰啊,这两个东西有什么不同呢?这里具体的关键是在内存的表现上会不同,简单地讲,引用类型一定是指向一个内存空间,我们前面所画的内存分配示意图,比方说,我们去new了一个对象,那么就new了一个空间出来,这个变量就指向这个空间,那么这个变量就叫引用变量,这个类型就是引用类型,那么从这样来讲,我说这么一句话大家看我说的对不对,在java里面,凡是用new去操作去得到的对象通通都叫引用对象,这句话对不对,这句话是正确的,为什么?new关键字的作用就体现了。事实上,大家可以看到,在java里面,除了基本类型,其他类型都是引用类型,为什么?其他类型全部都是对象,为什么呢,java是一个纯粹的面向对象的开发语言,事实上,严格的讲,连基本类型都不应该有,因为他们都不是对象,为什么会有呢?很简单,这就叫遗留产物,继承下来的,java从C++来的,C++又是从c继承而来,因为大家已经习惯,其实从第二代编程语言开始,这几个基本类型在所有的开发语言里面几乎都有,所以说它是一个继承的产物,它为了保持跟前面的一致,而不是全新设计的,如果完全面向对象的话,这几个基本类型就应该不要了,所以说,从这个角度来讲呢,在java里头,除了8种基本类型,其他都是引用类型,但是,这里还要给大家说,有一个东西特殊,那就是String,我们前面讲的时候也把String单独划出来讲的,如果按照我们刚才的讲法,String类型也应该属于引用类型,但是这个类型它有两种表达方法,第一种就是我们常写的Strings = “111”;第二种就是String s = new String(“111”);事实上,也只有它有两种表达方式,别的都只有一种,就它特殊,所以到底把它归到那个里面呢,我的意思就是干脆哪儿也不归,就单独列出吧。你知道就行了,既不好单独归到基本类型,也不好完全归入引用类型,从道理上来讲,它应该是引用类型,但是呢,它现在可以这么讲,它的样子是引用类型,但它的本质,做起来功能和操作呢是基本类型,属于脚踩两只船的那种。这就是什么叫引用类型。就是除了基本类型和String类型,其他你在java里面看到的都是引用类型。

引用类型的赋值

先看看如果下面两句话怎么赋值:

Car car1 = new Car();//用内存分配图表达

Car car2 = car1;//这个怎么赋值呢,用图表示,实际就是将car1赋值给car2,实际上就是car2指向car1指向的内存空间。也就是car1和car2指向同一个内存空间,中间有个变化的过程。大家对这种基本的内存分配大家要理解

如果在前面两句的基础上我再加一句,大家看看又是一个什么过程呢?

car2 = new Car();//该怎么赋值?我先new一个car,相当于有两个内存空间了,然后让car2指向新的空间,但是一个变量只能指向一个空间,因此,此时就应该把原来指向的那个空间去掉,而指向新的空间。两个空间两个变量,这两个也不会冲突。

好,引用类型的基本的内存分配示意图讲完了过后,接下来我们看一个比较重要的东西,就是按值传递和按引用传递

引用类型——按值传递和按引用传递

这里先简单说一下,按值传递和按引用传递的背景,在java里面其实从一开始就有争论,为什么争论呢?因为java是从c++变过来的,但是变过来的时候,它把指针拿掉了,把指针拿掉以后,java相对C++而言就变得简单了,变得简单了,难道就说java里面没有指针了吗,错,java里头同样也有指针,只不过隐藏了,在c++里,指针实际上是指向一个内存地址(内存空间),java里头同样也有这样一个概念,只不过是换了个说法,就是引用类型,java在一开始的时候,很多人就争论到底这个东西是按值传递还是按指针传递,很多人就在争论这个问题,事实上对我们来讲,不需要管它到底叫什么,对我们来讲,最重要的是对我们来讲,到底应该怎么去判断,对我们来讲还要说一点,那你说到底理解这个干什么呢?为什么非要理解按值传递还是按引用传递,有什么作用呢?那大家注意,如果说这个程序跑起来,那么这个按值传递和按引用传递直接影响的是什么呢?直接影响这个运行期间的值啊,很可能在这个运行过程当中你会发现值不同,你不理解这个,整个程序怎么跑的,你就搞不定了,这就是学它的意义,那么我们来看看吧,什么是按值传递?

按值传递是什么?

指的是在方法调用时,传递的参数是按值的拷贝传递。举个例子,靠例子来说明。

public class T1 extends Test{

   private voidtest(){

   }

   private voidt2(int a){

      a = 321;

      System.out.println(“t2.a==”+a);//这里的三个a是同一个a,但是和下面的a是不同的两个a,虽然两个a的值是相同的。传过来以后,在这里的a的值发生了改变,但是对下面的a没有任何影响,它变它的,和main里的a是无关的。

   }

   public staticvoid main(String[] args){

      T1 t = newT1();

      int a =123;//这个a和下面的a是一对

      t.t2(a);

      System.out.println(“main.a==”+a);

   }

}

按值传递重要特点:传递的是值的拷贝,也就是说传递后就互不相关了。

哪些东西是按值传递的呢?

也就说private void t2(int a)这里的int a 可以是哪些东西呢?基本的类型全部都是按值传递的,int可以换成char、short、long,float、double或者boolean,当然如果是boolean,下面就要写成a = false,在main方法中,将int a =123;改成boolean a = true;,都是可以的,基本类型都是按值传递的,除此之外,还有一个 也是按值传递的,那就是String,再举个例子,将int换成String类型就可以了,说明String也是按值传递的,那我们刚刚讲到String有两种表达方法,可以new一个String变量,那么我们来看看,将String a = “123”;写成Sting a = new String(“123”);结果是一样的,t2方法中也可以改成a = new String(“321”);结果仍然是一样的,至于这个原因,我们在后面讲String类的时候再讲,String非常的特殊,实际上严格的讲,String归类的话还是算引用类型,但是不管它怎么表示,实际操作的时候,还是按值传递的,原因简单地说,就是String有一个很重要的特点,就是值不可以改变。这个东西,我们后面专门有一个小节讲String类,这里大家先了解一下,总结一句话,基本类型和String类型都按值传递。说白了就是值的拷贝,你变你的,我变我的,我们两个之间没有关系。这就叫按值传递

又例:

public class TempTest{

   private voidtest1(int a ){

      a = 5;

      System.out.println(“test1方法中的a==”+a);

   }
   public static void main(String[] args){

      TempTest t =new TempTest();

      int a = 3;

      t.test1(a);//传递后,test1方法对变量值的改变不影响这里的a

      System.out.println(“main方法中的a==”+a);

   }

}

运行结果是

test1方法中的a==5

main方法中的a == 3

这个是比较简单的,接下来我们看按引用传递,这个就麻烦了,好,看一下,什么叫按引用传递。

按引用传递是什么?

按引用传递指的是在方法调用时,传递的参数是按引用进行传递,刚才讲了,什么叫引用啊,引用啥啊,指的是什么啊,就是指向的内存地址,所以按引用传递的实际上就是地址,也就是变量(对象)所对应的内存空间的地址。这个东西有什么作用呢?还拿上例来说,我们之前讲过,一个文件里头是不是可以写多个类啊,只是不能是public。

public class T1 extends Test{

   private voidtest(){

   }

   private voidt2(String a){ //1在这里面我不传a了,我改成t2(A a)

      a = newString(“321”);//4处改为a.name = “t2方法”;

      System.out.println(“t2.a==”+a);//5处改为t2.a == +a.name

   }

   public staticvoid main(String[] args){

      T1 t = newT1();

      String a =new String(“123”);//2应上面,我将此处改为A a = new A();

      a.name = “main方法” //3加这一句

      t.t2(a);

      System.out.println(“main.a==”+a);//6此处改为main.a == +a.name

   }

}

//下面的第一个写

class A{

   public Stringname = “”;//第一个写

}

请问此时输出结果是什么样子的?

输出结果都是t2.a == t2方法

           Main.a== t2方法

为什么呢?

我们再看下个例子:

第一行     publicclass TempTest{

第2行        private void test1(A a){

第3行           a.age = 20;

第4行           System.out.println(“test1方法中的age= ”+a.age);

第5行        }

第6行        public static void main(String [] args){

第7行           TempTest t = new TempTest();

第8行           A a = new A ();

第9行           a.age = 10;

第10行          t.test1(a);

第11行          System.out.println(“main方法中的age= ”+a.age);

第12行       }

第13行    }

第14行    class A{

第15行       Public int age = 0;

第16行    }

运行结果如下:

test1方法中的age = 20

main方法中的age = 20

我们通过内存分配示意图来讲解

理解按引用传递的过程——内存分配示意图

要想正确理解按引用传递的过程,就必须学会理解内存分配的过程,内存分配示意图可以辅助我们去理解这个过程。

 

理解了上面的例子,可能有人会问,那么能不能让按照引用传递的值,相互不影响呢?就是test1方法里面的修改不影响到main方法里面呢?其实我们刚才为什么会互相影响,大家有没有分析它的本质啊,原因是什么?原因就是main里的a和test方法中的a 指向的是同一个内存空间,那大家想想,这是不是刚才出这个问题的一个本质,就是同一块内存空间,所以说你不理解的话,你想想,假如说在程序当中你做了很多这样的东西,是不是到最后这个程序的值是多少你都不知道啊,就像昨天讲的,你就完全失控了,跑完了,我就问你,这到底是10还是20啊,本来你说我想用10去算,结果呢,一不小心用20来算了,想用20的时候,又不小心整成10了,那是不是就全乱套了,所以说,理解这个是必须的,否则你的程序肯定做不对,那么这个时候就有同学想了,刚才传的是同一个空间,所以说两个方法中的a就相互影响了,那我能不能想办法让它不要相互影响呢,大家想想,从思路上讲,只要怎么样他们就不会相互影响,只要让他们指向的是不同的内存空间,那么他们不就不会相互影响了嘛,那么怎么样去实现呢?让他们指向不同的空间呢,那是不是就分别new啊,只需要在t2方法体里加上a =new A():就可以了。结果就是t2.a=t2方法,main.a=main方法了。用内存分配示意图表示:

方法就是在test1方法里面新new 一个实例,并且重新赋值给a 就可以了。

所以从上面的例子我们就可以看出,这两个是比较简单但是也是很经典的两个小例子,关键是要大家学会,那么今后,它有可能程序写的非常大,也可能写的非常小,不管它大还是小,大家只要按照它的运行顺序,一步一步去画它的内存分配示意图,那么你画出来过后,最后你不用自己在那儿瞎猜到底是多少,是不是图上写多少它就是多少喽,这不就很简单了嘛,好,补充说明一下,其实刚才讲的就是按引用传递,按引用传递本质上是要互相影响的,但是我们可以让他不相互影响,那么补充一下:

1、“在java里面参数传递都是按值传递”这是好像很多人争论了很多年过后,到最后实在忍无可忍了,说我们总该得出个结论吧,它得出的结论叫什么呢?他说参数传递都是按值传递,实际上这句话的含义,按值传递传递的是什么?传递的是值的拷贝,按引用传递其实传递的是引用的地址值,所以统称按值传递。归根结底都是传值,其实大家想想,分析这个有意义没有,其实我们不关心你是按值传递,也不关心你是按引用传递,你就算说按指针传递又有什么区别呢?我们关心的是运行的本质,运行下来程序应该是什么样子就是什么样子。

2、在java里面只有基本类型和String是按值传递,其他的都是按引用传递。原因很简单,因为String有一个特点是:“String的值不可改变”。这个我们刚才已经讲过了。最后我们通过一个小小的题目来看一下。

测试:

1、请说出下述程序运行的结果:

class Foo{

   private int x;

   public Foo(intx){

      this.x = x;

   }

   public voidsetX(int x){

      this.x = x;

   }

   public intgetX(){

      return x;

   }

public class T{

   static FoofooBar(Foo foo){

      foo.setX(100);

      return foo;

   }

   public staticvoid main(String [] args){

      Foo foo = newFoo(300);

      System.out.print(foo.getX()+”-”);

      Foo fooFoo =fooBar( foo);

      System.out.print(foo.getX()+”-”);

      System.out.print(fooFoo.getX()+”-”);

      foo =fooBar(fooFoo);

      System.out.print(foo.getX()+”-”);

      System.out.print(fooFoo.getX());

   }

}

好,一起来看一下,其实大家看看,以后看到像这种题呢,该怎么做呢?其实啥也别想,上去就比着刚才的那个过程一步一步去画图啊,画完了是多少就是多少。那我们就来画画看。

这个题其实有很多的变数,特别害人,这个题就是SCJP的原题,SCJP就是SUN公司推出的一个关于JAVA程序员的一个认证,相当于初级程序员,如果改一个地方,能害死大多数人,只需将

public Foo(int x){

      this.x = x;//改为x=x;

   }

这个时候运行结果应该是多少,大多数人就不太注意这些细节,一个小的细节会导致一个程序值的不一样,本地变量赋予本地变量,x这个时候就是0,而不是300,然后把foo传进去,赋完值后x=100了,后面就是一样的了。

与此类似,这个地方再补一个小的练习,

给定如下代码:

int i = 1,j=10;

do(

   ifi++>--j)continue;

)while (i<5);

在执行完成后,i和j的值分别是多少?

类型转换

强制类型转换

什么叫强制类型转换呢?

把某种类型强制转换成另外一种类型就叫做强制类型转换。

光看这句话,以前有的同学可高兴了,为什么?从此以后我就不会再遇到类型问题了,我要什么类型我就强制转换,对不对?那大家想想,其实这里面有没有规律啊?有,不是说你能乱转,比方说你做一个小狗的类,再做一个小猪的类,你能不能拿一个小猪去转换成一个小狗啊,当然不能了吧,实际上,大家注意,它隐含一定条件的。你看看,像举得这个例子,

例如:可以将一个long值“挤压”到一个int变量中。显式转型做法如下:long bigValue = 99L;

      int squashed= (int)(bigValue);

你看我写的99L是不是一个long型的99啊,我可以把它转成,你看,强制转换怎么写?就是前面打括号,是不是就是强制转换,它说往这里头转没问题吧,但事实上,这有个潜在的值,潜在什么?你光看这个99是不是本身就是个int的值啊,所以说强制类型转换有这么一个规律,就是它原本就是这么一个值,你才能强制转换过去,明白这个概念吗?就比如说,int类型的值有一个取值范围,如果超过这个大小进行强制转换,会不会出问题啊?明显的会,所以对于这一点大家要注意,强制类型转换,写法上,大家看一下,就是在类型前打一个小括号,然后就强制转,这就叫强制类型转换,也就是说,在上述程序中,期待的目标类型被放置在圆括号内,并被当做表达式的前缀,该表达式必须被更改。一般来讲,建议用圆括号将需要转型的全部表达式封闭。否则,转型操作的优先级可能引起问题。大家注意一下,这里面有个地方要大家注意的地方。

注意:强制类型转换只能用在原本就是某个类型,但是被表示成了另外一种类型的时候,可以将它强制转换回来。强制类型转换并不能在任意的类型间进行转换。

比如上面的例子:99这个数本来就是一个int的数,但是它通过在后面添加L来表示成了一个long型的值,所以它才能够通过强制类型转换来转换回int类型。举个例子:

public class Car{

}

public class BMW extends Car{

}

public class Benz extends Car{

}

public class Me extends Person{

   public booleangotoMZD(Car car){

      BMW   b =(BMW) car;//意思就是不管你给什么车,我都当宝马跑,这行还是不行啊,这太阿q了,你骑个破自行车,你也当宝马,这就带调侃了,比如说这个类(BMW),它能够强制转换的前提是什么,你要传的是也是宝马,比如说我下面t.gotoMZD(car1),那你传的就是宝马,这就对了,这就叫做强制类型转换,大家体会这句话吧。这就是因为某种原因被表示成了另外一种类型了,现在这个宝马被表示成了Car类型,是不是这个概念,但是它原本就是一辆宝马啊,那我这个时候是不是就可以把它强制转换过来啊,明白这个意思吧,就是你要原本传宝马,我这儿才能强制转,如果你传car2的话,也就是你传奔驰的话,那就麻烦了,这个地方转换是不是就出错了,对吧。所以说,大家注意,用这个来举例子呢,理解这句话,原本就是某个类型(原本就是宝马,但是被表示成了Car类型啊,这个时候怎么办呢,我们就可以给它强制转换回来,否则的话,这个是不对的啊。

returntrue;

   }

   public staticvoid main(String[] args){

      Me m = newMe();

      Car car1 =new BMW();

      Car car2 =new Benz();

     

      t.gontMZD(car2);

   }

}

比如说,它能不能传个宝马进来,我能不能这么写(接上述红色字体)。

这就是强制转换,好,大家再体会一下,这个强制转换其实是什么,大家想想,其实从简单来讲,是不是把大的值往小的值转啊,像刚才这个Car和具体的宝马,谁大?是不是明显的是Car大啊,所以说要把一个大的值往小的值转,你就要强制转换。再来

升级和表达式的类型转换

升级转换,大家可以看一下,这是什么?他说,当没有信息丢失时,变量可被自动升级为一个较长的形式(如:int至long的升级),这句话大家一听就知道,是从小到大,从大到小叫强制转换,从小到大叫自动升级,就是自动转换,这个前面已经见过了,典型的就是把一个int型的值赋给long型,是不是就自动升级了,把一个char型赋给谁?赋给int,这就叫自动升级,明白这个概念吧,所以说呢,

long bigval = 6;//6是int类型,OK

int smallval = 99L;//99L是long型,非法

double z = 12.414F;//12.414F是float型,OK

float z1 = 12.414;//12.414是double型,非法

注意:

float a = 1000;(对)//把int型赋给float

float b = 1000L(对)把long型(64)赋给float(32),有的同学就犯嘀咕了,64位的怎么能赋给32位的呢?比它还小啊,但是什么呢,你看,这个时候别说你64位,你只要比我1000的范围大是不是就可以了,long型是可以赋给float,但是不能赋给int,

double c = 1000;(对)

double d = 1000L(对)

long e = 1000.1F(错)//float和double都不能赋给long型

long f = 1000.1(错)

其实大家想想,咱们常见的类型转换是不是就这几种啊,全部都罗列在这里了,float和double不能赋给Long型,那有同学说,我能不能强制转换呢,试试看,比方说,我们去这么写,如下:

public class T2{

   public staticvoid main(String[] args){

      long  a= (long)1000.01//把double强制转换成long

      System.out.println(“now=== ”+a);

   }

}

编译一下,通过了,这就说明什么?那输出的值是多少呢?

结果:now === 1000

看到结果了吧,也就是说,它转换的时候其实是不是和int型,把小数部分给扔了啊,换成1000.99,看看结果,是不是还是1000啊,这就是我们之前讲的,小数部分是不是直接给舍去了啊,float也是一样,连double都能转,float肯定也能转了。好了,这就是自动升级。

学了2种了,大的到小的,就是强制转换,小的到大的,就是自动升级。其实我们今后还会遇到向上造型。后面再说。

表达式的升级类型转换

这个地方强调

1、在四则运算表达式中,byte、short、char都会自动升级为int进行运算。所以,下述程序都会出错。

short a,b,c;             bytea,b,c            char a,b,c

a = 1;                a=1;               a =1;

b = 2;                b= 2;                b =2;

c=a+b(错)         c=a-b(错)         c=a*b(错)

所以说,以后写程序的时候,特别出错的几个点,大家看到,我用short定义a,b,c,a=1,b=2,这个都对,其实这个特别可气,为什么呢?有同学说,c=a+b,不就等于3嘛,没问题啊,我要直接写个3,还都对,但是我一写成a+b就不对了,为什么呢?直接写3对,你加下来也是3,怎么就错了呢?这个地方它是这样的,凡是在四则运算当中,带数的char会自动成为int,上面的1和2虽然都是int,但是赋值的时候,是不是把它当char来用的啊,但是一到四则运算当中,你看我这儿写的加减乘除,是不是都是错啊,就是说一旦到四则运算当中,全部当成int,int+int是不是也是int啊,也就是说a+b虽然是3,但是这个时候不是char3,而是int 3,我这个时候是不是相当于把一个大的值赋给一个小的值,那应该怎么办,就强制类型转换,是不是刚学过啊,所以要写成c = (short)(a+b);所以这个大家要注意。

第二个就是

2、在四则运算表达式里面,如果不强制进行类型转换,那么运算最后的结果就是精度最高的那个操作数决定的。比如;

3*5.0的结果就是double类型的,应该定义为:double a = 3*5.0;

为什么是最高的那个呢?它是不是就相当于运算的时候自动向上升级了。比如说我们写一个int a= 5.0+23;这句话就要出问题,编译就会出错,提示“损失精度”,这就说明,要以精度最高的那个类型去定义。

差不多第三章就over了,小结下吧,都学了些什么,是不是这一章学的有点多了这一次

 

 

 

 

 

 

 

 

 

 

 

                           什么是对象?

                           对象的基本构成

            面向对象基础   如何进行对象抽象?

                           抽象对象和实体对象的关系

                           java中的类和对象  包是什么 

                             java类的组成     包能干什么

           java类的基本构成  包             jdk常用包

                             import(接下)   如何定义

                              访问修饰符      如何编译

                               类定义         如何使用

                               构造方法

                               析构方法

                                属性

                                方法

                        实例变量和局部变量

再谈变量   变量初始化

           变量范围

                         引用类型是什么               

             引用类型    引用类型赋值

                         内存分配示意图

                         按值传递、按引用传递

             java类的基本运行顺序    类型转换

 

        功能是什么

import   如何表达,语法规则

        java类的搜寻方式

 


             private

访问修饰符   无修饰符

             protected

             public

           是什么

           定义规则

构造方法   功能是什么

           默认构造方法

 

         是什么

属性

         定义规则

        是什么

方法    定义规则

        形参、实参

        参数可变的方法

第四章 高级类特性

我们刚刚把基本的java类学完,就要去学高级类特性了,听起来挺吓人,但其实呢,也没那么高级,就是一些比较常规的语法规则,这些都还是算在基础里面的。来,看看到底都有哪些东西?

Java中的继承

刚刚前面才学过,A如果继承了B,则A就拥有了B类的属性和方法,这种现象就叫做继承。好,既然有这样子,那么怎么表达,是不是前面已经见到了啊。继承表达就是extends

extends关键字

在java里面如何来表达继承的关系呢,就是使用extends关键字,如下:public class A extends B{}

表示A类继承B类,A类具有B类允许其能访问的所有变量和属性

extends表达的就是这个意思,但英文翻译过来是扩展的意思。那也就是A扩展了B,B是父类,A是子类,如果说从功能上来讲,就是从这个大面的功能上来讲,从功能上讲,是A多还是B多啊,理论上来讲,A的功能应该比B多,是这个概念吧,但是,私有的属性和方法看不见吧,我们从看的见的功能上来讲,A至少应该不会比B少,这才是严格的,就是说,A可以一点点的加,但是A可不可以比B的公共部分还少,这是不可以的。好,看一下,我们知道继承怎么用的,接下来看继承后的运行顺序

继承后的基本运行顺序

规则:初始化子类必先初始化父类

比如说,T1继承Test,T2又继承T1,这就有很多的文章在里头。我们首先学第一个,大家看到,规则是什么?规则:初始化子类必先初始化父类,结合之前一句话,初始化一个类,必先初始化属性,两个结合起来。

public class T2 extends T1{

   {//第三个写

      System.out.println(“T2的属性”);

   }

   public T2(){//第二个写

      System.out.println(“T2的构造方法”);

   }

   public staticvoid main(String [] args){//先写

      T2 t = newT2();

   }

}

 

 

public class T1 extends Test{

   {

      System.out.println(“T1的属性”);

   }

   public T1(){

      System.out.println(“T1的构造方法”);

   }

public class Test{

   public String s= “11111”;

   {

      System.out.println(“Test的属性”);

   }

   public Test(){

      System.out.println(“Test的构造方法”);

   }

好,三个类定义好,我们看看怎么运行的,当我在T2中运行

T2 t = new T2();的时候,大家看看怎么运行的,初始化一个类必先初始化其父类,当调用new T2();的时候,应该调到哪儿,即调到public T2(){}这个位置,然后到哪里呢,它不会运行属性,而是运行T1,即跳到T1中,到T1里,先到public T1(){},然后它又跳到Test中,到了Test中以后,先跑到public Test(){这一行,然后,用我们开始讲的,初始化一个类必先初始化其属性,这时候,就输出test的属性,紧接着走谁,走Test的构造方法,父类初始化完了,回到T1中,然后初始化T1的属性,即输出T1的属性,然后再走T1的构造方法,输出T1的构造方法,再回到T2,初始化T2的属性,输出T2的属性,然后再走构造方法,输出T2的构造方法,明白了吧,瞧瞧,咱们编译运行一下。先走Test的属性,再走Test的构造方法,然后是T1的属性,T1的构造方法,最后走T2的属性,T2的构造方法。明白了吧,这是不是就把咱们讲的规则合起来用,这就是带父子类的情况,这个类怎么运行的顺序是不是就清楚了,规则很简单,说起来就两句话,

规则:初始化子类必先初始化父类,初始化一个类,必先初始化其属性。好,这是继承的第一个点,就是继承的运行顺序。再看,继承还有几个特点

单继承性

在java里面都有这样一个问题,就是java里面,继承只能继承一个,什么概念呢,就像前面的例子,如果我想T2继承T1,同时又去继承Test,可不可以,即public class T2 extends T1,Test{},不可以,即,extends后面只能有一个类,这就叫extends的单继承性。

单继承性:当一个类从一个唯一的类继承时,被称作单继承性。单继承性使代码更可靠。java类的继承只能是单继承的。

接下来,第二个特点:

构造方法不能被继承

其实我们刚才也说了,还有哪些不能被继承,私有的属性和私有的方法是不是都不能被继承啊,构造方法也不能被继承,大家仔细想想,为什么构造方法不能被继承呢?这个时候大家注意一下,从本质上来讲,不同的构造方法是不是用来初始化不同的对象的实例的啊,所以说在不同场景下去造东西,那么造出来的肯定都不一样,那你能让它继承下来吗,如果继承下来,那就乱了,所以说构造方法不能被继承。

一个类能得到构造方法,只有两个办法。一种自己写构造方法,另外一种是根本没有写构造方法,类有一个默认的构造方法。

好,第三个,我们看一下:

关键字super

这里有很多好玩的地方,看一下,学super我们就用类的学习方式,大家看第一句话,关键字super可被用来引用该类的父类,如果引用自己用什么啊?用this,大家知道了吧,super引用父类,this引用自己,很明显,这两个东西可以用类比的方法来学习吧,this我们都学了哪些功能,1、this可以点取自己的属性、方法2、this可以区分同名变量3、this可以作为方法名初始化对象,就是可以调构造方法。super也和它差不多。好,看看super的功能:

1、点取父类中被子类隐藏了的数据成员

2、点取已经覆盖了的方法

3、作为方法名表示父类构造方法

是不是也是这些东西啊,好,我给大家解释一下

先看第一个,它是点取父类当中被子类隐藏了的数据成员,举个例子,我在T1中增加一条属性,public String name = “This is T1”;

public class T1 extends Test{

   public String name = “This is T1”;//新增加的属性

   {

      System.out.println(“T1的属性”);

   }

   public T1(){

      System.out.println(“T1的构造方法”);

   }

然后在子类T2中我也增加一条属性定义:publicString name = “This is  T2”;

public class T2 extends T1{

   public String name = “This is T2”;//新增加的属性定义,这时候就出现了一个问题,你看我在下面输出T2的属性==//该行去掉

   {//第三个写

      System.out.println(“T2的属性==+name);//大家说我打出来的结果是T1还是T2,是T2,这是因为子类的属性将父类的属性给覆盖了,但是我现在我不想输出T2,而想输出T1,大家想想怎么办?很简单,只需改成super.name既可。这是不是和this的用法差不多啊,是不是局部变量和属性的定义一样啊,他们用this区分,父子类用super区分。super调的是父类的,正常情况下是自己的。但是如果我将该类中的属性定义去掉,大家看看,是什么结果,结果是输出T1,如果换成this.name或者super.name,结果还是输出T1,大家想想为什么?大家看看是不是很好玩的啊,我写三种写法,结果是不是都一样啊,尤其大家看到连this都可以调,这表示什么,this和super是不是都指向super的指向啊,因为这个时候这个类的属性现在只有一份儿,是不是不管你怎么调,大家指向的都是他啊,如果有两份儿,this和super就区分了,好,这就是super的第一个功能。

   }

   public T2(){//第二个写

      System.out.println(“T2的构造方法”);

   }

   public staticvoid main(String [] args){//先写

      T2 t = newT2();

   }

}

下面看第二个功能

点取已经覆盖了的方法

这里我们也演示一下,虽然还没学覆盖方法

public class T1 extends Test{

   public Stringname = “This is T1”;

   {

      System.out.println(“T1的属性”);

   }

   public T1(){

      System.out.println(“T1的构造方法”);

   }

   public void t2(Aa){

      a = new A();

      a. name = “t2方法”;

      System.out.println(“t2.a= ”+a.name);

   }

   public static voidmain(String[] args){

      T1 t = newT1();

      A a = newA();

      a. name = “main方法”;

      t.t2(a);

      System.out.println(“main.a==”+a.name);

   }

}

Class A{

   public Stringname = “”;

}

T2修改:

public class T2 extends T1{

   public String name = “This is T2”;

   {//第三个写

      System.out.println(“T2的属性==+name);//将该行注掉

      t2(null);

   }

   public void t2(Aa){

      System.out.println(“haha,t2”);

   }

   public T2(){//第二个写

      System.out.println(“T2的构造方法”);

   }

   public staticvoid main(String [] args){//先写

      T2 t = newT2();

   }

}

编译运行T2,大家看看输出结果是:

haha,t2

这里,为什么,调的是T2中的t2方法,而不是T1中的t2方法。假如我想调T1中的t2方法,应该怎么办呢?只需将t2(null)改为super.t2(null)既可。这就叫点取已经覆盖了的方法。其实第一个功能和第二个功能一个是针对父类的属性,一个是针对父类的方法。

第三,作为方法名表示父类构造方法

这是什么意思呢,就好比说,我在构造方法里面想要去调父类的构造方法,即在T2的构造方法中调T1的构造方法。该怎么调呢?

public T2(){

   super();//调父类的构造方法,参数为空,其实这行写不写都有的,虚拟机是自动加上这一句的。但是如果要写,一定要写在构造方法中的第一行。this也是一样

   System.out.println(“T2的构造方法”);

   super();//这句话写在这里就错了

}

但是如果说我在T1的构造方法中传一个参数,

public T1(int a){

   System.out.println(“T1的构造方法”):

}

此时,编译T2,大家看到还能通过吗?通不过了吧,为什么呢?第一个错误我们可以将T1中下面的main方法中的代码注释掉,和它们无关,提示错误是找不到构造函数T1();,而且位置是在T2的构造方法里头,原因是什么?它自己会在那个地方隐含地整一个super();结果呢,我们有没有一个super()为无参数的,因为我们说过了,构造方法一旦写了过后,就会将系统默认的构造方法覆盖了,这个时候应该怎么改呢?方案有两种,一种就是在T1中再构造一个无参数的构造方法,第二种,在T2中的构造方法中调super(5);带个参数就可以了,编译运行就可以了,这就是第三个功能。

好了,到这个地方,继承就讲完了。

接下来我们学习方法的覆盖

方法的覆盖

什么是方法的覆盖(Overridden Methods)

在刚才讲的继承里面,出现了一种现象,某个东西,子类有,父类也有,这种父子类完全一样的情况下,我们把它叫做覆盖方法,好,看一下,什么是方法的覆盖。

在父子类中,如果子类某个方法的名称、返回类型及参数列表(不包括权限、修饰符和异常列表)正好与父类中某个方法完全一样,那么这个方法就是覆盖方法,子类覆盖了父类的方法。参数列表一样是什么意思呢?先举个例子

public class T extends Parent{

   public voidt1(int a,int c,String b){如果将后两个对调,还算不算相同呢,就不算了

   }

}

class Parent{

   public voidt1(int a,int b,String c){

      System.out.println(“thisis p.t1”);

   }

}

怎么区分父子类的两个方法的参数列表是完全一样的呢?

大家注意:第一,首先参数的个数肯定要一样,第二,类型的顺序要一样,主要就是两个点,变量名一样不算,变量名可以随便叫。

再举个例子

public class Employee{

   String name;

   int salary;

   public StringgetDetails(){

      return “Name.”+name+”\n”+”Salary:”+salary;

   }

}

public class Manager extends Employee{

   Stringdepartment;

   public StringgetDetails(){

      return “Name.”+name+”\n”+”Managerof”+department;

   }

}

public class T extends Parent{

   public voidt1(int a,int c,String b){

      System.out.println(“this is T.t1”);//增加该行代码

   }

   public static void main(String[] args){

      T t= new T();

      t.t1(1,1,””);//这个时候该源文件中有两个t1方法,那么我此时调的到底是哪个t1方法呢

}

class Parent{

   public voidt1(int a,int b,String c){

      System.out.println(“thisis p.t1”);

   }

}

加上main方法之后,大家看一下,我用t.t1()调t1方法的时候,我们看到这时候是不是有两个t1方法啊,那么我这里调的是谁呢?如果我在main方法中再增加代码

Parent t1 = new T();

t1.t1(1,1,””);//这个t1方法又是调的哪个t1方法呢,再增加代码

Parent t2 = new Parent();

t2.t1(1,1,””);//这又是调的哪个t1呢

大家想想,这里面都分别调谁,这时候是不是就出现问题了啊,那么

我们先来学习一下

到底运行哪一个方法?

这里就会造成一个问题,就是覆盖的时候,那么到底运行哪个方法呢?

这里会给我们带来一个麻烦,父子类中有相同的方法,那么在运行时到底调用哪一个方法呢?

如果像上面的例子:

Employee e = new Employee();

Manager m = new Manager();

这个时候不会有困惑,就是自己类调自己的类方法,分别调自己的getDetails()方法,因为你这时候压根就没有交叉,不明显的如下所示,

Employee e = new Manager();

e.getDetails();

它该运行哪一个getDetails()方法呢?这种情况就会比较麻烦,那么这个时候呢在java里头就会有一个后期绑定的特点,我们先运行一下T这个文件看看

结果是:

this is T.t1

this is T.t1

this is p.t1

事实上,在java里头就有一个后期绑定的特点,我们先运行一下T,然后再来看这个特点,大家可以看到,前面两个调的都是T的t1方法,而最后那个调的是parent的t1方法,为什么会这样呢?大家看一下,也就是说,这个东西在java里头,它真正在运行的时候,它不是看你前头是什么,比如说,下面的代码,前面是Parent啊,那不应该调的是Parent类中的t1方法吗,大家注意,不是这样子的,事实上呢,这个在java绑定的时候,也就是说它本身有很多方法,它在什么时候把这个方法跟你这个变量绑定起来呢?大家注意啊,我们之前讲的内存分配讲的是什么,是不是只是讲的属性跟变量绑定起来了,大家想想啊,还是要理解这个概念,举个例子,还是通过画图来帮助大家理解。

Parent t1 = newT();

t1.t1(1,1,””);

我们前面讲的内存分配示意图中,new出来的空间里只有属性的值,并没有方法,没有类的定义,为什么?其实原因很简单,你想想看,方法还有类的定义一旦写完了,那是人写的吧,是不是以后用这个类,它就不再变了,它会不会每个实例都去拷贝一份,当然不会了,它要的只是一个统一的表达,当然不会每一个都拷贝一份儿了,所以在new出来的内存空间中只有属性,我们现在去调t.t1(),它不是从内存分配示意图里去走,其实这些方法,类会给他们分配一个公共的地方去存放,也就是说有一个方法存储区,里面放的t1方法,this.t1(),super.t1(),t2方法,你调t.t1的时候,它就会绑定一个具体的方法,如果你绑定的是this.t1(),那么你调的就是this.t1(),如果绑定的是super.t1(),那么你调的就是super.t1();所以这种运行期间,将方法与变量绑定在一块儿,在java里头叫后期绑定,所以正是因为有这样的功能,我们就把它总结成一个规律,那么有同学说,到底怎么绑定呢,

后期绑定的概念:你得到与变量运行时类型相关的行为,而不是与变量的编译时类型相关的行为。这是面向对象语言的一个重要特征,也是多态性的一个特征,并通常被称作虚拟方法调用或者动态绑定或者后期绑定。

正是因为有这样的功能,我们就总结了一个规律,那有同学说到底怎么绑定的呢,很简单,规律就是:编译时看数据类型,运行时看实际的对象类型(new操作符后跟的构造方法是哪个类的)。一句话,new谁就调用谁的方法。

也就是说,看我们刚才写的这个程序,

T t = new T();这个真正绑定的方法是T的方法,同样道理,

Parent t = new T();这个也是new的T,所以绑定的也是T的t1方法,至于前面你是Parent也好还是T也好,都无所谓,事实上,上面两句话调的t1方法实际上就是同一个。这种在java里面就叫后期绑定。规律就是New谁就调谁。听明白了吗,实际上这个概念很好理解对不对。实际上根内存空间一样,内存空间是谁把它两联系起来的,不就是靠赋值符号嘛,我先new一个空间,然后一赋值,不就把变量跟内存空间联系起来了嘛,那么变量和方法怎么联系呢,就是由具体的绑定来联系起来的,当然,运行期间它是动态绑定的,绑定谁你就调谁,那大家现在应该知道了,绑定谁是有规矩的,不是乱绑定的,如果说没有规矩,随机的,那这儿就真坏了,程序是不是就没法控制了,规矩就是new谁就调谁。

好了,这就是方法的覆盖里面出现的一个问题,那么接下来呢,咱们学习一下覆盖的规则

覆盖方法的规则:

1、覆盖方法的返回类型、方法名称、参数列表必须与它所覆盖的方法的相同

2、覆盖方法不能比它所覆盖的方法访问性差(即访问权限不允许缩小)

3、覆盖方法不能比它所覆盖的方法抛出更多的异常。

这些都是大家要记下来的。其实这些都很好记呢,怎么个好记法,我们只要按照方法的语法定义来记就很好记了,我们先从访问权限来看。访问权限有要求没有,有,那大家来看第二点,覆盖方法不能比它所覆盖的方法访问性差,这句话什么意思,权限值可以放大不可以缩小,第二个规则就是访问权限。举个例子。比方说,前面的例子,Parent父类中定义的方法t1是不是public定义的,那么我这里有几种选择,什么叫放大,public是不是已经是最大的了,那这就意味着我在子类T中的t1方法也只能用public来定义,如果父类中的t1定义时没有访问修饰符,那么子类的t1前面可以有几种访问修饰符呢,除了private其他三种都可以,分别演示一下,都是可以编译通过的,这就是权限只可以放大,理解这句话了吧,这就是第二条规则。

再看第三条,覆盖方法不能比它所覆盖的方法抛出更多的异常。由于异常还没有讲,我先演示给大家看看吧,什么叫做不能抛出比它更多的异常,我有很多可以抛的异常,比方说我在父类的t1方法定义中增加异常列表,我可以从JDK的API里面随便找两个异常,我们的包是lang包,随便抓两个Exception,

public void t1(int a,int b,String c) throwsNullPointerException,ArithmeticException{}

父类抛出了两个异常,那么子类如果不抛出异常可不可以,可以,但是如果子类抛出其他的异常就不可以了,也就是说子类要么不抛,要么抛出父类异常的子集,不能抛父类所没有的异常。这就是第三条规则。

那么理解了方法的覆盖以后,前面另外一个已经讲过了,如果在前面的例子中,我要明确的调父类的t1方法,就可以用super来调了。比如说我在上例中我就想调Parent的t1方法,那么就可以用super来调。接红字

比方我将上例改为:

public static void main(String [] args){

   T t = new T();

   Parent t3 =(Parent)t;//比方这里头t3,我在t3里头想要去掉Parent的东西,那有同学说该怎么写呢?难道写t3.super.t1()吗?好像不太好吧,那么这种情况下怎么办呢?我们可以通过写一个方法过度一下,接蓝字部分

   t3.t1(1,1,””);//你是不是可以调t2,将该行改为t3.t2(1,1””);,那么大家看看(连着上条语句一起看),我这么写对不对,这儿就有一个很有意思的问题,我告诉大家这句话是不对的,那有同学说凭什么说它是不对的呢?我不是new的一个t,然后再强制转换成Parent类型,那按道理,这个t变量里头是不是应该有个t2方法,那么这个时候大家注意,它这个时候以左边Parent t3作入口,以右边(Parent)t这个做实际的调用,也就是说实际调用调用的是T类里的东西,但实际上入口是谁呢?是Parent,也就是说从这个角度来讲,按照我们讲的封装是强迫用统一的界面去访问,Parent这个界面有没有t2这个方法,没有吧,Parent里面是没有t2这个方法的,所以我们编译一下看看,提示错误,t3.t2找不到t2这个符号,为什么找不到呢?这一点跟刚才是不一样的,大家仔细体会,原因就在于什么?就是说方法调用的时候,左边Parent t3是我调用的入口,实际上我从哪儿走啊,我从Parent里头走,那么我从Parent里头走的时候,大家想想,Parent里头有没有t2这个方法,没有吧,所以我调不到,但是我Parent里头有t1方法对不对,那我可以调,但是它因为是后期绑定,那么我调t1的时候,它是不是就由右边来决定真正执行哪个方法啊,右边是new的谁,new的T,所以它调的是T的方法,明白这个意思吧,所以说大家注意一下,这两个并不矛盾,所以这也是很有意思的一点,就是说你强制转换过后,你能调的方法就只能是谁的方法啊,就是父类里头有的方法了,所以这里你要是这么调,你还调不到父类的方法,你除非怎么做啊,就是将Parent改为T,即T t3 =(Parent)t,这样你才能调到,当然还有一个办法,父类也去写这么一个方法,它是不是就能调了,这个规则就是为了保证语法调用的安全,就是说,它以左边作为调用的入口,而右边作为后期绑定的执行。

}

public void t2(){

   super.t1(1,1,””);//写t2方法过度一下,在这个里头写super.t1(),然后接紫色字体部分

}

好,这就是有关覆盖方法的内容,应该就差不多了,下面看方法的重载

方法的重载

方法的重载是继承里面出现的一个特有的现象,大家想想,覆盖是不是有这种父子类的关系才会出现方法的覆盖啊,一个类有没有覆盖的说法,一个类是不会有覆盖的说法的,但是一个类里头可以有重载。那么什么叫重载呢?

什么叫重载?

在同一个java类中(包含父类),如果出现了方法名称相同,而参数列表不同的情况就叫做重载。

参数列表不同的情况包括:个数不同、类型不同、顺序不同等等。特别提示:仅仅参数变量名称不同是不可以的。来,看一下,什么叫做方法的重载呢?大体上就是这么个样子。比方说还是刚才T这个例子,我在类里面再定义一个t1方法:

public int t1(){

   return 0;

}

大家看这个t1方法和刚开始定义的t1方法算不算是重载,当然算了,为什么算重载呢?因为我们看是不是满足条件,方法名相同,参数列表肯定不同了,它三个,我一个都没有,首先个数就不同,这个地方特别要强调的就是,比方说我这里public int t1(int aa,int bb,String cc)这个算相同还是不同,这个就叫参数列表相同,这个算不算是重载呢?这就不算了,方法名和参数列表完全相同,我们编译一下,系统报错,为什么?因为这两个东西它已经定义了啊,就违反了语法规则,这个时候,我们先思考两点,为什么这个地方要重载这个东西出现呢?你这不是没事找事吗,你把方法名称写个不同的名称不就什么事情都解决了啊,对不对啊,为什么非得把方法名称和参数列表整成一样啊,大家注意一下,这里是有特点的,什么特点呢?就是这样,就是有些时候,我们知道,方法的命名应该是动词,要小写,要有明确的含义,就好比这个车开,都叫run,比如说我要写语法,run的方法,大家怎么写?因为名字代表的是它具体的功能,但是有些时候会出现我这个功能是一样的,但是具体的实现不一样,所以我应不应该用同一个名字啊,我应该用同一个名字,因为名字就揭示了功能,但是我里头实现又不一样,所以我要写很多很多不同的方法,大家是不是听起来感觉像前面讲的多态啊,但是大家注意一下,多态在真正的面向对象里面,类才有多态,方法本身有没有多态这一说,没有,但是这个地方你可以相互去理解,同一个方法有多个不同的实现或者体现,对不对啊,但事实上,你不能这个是多态,你心里明白就好了,就是也有这么一个现象,这种叫什么?其实就叫做重载,一个方法可能有不同的实现,就这么个概念,好,那么这个时候,既然名字是一样的,那么java怎么区分你要调的是哪个方法呢,就靠参数列表来区分,所以说,就会出现一个什么问题,如果参数列表一样,它就没法区分这两个谁是谁了,所以在重载的时候就要求参数列表必须不同,明白这个道理了吧,这就是为什么这么定义,所以这个地方怎么改,很简单,只要让参数列表不一样就可以了,去掉一个参数就编译通过了。好,这里面大家注意一点:

注意:跟成员方法一样,构造方法也可以重载。

实际上构造方法我们之前见过,就像

public T(){

}

public T(int a){

}

这就叫构造方法重载,最后它怎么知道调哪个方法呢?就是看调的时候传的参数是什么来判断。

好,这个就是先理解概念,什么叫做方法的重载。那么方法的重载理解过后呢,看一下规则

方法重载的规则:

1、方法名称必须相同

2、参数列表必须不同(个数不同,或类型不同,或参数排列顺序不同)

3、方法的返回类型可以相同也可以不同。

还是一样,从前到后来说,访问权限有没有限制,没有,修饰符有没有限制,也没有,然后就是返回类型有没有限制,也没有,限制的是什么,方法的名称必须相同,参数列表必须不同。好,这个地方有个要注意的地方,就是

注意:调用语句的参数列表必须有足够的不同,以至于允许区分出正确的方法被调用,正常的拓展晋升(如单精度类型float到双精度类型double)可能被应用,但是这样会导致在某些条件下的混淆。这个是什么概念呢,像下面的情况就会出问题。如单精度类型float到双精度类型double,比方说我有个方法是这样,大家来区分一下,我要重载t2方法,

我写一个public void t2(double a)和public void t2(int a)

public void t2(int a){
   System.out.println(“int a == ”+a);

}

public viod t2(double a){

   System.out.println(“doublea ==”+a);

}

回到main方法中,如果写t.t2(3),你说我调的是哪个方法,调int还是调double,按道理来讲,应该调谁,是不是应该调int啊,跑一下,结果是int a ==3,好像看起来没有任何问题,对不对,但是如果这个时候我把double这个t2方法移到父类上去,即放在Parent类中,这个时候算不算重载呢?也算吧,继承下来了啊。在T的main方法中我写上Parentt3 = (Parent)t,是不是给它造型一下,然后t3.t2(3);看还能不能正确识别,编译运行,输出结果为:double a == 3.0,还能正确识别吗?很明显,这个时候不是我想要的结果了,我想要的结果是int a == 3,它怎么就错了呢,按道理来讲,我这个T里头是不是有两个t2方法,分析一下,怎么就错了呢,以后就避免不要写出这样的错误来了,大家看啊,当你t3去调t2的时候,它是从哪个界面进入的,它是从Parent的界面进入的,从Parent进入的时候,它是不是先在自己里面先找一圈,因为你这是重载,记住,不是覆盖,如果是覆盖,比如说你将参数改为int a,这事反而简单了,它就算覆盖了,这个时候就new谁调谁,所以肯定是调T中的int t2方法,结果,你这里是doublea,这就意味着虚拟机在运行的时候,它认为还是覆盖吗?它肯定不会认为你int覆盖人家的double,它认为是重载,重载就不能套用覆盖的规则,就不是new谁调谁了,而是按照顺序,就跟classpath里头找类一样,它就按照顺序从上到下来找方法,找到匹配的它就调,好,这个时候由于你统一的界面是Parent,意思就是从Parent作为入口,入口进来,因为是重载嘛,那就在Parent里头看有没有t2方法,有没有啊,很明显,有,是不是就找到t2方法,找到这个方法,你去调这个3,它是int型,但是它是不是能自动升级为double,所以调的这个参数是对还是错,参数是对的,所以这个时候它从Parent作入口,它只能调到double的t2方法,如果我将double改为String,那大家想想这个时候会有什么后果,这个时候会调到子类中的t2方法吗,编译一下,系统报错,为什么?这就是刚才讲的,事实上,这个时候它是以Parent作入口,它能调的方法一定是谁有的,一定是Parent里头有的,你传一个int的值,这个时候Parent里头有没有类似的t2方法呢,没有啊,这个时候大家注意啊,这是很容易出问题的地方,所以说这个时候从现在的写法上来讲,它压根就调不到子类的t2方法上,如果我将Parent t3 = (Parent)t这句话注掉,看清楚,我们要把这个东西给玩熟,直接t.t2(3);如果这么调呢,这回就可以调到子类的t2方法了,这个时候是以T作入口,就能调到子类的t2方法了,那好,在这种情况下,我们再研究一下,怎么研究,这个时候我们把String还原成double,大家看清楚,我在怎么改这个东西,这就意味着以后每个知识点,你自己也可以这么学啊,实际上就应该这样,反复的来练,就是改不同情况,这个时候t.t2(3),意味着有两个t2方法可以调,既可以调父类的t2方法,也可以调子类中的t2方法,那么这种情况下它运行谁呢,是子类中的还是父类中的t2方法,结果还是调的子类中的t2方法,这个地方其实很简单,就是看匹配程度,看参数列表的匹配程度,这个时候有两个t2,一个int型,一个是double型,那么这个时候重载,根据你传的这个值3,毕竟传到double的话还需要一个自动升级的过程,这个意思就是这样,如果你没有int型,假如我把子类中的t2方法注掉,这个程序能跑吗?也能跑,就凑合了,调谁,调父类中的t2方法,这个实验就证明,这个T本身就有两个t2方法,凡是在一个类里头,重载方法一定是严格遵照匹配参数列表,谁最接近,它就优先调谁,好了,在重载里头就给大家演示这么多,为了避免今后你糊涂了,到底调哪个我也搞不清楚调谁了,最好在定义方法的时候不要出现这种状况,干脆就把参数列表整的差距大一点,是不是就不容易出错了,好,这就是重载。

接下来咱再看一下java中的多态

Java中的多态

其实前面已经讲过多态了,但是关于多态的使用我们并没有介绍,

多态是什么?

多态是同一个行为具有多种不同表现形式或形态的能力。

在BMW或Benz继承Car的情况下,一个Car既可以是一个普通的Car类,也可以是一个BMW类或Benz类。也就是说下述表示都是对的:

Car car = new Car();

Car car = new BMW();

从上面我们可以看到,同一个行为Car具有多个不同的表现形式,这就被称为多态。

注意:方法没有多态的说法,严格说多态是类的特性。但是也有对方法说多态的,了解一下,比如前面学到的方法覆盖也被称为动态多态,是一个运行时问题;方法重载称为静态多态,是一个编译时问题。

看一下多态的使用,其实就是多态与类型。

多态与类型

一个对象就是代表一种类型,所以说在这种情况下,一个对象只有种表现形式(是在构造时给它的),但是一个变量却有多种不同形式。这里就是说,其实变量指向的一个实例,最典型的就是这个

Employee(雇员)e =new Manager()

大家一看就知道,谁是子类?谁是父类?Manager是子类,Employee是父类。就像我们前面写的一样

Parent t1 = new T();

这是不是就是一种多态的一种应用,是不是这个概念,我们其实在前面讲多态的时候,还讲了一种多态的应用,比方说我在T中方法传参的时候,是不是可以传一个Parent t啊,如下:

public void tt(Parent t){

 

}

这个时候我既可以传一个子类的实例进去,也可以传一个父类的实例进去,这个前面是不是演示过了,这种就是多态跟类型的一个应用对吧。既然前面讲过了,咱们就不用花太多的时间来说了。但是在这个地方呢,特别强调,其实刚才也演示了,

使用变量e是因为,你能访问的对象部分只是Employee的一个部分,刚才是不是已经演示了,你能访问的入口是什么?刚才是不是就是Parent啊,所以说在这种情况下,Manager的特殊部分是隐藏的,所以说e是一个Employee,而不是一个Manager,所以说就不能调e.department,为什么?因为这个e.department是谁的?是Manager,也就是说Parent有没有,这个Employee有没有,没有的时候它就会出错。这个刚才是不是也演示过了,好了,其实java中的多态呢?如果你要用就怎么办呢?你就再强制转换回来,把它还原成Manager类型,就可以访问到Manager里面的属性和方法了。

Employee e = new Manager();

Manager m = (Manager)e;

m.department = “开发部”//这就是合法的了

就是它原本就是什么类型,就是再强制回那个类型。比方说像前面的例子:

Parent t1 = new T();

大家想想,我下面能不能这么写,我再把t1强制转换回T,

T t5 = (T)t1;

这么写对不对,编译下,对的,这就意味着,像这种强制转换的时候,编译期间会不会出错误啊,它只要能够互相转换,编译期间是不是不会出问题,假设说有问题的话,什么时候才出来,是不是运行期啊,对吧,所以说这个时候你不能光看这个,要真验证,还得怎么着啊,就调一个方法试试看,比如说t5.t2(5),看看能不能运行是不是才是真的啊,编译运行下,走了没有啊,走了,这说明什么,它确实能够调用,是不是这个概念,这就说明你这个转换是成功的,那么有的同学说,我编程不能老是撞大运啊,对不对,如果编程老是这样撞大运,那就完了,所以说这种情况下,我怎么能够保证它一定能转换成功呢?怎么能保证?就必须保证它本身是这个类型才能转换成功啊,那它怎么叫是这个类型呢?那么大家看到,下面我就给大家一个instanceof,instanceof什么意思啊,叫做实例属于,instance实例的意思,of属于的意思,意思就是说这是一个判断,就是用来判断它是否是某种类型,比方说像前面我就可以这么写,

Parent t1 = new T();

if (t1 instanceof T){//注意一下,它返回是一个boolean类型的值啊,这是一个比较操作符,就是比较t1是不是T类型,如果是,大家看到,我再给它转过来,这样我就能确保绝对不出错。因为它本身就是个T,所以我转换是不是绝对不会有问题,那么如果它不是T呢,不是T的话, 大家想想,它是不是就不用走下面的代码了。

T t5 = (T)t1;

T5.t2(5);

}

我们再编译下看看,大家看到结果还是一样啊,这说明进去了没有,进去了,那这个时候如果我改改呢,改成if(t1 instanceof  Parent),这句话是对还是错呢?跑跑,编译运行下,是不是还是对的啊,为什么?我们讲,t1其实可以有两种说法,怎么个两种说法,我们前面讲过is-a吧,t1 is a Parent,t1 is a T,是不是都对啊,本来t1就是个Parent,它怎么不能instanceof Parent啊,t1原本又是T,所以它当然能够转换成T,是这个概念吧,是不是这两句话都是对的啊,但是如果说我这个地方是new Parent();即

Parent t1 = new Parent();

那就好玩了,大家想想,我这句话还对不对啊,

if(t1 instance of Parent){

   T t5= (T)t1//这句话编译时应该不会出错的,运行的时候,报了类型转换错误,Parent类型不能转换成T的类型。也就是说这个东西,它要原本就是T,是不是才能转换成T啊,现在它new的就是Parent,它能不能再转换成T?那样能转换就坏了,为什么?那就不用子类了,我写一个类,我把它强制转换成所有我想要的东西,可能吗?变形金刚也没那么厉害。是不是这个概念啊,所以这个地方,大家看到,一定是要原本是什么东西,它才能转成什么,反过来,我这个地方能不能这么写:T t1 = new Parent();那就更不能了,编译就出错了,你new一个Parent,想要给一个子类,那是不可能的,因为Parent小,子类T大,所以说这个大家稍微了解一下。

   t5.t2(5);

}

这是instanceof,好,这个地方就提到了

向上强制类型转换总是允许的,什么叫向上呢,就是子类向父类转,这是可以的,这个强制类型转换是可以的,但是不存在向下类型转换的,那有的同学说,怎么没有向下转呢,如刚才的例子

Parent t1 = new T();

T t5 = (T)t1;

不就是强制转换成子类了吗?大家注意一下,这个叫强制类型转换没错,但是算不算向下造型呢?这是不算的,这个地方只是还原的啊,它原本是什么类型,我们就给它还回来啦,仅此而已,我们只是还回来了,没有做别的操作。好,像这个大家稍微了解一下就知道了,我们在java里头学过几种造型了啊,应该说是四种,一种叫自动造型,即自动升级,一种叫强制类型转换,一种叫表达式升级,一种叫向上造型,好,接下来我们再说一下动态绑定。

什么叫绑定?

将一个方法调用,同一个方法的主体连接到一起就称为“绑定”(Binding)。就是我们刚才说的,方法调用的时候,是不是真正拿出一个方法来运行啊,那么这个过程,就是将调用跟主体绑定在一起。

什么是动态绑定?

方法调用和方法体在运行时刻的连接就称之为动态绑定(dynamicbinding)。那么有动态绑定是不是就有静态绑定啊。静态绑定是什么?就是在编译期间就指定好的。java里头绑定的所有方法都采用后期绑定技术,后期绑定就是运行期绑定或者动态绑定,除非一个方法已被声明成final。final什么意思,马上就学到了。只有final是预先绑定好的,因为这个东西不再变了,否则全部都是后期绑定。这个我就不再强调了,上周我们是不是已经演示过了,就比如说在覆盖的时候,最常见的就是new谁就调谁。好,这个没啥可说的了,接下来该学第四个知识点了,那就是修饰符。

大家看到,我们在前面学的方法也好,学这个类也好,学属性也好,是不是都有一个叫做,前头第一个叫什么,叫访问修饰符,访问修饰符其实就是权限,然后就是什么,修饰符,一般修饰符是不是我们之前一个都没有讲过啊,但是我们都知道static是不是一般修饰符,那么接下来我们就要学两个,一个就叫static修饰符,一个叫final。然后到后面第七章还要学一个abstract,抽象的意思,也是一般修饰符。那么事实上,在Java里头后面学线程还要学一个synchronized,还有一个是我们不会学的叫native。好,下面我们就一个一个学,先学static修饰符。

static修饰符

static修饰符能够与属性、方法和内部类一起使用,表示是“静态”的。什么叫做静态的呢?我们看定义,static能够与内部类一起使用,也就是说,普通的类能不能使用,比方说我对上面的T类,我写成

public static class T extends Parent

能不能这样写,不能,人家说的是内部类里头才能加,没有说在这儿加,明白这个道理吧,好,这里它说表示它是静态的,那么什么叫做静态的呢,静态有什么含义呢?我们是不是要先把这个搞清楚啊,大家注意一下,所谓这个静态的含义就是动,就是不用去new实例,就是你不用new,我就能访问,这就是静态。我们知道,前面我们所有的实例、所有的属性、所有的方法,你都要怎么样才能调用,都要new一个类实例才能调用吧,但是静态的,它就有这么一个特点了,你不用new出一个实例,直接拿类就可以调用,明白这个意思吧,这个就是静态的,static,那好,一起来看一下,

public class T1{

   publicstatic String s = “java私塾”;

   publicstatic void main(String args[]){

      System.out.println(“s==”+s);

   }

}

这里面,我有没有new一个T类的实例啊,但是还是能够运行吧,原因就在于它是static静态的,还可以改为System.out.println(“s==”+T1.s);直接拿类名就开始调,明白吗,来,再编译一下,是不是还是通过的啊。这就是静态的含义,所谓静态的意思就是说能够不通过类实例,直接拿类就能够访问到的属性或者方法,我们就把它叫做静态的。现在理解这个是什么东西了吧。

类中的静态变量和静态方法能够与“类名”一起使用,不需要创建一个类的对象来访问该类的静态成员。包括成员属性和方法。所以static修饰的变量又称作“类变量”。前面讲变量的时候,变量有几种啊,分了几个级别,四个级别,第一个就是类级,现在知道什么是类级了吧,类级首先一定是属性,第二一定是static的,明白这个道理了吧,统称什么类级的变量、属性,就是既是属性又是static的。如果将上例中的static去掉,这就叫实例变量,加上就叫做类变量。那么,这个地方我想问大家一个问题,既然它能够用类去掉,那么我用实例调行不行,大家注意下,实例同样是可以的,它实际上是不是相当于级别更高的啊,比方说上例,我先new一个对象,然后再调

T1 t = new T1();

System.out.println(“s==”+t.s);

同样可以运行。所以说它不单纯可以通过类调,是不是实例也能调啊。那大家再想想,既然能够通过类去掉,何必再去创建实例呢。是这个道理吧,大家想想,从这个程序的角度来讲,哪一个更节省资源,很明显嘛,你去创建一个实例,要不要分配内存空间,肯定要吧,那我记让能不创建,不就是节省内存了嘛,所以能用类调的何必用实例去调呢,这是调属性,方法是不是一样啊,来,看一下吧,刚才光演示了属性,接下来再演示一下方法,在T1类中定义一个方法如下:

public static void aa(){

   System.out.println(“aaa”);

}

那么在main方法中怎么调,一种是我直接写

System.out.println(“s==”+s);

a();

编译运行一下,走了吧。

好,这就是静态,接下去我们看这个

上头理解了概念过后,我们就来看看static属性的内存分配

static属性的内存分配

static属性的内存分配是相当有特点的,有什么特点呢?大家看清楚啊,

一个类中,一个static变量只会有一个内存空间,虽然有多个类实例,但这些类实例中的这个static变量会共享同一个内存空间。这句话能理解吧,画个图来给大家说。这个东西呢,用图来说比较直观。

这里面,java会为static的属性专门分配一个空间,叫做T1的static池,就是凡是T1里定义的static变量通通都在这个空间里面,那么new T1()里的s就没有值了,而是指向T1的static池里的s=”java私塾”,就相当于这么个意思,明白这个概念吧,这个地方跟我们前面讲的就有一点点不同了,多出来一个T1的static池,如果我这个时候我再去new一个实例t2,又new出一个空间,但是这时候里面的s和s1还是指向同一个空间。这就是有static的时候,内存分配的示意图,那么为什么要让大家理解这个呢,原因很简单,这就意味着,如果我透过t2去改s1这个变量, 那么t里面的s1改了没有,改了吧,就是要让大家理解这一点,所以这里要让大家理解这句话,一个static变量虽然有多个类实例,但这些类实例中的这个static变量会共享同一个内存空间。大家看到,上图中不同的两个类实例,不同的类实例静态的指向的是不是同一个内存空间啊,这就意味着static变量相互之间是有联系的,意思就是一个改了,第二个也会跟着改,那有同学说真的吗,你现在是不是只画了一个示意图啊,你又没有程序跑给我看,真的是这样子的吗,咱们就把这个例子来做一下,咱们改怎么测试呢?

public class T1{

   publicstatic String s = “java私塾”;

   publicstatic String s1=”java私塾1”;

   publicint a =3;

   publicstatic void main(String args[]){

   T1 t1= new T1();

   T1 t2= new T1();

   t2.s=”haha”;

System.out.println(“t.s==”+t.s+”,t2.s==”+t2.s+”,T1.s==”+T1.s);

   }

   publicstatic void a(){

      System.out.println(“aaaaa”);

   }

}

 

编译运行,输出结果是t.s=haha,t2.s=haha,T1.s==haha

三个是不是都是haha啊,为什么啊,原因很简单,三个是不是指向的是同一个内存空间啊,明白这个意思了吧,所以说,这一点:这些类实例中的这个static变量会共享同一个内存空间,大家一定要牢记,这是非常重要的一点,好,再看一下static属性变量的初始化。

刚才讲的是static属性的内存分配,再看看什么时候初始化呢,也很有特点。大家注意一下,对于static属性,就像上例中的s和s1,他们是什么时候就有了的,也就是说什么时候就有了T1的static池这个空间的,实际上在类装载的时候它就有了,这是什么概念呢?就是说这个类刚开始运行的时候,是不是我们编译完了,是不是先class loader装载,装载完了是不是开始字节码校验,然后再解释执行,所以说这个东西是在类装载完成的时候这个空间就已经画出来了,这叫共享的内存空间,也就说static池的出现比两个newT1()空间出现的要早,这就是属性初始化的一个特点。

属性初始化

static的变量是在类装载的时候就会被初始化。也就是说,只要类被装载,不管你是否使用了这个static变量,它都会被初始化。

大家来理解一下这句话,static变量既然是类装载的时候就初始化,那么你猜猜看,它会被初始化几回啊,是不是只有一回啊,为什么?因为类只会被装载一回,那有同学说,难道我每次调它的时候不会装载吗,你觉得它会那么傻吗,装载完了,它就把这个空间留在那儿了,只装载一次,不会傻的每次都去装载。那么反过来讲,static变量只会被初始化一回。但是前提是在同一个虚拟机中,不同的虚拟机要重新装载的,就好比一个程序在我的机器上装载了,以后可以运行了,但是拿到你的机器上,是不是还要重新装载啊,当然要了,所以这点,大家注意一下就可以了。好, static的基本概念这几点刚才就给大家演示过了,图也画了,例子也写了,剩下的就该大家好好的去理解,再来看看static的基本规则,这些规则呢咱们也一个一个来,边看边用代码来测吧。

static的基本规则

1、一个类的静态方法只能访问静态属性

什么概念,意思就是说,直白点说,就是静态方法不能直接访问非静态属性。比方说上例,我在main方法中直接访问a1,对还是不对。

System.out.println(“a1==”+T1.a1);不对,a1是一个实例变量,而main方法是一个static方法,静态方法不能访问非静态变量。编译看一下就知道了,这里面我想问大家,为什么静态方法不能访问非静态变量?因为这是个静态方法,它可以不经过实例,直接拿类就可以调,拿类直接调的时候,有没有a1这个实例变量,没有吧,那你现在叫人家去调这个实例变量,是不是就出错了,因为还没有造出实例呢,你怎么调呢,对吧,所以大家看到,人家是有道理的,好,这是第一条规则

2、一个类的静态方法不能够直接调用非静态方法,道理是不是一样的,因为它还没有绑定好这个东西啊,所以你就不能调非静态方法。这个我就不演示了

3、如果访问控制权限允许,static属性和方法可以使用对象名加.方式调用,是不是就是类名加.(T1.s)这个概念,当然也可以使用实例加.方式调用。这个刚才是不是也演示了,就是t2.s。

4、静态方法中不存在当前对象,因而不能使用“this”,当然也不能使用”super”;比如上例”a1==”+a1改成“a1==”+this.a1,编译之后报错:无法从静态上下文中引用非静态变量this,this其实代表的谁?是对象还是对象的实例,是不是就是当前New的那个实例啊,对吧,当前那个实例叫this,所以你这样一看,马上就知道了,我都没有实例,我上哪儿来的this啊,有this才奇怪了,同样道理,没有this,有没有super,一样道理,所以说this和super不能出现在静态方法中。

5、静态方法不能被非静态方法覆盖,前面讲覆盖的时候,是不是没讲啊,因为那时候不懂,大家想想为什么?原因也很简单,如果一个静态方法被一个非静态方法覆盖了,那你到底是拿实例调还是拿着类调呢,对吧,你拿着类调,你想想这个道理,它立马就会出错,比如我们以前面T.java为例,

6、构造方法不允许声明为static的,其实仔细想,这些规则是不是都是有原因的,为什么不能呢?大家想想,根据我们前面讲的,这个时候就要开始把之前讲的东西结合在一起,你就这么想啊,先不说别的,先讲构造方法,构造方法我们说是一种回调方法,在这个回调方法里头我们通常都干些什么,是不是去赋初始值啊,给属性赋初始值,前提是什么?是不是这个对象空间已经有了啊,属性已经有了,是不是然后在这个空间里给属性赋值啊,好,假设这个时候它可以是static的,你看,它会出什么状况,我是不是可以直接拿着类去调这个方法, 我不是new,而是直接调,那么这个时候有没有内存空间,没有,我们说过,内存空间怎么来的,是不是靠new出来的啊,那么这个时候,我直接去调, 没有new啊,那好,没有内存空间,那这个时候我又要给属性赋值,请问我属性值放哪儿,对吧,它再厉害,是不是也得有空间,你才能放东西,所以,这样同样会造成错乱,明白这个概念吧,所以说,简单一想就知道,这个构造方法绝对不可以是static的,就是这样子。

好,这个地方,大概就是这6个基本规则,大家注意一下。还有一点注意下:

注:非静态变量只限于实例,并只能通过实例引用被访问。这是不是就是严格区分开类变量和实例变量。好了,static应该也就差不多了,

static这个基本的概念、语法及其两个重要特点,一个是属性共享,同一块内存空间,是不是刚才讲的啊,还有就是运行,装载的时候就初始化了,这些都是它的特点,当然这个static严格的讲啊,其实就算是基本讲完了,后面两个是稍微扩展一下,我们看一下,第一个

静态初始器——静态块

静态初始器什么概念呢?我们前面写程序的时候也给大家写过,就是在属性上写块,大家见过吧,原来叫属性块,对不对,那么这个时候有同学说,因为这个属性块里是不是能写代码啊,那有同学说,能不能把这个块整成静态的呢?可以,只要在块之前加static既可,这就叫静态块。按照我们前面讲的,属性块的地位其实跟谁差不多,是不是跟属性差不多啊,那么这个静态块跟谁差不多啊,就跟静态的类变量是差不多的,那么,由于它没有名字,能不能从外面调,不能,但是,它的初始化也是在类装载的时候就先初始化了,明白这个概念吧,道理是一样的,类装载的时候而不是new一个类的时候,大家注意这个特点。对吧,这个就叫做静态初始器。当然这个功能通常用来初始化静态的类属性。因为原来有实例块,是放实例用,那现在对于静态的变量,我想赋初始值怎么办,是不是可以用这个块来赋啊,对不对,这个大家体会一下,咱们来写点小例子。

 

静态初始器(static initializer)是一个存在与类中方法外面的静态块。静态初始器仅仅在类装载的时候(第一次使用类的时候)执行一次。

静态初始器的功能:通常用来初始化静态的类属性。

public class T1{

   publicstatic String s = “java私塾”;

   publicstatic String s1=”java私塾1”;

   publicint a =3;

   static{

      System.out.println(“static==123”);

   }//新增代码

   {

      System.out.println(“123”);

   }如果这个时候我把底下的代码全部都注掉,这个时候程序有没有输出?输出谁?输出的是static=123,从这个地方大家可以看出来上面两个块是严重的不同,怎么不同法,static块是在类被装载的时候就被运行了,但是下面的实例块要等到什么时候,等到有人new的时候才会运行,那好,我此时把下面代码中接紫色代码,大家看,这时候输出结果是什么?是两个都出来,还是输出三句,我new的时候是不是又从属性从上到下挨个走一遍啊,那是不是两个块都要运行啊,大家说,输出两句还是三句,还是两句。静态块只会被运行一次,不像实例块,你new一个就被运行一次。可以多new几个演示看看。

   publicstatic void main(String args[]){

   T1 t1 = new T1();

   /*T1 t1 = new T1();

   T1 t2= new T1();

   t2.s=”haha”;

System.out.println(“t.s==”+t.s+”,t2.s==”+t2.s+”,T1.s==”+T1.s);*/

   }

publicstatic void a(){

      System.out.println(“aaaaa”);

   }

}

所以说,这样一比较,就是这个程序的小知识点啊,当然老师讲是一个方面,事实上还有一个问题,就算我讲了,因为内容多啊,也未见得你听到,为什么?你每个字都听的那么认真吗,就算听到了,也可能忘掉了,一节课这么多东西,你根本不可能全部记住,这也很正常,所以说,有时候我们为什么特别强调方法,所以说这个东西咱们就要慢慢练,不对你不要太着急,当然了,要正视这个问题,要想办法去解决,就是这样,这个训练呢,肯定是有一个过程,好,这个就是static属性。是不是这样就理解了。再看下面,

静态import

这个简单说两句就好了,什么概念呢?比方说我们举个例子,这是我们后面要学到的Math的类,

当我们要获取一个随机数时,写法是:

double randomNum = Math.random();

可以使用静态import写为:

import static java.lang.Math.random;

double randomNum = random();

这里import的不是一个类,而是一个具体的方法,本来import是不是import一个类啊,这里加上static,就可以在后面直接指向一个方法,前提是这个方法是静态方法,那么底下呢,大家可以看到,你就可以直接拿这个方法用,而不用调Math.,大家说哪个好,很明显是上面的那个好,首先,光从写的角度,如果我只用一回的话,我要少写一个static语句,那如果说我要用个几百回呢,好像看起来底下那个就有点优势啊,但是这里还有一个问题,什么问题呢,不明确,就比方说,万一不小心,random()挺好的,我自己写一个random()方法怎么办,我自己能不能写random方法啊,当然可以,那好,在调的时候我就糊涂了,看来看去,到底是在调Math.random()还是调我自己写的random,是不是就有点糊涂了,所以说有时候像这种东西,有些该省,有些绝对不要省,这里面一带上Math,我一看,一目了然,是不是这个代码就非常清晰啊,我就知道调的是Math的方法,所以说,这个道理啊,了解一下,知道有这么个语法,事实上我不鼓励大家用。好,static就over了,下面我们来看final,它比static简单多了

final修饰符

什么是final修饰符

在Java中声明类、属性和方法时,可以使用关键字final来修饰。final所标记的成分具有“终态”的特征。表示“最终的”意思。

什么叫做最终的啊,意思就是它加上去过后就不能再改变了,这就已经是最终方案,终结者,对吧。那么它有什么规则呢,一起来看一下

final的具体规则:

1、final标记的类不能被继承,为什么?它已经是最后的了吧,当然就不能再扩展了啊,对不对,咱们还是拿代码来示例吧。比如以T为例,如果我把父类标明为final,即finalclass Parent{},大家看,编译一下,大家注意看,报错了吧,无法从最终Parent进行继承。

2、final标记的方法不能被子类重写。也就是不能被覆盖,覆盖和重写是一个含义,比如说我们把Parent里的t1方法前面用final定义,那么子类T就不能再写覆盖方法t1了。

3、final标记的变量(成员变量或局部变量)即成为常量,只能赋值一次。到T1里演示,比方说我将public int a = 3;改成public final int a =3;编译运行一下,没有错吧,但是如果我在main方法中增加一句代码:t.a1 = 5:这时候再次编译,就报错了吧,为什么啊?因为a是final变量,它只能被赋值一次。那万一我在开始定义的时候没有赋值,那我在程序中还能继续赋值吗?如public final int a1;

我还能继续赋值吗?大家注意,还是不可以,那么如果说我吧某个变量标记为final,但是又忘了赋值,我该怎么办呢,看第四条

4、final标记的成员变量必须在声明的同时赋值,如果在声明的时候没有赋值,那么只有一次赋值的机会,(有且只有一次),放哪儿了,大家看下一句话,而且只能在构造方法中显式赋值,然后才能使用。我们可以在构造方法中来赋值,演示看看

public final int a1

public T1(){

   a1=4;

}

再次编译,就通过了。

5、final标记的局部变量可以只声明不赋值,然后再进行一次性的赋值

6、final一般用于标记那些通用性的功能、实现方式或取值不能随意被改变的成分,以避免被误用。这个其实只是告诉我们一般用在什么地方,

所以说合起来呢大家看到,final简单的就是用在类、属性和方法上,我们前面讲static用在什么地方,static用在属性、方法和内部类,这意味着什么,我能不能在一个方法内部,比方说,我在main方法内部定义一个static变量,如static String c = “cc”;,能不能这么写,这个是不能的,原因是什么?就是static不能用在局部变量里头,那final可不可以呢?final修饰的是类、属性和方法,好像也没有说变量,对不对?但是,我告诉大家,final可以修饰局部变量,举个例子,比如将刚才的static改成final,编译就对了,也就说局部变量也可以是final的,同样道理,我还可以怎么做呢?我们可以改自己写的方法,可以将方法的参数定义成final修饰的,为什么?因为参数列表也是局部变量,比如定义一个方法,

public static void a(final int a){

   System.out.println(“aaaa”);

}

在main方法中调用,如a(3);编译也是可以通过的。

这就是final,所以说总结起来,fianl可以用在哪些地方?可以用在类上,可以用在方法上,也可以用在属性上,哎!这个地方大家注意一个新的问题,如果将final、static同时用在一个属性上可不可以呢?大家注意,这是可以的,那么有没有顺序,通过演示,说明顺序是无所谓,如果将final、static、publilc三个顺序互换,这个时候对不对呢?也是对的。为什么啊?不是说访问修饰符在修饰符前面吗,其实这个没有严格的区分,访问修饰符和一般修饰符统称为修饰符,那么顺序就无所谓了,为什么还要分访问修饰符和一般修饰符,目的还是为了便于大家好理解,以后学反射大家就知道了,这些统称修饰符,所以说它们三个颠来倒去没有关系。final用的地方比static多。static只能放在内部类,那有同学啥是内部类呢?马上我们就来看一下,内部类不是重点,因为内部类这种写法吧,实话实说,有点混淆人的感觉,其实刚开始的时候,挺推崇内部类的,但是后来很快大家就不再用了,因为那个东西,在一个类里头再嵌套很多内部类,显得程序就挺乱的,内部类马上还要去学,但是学的简单点。接下来就看一下内部类,下面还是比较轻松的,了解即可。

内部类

什么是内部类?

在一个类(或方法、语句块)的内部定义另一个类,后者称为内部类,有时也称为嵌套类(Nested Classes

示例:

public class  Outer{//一个文件中只能有一个一级的public class

   privateint size;

   publicclass Inner{//内部类也可以定义为public

      privateint counter = 10;

      publicvoid doStuff(){size++;}

   }

   publicstatic void main(String args[]){

      Outerouter = new Outer();//先new一个外部类的实例

      Innerinner = outer.new Inner();//调用该实例的内部类,将内部类当成方法来用

      inner.doStuff();

      System.out.println(outer.size);

      System.out.println(inner.counter);

System.out.println(counter);//编译错误,外部类不能访问内部类的private变量

   }

}

编译过后,出现一个outer$inner.class的类文件,这个其实就是内部类编译完过后用$来区分,所以前面建议大家$不要乱用,在这儿就体现出来了吧,它在内部类中是有特殊含义的。

注意:out.new Inner()这样的语法,其实是因为对于Outer类来说,Inner类相当于一个成员类,所以不能直接访问,应该用实例变量点取。如果把Inner类定义成static的,那么就可以直接Inner inner = new Inner();了。

这个就是内部类,其实是当一个方法来用,由于inner是一个类,所以new一个inner()来调。

再看内部类的分类

内部类按照使用上可以分为四种情形:

(1)类级:成员式,有static修饰,在一级类下面定义,当成一个属性或者一个方法

(2):对象级:成员式,普通,无static修饰

(3):本地内部类:局部式,在方法内部

(4):匿名级:局部式

举个例子:

成员式内部类

public class Outer1{

   privateint size;

   publicclass Inner{

      publicvoid doStudff(){

        size++;

      }

   }

}

成员式内部类如同外部类的一个普通成员

成员式内部类的基本规则:

(1)可以有各种修饰符,可以用4中权限、static、final、abstract定义(这点和普通的类是不同的);

(2)若有static限定,就为类级,否则为对象级。类级可以通过外部类直接访问,对象级需要先生成外部的对象后才能访问;

(3)内外部类不能同名

(4)非静态内部类中不能声明任何static成员

(5)内部类可以相互调用如下:

class A{

   classB{}//B、C间可以相互调用

   classC{}

}

接上例

public class  Outer{//一个文件中只能有一个一级的public class

   privateint size;

   publicclass Inner{//内部类也可以定义为public

      publicint counter = 10;//private改成public

      publicvoid doStuff(){size++;}

   }

   publicclass Inner2{

      publicvoid doStuff(){

        Innert = new Inner();

        System.out.println(t.counter);

      }

   }

   publicstatic void main(String args[]){

      Outerouter = new Outer();//先new一个外部类的实例

      Inner2inner = outer.new Inner2();//将Inner改成Inner2

      inner.doStuff();

      //System.out.println(outer.size);这两行注掉

      //System.out.println(inner.counter);

System.out.println(counter);//编译错误,外部类不能访问内部类的private变量

   }

}

编译一下,大家看看,在Inner2中调Inner,Inner t = new Inner();就是当成一个方法一样,一个方法里直接调另一个方法,就这么个意思。

成员内部类的访问

内部类访问外层类对象的成员时,语法为“外层类名.this.属性”

使用内部类时,由外部对象加”.new”操作符调用内部类的构造方法创建内部类的对象。

这些概念大家有空就看看,没空就算了,这不是什么重点。我演示一下,你熟悉一下就可以了,基本用不上。

本地内部类

本地类(Local class)是定义在代码块中的类,它们只在定义它们的代码块中是可见的。

示例:

public final class Outter{

   publicstatic final int TOTAL_NUMBER = 5;

   publicint id = 123;

   publicvoid t1(){

      finalint a = 15;

      Strings = “t1”;

      classInner{

        publicvoid innerTest(){

           System.out.println(TOTAL_NUMBER);

           System.out.println(id);

           System.out.println(a);\

           System.out.println(s);//不合法,只能访问本地方法的final变量

           }

      }

      newInner().innerTest();

   }

}

本地内部类几个重要特性:

(1)仅在定义了它们的代码块中是可见的

(2)可以使用定义它们的代码块中的任何本地final变量;

(3)本地类不可以是static的,里边也不能定义static成员

(4)本地类不可以用public、private、protected修饰,只能使用缺省的

(5)本地类可以是abstract的。

匿名内部类是什么?

匿名内部类是本地内部类的一种特殊形式,也就是没有类名的内部类,而且具体的类实现会写在这个内部类里面。把上例改造一下:

public final class Test{//类名改为Test

   publicstatic final int TOTAL_NUMBER = 5;

   publicint id = 123;

   publicvoid t1(){

      finalint a = 15;

      Strings = “t1”;

      newAclass(){//改为new Aclass()

        publicvoid testA(){

           System.out.println(TOTAL_NUMBER);

           System.out.println(id);

           System.out.println(a);\

           System.out.println(s);//不合法,只能访问本地方法的final变量

           }

      }.testA();//增加改句

   }删除原来的代码

}

class Aclass{

   publicvoid testA(){}

}

注意:匿名内部类是在一个语句里面,所以后面需要加”;”

匿名类的规则:

(1)匿名类没有构造方法

(2)匿名类不能定义静态的成员

(3)匿名类不能用4种权限、static、final、abstract修饰

(4)只可以创建一个匿名类实例

好,四种内部类就是这四种,成员式有两种,一种对象级,一种类级,还有就是方法内部类和匿名内部类。好,接下来我们看java的内存分配

再谈java的内存分配

事实上我们前面已经接触到了java的内存分配,只要是通过内存分配示意图来讲的,但是示意图毕竟不是完整的内存分配的知识体系。那我们一起来看一下

java程序运行时的内存结构分成:方法区、栈内存、堆内存、本地方法栈几种。

栈和堆都是数据结构的知识,如果不清楚,没有关系,就当成一个不同的名字就好了,下面的讲解不需要用到它们具体的知识。

下面先看第一个,方法区

方法区:方法区能用来干嘛

方法区存放装载的类数据信息

什么是类数据信息啊,前面我们写的java文件,这个java文件编译成了什么?是不是字节码啊,对不对,那我们写的这个程序编译成字节码,好,这个字节码是不是被class loader来装载,装载的时候,这个字节码放在哪儿呢?包含你的类的定义、方法的定义扔到哪儿去,就扔到这个方法区,明白这个意思吧,也就是说,事实上,大家想想,我们在前头讲,new一个对象实例的时候,实际上是不是分配一块空间,那块空间专门放什么,专门用来放属性的值,那么这个类的方法呢?这些就扔到这个方法区里,对吧,大家看一下,这个方法区里有哪些信息呢?

(1)基本信息:

   1)每个类的全限定名,什么叫全限定名,简单点说,就是带包结构的类名

   2)每个类的直接超类(父类)的全限定名

   3)该类是类还是接口

   4)该类型的访问修饰符

   5)直接超接口(父类)的全限定名的有序列表

这些东西直白点说就是类的定义信息全部都放在方法区里。

(2)每个已装载类的详细信息:

   1)运行时常量池:存放该类型所用的一切常量(用fianl定义的)(直接常量和对其他类型、字段、方法的符号引用),它们以数组形式通过索引被访问,是外部调用与类联系及类型对象化的桥梁。它是类文件(字节码)常量池的运行时表示。(还有一种静态常量池,在字节码文件中)

   2)字段信息:类中声明的每一个字段的信息(即属性的信息)(名,类型,修饰符)

   3)方法信息:类中声明的每一个方法的信息(名,返回类型,参数类型,修饰符,方法的字节码和异常列表)

   4)静态变量,static

   5)到类classloader的引用:即到该类的类装载器的引用

   6)到类class的引用:虚拟机为每一个被装载的类型创建一个class 实例,用来代表这个被装载的类。

一句换,简单点说,方法区里面主要放的就是有点像把这个类文件大卸八块,各自分门别类的存放在那儿,包括你的属性、方法、类的定义、构造方法等等全部都放在方法区里。大家注意,方法区里,这些东西会存放几个,就是说一共会存放几次这些东西啊,是不是只要一次就够了啊,这就是第一个,方法区。第二个,栈内存

栈内存

java栈内存以帧的形式存放在本地方法的调用状态(包括方法调用的参数,局部变量、中间结果等)。调用状态这说明栈内存存放的是静态的还是运行起来的这些东西,是不是运行起来的啊,也就是说,在方法中定义的一些基本类型的变量和对象的引用变量都在方法的栈内存中分配。

这个里头大家可以看到,它又分的很多

栈内存的构成:

java栈内存由局部变量区、操作数栈、帧数据区组成。这个下头我就不去一个一个去读了,为什么呢?因为大家对这个结构并不熟啊,对不对没有关系,那么对于栈内存,咱们简单的总结,大家都能听的懂的,什么意思呢?栈内存里面实际上直白点说,存放的,我告诉大家,一、基本类型,就是基本的值,不可改变的值,它多半会存在这个里头,那么还有一个,就是那些变量,就比方说,像我定义一个int a,这个变量名就存放在栈内存中,明白这个意思吗,这就是最典型的存放在栈内存里的东西,所以它的其他构成咱们就可以先不管它啊,对不对,好,这就是栈内存。

再看第三个,就是堆内存。

堆内存用来存放由new创建的对象和数组。一句话,只要new创建的就放哪儿啊,放到堆内存里头,在堆中分配的内存,由java虚拟机的的自动垃圾回收器来管理。从这个地方大家可以看到,栈内存能不能被回收,虚拟机的垃圾回收器管不管啊,不管,为什么呢?大家这个地方要注意,可能稍微涉及到一点点数据结构的知识,了解一下,栈这个数据结构的一些特点,简单点讲,什么叫栈呢,它存储的方式基本上就是先进后出,就是说,栈就相当于是这样,有个杯子,往里头一滴水一滴水的倒,先倒进去的就放到杯子底下,大家想想,这个效果就是杯口既是进口也是出口,先进的是不是在最底下,最先出来的就是最后倒进去的,这个东西就是栈的一个特点,是先进后出,所以对栈的操作的两个名字叫压栈和出栈,用英文表达就是一个叫push,一个叫pop,往一个栈里头放东西叫push,往外取东西叫pop,这个就是让大家先了解最最基本的概念,那么有了这个概念过后,那为什么栈内存它不要那个垃圾回收维护了,大家注意看,是这么干的,栈内存里头放的都是一些像临时变量啊,还有就是曾经使用的这些东西,它就会怎么办呢?一半它认为这个用完了,它会把这个从栈里头弹出去,它自己会清除,也就是说当这个变量用完了,是不是就弹出一个栈,弹出一个栈就相当于栈内存就被清空了,明白这个概念吧,也就是说,栈这个东西是由它自身去维护的,而堆里头的东西就由谁去维护,垃圾回收器,就是前面讲的虚拟机的垃圾回收来做的,那么这个大家想想看,方法区有人维护没有,大家注意啊,方法区是没有人维护的,为什么啊?方法区是不是类装载的时候就把方法区里的东西放在里头了啊,那个东西应不应该被干掉啊,当然不应该,你把这个东西干掉了,那我一调这个类,是不是还得重新装载啊,所以方法区里的东西一旦装载了它就一直要用,直到虚拟机关闭。所以大家看到,它们各有特点,栈内存是自己维护,堆内存由垃圾回收器来管理,那它为什么要用垃圾回收器来管理,原因是什么?大家看到,new分配的空间,这说明什么,说明还是很耗资源的啊,所以说呢,在java里头编程,有时候都会讲一个特点,要尽量复用对象,不要动不动去new,为什么?因为你每new一个,都要浪费掉一部分空间,对吧,就这么个概念,好了,那么对于栈内存和堆内存的一个比较,大家看到,

栈内存和堆内存的比较

栈的优势是,存取速度比堆要快,栈数据可以共享。缺点是存在栈中的数据大小与生存期必须是确定的,缺乏灵活性。简而言之,栈中主要存放一些基本类型的变量(int、short、long、byte、char、float、double、boolean)和对象句柄。什么概念呢,就是变量存放在栈里头,那个对象的变量不是要指向一个内存空间,内存空间在哪儿,内存空间在堆里头,是不是一个变量要指向一个内存空间啊,好了,它怎么指向,真的是像我们那样去画一根线吗?不是的,它是就在那个栈里头,比如说有一个变量叫a,这个a是不是有个值啊,这个值就是堆里头内存的首地址,就是说,它是不是就在变量里头记这个内存的首地址,这样的话,我是不是就可以通过这个变量找到谁,找到这个内存空间,明白这个概念吗,它是不是就能找到那个堆了啊,对不对,就这么个意思,好了,所以说,简单点说,栈和堆咱们理解不要太深的话,简单点讲,栈里头就放一些基本数据类型还有变量,堆里头就放啥,new的东西就放堆里头,那么栈和堆怎么联系起来呢,那就是栈的这个变量的地方放对应的内存的地址的首地址,理解到这些就可以了。还是画图对于大家可能更加好理解。栈还有一个很重要的特性,就是存在栈中的数据可以共享。实际上,这个地方我还要补充一句,栈当中还有一个毛病,除了压栈和弹栈之外,栈当中的数据不可以改,这个数据一旦进栈了,它就跟final一样,不能改了,看我举个例子,比方说,我定义

int a = 3;

int b =3;

你知道java虚拟机会怎么做啊,我画图给大家解释一下。

比方我画一个栈内存,首先在栈内存里头创造一个变量叫a,然后呢,就到这个栈内存里头去找,因为基本数据类型是不是都在这里头啊,它去找,看看有没有3,如果它找到一个3,刚好找到了,那就怎么办,找到了,它就直接让a指向3,也就是说,这个栈内存严格的讲,其实也是存放了这个3的地址了,对不对,好,这个时候,又来int b=3,它首先是不是要创造一个变量叫b,这个时候,同样在栈内找有没有3,有没有啊,有,它找到了过后,怎么办,就会用b也指向3,明白这个概念吧,也就是说,这个时候大家看到,我们前头学过一个a==b,这个时候a等不等于b,是不是等于啊,不管是从内存地址也好,还是从这个值也好,是不是a和b都是相等的啊,是不是都指向同一个地方,所以说你说内存地址等于,你说值它还等于,所以说a==b是true,这就是这么一个概念,好,这就是栈的内存,好,如果这个时候大家再看,再令a=4;那这个时候该怎么走呢,还是一样,先令a=3,找到了,指向它,b=3,找到了,也指向它,再令a=4,还是先找有没有4,比方说,找到了有一个4,这时候把a指向3的指向拿掉,变成a指向4,就变成这样的指向了,这个是不是跟咱们前头

栈内存

 

 

 

 

 

 

 


学过的,就是传方法的时候,比如说,从一个a赋给另一个变量a,就相当于是说,其实赋给它,就是把这个内存地址传递给它啊,这个你看有什么特点,a不会去改这个3,而是重新指向一个4,大家一定要注意这个特点,所以说,栈内存里头的一个特点就是什么呢?首先它是不可改变的,就像常量一样,说要用,就拿出来就用,所以才快啊,当然栈内存里头除了存放基本值,还有就是内存地址,是堆内的内存地址的首地址,所以我们前面画的这个,大家可以这么理解,我们前头画的所谓的内存分配示意图,实际上,左边的那个变量你可不可以看成就是栈内存啊,右边的那个东西就是堆内存,对吧,就相当于这么个概念,所以说,画出来是差不太多的,好,这是有关于栈内存和堆内存。事实上,这前头3个对于我们编程来讲都是蛮有意义的,一个叫方法区,一个叫栈内存,一个叫堆内存。再看最后一个。

本地方法栈内存

什么叫本地方法呢?就是java通过java本地接口JNI(Java Native Interface)来调用其它语言编写的程序,在java里头用native修饰符来描述一个方法是本地方法。本地方法里头也涉及到分配内存,这个就是本地方法栈内存,就目前来讲,我们不涉及做这一块儿,这块我们用不上,我们最多用前面的三个,这个大家了解一下就好了。最后我们来看一个特殊的东西

String的内存分配

String是非常特殊的一个东西,String怎么个特殊法呢?大家看一下,String是一个特殊的包装类数据,可以用

String str=new String(“abc”);

String str = “abc”;

两种形式来创建,第一种是用new()来新建对象的,它会存放于堆中。每调用一次就会创建一个新的对象。而第二种是先在栈中创建一个对String类的对象引用变量str,然后查找栈中有没有存放“abc”,如果没有,则将”abc”存放进栈,并令str指向”abc”,如果已经有”abc”,则直接令str指向”abc”。

这两种形式内存分配是怎么样子的呢?String首先有一个特点,就是String的值不可改变,既然String的值不可改变,大家想想看,你说它会怎么做,比方说String str = “abc”;它的内存分配图如下:

 

abc,值放在堆内存里头

 
       栈内存                              堆内存

 

 

 

 

 

 

 

    

 

从图中可以看出两个abc是不一样的,所以说大家来看一下,我们来测试一下,

public class T2{

   publicstatic void main(String args[]){

      Strings1 = “abc”;

      Strings2 = “abc”;

      System.out.println(s1==s2);

      Strings3 = new String(“abc”);

      Strings4 = new String(“abc”);

      System.out.println(s3==s4);

      System.out.println(s1==s3)

   }

}

 

运行结果为                     

true

false

false

再看一个例子,特别害人,怎么个害人法,大家看:

public class T3{

   publicstatic void main(String args[]){

      T3t = new T3();

      Strings3 = new String(“abc”);

      t.t1(s3);//我在这儿传一个s3,是不是该按引用传递了吧

      System.out.println(“s3==”+s3);

   }

   publicvoid t1(String s){

      s= “123”;

   }

}

请问,输出结果是什么?s3 = abc

很多同学会认为这个结果是s3=123,被什么害的,大家看一下,首先我们来画内存分配图,   ,

 

 

 

 

其实我们前面也讲过了,我们把String归到按值传递里头了,事实上,按值传递只是一种变相的效果,就其本质,它并没有按值传递,还是按引用传递,传的还是内存地址,所以说,String这个类它相当的特殊,就是说,它本身是一个类的类型,但是它的表现又像是基本类型,所以我们才把它单独拿出来讲。知道它特殊在哪儿了吧,实际上,按照我们画的内存分配图来看,它传的实际上还是地址,那我如果将上述程序中s = “123”;改为s = new String(“123”);输出结果又会是什么呢?还是abc,内存分配示意图改成:

 

 

所以说,不管你怎么折腾,main方法里的s3都是指向abc,不会改变的,所以这个东西它特别的特殊,表现是按值传递,实际上传的还是地址,其实这个无所谓,只要掌握内存分配示意图,管它是什么,拿过来你一画,结果是多少,图上立马就显示出来了,好了,这就是关于String的东西。

注意:由于String类的值不可变性(immutable),当String变量需要经常变换其值时,应该考虑使用StringBuffer或StringBuilder类,以提高程序效率。StringBuffer或StringBuilder类后面要讲,现在了解一下就好了。好,差不多内存分配也说完了,最后我们把第四章小结一下吧。

一、java中的继承

1、extends关键字

2、有继承的代码基本运行顺序:初始化子类必先初始化其父类,初始化一个类必先初始化其属性。这个东西大家真的要熟到什么地步呢,这些东西要熟到你完全没有意识,就是下意识的反应就是这样,明白这个概念吧,就是今后,包括我们的训练,尤其是思维方式,我现在就打个预防针,大家不要说,唉,这个好像我会了,我就放弃练习,错,你会和能做的很熟那是两码事,你做的很熟和到最后这个东西升华到你的思想里面去,又是两码事,所以说你们意义上的会和真的会差别很大,举个例子,比如说那些运动员,天天练那几个动作,你说他哪个动作他不会,他为什么还要天天练,因为事实上,真正在球场上的时候,他的动作全都是下意识的,你说吧,他从小练到大,练了那么多年,哪个动作他不会啊,实际上他不是说他不会,实际上他要熟到什么地步呢,就是往台子上那么一站,完全是意识反映,像条件反射一样,上去就是那个动作,必须要练到这个地步,所以说像咱们做程序也是这样,不要说这个东西我明白了就不练了,你这个会和真正的融会贯通还差的很远,到后来就发展到你不用想,随你提到什么东西,自然反应,它就是这么做,所以到后来训练java编程的思维方式的时候,就练到你忘了这件事,但是等你一出手写程序,就是什么,有的同学说明明我没想啊,反正写出来就这样,明白这个概念吧,就是要练到这个地步,先是有方法,再到练到忘记这个方法,就是完全是自然反应,这样才对,大家记着要注意这个问题,这个境界需要去锻炼。

3、单继承性

4、构造方法不能被继承

5、super关键字

二、方法的覆盖

1、什么是方法的覆盖:返回类型、参数列表、名字都相同,父子类中

2、到底运行哪个方法:new谁就调谁

3、覆盖方法的规则:权限只可以放大,不可以缩小,static方法不能被非static覆盖,final方法不能被覆盖,返回类型必须完全一样、方法名必须完全一样,参数列表的个数、顺序必须完全一样,异常列表只可以少,不可以多,其实就是方法的语法规则从前记到后,这个东西很简单,又好记

三、方法的重载

1、什么是重载:名字相同、参数相同,一般在一个类中,怎么确定调的谁,根据参数列表来判断调谁

2、重载的规则:权限没有限制,final方法可以被重载,返回类型无所谓,方法名称相同,参数列表必须相同,异常无所谓。

3、比较覆盖方法和重载方法

四、java中的多态

1、多态是什么?统一行为的多种不同实现或不同描述这种现象

2、多态和类型:多态就是某一个类型被分成很多不同的子类型

3、instanceof:判断这个实例原本的类型

4、多态对象的类型转换:强制造型,

5、理解动态绑定

五、static修饰符

1、修饰符的含义:表示静态的,就是拿类名就可以调

2、用static修饰符修饰过后如何使用:类名.***或实例名.***

3、static属性的类型分配:多个实例共享同一个内存区域,区域不变,值可以改变

4、static属性的初始化:类装载的时候

5、static的基本规则:6条

6、静态块

六、final修饰符

1、final修饰符的含义:最终的,能修饰类、属性、方法、变量

2、final的规则

七、内部类

1、什么是内部类

2、内部类的功能和特点

3、内部类的分类:成员式:类级、实例级   局部式:本地内部类、匿名类

八、再谈java内存分配

1、方法区:类的定义

2、栈内存区:基本数据类型的值、变量

3、堆内存:new出来的内存空间

4、本地方法栈内存

5、String的内存分配

 

 

 

 

 

 

 

 

 

第五章  数组

大家光看这一个,前头的那个“数”指的是什么啊?大家想想,猜猜看,按照我们以前讲过的,就是拿到一个东西你不用急着去看后面的东西,数、组,明显是拆分成两层意思了,数,指的是什么,数据,那么我们在前面已经学过了,跟数据相关的有什么东西啊,前面学过了数据类型,大家就要想到这么一个东西,就是这个数组跟我已经学过的或者已经会的那个数据类型有关系吗?是不是这个概念啊,当然这个地方我只是慢慢引导大家这种思维方式,这种思维方式你要持久的去练,以后每拿到一个东西,你都要好好的去练是这个意思,看到数组,你就去想,跟我以前学过的数据类型有什么关系吗?难道这个数就是以前所学过的数据,那我以前的数据是分类的,那这个数组也是指我这个分类的东西,那么后面组是什么?对我们来讲组是什么啊?是一个群体概念,就像我们说一个班集体,就相当于多个的意思,那么大家就要想,数组难道就是多个数据类型还是一个数据类型有多个值,是不是有这样子的一些揣测,但是你知不知道答案,你不知道答案,对吧,所以说这个东西,拿到过后,以后这个方法就是这样,就是拿到这个东西,有时候你觉得好像你在胡思乱想,其实不是这样,因为这个想法根据你会的东西来的,你长期这么的,以后你每拿到一个新的东西,当你会的越来越多,这就叫做技术的直觉,或者叫做技术的敏感性,这个时候单纯提技术,比如说我随便给你念一个技术词,对你来讲,你也就听到几个字母而已,为什么呢,因为你不了解这个东西,不熟悉这个东西,对它没有技术敏感性,对不对,但是如果对会这个技术的人来讲,你一提JCA,他的反应是什么?哦!那是干什么的,那是怎么怎么样,因为他懂这个意思,所以说他可能要听你怎么用这个东西,所以这个会的越来越多,那么以后,我就说过了,你拿到一个东西分析的时候,你就会分析个八九不离十,慢慢的就这样,比方说刚才我们数组我们分析了一堆,但是你都不知道哪个正确对不对,那好,这个时候就带着疑问来往下学,这种学习方法就是特别好的方法。就是你学的时候是有目的性的去学,带着疑问去学,也有可能你学下来,发现和你分析的有出入,那你就要想为什么我没想到呢,如果到后来,慢慢地你分析的就和它一样了,这个时候你接受起来是不是就很快了,跟你想的一样嘛,等于你看起来已经扫清了一个理解的障碍了,这个方法就是这样,这个方法第一节课的时候我就给大家讲过了,大家只要坚持这么去练,时间长了自然就能掌握这种方法了。好,下面我们看吧。

数组是什么?

数组是由相同类型的若干项数据组成的一个数据集合。集合就是一个组合啊,也就说,数组从目前看,其实就是数据的集合。翻译成大白话,就是一堆数据合在一起就是数组。就是把这些数也分成组,好了,一堆数据叫做集合,但是在前面它还有定语呢,并不是所有类型的数据放在一起都叫数组,它叫相同类型的若干项数据,这句话什么意思,也就是说,一个数组里头从这个地方大家可以看得出来,类型要不要相同,要,不能够出现其他的类型,相同类型的若干项数据,若干,可不可以为0,可以,也可以为很多个,大白话翻译过来就是很多个同类型的数据放在一起构成的这个东西就叫做数组,是这个概念吧,事实上呢,这个地方大家注意一下,从目前来讲,我们把数组是归为既算是一种数据的结构,也是一种数据的类型,就是把它看成是一种新的数据类型,那么这个新的数据类型归到哪儿呢,我们把数组归结为引用类型,那好,一提引用类型,有反映没有,想到什么了你们,一提引用类型就知道,引用类型是要放到哪儿?是不是在堆内存里头分配空间,还有呢,引用类型传递时候传的是什么?是地址,也就是说引用类型传的是地址进去的时候,相互之间是有影响的,是这个概念吧,这个就叫做技术敏感性,凡是见到一个东西,你学过的东西,那么你就有感觉,你要能马上回忆起来,迅速的回忆起来它相关的知识,并且用这些相关的知识来和现在学的东西进行类比,或者把现在学的东西往知识体系结构上加,你要长期坚持这么去学习,那这个东西学到后来,你慢慢就会有融会贯通的感觉了,因为你学的东西都不是孤立的,你学一个新的就必然把旧的就连带回忆复习了,那么这样反复多次,旧的和新的是不是就拉通了,对不对,这个东西你就好办了,好,这个东西大家看一下,现在说这个数组其实就是什么东西?对我们来讲,它是一种新的数据类型,或者是一个新的数据结构,那么是一个什么样的数据结构呢?它是用来存放很多相同类型的数据的数据集合或者数据组合叫做数组,这个理解就简单了,其实数组就是一个集合名词,就好像我说10NIIT1班,10NIIT班是个什么词,是个集合名词,指的是在座所有的同学,是这个概念吧,它并不单指某一个人,这个东西就相当于把某一个人看成人这个对象的话,合起来就是人的组合,人的群体,这就是数组。好,既然了解了数组是什么过后,关键是不是我们要来学在这个程序里面咱们怎么去用,大家现在再想想,一个数据类型,按照前面学的数据类型,咱们是按什么顺序学的啊,首先要学声明,一个数据类型是不是要先声明出来一个变量啊,然后学的是什么,是赋值,等你声明赋值了过后,后面是不是就开始学拿这个变量去用,怎么用,不外乎就是一种是取这个变量的值,还有一种就是给这个变量赋值,就是取值、赋值,也就说任何一个数据类型,最最基本的你要学会的就是如何声明、如何赋值,如何使用,使用又分取值和赋值,是不是这两种啊,无外乎就是这样嘛,而这个从哪儿来的,这个以前学基本类型的时候,是不是就是这样学的啊,如果说你要总结的话,前头就是这个步骤学的,那太好了,那么现在学数组,学这个类型的时候,就应该,这叫什么?这是不是用前面的学习方法去类比啊,那我看看我这个东西应该怎么声明,来,看一下

数组的声明

你可以声明任何类型的数组——原始类型或类类型

如:char s[];//定义了一个char的数组,那么这个【】在数组里头指维度,一个[]表示一维,声明了一个一维的char的数组。

   Pointp[];//这里Point是一个类,声明了一个一维的Point数组。

诸如此类的东西就太多了,咱们还是一边说一边写示例:

Public class Test{

   Publicstatic void main(String args[]){

      Inta[];//以下三种写法都是对的

      Int[] a1;

      Int[]a2;

      []int a3;//这个写法是错的,[]不能放在数据类型的前面

   }

}

Int a[]表示a这个数组里头只能放int类型的数据或者能自动升级成int类型的数据,比方说像char,byte,short。

在Java编程与语言中,即使数组是由原始类型构成,甚或带有其它类类型,数组也是一个对象。声明不能创建对象本身(声明本身我们说了,什么才能创建一个引用类型啊,是不是要New我们才能创建),而创建的是一个引用,该引用可被用来引用数组。

Java编程语言中数组的方括号可以位于变量名的左边或者右边都可以。刚才已经演示过了。

注意:1、声明不指出数组的实际大小。像int a[],我们有没有指明大小,就是你知不知道这个数组到底有多大啊?不知道吧,你只知道这是一个int类型的数组。

   2、当数组声明的方括号在左边时,该方括号可应用于所有位于其右的变量。这句话什么意思啊,这句话是说如果我一句话定义多个变量的话,举个例子,大家看到,前头我也讲过,比如说:

   intb1,b2,b3;//这句话的意思是定义3个int型的变量

   inta[],a1[],a2[];//这样定义对吗?对的

   int[] a,a1,a2;//这样定义也是对的,简化写法,说明a、a1、a2全部都是int型的数组。可以把int[]看成新的数据类型,a、a1、a2是这个类型的3个变量,这样理解也不无道理。我就是定义一个int[]当成一个新类型,后面3个变量就是这个类型的,是不是跟以前的方法一样了。

这就是数组的声明,就先说这么多,再来

我们说过了,声明是不是就要开始准备,现在它这里多出了一个创建,因为是一个引用类型,我们说引用类型,任何一个类,包括大家看jdk的时候,拿到过后,是不是要开始构造啊,因为我们说,引用类型,你必须要new一个实例,如果你得不到它的实例,你就不能操作,是不是这个道理啊,好,大家看一下,数组怎么进行创建。

数组的创建

使用new关键字来创建一个数组。创建的时候要指明数组的长度。就像刚才那个例子,我们定义了一个数组,a代表数组的一个变量啊,

int [] a,a1,a2;

a = new int[5];//刚才是定义了一个数组,现在要给它创建了一个空间,new是不是分配内存空间啊,意思就是说分配了5个int型的空间给a这个变量,那么这个时候a是指向,大家想想,是指向一个还是指向一个5个的群体,是不是指向一个群体啊,也就是这个地方就涉及到数组的内存分配,先看一下,数组怎么进行内存分配呢?它是有一个特点的,先看一下:

数组的内存分配

因为new关键字就是,我们讲过两个功能,一个是分配内存空间,第二点是初始化实例,所以到这个地方,它的概念也是一样的。画图演示一下:

比方说我们刚才new一个int型。

如:new int[3]

就是先在内存中开辟出一块空间出来,然后,再把这个空间里又分成了3块,这3块的内存空间是不是连续的啊,对吧,怎么连续的,是不是就是分的时候从一个大的块里头传下来的啊,所以大家看,它的内存分配有这样一个特点:

数组一旦被创建,在内存里面占用连续的内存地址。事实上它的分配就是这样,因为它是先看成一个整体类型,分出一个整块,然后再把这个整块分成3个,这就叫new int[3],明白这个意思吗?new int[3]过后,我们在前面还定义了一个a变量,让a指向这块空间,这里头注意一下,数组里头很特殊,什么意思呢?当你去new一个数组的时候,它因为是对象嘛,是不是就像,我们类的对象初始化实例一样,它也会自动进行初始化,假如说,我们把这个数组看成一个对象的话,它这里头的3个值是不是就相当于它的3个属性一样来看啊,所以说它也会自动做初始化,怎么自动初始化呢,就和咱们变量自动初始化的规矩是一样的,int型初始化成几啊,初始化为0是吧,也就是说这每个值自动是0,如果a= new Car(3),new的是Car这么一个类的类型,3个空间里面的初值就是null,null表示什么?是不是表示这个对象没有啊,不存在,所以说这个地方呢,比如说有同学说:

String s = “”;

String s = null;

这两个一不一样,这是大大地不一样的,像s=””,有没有内存空间,有,只不过空间里的值的空的,而s=null,s是不指向任何内存空间的,意思就是说它不指向任何内存,空间都不存在。所以大家注意这两个是不同含义的。

     

所以大家注意,数组是要做自动初始化的,这个其实好理解对不对,就相当于初始化属性一样啊,另外,大家注意一下,数组还具有一个非常重要的特点——数组的静态性(什么叫数组的静态性呢)就是说:数组一旦被创建,就不能更改数组的长度。这就意味着,我刚才已经new了new int[3],这个3能不能改,不能,就是这个数组空间这一块会不会改变,不会再改变,这是个特点,这点大家要注意一下,但是,如果是重新new的话,它就不是改这个空间了,而是重新分配出了一个空间,让a指向了一个新的空间,是不是这个概念啊,那原来这个空间是不是还是3个,不会变,对不对,这点大家注意一下,这就是数组一旦被创建,就不能更改数组的长度。好,这就是数组内存分配的示意图,理解了过后,大家再看数组创建是不是就简单了,

示例:s= new char[20]

      P= new Point[100]

第一行创建了一个20个char值的数组,第二行创建了一个100个类型Point的变量,创建了过后,是不是要给它赋值啊,所以,它并不是创建100个Point对象;创建100个对象的工作必须分别完成如下:

   p[0]= new Point();

   p[1]= new Point();

                                   new int[3]

这个大的空间叫a,可是这里面小的空间叫什么?它是不是要取个名字好来访问到它啊,对吧,那么这个在这里头怎么取的呢,它把这块叫做a,a是不是表示大的这块啊,然后它给它编了个索引号,从几开始呢,从0开始,也就是红色区域块表示a这个大块里头的第0号小块,就是第一号小块,注意,数组的索引是从0开始的,蓝色块和绿色块分别是a[1]和a[2],以此类推,如果后面还有很多,那就一直往下排了,中括号里面的数字在数组里头就叫做索引,对吧,大家来看一下,

用来指示单个数组元素的下标必须总是从0开始,并保持在合法范围之内——大于等于0并小于数组长度。任何访问在上述界限之外的数组元素的企图都会引起运行时出错。

数组的下标也称为数组的索引,必须是整数或者整数表达式,如下:

int i = new int[(9-2)*3];//这是合法的

其实,声明和创建可以定义到一行,而不用分开写。那该怎么定义,接上例:

int [] a = new int[5];

好了,创建我们是不是创建完了,数组是不是定义完了,学会定义了吧,一维的数组,接下来,就看,数组定义的时候会做初始化,那么什么叫初始化呢,我们说,一个类实例不能拿来直接用,要先初始化才能再用啊,初始化一个对象就要初始化其属性,数组也一样,既然是引用类型,想使用一个数组,你必须先new,new一个数组就一定要先实例化它的相当于它的属性,比如说像上面那个图一样,a想象成一个大的对象,a[0]、a[1]、a[2]就相当于它的三个属性,很类似的

好,下面我们看

数组的初始化

当创建一个数组时,每个元素都被自动使用默认值进行初始化。如果是基本类型就按照每种类型默认的值进行初始化,而引用类型初始化成null

注意——所有变量的初始化(包括数组元素)是保证系统安全的基础,变量绝不能在未初始化状态使用。

前面也学过,就像基本类型,局部变量是不是必须先初始化再使用啊,而属性变量是不是自动初始化,好了,初始化就讲完了。下面再看数组的快速定义

在前面例子里,int[] a = new int[5]定义完了过后,这里面有值没有,这里我a[0],是不是就代表第一个空间有值没有,有同学说有啊,几啊,0,可是我想让它有值怎么办呢,大家看清楚,这个时候先不看快速,先看看慢速的,慢速的就是如何进行数组元素的访问,

数组元素的访问

数组元素的访问,我们说分成两种,一种叫取值,一种叫赋值,大家看,就这个意思,a[0]这是不是就代表了数组的第一个元素啊,这个如果我这样写:a[0] = 3;这句话合起来什么意思,把3赋值给a[0]这个元素,是不是就有点相当于什么,就像这样,这个a[0]如果你把它看成属性的话,就变成下图所示了;

 

 

 

 

你把背景色去掉,是不是就和我们之前画的内存示意图一模一样了,看成了a的三个属性。所以说这个时候大家就知道了,怎么访问数组的元素呢,就是a[0]合起来看成一个属性或者一个变量就好办了,这个是不是就叫做赋值啊,这是不是就给一个数组赋值,给一个数组赋值大家看到,是不是就相当于给它的每个元素赋值啊,那么这地方我想再问大家一件事情,按照这个属性的初始化,初始化的时候,是不是所有的都在赋值啊,当然了也可以不赋值,那么这个地方,我是不是也可以对一些元素不赋值呢?比方说,我这个地方只给a[0]赋值,下面俩个不赋值,大家说对还是不对,这是对的,为什么?因为那两个有一个默认值是0,我可以给其中一个赋值,也可以给所有的都赋值,是不是没有任何问题,这就叫赋值。那赋值会了,我怎么再给它取出来呢,还记得我们以前讲过吧,放在等号左边就叫赋值,放在等号右边就叫啥?比方说,我定义一个int b = a[0];a[0]这个时候是不是就是一个变量了吧,就相当于定义一个变量就叫做a[0],合起来当一个整体看啊,这样你就好理解了,这就相当于把一个a[0]的变量赋给一个b,这就叫什么,这叫取值还是赋值,这对于数组元素来讲,这叫取值还是赋值啊,这就叫取值,太简单了,放到右边就叫取值,放到左边就叫赋值,是不是这个概念,同时我这么写可不可以啊,

System.out.println(“a[0]==”+a[0]);这么写可不可以,这叫取值还是赋值,这就叫取值,是不是把a[0]的值取出来,编译运行一下,输出结果为a[0]==3。搞定。

好,这是我们自己比较麻烦的取值、赋值。大家看一下

取值:所有元素的访问就通过数组的下标来访问

赋值:如果想要给某个数组元素赋值,就是通过数组的下标来赋。

好,下面我们再来看一下数组的快速定义

那么什么叫做快速定义呢?大家看到,如果我要完成我们刚才的这一系列工作,我要先声明,再创建,然后再赋值,最后是不是才能取值啊,大家看到,我是不是分开写,代码会比较多,是这个概念吧,比较麻烦,那么这个时候,就有人在想,这玩意儿能不能简单点啊,对不对,你这个东西太麻烦了,那好,简单点就怎么办呢,就有人想一个招儿,我们来做一个快速定义。

int[] a;

a = new int[5];

a[0]= 3;

a[1]=2;

a[2]=5;

大家来看,怎么快速定义法呢?就是把这些代码全部写到一句话里面去啊,比方说定义一个int[] c = {3,2,5};,那么这句话就跟上面的5句话是完全一样的,如果把这句话翻译出来,就是上面的5句话,明白这个意思吧,相当于什么概念,我定义了一个c的数组,长度为3,后面直接大括号表示什么,是不是就是里头放的元素从前到后,第一个就是第一个元素,第二个就是第二个元素,中间用逗号分隔,这个就叫数组的快速定义,明白了吧,再看下例:

String names [] = {“Georgianna”,”Jen”,”Simon”};

其结果与下列代码等同:

String names []= new String[3];

Names [0]=” Georgianna”;

Names [1]=”Jen”;

Names [2]=”Simon”;

这种速记法可用在任何元素类型。例如:

Myclass array[] = {new Myclass(),newMyclass(),new Myclass()};

注意下,上例我能不能这么写:

Int c[] = {3,2,5.0};这句话对不对

这句话就不对了,5.0是double类型,double不能赋给int类型。但是我可以写成这样int c[] = {3,2,’a’};可不可以,可以的,编译一下,通过了,为什么?是不是char自动升级为int型,对吧。Intc[] = {3,2,(int)5.0};这样就可以了。道理是一样的。好了,这就是快速定义。当然,这个地方我还想讲个几点,对于数组的快速定义,大家记清楚,只能是这么写:Int c[] = {3,2,5};,什么概念,就是声明和后面的大括号赋值必须写在同一个语句里面,而不能怎么写啊,大家看,

Int c[];//先定义一个c的数组

C ={3,2,5};//再进行赋值

这样写就是错误的了,那有同学会说,这两句话和在一起跟刚才那句话也没有什么区别啊,但是这个是不对的,语法规则要求不允许这么写,也就是说快速定义,必须直接定义在一行里头才行,一行的概念是指一个语句。

好,初始化讲完了,内存的分配也over了,元素的访问就是取值、赋值也讲完了,我会定义了,会创建了,也会使用了,那还需要学习什么呢?这个地方我们就学点,比方说,我们在赋值的时候,你是这么赋,还有取值,我想把数组的值全部取出来,那你怎么取啊,有同学说太简单了,我就一个一个取呗,那万一我有300个甚至我有3000个值呢,那你好了,上班一天什么都不用干,就在那a[0]、1、2……就做这个事情啊,这个东西肯定不能这么干,那么在这个里头,真正操作数组,它有非常的大的优势,什么优势呢?因为它的类型是一样的,大家都知道,类型一样这就意味着我是不是可以采用统一的操作方式啊,反正都是int嘛,那好了,这个时候其实我们就可以把循环派上用场了,比方说,我要给数组赋值,我怎么做呢?大家看

For(int i=0;i<a.length;i++){//数组总是从0开始,a.length是什么意思,length后面没有括号,表示它是一个属性,取的是数组的长度,什么是数组的长度呢,就是你在new int[5]的时候填的这个数字,这就表示数组的长度,这个地方是不是就取到了,这样我就可以赋值了

   a[i]= i+1;

}//循环赋值

For(int I =0;i<a.length;i++){

   System.out.println(“a”+i+”==”+a[i]);

}//循环取值

为什么要用for循环,而不是while循环,是不是因为一,大家看到,我是知道长度的,a.length是不是就是长度啊,第二,它的步长,是不是每次变化都是一个一个往上涨啊,而且它还是整数型,因此for循环和数组配合是最灵活的,当然你改成while也可以,但是没有必要,再来看一个更优化的for循环语句。

更优化的for循环语句

从jdk5.0开始,for循环有一种新的方法,大家注意,我们要学习这种新的写法

For(int I =0;i<a.length;i++)这个写出来是不是挺长的啊,那么这个东西呢很麻烦,相对而言呢,这人嘛,就是这样,我告诉大家,所有的人都想懒,对不对,不只是你们想懒,我也想懒,那些专家也想懒,这个时候呢,他们就想,因为数组用的很频繁啊,可以说它算是一种基础数据类型,用的很频繁,那能怎么办呢,每天都这么写太烦了,干脆整点快速的写法,怎么写呢,我给大家演示一下:

For(int d:a){//这句话的意思就是表达循环从a这个集合(数组)里面取出所有的d,类型是int型,也就是说它自动去循环,而且也不用管个数,它反正取完了就over了,这就意味着你就可以拿到每一个数组元素了,

   System.out.println(“d==”+d);

}

输出结果是:

d==1

d==2

d==3

d==4

d==5

但是比较遗憾的是什么,那有同学说我赋值怎么办呢,我能不能写成

for(int d : a){

   d =5;

   System.out.println(“d==”+d);

}

我把d赋值了5,那a的值会不会改啊?我将以下代码段放在刚才代码段后面

for(int I = 0;i<a.length;i++){

   System.out.println(“a”+i+”==”+a[i]);

}

如果说真的全部赋成5了,我底下打出来是不是全都是5,编译看看吧,能不能做这样的事情呢,结果是

D==5

D==5

D==5

D==5

D==5

a0==1

a1==2

a2==3

a3==4

a4==5

很明显,数组的值并没有改,这就说明,快速的for循环主要是用来进行取值用,赋值还是老实点吧,得一个一个往里去赋。这就是快速的取值,赋值这块还得老实的去做。OK了,这是数组的访问,基本的数组内容也就差不多了。

好,刚才咱们就把什么学完了啊,一维数组,我们说,一个[]是不是就表示一个维度啊,那么接下来我们就来学习二维数组或者是多维数组。

多维数组的基础知识

Java语言没有像其他语言那样提供多维数组。为什么呢?原因在什么地方呢?我们刚才学的,数组是存放同一个数据类型的多个数据,有没有说这个类型是什么类型,没有吧,可以是基本类型,也可以是引用类型,好,问题就在这个地方,既然这个地方它可以放引用类型,那这就意味着数组里面可不可以再放一个数组啊,你看我来定义这么一个类型,比方说,我来定义一个这个东西:

Public class Test{

   Publicstatic void main(String args[]){

      inta[] = new int[2];//定义了一个有两个值的一维数组,紧接着我要干什么呢?既然定义了一个一维数组,值为两个,那么我是不是就要去赋值,a[0]=多少,a[1]=多少,后面我只要是同一个类型是不是就可以啊,那么这个地方它是int型的,但是呢,大家看到,如果我在前面再加一个中括号,变为下面的红字

      int [] a[] = new int[2];//表示是一个int的数组,就是我往里头存放的不是int型的值,而是一个int型的数组,怎么办?这个时候大家应该看到,我是不是应该再继续定义一个一维的数组,如下:

      int[] a1,a2;

      a[0]=a1;

      a[1]=a2;

      这个时候如果我把int[]看成一个类型的话,这是不是一个一维数组,我现在就定义一个int[]的数组,是不是这个概念,int[]其实对我们来讲就是一个一维数组,那合起来就是定义了一个一维数组的数组,是不是这个概念,那这就变成几维啦,你看几个中括号了,二维,写出来你看a[0]=a1;意味着a[0]它赋的值是a1,是不是就是一个数组啦,而不再是一个单独的值,是这个概念吧,看明白刚才这一段了吗,意思就是说,为什么java不提供多维数组呢,太简单了,对它来讲,因为数组里头可以再放数组,所以不用提供多维,自然而然就形成多维了,你看,我这个数组里头如果放的是一维的数组,那我这个数组自然就成了二维数组了,那么大家看我刚才写的int [] a[] = new int[2];这样写对吗?编译一下,大家看到报错:不兼容的类型,为什么?因为左边需要两个维度,右边呢,只给了一个维度,是不是这个概念啊,那应该怎么办啊,只要再右边加个[]就好了,变为int[] a[] = new int[2][];再编译一下,就报错:未初始化的a1、a2了,为什么?a1和a2是不是还没有初始化,怎么初始化,加上

a1 = new int[1];

a2 = new int[2];

再次编译,就通过了。好,回过来,java为什么没有提供多维数组呢?

因为一个数组可被声明为任何基础类型,所以你可以创建数组的数组(数组的数组的数组,等等)。如果我要定义一个三维数组呢?很简单,int [][] a[],我还是把a[]当成一维数组,只不过这个一维数组的类型是int[][]类型的,所以三维数组就可以叫做一维的二维数组。注意啊,从这个角度来看,在java里面有没有多维数组这一说了,就没有了吧,归根结底,只有一维数组。这就叫一维的n维,就叫多维。这就是多维数组是不是很简单

Java多维数组的常用表达:

好了,对于多维数组他说,这种初始化的方法比较繁琐,通常的定义方式如下:inttwoDim[][] = new int[3][4]

这个[3][4]告诉我们什么啊,其实大家看到,二维其实就是什么啊,你看我把下表给你分解了,是怎么出现三行四列的呢?是不是这个概念啊,你看我来给大家画,就是怎么出现这么一个图的,你把这个图搞明白就等于你把这个来龙去脉弄明白了,我们首先是不是把int[][]当做一个类型,永远定义是一维数组对不对,那好,永远定义一维数组,他其实一开始是定义了一个a,指向一个大的空间,它画图方式是什么呢?首先你不用去管它后面[4],先当new int[3]来理解,这是不是就是一维的数组,意思就是把这个空间按照我们刚才的思路,是不是就分成三块啊,分别是a[0]、a[1]、a[2],在每个一维数组里存放

A[0][0]

A[0][1]

A[0][2]

A[0][3]

A[1][0]

A[1][1]

A[1][2]

A[1][3]

A[2][0]

A[2][1]

A[2][2]

A[2][3]

                  new int[3]    [4]

a

 

 

 

的时候又是一个一维数组,每一块又分为几块啊,4块,是不是这个概念啊,在每一块里头存放4个值,第一个小块就是a[0][0],以此类推,下面a[1]、a[2]也都分成4块,意思就是说每个数组永远都是几维操作啊,一维吧,所以说其实蛮好理解的,那好,这个时候又有同学提出来了,如果在后面再加一个中括号,new int[3][4][2],什么意思,就是把每一个小块再分成2块,每一个小块怎么称呼或者说怎么引用,是不是就是a[0][0][0]、a[0][0][1],上面那个表格实际上是不是相当于先分成三行,然后再把每一行又拆成了4个格,只不过它是怎么拆的,是不是左右这么拆的啊,为什么呢,因为二维数组,通常大家比较熟悉表格,表格是不是就是二维的啊,所以这里就是利用大家熟悉的东西在给大家画,那实际上真正的内存的分配是不是按照我们刚才画的才是对的啊,当然你竖着画也行,但是我横着画保持一个统一的方式,不管你多少维都是这样一步一步分下去的,这样好理解,如果你想用表格表达三维是不是就很难表达出来了啊,好,这个地方大家可以看一下,这就是一个二维的数组,它实际上就是按照行和列,也就是按照二维的维度查找每一个值啊,那么这个维度怎么划分,大家看到,从这个图里是不是看的也很清楚了,以后你实在分不出来,你就按照我刚才画的过程,是不是从大到小,每一次从一维里面再分一维啊,这样的话就分出来了,好,这个东西就是多维数组的常用表达。那么这里面的[][]我们还要再讲几点,这个时候我们先不讲三维,我们还还回去讲二维吧,这个地方先要讲这么几点,第一个,大家看到,我们把二维搞明白了,其他的是不是一样的道理啊,如果我定义一个二维的数组,那么这个时候出现一个什么问题呢,

Int [][] a = new int[2][],我能不能这么写,这么写对不对,就是后面那个【】我不指定维度,这个对还是不对,这个是对的,为什么呢?

因为你按照这个内存分配就看的出来,就是说二维数组是一维的一维对不对,我只关心第一次你划分成几块,后面的是不是你内部自己的事情了啊,你爱怎么划分就怎么划分,我知道你把大的内存砍成2块就完了啊,我只关心第一块就叫a[0],别的我就不关心了,至于a[0]里头怎么办,这个时候大家看,会出现一个新的问题,如果说我后面指定第二维是4,这就意味着每一块是不是都分成4块啊,这是不是就像一个矩阵一样,是不是整整齐齐的啊,但是如果这个时候我后面没有指定呢,这句话什么意思呢,意思就是说不就是个一维数组吗,我分成多少块随意,明白这个概念吗,就相当于你自己任意划分,这样就可能会出现什么现象,第一个格我分成2块,第二个格我可不可以不分啊,我就是整块,我0 啊,当然要是数组的话,我们还是分一下吧,比方我画成3块,可以还是不可以啊,这完全是可以的吧,是不是互不影响啊,但是我能不能在前头我一个都不定义,可不可以,这当然是不可以的,前头不能不分,前头如果不分就全乱套了,三维的我能不能只定义第一个数组的维度,后面两个是空的,可不可以,也是可以的,这什么意思啊,这意味着按照它的想法,我只要把第一块空间分完了,它其实只关心第一个,它从内存里头不是扒了一大块下来嘛,它是不是只关心第一个大块分成多少个小块啊,也就是说,就它的本质,它只关心谁,就关心一维,剩下的内存分配你自己随便折腾,明白这个概念吧,但其实你折腾不了什么,为什么,事实上现在我虽然只写了一个int [3],但是里头的,比方说是个二维的数组,我来进行定义

Int [][] a1 = new int [3] [];

Int[][] a2 = new int [5][];

A[0] = a1;//二维的赋给一个一维的是不是就是一个三维的了

A[1]= a2;

A[0]它里头放的就是二维数组啊,那好,这个时候就出现什么呢,就出现第一块空间里头放的是一个二维数组,分成3块,而第二块空间就分成5块,对吧,这就意味着它只关心第一块,分完了过后,每块就可以随意分了,意思就可以不对齐对吧,意思就是说数组不一定是矩阵式的,只要第一个一分,剩下的就随意了,定义方法就是只有第一个不能省,后面的都可以省略,那么再来,这里刚才讲的它不对称对不对,如果要它对称怎么办,后面不省略不就行了嘛,好,那么多维数组理解了过后,我们还是来定义声明创建一回吧,刚才我定义了一个三维的数组,现在就对这个三维数组进行操作,怎么操作呢?我想对三维数组进行赋值怎么赋,三维数组就要创建一个三个嵌套的for循环,为什么呢,说白了,就是我先取一维,循环3次,二维又有4个,我是不是又循环第二层,4个里面又分5个,所以我又循环第三层,

Public class Test{

   Publicstatic void main(String args[]){

      Int[][][] a = new int [3][4][5]

      For(inti=0;i<a.length;i++){

        For(intj=0;j<a[0].length;j++){//a[0]其实就是一个二维数组啊,这种写法只限于整齐的矩阵式的数组,如果不整齐那就麻烦了,所以一般不整齐的情况我们理解知道怎么做就行了,真正开发期间你可千万别写成不整齐的形式,为什么,不整齐就没法循环了

           For(intk=0;k<a[0][0].length;k++){

                                     System.out.println(“i=”+i+”,j=”+j+”,k=”+k+”value==”+a[i][j][k]);

           }

        }

      }

   }

}

编译运行一下,看看,输出60条值

这是取值,赋值就不用演示了吧

多维数组也可以直接定义并赋值,刚才是不是初始化全是0啊,我们把刚才的程序变换一下,大家看看怎么玩的,首先我们是不是当一维做啊,大家看清楚,我想这里给大家写一下这个过程,首先是个一维的,一维的里头是不是又是些数组啊,怎么画呢,我还是当一维的来画,

Public class Test{

   Publicstatic void main(String args[]){

      //Int[][][] a = new int [1][2][3]

      Int[][][]a = {

        {

           {1,2},{3,4}

},//每个里头是不是又是一个数组啊

        {

           {5,6},{7,8}

}

      };

      Newint[2][2][2];//这现在写出来是不是严格来讲就是222啊,所以说从我刚才写的过程就看得出来,三维数组我怎么赋的啊,先赋个一维,一维里头又赋个一维,一维里头又赋个一维,最后合起来就是三维了吧,编译运行一下,

最后看一下多维数组的本质

多维数组的本质:

N维数组就是一维的N-1维数组,其实平时多数用的还是一维数组,二维三维的也会用到,到三维以上基本就不会遇到了,其实你只要理解二维了,其他的都是差不多的,无非for循环多点嘛

好,下面来看一下数组的复制

数组一旦创建后,其大小不可调整,刚才说过了这是个很重要的特点,其实也是,内存都分好了,你怎么调啊,是不是就把位置就记下来了,那么这个时候呢,就可以把一个数组拷贝给另外一个数组了,这一块其实是想让大家,怎么说呢,学点简单的API,怎么看,你看它用的是不是System.arraycopy,找到以后,看一下怎么做的,

public static void arraycopy(Object src,//从这个地方拷出来

                             int srcPos,//从第几位开始拷

                             Object dest,//拷到什么数组

                             int destPos,//从什么位置开始放

                             int length)//拷贝多长

从指定源数组中复制一个数组,复制从指定的位置开始,到目标数组的指定位置结束。从 src 引用的源数组到 dest 引用的目标数组,数组组件的一个子序列被复制下来。被复制的组件的编号等于 length 参数。源数组中位置在 srcPos 到 srcPos+length-1 之间的组件被分别复制到目标数组中的 destPos 到 destPos+length-1 位置。

如果参数 src 和 dest 引用相同的数组对象,则复制的执行过程就好像首先将 srcPos 到 srcPos+length-1 位置的组件复制到一个带有 length 组件的临时数组,然后再将此临时数组的内容复制到目标数组的 destPos到 destPos+length-1 位置一样。

If 如果 dest 为 null,则抛出 NullPointerException 异常。

如果 src 为 null, 则抛出 NullPointerException 异常,并且不会修改目标数组。

否则,只要下列任何情况为真,则抛出 ArrayStoreException 异常并且不会修改目标数组:

·  src 参数指的是非数组对象。

·  dest 参数指的是非数组对象。

·  src 参数和 dest 参数指的是那些其组件类型为不同基本类型的数组。

·  src 参数指的是具有基本组件类型的数组且 dest 参数指的是具有引用组件类型的数组。

·  src 参数指的是具有引用组件类型的数组且 dest 参数指的是具有基本组件类型的数组。

否则,只要下列任何情况为真,则抛出 IndexOutOfBoundsException 异常,并且不会修改目标数组:

·  srcPos 参数为负。

·  destPos 参数为负。

·  length 参数为负。

·  srcPos+length 大于 src.length,即源数组的长度。

·  destPos+length 大于 dest.length,即目标数组的长度。

否则,如果源数组中 srcPos 到srcPos+length-1 位置上的实际组件通过分配转换并不能转换成目标数组的组件类型,则抛出ArrayStoreException 异常。在这种情况下,将 k 设置为比长度小的最小非负整数,这样就无法将 src[srcPos+k] 转换为目标数组的组件类型;当抛出异常时,从 srcPos 到 srcPos+k-1 位置上的源数组组件已经被复制到目标数组中的 destPos 到 destPos+k-1 位置,而目标数组中的其他位置不会被修改。(因为已经详细说明过的那些限制,只能将此段落有效地应用于两个数组都有引用类型的组件类型的情况。)

参数:

src - 源数组。

srcPos - 源数组中的起始位置。

dest - 目标数组。

destPos - 目标数据中的起始位置。

length - 要复制的数组元素的数量。

抛出:

IndexOutOfBoundsException - 如果复制会导致对数组范围以外的数据的访问。

ArrayStoreException - 如果因为类型不匹配而使得无法将 src 数组中的元素存储到 dest 数组中。

NullPointerException - 如果 src 或 dest 为 null。

这个地方,其实数组的拷贝并不是很常用,但是后面要讲常见类的应用,这里讲这个就是让大家先学会去看一个一个方法,像这种东西,比如说你要问,问同学也好,问老师也好,上班问其他同事也好,或者通过google查询也好,你只要查到这个方法,剩下你就不要再问了,千万不要问这个方法怎么用啊,这个话千万别问,为什么?这句话你问完了,人家真的就无语了,为什么?这个东西你不会直接从API上看啊,你问我怎么用,还不如让我帮你写得了,好,像这种东西从API上面看就可以了,好,我们来演示一下,咱们拿一维数组来演示吧,比如说

Public class Test{

   Publicstatic void main(String args[]){

      Int[] a = new int[3];

      For(i=0;i<a.length;i++){

        a[I]=i+1;

      }

      Intb[] = new int[8];//下面看我怎么拷贝

      System.arraycopy(a,0,b,0,a.length);

      System.arraycopy(a,0,b,5,a.length);

      For(i=0;i<b.length;i++){

        System.out.println(b[i]);

      }

好,编译运行一下

拷过来了吧,12300000,如果说我不想从0开始放呢,我想再拷一回,放到后面3个,该怎么放,接红色代码,再编译一下

好,这个就不再多说了,这就是数组的拷贝。最后呢,数组里头还有一个问题,叫做数组的排序,但是这里呢我加了一点东西,给大家讲讲一些基本的排序算法,这一提算法,有些同学就犯怵了,为什么?没学过啊,其实算法对我们开发应用程序而言,实话实说,大家接触到的用的范围其实并不是很广,因为那些复杂的算法实际上在大家目前的应用当中真不见得碰的上,当然也不是说完全碰不上,有些程序还是可能会碰上的,那么对于算法大概的概念,包括以后其他的算法,因为算法东西很多,那么对于算法大家应该持一个什么样的态度,一,每个算法你要大概理解它的功能,了解到它的写法,尤其它的思路,你要了解到,第二,算法本身的写法,你熟悉一下就好了,不需要去背,为什么呢,其实这个算法在java里头,就不止是java,包括C、c++也好,现在公开的算法都有标准的实现,就不需要我们再去研究它了,你要去研究它的话,就不要学java了,你该去学数学,研究这些东西是数学的事情,你要真能整几个特别经典的算法,全世界都来学,那你就不叫编程大师了,人家直接叫你数学家了知道吧,你只要知道个大概思路就行了,我们能看明白就行了,你是被动接受别人的思想,我们这里只是挑了几个算法,排序、查找这些都是最基本的算法,跟大家说这些就是让大家不要害怕,不要觉得我们好像没学过,一提算法就吓瘫了,实际上没那么严重,简单看一下,

冒泡排序

看看基本思路

对未排序的各元素从头到尾依次比较(假如说,升序排序的话,前头这个比后面这个大,是不是就把前头这个换到后头来,就这样依次的换,每一个都比较了过后,排序是不是就排好了啊)相邻的两个元素是否逆序(与欲排顺序相反),若逆序就交换这两元素,经过第一轮比较排序后便可把最大或最小的元素排好,然后再用同样的方法把剩下的元素逐个进行比较,就得到了你所要的顺序。

可以看出如果有N个元素,那么一共要进行N-1轮比较,第一轮要进行N-1次比较。(如:有5个元素,则要进行4轮比较,第三轮要进行5-3=2次比较)

      先看看程序吧,然后我再给大家画个示意图,或者我把过程给大家演示一遍,你就知道了,比方我先定义一个未排序的数组,

int a[] = new int[5];

a[0]=3;

a[1]=4;

a[2]=1;

a[3]=5;

a[4]=2;

我们看冒泡排序的程序

for(int i=0;i.a.length;i++){//外层首先是0,外层就是先选一个出来,就是拿它来冒泡,然后大家看清楚,为什么下面是j=i+1,而不是i,如果是i的时候是不是就跟自己比较啊,

   for(intj =i+1;j<a.length;j++{//注意j的开始值是i+1,因为按照排序规则,比a[i]大的值都应该在它后面

      if(a[i]>a[j](

        inttemp = a[j];

        a[j]=a[i];

        a[i]=temp;

      }

   }

}

白板演示比较过程

3  4    1     5    2

1  4    3     5    2

1  2    4     5    3

1  2    3     5    4

1  3    3     4    5

实际上是拿第一个位置和第二个位置比,和第三个位置比,依次比较,第一轮比完了,最后排在第一个位置的一定是最小的那个数,是不是后面的都比它大,同样道理,第二轮是拿第二个位置开始和后面的位置依次比较,这个时候是不是就不要和第一个位置的进行比较了,第一个位置已经排好了嘛,对不对,所以第二个位置就和第三个位置比,该交换吧,就变成3和4了,接下来拿第二个位置和第四个位置比,换不换,不换,再拿第二个位置和第五个位置比,换不换,该换了吧,是不是就变成1  2   4   5    3了吧,第二轮是不是也比完了,再拿第三个位置和第四个位置比,4和5比,不换,再和第五个位置比,4和3比,换,就变成1 2  3   5   4,再拿第四个位置和第五个位置比,5和4比,换,就变成1  2 3   4   5了,所以我拿这个过程没有去画图,因为画图其实还不如刚才这个演示的过程清晰啊,最后变完了,运行完了,1  2  3  4   5,Ok了吧,看明白了吧,你看这就是思路,大家要理解,这地方注意的呢,就是j=i+1,为什么呢,就是它和它后面的来比啊,第二个要注意的就是它的这个交换,交换是不是先定义了一个临时变量,因为你要交换,光是两个是没法交换的,为什么呢,我把我的值赋给你,是不是就把你的值盖掉了啊,这就意味着,你得先把你的值找个地方存着,然后呢,把我的赋给你,再把你原来的值赋给我,就完成了我们两个的交换,对不对,所以大家看到,对于这个算法,很简单,大家注意啊,冒泡排序大家最好还是要背着会写,为什么,就像现在有同学去面试,我告诉你,有一个面试题就是这样子的,给你个小题,要求你用冒泡排序,那么这个时候其实就是看他,事实上在笔试和面试的过程中,我告诉你,它考你的算法,一定都是基本的和简单的,你放心好了,顶多顶多看到二叉树,那就了不得了,绝对不会再往后考了,为什么,再往后考,首先第一,谁能背得出那么多,第二,后面的程序越来越大,你在这种不让我调试的情况下,说实在话,我搞不定他也未必搞的定,像他这种东西,其实是没有意义的,你这不就是考的背功嘛,因为算法的东西大家其实都是理解的,又不是我们发明的,都是来记一下,就像考java,就像默写java语法一样,没有什么太大的意义,但是它也考考常见的算法,是看看你了不了解这些基本的东西,因为像这种,一共写出来才3、5行,它认为你要是理解了,应该背着就能写出来了,它不会考你那些复杂的东西,所以说这个大家不用担心,基本的也就是考考排序,顶多再考考二分法查找对吧,就是排序和查找,顶多了,不会再考别的算法了,好了,这个就差不多了,如果要进行降序排列,就把if(a[i]>a[j])换成if(a[i]<a[j])就可以了,刚才是把小的排前头,现在把大的排前头就倒过来了。所以这个也没啥可讲的了。好,这个算法就这样吧。来接着往下看,

基本的排序算法——选择排序

那么什么叫做选择排序呢,大家看看它的思路是什么呢?

基本思路:从所有元素当中选择一个最小元素a[i]放在第一个位置a[0](即让最小元素a[i]与a[0]交换),作为第一轮;第二轮是从a[1]开始到最后的各个元素中选择一个最小元素,放在第二个位置a[1];依此类推。N个数要进行(N-1)轮。意思就是先选一个最小的放在第一个位置,然后再选一个最小的放在第二个位置,……是不是选到最后,是不是就排好了,那好,这个时候,比较的次数与冒泡法一样多,因为都是一个要和所有它后面的比较嘛,但是在每一轮中只进行一次交换,这什么概念,刚才冒泡是不是比较一次就可能交换一次啊,极端情况下,是不是比较一次交换一次,但是对于选择排序,它其实减少的就是交换次数,比冒泡法的交换次数少,相对于冒泡法效率高。大家看一下怎么做的:

//选择法排序

int temp

for(int i = 0;i<a.length;i++){

   intlowIndex = i;

   //找出最小的一个索引

      for(intj=i+1;j<a.length;j++){

        if(a[j]<a[lowIndex]{

           lowIndex=j;

        }

      }

      //交换

      temp=a[i];

      a[i]=a[lowIndex];

      a[lowIndex]=temp;

   }

还按照刚才的方法给大家演示一下:

3  4   1    5   2

先取3,拿3和4比,再拿3和1比,1小,再拿1和5比,和2比,这时候只得到一个值,经过内循环,它只是找到了最小的一个值的位置,然后再进行交换,就把1和3换过来了,变成

1  4    3   5    2

第二轮,拿4和后面的比较,先4和3比,3小,再拿3和5比,3小,再拿3和2比,2小,就把2这个的位置放在第二个位置a[1],

1  2   3   5   4

第三轮,依次类推吧

大家看到,每一轮比较过后,只交换一回,那么这种方式就叫做选择排序。对于这一个,乍一看,是不是和冒泡排序很像啊,你把交换放在内循环中,就是冒泡,放在外头,就是选择排序,所以这个也很简单,好,选择排序就说这么多,再看下一个

简单的排序算法——插入法排序

插入法排序它的思路是什么呢?

基本思路:每拿到一个元素,都要将这个元素与所有它之前的元素遍历比较一遍(看清楚,刚才选择冒泡都是和后面的元素比较),让符合排序顺序的元素挨个移动到当前范围内它最应该出现的位置。

这地方举个例子先看一下,就用前面的数组,我们要对一个有5个元素的数组进行升序排列,假设第一个元素的值被假定为已经排好序了,那么就拿第2个元素与数组中的部分(不就是第一个嘛)进行比较,如果第2个元素的值小,则将它插入到第1个元素的前面,如果比较完了,第2个比第1个大,就直接插在后面,取第三个的时候,就和前面2个比较,注意,它现在比较的方式是什么呢?如果第3个比第2个还大,还要不要和第1个比较,就不用了吧,第1个比第2个小,我比第2个还大,是不是肯定比第1个大啊,就不用比了吧,以此类推了,所以说,这个插入法排序从比较次数上来讲会减少,交换的次数也会减少,所以说相对而言,它的效率是不是更高一点,好,看看代码:

int temp;

for(i=1;i<a.length;i++){//i=1开始,因为第一个元素认为是已经排序好的

   for(intj=i;(j>0)&&(a[j]<a[j-1];j--){

      //交换

      temp=a[j];

      a[j]=a[j-1];

      a[j-1]=temp;

   }

}

我们再来演示一下它这个顺序,

3  4    1    5   2

你看程序是先从1开始,为什么?因为第0位先假定已经搞定了,所以大家看到,真正插入排序的时候,我们就认为第0个位置上的数就是最小的了,然后怎么办呢,j从i开始,就是从第1个位置开始,j要大于0,因为它是递减的嘛,如果它和它前面的那个比较,如果a[j]<a[j-1],是不是就不用交换了,那么就不用进循环了,所以第一轮

3和4先比较,即a[1]<a[0],进不进循环,不用吧,就不用动,然后紧接着j--,j就等于0了吧,因为要求j>0,是不是就不用循环,就跳出来,接下来,i=2了吧,也就是拿1进行比较了,注意,1首先和谁比,是和3比还是和4比,大家注意是不是(a[j]<a[j-1]啊,所以说1是和4比,1比4小吧,交不交换,交换,那就变成31452,这是首先1和4比,然后这个j--,j等于几了,等于1了吧,那就是a[1]和谁比,和a[0]比吧,说白了,是不是就是始终拿1和前面的值比啊,它又和a[0]的位置比,即1和3比,1比3小,又交换,所以第二轮比完就变成13452,再来,第三轮,i变成3了吧,就拿5和前面的比,5依次和4、3、1比,不变,所以第三轮结束,还是13452,最后第四轮,i变成4了,拿a[4]上的2和前面依次比较,2和5比,交换,然后和4比,交换,最后和1比,比1大,不动了,所以结果为12345,这就是插入法排序,从目前看来,插入法排序还是比较高效的,对吧,为什么呢?因为它的比较次数和交换次数都会减少,它的概念就是这样,如果倒序,直接将<改成>就OK了。这些程序大家把它编出来,然后测试运行看效果,这就不再多说了,好,最后一个排序,叫希尔(shell)排序,

排序的基本算法——希尔排序

希尔排序我们这里讲的是最简单的,现在只是了解一下,真正的希尔排序比这个要麻烦,看看它的基本思路

基本思路:现将数据按照固定的间隔分组(什么概念呢,大家看到,刚才极端情况下,5个数据比较,我是不是要比较4回,交换是不是也要交换4回啊,这个东西很麻烦,它就先来分组,比方说500个数据,我先分成5组或者10组,每组的数据是不是就少了啊,),例如每隔4个分成一组,然后排序个分组的数据,形成以分组来看数据已经排序,从全部数据来看,较小值已经在前面,较大值已经在后面;将初步处理了的分组再用插入排序来排序,那么数据交换和移动的次数会减少。可以得到比插入排序法更高的效率。先组内排序,再组间排序。如果我第二组的最小的比你第一组最大的还大,是不是直接把第二组放在第一组后面就可以了,通过这样来减少比较次数和交换次数。这个思路听起来好像是这么回事,但是做起来的时候,它有个麻烦的地方就是到底分几组是最合适的,而这个希尔算法为了专门就分几组它又整了个算法,明白这个概念吧,就是我先来算算整几个组合适,所以这个事情对我们来讲就很麻烦,所以咱们这儿做呢,没有去管分组的这个事情,看看程序吧

int j = 0;

int temp = 0;

//分组

for(int increment = a.length/2;increment >0;increment/=2){

   //每个组内排序

   for(inti = increment;i<a.length;i++){

      temp= a[i];

      for(j=i;j>=increment;j-=increment){

        if(temp<a[j-incrent]){

           a[j]=a[j-incrent];

        }else{

              break;

           }

        }

        a[j]=temp;

      }

   }

Increment(增量)=a.length/2,很明显,我是怎么分的,就是分成2各组,注意,不是2个分成1组,就是直接分成2组,increment/=2表示分出来的又分成2个小组,它是一个循环,然后组间比较这么一个思路,我们还是拿刚才例子来说,大家好理解一点

3  4    1    5    2

a.length是5吧,除以2 等于几啊,2,所以说increment刚开始是不是2,那就进到循环来,i=increment;是不是就从第a[2]处开始啊,这个位置上的值是1吧,然后看j>=increment;这个时候是大于还是等于,现在是等于,i等于2,increment是不是也等于2,这时候是不是就进入循环体,temp<a[j-incrent],大家看清楚,temp是谁,是不是就是取的1,a[j-incrent]是不是就是a[0]啊,就是拿1和3比,跳着比,明白这个概念吧,减少的是不是就是这个次数啊,1和3比,要不要交换,是不是要交换啊,执行a[j]=a[j-incrent],如果1比3大,是不是就break了,比完就变成14352了吧,这是第一回,然后就跳到外部循环了吧,即跳到第二层循环了,这时候for(int i = increment;i<a.length;i++),执行i++,i=3,是不是就取a[3]的值,就是5吧,5这个时候和谁来比,5和4来比,要不要交换,不要吧,再来谁和谁比,2和3比,大家看清楚,这个时候是比较1次还是2次,你看这个循环,实际上是这样,首先for(j=i;j>=increment;j-=increment),i等于4了吧,4>=increment,就是4大于2吧,那么temp<a[j-incrent],就是2和3来比,要交换吧,但是看清楚,它的交换是怎么交换的,它只是先把a[j]=a[j-incrent],即a[4]=a[4-2],14253,然后再拿2和1比较,不换,这个时候increment=4了,接下来就跳到第一层循环,increment/=2就等于1了,这就意味着开始来一个一个比较了,实际上这种就是先分组,两两跳着比,然后再用插入法一一比较,明白这个意思吧,它就相当于加强版的插入法。这就叫希尔排序。

其实我们学这4个算法是硬加上来的,数组排序其实不用搞这么麻烦的,jdk中有一个java.util.Arrays的类,里面有很多方法,大家可以看一看,这就说明什么啊,就是说这些算法java已经帮我们做好了,但是考试的时候(比如面试)有可能它让你来写这些方法,为什么这些java会自带?因为这些算法都是比较标准的算法,大家看看sort方法,很多sort方法,典型的重载方法,是不是参数都不一样,好,我们来通过一个程序试一下,比方说我们要对前面那个a数组进行排序,我们先import java.util.*;注意一下,lang包是默认导入的,util是不是要手工去导入啊,

import java.util.*;

public class Test{

   publicstatic void main(string[] args){

      inta[] = new int[3];

      for(inti=0;i<a.length;i++){

        a[i]=i+1;

      }

      intb[]=new int[8];

      system.arraycopy(a,0,b,0,2);

      system.arraycopy(a,0,b,5,2);

      Arrays.sort(b);//静态包,可以直接调,把b传进去

      for(int i=0;i<b.length;i++){

        system.out.println(b[i]);

      }

   }

}

编译运行一下,是不是已经排好序了。

所以说数组排序相当简单,但是对于Arrays这个类,大家可以花点时间把里面的方法都练练看看,因为这里头有些东西可能今后会用得上。如果你没有时间去练习,也可以先看看,都知道它有哪些功能,如果要用到某些功能,你就不用再自己去写了,可以直接利用这个Arrays的相应方法就可以了,所以对于API,常见的咱们要练,来不及练的咱们要看,怎么看呢,就是点到为止,知道它大概的功能就算了,千万不要把每一个方法的怎么使用都看的非常明白,如果你不练,看了以后你也不会明白,再说,这么多东西要想记下来,肯定记不住,所以说你只要大概知道这个类是干什么的,知道这个类里头有哪些功能就OK了,这才是学习的方法,记得太细,也不是好事,这个时候只要你记粗略点,到了编程的时候,碰到这个了,你一看,哎!我知道大概在哪个类,然后再到这个类里头去仔细看,这个时候就是随用随查,就是这个道理,好了,差不多数组就这么多。最后把这一章简单总结一下。

数组:

一、数组的声明

二、数组的创建

三、初始化

四、内存分配

五、元素访问

六、多维数组

七、数组的复制

八、数组排序,排序算法在准备找工作的时候再看一遍。

第六章 抽象类和接口

抽象类是什么?

有时在开发中,要创建一个体现某些基本行为的类(意思就是说这些类只有一些基本的行为),并为该类声明方法(声明方法是不是就是方法定义啊),但不能在该类中实现该行为(这句话什么意思,是不是这些方法只有声明而没有实现),他说,我们有时在开发的时候需要这么干,那么,问你怎么办?取而代之,在子类中实现该方法。知道其行为的其他类可以在类中实现这些方法。

这种只给出方法定义而不具体实现的方法被称为抽象方法,大家看清楚啊,我来给大家写一下,什么叫抽象方法,按照他的说法,这个类里头只有方法定义,而没有方法体,比方说,我这么写算不算,

Public class T{

   Publicvoid aa();

}

这个算不算,算不算没有方法体,算,如果加上{}算不算没有方法体,只要有{}就是有方法体,但是方法体为空,这就相当于等于空字符串和等于null一个概念啊,等于null是没有,等于空串是有而值为空,像这个就是有方法体,只不过方法体为空,这个方法体没有实现,所以说加{}的就不算抽象方法,只是一个很正常的方法定义,判断一个方法是不是抽象方法,只要看它有没有方法体,那么什么是抽象类呢,怎么来表达这个抽象类呢,那我们接着来看,

如何表达抽象类

使用abstract修饰符表达抽象。Abstract英文意思就是抽象吧,那么大家注意一下,它有两个地方会用到,第一,如果方法没有方法体,大家想想,它要不要跟正常方法区分开来,需要区分开吧,要不然两个都是这么定义的话,到底这是正常方法还是抽象方法是不是没有标记啊,那好,没有方法体的方法我们就给它标记成abstract,那么怎么标识一个类是抽象类呢,道理也是一样的,就在刚才的例子后写上abstract,如下:

Public abstract class T{

   Publicabstract void aa();

}

这就是一个基本的抽象类和抽象方法的表达,这就是abstract的基本的用法,但在这个地方呢,我们就想来看一下,大家看到,抽象类和抽象方法是不是看起来好像有点联系啊,这两个有什么联系呢,你看什么叫抽象方法,没有方法体,是不是没有实现的方法就是抽象方法啊,那么什么是抽象类呢,有抽象方法的类就叫抽象类这句话对吗,好,大家注意一下,在这个java的语法规则里面,大家听清楚,这两句我给它这么总结:有抽象方法的类一定是抽象类,但是反过来,抽象类一定有抽象方法这句话是错的,意思就是说,抽象类里头可以没有抽象方法,也就是说一个抽象类里头都是清一色的完整的、正确的方法的定义都是可以的,明白这个概念吧,比如像上面程序,如果将方法定义中的abstract去掉,后面加上方法体,这就变成了正确的方法定义了,这个是可以的,也就是说抽象类不必包含抽象方法,明白这个意思吧,编译测试一下就看到编译通过了,那么反过来,如果我对抽象方法的定义没有变,但是我对类的定义没有定义成抽象类,那么对不对呢,编译一下,不通过,这就是刚才跟大家说的,有抽象方法的类一定是抽象类,但是抽象类里头不一定有抽象方法,这就意味着我们拿以前任意一个正常的类都可以标记成抽象的啊,这句话就是对的,好,差不多了,抽象类如何表达这就可以了,那么反过头来看,这个时候再来看,什么是抽象类啊,你看我这么说可不可以,凡是用abstract修饰的类就是抽象类,对还是错,是对的,也就是说,今后怎么判断一个类是不是抽象类,它只要用abstract修饰的类就是抽象类,而不是看它有没有抽象方法,下面再来看看什么情况下会使用抽象类

什么情况下使用抽象类?

只有方法定义,没有方法实现,这个东西拿来干啥呢?也很简单,比方说,我们举个例子,考虑一个Drawing类,这是个画图的,该类包含用于各种绘图设备的方法,因为我们讲java是不是跨平台的啊,但这些必须以独立平台的方法实现,这个时候就麻烦了,比方说我这么定义这个Drawing类:

Public abstract class Drawing{

   Publicabstract void drawDot(int x,int y);//这个方法你就只知道画点,但是在不同的平台不同的设备上,它的实现方法是不是不一样啊,

   Public  void drawLine(int x1,int y1,int x2,int y2){

      //drawusing the drawDot() method repeatdely.

   }

}

一句话就是你只知道如何去定义,但是它又有很多不同的实现,那么这个时候问你怎么办,就是说你只有一个统一的定义,但是又有很多不同的实现,比方说,咱们举个现实的例子,我现在叫你们写程序或者做作业,我来给你们把这个题目发下去,这是不是就是一个要求,所有的人是不是都要完成这个工作,我给它用方法给它定义出来,我怎么定义,我要求大家交出作业来,返回一个String,接红色代码

Public abstract class T{

   Public abstract String writeZy(String tm);//大家看一下,你看这个现实的过程,我来把这个题目给大家,然后大家交回每个人写的作业,大家有没有发现一个问题,我这里给出的只是一个作业的定义啊,我能不能就给个实现在这里头,肯定是不能的啦,我给了实现大家岂不是不用做了,第二这个作业就其本质是不是应该你们每个人去做,而且每个人做出来,大家想想啊,每个人作出来最后肯定是不一样的吧,可能有些程序写出来,大家思路是一致的,有些程序写出来,不同同学会有不同的思路,都是对的,都是可行的啊,这就会出现什么情况呢,就会出现我只给出方法的定义,我要求你们去实现这个方法,然后你们每个人是不是就给我一份实现啊,这种情况就是只需要给出方法的定义,而不需要给出方法的实现,这就需要使用抽象类,好,这就是为什么使用抽象类,接着来考虑,有了抽象类过后,那我们该到底如何去实现,如何去用呢,大家想想,我光拿着这个抽象类,能不能直接用,肯定不能,都没能实现,它就能用,这就坏了,那岂不是说不需要我们了吗,大家想想,我们每个人的价值就体现在哪里,体现在大括号里头怎么做,这才是我们的价值,结果现在不需要实现的话,那我们的价值就没有体现的地方了,所以这个时候大家接着看,接绿色字体

}

如何使用抽象类

大家看清楚我讲的一个思路啊,这个东西它其实一环扣一环的,这个就比较简单了,在java里头是这么规定的,第一、抽象类本身不能直接使用,这个我估计一说大家心里都明白,他没有实现,怎么使用啊,必须用一个类去继承抽象类,并且实现它的抽象方法,然后,第三,使用这个抽象类的时候,就变成了使用这个具体的实现类(子类),这就是抽象类的使用。好,我们来看一下,抽象类该怎么用,比方说我还拿刚才的例子来说吧,我把刚才那个类放在下面,为什么呢?因为抽象类本身没法用,我又是一个public的,是不是就麻烦了,我直接这么写,public去掉,这样写,我就好写一个main方法来继承啊,

Public class T{

   Publicstatic void main(String args[]){//怎么用这个呢,我们不能这么用

   T1 t= new T1();//不能这么用,按照刚才说的,必须写一个类,如T2

     

 

 

 

Abstract class T1{

   Public abstract String writeZy(String tm);

Publicabstract String writeZy2(String tm);

Publicabstract String writeZy3(String tm);

}

Abstractclass T2 extends T1{//继承T1它又有两种情况,一种是它实现了writeZy这个方法,还有一种它实现了部分抽象方法,一个抽象类里头可能有多个抽象方法,比方还有一个抽象方法writeZy2和zy3,这个时候它可能只实现了writeZy,然后2和3它也没有实现,那怎么办,它也得继续为abstract,也就是说只要这个抽象类里头的抽象方法没有被全部实现,那么这个类是不是继续抽象啊,直到全部实现为止,那么怎么实现呢,这里大家注意一下,我把上面的2和3先注掉,那么怎么实现呢,大家看一下,既然是个正常的方法,下面的方法里的abstract可以去掉了,变为

   Publicabstract String writeZy(String tm);

   PublicString writeZy(String tm){

      Return“自己的作业”;//每个人就来返回自己的作业

   }

这么说吧,不同的人返回不同的作业,比方说张三的作业,将T2改为ZhangSan,Return “自己的作业”;改为Return “张三的作业”;这个时候李四是不是也可以来实现啊,

Abstractclass LiSi extends T1{

   PublicString writeZy(String tm){

      Return“李四的作业”;//每个人就来返回自己的作业

   }

这个时候大家就看,一个抽象类可能就有多种不同的实现类了,这种情况咱们讲过吧,叫什么啊,是不是多态啊,好,这个地方接着来,按照我们刚才说的,第一步,抽象类不能直接用,第二部要写类去实现它,第三呢,使用抽象类是不是等于使用它的实现,所以将T1 t = new T1();改为T1 t = new ZhangSan();这就对了,变为

Public class T{

   Publicstatic void main(String args[]){//怎么用这个呢,我们不能这么用

   T1 t= new ZhangSan();

   Strings = t.writeZy();//就相当于使用这个实现的类,但是我的类型可不可以是T1,可以吧,多态嘛,这不就是向上造型嘛,

 

   System.out.println(“s==”+s);

   }

}

这就是用完了,这个时候大家看一下,使用抽象类最大的好处是什么?这个时候不管张三也好,李四也好,王五也好,他们实现的动作或者说实现的功能是不是静态的,对不对,这就是典型的我们把它叫做什么,这就叫约束行为,也就是说我给你们发作业一样,所有人都是不同的个体,你们都是按照自己的思维去做,但是完成的功能是不是同一个啊,就是做作业,明白这个概念吧,这就叫典型的约束子类的行为,不是说我让你们做作业,你们就天马行空的随便乱写,所以这就叫典型的约束子类的行为,但是每个子类实现的又有差异,这样做还有个好处,假如说我要去评选一个优秀答案或者最佳答案,比方说先有两个同学交作业,他们两个总有一个好点的吧,但是有同学说我的比它的还好,那么它又加进来,他把它的作业加上来,这个时候我可以又加一个实现类,整个程序都不用改,这就意味着有了这个特性,约束了子类的行为,而本身又没有实现,那么子类是不是可以随意增加啊,这就意味着今后我的实现方式可以随时替换,比方说李四说张三的还没有我的好呢,就直接将T1 t = new ZhangSan();改为T1 t = new LiSi();就好了吧,编译运行一下,结果是不是就改成s==李四的作业,就改掉了吧,今后我就可以继续增加一种实现,这种实现是不是也可以跟前面有所不同啊,比方说,

Class WangWu extends T1{

   Public String writeZy(String tm){

      System.out.println(“王五抄的作业”);//这种情况有吧,肯定有同学是抄的作业啊,这是不是可以的啊,不同的实现,只要将前面改成new WangWu就可以了,别的是不是不用动啊,

      Return“李四的作业”;//返回仍然是李四的作业,但是我在前面写上

   }

编译运行,反映出王五是抄的李四的作业吧,好,回过头来看,现在如何使用抽象类大家会了吧,好,总结一下:

抽象类和抽象方法

下列情况下,一个类将成为抽象类:

1、当一个类的一个或多个方法是抽象方法时;

2、当类是一个抽象类的子类,并且不能为所有抽象方法提供任何实现细节或方法主体时,

3、当一个类实现一个接口,并且不能为所有抽象方法提供任何实现细节或方法主体时,

注意:

1、这里说的是这些情况下一个类将成为抽象类;没有说抽象类一定会有这些情况

2、一个典型的错误:抽象类一定包含抽象方法。但是反过来说“包含抽象方法的类一定是抽象类”就是正确的

3、事实上,抽象类可以是一个完全正常实现的类

为什么完全正常实现的类给它标记成抽象类呢?比如刚才张三那个类,我就标记成abstract,可不可以,但是从本质上来讲,我是不是已经全部实现了啊,编译是通过的,那有同学说为什么呢?你都已经实现完了,为什么还要我来实现呢,还不让我直接用,因为加了abstract了嘛,这种是什么原因呢,很快大家就会学到,大家想想这个抽象类有什么特点,它里面可以写完整的方法对吧,这个时候你仔细想想,你看为什么要把它标识成抽象类呢,第一点,能不能直接用,不能,我就是不允许你直接用吧,为了保护这个类,我就不允许你直接用,但是我又提供了约束,是不是要求你子类去实现啊,那么我提供一些什么样的约束呢,我可以写抽象方法,当然我也可以在父类里头写一个空的方法体吧,作为缺省实现,比方说我给作业的时候,我给一个空的方法体,就像我跟大家说给你们示范一下,作业写完了return myzuoye,也就是说我在父类里头做的事情是什么啊,是个真实的实现还是虚假的、模拟的、缺省的实现啊,其实是缺省的模拟的实现,明白这个意思吧,它并不是真的实现,由于我不是真的实现,那这个时候怎么办呢,是不是还得要大家真正实现啊,才能使用啊,但是如果说有些同学不实现,它是不是也可以用这个缺省实现,就相当于他没做作业呗,就是我发下去作业什么样,他又还回来了,交了个白卷,就变成这个样子了,如果说出现了这种状况,这就是为什么会出现一个完全正常的类被标识为抽象类。原因就是父类用来提供一个缺省的实现,子类去真正的实现,是不是就是覆盖实现,它不也完成了约束子类的行为嘛。好,抽象类就说到这儿吧。再来看下什么是接口

Abstract class T1{

   Public abstractString writeZy(String tm){//abstract去掉

 

   }

接口

什么是接口

在java中有一个叫interface的东西,我们把它称为接口,接口是一种特殊的抽象类,所以究其本质,我们说接口是个类对不对,对的,抽象类也是类嘛,其实在java里面所有东西归根结底都是Object,所以说接口是类是很自然的现象,那我问大家,是不是所有的抽象类都叫接口呢,当然不是了,是特殊的嘛,怎么特殊法呢,这种抽象类只包含常量和方法的定义,而没有变量和方法的实现。什么概念,尤其是没有方法的实现,也就是说,我们刚才定义的抽象类大家想想,可不可以有实现方法,可以吧,但是接口和我们刚才的抽象类是有不同的吧,如果完全相同就不用再起个名字叫接口了吧,它的特殊就在于接口来的更纯粹点,意思就是说接口的方法全部是抽象的,没有一个是有实现的,就是说没有一个是有方法体的,这种特殊的抽象类我们就把它叫做接口。好,看一下它的定义:

语法格式:

访问修饰符 修饰符 interface 接口名称{

   抽象属性集

   抽象方法集

}

来,我们还是去写一下。比方说我把刚才的T1改成接口可不可以,可以,

Interface T1{

   PublicString writeZy(String tm);//接口里面不需要去加abstract,为什么呢,因为凡是在接口里定义的方法,它默认就是public abstract的,也只能是public abstract的,因而可以在声明方法时省略这些修饰符,所以你这里不写public也是正确的,那能不能换成private呢,我告诉你,坚决不行,你一编译,就报错,不能使用private,要想用访问修饰符只能用public,如果只写abstract不写public也是对的。那我这儿能不能写成static修饰呢,当然不行,大家想想,用static修饰的方法可以拿类直接去调啊,你这个方法都没有实现,我调它干嘛。所以说肯定不能用static修饰。反正这个地方只能是public abstract,写也是它,不写也是它。用final也不行,为什么,用final修饰的方法不能再被覆盖实现,你自己没有实现,还不许别人实现,那肯定是不行的嘛,你一想就知道了。

}

接下来再看看它里头定义的属性,大家看到,它的特性是:

凡是在接口当中声明的属性默认为,也只能是public staticfinal的,什么意思

Interface T1{

   int a= 10;//比方说我随便定义一个,那么这个a再也不用改了,它默认就是public static final的,这就很麻烦了,这就意味着只要定义了东西,定义的属性一定是常量,也就是说这个a以后就再也不能改了,这也就意味着只要定义属性一定是大写。

   PublicString writeZy(String tm);

}

好了,定义这一块儿就说完了,下面再看一下接口和类

接口和类

目前看起来接口和类是差不多的,但是这里有一些特殊的点,接口本就是从抽象类中演化而来的,因而除特别规定,接口享有和类同样的待遇。但是这里有些特殊的点,比如,源程序中可以定义0~多个类或接口,但最多只能有一个public的类或接口,(接下面的红字)比方像上例源程序中,我不写类,只写接口,可不可以,只写成:

public interface T{

   int a= 10;

   PublicString writeZy(String tm);

}

也是可以的,也就是说,一个源文件可以有多个接口,但是public的只有一个,而且这个public的一定和源文件名字相同。它和类的定义是一模一样的,如果有,则源文件必须取和public的类和接口相同的名字。和类的继承格式一样,接口之间也可以继承,子接口可以继承父接口中的常量和抽象方法并添加新的抽象方法等。再给大家来演示一个:

public interface T extends T1,T2{//大家说这种写法对不对,不对,为什么?因为java是单继承的,是吗?编译一下,没问题吧,什么意思啊,看清楚,单继承指的是java的类,也就是说这里接口是可以多继承的,这是一个很特殊的地方,那这是为什么呢?因为所有的接口里的方法都是很纯粹的,都是没有实现的,事实上,你继承无非就是将这些方法的定义放在一起而已,会不会产生冲突,不会,但是如果说要是实现的话,这个时候就麻烦了,为什么,你想啊,我这个实现里头可能有相同的方法,但是我这个相同的方法会有不同的实现,那你调起来到底调谁呢,对不对,到底你调谁,是不是就乱套了,那么这个时候它就很不好办,那有同学说,接口出现相同的方法有没有危害呢,比方说我将T2中的方法也改为tt();大家说有没有危害,没有危害,为什么,反正都没实现,从定义上来讲,我们两是一模一样的,也就说到时候我们两个合并了就算一个不就完了嘛,是这个道理吧,它不会造成错乱,但是你要是两个类的话,两个tt实现的不一样,到时候你怎么办,就不好办了吧。

   int a= 10;

   PublicString writeZy(String tm);

}

interface T1{

   publicvoid tt();

}

我写了一个接口T1,让T接口继承T1,大家看可不可以,编译一下,正确的吧,当然现在我这个没法运行,没有main方法嘛,我再写一个接口

interface T2{

   publicvoid t2();

}

看清楚啊,T继承了T1,我加上,T2,接上例程序中的红字,这就是接口的继承,它和普通的类有一个很重要的区别。

好了,刚才是讲完了接口的定义,也讲完了接口的概念,下面看看接口咋用吧,

如何使用接口

跟抽象类一样,接口也不能直接使用,需要让java“实现”接口,然后使用其实现类,好,这个地方我要去写的话,该怎么写呢?我们将刚才的例子还原回去,即

public class T{

   publicstatic void main(string args[]){

   T1 t = new Impl;//实现接口,然后就可以调t的方法了

   Strings = t.writeZy(“”);

   System.out.println(“s==”+s);

}

class Impl implements T1{//implements实现的意思,用Impl实现T1这个接口,注意,如果说一个类去实现一个接口必须实现它所有的抽象方法,像这个地方最害人了,有同学把下面的writeZy抽象方法直接拷贝过来,如下:大家说对还是错,如果是错的,错在哪里呢?这个我们把它叫做覆盖实现,符不符合覆盖的规则,很明显不符合嘛,覆盖规则是怎么说的,下面的方法是抽象方法,抽象方法的方法默认是public的,我们说覆盖规则说方法的权限只可以放大不可以缩小,到了这儿,方法是正常的方法,默认的就是friendly的,是不是缩小了啊,所以必须在前面加上访问修饰符public。要不说这儿特别害人呢,然后我怎么去使用这个接口呢。接紫色字体。

   String writezy(string tm){

      Return“My Impl”;

   }

}

interface T1{

   publicstatic final a_height = 10;

   string writezy(string tm);

}

好,这就是接口的实现,抽象类叫继承,接口就叫实现,实际上是一个意思,都是拿子类去覆盖实现父类,好了,这就是如何使用接口。接下来是一个重要的思想性的东西,为什么要实现接口呢?

为什么实现接口?

实际上上次课我们说了,抽象类的出现,一个很重要的作用就是用来约束子类的行为,那么接口也是这样,他说

两个类中的两个类似的功能,调用它们的时候呢,我不知道到底是调A还是调B,而是动态调用实现的,那怎么办,这种情况下,我们就可以把它提炼出来做一个接口,那么有了接口就有什么好处呢,大家看到关键就在这个地方,接下面的红色代码

public class T{

   publicstatic void main(string args[]){

   T1 t = new Impl;//这个地方,一个接口,就像抽象类一样,是不是后面可以有很多实现类,也就是说实现可以变,但是我们底下的编程,就是使用接口会不会变,不会,也就是说,只要接口不变,我下面的使用就不会变,这一点在java里面非常重要,为什么呢,这样就意味着我可以随时改变功能,只要提供新的实现不就改变功能了嘛,就是说我有新的实现,只要接口不变,我就随时改变,所以说呢,接口是可插入性的保证。通俗的说,就是可以扩展。

   T t = new T();

   t.t1(t1);//下面两行先注掉,这就相当于把接口当多态使用了,我既可以传Impl,也可以传Impl2吧,即将上面的T1 t = new Impl改成T1 t = new Impl2;这就意味着我传不同的东西,你的功能是不是就不一样了,转回下面接口的基本作用4

   Strings = t.writeZy(“”);

   System.out.println(“s==”+s);//这两行就是拿着电就用,就算是接口的客户端,如果是代码,我们就是客户,相当于这两行代码,我们的任务是拿着接口就来用,这在java里面很重要,叫做面向接口编程,那这有什么好处呢,就是说内部实现我一概不管,我只关心只要你接口不变,这个插座不变,别的我就不管了,接口就相当于屏蔽,我们只看到这个接口,接口没变就行了,那个电到底是什么发的电你随便换,那个电就相当于具体的实现,我们只要面向这个接口,实现你可以随便换,这就叫可插入性,只要有统一的接口,就可以随时改实现,这就是程序里面为什么要用接口,

}

publicvoid t1(T1 t){//这个地方就传一个接口进来,是不是那个父类拿来做多态使用是一个概念啊,我就用接口来做这个类型化的参数,这个时候你是不是就可以往里传任意的实现类啊,再向上转到main方法中的橙色字体

String s = t.writeZy(“”);

   System.out.println(“s==”+s);

 

}

}

class Impl implements T1{

Stringwritezy(string tm){

      Return“My Impl”;

   }

}

class Impl2 implements T1{

Stringwritezy(string tm){

      Return“My Impl222”;

   }

}

 

interface T1{

   publicstatic final a_height = 10;

   string writezy(string tm);

}

我先举个实际的例子来说,比方说我们日常生活中的插座算不算接口啊,当然算了,那是跟谁的接口,跟电力公司对吧,我们之间怎么交互,主要是两个,一个是电力公司把电送过来,一个是我们去交电费,实际不就是这样嘛,那么这里最主要的问题,就好比插座就是一个接口,我们要用电,插入就用,是这个概念吧,那好,这个时候大家想想,我们这个时候就叫面向接口在使用,我们关不关心它如何实现的,我们管不管背后这个电是华东电网的电还是华北电网的电,是水电还是火电还是风力发电还是核能发电,知道吗,我们不需要知道,如果从编程的角度来讲,转代码中紫色字体,通俗点讲,有了这个接口,我这个程序就变得可维护了,为什么说是可维护了,就是当我发现哪段程序写错了,我就写一段正确的上去给它替换掉就好了,比方说原来的Impl1写的有问题,我只要重新写一个Impl2就给它替掉了,这就是一种可维护性的体现,还可以扩展吧,就比方说Impl1的功能已经无法满足需求了,我就用Impl2给它替掉,我的功能是不是就给它放大了,总之一句话,接口是可插入性的保证。把什么插入进去,插入到哪儿,这个可插入性就是把一个新的程序实现插入到原来就有的程序体系结构里面,有了接口实际上就相当于开了一道门,你随时就可以加进来,这就是为什么使用接口,这样一来,我们关心就不是哪一个具体的类,而是这个类是否实现了我们需要的接口。所以在java里面就有一点,今后做程序就是面向接口编程。这个概念先灌输一下,后面重点讲设计的时候大家再具体体会。面向接口编程是以后java编程的第一大要点。可插入性的保证就是程序可扩展可维护,随着程序规模越来越大,生命周期越来越长,接口就显得尤为重要,程序越大出错的可能性就越多,有了接口你就可以很方便的去改,很方便的去扩展整个程序的功能,所以你这个时候可以这么去想,当你在java里头真正去做一个程序,没有接口,实话实说,难以想象,尤其你这个程序还比较大。程序越大,接口就越显得重要,尤其后面你们要学体系结构的时候,没有接口是难以想象的,这点大家注意,好,再看看接口的作用

接口的基本作用

1、声明方法,期望一个或更多的类来实现该方法。

2、揭示一个对象的编程接口,而不揭示类的实际程序体。这是什么意思啊,是不是就是给出一个定义啊

3、捕获无关类之间的相似性,而不强迫类关系。就是说如果多个类当中都有这个公共的方法,可以提炼出来作为接口。如在上例中增加一个class Impl2,转到上述程序绿色字体

4、可以作为参数被传递到在其他对象上调用的方法中。很明显,这点就是告诉我们把接口当什么用啊,不就是多态嘛,把接口当多态用。什么概念呢,就假设说,在class T中有一个t方法,转到上述程序橙色字体,要求传一个T1,然后在此方法中运行两句话。这就是接口的基本作用。

多重接口

一个类实现多个接口就被称为多重接口。在java里面,我们前面复习过,它是单继承的,一个类只能继承一个父类,但是一个类可以实现多个接口,比方说在上例中加入如下代码:(红色代码)

public class T{

   publicstatic void main(string args[]){

   T1 t= new Impl();

   T2 t2 = new Impl2();

Tt = new T();

   t.t1(t1);//改为t.t1(t1,t2)

   publicvoid t1(T1 t,T2 t2){

String s = t.writeZy(“”);

   System.out.println(“s==”+s);

   t2.a();

}

}

class Impl implements T1,T2{

publicString writezy(string tm){

      Return“My Impl”;

   }

   public void a(){

      System.out.println(“aaa”);

   }

}

class Impl2 implements T1{

Stringwritezy(string tm){

      Return“My Impl222”;

   }

}

 

interface T1{

   publicstatic final a_height = 10;

   stringwritezy(string tm);

}

interface T2{

   publicvoid a();

}

假如我让类Impl实现这个接口,大家说应该怎么做,只需在class Impl implements T1,T2既可,大家注意,一旦你要去实现,就要全部实现抽象方法,接紫色代码

这就是多重接口,就是说一个类只可以继承一个父类,但可以实现多个接口。所以这种情况下,他从某种程度上也给了我们一个启示,我想让一个类既是一个T1,又是一个T2,又是一个自己,我该怎么办,可以用接口变相模拟实现多重继承。最后再看一点,一个类既可以继承,又可以实现多个接口,继承和实现到底应该怎么放呢,

extends和implements

注意:extends从句必须放在implements从句之前,比如前例Impl既继承Impl2又实现了T1和T2接口,可以写成如下:

class Impl extends Impl2 implements T1,T2

而不能先写实现接口再写继承类,即下面是错误的:

Class Impl implements T1,T2 extends Impl2;

所谓实现实际上也是覆盖实现,所以说实现接口也同样遵循咱们前面在类里头讲到的那些覆盖实现的规则,如果父子类都有某个方法,还是一样,new谁调谁。

接口的多重继承

   Java的接口是可以实现多继承的,类不允许多继承。

public interface Test extends A,B{

   //定义

}

interface A{

   //定义

}

interface B{

   //定义

}

好,到这个地方接口的基本语法就over了,接口的语法其实很简单,接口全部都是定义,里面是不是没什么要你写的啊,所以说写接口其实是最简单的,关键是大家要去理解接口的一些东西,我前面跟大家提到java最重要的一点是面向接口的编程,它为什么这么重要呢,好了,这个时候呢,我来给大家画个图来描述,为什么接口这个东西在java里头如此重要,以至于要把它排到NO.1,大家可要搞清楚,它的地位真正在做java程序的时候就是NO.1,不是一般的地位了,为什么这么重要呢,它的重要是有原因的,比如说我在做一个大项目的时候,大家想想应该怎么做?就像建一栋楼一样,其实我们一开始就给大家讲过,解决问题的思路是什么?就是把一个大的问题分解成一个一个小问题,然后依次分解,直到分解到你能解决的时候为止,真正在实现的时候,就是倒着实现,总之解决一个问题,你不会全面开花,而是一点一点先解决,是不是这个思路,如果说这个时候不是问题,开发软件就是解决问题吧,而是整个软件,一个软件系统可以分解成多个子系统,每个子系统又可以分解成多个模块,每个模块又可以分解成多个组件,上,什么叫组件呢?大家记一下,就是能完成一定这些都是java里面很常见的体系的这么一个概念,对于这些东西,我想跟大家说一下,在这个设计概念功能的封装体,那我解释一下,什么叫做封装体呢?封装体的概念就是这些东西被包装起来,封装起来,所有的东西都在这里头,比方说大家看到的空调是不是就是一个封装体,你要不要管它的内部电路怎么运作啊,你只要等他封装完了,放几个按钮给你,你只要知道怎么开关,怎么调节不就行了嘛,空调对我们来讲就是封装体,软件也是这样,封装体的概念就是它自身能够完成一定的功能,并且它自身内部是不对外的,就好比空调的电路它是不对外的,刚才说的这个不是java,而是从设计的层面来讲,后面很多都会提升到这个层面来讲东西,就是说不单java这么干,你去做c#、.net其他的都这么干,包括我们后面要讲的AWT、Swing等等,其实这个东西学了确实没啥用,但是咱们是用它作为一个载体,是用这个载体体现一些思想层面的东西,而且有些东西在java里头只有这儿有,别的地方没有,你必须在这儿学,比如说事件、机制等等,你在这儿学了,后面虽然不出现,但是后面频繁在用,明白这个概念吧,就是说它这个地方有这个知识,后面虽然不出现这些知识,但是都在用这些知识,你在这儿不把它学了,你在后面就不好理解了,好,像这个大家看一下,这是组件的概念,一定要体会封装体的概念,就是自己能完成一定的功能,那我上面写的组件、模块、子系统、系统是一个概念吗?从设计者的眼光来看的话,他们其实就是一个东西,都是封装体,所以说小到一个类,大到一整个系统,都可以叫做组件,这个东西大小是相对概念,所以说你要理解这个概念,那么理解了这个对我们理解接口有什么帮助呢,或者说为什么先讲这个然后再讲接口的思想呢,原因是这样:先理解了从设计层面上,这就意味着软件是什么样子呢,就是一个软件是由不同的模块组合而成构成了一个系统,如图:

好了,这个时候就开始来理解咱们的接口了,我们刚才讲了,模块本身是封装体对不对,既然是封装体,大家想想,是封装什么,你有什么东西不想对外,就像空调一样,你封装了,不能什么都不对外吧,总有对外的,不然还怎么用啊,那我们一般把这个对外的部分叫做什么,就是接口了,比方说我在图上画出一部分是对外的,就是外面能够看得到的部分,这一部分就叫做接口,我们叫做API,就像空调的那些按钮一样,是对外的,它本身是一个封装体,那就是说有一部分被封装在内部不对外,就是上面的那部分,封装在内部,不对外,就像空调一样,怎么制冷,怎么运转,那些部分是不是不对外啊,人家号称这叫技术,是不是这个概念啊,就是不能让你知道的,那好,大家看到,也就说着这任何一个模块里面,它一定都有具体的实现方式,具体实现让不让外面知道,肯定是不让的吧,各个模块之间都会有交互,我每个模块都开辟一块出来对外,如图,除了这些对外的,剩下的都是不对外的吧,那么不对外有什么好处呢,只要接口是固定的,比如这个空调,如果里面哪块坏了,是不是厂家该换的换,该修的修,除了接口之外,里面的东西它全给你改个遍,你知道还是不知道,你关心不关心,你不关心,爱咋地咋地,反正你只要保证我能用是不是就可以了,这样厂家就可以随时做技术改造,技术更新,就比方说新出的空调壳是一样的,但是里面的技术已经不一样了,没有问题啊,如果什么东西都让我们知道,那就麻烦了,那他不就没法改了,对不对,因为每个人的操作方式不一样。所以说一个模块是封装体,封装的是内部的实现,这样以后我的程序就好改好调整了,所以这个时候大家看到,接口的基本思想

接口的基本思想:

   接口及相关机制的最基本作用在于:通过接口可以实现不相关类的相同行为,而不需考虑这些类之间的层次关系。

这个里面大家看一下啊,面向对象程序设计讲究“提高内聚,降低耦合”,这里要给大家讲一下,什么叫做内聚,什么叫做耦合,所谓内聚,就是一个类的内聚就是这个类的功能都能在这个类的内部找到,一个模块的内聚就是这个模块的功能都能在这个模块内部找到,耦合的概念就是横向联系,就是一个模块要用到另一个模块里的功能,那么这两个模块之间是不是就有联系了,这个联系就是耦合,这时候就会出现一个问题,如果你在模块里面把这个功能给改了,那么我是不是也要跟着改啊,这个时候就麻烦了,你的改变别人未必知道啊,你在做,我也在做,等到我们两个合起来的时候才发现坏了,程序跑不了啊,是这么个概念,所以说,对于面向对象来讲,如果从设计上来说,像“提高内聚,松散耦合”这个规则基本上至少可以排进前三名,这都是很基本的一些概念对于做设计的人来说,正是因为有这一点,那有同学说,这个跟接口有什么关系呢?要松散耦合不是吗,这个时候大家想一下,有同学说,以后我们做程序,我把所有的东西都堆到一个类里头,我就可以彻底和别人不耦合了,可不可能啊,这个是不可能的,如果从设计类的角度,第一个原则就是类要单一,就是一个类最好只完成一类事情,就比方说我描述人,这个类里头就只能放跟人相关的东西,比方说我做了一个程序叫人与动物,里面定义了人的对象,各种动物的对象,这些对象要分开定义啊,人是一个吧,牛是一个吧,狗是一个吧,然后你要写一个动作叫斗牛,这个时候人和牛是不是就发生横向联系了,这个时候你绝对不能在人这个类里头有牛的这些动作和属性吧,从功能上也许能跑,但是从设计角度来看的话,这就一塌糊涂,所以说呢,类要单一,也就是说,在面向对象里面,其实这是面向对象的最大的一个软肋,就是它没有办法完全去除掉横向的联系,就是耦合是一定存在的,如果你做的程序里面没有耦合,只有两种情况:一、你实在是做的烂的可以,全部堆在一起;二、这个程序实在是简单的可以,比方你写个HelloWorld,你想耦合都没得耦合,你跟谁耦合啊,一共就那么一句话,所以这个特点大家要注意,那“提高内聚,松散耦合”到底和接口有什么联系呢,原因就在这里,大家看到,既然是一定要发生耦合的,那么我们就得想办法尽量把这些东西隔开啊,比如像上图中的模块1和模块2,如果一定要耦合的话,我们是不是要把这种相互的耦合这种关系降到最低啊,怎么来控制这个东西,就是互相只通过接口来访问,也就说就算你知道我的具体实现类是A,你都不能直接调我的具体实现类,而是要通过接口来操作,明白这个意思吗,这样的话,我们只要维护一个接口不变,内部剩下的是不是就可以随意改变了,而不是像原来那样,所有方法都可能被调用,所有方法都提心吊胆地不能随意改,现在就不存在了,这就是接口在这个地方起到的作用,那么到底是什么作用呢?接口在java程序设计中体现的思想就是封装隔离,这个要好好记住,它封装了谁,其实一个接口封装了谁啊,如果把上面的实现包起来的话,API是不是就把模块内部的具体实现全部封装好了,就像壳一样,然后API它自己一个人对外,所以我们有时候也把这个API叫做一个模块或者一个组件对外的外观,就是从外面看这个模块,你只看到了这个API,就是把具体封装起来包在它后面,隔离的是谁呢,也是这些具体实现,它把这些具体实现封装了过后,然后这些具体实现从此只能在API这个壳里头跑,不得与外界交换,她就把具体实现和外界完全分隔开来,外面想要访问,可以,找我API,就是找接口,你不能直接找具体实现,正是因为接口把程序封装成了一块一块的,才使得程序变得简单,横向联系也比较容易控制,然后相互呢,最终大家看到,理想的状况,一个大的程序是怎么作出来的,是不是就是整上几十个小块来组合出来的啊,其实这个就是咱们做设计的人一个很理想化的设计,就是整个程序是装配式的,组装式的出来的,最好的程序就像现在的工厂一样,都是标准化生产,大批量的拿过来直接组装,零配件都是有标准统一的生产,事实上,跟这些行业比,软件行业就相当落后了,软件行业还是手工作坊式的行业,代码全靠手敲啊,你有没有批量生产,有没有动不动就拿些组件组合组合就成为新的东西,没有吧,原因是什么,就是说明这个行业还是不够标准化,不够发达的,但是我们要相信,随着技术的发展,迟早软件行业也会进入到工业化时代,现在的软件行业仍然处于最原始的手工时代,还没进入工业化,如果进入了的话,那就可以进行流水线生产了,当然现在也在做很多这样的尝试,就是软件组装、装配这样的尝试,但是从目前来看,主要还是靠人工在做,靠人工一点一点去实现这些东西,所以说还是作坊型的行业,所以说咱们这个可算不上高科技,比其他行业都落后,好,这个大家理解一下,这就是接口的基本思想,这是非常重要的,当然现在我们在讲的时候,大家可能体会不到它的好处,等到了后面正儿八经做项目了,就一定会用到接口,那个时候你再去体会接口的封装隔离有多么管用了,因为封装隔离之后,内部就完全是自己的,随便改,只要接口不变,那就好办多了,好了,这一块呢,接口就说这么多,好了,最后一个呢,咱们再看一下,这一章咱们讲了两个东西,抽象类和接口,这两个大家注意,如果从设计层面上来讲,功能上是差不太多的,那么怎么样去选择这两个东西呢,大家可以看一下,就是

接口和抽象类的选择

在接口和抽象类的选择上,必须遵守这样一个原则,行为模型应该总是通过接口而不是抽象类定义。所以通常是:

(1)优先选用接口,尽量不用抽象类

对于java来讲,基本上都是面向接口在编程,那这个时候,有同学说,那抽象类岂不是没用,肯定是有用的嘛,不然还要抽象类干吗呢,这时候就要分析它的特点,你看,抽象类和接口比的话,抽象类有它自身的优势,有什么优势呢?很简单,因为它是个类,所以它可以有实现方法,这就是它的优势,这个时候抽象类就跟接口说:“你看,你不行了吧,我还可以实现一些东西,但是你不行”对不对,同样,它们两个都能约束子类的行为,但是接口不能提供实现,我还能提供一些实现,所以说这种情况下,大家看到,什么时候选择抽象类呢?

(2)既需要定义子类的行为,又要为子类提供共性的功能。也就是说子类比如有五六个,但是这五六个子类都要实现某个共同的方法,这个方法我干脆就实现到哪儿去,实现到父类,是不是所有的子类都有了啊,那么这种情况下就选择抽象类。

这个在今后的实践和设计当中,都是以这个原则来判断抽象类用的对不对,以后你们学习WEB开发的时候,就会多出一个抽象类的层次,就是为了定义一些公共的功能。比如说连接数据库就是公共的功能。我们还是来示范一下,毕竟文字性的东西大家不好理解。还是以刚才的例子为例:

public class T{

   publicstatic void main(string args[]){

   T1 t= new Impl();

   T2 t2 = new Impl2();

Tt = new T();

   t.t1(t1);//改为t.t1(t1,t2)

   publicvoid t1(T1 t,T2 t2){

String s = t.writeZy(“”);

   System.out.println(“s==”+s);

   t2.a();

}

}

abstract class P implements T1{

privateint conn(){

      return5;

   }

}//公共功能就拿到这个父类中了,下面的Impl1和Impl2就不用实现T1了,只需要继承P就可以了

class Impl implements T1,T2{//此处改为extendsP

   private int conn(){//比方说都要连接数据库,这就是公共的功能,Impl2也要有这个方法,将该段代码复制到Impl2类中。

      return5;

   }

publicString writezy(string tm){

      Return“My Impl”;

   }

   publicvoid a(){

      System.out.println(“aaa”);

   }

}

class Impl2 implements T1{//改为extends P

   private int conn(){//比方说都要连接数据库,这就是公共的功能

      return5;//用P父类实现以后,此处就可以不要了

   }//用P父类实现以后,此处就可以不要了

 

 

Stringwritezy(string tm){

      Return“My Impl222”;

   }

}

 

interface T1{

   publicstatic final a_height = 10;

   stringwritezy(string tm);

}

interface T2{

   publicvoid a();

}

大家有没有发现,如果这么写的话,是不是太繁琐了,我们该怎么办,你看,Impl1和Impl2都会有这个一模一样的方法,你这样写的话就不好了,为什么,因为相同的代码我只要把它作为公共的写一回就行了,你不能把它copy来copy去的,这种拷来拷去的写代码的方式是最烂的了,那么这种情况下,我们应该怎么写,就可以写一个抽象类P,让P去实现T1,那么实不实现T1里的抽象方法呢,我才不实现呢,我只把a2方法放到这个里面来,接紫色代码,编译运行,照样跑了,这就是这句话的意思。直白点说,选抽象类的时候,就是要为子类提供公共的功能。它的优势就在这儿,它是父类,既然是个类,它就有实现,它里头的实现所有的子类都有了,不就成了公共功能了嘛,好了,这一章就到这儿吧。最后总结一下:

一、抽象类

1、是什么?用abstract修饰的类就是抽象类

2、如何定义?

3、什么情况下用?一般就是有抽象方法的时候用,只想定义行为却又不想实现的时候,让子类去实现,这种时候用抽象类

4、如何使用:三点

5、抽象类和抽象方法的关系

二、接口

1、是什么:一种特殊的抽象类,怎么个特殊法:属性是常量,方法是抽象方法

2、接口和类:很相似,类比学习

3、接口特性:属性默认是public static final 方法默认是public abstract,写不写都是一样的

4、如何使用接口?使用接口等于使用它的实现类

5、为什么使用接口?做一个可插入性的保证,方便维护方便扩展,主要从设计体系上来讲,使得设计体系很灵活,很容易维护容易扩展,是从设计层面上来讲的,仅从功能上来讲,它是没有什么功能的。

6、接口的作用

7、多重接口

8、extends和implements

9、接口的基本思想

10、接口作为类型使用:多态性

11、接口和抽象类的选择

 

 

 

 

 

 

 

第七章       用户图形界面GUI

接下来咱们开始学习用户图形界面,java在这一块是弱项,其实这一块是用来做图形化的桌面级的程序,什么是桌面级的程序呢?就是在咱们单机上去跑的,所有的资源都在自己的机器上,而且是带图形界面的,典型就是早期的word、excel、IE浏览器等等,这些都是桌面级的程序,但是现在新版的office 2007已经开始向网络化转了,我能不能通过远程把我的文档通过你的word来操作啊,以前是不可以的,这点对java来讲是个弱项,而且还有一点就是这个东西学完了过后,想靠它去找工作,我告诉你,几乎没有,这块的应用范围特别的小,实际应用比较少,基本上java的强项是在Web这块的,是在服务器这一块,包括web开发和j2ee这一块,那有同学就说了,你说了这半天,那干嘛还学这一章呢,这个地方大家注意,我们的方向不一样,咱们不去搞那些噱头性的东西,也不去教大家做个游戏来玩,那个东西培养兴趣有好处,但是如果从咱们就业这个角度来讲,这个时间咱浪费不起,那么我们为什么要学这一章,大家挺清楚,不是说这一章不重要,而是非常重要,我们要在这个地方做到这么几件事情,第一,让大家初步去领略一个java程序(项目)是怎么样子的,前头大家所学习的六章都是属于java语法,你们也顶多会写class,还没有形成程序的概念,只会写一个小片段小片段的方法,对项目你还没有直观的认识,还没有系统的概念,第二个是要去学习整个表现层的做法,听清楚,我可没说是Swing,也没有说是AWT,我说的是整个表现层,表现层就是显示给你看的,或者让你操作的,我们要在这里学通用的做法,那么这个做法学完过后,它的影响非常深远,怎么个深远法呢,就是从此过后我们所有的表现层都是这么做,包括你学WEB的时候和框架的时候,甚至包括你以后学习一些java的AJAX技术等,我告诉你,从此以后就不再学了,而且咱们是从一个设计的角度去学,就是要理解整个这个概念,即使你不做java,你去做C#、.NET的程序,它都是这么做,所以想在这个地方讲的是这些东西,而不是AWT和Swing本身,所以这是很重要的,这是第二点,第三个重要的点,就是要在这里学习事件机制,事件机制是什么意思呢,这个机制也是影响极其的深远,它一个是体现了一些基本的思想,第二个它体现了它的这种做法在整个java体系里面也是到处都有的,但是只有在这个地方学,后面都不学,比方说像WEB,其实WEB也有事件机制,但是所有的WEB书都不会给你讲事件机制,包括后面的框架也有事件机制,但是它已经不讲了,那么在这个时候咱们必须给它掌握了,这是最最重要的三点,实话实说,AWT和Swing本身咱们也就是凑合一下,差不多也就行了,但是不是说不学,也保证你能开发,但是学的是方法,假如说算第四点,就是AWT和Swing学习的一个方法,比方说像组建哪些东西,学上一两个就够了,剩下的东西你就知道该怎么做了,如果从这个角度来看的话,咱们这一章不但重要,而且非常重要,可以这么说,你这一章很多东西理解不了,包括它的事件机制,包括表现层的一些做法,你理解不了,我告诉你,你后面的WEB就没法学了,到WEB你会听不懂的,比方后面的Servlet,讲的都是这儿的,你这个地方没好好学,你后面就晕了,你到网上都找不到,这个东西也就这儿总结了,别人没有总结,这是独家的,你没有这个基础,你到后面就完全听不明白,所有这个东西在这个地方就是非常重要,好,下面咱们一起来看一下,第一个,什么是GUI

什么是GUI(图形用户接口)

大家听清楚,我发的音是goo-ee,不是“鬼”,GUI:graphical User Interface,图形用户接口,也就是人机交互图形化的用户界面,实际上GUI表达的就是咱们做的应用程序,实际上PPT也是一个人机交互程序,它会给你提供一个图形化的界面,然后让你去操作,让你能够感受的到,这个地方要注意的就是,包括今后大家在java里头学的所有的技术,没有按拼音读的,比方说你们后面会学习一种设计模式叫做DAO设计模式,很多人喜欢读“到”,你一张嘴到,我一听就倒,这没办法,这个东西,说白了,完全就像一个外行去看一个东西,有些专业术语你都不会读,包括后面你们会用到的一个技术叫AJAX,很多人就读作阿贾克斯,还有人拿这个词到金山词霸里查,查到的就是阿贾克斯这个球队,音标也是那个,所以他只好照那个读了,你一定要相信这个技术是老外发明的,他发明的时候绝对没有考虑中国人的感受,绝对不会把拼音加进去,你如果你知道标准的英文发音就读英文,不行就读全部的单词,再不济就读字母,就像IBM、Ti这样的公司一样,这个大家要注意,反正稍微了解一下就行了,这就是GUI,下面大家看一下AWT是什么?AWT:Abstract window Toolkit,抽象窗口工具箱,包括了丰富的图形、用户界面组件和布局管理器的支持,还有对界面事件的处理机制等等。一句话,AWT就是用来画界面的,而且它还是非常古老的画界面的技术,但是有一点,如果说你想要去做手机应用程序开发,AWT是一定要好好看的,因为在做手机应用程序的时候,其实做手机应用程序无非就是做一些小游戏、小程序,它的重点就是两个,涉及到java的技术就是两个,一个就是AWT,其中最重要的就是canvas(画布),另外一个就是多线程,其实它涉及的java的东西不是太多,好,这个大家了解一下就行了,知道AWT就是用来画界面的就行了,大家再看下面一个

Component(组件)

在这个地方先讲一个设计上的概念,那么从设计上来讲,什么叫做组件呢?能完成一定功能的封装体就是组件,从设计角度上讲,它是一个封装体,就是表示它是一个完整的个体,它本身是独立的,而且它能完成一定功能,这就意味着这个功能是别人来帮它实现的还是它自己实现的啊,自己实现的对吧,封装的就是说它是可以独立存在的,是一个完整的个体,这个设计上的概念大家体会一下,事实上,我们可能会听到这样一些词,系统、子系统、模块、组件等等,那大家想想,最小的组件可以小到什么程度啊,小到一个类,大到整个系统都叫组件,放到图形界面中,组件就是具有图形界面,并能完成一定功能的封装体,也就是说Component就是GUI里头的组件,AWT提供用于所有java应用程序中的基本GUI组件,还为应用程序提供与机器的界面,这将保证一台计算机上出现的东西与另一台的相一致,这个其实就是表示可以移植。显示在屏幕上的每个GUI组件都是抽象类组件的子类。到底显示在界面上什么东西是组件呢,比方说一个文件浏览器上的各个菜单、toolbar等等都是界面上的组件,好,这就是component,大家理解一下,下面看另一个概念

Container(容器)

容器就是包含其他组件的组件。也就是说容器本身也是组件,只不过它这个组件比较特殊一点,它本身不显示某个按钮,某个控件,而是做一个框框,里面放其他的组件,Container是Component的一个抽象子类,它允许其他的组件被嵌套在里面。这些组件也可以是允许其他组件被嵌套在里面的容器。于是就创建了一个完整的层次结构。在屏幕上布置GUI组件,容器是很有用的,Container的分类,主要分了两个,一个是Panel,一个是Window,大家看这两个,我们先猜一下,只要你的英文不是烂到家的,都能猜出个大概,window什么意思总知道吧,窗口的意思吧,我们看到的界面,最外围的大框框就是window,panel什么意思呢?面板,什么叫面板呢,实际上所有的东西都是放在板上的啊,window又分成两类,window又有两种形式:Frame(框架)和Dialog(对话框),最典型的框架就是一个铁架子,黑板的边框嘛,里面放的白板就是Panel,对话框是什么?简单演示一下即可,所谓图形界面它的好处就是有一个直观的,大家看的到的,panel确定一个四边形,其他组件可以放在其中,panel必须放在window之中(或window的子类中)以便能显示出来。好,这就是容器的概念,再看下一个概念

组件定位

什么叫组件定位,就像我们刚才说的,比方说白板吧,外面是Frame,然后放了一个Panel,组件定位就是我的这些组件在这个Panel里头的摆放位置,就是组件在容器里的位置,就叫做组件定位,组件的位置是由布局管理器决定的。组件的位置就是由布局管理器来管理的。再看下面一个

组件大小

刚才我们说组件摆放的位置,哪还有大小吧,比方说我一个组件摆放在左上角,但是有多大呢,大小也是由布局管理器来管的,这就意味着今后我把组件往界面上摆放,目标就是要使得界面看起来和谐,再看一下补充几个概念,刚才的几个概念都是AWT和GUI里头非常普通的概念,接下来这几个可能书上就没有了,但是很重要的,所以大家要理解。

其他概念:

1、        界面是“画”出来的,咱们看到的这个图形界面拿放大镜看,实际上就是点阵,就是由一个一个的点组成的,比方说分辨率是1024*768,意思就是一排上面有1024个点,在有限的空间里面,点排的密一点,你看起来就是一条线了,咱们看到的所有的图形界面都是这样,我们用一个Button,其实用的就是别人画好的一个按钮,不是摆出来的,

2、        界面是“叠加”出来的,这是什么概念呢,还拿白板来看,如果从垂直角度看,就是带着字的白板,但是如果从侧面看呢,那就是分层的了,最底层是Frame,上面是Panel,再上面就是一个一个字了,就是三层。图形界面也是这样,就是由很多个界面叠加起来的,这个概念你要去理解,比方说我们后面讲的事件机制,事件机制里有事件链,它就是上层传给下层,下层再传给下层,你必须得分出层次才能传,要是你们看的都是平面一块,那就没法讲了,

3、        这里给大家推荐的是常见的界面组合方式:组件放在Panel里面,Panel放在Frame上,切换界面就是切换Panel。也就是说,通常用Panel来进行界面的组合,一个Panel就是一个界面,那么界面的切换就相当于切换Frame里面的Panel。管理的时候就不是一个组件一个组件的管理,而是一个Panel一个Panle的管理。

基本概念就讲完了,下面该写例子看看了

Frame的使用

Frame是window的一个子类。它是带有标题和缩放角的窗口。它继承于java.awt.Container,因此,可以用add()方式来给框架添加组件。框架的缺省布局管理器就是Border Layout。它可以用setLayout()方式来改变。我们来写个例子吧。

import java.awt.*

public class MyFrame extends Frame{

   publicstatic void main(String[] args){

      MyFramemf = new MyFrame();

 

      mf.setSize(500,500);

      mf.setBackground(Color.blue);

      mf.setLocation(100,100);

      mf.setVisible(true);

     

   }

}

Panel的使用

像Frame一样,Panel用来提供空间来连接任何GUI组件,包括其他面板。每个面板都可以有它自己的布局管理器。还是来接着上面的例子接着画。

import java.awt.*

public class MyFrame extends Frame{

   publicstatic void main(String[] args){

      MyFramemf = new MyFrame();

 

      mf.setSize(500,500);

      mf.setBackground(Color.blue);

      mf.setLocation(100,100);

 

      Panel pan = new Panel();

      pan.setBackground(Color.black);//设置Panel

 

      mf.add(pan);//将Panel放在Frame上,编译运行,全黑了,说明把下面的那层全盖住了,

 

      Panel pan2 = new Panel();

      pan2.setBackground(Color.blue);

 

      mf.add(pan2);//再加一个Panel,这个时候编译运行,全部变成蓝色了,这说明什么,这说明布局管理器用后面这个把前面那个盖掉了,从分层的角度来说,就是底层是红色的Frame,然后上面放了块黑板,然后在黑板上又放了块蓝色的板,如果我想都显示出来怎么办呢,那就通过调整布局管理来控制。这个我们后来再来说。

 

      mf.setVisible(true);

     

   }

}

这个时候Panel就写完了,再看下面基本组件的使用

Button(按钮):这个组件提供了“按下并动作”的基本用户界面。可以构造一个带文本标签的按钮,用来告诉用户它的作用。还是通过例子来看它的用法吧。

import java.awt.*

public class MyFrame extends Frame{

   publicstatic void main(String[] args){

      MyFramemf = new MyFrame();

 

      mf.setSize(500,500);

      mf.setBackground(Color.blue);

      mf.setLocation(100,100);

 

      Panelpan = new Panel();

      pan.setBackground(Color.black);

 

      mf.add(pan);//这行移到红色代码之后

 

      /*Panelpan2 = new Panel();

      pan2.setBackground(Color.blue);

 

      mf.add(pan2);*/该部分可以注掉

 

      Button b = new Button(“Test”);

      b.setBackground(Color.red);//组件放在哪儿,放在Panel里吧,所以写下面一句

      pan.add(b);

      mf.setVisible(true);

     

   }

}

编译运行看看效果,Test按钮还可以点呢,好了,写到这儿咱们先观察一下吧,有没有看出什么苗头来,总结一下吧,实际上把这点代码搞懂了,AWT就over了,你几乎就算已经会了,你看,组件放在Panel里,Panel放在Frame上,最后把Frame显示一下,搞定,再来分段分段看,有玄机,看一下,从Frame的角度来看,MyFrame mf = new MyFrame();就是初始化一个Frame,

mf.setSize(500,500);

      mf.setBackground(Color.blue);

      mf.setLocation(100,100);

这几句话是设置它的相关属性,mf.setVisible(true);这句代码是把Frame显示出来,这是不是和Frame相关的,好,再来看Panel,Panel pan = new Panel();这句代码是初始化一个Panel,pan.setBackground(Color.black);这句代码是设置Panel的属性,mf.add(pan);这句代码是把Panel加到Frame上,再看组件,Buttonb = new Button(“Test”);初始化一个按钮,b.setBackground(Color.red);设置组件的属性,pan.add(b);将组件加到Panel里面,你没发现这三个是一样一样的嘛,反正就是三部曲,先是初始化,然后是设置一堆的属性,然后如果是组件,就加到Panel里, Panel加到Frame上,Frame就显示出来,完了吧,这个时候你体会一下这个界面吧,不就是完成这三步就行了嘛。其他的组件我就不用一一再演示了吧,大家自己看API就可以了,大家自己去学,只要去看要设置哪些属性就可以了,一般设置组件的属性包括字体、大小、位置、颜色这四个。设置完了,组件基本上也就画出来了,所以说方法很重要,我后面的组件根本不用讲,组件基本就这么多吧,就学完了,当然我们学的只是画的部分,等于已经知道组件怎么画出来了,界面不就是组件组合出来的嘛,我就不再一个一个去演示了,这里特别要提的是canvas(画布)这个组件,我来给大家简单的演示一下,帮助大家理解是什么意思,

Canvas

提供了一个空白(背景色)的空间,要使用画布,需要写一个类来扩展canvas,然后实现panit方法。比方说接上例:

import java.awt.*

public class MyFrame extends Frame{

   publicstatic void main(String[] args){

      MyFramemf = new MyFrame();

 

      mf.setSize(500,500);

      mf.setBackground(Color.blue);

      mf.setLocation(100,100);

 

      Panelpan = new Panel();

      pan.setBackground(Color.black);

 

     

 

      /*Panelpan2 = new Panel();

      pan2.setBackground(Color.blue);

 

      mf.add(pan2);*/该部分可以注掉

 

      Buttonb = new Button(“Test”);

      b.setBackground(Color.red);

      pan.add(b);

 

      TextFieldt1 = new TextFiled();

      t1.setSize(100,100);

      pan.add(t1);

 

      Canvas c1 = new MyCanvas();

      c1.setSize(100,100);

      c1.setBackground(Color.white);

      pan.add(c1)//一般canvas不用加到panel里面,因为canvas一般是独立的,画布和panel是异曲同工,都是一块板,然后在里面加东西嘛

 

 

     

      mf.add(pan);

      mf.setVisible(true);

     

   }

}

class MyCanvas extends Canvas{//新建一个类来实现Canvas

   publicvoid paint(Graphics g){//Graphics表示画笔,接红色字体代码,canvas的意思就是给你一块画布,然后再给你一支画笔Graphics,剩下的就不管了,剩下的就是你来自己画了啊,如果你水平好,你可以画的相当的好,如果你水平差,你可能就什么都画不出来,咱们就来简单的画点吧,反正我也画不好,意思意思就行了,因为画笔有不同的意思,其实在这里头切换颜色就相当于换画笔

      g.setColor(Color.Cyan);

      g.drawLine(10,10,20,20);//画直线

      g.setColor(Color.black);

      g.draw3Drect(20,20,40,40,true);//画矩形

      g.setColor(Color.black);

      g.drawString(“haha”,10,60)

   }

}

编译运行,大家可以看到画布上有一条直线和一个3D的矩形框,你看它这个意思就是这样,你看它这个button,其实它怎么做呢,比方说我们自己在画布上做一个button,你先画一个框框,里面有一个String文本,当鼠标放上去点击的时候,你是不是自己响应事件,就像点button一样,有一个凹进去的样子,你就再画一个凹进去的,给人的感觉就是一点就凹进去了,就是你自己画这些东西,当然你现在有现成的就不用自己画了嘛,这就是canvas,但是到了手机应用上,J2ME上,这一块肯定是要用的,由于手机的Cpu和内存都非常小,它就需要少占资源,那么所有的东西都交给你来画,那就很麻烦了,当然这上面还可以写点字,接紫色代码,如果把haha放在中间,是不是就像一个button了,当你去点的时候只不过没有反应而已,点的时候你再画一个凹进去的,是不是就像一个button了,其实人家的button就是这么画出来的,好,这就是canvas,下面看看dialog吧

Dialog

它的基本用法

Dialog d = new Dialog(f,”Dialog”,false);

注意,dialog有一些它自己的东西,比方说,对话框又分为无模式和模式的,什么叫做模式对话框呢,演示一下,任意打开一个模式对话框,大家可以看到,除了对话框内可以操作,外面的部分是不是无法操作啊,就是它的父窗口是无法操作的,只有把当前对话框响应完了才能操作父窗口,这就是模式对话框,非模式对话框就是可以操作其父窗口,一时还抓不到例子,因为dialog是和Frame平级的,对话框在创建时通常是不可见的。通常在对按下按钮等用户输入作出反应时,才显示对话框。我们来写个例子看看吧,接着上面的例子继续写,通常Dialog是不可见的,通常响应一个事件过后才会显示dialog,

import java.awt.*

public class MyFrame extends Frame implementsActionListener{

   StaticDialog d = null;

   publicstatic void main(String[] args){

      MyFramemf = new MyFrame();

 

      mf.setSize(500,500);

      mf.setBackground(Color.blue);

      mf.setLocation(100,100);

 

      Panelpan = new Panel();

      pan.setBackground(Color.black);

 

     

 

      /*Panelpan2 = new Panel();

      pan2.setBackground(Color.blue);

 

      mf.add(pan2);*/该部分可以注掉

 

      Buttonb = new Button(“Test”);

      b.addActionListener(new MyFrame());

      b.setBackground(Color.red);

      pan.add(b);

 

      TextFieldt1 = new TextFiled();

      t1.setSize(100,100);

      pan.add(t1);

 

      Canvasc1 = new MyCanvas();

      c1.setSize(100,100);

      c1.setBackground(Color.white);

      pan.add(c1);   

      mf.add(pan);

      //mf.setVisible(true);将该行注掉

      Dialogd = new Dialog(mf,”dialog”,false);//传参数,第一个是Frame,就是它的Parent,也就是说Dialog它得有一个父窗口,他不能凭空存在,传进去的Frame就是它的父窗口,这个时候你去运行,可能啥都看不到,必须要响应一个事件才能显示出来,下面先写一个实现事件的类,接下方的红色代码,第二个是缺省的名字,

     

      d.add(newLabel(“haha,Dialog”));//写完紫色代码以后再写该行代码,加个Label组件

d.pack();//

   publicvoid actionPerformed(ActionEvents arg0){

      d.setVisible(true);//现在要去button里面去触发这个事件,转紫色字体代码

   }

   }

}

class MyCanvas extends Canvas{//新建一个类来实现Canvas

   publicvoid paint(Graphics g){

      g.setColor(Color.Cyan);

      g.drawLine(10,10,20,20);//画直线

      g.setColor(Color.black);

      g.draw3Drect(20,20,40,40,true);//画矩形

      g.setColor(Color.black);

      g.drawString(“haha”,10,60)

   }

}

编译运行,看看效果吧,这时候就是一个无模式对话框,这儿只是简单设置一下,这个东西只要会基本的方法,剩下的大家就可以通过API去查找它的使用方法了,如果将Dialog d = newDialog(mf,”dialog”,false);false改成true,就变成模式对话框了,今天讲这个Dialog大家听起来可能还比较够呛,因为这里面很明显要加个事件,事件我们还没讲,所以理解起来可能还有点问题,等事件讲完了,你就能理解了,你先有意识的记一下吧,我们再看一下FileDialog,很简单,但是很有用,演示一下吧,只要把刚才的Dialog改成FileDialog就可以了,编译运行一下,看看,很帅吧,以后你要做桌面的东西,比方说打开,保存等等操作,就需要这个东西了吧,去看一下API吧,


 [WHB1]抽象是看不见摸不着的,本身就是一个抽象的概念。就是一个思维方式。就是把他们共通的东西找到。

  • 3
    点赞
  • 0
    评论
  • 14
    收藏
  • 一键三连
    一键三连
  • 扫一扫,分享海报

©️2021 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、C币套餐、付费专栏及课程。

余额充值