JAVA学习之JAVASE基础
集合列表
List
ArrayList
利用空参创建的集合,在底层创建一个默认长度为0的数组
添加第一个元素时,底层会创建一个新的长度为10的数组
存满时,会扩容1.5倍
一次存多个元素,1.5倍还不够,则新创建的数组长度以实际为准
LinkedList
底层数据结构是双链表,查询慢,首位操作速度是极快的
复杂度分析
| 操作 | ArrayList | LinkedList |
|---|---|---|
| 查询 | O(1) | O(n) |
| 头部插入/删除 | O(n) | O(1) |
| 尾部插入 | O(1)* | O(1) |
| 中间插入/删除 | O(n) | O(n) |
| 空间开销 | O(n) | O(n) |
ArrayList尾部插入在扩容时为O(n)
遍历方式
增强for循环(for-each)
for (ElementType element : list) { // 处理element } 传统for
for (int i = 0; i < list.size(); i++) { ElementType element = list.get(i); // 处理element } iterator迭代器
Iterator<ElementType> iterator = list.iterator(); while (iterator.hasNext()) { ElementType element = iterator.next(); // 处理element } stream流(jdk8+)
list.stream().forEach(element -> { // 处理element }); parallelStream并行流
list.parallelStream().forEach(element -> { // 处理element });
Iterator
在底层创建一个内部类对象,表示ArrayList的迭代器
多次调用这个方法,相当于创建多个迭代器对象
如何避免并发修改异常: 在使用迭代器或者增强for遍历集合过程中,不要使用集合的方法去添加或者删除元素
平衡二叉树
规则:任意节点左右子树高度差不超过1
右旋
左左:一次右旋(当前根节点左子树有节点插入,导致二叉树不平衡)
左右:先局部左旋,再整体右旋
右右:一次左旋
右左:先局部右旋,再整体左旋
红黑树
红黑树是一种自平衡的二叉查找树,不是高度平衡的,而是通过红黑规则实现的
红黑规则:
每一个节点是红色或者黑色
根节点是黑色的
如果一个节点没有子节点或者父节点,则该节点相应的指针值为Nil,这些Nil视为叶节点,每个叶节点(Nil)是黑色的
如果一个节点是红色的,那么他的子节点必须是黑色的(不能出现两个红色节点相连的情况)
对每一个节点,从该节点到其所有后代叶节点的简单路径上,均包含相同数目的黑色节点
Set
特点
无序:存取顺序不一致
不重复:集合中的元素不重复
无索引:没有带索引的方法,不能通过普通的for循环遍历,也不能通过索引获取元素
HashSet
无序、不重复、无索引
底层原理
创建一个默认长度16,加载因子0.75的数组,数组名为table
根据元素的哈希值跟数组的长度计算出存入的位置
int index = (数组长度-1) & 哈希值;
判断当前元素是否为null,如果为null直接存入
如果位置不为null,表示有元素,则调用equals方法比较属性值
一样:不存 不一样:存入数组,形成链表 JDK8以前:新元素存入数组,老元素在新元素下面
JDK8以后:
新元素直接挂在老元素下面
当链表长度超过8,而且数组长度大于等于64时,自动转换为红黑树
如果集合中存储的是自定义对象,必须重写hashCode和equals方法
LinkedHashSet
有序、不重复、无索引
有序:指保证存储和取出的元素顺序一致
底层原理:底层数据结构是哈希表,每个元素额外多了一个双链表机制记录存储的顺序
TreeSet
可排序、不重复、无索引
可排序:按照元素的默认规则(由小到大)排序
底层原理:基于红黑树的数据结构实现排序,增删改查性能都比较好
两种排序方式
自然排序:Javabean类实现Comparable接口指定比较规则
public int compareTo(Student o) { return this.getAge()-o.getAge(); // this:表示当前元素 // o:表示已经在红黑树存在的元素 // 返回值 // 负数:表示当前要添加的元素是小的,存左边 // 正数:表示当前要添加的元素是大的,存右边 // 0:表示当前要添加的元素已经存在,不存 }比较器排序:创建TreeSet对象时,传递比较强Comparator指定规则
使用原则:默认第一种,如果第一种不能满足当前需求,就使用第二种
TreeSet<Student> set= new TreeSet<>(new Comparator<Student>() { @Override public int compare(Student o1, Student o2) { int i=o1.getAge()-o2.getAge(); return i; } });用lambda表达式简化:
TreeSet<Student> set= new TreeSet<>((s,p)->s.getAge()-p.getAge());
复杂度分析
| 操作 | HashSet | TreeSet | LinkedHashSet |
|---|---|---|---|
| 查询 (contains) | O(1) 平均 O(n) 最坏 | O(log n) | O(1) 平均 |
| 插入 (add) | O(1) 平均 O(n) 最坏 | O(log n) | O(1) 平均 |
| 删除 (remove) | O(1) 平均 O(n) 最坏 | O(log n) | O(1) 平均 |
| 空间复杂度 | O(n) | O(n) | O(n) |
各Set特点
HashSet:
基于哈希表实现
无序存储
性能最优
TreeSet:
基于红黑树实现
保持元素排序
有序存储
LinkedHashSet:
哈希表 + 链表实现
保持插入顺序
遍历方式
增强for循环(for-each)
for (ElementType element : set) { // 处理element } Iterator迭代器
Iterator<ElementType> iterator = set.iterator(); while (iterator.hasNext()) { ElementType element = iterator.next(); // 处理element } Stream API(Java 8+)
set.stream().forEach(element -> { // 处理element }); parallelStream并行流
set.parallelStream().forEach(element -> { // 处理element });
Map
Map集合也叫键值对集合
Map集合键不可以重复,值可以重复
遍历方式
增强for循环(for-each)
for (Map.Entry<K, V> entry : map.entrySet()) { K key = entry.getKey(); V value = entry.getValue(); // 处理key和value } Iterator迭代器
Iterator<Map.Entry<K, V>> iterator = map.entrySet().iterator(); while (iterator.hasNext()) { Map.Entry<K, V> entry = iterator.next(); K key = entry.getKey(); V value = entry.getValue(); // 处理key和value } forEach方法(Java 8+)
map.forEach((key, value) -> { // 处理key和value });lambda
map.forEach((k,v)-> System.out.println(k+" "+v));
Stream API(Java 8+)
map.entrySet().stream().forEach(entry -> { K key = entry.getKey(); V value = entry.getValue(); // 处理key和value });遍历键或值单独遍历
// 遍历键 for (K key : map.keySet()) { // 处理key } // 遍历值 for (V value : map.values()) { // 处理value }
HashMap
Set系列集合的底层就是基于Map实现的,只是Set集合中的元素只要键数据,不要值而已
HashMap底层原理和HashSet是一样的
| Map实现 | 查询(get) | 插入(put) | 删除(remove) | 空间复杂度 |
|---|---|---|---|---|
| HashMap | O(1) 平均 O(n) 最坏 | O(1) 平均 O(n) 最坏 | O(1) 平均 O(n) 最坏 | O(n) |
| TreeMap | O(log n) | O(log n) | O(log n) | O(n) |
| LinkedHashMap | O(1) 平均 | O(1) 平均 | O(1) 平均 | O(n) |
| Hashtable | O(1) 平均 O(n) 最坏 | O(1) 平均 O(n) 最坏 | O(1) 平均 O(n) 最坏 | O(n) |
| ConcurrentHashMap | O(1) 平均 O(n) 最坏 | O(1) 平均 O(n) 最坏 | O(1) 平均 O(n) 最坏 | O(n) |
特点说明
HashMap: 基于哈希表实现,无序存储,性能最优
TreeMap: 基于红黑树实现,按键排序存储
LinkedHashMap: 哈希表+链表实现,保持插入顺序
Hashtable: 线程安全的哈希表实现,性能相对较低
ConcurrentHashMap: 线程安全的现代哈希表实现,支持高并发
NavigableMap 常用方法
键查找方法
lowerKey(K key) - 返回严格小于给定键的最大键
floorKey(K key) - 返回小于或等于给定键的最大键
ceilingKey(K key) - 返回大于或等于给定键的最小键
higherKey(K key) - 返回严格大于给定键的最小键
Entry 查找方法
lowerEntry(K key) - 返回严格小于给定键的最大键值对
floorEntry(K key) - 返回小于或等于给定键的最大键值对
ceilingEntry(K key) - 返回大于或等于给定键的最小键值对
higherEntry(K key) - 返回严格大于给定键的最小键值对
边界元素方法
firstEntry() - 返回第一个(最小的)键值对
lastEntry() - 返回最后一个(最大的)键值对
pollFirstEntry() - 获取并移除第一个键值对
pollLastEntry() - 获取并移除最后一个键值对
导航和子集方法
descendingIterator() - 返回降序键迭代器
descendingMap() - 返回逆序视图的 NavigableMap
headMap(K toKey) - 返回严格小于指定键的部分视图
tilMap(K fromKey) - 返回大于等于指定键的部分视图
Stream流
Stream流是一种可以用于操作集合或者数组数据的一套API
示例
List<String> list2 = list.stream() .filter(s -> s.startsWith("张")) .filter(s -> s.length() == 3) .collect(Collectors.toList());获取stream流
list
List<String> s = list.stream();
map
Stream<String> s1 = map.keySet().stream(); Stream<Integer> s2 = map.values().stream(); Stream<Map.Entry<String, Integer>> s3 = map.entrySet().stream();
arrray
Stream<Integer> s1 = Arrays.stream(arr); Stream<Integer> s2 = Arrays.stream(arr); Stream<Integer> s3 = Stream.of(arr); Stream<String> s4 = Stream.of("张三", "李四", "王五");
Stream流常用方法
Stream流的终结方法
Collection工具类
字符集
ASCII
标准ASCII字符集使用一个字节存储一个字符,首位是0
GBK
汉字编码字符集,一个中文编码成两个字节的形式存储
汉字的第一个字节必须是1
Unicode字符集(统一码,也叫万国码)
Unicode是国际组织制定的,可以容纳世界上所有文字、符号的字符集
UTF-32:每4个字节表示一个字符
UTF-8
是Unicode字符集的一种编码方式,采取可变长度编码方案,共分4个长度区:1个字节、2个字节、3个字节、4个字节
英文字符、数字等占一个字节,汉字占用3个字节
IO流
I指input,称为输入流:负责把数据读到内存中
O指Outout,称为输出流:负责写数据出去
读文本适合用字符流,字节流适合数据的转移,比如:文件复制
字节流
文件字节输入流
FileInputStream file=new FileInputStream("1.text"); byte []buf= file.readAllBytes(); int len; while((len=file.read(buf))!=-1){ String str=new String(buf, 0, len); System.out.print(str); }文件字节输出流
FileOutputStream file=new FileOutputStream("out.txt",true); byte[] bytes = "马超".getBytes(StandardCharsets.UTF_8); file.write(bytes); file.write("\r\n".getBytes(StandardCharsets.UTF_8)); file.close();文件复制
字节流非常适合文件复制
public static void copyFile(String src,String dest) throws IOException { //1.创建一个文件字节输入流管道与源文件连通 FileInputStream fis = new FileInputStream(src); FileOutputStream fos = new FileOutputStream(dest); byte[] buffer = new byte[1024]; int len; while((len = fis.read(buffer))!=-1){ fos.write(buffer,0,len); } fos.close(); }资源释放
一般用于在程序执行完成后用于资源的释放操作
字符流
文件字符输入流
文件字符输出流
缓冲流
作用:提高字节输入读取数据的性能
原理:缓冲字节输入流自带8KB的缓冲池;缓冲字节输出流自带8KB缓冲池
缓冲字节流
缓冲字符输入流
try ( Reader fr = new FileReader(src); BufferedReader br = new BufferedReader(fr) ) { String line; while((line=br.readLine())!=null){ System.out.println(line); } }catch(Exception e){ e.printStackTrace(); }缓冲字符输出流
try ( Reader fr = new FileReader(src); BufferedReader br = new BufferedReader(fr); Writer fw=new FileWriter(dest,true); BufferedWriter bw=new BufferedWriter(fw) ) { String line; while((line=br.readLine())!=null){ bw.write(line); bw.newLine(); } }catch(Exception e){ e.printStackTrace(); }字符输入转换流
解决不同编码时,字符读取文本乱码问题
解决思路:先获取文件的原始字节流,再将其按真实的字符集编码转化成字符输入流,这样字符输入流中的字符就不乱码了
打印流
作用:更方便、更高效的打印数据出去,能实现打印啥就是啥
PrintStream ps = new PrintStream("3.text"); ps.println("hello"); ps.println(97);数据输出流
允许把数据和其类型一并写出去
IO框架
![]()
多线程
线程是一个程序内部的一条执行流程
多线程是指从软硬件上实现的多条执行流程技术
创建方式
继承Thread类
定义一个子类MyThread继承线程类,重写run()方法
创建MyThread对象
调用线程对象的start()方法启动线程(启动后还是执行run方法的)
public class Test{ public static void main(String[] args) { MyThread t1=new MyThread(); t1.start(); } } class MyThread extends Thread{ public void run(){ for(int i=0;i<10;i++){ System.out.println(Thread.currentThread().getName()+" "+i); } } }实现Runnable接口
定义一个线程任务类MyThread实现Runnable接口,重写run()方法
创建MyRunnable任务对象
把MyRunnable任务对象交给Thread处理
public class Test{ public static void main(String[] args) { MyRunnable mr = new MyRunnable(); Thread t1 = new Thread(mr,"线程1"); t1.start(); } } class MyRunnable implements Runnable{ public void run(){ for (int i = 0; i < 10; i++) { System.out.println(Thread.currentThread().getName()+"-->"+i); } } }或者使用匿名内部类方式
public static void main(String[] args) { //方式1 Runnable r = new Runnable() { @Override public void run() { for (int i = 0; i < 5; i++) { System.out.println(Thread.currentThread().getName()+" "+i); } } }; Thread t=new Thread(r); t.start(); //方式2 new Thread(() ->{ for (int i = 0; i < 5; i++) { System.out.println(Thread.currentThread().getName()+" "+i); } }).start(); }实现Callable接口
优点:可以返回线程执行完毕后的结果
定义一个类实现Callable接口,重写call方法
把Callable类型的对象封装成FutureTask(线程任务对象)
调用Thread对象的start()方法
线程执行完毕后,通过FutureTask对象的get方法获取线程任务执行的结果
public class Test{ public static void main(String[] args) { Callable<String> mc = new MyCallable(100); FutureTask<String> ft = new FutureTask<>(mc); Thread t=new Thread(ft); t.start(); try { System.out.println(ft.get()); } catch (Exception e) { e.printStackTrace(); } } } class MyCallable implements Callable<String> { private int n; public MyCallable(int n) { this.n = n; } @Override public String call() throws Exception { int sum=0; for(int i=0;i<n;i++){ sum+=i; } return "0-n的和:"+sum; } }线程安全问题
多个线程,同时访问一个共享资源,且存在修改时会发生线程安全问题
解决方法
同步代码块
把访问共享资源的核心代码上锁,以此保证线程安全
原理:每次指允许一个线程加锁后进入,执行完毕后自动解锁,其它线程才可以进来执行
使用规范:
建议使用共享资源作为锁对象,对于实列方法建议使用this作为锁对象
对于静态方法建议使用字节码(类名.class)对象作为锁对象
public void drawMoney(double money){ String namme=Thread.currentThread().getName(); synchronized (this) { if(this.money>=money){ System.out.println(namme+"取钱成功,取了"+money+"元"); this.money-=money; System.out.println("余额为"+this.money); }else{ System.out.println(namme+"取钱失败,余额不足"); } } }同步方法
作用:把共享资源的核心方法上锁,以此保护线程安全
public synchronized void drawMoney(double money){ String namme=Thread.currentThread().getName(); if(this.money>=money){ System.out.println(namme+"取钱成功,取了"+money+"元"); this.money-=money; System.out.println("余额为"+this.money); }else{ System.out.println(namme+"取钱失败,余额不足"); } }lock锁
public void drawMoney(double money){ try { String namme=Thread.currentThread().getName(); lock.lock(); if(this.money>=money){ System.out.println(namme+"取钱成功,取了"+money+"元"); this.money-=money; System.out.println("余额为"+this.money); }else{ System.out.println(namme+"取钱失败,余额不足"); } } finally { lock.unlock(); } }
线程池
线程池就是一个可以复用的技术
不使用线程池的问题:创建新线程的开销是很大的,并且请求过多时,肯定会产生大量的线程出来,这样会严重影响系统的性能。
创建线程池对象
使用ExecutorServixe的实现类ThreadPoolExecutor自创一个线程池对象
什么时候创建临时线程:
新任务提交时发现核心线程都在忙,任务队列也满了,并且还可以创建线程,此时才会创建临时线程
核心线程和临时线程都很忙,任务队列也满了,新任务过来才会开始拒绝任务
public static void main(String[] args) { ThreadPoolExecutor pool =new ThreadPoolExecutor(3,5, 10,SECONDS,new ArrayBlockingQueue<>(3), Executors.defaultThreadFactory(),new ThreadPoolExecutor.AbortPolicy()); Runnable r=new MyRunnable(); pool.execute(r);//提交任务,创建第1个线程 pool.execute(r);//提交任务,创建第2个线程 pool.execute(r);//提交任务,创建第3个线程 pool.execute(r);//复用线程 pool.execute(r);//复用线程 pool.execute(r);//创建临时线程 pool.execute(r);//创建临时线程 pool.execute(r);//创建临时线程 pool.execute(r);//任务拒绝策略 }使用Executors(线程池的工具类)调用方法返回不同特点的线程池对象
线程池的核心线程数量和最大线程数量的配置需要根据具体的应用场景、任务类型(CPU密集型或I/O密集型)以及硬件资源来决定。
CPU密集型任务: 核心线程数:CPU核心数 + 1 最大线程数:与核心线程数相同或稍大一些 这种情况下,线程数不宜过多,否则会因上下文切换导致性能下降。
I/O密集型任务: 核心线程数:CPU核心数 * 2 最大线程数:可以根据任务的具体情况适当增加,通常可以设置为几十到上百。 I/O操作会让线程等待,因此可以配置更多的线程来提高CPU利用率。
并发并行
正在运行的程序是一个独立的进程
线程是属于进程的,一个进程可以同时运行很多个线程
进程中的多个线程其实是并发和并行执行的
并发:并发是指在同一时间段内,多个任务交替执行,给人一种同时进行的感觉。它通过任务切换实现,尽管在单核CPU上同一时刻只有一个任务运行,但快速切换使得多个任务看似并行。
并行:并行是指多个任务在同一时刻真正同时执行,通常需要多核处理器或多台计算机的支持。与并发不同,并行任务不仅在逻辑上同时进行,物理上也在同一时间执行。
网络编程
计算机网络中,连接和通信数据的规则被称为网络协议
UDP协议(用户数据协议)
面向无连接,不可靠传输的通信协议
TCP协议
面向连接、可靠通信
目的:保证数据在不可靠信道上实现可靠传输。三次握手建立连接,四次挥手断开连接
UDP通信
![]()
一发一收
public class Server { public static void main(String[] args) throws Exception { //完成UDP通信一发一收,服务端开发 System.out.println("服务端启动..."); //1.创建服务器Socket对象 DatagramSocket socket = new DatagramSocket(8080); //2.创建数据包,用于接收客户端发送的数据 byte[] bys = new byte[1024*64]; DatagramPacket dp = new DatagramPacket(bys, bys.length); //3.接受数据,将数据封装到数据包中 socket.receive(dp); String data=new String(bys,0,dp.getLength()); System.out.println("客户端说:"+data); } } public class Client { public static void main(String[] args) throws Exception { //完成UDP通信一发一收,客户端开发 System.out.println("客户端启动..."); //1.创建发送端对象 DatagramSocket socket = new DatagramSocket(); //2.创建数据包对象封装要发送的数据 byte[] bytes = "你好,我是客户端".getBytes(); DatagramPacket dp = new DatagramPacket(bytes,bytes.length, InetAddress.getLocalHost(),8080); //3.发送数据 socket.send(dp); } }多发多收
public class Client { public static void main(String[] args) throws Exception { //完成UDP通信一发一收,客户端开发 System.out.println("客户端启动..."); //1.创建发送端对象 DatagramSocket socket = new DatagramSocket(); Scanner sc = new Scanner(System.in); while (true) { System.out.println("请输入要发送的数据(exit)终止:"); String msg=sc.nextLine(); if("exit".equals(msg)){ break; } //2.创建数据包对象封装要发送的数据 byte[] bytes = msg.getBytes(); DatagramPacket dp = new DatagramPacket(bytes,bytes.length, InetAddress.getLocalHost(),8080); //3.发送数据 socket.send(dp); } socket.close(); } }public class Server { public static void main(String[] args) throws Exception { //完成UDP通信一发一收,服务端开发 System.out.println("服务端启动..."); //1.创建服务器Socket对象 DatagramSocket socket = new DatagramSocket(8080); //2.创建数据包,用于接收客户端发送的数据 byte[] bys = new byte[1024*64]; DatagramPacket dp = new DatagramPacket(bys, bys.length); while (true) { //3.接受数据,将数据封装到数据包中 socket.receive(dp); String data=new String(bys,0,dp.getLength()); System.out.println("客户端说:"+data); String ip=dp.getAddress().getHostAddress(); int port=dp.getPort(); System.out.println("客户端的ip="+ip+" "+"客户端的端口="+port); System.out.println("------------------------------------"); } } }
TCP通信
![]()
一发一收
public class ClientDemo { public static void main(String[] args) throws Exception { //实现TCP通信一发一收,客户端开发 System.out.println("客户端启动..."); //1.获取Socket管道对象,请求与服务端的Socket连接 Socket socket = new Socket("127.0.0.1", 9999); //2.从Socket通信管道中得到一个字节输出流 OutputStream os = socket.getOutputStream(); //3.特殊数据流 DataOutputStream dos = new DataOutputStream(os); dos.writeInt(1); dos.writeUTF("你好,我是客户端"); //4.关闭流和Socket dos.close(); } }public class ServerDemo { public static void main(String[] args) throws Exception { //实现TCP通信下一发一收,服务端开发 System.out.println("服务端启动..."); //1.创建服务端ServerSocket对象 ServerSocket ss = new ServerSocket(9999); //2.调用accept方法,阻塞等待客户端连接,一旦有客户端连接,accept方法就会返回一个Socket对象 Socket socket = ss.accept(); //3.获取输入流,读取客户端发送的数据 InputStream is = socket.getInputStream(); //4.把字节输入流包装成特殊数据输入流 DataInputStream dis = new DataInputStream(is); //5.读取数据 int id=dis.readInt(); String msg=dis.readUTF(); System.out.println("id="+id+",msg="+msg); //6.客户端的ip和端口 System.out.println("客户端的ip="+socket.getInetAddress().getHostAddress()); System.out.println("客户端的端口="+socket.getPort()); } }多发多收
public class ClientDemo2 { public static void main(String[] args) throws Exception { //实现TCP通信一发一收,客户端开发 System.out.println("客户端启动..."); //1.获取Socket管道对象,请求与服务端的Socket连接 Socket socket = new Socket("127.0.0.1", 9999); //2.从Socket通信管道中得到一个字节输出流 OutputStream os = socket.getOutputStream(); //3.特殊数据流 DataOutputStream dos = new DataOutputStream(os); Scanner sc = new Scanner(System.in); while (true) { System.out.println("请输入要发送的内容(exit退出):"); String msg=sc.nextLine(); if("exit".equals(msg)){ System.out.println("客户端退出..."); socket.close(); break; } dos.writeUTF(msg); } } }public class ServerDemo2 { public static void main(String[] args) throws Exception { //实现TCP通信下一发一收,服务端开发 System.out.println("服务端启动..."); //1.创建服务端ServerSocket对象 ServerSocket ss = new ServerSocket(9999); //2.调用accept方法,阻塞等待客户端连接,一旦有客户端连接,accept方法就会返回一个Socket对象 Socket socket = ss.accept(); //3.获取输入流,读取客户端发送的数据 InputStream is = socket.getInputStream(); //4.把字节输入流包装成特殊数据输入流 DataInputStream dis = new DataInputStream(is); while (true) { //5.读取数据 String msg=dis.readUTF(); System.out.println("msg="+msg); //6.客户端的ip和端口 System.out.println("客户端的ip="+socket.getInetAddress().getHostAddress()); System.out.println("客户端的端口="+socket.getPort()); } } }
public class ServerThread { public static void main(String[] args) throws Exception { //实现TCP通信下一发一收,服务端开发 System.out.println("服务端启动..."); //1.创建服务端ServerSocket对象 ServerSocket ss = new ServerSocket(9999); while(true){ Socket socket = ss.accept(); new ServerReader(socket).start(); } } }public class ServerReader extends Thread{ private Socket Socket; public ServerReader(Socket socket) { this.Socket = socket; } @Override public void run() { try { DataInputStream dis = new DataInputStream(Socket.getInputStream()); while (true) { String msg = dis.readUTF(); System.out.println("msg=" + msg); System.out.println("客户端的ip=" + Socket.getInetAddress().getHostAddress()); System.out.println("客户端的端口=" + Socket.getPort()); } } catch (Exception e) { e.printStackTrace(); System.out.println("客户端断开连接"+Socket.getInetAddress().getHostAddress()); } } }反射
加载类,并允许以编程的方式解剖类中的各种成分(成员变量、方法、构造器等)
基本作用:
可以得到一个类的全部成分然后操作
可以破坏封装性
可以绕过泛型约束
最重要的用途:适合做Java的框架,基本上,主流的框架都会基于反射设计一些通用的功能
public static void saveObject(Object obj) throws Exception { PrintStream ps = new PrintStream(new FileOutputStream("5.text", true)); Class c=obj.getClass(); String simpleName = c.getSimpleName(); ps.println("============"+simpleName+"======================"); Field[] fields = c.getDeclaredFields(); for (Field field : fields) { String fieldName = field.getName(); field.setAccessible(true); Object fieldValue = field.get(obj)+""; ps.println(fieldName+"="+fieldValue); } }注解
注解是Java代码里的特殊标记,作用是:让其它程序根据注解信息来决定怎么执行该程序。
自定义注解
注解的解析
元注解
@Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface MyTest { int count() default 1; }public class AnnotationDemo { public static void main(String[] args) throws Exception { Class c= AnnotationDemo.class; AnnotationDemo ad=new AnnotationDemo(); Method[] methods=c.getMethods(); for (Method method : methods) { if(method.isAnnotationPresent(MyTest.class)){ MyTest myTest=method.getDeclaredAnnotation(MyTest.class); int count =myTest.count(); for (int i = 0; i < count; i++) { method.invoke(ad); } } } } @MyTest(count = 5) public void test1(){ System.out.println("test1方法执行了"); } public void test2(){ System.out.println("test2方法执行了"); } @MyTest public void test3(){ System.out.println("test3方法执行了"); } public void test4(){ System.out.println("test4方法执行了"); } }动态代理
![]()
public interface StarService { void sing(String name); String dance(); }@Data @AllArgsConstructor @NoArgsConstructor public class Star implements StarService{ private String name; public void sing(String name) { System.out.println(this.name + "正在唱歌"); } public String dance() { System.out.println(this.name + "正在跳舞"); return "谢谢"; } }public class ProxyUtil { public static StarService createProxy(Star star){ StarService proxy=(StarService) Proxy.newProxyInstance(ProxyUtil.class.getClassLoader(), star.getClass().getInterfaces(), new InvocationHandler() { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { String name = method.getName(); if(name.equals("sing")){ System.out.println("准备话筒,收钱20w"); } if(name.equals("dance")){ System.out.println("准备场地,收钱100w"); } return method.invoke(star, args); } }); return proxy; } }public class ProxyTest { public static void main(String[] args) { Star star = new Star("洛天依"); StarService proxy = ProxyUtil.createProxy(star); proxy.sing("霜雪千年"); System.out.println(proxy.dance()); } }加强版工具包
@SuppressWarnings("unchecked") public class UserUtil { public static <T> T createProxy(T t){ T proxy=(T) Proxy.newProxyInstance(UserUtil.class.getClassLoader(), t.getClass().getInterfaces(), new InvocationHandler() { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { long start = System.currentTimeMillis(); Object result = method.invoke(t, args); long end = System.currentTimeMillis(); System.out.println("耗时:"+(end-start)/1000+"秒"); return result; } }); return proxy; } }