这个部分主要介绍内部类以及异常
1、内部类
1.1、内部类概述
内部类介绍参考(第二篇介绍的反编译以及内部类的内存溢出较为困难,可以后面学完返回再看):
https://blog.csdn.net/zhao_miao/article/details/83245816
https://blog.csdn.net/hacker_zhidian/article/details/82193100
https://blog.csdn.net/weixin_42762133/article/details/82890555
将一个类定义在另一个类的里面,对里面那个类就称为内部类(内置类,嵌套类)。
访问特点:
1)内部类可以直接访问外部类中的成员,包括私有成员;
2)而外部类要访问内部类中的成员必须要建立内部类的对象(同样可以访问内部类的私有成员)。
3)如果在外界的一个类中,想要访问某一个外部类的某一个内部类,创建对象的格式如下:
在外部类外部 创建非静态内部类
语法: 外部类.内部类 内部类对象 = new 外部类().new 内部类();
举例: Outer.Inner in = new Outer().new Inner();
在外部类外部 创建静态内部类
语法: 外部类.内部类 内部类对象 = new 外部类.内部类();
举例: Outer.Inner in = new Outer.Inner();
(因为静态内部类可以直接通过外部类类名来访问,而不需要重新new Outer())
例子1:
/*
内部类的访问规则:
1,内部类可以直接访问外部类中的成员,包括私有。
之所以可以直接访问外部类中的成员,是因为内部类中持有了一个外部类的引用,格式 外部类名.this
2,外部类要访问内部类,必须建立内部类对象。
*/
public class InnerClassDemo {
public static void main(String[] args) {
//访问外部类的mothod()方法
Outer out = new Outer();
out.method();
//如果在外界的一个类中,想要访问某一个外部类的某一个内部类,应该要如下创建对象
//形式一
Outer out1 = new Outer();
Outer.Inner in1 = out1.new Inner();//也就是内部类的前面总要跟外部类的部分
in1.function();
//形式二
Outer.Inner in2 = new Outer().new Inner();
in2.function();
}
}
//---------------------------------------------------
//外部类
class Outer
{
private int x = 3;
//内部类
class Inner
{
void function()
{
//这里内部类直接访问外部类的x,相当于在前面默认添加一个Outer.this
System.out.println("inner:"+Outer.this.x);
}
}
//外部类的方法
void method()
{
//外部类如果想访问内部类的成员,必须创建内部类的对象
Inner in = new Inner();
in.function();
}
}
例子2:
public class InnerClassDemo {
public static void main(String[] args) {
Outer.Inner obj = new Outer().new Inner();
obj.function();
//结果
// 内部类的局部变量:3
// 内部类的成员变量:2
// 外部类的成员变量:1
}
}
//---------------------------------------------------
//外部类
class Outer
{
private int x = 1;
//内部类
class Inner
{
int x = 2;
void function()
{
int x = 3;
System.out.println("内部类的局部变量:"+x);//虚拟机会寻找最近的x
System.out.println("内部类的成员变量:"+this.x);//加上this表示访问类的成员(this表示类的对象,因此this访问类的成员而不是方法的成员)
System.out.println("外部类的成员变量:"+Outer.this.x);//默认是外部类.this.x
}
}
}
例子3
public class OuterClass {
public int outField1 = 1;
protected int outField2 = 2;
int outField3 = 3;
private int outField4 = 4;
//外部类的构造方法
public OuterClass() {
// 在外部类对象内部,直接通过 new InnerClass(); 创建内部类对象
//既在外部类中可以创建内部类对象,可以通过这个对象来访问内部类public、protected、private以及默认类型的变量
InnerClassA innerObj = new InnerClassA();
System.out.println("创建 " + this.getClass().getSimpleName() + " 对象");//此处的this代表外部类的对象
System.out.println("其内部类的 field1 字段的值为: " + innerObj.field1);
System.out.println("其内部类的 field2 字段的值为: " + innerObj.field2);
System.out.println("其内部类的 field3 字段的值为: " + innerObj.field3);
System.out.println("其内部类的 field4 字段的值为: " + innerObj.field4);
}
//内部类
public class InnerClassA {
public int field1 = 5;
protected int field2 = 6;
int field3 = 7;
private int field4 = 8;
// static int field5 = 5; // 编译错误!普通内部类中不能定义 static 属性
//内部类的构造方法
public InnerClassA() {
System.out.println("创建 " + this.getClass().getSimpleName() + " 对象");//此处的this代表外部类的对象
System.out.println("其外部类的 outField1 字段的值为: " + outField1);
System.out.println("其外部类的 outField2 字段的值为: " + outField2);
System.out.println("其外部类的 outField3 字段的值为: " + outField3);
System.out.println("其外部类的 outField4 字段的值为: " + outField4);
}
}
public static void main(String[] args) {
//这里创建外部类的对象后,马上调用外部类的构造方法,外部类的构造方法含有内部类的对象创建
//随后马上调用内部类的构造方法,执行,再返回来调用外部类构造方法的下面部分
//因此结果是1、2、3、4、,再是5、6、7、8
OuterClass outerObj = new OuterClass();
System.out.println("---------------------");
}
}
//结果是
创建 InnerClassA 对象
其外部类的 outField1 字段的值为: 1
其外部类的 outField2 字段的值为: 2
其外部类的 outField3 字段的值为: 3
其外部类的 outField4 字段的值为: 4
创建 OuterClass 对象
其内部类的 field1 字段的值为: 5
其内部类的 field2 字段的值为: 6
其内部类的 field3 字段的值为: 7
其内部类的 field4 字段的值为: 8
---------------------
我们注意到,内部类对象可以访问外部类对象中所有访问权限的字段,同时,外部类对象也可以通过内部类的对象引用来访问内部类中定义的所有访问权限的字段。
什么时候要使用内部类(参考视频9-3):当描述事物时,事物的内部还有事物,该事物用内部类来描述。因为内部事务在使用外部事物的内容(外部类的变量与方法)。
1.2、使用内部类的优点与缺点
先看如下代码
class Outer{
private String str ="外部类中的字符串";
//**************************
//定义一个内部类
class Inner{
private String inStr= "内部类中的字符串";
//定义一个普通方法
public void print(){
//调用外部类的str属性
System.out.println(str);
}
}
//**************************
//在外部类中定义一个方法,该方法负责产生内部类对象并调用print()方法
public void fun(){
//内部类对象
Inner in = new Inner();
//内部类对象提供的print
in.print();
}
}
public class Test{
public static void main(String[] args)
{
//创建外部类对象
Outer out = new Outer();
//外部类方法
out.fun();
}
}
这段代码首先是内部类的print()方法直接调用外部类的str属性,再在外部类的fun()方法里面创建内部类的对象,调用内部类的print()方法。如果不使用内部类,代码如下:
class Outer{
private String outStr ="Outer中的字符串";
public String getOutStr()
{
return outStr;
}
public void fun(){ //2
//this表示当前对象,也就是外部类的对象
//这里通过this直接将外部类的对象注入内部类的构造方法,相当于直接在内部类中将外部类的对象赋予out
Inner in = new Inner(this); //3,this相当于new Outer()
in.print(); //5
}
}
class Inner{
private String inStr= "Inner中的字符串";
private Outer out;//创建一个私有的外部类引用,这一部分不能少,我们必须创建一个Outer类型的容器out,才能在构造方法中装下外部传入的outer对象
//构造注入,我们在内部类的构造方法将外部类的引用传入,这样我们在外部类创建内部类的对象的时候
//内部类的构造方法同样注入了外部类的对象,相当于直接在内部类中直接将外部类的对象赋予out
//这样便可以直接使用外部类的对象调用外部类的print()方法
public Inner(Outer out) //3
{
this.out=out; //4.为Inner中的out变量初始化
}
public void print(){ //6
System.out.println(out.getOutStr()); //7
}
}
public class Test{
public static void main(String[] args)
{
Outer out = new Outer(); //1.
out.fun(); //2.
}
}
但是去掉内部类之后发现程序更加难以理解。加上内部类之后我们发现更加方便了!
内部类的优点:
1)内部类与外部类可以方便的访问彼此的私有域(包括私有方法、私有属性);
2)内部类是另外一种封装,对外部的其他类隐藏;
3)内部类可以实现java的单继承局限,既创建多个内部类来继承多个父类(每一个内部类还是只继承一个父类)。
内部类的缺点:
内部类的结构较为复杂。例子:使用内部类实现多继承:
class A {
private String name = "A类的私有域";
public String getName() {
return name;
}
}
class B {
private int age = 20;
public int getAge() {
return age;
}
}
class Outter {
private class InnerClassA extends A {
public String name() {
return super.getName();
}
}
private class InnerClassB extends B {
public int age() {
return super.getAge();
}
}
public String name() {
return new InnerClassA().name();
}
public int age() {
return new InnerClassB().age();
}
}
public class Test2 {
public static void main(String[] args) {
Outter outter = new Outter();
System.out.println(outter.name());
System.out.println(outter.age());
}
}
1.3、内部类的分类
在Java中内部类主要分为成员内部类、静态内部类、方法内部类、匿名内部类。(内部类通过加private修饰符可以将其进行封装)
1.3.1、成员内部类
类比成员方法。成员内部类内部不允许存在任何static变量或方法,正如成员方法中不能有任何静态属性 (成员方法与对象相关、静态属性与类有关,静态成员在类加载进内存的时候就已经存在,而成员方法只有被对象调用的时候才加载进内存,因此成员方法不能含有静态属性,成员内部类不能含有静态属性)。
成员内部类是依附外部类的,只有创建了外部类才能创建内部类。
class Outer {
private String name = "test";
public static int age =20;
class Inner{
// public static int num =10;//成员内部类不能含有静态属性
public void fun()
{
System.out.println(Outer.this.name);//对于非静态成员,先是Outer外部类名,再是this表示访问(外部)类的成员!
System.out.println(Outer.age);//对于外部类的静态成员,直接用Outer调用,当然Outer与Outer.this都可以省略
}
}
}
public class Test{
public static void main(String [] args)
{}
}
1.3.2、静态内部类
关键字static可以修饰成员变量、方法、代码块、其实还可以修饰内部类,使用static修饰的内部类我们称之为静态内部类,静态内部类和非静态内部类之间存在一个最大的区别,非静态内部类在编译完成之后会隐含的保存着一个引用,该引用是指向创建它的外围类,但是静态类没有。没有这个引用就意味着:
1.静态内部类的创建不需要依赖外部类可以直接创建。
2.静态内部类不可以使用任何外部类的非static类成员(包括属性和方法),但可以存在自己的静态成员变量,可以调用外部类的静态成员。
静态内部类中无法访问外部类的非静态成员,因为外部类的非静态成员是属于每一个外部类对象的,而本身静态内部类就是独立外部类对象存在的,所以静态内部类不能访问外部类的非静态成员,而外部类依然可以访问静态内部类对象的所有访问权限的成员,这一点和普通内部类无异。
记住一句:静态与否影响的是别人访问它的方式!!!
对于一个静态成员(变量、方法、内部类),要考虑的是他们的上一级可以直接通过类名对他们进行访问!比如一个内部类是静态,那么就可以直接通过外部类名来访问这个内部类,接下来如果访问内部类的静态成员,那么可以通过内部类名直接访问,就是Outer.Inner.内部类静态成员;如果要访问内部类的非静态成员,还需要创建内部类的对象Outer.new Inner().内部类非静态成员!
下面2个例子就是很好的体现!!!
静态内部类例子1:关于外部其他类如何调用静态/非静态内部类的静态/非静态方法。
class Outer
{
private static int x = 3;
private int y = 4;
static class Inner//静态内部类
{
//静态内部类的非静态方法
void function()
{
//静态内部类只能调用外部类的静态成员
//对于成员内部类,y实际上是“Outer.this.y”(this表示的是外部类的引用),而静态内部类没有this,不能直接调用非静态成员
// System.out.println("inner:"+y);错误
//如果是外部类的静态成员,就相当于Outer.x,既外部类静态成员可以直接通过外部类名访问
System.out.println("inner :"+x);//这里完整格式为Outer.x(x为外部类的静态变量,直接用外部类的类名访问它)
System.out.println("静态内部类的非静态方法");
}
//静态内部类的静态方法
static void show()
{
System.out.println("静态内部类的静态方法");
}
}
//非静态内部类
class Inner2
{ //非静态内部类的非静态方法
// 非静态内部类不能有静态成员
void method()
{
System.out.println("非静态内部类的非静态方法");
}
}
}
public class InnerClassDemo2
{
public static void main(String[] args)
{
//在外部其他类中,如何直接访问非静态内部类的非静态成员呢?
new Outer().new Inner2().method();
//在外部其他类中,如何直接访问static内部类的非静态成员呢?
//因为Inner()是静态,Outer一加载,它立刻加载,不需要再创建Outer对象来访问内部类,而是通过外部类Outer的类名直接调用内部类
//而function不是静态成员,因此需要创建内部类的对象来访问
//这里相当于Outer.Inner obj = new Outer.Inner();obj.function();
new Outer.Inner().function();//这里格式注意new Outer后不需要括号,而Inner之后需要括号,相当于将new前移,本来应该是Outer.new Inner().function();,但是这种写法是不正确的,会报错,因此用new Outer.Inner().function();
//另一方面,由于这是在外部类的外界调用,因此需要加上外部类名
//在外部其他类中,如何直接访问static内部类的静态成员呢?
//对于静态内部类的静态成员,不需要在通过内部类的对象来调用它,也不需要通过外部类的对象来访问内部类,因此直接用外部类的类名.内部类类名.静态内部类静态成员
Outer.Inner.show();
}
}
//结果是
//非静态内部类的非静态方法
//inner :3
//静态内部类的非静态方法
//静态内部类的静态方法
静态内部类例子2:关于外部类内部的静态/非静态方法如何调用静态/非静态内部类的静态/非静态方法。
//某一个成员是否是静态,影响的是别人访问它的限制于方式,而与它自己访问别人的方式无关!!!
//如外部类的静态与非静态方法,他们的静态与否只是影响外界(外部类)访问他们的方式,而与他们访问静态/非静态内部类的静态/非静态方法的方式无关
//注意,哪一部分是静态,就通过它的上一级直接访问它(既静态与否影响的是别人访问它的方式)
//如静态内部类,直接通过外部类名访问它;内部类的静态方法,直接通过内部类名访问它
class Outer
{
//静态内部类
static class Inner1
{
//静态内部类的非静态方法
static void function1()
{
System.out.println("静态内部类的非静态方法");
}
//静态内部类的静态方法
void function2()
{
System.out.println("静态内部类的静态方法");
}
}
//非静态内部类
class Inner2
{ //非静态内部类的非静态方法
void function3()
{
System.out.println("非静态内部类的非静态方法");
}
//非静态内部类没有静态方法
}
//外部类内部的静态方法访问静态内部类Inner1
public static void method1()
{
//外部类的静态方法访问静态内部类的静态方法
//因为是静态内部类,直接通过外部类名访问它,因为这是在外部类内部,因此外部类名省略
//又因为访问的是静态内部类的静态方法,静态方法通过内部类名直接访问
Inner1.function1();//完整格式是Outer.Inner1.function1();
//外部类的静态方法访问静态内部类的非静态方法
// Inner().function2();//错
//这里同样省略外部类名
//访问静态内部类的非静态方法,必须创建内部类的对象
new Inner1().function2();//完整格式是new Outer.Inner1().function2();,因为在外部类内部,因此省略Outer
}
//外部类内部的静态方法访问非静态内部类Inner2
public static void method2()
{
//此处,由于内部类Inner2是非静态,不可以直接通过外部类名对其直接访问,而必须通过创建外部类对象,通过外部类再创建内部类的对象来访问
Outer out = new Outer();
Outer.Inner2 in = out.new Inner2();//由于在外部类的内部,Outer可以省略
//上面这两句相当于Outer.Inner2 in = new Outer().new Inner2();
in.function3();
}
//外部类内部的非静态方法访问静态内部类Inner(与静态方法相同)
public void method3()
{
//非静态成员method3可以直接访问静态成员Inner1
//下面对静态内部类的静态方法的访问与静态方法相同
Inner1.function1();
//下面对静态内部类的非静态方法的访问与静态方法相同
new Inner1().function2();
}
//外部类内部的非静态方法访问非静态内部类Inner2(与静态方法相同)
public void method4()
{
//此处,由于内部类Inner2是非静态,不可以直接通过外部类名对其直接访问,而必须通过创建外部类对象,通过外部类再创建内部类的对象来访问
Outer out = new Outer();
Outer.Inner2 in = out.new Inner2();
//上面这两句相当于Outer.Inner2 in = new Outer().new Inner2();
//当然,对于非静态方法method4(),它想要访问非静态内部类Inner2的非静态方法,可以直接创建内部类的对象Inner in = new Inner2(),而不需要外部类的引导。而对于静态方法method2(),它想要访问非静态内部类Inner2的非静态方法,则必须有外部类对象的引导!(该结论目前记住就可以!!!)
in.function3();
}
}
public class InnerClassDemo2
{
public static void main(String[] args)
{
//静态与否影响的是别人访问它的方式
//静态的method()1与method()2直接通过外部类类名访问
Outer.method1();
System.out.println("--------------");
Outer.method2();
System.out.println("--------------");
//非静态的method()3与method()4直接通过外部类的对象访问
Outer out = new Outer();
out.method3();
System.out.println("--------------");
out.method4();
System.out.println("--------------");
}
}
//结果是
静态内部类的非静态方法
静态内部类的静态方法
--------------
非静态内部类的非静态方法
--------------
静态内部类的非静态方法
静态内部类的静态方法
--------------
非静态内部类的非静态方法
--------------
1.3.3、方法(局部)内部类(参考视频9-4)
方法内部类顾名思义就是定义在方法里的类,它有几个需要注意的点:
1)方法内部类不允许使用访问权限修饰符(public、private、protected);
2) 方法内部类对外部完全隐藏,除了创建这个类的方法可以访问它以外,其他地方均不能访问 (换句话说其他方法或者类都不知道有这个类的存在,也就是说方法内部类对外部完全隐藏。);
3) 方法内部类如果想要使用方法形参,该形参必须使用final声明(JDK8形参变为隐式final声明)。另外,如果方法内部类想要访问方法的局部变量,该变量必须直接被final修饰或者已被赋值且始终未改变(有且仅有赋值一次),引用指向不能改变(也就是只能访问常量,常量是final或者只被赋值一次)。
4)无法创造静态信息,因为该方法使用完之后内部类会随方法一起被释放,不能存在静态成员(静态只能修饰成员,而方法内部类在方法内部,不算成员(全局),是局部);
重点:静态只修饰成员,不能修饰局部
例子1:
class Outer{
private int num =5;
//外部类的普通方法
public void dispaly(final int temp)//此处final可以省略,既形参必须是final(JDK8默认final),也就是形参为常量
{
//方法内部类即嵌套在方法里面
class Inner{//方法内部类不能用修饰符修饰
public void fun()
{
System.out.println(num);
// temp++;//形参为常量,内部形参不可以再变化
System.out.println(temp);//使用方法形参,该形参temp必须为final类型
}
}
//方法内部类只能在所在方法里面调用
//由于fun()方法非静态,创建Inner的对象来调用它
//由于Inner在display方法中,因此与Outer无关
new Inner().fun();
}
}
public class Test{
public static void main(String[] args)
{
Outer out = new Outer();
out.dispaly(2);
}
}
例子2:
/*
内部类定义在局部时,
1,不可以被成员修饰符修饰
2,可以直接访问外部类中的成员,因为还持有外部类中的引用。
但是不可以访问它所在的局部中的变量。只能访问被final修饰的局部变量。
*/
class Outer
{
int x = 3;
void method(final int a)
{
final int y = 4;
class Inner
{
void function()
{
System.out.println(a);
}
}
new Inner().function();
}
}
class InnerClassDemo3
{
public static void main(String[] args)
{
Outer out = new Outer();
//这样连续给method的final形参赋值是可以的
//因为对于某一句语句,如out.method(7);执行的时候a首先进栈,并将7赋予a并锁住(a一直为7),接着执行下面的语句
//method()方法执行完毕后,out.method(7);这一句会出栈释放,下面再调用out.method(8);,a又进栈,又可以重新赋值
//也就是每一句执行的时候,先进栈,执行完再出栈释放,接着执行下一句
out.method(7);
out.method(8);
}
}
1.3.4、匿名内部类(参考视频9-4,10分钟开始处,注意这部分讲述的匿名内部类的成因)
匿名内部类就是一个没有名字的方法内部类,因此特点和方法与方法内部类完全一致,除此之外,还有自己的特点:
1)匿名内部类必须继承一个抽象类或者实现一个接口;
2)匿名内部类没有类名,因此没有构造方法。
(其他四个特点:没有修饰符,只在方法内有效,变量必须为final型,不能含有static属性)
匿名内部类的格式: new 父类或者接口(){定义子类的内容}
其实匿名内部类就是一个匿名子类对象(其实现了父类的抽象方法),它将定义类与建立对象封装为一体,可以理解为带内容的对象,匿名内部类中定义的方法最好不要超过3个。
例子1:
//匿名内部类
//声明一个接口
interface MyInterface {
//接口中方法没有方法体
public abstract void test();
}
class Outer{
private int num = 5;
//外部类的方法
public void dispaly(final int temp)//匿名内部类想要使用temp,temp必须为final,final可省略
{
//匿名内部类,匿名的实现了MyInterface接口
//隐藏的class声明,匿名内部类没有名字,也没有修饰符修饰,因此其class声明隐藏
//注意格式为:new 接口名(){重写接口的抽象方法}.接口的抽象方法 ,
//既在创建抽象接口对象的时候直接在{}内重写接口的抽象方法,并在对象后直接引用该抽象方法
new MyInterface()
{
public void test()
{
System.out.println("匿名实现MyInterface接口");
System.out.println(temp);
}
}.test();
}
}
public class Test{
public static void main(String[] args)
{
Outer out = new Outer();
//在匿名内部类处已经重写抽象方法,我们直接调用外部类的方法即可实现接口的抽象方法
out.dispaly(3);
}
}
例子2:
//先定义一个抽象类
abstract class AbsDemo
{
abstract void show();
}
class Outer
{
public void function()
{
AbsDemo obj = new AbsDemo()//这里相当于多态
{
void show()
{
System.out.println("抽象类的show方法");
}
void abc()//这种函数一般这样定义没啥意义,一般不会这样定义
{
System.out.println("匿名内部类自己的方法");
}
};
obj.show();
// obj.abc();//编译失败,因为abc()是子类自己的方法,不能用父类的引用来调用它!
}
}
class InnerClassDemo4
{
public static void main(String[] args)
{
new Outer().function();
}
}
例子3:
interface Inter
{
public abstract void method();
}
class Test
{
//将function()方法的返回值设为接口Inter类型,这样Test.function()就返回接口Inter的对象
//另外,为了外部类可以通过类名直接访问,我们将这个匿名内部类设置为static类型
static Inter function()
{
//返回Inter对象,并在这个对象里面重写method方法,这样在外界类中就可以直接调用重写了的method()方法
return new Inter()
{
public void method()
{
System.out.println("抽象接口的抽象方法method");
}
};
}
}
public class InnerClassTest
{
public static void main(String[] args)
{
//在外界直接调用匿名内部类的方法
Test.function().method();
//这里如果我们想要调用show,就必须传入一个Inter类型的对象
//普通做法是先定义一个类(内部或外部)实现Inter接口,并将类的对象作为show方法的形参,但是这样很麻烦
//通过匿名内部类的方式实现
//由于show()方法的参数是Inter对象,我们直接在show里面创建Inter接口的对象,并直接使用匿名内部类的方式实现Inter的抽象方法method
//这样show方法就可以直接用Inter的对象in调用method方法
show(new Inter()
{
public void method()
{
System.out.println("在show方法内调用抽象接口的抽象方法method");
}
});
}
//什么时候会使用匿名内部类
//假设有如下方法
public static void show(Inter in)
{
in.method();
}
//总结:当你使用的方法参数的类型是一个接口的类型,并且该方法内实现了接口的方法,且该接口的方法不超过3个,
//我们可以在调用该方法的地方定义一个匿名内部类实现接口的方法,这样在调用使用方法的地方就可以直接调用接口被匿名内部类实现的方法
}
class InnerTest
{
public static void main(String[] args)
{ //面试题
//如果我们没有一个父类也没有一个接口,但是就是想通过匿名内部类实现一个方法,
//我们可以直接new Object来做匿名内部类,因为所有的类都是Object类的子类
new Object()//{}内是Object的子类部分
{
public void function()
{
}
}.function();
// 创建对象再来调用在Object子类中新创建的function方法是不行的
}
}