Java类的高级特性:类包、final、内部类

一、Java类包

1、Java类包

       Java中每个接口或类都来自不同的类包,无论是JavaAPI中的类与接口还是自定义的类与接口都需要隶属于某-一个类包,这个类包包含了一些类和接口。如果没有包的存在,管理程序中的类名称将是一件非常麻烦的事情。在Java中采用类包机制非常重要,类包不仅可以解决类名冲突问题,还可以在开发庞大的应用程序时,帮助开发人员管理庞大的应用程序组件,方便软件复用。

       一个完整的类名需要包名与类名的组合,每个类都隶属于一个类包,只要保证同一类包中的类不同名,就可以有效地避免同名类冲突的情况。当然包名也可能产生冲突,为了避免这样的问题,在Java中定义包名时通常使用创建者的Internet域名的反序,由于Intermnet域名是独一-无二的,包名自然不会发生冲突。

例:

java.util.Date date = new java.util.Date();
java.sql.Date date2 = new java.sql.Date(2333);

       同一个包中的类不必存放在同一个位置,如com.lzw.class1和com.lzw.class2 可以一个放置在C盘,一个放置在D盘,只要将CLASSPATH分别指向这两个位置即可。

2、创建包

       在Java中包名设计应与文件系统结构相对应,如一个包名为com.lzw,那么该包中的类位于com文件夹下的lzw 子文件夹下。没有定义包的类会被归纳在预设包(默认包)中。在实际开发中,应该为所有类设置包名,这是良好的编程习惯。

在类中定义包名的语法如下:

package 包名

       在类中指定包名时需要将package表达式放置在程序的第一行,它必须是文件中的第一行非注释代码,当使用package关键字为类指定包名之后,包名将会成为类名中的一部分, 预示着这个类必须指定全名。

       注:Java包的命名规则是全部使用小写字母。

3、导入包

       使用import导入包。在使用import关键字时,可以指定类的完整描述,如果为了使用包中更多的类,可以在使用import关键字指定时在包指定后加上*,这表示可以在程序中使用包中的所有类。

       如果类定义中已经导入com.lzw.Math类,在类体中再使用其他包中的Math类时则必须指定完整的带有包格式的类名,如这种情况再使用java.lang 包的Math 类时就要使用全名格式java.lang.Math。在程序中添加import关键字时,就开始在CLASSPATH指定的目录中进行查找,查找子目录com.lzw,然后从这个目录下编译完成的文件中查找是否有名称符合者,最后寻找到Math.class文件。另外,当使用import指定了一个包中的所有类时,并不会指定这个包的子包中的类,如果用到这个包中的子类,需要再次对子包作单独引用。

       在Java中将Java源文件与类文件放在一起管理是极为不好的管理方式。可以在编译时使用-d参数设置编译后类文件产生的位置。使用DOS进入程序所在的根目录下,执行如下命令:

javac -d ./bin/ ./com/lzw/*.java

       这样编译成功后将在当前运行路径下的bin目录中产生com/lzw路径,并在该路径下出现相应源文件的类文件。如果使用Eeclipse编译器,并在创建项目时设置了源文件与输出文件的路径,编译后的类文件会自动保存在输出文件的路径中。

       如果不能在程序所在的根目录下使用javac.exe 命令,注意在path环境变量中设置Java编译器所在的位置,假如是C:Javaljdk1.6.0_03\bin, 可以使用set path=C:Java\jdk1 .6.0_ 03\bin;%path%命令在DOS中设置path环境变量。

4、使用import导入静态成员

       import关键字除了导入包之外,还可以导入静态成员,这是JDK 5.0以上版本提供的新功能。导入静态成员可以使程序员编程更为方便。语法如下:

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.println()表达式以及直接使用max()方法。

 

二、final

1、final变量

       final关键字可用于变量声明,一旦该变量被设定,就不可以再改变该变量的值。通常,由final 定义的变量为常量。

       final关键字定义的变量必须在声明时对其进行赋值操作。final除了可以修饰基本数据类型的常量,还可以修饰对象引用。由于数组也可以被看作一个对象来引用,所以final可以修饰数组。一旦一个对象引用被修饰为final 后,它只能恒定指向一个对象,无法将其改变以指向另一个对象。一个既是static又是final的字段只占据一段不能改变的存储空间。

例:

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。

例:

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开辟了一个恒定不变的区域,当再次实例化一个FinalStaticData对象时,仍然指向a2这块内存区域,所以a2的值保持不变。a2是在装载时被初始化,而不是每次创建新对象时都被初始化;而a1会在重新实例化对象时被更改。

2、final方法

        将方法定义为final类型可以防止子类修改该类的定义与实现方式,同时定义为final的方法的执行效率要高于非final方法。在修饰权限中曾经提到过private修饰符,如果一个父类的某个方法被设置为private修饰符,子类将无法访问该方法,自然无法覆盖该方法,所以一个定义为private的方法隐式被指定为final类型,这样无须将一个定义为private的方法再定义为final类型。

例:

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 {
	// 在子类中定义一个doit()方法
	public final void doit() { 
		System.out.println("子类.doit()");
	}
	
	//final方法不能覆盖
//	final void doit2(){		
//		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; 		// 执行向上转型操作才可覆盖final函数
		// p.doit(); 		//不能调用private方法
		p.doit2();
		p.doit3();
	}
}

输出:

       从本实例中可以看出,final 方法不能被覆盖,例如,doit2()方法不能在子类中被重写,但是在父类中定义了一个private final的doit()方法,同时在子类中也定义了一个doit()方法,从表面上来看,子类中的doit()方法覆盖了父类的doit()方法,但是覆盖必须满足一个对象向上转型(子类对象看作父类对象)为它的基本类型并调用相同方法这样一个条件。例如,在主方法中使用“Parents p=s;,"语句执行向上转型操作,对象p只能调用正常覆盖的doit3()方法,却不能调用doit()方法,可见子类中的doit()方法并不是正常覆盖,而是生成一个新的方法

3、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、成员内部类

(1)成员内部类简介

       在一个类中定义了一个内部类,则该内部类中可直接存取器所在类的私有成员变量。但是外部类不可直接访问内部类成员变量,需要通过内部类的对象来访问内部类的成员变量。

       内部类的实例一定要绑定在外部类的实例上,如果从外部类中初始化一个内部类对象,那么内部类对象就会绑定在外部类对象上。内部类初始化方式与其他类初始化方式相同,都是使用new关键字。

例:

public class OuterClass {
	private int outNum = 0;
	// 定义一个内部类
	class  innerClass{
		public innerClass() {
		}
		public void inf() {
			// 内部类可直接调用外部类中的成员
			outNum = 66;
			System.out.println("调用内部类inf(), outNum = " + outNum);
		}
		int y = 0;
	}
	// 在外部类实例化内部类对象引用
	innerClass in = new innerClass();
	// 外部类方法,返回内部类引用
	public innerClass doit() {
		// y = 6;	// 外部类不可直接范围内部类的成员
		in.y = 99;	// 外部类可通过内部类的对象来方法内部类的成员
		return new innerClass();	// 注意此处有一个new
	}
	// 在外部类通过内部类对象访问内部类方法
	public void ouf() {
		in.inf();
	}
	public static void main(String args[]) {
		OuterClass out = new OuterClass();
		// 内部类的对象实例化操作必须在外部类或外部类的非静态方法中实现
		OuterClass.innerClass in = out.doit();		        // 可通过外部类方法中返回的内部引用类访问内部方法
		in.inf();
		OuterClass.innerClass in2 = out.new innerClass();	// 内部类的实例一定要绑定在外部类的实例上
		in2.inf();
	}
}

输出:

注:

       如果在外部类和非静态方法之外实例化内部类对象,需要使用外部类。内部类的形式指定该对象的类型。

       在实例化内部类对象时,不能在new操作符之前使用外部类名称实例化内部类对象,而是应该使用外部类的对象来创建其内部类的对象。

        内部类对象会依赖于外部类对象,除非已经存在一个外部类对象,否则类中不会出现内部类对象。

(2)内部类向上转型为接口

       如果将一个权限修饰符为private的内部类向上转型为其父类对象,或者直接向上转型为一个接口,在程序中就可以完全隐藏内部类的具体实现过程。可以在外部提供一个接口,在接口中声明一个方法。如果在实现该接口的内部类中实现该接口的方法,就可以定义多个内部类以不同的方式实现接口中的同一个方法,而在一般的类中是不能多次实现接口中同一个方法的,这种技巧经常被应用在Swing编程中,可以在一个类中做出多个不同的响应事件。

例:

// 定义一个接口
interface OuterInterface{
	public void f();
}

// 注意此处的类不能定义为public
class OuterClass2 {
	// 定义一个内部类实现OuterInterface接口
	private class InnerClass implements OuterInterface{
		public InnerClass(String s) {
			System.out.println(s);
		}
		public void f() {
			System.out.println("访问内部类中的f()方法");
		}
	}
	
	// 外部类方法,返回内部类引用,注意此处返回值为OuterInterface
	public OuterInterface doit() {
		return new InnerClass("范围内部类构造方法");
	} 
}

public class InterfaceInner{
	public static void main(String args[]) {
		OuterClass2 out = new OuterClass2();
		OuterInterface outinter = out.doit();
		outinter.f();
	}
}

输出:

       Interfacelnner 类中最后一条语句,接口引用调用f()方法,从执行结果可以看出,这条语句执行的是内部类中的f()方法,很好地对继承该类的子类隐藏了实现细节,仅为编写子类的人留下一个接口和一个外部类,同时也可以调用f()方法,但是f()方法的具体实现过程却被很好地隐藏了,这就是内部类最基本的用途

(3)使用this关键字获取内部类与外部类的引用

       如果在外部类中定义的成员变量与内部类的成员变量名称相同,可以使用this关键字。

例:

public class TheSameName {
	private int x;
	
	private class Inner {
		private int x = 9;
		public void doit(int x) {
			x++; 						// 调用的是形参x
			this.x++; 					// 调用内部类的变量x
			TheSameName.this.x++; 	// 调用外部类的变量x
		}
	}
}

2、局部内部类

       内部类不仅可以在类中进行定义,也可以在类的局部位置定义,如在类的方法或任意的作用域中均可以定义内部类。

例:

interface OutInterface2 { // 定义一个接口
}

class OuterClass3 {
	// doit()方法参数为final类型
	public OutInterface2 doit(final int x, int y) { 
		// 在doit()方法中定义一个内部类
		class InnerClass2 implements OutInterface2 {
			InnerClass2(int s1, int s2) {
				s1 = x;
				s2 = y;
				// y = y + 1;	// 内部类中不能改变其所在方法的局部变量的值
				System.out.println(s1 + s2);
			}
		}
		return new InnerClass2(0, 0);
	}
	public static void main(String args[]) {
		OuterClass3 out3 = new OuterClass3();
		out3.doit(1, 2);
	}
}

输出:

       内部类InnerClass2是doit()方法的一部分,并非OuterClass3类中的一部分,所以在doit()方法的外部不能访问该内部类,但是该内部类可以访问当前代码块的常量以及此外部类的所有成员。

       注:doit()方法的参数设置为final 类型。如果需要在方法体中使用局部变量,该局部变量需要被设置为final类型,如果没设置为final也不能在内部变量中改变其值,换句话说,在方法中定义的内部类只能访问方法中final 类型的局部变量,这是因为在方法中定义的局部变量相当于一个常量,它的生命周期超出方法运行的生命周期,由于该局部变量被设置为final, 所以不能在内部类中改变其所在方法的局部变量的值。

3、匿名内部类

       匿名类,即没有名称的类,直接返回某一个类的对象引用,语法如下:

return new A(){
}

例:

class OuterInter2{
	OuterInter2(String s){
		System.out.println(s);
	}
	public void OuterInter2Print() {
		System.out.println("调用OuterInter2类中的方法");
	}
}

class OuterClass4 {
	// 定义doit()方法,返回值为类OuterInter2对象
	public OuterInter2 doit() {
		// 声明匿名内部类,返回OuterInter2的引用
		return new OuterInter2("调用类OuterInter2的构造方法") {
			private int i = 0;
			public int getValue() {
				return i;
			}
		};
	}
	
	public static void main(String args[]) {
		OuterClass4 out4 = new OuterClass4();
		out4.doit().OuterInter2Print();
		
	}
}

输出:

       在doit()方法内部首先返回一个OutInterface2的引用,然后在retum语句中插入一个定义内部类的代码,由于这个类没有名称,所以这里将该内部类称为匿名内部类。实质上这种内部类的作用就是创建- 一个实现于OutInterface2接口的匿名类的对象

       由于匿名内部类没有名称,所以匿名内部类使用默认构造方法来生成Outlnterface2 对象。在匿名内部类定义结束后,需要加分号标识,这个分号并不是代表定义内部类结束的标识,而是代表创建OutInterface2引用表达式的标识。

       匿名内部类编译以后,会产生以“外部类名$序号”为名称的.class文件,序号以1~n 排列,分别代表1~n个匿名内部类。

4、静态内部类

       在内部类前添加修饰符static,这个内部类就变为静态内部类了。个静态内部类中可以声明static成员,但是在非静态内部类中不可以声明静态成员。静态内部类有一个最大的特点,就是不可以使用外部类的非静态成员,所以静态内部类在程序开发中比较少见。

       可以这样认为,普通的内部类对象隐式地在外部保存了一个引用,指向创建它的外部类对象,但如果内部类被定义为statc,就会有更多的限制。静态内部类具有以下两个特点:

  • 如果创建静态内部类的对象,不需要其外部类的对象。
  • 不能从静态内部类的对象中访问非静态外部类的对象。

例:

public class StaticInnerClass {
	int x = 100;
	static int y = 99;
	static class Inner{
		void doitInner() {
			// x = 1000;	// 静态内部类不可直接调用外部类的非静态成员
			y = 999;
			System.out.println("内部类");
		}
	}
	public static void main(String args[]) {
		StaticInnerClass sout = new StaticInnerClass();		
		// StaticInnerClass.Inner in = sout.new Inner();    // 不能通过外部类对象来实例化静态内部类
		// System.out.println(sout.y);                      // 不能通过类对象调用类中静态变量
		System.out.println(sout.x);
	}
}

输出:

5、内部类继承

       内部类和其他普通类-样可以被继承,但是继承内部类比继承普通类复杂,需要设置专门的语法来完成。

例:

class ClassA{
	public ClassA(){
		System.out.println("调用ClassA构造方法");
	}
	class ClassB{
		public ClassB(){
			System.out.println("调用ClassB构造方法");
		}
	}
}

// OuterInnerClasss继承ClassA的内部类ClassB
public class OuterInnerClasss extends ClassA.ClassB{
	public OuterInnerClasss(ClassA a) {
		// 调用离自己最近的一个父类(超类)中的构造函数,即ClassB
		a.super();
	}
	public static void main(String args[]) {
		ClassA a = new ClassA();
		OuterInnerClasss out = new OuterInnerClasss(a);
	}
}

输出:

       在某个类继承内部类时,必须硬性给予这个类一个带参数的构造方法,并且该构造方法的参数为需要继承内部类的外部类的引用,同时在构造方法体中使用a.super()语句,即调用离自己最近的一个父类(超类)中的构造函数,这样才为继承提供了必要的对象引用。

原创文章 99 获赞 68 访问量 4万+

猜你喜欢

转载自blog.csdn.net/King_weng/article/details/103797382
今日推荐