java反射之Method的invoke方法实现

在框架中经常会会用到method.invoke()方法,用来执行某个的对象的目标方法。以前写代码用到反射时,总是获取先获取Method,然后传入对应的Class实例对象执行方法。然而前段时间研究invoke方法时,发现invoke方法居然包含多态的特性,这是以前没有考虑过的一个问题。那么Method.invoke()方法的执行过程是怎么实现的?它的多态又是如何实现的呢?

本文将从java和JVM的源码实现深入探讨invoke方法的实现过程。

首先给出invoke方法多态特性的演示代码:

public class MethodInvoke {public static void main(String[] args) throws Exception {Method animalMethod = Animal.class.getDeclaredMethod("print");Method catMethod = Cat.class.getDeclaredMethod("print");Animal animal = new Animal();Cat cat = new Cat();animalMethod.invoke(cat);animalMethod.invoke(animal);catMethod.invoke(cat);catMethod.invoke(animal);}}class Animal {public void print() {System.out.println("Animal.print()");}}class Cat extends Animal {@Overridepublic void print() {System.out.println("Cat.print()");}}

代码中,Cat类覆盖了父类Animal的print()方法, 然后通过反射分别获取print()的Method对象。最后分别用Cat和Animal的实例对象去执行print()方法。其中animalMethod.invoke(animal)和catMethod.invoke(cat),示例对象的真实类型和Method的声明Classs是相同的,按照预期打印结果;animalMethod.invoke(cat)中,由于Cat是Animal的子类,按照多态的特性,子类调用父类的的方法,方法执行时会动态链接到子类的实现方法上。因此,这里会调用Cat.print()方法;而catMethod.invoke(animal)中,传入的参数类型Animal是父类,却期望调用子类Cat的方法,因此这一次会抛出异常。代码打印结果为:

Cat.print()
Animal.print()
Cat.print()
Exception in thread "main" java.lang.IllegalArgumentException: object is not an instance of declaring classat sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)at java.lang.reflect.Method.invoke(Unknown Source)at com.wy.invoke.MethodInvoke.main(MethodInvoke.java:17)

