Java内部的秘密----内部类
1.内部类
1.1.内部类的定义:
在一个类的内部定义类,这样的类就成为内部类,内部类所在的类称为外部类。
1.2.为什么要使用内部类?
在《Think in java》中有这样一句话:每个类都能继承一个类和实现多个接口,内部类也不例外。所以无论外部类是否已经继承或实现了接口,内部类自己同样还是继承类或实现接口。
通过这句话其实我们能够看出内部类的一个非常重要的优点:解决类只能实现单继承的缺点。虽然说我们使用接口可以多实现,也可以解决一些问题。但是有时候一些接口比较难以解决的问题的时候,我们就可以用内部类来实现。比如说我们在一些程序设计的时候,我们有一个类需要实现其他类中特有的方法,但是我们只能够单继承,所以我们可以考虑使用接口来实现,但是有一个问题就是我们接口中的普通方法是抽象方法,是没有方法体的,即使我们实现了这些接口,我们还需要去重写我们想要的方法,那么我们为何不自己在类中写一个呢,于是内部类就是解决这些问题的最好方法。这就类似于我们类能够实现多继承咯。
1.3.特点
a.编译之后可以产生独立的字节码文件。
b.内部类可直接访问外部类的私有成员(private)而不破坏封装。
c.为外部类提供必要的内部功能组件。
d.内部类的访问修饰符可以是public 、protected、default、private。外部类只能是public、 default.
2.内部类—成员内部类
2.1.什么叫成员内部类?
其实我们从这个名字再结合之前学过的知识,大概可以联想到 成员变量和成员方法。没错,成员内部类在外部类中的地位就跟成员变量这些一样。在学习这个点的时候,我们老师就一直跟我们说把这个成员内部类看称是外部类中的一个成员X,像使用成员变量那个去使用它。
2.2.主要特点:
a.成员内部类依附于外部类,只能先创建外部类才能创建内部类。
Inner in=new Outer().new Inner();
b.成员内部类中不能存在任何static的变量和方法。但是可以调用外部类中的静态变量。
**c.**成员内部类可以继承类,也可以实现接口
d.当外部类、内部类存在重名属性时,会优先访问内部类属性。
e.直接访问外部类的私有成员。
直接看代码理解一下:
public class Test3 {
public static void main(String[] args) {
//外部类直接实例化
Outer out=new Outer();
//获取私有方法只能通过get、set公共方法
System.out.println(out.getNum());//8
out.test();//Outer 方法
System.out.println("-----------");
//Inner i=new Inner(); 错误:内部类只能在外部类直接创建对象
//我们内部类的实例化必须要依赖外部类的一个对象;
Inner in=new Outer().new Inner();//正确:创建内部类对象只能通过外部类对象创建
System.out.println(in.num);
in.test();
System.out.println("----------------------");
in.test2();
System.out.println("------------------------");
Inner oii=new Outer().new Inner();
}
}
//外部类Outer
class Outer{
private int num=8;
public Outer() {
}
public Outer(int num) {
this.num=num;
}
public int getNum() {
return num;
}
public void test() {
System.out.println("Outer 方法");
}
//外部类中可以直接实例化内部类。通过反编译可以看到外部类中会生成一个内部类无参构造器
//内部类中会生成一个带外部类参数的构造器
Inner n1=new Inner();
//内部类Inner
class Inner{
int num=9;
//static String name="kxf";报错,成员内部类中不能包含静态变量
static final String name="kxf";//静态常量可以运行
public void test() {
System.out.println("Inner 方法");
}
public void test2() {
int num=5;
System.out.println("Inner 方法2");
System.out.println("内部类局部变量:"+num);//直接变量名-->方法里面的变量 5
System.out.println("内部类成员变量:"+this.num);//this.变量名-->当前对象(内部类)变量9
System.out.println("内部类静态常量:"+this.name);
System.out.println("外部类私有成员变量:"+Outer.this.num);//外部类名.this.变量名--->外部类变量 8
}
}
//内部类继承A类
class InnerA extends A{
}
class InnerB extends B{
}
}
class A{
public void funA() {
System.out.println("这是A里面的方法");
}
}
class B{
public void funB() {
System.out.println("这是B里面的方法");
}
}
3.内部类—静态内部类
3.1.什么叫静态内部类。
同样的,我们从名字也能看出一二。静态嘛,就是被static修饰过的属性或方法,静态内部类就是被static修饰过的内部类咯。
所以对于static的特性,静态内部类也基本都具备。
3.2.主要特点:
a.可以用有静态成员和静态方法。
b.直接通过类名.静态名访问
c.不能直接访问外部类中的非静态成员,可以通过 new 外部类().成员 访问
d.创建内部类对象时,不需要外部类对象,可以直接创建。Inner in=new Inner();
e.在其他类中创建内部类对象时,不需要外部类对象创建。Outer.Inner in=new Outer.Inner();
直接看代码理解:
//静态内部类.
//普通类中:静态方法只能访问静态变量.
//静态内部类中:静态方法也是只能访问被static修饰过的变量
public class Test {
public static void main(String[] args) {
Outer.Inner.fun1();
Outer out=new Outer();
Inner in=new Outer.Inner();
in.fun2();//fun2没有被static修饰,必须通过对象访问
}
}
class Outer{
//静态成员变量
private static String name="外部类";
private int age=88;
//静态方法
static void test() {
System.out.println("外部类的静态方法:");
}
//加上static,静态类就具有static所有特征
//static特征:全局唯一;直接通过类名调用;静态方法只能调用静态属性
static class Inner {
//成员内部类不可以有静态属性和静态方法
//静态内部类可以有静态属性和静态方法
static int number=22;
int age=40;
static String name="内部类";
//静态方法
static void fun1() {
System.out.println("静态内部类静态方法访问外部类的静态属性:"+Outer.name);
System.out.println("静态内部类的静态方法fun1访问内部类的静态属性:"+name);
//System.out.println(age);//在静态内部类中,外部类中不是静态属性不能直接访问
//System.out.println(Outer.this.age);报错
//只能通过对象访问
System.out.println("静态内部类访问外部类非静态属性:"+new Outer().age);
//System.out.println("静态内部类中非静态变量:"+age);静态方法只能访问静态变量
System.out.println("静态内部类访问内部类静态属性:"+number);
}
//非静态方法
void fun2() {
System.out.println("实例方法2:"+Outer.name);
System.out.println("实例方法2:"+new Outer().age);
System.out.println("静态内部类中非静态变量:"+age);
System.out.println(number);
}
}
}
4.内部类—局部内部类
4.1.什么叫局部内部类
跟局部变量一样,都有"局部"二字,那么用法是否也一样呢?其实局部内部类的用法和定义就跟局部变量是差不多的。只不过他既然是内部类,那么就是一个声明在方法里面的类咯。
这个类的使用主要是应用与解决一些比较复杂的问题,想创建一个类来辅助我们的解决方案,但是又不希望这个类是公共可用的,所以就产生了局部内部类。局部内部类和成员内部类一样被编译,只是它们的作用范围发生了改变。局部内部类与局部变量一样,作用域只在方法里面,不能在方法外访问。
4.2.主要特点
a.不能有访问修饰符修饰。跟作用域有关
b.不能被static修饰。也是跟作用域与static的特点有关。
c.局部内部类访问外部类的方法中的局部变量的两种情况(反编译最直接了当)
1.如果当前变量只是被引用,没有具体的使用,那么默认局部变量不会添加final
2.如果当前变量被引用,并且被使用,在jdk8后系统会自动为变量添加上final关键字
d.局部内部类的生命周期是当该内部类实例被GC回收期回收的时间点,而不是方法结束的时间点。
看代码:
import java.util.Scanner;
public class Test {
public static void main(String[] args) {
Outer out=new Outer();
out.test();
out.test2();
}
}
class Outer{
//成员变量
int number=10;
//静态成员变量
static String name="tom";
//成员方法
public void test() {
//局部变量
int num=39;
String name="jack";
//局部内部类
//使用范围:方法内部
//生命周期:该实例被GC(垃圾回收器)回收的时间点,而不是方法完成的时间点
class JuBuInner{
//局部内部类的方法
public void fun1() {
System.out.println("局部内部类的方法"+num);//39
}
}
//真正使用这个方法的时候,局部变量系统会自动变成final
//如果没有使用这个方法,那么局部变量不会变成final.
JuBuInner jc=new JuBuInner();
jc.fun1();
}
}
反编译结果
import java.io.PrintStream;
class Outer
{
int number = 10;
static String name = "tom";
public void test()
{
final int num = 39;
String name = "jack";
Object jc = new Object()
{
public void fun1()
{
System.out.println("局部内部类的方法" + num);
}
};
jc.fun1();
}
}
可以看到局部变量num自动加上了final。因为我们创建了内部类实例并调用了fun1方法,所以会自动加上final。
4.3.应用
有些情况下,不方便对外暴露整个类以及使用类做生命事情的情况下使用
public class TestSchool {
public static void main(String[] args) {
School sc=new School();
//获取一个老师
Teacher teacher=sc.getTeacher("super");
teacher.teach();
}
}
//老师接口
interface Teacher{
//老师讲课
public void teach();
}
//学校类
class School{
public Teacher getTeacher(String type) {
//声明一个高级讲师的内部类
class BigTeacher implements Teacher{
public void teach() {
System.out.println("高级讲师在讲课!");
}
}
//声明一个超级讲师的内部类
class SuperTeacher implements Teacher{
public void teach() {
System.out.println("超级讲师在讲人生!");
}
}
if("super".equals(type)) {
return new SuperTeacher();
}
else
return new BigTeacher();
}
}
5.内部类—匿名内部类
5.1.什么叫匿名内部类
从名字上看,那肯定就是一个没有名字的内部类。没错,但是它还是一个局部内部类,准确说就是一个 局部匿名内部类。多关注的是它的实现而不关注名称。
5.2.特点
a.使用匿名内部类时,我们必须得是继承一个类或者实现一个接口,但是又不能同时继承和实现。
b.匿名内部类是唯一一个没有构造方法的类,因为没有名字要怎么声明构造方法。
c.匿名内部类中不能存在任何的静态成员变量和静态方法。
d.匿名内部类为局部内部类,所以局部内部类的所有限制对匿名内部类同样有效
e.匿名你不来不能是抽象的,它必须要实现继承的类或实现的接口中的所有抽象方法。
f.匿名内部类可以声明自己独有的方法,但是没必要。
看代码
public class Test {
public static void main(String[] args) {
Outer out=new Outer();
out.test();
}
}
interface NM{
public void eat();
}
//外部类
class Outer{
public void test() {
//通过接口声明生成一个匿名内部类
NM n=new NM() {
//static int n=9;不能存在静态变量
// public static void test() { 不能存在静态方法
// }
public void eat() {
System.out.println("吃饭");
}
};
n.eat();
NM n1=new NM() {
public void eat() {
n.eat();
System.out.println("干完活再吃饭");
}
};
n1.eat();
}
}
5.3.应用
public class TestSchool {
public static void main(String[] args) {
School sc = new School();
Teacher tc = sc.getTeacher(10000);
tc.teach();
}
}
// 讲师接口
interface Teacher {
// 讲师讲课的方法
public abstract void teach();
}
// 学校类
class School {
// 学校安排老师
private Teacher t;
public Teacher getTeacher(int num) {
if (num % 3 == 0) {
t = new Teacher() {
public void teach() {
System.out.println("高级讲师讲课!");
}
};
}
else {
t = new Teacher() {
public void teach() {
System.out.println("超级讲师讲课!");
}
};
}
return t;
}
}