01-面向对象(内部类访问规则)
内部类:
从名称上直观的来分析, 这个类是已经定义到另外的类的里边啦。
所以,将一个类定义在另一个类的里面,对立面那个类就称为内部类(又叫内置类,嵌套类)。
比如:
访问特点:
内部类可以直接访问外部类中的成员,包括私有成员。
而外部类要访问内部类中的成员必须要建立内部类的对象。
这就相当于孙悟空要找到牛魔王的心脏,就要先找到牛魔王再找到它的心脏,就是牛魔王.get心脏()方法。但是孙悟空如果跑到牛魔王肚子里面去了,他访问牛魔王的心脏就可以直接访问啦。
我们可以直接访问内部类中的成员吗?
可是如果还有一个类叫Outer2,里面也有个内部类叫Inner,怎么区分是哪个类里的内部类呢?
所以一定要指明是哪个类里的Inner()。
右边格式也要改一改呢,要写成酱紫:
但是这个很少被用到,因为内部类可能会被私有,就无法直接访问了。这个只是一种格式,应该只有面试的时候可能会用到。
类可以被私有吗?
内部类可以被私有。当一个类是另一个类的内部类时,可以被私有。
但是一般的类绝对不可以私有。
为什么内部类可以直接访问到外部类的内容?
一个例子。
如果想打印x=6,则:
如果想打印x=4,则:
如果想打印x=3,则:
当然,如果内部类里面没有另外定义x=4和x=6,那么打印x就默认打印的是外部类的x=3。这个时候x前面有隐式的Outer.this。
之所以内部类可以直接访问外部类中的成员,是因为内部类中持有了一个外部类的引用,格式 外部类名.this。
02-面向对象(静态内部类)
访问格式:
1,当内部类定义在外部类的成员位置上,而且非私有,可以在外部其他类中,直接建立内部类对象。
格式:
外部类名.内部类名 变量名=外部类对象.内部类对象;
即 Outer.Inner in=new Outer().new Inner();
2,当内部类在外部类的成员位置上,就可以被成员修饰符所修饰。
比如,private:将内部类在外部类中进行封装。
static:内部类就具备static的特性。当内部类被静态修饰后,只能直接访问外部类中
的static成员,出现了访问局限。
在外部其他类中,如何直接访问static内部类的非静态成员呢?
这样即可:
如果fuction()也静态了呢?
这样即可:
但是使用频率很低。
注意:当内部类中定义了静态成员,该内部类必须是静态的。否则会报错。
这样才对:
再一个例子:
改成酱紫就OK啦:
注意:当外部类中的静态方法访问内部类时,内部类也必须是静态的。
03-面向对象(内部类定义原则)
当描述事物时,事物的内部还有事物,该事物用内部类来描述。
因为内部事务在使用外部事物的内容。
比如心脏之于人体。
我们的心脏不可以直接被其他人访问到,所以要私有。
当一个类需要直接访问到另外一个类中的成员的时候,就把另外一个类写到这个类的里面。写完之后,争取把这个类封装在外部类中,不被外暴露,而是对外提供一个方法进行访问。
04-面向对象(匿名内部类)
我们发现,刚才写的内部类,定义在外部类的成员位置上,只有定义在成员位置上的内部类,才可以被私有或静态修饰。一般内部类是不会被公有修饰的。有没有这种情况呢?有。比较特殊的情况会出现。这个就不说啦。
除了定义在外部类的成员位置上,内部类还可以写在其他位置上呢,比如写在外部类的方法当中:
这个类就是局部的。但是访问规则没有变,它还是可以直接访问外部类中的成员。
那它还能被静态所修饰吗?不能。因为静态修饰符只修饰成员,现在它是局部的,所以不能被修饰了。那它还能定义静态的成员吗?不能。因为内部类中若有静态成员,这个内部类也必须是静态的,所以它也不可以定义静态的成员。
现在调用这个内部类中的function,因为必须要有这个内部类的对象才可以顶用它,所以要这样写:
可以看到运行是没有问题的。
这是内部类定义在局部位置上的一个小特点。
为什么不把定义内部类对象放在前面呢?会报错的:
因为如果放在前面,类定义又在后面,定义内部类对象的时候,还没有读到这个类的定义呢。所以内部类对象的定义要放在内部类的定义的后面。
那么内部类是否可以访问它所在的方法中定义的变量y呢?会报错:
这样就OK啦:
内部类定义在局部时:
1,不可以被成员修饰符修饰。
2,可以直接访问外部类中的成员,因为还持有外部类中的引用。
但是不可以访问它所在的局部中的变量,只能访问被final修饰的局部变量。
这个a可以被打印吗?
不可以喔。
为什么呢?
因为a是一个变量,需要被final修饰才能访问。
像酱紫:
这时候就郁闷了,a不就变成常量了嘛?那底下不是又给a赋值7,这不是很矛盾吗?
这其实没问题,把7赋值给a,这之后a就被锁住了。看,运行没有问题的:
试试再给它赋值为8:
或者这个形式:
不过,对于给a赋值来说,这两种方法一点区别都没有。
因为a并不是在对象中。它是标准的局部变量。这两种方法如果是给x赋值,就会有区别的。
好,其实不管哪种方法,这样都是可以编译通过的,为什么呢?a不是常量吗?
分析一波:调用method()方法,它就进栈内存了, 栈里也有a,把7赋值给a,锁住了,a就一直为7。直到out.method(7)这句话执行完了,就要出栈了,释放了。下一句话,再调用method(),又进栈了,就是新的a了,这时把8赋值给a,a锁住了。所以这样是OK的。
那什么情况是不OK的呢?
酱紫就不阔以哦:
匿名内部类:
1,匿名内部类其实就是内部类的简写格式。
2,定义匿名内部类的前提:内部类必须是继承一个类或者实现接口。
比如每个人都是有心脏的,在外部定义一个抽象的心脏,所有人的内部的心脏类都继承它,继承它里面的方法。
这是一个常规的内部类:
我们现在要把它简化成一个匿名内部类。怎么简化呢?
绿色的部分,其实就是我们需要简化的部分。
简化之前我们先分析一下,内部类都做了什么事情呢?
1,它继承了一个类;2,复写父类的方法;3,创建对象;4,调用。
这个内部类本来的名字叫Inner,现在没这个名字了,我们怎么调用它呢?
酱紫来:
因为是写匿名,所以这个时候Inner没有了,我们把它改成父类的名字:
这个时候需要覆盖父类的方法,继续:
这样就OK啦。这就是一个匿名内部类,也是一个带着内容的对象,一个很胖哒对象!
记住:这个整体是一个对象!是一个什么对象呢?是AbsDemo的子类对象,因为只有子类才能复写AbsDemo中的抽象方法。这个整体就是绿色部分的简化写法~
这种写法很简化,但是可能不是很好理解。但是开发中这种写法非常常见。
所以接着上面,匿名内部类:
3,匿名内部类的格式: new 父类或者接口(){定义子类的内容}
4,其实匿名内部类就是一个匿名子类对象。而且这个对象有点胖。可以理解为带内容的对象。
也可以在里面写其它函数并调用哦:
如果又想调用abc(),又想调用show()呢?酱紫:
但是这样太麻烦了,能不能起个名字呢?但是不是匿名类吗?不是不可以起名字吗?但是可以这样:
这其实就是多态中的父类调用子类对象。但是第二个abc就不可以被父类调用,因为abc是子类的方法,父类中没有定义。所以这样只能调用父类方法,不能调用子类方法,感觉意义也不大。我们写匿名类就是为了简化书写,这样一来感觉更麻烦了。
匿名内部类是有局限性的。它的出现就是为了简化书写,所以这样一来有好处也有弊端,弊端就是直接调用自己的方法还不行(就是上面那种情况),还有一个弊端,就是父类里面的方法过多的话就不要定义匿名内部类了,比如:
匿名内部类里面就是这样的:
这样如果你想调用的话,就会非常麻烦,里面那么多方法,阅读性已经没有了,如果每个方法里面的代码再多一点,这就是一屏(甚至好几屏)的代码,我们都没眼看了!
所以,一般写匿名内部类,它里面的方法不会超过三个,大多两个或一个,这样匿名对象调用方法就会方便很多。
所以,再接前面的,匿名内部类:
5,匿名内部类中定义的方法最好不要超过3个。
那如果没办法,父类里的方法就是那么多怎么办?那就按常规的内部类来咯,定义一个内部类,继承它,后面可以建立这个类的内部对象慢慢一个个调用~
匿名内部类就是在方法少的情况下,可以简化书写、方便调用,里面写得太复杂就失去意义了。
下面来一个小练习:
分析一下这句话:
Test类中肯定有静态成员,而且这个静态成员方法的名字叫function。
所以,可以确定先写成这样:
继续分析这句话:
调用完function之后紧接着又调用了method方法,从前面Inter接口的内容可以看出,method是非静态的(因为它是抽象的所以一定是非静态的)。由此可以推出,Test调用了function的运算结果是一个对象。因为只有返回了对象,对象才能调用method方法。
我们想想哪个对象可以调用method?必然是Inter的对象。所以返回值类型也确定了,是Inter:
我们先用内部类来写一遍,运行是没有问题的:
接下来用匿名内部类来写:
总结回顾一下:
有一种需要用匿名内部类的情况很常见,说一下:
现在的想法是,调用show(),这个括号里面是不是要传进来一个接口类型的对象。
我们可以怎么做呢?
分解做的话就是,先搞一个类,去把这个接口实现一下,这个类定义在内部也行,定义在外部也行,定义完之后,把这个类的对象作为参数传给show方法。但是这么做很麻烦。
当使用的对象是接口类型时,查看一下这个接口里的方法,如果不超过三个,我们可以定义一个匿名内部类,把匿名内部类作为参数传进去。
酱紫:
万一没有父类,也没有接口,就是想调用一个function方法,还可以写匿名内部类吗?
厉害的来啦,我们可以new一个Object对象。
对了,一定要注意:
后面带分号:
后面是大括号:
好,这样就OK啦:
然后我们还想加一个函数,这个时候就需要给它起名字:
注意这样是不可以的哦,这时一个错误示例,以为Object类里面并没有定义这个方法,所以不可以这样调用。
05-面向对象(异常概述)
什么叫异常呢?就是我们所谓的不正常。
写了一个除法的小程序:
但有时候会传不合法的值进去,我们会发现,编译的时候没有问题,但是运行的时候出现了问题,程序不正常结束了:
这就是我们所说的异常。
异常:就是程序在运行时出现的不正常情况。
异常由来:问题也是现实生活中一个具体的事物,也可以通过java的类的形式进行描述,并封装成对象。
其实就是java对不正常情况进行描述后的对象体现。
对于问题的划分,它分成两种:一种是严重的问题,一种是非严重的问题。
对于严重的,java通过Error类进行描述。
对于Error,一般不编写针对性的代码对其进行处理。(就好像得了癌症一样,只是进行保守治疗,没有什么针对性的治疗方式可以药到病除了)
对于非严重的,java通过Exception类进行描述。
对于Exception可以使用针对性的处理方式进行处理。(对于感冒发烧,可以喝感冒药治好)
所以我们重点讲能处理的部分,也就是Exception。
一个内存溢出的例子:
虚拟机本身有自己启动的默认空间,任何一个软件启动都会划分到这个默认空间来存放数据,但是虚拟机它的空间是有限的,就这么大,你非要开一个超过它的大小,就挂掉了。道理很简单,超出虚拟机的范围。
虚拟机在启动的时候,可以通过它提供的参数,来将它改变成更大的空间。但是你分配多大都没用,如果数组这个空间,已经超出物理内存空间的话,再改变也没戏。就像这个问题,怎么处理也没戏。
那Error和Exception有没有共性呢?
有的。
拿感冒发烧来举例,这两个疾病都有它的名称,都有发病原因。
无论Error或者Exception都具有一些共性内容。
比如:不正常情况的信息,引发原因等。
那么它们有了共性会向上抽取,抽取完了之后就会形成一个基本的体系,抽取出来的这个父类就叫做Throwable,Throwable下面就两个子类,一个叫Error,一个叫Exception,而这两个类的下面会有很多的子类出现。(就像感冒发烧一样,也会划分为不同类型的感冒)
我们来看一下Java的API文档,看看它是怎么对这个体系进行介绍的。
在java.lang中,有一个Throwable类。Throw是抛的意思,加上able就是可抛的意思。
Throwable是Java中所有异常的父类(超类)。
在这个类中,就定义了这个体系的共性内容。那么要使用一个体系,是不是就要看这个体系的父类父类的定义,建立子类的对象。
来看一下这个类的定义。它继承了Object,实现了Serializable。它有两个直接子类,而我们要讲的就是Exception。
先看Error:
Error的小弟好多哦!而且我们发现,它的小弟名字最后都有Error。所以,Java在命名的时候,会把父类名称放在子类名字的最后,这个也是我们以后开发中可以借鉴的呢!而且这样命名,谁是它的父类,它是谁的子类也一目了然辣!
我们再看下Exception:
哇!它的小弟更多了耶。我们要把它们都学完吗?
试着点进其中一个子类,发现:
小弟竟然还有小弟,呜呜,学不完这么多呀。
所以说我们把根本性的东西搞定就行了。
Java把一些常见的问题都进行描述,并封装成异常了。
这就是异常的基本的体系。
06-面向对象(异常try-catch)
接下来我们的程序已经发生异常啦!我们是不是可以用抛出异常来处理呢?
就因为被除数为0,所以抛出了异常,我们需要处理一下。为什么呢?因为就因为这个小问题,导致后面的都不能运行了,哼!怎么办呢?
在解决之前我们先了解一下这个抛出异常的过程。
Java虚拟机,它认识这个叫ArithmeticException的异常,因为这是它定义的。引发了它所熟悉的状况,它就给你处理了。那是因为,Java虚拟机内部,有一个内置的异常处理机制。它把它熟悉的异常就给搞定了,但是搞定的方式很简单,就是只要程序出现问题,就不再往下运行。但是我们希望的是,把问题处理掉。
这时候就涉及到第二部分,异常的处理。
对于异常的处理,java为我们提供了特有的语句进行处理。
try
{
需要被检测的代码;
}
catch(异常类 变量)
{
处理异常的代码;(处理方式)
}
finally
{
一定会执行的语句;
}
现在我们改写一下刚刚的代码,over就被打印啦。
我们来说一下这个异常:
有一个小问题,catch中,处理这个问题并没有用到传进来的对象e。
想用呀?没问题~
catch接收到异常后,相当于这个~
这是不是多态呀?父类的引用指向子类的对象。
想操作这个e该怎么办呢?当然是找方法。而异常对象的共性方法定义在Throwable当中。我们去Throwable当中看一下,都有什么方法呢?
心好痛啊
不管怎样,我都会好好努力的。
我们发现,这里有个getMessage方法,可以获取信息,它返回的是一个字符串。
对捕获到的异常对象进行常见方法操作:
String getMessage();//获取异常的信息
试一下:
还有一个方法也可以:
试一下:
这个打印的就更全面啦,既有异常的信息,又有异常的名字。
String toString();//打印:异常名称:异常信息
还有这个方法:
打印堆栈中的跟踪信息。注意,这个方法没有返回值,所以不要放到输出语句当中输出,它本身自己就可以打印了。
试试:
它是不是hin全面呀?既有异常名称、异常信息,同时又有异常的位置。
void printStackTrace();//打印:异常名称,异常信息,异常出现的位置。
其实jvm默认的异常处理机制,就是在调用printStackTrace方法,打印异常的堆栈的跟踪信息。
07-面向对象(异常声明throws)
接下来问题来了,这个功能是别人编写的,你在使用的时候,一定要做try处理吗?不一定。为什么呢?因为你根本不知道这个功能会不会发生问题。所以,这个问题你可处理可不处理,这样会导致一个现象,你在传参数的时候这个程序可能会停掉。
所以这样开发是不OK的,怎么做是OK的呢?
作为编写Demo的人,在编写了除法方法的时候,就要考虑到有可能会出现传进来的被除数为0的情况,这个时候就要在方法上做一个标识,告诉调用这个方法的人,“这个方法可能会出现问题哦”。这个标识就是throws。
标识完会出现什么现象呢?
我们试一下:
对方声明了一个功能:这有可能发生问题。而这个问题,它又解决不了,它就把问题告诉给调用者。
而这个时候,作为调用者,对方已经告诉你有可能发生问题,这个时候你需要做的就是:处理。
如果你不处理,就会编译失败,不处理它就不让你用哦。它也是为了提高安全性。
那该怎么处理呢?
我们再来读一下这个报错:
我们处理问题的方式有两种,要么捕捉它,要么抛出去。
处理方式1:
我也不管!把它抛出去!
这是抛给谁了呢?抛给了虚拟机。结果是这样的:
所以问题还是没有解决。
一般情况下,我们不抛,而是捕捉一下。(后面还会讲什么时候抛,什么时候try,这是有区别的。)
酱紫来写,运行一下,OK的喔:
来一个传了错值的:
用一个例子比较一下抛出和捕捉这两种方式的不同:
有一个面包店,它家有一块面包放了三天都没有卖出去,这个时候这块面包就有可能会坏掉了。所以良心的店主给上面贴了个标签:放置三天后可能会坏。然后将它降价处理,从3块钱降到3毛钱。有个人就把它买走啦。
抛出:回家之后一想,这个有可能坏掉,我也不知道该怎么解决,就把它给别人吧,有可能这个人也会给别人,但总会有个终点,就相当于虚拟机。都别吃啦!
捕捉:把它放在微波炉里加热一下(catch),然后吃掉(最后正常执行的println(“over”);)。
有点不太恰当~
08-面向对象(多异常处理)
我们在定义功能的时候,有可能会发生不止一个问题,我们在函数上声明异常的时候,声明的也就不只一个了。像刚刚声明的异常throws Exception,这个方式其实不是很好。因为它太广义啦,这里的问题是除法运算中可能会出现被除数为0的情况,如果声明得再具体一些,我们就能处理得更具体了。
这个才是我们一般处理的方法。
声明异常时,建议声明更为具体的异常,这样处理的可以更具体。
比如在刚刚那个例子中,我们就可以抛出算数异常:
再多写一些语句,来看一下多种异常的情况:
调用时的处理:
注意声明了几个异常,调用的时候就要写几个catch语句哦。
没有出现异常的情况:
出现算数异常的情况:
出现数组角标异常的情况:
那会不会两种异常同时发生呢?不会。因为当出现一个异常之后,这个异常就被抛出了,后面的语句就不会执行了哦。
我们不写这么多catch,也一个catch可以吗?
这样来写:
它存在的原因是多态性。不管抛什么异常它都可以处理。但是它处理没有针对性哦。
所以最好写上有针对性的处理,每种异常要有与之匹配的catch。所以一般声明了几个抛出异常,调用时就应该写几个catch。
这时候又有个问题,调用者担心万一发生的问题是那两个之外的,又写了一个catch语句:
这样写也不是不可以,但是它有一个问题,就是处理的不具体。也就是说,你都不知道具体发生了,就把这个问题干掉了,相当于把这个问题隐藏了。而程序还在继续运行,也就是说,程序不知道发生什么事情了,继续在运行当中。为了安全性考虑,如果这个时候发生了对方指定的问题以外的问题,这个时候最好的处理方式就是:程序停掉。因为我们得知道,到底哪里卡住了,哪里出问题了,哪里是需要修正的。不能莫名其妙的把问题处理掉,发生了什么都不清楚。
所以这个时候依旧写两个catch,当发生第三种问题的时候,我们就是要让程序停下来,来寻找这第三个问题到底是什么,把它标识出来,声明出来,调用的时候也可以提出具体的更有针对性的解决方法。
注意,还有一种情况:
运行之后会这样:
第一个catch是大哥,后面两个是小弟,发生什么问题,大哥都能处理。因为catch是按顺序执行,而第一个catch都能处理,所以后面两个catch的存在,跟废话一样,永远执行不到。所以就报错了。
综上:
对方声明几个异常,就对应有几个catch块。不要定义多余的catch块。
如果多个catch块中的异常出现继承关系,父类异常catch块放在最下面。
建议在进行catch处理时,catch中一定要定义具体的处理方式。
不要简单定义一句e.printStackTrace(),也不要简单的就书写一条输出语句。(刚刚那样写只是在演示,正式编写中不是这样简单处理的哦)因为打印它没有意义,用户看到了又能怎样呢?真正发生问题之后,一般会用一个文档把这些异常信息记录下来,记录完之后,我们把它称为异常日志文件。这样,在我们运行过程中,每天都会产生这些文件,记录了我们程序每天的运行状况:在什么时候几点几分,发生了一个什么问题。管理人员/网站的维护人员,就会经常去看这个日志,看完之后就会知道什么时候在代码的什么位置发生了什么问题,然后去把它搞定一下,看看为什么会产生这个问题,这个问题是不是大问题。所以,用文件记录下来,这是靠谱的。