当前位置 : 主页 > 编程语言 > java >

java方法的传参与变量的数据类型的关系

来源:互联网 收集:自由互联 发布时间:2023-03-22
写在前面 你可能只需要看写在前面 这篇文章的内容就是讲清楚以下三句话,如果这三句话都理解透的同学,可绕过。 Java的传参方式只有传值。Java程序设计语言总是采用按值调用(cal

写在前面 你可能只需要看写在前面

这篇文章的内容就是讲清楚以下三句话,如果这三句话都理解透的同学,可绕过。

  • Java的传参方式只有传值。Java程序设计语言总是采用按值调用(call by value)。也就是说,方法得到的是所有参数值的一个副本。具体来讲,方法不能修改传递给它的任何参数变量的内容。(Java核心技术卷I)
  • Java的基本类型变量里保存的是数据本身的值
  • Java的引用类型变量里保存了其引用的数据(可以是类类型、接口或数组等一切非基本类型数据)的地址。

形参复制了一份实参的值。不管参数是基本类型或引用类型,都是将实参变量的值复制一份给形参变量。注意:是复制变量的值。所以,基本类型形参复制的是其数据本身的值,引用类型的形参复制的是被引用数据的地址。

下面还有两问,如果觉得自己上面三句话都理解透了,但下面这两问又把自己搞蒙了的同学,对不起,你没透。

  • 某方法的形参为数组,并在方法中修改了这个数组其中一个元素的值,且此方法没有返回值。为什么方法调用结束后,实参所引用数组的这个元素的值也改了?
  • 某方法的一个形参为某个对象,并在方法中修改了这个对象的属性值,且此方法没有返回值。为什么方法调用结束后,实参所引用对象的属性值也变了?

有三有二,那再来一个一

  • 下面代码两次打印的值分别是什么?
public static void main(String[] args) { String ss="我在main函数中被赋值了"; stringTest(ss); System.out.println("这是编号1打印ss="+ss); } public static void stringTest(String ss){ System.out.println("这是编号2打印ss="+ss); ss="我在stringTest方法中被改写了"; }

如果以上三句话能理解,两个问题能回答,一段代码没疑惑,那么同学,请出列,后面没你啥事了。注:有同学问,为什么不把答案写在这里?那个,如果对自己的答案不敢100%确定的,咱们还能再处处?

一、 问题引入

今天有一个不是太小朋友的小朋友问了我一个关于传参的问题。demo的代码如下:注:如果下面的代码有小朋友看不懂,不要慌,后续会一层一层的慢慢解释让你看懂。

public static void main(String[] args) { List<String> list = new ArrayList<>(); list.add("1"); list.add("2"); list.add("3"); list.add("4"); testList(list); System.out.println("list.size:"+ list.size()); } public static void testList(List<String> list) { list = list.stream().filter(x -> { if(x.equals("3")) return false; return true; }).collect(Collectors.toList()); System.out.println("test.list size:"+list.size()); }

运行结果是这样子的:

test.list size:3 list.size:4

他问,为什么最后打印的结果,main方法中list集合里还是4个元素,不应该是3个元素吗?关于这个问题,我们先讨论一下java的传参

二、 java的数据类型

大家都知道,java的数据类型分两种:基本数据类型和引用数据类型。除了那8种基本数据类型,其它的数据类型都是引用类型。基本数据类型变量声明并赋值后,其值直接保存在变量中。而所有的声明为引用类型的变量里保存的值,其实都是它所引用数据的地址。重要的事情我加粗,就不说三遍了

三、 Java基本类型

3.1基本数据类型的变量声明及赋值

int a; a=5;

上面两行代码的解析:

  • 首先声明了int类型的变量a,则JVM给变量a开辟了一个内存空间。

java方法的传参与变量的数据类型的关系_java

  • 其次给a赋初值,直接将字面值5写入变量a的内存空间中。

java方法的传参与变量的数据类型的关系_java传参_02

3.2 基本数据类型传参

  • 所有的形参都是实参的复制。
  • 形参的值的改变,不会影响到实参示例代码如下:

public static void main(String[] args) { //声明变量a,并赋初值 10 int a=10; //调用方法 addIntSelf,a做为实参传入 addIntSelf(a); //打印变量a的值 System.out.println("main a="+a); } public static void addIntSelf(int x){ // x自加 =>x=x+x x+=x; //打印变量x的值 System.out.println("addIntSelf x="+x); }

代码解析:片断一 :在main方法中的前两句代码解析

//声明变量a,并赋初值 10 int a=10; //JVM会在栈中为a开辟内存空间,并存入10 //调用方法 addIntSelf,a做为实参传入 addIntSelf(a); // jvm在栈中为addIntSelf的形参x开辟内存空间, //并存入实参a 的值。此时实参a与形参x分别占据两个不同的空间地址

java方法的传参与变量的数据类型的关系_java_03

由上图可见,此时栈中有两个元素,分别是实参a和形参x,其值都是 10。

片断二 :在addIntSelf方法中的代码解析

//本次调用x的值为10 public static void addIntSelf(int x){ // x自加 =>x=x+x x+=x; // x的值变为 20 //打印变量x的值 System.out.println("addIntSelf x="+x); }

java方法的传参与变量的数据类型的关系_java_04

由上图可见,在addIntSelf方法中,形参x的值改为了20,但实参a的值没有变化,还是10.

addIntSelf中的代码运行完毕,返回main方法。此时addIntSelf方法中的变量出栈,销毁。此时栈中还保留 变量a,其值为10。所以,以上代码最终运行结果如下:

addIntSelf x=20main a=10

四、Java引用类型

4.1 Java引用类型的声明和赋初值

所有不是8种基本类型的变量都是引用类型变量。声明一个引用类型的变量,会在内存空间中为它开辟一个空间等待保存地址。赋值后,其引用的数据所在内存空间地址会做为变量的值保存在变量中。下面用数组变量和自定义类的变量来举例说明一下。

4.1.1 数组变量的声明

int[] arrInt=new int[4];

代码解析:

首先给变量arrInt开辟一个内存空间(如果变量声明在方法中,则内存空间在栈中)。

其次,在堆中开辟一个数组的内存空间。这个内存空间由连续的四个int类型的内存空间组成。首元素的地址即这个数组内存空间的地址,保存在变量 arrInt中,做为arrInt的值。

java方法的传参与变量的数据类型的关系_java传参_05

4.1.2 自定义类的对象变量的声明

定义一个Student类,类中只有一个属性name。get和set方法分别对这个属性进行读写操作。

public class Student { private String name; public String getName() { return name; } public void setName(String name) { this.name = name; }}

创建一个学生对象

Student stu=new Student();

创建对象的代码解析:

首先,给Student类型的变量stu开辟一个内存空间。

其次,在堆中开辟一个内存空间,用来存放Student类型的对象。

最后,将对象的空间地址赋值给变量stu.

java方法的传参与变量的数据类型的关系_java传参_06

好了,至此,我们明确了,引用类型变量里保存的是地址,地址,地址

4.2 Java引用类型传参

重要的事情再说一遍

  • 所有的形参都是实参的复制。
  • 形参的值的改变,不会影响到实参但是,引用类型变量里存的是地址。所以,形参复制了实参的值,得到的是地址。再去操作这个地址所指向的对象、数组、集合什么的,好象是合法的哇。是不是突然发现了什么大不了的事情?
  • 当实参是引用类型变量时,将值(实际是被引用数据的地址)复制给形参。

4.2.1形参得到地址后,去操作所引用类型的数据

上示例代码

public static void main(String[] args) { Student stu=new Student(); stu.setName("张三"); System.out.println("学生姓名:"+stu.getName()); setStudentName(stu); System.out.println("*****调用方法后*****"); System.out.println("学生姓名:"+stu.getName()); } public static void setStudentName(Student s){ s.setName("李四"); }

运行结果:

学生姓名:张三*****调用方法后*****学生姓名:李四

代码解析:

​1.  在main方法中创建一个学生对象,并将其属性name赋值为:张三

java方法的传参与变量的数据类型的关系_java变量的数据类型_07

  • 调用setStudentName方法,传入学生对象(其实质是传入的学生对象所在地址) 在栈中给setStudentName方法的参数s开辟一个内存空间,里面保存传入的学生对象的地址(即复制了实参stu的值)。
  • java方法的传参与变量的数据类型的关系_java_08

  • 运行 setStudentName方法中的代码:
  • s.setName("李四");
  • 将学生对象的name属性的值从张三更改为李四

    java方法的传参与变量的数据类型的关系_java传参_09

    4. setStudentName方法中的代码运行结束,返回main方法。形参s退栈。

    而main方法中的stu变量所引用的学生对象其name属性已被更改为李四。有没有问题?

    java方法的传参与变量的数据类型的关系_java传参_10

    4.2.2形参指向另一个对象

    上示例代码

    public static void main(String[] args) { Student stu=new Student(); stu.setName("张三"); System.out.println("学生姓名:"+stu.getName()); setStudentName(stu); System.out.println("*****调用方法后*****"); System.out.println("学生姓名:"+stu.getName()); } public static void setStudentName(Student s){ s=new Student(); s.setName("李四"); }

    运行结果:

    学生姓名:张三*****调用方法后*****学生姓名:张三

    代码解析:第1、2步与上例(4.1.1的示例)相同。

    ​1.  在main方法中创建一个学生对象,并将其属性name赋值为:张三

    java方法的传参与变量的数据类型的关系_java传参_11

  • 调用setStudentName方法,传入学生对象(其实质是传入的学生对象所在地址) 在栈中给setStudentName方法的参数s开辟一个内存空间,里面保存传入的学生对象的地址(即复制了实参stu的值)。
  • java方法的传参与变量的数据类型的关系_java变量的数据类型_12

    3.运行 setStudentName方法中的代码:

  • s=new Student();s.setName("李四");
    • 新建一个学生对象,并将这个新的学生对象的地址赋值给s。
    • 将s所指学生对象的name属性赋值为李四

    java方法的传参与变量的数据类型的关系_java变量的数据类型_13

    4.setStudentName方法中的代码运行结束,返回main方法。形参s退栈。

    而main方法中的stu变量所引用的学生对象其name属性还是张三,没有更改。而新的student对象没有引用变量指向它,呆在堆中等待被回收,此时只能想静静。此时没问题吧。

    java方法的传参与变量的数据类型的关系_java变量的数据类型_14

    五、结论
    • 参数是基本类型,形参不能对实参造成任何影响。
    • 参数是引用类型
      • 形参和实参所引用的数据相同(同一个副本)。可以使用形参来修改实参所引用的数据。
      • 如果给形参重新赋值,则形参所引用的数据与实参不再相同(不再是同一个副本)。此时修改形参所引用数据的值,实参所引用数据不会被改变。

    5.1解决引入问题

    public static void main(String[] args) { List<String> list = new ArrayList<>(); list.add("1"); list.add("2"); list.add("3"); list.add("4"); testList(list); System.out.println("list.size:"+ list.size()); } public static void testList(List<String> list) { list = list.stream().filter(x -> { if(x.equals("3")) return false; return true; }).collect(Collectors.toList()); System.out.println("test.list size:"+list.size()); }

    运行结果是这样子的:

    test.list size:3 list.size:4

    他问,为什么最后打印的结果,main方法中list集合里还是4个元素,不应该是3个元素吗?答案解析list.stream()方法和list.stream().filter()都会新建一个对象,我们来简单看看源代码。list.stream()

    public static <T> Stream<T> stream(Spliterator<T> spliterator, boolean parallel) { Objects.requireNonNull(spliterator); return new ReferencePipeline.Head<>( spliterator, StreamOpFlag.fromCharacteristics(spliterator), parallel); }

    list.stream().filter()

    public final Stream<P_OUT> filter(Predicate<? super P_OUT> predicate) { Objects.requireNonNull(predicate); return new StatelessOp<P_OUT, P_OUT>(this, StreamShape.REFERENCE, StreamOpFlag.NOT_SIZED) { // ... }; }

    首先:list.stream()方法返回的就是一个全新的对象(我们给它命个名:S)。其次:filter()方法接收到全新的对象S,对传入的数据过滤筛选后,将合条件的数据保存到更新的对象(此处它拥有姓名:F)传出。将这个新建的对象F重新赋值给list变量。则此时list变量与实参所指对象不再是同一个。而且整个过程中实参所指对象并没有任何更改。所以调用testList(list) 结束后,在main方法中打印list,结果是4个元素。

    5.2解决那个一

    • 下面代码两次打印的值分别是什么?

    public static void main(String[] args) { String ss="我在main函数中被赋值了"; stringTest(ss); System.out.println("这是编号1打印ss="+ss); } public static void stringTest(String ss){ System.out.println("这是编号2打印ss="+ss); ss="我在stringTest方法中被改写了"; }

    运行结果

    这是编号2打印ss=我在main函数中被赋值了这是编号1打印ss=我在main函数中被赋值了

    答案解析首先字符串是不能被更改的。只要更改了,就是另外一个对象了。其次,形参ss在stringTest()方法中被重新赋值,指向了另一个字符串对象,不会影响到实参的值。

    5.3解决最后的两个问题

    • 某方法的形参为数组,并在方法中修改了这个数组其中一个元素的值,且此方法没有返回值。为什么方法调用结束后,实参所引用数组的这个元素的值也改了?答案:因为形参和实参所引用的是同一个数组。
    • 某方法的一个形参为某个对象,并在方法中修改了这个对象的属性值,且此方法没有返回值。为什么方法调用结束后,实参所引用对象的属性值也变了?答案:因为形参和实参所引用的是同一个对象。---突然感觉这个答案写得好象有点不走心?涉嫌抄袭上一题答案?

    打完收工

    上一篇:ElasticSearch-基础篇
    下一篇:没有了
    网友评论