Java8

Java弱引用

Java中存在四种引用,它们由强到弱依次是:强引用、软引用、弱引用、虚引用。下面我们简单介绍下除弱引用外的其他三种引用:

  • 强引用(Strong Reference):通常我们通过new来创建一个新对象时返回的引用就是一个强引用,若一个对象通过一系列强引用可到达,它就是强可达的(strongly reachable),那么它就不被回收
  • 弱引用(Weak Reference):弱引用的对象拥有更短暂的生命周期。在垃圾回收器线程扫描它所管辖的内存区域的过程中,一旦发现了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存
  • 软引用(Soft Reference):软引用和弱引用的区别在于,若一个对象是弱引用可达,无论当前内存是否充足它都会被回收,而软引用可达的对象在内存不充足时才会被回收,因此软引用要比弱引用“强”一些
  • 虚引用(Phantom Reference):虚引用是Java中最弱的引用,那么它弱到什么程度呢?它是如此脆弱以至于我们通过虚引用甚至无法获取到被引用的对象,虚引用存在的唯一作用就是当它指向的对象被回收后,虚引用本身会被加入到引用队列中,用作记录它指向的对象已被回收。

判断弱引用对象的关键在于只具有弱引用的对象,也就是说,如果一个对象有强引用,那么在系统GC时,是不会回收此对象的,也不会释放弱引用。

为什么使用弱引用

Java常通过使用弱引用来避免内存泄漏,例如在JDK中有一种内存变量ThreadLocal,通过ThreadLocal变量可以使共享的变量在不同的线程中有不同的副本,原理是在每一个Thread有一个threadLocalMap的属性,用来存放ThreadLocal对象,ThreadLocalMap中是通过一个Entry[]的散列表存放ThreadLocal变量以及ThreadLocal的value,而作为Entry的key的ThreadLocal就是使用的弱引用,结构如下:

static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;

Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}

Entry通过继承了WeakReference并通过get、set设置ThreadLocal为Entry的referent。

这里为什么要使用弱引用呢?

原因是如果不使用弱引用,那么当持有value的强引用释放掉后,当线程没有回收释放时,threadLocalMap会一直持有ThreadLocal以及value的强应用,导致value不能够被回收,从而造成内存泄漏。

通过使用弱引用,当ThreadLocal的强引用释放掉后,通过一次系统gc检查,发现ThreadLocal对象只有threadLocalMap中Entry的若引用持有,此时根据弱引用的机制就会回收ThreadLocal对象,从而避免了内存泄露。当然ThreadLocal还有一些额外的保护措施,详细分析可以参考:死磕Java源码之ThreadLocal实现分析

这里我们可以通过一个示例来验证一下:

WeakReferenceDemo.java

import java.lang.ref.WeakReference;

/**
* 弱引用回收测试
*/
public class WeakReferenceDemo {

public static WeakReference<String> weakReference1;
public static WeakReference<String> weakReference2;

public static void main(String[] args) {

test1();
//可以输出hello值,此时两个弱引用扔持有对象,而且未进行gc
System.out.println("未进行gc时,只有弱引用指向value内存区域:" + weakReference1.get());

//此时已无强一用执行"value"所在内存区域,gc时会回收弱引用
System.gc();

//此时输出都为nuill
System.out.println("进行gc时,只有弱引用指向value内存区域:" + weakReference1.get());

}

public static void test1() {
String hello = new String("value");

weakReference1 = new WeakReference<>(hello);

System.gc();
//此时gc不会回收弱引用,因为字符串"value"仍然被hello对象强引用
System.out.println("进行gc时,强引用与弱引用同时指向value内存区域:" + weakReference1.get());

}
}

输出:

进行gc时,强引用与弱引用同时指向value内存区域:value
未进行gc时,只有弱引用指向value内存区域:value
进行gc时,只有弱引用指向value内存区域:null

分析输出结果可以看出:

当有强引用指向value内存区域时,即使进行gc,弱引用也不会被释放,对象不回被回收。

