最新发布《数智时代的AI人才粮仓模型解读白皮书(2024版)》,立即领取! 了解详情
写点什么

PHP 8:类型系统改进

  • 2023-07-25
    北京
  • 本文字数:8834 字

    阅读完需:约 29 分钟

PHP 8:类型系统改进

本文属于专题文章《深入浅出 PHP 8》


根据w3tech的数据,PHP 仍然是 Web 上使用最广泛的脚本语言之一,77.3%的网站使用 PHP 进行服务器端编程。PHP 8 带来了许多新特性和其他改进,我们将在本系列文章中进行探讨。PHP 8.0 添加了对多个函数和方法相关特性的支持,其中一些是对现有特性的改进,而另一些则是全新的特性。PHP 8.1 中增强的可调用语法可用于通过可调用对象创建匿名函数。命名函数参数可以与位置参数一起使用,另外还有一个好处,即命名参数没有顺序,可以通过它们的名称来传达含义。纤程(Fiber)是可中断的函数,增加了对多任务的支持。


在本文中,我们将讨论 PHP 8、8.1 和 8.2 对 PHP 类型系统的扩展,其中包括联合类型、交集类型和mixed 类型,以及返回类型staticnever


此外,PHP 8 还支持独立类型truenullfalse

一些定义


在 PHP 中,类型声明与类属性、函数参数和函数返回类型一起使用。我们经常使用各种定义从类型系统方面描述一种语言:强/弱,动态/静态。


PHP 是一种动态类型语言。所谓动态类型是指类型检查是在运行时进行的,与之相对的是在静态编译时进行类型检查。PHP 默认是弱类型的,这意味着它在运行时支持的隐式类型转换规则比较少。不过,在 PHP 中可以启用强类型。


PHP 会在不同的上下文中使用类型:


  • 独立类型:可以在类型声明中使用的类型,如intstringarray

  • 字面量类型:除了值的类型之外,还对值本身进行检查的类型。PHP 支持两种字面量类型:truefalse

  • 单元类型:保存单个值的类型,如null。除了简单类型之外,PHP 8 还引入了复合类型,如联合类型交集类型联合类型是多个简单类型的并集。其值只需匹配联合类型中的一种类型。联合类型可用于指定类属性、函数形参的类型或函数返回类型。新增类型mixed 是联合类型的一种特殊类型。


PHP 8.1 还增加了交集类型,用于说明那种是多个类类型的交集的类类型。它还增加了两个新的返回类型。如果函数无返回值,则使用返回类型never 。例如,当函数抛出异常或调用exit()时,可能出现这种情况。返回类型static意味着返回值必须是调用该方法的类的实例。

联合类型


如果你熟悉文氏图,那么你可能还记得集合的并集和交集。为了支持简单类型的并集,PHP 8 引入了联合类型。用于声明联合类型的语法如下:


Type1|Type2|....|TypeN
复制代码


我们首先看个例子。在下面的脚本中,$var1属于联合类型int|string|array 。它被初始化为一个整数值,然后它被设置为联合类型声明中包含的其他类型的值。


<?php
class A{
public int|string|array $var1=1; }
$a= new A();echo $a->var1;$a->var1="hello";echo $a->var1;$a->var1=array( "1" => "a", "2" => "b",);var_dump($a->var1);
复制代码


上述脚本输出如下:


1helloarray(2) { [1]=> string(1) "a" [2]=> string(1) "b" }
复制代码


由于 PHP 是弱类型语言,如果将$var1的值设置为float1.0,就会执行隐式转换。下面的脚本将输出1


<?php
//declare(strict_types = 1);class A{
public int|string|array $var1=1; }
$a= new A(); $a->var1=1.0;echo $a->var1;
复制代码


然而,如果在声明时启用了强类型declare(strict_types = 1) ,那么就不能将$var1 设置为1.0 ,否则会报下面这个错:


Uncaught TypeError:无法将浮点数赋给array|string|int类型的属性A::$var1
复制代码


有时候,在弱类型的情况下,可以将值转换为密切相关的类型,但这种转换并非总能执行。例如,我们不能像下面的脚本那样,给联合类型(int|array )的变量赋字符串值:


<?php
class A{
public int|array $var1=2; }
$a= new A(); $a->var1="hello";echo $a->var1;
复制代码


上述脚本会报如下错误:


Uncaught TypeError: 无法将字符串赋给array|int类型的属性A::$var1
复制代码


