引用/指针与对象
您的术语“已保存”没有技术含义,可能会让您感到困惑。
让我们变得更具体。将 A 类替换为 Cat 类。将 B 类替换为 Breed 类。
假设在实例化Cat 时,我们还实例化了该Cat 对象中的Breed 对象。换句话说,在这个例子中,我们不在做lazy-loading。所以在实例化Cat 时,我们将Breed 的实例传递给构造函数。
两行
Cat a 变量不包含 Cat 对象,它包含对在内存中其他位置构造的 Cat 对象的引用(指针)。 Cat 对象在内存中的位置,基本上是一个数字,是 Cat a 所持有的。
Cat a = new Cat( "Fluffy" , new Breed( "Tabby" ) ) ; Passing new instance of `Breed` to constructor of `Cat`, with resulting `Cat` object’s location in memory being returned to the `a` reference/pointer variable.
你可以把那行代码想象成这样:
Pointer-holding-memory-location-of-object-of-class-Cat a = Instantiate-a-new-Cat-object-somewhere-in-memory-and-return-its-memory-location( … ) ;
概念图:
请注意,访问Breed 对象的唯一方法是通过Cat 对象Fluffy。我们的代码可以访问该Cat 对象的唯一方法是通过名为a 的引用/指针变量。
Breed b = a.getBreed() ; // Accessing the `Cat` instance, then traversing to the `Breed` object.
在这段代码之后,我们现在分配了一点内存来保存另一个指针/引用,这个变量名为b。 b 变量既不是Cat 也不是Breed,它是在内存中其他位置找到Breed 对象的内存位置。我们现在可以直接访问Breed 对象,而无需通过Cat 实例。
概念图:
一行
您的其他代码:
B b = new A().getB() ;
...转换为我们的示例:
Breed b = new Cat( "Fluffy" , new Breed( "Tabby" ) ).getBreed() ;
...非常相似,但从未将引用a 建立为命名变量。参考是生成的,至少在概念上是这样(实际上JVM可能有优化)。简短的临时引用用于立即调用getBreed 方法。
在获得Breed 对象的内存位置后,对新Cat 对象的临时引用被释放,新Cat 对象超出范围。从技术上讲,新的Cat 对象可能仍会在内存中浮动片刻,成为garbage-collection 的候选对象,因为它会从内存中清除。
当然,上面看到的使用Cat 和Breed 的新单行代码变得很傻:我们实例化一个新的Breed,将它传递给Cat 的构造函数,然后立即要求取回它。为了这个内存管理的讨论,让我们忽略尴尬的愚蠢。
概念图:
至于内存管理,在两种情况下可能会占用相同数量的内存(取决于您的 JVM 实现)。这两种情况都是建立一对对象和一对引用。一个细微的区别是,在您的第二种情况下,Cat 对象及其临时指针立即超出范围,并成为垃圾收集的候选对象。因此,内存可能会比您的第一个场景更快地被回收。在实践中,差异可能是微不足道的。
不要微优化
正如其他人评论的那样,所有这些都只是学术性的。您通常不应该担心这种微优化。
现代编译器和 JVM 的优化非常好,是有史以来优化和测试最好的大型软件项目。所以编写Java代码的最好方法就是简单。为人类编写简单易读和易于理解的代码,您还将编写最有可能被编译器和 JVM 优化的代码。具有讽刺意味的是,试图通过超越编译器和 JVM 来变得聪明可能会适得其反,从而导致代码的优化程度较低。