主頁 > 後端開發 > 從頭捋了一遍 Java 代理機制,識訓頗豐

從頭捋了一遍 Java 代理機制,識訓頗豐

2021-03-02 06:15:05 後端開發

?? 盡人事,聽天命,博主東南大學碩士在讀,熱愛健身和籃球,樂于分享技術相關的所見所得,關注公眾號 @ 飛天小牛肉,第一時間獲取文章更新,成長的路上我們一起進步

?? 本文已收錄于 「CS-Wiki」Gitee 官方推薦專案,現已累計 1.5k+ star,致力打造完善的后端知識體系,在技術的路上少走彎路,歡迎各位小伙伴前來交流學習

?? 如果各位小伙伴春招秋招沒有拿得出手的專案的話,可以參考我寫的一個專案「開源社區系統 Echo」Gitee 官方推薦專案,目前已累計 330+ star,基于 SpringBoot + MyBatis + MySQL + Redis + Kafka + Elasticsearch + Spring Security + ... 并提供詳細的開發檔案和配套教程,公眾號后臺回復 Echo 可以獲取配套教程,目前尚在更新中

 

前文提到,動態代理機制使用了反射,Spring 中的 AOP 由于使用了動態代理,所以也相當于使用了反射機制,那么,代理是什么?動態代理又是什么?動態代理中是如何使用反射的?全文脈絡思維導圖如下:

1. 常規編碼方式

在學習代理之前,先回顧以下我們的常規編碼方式:所有 interface 型別的變數總是通過向上轉型并指向某個實體的,

1)首先,定義一個介面:

1 public interface SmsService {
2     String send(String message);
3 }

 

2)然后撰寫其實作類:

1 public class SmsServicseImpl implements SmsService {
2     public String send(String message) {
3         System.out.println("send message:" + message);
4         return message;
5     }
6 }

 

3)最后創建該實作類的實體,轉型為介面并呼叫:

SmsService s = new SmsServicseImpl();
s.send("Java");

 

上述這種方式就是我們通常撰寫代碼的方式,而代理模式和這種方式有很大的區別,且看下文,

2. 代理模式概述

簡單來說,代理模式就是 使用代理物件來代替對真實物件的訪問,這樣就可以在不修改原目標物件的前提下,提供額外的功能操作,擴展目標物件的功能,

代理模式大致有三種角色:

  • Real Subject:真實類,也就是被代理類、委托類,用來真正完成業務服務功能;

  • Proxy:代理類,將自身的請求用 Real Subject 對應的功能來實作,代理類物件并不真正的去實作其業務功能;

  • Subject:定義 RealSubject 和 Proxy 角色都應該實作的介面,

通俗來說,代理模式的主要作用是擴展目標物件的功能,比如說在目標物件的某個方法執行前后你可以增加一些額外的操作,并且不用修改這個方法的原有代碼,如果大家學過 Spring 的 AOP,一定能夠很好的理解這句話,

舉個例子:你找了小紅來幫你向小綠問話,小紅就看作是代理我的代理類 Proxy,而你是 Real Subject,因為小紅要傳達的話其實是你說的,那么你和小紅都需要實作的介面(Subject)就是說話,由于你倆都能說話,在外界看來你倆就是一樣的(滑稽,大家理解就好,不用較真)

看到這里,不知道大家能不能理解了為什么委托類和代理類都需要實作相同的介面?

那是為了保持行為的一致性,在訪問者看來兩者之間就沒有區別,這樣,通過代理類這個中間層,很好地隱藏和保護了委托類物件,能有效屏蔽外界對委托類物件的直接訪問,同時,也可以在代理類上加上額外的操作,比如小紅在說話之前會跳一段舞,外界就會覺得你在說話前會跳一段舞,所以,這就實作了委托類的功能增強

代理模式有靜態代理和動態代理兩種實作方式,

3. 靜態代理

什么是靜態代理

先來看靜態代理的實作步驟:

1)定義一個介面(Subject)

