Java反射詳解篇--一篇入魂

来源:https://www.cnblogs.com/dennyLee2025/archive/2022/03/25/16056246.html
-Advertisement-
Play Games

1.反射概述 Java程式在運行時操作類中的屬性和方法的機制,稱為反射機制。 一個關鍵點:運行時 一般我們在開發程式時,都知道自己具體用了什麼類,直接創建使用即可。但當你寫一些通用的功能時沒辦法在編寫時知道具體的類型,並且程式跑起來還會有多種類型的可能,則需要在運行時動態的去調用某個類的屬性和方法, ...


1.反射概述

Java程式在運行時操作類中的屬性和方法的機制,稱為反射機制。

一個關鍵點:運行時

一般我們在開發程式時,都知道自己具體用了什麼類,直接創建使用即可。但當你寫一些通用的功能時沒辦法在編寫時知道具體的類型,並且程式跑起來還會有多種類型的可能,則需要在運行時動態的去調用某個類的屬性和方法,這就必須使用反射來實現。

例子說明:

Father f = new Children();

編譯時變數f 為Father類型,運行時為Children類型;

public void demo(Object obj){
 // 不知道調用者傳什麼具體對象
    ……
}

編譯時demo方法參數類型為Object,一般有兩種做法

第一種做法是知道參數類型有哪幾種情況,可以使用instanceof運算符進行判斷,再利用強制類型轉換將其轉換成其運行時類型的變數即可。

第二種做法是編譯時根本無法預知該對象和類可能屬於哪些類,程式只依靠運行時信息動態的來發現該對象和類的真實信息,這就必須使用反射。

那反射是怎麼做到在運行時獲取類的屬性和方法的呢?

理解類的載入機制的應該知道,當java文件編譯成.class文件,再被載入進入記憶體之後,JVM自動生成一個唯一對應的Class對象,這個Class是一個具體的類,這個Class類就是反射學習的重點。反射的操作對象就是這個Class類,通過Class類來獲取具體類的屬性和方法。

2.Class類

Class 類是用於保存類或介面屬性和方法信息的類,就是保存類信息的類,它類名稱就叫 Class。

2.1.理解Class類

Class類和構造方法源碼

public final class Class<T> implements java.io.Serializable,
                              GenericDeclaration,
                              Type,
                              AnnotatedElement {
 private final ClassLoader classLoader;
 
 private Class(ClassLoader loader) {
  classLoader = loader;
    }

 ……
}

簡單分析下Class類

  1. Class類和String類都是被final關鍵字修飾的類,是不可以被繼承的類;
  2. Class類支持泛型T,也就是說在編寫程式時可以做到:反射 + 泛型;
  3. Class類實現了序列化標記介面Serializable,既是Class類可以被序列化和反序列化;
  4. Class類不能被繼承,同時唯一的一個構造器還是私有的,因為設計之初就是讓JVM在類載入後傳入ClassLoader對象來創建Class對象(每個類或介面對應一個JVM自動生成Class對象),開發人員只是調用Class對象,並沒有直接實例化Class的能力。

Class對象的創建是在載入類時由 Java 虛擬機以及通過調用類載入器中的defineClass 方法自動構造的,關於類的載入可以通過繼承ClassLoader來實現自定義的類載入器,本文著重講反射,在此不展開講類載入相關知識。

2.2.獲取Class對象的三種方式

方式一:常用方式,Class.forName("包名.類名")

public static void main(String[] args) {
    // 方式一:全限定類名字元串
    Class<?> childrenClass = null;
    try {
        childrenClass = Class.forName("com.yty.fs.Children"); // 包名.類名
    } catch (ClassNotFoundException e) {
        e.printStackTrace();
    }
    // 獲取類名稱
    System.out.println("全限定類名="+childrenClass.getName());
}

執行結果:全限定類名=com.yty.fs.Children

方式二:每個類下的靜態屬性 class,類名.class

public static void main(String[] args) {
    // 方式二:每個類下的靜態屬性 class
    Class<Children> childrenClass2 = Children.class;
    System.out.println("類名稱="+childrenClass.getSimpleName());
}

執行結果:類名稱=Children

方式三:每個類最終都繼承了Object,Object類下的getClass()

