在面向对象程序设计中,使用属性来描述对象的状态,使用方法来处理对象的行为。
面向对象程序设计的特点主要概括为封装性、继承性和多态性,下面进行详细介绍。
1.封装性
面向对象编程的核心思想之一就是将对象的属性和方法封装起来,使用户只需要知道并使用对象提供的属性和方法等接口即可,而不需要知道对象的具体实现。例如,一部手机就是一个封装的对象,当使用手机拨打电话时,只需要使用它提供的键盘输入电话号码,并按下发送键即可,而不需要知道手机内部是如何工作的。
采用封装的原则可以使对象以外的部分不能随意存取对象内部的数据,从而有效地避免了外部错误对内部数据的影响,实现错误局部化,大大降低了查找错误和解决错误的难度。此外,采用封装的原则,也可以提高程序的可维护性,因为当一个对象的内部结构或实现方法改变时,只要对象的接口没有改变,就不用改变其他部分的处理。
2.继承性
面向对象程序设计中,允许通过继承原有类的某些特性或全部特性而产生新的类,这时,原有的类称为父类(或超类),产生的新类称为子类(或派生类)。子类不仅可以直接继承父类的共性,而且也允许子类创建它特有的个性。例如,已经存在一个手机类,该类中包括两个方法,分别是接听电话的方法receive()和拨打电话的方法send(),这两个方法对于任何手机都适用。
现在要定义一个时尚手机类,该类中除了要包括普通手机类包括的receive()和send()方法外,还需要包括拍照方法photograph()、视频摄录的方法kinescope()和播放MP4的方法playmp4(),这时就可以通过先让时尚手机类继承手机类,然后再添加新的方法完成时尚手机类的创建。由此可见,继承性简化了对新类的设计。
3.多态性
多态性是面向对象编程的又一重要特征。它是指在基类中定义的属性和方法被子类继承之后,可以具有不同的数据类型或表现出不同的行为。这使得同一个属性或方法在基类及其各个子类中具有不同的语义。例如,定义一个动物类Animal,该类中存在一个指定动物行为的通用方法cry()及调用该方法的doCry(Animal a)方法。其中,在doCry(Animal a)方法中,只有一句代码“a.cry();”。再定义两个动物类的子类,狗类Dog和猫类Cat,这两个类都具有cry()方法,并且都进行了相应的处理。这时,在动物园类中执行doCry(animal)方法时,如果参数为Animal类型,则会输出“动物发出叫声!”;如果参数为Dog类型,则会输出“狗发出“汪汪……”声!”;如果参数为Cat类型,则会输出字符串“猫发出“喵喵……”声!”。由此可见,在doCry(Animal a)方法中,根本不用判断应该去执行哪个类的cry()方法,因为Java编译器会自动根据所传递的参数进行判断,这就是动态绑定,即根据运行时对象的类型不同而执行不同的操作。多态性丰富了对象的内容,扩大了对象的适应性,改变了对象单一继承的关系。
Java中定义类主要分为两部分:类的声明和类体:
1.类的声明
在类声明中,需要定义类的名称、对该类的访问权限和该类与其他类的关系等。类声明的格式如下:
[修饰符] class <类名> [extends 父类名] [implements 接口列表]{
}
修饰符:可选参数,用于指定类的访问权限,可选值为public、abstract和final。
类名:必选参数,用于指定类的名称,类名必须是合法的Java标识符。一般情况下,要求首字母大写。
extends 父类名:可选参数,用于指定要定义的类继承于哪个父类。当使用extends关键字时,父类名为必选参数。
implements 接口列表:可选参数,用于指定该类实现的是哪些接口。当使用implements关键字时,接口列表为必选参数。
2.类体
在类声明部分的大括号中的内容为类体。类体主要由两部分构成,一部分是成员变量的定义,另一部分是成员方法的定义。类体的定义格式如下。
[修饰符] class <类名> [extends 父类名] [implements 接口列表]{
定义成员变量
定义成员方法
}
public class Fruit {
public String color=""; //定义颜色成员变量
//定义种植成员方法
public void plant(){
System.out.println("果树正在种植……");
}
//定义生长的成员方法
public void grow(){
System.out.println("果树正在生长……");
}
//定义收获的成员方法
public void harvest(){
System.out.println("水果已经收获……");
}
}
(1)类的成员方法:
类的成员方法由方法的声明和方法体两部分组成,其一般格式如下:
[修饰符] <方法返回值的类型> <方法名>( [参数列表]) {
[方法体]
}
修饰符: 可选参数,用于指定方法的被访问权限,可选值为public、protected和private。
方法返回值的类型: 必选参数,用于指定方法的返回值类型,如果该方法没有返回值,可以使用关键字void进行标识。方法返回值的类型可以是任何Java数据类型。
方法名: 必选参数,用于指定成员方法的名称,方法名必须是合法的Java标识符。
参数列表: 可选参数,用于指定方法中所需的参数。当存在多个参数时,各参数之间应使用逗号分隔。方法的参数可以是任何Java数据类型。
方法体:可选参数,方法体是方法的实现部分,在方法体中可以定义局部变量,需要注意的是:当方法体省略时,其外面的大括号一定不能省略。
public String harvest(){
String rtn="水果已经收获……"; //定义一个局部变量
return rtn;
}
在上面的代码中,return关键字用于将变量rtn的值返回给调用该方法的语句。
(2)成员变量和局部变量
在类体中变量定义部分所声明的变量为类的成员变量,而在方法体中声明的变量和方法的参数则称为局部变量。下面分别进行介绍。
静态变量与实例变量的区别:静态变量在内存中只有一个拷贝,运行时Java虚拟机只为静态变量分配一次内存,在加载类的过程中完成静态变量的内存分配。可以直接通过类名访问静态变量。而实例变量则可以在内存中存在多个拷贝,互不影响,每创建一个实例,就会为实例变量分配一次内存。
成员变量和局部变量的区别
成员变量和局部变量的区别在于其有效范围不同。成员变量在整个类内都有效,而局部变量只在定义它的成员方法内才有效。
当局部变量和成员变量的名字相同时,成员变量就会被隐藏,这时如果想使用成员变量,则必须使用关键字this。
(3)构造方法
构造方法是一种特殊的方法,它的名字必须与它所在类的名字完全相同,并且没有返回值,也不需要使用关键字void进行标识。构造方法用于对对象中的所有成员变量进行初始化,在创建对象时立即被调用。需要注意的是:如果用户没有定义构造方法,Java会自动提供一个默认的构造方法,用来实现成员变量的初始化。Java为各种类型的变量设置的初始值如表1所示。
byte 0
short 0
int 0
float 0
long 0
double 0
char '/u0000'
boolean false
引用类型 null
public class Fruit {
public String color; //定义颜色成员变量
//定义构造方法
public Fruit(){
color="绿色"; //对变量color进行初始化
}
//定义收获的方法
public void harvest(){
String color="红色"; //定义颜色局部变量
System.out.println("水果是:"+color+"的!"); //此处输出的是局部变量color
System.out.println("水果已经收获……");
System.out.println("水果原来是:"+this.color+"的!"); //此处输出的是成员变量color
}
public static void main(String[] args) {
Fruit fruit=new Fruit(); //声明Fruit类的一个对象fruit,并为其分配内存
fruit.harvest(); //调用Fruit类的harvest()方法
}
}
(4)创建对象
在Java中,创建对象包括声明对象和为对象分配内存两部分
1.声明对象
对象是类的实例,属于某个已经声明的类。因此,在对对象进行声明之前,一定要先定义该对象的类。声明对象的一般格式如下:
类名 对象名;
类名:必选,用于指定一个已经定义的类。
对象名:必选,用于指定对象名称,对象名必须是合法的Java标识符。
例如,声明Fruit类的一个对象fruit的代码如下:
Fruit fruit;
在声明对象时,只是在内存中为其建立一个引用,并置初值为null,表示不指向任何内存空间,因此,还需要为对象分配内存。
2.为对象分配内存
为对象分配内存也称为实例化对象。在Java中使用关键字new来实例化对象,具体语法格式如下:
对象名=new 构造方法名([参数列表]);
对象名:必选,用于指定已经声明的对象名。
类名:必选,用于指定构造方法名,即类名,因为构造方法与类名相同。
参数列表:可选参数,用于为指定构造方法的入口参数。如果构造方法无参数,则可以省略。
如,在声明Fruit类的一个对象fruit后,可以通过以下代码为对象fruit分配内存:
fruit=new Fruit();
在声明对象时,也可以直接为其分配内存:Fruit fruit=new Fruit();
(5)使用对象
创建对象后,就可以通过对象来引用其成员变量,并改变成员变量的值,而且还可以通过对象来调用其成员方法。通过使用运算符“.”实现对成员变量的访问和成员方法的调用。下面通过具体实例介绍如何访问成员变量和调用成员方法。
public class Circ {
final float PI=3.14159f; //定义一个用于表示圆周率的常量PI
public float r=0.0f;
//定义计算圆面积的方法
public float getArea() {
float area=PI*r*r; //计算圆面积并赋值给变量area
return area; //返回计算后的圆面积
}
}
//定义计算圆周长的方法
public float getCircumference(float r) {
float circumference=2*PI*r; //计算圆周长并赋值给变量circumference
return circumference; //返回计算后的圆周长
}
//定义主方法测试程序
public static void main(String[] args) {
Circ circ=new Circ();
circ.r=20; //改变成员变量的值
float r=20;
float area=circ.getArea(); //调用成员方法
System.out.println("圆的面积为:"+area);
float circumference=circ.getCircumference(r); //调用带参数的成员方法
System.out.println("圆的周长为:"+circumference);
}
}
(6)销毁对象
在许多程序设计语言中,需要手动释放对象所占用的内存,但是,在Java中则不需要手动完成这项工作。Java提供的垃圾回收机制可以自动判断对象是否还在使用,并能够自动销毁不再使用的对象,收回对象所占用的资源。
Java提供了一个名为finalize()的方法,用于在对象被垃圾回收机制销毁之前,由垃圾回收系统调用。但是垃圾回收系统的运行是不可预测的。因此,在Java程序中,也可以使用析构方法finalize()随时来销毁一个对象。析构方法finalize()没有任何参数和返回值,每个类有且只有一个析构方法。
包的概念
包(package)是Java提供的一种区别类的名字空间的机制,是类的组织方式,是一组相关类和接口的集合,它提供了访问权限和命名的管理机制。Java中提供的包主要有以下3种用途。
将功能相近的类放在同一个包中,可以方便查找与使用。
由于在不同包中可以存在同名类,所以使用包在一定程度上可以避免命名冲突。
在Java中,某次访问权限是以包为单位的。
在Java中提供的包,相当于系统中的文件夹
package com.wgh;
public class Circ {
final float PI=3.14159f; //定义一个用于表示圆周率的常量PI
//定义一个绘图的方法
public void draw(){
System.out.println("画一个圆形!");
}
}
使用包中的类:
类可以访问其所在包中的所有类,还可以使用其他包中的所有public类。访问其他包中的public类可以有以下两种方法。
1.
使用长名引用包中的类
使用长名引用包中的类比较简单,只需要在每个类名前面加上完整的包名即可。例如,创建Circ类(保存在com.wgh包中)的对象并实例化该对象的代码如下:
com.wgh.Circ circ=new com.wgh.Circ();
2.
使用import语句引入包中的类
由于采用使用长名引用包中的类的方法比较繁琐,所以Java提供了import语句来引入包中的类。import语句的基本语法格式如下:
import 包名1[.包名2.……].类名|*;
当存在多个包名时,各个包名之间使用“.”分隔,同时包名与类名之间也使用“.”分隔。
import com.wgh.*;
继承的使用原则:
子类可以继承父类中所有可被子类访问的成员变量和成员方法,但必须遵循以下原则:
(1)子类能够继承父类中被声明为public和protected的成员变量和成员方法,但不能继承被声明为private的成员变量和成员方法。
(2)子类能够继承在同一个包中的由默认修饰符修饰的成员变量和成员方法。
(3)如果子类声明了一个与父类的成员变量同名的成员变量,则子类不能继承父类的成员变量,此时称子类的成员变量隐藏了父类的成员变量。
(4)如果子类声明了一个与父类的成员方法同名的成员方法,则子类不能继承父类的成员方法,此时称子类的成员方法覆盖了父类的成员方法。
eg:鸟继承动物
使用this关键字:
当局部变量和成员变量的名字相同时,成员变量就会被隐藏,这时如果想在成员方法中使用成员变量,则必须使用关键字this。关键字this的基本格式如下:
this.成员变量名
this.成员方法名()
例如,在Fruit类中定义一个成员变量color,并且在该类的成员方法中又定义了一个局部变量color,这时,如果想在成员方法中使用成员变量color,则需要使用this关键字,具体代码如下:
public class Fruit {
public String color="绿色"; //定义颜色成员变量
//定义收获的方法
public void harvest(){
String color="红色"; //定义颜色局部变量
System.out.println("水果是:"+color+"的!"); //此处输出的是局部变量color
System.out.println("水果已经收获……");
System.out.println("水果原来是:"+this.color+"的!"); //此处输出的是成员变量color
}
}
使用super关键字:
如果子类中声明的成员变量与父类的成员变量同名,则子类不能继承父类的成员变量,此时称子类的成员变量隐藏了父类的成员变量。如果子类中声明的成员方法与父类的成员方法同名,并且方法的返回值及参数个数和类型也相同,则子类不能继承父类的成员方法,此时称子类的成员方法覆盖了父类的成员方法。这时,如果想在子类中访问父类中被子类隐藏的成员方法或变量时,就可以使用super关键字。super关键字主要有以下两种用途。
1.调用父类的构造方法
在Animal类中添加一个默认的构造方法和一个带参数的构造方法,具体代码如下:
public Animal(){
}
public Animal(String strSkin){
skin=strSkin;
}
这时,如果想在子类Bird中使用父类的带参数的构造方法,则需要在子类Bird的构造方法中通过以下代码进行调用。
public Bird(){
super("羽毛");
}
2.操作被隐藏的成员变量和被覆盖的成员方法
如果想在子类中操作父类中被隐藏的成员变量和被覆盖的成员方法,也可以使用super关键字,具体格式如下:
super.成员变量名
super.成员方法名([参数列表])
例如,如果想在子类Bird的方法中改变父类Animal的成员变量skin的值可以使用以下代码:
super.skin="羽毛";
如果想在子类Bird的方法中使用父类Animal的成员方法move()可以使用以下代码:
super.move();
方法的重载:
方法的重载是指在一个类中,出现多个方法名相同,但参数个数或参数类型不同的方法,则称为方法的重载。Java在执行具有重载关系的方法时,将根据调用参数的个数和类型区分具体执行的是哪个方法。
例如,定义一个名称为Calculate的类,在该类中定义两个名称为getArea()的方法(参数个数不同)和两个名称为draw()的方法(参数类型不同),具体代码如下:
public class Calculate {
final float PI=3.14159f; //定义一个用于表示圆周率的常量PI
//求圆形的面积
public float getArea(float r){ //定义一个用于计算面积的方法getArea()
float area=PI*r*r;
return area;
}
//求矩形的面积
public float getArea(float l,float w){ //重载getArea ()方法
float area=l*w;
return area;
}
//画任意形状的图形
public void draw(int num){ //定义一个用于画图的方法draw()
System.out.println("画"+num+"个任意形状的图形");
}
//画指定形状的图形
public void draw(String shape){ //重载draw()方法
System.out.println("画一个"+shape);
}
public static void main(String[] args) {
Calculate calculate=new Calculate(); //创建Calculate类的对象并为其分配内存
float l=20;
float w=30;
float areaRectangle=calculate.getArea(l, w);
System.out.println("求长为"+l+" 宽为"+w+"的矩形的面积是:"+areaRectangle);
float r=7;
float areaCirc=calculate.getArea(r);
System.out.println("求半径为"+r+"的圆的面积是:"+areaCirc);
int num=7;
calculate.draw(num);
calculate.draw("三角形");
}
}
重载的方法之间并不一定必须有联系,但是为了提高程序的可读性,一般只重载功能相似的方法。
注意:
在方法的重载时,方法返回值的类型不能作为区分方法的标志。
方法的覆盖:
覆盖是指父子类之间的关系,当子类继承父类中所有可能被子类访问的成员方法时,如果子类的方法名与父类的方法名相同,那么子类就不能继承父类的方法,此时,称为子类的方法覆盖了父类的方法。覆盖体现了子类补充或者改变父类方法的能力,通过覆盖,可以使一个方法在不同的子类中表现出不同的行为。
(1)创建一个名称为Animal的类,在该类中声明一个成员方法cry(),具体代码如下:
Animal.java类文件的代码
public class Animal {
public Animal(){
}
public void cry(){
System.out.println("动物发出叫声!");
}
}
(2)创建一个Animal类的子类Dog类,在该类中覆盖了父类的成员方法cry(),具体代码如下:
Dog.java类文件的代码
public class Dog extends Animal {
public Dog(){
}
public void cry(){
System.out.println("狗发出"汪汪……"声!");
}
}
(3)创建一个Animal类的子类Cat类,在该类中覆盖了父类的成员方法cry(),具体代码如下:
Cat.java类文件的代码
public class Cat extends Animal{
public Cat(){
}
public void cry(){
System.out.println("猫发出"喵喵……"声!");
}
}
(4)创建一个Animal类的子类Cattle类,在该类中不定义任何方法,具体代码如下:
Cattle.java类文件的代码
public class Cattle extends Animal {
}
(5)创建一个名称为Zoo的类,在该类的main()方法中创建子类Bird的对象并为该对象分配内存,然后对象调用该类的成员方法,具体代码如下:
Zoo.java类文件的代码
public class Zoo {
public static void main(String[] args) {
Dog dog=new Dog(); //创建Dog类的对象并为其分配内存
System.out.println("执行dog.cry();语句时的输出结果:");
dog.cry();
Cat cat=new Cat(); //创建Cat类的对象并为其分配内存
System.out.println("执行cat.cry();语句时的输出结果:");
cat.cry();
Cattle cattle=new Cattle(); //创建Cattle类的对象并为其分配内存
System.out.println("执行cattle.cry();语句时的输出结果:");
cattle.cry();
}
}
由于Dog类和Cat类都重载了父类的方法cry(),所以执行的是子类中的cry()方法,但是Cattle类没有重载父类的方法,所以执行的是父类中的cry()方法。
注意:
在进行方法覆盖时,需要注意以下内容。
子类不能覆盖父类中声明为final或者static的方法。
子类必须覆盖父类中声明为abstract的方法,或者子类也应该声明为abstract。
子类覆盖父类中的同名方法时,子类的方法声明也必须和父类中被覆盖的方法的声明一样。
抽象类:
所谓抽象类就是只声明方法的存在而不去具体实现它的类。抽象类不能被实例化,也就是不能创建其对象。在定义抽象类时,要在关键字class前面加上关键字abstract。其具体格式如下:
abstract class 类名{
类体
}
定义一个名称为Fruit的抽象类,代码如下:
abstract class Fruit { //定义抽象类
public String color; //定义颜色成员变量
//定义构造方法
public Fruit(){
color="绿色"; //对变量color进行初始化
}
}
在抽象类中创建的,没有实际意义的,必须要子类重写的方法称为抽象方法。抽象方法只有方法的声明,而没有方法的实现方法,用关键字abstract进行修饰。声明一个抽象方法的基本格式如下:
abstract <方法返回值类型> 方法名(参数列表);
方法返回值类型:必选参数,用于指定方法的返回值类型,如果该方法没有返回值,可以使用关键字void进行标识。方法返回值的类型可以是任何Java数据类型。
例如,在上面定义的抽象类中添加一个抽象方法:
//定义抽象方法
public abstract void harvest(); //收获的方法
注意:
抽象方法不能使用private或static关键字进行修饰。
包含一个或多个抽象方法的类必须被声明为抽象类。这是因为抽象方法没有定义方法的实现部分,如果不声明为抽象类,这个类将可以生成对象,这时当用户调用抽象方法时,程序就不知道如何处理了。
Fruit.java类
abstract class Fruit { //定义抽象类
public String color; //定义颜色成员变量
//定义构造方法
public Fruit(){
color="绿色"; //对变量color进行初始化
}
//定义抽象方法
public abstract void harvest(); //收获的方法
}
Apple.java类
public class Apple extends Fruit {
public void harvest() {
System.out.println("苹果已经收获!"); //输出字符串“苹果已经收获!”
}
}
Orange.java类
public class Orange extends Fruit {
public void harvest() {
System.out.println("桔子已经收获!"); //输出字符串“桔子已经收获!”
}
}
Farm.java类
public class Farm {
public static void main(String[] args) {
System.out.println("调用Apple类的harvest()方法的结果:");
Apple apple=new Apple(); //声明Apple类的一个对象apple,并为其分配内存
apple.harvest(); //调用Apple类的harvest()方法
System.out.println("调用Orange类的harvest()方法的结果:");
Orange orange=new Orange(); //声明Orange类的一个对象orange,并为其分配内存
orange.harvest(); //调用Orange类的harvest()方法
}
}
使用关键字final进行修饰的类称为final类,该类不能被继承,即不能有子类。有时为了程序的安全性,可以将一些重要的类声明为final类。例如,Java提供的System类和String类,对于编译器和解释器的正常运行起到很大的作用,不能被轻易改变,所以被声明为final类。
使用final关键字修饰的方法称为final方法,该方法不能被重写。
接口:
定义一个用于计算的接口,在该接口中定义了一个常量PI和两个方法,具体代码如下:
public interface Calculate {
final float PI=3.14159f; //定义用于表示圆周率的常量PI
float getArea(float r); //定义一个用于计算面积的方法getArea()
float getCircumference(float r); //定义一个用于计算周长的方法getCircumference()
}
注意:
与Java的类文件一样,接口文件的文件名必须与接口名相同。
实现接口:
在类的继承中,只能做单重继承,而实现接口时,一次则可以实现多个接口,每个接口间使用逗号“,”分隔。这时就可能出现常量或方法名冲突的情况,解决该问题时,如果常量冲突,则需要明确指定常量的接口,这可以通过“接口名.常量”实现。如果出现方法冲突时,则只要实现一个方法就可以了。下面通过一个具体的实例详细介绍以上问题的解决方法。
(1)创建一个名称为Calculate的接口,在该接口中声明一个常量和两个方法。
完整代码如下:
Calculate.java类
public interface Calculate {
final float PI=3.14159f; //定义一个用于表示圆周率的常量PI
float getArea(float r); //定义一个用于计算面积的方法getArea()
float getCircumference(float r); //定义一个用于计算周长的方法getCircumference()
}
(2)创建一个名称为GeometryShape的接口,在该接口中声明一个常量和3个方法,具体代码如下:
GeometryShape.java类
public interface GeometryShape {
final float PI=3.14159f; //定义一个用于表示圆周率的常量PI
float getArea(float r); //定义一个用于计算面积的方法getArea()
float getCircumference(float r); //定义一个用于计算周长的方法getCircumference()
void draw(); //定义一个绘图方法
}
(3)创建一个名称为Circ的类,该类实现例程16和例程17所定义的接口,具体代码如下:
Circ.java类
public class Circ implements Calculate,GeometryShape {
//定义计算圆面积的方法
public float getArea(float r) {
float area=Calculate.PI*r*r; //计算圆面积并赋值给变量area
return area; //返回计算后的圆面积
}
//定义计算圆周长的方法
public float getCircumference(float r) {
float circumference=2*Calculate.PI*r; //计算圆周长并赋值给变量circumference
return circumference; //返回计算后的圆周长
}
//定义一个绘图的方法
public void draw(){
System.out.println("画一个圆形!");
}
//定义主方法测试程序
public static void main(String[] args) {
Circ circ=new Circ();
float r=7;
float area=circ.getArea(r);
System.out.println("圆的面积为:"+area);
float circumference=circ.getCircumference(r);
System.out.println("圆的周长为:"+circumference);
circ.draw();
}
}