Wiesler

导航

day8 网络编程, 简要复习, 容器与非容器组件(JFrame,JoptionPanel对话框), Bat批处理文件及命令, 对象的深浅克隆,内存泄漏, 懒汉单例设计模式(线程安全), 观察者设计模式, 反射, 工厂模式

摘要:

网络编程, 简要复习, 容器与非容器组件(JFrame,JoptionPanel对话框), Bat批处理文件及命令, 对象的深浅克隆,内存泄漏, 懒汉单例设计模式(线程安全), 观察者设计模式, 反射, 工厂模式

网络编程:主要用于解决计算机与计算机(手机, 平板等)之间的数据传输问题

网络编程:不需要基于HTML页面,可以达到数据传输,如QQ

网页编程:基于HTML页面的数据交互

 

网络编程:

Web方向:

Android方向:常用...


计算机网络:分布在不同地域的计算机通过外部连接设备(switch交换机,router路由器等)连接起来,达到消息互通,资源共享的效果就是计算机网络


网络通讯的三要素:

1 IP

2 端口号:用于标明该消息是给哪个应用程序处理的,只不过是一个标识符而已

3 协议,在路由和交换时会加一些header或trailer,也叫加帧,也有必要的解开的过程


IP地址:Ip地址的本质就是由32位二进制数组成的数据,每段8位,总共4段,每一段是从0~2^8也就是0~255

IP地址实际上:网络号 + 主机号

IP地址分类:

  A类:1段网络号+3段主机号  可以配置2^24台电脑,通常是政府部门使用

  B类:2段网络号+2段主机号  可以配置2^16台电脑,事业单位

  C类:3段网络号+1段主机号  可以配置2^8台电脑,私人使用

Java作为面向对象的语言,IP地址也作为类存在,是IP类

网络编程所有的类都在java.net包

InetAddress就是IP类

常用方法:

返回本机的InetAddress对象

  getLocalHost()  返回一个InetAddress对象,本机的IP地址,该方法会抛出UnknownHostException, 没装网卡就没有IP地址

获取别人机器的InetAddress对象

  getByName(String host) 给定主机名的情况下,返回对方的InetAddress地址对象

其实也是可以根据一个IP地址的字符串形式或者主机名生成一个InetAddress地址对象

InetAddress ip = InetAddress.getByName(“”)

System.out.println(InetAddress.getByName("192.168.2.8").getHostName());
System.out.println(InetAddress.getByName("think-PC").getHostName());

  getAllByName(String host)  返回一个InetAddress[]数组,可以接受IP地址字符串,也可以接受域名比如www.baidu.com

InetAddress[] arr = InetAddress.getAllByName(InetAddress.getLocalHost().getHostAddress());
InetAddress[] arr1 = InetAddress.getAllByName("www.baidu.com");
System.out.println(Arrays.toString(arr1));

得到ip地址对象后,用ip对象调用:

getHostAddress() 返回IP地址的字符串

//获取本机的IP地址对象
InetAddress ip = InetAddress.getLocalHost();
//返回本机的IP地址字符串
System.out.println(ip.getHostAddress());

getHostName() 返回主机名字符串

System.out.println(ip.getHostName());

 

域名会在DNS服务器上寻找对应的IP地址,有时候某个域名的访问量过大,会有多台服务器,多个IP,再将请求发送到后方的服务器集群。负载均衡技术

 

端口号是没有类来描述的

端口号的范围是0~65535个

从0~1023的端口已经被系统占用了

从1024~49151,系统松散绑定一些端口,有些没有被占用

所以可以使用1024~65535的端口号

网络通讯的协议:

UDP协议

将数据源和目的封装为数据包,面向无连接(不需要建立连接)

大小限制在64KB

因为无连接,所以不可靠,但是速度却很快

不分服务端和客户端,只有发送端和接收端

比如QQ的聊天(不论对方是否在线都可以发送信息)

游戏,比如CS,服务器不断地发送数据给每个用户,如果有用户无法处理完,那么就会出现卡顿

 

TCP协议:

面向链接,传输量很大,

三次握手机制建立连接,传输可靠,有客户端和服务端

通信前建立连接,效率较低

在Java中,网络通讯也称为socket(套接字, 插座)通讯,要求通讯的两台机器都必须要安装socket

不同的协议,有不同的socket

UDP协议下的socket:

  DatagramSocket(udp插座服务)发送和接收,构造方法

    DatagramSocket() 无参构造函数,负责发送

    DatagramSocket(int port)监听一个端口,负责接收

    方法有:

      send(DatagramPacket p)

      receive(DatagramPacket p)

DatagramPacket(数据包类)

  DatagramPacket(byte[] buf, int length, InetAddress address, int port) buf发送数据内容,length数据内容大小,address地址,port端口号,负责发送

  DatagramPacket(byte[] buf, int length) 负责接收

  DatagramPacket类里有个

    方法.getLength()获取数据包存储了几个字节,

    方法.getAddress() 获取接受的数据的InetAddress对象

发送端的使用步骤:

  1 建立udp服务(像码头发送货物,地址等信息写在包裹上)

  2 准备数据,把数据封装在数据包中,发送端的数据包要带上IP地址和端口号

  3 调用udp的服务,发送数据

  4 关闭资源(端口号) 

import java.io.*;
import java.net.*;
public class Demo1 {
   public static void main(String[] args) throws IOException {
       //建立udp服务
       DatagramSocket datagramSocket = new DatagramSocket();
       //准备数据,把数据封装到数据包
       String data = "Hello World";
       //创建数据包
       DatagramPacket packet = new DatagramPacket(data.getBytes(), data.getBytes().length, InetAddress.getLocalHost(), 9090);
       //调用udp服务,发送数据
       datagramSocket.send(packet);
       //关闭资源......释放占用的端口号
       datagramSocket.close();
   }
}

接收端的使用步骤

  1 建立udp的服务

  2 准备空的数据包,接收数据

  3 调用udp的服务接收数据

  4 关闭资源

import java.io.*;
import java.net.*;
public class Demo2 {
   //接收端
   public static void main(String[] args) throws IOException {
       //建立udp服务,并且监听一个端口
       DatagramSocket datagramSocket = new DatagramSocket(9090);
       //准备空的数据包用于存放数据
       byte[] buf = new byte[1024];
       DatagramPacket datagramPacket = new DatagramPacket(buf, buf.length);
       //调用udp的服务,接收数据
       datagramSocket.receive(datagramPacket);//数据存储到了buf数组中
       System.out.println("接收到的数据" + new String(buf, 0, datagramPacket.getLength()));
       //关闭资源
       datagramSocket.close();
   }
}

在这里receive是个阻塞型的方法,没有接收到数据包之前会一直等待,先启动接收端,再启动发送端

收发交流:

发送端

public static void main(String[] args) throws IOException {
   //发送端
   DatagramSocket datagramSocket = new DatagramSocket(9091);
   Scanner scanner  = new Scanner(System.in);
   String content = "";
   while (true) {
       System.out.println("请发言");
       content = scanner.next();
       if ("q".equals(content)) break;
       DatagramPacket datagramPacket = new DatagramPacket(content.getBytes(), content.getBytes().length, InetAddress.getLocalHost(), 9090);
       datagramSocket.send(datagramPacket);
       byte[] buf = new byte[1024];
       DatagramPacket datagramPacket1 = new DatagramPacket(buf, buf.length);
       System.out.println("请等待....");
       datagramSocket.receive(datagramPacket1);
       String ret = new String(buf, 0, datagramPacket1.getLength());
       if ("q".equals(ret)){
           System.out.println("对方中止了对话");
           break;
       }
       System.out.println("对方: " + new String(buf, 0, datagramPacket1.getLength()));
   }
   datagramSocket.close();
}
发送端

接收端