当无强引用指向value内存区域是,此时进行gc,弱引用会被释放,对象将会执行回收流程。

引用队列

下面我们来简单地介绍下引用队列的概念。实际上,WeakReference类有两个构造函数:

//创建一个指向给定对象的弱引用``WeakReference(T referent) 
//创建一个指向给定对象并且登记到给定引用队列的弱引用``WeakReference(T referent, ReferenceQueue<? ``super` `T> q)

我们可以看到第二个构造方法中提供了一个ReferenceQueue类型的参数,通过提供这个参数,我们便把创建的弱引用对象注册到了一个引用队列上,这样当它被垃圾回收器清除时,就会把它送入这个引用队列中,我们便可以对这些被清除的弱引用对象进行统一管理。

ThreadLocal

使用场景

  • 1、在进行对象跨层传递的时候,使用ThreadLocal可以避免多次传递,打破层次间的约束。
  • 2、线程间数据隔离
  • 3、进行事务操作,用于存储线程事务信息。
  • 4、数据库连接,Session会话管理。

ThreadLocal怎么用?

下面让我们来看一个例子:

public class ThreadLocalTest02 {

public static void main(String[] args) {
ThreadLocal<String> local = new ThreadLocal<>();

IntStream.range(0, 10).forEach(i -> new Thread(() -> {
local.set(Thread.currentThread().getName() + ":" + i);
System.out.println("线程:" + Thread.currentThread().getName() + ",local:" + local.get());
}).start());
}
}

输出结果:
线程:Thread-0,local:Thread-0:0
线程:Thread-1,local:Thread-1:1
线程:Thread-2,local:Thread-2:2
线程:Thread-3,local:Thread-3:3
线程:Thread-4,local:Thread-4:4
线程:Thread-5,local:Thread-5:5
线程:Thread-6,local:Thread-6:6
线程:Thread-7,local:Thread-7:7
线程:Thread-8,local:Thread-8:8
线程:Thread-9,local:Thread-9:9

从结果可以看到,每一个线程都有自己的local 值,这就是TheadLocal的基本使用 。

下面我们从源码的角度来分析一下,ThreadLocal的工作原理。

ThreadLocal源码分析

1、set 方法

public void set(T value) {
//首先获取当前线程对象
Thread t = Thread.currentThread();

//获取线程中变量 ThreadLocal.ThreadLocalMap
ThreadLocalMap map = getMap(t);

//如果不为空,
if (map != null)
map.set(this, value);
else
//如果为空,初始化该线程对象的map变量,其中key 为当前的threadlocal 变量
createMap(t, value);
}

// 初始化线程内部变量 threadLocals ,key 为当前 threadlocal
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}

ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
table = new Entry[INITIAL_CAPACITY];
int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
table[i] = new Entry(firstKey, firstValue);
size = 1;
setThreshold(INITIAL_CAPACITY);
}

static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;

Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}

汇总下,ThreadLocalMapThreadLocal 的一个静态内部类,里面定义了Entry 来保存数据。而且是继承的弱引用。在Entry内部使用ThreadLocal作为key,使用我们设置的value作为value

对于每个线程内部有个ThreadLocal.ThreadLocalMap 变量,存取值的时候,也是从这个容器中来获取。

2、get方法

public T get() {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
return setInitialValue();
}

通过上面的分析,相信你对该方法已经有所理解了,首先获取当前线程,然后通过key threadlocal 获取 设置的value

ThreadLocal 内存泄漏问题

我们首先来看下,下面这个类:

/**
* The entries in this hash map extend WeakReference, using
* its main ref field as the key (which is always a
* ThreadLocal object). Note that null keys (i.e. entry.get()
* == null) mean that the key is no longer referenced, so the
* entry can be expunged from table. Such entries are referred to
* as "stale entries" in the code that follows.
*/
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;

Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}

注释说的很清楚了,Note that null keys (i.e. entry.get()* == null)

如果 key threadlocalnull 了,这个 entry 就可以清除了。

