首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >Java中如何像Python一样实现动态参数?

Java中如何像Python一样实现动态参数?

作者头像
马拉松程序员
发布2022-06-15 12:37:23
发布2022-06-15 12:37:23
1.1K0
举报

在Python中有一个可变参数的语法,就是在编写代码的,并不能明确有多少个参数,那么就可以使用可变参数。写法如下

代码语言:javascript
复制
def get(data, *args, **kwargs):   
    pass

*args称之为Non-keyword Variable Arguments,无关键字参数

**kwargs称之为keyword Variable Arguments,有关键字参数

当函数中以列表或者元组的形式传参时,就要使用*args,当传入字典形式的参数时,就要使用**kwargs。如两者在同一个方法使用中,*args需要在**kwargs前面。

两者区分是靠*和**,跟后面的名字没关系。args和kwargs表示的形参,可以随意起名字,用a,b,c都没问题,但是在代码编写的时候,变量名字,最好不要随意起名,中英文混合。

下面代码演示一下基本使用:

代码语言:javascript
复制
def method(data, *a, **b):
    print(f"data:{data}")
    print(f"a:{a}")
    print(f"b:{b}")
method("data", (1, 2, 3), ["a", "b"], 3, a="a1", b="b2", c="c3")
#代码输出结果:
data:data
a:((1, 2, 3), ['a', 'b'], 3)
b:{'a': 'a1', 'b': 'b2', 'c': 'c3'}

在实际的使用中,动态参数使用地方最多的一般是在封装的包里,来实现一些丰富的功能,比如最常用requests的包中的get方法。

代码如下:

代码语言:javascript
复制
def get(url, params=None, **kwargs):
    r"""Sends a GET request.

    :param url: URL for the new :class:`Request` object.
    :param params: (optional) Dictionary, list of tuples or bytes to send
        in the query string for the :class:`Request`.
    :param \*\*kwargs: Optional arguments that ``request`` takes.
    :return: :class:`Response <Response>` object
    :rtype: requests.Response
    """

    kwargs.setdefault('allow_redirects', True)
    return request('get', url, params=params, **kwargs)

作用呢,就是发送一个get请求,有三个参数,分别是url, params=None, **kwargs。

其中url是一个必选参数,为请求的地址,得让requests知道,你要访问哪个地址吧。

Params是表示传参的参数,支持字典,列表,元组等等,一般是就是在url?之后的内容,多数是用字典。

后面的**kwargs 就表示关键字可选参数,这样参数可有可没有。有的时候不用也可以成功请求,但是有的网站会拒绝无头请求,那么需要在kwargs 中设置上请求头。

不过呢,要是点进去,看下get请求的代码,其实在最外层看到的,是为了方便使用的调用方法,其实在创建请求对象的时候,也设置了很多的参数。

以下来自request包中的Session类中的方法request方法的节选。

代码语言:javascript
复制
    def request(self, method, url,
            params=None, data=None, headers=None, cookies=None, files=None,
            auth=None, timeout=None, allow_redirects=True, proxies=None,
            hooks=None, stream=None, verify=None, cert=None, json=None):
        # Create the Request.
        req = Request(
            method=method.upper(),
            url=url,
            headers=headers,
            files=files,
            data=data or {},
            json=json,
            params=params or {},
            auth=auth,
            cookies=cookies,
            hooks=hooks,
        )
        ......

其实在创建Request对象的时候,也需要很多的参数,但是核心的参数有1个或者2个,其余的是可选的。

当创建具体对象的时候,根据实际的不同的参数,创建不同的对象。这样暴露到最外层的调用方法就比较简洁。对于使用者也比较友好,对于非必选的参数不需要太在意。

那么Java中也可以这样吗?肯定是不能,在参数中根本没办法定义**啊。假设呢,现在也在java中实现一个类似的功能,如何搞呢?

我们先按照Request对象的属性,在Java中创建一个Request的类,下面是随便写的。

代码语言:javascript
复制
//为了方便,这里把所有的字段都设置成string
public class Request {
   private String method;
   private String url;
   private String headers;
   private String files;
   private String data;
   private String json;
   private String params;
   private String auth;
   private String cookies;
   private String hooks;
    //get set 方法
}

通常情况下,如果创建一个Request对象,在类中默认有一个无参构造器,Request() ,但是在初始化的时候,一般都是带参数的,不然这个Request对象请求的发给谁,什么方式去发,都不知道。

所以通常会加一些带参数的构造器,大概如下:

代码语言:javascript
复制
    //部分参数的
    public Request(String method, String url, String headers) {
        this.method = method;
        this.url = url;
        this.headers = headers;
    }

    public Request(String method, String url, String data, String json) {
        this.method = method;
        this.url = url;
        this.data = data;
        this.json = json;
    }
    //全部参数的
    public Request(String method, String url, String headers, String files, String data, String json, String params, String auth, String cookies, String hooks) {
        this.method = method;
        this.url = url;
        this.headers = headers;
        this.files = files;
        this.data = data;
        this.json = json;
        this.params = params;
        this.auth = auth;
        this.cookies = cookies;
        this.hooks = hooks;
    }

