原文:Java 语言基础(异常机制和File类,IO流,多线程,网络编程,反射机制)
异常机制和File类
异常机制
基本概念
异常就是"不正常"的含义,在 Java 语言中主要指程序执行中发生的不正常情况。
java.lang.Throwable
类是 Java 语言中错误 (Error) 和异常 (Exception) 的超类。
其中Error
类主要用于描述 Java 虚拟机无法解决的严重错误,通常无法编码解决,如:JVM挂掉了等。
其中Exception
类主要用于描述因编程错误或偶然外在因素导致的轻微错误,通常可以编码解决,如:0 作为除数等。
想了解更多,欢迎关注我的微信公众号:Renda_Zhang
异常的分类
java.lang.Exception
类是所有异常的超类,主要分为以下两种:
RuntimeException
- 运行时异常,也叫作非检测性异常
IOException
和其它异常 - 其它异常,也叫作检测性异常,所谓检测性异常就是指在编译阶段都能被编译器检测出来的异常。
其中RuntimeException
类的主要子类:
ArithmeticException
类 - 算术异常
ArrayIndexOutOfBoundsException
类 - 数组下标越界异常
NullPointerException
- 空指针异常
ClassCastException
- 类型转换异常
NumberFormatException
- 数字格式异常
注意:当程序执行过程中发生异常但又没有手动处理时,则由 Java 虚拟机采用默认方式处理异常,而默认处理方式就是:打印异常的名称、异常发生的原因、异常发生的位置以及终止程序。
异常的避免
在开发中尽量使用if
条件判断来避免异常的发生。
但是过多的if
条件判断会导致程序的代码加长、臃肿,可读性差。
异常的捕获
语法格式
try {
编写可能发生异常的代码;
}
catch(异常类型 引用变量名) {
编写针对该类异常的处理代码;
}
...
finally {
编写无论是否发生异常都要执行的代码;
}
注意事项
当需要编写多个catch
分支时,切记小类型应该放在大类型的前面;
懒人的写法:
catch(Exception e) {}
finally
通常用于进行善后处理,如:关闭已经打开的文件等。
执行流程
try {
a;
b; // 可能发生异常的语句 b
c;
}catch(Exception ex) {
d;
}finally {
e;
}
当没有发生异常时的执行流程:a b c e
;
当发生异常时的执行流程:a b d e
;
异常的抛出
基本概念在某些特殊情况下有些异常不能处理或者不便于处理时,就可以将该异常转移给该方法的调用者,这种方法就叫异常的抛出。当方法执行时出现异常,则底层生成一个异常类对象抛出,此时异常代码后续的代码就不再执行。
语法格式访问权限 返回值类型 方法名称(形参列表) throws 异常类型1,异常类型2,...{ 方法体; }
如:
public void show() throws IOException{}
方法重写的原则
要求方法名相同、参数列表相同以及返回值类型相同,从 jdk 1.5 开始支持返回子类类型;
要求方法的访问权限不能变小,可以相同或者变大;
要求方法不能抛出更大的异常;
注意:子类重写的方法不能抛出更大的异常、不能抛出平级不一样的异常,但可以抛出一样的异常、更小的异常以及不抛出异常。
如果在 main 方法直接抛出 (throws)异常,就会让 JVM 去处理,增加了 JVM 的负担,所以不建议使用。
经验分享若父类中被重写的方法没有抛出异常时,则子类中重写的方法只能进行异常的捕获处理。若一个方法内部又以递进方式分别调用了好几个其它方法,则建议这些方法内可以使用抛出的方法处理到最后一层进行捕获方式处理。
自定义异常
基本概念当需要在程序中表达年龄不合理的情况时,而 Java 官方又没有提供这种针对性的异常,此时就需要程序员自定义异常加以描述。
实现流程
自定义xxxException
异常类继承Exception
类或者其子类。
提供两个版本的构造方法,一个是无参构造方法,另外一个是字符串作为参数的构造方法。
异常的产生throw new 异常类型(实参);
如:
throw new AgeException("年龄异常");
Java 采用的异常处理机制是将异常处理的程序代码集中在一起,与正常的程序代码分开,使得程序简洁、优雅,并易于维护。
File类
基本概念
java.io.File
类主要用于描述文件或目录路径的抽象表示信息,可以获取文件或目录的特征信息,如:大小等。
绝对路径:主要以根目录开始的路径信息,如,c:/
,d:/
,/..
。相对路径:主要以当前目录所在位置开始的路径信息,如./
,../
。
常用的方法
IO 流
IO 流的概念
IO 就是 Input 和 Output 的简写,也就是输入和输出的含义。
IO 流就是指读写数据时像流水一样从一端流到另外一端,因此得名为“流"。
基本分类
按照读写数据的基本单位不同,分为 字节流 和 字符流。
其中字节流主要指以字节为单位进行数据读写的流,可以读写任意类型的文件。
其中字符流主要指以字符 (2 个字节) 为单位进行数据读写的流,只能读写文本文件。
按照读写数据的方向不同,分为 输入流 和 输出流(站在程序的角度)。
其中输入流主要指从文件中读取数据内容输入到程序中,也就是读文件。
其中输出流主要指将程序中的数据内容输出到文件中,也就是写文件。
按照流的角色不同分为节点流和处理流。
其中节点流主要指直接和输入输出源对接的流。
其中处理流主要指需要建立在节点流的基础之上的流。
体系结构
相关流的详解
FileWriter 类
基本概念
java.io.FileWriter
类主要用于将文本内容写入到文本文件。
常用的方法
FileReader 类
基本概念
java.io.FileReader
类主要用于从文本文件读取文本数据内容。
常用的方法
FileOutputStream 类
基本概念
java.io.FileOutputStream
类主要用于将图像数据之类的原始字节流写入到输出流中。
常用的方法
FileInputStream 类
基本概念
java.io.FileInputStream
类主要用于从输入流中以字节流的方式读取图像数据等。
常用的方法
BufferedOutputStream 类
基本概念
java.io.BufferedOutputStream
类主要用于描述缓冲输出流,此时不用为写入的每个字节调用底层系统。
常用的方法
BufferedInputStream 类
基本概念
java.io.BufferedInputStream
类主要用于描述缓冲输入流。
常用的方法
BufferedWriter 类
基本概念
java.io.BufferedWriter
类主要用于写入单个字符、字符数组以及字符串到输出流中。
常用的方法
BufferedReader 类
基本概念
java.io.BufferedReader
类用于从输入流中读取单个字符、字符数组以及字符串。
常用的方法
PrintStream 类
基本概念
java.io.PrintStream
类主要用于更加方便地打印各种数据内容。
PrintStream
永远不会抛出 IOException 异常。出现异常情况会在内部设置标识,通过checkError()
获取此标识。
常用的方法
PrintWriter 类
基本概念
java.io.PrintWriter
类主要用于将对象的格式化形式打印到文本输出流。
PrintWriter
可以正常的存储中文,减少乱码输出。
常用的方法
OutputStreamWriter 类
基本概念
java.io.OutputStreamWriter
类主要用于实现从字符流到字节流的转换。可以指定编码格式。
常用的方法
InputStreamReader 类
基本概念
java.io.InputStreamReader
类主要用于实现从字节流到字符流的转换。可以指定编码格式。
常用的方法
字符编码
编码表的由来
计算机只能识别二进制数据,早期就是电信号。为了方便计算机可以识别各个国家的文字,就需要将各个国家的文字采用数字编号的方式进行描述并建立对应的关系表,该表就叫做编码表。
常见的编码表
ASCII:美国标准信息交换码, 使用一个字节的低 7 位二位进制进行表示。
ISO8859-1:拉丁码表,欧洲码表,使用一个字节的 8 位二进制进行表示。
GB2312:中国的中文编码表,最多使用两个字节 16 位二进制为进行表示。
GBK:中国的中文编码表升级,融合了更多的中文文字符号,最多使用两个字节 16 位二进制位表示。
Unicode:国际标准码,融合了目前人类使用的所有字符,为每个字符分配唯一的字符码。所有的文字都用两个字节 16 位二进制位来表示。
编码的发展
面向传输的众多 UTF(UCS Transfer Format)标准出现了,UTF-8 就是每次 8 个位传输数据,而 UTF-16 就是每次 16 个位。这是为传输而设计的编码并使编码无国界,这样就可以显示全世界上所有文化的字符了。
Unicode 只是定义了一个庞大的、全球通用的字符集,并为每个字符规定了唯一确定的编号,具体存储成什么样的字节流,取决于字符编码方案。推荐的 Unicode 编码是 UTF-8 和 UTF-16。
UTF-8:变长的编码方式,可用 1 - 4 个字节来表示一个字符。
DataOutputStream 类
基本概念
java.io.DataOutputStream
类主要用于以适当的方式将基本数据类型写入输出流中。
常用的方法
DataInputStream 类
基本概念
java.io.DataInputStream
类主要用于从输入流中读取基本数据类型的数据。
常用的方法
ObjectOutputStream 类
基本概念
java.io.ObjectOutputStream
类主要用于将一个对象的所有内容整体写入到输出流中。
只能将支持java.io.Serializable
接口的对象写入流中。
类通过实现java.io.Serializable
接口以启用其序列化功能。
所谓序列化主要指将一个对象需要存储的相关信息有效组织成字节序列的转化过程。
常用的方法
ObjectInputStream 类
基本概念
java.io.ObjectInputStream
类主要用于从输入流中一次性将对象整体读取出来。
所谓反序列化主要指将有效组织的字节序列恢复为一个对象及相关信息的转化过程。
常用的方法
序列化版本号
序列化机制是通过在运行时判断类的serialVersionUID
来验证版本一致性的。在进行反序列化时,JVM会把传来的字节流中的serialVersionUID
与本地相应实体类的serialVersionUID
进行比较,如果相同就认为是一致的,可以进行反序列化,否则就会出现序列化版本不一致的异常 (InvalidCastException
)。
transient 关键字
transient
是 Java 语言的关键字,用来表示一个域不是该对象串行化的一部分。当一个对象被串行化的时候,transient
型变量的值不包括在串行化的表示中,然而非transient
型的变量是被包括进去的。
经验的分享
当希望将多个对象写入文件时,通常建议将多个对象放入一个集合中,然后将集合这个整体看做一个对象写入输出流中,此时只需要调用一次readObject
方法就可以将整个集合的数据读取出来,从而避免了通过返回值进行是否达到文件末尾的判断。
RandomAccessFile 类
基本概念
java.io.RandomAccessFile
类主要支持对随机访问文件的读写操作。
常用的方法
多线程
基本概念
程序和进程的概念
程序 = 数据结构 + 算法,主要指存放在硬盘上的可执行文件。
进程 - 主要指运行在内存中的可执行文件。
目前主流的操作系统都支持多进程,为了让操作系统同时可以执行多个任务,但进程是重量的,也就是新建一个进程会消耗 CPU 和内存空间等系统资源,因此进程的数量比较局限。
线程的概念
为了解决上述问题就提出线程的概念,线程就是进程内部的程序流,也就是说操作系统内部支持多进程的,而每个进程的内部又是支持多线程的,线程是轻量的,新建线程会共享所在进程的系统资源,因此目前主流的开发都是采用多线程。
多线程是采用时间片轮转法来保证多个线程的并发执行,所谓并发就是指宏观并行微观串行的机制。
线程的创建
Thread类的概念
java.lang.Thread
类代表线程,任何线程对象都是Thread
类(子类)的实例。
Thread
类是线程的模板,封装了复杂的线程开启等操作,封装了操作系统的差异性。
创建方式
自定义类继承Thread
类并重写run
方法,然后创建该类的对象调用start
方法。
自定义类实现Runnable
接口并重写run
方法,创建该类的对象作为实参来构造Thread
类型的对象,然后使用Thread
类型的对象调用start
方法。
相关的方法
执行流程
执行main
方法的线程叫做主线程,执行run
方法的线程叫做新线程/子线程。
main
方法是程序的入口,对于start
方法之前的代码来说,由主线程执行一次,当start
方法调用成功后线程的个数由 1 个变成了 2 个,新启动的线程去执行run
方法的代码,主线程继续向下执行,两个线程各自独立运行互不影响。
当run
方法执行完毕后子线程结束,当main
方法执行完毕后主线程结束。
两个线程执行没有明确的先后执行次序,由操作系统调度算法来决定。
方式的比较 -- 继承Thread
类 和 实现Runnable
接口
继承Thread
类的方式代码简单,但是若该类继承Thread
类后则无法继承其它类,而实现Runnable
接口的方式代码复杂,但不影响该类继承其它类以及实现其它接口,因此以后的开发中推荐使用第二种方式。
匿名内部类的方式
使用继承加匿名内部类的方式创建并启动线程。
new Thread() {
@Override
public void run() {
...
}
}.start();
使用实现接口加匿名内部类的方式创建并启动线程。
new Thread(new Runnable() {
@Override
public void run() {
...
}
}).start();
// Lambda 表达式
new Thread(()-> System.out.println(“hello”)).start();
线程的生命周期
新建状态 - 使用new
关键字创建之后进入的状态,此时线程并没有开始执行。
就绪状态 - 调用start
方法后进入的状态,此时线程还是没有开始执行。
运行状态 - 使用线程调度器调用该线程后进入的状态,此时线程开始执行,当线程的时间片执行完毕后任务没有完成时回到就绪状态。
消亡状态 - 当线程的任务执行完成后进入的状态,此时线程已经终止。
阻塞状态 - 当线程执行的过程中发生了阻塞事件进入的状态,如:sleep
方法。阻塞状态解除后进入就绪状态。
线程的编号和名称
常用的方法
线程同步机制
基本概念
当多个线程同时访问同一种共享资源时,可能会造成数据的覆盖等不一致性问题,此时就需要对线程之间进行通信和协调,该机制就叫做线程的同步机制。
多个线程并发读写同一个临界资源时会发生线程并发安全问题。
异步操作: 多线程并发的操作,各自独立运行。
同步操作: 多线程串行的操作,先后执行的顺序。
解决方案
由程序结果可知:当两个线程同时对同一个账户进行取款时,导致最终的账户余额不合理。
引发原因:线程一执行取款时还没来得及将取款后的余额写入后台,线程二就已经开始取款。
解决方案:让线程一执行完毕取款操作后,再让线程二执行即可,将线程的并发操作改为串行操作。
经验分享:在以后的开发尽量减少串行操作的范围,从而提高效率。
实现方式
在 Java 语言中使用synchronized
关键字来实现同步/对象锁机制从而保证线程执行的原子性。
如果多线程里面synchronized(object)
的object
不是同一个对象,则锁不住。
使用同步代码块的方式实现部分代码的锁定,格式如下:
synchronized(类类型的引用) {
编写所有需要锁定的代码;
}
使用同步方法的方式实现所有代码的锁定,直接使用 synchronized 关键字来修饰整个方法即可,该方式等价于:
synchronized(this) {
整个方法体的代码;
}
静态方法的锁定
当我们对一个静态方法加锁,如:
public synchronized static void xxx(){ ... }
那么该方法锁的对象是类对象。每个类都有唯一的一个类对象。获取类对象的方式:类名.class
。
静态方法与非静态方法同时使用了synchronized
后它们之间是非互斥关系的。
原因在于:”静态方法“锁的是类对象,而”非静态方法“锁的是当前方法所属对象。
注意事项
使用synchronized
保证线程同步应当注意
多个需要同步的线程在访问同步块时,看到的应该是同一个锁对象引用。
在使用同步块时应当尽量减少同步范围以提高并发的执行效率。
线程安全类和不安全类
StringBuffer
类是线程安全的类,但StringBuilder
类不是线程安全的类。
Vector
类和Hashtable
类是线程安全的类,但ArrayList
类和HashMap
类不是线程安全的类。
Collections.synchronizedList()
和Collections.synchronizedMap()
等方法实现安全。
死锁的概念
线程一执行的代码:
public void run(){
synchronized(a){ // 持有对象锁a,等待对象锁b
synchronized(b){
编写锁定的代码;
}
}
}
线程二执行的代码:
public void run(){
synchronized(b){ // 持有对象锁b,等待对象锁a
synchronized(a){
编写锁定的代码;
}
}
}
注意:在以后的开发中尽量减少同步的资源,减少同步代码块的嵌套结构的使用!
使用Lock
(锁)实现线程同步
基本概念
从 Java 5 开始提供了更强大的线程同步机制—使用显式定义的同步锁对象来实现。
java.util.concurrent.locks.Lock
接口是控制多个线程对共享资源进行访问的工具。
该接口的主要实现类是ReentrantLock
类,该类拥有与synchronized
相同的并发性,在以后的线程安全控制中,经常使用ReentrantLock
类显式加锁和释放锁。
常用的方法
与synchronized
方式的比较
Lock
是显式锁,需要手动实现开启和关闭操作,而synchronized
是隐式锁,执行锁定代码后自动释放。
Lock
只有同步代码块方式的锁,而synchronized
有同步代码块方式和同步方法两种锁。
使用Lock
锁方式时,Java 虚拟机将花费较少的时间来调度线程,因此性能更好。
Object
类常用的方法
生产者和消费者模型
合成复用原则 -- Composite Reuse Principle,CRP。它要求在软件复用时,要尽量先使用组合或者聚合等关联关系来实现,其次才考虑使用继承关系来实现。
消费者和生产者都声明一个仓库类型的引用作为成员变量,从而能调用调用仓库类中的生产方法或消费方法。确保了两个线程共用同一个仓库。
代码
public class ProducerThread ... {
private Warehouse warehouse; // 仓库类型的引用
public ProduceThread(Warehouse warehouse) {
this.warehouse = warehouse;
}
... // Override run() to produce product
}
public class ConsumerThread ... {
private Warehouse warehouse; // 仓库类型的引用
public ConsumerThread(Warehouse warehouse) {
this.warehouse = warehouse;
}
... // Override run() to consume product
}
public class Warehouse {
// 生产方法
public synchronized void produceProduct() {
...
}
// 消费方法
public synchronized void consumeProduct() {
...
}
}
线程池
实现Callable接口
从 Java 5 开始新增加创建线程的第三种方式为实现java.util.concurrent.Callable
接口。
常用的方法如下
FutureTask类
java.util.concurrent.FutureTask
类用于描述可取消的异步计算,该类提供了Future
接口的基本实现,包括启动和取消计算、查询计算是否完成以及检索计算结果的方法,也可以用于获取方法调用后的返回结果。
常用的方法如下:
代码
public class MyCallable implements Callable {
@Override
public Object call() throws Exception {
int sum = 0;
for (int i = 1; i <= 10; i++) {
sum +=i;
}
System.out.println("sum: " + sum);
return sum;
}
public static void main(String[] args) {
MyCallable mc = new MyCallable();
FutureTask ft = new FutureTask(mc);
Thread t1 = new Thread(ft);
t1.start();
Object obj = null;
try {
obj = ft.get();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
System.out.println("Thread return value is: " + obj);
}
}
线程池的由来
在服务器编程模型的原理,每一个客户端连接用一个单独的线程为之服务,当与客户端的会话结束时,线程也就结束了,即每来一个客户端连接,服务器端就要创建一个新线程。
如果访问服务器的客户端很多,那么服务器要不断地创建和销毁线程,这将严重影响服务器的性能。
概念和原理
线程池的概念:首先创建一些线程,它们的集合称为线程池,当服务器接受到一个客户请求后,就从线程池中取出一个空闲的线程为之服务,服务完后不关闭该线程,而是将该线程还回到线程池中。
在线程池的编程模式下,任务是提交给整个线程池,而不是直接交给某个线程,线程池在拿到任务后,它就在内部找有无空闲的线程,再把任务交给内部某个空闲的线程,任务是提交给整个线程池,一个线程同时只能执行一个任务,但可以同时向一个线程池提交多个任务。
相关类和方法
从 Java 5 开始提供了线程池的相关类和接口:java.util.concurrent.Executors
类和java.util.concurrent.ExecutorService
接口。
其中Executors
是个工具类和线程池的工厂类,可以创建并返回不同类型的线程池,常用方法如下:
其中ExecutorService
接口是真正的线程池接口,主要实现类是ThreadPoolExecutor
,常用方法如下:
代码
ExecutorService executorService = Executors.newFixedThreadPool(10);
Future future = executorService.submit(new MyCallable());
executorService.shutdown();
Object returnValue = future.get();
网络编程
网络编程的常识
七层网络模型
OSI(Open System Interconnect),即开放式系统互联,是 ISO(国际标准化组织)组织在 1985 年研究的网络互连模型。
OSI七层模型和TCP/IP五层模型的划分如下:
当发送数据时,需要对发送的内容按照上述七层模型进行层层加包后发送出去。
当接收数据时,需要对接收的内容按照上述七层模型相反的次序层层拆包并显示出来。
相关的协议
协议的概念
计算机在网络中实现通信就必须有一些约定或者规则,这种约定和规则就叫做通信协议,通信协议可以对速率、传输代码、代码结构、传输控制步骤、出错控制等制定统一的标准。
TCP 协议
传输控制协议 (Transmission Control Protocol),是一种面向连接的协议,类似于打电话。
建立连接 => 进行通信 => 断开连接
在传输前采用"三次握手"方式。
在通信的整个过程中全程保持连接,形成数据传输通道。
保证了数据传输的可靠性和有序性。
是一种全双工的字节流通信方式,可以进行大数据量的传输。
传输完毕后需要释放已建立的连接,发送数据的效率比较低。使用四次挥手断开链接。
UDP 协议
用户数据报协议 (User Datagram Protocol),是一种非面向连接的协议,类似于写信。
在通信的整个过程中不需要保持连接,其实是不需要建立连接。
不保证数据传输的可靠性和有序性。
是一种全双工的数据报通信方式,每个数据报的大小限制在 64 K 内。
发送数据完毕后无需释放资源,开销小,发送数据的效率比较高,速度快。
IP 地址
192.168.1.1
- 是绝大多数路由器的登录地址,主要配置用户名和密码以及 MAC 过滤。
IP 地址是互联网中的唯一地址标识,本质上是由 32 位二进制组成的整数,叫做 IPv4,当然也有 128 位二进制组成的整数,叫做 IPv6,目前主流的还是 IPv4。
日常生活中采用点分十进制表示法来进行 IP 地址的描述,将每个字节的二进制转化为一个十进制整数,不同的整数之间采用小数点隔开。
如:0x0102030a => 1.2.3.10
查看IP地址的方式
Windows 系统:在 DOS 窗口中使用ipconfig
或ipconfig/all
命令即可
Unix/linux 系统:在终端窗口中使用ifconfig
或/sbin/ifconfig
命令即可
特殊的地址
本地回环地址 (hostAddress):127.0.0.1
主机名(hostName):localhost
端口号
IP 地址 - 可以定位到具体某一台设备。
端口号 - 可以定位到该设备中具体某一个进程。
端口号本质上是 16 位二进制组成的整数,表示范围是:0 ~ 65535
,其中0 ~ 1024
之间的端口号通
常被系统占用,建议编程从1025
开始使用。
特殊的端口:
HTTP:80
FTP:21
Oracle:1521
MySQL:3306
Tomcat:8080
网络编程需要提供:IP地址 + 端口号,组合在一起叫做网络套接字:Socket。
基于 tcp 协议的编程模型
C/S 架构的简介
在 C/S 模式下客户向服务器发出服务请求,服务器接收请求后提供服务。
例如:在一个酒店中,顾客找服务员点菜,服务员把点菜单通知厨师,厨师按点菜单做好菜后让服务员端给客户,这就是一种 C/S 工作方式。如果把酒店看作一个系统,服务员就是客户端,厨师就是服务器。这种系统分工和协同工作的方式就是 C/S 的工作方式。
客户端部分:为每个用户所专有的,负责执行前台功能。
服务器部分:由多个用户共享的信息与功能,招待后台服务。
编程模型
服务器:
(1)创建ServerSocket
类型的对象并提供端口号;
(2)等待客户端的连接请求,调用accept()
方法;
(3)使用输入输出流进行通信;
(4)关闭Socket
;
客户端:
(1)创建Socket
类型的对象并提供服务器的 IP 地址和端口号;
(2)使用输入输出流进行通信;
(3)关闭Socket
;
相关类和方法的解析
ServerSocket 类
.ServerSocket
类主要用于描述服务器套接字信息(大插排)。
常用的方法如下
Socket 类
.Socket
类主要用于描述客户端套接字,是两台机器间通信的端点(小插排)。
常用的方法如下
注意事项
客户端Socket
与服务器端Socket
对应, 都包含输入和输出流。
客户端的socket.getInputStream()
连接于服务器socket.getOutputStream()
。
客户端的socket.getOutputStream()
连接于服务器socket.getInputStream()
。
基于 udp 协议的编程模型
编程模型
接收方:
(1)创建DatagramSocket
类型的对象并提供端口号;
(2)创建DatagramPacket
类型的对象并提供缓冲区;
(3)通过Socket
接收数据内容存放到Packet
中,调用receive
方法;
(4)关闭Socket
;
发送方:
(1)创建DatagramSocket
类型的对象;
(2)创建DatagramPacket
类型的对象并提供接收方的通信地址;
(3)通过Socket
将Packet
中的数据内容发送出去,调用send
方法;
(4)关闭Socket
;
相关类和方法的解析
DatagramSocket 类
.DatagramSocket
类主要用于描述发送和接收数据报的套接字(邮局)。换句话说,该类就是包裹投递服务的发送或接收点。
常用的方法如下
DatagramPacket 类
.DatagramPacket
类主要用于描述数据报,数据报用来实现无连接包裹投递服务。
常用的方法如下
InetAddress 类
.InetAddress
类主要用于描述互联网通信地址信息。
常用的方法如下
URL 类
基本概念
.URL
(Uniform Resource Identifier)类主要用于表示统一的资源定位器,也就是指向万维网上“资源”的指针。这个资源可以是简单的文件或目录,也可以是对复杂对象的引用,例如对数据库或搜索引擎的查询等。
通过 URL 可以访问万维网上的网络资源,最常见的就是 www 和 ftp 站点,浏览器通过解析给定的 URL 可以在网络上查找相应的资源。
URL 的基本结构如下:<传输协议>://<主机名>:<端口号>/<资源地址>
常用的方法
URLConnection 类
基本概念
.URLConnection
类是个抽象类,该类表示应用程序和 URL 之间的通信链接的所有类的超类,主要实现类有支持 HTTP 特有功能的HttpURLConnection
类。
HttpURLConnection 类的常用方法
反射机制
基本概念
通常情况下编写代码都是固定的,无论运行多少次执行的结果也是固定的,在某些特殊场合中编写代码时不确定要创建什么类型的对象,也不确定要调用什么样的方法,这些都希望通过运行时传递的参数来决定,该机制叫做动态编程技术,也就是反射机制。
通俗来说,反射机制就是用于动态创建对象并且动态调用方法的机制。
目前主流的框架底层都是采用反射机制实现的。
如:
Person p = new Person(); // 表示声明 Person 类型的引用指向 Person 类型的对象
p.show(); // 表示调用 Person 类中的成员方法 show
Class 类
基本概念
java.lang.Class
类的实例可以用于描述 Java 应用程序中的类和接口,是一种数据类型。
该类没有公共构造方法,该类的实例由 Java 虚拟机和类加载器自动构造完成,本质上就是加载到内存中的运行时类。
获取 Class 对象的方式
使用数据类型.class
的方式可以获取对应类型的Class
对象(掌握)。
Class c1 = String.class;
System.out.println("c1 = " + c1); // class java.lang.String
c1 = Integer.class;
System.out.println("c1 = " + c1); // class java.lang.Integer
c1 = int.class;
System.out.println("c1 = " + c1); // int
c1 = void.class;
System.out.println("c1 = " + c1); // void
使用引用/对象.getClass()
的方式可以获取对应类型的Class
对象。基本数据类型不能调用该方法。
String str1 = new String("hello");
c1 = str1.getClass();
System.out.println("c1 = " + c1); // class java.lang.String
Integer it1 = 20;
c1 = it1.getClass();
System.out.println("c1 = " + c1); // class java.lang.Integer
使用包装类.TYPE
的方式可以获取对应基本数据类型的Class
对象。
c1 = Integer.TYPE;
System.out.println("c1 = " + c1); // int
使用Class.forName()
的方式来获取参数指定类型的Class
对象(掌握)。
c1 = Class.forName("java.lang.String");
System.out.println("c1 = " + c1); // class java.lang.String
c1 = Class.forName("java.util.Date");
System.out.println("c1 = " + c1); // class java.util.Date
使用类加载器ClassLoader
的方式获取指定类型的Class
对象。
ClassLoader classLoader = ClassTest.class.getClassLoader();
System.out.println("classLoader = " + classLoader); // null
c1 = classLoader.loadClass("java.lang.String");
System.out.println("c1 = " + c1); // class java.lang.String
常用的方法
Constructor 类
基本概念
java.lang.reflect.Constructor
类主要用于描述获取到的构造方法信息
Class 类的常用方法
Constructor 类的常用方法
Field 类
基本概念
java.lang.reflect.Field
类主要用于描述获取到的单个成员变量信息。
Class 类的常用方法
Field 类的常用方法
Method 类
基本概念
java.lang.reflect.Method
类主要用于描述获取到的单个成员方法信息。
Class类的常用方法
Method类的常用方法
获取其它结构信息