美文网首页
Java基础day09笔记:内部类访问规则|静态内部类|内部类定

Java基础day09笔记:内部类访问规则|静态内部类|内部类定

作者: 楠楠喜欢泡枸杞 | 来源:发表于2018-12-08 20:25 被阅读0次

        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对象。

            对了,一定要注意:

            后面带分号:

    Object对象

            后面是大括号:

    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(),也不要简单的就书写一条输出语句。(刚刚那样写只是在演示,正式编写中不是这样简单处理的哦)因为打印它没有意义,用户看到了又能怎样呢?真正发生问题之后,一般会用一个文档把这些异常信息记录下来,记录完之后,我们把它称为异常日志文件。这样,在我们运行过程中,每天都会产生这些文件,记录了我们程序每天的运行状况:在什么时候几点几分,发生了一个什么问题。管理人员/网站的维护人员,就会经常去看这个日志,看完之后就会知道什么时候在代码的什么位置发生了什么问题,然后去把它搞定一下,看看为什么会产生这个问题,这个问题是不是大问题。所以,用文件记录下来,这是靠谱的。

       

    相关文章

      网友评论

          本文标题:Java基础day09笔记:内部类访问规则|静态内部类|内部类定

          本文链接:https://www.haomeiwen.com/subject/hbgncqtx.html