十五、类的高级特性
1. JAVA类包
类名冲突:JAVA中每个接口或类都来自不同的类包,无论是JAVA API中的类与接口还是自定义的类与接口,都要隶属于某一个类包,这些类包包含了一些类和接口。如果没有包的存在,管理程序中的类名称将是一件非常麻烦的事情。如果程序只是由一个类定义组成,并不会给程序带来什么影响,但是随着程序代码的增多,难免会出现类同名的问题,这会导致类名冲突,编译器不会允许存在同名的类文件。解决这类问题的方法是将这两个类放置在不同的类包中。
同一个包中的类相互访问时,可以不指定包名。
同一个包中的类不必放在同一个位置,如com.lzw.class1和com.lzw.class2可以一个放在C盘,一个放在D盘,只要将CLASSPATH分别指向这两个位置即可。
-
使用import关键字导入包
import com.lzw.*; //指定com.lzw包中的所有类在程序中都可以使用 import com.lzw.Math; //指定com.lzw包中的Math类在程序中可以使用
在使用import关键字时,可以指定类的完整描述,如果为了使用包中更多的类,可以在使用import关键字指定时在包指定后加上*,这表示可以在程序中使用包中的所有类。
如果类定义中已经导入com.lzw.Math类,在类体中再使用其他包中的Math类是就必须指定完整的带有包格式的类名,如这种情况再使用java.lang包中的Math类时就要使用全名格式java.lang.Math。
-
使用import关键字导入静态成员
import static 静态成员
导入静态成员案例:
package com.lzw;
import static java.lang.Math.*;
import static java.lang.System.*;
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.pritln()表达式以及直接使用max()方法。
2. final变量
-
final关键字可以用于变量声明,一旦该变量被设定,就不可以再改变该变量的值。通常,由final定义的变量为常量,语法如下:
final double PI=3.14;
final关键字定义的变量必须在声明时对其进行赋值操作。final除了可以修饰基本数据类型的常量,还可以修饰对象引用。由于数组可以被看作是一个对象,所以也可以用来修饰数组。一旦一个对象被修饰为final后,它只能恒定指向一个对象,无法将其改变以指定另外一个对象。一个既是static又是final的字段只占据一段不能改变的存储空间。
定义各种类型的final变量:
package com.lzw;
import static java.lang.System.*;
import java.util.*;
class Test {
int i = 0;
}
public class FinalData {
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 String toString() {
return i4 + " " + i5 + " ";
}
public static void main(String[] args) {
FinalData data = new FinalData();
// data.test=new Test();
//可以对指定为final的引用中的成员变量赋值
//但不能将定义为final的引用指向其他引用
// data.value2++;
//不能改变定义为final的常量值
data.test2 = new Test(); // 可以将没有定义为final的引用指向其他引用
for (int i = 0; i < data.a.length; i++) {
// a[i]=9;
// //不能对定义为final的数组赋值
}
out.println(data);
out.println("data2");
out.println(new FinalData());
// out.println(data);
}
}
运行结果:
- 在该案例中,被定义为final的常量定义时需要使用大写字母命名,并且中间使用下划线进行连接,这是JAVA的编码规则。同时,定义为final的数据无论是常量、对象引用还是数组,在主函数中都不可以被改变。
- 一个被定义为final的对象引用只能指向唯一一个对象,不可以将它再指向其他对象。但是由于一个对象本身的值是可以改变的,因此为了使一个常量真正做到不可更改,可以将常量声明为static final。
package com.lzw;
import static java.lang.System.*;
import java.util.*;
public class FinalStaticData {
private static Random rand = new Random(); // 实例化一个Random类对象
// 随机产生0~10之间的随机数赋予定义为final的a1
private final int a1 = rand.nextInt(10);
// 随机产生0~10之间的随机数赋予定义为static final的a2
private static final int a2 = rand.nextInt(10);
public static void main(String[] args) {
FinalStaticData fdata = new FinalStaticData(); // 实例化一个对象
// 调用定义为final的a1
out.println("重新实例化对象调用a1的值:" + fdata.a1);
// 调用定义为static final的a2
out.println("重新实例化对象调用a1的值:" + fdata.a2);
// 实例化另外一个对象
FinalStaticData fdata2 = new FinalStaticData();
out.println("重新实例化对象调用a1的值:" + fdata2.a1);
out.println("重新实例化对象调用a2的值:" + fdata2.a2);
}
}
运行结果:
- 从运行结果可以看到,定义为final的变量不是恒定不变的,将随机数赋予定义为final的变量,可以做到每次运行程序时改变a1的值。但是a2与a1不同,由于它是被声明为static final形式,所以在内存中为a2开辟了一个恒定不变的区域,当再次实例化一个对象时,仍然指向a2这块区域,所以a2的值保持不变。a2是在装载时被初始化,而不是每次创建新对象时都被初始化;而a1会在重新实例化对象时被更改。
3. final方法
被定义为final的方法不能被重写。
将方法定义为final类型,可以防止子类修改该类的定义与实现方式,同时定义为final的方法的执行效率要高于非final方法。当定义为final方法时,就无需用private关键字修饰。
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.print("子类.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();
}
}
运行结果:
- 从本例可以看出,final方法不能被覆盖,例如,doit2()方法不能在子类中被重写,但在父类中定义了一个private final的doit()方法,同时在子类中也定义了一个doit()方法,表面看子类的doit()方法覆盖了父类的doit()方法,但是覆盖必须满足一个对象向上转型为它的基本类型并调用相同方法这样一个条件。例如,在主方法中使用
parents p=s;
语句执行向上转型操作,对象p只能调用正常覆盖的doit3()方法,却不能调用doit()方法,可见子类中的doit()方法并不是被正常覆盖,而是生成了一个新的方法。
4. 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);
}
}
运行结果:
5. 内部类
- 在类中再定义一个类,则将在类中再定义的那个类称为内部类。
成员内部类案例:
public class OuterClass {
innerClass in = new innerClass(); // 在外部类实例化内部类对象引用
public void ouf() {
in.inf(); // 在外部类方法中调用内部类方法
}
class innerClass {
innerClass() { // 内部类构造方法
}
public void inf() { // 内部类成员方法
}
int y = 0; // 定义内部类成员变量
}
public innerClass doit() { // 外部类方法,返回值为内部类引用
// y=4; //外部类不可以直接访问内部类成员变量
in.y = 4;
return new innerClass(); // 返回内部类引用
}
public static void main(String args[]) {
OuterClass out = new OuterClass();
// 内部类的对象实例化操作必须在外部类或外部类中的非静态方法中实现
OuterClass.innerClass in = out.doit();
OuterClass.innerClass in2 = out.new innerClass();
}
}
内部类可以随意使用外部类的成员方法以及成员变量,尽管这些成员被修饰为private。内部类的实例一定要绑在外部类的实例上,如果从外部类中初始化一个内部类对象,那么内部类对象就会绑定在外部类对象上。
如果在外部类和非静态方法之外实例化内部类对象,需要使用外部类。内部类的形式指定该对象的类型。
内部类对象会依赖于外部类对象,除非已经存在一个外部类对象,否则类中不会出现内部类对象。
内部类向上转型为接口:
package com.lzw;
interface OutInterface { // 定义一个接口
public void f();
}
public class InterfaceInner {
public static void main(String args[]) {
OuterClass2 out = new OuterClass2(); // 实例化一个OuterClass2对象
// 调用doit()方法,返回一个OutInterface接口
OutInterface outinter = out.doit();
outinter.f(); // 调用f()方法
}
}
class OuterClass2 {
// 定义一个内部类实现OutInterface接口
private class InnerClass implements OutInterface {
InnerClass(String s) { // 内部类构造方法
System.out.println(s);
}
public void f() { // 实现接口中的f()方法
System.out.println("访问内部类中的f()方法");
}
}
public OutInterface doit() { // 定义一个方法,返回值类型为OutInterface接口
return new InnerClass("访问内部类构造方法");
}
}
运行结果:
- 从上述代码可以看出,OuterClass2类中定义了一个修饰权限为private的内部类,这个内部类实现了OutInterface接口,然后修改了doit()方法,使方法返回一个OutInterface接口。
- 非内部类不能被声明为private或protected访问类型。
局部内部类
- 内部类不仅仅可以在类中进行定义,也可以在类的局部位置定义,如在类的方法或者任意的作用域中均可以定义内部类。
interface OutInterface2 { // 定义一个接口
}
class OuterClass3 {
public OutInterface2 doit(final String x) { // doit()方法参数为final类型
// 在doit()方法中定义一个内部类
class InnerClass2 implements OutInterface2 {
InnerClass2(String s) {
s = x;
System.out.println(s);
}
}
return new InnerClass2("doit");
}
}
- 从上述代码可以看出,内部类被定义在了doit()方法内部。但是需要注意的是,内部类InnerClass2是doit()方法的一部分,并非OuterClass3类中的一部分,所以在doit()方法的外部不能访问该内部类,但是该内部类可以访问当前代码块的常量以及此外部类的所有成员。
静态内部类
- 如果创建静态内部类的对象,不需要其外部类的对象。
- 不能从静态内部类的对象中访问非静态外部类的对象。
内部类的继承
- 内部类和其他普通类一样,可以被继承。但是继承内部类比继承普通类复杂,需要设置专门的语法来完成。
在项目中创建OutputInnerClass类,使OutputInnerClass类继承ClassA类中的内部类ClassB:
public class OutputInnerClass extends ClassA.ClassB{ //继承内部类ClassB
public OutputInnerClass(ClassA a){
a.super();
}
}
class ClassA{
class ClassB{
}
}
- 在某个类继承内部类时,必须硬性给予这个类一个带参数的构造方法,并且该构造方法的参数为需要继承内部类的外部类的引用,同时在构造方法体中使用a.super()语句,这样才为继承提供了必要的对象引用。