//接收端
public static void main(String[] args) throws IOException {
   Scanner scanner = new Scanner(System.in);
   DatagramSocket datagramSocket = new DatagramSocket(9090);
   while (true) {
       byte[] buf = new byte[1024];
       DatagramPacket datagramPacket = new DatagramPacket(buf, buf.length);
       System.out.println("请等待....");
       datagramSocket.receive(datagramPacket);
       String content = new String(buf, 0, datagramPacket.getLength());
       if ("q".equals(content)){
           System.out.println("对方中止了对话");
           break;
       }
       System.out.println("对方: " + content);
       System.out.println("请发言:");
       String content1 = scanner.next();
       DatagramPacket datagramPacket1 = new DatagramPacket(content1.getBytes(), content1.getBytes().length, InetAddress.getLocalHost(), 9091);
       datagramSocket.send(datagramPacket1);
       if ("q".equals(content1))break;
   }
   datagramSocket.close();
}
接收端

在用控制台作测试的时候,如果程序有package ...包名,那么必须要把程序包装在包名的文件夹中,然后Java 包名.class类,如果只写java class类名会报错找不到无法加载主类

每个网络程序都有自己所处理的特定格式数据,如果接受到的数据不符合指定格式,那么就会被当成垃圾数据丢弃(相当于作了加密,任何人不能仅仅通过ip和端口来操作某程序)

飞Q接受数据格式:

version:time:sender:ip:flag:content

版本      时间  发送人 ip 发送标识符  内容


在udp协议中,有一个ip地址,称为广播地址,广播地址就是主机号为255的地址

给广播ip地址发送消息的时候,在同一个网络段的主机都可以接收到信息

比如192.168.15.255


Udp不可靠通信协议,

什么情况下出现数据丢失:

  1 带宽不足

  2 CPU处理能力不足(CPU中的寄存器的容量非常小,发来的数据包如果高于CPU处理能力,寄存器会寄存一部分,但是寄存器容量非常小1KB)

 

TCP协议:

特点:

  1 tcp协议是基于IO流进行数据的传输的,是面向链接的

  2 tcp进行数据传输没有大小限制

  3 tcp面向连接的,通过三次握手保证数据完整性,可靠协议

  4 tcp是面向连接的所以速度慢

  5 tcp是区分客户端和服务端的

 

TCP协议下的socket有两个:

Serversocket服务端类

  ServerSocket(int port)

  方法有:

    accept()  接受连接,阻塞的方法,返回一个Socket对象

    这样服务端就可以用这个对象调用getInputStream()方法,继而读取数据

Socket客户端类

  Socket(InetAddress address, int port)

  方法有:

    getInputStream() 返回一个InputStream对象,用于读数据

    getOutputStream() 返回一个OutputStream对象,用于写数据

    Tcp的客户端一旦启动马上要与服务端建立链接

TCP客户端使用步骤:

  1 建立TCP的客户端服务

  2 获取到对应的流对象

  3 利用输出流对象写数据

  4 关闭资源

public static void main(String[] args) throws IOException {
   //建立TCP客户端
   Socket socket = new Socket(InetAddress.getLocalHost(), 9090);
   //获取到socket的输出流对象
   OutputStream outputStream = socket.getOutputStream();
   //利用输出流对象,写出数据
   String content = "Hello World";
   outputStream.write(content.getBytes());
   //获取到一个输入流对象,读取服务端回送的数据
   InputStream inputStream = socket.getInputStream();
   byte[] buf = new byte[1024];
   int length = 0;
   length = inputStream.read(buf);
   System.out.println(new String(buf, 0, length));
   //关闭资源
   socket.close();//关闭了socket也会自动关闭outputStream
}

TCP 服务端,先启动服务端等待客户端的链接

使用步骤

  1 建立ServerSocket服务端的对象

  2 接受客户端的链接请求,产生一个Socket对象

  3 获取对应的流对象,读取或者写出数据

  4 关闭资源

public static void main(String[] args) throws IOException {
   //TCP服务端, 并且监听一个端口
   ServerSocket serverSocket = new ServerSocket(9090);//不要重复执行服务端,会报出端口被占用的异常
   //接受客户端的链接
   Socket socket = serverSocket.accept();//该方法也是一个阻塞型的方法
   //获取输入流对象,读取客户端发送的内容
   InputStream inputStream = socket.getInputStream();
   byte[] buf = new byte[1024];
   int length = 0;
   length = inputStream.read(buf);
   System.out.println(new String(buf, 0, length));
   //获取socket输出流对象,向客户端发送数据
   OutputStream outputStream = socket.getOutputStream();
   outputStream.write("How are you doing today?".getBytes());
   serverSocket.close();
}

这里读取不要用 while ((length = inputStream.read(buf)) != -1){}这个是读文件时才会判断到文件末尾会传回-1

 

为什么ServerSocket不设计一个getInputStream与getOutputStream方法呢?

如果有多个用户连接服务端,那么针对不同的用户的链接,就用accept得到一个socket专门用于回复该用户。对不同的用户都可以针对性的回复。而不是自己有一个OutputStream,这样回复没有针对性。

需求:客户端与服务端一问一答地聊天

注意:

  1 如果使用BufferedReader的readLine方法一定要加上\r\n才把数据写出(因为服务端用readLine 方法读取的时候一定要看到\r\n才能停止录入,并且write数据的时候,是不会默认发送\r\n的)

  2 使用字符流一定到调用flush方法,数据才能写出

服务端:

public static void main(String[] args) throws IOException {
   //监听TCP服务端
   ServerSocket serverSocket = new ServerSocket(9090);
   //接受客户端的链接请求,产生一个socket
   System.out.println("服务端\r\n等待链接");
   Socket socket = serverSocket.accept();
   System.out.println("已链接");
   BufferedReader bufferedReader = new BufferedReader(new InputStreamReader (socket.getInputStream()));
   //获取键盘的输入流对象
   BufferedReader bufferedReader1 = new BufferedReader(new InputStreamReader(System.in));
   //获取socket的输出流对象
   OutputStreamWriter outputStreamWriter = new OutputStreamWriter(socket.getOutputStream());
   //读取客户端数据
   String line = null;
   while ((line = bufferedReader.readLine()) != null) {
       System.out.println("请等待...");
       System.out.println("客户端: " + line);
       System.out.println("请输入:");
       String line1 = bufferedReader1.readLine();
       outputStreamWriter.write(line1 + "\r\n");
       outputStreamWriter.flush();
   }
   //关闭资源
   serverSocket.close();
}
服务端

 

客户端:

public static void main(String[] args) throws IOException {
   //建立TCP客户端
   Socket socket = new Socket(InetAddress.getLocalHost(), 9090);
   //获取到socket的输出流对象, 并转换为字符流
   OutputStreamWriter socketOut = new OutputStreamWriter(socket.getOutputStream());
   //接受键盘录入
   BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(System.in));
   //获取socket的输入流对象
   BufferedReader bufferedReader1 = new BufferedReader(new InputStreamReader(socket.getInputStream()));
   String line = null;
   //不断取得键盘录入的数据,把数据写出
   System.out.println("客户端");
   while ((line = bufferedReader.readLine()) != null) {
       System.out.println("请输入");
       socketOut.write(line + "\r\n");//将\r\n发过去,因为服务端用readLine接收,如果没有收到\r\n就继续等待不会显示
       //必须要刷新一下,不然数据会都存到缓冲数组
       socketOut.flush();
       //读取服务端回送的数据
       System.out.println("请等待...");
       line = bufferedReader1.readLine();
       System.out.println("服务端: " + line);
   }
   socket.close();
}
客户端

接受键盘录入的新方法:

  BufferedReader bReader = new BufferedReader(new InputStreamReader(System.in))

  String username = bReader.readLine();

  String pwd = bReader.readLine();

以后可以不用Scanner了

 

浏览器与服务器就是通过tcp协议通讯的


模拟Tomcat服务器