public static void main(String[] args) {
    // 方式三:Object類下的getClass()
    Children children = new Children();
    Class<?> childrenClass3 = children.getClass();
    System.out.println("類所在包="+childrenClass3.getPackage());
}

執行結果:類所在包=package com.yty.fs

三種方式簡單對比:

  • 方式一通過全限定類名字元串既可以獲取,其他兩種方式都要導入類Children才可以;
  • 方式二獲取的Class不需要強轉即可獲得指定類型Class,其他兩種方式獲得的都是未知類型Class<?>;
  • 方式三通過實例化對象的Object中的方法獲取,其他兩種都不需要實例化對象。

怎麼選:

  • 只有全限定類名字元串,沒有具體的類可以導入的只能選方式一;
  • 有具體類導入沒有實例化對象的使用方式二;
  • 作為形參使用的使用方式三,通過形參引用來獲取Class。

Class類中有非常多的方法,通過案例掌握常用的方法即可。

2.3.案例一:構造方法、成員變數和成員方法的獲取和使用

1.構造方法操作

1.1.所有構造方法

1.2.所有public構造方法

1.3.無參構造方法

1.4.單個私有構造方法

2.欄位操作(成員變數)

2.1.獲取所有成員變數

2.2.獲取所有公共成員變數

2.3.獲取單個公共成員變數

2.4.獲取單個私有成員變數

3.方法操作(成員方法)

3.1.獲取所有方法--不會獲取父類的方法

3.2.獲取所有公共方法--會獲取父類的方法

3.3.獲取單個公共方法

3.3.1.獲取單個公共方法--無參方法

3.3.2.獲取單個公共方法--有參方法

3.4.獲取單個私有方法

具體看代碼

測試類:Children類

public class Children {
    public String testString; //測試用
    private int id;
    private String name;

    // 無參構造方法
    public Children() {
        System.out.println("====無參構成方法被調用");
    }
    // 多個參數構造方法
    public Children(int id, String name) {
        this.id = id;
        this.name = name;
    }
    // default構造方法--測試
    Children(String name, int id){
        this.id = id;
        this.name = name;
    }
    // 受保護構造方法--測試
    protected Children(int id) {
        this.id = id;
    }
    // 私有構造方法--測試
    private Children(String name) {
        this.name = name;
    }
    
    public int getId() {
        return id;
    }
    public void setId(int id) {
        this.id = id;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }

    @Override
    public String toString() {
        return "Children{ id=" + id + ", name=" + name + "}";
    }

    public void printName(){
        System.out.println("====printName--"+this.name);
    }
    public void printName(String name){
        this.name = name;
        System.out.println("====printName--"+this.name);
    }
    private void demoTest(){
        System.out.println("====demoTest--執行了");
    }
}

Class類的具體操作

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;