接下来,我们来看看invoke()方法的实现过程。

    public Object invoke(Object obj, Object... args) throws IllegalAccessException, IllegalArgumentException, InvocationTargetException{if (!override) {if (!Reflection.quickCheckMemberAccess(clazz, modifiers)) {Class<?> caller = Reflection.getCallerClass(1);checkAccess(caller, clazz, obj, modifiers);}}MethodAccessor ma = methodAccessor;             // read volatileif (ma == null) {ma = acquireMethodAccessor();}return ma.invoke(obj, args);}

invoke()方法中主要分为两部分:访问控制检查和调用MethodAccessor.invoke()实现方法执行。

首先看一下访问控制检查这一块的逻辑。第一眼看到这里的逻辑的时候,很容易搞不清楚是干嘛的。通俗来讲就是通过方法的修饰符(public/protected/private/package),来判断方法的调用者是否可以访问该方法。这是java的基础内容,不过用代码写出来,一下子不容易想到。访问控制检查分为3步:

  1. 检查override,如果override为true,跳过检查;否则继续;
  2. 快速检查,判断该方法的修饰符modifiers是否为public,如果是跳过检查;否则继续;
  3. 详细检查,通过方法的(protected/private/package)修饰符或方法的声明类(例如子类可以访问父类的protected方法)与调用者caller之间的关系,判断caller是否有权限访问该方法。

override属性是Method的父类AccessibleObject中声明的变量,使得程序可以控制是否跳过访问权限的检查。另外,Method的实例对象中,override属性的初始值设置为false。

    public void setAccessible(boolean flag) throws SecurityException {SecurityManager sm = System.getSecurityManager();if (sm != null) sm.checkPermission(ACCESS_PERMISSION);setAccessible0(this, flag);}private static void setAccessible0(AccessibleObject obj, boolean flag)throws SecurityException{if (obj instanceof Constructor && flag == true) {Constructor<?> c = (Constructor<?>)obj;if (c.getDeclaringClass() == Class.class) {throw new SecurityException("Can not make a java.lang.Class" +" constructor accessible");}}obj.override = flag;}

多说一句,Field同样继承了AccessibleObject,且Field的override也是初始化为false的,也就是说并没有按照变量的修饰符去初始化不同的值。但是我们在调用Field.set(Object obj, Object value)时,如果该Field是private修饰的,会因没有访问权限而抛出异常,因此必须调用setAccessible(true)。此处非常容易理解为因为变量是public的,所以override就被初始化为true。

invoke()方法中,访问控制检查之后,就是通过MethodAccessor.invoke()调用方法。再来看一下代码:

        MethodAccessor ma = methodAccessor;             // read volatileif (ma == null) {ma = acquireMethodAccessor();}return ma.invoke(obj, args);

这里的逻辑很简单,首先将变量methodAccessor赋值给ma,在方法栈中保存一个可以直接引用的本地变量,如果methodAccessor不存在,调用acquireMethodAccessor()方法创建一个。

    private volatile MethodAccessor methodAccessor;private Method root;private MethodAccessor acquireMethodAccessor() {// First check to see if one has been created yet, and take it// if soMethodAccessor tmp = null;if (root != null) tmp = root.getMethodAccessor();if (tmp != null) {methodAccessor = tmp;} else {// Otherwise fabricate one and propagate it up to the roottmp = reflectionFactory.newMethodAccessor(this);setMethodAccessor(tmp);}return tmp;}void setMethodAccessor(MethodAccessor accessor) {methodAccessor = accessor;// Propagate upif (root != null) {root.setMethodAccessor(accessor);}}Method copy() {Method res = new Method(clazz, name, parameterTypes, returnType,exceptionTypes, modifiers, slot, signature,annotations, parameterAnnotations, annotationDefault);res.root = this;res.methodAccessor = methodAccessor;return res;}

综合acquireMethodAccessor(),setMethodAccessor()以及copy()这三个方法,可以看到一个Method实例对象维护了一个root引用。当调用Method.copy()进行方法拷贝时,root指向了被拷贝的对象。那么当一个Method被多次拷贝后,调用一次setMethodAccessor()方法,就会将root引用所指向的Method的methodAccessor变量同样赋值。例如:D -> C -> B -> A,其中X-> Y表示X = Y.copy(), 当C对象调用setMethodAccessor()时,B和A都会传播赋值methodAccessor, 而D的methodAccessor还是null。紧接着,当D需要获取methodAccessor而调用acquireMethodAccessor()时,D获取root的methodAccessor, 那么D将和ABC持有相同的methodAccessor。

虽然Method中,通过维护root引用意图使相同的方法始终保持只有一个methodAccessor实例,但是上述方法仍然无法保证相同的方法只有一个methodAccessor实例。例如通过copy()使ABCD保持关系:D -> C -> B -> A, 当B对象调用setMethodAccessor()时,B和A都会赋值methodAccessor, 而C、D的methodAccessor还是null。这时D调用acquireMethodAccessor()时,D获取root也就是C的methodAccessor,发现为空,然后就新创建了一个。从而出现了相同的方法中出现了两个methodAccessor实例对象的现象。

在Class.getMethod()、Class.getDeclaredMethod()以及Class.getDeclaredMethod(String name, Class<?>... parameterTypes)方法中最终都会调用copy()方法来保障Method使用的安全性。 在比较极端加巧合的情况下,可能会引起类膨胀的问题,这就是接下来要讲到的MethodAccessor的实现机制。

copy

在前面代码中,MethodAccessor的创建是通过反射工厂ReflectionFactory的newMethodAccessor(Method)方法来创建的。

    public MethodAccessor newMethodAccessor(Method method) {checkInitted();if (noInflation) {return new MethodAccessorGenerator().generateMethod(method.getDeclaringClass(),method.getName(),method.getParameterTypes(),method.getReturnType(),method.getExceptionTypes(),method.getModifiers());} else {NativeMethodAccessorImpl acc =new NativeMethodAccessorImpl(method);DelegatingMethodAccessorImpl res =new DelegatingMethodAccessorImpl(acc);acc.setParent(res);return res;}}

其中, checkInitted()方法检查从配置项中读取配置并设置noInflation、inflationThreshold的值:

    private static void checkInitted() {if (initted) return;AccessController.doPrivileged(new PrivilegedAction<Void>() {public Void run() {if (System.out == null) {// java.lang.System not yet fully initializedreturn null;}String val = System.getProperty("sun.reflect.noInflation");if (val != null && val.equals("true")) {noInflation = true;}val = System.getProperty("sun.reflect.inflationThreshold");if (val != null) {try {inflationThreshold = Integer.parseInt(val);} catch (NumberFormatException e) {throw (RuntimeException)new RuntimeException("Unable to parse property sun.reflect.inflationThreshold").initCause(e);}}initted = true;return null;}});}

可以通过启动参数-Dsun.reflect.noInflation=false -Dsun.reflect.inflationThreshold=15来设置:

结合字面意思及下面代码理解,这两个配置sun.reflect.noInflation是控制是否立即进行类膨胀,sun.reflect.inflationThreshold是设置类膨胀阈值。

创建MethodAccessor有两种选择,一种是当sun.reflect.noInflation配置项为true时,ReflectionFactory利用MethodAccessor的字节码生成类 MethodAccessorGenerator直接创建一个代理类,通过间接调用原方法完成invoke()任务,具体实现稍后给出。MethodAccessor的另一种实现方式是,创建DelegatingMethodAccessorImpl 委托类,并将执行invoke()方法的具体内容交由NativeMethodAccessorImpl实现,而NativeMethodAccessorImpl最终调用native方法完成invoke()任务。以下是NativeMethodAccessorImpl的invoke()方法实现。

    public Object invoke(Object obj, Object[] args) throws IllegalArgumentException, InvocationTargetException{if (++numInvocations > ReflectionFactory.inflationThreshold()) {MethodAccessorImpl acc = (MethodAccessorImpl)new MethodAccessorGenerator().generateMethod(method.getDeclaringClass(),method.getName(),method.getParameterTypes(),method.getReturnType(),method.getExceptionTypes(),method.getModifiers());parent.setDelegate(acc);}return invoke0(method, obj, args);}private static native Object invoke0(Method m, Object obj, Object[] args);

可以看到,当numInvocations数量大于配置项sun.reflect.inflationThreshold即类膨胀阈值时, 使用MethodAccessorGenerator创建一个代理类对象,并且将被委托的NativeMethodAccessorImpl的parent,也就是委托类DelegatingMethodAccessorImpl的委托类设置为这个生成的代理对象。这么说可能有点绕,下面用一幅图表示这个过程。

delegate

总体来说,当调用invoke()时,按照默认配置,Method首先创建一个DelegatingMethodAccessorImpl对象,并设置一个被委托的NativeMethodAccessorImpl对象,那么method.invoke()就被转换成DelegatingMethodAccessorImpl.invoke(),然后又被委托给NativeMethodAccessorImp.invoke()实现。当NativeMethodAccessorImp.invoke()调用次数超过一定热度时(默认15次),被委托方又被转换成代理类来实现。

之前提到过在极端情况下,同一个方法的Method对象存在多个不同拷贝拷贝时,可能存在多个MethodAccessor对象。那么当多次调用后,必然会生成两个重复功能的代理类。当然,一般情况下,生成两个代理类并没有较大的影响。

其中代理类的具体字节码实现过程较为复杂,大体思想是生成一个如下所示的类:

public class GeneratedMethodAccessor1 extends MethodAccessorImpl {public GeneratedMethodAccessor1 () {super();}public Object invoke(Object obj, Object[] args)throws IllegalArgumentException, InvocationTargetException {if (!(obj instanceof Cat)) {throw new ClassCastException();}if (args != null && args.length != 0) {throw new IllegalArgumentException();}try {Cat cat = (Cat) obj;cat.print();return null;} catch (Throwable e) {throw new InvocationTargetException(e, "invoke error");}}}

到目前为止,除了在代理的GeneratedMethodAccessor1 类中,方法的执行有多态的特性,而NativeMethodAccessorImp的invoke()实现是在jdk中的完成的。接下来我们将目光移到NativeMethodAccessorImp的native方法invoke0();

openJDK下载地址

首先,在\jdk\src\share\native\sun\reflect路径下找到NativeAccessors.c, 其中有Java_sun_reflect_NativeMethodAccessorImpl _invoke0()方法,根据JNI定义函数名的规则"包名_类名_方法名",这就是我们要找的native方法实现入口。

JNIEXPORT jobject JNICALL Java_sun_reflect_NativeMethodAccessorImpl_invoke0
(JNIEnv *env, jclass unused, jobject m, jobject obj, jobjectArray args)
{return JVM_InvokeMethod(env, m, obj, args);
}

方法调用JVM_InvokeMethod(), 一般以JVM_开头的函数定义在jvm.cpp文件中,不熟悉的话可以通过头文件jvm.h看出来。继续追踪,发现jvm.cpp文件位于spot\src\share\vm\prims文件夹下。

JVM_ENTRY(jobject, JVM_InvokeMethod(JNIEnv *env, jobject method, jobject obj, jobjectArray args0))JVMWrapper("JVM_InvokeMethod");Handle method_handle;if (thread->stack_available((address) &method_handle) >= JVMInvokeMethodSlack) {method_handle = Handle(THREAD, JNIHandles::resolve(method));Handle receiver(THREAD, JNIHandles::resolve(obj));objArrayHandle args(THREAD, objArrayOop(JNIHandles::resolve(args0)));oop result = Reflection::invoke_method(method_handle(), receiver, args, CHECK_NULL);jobject res = JNIHandles::make_local(env, result);if (JvmtiExport::should_post_vm_object_alloc()) {oop ret_type = java_lang_reflect_Method::return_type(method_handle());assert(ret_type != NULL, "sanity check: ret_type oop must not be NULL!");if (java_lang_Class::is_primitive(ret_type)) {// Only for primitive type vm allocates memory for java object.// See box() method.JvmtiExport::post_vm_object_alloc(JavaThread::current(), result);}}return res;} else {THROW_0(vmSymbols::java_lang_StackOverflowError());}
JVM_END

其中oop result = Reflection::invoke_method(method_handle(), receiver, args, CHECK_NULL)是方法的执行过程,在\hotspot\src\share\vm\runtime路径下找到reflection.cpp文件。

oop Reflection::invoke_method(oop method_mirror, Handle receiver, objArrayHandle args, TRAPS) {oop mirror             = java_lang_reflect_Method::clazz(method_mirror);int slot               = java_lang_reflect_Method::slot(method_mirror);bool override          = java_lang_reflect_Method::override(method_mirror) != 0;objArrayHandle ptypes(THREAD, objArrayOop(java_lang_reflect_Method::parameter_types(method_mirror)));oop return_type_mirror = java_lang_reflect_Method::return_type(method_mirror);BasicType rtype;if (java_lang_Class::is_primitive(return_type_mirror)) {rtype = basic_type_mirror_to_basic_type(return_type_mirror, CHECK_NULL);} else {rtype = T_OBJECT;}instanceKlassHandle klass(THREAD, java_lang_Class::as_Klass(mirror));Method* m = klass->method_with_idnum(slot);if (m == NULL) {THROW_MSG_0(vmSymbols::java_lang_InternalError(), "invoke");}methodHandle method(THREAD, m);return invoke(klass, method, receiver, override, ptypes, rtype, args, true, THREAD);
}oop Reflection::invoke(instanceKlassHandle klass, methodHandle reflected_method,Handle receiver, bool override, objArrayHandle ptypes,BasicType rtype, objArrayHandle args, bool is_method_invoke, TRAPS) {ResourceMark rm(THREAD);methodHandle method;      // actual method to invokeKlassHandle target_klass; // target klass, receiver's klass for non-static// Ensure klass is initializedklass->initialize(CHECK_NULL);bool is_static = reflected_method->is_static();if (is_static) {// ignore receiver argumentmethod = reflected_method;target_klass = klass;} else {// check for null receiverif (receiver.is_null()) {THROW_0(vmSymbols::java_lang_NullPointerException());}// Check class of receiver against class declaring methodif (!receiver->is_a(klass())) {THROW_MSG_0(vmSymbols::java_lang_IllegalArgumentException(), "object is not an instance of declaring class");}// target klass is receiver's klasstarget_klass = KlassHandle(THREAD, receiver->klass());// no need to resolve if method is private or <init>if (reflected_method->is_private() || reflected_method->name() == vmSymbols::object_initializer_name()) {method = reflected_method;} else {// resolve based on the receiverif (reflected_method->method_holder()->is_interface()) {// resolve interface callif (ReflectionWrapResolutionErrors) {// new default: 6531596// Match resolution errors with those thrown due to reflection inlining// Linktime resolution & IllegalAccessCheck already done by Class.getMethod()method = resolve_interface_call(klass, reflected_method, target_klass, receiver, THREAD);if (HAS_PENDING_EXCEPTION) {// Method resolution threw an exception; wrap it in an InvocationTargetExceptionoop resolution_exception = PENDING_EXCEPTION;CLEAR_PENDING_EXCEPTION;JavaCallArguments args(Handle(THREAD, resolution_exception));THROW_ARG_0(vmSymbols::java_lang_reflect_InvocationTargetException(),vmSymbols::throwable_void_signature(),&args);}} else {method = resolve_interface_call(klass, reflected_method, target_klass, receiver, CHECK_(NULL));}}  else {// if the method can be overridden, we resolve using the vtable index.assert(!reflected_method->has_itable_index(), "");int index = reflected_method->vtable_index();method = reflected_method;if (index != Method::nonvirtual_vtable_index) {// target_klass might be an arrayKlassOop but all vtables start at// the same place. The cast is to avoid virtual call and assertion.InstanceKlass* inst = (InstanceKlass*)target_klass();method = methodHandle(THREAD, inst->method_at_vtable(index));}if (!method.is_null()) {// Check for abstract methods as wellif (method->is_abstract()) {// new default: 6531596if (ReflectionWrapResolutionErrors) {ResourceMark rm(THREAD);Handle h_origexception = Exceptions::new_exception(THREAD,vmSymbols::java_lang_AbstractMethodError(),Method::name_and_sig_as_C_string(target_klass(),method->name(),method->signature()));JavaCallArguments args(h_origexception);THROW_ARG_0(vmSymbols::java_lang_reflect_InvocationTargetException(),vmSymbols::throwable_void_signature(),&args);} else {ResourceMark rm(THREAD);THROW_MSG_0(vmSymbols::java_lang_AbstractMethodError(),Method::name_and_sig_as_C_string(target_klass(),method->name(),method->signature()));}}}}}}// I believe this is a ShouldNotGetHere case which requires// an internal vtable bug. If you ever get this please let Karen know.if (method.is_null()) {ResourceMark rm(THREAD);THROW_MSG_0(vmSymbols::java_lang_NoSuchMethodError(),Method::name_and_sig_as_C_string(klass(),reflected_method->name(),reflected_method->signature()));}// In the JDK 1.4 reflection implementation, the security check is// done at the Java levelif (!(JDK_Version::is_gte_jdk14x_version() && UseNewReflection)) {// Access checking (unless overridden by Method)if (!override) {if (!(klass->is_public() && reflected_method->is_public())) {bool access = Reflection::reflect_check_access(klass(), reflected_method->access_flags(), target_klass(), is_method_invoke, CHECK_NULL);if (!access) {return NULL; // exception}}}} // !(Universe::is_gte_jdk14x_version() && UseNewReflection)assert(ptypes->is_objArray(), "just checking");int args_len = args.is_null() ? 0 : args->length();// Check number of argumentsif (ptypes->length() != args_len) {THROW_MSG_0(vmSymbols::java_lang_IllegalArgumentException(), "wrong number of arguments");}// Create object to contain parameters for the JavaCallJavaCallArguments java_args(method->size_of_parameters());if (!is_static) {java_args.push_oop(receiver);}for (int i = 0; i < args_len; i++) {oop type_mirror = ptypes->obj_at(i);oop arg = args->obj_at(i);if (java_lang_Class::is_primitive(type_mirror)) {jvalue value;BasicType ptype = basic_type_mirror_to_basic_type(type_mirror, CHECK_NULL);BasicType atype = unbox_for_primitive(arg, &value, CHECK_NULL);if (ptype != atype) {widen(&value, atype, ptype, CHECK_NULL);}switch (ptype) {case T_BOOLEAN:     java_args.push_int(value.z);    break;case T_CHAR:        java_args.push_int(value.c);    break;case T_BYTE:        java_args.push_int(value.b);    break;case T_SHORT:       java_args.push_int(value.s);    break;case T_INT:         java_args.push_int(value.i);    break;case T_LONG:        java_args.push_long(value.j);   break;case T_FLOAT:       java_args.push_float(value.f);  break;case T_DOUBLE:      java_args.push_double(value.d); break;default:THROW_MSG_0(vmSymbols::java_lang_IllegalArgumentException(), "argument type mismatch");}} else {if (arg != NULL) {Klass* k = java_lang_Class::as_Klass(type_mirror);if (!arg->is_a(k)) {THROW_MSG_0(vmSymbols::java_lang_IllegalArgumentException(), "argument type mismatch");}}Handle arg_handle(THREAD, arg);         // Create handle for argumentjava_args.push_oop(arg_handle); // Push handle}}assert(java_args.size_of_parameters() == method->size_of_parameters(), "just checking");// All oops (including receiver) is passed in as Handles. An potential oop is returned as an// oop (i.e., NOT as an handle)JavaValue result(rtype);JavaCalls::call(&result, method, &java_args, THREAD);if (HAS_PENDING_EXCEPTION) {// Method threw an exception; wrap it in an InvocationTargetExceptionoop target_exception = PENDING_EXCEPTION;CLEAR_PENDING_EXCEPTION;JavaCallArguments args(Handle(THREAD, target_exception));THROW_ARG_0(vmSymbols::java_lang_reflect_InvocationTargetException(),vmSymbols::throwable_void_signature(),&args);} else {if (rtype == T_BOOLEAN || rtype == T_BYTE || rtype == T_CHAR || rtype == T_SHORT)narrow((jvalue*) result.get_value_addr(), rtype, CHECK_NULL);return box((jvalue*) result.get_value_addr(), rtype, CHECK_NULL);}
}

Reflection::invoke_method()中调用Reflection::invoke(),然后在Reflection::invoke()方法中,当反射调用的方法是接口方法时,调用Reflection::resolve_interface_call(),该方法依赖LinkResolver::resolve_interface_call()来完成方法的动态链接过程,具体实现就不在这里展示。

method = resolve_interface_call(klass, reflected_method, target_klass, receiver, CHECK_(NULL));
methodHandle Reflection::resolve_interface_call(instanceKlassHandle klass, methodHandle method,KlassHandle recv_klass, Handle receiver, TRAPS) {assert(!method.is_null() , "method should not be null");CallInfo info;Symbol*  signature  = method->signature();Symbol*  name       = method->name();LinkResolver::resolve_interface_call(info, receiver, recv_klass, klass,name, signature,KlassHandle(), false, true,CHECK_(methodHandle()));return info.selected_method();
}

如果反射调用的方法是可以被覆盖的方法,例如Animal.print(), Reflection::invoke()最终通过查询虚方法表vtable来确定最终的method。

        // if the method can be overridden, we resolve using the vtable index.assert(!reflected_method->has_itable_index(), "");int index = reflected_method->vtable_index();method = reflected_method;if (index != Method::nonvirtual_vtable_index) {// target_klass might be an arrayKlassOop but all vtables start at// the same place. The cast is to avoid virtual call and assertion.InstanceKlass* inst = (InstanceKlass*)target_klass();method = methodHandle(THREAD, inst->method_at_vtable(index));}

 

总结

1.method.invoke()方法支持多态特性,其native实现在方法真正执行之前通过动态连接或者虚方法表来实现。

2.框架中使用method.invoke()执行方法调用时,初始获取method对象时,可以先调用一次setAccessable(true),使得后面每次调用invoke()时,节省一次方法修饰符的判断,略微提升性能。业务允许的情况下,Field同样可以如此操作。

3.委托模式可以解决一种方案的多种实现之间自由切换,而代理模式只能根据传入的被代理对象来实现功能。

 

 

参考文章:

JAVA深入研究——Method的Invoke方法。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://xiahunao.cn/news/1619722.html

如若内容造成侵权/违法违规/事实不符,请联系瞎胡闹网进行投诉反馈,一经查实,立即删除!

相关文章

java method方法_java入门(六) | 方法(Method)的定义和使用

上一期是对java的分支结构进行讲解和实练,分支结构有if 、if(判断语句..){满足条件的代码...}else{不满足的代码}、else if以及switch case,你对他们有理解的怎样呢? 这一期是对方法method的介绍,它也被称之为函数,它的基本格式为: 方法的修饰符 方法的返回值 方法的名字…

Java中的方法(method)

1.方法概述 什么是方法&#xff1f; 方法是具有独立功能的代码块组织成为一个整体&#xff0c;使其具有特殊功能的代码集 注意&#xff1a; 方法必须先创建才可以使用&#xff0c;该过程称为方法定义 方法创建后并不是直接运行的&#xff0c;需要手动使用后执行&#xff0c;该过…

计算机组成原理(主存储器的基本组成、 运算器的基本组成、 控制器的基本组成、完成一条指令的三个阶段)

主存储器的基本组成&#xff1a; 这个是读数据操作图&#xff1a; 读入数据与菜鸟驿站的取货流程差不多&#xff1a; 写入数据的过程与读入数据类似&#xff1a; 1、cpu 指明想要写入到那个位置&#xff08;写到MAR中&#xff09; 2、想要写入的数据会放到MDR中 3、c…

MVSNet 代码注释版 下载 (pytorch版)(注释非常详细,较源码结构有调整,使用起来更方便)

MVSNet 代码注释版 下载 &#xff08;注释非常详细&#xff0c;代码结构有所调整&#xff0c;使用起来更方便&#xff09; 本代码不仅进行了详细注释&#xff0c;还对源码做了相应调整&#xff0c;可以更方便用户使用&#xff0c; 结构上&#xff0c;更加清晰&#xff1b; 代…

超级计算机运行吃鸡,决赛圈的时候,如果两个人同时被手雷炸死该怎么办呢?...

这款这么火的绝地求生&#xff0c;在决赛圈的时候如果真的两个人被手雷同时炸死该怎么办呢&#xff1f;小编就告诉你自己的亲身经历吧。 第一个&#xff1a;手雷在你手中爆炸&#xff0c;然后与敌人同归&#xff0c;相信大多数人有这样一种习惯&#xff0c;捏住手雷5秒扔掉&…

WebGL非矩阵变换

目录 平移 示例代码&#xff1a; 齐次坐标矢量的最后一个分量w 旋转 p的坐标&#xff0c;可得等式 R1&#xff1a; 使用r、α、β来表示点p的坐标&#xff0c;可得等式 R2&#xff1a; 利用三角函数两角和公式&#xff0c;可得等式 R3&#xff1a; 最后&#xff0c;将…

Unity中抛物线C#实现的两种方式

在游戏中会出现抛物线的方式&#xff08;如&#xff1a;炮弹轨迹&#xff0c;扔手雷等&#xff09; 下面是实现了两种方式&#xff0c;在学习之路上不停探索。欢迎指出不足&#xff0c;共同进步 实现— 根据开始位置跟结束位置做抛物线 using UnityEngine; using System.Col…

iPhone游戏 Fragger 扔手雷 通关

这个小游戏我觉得根愤怒的小鸟有的一拼,而且它的操作方式又很类似早期的PC网络游戏疯狂坦克. 我在疯狂坦克中的排名是1000以内,所以我是金牌,玩这个游戏又找到了当年的感觉. 以图片记录一该游戏中的关卡,个人觉得它们都是不错的设计.

Unity实用案例之——“吃鸡”手雷弹道模拟

最近吃鸡游戏火啊&#xff0c;至今也吃了好几晚的鸡了&#xff0c;无奈手雷就是丢不准&#xff0c;从窗户丢雷丢几个弹出几个&#xff0c;各种误伤自己人……而别人家的手雷&#xff1a; 一般的游戏里手雷都是盲投&#xff0c;不过一般游戏也不会对弹道有这么精确的要求&…

Unity3D——射击游戏(多地图,多人物,枪支切换,驾车,扔手雷等功能,堪比小型和平精英)

演示1: 演示2: 演示3: 演示4&#xff1a; 源代码和运行程序 链接&#xff1a;https://pan.baidu.com/s/1QZ9UGWPAHO1zRgW5qNCjcw?pwd4m31 提取码&#xff1a;4m31 本款游戏是一个多场景&#xff0c;多角色&#xff0c;多枪支可选的枪战游戏&#xff0c;类似于市面上的小型…

C# textBox1.Text=““与textBox1.Clear()的区别

一、区别 textbox.Text "" 和 textbox.Clear() 都可以用于清空文本框的内容&#xff0c;但它们之间有一些细微的区别。 textbox.Text "": 这种方式会将文本框的 Text 属性直接设置为空字符串。这样会立即清除文本框的内容&#xff0c;并将文本框显示为空…

几个nlp的小任务(生成任务(摘要生成))

几个nlp的小任务生成任务——摘要生成 安装库选择模型加载数据集展示数据集数据预处理 tokenizer注意特殊的 token处理组成预处理函数调用map,对数据集进行预处理微调模型,设置参数设置数据收集器,将处理好的数据喂给模型封装测评方法将参数传给 trainer,开始训练安装库 选…

Vue3(开发h5适配)

在开发移动端的时候需要适配各种机型&#xff0c;有大的&#xff0c;有小的&#xff0c;我们需要一套代码&#xff0c;在不同的分辨率适应各种机型。 因此我们需要设置meta标签 <meta name"viewport" content"widthdevice-width, initial-scale1.0">…

JDK介绍

JDK,JRE和JVM之间的关系 JVM是运行环境&#xff0c;JRE是含运行环境和相关的类库&#xff0c;跟node环境是一个意思 JDK目录介绍 目录名称说明bin该路径下存放了JDK的各种工具命令。javac和java就放在这个目录。conf该路径下存放了JDK的相关配置文件include该路径下存放了一些…

【算法训练-链表】合并两个有序链表、合并K个有序链表

废话不多说&#xff0c;喊一句号子鼓励自己&#xff1a;程序员永不失业&#xff0c;程序员走向架构&#xff01;首先&#xff0c;链表对应的数据结构在这篇Blog中&#xff1a;【基本数据结构 一】线性数据结构&#xff1a;链表&#xff0c;基于对基础知识的理解来进行题目解答。…

alias命令手册

alias&#xff1a;查看和定义别名 功能描述 alias命令用来设置指令的别名。使用alias时&#xff0c;用户可以使用单引号 ‘ ‘ 将原来的命令引起来&#xff0c;防止特殊字符导致错误。 alias命令的作用只局限于该次登入的操作。若要每次登入都能够使用这些命令别名&#xff0c;…

Ubuntu学习之alias命令

Ubuntu学习之alias命令 1.1 alias功能介绍 当我们经常需要在命令窗键入复杂冗长的命令时&#xff0c;alias就派上用场啦。alias允许用户为命令创建简单的名称或缩写&#xff0c;哪怕这个缩写只有一个字符。即为指令设置别名。 1.2 alias语法 语法&#xff1a;alias [name”va…

linux alias命令路径,Linux alias命令

本文概述 Linux的” alias”命令将shell中的一个字符串替换为另一个字符串。这是一个shell内置命令。它将复杂的命令转换为更简单的命令, 换句话说, 通过将其替换为更简单的命令来创建快捷方式。 在命令行中使用”alias”会创建一个临时的”alias”。临时alias仅在退出外壳程序…

git alias

git alias 其实之前就用过一些 alias&#xff0c;比如说 git reflog show 就是 git log -g --abbrev-commit --prettyoneline 的 alias&#xff0c;一般 alias 可以存储到 git 的 config 文件&#xff0c;repo 等级的在 .git 下&#xff0c;global 的一般在 ~/.gitconfig 或者…