Java程序设计进阶

Java异常处理机制

异常

异常的最高父类是 Throwable,在 java.lang 包下。

Throwable 类的方法主要有:

方法 说明
public String getMessage() 返回对象的错误信息
public void printStackTrace() 输出对象的跟踪信息到标准错误输出流
public void printStackTrace(PrintStream s) 输出对象的跟踪信息到输出流 s
public String toString() 返回对象的简短描述信息

Throwable 类的子类有 Error 错误和 Exception 违例。

异常的捕获处理

       当 Java 运行时系统接收到异常对象时,会寻找能处理这一异常的代码并把当前异常对象交给其处理,这一过程称为捕获异常

try-catch

try 语句块:

  • 将可能出现异常的代码都放在 try 代码块中。
  • try 语句块中发现异常,剩下的语句将不再执行。

catch 语句块:

  • 在 catch 语句块中是对异常对象进行处理的代码。
  • 每个 try 语句块可以伴随一个或多个 catch 语句,用于处理可能产生的不同类型的异常对象。
  • 通过 getMessage( ) 获取异常信息或 printStackTrace( ) 跟踪异常信息。

finally

        finally 关键字主要是和捕获异常的 try-catch 语句一起使用,放在 finally 语句块中的内容表示无条件执行的部分,也就是说不管程序有异常或没有异常都需要执行的部分。

企业面试时,经常会提到的问题:final、finally 和 finalize 的区别是什么?

  • final 关键字,是用来修饰属性、方法、类的。
  • finally 关键字,可以配合异常处理,进行无条件执行操作。
  • finalize 不是关键字,是 Object 类中的一个方法,是 Java 垃圾回收机制中进行资源释放的方法。

异常的抛出处理

        Java 程序的执行过程中如出现异常,会自动生成一个异常类对象,该异常对象将被提交给 Java 运行时系统,这个过程称为抛出异常

        throws 抛出的异常可以是 0 或多个,也就是说声明方法时可以不抛出异常,也可以抛出 1 个,或多个。

        throw 关键字后只能抛出一个确切的异常类对象,而 throws 后可以抛出多个异常类,而非 new 的对象。

        throw 之后的只能是异常对象,不能是其他对象,也就是说不能这样写 throw new String("错误信息") 。

自定义异常

创建异常类,只需满足以下两个要求:

  1. 声明一个类需要继承 Exception 或是它的子类。
  2. 提供至少 2 个构造方法,一个无参构造器,一个带参构造器,参数需要包含传递的异常信息。

Java程序设计进阶_第1张图片


集合和泛型

集合框架

        集合框架可以分为 Collection 和 Map 两类

        Collection 接口

        主要有三个子接口,分别是 Set 接口、List 接口和 Queue 接口,下面简要介绍这三个接口。

  • Set 接口

    Set 实例用于存储一组不重复的,无序的元素。

  • List 接口

    List 实例是一个有序集合。程序员可对 List 中每个元素的位置进行精确控制,可以根据索引来访问元素,此外 List 中的元素是可以重复的。

  • Queue 接口

    Queue 中的元素遵循先进先出的规则,是对数据结构 “队列” 的实现。

        Map接口

        定义了存储和操作一组 “键(key)值(value)” 映射对的方法。

        区别        

        Map 接口和 Collection 接口的本质区别在于,Collection 接口里存放的是一系列单值对象,而 Map 接口里存放的是一系列 key-value 对象。Map 中的 key 不能重复,每个 key 最多只能映射到一个值。

Set接口(无重复,无序)

         Set 接口继承自 Collection 接口的主要方法。

  • boolean add(Object obj)

    向集合中添加一个 obj 元素,并且 obj 不能和集合中现有数据元素重复,添加成功后返回 true。如果添加的是重复元素,则添加操作无效,并返回 false。

  • void clear()

    移除此集合中的所有数据元素,即将集合清空。

  • boolean contains(Object obj)

    判断此集合中是否包含 obj,如果包含,则返回 true。

  • boolean isEmpty()

    判断集合是否为空,为空则返回 true。

  • Iterator iterator()

    返回一个 Iterator 对象,可用它来遍历集合中的数据元素。

  • boolean remove(Object obj)

    如果此集合中包含 obj,则将其删除,并返回 true。

  • int size()

    返回集合中真实存放数据元素的个数,注意与数组、字符串获取长度的方法的区别。

  • Object[] toArray()

    返回一个数组,该数组包含集合中的所有数据元素。

        HashSet

//创建一个HashSet对象,存放学生姓名信息
Set nameSet = new HashSet();
// 操作
System.out.println("添加王云是否成功:" + nameSet.add("王云"));

System.out.println("显示集合内容:" + nameSet);

System.out.println("集合里是否包含南天华:" + nameSet.contains("南天华"));

System.out.println("从集合中删除\"南天华\"...");
nameSet.remove("南天华");

System.out.println("集合里是否包含南天华:" + nameSet.contains("南天华"));

System.out.println("集合中的元素个数为:" + nameSet.size());

        HashSet 是如何判断元素重复的?

        当向 HashSet 中增加元素时,HashSet 会先计算此元素的 hashcode,如果 hashcode 值与 HashSet 集合中的其他元素的 hashcode 值都不相同,那么就能断定此元素是唯一的。否则,如果 hashcode 值与 HashSet 集合中的某个元素的 hashcode 值相同,HashSet 就会继续调用 equals() 方法进一步判断它们的内容是否相同,如果相同就忽略这个新增的元素,如果不同就把它增加到 HashSet 中。

        TreeSet

        TreeSet 类在实现了 Set 接口的同时,也实现了 SortedSet 接口,是一个具有排序功能的 Set 接口实现类。TreeSet 集合中的元素是按字典顺序进行排列输出的。

        常用方法

  • add() 方法,为集合添加元素。
  • toArray() 方法,把集合中的所有数据提取到一个新的数组中。
 // 创建整型数组
 Integer[] array = new Integer[size];
 // 将集合元素转换为数组元素
 set.toArray(array);

