我遇到了很多关于Java 可选参数的问题和讨论!所以我写这篇文章是为了突出和深入介绍用于在 Java 中实现和处理默认参数的所有变通方法。
如果您是想要了解可选参数在 Java 中如何工作的人之一,那么也许这篇文章就是为您编写的!请继续阅读以了解所有详细信息!
什么是可选参数?
顾名思义,Java 中的可选参数仅指对于方法调用而言可能是可选的参数!
它描述了在不指定其定义中使用的某些参数的情况下调用方法的可能性!
参数是可以传递给方法或函数的变量!当您调用该方法时,您需要确保要传递的参数的类型和顺序必须与方法声明中指定的相匹配!
Java 是否支持可选参数?
不幸的是,Java 在设计上并不直接支持方法或构造函数中的默认参数,这与 Kotlin 和 Python 等其他语言不同!
话虽如此,Java 中的所有参数都是必需的!当您想调用一个方法时,您必须指定在其声明中使用的所有参数!
如何在Java中使参数可选?
有几种策略和方法可用于模拟和实现 Java 默认参数!
让我们仔细看看,并一一详细探讨所有这些方法和选项。
方法重载:
重载是一种允许定义具有相同名称和不同参数列表的多个方法的机制!很简单,对吧?
根据传递的参数,编译器可以选择正确的版本来调用!
这种方法的想法非常简单:我们可以创建多个版本(就参数而言)同一方法,而不是为可选参数提供默认值!
每个重载的方法必须只接受需要的参数!下面的例子展示了如何使用方法重载策略在 Java 中实现可选参数!
package com.azhwani.optional.params
public class OptionalParams {
public OptionalParams() {
}
// First version of the method display()
public void display(int x) {
System.out.println("First version : "+x);
}
// Second version of the method display()
public void display(int x, int y) {
System.out.println("Second version : "+x+" "+y);
}
// Second version of the method display()
public void display(int x, int y, int z) {
System.out.println("Third version : "+x+" "+y +" "+z);
}
public static void main(String[] args) {
// TODO Auto-generated method stub
OptionalParams obj = new OptionalParams();
// Demo to demonstrate Java optional parameters
obj.display(2);
obj.display(3,5);
obj.display(1,2,4);
}
}
Java默认参数演示:
当您运行上面的示例时,输出将是:
First version : 2
Second version : 3 5
Third version : 1 2 4
在上面的例子中,我们可以看到方法optionalParams()是根据参数个数重载的!
该策略的简洁性和灵活性使其成为实现 Java 可选参数概念的最知名方式之一!
此外,值得一提的是,这种方法的缺点之一是,如果方法有太多参数需要处理,重载会很快变得难以维护。
伸缩构造器
构造函数就像方法一样,它们也可以被重载,以便每个函数执行一个单一的职责。凉爽的!
Java 并不真正支持构造函数参数的默认值,因此伸缩构造函数是传达某些参数在 Java 中是可选的可用选项之一!
伸缩构造函数概念背后的主要思想是基于委托:每个构造函数调用另一个具有更多参数的构造函数!
使困惑?让我解释!具有一个参数的构造函数调用具有两个参数的构造函数……您可以将构造函数链接起来,直到覆盖所有可选参数以避免代码重复!
public class Student {
private int Id;
private String firstname;
private String lastname;
private String email;
private int age;
public Student(String firstname) {
this(firstname,"Last name");
}
public Student(String firstname,String lastname) {
this(firstname,lastname,"email@email.com");
}
public Student(String firstname,String lastname,String email) {
this(firstname,lastname,email,20);
}
public Student(String firstname,String lastname,String email,int age) {
this.firstname = firstname;
this.lastname = lastname;
this.email = email;
this.age = age;
}
public static void main(String[] args) {
Student sdt01 = new Student("John");
Student sdt02 = new Student("Patricia","Edwards");
Student sdt03 = new Student("Donna","Patterson","donna.patterson@email.com");
}
}
如您所见,每个构造函数都有不同数量的参数,所以基本上,您可以使用您想要的参数组合创建一个实例!
如果您的参数数量很少,这种方法非常简单且易于实现!
然而,另一方面,这个选项被认为是一种反模式,具有许多参数的构造函数很难阅读和维护!
如果您有 2 或 3 个相同类型的参数,那么您可能会不小心切换它们的值而没有任何编译错误!
JavaBeans:
简而言之,JavaBean 代表一个简单的Java 类,它遵循有关方法命名、构造和行为的某些特定约定:
-
它必须实现 Serializable 接口。
-
它必须有一个公共的无参数构造函数。
-
所有属性都必须声明为私有。
-
每个私有属性都必须有公共的 getter 和 setter 方法。
JavaBean 约定提出了一种在 Java 中设置可选参数的最简单且可扩展的方法!它允许使用分步策略创建对象,首先您需要调用默认构造函数来实例化类,然后调用 setXXX 方法来设置对象属性!
现在让我们一起来看一个 JavaBean 是如何在 Java 中定义的示例:
public class Employee {
private String firstname;
private String lastname;
private String email;
private boolean ismanager;
public Employee () {
}
public String getFirstname() {
return firstname;
}
public void setFirstname(String firstname) {
this.firstname = firstname;
}
public String getLastname() {
return lastname;
}
public void setLastname(String lastname) {
this.lastname = lastname;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public boolean isManager() {
return ismanager;
}
public void setManager(boolean ismanager) {
this.isManager = ismanager;
}
}
这种方法有几个优点:
-
易于实施和更改。
-
易于阅读和扩展 - 添加更多参数并不是什么大问题!大多数 Java IDE 都可以为您生成 getter 和 setter!
但是,也有一些缺点:
-
JavaBean 是反模式的,它违反了 OOP 的一些基本原则。
-
它创建了一个状态不一致的空对象(默认构造函数),这意味着该对象尚未完全构造!
-
JavaBean 对象是可变的,因此这种方法与不可变模式不兼容。
-
JavaBean 在处理线程安全方面需要额外的努力。
JavaBeans 模式有严重的缺点 - Joshua Bloch,Effective Java
静态工厂方法:
静态工厂方法提供了另一种创建对象的灵活方式。您可以定义一个公共静态工厂方法,而不是直接使用构造函数来创建一个实例,它只返回一个类的实例!
毕竟,构造函数没有被命名的能力!
为了更容易理解,比较这种方法:
LocalDateTime mydate = new LocalDateTime(Instant.now());
对此:
LocalDateTime date = LocalDateTime.ofInstant(Instant.now());
// Or You can do this :
LocalDateTime mydate = LocalDatetime.randomTimeBeforeInstant(Instant.now());
更干净更清晰对吗?
正如我们在前一章中已经看到的,您可以创建多个构造函数来处理 Java 可选参数!但是,由于它们都具有相同的名称,因此很难区分它们。
这种方法背后的想法是创建具有不同名称的静态方法,以便创建对象的意图变得清晰明了。
您可以将构造函数标记为私有,以便只能从类内部调用它们,并将工厂方法标记为静态,以便在不实例化类的情况下调用它们。
public class Person {
private String name;
private int age;
private Person (String name) {
this.name = name;
}
private Person (String name, int age) {
this.name = name;
this.age = age;
}
public void setName(String name) {
this.name = name;
}
public String getName() {
return this.name;
}
public void setAge(int age) {
this.age = age;
}
public int getAge() {
return this.age;
}
public static Person getPersonByName(String name) {
return new Person(name);
}
public static Person getPersonByNameAndAge(String name,int age) {
return new Person(name,age);
}
public static void main(String[] args) {
Person prs01 = Person.getPersonByName("Samantha");
Person prs02 = Person.getPersonByNameAndAge("Linda",23);
}
}
以下是使用静态工厂方法的一些优点:
-
与构造函数不同,每个方法都可以有不同的名称,因此这有助于阐明用于创建实例的方式。
-
静态方法不需要每次都返回新实例!
该策略的主要缺点之一是它不能很好地扩展,如果可选参数的数量很大,则很难实现和维护。
建造者模式:
这种设计模式提供了一种非常有趣的方式来创建一个复杂的对象。主要目的是将对象的构造与其表示分开!
构建器模式是另一种处理 Java 可选参数的方法!为了构造带有可选参数的对象,这是一个很好的选择!
让我们从创建一个具有一些可选属性的具体类 Employee 开始!
public class Employee {
// Required properties
private String firstname;
private String lastname;
// Optional properties
private String email;
private String address;
private String phone;
public Employee() {
}
// Getters + Setters
}
您可以简单地依靠构造函数重载概念并在您的类中创建多个构造函数来涵盖所有可能的参数组合!
但是如果你的类有太多字段怎么办?你最终肯定会得到一个相当庞大的丑陋和令人困惑的构造函数列表!正确的 ?
如何避免这个问题?简单地说,使用构建器模式!
好吧,以下是我在实现构建器模式时考虑的一些关键点:
- 我在 Employee 类中创建了一个公共静态嵌套类:EmployeeBuilder!这是我们的建设者班!
- 我在 EmployeeBuilder 中定义了外部类的所有字段。
- 我为 EmployeeBuilder 创建了一个公共构造函数,其中包含所有必需的属性作为参数!
- 我在 EmployeeBuilder 类中创建了用于设置可选参数的方法!它们返回相同的 Builder 对象!
在使用构建器模式后,现在看一下上一个示例中的 Employee 类:
public class Employee {
// Required properties
// Optional properties
private Employee() {
}
// Getters + Setters
// Our builder class
public static class EmployeeBuilder {
private String firstname;
private String lastname;
private String email;
private String address;
private String phone;
public EmployeeBuilder(String firstname,String lastname){
this.firstname = firstname;
this.lastname = lastname;
}
public EmployeeBuilder withEmail(String email) {
this.email = email;
return this;
}
public EmployeeBuilder withAddress(String address) {
this.address = address;
return this;
}
public EmployeeBuilder withPhone(String phone) {
this.phone = phone;
return this;
}
public Employee build() {
Employee emp = new Employee();
emp.firstname = this.firstname;
emp.lastname = this.lastname;
emp.email = this.email;
emp.address = this.address;
emp.phone = this.phone;
return emp;
}
}
public static void main(String[] args) {
// First Employee object
Employee emp01 = new Employee.EmployeeBuilder("Johnny","Smith")
.withEmail("johnnysmith@email.com")
.withAddress("Johnny Smith ADR")
.withPhone("1 (844) 213-9662")
.build();
// Second Employee object with some default values
Employee emp02 = new Employee.EmployeeBuilder("Samantha","Williams")
.withPhone("1 (833) 737-9738")
.build();
}
}
构建器模式提高了代码的灵活性和可读性!它允许使用逐步方法创建具有可选参数的复杂对象!
行数随着参数数量的增加而增加。构建器模式的主要缺点是冗长,因为它需要重复代码!
Null 和 @Nullable 注释:
Java 允许传递 null 作为参数来设置可选参数的默认值!好吧,指定 null 以指示给定参数是可选的,这似乎是有史以来最简单的选择!
您也可以使用@Nullable 注解,它使方法可以接受空参数变得简单明了!
该策略的基本思想是简单地对方法体内的可选参数进行空检查以避免 NullPointerException!
public class NullableParams {
public NullableParams() {
}
public void display(String x, String y, int z) {
String xx = x != null ? x : "Default X";
String yy = y != null ? y : "Default Y";
System.out.println("Printing some random values : "+xx+" "+yy +" "+z);
}
public static void main(String[] args) {
// TODO Auto-generated method stub
NullableParams obj = new NullableParams();
// Java default parameters example
obj.display(null,null,5);
obj.display("Hello",null,5);
}
}
Printing some random values : Default X Default Y 5
Printing some random values : Hello Default Y 5
调用该方法时,您可以为可选参数提交空值!简单吧?不!魔鬼在细节中!
这种方法有什么问题?实际上,这种方法是一种反模式!这被认为是一种不好的做法,因为它使方法变得复杂且难以阅读!
此外,在将空值传递给重载方法时,我们需要小心!如果遇到相同方法的不同声明(接受空值),编译器可能会感到困惑!这可能会标记歧义错误!
public class NullableMathods {
public NullableMathods() {
}
public void print(String str) {
}
public void print(Integer intgr) {
}
public static void main(String[] args) {
NullableMathods myobj = new NullableMathods();
myobj.print(null); // error: reference to fun is ambiguous
// Both Integer and String are objects in Java which means they can be null.
}
}
Java 可变参数:
Varargs 作为一个名字,指的是可变参数,作为一个概念它提供了一种简单的方法来传递可变数量的参数!完美模拟 Java 可选参数,对吧?
为什么?好吧,有时我们不知道要为给定方法提供多少参数!
出于什么目的?主要是为了简化将数组作为参数传递给方法的想法!
如何使用它?我们可以在数据类型之后使用三个点 (...)!
在 Java 中使用可变参数时要考虑的重要要点:
- 方法声明中只允许使用一个 varargs 表达式。
- 如果方法有其他参数,可变参数必须是最后一个参数。
public class VarargsParams {
public VarargsParams() {
}
public void print(String... values) {
System.out.println("Number of parameters: " + values.length);
for (String value: values)
System.out.println(value);
}
public static void main(String[] args) {
// TODO Auto-generated method stub
VarargsParams obj = new VarargsParams();
obj.print();
obj.print("Hello","varargs","In","Java");
}
}
因此,如果您遇到不知道要向方法传递多少参数的情况,那么 varargs 可能是您最好的朋友!
这种方法的主要缺点是,直接使用它并不总是安全的,它可能导致堆污染和 ClassCastException!
又是一件严肃的事情!如何告诉使用您的方法的人它只期望三个或四个值而不是更多?
Java 8 可选参数:
Optional是 Java 8 中引入的一个新概念,作为一种新类型 Optional包装可选值!
Optional 类的主要目的是避免不必要的丑陋 NullPointerExceptions 并帮助开发干净、整洁和可理解的代码!
以下是关于 Java 8 Optional 类的一些要点:
- 可能包含也可能不包含非空值。
- 不需要空检查。
- 避免空指针异常。
- 减少锅炉板代码。
public class OptionalClass {
public OptionalClass() {
}
public void test(String x, Optional yOpt) {
String y = yOpt.isPresent() ? yOpt.get() : "Java 8 default parameter";
System.out.println(x+" "+y);
}
public static void main(String[] args) {
// TODO Auto-generated method stub
OptionalClass opobj = new OptionalClass();
opobj.test("Hello",Optional.of("Required argument"));
opobj.test("Hi",Optional.empty());
}
}
运行上面的示例将给出:
Hello Required argument
Hi Java 8 default parameter
您可以使用 Java 8 Optional 类来表示方法参数可以是可选的,但它不是为此目的而设计的!
好吧, Optional 主要是为了表示方法返回类型!将 Optional 作为参数传递给方法会导致在调用者级别进行不必要的包装。
地图
当您有太多参数并且大多数参数通常使用默认值时,您可以将映射作为方法参数传递!这是 varargs 选项的一个很好的选择!
public class ParamsWithMap {
public ParamsWithMap() {
}
public void foo(Map<String,Object> params) {
String param01 = "default";
Integer param02 = 0;
if (params.containsKey("param01")) {
if (params.get("param01") instanceof String) {
param01 = (String)params.get("param01");
}
}
if (params.containsKey("param02")) {
if (params.get("param02") instanceof Integer) {
param02 = (Integer)params.get("param02");
}
}
System.out.println(param01 +" : "+param02);
}
public static void main(String[] args) {
ParamsWithMap mapobj = new ParamsWithMap();
Map<String,Object> parameters = new HashMap<>();
parameters.put("param01", "Hello Test");
parameters.put("param02", 5);
mapobj.foo(parameters);
Map<String,Object> opparameters = new HashMap<>();
opparameters.put("param01", "Azhwani");
mapobj.foo(opparameters);
}
}
上面的示例将产生以下输出:
Hello Test : 5
Azhwani : 0
Java 9引入了新的工厂方法来简化创建不可变 Map 的逻辑!
public class ParamsWithJava9Map {
public ParamsWithJava9Map() {
}
public void boo(Map<String,Object> params) {
String x = "default";
Integer y = 0;
if (params.containsKey("x")) {
if (params.get("x") instanceof String) {
x = (String)params.get("x");
}
}
if (params.containsKey("y")) {
if (params.get("y") instanceof Integer) {
y = (Integer)params.get("y");
}
}
System.out.println(x +" : "+y);
}
public static void main(String[] args) {
ParamsWithJava9Map mapobj = new ParamsWithJava9Map();
Map<String,Object> map1 = Map.of("x", "Hello java 9", "y", 62);
mapobj.boo(map1);
Map<String,Object> map2 = Map.of("x", "Another hello");
mapobj.boo(map2);
}
}
如果您想使用映射来保存默认参数,即使是编译器也没有人会抱怨,但它根本不是为此目的而设计的!
在 Java 中使用 map 处理可选参数可能会导致方法内部出现条件逻辑,从而在某些时候导致性能下降!
结论:
就是这样,让我们总结一下我们在本文中学到的所有内容:Java 不支持默认参数概念以避免复杂性和歧义!
如果您遇到必须在 Java 中实现可选参数的情况,那么您可以使用我上面提到的方法之一!
如果您对 Java 可选参数有任何疑问,请随时在评论部分与我们讨论。
祝您学习愉快,度过愉快的一天!