原型模式
用一个已经创建的实例作为原型,通过复制该原型对象来创建一个和原型相同或相似的新对象。使用这种方式创建对象非常高效,根本无需知道对象创建的细节。
这让我想到了docker的镜像
Java中的原型模式
Java自带了原型模式clone()方法,但是使用clone()方法有两个条件
- 重写父类(Object类)的
clone
方法,将clone
设置为public
的 - 实现标记性接口
Cloneable
简单的例子
public class PrototyObj implements Cloneable{
String str;
Integer intVal;
MyObj myObj;
@Override
public PrototyObj clone() throws CloneNotSupportedException {
return (PrototyObj) super.clone();
}
}
public class MyObj {
String code;
String title;
}
public class PrototypeMain {
public static void main(String[] args) throws CloneNotSupportedException {
PrototyObj prototyObj = new PrototyObj();
prototyObj.intVal=88;
prototyObj.str="88";
MyObj myObj = new MyObj();
myObj.code="zhangsan";
myObj.title="张三";
prototyObj.myObj=myObj;
PrototyObj cloneObj = prototyObj.clone();
System.out.println("(cloneObj==prototyObj) = " + (cloneObj == prototyObj));
System.out.println("(cloneObj.intVal== prototyObj.intVal) = " + (cloneObj.intVal == prototyObj.intVal));
System.out.println("(cloneObj.str==prototyObj.str) = " + (cloneObj.str == prototyObj.str));
}
}
然后我们看下运行结果:
从结果中可以看到,cloneObj
中所有的属性都和prototyObj
是一样的。也就是我们成功的创建了一个prototyObj的克隆对象。
浅拷贝
在上面的例子中,由于PrototyObj的myObj属性不是8大基本类型,所以cloneObj
中的myObj和prototyObj
中的myObj是相等的,也就是指向了同一个内存地址。这种拷贝被称为浅拷贝。
由于克隆对象和原始对象都指向了同一个myObj
,所以一旦原始对象的myObj发生改变,克隆对象的myObj也会对应着发生变化,就像下面这样:
public class PrototypeMain {
public static void main(String[] args) throws CloneNotSupportedException {
PrototyObj prototyObj = new PrototyObj();
prototyObj.intVal=88;
prototyObj.str="88";
MyObj myObj = new MyObj();
myObj.code="zhangsan";
myObj.title="张三";
prototyObj.myObj=myObj;
PrototyObj cloneObj = prototyObj.clone();
System.out.println("(cloneObj==prototyObj) = " + (cloneObj == prototyObj));
System.out.println("(cloneObj.myObj==prototyObj.myObj) = " + (cloneObj.myObj == prototyObj.myObj));
//浅拷贝中修改原始对象的myObj的属性将会影响拷贝对象的myObj的属性
prototyObj.myObj.code="lisi";
prototyObj.myObj.title="李四";
System.out.println("cloneObj.myObj.code = " + cloneObj.myObj.code);
System.out.println("cloneObj.myObj.title = " + cloneObj.myObj.title);
}
}
看下运行结果,果然克隆对象的myObj
的属性也受到了影响
深拷贝
浅拷贝中发生的问题算不上是问题,很多时候我们就是需要一个浅拷贝即可。
那么如何避免上面的问题呢?答案很简单,将myObj也复制一份,当然,我们需要对源码进行修改
- 首先,MyObj对象也需要实现Cloneable接口
- PrototyObj在进行复制时,需要额外处理一下myObj属性
然后, 我们再跑一次程序,就得到了如下的结果:
从结果中可以看到,克隆对象的myObj属性和原始对象的myObj属性已经不是指向同一个MyObj对象了。所以当原始对象的myObj属性发生改变后,克隆对象并没有收到影响。
所谓深拷贝就是复制出来的对象的每个属性以及属性的属性都和原始对象不是同一个对象。
效率比较
原型模式有两个点:
- 方便的创建对象-------->通过clone来创建对象确实方便
- 高效-------->clone比我们直接new对象更高效吗?
是时候拿出我们的JMH跑一跑了
JMH测试样例
@BenchmarkMode({Mode.SampleTime})
@OutputTimeUnit(TimeUnit.NANOSECONDS)
@Warmup(time = 2, batchSize = 100)
@Measurement(time = 2, batchSize = 500)
@Fork(2)
@Threads(10)
@State(value = Scope.Benchmark)
public class PrototypeTest {
private PrototyObj prototyObj;
public static void main(String[] args) throws RunnerException {
Options opt = new OptionsBuilder()
.include(PrototypeTest.class.getSimpleName())
.result("PrototypeTest_result.json")
.resultFormat(ResultFormatType.JSON).build();
new Runner(opt).run();
}
@Setup
public void init() {
prototyObj = new PrototyObj();
prototyObj.intVal=88;
prototyObj.str="88";
MyObj myObj = new MyObj();
myObj.code="zhangsan";
myObj.title="张三";
prototyObj.myObj = myObj;
}
@Benchmark
public PrototyObj testClone() throws CloneNotSupportedException {
return prototyObj.clone();
}
@Benchmark
public PrototyObj testNew() {
PrototyObj clone = new PrototyObj();
clone.intVal = prototyObj.intVal;
clone.str=prototyObj.str;
MyObj oriMyObj = prototyObj.myObj;
MyObj myObj = new MyObj();
myObj.code = oriMyObj.code;
myObj.title = oriMyObj.title;
clone.myObj =myObj;
return clone;
}
}
测试结果
# JMH version: 1.33
# VM version: JDK 1.8.0_261, Java HotSpot(TM) 64-Bit Server VM, 25.261-b12
# VM invoker: C:\Program Files\Java\jdk1.8.0_261\jre\bin\java.exe
# VM options: -javaagent:D:\jetbrains\IntelliJ IDEA 2021.2\lib\idea_rt.jar=56720:D:\jetbrains\IntelliJ IDEA 2021.2\bin -Dfile.encoding=UTF-8
# Blackhole mode: full + dont-inline hint (default, use -Djmh.blackhole.autoDetect=true to auto-detect)
# Warmup: 5 iterations, 2 s each, 100 calls per op
# Measurement: 5 iterations, 2 s each, 500 calls per op
# Timeout: 10 min per iteration
# Threads: 10 threads, will synchronize iterations
# Benchmark mode: Sampling time
# Benchmark: com.example.jmh.PrototypeTest.testClone
# Run progress: 0.00% complete, ETA 00:01:20
# Fork: 1 of 2
# Warmup Iteration 1: 5518.031 ±(99.9%) 264.531 ns/op
# Warmup Iteration 2: 5658.041 ±(99.9%) 1082.759 ns/op
# Warmup Iteration 3: 6559.181 ±(99.9%) 627.511 ns/op
# Warmup Iteration 4: 4881.049 ±(99.9%) 368.714 ns/op
# Warmup Iteration 5: 7646.121 ±(99.9%) 1032.180 ns/op
Iteration 1: 22776.367 ±(99.9%) 916.867 ns/op
testClone·p0.00: 6800.000 ns/op
testClone·p0.50: 15488.000 ns/op
testClone·p0.90: 19072.000 ns/op
testClone·p0.95: 25184.000 ns/op
testClone·p0.99: 123648.000 ns/op
testClone·p0.999: 988408.832 ns/op
testClone·p0.9999: 7048259.174 ns/op
testClone·p1.00: 43319296.000 ns/op
Iteration 2: 24346.085 ±(99.9%) 1974.777 ns/op
testClone·p0.00: 6696.000 ns/op
testClone·p0.50: 15392.000 ns/op
testClone·p0.90: 19072.000 ns/op
testClone·p0.95: 25472.000 ns/op
testClone·p0.99: 121856.000 ns/op
testClone·p0.999: 1094416.384 ns/op
testClone·p0.9999: 11166567.629 ns/op
testClone·p1.00: 157548544.000 ns/op
Iteration 3: 22157.449 ±(99.9%) 675.149 ns/op
testClone·p0.00: 6800.000 ns/op
testClone·p0.50: 15600.000 ns/op
testClone·p0.90: 18592.000 ns/op
testClone·p0.95: 24480.000 ns/op
testClone·p0.99: 115467.520 ns/op
testClone·p0.999: 1071736.832 ns/op
testClone·p0.9999: 7350516.122 ns/op
testClone·p1.00: 21364736.000 ns/op
Iteration 4: 20359.747 ±(99.9%) 498.717 ns/op
testClone·p0.00: 6600.000 ns/op
testClone·p0.50: 15600.000 ns/op
testClone·p0.90: 19200.000 ns/op
testClone·p0.95: 24672.000 ns/op
testClone·p0.99: 105984.000 ns/op
testClone·p0.999: 577536.000 ns/op
testClone·p0.9999: 3781365.760 ns/op
testClone·p1.00: 25559040.000 ns/op
Iteration 5: 23996.060 ±(99.9%) 1572.413 ns/op
testClone·p0.00: 6696.000 ns/op
testClone·p0.50: 15488.000 ns/op
testClone·p0.90: 18688.000 ns/op
testClone·p0.95: 24672.000 ns/op
testClone·p0.99: 120720.640 ns/op
testClone·p0.999: 1130932.224 ns/op
testClone·p0.9999: 10015722.701 ns/op
testClone·p1.00: 110231552.000 ns/op
# Run progress: 25.00% complete, ETA 00:01:06
# Fork: 2 of 2
# Warmup Iteration 1: 5691.404 ±(99.9%) 364.499 ns/op
# Warmup Iteration 2: 3977.670 ±(99.9%) 174.700 ns/op
# Warmup Iteration 3: 4107.415 ±(99.9%) 105.080 ns/op
# Warmup Iteration 4: 6307.642 ±(99.9%) 387.018 ns/op
# Warmup Iteration 5: 5057.968 ±(99.9%) 196.002 ns/op
Iteration 1: 18594.193 ±(99.9%) 193.192 ns/op
testClone·p0.00: 6400.000 ns/op
testClone·p0.50: 15792.000 ns/op
testClone·p0.90: 18080.000 ns/op
testClone·p0.95: 23200.000 ns/op
testClone·p0.99: 88832.000 ns/op
testClone·p0.999: 430985.728 ns/op
testClone·p0.9999: 1875542.221 ns/op
testClone·p1.00: 6479872.000 ns/op
Iteration 2: 17515.850 ±(99.9%) 249.191 ns/op
testClone·p0.00: 6200.000 ns/op
testClone·p0.50: 15888.000 ns/op
testClone·p0.90: 17696.000 ns/op
testClone·p0.95: 20576.000 ns/op
testClone·p0.99: 49344.000 ns/op
testClone·p0.999: 334231.040 ns/op
testClone·p0.9999: 2072817.664 ns/op
testClone·p1.00: 18186240.000 ns/op
Iteration 3: 17655.096 ±(99.9%) 216.672 ns/op
testClone·p0.00: 6400.000 ns/op
testClone·p0.50: 16000.000 ns/op
testClone·p0.90: 17696.000 ns/op
testClone·p0.95: 20288.000 ns/op
testClone·p0.99: 59722.240 ns/op
testClone·p0.999: 377344.000 ns/op
testClone·p0.9999: 1784104.960 ns/op
testClone·p1.00: 7839744.000 ns/op
Iteration 4: 17349.219 ±(99.9%) 190.369 ns/op
testClone·p0.00: 6400.000 ns/op
testClone·p0.50: 16000.000 ns/op
testClone·p0.90: 17696.000 ns/op
testClone·p0.95: 19872.000 ns/op
testClone·p0.99: 50240.000 ns/op
testClone·p0.999: 330240.000 ns/op
testClone·p0.9999: 1307788.493 ns/op
testClone·p1.00: 10059776.000 ns/op
Iteration 5: 17475.811 ±(99.9%) 173.673 ns/op
testClone·p0.00: 6400.000 ns/op
testClone·p0.50: 15888.000 ns/op
testClone·p0.90: 17696.000 ns/op
testClone·p0.95: 20480.000 ns/op
testClone·p0.99: 56256.000 ns/op
testClone·p0.999: 325248.000 ns/op
testClone·p0.9999: 1695232.000 ns/op
testClone·p1.00: 5046272.000 ns/op
Result "com.example.jmh.PrototypeTest.testClone":
N = 4893390
mean = 20186.672 ±(99.9%) 272.095 ns/op
Histogram, ns/op:
[ 0.000, 12500000.000) = 4893281
[ 12500000.000, 25000000.000) = 74
[ 25000000.000, 37500000.000) = 20
[ 37500000.000, 50000000.000) = 6
[ 50000000.000, 62500000.000) = 2
[ 62500000.000, 75000000.000) = 1
[ 75000000.000, 87500000.000) = 2
[ 87500000.000, 100000000.000) = 1
[100000000.000, 112500000.000) = 2
[112500000.000, 125000000.000) = 0
[125000000.000, 137500000.000) = 0
[137500000.000, 150000000.000) = 0
[150000000.000, 162500000.000) = 1
[162500000.000, 175000000.000) = 0
[175000000.000, 187500000.000) = 0
Percentiles, ns/op:
p(0.0000) = 6200.000 ns/op
p(50.0000) = 15696.000 ns/op
p(90.0000) = 18176.000 ns/op
p(95.0000) = 23200.000 ns/op
p(99.0000) = 90496.000 ns/op
p(99.9000) = 635904.000 ns/op
p(99.9900) = 4303436.186 ns/op
p(99.9990) = 20862104.371 ns/op
p(99.9999) = 84163589.832 ns/op
p(100.0000) = 157548544.000 ns/op
# JMH version: 1.33
# VM version: JDK 1.8.0_261, Java HotSpot(TM) 64-Bit Server VM, 25.261-b12
# VM invoker: C:\Program Files\Java\jdk1.8.0_261\jre\bin\java.exe
# VM options: -javaagent:D:\jetbrains\IntelliJ IDEA 2021.2\lib\idea_rt.jar=56720:D:\jetbrains\IntelliJ IDEA 2021.2\bin -Dfile.encoding=UTF-8
# Blackhole mode: full + dont-inline hint (default, use -Djmh.blackhole.autoDetect=true to auto-detect)
# Warmup: 5 iterations, 2 s each, 100 calls per op
# Measurement: 5 iterations, 2 s each, 500 calls per op
# Timeout: 10 min per iteration
# Threads: 10 threads, will synchronize iterations
# Benchmark mode: Sampling time
# Benchmark: com.example.jmh.PrototypeTest.testNew
# Run progress: 50.00% complete, ETA 00:00:43
# Fork: 1 of 2
# Warmup Iteration 1: 5233.929 ±(99.9%) 406.860 ns/op
# Warmup Iteration 2: 4409.203 ±(99.9%) 355.687 ns/op
# Warmup Iteration 3: 4140.384 ±(99.9%) 191.148 ns/op
# Warmup Iteration 4: 4425.205 ±(99.9%) 274.721 ns/op
# Warmup Iteration 5: 4703.808 ±(99.9%) 315.689 ns/op
Iteration 1: 20516.239 ±(99.9%) 1245.102 ns/op
testNew·p0.00: 4000.000 ns/op
testNew·p0.50: 12800.000 ns/op
testNew·p0.90: 17184.000 ns/op
testNew·p0.95: 21888.000 ns/op
testNew·p0.99: 125696.000 ns/op
testNew·p0.999: 1052672.000 ns/op
testNew·p0.9999: 7712489.472 ns/op
testNew·p1.00: 115474432.000 ns/op
Iteration 2: 18789.290 ±(99.9%) 721.128 ns/op
testNew·p0.00: 3800.000 ns/op
testNew·p0.50: 13600.000 ns/op
testNew·p0.90: 16896.000 ns/op
testNew·p0.95: 22272.000 ns/op
testNew·p0.99: 88832.000 ns/op
testNew·p0.999: 773120.000 ns/op
testNew·p0.9999: 5905665.229 ns/op
testNew·p1.00: 43646976.000 ns/op
Iteration 3: 18495.429 ±(99.9%) 579.992 ns/op
testNew·p0.00: 3900.000 ns/op
testNew·p0.50: 13296.000 ns/op
testNew·p0.90: 16384.000 ns/op
testNew·p0.95: 19584.000 ns/op
testNew·p0.99: 93440.000 ns/op
testNew·p0.999: 899610.624 ns/op
testNew·p0.9999: 4818875.187 ns/op
testNew·p1.00: 29523968.000 ns/op
Iteration 4: 18666.115 ±(99.9%) 588.180 ns/op
testNew·p0.00: 3800.000 ns/op
testNew·p0.50: 13888.000 ns/op
testNew·p0.90: 16896.000 ns/op
testNew·p0.95: 21088.000 ns/op
testNew·p0.99: 87808.000 ns/op
testNew·p0.999: 751616.000 ns/op
testNew·p0.9999: 5836304.384 ns/op
testNew·p1.00: 27951104.000 ns/op
Iteration 5: 20645.492 ±(99.9%) 1139.081 ns/op
testNew·p0.00: 3900.000 ns/op
testNew·p0.50: 12800.000 ns/op
testNew·p0.90: 16288.000 ns/op
testNew·p0.95: 21984.000 ns/op
testNew·p0.99: 126325.760 ns/op
testNew·p0.999: 1158332.416 ns/op
testNew·p0.9999: 8136110.899 ns/op
testNew·p1.00: 50987008.000 ns/op
# Run progress: 75.00% complete, ETA 00:00:21
# Fork: 2 of 2
# Warmup Iteration 1: 5381.014 ±(99.9%) 345.255 ns/op
# Warmup Iteration 2: 5295.847 ±(99.9%) 567.626 ns/op
# Warmup Iteration 3: 5559.507 ±(99.9%) 442.106 ns/op
# Warmup Iteration 4: 4024.242 ±(99.9%) 489.348 ns/op
# Warmup Iteration 5: 4709.293 ±(99.9%) 412.818 ns/op
Iteration 1: 18146.819 ±(99.9%) 671.877 ns/op
testNew·p0.00: 3700.000 ns/op
testNew·p0.50: 14192.000 ns/op
testNew·p0.90: 16480.000 ns/op
testNew·p0.95: 18592.000 ns/op
testNew·p0.99: 89088.000 ns/op
testNew·p0.999: 680060.928 ns/op
testNew·p0.9999: 3419793.818 ns/op
testNew·p1.00: 58195968.000 ns/op
Iteration 2: 18237.222 ±(99.9%) 349.829 ns/op
testNew·p0.00: 3900.000 ns/op
testNew·p0.50: 14096.000 ns/op
testNew·p0.90: 16992.000 ns/op
testNew·p0.95: 20480.000 ns/op
testNew·p0.99: 100096.000 ns/op
testNew·p0.999: 741957.632 ns/op
testNew·p0.9999: 3071115.264 ns/op
testNew·p1.00: 13877248.000 ns/op
Iteration 3: 18822.709 ±(99.9%) 451.900 ns/op
testNew·p0.00: 3800.000 ns/op
testNew·p0.50: 14192.000 ns/op
testNew·p0.90: 17088.000 ns/op
testNew·p0.95: 23488.000 ns/op
testNew·p0.99: 107520.000 ns/op
testNew·p0.999: 762880.000 ns/op
testNew·p0.9999: 3489378.304 ns/op
testNew·p1.00: 23003136.000 ns/op
Iteration 4: 20049.312 ±(99.9%) 1085.889 ns/op
testNew·p0.00: 4000.000 ns/op
testNew·p0.50: 13296.000 ns/op
testNew·p0.90: 16288.000 ns/op
testNew·p0.95: 19296.000 ns/op
testNew·p0.99: 95744.000 ns/op
testNew·p0.999: 909457.408 ns/op
testNew·p0.9999: 10102367.846 ns/op
testNew·p1.00: 45875200.000 ns/op
Iteration 5: 18218.396 ±(99.9%) 375.050 ns/op
testNew·p0.00: 3900.000 ns/op
testNew·p0.50: 14800.000 ns/op
testNew·p0.90: 16896.000 ns/op
testNew·p0.95: 18976.000 ns/op
testNew·p0.99: 82798.080 ns/op
testNew·p0.999: 762208.256 ns/op
testNew·p0.9999: 2597145.805 ns/op
testNew·p1.00: 12812288.000 ns/op
Result "com.example.jmh.PrototypeTest.testNew":
N = 5052013
mean = 19049.281 ±(99.9%) 246.082 ns/op
Histogram, ns/op:
[ 0.000, 12500000.000) = 5051859
[ 12500000.000, 25000000.000) = 115
[ 25000000.000, 37500000.000) = 27
[ 37500000.000, 50000000.000) = 7
[ 50000000.000, 62500000.000) = 4
[ 62500000.000, 75000000.000) = 0
[ 75000000.000, 87500000.000) = 0
[ 87500000.000, 100000000.000) = 0
[100000000.000, 112500000.000) = 0
[112500000.000, 125000000.000) = 1
[125000000.000, 137500000.000) = 0
[137500000.000, 150000000.000) = 0
[150000000.000, 162500000.000) = 0
[162500000.000, 175000000.000) = 0
[175000000.000, 187500000.000) = 0
Percentiles, ns/op:
p(0.0000) = 3700.000 ns/op
p(50.0000) = 13696.000 ns/op
p(90.0000) = 16800.000 ns/op
p(95.0000) = 20800.000 ns/op
p(99.0000) = 99712.000 ns/op
p(99.9000) = 850944.000 ns/op
p(99.9900) = 5251014.656 ns/op
p(99.9990) = 22952004.157 ns/op
p(99.9999) = 50443805.260 ns/op
p(100.0000) = 115474432.000 ns/op
# Run complete. Total time: 00:01:26
REMEMBER: The numbers below are just data. To gain reusable insights, you need to follow up on
why the numbers are the way they are. Use profilers (see -prof, -lprof), design factorial
experiments, perform baseline and negative tests that provide experimental control, make sure
the benchmarking environment is safe on JVM/OS/HW level, ask for reviews from the domain experts.
Do not assume the numbers tell you what you want them to tell.
Benchmark Mode Cnt Score Error Units
PrototypeTest.testClone sample 4893390 20186.672 ± 272.095 ns/op
PrototypeTest.testClone:testClone·p0.00 sample 6200.000 ns/op
PrototypeTest.testClone:testClone·p0.50 sample 15696.000 ns/op
PrototypeTest.testClone:testClone·p0.90 sample 18176.000 ns/op
PrototypeTest.testClone:testClone·p0.95 sample 23200.000 ns/op
PrototypeTest.testClone:testClone·p0.99 sample 90496.000 ns/op
PrototypeTest.testClone:testClone·p0.999 sample 635904.000 ns/op
PrototypeTest.testClone:testClone·p0.9999 sample 4303436.186 ns/op
PrototypeTest.testClone:testClone·p1.00 sample 157548544.000 ns/op
PrototypeTest.testNew sample 5052013 19049.281 ± 246.082 ns/op
PrototypeTest.testNew:testNew·p0.00 sample 3700.000 ns/op
PrototypeTest.testNew:testNew·p0.50 sample 13696.000 ns/op
PrototypeTest.testNew:testNew·p0.90 sample 16800.000 ns/op
PrototypeTest.testNew:testNew·p0.95 sample 20800.000 ns/op
PrototypeTest.testNew:testNew·p0.99 sample 99712.000 ns/op
PrototypeTest.testNew:testNew·p0.999 sample 850944.000 ns/op
PrototypeTest.testNew:testNew·p0.9999 sample 5251014.656 ns/op
PrototypeTest.testNew:testNew·p1.00 sample 115474432.000 ns/op
从结果上看,在我的测试对象上看new的效率与clone的效率基本持平。不过这样也很不错了,对于属性非常多的对象,可以安心的使用clone方法而不用担心效率问题了。