文章目录
1. 内部类概述
1.1 含义:如果一个事物的内部包含另一个事物,那么这就是一个类内部包含另一个类。
1.2 例如:身体和心脏的关系、汽车和发动机的关系
1.3 分类:
- 成员内部类
- 局部内部类(包含匿名内部类)
2. 成员内部类的定义
2.1 成员内部类的定义格式
修饰符 class 外部类名称{
修饰符 class 内部类名称{
//.....
}
//......
}
2.2 注意:
- 内部类使用外部类的成员,随便访问;但是外用内,需要内部类对象
2.3 代码演示:外部类和内部类大概长什么样子
- 来一个 Body类 包含 Heart类 的,就是身体和心脏的包含关系嘛
//Body 类作为内部类
public class Body {
//Heart 类作为成员内部类
public class Heart{
//内部类方法
public void heartMethod(){
System.out.println("我是心脏,内部类成员方法");
System.out.println("我叫" + name); //可以调用外部类的 name
}
}
//外部类成员变量
private String name = "haha";
//外部类方法
public void bodyMethod(){
System.out.println("我是身体,外部类成员方法");
}
//外部类的getter
public String getName() {
return name;
}
//外部类的setter
public void setName(String name) {
this.name = name;
}
}
-
从代码可以看到,内部类Heart是可以调用外部类的成员的,并且内部类Heart在文件夹中的点class文件是
Body$Heart.class
,在内部类中‘$’符号是有特殊含义的。[外链图片转存失败(img-dpOvovCk-1564136694827)(C:\Users\STF\AppData\Roaming\Typora\typora-user-images\1564056143553.png)]
3. 成员内部类的使用
3.1 两种方式:
-
间接方式:在外部类的方法当中,使用内部类;然后main只要调用外部类的方法,就能间接使用到内部类
-
直接方式,和这个平常用的【类名称 对象名 = new 类名称();】差不多,多了一层而已,它是:
【外部类名称.内部类名称 对象名 = new 外部类名称().内部类名称();】
3.2 方式1:间接方式
- 在外部类的方法当中,使用内部类;然后main只要调用外部类的方法,就能间接使用到内部类。把上面的Body类代码搬下来,只加多一句代码(有注释的那一行)
public class Body {
public class Heart{
public void heartMethod(){
System.out.println("我是心脏,内部类成员方法");
System.out.println("我叫" + name); //可以调用外部类的 name
}
}
private String name = "haha";
public void bodyMethod(){
System.out.println("我是身体,外部类成员方法");
//就是这句代码,在外部类方法中,新建了这个匿名内部类,并顺便调用内部类的方法heartMethod()
new Heart().heartMethod();
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
- 在main中调用外部类方法bodyMethod()完成对内部类的间接使用
public class Demo07Main {
public static void main(String[] args) {
Body body = new Body();
body.bodyMethod(); //该方法里面新建了一个内部类,并调用了内部类的方法
}
}
/*输出结果:
我是身体,外部类成员方法
我是心脏,内部类成员方法
我叫haha
*/
3.3 方式2:直接公式
【外部类名称.内部类名称 对象名 = new 外部类名称().内部类名称();】
上面的Body类代码不变,在main中我们这样写
public class Demo07Main {
public static void main(String[] args) {
//公式:【外部类名称.内部类名称 对象名 = new 外部类名称().内部类名称();】
Body.Heart heart = new Body().new Heart();
heart.heartMethod();
}
}
4. 内部类的同名变量访问
假如内部类和外部类都有一个成员变量叫num,那怎么区分它们呢?
看下面代码注释就明白了
-
来一个Outer类,代表外部类;里面有一个Inner类,代表内部类
public class Outer { int num = 10; //外部类的成员变量 public class Inner{ int num = 20; //内部类的成员变量 public void methodInner(){ int num = 30; //内部类方法的局部变量num = 30 System.out.println(num); //局部变量,就近原则 System.out.println(this.num); //内部类的成员变量num = 20 System.out.println(Outer.this.num); //外部类的成员变量num = 10 } } }
-
在main中调用内部类Inner的方法methodInner,看看能不能正确地区分不同的num
public class Demo07Main_ { public static void main(String[] args) { //【外部类名称.内部类名称 对象名 = new 外部类名称().new 内部类名称()】 Outer.Inner inner = new Outer().new Inner(); //用内部类对象 inner 调用方法 innerMethod inner.methodInner(); } } /*输出结果: 30 20 10 */
5. 局部内部类定义
如果一个类是定义在一个方法内部的,那么这就是一个局部内部类
5.1 “局部”的概念:只有当前所属的方法才能使用它,出了这个方法外面就不能用了,这个方法外面的是“外部”
5.2 定义格式:
修饰符 class 外部类名称{
修饰符 返回值类型 外部类方法名称(参数列表){
class 局部内部类名称{
//....
}
}
}
5.3 和成员内部类有什么区别
成员内部类在方法外面,局部内部类在方法里面
局部内部类一旦走出了方法外面,就会失去作用,和局部变量一样
5.4 局部内部类里面的方法怎么使用
-
局部内部类里面的方法怎么使用,通过外部类方法来启动它
public class Outer { //这是一个外部类方法 public void methodOutter(){ //在外部类方法里面创建一个类Inner,class前面是没有关键字的 class Inner{ int num = 30; public void methodInner(){ //这个类里面也有一个方法,为局部内部类方法 System.out.println(num); } } //在这里新建一个局部内部类对象,启动它的methodInner方法 //当 methodOutter 方法运行时,methodInner 方法也会被连带运行 Inner inner = new Inner(); inner.methodInner(); } }
-
在main中启动外部类方法,外部类方法里面的局部内部类方法也会被启动
public class Demo07Main_ { public static void main(String[] args) { Outer outer = new Outer(); outer.methodOutter(); //30,这是局部内部类里面的成员num } }
5.5 内部类的权限问题
权限排名:public > protected > (default) > private
定义一个类的时候,权限修饰符规则:
- 外部类:public / (default) 这两个都可以
- 成员内部类:public / protected / (default) / private
- 局部内部类:什么都不要写,因为只有成员才有权限,局部内部类不算外部类的成员,只有那个方法才能访问它。
6. 局部内部类的final问题
局部内部类如果希望访问所在方法的局部变量,那么这个局部变量必须是【有效的final】
备注:从java8+开始,只要局部变量事实不变,那么final关键字可以省略
请注意下面代码中的那个错误的写法,因为num = 20之后,又把它重新赋值num = 10,所以报错,为什么?
原因:生命周期的问题
- new出来的对象是在堆内存中的,所以inner对象是在堆内存中的
- 局部变量num是跟着方法走的,在栈内存当中,当方法运行结束了,局部变量就会立马出栈,然后消失
- 但是new出来的对象并不会随着方法的结束而消失,因为它是在堆中,会持续存在,直到垃圾回收
- 那问题来了,num消失了,inner对象还没消失,那inner里面的methodInner()方法还怎么访问num呀?inner对象会提前将num复制一遍存好,num就算消失也无碍,但前提是要保证num这个值事实不变,如果变了,inner对象也不知该存哪个好,所以报错。
public class Outer {
public void methodOutter(){
//final int num = 20; 正确写法
/* int num = 20;
num = 10 错误写法,num = 20之后,不可以再重新赋值为10*/
int num = 20; //正确写法
class Inner{
public void methodInner(){
System.out.println(num); //打印num
}
}
Inner inner = new Inner();
inner.methodInner();
}
}
7. 匿名内部类(重点)
7.1 知识回顾:如何使用接口
一般要使用到接口的时候,我们会先写一个接口的实现类,然后重写实现类里面的抽象方法,最后创建实现类对象并通过对象去调用方法。用代码来回顾一下这个经典的流程
-
创建一个接口叫MyInterface,里面有一个抽象方法叫method()
public interface MyInterface { public abstract void method(); }
-
创建一个MyInterface接口的实现类叫 Impl ,重写实现类里面的抽象方法method
public class Impl implements MyInterface { @Override public void method() { System.out.println("创建了实现类,并重写了抽象方法"); } }
-
最后在main中创建实现类对象,通过对象去调用重写的method方法,至此,完成对接口的使用
public class Demo08Main { public static void main(String[] args) { Impl impl = new Impl(); impl.method(); //创建了实现类,并重写了抽象方法 } }
7.2 假如我们只想要使用一次接口,又不想像上面那么麻烦,就可以使用匿名内部类进行操作。
使用【匿名内部类】的时候,我们可以省略掉该类的定义
7.2.1 匿名内部类的定义格式:
//【匿名内部类】
接口名称 对象名 = new 接口名称(){
//覆盖重写所有抽象方法
}; // 这个大括号{}里面的就是一个内部类,并且没有名字,俗称匿名内部类
或者我们省略了对象名称,直接new就行
//【匿名对象】
new 接口名称(){ // 这个大括号{}里面的就是一个内部类,并且没有名字,俗称匿名内部类
//覆盖重写所有的抽象方法
}.方法名();
7.2.2 还是用上面的MyInterface接口,我们不需要再创建一个实现类,在main中重写接口的method方法
public class Demo08Main {
public static void main(String[] args) {
//方式1:使用匿名内部类的方法重写接口 MyInterface 中的method 方法, 【匿名内部类】
MyInterface obj = new MyInterface() {
@Override
public void method() {
System.out.println("实现了抽象方法method,接口对象不省略");
}
};
//用 MyInterface 接口的对象调用 method 方法
obj.method();
//方式2:使用匿名内部类的方法重写接口 MyInterface 中的method 方法, 【匿名对象】
new MyInterface(){
@Override
public void method() {
System.out.println("实现了抽象方法method,省略接口的对象");
}
}.method(); //在这里直接调用重写的 method 方法
}
}
7.2.3 注意:
-
【匿名内部类】,在【创建对象】的时候,只能创建一个对象,如果希望多次创建对象,而且类的内容一样的话,那么就必须使用单独定义的实现类了。
-
【匿名对象】,在【调用方法】的时候,只能调用唯一次方法。如果希望同一个对象,调用多次方法,那么就必须给对象起个名字。
-
【匿实名内部类】是省略了【实现类/子类名称】,但是【匿名对象】是省略了【对象名称】。强调;匿名内部类和匿名对象不是一回事,有区别!!!
8. 类作为成员变量
比如我创建一个武器类Weapon,然后再创建一个英雄类Hero,武器类是英雄类里面的一个成员变量
-
Weapon类
public class Weapon { //武器代号 private String code; public Weapon() { } public Weapon(String code) { this.code = code; } public String getCode() { return code; } public void setCode(String code) { this.code = code; } }
-
Hero类
//英雄类 public class Hero { //英雄的名字 private String name; //英雄使用的武器 private Weapon weapon; public Hero() { } public Hero(String name, Weapon weapon) { this.name = name; this.weapon = weapon; } //给英雄一个攻击的功能 public void attach(){ System.out.println(this.getName() + "英雄正在用" + weapon.getCode() + "武器攻击对方" ); } public String getName() { return name; } public void setName(String name) { this.name = name; } public Weapon getWeapon() { return weapon; } public void setWeapon(Weapon weapon) { this.weapon = weapon; } }
-
在main中创立英雄对象和武器对象,并把武器对象装备到英雄对象上
public class DemoHero { public static void main(String[] args) { //创建一个英雄对象 Hero hero = new Hero(); hero.setName("IronMan"); //创建一个武器给英雄,叫AK-47吧 Weapon weapon = new Weapon(); weapon.setCode("AK-47"); //给英雄装备上AK-47 hero.setWeapon(weapon); //让英雄上战场 hero.attach(); //IronMan英雄正在用AK-47武器攻击对方 } }
9. 接口作为成员变量
接口作为成员变量和类作为成员变量,其实两个都差不多,也都没有复杂
创建一个英雄类Hero,和一个名为技能的接口Skill,Skill里面有各种技能(抽象方法),现在把接口Skill作为英雄类的成员变量,在main中创建英雄对象并通过它来使用技能。
-
Hero类
public class Hero { //英雄的名字 private String hero; //英雄使用的技能 private Skil skil; public Hero() { } public Hero(String name, Skil skil) { this.hero = name; this.skil = skil; } //给英雄一个使用技能的方法 public void useSkill() { System.out.println(this.getHero() + "英雄正在使用" + "技能"); skil.use(); } public String getHero() { return hero; } public void setHero(String hero) { this.hero = hero; } public Skil getSkil() { return skil; } public void setSkil(Skil skil) { this.skil = skil; } }
-
Skill接口
public interface Skill { //使用技能,是一个抽象方法 public abstract void use(); }
-
Skill接口的实现类SkillImpl类
public class SkillImpl implements Skill { @Override public void use() { System.out.println("Biu...Biu...Biu"); } }
-
在main中创建英雄对象,设置英雄对象的技能,使用技能
public class Demo09Main { public static void main(String[] args) { //创建一个英雄 Hero hero = new Hero(); //英雄的名字叫艾希 hero.setHero("艾希"); //设置该英雄的技能,,这里把实现类对象当做接口传进去,编译器会自动向上转型 hero.setSkill(new SkillImpl()); //英雄开始使用技能 hero.useSkill(); System.out.println("------------------------"); //我们还可以使用匿名内部类的写法重写Skill接口中的方法 Skill skill = new Skill() { @Override public void use() { System.out.println("再来Biu...Biu...Biu"); } }; //给英雄设置技能 hero.setSkill(skill); //英雄开始使用技能 hero.useSkill(); System.out.println("------------------------"); //我们还可以使用匿名对象的方法,不用创建一个SkillImpl的实现类 hero.setSkill(new Skill() { @Override public void use() { System.out.println("还来Biu...Biu...Biu"); } }); //英雄开始使用技能 hero.useSkill(); } }
10. 接口作为方法的参数
我们就以ArrayList作为例子
ArrayList<T>
类是List<T>
接口的实现类
请看一下代码,写一个方法叫addNames,参数正是List接口
import java.util.ArrayList;
import java.util.List;
public class Main {
public static void main(String[] args) {
//多态的写法,左边接口,右边实现类
List<String> list = new ArrayList<>();
//调用addNames方法
list = addNames(list);
//打印 list 中添加的名字
for (int i = 0; i < list.size(); i++) {
System.out.println(list.get(i));
}
/* 输出结果:
周杰伦
权志龙
贾斯汀比伯
*/
}
public static List<String> addNames(List<String> list){
list.add("周杰伦");
list.add("权志龙");
list.add("贾斯汀比伯");
return list;
}
}