2)創建一個委托類(Real Subject)實作這個介面

3)創建一個代理類(Proxy)同樣實作這個介面

4)將委托類 Real Subject 注入進代理類 Proxy,在代理類的方法中呼叫 Real Subject 中的對應方法,這樣的話,我們就可以通過代理類屏蔽對目標物件的訪問,并且可以在目標方法執行前后做一些自己想做的事情,

從實作和應用角度來說,靜態代理中,我們對目標物件的每個方法的增強都是手動完成的,非常不靈活(比如介面一旦新增加方法,目標物件和代理物件都要進行修改)且麻煩(需要對每個目標類都單獨寫一個代理類), 實際應用場景非常非常少,日常開發幾乎看不到使用靜態代理的場景,

從 JVM 層面來說, 靜態代理在編譯時就將介面、委托類、代理類這些都變成了一個個實際的 .class 檔案,

代碼示例

1)定義發送短信的介面

public interface SmsService {
    String send(String message);
}

 

2)創建一個委托類(Real Subject)實作這個介面

public class SmsServiceImpl implements SmsService {
    public String send(String message) {
        System.out.println("send message:" + message);
        return message;
    }
}

 

3)創建一個代理類(Proxy)同樣實作這個介面

4)將委托類 Real Subject 注入進代理類 Proxy,在代理類的方法中呼叫 Real Subject 中的對應方法,這樣的話,我們就可以通過代理類屏蔽對目標物件的訪問,并且可以在目標方法執行前后做一些自己想做的事情,

public class SmsProxy implements SmsService {
    
    // 將委托類注入進代理類
    private final SmsService smsService;
?
    public SmsProxy(SmsService smsService) {
        this.smsService = smsService;
    }
?
    @Override
    public String send(String message) {
        // 呼叫委托類方法之前,我們可以添加自己的操作
        System.out.println("before method send()");
        // 呼叫委托類方法
        smsService.send(message); 
        // 呼叫委托類方法之后,我們同樣可以添加自己的操作
        System.out.println("after method send()");
        return null;
    }
}

 

那么,如何使用這個被增強的 send 方法呢?

public class Main {
    public static void main(String[] args) {
        SmsService smsService = new SmsServiceImpl();
        SmsProxy smsProxy = new SmsProxy(smsService);
        smsProxy.send("Java");
    }
}

 

運行上述代碼之后,控制臺列印出:

before method send()
send message:java
after method send()

 

從輸出結果可以看出,我們已經增強了委托類 SmsServiceImplsend() 方法,

當然,從上述代碼我們也能看出來,靜態代理存在一定的弊端,假如說我們現在新增了一個委托類實作了 SmsService 介面,如果我們想要對這個委托類進行增強,就需要重新寫一個代理類,然后注入這個新的委托類,非常不靈活,也就是說靜態代理是一個委托了對應一個代理類,能不能將代理類做成一個通用的呢?為此,動態代理應用而生,

4. Java 位元組碼生成框架

在講解動態之前,我們有必要詳細說一下 .class 位元組碼檔案這個東西,動態代理機制和 Java 位元組碼生成框架息息相關,

在上文反射中我們提到,一個 Class 類對應一個 .class 位元組碼檔案,也就說位元組碼檔案中存盤了一個類的全部資訊,位元組碼其實是二進制檔案,內容是只有 JVM 能夠識別的機器碼,

決議程序這樣的:JVM 讀取 .class 位元組碼檔案,取出二進制資料,加載到記憶體中,決議位元組碼檔案內的資訊,生成對應的 Class 類物件:

顯然,上述這個程序是在編譯期就發生的,

那么,由于JVM 是通過 .class 位元組碼檔案(也就是二進制資訊)加載類的,如果我們在運行期遵循 Java 編譯系統組織 .class 位元組碼檔案的格式和結構,生成相應的二進制資料,然后再把這個二進制資料加載轉換成對應的類,這樣,我們不就完成了在運行時動態的創建一個類,這個思想其實也就是動態代理的思想,