/**
 * 案例一:構造方法、成員變數和成員方法的獲取和使用
 */public class Demo1 {
    public static void main(String[] args) throws Exception {
        Class<?> chilrenClass = Class.forName("com.yty.fs.Children");

        // 1.構造方法操作
        // 1.1.獲取所有構造方法
        System.out.println("1.構造方法操作\n1.1.所有構造方法");
        Constructor<?>[] declaredConstructors = chilrenClass.getDeclaredConstructors();
        for (Constructor constructor : declaredConstructors){
            System.out.println(constructor.toString()); // Constructor類的toString已重寫
        }

        // 1.2.獲取所有public構造方法
        System.out.println("1.2.所有public構造方法");
        Constructor<?>[] constructors = chilrenClass.getConstructors();
        for(Constructor constructor : constructors){
            System.out.println(constructor.toString());
        }

        // 1.3.獲取無參構造方法
        Constructor<?>  onParamConstructor = chilrenClass.getConstructor();//參數類型為null,表示無參
        System.out.println("1.3.無參構造方法:\n"+onParamConstructor.toString());
        // 實例化對象
        Object o = onParamConstructor.newInstance();
        if(o instanceof Children){
            Children children = (Children)o;
            children.setId(111);
            children.setName("myName");
            System.out.println(o.toString());// Children類重寫了toString
        }

        // 1.4.獲取單個私有構造方法
        // 指定了私有構造方法的參數類型,所以只會獲取到一個構造方法
        Constructor<?> privateConstructor = chilrenClass.getDeclaredConstructor(String.class);
        System.out.println("1.4.單個私有構造方法:\n"+privateConstructor.toString());
        //私有構造方法需要取消訪問許可權檢查,否則報異常:IllegalAccessExceptionw
        privateConstructor.setAccessible(true);
        Object obj = privateConstructor.newInstance("myName");
        System.out.println(o.toString());

        // 2.欄位操作(成員變數)
        // 2.1.獲取所有成員變數
        System.out.println("2.欄位操作(成員變數)\n2.1.獲取所有成員變數");
        Field[] declaredFields = chilrenClass.getDeclaredFields();
        for (Field declaredField : declaredFields){
            // 獲取fieldName
            System.out.println(declaredField.getName());
        }

        // 2.2.獲取所有公共成員變數
        System.out.println("2.2.獲取所有公共成員變數");
        Field[] fields = chilrenClass.getFields();
        for (Field field : fields){
            // 獲取fieldName
            System.out.println(field.getName());
        }

        // 2.3.獲取單個公共成員變數
        System.out.println("2.3.獲取單個公共成員變數");
        Field field = chilrenClass.getField("testString");
        Object o1 = chilrenClass.getConstructor().newInstance();
        field.set(o1,"yty");
        Object o1_1 = field.get(o1);
        // 獲取fieldName
        System.out.println("成員變數名-值:"+field.getName()+"="+o1_1.toString());

        // 2.4.獲取單個私有成員變數
        System.out.println("2.4.獲取單個私有成員變數");
        Field field2 = chilrenClass.getDeclaredField("name");
        //私有成員變數需要取消訪問許可權檢查,否則報異常:IllegalAccessExceptionw
        field2.setAccessible(true);
        Object o2 = chilrenClass.getConstructor().newInstance();
        field2.set(o2,"myName");
        Object o2_2 = field2.get(o2);
        // 獲取fieldName
        System.out.println("成員變數名-值:"+field2.getName()+"="+o2_2.toString());


        // 3.方法操作(成員方法)
        // 3.1.獲取所有方法(成員方法)
        System.out.println("3.方法操作(成員方法)\n3.1.獲取所有方法--不會獲取父類的方法");
        Method[] declaredMethods = chilrenClass.getDeclaredMethods();
        for (Method method : declaredMethods){
            // 獲取方法名
            System.out.println(method.getName());
        }

        // 3.2.獲取所有公共方法
        System.out.println("3.2.獲取所有公共方法--會獲取父類的方法");
        Method[] methods = chilrenClass.getMethods();
        for (Method method : methods){
            // 獲取方法名
            System.out.println(method.getName());
        }

        // 3.3.獲取單個公共方法
        System.out.println("3.3.獲取單個公共方法\n3.3.1.獲取單個公共方法--無參方法");
        Method printName = chilrenClass.getMethod("printName"); //方法名稱
        System.out.println(printName);

        System.out.println("3.3.2.獲取單個公共方法--有參方法");
        Method printName2 = chilrenClass.getMethod("printName",String.class); //方法名稱,參數類型
        System.out.println("參數個數:"+printName2.getParameterCount());
        // 遍歷所有參數信息
        Parameter[] parameters = printName2.getParameters();
        for (int i=0;i<printName2.getParameterCount();i++){
            Parameter param = parameters[i];
            if(param.isNamePresent()){
                System.out.println("第"+ (i+1) +"個參數信息");
                System.out.println("參數類型="+param.getType());
                System.out.println("參數名稱="+param.getName());
            }
        }
        // 使用有參方法
        Object o3 = chilrenClass.getConstructor().newInstance();
        printName2.invoke(o3,"myName");//傳入參數值、執行方法

        // 3.4.獲取單個私有方法
        System.out.println("3.4.獲取單個私有方法");
        Method demoTest = chilrenClass.getDeclaredMethod("demoTest");
        // 使用私有無參方法
        Object o4 = chilrenClass.getConstructor().newInstance();
        demoTest.setAccessible(true);
        demoTest.invoke(o4);

    }
}

執行結果:拷貝過去執行就知道了……

2.4.案例二:註解的相關操作

自定義一個測試用的註解

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Retention(value = RetentionPolicy.RUNTIME)@Target(ElementType.FIELD)
public @interface PersonAnnotation {
    String name() default "myName";
}

註解使用類和測試用的main方法

import java.lang.reflect.Field;

