Lambda 表达式主要用于提供一种简洁的方式来表示匿名方法,使 Java 具备了函数式编程的特性。
比如说我们可以使用 Lambda 表达式来简化线程的创建:
new Thread(() -> System.out.println("Hello World")).start();
这比以前的匿名内部类要简洁很多。
所谓的函数式编程,就是把函数作为参数传递给方法,或者作为方法的结果返回。比如说我们可以配合 Stream 流进行数据过滤:
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6);
List<Integer> evenNumbers = numbers.stream()
.filter(n -> n % 2 == 0)
.collect(Collectors.toList());
其中 n -> n % 2 == 0
就是一个 Lambda 表达式。表示传入一个参数 n,返回 n % 2 == 0
的结果。
Java 集合框架可以分为两条大的支线:
①、Collection,主要由 List、Set、Queue 组成:
②、Map,代表键值对的集合,典型代表就是 HashMap。
二哥的 Java 进阶之路:Java集合主要关系
可以使用 Collections.synchronizedList()
方法,它将返回一个线程安全的 List。
SynchronizedList list = Collections.synchronizedList(new ArrayList());
内部是通过 synchronized 关键字加锁来实现的。
也可以直接使用 CopyOnWriteArrayList,它是线程安全的,遵循写时复制的原则,每当对列表进行修改(例如添加、删除或更改元素)时,都会创建列表的一个新副本,这个新副本会替换旧的列表,而对旧列表的所有读取操作仍然可以继续。
CopyOnWriteArrayList list = new CopyOnWriteArrayList();
通俗的讲,CopyOnWrite 就是当我们往一个容器添加元素的时候,不直接往容器中添加,而是先复制出一个新的容器,然后在新的容器里添加元素,添加完之后,再将原容器的引用指向新的容器。多个线程在读的时候,不需要加锁,因为当前容器不会添加任何元素。这样就实现了线程安全。
在 Java 中,有 3 种线程安全的 Map 实现,最常用的是ConcurrentHashMap和Collections.synchronizedMap(Map)
包装器。
Hashtable 也是线程安全的,但它的使用已经不再推荐使用,因为 ConcurrentHashMap 提供了更高的并发性和性能。
①、HashTable 是直接在方法上加 synchronized 关键字,比较粗暴。
二哥的 Java 进阶之路:HashTable
②、Collections.synchronizedMap
返回的是 Collections 工具类的内部类。
二哥的 Java 进阶之路:Collections.synchronizedMap
内部是通过 synchronized 对象锁来保证线程安全的。
③、ConcurrentHashMap 在 JDK 7 中使用分段锁,在 JKD 8 中使用了 CAS(Compare-And-Swap)+ synchronized 关键字,性能得到进一步提升。
初念初恋:ConcurrentHashMap 8 中的实现
JDK 1.8 新增了不少新的特性,如 Lambda 表达式、接口默认方法、Stream API、日期时间 API、Optional 类等。
三分恶面渣逆袭:JDK1.8主要新特性
①、Java 8 允许在接口中添加默认方法和静态方法。
public interface MyInterface {
default void myDefaultMethod() {
System.out.println("My default method");
}
static void myStaticMethod() {
System.out.println("My static method");
}
}
②、Lambda 表达式描述了一个代码块(或者叫匿名方法),可以将其作为参数传递给构造方法或者普通方法以便后续执行。
public class LamadaTest {
public static void main(String[] args) {
new Thread(() -> System.out.println("沉默王二")).start();
}
}
《Effective Java》的作者 Josh Bloch 建议使用 Lambda 表达式时,最好不要超过 3 行。否则代码可读性会变得很差。
③、Stream 是对 Java 集合框架的增强,它提供了一种高效且易于使用的数据处理方式。
List<String> list = new ArrayList<>();
list.add("中国加油");
list.add("世界加油");
list.add("世界加油");
long count = list.stream().distinct().count();
System.out.println(count);
④、Java 8 引入了一个全新的日期和时间 API,位于java.time
包中。这个新的 API 纠正了旧版java.util.Date
类中的许多缺陷。
LocalDate today = LocalDate.now();
System.out.println("Today's Local date : " + today);
LocalTime time = LocalTime.now();
System.out.println("Local time : " + time);
LocalDateTime now = LocalDateTime.now();
System.out.println("Current DateTime : " + now);
⑤、引入 Optional 是为了减少空指针异常。
Optional<String> optional = Optional.of("沉默王二");
optional.isPresent(); // true
optional.get(); // "沉默王二"
optional.orElse("沉默王三"); // "bam"
optional.ifPresent((s) -> System.out.println(s.charAt(0))); // "沉"
HTTP 全称是超文本传输协议(HyperText Transfer Protocol),是一个基于请求与响应模式的应用层协议,基于 TCP/IP 协议传输数据。
HTTP 遵循标准的客户端-服务器模型,客户端打开连接以发出请求,然后等待它收到服务器端响应。
三分恶面渣逆袭:HTTP 请求的过程和原理
客户端发送一个请求到服务器,服务器处理请求并返回一个响应。这个过程是同步的,也就是说,客户端在发送请求后必须等待服务器的响应。在等待响应的过程中,客户端不会发送其他请求。
进程说简单点就是我们在电脑上启动的一个个应用,比如我们启动一个浏览器,就会启动了一个浏览器进程。进程是操作系统资源分配的最小单位,它包括了程序、数据和进程控制块等。
线程说简单点就是我们在 Java 程序中启动的一个 main 线程,一个进程至少会有一个线程。当然了,我们也可以启动多个线程,比如说一个线程进行 IO 读写,一个线程进行加减乘除计算,这样就可以充分发挥多核 CPU 的优势,因为 IO 读写相对 CPU 计算来说慢得多。线程是 CPU 分配资源的基本单位。
三分恶面渣逆袭:进程与线程关系
一个进程中可以有多个线程,多个线程共用进程的堆和方法区(Java 虚拟机规范中的一个定义,JDK 8 以后的实现为元空间)资源,但是每个线程都会有自己的程序计数器和栈。
Java 中创建线程主要有三种方式,分别为继承 Thread 类、实现 Runnable 接口、实现 Callable 接口。
二哥的 Java 进阶之路
在 Java 中,线程共有 6 种状态:
状态 | 说明 |
---|---|
NEW | 当线程被创建后,如通过new Thread(),它处于新建状态。此时,线程已经被分配了必要的资源,但还没有开始执行。 |
RUNNABLE | 当调用线程的start()方法后,线程进入可运行状态。在这个状态下,线程可能正在运行也可能正在等待获取 CPU 时间片,具体取决于线程调度器的调度策略。 |
BLOCKED | 线程在试图获取一个锁以进入同步块/方法时,如果锁被其他线程持有,线程将进入阻塞状态,直到它获取到锁。 |
WAITING | 线程进入等待状态是因为调用了如下方法之一:Object.wait()或LockSupport.park()。在等待状态下,线程需要其他线程显式地唤醒,否则不会自动执行。 |
TIME_WAITING | 当线程调用带有超时参数的方法时,如Thread.sleep(long millis)、Object.wait(long timeout) 或LockSupport.parkNanos(),它将进入超时等待状态。线程在指定的等待时间过后会自动返回可运行状态。 |
TERMINATED | 当线程的run()方法执行完毕后,或者因为一个未捕获的异常终止了执行,线程进入终止状态。一旦线程终止,它的生命周期结束,不能再被重新启动。 |
线程在自身的生命周期中,并不是固定地处于某个状态,而是在不同的状态之间进行切换:
三分恶面渣逆袭:Java线程状态变化
如果等待队列满了,而且正在运行的线程数量大于或等于 maximumPoolSize,那么线程池会执行拒绝策略。
AOP,也就是 Aspect-oriented Programming,译为面向切面编程。
简单点说,就是把一些业务逻辑中的相同代码抽取到一个独立的模块中,让业务逻辑更加清爽。
三分恶面渣逆袭:横向抽取
举个例子,假如我们现在需要在业务代码开始前进行参数校验,在结束后打印日志,该怎么办呢?
我们可以把日志记录
和数据校验
这两个功能抽取出来,形成一个切面,然后在业务代码中引入这个切面,这样就可以实现业务逻辑和通用逻辑的分离。
三分恶面渣逆袭:AOP应用示例
业务代码不再关心这些通用逻辑,只需要关心自己的业务实现,这样就实现了业务逻辑和通用逻辑的分离。
所谓的IoC(控制反转,Inversion of Control),就是由容器来控制对象的生命周期和对象之间的关系。以前是我们想要什么就自己创建什么,现在是我们需要什么容器就帮我们送来什么。
引入IoC之前和引入IoC之后
也就是说,控制对象生命周期的不再是引用它的对象,而是容器,这就叫控制反转。
三分恶面渣逆袭:控制反转示意图
没有 IoC 之前:
我需要一个女朋友,刚好大街上突然看到了一个小姐姐,人很好看,于是我就自己主动上去搭讪,要她的微信号,找机会聊天关心她,然后约她出来吃饭,打听她的爱好,三观。。。
有了 IoC 之后:
我需要一个女朋友,于是我就去找婚介所,告诉婚介所,我需要一个长的像赵露思的,会打 Dota2 的,于是婚介所在它的人才库里开始找,找不到它就直接说没有,找到它就直接介绍给我。
婚介所就相当于一个 IoC 容器,我就是一个对象,我需要的女朋友就是另一个对象,我不用关心女朋友是怎么来的,我只需要告诉婚介所我需要什么样的女朋友,婚介所就帮我去找。
Spring 倡导的开发方式就是这样,所有的类创建都通过 Spring 容器来,不再是开发者去 new,去 = null 销毁,这些创建和销毁的工作都交给 Spring 容器来。
于是,对于某个对象来说,以前是它控制它依赖的对象,现在是所有对象都被 Spring 控制,这就是控制反转。
图片来源于网络
DI(依赖注入,Dependency Injection):有人说 IoC 和 DI 是一回事,有人说 IoC 是思想,DI 是 IoC 的实现。2004 年,Martin Fowler 在他的文章《控制反转容器&依赖注入模式》首次提出了依赖注入这个名词。
控制反转这个词太宽泛,并不能很好地解释这个框架的具体实现,于是就想到了一个新名词:依赖注入。
打个比方,你现在想吃韭菜馅的饺子,这时候就有人用针管往你吃的饺子里注入韭菜鸡蛋馅。就好像 A 类需要 B 类,以前是 A 类自己 new 一个 B 类,现在是有人把 B 类注入到 A 类里。
TCP 是面向连接的,而 UDP 是无连接的。
三分恶面渣逆袭:TCP 和 UDP 区别
可以这么形容:TCP 是打电话,UDP 是大喇叭(😂)。
三分恶面渣逆袭:TCP 和 UDP 比喻
在数据传输开始之前,TCP 需要先建立连接,数据传输完成后,再断开连接。这个过程通常被称为“三次握手”。
UDP 是无连接的,发送数据之前不需要建立连接,发送完毕也无需断开连接,数据以数据报形式发送。
在此基础上,我们可以得出:TCP 是可靠的,它通过确认机制、重发机制等来保证数据的可靠传输。而 UDP 是不可靠的,数据包可能会丢失、重复、乱序。
MySQL 是一个开源的关系型数据库管理系统,现在隶属于 Oracle 旗下。MySQL 也是我们国内使用频率最高的一种数据库,我在本地安装的是最新的 8.0社区版。
MySQL 官网
Redis 是 Remote Dictionary Service 三个单词中加粗字母的组合,是一种基于键值对(key-value)的 NoSQL 数据库。
三分恶面渣逆袭:Redis图标
但比一般的键值对,比如 HashMap 强大的多,Redis 中的 value 支持 string(字符串)、hash(哈希)、 list(列表)、set(集合)、zset(有序集合)、Bitmaps(位图)、 HyperLogLog(基数估算)、GEO(地理信息定位)等多种数据结构。
而且因为 Redis 的所有数据都存放在内存当中,所以它的读写性能非常出色。