public class Demo3 extends Thread {
   Socket socket;
   public Demo3 (Socket socket) {
       this.socket = socket;
   }
   public static void main(String[] args) throws IOException {
       //建立tcp服务端
       ServerSocket serverSocket = new ServerSocket(8080);
       //不断接受客户端
       while (true) {
           Socket socket = serverSocket.accept();
           new Demo3(socket).start();
       }
   }
   public void run () {
       try {
           //获取socket的输出流对象
           OutputStream outputStream = socket.getOutputStream();
           //把数据写到浏览器上
           outputStream.write("<h1>Hello World</h1>".getBytes());
           //关闭资源
           socket.close();
       } catch (IOException e) {
           e.printStackTrace();
       }
   }
View Code

只要启动了服务端程序,那么在浏览器中输入localhost:8080那么就可以链接到服务器,并且看到服务端发回的HTML 程序,或者输入自己的ip地址192.168.91.1:8080

 

作业:

1 编写一个服务器可以给多个客户端发送图片(多线程)

先写一个单线程的,再改成多线程的就好

public class ImageServer extends Thread {
   Socket socket;
   //该集合用于存储ip地址
   static HashSet<String> ips = new HashSet<String>();
   public ImageServer (Socket socket) {
       this.socket = socket;
   }
   public static void main(String[] args) throws IOException {
       //建立tcp的服务,并且监听一个端口
       ServerSocket serverSocket = new ServerSocket(9090);
       while (true) {
           //接受用户的链接
           Socket socket = serverSocket.accept();
           new ImageServer(socket).start();
       }
       //serverSocket是必须要保持打开的状态的
   }
   @Override
   public void run () {
       try {
           OutputStream outputStream = socket.getOutputStream();
           //获取图片的输入流对象
           FileInputStream fileInputStream = new FileInputStream(new File("E:\\cis675.png"));
           //读取图片数据,把数据写出
           byte[] buf = new byte[1024];
           int length = 0;
           while ((length = fileInputStream.read(buf)) != -1) {
               outputStream.write(buf, 0, length);
           }
           String ip = socket.getInetAddress().getHostAddress();//获取对方的IP地址对象
           if (ips.add(ip)){
               System.out.println("恭喜" + ip + "同学成功下载,当前下载人数是" + ips.size());
           }
           //关闭资源
           fileInputStream.close();
           socket.close();
       } catch (IOException e) {
           e.printStackTrace();
       }
   }
View Code

2 实现登录与注册功能

客户端与服务端链接的时候,就要提示客户端选择功能

客户端注册的时候,用户名和密码都是发送给服务端的,服务端把数据保存在服务端的文件上

登录:登录时客户端发送用户名和密码发送给服务端,服务端需要校验返回结果给客户端

客户端:

import java.io.*;
import java.util.*;
import java.net.*;
public class LoginClient {
   public static void main(String[] args) throws IOException {
       //客户端
       Socket socket = new Socket(InetAddress.getLocalHost(), 9090);
       //获取socket的输出流对象
       OutputStreamWriter socketWriter = new OutputStreamWriter(socket.getOutputStream());
       //获取socket的输入流对象
       BufferedReader socketReader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
       //接受键盘输入,键盘字符流入对象
       BufferedReader bReader = new BufferedReader(new InputStreamReader(System.in));
       while (true) {
           System.out.println("请选择:A 登录  B 注册");
           String option = bReader.readLine();
           if ("A".equalsIgnoreCase(option)) {
               getInfo(socketWriter, bReader, option);
               //读取服务器的反馈信息
               String line = socketReader.readLine();
               System.out.println(line);
           } else if ("B".equalsIgnoreCase(option)) {
               getInfo(socketWriter, bReader, option);
               String line = socketReader.readLine();
               System.out.println(line);
           }
       }
   }
   private static void getInfo(OutputStreamWriter socketWriter, BufferedReader bReader, String option) throws IOException {
       System.out.println("用户名:");
       String username = bReader.readLine();
       System.out.println("密码");
       String pwd = bReader.readLine();
       String info = option + " " + username + " " + pwd + "\r\n";
       socketWriter.write(info);
       socketWriter.flush();
   }
}
客户端

 

服务端:

import java.util.*;
import java.net.*;
import java.io.*;
public class LoginServer extends Thread {
   Socket socket;
   static File file = new File("E:\\users.properties");
   public LoginServer (Socket socket) {
       this.socket = socket;
   }
   static {
       try {
           if (!file.exists()) {
               file.createNewFile();
           }
       } catch (IOException e) {
           e.printStackTrace();
       }
   }
   public static void main(String[] args) throws IOException {
       ServerSocket serverSocket = new ServerSocket(9090);
       while (true) {
           Socket socket = serverSocket.accept();
           new LoginServer(socket).start();
       }
   }
   public void run () {
       try {
           //获取用户的输入流
           BufferedReader bReader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
           OutputStreamWriter outputStreamWriter = new OutputStreamWriter(socket.getOutputStream());
           //读取客户端输入的信息
           String info = bReader.readLine();
           String[] datas = info.split(" ");
           String option = datas[0];
           String username = datas[1];
           String pwd = datas[2];
           if ("a".equalsIgnoreCase(option)) {
               //登录
               Properties properties = new Properties();
               //加载配置文件
               properties.load(new FileReader(file));
               if (properties.containsKey(username)) {
                   String pwd1 = (String) properties.get(username);
                   if (pwd1.equals(pwd)) {
                       outputStreamWriter.write("欢迎" + username + "登录成功\r\n");
                   } else {
                       outputStreamWriter.write("密码错误\r\n");
                   }
               } else {
                   outputStreamWriter.write("该用户不存在,请重新输入\r\n");
               }
               outputStreamWriter.flush();
           } else if ("b".equalsIgnoreCase(option)) {
               //注册
               File file = new File("E:\\users.properties");
               if (file.exists()) {
                   file.createNewFile();
               }
               //创建一个配置文件类
               Properties properties = new Properties();
               //先加载原来的配置文件
               properties.load(new FileReader(file));
               if (!properties.containsKey(username)) {
                   //不存在该用户名
                   properties.setProperty(username, pwd);
                   //生成一个配置文件
                   properties.store(new FileWriter(file), "users");
                   outputStreamWriter.write("注册成功\r\n");
                   outputStreamWriter.flush();
               } else {
                   //存在该用户名
                   outputStreamWriter.write("用户名已经被注册,请重新输入\r\n");
                   outputStreamWriter.flush();
               }
           }
       } catch (Exception e) {
           e.printStackTrace();
       }
   }
}
服务端

函数的重载:函数名相同,形参列表不同,与返回值无关

函数的重写:函数名相同,形参列表相同,子类的返回值类型<=父类的,子类的报出异常类型<=父类的,子类的权限修饰符>=父类的。(表示子类相比父类少说废话,少干坏事,权利更大,比父类优秀了)

 

Java内存管理分三部分栈内存,堆内存,方法区(包括数据共享区,存放静态成员变量)

JVM虚拟机运行时,将类的字节码加载进入方法区。运行时,在栈内存开辟一片空间给main方法,在这个空间内,创建对象,对象的变量名在栈内存,对象的成员在堆内存,栈内存的变量存储对象的堆内存地址。main方法执行之后,就从栈内存中消失

封装的步骤:

1 私有化要封装的属性

2 根据需求提供对应的get或set方法

如果是实体类的成员属性,在现实开发中全部封装起来,比如人员类,姓名等属性

 

构造代码块:给对象进行统一的初始化工作(尤其是当构造函数重载的时候,构造代码块可以给每个构造函数都加上相同的初始化内容), 构造代码块就是位于成员位置的{}

而静态代码块是在成员位置  static{...}

创建对象的时候,每次创建都会执行构造代码块的内容,而静态代码块只执行一次,因为是类加载


静态代码块应用场景:准备一个项目的初始化工作

比如从配置文件中读取用户名和密码


继承的问题;隐式继承

父类的私有属性不能被继承。其实是可以继承,但是不能直接访问而已

public class Demo3 {
   public static void main(String[] args) {
       Teacher t = new Teacher();
       t.setName("aaa");
   }
}
class Worker {
   private String name;
   public void setName (String name) {
       this.name = name;
       System.out.println(this.name);
   }
}
class Teacher extends Worker {
   //实际还是继承了父类的name属性
}

私有属性不能直接访问,要通过方法来访问

 

多态的应用:

