主頁 > 後端開發 > 面試題系列:用了這么多年的 Java 泛型,我竟然只知道它的皮毛

面試題系列:用了這么多年的 Java 泛型,我竟然只知道它的皮毛

2021-11-06 06:08:50 後端開發

面試題:說說你對泛型的理解?

面試考察點

考察目的:了解求職者對于Java基礎知識的掌握程度,

考察范圍:作業1-3年的Java程式員,

背景知識

Java中的泛型,是JDK5引入的一個新特性,

它主要提供的是編譯時期型別的安全檢測機制,這個機制允許程式在編譯時檢測到非法的型別,從而進行錯誤提示,

這樣做的好處,一方面是告訴開發者當前方法接識訓回傳的引數型別,另一方面是避免程式運行時的型別轉換錯誤,

泛型的設計推演

舉一個比較簡單的例子,首先我們來看一下ArrayList這個集合,部分代碼定義如下,

public class ArrayList{
   transient Object[] elementData; // non-private to simplify nested class access
}

在ArrayList中,存盤元素所使用的結構是一個Object[]物件陣列,意味著可以存盤任何型別的資料,

當我們使用這個ArrayList來做下面這個操作時,

public class ArrayExample {

    public static void main(String[] args) {
        ArrayList al=new ArrayList();
        al.add("Hello World");
        al.add(1001);
        String str=(String)al.get(1);
        System.out.println(str);
    }
}

運行程式后,會得到如下的執行結果

Exception in thread "main" java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String
	at org.example.cl06.ArrayExample.main(ArrayExample.java:11)

這種型別轉換錯誤,相信大家在開發中有遇到過,總的來說,在沒有泛型的情況下,會有兩個比較嚴重的問題

  1. 需要對型別進行強制轉換
  2. 使用不方便,容易出錯

怎么解決上面這個問題呢?要解決這個問題,就得思考這個問題背后的需求是什么?

我簡單總結兩點:

  1. 要能支持不同型別的資料存盤
  2. 還需要保證存盤資料型別的統一性

基于這兩個點不難發現,對于一個資料容器中要存盤什么型別的資料,其實是由開發者自己決定的,因此,為了解決這個問題,在JDK5中就引入了泛型的機制,

其定義形式是:ArrayList<E>,它相當于給ArrayList提供了一個型別輸入的模板EE可以是任意型別的物件,它的定義方式如下,

public class ArrayList<E>{
   transient E[] elementData; // non-private to simplify nested class access
}

在ArrayList這個類的定義中,使用<>語法,并傳入一個用來表示任意型別的物件E,這個E可以隨便定義,你可以定義成ABC都可以,

接著,把用來存盤元素的陣列elementData的型別,設定為E型別,

有了這個配置之后,ArrayList這個容器中,你想存盤什么型別的資料,是由使用者自己決定,比如我希望ArrayList只存盤String型別,那么它可以這么實作

public class ArrayExample {

    public static void main(String[] args) {
        ArrayList<String> al=new ArrayList();
        al.add("Hello World");
        al.add(1001);
        String str=(String)al.get(1);
        System.out.println(str);
    }
}

在定義ArrayList時,傳入一個String型別,這樣寫意味著后續往ArrayList這個實體物件al中添加元素,必須是String型別,否則會提示如下的語法錯誤,

image-20211104235945672

同理,如果需要保存其他型別的資料,可以這么寫:

  1. ArrayList
  2. ArrayList

總結:所謂泛型定義,其實本質上就是一種型別模板,在實際開發中,我們把一個容器或者一個物件中需要保存的屬性的型別,通過模板定義的方式,給到呼叫者來決定,從而保證了型別的安全性,

泛型的定義

泛型定義可以從兩個維度來說明:

  1. 泛型類
  2. 泛型方法

泛型類

泛型類指的是在類名后面添加一個或多個型別引數,一個泛型引數,也被稱為一個型別變數,是用于指定一個泛型型別名稱的識別符號,因為他們接受一個或多個引數,這些類被稱為引數化的類或引數化的型別,

型別變數的表示標記,常用的是:E(element)T(type)K(key)V(value)N(number)等,這只是一個表示符號,可以是任何字符,沒有強制要求,

下面的代碼是關于泛型類的定義,

該類接收一個T標記符的型別引數,該類中有一個成員變數,使用T型別,

public class Response <T>{
    
