第七章总结

7.1 类的封装

封装是面向对象编程的核心思想,将对象的属性和行为封装起来,其载体就是类

代码7.1

public class H7_1 {//创建类
		public static void main(String[] args) {//主函数
	String cookName="Tom Cruise";                          //厨师的名字叫 Tom Cruise 
			System.out.println("**请让厨师为我做一份香辣肉丝。***");//输出结果
			System.out.println(cookName +"切葱花");              //输出切葱花"
			System.out.println(cookName +"洗蔬菜");          //输出洗蔬菜"
			System.out.println(cookName+"开始烹饪"+"香辣肉丝");//输出开始烹饪"+"香辣肉丝
			System.out.println("**请问厨师叫什么名字?***"); //输出**请问厨师叫什么名字?***
			System.out.println(cookName);//调用cookName
			System.out.println("**请让厨师给我切一点葱花。***"); //输出**请让厨师给我切一点葱花。***
			System.out.println(cookName +"切葱花");      //输出切葱花
 }
}

结果

System.out.println("**请让厨师给我切一点葱花。***");

System.out.println(cookName+"搅鸡蛋");   //被乱改之后

 System.out.println("**请让厨师为我做一份清蒸鱼。***");

System.out.println(cookName+"你是我的小呀小苹果~");//被乱改之后

代码7.2
 

public class H7_2 {//创建类
	public static void main(String[] args) {//主函数
	  Cook1 cook = new Cook1();              // 创建厨师类的对象
		  System.out.println("**请让厨师为我做一份香辣肉丝。***");  //输出
		  cook.cooking("香辣肉丝");                 // 厨师烹饪香辣肉丝
		  System.out.println("**你们的厨师叫什么名字?***");//输出**你们的厨师叫什么名字?***
		  System.out.println(cook.name);      // 厨师回答自己的名字
		  System.out.println("**请让厨师给我切一点葱花。***");//输出**请让厨师给我切一点葱花。***
		  cook.cutOnion();      // 厨师去切葱花
		 }
		}
 
		class Cook1 {   //创建Cook1类
		 String name;// 厨师的名字
 
		 public Cook1() {            //普通类
		  this.name = "Tom Cruise"; // 厨师的名字叫Tom Cruise
		 }
 
		 void cutOnion() {       // 厨师切葱花
		  System.out.println(name + "切葱花");  //输出厨师切葱花
		 }
 
		 void washVegetavles() {    // 厨师洗蔬菜
		  System.out.println(name + "洗蔬菜");  //输出厨师洗蔬菜
		 }
 
		 void cooking(String dish) {// 厨师烹饪顾客点的菜
		  washVegetavles();     //洗蔬菜
		  cutOnion();             //切洋葱
		  System.out.println(name + "开始烹饪" + dish); //输出name + "开始烹饪" + dis
}}

结果

 将厨师单独封装成一个类, 将厨师的工作定义成厨师类的行为,当我们想让厨师做菜,只能通过调用对象成员方法的方式实现,而我们却不知道这个方法到底是怎么写的,所以就无法随意修改了。餐馆没有义务告诉我们厨师的任何信息,并且厨师也不会随意受我们差遣,所以说厨师有些属性和行为是不予公开的 。

代码7.3 

public class H7_3 {
	public static void main(String[] args) {
		Cook2 cook = new Cook2();// 创建厨师类的对象
		System.out.println("**请让厨师为我做一份香辣肉丝。***");
		cook.cooking("香辣肉丝");// 厨师烹饪香辣肉丝
		System.out.println("**你们的厨师叫什么名字?***");
		System.out.println(cook.name);// 厨师回答自己的名字
		System.out.println("**请让厨师给我切一点葱花。***");
		cook.cutOnion();// 厨师去切葱花
	}
}
 
class Cook2 {
	private String name;//厨师的名字
 
	public Cook2() {
		this.name = "Tom Cruise";//厨师的名字叫Tom Cruise
	}
 
	private void cutOnion() {//厨师切葱花
		System.out.println(name + "切葱花");
	}
 
	private void washVegetavles() {//厨师洗蔬菜
		System.out.println(name + "洗蔬菜");
	}
 
	void cooking(String dish) {//厨师烹饪顾客点的菜
		washVegetavles();
		cutOnion();
		System.out.println(name + "开始烹饪" + dish);
		}
	}

结果

 其实按照日常生活场景来讲,顾客去餐馆吃饭,下单的是服务员,上菜的也是服务员, 顾客跟本没有接触厨师的机会,所以厨师这个角色是对顾客隐藏起来的,被封装在餐馆的类当中。 

代码7.4 

public class H7_4 {//主函数
	private Cook2 cook = new Cook2();// 餐厅封装的厨师类
 
	public void takeOrder(String dish) {// 下单
		cook.cooking(dish);// 通知厨师做菜
		System.out.println("您的菜好了,请慢用。");//输出"您的菜好了,请慢用。"
	}
 
	public String saySorry() {// 拒绝顾客请求
		return "抱歉,餐厅不提供此项服务。";//输出"抱歉,餐厅不提供此项服务。"
	}
 
	public static void main(String[] args) {//主函数
		H7_4 water = new H7_4();// 创建餐厅对象,为顾客提供服务
		System.out.println("**请让厨师为我做一份香辣肉丝。***");//输出"**请让厨师为我做一份香辣肉丝。***"
		water.takeOrder("香辣肉丝");// 服务员给顾客下单
		System.out.println("**你们的厨师叫什么名字?***");//输出"**你们的厨师叫什么名字?***"
		System.out.println(water.saySorry());// 服务员给顾客善意的答复
		System.out.println("**请让厨师给我切一点葱花。***");//输出"**请让厨师给我切一点葱花。***"
		System.out.println(water.saySorry());//服务员给善意的答复顾客
}
}

结果

从这个例子我们就能看出,作为顾客,我始终是和服务员进行交流,再由服务员与厨师进行交流整个过程中,顾客与厨师是完全没有交集的。作为顾客,我不知道我品尝的美食是由哪位厨师用何种方法烹任出来的,这种编程模式,就是封装。
        将对象的属性和行为封装起来的载体就是类,类通常对客户隐藏其实现细节,这就是封装的思想。

7.2 类的继承
         继承在面向对 象开发思想中是一个非常重要的概念,它使整个程序架构具有-定的弹性,在程
序中复用已经定义完善的类不仅可以减少软件开发周期,还可以提高软件的可维护性和可扩展性。资源包本节将详细讲解类的继承。在第6章中曾简要介绍过继承,其基本思想是基于某个父类的扩展,制定出一个新的子类,子类可以继承承父类原有的属性和方法,也可以增加原来父类所不具备的属性和方法,或者直接重写父空中的某些方法。例如,平行四边形是特殊的四边形,可以说平行四边形类继承了四边形类,这时江边形类将所有四边形具有的属性和方法都保留下来,并基于四边形类展了一些新的平行四边形类特有的属性和方法。

