---------------------------------------- JavaEE+云物联、期待与您交流!---------------------------------------------
前期准备01-2.2
十三、内部类
将一个类,定义在了另一个类的里面,对里面的那个类,就称为内部类,也可以称为内置类、嵌套类。而被嵌套的那个类,则就被称为外部类。
注意:非被嵌套的外部类,此处定义为其它外部类。
①内部类的意义:
当描述事物时,事物的内部还有事物,该事物就用内部类来描述,因为内部事物在使用外部事物中的内容。
②内部类的访问特点:
❶内部类可以直接访问外部类中的成员,包括私有成员。
内部类之所以可以直接访问外部类中的成员,是因为内部类中持有了一个外部类的引用,格式为:外部类名.this
❷外部类若要访问内部类中的非静态成员,则必须建立内部类的对象。
❸当内部类定义在外部类的成员位置上时,内部类就是外部类的一个成员,可以被成员修饰符所修饰。
private:将内部类在外部类中进行封装。
static:内部类具备了静态的特性。
❹成员内部类的访问方式:
对于在其它外部类中创建非私有成员内部类的对象:
格式:外部类名.内部类名 变量名=new 外部类名().new 内部类名();
对于在其它外部类中访问,静态成员内部类的非静态成员:
格式:new 外部类名.内部类名().内部类的非静态成员
对于在其它外部类中访问,静态成员内部类的静态成员:
格式:外部类名.内部类名.内部类的静态成员
注意:当成员内部类中定义了静态成员时,则内部类必须是静态的。当外部类中的静态方法访问成员内部类时,内部类也必须是静态的。
❺局部内部类:
当内部类定义在局部时,不可以被成员修饰符所修饰;可以直接访问外部类中的成员,因为还持有外部类中的引用;只能够访问它所在的局部的被final修饰的局部变量。
③匿名内部类:
匿名内部类,就是内部类的简写格式。其实,匿名内部类是一个对象。
❶定义匿名内部类的前提:
必须继承了一个类或者实现接口。
❷匿名内部类的格式:
new 父类或接口(){定义子类内容}
创建父类对象,补足子类功能。
匿名内部类其实就是一个匿名的子类对象。多态的一种体现。
❸匿名内部类的弊端:
匿名内部类中定义的方法一般不超过2个,因为匿名内部类是为了简化书写而存在的;一个匿名内部类只能调用一个方法。
❹匿名内部类的应用:
当一个函数的参数列表是一个接口类型,并且该接口中的方法不超过2个时,就可以通过定义匿名内部类,完成向函数传递参数的动作。
十四、异常
问题也是现实生活中的一个具体的事物,也可以通过Java类的形式进行描述,并封装成对象。把问题封装成对象就是异常。
①异常的体系:
Throwable(['θrəuˌeibl])
Java中所有错误和异常的超类。只有当对象是此类或者其子类的实例时,才能够通过Java虚拟机或throw语句抛出,才可以是catch子句中的参数类型。可抛性是Throwable体系独有的特点。
❶Error(['erə])
通常出现重大问题:如,运行的类不存在或者内存溢出等。
一般不编写针对代码对其处理。
❷Exception([ik'sepʃən])
在运行时,运行出现了一些状况,可以通过try、catch、finally处理。
❸Error和Exception的子类名都是以父类名作为后缀的。
②异常的声明方式:
关键字throws:用在函数上,声明方法可能会抛出的异常类型。
❶异常的声明可以提高程序的安全性,对于编译异常,调用者必须要处理。
❷声明异常时,建议声明为更加具体的异常,这样在处理时也会更加有针对性。
❸一个方法可以抛出多个异常类型,用逗号将其隔开。
③处理异常的格式:
❶try{异常代码}catch(异常类型 参数){处理方式}finally{关闭资源}
❷try{异常代码}catch(异常类型 参数){处理方式}
❸try-catch的跳转操作,也可以应用于程序执行时对某些代码的跨越。
④对捕获的异常对象进行处理的简单方法:
❶getMessage方法(['getˌmesidʒ])
public String getMessage()
返回此throwable对象的详细消息字符串。
❷printStackTrace方法([print][stæk][treis])
public void printStackTrace()
将此throwable对象及其被追踪的信息输出至标准错误流。输出堆栈跟踪,包含异常类型、异常信息、异常帧的位置。Java虚拟机默认的处理方式。
❸toString方法
public String toString()
返回此throwable对象的简短描述。
结果为:
此对象的类名:调用此对象getLocalizedMessage()方法的结果
❹getLocalizedMessage方法([get]['ləukə'laizd]['mesidʒ])
public String getLocalizedMessage()
创建此throwable对象的本地化描述。子类可以重写此方法,以便生成特定于其所处语言环境的消息。对于不重写此方法的子类,默认实现返回与getMessage()相同的结果。
❺fillInStackTrace方法([fil][in][stæk][treis])
public Throwable fillInStackTrace()
返回对此Throwable实例的引用(异常帧)。在异常堆栈跟踪中的填充,此方法在Throwable对象的信息中,记录有关当前线程堆栈帧的当前状态。
⑤异常的处理原则:
❶方法中只要发生一个未被处理的异常,方法就会被终止。
❷方法中发生了未被抛出的异常,则应停止运行,方便于对程序的修正。
❸方法中抛出几种异常类型,就处理几种类型的异常,一个catch块对应一个异常类型。
❹处理多个异常类型时,若是异常类型间存在继承关系,则应该将最父类的异常类型,放在最后面处理。否则,其子类异常将没有被执行的机会,并且编译时不会被通过。
❺处理异常时,一定要使用具体的处理方式,如:异常日志。
❻异常是由谁造成的就由谁去处理。
❼在程序的互动环节发生异常时,遵循异常处理的分层原则,处理自己的异常,抛出交互的异常。
⑥自定义异常:
实际的项目中,会出现特有的问题。而这些问题,并未被Java所描述和封装。对于这些特有的问题,可以借鉴Java对问题进行封装的思想,通过对Java异常类的继承,对其进行自定义的封装。
❶自定义异常的方法:
定义一个类,继承Java的一个异常类,描写自定义的处理方式。
之所以要继承Java的异常类,是为了让其具有可抛性。
❷自定义异常的使用:
Java虚拟机,既能识别被自动抛出的异常对象,也能识别被手动抛出的异常对象。对于自定义异常,需要被手动抛出。
❸关键字throw:
用于在函数内部,手动抛出异常的对象。
如果throw语句单独存在,其后面就不能够再定义其它语句,因为必然会发生异常,后面的语句执行不到。而且,编译会失败。类似于continue、break、return,单独存在时,其后面都不能够有任何语句。
❹只要方法中用throw抛出了异常对象,就必须要对异常做出处理动作,要么throws(在方法上声明),要么try(在内部捕捉并处理)。运行异常除外。
一般情况下,只是在函数上声明出来,让该函数的调用者去处理。
❺在自定义的异常类中,可以通过构造函数向父类的构造函数中传值,用以调用父类的方法。当然,也可以定义自己的方法,获取需要的内容。
⑦RuntimeException(['rʌntaim][ik'sepʃən])
当此类及其子类的对象被抛出时,函数上可以不用声明。如果在函数上声明了这种类型的异常,调用者也可以不用处理。
❶此类异常不用声明,是因为不需要调用者去处理。当该类异常发生时,希望程序停止。因为,在程序运行时,出现了让程序无法继续运行的情况,需要在程序停止后,对相应的代码进行修正。
❷在自定义异常时,如果该异常的发生,让程序无法继续运行,就让它继承RuntimeException。
❸异常在编译时的两种处理方式:一是编译时被检测的异常;二是编译时不被检测的异常(RuntimeException及其子类)。
即异常可以被分为:编译时异常和运行时异常。
❹RuntimeException中的几个常见的子类:
ArithmeticException
([ə'riθmətik][ik'sepʃən])
在出现异常的运算条件时抛出。算术异常。
IndexOutOfBoundsException
(['indeks][aut][əv]['baundz][ik'sepʃən])
在某排序索引(例如对数组、字符串或向量的排序)超出范围时抛出。角标越界异常。
NullPointerException
([nʌl]['pɔintə][ik'sepʃən])
当程序试图在需要对象的地方使用null时,抛出此异常。空指针异常。
IllegalArgumentException
([i'li:gl]['a:gjumənt][ik'sepʃən])
在向方法传递了一个不合法或者不正确的参数时抛出。参数异常。
⑧关键字finally
❶和try搭配使用,表示不管try语句是否发生异常,都要执行finally中的代码,一般用于关闭与资源的连接。
❷只有一种情况例外,finally中的代码不被执行,即Java虚拟机被立刻终止(System.exit(0))。
❸try{异常代码}finally{关闭资源}(此处没有catch,没有处理异常)
⑨异常的覆盖
❶如果父类方法中抛出异常,那么子类在覆盖该方法时,只能抛出父类的异常或者该异常的子类。
❷如果父类方法中抛出了多个异常,那么子类在覆盖该方法时,只能抛出父类异常的子集。
❸如果父类方法中没有抛出异常,那么子类在覆盖该方法时,也不可以抛出异常。如果子类的方法中发生了异常,就只能用try-catch处理。
⑩异常的应用
❶在开发过程中,一旦需要描述问题,就用异常的方式进行封装。
❷异常的使用,可以将正常流程代码和问题处理代码相分离,提高程序的阅读性。
十五、包
①关键字package:
用于定义包,一系列相关的类,组成一个包。
②包的意义:
❶包也是一种封装的形式。
❷给类提供了多层的命名空间。
❸方便于对类文件的管理。
❹可以将类文件与源文件相分离。
③包的定义方式:
package 包名;
❶包的定义,一定要放在程序代码的第一行。
❷包名的所有字母都小写。
❸为了避免包名不会重复,可以使用url来定义,因为url是唯一的。一般使用其反序的排列。
url(Uniform Resource Locator)统一资源定位器
(['ju:nifɔ:m][ri'zɔ:s][ləu'keitə])
④包的实现方式:
❶编译Java源文件时,要为包的存放指定位置。
格式为:
javac -d 包的存放目录 源文件名
❷对于包的存放路径,用句点表示当前目录。若是被指定的文件夹不存在,系统会自动新建一个。
❸当定义了包之后,类名的全称是:包名.类名。执行类文件时,要使用类的全称。在使用某一包中的类时,也要使用该类的全称。
❹当定义了包之后,classpath中定义的路径应是包的父目录。
⑤包的访问:
❶跨包访问时,被访问的类,其所有的权限修饰都必须是公有的,包括类自身,也包括类中的成员。
❷关键字protected
受保护的,用于修饰类中的成员变量和成员函数。被protected修饰的成员,可以被本类中的成员访问,可以被同一包内的其它成员访问,可以被其子类的成员访问。
❸访问权限列表
| public | protected | default | private |
同一个类中 | 可以访问 | 可以访问 | 可以访问 | 可以访问 |
同一个包中 | 可以访问 | 可以访问 | 可以访问 |
|
子类 | 可以访问 | 可以访问 |
|
|
不同包中 | 可以访问 |
|
|
|
❹关键字import
导入类。
Java中使用import导入类,有两种方式:一是单类型导入,好处是编译速度快,可以避免命名冲突,缺点是导入语句可能会很长;二是按需类型导入(使用通配符*),这种形式会增加Java代码的编译时间,而且有可能会造成命名冲突。
开发时,一般使用单类型导入,开发工具会有相关的技术支持。
十六、多线程
①进程
❶进程的概念
进程是操作系统结构的基础;是一次程序的执行;是一个程序及其数据,在处理机上顺序执行时所发生的活动;是程序在一个数据集合上运行的过程;它是系统进行资源分配和调度的一个独立单位。
❷进程的特点
从概念上来说,有两点:一是,进程是一个实体(包括文本区、数据区、堆栈);二是,进程是一个正在执行中的程序。
从结构上来说,有三点:包含程序、数据、进程控制块。
从执行上来说,有四点:动态性、并发性、独立性、异步性。
②线程
❶线程的概念
线程是进程中的某一个单一顺序的控制流;是进程中程序的最基本的调度单位;是程序执行流的最小单元。也被称为轻量进程。线程存在于进程之中,依附于进程而存在。
❷多线程
当一个进程中,存在多个执行路径(控制单元)时,即可以称为多线程。
❸线程的特点
必然性:一个进程至少包含一个线程
独立性:是一个可以独立执行的控制单元
并发性:多个线程之间可以并发执行
动态性:多个线程并发执行,必然使得线程在执行时会有短暂的停顿
异步性:多个线程并发执行,必然使得线程在执行时会有不和谐的时候
共享性:多个线程同时存在时,共享该进程的所有资源
关联性:一个线程可以创建和撤销本进程的另一个线程
中断性:多个线程并发运行时,某些线程可以在执行过程中暂停或等待
③进程与线程的关系
❶进程是资源分配的基本单位
❷线程是程序执行的基本单位
❸线程依附于进程存在,进程中至少有一个线程,即其自身
④多线程的意义
多线程的出现,提高了程序的执行效率。
多线程提高的并不是CPU的运行效率,而是通过提高对资源的使用效率,来提升系统的性能,进而提高了程序的执行效率。
⑤JVM中的主线程
JVM启动时,会有一个进程,即java.exe。该进程中至少有一个线程负责Java程序的执行,而且这个线程运行的代码存放于main方法中,该线程称之为主线程。
JVM是一个多线程,因为在主线程启动时,同时还有可能会有垃圾回收机制的线程也在被执行。
⑥在程序中自定义线程
创建新的执行线程有两种方法:
❶Thread([θred])
将类声明为Thread的子类,然后在子类中覆盖Thread类的run方法。public void run(){}
创建了子类的对象之后,就已经创建了一个线程,只是还没有被启动。
通过调用start方法(对象名.start()),来启动该线程;并且,JVM会自动调用该对象的run方法。
之所以在子类中一定要覆盖Thread类的run方法,是因为Thread类是用于描述线程的,其内部就定义了一个功能,用于存放线程要运行的代码,该存储功能就是run方法。每一个线程都应该拥有自己独特的执行代码。
❷调用方法start和run的区别:
如果直接用子类对象调用run方法,则仅仅是一个对象在调用自己的方法,此时的执行者是主线程,该对象的线程虽然被创建了,但是没有被启动。
如果调用start方法,就可以启动该线程,同时,run方法也会被JVM自动调用并执行。
❸Runnable(['rʌnəbl])
将类声明为实现Runnable接口的类,然后在该类中实现run方法。
创建该类的对象,并将它当做参数传递给Thread对象(Thread对象被创建后,就有了新的线程)的构造函数,再由Thread对象去调用其start方法,实现新线程的创建并被运行。这时Thread对象执行的run方法,是实现了Runnable接口的类的run方法。
Runnable为非Thread子类的类提供了一种激活方式。
❹Thread和Runnable区别:
线程代码存放的位置不同。
Runnable避免了Java中的单继承的局限性。
建议使用实现Runnable接口的方式,创建新的线程。
⑦线程的五种状态
❶被创建,线程存在,没有执行资格,没有执行权
❷运行,有执行资格,有执行权
❸阻塞状态(临时状态),有执行资格,没有执行权
❹冻结(包括睡眠和等待),没有执行资格,没有执行权
❺消亡,进程结束
线程的五种状态的关系,见下图:
⑧自定义线程的名称
❶系统有默认名称,从Thread-0开始,编号为int型。
❷可以通过构造函数,自定义名称,需要调用父类相应的构造函数。也可以通过setName()来设置线程的名称。
❸Thread中的方法
static Thread currentThread()
(['stætik][θred]['kʌrənt][θred])
获取当前线程的对象,通用格式,应用环境大于this。
public final void setName(String name):
改变线程名称,使之与参数name相同。
public final String getName():
获取线程名称。
public static void sleep(long millis,int nanos) (['milis]['nænəuz])
throws InterruptedException ([intə'rʌptid][ik'sepʃn])
在指定毫秒数加指定的纳秒数这段时间,让当前正在执行的线程休眠(暂停执行)。该线程不会丢失任何监视器的所属权。可以只传入一个毫秒值。
⑨同步
为了解决多线程在并发执行时,程序的安全问题。
❶多线程的安全问题
当一个进程的共享数据被多条语句操作时,某个线程对多条语句只执行了一部分,还没有执行完,其它线程就参与进来执行,导致了共享数据被执行了错误的操作。
❷对于多线程安全问题的解决方式
对于多条的操作共享数据的语句,只能让一个线程都执行完。在其执行过程中,其它线程不可以参与执行。
❸关键字synchronized
同步,表示在同一时间只能由一个线程访问的代码块或者函数。同步函数使用的锁对象有两个,静态函数使用的是类名.class对象,非静态函数使用的是this对象。
格式:synchronized(对象){需要同步的代码;}
❹同步的前提:
必须要有两个或两个以上的线程在操作共享数据;必须是所有参与同步的线程使用同一把锁。
❺同步的好处
解决了多线程的安全问题。
❻同步的弊端
多个线程判断锁的动作,会消耗系统的资源。
❼同步的两种方式
同步代码块,将需要同步的代码封装起来。
同步函数,将需要同步的函数用synchronized修饰。
❽判断如何实现同步的方法
明确哪些代码是多线程运行代码。
明确哪些是共享数据。
明确多线程运行代码中哪些语句是操作共享数据的。
❾多线程中的单例设计模式
在多线程中,懒汉式容易出现异常。可以用同步来解决此问题,同步函数比较低效,每一个线程在使用该对象时都要判断一次锁;用同步代码块配合if语句的双重判断,可以稍微提高一下程序的运行效率。
采用单例设计模式时,建议使用饿汉式。
❿死锁
同步中嵌套同步,而锁却不同,容易发生死锁现象。
⑩多线程间通信
❶多线程间通信要解决的问题
当多个线程对共享数据做不同的操作时,即便添加了同步也依然会出现问题,每个线程会各做各的。这时就需要各个线程间有信息的互动,才能让程序正常运行。
❷等待唤醒机制
多线程间通信的一种方式。
设置一个标志位,各个线程操作共享数据时,同时操作标志位。通过对标志位的判断,以wait、notify的方法来实现CPU执行权的转移,完成通信的动作。
❸线程池
线程池中的线程都是后台线程。处于等待状态的线程都被放置在线程池中。notify唤醒的通常是第一个被等待的线程,当然这是不确定的。
❹wait()、notify()、notifyAll() ([weit]['nəutifai]['nəutifai][ɔl])
此三个方法都只能使用在同步中。因为要对持有监视器(同步锁)的线程进行操作,而只有同步中才有锁,所以要使用在同步中。
调用方式为:锁对象.wait()、锁对象.notify()、锁对象.notifyAll()。
之所以要把这些操作线程的方法定义在Object类中,是因为锁可以是任意对象。
这些方法在操作同步中的线程时,都必须要标识它们所操作的线程持有的锁,只有在同一个锁上的被等待线程,才可以被同一个锁上的notify唤醒,不可以对不同锁上的线程进行唤醒。即,被等待和唤醒的进程,必须持有同一个锁。
❺notify和notifyAll
if和notify搭配使用,适用于只有两个线程,对共享资源进行各自不同的操作。
while和notifyAll搭配使用,适用于多个线程对共享资源进行不同的操作。唤醒所有线程,循环判断标记。
❻lock([lɔk]锁)(JDK1.5之后)
public interface Lock
一个接口。
Lock实现,提供了比使用synchronized方法和语句,可获得的更广泛的锁定操作。此实现允许更灵活的结构,可以具有差别很大的属性,可以支持多个相关的condition([kən'diʃn]条件)对象。
Lock替代了synchronized方法和语句的使用,将之前的隐式调用,变为显示调用。
❼ReentrantLock([ri:'entrənt][lɔk]可重入锁)
实现了Lock接口的类。通过创建它的对象,可以使用Lock接口的所有功能。
public void lock():获取锁。
public void unlock():释放锁。
public Condition newCondition():返回与此Lock实例一起使用的Condition实例。即返回一个锁对象。一个Lock实例可以捆绑多个锁对象,方便区别唤醒。
❽Condition([kən'diʃn]条件)
一个接口。
Condition将Object监视器(同步锁)方法(wait、notify、notifyAll)分解成截然不同的对象,以便通过将这些对象与任意Lock实现组合使用,为每个对象提供多个等待设置(wait-set)。
Lock替代了synchronized方法和语句的使用,Condition替代了Object监视器(同步锁)方法的使用。
public void await() throws InterruptedException:
([ə'weit]等待[θrəuz][intə'rʌptid]中断[ik'sepʃn])
使得当前线程在接到信号或被中断之前一直处于等待状态,调用者需要做异常处理。try-finally,将释放锁作为必须执行的语句。
public void signal()(['signl]信号):唤醒一个等待线程。如果所有的线程都在等待此条件,则选择其中的一个唤醒,通常是第一个进入等待状态的线程。
public void signalAll()([vɔid]['signl][ɔl]):唤醒所有等待线程。如果所有的线程都在等待此条件,则唤醒所有线程。
⑪停止线程
❶stop方法已经过时。
❷将run方法结束。
开启多线程运行,所运行的代码通常都是循环结构。只要控制住循环,就可以让run方法结束,也就是让线程结束。
可以在run方法中设置一个标记,当标记为true时,线程继续运行;当标记为false时,线程结束。
如果线程处于冻结状态,就不会读到标记,那么线程就不会结束。这时,就可以使用该线程的interrupt方法,解除此线程的冻结状态,并修改标记位,让该线程结束。
❸interrupt(Thread中的方法)
public void interrupt()
(['pʌblik][vɔid][intə'rʌpt])
中断线程。清除线程的冻结状态。
当线程在调用Object类的wait方法或Thread类的join、sleep方法过程中受阻,则其中断状态(冻结状态)将被清除,并且同时收到一个InterruptedException([intə'rʌptid][ik'sepʃn])。
当没有指定的方式让冻结的线程恢复到运行状态时,就需要对冻结状态进行清除,强制让线程恢复到运行状态中来。这时被中断的程序会抛出中断异常,就需要捕捉并处理此异常。在处理异常的同时,可以通过修改标记,让线程的运行条件变为false,从而中断该线程。
⑫Thread类的其它常用方法
❶public final voidsetDaemon(boolean on)
(['pʌblik]['fainl][vɔid][set]['di:mən]['buliən][ɔn])
将该线程标记为守护线程(后台线程)或用户线程。当正在运行的线程都是守护线程(后台线程)时,Java虚拟机退出。
该方法必须在启动线程前调用。on为true时,标记为守护线程。
后台线程的特点:当前台线程在运行时,与前台线程并发运行;当前台线程结束时,后台线程也结束。后台线程依赖于前台线程,就像是在守护前台线程一样。
❷public final void join()throws InterruptedException
(['pʌblik]['fainl][vɔid][dʒɔin]加入[θrəuz][intə'rʌptid][ik'sepʃn])
等待该线程终止。当前线程等待该方法调用者的运行结束。
当A线程执行到了B线程.join()方法时,A就会进入等待状态,等B线程都执行完,A才会执行。也就是说,A线程会一直等待B线程的运行结束,才能够获取执行资格。B线程可以与非A的线程并发执行。
此方法可以用于临时加入线程执行。
当A线程需要用到B线程的运算结果,才可以继续运行时,就可以让A线程调用B.join(),一直等待B线程的运行结束。
如果任何线程中断了处于等待状态的线程,处于等待状态的线程就会抛出中断异常,同时该线程的中断状态将被清除,并再次获得执行资格。
❸public String toString()
返回该线程的字符串表示形式,包括线程的名称、优先级和所属的线程组。
❹public final voidsetPriority(int newPriority)
(['pʌblik]['fainl][vɔid][set][prai'ɔriti])
更改线程的优先级。(1~10,1最低,10最高)
默认情况下,线程的优先级都是5。
MIN_PRIORITY:线程可以具有最低优先级[min][prai'ɔriti]
NORM_PRIORITY:分配给线程的默认优先级[nɔ:m][prai'ɔriti]
MAX_PRIORITY:线程可以具有最高的优先级[mæks][prai'ɔriti]
线程的优先级越大,获取执行资格的几率就越高。
❺public static void yield()
(['pʌblik]['stætik][vɔid][ji:ld])
暂停当前正在被执行的线程对象,然后让当前线程同其他线程,再次一起抢夺执行权。此方法能够让每个线程都可以比较均衡的获取到执行权。
⑬多线程的设计
❶当有某些程序需要被同时执行时,就用单独的线程进行封装。
❷使用Thread定义匿名内部类,可以直接调用start运行一个新的线程。
❸使用Runnable创建一个内部类,并new一个对象r,再用Thread创建一个匿名对象并传入r,然后调用其start方法,运行一个新的线程。
---------------------------------------- JavaEE+云物联、期待与您交流!---------------------------------------------