第 4 章 抽象与封装
本章主要内容 抽象性与封装性的实现机制 定义类 对象的创建与引用 成员方法和构造方法 访问修饰符 static 修饰符 抽象类
一、抽象与封装的实现技术 在解决实际问题时,首先需要利用抽象技术对问题域中出现的所有实体进行分析,归纳出共性的特性形成一类实体的基础特征,这些特征包括属性和行为,然后将它们包装在一起,构成一个描述实体特性的封装体,这是面向对象程序设计方法实现软件系统所使用的核心技术。在Java语言中,用类和对象共同实现这种抽象和封装的处理机制。
二、类定义 类是对具有相同属性和行为的对象特性的描述。属性是对象的静态部分,行为是对象的动态部分。在Java中,用类的数据成员(域)表示静态属性;用类的成员方法(方法)表示动态行为。
类的定义格式 [修饰符] class 类名 [extends 父类名] [ implements 接口名] { 数据成员声明; 成员方法定义; } 类名称 数据成员 成员方法 UML表示方法
public class Box //Box类 文件名 Box.java { private int length,width, height; public void setLong(int lengthVal){length=lengthVal;} public void setWidth(int widthVal){width=widthVal;} public void setHeight (int heightVal){ height = heightVal;} public void setBox(int lengthVal,int widthVal,int heightVal) length=lengthVal; width=widthVal; height= heightVal; } public int getLength( ){return length;} public int getWidth( ){return width;} public int getHeight ( ){return height;} Box -int length -int width -int height +void setLength() +void setWidth() +void setHeight() +void setBox() +int getLength() +int getWidth() +int getHeight()
封装体 length width height setLength( ) getLength( ) setWidth( ) getWidth( ) setHeight( ) getHeight( ) setBox( ) 封装体
public class Date //日期类 { private int year,month,day; public void setDate(int y,int m,int d) {year=y;month=m;day=d;} public int getYear( ){return year;} public int getMonth( ){return month;} public int getDay( ){return day;} }
public class StudentInfo { private int no; //学号 private String name; //姓名 private char sex; //性别 private Date birthDay; //出生日期 public void setInformation(int no,String name, char sex,int y,int m,int d) this.no=no; this.name=name; this.sex=sex; birthDay=new Date( ); birthDay.setDate(y,m,d); } public int getNo( ) {return no;} public String getName( ) {return name;} public char getSex( ) {return sex;} public Date getBirthDay(){return birthDay;} 若类成员是类对象, 则需要用new创建。 整体-部分 StudentInfo Date
说明 在Java中,若没有指明父类,则默认父类为Object。Object在java.lang包中。 若类中某个数据成员属于某个类,则在类中一定要利用 new 创建这个成员对象,以达到分配空间的目的。
三、对象的创建和引用 对象是类的实例。 声明对象格式: 类名 对象名; Box myBox; myBox为引用Box类对象的变量
注意:类属于引用型数据类型,因此,在声明对象时,系统并没有为对象分配空间,用户需要应用 new 运算完成分配空间的任务。 myBox=new Box( );
public class BoxClass { public static void main(String[] agrs) Box myBox = new Box( ); myBox.setBox( 10,20,15); System.out.println(“长: ”+myBox.getLength( )); System.out.println(“宽: ”+myBox.getWidth( )); System.out.println(“高: ”+myBox.getHeight( )); }
public class Student { public static void main(String[] args) StudentInfo s=new StudentInfo( ); s.setInformation(100,"Zhang",'f',1984,10,25); System.out.println("No: "+s.getNo( )); System.out.println("Name: "+s.getName( )); System.out.println("Sex: "+s.getSex( )); System.out.println("Brithday:"+s.getBirthDay().getYear( ) +","+s.getBirthDay().getMonth( ) +","+s.getBirthDay().getDay( )); }
对象数组需要用new为其分配空间 s import java.io.*; public class Students StudentInfo StudentInfo[ ] import java.io.*; public class Students { public static void main(String[] args) { StudentInfo[] s=new StudentInfo[10]; for (int i=0;i<10;i++) { s[i]=new StudentInfo( ); s[i].setInformation(100+i,"Zhang",'f',1984+i,10,25); System.out.print("No: "+s[i].getNo( )); System.out.print(" Name: "+s[i].getName( )); System.out.print(" Sex: "+s[i].getSex( )); System.out.println(" Birthday: " +s[i].getBirthDay().getYear( ) +","+s[i].getBirthDay().getMonth( )+"," + s[i].getBirthDay().getDay( )); } 对象数组需要用new为其分配空间
四、成员方法 在类定义中,用数据成员(域)描述实体的属性;用成员方法(类方法)表现实体的操作行为。 《成员方法的定义》 [修饰符] 返回类型 方法名称([参数表]) { //方法体 }
《成员方法的重载》 在 Java 语言中,同一个成员方法名在同一个作用域中可以被定义多次,但要求参数表不能完全一样。调用成员方法时,系统将通过成员方法名和参数确定所调用的具体方法。这种现象叫做成员方法的重载。
import java.awt.*; import javax.swing.*; public class MethodOverload extends JApplet { public void paint( Graphics g ) g.drawString( "The square of integer 17 is “ + square( 17 ), 25, 25 ); g.drawString( "The square of int 17.5 is " + square( 17.5 ), 25, 40 ); } public int square( int x ) { return x * x; } public double square( doble y ) { return y * y; }
《main( )成员方法》 在 Application 应用程序中,必须有一个且仅有一个main( )。当一个程序文件中包含一个或多个类时,程序文件的名称必须与包含main( )方法的类名相同。main( )方法是Java程序的入口点。
import java.io.*; public class MainTest { public static void main( String args[ ] ) int n = args.length; if( n == 0 ) { System.out.println( " no parament ! " ); } else System.out.println( " number of paraments : " + n ); for ( int i = 0; i < n; i++ ) System.out.println( " args[ " + i + " ] = " + args[i] ); }
五、构造方法 为类中数据成员赋初值的途径: 各种类型的默认值 定义数据成员时赋初值 初始化块 利用构造函数赋初值
public class Box //Box类 { private int length,width, height; { //初始化块 length=10; width=20; height=40; } public void setLong(int lengthVal){ length=lengthVal; } public void setWidth(int widthVal){ width=widthVal; } public void setHeight (int heightVal){ height = heightVal; } public void setBox(int lengthVal,int widthVal,int heightVal) length=lengthVal; width=widthVal; height= heightVal; public int getLength( ){ return length; } public int getWidth( ){ return width; } public int getHeight ( ){ return height; }
构造方法是一个特殊的成员函数,它与类名相同。在创建对象后(即为对象分配空间),系统自动根据参数调用相应的构造函数。构造方法的主要功能是为数据成员赋初值。
import java.io.*; public class Box { private int length,width,height; public Box(int l,int w,int h) { length=l; width=w; height=h;} public Box( ) { length=10 ; width= 10; height= 10; } public Box(Box box) { length=box.length; width=box.width; height=box.height;} public int getLength( ){return length;} public int getWidth( ){return width;} public int getHeight( ){return height;} public int volume( ) { return length*width*height; } public String toString() { return “长度”+ length+“ 宽度”+ width+“ 高度”+ height; } }
import java.awt.*; import javax.swing.*; public class Box_class extends JApplet { Box box1,box2,box3; public void init( ) box1=new Box( ); box2=new Box(100,120,80); box3=new Box(box2); } public void paint(Graphics g) g.drawString(" Box1: "+box1, 15,20); g.drawString(" Box2: " +box2,15,50); g.drawString(" Box3: " +box3,15,80);
public class Date { private int year,month,day; public Date(int y, int m, int d) {year=y;month=m;day=d;} public Date( ){ this(2004,10,1); } public void setDate(int y,int m,int d) { year=y;month=m;day=d; } public int getYear( ){ return year; } public int getMonth( ){ return month; } public int getDay( ){ return day; } }
public class StudentInfo { private int no; private String name; private char sex; private Date birthDay; public StudentInfo(int no,String name, char sex,int y,int m,int d) { this.no=no; this.name=name; this.sex=sex; this.birthDay=new Date(y,m,d ); } public StudentInfo( ) { no=0; name=“”; sex=‘m’; birthDay=new Date( ); } public int getNo( ){return no;} public String getName( ){return name;} public char getSex( ){return sex;} public Date getBirthDay(){return birthDay;}
public class Student { public static void main(String[] args) StudentInfo s1=new StudentInfo( ); StudentInfo s2= new StudentInfo(100,“Zhang” ,'f',1984,10,25); System.out.println("No: "+s1.getNo( )); System.out.println("Name: "+s1.getName( )); System.out.println("Sex: "+s1.getSex( )); System.out.println("Brithday:"+s1.getBirthDay().getYear( ) +","+s1.birthDay.getMonth( )+","+s1.getBirthDay().getDay( )); System.out.println("No: "+s2.getNo( )); System.out.println("Name: "+s2.getName( )); System.out.println("Sex: "+s2.getSex( )); System.out.println("Birthday:"+s2.getBirthDay().getYear( ) +","+s2.birthDay.getMonth( )+","+s2.getBirthDay.getDay( )); }
构造方法的设计原则 默认的构造方法 带完整参数的构造方法 如果类中没有构造方法,系统将自动地添加一个不带参数的默认构造方法,其方法体为空。但若包含了用户自定义的构造方法,系统给予的这个构造方法将不复存在,因此需要用户自己重新定义。
对象的生成、使用和清除 在Java程序中,创建类对象需要经过声明、实例化(new)和初始化三个步骤。 使用对象包括引用类对象的数据成员和成员方法。格式为类对象名.成员名。 类对象的清除由系统完成,不需要用户干预。
访问控制修饰符是一组限定类、属性或方法被程序里的其他部分访问的符号。 六、访问控制修饰符 访问控制修饰符是一组限定类、属性或方法被程序里的其他部分访问的符号。 类:public、默认、protected(内部类)、private(内部类) 数据成员和成员方法:public、private、protected、默认
public(公有) 若类声明为public,则表明这个类可以被所有的其他类引用,即其他类可以声明或创建这个类的对象,并访问这个类内部的可见数据成员、调用可见成员方法; 若数据成员声明为 public,任何可以引用这个类的成员方法都可以直接引用它,但这样做将会破坏对象的封装性; 若成员方法声明为public,任何可以引用这个类的成员方法都可以直接调用它,这样的成员方法往往作为类对象的接口。
同一个包中类的引用 public class class_b { //数据成员 ......; //成员方法 } class class_a { ......; class_b item; .......; void fun( ) { class_b c_var; ...... } public class class_b { //数据成员 ......; //成员方法 } 同一个包中类的引用
不同包中类的引用 package p2; public class class_b { //数据成员 ......; //成员方法 } { //数据成员 ......; //成员方法 } package p1; import p2.class_b; class class_a { ......; class_b item; .......; void fun( ) { class_b c_var; ...... } 不同包中类的引用
同一个包中public类成员的引用 public class Date { public int year,month,day; public Date( ) {......}; ......; } class Visit_Date Date birthDay; void Display( ) { ......; System.out.print(birthDay.year+ birthDay.month+birthDay.day); 同一个包中public类成员的引用
不同包中public类成员的引用 package p2; import p1.Date; class Visit_Date { Date birthDay; void Display( ) { ......; System.out.print(birthDay.year+ birthDay.month+birthDay.day); ......; } package p1; public class Date { public int year,month,day; Date( ) {......}; ......; } 不同包中public类成员的引用
public的适用场合 公用类应该设置成public 类中作为接口的成员方法应该设置成public
默认 若没有设置访问控制符,则说明该类、数据成员或成员方法具有默认访问控制权限。这样的类、数据成员和成员方法只能被同一个包中的类引用,因此,我们又将其称为包访问性。
同一个包中缺省访问权限类的引用 class class_b { //数据成员 ......; //成员方法 } class class_a { ......; class_b item; .......; void fun( ) { class_b c_var; ...... } class class_b { //数据成员 ......; //成员方法 } 同一个包中缺省访问权限类的引用
不同包中不能引用缺省访问权限的类 package p1; import p2.class_b; package p2; class class_a { ......; class_b item; //错误 .......; void fun( ) { class_b c_var; //错误 ...... } package p2; class class_b { //数据成员 ......; //成员方法 } 不同包中不能引用缺省访问权限的类
同一个包中可以引用缺省访问权限的成员 public class Date { int year,month,day; ......; } class Visit_Date Date birthDay; void Display( ) { ......; System.out.print(birthDay.year+ birthDay.month+birthDay.day); 同一个包中可以引用缺省访问权限的成员
不同包中不能引用缺省访问权限的成员 package p2; import p1.Date; class Visit_Date { Date birthDay; void Display( ) { ......; System.out.print(birthDay.year+ birthDay.month+birthDay.day); ......; } package p1; public class Date { int year,month,day; Date( ) {......}; ......; } 编译错误 不同包中不能引用缺省访问权限的成员
private(私有) 用 private 修饰的数据成员和成员方法只能被类自身的成员方法引用,而不能被任何其他类(包括子类)引用。应该将不希望他人随意引用或修改的数据成员设置成 private,以此将它们隐藏起来,从而提高程序的安全性和可维护性。
private成员的引用 public class CTime { private int hour,minute,second; public CTime( ){ hour=0; minute=0; secong=0; } public CTime(int h,int m,int s) if (h<0||h>23) hour=0;else hour=h; if (m<0||m>59) minute=0;else minute=m; if (s<0||s>59) second=0;else second=s; } public int getHour( ){ return hour; } public int getMinute( ){ return minute; } public int getSecond( ){ return second; } private成员的引用
此条语句出现访问私有成员的错误 不能直接引用private成员 public class VisitCTime { public static void main(String[] agrs) CTime time=new CTime(10,24,56); System.out.print(time.hour+”:”+ time.minute+”:”+time.second); System.out.print(time.getHour( )+ ”:”+ time.getMinute( )+ ”:”+ time.getSecond( )); } 此条语句出现访问私有成员的错误 不能直接引用private成员
============================ protected 用protected修饰的成员可被三种类引用: 该类自身 同一个包中的其他类 在其他包中的该类的子类 ============================ 作用: 允许存在其他包中的该类的子类引用
控制成员访问权限小结 访问控制 本类 同一包 中的类 其他包 中子类 public 默认 × private protected
名片类 public class Card //卡片类 { private String name; //姓名 private String appellation; //称呼 private String department; //工作单位 private String tel; //电话号码 private String handset; //手机号码 private String email; //电子邮箱 public Card( ){…… } public Card(String n,String a,String d,String t,String h,String e) {…… } public String getName( ){return name;} public String getAppellation( ){return appellation;} public String getDepartment( ){return department;} public String getTel( ){return tel;} public String getHandset( ){return handset;} public String getEmail( ){return email;} public String toString( ) { return department+"\n\t"+name+"\t"+appellation+"\n\n" +"\tTel:"+tel+"\n\t手机:"+handset+"\n\tEmail:"+email; } 名片类
public class Test //测试类 { public static void main(String[] args) Card card=new Card("王军","先生", "软件公司","800900","13900000000","xx@163.com"); System.out.println("-------------------------------------"); System.out.println(card); }
设计类成员的基本原则 将能够反映实体特征且与需求有关的属性和行为设计成数据成员和成员方法; 应该将数据成员的访问属性设计成private;成员方法的访问属性设计成public; 一定要设计构造方法,包含有参数和无参数。参数格式要适合人们的使用习惯; 对于每一个数据成员都应该提供一套set_()和get_()成员方法,以便外界对其操作; 设计一个toString(),方便对象的输出。
举例1 — 单向链表类 public class NodeClass { private int data; private NodeClass next; public NodeClass(int data) {this.data=data;next=null;} public NodeClase( ){this(0);} public void setNode(int data){this.data=data} public void setNext(int data) { next=new NodeClass(data);} public void setNode( ){next=new NodeClass( );} public int getData( ){return data;} public NodeClass getNext( ){return next;} public String toString( ){ return data+ “ ”;} } LinkList NodeClass
public class LinkList { private NodeClass head; private int length; public LinkList( ){head=null; length=0;} public void appendNode(int data){......} public void addNode(int data,int index){......} public int delNode(int index){......} public NodeClass getHead( ){return head;} public int getLength( ){return length;} ....... public String toString( ){......} } String str=""; Node Class p; p=head; while (p!=null){ str=str+p; p=p.setNext(); }
七、修饰符 —— static static可以修饰类中的数据成员和成员方法。静态数据成员在加载类时分配空间。它属于类(又称为类变量,而非静态变量称为实例变量),不属于某个特定的对象,因此可以通过类进行访问。往往将属于该类的每个对象共用的数据成员声明成static。
类 object1 object2 object3 静态部分 实例部分 静态部分(该类的所有对象共享) 实例 部分 静态部分
用static修饰的成员方法属于整个类的成员方法(又称为类方法),其特点为: 调用该方法时,前缀可以使用类名 该方法的代码段随着类加载在内存中 由于该方法属于整个类,所以,它不能处理和调用属于对象的实例成员,而只能处理或调用static成员。
a1 a2 a2 class StaticClass { int x; static int y = 3; void showx( ) { System.out.println("x = " + x); } static void show( ) { System.out.print("static: "); } static void showy( ) { System.out.println("y = " + y); } static void show_static( ) {show( ); showy( ); } } public class StaticTest { public static void main(String args[ ]) { StaticClass.y += 1; StaticClass.show_static( ); StaticClass a1 = new StaticClass( ); a1.showx( ); StaticClass.showy( ); a1.showy( ); StaticClass a2 = new StaticClass( ); a2.x = 6; a2.y = 7; a2.showx( ); a2.show_static( ); a1.show_static( ); StaticClass.show_static( ); y:4 show( ) showy( ) show_static( ) y:7 show( ) showy( ) show_static( ) y:3 show( ) showy( ) show_static( ) x:0 showx( ) a1 x:6 showx( ) a2 x:0 showx( ) a2
初始化静态成员变量的初始化器 静态初始化器是在类定义中由关键字static 引导的一对大括号括起来的语句组。它的任务是对静态成员变量进行初始化。
public class Employee { private int m_EmpNo; //雇员编号 private String m_EmpName; //雇员姓名 private char m_EmpSex; //雇员性别 private int m_EmpSalary; //雇员工资 static int m_MinSalary; //雇员最低工资 static int m_NextEmpNo; //下一个雇员的编号 } static { //静态初始化器 m_MinSalary=250; m_NextEmpNo=3001; public Employee(String name,char sex,int sal) //构造方法 {……}
初始化器与构造函数的区别 构造方法 静态初始化器 初始化对象 初始化类 new时系统自动调用 加载类时系统调用 特殊的成员方法 不是成员方法
八、抽象类与修饰符 abstract 用abstract修饰的类被称为抽象类。所谓抽象类是指没有完整实现类定义的类。它的主要用途是用来描述概念性的内容,这样可以提高开发效率,更好地统一用户接口。需要注意的是:不能创建抽象类的实例对象。
抽象方法 abstract 可以修饰成员方法。表明该成员方法是一个抽象的方法,即没有方法体。必须在子类中具体描述方法的实现过程。抽象方法必须存在于抽象类之中。
abstract class A { abstract void show( ); abstract void show(int i); } class B extends A { int x; void show( ) { System.out.println("x="+x); } void show(int i) { x=i; System.out.println("x="+x); } public class AbstractUse { public static void main(String[] args) { B b = new B( ); b.show( ); b.show(7);
abstract class Shape //图形 { protected int x=10; protected int y=5; abstract double area( ); } class Triangle extends Shape //三角形 { Triangle(int a,int b) { x=a; y=b; } double area( ) { return 0.5*x*y; } class Rectangle extends Shape //矩形 Rectangle(int a,int b) { x=a; y=b; } double area( ) { return x*y; } class Circle extends Shape //圆形 Circle(int a) { x=a; } double area( ) {return Math.PI*x*x; }
class Polymorphism { public static void main(String args[ ]) Triangle t = new Triangle(5 , 2 ); System.out.println(“The triangle area is “+ t.area( )); Rectangle r = new Rectangle(3 ,4 ); System.out.println(“The rectangle area is “+ r.area( )); Circle c = new Circle(10 ); System.out.println(“The circle area is “+ c.area( )); }
抽象类小结 抽象方法一定要位于抽象类中; 设置抽象类的目的是概念化具有共性的所有事物,并为之设置统一的用户接口,而具体操作的实现在子类中; 抽象类中的抽象方法应该是每个子类都具有的操作,但每个子类的具体操作内容不一样; 使用抽象类的好处:隐藏具体的操作细节,有利于软件的维护和扩展。
九、修饰符 —— final 用final声明的类(最终类)不能再有子类;成员方法(最终方法)不能再被子类覆盖;数据成员(最终属性)初始化后,不能再被重新赋值。
用final声明的作用 用 final 声明的类:通常是一些有固定作用、用来完成某种标准功能的类;
终结器 终结器是名为 finalize 的方法,它没有参数列表和返回值。在系统回收对象资源时,系统自动地调用它。因此若在此时需要完成一些特殊的操作,就应该声明该方法。其格式为: protected void finalize( ) { 必要的操作 }
第 4 章上机作业 设计一个身份证管理程序。 提示 应该定义下列类: (1)表示出生日期的Date类; (2)表示身份证信息的身份证类; (3)存储全部身份证的链表类; (4)测试整个程序的main类