本文最后更新于:5 个月前
                  
                
              
            
            
              
                
                破冰 🥇 推荐阅读:
🍖 反射:
java中的反射原理,为什么要使用反射以及反射使用场景(面试常问) - 掘金 (juejin.cn) 
Java 反射机制详解 | JavaGuide(Java面试 + 学习指南) 
☕ 代理:
Java 代理模式详解 | JavaGuide(Java面试 + 学习指南) 
🥣 注解:
详解 JAVA 注解机制 - 掘金 (juejin.cn) 
🌮 语法糖:
Java 语法糖详解 | JavaGuide(Java面试 + 学习指南) 
🍛 集合:
【Java从0到1学习】11 Java集合框架-CSDN博客 
思维碰撞 新特性 
2024年6月20日
 
JShell:快速验证简单的小问题 挺有意思的小功能,可以用来做简单的输出和运算:
反射机制 什么是反射 
Java 中的反射机制 是指:在运行状态中,对于任何一个类,我们都能获取到这个类的所有属性和方法,还可以调用这些属性和方法
 
反射的应用场景 
Spring 中的依赖注入、RPC 调用、Java 中的动态代理实现
 
简单演示 
👏 有关反射内容的介绍,网上多了去了,详尽且细致 ,可以参考本文开头的推荐阅读篇目 挑选阅读
 
我们的特点就是不讲任何多余的废话 ,直接通过简单的代码演示,体会 Java 中的反射机制 ** (2023/10/24午)  
 
