集合类参数的传递


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

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

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值