💎一站式轻松地调用各大LLM模型接口,支持GPT4、智谱、星火、月之暗面及文生图 广告
## [组合语法](https://lingcoder.gitee.io/onjava8/#/book/08-Reuse?id=%e7%bb%84%e5%90%88%e8%af%ad%e6%b3%95) 在前面的学习中,“组合”(Composition)已经被多次使用。你仅需要把对象的引用(object references)放置在一个新的类里,这就使用了组合。例如,假设你需要一个对象,其中内置了几个**String**对象,两个基本类型(primitives)的属性字段,一个其他类的对象。对于非基本类型对象,将引用直接放置在新类中,对于基本类型属性字段则仅进行声明。 ~~~ // reuse/SprinklerSystem.java // (c)2017 MindView LLC: see Copyright.txt // We make no guarantees that this code is fit for any purpose. // Visit http://OnJava8.com for more book information. // Composition for code reuse class WaterSource { private String s; WaterSource() { System.out.println("WaterSource()"); s = "Constructed"; } @Override public String toString() { return s; } } public class SprinklerSystem { private String valve1, valve2, valve3, valve4; private WaterSource source = new WaterSource(); private int i; private float f; @Override public String toString() { return "valve1 = " + valve1 + " " + "valve2 = " + valve2 + " " + "valve3 = " + valve3 + " " + "valve4 = " + valve4 + "\n" + "i = " + i + " " + "f = " + f + " " + "source = " + source; // [1] } public static void main(String[] args) { SprinklerSystem sprinklers = new SprinklerSystem(); System.out.println(sprinklers); } } /* Output: WaterSource() valve1 = null valve2 = null valve3 = null valve4 = null i = 0 f = 0.0 source = Constructed */ ~~~ 这两个类中定义的一个方法是特殊的:`toString()`。每个非基本类型对象都有一个`toString()`方法,在编译器需要字符串但它有对象的特殊情况下调用该方法。因此,在 \[1\] 中,编译器看到你试图“添加”一个**WaterSource**类型的字符串对象 。因为字符串只能拼接另一个字符串,所以它就先会调用`toString()`将**source**转换成一个字符串。然后,它可以拼接这两个字符串并将结果字符串传递给`System.out.println()`。要对创建的任何类允许这种行为,只需要编写一个**toString()**方法。在`toString()`上使用**@Override**注释来告诉编译器,以确保正确地覆盖。\*\*@Override\*\* 是可选的,但它有助于验证你没有拼写错误 (或者更微妙地说,大小写字母输入错误)。类中的基本类型字段自动初始化为零,正如**object Everywhere**一章中所述。但是对象引用被初始化为**null**,如果你尝试调用其任何一个方法,你将得到一个异常(一个运行时错误)。方便的是,打印**null**引用却不会得到异常。 编译器不会为每个引用创建一个默认对象,这是有意义的,因为在许多情况下,这会导致不必要的开销。初始化引用有四种方法: 1. 当对象被定义时。这意味着它们总是在调用构造函数之前初始化。 2. 在该类的构造函数中。 3. 在实际使用对象之前。这通常称为*延迟初始化*。在对象创建开销大且不需要每次都创建对象的情况下,它可以减少开销。 4. 使用实例初始化。 以上四种实例创建的方法例子在这: ~~~ // reuse/Bath.java // (c)2017 MindView LLC: see Copyright.txt // We make no guarantees that this code is fit for any purpose. // Visit http://OnJava8.com for more book information. // Constructor initialization with composition class Soap { private String s; Soap() { System.out.println("Soap()"); s = "Constructed"; } @Override public String toString() { return s; } } public class Bath { private String // Initializing at point of definition: s1 = "Happy", s2 = "Happy", s3, s4; private Soap castille; private int i; private float toy; public Bath() { System.out.println("Inside Bath()"); s3 = "Joy"; toy = 3.14f; castille = new Soap(); } // Instance initialization: { i = 47; } @Override public String toString() { if(s4 == null) // Delayed initialization: s4 = "Joy"; return "s1 = " + s1 + "\n" + "s2 = " + s2 + "\n" + "s3 = " + s3 + "\n" + "s4 = " + s4 + "\n" + "i = " + i + "\n" + "toy = " + toy + "\n" + "castille = " + castille; } public static void main(String[] args) { Bath b = new Bath(); System.out.println(b); } } /* Output: Inside Bath() Soap() s1 = Happy s2 = Happy s3 = Joy s4 = Joy i = 47 toy = 3.14 castille = Constructed */ ~~~ 在**Bath**构造函数中,有一个代码块在所有初始化发生前就已经执行了。当你不在定义处初始化时,仍然不能保证在向对象引用发送消息之前执行任何初始化——如果你试图对未初始化的引用调用方法,则未初始化的引用将产生运行时异常。 当调用`toString()`时,它将赋值 s4,以便在使用字段的时候所有的属性都已被初始化。