List接口(重复,有序)

        List 是 Collection 接口的子接口,List 中的元素是有序的,而且可以重复。常用的 List 实现类是 ArrayList 和 LinkedList。

        常用方法

  • void add(int index,Object o)

    在集合的指定 index 位置处,插入指定的 o 元素。

  • Object get(int index)

    返回集合中 index 位置的数据元素。

  • int indexOf(Object o)

    返回此集合中第一次出现的指定 o 元素的索引,如果此集合不包含 o 元素,则返回-1。

  • int lastIndexOf(Object o)

    返回此集合中最后出现的指定 o 元素的索引,如果此集合不包含 o 元素,则返回-1。

  • Object remove(int index)

    移除集合中 index 位置的数据元素。

  • Object set(int index,Object o)

    用指定的 o 元素替换集合中 index 位置的数据元素。

        ArrayList 类

        数组(顺序表)在插入或删除数据元素时,需要批量移动数据元素,故性能较差;但在根据索引获取数据元素时,因为数组是连续存储的,所以在遍历元素或随机访问元素时效率高。

        ArrayList 实现类的底层就是数组,因此 ArrayList 实现类更加适合根据索引访问元素的操作。

        LinkedList 类

        LinkedList 的底层是链表。LinkedList 和 ArrayList 在应用层面类似,只是底层存储结构上的差异导致了二者对于不同操作,存在性能上的差异。这其实就是顺序表和链表之间的差异。一般而言,对于 “索引访问” 较多的集合操作建议使用 ArrayList 实现类,而对于 “增删” 较多的集合操作建议使用 LinkedList 实现类

泛型

        泛型是指在定义集合的同时也定义集合中元素的类型,需要 “< >” 进行指定,其语法形式如下:

集合<数据类型> 引用名 = new 集合实现类<数据类型> ();

        在定义集合的同时使用泛型,用 “< >” 进行指定集合中元素的类型后,再从集合中取出某个元素时,就不需要进行类型转换,不仅可以提高程序的效率,也让程序更加清晰明了,易于理解。

Iterator 接口

        Iterator 接口为遍历集合而生,是 Java 语言解决集合遍历的一个工具。

        iterator() 方法定义在 Collection 接口中,因此所有单值集合的实现类,都可以通过 iterator() 方法实现遍历。

        Iterator 接口的三个方法:

  • boolean hasNext()

    判断是否存在下一个可访问的数据元素。

  • Object next()

    返回要访问的下一个数据元素,通常和 hasNext() 在一起使用。

  • void remove()

    从迭代器指向的 Collection 集合中移除迭代器返回的上一个数据元素。

Integer[] infos = {12,45,23,86,100,78,546,1,45,99,136,23};
Set s = new TreeSet();
for (Integer i : infos) {
	s.add(i);
}
		
// 使用迭代器遍历集合数据
Iterator it = s.iterator();
while(it.hasNext()) {
	System.out.println(it.next());
}

Map接口

        Map 接口,用于保存具有映射关系的键值对数据。

        Map 接口中的 key 和 value 可以是任何引用类型的数据,key 不允许重复value 可以重复,key 和 value 都可以是 null 值,但需要注意的是,key 为 null 只能有一个value 为 null 可以多个,它们之间存在单向一对一关系,也就是说通过指定存在的 key 一定找到对应的 value 值。

        常用方法

  • Object put(Object key,Object value)

    将指定键值对(key 和 value)添加到 Map 集合中,如果此 Map 集合以前包含一个该键 key 的键值对,则用参数 key 和 value 替换旧值。

  • Object get(Object key)

    返回指定键 key 所对应的值,如果此 Map 集合中不包含该键 key,则返回 null。

  • Object remove(Object key)

    如果存在指定键 key 的键值对,则将该键值对从此 Map 集合中移除。

  • Set keySet()

    返回此 Map 集合中包含的键的 Set 集合。在上面的程序最后添加下面的语句:System.out. println(domains.keySet());,则会输出[com, edu, org, net]

  • Collection values()

    返回此 Map 集合中包含的值的 Collection 集合。在上面的程序最后添加下面的语句:System.out.println(domains.values());,则会输出[工商企业,教研机构,非营利组织,网络服务商]

  • boolean containsKey(Object key)

    如果此 Map 集合包含指定键 key 的键值对,则返回 true。

  • boolean containsValue(Object value)

    如果此 Map 集合将一个或多个键映射到指定值,则返回 true。

  • int size()

    返回此 Map 集合的键值对的个数。


IO和XML

File 类

        File 类生成的对象就代表一个特定的文件或目录,并且 File 类提供了若干方法对这个文件或目录进行读写等各种操作。 File 类在 java.io 包下,与系统输入/输出相关的类通常都在此包下。

        File 类的构造方法有如下四个:

  • File(String pathname):创建一个新的 File 实例,该实例的存放路径是 pathname。
  • File(String parent, String child):创建一个新的 File 实例,该实例的存放路径是由 parent 和 child 拼接而成的。
  • File(File parent, String child):创建一个新的 File 实例。 parent 代表目录, child 代表文件名,因此该实例的存放路径是 parent 目录中的 child 文件。
  • File(URI uri):创建一个新的 File 实例,该实例的存放路径是由 URI 类型的参数指定的。

        File 类中常用的方法