  1 应用于形参类型时,接受更多类型的参数(参数写父类类型或父接口类型)

  2 多态用于返回值类型时而已返回更多类型的数(返回值写父类或父接口类型)

内部类:应用的经典例子就是迭代器,作为集合类的一个内部类,其可以访问元素的属性


匿名内部类的使用前提:有继承关系

因为匿名内部类没有名字,所以使用时必须借助父类或父接口的名字。只不过是没有类名,其他一概成员都是具备的


匿名内部类主要是作为参数传递使用

多线程创建方式:

方式一:继承Thread

  1 自定义一个类继承Thread类

  2 重写Thread的run方法,把自定义线程的任务代码放在run方法里

  3 创建Thread类的子类对象,调用start开启线程

class MyThread extends Thread {
   @Override
   public void run () {
       for (int i = 0; i < 50; i ++) {
           System.out.println(Thread.currentThread().getName() + i);
       }
   }
}

方式二:

  1 自定义一个类实现Runnable接口

  2 实现Runnable的run方法

  3  创建Runnable实现类的对象

  4 创建Thread的对象,然后把Runnable实现类的对象作为参数传递

  5 调用Thread对象的start方法开启线程

public class Demo3{
   public static void main(String[] args) {
       MyThread1 thread1 = new MyThread1();
       Thread t = new Thread(thread1);
       t.start();
   }
}
class MyThread1 implements Runnable {
   @Override
   public void run () {
       for (int i = 0; i < 50; i ++) {
           System.out.println(Thread.currentThread().getName() + i);
       }
   }
}

Java中的同步机制:

1 同步代码块

synchronized (锁对象) {
   //......
}

出现线程安全的原因:存在两个或两个以上的线程共享资源,操作资源的代码必须有多句(多线程多语句操作同一个资源)

注意:

  1 锁对象可以是任意对象

  2 锁对象必须是多线程共享资源,否则锁不住

  3 没有线程安全问题的时候,不要使用锁,会导致效率降低

  4 调用sleep方法并不会释放锁对象,但是调用wait方法的线程就会释放锁对象


2 同步函数

修饰符 synchronized 返回值类型 函数名 (形参列表) {
   //......
}

同步函数的锁对象不能是任意的,

1 非静态同步函数的锁对象是this对象,静态函数锁对象是当前字节码对象

2 同步函数的锁不能由你指定

 

集合:

单例集合:

------| Collection  单例集合根接口

------------| List  实现List接口,具备的特点是有序,可重复

---------------| ArrayList  底层使用Object[]数组实现的,查询快增删慢

---------------| LinkedList  底层使用了链表结构实现的,增删快查询慢

---------------| Vector  底层也使用Object[]数组实现,但是线程安全的,操作效率低

将Collection和List的方法都学会就基本涵盖了所有常用方法了

------------| Set  实现Set接口的集合具备特点是无序,不可重复

---------------| HashSet  底层使用了哈希表数据结构

---------------| TreeSet  底层使用二叉树实现的

双列集合:

------| Map (只需要把Map接口的方法全部练习一次就可以)

-----------| HashMap  底层使用哈希表实现

-----------| TreeMap  底层使用二叉树实现

-----------| HashTable


从键盘输入一个字母组成的字符串,分别统计每个字母出现的次数,输出效果要按字母顺序输出a(7)b(5)....

使用TreeMap更好,因为有排序更可以记录次数

public static void main(String[] args) throws IOException {
   System.out.println("请输入一个字符串");
   BufferedReader bReader = new BufferedReader( new InputStreamReader(System.in));
   String str = bReader.readLine();
   char[] arr = str.toCharArray();//先把字符串转换为字符数组
   TreeMap<Character, Integer> map = new TreeMap<Character, Integer>();
   for (char item: arr) {
       if (map.containsKey(item)) {
           //map集合已经包含了该字符
           int count = map.get(item);
           map.put(item, ++count);
       } else {
           //map集合没有包含该字符
           map.put(item, 1);
       }
   }
   System.out.println(map);
}

好好利用集合的特性

 

软件的交互方式:

1 控制台 2图形化交互

这是最不重要的一个内容, 因为市场上没有用Java来写桌面应用软件

Java使用到的图形类主要在java.awt包和javax.swing包中

一般来说在java.awt包中有个类比如Button那么在javax.swing中就有个对应的JButton


Java.awt中使用的图形都是依赖于系统的图形库的

Javax.swing中使用的图形都是SUN自己实现的,不需要依赖系统图形库


因为每个系统都有自己的图形库,所以过度依赖系统图形库,那么跨系统时图形界面就会更改得面目全非不统一。

既然swing包的图形类已经取代awt包的图形类,为什么不删除awt包呢?

之前已经有awt开发的程序。awt包里除了图形类还有事件类,在swing包里是没有的。


Java中所有的图形类都被称为组件类

组件的类别:

--------| 容器组件:窗口

--------| 非容器组件:按钮

JFrame(String title) 创建一个不可见窗口,带有标题title

JFrame frame = new Frame()

frame.setVisible(true)  窗口设置可见

frame.setSize(width, height) 窗口设置大小

frame.setBounds(x, y, width, height) 第一个参数左上角x轴坐标,第二个参数左上角y轴坐标,width和height就是窗体的宽和高

 

如何获取屏幕的分辨率呢?

需要用到java.awt包中的Toolkit.getDefaultToolkit()来获取工具包类,里面有默认的系统参数

窗口在屏幕居中(自己写一个initFrame函数,如下,非常有用,作为初始化窗口,每次有窗口都要执行)

public static void main(String[] args) {
   JFrame frame = new JFrame("第一个窗口");
   frame.setVisible(true);
   initFrame(frame, 300, 400);
}
public static void initFrame (JFrame frame, int width, int height) {//窗口屏幕居中
   Toolkit toolkit = Toolkit.getDefaultToolkit();//获取与系统相关的工具类对象
   Dimension dimension = toolkit.getScreenSize();
   int x = (int)dimension.getWidth();
   int y = (int)dimension.getHeight();
   frame.setBounds((x - width) / 2, (y - height) / 2, width, height);
}

 

设置窗体关闭的事件

frame.setDefaultCloseOperation(operation) 

frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);

对话框类Dialog

JDialog(Dialog owner, String title, boolean modal)

owner所有者,title标题,modal模式(true只允许用户操作当前对话框,false用户即可以操作当前窗口,也可以操作其他窗口)

对话框也是初始不可见的

 

另一个方便使用的对话框JOptionPane

消息对话框

JOptionPane.showMessageDialog(frame, "Hello World", "消息对话框", JOptionPane.INFORMATION_MESSAGE);

警告对话框

JOptionPane.showMessageDialog(frame, "警告", "警告对话框", JOptionPane.WARNING_MESSAGE);

 

错误对话框

JOptionPane.showMessageDialog(frame, "错误", "错误对话框", JOptionPane.ERROR_MESSAGE);

输入对话框

String price = JOptionPane.showInputDialog("金额");
System.out.println(price);

确认框

int num = JOptionPane.showConfirmDialog(frame, "你确定卸载吗");
System.out.println(num);

是返回0,否返回1,取消返回2

 

文件对话框(FileDialog):

在java.awt包中

FileDialog(Dialog parent, String title, int mode)

parent对话框所有者,title对话框的标题,mode模式(FileDialog.LOAD打开,FileDialog.SAVE保存)

方法:getDirectory()获取路径,getFile()获取文件名 

JFrame frame = new JFrame("窗体");
initFrame(frame, 300, 400);
FileDialog fileDialog = new FileDialog(frame, "请选择打开的文件", FileDialog.LOAD);
fileDialog.setVisible(true);
System.out.println("文件所在的目录:" + fileDialog.getDirectory());
System.out.println("文件名:" + fileDialog.getFile());

面板(JPanel)

需要父类容器添加面板,frame.add(panel)

非容器组件

制作一个注册页面来学习非容器组件

public class Demo3{
   public static void main(String[] args) {
       JFrame frame = new JFrame("注册");
       //创建一个面板
       JPanel panel = new JPanel();
       frame.add(panel);
       //用户名,要用标签
       JLabel nameLabel = new JLabel("用户名");
       JTextField username = new JTextField(12);//指定可以输入12列
       panel.add(nameLabel);
       panel.add(username);
       //密码
       JLabel pwdLabel = new JLabel("密码");
       JPasswordField pwd = new JPasswordField(12);
       panel.add(pwdLabel);
       panel.add(pwd);
       //性别
       JLabel genderLabel = new JLabel("性别");
       JRadioButton man = new JRadioButton("男", true);//默认选中一项
       JRadioButton woman = new JRadioButton("女");
       //单选框必须要分组,同一组的单选框只能选一个
       ButtonGroup group = new ButtonGroup();
       group.add(man);
       group.add(woman);
       panel.add(genderLabel);
       panel.add(man);
       panel.add(woman);
       //来自城市,下拉框
       JLabel cityLabel = new JLabel("来自的城市");
       Object[] arr = {"北京", "上海", "广州", "深圳"};
       JComboBox cities = new JComboBox(arr);
       panel.add(cityLabel);
       panel.add(cities);
       //兴趣爱好,复选框
       JLabel hobbyLabel = new JLabel("兴趣爱好");
       JCheckBox checkBox1 = new JCheckBox("篮球", true);
       JCheckBox checkBox2 = new JCheckBox("JAVA", true);
       JCheckBox checkBox3 = new JCheckBox("JavaScript");
       JCheckBox checkBox4 = new JCheckBox("Android");
       panel.add(hobbyLabel);
       panel.add(checkBox1);
       panel.add(checkBox2);
       panel.add(checkBox3);
       panel.add(checkBox4);
       //个人简介
       JLabel descLaebl = new JLabel("个人简介");
       JTextArea textArea = new JTextArea(20, 15);//参数(row, column)
       textArea.setLineWrap(true);
       panel.add(descLaebl);
       panel.add(textArea);
       initFrame(frame, 800, 400);
   }
   public static void initFrame (JFrame frame, int width, int height) {//窗口的必要操作放在函数内
       Toolkit toolkit = Toolkit.getDefaultToolkit();
       Dimension dimension = toolkit.getScreenSize();
       int x = (int) dimension.getWidth();
       int y = (int) dimension.getHeight();
       frame.setVisible(true);
       frame.setBounds((x - width) / 2, (y - height) / 2, width, height);
       frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
   }
}

菜单组件:

菜单条(MenuBar) 菜单(Menu)  菜单项(MenuItem)

菜单条可以添加菜单,菜单可以添加菜单项

 

复选菜单:

首先菜单添加菜单项

JFrame frame = new JFrame("记事本");
//菜单条
JMenuBar menuBar = new JMenuBar();
JMenu fileMenu = new JMenu("文件");
JMenu editMenu = new JMenu("编辑");
JMenu switchMenu = new JMenu("切换工作目录");
//菜单项
JMenuItem openMenu = new JMenuItem("打开");
JMenuItem saveMenu = new JMenuItem("保存");

JMenuItem aboutMenu = new JMenuItem("关于");
JMenuItem closeMenu = new JMenuItem("关闭");

JMenuItem workMenu1 = new JMenuItem("project09");
JMenuItem workMenu2 = new JMenuItem("project10");
JMenuItem workMenu3 = new JMenuItem("project11");

//书写区域
JTextArea textArea = new JTextArea(30,20);
frame.add(textArea);
//将菜单项添加进入菜单
fileMenu.add(openMenu);
fileMenu.add(saveMenu);
editMenu.add(aboutMenu);
editMenu.add(closeMenu);
//复选菜单
switchMenu.add(workMenu1);
switchMenu.add(workMenu2);
switchMenu.add(workMenu3);
//菜单添加菜单就是复选菜单
fileMenu.add(switchMenu);

menuBar.add(fileMenu);
menuBar.add(editMenu);
frame.add(menuBar, BorderLayout.NORTH);
initFrame(frame, 800, 400);

布局管理器:布局管理器,用于指定组件的摆放位置

每种布局管理器都有自己的摆放风格

BorderLayout(边框布局管理器),都在java.awt包

摆放的风格:

上北下南左西右东

BorderLayout注意事项:

