前言
本章知识较前几章难度有所提升,需注意。本部分总结了本章前四个小节,包括编程语言的发展阶段、类、对象和参数。
基本知识
一、面向对象的编程
面向对象的编程主要体现下列 3 个特点:
1.封装性:将数据和对数据的操作封装在一起(面向对象编程的核心思想);
2.继承:子类可以继承父类的属性和功能,即既继承了父类所具有的数据和数据上的操作,同时又可以增添子类独有的数据和数据上的操作;
3.多态:两种意义的多态,
(1)操作名称的多态:有多个操作具有相同的名字,但这些操作所接收的消息类型必须不同。
(2)和继承有关的多态:指同一个操作被不同类型的对象调用时可能产生不同的行为。
类是用来创建对象的模板。要学习 Java 编程就必须学会怎样去写类,即怎样用 Java 的语法去描述一类事物共有的属性和功能。
二、类
类是组成 Java 程序的基本要素。类封装了一类对象的状态和方法。类是用来定义对象的模板。
类的实现包括类声明和类体两部分。基本格式如下:
class 类名
{
类体的内容
}
class 是用于定义类的关键字。“class 类名”是类的声明部分,类名必须是合法的标识符。
1.类声明
习惯上类名的第一个字母大写,但这不是必须的。命名时最好遵守以下原则:
(1)如果类名使用拉丁字母,那么名字的首字母使用大写字母,如 Hello、Time、People等。
(2)类名最好容易识别,见名知义。当类名由几个“单词”复合而成时,每个单词的首字母必须大写,如 BeijingTime、AmericanGame、HelloChina。
2.类体
类体内容由两部分构成:
(1)对变量的定义,用于刻画属性;
(2)对方法的定义,用于刻画功能。
class 梯形
{ float 上底,下底,高,laderArea; \\变量定义部分
float 计算面积() \\方法定义
{ laderArea=(上底+下底)*高/2.0f;
return laderArea;
}
void 修改高(float h) \\方法定义
{ 高=h;
}
}
3.成员变量和局部变量
类体两部分中,
①变量定义部分所定义变量被称为类的成员变量;
②方法定义部分所定义变量和方法的参数被称为局部变量。
(1)变量的类型
成员变量和局部变量的类型都可以是 Java 中的任何一种数据,包括基本类型(整形、浮点型、字符型)、引用类型(数组、对象)。
举例:
class People
{ int boy;
float a[];
void f()
{ boolean cool;
Workman zhangboy; \\cool和zhangboy是局部变量,cool是boolean型,zhangboy是类workman声明的变量,即对象
}
}
class Workman
{ double x;
}
(2)变量的有效范围
成员变量在整个类中都有效,局部变量只在定义它的方法中有效。如:
class A
{ void f()
{ int m=10,sum=0; \\成员变量,在整个类中有效
if(m>9)
{ int z=10; \\z仅在该复合语句中有效
z=2*m+z;
}
for(int i=0;i<m;i++)
{
sum=sum+i; \\i仅在该循环语句中有效
}
m=sum; \\合法,因为m和sum有效
z=i+sum; \\非法,因为i和z无效
}
}
成员变量的有效性与它在类体中的位置无关。如以上梯形类也可以写成如下形式:
class 梯形
{ float 上底,landerArea; \\成员变量的定义
float 计算面积()
{ laderArea=(上底+下底)*高/2.0f;
return laderArea;
}
float 下底; \\成员变量的定义
void 修改高(float h) \\方法定义
{ 高=h;
}
float 高; \\成员变量的定义
}
但不提倡把成员变量的定义分散地写在方法之间或类体的最后。
(3)实例变量和类变量
类变量也称作 static 变量。成员变量也分为实例变量和类变量。
如:
class Dog
{ float x;
static int y; \\若成员变量的类型前加上关键字static,就被称为类变量或静态成员变量
}
该段代码中,x 是实例变量,y 是类变量。
(4)成员变量的隐藏
若局部变量的名字与成员变量的名字相同,则成员变量将被隐藏,即此成员变量在此方法中暂时失效。如:
class Tom
{ int x=98,y;
void f()
{ int x=3;
y=x; \\y得到的值是3,不是98.如果方法f中没有"int x=3;"语句,y的值将是98
}
}
若想在该方法中使用被隐藏的成员变量,必须使用关键字 this,如:
class 三角形
{ float sideA,sideB,sideC,lengthSum;
void setSide(float sideA,float sideB,float sideC)
{ this.sideA=sideA;
this.sideB=sideB;
this.sideC=sideC;
}
}
\\this.sideA、this.sideB、this.sideC分别表示成员变量sideA、sideB、sideC。
4.方法
方法的定义包括两部分:方法声明和方法体。一般格式如下:
方法声明部分
{ 方法体的内容
}
(1)方法声明
最基本的方法声明包括方法名和方法的返回类型,如:
float area()
{……
}
①方法返回的数据类型可以是任意的 Java 的数据类型,而当一个方法不需要返回数据时,返回类型必须是 void。
②方法的参数可以是任意的 Java 数据类型。很多方法声明中都会给出方法的参数,参数是用逗号隔开的一些变量声明。
③方法的名字必须符合标识符规定。给方法命名时(包含局部变量),应注意:
i)名字如果使用拉丁字母,则首字母必须小写;
ii)如果由多个单词组成,则从第 2 个单词开始的其他单词的首字母必须大写。(和类名作区别)
如:
float getTrangleArea()
void setCircleRadius(double radius)
以下 Trangle 类中有 5 个方法:
class Trangle
{ double sideA,sideB,sideC;
void setSide(double a,double b,double c)
{ sideA=a;
sideB=b;
sideC=c;
}
double getSideA()
{ return sideA;
}
double getSideB()
{ return sideB;
}
double getSideC()
{ return sideC;
}
boolean isOrNotTrangle()
{ if(sideA+sideB>sideC&&sideA+sideC>sideB+sideC>sideA)
{ return true;
}
else
{ return false;
}
}
}
(2)方法体
方法体的内容包括局部变量的定义和合法的 Java 语句,如:
int getPrimNumberSum(int n)
{ int sum=0;
for(int i=1;i<=n;i++)
{ int j;
for(j=2;j<i;j++)
{ if(i%j==0)
break;
}
if(j>=i)
{ sum=sum+i;
}
}
return sum;
}
写一个方法和在 C 语言中写一个函数完全类似,只不过 Java 称作方法。
5.方法重载
Java 存在两种多态:重载(Overload)和重写(Override)。
(1)方法重载的意思是,一个类中可以有多个方法具有相同的名字,但这些方法的参数必须不同,即要么是参数的个数不同,或者是参数的类型不同。
(2)方法重载是多态性的一种,所谓功能多态性是指可以向功能传递不同的消息,以便让对象根据相应的消息来产生相应的行为。
(3)对象的功能通过类中的方法来体现,则功能的多态性就是方法的重载。
class Area
{ float getArea(float r)
{ return 3.14f*r*r;
}
double getArea(float x,int y)
{ return x*y
}
float getArea(int x,float y)
{ return x*y;
}
double getArea(float x,float y,float z)
{ return (x*x+y*y+z*z)+2.0;
}
}
注意:方法的返回类型和参数的名字不参与比较,即如果两个方法的名字相同,即使类型不同,也必须保证参数不同。
6.构造方法
构造方法是一种特殊方法,它的名字必须与它所在的类的名字完全相同,而且没有类型,构造方法也可以重载。
class 梯形
{ float 上底,下底,高;
梯形()
{ 上底=60;
下底=100;
高=20;
}
梯形(float x,int y,float h)
{ 上底=x;
下底=y;
高=h;
}
}
7.类方法和实例方法
和成员变量一样,类中的方法也可以分为实例方法和类方法。如:
class A
{ int a;
float max(float x,float y) \\实例方法
{……
}
static float jerry() \\类方法,static需放在方法的类型的前面
{……
}
static void speak(String s) \\类方法,类A中的方法jerry和speak是类方法,max是实例方法。
{……
}
}
由以上代码段可知,方法声明时,方法类型前面不加关键字 static 的是实例方法,加 static 的是类方法。static 需放在方法的类型前面。
8.两个值得注意的问题
(1)对成员变量的操作只能放在方法中,方法可以对成员变量和方法体中自己定义的局部变量进行操作。在定义类的成员变量时可以同时赋予初值。如:
class A
{ int a=12;
float b=12.56f;
}
但不可以写成以下形式:
class A
{ int a;
float b;
a=12; \\非法
b=12.56f; \\非法
}
这是因为类体的内容由成员变量的定义和方法的定义两部分组成。如:
class A
{ int a;
float b;
void f()
{ int x,y;
x=34;
y=-23;
a=12;
b=12.56f;
}
}
需要注意的是,实例方法既能对类变量操作也能对实例变量操作,而类方法只能对类变量进行操作。如:
class A
{ int a;
static int b;
void f(int x,int y)
{ a=x; \\合法
b=y; \\合法
}
static void g(int z)
{ b=23; \\合法
a=z; \\非法
}
}
(2)一个类中的方法可以互相调用,实例方法可以调用该类中的其他方法;类中的类方法只能调用该类的类方法,不能调用实例方法。如:
class A
{ float a,b;
void sum(float x,float y)
{ a=max(x,y);
b=min(x,y);
}
static float getMaxSqrt(float x,float y)
{ return max(x,y)*min(x,y);
}
static float max(float x,float y)
{ return a>b?a:b;
}
float min(float x,float y)
{ return a<b?a:b;
}
}
三、对象
类是创建对象的模板。当使用一个类创建了一个对象时,也相当于给出了这个类的实例。
1.创建对象
创建一个对象包括对象的声明和为对象分配内存两个步骤。
(1)对象的声明
一般格式如下:
类的名字 对象名字;
如:
People zhangping;
此处的 People 是一个类的名字,zhangping 是声明的对象的名字。
(2)为声明的对象分配内存
Java 使用 new 运算符和类的构造方法为声明的对象分配内存。
若类中没有构造方法,系统会调用默认的构造方法。默认的构造方法是无参数的,且方法体中没有语句。如:
zhangPing=new People();
两个详细的例子:
class XiyoujiRenwu
{ float height,weight;
String head,ear,hand,foot,mouth;
void speak(String s)
{ System.out.println(s);
}
}
class A
{ public static void main(String args[])
{ XiyoujiRenwu zhubajie; \\声明对象
zhubajie=new XiyoujiRenwu(); \\为对象分配内存,使用new运算符和默认的构造方法
}
}
class Point
{ int x,y;
point(int a,int b)
{ x=a;
y=b;
}
}
public Class A
{ public static void main(String args[])
{ Point p1,p2; \\声明对象p1和p2
p1=new Point(10,10); \\为对象分配内存,使用new和类中的构造方法
p2=new Point(23,35); \\为对象分配内存,使用new和类中的构造方法
}
}
注意:如果类中定义了一个或多个构造方法,那么 Java 不提供默认的构造方法。例 2 A 类只提供了一个构造方法,但没有提供无参数的构造方法,因此,下列代码直接创建对象是非法的。
p1=new point();
(3)对象的内存模型
使用上节中例一来说明对象的内存模型。
①声明对象时的内存模型
声明对象变量后,变量的内存中还没有任何数据,这时可称其为一个空变量,必须再进行对象分配内存的步骤,即为对象分配实体。
②对象分配内存后的内存模型
当系统见到:
zhubajie=new XiyoujiRenwu();
时,就会做下面两件事:
i)首先为该类的成员变量分配内存空间,然后执行构造方法中的语句。所谓为对象分配内存就是指为它分配变量,并获得一个引用,以确保这些变量由它来“操作管理”。
ii)接着给出一个信息,以确保这些变量是属于该类的,即这些内存单元将由该类操作管理。如果成员变量在声明时没有指定初值,所使用的构造方法也没有对成员变量进行初始化操作,那么,对于整型变量,默认初值为 0;对于浮点型,默认初值为0.0;对于 boolean 型,默认初值是 false;对于引用型,默认初值是 null。
对象的声明和为对象分配内存可以用一个等价的步骤完成,如:
XiyoujiRenwu zhubajie=new XiyoujiRenwu();
③创建多个不同的对象,一个类通过使用 new 运算符可以创建多个不同的对象,这个对象将被分配不同的内存空间,因此,改变其中一个对象的状态不会影响其他对象的状态。
同一个类中,两个不同局部变量所占据的内存空间是互不相同的。
2.使用对象
对象不仅可以操作自己的变量改变状态,而且还拥有了使用创建它的那个类中的方法的能力。对象通过使用这些方法可以产生一定的行为。
(1)对象操作自己的变量(对象的属性)
对象创建后,就有了自己的变量,即对象的实体。通过运算符“.”,对象可以实现对自己的变量的访问。
(2)对象调用类中的方法(对象的功能)
当对象调用方法时,方法中出现的成员变量就是指该对象的成员变量,同时方法中的局部变量被分配内存空间。需注意的是,局部变量声明时如果没有初始化,就没有默认值。因此,在使用局部变量前,要事先为其赋值。
下面例子中,有 zhubajie、sunwukong 两个对象
class XiyoujiRenwu
{ float height,weight;
String head,ear,hand,foot,mouth;
void speak(String s)
{ head="歪着头";
System.out.println(s);
}
}
public class Example4_3
{ public static void main(String args[])
{ XiyoujiRenwu zhubajie,sunwukong; \\声明对象
zhubajie=new XiyoujiRenwu(); \\为对象分配内存,使用new运算符和默认的构造方法
sunwukong=new XiyoujiRenwu();
zhubajie.height=1.80f; \\对象给自己的变量赋值
zhubajie.weight=160f;
zhubajie.hand="两只黑手";
zhubajie.foot="两只大脚";
zhubajie.head="大头";
zhubajie.ear="一双大耳朵";
zhubajie.mouth="一只大嘴";
sunwukong.height=1.62f; \\对象给自己的变量赋值
sunwukong.weight=1000f;
sunwukong.hand="白嫩小手";
sunwukong.foot="两只秀脚";
sunwukong.head="秀发飘飘";
sunwukong.ear="一对小耳";
sunwukong.mouth="樱桃小嘴";
System.out.println("zhubajie的身高:"+zhubajie.height);
System.out.println("zhubajie的头:"+zhubajie.head);
System.out.println("sunwukong的重量:"+sunwukong.weight)
System.out.println("sunwukong的头:"+sunwukong.head);
zhubajie.speak("俺老猪我想娶媳妇"); \\对象调用方法
System.out.println("zhubajie现在的头:"+zhubajie.head);
sunwukong.speak("老孙我重1000斤,我想骗八戒背我"); \\对象调用方法
System.out.println("sunwukong现在的头:"+sunwukong.head);
}
}
代码运行结果:
D:\3000>java Example4_3
zhubajie的身高:1.8
zhubajie的头:大头
sunwukong的重量:1000
sunwukong的头:秀发飘飘
俺老猪我想娶媳妇
zhubajie现在的头:歪着头
老孙我重1000斤,我想骗八戒背我
sunwukong现在的头:歪着头
上例中,当对象 zhubajie 调用过方法 speak 后,就将自己的头修改成“歪着头”;同样,对象 sunwukong 调用过方法 speak 后,也将自己的头修改成“歪着头”。
3.对象的引用和实体
(1)堆(heap)是一种运行时的数据结构,是一个大的存储区域。当用类创建一个对象时,类中的成员变量在堆中分配内存空间,这些内存空间称作该对象的实体或对象的变量;而对象中存放着引用,该引用在堆栈(stack)中分配内存,以确保实体由该对象操作使用。
(2)一个类所创建的两个对象,如果具有相同引用,那么就具有完全相同的实体。无实体的对象称作空对象。若程序中使用了空对象,则程序在运行时会出现异常。Java 的编译器对空对象不做检查。
(3)Java 有所谓的“垃圾收集”机制,这种机制周期地检测某个实体是否已不再被任何对象所拥有,如果发现这样的实体,就释放实体占有的内存。
对以下代码:
Point p1=new Point(12,16);
Point p2=new Point(6,18);
p1=p2;
当将p2的引用赋给p1后,最初分配给对象p1的变量(实体)所占有的内存就会被释放。因此 Java 很少出现由于程序忘记释放内存所导致的内存溢出。
四、参数
1.参数传值
当方法被调用时,如果方法有参数,参数变量必须有具体的值。
在 Java 中,方法的所有参数都是“传值”的,即方法中参数变量的值是调用者指定的值的副本。例如如果向方法的 int 型参数 x 传递一个 int 值,那参数 x 得到的值是传递值的副本。
方法如果改变参数的值,不会影响向参数“传值”的变量的量。
(1)基本数据类型参数的传值
对于基本数据类型的参数,向该参数传递的值的级别不可以高于该参数的级别。例如,不可以向 int 型参数传递一个 float 值,但可以向 double 型参数传递一个 float 值。
(2)引用类型参数的传值
Java 的引用型数据包括对象、数组以及接口。当参数是引用类型时,“传值”传递的是变量中存放的“引用”,而不是变量所引用的实体。
对于两个同类型的引用型变量,如果具有相同的引用,就会用相同的实体。因此,如果改变参数变量所引用的实体,就会导致原变量的实体发生同样的变化。但改变参数中存放的“引用”不会影响向其传值的变量中存放的“引用”。
下面例子涉及基本数据类型参数和引用类型参数。
class Dog
{ int legs;
void setLegs(int legs)
{ this.legs=legs;
}
int getLegs()
{ return legs;
}
}
class A
{ void f(int x, Dog beibei)
{ x=100;
beibei.setLegs(4);
System.out.println("参数x的值是:"+x);
System.out.println("参数对象是beibei的leg:"+beibei.getLegs());
}
}
public class Example4_4
{ public static void main(String args[])
{ int x=0;
Dog dog=new Dog();
dog.setLegs(6);
System.out.println("main方法中x的值:"+x);
System.out.println("main方法中对象dog的leg:"+dog.getLegs());
A a=new A();
a.f(x,dog);
System.out.println("方法f执行后,main方法中x的值:"+x);
System.out.println("方法f执行后,main方法中对象dog的leg:"+dog.getLegs());
}
}
D:\3000>java Example4_4
main方法中x的值:0
main方法中对象dog的leg:6
参数x的值时:100
参数对象beibei的leg:4
方法f执行后,main方法中x的值:0
方法f执行后,main方法中对象dog的leg:4
下面例子中,“圆锥”类在创建对象时,将一个圆的对象的引用传递给圆锥对象的底圆。
class 圆
{ double 半径;
圆(double r)
{ 半径=r;
}
double 计算面积()
{ return 3.14*半径*半径;
}
void 修改半径(double 新半径)
{ 半径=新半径;
}
double 获取半径()
{ return 半径;
}
}
class 圆锥
{ 圆 底圆;
double 高;
圆锥(圆 circle,double h)
{ this.底圆=circle;
this.高=h;
}
double 计算体积()
{ double volume
volume=底圆.计算面积()*高/3.0;
return volume;
}
void 修改底圆半径(double r)
{ 底圆.修改半径(r);
}
double 获取底圆半径()
{ return 底圆.获取半径();
}
}
class Example4_5
{ public static void main(String args[])
{ 圆 circle=new 圆(10);
圆锥 circular=new 圆锥(circle,20);
System.out.println("圆锥底圆半径:"+circular.获取底圆半径());
System.out.println("圆锥的体积:"+circular.计算体积());
circular.修改底圆半径(100);
System.out.println("圆锥底圆半径:"+circular.获取底圆半径());
System.out.println("圆锥的体积:"+circular.计算体积());
}
}
2.可变参数
可变参数指在声明方法时不给出参数列表中从某项直至最后一项参数的名字和个数(省略作用,“等等”、“etc.”),但这些参数的类型必须相同。可变参数使用“……”表示若干个参数,这些参数的类型必须相同,最后一个参数必须是参数列表中的最后一个参数。如:
public void f(double a,int……x)
下面例子中,A 类中的方法 f 使用了参数代表。
class A
{ public double f(double a,int …… x) \\参数代表
{ double result=0;
int sum=0;
for(int i=0;i<x.length;i++)
{ sum=sum+x[i];
}
result=a*sum;
return result;
}
}
public class Example4_6
{ public static void main(String args[])
{ A a=new A();
double result=a.f(1,28,10,20,30); \\“参数代表”x代表了3个参数
System.out.println(result);
result=a.f(62.0629,-1,-2,-3,-4,-5,-6); \\“参数代表”x代表了6个参数
System.out.println(result);
}
}