方法 说明
canExecute() 判断 File 类对象是否可执行。
canRead() 判断 File 类对象是否可读。
canWrite() 判断 File 类对象是否可写。
createNewFile() 当不存在该文件时,创建一个新的空文件。
exists() 判断 File 类对象是否存在。
getAbsoluteFile() 获取 File 类对象的绝对路径。
getName() 获取 File 类对象的名称。
getParent() 获取 File 类对象父目录的路径。
getPath() 获取 File 类对象的路径。
isAbsolute() 判断 File 类对象是否是绝对路径。
isDirectory() 判断 File 类对象是否是目录。
isFile() 判断 File 类对象是否是文件。
isHidden() 判断 File 类对象是否有隐藏的属性。
lastModified() 获取 File 类对象最后修改时间。
length() 获取 File 类对象的长度。
listRoots() 列出可用的文件系统根目录。

IO流

        流是对 I/O 操作的形象描述,水从一个地方转移到另一个地方就形成了水流,而信息从一处转移到另一处就叫做 I/O 流。

        在 Java 中,文件的输入和输出是通过流(Stream)来实现的,流的概念源于 UNIX 中管道(pipe)的概念。在 UNIX 系统中,管道是一条不间断的字节流,用来实现程序或进程间的通信,或读写外围设备、外部文件等。

        输入和输出

        在计算机的世界中,输入 Input 和输出 Output 都是针对计算机的内存而言的。     

        在 Java 中流分为 :字节流和字符流

        字节流的处理单位是字节,通常用来处理二进制文件,如音乐、图片文件等,并且由于字节是任何数据都支持的数据类型,因此字节流实际可以处理任意类型的数据。而字符流的处理单位是字符,因为 Java 采用 Unicode 编码,Java 字符流处理的即 Unicode 字符,所以在操作文字、国际化等方面,字符流具有优势。

        输出字节流类和输入字节流类存在对应关系

  • FileInputStream:把一个文件作为输入源,从本地文件系统中读取数据字节,实现对文件的读取操作。
  • ByteArrayInputStream:把内存中的一个缓冲区作为输入源,从内存数组中读取数据字节。
  • ObjectInputStream:对以前使用 ObjectOutputStream 写入的基本数据和对象进行反序列化,用于恢复那些以前序列化的对象,注意这个对象所属的类必须实现 Serializable 接口。
  • PipedInputStream:实现了管道的概念,从线程管道中读取数据字节。主要在线程中使用,用于两个线程间的通信。
  • SequenceInputStream:其他输入流的逻辑串联。它从输入流的有序集合开始,并从第一个输入流开始读取,直至到达文件末尾,接着从第二个输入流读取,依次类推。
  • System.in:从用户控制台读取数据字节,在 System 类中,in 是 InputStream 类型的静态成员变量。

        InputStream 输入流的方法

  • int read():从输入流中读取数据的下一字节,返回 0 ~ 255 范围内的整型字节值;如果输入流中已无新的数据,则返回 -1,否则返回值 > -1。
  • int read(byte[] b):从输入流中读取一定数量的字节,并将其存储在字节数组 b 中,以整数形式返回实际读取的字节数(要么是字节数组的长度,要么小于字节数组的长度)。
  • int read(byte[] b, int off, int len):将输入流中最多 len 个数据字节读入字节数组 b 中,以整数形式返回实际读取的字节数,off 指数组 b 中将写入数据的初始偏移量。
  • void close():关闭此输入流,并释放与该流关联的所有系统资源。
  • int available():返回可以不受阻塞地从此输入流读取(或跳过)的估计字节数。
  • void mark(int readlimit):在此输入流中标记当前的位置。
  • void reset():将此输入流重新定位到上次 mark 的位置。
  • boolean markSupported():判断此输入流是否支持 mark() 和 reset() 方法。
  • long skip(long n):跳过并丢弃此输入流中数据的 n 字节。

        字符流

        读取字符流类继承自抽象类 Reader,写入字符流继承自抽象类 Writer

        输出字符流类和输入字符流类存在对应关系

  • FileReader :与 FileInputStream 对应,从文件系统中读取字符序列。
  • CharArrayReader :与 ByteArrayInputStream 对应,从字符数组中读取数据。
  • PipedReader :与 PipedInputStream 对应,从线程管道中读取字符序列。
  • StringReader :从字符串中读取字符序列。
        Writer 输出字符流

        操作的数据是 char 相关类型,不是 byte 类型。

  • Writer append(char c):将指定字符 c 追加到此 Writer,此处是追加,不是覆盖。
  • Writer append(CharSequence csq):将指定字符序列 csq 添加到此 Writer。
  • Writer append(CharSequence csq, int start, int end):将指定字符序列 csq 的子序列,追加到此 Writer。
  • void write(char[] cbuf):写入字符数组 cbuf。
  • void write (char[] cbuf, int off, int len):写入字符数组 cbuf 的某一部分。
  • void write(int c):写入单个字符 c。
  • void write(String str):写入字符串 str。
  • void write(String str, int off, int len):写入字符串 str 的某一部分。
  • void close():关闭当前流。

缓冲流 BufferedReader /Writer 

        缓冲流、转换流和数据流,它们的底层都遵循着一个相同的设计模式——装饰器模式。简单的讲,装饰器模式就是通过方法,将对象逐步进行包装

        例如,字节输出流 FileOutputStream 对象放在缓冲输出流 BufferedOutputStream 类的构造方法中以后,就变成了一个缓冲输出流 BufferedOutputStream 对象,如下:

BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(...)) ;

        更进一步,缓冲输出流对象如果又被传入到了数据流 DataOutputStream 类的构造方法中,就又变成了一个数据流 DataOutputStream 对象,如下:

DataOutputStream out = new DataOutputStream(bos);

        类似这种形式,就是装饰器模式的具体应用。

        装饰器模式在语法上要求包装类和被包装类属于同一个继承体系,并且包装后外观未变,但功能得到了增强

Java程序设计进阶_第2张图片

XML

        XML 是可扩展标记语言(Extensible Markup Language)的简称。

        XML 文档总是以 XML 声明开始,即告知处理程序,本文档是一个 XML 文档。在 XML 声明中,通常包括版本、编码等信息,以  开始,以 ?> 结尾。

        XML 文档由元素组成,一个元素由一对标签来定义,包括开始和结束标签,以及其中的内容。元素之间可以嵌套,但不能交叉,也就是说元素的内容里还可以包含元素。

        XML 文档规范主要分成两种,一种是 DTD 规范,一种是 Schema 规范。DTD 规范有自己的语法规范要求,不过相对比较简单;而 Schema 规范是完全基于 XML 规范来进行编写的。


Java反射机制

        Java 反射是指 Java 程序在运行时,可以动态的加载类,并调用类的属性和方法

        在运行时动态获取类的信息以及动态调用对象方法的功能称为 Java 反射机制。

class类

        在 JDK 中,Class 类存在于 java.lang 包中,其余反射相关的类都存在于 java.lang.reflect 包中。

        Class 类是使用 Java 反射机制的入口,封装了一个类或接口的运行时信息,开发者通过调用 Class 类的方法可以获取这些信息。

        获取 Class 对象的 5 种方式

        反射的入口类是 Class,常用的获取 Class 对象的方法有 Class.forName("全类名")、 类名.class和 对象名.getClass() 三种。

  • Class.forName("全类名")

    将传入字符串形式的全类名,转为反射的入口 Class 对象。

  Class c = Class.forName("java.lang.Object");
  • 类名.class

    通过类名定义反射的入口 Class 对象。

  Class c = Car.class;
  • 包装类.TYPE

    通过包装类提供的 TYPE 属性,定义反射的入口 Class 对象。

  Class c = Integer.TYPE;
  • 对象名.getClass()

    通过 Object 基类中的 getClass() 方法,定义反射的入口 Class 对象。

  String name="大力士";
  Class c = name.getClass();

lass 对象.getSuperClass()

通过 Class 类中的 getSuperClass() 方法,定义反射的入口 Class 对象。

  Class c = String.class.getSuperClass();

        常用方法

  • Method[] getMethods()

    返回一个包含 Method 对象的数组,数组中存放的是该类及父类(或父接口)中用 public 修饰的方法。

  • Method[] getDeclaredMethods()

    返回一个包含 Method 对象的数组,存放该类(或接口)中 private 等四种访问修饰符修饰的所有方法(不含父类或父接口中定义的方法)。可见,该方法也可以突破访问修饰符的限制。

  • Constructor[] getConstructors()

    返回一个包含 Constructor 对象的数组,存放该类中所有用 public 修饰的公共构造方法。

  • Constructor getConstructor(Class... parameterTypes)

    返回一个指定参数列表的 Constructor 对象。

  • Class[] getInterfaces()

    返回一个包含 Class 对象的数组,存放该类或接口实现的接口。

  • T newInstance()

    使用无参构造方法创建该类的一个新实例。

  • String getName()

    以 String 的形式返回该类(类、接口、数组类、基本类型或 void)的完整名。

getDeclaredMethods 和 getMethods 方法的区别

        使用 Class 类的 getMethods() 方法获取的是 Class 所表示的 Sub 类及其父类中所有的公共方法(即 public 修饰的方法)。

        使用 getDeclaredMethods() 方法获得了 Sub 类的全部修饰符修饰的方法,但不包括继承而来的方法。

动态的创建对象

        通过 Class 类的 newInstance() 方法创建对象,该方法要求该 Class 对应类有无参构造方法。执行 newInstance() 方法实际上就是使用对应类的无参构造方法来创建该类的实例,其代码的作用等价于Sub sub = new Sub();

Class c = Class.forName("Sub");
// 通过 Class 类的 newInstance() 方法创建对象
 Sub sub = (Sub) c.newInstance();

Java程序设计进阶_第3张图片


Java多线程机制

线程

        线程是操作系统中的基本调度单元

        进程和线程

        进程(Process)和线程(Thread)是操作系统中的概念,用于实现并发执行和多任务处理。它们具有以下区别和联系:

        区别:

        1、定义:进程是程序的执行实例,是操作系统分配资源的基本单位,具有独立的内存空间和系统资源;线程是进程的执行单元,是程序执行的最小单位,共享所属进程的内存空间和系统资源。
        2、资源占用:每个进程都有独立的内存空间和系统资源,进程间切换开销较大;线程共享所属进程的内存空间和系统资源,线程间切换开销较小。
        3、切换开销:进程切换需要保存和恢复整个进程的上下文,开销较大;线程切换只需要保存和恢复线程的上下文,开销较小。
        4、通信与同步:进程间通信需要使用特定的机制(如管道、消息队列、共享内存等)进行数据交换;线程间通信可以直接共享内存,使用共享变量等机制进行数据交换。