public class PersonAnnotationDemo {
    @PersonAnnotation(name = "張三")
    private String name;
    private int age;

    @Override
    public String toString() {
        return "PersonAnnotationDemo{" + "name=" + name +  ", age=" + age + "}";
    }

    public static void main(String[] args) throws Exception {
        Class<?> annotateClass = Class.forName("com.yty.fs.PersonAnnotationDemo");
        Object o = annotateClass.newInstance();
        System.out.println("PersonAnnotationDemo是否是註解類:"+annotateClass.isAnnotation());

        Field[] declaredFields = annotateClass.getDeclaredFields();
        for(Field field : declaredFields){
            // PersonAnnotationDemo類中的成員變數是否有 PersonAnnotation註解
            if (field.isAnnotationPresent(PersonAnnotation.class)){
                // 獲取成員變數中 單個PersonAnnotation註解
                PersonAnnotation annotation = field.getAnnotation(PersonAnnotation.class);
                // 獲取 多個PersonAnnotation註解
                // PersonAnnotation[] annotationsByType = field.getAnnotationsByType(PersonAnnotation.class);
                /**
                 * 相類似的獲取註解方法:getDeclaredAnnotation、getDeclaredAnnotationsByType、getAnnotations、getDeclaredAnnotations
                 */
                // 輸出註解中的值
                System.out.println("輸出註解中的值:"+field.getName()+"="+annotation.name());
                // 將註解的值 賦值 到PersonAnnotationDemo對象對應欄位
                field.setAccessible(true);//私有欄位需要忽略修飾符
                field.set(o,annotation.name());
            }
        }
        // 輸出:註解的值 賦值給 對象
        System.out.println(o.toString());

    }
}

執行結果:

PersonAnnotationDemo是否是註解類:false

輸出註解中的值:name=張三

PersonAnnotationDemo{name=張三, age=0}

3.反射的應用

常用於框架底層開發

3.1.實戰一:通過配置文件解耦類和反射

Spring 框架通過將成員變數值以及依賴對象等信息都放在配置文件中進行管理的,類發生改變時只需要更新配置文件,對於反射模塊則無需更改,從而實現了較好的解耦。

測試類

public class BigBanana {

    public void printBigBanana(String color){
        System.out.println("Do you like "+color+" BigBanana?");
    }

}

配置文件信息

bigbanana.class.name=com.yty.fs.BigBanana
bigbanana.class.method=printBigBanana
bigbanana.class.method.param=java.lang.String

反射測試類

public class TestBigBanana {
    private static Properties properties;
    // 通過Key 獲取配置文件value 值
    public static String getProperty(String key) throws IOException {
        if (properties == null){
            properties = new Properties();
            FileReader fileReader = new FileReader(new File("properties.properties"));
            properties.load(fileReader);
        }
        return properties.getProperty(key);
    }

    // 測試
    public static void main(String[] args) throws Exception {

        Class<?> aClass = Class.forName(getProperty("bigbanana.class.name"));
        Object o = aClass.getConstructor().newInstance();
        Class<?> paramClass = Class.forName(getProperty("bigbanana.class.method.param"));
        Method method = aClass.getMethod(getProperty("bigbanana.class.method"),paramClass);
        method.invoke(o,"yellow");
    }

}

執行結果:Do you like yellow BigBanana?

3.2.實戰二:代理賣手機--JDK動態代理

3.2.1.簡單理解代理模式

通過代理的方式(增加中間層)對想要訪問的類做一些控制,使代碼職責清晰、通用化、智能化、易擴展。

代理模式三個要點:一個公共介面、一個具體類(被代理類)、一個代理類

代理模式分為:靜態代理和動態代理

  • 靜態代理:代理類在編譯時已創建好具體類的對象,簡言之是幫你提前new好了對象;
  • 動態代理:代理類在程式運行時才創建具體類的對象,根據程式的運行不同可能調用的具體類不同。

JDK動態代理本質是通過反射來實現,涉及InvocationHandler介面和Proxy類。

Proxy類:創建動態代理實例;

InvocationHandler對象:當執行被代理對象里的方法時,實際上會替換成調用InvocationHandler對象的invoke方法,動態的代理到介面下的實現類。

本次案例的類圖關係:

  • 綠色名為JDK動態代理關鍵介面和類
  • 紅色名為本案例要編寫的關鍵介面和類