ThreadLocal是一个弱引用,当为null时,会被当成垃圾回收 。

img

重点来了,突然我们ThreadLocal是null了,也就是要被垃圾回收器回收了,但是此时我们的ThreadLocalMap(thread 的内部属性)生命周期和Thread的一样,它不会回收,这时候就出现了一个现象。那就是ThreadLocalMap的key没了,但是value还在,这就造成了内存泄漏。

解决办法:使用完ThreadLocal后,执行remove操作,避免出现内存溢出情况。

所以 如同 lock 的操作 最后要执行解锁操作一样,ThreadLocal使用完毕一定记得执行remove 方法,清除当前线程的数值。

如果不remove 当前线程对应的VALUE ,就会一直存在这个值。

使用了线程池,可以达到“线程复用”的效果。但是归还线程之前记得清除ThreadLocalMap,要不然再取出该线程的时候,ThreadLocal变量还会存在。这就不仅仅是内存泄露的问题了,整个业务逻辑都可能会出错。

为什么key使用弱引用?

如果使用强引用,当ThreadLocal 对象的引用(强引用)被回收了,ThreadLocalMap本身依然还持有ThreadLocal的强引用,如果没有手动删除这个key ,则ThreadLocal不会被回收,所以只要当前线程不消亡,ThreadLocalMap引用的那些对象就不会被回收, 可以认为这导致Entry内存泄漏。

附:强引用-软引用-弱引用

  • 强引用:普通的引用,强引用指向的对象不会被回收;
  • 软引用:仅有软引用指向的对象,只有发生gc且内存不足,才会被回收;
  • 弱引用:仅有弱引用指向的对象,只要发生gc就会被回收。

Lambda(行为参数化)

Lambda 表达式中引用的局部变量必须是 final 或既成事实上的 final 变量。

Lambda 表达式的不同形式

Runnable noArguments = () -> System.out.println("Hello World"); 

ActionListener oneArgument = event -> System.out.println("button clicked");

Runnable multiStatement = () -> {
System.out.print("Hello");
System.out.println(" World");
};

BinaryOperator<Long> add = (x, y) -> x + y;

BinaryOperator<Long> addExplicit = (Long x, Long y) -> x + y;

函数接口

函数接口是只有一个抽象方法的接口,用作 Lambda 表达式的类型。

@FunctionalInterface函数式接口注解

// ActionListener 只有一个抽象方法:actionPerformed,被用来表示行为:接受一个参数, 返回空。记住,由于 actionPerformed 定义在一个接口里,因此 abstract 关键字不是必需 的。该接口也继承自一个不具有任何方法的父接口:EventListener。
public interface ActionListener extends EventListener {
public void actionPerformed(ActionEvent event);
}

Java中重要的函数接口

image-20221109235606337

Predicate 接受一个对象,返回一个布尔值

public interface Predicate<T> { 
boolean test(T t);
}
image-20221110121723647

BinaryOperator 接受两个参数,返回一个值

BinaryOperator 是一个具有泛型参数的函数 接口,该类型既是参数 x 和 y 的类型,也是返回值的类型。

BinaryOperator<Long> addLongs = (x, y) -> x + y;

类型推断

Stream流

总览图

stream流

流的创建

// 1、通过Collection系列提供的stream()(串行) 或parallelStream()(并行)获取
List<String> list = new ArrayList<>();
// 串行流
list.stream();
// 并行流
list.parallelStream();

// 2、通过Arrays中的静态方法stream() 获取数据流
String[] u = new String[2];
Arrays.stream(u);

// 3、通过Stream;类中的静态方法of()
Stream.of("11", "2");

peek

需求是将Map<String,Object>改为Map<String,Object.字段>

Map<String,Object> map = new HashMap<>();
map.put("a",new Student(1,"张三"));
map.put("b",new Student(2,"李四"));
map.put("c",new Student(3,"王五"));
map.put("d",new Student(4,"赵六"));

