网络编程 什么是计算机网络 eg:信封 写信 讲述包的概念（对方的信息，地址；自己的地址） eg: 打电话 --连接-- 接了 --通话 TCP 发短信 --发送了就完事了 --接受 UDP 计算机网络 计算机网络（英语：computer network），通常也简称网络，是指容许节点分享资源的数字电信网络[1]:1-3。在电脑网络，电脑设备会透过节点之间的连接（数据链路）互相交换数据。传输介质可分为有线及无线两类——有线的可用到双绞线、光纤电缆等介质[1]:1-4；无线则可用到Wi-Fi、NFC[2]:4-32。 网络编程的目的 无线电台 传播交流信息 数据交换 通信 要达到效果所需的 如何准确定位网络上的一台主机 eg：192.168.16.124 : 端口 ，定位到这个计算机上的某个资源 找到这个主机，如何传输数据？ 传输介质 javaweb：网页编程 网络编程：TCP/IP C/S 网络通信的两个要素 如何实现网络的通信？ 通信双方的地址： ip 端口号 eg：192.168.16.124：5900 规则：网络通信的协议 TCP/IP 参考模型 网络编程中两个主要的问题 如何准确定位到网络上的一台或者多台主机 找到主机之后如何进行通信 网络编程的要素 IP 和 端口号 ip 网络通信协议 udp，tcp 万物皆对象 ---Java IP地址 IP地址：InetAddress 唯一定位一台网络上计算机 127.0.0.1 ： 本机localhost ip地址的分类 IPv4/IPv6 IPv4 127.0.0.1 4个字节组成。 0~255，42亿个；30亿都在北美，亚洲4亿；2011年用尽IPv4 IPv6 fe80::d06b:cf88:8c85:7e09%9 128位；8个无符号整数！ eg: 2001:0bb2:aaaa:0015:0000:0000:1aaa:1321 公网（互联网） 私网（局域网） ABCD类地址 192.168.xxx.xxx 专门给组织内部使用的 域名：IP地址不容易记忆，用域名更方便 eg：我的blog域名为 www.kok-s0s.top package lesson01; import java.net.InetAddress; import java.net.UnknownHostException; //测试ip public class TestInetAddress { public static void main(String[] args) { try { //查询本机地址 InetAddress inetAddress1 = InetAddress.getByName("127.0.0.1"); System.out.println(inetAddress1); InetAddress inetAddress3 = InetAddress.getByName("localhost"); System.out.println(inetAddress1); InetAddress inetAddress4 = InetAddress.getLocalHost(); System.out.println(inetAddress1); //查询我的blog地址 InetAddress inetAddress2 = InetAddress.getByName("www.kok-s0s.top"); System.out.println(inetAddress2); //常用方法 System.out.println(inetAddress2.getAddress()); System.out.println(inetAddress2.getCanonicalHostName()); System.out.println(inetAddress2.getHostAddress()); System.out.println(inetAddress2.getHostName()); } catch (UnknownHostException e) { e.printStackTrace(); } } } 端口Port 端口表示计算机上的一个程序的进程： 不同的进程有不同的端口号！用来区分软件！ 规定范围 0~65535 * 2 可以 TCP:80 UDP:80 同时存在，但单个协议下，端口号不能冲突 端口分类 公有端口 0~1023 HTTP : 80 HTTPS : 443 FTP : 21 Telent : 23 程序注册端口：1024~49151，分配给用户或者程序 Tomcat : 8080 MySQL : 3306 Oracle : 1521 动态，私有：49152~65535 netstat -ano #查看所有的端口 netstat -ano|findstr "5900" #查看指定的端口 tasklist|findstr "8696" #查看指定端口的进程 Ctrl + Shift + ESC #快速打开任务管理器 package lesson01; import java.net.InetSocketAddress; public class TestInetSocketAddress { public static void main(String[] args) { InetSocketAddress inetSocketAddress1 = new InetSocketAddress("127.0.0.1", 8080); InetSocketAddress inetSocketAddress2 = new InetSocketAddress("localhost", 8080); System.out.println(inetSocketAddress1); System.out.println(inetSocketAddress2); System.out.println(inetSocketAddress1.getAddress()); System.out.println(inetSocketAddress1.getHostName());//地址 System.out.println(inetSocketAddress1.getPort());//端口 } } 通信协议 协议：约定，好比人们之间交流使用的普通话一样 网络通信协议：速率，传输码率，代码结构，传输控制······ 问题：有很多复杂的问题 能够大事化小：分层！ TCP/IP协议簇：一组协议 重要： TCP：用户传输协议 UDP：用户数据报协议 IP：网络互联协议 TCP与UDP的对比： TCP：打电话 连接，稳定 ‘三次握手’ ‘四次握手’ 最少需要三次，保证稳定连接！ A：你瞅啥？ B: 瞅你咋地？ A: 干一场！ A：我要走了 B：你真的要走了吗？ B: 你真的要走了！ A：我真的要走了！ 客户端 服务端 传输完成，释放连接，效率低 UDP：发短信 不连接，不稳定 客户端，服务端，没有明确的界限 不管有没有准备好，都可以发给你 DDOS：洪水攻击！（饱和攻击） TCP实现聊天 客户端 连接服务器 Socket 发送消息 package lesson02; import java.io.IOException; import java.io.OutputStream; import java.net.InetAddress; import java.net.Socket; //客户端 public class TcpClientDemo01 { public static void main(String[] args) { Socket socket=null; OutputStream os=null; try { //1. 要知道服务器的地址 InetAddress serverIP = InetAddress.getByName("127.0.0.1"); int port = 9999; //2. 创建一个socket连接 socket = new Socket(serverIP, port); //3. 发送消息 IO流 os = socket.getOutputStream(); os.write("kok-s0s,你好".getBytes()); } catch (IOException e) { e.printStackTrace(); } finally { if (socket != null) { try { socket.close(); } catch (IOException e) { e.printStackTrace(); } } if (os != null) { try { os.close(); } catch (IOException e) { e.printStackTrace(); } } } } } 服务器 建立服务的端口 ServerSocket 等待用户的连接 accept 接受用户的消息 package lesson02; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.net.ServerSocket; import java.net.Socket; //服务端 public class TcpServerDemo01 { public static void main(String[] args) throws IOException { ServerSocket serverSocket = null; Socket socket = null; InputStream is = null; ByteArrayOutputStream baos = null; try { //1. 客户端想知道我的地址，那我需要有一个地址 serverSocket = new ServerSocket(9999); while (true) { //2. 等待客户端请求连接 socket = serverSocket.accept(); //3. 读取客户端的消息 is = socket.getInputStream(); //管道流 baos = new ByteArrayOutputStream(); byte[] buffer = new byte[1024]; int len; while ((len = is.read(buffer)) != -1) { baos.write(buffer, 0, len); } System.out.println(baos.toString()); } } catch (IOException e) { e.printStackTrace(); } finally { //关闭资源 if (baos != null) { try { baos.close(); } catch (IOException e) { e.printStackTrace(); } } if (is != null) { try { is.close(); } catch (IOException e) { e.printStackTrace(); } } if (socket != null) { try { socket.close(); } catch (IOException e) { e.printStackTrace(); } } if (serverSocket != null) { try { serverSocket.close(); } catch (IOException e) { e.printStackTrace(); } } } } } TCP文件上传实现 下面的代码没有做异常处理 服务端 package lesson02; import java.io.File; import java.io.FileOutputStream; import java.io.InputStream; import java.io.OutputStream; import java.net.ServerSocket; import java.net.Socket; public class TcpServerDemo02 { public static void main(String[] args) throws Exception { //1. 创建服务 ServerSocket serverSocket = new ServerSocket(9000); //2. 监听客户端的连接 Socket socket = serverSocket.accept();//阻塞式监听，会一直等待客户端连接 //3. 获取输入流 InputStream is = socket.getInputStream(); //4. 文件输出 FileOutputStream fos = new FileOutputStream(new File("receive.jpg")); byte[] buffer = new byte[1024]; int len; while ((len = is.read(buffer)) != -1) { fos.write(buffer, 0, len); } //通知客户端 接受完毕了 OutputStream os = socket.getOutputStream(); os.write("接受完毕了，你可以断开了".getBytes()); //5. 关闭资源 os.close(); fos.close(); is.close(); socket.close(); serverSocket.close(); } } 客户端 package lesson02; import java.io.*; import java.net.InetAddress; import java.net.Socket; public class TcpClientDemo02 { public static void main(String[] args) throws Exception { //1. 创建一个socket连接 Socket socket = new Socket(InetAddress.getByName("127.0.0.1"), 9000); //2. 创建一个输出流 OutputStream os = socket.getOutputStream(); //3. 读取文件 FileInputStream fis = new FileInputStream(new File("kok-s0s.jpg")); //4. 写出文件 byte[] buffer = new byte[1024]; int len = 0; while ((len = fis.read(buffer)) != -1) { os.write(buffer, 0, len); } //通知服务器我已经结束了 socket.shutdownOutput();//传输已经完毕了 //确定服务器接受完毕，才能够断开连接 InputStream inputStream = socket.getInputStream(); //String byte[] ByteArrayOutputStream baos = new ByteArrayOutputStream(); byte[] buffer2 = new byte[1024]; int len2 = 0; while ((len2 = inputStream.read(buffer2)) != -1) { baos.write(buffer2, 0, len2); } System.out.println(baos.toString()); //5. 关闭资源 baos.close(); inputStream.close(); fis.close(); os.close(); socket.close(); } } Tomcat 服务端 自定义 S Tomcat服务器 S ：Java后台开发 客户端 自定义 C 浏览器 B UDP消息发送 发消息：无需连接，但需要知道对方的地址！ 发送端 package lesson03; import java.net.DatagramPacket; import java.net.DatagramSocket; import java.net.InetAddress; //不需要连接服务器 public class UdpA { public static void main(String[] args) throws Exception { //1. 建立一个Socket DatagramSocket socket = new DatagramSocket(); //2. 建个包 String msg = "hello,服务器！"; InetAddress localhost = InetAddress.getByName("localhost"); int port = 9090; //数据，数据的长度 起始 要发送给谁 DatagramPacket packet = new DatagramPacket(msg.getBytes(), 0, msg.getBytes().length, localhost, port); //3. 发送包 socket.send(packet); //4. 关闭流 socket.close(); } } 接收端 package lesson03; import java.net.DatagramPacket; import java.net.DatagramSocket; //接受方 需要等待客户端的连接，eg：类似取快递，要有固定地址才能送货 public class UdpB { public static void main(String[] args) throws Exception { //开放端口 DatagramSocket socket = new DatagramSocket(9090); //接收数据包 byte[] buffer = new byte[1024]; DatagramPacket packet = new DatagramPacket(buffer, 0, buffer.length); socket.receive(packet); System.out.println(packet.getAddress().getHostAddress()); System.out.println(new String(packet.getData(), 0, packet.getLength())); //关闭连接 socket.close(); } } UDP聊天实现 发送方循环发送 package chat; import java.io.BufferedReader; import java.io.InputStreamReader; import java.net.DatagramPacket; import java.net.DatagramSocket; import java.net.InetSocketAddress; public class UdpSender { public static void main(String[] args) throws Exception { DatagramSocket socket = new DatagramSocket(8888); //准备数据：控制台读取 BufferedReader reader = new BufferedReader(new InputStreamReader(System.in)); while (true) { String data = reader.readLine(); byte[] datas = data.getBytes(); DatagramPacket packet = new DatagramPacket(datas, 0, datas.length, new InetSocketAddress("localhost", 6666)); socket.send(packet); if (data.equals("cool")) break; } socket.close(); } } 接受方循环接受 package chat; import java.net.DatagramPacket; import java.net.DatagramSocket; public class UdpReceiver { public static void main(String[] args) throws Exception { DatagramSocket socket = new DatagramSocket(6666); while (true) { //准备接受包裹 byte[] container = new byte[1024]; DatagramPacket packet = new DatagramPacket(container, 0, container.length); socket.receive(packet);//阻塞式接受包裹 //断开链接 byebye byte[] data = packet.getData(); String receiveData = new String(data, 0, packet.getLength()); System.out.println(receiveData); if (receiveData.equals("cool")) break; } socket.close(); } } UDP多线程在线聊天 线程Sender package chat; import java.io.BufferedReader; import java.io.InputStreamReader; import java.net.DatagramPacket; import java.net.DatagramSocket; import java.net.InetSocketAddress; public class TalkSend implements Runnable { DatagramSocket socket = null; BufferedReader reader = null; private int formPort; private String toIP; private int toPort; public TalkSend(int formPort, String toIP, int toPort) { this.formPort = formPort; this.toIP = toIP; this.toPort = toPort; try { socket = new DatagramSocket(formPort); reader = new BufferedReader(new InputStreamReader(System.in)); } catch (Exception e) { e.printStackTrace(); } } @Override public void run() { while (true) { try { String data = reader.readLine(); byte[] datas = data.getBytes(); DatagramPacket packet = new DatagramPacket(datas, 0, datas.length, new InetSocketAddress(this.toIP, this.toPort)); socket.send(packet); if (data.equals("bye")) break; } catch (Exception e) { e.printStackTrace(); } } socket.close(); } } 线程Receiver package chat; import java.net.DatagramPacket; import java.net.DatagramSocket; import java.net.SocketException; public class TalkReceive implements Runnable { DatagramSocket socket = null; private int port; private String msgFrom; public TalkReceive(int port, String msgFrom) { this.port = port; this.msgFrom = msgFrom; try { socket = new DatagramSocket(port); } catch (SocketException e) { e.printStackTrace(); } } @Override public void run() { while (true) { try { //准备接受包裹 byte[] container = new byte[1024]; DatagramPacket packet = new DatagramPacket(container, 0, container.length); socket.receive(packet);//阻塞式接受包裹 //断开链接 byebye byte[] data = packet.getData(); String receiveData = new String(data, 0, packet.getLength()); System.out.println(msgFrom + ":" + receiveData); if (receiveData.equals("bye")) break; } catch (Exception e) { e.printStackTrace(); } } socket.close(); } } girl package chat; public class TalkA { public static void main(String[] args) { new Thread(new TalkSend(7777, "localhost", 9999)).start(); new Thread(new TalkReceive(8888, "boy")).start(); } } boy package chat; public class TalkB { public static void main(String[] args) { new Thread(new TalkSend(5555, "localhost", 8888)).start(); new Thread(new TalkReceive(9999, "girl")).start(); } } URL下载网络资源 url：统一资源定位符，定位互联网上的某一个资源 DNS 域名解析 协议://ip地址:端口/项目名/资源 # 可以少但不能多 package lesson04; //http://localhost:8080/kok-s0s/hello.txt import java.net.MalformedURLException; import java.net.URL; public class URLDemo01 { public static void main(String[] args) throws MalformedURLException { URL url = new URL("http://localhost:8080/kok-s0s/index.jsp?username=kok-s0s&password=123"); System.out.println(url.getProtocol()); //协议 System.out.println(url.getHost()); //主机ip System.out.println(url.getPort()); //端口 System.out.println(url.getPath()); //文件 System.out.println(url.getFile()); //全路径 System.out.println(url.getQuery()); //参数 } } 下载音乐 package lesson04; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.net.HttpURLConnection; import java.net.URL; public class UrlDown { public static void main(String[] args) throws IOException { //1. 下载地址 URL url = new URL("https://m701.music.126.net/20210330161559/0adf1c364da84a4b3b8d39adbbe7ee39/jdyyaac/obj/w5rDlsOJwrLDjj7CmsOj/7449465886/c45e/1cfa/63cc/e64fb7fc577f4ab0892b23ba6a7f6632.m4a"); //2. 连接到这个资源 HTTP HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection(); InputStream inputStream = urlConnection.getInputStream(); FileOutputStream fos = new FileOutputStream("ado.mp3"); byte[] buffer = new byte[1024]; int len; while ((len = inputStream.read(buffer)) != -1) { fos.write(buffer, 0, len); //写出这个资源 } fos.close(); inputStream.close(); urlConnection.disconnect(); //断开连接 } }
⭐Important 学习渠道_遇见狂神说B站视频 线程基本概念 线程 进程 多线程 多线程 多个同时在运行的任务 一个进程可以有多个线程，如视频中同时听声音，看图像，看弹幕，等等 Process与Thread 程序，是指令和数据的有序集合，其本身没有任何运行的含义，是一个静态的概念。 进程则是执行程序的一次执行过程，它是一个动态的概念。是系统资源分配的单位。 通常一个进程中可以包含若干个线程，当然一个进程中至少有一个线程，不然没有存在的意义。线程是CPU调度和执行的单位。 Tips：很多多线程是模拟出来的，真正的多线程是指有多个CPU，即多核，如服务器。如果是模拟出来的多线程，即在一个CPU的情况下，在同一个时间点，CPU只能执行一个代码，因为切换的很快，所以就有同时执行的错觉。 概念 线程就是独立的执行路径； 在程序运行时，即使没有自己创建线程，后台也会有很多个线程，如主线程，gc线程； main()称之为主线程，为系统的入口，用于执行整个程序； 在一个进程中，如果开辟了多个线程，线程的运行由调度器安排调度，调度器是与操作系统紧密相关的，先后顺序是不能人为干预的； 对同一份资源操作时，会存在资源抢夺的问题，需要加入并发控制； 线程会带来额外的开销，如CPU调度时间，并发控制开销； 每个线程在自己的工作内存交互，内存控制不当会造成数据不一致； 继承Thread类 Thread细节看Java SE 8帮助文档 package demo01; //创建线程方式一：继承Thread类，重写run()方法，调用start开启线程 //总结：注意，线程开启不一定立即执行，由CPU调度执行 public class TestThread extends Thread { @Override public void run() { //run方法线程体 for (int i = 0; i < 200; i++) { System.out.println("I'm watching you!" + i); } } public static void main(String[] args) { //main线程，主线程 //创建一个线程对象 TestThread testThread1 = new TestThread(); //调用start()方法开启线程 testThread1.start(); for (int i = 0; i < 1000; i++) { System.out.println("I'm learning!" + i); } } } 网图下载 package demo01; import org.apache.commons.io.FileUtils; import java.io.File; import java.io.IOException; import java.net.URL; //练习Thread，实现多线程同步下载图片 public class TestThread2 extends Thread { private String url; //网络图片地址 private String name; //保存的文件名 public TestThread2(String url, String name) { this.url = url; this.name = name; } @Override public void run() { WebDownloader webDownloader = new WebDownloader(); webDownloader.downloader(url, name); System.out.println("下载了文件名为：" + name); } public static void main(String[] args) { TestThread2 t1 = new TestThread2("https://www.kok-s0s.top/usr/uploads/2021/03/750247936.jpg", "1.jpg"); TestThread2 t2 = new TestThread2("https://www.kok-s0s.top/usr/uploads/2021/03/1165126803.jpg", "2.jpg"); TestThread2 t3 = new TestThread2("https://www.kok-s0s.top/usr/uploads/2021/03/564529999.jpg", "3.jpg"); //线程并不一定立即执行，由CPU进行调度 t1.start(); t2.start(); t3.start(); } } //下载器 class WebDownloader { //下载方法 public void downloader(String url, String name) { try { FileUtils.copyURLToFile(new URL(url), new File(name)); } catch (IOException e) { e.printStackTrace(); System.out.println("IO异常，downloader方法出现问题"); } } } 实现Runnable接口 package demo01; //创建线程方式2：实现runnable接口，重写run()方法，执行线程需要丢入runnable接口实现类，调用start()方法 public class TestThread3 implements Runnable { @Override public void run() { //run方法线程体 for (int i = 0; i < 200; i++) { System.out.println("I'm watching you!" + i); } } public static void main(String[] args) { //创建runnable接口的实现类对象 TestThread3 testThread3 = new TestThread3(); //创建线程对象，通过线程对象来开启我们的线程，代理 //Thread thread = new Thread(testThread3); //thread.start(); new Thread(testThread3).start(); for (int i = 0; i < 1000; i++) { System.out.println("I'm learning!" + i); } } } 继承Thread类 子类继承Thread类具备多线程能力 启动线程：子类对象.start() 不建议使用：避免OOP单继承局限性 实现Runnable接口 实现接口Runnable具有多线程能力 启动线程：传入目标对象+Thread对象.start() 推荐使用：避免单继承局限性，灵活方便，方便同一个对象被多个线程使用 一份资源，多个代理 初识并发问题 package demo01; //多个线程同时操作同一个对象 //买火车票的例子 //发现问题：多个线程操作同一个资源的情况下，线程不安全，数据紊乱。 public class TestThread4 implements Runnable { //票数 private int ticketNums = 10; @Override public void run() { while (true) { if (ticketNums <= 0) { break; } //模拟延时 try { Thread.sleep(200); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + "-->拿到了第" + ticketNums-- + "张票"); } } public static void main(String[] args) { TestThread4 ticket = new TestThread4(); new Thread(ticket, "小明").start(); new Thread(ticket, "老师").start(); new Thread(ticket, "黄牛").start(); } } 龟兔赛跑 package demo01; //模拟龟兔赛跑 public class Race implements Runnable { //胜利者 private static String winner; @Override public void run() { for (int i = 0; i <= 100; i++) { //模拟兔子休息 if (Thread.currentThread().getName().equals("兔子") && i % 10 == 0) { try { Thread.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } } //判断比赛是否结束 boolean flag = gameOver(i); //如果比赛结束，就停止程序 if (flag) { break; } System.out.println(Thread.currentThread().getName() + "-->跑了" + i + "步"); } } //判断是否完成比赛 private boolean gameOver(int steps) { //判断是否有胜利者 if (winner != null) { //已经存在胜利者 return true; } { if (steps >= 100) { winner = Thread.currentThread().getName(); System.out.println("winner is " + winner); return true; } } return false; } public static void main(String[] args) { Race race = new Race(); new Thread(race, "乌龟").start(); new Thread(race, "兔子").start(); } } 实现Callable接口 package demo02; //线程创建方式三：实现Callable接口 /* callable可以定义返回值，可以抛出异常 */ import org.apache.commons.io.FileUtils; import java.io.File; import java.io.IOException; import java.net.URL; import java.util.concurrent.*; public class TestCallable implements Callable<Boolean> { private String url; //网络图片地址 private String name; //保存的文件名 public TestCallable(String url, String name) { this.url = url; this.name = name; } @Override public Boolean call() { WebDownloader webDownloader = new WebDownloader(); webDownloader.downloader(url, name); System.out.println("下载了文件名为：" + name); return true; } public static void main(String[] args) throws ExecutionException, InterruptedException { TestCallable t1 = new TestCallable("https://www.kok-s0s.top/usr/uploads/2021/03/750247936.jpg", "1.jpg"); TestCallable t2 = new TestCallable("https://www.kok-s0s.top/usr/uploads/2021/03/1165126803.jpg", "2.jpg"); TestCallable t3 = new TestCallable("https://www.kok-s0s.top/usr/uploads/2021/03/564529999.jpg", "3.jpg"); //线程并不一定立即执行，由CPU进行调度 //创建执行服务 ExecutorService ser = Executors.newFixedThreadPool(3); //提交执行 Future<Boolean> r1 = ser.submit(t1); Future<Boolean> r2 = ser.submit(t2); Future<Boolean> r3 = ser.submit(t3); //获取结果 boolean rs1 = r1.get(); boolean rs2 = r2.get(); boolean rs3 = r3.get(); System.out.println(rs1); System.out.println(rs2); System.out.println(rs3); //关闭服务 ser.shutdownNow(); } } //下载器 class WebDownloader { //下载方法 public void downloader(String url, String name) { try { FileUtils.copyURLToFile(new URL(url), new File(name)); } catch (IOException e) { e.printStackTrace(); System.out.println("IO异常，downloader方法出现问题"); } } } 静态代理模式 package demo03; //静态代理 //真实对象和代理对象都要实现同一个接口 //代理对象要代理真实角色 //好处： //代理对象可以做很多真实对象做不了的事情 //真实对象专注于自己的事情 public class StaticProxy { public static void main(String[] args) { You you = new You();//你要结婚 //匿名写法 new Thread(() -> System.out.println("i love you!")).start(); new WeddingCompamy(new You()).HappyMarry(); // WeddingCompamy weddingCompamy = new WeddingCompamy(you); // weddingCompamy.HappyMarry(); } } interface Marry { //结婚 void HappyMarry(); } //真实角色 ，你要结婚。 class You implements Marry { @Override public void HappyMarry() { System.out.println("你想结婚吗？"); } } //代理角色，帮助你结婚 class WeddingCompamy implements Marry { //代理谁-->真实目标角色 private Marry target; public WeddingCompamy(Marry target) { this.target = target; } @Override public void HappyMarry() { before(); this.target.HappyMarry();//这就是真实对象 after(); } private void after() { System.out.println("结婚后，收尾款！"); } private void before() { System.out.println("布置婚礼现场"); } } Lambda表达式 package demo04; /* 推导lambda表达式 */ public class TestLambda1 { //3.静态内部类 static class Like2 implements ILike { @Override public void lambda() { System.out.println("I like lambda2!"); } } public static void main(String[] args) { ILike like = new Like(); like.lambda(); like = new Like2(); like.lambda(); //4.局部内部类 class Like3 implements ILike { @Override public void lambda() { System.out.println("I like lambda3!"); } } like = new Like3(); like.lambda(); //5.匿名内部类，没有类的名称，必须借助接口或者父类 like = new ILike() { @Override public void lambda() { System.out.println("I like lambda4!"); } }; like.lambda(); //6.用lambda简化 like = () -> { System.out.println("I like lambda5!"); }; like.lambda(); } } //1.定义一个函数式接口 interface ILike { void lambda(); } //2.实现类 class Like implements ILike { @Override public void lambda() { System.out.println("I like lambda!"); } } package demo04; public class Testlambda2 { public static void main(String[] args) { // ILove love = null; //1.lambda表示简化 ILove love = (int a, int b) -> { System.out.println("she love you-->" + a); }; //简化1.去掉参数类型 love = (a, b) -> { System.out.println("he love you-->" + a); System.out.println(b + "不喜欢你！"); }; //简化2.简化括号 // love = a -> { // System.out.println("do you love " + a); // System.out.println("但她不喜欢你了"); // }; //简化3.去掉花括号 //love = a -> System.out.println("i reall love " + a); love.love(40, 11); //总结： //lambda表达式只能有一行代码的情况下次才能简化成为一行，如果有多行，需要用代码块包裹 //前提接口是函数式接口（接口里只能有一个方法） //多个参数也可以去掉参数类型，要去掉就全都去掉了，必须加上括号 } } interface ILove { void love(int a, int b); } 线程五大状态 停止线程 不推荐使用JDK提供的stop()，destroy()方法。【已废弃】 推荐让线程自己停止下来 建议使用一个标志位进行终止变量 当flag==false，则终止线程运行。 package state; //测试stop //1.建议线程正常停止-->利用次数，不建议死循环 //2.建议使用标志位-->设置一个标志位 //3.不要使用stop或者destroy等过时或者JDK不建议使用的方法 public class TestStop implements Runnable { //设置一个标志位 private boolean flag = true; @Override public void run() { int i = 0; while (flag) { System.out.println("run...Thread!" + i++); } } //2.设置一个公开的方法停止线程，转换标志位 public void stop() { this.flag = false; } public static void main(String[] args) { TestStop testStop = new TestStop(); new Thread(testStop).start(); for (int i = 0; i < 1000; i++) { System.out.println("main线程运行中 " + i); if (i == 900) { //调用stop方法转换标志位，让线程停止 testStop.stop(); System.out.println("线程停止了"); } } } } 线程休眠——sleep sleep（时间）指定当前线程阻塞的毫秒数； sleep存在异常InterruptedException； sleep时间达到后线程进入就绪状态； sleep可以模拟网络延时，倒计时等。 每一个对象都有一个锁，sleep不会释放锁； package state; //模拟网络延时 作用：放大问题的发生性 public class TestSleep implements Runnable { //票数 private int ticketNums = 10; @Override public void run() { while (true) { if (ticketNums <= 0) { break; } //模拟延时 try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + "-->拿到了第" + ticketNums-- + "张票"); } } public static void main(String[] args) { TestSleep ticket = new TestSleep(); new Thread(ticket, "小明").start(); new Thread(ticket, "老师").start(); new Thread(ticket, "黄牛").start(); } } package state; import java.text.SimpleDateFormat; import java.util.Date; //模拟倒计时... public class TestSleep2 { public static void main(String[] args) { // try { // tenDown(); // } catch (InterruptedException e) { // e.printStackTrace(); // } //打印当前系统时间 Date startTime = new Date(System.currentTimeMillis());//获取系统当前时间 while (true) { try { Thread.sleep(1000); System.out.println(new SimpleDateFormat("HH:mm:ss").format(startTime)); startTime = new Date(System.currentTimeMillis());//更新当前时间 } catch (InterruptedException e) { e.printStackTrace(); } } } //模拟倒计时 public static void tenDown() throws InterruptedException { int num = 10; while (true) { Thread.sleep(1000); System.out.println(num--); if (num <= 0) { break; } } } } 线程礼让 礼让线程，让当前正在执行的线程暂停，但不阻塞 将线程从运行状态转为就绪状态 让CPU重新调度，礼让不一定成功！看CPU心情 package state; //测试礼让线程 // 礼让不一定成功，看CPU心情 public class TestYied { public static void main(String[] args) { MyYied myYied = new MyYied(); new Thread(myYied, "a").start(); new Thread(myYied, "b").start(); } } class MyYied implements Runnable { @Override public void run() { System.out.println(Thread.currentThread().getName() + "线程开始执行"); Thread.yield();//礼让 System.out.println(Thread.currentThread().getName() + "线程停止执行"); } } 线程强制执行__join Join合并线程，待此线程执行完成后，再执行其它线程，其它线程阻塞 类比于插队 package state; //测试join方法 类比于插队 public class TestJoin implements Runnable { @Override public void run() { for (int i = 0; i < 10; i++) { System.out.println("线程VIP来了" + i); } } public static void main(String[] args) throws InterruptedException { //启动我们的线程 TestJoin testJoin = new TestJoin(); Thread thread = new Thread(testJoin); thread.start(); //主线程 for (int i = 0; i < 50; i++) { if (i == 10) { thread.join();//插队 } System.out.println("main" + i); } } } 观测线程状态 Thread.State 线程状态。线程可以处于以下状态之一： NEW 尚未启动的线程出于此状态 RUNNABLE 在Java虚拟机中执行的线程出于此状态 BLOCKED 被阻塞等待监视器锁定的线程出于此状态 WAITING 正在等待另一个线程执行特定动作的线程出于此状态 TIMED_WAITING 正在等待另一个线程执行动作达到执行等待时间的线程出于此状态 TERMINATED 已退出的线程处于此状态 一个线程可以在给的那个时间点处于一个状态。这些状态是不反映任何操作系统线程状态的虚拟机状态。 package state; //观察测试线程的状态 public class TestState { public static void main(String[] args) throws InterruptedException { Thread thread = new Thread(() -> { for (int i = 0; i < 5; i++) { try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println("////////"); }); //观察状态 Thread.State state = thread.getState(); System.out.println(state); //观察启动后 thread.start();//启动线程 state = thread.getState(); System.out.println(state); //Run while (state != Thread.State.TERMINATED) { //只要线程不终止，就一直输出状态 Thread.sleep(100); state = thread.getState();//更新线程状态 System.out.println(state);//输出状态 } // thread.start(); //死亡的线程无法再次启动 } } 线程的优先级 Java提供一个线程调度器来监控程序中启动后进入就绪状态的所有线程，线程调度器按照优先级决定应该调度哪个线程来执行。 线程的优先级用数字表示，范围从1~10 Thread.MIN_PRIORITY = 1; Thread.MAX_PRIORITY = 10; Thread.NORM_PRIORITY = 5; 使用以下方式改变或获取优先级 getPriority() setPriority(int xxx) 优先级的设定建议在start()调度前 优先级低只是意味着获得调度的概率低，并不是优先级低就不会被调用了，主要看CPU的调度 question：性能倒置 package state; //测试线程的优先级 public class TestPriority { public static void main(String[] args) { //主线程默认优先级 System.out.println(Thread.currentThread().getName() + "-->" + Thread.currentThread().getPriority()); MyPriority myPriority = new MyPriority(); Thread t1 = new Thread(myPriority); Thread t2 = new Thread(myPriority); Thread t3 = new Thread(myPriority); Thread t4 = new Thread(myPriority); Thread t5 = new Thread(myPriority); Thread t6 = new Thread(myPriority); //先设置优先级，再启动 t1.start(); t2.setPriority(1); t2.start(); t3.setPriority(4); t3.start(); t4.setPriority(Thread.MAX_PRIORITY); t4.start(); t5.setPriority(8); t5.start(); t6.setPriority(7); t6.start(); } } class MyPriority implements Runnable { @Override public void run() { System.out.println(Thread.currentThread().getName() + "-->" + Thread.currentThread().getPriority()); } } 守护（daemon）线程 线程分为用户线程和守护线程 虚拟机必须确保用户线程（如main线程）执行完毕 虚拟机不用等待守护线程执行完毕 如：后台记录操作日志，监控内存，垃圾回收等待··· package state; //测试守护线程 //eg:上帝守护你 public class TestDaemon { public static void main(String[] args) { God god = new God(); You you = new You(); Thread thread = new Thread(god); thread.setDaemon(true); //默认是false表示是用户线程，正常的线程都是用户线程 thread.start();//上帝守护线程启动 new Thread(you).start(); //你 用户线程启动··· } } //上帝 class God implements Runnable { @Override public void run() { while (true) { System.out.println("上帝保护你"); } } } //你 class You implements Runnable { @Override public void run() { for (int i = 0; i < 35600; i++) { System.out.println("一生开心的活着！"); } System.out.println("-==========say goodbye!"); } } 线程同步 线程同步机制 并发：同一个对象被多个线程同时操作 队列 锁 eg：景区厕所排队（厕所可以关起来不让其他人进入） 线程同步 由于同一进程的多个线程共享一块存储空间，在带来方便的同时，也带来了访问冲突问题，为了保证数据在方法中被访问时的正确性，在访问时加入锁机制synchronized，当一个线程获得对象的排它锁，独占资源，其它线程必须等待，使用后释放锁即可，存在以下问题： 一个线程持有锁会导致其它所有需要此锁的线程挂起； 在多线程竞争下，加锁，释放锁会导致比较多的上下文切换和调度延时，引起性能问题； 如果一个优先级高的线程等待一个优先级低的线程释放锁会导致优先级倒置，引起性能问题； 三大不安全案列 买票不安全 package syn; //不安全的买票 //线程不安全，有负数 public class UnsafeBuyTicket { public static void main(String[] args) { BuyTicket station = new BuyTicket(); new Thread(station, "小明").start(); new Thread(station, "黄牛").start(); new Thread(station, "teacher").start(); } } class BuyTicket implements Runnable { boolean flag = true;//外部停止方式 //票 private int ticketNums = 10; @Override public void run() { //买票 while (flag) { try { buy(); } catch (Exception e) { e.printStackTrace(); } } } private void buy() { //判断是否有票 if (ticketNums <= 0) { return; } //sleep可以放大问题的发生性 try { Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } //买票 System.out.println(Thread.currentThread().getName() + "拿到" + ticketNums--); } } 取钱不安全 package syn; //不安全的取钱 //两个人去银行取钱（需要账户） public class UnsafeBank { public static void main(String[] args) { //账户 Account account = new Account(100, "零用钱"); Drawing you = new Drawing(account, 100, "you"); Drawing girlFriend = new Drawing(account, 100, "girlFriend"); you.start(); girlFriend.start(); } } //账户 class Account { int money;//余额 String name;//卡名 public Account(int money, String name) { this.money = money; this.name = name; } } //银行：模拟取款 class Drawing extends Thread { //继承Thread类，可以使用该类中的方法 Account account;//账户 int drawingMoney;//去了多少钱 int nowMoney; //现在手里有多少钱 public Drawing(Account account, int drawingMoney, String name) { super(name); this.account = account; this.drawingMoney = drawingMoney; } //取钱 @Override public void run() { //判断是否还有钱 if (account.money - drawingMoney < 0) { System.out.println(Thread.currentThread().getName() + "钱不够，无法取钱！"); return; } //sleep可以放大问题的发生性 try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } //卡内余额 = 余额 - 你取的钱 account.money = account.money - drawingMoney; //你手里的钱 nowMoney = nowMoney + drawingMoney; System.out.println(account.name + "余额为" + account.money); // Thread.currentThread().getName() = this.getName() System.out.println(this.getName() + "手里的钱为" + nowMoney); } } 线程不安全 package syn; import java.util.ArrayList; import java.util.List; //线程不安全的集合 public class UnsafeList { public static void main(String[] args) { List<String> list = new ArrayList<String>(); for (int i = 0; i < 10000; i++) { new Thread(() -> { list.add(Thread.currentThread().getName()); //可能会存在有线程覆盖了，导致元素减少 }).start(); } try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(list.size()); } } 同步方法及同步块 安全买票 package syn; //不安全的买票 //线程不安全，有负数 public class UnsafeBuyTicket { public static void main(String[] args) { BuyTicket station = new BuyTicket(); new Thread(station, "小明").start(); new Thread(station, "黄牛").start(); new Thread(station, "teacher").start(); } } class BuyTicket implements Runnable { boolean flag = true;//外部停止方式 //票 private int ticketNums = 10; @Override public void run() { //买票 while (flag) { try { Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } try { buy(); } catch (Exception e) { e.printStackTrace(); } } } //synchronized 同步方法，锁的是this private synchronized void buy() { //判断是否有票 if (ticketNums <= 0) { return; } //sleep可以放大问题的发生性 try { Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } //买票 System.out.println(Thread.currentThread().getName() + "拿到" + ticketNums--); } } 安全取钱 package syn; //不安全的取钱 //两个人去银行取钱（需要账户） public class UnsafeBank { public static void main(String[] args) { //账户 Account account = new Account(10000, "零用钱"); Drawing you = new Drawing(account, 100, "you"); Drawing girlFriend = new Drawing(account, 100, "girlFriend"); you.start(); girlFriend.start(); } } //账户 class Account { int money;//余额 String name;//卡名 public Account(int money, String name) { this.money = money; this.name = name; } } //银行：模拟取款 class Drawing extends Thread { //继承Thread类，可以使用该类中的方法 Account account;//账户 int drawingMoney;//去了多少钱 int nowMoney; //现在手里有多少钱 public Drawing(Account account, int drawingMoney, String name) { super(name); this.account = account; this.drawingMoney = drawingMoney; } //synchronized 同步方法 默认锁的是this它本身 //取钱 @Override public void run() { //锁的对象是变化的量 需要增删改的 //synchronized 同步块可以锁任何对象 synchronized (account) { //判断是否还有钱 if (account.money - drawingMoney < 0) { System.out.println(Thread.currentThread().getName() + "钱不够，无法取钱！"); return; } //sleep可以放大问题的发生性 try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } //卡内余额 = 余额 - 你取的钱 account.money = account.money - drawingMoney; //你手里的钱 nowMoney = nowMoney + drawingMoney; System.out.println(account.name + "余额为" + account.money); // Thread.currentThread().getName() = this.getName() System.out.println(this.getName() + "手里的钱为" + nowMoney); } } } 线程安全 package syn; import java.util.ArrayList; import java.util.List; //线程不安全的集合 public class UnsafeList { public static void main(String[] args) { List<String> list = new ArrayList<String>(); for (int i = 0; i < 10000; i++) { new Thread(() -> { synchronized (list){ list.add(Thread.currentThread().getName()); } // list.add(Thread.currentThread().getName()); //可能会存在有线程覆盖了，导致元素减少 }).start(); } try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(list.size()); } } CopyOnWriteArrayList package syn; import java.util.concurrent.CopyOnWriteArrayList; //测试JUC安全类型的集合 安全的 public class TestJUC { public static void main(String[] args) { CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<String>(); for (int i = 0; i < 10000; i++) { new Thread(() -> { list.add(Thread.currentThread().getName()); }).start(); } try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(list.size()); } } 死锁 多个线程各自占有一些共享资源，并且互相等待其它线程占有的资源才能运行，而导致两个或者多个线程都在等待对方释放资源，都停止执行的情况。某一个同步块同时拥有"两个以上对象的锁"时，就可能发生“死锁”的问题。 产生死锁的四个必要条件： 互斥条件：一个资源每次只能被一个进程使用。 请求与保持条件：一个进程因请求资源而阻塞时，对已获得的资源保持不放。 不剥夺条件：进程已获得的资源，在未使用完之前，不能强行剥夺。 循环等待条件：若干进程之间形成一种头尾相接的循环等待资源关系。 死锁避免方法：只需要破坏上述四个条件中的任意一个或多个条件就可以避免死锁发生。 package thread; //死锁：多个线程互相抱着对方需要的资源，然后形成僵持 public class DeadLock { public static void main(String[] args) { Makeup g1 = new Makeup(0, "灰姑娘"); Makeup g2 = new Makeup(1, "白雪公主"); g1.start(); g2.start(); } } //口红 class Lipstick { } //镜子 class Mirror { } class Makeup extends Thread { //需要的资源只有一份，用static来保证只有一份 static Lipstick lipstick = new Lipstick(); static Mirror mirror = new Mirror(); int choice;//选择 String girlName; //使用化妆品的人 Makeup(int choice, String girlName) { this.choice = choice; this.girlName = girlName; } @Override public void run() { //化妆 try { makeup(); } catch (Exception e) { e.printStackTrace(); } } //化妆，互相持有对方的锁，就是需要拿到对方的资源 private void makeup() { if (choice == 0) { synchronized (lipstick) {//获得口红的锁 System.out.println(this.girlName + "获得口红的锁"); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } // synchronized (mirror) {//一秒钟后想获得镜子 // System.out.println(this.girlName + "获得镜子的锁"); // } } synchronized (mirror) {//一秒钟后想获得镜子 System.out.println(this.girlName + "获得镜子的锁"); } } else { synchronized (mirror) {//获得镜子的锁 System.out.println(this.girlName + "获得镜子的锁"); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } // synchronized (lipstick) {//一秒钟后想获得口红 // System.out.println(this.girlName + "获得口红的锁"); // } } synchronized (lipstick) {//一秒钟后想获得口红 System.out.println(this.girlName + "获得口红的锁"); } } } } Lock锁 从JDK5.0开始，Java提供了更强大的线程同步机制——通过显式定义同步锁对象来实现同步。同步锁使用Lock对象充当。 java.util.concurrent.locks.Lock接口时控制多个线程对共享资源进行访问的工具。锁提供了对共享资源的独占访问，每次只能有一个线程对Lock对象加锁，线程开始访问共享资源之前应先获得Lock对象。 ReentrantLock可重入锁类实现了Lock，它拥有与synchronized相同的并发性和内存语义，在实现线程安全的控制中，比较常用的是ReentrantLock，可以显式加锁，释放锁。 package learn_lock; import java.util.concurrent.locks.ReentrantLock; public class TestLock { public static void main(String[] args) { TeskLock2 teskLock2 = new TeskLock2(); new Thread(teskLock2).start(); new Thread(teskLock2).start(); new Thread(teskLock2).start(); } } class TeskLock2 implements Runnable { //定义lock锁 private final ReentrantLock lock = new ReentrantLock(); int ticketNums = 10; @Override public void run() { while (true) { try { lock.lock();//加锁 if (ticketNums > 0) { try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(ticketNums--); } else { break; } } finally { //解锁 lock.unlock(); } } } } 一般使用样例 class A { private final ReentrantLock lock = new ReentrantLock(); public void m() { try { lock.lock(); //保证线程安全的代码 } finally { lock.unlock(); //如果同步代码有异常，要将unlock()写入finally语句块 } } } synchronized与Lock的对比 Lock是显式锁（手动开启和关闭锁，不能忘记关闭锁），synchronized是隐式锁，出了作用域自动释放 Lock只有代码块锁，synchronized有代码块锁和方法锁 使用Lock锁，JVM将花费较少的时间来调度线程，性能更好。并且具有更好的扩展性（提供更多的子类） 优先使用顺序： Lock > 同步代码块（已经进入了方法体，分配了相应资源）> 同步方法（在方法体之外） 线程通信 生产者消费者问题 video 管程法 并发协作模型 “生产者/消费者模式”--->管程法 生产者：负责生产数据的模块（可能是方法，对象，线程，进程）； 消费者：负责处理数据的模块（可能是方法，对象，线程，进程）； 缓冲区：消费者不能直接使用生产者的数据，它们之间有个“缓冲区” 生产者将生产好的数据放入缓冲区，消费者从缓冲区拿出数据。 package learn_lock; //生产者消费者模型-->利用缓冲区解决：管程法 //生产者，消费者，产品，缓冲区 public class TestPC { public static void main(String[] args) { SynContainer container = new SynContainer(); new Productor(container).start(); new Consumer(container).start(); } } //生产者 class Productor extends Thread { SynContainer container; public Productor(SynContainer container) { this.container = container; } //生产 @Override public void run() { for (int i = 0; i < 100; i++) { System.out.println("生产了" + i + "只鸡"); container.push(new Chicken(i)); } } } //消费者 class Consumer extends Thread { SynContainer container; public Consumer(SynContainer container) { this.container = container; } //消费 @Override public void run() { for (int i = 0; i < 100; i++) { System.out.println("消费了-->" + container.pop().id + "只鸡"); } } } //产品 class Chicken { int id;//产品编号 public Chicken(int id) { this.id = id; } } //缓冲区 class SynContainer { //需要一个容器大小 Chicken[] chickens = new Chicken[10]; //容器计数器 int count = 0; //生产者放入产品 public synchronized void push(Chicken chicken) { //如果容器满了，就需要等待消费者消费 if (count == chickens.length) { //通知消费者消费，生产等待 try { this.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } //如果没有满，我们就需要丢入产品 chickens[count] = chicken; count++; //可以通知消费者消费了 this.notifyAll(); } //消费者消费产品 public synchronized Chicken pop() { //判断能否消费 if (count == 0) { //等待生产者生产，消费者等待 try { this.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } //如果可以消费 count--; Chicken chicken = chickens[count]; //吃完了，通知生产者生产 this.notifyAll(); return chicken; } } 信号灯法 并发协作模型 “生产者/消费者模式”--->信号灯法 eg：红绿灯 package learn_lock; //测试生产者消费者问题2：信号灯法，标志位flag解决 public class TestPC2 { public static void main(String[] args) { TV tv = new TV(); new Player(tv).start(); new Watcher(tv).start(); } } //生产者--->演员 class Player extends Thread { TV tv; public Player(TV tv) { this.tv = tv; } @Override public void run() { for (int i = 0; i < 20; i++) { if (i % 2 == 0) { this.tv.play("快乐大本营播放中"); } else { this.tv.play("video：tv shows"); } } } } //消费者--->观众 class Watcher extends Thread { TV tv; public Watcher(TV tv) { this.tv = tv; } @Override public void run() { for (int i = 0; i < 20; i++) { tv.watch(); } } } //产品--->节目 class TV { //演员表演，观众等待 T //观众观看，演员等待 F String voice;//表演的节目 boolean flag = true; //表演 public synchronized void play(String voice) { if (!flag) { try { this.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println("演员表演了：" + voice); //通知观众观看 this.notifyAll();//通知唤醒 this.voice = voice; this.flag = !this.flag; } //观看 public synchronized void watch() { if (flag) { try { this.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println("watcher观看了：" + voice); //通知演员表演 this.notifyAll(); this.flag = !this.flag; } } 线程池 使用线程池 背景：经常创建和销毁，使用量特别大的资源，比如并发情况下的线程，对性能影响很大。 思路：提前创建好多个线程，放入线程池中，使用时直接获取，使用完放回池中。可以避免频繁创建销毁，实现重复利用。类似生活中的公共交通工具。 好处： 提高响应速度（减少了创建新线程的时间） 降低资源消耗（重复利用线程池中线程，不需要每次都创建） 便于线程管理（···） corePoolSize：核心池的大小 maximumPoolSize：最大线程数 keepAliveTime：线程没有任务时最多保持多长时间后会终止 JDK5.0起提供了线程池相关API：ExecutorService和Executors ExecutorService：真正的线程池接口。常见子类ThreadPoolExecutor void executr(Runnable command)：执行任务/命令，没有返回值，一般用来执行Runnable <T> Future<T> submit(Callable<T> task)：执行任务，有返回值，一般用来执行Callable void shutdown()：关闭连接池 Executors：工具类，线程池的工厂类，用于创建并返回不同类型的线程池 package learn_lock; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; //测试线程池 public class TestPool { public static void main(String[] args) { //1.创建服务，创建线程池 //newFixedThreadPool 参数为：线程池大小 ExecutorService service = Executors.newFixedThreadPool(10); service.execute(new MyThread()); service.execute(new MyThread()); service.execute(new MyThread()); service.execute(new MyThread()); service.execute(new MyThread()); //2.关闭连接 service.shutdown(); } } class MyThread implements Runnable { @Override public void run() { System.out.println(Thread.currentThread().getName()); } } 总结 package thread; import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; import java.util.concurrent.FutureTask; //回顾总结线程的创建 public class ThreadNew { public static void main(String[] args) { new MyThread1().start(); new Thread(new MyThread2()).start(); FutureTask<Integer> futureTask = new FutureTask<Integer>(new MyThread3()); new Thread(futureTask).start(); try { Integer integer = futureTask.get(); System.out.println(integer); } catch (InterruptedException e) { e.printStackTrace(); } catch (ExecutionException e) { e.printStackTrace(); } } } //1.继承Thread类 class MyThread1 extends Thread { @Override public void run() { System.out.println("MyThread1"); } } //2.实现Runnable接口 class MyThread2 implements Runnable { @Override public void run() { System.out.println("MyThread2"); } } //3.实现Callable接口 class MyThread3 implements Callable<Integer> { @Override public Integer call() throws Exception { System.out.println("MyThread3"); return 100; } } 未完待续···
⭐Important 学习渠道_遇见狂神说B站视频 JavaBase 注释 标识符 数据类型 基本类型（primitive type） 引用类型（reference type） 数据类型面试相关 类型转换 变量 变量作用域 常量 变量的命名规范 运算符 包机制 JavaDoc Java流程控制 用户交互Scanner Scanner进阶的使用 顺序结构 选择结构 原文件及switch实例 反编译结果 循环结构 while do...while for break && continue 练习 Java方法详解 什么是方法？ 方法调用 方法的重载 命令行传参 可变参数 递归 Java数组 什么是数组 数组的声明和创建 三种初始化及内存分析 下标越界及小结 数组的使用 二维数组 Arrays类讲解 冒泡排序 稀疏数组 面向对象编程 什么是面向对象 回顾与新知 类与对象的创建 构造器详解 创建对象内存分析 封装详解 什么是继承 Super详解 方法重写 什么是多态 instanceof和类型转换 static关键字详解 抽象类 接口的定义与实现 N种内部类 异常 Error和Exception 捕获和抛出异常 自定义异常 经验总结 JavaBase 注释 单行注释 //这是一个注释 多行注释 /* 这是个多行注释 */ 文档注释JavaDoc /** *@Description Hello *@Author kok-s0s */ 标识符 关键字 java所有的组成部分都需要名字。类名，变量名以及方法名都被称为标识符 标识符注意点 所有的标识符都应该以字母（A-Z或者a-z），美元符（$），或者下划线（_）开始 首字符之后可以是字母，美元符，下划线或数字的任何字符组合 不能使用关键字作为变量名或方法名 标识符是大小写敏感的 合法标识符eg：age，$salary，_value，__1_value 非法标识符eg：123abc，-salary，#abc public static void main(String[] args) { String 王者荣耀 = "最强王者"; System.out.println(王者荣耀); } 可以使用中文命名，但是一般不建议使用，也不建议使用拼音表达，因为很low 数据类型 强类型语言 要求变量的使用要严格符合规定，所有变量都必须先定义后才能使用 弱类型语言 与强类型相反 eg: JavaScript 基本类型（primitive type） 数值类型 整数类型 byte 占1个字节范围 int 占4个字节范围 short 占2个字节范围 long 占8个字节范围 浮点类型 float 占4个字节范围 double 占8个字节范围 字符类型 char 占2个字节范围 boolean类型 占1位 （true or false） 引用类型（reference type） 类 接口 数组 位（bit）：是计算机内部数据存储的最小单位，11001100是一个八位二进制数。 字节（byte）：是计算机中数据处理的基本单位，习惯商用大写B来表示。 1B（byte，字节）=8bit（位） 字符：是指计算机中使用的字母，数字 ，字和符号。 //八大基本数据类型 //整数 int num1 = 10; byte num2 = 20; short num3 = 30; long num4 = 30L; //long类型要在数字后面加个L //小数：浮点数 float num5 = 50.1F; //float类型要在数字后面加个F double num6 = 3.1415926; //字符 char name = '人'; //字符串，String不是关键字，是个类 //String name1 = "人类"; //布尔值：是非 boolean flag = true; //boolean flag = false; 数据类型面试相关 //整数扩展： 进制 二进制0b 十进制 八进制0 十六进制0x int i = 10; int i2 = 010; //八进制0 int i3 = 0x10; //十六进制0x 0~9 A~F 16 System.out.println(i); System.out.println(i2); System.out.println(i3); System.out.println("============================"); //================================================ //浮点数扩展？ 银行业务怎么表示？ 钱 //BigDecimal 数学工具类 //================================================ //float 有限 离散 舍入误差 大约 接近但不等于 //double //最好完全使用浮点数进行比较 //最好完全使用浮点数进行比较 //最好完全使用浮点数进行比较 float f = 0.1f; //0.1 double d = 1.0 / 10; //0.1 System.out.println(f == d); //false System.out.println(f); System.out.println(d); float d1 = 231212312312312f; float d2 = d1 + 1; System.out.println(d1 == d2); //true //================================================ //字符扩展？ //================================================ char c1 = 'a'; char c2 = '中'; System.out.println(c1); System.out.println((int) c1); //强制转换 System.out.println(c2); System.out.println((int) c2); //强制转换 //所有的字符本质还是数字 //编码 Unicode 2个字节 可表示65536个字符 Excel 2 16 = 65536 //Unicode 表 （97 = a 65 = A） //U0000 UFFFF char c3 = '\u0061'; //Unicode编码表示方式 System.out.println(c3); //a //转义字符 // \t 制表符 // \n 换行符 System.out.println("Hello\tWorld"); System.out.println("Hello\nWorld"); // System.out.println("================================"); String sa = new String("hello world"); String sb = new String("hello world"); System.out.println(sa == sb); String sc = "hello world"; String sd = "hello world"; System.out.println(sc == sd); //对象 从内存分析 //布尔值扩展 boolean flag = true; if(flag){ flag=false; }//老手 //Less is more 代码要精简易读 类型转换 由于java是强类型语言，所以要进行有些运算的时候的，需要用到类型转换。 低 ------------------------------------高 byte,short,char->int->long->float->double 运算中，不同类型的数据需要先转化为同一类型，然后进行运算。 强制类型转换 自动类型转换 int i = 128; byte b = (byte) i; System.out.println(i); System.out.println(b); //内存溢出 //强制转换 （类型）变量名 高--低 //自动转换 低--高 //转换的时候避免内存溢出 /* 注意点 1.无法对boolean类型进行转换 2.不能把对象类型转换为不相干的类型 3.在把高容量转换到低容量的时候，强制转换 4.转换的时候可能存在内存溢出，或者精度问题！ */ System.out.println("================"); System.out.println((int) 23.7); //23 System.out.println((int) -35.89f); //-45 System.out.println("================"); char c = 'a'; int d = c + 1; System.out.println(d); System.out.println((char) d); //操作数比较大的时候，注意溢出问题。 //JDK7新特性，数字之间可以用下划线分割 int money = 10_0000_0000; int years = 20; int total = money * years; System.out.println(total); //-1474836480 计算的时候会溢出 long total2 = money * years; //默认是int，转换之前已经存在问题了？ System.out.println(total2); //-1474836480 long total3 = money * ((long) years); //先把一个数转换为long System.out.println(total3); //20000000000 //L l //都是用L表示long类型 变量 变量：既可以变化的量。 Java是一种强类型语言，每个变量都必须声明其类型。 java变量是程序中最基本的存储单元，其要素包括变量名，变量类型和作用域。 type varName [=value] [{,varName[=value]}] 数据类型 变量名 = 值；可以使用逗号隔开来声明多个同类型变量 tips 每个变量都有类型。类型可以是基本类型。也可以是引用类型。 变量名必须是合法的标识符。 变量声明是一条完整的语句，因此每一个声明都必须以分号结束。 变量作用域 类变量 实例变量 局部变量 //类变量 static static double salary = 2500; //属性：变量 //实例变量：从属于对象；如果不自行初始化，这个类型的默认值 0 0 0 //布尔值：默认是false //除了基本类型，其余的都是null String name1; int age; //main方法 public static void main(String[] args) { //int a,b,c; //int a = 1, b = 2, c = 3; //程序可读性 String name = "kok-s0s"; char x = 'x'; double pi = 3.14; //局部变量：必须声明和初始化值 int i = 10; System.out.println(i); //变量类型 Demo05 demo05 = new Demo05(); System.out.println(demo05.name1); System.out.println(demo05.age); //类变量 static System.out.println(salary); } //其它方法 public void add() { } 常量 常量（Constant）：初始化（initialize）后不能在改变值！不会变动的值。 所谓常量可以理解成一种特殊的变量，它的值被设定后，在程序运行过程中不允许被改变。 final 常量名=值 final double PI=3.14 常量名一般使用大写字符 //修饰符，不存在先后顺序 static final double PI = 3.14; final static double height = 100; public static void main(String[] args) { System.out.println(PI); System.out.println(height); } 变量的命名规范 所有变量，方法，类型：见名知意 类成员变量：首字母小写和驼峰原则：monthSalary除了第一个单词以外，后面的单词首字母大写 wrong:lastname right:lastName 局部变量：首字母小写和驼峰原则 常量：大写字母和下划线：MAX_VALUE 类名：首字母大写和驼峰原则：Man,GoodMan 方法名：首字母小写和驼峰原则：run(),runRun 运算符 算术运算符: + - * / % ++ -- 赋值运算符: = 关系运算符: > < >= <= == != instanceof 逻辑运算符: && || ! 位运算符: & | ^ ~ >> << >>> 条件运算符: ? : 扩展赋值运算符: += -= *= /= //++ -- 自增，自减 一元运算符 int a = 3; int b = a++; //执行完这行代码后，先给b赋值，a再自增 //a++ a = a + 1 System.out.println(a); int c = ++a; //执行完这行代码前，a先自增，再给b赋值 System.out.println(a); System.out.println(b); System.out.println(c); //幂运算 2^3 2*2*2 = 8 double p = Math.pow(2, 3); System.out.println(p); //++ -- 自增，自减 一元运算符 int a = 3; int b = a++; //执行完这行代码后，先给b赋值，a再自增 //a++ a = a + 1 System.out.println(a); int c = ++a; //执行完这行代码前，a先自增，再给b赋值 System.out.println(a); System.out.println(b); System.out.println(c); //幂运算 2^3 2*2*2 = 8 double p = Math.pow(2, 3); System.out.println(p); /* A = 0011 1100 b = 0000 1101 ----------------------- A&B 0000 1100 A|B 0011 1101 A^B 0011 0001 ~B 1111 0010 2*8 = 16 2*2*2*2 << >> 0000 0000 0 0000 0001 1 0000 0010 2 0000 0011 3 0000 0100 4 0000 1000 8 0001 0000 16 */ System.out.println(2 << 3); //16 int a = 10; int b = 25; a += b; System.out.println(a); a -= b; System.out.println(a); //字符串连接符 + ，String System.out.println("" + a + b); System.out.println(a + b + ""); //x ? y : z //如果x==true，则结果为y，否则结果为z int score = 80; String type = score < 60 ? "不及格" : "及格"; //if System.out.println(type); //优先级 () 包机制 为了更好地组织类，Java提供了包机制，用于区别类名的命名空间。 包语句的语法格式为： ​ package pkg1[. pkg2[. pkg3...]] 一般利用公司域名倒置作为包名 为了能够使用某一个包的成员，我们需要在Java程序中明确导入该包。使用import语句可以完成此功能 ​ import package1[.package...].(classname|*); //*为通配符 JavaDoc javadoc命令是使用来生成自己的API帮助文档的 参数信息 @author 作者名 @version 版本号 @since 指明需要最早使用的jdk版本 @param 参数名 @return 返回值情况 @throws 异常抛出情况 Windows下cmd命令行生成javaDoc文件操作： eg:javadoc -encoding UTF-8 -charset UTF-8 Demo07.java IDEA自动生成JavaDoc文件操作： learn 快捷键Ctrl+\对着一个函数使用可直接生成相应的JavaDoc Java流程控制 用户交互Scanner java.util.Scanner是java5的新特征，能够通过Scanner类来获取用户的输入 基本语法： Scanner s = new Scanner(System.in); 通过Scanner类的`next()与nextLine()方法获取输入的字符串，在读取前我们一般需要使用hasNext()与hasNextLine判断是否还有输入的数据。 next() 一定要读取到有效字符后才可以结束输入。 对输入有效字符之前遇到的空白，next()方法会自动将其去掉。 只有输入有效字符后才能将其后面输入的空白作为分隔符或者结束符。 next()不能得到带有空格的字符串。 package process_control; import java.util.Scanner; public class Demo01 { public static void main(String[] args) { //创建一个扫描器对象，用于接收键盘数据 用完即使关闭，因为输入输出（IO）流占用资源多 Scanner scanner = new Scanner(System.in); System.out.println("使用next方式接受："); //判断用户有没有输入字符串 if (scanner.hasNext()) { String str = scanner.next(); System.out.println("输出的内容为：" + str); } //凡是属于IO流的类如果不关闭会一直占用资源，养成好习惯用完就关掉 scanner.close(); } } nextLine() 以`Enter为结束符，即使用nextLine()方法返回的是输入回车之前的所有字符。 可以获得空白。 package process_control; import java.util.Scanner; public class Demo02 { public static void main(String[] args) { //从键盘接受数据 Scanner scanner = new Scanner(System.in); System.out.println("使用nextLine方式接受："); //判断是否还有输入 if (scanner.hasNextLine()) { String str = scanner.nextLine(); System.out.println("输出的内容为：" + str); } scanner.close(); } } 方便的写法 package process_control; import java.util.Scanner; public class Demo03 { public static void main(String[] args) { //从键盘接受数据 Scanner scanner = new Scanner(System.in); System.out.println("请输入数据："); String str = scanner.nextLine(); System.out.println("输出的内容为：" + str); scanner.close(); } } Scanner进阶的使用 package process_control; import java.util.Scanner; public class Demo04 { public static void main(String[] args) { Scanner scanner = new Scanner(System.in); //从键盘接受数据 int i = 0; float f = 0.0f; System.out.println("请输入整数："); //if...else... if (scanner.hasNextInt()) { i = scanner.nextInt(); System.out.println("整数数据：" + i); } else { System.out.println("输入的不是整数数据！"); } System.out.println("请输入小数："); //if...else... if (scanner.hasNextFloat()) { f = scanner.nextFloat(); System.out.println("小数数据：" + f); } else { System.out.println("输入的不是小数数据！"); } scanner.close(); } } package process_control; import java.util.Scanner; public class Demo05 { public static void main(String[] args) { //我们可以输入多个数字，并求其总和与平均数，每输入一个人数字用回车确认，通过输入非数字来结束输入并输出执行结果。 Scanner scanner = new Scanner(System.in); //和 double sum = 0; //计算输入了多少个数字 int m = 0; //通过循环判断是否还有输入，并在里面对每一次进行求和统计 while (scanner.hasNextDouble()) { double x = scanner.nextDouble(); m = m + 1;//m++ sum = sum + x;//sum+=x; System.out.println("你输入了第" + m + "个数据，当前结果为" + sum); } System.out.println(m + "个数的和为" + sum); System.out.println(m + "个数的平均值是" + (sum / m)); scanner.close(); } } 顺序结构 package struct; public class ShunXuDemo { public static void main(String[] args) { System.out.println("nice"); System.out.println("nice"); System.out.println("nice"); System.out.println("nice"); System.out.println("nice"); } } 选择结构 if单选择结构 if双选择结构 if多选择结构 嵌套的if结构 switch多选择结构 switch case 语句判断一个变量与一系列值中某个值是否相等，每个值称为一个分支。 switch语句中的变量类型可以是： byte，short，int 或者char。 从Java SE 7 开始 switch支持字符串String类型了 同时case标签必须为字符串常量和或字面量 switch(expression) { case value： //语句 break; //可选 case value： //语句 break; //可选 //你可以有任意数量的case语句 default ：//可选 //语句 } 原文件及switch实例 package struct; public class switchDemo { public static void main(String[] args) { String name = "kok-s0s"; //JDK7的新特性，表达式结果可以是字符串！！！ //字符的本质是数字 //反编译 java---class（字节码文件）---反编译（IDEA） switch (name) { case "kok-s0s": System.out.println("ok!"); break; case "who": System.out.println("who are you?"); break; default: System.out.println("what are you going to do?"); } } } 反编译结果 // // Source code recreated from a .class file by IntelliJ IDEA // (powered by FernFlower decompiler) // package struct; public class switchDemo { public switchDemo() { } public static void main(String[] args) { String name = "kok-s0s"; byte var3 = -1; switch(name.hashCode()) { case -543248720: if (name.equals("kok-s0s")) { var3 = 0; } break; case 117694: if (name.equals("who")) { var3 = 1; } } switch(var3) { case 0: System.out.println("ok!"); break; case 1: System.out.println("who are you?"); break; default: System.out.println("what are you going to do?"); } } } 循环结构 while package struct; public class whileDemo01 { public static void main(String[] args) { //输出1~100 int i = 0; while (i <= 100) { System.out.println(i); i++; } } } package struct; public class whileDemo02 { public static void main(String[] args) { //计算1+2+...+100=？ int i = 0; int sum = 0; while (i <= 100) { sum = sum + i; i++; } System.out.println(sum); } } do...while 至少能执行一次循环 package struct; public class DowhileDemo01 { public static void main(String[] args) { int i = 0; int sum = 0; do { sum += i; i++; } while (i <= 100); System.out.println(sum); } } package struct; public class DowhileDemo02 { public static void main(String[] args) { int a = 1; while (a < 1) { System.out.println(a); a++; } System.out.println("==============="); do { System.out.println(a); a++; } while (a < 1); } } for package struct; public class forDemo01 { public static void main(String[] args) { for (int i = 1; i <= 100; ++i) { System.out.println(i); } System.out.println("for循环结束"); } } package struct; public class forDemo02 { public static void main(String[] args) { //练习1：计算0到100之间的奇数以及偶数的和 int oddSum = 0; int evenSum = 0; for (int i = 0; i < 100; i++) { if (i % 2 != 0) { oddSum += i; } else { evenSum += i; } } System.out.println("奇数和：" + oddSum); System.out.println("偶数和：" + evenSum); } } package struct; public class forDemo03 { public static void main(String[] args) { //练习2：用while或for循环输出1-1000之间能被5整除的数，并且每行输出三个结果 for (int i = 0; i < 1000; i++) { if (i % 5 == 0) { System.out.print(i + "\t"); } if (i % (5 * 3) == 0) { System.out.println(); } } } } 九九乘法表 package struct; public class forDemo04 { public static void main(String[] args) { for (int i = 1; i <= 9; i++) { for (int j = 1; j <= i; j++) { System.out.print(i + "*" + j + "=" + (i * j) + "\t"); } System.out.println(); } } } 增强for循环 java 5引入的一种主要用于数组或集合的增强型for循环 package struct; public class forDemo05 { public static void main(String[] args) { int[] numbers = {1, 2, 3, 4, 7, 5, 6}; for (int x : numbers) { System.out.println(x); } } } break && continue pass boring & easy 练习 package struct; public class TestDemo01 { public static void main(String[] args) { //打印三角形 5行 for (int i = 1; i <= 5; i++) { for (int i1 = 5; i1 >= i; i1--) { System.out.print(" "); } for (int j = 1; j <= i; j++) { System.out.print("*"); } for (int j = 1; j < i; j++) { System.out.print("*"); } System.out.println(); } } } Java方法详解 什么是方法？ Java方法是语句的集合，它们在一起执行一个功能。 方法是解决一类问题的步骤的有序组合 方法包含于类或对象中 方法在程序中被创建，在其他地方被引用 设计方法的原则：方法的本意是功能块，就是实现某个功能的语句块的集合。我们设计方法的时候，最好保持方法的原子性，就是一个方法只完成一个功能，这样便于后期的扩展。 Java的方法类似于其它语言的函数，是一段用来完成特定功能的代码片段，一般情况下，定义一个方法包含以下语法： 方法包含一个方法头和一个方法体。 下面是一个方法的所有部分： 修饰符：修饰符是一个可选项，告诉编译器如何调用该方法，定义了该方法的访问类型。 返回值类型：方法可能会返回值。returnValueType是方法返回值的数据类型。有些方法执行所需的操作，但没有返回值。在这种情况下，returnValueType是void。 方法名：是方法的实际名称。方法名和参数表共同构成方法签名。 参数类型：参数像是一个占位符。当方法被调用时，传递值给参数。这个值被称为实参或变量。参数列表是指方法的参数类型，顺序和参数的个数。参数是可选的，方法可以不包含任何参数。 形式参数：在方法被调用时用于接收外界输入的数据 实参：调用方法时实际传给方法的数据 方法体：方法体包含具体的语句，定义该方法的功能。 修饰符 返回值类型 方法名（参数类型 参数名）{ ··· 方法体 ··· return 返回值; } package method; public class Demo01 { //main方法 public static void main(String[] args) { //实际参数:实际调用传递给它的参数 int sum = add(1, 2); System.out.println(sum); test(); } //加法 //形式参数，用来定义作用的 public static int add(int a, int b) { return a + b; } public static void test() { for (int i = 1; i <= 5; i++) { for (int i1 = 5; i1 >= i; i1--) { System.out.print(" "); } for (int j = 1; j <= i; j++) { System.out.print("*"); } for (int j = 1; j < i; j++) { System.out.print("*"); } System.out.println(); } } } 方法调用 调用方法：对象名.方法名(实参列表) Java支持两种调用方法的方式，根据方法是否返回值来选择 当方法返回一个值的时候，方法调用通常被当作一个值。例如： int larger = max(30,40); 如果方法返回值是void，方法调用一定是一条语句。 System.out.println("Hello,kok-s0s!"); package method; public class Demo02 { public static void main(String[] args) { int Max = max(10, 10); System.out.println(Max); } //比大小 public static int max(int a, int b) { int result = 0; if (a == b) { System.out.println("a==b"); return 0;//终止大小 } else if (a > b) { result = a; } else { result = b; } return result; } } 方法的重载 重载就是在一个类中，有相同的函数名称，但参数不同的函数 方法重载的规则： 方法名称必须相同。 参数的列表必须不同（个数不同，或类型不同，参数排列顺序不同等）。 方法的返回类型可以相同也可以不相同。 仅仅返回类型不同不足以成为方法的重载。 实现理论： 方法名称相同时，编译器会根据调用方法的参数个数，参数类型等去逐个匹配，已选择对应的方法，如果匹配失败，则编译器报错。 命令行传参 运行一个程序时传递信息，依靠命令行传参数给main()函数实现。 package method; public class Demo03 { public static void main(String[] args) { //args.length 数组长度 for (int i = 0; i < args.length; i++) { System.out.println("args["+i+"]:"+args[i]); } } } 可变参数 JDK1.5开始，Java支持传递同类型的可变参数给一个方法。 在方法声明中，在指定参数类型后加一个省略号(...)。 一个方法中只能指定一个可变参数，它必须是方法的最后一个参数。任何普通的参数必须在它之前声明。 package method; public class Demo04 { public static void main(String[] args) { //调用可变参数的方法 printMax(23, 123, 412341, 54121); printMax(new int[]{1, 2, 3, 4, 5, 6, 7}); } public static void printMax(int... numbers) { int Max = 0; for (int i = 0; i < numbers.length; i++) { if (Max < numbers[i]) Max = numbers[i]; } System.out.println("the max value is:" + Max); } } 递归 递归就是：A方法调用A方法！就是自己调用自己 利用递归可以用简单的程序来解决一些复杂的问题。它通常把一个大型复杂的问题层层转化为一个与原问题相似的规模较小的问题来求解，递归策略只需少量的程序就可描述出解题过程所需要的多次重复计算，大大地减少了程序的代码量。递归的能力在于有效的语句来定义对象的无限集合。 递归结构包括两部分： 递归头：什么时候不调用自身方法。如果没有头，将陷入死循环。 递归体：什么时候需要调用自身方法。 Java数组 什么是数组 数组是相同类型数据的有序集合。 数据描述的是相同类型的若干个数据，按照一定的先后次序排列组合而成。 其中，每一个数据称作一个数组元素，每个数组元素可以通过一个下标来访问它们。 数组的声明和创建 首先必须声明数组变量，才能在程序中使用数组。使用语法如下： dataType[] arrayRefVar; //首选的方法 或 dataType arrayRefVar[]; //效果相同，但不是首选方法 Java语言使用new操作符来创建数组，语法如下： dataType[] arrayRefVar = new dataType[arraySize]; 数组的元素是通过索引访问的，数组索引从0开始。 arrays.length获取数组长度 package array; public class Demo01 { public static void main(String[] args) { //变量的类型 变量的名字 = 变量的值； //数组类型 int[] nums1;//定义 1.声明一个数组 // int nums2[]; //C/C++风格 nums1 = new int[10]; //2.创建一个数组 这里面可以存放10个int类型的数字 int sum = 0; //给数组元素进行赋值 for (int i = 0; i < nums1.length; i++) { nums1[i] = i + 1; sum += nums1[i]; } for (int i = 0; i < nums1.length; i++) { System.out.println("nums1[" + i + "]=" + nums1[i]); } System.out.println(sum); } } 三种初始化及内存分析 Java内存分析 堆 存放new的对象和数组 可以被所有的线程共享，不会存放别的对象引用 栈 存放基本变量类型（会包含这个基本类型的具体数值） 引用对象的变量（会存放这个引用在堆里面的具体地址） 方法区 可以被所有的线程共享 包含了所有的class和stack变量 初始化 静态初始化 int [] a = {1,2,3}; Man[] mans = {new Man(1,1),new Man(2,2)}; 动态初始化 int [] a = new int[2]; a[0]=1; a[1]=2; 数组的默认初始化 数组是引用类型，它的元素相当于类的实例变量，因此数组一经分配空间，其中的每个元素也被按照实例变量同样的方式被隐式初始化 下标越界及小结 下标的合法区间：[0,length-1]，如果越界就会报错； public static void main(String[] args) { int[] a = new int[2]; System.out.println(a[2]); } ArrayIndexOutOfBoundsException 小结： 数组是相同数据类型（数据类型可以为任意类型）的有序集合 数组也是对象，数组元素相当于对象的成员变量 数组长度是确定的，不可变的，如果越界，则报错：“ArrayIndexOutOfBoundsException” 数组的使用 普通的For循环 For-Each循环 数组做方法入参 数组做返回值 package array; public class Demo02 { public static void main(String[] args) { int[] arrays = {1, 2, 3, 4, 5}; int sum = 0; int max = 0; //打印全部的数组元素 for (int array : arrays) { System.out.println(array); } System.out.println("++++++++++++++"); //计算所有元素的和 for (int array : arrays) { sum += array; } System.out.println("sum=" + sum); System.out.println("=============="); //查找最大的元素 for (int array : arrays) { if (array > max) max = array; } System.out.println("max=" + max); } } package array; public class Demo03 { public static void main(String[] args) { int[] arrays = {1, 2, 3, 4, 5}; //JDK1.5 没有下标 for (int array : arrays) { System.out.println(array); } printArray(arrays); System.out.println("===================="); int[] reverse = reverse(arrays); printArray(reverse); } //打印数组元素 public static void printArray(int[] arrays) { for (int i = 0; i < arrays.length; i++) { System.out.println(arrays[i] + " "); } } //反转数组 public static int[] reverse(int[] arrays) { int[] result = new int[arrays.length]; for (int i = 0, j = result.length - 1; i < arrays.length; i++, j--) { result[j] = arrays[i]; } return result; } } 二维数组 多维数组可以看成是数组的数组，比如二维数组就是一个特殊的一维数组，其每一个元素都是一个一维数组。 二维数组 int a[][] = new int[2][5]; Arrays类讲解 数组的工具类java.util.Arrays 由于数组对象本身并没有什么方法可以供我们调用，但API中提供了一个工具类Arrays供我们使用，从而可以对数据对象进行一些基本的操作。 查看JDK帮助文档 Arrays类中的方法都是static修饰的静态方法，在使用的时候可以直接使用类名进行调用，而“不用”使用对象来调用（注意，是“不用”而不是“不能”） package array; import java.util.Arrays; public class Demo04 { public static void main(String[] args) { int[] a = {1, 2, 3, 4, 23123, 23123, 43, 24, 534}; System.out.println(a); System.out.println(Arrays.toString(a)); Arrays.sort(a); System.out.println(Arrays.toString(a)); Arrays.fill(a, 2, 4, 0); System.out.println(Arrays.toString(a)); } } 冒泡排序 package array; import java.lang.reflect.Array; import java.util.Arrays; public class Demo05 { public static void main(String[] args) { int[] a = {2, 3, 4, 1, 77, 23, 5, 6}; int[] test = sort(a); System.out.println(Arrays.toString(test)); } //冒泡排序 //1. 比较数组中，两个相邻的元素，如果第一个数比第二个数打，我们就交换它们的位置 //2. 每一次比较，都会产生出一个最大，或者最小的数字 //3. 下一轮可以少一次排序 //4. 依次循环，知道结束！ public static int[] sort(int[] array) { //外层循环，判断我们这个要走多少步 for (int i = 0; i < array.length - 1; i++) { boolean flag = false;//通过flag标识位减少没有意义的比较 //内层循环，比较判断两个数。如果第一个数，比第二个数打，则交换位置 for (int j = 0; j < array.length - 1; j++) { if (array[j + 1] < array[j]) { int temp = array[j + 1]; array[j + 1] = array[j]; array[j] = temp; flag = true; } } if (flag = false) { break; } } return array; } } 稀疏数组 package array; public class Demo06 { public static void main(String[] args) { //1. 创建一个二维数组 11*11 0：没有棋子 1：黑棋 2：白棋 int[][] array1 = new int[11][11]; array1[1][2] = 1; array1[2][3] = 2; //输出原始的数组 System.out.println("输出原始的数组："); for (int[] ints : array1) { for (int anInt : ints) { System.out.print(anInt + "\t"); } System.out.println(); } //转换为稀疏数组保存 //获取有效值的个数 int sum = 0; for (int i = 0; i < 11; i++) { for (int j = 0; j < 11; j++) { if (array1[i][j] != 0) sum++; } } System.out.println("有效值的个数：" + sum); //2. 创建一个稀疏数组的数组 int[][] array2 = new int[sum + 1][3]; array2[0][0] = 11; array2[0][3] = 11; array2[0][2] = sum; //遍历二维数组，将非零的值。存放在稀疏数组中 int count = 0; for (int i = 0; i < array1.length; i++) { for (int j = 0; j < array1[i].length; j++) { if (array1[i][j] != 0) { count++; array2[count][0] = i; array2[count][4] = j; array2[count][2] = array1[i][j]; } } } //输出稀疏数组 System.out.println("稀疏数组"); for (int i = 0; i < array2.length; i++) { System.out.println(array2[i][0] + "\t" + array2[i][5] + "\t" + array2[i][2] + "\t"); } System.out.println("=========================="); System.out.println("还原"); //1. 读取稀疏数组 int[][] array3 = new int[array2[0][0]][array2[0][6]]; //2. 给其中的元素还原它的值 for (int i = 1; i < array2.length; i++) { array3[array2[i][0]][array2[i][7]] = array2[i][2]; } //3. 打印 System.out.println("输出还原的数组"); for (int[] ints : array1) { for (int anInt : ints) { System.out.print(anInt + "\t"); } System.out.println(); } } } 面向对象编程 java的核心思想是OOP 什么是面向对象 面向对象编程（Object-Oriented Programming，OOP） 面向对象编程的本质就是：以类的方式组织代码，以对象的组织（封装）数据。 抽象 三大特性： 封装 继承 多态 从认识论角度考虑是有对象后有类。对象，是具体的事物。类，是抽象的，是对对象的抽象。 从代码运行角度考虑是先有类后有对象。类是对象的模板。 回顾与新知 package oop; //Demo01 类 public class Demo01 { //mian方法 public static void main(String[] args) { } /* 修饰符 返回值类型 方法吗，方法名（···）{ //方法体 return 返回值 } */ public String sayHello() { return "Hello World!"; } } package oop; public class Demo02 { //和类一起加载的 public static void a(){ // b(); } //类实例化 之后才存在 public void b(){ } } package oop; public class Demo03 { public static void main(String[] args) { //实际参数和形式参数的类型要对应！ Demo03 demo03 = new Demo03(); int ans = demo03.add(3, 4); System.out.println(ans); } public int add(int a, int b) { return a + b; } } package oop; //值传递 public class Demo04 { public static void main(String[] args) { int a = 1; System.out.println(a); Demo04.change(a); System.out.println(a); //1 } //返回值为空 public static void change(int a) { a = 10; } } package oop; //引用传递：对象，本质还是值传递 public class Demo05 { public static void main(String[] args) { Person person = new Person(); System.out.println(person.name); Demo05.change(person); System.out.println(person.name); } public static void change(Person person) { //person是个对象，指向一个具体的人，可以改变属性！ person.name = "kok-s0s"; } } //定义一个Person类，有一个属性：name class Person { String name; //null } 类与对象的创建 使用new关键字创建对象 使用new关键字创建的时候，除了分配内存空间之外，还会给创建好的对象进行默认的初始化以及对类中构造器的调用。 类中的构造器也称为构造方法，是在进行创建对象的时候必须要调用的。并且构造器有以下两个特点： 必须和类的名字相同 必须没有返回类型，也不能写void package oop.lesson; //学生类 public class Student { //属性：字段 String name; //null int age; //0 //方法 public void study(){ System.out.println(this.name+"在学习"); } } package oop.lesson; public class Application { //一个项目应该只有一个main方法 public static void main(String[] args) { //类：抽象的，实例化 //类实例化后返回一个自己的对象 //student对象就是一个student类的具体实例 Student kk = new Student(); Student gg = new Student(); kk.name = "sdsa"; kk.age = 19; System.out.println(kk.name); System.out.println(kk.age); } } 构造器详解 package oop.lesson; public class Person { // 一个类即使什么都不写，它也会存在一个方法 //现实的定义构造器 String name; //实例化初始值 //1. 使用new关键字，本质实在调用构造器 //2. 用来初始化值 public Person() { //this表示当前类 this.name = "kok-s0s"; } //有参构造，一旦定义有参构造，无参构造就必须显示定义 public Person(String name) { this.name = name; } //alt+insert快捷键生成构造器 } package oop.lesson; public class Application { public static void main(String[] args) { //new 实例化一个对象 Person person = new Person(); } } 创建对象内存分析 堆 、栈、 方法区 封装详解 该露的露，该藏的藏 我们程序设计要追求“高内聚，低耦合”。高内聚就是类的内部数据操作细节自己完成，不允许外部干涉；低耦合：仅暴露少量的方法给外部使用。 封装（数据的隐藏） 通常，应禁止直接访问一个对象中数据的实际表示，而应通过操作接口来访问，这称为信息隐藏。 important ：属性私有，get/set package oop; import oop.lesson02.Student; public class Application { public static void main(String[] args) { Student s1 = new Student(); s1.setName("kok-s0s"); s1.setId(18); s1.setSex('b'); System.out.println(s1.getName() + "\t" + s1.getId() + "\t" + s1.getSex()); } } package oop.lesson02; //类 私有 public class Student { private String name; private int id; private char sex; //提供一些可以操作私有属性的方法！ //提供一些public的get，set方法 public String getName() { return name; } public void setName(String name) { this.name = name; } public int getId() { return id; } public void setId(int id) { this.id = id; } public char getSex() { return sex; } public void setSex(char sex) { this.sex = sex; } } 什么是继承 集成的本质是对某一批的抽象，从而实现对世界更好的建模。 extends的意思是“扩展”。子类是父类的扩展。 JAVA中类只有单继承，没有多继承！ 继承是类和类之间的一种关系。除此之外，类和类的关系还有依赖，组合，聚合等。 继承关系的两个类，一个为子类（派生类），一个为父类（基类）。子类继承父类，使用关键字extends来表示。 子类和父类之间，从意义上讲应该具有“is a”的关系。 object类 super 方法重写 Super详解 super注意点： 1. super调用父类的构造方法，必须在构造方法的第一个 2. super 必须只能出现在子类的方法或者构造方法中！ 3. super和this不能同时调用构造方法！ Vs this： 代表的对象不同： this： 本身调用者这个对象 super： 代表父类对象的应用 前提 this： 没有继承也可以使用 super： 只能在继承条件才可以使用 构造方法 this() 本类的构造 super() 父类的构造 package oop.lesson03; //在Java中，所有的类，都默认直接或者间接继承object //Person 人：父类 public class Person { public Person() { System.out.println("Person无参构造器执行了"); } protected String name = "kok-s0s"; //私有的东西无法被继承！ private void print() { System.out.println("Person"); } private int money = 10_0000_0000; public void say() { System.out.println("say a word!"); } public int getMoney() { return money; } public void setMoney(int money) { this.money = money; } } package oop.lesson03; //教师 is 人 :派生类 ， 子类 public class Teacher extends Person{ } package oop.lesson03; //学生 is 人 :派生类 ， 子类 //子类继承父类，就会拥有父类的全部方法！ public class Student extends Person { //Ctrl + H 打开继承树 public Student() { //隐藏代码（super()）：调用了父类的无参构造！ super();//调用父类的构造器，必须要在子类构造器的第一行 System.out.println("Student无参构造器调用了"); } private String name = "man"; public void test(String name) { System.out.println(name); System.out.println(this.name); System.out.println(super.name); } public void print() { System.out.println("Student"); } public void test1() { print(); //Student this.print(); //Student // super.print(); //Person } } package oop; import oop.lesson03.Person; import oop.lesson03.Student; public class Application { public static void main(String[] args) { Student student = new Student(); // student.test("cool"); // student.test1(); } } 方法重写 重写：需要有继承关系，子类重写父类的方法！ 1. 方法名必须相同 2. 参数列表必须相同 3. 修饰符：范围可以扩大但不能缩小！ public>Protected>Default>private 4. 抛出的异常：范围，可以被缩小，但不能扩大：classNotException-->Exception(大) 重写，子类的方法和父类必须一致，方法体不同！ 为什么需要重写： 1. 父类的功能，子类不一定需要，或者不一定满足！ Alt + Insert; Override package oop.lesson04; //重写都是方法的重写，和属性无关 public class B { public void test(){ System.out.println("B=>test()"); } } package oop.lesson04; //继承 public class A extends B{ //Override 重写 @Override //注释：有功能的注释！ public void test() { System.out.println("A=>test()"); } } package oop; import oop.lesson04.A; import oop.lesson04.B; public class Application { //静态方法和非静态的方法区别很大！ public static void main(String[] args) { //方法的调用只和左边，定义的数据类型有关 A a = new A(); a.test(); //A //父类的引用指向了子类 B b = new A(); b.test(); //B } } 什么是多态 /* 多态注意事项： 1. 多态是方法的多态，属性没有多态 2. 父类和子类，有联系 类型转换异常！ClassCastException! 3. 存在条件：继承关系，方法需要重写，父类引用指向子类对象！ Father f1 = new Son(); 1. static 方法，属于类，它不属于实例 2. final 常量 3. private 方法 */ package oop.lesson05; public class Student extends Person{ public void run(){ System.out.println("son"); } } package oop.lesson05; public class Person { public void run(){ System.out.println("run"); } } package oop; import oop.lesson05.Person; import oop.lesson05.Student; public class Application { public static void main(String[] args) { //一个对象的实际类型是确定的 //new Student(); //new Person(); //可以指向的引用类型就不确定了；父类的引用指向子类 //Student 能嗲用的方法都是自己的或者继承父类的！ Student s1 = new Student(); //Person 父类型，可以指向子类，但是不能调用子类独有的方法 Person s2 = new Student(); Object s3 = new Student(); //对象能执行哪些方法，主要看对象左边的类型，和右边关系不大！ s2.run();//子类重写了父类的方法，执行子类的放啊 s1.run(); } } instanceof和类型转换 https://www.runoob.com/java/method-instanceof.html B站 static关键字详解 package oop.last; //static public class Student { private static int age; //静态变量 多线程！ private double score; //非静态变量 public void run(){ } public static void go(){ } public static void main(String[] args) { Student s1 = new Student(); go(); System.out.println(Student.age); // System.out.println(Student.score); System.out.println(age); System.out.println(s1.score); } } package oop.last; public class Person { //2 赋初始值~ { //代码块（匿名代码块） System.out.println("匿名代码块"); } //1：只执行一次 static { //静态代码块 System.out.println("静态代码块"); } //3 public Person() { System.out.println("构造方法"); } public static void main(String[] args) { Person person = new Person(); System.out.println("==============="); Person person1 = new Person(); } } //静态>匿名>构造 /* 静态代码块 匿名代码块 构造方法 */ package oop.last; //静态导入包 import static java.lang.Math.random; public class Test { public static void main(String[] args) { System.out.println(random()); } } final是断子绝孙修饰符 抽象类 package oop.again; //abstract 抽象类 ：类 extends： 单继承~ （接口可以多继承） public abstract class Action { //约束~有人帮我们实现 //abstract ， 抽象方法，只有方法名字，没有方法实现 public abstract void doSomething(); //1. 不能new这个抽象类，只能靠子类去实现它；约束！ //2. 抽象类中可以写普通的方法~ //3. 抽象方法必须在抽象类中~ //抽象的抽象：约束~ } package oop.again; //抽象类的所有方法，继承了它的子类，都必须要实现它的方法 public class A extends Action{ @Override public void doSomething() { } } 接口的定义与实现 package oop.demo; //抽象的思维~ Java 架构师~ //interface 定义的关键字 ， 接口都需要有实现类 public interface UserService { //常量~ public static final int AGE = 99; //接口中的所有定义其实都是抽象的 public abstract void add(String name); void delete(String name); void update(String name); void query(String name); } package oop.demo; public interface TimeService { void timer(); } package oop.demo; //抽象类 extends~ //类 可以实现接口 implements 接口 //实现了接口的类，就需要重写接口中的方法~ //多继承~利用接口实现多继承~ public class UserServiceImp1 implements UserService, TimeService { @Override public void add(String name) { } @Override public void delete(String name) { } @Override public void update(String name) { } @Override public void query(String name) { } @Override public void timer() { } } 作用： 1. 约束 2. 定义一些方法，让不同的人实现~ 10---->1 3. public abstract 4. public static final 5. 接口不能被实例化~，接口中没有构造方法~ 6. implements可以实现多个接口 7. 必须要重接口中的方法~ N种内部类 https://www.bilibili.com/video/BV12J41137hu?p=76&spm_id_from=pageDriver 异常 Error和Exception 异常，英文：Exception，意思是例外 异常是指程序运行中出现的不期而至的各种状况，如：文件找不到，网络连接失败，非法参数等。 异常发生在程序运行期间，它影响了正常的程序执行流程。 异常简单分类： 检查性异常 运行时异常 错误ERROR 异常体系结构 Java把异常当作对象来处理，并定义一个基类java.lang.Throwable作为所有异常的超类。 在Java API中已经定义了许多异常类，这些异常分为两大类，错误Error和异常Exception\ note 捕获和抛出异常 抛出异常 捕获异常 异常处理五个关键词：| try，catch，finally，throw，throws package exception; public class Test { public static void main(String[] args) { try { new Test().test(1, 0); } catch (ArithmeticException e) { e.printStackTrace(); } // // int a = 1; // int b = 0; // // //假设要捕获多个异常，从小到大！ // try { //try监控区域 // // if (b == 0) {//主动抛出异常 throw throws // throw new ArithmeticException();//主动的抛出异常 // } // // System.out.println(a / b); //// new Test().a(); // } catch (Error e) { //catch(想要捕获的异常类型！) 捕获异常 // System.out.println("Error"); // } catch (Exception e) { // System.out.println("Exception"); // } catch (Throwable t) { // System.out.println("Throwable"); // } finally { //处理善后工作 // System.out.println("finally"); // } //finally 可以不要finally 假设IO 资源，关闭！ } //假设这方法中，处理不了这个异常，方法上抛出异常 public void test(int a, int b) throws ArithmeticException{ if (b == 0) {//主动抛出异常 throw throws throw new ArithmeticException();//主动的抛出异常,一般在方法中使用 } System.out.println(a / b); } } package exception; public class Test02 { public static void main(String[] args) { int a=1; int b=0; //Ctrl + Alt + T try { System.out.println(a/b); } catch (Exception e) { e.printStackTrace();//打印错误的栈信息 } finally { System.out.println("over"); } } } 自定义异常 用户自定义异常，只需要继承Exception类即可。 package exception; //自定义的异常类 public class MyException extends Exception { //传递数字>10 private int detail; public MyException(int a) { this.detail = a; } //toString:异常的打印信息 @Override public String toString() { return "MyException{" + detail + "}"; } } package exception; public class Test03 { //可能会存在异常的方法 static void test(int a) throws MyException { System.out.println("传递的参数：" + a); if (a > 10) { throw new MyException(a);//抛出 } System.out.println("ok"); } public static void main(String[] args) { try { test(11); } catch (MyException e) { System.out.println("MyException=>" + e); // e.printStackTrace(); } } } 经验总结 处理运行异常时，采用逻辑去合理规避同时辅助try-catch处理 在多重catch块后面，可以加一个catch（Exception）来处理可能会被遗落的异常 对于不确定的代码，也可以加上try-catch，处理潜在的异常 尽量去处理异常，切忌只是简单地调用printStackTrace()去打印输出 具体如何处理异常，要根据不同的业务需求和异常类型去决定 尽量添加finally语句块去释放占用的资源，IO~ Scanner~
卸载JDK 1.删除java的安装目录 2.清除变量设置里的JAVA_HOME 3.清除path与java相关的信息 4.试试java -version判断是否是否删除干净。 IDE 1.psvm自动生成主函数语句 2.sout快捷键自动生成输出语句 3.ctrl+alt+L能够做自动排版工作 4.Ctrl + D 复制当前行到下一行 intellij java 反编译 打开intellij的project struck选项,找到字节码文件的位置，将相应的文件拖到存放对应class文件的地方，再用intellij打开其文件，IDEA会自动反编译文件出来。 原文件 package struct; public class switchDemo { public static void main(String[] args) { String name = "kok-s0s"; //JDK7的新特性，表达式结果可以是字符串！！！ //字符的本质是数字 //反编译 java---class（字节码文件）---反编译（IDEA） switch (name) { case "kok-s0s": System.out.println("ok!"); break; case "who": System.out.println("who are you?"); break; default: System.out.println("what are you going to do?"); } } } 反编译结果 // // Source code recreated from a .class file by IntelliJ IDEA // (powered by FernFlower decompiler) // package struct; public class switchDemo { public switchDemo() { } public static void main(String[] args) { String name = "kok-s0s"; byte var3 = -1; switch(name.hashCode()) { case -543248720: if (name.equals("kok-s0s")) { var3 = 0; } break; case 117694: if (name.equals("who")) { var3 = 1; } } switch(var3) { case 0: System.out.println("ok!"); break; case 1: System.out.println("who are you?"); break; default: System.out.println("what are you going to do?"); } } }