一、内部类
1.定义:
指在一个外部类的内部再定义一个类,内部类可用protected、private修饰(外部类只能用public和缺省(没有方法体的构造器)的包访问权限)
2.特点:
- 内部类仍是一个独立的类,在编译之后内部类会被编译成独立的.class文件,但前面冠以外部类的类名和$符号
- 内部类不能用普通的方式访问
- 内部类声明为静态时,只能访问外部类的静态成员变量
- 外部类不能直接访问内部类的的成员,但可以通过内部类对象来访问
- 内部类可以自由地访问外部类的成员变量,无论是否是private的(因为内部类是外部类的一个成员)
3.分类(静态和非静态区分):
- 静态内部类:
静态内部类的作用:
降低包的深度,方便类的使用,静态内部类适用于包含类当中,但不依赖外在的类
为了方便管理类结构而定义(创建静态内部类的时候,不需要外部类对象的引用)
- 非静态内部类:
非静态内部类的作用:
内部类继承自某个类或实现某个接口,内部类的代码操作创建其他外围类的对象,即内部类提供了某种进入其外围类的窗口
每个内部类都能独立地继承自一个(接口的)实现,所以无论外围类是否已经继承了某个(接口的)实现,对于内部类都没有影响
如果没有内部类提供的可以继承多个具体的或抽象的类的能力,一些设计与编程问题就很难解决。 从这个角度看,内部类使得多重继承的解决方案变得完整。接口解决了部分问题,而内部类有效地实现了"多重继承"
4.分类(内部类所在位置区分):
普通内部类(成员内部类):
- 静态内部类与普通态内部类的区别:
- 静态内部类不持有外部类的引用
- 静态内部类只可以访问外部类的静态方法和静态属性(如果是private权限也能访问,这是由其代码位置所决定的)
- 普通态内部类持有外部类的引用
- 普通态内部类可以直接访问外部类的属性、方法,即使是private类型也可以访问,这是因为普通态内部类持有一个外部类的引用,可以自由访问
- 静态内部类不依赖外部类(可以独立存在,即使外部类消亡,静态内部类还是可以存在)
- 普通态内部类与外部类之间是相互依赖的关系(同生同死,一起声明,一起被垃圾回收器回收)
- 普通态内部类不能声明static的方法和变量(可以声明常量,如final static修饰的属性)
- 为什么成员内部类(普通内部类)不能有静态变量:
- 成员内部类之所以叫做成员,即它是类实例的一部分 而不是类的一部分
- 结构上来说,它和你声明的成员变量是一样的地位 一个特殊的成员变量,而静态的变量是类的一部分和实例无关
- 你若声明一个成员内部类,让它成为主类的实例一部分,然后又想在内部类声明和实例无关的静态的东西,你让JVM情何以堪啊
- 若想在内部类内声明静态字段 就必须将其内部类本身声明为静态
例1:
//本节讨论内部类以及不同访问权限的控制
//内部类只有在使用时才会被加载。
//外部类B
public class B{
int i = 1;
int j = 1;
static int s = 1;
static int ss = 1;
A a;
AA aa;
AAA aaa;
//内部类A
public class A {
// static void go () {
//
// }
// static {
//
// }
// static int b = 1;//非静态内部类不能有静态成员变量和静态代码块和静态方法,
// 因为内部类在外部类加载时并不会被加载和初始化。
//所以不会进行静态代码的调用
int i = 2;//外部类无法读取内部类的成员,而内部类可以直接访问外部类成员
public void test() {
System.out.println(j);
j = 2;
System.out.println(j);
System.out.println(s);//可以访问类的静态成员变量
}
public void test2() {
AA aa = new AA();
AAA aaa = new AAA();
}
}
//静态内部类S,可以被外部访问
public static class S {
int i = 1;//访问不到非静态变量。
static int s = 0;//可以有静态变量
public static void main(String[] args) {
System.out.println(s);
}
@Test
public void test () {
// System.out.println(j);//报错,静态内部类不能读取外部类的非静态变量
System.out.println(s);
System.out.println(ss);
s = 2;
ss = 2;
System.out.println(s);
System.out.println(ss);
}
}
//内部类AA,其实这里加protected相当于default
//因为外部类要调用内部类只能通过B。并且无法直接继承AA,所以必须在同包
//的类中才能调用到(这里不考虑静态内部类),那么就和default一样了。
protected class AA{
int i = 2;//内部类之间不共享变量
public void test (){
A a = new A();
AAA aaa = new AAA();
//内部类之间可以互相访问。
}
}
//包外部依然无法访问,因为包没有继承关系,所以找不到这个类
protected static class SS{
int i = 2;//内部类之间不共享变量
public void test (){
//内部类之间可以互相访问。
}
}
//私有内部类A,对外不可见,但对内部类和父类可见
private class AAA {
int i = 2;//内部类之间不共享变量
public void test() {
A a = new A();
AA aa = new AA();
//内部类之间可以互相访问。
}
}
@Test
public void test(){
A a = new A();
a.test();
//内部类可以修改外部类的成员变量
//打印出 1 2
B b = new B();
}
}
局部内部类(在方法中定义的内部类):
- 局部内部类只能在定义该内部类的方法内实例化,不可以在此方法外对其实例化
- 局部内部类不能有访问说明符,因为它不是外围类的一部分,但是它可以访问当前代码块内的常量和此外围类的所有的成员
例2:
public class 局部内部类 {
class A {//局部内部类就是写在方法里的类,只在方法执行时加载,一次性使用。
public void test() {
class B {
public void test () {
class C {
}
}
}
}
}
@Test
public void test () {
int i = 1;
final int j = 2;
class A {
@Test
public void test () {
System.out.println(i);
System.out.println(j);
}
}
A a = new A();
System.out.println(a);
}
static class B {
public static void test () {
//static class A报错,方法里不能定义静态内部类。
//因为只有在方法调用时才能进行类加载和初始化。
}
}
}
匿名内部类(没有名字的内部类,是局部内部类的一种特殊形式):
在匿名内部类中,当所在的方法的形参需要被内部类里面使用时,该形参必须为final:
因为虽然在内部类中的属性和外部方法的参数两者从外表上看是同一个东西,但实际上却不是,所以他们两者是可以任意变化的,也就是说在内部类中我对属性的改变并不会影响到外部的形参,而然这从程序员的角度来看这是不可行的。
毕竟站在程序的角度来看这两个根本就是同一个,如果内部类该变了,而外部方法的形参却没有改变这是难以理解和不可接受的,所以为了保持参数的一致性,就规定使用final来避免形参的不改变。
简单理解就是,拷贝引用,为了避免引用值发生改变,例如被外部类的方法修改等,而导致内部类得到的值不一致,于是用final来让该引用不可改变。
- 使用条件:
- 只用到类的一个实例
- 类在定义后马上用到
- 类非常小(SUN推荐是在4行代码以下)
- 使用原则:
- 匿名内部类不能有构造方法
- 匿名内部类不能定义任何静态成员、方法和类
- 匿名内部类不能是public,protected,private,static
- 只能创建匿名内部类的一个实例
- 一个匿名内部类一定是在new的后面,用其隐含实现一个接口或实现一个类
- 因匿名内部类为局部内部类,所以局部内部类的所有限制都对其生效
例3:
public class 匿名内部类 {
}
interface D{
void run ();
}
abstract class E{
E (){
}
abstract void work();
}
class A {
@Test
public void test (int k) {
//利用接口写出一个实现该接口的类的实例。
//有且仅有一个实例,这个类无法重用。
new Runnable() {
@Override
public void run() {
// k = 1;报错,当外部方法中的局部变量在内部类使用中必须改为final类型。
//因为方外部法中即使改变了这个变量也不会反映到内部类中。
//所以对于内部类来讲这只是一个常量。
System.out.println(100);
System.out.println(k);
}
};
new D(){
//实现接口的匿名类
int i =1;
@Override
public void run() {
System.out.println("run");
System.out.println(i);
System.out.println(k);
}
}.run();
new E(){
//继承抽象类的匿名类
int i = 1;
void run (int j) {
j = 1;
}
@Override
void work() {
}
};
}
}
- 匿名内部类初始化:
- 匿名内部类使用构造代码块进行初始化
例4:
public class OutClass {
public InnerClass getInnerClass(final int age,final String name){
return new InnerClass() {
int age_ ;
String name_;
//构造代码块完成初始化工作
{
if(0 < age && age < 200){
age_ = age;
name_ = name;
}
}
public String getName() {
return name_;
}
public int getAge() {
return age_;
}
};
}
例5(匿名内部类基本格式):
new 接口/类名(参数1, 参数2...){
实现方法1(){
}
实现方法2(){
}
......
};
例6(内部类使用):
//具体类
public class Class01 {
public void show(String s){
System.out.println("啦啦啦");
}
}
//抽象类
public abstract class AbstractClass01 {
abstract void show(String s);
}
//接口
public interface Interface01 {
void show(String s);
}
public class TestInner {
public static void main(String[] args) {
//重写具体类的方法
new Class01(){
@Override
public void show(String s) {
System.out.println("我是一个" + s);
}
}.show("具体类");
//重写抽象类的抽象方法
new AbstractClass01(){
@Override
void show(String s) {
System.out.println("我是一个" + s);
}
}.show("抽象类");
//实现接口的抽象方法
new Interface01(){
@Override
public void show(String s) {
System.out.println("我是一个" + s);
}
}.show("接口");
}
}
//运行结果:
//我是一个具体类
//我是一个抽象类
//我是一个接口
5.内部类的加载(遵循Java加载顺序):
- 内部类是延时加载的,也就是说只会在第一次使用时加载。不使用就不加载,所以可以很好的实现单例模式
- 静态内部类与非静态内部类都是在第一次使用时才会被加载
- 对于非静态内部类是不能出现静态模块(包含静态块,静态属性,静态方法等)
- 非静态类的使用需要依赖于外部类的对象
- 普通内部类在第一次用到时加载,并且每次实例化时都会执行内部成员变量的初始化,以及代码块和构造方法
- 静态内部类在第一次用到时被加载,但当它加载完以后就会将静态成员变量初始化,运行静态代码块,并且只执行一次。当然,非静态成员和代码块每次实例化时也会执行
6.内部类的重载:
内部类不可以被重载,当你继承了某个外围类的时候,内部类并没有发生什么特别神奇的变化。这两个内部类是完全独立的两个实体,各自在自己的命名空间内
例5:
package com.lwf.thinking.eight;
class Out{
Out(){
System.out.println("Out class");
Inner y = new Inner();
}
class Inner{
Inner(){
System.out.println("inner class");
}
}
}
public class AnonymousInner extends Out {
public class Inner{
Inner(){
System.out.println("AnonymousInner innerclass");
}
}
public static void main(String[] args) {
AnonymousInner out = new AnonymousInner();
}
}
//Out class
//inner class
//无AnonymousInner innerclass
7.内部类的继承:
例6:
class WithInner {
class Inner {
Inner(){
System.out.println("this is a constructor in WithInner.Inner");
};
}
}
public class InheritInner extends WithInner.Inner {
// ! InheritInner() {} // Won't compile
InheritInner(WithInner wi) {
wi.super();
System.out.println("this is a constructor in InheritInner");
}
public static void main(String[] args) {
WithInner wi = new WithInner();
InheritInner ii = new InheritInner(wi);
}
}
//this is a constructor in WithInner.Inner
//this is a constructor in InheritInner
可以看到,InheritInner 只继承自内部类,而不是外围类。但是当要生成一个构造器时,缺省的构造器并不算好,而且你不能只是传递一个指向外围类对象的引用。此外,你必须在构造器内使用如下语法: enclosingClassReference.super(); 这样才提供了必要的引用,然后程序才能编译通过。
8.Java的加载顺序:
- 初始化构造时,先父后子;只有在父类所有都构造完后子类才被初始化
- 类加载先是静态、后非静态、最后是构造函数
- 静态构造块、静态类属性按出现在类定义里面的先后顺序初始化,同理非静态的也是一样的,只是静态的只在加载字节码时执行一次,不管你new多少次,非静态会在new多少次就执行多少次
- java中的类只有在被用到的时候才会被加载
- java类只有在类字节码被加载后才可以被构造成对象实例
参考文章:
Java-Tutorial/18、深入理解内部类.md at master · h2pl/Java-Tutorial · GitHub