在需要新建对象的时候,进行如下的操作:

代码语言:javascript
复制
Request r1 = new Request("1", "2", "3");
Request r2 = new Request("1", "2", "3", "4");
Request r3 = new Request("1", "2", "3","3","3","3", null, null, null,"7");//这里有10个参数

如果参数过多,很容易分不清顺序,好在像IDEA中创建对象的传参的时候是有每个参数是谁的提示。

但这种方式有着明显的缺点就是:它们都不能很好的扩展到大量的可选参数。而且代码的可读性变差。如果有可选参数比较多,那么为了适应每一种构造方式,可能还需要创建多种对象的构造方法。

除此之外,还有一种方法就是JavaBeans模式,也是在代码中最常用的,就是新建一个无参对象,通过set方法进行赋值。

最开始写的Request类就是JavaBean方式创建的,目前多数场景都是使用此方式来创建。

JavaBean是一个遵循特定写法的Java类,它通常具有如下特点:(1)这个Java类必须具有一个无参的构造函数(2)属性必须私有化。(3)私有化的属性必须通过public类型的方法暴露给其它程序,并且方法的命名也必须遵守一定的命名规范。

代码语言:javascript
复制
Request request = new Request();
request.setMethod("1");
request.setUrl("2");
request.setHeaders("3");
// 456789

JavaBeans 模式自身有着严重的缺点: 那就是构造过程被分解到了几个调用中,在构造过程中JavaBean可能处于不一致的状态。

从全部参数都具有set方法来说,并不能明确知道,那些是必须参数,那些是可选参数,可能request 不seturl,这个对象没有实际意义的,并不是最初设计Request 应该达到的状态。

所以接下来,还有一种创建对象的方式就是建造者(Builder)模式。

Builder模式:不直接生成想要的对象,而是让客户端利用所有必要的参数调用构造器(或者静态工厂),得到一个builder对象。然后客户端再builder对象上调用类似于setter方法,来设置每个相关的可选参数。最后,客户端调用无参的build方法来生产不可变对象。

按照建造者模式构建对象的写法应该是这样的:

代码语言:javascript
复制
public class Request {

    private String method;
    private String url;
    private String headers;
    private String files;
    private String data;
    private String json;
    private String params;
    private String auth;
    private String cookies;
    private String hooks;

    public static class Builder {
        private String method;
        private String url;
        private String headers;
        private String files;
        private String data;
        private String json;
        private String params;
        private String auth;
        private String cookies;
        private String hooks;

        public Builder(String method, String url) {
            this.method = method;
            this.url = url;
        }

        public Builder headers(String headers) {
            this.headers = headers;
            return this;
        }

        public Builder files(String files) {
            this.files = files;
            return this;
        }

        public Builder data(String data) {
            this.data = data;
            return this;
        }

        public Builder json(String json) {
            this.json = json;
            return this;
        }

        public Builder params(String params) {
            this.params = params;
            return this;
        }

        public Builder auth(String auth) {
            this.auth = auth;
            return this;
        }

        public Builder cookies(String cookies) {
            this.cookies = cookies;
            return this;
        }

        public Builder hooks(String hooks) {
            this.hooks = hooks;
            return this;
        }

        public Request builder() {
            return new Request(this);
        }
    }
    private Request(Builder builder) {
        method = builder.method;
        url = builder.url;
        headers = builder.headers;
        files = builder.files;
        data = builder.data;
        json = builder.json;
        params = builder.params;
        auth = builder.auth;
        cookies = builder.cookies;
        hooks = builder.hooks;
    }
}

新建对象是这样的:

代码语言:javascript
复制
Request request = new Request.Builder("method", "url")
    .params("1").auth("2").builder();

这样的Builder 构造器中传入必选的参数(Builder("method", "url")),其后添加可选参数(.params("1").auth("2")),想配置什么可以动态的添加,更方便的快速创建一个对象。

这样写法非常简洁,容易让别人读懂,那些是必选,那些是可选,也间接的实现了像Python一样,具有动态参数的语法功能。

这也是《Effective Java》中的第二条,遇到多个构造器参数的时要考虑使用构建器,上文中部分内容引用在书中原话。

Builder模式适用于属性多,但很多为属性是可选的时候,可以优雅的创建对象。如果只有两三个属性,那么就没有必要了。

不过即便是需要使用Builder,也不需要写这么多的代码,在lombok里,提供了@Builer的注解,可以替代上面那些代码。

代码语言:javascript
复制
import lombok.Builder;
import lombok.Data;
@Data
@Builder
public class Request {
    private String method;
    private String url;
    private String headers;
    private String files;
    private String data;
    private String json;
    private String params;
    private String auth;
    private String cookies;
    private String hooks;
}

在实际的Java中的发起http请求的httpcilent包中,一些地方也使用了build模式。比如

RequestConfig 类。

感兴趣的可以自行了解阅读。

好了,今天就分享到这里,我是马拉松程序员,可不止于代码。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2022-04-28,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 马拉松程序员 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档