威海精神文明建设办公室网站,即墨医院网站制作公司,搜索推广竞价托管哪家好,室内设计师网名#x1f345; 作者简介#xff1a;哪吒#xff0c;CSDN2021博客之星亚军#x1f3c6;、新星计划导师✌、博客专家#x1f4aa; #x1f345; 哪吒多年工作总结#xff1a;Java学习路线总结#xff0c;搬砖工逆袭Java架构师 #x1f345; 技术交流#xff1a;定期更新… 作者简介哪吒CSDN2021博客之星亚军、新星计划导师✌、博客专家 哪吒多年工作总结Java学习路线总结搬砖工逆袭Java架构师 技术交流定期更新Java硬核干货不定期送书活动 关注公众号【哪吒编程】回复 1024 获取《10万字208道Java经典面试题总结(附答案)》2024修订版pdf背题更方便一文在手面试我有 目录 一、构造函数的问题1、方法名都是一个容易混淆2、扩展性问题3、构造函数每次被调用都要创建一个新对象 二、静态工厂方法如何解决构造器的问题呢问题1方法名都是一个容易混淆问题2扩展性问题问题3构造函数每次被调用都要创建一个新对象 三、实例受控四、静态工厂方法可以返回任何子类型的对象五、Java 8中允许接口包含静态方法了和静态工厂方法有关系吗1、接口与静态方法2、与默认方法结合使用3、支持函数式接口 六、Java9中支持私有的静态方法但静态字段必须是公有的1、为什么Java9中支持私有的静态方法2、为什么静态字段必须是公有的 七、总结 在设计类时我们经常为其提供公有的构造器通过构造器来实例化类。
但在我学习设计模式时有一点经常被提及用静态工厂方法代替构造器。
今天就来分析一下其中的利与弊。
首先要先弄懂构造函数的问题是什么
一、构造函数的问题
当类有多个构造器时静态工厂方法可以通过有意义的名称来区分不同的创建方式使代码更易读。
有的杠精可能要说话了通过改变参数列表中参数类型的顺序或个数我就可以提供多个不同的构造器有什么问题吗
我想说的是你这样并不符合编码规范有如下几个问题
1、方法名都是一个容易混淆
编码规约规定方法名称见名知意你TMD都是一个名搞笑呢
如果某人看到new Person(“John”, 25)或new Person(25, “John”)他可能会混淆它们的作用尤其是当参数数量和类型相同而顺序不同的时候。
public class Person {private String name;private int age;private boolean isEmployed;public Person(String name, int age) {this.name name;this.age age;this.isEmployed false; // 默认值}public Person(String name, boolean isEmployed) {this.name name;this.age 0; // 默认值this.isEmployed isEmployed;}public Person(int age, String name) {this.name name;this.age age;this.isEmployed false; // 默认值}
}参数相同顺序不同谁是谁你记得住吗
参数顺序不同的构造器可能导致调用时不小心传入了错误顺序的参数结果得到一个非预期的对象。
如果顺序被搞错编译器不会报错但会导致运行时的逻辑错误。
Person person new Person(John, 25); // 想调用第一个构造器
Person person2 new Person(25, John); // 想调用第三个构造器结果参数位置错误产生错误的数据2、扩展性问题
当你需要扩展类并添加更多构造器时如果通过改变参数顺序和数量来实现重载可能会迅速导致构造器的组合方式爆炸难以维护估计连你自己都看不明白了。
这谁写的代码拉出去砍了。
现在裁员盛行你要小心了。
3、构造函数每次被调用都要创建一个新对象
这能有什么问题这不都是Java中默认的嘛每当你使用 new 关键字调用一个类的构造函数时都会创建一个对象实例。
创建新对象涉及分配内存和初始化对象这在性能上有一定开销。如果在短时间内需要频繁创建和销毁大量对象这种开销可能会显著影响系统性能。
构造函数的每次调用都创建新对象也会使对象的缓存和复用变得困难。
在数据库连接池中我们希望复用已经存在的连接而不是每次都创建一个新连接这时构造函数的行为就显得不合适。
二、静态工厂方法如何解决构造器的问题呢
问题1方法名都是一个容易混淆
静态工厂方法可以通过有意义的命名来避免这种混淆见名知意。
这是静态工厂方法的一个重要优势。不同于构造函数必须与类名相同静态工厂方法可以根据其创建对象的逻辑和用途使用不同的名称从而提高代码的可读性和明确性。
通过静态工厂方法如createChild、createEmployedAdult和createUnemployedAdult你可以清晰地表达每个方法的用途避免了通过构造器参数类型来区分的混淆问题。每个方法的名字直接说明了创建对象的用途提升了代码的可读性。
改进后的代码
public class Person {private String name;private int age;private boolean isEmployed;// 私有构造器防止直接实例化private Person(String name, int age, boolean isEmployed) {this.name name;this.age age;this.isEmployed isEmployed;}// 静态工厂方法解决方法名混淆问题public static Person createChild(String name) {return new Person(name, 0, false); // 默认年龄为0未就业}public static Person createEmployedAdult(String name, int age) {return new Person(name, age, true); // 成人已就业}public static Person createUnemployedAdult(String name, int age) {return new Person(name, age, false); // 成人未就业}
}问题2扩展性问题
静态工厂方法可以通过添加新的方法来扩展对象创建的方式从而提高代码的扩展性。
假如需要增加新的构造方式如裁员人员你可以轻松地通过添加createRetiredPerson静态工厂方法来实现而无需更改现有的构造函数或担心重载导致的代码复杂化。静态工厂方法的扩展性使得代码更容易维护和增强。
// 新增静态工厂方法支持扩展需求
public static Person createLayoffPerson(String name) {return new Person(name, 35, true); // 默认35岁在职
}问题3构造函数每次被调用都要创建一个新对象
静态工厂方法可以通过缓存对象、实现单例模式或其他优化策略避免每次都创建新对象从而提升性能和资源利用效率。
public class Person {private String name;private int age;private boolean isEmployed;private static final Person DEFAULT_CHILD_INSTANCE new Person(Default Child, 0, false);// 私有构造器private Person(String name, int age, boolean isEmployed) {this.name name;this.age age;this.isEmployed isEmployed;}// 使用缓存的实例避免每次都创建新对象public static Person getDefaultChildInstance() {return DEFAULT_CHILD_INSTANCE;}// 其他静态工厂方法...
}对于重复多次的调用静态工厂方法可以返回同一个对象这就可以控制存在哪些实例被称为实例受控。
三、实例受控
遇到生僻词汇哪吒的第一反应就是问问ChatGPT。 import java.util.HashMap;
import java.util.Map;public class Person {private String name;private int age;private boolean isEmployed;// 用于存储创建的实例private static final MapString, Person instances new HashMap();// 私有构造器防止直接实例化private Person(String name, int age, boolean isEmployed) {this.name name;this.age age;this.isEmployed isEmployed;}// 静态工厂方法解决方法名混淆问题并实现实例受控public static Person createChild(String name) {String key name :child;if (!instances.containsKey(key)) {instances.put(key, new Person(name, 0, false));}return instances.get(key);}public static Person createEmployedAdult(String name, int age) {String key name :employed: age;if (!instances.containsKey(key)) {instances.put(key, new Person(name, age, true));}return instances.get(key);}public static Person createUnemployedAdult(String name, int age) {String key name :unemployed: age;if (!instances.containsKey(key)) {instances.put(key, new Person(name, age, false));}return instances.get(key);}Overridepublic String toString() {return Person{name name , age age , isEmployed isEmployed };}
}通过静态工厂方法和实例缓存机制你可以在重复调用时返回同一个对象从而实现实例受控。这种设计可以精确控制一个类的实例数量避免内存浪费提高性能同时提供了更好的代码管理和扩展性。
四、静态工厂方法可以返回任何子类型的对象
静态工厂方法可以返回Person类的任意子类型对象而不仅限于返回Person本身。这种设计使得代码更具扩展性和灵活性。
通过静态工厂方法子类的具体实现对外部是透明的。外部调用者不需要知道这些子类的存在或如何实现只需通过工厂方法获取所需的对象。这种设计增强了封装性方便后续扩展。
这种灵活性使得静态工厂方法比构造器更有优势特别是在实现设计模式如工厂模式、单例模式、享元模式时。
我们创建几个Person类的子类Child、EmployedAdult、UnemployedAdult。
每个子类代表不同类型的Person具有特定的行为和属性。
例如Child类的age默认设为0isEmployed设为false表示未就业EmployedAdult和UnemployedAdult分别表示不同就业状态的成年人。
通过静态工厂方法分别创建并返回不同子类的实例它们隐藏了子类的具体实现调用者只需要通过这些方法创建对象不需要知道具体的子类结构。
五、Java 8中允许接口包含静态方法了和静态工厂方法有关系吗
1、接口与静态方法
我觉得这一变革多少和静态工厂方法的使用有一定的关系它提供了接口设计更大的灵活性和功能性。
在java 8之前实用工具方法通常定义在单独的工具类中例如Collections或Arrays而不是直接在接口中。这导致了一些不便比如工具类不能直接访问接口的内部细节。
允许接口包含静态方法使接口本身能够提供多样化的对象创建逻辑这种灵活性是传统构造器所无法提供的。
例如你可以在接口中定义一个静态工厂方法来返回该接口的某个实现类的实例这样做简化了客户端代码的使用也隐藏了实现的细节。
public interface Person {String getName();int getAge();static Person create(String name, int age) {return new PersonImpl(name, age); // 返回接口的实现类实例}
}// 私有实现类
class PersonImpl implements Person {private final String name;private final int age;private PersonImpl(String name, int age) {this.name name;this.age age;}Overridepublic String getName() {return name;}Overridepublic int getAge() {return age;}
}// 使用示例
public class Main {public static void main(String[] args) {Person person Person.create(哪吒, 30);System.out.println(person.getName() is person.getAge() years old.);}
}2、与默认方法结合使用
Java 8还引入了默认方法允许接口在提供方法签名的同时也提供默认实现。默认方法与静态方法结合使得接口不仅可以提供默认行为还可以提供创建实例的方法和相关工具。
这使得接口可以拥有类似于抽象类的功能而无需引入多继承的复杂性。
3、支持函数式接口
Java 8引入了函数式接口的概念特别是在Lambda表达式和方法引用中被广泛使用。静态方法可以用来提供工厂方法或其他辅助方法支持和增强函数式接口的使用。
例如在Comparator接口中静态方法comparing是一个静态工厂方法用于创建比较器这在函数式编程中非常有用。
六、Java9中支持私有的静态方法但静态字段必须是公有的
1、为什么Java9中支持私有的静态方法
1实现代码重用和封装
Java 9中支持私有的静态方法是为了增强代码的封装性和重用性特别是在使用静态工厂方法代替构造器的场景中私有静态方法可以帮助我们更好地组织和管理对象创建的复杂逻辑。
在这个例子中validateName和validateAge是私有的静态方法它们封装了对象创建时的验证逻辑。这些方法只在接口内部使用并不会暴露给接口的使用者。这种设计提高了代码的复用性和封装性同时使得静态工厂方法的实现更加简洁。
public interface Person {String getName();int getAge();static Person createChild(String name) {validateName(name);return new Child(name);}static Person createEmployedAdult(String name, int age) {validateName(name);validateAge(age);return new EmployedAdult(name, age);}// 私有静态方法供内部逻辑使用private static void validateName(String name) {if (name null || name.isEmpty()) {throw new IllegalArgumentException(Name cannot be null or empty);}}private static void validateAge(int age) {if (age 18) {throw new IllegalArgumentException(Age must be at least 18);}}
}2支持更复杂的对象创建逻辑
通过在接口中定义私有静态方法可以在静态工厂方法中分解复杂的对象创建逻辑。这使得我们可以用静态工厂方法来代替构造器同时保持代码的整洁和易于维护。
例如一个静态工厂方法可能涉及多个步骤来创建对象通过私有静态方法可以将这些步骤独立出来使代码更易于理解和测试。
2、为什么静态字段必须是公有的
如果静态字段是私有的那么这些字段只能在接口内部使用无法被接口的实现类或外部使用者访问这将限制接口的功能性和应用场景。
静态字段必须是公共的这是基于接口的契约性质和设计原则确保接口的实现类和使用者可以一致地访问这些字段从而保持接口的简洁性和实用性。
七、总结
使用静态工厂方法代替构造器是一种常见的设计模式提供了比传统构造器更多的灵活性。
静态工厂方法允许命名以提高代码的可读性和表达力同时可以返回不同子类型的实例而不仅限于返回类本身的实例。这种方法还支持缓存和对象复用避免每次都创建新对象从而提高性能。
随着Java 8和Java 9对接口功能的扩展静态工厂方法还可以被集成到接口中通过静态方法直接提供对象创建逻辑结合私有静态方法进一步优化代码的封装性和维护性。
总体而言静态工厂方法在提升代码灵活性、可维护性和性能方面具有显著优势。 GPT功能
GPT-4o知识问答支持1000token上下文记忆功能最强代码大模型Code Copilot代码自动补全、代码优化建议、代码重构等DALL-E AI绘画AI绘画 剪辑 自媒体新时代私信哪吒直接使用GPT-4o 文章收录于100天精通Java从入门到就业
哪吒数年工作总结之结晶。
哪吒多年工作总结Java学习路线总结搬砖工逆袭Java架构师。 华为OD机试 2023B卷题库疯狂收录中刷题点这里 刷的越多抽中的概率越大每一题都有详细的答题思路、详细的代码注释、样例测试发现新题目随时更新全天CSDN在线答疑。 点击下方名片回复1024获取《10万字208道Java经典面试题总结(2024修订版).pdf 》