Java Evaluation Strategies
简单记录一下关于Java Evaluation Strategies的tips
Evaluation strategies are used by programming languages to determine two things—when to evaluate the arguments of a function call and what kind of value to pass to the function.
Strict evaluation (Eager evaluation)
In strict evaluation, the arguments to a function are always evaluated completely before the function is applied.
Applicative order
https://docs.oracle.com/javase/specs/jls/se8/html/jls-15.html#jls-15.7
Applicative order evaluation is an evaluation strategy in which an expression is evaluated by repeatedly evaluating its leftmost innermost reducible expression.
这里有两个比较有意思的case
- Run-Time Evaluation of Class Instance Creation Expressions
class List {
int value;
List next;
static List head = new List(0);
List(int n) { value = n; next = head; head = this; }
}
class Test {
public static void main(String[] args) {
int id = 0, oldid = 0;
try {
for (;;) {
++id;
new List(oldid = id);
}
} catch (Error e) {
List.head = null;
System.out.println(e.getClass() + ", " + (oldid==id));
}
}
}
prints
class java.lang.OutOfMemoryError, false
- Run-Time Evaluation of Array Creation Expressions
class Test3 {
public static void main(String[] args) {
int len = 0, oldlen = 0;
Object[] a = new Object[0];
try {
for (;;) {
++len;
Object[] temp = new Object[oldlen = len];
temp[0] = a;
a = temp;
}
} catch (Error e) {
System.out.println(e + ", " + (oldlen==len));
}
}
}
prints
java.lang.OutOfMemoryError, true
Call by value
Call-by-value,也就是值传递,有如下几个特点
1. evaluated and bounded to the variable in the function(frequently by copying the value into a new memory region)
2. anything passed into a function call is unchanged in the caller's scope when the function returns
3. order left/right
典型的就是C和Pascal,in C or Pascal, calling a function with a large structure as an argument will cause the entire structure to be copied (except if it’s actually a reference to a structure), potentially causing serious performance degradation, and mutations to the structure are invisible to the caller. 比起C和Pascal的低效率值拷贝, java和VB就舒服了很多, in Java or Visual Basic only the reference to the structure is copied, which is fast, and mutations to the structure are visible to the caller, 也就是传说中的 Call-by-sharing 这个我们后面细说.
这里有个特殊情况,就是对于多态的情况,如果方法参数用的是父类,而传递的值是子类,name在进行值传递的时候,value copy只会对父类的结构进行构造,对于子类自有的特殊模块会丢弃,所以在值传递的情况下,想在方法内使用子类的特性是无法实现的。
Call by reference
Call-by-reference也就是我们常说的引用传递,要看某种语言是否是引用传递,就看是否支持swap(a, b).其实无非就是将实际值的地址传递到函数中,那么在function中的修改就会对原值产生影响。
这里还有一个指针传递,传递的时候都传递copy的内存地址。对于引用和指针来讲,两者都指向一块内存。但又大有不同,主要是指针指向的是一个内存地址,运行至可以随意更改,而引用是依赖于内存块的一个别名,依附于对象,指向的地址只能在定义时被初始化一次,除非重新定义或者对象内存地址发生变更(如内存碎片整理),否则不可变
程序在编译时分别将指针和引用添加到符号表上,符号表上记录的是变量名及变量所对应地址。
指针变量在符号表上对应的地址值为指针变量的地址值,而引用在符号表上对应的地址值为引用对象的地址值。
符号表生成后就不会再改,因此指针可以改变其指向的对象(指针变量中的值可以改),而引用对象则不能修改。
所以在一些场景下,比如内存碎片整理后,引用的值会发生变化而指针的值不会变。
这里说一点引用传递的问题,call-by-reference language makes it more difficult for a programmer to track the effects of a function call, and may introduce subtle bugs. 而且相比引用是类型安全的,指针缺乏类型检查,所以在程序安全性方面指针传递引起的问题会更多一些。Many languages support call by reference in some form, but few use it by default.
Call by sharing
Call by sharing (also known as “call by object” or “call by object-sharing”) 也就是值传递,这个我们接触就比较多了,it used by languages such as Python,[6] Java (for object references), Ruby, JavaScript, Scheme, OCaml, AppleScript, and many others.
Call by sharing implies that values in the language are based on objects rather than primitive types, i.e., that all values are “boxed”. Because they are boxed they can be said to pass by copy of reference (where primitives are boxed before passing and unboxed at called function). 对于java而言,当我们传递的是primitive types的时候其实是直接对参数进行了值复制,当我们传递的是引用的时候其实是对引用的值进行了复制并赋值给function var.
However, since the function has access to the same object as the caller (no copy is made), mutations to those objects, if the objects are mutable, within the function are visible to the caller, which may appear to differ from call by value semantics. Mutations of a mutable object within the function are visible to the caller because the object is not copied or cloned—it is shared.
但是日常我们并不将java里的传递策略叫做call-by-sharing, the term “call by sharing” is not in common use; the terminology is inconsistent across different sources. For example, in the Java community, they say that Java is pass-by-value, whereas in the Ruby community, they say that Ruby is pass-by-reference[citation needed], even though the two languages exhibit the same semantics. Call-by-sharing implies that values in the language are based on objects rather than primitive types.