初学Java类的时候,有很多概念不清楚–外部类,内部类,静态嵌套类……等等一堆词语,都是干啥的?有啥区别?很不明白.专门花了一下午的时间,到自己写程序的时候,毫无信心.什么时候改用啥,甚是恐慌.因此专门花了一下午的时间,将The Java™ Tutorials ->Classes and Objects 仔细读了一遍,并做了些梳理与总结.
总的来说,Java中各种类的关系和特点可以用下面的图来表示:
如上图所示,class可以分为两种:
- 普通的class.这里为了和将要讨论的内部类相对应,称之为外部类.这只是我在总结的时候为了明显区分自己这么写的.
- 嵌套类.就是在类的内部定义的类,而更具体的,嵌套类又可以再分为两种:
- 非静态的嵌套类,习惯上称之为内部类.
- 静态的嵌套类.
区分静态内部类和非静态类的方法很简单,顾名思义,就是看看定义类的时候,有没有用static
这个关键字修饰.他们两个最主要的区别就是内部类可以直接访问外部类的成员,不论是private
的还是public
的,而静态嵌套类是不能直接访问外部类的实例成员,注意是实例成员,对于类成员,静态嵌套类是可以直接访问的.例如:
1 import java.text.*;
2 import java.util.*;
3 public class Test{
4 private String mem = "I am the static menber of outer";
5 public static void main(String... args){
6 new Test().test();
7 }
8 public void test(){
9
10 NestedStaticClass nsc =new NestedStaticClass();
11 nsc.accessOuterStaticMember();
12 }
13 static class NestedStaticClass{
14
15 public void accessOuterStaticMember(){
16 System.out.println(mem);
17 }
18 }
19 }
静态嵌套类是不能直接访问外部类Test
的私有成员是实例成员mem
的,这样做是不能通过编译的,而如果在mem
前面用static
修饰,这样做就没问题了.
此外,内部类是不能定义静态成员的.但是可以有静态常量.为什么会这样呢?如果把他们看做外部类的一个成员,这就容易理解了.我们知道,被static
修饰的是类成员,而类成员是不能直接访问实例成员的,必须通过实例进行引用.而内部类是用于和类的实例进行交互的,如果我们在它内部定了静态成员,这就矛盾了.
而静态嵌套类因为有static
修饰,使得可以解脱束缚,这样的性质,就使得它可以和普通类一样使用,不用收外部类的限制,可以直接通过new Outer.Inner()
的方式实例化,而内部类inner class
是不可以这样的,它只能通过外部类的实例来引用.
内部类又有两个特殊形式:
- 局部类.
- 匿名内部类.
局部类,就是定义在语句块内的一个类,可以在函数中,亦可以在for
循环等的循环体中.只要有花括号{}
圈起来都行.匿名内部类,就是一个没有名字的局部类,严格的说,其实匿名内部类就是一个表达式而已.
它们既然是内部类的特例,那么就具有内部类的性质,例如,可以直接访问外部类的成员,不能定义静态变量等.此外,由于它们是定义在语句块中,它们就不能有public
这些修饰符修饰了.
值得一提的是,局部类和匿名内部类如果要访问局部变量,局部变量必须是final
修饰的,也就是常量.从jdk 8开始,只要是实际上的赋值后不在改变的变量都可以.这是因为它们所引用的局部变量是“俘获”的.什么意思,就是它会复制一份在自己的堆里面.从虚拟机的角度来看,不管什么类,在虚拟机看来都是独立的一个类,因此,局部类需要使用局部变量时会对其进行复制,在自己的空间保存一份副本.
还有,匿名内部类是不能有构造函数的,因为名字都没有,更何谈构造函数?要是真的有,他得叫啥?
嵌套内部类使用的基本事项基本就是这样了,下面来简单说说类的一些继承性质.
Java类是单继承的.也就是所有类只能有一个直接父类(Object除外),简单的说,就是extends
后只能有一个类,不过这也有例外,就是如果是interface,extends
后面是可以跟任意个interface的,是不是很神奇?当然,我们要说的并不是这些,我们想说的是继承的一些性质,主要有三点:
- 子类复写父类非静态方法,子类对象赋值给父类引用,调用方法版本是子类的方法;子类复写父类静态方法(隐藏),子类对象赋值给父类引用,调用的方法是父类的方法.特别需要注意的是,当实现接口时,对于接口中的
default
,static
子类对象赋值给接口的引用,调用方法版本是子类实现的的方法. - 子类中只要有与父类名字相同的field,就会把父类的屏蔽,不管类型是否相同.
最后,在interface所有成员默认是公有的,因此public
可以省略不写,编译器会自动加上.