《Java编程思想》第四版之内部类学习之(二)——匿名内部类

  版权声明:学习内容均为本人笔记,代码均为本人依据课本所写或改编,笔记均为个人心得或书中摘抄



引言:内部类,即将一个类的定义放在另一个类的定义内部。内部类与组合是完全不同的概念。内部类看似是一种代码的隐藏机制,其实,它能够了解外部类,并且与之通信,这为我们的编程提供了极大的方便。


1.4内部类与向上转型

内部类转型为基类或者为接口时,就获得了该基类或者接口的引用,此时,接口的实现完全不可见,内部类隐藏了实现的细节
//194页

interface Destination{
	String readLabel();
}
interface Contents{
	int value();
}
public class Parcel4 {
	private class PContents implements Contents{
		private int i=11;
		public int value(){
			return i;
		}
	}
	protected class PDestination implements Destination{
		private String label;
		public PDestination(String whereTo) {
			label=whereTo;
		}
		public String readLabel(){
			return label;
		}
	}
	public Destination destination(String s){//通过外部类方法创建内部类对象,并向上转型
		return new PDestination(s);
	}
	public Contents contents(){
		return new PContents();
	}
	public static void main(String[] args){
		Parcel4 p=new Parcel4();
		Destination c=p.destination("Tasmania");//向上转型为Destination接口
		Contents d=p.contents();
		//不能使用下面这种方法,因为PContents内部类为私有的
		//Parcel4.PContents pc=p.new PContents(); 
	}
}
我们可以看到外部类Parcel4中的内部类PContents是private类型的,除了Parcel4,无法访问它。内部类PDestination是protected,也仅仅只有Parcel4及其子类和同一个包中的类才具有访问权限。所以客户端程序员访问这两个内部类是受到限制的,但是可以通过访问Parcel4中的destination()方法和contents()方法获取接口的引用,然而内部类具体的实现被隐藏,客户端程序员并不知道,也不需要知道,只需要调用接口中相应的方法就好了。


1.5在方法和作用域内的内部类

可以在一个方法或者在任意的作用域内定义内部类。
局部内部类
public class Parcel5{
	public Destination destination(String s){
		class PDestination implements Destination{//内部类在方法中
			private String label;
			private PDestination(String whereTo){
				label=whereTo;
			}
			public String readLabel(){
				return label;
			}
		}
		return new PDestination(s);//只有在方法作用域内才可以使用作用域内的内部类
	}
	public static void main(String[] args){
		Parcel5 p=new Parcel5();
		Destination d=p.destination("Tasmania");
	}
}
作用域内的类与其他类共同编译,但是只在作用域内可用,在其他作用域中使用相同的类名不会有命名冲突

1.6匿名内部类

匿名内部类在创建某个对象进行返回时,对该对象的类进行定义。类的定义和使用放到了一起。下面根据具体例子说明情况
//197页

interface Contents{
	 int value();
}

public class Parcel7 {
	public Contents contents(){
		return new Contents() {//匿名内部类,类的使用与定义结合到了一起
			private int i=1;
			public int value(){
				return i;
			}
		};
	}
	public static void main(String[] args){
		Parcel7 p=new Parcel7();
		Contents c=p.contents();
	}
}
程序中我们可以看到,在contents()方法的内部,在返回了一个Contents()引用的时候,插入了一个类的定义。这里实际的情况是,创建了一个继承自Contents的匿名类的对象,通过new表达式返回的时候,实际上已经向上转型为对Contents的引用了。具体来说,就是下面的代码
interface Contents{
	int value();
}

public class Parcel7b {
	class MyContents implements Contents{//MyContents实现了Contents
		private int i=11;
		public int value(){
			return i;
		}
	}
	public Contents contents(){
		return new MyContents(); //contents()方法返回了一个MyContents对象,并且向上转型为Contents
	}
	public static void main(String[] args){
		Parcel7b p=new Parcel7b();
		Contents c=p.contents();
	}
}
上述匿名内部类使用了默认的构造器生成Contents,也可以使用有参数的构造器。
//197页

class Wrapping{
	private int i;
	public Wrapping(int x){
		i=x;
	}
	public int value(){
		return i;
	}
}

public class Parcel8 {
	public Wrapping wrapping(int x){
		return new Wrapping(x){//传递了适合基类构造器的参数
			public int value(){
				return super.value()*47;
			}
		};
	}
	public static void main(String[] args){
		Parcel8 p=new Parcel8();
		Wrapping w=p.wrapping(10);
		System.out.println(w.value());//Wrapping引用匹配到子类的方法
	}
}/*Output:
470*/
匿名内部类中可以看到,传入了一个适合基类构造器的参数。而且尽管Wrapping是一个具有具体实现的类,但是被导出类当作“接口”使用。