稳定性:进程的稳定性较高,一个进程崩溃不会影响其他进程;线程的稳定性较低,一个线程崩溃可能导致整个进程崩溃。

        联系:

        1、关系:一个进程可以包含多个线程,线程是进程的一部分,线程依赖于进程的存在。
        2、共享资源:同一进程内的线程可以共享进程的内存空间和系统资源,可以方便地共享数据和通信。
        3、并发执行:多个线程可以在同一进程内并发执行,实现多任务处理和提高程序的执行效率。

        线程状态

        线程主要有下列 5 种状态。

  1. 新建状态:创建一个新的子线程。
  2. 就绪状态:线程已经具备运行的条件,等待调度程序分配 CPU 资源给这个线程运行。
  3. 运行状态:调度程序分配 CPU 资源给该线程,该线程正在执行。
  4. 阻塞状态:线程正等待除了 CPU 资源以外的某个条件符合或某个事件发生。
  5. 死亡状态:又可称为终止状态,表示线程体操作已经完成结束。

        线程状态转换图

Java程序设计进阶_第4张图片

对线程的基本操作主要有以下五种,通过这五种操作,使线程在各个状态之间转换。

  • 派生:线程属于进程的一部分,因此可以由进程派生出线程,但也可以由线程自身派生。

    在 Java 中,可以创建一个线程并通过调用该线程的 start() 方法使该线程进入就绪状态

  • 调度:系统分配 CPU 资源给就绪状态的线程,使线程获得 CPU 资源进行运行。

    执行 Java 线程类中 run() 方法里的内容

  • 阻塞:当线程缺少除了 CPU 资源以外的某个条件符合或某个事件时,就会进入阻塞状态。

    阻塞时,寄存器上下文、程序计数器以及堆栈指针都会得到保存。

  • 激活:在阻塞状态下的线程,如果需要等待的条件符合或事件发生,则该线程被激活并进入就绪状态。

  • 结束:在运行状态的线程,线程执行结束,它的寄存器上下文以及堆栈内容等将被释放。

        线程的创建

        1、继承 Thread 类

[public] class 类名 extends Thread{
    //属性
    //其他方法
    public void run() { // 重写 Thread 类中的 run() 方法
        //线程需要执行的核心代码
    }
}

        2、实现 Runnable 接口

[public] class 类名 implements Runnable{
    //属性
    //其他方法
    public void run() { // 实现 Runnable 接口中的 run() 方法
        //线程需要执行的核心代码
    }
}

        Runnable 接口中仅仅定义了 run() 这么一个方法,因此还必须将 Runnable 对象转换为 Thread 对象,从而使用 Thread 类中的线程 API ,转换的方法如下所示

Runnable实现类名 对象名 = new  Runnable实现类名();
Thread 线程对象名 = new Thread(对象名);

        线程结束

线程通常在三种情况下会终止:

  • 最普遍的情况是线程中的 run() 方法执行完毕后线程终止;
  • 线程抛出了异常且未进行异常处理;
  • 调用当前线程的 stop() 方法终止线程(该方法已被废弃)。

线程控制(sleep、join)

        线程控制方法

  • void start():使该线程开始执行,Java 虚拟机负责调用该线程的 run() 方法。
  • void sleep(long millis):静态方法,线程进入阻塞状态,在指定时间(单位为毫秒)到达之后进入就绪状态。
  • void yield():静态方法,当前线程放弃占用 CPU 资源,回到就绪状态,使其他优先级不低于此线程的线程有机会被执行。
  • void join():只有当前线程等待加入的线程完成,才能继续往下执行。
  • void interrupt()中断线程的阻塞状态(而非中断线程),例如一个线程 sleep(1000000000) ,为了中断这个过长的阻塞过程,可以调用该线程的 interrupt() 方法,中断阻塞。需要注意的是,此时 sleep() 方法会抛出 InterruptedException 异常。
  • void isAlive():判定该线程是否处于活动状态,处于就绪、运行和阻塞状态的都属于活动状态。
  • void setPriority(int newPriority)设置当前线程的优先级
  • int getPriority():获得当前线程的优先级。

        yield() 和 sleep() 的区别

        yield() 方法,是一个让线程放弃 CPU 资源的方法。

        yield() 方法和 sleep() 方法都是 Thread 类的静态方法,都会使当前处于运行状态的线程放弃 CPU 资源,把运行机会让给别的线程。

        但两者的区别在于:

  1. sleep() 方法会给其他线程运行的机会,不考虑其他线程的优先级,因此会给较低优先级线程一个运行的机会;而 yield() 方法只会给相同优先级或者更高优先级的线程一个运行的机会。
  2. 当线程执行了 sleep(long millis) 方法后,将转到阻塞状态,参数 millis 指定了睡眠时间;而当线程执行了 yield() 方法后,将转到就绪状态。
  3. sleep() 方法声明抛出 InterruptedException 异常,而 yield() 方法没有声明抛出任何异常。yield() 方法只会给相同优先级或者更高优先级的线程一个运行的机会,因此这是一种不可靠的提高程序并发性的方法,只是让系统的调度程序再重新调度一次,在实际编程过程中并不推荐使用。

        线程优先级

        线程的优先级用数字 1 ~ 10 表示(默认值为 5),其中 10 表示优先级最高。

        尽管 JDK 给线程优先级设置了 10 个级别,但仍然建议只使用 MAX_PRIORITY(级别为 10)、NORM_PRIORITY(级别为 5)和 MIN_PRIORITY(级别为 1)三个常量来设置线程优先级,让程序具有更好的可读性。

        守护线程

        守护线程是为其他线程的运行提供便利的线程。

        Java 的垃圾收集机制中的一些实现就使用了守护线程。

        程序可以包含守护线程和用户线程,当程序只有守护线程时,该程序才能真正结束运行。

        如果要使一个线程成为守护线程,则必须在调用它的 start() 方法之前,调用线程的 setDaemon(true) 方法。并且可以使用 isDaemon() 方法的返回值( true 或 false )判断一个线程是否为守护线程。

