Java第九篇反射和异常处理

news/2024/5/19 6:03:40 标签: java, 反射, 异常处理

 上一篇已经介绍了C#类和java类的区别,由于工作原因经过三天的间断整理才合完,以备后面复习时对比。这里把发射单独做一篇讲解。

首先;当程序要使用某个类时,如果该类还未被加载到内存,则系统通过加载(通过类加载器完成),连接,初始化实现对这个类的加载。加载:就是指将class文件读入内存,并为之创建一个Class对象,任何类被使用时都会建立一个Class对象。连接:验证:是否有正确的内部结构,并和其他类协调一致,准备:负责为类的静态成员分配内存,并设置默认初始化设置,解析:将类的二进制数据中的符号引用替换为直接引用。初始化就是创建对象的过程。

类加载器:1.根类加载器:BootstrapClassLoader 负责java核心类的加载 2.扩展加载器:ExtensionClassLoader :扩展目录中的jar包加载。3.系统类加载器 :systemClassLoader 负责 加载自己的。

通过java反射机制,可以在程序中访问已经装载到JVM中的Java对象的描述,实现访问(私有也可以),检测,和修改描述Java对象本身信息的功能,Java反射机制的功能十分强大,在Java。lang。reflect包中提供了对该功能的支持。所有的类都继承了object类,在object类中定义了getclass方法,该方法返回一个类型为Class的对象。格式:Class 对象名 = 对象。getClass(),得到的对象名可以访问很多方法,实质是通过Class对象获取字段,构造方法,方法,等对象,再通过对象调用此结构。

注意:一个类的不同对象通过getclass方法获取的Class对象是一样的。因为获取的是字节码文件的对象。

获取Class文件对象的几种方式:

1.Object类的getClass()方法,需要对象调用 2.数据类型的静态属性class 3.Class类的静态方法forName(参数为带有包名的类全名,比如:ReflectD.Person) 开发一般用第三种。

举例说明:访问构造方法,将返回Constructor类型的对象或数组,每个Constructor对象代表一个构造方法,利用Constructor对象可以操作相应的构造方法。,如访问指定的构造方法,需要根据该构造方法的入口参数的类型来访问,例如访问一个入口参数类型依次为String 和int型的构造方法,1.对象名。getDeclaredConstructor(String.class, int.Class)2.对象名。getDeclaredConstructor(new Class [] {String.class, int.Class}),getConstructor(类<?>... parameterTypes) 方法是获取想要的构造参数,需要传入想要的构造参数带有的参数的Class对象(String.class),但是想要获取私有的用带Declared的方法并且获取之后要调用SetAccessible(true)方法,true的意思是指反射对象在使用时应该取消java语言访问检查。而Constructor对象又有很多方法可以运用,newInstance()来创建一个目标对象。上面getmethods()是获取包括父亲的公共方法,而带DeclaredMethods()是获取自己的方法。

利用java。lang。reflect。modifier 类可以解析出getModifiers()方法返回的值所表示的修饰符的信息,该功能可以查看该构造函数被什么修饰符修饰。

同理Field中常用的方法:set方法中的obj为通过构造方法返回的目标类对象,访问私有时跟构造方法一样,依然要暴力访问,即用带Declared的方法,并且字段对象调用SetAccessible(true)方法。

Method类中常用的方法 invoke为执行方法,obj为目标类对象。

 

反射有三种实现方法:

第一种方法,forName()静态方法:Class.forName(String str);

// 注意:str - 所需类的完全限定名。

Class class1 = Class.forName("com.mzsds.fanshe.fanshe");

// 第二种方法,.class

Class class2 = String.class;

// 第三种方法,.getClass();

fanshe fs = new fanshe();

Class class3 = fs.getClass();

最后:利用反射可以调用类的私有方法,也可以调用父类的私有方法,当调用父类的私有方法时需要特殊处理,因为可能获取不到,以下为转载的链接

注意调用私有成员前要 constructor/Field/method(各种对象).setAccessible(true); 

通过反射可以绕过泛型检查,比如ArrayList<String>的add方法添加int。

        ArrayList<Integer> arr = new ArrayList<Integer>();
        Class c = arr.getClass();
        Method m = c.getMethod("add", Object.class);
        m.invoke(arr, "hello");
        m.invoke(arr, "world");
        m.invoke(arr, "java");
        System.out.println(arr);

