在Java中,可以将一个类定义在另一个类里面或者一个方法里面,这样的类称为内部类。与之对应,包含内部类的类被称为外部类。
优势:内部类提供了更好的封装,可以把内部类隐藏在外部类之内,通过访问修饰符控制其他类对内部类的访问,更好的实现了信息隐藏
分类:成员内部类、静态内部类、方法内部类、匿名内部类
成员内部类
内部类中最常见的就是成员内部类,也称为普通内部类
public class Person {
int age;
//获取内部类对象方法
public Heart getHeart(){
return new Heart();
}
//成员内部类
class Heart {
public String beat(){
return "心脏在跳动";
}
}
}
获取成员内部类对象实例
方式一:通过外部类对象创建内部类实例,即new 外部类().new 内部类()
或者 外部类对象.new 内部类()
方式二:通过外部类定义的成员方法获取,即外部类对象.获取方法()
public class Test {
public static void main(String[] args) {
Person ricky = new Person();
//获取成员内部类对象实例,方式1:new 外部类.new 内部类
Person.Heart heart = new Person().new Heart();
System.out.println(heart.beat());
//获取成员内部类对象实例,方式1:外部类对象.new 内部类
heart = ricky.new Heart();
//获取成员内部类对象实例,方式2:外部类对象.获取方法
Person.Heart heart2 = ricky.getHeart();
System.out.println(heart2.beat());
}
}
注意事项
1、内部类在外部使用时,无法直接实例化,需要借由外部类信息才能完成实例化
2、内部类的访问修饰符,可以任意,但是访问范围会受到影响
3、内部类可以直接访问外部类的成员;如果出现同名属性或者方法,优先访问内部类中定义的
4、可以使用外部类.this.成员
的方式,访问外部类中同名的信息(成员属性和成员方法)
5、外部类访问内部类信息,需要通过内部类实例,无法直接访问
6、内部类编译后.class
文件的名称:外部类$内部类.class
public class Person {
String name = "大大";
//获取内部类对象方法
public Heart getHeart(){
//5.外部类访问内部类信息,需要通过内部类实例,无法直接访问name
new Heart().name = "明明";
return new Heart();
}
public void eat(){
System.out.println("人会吃东西");
}
//2.访问修饰符可以任意,设置为private则只能在此外部类中创建实例
private class Heart {
String name = "小小";
public String beat(){
//3.同名属性这里优先访问的是内部类中的name
String str = name + "的心脏在跳动";
//4.访问外部类中的同名属性
str = Person.this.name + "的心脏在跳动";
//访问外部类中的同名方法,直接调用eat()优先访问的是内部类中定义的
Person.this.eat();
return str;
}
//同名方法
public void eat(){
System.out.println("吃东西");
}
}
}
静态内部类
静态内部类,使用static
修饰,对象可以不依赖于外部类对象,直接创建
获取静态内部类对象实例:new 外部类名.静态类()
注意事项
1、静态内部类中,只能直接访问外部类的静态成员,如果需要调用非静态成员,可以通过对象实例
2、可以通过外部类.内部类.静态成员
的方式,访问内部类中的静态成员
3、当内部类中静态成员与外部类静态成员同名时,默认直接调用内部类中的成员,可以通过 外部类.成员
的方式访问外部类的同名静态成员,而对于外部类中的非静态成员,不管同不同名,都需要通过外部类实例访问
public class Person {
String name = "大大";
public static int age = 22;
//获取内部类对象方法
public Heart getHeart(){
return new Heart();
}
public void eat(){
System.out.println("人会吃东西");
}
//静态内部类
public static class Heart {
String name = "小小";
public static int age = 12;
public String beat(){
//2.静态内部类中,只能直接访问外部类的静态成员,如果需要调用非静态成员,可以通过对象实例
//直接访问eat()会报错
new Person().eat();
//成员方法可以直接访问内部类中的非静态成员和静态成员
String str = name + age + "岁的心脏在跳动";
//4.访问外部类中的非静态成员和同名静态成员
return new Person().name + Person.age + "的心脏在跳动";
}
}
//测试
public static void main(String[] args) {
//1.获取静态内部类对象实例
Person.Heart heart = new Person.Heart();
System.out.println(heart.beat());
//3可以通过外部类.内部类.静态成员的方式,访问内部类中的静态成员
Person.Heart.age = 15;
}
}
方法内部类
定义在外部类方法中的内部类,也称局部内部类(符合方法的相关规则)
注意事项
1、定义在方法内部,作用范围也在方法内
2、和方法内部成员使用规则一样,方法内部类不能使用任何访问修饰符,不能使用static
修饰
3、类中不能包含静态成员,但可以包含final
、abstract
修饰的成员
public class Person {
String name = "大大";
public static int age = 22;
public Object getHeart(){
//方法内部类:不能使用任何访问修饰符,不能使用static修饰
class Heart {
String name = "小小";
//类成员可以使用访问修饰符、final,但不能使用static修饰
public final int age = 12;
public String beat(){
new Person().eat();
return name + Person.age + "的心脏在跳动";
}
}
return new Heart().beat();
}
public void eat(){
System.out.println("人会吃东西");
}
//测试
public static void main(String[] args) {
Person ricky = new Person();
//调用包含方法内部类的方法
System.out.println(ricky.getHeart());
}
}
匿名内部类
顾名思义,就是没有名字,隐藏名字的意思,通常我们使用类都是通过class
类名定义类,然后使用时通过new
构造方法进行实例化,但是在一些场景下,对某个类的实例只会使用一次,那么这个类的名字对于程序而言就可有可无了。此时我们就可以将类的定义与类的创建,放到一起完成,简化程序的编写。
所以匿名内部类也就是没有名字的类,通常情况下我们可以通过匿名内部类来简化对于抽象类和接口实现的操作。
示例:完成一个不同人进行阅读的操作
//定义抽象父类
public abstract class Person {
//阅读方法
public abstract void read();
}
//定义子类
public class Man extends Person{
@Override
public void read() {
System.out.println("男生喜欢看科幻类书籍");
}
}
public class Women extends Person{
@Override
public void read() {
System.out.println("女生喜欢读言情小说");
}
}
使用传统的多态方式实现以及匿名内部类实现
public class Test {
//需求:根据传入的不同的人的类型,调用对应的read方法
//方案1:利用多态调用对应子类的实现
public void getRead(Person person){
person.read();
}
public static void main(String[] args) {
//方案一
Test test = new Test();
Man one=new Man();
Woman two=new Woman();
test.getRead(one);
test.getRead(two);
//方案二:不定义任何子类,使用匿名内部类完成具体的read方法实现
test.getRead(new Person(){
@Override
public void read() {
System.out.println("男生喜欢看科幻类书籍");
}
});
test.getRead(new Person(){
@Override
public void read() {
System.out.println("女生喜欢读言情小说");
}
});
}
}
匿名内部类在合适的场景下对于内存的损耗和对系统的性能影响就会相对较小,弊端就是只能使用一次,无法重复使用。
适用场景
- 只用到类的一个实例
- 类在定义后马上用到
- 给类命名并不会导致代码更容易被理解
典型应用
继承式的匿名内部类
public class Demo {
public static void main(String[] args) {
//继承式的匿名内部类(相当于定义了一个匿名的Thread子类,目的是重写其方法)
Thread thread = new Thread() {
@Override
public void run() {
for (int i = 0; i <= 5; i++) {
System.out.println(i + " ");
}
}
};
thread.start();
}
}
接口式的匿名内部类
public class Demo {
public static void main(String[] args) {
//接口式的匿名内部类(相当于创建了一个实现了接口的匿名类)
Runnable r = new Runnable() {
@Override
public void run() {
for (int i = 0; i <= 5; i++) {
System.out.println(i + " ");
}
}
};
Thread thread = new Thread(r);
thread.start();
}
}
参数式的匿名内部类
public class Demo {
public static void main(String[] args) {
//参数式的匿名内部类
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i <= 5; i++) {
System.out.println(i + " ");
}
}
});
thread.start();
}
}
结论:由上面三个例子可以看出,匿名内部类可以继承一个具体的父类,也可以实现某个接口。只要一个类是抽象的或是一个接口,那么其子类中的方法都可以使用匿名内部类来实现
注意事项
1、匿名内部类没有类型名称、实例对象名称
2、编译后的文件命名:外部类$数字.class
3、无法使用访问修饰符、也无法使用abstract
、static
修饰
4、无法编写构造方法,可以添加构造代码块,通过代码块完成匿名内部类的初始化
5、不能出现静态成员,不能出现抽象方法
6、匿名内部类可以实现接口也可以继承父类,但是不可兼得