先上一段代码,无码无真相 ^-^
import java.util.ArrayList;
import java.util.Date;
public class JavaReference
{
public static void change(Date date)
{
date = null;
}
public static void changeList(ArrayList<Integer> lst)
{
lst.add(3);
lst = null;
}
public static void main(String[] args)
{
Date d = new Date();
System.out.println(d);
JavaReference.change(d);
System.out.println(d); // 这里会打印 null 吗?
ArrayList<Integer> list = new ArrayList<Integer>();
list.add(1);
list.add(2);
System.out.println(list.size());
JavaReference.changeList(list);
System.out.println(list.size()); // 这里会报空指针错误码?还是会打印 3 呢?
}
}
上面的 d 会打印null吗?
最后的打印语句会报空指针吗?
继续看之前,请您在心中该处答案,并且分析为什么结果是这样的。
===================================华丽的分割线======================================
打印结果为:
Wed Dec 03 13:35:21 CST 2014
Wed Dec 03 13:35:21 CST 2014
2
3
您答对了吗?
为什么结果是这样的呢?
原因分析:
有人说Java中没有指针,但是java中有处处是指针。指针的本质是什么呢?学过C/C++的应该不陌生,指针是一个变量,他自己的内存空间在栈上分配,既然指针是一个变量,那么这个变量的值是什么呢?指针这个变量专门用于保存 地址值 ,而这个地址值,在Java中是一个堆上的对象的地址。在C/C++中指针也会指向栈上的变量或者对象。但是在Java中指针或者说引用只能指向堆上的某个对象。
在Java中函数参数的传递,是值传递呢?还是引用传递呢?
没有异议:绝对是值传递!
有了上面的基础,再来分析上面的结果:
JavaReference.change(d);
change函数将 d 这个保存了 new Date() 语句所产生的对象的地址传给了函数change,在C/C++中,如果要在change函数中修改 d 所指向的对象,必须如下操作:
*d = null;
这里的含义是: *d 表示取得 d 所指向的对象,就是得到d所指向的对象,然后再来修改d所指向的那个对象。
但是在java中不能这样操作,那么java中change函数中的
date = null
是什么意思呢?
是这样的:change(d) 将 new Date() 对象的地址传递给了 change函数的参数 date, 此时date是change函数局部变量,然后再将null赋值给这个局部变量,那么实际参数 d 的值变了吗?没有!d 还是指向 new Date() 所产生的那个对象。
所以 System.out.println(d); 不会打印null,他会自动调用 d.toString()
注意注意:d.toString() 这个调用很神奇!!!! 他为什么神奇,他哪里神奇呢?
我们知道 d 保存了指向 new Date() 的那个对象,那么 d 是如何访问 new Date() 对象的方法的呢?语法我们都知道: d.toString(),但是其中的暗含了下面的过程:
1)先获得 d 指向或者说引用的对象,即那个 new Date() 产生的对象;
2)然后调用他的 toString()方法;
在C/C++中相当于: d->toString(); 或者: (*d).toString();
看到这里,您应该明白了,因为java中没有 (*d) 这样的操作符来访问d所指向的对象,所以java中直接省略了(*d) 或者,将 (*d) 暗含在 d.toString()中了。
所以java 中的 d.toString() 等价于C/C++中的 (*d).toString().
明白了这一点就一切真相大白了。
JavaReference.changeList(list);
调用,在changeList中,将list所指向的 new ArrayList<Integer>() 对象的地址传递给了changeList的局部变量lst ,然后 lst.add(3) 相当于C/C++中的 (*lst).add(3);即先获得lst这个局部变量中保存的地址所指向的对象,也就是指向 new ArrayList<Integer>() ,然后调用这个对象 的 add(3)方法。所以list.size() == 3
那么 局部变量 lst = nul; 是什么意思呢?他只是将 局部变量 lst 的值赋值为null,他没有改变实际参数 list.
lst = null 不等价于C/C++中的 (*lst) = null;
原因是这里没有使用调用操作符 “.”,他就是普通的局部变量的赋值操作。
调用操作符 “.”为什么会相当于C/C++中的 (*lst).add(3)呢?即局部变量 lst 调用函数 add(3) 时,为什么先要 访问 lst 所指向的对象 new ArrayList<Integer>() ??
废话,不先访问他指向的对象,怎么去调用他的方法呢???
所以到这里:最重要的是,java中对象参数的传递时基于值的传递,传递的是实际参数所引用的那个对象的地址。指针,或者说引用在调用函数时,会自动先去访问它的地址所指向的那个对象,也就是它说引用的那个对象,然后调用该对象的方法。所以就修改了该对象。所以最后的list.size() == 3;
分享到:
相关推荐
JNI接口定义了如何在Java和本地代码之间传递参数、执行方法调用以及返回结果。 在Eclipse中开始JNI开发,你需要做以下几步: 1. **创建Java项目**:首先,我们需要一个Java项目,其中包含至少一个Java类,该类声明...
### cocos2d-x初探学习笔记(3)—— ...通过以上内容,我们可以看到`cocos2d-x`中的`CCAction`不仅提供了丰富的动作类型供开发者选择,还支持灵活的动作组合以及消息传递机制,极大地提高了游戏开发的效率和灵活性。
2.1.2 Scala:Scala是一种多范式编程语言,它集成了面向对象和函数式编程的特点。虽然使用Scala开发Android应用相对较少,但它提供了强大的语言特性和表达能力。 2.1.3 Java:Java是Android开发最常用的语言,...
8.3 按值传递机制 304 8.4 函数声明 305 8.5 指针用作参数和返回值 307 8.5.1 常量参数 310 8.5.2 从函数中返回指针值 318 8.5.3 在函数中递增指针 322 8.6 小结 322 8.7 习题 323 第9章 函数再探 325 9.1 ...
为了传递额外的参数,代码展示了使用`createFunction`函数来包装事件处理器,使其能接收任意数量的参数。`createFunction`通过`arguments`对象收集额外参数,并在事件触发时使用`apply`方法将它们传递给实际的事件...
介绍函数的概念,包括如何定义函数、传递参数、返回值,以及局部变量与全局变量的区别。函数是模块化编程的核心,有助于提高代码的复用性和可维护性。 #### 第6章:类与对象 探索面向对象编程(OOP)的基本原理,...
- **构造函数参数**:指定构造函数所需的参数。 - **属性值**:用于通过setter方法注入的属性值。 这些配置信息通常通过配置文件(如XML文件)或者注解的形式来定义。 ##### 2.3 注入参数 注入参数是指容器为Bean...
了解函数的定义、参数传递和返回值是编程的基础。 4. **类与对象**:Java是面向对象的语言,类是对象的蓝图,而对象则是类的实例。理解封装、继承和多态是理解面向对象编程的关键。 5. **数组**:数组是存储相同...
1. **接口注入**:通过将依赖对象作为参数传递给目标对象的方法,实现依赖的注入。 - **优点**:易于测试。 - **缺点**:可能会导致方法参数过多,降低代码的可读性。 2. **设值注入**:通过setter方法将依赖对象...
3. **Type3 构造子注入**:依赖通过构造函数参数传递,这种方式确保了依赖在对象创建时即被初始化,从而避免了潜在的null引用问题。虽然可能使得构造函数变得冗长,但在高并发或多线程环境中,它能提供更好的线程...
5. **通信机制**:JADE提供了丰富的消息传递机制,例如,Agent之间可以通过`ACLMessage`进行通信。在`HelloWorldAgent`中,你可以发送或接收`ACLMessage`,处理与其他Agent的交互。 6. **调试与监控**:JADE提供了...
2. **构造子注入**:通过类的构造函数传递依赖对象,这种方法确保了Bean的不可变性,增强了组件的安全性和稳定性。 3. **设值注入**:通过setter方法注入依赖,这是Spring中最常用的依赖注入方式,因为其灵活性高,...
这种特性使得在C语言中可以实现更加灵活的编程方式,比如可以将函数作为参数传递给其他函数,或者在运行时根据条件动态选择调用不同的函数。 ```c int add(int a, int b) { return a + b; } int (*operation)...
- **构造子注入**:在构造函数中声明依赖,由Spring在实例化Bean时传入。 - **设值注入**:通过setter方法将依赖注入到Bean中。 每种注入方式都有其适用场景和优缺点,选择合适的注入方式对于提高代码质量和可维护...
构建器注入(constructor injection)是指通过构造函数传递依赖项。这种方式能够确保对象在创建时就有所有必要的依赖项,提高了代码的稳定性和可测试性。 **2.2.3 属性注入** 属性注入(property injection)是通过...
- **Type3 构造子注入**:通过构造函数传递依赖,确保了对象的不可变性和依赖的一致性,适合于依赖不可变或者必不可少的情况。 - **几种依赖注入模式的对比总结**:每种依赖注入模式都有其适用场景,选择哪一种取...
构造器注入是在创建对象时通过构造函数传递依赖;setter注入则是通过调用setter方法;接口注入是通过实现特定接口来接收依赖。 - **容器指令**:容器通过一系列的指令来指导如何创建和管理对象,例如bean的定义、...
下面是一个简单的示例,演示了如何创建一个线程并传递参数给它: ```c #include #include void* printMessage(void* message) { printf("%s\n", (char*)message); return NULL; } int main() { ...