    private T data;

    public T getData() {
        return data;
    }

    public void setData(T data) {
        this.data = https://www.cnblogs.com/mic112/p/data;
    }
}

使用方式如下:

public static void main(String[] args) {
  Response<String> res=new Response<>();
  res.setData("Hello World");
}

泛型方法

泛型方法是指指定方法級別的型別引數,這個方法在呼叫時可以接收不同的引數型別,根據傳遞給泛型方法的引數型別,編譯器適當地處理每一個方法呼叫,

下面的代碼表示泛型方法的定義,用到了JDK提供的反射機制,來生成動態代理類,

public interface IHelloWorld {

    String say();
}

定義getProxy方法,它用來生成動態代理物件,但是傳遞的引數型別是T,也就是說,這個方法可以完成任意介面的動態代理實體的構建,

在這里,我們針對IHelloWorld這個介面,構建了動態代理實體,代碼如下,

public class ArrayExample implements InvocationHandler {

    public <T> T getProxy(Class<T> clazz){
        // clazz 不是介面不能使用JDK動態代理
        return (T) Proxy.newProxyInstance(clazz.getClassLoader(), new Class<?>[]{ clazz }, ArrayExample.this);
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        return "Hello World";
    }

    public static void main(String[] args) {
        IHelloWorld hw=new ArrayExample().getProxy(IHelloWorld.class);
        System.out.println(hw.say());
    }
}

運行結果:

Hello World

關于泛型方法的定義規則,簡單總結如下:

  1. 所有泛型方法的定義,都有一個用<>表示的型別引數宣告,這個型別引數宣告部分在方法回傳型別之前,
  2. 每一個型別引數宣告部分包含一個或多個型別引數,引數間用逗號隔開,一個泛型引數,也被稱為一個型別變數,是用于指定一個泛型型別名稱的識別符號,
  3. 型別引數能被用來宣告回傳值型別,并且能作為泛型方法得到的實際引數型別的占位符
  4. 泛型方法體的宣告和其他方法一樣,注意型別引數只能代表參考型型別,不能是原始型別(像 int、double、char 等),##

多型別變數定義

上在我們只定義了一個泛型變數T,那如果我們需要傳進去多個泛型要怎么辦呢?

我們可以這么寫:

public class Response <T,K,V>{
}

每一個引數宣告符號代表一種型別,

注意,在多變數型別定義中,泛型變數最好是定義成能夠簡單理解具有含義的字符,否則型別太多,呼叫者比較容易搞混,

有界型別引數

在有些場景中,我們希望傳遞的引數型別屬于某種型別范圍,比如,一個運算元字的方法可能只希望接受Number或者Number子類的實體,怎么實作呢?

泛型通配符上邊界

上邊界,代表型別變數的范圍有限,只能傳入某種型別,或者它的子類,

我們可以在泛型引數上,增加一個extends關鍵字,表示該泛型引數型別,必須是派生自某個實作類,示例代碼如下,

public class TypeExample<T extends Number> {
    private T t;

    public T getT() {
        return t;
    }

    public void setT(T t) {
        this.t = t;
    }

    public static void main(String[] args) {
        TypeExample<String> t=new TypeExample<>();
    }
}

上述代碼,宣告了一個泛型引數T,該泛型引數必須是繼承Number這個類,表示后續實體化TypeExample時,傳入的泛型型別應該是Number的子類,

所以,有了這個規則后,上面這個測驗代碼,會提示java: 型別引數java.lang.String不在型別變數T的范圍內錯誤,

泛型通配符下邊界

下邊界,代表型別變數的范圍有限,只能傳入某種型別,或者它的父類,

我們可以在泛型引數上,增加一個super關鍵字,可以設定泛型通配符的上邊界,實體代碼如下,

public class TypeExample<T> {
    private T t;

    public T getT() {
        return t;
    }
    public void setT(T t) {
        this.t = t;
    }
    public static void say(TypeExample<? super Number> te){
        System.out.println("say: "+te.getT());
    }
    public static void main(String[] args) {
        TypeExample<Number> te=new TypeExample<>();
        TypeExample<Integer> te2=new TypeExample<>();
        say(te);
        say(te2);
    }
}

say方法上宣告TypeExample<? super Number> te,表示傳入的TypeExample的泛型型別,必須是Number以及Number的父型別別,

在上述代碼中,運行時會得到如下錯誤:

java: 不兼容的型別: org.example.cl06.TypeExample<java.lang.Integer>無法轉換為org.example.cl06.TypeExample<? super java.lang.Number>

如下圖所示,表示Number這個類的類關系圖,通過super關鍵字限定后,只能傳遞Number以及父類Serializable

image-20211105101105344

型別通配符?

型別通配符一般是使用 ? 代替具體的型別引數,例如 List<?> 在邏輯上是 List,List 等所有 List<具體型別實參> 的父類,

來看下面這段代碼的定義,在say方法中,接受一個TypeExample型別的引數,并且泛型型別是<?>,代表接收任何型別的泛型型別引數,

public class TypeExample<T> {
    private T t;

    public T getT() {
        return t;
    }

    public void setT(T t) {
        this.t = t;
    }
    public static void say(TypeExample<?> te){
        System.out.println("say: "+te.getT());
    }
    public static void main(String[] args) {
        TypeExample<Integer> te1=new TypeExample<>();
        te1.setT(1111);
        TypeExample<String> te2=new TypeExample<>();
        te2.setT("Hello World");
        say(te1);
        say(te2);
    }
}

運行結果如下

say: 1111
say: Hello World

同樣,型別通配符的引數,也可以通過extends來做限定,比如:

public class TypeExample<T> {
    private T t;

    public T getT() {
        return t;
    }

    public void setT(T t) {
        this.t = t;
    }
    public static void say(TypeExample<? extends Number> te){ //修改,增加extends
        System.out.println("say: "+te.getT());
    }
    public static void main(String[] args) {
        TypeExample<Integer> te1=new TypeExample<>();
        te1.setT(1111);
        TypeExample<String> te2=new TypeExample<>();
        te2.setT("Hello World");
        say(te1);
        say(te2);
    }
}

由于say方法中的引數TypeExample,在泛型型別定義中使用了<? extends Number>,所以后續在傳遞引數時,泛型型別必須是Number的子型別,

因此上述代碼運行時,會提示如下錯誤:

java: 不兼容的型別: org.example.cl06.TypeExample<java.lang.String>無法轉換為org.example.cl06.TypeExample<? extends java.lang.Number>

注意: 構建泛型實體時,如果省略了泛型型別,則默認是通配符型別,意味著可以接受任意型別的引數,

泛型的繼承

泛型型別引數的定義,是允許被繼承的,比如下面這種寫法,

表示子類SayResponse和父類Response使用同一種泛型型別,

public class SayResponse<T> extends Response<T>{
    private T ox;
}

JVM是如何實作泛型的?

在JVM中,采用了型別擦除Type erasure generics)的方式來實作泛型,簡單來說,就是泛型只存在.java原始碼檔案中,一旦編譯后就會把泛型擦除.

我們來看ArrayExample這個類,編譯之后的位元組指令,

public class ArrayExample implements InvocationHandler {

    public <T> T getProxy(Class<T> clazz){
        // clazz 不是介面不能使用JDK動態代理
        return (T) Proxy.newProxyInstance(clazz.getClassLoader(), new Class<?>[]{ clazz }, ArrayExample.this);
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        return "Hello World";
    }

    public static void main(String[] args) {
        IHelloWorld hw=new ArrayExample().getProxy(IHelloWorld.class);
        System.out.println(hw.say());
    }
}

通過javap -v ArrayExample.class查看位元組指令如下,

 public <T extends java.lang.Object> T getProxy(java.lang.Class<T>);
    descriptor: (Ljava/lang/Class;)Ljava/lang/Object;
    flags: ACC_PUBLIC
    Code:
      stack=5, locals=2, args_size=2
         0: aload_1
         1: invokevirtual #2                  // Method java/lang/Class.getClassLoader:()Ljava/lang/ClassLoader;

可以看到,getProxy在編譯之后,泛型T已經被擦除了,引數型別替換成了java.lang.Object.

并不是所有型別都會轉換為java.lang.Object,比如如果是,則引數型別是java.lang.String,

同時,為了保證IHelloWorld hw=new ArrayExample().getProxy(IHelloWorld.class);這段代碼的準確性,編譯器還會在這里插入一個型別轉換的機制,

下面這個代碼是ArrayExample.class反編譯之后的呈現,

IHelloWorld hw = (IHelloWorld)(new ArrayExample()).getProxy(IHelloWorld.class);
System.out.println(hw.say());

