lampeter123 发表于 2010-7-19 16:33

JAVA入门教程(1)

Java历史
1991年,电视机,机顶盒,录象机的开发设计需要一种可移植、方便、高效的计算机语言。为了满足这种需求,由Sun公司的Patrick Haughton和James Gosling领导的技术小组开发了JAVA。
上世纪九十年代中期,Sun推出了Sun Java Development Toolkits 1.0,简称JDK1.0。JDK1.0是一个功能强大的软件包,可以用来开发小应用程序和多种操作系统(Sun Solaris,Windows Nt,Windows 95,Macintosh)的应用程序。
1998年Sun推出Java 2 Platform,它定义了所有Java技术的概念和标准,即包括已经实现的技术也包括尚未实现的技术;即包括Sun的实现的,也包括其它公司的实现。目前,Java 2 SDK1.6是Java 2 Platform的最新定义。这个定义又可以细分为四个版本:
标准版:J2SE(Java SE),用于开发普通的小应用程序和应用程序。
企业版:J2EE(Java EE),用于企业级应用。
微型版:J2ME(Java  ME),用于开发移动电话,机顶盒,个人数字设备等。
JavaCard:适用于智能卡的Java平台。

Java安装配置
各平台安装程序可以到SUN的网站(java.sun.com)下载,在安装完成后应该进行配置,通常集成开发环境(IDE)自带JDK或进行了自动配置,如果我们通过控制台编译或运行Java程序则必须手动配置。
JDK: 是Java开发工具包(J2SE Software Development Kit)的缩写,用Java语言编写applet小程序和应用程序的软件开发环境,Java开发者应该安装JDK,并且通常JDK中已包含一个JRE拷贝。
JRE: 是Java运行环境 (Java Runtime Environment) 的缩写。它基本上就和Java虚拟机是同一个概念,运行Java程序的应该安装JRE。

Windows环境变量
应设置的环境变量包括:JAVA_HOME, PATH, CLASSPATH。
假设J2DK安装在“C:\JDK1.5”,环境变量可设置为:
    JAVA_HOME=C:\JDK1.5
    CLASSPATH=.;%JAVA_HOME%\lib\tools.jar;%JAVA_HOME%\lib\rt.jar
    PATH=%JAVA_HOME%\BIN;%PATH%

第一个Java程序
编写一个程序,输出一行文本信息:"Welcome  to Java Programming"

[attach]51943[/attach]

编写程序
1 //这是一行注释
2 public class Welcome {
3
4         public static void main(String[] args) {
5            System.out.println("Welcome  to Java Programming!");
6       }//main方法结束
7
8 }//类结束

行1:注释:用来说明程序的作用
行2:开始声明一个类,类名为Welcome, class是JAVA的关键字,用来定义类,public为访问说明。在这里我们声明了一个类,类的名字为Welcome.
行3,7:空行,用来增加程序的可读性
行4:是JAVA应用程序必须包含的方法,被称为入口方法:程序运行时从这里开始执行,其形式必须是:
public static void main(String[] args) 或者
static public void main(String[] args)
上面的语句里,只能修改 args这个字符串数组的名字。
static 表示直接能够加载该方法运行。
void 是方法的返回类型 ,每个方法必须有返回类型(除了构造方法)。
main是方法的名字,是程序的入口调用的方法。
String[] 是方法的参数,必须是String 数组类型的 。
行5:    System.out.println("Welcome  to Java Programming!");
就是输出字符串的,双引号中的字符串会在控制台输出。

也可以使用以下语句输出一个对话框显示信息:
JOptionPane.showMessageDialog(null, "Welcome\nto\nJava\nProgramming!" );
程序的开头要加入以下语句:
import javax.swing.JOptionPane;

行6:"}"表示方法的结束。
行8:表示类的结束。
编写完成后,以Welcome.java文件名保存到指定目录中。

配置环境和编译
在控制台使用如下命令编译刚才保存的文件Welcome.java
将会生成对应类名的.class文件:Welcome.class
[attach]51944[/attach]

运行JAVA程序
在控制台使用如下命令运行刚才生成的文件Welcome.class
[attach]51945[/attach]

[[i] 本帖最后由 lampeter123 于 2010-7-20 10:18 编辑 [/i]]

lampeter123 发表于 2010-7-19 16:55

Eclipse入门

1 开始使用Eclipse
假设你已经将Eclipse安装在目录c:\eclipse下。要启动Eclipse

双击后出现了工作区装载窗口

[attach]51946[/attach]
输入你的工作区所在的目录,本例所有项目、程序都存放在c:\smith目录下,故输入c:\smith,然后点击OK,Eclipse的图形界面就展现在你的眼前了。
[attach]51947[/attach]

如果你的工作区已经有项目存在,则项目就会显示在图形界面上。工作区实际上是一个存放项目文件的目录。

2.创建一个项目
要创建一个新项目,需要选择菜单项File->New->Project来启动新项目创建向导。

[attach]51948[/attach]

选择Java Project然后点击按钮Next进入下一步

[attach]51949[/attach]

3.创建程序
项目创建完毕后就可以在这个项目中创建程序了,选择菜单项File->New->Class来启动Java类创建向导
[attach]51950[/attach]

在Name后面的文本框中输入Welcome,检查是否选中了选项public void main(String[] args)。点击按钮Finish从Java Application的模板创建一个文本Welcome.java

[attach]51951[/attach]

在程序Welcome.java的main方法中输入语句System.out.println(“Welcome to Java”);。

[attach]51952[/attach]

4.编译和运行程序
一般情况下,源代码在你键入的时候就会自动进行编译。比如,如果你忘记在语句结束的时候键入分号(;),那么你就会看到一条红色的波浪线来指出你的错误。
在运行程序前要保证程序是正确并被编译通过了,换句话说就是程序中没有红色的波浪线了。

要运行程序Welcome.java,右键点击Package视图中的该程序,在弹出的菜单中选择Run->JavaApplication,

[attach]51953[/attach]

[attach]51954[/attach]

lampeter123 发表于 2010-7-19 17:01

面向对象概述

面向对象(Object Oriented)
面向对象是一个广泛使用但涵义并不清晰的术语。上世纪80年代面向对象主要指一种程序设计方法,但在随后的发展中,面向对象的方法已经渗透到计算机软件领域的许多分支。在了解面向对象之前我们必须得了解两个重要概念:对象和类。

什么是对象?
首先,我们来了解一下什么是对象。对象的概念最早出现于五十年代人工智能的早期著作中,在现实世界中,我们可以将对象定义为:

对象(Object):一个具有明确行为的有形实体,具有行为和状态,有着清晰的边界。
以面向对象的眼光来看待现实世界,现实世界中的每一个具体事物,我们都会将其看作一个对象,比如狗、电视机、自行车等,都可以是对象。狗有特定的名称、毛色、品种、会饿等特点,还有狗叫、尾巴左右摇摆等动作;同样的,自行车也有品牌、重量等特点,同时具备换速、刹车等动作;依此类推,我们不难发现,现实中的每一个对象都会有自己的特点以及相关动作,我们将对象所有特点(属性)称之为对象的状态,而动作称之为行为。

属性(Attribute):对象的一种内在的或独特的性质、特点、质量或特征,它构成了一个独一无二的对象(摘自<<Objects-Oriented Analysis and Design with Applications>>)
每一个学生具备名称、性别、年龄、身高、长相、爱好等属性,通过这些属性我们可以很快地区分所有的学生对象,因为这些属性构成了每一个独一无二的学生对象。比如说,在大部分情况下,我们会通过学生名称来区分每一个学生对象。我们经常会说:某某人很高,某某人很年轻等,高和年轻实际上就表现出某一个对象的内在的特点。因此,可以这样理解,我们是通过每一个对象特有的属性来区分各个对象。
注意:属性都有其对应的值,而且某些属性值是可以动态改变的,比如学生的体重、爱好是可以改变的,而DNA、指纹却是不能改变的。
随着属性值的改变,状态也随之改变了。比如,学生甲的身高值逐渐增长了,那学生甲的状态也发生了变化----长高了!

状态的具体定义如下:
状态(State):包含对象的所有属性以及属性值,会随着属性值的改变而改变。
没有一个对象是孤立存在的,对象之间是相互作用的。行为就是指这种作用。
行为(Behavior):是一个对象根据它的状态改变和消息传送所采取的行动和做出的反应,代表了对象对外的可见、可测试的动作。(摘自<<Objects-Oriented Analysis and Design with Applications>>)

行为和状态是相互的。Dog处于饿的状态时,就会采取进食等行为,进食完毕后,Dog又会处于饱的状态。现实中,大多对象是通过接受另一个对象传递的一个消息(操作)来改变自身的状态。比如:驾驶员扭动汽车启动钥匙至关闭位置,此时,汽车关闭引擎。其中,驾驶员、汽车都是对象,而关闭引擎则是一个特定消息,是由驾驶员传递给汽车的消息,汽车根据这个消息改变状态---从启动状态变为关闭状态。因此,消息实际上就是一个对象对另一个对象施加的操作。

lampeter123 发表于 2010-7-19 17:03

什么是类?
在现实世界中,我们会将所有脊椎动物对象进行分类:鸟类、两栖爬行类、哺乳类,比如我们将leopard、tiger、monkey等脊椎对象都归纳为哺乳类,而eagle、crane、swan属于鸟类。那我们又是怎样去判断哪种动物是属于哪一类的?
我们发现,leopard、tiger、monkey都具备viviparity(胎生)、lactation(哺乳)、coat(皮毛)等哺乳动物特征,而eagle、crane、swan却具备feather(羽毛)、flight(飞翔)、beak(喙)等鸟类特有的特征。不难发现,。这些对象都具有一些共同的属性和行为,通过这些属性和行为的划分,我们将leopard、tiger、monkey划分为哺乳类,而eagle、crane、swan划分为鸟类。

类和对象是紧密关联的,没有对象就没有类!

类的定义:类(Class)是一组共享公共属性和公共行为的对象的集合。

类可以是一种抽象,是一个蓝图,即可以不具体存在;它实际上是对一组对象的公共部分的归纳集合,而对象却是一个具体的存在,是实体而非抽象。我们可以这样说:类Mammal代表所有哺乳动物的共有特征,所有的非公有特征都不属于类Mammal,比如皮毛的颜色、喜好的食物等都不会归纳到类Mammal。而当我们想去识别某个特定的哺乳动物时,就必须指定"这个哺乳动物"或者"那个哺乳动物",即对象,也就是说,当我们需要去分析哺乳动物特有的属性和行为时,那就必须针对具体的对象来研究,而不是针对一个抽象的类!

注意:在软件开发中,我们将一个对象称之为类的一个实例。

没有公共结构和公共行为的对象,我们不能将它们组合在一个类中。比如,我们都知道不能将pinaster和squirrel组合成一个类,是因为pinaster和squirrel并没有共享结构和公共行为,比如 pinaster有枝叶而squirrel没有,squirrel能行走而pinaster不能等等。

在现实世界中,我们认识事物的时候都是首先将所见事物进行分类,然后按照每种类别去逐一研究事物对象。同样,在软件领域中,我们也是将问题进行分解分类(将大问题分解成许多小问题,每个小问题可以归纳为一类),然后再针对每一类进行分析设计以及实现,这就是广义的面向对象。

面向对象定义:尽量模仿现实世界,在软件中将复杂问题中的实体都作为一个对象来处理,然后根据这些对象的结构和行为再划分出类(即实现问题的分解),最后实现软件的模拟仿真。
也可以理解为:现实世界中的实体抽象成模型世界、程序世界中的对象和类,设计它们以完成预期任务并具有更好的可维护性和健壮的结构。

注意:在软件领域中,类包含了对象的属性和行为的定义,是一组属性和行为操作的集合!

面向对象的主要特性
程序语言发展到如今,已出现了多种风格不一的编程语言,大体主要分为以下两种风格的语言:
    面向过程(以C语言为代表)
    面向对象(Java、Smalltalk、C++等)
并不是说哪种语言是最好的,每种风格语言的好坏是针对不同方面的应用程序而言的。比如说,面向过程语言适用于计算密集型操作(复杂算法等)的设计,而面向对象适用于广阔的应用程序范围(这也是Java能迅速发展并占据广大市场的原因之一)。

面向过程(Process Oriented):基于该风格的程序的设计都是在想怎样才能一步一步的解决问题,思维方式是过程,或是说步骤。

那怎么区分面向过程和面向对象的区别,只要是具备以下特点的语言我们都可以将其看作为实现了面向对象的语言:

抽象(Abstraction)
在前面,我们已知道通过抽象创建出了类,类就是抽象的一种表示。抽象是解决问题复杂性的一种基本方法,抽象会重点强调我们应该注意什么,而不应该注意什么!通过抽象我们可以着重实现需关注的细节而忽略一些非实质性或无关紧要的细节,从而能够更好的分析问题解决问题!

抽象:表示一个对象与其它所有对象区别的基本特征,为不同的观察者提供不同的角度来观察对象。
对同一个对象---电视机,维修员只会关注电视机的内部结构:线路是否断裂、显像管是否损坏等,而购买电视的客户却只会关心电视机的画面是否逼真、外观是否好看等等,客户不会也不用关心电视机的内部结构。可以看出,维修员只关心电视机的内部,购买用户只关心电视机的外观及实用性,他们观察电视机的角度不一致,这就是抽象的实现,在现实生活中,到处都存在着抽象!

抽象强调实体的本质、内在的属性,在软件开发中,抽象指的是在决定如何实现对象之前对对象的意义和行为的定义----即类的实现!通过对类的了解,我们也可以将抽象定义为:从许多事物中舍弃个别的、非本质的特征,抽取共同的、本质性的特征。   

为什么需要抽象?
首先,问题的复杂性日益增长,其中描述的事物也越来越复杂,如果分析员去了解每一个事物的所有方面,那将是一个穷举耗时、低效率的工作
其次,如果没有识别所有对象的共同特征的行为,那么对这些对象的分类将无法进行!
对象分类是面向对象中非常重要的环节,而抽象则是实现该环节的必须解决方案!抽象的本质就是将问题分解,在面向对象中,所有对象被抽象成各种类,从而让程序员集中关注于功能的实现!

lampeter123 发表于 2010-7-19 17:04

封装(Encapsulation):
我们再来思考上面的例子,对于客户来说,他只会关心电视机的外观及性能是否良好等方面,电视机中的显像管、集成电路板对客户而言是不可见的,即电视机的生产商已将这些配件通过电视机外壳包装了起来,对客户,配件是隐藏的!现实中,这样的例子很多,比如计算机对于购买者而言,其组成部分CPU、Mainboard、HD等PC配件都被封装在机箱内,是不可见的!

