前言
本来平平无奇的知识点,在工作中很多人会选择忽略,但是一旦有积累之后,你才会发现这些看似理所应当的点,其实对整个代码影响是深远的。例如初级程序员看来对象深浅拷贝,关系着引用传递,一处改动处处改动,但作为久经沙场的可能意识到二叉树的生成拷贝时会出现很多问题。
求值策略
分为严格和非严格两大基本策略类型。一个语言可以组合多个求值策略,例如C++组合了值传递
和引用传递
。
值传递:实参的值复制一个副本,把这个副本传递给被调用函数的形参,被调用函数改变形参,不会改变实参的值。
引用传递:实参的引用(引用其他对象的对象,比如指针)直接传递个形参,函数调用过程中形参被改变,实参就被改变了。
共享对象传递:实参的引用拷贝一个副本,把这个地址副本传递给形参,函数调调用过程中形参内容被改变,实参就被改变了。
附件—恢复传递:向被叫进程传递一个实参的引用副本,实参与调用进程没有关系,进程调用结束,再把副本中更新的内容复制回实参引用。
共享对象传递的过程和值传递一样,实参拷贝成副本,传递副本。但结果又与引用传递一样:调用者改变形参内容,实参也就改变了。对于该问题关注过程而非结果,
我们知道目前java进程间通信在jvm层面还没有实现,但附件—恢复传递为它提供了一个策略。python的进程间通信有一个Server Process模型和他类似。只是传递的不是实参的副本,而是是一个代理对象。
数据类型
java是面向对象语言,但它数据类型保留了8种基本数据类型,基本数据类型值传递
,引用类型共享对象传递
。他们过程又都一样,复制、传递。所以通常我们说java值传递
。但肯定没有引用传递
。
数据类型
{
基本类型
引用类型
数据类型\begin{cases} 基本类型\\引用类型& \end{cases}
数据类型{基本类型引用类型
基本数据类型
八种基本类型对应的装箱类型。基本类型是内置类型,性能优于对象。但面向对象必须是以对象为主体,否则就无法实现面向对象的编程功能。所以就设计了基本类型的对应装箱类。比如实现泛型,必须是对象类型,泛型在编译阶段就会泛型擦除
,这时类型就是Object
,基本类型不属于类,所以会在编译期就报转型失败错误。
基本类型 | boolean | byte | char | short | Int | long | float | double |
---|---|---|---|---|---|---|---|---|
二进制位数 | 1 | 8 | 16 | 16 | 32 | 64 | 32 | 64 |
装箱类型 | Boolean | Byte | Character | Short | Integer | Long | Float | Double |
引用类型
引用类型赋值操作用计算机科学求值策略看,它是共享对象传递,引用副本传递之后,两(多)个不同的引用指向同一个内存地址,因此对于java初学者会引发连锁反应,一处更改,处处变动。
java求值策略分析
基本数据类型
public static void main(String[] args) {
int i = 1;
System.out.println("传递之前变量i的值:"+i);
product(i);
System.out.println("传递之后变量i的值:"+i);
}
public static void product(int arg) {
System.out.println("操作之前形参arg的值:"+arg);
arg+=1;
System.out.println("操作之后形参arg的值:"+arg);
}
/**
传递之前变量i的值:1
操作之前形参arg的值:1
操作之后形参arg的值:2
传递之后变量i的值:1
*/
引用类型
对象中的String赋值,只是改变了属性引用指向的,外部对象引用指向没变,所以外部看来拿到那个一个对象,看到name属性的值变化了。并不违背String不可变性
public static void main(String[] args) {
User us = new User();
us.name="wellim";
product(us);
System.out.println("操作之后形参us.name的值:"+arg);
}
public static void product(User arg_user) {
arg_user.name = "jack";
}
/*
操作之后形参us.name的值: jack
*/
String
String也是引用类型,为什么特立独行,不会被改变。它是final
,实际存储值的字节数组也是final,String对象一经创建,值就是不可变的,我们赋值,只是改变了这个引用的指向,指向了另一个常量池常量。看着似乎值改变了,其实并不是同一个内存区域。
public final class String
implements java.io.Serializable, Comparable<String>, CharSequence,
Constable, ConstantDesc {
private final byte[] value;
}
public static void main(String[] args) {
String s1 = "123";
product(s1);
}
public static void product(String arg) {
//只是s1引用副本值为123456。s1仍然是123
arg+="456";
}
StringBuilder
如果是引用传递,直接传递的是自己的引用,那么arg=null
之后,最后一行输出应该是null,所以并不是引用传递,而是共享对象传递,传递的是引用副本
.这时的StringBuilder和上面的User一样的,它继承自父类AbstractStringBuilder
的char[] value
,就相当于User的name属性,最终是value的引用指向了123456
这个字符串。也就是说s1指向StringBuilder这个对象没有变,只是对象内属性指向变了,所以外界看来,s1这个对象被修改该。
public static void main(String[] args) {
StringBuilder s1 = new StringBuilder("123");
System.out.println("调用之前:"+s1);
product(s1);
System.out.println("调用之后:"+s1);
}
public static void product(StringBuilder arg) {
arg.append("456");
System.out.println("调用之后arg值:"+arg);
arg = null;
}
/*
* 调用之前:123
调用之后arg值:123456
调用之后:123456
*/
装箱类
为了解决让基本类型也可以用泛型,就把它包装成对象。
装箱过程
/**
* 以iot这个类变量的装箱为例
*/
public class TestInt {
private Integer iot = 1027;
}
iot变量的token
例如private Integer iot = 1027
编译器会为我们自动装箱,调用Integer.valueOf(int i)
方法把int
类型的1027
封装为Integer
。
/** VariableInitializer = ArrayInitializer | Expression
* 编译时语法分析要校验初始化器数组或者表达式类型
* Integer走的就是parseExpression
*/
public JCExpression variableInitializer() {
return token.kind == LBRACE ? arrayInitializer(token.pos, null) : parseExpression();
}
/**
* 对Token流进行分析
*/
JCExpression term3() {
switch (token.kind) {
case INTLITERAL: case LONGLITERAL: case FLOATLITERAL: case DOUBLELITERAL:
case CHARLITERAL: case STRINGLITERAL:
case TRUE: case FALSE: case NULL:
if (typeArgs == null && (mode & EXPR) != 0) {
selectExprMode();
t = literal(names.empty);
} else return illegal();
break;
}
}
JCExpression literal(Name prefix) {
return literal(prefix, token.pos);
}
/**
* 字面量分析
*/
JCExpression literal(Name prefix, int pos) {
switch (token.kind) {
case INTLITERAL:
try {
t = F.at(pos).Literal(
TypeTag.INT,
Convert.string2int(strval(prefix), token.radix()));
}
}
/** Convert string to integer.
*/
public static int string2int(String s, int radix)
throws NumberFormatException {
if (radix == 10) {//如果是十进制,调用Integer的parseInt
return Integer.parseInt(s, radix);
} else {
char[] cs = s.toCharArray();
int limit = Integer.MAX_VALUE / (radix/2);
int n = 0;
for (char c : cs) {
int d = Character.digit(c, radix);
if (n < 0 ||
n > limit ||
n * radix > Integer.MAX_VALUE - d)
throw new NumberFormatException();
n = n * radix + d;
}
return n;
}
}
public static int parseInt(String s, int radix)
throws NumberFormatException
{
/*
* WARNING: This method may be invoked early during VM initialization
* before IntegerCache is initialized. Care must be taken to not use
* the valueOf method.
*/
if (s == null) {
throw new NumberFormatException("null");
}
if (radix < Character.MIN_RADIX) {
throw new NumberFormatException("radix " + radix +
" less than Character.MIN_RADIX");
}
if (radix > Character.MAX_RADIX) {
throw new NumberFormatException("radix " + radix +
" greater than Character.MAX_RADIX");
}
boolean negative = false;
int i = 0, len = s.length();
int limit = -Integer.MAX_VALUE;
if (len > 0) {
char firstChar = s.charAt(0);
if (firstChar < '0') { // Possible leading "+" or "-"
if (firstChar == '-') {
negative = true;
limit = Integer.MIN_VALUE;
} else if (firstChar != '+') {
throw NumberFormatException.forInputString(s, radix);
}
if (len == 1) { // Cannot have lone "+" or "-"
throw NumberFormatException.forInputString(s, radix);
}
i++;
}
int multmin = limit / radix;
int result = 0;
while (i < len) {
// Accumulating negatively avoids surprises near MAX_VALUE
int digit = Character.digit(s.charAt(i++), radix);
if (digit < 0 || result < multmin) {
throw NumberFormatException.forInputString(s, radix);
}
result *= radix;
if (result < limit + digit) {
throw NumberFormatException.forInputString(s, radix);
}
result -= digit;
}
return negative ? result : -result;
} else {
throw NumberFormatException.forInputString(s, radix);
}
}
Symbol valueOfSym = lookupMethod(tree.pos(),
names.valueOf,
box,
List.<Type>nil()
.prepend(tree.type));
对象拷贝
一个对象要调用clone方法拷贝对象,就必须实现Cloneable,它是一个标记接口。就可以实现对象浅拷贝。如果要深拷贝就需要重写clone方法
浅拷贝
object的clone方法就是浅拷贝,当属性是基本类型或者String,直接clone()就可以实现浅拷贝。这里的String很特殊,它本身是个引用类型,按理需要深拷贝的,但是String又是final类,只要值一改变,就意味着jvm内部会新创建一个String对象,所以表面看着是String被深拷贝了。
深拷贝
当要拷贝的对象中属性有引用类型,就需要深拷贝。重写clone
Person p = super.clone();
//引用类型也要调用clone
this.car.clone();
但如果这种引用类型嵌套,我们岂不是需要每一个clone方法中都要重写调用?很麻烦。
序列化
每个类都实现Serializable接口,这也是一个标记接口。通过序列化,反序列化达到深拷贝的目的。反序列化的时候调用readOrdinaryObject
,通过类描述器构建新的实例。这样就达到了两个完全不同的对象的目的。
obj = desc.isInstantiable() ? desc.newInstance() : null;