JavaEE学习(二)Java高级特性

本文是学习JavaEE开发的记录笔记

大纲

Java数据结构

Java工具包提供的数据结构有

但这些类设计较落后,缺少一个核心、统一的主题

Java集合框架(java.util)

Java集合框架提供一组围绕统一标准接口设计,性能优良,使用方便的接口和类,可以直接使用这些接口的标准实现LinkedList、HashSet、TreeSet等类,也可以通过这些接口实现自己的集合
集合框架包含内容:

  • 接口:代表集合的抽象数据类型
  • 实现(类):集合接口的实现,具体来讲是可重复使用的数据结构,如ArrayList,LinkedList、HashSet、TreeSet等
  • 算法:实现集合接口的对象里的有用的计算,如搜索和排序,是多态的,相同的方法可以在相似的接口上有不同的实现

Java泛型(Generics)

Java集合框架可以容纳任何类型的对象是通过泛型机制完成的
泛型类:泛型类的类型参数声明部分也包含一个或多个类型参数,参数间用逗号隔开,如

1
ArrayList<String> list = new ArrayList<String>(); 

泛型方法:方法在调用时可以接收不同类型的参数,根据传递给泛型方法的参数类型,编译器适当地处理每一个方法调用,声明泛型方法用尖括号+变量名组成,参数间用逗号隔开,声明位置在方法返回类型之前,如
1
2
3
4
5
6
public static <E> void printArray(E[] inputArray)
{
for(E val:inputArray){
System.out.printf("%s",val);
}
}

类型通配符:当方法需要接受泛型类作为参数,可用<?>代替对象的泛型,如
1
2
3
public static void getData(List<?> data) {
System.out.println("data :" + data.get(0));
}

类型通配符上限/下限:通过<? extends T>示该通配符所代表的类型是T类型的子类(T类为上限),通过List<? super T>表示该通配符所代表的类型是T类型的父类(T类为下限)

Java序列化

Java提供一种对象序列化机制,可以将对象表示为字节序列,存入文件中保存,并且可以在文件中读出进行反序列化在内存中新建对象
对象序列化条件:实现java.io.Serializable接口+类所有属性是可序列化的(transient关键字修饰的属性是不参与序列化的)
序列化对象:

1
2
3
4
5
6
7
8
9
10
//声明并创建对象
type objectName=new type();
//序列化对象成字节序列存储文件
FileOutputStream fileOut =new FileOutputStream("/tmp/class.ser");
ObjectOutputStream out = new ObjectOutputStream(fileOut);
//序列化名为objectName的对象
out.writeObject(objectName);
//关闭序列化对象和文件流
out.close();
fileOut.close();

反序列化对象:
1
2
3
4
5
6
7
8
9
10
//声明存储对象的引用
type objectName=null;
//读取字节序列文件并解析成对象
FileInputStream fileIn = new FileInputStream("/tmp/employee.ser");
ObjectInputStream in = new ObjectInputStream(fileIn);
//存放在引用中
objectName=(type)in.readObject();
//关闭序列化对象和文件流
in.close();
fileIn.close();

Java网络编程

java.net包提供低层次的的通信细节,提供两种常见的网络协议支持(TCP+UDP),提供Socket编程和URL处理

