第一部分 类的高级特性 1.1 Java 类包
在 Java 中定义好每一个类,通过 Java 编译器编译之后,都会生成一个扩展名为 .class 的文件。当程序的规模逐渐扩大的时候,就会出现类名称冲突的现象。Java 中提供了一种管理类名剑的机制,就是类包。
Java 中每个接口或类都来自不同的类包,无论是 Java API 中的类与接口还是自定义的类与接口,都需要隶属于某一个类包,这个类包包含了一些类和接口。如果没有类包的存在。如果没有包的存在,管理程序中的类名称将是一件麻烦的事。编译器不会允许存在两个类名一样的文件。解决这类问题的办法就是将两个类放置在不同的类包中。
比如 Math 类,其实 Math 类并不是它的完整名称,它的完整名称是 java.lang.Math。一个类的完整名称需要包与类名的组合,每个类都隶属于一个类包,只要保证同一类包中的类不同名,就可以有效地避免同名类冲突的情况。例如,在一个程序中同时使用到 java.util.Date 类与 java.sql.Date 类,如果在程序中不指定完整类路径,编译器不会知道这段代码使用的是哪个类包中的 Date 类,所以需要在指定的代码中给出完整的类路径。
java.util.Date date = new java.util.Date(); java.sql.Date date = new java.sql.Date();
在 Java 中采用类包机制非常重要,类包不仅仅可以解决类名冲突问题,还可以在开发庞大的应用程序时,帮助开发人员管理庞大的应用程序组件,方便软件复用。
对于如何在一个程序中创建包,在 Eclipse 或者 IDEA 中都有明确的指示。在实际开发过程中,应该为所有的类设置包名。在类中定义包的语法如下:
package 包名
在类中指定包名时,需要将 package 表达式放置在程序的第一行,它必须是文件中的第一行非注释代码。
在 Java 中定义包名时通常使用创建者的 Internet 域名的反序,由于 Internet 域名是独一无二的,包名自然不会冲突。
在 Java 中,还可以使用 import 关键字来导入包。
在使用 import 关键字时,可以指定类的完整描述。import 关键字的语法如下:
import com.lzw.*; //指定com.lzw包中的所有类在程序中都可以使用 import com.lzw.Math; //指定com.lzw包中的Math类可以在程序中使用
在 Java 中将 Java 源文件和类文件放在一起管理是极为不好的管理方式。可以在编译时使用 -d 参数设置编译后文件产生的位置。使用 DOS 进入程序所在根目录,执行如下指令:
javac -d./bin/ ./com/lzw/*.java
这样编译成功后将在当前运行路径下的 bin 目录产生 com/lzw 路径,并在该路径下出现相应源文件的类文件。
此外,还可以使用 import 导入静态成员。
import 关键字除了导入包以外,还可以导入静态成员。导入静态成语可以使编程更为方便。使用 import 到导入静态成员变量的语法如下:
import static 静态成员
下面来看一个具体的例子,在项目中创建 ImportTest 类,在该类中使用 import 关键字导入静态成员变量。
package com.lzw;
import static java.lang.Math.max; //导入静态成员方法
import static java.lang.System.out; //导入静态成员变量
public class ImportTest{
public static void main(String[] args){
//在主方法中可以直接使用这些静态成员
out.println("1和4的较大值为:"+max(1,4));
}
}
从本实例可以看出,分别使用 import static 导入了 java.lang.Math 类中的静态成员方法 max() 和 java.lang.System 类中的 out 成员变量,这时就可以在程序中直接引用这些静态成员,如在主方法中的 out.pritnln() 表达式以及直接使用 max() 方法。
1.2 final 变量final 关键字可用于申明变量。一旦该变量被设定,就不可以再改变该变量的值。通常,由 final 定义为常量。例如,在类中定义 PI 值,可以使用如下语句:
final double PI=3.14;
当在程序中使用到 PI 这个常量时,它的值就是 3.14.如果在程序中再次对定义为 final 的常量赋值,编译器将不会接受。
final 关键字定义的变量必须在声明时对其进行赋值操作。 final 除了可以修饰基本数据类的常量,还可以修饰对象引用。由于数组也可以看作一个对象来引用,所有 final 可以修饰数组。一旦一个对象引用被修饰为 final 后,它只能恒定指向一个对象,无法将其改变以指向另一个对象。一个既是 static 又是 final 的字段只占一段不能改变的存储空间。
package com.lzw;
import static java.lang.System.out;
import java.util.Random;
class Test{
int i = 0;
}
public class FinalDate{
static Random rand = new Random();
private final int VALUE_1 = 9; //声明一个 final常量
private static final int VALUE_2 = 10; //声明一个 final、static常量
private final Test test = new Test(); //声明一个 final引用
private Test test2 = new Test(); //声明一个不是 final的引用
private final int[] a = {1,2,3,4,5,6}; //声明一个定义为 final的数组
private final int i4 = rand.nextInt(20);
private static final int i5 = rand.nextInt(20);
public Strinbg toString(){
return i4 + "" + i5 + "";
}
public static void main(String[] args){
FinalDate date = new FinalDate();
date.test = new Test();
//可以指定为 final引用中的成员变量赋值
//但不能将定义为 final的引用指向其他引用
data.value2++;
//不能改变定义为 final的常量值
date.test2 = new Test();
for(int i=0;i
a[i] = 9;
//不能对定义为 final的数组赋值
}
out.println(date);
out.println("date2");
out.println(new FinalDate());
out.println(date);
}
}
一个被定义为 final 的对象只能指向唯一一个对象,不可以将他再指向其他对象。在 Java 中定义全局变量,通常使用 public static final 修饰,这样的常量只在定义时被赋值。
可以将方法的参数定义为 final 类型,这预示着无法在方法中更改参数所指向的对象。
首先,定义为 final 的方法不能被重写。将方法定义为 fnal 类型,可以防止子类修改该类的定义与实现方式,同时定义为 final 的方法的执行效率要高于非 final 方法。在修饰权限中曾提及 private 修饰符,如果一个父类的某个方法被设置为 private 修饰符,子类将无法访问该方法,自然无法覆盖该方法。也就是说,一个定义为 private 的方法隐式被指定为 final 类型,因此无须将一个定义为 private 的方法再定义为 final 类型。
在父类中被定义为 private final 的方法不可以被子类覆盖,而是生成了一个新的方法,如下面的例子中,子类中的 doit() 方法并不是被正常覆盖,而是生成了一个新的方法。
class Parents{
private final void doit(){
System.out.println("父类.doit()");
}
final void doit2(){
System.out.println("父类.doit2()");
}
public void doit3(){
System.out.println("父类.doit3()");
}
}
class Sub extends Parents{
public final void doit(){ //在子类中定义以一个 doit()方法
System.out.println("子类.doit()");
}
// final void doit2(){ // final方法不能被覆盖
// System.out.println("子类.doit2()");
// }
public void doit3(){
System.out.println("子类.doit3()");
}
}
public class FinalMethod{
public static void main(String[] args){
Sub s = new Sub(); //实例化
s.doit(); //调用 doit()方法
Parents p = s; //执行向上转型操作
//p.doit(); //不能调用 private方法
p.doit2();
p.doit3();
}
}
1.4 final 类
定义为 final 的类不能够被继承。如果希望一个类不被任何类继承,并且不允许其他人对这个类进行任何改动,可以将这个类设置为 final 形式。
final 类的语法如下:
final 类名{…}
如果将某个类设置为 final 形式,则类中所有方法都被隐式设置为 final 形式,但是 final 类中的成员变量可以被定义为 final 或非 final 形式。
final class FinalClass{
int a = 3;
void doit(){
}
public static void main(String[] args){
FinalClass f = new FinalClass();
f.a++;
System.out.println(f.a);
}
}
1.5 内部类
在一个文件中定义两个类,则其中任何一个类都不在另一个类的内部;如果在类中再定义一个类,则将在类中再定义的那个类称为内部类。内部类可以分为成员内部类、局部内部类以及匿名类。
1. 成员内部类
成员内部类的语法如下:
public class OuterClass{ //外部类
private class InterClass{ //内部类
...
}
}
- 在内部类中可以任意使用外部类的成员方法以及成员变量,尽管这些类成员被修饰为 private。
- 内部类的实例一定要绑定在外部类的实例之上,如果从外部类中初始化一个内部类对象,那么内部类对象就会绑定在外部类对象上。内部类初始化方式与其他类初始化方式相同,都是使用 new 关键字。
- 内部对象与外部对象关系非常紧密,内外可以交互使用彼此类中定义的变量。
内部类向上转型为接口
如果将一个权限修饰符为 private 的内部类向上转型为其父类对象,或者直接向上转型一个接口,在程序中就可以完全隐藏内部类的具体实现过程。可以在外部提供一个接口,在接口中声明一个方法。如果在实现该接口的内部类中实现该接口的方法,就可以定义多个内部类以不同的方式实现接口中的同一个方法,而在一般的类中是不能多次实现接口中同一个方法的,这种技巧常被应用在 Swing 编程中,可以在一个类中做出多个不同的响应事件。
使用 this 关键字获取内部类与外部类的引用
如果在外部类中定义的成员变量与内部类的成员变量方法名称相同,可以使用 this 关键字。此外,在类中,如果遇到内部类与外部类的成员变量名称相同的情况,可以使用 this 关键字进行处理。
2. 局部内部类
内部类不仅可以在类中进行定义,也可以在类的局部位置定义,如在类的方法或任意的作用域中均可以定义内部类。
interface OutInterface{ //定义一个接口
}
class OuterClass3{
public OutInterface doit(final String x){// doit()方法参数为 final类型
class InnerClass2 implements OutInterface{
InnerClass2(String s){
s = x;
System.out.println(s);
}
}
return new InnerClass2("doit");
}
}
3. 匿名内部类
class Outerlass4P
punlic OutInerface2 doit(){ //定义 doit()方法
return new OutInterface2(){ //声明匿名内部类
private int i = 0;
public int getValue(){
return i;
}
};
}
}
匿名类的所有实现代码都需要在大括号之间进行编写。语法如下:
return new A(){
...//内部类体
};
在匿名内部类定义结束以后, 需要加分号进行标识。
4. 静态内部类
在内部类前添加修饰符 static,这个内部类就变成了静态内部类了。一个静态内部类中可以声明 static 成员,但是在非静态内部类中不可以声明静态成员。静态内部类的最大特点就是不可以使用外部类的非静态成员。(静态内部类在程序开发中比较少见)
5. 内部类的继承
内部类和其他普通类一样,都可以被继承。但是继承内部类比普通类复杂,需要设置专门的语法来完成。
public class OutputInnerClass extends ClassA.ClassB{ //继承内部类 ClassB
public OutputInnerClass(ClassA a){
a.super();
}
}
class ClassA{
class ClassB{
}
}
在某个类继承内部类时,必须硬性给予这个类一个带参数的构造方法,并且该构造方法的参数为需要继承内部类的外部类的引用,同时在构造方法体中使用 a.super() 语句,这样才为继承提供了必要的引用对象。