封装:是一种信息隐蔽技术,是一个划分抽象的结构和行为的过程。

封装和抽象是互补的,抽象着重于对象的行为,而封装着重于对象的行为的实现。抽象实现抽取众多对象的公共特征和行为,而封装则是在不同的抽象之间设置明显的分隔线,从而导致每一个观察者关注内容的明显分离!没有抽象的封装将无意义;没有封装的抽象是不完整的!
在开发中,封装可以更通俗的理解为:把代码和代码所操作的数据捆绑在一起,使这两者不受外界干扰和误用的机制,封装可被理解为一种用作保护的包装器,以防止代码和数据被包装器外部所定义的其他代码任意访问,对包装器内部代码与数据的访问通过一个明确定义的接口来控制。
封装代码的好处是每个人都知道怎样访问代码,进而无需考虑实现细节就能直接使用它,同时不用担心不可预料的副作用。


继承(Inheritance)
类和类之间的关系之一,是面向对象系统的最基本要素。继承表明为一个"是一种"的关系,在现实中有很多这样的例子:人类是一种哺乳动物;树是一种植物类…….。为什么人类是一种哺乳动物?答案:因为人类具备所有哺乳动物的特征(结构和行为),也就是说,人类继承了哺乳动物的特点!我们经常会这样描述:儿子继承了父(母)亲的优(缺)点!
继承:一个类共享一个或多个类中定义的结构和行为,表示的是一种泛化/特化的层次关系。

注意:类和类之间实现继承的前提是这些类之间有着必然的联系---"类A是一种类B"!假如:我们将树继承于哺乳动物,而人类则继承于植物类!大家想想看,结果会如何?
我们在使用继承的时候必须得遵守这样的检验标准:如果类B"不是一种"类A,那么类B必然不是从类A继承而来!
    当类B继承于一个类A,那么这种继承称之为单继承。
    当类B继承于多个类A、C、D等,那么这种继承称之为多继承。
也可以这样理解,当实现单继承时,那类B继承了类A的所有属性和方法(比如人类继承了哺乳动物的所有属性和行为);而实现多继承时,类B继承了A、C、D等多个类的所有属性和方法(比如儿子继承了母亲和父亲的所有优、缺点)。其中,类B称之为子类(派生类)subclass,而类A、C、D则称之为超类(父类、基类)superclass

泛化(generalization):将一些有关联的不同类中的共同结构和行为抽取出来,构成一个新的超类。
特化(specialization):和泛化正好相反,从一个或者多个超类中派生出多个子类。

简单理解就是:超类的创建代表泛化,子类的派生代表特化!
子类除了能通过继承共享父类的属性和行为之外,还能修改继承于父类的属性和行为,最重要的一点就是:能在继承父类的基础上定义属于子类自身的属性和方法,从而实现扩展!以上这段话就是继承的最神奇之处!面向对象使继承得到了充分的体现!

学习技巧:大部分同学在初学时往往对继承、抽象等概念一知半解,建议大家多结合实际例子来学习,可以多想想周边的现实世界中哪些是继承、哪些是抽象。

多态(Polymorphism)
我们来假设思考一下现实中的一个问题:
假设,某条snake和某条cabrite,它们都取名为Jack,注意,snake和cabrite有着共同的超类---爬行类,都具备爬行类的公共行为:爬行。但我们发现,虽然一个名字Jack代表两种不同类对象,但是这两个对象却是用不同的方式来爬行,Jack(snake)通过蠕动身上的鳞片来爬行,而Jack(cabrite)则通过四肢来爬行。
虽然,在现实中我们可以通过给这两个对象取不同名字用以区分,但上面的例子向我们演示了这样一个过程:
一个名字可以表示许多不同类(这些不同类必须拥有一个共同的超类)的对象,从而实现以不同的方式来响应某个共同的操作集。
以上就是多态的定义,多态只有通过继承才能实现!在后面章节我们会详细掌握多态以及其运用。
以上就是面向对象的一些主要特征,具备了这样特征的程序语言我们就可以称之为面向对象语言。


[[i] 本帖最后由 lampeter123 于 2010-7-19 17:05 编辑 [/i]]

lampeter123 发表于 2010-7-19 17:14

面向对象的程序设计

类和对象的描述
现实社会中一切皆对象。比如:人,树,石头,猫,鸟,汽车,地球等等。任何一种对象都具有静态的属性,但不一定具有动态的行为。比如:石头。一般情况下,对象既有静态的属性,也有动态的行为。对象本身的属性和行为之间可以相互影响,比如:一个人饿了(属性),就会去吃饭(行为)。相反,这个人吃饭(行为)后,就饱了(属性),体重(属性)也增加了。不同的对象之间也可以相互作用。比如:人看到汽车开过来了,就会沿着路边走。如果这个人站在路中间不动(他不怕死),那么汽车就会停下来。那么怎么用Java语言来实现上述功能呢?后面实例分析有实现。
如同建筑设计师设计建筑图(建筑的蓝图),可以用该图来盖出许许多多这种风格的房子一样。类是对象的蓝图,是用来描述对象的,你可以用该类,来实例化许许多多个该类型的对象,类就是对象的模板。在类中定义了一套数据元素(属性)和一套行为(方法)。数据是用来描述具体的一个对象(静态),行为是用来描述该类型对象的共性,也就是该对象能够做什么(动态),以及完成相关对象之间的交互,从而改变对象的状态。同样对象的状态也能够对象的行为。属性和方法都叫做类的成员。例如杯子装水的时候:最大盛水量和当前盛水量。盛水的方法要始终跟踪这两个属性。 装水时改变了当前盛水量的属性,同样当当前盛水量等于最大盛水量(水装满时),就会影响装水的行为,将不再倒水。
我们下面通过一个具体事例来说明Java中如何实现以上概念。
例1:
编写一个类,描述人吃饭,体重增加这个简单操作。
下图描述了一个“人”

[attach]51958[/attach]

这是一个UML中的类图,我们对它进行简单说明。
    第一行:是类名Person,代表我们正在说明一个“人”的概念。
    第二行:是属性,“-” 号代表这个属性只有这个类自己可以访问,weight代表属性的名字,double表示属性的类型,这里意思是“人有一个体重的特性,体重可以是小数,别人不能直接看出人有多重,必须使用某种称量体重的方法”。
    第三行、第四行:是构建器,“+” 号代表public访问权限,含义是任何人可以访问到它。构建器是外界创造出这个“概念”的实际“例子”的入口,第三行是按照“缺省”方式构建,第四行是按照特定方式构建,特定方式是指按照参数指定的属性构建
    第五行、第六行:是方法,其中eat方法有参数,参数名字是temp,参数类型是double,该方法的返回类型为void,该方法含义是人可以吃一定数量的食物,吃完不需要给外界任何回馈。第六行的方法getWeight()没有参数,返回double类型,含义是看这个人的重量。
声明类
Java中的语法和C一样,语句都是以分号结束,区分大小写。
在Java技术中采用下列方法声明类:
<modifier>  class  <name>{
<attribute_declaration>
    <constructor_declaration>
    <method_declaration>
}
说明:
<modifier>:暂时只用"public",含义为:可以被所有其它类访问。或者不加public,    在修饰类的访问权限的时候,只有两种:1,就是加上public,表示所有类都可以访问。 2,就是什么也不写,表示本包访问权限,在讲到包的含义时再理解。
<name>:任何合法的标识符。它代表所声明类的名称。
Java中的标识符(类名,变量名,方法名)是由字母,数字,下划线(_),美圆符($)组成,数字不能用于标识符的开始。其中长度不受限制,不能使用java中的关键字,并且是区分大小写的。比如:class,void等关键字。Java中的关键字都是由小写的字母组成的,所以在我们并不知道java中有那些关键字的情况下,在定义标识符的时候,只要不全是小写的字母,就不会和java中的关键字相冲突。
<attribute_declaration>:声明属性。也就是说用变量表示事物的状态。
<constructor_declaration>:声明构造函数。也叫构造方法,也叫构造器。是用来实例化该类的实例(对象)的。
<method_declaration>:声明方法。来说明事物能够做的事情,也就是行为。
注意:属性,方法,构造函数在类中的顺序没有固定的约束。一般习惯性地先声明属性,后声明方法(习惯性地把构造方法写普通方法的前面)。
所以Person这个类的初始版本应该是:
public  class Person{
}
声明属性
<modifier>  <type>  <name>  [ = <default_value> ];
说明:
<name>:任何合法的标识符。它代表所声明属性的名称。
<modifier>:暂时只用“public”和“private”,其中private含义为:仅能被所属类中的方法访问,这称作封装。
<type>:可以是任何原始类型(基本类型)或其它类(引用类型)。
[=<default_value>]是给属性初始化为给定的值。如果没有的话初始化为默认的值。(基本类型的初始化相应的值:比如:int,short,byte,long,char(Unicode码值)初始化为0,float,double初始化为0.0,boolean初始化为false,所有的引用类型都初始化为null)。
注意:Java语言与其它语言不同,在JAVA中声明属性和初始化属性值必须一句完成。不能分开写:先声明,再另起一行初始化。
例如:
private int a ;
a=5;   //错误
private int b=6;//声明一个属性 b,并初始化为6;
在类里面除了声明语句之外,是不能直接写其它执行语句的。 a=5 是赋值语句。如果要执行语句应放在语句块(方法块,初始化块等)里执行。
据此,我们的Person类成为如下样子:
public  class Person{
private double weight;
}
声明构造器
<modifier> <class_name>  (<parameter>) {
<statement>
}
说明:
<class_name>:是类名,构造器的名字和类名必须一样。
<modifier>:可以是public,指明可以被任何其它代码访问。private,指明仅能被同一个类中的其它代码访问。
<parameter>:向构造器传递参数,可以没有参数。传递多个参数时,参数之间用逗号分开。每个参数由参数类型和标识符组成,这和声明属性的方式很类似,但是不能向参数赋初始值。
注意:构造器必须没有返回类型,并且构造方法的名字和类名必须一样。当一个类里面如果没有声明构造方法,那么虚拟机将自动给该类一个默认的构造器(不带参数的构造器)。如果在该类里面,定义了带参数的构造方法,那么虚拟机将不再为该类提供默认的构造方法,也就是该类不能按缺省方式构建了。
我们的类此时变成:
public class Person {
    private double weight;

    // 该类的默认的构造器
    public Person() {
    }

    // 带参数的构造器
    public Person(double d_weight) {
        weight = d_weight; // 实例化对象时,给weight属性赋初始值
    }
}
声明成员方法
<modifier>  <return_type>  <name>  ( <parameter> ){
<statement>
}
<name>:任何合法的标识符。
<modifier>:可以是public,指明可以被任何其它代码访问,private:指明仅能被同一个类中的其它代码访问。
< return_type>:指明方法返回值的类型。假如方法不返回值,应被声明为void。
<parameter>:向方法传递参数。传递多个参数时,参数之间用逗号分开。每个参数由参数类型和标识符组成。
注意:方法必须有返回类型,返回类型为void和没有返回类型是不一样的。除了返回类型void外,有其它返回类型的方法块里必须有return语句。
我们的类此时变成(Person.java):
public class Person {
    private double weight;

    // 该类的默认的构造器
    public Person() {
    }

    // 带参数的构造器
    public Person(double d_weight) {
        weight = d_weight; // 实例化对象时,给weight属性赋初始值
    }

    public void eat(double temp) { // 吃饭的方法
        weight = weight + temp; // 吃多少,体重就增加多少
    }

    public double getWeight() {// 得到人的体重属性
        return weight; //返回weight属性
    }
}

private封装
在类里面声明属性的时候,一般把属性的访问权限定义成private,封装的要求。这样只能在类里面访问该属性,在类的外面是没有访问的权限的,也就是说对于该类的实例(对象),是不能够直接访问该对象的属性的。这样就会保护对象状态不会非法改变。
比如,人的体重是不能直接修改的,通过吃饭可以增加人的体重,如果该人很瘦,是不能直接把20斤牛肉放到该人身上,就算增加该人的体重的。同样的道理,如果该人很胖,也不能够从该人身上割下20斤肉,而让体重下降20斤。
所以我们在以上的类中声明weight属性为private。
public 公共访问
在类里面声明方法的时候,一般把该方法定义成public访问权限。在程序运行的时候,就是通过对象和对象之间的交互来实现的。为了保证对象都能够执行功能(方法),应该把方法的访问权限定义成public。
我们对方法getWeight()的处理就是这样。
下面是测试Person的PersonApp.java
public class PersonApp {
    public static void main(String[] args) {
        // p1是声明的变量,类型是Person类型的,并且引用了Person类的一个对象,且使用默认的构造器构造对象
        Person p1 = new Person();
        // p2 同p1,使用带参数的构造器
        Person p2 = new Person(120);

        // p1所引用的对象(简称p1对象),吃了2.5斤
        p1.eat(2.5);
        // p2 对象 吃了4.3斤
        p2.eat(4.3);

        // 打印出p1的体重
        System.out.println("p1的体重为:" + p1.getWeight());
        // 打印出p2的体重
        System.out.println("p2的体重为:" + p2.getWeight());
    }
}
编译和运行的过程如下图:
[attach]51959[/attach]

lampeter123 发表于 2010-7-19 17:21

例2
我们知道如果文件名出现重复,那么我们要放在不同的目录中,class文件作为类的字节码载体,如果存在类名重复,那么class文件就应该放到不同的目录下,就类似文件夹的方式来管理文件。在Java里面是通过包的结构来管理类的。下面我们就上面例子修改下,再加个类,定义不同的包,放在不同目录下进行访问。
源文件的布局
Java技术源文件只有三部分,采用下面的布局:
[<package_declaration>]
<import_declaration>
<class_declaration>
说明:
<package_declaration> 声明包的语句,包通常使用小写字母,用.作为分割符,这是一种逻辑结构划分的方法。
<import_declaration> 导入包语句
<class_declaration> 类的声明语句

源文件命名
源文件的名字必须与该文件中声明的公有类的名字相同。一个源文件中可以包含多个类,但是最多只能包含一个公有类,显然,这个文件的名字应该是这个public类的名字后缀是“java”。如果源文件中不含公有类,源文件的名字不受限制。
包的声明
多数软件系统是庞大的。为了方便管理,通常要将功能相似的类组织成包,通过包来管理类文件。在包中可以存放类,也可以存放子包,从而形成具有层次结构的包。包可以根据需要任意组织,通常,要按照类的用途、含义来组织包。如下UML 包图:
[attach]51960[/attach]

