泛型函數(shù)(Generic function)。"一次編寫,多種類型通用,還不會搞混類型" 的函數(shù),就像超市的購物袋,它本身不規(guī)定裝什么(可以裝水果、零食、日用品),但你裝進(jìn)去蘋果,拿出來還是蘋果;裝進(jìn)去牛奶,拿出來還是牛奶。這個購物袋就類似泛型的作用 —— 它不限制具體裝什么,但保證你取出來的和放進(jìn)去的是同一種東西。使用java中的泛型來舉例解釋。
例如:在java中創(chuàng)建一個打印集合中的元素的方法,如果集合在創(chuàng)建的時候就指定了List中的元素類型,如果我們在不使用泛型的情況下,可以對每一種類型的集合創(chuàng)建一個方法(方法的重載)這種說法其實也不完全準(zhǔn)確,為了方便講解。
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);
}
// 這里創(chuàng)建一個打印String List中元素的函數(shù)
public static void printStringList(List<String> lst) {
for (String l1 : lst) {
System.out.println(l1);
}
}
// 這里再創(chuàng)建一個打印Integer List中的元素的函數(shù)
public static void printIntegerList(List<Integer> lst) {
for (Integer l2 : lst) {
System.out.println(l2);
}
}
}
在上面我們在主函數(shù)中創(chuàng)建了兩個元素類似的集合。我們在這里使用了一個比較笨的方式,對集合元素打印的函數(shù)進(jìn)行重寫來滿足集合中不同元素類型的打印。當(dāng)然,如何我們使用泛型來實現(xiàn)呢?
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);
// 這里我們使用泛型函數(shù)
printList(lst1); // 打印String 集合
printList(lst2); // 打印Integer 集合
// 如果我們再創(chuàng)建一個新類型的集合
List<Double> lst3 = Arrays.asList(1.1, 1.2, 1.2);
printList(lst3);
}
// 這里咱們創(chuàng)建一個泛型函數(shù)
public static <T> void printList(List<T> lst) {
for (T item : lst) {
System.out.println(item);
}
}
}
通過這個簡單的java中方法泛型的小例子,可以對泛型有一點了解。那么R中的泛型如何實現(xiàn)呢?R中在面向?qū)ο蟮臅r候有多種對象類型,不同的對象類型的泛型方法是否也不同呢?
是的,R 中的泛型方法以 S3 泛型 最為基礎(chǔ)和常用,覆蓋了大部分內(nèi)置功能;S4 泛型用于更嚴(yán)格的面向?qū)ο髨鼍埃坏谌桨ㄈ?tidyverse)會根據(jù)需求定義領(lǐng)域特定的泛型。泛型的核心作用是:對不同類型的對象,用統(tǒng)一的函數(shù)名實現(xiàn)不同的具體操作。
下面我們先介紹一下R中的S3泛型。
# 這里我們創(chuàng)建一個泛型函數(shù)
genericFun <- function(x,...){UseMethod("genericFun")}
# 指定泛型函數(shù)作用的類型
genericFun.numeric <- function(x){
print("this is a number")
}
genericFun(10)
# 當(dāng)我們運行這個泛型函數(shù)的時候,打?。簍his is a number
# 如果我傳入的參數(shù)不是整型呢?
genericFun("S")
# 發(fā)生了報錯
# Error in UseMethod("genericFun") :
# no applicable method for 'genericFun' applied to an object of class "character"
S3 泛型的方法分派只看第一個參數(shù)(通常是 x)的類,其他參數(shù)的類型不影響方法匹配。S3 對方法的參數(shù)要求很靈活:方法可以只定義它需要的參數(shù),無需與泛型函數(shù)的參數(shù)完全匹配(只要能處理傳入的參數(shù)即可)。 R中的S3泛型函數(shù)好靈活啊。
S4泛型函數(shù)相較S3泛型函數(shù)更加的嚴(yán)格,那如何簡單使用呢?
S4 泛型通常與 S4 類配合使用,需先通過 setClass() 定義類(包含 slots 和繼承關(guān)系)
● 方法(Method):與特定類綁定的函數(shù),實現(xiàn)該類的具體邏輯,必須通過泛型函數(shù)調(diào)用。
● 類(Class):S4 類需顯式定義(包含 slots 結(jié)構(gòu),類似 “屬性”),具有嚴(yán)格的繼承關(guān)系。
● 分派機制:S4 支持多參數(shù)分派(根據(jù)多個參數(shù)的類匹配方法),而 S3 僅基于第一個參數(shù)。
這里我們舉個例子吧。
# S4泛型函數(shù)是依賴S4類的,這里我們先定義一個S4類
# 定義一個"S4Person"類,包含name(字符型)和age(數(shù)值型)兩個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() 定義泛型函數(shù),需指定函數(shù)名和參數(shù),核心是用 standardGeneric() 聲明泛型接口
# 定義泛型函數(shù)"greet",功能是向?qū)ο蟠蛘泻?setGeneric(
name = "greet", # 泛型函數(shù)名
def = function(x) { # 定義參數(shù)(至少包含要分派的對象)
standardGeneric("greet") # 聲明為S4泛型
}
)
# 為"S4Person"類注冊greet方法
setMethod(
f = "greet", # 綁定到的泛型函數(shù)名
signature = "S4Person", # 方法對應(yīng)的類(單類)
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, ".")
}
)
# 多參數(shù)分派示例:為x="S4Person"、y="S4Student"注冊greetTwo方法
setMethod(
f = "greetTwo",
signature = signature(x = "S4Person", y = "S4Student"), # 多個參數(shù)的類
definition = function(x, y) {
paste0(x@name, " says hi to ", y@name, " (student at ", y@school, ").")
}
)
#### 測試greet泛型函數(shù)
# 創(chuàng)建S4Person對象
p <- new("S4Person", name = "Alice", age = 30)
greet(p) # 調(diào)用S4Person的greet方法
#> [1] "Hello, I'm Alice, 30 years old."
# 創(chuàng)建S4Student對象
s <- new("S4Student", name = "Bob", age = 20, school = "MIT")
greet(s) # 調(diào)用S4Student的greet方法
#> [1] "Hello, I'm Bob from MIT."
這里簡單總結(jié)一下兩者的區(qū)別。