  1 使用BorderLayout添加组件的时候,如果没有指定组件的方位,那么默认添加到中间的位置

  2 使用BorderLayout的时候,如果东西南北哪个方向没有对应的组件,那么中间位置的组件就会占据其空缺的位置

  3 窗体frame默认的布局管理器就是BorderLayout,如果没有frame.setLayout也会有默认布局

JFrame frame = new JFrame("边框布局管理");
BorderLayout borderLayout = new BorderLayout();
//让BorderLayout管理frame窗框
frame.setLayout(borderLayout);
frame.add(new JButton("北"), BorderLayout.NORTH);
frame.add(new JButton("南"), BorderLayout.SOUTH);
frame.add(new JButton("西"), BorderLayout.WEST);
frame.add(new JButton("东"), BorderLayout.EAST);
frame.add(new JButton("中"), BorderLayout.CENTER);
initFrame(frame, 800, 400);

流式布局管理器(FlowLayout)

一排一排地摆放,如果一排放不下就放在第2排

FlowLayout flowLayout = new FlowLayout()可以是无参的构造函数,

FlowLayout(int align)也可以传入一个参数,表示对齐方式FlowLayout.LEFT左对齐,默认是居中

FlowLayout(int align, int hgap, int vgap) 接受对齐方式,水平间隙和垂直间隙

JFrame frame = new JFrame("边框布局管理");
JPanel panel = new JPanel();
frame.add(panel);
//创建流式布局管理器
FlowLayout flowLayout = new FlowLayout(FlowLayout.LEFT);//指定对齐方式
//让流式布局管理器来管理面板
panel.setLayout(flowLayout);
panel.add(new JButton("button 1"));
panel.add(new JButton("button 2"));
panel.add(new JButton("button 3"));
panel.add(new JButton("button 4"));
initFrame(frame, 800, 400);

流式布局管理器,注意:

  1 流式布局管理器默认情况是居中对齐,可以在创建流式管理器时指定排列方式和组件间隙

  2 面板(JPanel)默认的布局管理器就是FlowLayout

表格布局管理器(GridLayout)

构造方法

GridLayout()

GridLayout(int rows, int cols)

GridLayout(int rows, int cols, int hgap, int vgap)

JFrame frame = new JFrame("边框布局管理");
GridLayout gridLayout = new GridLayout(4, 4, 5, 5);
frame.setLayout(gridLayout);
for (int i = 0; i < 10; i ++) {
   frame.add(new JButton(i + ""));
}
frame.add(new JButton("+"));
frame.add(new JButton("-"));
frame.add(new JButton("*"));
frame.add(new JButton("/"));
frame.add(new JButton("="));
frame.add(new JButton("."));
initFrame(frame, 400, 400);

注意事项:

1 如果表格数量不够使用时,默认会多加一列

 

卡片布局管理器(CardLayout)

CardLayout()

CardLayout(int hgap, int vgap)

JFrame frame = new JFrame("边框布局管理");
final CardLayout cardLayout = new CardLayout(5, 5);
final JPanel panel = new JPanel();
frame.add(panel);
panel.setLayout(cardLayout);
//往面板添加数据
JButton button = new JButton("王");
panel.add(button);
panel.add(new JButton("红桃"));
panel.add(new JButton("黑桃"));
panel.add(new JButton("方片"));
panel.add(new JButton("梅花"));
button.addActionListener(new ActionListener(){
   public void actionPerformed (ActionEvent e) {
       cardLayout.next(panel);
   }
});
initFrame(frame, 400, 400);

事件:

当发生了某个事件的时候,就会有相应的处理方案

事件源:

监听器:

处理方案:

事件:

addActionListener(ActionListener l) 接受一个ActionListener的接口实现类对象

ActionListener是一个接口,该接口只有一个方法