Proxy类是一个代理类 需要自己实现一个InvocationHandler的自定义类。自定义类实现invoke方法,Proxy类调用newProxyInstance方法。类似中介设计模式。

反射后getModifiers()获取的int值为Java修饰符在Java中的整数代号。

PUBLIC: 1
PRIVATE: 2
PROTECTED: 4
STATIC: 8
FINAL: 16
SYNCHRONIZED: 32
VOLATILE: 64
TRANSIENT: 128
NATIVE: 256
INTERFACE: 512
ABSTRACT: 1024
STRICT: 2048

上面返回0的情况是因为 不写修饰符的时候就不是public 其他flag也不满足 所以是0,但经过测试后发现是default级别。

arameterizedType

这是在 java.lang.reflect 包中一个接口的名称,很多文章中把它翻译为“参数化类型,也可以称为泛型实例。其实这个接口就是在说明一个带参数的类型,如:Collection<String>,Map<String,Object>等。这看似大家平常所说的泛型,但其实不然。我们大家平时所说的泛型是Collection<E>和Map<K,V>等,而ParameterizedType表示的是泛型(Collection<E>和Map<K,V>等)的一种实例(Collection<String>和Map<String,Object>)。

1.Type[] getActualTypeArguments() 获取“泛型实例”中<>里面的“泛型变量”(也叫类型参数)的值,这个值是一个类型。因为可能有多个“泛型变量”(如:Map<K,V>),所以返回的是一个Type[]。注意:无论<>中有几层<>嵌套,这个方法仅仅脱去最外层的<>,之后剩下的内容就作为这个方法的返回值,所以其返回值类型是不确定的。

  • List<ArrayList> a1;//返回ArrayList,Class类型
  • List<ArrayList<String>> a2;//返回ArrayList<String>,ParameterizedType类型
  • List<T> a3;//返回T,TypeVariable类型
  • List<? extends Number> a4; //返回? extends Number,WildcardType类型
  • List<ArrayList<String>[]> a5;//返回ArrayList<String>[],GenericArrayType 类型

2.Type getRawType()

返回最外层<>前面那个类型,即Map<K ,V>的Map。

3.Type getOwnerType()

获得这个类型的所有者的类型。这主要是对嵌套定义的内部类而言的,例如于对java.util.Map.Entry<K,V>来说,调用getOwnerType方法返回的就是interface java.util.Map。如果当前类不是内部类,而是一个顶层类,那么getOwnerType方法将返回null。

例子:Field里的getGenericType()方法如果该字段是泛型类型字段,则返回Type(可以强转为arameterizedType),如果不是泛型类型字段,会调用getType返回Class。

TypeVariable

这是在 java.lang.reflect 包中一个接口的名称,其全名为:TypeVariable<D extends GenericDeclaration>,可以称为泛型变量,其实这个接口是在说明“泛型”中的可变量,也就是Collection<E>和Map<K,V>中的E,K和V。

GenericDeclaration

这也是java.lang.reflect包中的一个接口,称它为“可以声明范型变量的实体”。只有实现了这个接口的‘实体’才能声明‘范型变量’”。实现了这个接口的“实体”有哪些呢?如下所示:Class,Constructor,Method。

GenericArrayType

这个也是java.lang.reflect中的接口,其实它是用来描述形如A<T>[]或T[]类型的。如此看来称之为“泛型数组”更为适合。

WildcardType

这个是java.lang.reflect中的接口,其实它是用来描述“泛型”中的通配符表达式(也可以叫泛型参数表达式)的。用于限定“泛型参数”的类型。形如:? extends classA、?super classB。

异常处理

Throwable类是所有异常类的超类,该类的两个直接子类是Error和Exception。其中Error及子类用于指示合理的应用程序不应该试图捕获的严重问题,Expection及其子类给出了合理应用程序需要捕获的异常。而expection又分为编译期异常和运行时异常。runtimeException及其子类是运行时异常(可以不处理),其他为编译器异常(必须处理)。

可控式异常:

IOExpection:当发生某种I/0异常时,抛出此异常

SQLExpection:提供关于数据库访问错误的其他错误信息异常

ClassNotFoundExpection:没找到类的异常

NoSuchFieldExpection:类不包含指定的字段

