传递对象就是在传递它的引用
- java不带指针,但java中所有对象标识(除基本类型)都是指针,同时编译和运行时严格限制和保护这些指针
- java的指针即引用,java没有指针运算,是安全的指针
2.1 传递引用
把引用传递给方法后,该引用仍指向原来的对象
java
public class PassReferences {
public static void f(PassReferences h) {
System.out.println("h = " + h);
}
public static void main(String[] args) {
PassReferences p = new PassReferences();
System.out.println("p=" + p);
f(p);
//p=com.edfeff.ch02.PassReferences@723279cf
//h = com.edfeff.ch02.PassReferences@723279cf
}
}
引用别名 多个引用指向同一个对象。
- 有引用写,有引用读,就会很容易导致意外情况发生。
- 所以在同一个作用域内,尽量不要使用多个别名,最好只用一个,减少理解和调试难度
- 方法的参数传递其实也是自动命名引用别名了,也导致可以修改外部对象
- 方法一般是为了获取返回值,或者改变方法调用者的状态,改变传递参数对象的状态是一个副作用,会带来隐患,最好避免在方法内修改参数的状态。
java
public class Alias1 {
private int i;
public Alias1(int i) {
this.i = i;
}
public static void f(Alias1 ref) {
ref.i++;
}
public static void main(String[] args) {
Alias1 x = new Alias1(7);
Alias1 y = x;
System.out.println("x=" + x.i);
System.out.println("y=" + y.i);
x.i++;
System.out.println("x=" + x.i);
System.out.println("y=" + y.i);
f(x);
System.out.println("x=" + x.i);
System.out.println("y=" + y.i);
}
}
java中的参数传递都是通过传递引用实现的。
- 引用别名在传递参数时自动发生
- 并没有本地对象,只有本地引用
- 引用有作用域,对象没有
2.2 创建本地副本
由于参数对象是传递引用,并且java并没有提供防止对象被修改的相关支持(比如const),所以有时为了避免引用别名的副作用,需要传递一个副本。
2.2.1 值传递
值传递的两种理解
- java传递的都是值。
- 传递基本类型就是单独的副本
- 传递引用就是引用的副本
- java传递基本类型是值,传递对象是引用
- 引用是对象的别名,传入方法时,并没有得到对象的副本,所以传递的不是对象的值
上面的观点都很有意思,但重要的是,要理解传递引用会使得外部对象被意外修改
2.2.2 克隆对象
需要修改对象,但不能影响外部对象,则需要副本。
- 使用clone()方法
- ArrayListd的clone方法没有复制内部的每一个对象,只是同一个对象的不同引用别名,称之为浅拷贝
- 深拷贝,创建所有内容的完整副本
java
class Int {
private int i;
public Int(int i) {
this.i = i;
}
public void increment() {
i++;
}
@Override
public String toString() {
return Integer.toString(i);
}
}
public class CloneArrayList {
public static void main(String[] args) {
ArrayList<Int> v = IntStream
.range(0, 10)
.mapToObj(Int::new)
.collect(Collectors.toCollection(ArrayList::new));
System.out.println("v=" + v);//v=[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
@SuppressWarnings("unchecked")
ArrayList<Int> clone = (ArrayList<Int>) v.clone();
clone.forEach(Int::increment);//v=[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
System.out.println("v=" + v);
}
}
2.2.3 为类增加可克隆能力
- 重写clone方法,修饰符改为public
- 实现Cloneable接口,没有实现该接口,调用clone时报错CloneNotSupportedException
2.2.4 成功的克隆
示例如下
- Object的clone方法返回类型是Object,需要向下转型为Duplo
java
class Duplo implements Cloneable {
private int n;
public Duplo(int n) {
this.n = n;
}
public Duplo clone() {
try {
return (Duplo) super.clone();
} catch (CloneNotSupportedException e) {
throw new RuntimeException();
}
}
public int getValue() {
return n;
}
public void setValue(int n) {
this.n = n;
}
public void increment() {
n++;
}
@Override
public String toString() {
return Integer.toString(n);
}
}
public class LocalCopy {
// 修改外部对象
public static Duplo g(Duplo v) {
v.increment();
return v;
}
// 创建本地副本并修改
public static Duplo f(Duplo v) {
v = v.clone();
v.increment();
return v;
}
public static void main(String[] args) {
Duplo a = new Duplo(11);
Duplo b = g(a);
System.out.println("a==b " + (a == b) + " a=" + a + " b=" + b);//a==b true a=12 b=12
Duplo c = new Duplo(47);
Duplo d = f(c);
System.out.println("c==d " + (c == d) + " c=" + c + " d=" + d);//c==d false c=47 d=48
}
}
2.2.5 Object.clone()的效果
下面的示例中,是一个单向链表,clone之后的s2只有第一段被复制了,也就是浅拷贝。 如果要实现深拷贝,需要在clone中做额外操作。
java
public class Snake implements Cloneable {
private Snake next;
private char c;
public Snake(int i, char x) {
c = x;
if (--i > 0) {
next = new Snake(i, (char) (x + 1));
}
}
public void increment() {
c++;
if (next != null) {
next.increment();
}
}
@Override
public String toString() {
String s = ":" + c;
if (next != null) {
s += next.toString();
}
return s;
}
@Override
public Snake clone() {
try {
return (Snake) super.clone();
} catch (CloneNotSupportedException e) {
throw new RuntimeException();
}
}
public static void main(String[] args) {
Snake s = new Snake(5, 'a');
System.out.println("s=" + s);
Snake s2 = s.clone();
System.out.println("s2=" + s2);
s.increment();
System.out.println("s2=" + s2);
}
}
2.2.6 克隆组合对象
深拷贝组合对象时,需要所有成员对象中的clone方法都按照顺序对各自的引用执行深拷贝。
- 这意味着为了正确的深拷贝,需要控制所有类的所有代码。
基础类
java
package com.edfeff.ch02;
public class TemperatureReading implements Cloneable {
private long time;
private double temperature;
public TemperatureReading(double temperature) {
this.time = System.currentTimeMillis();
this.temperature = temperature;
}
public double getTemperature() {
return temperature;
}
public void setTemperature(double temperature) {
this.temperature = temperature;
}
public TemperatureReading clone() {
try {
return (TemperatureReading) super.clone();
} catch (CloneNotSupportedException e) {
throw new RuntimeException();
}
}
@Override
public String toString() {
return String.valueOf(temperature);
}
}
typescript
public class DepthReading implements Cloneable {
private double depth;
public DepthReading(double d) {
depth = d;
}
public DepthReading clone() {
try {
return (DepthReading) super.clone();
} catch (CloneNotSupportedException e) {
throw new AssertionError();
}
}
public double getDepth() {
return depth;
}
public void setDepth(double depth) {
this.depth = depth;
}
@Override
public String toString() {
return String.valueOf(depth);
}
}
组合类
java
package com.edfeff.ch02;
public class OceanReading implements Cloneable {
private DepthReading depth;
private TemperatureReading temperature;
public OceanReading(double tdata, double ddata) {
temperature = new TemperatureReading(tdata);
depth = new DepthReading(ddata);
}
public OceanReading clone() {
OceanReading or = null;
try {
or = (OceanReading) super.clone();
} catch (CloneNotSupportedException e) {
throw new RuntimeException();
}
//引用克隆
or.depth = or.depth.clone();
or.temperature = or.temperature.clone();
return or;
}
public DepthReading getDepth() {
return depth;
}
public void setDepth(DepthReading depth) {
this.depth = depth;
}
public TemperatureReading getTemperature() {
return temperature;
}
public void setTemperature(TemperatureReading temperature) {
this.temperature = temperature;
}
@Override
public String toString() {
return "depth=" + depth + ", temperature=" + temperature;
}
public static void main(String[] args) {
OceanReading reading = new OceanReading(33.9, 100.5);
//
OceanReading clone = reading.clone();
TemperatureReading tr = clone.getTemperature();
tr.setTemperature(tr.getTemperature() + 1);
clone.setTemperature(tr);
DepthReading dr = clone.getDepth();
dr.setDepth(dr.getDepth() + 1);
clone.setDepth(dr);
System.out.println("reading=" + reading);
System.out.println("clone=" + clone);
// reading=depth=100.5, temperature=33.9
// clone=depth=101.5, temperature=34.9
}
}
2.2.7 深拷贝ArrayList
深拷贝ArrayList,需要克隆内部的每一个元素
java
class Int2 implements Cloneable {
private int i;
Int2(int i) {
this.i = i;
}
public void increment() {
i++;
}
@Override
public String toString() {
return Integer.toString(i);
}
@Override
public Int2 clone() {
try {
return (Int2) super.clone();
} catch (CloneNotSupportedException e) {
throw new RuntimeException();
}
}
}
//继承不会移除可克隆性
class Int3 extends Int2 {
private int j;
Int3(int i) {
super(i);
}
}
public class AddingClone {
public static void main(String[] args) {
Int2 x = new Int2(10);
Int2 x2 = x.clone();
x2.increment();
System.out.println("x=" + x + " x2=" + x2);//x=10 x2=11
//
Int3 x3 = new Int3(7);
x3 = (Int3) x3.clone();
ArrayList<Int2> v = IntStream.range(0, 10).mapToObj(Int2::new)
.collect(Collectors.toCollection(ArrayList::new));
System.out.println("v=" + v);//v=[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
ArrayList<Int2> v2 = (ArrayList<Int2>) v.clone();
//克隆每个元素
IntStream.range(0, v.size()).forEach(i -> v2.set(i, v.get(i).clone()));
//递增v2
v2.forEach(Int2::increment);
System.out.println("v2=" + v2); //v2=[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
System.out.println("v=" + v); //v=[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
}
}
2.2.8 通过序列化进行深拷贝
对一个对象先进行序列化,再将其反序列化,那么它实际上就是被克隆了。
- 序列化至少比克隆慢一个数量级
java
class Thing1 implements Serializable {}
class Thing2 implements Serializable {
Thing1 t1 = new Thing1();
}
class Thing3 implements Cloneable{
@Override
public Thing3 clone() throws CloneNotSupportedException {
return (Thing3) super.clone();
}
}
class Thing4 implements Cloneable {
Thing3 t3 = new Thing3();
@Override
public Thing4 clone() throws CloneNotSupportedException {
Thing4 t4 = (Thing4) super.clone();
t4.t3 = t3.clone();
return t4;
}
}
public class Compete {
static final int SIZE = 1000000;
public static void main(String[] args) throws IOException, ClassNotFoundException, CloneNotSupportedException {
Thing2[] a = new Thing2[SIZE];
for (int i = 0; i < SIZE; i++) {
a[i] = new Thing2();
}
Thing4[] b = new Thing4[SIZE];
for (int i = 0; i < SIZE; i++) {
b[i] = new Thing4();
}
long start = System.currentTimeMillis();
ByteArrayOutputStream buf = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(buf);
for (Thing2 a1 : a) {
oos.writeObject(a1);
}
ObjectInputStream in = new ObjectInputStream(new ByteArrayInputStream((buf.toByteArray())));
Thing2[] c = new Thing2[SIZE];
for (int i = 0; i < SIZE; i++) {
c[i]= (Thing2) in.readObject();
}
System.out.println("serial time: " + ((System.currentTimeMillis() - start)));
//克隆时间测试
start = System.currentTimeMillis();
Thing4[] d = new Thing4[SIZE];
for (int i = 0; i < SIZE; i++) {
d[i] = b[i].clone();
}
System.out.println("clone time: " + ((System.currentTimeMillis() - start)));
// serial time: 1155
// clone time: 25
}
}
2.2.9 在继承层次结构中增加可克隆性并向下覆盖
默认对象是不具备克隆性的,只有实现了Cloneable接口并重写clone方法后,该类及其子类就具有了可克隆性
java
class Person{}
class Hero extends Person{}
class Scientist extends Person implements Cloneable{
@Override
public Scientist clone() throws CloneNotSupportedException {
return (Scientist) super.clone();
}
}
class MadScientist extends Scientist{}
public class HorrorFlick {
public static void main(String[] args) throws CloneNotSupportedException {
Person p = new Person();
Hero h = new Hero();
Scientist s = new Scientist();
MadScientist ms = new MadScientist();
s = s.clone();
ms = (MadScientist) ms.clone();
}
}
2.2.10 为什么用这种奇怪的设计
Object的clone方法一开始是public的,由于被互联网广泛应用后,安全问题突出,任意复制与安全相关的对象是危险的。所以clone变成了protected,必须实现cloneable接口和重写clone方法。
2.3 控制可克隆性
2.4 不可变类
创建只读的不可变对象,外部引用不会修改该对象的任何状态。
2.4.1 创建不可变类
data 是private的,不提供public的方法来修改该data
- multiply并没有修改data,而是直接返回一个新对象
csharp
public class Immutable1 {
private int data;
public Immutable1(int data) {
this.data = data;
}
public int read() {
return data;
}
public boolean nonzero() {
return data != 0;
}
public Immutable1 multiply(int value) {
return new Immutable1(data * value);
}
public static void f(Immutable1 i) {
Immutable1 quad = i.multiply(4);
System.out.println("i=" + i.read());
System.out.println("quad=" + quad.read());
}
public static void main(String[] args) {
Immutable1 x = new Immutable1(47);
System.out.println("x=" + x.read());
f(x);
System.out.println("x=" + x.read());
//x=47
//i=47
//quad=188
//x=47
}
}
2.4.2 不可变性的缺点
- 创建新对象的性能开销
- 更频繁的GC
解决方法是创建可修改的伴生类,使用伴生类修改,完成后切换回不可变类
- 需要不可变类,同时需要频繁修改操作,或者创建不可变对象成本高
csharp
class Mutable {
private int data;
public Mutable(int data) {
this.data = data;
}
public Mutable add(int x) {
data += x;
return this;
}
public Mutable multiply(int x) {
data *= x;
return this;
}
public Immutable2 makeImmutable() {
return new Immutable2(data);
}
}
public class Immutable2 {
private int data;
public Immutable2(int data) {
this.data = data;
}
public int read() {
return data;
}
public Immutable2 add(int x) {
return new Immutable2(data + x);
}
public Immutable2 multiply(int x) {
return new Immutable2(data * x);
}
public Mutable makeMutable() {
return new Mutable(data);
}
public static Immutable2 modify1(Immutable2 y) {
Immutable2 val = y.add(12);
val = val.multiply(3);
val = val.add(11);
val = val.multiply(2);
return val;
}
public static Immutable2 modify2(Immutable2 y) {
// 切换到可变伴生类
Mutable val = y.makeMutable();
val = val.add(12)
.multiply(3)
.add(11)
.multiply(2);
// 切换到不可变类
return val.makeImmutable();
}
public static void main(String[] args) {
Immutable2 i2 = new Immutable2(47);
Immutable2 r1 = modify1(i2);
Immutable2 r2 = modify2(i2);
System.out.println("i2=" + i2.read());
System.out.println("r1=" + r1.read());
System.out.println("r2=" + r2.read());
//i2=47
//r1=376
//r2=376
}
}
2.4.3 String很特殊
String本质是不可变的。修改String会返回新String对象。