匿名内部类没有类名,没办法创建构造函数,那么如何进行初始化工作呢?
//199页

public class Parcel10 {
	public Destination destination(String dest,float price){
		return new Destination() {
			private int cost;
			{//带有实例初始化
				cost=Math.round(price);
				if(cost>100)
					System.out.println("Over budget!");
			}
			//下面这句话不能通过编译!
			//dest="newTasmania";
			private String label=dest;
			public String readLabel(){
				return label;
			}
		};
	}
	public static void main(String[] args){
		Parcel10 p=new Parcel10();
		Destination d=p.destination("Tasmania", 101.395F);
	}
}/*Output:
Over budget!*/
在实例初始化的内部,实现了构造器的行为——初始化,但是,你不能重载实例初始化方法, 所以你仅仅有一个这样的构造器。
注意:代码中,你看到了有一行不能通过编译,这是因为,在内部类使用的非final对象将会接受检查——它们不允许被修改。
《Java编程思想》这块,作者写的是内部类使用外部类对象必须要求是final类型的(作者使用的是JAVA8之前的版本),然而我使用的是JAVA8,JAVA8中,匿名内部类使用外部变量不再被强制要求用final修饰但是要求初始化后的值不能被修改,这是为何呢?
对于final类型来说:编译器编译后,final类型是常量,被存储到了常量池中,在匿名内部类中使用该变量的地方都被替换成了具体的常量值,关于外部类的变量的信息,内部类是不知道的。
对于非final类型来说:传入内部类的仅仅只是传值操作,所以在匿名内部类中改变这个值是无效的。如果在外部类中修改这个值,那么匿名内部类得到的参数值可能已经不是期望中的那个值。所以,在内部类使用外部类的变量时,不允许做任何修改才会避免所以问题。
JAVA8版本对于非final类型会进行检查,要求不允许修改。final变量自然不会被修改,也不会检查,JAVA8以前的版本要求必须是final变量才能给匿名内部类使用。

1.6.1再访工程方法

//201页

interface Game{
	boolean move();
}
interface GameFactory{
	Game getGame();
}

class Checkers implements Game{
	private Checkers(){}//构造器为private,不能直接创建对象
	private int moves=0;
	private static final int MOVES=3;
	public boolean move(){
		System.out.println("Checkers move "+moves);
		return ++moves!=MOVES;
	}
	public static GameFactory factory=new GameFactory() {//单一的工厂对象
		public Game getGame(){
			return new Checkers();
		}
	};
}

class Chess implements Game{
	private Chess(){}//构造器为private,不能直接创建对象
	private int moves=0;
	private static final int MOVES=4;
	public boolean move(){
		System.out.println("Chess move "+moves);
		return ++moves!=MOVES;
	}
	public static GameFactory factory=new GameFactory() {//单一的工厂对象
		public Game getGame(){
			return new Chess();
		}
	};
}

public class Games {
	public static void playGame(GameFactory factory){//不同的工厂对象生成不同的具体类的对象
		Game s=factory.getGame();//GameFactory接口调用相应的getGame()方法返回不同的Game对象后向上转型
		while(s.move());//Game接口自动找到相应实现类的move()方法
	}
	public static void main(String[] args){
		playGame(Checkers.factory);//传入Checkers类中的中工厂对象
		playGame(Chess.factory);//闯入Chess类中的工厂对象
	}
}/*Output:
Checkers move 0
Checkers move 1
Checkers move 2
Chess move 0
Chess move 1
Chess move 2
Chess move 3*/
可以看到,Chess和Checker类中的构造器均为private类型的,所以不能直接创建该类的对象。但是,我们可以通过这两个类中的静态(单例)工厂对象来创建属于本类的对象。

1.7总结

在作用域中的类与其他类共同编译,但只在作用域内可用,在其他作用域使用相同的类名,不会有命名冲突。传入内部类的参数:在JAVA8以前要求必须是final修饰的常量,JAVA8开始移除了这个限制,但是要求这个传入的参数在初始化后不能被修改。匿名内部类实际上是继承自一个类,将类的使用和定义放在了一起。



温馨提示:如果有什么错误,或者有什么意见(对于排版、知识块内容选取、讲述方式等),烦请评论或私聊,也许您的一个建议和一点指点能使我更加完善自己,也能让您感受到帮助他人的乐趣。祝您的编程之路一帆风顺!


如有交流请加微信:备注CSDN博友


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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值