本文最后更新于:3 个月前
破冰 🥇 推荐阅读:
🍖 反射:
java中的反射原理,为什么要使用反射以及反射使用场景(面试常问) - 掘金 (juejin.cn)
Java 反射机制详解 | JavaGuide(Java面试 + 学习指南)
☕ 代理:
Java 代理模式详解 | JavaGuide(Java面试 + 学习指南)
🥣 注解:
详解 JAVA 注解机制 - 掘金 (juejin.cn)
🌮 语法糖:
Java 语法糖详解 | JavaGuide(Java面试 + 学习指南)
🍛 集合:
【Java从0到1学习】11 Java集合框架-CSDN博客
思维碰撞 反射机制 什么是反射
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 () { System.out.println("Hello!" ); }private void sayHi () { System.out.println("Hi!" ); }private void sayName (String name) { System.out.println(name); }
当然了,每个成员属性都提供有 getter、setter 方法 ,这里省略不写
获取Class对象
1 2 3 4 5 6 7 8 9 10 11 12 Class<Person> personClass1 = Person.class;Person person = new Person (); Class<? extends Person > personClass2 = person.getClass(); Class<?> personClass3 = Class.forName("反射.Person" ); System.out.println("-----------获取Class对象-----------" ); System.out.println(personClass1); System.out.println(personClass2); System.out.println(personClass3);
获取属性
1 2 3 4 5 6 Field[] fields = personClass1.getFields(); System.out.println("-----------获取属性(Public)-----------" );for (Field field : fields) { System.out.println("属性名: " + 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 ); Field[] fields2 = personClass1.getDeclaredFields(); System.out.println("-----------获取属性(所有)-----------" );for (Field field : fields2) { field.setAccessible(true ); System.out.println("属性名: " + field.getName() + " | 类型: " + field.getType() + " | 访问修饰符: " + field.getModifiers() + " | 属性值: " + field.get(person1)); }
获取方法
1 2 3 4 5 6 System.out.println("-----------获取方法(所有)-----------" ); Method[] methods = personClass1.getMethods();for (Method method : methods) { System.out.println("方法名: " + method.getName()); }
1 2 3 4 5 System.out.println("-----------调用指定方法(指定方法名)-----------" );Method sayHello = personClass1.getMethod("sayHello" ); System.out.print("方法名: " + sayHello.getName() + " | 调用结果: " ); sayHello.invoke(person);
1 2 3 4 5 6 System.out.println("-----------调用私有方法-----------" );Method sayHi = personClass1.getDeclaredMethod("sayHi" ); sayHi.setAccessible(true ); System.out.print("方法名: " + sayHi.getName() + " | 调用结果: " ); sayHi.invoke(person);
1 2 3 4 5 6 System.out.println("-----------调用带参方法-----------" );Method sayName = personClass1.getDeclaredMethod("sayName" , String.class); sayName.setAccessible(true ); System.out.print("方法名: " + sayName.getName() + " | 调用结果: " ); sayName.invoke(person, "邓哈哈" );
获取构造器
1 2 3 4 5 6 7 System.out.println("-----------获取构造器(Public)-----------" ); Constructor<?>[] constructors = personClass1.getConstructors();for (Constructor constructor1 : constructors) { System.out.println(constructor1); }
1 2 3 4 5 6 System.out.println("-----------获取构造器(所有)-----------" ); Constructor[] constructors01 = personClass1.getDeclaredConstructors();for (Constructor constructor1 : constructors01) { System.out.println(constructor1); }
踩坑记录
代理模式 什么是代理
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) { System.out.println("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) { System.out.println("----------在send()方法之前执行的操作----------" ); smsService.send(message); System.out.println("----------在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); smsProxy.send("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 { String send (String message) ; }
1 2 3 4 5 6 7 8 9 public class SmsServiceImpl implements SmsService { public String send (String message) { System.out.println("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 { System.out.println("----------在send()方法之前执行的操作----------" ); Object result = method.invoke(target, args); System.out.println("----------在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( target.getClass().getClassLoader(), target.getClass().getInterfaces(), 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 ()); smsService.send("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 ) { System.out.println(content); } } 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 ) { System.out.print(new String (bytes, 0 , len)); System.out.print(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 ) { System.out.println((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 ) { System.out.println(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()); System.out.println(str); } 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 ) { bos.write(bytes, 0 , len); } } catch (IOException e) { e.printStackTrace(); } long end = System.currentTimeMillis(); System.out.println("使用缓冲流复制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(); fos.write(bytes); } 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 ) { fos.write(bytes, 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" ; s.charAt(0 ) = 'h' ; s.length() = 5 ; String t = s.substring(0 , 5 ); t = t + " 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" }; arr[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++) { buf[j] = value[j]; } while (i < len) { byte c = value[i]; buf[i] = (c == (byte )oldChar) ? (byte )newChar : c; i++; } return new String (buf, LATIN1); } else { byte [] buf = StringUTF16.newBytesFor(len); inflate(value, 0 , buf, 0 , i); while (i < len) { char c = (char )(value[i] & 0xff ); StringUTF16.putChar(buf, i, (c == oldChar) ? newChar : c); i++; } 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 (); ArrayList<Double> arrayList = new ArrayList <>(); long startTime = System.currentTimeMillis(); for (int i = 0 ; i < 100000 ; i++) { arrayList.add(random.nextDouble()); }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 Collections.sort(arrayList, new Comparator <Double>() { @Override public int compare (Double o1, Double o2) { return o1.compareTo(o2); } }); Collections.sort(arrayList, (o1, o2) -> { return o1.compareTo(o2); }); Collections.sort(arrayList, (o1, o2) -> o1.compareTo(o2));
1 2 3 4 5 6 7 8 9 arrayList.forEach(item -> { System.out.println(item); }); arrayList.stream().forEach(item -> System.out.println(item)); }
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++) { userList.add(rand.nextInt(1000 )); } List<Integer> userList2 = new ArrayList <>(); userList2.addAll(userList); Long startTime1 = System.currentTimeMillis(); userList2.stream().sorted(Comparator.comparing(Integer::intValue)).collect(Collectors.toList()); System.out.println("stream.sort耗时:" + (System.currentTimeMillis() - startTime1) + "ms" ); Long startTime = System.currentTimeMillis(); userList.sort(Comparator.comparing(Integer::intValue)); System.out.println("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(); System.out.println(dateTime); DateTimeFormatter pattern = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss" ); String format = dateTime.format(pattern); System.out.println(format); String now = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy年MM月dd日 HH时mm分ss秒" )); System.out.println(now); }
1 2 3 4 5 6 7 static void test2 () { Duration between = Duration.between(LocalDate.of(2022 , Month.JULY, 10 ), LocalDate.now()); System.out.println(between); Duration between1 = Duration.between(LocalTime.of(12 , 29 , 10 ), LocalDate.now()); System.out.println(between1); }
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 () { Optional<String> optional = Optional.of("hhh" ); if (optional.isPresent()) { System.out.println(optional.get()); } Optional.of("hhh" ).ifPresent(System.out::println); }
1 2 3 static void test4 (User user) { Optional.ofNullable(user).map(User::getName).ifPresentOrElse(out::println, user != null ? user.setName("ccc" ) : null ); }
1 2 Optional<String> empty = Optional.empty();
1 2 Optional<String> hello = Optional.of("Hello" );
1 2 Optional<String> name = Optional.ofNullable("Hello" );
1 2 3 4 5 6 7 8 Optional<String> name1 = Optional.ofNullable("tom" );if (name.isPresent()) { System.out.println("Hello, " + name1.get()); } else { System.out.println("Name is not available" ); }
1 2 3 4 5 6 7 Optional<String> name2 = Optional.ofNullable("tom" ); name.ifPresent(s -> { System.out.println("Hello, " + name2.get()); });
1 2 3 4 5 6 Optional<String> name3 = Optional.ofNullable(null );String greeting = "Hello, " + name3.orElse("Guest" ); System.out.println(greeting);
1 2 3 4 5 Optional<String> name4 = Optional.ofNullable("null" );String greeting2 = "Hello, " + name4.orElseThrow(() -> new NullPointerException ("null" ));
1 2 3 4 5 6 Optional<String> name5 = Optional.ofNullable("tom" );String greeting3 = "Hello, " + name5.map(s -> s.toUpperCase()).get(); System.out.println(greeting3);
1 2 3 4 5 Optional<String> name6 = Optional.ofNullable("tom" );String greeting4 = name6.flatMap(s -> Optional.of("Hello " + s)).get(); System.out.println(greeting4);
1 2 3 4 5 Optional<String> name7 = Optional.ofNullable("tom" );String greeting5 = "Hello " + name7.filter(s -> !s.isEmpty()).get(); System.out.println(greeting5);
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); out.println(add); out.println(subtract); out.println(multiply); System.out.println(divide); BigDecimal bigDecimal = divide.stripTrailingZeros(); out.println(bigDecimal); BigDecimal value = new BigDecimal ("1888977466432.1241341341413414" ); double doubleValue = value.doubleValue(); out.println(doubleValue); double doubleValue1 = value.toBigInteger().doubleValue(); out.println(doubleValue1); out.println(add.compareTo(subtract) > 0 ? "add大于sub" : "add小于sub" ); }
数组转字符串
1 2 3 4 List<String> list = Arrays.asList("Apple" , "Banana" , "Cherry" , "Date" , "Elderberry" ); String[] arr = {"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++) { sb.append(i); if (i < list.size() - 1 ) { sb.append("," ); } }
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) { Map<String, Integer> map = new HashMap <>(); map.put("apple" , 1 ); map.put("banana" , 2 ); map.computeIfAbsent("orange" , key -> { System.out.println("Performing computation for orange" ); return key.length(); }); map.computeIfAbsent("apple" , key -> { System.out.println("Performing computation for apple" ); return key.length(); }); System.out.println(map); } }
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) { Map<String, Integer> map = new HashMap <>(); map.put("apple" , 1 ); map.put("banana" , 2 ); map.computeIfPresent("apple" , (key, value) -> value * 2 ); map.computeIfPresent("orange" , (key, value) -> value * 2 ); System.out.println(map); } }
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) { Map<String, Integer> map = new HashMap <>(); map.put("apple" , 1 ); map.put("banana" , null ); map.computeIfPresent("apple" , (key, value) -> value * 2 ); map.computeIfPresent("banana" , (key, value) -> value * 2 ); System.out.println(map); map.compute("apple" , (key, value) -> value + 3 ); map.compute("banana" , (key, value) -> value + 3 ); System.out.println(map); } }
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 ; public class MergeExample { public static void main(String [] args) { Map <String , Integer> map1 = new HashMap<>(); map1.put("apple" , 1 ); map1.put("banana" , 2 ); Map <String , Integer> map2 = new HashMap<>(); map2.put("apple" , 5 ); map2.put("orange" , 3 ); map2.forEach((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 ; } Map<Character, Integer> frequency = new HashMap <Character, Integer>(); for (int i = 0 ; i < s.length(); ++i) { char ch = s.charAt(i); frequency.put(ch, frequency.getOrDefault(ch, 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) { List<Person> people = new ArrayList <>(); people.add(new Person (25 , "Alice" )); people.add(new Person (30 , "Bob" )); people.add(new Person (25 , "Charlie" )); people.add(new Person (40 , "David" )); people.add(new Person (30 , "Emily" )); Map<Integer, List<Person>> groups = people.stream() .collect(Collectors.groupingBy(Person::getAge)); System.out.println(groups); } }
泛型理解
学习Java泛型:(2023/10/13晚)
泛型类
泛型接口
泛型方法:返回值、入参位置、方法体中
个人感受:泛型很好理解:我们经常讲到一个对象实例的就是以类作为模板创建的,那么也可以讲一个普通类可以以泛型类作为模板;那么泛型是用来干嘛的呢,我们为什么要使用泛型呢?其实,所有的泛型类在编译后生成的字节码与普通类无异,因为在编译前,所有泛型类型就被擦除了。所以我们可以把泛型看作一个语法糖,将类型转换的校验提前在编译时,减少类型转换错误的发生,使编写的程序更加具有健壮性。
我觉得以下这段总结更妙:
泛型是Java语言中的一项强大的特性,它允许在编译时指定类、接口或方法的参数类型,从而在编译阶段就能够进行类型检查。这样可以减少类型转换的错误,并提高代码的安全性和可读性。
通过使用泛型,我们可以在编译时捕捉到一些类型错误,而不是在运行时才发现,这样可以避免一些潜在的bug。泛型还可以增加代码的可重用性和灵活性,因为泛型类、接口和方法可以用于多种不同的类型,而无需针对每一种类型单独编写或重复编写相似的代码。
总的来说,通过使用泛型,我们可以在编写Java代码时更好地约束和使用类型信息,减少类型错误,提高代码的可读性和健壮性。
了解泛型的实现原理,理解泛型的使用方式,更加加深了我对 Java 语言的理解
JVM
🥣 推荐阅读:jvm内存模型(运行时数据区)简介 - 知乎 (zhihu.com) (2023/12/13)
锦绣收官