Thread 类中提供的方法:

  • public final void setDaemon(boolean on)

    当 on 设置为 true 时,该线程为守护线程;当 on 设置为 false 时,该线程为用户线程;默认情况下是 false。

  • public final boolean isDaemon()

    判断当前线程是否为守护线程。true - 是,false - 否。

这两个方法都是被 final 修饰的,也就是说这两个方法只能被调用,不能被重写。

Thread userThread = new Thread(new Worker());
Thread daemonThread = new Thread(new Timer());
 // 设置守护线程
daemonThread.setDaemon(true);
 // 启动用户和守护线程
 userThread.start();
 daemonThread.start();
 System.out.println("Worker 是否为守护线程:" + userThread.isDaemon());
 System.out.println("Timer 是否为守护线程:" + daemonThread.isDaemon());

多线程同步

        对象锁机制

        多线程同步依靠的是对象锁机制,synchronized 关键字就是利用锁来实现对共享资源的互斥访问

        实现多线程同步的方法之一就是同步代码块,其语法形式如下:

synchronized(obj){
//同步代码块
}

        要想实现线程的同步,则这些线程必须去竞争一个唯一的共享的对象锁

        synchronized(lock){...},lock 需要是准备好的任何对象。

//定义了一个对象锁lock
static final Object lock = new Object();

        synchronized(this){...},当程序中只有一层同步块处理时,可以使用 this 关键字作为竞争对象,简化代码的书写。

        synchronized 关键字实现同步方法

访问修饰符 synchronized 返回类型 方法名{
   //同步方法体内代码块
}

//通过类的静态方法实现互斥访问
private static synchronized void doTask(int tid) {
    for (int i = 0; i < 10; i++) {
      System.out.println("线程ID名为: " + tid + "正在输出:" + i);
    }
}

        每个类实例都对应一把锁,每个 synchronized 方法都必须获得调用该方法的类实例的锁方能执行,否则所属线程阻塞。

        synchronized 方法一旦执行,就独占该锁,直到该方法返回时才将锁释放,此后被阻塞的线程方能获得该锁,重新进入就绪状态。

        这种机制确保了同一时刻对于每个类实例,其所有声明为 synchronized 的方法中至多只有一个处于就绪状态,从而有效避免了类成员变量的访问冲突。

线程死锁

        死锁(互相等待对方释放资源)

        如果线程 A 只有等待线程 B 的完成才能继续,而在线程 B 中又要等待线程 A 的资源,那么这两个线程相互等待对方释放锁时就会发生死锁。出现死锁后,不会出现异常,因此不会有任何提示,只是相关线程都处于阻塞状态,无法继续运行。

        产生原因

        死锁产生的原因有以下三个方面:

  1. 系统资源不足。如果系统的资源充足,所有线程的资源请求都能够得到满足,自然就不会发生死锁。
  2. 线程运行推进的顺序不合适。
  3. 资源分配不当等。

        产生死锁的必要条件有以下四个:

  1. 互斥条件:一个资源每次只能被一个线程使用。
  2. 请求与保持条件:一个线程因请求资源而阻塞时,对已获得的资源保持不放。
  3. 不剥夺条件:线程已获得的资源,在未使用完之前,不能强行剥夺。
  4. 循环等待条件:若干线程之间形成一种头尾相接的循环等待资源关系。

        只要系统发生死锁,这四个条件就必然成立;反之,只要破坏四个条件中的任意一个,就可以避免死锁的产生。

线程协作(wait、notify) 

        JDK 的 Object 类提供了 void wait()、void notify()、void notifyAll() 三个方法,解决线程之间协作的问题。

        语法上,这三个方法都只能在 synchronized 修饰的同步方法或者同步代码块中使用,否则会抛出异常。

  • void wait():让当前线程等待,等待其他线程调用此对象的 notify() 方法或 notifyAll() 方法将其唤醒。
  • void notify():唤醒在此对象锁上等待的单个线程;如果有多个等待的线程,则随机唤醒一个。
  • void notifyAll():唤醒在此对象锁上等待的所有线程。

        线程等待与唤醒的流程

Java程序设计进阶_第5张图片

Java程序设计进阶_第6张图片


Java网络编程API

网络协议的三要素

  1. 语义:规定了通信双方为了完成某种目的,需要发出何种控制信息以及基于这个信息需要做出何种行动。

    例如:A 处民宅发生火灾,需要向 B 处城市报警台报警,则 A 发送 “119+民宅地址” 的信息给 B,B 获得这个信息后根据 119 知道是火警,则通知消防队去民宅地址灭火。

  2. 语法:是用户数据与控制信息的结构与格式,以及数据出现的先后顺序。

    例如,语法可以规定 A 向 B 发送的数据前部是 “119” ,后部是 “民宅地址” 。

  3. 时序:是对事件发生顺序的详细说明。

    比如,何时进行通信,先讲什么,后讲什么,讲话的速度等。

这三个要素可以描述为:语义表示要做什么,语法表示要怎么做,时序表示做的顺序。

网络 OSI 七层模型

它将计算机网络体系结构的通信协议划分为七层,分层模型称为 OSI 七层模型,自下而上依次为:

  • 物理层(Physics Layer)
  • 数据链路层(Data Link Layer)
  • 网络层(Network Layer)
  • 传输层(Transport Layer)
  • 会话层(Session Layer)
  • 表示层(Presentation Layer)
  • 应用层(Application Layer)

TCP/IP 四层模型

  • 网络接口层(Network Interface Layer)
  • 网络层(Network Layer)
  • 传输层(Transport Layer)
  • 应用层(Application Layer)

TCP和UDP的区别

  • TCP基于连接,UDP基于无连接
  • TCP要求系统资源较多,UDP较少
  • UDP程序结构简单
  • TCP保证数据准确性,UDP可能丢包
  • TCP保证顺序性,UDP不保证