我们看一个稍微复杂一点的例子。下面的脚本在类属性声明、函数参数和函数返回类型中使用了联合类型。


<?php
class A{
public string|int|bool $var1=true;
function fn1(string|int|array $a, object|string $b):
string|bool|int { return $a;}
}$a=new A();echo $a->var1;echo $a->fn1("hello","php");
复制代码


输出如下:


1hello
复制代码

联合类型中的 null


联合类型可以为空,在这种情况下,null是联合类型声明中的类型之一。在下面的脚本中,类属性、函数参数和函数返回类型都声明为可空的联合类型。


<?php
class A{
public string|null $var1=null;
function fn1(string|int|null $a=null, object|false|null $b=null):
string|bool|null { return null;}
}$a=new A();echo $a->var1;echo $a->fn1();
复制代码

联合类型中的 false


伪类型false可用于联合类型。在下面的示例中,false类型用于类属性声明、函数参数和函数返回类型,它们全都声明为联合类型。


<?php
class A{
public string|int|false $var1=1;
function fn1(string|int|false $a, false|string $b):
string|false|int { return $a;}
}$a=new A();echo $a->var1;echo $a->fn1("hello",false);
复制代码


输出如下:


1hello
复制代码


如果在联合类型中使用了bool,则不能再使用false,那会被认为是重复声明。考虑下面的脚本,其中一个函数在声明参数时使用了包含falsebool的联合类型。


<?phpfunction fn1(string $a, bool|string|false  $b): object {    return $b;} 
复制代码


执行上述脚本会显示如下错误信息:


重复类型false是多余的
复制代码

联合类型中的 class 类型


Class 类型可以用于联合类型。如下所示,在联合类型中使用 class 类型A


<?php
class A{} function fn1(string|int|A $a, array|A $b): A|string { return $a;}$a=new A();var_dump(fn1($a,$a));
复制代码


输出如下:


object(A)#1 (0) { }
复制代码


但是,如果联合类型中使用了object 类型,就不能再使用 class 类型了。下面的脚本在联合类型中同时使用了 class 类型和object


<?php
class A{} function fn1(object|A $a, A $b): A { return $a;}
复制代码


执行上述脚本会显示如下错误信息:


类型A|object中同时包含object和class类型,这是多余的
复制代码


如果联合类型中使用了iterable,则不能再使用arrayTraversable。下面的脚本在联合类型中同时使用了arrayiterable


<?php
function fn1(object $a, iterable|array $b): iterable { }
复制代码


执行上述脚本会显示如下错误信息:


类型iterable|array中同时包含iterable和array类型,这是多余的
复制代码

联合类型与类继承

如果一个类继承了另一个类,那么联合类型可以单独声明两个类,或者只声明超类。例如,在下面的脚本中,类C继承了类B,类B又继承了类A。然后,在声明联合类型的函数参数时把类ABC都包含了进去。


<?php
class A {function fn1(){ return "Class A object";}}
class B extends A {function fn1(){ return "Class B object";}}
class C extends B {function fn1(){ return "Class C object"; }}
$c = new C();
function fn1(A|B|C $a, A|B|C $b): string { return $a->fn1();}
echo fn1($c,$c);
复制代码


输出如下:


Class C object
复制代码


或者,在声明fn1 的参数时可以只使用A 类型,输出不变:


  function fn1(A $a, A $b): string {         return $a->fn1();}
复制代码

联合类型中的 void


不能在联合类型的返回类型中使用void 。执行如下脚本:


<?php
function fn1(int|string $a, int|string $b): void|string { return $a;}
复制代码


将显示如下错误信息:


Void只能作为独立类型使用
复制代码

联合类型的隐式类型转换


前面我们提到过,如果不启用强类型,那么当一个值与联合类型中的任何类型都无法匹配时,它将转换为与之密切相关的类型。但是,哪些是密切相关的类型呢?隐式转换会按以下优先顺序进行:


  1. 整型

  2. 浮点型

  3. 字符串型

  4. 布尔型例如,在下面的脚本中,字符串值"1"将被转换成一个浮点数:


<?php
class A{
public float|bool $var1=true; }
$a= new A(); $a->var1="1";var_dump($a->var1);?>
复制代码


输出如下:


float(1)
复制代码


但是,如果联合类型中包含int,则输出为int(1)。在下面的脚本中,联合类型(int|float)变量被赋值为字符串"1.0"


<?php
class A{
public int|float $var1=1; }
$a= new A(); $a->var1="1.0";var_dump($a->var1);?>
复制代码