Socket编程:

  • Socket:套接字使用TCP提供计算机网络之间的通信机制,客户端程序创建一个套接字连接服务器的套接字,连接建立后,服务器会创建Socket对象,客户端和服务器通过对Socket对象的写入和读取来进行通信
  • Socket通信过程:
  1. 服务器实例化一个java.net.ServerSocket对象,表示通过服务器上的端口通信
  2. 服务器调用ServerSocket对象的accept()方法监听客户端连接到服务器给定端口
  3. 客户端实例化一个java.net.Socket对象,指定服务器名称和端口号请求连接
  4. Socket类的构造函数试图将客户端连接到服务器给定端口号,若通信建立,则在客户端创建一个Socket对象与服务器进行通信
  5. accept()方法返回服务器上一个新的socket引用,该socket连接到客户端的socket
  6. 通过I/O流进行通信,每个socket有输入流和输出流,客户端输出流连接到服务器输入流,客户端输入流连接到服务器输出流
  • ServerSocket类:服务器使用ServerSocket类获取一个端口并监听客户端请求
    GreetingServer.java
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    import java.net.*;
    import java.io.*;

    //继承Thread类重写run()方法进行线程开发
    public class GreetingServer extends Thread
    {
    //声明ServerSocket类对象serverSocket
    private ServerSocket serverSocket;

    //构造函数 输入端口号+设置过期时间
    public GreetingServer(int port) throws IOException
    {
    serverSocket = new ServerSocket(port);
    serverSocket.setSoTimeout(10000);
    }

    //重写run()方法
    public void run()
    {
    while(true){
    try{
    //在线程中等待连接 阻塞线程
    System.out.println("等待远程连接...");
    System.out.println("连接成功!端口号为:"+serverSocket.getLocalPort());

    //accept()得到连接成功后的socket引用
    Socket server = serverSocket.accept();
    System.out.println("远程主机地址:"+server.getRemoteSocketAddress());

    //通过getOutputStream和getInputStream获取相应的输入输出流,进行相应的I/O操作
    DataInputStream in = new DataInputStream(server.getInputStream());
    System.out.println("接受客户端消息:"+in.readUTF());
    DataOutputStream out = new DataOutputStream(server.getOutputStream());
    out.writeUTF("Hello! From"+server.getLocalSocketAddress());

    //关闭连接 不会立即释放端口 tcp连接中的time-wait现象
    server.close();
    }catch(SocketTimeoutException s){
    System.out.println("Socket Timed Out!");
    break;
    }catch(IOException e){
    e.printStackTrace();
    break;
    }
    }
    }

    //主函数入口
    public static void main(String [] args)
    {
    //获取监听的端口号
    int port = Integer.parseInt(args[0]);
    try{
    //实例化GreetingServer类,通过构造函数创建ServerSocket类对象
    Thread t = new GreetingServer(port);
    //run()开启线程 进行监听端口
    t.run();
    }catch(IOException e){
    e.printStackTrace();
    }
    }
    }

关键点:

  1. 使用线程:ServerSocket通过使用accept方法进行客户端请求的处理,每当请求队列里有客户端请求时,serversocket就会从队列顶端取一个socket请求进行处理,生成一个socket来负责与客户端通信。如果一个时间只能处理一个socket,当有多个客户端请求时,则必须要排队处理,等待所有前面的socket处理完,这是个不合理的过程。因此,引入了线程的概念。
  2. 构造函数:
    2.1 ServerSocket()无参构造函数,必须使用bind方法进行端口绑定,好处在于绑定前可以设置相应属性,如so_reuseaddress等
    2.2 ServerSocket(int port)需要一个端口号(1024-65535),0-1023属于系统预留端口
    2.3 ServerSocket(int port, int backlog)需要端口号+backlog(监听对列大小),设置连接请求数的最大值,若超过这个值则会报ConnectException异常,若无设置则系统默认
    2.4 ServerSocket(int port, int backlog, InetAddress bindAddr)需要端口号+监听队列大小+ip地址,可以进行相应服务器ip地址绑定,服务器存在多个网卡时可以使用这个参数设定客户端访问的ip
  • Socket类:java.net.Socket类代表客户端和服务器都用来互相沟通的套接字,客户端要获取一个Socket对象通过实例化,而服务器获得一个Socket对象则通过accept()方法的返回值

    GreetingClient.java
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    //客户端实例化Socket对象
    import java.net.*;
    import java.io.*;

    public class GreetingClient
    {
    public static void main(String [] args)
    {
    //传入第一个参数作为连接服务器主机名
    String serverName = args[0];
    //传入第二个参数作为连接服务器端口号
    int port = Integer.parseInt(args[1]);
    try
    {
    System.out.println("连接到主机:" + serverName + " 端口号:" + port);
    //实例化Socket类得到Socket对象
    Socket client = new Socket(serverName, port);

    //连接成功打印相关信息
    System.out.println("远程主机地址:"+client.getRemoteSocketAddress());

    //通过getOutputStream和getInputStream获取相应的输入输出流,进行相应的I/O操作
    //发送消息
    OutputStream outToServer = client.getOutputStream();
    DataOutputStream out = new DataOutputStream(outToServer);
    out.writeUTF("Hello from " + client.getLocalSocketAddress());
    //接受消息
    InputStream inFromServer = client.getInputStream();
    DataInputStream in = new DataInputStream(inFromServer);
    System.out.println("接受服务器消息: " + in.readUTF());

    //关闭连接
    client.close();
    }catch(IOException e)
    {
    e.printStackTrace();
    }
    }
    }
  • 编译文件:

  • 执行GreetingServer监听端口号6666:
  • 执行GreetingClient访问本地主机localhost的端口6666:

Java多线程

线程的生命周期:

  • 新建状态:线程对象被new后,直到被调用start()启动
  • 就绪状态:线程对象被调用start()启动后,线程处于就绪队列中,等待JVM线程调度器的调度
  • 运行状态:就绪状态的线程获取到CPU资源,会自动执行run()进入运行状态
  • 阻塞状态:运行的线程让出CPU资源进入阻塞状态,等到恢复后可以重新进入就绪状态
    • 等待阻塞:运行的线程执行wait()方法
    • 同步阻塞:运行的线程获取synchronized同步锁失败
    • 其他阻塞:运行的线程执行sleep()、join()或者发起IO请求
  • 死亡状态:运行的线程完成任务或被终止

创建线程的三种方法:

  • 继承Thread类:创建线程类继承Thread类,重写run方法,创建线程类对象,调用start()方法启动线程

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    //继承Thread类
    public class MyThread extends Thread{
    //重写run方法
    public void run(){
    //线程执行体
    }

    //主函数入口
    public static void main(String [] args){
    //创建线程
    MyThread myThread=new MyThread();
    //启动线程
    myThread.start();
    }
    }
  • 实现Runnable接口:创建线程类实现Runnable接口,重写run方法,创建线程类对象,使用线程对象作为Thread对象的构建参数创建Thread对象,调用Thread对象的start()方法启动线程

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    //实现Runnable接口
    public class MyThread implements Runnable{
    //重写run方法
    public void run(){
    //线程执行体
    }

    //主函数入口
    public static void main(String [] args){
    //创建线程对象
    MyThread myThread=new MyThread();
    //根据线程对象创建Thread类对象
    Thread thread=new Thread(myThread);
    //启动线程
    thread.start();
    }
    }
  • 实现Callable接口和FutureTask对象:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    import java.util.concurrent.Callable;  
    import java.util.concurrent.ExecutionException;
    import java.util.concurrent.FutureTask;


    //实现Callable接口
    public class MyThread implements Callable<Integer>
    {
    //重写call方法 有返回值
    public Integer call() throws Exception
    {
    int i = 5;
    return i;
    }

    //主函数入口
    public static void main(String [] args){
    //创建线程对象
    MyThread myThread=new MyThread();

    //使用FutureTask类来包装Callable对象
    FutureTask<Integer> ft = new FutureTask<>(myThread);

    //根据FutureTask对象创建Thread类对象
    Thread thread=new Thread(ft);

    //启动线程
    thread.start();

    //获取线程的返回值
    ft.get();
    }
    }
  • 创建线程方式优缺点:

    • 实现接口方式:实现Runnable、Callable接口的方式创建多线程时,线程类只是实现了Runnable接口或Callable接口,还可以继承其他类
    • 继承Thread方式:继承Thread类的方式创建多线程时,编写简单访问当前线程方便,则无需使用Thread.currentThread()方法,直接使用this即可获得当前线程