在運行時期按照 JVM 規范對 .class 位元組碼檔案的組織規則,生成對應的二進制資料,當前有很多開源框架可以完成這個功能,如

  • ASM

  • CGLIB

  • Javassist

  • ......

需要注意的是,CGLIB 是基于 ASM 的, 這里簡單對比一下 ASM 和 Javassist:

  • Javassist 源代碼級 API 比 ASM 中實際的位元組碼操作更容易使用

  • Javassist 在復雜的位元組碼級操作上提供了更高級別的抽象層,Javassist 源代碼級 API 只需要很少的位元組碼知識,甚至不需要任何實際位元組碼知識,因此實作起來更容易、更快,

  • Javassist 使用反射機制,這使得它比 ASM 慢,

總的來說 ASM 比 Javassist 快得多,并且提供了更好的性能,但是 Javassist 相對來說更容易使用,兩者各有千秋,

以 Javassist 為例,我們來看看這些框架在運行時生成 .class 位元組碼檔案的強大能力,

正常來說,我們創建一個類的代碼是這樣的:

package com.samples;
?
public class Programmer {
    public void code(){
        System.out.println("I'm a Programmer,Just Coding.....");
    }
}

 

下面通過 Javassist 創建和上面一模一樣的 Programmer 類的位元組碼:

import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtMethod;
import javassist.CtNewMethod;
?
public class MyGenerator {
    public static void main(String[] args) throws Exception {
        ClassPool pool = ClassPool.getDefault();
        // 創建 Programmer 類      
        CtClass cc= pool.makeClass("com.samples.Programmer");
        // 定義方法
        CtMethod method = CtNewMethod.make("public void code(){}", cc);
        // 插入方法代碼
        method.insertBefore("System.out.println(\"I'm a Programmer,Just Coding.....\");");
        cc.addMethod(method);
        // 保存生成的位元組碼
        cc.writeFile("d://temp");
    }
}

 

通過反編譯工具打開 Programmer.class 可以看到以下代碼:

恐怖如斯!

5. 什么是動態代理

OK,了解了 Java 位元組碼生成框架,可以開始學習動態代理(Dynamic Proxy)了,

回顧一下靜態代理,我們把靜態代理的執行程序抽象為下圖:

可以看見,代理類無非是在呼叫委托類方法的前后增加了一些操作,委托類的不同,也就導致代理類的不同,

那么為了做一個通用性的代理類出來,我們把呼叫委托類方法的這個動作抽取出來,把它封裝成一個通用性的處理類,于是就有了動態代理中的 InvocationHandler 角色(處理類),

于是,在代理類和委托類之間就多了一個處理類的角色,這個角色主要是對代理類呼叫委托類方法的這個動作進行統一的呼叫,也就是由 InvocationHandler 來統一處理代理類呼叫委托類方法這個操作,看下圖:

從 JVM 角度來說,動態代理是在運行時動態生成 .class 位元組碼檔案 ,并加載到 JVM 中的,這個我們在 Java 位元組碼生成框架中已經提到過,

雖然動態代理在我們日常開發中使用的相對較少,但是在框架中的幾乎是必用的一門技術,學會了動態代理之后,對于我們理解和學習各種框架的原理也非常有幫助,Spring AOP、RPC 等框架的實作都依賴了動態代理

就 Java 來說,動態代理的實作方式有很多種,比如:

  • JDK 動態代理

  • CGLIB 動態代理

  • Javassit 動態代理

  • ......

下面詳細講解這三種動態代理機制,

6. JDK 動態代理機制

使用步驟

先來看下 JDK 動態代理機制的使用步驟:

1)定義一個介面(Subject)

2)創建一個委托類(Real Subject)實作這個介面

3)創建一個處理類并實作 InvocationHandler 介面,重寫其 invoke 方法(在 invoke 方法中利用反射機制呼叫委托類的方法,并自定義一些處理邏輯),并將委托類注入處理類