NoSuchMethodExpection:无法找到某一特定方法,抛出该异常。

运行时异常:

IndexOutofBoundsExpection:数组或者集合超出界限,

NullPointerExpection:访问对象为null时的异常

ArithmeticExpection:异常的运算条件时的异常,

IllegalArgumentExpection:表明向方法传递了一个不合法或者不正确的参数

ClassCastExpection:当试图讲对象转换为不是实例的子类时抛出该异常。

获取异常信息:

getlocalizedMessage()返回Throwable的本地化描述,

getMessage()获取此throwable的详细信息字符串 ,

printStackTrace()将此Throwable及其栈踪迹输出至标准错误流

tostring()获取此throwable的剪短描述

在一个方法的运行过程中,如果发生了异常,则Java虚拟机生成一个代表该异常的对象(它包含了异常的详细信息),并把它交给运行时系统,运行时系统寻找相应的代码来处理这一异常。我们把生成异常对象并把它提交给运行时系统的过程称为抛出(throw)一个异常

异常处理方法A:有些时候我们处理不了异常,我们就用throws抛出异常, 格式:放在方法后面 throws 异常类名。抛出后谁调用 谁需要用try cath 处理 或者继续往上抛。这里指的是编译时异常,如果抛出的是运行时异常,则不用管。tryCatch不能捕获在try语句块里新开的线程里抛出的异常。

throwsthrow 的区别:

throws

用在方法声明后面,跟的是异常类名,可以跟很多个异常类名,用逗号隔开, 表示抛出异常,由该方法调用者处理,throws表示出现异常的一种可能性,并不一定发生这些异常

throw 

用在方法体内,跟的是异常对象, 只能抛出一个异常对象名, 表示抛出异常,由方法体内的语句处理,执行throw一定抛出了某种异常。

异常处理方法B:运行时系统寻找相应的代码来处理这一异常,系统在方法的调用栈中查找,从产生异常的方法开始进行回朔,沿着被调用的顺序往前寻找,直到找到包含相应异常处理的方法为止。这一过程称为捕获(catch)一个异常

try/catch和throws的区别:前者后面程序需要继续运行,后者后续程序不需要继续运行。

格式:

try{

正常程序段,可能抛出异常;

}

catch (异常类1  异常变量) {

捕捉异常类1有关的处理程序段;

}

catch (异常类2  异常变量) {

捕捉异常类2有关的处理程序段;

}

……

finally{

一定会运行的程序代码;

}

l  try块——捕获异常:用于监控可能发生异常的程序代码块是否发生异常,如果发生异常,Try代码块将抛出异常类所产生的对象并立刻结束执行,而转向异常处理catch部分。(try里的代码越少越好)。一旦匹配成功就进行catch处理,然后不再继续执行try里的语句,而继续执行try外面的语句。

对于系统产生的异常或程序块中未用try监控所产生的一场,将一律由java 编译系统自动将异常对象抛出。

l  catch块——处理异常 :抛出的异常对象如果属于catch内所定义的异常类,则catch会捕获该异常,并进入catch中的对应代码段继续运行程序,如果异常对象不属于catch中所定义的异常类,则进入finally块继续运行程序。(catch里必须有内容。catch里的异常类型尽量明确,不明确时用Exception)。jdk7的新特性,可以在catch(异常类型1  | 异常类型2 | ... 对象名)同时处理多个处理方式一样的异常,但是多个异常间必须是平级关系。

Catch包括两个参数:一个是类名,指出捕获的异常类型,必须使Throwable类的子类;一个是参数名,用来引用被捕获的对象。Catch块所捕获的对象并不需要与它的参数类型精确匹配,它可以捕获参数中指出的异常类的对象及其所有子类的对象

l  finally块——最终处理:无论是否发生异常都会执行的语句块。比如执行关闭打开的文件、删除临时文件,关闭数据库连接等操作。如果在finally块之前jvm退出了就不能执行了(比如在catch里执行system.exit()方法)。即使catch和try语句块里有return, finally中的最后也会执行,也就是说,当执行到前面的return时,如果有finally,则先执行finally。执行完毕后继续返回前面的return继续执行,但这种情况在finally里修改要返回的值,则不能影响前面return返回的值,如果finally里也有return,则不在返回到前面的return语句,也就是说直接执行finally里的return,可以影响返回值。

 final:最终,修饰类(不能被继承),成员变量(常量),方法(不能被重写),finally:异常的最终处理,finalize:object的方法,用于垃圾回收。