    void actionPerformed(ActionEvent e)

ActionEvent当前按钮被点击时,jvm就会把对应的事件传递ActionEvent,并调用ActionPerformed方法

 

button.addActionListener(new ActionListener(){//这里并不是创建接口对象,而是一种匿名内部类的写法,需要借用父类或者父接口的类名

  Public void actionPerformed (ActionEvent e) {

    //事件执行

  }

})

这里有点类似于JavaScript了,之后用ActionEvent对象e来获取事件源等信息

e.getSource() 获取事件源

JFrame frame = new JFrame("事件");
JButton button = new JButton("按钮");
frame.add(button, BorderLayout.NORTH);
button.addActionListener(new ActionListener() {
   @Override
   public void actionPerformed (ActionEvent e) {
       JButton button = (JButton) e.getSource();
       if ("按钮".equals(button.getText())) {
           button.setText("点过了");
       } else {
           button.setText("按钮");
       }
   }
});

动作事件监听器,对于鼠标点击以及空格键都会起作用

 

鼠标事件监听器:

button.addMouseListener(new MouseAction () {})

button.addMouseListener(new MouseListener() {
   @Override
   public void mouseClicked(MouseEvent e) {//点击事件
       System.out.printf("点击了");
   }
   @Override
   public void mousePressed(MouseEvent e) {//鼠标按下
       System.out.println("按下了");
   }
   @Override
   public void mouseReleased(MouseEvent e) {//鼠标松开
       System.out.println("松开了");
   }
   @Override
   public void mouseEntered(MouseEvent e) {//鼠标滑入
       System.out.println("滑入");
   }
   @Override
   public void mouseExited(MouseEvent e) {//鼠标移出
       System.out.println("移出");
   }
});

添加鼠标监听器时我只想用单击事件,但是MouseAction却要我们实现所有的接口方法?

解决方案:适配器MouseAdapter

适配器只不过就是实现了MouseListener的所有方法,但是实现的方法全部都是空实现。

  button.addMouseAdapter(new MouseAction () {

    public void mouseClicked (MouseEvent e) {...}

  })

button.addMouseListener(new MouseAdapter () {
   @Override
   public void mouseClicked (MouseEvent e) {
       System.out.println("鼠标单击了");
   }
});
initFrame(frame, 400, 400);

 

双击

button.addMouseListener(new MouseAdapter () {
   @Override
   public void mouseClicked (MouseEvent e) {
       if (e.getClickCount() == 2) {
           System.out.println("双击");
       }
       System.out.println("单击");
   }
});

键盘事件监听器

button.addKeyListener(new KeyListener () {

  public void keyTyped (KeyEvent e) {...}

});

JFrame frame = new JFrame("鼠标事件监听器");
JButton button = new JButton("button");
frame.add(button);
button.addKeyListener(new KeyListener() {
   @Override
   public void keyTyped(KeyEvent e) {
       System.out.println("键入某个键");
   }
   @Override
   public void keyPressed(KeyEvent e) {
       System.out.println("键弹起");
   }
   @Override
   public void keyReleased(KeyEvent e) {
       System.out.println("键按下");
   }
});
initFrame(frame, 400, 400);

另外,键盘监听也有KeyAdapter,以后我们只要使用KeyAdapter就可以,不用非要实现所有的键盘事件

button.addKeyListener(new KeyAdapter () {
   public void keyPressed (KeyEvent e) {
       System.out.println(e.getKeyCode());
   }
});

e.getKeyCode()获取键码值

e.getKeyChar()获取键所对应的字符

 

KeyTyped事件和KeyPressed,KeyReleased事件的不同之处

只有KeyTyped事件是可以使用getKeyChar()来获取字符的。而KeyPressed和KeyReleased事件则使用getKeyCode()


文件搜索器:

public class Demo3{
   public static void main(String[] args) {
       JFrame frame = new JFrame("文件搜索器");
       JPanel panel = new JPanel();
       final JTextField search = new JTextField("请输入目录名",15);
       JButton button = new JButton("搜索");
       final JTextArea textArea = new JTextArea(15, 15);
       textArea.setLineWrap(true);
       ScrollPane scrollPane = new ScrollPane();
       scrollPane.add(textArea);
       //先把组件添加到panel上
       panel.add(search);
       panel.add(button);
       frame.add(panel, BorderLayout.NORTH);
       frame.add(textArea);
       search.addMouseListener(new MouseAdapter () {
           @Override
           public void mouseClicked(MouseEvent e){
               JTextField textField = (JTextField) e.getSource();
               if ("请输入目录名".equals(textField.getText())) {
                   textField.setText("");
               }
           }
       });
       button.addActionListener(new ActionListener(){
           @Override
           public void actionPerformed (ActionEvent e) {
               String path = search.getText();
               //使用输入的路径构建一个File对象
               File dir = new File(path);
               File[] files = dir.listFiles();
               for (File file: files) {
                   textArea.setText(textArea.getText() + file.getName() + "\n\r");
               }
           }
       });
       initFrame(frame, 300, 200);
   }
   public static void initFrame (JFrame frame, int width, int height) {//窗口的必要操作放在函数内
       Toolkit toolkit = Toolkit.getDefaultToolkit();
       Dimension dimension = toolkit.getScreenSize();
       int x = (int) dimension.getWidth();
       int y = (int) dimension.getHeight();
       frame.setVisible(true);
       frame.setBounds((x - width) / 2, (y - height) / 2, width, height);
       frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
   }
}
文件搜索器

 

 

Bat处理文件: 可以一次性执行多个命令的文件

 

为什么要学批处理文件,快速运行一个软件我们一般都会把软件打包成jar包文件

jar包双击可以运行,仅对于图形化界面起作用,对控制台程序是不起作用的

对于控制台的程序,我们可以使用批处理文件快速启动一个项目

比如对于一个.java源文件,那么我们需要javac编译,并java启动JVM虚拟机才可以执行,如何一步执行呢?批处理文件(批量运行控制台命令)

如何编写批处理文件?

步骤:

编写一个自定义的文本文件,把后缀名改成bat即可,然后把所要执行的命令写在批处理文件即可

将.java文件或package压缩成.zip压缩包,然后打开控制台,输入set classpath = …\xxx.zip进入该压缩包后,再用java命令运行相关文件

当然在控制台输入set classpath = …\xxx.zip仅对当前控制台窗口有效,是个临时的设置,一旦关闭就会消失

在控制台输入java命令,可以看到java Demo1 这个命令其实也有很多参数,其中-classpath用来设置文件的路径。这样设置的classpath是仅对本次运行有效

    java -classpath  …\xxx.zip;  Demo1

注意格式就是  java -classpath 路径; 包名.类名  一定不能少分号(分号相当于表示控制台当前路径,表示现在指定路径下找类文件,找不到就去控制台当前路径下找)

所以我们写好了.java源代码,编译后得到了.class文件,那么就打包为.zip文件,并创建.bat批处理文件,在内部写上java -classpath  …\xxx.zip  包名.类名。然后双击.bat文件来运行控制台程序 

bat批处理文件的常用命令

echo 向控制台输出指定的内容(最好保持编码方式,如果默认是ANSI那么就不要改成UTF-8)

echo off 隐藏echo off后面执行过的命令(把执行过的命令都隐藏起来,只看效果),比较装逼,因为看不见输入命令,只能看见执行结果

@  符号,隐藏当前行命令(使用这个符号可以把echo off也隐藏起来)

也就是说在.bat文件的开头位置加上@echo off就不显示执行的命令了,只显示结果

palse: 让当前控制台停留,不要执行完立即消失

title 改变当前控制台窗口的标题

color 指定控制台的背景色与前景颜色

颜色属性由两个十六进制的数字指定,第一个为背景,第二个为前景(字体)

每一位数字可以是下列数字中选