// 需求是将Map<String,Object>改为Map<String,Object.字段>
Map<String, Object> collect = map.entrySet()
.stream()
// peek支持在每个元素上执行一个操作并且返回新的stream,我们就利用这个方法转换数据
.peek(obj -> obj.setValue(((Student) obj.getValue()).getName()))
// collect方法用来将流转到集合对象
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
//遍历输出
collect.forEach((key,value)->System.out.println(key+":"+value));

concat合并流

// 合并流 数组流和集合流也可以合并
static <T> Stream<T> concat(Stream<? extends T> a, Stream<? extends T> b);

字符集合,合并成字符串

strings.stream().collect(Collectors.joining(", "));

filter

Stream<T> filter(Predicate<? super T> predicate); // 过滤元素

list.stream()
.filter(s -> s.startsWith("张"))
.filter( s -> s.length()== 3 )
.forEach(System.out::println);

// filter 筛选出原Id集合中包含130的id元素
List<String> newId = oldId.stream().filter(item -> item.contains("130"));

mapToInt

// 返回一个IntStream ,其中包含将给定函数应用于此流的元素的结果。 
IntStream mapToInt(ToIntFunction<? super T> mapper);

IntSummaryStatistics stats = integers.stream().mapToInt((x) ->x).summaryStatistics();
stats.getMax();// 列表中最大的数
stats.getMin(); // 列表中最小的数
stats.getSum();// 求和
stats.getAverage(); // 平均数

parallelStream

count = strings.parallelStream().filter(string -> string.isEmpty()).count();
System.out.println("空字符串的数量为: " + count);

sorted

// 返回由此流的元素组成的流,根据自然顺序排序。 
// 如果该流的元件不是Comparable ,一个java.lang.ClassCastException执行终端操作时,
// 可以抛出。对于有序流,排序稳定。 对于无序的流,不能保证稳定性。
Stream<T> sorted();
// 返回由该流的元素组成的流,根据提供的Comparator进行排序。
// 对于有序流,排序稳定。 对于无序的流,不能保证稳定性。
Stream<T> sorted(Comparator<? super T> comparator);

map

// 加工方法(把原来的元素加工以后,重新放上去)
<R> Stream<R> map(Function<? super T,? extends R> mapper);

Stream<String> nameStream = persons.stream().map(Person::getName);

List转Map

List<String> strList = Arrays.asList("a", "ba", "bb", "abc", "cbb", "bba", "cab");
Map<Integer, String> strMap = strList.stream()
.collect( Collectors.toMap( str -> strList.indexOf(str), str -> str ) );

limit

Stream<T> limit(long maxSize); // 取前几个元素

// limit 筛选出前10条数据
List<String> newId = oldId.stream().limit(10);

skip跳过前几个

Stream<T> skip(long n); 

// skip 跳过前10个元素,此时就会从第11个元素开始操作
List<String> newId = oldId.stream().skip(10);

distinct去重

Stream<T> distinct();

List<String> newId = oldId.stream().distinct();

其他API

long count(); // 统计个数
forEach(); // 逐一处理(遍历)

多级分组

List<Student> testList = new ArrayList<Student>();
testList.add(new Student("张一", 1, 13, "3"));
testList.add(new Student("张二", 2, 13, "4"));
testList.add(new Student("张三", 3, 14, "4"));
testList.add(new Student("老王", 4, 14, "2"));
testList.add(new Student("张四", 1, 15, "3"));
testList.add(new Student("张五", 2, 16, "1"));
testList.add(new Student("张六", 3, 17, "3"));
testList.add(new Student("张七", 3, 18, "5"));
testList.add(new Student("老王", 1, 15, "1"));
testList.add(new Student("张八", 5, 15, "3"));
testList.add(new Student("张九", 2, 15, "2"));
testList.add(new Student("老王", 4, 13, "3"));

// 普通分组
Map<Integer, List<Student>> testMap = testList.stream().collect(Collectors.groupingBy(s -> s.getStuId()));