域名

        域名(Domain Name),是由一串用 “.” 分隔的字符串组成的 Internet 上某一台计算机或计算机组的名称,用于在进行数据传输时标识计算机的电子方位。        

        域名可分为不同级别,包括顶级域名、二级域名等。

        顶级域名又可分为以下两类:

  • 国家顶级域名,200 多个国家都按照 ISO3166 国家代码分配了顶级域名。

    例如中国是 cn,美国是 us,韩国是 kr 等。

  • 国际顶级域名,一般表示着注册企业类别的符号。

    例如表示工商企业的 com,表示网络提供商的 net,表示非营利组织的 org 等。

        二级域名是指顶级域名之下的域名,由域名注册人申请注册的网上名称。例如,sohu、apple、microsoft 等。


Java注解

注解

        注解使得 Java 源代码中不但可以包含功能性的实现代码,还可以添加元数据。所谓元数据,就是描述数据的数据。举个例子来说,比如一张图片,图片的内容为主体数据,是需要展现给图片浏览者看到的信息,而图片的创建日期、图片大小等这类信息就是元数据。        

注解和注释

        注解和注释都属于对代码的描述:

  • 注释的作用只是简单地描述程序的信息,方便开发者再次阅读,不会被程序所读取。
  • 注解则是 Java 代码中的特殊标记,这些标记可以在编译、类加载、运行时被读取,并执行相应的处理

注解形式

        Java 注解有以下三种形式:

  • 不带参数的注解: @Annotation,例如 @Override
  • 带一个参数的注解: @Annotation(参数),例如 @SuppressWarnings(value="unused")
  • 带多个参数的注解: @Annotiation({参数1, 参数2, 参数3...}),例如 @MyTag(name="jhon",age=20)

定义注解

        定义注解的语法形式和接口差不多,只是在 interface 前面多了一个 ”@“ 符号,结构如下:

[public] @interface 注解名{
   // 定义属性
   数据类型 属性名() [default 属性值];
}

4 个元注解

  1. Target:指明注解支持的使用范围,以下为各取值的含义:

    • ElementType.TYPE 用于注解类、接口、枚举
    • ElementType.FIELD 用于注解属性
    • ElementType.METHOD 用于注解方法
    • ElementType.PARAMETER 用于注解参数
    • ElementType.CONSTRUCTOR 用于注解构造器
    • ElementType.LOCAL_VARIABLE 用于注解局部变量
    • ElementType.ANNOTATION_TYPE 用于注解注解
    • ElementType.PACKAGE 用于注解包
  2. Retention:指明注解保留的时间长短,以下为各取值的含义:

    • SOURCE 表示源文件中保留
    • CLASS 表示 class 编译时保留
    • RUNTIME 表示运行时保留
  3. Inherited:指明该注解类型被自动继承。

    如果一个 annotation 注解被 @Inherited 修饰,那么该注解作用于类的子类也会使用该 annotation 注解。

  4. Documented:指明拥有这个注解的元素可以被 javadoc 此类的工具文档化。

        @Target 注解使用

   @Target 注解的目的是用于指定被修饰的注解能用于修饰哪些程序元素(属性、方法、类或者接口等)。

        用 @Target 进行注解,以限制此注解只能使用在属性上。

        此时如果将此注解使用在方法上,编译器会报出 “注释类型不适用于该类型的声明” 的错误。

        @Retention 注解使用

        当 @Retention 注解的属性 value 设置为 RetentionPolicy.RUNTIME 时,编译器将把注解记录在 class 文件中,当运行 Java 程序时,虚拟机保留注解,程序可以通过反射获取该注解。

        @Documented 注解使用

        如果想在文档中也包含注解,就需要使用 @Documented 为文档注解。@Documented 元注解类型中没有成员变量。如果定义注解时使用了 @Documented 修饰,则所有使用该注解修饰的程序元素的 API 文档中都将包含该注解说明。

        @Inherited 注解的使用

        默认情况下,父类的注解不被子类继承,如果要想继承父类注解,就必须使用 @Inherited 元注解。

自定义注解

        当 @Retention 注解的属性 value 设置为 RetentionPolicy.RUNTIME 时,编译器将把注解记录在 class 文件中,当运行 Java 程序时,虚拟机保留注解程序可以通过反射获取该注解。方便获取自定义注解的信息。