7.2.1 extends 关键字
 在Java中,让一个类继承另一个类,用extends关键字,语法如下:

child extends parents

这里child这个类作为子类继承了parents 这个类,并继承parents中的属性和方法。
举一个简单的例子:每个人都用过计算机,最常见的计算机就是台式机。后来随着科技的发展,计算机变得越来越小,台式机改良成了可移动的笔记本电脑,笔记本电脑又改良成了更轻薄的平板电脑。我们可以把普通计算机看成一个类,那么笔记本电脑和平板电脑都是这个类衔生出的子类。 

注意:Java中的类只支持单继承,即一个子类只能继承一个父类。
代码7.5

class Computer {// 父类:电脑
	 String screen="液晶显示屏";    //定义初值
	  void startup() {       //返回参数
	  System.out.println("电脑正在开机,请等待...");//输出电脑正在开机,请等待...
	}}
public class H7_5 extends Computer {//父类:电脑
	String battery = "5000毫安电池";// 子类独有的属性
	public static void main(String[] args) {//主函数
		Computer pc = new Computer();// 电脑类
		System.out.println("computer的屏幕是:" + pc.screen);//输出"computer的屏幕是:" + pc.screen
		pc.startup();//输出startup
		H7_5 ipad = new H7_5();// 平板电脑类
		System.out.println("pad的屏幕是:" + ipad.screen);// 子类可以直接使用父类属性
		System.out.println("pad的电池是:" + ipad.battery);// 子类独有的属性
		ipad.startup();// 子类可以直接使用父类方法
	}
 
}

结果 

从这个结果可以看出,Pad类继承了Computer类之后,虽然没有定义任何成员方法,但仍可以调用父类的方法。这个方法是从父类那继承过来的。 

 7.2.2 方法的重写
1.重写的实现
        继承并不只是扩展父类的功能,还可以重写父类的成员方法。重写(还可以称为覆盖)就是在子类中将父类的成员方法的名称保留,重新编写成员方法的实现内容,更改成员方法的存储权限,或是修改成员方法的返回值类型(重写父类成员方法的返回值类型是基于J2SE 5.0版本以上编译器提供的新功能)。在继承中还有种特殊的重写方式, 子类与父类的成员方法返回值、 方法名称、参数类型及个教完全相同,唯一不同的是方法实现内容,这种特殊重写方式被称为重构。

 代码7.6

class Computer2 {// 父类:电脑
 
	void showPicture() {//显示图片
		System.out.println("鼠标点击");//输出鼠标点击
	}
}
public class H7_6 extends Computer2{//子类继承父类
	void showPicture() {//显示图片
		System.out.println("手指点击触摸屏");//输出手指点击触摸屏
	}
	public static void main(String[] args) {//主函数
		Computer2 pc = new Computer2();// 电脑类
		System.out.print("pc打开图片:");//输出pc打开图片:
		pc.showPicture();// 调用方法
 
		H7_6 ipad = new H7_6();// 平板电脑类
		System.out.print("ipad打开图片:");//输出ipad打开图片:
		ipad.showPicture();// 重写父类方法
 
		Computer2 computerpad = new H7_6();// 父类声明,子类实现
		System.out.print("computerpad打开图片:");//输出computerpad打开图片:
		computerpad.showPicture();// 调用父类方法,实现子类重写的逻辑
	}
}

结果

从这个结果我们可以看出,虽然子类调用了父类的方法,但实现的是子类重写后的逻辑, 而不是父类原有的逻辑。如果父类声明的对象是由子类实例化的,那么这个对象所调用的方法也是被子类重写过的。 

注意:在Java语言中,一个类只可以有一个父类!

2.super 关键字
如果子类重写了父类的方法,就再也无法调用到父类的方法了吗?如果想在子类的方法中实现父类原有的方法怎么办?为了解决这种需求,Java 提供了super 关键字。
super关键字的使用方法与this关键字类似。this 关键字代表本类对象,super 关键宇代表父类对象,使用方法如下:

super.property; //调用父类的属性

super.method(); //调用父类的方法 

代码7.7
 

class Computer3 {// 父类:电脑
	String  sayHello(){//定义一个方法
		return "欢迎使用";//返回一个字符串
	}
}
public class H7_7 extends Computer3{//子类继承父类
 
	String sayHello() {// 子类重写父类方法
		return super.sayHello() + "平板电脑";// 调用父类方法,在其结果后添加字符串
	}
 
	public static void main(String[] args) {//主函数
		Computer3 pc = new Computer3();// 电脑类
		System.out.println(pc.sayHello());//输出pc.sayHello()
		H7_7 ipad = new H7_7();// 平板电脑类
		System.out.println(ipad.sayHello());//输出ipad.sayHello()
	}
}

结果

注意:如果子类构造方法中使用类似super()的构造方法,其他初始化代码只能写在super()之后,不能写在前面,否则会报错。

7.2.3 所有类的父类——Object类
 在开始学习使用class关键字定义类时,就应用了继承原理,因为在Java中,所有的类都直接或间接继承了java.lang.Object 类。Object 类是比较特殊的类,它是所有类的父类,是Java类层中的最高层类。当创建一一个类时,总是在继承,除非某个类已经指定要从其他类继承,否则它就是从java.lang.Object类继承而来的,可见Java中的每个类都源于java.lang.Object 类,如String、Integer 等类都是继承于Object类;除此之外自定义的类也都继承于Object类。由于所有类都是Object子类,所以在定义类时,省略了extends Object关键字,如图7.10所示便描述了这一原则。