// 多级分组
Map<Integer, Map<Integer, Map<String, List<Student>>>> stuIdMapMap = testList.stream()
.collect(Collectors.groupingBy(Student::getStuId,
Collectors.groupingBy(Student::getStuAge,
Collectors.groupingBy(Student::getClassNum))));

// 分组之后进行业务处理
stuIdMapMap.forEach((stuId, stuAgeMapMap) -> {
// 业务处理
stuAgeMapMap.forEach((stuAge, classNumMapMap) -> {
// 业务处理
classNumMapMap.forEach((classNum, studentList) -> {
Optional.ofNullable(studentList).orElse(Collections.emptyList()).forEach(student -> {
// 业务处理
});
});
});
});

Optional

类详解

public final class Optional<T> {
// 空的Optional对象
private static final Optional<?> EMPTY = new Optional<>();

// 如果非空,就是value值,如果为空,就没有值
private final T value;

// 无参构造,将value初始化为null
private Optional() {
this.value = null;
}

// 获取一个Optional对象,value为null
public static<T> Optional<T> empty() {
@SuppressWarnings("unchecked")
Optional<T> t = (Optional<T>) EMPTY;
return t;
}

// 使用构造器生成一个Optional对象,如果value值为null,就抛出空指针异常
private Optional(T value) {
this.value = Objects.requireNonNull(value);
}

// 使用静态方法获取一个Optional对象,如果value值为null,就抛出空指针异常
public static <T> Optional<T> of(T value) {
return new Optional<>(value);
}

// 如果value为null,返回value为null的Optional对象,否则生成一个value为传入value的对象
public static <T> Optional<T> ofNullable(T value) {
return value == null ? empty() : of(value);
}

// 获取当前Optional对象的value值,如果value为null,则抛出异常
public T get() {
if (value == null) {
throw new NoSuchElementException("No value present");
}
return value;
}

// 判断当前Optional对象value值是否非空,不为null则返回true
public boolean isPresent() {
return value != null;
}

// 对当前Optional对象的value值进行操作
public void ifPresent(Consumer<? super T> consumer) {
if (value != null)
// 使用consumer消费者方法
consumer.accept(value);
// 否则不做任何操作
}

// 根据条件过滤Optional对象
public Optional<T> filter(Predicate<? super T> predicate) {
// 如果predicate比较方法为null,则抛出空指针异常
Objects.requireNonNull(predicate);
// 如果value值为空,则返回当前Optional对象
if (!isPresent())
return this;
else
// 如果value有值
// 则使用predicate进行比较,如果匹配则返回当前Optional对象
// 不匹配则返回一个value为null的Optional对象
return predicate.test(value) ? this : empty();
}

// 将当前Optional对象的value根据mapper函数封装成另外的Optional对象
// mapper函数入参的类型为value类型的超类型,返回类型为U类型的子类型
public<U> Optional<U> map(Function<? super T, ? extends U> mapper) {
// 如果mapper函数为null,则抛出空指针异常
Objects.requireNonNull(mapper);
// 如果value值为空,则返回value为null的Optional对象
if (!isPresent())
return empty();
else {
// 如果value有值
// 将mapper.apply(value)的返回值等装成一个Optional对象
return Optional.ofNullable(mapper.apply(value));
}
}

// 将当前Optional对象的value根据mapper函数封装成另外的Optional对象
// mapper函数入参的类型为value类型的超类型,返回类型为value为U类型的Optional对象
public<U> Optional<U> flatMap(Function<? super T, Optional<U>> mapper) {
// 如果mapper函数为null,则抛出空指针异常
Objects.requireNonNull(mapper);
// 如果value值为空,则返回value为null的Optional对象
if (!isPresent())
return empty();
else {
// 如果value有值
// 如果mapper.apply(value)为null,则抛出空指针异常
// 返回mapper.apply(value)
return Objects.requireNonNull(mapper.apply(value));
}
}

// 如果value不为空则返回value,否则返回other
public T orElse(T other) {
return value != null ? value : other;
}

// 如果value不为空则返回value,否则返回other.get()
public T orElseGet(Supplier<? extends T> other) {
return value != null ? value : other.get();
}

// 如果value不为空则返回value,否则抛出exceptionSupplier.get()定义的异常
public <X extends Throwable> T orElseThrow(Supplier<? extends X> exceptionSupplier) throws X {
if (value != null) {
return value;
} else {
throw exceptionSupplier.get();
}
}

// 比较当前Optional对象与obj是否相等
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}

