第一章 起步
Java API:是预先建立的组件大型集合,他们提供了许多有用的功能。
applet:是一种遵守某种约定,可以在支持java的浏览器中运行的程序。
servlet:可以看作是在服务器端运行的applet。
servlet和applet的相似之处在于他们都是应用程序的运行时扩展。
applet利用了允许动态(on the fly)装载新类,而不必重新编译整个程序的特性。
实际上,每个applet都必须定义一个Applet类的子类。
每个applet都应该至少实现以下方法之一:init,start,paint。与应用程序不同,applet不用实现main方法。
import java.applet.Applet;
import java.awt.Graphics;
public class HelloWorld extends Applet {
public void paint(Graphics g) {
g.drawString("Hello world!", 50, 25);
}
}
System.out结构是system类中的out变量的完整名称,out是一个类变量。当System类被装载进应用程序时,它实例化PrintStream类并为类变量out分配一个新的PrintStream对象,既然有了一个类的实例,那么就能够调用它的实例方法(instance method)。
Java.lang包中的所有内容会自动被导入到每个用java编程语言写的程序中。
一个包中的所有类都可以互相引用,而无需前缀。
第二章 面向对象的编程概念
对象是变量和相关方法的软件组合。
对象的变量组成了对象的核心。方法环绕在变量的周围,并将变量对于程序中其他对象隐藏起来。将对象的变量置于它的方法的保护之下,这叫做“封装”。
子类可以覆盖(override)继承的方法并为这些方法提供特殊化的实现。
使用接口(interface)定义一个行为协议,类层次结构中任何地方的任何类都可以实现这个行为协议。
当对象A希望对象B执行B的一个方法时,对象A就发送一个消息给对象B。此信息作为参数随消息一起传递。(就是方法调用。)
消息由3个部分组成:
1。消息发送到的对象。
2。要执行的方法的名称。
3。需要由此方法传递的任何参数。
第三章 语言基础
参数是方法或构造器的正式自变量,用于向方法和构造器传递值,参数 的范围是它所在的方法或者构造器的全部代码。
异常处理器参数与参数类似,但他们是异常处理器的自变量,而不是方法或者构造器的。异常处理器参数的作用范围是catch语句后面的{}之间的代码块。
在final变量初始化后,它的值不能被修改。相当于其他编程语言的常量。
方法参数也是变量。
&&和||、 &和|详细解释:
操作数1 && 操作数2 操作数1 || 操作数2 操作数1 & 操作数2 操作数1 | 操作数2
&&操作符:如果2个操作数都为true时,返回true。有条件的运算操作数2。
所以如果操作符左边是false,就无需运算右边的操作数就可以判断出&&的返回值。在这样的情况下,解释器将不运算右边的操作数。(如果右边的操作数会导致副作用,比如进行流的读取,那么这种节省是很有意义的,可以提高程序性能。)
||操作符:如果操作数1或者操作数2是true,那么返回true。有条件的运算操作数2。
&操作符:如果操作数1和操作数2都是布尔值,而且都等于true,那么返回true;总是运算操作数1和操作数2。
如果2个操作数都是数字,那么执行 位与(AND)操作。
|操作符:如果操作数1和操作数2都是布尔值,而且其中至少有一个等于true,那么返回true;总是运算操作数1和操作数2。
如果2个操作数都是数字,那么执行 位或(OR)操作。
注意:当2个操作数都是布尔值时,&&与&执行一样的操作。但是&总是运算它的2个操作数并在他们都是true时返回true。
当2个操作数都是布尔值时,||与|执行一样的操作。但是|总是运算它的2个操作数并在其中一个操作数为true时返回true。
当操作数为数字时,&和|执行为操作。
一般不应该对浮点数使用相等操作符==,因为浮点数本质上很难精确匹配。作为替代,应该检查数字是否接近0。
一个if可以与任意数量的else if语句配合,但是只能有一个else语句。
if语句可以用于根据某范围的值或条件进行判断,而switch语句只能根据单个整数值进行判断;另外给每个case语句提供的值必须是唯一的。
一般情况下在每个case后面都要加上break语句,但是如果多个case都执行同一个语句的话,就可以把break放在最后一个case语句后面。例如
Swtich(expression){
Case 1:
Case 2:
Case 3:
System.out.println("XXXX");
Break;
Case 4:
XX;
Break;
Case 5:
YY;
Break;
Default:
ZZ;
Break;
}
//可以在switch语句的末尾使用break语句,来处理未被case语句明确处理的值。
异常处理语句:
Try
{ }
Catch(exception e)
{ }
Finally{}
finally语句必须与一个try语句相关联,它标识的语句块无论try块是否发生错误都会被执行。
final与finally的区别:
final:定义一个变量后,该对象的值不能改变。
finally:用在try,catch语句后面。它标识的语句块无论try块是否发生错误都会被执行。
无标签的break语句终止封闭它的switch语句,而控制流程转到switch语句后的第一个语句。也可以使用break语句的无标签形式终止for,while或do-while循环。
有标签的形式终止由break语句中指定的标签标识的外层语句。
continue语句用于跳过for,while,do-while循环的当前迭代。
无标签形式跳到最内层循环体的末尾,并计算控制循环的布尔表达式,这基本就是跳过循环的本次迭代的余下部分。
有标签形式跳过有给定标签的外层循环的当前迭代。
return语句用于退出当前方法。流程控制返回到原方法调用后面的语句。return语句有2种形式:1、返回值;2、不返回值。
return语句实现了java的封装性。
当方法被声明为void时,使用不返回值的renturn形式。
第4章 对象基础和简单数据对象
Number类是java平台中所有数字类的超类,它的子类包括Float,Integer。
new操作符通过为新对象分配内层来实例化一个类。(new操作符返回一个对象引用)
Int height = new Rectangle().height;
此语句执行后,程序不再拥有对刚创建的Rectangle对象的引用,因为程序没有将此引用存储在变量中。
不建议你通过其他对象或类直接操作一个对象的变量,因为这很可能将变量设置为无效的值。
在某个对象上调用方法就是向此对象发送一条信息。
处理字符数据的类:
1、Character类:可以包含单个字符值。一旦创建了Character对象,它包括的值就不能改变。
2、String类:用于处理由多个字符组成的不可改变的数据。一旦创建了String对象,它包括的值就不能改变。
3、StringBuffer类:用于存储和操作多个字符组成的可改变的数据。
一般都是StringBuffer dest = new StringBufffer(len);//这样分配内存,这样效率比较高。
因为字符串是常量,所以使用它们必使用字符串缓冲区更高效,而且字符串可以被共享。
String类用于其值不能改变的字符串。(String类的某些方法看起来可以修改字符串,但是实际上字符串不能被修改,所以这些方法实际上是创建并返回包含结果的第2个字符串。)
StringBuffer类用于将被改变的字符串。
String s = "abc";
String ss=new String("bbb");
尽量减少内存分配的操作的次数。这样效率就会更高。
可以使用charAt访问器方法,得到字符串或字符串缓冲区中某索引位置上的字符。
String s = "abc";
char a = s.charAt(0);//a='a';
这个char是基本数据类型,不是类。
String类提供2个访问器方法:indexOf和lastIndexOf方法,他们返回特定的字符或字符串在字符串中的位置。
int i=s.indexOf('c');
//i=2
int j=s.lastIndexOf('c');
//j=2
StringBuffer类不支持indexOf和lastIndexOf方法,如果需要在字符串缓冲区上使用这些方法,那么先使用toString方法将字符串缓冲区转换为字符串。
注意:
String s="abc"; 编译器为它遇到的每个字符串直接值创建一个新的String对象,所以可以用一个字符串直接值初始化一个字符串。
但是不建议以下这样做:
String s = new String("abc");//这样会创建2个相同的字符串。
除了数字类,java平台还包含Boolean,Character和Void类。它们与数字类一起统称为类型包装器类(type-wrapper class)。
BigInteger和BigDecimal扩展了原始数据类型,他们允许任意精度的数字(这些数字可能不适合任何原始数据类型。) 注意上面其他类在java.lang包中。而BigInteger和BigDecimal在java.math包中。
Float f1=new Float(1.0);
Float f2=new Float(1.0);
Double d1=new Double(1.0);
System.out.println("f1 equals d1 is " + f1.equals(d1));//输出 f1 equals d1 is false
System.out.println("f1 equals f2 is " + f1.equals(f2));//输出 f1 equals f2 is true
因为他们的数据类型不同,尽管他们包含的值在数字方面都等于1。
所以基本数据类型对象的equals方法除了比较数值是否相同,还比较数据类型是否相同。只有都相同的时候才为true。
将字符串转换为数字(可以用数字类型包装器类实现):
数字类型包装器类(Byte,Integer,Double,Float,Long,Short)都提供了一个称为ValueOf的类方法,它将字符串转换为此类型的对象。
String sss="1.5";
String ssss="9.5";
System.out.println(sss);
float f3=Float.valueOf(sss).floatValue();
float f4=Float.valueOf(ssss).floatValue();
System.out.println(f3);//输出1.5
System.out.println(f4);//输出9.5
System.out.println("f3 + f4 = " + (f3 + f4));//输出 f3 + f4 = 11.0
将数字转换为字符串:
所有类都从Object类继承了一个称为toString的方法。类型包装器类覆盖此方法以提供数字对象包含的值的字符串表示。
String sx=Double.toString(95.1);
每个数字类都有一个称为toString的实例方法,可以在此类型的实例上调用它。
对数字进行格式化
我们可以使用NumberFormat类和它的子类DecimalFormat类对原始类型的数字(比如double)和它们对应的包装器对象(比如Double)进行格式化。
NumberFormat类和DecimalFormat类在java.text包中。
Double dd=new Double(132154.12);
NumberFormat nu=NumberFormat.getInstance();
String sdd=nu.format(dd);
System.out.println(sdd);//输出132,154.12
下面的代码创建一个数字格式化对象,按照法国进行格式化:
import java.text.NumberFormat;
import java.util.Locale;
System.out.println(NumberFormat.getNumberInstance(Locale.FRANCE));
//输出java.text.DecimalFormat@674dc
格式化货币:
Double ddd=new Double(98413513.12);
NumberFormat nucc=NumberFormat.getCurrencyInstance();
String xxd=nucc.format(ddd);
System.out.println(xxd);
//输出¥98,413,513.12
格式化百分比:
System.out.println();
Double ddd_=new Double(0.85);
NumberFormat nucc_=NumberFormat.getPercentInstance();
String xxd_=nucc_.format(ddd_);
System.out.println(xxd_);
//输出85%
其他格式化,见书P105
比较数据大小(使用Math类):
double dxd1=45.786;
double dxd2=78.4;
System.out.println(Math.min(dxd1,dxd2));//45.786
System.out.println(Math.max(dxd1,dxd2));//78.4
Math类中的方法是类方法,所以直接从Math类调用他们。
Math.random()方法返回一个0.0到1.0之间的伪随即数,此范围包括0.0,但不包括1.0。
for(int ii=0;ii<10;ii++)
{
System.out.println(Math.random());//可以输出0.0到1.0之间的数
System.out.println(Math.random()*10);//可以输出1.0到10.1之间的数
}
如果想得到单个数字,那么可以使用Math.random。
如果想得到一系列随机数,那么应该创建java.util.Random的一个实例,并且在此对象上调用方法来产生数字。
第五章 类和继承
声明类:
public:声明此类可以被任何其他类使用。
abstract:声明不能被实例化。
final:声明不能被子类化。
class:一个类声明。
Extends super:子类。
implements:实现接口。
声明成员变量:
accessLevel:通过指定4个访问级别(public,protected,package,private)之一,可以控制那些其他类可以访问成员变量。
static:声明一个类变量,而不是成员变量。
final:成员不能改变。
transient:成员变量不应该被串行化。
volatile:防止编译器在成员上执行某种优化。
type:成员变量的类型。
name:成员变量的名称。
常量值的名称以大写字母拼写。
定义方法:
accessLevel:通过指定4个访问级别(public,protected,package,private)之一,可以控制那些其他类可以访问方法。
static:是一个类方法,而不是实例方法。
abstract:抽象方法没有实现,而且必须属于抽象类。
final:方法不能被子类覆盖。
native:如果有一个用其他语言编写的有价值的函数库,那么希望利用它,并用java编写的程序使用这些函数,那么可以使用native。用另外一种语言实现的方法被称为本机方法,并使用native关键字声明。
synchronized:并发运行的线程常常调用在相同数据上进行操作的方法,将这些方法标上synchronized关键字可以确保以线程安全的方式访问信息。
命名方法应该是大小写混合的,第一个字母小写,每个内部单词的首字母大写。
在以下3种情况下,方法可能与类或子类中的其他方法同名:覆盖方法,隐藏方法,名称重载。
如果子类中的方法与超类中的方法有相同的标记和返回类型,那么它就覆盖(override)或者隐藏(hide)了超类的方法。
如果同一类中的多个方法有不同的参数列表,那么他们可以具有相同的名称,这叫重载(name overloading)。
重载方法由传递给他们的参数的数量和类型来区分。
构造器不是方法,所以他们没有返回类型。构造器由new操作符调用,new操作符自动返回新创建的对象。不能在构造器中使用return语句。
如果一个类提供了带有参数的构造器,还必须提供一个没有参数的构造器。
构造器有以下4种访问说明符。
public:
private:只有这个类可以使用这个构造器。如果一个类中所有构造器都是私有的,那么类可能包含用于创建和创建和初始化此类的实例的公共类方法(称为工厂方法)。其他类可以使用这些工厂方法创建这个类的实例。
protected:这个类 的子类和相同包中的类可以使用这个构造器。
无说明符:给予包访问权限。
java不允许将方法传递给方法。但是可以将对象传递给方法,然后调用此对象的方法。
传递给方法的参数可以与类的成员变量同名。如果这种情况,那么参数被称为隐藏(hide)成员变量。
在构造器中,还可以使用this关键字调用相同类中的另一个构造器,这种做法被称为显式构造器调用(explicit constructor invocation)。
public class Rectangle {
private int x,y;
private int width,heiht;
public Rectangle()
{
this(0,0,0,0);
}
public Rectangle(int width,int heiht)
{
this(0,0,width,heiht);
}
public Rectangle(int x,int y,int width,int heiht)
{
this.x=x;
this.y=y;
this.width=width;
this.heiht=heiht;
}
}
访问权限
说明符
类
包
子类
所有类
Private
Ok
-
-
-
无说明符
Ok
Ok
-
-
Protected
Ok
Ok
Ok
-
Public
Ok
Ok
Ok
ok
访问级别在2个方面有影响:
1、当你使用来自另一个源的类(比如java平台中的类)时访问级别决定你的类可以使用这些类中的哪些成员。
2、在编写类时,相应决定类中的每个成员变量和方法应该具有什么访问级别。(p129)
成员的访问级别决定哪些类可以访问此成员,而不是决定哪些实例可以访问此成员。所以相同类的实例可以访问另一个实例的私有成员。
eg:
package One;
public class Alpha {
//member variables
private int iamprivate = 1;
int iampackage = 2;
//package access
protected int iamprotected = 3;
Public int iampublic = 4;
//methods
Private void privateMethod() {
System.out.println("iamprivate Method");
}
/**/void packageMethod() { //package access
System.out.println("iampackage Method");
}
protected void protectedMethod() {
System.out.println("iamprotected Method");
}
Public void publicMethod() {
System.out.println("iampublic Method");
}
public static void main(String[] args) {
Alpha a = new Alpha();
a.privateMethod();
//legal
a.packageMethod();
//legal
a.protectedMethod(); //legal
a.publicMethod();
//legal
System.out.println("iamprivate: " + a.iamprivate);
//legal
System.out.println("iampackage: " + a.iampackage);
//legal
System.out.println("iamprotected: " + a.iamprotected); //legal
System.out.println("iampublic: " + a.iampublic);
//legal
}
public boolean isEqualTo(Alpha anotherAlpha)
{
if (this.iamprivate ==anotherAlpha.iamprivate)
{return true;}
else
{return false;}
}
}
package One;
public class DeltaOne {
public static void main(String[] args) {
Alpha a = new Alpha();
//a.privateMethod();
//illegal
a.packageMethod(); //legal
a.protectedMethod(); //legal
a.publicMethod();
//legal
//System.out.println("iamprivate: " + a.iamprivate);//illegal
System.out.println("iampackage: " + a.iampackage);//legal
System.out.println("iamprotected: " + a.iamprotected); //legal
System.out.println("iampublic: " + a.iampublic);//legal
System.out.println();
System.out.println(a.isEqualTo(a));
}
}
保护访问级别允许子类通过对象引用访问受保护的成员,但是此对象引用的类型必须与这个类或者子类相同。
eg:
package Two;
import One.*;
public class AlphaTwo extends Alpha {
public static void main(String[] args) {
Alpha a = new Alpha();
//a.privateMethod();
//illegal
//a.packageMethod();
//illegal
//a.protectedMethod();
//illegal
a.publicMethod();
//legal
//System.out.println("iamprivate: " + a.iamprivate);
//illegal
//System.out.println("iampackage: " + a.iampackage);
//illegal
//System.out.println("iamprotected: " + a.iamprotected); //illegal
System.out.println("iampublic: " + a.iampublic);//legal
AlphaTwo a2 = new AlphaTwo();
a2.protectedMethod();
//legal
System.out.println("iamprotected: " + a2.iamprotected); //legal
}
}
最后Delta在类层次结构方面与Alpha无关,而它与alpha不在相同的包中。所以Delta只能访问Alpha的公共成员。
eg:
package Two;
import One.*;
public class DeltaTwo {
public static void main(String[] args) {
Alpha a = new Alpha();
//a.privateMethod();
//illegal
//a.packageMethod();
//illegal
//a.protectedMethod(); //illegal
a.publicMethod();
//legal
//System.out.println("iamprivate: " + a.iamprivate); //illegal
//System.out.println("iampackage: " + a.iampackage);
//illegal
//System.out.println("iamprotected: " + a.iamprotected); //illegal
System.out.println("iampublic: " + a.iampublic); //legal
}
}
下面的提示有助于你决定某个成员应该具有什么访问级别:
1、尽可能对成员使用最严格的访问级别。如果没有充分的理由,就使用private级别。
2、除了常量外,对所以成员变量避免使用public级别。公共成员变量会将你限制在某个特定的实现上,而且会导致错误和误用。另外,如果一个成员变量只能通过调用方法来修改,那么你就能够得知其他类或者对象对此成员变量进行了修改。如果允许对成员变量进行公共访问,那么就不可能知道了。如果给予公共访问权可以带来显著的性能受益,那么可以考虑这么做。
3、限制受保护的成员变量和包成员变量的数量。
4、如果成员变量时JavaBeans属性,那么它必须是私有的。
类实例和类成员(类成员包括变量和方法)
public static int x=7;
//类成员变量
public int y=3;
//类实例变量
运行时系统为程序创建的每个类实例创建每个实例的拷贝。所以只能通过对实例的引用 访问实例成员和实例方法。
类变量是使用static修饰符声明的。运行时系统为每个类分配一个类变量,而不管创建了多少个这个类的实例。在系统第一次遇到这个类时,它为类变量分配内存。这个类的所有实例共享这个类的类变量的相同拷贝。既可以通过实例,可可以通过类本身访问类变量(或者类方法)。注意:当程序修改类类成员变量的值时,此修改对所有的实例有效。
类实例和类成员的初始化
要对类成员变量进行初始化,应该将初始化代码放在静态初始化块中。
要对类实例变量进行初始化,应该将初始化代码放在构造器中。
如果想对实例变量进行初始化,而且因为其他原因不能在变量中进行初始化,那么将初始化代码放在类的构造器中。
在成员的声明中使用static关键字,可以指定类变量或者类方法。没有声明为static的成员隐含的成为实例成员。
在使用内部类的时,内部类可以访问包含它的类的私有成员。注意构造器不是成员,所以不被子类继承。
隐藏和覆盖方法
如果子类中的一个实例方法与超类中的一个实例方法具有相同的标记和返回值,这被称为子类方法覆盖(override)超类方法。(方法的标记是指它的名称,参数数量,参数类型)。
覆盖方法可以具有不同的throws子句,条件是它没有指定被覆盖方法的throws子句没有指定的任何类型。另外覆盖方法的访问说明符允许的访问可以比被覆盖的方法多,但不能少。例如:超类的保护方法在子类中可以变成公共的,但不能变成私有的。
子类必须覆盖超类中声明为abstract的方法,否则子类本身必须是抽象的。
java允许通过改变方法参数的数量和类型来重载(overload)方法。
eg:
class MyClass{
private int anInt=4;
//覆盖(overrides) toString in Object class
public String toString(){
return "Instance of MyClass.anInt= " + anInt;
}
//重载(overloads) toString method name to provide additional functionality
public String toString(String prefix){
return prefix+":"+toString();
}
}
对于类方法(static标识的方法)才有隐藏;对于类成员方法才有覆盖和重载。
如果子类定义的类方法与超类中的类方法具有相同的标记,那么子类方法隐藏(hide)超类方法。
注意:隐藏与覆盖之间的差异具有很重要的影响。
public class Planet {
public static void hide() {
System.out.println("The hide method in Planet.");
}
public void override() {
System.out.println("The override method in Planet.");
}
}
public class Earth extends Planet {
public static void hide() {
System.out.println("The hide method in Earth.");
}
public void override() {
System.out.println("The override method in Earth.");
}
public static void main(String[] args) {
Earth myEarth = new Earth();
Planet myPlanet = (Planet)myEarth;
myPlanet.hide();
myPlanet.override();
}
}
Earth类覆盖Planet类中的实例方法override,并隐藏Planet中的类方法hide。这个类中的main方法创建一个Earth实例,将此实例转换为一个Planet引用,然后在此实例上调用hide和override方法。
程序输出:
The hide method in Planet.
The override method in Earth.
被调用的隐藏方法是超类中的方法,而被调用的覆盖方法是子类中的方法。
对于类方法,运行时系统调用当前对象引用的编译时类型中定义的方法。例如:myPlanet的编译时类型是Planet。所以运行时系统调用Planet中定义的hide方法。
对于实例方法,运行时系统调用当前对象引用的运行时类型中定义的方法。在本例中,myplanet的运行时类型Earth。所以运行时系统调用Earth中定义的override方法。
实例方法不能覆盖静态方法,而静态方法不能隐藏实例方法。
子类方法与超类方法具有相同的标记的影响
超类实例方法 超类静态方法
实例方法
覆盖(还必须有相同的返回类型) 产生编译时错误
静态方法
产生编译时错误 隐藏
讲这里的意义是什么?
隐藏成员变量
在类中,如果一个成员变量与超类中的成员变量同名(即使他们的类型不同),那么它隐藏超类成员变量。
一般,不建议隐藏成员变量。
如果你的方法覆盖了超类方法之一,那么可以通过super调用被覆盖的方法。也可以使用super引用被隐藏的成员变量。
public class Superclass {
public boolean aVariable;
public void aMethod() {
aVariable = true;
}
}
public class Subclass extends Superclass {
public boolean aVariable;
//hides aVariable in Superclass
public void aMethod() {
//overrides aMethod in Superclass
aVariable = false;
super.aMethod();
System.out.println(aVariable);
System.out.println(super.aVariable);
}
}
Subclass中的aVarible引用的是Subclass中声明的aVarible变量,它隐藏了Superclass中声明的aVarible变量。类似aMethod引用的是Subclass中声明的aMethod方法,它覆盖了Superclass中声明的aMethod方法。
可以在构造器中使用super调用超类的构造器。
class AnimationThread extends Thread{
int framesPerSecond;
int numImages;
Image[] images;
AnimationThread(int fps,int num){
super("AnimationThread");
this.framesPerSecond=fps;
this.numImages=num;
this.images=new Image[numImages];
for (int i=0;i<=numImages;i++){
//Load all the images;
}
}
}
它调用AnimationThread的超类(Thread)提供的构造器,这个Thread构造器取一个字符串参数,用于设置Thread的名称。
如果存在显式的超类构造器调用,那么它必须是子类构造器中的第一句:对象应该首先执行较高层的初始化。如果构造器没有显示调用超类构造器,那么java运行时系统在执行此构造器中的任何语句之前自动调用超类的无参数构造器。
编写final类和方法
将类声明为final,就不能被子类化。这么做可能有2个原因:
1)安全性:黑客用来颠覆系统的一种机制就是创建一个类的子类,然后用子类替代原来的类。子类看起来象原来的类,但是做不同的事情;
2)设计:还可能因为想实现面向对象设计而将类声明为final;你认为你的类是“完美的”,或者理论上根本不需要需要子类。
在方法声明中使用final关键字表示这个方法不能被子类覆盖。
编写抽象类和方法
在面向对象编程中,可以对抽象概念进行建模,而不能创建它的实例。
Number这种代表抽象概念的类被称为抽象类(abstract class)。他们不应该被实例化。抽象类只能被子类化,不能被实例化。
抽象类中可以包含抽象方法(abstract method),即没有实现的方法。实际上,抽象类应该提供至少一个方法的完整实现或者部分实现。
管理继承的小结
类从它的所有(直接或者间接)超类继承成员变量和方法。子类可以覆盖它继承的方法,还可以隐藏它继承的变量或方法。
实现嵌套的类
嵌套类是另一个类的成员。
可以定义一个类为另一个类的成员。这样的类被称为嵌套的类(nested class)。
使用嵌套类来反映并实施2个类之间的关系。当嵌套类只在包含它的类中有意义时,或者在它依赖于包含它的类实现功能时,应该在另一个类中定义这个类。例如:文本光标指在文本组件有意义。
作为包含它的类的成员,嵌套类具有一种特殊的特权:它可以无限制地访问包含它的类的成员,即使这些成员是私有的,嵌套类也可以访问他们。但是,这种特殊的特权其实并不特殊。这与private和其他访问说明符的含有完全一致。访问说明符限制的是当前类外面的类对成员的访问。嵌套类位于包含它的类的内部,所以它可以访问包含它的类的成员。
与其他成员一样,嵌套类可以被声明为静态的,或者非静态的。
静态的嵌套类就被叫做静态的嵌套类,而非静态的嵌套类被称为内部类。
Class EnclosingClass{
……
Static class StaticNesstesClass{
…...
}
Class InnerClass{
…...
}
…...
}
静态的嵌套类不能直接引用包含它的类中定义的实例变量或者实例方法--只能通过一个对象引用来使用他们。
与实例方法和变量一样,内部类包含它的类的一个实例相关联,它可以直接访问此对象的实例变量和实例方法。另外因为内部类与实例相关联,所以它不能定义任何静态成员。
为了进一步区分嵌套类和内部类:
嵌套类反映2个类之间的句法关系(syntactic relationship);也就是说,在句法上一个类的代码出现在另外一个类的代码内。
内部类反映2个类的实例之间的关系。
InnerClass的实例只在EnclosingClass的实例内存在,而且它可以直接访问包含它的实例的实例变量和实例方法。
内部类是一种嵌套类,它的实例只在包含它的类的实例中存在。内部类可以直接访问包含它的实例的实例变量。
内部类说明:
假设你想在Stack类中添加一个特性,以便允许另一个类使用java.util.Iterator接口(这个接口是sdk1.2版本中增加的,现在应该使用java.util.Enumeration接口)枚举堆栈中的元素。
这个接口包含3个方法声明:
Public boolean hasNext();
Public Object next();
Public void remove();
Iterator定义的方法用于在一个有序集中依次访问各个元素,每次访问一个。使用方式如下:
While (hasNext()){
Next();
}
stack类本身不应该实现Iterator接口,这是因为Iterator接口的api造成了某些限制:2个单独的对象不能同时枚举stack中的项目,这是因为无法知道谁在调用next方法;枚举不能被重新开始,这是因为Iterator接口没有支持此功能的方法;枚举只能被调用一次,这是因为Iterator接口没有用于向回移动的方法。所以应该有一个辅助类来为stack做这些事情。
这个辅助类必须可以访问stack类的元素,而且必须能够直接访问他们,因为stack的公共接口只支持LIFO(后进先出)访问。这正好是内部类的用武之地。
Public class stack{
Private Vector items;
//code for stack's method and constructors not shown…
Class StackIterator implements Iterator{
Int currentItem= items.size()-1;
Public boolean hasNext(){}
Public Object next(){}
Public void remove(){}
...
}
}
注意,stackIterator类可以直接引用stack类的items实例变量。
内部类主要用于实现StackIterator类这样的辅助类。如果打算处理用户界面事件,就需要知道然后使用内部类,因为事件处理机制需要大量使用内部类。
练习:
1、这个嵌套类的使用者只是包含它的类的实例或者包含它的类的子类实例---受保护的内部类
2、任何人都可以使用这个嵌套类---公共静态嵌套类
3、只有声明这个嵌套类的类的实例需要使用它,而且一个实例可能使用它多次---私有内部类
4、这个小嵌套类只被使用一次,用于创建一个实现某接口的对象---匿名嵌套类
5、这个嵌套类提供关于包含它的类(不是关于这个类的实例)的信息,而且只由包含它的类及其子类使用---受保护的静态嵌套类
6、与5一样,但是不能由子类使用---私有静态嵌套类