注意:

l  一个try、catch、finally块之间不能插入任何其它代码

l  catch可以有多个,try和finally只能有一个

l  try后面必须要跟catch、finally其中的一个,即但一个try、catch、finally语句只能省略catch、finally中的一个。

定义多个catch可精确地定位java异常。如果为子类的异常定义了特殊的catch块,而父类的异常则放在另外一个catch块中,此时,必须满足以下规则:子类异常的处理块必须在父类异常处理块的前面,否则会发生编译错误。所以,越特殊的异常越在前面处理,越普遍的异常越在后面处理。这类似于 制订防火墙的规则次序:较特殊的规则在前,较普通的规则在后。

 

异常类常用方法

常用非法

用途

Void String getMessage()

返回异常对象的一个简短描述

Void String toString()

获取异常对象的详细信息

Void printStackTrace()

在控制台上打印异常对象和它的追踪信息

 

异常的抛出可以分为两大类:

l  系统自动抛出异常

比如上面的例子就是系统自动抛出异常,通过try catch捕获异常对象,并继续相应的处理。

 

l  通过关键字throw将异常对象显性地抛出。

即在程序中生成自己的异常对象,即异常可以不是出错产生,而是人为编写代码主动抛出。显性抛出异常从某种程度上实现了将处理异常的代码从正常流程代码中分离开了,使得程序的主线保证相对完整,同时增加了程序的可读性和可维护性。异常沿着调用层次向上抛出,交由调用它的方法来处理。

 

为什么要在方法中抛出异常?

系统自动抛出异常一般就能解决大部分问题,但有时候,程序会产生特定的要求,需要由用户自己定义异常信息,又或者联合开发程序模块时,不同程序员需要将各自负责代码部分尽量避免因程序出错影响其他人的编码,都需要显式抛出异常,以便程序进行处理。这时候就需要在方法中抛出异常。

 

异常抛出的语法:

 throw  new  异常类( );

其中异常类必须Throwable类及其子类。

 

throws子句的方法声明的一般格式如下:

 

<类型说明> 方法名(参数列表) throws  <异常类型列表>

{

  方法体;

}

 

举例:

java">class ThrowException{
         // throwOne方法后用throws声明异常类ArithmeticException
  static void throwOne(int i) throws ArithmeticException {
    if(i==0)
      throw  new  ArithmeticException("i值为零");  //用throw抛出一个异常
  }
public static void main(String  args[]){
     //捕获异常
try{
      throwOne(0);
      }
   catch(ArithmeticException e){
             System.out.println("已捕获到异常错误: "+e.getMessage());
   }
  } 
}

程序执行结果:已捕获到异常错误: i值为零

四、自定义异常

用户自定义的异常类,只需继承一个已有的异常类就可以了,包括继承Execption类及其子类,或者继承已自定义好的异常类。如果没有特别说明,可以直接用Execption类作为父类。

   自定义类的格式如下:

class   异常类名 extends  Exception

{

  ……

}

1.自定义异常类必须继承自Throwable或Exception类,建议用Exception类。一般不把自定义异常作为Error的子类,因为Error通常被用来表示系统内部的严重故障。

2. 当自定义异常是从RuntimeException及其子类继承而来时,该自定义异常是运行时异常,程序中可以不捕获和处理它。

3.  当自定义异常是从Throwable、Exception及其子类继承而来时,该自定义异常是编译时异常,也即程序中必须捕获并处理它。

使用自定义异常的步骤如下:

l  首先通过继承java.lang.Exception类声明自定义的异常类。

l  在方法的声明部分用throws语句声明该方法可能抛出的异常。

l  在方法体的适当位置创建自定义异常类的对象,并用throw语句将异常抛出。

l  调用该方法时对可能产生的异常进行捕获,并处理异常。

