相信大家在初学Java类这一章节时,遇到内部类、外部类的内容会感觉比较绕,尤其是遇到一些语法规定时会觉得难记,其实这些都是有原理的,如果我们能摸清其中的机制,便可以得心应手的使用了。
1.关于static
特性:在Java中,被static修饰的成员,称之为静态成员,也可以称为类成员,其不属于某个具体的对象,而是所有对象所共有的。
所以它并不是随着实例对象的出现被加载的,而是在类被加载的时候就被加载了。所以它只会被加载一次,因为它是随类加载的。
举个例子:
比如我们要一个关于【三班学生的类】,字段包括班级、姓名、年龄。因为班级是所有学生对象共有的特性(所有学生都是3班),所以我们可以用static修饰班级,表示这是个静态成员,即所有对象所共有的。而姓名跟年龄,则是某一个具体的实例对象拥有的特性。
public class Class3Student {
public static int clas = 3; //班级
public String name; //姓名
public int age;//年龄
public Class3Student(String name, int age){
this.name = name;
this.age = age;
}
public static void main(String[] args) {
Class3Student student1 = new Class3Student("小王",12);
Class3Student student2 = new Class3Student("小李", 13);
Class3Student student3 = new Class3Student("小周",14);
System.out.println(student1.clas+" "+student1.name); //3 小王
System.out.println(student2.clas+" "+student2.name);//3 小李
System.out.println(student3.clas+" "+student3.name);//3 小周
}
}
但是上诉代码中会出现黄色警告,因为我们使用了实例对象.静态成员去访问静态成员。正确访问静态成员的方式应该是【类名.静态成员】(即:Class3Student.clas)。因为它属于类成员,并不单独的属于某一个实例对象,所以应该用类名的方式去访问更为准确。
既然static修饰的成员表示类成员,那么是不是就表示我们不需要new一个对象就可以实现静态成员的访问呢?
运行的结果证实了我们的猜想。
1.1总结及扩展
1. 不属于某个具体的对象,是类的属性,所有对象共享的,不存储在某个对象的空间中
2. 既可以通过对象访问,也可以通过类名访问,但一般更推荐使用类名访问
3. JDK7及以前,HotSpot(Java虚拟机)中存储在方法区,JDK8及之后,类变量存储在Java堆中
4. 类变量存储在方法区当中
5. 生命周期伴随类的一生(即:随类的加载而创建,随类的卸载而销毁)
2.普通内部类(非静态内部类)
定义:定义在一个类中的非静态类(没有用static修修饰的类)称为普通内部类。
举个例子:
class Outer{
public int a = 1;
public int b = 2;
public static int data = 3;
private int c = 4;
public void func(){
System.out.println("这是外部的成员方法"+a);
}
public static void func2(){
System.out.println("这是外部的静态成员方法");
}
class Inner{
public int a = 10;
public void func(){
System.out.println("这是内部的成员方法"+a);
System.out.println("这是内部的成员方法"+Outer.this.a);
}
}
}
public class Test {
public static void main(String[] args) {
Outer.func2();
Outer outer = new Outer();
Outer.Inner inner = outer.new Inner();
outer.func();
inner.func();
}
}
上述代码中,我们先用了外部类名去访问它的静态方法,为第一点讲的static做一个回顾,我们不需要实例化对象,就可以调用静态成员方法。Outer.func2();
通过实例化对象outer,我们调用了Outer类中的普通成员方法。
Outer outer = new Outer(); outer.func();
通过实例化外部类(Outer类),我们实例化了Inner类, Outer.Inner inner = outer.new Inner();
这里需要注意当内部类成员变量和外部类成员变量同名时,需要通过外部类名.this.成员变量 的方式访问外部的成员(本例中写法是:Outer.this.a )。
=========================================================================
现在我们来思考一个问题,是否能在外部类的方法中访问到内部类的成员变量呢?在刚刚的例子中我们已经知道了在内部类是可以通过Outer.this.a 的方法成功访问到外部类的成员变量的。那反过来呢?我们用代码实验一下:
我们可以看到这样会使编译出错。
那怎么样才能访问到呢?在func方法中创建一个内部类对象去引用它的成员变量d即可。
public void func(){
System.out.println("这是外部的成员方法" + a );
System.out.println( "内部的成员变量:"+ new Inner().d);
}
我们在main方法中试一下是否可以成功运行。
public static void main(String[] args) {
Outer outer = new Outer();
outer.func();
}
=========================================================================
我们试着来梳理一下为什么这样是可行的,为什么不能直接访问,而是需要实例出一个Inner对象才能访问。而为什么Inner的方法中就可以直接访问Outer类的成员?
首先Inner类是一个在Outer类内部定义的一个类,它也可以被认为是Outer类中的一个特殊的成员变量,只不过这个变量是类而已。但是这个类是普通类,并不被static所修饰。这就是说Inner类只有在Outer类实例出一个具体的对象的时候才会存在。
我们可以将其与一开始讲述的static成员联系,之前讲了被static修饰的成员是类成员,只要类加载了,我们不需要实例对象(也就是不需要new一个对象),我们就可以直接用【类名.静态成员】访问使用。那反过来不被static修饰的成员,就是需要实例对象才能访问的成员,它是随着你的对象被创建出来(new出来),它才被加载的。
总结:
被static修饰的成员,随类加载,可以不创建对象直接使用。不被static所修饰的成员,必须创建对象,才能使用。
一些疑惑:
为什么随类加载可以直接使用?========》因为在你创建对象之前,你的这个变量就已经随着类加载进来了,机器已经知道你这个东西了,并且开辟了内存给你放好了,你直接用就是了,根本就不用再创建对象。而不被static修饰的成员变量,只有你new了对象,机器才知道原来还有这些变量存在,然后再给你开辟空间储存。
扩展:
一个类中,被static修饰的成员存储的区是方法区,不被static修饰的成员存储的区是堆。
问题:【为什么不能直接访问,而是需要实例出一个Inner对象才能访问。而为什么Inner的方法中就可以直接访问Outer类的成员?】
首先这是Outer类中普通的成员方法(不被static修饰),所以说明要调用这个方法本身就需要new一个Outer对象。然后在看内部类中的成员变量d,它也没有被static所修饰,所以我们也需要new一个Inner对象才能访问。但是因为本身能进到这个方法里面(能调用这个方法)就已经说明了我们已经创建了Outer对象,所以我们只需要在里面创建一个Inner对象就可以访问到Inner中的d变量了。
那如果是我们Outer中静态方法func2调用内部类的d是不是就需要new Outer对象了呢?
我们可以看到一开始没有new Outer()会报错,加了new Outer之后就不会报错了。因为static修饰的方法是可以没有实例对象的,所以这里编译会报错,我们必须要确定有一个实例对象才能对非静态变量成员进行访问。其中Inner是Outer的一个特殊的非静态成员变量,d是Inner一个非静态成员变量。
3.为什么普通内部类不能有static修饰的成员变量或方法?
如果你对上述内容已经理解,相信你心里多多少少已经有答案了。
普通内部类(没有被static修饰的类)======》外部类中特殊的普通成员变量======》普通成员变量必须依赖实例对象(必须new一个对象)======》普通内部类依赖于外部类的实例对象
结论:
普通内部类依赖于外部类的实例对象。
那么如果我们内部类有static(静态)修饰的成员变量或方法会怎么样呢?
内部类中的静态成员变量/方法======》内部类不用实例对象就可以直接访问======》【内部类.成员变量名】直接使用。
但是我们可以直接使用吗?我们之前得出结论普通内部类依赖于外部类的实例对象 。那么我们怎么可以直接用【内部类.成员变量名】呢,我们必须要依附于外部类的实例对象创建才可以。所以这是与我们的初衷相悖的。所以这是矛盾的,不可行。
从机制上来讲,static成员是类成员,随着类被加载而加载,但是没有实例化外部类,内部类未加载,却试图在内存中创建static的属性和方法,这当然是错误的。