输出如下:


float(1)
复制代码


在下面的脚本中,字符串值"true"会被解释成string 值,因为联合类型中包含string


<?php
class A{
public float|bool|string $var1=1; }
$a= new A(); $a->var1="true";var_dump($a->var1);?>
复制代码


输出如下:


string(4) "true"
复制代码


但是,在下面的脚本中,字符串值"true"会被转换成bool 值,因为联合类型中不包含string


<?php
class A{
public float|bool $var1=1; }
$a= new A(); $a->var1="true";var_dump($a->var1);?>
复制代码


输出如下:


bool(true)
复制代码


下面这个例子的输出难以预测。这个脚本将一个字符串值赋给联合类型为int|bool|float的变量。


<?php
class A{
public int|bool|float $var1=1; }
$a= new A(); $a->var1="hello";var_dump($a->var1);?>
复制代码


输出如下:


bool(true)
复制代码


string被转换为bool值,因为它无法转换为intfloat

新增的 mixed 类型


PHP 8 引入了一个名为mixed的新类型,它相当于联合类型object |resource|array|string|int|float|bool|null。例如,在下面的脚本中,mixed被用作类属性类型、函数参数类型和函数返回类型。启用强类型是为了证明mixed不受强类型所影响。


<?phpdeclare(strict_types = 1);
class A{
public mixed $var1=1; function fn1(mixed $a):mixed{ return $a;}}
$a= new A(); var_dump($a->fn1(true));var_dump($a->var1);$a->var1="hello";var_dump($a->var1);
复制代码


显然,mixed非常灵活,可以输出不同类型:


bool(true) int(1) string(5) "hello"
复制代码


在联合类型中将其他标量类型与混合类型一起使用是多余的,因为mixed类型是所有其他标量类型的联合类型。请看下面的脚本,在一个联合类型中同时使用intmixed类型。


<?php class A{
function fn1(int|mixed $a):mixed{ return $a;}}
复制代码


执行脚本会显示如下错误信息:


类型mixed只能作为独立类型使用
复制代码


类似地,mixed 也不能和任何类类型一起使用。下面的脚本会显示同样的错误信息:


<?php class A{}class B{
function fn1(A|mixed $a):mixed{ return $a;}}
复制代码


子类的方法可以缩小返回类型mixed。例如,子类中的函数fn1将返回类型mixed缩小为array


