-
2020-08-03 07:50:53
我最近给我fork的项目QuickTheories增加了一个接口:
@FunctionalInterface public interface QuadFunction<A, B, C, D, E> { E apply(A a, B b, C c, D d); }
这让非常好奇一个方法能够有多少个类型参数呢?据我所知,Java的语言规范并没有提到这个问题。1
关于在实现上这个阈值的定义,我有两个猜测:
- 编译器会强制一个可预测的阈值,例如255或者65535。
- 由于实现细节的原因,编译器的异常处理会施加意想不到的限制。
我不想通过我薄弱的C++技能来测试源代码,所以我决定直接来测试编译器2。我写了一个Python脚本,通过二分法找到一个会触发错误的最小值。完整的代码请见连接Github Repo。
最直接的办法就是生成方法。幸运的是,我们不必使用任何已有的类型参数,只需要按照<A,B,C..>的形式来生成:
def write_type_plain(count): with open('Test.java', 'w') as f: f.write("public class Test {\n") f.write("public <") for i in range(count): if (i > 0): f.write(", ") f.write("A" + str(i + 1)) f.write("> void testMethod() {}") f.write("}")
运行这个二分法的代码会有如下输出:
>>> error: UTF8 representation for string "<A1:Ljava/lang/Objec..." is too long for the constant pool >>> largest type: 2776
这个错误让人有点费解,但是从事后来看还是可以理解的。编译器生成的类文件包含多个字符串,包括每个方法的方法签名。这些字符串保存在常量池内,而常量池的内容有最大65535字节数的限制,这个是JVM的所定义的。
所以,我之前的猜测都不是完全的正确。类型参数的最大个数是一个意料之外的值,而不是一个确定值。但是,编译器的实现本身并不是导致错误的原因3。相反,是JVM类文件的格式要求限制了类型参数可使用的数量。其实JVM对泛型本身一无所知。
这同时也表示类型参数的最大个数取决于你写的方法代码4。我尝试用另外一种类型参数的编码方案(先前链接文中的
write_type_compact
),使用全部合法的ASCII字符。这个实现是有点繁琐的,因为字符0-9是合法的,但不能作为标识符的首字母,并且Java关键字也不能作为类型参数。我仅仅将if
和do
替换为等长的UTF-8字符。采用这种更紧凑的编码方案让类型参数的个数从2776提升到了3123。还是有一些不太方便的地方,例如
_A
是一个合法的Java标识符,但是_
不是。我的编码在不使用_
作为首字幕的情况下,最高生成了3392个2字节的类型参数。所以我觉得不用考虑_
作为首字母的情况了。另外一个技巧
通过反编译类文件,我观察到65536个字符中大部分都不是我生成的类型参数,而是重复的字符串
Ljava/lang/Object;
。这是因为类型参数没有包含额外的信息,所以类文件将其视为Object
的继承,并将它们编入方法签名内。我通过修改我的生成器来优化这个问题。循环的关键代码修改为:
s = type_var(i) f.write(s) if (s != 'A'): f.write(" extends A")
除开一个实例之外,所有的类型参数都从继承
java/lang/Object
改为继承A
。这个修改将类型参数的数量提升到9851个。类型参数的数量提升了非常多,而我所使用的编码方法还可以继续改进。例如使用非ASCII unicode标识符,不过我已经比较满意现在的效果了。
这些都不重要
在实际情况中是不太可能达到上述数量限制的。代码生成时可能会达到语言或者编译器的某些极限,就算罕见的遇到了生成上百个类型参数的情况,那距离几千个的限制仍然还相距很远。
尽管如此,如果我是规则的制定者,我将不允许任何类或者方法使用超过255个类型参数的情况。即使只影响了百万分之一的程序,有明确的限制会更好。
- §4.4, §8.1.2, §9.1.2, §8.4.4, §8.8.4 这些章节都和方法或者类的类型参数有关,但是都没有指明允许有多少个类型参数。
- 当我写这段话时,我想起了Hotspot是C++写的,javac是Java写的。就算这样我依然会选择做代码实验,而不是阅读代码。阅读别人代码是种煎熬
- 逗号之后的空格不会影响,因为编译器会规范化它的输出。
- 这也表示与我使用哪个JVM无关。为了完整性,我在Fedora 29上使用了1.8.0_191-b13版本的OpenJdk。
本文作者:justinblank, 翻译:1 Way 原文链接:https://justinblank.com/experiments/howmanytypeparameterscanajavamethodhave.html 译文首发:http://blog.didispace.com/howmanytypeparameterscanajavamethodhave/
本文有spring4all技术翻译组完成,更多国外前沿知识和干货好文,欢迎关注公众号:后端面试那些事儿。
更多相关内容 -
Java中方法的参数传递机制以及形参个数可变的方法
2019-05-17 16:10:21方法,必须由其所在类或对象调用才有意义。若方法含有参数: 形参:方法声明时的参数 实参: 方法调用时实际传给形参的参数值 Java的实参值如何传入方法呢? Java里方法的参数传递方式只有一种: 值传递。 ...方法,必须由其所在类或对象调用才有意义。若方法含有参数:
-
形参:方法声明时的参数
-
实参: 方法调用时实际传给形参的参数值
Java的实参值如何传入方法呢?
Java里方法的参数传递方式只有一种: 值传递。 即将实际参数值的副本(复制品)传入方法内,而参数本身不受影响。
一、基本数据类型参数传递
public static void swap(int a, int b) { // 下面三行代码实现a、b变量的值交换 int tmp = a; a = b; b = tmp; System.out.prinln("swap方法里,a的值是" + a + ";b的值是" + b); } public static void main(String[] args) { int a = 6; int b = 9; swqp(a, b); System.out.println("交换结束后,变量a的值是" + a + ";变量b的值是" + b); }
运行下面的程序,结果如下:
swap方法里,a的值是9;b的值是6 交换结束后,变量a的值是6;变量b的值是9
二、引用数据类型的参数传递:将实参引用数据类型变量的“地址值”传递给形参
class DataWrap { int a; int b; } public class ReferenceTransferTest { public static void swap(DataWrap dw) { int tmp = dw.a; dw.a = dw.b; dw.b = tmp; System.out.println("swap方法里,a成员变量的值是" + dw.a + ";b成员变量的值是" + dw.b); } public static void main(String[] args) { DataWrap dw = new DataWrap(); dw.a = 6; dw.b = 9; swap(dw); System.out.println("交换结束后,a成员变量的值是" + dw.a + ";b成员变量的值是" + dw.b); } }
执行上面的程序,看到如下运行结果:
swap方法里,a成员变量的值是9;b成员变量的值是6 交换结束后,a成员变量的值是9;b成员变量的值是6
看似swap方法交换了main方法中的变量的值,实则不然。
程序从main()方法开始执行,main()方法开始创建了一个DataWrap对象,并定义了一个dw引用变量来指向DataWrap对象,这是一个与基本类型不同的地方。下图是main()方中创建了DataWrap对象后的存储示意图:
接下来,main()方法开始调用swap()方法,main()方法并未结束,系统会分别为main()和swap()开辟出两个栈区,用于存放main()和swap()方法的局部变量。调用swap()方法时,dw变量作为实参传入swap方法,同样采用值传递方式,不过,dw是一个引用(也就是一个指针),它保存了DataWrap对象的地址值,当把dw的值赋给swap()方法的dw形参后,即swap()方法的dw形参也保存了这个地址值,也会引用到堆内存中的DataWrap对象。如下图所示:
所以,关键在于dw只是一个引用变量,当程序在swap()方法中操作dw形参时,实际操作的还是对内存中的DataWrap对象。
三、形参个数可变
从JDK 1.5之后,Java允许定义形参个数可变的参数。如果在定义方法时,在最后一个形参的类型后增加三点(…),则表明该形参可以接受多个参数值,多个参数值被当成数组传入。
形参个数可变的参数本质就是一个数组参数,也就是说,下面两个方法签名的效果完全一样。
// 以可变个数形参来定义方法 public static void test(int a, String... books);
public static void test(int a, String[] books);
两种形式的区别
-
是调用两个方法时存在差别,对于第一个方法,调用 时更加简洁,如下:
test(5, “小明”, “小红”);
传给books参数的实参数值无须是一个数组。但如果采用数组形参来声明方法(即上面的第二种方法),调用时必须传给该形参一个数组。 -
数组形式的形参可以处于形参列表中的任意位置,但个数可变的形参只能处于形参列表的最后。也就是说,一个方法中最多只能有一个长度可变的形参。
参考链接:https://blog.csdn.net/static_eye/article/details/79210866
-
-
python笔记 2021/01/30 函数,参数,形参和实参,参数的传递
2021-02-02 21:19:44函数也是一个对象 函数用来保存一些可执行的代码,并且可以在需要时,对这些语句进行多次调用 语法 def 函数名([形参1,形参2,形参3…]): 代码块 注意:函数名必须符合标识符的规范(可以包含字母、数字、下划线...8. 函数
8.1 函数概念
-
函数也是一个对象
-
函数用来保存一些可执行的代码,并且可以在需要时,对这些语句进行多次调用
-
语法
def 函数名([形参1,形参2,形参3…]):
代码块注意:函数名必须符合标识符的规范(可以包含字母、数字、下划线但是不能以数字开头)
-
函数对象和调用函数:拿print函数举例子,print是函数对象 print()是调用函数
8.2 函数的参数
简单的来说,函数是一个处理的过程形如fun()。但是如果要用函数将输入的数据进行处理,则要将数据导入进函数,于是就可以将数据在fun后的()输入,从而以参数的形式进入到函数中,参数的形式可以是字符串、数字、列表、字典、甚至是已经定义好的函数。
1. 形参和实参
- 形参(形式参数) 定义形参就相当于在函数内部声明了变量,但是并不是赋值
- 实参(实际参数)指定了形参,那么在调用函数时必须传递实参,实参将会赋值给对应的形参,简单来说有几个形参就要有几个实参
- 形参就是形式上的参数,只是定义好了的,调用的时候需要进行参数传递,用实参进行传递
- 任意类型的数据(包括列表、字典、字符串、元组等)都是可以通过函数传递的
2. 函数的传递方式
- 默认参数: 定义形参时,可以为形参指定默认值。指定了默认值以后,如果用户传递了参数则默认值不会生效。如果用户没有传递,则默认值就会生效
- 位置参数:位置参数就是将对应位置的实参赋值给对应位置的形参
- 关键字参数 : 关键字参数可以不按照形参定义的顺序去传递,而根据参数名进行传递
- 混合使用位置参数和关键字参数的时候必须将位置参数写到关键字参数前面去
3. 不定长参数
- 带 * 的形参:
1.定义函数时,可以在形参前面加一个 * ,这样这个形参可以获取到所有的实参,它会将所有的实参保存到一个元组中。* 形参只能接受位置参数,不能接受关键字参数
2.带 * 号的形参只能有一个,可以和其他参数配合使用
3.习惯上用*args来表示 - 带 ** 的形参:
1.** 形参可以接收其他的关键字参数,它会将这些参数统一保存到字典当中。字典的key就是参数的名字,字典的value就是参数的值
2.** 形参只有一个,并且必须写在所有参数的后面
3.习惯上用**kwargs来表示 - *和**都只能存在一个,但是两种可以同时使用。
- 参数arg、*args、**kwargs三个参数的位置必须是一定的。必须是(arg,*args,**kwargs)这个顺序,否则程序会报错。
4. 参数的解包
• 传递实参时,也可以在序列类型的参数前添加星号,这样它会自动的将序列中元素依次作为参数传递
• 要求序列中的元素的个数必须和形参的个数一致8.3 作业
- 打印名片程序:输入姓名,电话号码,性别,最后打印出来名片
• 控制姓名长度为6-20
• 电话号码长度11
• 性别只能允许输入男或女
• 每一样信息不允许为空
def card(): while True: name = input('请输入你的名字:') if 6 < len(name) < 20: break else: print('名字太长或太短,重新输入') while True: telephone = input('请输入电话号码:') if len(telephone) == 11: if telephone.isdigit(): # isdigit判断字 break else: print('手机号码由数字组成') continue else: print('请输入正确的手机号码') continue while True: gender = input('请输入你的性别') if gender == '男' or gender == '女': break else: print('难道你从泰国回来啦?') continue return name, telephone, gender r = card() print(r)
- 使用函数求前20个斐波那契数列斐波那契数列:1,1,2,3,5,8,13,21…即: 起始两项均为1,此后的项分别为前两项之和
def fun(n): a = b = 1 list1 = [1, 1, 2] for i in range(n): if i == 0: list1.append(a) if i > 0: a, b = b, b + a list1.append(a)
- 编写一段代码,定义一个函数求1-100之间所有整数的和,并调用该函数打印出结果
def m(a,b): m = 0 for i in range(a,b+1): m = m + i print(m) m(1,100)
-
-
java中一个方法可以有多少个参数
2017-12-21 12:39:21java中一个方法可以有多少个参数 疯子又勇9 | 浏览 289 次 |举报 我有更好的答案 2017-09-29 最佳答案 java的方法可以动态传入参数,比如methodA(String... values),它相当于传进入...2017-09-29 最佳答案java的方法可以动态传入参数,比如methodA(String... values),它相当于传进入一个values的数组,因此在你的内存支持的情况下,参数个数可以是无限个的。
其他回答
这个没有限制;不过参数过多建议封装成新的对象进行传递dhweicheng 发布于2017-09-29在你的内存支持的情况下,参数个数可以是无限个的。西安_尚学堂 发布于2017-09-29
-
函数实参和函数形参可以同名
2021-03-07 21:38:37(4.0分)【其它】设计一个一维数组的排序函数p_sort,并调用它对10个整数进行排序。p_sort函数原型如下: void p_sort(int *p,int n) 其中,p是指向int型一维数组的指针变量,n是数组长度。 (16.0分)【单选题】有如下... -
js里最大的形参个数
2017-12-08 18:25:14利用apply来测试,因为apply()改变this指向并执行,第二个参数是一个数组,通过数组传到方法里的形参上,这样就不用在方法上编写多个形参。 把循环生成的数组传入方法中,测试 谷歌浏览器 传300000个实参: ... -
php中函数的形参与实参的问题说明
2020-12-18 07:12:35然而,当 实参个数>形参个数 时,php是不会报错的,它只会取前面的几个参数,多余的则将会丢弃。 在PHP中编写函数,一般情况下调用函数的时候,改变的值都是形参而不是实参.但是如果在形参中加入地址符时候就会改变... -
python中形参的几种传递方式
2022-03-21 22:10:54第一种--顺序传参(下面两个都是正确的形式): def add(a,b): print(a) print(b) add(1,2) def add2(b,a): print(a) print(b) add2(1,2) 第二种--默认传参,记住默认传参必须在后面 正确的形式: def ... -
形参可以是常量、变量或表达式 C. 实参可以为任意类型 D. 形参应与其对应的实参类型一致
2021-05-20 06:06:46形参可以是常量、变量或表达式 C. 实参可以为任意类型 D. 形参应与其对应的实参类型一致更多相关问题()是调整商事关系的法律规范。18.下列不属于“政善治”的观点是17、周易是哪个朝代的易经?称为可变脂的是衡量一... -
可变个数形参的方法
2021-05-07 04:05:302.2 当调用可变个数形参的方法时,传入的参数个数可以是:0个,1个,2个,。。。 2.3 可变个数形参的方法与本类中方法名相同,形参不同的方法之间构成重载 2.4 可变个数形参的方法与本类中方法名相同,形参类型也相同... -
C++中数组作为函数形参的几种形式
2022-02-14 15:15:57C++中数组作为形参的几种形式 -
方法详解、方法参数传递机制、形参个数可变
2019-04-21 19:12:47方法是类或对象的行为特征的抽象,方法是类或对象最重要...在结构化程序语言里,函数是一等公民,整个软件由一个个的函数组成,在面向对象编程语言里,类才是一等公民,整个系统由一个一个的类组成,因此在Java语言... -
写函数,统计字符串中有几个字母,几个数字,几个空格,几个其他字符,并返回结果
2021-05-17 11:54:19import string #调用字符串函数 def add(str): a = 0 b = 0 g = 0 d = 0 for c in str: if c.isalpha(): #函数:检测字符串中是否全为大写或小写字母 a+=1 #是+1 elif c.isdigit():#函数:检测字符串中是否全为数字 ... -
const形参和实参
2021-05-22 01:39:30当形参是const时,必须要注意关于顶层const的讨论。如前所述,顶层const的作用于对象本身:const int ci=42; //不能改变ci,const是顶层的int i=ci; //正确:当拷贝ci时,忽略了它的顶层constint *const p=&i; ... -
springmvc方法形参上可以用多个@RequestBody?
2018-04-17 18:07:52至于要将一个request body中的content反序列化成几个Java实例是另外一个问题。有三个解决方向:创建一个新的entity,将你的两个entity都进去。这是最简单的,但是不够“优雅”。用Map<String, Ob... -
C++三种函数形参形式
2020-08-04 19:16:38C++三种函数形参简介为什么使用指针或引用作为形参使用引用和指针有什么区别使用常量引用 简介 普通形参 :普通形参只是把实参的值拷贝给形参,对形参的改变不会影响到实参。 指针形参 :指针形参传入一个... -
关于c语言函数实参形参传递的几种方式总结
2019-07-28 11:21:54关于函数实参形参传递的几种方式下面用简单的打印函数来实现 1.常量传递,实参是常量,形参是变量 #include <stdio.h> #define PI 3 //定义了一个常量PI ,其中值为3 void print(int n); //声明了一个print... -
形参个数不确定时如何去写函数
2020-02-23 10:53:20处理大量的数据的时候我们可以用指针的方法,我们可以在形参的定义中给出数组的首地址以及参数的个数n,格式如下average(double *a,int n)这个方法就是把a这个数组的首地址给出 #include using namespace std; int... -
为什么后置++和后置--是单目运算符却有一个int形参
2020-11-06 18:56:33为什么后置++和后置--是单目运算符却有一个int形参 今天做到了这样的一道题 我是这么想的:友元函数,那也就是说没有this指针,所以操作数都应该在函数的参数列表里。 A、+是双目运算符,参数列表里只有1个形参,很... -
java一个方法形参有两个,如何在调用的时候只传入一个参数
2016-04-16 11:10:08如题,有一个方法里两个形参,我另一个文件类中一个方法想要那个方法的返回值,可是第二个参数在这里用不到,能否只传第一个参数 如何实现,前提这个类不能继承后重写方法,因为多人合同写的。 -
书上说C语言函数的参数分形参和实参两种,它们有什么分别?使用时要注意什么?
2021-05-19 19:18:25谢邀。...例如,我们需要打印出 4+6 的值,相关的C语言函数可以如下定义:的确,add 函数能够打印出 4+6 的值,解决了问题。但是,可能还会有类似,但不相同的问题出现,比如需要打印出 3+4 的值... -
形参和实参的区别
2021-06-03 09:48:21在函数定义中出现的参数可以看做是一个占位符,它没有数据,只能等到函数被调用时接收传递进来的数据,所以称为形式参数,简称形参。 实参(实际参数) 函数被调用时给出的参数包含了实实在在的数据,会被函数内部... -
C语言中形参和实参详解及实例代码
2021-05-21 00:39:44形参出现在函数定义中,在整个函数体内都可以使用,离开该函数则不能使用。实参出现在主调函数中,进入被调函数后,实参变量也不能使用。形参和实参的功能是作数据传送。发生函数调用时,主调函数把实参的值传送给被... -
[译] 一个简单方式教你记住Kotlin的形参和实参
2018-10-17 00:13:31它可以是具体的类型或者另一个类型形参 。所以你会发现ArrayList 中的T和List中的T实际上有所不一样的,就是类型形参和类型实参的区别。 第二、初学者很容易走进一个误区,认为类似T,K,V这种没有具体意义的... -
Python带*参数和带**参数
2020-05-09 17:36:39一、带*形参 1、格式:*形参名,如*args 2、数据类型:元组 3、传参方式:接收多个位置参数,也可以不传参。 4、位置:在一个函数里只能有一个,且放在末尾。 二、带**形参 1、格式:**形参名,如**kwargs 2、数据... -
形参的种类及其能否改变实参
2019-03-10 18:38:29**的作用是让函数运行时不开辟新的空间,不产生新的形参,而是根据地址直接到函数外找到传入的实参,对实参进行操作,因此只要有&amp;都是可以改变形参的。 在这里说明,由于数组名的实质是整个数组首元素... -
关于Java中形参与实参的理解
2021-03-18 11:53:06今天阅读了一个写的非常棒的博文,通过此博文再次复习了Java中参数传递的知识(即值传递与引用传递的区别)。参考网站http://www.cnblogs.com/binyue/p/3862276.html。下面我将通过次博文记录自己的理解,还望能够帮助... -
JS: 数值方法的特殊调用方式以及函数的形参个数
2020-07-17 13:21:13数组表达式运算练习题 let res = [1, 2, 3, 4, 5][0..toString.length]; console.log(res); 看看结果: ...相当于数值,所以数字访问toString()方法有2种方式: 123..toString(); 123['toString'] -
Python的形参和实参使用方式
2020-12-15 23:40:02Python的形参和实参使用方式形参可以设置参数默认值,设置遵循从右至左原则例如:fun(x=0,y=1),fun(x,y=1),但不可以是fun(x=1,y)形参设置可以为数字字符串变量、元组和字典等任意类型数据,元组形参是在变量名前加*... -
# 写一个函数,统计字符串中有几个字母,几个数字,几个空格,几个其他字符,并返回结果。
2020-05-07 15:19:15import re def statistical(s): letter = "[a-zA-Z]" digital = "[0-9]" blank = "[ ]" letter_numbers = 0 digital_numbers = 0 blank_numbers = 0 for i in s: if(re.match(letter,i)) !...