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"
编写程序
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
运行JAVA程序
在控制台使用如下命令运行刚才生成的文件Welcome.class
[ 本帖最后由 lampeter123 于 2010-7-20 10:18 编辑 ]
----------------解决方案--------------------------------------------------------
Eclipse入门
1 开始使用Eclipse
假设你已经将Eclipse安装在目录c:\eclipse下。要启动Eclipse
双击后出现了工作区装载窗口
输入你的工作区所在的目录,本例所有项目、程序都存放在c:\smith目录下,故输入c:\smith,然后点击OK,Eclipse的图形界面就展现在你的眼前了。
如果你的工作区已经有项目存在,则项目就会显示在图形界面上。工作区实际上是一个存放项目文件的目录。
2.创建一个项目
要创建一个新项目,需要选择菜单项File->New->Project来启动新项目创建向导。
选择Java Project然后点击按钮Next进入下一步
3.创建程序
项目创建完毕后就可以在这个项目中创建程序了,选择菜单项File->New->Class来启动Java类创建向导
在Name后面的文本框中输入Welcome,检查是否选中了选项public void main(String[] args)。点击按钮Finish从Java Application的模板创建一个文本Welcome.java
在程序Welcome.java的main方法中输入语句System.out.println(“Welcome to Java”);。
4.编译和运行程序
一般情况下,源代码在你键入的时候就会自动进行编译。比如,如果你忘记在语句结束的时候键入分号(;),那么你就会看到一条红色的波浪线来指出你的错误。
在运行程序前要保证程序是正确并被编译通过了,换句话说就是程序中没有红色的波浪线了。
要运行程序Welcome.java,右键点击Package视图中的该程序,在弹出的菜单中选择Run->JavaApplication,
----------------解决方案--------------------------------------------------------
面向对象概述
面向对象(Object Oriented)
面向对象是一个广泛使用但涵义并不清晰的术语。上世纪80年代面向对象主要指一种程序设计方法,但在随后的发展中,面向对象的方法已经渗透到计算机软件领域的许多分支。在了解面向对象之前我们必须得了解两个重要概念:对象和类。
什么是对象?
首先,我们来了解一下什么是对象。对象的概念最早出现于五十年代人工智能的早期著作中,在现实世界中,我们可以将对象定义为:
对象(Object):一个具有明确行为的有形实体,具有行为和状态,有着清晰的边界。
以面向对象的眼光来看待现实世界,现实世界中的每一个具体事物,我们都会将其看作一个对象,比如狗、电视机、自行车等,都可以是对象。狗有特定的名称、毛色、品种、会饿等特点,还有狗叫、尾巴左右摇摆等动作;同样的,自行车也有品牌、重量等特点,同时具备换速、刹车等动作;依此类推,我们不难发现,现实中的每一个对象都会有自己的特点以及相关动作,我们将对象所有特点(属性)称之为对象的状态,而动作称之为行为。
属性(Attribute):对象的一种内在的或独特的性质、特点、质量或特征,它构成了一个独一无二的对象(摘自<<Objects-Oriented Analysis and Design with Applications>>)
每一个学生具备名称、性别、年龄、身高、长相、爱好等属性,通过这些属性我们可以很快地区分所有的学生对象,因为这些属性构成了每一个独一无二的学生对象。比如说,在大部分情况下,我们会通过学生名称来区分每一个学生对象。我们经常会说:某某人很高,某某人很年轻等,高和年轻实际上就表现出某一个对象的内在的特点。因此,可以这样理解,我们是通过每一个对象特有的属性来区分各个对象。
注意:属性都有其对应的值,而且某些属性值是可以动态改变的,比如学生的体重、爱好是可以改变的,而DNA、指纹却是不能改变的。
随着属性值的改变,状态也随之改变了。比如,学生甲的身高值逐渐增长了,那学生甲的状态也发生了变化----长高了!
状态的具体定义如下:
状态(State):包含对象的所有属性以及属性值,会随着属性值的改变而改变。
没有一个对象是孤立存在的,对象之间是相互作用的。行为就是指这种作用。
行为(Behavior):是一个对象根据它的状态改变和消息传送所采取的行动和做出的反应,代表了对象对外的可见、可测试的动作。(摘自<<Objects-Oriented Analysis and Design with Applications>>)
行为和状态是相互的。Dog处于饿的状态时,就会采取进食等行为,进食完毕后,Dog又会处于饱的状态。现实中,大多对象是通过接受另一个对象传递的一个消息(操作)来改变自身的状态。比如:驾驶员扭动汽车启动钥匙至关闭位置,此时,汽车关闭引擎。其中,驾驶员、汽车都是对象,而关闭引擎则是一个特定消息,是由驾驶员传递给汽车的消息,汽车根据这个消息改变状态---从启动状态变为关闭状态。因此,消息实际上就是一个对象对另一个对象施加的操作。
----------------解决方案--------------------------------------------------------
什么是类?
在现实世界中,我们会将所有脊椎动物对象进行分类:鸟类、两栖爬行类、哺乳类,比如我们将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)
在前面,我们已知道通过抽象创建出了类,类就是抽象的一种表示。抽象是解决问题复杂性的一种基本方法,抽象会重点强调我们应该注意什么,而不应该注意什么!通过抽象我们可以着重实现需关注的细节而忽略一些非实质性或无关紧要的细节,从而能够更好的分析问题解决问题!
抽象:表示一个对象与其它所有对象区别的基本特征,为不同的观察者提供不同的角度来观察对象。
对同一个对象---电视机,维修员只会关注电视机的内部结构:线路是否断裂、显像管是否损坏等,而购买电视的客户却只会关心电视机的画面是否逼真、外观是否好看等等,客户不会也不用关心电视机的内部结构。可以看出,维修员只关心电视机的内部,购买用户只关心电视机的外观及实用性,他们观察电视机的角度不一致,这就是抽象的实现,在现实生活中,到处都存在着抽象!
抽象强调实体的本质、内在的属性,在软件开发中,抽象指的是在决定如何实现对象之前对对象的意义和行为的定义----即类的实现!通过对类的了解,我们也可以将抽象定义为:从许多事物中舍弃个别的、非本质的特征,抽取共同的、本质性的特征。
为什么需要抽象?
首先,问题的复杂性日益增长,其中描述的事物也越来越复杂,如果分析员去了解每一个事物的所有方面,那将是一个穷举耗时、低效率的工作
其次,如果没有识别所有对象的共同特征的行为,那么对这些对象的分类将无法进行!
对象分类是面向对象中非常重要的环节,而抽象则是实现该环节的必须解决方案!抽象的本质就是将问题分解,在面向对象中,所有对象被抽象成各种类,从而让程序员集中关注于功能的实现!
----------------解决方案--------------------------------------------------------
封装(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)则通过四肢来爬行。
虽然,在现实中我们可以通过给这两个对象取不同名字用以区分,但上面的例子向我们演示了这样一个过程:
一个名字可以表示许多不同类(这些不同类必须拥有一个共同的超类)的对象,从而实现以不同的方式来响应某个共同的操作集。
以上就是多态的定义,多态只有通过继承才能实现!在后面章节我们会详细掌握多态以及其运用。
以上就是面向对象的一些主要特征,具备了这样特征的程序语言我们就可以称之为面向对象语言。
[ 本帖最后由 lampeter123 于 2010-7-19 17:05 编辑 ]
----------------解决方案--------------------------------------------------------
面向对象的程序设计
类和对象的描述
现实社会中一切皆对象。比如:人,树,石头,猫,鸟,汽车,地球等等。任何一种对象都具有静态的属性,但不一定具有动态的行为。比如:石头。一般情况下,对象既有静态的属性,也有动态的行为。对象本身的属性和行为之间可以相互影响,比如:一个人饿了(属性),就会去吃饭(行为)。相反,这个人吃饭(行为)后,就饱了(属性),体重(属性)也增加了。不同的对象之间也可以相互作用。比如:人看到汽车开过来了,就会沿着路边走。如果这个人站在路中间不动(他不怕死),那么汽车就会停下来。那么怎么用Java语言来实现上述功能呢?后面实例分析有实现。
如同建筑设计师设计建筑图(建筑的蓝图),可以用该图来盖出许许多多这种风格的房子一样。类是对象的蓝图,是用来描述对象的,你可以用该类,来实例化许许多多个该类型的对象,类就是对象的模板。在类中定义了一套数据元素(属性)和一套行为(方法)。数据是用来描述具体的一个对象(静态),行为是用来描述该类型对象的共性,也就是该对象能够做什么(动态),以及完成相关对象之间的交互,从而改变对象的状态。同样对象的状态也能够对象的行为。属性和方法都叫做类的成员。例如杯子装水的时候:最大盛水量和当前盛水量。盛水的方法要始终跟踪这两个属性。 装水时改变了当前盛水量的属性,同样当当前盛水量等于最大盛水量(水装满时),就会影响装水的行为,将不再倒水。
我们下面通过一个具体事例来说明Java中如何实现以上概念。
例1:
编写一个类,描述人吃饭,体重增加这个简单操作。
下图描述了一个“人”
这是一个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());
}
}
编译和运行的过程如下图:
----------------解决方案--------------------------------------------------------
例2
我们知道如果文件名出现重复,那么我们要放在不同的目录中,class文件作为类的字节码载体,如果存在类名重复,那么class文件就应该放到不同的目录下,就类似文件夹的方式来管理文件。在Java里面是通过包的结构来管理类的。下面我们就上面例子修改下,再加个类,定义不同的包,放在不同目录下进行访问。
源文件的布局
Java技术源文件只有三部分,采用下面的布局:
[<package_declaration>]
<import_declaration>
<class_declaration>
说明:
<package_declaration> 声明包的语句,包通常使用小写字母,用.作为分割符,这是一种逻辑结构划分的方法。
<import_declaration> 导入包语句
<class_declaration> 类的声明语句
源文件命名
源文件的名字必须与该文件中声明的公有类的名字相同。一个源文件中可以包含多个类,但是最多只能包含一个公有类,显然,这个文件的名字应该是这个public类的名字后缀是“java”。如果源文件中不含公有类,源文件的名字不受限制。
包的声明
多数软件系统是庞大的。为了方便管理,通常要将功能相似的类组织成包,通过包来管理类文件。在包中可以存放类,也可以存放子包,从而形成具有层次结构的包。包可以根据需要任意组织,通常,要按照类的用途、含义来组织包。如下UML 包图:
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目录中,如下图:
为了达到以上目的,我们手工开始工作,虽然将来有集成开发环境(IDE)帮助我们,但是这对我们明白包的含义很有帮助。
1、手动建立文件夹。把包的结构创建出来
我们分别使用三个包,在一个我们选定的工作目录下按照上图结构创建好目录,其中build目录下的结构无需创建(但是build自身要创建),src目录中存放源文件,他们分别按照自己的package声明存放到不同目录。
2、自动把包的目录结构生成出来。
进入工作目录(src和build的上级目录),执行以下命令:
就自动把类按包的结构生成到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......");
}
}
运行时情况如下:
当然我们也可以在build目录下,利用CLASSPATH环境变量已经带有的.(当前路径)直接找到要运行的类(shenzhen.luohu.PersonApp),我们也可以利用绝对路径形式制定CLASSPATH,例如:
而且绝对路径相对路径的概念在编译、运行的时刻同样有效。
对于CLASSPATH我们可以通过环境变量方式设置,例如希望从系统级别设置类路径,也可以通过-CLASSPATH参数形式设置,例如希望临时更改类路径。
----------------解决方案--------------------------------------------------------
例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());// 测试人是否能行走,也就是说车的行为,影响了人的行为
}
}
编译和运行:
----------------解决方案--------------------------------------------------------
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
可见,方法只能改变引用类型的值,而不能改变引用类型的地址和基本类型的值。
----------------解决方案--------------------------------------------------------
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);
----------------解决方案--------------------------------------------------------