java">//NewException.java
class NumberRangeException extends Exception{
     public NumberRangeException(String msg){
           super(msg);
     }
 
//throws重新抛出异常NumberRangeException
         public int CalcAnswer(String str1,String str2) throws NumberRangeException{
                    int int1, int2;
                    int answer = -1;
                    try {   
                             int1 = Integer.parseInt(str1); //可能产生异常对象NumberFormatException e
                             int2 = Integer.parseInt(str2); //可能产生异常对象NumberFormatException e
                              if( (int1 < 10) || (int1 > 20) || (int2< 10) || (int2 > 20) ){ 
                                       NumberRangeException e = new NumberRangeException("Numbersare not within the specified range.");
                                      throw e; //抛出自定义异常对象NumberRangeExceptione
                              }
                              answer = int1 + int2;
                    }
                   catch (NumberFormatExceptione){   //捕获异常对象NumberRangeException e
                              System.out.println( e.toString() );
                    }
                    return answer;
   }
//在调用方法getAnswer中捕获异常
   public void getAnswer(){
                   String answerStr;
                   try{
                            //将num1、num2的中的数字更改为小于10或大于20,以查看捕获异常结果。
                            Stringnum1="13";
                            Stringnum2="12";
                      int answer = CalcAnswer(num1, num2); //抛出异常对象NumberRangeException e
                      answerStr = String.valueOf(answer);
                   }
                   catch (NumberRangeExceptione){        //捕获异常对象NumberRangeException e
                      answerStr = e.getMessage();
                   }
                   System.out.println(answerStr);
   }
  
