type information - Using Class Literals

使用类字面量重构PetCreator
本文介绍了如何使用类字面量重实现PetCreator,这种方法在很多方面使代码更简洁。通过在OnJava8-Examples目录下运行PetCount2,并对比输出结果,可以展示类字面量的优势。

previous article

If we reimplement PetCreator using lcass literals, the result is cleaner in many ways:

// typeinfo/pets/LiteralPetCreator.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.
// Using class literals
// {java typeinfo.pets.LiteralPetCreator}
package typeinfo.pets;

import java.util.*;

public class LiteralPetCreator extends PetCreator {
  // No try block needed.
  @SuppressWarnings("unchecked")
  public static final List<Class<? extends Pet>> ALL_TYPES =
      Collections.unmodifiableList(
          Arrays.asList(
              Pet.class,
              Dog.class,
              Cat.class,
              Rodent.class,
              Mutt.class,
              Pug.class,
              EgyptianMau.class,
              Manx.class,
              Cymric.class,
              Rat.class,
              Mouse.class,
              Hamster.class));
  // Types for random creation:
  private static final List<Class<? extends Pet>> TYPES =
      ALL_TYPES.subList(ALL_TYPES.indexOf(Cat.class), ALL_TYPES.size());

  @Override
  public List<Class<? extends Pet>> types() {
    return TYPES;
  }

  public static void main(String[] args) {
    System.out.println(TYPES);
  }
}
/* Output:
[class typeinfo.pets.Cat, class typeinfo.pets.Rodent, class typeinfo.pets.Mutt, class typeinfo.pets.Pug, class typeinfo.pets.EgyptianMau, class typeinfo.pets.Manx
, class typeinfo.pets.Cymric, class typeinfo.pets.Rat, class typeinfo.pets.Mouse, class typeinfo.pets.Hamster]
*/

run it by in OnJava8-Examples directory:

% javac typeinfo/pets/LiteralPetCreator.java
% java typeinfo.pets.LiteralPetCreator
/**
     * Returns an unmodifiable view of the specified list.  This method allows
     * modules to provide users with "read-only" access to internal
     * lists.  Query operations on the returned list "read through" to the
     * specified list, and attempts to modify the returned list, whether
     * direct or via its iterator, result in an
     * <tt>UnsupportedOperationException</tt>.<p>
     *
     * The returned list will be serializable if the specified list
     * is serializable. Similarly, the returned list will implement
     * {@link RandomAccess} if the specified list does.
     *
     * @param  <T> the class of the objects in the list
     * @param  list the list for which an unmodifiable view is to be returned.
     * @return an unmodifiable view of the specified list.
     */
    public static <T> List<T> unmodifiableList(List<? extends T> list) {
        return (list instanceof RandomAccess ?
                new UnmodifiableRandomAccessList<>(list) :
                new UnmodifiableList<>(list));
    }
// typeinfo/pets/Pets.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.
// Facade to produce a default PetCreator
package typeinfo.pets;
import java.util.*;
import java.util.stream.*;

public class Pets {
  public static final PetCreator CREATOR =
    new LiteralPetCreator();
  public static Pet get() {
    return CREATOR.get();
  }
  public static Pet[] array(int size) {
    Pet[] result = new Pet[size];
    for(int i = 0; i < size; i++)
      result[i] = CREATOR.get();
    return result;
  }
  public static List<Pet> list(int size) {
    List<Pet> result = new ArrayList<>();
    Collections.addAll(result, array(size));
    return result;
  }
  public static Stream<Pet> stream() {
    return Stream.generate(CREATOR);
  }
}
// typeinfo/PetCount2.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.

import typeinfo.pets.*;

public class PetCount2 {
  public static void main(String[] args) {
    PetCount.countPets(Pets.CREATOR);
  }
}
/* Output:
Mouse Manx Pug Rodent Rodent Hamster Mouse Cat Mutt Rat Mouse Mouse Rodent Hamster Hamster Mouse Mouse Rodent Cat Mouse
{Pug=1, Mouse=7, Rat=1, Cat=3, Manx=1, Rodent=15, Mutt=1, Dog=2, Pet=20, Hamster=3}
*/