image

3.2.2.手機介面

public interface Phone {
    void sellPhone();
}

3.2.3.三款手機類

華為

public class Huawei implements Phone {
    // 型號
    private String phoneModelName;

    public Huawei(String phoneModelName){
        this.phoneModelName=phoneModelName;
    }

    @Override
    public void sellPhone() {
        System.out.println("賣Huawei "+this.phoneModelName+" 的手機");
    }
}

小米

public class Xiaomi implements Phone{
    // 型號
    private String phoneModelName;

    public Xiaomi(String phoneModelName){
        this.phoneModelName=phoneModelName;
    }

    public Xiaomi(){

    }
    @Override
    public void sellPhone() {
        System.out.println("賣Xiaomi "+this.phoneModelName+" 的手機");
    }
}

愛瘋

public class IPhone implements Phone {
    // 型號
    private String phoneModelName;

    public IPhone(String phoneModelName){
        this.phoneModelName=phoneModelName;
    }

    @Override
    public void sellPhone() {
        System.out.println("賣IPhone "+this.phoneModelName+" 的手機");
    }
}

3.2.4.Invocation 實現類

public class MyInvocationHandler implements InvocationHandler {
    private Object target;
    public MyInvocationHandler(Object target){
        this.target = target;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        /**
         * 代理前可以考慮做些跟有趣的事
         */
        System.out.println("代理方法--start--代理前可以考慮做些跟有趣的事");
        Object invoke = method.invoke(target, args);
        /**
         * 代理後可能你有更想要做的事
         */
        System.out.println("代理方法--end--代理後可能你有更想要做的事\n");
        return invoke;
    }
}

3.2.5.代理商類

public class MyProxy {
    public static Object getProxy(Object target){
        MyInvocationHandler myInvocationHandler = new MyInvocationHandler(target);
        Object proxyInstance = Proxy.newProxyInstance(Phone.class.getClassLoader(), new Class[]{Phone.class}, myInvocationHandler);
        return proxyInstance;
    }
}

3.2.6.測試類

public class Test {

    public static void main(String[] args) {
        // 不管你想要買什麼手機,只要通過同一個代理商就可以買到
        Phone huawei = (Phone) MyProxy.getProxy(new Huawei("Huawei 16 pro"));
        Phone xiaomi = (Phone) MyProxy.getProxy(new Xiaomi("MI 13 pro"));
        Phone iphone = (Phone) MyProxy.getProxy(new IPhone("IPhone 13 pro"));
        huawei.sellPhone();
        xiaomi.sellPhone();
        iphone.sellPhone();
    }
}

測試結果:

代理方法--start--代理前可以考慮做些跟有趣的事

賣Huawei Huawei 16 pro 的手機

代理方法--end--代理後可能你有更想要做的事

代理方法--start--代理前可以考慮做些跟有趣的事

賣Xiaomi MI 13 pro 的手機

代理方法--end--代理後可能你有更想要做的事

代理方法--start--代理前可以考慮做些跟有趣的事

賣IPhone IPhone 13 pro 的手機

代理方法--end--代理後可能你有更想要做的事

4.反射與泛型的簡單實戰

通過實戰進一步理解泛型和反射。

4.1.實戰一:泛型方法和反射的結合

PrintResult類:註意成員方法私有

public class PrintResult {

    private void printSuccessInfo(){
        System.out.println("printSuccessInfo--執行成功");
    }
    private void printAdd(int[] ints){
        int n=0;
        for (int i :ints){
            n=n+i;
        }
        System.out.println("求和結果="+n);
    }
}

測試類

public class Demo {

    // 執行指定類型的方法
    public <T> void demo1(T t,String methodName,int... args) throws Exception {
        Class<?> tClass = t.getClass();
        Object o = tClass.getConstructor().newInstance();
        if (args == null){
            Method method = tClass.getDeclaredMethod(methodName);
            method.setAccessible(true);
            System.out.println("執行的方法="+method.getName());
            method.invoke(o);
        }else {
            Method method = tClass.getDeclaredMethod(methodName,int[].class);
            method.setAccessible(true);
            System.out.println("執行的方法="+method.getName());
            method.invoke(o,args);
        }
    }


