★内部类
把类定义在其他类的内部,这个类就被称为内部类。
举例:在类A中定义了一个类B,类B就是内部类。
内部类的访问特点
内部类可以直接访问外部类的成员,包括私有。
外部类要访问内部类的成员,必须创建对象。
package 成员内部类;
/*
成员内部类的修饰符:
private 为了保证数据的安全性
static 为了方便访问数据
注意:静态内部类访问的外部类数据必须用静态修饰。
*/
public class Outer {
private int num = 10;
private static int staticNum= 100;
protected class Inner {
public void show() {
System.out.println(num);
System.out.println(staticNum);
}
//非静态内部类中不能有静态成员变量和方法
/*static int k = 0;
public static void show3() {
System.out.println("show3");
}
*/
}
//内部类用静态修饰是因为内部类可以看成是外部类的成员
public static class StaticInner {
public void show() {
//System.out.println(num);
//被静态修饰的成员内部类只能访问外部类的静态成员
System.out.println(staticNum);
}
//静态内部类中可以有静态成员变量和方法
static int k =0;
public static void show2() {
//被静态修饰的成员内部类只能访问外部类的静态成员
//System.out.println(num);
System.out.println(staticNum);
}
}
}
class InnerClassDemo4 {
public static void main(String[] args) {
//成员内部类被静态修饰后的访问方式是:
//格式:外部类名.内部类名 对象名 = new 外部类名.内部类名();
//限定的静态内部类
Outer.StaticInner staticInner = new Outer.StaticInner();
staticInner.show();
staticInner.show2();
//show2()的另一种调用方式
Outer.StaticInner.show2();
//成员内部类的访问方式是:
//格式:外部类名.内部类名 对象名 = new 外部类名().内部类名();
//非静态内部类
Outer.Inner oi = new Outer().new Inner();
oi.show();
}
}
思考一下:为什么被静态修饰的成员内部类只能访问外部类的静态成员?
首先回顾一下静态内部类的加载时机和加载过程
类的加载时机:
1.new 一个类的时候
2.调用类的静态变量
3.调用类的静态方法
4.调用类的静态内部类
静态内部类的加载时机:静态内部类的加载时机是在程序中调用静态内部类的时候加载的,和外部类的加载没有必然关系。如果在程序中单纯的使用外部类,并不会触发静态内部类的加载。
加载过程:在加载静态内部类的时候 发现外部类还没有加载,那么就会先加载外部类,加载外部类的静态变量、静态代码块、非静态变量等等,加载完外部类之后,再加载静态内部类(初始化静态变量和静态代码块等)只有这样才能保证,静态内部类一被加载就可以访问到外部的静态变量。
静态内部类优先于外部类的非静态变量加载,如果静态内部类加载完成,访问非静态变量,但是非静态变量还没加载完成,那么就会存在问题。
https://blog.csdn.net/xiangqianzou_liu/article/details/105441903
内部类位置:按内部类在类中位置
按照内部类在类中定义的位置不同,可以分为如下两种格式:
成员位置(成员内部类)
局部位置:方法中(局部内部类)
/*
内部类位置
成员位置:在成员位置定义的类,被称为成员内部类。
局部位置:在局部位置定义的类,被称为局部内部类。
*/
class Outer {
private int num = 10;
//成员位置
class MemberInner {
private int i=0;
}
public void method() {
Outer.MemberInner oi = new Outer().new MemberInner();
Outer.MemberInner memberInner = new Outer.MemberInner();
System.out.println(memberInner.i);
System.out.println(oi.i);
//局部位置
class LocalInner {
private int j=0;
}
}
public static void main(String[] args) {
new Outer().method();
}
}
☆加载时机:第一次使用被加载
内部类是延时加载的,也就是说只会在第一次使用时加载。
不使用就不加载,所以可以很好的实现单例模式。
☆内部类注意事项
1.加载内部类会触发加载外部类
实例化外部类,调用外部类的静态方法、静态变量、静态内部类,会加载外部类。
2.加载外部类不会触发加载内部类
外部类初次加载,会初始化静态变量、静态代码块、静态方法。
但不会加载内部类和静态内部类,静态内部类和非静态内部类一样,都是在被调用时才会被加载。
3.静态成员内部类只能访问外部类的静态成员
内部类用静态修饰是因为内部类可以看成是外部类的成员。
被静态修饰的成员内部类只能访问外部类的静态成员。
直接调用外部类的静态内部类和外部类的静态变量,静态方法都会让外部类被加载。
如果只调用外部类的静态变量,静态方法时,是不会让静态内部类的被加载。
5.非静态内部类里面不能有静态成员和方法
package 内部类;
import java.util.Random;
public class OuterClass {
public static long OUTER_DATE = System.currentTimeMillis();
static {
System.out.println("外部类静态块加载时间:" +
System.currentTimeMillis());
}
public OuterClass() {
timeElapsed();
System.out.println("外部类静态变量加载时间:" + OUTER_DATE);
System.out.println("外部类构造函数时间:" +
System.currentTimeMillis());
}
static class InnerStaticClass {
public static long INNER_STATIC_DATE = System.currentTimeMillis();
}
class InnerClass {
public long INNER_DATE = 0;
public InnerClass() {
timeElapsed();
INNER_DATE = System.currentTimeMillis();
}
}
public static void main(String[] args) throws InterruptedException {
OuterClass outer = new OuterClass();
Thread.sleep(1000);
System.out.println("非静态内部类加载时间"
+ outer.new InnerClass().INNER_DATE);
Thread.sleep(1000);
System.out.println("静态内部类加载时间:"
+ InnerStaticClass.INNER_STATIC_DATE);
}
//单纯的为了耗时,来扩大时间差异
private void timeElapsed() {
for (int i = 0; i < 10000000; i++) {
int a = new Random(100).nextInt(),
b = new Random(100).nextInt();
a = a + b;
}
}
}
外部类静态块加载时间: 1627945301254
外部类静态变量加载时间:1627945301254
外部类构造函数时间:1627945301630
非静态内部类加载时间1627945302978
静态内部类加载时间:1627945303990
package 内部类;
import java.util.Random;
public class OuterClass2 {
public static long OUTER_DATE = System.currentTimeMillis();
static {
System.out.println("外部类静态块加载时间:" + System.currentTimeMillis());
}
public OuterClass2() {
timeElapsed();
System.out.println("外部类构造函数时间:" + System.currentTimeMillis());
}
static class InnerStaticClass {
public static long INNER_STATIC_DATE = System.currentTimeMillis();
public static long show(){
return INNER_STATIC_DATE;
}
}
class InnerClass {
public long INNER_DATE = 0;
public InnerClass() {
timeElapsed();
INNER_DATE = System.currentTimeMillis();
}
}
public static void main(String[] args) throws InterruptedException {
System.out.println("外部类静态变量加载时间:" + OuterClass2.OUTER_DATE);
//不管把静态内部类注释还是非静态内部类注释 总是会加载外部类
//System.out.println("非静态内部类加载时间" + new OuterClass2().new InnerClass().INNER_DATE);
//OuterClass2.InnerStaticClass staticInner = new OuterClass2.InnerStaticClass();
System.out.println("静态内部类加载时间" + OuterClass2.InnerStaticClass.show());
}
//单纯的为了耗时,来扩大时间差异
private void timeElapsed() {
for (int i = 0; i < 10000000; i++) {
int a = new Random(100).nextInt(), b = new Random(100).nextInt();
a = a + b;
}
}
}
外部类静态块加载时间:1627945922547
外部类静态变量加载时间:1627945922547
静态内部类加载时间1627945922550
外部类静态块加载时间:1627946028821
外部类静态变量加载时间:1627946028820
外部类构造函数时间:1627946029235
非静态内部类加载时间1627946029671
6.内部类的加载过程
静态内部类和非静态内部类一样,都是在被调用时才会被加载。在加载静态内部类的过程中也会加载外部类。
非静态内部类的实例化依赖于外部类,所以内部类的实例化会导致外部类的实例化,自然也需要加载外部类。
https://www.cnblogs.com/maohuidong/p/7843807.html
成员内部类
外界如何创建对象
1.非静态内部类
Outer.MemberInner oi = new Outer().new MemberInner();
2.静态内部类
Outer.MemberInner memberInner = new Outer.MemberInner();
刚才我们讲解过了,成员内部类的使用,但是一般来说,在实际开发中是不会这样使用的。
因为一般内部类就是不让外界直接访问的。
成员内部类的常见修饰符:private&static
private 为了保证数据的安全性
static 为了让数据访问更方便
内部类用静态修饰是因为内部类可以看成是外部类的成员
被静态修饰的成员内部类只能访问外部类的静态成员
package 成员内部类;
/*
成员内部类的修饰符:
private 为了保证数据的安全性
static 为了方便访问数据
注意:静态内部类访问的外部类数据必须用静态修饰。
*/
public class Outer {
private int num = 10;
private static int num2 = 100;
protected class Inner {
public void show() {
System.out.println(num);
System.out.println(num2);
}
//非静态内部类中不能有静态成员变量和方法
/*static int k = 0;
public static void show3() {
System.out.println("show3");
}
*/
}
//内部类用静态修饰是因为内部类可以看成是外部类的成员
public static class StaticInner {
public void show() {
//System.out.println(num);
//被静态修饰的成员内部类只能访问外部类的静态成员
System.out.println(num2);
}
//静态内部类中可以有静态成员变量和方法
static int k =0;
public static void show2() {
//被静态修饰的成员内部类只能访问外部类的静态成员
//System.out.println(num);
System.out.println(num2);
}
}
}
class InnerClassDemo4 {
public static void main(String[] args) {
//成员内部类被静态修饰后的访问方式是:
//格式:外部类名.内部类名 对象名 = new 外部类名.内部类名();
// 限定的静态内部类
Outer.StaticInner staticInner = new Outer.StaticInner();
staticInner.show();
staticInner.show2();
//show2()的另一种调用方式
Outer.StaticInner.show2();
//成员内部类的访问方式是:
//格式:外部类名.内部类名 对象名 = new 外部类名().内部类名();
//非静态内部类
Outer.Inner oi = new Outer().new Inner();
oi.show();
}
}
非静态内部类里面不能有静态成员和方法
非静态内部类不能有静态成员!因为成员内部类必须先实例化外部类对象然后再实例化成员内部类。
非static的内部类,在外部类加载的时候,并不会加载它,所以它里面不能有静态变量或者静态方法。因为还没有被加载,无法通过类名调用!
1.static类型的属性和方法,在类加载的时候就会存在于内存中。
2.要使用某个类的static属性或者方法,那么这个类必须要加载到jvm中。
基于以上两点,可以看出,如果一个非static的内部类如果具有static的属性或者方法.
那么就会出现一种情况:内部类未加载,但是却试图在内存中创建static的属性和方法,这当然是错误的。
原因:类还未加载,在方法区不存在,但却希望操作它的属性和方法。
java很多像这类不能共同存在的 一般都与他们的生命周期有关。
比如 静态成员和静态方法是随着类的加载而存在的,也就是说内部类的静态属性是随着类的加载的,但是内部类的实例,是创建后才存在的,也就是说其静态属性优先存在于他的类实例的存在,这显然是矛盾的。所以如果有静态成员变量也要把内部类设为静态的,这样他们的生命周期就是相同了。
如果内部类没有被static修饰,就需要实例化内部类才能调用,说明非static的内部类不是自动跟随主类加载的,而是被实例化的时候才会加载。
而static的语义,就是主类能直接通过内部类名来访问内部类中的static方法,而非static的内部类又是不会自动加载的,所以这时候内部类也要static,否则会前后冲突。
成员内部类面试题
//补齐程序(注意:内部类和外部类没有继承关系)
class Outer {
public int num = 10;
class Inner {
public int num = 20;
public void show() {
int num = 30;
System.out.println(?);
System.out.println(??);
System.out.println(???);
}
}
}
在控制分别输出:30,20,10
public class Outer {
public int num = 10;
class Inner {
public int num = 20;
public void show() {
int num = 30;
System.out.println(num);
System.out.println(this.num);
System.out.println(new Outer().num);
//System.out.println(Outer.this.num);
}
}
public static void main(String[] args) {
new Outer().new Inner().show();
}
}
局部内部类和匿名内部类
局部内部类(有类名)
匿名内部类(无类名,重点)
局部内部类可以直接访问外部类的成员,可以创建内部类对象。
通过对象调用内部类方法,来使用局部内部类功能。
为什么局部内部类的局部变量必须被final修饰
这个局部变量包括形参和内部类方法中的局部变量
内部类和外部类是都是类级别的,内部类不会因为定义在方法中就会随着方法的执行完毕就被销毁。
所以为了防止方法执行完毕,引用了一个不存在的变量,就copy一份局部变量作为内部类的成员变量。
当方法执行完毕局部变量被回收之后,实际访问的是局部变量的复制品即局部内部类的成员变量。
设置为final,是为了保证一致性。
package 匿名内部类;
public interface InnerInterface {
void printA();
}
package 匿名内部类;
/**
*内部类和外部类是处于同一个级别的,内部类不会因为定义在方法中就会随着方法的执行完毕就被销毁;
*所以为了防止方法执行完毕,引用了一个不存在的变量,就copy一份局部变量作为内部类的成员变量,
*当局部变量没有之后,实际访问的是copy的复制品.
*/
public class OuterClass{
public static void main(String[] args) {
OuterClass outerClass = new OuterClass();
//在调用完fun方法后,InnerInterface的printA方法中的a变量就要被销毁
outerClass.print(100);
System.out.println("方法执行完成之后,局部变量作用域终止了");
//但是执行printA方法发现a变量仍然存在
obj.printA();
outerClass.show(100);
}
public static InnerInterface obj = null;
/**
* 匿名内部类
* 局部变量a作用域只能是方法内部,但是匿名内部类不会随着方法执行完毕而被销毁,
* 就copy了一份局部变量,同时为了保证一致性,设置为final。
* */
public void print(int a) {
obj = new InnerInterface() {
@Override
public void printA() {
System.out.println(" print final param " + a);
}
};
}
/**
* 局部内部类
* 同样拷贝一份作为InnerClass的成员变量
* */
public void show(final int b) {
class InnerClass {
public void printB() {
System.out.println(" show final param " + b);
}
}
new InnerClass().printB();
}
}
因为局部变量会随着方法的调用完毕而消失。
这个时候,局部内部类对象obj并没有立马从堆内存中消失,obj还有引用指向它不能被立刻回收。
为了让数据还能继续被使用,就需要用final修饰,这样,在堆内存里面存储的其实是一个常量值。
通过反编译工具可以看一下。
根本原因就是作用域中变量的生命周期导致的
首先需要知道的一点是:内部类和外部类是同属于类级别,内部类不会因为定义在方法中就会随着方法的执行完毕就被销毁。
这里就会产生问题:当外部类的方法结束时,局部变量就会被销毁了,但是内部类对象可能还存在(只有没有人再引用它时,才会死亡)。这里就出现了一个矛盾:内部类对象访问了一个不存在的变量。
为了解决这个问题,就将局部变量复制了一份作为内部类的成员变量,这样当局部变量死亡后,内部类仍可以访问它,实际访问
的是局部变量的”copy”。这样就好像延长了局部变量的生命周期。
我们可以通过反编译生成的.class文件来实验:先执行命令 javac 匿名内部类/*.java -encoding utf-8 进行编译
javap是 Java class文件分解器,可以反编译,也可以查看java编译器生成的字节码。
//匿名内部类
class 匿名内部类.InnerClassTest$1 implements 匿名内部类.InnerInterface {
final int val$a;
final 匿名内部类.InnerClassTest this$0;
匿名内部类.InnerClassTest$1(匿名内部类.InnerClassTest, int);
public void printA();
}
//成员内部类
class 匿名内部类.InnerClassTest$1InnerClass {
final int val$b;
final 匿名内部类.InnerClassTest this$0;
匿名内部类.InnerClassTest$1InnerClass();
public void printB();
}
可见方法中的局部变量实际上确实会复制为内部类的成员变量使用。
问题又出现了:将局部变量复制为内部类的成员变量时,必须保证这两个变量是一样的,也就是如果我们在内部类中修改了成员变量,方法中的局部变量也得跟着改变,怎么解决问题呢?
将局部变量设置为final,对它初始化后,我就不让你再去修改这个变量,就保证了内部类的成员变量和方法的局部变量的一致性。这实际上也是一种妥协。
若变量是final时:
若是基本类型,其值是不能改变的,就保证了copy与原始的局部变量的值是一样的;
若是引用类型,其引用是不能改变的,保证了copy与原始的变量引用的是同一个对象。
这就使得局部变量与内部类内建立的拷贝保持一致。
匿名内部类
就是内部类的简化写法。
前提:存在一个类或者接口。这里的类可以是具体类也可以是抽象类。
格式:
new 类名或者接口名() {重写方法;}
本质:
是一个继承了类或者实现了接口的子类匿名对象
package 匿名内部类;
/*
匿名内部类
就是内部类的简化写法。
前提:存在一个类或者接口
这里的类可以是具体类也可以是抽象类。
格式:
new 类名或者接口名(){
重写方法;
}
本质是什么呢?
是一个继承了类或者实现了接口的子类匿名对象。
*/
interface Inter {
public abstract void show();
public abstract void show2();
}
class Outer {
public void method() {
//一个方法的时候
/*
new Inter() {
public void show() {
System.out.println("show");
}
}.show();
*/
//二个方法的时候
/*
new Inter() {
public void show() {
System.out.println("show");
}
public void show2() {
System.out.println("show2");
}
}.show();
new Inter() {
public void show() {
System.out.println("show");
}
public void show2() {
System.out.println("show2");
}
}.show2();
*/
//如果我是很多个方法,就很麻烦了
//那么,我们有没有改进的方案呢?
Inter i = new Inter() { //多态
public void show() {
System.out.println("show");
}
public void show2() {
System.out.println("show2");
}
};
i.show();
i.show2();
}
}
class InnerClassDemo6 {
public static void main(String[] args) {
Outer o = new Outer();
o.method();
}
}
匿名内部类面试题
//按照要求,补齐代码
interface Inter { void show(); }
class Outer {
//补齐代码
}
class OuterDemo {
public static void main(String[] args) {
Outer.method().show();
}
}
要求在控制台输出”HelloWorld”
package digitalwallet;
import org.omg.PortableInterceptor.INACTIVE;
interface Inter {
void show();
}
class Outer {
public static Inter method() {
return new Inter() {
@Override
public void show() {
System.out.println("Hello World");
}
};
}
}
class OuterDemo {
public static void main(String[] args) {
Outer.method().show();
}
}