<?php class A{public function fn1(mixed $a):mixed{ return $a;}}class B extends A{
public function fn1(mixed $a):array{ return $a;
}
复制代码

新增的独立类型 null、false 和 true


在 PHP 8.2 之前,null类型是 PHP 的单元类型,即保存单个值的类型。类似地,false类型是bool类型的字面量类型。不过,nullfalse类型只能在联合类型中使用,而不能作为独立类型使用。要证明这一点,可在 PHP 8.1 及更早的版本中运行如下脚本:


<?php
class A{
public null $var1=null;
}$a=new A();echo $a->var1;
复制代码


在 PHP 8.1 中,该脚本会输出以下错误信息:


Null不能作为独立类型使用
复制代码


类似地,为了证明在 PHP 8.1 或更早的版本中,false类型不能作为独立类型使用,可运行以下脚本:


<?php
class A{
public false $var1=false;
}
复制代码


在 PHP 8.1 中,该脚本会生成如下错误信息:


False不能作为独立类型使用
复制代码


PHP 8.2 支持将nullfalse作为独立类型来使用。下面的脚本使用null作为方法参数类型和方法返回类型。


<?php
class NullExample { public null $nil = null; public function fn1(null $v): null { return null; }}
复制代码


null不能使用?null 显式标记为可空。要证明这一点,可运行以下脚本:


<?php class NullExample {    public null $nil = null;     public function fn1(?null $v): null { return null;  }}
复制代码


该脚本会生成以下错误信息:


null不能标记为可空
复制代码


以下脚本将false 作为独立类型来使用:


<?php class FalseExample {    public false $false = false;     public function fn1(false $f): false { return false;}}
复制代码


nullfalse 可以用于联合类型,如下所示:


<?php class NullUnionExample {    public null $nil = null;     public function fn1(null $v): null|false { return null;  }}
复制代码


此外,PHP 8.2 还新增了一个类型true,它可以用作独立类型使用。下面的脚本使用true作为类属性类型、方法参数类型和方法返回类型。


<?php
class TrueExample { public true $true = true; public function f1(true $v): true { return true;}}
复制代码


true 类型不能和false一起用于联合类型,如下所示:


<?php class TrueExample {     public function f1(true $v): true|false { return true;}}
复制代码


该脚本会生成以下错误信息:


类型同时包含true和false,应该用bool来替代它们
复制代码


类似地,true 也不能和bool 一起用于联合类型,如下所示:


class TrueExample {        public function f1(true $v): true|bool { return true;}}
复制代码


该脚本会生成以下错误信息:


重复类型true是多余的
复制代码

交集类型


PHP 8.1 将交集类型作为复合类型引入。交集类型可以与类和接口类型一起使用。交集类型用于表示多个类和接口类型的类型,而不是单个类或接口类型的类型。交集类型的语法如下:


Type1&Type2...TypeN
复制代码


何时使用交集类型,何时使用联合类型?如果一个类型表示多个类型中的一个,则使用联合类型。如果一个类型要同时表示多个类型,则使用交集类型。下面这个例子很好地说明了这种差异。ABC是 3 个相互之间没有关系的类。如果一个类型要表示这些类型中的任何一个,则要使用联合类型,如下所示:


<?php
class A{ function fn1(){ return "Class A object"; }}
class B { function fn1(){ return "Class B object"; }}
class C { function fn1(){ return "Class C object"; }}
$c = new C();
function fn1(A|B|C $a, A|B|C $b): string { return $a->fn1();}
echo fn1($c,$c); ?>
复制代码


输出如下:


Class C object
复制代码


这里如果使用交集类型,就会产生错误。修改这个函数,使用交集类型:


function fn1(A&B&C $a, A&B&C $b): string {         return $a->fn1();}
复制代码


执行上述脚本会产生以下错误信息:


Uncaught TypeError: fn1(): 参数 #1 ($a)的类型必须是A&B&C,但提供了C
复制代码


如果C 继承了BB 继承了A ,就可以使用交集类型了,如下所示:


<?php
class A{ function fn1(){ return "Class A object";}}
class B extends A{ function fn1(){ return "Class B object";}}
class C extends B{ function fn1(){ return "Class C object"; }}
$c = new C();
function fn1(A&B&C $a, A&B&C $b): string { return $a->fn1();}
echo fn1($c,$c); ?>
复制代码


输出如下:


Class C object
复制代码

标量类型与交集类型


交集类型只能与类和接口类型一起使用,但不能与标量类型一起使用。为了证明这一点,修改前述脚本中的fn1函数,如下所示:


function fn1(A&B&C&string $a, A&B&C $b): string {     }
复制代码


执行上述脚本会产生以下错误信息:


string类型不能作为交集类型的一部分
复制代码

交集类型与联合类型


交集类型不能与联合类型组合使用。具体来说,在同一类型声明中,交集类型表示法不能与联合类型表示法组合使用。为了证明这一点,修改fn1函数如下:


function fn1(A&B|C $a, A&B|C $b): string {    }
复制代码


上述脚本会导致如下解析错误信息:


Parse error: 语法错误,需要变量,但意外遇到符号“|”
复制代码


在同一个函数声明中,交集类型可以与联合类型一起使用,如下所示:


function fn1(A&B&C $a, A|B|C $b): string {       }
复制代码

返回类型 static 与 never


PHP 8.0 引入了一个新的返回类型static ,PHP 8.1 引入了一个新的返回类型 never

返回类型 static


如果返回类型指定为static ,则返回值的类型必须是定义方法的类的类型。例如,类A中定义的fn1方法返回类型为static,因此,该方法必须返回类型为A的值,即声明该函数的类。


<?php
class A { public int $var1=1; public function fn1(): static { return new A(); }}
$a=new A();echo $a->fn1()->var1;
复制代码


输出如下:


1
复制代码


返回类型声明为static 的函数必须属于某个类。为了证明这一点,声明一个返回类型为static 的全局函数:


<?php
function fn1(): static { }
复制代码


上述脚本会导致如下错误:


当没有处于活动状态的类作用域时,不能使用“static”
复制代码


返回的类对象必须是外围类。下面的脚本会生成一个错误,因为返回值是类类型B,而返回类型static 要求返回类型为类型A


<?php
class B{}class A { public int $var1=1; public function fn1(): static { return new B(); }}
复制代码


上述脚本会产生以下错误:


Uncaught TypeError: A::fn1(): 返回类型必须为类型A,但返回了B 
复制代码


如果类B 继承了类A ,那么上述脚本就不会有什么问题,将输出1


class B extends A{}
复制代码


返回类型static 可用于联合类型。如果在联合类型中使用static ,则返回值不一定是类类型。例如,以下脚本在联合类型中使用了static


<?php
class A { public int $var1=1; public function fn1(): static|int { return 1; }}
$a=new A(); echo $a->fn1();
复制代码


输出如下:


 1
复制代码


类型static 不能用于交集类型。为了证明这一点,请看下面的脚本:


<?php
class B extends A{}class A { public function fn1(): static&B { return new B(); }}
复制代码


该脚本会导致以下错误:


类型static不能作为交集类型的一部分
复制代码

返回类型 never


如果返回类型为never,则函数必须不返回值,或者根本不返回,即函数不终止。返回类型never 是其他所有返回类型的子类型。也就是说,在继承一个类时,never 可以在重写方法中替换任何其他返回类型。返回never 的函数必须执行以下操作之一:


  1. 抛出一个异常

  2. 调用 exit()

  3. 启动一个无限循环如果返回never 的函数永远不会被调用,那么这个函数可以为空,如下所示:


<?php
class A{function fn1(): never { } }
复制代码


A中的函数fn1()不能被调用,因为该函数隐式返回NULL 。为了证明这一点,我们将上述脚本修改为:


<?php
class A {function fn1(): never { } }
$a=new A();$a->fn1();
复制代码


执行该脚本将生成以下错误信息:


Uncaught TypeError: A::fn1(): 返回never的函数不能隐式返回
复制代码


下面的脚本将生成同样的错误消息,因为if条件永远无法满足,而函数隐式返回NULL


<?php
function fn1(): never{ if (false) { exit(); }}fn1();
复制代码


static 返回类型不同,never可以用作不属于类作用域的函数的返回类型,例如:


<?php
class A{ } function fn1(): never { }
复制代码


返回类型为never的函数一定不能返回值。为了证明这一点,下面的脚本声明了一个函数,该函数试图返回值,尽管它的返回类型为never


<?php
function fn1(): never { return 1; }
复制代码


执行上述脚本会生成以下错误信息:


返回never的函数必须没有返回值
复制代码


如果返回类型为never,则函数即使是隐式返回也不行。例如,下述脚本中的fn1函数不返回值,而是在其作用域结束时隐式返回。


<?php
function fn1(): never { }
fn1();
复制代码


上述脚本会导致以下错误:


Uncaught TypeError: fn1(): 返回never的函数不能隐式返回
复制代码


声明返回类型为never且不会终止的函数有什么用?返回类型never 可以在开发、测试和调试期间使用。返回never类型的函数可以通过调用exit()退出。这样的函数甚至可以被调用,如下面的脚本所示:


<?php
function fn1(): never { exit(); }
fn1();
复制代码


返回never类型的函数可以抛出异常,如下所示:


<?php
function fn1(): never { throw new Exception('Exception thrown'); }
复制代码


包含无限循环的函数可以将返回类型声明为never ,如下所示:


<?php
function fn1(): never { while (1){}}
复制代码


返回类型never 可以覆盖派生类中的任何类型,如下所示:


<?phpclass A{     function fn1(): int {        }}class B extends A{function fn1(): never {      }}
复制代码


返回类型never 不能用于联合类型。为了证明这一点,下面的脚本在联合类型中使用了never


<?php class A{  function fn1(): never|int {      }}
复制代码


该脚本将导致以下错误:


never只能作为独立类型使用
复制代码


never 类型不能用于交集类型。为了证明这一点,请运行以下将never和类类型B 一起使用的脚本。


<?php class B{}class A{  function fn1(): never&B {      }}
复制代码


该脚本将导致以下错误:


类型never不能作为交集类型的一部分
复制代码

标量类型不支持别名


从 PHP 8 开始,如果使用标量类型别名,就会生成警告信息。例如,如果使用boolean代替bool,则生成一条消息,说明boolean 将被解释成类名。为了证明这一点,考虑下面的脚本,函数声明将integer作为参数类型。


<?php    function fn1(integer $param) {}    fn1(1);?>
复制代码


如下所示,该脚本的输出中将包含一条警告信息:


警告:“integer”将被解释为类名。你是指“int”吗?输入“\integer”来消除该警告
Fatal error: Uncaught TypeError: fn1():参数#1 ($param)必须是integer类型,但提供的是int
复制代码

不再支持从 void 函数通过引用返回


从 PHP 8.1 开始,不再支持从void函数通过引用返回,因为只有变量引用可以通过引用返回,而void返回类型不返回值。为了证明这一点,可运行下面的脚本:


<?phpfunction &fn1(): void {}?>
复制代码


该脚本将输出一条弃用提示信息:


弃用:不再支持从void函数通过引用返回
复制代码

小结


在本文中,我们讨论了 PHP 8 中引入的与类型相关的新特性,包括联合类型、交集类型和mixed 类型,以及返回类型staticnever 。在下一篇文章中,我们将介绍与 PHP 数组、变量、运算符和异常处理相关的新特性。


原文链接:

https://www.infoq.com/articles/php-8-type-system-improvements/


相关阅读:

PHP 8:注解、match 表达式及其他改进

PHP 8:类和枚举

2023-07-25 08:002853

评论 1 条评论

发布
用户头像
当年的屠龙的勇士,最终成为了龙
2023-07-26 14:52 · 北京
回复
没有更多了
发现更多内容

明年 Flyme 就上车?魅族与星纪时代战略合作后,携手发力出行领域

Geek_2d6073

极狐 GitLab 冷知识:Quick Actions 快速操作 Issue

郭旭东

极狐GitLab JIHULAB 101

被忽略的一点:Docker的单进程模型

董哥的黑板报

Docker Kubernetes 容器 云原生 进程

何为项目

尚影嫣

项目管理 8月月更

开源一夏 | JS超集对TypeScript的Map对象以及联合类型的深入实战

恒山其若陋兮

开源 8月月更

自智网络简介

俞凡

网络 自智网络

【云原生】Docker入门 -- 阿里云服务器环境下安装Docker

Bug终结者

Docker 阿里云 云原生 服务器 8月月更

新专利曝光,魅族 19 热帖发布!魅族不断布局出行科技圈

Geek_2d6073

基于EFK的Kubernetes日志采集方案

Albert Edison

Kubernetes EFK 签约计划第三季 日志收集架构

再也不敢精通Java了——get/set篇

4ye

Java Jackson lombok 签约计划第三季

selenium脚本编写注意点(二)

Xd

相辅相成!沈子瑜掌舵魅族科技,互相协同迈入多终端互联生态

Geek_2d6073

MySQL 原理与优化:行锁

老崔说架构

RT-Thread记录(六、IPC机制之信号量、互斥量和事件集)

矜辰所致

RT-Thread 8月月更 IPC机制

如何成为一名合格的文案:需要具备哪些技能

石头IT视角

一张图进阶 RocketMQ - 消费者这个大冤种

三此君

RocketMQ 消息队列 消费者consumer 签约计划第三季

types-paddle: 为Paddle增加Tensor类型注释特性

吴京

Python paddle machine-learning

开源一夏|OpenHarmony中FA模型中应用包结构的参数

坚果

开源 OpenHarmony 8月月更

STM32入门开发 采用IIC硬件时序读写AT24C08(EEPROM)

DS小龙哥

8月月更

深入了解JAVA篇之内存分析

邱学喆

JVM 堆内存 内存结构 MAT

构建在Findora上的Forlend,具备隐私特性的借贷协议

BlockChain先知

魅族与星纪时代合作,沈子瑜透露品牌计划!补全消费者业务布局成关键

Geek_2d6073

JAVA开源商城系统

源字节1号

后端开发

开源一夏 |Aeraki --- 管理 Istio 服务网格中任何 7 层协议

叶秋学长

开源 云原生 8月月更

一篇文章和你从Java1聊到Java18

瀛洲骇客

Java core

他居然发邮件请我去吃饭——邮件伪造那些事儿

Regan Yue

网络安全 8月日更 8月月更 邮件伪造

极狐 GitLab 冷知识:使用 Email 也可以创建 Issue?

郭旭东

极狐GitLab JIHULAB 101

大融合!魅族星纪时代达成战略合作,董事长沈子瑜透露未来大动作

Geek_2d6073

兄弟携手!魅族与星纪时代共同发力出行领域,沈子瑜成舵手

Geek_2d6073

RxJS实现“搜索”功能

掘金安东尼

前端 函数式编程 8月月更

开源一夏 | 使用 JavaScript 和 CSS 的随机颜色生成器

海拥(haiyong.site)

开源 8月月更

PHP 8:类型系统改进_编程语言_Deepak Vohra_InfoQ精选文章