集合类参数的传递

原创 2007年09月17日 11:02:00


对于传参,从计算机的本质而言是传值.因为从数据模型而言计算机只认识数学值.但是不同的数学值代表的意义不同,有的数学值表示的是另一个数据的地址.所以根据这个数学值能访问到它表示的数据,我们就把这样的操作称为传址.其实就是某个数据所在的地址的数学表示.其本质还是传值.

 从应用层而言我们所说的地址当然是指虚拟地址.而对于中间语言(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 中的元素.
这不仅是安全的问题.设计一个不变类,如果传入参数是数据结构的容器类,那就不能保证是不变类,但只要在处理前复制为方法内的局部变量就可以保证类的不变性.
 

相关文章推荐

Java:数组和集合类作为参数传递时的差别(问题解决,谢谢大家提醒)

最近在做项目时遇到一个List集合作为参数传递的问题,想起了以前总结的参数传递,参数传递包括值传递和引用传递,集合类的参数应该属于引用传递,脑子里突然就闪现到了数组,感觉数组也是一种特殊的集合,也应该...

方法的参数传递机制测试类

  • 2015年11月05日 17:13
  • 2KB
  • 下载

spring mvc 如何传递集合参数(list,数组)

spring mvc 可以自动的帮你封装参数成为对象,不用自己手动的通过request一个一个的获取参数,但是这样自动的参数封装碰碰到了集合参数可能就需要点小技巧才可以了。 一、基础类型和引用类...

三层架构下带实体参数泛型集合的“传递”

不知道大家

android中传递复杂参数,activity之间和fragment之间的bundle传递集合/对象

在Android开发中,Activity之间通过Intent使用bundle,fragment之间和Activityty通过setArguments使用bundle,对于一些简单的参数传递比较简单,而...
  • Kern_
  • Kern_
  • 2015年05月25日 20:59
  • 9225

Android Intent参数传递,包括对象复杂集合等多方式传递

用Intent传递数据的时候,如果传递的是基本类型,不用说很容易,单个的就是单个的传,多个就用bundle传递,如果传递的是对象或者是集合,简单的某个集合还是可以直接传的,但是大多数的是不可以传的。在...
  • pcaxb
  • pcaxb
  • 2015年06月24日 10:17
  • 1605

Java值传递和地址传递:关于String类型和集合类型作为函数参数时传值问题的测试

这个问题,可能是太简单了。稍微有点经验的,估计都不会犯这个错误。不好意思,过了个年,几条不写代码,生疏了,也就有了下面的bug。 先看代码,你要是一眼就看出问题了,那就请允许在下水那么一回。 publ...

jsp页面,使用Struts2标签,传递和获取Action类里的参数,注意事项。<s:a action><s:iterator><s:param>ognl表达式 在编写SSH2项目的时候,除

jsp页面,使用Struts2标签,传递和获取Action类里的参数,注意事项。ognl表达式      在编写SSH2项目的时候,除了使用表单标签向Action类跳转并传递参数之外...
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:集合类参数的传递
举报原因:
原因补充:

(最多只允许输入30个字)