該方法有下面三個引數:

  • proxy:代理類物件(見下一步)

  • method:還記得我們在上篇文章反射中講到的 Method.invoke 嗎?就是這個,我們可以通過它來呼叫委托類的方法(反射)

  • args:傳給委托類方法的引數串列

4)創建代理物件(Proxy):通過 Proxy.newProxyInstance() 創建委托類物件的代理物件

這個方法需要 3 個引數:

  • 類加載器 ClassLoader

  • 委托類實作的介面陣列,至少需要傳入一個介面進去

  • 呼叫的 InvocationHandler 實體處理介面方法(也就是第 3 步我們創建的類的實體)

也就是說:我們在通過 Proxy 類的 newProxyInstance() 創建的代理物件在呼叫方法的時候,實際會呼叫到實作了 InvocationHandler 介面的處理類的 invoke()方法,可以在 invoke() 方法中自定義處理邏輯,比如在方法執行前后做什么事情,

代碼示例

1)定義一個介面(Subject)

public interface SmsService {
    String send(String message);
}

 

2)創建一個委托類(Real Subject)實作這個介面

public class SmsServiceImpl implements SmsService {
    public String send(String message) {
        System.out.println("send message:" + message);
        return message;
    }
}

 

3)創建一個處理類并實作 InvocationHandler 介面,重寫其 invoke 方法(在 invoke 方法中利用反射機制呼叫委托類的方法,并自定義一些處理邏輯),并將委托類注入處理類

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
?
public class DebugInvocationHandler implements InvocationHandler {
    
    // 將委托類注入處理類(這里我們用 Object 代替,方便擴展)
    private final Object target;
?
    public DebugInvocationHandler(Object target) {
        this.target = target;
    }
    
    // 重寫 invoke 方法
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws InvocationTargetException, IllegalAccessException {
        //呼叫方法之前,我們可以添加自己的操作
        System.out.println("before method " + method.getName());
        Object result = method.invoke(target, args);
        //呼叫方法之后,我們同樣可以添加自己的操作
        System.out.println("after method " + method.getName());
        return result;
    }
}

 

4)定義一個創建代理物件(Proxy)的工廠類:通過 Proxy.newProxyInstance() 創建委托類物件的代理物件

public class JdkProxyFactory {
    public static Object getProxy(Object target) {
        return Proxy.newProxyInstance(
                target.getClass().getClassLoader(),
                target.getClass().getInterfaces(),
                new DebugInvocationHandler(target)
        );
    }
}

 

5)實際使用

SmsService smsService = (SmsService) JdkProxyFactory.getProxy(new SmsServiceImpl());
smsService.send("Java");

 

運行上述代碼之后,控制臺列印出:

before method send
send message:Java
after method send

 

7. CGLIB 動態代理機制

使用步驟

JDK 動態代理有一個最致命的問題是它只能代理實作了某個介面的實作類,并且代理類也只能代理介面中實作的方法,要是實作類中有自己私有的方法,而介面中沒有的話,該方法不能進行代理呼叫

為了解決這個問題,我們可以用 CGLIB 動態代理機制,

上文也提到過,CGLIB(Code Generation Library)是一個基于 ASM 的 Java 位元組碼生成框架,它允許我們在運行時對位元組碼進行修改和動態生成,原理就是通過位元組碼技術生成一個子類,并在子類中攔截父類方法的呼叫,織入額外的業務邏輯,關鍵詞大家注意到沒有,攔截!CGLIB 引入一個新的角色就是方法攔截器 MethodInterceptor,和 JDK 中的處理類 InvocationHandler 差不多,也是用來實作方法的統一呼叫的,看下圖:

另外由于 CGLIB 采用繼承的方式,所以被代理的類不能被 final 修飾,

很多知名的開源框架都使用到了 CGLIB, 例如 Spring 中的 AOP 模塊中:如果目標物件實作了介面,則默認采用 JDK 動態代理,否則采用 CGLIB 動態代理

