小胖从官网出发,研究下为什么我们需要些内部类,内部类的区别和联系。
思考三个问题:
(1)为什么需要内部类?静态内部类和非静态内部类有什么区别;
(2)为什么内部类可以无条件访问外部类成员;
(3)为什么jdk1.8之前,局部内部类和匿名内部类访问局部变量或者方法参数需要加final修饰符?
1. 官网阅读:
1.1 为什么需要内部类
It is a way of logically grouping classes that are only used in one place: If a class is useful to only one other class, then it is logical to embed it in that class and keep the two together. Nesting such "helper classes" makes their package more streamlined.
简化包配置:如果一个类只对另一个类有用,将他们嵌套在一起是合理的。嵌套一些“有帮助的类”可以使得包更加简化
It increases encapsulation: Consider two top-level classes, A and B, where B needs access to members of A that would otherwise be declared private. By hiding class B within class A, A's members can be declared private and B can access them. In addition, B itself can be hidden from the outside world.
增加了封装:两个顶级类A和B,B需要访问A中声明为private的成员。
It can lead to more readable and maintainable code: Nesting small classes within top-level classes places the code closer to where it is used.
易读和可维护:在顶级类中嵌套小类会使代码更接近于使用它的位置。
1.2 为什么Java内部类设计为静态和非静态
Nested classes are divided into two categories: static and non-static. Nested classes that are declared static are called static nested classes. Non-static nested classes are called inner classes.
嵌套类一般分为两类:静态和非静态。声明static的嵌套类称为静态嵌套类。非静态嵌套类称为内部类。
A static nested class interacts with the instance members of its outer class (and other classes) just like any other top-level class. In effect, a static nested class is behaviorally a top-level class that has been nested in another top-level class for packaging convenience.
静态嵌套类就像任何顶级类一样 与 其外部类(其他的类)的实例成员交互。事实上,静态内部类在行为上就是一个顶级类,它嵌套在一个顶级类中以方便打包。
As with class methods and variables, a static nested class is associated with its outer class. And like static class methods, a static nested class cannot refer directly to instance variables or methods defined in its enclosing class: it can use them only through an object reference.
和静态方法一样,静态嵌套类不能直接引用封闭类中定义的实例变量和方法。只能通过对象的引用来使用它们。
静态方法引用对象:
public class TestNest {
private String abc;
public String getAbc() {
return abc;
}
public static String nestSta() {
TestNest testNest = new TestNest();
return testNest.getAbc();
}
}
As with instance methods and variables, an inner class is associated with an instance of its enclosing class and has direct access to that object's methods and fields. Also, because an inner class is associated with an instance, it cannot define any static members itself.
内部类可以直接访问该对象的字段和方法,由于内部类和实例相关联,因此无法定义任何的静态成员变量。
那我们怎么理解呢?
静态内部类就是一个独立的类。为什么使用静态内部类呢?
比如A,B两个类,B有点特殊,虽然可以单独存在,但只能被A使用。那么此时应该怎么办?把B并入到A里面,复杂性提高,搞的A违反单一职责。如果B独立,又可以被其他类依赖,不符合设计本意,不如将其变成A.B。其他类就不能使用B了。
而相比起来,非静态的才是真正的内部类,对其外部类有一个引用。
1.3 序列化
Serialization of inner classes, including local and anonymous classes, is strongly discouraged. When the Java compiler compiles certain constructs, such as inner classes, it creates synthetic constructs;.....However, synthetic constructs can vary among different Java compiler implementations, which means that .class files can vary among different implementations as well. Consequently, you may have compatibility issues if you serialize an inner class and then deserialize it with a different JRE implementation.
强烈建议不要对内部类进行序列化。java编译器编译某些构造(如内部类)时,他会创建“合成构造”。合成构造在不同的java编译器中变化。序列化内部类,然后使用不同的JRE实现反序列化,则可能存在兼容的问题。
2. 实战内部类
2.1 成员内部类
类的成员 无条件访问外部类 依赖外部类 多种访问权限
2.1.1 内部类的特点
成员内部类中不能定义静态变量
public class Outer {
private String name;
public Outer(String name) {
this.name = name;
}
//外部类使用内部类的方法
public void write() {
Inner inner = new Inner();
inner.say();
System.out.println(inner.getInnnerName());
}
//成员内部类
class Inner {
private String InnnerName;
public String getInnnerName() {
return InnnerName;
}
public void setInnnerName(String innnerName) {
InnnerName = innnerName;
}
public void say() {
System.out.println(name);
}
}
}
成员内部类相当于类的成员变量,可以无条件访问外部类的成员。但不过要注意的是,成员内部类和外部内部类拥有同名的成员方法或者方法时,要显式的声明,否则默认情况下访问的是内部类成员。
外部类.this.成员变量
外部类.this.成员方法
反编译后的class文件:
public class Outer {
private String name;
public Outer(String name) {
this.name = name;
}
public void write() {
Outer.Inner inner = new Outer.Inner();
inner.say();
System.out.println(inner.getInnnerName());
}
class Inner {
private String InnnerName;
Inner() {
}
public String getInnnerName() {
return this.InnnerName;
}
public void setInnnerName(String innnerName) {
this.InnnerName = innnerName;
}
public void say() {
System.out.println(Outer.this.name);
}
}
}
外部类想访问内部类的成员必须先创建一个内部类的对象,再通过指向这个对象的引用来访问。
public void wirte() {
Inner inner = new Inner();
inner.say();
System.out.println(i + ":" + name);
}
于是外部类就可以访问内部类的所有成员了!
2.1.2 如何创建内部类
我们知道,成员内部类是依赖于外部类而存在的。也就是说,想要创建成员内部类,前提是有一个外部类对象。
方式一:
Outer outer = new Outer();
Inner inner = outer.new Inner();
方式二:
Outter.Inner inner1 = outter.getInnerInstance();
//getInnerInstance()方法
public Inner getInnerInstance() {
if(inner == null)
inner = new Inner();
return inner;
}
2.1.3 成员内部类权限问题
成员内部类可以拥有public、default、protected、private权限。
private只能在外部类中被访问。
default同一个包下访问。
protected同一个包下或继承外部类的情况下。
public任何地方。
而外部类只有public和default两种访问权限。
2.1.4 小结
成员内部类是依附于外部类存在的,并且和外部类的一个成员变量有相似之处。内部类可以无条件访问外部类的成员、外部类需要内部类的引用才能访问、四种访问权限。
请注意(下面两个内部类):jdk版本是1.8,看起来似乎编译器取消了没有声明为final的变量或参数也可以在局部内部类和匿名内部类被访问。但事实上是Java8引入了effectively final概念,被默认成为了final类型。
2.2 局部内部类
方法或作用域内 局部变量
注意:局部内部类和成员内部类的区别就是局部内部类的访问仅限于方法内或者该作用域内。不能定义静态变量。
class Outer {
Object method() {
int locvar = 1;
System.out.println("1111");
class Inner {
void displayLocvar() {
System.out.println("locvar = " + locvar);
}
}
Object in = new Inner();
return in;
}
}
注意:局部内部类更像一个局部变量,是没有访问修饰符的。
2.3 匿名内部类
一般来说,匿名内部类用于继承其他类或是实现接口,并不需要增加额外的方法,只是对继承的方法的实现或者重写。
匿名类的格式:
new Thread(new Runnable() {
@Override
public void run() {
//TODO
}
});
通过new XXX(){};的方式创建了一个只能使用一次子类。
--
为什么叫做匿名内部类呢?
匿名内部类只能使用一次。他通常用来简化代码。但是使用匿名内部类还有一个前提条件:必须继承一个父类(抽象类或具体类)或是实现一个接口。
对于这个问题,首先我们应该明确的一点是对于匿名内部类,它可能引用三种外部变量:
外部类的成员变量(所有的变量);
外部方法或作用域内的局部变量;
外部方法的参数;
实际上,只有第一种变量不需要声明为final。
原因:
首先,在这里提出,网上的答案基本是:局部变量声明周期和局部类的声明周期不同。会导致内部类失去引用造成错误!!!等等,一个变量加上final就可以延长生命周期了吗?那加上final岂不是会造成内存短暂泄露?
正解:匿名内部类和所有类一样,也是有自己的构造函数的,只不过这个构造函数是隐式的。
加入final修饰符是为了保持内部和外部的数据的一致性。
编译前:
public class Outer {
String string = "";
void outerTest(final char ch){
final Integer integer = 1;
Inner inner = new Inner() {
void innerTest() {
System.out.println(string);
System.out.println(ch);
System.out.println(integer);
}
};
}
public static void main(String[] args) {
new Outer().outerTest(' ');
}
class Inner {
}
}
编译后:
class Outer$1extends Outer.Inner
{
Outer$1(Outer paramOuter, char paramChar, Integer paramInteger)
{
super(paramOuter);
}
void innerTest()
{
System.out.println(this.this$0.string);
System.out.println(this.val$ch);
System.out.println(this.val$integer);
}
}
匿名内部类之所以可以访问局部变量,是因为在底层将这个局部变量的值传入了匿名内部类中,并且以匿名内部类的成员变量存在,这个值的传递过程是通过匿名内部类的构造器完成的。
我们可以看到匿名内部类引用的局部变量和方法参数以及外部类的引用都会被当做构造函数的参数。但是外部类的成员变量是通过外部类的引用来访问的。
那么为什么匿名内部类访问外部类的成员变量,无需final修饰呢?
因为非静态内部类的对象保存了外部类对象的引用,因此内部类对外部类成员变量的修改都会真实的反应到外部类实例本身,所以不需要final修饰。
需要引入两个知识点:
值传递和引用传递:基本类型作为参数传递时,传递的是值的引用,无论怎么改变这个拷贝,原值是不会改变的;当对象作为参数传递时,传递是对象引用的拷贝,无论怎么改变新引用的指向,原引用是不会改变的(当然通过新引用改变对象的内容,那么改变就是确确实实发生了)。
final作用:被final修饰基本类型变量,不可更改其值;当被final修饰引用变量,不可改变其指向,只能改变对象的内容。
于是,假设允许不对局部变量加final,当匿名内部类里面尝试改变外部基本类型的值的时候,或者改变外部引用变量的指向的时候,表面上看起来是成功了,但是实际上并不会影响到外部的变量。
所以java就一刀切,强制加上了final修饰。
2.4 静态内部类
我们上面知道,静态内部类是一个独立的类,不需要依赖外部类也能存在的。所以静态内部类不能使用外部类非static成员变量或者方法。因为外部类的非静态成员必须依附于具体的对象。
静态内部类
静态内部类的创建方法:
外部类.内部类 引用名=new 外部类.内部类();
public static void main(String[] args) {
//静态内部类的创建方法:
Outer.Inner in = new Outer.Inner();
in.say();
}
3. 问题解答
看到这里,我相信大家应该心里对问题也有了自己的答案。
静态内部类是不依附与外部类存在的。而非静态内部类就是外部类的一个成员,是需要依附于外部类。
非静态内部类中含有构造函数,构造函数中会将外部类的引用传入,所以,内部类可以无条件访问外部类成员。
为什么使用final和生命周期是无关的,主要是java为了保持内部和外部变量的统一。
4. 内部类常见面试题
根据注释填写(1),(2),(3)处的代码
public class Test{
public static void main(String[] args){
// 初始化Bean1
(1)
bean1.I++;
// 初始化Bean2
(2)
bean2.J++;
//初始化Bean3
(3)
bean3.k++;
}
class Bean1{
public int I = 0;
}
static class Bean2{
public int J = 0;
}
}
class Bean{
class Bean3{
public int k = 0;
}
}
我们可以知道,成员内部类,必须先产生外部类的实例化对象,才能产生内部类的实例化对象。而静态内部类不需要产生实例化对象即可产生内部类的实例化对象。
创建静态内部类:
外部类类名.内部类类名 xxx=new 外部类类名.内部类类名();
创建成员内部类:
外部类类名.内部类类名 xxx=外部类对象名.new 内部类类名();
因此,(1),(2),(3)处的代码分别为:
Test test = new Test();
Test.Bean1 bean1 = test.new Bean1();
Test.Bean2 b2 = new Test.Bean2();
Bean bean = new Bean();
Bean.Bean3 bean3 = bean.new Bean3();
2.下面这段代码的输出结果是什么?
public class Test {
public static void main(String[] args) {
Outter outter = new Outter();
outter.new Inner().print();
}
}
class Outter
{
private int a = 1;
class Inner {
private int a = 2;
public void print() {
int a = 3;
System.out.println("局部变量:" + a);
System.out.println("内部类变量:" + this.a);
System.out.println("外部类变量:" + Outter.this.a);
}
}
}
输出答案
3 2 1
总结:内部类和外部类变量的访问权限问题:
非静态内部类依赖于外部类对象的创建,所以,非静态类中不能定义静态变量。
非静态内部类的构造方法中,含有外部类的引用。可以直接使用所有的外部类成员。
外部类不能直接使用非静态内部类的成员。除非创建内部类对象。
可以把静态内部类看做一个独立的静态类,所以不能直接使用一个类的实例成员。
匿名类必须继承一个类(抽象类或具体类)或者实现一个接口。new XXX(){};就是一个内部类。
只含有private构造方法的类不能被继承,所以可以使用protected修饰类,以达到让子类继承的目的,此时,使用匿名内部类的new XXX(){};的方式就可以创建出一个XXX的子类对象。