对于传参,从计算机的本质而言是传值.因为从数据模型而言计算机只认识数学值.但是不同的数学值代表的意义不同,有的数学值表示的是另一个数据的地址.所以根据这个数学值能访问到它表示的数据,我们就把这样的操作称为传址.其实就是某个数据所在的地址的数学表示.其本质还是传值.
从应用层而言我们所说的地址当然是指虚拟地址.而对于中间语言(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 中的元素.
这不仅是安全的问题.设计一个不变类,如果传入参数是数据结构的容器类,那就不能保证是不变类,但只要在处理前复制为方法内的局部变量就可以保证类的不变性.
分享到:
相关推荐
Spring MVC传参是Spring学习中的比较容易混淆的地方,因为传参方式种类多,接受参数种类多,初学者较容易因为不懂HTTP参数传递原理,而摸不着头脑,该篇文章介绍Spring MVC参数绑定的几种方式。
7、设计一个集合类Set,包括将集合置空、添加元素、判断元素是否在集合中、输出集合,以及将集合中元素逆置。10、有若干教师,每个教师只有姓名,一个教师可以指导多名研究生;每名研究生有姓名、研究方向和班号数据...
本篇文章主要介绍了Android中Activity之间跳转和参数传递的实例,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
函数的参数传递本质上就是:从实参到形参的赋值操作。 Python 中“一切皆对象”,所有的赋值操作都是“引用的赋值”。所以,Python 中参数的传递都是“引用传递”,不是“值传递”。具体操作时分为两类: 对“可变...
而按引用传递参数,使得可以按需要同时返回的几个值都作为参数传递给方法,被调用的方法在其程序的内部对这些值做了修改后,调用者可以访问这些按引用传递的参数,来获取被调用方法计算的结果。
任务九:使用集合的方法(ArryList集合对类对象Member和Goods类) 完成淘宝shopping”购物管理系统购物结算模块和客户管理模块 1.需要完成的任务 开发“淘宝Shopping购物管理系统”: (1)实现系统数据传递 (2)...
表格超连接列传递参数 清空Cookie 获取错误信息并到指定页面 自定义异常处理 javascript小技巧 DotNet 密码加密的技术 用户MD5加密 ASP.net验证码实现 图片水印 防盗链 .NET(C#)...
下面小编就为大家带来一篇Java中前台往后台传递多个id参数的实例。小编觉得挺不错的,现在就分享给大家,也给大家做个参考。一起跟随小编过来看看吧
还有一点,就是l 类(Class这一级)――包括集合类与非集合,l 非集合类 需要序列化的属性里的访问方法(不用序列化的属性例外),l 在集合类里,上面提到过的Add方法,Item属性、Count属性、Current属性的访问方法...
args:这个是数组或类数组,apply方法把这个集合中的元素作为参数传递给被调用的函数。 call call方法与apply方法的第一个参数是一样的,只不过第二个参数是一个参数列表 在非严格模式下当我们第一个参数传递为
4.2.3 常用系统预定义的集合类 4.3 本章小结 4.4 上机练习 4.5 习题 第5章 C#面向对象程序设计基础 5.1 面向对象程序设计概述 5.2 类与对象 5.2.1 类与对象概述 5.2.2 面向对象程序设计相关概念 5.2.3 类的声明与...
可以把类型参数看作是使用参数化类型时指定的类型的一个占位符,就像方法的形式参数是运行时传递的值的占位符一样。 可以在集合框架(Collection framework)中看到泛型的动机。例如,Map 类允许您向一个 Map 添加...
可以把类型参数看作是使用参数化类型时指定的类型的一个占位符,就像方法的形式参数是运行时传递的值的占位符一样。 可以在集合框架(Collection framework)中看到泛型的动机。例如,Map类允许您向一个Map添加任意...
在 SqlCommand 对象能够被执行之前,所有参数都必须添加到 Parameters 集合中,并且必须正确设置 Connection、CommandType、CommandText 和 Transaction 属性。SqlHelper 类中的专用函数主要用于提供一种一致的方式...
编写和调用类的带参数的方法和不带参数的方法实现通信 3、使用循环语句编写程序 使用if-else和switch条件结构进行分支判断 使用for、while和do—while循环进行输出 跳转语句break的使用 4、使用数组和集合存储数据...
java发送http请求时,post、get工具集合、支持各种参数类型传递
将 this 作为最后一个参数传递给 drawImage()便可将 Applet 对象传递过去;或者直接采用 null 对象 作为最后一个参数。 2、EarthPlanetApplet 程序代码如下 (1)回顾 Java 中的数组 数组也是一个对象,并且数组也...
参数传递.docx 第二周所学总结.docx 反射机制.docx 泛型.docx 封装和继承以及多态部分.docx 接口和抽象类以及实现类.docx 枚举enum.docx 设计模式.docx 数组.docx 网络编程.docx 线程和内部类.docx 循环和类对象....
3.4.2 传递命名参数 58 3.4.3 消除可选参数和命名参数的歧义 59 第3章快速参考 63 第4章 使用决策语句 65 4.1 声明布尔变量 65 4.2 使用布尔操作符 66 4.2.1 理解相等和关系操作符 66 4.2.2 理解条件逻辑操作...