   0 = Black 黑         8 = Gray

   1 = Blue   蓝          9 = Light Blue  

   2 = Green 绿        A = Light Green

   3 = Aqua  浅绿       B = Light Aqua

   4 = Red            C = Light Red

   5 = Purple         D = Light Purple

   6 = Yellow         E = Light Yellow

   7 = White          F = Bright White

%....注释内容....% 用双百分号包括的内容在.bat文件中为注释内容

批处理文件传参:

  %1 ~ %9  给批处理文件传入参数. 写在文件中需要传参的位置上,相当于一个变量,等待传入。这时候就不能双击运行了,就要用控制台来运行.bat文件。

  比如对于run.bat文件,在控制台,cd路径后,可以直接写文件名运行不含扩展名来运行

  run hello 来运行

@echo off
title %1
color 4f
echo welcome to learn bat commands
java -classpath .\Demo1.zip; Demo2.Demo1
pause

运行一个run.bat批处理文件,传入参数helloworld

对象的克隆

对象的浅克隆(copy) 和 对象的深克隆(deep copy):

Person p1 = new Person();

Person p2 = (Person) p1.clone();

对象浅克隆:

注意:

  1 如果一个对象需要调用clone的方法,那么该对象所属的类必须要实现Cloneable接口

    比如上述例子中,p1的所属类Person类要implements Cloneable接口,因为p1要执行clone方法

  2 Cloneable接口只不过是一个标识接口,没有任何方法

  3 clone方法是Object类的方法,本身是protected修饰的,从一个类克隆需要继承关系,才可以使用。需要调用clone方法的类需要重写clone方法

@Override
protected Object clone() throws CloneNotSupportedException {
   return super.clone();
}

  4 对象的浅克隆就是克隆一个对象的时候,如果被克隆的对象中维护了另外一个类的对象,这时候只是克隆另外一个对象的地址,而没有把另外一个对象也克隆

    浅克隆.clone() 就是只克隆第一层对象,该对象内部维护的其他对象都是保存引用地址,其它层都是克隆引用地址,实际上是共享了一份数据

  5 对象的浅克隆也不会调用构造方法的。所以,产生新的对象不一定要调用构造方法

    对象深克隆:利用了对象的输入输出流,把对象先写在文件上,然后再读取对象的信息

注意:

  1 被克隆的对象的所属类和操作的类都要实现Serializable接口

import java.io.*;
public class Demo1 implements Serializable {
   public Demo1(){}
   public static void main(String[] args) throws Exception {
       Address city = new Address("广州");
       Person p1 = new Person(101, "alex", city);
       writeObj(p1);
       Person p = readObj();
       p.address.city = "长沙";
       System.out.println(p1);
       System.out.println(p);
   }
   //先把对象写到文件上
   public static void writeObj (Person p) throws IOException {
       //建立一个文件的输出流对象
       FileOutputStream fileOutputStream = new FileOutputStream("E:\\a.txt");
       //建立对象的输出流
       ObjectOutputStream objectOutputStream = new ObjectOutputStream(fileOutputStream);
       //把对象写出
       objectOutputStream.writeObject(p);
       //关闭资源
       objectOutputStream.close();
   }
   //从文件中都读对象
   public static Person readObj () throws IOException, ClassNotFoundException {
       FileInputStream fileInputStream = new FileInputStream("E:\\a.txt");
       ObjectInputStream objectInputStream = new ObjectInputStream(fileInputStream);
       return (Person) objectInputStream.readObject();
   }
}

  2 要借用IO流,先将对象写入文件,再读进来.

深克隆后得到的对象就是完全的新的对象,其内部维护的对象也是新的对象,并没有共享

内存泄漏

编写一个类,使用数组模拟堆栈的存储方式

堆栈的存储特点,先进后出,后进先出


注意:如果对象不再使用了,这时候应该不要让变量指向该对象,要让该对象尽快被垃圾回收器回收

//出栈: 删除集合中的元素并返回
public Object pop () {
   int curIndex = -- idx;
   Object obj = this.elements[curIndex];
   this.elements[curIndex] = null;//让该位置不再引用着指定的对象,让垃圾回收器赶紧回收
   return obj;
}

进入一个类,使用alt + 7或者最左边的键就可以浏览类内的所有方法

单例设计模式:

饿汉单例设计模式,懒汉单例设计模式:

懒汉单例设计模式存在线程安全问题,解决方案

步骤:

  1 私有化构造函数private Single(){}

  2 声明本类的引用类型变量,但是不要创建对象private static Single s;

  3 声明一个公共静态的方法,获取本类的变量,获取之前先判断是否已经创建了本类的对象,如果已经创建了就不要再创建了,直接返回,如果还没有创建,就先创建本类的引用类型变量,再返回

    if (s == null) s = new Single();

    return s;

  4 加锁再加一层判断

class Single {
   private static Single s;
   private Single () {}
   public static Single getInstance () {
       if (s == null) {
           synchronized ("锁") {
               if (s == null) {
                   s = new Single();
               }
           }
       }
       return s;
   }
}

观察者设计模式:观察者设计模式,解决的问题是当一个对象发生指定的动作时,要通知另外一个对象做出相应的处理

观察者设计模式又可以称作 模型/视图(model/view)模式


需求:编写一个气象站,一个工人类,当天气更新时,要通知人做相应的处理

  问题1:气象站更新了多次天气,人才做出一次的处理

    解决:在气象站内维护一个人,并在更新天气后,立即调用人的反应函数就可以同步

  问题2:目前气象站只能通知一个人而已

    解决:维护一个AddListener方法,将每个对象添加到ArrayList里

  问题3:在现实生活中其他的群体也需要关注天气

    解决:利用接口,让每个传入的类都实现这个接口,相当于订阅天气预报

程序设计,讲究低耦合,也就是尽量不要让一个类过分依赖于另外一个类

观察者设计模式的步骤:

1 定义一个接口(当目前对象发生指定动作时要通知另外对象做出相应的处理,应该把对方的相应处理方法定义在接口上)

2 在当前对象中,维护接口的引用,当当前对象发生指定动作时,即可调用接口中的方法

import java.util.*;
public class Demo1 {
   public static void main(String[] args) throws Exception {
       Emp emp = new Emp("小明");//工人必须是Weather接口的实现类
       Emp emp1 = new Emp("小王");
       Student s1 = new Student("alex");//学生也是Weather接口的实现类
       Student s2 = new Student("bob");
       WeatherStation w = new WeatherStation();
       w.addListener(emp);
       w.addListener(emp1);
       w.addListener(s1);
       w.addListener(s2);
       w.stationWork();
   }
}
//订阅天气预报的接口
interface Weather {
   public void notifyWeather (String weather);
}
class WeatherStation {//天气预报站
   String weather;//当前天气
   //该集合中存储的都是需要接收天气的人,也就是实现了Weather接口的人,也是维护了接口的引用
   ArrayList<Weather> list = new ArrayList<Weather>();//程序设计,讲究低耦合,尽量不要让一个类过度依赖另一个类
   public void addListener (Weather e) {
       list.add(e);
   }
   String[] weathers = {"晴天", "刮风", "雾霾", "冰雹", "下雪"};
   public void stationWork () throws InterruptedException {//气象站工作的方法
       while (true) {//每1~1.5秒更新一次天气
           int millis = (int) (Math.random() * (1500 - 1000 + 1) + 1000);
           updateWeather();
           for (Weather e: list) {
               e.notifyWeather(weather);
           }
               Thread.sleep(millis);
       }
   }
   public void updateWeather () {
       Random random = new Random();
       int index = random.nextInt(weathers.length);
       weather = weathers[index];
       System.out.println("the current weather: " + weather);
   }
}
//工人类,要实现Weather接口
class Emp implements Weather {
   String name;
   public Emp (String name) {
       this.name = name;
   }
   //人要根据相应的天气做出反应
   public void notifyWeather (String weather) {
       if ("晴天".equals(weather)) {
           System.out.println("上班");
       } else if ("雾霾".equals(weather)) {
           System.out.println("戴口罩上班");
       } else if ("刮风".equals(weather)) {
           System.out.println("穿大衣");
       } else if ("冰雹".equals(weather)) {
           System.out.println("戴头盔");
       } else if ("下雪".equals(weather)) {
           System.out.println("盖被子");
       }
   }
}
//学生类,要实现Weather接口
class Student implements Weather {
   String name;
   public Student (String name) {
       this.name = name;
   }
   @Override
   public void notifyWeather(String weather) {
       System.out.println("风雨无阻,上学去");
   }
}

反射

当一个字节码加载到内存中时,Java虚拟机会对该字节码进行解剖,然后会创建一个Class类的对象,把字节码文件的信息全部都存储到该class对象中,我们只要获取到这个Class对象,我们就可以使用字节码对象设置对象的属性或者调用对象的方法等操作

 

注意:在反射技术中,一个类的任何成员都有对应的类进行描述。成员变量由Field类描述,方法Method类


获取Class对象的方式1:

  通过forName方法获取(推荐使用)

  Class c1 = Class.forName(“包名.类名”);

  System.out.println(c1);

会报出异常,担心你乱写类名,找不到。


获取Class对象的方式2:

  通过类名获取

  Class c2 = People.class

获取Class对象的方式3:

  通过对象获取

  new People().getClass();


如何通过class对象获取构造方法

  1 获取到Class对象

  2 通过Class对象调用getConstructors() 获取所有的公共构造方法

  调用getDeclaredConstructors()获取所有的公共或私有构造方法

  调用getConstructor(int.class, String.class) 获取指定的包含有接受一个int类参数和一个String类参数的构造方法

Class c1 = Class.forName("Demo2.People");
//获取一个类的所有公共的构造方法
Constructor[] constructors = c1.getConstructors();
for (Constructor c: constructors) {
   System.out.println(c);
}
//在反射里凡是私有私有的都要加一个Declared才能获取
//获取一个类所有构造方法,包括私有的构造方法
Constructor[] constructors1 = c1.getDeclaredConstructors();
for (Constructor c: constructors1) {
   System.out.println(c);
}
//获取单个的指定的构造方法
Constructor c2 = c1.getConstructor(int.class, String.class);
People p = (People) c2.newInstance(999, "alex");

利用获取到的构造方法创建对象。使用.newInstance()方法

  Constructor constructor = c.getConstructor(int.class, String.class)

  People p = (People)constructor.newInstance(999, “alex”);

 

如果是获取到的是私有的构造方法,那么默认会阻止你创建对象,不过也可以暴力修改权限

  Constructor constructor = c.getDeclaredConstructor(null);

  constructor.setAccessible(true);

  People p = (People) constructor.newInstance(null);

Class c1 = Class.forName("Demo2.People");
//获取单个的指定的构造方法
Constructor c2 = c1.getDeclaredConstructor(null);
//暴力反射
c2.setAccessible(true);//对于私有的方法,设置访问权限为允许访问
System.out.println(c2);
People p = (People) c2.newInstance(null);
System.out.println(p);

不过最好不要在对方使用单例设计模式,或者有意设计私有构造方法时,采用暴力反射

 

通过class对象获取到对应的方法,方法在反射技术中,使用了Method类描述了方法

  1 获取到Class对象

  2 通过Class对象

    调用getMethods()返回所有公共方法(包括父类的方法)

    调用getDeclaredMethods()返回所有的公共和私有的方法(不含父类方法)

    调用getMethod(方法名, 形参列表),获取指定的公共方法, 方法名是字符串,形参列表如果无参就写null,如果有int参数,就写int.class

    调用getDeclaredMethod(方法名, 形参列表), 获取指定的公共或私有方法。如果是私有的,需要该方法对象调用.setAccessible(true)来暴力反射设定允许访问

    那么得到了指定的函数后,可以用该函数对象,调用invoke(obj,args)方法来执行该方法,obj表示方法的执行者,args表示传入的参数

    如果该方法对象是个static静态方法,那么不需要对象来调用,就可以用

    invoke(null, args);来调用

//获取class对象
Class c1 = Class.forName("Demo2.People");
//获取所有公共的方法(包含父类的方法)
Method[] methods = c1.getMethods();
for (Method m: methods) {
   System.out.println(m);
}
//获取所有的公共和私有方法(不包含父类的方法)
Method[] allMethods = c1.getDeclaredMethods();
for (Method m: allMethods) {
   System.out.println(m);
}
//获取指定的公共方法,并执行
Method eat = c1.getMethod("eat", null);
eat.invoke(new People(101, "bob"));
//获取指定的公共或私有方法, 并执行
Method sleep = c1.getDeclaredMethod("sleep", int.class);//函数名和形参列表,没有参数就写null
sleep.setAccessible(true);
sleep.invoke(new People(101, "alex"), 12);
//如果函数接受的是数组类型的参数
Method getLength = c1.getMethod("getLength", int[].class);
getLength.invoke(new People(101, "alex"), new int[6]);
getLength.invoke(new People(101, "alex"), new int[]{});

通过class对象获取到对应的成员变量

在反射技术中,使用了Field类描述了成员变量

  1 获取到Class对象

  2 通过Class对象

    调用getFields()返回所有公共变量(public修饰的,像int id这样是默认修饰符,获取不到)

    调用getDeclaredFields()返回所有的公共和私有的变量

    调用getField(变量名),获取指定的公共变量, 方法名是字符串

    调用getDeclaredField(方法名), 获取指定的公共或私有方法。如果是私有的,需要该变量对象调用.setAccessible(true)来暴力反射设定允许访问

  那么得到了指定的变量后,可以用该变量对象,调用set(obj,value)方法来执行该方法,obj表示方法的执行者,value表示设置的值

//获取class对象
Class c1 = Class.forName("Demo2.People");
//获取到所有公共成员变量(也就是public修饰的,如果int id;这样是默认修饰符)
Field[] fields = c1.getFields();
for (Field field: fields) {
   System.out.println(field);
}
//获取到所有的公共和私有的成员变量
Field[] fields1 = c1.getDeclaredFields();
for (Field field: fields1) {
   System.out.println(field);
}
//获取到指定的公共成员变量
Field test = c1.getField("test");
test.set(new People(101, "alex"), 123);
System.out.println(test);
//获取到指定的公共或私有变量
Field id = c1.getDeclaredField("id");
id.set(new People(101, "alex"), 102);
System.out.println(id);
//对于私有的变量,要暴力反射,设置其允许访问
Field sex = c1.getDeclaredField("sex");
sex.setAccessible(true);
sex.set(new People(101, "alex"), "male");
System.out.println(sex);

工厂设计模式

工厂设计模式就是用于产生对象的


需求:编写一个工厂方法,根据配置文件,返回对应的对象

对于配置文件生成对象

import java.io.*;
import java.lang.reflect.*;
public class Demo2 {
   public static void main(String[] args) throws Exception {
       People p = (People) getObj();
       p.eat();
   }
   public static Object getObj () throws Exception {
       //读取配置文件
       BufferedReader bufferedReader = new BufferedReader(new FileReader("E:\\a.txt"));
       //读取第一行,类文件的信息
       String className = bufferedReader.readLine();
       Class c1 = Class.forName(className);
       Constructor constructor = c1.getDeclaredConstructor(null);
       constructor.setAccessible(true);
       Object obj = constructor.newInstance(null);
       //给对象设置对应的属性值
       String line = null;
       while ((line = bufferedReader.readLine()) != null) {
           String[] args = line.split("=");
           Field field = c1.getDeclaredField(args[0]);
           field.setAccessible(true);
           if (field.getType() == int.class) {
               field.set(obj, Integer.parseInt(args[1]));
           } else {
               field.set(obj, args[1]);
           }
       }
       return obj;
   }
}

 

 

。。。。。。。。。。。。。

posted on 2016-09-15 10:15  Wiesler  阅读(146)  评论(0)    收藏  举报