1、面向对象知识小结及答疑一、面向对象基本概念回顾Java 的面向 对象概念, 绝大部分是模拟现实世界中的事物而设计的。继承:根据功能和用途,将类按照 层次关系进行设计。越在下层的类,功能越多越具体;越在上层的类,其“ 权力”越大。类一方面使得学习出现了暂时的困难,但也使得代 码的复用 变得很有意思。系 统提供的标准类,其名字必须要经常查阅 翻译辞典以加深印象。构造方法:对象在新生成的时候必须要执行的方法,名字应该和类名字完全一致。class A int x;A() x = 2;A(int x) this.x = x;void print() System.out.println(“x=” + x
2、);默认情况下,A() 构造方法默 认是会自动生成的,当然里面一点代码都没有(也就是说,你不写的话,系统会帮你写上一个空架子的构造方法)。 构造方法前面必 须是没有“返回类型”的。另外,类中的任何除构造方法之外的其它方法必 须要“ 返回类型”的。方法重载:在类中存在两个同名的方法,但方法的参数类型 不相同。void print(int x) void print(int a) 注意,上面写的这两个方法不能叫重 载的(参数个数、 类型完全相同)方法重写/覆盖:在子类和父中存在两个同名、同参数的方法。访问修饰符:private ,protected,public,(默认)static,final对
3、类中的一个 static 方法来说,它可以直接通 过类名进行调 用,而不需要生成 对象。final 放在一个 变量前面,表示 这是一个常量,只能有一个值。final 放在一个方法前面,表示这是一个不能被覆盖的方法。final 放在一个 类前面,表示这 是一个最终类,它不能被 别的类继承。抽象类:就是在 class 之前加了一个 abstract 的类,这样的类就不能用来生成对象了。因此,通常如果一个类不适合生成 对象的话,我 们就要把这个 类定义成抽象的。或者,如果一个类中包含了没有方法体的方法(也就是抽象方法),这样 的类也必须定义成抽象类。其它 类继承了抽象类的话,就必须补 全这个抽象类中
4、的所有抽象方法的具体代 码。abstract class A int x;abstract void print();接口:在其中只定义抽象方法。接口的目的,是为了强制某些类中必须具有同样的方法。因此,只要类实现了接口,这些类就必须提供接口中方法的具体代 码。因此这些类看起来就有了一些共性了(都包含了接口中规定的方法)。 这是接口的一个 强制规定。此外,接口中的抽象方法必须是 public 的,如果不写上的话,系 统会自定帮我 们加上。习惯上我们还是自己写上 public。interface C public void print();class A implements C int x;pu
5、blic void print() System.out.println(“x=” + x);class B implements C public void print() System.out.println(“hello”);上面的类 A 和类 B 因为实现了接口 C,它们就必须提供接口 C 中规定的方法/函数,否则编译就报错了。这就是接口的一大特点。接口可以 这样使用:C c = new A();c.print(); / 其实是调用 A 类的对象中的 print()方法c = new B();c.print(); / 其实是调用 B 类的对象中的 print()方法可见,接口变量能够指
6、向任何 实现了该接口的类的对象,但只能调用对象中的那些“由接口规定的”方法。对接口变量 c 来讲,无论它指向的是 A 类对象还是 B 类对象,接口变量 c 只知道对象中有 print()方法(也就是接口 C 中规定的方法), 对象中的其它内容,接口变量 c 则一无所知。如同一个 USB 接口,可以插上任何实现了 USB 接口的实际设备,如插上优盘、移动硬盘等等。但是若移动硬盘还有 MP3 播放功能的话,我 们的 USB 接口也不会知道。USB 接口仍会把这种带有 MP3 播放功能的移动硬盘当作普通的移动硬盘来对待。那么在 Java 中又为什么需要接口呢?我们再看上面的这两个 类,改写一下(去掉
7、了implements C 语 句):class A int x;public void print() System.out.println(“x=” + x);class B public void print() System.out.println(“hello”);现在的类 A、B 定义只是少了 implements C,里面的内容和前面一模一样。唯一的不同是,类 A、B 里面的方法名字可以随便更改了(不会受任何 约束):class A int x;public void print222() System.out.println(“x=” + x);class B public v
8、oid show() System.out.println(“-”);我们看到,现在的 A、B 类里面已经乱七八糟了,甚至 B 类中连 print()方法都没有了。但这两个类在编译时没有任何问题,它 们只是没有遵守统一的接口 约定而已。为了更好地理解面向对象这些概念, 请同学们务必仔细琢磨明白提供的那几个程序文件:子类构造方法Abc.java抽象类Abc.java接口Abc.java方法修饰符Abc.java方法重写Abc.java方法重载Abc.java继承Abc.java访问修饰符Abc.java二、异常异常的设计,主要是提供一种集中 处理问题的机制。大家体会一下下面的两个例子。 这个例子
9、是教材上的一个上机练习(教材第 329 页的练习 1),其目的是计算一个从键盘输入数的阶乘(args0就是一个从键盘 上输入的数字字符串)。常 规的做法是这样的:/ 判断是否在命令行中输入了字符串。args 是一个数组变量/ 如果数组大小少于 1 的话,说明使用者没有输入要计算阶乘的那个数if (args.length 9) System.out.println(“提供的参数不是数字“);return;/ 将从键盘输入的数字字符串转换成一个整数,并是否是正数num = Integer.parseInt(args0);if (num = 0) System.out.println(“难道要计算负
10、数的阶 乘?“);return;/ 计算 num 的阶乘,存放在 n 变量中for (i=1; i=num; i+)n = n * i;System.out.println(args0 + “的阶乘是:“ + n);我们发现,如果是按常规做法 ,步骤非常繁琐。而如果使用 try-catch 的话就好很多:try num = Integer.parseInt(args0);if (num = 0)throw new IllegalArgumentException();for (i=1; i=num; i+)n = n * i;System.out.println(args0 + “的阶乘是:“
11、 + n); catch(ArrayIndexOutOfBoundsException e1) System.out.println(“请提供一个数字!“); catch(NumberFormatException e2) System.out.println(“提供的参数不是数字“); catch(IllegalArgumentException e3) System.out.println(“难道要计算负数的阶乘?“);上面的代码是标准的参考代码,使用 try-catch 可以看出代 码的清晰程度(我们将来会大量使用下面的这种处理问题的方式)。如果 try 中的任一行代码在执行时抛出一个异
12、常的话,接下来的 catch 就要开始工作了。如果我们要使得一个普通方法在调用时也有能力抛出异常,我们就需要这样定义我们自己写的方法:void check(int x) throws IllegalArgumentException if (x 0) throw new IllegalArgumentException ();这个方法和普通方法差不多,就是后面跟着一个:throws IllegalArgumentException,表示这个方法有可能会抛出一个 IllegalArgumentException 这样的异常,因此在调用时就必须通过 try-catch 进 行捕捉 处理。现在可以这
13、样调用上面的 check()方法了:try num = Integer.parseInt(args0);check(num);for (i=1; i=num; i+)n = n * i;System.out.println(args0 + “的阶乘是:“ + n); catch(ArrayIndexOutOfBoundsException e1) System.out.println(“请提供一个数字!“); catch(NumberFormatException e2) System.out.println(“提供的参数不是数字“); catch(IllegalArgumentExcepti
14、on e3) System.out.println(“难道要计算负数的阶乘?“);你可以想像一下,系统中的 Integer.parseInt()方法就是按照类似我们上面的做法实现的。现在有同学就会问了,我 们怎么知道什么时候要抛出什么 样的异常呢?其实,上面我 们只是选择了一个标准的异常类 IllegalArgumentException(非法参数异常。Illegal 是“非法”的意思,Argument 是“参数” 的意思,Exception 是“异常”的意思)。我们在课堂中给大家看了一些从 Exception 继承而来的标准异常类,如果 这些类可以使用的话,我 们就可以直接选择其中一个适合我
15、们处理情况的类来使用,就像我 们上面做的一 样。否 则,就要自定义异常类了(必须从 Exception 继承):class FushuException extends ExceptionFushuException() / 构造方法super(“负数异常“);void check(int x) throws FushuException if (x 0) throw new FushuException ();或者在 try 中直接这样做:if (x0) throw new FushuException ();大家要注意,try-catch-finally 异常处理只是提供了一种更方便的“
16、处理问题”的方式。其实你完全可以在执行代码时用 if 去做一大堆判断,如同前面的代 码。这样做的思路不怎样清晰,也就是说“ 执行的操作”和“问题处理”全部揉合在一起,不便于我们的观察分析。因此,大家必须先掌握异常处理的这种手段,以后我 们会不断地接触到其它地方是怎 样使用 try-catch 做异常 处 理的。通过不断的接触、观察、分析、总结,从而我们就能模仿着写自己的异常处理了。另外,Integer 是一个独立的 类 ,表示“整数类” 的意思,其中包含了一个 int 值。但它和 int又是有区别的,int 只是单纯的一个整数,没有其它任何东西;Integer 是一个类(想像一下结构体),里面
17、包含了属性字段变 量和方法/ 函数。以下是从 JDK 文档中摘录出来的对 Integer类的描述:public final class Integer extends Number implements ComparableInteger 类在对象中包装了一个基本类型 int 的值。Integer 类型的对象包含一个 int 类型的字段。 此外,该类提供了多个方法,能在 int 类型和 String 类型之间互相转换,还提供了处理 int 类型时非常有用的其他一些常量和方法。三、Java 语言学习指导和答疑1、Java 是由许许 多多的“ 类”构成的,这些类有的是抽象类,有的类又实现了一些接口
18、(遵从了接口中的方法/函数约 定)。但基本上是一个 类就负责 “一块事情”。 类名字是有规律的,类名单词的第一个字母总是大写的。2、我们经常接触的类是必 须要知道的。也就是 说,类名单词只能记下来。当然,记下来并没有那么痛苦,我们可以将 经常接触到的类名写在一个本子上,经常翻阅。比如仿照下面的方式:String 字符串类,包含了许多处理字符串的方法Integer 整数类,包含了 许多处理整数的方法至于类里面包含的方法的具体使用,我们完全可以通 过 JDK 文档查阅到。3、Java 中显得复 杂的内容,就是类。我 们将来会不断地遇到很多的类。初学者往往会 觉得 Java 的难就 难在类太多,很乱
19、。 这在开始是属于正常现 象。随着我 们学习的推进,大家会慢慢理清这些内容的。4、几乎所有同学课题上都不做笔 记。 难怪同学们在课堂上听起来比 较舒服,课后就什么都不懂了!笔记怎么做?碰到难的问题或者老师一再强调的地方, 记录一下要点,以便课后查阅。不是不分重点一股脑儿什么都记录下来,也不是做 课堂录音。答疑 1:为什么在定义类时,习惯 上将属性字段写成 private 的,而将方法写成 public 的?class Bear int legs;public void show() System.out.println(“本熊有 “ + legs + “ 条腿“) ;class Test pu
20、blic static void main(String args) Bear b1, b2;b1 = new Bear();b1.legs = 4;b1.show();System.out.println(“这只熊确实有“ + b1.legs + “条腿“);b2 = new Bear();b2.legs = -2;b2.show();System.out.println(“这只熊确实有“ + b2.legs + “条腿“);我们可以看到上面的 b2.legs 赋值明显是不合逻辑的,腿的数量不可能是负数。那么有没有什么办法来控制这种情况?我们知道,因 为 legs 字段变量是在类 Bear
21、中被管辖的,因此最好让 Bear 类来负责这件事情。因此改变成现在这样:class Bear int legs;public void show() System.out.println(“本熊有 “ + legs + “ 条腿“) ;public void setLegs(int legs) if (legs 0)this.legs = 4;elsethis.legs = legs;我们希望编程人员使用 setLegs()方法来设置熊的腿数量:b1.setLegs(3);b1.show();System.out.println(“这只熊确实有“ + b1.legs + “条腿“);b2.se
22、tLegs(-4);b2.show();System.out.println(“这只熊确实有“ + b2.legs + “条腿“);现在的情况比前面好多了,至少不会出现腿的数量是负数的情况。但总有那么一些不安分的编程人员就是要这样写(或者会不小心这样):b2.legs = -4;也就是说,尽管我们提供了一个 设置熊腿数量的方法 setLegs(),但仍无法杜绝将熊腿的数量设置为负数的情况。现在,我们在 int legs 前面增加一个 private 就可以彻底解决这个问题:class Bear private int legs;public void show() System.out.pri
23、ntln(“本熊有 “ + legs + “ 条腿“) ;public void setLegs(int legs) if (legs 0)this.legs = 4;elsethis.legs = legs;现在:b1.legs = 4;b2.legs = -2;都是不允许做的(不可以在类对象的外面直接访问私有的东西),故而要设置熊的腿数量,就只能调用 b1.setLegs(4)和 b2.setLegs(-2)了,并且执行后的结果就保证了腿数为正数。不过我们又发现了另一个不足,就是下面 这条语句出 现编译错误:System.out.println(“这只熊确实有“ + b1.legs + “
24、条腿“);System.out.println(“这只熊确实有“ + b2.legs + “条腿“);原因在于,legs 字段变量是 Bear 类中私有的东西,不能在类对象以外访问。既不能在类的对象之外修改,也不能在类 的对象之外读取。所以,为了使得外面的代码仍可以访问到熊的腿数,我们需要再增加一个 读取 legs 值的方法:class Bear private int legs;public void show() System.out.println(“本熊有 “ + legs + “ 条腿“) ;public void setLegs(int legs) if (legs 0)this.
25、legs = 4;elsethis.legs = legs;public int getLegs() return legs;这样将上面的测试代码再重写一遍就好了:b1.setLegs(3);b1.show();System.out.println(“这只熊确实有“ + b1.getLegs() + “条腿“);b2.setLegs(-4);b2.show();System.out.println(“这只熊确实有“ + b2. getLegs() + “条腿“);所以,以后我们在写自己的类时 ,大多数情况下都是将属性字段变量设置为 private 的,然后提供一系列 set和 get方法来修改
26、或读取那些 private 属性字段的值。这种方法命名的特点是,set 或 get 后面跟着变量名字,且 变量名字的第一个字母改为大写。答疑 2:所有类都是继承自 Object 类的。也就是说,Object 类是 Java 语言中的所有类的鼻祖!(你不写的话,系统会自定帮你添上)class A 等价于 class A extends Object 答疑 3:父类的引用变量,可以指向其子 类的对象。class A int x = 1;void print() System.out.println(“x=“ + x);class B extends A int y = 2;void show()
27、System.out.println(“y=“ + y);上面 A 是 B 的父类。我们可以生成两个 对象看看:A a = new A();a.print();B b = new B();b.print();b.show();现在这样:a = b; / b 的辈份比 a 小, “小” 的直接覆值给“大 ”的是允许的a.print(); / 调用的是 B 类对象的 print()方法,因 为 a 指向的是 B 类对象上面的这个 a,其 实就相当于是一个指针变量,它指向的 实际对 象是 B 类对象。当a.print();执行时,执行的实质上就是 B 类对象中的 print()方法。现在有一个问题,
28、既然 a 指向的实际上是一个 B 类对象,而我们又知道,B 类对象中不仅有从类 A 继承过来的东西( x 和 print()方法),还有在类 B 中新扩充的东西(y 和 print()方法)。那么我们能不能这样呢:a.y = 2 ; / a 已经不知道 B 类对象中有 y 属性字段a.show() ; / a 也不知道 B 类对象中有 show()方法答案是否定的,上面两句代码 是错误的。那么 应该怎么理解 这个问题呢?即:a = b;我们知道,a 是一个父 类变量,b 是一个子类变量。当 执行 a=b;时,我们可以认为是“一个子类对象 b 被当成是一个父类对象 a”(儿子冒充为老子)。现在假
29、定老子只会弹钢琴,则儿子也一定会弹钢琴(从老子那里继承而来), 还会编程序。此时,因为儿子已经冒充是一个老子了,对外人来说,一定也会以为这个“假冒的老子”只会弹钢 琴了,而不会知道他 还会编程序。Java 语言 规定,当父 类引用变量 a 指向了一个子类 B 的对象时,通过变量 a 只能调用这个子类对象中的“ 从父类 A 中继承过来的东西”。所以,我们只能使用 a.x 和调用 a.print()方a BbAb Ba法了,而不能使用 a.y 和调用 a.show()方法。也就是说,当父类引用变量指向子 类对象时,我 们只能通过 父类引用变量执行子类对象中的“那些从父类继承而来的东西”。换句话说,
30、父 类引用变 量已经看不到子类对象中扩充的新内容了(新内容是指在 B 类 中定义的属性字段、方法等)。再例如:A a2 = new B(); / a2 指向了一个 B 类对象a2.x = 3;a2.print();我们可以通过下面的方式还原变量 a2 指向的 B 类对象:B b2 = (B) a2; / 还原了 B 类对象的庐山真面目,因为 a2 指向的是 B 类对象b2.x = 1;b2.y = 2;b2.print();b2.show();第一句需要进行强制类型转换。因 为从表面上看, a2 的辈 份是比 b2 要大的。当一个 “大”的东西赋值给一个“小” 的东西 时,是需要进行强制类型转换的。我们已经知道,既然 Java 语言中的 Object 类是任何类的父类,因此我 们总可以这样:Object obj;obj = new B(); / 让 obj 指向了一个 B 类对象B b3 = (B) obj; / 还原 B 类对象的庐山真面目冒充,化装还原