來看 CGLIB 動態代理的使用步驟:

1)首先創建一個委托類(Real Subject)

2)創建一個方法攔截器實作介面 MethodInterceptor,并重寫 intercept 方法,intercept 用于攔截并增強委托類的方法(和 JDK 動態代理 InvocationHandler 中的 invoke 方法類似)

該方法擁有四個引數:

  • Object var1:委托類物件

  • Method var2:被攔截的方法(委托類中需要增強的方法)

  • Object[] var3:方法入參

  • MethodProxy var4:用于呼叫委托類的原始方法(底層也是通過反射機制,不過不是 Method.invoke 了,而是使用 MethodProxy.invokeSuper 方法)

3)創建代理物件(Proxy):通過 Enhancer.create() 創建委托類物件的代理物件

也就是說:我們在通過 Enhancer 類的 create() 創建的代理物件在呼叫方法的時候,實際會呼叫到實作了 MethodInterceptor 介面的處理類的 intercept()方法,可以在 intercept() 方法中自定義處理邏輯,比如在方法執行前后做什么事情,

可以發現,CGLIB 動態代理機制和 JDK 動態代理機制的步驟差不多,CGLIB 動態代理的核心是方法攔截器 MethodInterceptorEnhancer,而 JDK 動態代理的核心是處理類 InvocationHandlerProxy

代碼示例

不同于 JDK 動態代理不需要額外的依賴,CGLIB 是一個開源專案,如果你要使用它的話,需要手動添加相關依賴,

<dependency>
  <groupId>cglib</groupId>
  <artifactId>cglib</artifactId>
  <version>3.3.0</version>
</dependency>

 

1)首先創建一個委托類(Real Subject)

public class AliSmsService {
    public String send(String message) {
        System.out.println("send message:" + message);
        return message;
    }
}

 

2)創建一個方法攔截器實作介面 MethodInterceptor,并重寫 intercept 方法

import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;

public class DebugMethodInterceptor implements MethodInterceptor {

    @Override
    public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
        // 呼叫方法之前,我們可以添加自己的操作
        System.out.println("before method " + method.getName());
        // 通過反射呼叫委托類的方法
        Object object = methodProxy.invokeSuper(o, args);
        // 呼叫方法之后,我們同樣可以添加自己的操作
        System.out.println("after method " + method.getName());
        return object;
    }

}

 

3)創建代理物件(Proxy):通過 Enhancer.create() 創建委托類物件的代理物件

import net.sf.cglib.proxy.Enhancer;

public class CglibProxyFactory {
    public static Object getProxy(Class<?> clazz) {
        // 創建動態代理增強類
        Enhancer enhancer = new Enhancer();
        // 設定類加載器
        enhancer.setClassLoader(clazz.getClassLoader());
        // 設定委托類(設定父類)
        enhancer.setSuperclass(clazz);
        // 設定方法攔截器
        enhancer.setCallback(new DebugMethodInterceptor());
        // 創建代理類
        return enhancer.create();
    }
}

 

setSuperclass 我們就能看出,為什么說 CGLIB 是基于繼承的,

4)實際使用

AliSmsService aliSmsService = 
    (AliSmsService) CglibProxyFactory.getProxy(AliSmsService.class);
aliSmsService.send("Java");

 

運行上述代碼之后,控制臺列印出:

before method send
send message:Java
after method send

 

JDK 動態代理和 CGLIB 動態代理對比

1)JDK 動態代理是基于實作了介面的委托類,通過介面實作代理;而 CGLIB 動態代理是基于繼承了委托類的子類,通過子類實作代理,

2)JDK 動態代理只能代理實作了介面的類,且只能增強介面中現有的方法;而 CGLIB 可以代理未實作任何介面的類,

3)就二者的效率來說,大部分情況都是 JDK 動態代理的效率更高,隨著 JDK 版本的升級,這個優勢更加明顯,