if (!(obj instanceof Optional)) {
return false;
}

Optional<?> other = (Optional<?>) obj;
return Objects.equals(value, other.value);
}

// 获取当前value的hashcode
@Override
public int hashCode() {
return Objects.hashCode(value);
}

// toString
@Override
public String toString() {
return value != null
? String.format("Optional[%s]", value)
: "Optional.empty";
}
}

ifPresent、ifPresentOrElse

// 判断对象是否有值
boolean present = Optional.ofNullable(user).isPresent();

// 存在即执行
Optional.ofNullable(testList).ifPresent(System.out::println);

// 存在时,消费value,为null时,消费空Runnable emptyAction
Optional.ofNullable(testList).ifPresentOrElse((studentList) -> {
System.out.println("Value is present, its: " + studentList);
}, () -> {
System.out.println("Value is empty");
});

orElse

Optional.ofNullable(testList).orElse(Collections.emptyList());

https://blog.csdn.net/suyujiezhang/article/details/81215386
// orElse(T)无论前面Optional容器是null还是non-null,都会执行orElse里的方法,orElseGet(Supplier)并不会,
// 如果service无异常抛出的情况下,Optional使用orElse或者orElseGet的返回结果都是一样的
return Optional.ofNullable(service.A()).orElse(service.B());
// 修改为:
return Optional.ofNullable(service.A()).orElseGet(() -> service.B())

// 结论:Optional的orElse(T)若方法不是纯计算型的,有与数据库交互或者远程调用的,都应该使用orElseGet()

map

Optional.ofNullable(user).map(User::getAge).orElse(0);

方法引用

1.静态方法的引用。(类名::静态方法)

Collections.sort(lists, ( o1, o2) -> Student.compareByAge(o1 , o2));
// 如果前后参数是一样的,而且方法是静态方法,既可以使用静态方法引用
Collections.sort(lists, Student::compareByAge);

2.实例方法的引用。(对象::实例方法)

// 对象是 System.out = new PrintStream();
// 实例方法:println()
// 前后参数正好都是一个
lists.forEach(s -> System.out.println(s));
lists.forEach(System.out::println);

3.特定类型方法的引用。(特定类型::方法。)

// 如果第一个参数列表中的形参中的第一个参数作为了后面的方法的调用者,
// 并且其余参数作为后面方法的形参,那么就可以用特定类型方法引用了。
Arrays.sort(strs, ( s1, s2 ) -> s1.compareToIgnoreCase(s2));
// 特定类型的方法引用:
Arrays.sort(strs, String::compareToIgnoreCase);

4.构造器引用(类名::new)

// 注意点:前后参数一致的情况下,又在创建对象就可以使用构造器引用
List<String> lists = new ArrayList<>();
lists.add("java1");
lists.add("java2");
lists.add("java3");
// 集合默认只能转成Object类型的数组。
Object[] objs = lists.toArray();
System.out.println("Object类型的数组:"+ Arrays.toString(objs));
// 我们想指定转换成字符串类型的数组!!
// 最新的写法可以结合构造器引用实现 。
// default <T> T[] toArray(IntFunction<T[]> generator)
String[] strs = lists.toArray(new IntFunction<String[]>() {
@Override
public String[] apply(int value) {
return new String[value];
}
});
String[] strs1 = lists.toArray(s -> new String[s] );
String[] strs2 = lists.toArray(String[]::new);
System.out.println("String类型的数组:"+ Arrays.toString(strs2));