版权声明:学习内容均为本人笔记,代码均为本人依据课本所写或改编,笔记均为个人心得或书中摘抄
引言:内部类,即将一个类的定义放在另一个类的定义内部。内部类与组合是完全不同的概念。内部类看似是一种代码的隐藏机制,其实,它能够了解外部类,并且与之通信,这为我们的编程提供了极大的方便。
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博友