构造 Person 对象 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 private  String name;protected  Double grade;long  id;public  int  age;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 public  Person (String name, Double grade, long  id, int  age)  {this .name = name;this .grade = grade;this .id = id;this .age = age;
1 2 3 4 5 6 7 8 9 10 11 12 private  Person (String name, Double grade, long  id)  {this .name = name;this .grade = grade;this .id = id;
1 2 3 4 5 6 7 8 9 10 11 public  void  sayHello () {"Hello!" );private  void  sayHi () {"Hi!" );private  void  sayName (String name) {
当然了,每个成员属性都提供有 getter、setter 方法 ,这里省略不写 
 
获取Class对象 
1 2 3 4 5 6 7 8 9 10 11 12 Person  person  =  new  Person ();extends  Person > personClass2 = person.getClass();"反射.Person" );"-----------获取Class对象-----------" );
获取属性 
1 2 3 4 5 6 "-----------获取属性(Public)-----------" );for  (Field field : fields) {"属性名: "  + field.getName() + " | 类型: "  + field.getType());
1 2 3 4 5 6 7 8 9 10 11 12 13 Person  person1  =  new  Person ("邓哈哈" , 99.0 , 18889898988L , 18 );"-----------获取属性(所有)-----------" );for  (Field field : fields2) {true ); "属性名: "  + field.getName()" | 类型: "  + field.getType()" | 访问修饰符: "  + field.getModifiers()" | 属性值: "  + field.get(person1));
获取方法 
1 2 3 4 5 6 "-----------获取方法(所有)-----------" );for  (Method method : methods) {"方法名: "  + method.getName());
1 2 3 4 5 "-----------调用指定方法(指定方法名)-----------" );Method  sayHello  =  personClass1.getMethod("sayHello" );"方法名: "  + sayHello.getName() + " | 调用结果: " );
1 2 3 4 5 6 "-----------调用私有方法-----------" );Method  sayHi  =  personClass1.getDeclaredMethod("sayHi" );true );"方法名: "  + sayHi.getName() + " | 调用结果: " );
1 2 3 4 5 6 "-----------调用带参方法-----------" );Method  sayName  =  personClass1.getDeclaredMethod("sayName" , String.class);true );"方法名: "  + sayName.getName() + " | 调用结果: " );"邓哈哈" );
获取构造器 
1 2 3 4 5 6 7 "-----------获取构造器(Public)-----------" );for  (Constructor constructor1 : constructors) {
1 2 3 4 5 6 "-----------获取构造器(所有)-----------" );for  (Constructor constructor1 : constructors01) {
踩坑记录 
代理模式 什么是代理 
Java 中的代理模式是指:使用代理对象来代替对真实对象的访问,这样可以在不修改原目标对象的前提下,提供额外的功能操作
代理模式可隐藏客户端真正调用的对象 ,实现代码解耦 ,增强系统的可维护性和可扩展性 
代理模式常用于需要控制对对象的访问,并提供远程访问、安全检查和缓存 等功能 (2023/10/20午)  
 
 
静态代理 实现步骤 
定义一个接口及其实现类(被代理类) 
创建一个代理类,同样实现这个接口 
将目标对象注入进代理类,在代理类的对应方法中调用目标类的对应方法,这样我们就可以通过代理类屏蔽对目标对象的访问,并且可以在目标方法执行前后做一些自己想做的事情 
 
代码实现 
1 2 3 4 5 6 public  interface  SmsService  {void  send (String message) ;
1 2 3 4 5 6 7 8 public  class  SmsServiceImpl  implements  SmsService  {public  void  send (String message)  {"send message:"  + message);
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 public  class  SmsProxy  implements  SmsService  {private  final  SmsService smsService;public  SmsProxy (SmsService smsService)  {this .smsService = smsService;@Override public  void  send (String message)  {"----------在send()方法之前执行的操作----------" );"----------在send()方法之后执行的操作----------" );
1 2 3 4 5 6 7 public  class  Main  {public  static  void  main (String[] args)  {SmsService  smsService  =  new  SmsServiceImpl ();SmsProxy  smsProxy  =  new  SmsProxy (smsService);"java" );
动态代理 
相较于静态代理来说,动态代理要更加灵活,我们不需要针对每个目标类都单独创建一个代理类,也不必需要实现接口,我们可以直接代理实现类
👏 实现方式 :就 Java 来说,动态代理的实现方式有很多种,比如 JDK 动态代理 、CGLIB 动态代理 
动态代理在我们日常开发中几乎用不到,或者说使用相对较少,但在框架中,几乎是必用的一门技术。学会了动态代理之后,对于我们理解和学习各种框架的原理也非常有帮助
🧯 使用场景 :Spring AOP 、RPC 框架 
7000字详解 动态代理(JDK动态代理 CGLIB动态代理)与静态代理_jdk17代理-CSDN博客 
 
JDK 动态代理 JDK 动态代理的实现原理是:动态创建代理类,然后通过指定类加载器进行加载 。在创建代理对象时,需要将 InvocationHandler 对象作为构造参数传入;当调用代理对象时,会调用 InvocationHandler.invoke() 方法,从而执行代理逻辑,最终调用真正业务对象的相应方法。
JDK 动态代理执行流程 :通过 Proxy 类的 newProxyInstance() 创建的代理对象再调用方法的时候,实际会调用代理类(实现 InvocationHandler 接口的类)的 invoke() 方法。
还是那句话,有关 JDK 动态代理的实现原理,这里不做详尽的解释,我们主打不讲任何多余的废话,通过简单的代码演示:
🔥 推荐阅读 :Java 代理模式详解 | JavaGuide(Java面试 + 学习指南) 
 
1 2 3 4 5 6 public  interface  SmsService  {send (String message) ;
1 2 3 4 5 6 7 8 9 public  class  SmsServiceImpl  implements  SmsService  {public  String send (String message)  {"send message:"  + message);return  message;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 public  class  DebugInvocationHandler  implements  InvocationHandler  {private  final  Object target;public  DebugInvocationHandler (Object target)  {this .target = target;public  Object invoke (Object proxy, Method method, Object[] args)  throws  InvocationTargetException, IllegalAccessException {"----------在send()方法之前执行的操作----------" );Object  result  =  method.invoke(target, args);"----------在send()方法之后执行的操作----------" );return  result;
1 2 3 4 5 6 7 8 9 10 11 12 public  class  JdkProxyFactory  {public  static  Object getProxy (Object target)  {return  Proxy.newProxyInstance(new  DebugInvocationHandler (target)   
1 2 3 4 5 6 public  class  Main  {public  static  void  main (String[] args)  {SmsService  smsService  =  (SmsService) JdkProxyFactory.getProxy(new  SmsServiceImpl ());"java" );
CGLIB 动态代理 
CGLIB 动态代理执行流程 :自定义 MethodInterceptor 并重写 intercept 方法,intercept 用于拦截增强被代理增强的方法,通过 Enhancer 类的 create() 创建代理类。当代理类调用方法的时候,实际调用的是 MethodIntercept 中的 intercept 方法
有关CGLIB 动态代理的实现原理,这里不做详尽的解释,我们主打不讲任何多余的废话,通过简单的代码演示:
🔥 推荐阅读 :Java 代理模式详解 | JavaGuide(Java面试 + 学习指南) (2023/10/30晚) 
 
I/O 流 字节流 1 2 3 4 5 6 7 8 9 10 11 12 static  void  testFileInputStream ()  {try  {FileInputStream  fis  =  new  FileInputStream ("D:\\Project\\IDEA\\demo\\demo2\\demo\\algorisem\\IO\\IO流练习\\text\\hello.txt" );int  content;while  ((content = fis.read()) != -1 ) {catch  (IOException e) {throw  new  RuntimeException (e);
1 2 3 4 5 6 7 8 9 10 11 12 13 14 static  void  testFileInputStream2 ()  {try  {FileInputStream  fis  =  new  FileInputStream ("D:\\Project\\IDEA\\demo\\demo2\\demo\\algorisem\\IO\\IO流练习\\text\\hello.txt" );int  len;byte [] bytes = new  byte [1024 ];while  ((len = fis.read(bytes)) != -1 ) {new  String (bytes, 0 , len));catch  (IOException e) {throw  new  RuntimeException (e);
1 2 3 4 5 6 7 8 9 10 11 12 static  void  testFileReader ()  {try  {FileReader  fr  =  new  FileReader ("D:\\Project\\IDEA\\demo\\demo2\\demo\\algorisem\\IO\\IO流练习\\text\\hello.txt" );int  content;while  ((content = fr.read()) != -1 ) {char ) content);catch  (IOException e) {throw  new  RuntimeException (e);
1 2 3 4 5 6 7 8 9 10 11 12 13 static  void  testFileReader2 ()  {try  {FileReader  fr  =  new  FileReader ("D:\\Project\\IDEA\\demo\\demo2\\demo\\algorisem\\IO\\IO流练习\\text\\hello.txt" );int  len;char [] chars = new  char [1024 ];while  ((len = fr.read(chars)) != -1 ) {new  String (chars, 0 , len));catch  (IOException e) {throw  new  RuntimeException (e);
1 2 3 4 5 6 7 8 9 10 11 static  void  testBufferInputStream ()  {try  {BufferedInputStream  bis  =  new  BufferedInputStream (new FileInputStream ("D:\\Project\\IDEA\\demo\\demo2\\demo\\algorisem\\IO\\IO流练习\\text\\hello.txt" ));String  str  =  new  String (bis.readAllBytes());catch  (IOException e) {throw  new  RuntimeException (e);
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 static  void  copy_pdf_to_another_pdf_with_byte_array_buffer_stream ()  {long  start  =  System.currentTimeMillis();try  (BufferedInputStream  bis  =  new  BufferedInputStream (new  FileInputStream ("D:\\Project\\IDEA\\demo\\demo2\\demo\\algorisem\\IO\\IO流练习\\text\\1.png" ));BufferedOutputStream  bos  =  new  BufferedOutputStream (new  FileOutputStream ("D:\\Project\\IDEA\\demo\\demo2\\demo\\algorisem\\IO\\IO流练习\\text\\111.png" ))) {int  len;byte [] bytes = new  byte [4  * 1024 ];while  ((len = bis.read(bytes)) != -1 ) {0 , len);catch  (IOException e) {long  end  =  System.currentTimeMillis();"使用缓冲流复制PDF文件总耗时:"  + (end - start) + " 毫秒" );
1 2 3 4 5 6 7 8 9 10 static  void  testFileOutputStream ()  {try  {FileOutputStream  fos  =  new  FileOutputStream ("D:\\Project\\IDEA\\demo\\demo2\\demo\\algorisem\\IO\\IO流练习\\text\\hello2.txt" );byte [] bytes = "你好,这又是一段话" .getBytes();catch  (IOException e) {throw  new  RuntimeException (e);
1 2 3 4 5 6 7 8 9 10 11 12 13 14 static  void  copyInputStream ()  {try  {FileInputStream  fis  =  new  FileInputStream ("D:\\Project\\IDEA\\demo\\demo2\\demo\\algorisem\\IO\\IO流练习\\text\\hello2.txt" );FileOutputStream  fos  =  new  FileOutputStream ("D:\\Project\\IDEA\\demo\\demo2\\demo\\algorisem\\IO\\IO流练习\\text\\hello3.txt" );int  len;byte [] bytes = new  byte [1024 ];while  ((len = fis.read(bytes)) != -1 ) {0 , len);catch  (IOException e) {throw  new  RuntimeException (e);
字符流 注解 
🍖 推荐阅读:Spring Boot 自定义注解 - 掘金 (juejin.cn) (2024/01/07晚) 
 
元注解 @Target 
@Retention 
@Document 
@Inherited 
JDK 内置注解 @Override @Deperecated @SuppressWarnings 自定义注解 八股吟唱 谈一谈你对多态的理解 我们经常讲,面向对象有三大特征:封装、继承和多态(2023/11/18早)
多态是面向对象编程中相当重要的一个概念,它允许通过父类类型的引用变量引用子类对象,在运行时根据实际的对象类型来确定调用哪个方法,一个对象能够根据不同的场景表现出多种形态
那多态具体是怎么实现的?在编译时,Java 编译器只能知道变量的声明类型,也就是父类类型,而无法确定实际的对象类型;而在运行时,Java 虚拟机会通过动态绑定解析出实际对象的类型,根据实际的对象类型调用被子类重写的方法。
也就是说,编译器会把方法的绑定,即方法的具体调用推迟到运行时,这就是动态绑定,这就是多态的实现原理 
我们发现使用多态有这样的好处:我们通过父类类型的引用来访问子类对象的方法,统一对象的接口,这是接口统一性;子类对象可以随时替代父类对象,向上转型,这就是可替换性;我们可以通过添加新的子类,扩展系统功能,这就是可扩展性;通过多态,能够实现对象间的解耦,因为我们不再需要指定具体对象去实现具体方法了,这使得代码更加简洁通用、更加易于维护
我们在编码开发中接触到的方法重载、方法重写、接口实现就是多态的具体实现,方法重载体现的是编译时的多态,而方法重写和接口实现体现的是运行时的多态
String 内容不可变 
🔥 有关 Java 中 String 内容不可变的解释(2023/11/19午) 
 
我们经常听到这样的定义:String 对象一旦被创建,其内容就一定不可变
这句话的意思是:对该对象的所有操作(如 replace()、contact()、substring())都将返回新的 String 对象,而不是在原 String 对象的内容上作修改。这些操作也都是不被允许的:
1 2 3 4 5 6 7 8 9 String  s  =  "Hello" ;  0 ) = 'h' ; 5 ; String  t  =  s.substring(0 , 5 ); " World" ; 
为什么是这样的?String 对象内容不可变是如何保证的,这样做又有什么好处呢?
String 类被设计出来是为了方便我们对字符串进行操作,我们常见的字符串拼接、比较字符串内容、字符串长度等等,应用十分广泛。而 String 类底层是通过 char [] 数组(Java 9 之后改为 byte [] 实现了)来维护字符串:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 public  final  class  String implements  java .io.Serializable, Comparable<String>, CharSequence {@Stable private  final  byte [] value;
需要注意的是,这个字符串使用了 final 关键字修饰。
我们都知道被 final 关键字修饰的类不能被继承、修饰的方法不能被重写、修饰的基本数据类型变量的值不能被改变,修饰的引用类型变量不能再指向其他对象。很显然,这里的 char [] 数组属于引用型变量,所以其内容是可以改变的
这就是很多人疑惑的点了:难道不是很奇怪吗?String的内容是不可变的,但 String 底层是 final 修饰的 char[] 数组实现的,而这个数组内容是可变的。所以你给解释一下String内容不可变到底是怎么一回事
很多八股在这里都在扯淡,在这里我给出正确答案:这里的 char [] 数组属于引用型变量,理论上它的内容当然是可以改变的:
1 2 3 final  String[] arr = new  String []{"Hello" , "World" }; 0 ] = "Hi" ; 
但是这一点跟 String 内容是不可变的本身没有冲突,因为 String 并没有对外提供任何方法,去改变内置的 char [] 数组的内容,所以String 对外表现出的 String 内容不可变,这就是:String 对象一旦被创建,其内容就一定不可变 的正确解释
综上所述,String 类是不可变的,这意味着一旦一个 String 对象被创建,它的内容就不能被修改。即使 String 底层是通过 final 修饰的 char 数组实现的,但是这个 char 数组的内容也不能被修改,因为 String 并没有对外提供任何方法,允许我们去改变内置的 char [] 数组的内容。因此,即使我们可以访问到 String 对象的底层 char 数组,我们也不能通过改变这个数组来修改 String 对象的内容。任何尝试修改 String 对象内容的操作都会返回一个新的 String 对象,而原来的 String 对象保持不变
查看源码你就能清楚地看到这个过程了,当然源码很复杂,这里展示出 replace 的部分源码,你可以看到在执行这个操作的过程中,是 new 了新的 byte [] 的:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 public  static  String replace (byte [] value, char  oldChar, char  newChar)  {if  (canEncode(oldChar)) {int  len  =  value.length;int  i  =  -1 ;while  (++i < len) {if  (value[i] == (byte )oldChar) {break ;if  (i < len) {if  (canEncode(newChar)) {byte  buf[] = new  byte [len];for  (int  j  =  0 ; j < i; j++) {    while  (i < len) {byte  c  =  value[i];byte )oldChar) ? (byte )newChar : c;return  new  String (buf, LATIN1);else  {byte [] buf = StringUTF16.newBytesFor(len);0 , buf, 0 , i);while  (i < len) {char  c  =  (char )(value[i] & 0xff );return  new  String (buf, UTF16);return  null ; 
这样做有什么好处?这种不可变性是 Java String 类的一个重要特性,使得 String 可以安全地被共享和传递,而不需要担心其他部分的代码会修改它的内容 
 
Java基础 
本人太菜,不定时巩固Java基础,今天巩固如下操作:(2023/08/14早) 
 
1 2 3 4 5 6 7 Random  random  =  new  Random ();new  ArrayList <>();long  startTime  =  System.currentTimeMillis();for  (int  i  =  0 ; i < 100000 ; i++) {
1 2 3 4 5 6 7 8 9 10 11 12 13 14 new  Comparator <Double>() {@Override public  int  compare (Double o1, Double o2)  {return  o1.compareTo(o2);return  o1.compareTo(o2);
1 2 3 4 5 6 7 8 9 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 List<Integer> userList = new  ArrayList <>();Random  rand  =  new  Random ();for  (int  i  =  0 ; i < 10000 ; i++) {1000 ));new  ArrayList <>();Long  startTime1  =  System.currentTimeMillis();"stream.sort耗时:"  + (System.currentTimeMillis() - startTime1) + "ms" );Long  startTime  =  System.currentTimeMillis();"List.sort()耗时:"  + (System.currentTimeMillis() - startTime) + "ms" );
Date/Time API 
1 2 3 4 5 6 7 8 9 10 11 12 static  void  test ()  {LocalDateTime  dateTime  =  LocalDateTime.now();DateTimeFormatter  pattern  =  DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss" );String  format  =  dateTime.format(pattern);String  now  =  LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy年MM月dd日 HH时mm分ss秒" ));
1 2 3 4 5 6 7 static  void  test2 ()  {Duration  between  =  Duration.between(LocalDate.of(2022 , Month.JULY, 10 ), LocalDate.now());Duration  between1  =  Duration.between(LocalTime.of(12 , 29 , 10 ), LocalDate.now());
Optional 容器类型 
null 不好,我真的推荐你使用 Optional - 掘金 (juejin.cn) 
那么使用 Optional 容器类型 有什么好处呢?(2023/11/29午) 
可以避免空指针异常,提高代码的健壮性和可读性。 
可以减少显式的空值检查和 null 的使用,使代码更简洁和优雅。 
可以利用函数式编程的特性,实现更灵活和高效的逻辑处理。 
可以提高代码的可测试性,方便进行单元测试和集成测试。 
 
 
ofNullable() 方法:创建一个可能包含 null 值的 Optional 对象(2023/08/18午)
isPresent() 方法:判断 Optional 中是否存在值。返回 ture 表示存在值 返回 false 表示为null
get() 方法:如果 Optional 的值存在则返回该值,否则抛出 NoSuchElementException 异常。
 
1 2 3 4 5 6 7 8 9 static  void  test3 ()  {"hhh" );if  (optional.isPresent()) {"hhh" ).ifPresent(System.out::println);
1 2 3 static  void  test4 (User user)  {null  ?      user.setName("ccc" ) : null );
1 2 
1 2 "Hello" );
1 2 "Hello" );
1 2 3 4 5 6 7 8 "tom" );if  (name.isPresent()) {"Hello, "  + name1.get());else  {"Name is not available" );
1 2 3 4 5 6 7 "tom" );"Hello, "  + name2.get());
1 2 3 4 5 6 null );String  greeting  =  "Hello, "  + name3.orElse("Guest" );
1 2 3 4 5 "null" );String  greeting2  =  "Hello, "  + name4.orElseThrow(() -> new  NullPointerException ("null" ));
1 2 3 4 5 6 "tom" );String  greeting3  =  "Hello, "  + name5.map(s -> s.toUpperCase()).get();
1 2 3 4 5 "tom" );String  greeting4  =  name6.flatMap(s -> Optional.of("Hello "  + s)).get();
1 2 3 4 5 "tom" );String  greeting5  =  "Hello "  + name7.filter(s -> !s.isEmpty()).get();
BigDecimal 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 static  void  test5 ()  {BigDecimal  dec1  =  new  BigDecimal ("1.3" );BigDecimal  dec2  =  new  BigDecimal ("1.3" );BigDecimal  add  =  dec1.add(dec2);BigDecimal  subtract  =  dec1.subtract(dec2);BigDecimal  multiply  =  dec1.multiply(dec2);BigDecimal  divide  =  dec1.divide(dec2).setScale(3 , RoundingMode.HALF_DOWN);BigDecimal  bigDecimal  =  divide.stripTrailingZeros();BigDecimal  value  =  new  BigDecimal ("1888977466432.1241341341413414" );double  doubleValue  =  value.doubleValue();double  doubleValue1  =  value.toBigInteger().doubleValue();0  ? "add大于sub"  : "add小于sub" );
数组转字符串 
1 2 3 4 "Apple" , "Banana" , "Cherry" , "Date" , "Elderberry" );"0" , "1" , "2" , "3" , "4" , "5" };
如果是List,则有如下转字符串的方法:(2023/09/20晚)  
1 2 String  collect  =  list.stream().collect(Collectors.joining("," ));
1 2 String  join  =   StringUtils.join(longList, "," );
1 2 3 4 5 6 7 8 StringBuilder  sb  =  new  StringBuilder ();for  (int  i  =  0 ; i < list.size(); i++) {if  (i < list.size() - 1 ) {"," );
1 2 String  join  =  String.join("," , list);
以上方法,转换所消耗的时间越来越少 ,效率越来越高 
如果是String[],则有如下转字符串的方法:(2023/09/20晚)  
1 2 String  join  =   StringUtils.join(longList, "," );
1 2 String  join  =  String.join("," , list);
Java8中的Map函数 computeIfAbsent 
如果指定的key不存在于Map中,那么会执行指定的函数来计算并将结果作为value放入到Map中。 
如果指定的key已经存在于Map中,则不会执行计算函数,而是直接返回已存在的value。 
 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 import  java.util.HashMap;import  java.util.Map;public  class  ComputeIfAbsentExample  {public  static  void  main (String[] args)  {new  HashMap <>();"apple" , 1 );"banana" , 2 );"orange" , key -> {"Performing computation for orange" );return  key.length();"apple" , key -> {"Performing computation for apple" );return  key.length();
computeIfPresent 
如果指定的key存在于Map中,那么会执行指定的函数来计算并将结果作为新的value放入到Map中。 
如果指定的key不存在于Map中,则不会执行计算函数,什么也不做 
 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 import  java.util.HashMap;import  java.util.Map;public  class  ComputeIfPresentExample  {public  static  void  main (String[] args)  {new  HashMap <>();"apple" , 1 );"banana" , 2 );"apple" , (key, value) -> value * 2 );"orange" , (key, value) -> value * 2 );
compute 
而compute()函数无论旧的value是否为null,都会调用计算函数来计算新的value,并将计算结果更新到Map中。 
如果计算结果为null,则会将对应的key从Map中移除。 
 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 import  java.util.HashMap;import  java.util.Map;public  class  ComputeExample  {public  static  void  main (String[] args)  {new  HashMap <>();"apple" , 1 );"banana" , null );"apple" , (key, value) -> value * 2 );"banana" , (key, value) -> value * 2 );"apple" , (key, value) -> value + 3 );"banana" , (key, value) -> value + 3 );
merge 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 import  java.util.HashMap;import  java.util.Map ;class  MergeExample  {static  void  main(String [] args) {Map <String , Integer> map1 = new  HashMap<>();"apple" , 1 );"banana" , 2 );Map <String , Integer> map2 = new  HashMap<>();"apple" , 5 );"orange" , 3 );((key, value) -> {             map1.merge(key, value, (oldValue, newValue) -> oldValue + newValue);         }) ;        System .out .println (map1) ; // 输出: {orange =3, apple =6, banana =2}     } } 
getOrDefalut 
当 key 存在时,取对应 value,否则取默认值 
 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 public  static  int  firstUniqChar (String s)  {if  (s == null  || s.length() == 0 ) {return  0 ;new  HashMap <Character, Integer>();for  (int  i  =  0 ; i < s.length(); ++i) {char  ch  =  s.charAt(i);0 ) + 1 );for  (int  i  =  0 ; i < s.length(); ++i) {if  (frequency.get(s.charAt(i)) == 1 ) {return  i;return  -1 ;
putIfAbsent 
与 put 的区别:如果有重复 key,保存的是最早存入的键值对  (2023/10/08早)  
 
forEach Java8中的Stream流函数 groupingBy 
将集合中的元素,按某个属性进行分组 ,返回的结果 是一个 Map 集合  (2023/10/08早)  
 
1 2 3 4 5 6 7 8 9 public  class  Person  {private  int  age;private  String name;public  Person (int  age, String name)  {this .age = age;this .name = name;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 public  class  GroupingByExample  {public  static  void  main (String[] args)  {new  ArrayList <>();new  Person (25 , "Alice" ));new  Person (30 , "Bob" ));new  Person (25 , "Charlie" ));new  Person (40 , "David" ));new  Person (30 , "Emily" ));
泛型理解 
学习Java泛型:(2023/10/13晚) 
泛型类 
泛型接口 
泛型方法:返回值、入参位置、方法体中 
个人感受:泛型很好理解:我们经常讲到一个对象实例的就是以类作为模板创建的,那么也可以讲一个普通类可以以泛型类作为模板;那么泛型是用来干嘛的呢,我们为什么要使用泛型呢?其实,所有的泛型类在编译后生成的字节码与普通类无异,因为在编译前,所有泛型类型就被擦除了。所以我们可以把泛型看作一个语法糖,将类型转换的校验提前在编译时,减少类型转换错误的发生,使编写的程序更加具有健壮性。 
 
我觉得以下这段总结更妙:  
泛型是Java语言中的一项强大的特性,它允许在编译时指定类、接口或方法的参数类型,从而在编译阶段就能够进行类型检查。这样可以减少类型转换的错误,并提高代码的安全性和可读性。
通过使用泛型,我们可以在编译时捕捉到一些类型错误,而不是在运行时才发现,这样可以避免一些潜在的bug。泛型还可以增加代码的可重用性和灵活性,因为泛型类、接口和方法可以用于多种不同的类型,而无需针对每一种类型单独编写或重复编写相似的代码。
总的来说,通过使用泛型,我们可以在编写Java代码时更好地约束和使用类型信息,减少类型错误,提高代码的可读性和健壮性。
 
了解泛型的实现原理,理解泛型的使用方式,更加加深了我对 Java 语言的理解  
JVM 
🥣 推荐阅读:jvm内存模型(运行时数据区)简介 - 知乎 (zhihu.com)  (2023/12/13) 
 
锦绣收官