这是本人学习的总结,主要学习资料如下
- 疯狂Java讲义第三版,李刚编,电子工业出版社出版
1、内部类
1.1、内部类简介
- 什么是内部类:一般类是一个独立的程序单元,但某些情况下会把一个类定义在另一个类的内部,这时在内部的类就是内部类。
- 什么情况下使用内部类:如果一个内部类只在某个外部类中可以使用到,一旦这个内部类离开这个外部类后就没有意义,那么这个内部类就可以定义成内部类。比如创建一个Cow类,这个类需要CowLeg类。而CowLeg类离开Cow类没什么意义。Cow类又不希望CowLeg类被其他人访问,那么CowLeg就适合设为Cow类的内部类。
1.2、内部类与外部类的关系和区别:
- 内部类可以看成是外部类的一个成员变量(但有不同):内部类也不能完全看成一个普通的成员,内部类中非静态内部类和静态内部类 拥有的访问权限并不相同,还需要具体区分。
- 内部类可以使用private,protected和static修饰,而外部类不可以:先说外部类为什么不能使用。外部类应该是作为一个独立的程序单元运行,设为private(任何外部都不能调用)完全没意义,类加载器都不能找到它让它独立运行;protected(子类可调用)也没意义,只有子类能调用没什么用;static表示这个变量,方法或类属于某一个类(上一级),但是普通的类的上一级是一个包,普通的类加上static意义上说不过去,所以普通的类不能加static。
- 非静态内部类不能拥有静态对象。
1.3、内部类的语法
只要在一个类的花括号内定义,那就是内部类。比如下面的代码。
public class Outer{
public class inner{
...
}
...
}
内部类甚至可以定义在类的方法中,那种叫局部内部类,作用于也只在那个方法中。
下面开始详细说非静态内部类的和静态内部类。
2、 非静态内部类
拥有内部类的类,在编译时产生的.class文件比较特殊,比如下面的代码
public class Outer {
private class Inner{
}
public static void main(String[] args) {
}
}
产生的.class文件如下图所示
除了外部类本身会产生的.class文件以外,还会产生 外部类名$内部类名.class文件。
前面说非静态内部类可以直接访问外部类的私有成员,这是因为非静态内部类的对象中保存了关于外部类对象的引用,如下图所示
CowLeg是非静态内部类,Cow是外部类。上面时是两个类的实例对象,可以看出,在CowLeg类对象中有一个Cow.this的引用帮助CowLeg类对象访问Cow类的成员。
当内部类想访问某个变量时,系统是先在内部类中查找有没有该变量名,如果没有再转到外部类中搜索,如果还是没找到,那时候才会报编译错误。
当内部类的变量和外部类的变量重名时,可以使用this
,和外部类类名.this
作为区分。
非静态内部类必须寄生于某一个外部类对象,也就是说,当存在一个非静态内部类实例时,一定有一个被他寄生的外部类实例,反之则不一定。所以,非静态内部类可以直接访问外部类的成员,因为外部类实例一定存在;而外部类不能直接访问非静态内部类成员,因为内部类对象可能不存在(可能还没实例化,所以不能直接访问。要先实例化内部类,才能通过内部类实例访问内部类成员)。
所以,静态代码块和静态方法中不能使用非静态的内部类,因为静态的代码块和方法属于类本身,而非静态内部类属于类实例,如果静态代码块和方法使用到非静态内部类,那计算机会糊涂,不知道要使用哪一个内部类实例,所以静态代码快和静态方法不能使用非静态内部类。
非静态内部类里不能使用静态的任何东西,包括方法,代码块,变量等。这个按道理来说应该是可以使用的,但这就是规定,不用去理解。
下面讲一下内部类的使用。这得分两种情况
- 在外部类内部使用内部类:在外部类内部使用内部类和一般情况下使用普通的类是相同的,可以直接new 一个内部类。唯一需要注意的是不要再静态的部分使用非静态内部类。
- 在外部类外部使用内部类:因为非静态内部类一定是依附于某个外部类的实例的,所以在外部使用非静态内部类时一定要通过外部类的实例去调用非静态内部类的构造器。这时候,非静态内部类前面的修饰,private,默认,protected,和public就是在这里起作用,这些修饰符表示的意义和他们修饰普通变量是一样的。private表示该内部类只能在外部类内部使用,默认就只能在外部类的包中使用,其它的也一样。
3、静态内部类
静态内部类就是在前面加上static。静态内部类也就是属于类本身,而不属于类的实例,那它不能调用外部类的非静态成员。其它的没什么好说的和非静态内部类一样。
4、匿名内部类
匿名内部类是需要重点关注的,因为理解了匿名内部类才能理解Java8
的新特性lambda表达式
。
匿名内部类比较实用,它比较符合当初设计内部类时的场景。就是在某些场合需要使用类,但这个类只使用一次,以后再也不会使用了。
匿名内部类的语法
new 接口或父类构造器(实参列表){
成员变量
}
匿名内部类因为是只使用一次,所以会在定义时就创建对象,所以匿名内部类不能是抽象的类。从上面的语法可以看出,匿名内部类因为没有类名,所以也不能定义构造器,只能通过初始化代码块来代替构造器。
匿名内部类最常用的场景是需要创建某个接口的对象。比如某个方法的参数正好需要一个接口的实例,专门定义一个类去实现这个接口没必要,这时候可以用匿名内部类。代码如下所示
interface Product{
public double getPrice();
public String getName();
}
public class Outer {
public void anonymousTest(Product p){
System.out.println("购买" + p.getName() + "花费了" + p.getPrice());
}
public static void main(String[] args) {
Outer outer = new Outer();
outer.anonymousTest(new Product()
{
@Override
public double getPrice(){
return 2.00;
}
@Override
public String getName(){
return "衣服";
}
});
}
}
类Outer中方法anonymousTest的参数是接口Product,为了这个方法专门定义一个类去实现Product没什么必要,所以这里就用匿名内部类去实现这个接口来满足这个anonymousTest的需要。
匿名内部类继承父类时会涉及到参数列表的问题,这里的参数列表表示调用父类的构造器。
比如下面的代码
abstract class AnonymousTest{
public AnonymousTest(){}
public AnonymousTest(int a){
System.out.println("进入了有a的构造器");
}
}
public class Outer {
public void test(AnonymousTest a){
System.out.println("匿名类测试");
}
public static void main(String[] args) {
Outer outer = new Outer();
outer.test(new AnonymousTest()
{
//donothing
});
int a = 2;
outer.test(new AnonymousTest(2) {
//donothing
});
}
}
输出结果如下
使用匿名内部类时,还需要注意一点,就是匿名内部类使用到匿名类外部的变量时,那个变量会被自动加上final修饰符,比如下面的代码。
interface AnonymousTest{
void test();
}
public class Outer {
public void test(AnonymousTest a){
System.out.println("匿名类测试");
}
public static void main(String[] args) {
int age = 0;
Outer outer = new Outer();
outer.test(new AnonymousTest() {
@Override
public void test() {
System.out.println(age);
}
});
//这句代码会报错
age = 4;
}
}
Java8会为变量自动加上final,但是在java8之前匿名内部类引用类外变量之前必须为变量加上final,否则会报错。
为什么变量必须是final,其实是因为如果不加final,编译器实现起来会很困难,具体相关内容这里不赘述。
理解了匿名内部类以后,就能更好的理解lambda表达式
,具体可看这篇文章。