泛型函数(Generic function)。"一次编写,多种类型通用,还不会搞混类型" 的函数,就像超市的购物袋,它本身不规定装什么(可以装水果、零食、日用品),但你装进去苹果,拿出来还是苹果;装进去牛奶,拿出来还是牛奶。这个购物袋就类似泛型的作用 —— 它不限制具体装什么,但保证你取出来的和放进去的是同一种东西。使用java中的泛型来举例解释。
例如:在java中创建一个打印集合中的元素的方法,如果集合在创建的时候就指定了List中的元素类型,如果我们在不使用泛型的情况下,可以对每一种类型的集合创建一个方法(方法的重载)这种说法其实也不完全准确,为了方便讲解。
package src.Generics_;
import org.w3c.dom.ls.LSInput;
import java.util.Arrays;
import java.util.List;
public class GenericTest2 {
public static void main(String[] args) {
List<String> stringList = Arrays.asList("a", "b", "c");
List<Integer> integerList = Arrays.asList(1, 2, 3);
// Sting集合打印元素
printStringList(stringList);
// Integer集合打印元素
printIntegerList(integerList);
}
// 这里创建一个打印String List中元素的函数
public static void printStringList(List<String> lst) {
for (String l1 : lst) {
System.out.println(l1);
}
}
// 这里再创建一个打印Integer List中的元素的函数
public static void printIntegerList(List<Integer> lst) {
for (Integer l2 : lst) {
System.out.println(l2);
}
}
}
在上面我们在主函数中创建了两个元素类似的集合。我们在这里使用了一个比较笨的方式,对集合元素打印的函数进行重写来满足集合中不同元素类型的打印。当然,如何我们使用泛型来实现呢?
package src.Generics_;
import java.util.Arrays;
import java.util.List;
public class GenericTest3 {
public static void main(String[] args) {
List<String> lst1 = Arrays.asList("A", "B", "C");
List<Integer> lst2 = Arrays.asList(1, 2, 3, 4);
// 这里我们使用泛型函数
printList(lst1); // 打印String 集合
printList(lst2); // 打印Integer 集合
// 如果我们再创建一个新类型的集合
List<Double> lst3 = Arrays.asList(1.1, 1.2, 1.2);
printList(lst3);
}
// 这里咱们创建一个泛型函数
public static <T> void printList(List<T> lst) {
for (T item : lst) {
System.out.println(item);
}
}
}
通过这个简单的java中方法泛型的小例子,可以对泛型有一点了解。那么R中的泛型如何实现呢?R中在面向对象的时候有多种对象类型,不同的对象类型的泛型方法是否也不同呢?
是的,R 中的泛型方法以 S3 泛型 最为基础和常用,覆盖了大部分内置功能;S4 泛型用于更严格的面向对象场景;第三方包(如 tidyverse)会根据需求定义领域特定的泛型。泛型的核心作用是:对不同类型的对象,用统一的函数名实现不同的具体操作。
下面我们先介绍一下R中的S3泛型。
# 这里我们创建一个泛型函数
genericFun <- function(x,...){UseMethod("genericFun")}
# 指定泛型函数作用的类型
genericFun.numeric <- function(x){
print("this is a number")
}
genericFun(10)
# 当我们运行这个泛型函数的时候,打印:this is a number
# 如果我传入的参数不是整型呢?
genericFun("S")
# 发生了报错
# Error in UseMethod("genericFun") :
# no applicable method for 'genericFun' applied to an object of class "character"
S3 泛型的方法分派只看第一个参数(通常是 x)的类,其他参数的类型不影响方法匹配。S3 对方法的参数要求很灵活:方法可以只定义它需要的参数,无需与泛型函数的参数完全匹配(只要能处理传入的参数即可)。 R中的S3泛型函数好灵活啊。
S4泛型函数相较S3泛型函数更加的严格,那如何简单使用呢?
S4 泛型通常与 S4 类配合使用,需先通过 setClass() 定义类(包含 slots 和继承关系)
● 方法(Method):与特定类绑定的函数,实现该类的具体逻辑,必须通过泛型函数调用。
● 类(Class):S4 类需显式定义(包含 slots 结构,类似 “属性”),具有严格的继承关系。
● 分派机制:S4 支持多参数分派(根据多个参数的类匹配方法),而 S3 仅基于第一个参数。
这里我们举个例子吧。
# S4泛型函数是依赖S4类的,这里我们先定义一个S4类
# 定义一个"S4Person"类,包含name(字符型)和age(数值型)两个slots
setClass(
Class = "S4Person", # 类名
slots = list(
name = "character", # slot名称及类型约束
age = "numeric"
)
)
# 定义继承类"S4Student"(继承自"S4Person")
setClass(
Class = "S4Student",
slots = list(school = "character"), # 新增school slot
contains = "S4Person" # 继承自"S4Person"
)
# setGeneric() 定义泛型函数,需指定函数名和参数,核心是用 standardGeneric() 声明泛型接口
# 定义泛型函数"greet",功能是向对象打招呼
setGeneric(
name = "greet", # 泛型函数名
def = function(x) { # 定义参数(至少包含要分派的对象)
standardGeneric("greet") # 声明为S4泛型
}
)
# 为"S4Person"类注册greet方法
setMethod(
f = "greet", # 绑定到的泛型函数名
signature = "S4Person", # 方法对应的类(单类)
definition = function(x) { # 方法逻辑
paste0("Hello, I'm ", x@name, ", ", x@age, " years old.")
}
)
# 为"S4Student"类注册greet方法(继承类可重写方法)
setMethod(
f = "greet",
signature = "S4Student",
definition = function(x) {
paste0("Hello, I'm ", x@name, " from ", x@school, ".")
}
)
# 多参数分派示例:为x="S4Person"、y="S4Student"注册greetTwo方法
setMethod(
f = "greetTwo",
signature = signature(x = "S4Person", y = "S4Student"), # 多个参数的类
definition = function(x, y) {
paste0(x@name, " says hi to ", y@name, " (student at ", y@school, ").")
}
)
#### 测试greet泛型函数
# 创建S4Person对象
p <- new("S4Person", name = "Alice", age = 30)
greet(p) # 调用S4Person的greet方法
#> [1] "Hello, I'm Alice, 30 years old."
# 创建S4Student对象
s <- new("S4Student", name = "Bob", age = 20, school = "MIT")
greet(s) # 调用S4Student的greet方法
#> [1] "Hello, I'm Bob from MIT."
这里简单总结一下两者的区别。