Java技术提供了包的机制,以次来组织相关的类。声明包的句法如下:
package <top_pkg_name> [.<sub_pkg_name>];
你可以使用package命令指明源文件中的类属于某个特定的包。例如:
package    shenzhen.luohu;
public class    Person{
    //…
}
package声明必须放在源文件的最前面,或者说可执行代码的第一行,或者除了注释之外的第一行。一个源文件最多只能有一条package声明。一条package声明对源文件中的所有类起作用。如果你的源文件中没有package声明,你的类将在“缺省”包中,这个缺省包的位置就是当前目录。
包的导入
当你想要使用包中的类的时候,可以用import命令告诉编译器类在哪里。import命令的语法:
import  <pkg_name>[.<sub_pkg_name>].<class_name | *>;
例如:
import    shenzhen.nanshan.*;
import    shenzhen.futian.*;

import    java.util.List;
import    java.io.*;
当你使用import指令时,你并没有将那个包或那个包中的类拷贝到当前文件或当前包中。你仅仅是将你在import指令中选择的类加入到你的当前名字空间。无论你是否导入当前包,当前包都是你的名字空间的一部分。
import命令指明你要访问的类。例如,你需要访问Writer类,你需要下面的命令:
import    java.io.Writer;
如果你需要访问一个包中的所有类,你需要下面的命令:
import    java.io.*;
但是不会导入java.io下子包的类。
默认情况下,系统将自动导入java.lang包。
import  java.lang.*; //源文件里不管有没有写上该句,该句的功能永远存在。
import    java.io.Writer;与import    java.io.*;的区别如下:
如果写类名,那么虚拟机将直接从所在包里找到该加载执行,如果写*号,编译器会CLASSPATH指定的路径,一个一个路径去找,直到找到该类为止。
javac和java命令都有-classpath 参数,他们就是通知编译器或虚拟机在哪些路径中查找可能用到的类。
包与目录的布局
由于编译后的字节码文件在文件系统中存放,包结构就以目录结构的方式体现,包的名字就是目录的名字。例如,shenzhen.luohu包中的PersonApp.class文件应该在 path\shenzhen\luohu目录中。
*运行的时候进入到path目录下:
path>java  shenzhen.luohu.PersonApp 或者
path>java  shenzhen/luohu/PersonApp
我们没有使用-CLASSPATH参数,系统是如何工作的呢?
首先,系统级别的CLASSPATH环境变量应该是个“.”,表示当前路径,在运行javac或java的时候如果没有-classpath参数就会使用环境变量中的CLASSPATH环境变量,所以系统将从当前路径开始查找类,也就是在“path”路径下,那么按照shenzhen/luohu/路径查下去,确实能找到PersonApp类,所以运行正常,在这里我们把path路径称为顶层目录。
在这种情况下不能进入到shenzhen目录下运行:path\shenzhen>java luohu.PersonApp或者path\shenzhen>java luohu\PersonApp,也不能进入到上一级目录运行。假如你把shenzhen 放在daima文件夹里,你也不能进入到daima 那个文件所在目录下运行:
path>java daima\shenzhen\luohu\PersonApp 或者
path>java daima.shenzhen.luohu.PersonApp
如果想在任何目录下运行正确,必须有三个限制:
n    CLASSPATH环境变量或-CLASSPATH参数已指向顶层目录
n    class文件按包路径完整结构存放。
n    类的名字必须是全限定名,就是必须包含包路径的类名。
上面是运行时的目录结构,我们可以通过拷贝的方式将类文件防止为以上形式并运行成功。我们也可以在编译的时候用下面两种方式把包的结构生成出来。
通常我们需要把源文件和类文件完全分离,例如我们我们把源文件按包路径完整的存放到src目录中,我们将来编译的类文件将按照包路径完整的存放到build目录中,如下图:
[attach]51961[/attach]

为了达到以上目的,我们手工开始工作,虽然将来有集成开发环境(IDE)帮助我们,但是这对我们明白包的含义很有帮助。
1、手动建立文件夹。把包的结构创建出来
我们分别使用三个包,在一个我们选定的工作目录下按照上图结构创建好目录,其中build目录下的结构无需创建(但是build自身要创建),src目录中存放源文件,他们分别按照自己的package声明存放到不同目录。
2、自动把包的目录结构生成出来。
进入工作目录(src和build的上级目录),执行以下命令:
[attach]51962[/attach]
就自动把类按包的结构生成到build目录中了。
其中-d指定了生成带有包结构的类的顶层目录,-sourcepath 指定了源文件查找路径,显然,由于PersonApp要使用到其他包里的其他类,而这些类还是以源文件形式存在的时候,编译器必须能查找到他们并同时将他们编译成功才可以编译PersonApp自身,编译器对源文件的查找方式和虚拟机对类的查找方式类似。
<PersonApp.java>
//声明包
package shenzhen.luohu;

//导入包
import shenzhen.nanshan.*;
import shenzhen.futian.*;

//公共类PersonApp
public class PersonApp {
    public static void main(String[] args) {
        // p1是声明的变量,类型是Person类型,并且引用了Person类的一个对象
        Person p1 = new Person();
        // p2 同p1
        Person p2 = new Person(120);
        // c1是声明的变量,类型是Cat类型的,并且引用了Cat类的一个对象
        Cat c1 = new Cat();
        c1.jiao();

        // p1所引用的对象(简称p1对象),吃了2.5斤
        p1.eat(2.5);
        // p2 对象 吃了4.3斤
        p2.eat(4.3);

        // 打印出p1的体重
        System.out.println("p1的体重为:" + p1.getWeight());
        // 打印出p2的体重
        System.out.println("p2的体重为:" + p2.getWeight());
    }
}
<Person.java>
//声明包
package shenzhen.nanshan;
//声明公共类Person
public class Person {
    // 声明该类的一个属性,访问权限为private ,对该属性进行封装,实例化时,给该属性的初始化默认值0。
    private double weight;

    // 该类的默认的构造器
    public Person() {
    }

    // 带参数的构造器
    public Person(double init_weight) {
        // 实例化对象时,给weight属性赋初始值
        weight = init_weight;
    }

    // 吃饭的方法
    public void eat(double temp) {
        // 吃多少,体重就增加多少
        weight = weight + temp;
    }

    // 得到人的体重属性
    public double getWeight() {
        // 返回weight属性
        return weight;
    }
}
<Cat.java>
//声明包
package shenzhen.futian;

//声明公共类
public class Cat {
    public void jiao() {
        System.out.println("cat jiao......");
    }
}
运行时情况如下:
[attach]51963[/attach]
当然我们也可以在build目录下,利用CLASSPATH环境变量已经带有的.(当前路径)直接找到要运行的类(shenzhen.luohu.PersonApp),我们也可以利用绝对路径形式制定CLASSPATH,例如:
[attach]51964[/attach]
而且绝对路径相对路径的概念在编译、运行的时刻同样有效。
对于CLASSPATH我们可以通过环境变量方式设置,例如希望从系统级别设置类路径,也可以通过-CLASSPATH参数形式设置,例如希望临时更改类路径。

lampeter123 发表于 2010-7-19 17:22

例3
人看到汽车开过来了,就会沿着路边走。如果这个人站在路中间不动(他不怕死),那么汽车就会停下来
这里涉及到两个对象的相互作用,我们先声明两个类,来描述上述现象,然后再用一个测试类来实现。
〈CarPersonApp.java〉
class Car {
    private boolean moving;

    public boolean getMoving() {
        return moving;
    }

    public void move(boolean side) {
        if (side) {
            System.out.println("车继续行驶");
            moving = true;
        } else {
            System.out.println("车停下来");
            moving = false;
        }
    }
};

class Person {
    boolean side; // 表示人是否在路边

    public boolean walk(boolean car) {
        if (car) // 如果有车的话,人往路边走
        {
            System.out.println("人往路边走");
            side = true;
        } else {
            System.out.println("人直着走");
            side = false;
        }

        return side;

    }
};

public class CarPersonApp {
    public static void main(String[] args) {
        Person p = new Person();
        Car c = new Car();
        c.move(true); // 车在行驶
        p.walk(c.getMoving());// 测试人是否能行走,也就是说车的行为,影响了人的行为
    }
}
编译和运行:
[attach]51965[/attach]

lampeter123 发表于 2010-7-19 17:29

Java语法基础

基本语法元素

注 释
注释是程序员用来标记、说明程序的。编译器会忽略注释中的内容,注释中的内容不会对程序的运行产生任何影响。Java语言允许三种风格的注释:
//  单行注释
多用于对属性,变量以及算法重要转折时的提示
/*  多行
注释 */
多用于对类、方法及算法的详细说明,一般在对类的注释中要有以下内容:
1.    类的简要说明
2.    创建者及修改者
3.    创建日期或者最后修改日期
/** JAVA文档
*注释
*/
产生Java文档,使用javadoc命令.

分号
在Java编程语言中,语句是一行由分号(;)终止的代码。
例如:
totals = a + b + c + d + e + f;
语句块(block)
语句块(block)也叫做复合语句。一个语句块(block)是以上括号和下括号{}为边界的语句集合;语句块也被用来组合属于某个类的语句。例如:
public class Date {
    private int day = 3;

    private int month;

    private int year;

    public void pri() {
    }

    public static void main(String[] a) {
    }
}

语句块可被嵌套。我们以前见到的main方法就是一个语句块,它是一个独立单元。
下面的语句是合法的:
// a block statement
{
    x = y + 1;
    y = x + 1;
}

// an example of a block statement nested within another block
// statement
while ( i < large ) {
    a = a + i;
    if ( a == max ) {
        b = b + a;         // nested block is here
        a = 0;
    }
    i++;
}
还有一种静态语句块,这个我们将在学习static关键字时介绍.

空白
空白:是空格、tabs和新行(换行符)的统称。
在源代码元素之间允许插入任意数量的空白。空白可以改善源代码的视觉效果,增强源代码的可读性。例如:
{
    int x;
    x = 23 * 54;
}

{
    int  x;
    x  =  23  +  54;
}

标识符定义
标识符是语言元素的名称,是我们在程序中表示变量、类或方法等等的符号。
n    标识符由字母、下划线(_)、美元符号($)或数字组成,但不能以数字开头。另外可以使用中文做标识符,但实际开发中不推荐这样做。
n    标识符是大小写不同的敏感。
n    标识符未规定最大长度,但实际工作中不会对标识符命名过长,10个字符以内合适,标识符的命名尽可能的有意义 。
下列标识符是有效的:
idendsafdstifier
ugfdsgName
Udsaf_dsfe
_sys_varldaf
$changdsafe
Java技术源程序采用双字节的"统一字符编码" (Unicode,使用16bit编码)标准,而不是单字节的 ASCII(使用8bit编码)文本。因而,一个字母有着更广泛的定义,而不仅仅是a到z和A到Z。
标识符不能是关键字,但是它可包含一个关键字作为它的名字的一部分。例如,thisone是一个有效标识符,但this却不是,因为this是一个Java关键字。

Java关键字
下面列出了在Java编程语言中使用的关键字。
abstract    do                implements    private    throw
boolean    double    import    protected    throws
break    else             instanceof    public      transient
byte    extends                int      return    true
case    false    interface    short          try
catch    final    long    static         void
char    finally            native      super      volatile
class    float    new    switch         while
continue                  for    null    synchronized    default
if               package    this        

关键字对Java技术编译器有特殊的含义,它们可标识数据类型名或程序构造(construct)名。
以下是有关关键字的重要注意事项:
    true、false和null为小写,而不是象在C++语言中那样为大写。
    无sizeof运算符;所有类型的长度和表示是固定的,不依赖执行。
    goto和const不是Java编程语言中使用的关键字。

基本Java数据类型
Java编程语言定义了八种原始数据类型:
    类型    位数(bit)    默认值
逻辑型    boolean    1bit    false/true
文本型    char    16bit(2byte)    '\u0000'0(Unicode编码)
整数型    byte    8bit(1byte)    0
    short,    16bit(2byte)    0
    int,    32bit(4byte)    0
    long    64bit(8byte)    0
浮点型    double,    64bit(8byte)    0.0
    float    32bit(4byte)    0.0
注意:整数类型默认的是int,浮点型默认的是double

逻辑型--boolean
逻辑值有两种状态,即人们经常使用的 “true”和“false”。这样的值是用boolean类型来表示的。boolean有两个文字值,即true和false。
以下是一个有关boolean类型变量的声明和初始化:
boolean truth = true; //声明变量值为真
注意:在整数类型和boolean类型之间无转换计算。有些语言(特别值得强调的是C和C++)允许将数字值转换成逻辑值, 这在Java编程语言中是不允许的;boolean类型只允许使用boolean值。