泛型型別擦除實作帶來的缺陷

擦除方式實作泛型,還是會存在一些缺陷的,簡單舉幾個案例說明,

不支持基本型別

由于泛型型別擦除后,變成了java.lang.Object型別,這種方式對于基本型別如int/long/float等八種基本型別來說,就比較麻煩,因為Java無法實作基本型別到Object型別的強制轉換,

 ArrayList<int> list=new ArrayList<int>();

如果這么寫,會得到如下錯誤

java: 意外的型別
  需要: 參考
  找到:    int

所以,在泛型定義中,只能使用參考型別,

但是作為參考型別,如果保存基本型別的資料時,又會涉及到裝箱和拆箱的程序,比如

List<Integer> list = new ArrayList<Integer>();
list.add(10); // 1
int num = list.get(0); // 2

在上述代碼中,宣告了一個List<Integer>泛型型別的集合,

在標記1的位置,添加了一個int型別的數字10,這個程序中,會涉及到裝箱操作,也就是把基本型別int轉換為Integer.

在標記2的位置,編譯器首先要把Object轉換為Integer型別,接著再進行拆箱,把Integer轉換為int,因此上述代碼等同于

List list = new ArrayList();
list.add(Integer.valueOf(10));
int num = ((Integer) list.get(0)).intValue();

增加了一些執行步驟,對于執行效率來說還是會有一些影響,

運行期間無法獲取泛型實際型別

由于編譯之后,泛型就被擦除,所以在代碼運行期間,Java 虛擬機無法獲取泛型的實際型別,

下面這段代碼,從原始碼上兩個 List 看起來是不同型別的集合,但是經過泛型擦除之后,集合都變為 ArrayList,所以 if陳述句中代碼將會被執行,

public static void main(String[] args) {
  ArrayList<Integer> li = new ArrayList<>();
  ArrayList<Float> lf = new ArrayList<>();
  if (li.getClass() == lf.getClass()) { // 泛型擦除,兩個 List 型別是一樣的
    System.out.println("型別相同");
  }
}

運行結果:

型別相同

這就使得,我們在做方法多載時,無法根據泛型型別來定義重寫方法,

也就是說下面這種方式無法實作重寫,

public void say(List<Integer> a){}
public void say(List<String> b){}

另外還會給我們在實際使用中帶來一些限制,比如說我們沒辦法直接實作以下代碼

public <T> void say(T a){
  if(a instanceof T){

  }
  T t=new T();
}

上述代碼會存在編譯錯誤,

既然通過擦除的方式實作泛型有這么多缺陷,那為什么要這么設計呢?

要回答這個問題,需要知道泛型的歷史,Java的泛型是在Jdk 1.5 引入的,在此之前Jdk中的容器類等都是用Object來保證框架的靈活性,然后在讀取時強轉,但是這樣做有個很大的問題,那就是型別不安全,編譯器不能幫我們提前發現型別轉換錯誤,會將這個風險帶到運行時, 引入泛型,也就是為解決型別不安全的問題,但是由于當時java已經被廣泛使用,保證版本的向前兼容是必須的,所以為了兼容老版本jdk,泛型的設計者選擇了基于擦除的實作,

問題解答

面試題:說說你對泛型的理解?

回答: 泛型是JDK5提供的一個新特性,它主要提供的是編譯時期型別的安全檢測機制,這個機制允許程式在編譯時檢測到非法的型別,從而進行錯誤提示,

問題總結

深入理解Java泛型是程式員最基礎的必備技能,雖然面試很卷,但是實力仍然很重要,

關注[跟著Mic學架構]公眾號,獲取更多精品原創

轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/349478.html

標籤:Java

上一篇:為什么IDEA不推薦你使用@Autowired ?