在Object类中主要包括clone(、finalize(、equalsO、toString0等方法,其中常用的两个方法为equals()和 toString0方法。由于所有的类都是Object 类的子类,所以任何类都可以重写Object类中的方法。


注意:Object类中的getClass()、notify()、notifyAll()、wait()等方法不能被重写,因为这些方法被定义为final类型。

Object的重要方法:

1. getClass( )方法
getClass0方法是Object类定义的方法,它会返回对象执行时的Class实例,然后使用此实例调用getName0方法可以取得类的名称。
getClass( ) . getName( );
可以将getClass0方法与toString0方法联合使用。

2. toString( )方法
toString0方法的功能是将一一个对象返回为字符串形式, 它会返回一 个String 实例。在实际的应用中通常重写toString( )方法,为对象提供一个特定的输出模式。当这个类转换为字符串或与字符串连接时,将自动调用重写的toString0方法。的重要方法。
代码7.8

public class H7_8 {//创建类
	public String toString() { 						//重写toString()方法
		return "在" + getClass().getName() + "类中重写toString()方法";//输出"在" + getClass().getName() + "类中重写toString()方法
	}
	public static void main(String[] args) {//主函数
		System.out.println(new H7_8()); 	//打印本类对象
	}
}

 结果

在本实例中重写父类Object的toString( )方法,在子类的toString( )方法中使用Object 类中曲getClass( )方法获取当前运行的类名,定义一段输出字符串,当用户打印ObjectInstance类对象时,将自动调用toString( )方法。

3. equals()方法
        前面章节曾讲解过equals()方法,当时是比较“==”运算符与equalsl()方法,说明“==”比较的是两个对象的引用是否相等,而equals0方法比较的是两个对象的实际内容。 

代码7.9

class V { // 自定义类V
}
public class H7_9 {//创建类
	public static void main(String[] args) {//主函数
		String s1 = "123"; // 实例化两个对象,内容相同
		String s2 = "123"; // 实例化两个对象,内容相同
		System.out.println(s1.equals(s2)); // 使用equals()方法调用
		V v1 = new V(); // 实例化两个V类对象
		V v2 = new V();// 实例化两个V类对象
		System.out.println(v1.equals(v2)); // 使用equals()方法比较v1与v2对象
	}
}

结果

7.3 类的多态
多态意为一个名字可具有多种语义,在程序设计语言中,多态性是指“一种定义,多种实现”例如,运算符“+”作用于两个整型量时是求和,而作用于两个字符型量时则是将其连接在一起。利用多态可以使程序具有良好的扩展性,并可以对所有类对象进行通用的处理。类的多态性可以从两方面体现:一是方法的重载,二是类的上下转型。

7.3.1 方法的重载
在第6章中曾学习过构造方法,知道构造方法的名称由类名决定,所以构造方法只有一一个名称,但如果希望以不同的方式来实例化对象,就需要使用多个构造方法来完成。由于这些构造方法都需要根据类名进行命名,为了让方法名相同而形参不同的构造方法同时存在,必须用到“方法重载”。虽然方法重载起源于构造方法,但是它也可以应用到其他方法中。本节将讲述方法的重载。
东法的重获就是在网个类中允许网时存在个以 上的同名方法,只要这些方法的参数个数或类型不同即可。

代码7.10
 

public class H7_10 {//创建类
	// 定义一个方法
	public static int add(int a) {// 定义一个方法
				return a;//定义a
			}
	public static int add(int a, int b) {// 定义与第一个方法参数个数不同的方法
				return a + b;//定义a+b
			}
	public static double add(double a, double b) {// 定义与第一个方法相同名称、参数类型不同的方法
				return a + b;//定义a+b
			}
	public static int add(int a, double b) {// 定义一个成员方法
				return (int) (a + b);//定义a+b
			}
	public static int add(double a, int b) {// 这个方法与前一个方法参数次序不同
				return (int) (a + b);//定义a+b
			}
	public static int add(int... a) {// 定义不定长参数
				int s = 0;//定义s初值
	  for (int i = 0; i < a.length; i++) {// 根据参数个数循环操作
		 s += a[i];// 将每个参数的值相加
	}
		return s;// 将计算结果返回
	}
	public static void main(String args[]) {//主方法
			System.out.println("调用add(int)方法:" + add(1));//输出"调用add(int)方法:" + add(1)
			System.out.println("调用add(int,int)方法:" + add(1, 2));//输出"调用add(int,int)方法:" + add(1, 2)
			System.out.println("调用add(double,double)方法:" + add(2.1, 3.3));//输出"调用add(double,double)方法:" + add(2.1, 3.3)
			System.out.println("调用add(int a, double b)方法:" + add(1, 3.3));//输出"调用add(int a, double b)方法:" + add(1, 3.3)
			System.out.println("调用add(double a, int b) 方法:" + add(2.1, 3));//输出"调用add(double a, int b) 方法:" + add(2.1, 3)
			System.out.println("调用add(int... a)不定长参数方法:"+ add(1, 2, 3, 4, 5, 6, 7, 8, 9));//输出"调用add(int... a)不定长参数方法:"+ add(1, 2, 3, 4, 5, 6, 7, 8, 9)
			System.out.println("调用add(int... a)不定长参数方法:" + add(2, 3, 4));//输出"调用add(int... a)不定长参数方法:" + add(2, 3, 4)
	}
 
}

结果

 

7.3.2 向上转型
 对象类型的转换在Java编程中经常遇到,主要包括向上转型与向下转型操作。本节将首先介绍向上转型。
        因为平行四边形是特殊的四边形,也就是说平行四边形是四边形的一种,那么就可以将平行四边形对象看作是一一个四边形对象。例如,鸡是家禽的一种, 而家禽是动物中的一类,那么也可以将鸡对象看作是一个动物对象。 

代码7.11

class Quadrangle { // 四边形类
	public static void draw(Quadrangle q) { // 四边形类中的方法
		// SomeSentence
	}
}
public class H7_11 extends Quadrangle{//子类继承父类
	public static void main(String args[]) {//主函数
		H7_11 p = new H7_11(); // 实例化平行四边形类对象引用
		draw(p); // 调用父类方法
	}}

平行四边形类继承了四边形类,四边形类存在一一个 draw0方法,它的参数是Quadranol(四边形类)类型,而在平行四边形类的主方法中调用draw0时给予的参数类型却是Prllelogram (平行四边形类)类型的。这里一直在强调- 一个问题, 就是平行四边形也是种类型的四边形, 所以可以物平行四边形类的对象看作是个四边形类的对象, 这就相当于“Quadrangleobj = new Parallelogram0;”,就是把子类对象赋值给父类类型的变量,这种技术被称为“向上转型”。试想一.下正方形类对象可以作为draw0方法的参数,梯形类对象同样也可以作为draw0方法的参数,如果在四边形类的draw0方法中根据不同的图形对象设置不同的处理,就可以做到在父类中定义一个方法完成各个子类的功能,这样可以使同一份代码毫无差别地运用到不同类型之上,这就是多态机制的基本思想。平行四边形类继承了四边形类,平行四边形类 与四边承图都是将顶级类设置在页面的顶部,然后逐渐向下,所以将子类对形类的关系对象看作是父类对象被称为“向上转型”。由于向上转型是从一个较具体的类到较抽象的类的转换,所以它总是安全的,如可以说平行四边形是特殊的四边形,但不能说四边形是平行四边形。


7.3.3 向下转型

通过向上转型可以推理出向下转型是将较抽象类转换为较具体的类。这样的转型通常会出现问题,例如,不能说四边形是平行四边形的一种、 所有的鸟都是鸽子,因为这非常不合乎逻辑。可以说子类对象总是父类的一个实例, 但父类对象不一定是子类的实例。 

代码7.12

class Quadrangle {//父类,四边形类
	public static void draw(Quadrangle q) {//四边形类中的办法
		// SomeSentence
	}
}
public class H7_12 extends Quadrangle {//平行四边形类,继承了四边形类
	public static void main(String args[]) {//主函数
		draw(new H7_12());
		// 将平行四边形类对象看作是四边形对象,称为向上转型操作
		Quadrangle q = new H7_12();// 将父类对象赋予子类对象
		H7_12 p = (H7_12) q; // 将父类对象赋予子类对象
		Parallelogram p = (Parallelogram) q;//将父类对象赋予子类对象,并强制转换为子类型
	}
}

结果

 如果将父类对象直接赋予子类,会发生编译器错误,因为父类对象不一定是子类的实例。例如,一个四边形不一定就是指平行四边形,它也许是梯形,也许是正方形,也许是其他带有四条边的不规则图形。
        越是具体的对象具有的特性越多,越抽象的对象具有的特性越少。在做向下转型操作时,将特性范围小的对象转换为特性范围大的对象肯定会出现问题,所以这时需要告知编译器这个四边形就是平行四边形。将父类对象强制转换为某个子类对象,这种方式称为显式类型转换。

7.3.4 instanceof关键字
当在程序中执行向下转型操作时,如果父类对象不是子类对象的实例,就会发生Class,CastException异常,所以在执行向下转型之前需要养成一个 良好的习惯,就是判断父类对象是否子类对象的实例。这个判断通常使用instanceof操作符来完成。可以使用instanceof操作符判断是口一个类实现了某个接口,也可以用它来判断一个实例对象是否属于个类。
instanceof的语法格式如下:                                                                                                              myobject instanceof ExampleClass
myobject: 某类的对象引用。
ExampleClass: 某个类。

 使用instanceof操作符的表达式返回值为布尔值。如果返回值为true, 说明myobject对象对ExampleClas的实例对象;如果返回值为false, 说明myobjet对象不是ExmpleClas的实例对象。

代码7.13

class Square extends Quadrangle {//Square继承Quadrangle
		// SomeSentence
	}
 
	class Anything {//普通类
		// SomeSentence
	}
public class H7_13 extends Quadrangle {//H7_13继承Quadrangle
	public static void main(String args[]) {//主方法
		Quadrangle q = new Quadrangle(); // 实例化父类对象
		// 判断父类对象是否为H7_13子类的一个实例
		if (q instanceof H7_13) {// 判断父类对象是否为H7_13子类的一个实例
			H7_13 p = (H7_13) q; // 进行向下转型操作
		}
		if (q instanceof Square) {		// 判断父类对象是否为H7_13子类的一个实例
			Square s = (Square) q; // 进行向下转型操作
		}
        // 由于q对象不为Anything类的对象,所以这条语句是错误的
		// System.out.println(q instanceof Anything);
	}
}
 
	

结果

符与向下转型操作结合使用。在程序中定义了两个子类,即平行四边形类和正方形类,这两个类分别继承四边形类。在主方法中首先创建四边形类对象,然后使用instanceof操作符判断四边形类对象是否为平行四边形类的一 个实例,是否为正方形类的一 个实例,如果判断结果为true,将进行向下转型操作。

7.4 抽象类与接口
通常可以说四边形具有4条边,或者更具体点,平行四边形是具有对边平行且相等特性的特殊四边形,等腰三角形是其中两条边相等的三角形,这些描述都是合乎情理的,但对于图形对象却不能使用具体的语言进行描述,它有几条边,究竟是什么图形,没有人能说清楚,这种类在Java中被定义为抽象类。

7.4.1 抽象类与抽象方法
在解决实际问题时,一般将父类定义为抽象类,需要使用这个父类进行继承与多态处理。回想继承和多态原理,继承树中越是在上方的类越抽象,如鸽子类继承鸟类、鸟类继承动物类等。在多态机制中,并不需要将父类初始化对象,我们需要的只是子类对象,所以在Java语言中设置抽象类不可以实例化对象,因为图形类不能抽象出任何一种具体图形,但它的子类却可以。
Java 中定义抽象类时,需要使用 abstract 关键字,其语法如下:                                                   

  [权限修饰符] abstract class 类名{
类体                                                                                                                                                     } 

 使用absrnat关键字定义的类称为抽象类,而使用abtect关键字定义的方法称为抽象方法,抽象方法的定义语法如下:
从上面的语法可以看出,抽象方法是直接以分号结尾的,它没有方法体,抽象方法本身没有任何意义,除非它被重写, 而承载这 个抽象方法的抽象类必须被继承, 实际上,抽象类除了被继承之外没有任何意义。


继承抽象类的所有子类都需要将抽象类中的抽象方法进行覆盖,这样在多态机制中,就可以将父类修改为抽象类,将draw(方法设置为抽象方法,然后每个子类都重写这个方法来处理。

注意:构造方法不能定义为抽象方法。 

代码7.14

public abstract class Market {//创建类
	public String name;//商场名称
	public String goods;   //商品名称
	public abstract void shop();//抽象方法,用来输出信息
}
public class TaobaoMarket extends Market{//TaobaoMarket继承Market
	@Override
	public void shop() {//购物
		System.out.println(name+"网购"+goods); //输出网购+goods
	}
}
public class WallMarket extends Market{//WallMarket继承Market
	@Override
	public void shop() {//购物
		System.out.println(name+"实体店购买"+goods);//输出实体店购买+goods
	}
}
public class H7-14 {//创建类
	public static void main(String[] args) {//主方法
		Market market = new WallMarket();// 使用派生类对象创建抽象类对象
		market.name = "沃尔玛";//沃尔玛
		market.goods = "七匹狼西服";//七匹狼西服
		market.shop();//创建商店
		market = new TaobaoMarket();// 使用派生类对象创建抽象类对象
		market.name = "淘宝"; //淘宝
		market.goods = "韩都衣舍花裙";//韩都衣舍碎花裙
		market.shop();//创建商店
	}
}

 

 综上所述,使用抽象类和抽象方法时,需要遵循以下原则:
(1)在抽象类中,可以包含抽象方法,也可以不包含抽象方法,但是包含了抽象方法的类必须被定义为抽象类。
(2)抽象类不能直接实例化,即使抽象类中没有声明抽象方法,也不能实例化。
(3)抽象类被继承后,子类需要实现其中所有的抽象方法。
(4)如果继承抽象类的子类也被声明为抽象类,则可以不用实现父类中所有的抽象方法。

使用抽象类时,可能会出现这样的问题:程序中会有太多冗余的代码,同时这样的父类局限性很大,例如,上面的例子中,也许某个不需要shop()方法的子类也必须重写shop()方法。如果将这个shop()方法从父类中拿出,放在别的类里,又会出现新问题,就是某些类想要实现“买衣服”的场景,竟然需要继承两个父类。Java 中规定,类不能同时继承多个父类,面临这种问题时,接口的概念便出现了。

7.4.2接口的声明及实现 
        使用抽象类时,可能会出现这样的问题:程序中会有太多冗余的代码,同时这样的父类局限性很大,例如,上面的例子中,也许某个不需要shop()方法的子类也必须重写shop()方法。如果将这个shop()方法从父类中拿出,放在别的类里,又会出现新问题,就是某些类想要实现“买衣服”的场景,竟然需要继承两个父类。Java 中规定,类不能同时继承多个父类,面临这种问题时,接口的概念便出现了。

接口使用interface 关键字进行定义,其语法如下:

[修饰符] interface 接口名[extends 父接口名列表] {undefined
    [public] [static] [final] 常量;
    [public] [abstract] 方法;
}

修饰符:可选,用于指定接口的访问权限,可选值为public。 如果省略则使用默认的访问权限。
接口名: 必选参数,用于指定接口的名称,接口名必须是合法的Java标识符。一般情况下,要求首字母大写。                                                                                                                             extends 父接口名列表:可选参数,用于指定要定义的接口继承于哪个父接口。当使用extends关键字时,父接口名为必选参数。
方法:接口中的方法只有定义而没有被实现。

一个类实现一个接口可以使用implements 关键字,代码如下: 

public class Parallelogram extends Quadrangle implements drawTest{undefined
    ...//

}

接口是抽象类的延伸,可以将它看作是纯粹的抽象类,接口中的所有方法都没有方法体。对于1小节中遗留的问题,可以将draw()方法封装到一个接口中,这样可以让一个类既能继承图形类,又能实现draw()方法接口,这就是接口存在的必要性。

代码7.15

interface drawTest { // 定义接口
	public void draw(); // 定义方法
}
class ParallelogramgleUseInterface implements drawTest {// 定义平行四边形类,该类实现了drawTest接口
	public void draw() { // 由于该类实现了接口,所以需要覆盖draw()方法
		System.out.println("平行四边形.draw()");//输出平行四边形
	}
}
class SquareUseInterface implements drawTest {//定义正方形类,该类实现了drawTest接口
	public void draw() {// 由于该类实现了接口,所以需要覆盖draw()方法
		System.out.println("正方形.draw()");// 输出正方形
	}
}
public class H7-15 {//创建类
	public static void main(String[] args) {//主方法
		drawTest[] d = { // 接口也可以进行向上转型操作
				new SquareUseInterface(), new ParallelogramgleUseInterface() };//新建数组
		for (int i = 0; i < d.length; i++) {//控制长度,累加
			d[i].draw(); // 调用draw()方法
		}
	}
}

在本实例中,正方形类与平行四边形类分别实现了drawTest接口,所以需要覆盖接口中的方法。在调用draw()方法时,首先将平行四边形类对象与正方形类对象向上转型为drawTest接口形式。这里也许很多读者会有疑问,接口是否可以向上转型?其实在Java 中无论是将一个类向上转型为父类对象,还是向上转型为抽象父类对象,或者向上转型为该类实现接口,都是没有问题的。然后使用d[i]数组中的每一个对象调用draw(), 由于向上转型,所以d[i]数组中的每一个对象分别代表正方形类对象与平行四边形类对象,最后结果分别调用正方形类与平行四边形类中覆盖的draw()方法。

7.4.3 多重继承
在Java中类不允许多重继承,但使用接口就可以实现多重继承,因为一个类可以同时实现多个接口,这样可以将所有需要实现的接口放置在implements关键字后并使用逗号“,”隔开,但这可能会在一个类中产生庞大的代码量,因为继承一个接口时需要实现接口中所有的方法。

代码7.16


 

public interface IFather {//定义一个接口
	void smoking();//抽烟的方法
	void goFishing();//钓鱼的方法
}
public interface IMother {//定义一个接口
	void watchTV();//看电视的方法
	void cooking();//做饭的方法
}
public class H7-16 implements IFather,IMother{ //创建一个Me类继承IFather接口和IMother接口
	@Override
	public void watchTV() {// 重写watchTV()方法
		System.out.println("我喜欢看电视");//输出我喜欢看电视
	}
	@Override
	public void cooking() {// 重写cook()方法
		System.out.println("我喜欢做饭");//输出我喜欢做饭
	}
	@Override
	public void smoking() {// 重写smoke()方法
		System.out.println("我喜欢抽烟"); //输出我喜欢抽烟
	}
	@Override
	public void goFishing() {// 重写goFishing()方法
		System.out.println("我喜欢钓鱼");//输出我喜欢钓鱼
	}
	public static void main(String[] args) {//主方法
		IFather father = new H7-16();// 通过子类创建IFather接口对象
		System.out.println("爸爸的爱好:"); //输出爸爸的爱好
		father.smoking();// 使用接口对象调用子类中实现的方法
		father.goFishing();// 使用接口对象调用子类中实现的方法
		IMother mother =new H7-16();// 通过子类创建IMather接口对象
		System.out.println("\n妈妈的爱好:"); //输出妈妈的爱好
		mother.cooking();// 使用接口对象调用子类中实现的方法
		mother.watchTV();// 使用接口对象调用子类中实现的方法
	}
}
}

7.4.4 区分抽象类与接口
抽象类和接口都包含可以由子类继承实现的成员,但抽象类是对根源的抽象,而接口是对动抽象。抽象类的功能要远超过接口,那为什么还要使用接口呢?这主要是由于定义抽象类的个高(因为每个类只能继承一个类,在这个类中,必须继承或编写出其子类的所有共性,因此,虽然接口在功能上会弱化许多,但它只是针对一个动作的描述,而且可以在一个类中同时实现多个接这样会降低设计阶段的难度。
抽象类和接口的区别主要有以下几点。
(1)子类只能继承一个抽象类,但可以实现任意多个接口。
(2)一个类要实现一个接口必须实现接口中的所有方法,而抽象类不必。
(3)抽象类中的成员变量可以是各种类型,而接口中的成员变量只能是public static final 的(4)接口中只能定义抽象方法,而抽象类中可以定义非抽象方法。(5)抽象类中可以有静态方法和静态代码块等,接口中不可以。
(6)接口不能被实例化,没有构造方法,但抽象类可以有构造方法。

抽象类与接口的不同

 7.5 访问控制
前面多次提到了public、private、包等关键字或者概念,这些都是用来控制类、方法或者变量的访问范围的,Java 中主要通过访问控制符、类包和final关键字对类、方法或者变量的访问范围行控制,本节将对Java中访问控制知识进行详细讲解

7.5.1 访问控制符
前面介绍了面向对象的几个基本特性,其中包括封装性,封装实际上有两方面的含义:把该隐藏的隐藏起来、把该暴露的暴露出来,这两个方面都需要通过使用Java 提供的“访问控制符”来实现,本节将对Java 中的访问控制符进行详细讲解。
Java 中的访问控制符主要包括 public、protected、private 和default(缺省)等4种,这些控制符控制着类和类的成员变量以及成员方法的访问权限。

Java语言中的访问控制符权限

注意:声明类时,如果不使用public修饰符设置类的权限,则这个类默认为default(缺省)修饰。 

 使用访问控制符时,需要遵循以下原则。                                                                                       (1)大部分顶级类都使用 public 修饰;
(2)如果某个类主要用作其他类的父类,该类中包含的大部分方法只是希望被其子类重写,而不想被外界直接调用,则应该使用protected修饰;
(3)类中的绝大部分属性都应该使用private修饰,除非一些static或者类似全局变量的属性才考虑使用 public 修饰;
(4)当定义的方法只是用于辅助实现该类的其他方法(即工具方法),应该使用private 修(5)希望允许其他类自由调用的方法应该使用public 修饰。

7.5.2 Java 类包
在Java义好类,通Java编译器进行编译之后,都会生成一个扩展名为.class的文件,当这个程序的规模逐渐庞大时,就很容易发生类名称冲突的现象。那么JDKAPI中提供了成千上万具有各种功能的类,又是如何管理的呢?Java 中提供了一种管理类文件的机制,就是类包。
Java中每个接口或类都来自不同的类包,无论是JavaAPI中的类与接口还是自定义的类与接口都需要隶属于某一个类包,这个类包包含了一些类和接口。如果没有包的存在,管理程序中的类名称将是一件非常麻烦的事情,如果程序只由一个类定义组成,并不会给程序带来什么影响,但是随着程序代码的增多,难免会出现类同名的问题。例如,在程序中定义一个 Login 类,因业务需要,还要定义一个名称为Login的类,但是这两个类所实现的功能完全不同,于是问题就产生了,编译器不会允许存在同名的类文件。解决这类问题的办法是将这两个类放置在不同的类包中,实际上 Java中类的完整名称是包名与类名的组合。

 (1)在项目的sre节点上单击鼠标右键,选择“New- Package"命令。
(2)弹出New Java Package对话框,在Name文本框中输入新建的包名,如om.migy然后单击"Finish” 按钮。

(3)在Eclipse 中创建类时,可以在新建立的包上单击鼠标右键,选择“New" 命令,这样街建的类会默认保存在该包中。另外也可以在NewJavaClass对话框中指定新建类所在的包。在Java中包名设计应与文件系统结构相对应,如一个包名为com.mingrisoft,那么该包中的类位于com文件夹下的mingrisoft子文件夹下。没有定义包的类会被归纳在预设包(默认包)中。在实际开发中,应该为所有类设置包名,这是良好的编程习惯。

在类中定义包名的语法如下:
package包名1[.包名2[.包名3...]];
在上面的语法中,包名可以设置多个,包名和包名之间使用.分割,包名的个数没有限制,其中前面的包名包含后面的包名。
在类中指定包名时需要将package放置在程序的第一行, 它必须是文件中的第一行非注释代码,当使用package关键字为类指定包名之后,包名会成为类名中字的部分,预示着这个类必须指定全名,例如,在使用位于com.mingrisoft包下的Dog.java类时,需要使用形如com.mingrisoft.Dog这样的格式。

注意:Java包的命名规则是全部使用小写字母,另外,由于包名将转换为文件的名称,所以包名中不能包含特殊字符。

定义完包之后,如果使用包中的类,可以使用Java中的import关键字指定。

import包名1[.包名2[.包名3...] ].类名;

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

import com.lzw.*;           //指定import com.lzw包中的所有类在程序中都可以使用                  import com.lzw.math       //指定import com.lzw包中的math类在程序中都可以使用

注意:如果类定义中已经导入com.lzw.Math类,在类体中还想使用其他包中的Math类时,则必须使用完整的带有包格式的类名,比如,这种情况再使用java.lang包的Math类时就要使用全名格式java. lang.Math。
        在程序中添加import关键字时,当使用import指定了一个包中的所有类,并不会指定这个包的子包中的类,如果用到这个包中的子类,则需要再次对子包单独引用。

7.5.3 final 关键字 
1.final

定义为final的类不能被继承。
如果希望一个类不允许任何类继承,并且不允许其他人对这个类进行任何改动,可以将这个类设置为final形式。                                                                                                                                        final类的语法如下:
        final class 类名{ }
        如果将某个类设置为fnal 形式,则类中的所有方法都被隐式地设置为final 形式,但是final类中的成员变量可以被定义为final 或非final形式。

代码7.17

final class FinalClass {//final类
	int a = 3; //定义初值
	void doit() {//调用 doit()方法
	}
	public static void main(String args[]) {//主方法
		FinalClass f = new FinalClass();//新建数组
		f.a++;//累加
		System.out.println(f.a);//输出结果
	}
}


结果


2.final方法

首先,读者应该了解定义为final的方法不能被重写。
将方法定义为final 类型可以防止子类修改该类的定义与实现方式,同时定义final 的方法的执行效率要高于非final方法。在修饰权限中曾经提到过private修饰符,如果一个父类的某个方法被设置为private修饰符,子类将无法访问该方法,自然无法覆盖该方法, 所以一个定义为private的方法隐式被指定为final类型,这样无需将一个定义为private的方法再定义为final类型。例如下面的语句:

private final void test() {
    ...//省略些程序代码
}
 但是在父类中被定义为private final的方法似乎可以被子类覆盖.
代码7.18

class Parents{//创建父类
	private final void doit() {//调用final类
		System.out.println("父类.doit()");//输出调用父类
	}
	final void doit2() {//调用doit2()方法
	    System.out.println("父类.doit2()");//输出调用父类
	  }
	public void doit3() {//调用doit3()方法
		   System.out.println("父类.doit3()");//输出调用父类
		  }
}
class Sub extends Parents {//Sub继承Parents类
	  public final void doit() { //在子类中定义一个doit()方法
	   System.out.println("子类.doit()");//输出调用子类
	  }
	// final void doit2(){ //final 方法不能覆盖 
	  //  System.out.println("子类.doit2()"); //输出调用子类
	  // }
	  public void doit3() {//调用doit3()方法
		  System.out.println("子类.doit3()");//输出调用子类
	 } 
}
public final class FinalMethod {//创建类
	public static void main(String[] args) {//主方法
		Sub s=new Sub(); //实例化 
		   s.doit(); //调用 doit()方法 
		   Parents p=s; //执行向上转型操作 
		   //p.doit(); //不能调用private方法 
		   p.doit2(); //调用 doit2()方法
		   p.doit3();//调用 doit3()方法
	}
}

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

3.final变量

        final关键字可用于变量声明,一旦该变量被设定,就不可以再改变该变量的值。通常,由final定义的变量为常量。例如,在类中定义PI值,可以使用如下语句:

final double PI=3.14;
当在程序中使用PI这个常量时,它的值就是3.14,如果在程序中再次对定义为final的常量赋值,编译器将不会接受。

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

代码7.19
 

import static java.lang.System.out; //导入要用到的包
import java.util.Random;//导入要用到的包
class Test {//类名
	 int i = 0;//定义i=0
	}
public final class H7_19 { //创建类
	 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);//声明一个final常量
	 private static final int i5= rand.nextInt(20); //声明一个final常量
	 public String toString() {//调用toString()
	  return i4 +" "+i5+" ";//输出结果
	 }
public static void main(String[] args){//主方法
	  FinalData data = new H7_19(); //创建新数组
	  data.test=new Test();//输出结果
	  //可以对指定为final的引用中的成员变量赋值
	  //但不能将定义为final的引用指向其他引用 
	  //data.VALUE_2++;
	  //不能改变定义为final的常量值
	  data.test2=new Test(); //可以将没有定义为 final的引用指向其他 
	  for (int i = 0; i < data.a.length; i++) { //控制长度
	  //a[i]=9; 
	  //不能对定义为final的数组赋值
	  }
	  out.println(data);//输出data结果
	  out.println("data2");//输出data2结果
	  out.println(new H7_19());//输出数组结果
	  out.println(data);//输出data结果
	 }
}

代码7.20

import java.util.Random;//导入要用到的包
import static java.lang.System.out; //导入要用到的包
public class H7_20 {//创建类
	private static Random rand = new Random();//实例化一个random类对象
	 private final int a1 = rand.nextInt(10);//随机产生0~10之间的随机数赋予定义为final的a1
	 private static final int a2 = rand.nextInt(10);//随机产生0~10之间的随机数赋予定义为static final的a2
	 public static void main(String[] args){//主方法
		 FinalStaticData fdata = new H7_20 ();//实例化一个对象
		 out.println("重新实例化对象调用a1的值:"+ fdata.a1);//调用定义为final的a1
		 out.println("重新实例化对象调用a2的值:"+ fdata.a2);//调用定义为static final的a2
		 FinalStaticData fdata2= new H7_20 ();//实例化另外一个对象
		 out.println("重新实例化对象调用a1的值:"+ fdata2.a1);//输出结果
		 out.println("重新实例化对象调用a2的值:"+ fdata2.a2);//输出结果
	 }
}

从本实例的运行结果中可以看出,定义为final 的常量不是恒定不变的,将随机数赋予定义为fnal的常量,可以做到每次运行程序时改变al的值。但是a2与al不同,由于它被声明为static final形式,所以在内存中为a2开辟了一个恒定不变的区域,当再次实例化一个FinalStaticData对象时,仍然指向a2这块内存区域,所以a2的值保持不变。a2是在装载时被初始化,并不是每次创建新对象时都被初始化,而al会在重新实例化对象时被更改。

技巧:在Java中定义全局常量,通常使用public static final修饰,这样的常量只能在定义时被赋值。

        可以将方法的参数定义为final类型,这预示着无法在方法中更改参数引用所指向的对象。
最后总结一下在程序中final 数据可以出现的位置。图7.27清晰地表明了在程序中哪些位置可以定义final 数据。 

7.6 内部类 
 前面曾经学习过在一个 文件中定义两个类,但其中任何一个类都不在另一个类的内部,而如果在类中再定义一个类,则将在类中再定义的那个类称为内部类,这里可以想像一下汽车和发动机的关系,很显然,此处不能单独用属性或者方法表示一个发动机,发动机是一个类, 而发动机又在汽车之中,汽车也是一个类,正如同内部类在外部类之中,这里的发动机类就好比是一个内部类。中部类可分为成员内部类、局部内部类以及匿名类。本节将对内部类的使用进行讲解。

7.6.1成员内部类
1.成员内部类简介

 在一个类中使用内部类,可以在内部类中直接存取其所在类的私有成员变量。本节首先介的成员内部类。

成员内部类的语法如下:

public class OuterClass{     //外部类
    private class InnerClass {   //内部类
      //...
    }
}

 在内部类中可以随意使用外部类的成员方法以及成员变量,尽管这些类成员被修饰为private图7.28充分说明了内部类的使用,尽管成员变量i以及成员方法g()都在外部类中被修饰为private但在内部类中可以直接使用外部类中的类成员。

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

代码7.21


 

 
public class H7_21 {//创建类
	innerClass in = new innerClass();//在外部类实例化内部类对象引用
	public void ouf() {//  普通类
          in.inf();     //在外部类方法中调用内部类方法
	}
	class innerClass{//创建innerClass
		innerClass(){//内部类构造方法
		}
		public void inf() { //内部类成员方法
		}
		int y = 0;//定义内部类成员变量
	}
	public innerClass doit(){ //创建innerClassdoit()方法
		in.y = 4;//定义y=4
		return new innerClass();//新建innerClass
	}
	public static void main(String[] args) {//主方法
		OuterClass out = new H7_21();//创建新数组
		OuterClass.innerClass in = out.doit();//创建新数组doit()
		OuterClass.innerClass in2 = out.new innerClass();//创建新数组innerClass()
	}
}

 在例题的主方法中如果不使用doit( )方 法返回内部类对象引用,可以直接使用内部类实例化内部类对象,但由于是在主方法中实例化内部类对象,必须在new操作符之前提供一个外部类的引用。
例如,在主方法中实例化一个内 部类对象。

public static void main (String args[]) {//主函数
    OuterClass out=new ”OuterClass() ;//实例化一个对象
    OuterClass. innerClass in=out.doit() ;//实例化一个对象
    OuterClass. innerClass in2=out.new innerClass();//实例化内部类对象
}

从上面代码可以看出,在实例化内部类对象时,不能在new操作符之前使用外部失实明化内部类对象,而应该使用外部类的对象来创建其内部类的对象。

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

2.内部类向上转型为接口

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

interface OutInterface {//定义一个接口
	 public void f(); //普通类
	}
public classH7_22 {//创建类
	public static void main(String[] args) {//主方法
		OutClass2 out = new OutClass2();//实例化一个OutClass2对象
		  OutInterface  outinter = out.doit();//调用doit()方法,返回一个OutInterface 接口
		  outinter.f();//调用f()方法
		 }
		}
		 class OutClass2 { //定义一个内部类实现OutInterface接口
		  private class InnerClass implements OutInterface {//定义一个内部类实现OutInterface接口
		   InnerClass(String s) {//返回参数
		     System.out.println(s);// 输出结果
		   }
		   public  void f() {//实现接口中的f()方法
		     System.out.println("访问内部类中的f()方法");//输出访问内部类中的f()方法
		   }
		  }
		  public  OutInterface doit() {//定义一个方法,返回值类型OutInterface接口
		   return new InnerClass ("访问内部类构造方法");//输出访问内部类构造方法
	}
}

 从上述实例中可以看出,OuterClass2 类中定义了一个修饰权限为private的内部类,这个内部类实现了Outlnterface 接口,然后修改doit(方法,使该方法返回个Outlnterface 接口。由于内部类InmerClass修饰权限为private,所以除了OuterClass2 类可以访问该内部类之外,其他类都不能访问,而可以访问doit(方法。由于该方法返回一一个外部接口类型,这个接口可以作为外部使用的接口。它包含一个f()方法,在继承此接口的内部类中实现了该方法,如果某个类继承了外部类,由于内部的权限不可以向下转型为内部类InnerClass,同时也不能访问f0方法,但是却可以访问接口中的f0方法。例如,InterfaceInner 类中最后一条语句,接口引用调用f0方法,从执行结果可以看出,这条语句执行的是内部类中的f()方法,很好地对继承该类的子类隐藏了实现细节,仅为编写子类的人留下个接口和一个外部类,同时也可以调用f()方法,但是f()方法的具体实现过程却被很好地隐藏了,这就是内部类最基本的用途。

注意:非内部类不能被声明为private或protected访问类型。

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

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

代码7.23

 
public class H7_23 {//创建类
 private int x;    //定义private int x方法
 private class Inner{    //普通方法
  private int x = 9;    //定义private int x=9
  public void doit(int x) {       //调用的是形参x 
   x++;     //调用的是形参x
   this.x++;     //调用内部类的变量x
   TheSameName.this.x++;     //调用外部类的变量x
  }
 }
}

在类中,如果遇到内部类与外部类的成员变量重名的情况,可以使用this关键字进行处理。例如,在内部类中使用this.x语句可以调用内部类的成员变量x,而使用TheSameName.this.x语句可以调用外部类的成员变量x,即使用外部类名称后跟一个点操作符和this关键字便可获取外部类的一个引用。


读者应该明确一点,在内存中所有对象均被放置在堆中,方法以及方法中的形参或局部变量放置在栈中。在图中,栈中的doit( )方法指向内部类的对象,而内部类的对象与外部类的对象是相互依赖的,Outer.this对象指向外部类对象。

综上所述,使用成员内部类时,应该遵循以下原则:
(1)可以有各种修饰符,可以用private、 public、 protected、 static、 final、 abstract等修饰;
(2)如果内部类有static限定,就是类级别的,否则为对象级别。类级别可以通过外部类直接访问,对象级别需要先生成外部的对象后才能访问;
(3)内外部类不能同名;
(4)非静态内部类中不能声明任何static成员;
(5)内部类可以互相调用。 

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

代码7.24

 
public interface H7_24 {  //创建类
}
class OuterClass3 {    //普通类
  public OutInterface2 doit(final String x) {        // doit()方法参数为final类型
   class InnerClass2 implements H7_24 {     // 在doit()方法中定义一个内部类
    InnerClass2(String s) {   // 在doit()方法中定义一个内部类
     s = x;         //定义s=x
     System.out.println(s);   //输出s
    }
   }
   return new InnerClass2("doit");     // 输出结果doit()方法中定义一个内部类
  }
}

从上述代码中可以看出,内部类被定义在了doit()方法内部。但是有一点值得注意,内部类InnerClass2是doit()方法的一部分, 并非OuterClass3类中的一部分, 所以在doit()方法的外部不能间该内部类,但是该内部类可以访问当前代码块的常量以及此外部类的所有成员。
        有的读者会注意到例题中的一个修改细节,就是将doit()方法的参数设置为fnal类型。如果需要在方法体中使用局部变量,该局部变量需要被设置为final 类型,换句话说,在方法中定义的内部类只能访问方法中final类型的局部变量,这是因为在方法中定义的局部变量相当于一个常量, 它的生命周期超出方法运行的生命周期,由于该局部变量被设置为final,所以不能在内部类中改变该局部变量的值。

7.6.3 匿名内部类
该例题中定义的内部类再次进行修改,在doit()方法中将return语向和内部类定义语句合并在一起,下面通过一个实例说明。

代码7.25

 
public interface H7_25 {   //接口  
}
class OuterClass4 {      //类名
   public H7_25 doit() {    // 定义doit()方法
    return new H7_25() {     // 声明匿名内部类
     private int i = 0;     //定义i=0
     public int getValue() {    //int匿名内部类  
      return i;       //输出结果
     }
    };
   }
 }

从例题中可以看出,笔者将doit0方法修改得有一些莫名其妙, 但这种写法确实被Java编译认可,在dit0方法内部首先返回一个OutercC的引用,然后在retrm语向中插入一个定义内类的代码, 由于这个类没有名称,所以这里将该内部类称为匿名内部类。实质上这种内部类的作就是创建一 个实现于OutInterface2接口的匿名类的对象。
匿名类的所有实现代码都需要在大括号之间进行编写。

return new A() {undefined
    ...//内部类体
};

 其中,A指类名。

        由于匿名内部类没有名称,所以匿名内部类使用默认构造方法来生成Outinterface2对象。在匿名内部类定义结束后,需要加分号标识,这个分号并不是代表定义内部类结束的标识,而是代表创建OutInterface2引用表达式的标识。
说明:匿名内部类编译以后,会产生以“外部类名S5序号”为名称的icass文件,序号以1-n排列,分别代表1-n个匿名内部类。

使用匿名内部类时应该遵循以下原则:
(1)匿名类没有构造方法;
(2)匿名类不能定义静态的成员;
(3)匿名类不能用private pulie. protecte. stati. final. abstract 等修饰;
(4)只可以创建一个匿名类实例。

7.6.4 静态内部类
在内部类前添加修饰符static,这个内部类就变为静态内部类了。一个静态内部类中可以声明静态成员,但是在非静态内部类中不可以声明静态成员。静态内部类有一一个最大的特点,就是不能你用外部类的非静态成员,所以静态内部类在程序开发中比较少见。
        可以这样认为,普通的内部类对象隐式地在外部保存了一个引用,指向创建它的外部类对象,但如果内部类被定义为static,就会有更多的限制。静态内部类具有以下两个特点:
(1)如果创建静态内部类的对象,不需要创建其外部类的对象;
(2)不能从静态内部类的对象中访问非静态外部类的对象.

代码7.26


 

 
public class H7_26 { {//创建类
	int x = 100;//定义x的值为100
	static class Inner{//普通类名
		void doitInner() {//doitInner() 调用方法
		 // System.out.println("外部类"+x);//输出外部类+x
		  }
 
		  public static void main(String args[]) {//主方法
		   System.out.println();//换行
		}
	}
}

 

7.6.5 内部类的继承

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

代码7.27

 
public class H7_27 extends ClassA.ClassB { // 继承内部类ClassB
	public H7_27(ClassA a) {//继承类中内部类
		   a.super();//构造方法体中使用 a.super()
		  }
}
		 class ClassA {//类名A
		 class ClassB {//内部类名B
		  }
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值