读读这段Lambda表达式代码,测试下你对JAVA传参方式的理解程度

简介: 来了来了,最近有点忙,有一段时间没发推文啦,真是自惭形秽。接下来会提升推文频率的……

来了来了,最近有点忙,有一段时间没发推文啦,真是自惭形秽。接下来会提升推文频率的……


直接进入正题,首先我们有一个Uer对象如下:


static class Student {    private String name;    private int score;    private int star;    Student(String name, int score, int star){        this.name = name;        this.score = score;        this.star = star;    }    //省略get set方法}


然后我们假设一个场景:对成绩大于等于60分的同学都加一颗星,并输出这些学生信息。


来看看代码实现:


public static void main(String[] args) {    List<Student> students = new ArrayList<>();    for (int i=0; i<10; i++){        Student student = new Student("student" + i, 55+i, 0);        students.add(student);    }    //过滤用户    filterUser(students);    students.forEach(student -> System.out.println(student.getName() + "|" + student.getScore() + "|" + student.getStar()));}
private static void filterUser(List<Student> students){    students = students.stream().filter(student -> student.getScore() >= 60).collect(Collectors.toList());    students.forEach(student -> student.setStar(student.getStar()+1));}


大家可以先想想输出的结果是什么。然后再看看下面的输出和你想的是不是一样:


student0|55|0student1|56|0student2|57|0student3|58|0student4|59|0student5|60|1student6|61|1student7|62|1student8|63|1student9|64|1


从上面的输出结果我们可以看到,按照我们正常的想法,应该是输出student5~9的学生,为什么把不及格的学生也输出出来了呢?难道过滤条件没有起到作用,可是,student5~9的学生的星星确实又都加了1了啊,这么说过滤应该是有效果的才对。


好吧,先卖个关子,强哥先问大家一道面试题:


问:以下对Java的方法传参方式哪个选项的说法是正确的:


  1. Java传参方式是引用传递。
  2. Java传参方式是值传递。
  3. 值传递和引用传递,区分的条件是传递的内容,如果是个值,就是值传递。如果是个引用,就是引用传递。
  4. 传递的参数如果是基本类型,那就是值传递,如果是对象,那就是引用传递。


答案是:(9+7-10)/3。(为了不让大家直接看出答案,大家计算完上面的算式就能得到结果)。


好吧,没错,Java的传参方式只有一种,那就是值传递。我相信很多人的答案会是4,对于基本类型,Java和很多语言一样用值传递这个很好理解,可是如果是对象类型,难道不是引用传递吗?这里我们就要先梳理下值传递和引用传递的区别了:


  • 值传递(pass by value)是指在调用函数时将实际参数复制一份传递到函数中,这样在函数中如果对参数进行修改,将不会影响到实际参数。
  • 引用传递(pass by reference)是指在调用函数时将实际参数的地址直接传递到函数中,那么在函数中对参数所进行的修改,将影响到实际参数。


也就是说,并不是我们给方法传递的是对象的引用,就是引用传递,而是要看传递给函数的是什么东西,是实际参数复制的一份值,还是实际参数对应的物理地址。


这么说可能不好理解,针对对象传递我们来举个形象的例子:


你有一把钥匙(对象引用在堆栈的地址),当你的朋友想要去你家(堆中具体的对象)的时候,如果你直接把你的钥匙给他了,这就是引用传递(也就相当于把对象引用对应的地址给了方法参数)。这种情况下,如果他对这把钥匙做了什么事情,比如他把钥匙弄断了(修改了对象引用的地址),那么这把钥匙还给你的时候,你自己的钥匙就是断了的这把。


而假如你有一把钥匙(对象引用在堆栈的地址),当你的朋友想要去你家的时候,你复刻了一把新钥匙(新的一个对象引用,与原对象引用在堆栈中的地址值不同,但该地址中存的值(指向对象的地址)是一样的)给他,自己的还在自己手里,这就是值传递。这种情况下,他对这把钥匙做什么都不会影响你手里的这把钥匙。


而Java中,只存在值传递而不存在引用传递!而Java中,只存在值传递而不存在引用传递!而Java中,只存在值传递而不存在引用传递!


重要的事情说三遍。那既然是这样,为什么上面我们在filterUser中修改了students列表对象值,在输出的时候却还是输出了所有的内容呢?这里再给大家看看下面一个修改后的filterUser方法:


private static void filterUser(List<Student> students){    students.removeIf(user -> user.getScore() < 60);    students.forEach(student -> student.setStar(student.getStar()+1));}


输出结果如下:


student5|60|1student6|61|1student7|62|1student8|63|1student9|64|1


啊,这回终于是我们想要的结果了。额,比较下和上一个filterUser方法的区别,在这两行代码:


students = students.stream().filter(student -> student.getScore() >= 60).collect(Collectors.toList());


students.removeIf(user -> user.getScore() < 60);


啊,再细想一下,难不成,哈哈,对的,我们对students进行了赋值操作,而赋给他的值如果是原对象倒是没有问题,但是如果你把students赋值了一个新new出来的对象,那么你再对filterUser方法中的students对象进行操作就无法影响到原来的main方法中的students了。


就相当于你给了朋友的这把复刻的钥匙,朋友是个高手,把他的钥匙修了一下之后用来开另一人的房间去了,这样就开不了你的房间了。而我们第一个方法中的Lamda表达式中的Collectors.toList()这句就是new出来了一个新的列表(即搞出来了一个新的房间):


/** * Returns a {@code Collector} that accumulates the input elements into a * new {@code List}. There are no guarantees on the type, mutability, * serializability, or thread-safety of the {@code List} returned; if more * control over the returned {@code List} is required, use {@link #toCollection(Supplier)}. * * @param <T> the type of the input elements * @return a {@code Collector} which collects all the input elements into a * {@code List}, in encounter order */public static <T>Collector<T, ?, List<T>> toList() {    return new CollectorImpl<>((Supplier<List<T>>) ArrayList::new, List::add,                               (left, right) -> { left.addAll(right); return left; },                               CH_ID);}


嗯,这样我么就知道了为什么最后输出还是会把student0~9都输出出来了。可是问题又来了,上面说的是给new了一个新的对象,那为什么student0~4的start是0,而student5~9的却变成了1了呢,这不是还是改变了对象中的值了吗,不是说钥匙现在只能开另一个房间了吗,怎么把原来房间的东西给变了。


嗯,其实这里还有一点原因就是students对应的是个列表,而列表里存的又是Student对象。我们打个比方就是,第一个房间里其实还有10把钥匙,用来开10个学生的房间,而第二个房间因为是拿第一个房间过滤改造出来的(这里可能是另一维度的世界了吧,哈哈),第二个房间只有5~9号房间的钥匙,用来开这5个学生的房间。而你朋友现在拿着这5把钥匙,开了对应学生的房间,分别放进去了一个小星星。那么,当你拿着这10把钥匙开这10个学生的房间时,自然会发现有5个学生的房间多了一颗小星星啦。


所以,为什么第一个filterUser方法输出的结果包含10个学生,且student0~4的start是0,而student5~9的却变成了1也就清楚啦。


我们只要记住,Java中的传参方式只有一种就是值传递就行了。更有甚者,其实我们只要弄清了Java中对象及对象引用的存储方式,至于它是值传递还是引用传递就没有这么重要了。不过,做面试题的时候还是挺重要的。


说了这么多,既然Java只有值传递,那么引用传递能不能给个例子嘞,那就亮一下支持引用传递的语言吧,这里拿C++举例:


#include <stdio.h>void swap1(int* a,int* b);void swap2(int& a,int& b);void swap3(int* a,int* b);
void main(){    printf("Hello World!\n");    int a = 3;    int b = 4;    printf("bef swap, add of a = %d\n",&a);    printf("aft swap, val of a = %d\n",a);    //swap(a,b);    //swap1(&a,&b);    swap2(a,b);    //swap3(&a,&b);        printf("aft swap, add of a = %d\n",&a);    printf("aft swap, val of a = %d\n",a);}// pass by valuevoid swap(int a,int b){    int temp = a;    a = b;    b = temp;}// pass by addressvoid swap1(int* a,int *b){    int temp = *a;    *a = *b;    *b = temp;}// pass by referencevoid swap2(int& a,int& b){    int temp = a;    a = b;    b = temp;}// pass by value ?void swap3(int* a,int *b){    int* temp = a;    a = b;    b = temp;}


调用不同方法输出结果:


26.jpg


swap2就是引用传递啦~。结合其他几个方法好好理解下吧。

相关文章
|
1月前
|
Java API 开发者
Java中的Lambda表达式与Stream API的协同作用
在本文中,我们将探讨Java 8引入的Lambda表达式和Stream API如何改变我们处理集合和数组的方式。Lambda表达式提供了一种简洁的方法来表达代码块,而Stream API则允许我们对数据流进行高级操作,如过滤、映射和归约。通过结合使用这两种技术,我们可以以声明式的方式编写更简洁、更易于理解和维护的代码。本文将介绍Lambda表达式和Stream API的基本概念,并通过示例展示它们在实际项目中的应用。
|
2月前
|
Java API 开发者
Java中的Lambda表达式:简洁代码的利器####
本文探讨了Java中Lambda表达式的概念、用途及其在简化代码和提高开发效率方面的显著作用。通过具体实例,展示了Lambda表达式如何在Java 8及更高版本中替代传统的匿名内部类,使代码更加简洁易读。文章还简要介绍了Lambda表达式的语法和常见用法,帮助开发者更好地理解和应用这一强大的工具。 ####
|
1月前
|
安全 Java API
Java中的Lambda表达式:简化代码的现代魔法
在Java 8的发布中,Lambda表达式的引入无疑是一场编程范式的革命。它不仅让代码变得更加简洁,还使得函数式编程在Java中成为可能。本文将深入探讨Lambda表达式如何改变我们编写和维护Java代码的方式,以及它是如何提升我们编码效率的。
|
2月前
|
安全 Java API
Java中的Lambda表达式与Stream API的高效结合####
探索Java编程中Lambda表达式与Stream API如何携手并进,提升数据处理效率,实现代码简洁性与功能性的双重飞跃。 ####
33 0
|
4天前
|
监控 Java
java异步判断线程池所有任务是否执行完
通过上述步骤,您可以在Java中实现异步判断线程池所有任务是否执行完毕。这种方法使用了 `CompletionService`来监控任务的完成情况,并通过一个独立线程异步检查所有任务的执行状态。这种设计不仅简洁高效,还能确保在大量任务处理时程序的稳定性和可维护性。希望本文能为您的开发工作提供实用的指导和帮助。
42 17
|
15天前
|
Java
Java—多线程实现生产消费者
本文介绍了多线程实现生产消费者模式的三个版本。Version1包含四个类:`Producer`(生产者)、`Consumer`(消费者)、`Resource`(公共资源)和`TestMain`(测试类)。通过`synchronized`和`wait/notify`机制控制线程同步,但存在多个生产者或消费者时可能出现多次生产和消费的问题。 Version2将`if`改为`while`,解决了多次生产和消费的问题,但仍可能因`notify()`随机唤醒线程而导致死锁。因此,引入了`notifyAll()`来唤醒所有等待线程,但这会带来性能问题。
Java—多线程实现生产消费者
|
17天前
|
安全 Java Kotlin
Java多线程——synchronized、volatile 保障可见性
Java多线程中,`synchronized` 和 `volatile` 关键字用于保障可见性。`synchronized` 保证原子性、可见性和有序性,通过锁机制确保线程安全;`volatile` 仅保证可见性和有序性,不保证原子性。代码示例展示了如何使用 `synchronized` 和 `volatile` 解决主线程无法感知子线程修改共享变量的问题。总结:`volatile` 确保不同线程对共享变量操作的可见性,使一个线程修改后,其他线程能立即看到最新值。
|
17天前
|
消息中间件 缓存 安全
Java多线程是什么
Java多线程简介:本文介绍了Java中常见的线程池类型,包括`newCachedThreadPool`(适用于短期异步任务)、`newFixedThreadPool`(适用于固定数量的长期任务)、`newScheduledThreadPool`(支持定时和周期性任务)以及`newSingleThreadExecutor`(保证任务顺序执行)。同时,文章还讲解了Java中的锁机制,如`synchronized`关键字、CAS操作及其实现方式,并详细描述了可重入锁`ReentrantLock`和读写锁`ReadWriteLock`的工作原理与应用场景。
|
17天前
|
安全 Java 编译器
深入理解Java中synchronized三种使用方式:助您写出线程安全的代码
`synchronized` 是 Java 中的关键字,用于实现线程同步,确保多个线程互斥访问共享资源。它通过内置的监视器锁机制,防止多个线程同时执行被 `synchronized` 修饰的方法或代码块。`synchronized` 可以修饰非静态方法、静态方法和代码块,分别锁定实例对象、类对象或指定的对象。其底层原理基于 JVM 的指令和对象的监视器,JDK 1.6 后引入了偏向锁、轻量级锁等优化措施,提高了性能。
42 3
|
17天前
|
存储 安全 Java
Java多线程编程秘籍:各种方案一网打尽,不要错过!
Java 中实现多线程的方式主要有四种:继承 Thread 类、实现 Runnable 接口、实现 Callable 接口和使用线程池。每种方式各有优缺点,适用于不同的场景。继承 Thread 类最简单,实现 Runnable 接口更灵活,Callable 接口支持返回结果,线程池则便于管理和复用线程。实际应用中可根据需求选择合适的方式。此外,还介绍了多线程相关的常见面试问题及答案,涵盖线程概念、线程安全、线程池等知识点。
101 2
下一篇
开通oss服务