下一篇:使用策略模式重構電商折扣和支付場景

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 【C++】Microsoft C++、C 和匯編程式檔案

    ......

    uj5u.com 2020-09-10 00:57:23 more
  • 例外宣告

    相比于斷言適用于排除邏輯上不可能存在的狀態,例外通常是用于邏輯上可能發生的錯誤。 例外宣告 Item 1:當函式不可能拋出例外或不能接受拋出例外時,使用noexcept 理由 如果不打算拋出例外的話,程式就會認為無法處理這種錯誤,并且應當盡早終止,如此可以有效地阻止例外的傳播與擴散。 示例 //不可 ......

    uj5u.com 2020-09-10 00:57:27 more
  • Codeforces 1400E Clear the Multiset(貪心 + 分治)

    鏈接:https://codeforces.com/problemset/problem/1400/E 來源:Codeforces 思路:給你一個陣列,現在你可以進行兩種操作,操作1:將一段沒有 0 的區間進行減一的操作,操作2:將 i 位置上的元素歸零。最終問:將這個陣列的全部元素歸零后操作的最少 ......

    uj5u.com 2020-09-10 00:57:30 more
  • UVA11610 【Reverse Prime】

    本人看到此題沒有翻譯,就附帶了一個自己的翻譯版本 思考 這一題,它的第一個要求是找出所有 $7$ 位反向質數及其質因數的個數。 我們應該需要質數篩篩選1~$10^{7}$的所有數,這里就不慢慢介紹了。但是,重讀題,我們突然發現反向質數都是 $7$ 位,而將它反過來后的數字卻是 $6$ 位數,這就說明 ......

    uj5u.com 2020-09-10 00:57:36 more
  • 統計區間素數數量

    1 #pragma GCC optimize(2) 2 #include <bits/stdc++.h> 3 using namespace std; 4 bool isprime[1000000010]; 5 vector<int> prime; 6 inline int getlist(int ......

    uj5u.com 2020-09-10 00:57:47 more
  • C/C++編程筆記:C++中的 const 變數詳解,教你正確認識const用法

    1、C中的const 1、區域const變數存放在堆疊區中,會分配記憶體(也就是說可以通過地址間接修改變數的值)。測驗代碼如下: 運行結果: 2、全域const變數存放在只讀資料段(不能通過地址修改,會發生寫入錯誤), 默認為外部聯編,可以給其他源檔案使用(需要用extern關鍵字修飾) 運行結果: ......

    uj5u.com 2020-09-10 00:58:04 more
  • 【C++犯錯記錄】VS2019 MFC添加資源不懂如何修改資源宏ID

    1. 首先在資源視圖中,添加資源 2. 點擊新添加的資源,復制自動生成的ID 3. 在解決方案資源管理器中找到Resource.h檔案,編輯,使用整個專案搜索和替換的方式快速替換 宏宣告 4. Ctrl+Shift+F 全域搜索,點擊查找全部,然后逐個替換 5. 為什么使用搜索替換而不使用屬性視窗直 ......

    uj5u.com 2020-09-10 00:59:11 more
  • 【C++犯錯記錄】VS2019 MFC不懂的批量添加資源

    1. 打開資源頭檔案Resource.h,在其中預先定義好宏 ID(不清楚其實ID值應該設定多少,可以先新建一個相同的資源項,再在這個資源的ID值的基礎上遞增即可) 2. 在資源視圖中選中專案資源,按F7編輯資源檔案,按 ID 型別 相對路徑的形式添加 資源。(別忘了先把檔案拷貝到專案中的res檔案 ......

    uj5u.com 2020-09-10 01:00:19 more
  • C/C++編程筆記:關于C++的參考型別,專供新手入門使用

    今天要講的是C++中我最喜歡的一個用法——參考,也叫別名。 參考就是給一個變數名取一個變數名,方便我們間接地使用這個變數。我們可以給一個變數創建N個參考,這N + 1個變數共享了同一塊記憶體區域。(參考型別的變數會占用記憶體空間,占用的記憶體空間的大小和指標型別的大小是相同的。雖然參考是一個物件的別名,但 ......

    uj5u.com 2020-09-10 01:00:22 more
  • 【C/C++編程筆記】從頭開始學習C ++:初學者完整指南

    眾所周知,C ++的學習曲線陡峭,但是花時間學習這種語言將為您的職業帶來奇跡,并使您與其他開發人員區分開。您會更輕松地學習新語言,形成真正的解決問題的技能,并在編程的基礎上打下堅實的基礎。 C ++將幫助您養成良好的編程習慣(即清晰一致的編碼風格,在撰寫代碼時注釋代碼,并限制類內部的可見性),并且由 ......

    uj5u.com 2020-09-10 01:00:41 more
最新发布
  • Rust中的智能指標:Box<T> Rc<T> Arc<T> Cell<T> RefCell<T> Weak

    Rust中的智能指標是什么 智能指標(smart pointers)是一類資料結構,是擁有資料所有權和額外功能的指標。是指標的進一步發展 指標(pointer)是一個包含記憶體地址的變數的通用概念。這個地址參考,或 ” 指向”(points at)一些其 他資料 。參考以 & 符號為標志并借用了他們所 ......

    uj5u.com 2023-04-20 07:24:10 more
  • Java的值傳遞和參考傳遞

    值傳遞不會改變本身,參考傳遞(如果傳遞的值需要實體化到堆里)如果發生修改了會改變本身。 1.基本資料型別都是值傳遞 package com.example.basic; public class Test { public static void main(String[] args) { int ......

    uj5u.com 2023-04-20 07:24:04 more
  • [2]SpinalHDL教程——Scala簡單入門

    第一個 Scala 程式 shell里面輸入 $ scala scala> 1 + 1 res0: Int = 2 scala> println("Hello World!") Hello World! 檔案形式 object HelloWorld { /* 這是我的第一個 Scala 程式 * 以 ......

    uj5u.com 2023-04-20 07:23:58 more
  • 理解函式指標和回呼函式

    理解 函式指標 指向函式的指標。比如: 理解函式指標的偽代碼 void (*p)(int type, char *data); // 定義一個函式指標p void func(int type, char *data); // 宣告一個函式func p = func; // 將指標p指向函式func ......

    uj5u.com 2023-04-20 07:23:52 more
  • Django筆記二十五之資料庫函式之日期函式

    本文首發于公眾號:Hunter后端 原文鏈接:Django筆記二十五之資料庫函式之日期函式 日期函式主要介紹兩個大類,Extract() 和 Trunc() Extract() 函式作用是提取日期,比如我們可以提取一個日期欄位的年份,月份,日等資料 Trunc() 的作用則是截取,比如 2022-0 ......

    uj5u.com 2023-04-20 07:23:45 more
  • 一天吃透JVM面試八股文

    什么是JVM? JVM,全稱Java Virtual Machine(Java虛擬機),是通過在實際的計算機上仿真模擬各種計算機功能來實作的。由一套位元組碼指令集、一組暫存器、一個堆疊、一個垃圾回收堆和一個存盤方法域等組成。JVM屏蔽了與作業系統平臺相關的資訊,使得Java程式只需要生成在Java虛擬機 ......

    uj5u.com 2023-04-20 07:23:31 more
  • 使用Java接入小程式訂閱訊息!

    更新完微信服務號的模板訊息之后,我又趕緊把微信小程式的訂閱訊息給實作了!之前我一直以為微信小程式也是要企業才能申請,沒想到小程式個人就能申請。 訊息推送平臺🔥推送下發【郵件】【短信】【微信服務號】【微信小程式】【企業微信】【釘釘】等訊息型別。 https://gitee.com/zhongfuch ......

    uj5u.com 2023-04-20 07:22:59 more
  • java -- 緩沖流、轉換流、序列化流

    緩沖流 緩沖流, 也叫高效流, 按照資料型別分類: 位元組緩沖流:BufferedInputStream,BufferedOutputStream 字符緩沖流:BufferedReader,BufferedWriter 緩沖流的基本原理,是在創建流物件時,會創建一個內置的默認大小的緩沖區陣列,通過緩沖 ......

    uj5u.com 2023-04-20 07:22:49 more
  • Java-SpringBoot-Range請求頭設定實作視頻分段傳輸

    老實說,人太懶了,現在基本都不喜歡寫筆記了,但是網上有關Range請求頭的文章都太水了 下面是抄的一段StackOverflow的代碼...自己大修改過的,寫的注釋挺全的,應該直接看得懂,就不解釋了 寫的不好...只是希望能給視頻網站開發的新手一點點幫助吧. 業務場景:視頻分段傳輸、視頻多段傳輸(理 ......

    uj5u.com 2023-04-20 07:22:42 more
  • Windows 10開發教程_編程入門自學教程_菜鳥教程-免費教程分享

    教程簡介 Windows 10開發入門教程 - 從簡單的步驟了解Windows 10開發,從基本到高級概念,包括簡介,UWP,第一個應用程式,商店,XAML控制元件,資料系結,XAML性能,自適應設計,自適應UI,自適應代碼,檔案管理,SQLite資料庫,應用程式到應用程式通信,應用程式本地化,應用程式 ......

    uj5u.com 2023-04-20 07:22:35 more