字符型--char
使用char类型可表示单个字符。一个char代表一个16-bit无符号的(不分正负的)Unicode字符。一个char文字必须包含在单引号内(‘’’’)。
‘a’
‘\t’       一个制表符
‘\u????’   一个特殊的Unicode字符。????应严格按照四个16进制数字进行替换。例如: ’\u03A6’表示希腊字母“Φ”
char类型变量的声明和初始化如下所示:
char ch = `’A'’; // 声明并初始化一个char型变量
char ch1,ch2 ; // 声明两个char型变量
char是int兼容的类型,比如可以如下声明:
int a = ‘a’; // a = 97
char c = 65; // c = ‘A’

字符串类--String
String不是原始类型,而是一个类(class),它被用来表示字符序列。字符本身符合Unicode标准。与C和C++不同,String不能用 \0作为结束。
String的文字应用双引号封闭,如下所示:
“The  quick brown fox jumped over the lazy dog.”

String类型变量的声明和初始化如下所示:
// 声明两个String型变量并初始化他们
String greeting = "Good Morning !! \n" ;
String err_msg = "Record Not Found !" ;
String str1,str2 ; // 声明两个字符串变量

整数型--byte, short, int, long
在Java编程语言中有四种整数类型,它们分别使用关键字byte, short, int和long中的任意一个进行声明。整数类型的文字可使用十进制、八进制和16进制表示,如下所示:
    十进制值是2
    首位的0表示这是一个八进制的数值
    0xBAAC  首位的0x表示这是一个16进制的数值
注意──所有Java编程语言中的整数类型都是带符号的数字。
整数类型文字数字被默认为int类型。
整数类型数文字后面紧跟着一个字母“L”,可以强制它为long型。
例如:
    2L        十进制值是2,是一个long
    077L      首位的0表示这是一个八进制的数值
    0xBAACL  前缀0x表示这是一个16进制的数值
四种整数类型的长度和范围前面已经列出,这些长度和范围是按Java编程语言规范定义的,是不依赖于平台。

浮点数--float和double
如果一个数字文字包括小数点或指数部分,则该数字文字默认为double型浮点数。
例如:   
3.14
3.02E23        
如果一个数字文字后带有字母F或f,则该数字文字为float型浮点数。
例如:      
2.718F        
如果一个数字文字后带有字母D或d,则该数字文字为double型浮点数。
例如:      
123.4E-306D  
浮点变量可用关键字float或double来声明。
Java技术规范的浮点数的格式是由电力电子工程师学会(IEEE)754定义的,是独立于平台的。

变量声明和赋值
变量用于存储信息。一个变量代表一个特殊类型的存储位置,它指向内存的某个单元,而且指明这块内存有多大。变量的值可以是基本类型,也可以是对象类型。
下列程序显示了如何多种类型的 变量,的如何进行声明及赋值的。
public class TestAssign {
    public static void main(String args[]) {
        int a, b; // declare int variables
        float f = 5.89f; // declare and assign float
        double d = 2.78d; // declare and assign double
        boolean b = true;// declare and assign boolean
        char c; // declare character variable
        String str; // declare String
        String str1 = "good"; // declare and assign String variable

        c = 'A'; // assign value to char variable
        str = "hello ,hello"; // assign value to String variable
        a = 8;
        b = 800; // assign values to int variables
    }
}

引用(Reference)类型
从大的范围来讲,Java中的数据类型就分为两种:基本数据 类型和引用类型,前面已经对基本数据 类型(也称为主数据类型)进行了讲解,下面我们再来理解引用类型。
创建一个新类型
为克服Java中数据类型的不完整,Java编程语言使用类来创建新类型。例如可以用下面的类表示人:
class Person {
    private double height = 1.75;
    private double weight = 65;
    private String name;

    public Person(String aName) {
        name = aName;
    }

    public Person() {
    }
}
关键字class是用来声明类的。
Person是指定我们给表示人的这个 类指定的名称。
height一个变量可被声明,是为归属于类型Person的一个属性。, 本类Person中包含有3个属性。

而从而体重,身高部分将被隐含声明。例如:
Person zhangsan, lisi; 声明Person类有两个引用,引用名称为:zhangsan、lisi。
zhangsan = new Person();//类这两个对象为引用类型,zhangsan,lisi 分别指向了各自的对象Persion构造出实际的内存空间,zhangsan这个引用,指向这个内存空间。
lisi = new Person();//同上。因此这两个变量zhangsan、lisi都是引用类型的,并不实际存储这些数据

创建并初始化一个对象
当任何原始基本数据 类型(如boolean, byte, short, char, int, long, float或和 double类型) 的变量被声明时,内存作为上述操作的一部分,存储器空间也同时被分配。空间同时被分配,此分配属栈;
使用非原始基本数据类型(如String或class)的变量的声明,不为对象同时分配存储器内存空间。事实上,使用class类型声明的变量不是数据本身,而是数据的引用(reference)。引用可以理解为C语言的指针(Pointer),但是不能象C语言那样计算指针。
在你使用引用变量之前,必须为它分配实际存储空间。这个工作是通过使用关键字new来实现的。如下所示:
Person pangzi;
pangzi= new Person();
第一个语句仅为引用分配了空间,而第二个语句则通过调用对象的构造函数Person()为对象生成了一个实例。这两个操作被完成后,Person对象的内容则可通过pangzi进行访问。
还可以用一条语句创建并初始化一个对象:
Person pangzi = new Person(“danan”);
使用非基本数据类型(String)变量的声明,分两种情况:
使用String str = “班集”,指向的是内存中的特殊区域,叫字符串池;
而 String str = new String(),则和class变量声明的规则一致。

存储器分配和布局
在一个方法体中,做如下声明:
Person liuxiang;
liuxiang= new Person();
语句Person liuxiang;  仅为一个引用分配存储器,存储空间里的值没有确定。
         liuxiang                                             
在语句liuxiang= new Person() 中关键字new意味着为对象分配存储器内存空间,并初始化。:   
注意:由于使用了缺省构造函数,而缺省构造函数中并未对name属性进行赋值操作,所以name属性是没有初始化的。

引用类型的赋值
在Java编程语言中,一个被声明为类的变量,叫做引用类型变量,这是因为它正在引用一个非原始基本数据类型,这对赋值具有重要的意义。请看下列代码片段:
int x = 7;
int y = x;
String s = “Hello”;
String t = s;
四个变量被创建:两个原始基本数据类型 int 和两个引用类型String。x的值是7,而这个值被复制到y。x 和 y是两个独立的变量且其中任何一个的进一步的变化都不对另外一个构成影响。
至于变量 s 和 t,只有一个String 对象存在, 它包含了文本”Hello” ,s 和 t均引用这个单一的对象。

将变量t 重新定义, t= "World";则新的对象World被创建,而 t 引用这个对象。

Java
值基本数据 传递与引用传递
Java中的参数传递,都称为是传值。但传的这个值,到底是什么,这个就是我们需要研究的。传递的是一个基本数据,还是一个引用,这就要仔细区别。
基本数据类型,值是在栈中,引用数据类型,值是在堆中。
方法只能改变引用类型的值,而不能改变引用类型的地址和基本类型的值。也就是说方法只能改变堆内存中的值,而不能改变栈内存中的值。实例变量都是保存在堆内存里的。不管是引用类型还是基本类型。所有的引用类型的值都是保存在堆内存里的。
特别注意:对基本数据类型是pass by value,而对引用类型则是pass by ref.
例如:
public class TestReference {
    int i = 5;

    int j = 6;

    A a = new A();

    public void changeIJ(int m, int n)    {// 试图改变基本类型的参数的值
        int z = 0;
        z = m;
        m = n;
        n = z;
    }

    public void changeAB(A a1, A a2){ // 试图改变引用类型的参数的地址

        A a = null;
        a = a1;
        a1 = a2;
        a2 = a;
    }

    public void test(int c){ // 试图改变基本类型的参数的值

        c = c + 6;
    }

    public void testA(A a)    {// 试图改变引用类型参数的值

        a.i = 100;
    }

    public static void main(String[] args) {
        TestReference t = new TestReference(); // 构造本类的一个对象
        int z = 5;
        int y = 6;
        A aa = new A(); // 定义A类的一个对象aa
        t.testA(aa); // 改变对象aa的值
        System.out.println(aa.i); // 输出aa的值(属性值)
        A bb = new A(); // 再定义A类的一个对象bb
        t.changeIJ(z, y); // 试图改变两个基本类型的值
        t.changeAB(aa, bb);// 试图改变两个引用类型的地址
        System.out.println(z);
        System.out.println(y);
        System.out.println(aa.i);
        System.out.println(bb.i);
    }
}

class A {
    int i = 5;
};
输出结果如下:
100
5
6
100
5
可见,方法只能改变引用类型的值,而不能改变引用类型的地址和基本类型的值。

lampeter123 发表于 2010-7-19 17:30

this引用
this作为一个Java关键字,有两个作用:
n    代表隐含参数的调用
n    调用本类的其它的构造器
关键字this是用来指向当前对象(类实例)的。这里,this.name指的是当前对象的name字段。
例1
public class Person {
    private double height = 1.75;
    private double weight = 65;
    private String name;

    public Person(String aName) {
        this.name = aName;// 全称应该是:Person.this.name
    }

    public Person() {
    }
}
例2
public class Person {
    private double height = 1.75;
    private double weight = 65;
    private String name;

    public Person(String aName) {
        this.name = aName;// 全称应该是:Person.this.name
    }

    public Person(){
        this(“zhangsan”);
    }
}
例3
class Lamp {
    int watts = 60;
    boolean isOn = false;//属性声明

    Lamp(boolean startOn) {
        isOn = startOn;//这里isOn是上面声明的属性
    }

    public void setIsOn(boolean isOn) {
        for (int dummy = 1; dummy < 1000; dummy++) {
            System.out.println("The count is " + dummy);
        }
        this.isOn = isOn;//注意参数和属性名称相同,必须用this关键字来区分不同作用域
    }
}
下面的代码用于调用上面的代码
Lamp aLamp = new Lamp();
aLamp.setIsOn(true);

lampeter123 发表于 2010-7-19 17:30

Java编码约定
虽然任何一个合法的标识符都可以用来当作变量、类、方法等的名称,但是Java编程语言极力推荐它的编码约定:
n    classes──类名应该是名词,大小写可混用,但首字母应大写。
例如:
class  AccountBook
class  ComplexVariable
n    interface──界面(接口)名大小写规则与类名相同。
interface  Account
n    method──方法名应该是动词,大小写可混用,但首字母应小写。在每个方法名内,大写字母将词分隔并限制使用下划线。例如:
balanceAccount()
addComplex ()
n    Variables──所有变量都可大小写混用,但首字符应小写。词由大写字母分隔,限制用下划线,限制使用美元符号($),因为这个字符对内部类有特殊的含义。
currentCustomer
变量应该代表一定的含义,通过它可传达给读者使用它的意图。尽量避免使用单个字符, 除非是临时“即用即扔”的变量(例如,用i, j, k作为循环控制变量)
n    constant──原始常量应该全部大写并用下划线将词分隔;对象常量可大小写混用。
HEAD_COUNT
MAXIMUM_SIZE

n    control structures──当语句是控制结构的一部分时,即使是单个语句也应使用括号({})将语句封闭。例如:
if  (condition) {
   do something
}else  {
   do something else


n    spacing──每行只写一个语句并使用 tab键缩格使你的代码更易读。
n    comments──用注释来说明那些不明显的代码段落;对一般注释使用 //  分隔符, 而大段的代码可使用 /*···*/分隔符。使用 /**···*/将注释形成文档,并输入给javadoc以生成HTML代码文档。

// A comment that takes up only one line.
/* Comments that continue past one line and take up space on multiple lines... */
/**
* A comment for documentation purposes.
*
* @see Another class for more information
*/

lampeter123 发表于 2010-7-19 17:33

分支语句
条件语句使部分程序可根据某些表达式的值被有选择地执行。Java编程语言支持双路if和多路switch分支语句。
if,else语句
基本句法:
if ( /* 布尔表达式 */ ){
    // 语句或块;
}  else  {
    // 语句或块;

在Java编程语言中,if()用的是一个布尔表达式,而不是数字值,这一点与C/C++不同。前面已经讲过,布尔类型和数字类型不能相互转换。因而,如果出现下列情况:
if  ( x )  // x is int
你应该使用下列语句替代:
if  ( x != 0 )
else部分是可选的,并且当条件为假时如不需做任何事,else部分可被省略。
switch语句
基本句法:
switch (expr1){// int 兼容型
case expr2:
    //statements;
    break;
case expr3:
    //statements;
    break;
default:
    //statements;
    break;

注意:
在switch (expr1) 语句中,expr1必须与int类型是赋值兼容的;byte, short或char类型可被升级;不允许使用浮点或long表达式。
当变量或表达式的值不能与任何case值相匹配时,可选缺省符(default)指出了应该执行的程序代码。如果没有break语句作为某一个case代码段的结束句,则程序的执行将继续到下一个case,而不检查case表达式的值。
例:
switch (colorNum) {
case 0:
    setBackground(Color.red);
    break;
case 1:
    setBackground(Color.green);
    break;
default:
    setBackground(Color.black);
    break;
}
for循环
基本句法:
for (init_expr; boolean testexpr; alter_expr){
    //语句或块

例如:
for (int i = 0; i < 10; i++){
    System.out.println("Are you finished yet?");
}
System.out.println("Finally!");        
注意:
Java编程语言允许在for() 循环结构中使用逗号作为分隔符。 例如,for (i= 0, j = 0; j<10; i++, j++)是合法的;它将i值初始化为零,并在每执行完一次循环体后,增加一次它们的值。
while循环
基本句法:
while(布尔表达式){
    //语句或块        

例如:
int i = 0;
while (i < 10){
    System.out.println("Are you finished yet?");
    i++;
}
System.out.println("Finally!");        
请确认循环控制变量在循环体被开始执行之前已被正确初始化,并确认循环控制变量是真时,循环体才开始执行。控制变量必须被正确更新以防止死循环。
do循环
do循环的句法是:
do {
    //语句或块;
} while (布尔测试)

例如:
int i = 0;
do {
    System.out.println("Are you finished yet?");
    i++;
} while (i < 10);
象像while循环一样,请确认循环控制变量在循环体中被正确初始化和测试并被适时更新。
作为一种编程惯例,for循环一般用在那种循环次数事先可确定的情况,而while和do用在那种循环次数事先不可确定的情况。
break和continue
下列语句可被用在更深层次的控制循环语句中:
break [标注];
continue[标注];
label: 语句;    // 语句必须是有效的
break语句被用来从switch语句、loop语句和预先给定了label的块中退出。
continue语句被用来略过并跳到循环体的结尾。
label可标识控制需要转换到的任何有效语句,它被用来标识循环构造的复合语句。
例如:
loop:    while (true){
            for (int i = 0; i < 100; i++) {
                switch (c = System.in.read()) {
                case -1:
                case ' \n ':                  
                    break loop;
                    ....
                }
            } // end for
        } // end while

test:     for (...) {
            ....
            while (...) {
                if (j > 10) {
                    // jumps to the increment portion of
                    // for-loop at line #13
                    continue test;
                }
            } // end while
        } // end for   

计算1到100的合计值。
public class Number    {
    public int sum(){
        int totle = 0;
        for (int i = 1; i <= 100; i++){
            totle += i;
        }
        return totle;
    }
   
    public static void main(String[] args){
        Number obj = new Number();
        int temp = obj.sum();
        System.out.println(temp);
    }
}
输出结果
5050

lampeter123 发表于 2010-7-19 17:40

数组


数组的描述
数组是我们接触的第一个容器,数组是长度固定的容器。一但定义好大小,将不能改变。由于Java中的数据类型分为两种:基本类型和引用类型。所以数组也有两种类型的:基本类型的数组和引用类型的数组(数组的数组也是引用类型的数组)
数组声明
语法:
<modifier>  <type>  <name>[];

<modifier>  <type>[]  <name>;
说明:
<modifier> 目前可以用private,public或着默认的修饰,private是封装的访问权限。
<type> 可以是任何原始类型(基本类型)或其它类(引用类型)。
<name>:任何合法的标识符。它代表所声明属性的名称。
举例:
char s[ ];
Point p[ ]; // where point is a class
在Java编程语言中,无论数组元素由原始类型构成,还是由类构成,数组都是一个对象。声明不能创建对象本身,而创建的是一个引用,该引用可被用来引用数组。数组元素使用的实际存储器可由new语句或数组动态初始化来分配。
上述这种将方括号置于变量名之后的声明数组的格式,是用于C、C++和Java编程语言的标准格式。但 这种格式会使数组的声明的格式复杂难懂,因而,Java编程语言允许一种替代的格式,该格式中的方括号位于变量名的左边,意思是:声明一类型的数组,这个数组有一个引用名叫什么?:JAVA推荐使用此格式。
char[ ] s;
Point[ ] p;
这样的结果是,你可以认为类型部分在左,而变量名在右。上述两种格式并存,你可选择一种你习惯的方式。创建一组基本数据类型 char 的数组,其引用名为 s
创建一组对象 Point 的数组,其引用名为 p

数组声明不指出数组的实际大小。
注意:当数组声明的方括号在左边时,该方括号可应用于所有位于其右的变量。
char[ ] s1,    s2;        // s1,s2都是字符数组。
char     s1[ ],s2;        // s1是字符数组,s2是字符。

创建数组
语法:
<name> = new <type>[<int>];

<name> = new  <type>[]{};
说明:
<name>:任何合法的标识符。它代表所声明属性的名称。
<type>:可以是任何原始类型(基本类型)或其它类(引用类型)。
<int>: 一个整数值,数组的大小
用来指示单个数组元素的下标必须总是从0开始,并保持在合法范围之内--大于或等于0,并小于数组长度。任何访问在上述界限之外的数组元素的企图都会引起运行时出错。
public int[] createIntArray() {
    int[] k = null;
    k = new int[5];
    for (int i = 0; i < k.length; i++)
        k[i] = i + 1;
    return k;
}
你可以象创建对象一样,使用关键字new 创建一个数组。
k = new int [5];
创建了一个5个int值元素的数组。
内存中数组初始化情形如下:
[attach]51966[/attach]

对于应用类型元素的数组,示例如下:
public Point[] createArray() {
    Point[] p= null;
    p = new Point[5];
    for (int i = 0; i < p.length; I++)
        p[i] = new Point(i,i+1);
    return p;
}

数组在内存中的映像则如下:
[attach]51967[/attach]
当创建一个数组时,每个元素都被初始化。在上述int数组k的例子中,每个值都被初始化为0;在数组p的例子中, 每个值都被初始化为null,表明它还未引用一个Point对象。在经过赋值 p[0] = new Point(i,i+1)之后,数组的第一个元素引用为实际Point对象。
Java编程语言允许使用下列形式快速创建数组:
String[]  names [ ] = {
“Georgianna”,
   “Jen”,
   “Simon”,
};
其结果与下列代码等同:
String[]  names [ ] ;
names = new String [3];
names [0] = “Georgianna”;
names [1] = “Jen”;
names [2] = “Simon”;
这种”速记”法可用在任何元素类型。例如:
MyDate[]  Dates[ ] = {
new MyDate (22,7,1964),
new MyDate (1,1,2000),
   new MyDate (22,12,1964)
};
适当类型的常数值也可被使用:
Color[] palette  [ ] = {
Color.blue,
Color.red,
Color.white
};

例1
public class TestArray1 {
    public static void main(String[] args) {
        // 声明并初始化一个数组
        int[] num = new int[40];
        // 记录数组下标
        int k = 0;
        // 循环找素数
        for (int i = 2; i <= 100; i++) {
            int j = 2;
            for (; j < i; j++) {
                if (i % j == 0)
                    break;
            }

            if (j == i) // 表示是素数
            {
                num[k++] = i;
            }
        }

        // 输出所有素数,5个一行
        for (int i = 0; i < k; i++) {
            // 如果是小于10的数,多输出个空格,整齐
            if (num[i] < 10)
                System.out.print(num[i] + "   ");
            else
                System.out.print(num[i] + "  ");
            // 够5个数换行
            if ((i + 1) % 5 == 0)
                System.out.println();
        }
    }
}
结果如下:
[attach]51968[/attach]

例2
把一个班的名字(String类型,引用类型)保存起来,并进行输出。
public class TestStudents {
    public static void main(String[] args) {
        // 定义3个空间大小的数组
        String[] stu = new String[3];
        // 数组是通过下标来赋值,下标从0开始
        stu[0] = "java";
        stu[1] = "c++";
        stu[2] = "c#";
        // 下面的赋值将产生异常
        stu[3] = "j2me";
        // 输出数据
        for (int i = 0; i < stu.length; i++)
            System.out.println(stu[i]);
    }
}
运行的结果如下:
[attach]51969[/attach]

如果把stu[3] = "j2me"; 这句去掉,运行正常,结果如下:
[attach]51970[/attach]

例3
保存1到100之间的素数(除了1和它本身不能被其他整数整除的数)。
public class TestArray3 {
    public static void main(String[] args) {
        // 声明并初始化一个数组
        int[] num = new int[40];
        // 记录数组下标
        int k = 0;
        // 循环找素数
        for (int i = 2; i <= 100; i++) {
            int j = 2;
            for (; j < i; j++) {
                if (i % j == 0)
                    break;
            }

            if (j == i) {// 表示是素数
                num[k++] = i;
            }
        }

        // 输出所有素数,5个一行
        for (int i = 0; i < k; i++) {
            // 如果是小于10的数,多输出个空格,整齐
            if (num[i] < 10)
                System.out.print(num[i] + "   ");
            else
                System.out.print(num[i] + "  ");
            // 够5个数换行
            if ((i + 1) % 5 == 0)
                System.out.println();
        }
    }
}
运行结果如下:
[attach]51971[/attach]

lampeter123 发表于 2010-7-19 17:44

多维数组
Java编程语言没有象其它语言那样提供多维数组。因为一个数组可被声明为具有任何类型,所以你可以创建数组的数组(和数组的数组的数组,等等)。一个二维数组(JAVA中没有二维数组的概念,二维数组其实就是数组的数组)如下例所示:
Int[][]  twoDim [][] = new int [4][];
twoDim[0] = new int[5];
twoDim[1] = new int[5];
首次调用new而创建的对象是一个数组,它包含4个元素,每个元素的类型也是int数组型,必须将数组的每个元素分别初始化。
注意:
尽管声明的格式允许方括号在变量名左边或者右边,但此种灵活性不适用于数组句法的其它方面。
例如: new int [ ][4]是非法的。
因为这种对每个元素的分别初始化,所以有可能创建非矩形数组的数组。也就是说,twoDim的元素可按如下方式初始化:
twoDim[0] = new int [2]
twoDim[1] = new int [4];
twoDim[2] = new int [6];
twoDim[3] = new int [8];
由于此种初始化的方法烦琐乏味,而且矩形数组的数组是最通用的形式,因而产生了一种”速记”方法来创建二维数组。例如:
Int[][]  twoDim [ ][ ]  =  new int [4][5];
可被用来创建一个每个数组有5个整数类型的4个数组的数组。
数组界限
在Java编程语言中,所有数组的下标都从0开始。 一个数组中元素的数量存储在length属性中; 这个值被用来检查所有运行时访问的界限。如果发生了一个越出界限的访问,那么运行时的报错也就出现了。
使用length属性的例子如下:
Int[] list [ ] = new int [10];
for (int i= 0; i < list.length; i++){
System.out.println(list[i]);
}
使用length属性使得程序的维护变得更简单。
例4
存放java班,c++班,j2me班,嵌入式班四个班学生的名字,并打印出来。
public class TestFourClass {
    public static void main(String[] args) {
        // 声明4个空间大小,类型是String数组的数组
        String[][] stu = new String[4][];
        // 初始化java班的6个人
        stu[0] = new String[6];
        for (int i = 0; i < stu[0].length; i++)
            stu[0][i] = "java" + (i + 1);
        // 初始化c++班的3个人
        stu[1] = new String[3];
        for (int i = 0; i < stu[1].length; i++)
            stu[1][i] = "c++" + (i + 1);
        // 初始化j2me班的5个人
        stu[2] = new String[5];
        for (int i = 0; i < stu[2].length; i++)
            stu[2][i] = "j2me" + (i + 1);
        // 初始化潜入式班的4个人
        stu[3] = new String[4];
        for (int i = 0; i < stu[3].length; i++)
            stu[3][i] = "嵌入式" + (i + 1);
        // 打印各个班级的名单
        for (int i = 0; i < stu.length; i++) {
            switch (i) {
            case 0:
                System.out.println("java班:");
                break;
            case 1:
                System.out.println("c++班:");
                break;
            case 2:
                System.out.println("j2me班:");
                break;
            case 3:
                System.out.println("嵌入式班:");
            }
            System.out.print("   ");
            for (int j = 0; j < stu[i].length; j++) {
                System.out.print(stu[i][j] + "  ");
            }
            System.out.println();
        }
    }
}
运行的结果如下:
[attach]51972[/attach]

拷贝数组
数组一旦创建后,其大小不可调整。然而,你可使用相同的引用变量来引用一个全新的数组:
iInt[]  myArray[ ] = new int [6];
myArray = new int[10];
在这种情况下,第一个数组被丢弃,除非对它的其它引用保留在其它地方。
Java编程语言在System类中提供了一种特殊方法拷贝数组,该方法被称作arraycopy()。例如,araycopy可作如下使用:
iInt[]  myArray[] = { 1, 2, 3, 4, 5, 6 };      // 原始数组
int[]  hold[] = { 10, 9, 8, 7, 6, 5, 4, 3, 2, 1 };    // 新的更大的数组

System.arraycopy(myArray, 0, hold, 0, myArray.length); // 从没有Array拷贝所有元素到hold,从下标0开始
这时,数组hold有如下内容:1,2,3,4,5,6,4,3,2,1。
注意:如果数组保存的是基本类型的话直接把值拷贝过来。如果数组存放的是引用类型(类类型,数组类型(多维数组的拷贝)等),那么拷贝的是引用类型的地址,请看下面的例子:
例5
class AA {
    int i;

    public AA(int ii) {
        i = ii;
    }
}

public class TestArrayCopy {
    public static void main(String[] args) {
        // 声明数组并初始化,源数组
        AA str1[] = new AA[] { new AA(1), new AA(2), new AA(3), new AA(4) };
        // 拷贝的目的数组
        AA str2[] = new AA[str1.length];
        // 完全拷贝,array方法参数的介绍看api
        System.arraycopy(str1, 0, str2, 0, str1.length);
        // 改变目的数组
        str2[1].i = 5;
        // 打印原始数组,如果没有改变说明是两个数组
        for (int i = 0; i < str1.length; i++) {
            System.out.print(str1[i].i + "  ");
        }
        
    }
}
输出的结果如下:
[attach]51973[/attach]
显然,数组发生了改变,也就是经过拷贝操作后,原始的数组和新拷贝的数组没有分离,因为所拷贝的将是元素的引用。
对于多维数组,由于数组本身是引用类型,所以其拷贝特性与引用类型数组相同。
例6
public class TestMutipleDemensionArrayCopy {
    public static void main(String[] args) {
        // 定义数组的数组
        int[][] source = new int[5][];

        // 定义目的数组
        int[][] target1 = new int[5][];
        int[][] target2 = new int[5][];

        // 给源数组赋值
        for (int i = 0; i < 5; i++) {
            source[i] = new int[i + 1];
            // int temp=i;
            for (int j = 0; j < source[i].length; j++)
                source[i][j] = j + 1;
        }
        // 打印源数组的数据
        System.out.println("-------------源数据-------------");
        for (int i = 0; i < source.length; i++) {
            for (int j = 0; j < source[i].length; j++)
                System.out.print(source[i][j] + "   ");
            System.out.println();
        }
        // 数组的拷贝(浅拷贝)
        System.arraycopy(source, 0, target1, 0, source.length);
        // 改变目的1数组的值
        target1[1][0] = 100;
        // 打印源数组的信息,可以看到值改变,说明没有深拷贝
        System.out.println("-----------浅拷贝后输出-----------");
        for (int i = 0; i < source.length; i++) {
            for (int j = 0; j < source[i].length; j++)
                System.out.print(source[i][j] + "   ");
            System.out.println();
        }

        // 数组的深拷贝,先拷贝”第一维“的
        System.arraycopy(source, 0, target2, 0, source.length);
        // 再深拷贝
        for (int i = 0; i < 5; i++) {
            target2[i] = new int[i + 1];
            System.arraycopy(source[i], 0, target2[i], 0, i + 1);
        }
        // 改变目的2数组的数据
        target2[1][0] = 999;
        // 打印源数组的信息,可以看到值没有改变,说明是深拷贝
        System.out.println("-----------深拷贝后输出未把100改成999-----------");
        for (int i = 0; i < source.length; i++) {
            for (int j = 0; j < source[i].length; j++)
                System.out.print(source[i][j] + "   ");
            System.out.println();
        }
    }
}
输出的结果如下:
[attach]51974[/attach]

lampeter123 发表于 2010-7-20 08:09

继承

单继承(single inheritance)
在面向对象一章中我们学习了OO的特征之一:继承,是任何面向对象的语言必然实现的特性,java也不例外,但我们应该注意的是,java和某些面向对象语言(如c++)在实现继承的不同之处在于java只支持单继承,不支持多重继承。
即,java中一个类只能继承于另一个类。我们将被继承的类称之为父类(基类),继承类称之为子类(派生类)。在java中用关键字extends来实现单继承,语法如下:
class subclass extends superclass{...}
在前面所讲已知,实现继承关系的类之间有着必然的联系,不能将不相关的类实现继承,就象人类不能继承于鸟类!
那怎么去判断类和类之间是否有着必然联系呢?实际上,在第一章里面,我们已知当某类A和类B之间有着共同的属性和行为时,那么类A和类B之间就可能是继承关系或者有着共同的父类。
下面,假设我们开发某公司的员工管理系统,已知类Manager和类Employee,代码如下:
        
class Employee
        {
            public String f_name;
            public String l_name;
            public float salary = 0.0f;

            public String getEmpDetails()
            {....}
        }

        class Manager
        {
            public String f_name;
            public String l_name;
            public float salary;
            public String dept;

            public String getEmpDetails()
            {....}
        }

通过分析得知,在类Employee和类Manager中存在许多共同的属性和行为,在现实生活中,Manager是公司Employee之一,因此,我们可以将Manager类定义成Employee类的子类,修改类Manager如下:
        class Manager extends Employee
        {
            public String dept;

            public String getEmpDetails()
            {
                return "This is Manager!";
            }
        }
UML中类图表示为:
[attach]51982[/attach]

大家可能会质疑:Manager类重新定义后,原有的属性减少了(f_name,f_name,salary),岂不是违背了需求?!
当类A继承于类B时,子类A拥有父类B的所有成员变量和方法,换句话说,子类继承了父类的所有成员属性和方法,在父类中已定义的属性和方法,在子类中可以无需定义(除非方法覆盖)。
所以,在子类Manager中已继承了父类Employee中的属性和方法,无需再定义在父类中已有的属性(f_name,f_name,salary)。
好,我们已经学会了基本的继承语法,下面就来探讨一下继承带来的一些好处:
a.减少代码冗余
从上面的例子就可以看出,类Manager通过继承而无需再定义属性(f_name,f_name,salary),从而减少了代码量,试想一下,当公司员工分为许多不同级别员工时(如定义秘书、工程师、CEO等员工类),如果没有继承,那将是怎样的结果?
b.维护变得简单
假设公司要求给所有员工添加生日这一属性,那么,在没有继承时,我们的维护将变得困难(需修改每一个级别的员工类)。
c.扩展变得容易
当一个新的级别员工类需创建时,我们只需将该类继承所有员工父类Employee,接着再定义属于该员工的特有属性即可。
当然,以上所举例子只是继承带来的好处的部分体现,在下面的学习中我们将逐渐深入体会继承带来的优势。

小测试:以下代码是否正确?
        class B{...}
        class C{...}
        class D{...}
        class A extends B,C,D
        {.....}
java中一个类只能继承一个类,但一个类可以被多个类所继承。如:
        class Engineer extends Employee{...}
        class Secretary extends Employee    {...}

        class Manager extends Employee
        {
            public String dept;
            ...
        }
以上三个不同类分别继承了Employee类,即三个类拥有从父类继承过来的共同属性和方法。但是,请注意:这仍旧是单继承!以UML类图表示为:
[attach]51983[/attach]

提醒:构造方法不能被继承!一个类得到构造构造方法只有两种途径:自定义构造方法;使用JVM分配的缺省构造方法。但是,可以在子类中访问父类的构造方法,后面我们会深入。


访问控制
在java中是通过各种访问区分符来实现数据封装的,共分为四种访问级别(由高到低):private(私有)、friendly(缺省)、protected(受保护)、public(公共)。
注意:以上四种访问修饰符可以作用于任何变量和方法,类只可以定义为默认或公共级别(嵌套类除外)。
¨    public(公共)
当变量或方法被public修饰时,该变量和方法可以在任何地方(指的是任何包中)的任何类中被访问。

//类PublicSample中的构造方法、成员变量及方法均被定义为公共的访问级别
             package com.itjob;
              class PublicSample {
                 public PublicSample(){....}

                 public int num1;
              public byte bt1;
              public char ch1;

              public void method1(){....}
           }
通过以上定义,类PublicSample中的成员方法和变量可以在任何包中的任何类中访问,即访问是公共的,不受限制的。以下访问都是允许的:
访问1
             class PublicSample {
                 ......
                 public static void main(String[] args) {
                    new PublicSample().num1 = 100;
                    new PublicSample().ch1 = '\u0000';
                    new PublicSample().bt1 = 10;
                    new PublicSample().method1();
                 }
              }
访问2
             package com.java;
              import com.itjob.*;
              class A {
                 public void method1() {
                    new PublicSample().ch1 = 'a';
                    ....
                 }
              }
¨    protected(受保护的)
当类的变量或方法被protected修饰时,该变量和方法只可以在同包中的任何类、不同包中的任何当前类的子类中所访问。即不同包中的任何不是该类的子类不可访问级别为protected的变量和方法。
              //受保护的变量
              protected Stirng str = "";
              //受保护的方法
              protected String get(){return "";}

¨    friendly(缺省的)
当类的变量和方法没有显式地被任何访问区分符修饰时,该变量和方法的访问级别是缺省的。缺省的变量和方法只能在同包的类中访问。
              //缺省访问级别的变量和方法、类
              Float f1 = null;
              void method(){...}
              class C1{...}
¨    private(私有的)
被private所修饰的所有变量和方法只能在所属类中被访问。即类的私有成员和变量只能在当前类中被访问。
              //私有的构造方法和成员变量、方法
              private ClassName(){....}
              private int num = 0;
              private void method(){....}
通过以上学习,我们可以通过以下表格来描述四种访问区分符的访问限制:

方法重载(method overloading)
¨    成员方法重载
学习重载之前,我们来了解一下在java中方法的特征。
在java中,每一个方法都有自己的特征,其特征主要是指方法名以及方法的参数。
            void method1(){}
            void method2(){}
method1()和method2()可以被理解为是两个方法名不同的方法,即方法的特征不一致。
            void method1(int x){}
            void method1(){}
第一个method1()与第二个method1()虽然名字一样,但是却有不同的参数,因此,这两个同名方法仍有着不同的特征。
对于java编译器来说,它只依据方法的名称、参数列表 的不同来判断两个方法是否相同,如果出现两个名称相同、参数也完全一致的方法,那么编译器就认为这两个方法是完全一样的,也就是说方法被重复定义!
以下定义是错误的:
            class ClassName {
                void m1(){}
                void m1(){}
            }
对于以上两个方法定义语句,java解释器认为这两个方法完全相同,当执行到第二条语句时,它会告诉你方法m1()已在类ClassName中被定义!
可以这样理解,当我们在一个类中不能定义相同名称的多个方法,除非这些方法具有不同的方法特征(参数的不一致)。将上面语句修改为:
            class ClassName {
                void m1(int x){}
                void m1(){}
            }
这样,虽然方法名相同,但由于两个方法的参数不一致,因此,编译器就认为这是两个不同的方法,从而不会产生歧义。

好,在这里,大家可能就会质疑,把其中的某个方法换成不同的名称不也可以正常运行吗?
对,这样确实可以解决问题,但我们知道,在现实中,往往一个类会实现复杂的功能,其中定义的多种方法可能实现的功能意义都是一样,比如我们已经熟悉的System类中的静态对象中方法println(),在该类中println()被定义了多个,每一个方法都有不同的参数,现在我们已知道每一个println()都具有相同的功能:在控制台上输出内容!我们来假想一下,如果按照每个方法定义一个不同名称,那么我们将在System类中定义十多种不同名称的打印方法,虽然功能实现了,首先,我们是否需要编写代码前给这十几种方法取不同名称,并且还得保证名称唯一,这就会增加我们的工作量;其次我们还得记住每一个方法名对应的功能,如果稍有记错,那就会得到错误的结果!
因此,我们有更好的解决办法,通过重载,可以在一个类中定义相同名称、不同参数的实现相同功能的多个方法,这样就避免了给每个方法取不同名称、熟记每个不同名的方法对应的功能的额外工作量,提高了我们的开发效率。
当一个类中的多个同名方法满足以下条件时之一时,即实现了方法重载:
a.不同的参数个数
b.不同的参数类型
c.不同的参数顺序
            
小测试:以下哪几组方法实现了重载,满足了重载的那一个条件?
                组一:
                void m1(int x){}
                void m1(int x, int y){}
                组二:
                void m1(int x, String str){}
                void m1(String str, int x){}
                组三:
                void m1(int x, int y){}
                void m1(int y, int x){}
                组四:
                void m1(int x){}
                int m1(int x, int y){}
                组五:
                void m1(int x){}
                void m2(int x){}

¨    构造方法重载
如果有一个类带有几个构造函数,那么也许会想复制其中一个构造函数的某些功能到另一个构造函数中。可以通过使用关键字this作为一个方法调用来达到这个目的。
public class Employee {
    private String name;
    private int salary;

    public Employee(String n, int s) {
        name = n;
        salary = s;
    }

    public Employee(String n) {
        this(n, 0);
    }

    public Employee() {
        this(" Unknown ");
    }
}
在第二个构造函数中,有一个字符串参数,调用this(n,0)将控制权传递到构造函数的另一个版本,即采用了一个String参数和一个int参数的构造函数中。
在第三个构造函数中,它没有参数,调用this("Unknownn")将控制权传递到构造函数的另一个版本,即采用了一个String参数的构造函数中。
注:对于this的任何调用,如果出现,在任何构造函数中必须是第一个语句。
    构造函数中调用另一构造函数,其调用(this()、super())有且只能有一次,并不能同时出现调用。
分析例题3(Example3.java)的执行结果。
提醒:方法的重载都是基于同一个类!



方法覆盖(method overriding)
覆盖是基于继承的,没有继承就没有覆盖。在java中,覆盖的实现是在子类中对从父类中继承过来的非私有方法的内容进行修改或扩展 的一个动作(注意:不能违反访问级别的限制,即子类方法的访问级别不能低于父类方法的访问级别 )。
下面我们来分析一个例子:
        class A {
            public void method() {
                System.out.println("SuperClass method()");
            }
}

        class B extends A {
            public static void main(String[] args) {
                new B().method();
            }
}
执行以上代码得到的打印结果:"SuperClass method()"。
结果分析:
子类B继承了父类A中的公共方法method(),调用子类B的method()方法实际就是调用从父类中继承过来的method()的方法。

修改子类B:
    class B extends A     {
        public void method() {
            System.out.println("SubClass method()");
        }
        // main()
        ..............
}

再次执行以上代码得到的结果是:"SubClass method()"。
结果分析:
在类B中实现方法覆盖,父类 方法method()被子类B 覆盖了。在子类B定义了一个返回类型、方法名、方法参数列表 都和从父类中继承过来的方法method()一样的方法,那么,新定义的方法就会覆盖原有的方法,实际上就是对从父类继承过来的方法重写(覆盖) !
实现方法的覆盖必须满足以下所有条件:
        a. 覆盖方法的返回类型必须与父类中被覆盖方法的返回类型相同
        b. 覆盖方法的参数列表 类型、次序和方法名称必须与被覆盖方法的参数列表 类型、次序和方法名称相同
        c. 覆盖方法的访问级别不能比被覆盖方法访问级别低
     d. 覆盖方法不能比它所覆盖的方法抛出更多的异常。(异常将在下一个模块中讨论)
        
父类中定义的方法:public void m1(int x, String str){...}
以下是在各个子类中定义的方法:
        子类1:public String m1(int x, String str){...}
        分析:没有实现覆盖,方法的返回类型不同
        子类2:public void m1(String str, int x){...}
        分析:没有实现覆盖,方法的参数类型不同
        子类3:void m1(int x, String str){...}
        分析:没有实现覆盖,方法的访问级别不能被降低
        子类4:public void m1(int x, String str){...}
        分析:已实现覆盖,满足覆盖的所有条件

lampeter123 发表于 2010-7-20 08:10

基于继承的多态实现
多态:一个名字可以表示许多不同类(这些不同类必须拥有一个共同的超类)的对象,从而实现以不同的方式来响应某个共同的操作集。
在java中,名字指的就是变量名,我们可以认为每个对象都有一个名字---引用该对象的变量名。比如String str = "abcd";我们就称之为对象str。因此,在java中的多态就体现在一个变量可以引用多个不同类对象,前提是这些不同类必须有者共同的父类,从而该变量可以且只能调用每个不同对象之间的公共操作集(方法)。
很明显,多态的实现是基于继承的。比如,前面所说的Manager类继承了父类Employee的所有属性和方法,这就是说,任何在Employee上的合法操作在Manager上也合法!
一个变量只能有一个类型!这个类型是在编译时指定的,按照多态的定义,一个变量可以指向不同类型的对象,在java中,当这个条件成立时,那这个变量就是多态性的!我们先来看看java中多态的实现。
父类变量可以引用子类对象!这在java是允许的,即Employee e = new Manager()是合法的,但是请注意,变量e只能调用共同的成员属性与方法,即e只能访问子类从父类中继承过来的成员!
//error! 父类变量不能访问属于子类特有的成员(即非公有成员)
e.dept = "";
以上只是实现了多态的一部分:一个父类变量引用许多不同子类对象。
接着,我们再来分析以下例题:
class Engineer extends Employee {
    public String getEmpDetails() {
        return "Engineer getEmpDetails()";
    }
}

class Manager extends Employee {
    public String getEmpDetails() {
        return "Manager getEmpDetails()";
    }

    public void m1() {
    }
}
通过以上定义,我们已知类Engineer和类Manager分别继承了类Employee,并且都覆盖了从父类中继承过来的方法getEmpDetails(),在main方法中执行以下代码:
        1  Employee e = new Manager();
        2  System.out.println(e.getEmpDetails());
        3  e = new Engineer();
        4  System.out.println(e.getEmpDetails());
结果是执行语句2打印:" Manager  getEmpDetails()",执行语句4打印:" Engineer  getEmpDetails()"。
分析:首先,通过语句1和3可以得知父类变量e可以引用不同类型的子类对象,并且,当e引用子类Manager对象时调用方法getEmpDetails()时,返回的是子类Manager已覆盖的方法内容,因此,现在我们可以肯定一点,当子类覆盖父类方法时,子类对象访问的将是已被覆盖的内容!
其次,通过语句2和4的执行结果对比,父类变量引用子类对象时,调用的将是每一个被引用对象的成员方法,即引用不同的子类对象则调用该子类的成员方法,互不干扰!
如果增加以下语句:e.m1();
将会是编译错误,因为该父类变量调用了一个不属于公有的方法,m1()是Manager类的特有成员,而不是从父类继承下来的。Manager的特殊部分是隐藏的。这是因为编译者应意识到,e 是一个Employee的引用 ,而不是一个Manager的引用
因此,通过以上分析,我们可以得出结论:父类引用 可以引用子类对象,同时该父类引用 只能访问所有子类的公有操作集(从父类继承过来的成员);当子类中已覆盖继承方法时,父类变量调用的将是子类中的已覆盖方法!
可以创建具有共同类的对象的收集(如数组)。这种收集被称作同类收集。
就是因为有了java的这种多态机制,我们因而可以实现异类收集!java拥有一个顶层父类java.lang.Object,该类是所有类的顶级父类,在我们平常定义各种类时,虚拟机会自动在类的声明语句后加上继承Object类,如下:
    class Employee 与 class Employee extends Object是等同的!extends Object是JVM自动给任意类加上去的,从而保证java中的所有类都具备一个通用父类。
既然Object是所有类的父类,那么我们就可以通过Object数组来实现异类收集,由于多态性,Object数组就能收集所有种类的元素,如:
    Object[] obj = {"123", new Employee(), new Manager()}; //收集了三种不同类型对象
    Employee[] e = {new Employee(), new Manager(), new Engineer()};  //收集了三种不同子类对象
    可以分解为: e[0] = new Employee();
            e[1] = new Manager();
            e[2] = new Engineer();
以上语句恰好满足了java中的父类变量可以引用子类对象的定义,因此,以上语句都是合法的。
异类收集就是不相同的对象的收集。

请参考例题4(Example4.java)来详细了解多态的使用。

隐藏(hiding)
通过上面的学习,我们发现,父类的私有方法对于子类来说是不可见的,注意,不可见不等于 没有,子类仍旧继承了父类所有的成员,那么这些私有的父类成员去哪了?
实际上,它们都被隐藏,对子类来说,这些父类的私有成员都被隐藏了起来,从而导致子类中的不可见。

分析以下例题:
class A {
    private void method(String str, int i) {
        System.out.println("SuperClass method()");
    }
}

class B extends A {
    public static void main(String[] args) {
        // error! 父类中的私有方法对于子类来说是隐藏的,不可在子类中访问已被隐藏的成员
        // new B().method("",0);
    }
}

构造方法在继承中的使用
分析例题:
class A {
    A() {
        System.out.println("A()");
    }
}

class B extends A {
    B() {
        System.out.println("B()");
    }

    public static void main(String[] args) {
        new B();
    }
}

执行结果为分别打印:
        A()
        B()
分析得知,父类的对象是优先于子类对象而存在的(在现实生活中也是如此),也就是说,父类对象的构造在子类对象构造之前,先调用父类构造方法创建父类对象,再调用子类构造方法创建子类对象。
因此,在继承中,父类和子类的构造方法的调用次序如下:先调用父类构函再调用子类构函!

当然,不管是构造父类对象还是子类对象,都必须遵循以下步骤执行:
         a.静态语句的执行
         b.成员变量的初始化
         c.语句块的初始化
         d.构造方法的执行

         分析例题5()的执行结果。

super关键字
构造方法不能被继承,因此,在子类中调用父类构造方法只能通过super关键字来实现。super可以理解为父类在子类中的一个对象,我们可以象使用父类对象一样使用子类对象。
例:
class A {
    A() {
        System.out.println("A()");
    }
}

class B extends A {
    B() {
        super(); // 调用父类A的构造方法,打印"A()"语句
        System.out.println("B()");
    }
}

当执行new B()语句时,结果仍是:
        A()
        B()
因此,我们通过super关键字显示地调用父类的构造 函数 ,当然,也可以象使用this对象一样通过super调用父类的成员方法。比如:super.m1()等
但是,必须得注意:父类构造 函数 只能在子类构造 函数 中通过super显示调用,并且必须是第一句!
下面语句是错误的:
        B()
        {
            System.out.println("B()");
            //error! super()语句必须是方法中的第一条语句!
            super();  
        }

lampeter123 发表于 2010-7-20 08:11

包装类
对于每一个java基本数据类型,java都提供了对应的包装类,如:Boolean、Character、Integer、Double等8种包装类。

在java中,包装类主要是用于将基本数据类型与对象之间进行转换连接 。
        int num = 10;
String str = (String)num;
当执行第二条语句时,就会产生转换异常,因为我们在将一个非对象类型转换成一个对象类型!问题就是,我们应该怎样才能把基本类型转换成对象,很幸运的是,java的包装类就是为了专门解决这个问题而诞生的,我们修改以上语句,修改结果如下:
        int num = 10;
        Integer numObj = new Integer(10);
        String str = (String)numObj;
这样,就能正常实现转换,因此,可以得知,包装类就是解决基本类型转换成对象的专用类。
在5.0中,java已实现了自动装、拆箱,即基本类型和对应的包装类之间可以直接赋值,转换的工作由虚拟机代劳了!
    Integer numObj = 10;语句是正确的!其中基本类型到包装类的自动转换我们称之为自动装箱,反过来就是自动拆箱,比如:
        Integer numObj = 10; //自动装箱
        int num = numObj;    //自动拆箱
因为java是面向对象语言,我们在许多场合都会将基本类型作为一个对象来处理,因此,包装类就起到了很方便的作用!
包装类有许多共同的方法和静态常量:
        valueof(***) --static方法,将对应的基本类型转换成包装类
        parseXXX(String)   --static方法,将字符串转换成对应的基本类型
        toString()   --static方法,将基本类型转换成字符串对象。
        SIZE --静态常量,返回对应基本类型占用的字节位大小。
        TYPE --静态常量,返回对应的基本数据类型名称

== 与 equals()
在java中,==与equals()都是用来比较引用,只是==即可以比较基本类型,也可以比较对象,而equals()则只能在对象之间进行引用比较。
先来了解一下==运算符,分析一下语句执行结果:
        int num1 = 10;
        int num2 = 10;
        System.out.println(num1 == num2);  //打印true
        String str1 = new String("123");
        String str2 = new String("123");
        System.out.println(str1 == str2);  //打印false
可以看出,由于基本类型不是对象,即不存在引用,所以==运算符只会比较两个基本类型的值,但当==作用于对象时,我们可以通过第二条打印语句发现,虽然str1与str2都是引用相同的值"123",但是这两个对象分别是引用两个不同内存地址的值,即引用不相同,因此str1 == str2返回false,==作用于对象时,比较的是两个对象的引用是否相同!
==运算符进行等值比较。也就是说,对于任何引用值X和Y,当且仅当X和Y指向同一对象时, X==Y返回真。
Java.lang包中的Object类有public boolean equals(Object obj)方法。它也比较两个对象是否相等。仅当被比较的两个引用指向同一对象时,对象的equals()方法返回true。
Object类的equals()方法很少被使用,因为,多数情况下我们希望比较两个对象的内容,而不是判断两个引用是否指向同一对象。
String类中的覆盖equals()方法返回true,当且仅当参数是一个不为null 的String对象,该对象与调用该方法的String对象具有相同的字符顺序。例如:
        String s1 = new String("JDK1.2");
        String s2 = new String("JDK1.2");
方法s1.equals(s2)返回真,尽管s1和s2指向两个不同的对象。
下面的例子使用equals方法测试雇员的名字和生日:
public class Employee {
    private String name;

    // private Mydate birthDate;
    private float salary;

    public Employee(String name, float salary) {
        this.name = name;
        // this.birthDate = Dob;
        this.salary = salary;
    }

    public boolean equals(Object o) {
        boolean result = false;
        if ((o != null) && (o instanceof Employee)) {
            Employee e = (Employee) o;
            if (name.equals(e.name)) {
                result = true;
            }
        }
        return result;
    }

    public int hashCode() {
        return (name.hashCode());
    }
}

我们覆盖了hashCode方法。这样做保证了相同的雇员对象有相同的hashCode。

下面的程序判断两个雇员对象引用是否相同:
public class TestEquals {
    public static void main(String[ ] args){
        Employee emp1 = new Employee("Fred Smith", 25000.0F);
        Employeeemp2 = new Employee("Fred Smith", 25000.0F);
if(emp1 == emp2){
            System.out.println("emp1 is identical to emp2")'
        }else{
            System.out.println("emp1 is not identical to emp2")'
        }

        if(emp1.equals(emp2) ){
            System.out.println("emp1 is equals to emp2")'
        }else{
            System.out.println("emp1 is not equals to emp2")'
        }

        emp2 = emp1;
        System.out.println("set emp2 = emp1");
        if(emp1 == emp2){
            System.out.println("emp1 is identical to emp2")'
        }else{
            System.out.println("emp1 is not identical to emp2")'
        }
    }
}
执行结果为:
        emp1 is not identical to emp2
        emp1 is equals to emp2
        set emp2 = emp1
        emp1 is identical to emp2

toString( )方法
toString方法被用来将一个对象转换成String表达式。当自动字符串转换发生时,它被用作编译程序的参照。例如:
        Date now = new Date()
        System.out.println(now)
        将被翻译成:
        System.out.println(now.toString());
对象类定义缺省的toString()方法,它返回类名称和它的引用的地址(通常情况下不是很有用)。许多类覆盖toString()以提供更有用的信息。例如,所有的包装类覆盖toString()以提供它们所代表的值的字符串格式。甚至没有字符串格式的类为了调试目的常常实现toString()来返回对象状态信息。
覆盖toString()方法之前:
class A {
    public static void main(String[] args) {
        System.out.println(new A());
    }
}
执行结果是完整类名称+ @ +引用地址!(垃圾数字)
覆盖toString()方法之后:
class A {
    public String toString() {
        return "This is object of Class A";
    }

    public static void main(String[] args) {
        System.out.println(new A());
    }
}
执行结果变为:"This is object of Class A"

lampeter123 发表于 2010-7-20 08:15


static关键字
static关键字用来声明成员属于类,而不是属于类的对象。
1. static (类)变量
类变量可以被类的所有对象共享,以便与不共享的成员变量区分开来。        
2. static (类)方法
静态方法可以通过类名直接调用该方法,而不用通过对象调用。
例如:
class PersonCount {
    private int personID;

    private static int num = 0;

    public PersonCount() {
        num++;
        personID = num;
    }

    public static String getPersonDes() {
        return "this is a policeman";
    }
}

class TestPersonCount {
    public static void main(String[] args) {
        // 直接用类名来访问该静态方法,而不需要该类的对象
        String s = PersonCount.getPersonDes();
        System.out.println(s);
    }
}

main()是静态的,因为它必须在任何实例化发生前被顺序地访问,以便应用程序的运行。
静态方法不能被覆盖成非静态。同样,非静态方法也不能被覆盖成静态方法。
        
3 "单态 "设计模式
单态 设计模式,也就是说一个类只产生一个对象。那么怎么才能做到这一点呢?我们知道构造器是用来构造对象的。首先要对构造器入手。既然只产生一个对象,那么我们就干脆先一刀砍断,把构造器的访问权限定义成私有,不能在类的外面再构造该类的对象。也就是说只能在类的里面调用该类的构造器来产生对象。那么在该类里应该定义一个静态的属性,并初始化该类的一个对象。(原因是静态的东西只执行一次,也就是说该属性只初始化一次。那么每次得到的应该是同一个实例)
class TestSC {
    public static void main(String[] args) {
        SingleClass sc1 = SingleClass.sc;
        SingleClass sc2 = SingleClass.sc;
        sc1.test();
        sc2.test();
    }
}

class SingleClass {
    int i = 0;

    static SingleClass sc = new SingleClass();

    private SingleClass() {
    }

    public void test() {
        System.out.println("hello " + (++i));
    }
}

运行的结果为:
                hello 1
                hello 2
说明是同一个实例。
在类的设计的时候,我们也应该遵守封装的要求。把属性定义成私有的。再定义一个共有的方法来去到该属性的值。
改后的代码:
class TestSC {
    public static void main(String[] args) {
        SingleClass sc1 = SingleClass.getSingleClass();
        SingleClass sc2 = SingleClass.getSingleClass();
        sc1.test();
        sc2.test();
    }
}

// 型态1:此时,还是可以直接调用类静态变量,得到类对象,还不完全达到我们的设定
class SingleClass {
    int i = 0;

    private static SingleClass sc = new SingleClass();

    private SingleClass() {
    }

    /*
     * 因为在类的外面不能来构造给类的实例了, 所有该方法定义成静态的,通过类名直接可以调用。
     */
    public static SingleClass getSingleClass() {
        return sc;
    }

    public void test() {
        System.out.println("hello " + (++i));
    }
}

// 型态2:此时,只能调用类静态方法,得到类对象
class SingleClass {
    int i = 0;

    private static SingleClass sc = null;

    private SingleClass() {
    }

    /*
     * 因为在类的外面不能来构造给类的实例了, 所有该方法定义成静态的,通过类名直接可以调用。
     */
    public static SingleClass getSingleClass() {
        if (sc == null) {
            sc = new SingleClass();
        }

        return sc;
    }

    public void test() {
        System.out.println("hello " + (++i));
    }
}

final关键字
1 、final类
Java编程语言允许关键字final修饰类。如果这样做了,类便不能被继承。比如,类Java.lang.String就是一个final类。这样做是出于安全原因,因为它保证,如果方法有字符串的引用,它肯定就是类String的字符串,而不是某个其它类的字符串。
2 、final方法
方法也可以被标记为final。被标记为final的方法不能被覆盖。这是由于安全原因。如果方法具有不能被改变的实现,而且对于对象的一致状态是关键的,那么就要使方法成为final。
被声明为final的方法有时被用于优化。编译器能产生直接对方法调用的代码,而不是通常的涉及运行时查找的虚拟方法调用。
被标记为static或private的方法被自动地final。
3 、final变量
如果变量被标记为final,其结果是使它成为常数。想改变final变量的值会导致一个编译错误。下面是一个正确定义final变量的例子:
            public final int PI = 3.14;   
   
扩展知识:
内部类
    内部类,有时叫做嵌套类。内部类允许一个类定义被放到另一个类定义里。内部类是一个有用的特征,因为它们允许将逻辑上同属性的类组合到一起,并在另一个类中控制一个类的可视性。内部类可以访问外部类的属性和方法。你可以把内部类看作"方法"一样,在使用的时候调用执行。你也可以把内部类看作"属性"一样,在构造内部类对象的时候,也会在堆里为内部类的属性分配存储空间。所以内部类也有类似像修饰属性,方法那样的修饰符,比如:public,private,static 等等。当一个类没有用static  关键字修饰的时候,这个内部类就叫做成员类,类似属性,方法,作为类的成员。

成员内部类:
public class OC1 {
    private int size;
   
    public class IC {
        public void addSize() {            
            size++;
        }
    }

    public void testTheIC() {
        IC i = OC1.new IC();
        i.addSize();
    }
}
    内部对象拥有一个外部对象的引用:
[attach]51984[/attach]
例2:
这个例子阐述了如何在其它类(外部类的外部)实例化内部类:
class OC2 {
    private int size;

    public class IC {
        public void addSize() {
            size++;
        }
    }
}

public class TestIC // Test Inner Class
{
    public static void main(String[] args) {
        OC2 outer = new OC2();

        // 因为是成员内部类,所以必须用外部类的对象来构造内部类的对象,类似调用方法一样。
        OC2.IC inner = outer.new IC();
        inner.addSize();
    }
}

内部类要在外部类实例的上下文中实例化:
[attach]51985[/attach]

例3
this的一个作用是调用本类的其它构造器,另外一个作用就是做隐含参数的调用,代表当前的实例。完整的写法应该是 该类的类名.this 如下例:
本例阐述如何区分同名变量:
public class OC3 {
    private int size;

    public class IC // Inner Class
    {
        private int size;

        public void addSize(int size) {
            // 方法里的临时变量,当方法执行完自动消失
            size++;
            this.size++;
            // 代表本类的当前对象,全称是IC.this.size++;
            OC3.this.size++;
        }
    }
}
例4
静态内部类(也叫顶层类):
class OC4 {
    private static int size;

    // 声明一个内部类 叫 "IC"
    public static class SIC // Static Inner Class
    {
        public void addSize() {
            // 访问外部类的属性
            size++;
        }
    }

}

public class TestSIC // Test Static Inner Class
{
    public static void main(String[] args) {
        // 因为内部类是静态内部类,所以直接可以构造内部类的一个对象,与调用静态方法类似
        OC4.SIC inner = new OC4.SIC();
        inner.addSize();
    }
}

例5
方法内部定义内部类:
class OC5 {
    // 内部类访问,应该定义成final
    public Object makeObject(final int i) {
        class MIC // Methord Inner Class
        {
            int k = i;

            public String toString() {
                return ("属性k :" + k);
            }
        }
        return new MIC();
    }

    public static void main(String[] args) {
        OC5 oc = new OC5();
        Object o = oc.makeObject(5);
        System.out.println(o);
    }
}

注意:在方法中定义的内部类的方法,不能访问外方法的运行时变量空间(line 10),可以访问外方法的非运行时变量空间(line 11)。
例6
匿名内部类:有时候定义一个类,并不需要提供名字。所以叫匿名类。
class OC6 {
    // 多态,传递的参数应该是实现该接口的任何类产生的对象
    public void testFly(Fly f) {
        f.fly();
    }

    public static void main(String[] args) {

        OC6 oc = new OC6();
        // 画下划线的代码就是构造了实现Fly接口的某个类的对象,类名并不需要知道,只
        // 知道该对象具有接口的功能就行。
        oc.testFly(new Fly() {
            public void fly() {
                System.out.println("fly higher and higher");
            }
        });
    }
}

interface Fly {
    public void fly();
}

匿名类具有广泛性。不只是对接口才有,对抽象类或者具体的类都适用。例如:
class OC7 {
    public static void main(String[] args) {
        System.out.println(new Person() {
            public String toString() {
                return "this is a person";
            }
        });
    }
}

class Person {
}
注意:如果是接口或者抽象类的话,在匿名类里面必须实现接口或者抽象类里所有的抽象方法。如果是具体的类的话就没必要了,需要的话可以覆盖方法,不需要时可以不写任何代码。看下例:
class OC7 // Outer Class 7
{
    public static void main(String[] args) {
        // 这里其实是Person类里的一个子类,只不过该子类并没有扩展功能
        System.out.println(new Person() {
        });
    }
}

class Person {
    public String toString() {
        return "this is a person";
    }
}
内部类有如下属性:
    1 内部类只在定义他们的代码段内可见。
    2 内部类可以被定义在方法中。如果方法中的变量被标记为final,那么,就可以被内部类中的方法访问。
    3 内部类可以使用所嵌套类的类和实例变量以及所嵌套的块中的本地变量。
    4 内部类可以被定义为abstract.
    5 只有内部类可以被声明为private或protected,以便防护它们不受来自外部类的访问。
    6 一个内部类可以作为一个接口,由另一个内部类实现。
    7 被声明为static的内部类自动地成为顶层类。这些内部类失去了在本地范围和其它内部类中使用数据或变量的能力。
    8 内部类不能声明任何static成员;只有顶层类可以声明static成员。因此,一个需求static成员的内部类必须使用来自顶层类的成员。

lampeter123 发表于 2010-7-20 08:20

抽象类,接口
抽象类    Java语言允许你在一个类中声明一些方法,然而又不实现它。这种方法叫抽象方法。包含一个或多个抽象方法的类叫做抽象类。你可以在抽象类的子类中实现抽象方法。抽象类只能被其它类继承,不能用来创建实例。
如图设计抽象类:
[attach]51986[/attach]

计算fuel调用了Vehicle类的两个抽象方法,Vehicle类的抽象方法在它的子类中实现。这就是"模板方法"
接口
接口是用关键字interface来定义的,接口是客户端代码与提供服务的类之间的"规约"。接口是抽象类的变体。接口中的所有方法都是抽象的,没有一个有程序体。接口只可以定义static final成员变量。
接口的使用,弥补了Java技术单继承规则的不足。一个类可以实现多个接口。接口的实现与子类相似,当类实现某个接口时,它必须定义这个接口的所有方法。
例如:一些对象都具有飞行的能力,可以定义一个Flyer接口,它支持三种操作:
[attach]51987[/attach]

takeoff() 、land()、fly()。类Airplane可以实现Flyer。
interface Flyer {
    public void takeoff();
    public void land();
    public void fly();
}
public class Airplane implents Flyer{
    public void takeoff(){
        // accelerate until lift-off
        // raise landing gear
    }
    public void land(){
        // lower landing gear
        // decelerate . . .
    }
    public void fly(){
        // keep those engines running
    }
}
Java允许多个类实现同一个接口:
[attach]51988[/attach]
飞机是一种交通工具,并且能够飞行。鸟是一种动物,也能够飞行。这些例子说明继承与实现接口可以混合使用。
[attach]51989[/attach]
一个类可以实现多个接口:
[attach]51990[/attach]

类能实现许多接口。由类实现的接口出现在类声明的末尾以逗号分隔的列表中,如下所示:
     public class MyApplet extends Applet implements Runnable, MouseListener{ }

什么时候使用接口
    对于下述情况,应该使用接口:
    声明方法,期望一个或更多的类来实现该方法。
    揭示一个对象的编程接口,而不揭示类的实际程序体。(当将类的一个包输送到其它开发程序中时它是非常有用的。)
    捕获无关类之间的相似性,而不强迫类关系。
    描述"似函数"对象,它可以作为参数被传递到在其它对象上调用的方法中。它们是"函数指针"(用在C和C++中)用法的一个安全的替代用法.   

    结果:
            public abstract class Vehicle{
                public abstract double calcFuelEfficiency();
                public abstract double calcTripDistance();
            }
编写代码
public class Truck extends Vehicle {
    public Truck(double max_load) {
    }

    public double calcFuelEfficiecy() {
        // calculate
    }

    public double calcTripDistance() {
        // calculate
    }
}

public class RiverBarge extends Vehicle {
    public RiverBarge(double max_load) {
    }

    public double calcFuelEfficiecy() {
        // calculate
    }

    public double calcTripDistance() {
        // calculate
    }
}

lampeter123 发表于 2010-7-20 08:24

异常

异常的概念
在Java编程语言中,异常类定义程序中可能遇到的轻微的错误条件。你可以写代码来处理异常,并继续执行程序,而不需要让程序中止。
在程序执行中,任何中断正常程序流程的条件都是异常。例如,发生下列情况时,会出现异常:
    想打开的文件不存在
    网络连接中断
    操作数超出预定范围
    正在 装载的类文件丢失
在Java编程语言中,错误类定义被认为是不能恢复的严重错误条件。在大多数情况下,当遇到这样的错误时,建议让程序中断。
在 程序中发生错误时,发现错误的方法抛 出一个异常到其调用程序,给 出已经发生问题的信号。然后,调用方法捕获抛出的异常,在可能时,再恢复回来。这个方案给程序员一个写处理程序的选择,来处理异常。
通过浏览API,可以决定方法抛出的是什么样的异常。
异常的分类
在Java编程语言中java .lang.Throwable类充当所有对象的父类,可以使用异常处理机制将这些对象抛出并捕获。在Throwable类中定义方法来检索与异常相关的错误信息,并打印显示异常发生的栈跟踪信息。它有Error和Exception两个基本子类。
Throwable类不能直接 使用,我们 使用其 子类来捕获和描述异常信息。
异常结构如图:
[attach]51991[/attach]
Error表示严重的错误问题 。比如说内存溢出。不可能指望程序能处理这样的情况。
Exception 则是我们关心和需要处理的错误。
RuntimeException表示一种设计或实现问题。也就是说,它表示如果程序运行正常,从不会发生的情况。比如,如果数组索引扩展不超出数组界限,那么,ArrayIndexOutOfBoundsException异常从不会抛出。比如,这也适用于取消引用一个空值对象变量。因为一个正确设计和实现的程序从不出现这种异常,通常对它不做处理。这会导致一个运行时信息,应确保能采取措施更正问题,而不是将它藏到谁也不注意的地方。
其它异常表示一种运行时的困难,它通常由环境效果引起,可以进行处理。例子包括文件未找到或无效URL异常(用户打了一个错误的URL),如果用户误打了什么东西,两者都容易出现。这两者都可能因为用户错误而出现,这就鼓励程序员去处理它们。
预定义 异常
Java编程语言提供几种预定义的异常。下面是可能遇到的更具共同性的异常中的几种:
    ArithmeticException:整数被0除,运算得出的结果。
        int i = 12 / 0;
    NullPointerException:当对象没被实例化时,访问对象的属性或方法的尝试:
        Date d = null;
        System.out.println(d.toString());
    NegativeArraySizeException:创建带负维数大小的数组的尝试。
    ArrayIndexoutofBoundsException:访问超过数组大小范围的一个元素的尝试。
    SecurityException:典型地被抛出到浏览器中,SecurityManager类将抛出applets的一个异常,该异常企图做下述工作(除非明显地得到允许):
    访问一个本地文件
打开主机的一个socket,这个主机与服务于applet的主机不是同一个。
    在运行时环境中执行另一个程序
异常的处理机制
一般来说,异常的处理机制有以下三种:
try-catch-finally  主动异常处理
throws 消极异常处理
throw 引入异常
实例分析
例1
问题的描述:
    写一个常见的异常
解决方案:
请看下例:
public class TestException {
    public static void main(String args[]) {
        int i = 0;

        String ss[] = { "Hello world!", "您好,世界!", "HELLO WORLD!!" };

        for (; i < 6; i++) {
            System.out.println(ss[i]);
        }
    }
}
运行的结果为如图。
[attach]51992[/attach]

要处理异常,将能够抛出异常的代码放入try块中,然后创建相应的catch块的列表,每个可能被抛出异常都有一个。如果生成的异常与catch中提到的相匹配,那么catch条件的块语句就被执行。在try块之后,可能有许多catch块,每一个都处理不同的异常。
请看下例:
class TestException2 {
    public static void main(String args[]) {
        int i = 0;

        String ss[] = { "Hello world!", "您好,世界!", "HELLO WORLD!!" };

        for (; i < 5; i++) {
            try {
                System.out.println("第" + (i + 1) + "次循环:");
                System.out.println(ss[i]);
            } catch (Exception e) {
                System.out.println("数组越界");
            } finally {
                System.out.println("finally execute");
            }
        }
    }
}
运行的结果如图
[attach]51993[/attach]
总结:从上例可以看出:不管出现不出现异常,finally语句块都会执行。在try语句块里除了System.exit(int)语句外,finally语句块必须执行。
Throws则是自己不处理异常,而交给上级处理,俗称“异常上抛”,比如:
public static void main(String args) throws RuntimeException
这样,一旦在代码里出现RuntimeException,在本类里不做任何处理动作,而是交由上次程序或者虚拟机去处理。
自定义异常
用户定义异常是通过扩展Exception类来创建的。这种异常类可以包含一个"普通"类所包含的任何东西。下面就是一个用户定义异常类例子,它包含一个构造函数、几个变量以及方法
请看下例:
class TestMyException {
    public static void main(String[] args) {
        ABC abc = new ABD();// ABD 是 ABC的子类
        try {
            abc.a(5);
        } catch (EA e) {
            e.test();
        }
    }
}
创建自己的运行时异常,如果改成Exception,就是创建编译时的异常了,编译的时候就应该对异常处理。
class EA extends RuntimeException {
    String s;

    public EA(String s) {
        this.s = s;
    }

    public EA() {
    }

    public void test() {
        System.out.println(s);
    }
};

class EA1 extends EA // 创建自己的异常的子异常
{
    public EA1(String s) {
        super(s);
    }
};

class EA2 extends EA // 创建自己的异常的子异常
{
    public EA2(String s) {
        super(s);
    }
};

class ABC {
    public void a(int i) {
        if (i < 0)
            System.out.println("normal");
        else
            throw new EA("no normal");
    }
};

class ABD extends ABC {
    public void a(int i) {
        if (i < 0)
            System.out.println("dfsdafds");
        else if (i == 0)
            throw new EA1("参数 == 0");
        else
            throw new EA2("参数 > 0");
    }
};
运行的结果为:
     参数 > 0
方法覆盖和异常
方法覆盖要注意两点:
n    访问权限不能比父类的弱
n    抛出的异常不能比父类的多。(注意多并不是数量上的多,而是父类方法抛出的异常必须包含子类覆盖方法抛出的异常)
请看下例:
class TestMyException2 {
    public static void main(String[] args) {
        ABC abc = new ABD();
        try {
            abc.a(5);
        } catch (EA e) {
            e.test();
        }
    }
}

class EA extends Exception // 创建自己的编译时异常
{
    String s;

    public EA(String s) {
        this.s = s;
    }

    public EA() {
    }

    public void test() {
        System.out.println(s);
    }
};

class EA1 extends EA // 创建自己的异常的子异常
{
    public EA1(String s) {
        super(s);
    }
};

class EA2 extends EA // 创建自己的异常的子异常
{
    public EA2(String s) {
        super(s);
    }
};

class ABC {
    public void a(int i) throws EA {
        if (i < 0)
            System.out.println("normal");
        else
            throw new EA("no normal");
    }
};

class ABD extends ABC {
    public void a(int i) throws EA1, EA2 // 虽然抛出的异常比父类多,但是都是父类异常的子类
    {
        if (i < 0)
            System.out.println("dfsdafds");
        else if (i == 0)
            throw new EA1("参数 == 0");
        else
            throw new EA2("参数 > 0");
    }
};

运行结果为:
    参数 > 0


页: [1] 2

编程论坛