第3章面向对象设计基础——抽象和封装 3.1实验目的  理解对象和类的基本概念,并初步理解面向对象设计原则中的抽象、封装,掌握get方法和set方法的设计,掌握toString()方法和equals()方法的设计技巧,掌握方法重载的概念和实现技巧。  理解Java中数组的概念和使用技巧,并掌握基本类型变量和引用变量的区别。 3.2相关知识 面向对象程序设计是使用类和对象将现实世界中真实的实体或抽象的概念在程序中建立起相应的模型,这一过程本身就是抽象,是人类特有的一种不断训练和强化的能力。还要重点理解类和对象的关系,类是创建对象的代码模板,对象是用类创建的实例。在设计类时,要采用封装的思想,使用private关键字将数据和方法对外隐藏,用public关键字提供对象和外部进行信息交换的接口,并在这些接口方法中,提供合理的代码设计用来过滤传入和传出数据,也就是说封装不是简单用private关键字私有化某些成员,而是保证被封装的对象是一个有机的整体,不能因为传入“坏”的数据导致对象出问题。在Java中数组被看成是对象,它有一个属性length用来指明此数组的元素的个数,通过下标使用数组元素。 视频讲解 3.3实验内容 3.3.1验证实验 (1) 理解抽象和封装。在第一个验证实验中,抽象一个简单的Person类,只抽象了“人”的三个最基本的属性: 年龄(age)、姓名(name)和性别(sex)。对它们进行了封装,并提供了相应的get方法和set方法,同时在类中也提供了两个构造方法,并给出了equals()和toString()方法。 ① 用记事本或Ultraedit输入以下程序并以Person.java存盘。 ② 用javac编译,用java执行,然后填空。 public class Person{ private int age=0; private String name="noname"; private char sex='M'; public Person(){} public Person(String n,int a,char s){ name=n; if(a>=0&&a<140) age=a; //数据过滤 else age=0; if(s=='M') sex=s; //数据过滤 else sex='F'; } public void introduceme() { System.out.println("my name is: "+name+"\tmy age is: "+age); if(sex=='M') System.out.println("I am man!"); else System.out.println("I am woman!"); } public String getName(){return name;} public void setName(String n){name=n;} public int getAge(){return age;} public void setAge(int a){//注意数据过滤 if(a>=0&&a<140) age=a; else age=0; } public char getSex(){return sex;} public void setSex(char s){//注意数据过滤 if(s=='M') sex='M'; else sex='F'; } public boolean equals(Person a){ if(this.name.equals(a.name)&&this.age==a.age &&this.sex==a.sex) return true; else return false; } public String toString(){ return name+","+sex+","+age; } } class PersonTest{ public static void main(String args[]) { Person p1,p2; p1=new Person("张三",28, 'M'); p2=new Person (); p2.setName("陈红");p2.setAge(38);p2.setSex('F'); p1.introduceme(); p2.introduceme(); } } 封装的意思是。 p1=new Person("张三",28, 'M'); 这条语句的含义和作用是。 p2.setName("陈红");的用处是。 (2) 数组测试。数组是将相同类型的数据放在连续的存储区域,通过数组名和下标来使用每一个数组元素,注意Java中的数组名仅是一个引用变量名,并且Java认为数组是一个对象,有一个length属性用来表示数组元素个数,通过下标使用数组元素。请输入以下代码并以ArrayTest.java为文件名保存,然后编译运行,并回答问题。 public class ArrayTest { public static void main(String[] args){ int[] a; Person[] b; a=new int[10]; b=new Person[3]; for(int i=0;i<10;i++){ a[i]=(int)(100*Math.random()); } b[0]=new Person("张三",28,'M'); b[1]=new Person("李四",20,'M'); b[2]=new Person(); b[2].setName("葛优"); b[2].setAge(46); b[2].setSex('F'); for(int i=0;i<10;i++) { System.out.println("a["+i+"]="+a[i]); } System.out.println(b[0]+"\n"+b[1]+"\n"+b[2]); System.out.println("a中元素个数: "+a.length); System.out.println("b中元素个数: "+b.length); } } 试解释Java中数组和C语言中数组的区别。 试解释b=new Person[3];语句和b[0]=new Person("张三",28,'M');语句的作用,以及它们之间的区别和关系。 (3) Java方法的参数传递用法。Java方法的参数传递是传值操作。对于基本数据类型(如int、char类型)变量作为参数,方法内对参数的操作实质是对参数复制变量的操作,不会改变原变量的值; 对于引用类型(如数组、字符串)变量作为参数,方法内对该变量的操作是对引用变量所指向对象的操作,会改变原对象的数据。下面示例演示了方法参数调用的传递情况。 public class MethodParameter { public static void main(String[] args) { int a=6; char[] str = new char[] { 'H', 'e', 'l', 'l', 'o' }; StringBuffer sb = new StringBuffer("TOM"); changeAddr(str, sb); System.out.println(str); System.out.println(sb.toString()); changeValue(a, str, sb); System.out.println(a); System.out.println(str); System.out.println(sb.toString()); } private static void changeAddr( char[] c, StringBuffer sb) { c = new char[] { 'Y', 'e', 'l', 'l', 'o' }; sb = new StringBuffer("SawYer"); } private static void changeValue(int a, char[] c, StringBuffer sb) { a=8; c[0] = 'Y'; sb.append(" Sawya"); } } 程序的运行结果是。 (4) 重载方法演示。在同一个类中,有多个同名的方法,但方法参数列表不同,执行代码也不同,称为方法重载。请输入以下程序代码进行分析,学习方法重载。 public class DemoOverloading { public void disp(char c){ System.out.println(c); } public void disp(char c, int num){ for(int i=1;i<=num;i++) System.out.print(c); System.out.println(); } public void disp(String s){ System.out.println(s.toUpperCase().charAt(1)); } public void disp(String s,int num) { for(int i=1;i<=num;i++) System.out.print(s+" "); System.out.println(); } public static void main(String[] args){ DemoOverloading obj=new DemoOverloading(); obj.disp('*'); obj.disp('=',10); obj.disp("abcdefg"); obj.disp("abcdefg",10); } } 试解释方法重载的实现机制,即编译器是如何识别不同的方法。 3.3.2填空实验 (1) 理解抽象和封装。把主教材3.2.1节中的屏幕抽象和矩形抽象示例实验一下: 假设要在抽象屏幕上用“*”打印矩形,可以把此矩形看成一个对象,用面向对象的思维来进行分析和抽象,所有的矩形都有宽(w)和高(h),并且在屏幕上有一个位置,而位置是由形如(x、y)的坐标标识出来的,所以最简单的抽象就是通过(w,h,x,y)来定义一个矩形类(Rectangle),然后提供一个printme()方法在抽象屏幕上打印出这个矩形。 //Rectangle.java public class Rectangle { int x,y,w,h; Rectangle() { ; //调用另一个构造方法传递参数(0,0,1,1) } public Rectangle(int x,int y,int w,int h) { this.x=x; this.y=y; this.w=w; this.h=h; } public void printme(Screen myscreen) { myscreen.setY(y); for(int i=1;i<=h;i++){ myscreen.setX(x); myscreen.repeat('*',w); myscreen.println(); } } } //Screen.java public class Screen { int SCREEN_WIDTH; int SCREEN_HEIGHT; int x,y; char[][] data; int getX(){ return x; } public void setX(int x){ this.x = x; } public int getY(){ return y; } public void setY(int y){ this.y = y; } public Screen(){ SCREEN_HEIGHT=50; SCREEN_WIDTH=80; data=new char[SCREEN_HEIGHT][SCREEN_WIDTH]; } public Screen(int r,int c) { SCREEN_HEIGHT=r; SCREEN_WIDTH=c; data=new char[SCREEN_HEIGHT][SCREEN_WIDTH]; } public void cls() { for(int i=0;i= 1 && r <= 1000) SCREEN_HEIGHT = r; else SCREEN_HEIGHT = 50; if (c >= 1 && c <= 1000) SCREEN_WIDTH = c; else SCREEN_WIDTH = 80; data = new char[SCREEN_HEIGHT][SCREEN_WIDTH]; } public void cls() { for (int i = 0; i < SCREEN_HEIGHT; i++){ for(int j = 0; j < SCREEN_WIDTH; j++) { data[i][j] = ' '; } } } public void display(){ for (int i = 0; i < SCREEN_HEIGHT; i++){ for (int j = 0; j < SCREEN_WIDTH; j++){ System.out.print(data[i][j]); } System.out.println(); } } public void repeat(char ch, int m){ for (int i = 1; i <= m; i++) print(ch); } public void print(char ch){ if (y < SCREEN_HEIGHT && x < SCREEN_WIDTH){ data[y][x] = ch; x++; if (x == SCREEN_WIDTH){ y++; if (y == SCREEN_HEIGHT) { scroll(); y = SCREEN_HEIGHT - 1; } x = 0; } } else { System.out.println("错误: 超出屏幕了!"); } } public void println(){ y++; if (y == SCREEN_HEIGHT){ scroll(); y = SCREEN_HEIGHT - 1; } x = 0; } public void scroll(){ for (int i = 0; i < data.length - 1; i++){ data[i] = data[i + 1]; } data[data.length - 1] = new char[SCREEN_WIDTH]; } } 修改后,屏幕(Screen)类对象的内部数据外部就无法修改了,并对相应方法的代码也做了过滤处理,非法数据无法进入。读者可以用前面例子中的测试类进行测试,看看效果。 请读者将矩形类Rectangle进行封装,然后再使用下面的测试程序进行测试,看看封装是否成功。 //TestEnCapsulation.java public class TestEnCapsulation { public static void main(String[] args){ Screen myscreen=new Screen(); myscreen.cls(); Rectangle rc1=new Rectangle(0,0,6,5); rc1.h=-3;//试图直接修改数据,无法通过编译 rc1.x=10; //试图直接修改数据,无法通过编译 rc1.printme(myscreen); Rectangle rc2=new Rectangle(32,4,5,7); rc2.w=10; //试图直接修改数据,无法通过编译 rc2.printme(myscreen); myscreen.data[5][33]=’中’;//试图直接修改数据 myscreen.display(); } } 将封装好的Rectangle.java给实验老师检查。 (2) 数组使用。用面向对象方法实现筛法求素数,从面向对象的视角看,筛法求素数需要下列器件: 一个数字产生器; 能够逐一输出需要判断的数据; 另一个是筛子: 用于数据的过滤。筛子中有一个过滤器,用于存储素数。每次过滤时,就是判断数据是否被过滤器中的所有数据整除,若无法过滤掉,则将该数据保留在过滤器中,请填空以完成程序。 class Shiyan3_2_2 { public static void main(String[] args){ int n=100; ;//创建Sieve类的对象 s.executeFilter(n); System.out.println("小于"+n+"的素数有: "); s.outFilter(); } } class Counter{//数字产生器 private int value;//数字产生器的初值 Counter(int val){value=val;} public int getValue(){return value;} public void next(){value++;}//产生下一个数字 } class Sieve{ //筛子 final int Max=100;//设定过滤器的最大值 private int filterCount=0; private int[] f; //存储过滤器数据的数组 public Sieve(){f=new int[Max];filterCount=0;} public void executeFilter(int n){//执行过滤,产生2~n素数 Counter c=new Counter(2); for(;c.getValue()