提一嘴,常見的還有 Javassist 動態代理機制,和 CGLIB 一樣,作為一個 Java 位元組碼生成框架,Javassist 天生就擁有在運行時動態創建一個類的能力,實作動態代理自然不在話下, Dubbo 就是默認使用 Javassit 來進行動態代理的,

8. 什么情況下使用動態代理

1)設計模式中有一個設計原則是開閉原則,即對修改關閉,對擴展開放,我們在作業中有時會接手很多前人的代碼,里面代碼邏輯讓人摸不著頭腦,就很難去下手修改代碼,那么這時我們就可以通過代理對類進行增強,

2)我們在使用 RPC 框架的時候,框架本身并不能提前知道各個業務方要呼叫哪些介面的哪些方法 ,那么這個時候,就可用通過動態代理的方式來建立一個中間人給客戶端使用,也方便框架進行搭建邏輯,某種程度上也是客戶端代碼和框架松耦合的一種表現,

3)Spring 的 AOP 機制同樣也是采用了動態代理,此處不做詳細討論,

9. 靜態代理和動態代理對比

1)靈活性 :動態代理更加靈活,不需要必須實作介面,可以直接代理實作類,并且可以不需要針對每個目標類都創建一個代理類,另外,靜態代理中,介面一旦新增加方法,目標物件和代理物件都要進行修改,這是非常麻煩的

2)JVM 層面 :靜態代理在編譯時就將介面、實作類、代理類這些都變成了一個個實際的 .class 位元組碼檔案,而動態代理是在運行時動態生成類位元組碼,并加載到 JVM 中的,

10. 總結

全部捋一遍下來還是識訓蠻多的,我感覺只要理解了位元組碼在編譯期生成還是在運行期生成,就差不多能夠把握住靜態代理和動態代理了,總結一下靜態代理和動態代理中的角色:

靜態代理:

  • Subject:公共介面

  • Real Subject:委托類

  • Proxy:代理類

JDK 動態代理:

  • Subject:公共介面

  • Real Subject:委托類

  • Proxy:代理類

  • InvocationHandler:處理類,統一呼叫方法

CGLIB 動態代理:

  • Subject:公共介面

  • Real Subject:委托類

  • Proxy:代理類

  • MethodInterceptor:方法攔截器,統一呼叫方法

參考資料

  • 《Java 核心技術 - 卷 1 基礎知識 - 第 10 版》

  • 《Thinking In Java(Java 編程思想)- 第 4 版》

  • JavaGuide:https://snailclimb.gitee.io/javaguide

  • 亦山 — Java動態代理機制詳解(JDK 和CGLIB,Javassist,ASM):https://blog.csdn.net/luanlouis/article/details/24589193

?? 關注公眾號 | 飛天小牛肉,即時獲取更新

  • 博主東南大學碩士在讀,利用課余時間運營一個公眾號『 飛天小牛肉 』,2020/12/29 日開通,專注分享計算機基礎(資料結構 + 演算法 + 計算機網路 + 資料庫 + 作業系統 + Linux)、Java 基礎和面試指南的相關原創技術好文,本公眾號的目的就是讓大家可以快速掌握重點知識,有的放矢,希望大家多多支持哦,和小牛肉一起成長 ??

  • 并推薦個人維護的開源教程類專案: CS-Wiki(Gitee 推薦專案,現已累計 1.5k+ star), 致力打造完善的后端知識體系,在技術的路上少走彎路,歡迎各位小伙伴前來交流學習 ~ ??

  • 如果各位小伙伴春招秋招沒有拿得出手的專案的話,可以參考我寫的一個專案「開源社區系統 Echo」Gitee 官方推薦專案,目前已累計 330+ star,基于 SpringBoot + MyBatis + MySQL + Redis + Kafka + Elasticsearch + Spring Security + ... 并提供詳細的開發檔案和配套教程,公眾號后臺回復 Echo 可以獲取配套教程,目前尚在更新中,

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

標籤:Java

上一篇:spring注解整理

下一篇:Docker 常用命令,還有誰不會?

標籤雲
其他(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