对于传参,从计算机的本质而言是传值.因为从数据模型而言计算机只认识数学值.但是不同的数学值代表的意义不同,有的数学值表示的是另一个数据的地址.所以根据这个数学值能访问到它表示的数据,我们就把这样的操作称为传址.其实就是某个数据所在的地址的数学表示.其本质还是传值.
从应用层而言我们所说的地址当然是指虚拟地址.而对于中间语言(java的byteCode/.NET的IL)而言是托管地址.我们不必关心实际的内存地址如何和它们对应,这由作业系统和应用环境来决定,你想关心也关心不了.我们要关心的是理解作业系统和应用环境提供给我们的可访问地址的内存布局.
对于集合类参数,如果传入方法后在方法外重新赋值参数本身,这和其它引用参数一样不会影响方法内的参数.但如果对集合中元素重新赋值则改变了方法内的集合中的元素,因为方法外和方法内的集合就是本身是同一对象.
class MyRun{
public static void exec(String[] args){
for(int i=0;i<args.length;i++){
try{
check(args[ i ]);
}catch(Exception e){}
invoke(args[ i ]);
}
}
static void check(String str) throws Exception{
if(str.equals("s2")) throw new Exception("error1");
}
static void invoke(String str){
System.out.println("执行的语句是"+str);
}
}
这段程序设计是否有错?如果有错,如何修改?
这是我在bea论坛上贴出来的一段程序,最初没有一个人能说明有什么错,更别说如何修改.(我知道有很多水平很高的高手根本不去bea论坛所以没有看到这个问题)
更可悲的是我把问题展示了很多根本看不懂的人说我在胡说八道,这就是中国程序员的现状,他没有能力理解和不知道的东西都叫胡说八道,当他上小学时他说初中,高中,大学,研究生的知识是胡说八道的.
这个问题的展示很简单:
当你设计了上面的类以后, 那么我作为调用者,我可以任何方式调用你的类,你都应该是安全的.
好,我现在这样调用:
先设计一个用来改变数据的线程:
class ModifyThread extends Thread{
private String[] arr;
public ModifyThread(String[] arr){
this.arr = arr;
}
public void run(){
try{
Thread.currentThread().sleep(20);
}catch(Exception e){}
arr[0] = "s2";
}
}
为了说明问题,我在你设计的类中插入一段sleep来模拟线程运行到那里时被切换到其它线程运行,然后又切换回来到本线程运行的情况:
class MyRun{
public static void exec(String[] args){
for(int i=0;i<args.length;i++){
try{
check(args[ i ]);
//为了说明问题,在这儿sleep(100)来模拟运行到这里时线切换到其它线程去运行
Thread.currentThread().sleep(100);
}catch(Exception e){}
//然后又回到这个这线程继续运行.
invoke(args[ i ]);
}
}
static void check(String str) throws Exception{
if(str.equals("s2")) throw new Exception("error1");
}
static void invoke(String str){
System.out.println("执行的语句是"+str);
}
}
然后调用:
public class Main {
/** Creates a new instance of Main */
public static void main(String[] args) {
// TODO code application logic here
String[] strs = {"s1"};
new ModifyThread(strs).start();
MyRun.exec(strs);
}
}
试试看,我利用一个辅助线程就把s2传进去执行了.
好,有人说要同步:
class MyRun{
public static void exec(String[] args){
for(int i=0;i<args.length;i++){
synchronized(args){
try{
check(args[ i ]);
//为了说明问题,在这儿sleep(100)来模拟运行这里时线切换到其它线程去运行了.
//然后又回到这个这线程继续运行.
Thread.currentThread().sleep(100);
}catch(Exception e){}
invoke(args[ i ]);
}
}
}
static void check(String str) throws Exception{
if(str.equals("s2")) throw new Exception("error1");
}
static void invoke(String str){
System.out.println("执行的语句是"+str);
}
}
再调用看看,s2仍然越过了check.因为同步只能保证明多个MyRun.exec()方法在执行时只有一个线程能访问方法内的args数组,根本无法保证从方法外修改strs, 因为方法外的代码没有和方法内共同竞争同一对象锁,.我用sleep来模拟线程间执行的切换.其实只要我用足够多的ModifyThread线程在不同时刻运行起来,和MyRun.exec一起运行,产生这种切换的可能性就非常大.简单说我只要用足够多的辅助线程就能绕过你设计的check.
其实要解决问题非常容易:
class MyRun{
public static void exec(String[] args){
//将传入变量复制为方法内的本地变量,打断与方法外的联系.
//然后只能本地变量操作
String[]temp = new String[args.length];
System.arraycopy(args, 0,temp, 0, args.length);
for(int i=0;i<temp.length;i++){
//synchronized(temp){
//然后根本不需要同步.
try{
check(temp[ i ]);
//为了说明问题,在这儿sleep(100)来模拟运行这里时线切换到其它线程去运行了.
//然后又回到这个这线程继续运行.
Thread.currentThread().sleep(100);
}catch(Exception e){}
invoke(temp[ i ]);
//}
}
}
static void check(String str) throws Exception{
if(str.equals("s2")) throw new Exception("error1");
}
static void invoke(String str){
System.out.println("执行的语句是"+str);
}
}
如果你还不能理解或者不相信我,我们来看看JDK(1.6)是如何处理的.Runtime.exec最终调用了ProcessBuilder的start()方法.在ProcessBuilder中,外部命令可以通过command(List<String> command)这样的方法将一个List传进来,而本地用全局的List command保存也就是command(List<String> command)的实现是: this.command = command;
这样外部传入一个保存了多个命令的List后,从外部仍然可以访问方法内的List中的内容.所以在start()方法中JDK这样处理,而且加上了这样的注释:
// Must convert to array first -- a malicious user-supplied
// list might try to circumvent the security check.
String[] cmdarray = command.toArray(new String[command.size()]);
然后对方法内的cmdarray 进行操作,用户修改外部的list就不会影响到cmdarray 中的元素.
这不仅是安全的问题.设计一个不变类,如果传入参数是数据结构的容器类,那就不能保证是不变类,但只要在处理前复制为方法内的局部变量就可以保证类的不变性.
分享到:
相关推荐
在C#编程中,跨窗体参数传递是应用程序设计中常见的需求,特别是在开发具有多个界面的桌面应用时。本主题将深入探讨如何在不同的窗体之间有效地传递数据,以实现功能如从一个窗体的文本框输入数据并在另一个窗体上...
本文介绍了一种基于C++模板的模块间参数传递方案,该方案通过定义通用的参数集合类和转换函数实现了参数的灵活传递。这种方案不仅可以减少代码冗余,提高代码的可读性和可维护性,还能有效避免类型安全问题。对于从...
本讲主要聚焦于Spring MVC中的参数传递类型,探讨如何有效地从请求中提取不同类型的数据。 首先,Spring MVC支持的基本参数类型包括字符串、整型、浮点型等基本数据类型,以及它们的包装类。例如,通过在控制器方法...
标题和描述提及的“vc 多线程集合,调用,参数传递”主要涵盖了如何在Microsoft Visual C++ (VC++)环境中创建和管理多线程,以及如何在不同线程之间传递参数。 多线程的引入是为了解决单线程程序在执行长时间任务时...
本教程将重点讲解如何接收ajax和form表单传递的集合参数。 一、接收Ajax传递的集合参数 1. JSON格式数据:Ajax通常使用JSON格式传递复杂数据,如集合。在JavaScript中,你可以创建一个JSON对象,将集合数据转换...
在实际应用中,我们经常需要通过Web服务传递集合类型的数据,例如数组或列表,以实现更复杂的数据交互。本实例将深入探讨如何在Web服务中有效地传递集合。 在Web服务中,传递集合通常涉及到以下关键知识点: 1. **...
总的来说,水晶报表的参数传递是通过创建参数字段对象,设置其参数名称和值,然后将这些参数传递给报表查看器实现的。这种方法使得我们能够灵活地控制报表的数据源和呈现内容,从而提供了更丰富的报表功能。在开发...
### Weka中参数传递的深度解析与应用 #### 引言 Weka,全称Waikato Environment for Knowledge Analysis,是一款开源的数据挖掘软件工具包,主要用于机器学习和数据挖掘任务。它提供了丰富的算法集合,包括分类、...
例如,创建ArrayList时,可以通过传递初始容量参数来减少扩容次数。此外,了解各种集合类的内部实现,比如HashMap的负载因子和扩容策略,可以帮助我们更好地理解如何影响性能和内存使用。 总之,理解Java集合类API...
Spring MVC传参是Spring学习中的比较容易混淆的地方,因为传参方式种类多,接受参数种类多,初学者较容易因为不懂HTTP参数传递原理,而摸不着头脑,该篇文章介绍Spring MVC参数绑定的几种方式。
Delegate是.NET框架中的一种类型安全的函数指针,它允许我们将方法作为参数传递给其他方法,同时也支持事件处理。 首先,理解委托的概念。委托在VB.NET中相当于一个事件处理程序的签名,它定义了一个方法集合的...
### .NET中WinForm参数传递过程详解 #### 一、前言 在.NET框架中,Windows窗体(WinForms)是一种强大的工具,用于构建丰富的Windows应用程序。它提供了一个结构化、面向对象且可扩展的类集合,使得开发者能够轻松...
在这个特定的项目中,我们关注的是三个关键知识点:JSP分页组件、JSP中的存储过程调用以及URL参数传递。 首先,让我们详细了解一下**JSP分页组件**。在处理大量数据时,分页是常见的优化手段,可以提高用户体验并...
在实际开发中,我们经常需要将数组或集合类作为参数传递给MyBatis的Mapper文件,并在SQL语句中使用foreach遍历出其中的值,例如我们想从用户表中获取一组ID对应的用户信息。 首先,在Java DAO层接口中,我们需要...
'声明将参数传递给查看器控件所需的变量。 Dim paramFields As New ParameterFields() Dim paramField As New ParameterField() Dim discreteVal As New ParameterDiscreteValue() Dim rangeVal As New ...
另一种解决方案是将参数封装到数组或集合中,然后将整个集合作为单一参数传递。例如,对于需要两个`int`参数的情况,可以这样做: ```csharp void Method(object o) { int[] paramsArray = (int[])o; method...
在Windows Forms(Winform)开发中,窗体间的参数传递是一项基本且重要的技能。本节主要探讨如何在C#环境下实现窗体之间的数据交互。如果你是从Delphi背景转到C#,可能会发现两者在实现这一功能上有一定的差异,但...
为了确保服务端和客户端能正确通信,通常会编写测试类来模拟各种参数传递场景。这些测试类可能包含: - 单个基本类型参数:如整型、字符串等。 - 复杂类型参数:如自定义对象,可能需要序列化和反序列化。 - ...
本文档重点介绍了如何使用ksoap2库调用Web Service服务,包括不同类型的参数传递方法。 1. Web Services简介 Web Services是一种基于开放Web标准,特别是XML的互联网应用程序。它们可以通过HTTP协议进行通信,允许...
SqlHelper类是基于ADO.NET的一组实用方法的集合,它封装了常见的SQL查询和存储过程调用,如执行SQL命令、执行存储过程、分页查询等。通过使用此类,开发者可以避免编写重复的数据库连接和关闭代码,提高代码的可读性...