@Target({ElementType.TYPE,ElementType.FIELD,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation {
    String value();
    //定义两个属性 name 和 age,带上默认值
    String name() default "张三";
    int age() default 18;
}

Java程序设计进阶_第7张图片


Junit测试框架

        使用 JUnit 可以编写和运行可重复执行的单元测试。

        在使用 JUnit 时,程序员必须知道被测试的程序如何进行,以及要完成什么样的功能,因此 JUnit 属于白盒测试

JUnit3 进行单元测试

  1. 工程导入 JUnit3 所需的 jar 包。
  2. 创建 Source Folder 测试文件夹 test。
  3. 根据需要测试的类文件,创建测试文件,进行单元测试

JUnit4 进行单元测试

  1. 创建 Source Folder 测试文件夹 test。
  2. 根据需要测试的类文件,创建测试文件,在创建测试文件的同时导入 JUnit4 所需的 jar 包。
  3. 在 JUnit4 版本中,采用了 Java 注解进行操作,需要测试的方法需要在方法前添加 @Test 注解才能测试,否则不会进入测试方法中。

JUnit5 进行单元测试

  1. 创建 Source Folder 测试文件夹 test。
  2. 根据需要测试的类文件,创建测试文件,在创建测试文件的同时导入 JUnit5 所需的 jar 包。
  3. 在 JUnit5 版本中,引用的都是在 org.junit.jupiter.api 包中,采用的注解与 JUnit4 版本中有所不同 @BeforeEach 和 @AfterEach,不过需要测试的方法前还是添加 @Test 注解。
  4. JUnit5 使用了新的断言类 org.junit.jupiter.api.Assertions。

Java程序设计进阶_第8张图片


JDK8与函数式编程

Lambda 表达式

        Lambda 表达式可以帮助开发者们大幅度地简化代码

        Java 中的 Lambda 表达式是用箭头符号(–>)将参数列表和方法体连接起来的,因此一个 Lambda 表达式由以下三部分组成。

  1. 用逗号分隔的参数列表;
  2. 箭头符号(–>);
  3. 方法体(表达式或代码块)。

        使用 Lambda 表达式对集合进行遍历

 ArrayList names = new ArrayList();
        names.add("张三");
        names.add("李四");
        names.add("王五");
        names.add("赵六");
        names.forEach(name -> System.out.println(name));
    }
  • 集合中的 forEach() 方法可以帮助我们遍历集合。
  • Lambda 表达式可以极大简化代码量。

方法引用的使用

        方法引用是用来直接访问类或者实例的已经存在的方法或者构造方法。

        方法引用提供了一种引用而不执行方法的方式。方法引用是一种更简洁易懂的 Lambda 表达式。

        引用的符号是 ::,其语法形式如下所示:

类名 :: 方法名

         注意:在使用方法引用时,只需要写方法名,不需要写小括号。

 void methodLambda(MyInterface mi) {
   // Lambda表达式
   mi.method(() -> Math.random());
}


void methodRef(MyInterface mi) {
    // 方法引用
    mi.method(Math::random);
}

        必要条件和类型

        使用方法引用必须满足:Lambda 所重写方法的参数列表,必须与所引用方法的参数列一致(或可兼容)。

        常见的方法引用有五种类型,如下表所示:

方法引用的类型 示 例
引用静态方法 类名 :: 静态方法名
引用某个对象的实例方法 对象名 :: 非静态方法
引用类中的实例方法 类名 :: 非静态方法
引用构造方法 类名:: new
引用数组 元素类型[] :: new

Stream 流

        Stream 是以接口的形式定义的。

生成流

        使用 Stream 的第一步就是将集合等类型的数据先转为 Stream 类型,也就是生成流。

        Stream 是用于处理数组、集合等批量数据的,因此在相关的数组类(如 Arrays)、集合接口(如 Collection)和 Stream 接口中就存在着生成流的方法。

        // 使用集合接口Collection中的方法生成流
        List list = new ArrayList<>();
        list.add("a");
        list.add("ab");
        list.add("abc");
        list.add("hello");
        list.add("stream");
        // stream()会以单线程的方式,将集合中的数据转为Stream类型
        Stream stream1 = list.stream();
        // parallelStream()会以多线程的并发方式,将集合中的数据转为Stream类型
        Stream stream2 = list.parallelStream();

        // 使用数组类Arrays中的方法生成流
        String[] arr = new String[]{"hello", "stream"};
        Stream stream3 = Arrays.stream(arr);

        // 使用Stream接口中的方法生成流
        Stream stream4 = Stream.of(arr);

转换流

        转换流是指对已经生成的 Stream 对象进行转换。

        Stream 接口提供了很多常见的转换方法,通过 filter() 方法对 Stream 对象中的数据进行过滤,或者通过 limit() 限制 Stream 对象中的数据个数等。

终止流

        终止流就是 Stream 对象的终端操作。

        终止流的操作也都存在于 Stream 接口中,可以使用:

  • forEach() 方法遍历 Stream 对象中的元素;
  • reduce() 方法对 Stream 对象中的多个元素进行归约处理;
  • max()/min()/count() 等方法对 Stream 对象中的元素进行统计操作等。
  // 使用集合接口Collection中的方法生成流
        List list = new ArrayList<>();
        list.add("a");
        list.add("ab");
        list.add("abc");
        list.add("hello");
        list.add("stream");
        // stream()会以单线程的方式,将集合中的数据转为Stream类型
        Stream stream1 = list.stream();
        // 使用filter()和limit()方法进行转换流操作
        Stream stream;
        //先使用filter()方法筛选出stream1中字符串长度大于2的元素
        stream = stream1.filter((x) -> x.length() > 2)
        // 然后再通过limit()方法从结果元素中只保留两个元素
        .limit(2);
        // 使用forEach()遍历并输出 stream 中的元素
        stream.forEach(System.out::println);

        // parallelStream()会以多线程的并发方式,将集合中的数据转为Stream类型
        Stream stream2 = list.parallelStream();

        //使用map方法进行转换流操作
        stream = stream2.map(str -> str.toUpperCase());
        //统计stream2中的元素个数
        long count = stream.count();
        System.out.println("stream 中的元素个数是:" + count);

        // 使用数组类Arrays中的方法生成流
        String[] arr = new String[]{"hello", "stream"};
        Stream stream3 = Arrays.stream(arr);
        stream = stream3.map(str -> str.toUpperCase());
        //终止流
        //reduce()方法可以聚合流中的所有元素,也就是将Stream中的所有元素依次按表达式计算,最终得出一个值
        String reduce = stream.reduce("", (a, b) -> a + b);
        System.out.println("reduce:" + reduce);

        // 使用Stream接口中的方法生成流
        Stream stream4 = Stream.of(arr);
        // 使用forEach()遍历并输出 stream4 中的元素
        stream4.forEach(x -> System.out.print(x + "\t"));

Java程序设计进阶_第9张图片

你可能感兴趣的:(总结,java,开发语言,1024程序员节)