    public static void main(String[] args) throws Exception {
        Demo demo = new Demo();
        PrintResult printResult = new PrintResult();
        demo.demo1(printResult,"printSuccessInfo",null);//執行printResult 無參方法
        demo.demo1(printResult,"printAdd",1888,2222,333);
    }
}

執行結果:

執行的方法=printSuccessInfo

printSuccessInfo--執行成功

執行的方法=printAdd

求和結果=4443

在此可以看到泛型T 的對象t,是可以像普通的對象一樣使用反射。

4.2.實戰二:通過反射越過泛型檢查

泛型在編譯期通過類型抹除機制來完成;

反射在運行期完成執行,可以理解為反射是在運行期將編譯好的list集合再新增元素進去。

public class Demo2 {

    public static void main(String[] args) throws Exception{
        List<String> list = new ArrayList<>();
        list.add("qwert");
        list.add("1234Z");
//        list.add(222); //指定了泛型類型為String後,無法add 非String的值

        Class listClass = list.getClass();
        //獲取和調用 list中的add()方法
        Method m = listClass.getMethod("add", Object.class);
        m.invoke(list, 100);

        //輸出List集合 -- toString已經被AbstractCollection重寫的了
        System.out.println("集合中的內容:"+list.toString());
    }
}

執行結果:集合中的內容:[qwert, 1234Z, 100]

image

Java往期文章

Java全棧學習路線、學習資源和麵試題一條龍

我心裡優秀架構師是怎樣的?

免費下載經典編程書籍

image

原創不易,三聯支持:點贊、分享


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • Keep It Simple, Stupid. 這是開發人耳熟能詳的 KISS 原則,也像是一句有調侃意味的善意提醒,提醒每個前端人,簡潔易懂的用戶體驗和刪繁就簡的搭建邏輯就是前端開發的至簡大道。 這也是袋鼠雲數棧前端開發團隊追求的目標。 數棧是一個專註一站式產品體系,覆蓋數據全鏈路開發流程,全面國 ...
  • 一,server 端的存儲模式為:Server 端 存 儲 模 式 (store-mode) 支 持 三 種 : file: ( 默 認 ) 單 機 模 式 , 全 局 事 務 會 話 信 息 內 存 中 讀 寫 並 持 久 化 本 地 文 件 root.data , 性 能 較 高 ( 默 認 ) ...
  • 一:遍歷 A:遍歷 方式1: public static void printArray(int[] arr) { for(int x=0; x<arr.length; x++) { System.out.println(arr[x]); } } 方式2: public static void pr ...
  • 文末源碼,閱讀大約2.8分鐘 傻瓜式教程 - 體驗滑塊,提供練習場景及源碼。 @ 環境安裝 安裝python需要的依賴包 cv2 安裝可以參考這裡:https://javapub.blog.csdn.net/article/details/123656345 安裝webdriver → chrome ...
  • HashSet集合存儲自定義對象並遍歷。如果對象的成員變數值相同即為同一個對象 * * 註意了: * 你使用的是HashSet集合,這個集合的底層是哈希表結構。 * 而哈希表結構底層依賴:hashCode()和equals()方法。 * 如果你認為對象的成員變數值相同即為同一個對象的話,你就應該在自 ...
  • 《零基礎學Java》 文本組件 文本組件在實際的項目開發中使用的最為廣泛,尤其是 文本框 與 密碼框 組件。 JTextField文本框組件 文本框(JTextField)是用來 顯示 或 編輯 一個單行文本,在 Swing 中通過 javax.swing.JTextField類 對象創建(該類繼承 ...
  • 圖論 圖論是數學的一個分支。它以圖為研究對象。圖論中的圖是由若幹給定的點及連接兩點的線所構成的圖形,這種圖形通常用來描述某些事物之間的某種特定關係,用點代表事物,用連接兩點的線表示相應兩個事物間具有這種關係。 樹 定義 樹是遞歸定義的。 一棵樹是由n(n>0)個元素組成的有限集合,其中每個元素稱為結 ...
  • 1.概述 是什麼:是對方法、類、參數、包、域以及變數等進行代碼功能的增強或者修改程式的行為等操作。 應用 跟蹤代碼依賴性,實現替代配置文件功能在反射中使用Annotation,欄位格式化(如:數據欄位脫敏、日期格式)在編譯時進行格式檢查生成幫助文檔 2.註解相關知識 元註解指的是所有註解都基於它們而 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...