内部类(Inner Class)是Java中一种特殊的类,它定义在另一个类的内部。内部类(四种内部类)可以访问其外部类(即包含它的类)的所有成员(属性和方法),包括私有成员,因为内部类作为类的五大成员(属性、方法、构造器、代码块、内部类)之一相当于外部类的一部分,因此它有权访问外部类的所有成员,无论这些成员的访问级别如何。
一、成员内部类
成员内部类(Member Inner Class)是定义在外部类中的内部类,它作为外部类的一个成员存在。成员内部类可以访问外部类的所有成员,包括私有成员。成员内部类的对象总是与创建它的外部类对象相关联。
定义:
public class A{
private int num=10;
public class B{
}
}
细节:
1.成员内部类可以访问外部类的所有成员,包括私有成员。
2.可以添加任意访问修饰符(public、protected、默认、private),因为它的地位就是一个成员。注意:在成员内部类中使用访问修饰符只对外部其他类有效,对于外部类本身,它总是可以访问其内部类的成员,无论这些内部类的访问修饰符是什么。这是因为内部类是外部类的一部分,它们共享同一个命名空间。(注意区分 外部其他类、外部类、成员内部类)
public class A{//外部类
private class B{//创建一个私有的成员内部类
private int num=10;
}
public static void main(String[] args) {
A a = new A();
A.B b = a.new B();
}
}
class Test{//这是外部其他类
public static void main(String[] args) {
A a = new A();
A.B b = a.new B();//这里会报错因为成员内部类是私有的
}
}
解释:因为成员内部类的访问修饰是private所以外部其他类不能创建成员内部类的对象!!!
如果将成员内部类的访问修饰改为public,外部其他类就能创建成员内部类的对象 ,但是成员内部类的私有属性和方法在外部其他类也不能访问!!!
public class A{//外部类
public class B{//创建一个公共的成员内部类
private int num=10;//这是一个私有属性
}
public static void main(String[] args) {
A a = new A();
A.B b = a.new B();
System.out.println(b.num);
}
}
class Test{//这是外部其他类
public static void main(String[] args) {
A a = new A();
A.B b = a.new B();
System.out.println(b.num);//这里会报错因为成员内部类的属性是私有的
}
}
解释: 因为成员内部类的属性num的访问修饰是private所以在外部其他类中创建的成员内部类对象也不能访问私有的num属性
3.成员内部类不能用static修饰属性或方法
解释:成员内部类是外部类的一个成员,它依赖于外部类的实例。内部类的对象总是与创建它的外部类对象相关联。因此,内部类的非静态成员(包括实例变量和方法)需要通过外部类的实例来访问。也可以理解为外部类的static只能修饰成员中的属性或方法不能修饰成员内部类
4.作用域:和外部类的其他成员一样是整个类体
5.调用成员内部类的方式:在外部类的成员方法中创建成员内部类对象,再到主方法中创建外部类的对象调用成员方法(该方法是成员内部类对象所在的方法)
public class A{
private class B{//创建一个私有的成员内部类
private int num=10;
public void test(){
System.out.println("hello");
}
}
public void inner(){//在成员方法中创建内部类的对象并调用方法
B b = new B();
b.test();
System.out.println(b.num);
}
public static void main(String[] args) {
A a = new A();
a.inner();
}
}
结果如下:
hello
10
6.外部其他类访问成员内部类:
(1)直接在外部其他类的主方法中创建外部类的对象再调用成员内部类的对象(理解:通过外部类的对象名访问成员内部类,因为成员内部类是外部类的一个成员)
语法格式:
外部类名.内部类名 变量名 = 外部类对象名.new 内部类名();
public class A{//外部类
public class B{//公共的成员内部类
}
}
class Test{//这是外部其他类
public static void main(String[] args) {
A a = new A();
A.B b = a.new B();//创建成员内部类的对象
}
}
(2)直接在主方法中创建内部类的对象(其实就是将外部类的对象名替换成创建外部类对象)
语法格式:
外部类名.内部类名 变量名 = new 外部类名().new 内部类名();
public class A{//外部类
public class B{//公共的成员内部类
}
}
class Test{//这是外部其他类
public static void main(String[] args) {
A.B b = new A().new B();//创建成员内部类的对象
}
}
8.如果外部类和成员内部类的属性或方法重名时,默认遵循就近原则,如果想访问外部类的成员,则可以使用(外部类名.this.成员)去访问
public class A{//外部类
public int num=11;
class B{//内部类
public int num=10;
public void print(){
System.out.println("访问外部类的属性:"+A.this.num);
System.out.println("访问成员内部类的属性:"+num);
}
}
public void inner(){
B b = new B();
b.print();
}
public static void main(String[] args) {
A a = new A();
a.inner();
}
}
二、静态内部类
静态内部类(Static Nested Class)是定义在另一个类的静态成员位置上的内部类。静态内部类与外部类的关系类似于静态成员与外部类的关系。静态内部类可以访问外部类的静态成员,但不能直接访问外部类的非静态成员。外部类可以通过创建静态内部类的实例来访问静态内部类的成员。
定义:
public class A{//外部类
static class B{//静态内部类
}
}
细节:
1.静态内部类定义在外部类的静态成员位置所以用static修饰
2.可以直接访问外部类的所有静态成员(包含私有的)但不能直接访问外部类的非静态成员,如果想访问外部类的非静态成员需要创建外部类的实例对象
public class A{//外部类
private int num=10;
static class B{//静态内部类
public void print(){//访问外部类的非静态成员需要创建外部类的对象
A a = new A();
System.out.println(a.num);
}
}
}
3.可以添加任意访问修饰符(public、protected、默认、private),因为它的地位就是一个静态成员。(作用和成员内部类一样)
4.作用域:同其他成员一样是整个类体
5.静态内部类可以声明静态和非静态成员变量和方法,而成员内部类、局部内部类、匿名内部类不能声明静态成员变量和方法。
public class A{//外部类
static class B{//静态内部类
public static int num=10;
public void print(){//访问外部类的非静态成员需要创建外部类的对象
System.out.println(num);
}
}
}
6.外部类可以访问静态内部类的所有成员,在外部类的成员方法中创建静态内部类对象,再到主方法中创建外部类的对象调用成员方法(该方法是静态内部类对象所在的方法)这里和成员内部类的调用方式一样
public class A{//外部类
static class B{//静态内部类
public static int num=10;
public void print(){//访问外部类的非静态成员需要创建外部类的对象
System.out.println(num);
}
}
public void inner(){
B b = new B();
b.print();
}
public static void main(String[] args) {
A a = new A();
a.inner();
}
}
7.外部其他类访问静态内部类:
(1)在主方法中直接创建静态内部类对象(理解:因为静态内部类是静态成员所以直接通过类名调用,看new 和()中间部分)
外部类名.静态内部类名 变量名 = new 外部类名.静态内部类名();
public class A{//外部类
static class B{//静态内部类
public static int num=10;
public void print(){//访问外部类的非静态成员需要创建外部类的对象
System.out.println(num);
}
}
}
class Test{//外部其他类
public static void main(String[] args) {
A.B b = new A.B();
b.print();
}
}
结果如下:
10
(2)外部其他类可以通过"外部类.内部类.静态成员"的方式访问内部类中的静态成员,但是其中的非静态成员需要上面创建静态内部类的实例对象才能访问。
public class A{//外部类
static class B{//静态内部类
public static int num=10;
public void print(){//访问外部类的非静态成员需要创建外部类的对象
System.out.println(num);
}
}
}
class Test{
public static void main(String[] args) {
System.out.println(A.B.num);
}
}
结果和上面一样
8.如果外部类和静态内部类的静态属性或静态方法重名时,默认遵循就近原则,如果想访问外部类的静态成员,则可以使用(外部类名.静态成员)去访问,因为静态属性是类的成员所以直接通过类名调用
public class A{//外部类
public static int num=20;
static class B{//静态内部类
public static int num=10;
public void print(){//访问外部类的非静态成员需要创建外部类的对象
System.out.println("访问静态内部类的静态属性num:"+num);
System.out.println("访问外部类的静态属性num:"+ A.num);
}
}
}
class Test{
public static void main(String[] args) {
A.B b = new A.B();
b.print();
}
}
结果如下:
访问静态内部类的静态属性num:10
访问外部类的静态属性num:20
三、局部内部类(方法内部类)
局部内部类是在一个方法或代码块中定义的类。局部内部类可以访问外部类的成员变量和方法,以及局部变量。局部内部类的生命周期只限于定义它的方法或代码块。
细节:
1.定义:局部内部类定义在一个方法中或代码块中
public class Test {
private void inner(){
class Inner{
//这是一个内部类,定义在inner方法中
}
}
{ class inner2{
//这是一个内部类,定义在代码块中
}
}
}
2.可以直接访问外部类的所有成员,包括私有的
public class Test {
private int num=10;
private void hi(){
System.out.println("hi");
}
private void inner(){
class Inner{
//这是一个内部类,定义在inner方法中
public void test(){
System.out.println(num);//访问外部类的私有属性
hi();//访问外部类的私有方法
}
}
}
}
解释:因为内部类作为类的五大成员(属性、方法、构造器、代码块、内部类)之一相当于外部类的一部分,因此它有权访问外部类的所有成员,无论这些成员的访问级别如何。
3.外部类访问内部类的成员:(1)创建内部类的对象,对象调用方法(必须写在外部类方法作用域内,内部类外)(2)需要创建外部类的对象,然后使用外部类对象来调用定义方法内部类的方法。
public class Test {
private int num=10;
private void hi(){
System.out.println("hi");
}
public static void main(String[] args) {
Test test = new Test();
test.inner();
}
private void inner(){
class Inner{
//这是一个内部类,定义在inner方法中
public void test(){
System.out.println(num);//访问外部类的私有属性
hi();//访问外部类的私有方法
}
}
Inner inner = new Inner();
inner.test();
}
}
结果如下:
10
hi
为什么不能直接在外部类创建内部类的对象然后调用内部类的方法?
答:因为局部内部类位于外部类的一个方法里面,它在一个局部作用域所以不能直接创建类的对象进行访问,而是创建外部类的对象,然后使用外部类对象来调用定义方法内部类的方法
4.局部内部类因为在成员方法中创建的,因此和方法内部成员使用规则一样,局部内部类的类名不能使用任何访问修饰符,内部类中的方法也不能使用static修饰(即类中不能包含静态成员)
解释:因为局部内部类的作用域仅限于定义它的方法或代码块,因此没有外部类可以用来限定它的访问级别。方法内部类通常用于封装一个与外部类方法紧密相关的辅助功能,而不需要在类级别上暴露这个内部类。由于它只在方法内部使用,所以不需要使用访问修饰符来控制它的可见性。
public class Test {
private void inner(){
// 以下代码将导致编译错误
private class Inner{
}
}
}
解释:内部类中的方法不能使用static修饰:
作用域:成员方法的作用域通常限于方法的执行期间。一旦方法执行完毕,方法的局部变量就会消失。静态变量是类的成员,它们与类的生命周期相关,而不是与方法的生命周期相关。因此,在成员方法中定义静态变量会导致作用域的不一致,在普通方法中定义静态变量类的成员就会报错
实例化:成员方法通常需要通过类的实例来调用,因为它们依赖于类的实例来访问类的成员。静态变量不需要类的实例,它们可以通过类名直接访问。因此,在成员方法中定义静态变量会导致实例化逻辑的不一致,在普通方法中定义静态变量类的成员就会报错
加载:静态属性和静态方法是在类加载时就被加载和初始化的,普通属性和方法需要在创建类的实例时才会被分配空间加载,如果在普通方法中定义静态变量类的成员就会报错,因为静态属性和静态方法与类的生命周期相关,而不是与实例的生命周期相关
5.内部类可以用final和abstract修饰(本质还是类)
代码演示:(当使用abstract修饰方法时,那么包含抽象方法的类必须声明为抽象类)
public class Test {
private void inner(){
//这是一个抽象的内部类
abstract class Inner{
public abstract void hi();
}
class Inner2 extends Inner{
public void hi(){
}
}
}
}
这里创建了一个抽象的内部类Inner并且有个子内部类重写了Inner的hi()方法
代码演示:(当你声明该类为final时,子类将不能继承该类)
public class Test {
private void inner(){
//这是一个声明为final内部类
final class Inner{
}
//Inner2不能继承声明为final的类,这里会报错!
class Inner2 extends Inner{
}
}
}
6.如果外部类和局部内部类的成员重名时,默认遵循就近原则,如果想访问外部类的成员,则可以使用(外部类名.this.成员)去访问
public class Test {
private int num=10;
public static void main(String[] args) {
Test test = new Test();
test.inner();
}
private void inner(){
//这是一个抽象的内部类
class Inner{
private int num=10;
public void print(){
System.out.println("访问Inner类的属性:"+num);
System.out.println("访问外部类Test的属性:"+Test.this.num);
}
}
Inner inner = new Inner();
inner.print();
}
}
解释:外部类名.this 这一部分可以理解为外部类的对象,然后通过对象名调用属性
四、匿名内部类
顾名思义,匿名内部类即没有类名的内部类。在一般情况下,类的定义都需要使用关键字class,然后使用new进行实例化,但是如果对某个类只会使用一次,那么这个类的名字对于程序而言就可有可无,这时可以将该类的定义及其对象的创建放到一起完成(与传统写法的区别是:匿名内部类实现了接口,重写了该接口中全部的抽象方法,同时还进行了实例对象的创建,完成了参数的传递,注意这些步骤是一起完成的),以简化程序的编写,这就是匿名内部类的使用场景,匿名内部类常用于简化抽象类和接口的实现。
定义:
new父类(参数列表)或 父接口(){
//匿名内部类实现部分
};
使用匿名内部类的前提是内部类可以继承一个类或者实现一个接口,所以实际上匿名内部类会隐式地继承一个类或者实现一个接口,或者说,匿名内部类是一个继承了该类或者实现了该接口的匿名子类。需要注意的是,由于接口没有构造方法,所以一个实现接口的匿名内部类的括号里一定是空参数;而继承一个类的匿名内部类会调用其父类的构造方法,所以括号里可以是空参数,也可以传入参数。
如果想要接收实例对象:
父类/父接口 对象名 = new 父类(参数列表)或 父接口(){
//匿名内部类实现部分
};
细节:
注意:成员内部类、匿名内部类和局部内部类都是非静态内部类的情况,匿名内部类和局部内部类都是在局部作用域中所以局部内部类的所有限制同样对匿名内部类生效,即:和局部内部类的细节一样,这里就不再重复!!!
1.匿名内部类返回的是一个实例对象,返回类型是它所实现的接口或继承的类的类型。当匿名内部类实现接口时,它实际上创建了一个实现了该接口的新类(尽管这个类没有名字),并返回了这个新类的一个实例对象。但是,因为匿名内部类没有名字(隐藏的),所以我们无法直接引用它的类型,当匿名内部类实现接口时,返回的类型是接口本身的类型,而不是匿名内部类本身的类型。
代码演示:(对比传统写法和使用匿名内部类的写法)
(1)传统写法
public interface A {
void speak();
}
class B implements A{
public void speak(){
System.out.println("hello");
}
}
class Test{
public static void main(String[] args) {
B b = new B();
b.speak();
}
}
结果如下:
hello
(2)使用匿名内部类
public interface A {
void speak();
}
//class B implements A{
// public void speak(){
// System.out.println("hello");
// }
//}
class Test{
public static void main(String[] args) {
A b =new A(){
@Override
public void speak() {
System.out.println("hello");
}
};
b.speak();
}
}
或者另一种写法:
public interface A {
void speak();
}
//class B implements A{
// public void speak(){
// System.out.println("hello");
// }
//}
class Test{
public static void main(String[] args) {
new A(){
@Override
public void speak() {
System.out.println("hello");
}
}.speak();
}
}
在这个例子中,b
是一个匿名内部类的对象的引用(对象名),它引用所指向的对象实现了A
接口。b
的类型是A
,因为它被声明为A
类型的变量。这意味着b
可以调用A
接口中定义的任何方法。
这里可以看作是向上转型。向上转型是指将一个子类对象赋值给一个父类类型的变量(对象名),或者将一个实现了某个接口的类对象赋值给一个接口类型的变量。在Java中,向上转型是安全的,因为子类对象拥有父类所有的属性和方法。
在这个例子中,b
是一个A
类型的变量(对象名),但它实际上引用的是一个实现了A
接口的匿名内部类的实例对象。这个匿名内部类是A
接口的子类,因此可以将它的实例对象赋值给一个A
类型的变量,这就是向上转型。
public interface A {
void speak();
}
class Test{
public void test(A a){//接收接口类型参数
a.speak();
}
public static void main(String[] args) {
Test test = new Test();
test.test(new A() {
@Override
public void speak() {
System.out.println("hello");
}
});
}
}
在这个例子中,Test类中的test方法方法需要传入一个接口A类型的参数,这里直接在主方法中通过创建Test类的对象再调用test方法,在参数列表中使用匿名内部类,该类实现了A接口,重写了该接口中全部的抽象方法,同时还进行了实例对象的创建,完成了参数的传递。(即:匿名内部类返回的是一个实例对象,返回类型是它所实现的接口或继承的类的类型)
另一种写法:
public interface A {
void speak();
}
class Test{
public void test(A a){//接收接口类型参数
a.speak();
}
public static void main(String[] args) {
Test test = new Test();
A a = new A() {
@Override
public void speak() {
System.out.println("hello");
}
};
test.test(a);
}
}
2.查找匿名内部类的类名用 对象名.getClass()
在Java中,匿名内部类没有显式的类名,因为它们是在创建对象时直接定义的。然而,编译器会为每个匿名内部类生成一个唯一的类名。这个类名通常由外部类的名称、$
符号和匿名内部类的序号组成。例如,如果外部类名为OuterClass
,那么第一个匿名内部类的类名可能是OuterClass$1
,第二个可能是OuterClass$2
,依此类推。
要查找匿名内部类的类名,您可以使用Java反射API。以下是一个示例代码,演示如何获取匿名内部类的类名:
public interface A {
void speak();
}
class Test{
public static void main(String[] args) {
A a = new A() {
@Override
public void speak() {
}
};
System.out.println(a.getClass());
}
}
在这个示例中,a
是一个匿名内部类的对象名。通过调用a.getClass()
,我们可以获取到这个匿名内部类的类名。输出将是类似于Test$1
的字符串,其中Test
是包含匿名内部类的类的名称(外部类),1
是匿名内部类的序号。本质:jdk底层在创建匿名内部类 Test$1
,然后创建了Test$1的实例对象并且把地址返回给a。
请注意,这个类名是由编译器生成的,并且可能因编译器或JVM的不同而有所不同。
结果如下:
class Test$1
3.匿名内部类的作用域通常限制在它们被定义的代码块中。例如,它们通常在方法内部或代码块内部定义。这意味着匿名内部类只能在该作用域内使用,并且只能在该作用域内实例化一次。下面代码证明匿名内部类只能使用一次的情况:
interface A{
void greet();
}
class Main {
public static void main(String[] args) {
// 第一次使用匿名内部类
A a = new A() {
@Override
public void greet() {
}
};
// 第二次使用匿名内部类
A a2 = new A() {
@Override
public void greet() {
}
};
// 比较两个匿名内部类的类名
System.out.println("a class name: " + a.getClass().getName());
System.out.println("a2 class name: " + a2.getClass().getName());
}
}
结果如下:
a class name: Main$1
a2 class name: Main$2
在这个示例中,我们创建了两个匿名内部类的实例a和a2
。通过比较它们的类名,我们可以看到它们是不同的类。这证明了匿名内部类只能使用一次。