   public static void main(String args[]) {
     NumberRangeException t1=new NumberRangeException("test");
     t1.getAnswer();
  }

注意事项:1.子类重写父类方法时,子类方法必须抛出相同的异常或者父类异常的子类。2.如果父类抛出多个异常,子类重写父类时,只能抛出相同异常或者它的子类(可以少,不能多), 子类只可以抛出运行时异常,不可以抛出父类没有的编译时异常。3.如果被重写的方法在父类中没有异常抛出,那么子类的方法绝不可以抛出异常,如果子类方法中有异常发生,那么子类只能try,不能throws。

try-with-resources语法糖:《Effective Java》第三版的时候,看到了其中建议将try-finally替换为try-with-resources

Java库中有很多资源需要手动关闭,比如InputStream、OutputStream、java.sql.Connection等等。在此之前,通常是使用try-finally的方式关闭资源;Java7之后,推出了try-with-resources声明来替代之前的方式。 try-with-resources 声明要求其中定义的变量实现 AutoCloseable 接口,这样系统可以自动调用它们的close方法,从而替代了finally中关闭资源的功能。

java">        InputStream in = null;
		OutputStream out = null;
		try {
			in = new FileInputStream(src);
			out = new FileOutputStream(dst);
			byte[] buff = new byte[1024];
			int n;
			while ((n = in.read(buff)) >= 0) {
				out.write(buff, 0, n);
			}
		} catch (IOException e) {
			e.printStackTrace();
		} finally {
			if (in != null) {
				try {
					in.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
			if (out != null) {
				try {
					out.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
		}

可以看出,这种实现非常的丑陋。

下面来看使用了try-with-resources后的效果:

java">	public static void copy2(String src, String dst)
	{
		try (InputStream in = new FileInputStream(src);
		         OutputStream out = new FileOutputStream(dst)) {
		        byte[] buff = new byte[1024];
		        int n;
		        while ((n = in.read(buff)) >= 0) {
		            out.write(buff, 0, n);
		        }
		    } catch (IOException e) {
		        e.printStackTrace();
		    }
	}

try-with-resources将会自动关闭try()中的资源,并且将先关闭后声明的资源。

AutoCloseable接口:

java">public interface AutoCloseable {
    void close() throws Exception;
}

其中仅有一个close方法,实现AutoCloseable接口的类将在close方法中实现其关闭资源的功能。

try-with-resources其实是个语法糖,它将在编译时编译成关闭资源的代码。我们将上述例子中的代码编译成class文件,再反编译回java文件,就能看到如下代码:

java">public static void copy(String var0, String var1) throws IOException {
    FileInputStream var2 = new FileInputStream(var0);
    Throwable var3 = null;

    try {
        FileOutputStream var4 = new FileOutputStream(var1);
        Throwable var5 = null;

        try {
            byte[] var6 = new byte[1024];

            int var7;
            while((var7 = var2.read(var6)) >= 0) {
                var4.write(var6, 0, var7);
            }
        } catch (Throwable var29) {
            var5 = var29;
            throw var29;
        } finally {
            if (var4 != null) {
                if (var5 != null) {
                    try {
                        // 关闭FileOutputStream
                        var4.close();
                    } catch (Throwable var28) {
                        var5.addSuppressed(var28);
                    }
                } else {
                    var4.close();
                }
            }

        }
    } catch (Throwable var31) {
        var3 = var31;
        throw var31;
    } finally {
        if (var2 != null) {
            if (var3 != null) {
                try {
                    // 关闭FileInputStream
                    var2.close();
                } catch (Throwable var27) {
                    var3.addSuppressed(var27);
                }
            } else {
                var2.close();
            }
        }

    }

}

除却处理异常相关的代码,其实就是调用了资源的close方法。我们自定义实现AutoCloseable方法的类可以在close中关闭资源。

java">public class CloaseTest {

	public static void main(String[] args) throws Exception {
		try (CT c = new CT()) {//
			// CT c = new CT();这里生命不会调用close方法
			//我们可以在自定义重写的close中关闭资源
			c.main(); //haha   close
		}
	}
	
}
class CT implements AutoCloseable {

	public void main() {
		System.out.println("haha");
	}
	@Override
	public void close() throws Exception {
		System.out.println("close");
	}
}

 


http://www.niftyadmin.cn/n/1577765.html

相关文章

一个简单的Web音频数据可视化模块

准备跳槽了&#xff0c;意味着我又要做一个什么拿的出手的作品来忽悠面试官了&#xff0c;所以这几天赶了一个音乐播放器出来&#xff0c;写到一半&#xff0c;把里面用到的显示音乐频谱数据的功能分离了出来&#xff0c;在此分享给诸位。 演示地址&#xff1a; http://margox.…

Java第12篇输入输出I/O和ASCII码

Unix使用斜杆/ 作为路径分隔符&#xff0c;而web应用最新使用在Unix系统上面&#xff0c;所以目前所有的网络地址都采用 斜杆/ 作为分隔符。 Windows由于使用 斜杆/ 作为DOS命令提示符的参数标志了&#xff0c;为了不混淆&#xff0c;所以采用 反斜杠\ 作为路径分隔符。所以目前…

Java8新特性-Lambda表达式

1. 什么是Lambda表达式&#xff1f;Lambda表达式就是可以把函数作为参数传递&#xff0c;或者说把代码作为数据传递给函数。2. Lambda表达式的语法格式基本语法格式如下:基本语法下多个变体的说明&#xff1a;1). 多个参数中间用逗号分隔&#xff1a;(int m,int n)>{int re…

Swing程序设计

Swing是 GUI&#xff08;图形用户界面&#xff09;开发工具包&#xff0c;在AWT&#xff08;CLI&#xff09;&#xff08;抽象窗口工具包&#xff09;的基础上使开发开发跨平台的java应用程序界面成为可能&#xff0c;早期的AWT依赖于本地系统&#xff0c;Swing开发的不受平台限…

AsyncTask.cancel()的结束问题

实际项目中有这么一个问题&#xff0c;用户进入详情界面&#xff0c;那么我们就要网络加载数据并展现在UI上&#xff0c;这个加载用线程或者异步。 这里就拿项目中统一用异步任务来获取网络数据把。 用户可能会有这么一个操作&#xff0c;它在一个商品&#xff08;说说等&#…

CREATING A WINDOWS IMAGE FOR OPENSTACK(转)

2019独角兽企业重金招聘Python工程师标准>>> CREATING A WINDOWS IMAGE FOR OPENSTACK If you want to build a Windows image for use in your OpenStack environment, you can follow the example in the official documentation, or you can grab a Windows 2012…

哎呦,我这暴脾气

2019独角兽企业重金招聘Python工程师标准>>> 不知道大家有没有注意到一个令人匪夷所思的现象&#xff0c;在我们的生活当中&#xff0c;往往脾气差难相处的人会比脾气好易相处的人更容易得到别人的尊敬&#xff0c;甚至是赞赏。虽然这个现象有悖我们普通人的交际理念…

C#的委托和java里的委托

C#&#xff1a;委托是方法的代理&#xff0c;委托的是方法&#xff0c;当调用委托时就调用了它的方法&#xff0c;是一类行为的抽象&#xff0c;是一种自定义数据类型。它有统一的返回类型和参数。1.定义委托&#xff1a;访问级别 delegate 返回值 委托的名称&#xff08;参数组…