run PetCount2 please use 

./gradlew  :typeinfo:PetCount2

and use 

./gradlew  :typeinfo:PetCount

compare the output results.

next article

refrences:

1. https://github.com/wangbingfeng/OnJava8-Examples/blob/master/typeinfo/pets/LiteralPetCreator.java

2. http://hg.openjdk.java.net/jdk8/jdk8/jdk/file/687fd7c7986d/src/share/classes/java/util/Collections.java

3. https://github.com/wangbingfeng/OnJava8-Examples/blob/master/typeinfo/pets/PetCount.java

4. https://github.com/wangbingfeng/OnJava8-Examples/blob/master/typeinfo/pets/PetCount2.java

Lab 2 Chapter 1 Java Language Programming Chapter 1: Variables and Primitive Data Types (Part 2) Instructor: Ahsan Shehzad Date: September 3, 2025 Practical Session Objective Lab Goal: Store and Display a Contact Our objective is to create a simple Java program that stores the information for one contact using variables and then prints that information neatly to the console. Final Result Preview: (This is what your console output will look like) (Screenshot of the final console output will go here) Prerequisites: Java Development Kit (JDK) 11 or higher installed. An IDE like IntelliJ IDEA Community Edition ready to go. Step-by-Step Guided Exercise Step 1: Create the Project Structure Goal: Set up your project in IntelliJ and create the main class file. Instructions: 1. Open IntelliJ IDEA. 2. Go to File > New > Project... . 3. Name your project MyContactManager and choose a location to save it. Contact Profile: Name: John Doe Age: 32 Phone: 447700900123 Is a Friend: true 1 2 3 4 54. Ensure Java is selected and you have a JDK configured. 5. Once the project is created, right-click the src folder in the Project Explorer. 6. Select New > Java Class . 7. Name the class Main and press Enter. Code Block: Your IDE will generate this boilerplate code for you. Add the main method inside the class. Expected Result: You have a clean Main.java file, and the program can be run (though it won't do anything yet). Step 2: Declare and Initialize Contact Variables Goal: Create variables inside the main method to hold a contact's data. Instructions: 1. Inside the main method, declare and initialize variables for each piece of contact information. 2. Choose the most appropriate data type for each value. 3. Pay special attention to the literals for long ( L ) and String (double quotes). Code Block: Add the following lines inside your main method. Expected Result: The program should compile without any errors. When you run it, still nothing will happen, but the data is now stored in memory. public class Main { public static void main(String[] args) { // Our code will go here! } } 1 2 3 4 5 public static void main(String[] args) { // String is a special object type we use for text. String firstName = "John"; String lastName = "Doe"; // Use 'int' for age. int age = 32; // A phone number can be large, so 'long' is a safe choice. long phoneNumber = 447700900123L; // 'boolean' is perfect for a simple true/false status. boolean isFriend = true; } 1 2 3 4 5 6 7 8 9 10 11 12 13 14Step 3: Display the Information Goal: Use System.out.println() and string concatenation ( + ) to print the stored data to the console. Instructions: 1. After the variable declarations, add a println statement to act as a header. 2. For each variable, write a println that combines a descriptive label (e.g., "Name: ") with the variable itself using the + operator. Code Block: Add these lines after your variables in the main method. Expected Result: When you run the main method, you should see the formatted contact details printed to your console, matching the goal on slide 13. Putting It All Together Goal: Review the complete, final code for this lab session. Instructions: Your final Main.java file should look exactly like this. Make sure your code matches and run it one last time to confirm the output. Code Block: // ... variables from previous slide ... // --- Displaying the Information --- System.out.println("Contact Profile:"); System.out.println("Name: " + firstName + " " + lastName); System.out.println("Age: " + age); System.out.println("Phone: " + phoneNumber); System.out.println("Is a Friend: " + isFriend); 1 2 3 4 5 6 7 8 public class Main { public static void main(String[] args) { // --- Storing Contact Information --- // String is a special object type we use for text. String firstName = "John"; String lastName = "Doe"; // Use 'int' for age. int age = 32; // A phone number can be large, so 'long' is a safe choice. long phoneNumber = 447700900123L; // 'boolean' is perfect for a simple true/false status. boolean isFriend = true; 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16Expected Result: A clean, working program that successfully stores and displays data. You've just completed Phase 1 of the project! (This slide is intentionally left blank to fit the 21-slide structure. It can be used for instructor-specific notes or an extra exercise if needed.) Challenge Task For Those Who Finish Early... Challenge 1: Add a Second Contact Declare and initialize a new set of variables for a second person (e.g., firstName2 , age2 , etc.). Print their details to the console, separated from the first contact by a line of dashes ( "----------" ). Challenge 2: Perform a Calculation After creating both contacts, declare a new double variable named averageAge . Calculate the average age of the two contacts and store it in the variable. Hint: (age1 + age2) / 2.0 . Why / 2.0 and not / 2 ? Print the average age to the console with a descriptive label. // --- Displaying the Information --- System.out.println("Contact Profile:"); System.out.println("Name: " + firstName + " " + lastName); System.out.println("Age: " + age); System.out.println("Phone: " + phoneNumber); System.out.println("Is a Friend: " + isFriend); } } 17 18 19 20 21 22 23 24 答案是什么
09-22
<think> 我们遇到了一个ArkTS的编译错误:`Class literals are not supported (arkts-no-class-literals)`。这是因为在ArkTS中,类表达式(class expressions)作为值直接使用是不被允许的,而静态属性赋值一个类表达式正是这种情况。 解决方案: 我们可以改用静态方法或者将构造者类定义在外部,但这样会破坏封装性。另一种方法是使用命名类并导出,但这里我们希望保持构造者与产品类的紧密关联。 因此,我们调整方案:将构造者类定义为外部类,但通过命名空间关联。或者,我们可以将构造者类定义为静态属性,但使用函数返回类(但是ArkTS也不支持在类内部动态定义类)。考虑到ArkTS的限制,我们可以在同一个模块中定义两个类,并将构造者类独立出来,然后通过命名约定来关联。 但是,如果我们希望保持构造者类与产品类的静态关联,我们可以这样做: 1. 将构造者类定义在外部,但产品类提供一个静态方法来获取构造者实例。 2. 或者,将构造者类定义为产品类的静态属性,但使用一个工厂函数来返回构造者类(但ArkTS不支持类表达式,所以不行)。 鉴于ArkTS的限制,我们采用以下替代方案: 方案A:独立构造者类,产品类提供静态方法创建构造者 方案B:使用嵌套命名空间(但ArkTS没有命名空间,有模块) 这里我们选择方案A: 我们将构造者类独立定义,然后产品类提供一个静态方法`builder`来创建构造者实例。 修改后的代码: ```typescript // 1. 定义构造者类 class ComputerBuilder { // 必需属性(构造时初始化) private cpu: string; // 可选属性 private gpu?: string; private ram?: number; private storage?: number; private hasBluetooth: boolean = false; private hasWifi: boolean = false; constructor(cpu: string) { this.cpu = cpu; } // 添加可选组件的链式方法 setGPU(gpu: string): this { this.gpu = gpu; return this; } setRAM(ram: number): this { this.ram = ram; return this; } setStorage(storage: number): this { this.storage = storage; return this; } enableBluetooth(): this { this.hasBluetooth = true; return this; } enableWifi(): this { this.hasWifi = true; return this; } // 构建最终产品 build(): Computer { return new Computer(this); } } // 2. 定义产品类 class Computer { // 必需属性 cpu: string; // 可选属性 gpu?: string; ram?: number; storage?: number; hasBluetooth: boolean; hasWifi: boolean; // 注意:这里改为public,因为我们需要从外部构建,但也可以保持private并提供静态方法 constructor(builder: ComputerBuilder) { this.cpu = builder['cpu']; // 因为属性在Builder中是private,所以不能直接访问,需要调整为public或者提供getter // 更好的方式是让Builder提供获取属性的方法,或者将属性改为public(但破坏了封装) // 或者使用相同模块的优势:因为Computer和ComputerBuilder在同一个模块,所以可以访问private属性(TypeScript中模块内私有可见) // 在同一个文件中,我们可以访问private属性?在TypeScript中,private在同一个类中可见,不同类不可见,即使同一个模块。 // 所以我们需要调整:将ComputerBuilder的属性改为public,或者提供getter。 // 这里我们调整:将Builder中的属性改为public,因为Builder本身就是用于构建的,而且我们希望Computer可以访问。 // 但这样外部也可以修改,为了避免,我们可以将Builder放在和Computer同一个模块,并且不导出Builder(如果不需要外部直接使用Builder的话)。 // 另一种做法:在Builder中提供私有属性,然后提供公共的getter给Computer使用。但这样需要为每个属性写getter,比较繁琐。 // 简单做法:我们将Builder中的属性改为public,因为Builder就是用来配置的,配置完后构建,构建后就不再使用Builder,所以暴露属性问题不大。 // 因此,我们在Builder中直接使用public属性,这样在Computer中就可以通过builder.cpu访问了。 // 注意:上面代码中我们已经将属性定义为private,现在改为public。 // 但我们上面使用的是private,所以这里我们需要修改Builder,将属性改为public。 // 重新调整:在ComputerBuilder中,我们将属性设为public(或者protected,这样Computer可以访问?但TypeScript中protected只能子类访问,不同类不行) // 所以,要么都改为public,要么提供getter。 // 由于ArkTS(TypeScript)的模块内访问规则,其实在同一个模块(文件)中,即使属性是private,另一个类也是无法访问的。因此,我们只能将属性设为public,或者使用getter。 // 这里我们选择将属性设为public,因为Builder的目的就是配置属性,然后构建产品,公开这些属性并不会带来太大问题。 // 但是,我们上面已经将属性定义为private,所以需要修改Builder中的属性为public。 } } ``` 但是,这样修改后,构造者类的属性都是public的,外部可以随意修改,我们并不希望这样。所以,我们调整思路: 方案C:将构造者类和产品类放在同一个文件中(同一个模块),并且利用TypeScript/ArkTS的跨类私有访问(实际上不支持,所以不可行)。 方案D:在构造者类中提供getter方法,产品类通过getter方法获取属性值。 调整后的ComputerBuilder: ```typescript class ComputerBuilder { private _cpu: string; private _gpu?: string; private _ram?: number; private _storage?: number; private _hasBluetooth: boolean = false; private _hasWifi: boolean = false; constructor(cpu: string) { this._cpu = cpu; } // ... 链式方法设置这些私有属性 // 提供私有属性的getter给Computer类使用 get cpu(): string { return this._cpu; } get gpu(): string | undefined { return this._gpu; } get ram(): number | undefined { return this._ram; } get storage(): number | undefined { return this._storage; } get hasBluetooth(): boolean { return this._hasBluetooth; } get hasWifi(): boolean { return this._hasWifi; } // ... 其他方法 } ``` 然后在Computer的构造函数中,就可以通过getter获取: ```typescript class Computer { cpu: string; gpu?: string; ram?: number; storage?: number; hasBluetooth: boolean; hasWifi: boolean; constructor(builder: ComputerBuilder) { this.cpu = builder.cpu; // 使用getter this.gpu = builder.gpu; this.ram = builder.ram; this.storage = builder.storage; this.hasBluetooth = builder.hasBluetooth; this.hasWifi = builder.hasWifi; } } ``` 这样,我们既保证了Builder内部属性的封装性(私有),又可以让Computer在构建时获取这些值。 另外,为了让产品类与构造者类紧密关联,我们在产品类中提供一个静态方法用于创建构造者,这样使用起来更流畅: ```typescript class Computer { // ... 属性 // 提供一个静态方法创建Builder static builder(cpu: string): ComputerBuilder { return new ComputerBuilder(cpu); } // ... 其他 } ``` 使用方式: ```typescript const computer = Computer.builder('Intel i7') .setGPU('NVIDIA 1080') .build(); ``` 这样,我们就可以避免使用类表达式,同时保持较好的封装性。 完整代码: ```typescript // 构造者类 class ComputerBuilder { private _cpu: string; private _gpu?: string; private _ram?: number; private _storage?: number; private _hasBluetooth: boolean = false; private _hasWifi: boolean = false; constructor(cpu: string) { this._cpu = cpu; } setGPU(gpu: string): this { this._gpu = gpu; return this; } setRAM(ram: number): this { this._ram = ram; return this; } setStorage(storage: number): this { this._storage = storage; return this; } enableBluetooth(): this { this._hasBluetooth = true; return this; } enableWifi(): this { this._hasWifi = true; return this; } // Getter methods for Computer to access private properties get cpu(): string { return this._cpu; } get gpu(): string | undefined { return this._gpu; } get ram(): number | undefined { return this._ram; } get storage(): number | undefined { return this._storage; } get hasBluetooth(): boolean { return this._hasBluetooth; } get hasWifi(): boolean { return this._hasWifi; } build(): Computer { return new Computer(this); } } // 产品类 class Computer { cpu: string; gpu?: string; ram?: number; storage?: number; hasBluetooth: boolean; hasWifi: boolean; constructor(builder: ComputerBuilder) { this.cpu = builder.cpu; this.gpu = builder.gpu; this.ram = builder.ram; this.storage = builder.storage; this.hasBluetooth = builder.hasBluetooth; this.hasWifi = builder.hasWifi; } static builder(cpu: string): ComputerBuilder { return new ComputerBuilder(cpu); } displaySpecs(): void { console.log('Computer Specification:'); console.log(`CPU: ${this.cpu}`); console.log(`GPU: ${this.gpu ?? 'Integrated'}`); console.log(`RAM: ${this.ram ?? 8}GB`); console.log(`Storage: ${this.storage ?? 256}GB SSD`); console.log(`Bluetooth: ${this.hasBluetooth ? 'Yes' : 'No'}`); console.log(`Wi-Fi: ${this.hasWifi ? 'Yes' : 'No'}`); } } // 使用示例 @Entry @Component struct BuilderExample { build() { const computer = Computer.builder('AMD Ryzen 5') .setGPU('Radeon RX 6700') .setRAM(16) .enableWifi() .build(); computer.displaySpecs(); } } ``` 这样,我们既避开了ArkTS中不支持类表达式的问题,又保持了构造者模式的链式调用和封装性。 注意:由于构造者类独立定义,所以需要在同一个模块(文件)中,这样Computer才能访问到ComputerBuilder的私有属性(通过getter)。如果分文件,则需要导出,并且getter是public的,其他类也可以访问,但这是安全的,因为这些getter只返回属性值,并不修改。 §§相关问题§§: 1. 在构造者模式中如何处理必填参数和可选参数? 2. 如何防止构造者模式中构建出无效或不完整的对象? 3. 在ArkTS中,如何实现单例模式? 4. 构造者模式与原型模式结合使用的场景有哪些? </think> ### 解决 ArkTS 中静态构造者模式的类字面量问题 在 ArkTS 中,直接使用 `class` 字面量作为静态属性会触发 `arkts-no-class-literals` 错误。这是因为 ArkTS 严格遵循 TypeScript 的子集规范,不支持直接在类中定义类表达式作为属性值。以下是符合 ArkTS 规范的解决方案: ```typescript // 1. 创建独立的构造者类 class ComputerBuilder { private readonly computer: Computer; // 使用私有构造函数确保只能通过静态方法创建 private constructor(cpu: string) { this.computer = new Computer(cpu); } // 静态创建方法替代构造函数 static create(cpu: string): ComputerBuilder { return new ComputerBuilder(cpu); } // 链式配置方法 setGPU(gpu: string): ComputerBuilder { this.computer.gpu = gpu; return this; } setRAM(ram: number): ComputerBuilder { this.computer.ram = ram; return this; } setStorage(storage: number): ComputerBuilder { this.computer.storage = storage; return this; } enableBluetooth(): ComputerBuilder { this.computer.hasBluetooth = true; return this; } enableWifi(): ComputerBuilder { this.computer.hasWifi = true; return this; } // 构建方法返回最终产品 build(): Computer { return this.computer; } } // 2. 产品类保持独立 class Computer { // 必需属性 (通过构造函数初始化) readonly cpu: string; // 可选属性 (带默认值) gpu: string = 'Integrated'; ram: number = 8; storage: number = 256; hasBluetooth: boolean = false; hasWifi: boolean = false; // 构造函数设为包内可见 (同一文件内可访问) constructor(cpu: string) { this.cpu = cpu; } // 显示配置的方法 displaySpecs(): void { console.log('Computer Specification:'); console.log(`CPU: ${this.cpu}`); console.log(`GPU: ${this.gpu}`); console.log(`RAM: ${this.ram}GB`); console.log(`Storage: ${this.storage}GB`); console.log(`Bluetooth: ${this.hasBluetooth ? 'Yes' : 'No'}`); console.log(`Wi-Fi: ${this.hasWifi ? 'Yes' : 'No'}`); } } // 3. 使用示例 @Entry @Component struct BuilderExample { build() { // 通过静态方法创建构造者 const gamingPC = ComputerBuilder.create('Intel i9') .setGPU('NVIDIA RTX 4090') .setRAM(32) .setStorage(2000) .enableBluetooth() .enableWifi() .build(); gamingPC.displaySpecs(); } } ``` ### 关键解决方案说明 1. **解决类字面量问题**: - 将构造者类定义为独立的顶层类 - 使用静态方法 `create()` 替代构造函数 - 避免在类内部定义类表达式 2. **封装性保证**: ```typescript // 产品类构造函数设为包内可见 constructor(cpu: string) { ... } ``` - 产品类的构造函数不是公开的 - 只有同一文件中的构造者类可以创建实例 3. **链式调用实现**: ```typescript setGPU(gpu: string): ComputerBuilder { this.computer.gpu = gpu; return this; } ``` - 每个配置方法返回构造者实例自身 (`this`) - 保持流畅的链式调用接口 4. **构建过程**: ```typescript build(): Computer { return this.computer; } ``` - `build()` 方法返回最终配置好的产品 - 构造完成后返回不可变的产品实例 ### 替代方案:工厂函数模式 如果你的项目需要更简洁的实现,可以使用工厂函数模式: ```typescript // 使用工厂函数代替构造者类 function createComputerBuilder(cpu: string) { const computer = new Computer(cpu); return { setGPU: (gpu: string) => { computer.gpu = gpu; return this; }, setRAM: (ram: number) => { computer.ram = ram; return this; }, build: () => computer }; } // 使用示例 const basicPC = createComputerBuilder('AMD Ryzen 5') .setRAM(16) .build(); ``` ### ArkTS 构建者模式最佳实践 1. **文件组织**: - 将构造者和产品类放在同一文件中 - 导出产品类,但保持构造者为模块私有 2. **不可变性**: - 对产品类中的核心属性使用 `readonly` - 构建完成后返回不可变对象 3. **默认值处理**: - 在声明时初始化可选属性 - 避免在构建过程中出现未定义值 4. **类型安全**: - 为所有配置方法添加明确的类型注解 - 使用 TypeScript 的类型检查确保参数正确
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值