类型推导与推断

关于 JAVA go php 中类型推导与推断对比。

首先 php 这种弱类型解释型语言其实本身在使用用过程中不是严格需要类型推导的。比如下面的简单代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<?php
class Cat{
function void say(){
echo 'miaomiao~';
}
}
class Dog{
function void eat(){
echo 'wangwang';
}
}

function void runCat($var){
$var->say();
}

runCat(new Cat());

上面的代码是可以运行且不报错的,那么类型推导的话题原因是从何而来的呢。现在在对比看一下 java 的代码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class Main {
static class Cat{
public void say(){
System.out.println("miaomiao");
}
}
static class Dog{
public void eat(){
System.out.println("wangwang");
}
}

static void runCat(Object obj){
obj.say();
}

public static void main(String[] args) {
runCat(new Cat());
}
}

上述的代码是不不可通过编译的。其实在此处,所提到的类型推导就出现了需要的点。关于java的代码是可以这样处理的。

1
2
3
4
static void runCat(Object obj){
Cat c = (Cat) obj;
c.say();
}

这样处理下 java 的代码就可以正常运行了。

但是上述两个例子传递的内容不是 Cat , 而是 Dog。

其中php报错为方法不存在,java可正常编译,而运行时报org.example.Main$Dog cannot be cast to org.example.Main$Cat

这次话题就正式展开了,首先这种场景的类型推导/转化是因为在代码中使用了一个范围非常大的参数变量 php 中体现为没有加类型限定。java 中体现为使用 object 作为形参。对应到 go 则就是用 any 作为形参。

关于 go 用 any 作为形参时的类型转化有这么几种方式。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
type Cat struct {
}

func (itself *Cat) Say() {
fmt.Println("miaomiao")
}

func runCat(c any) {
if cEntity, ok := c.(Cat); ok {
cEntity.Say()
}
}

func runCat2(c any) {
switch entity:=c.(type) {
case Cat:
entity.Say()
break
}
}

func runTmp(cmd *cobra.Command, args []string) {
runCat(new(Cat))
}

看到这其实已经有些相似之处了。现在再看下面两份代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<?php
class Cat{
function void say(){
echo 'miaomiao~';
}
}
class Dog{
function void eat(){
echo 'wangwang';
}
}

function void runCat($var){
if($var instanceof Cat){
$var->say();
} else {
throw new Exception(get_class($var).' can\'t cast to Cat');
}
}

runCat(new Cat());
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26

public class Main {
static class Cat{
public void say(){
System.out.println("miaomiao");
}
}
static class Dog{
public void eat(){
System.out.println("wangwang");
}
}

static void say(Object obj) throws Exception {
if(obj instanceof Cat){
Cat c = (Cat) obj;
c.say();
} else {
throw new Exception("org.example.Main$Dog cannot be cast to org.example.Main$Cat");
}
}

public static void main(String[] args) throws Exception {
say(new Dog());
}
}

上面的代码显式的展示了类型报错,其实就知道这种情况下的处理方式。首先 php 是运行时报错,所以编码时要提前考虑,如果假如了类型限定需要 phpstan 做检测。而 java 的编译也不会对类型转化做严格的检测,等到运行时开始报错。go 的类型推导相对严格,如果没有处理好,则编译阶段就无法通过。如果真的需要用一个 any/object/param 接收不同类型需要提前做好处理。当然正常的情况下用 object 这种变量传参肯定是不推荐的。但是如果用了且出现多个不同的实参类型,则要考虑周到也就是上述代码中 else 中的处理逻辑,否则运行过程中代码就会把错误扔出来了。这个事情的起因也是因为一次公司内的微服务之间通讯用 object 作为协议参数。但是传参方更改了实参。接收方只做了一种类型的强制转化,没有做类型推导判断,最终出现了报错,从源头上来说是服务方采用了不合理的协议设计。但是实及表现上来说 java 这种 object 类型转化更类似于类型断言,如果需要使用的话,还是需要利用 instanceof 来对类型进行真正的检测,从而对类型进行周全的处理。