当前位置:首页 > 编程教程 > java技术文章 > Java中invokedynamic字节码指令问题

如何解决Java中invokedynamic字节码指令问题

  • 发布时间:
  • 作者:码农之家
  • 点击:117

这篇文章主要知识点是关于java、invokedynamic、的内容,如果大家想对相关知识点有系统深入的学习,可以参阅以下电子书

Java微服务
Java微服务高清版
  • 类型:Java大小:120 MB格式:PDF作者:沙鲁巴·夏尔马
立即下载

Java中invokedynamic字节码指令问题

1. 方法引用和invokedynamic

invokedynamic是jvm指令集里面最复杂的一条。本文将从高观点的角度下分析invokedynamic指令是如何实现方法引用(Method reference)的。

具体言之,有这样一个方法引用:

interface Encode {
  void encode(Derive person);
}
class Base {
  public void encrypt() {
    System.out.println("Base::speak");
  }
}
class Derive extends Base {
  @Override
  public void encrypt() {
    System.out.println("Derive::speak");
  }
}
public class MethodReference {
  public static void main(String[] args) {
    Encode encode = Base::encrypt;
    System.out.println(encode);
  }
}

使用javap -verbose MethodReference.class查看对应字节码:

// 常量池
Constant pool:
  #1 = Methodref     #6.#22     // java/lang/Object."<init>":()V
  #2 = InvokeDynamic   #0:#27     // #0:encode:()LEncode;
  #3 = Fieldref      #28.#29    // java/lang/System.out:Ljava/io/PrintStream;
  #4 = Methodref     #30.#31    // java/io/PrintStream.println:(Ljava/lang/Object;)V
  #5 = Class       #32      // MethodReference
  #6 = Class       #33      // java/lang/Object
  #7 = Utf8        <init>
  #8 = Utf8        ()V
  #9 = Utf8        Code
 #10 = Utf8        LineNumberTable
 #11 = Utf8        LocalVariableTable
 #12 = Utf8        this
 #13 = Utf8        LMethodReference;
 #14 = Utf8        main
 #15 = Utf8        ([Ljava/lang/String;)V
 #16 = Utf8        args
 #17 = Utf8        [Ljava/lang/String;
 #18 = Utf8        encode
 #19 = Utf8        LEncode;
 #20 = Utf8        SourceFile
 #21 = Utf8        MethodReference.java
 #22 = NameAndType    #7:#8     // "<init>":()V
 #23 = Utf8        BootstrapMethods
 #24 = MethodHandle    #6:#34     // invokestatic java/lang/invoke/LambdaMetafactory.metafactory:(Ljava/lang/invoke/MethodHandles$Lookup;L
java/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang
/invoke/CallSite;
 #25 = MethodType     #35      // (LDerive;)V
 #26 = MethodHandle    #5:#36     // invokevirtual Base.encrypt:()V
 #27 = NameAndType    #18:#37    // encode:()LEncode;
 #28 = Class       #38      // java/lang/System
 #29 = NameAndType    #39:#40    // out:Ljava/io/PrintStream;
 #30 = Class       #41      // java/io/PrintStream
 #31 = NameAndType    #42:#43    // println:(Ljava/lang/Object;)V
 #32 = Utf8        MethodReference
 #33 = Utf8        java/lang/Object
 #34 = Methodref     #44.#45    // java/lang/invoke/LambdaMetafactory.metafactory:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/Str
ing;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallS
ite;
 #35 = Utf8        (LDerive;)V
 #36 = Methodref     #46.#47    // Base.encrypt:()V
 #37 = Utf8        ()LEncode;
 #38 = Utf8        java/lang/System
 #39 = Utf8        out
 #40 = Utf8        Ljava/io/PrintStream;
 #41 = Utf8        java/io/PrintStream
 #42 = Utf8        println
 #43 = Utf8        (Ljava/lang/Object;)V
 #44 = Class       #48      // java/lang/invoke/LambdaMetafactory
 #45 = NameAndType    #49:#53    // metafactory:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Lj
ava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;
 #46 = Class       #54      // Base
 #47 = NameAndType    #55:#8     // encrypt:()V
 #48 = Utf8        java/lang/invoke/LambdaMetafactory
 #49 = Utf8        metafactory
// 字节码指令
 public static void main(java.lang.String[]);
   0: invokedynamic #2, 0       // InvokeDynamic #0:encode:()LEncode;
   5: astore_1
   6: getstatic   #3         // Field java/lang/System.out:Ljava/io/PrintStream;
   9: aload_1
  10: invokevirtual #4         // Method java/io/PrintStream.println:(Ljava/lang/Object;)V
  13: return
// 属性
SourceFile: "MethodReference.java"
InnerClasses:
   public static final #51= #50 of #56; //Lookup=class java/lang/invoke/MethodHandles$Lookup of class java/lang/invoke/MethodHandles
BootstrapMethods:
 0: #24 invokestatic java/lang/invoke/LambdaMetafactory.metafactory:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/Method
Type;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;
  Method arguments:
   #25 (LDerive;)V
   #26 invokevirtual Base.encrypt:()V
   #25 (LDerive;)V

使用invokedynamic指令生成encode对象,然后存入局部变量槽#1。接着获取getstatic获取java/lang/System类的out字段,最后局部变量槽#1作为参数压栈,invokevirtual虚函数调用System.out的println方法。

那么invokedynamic到底是怎么生成encode对象的呢?

1.虚拟机解析

hotspot对invokedynamic指令的解释如下:

CASE(_invokedynamic): {
    u4 index = Bytes::get_native_u4(pc+1);
    ConstantPoolCacheEntry* cache = cp->constant_pool()->invokedynamic_cp_cache_entry_at(index);
    // We are resolved if the resolved_references field contains a non-null object (CallSite, etc.)
    // This kind of CP cache entry does not need to match the flags byte, because
    // there is a 1-1 relation between bytecode type and CP entry type.
    if (! cache->is_resolved((Bytecodes::Code) opcode)) {
     CALL_VM(InterpreterRuntime::resolve_from_cache(THREAD, (Bytecodes::Code)opcode),
         handle_exception);
     cache = cp->constant_pool()->invokedynamic_cp_cache_entry_at(index);
    }
    Method* method = cache->f1_as_method();
    if (VerifyOops) method->verify();
    if (cache->has_appendix()) {
     ConstantPool* constants = METHOD->constants();
     SET_STACK_OBJECT(cache->appendix_if_resolved(constants), 0);
     MORE_STACK(1);
    }
    istate->set_msg(call_method);
    istate->set_callee(method);
    istate->set_callee_entry_point(method->from_interpreted_entry());
    istate->set_bcp_advance(5);
    // Invokedynamic has got a call counter, just like an invokestatic -> increment!
    BI_PROFILE_UPDATE_CALL();
    UPDATE_PC_AND_RETURN(0); // I'll be back...
   }

使用invokedynamic_cp_cache_entry_at获取常量池对象,然后检查是否已经解析过,如果没有就解析反之复用,然后设置方法字节码,留待后面解释执行。那么,重点是这个解析。我们对照着jvm spec来看。

根据jvm文档的描述,invokedynamic的操作数(operand)指向常量池一个动态调用点描述符(dynamic call site specifier)。

 动态调用点描述符是一个CONSTANT_InvokeDynamic_info结构体:

CONSTANT_InvokeDynamic_info {
 u1 tag;
 u2 bootstrap_method_attr_index;
 u2 name_and_type_index;
}

•tag 表示这个结构体的常量,不用管
•bootstrap_method_attr_index 启动方法数组
•name_and_type_index 一个名字+类型的描述字段,就像这样Object p放到虚拟机里面表示是Ljava/lang/Object; p

然后启动方法数组结构是这样:

BootstrapMethods_attribute {
 ...
 u2 num_bootstrap_methods;
 { 
 u2 bootstrap_method_ref;
 u2 num_bootstrap_arguments;
 u2 bootstrap_arguments[num_boot]
 } bootstrap_methods[num_bootstrap_methods];
}

就是一个数组,每个元素是{指向MethodHandle的索引,启动方法参数个数,启动方法参数}

MethodlHandle是个非常重要的结构,指导了虚拟机对于这个启动方法的解析,先关注一下这个结构:

CONSTANT_MethodHandle_info {
 u1 tag;//表示该结构体的常量tag,可以忽略
 u1 reference_kind;
 u2 reference_index;
}

•reference_kind是[1,9]的数,它表示这个method handle的类型,这个字段和字节码的行为有关。
•reference_index 根据reference_kind会指向常量池的不同类型,具体来说 ◦reference_kind==1,3,4 指向CONSTANT_Fieldref_info结构,表示一个类的字段
◦reference_kind==5,8,指向CONSTANT_Methodref_info,表示一个类的方法
◦reference_kind==6,7, 同上,只是兼具接口的方法或者类的方法的可能。
◦reference_kind==9,指向CONSTATN_InterfaceMethodref_info,表示一个接口方法

通过invokedynamic,我们可以得

1.名字+描述符的表示(由name_and_type_index给出)

2.一个启动方法数组(由bootstrap_method_attr_index给出)

2.手动解析

可以手动模拟一下解析,看看最后得到的数据是什么样的。在这个例子中:

  0: invokedynamic #2,  0   //第二个operand总是0

查看常量池#2项:

#2 = InvokeDynamic   #0:#27     // #0:encode:()LEncode;
#27 = NameAndType    #18:#37    // encode:()LEncode;

BootstrapMethods:
 0: #24 invokestatic java/lang/invoke/LambdaMetafactory.metafactory:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/Method
Type;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;
  Method arguments:
   #25 (LDerive;)V
   #26 invokevirtual Base.encrypt:()V
   #25 (LDerive;)V

得到的名字+描述符是:Encode.encode(),启动方法数组有一个元素,回忆下之前说的,这个元素构成如下:

{指向MethodHandle的索引,启动方法参数个数,启动方法参数}

这里得到的MethodHandle表示的是LambdaMetafactory.metafactory:

#24 invokestatic java/lang/invoke/LambdaMetafactory.metafactory:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/Method
Type;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;`

启动方法参数有:

•#25 (LDerive;)V
•#26 invokevirtual Base.encrypt:()V
•#25 (LDerive;)V

3. java.lang.invoke.LambdaMetafactory

先说说LambdaMetafactory有什么用。javadoc给出的解释是:

Facilitates the creation of simple "function objects" that implement one or more interfaces by delegation to a provided MethodHandle, after appropriate type adaptation and partial evaluation of arguments. Typically used as a bootstrap method for invokedynamic call sites, to support the lambda expression and method reference expression features of the Java Programming Language.
 When the target of the CallSite returned from this method is invoked, the resulting function objects are instances of a class which implements the interface named by the return type of invokedType, declares a method with the name given by invokedName and the signature given by samMethodType. It may also override additional methods from Object.

LambdaMetafactory方便我们创建简单的"函数对象",这些函数对象通过代理MethodHandle实现了一些接口。

 当这个函数返回的CallSite被调用的时候,会产生一个类的实例,该类还实现了一些方法,具体由参数给出

将上面得到的MethodHandle写得更可读就是调用的这个方法:

public static CallSite LambdaMetafactory.metafactory(MethodHandles.Lookup caller,
                    String invokedName,
                    MethodType invokedType,
                    MethodType samMethodType,
                    MethodHandle implMethod,
                    MethodType instantiatedMethodType);

六个参数,慢慢来。

3.1 LambdaMetafactory.metafactory()调用前

要知道参数是什么意思,可以从它的调用者来管中窥豹:

 

static CallSite makeSite(MethodHandle bootstrapMethod,
               // Callee information:
               String name, MethodType type,
               // Extra arguments for BSM, if any:
               Object info,
               // Caller information:
               Class<?> callerClass) {
    MethodHandles.Lookup caller = IMPL_LOOKUP.in(callerClass);
    CallSite site;
    try {
      Object binding;
      info = maybeReBox(info);
      if (info == null) {
        binding = bootstrapMethod.invoke(caller, name, type);
      } else if (!info.getClass().isArray()) {
        binding = bootstrapMethod.invoke(caller, name, type, info);
      } else {
        Object[] argv = (Object[]) info;
        maybeReBoxElements(argv);
        switch (argv.length) {
        ...
        case 3:
          binding = bootstrapMethod.invoke(caller, name, type,
                           argv[0], argv[1], argv[2]);
          break;
        ...
        }
      }
      //System.out.println("BSM for "+name+type+" => "+binding);
      if (binding instanceof CallSite) {
        site = (CallSite) binding;
      } else {
        throw new ClassCastException("bootstrap method failed to produce a CallSite");
      }
      ...
    } catch (Throwable ex) {
      ...
    }
    return site;
  }

对java.lang.invoke.LambdaMetafactory的调用是通过MethodHandle引发的,所以可能还需要补一下MethodHandle的用法,百度一搜一大堆,javadoc也给出了使用示例:

String s;
MethodType mt; MethodHandle mh;
MethodHandles.Lookup lookup = MethodHandles.lookup();
// mt is (char,char)String
mt = MethodType.methodType(String.class, char.class, char.class);
mh = lookup.findVirtual(String.class, "replace", mt);
s = (String) mh.invoke("daddy",'d','n');
// invokeExact(Ljava/lang/String;CC)Ljava/lang/String;
assertEquals(s, "nanny");

回到源码,关键是这句:

binding = bootstrapMethod.invoke(caller, name, type,
                argv[0], argv[1], argv[2]);

argv[0],argv[1],argv[2]分别表示之前启动方法的三个参数,

caller即调用者,这里是MethodReference这个类,然后name和type参见下面的详细解释:

•MethodHandles.Lookup caller 表示哪个类引发了调动
•String invokedName 表示生成的类的方法名,对应例子的encode
•MethodType invokedType 表示CallSite的函数签名,其中参数类型表示捕获变量的类型,返回类型是类要实现的接口的名字,对应例子的()Encode,即要生成一个类,这个类没有捕获自由变量(所以参数类为空),然后这个类要实现Encode接口(返回类型为生成的类要实现的接口)

接下来

•MethodType samMethodType 表示要实现的方法的函数签名和返回值,对于例子的#25 (LDerive;)V,即实现方法带有一个形参,返回void
•MethodHandle implMethod 表示实现的方法里面应该调用的函数,对于例子的#26 invokevirtual Base.encrypt:()V,表示调用Base的虚函数encrypt,返回void
•MethodType instantiatedMethodType 表示调用方法的运行时描述符,如果不是泛型就和samMethodType一样

3.2 LambdaMetafactory.metafactory()调用

源码面前,不是了无秘密吗hhh,点进源码看看这个LambdaMetafactory到底做了什么:

 */
  public static CallSite metafactory(MethodHandles.Lookup caller,
                    String invokedName,
                    MethodType invokedType,
                    MethodType samMethodType,
                    MethodHandle implMethod,
                    MethodType instantiatedMethodType)
      throws LambdaConversionException {
    AbstractValidatingLambdaMetafactory mf;
    mf = new InnerClassLambdaMetafactory(caller, invokedType,
                       invokedName, samMethodType,
                       implMethod, instantiatedMethodType,
                       false, EMPTY_CLASS_ARRAY, EMPTY_MT_ARRAY);
    mf.validateMetafactoryArgs();
    return mf.buildCallSite();
  }

它什么也没做,做事的是InnerClassLambdaMetafactory.buildCallSite()创建的最后CallSite,那就进一步看看InnerClassLambdaMetafactory.buildCallSite():

 @Override
  CallSite buildCallSite() throws LambdaConversionException {
    // 1. 创建生成的类对象
    final Class<?> innerClass = spinInnerClass();
    if (invokedType.parameterCount() == 0) {
      // 2. 用反射获取构造函数
      final Constructor<?>[] ctrs = AccessController.doPrivileged(
          new PrivilegedAction<Constructor<?>[]>() {
        @Override
        public Constructor<?>[] run() {
          Constructor<?>[] ctrs = innerClass.getDeclaredConstructors();
          if (ctrs.length == 1) {
            // The lambda implementing inner class constructor is private, set
            // it accessible (by us) before creating the constant sole instance
            ctrs[0].setAccessible(true);
          }
          return ctrs;
        }
          });
      if (ctrs.length != 1) {
        throw new LambdaConversionException("Expected one lambda constructor for "
            + innerClass.getCanonicalName() + ", got " + ctrs.length);
      }

      try {
        // 3. 创建实例 
        Object inst = ctrs[0].newInstance();
        // 4. 根据实例和samBase(接口类型)生成MethodHandle
        // 5. 生成ConstantCallSite
        return new ConstantCallSite(MethodHandles.constant(samBase, inst));
      }
      catch (ReflectiveOperationException e) {
        throw new LambdaConversionException("Exception instantiating lambda object", e);
      }
    } else {
      try {
        UNSAFE.ensureClassInitialized(innerClass);
        return new ConstantCallSite(
            MethodHandles.Lookup.IMPL_LOOKUP
               .findStatic(innerClass, NAME_FACTORY, invokedType));
      }
      catch (ReflectiveOperationException e) {
        throw new LambdaConversionException("Exception finding constructor", e);
      }
    }
  }

首先它生成一个.class文件,虚拟机默认不会输出,需要下面设置VM option-Djdk.internal.lambda.dumpProxyClasses=.,Dump出虚拟机生成的类我得到的是:

import java.lang.invoke.LambdaForm.Hidden;
// $FF: synthetic class
final class MethodReference$$Lambda$1 implements Encode {
  private MethodReference$$Lambda$1() {
  }
  @Hidden
  public void encode(Derive var1) {
    ((Base)var1).encrypt();
  }
}

该类实现了传来的接口函数(动态类生成,熟悉spring的朋友应该很熟悉)。

回到buildCallSite()源码,它使用MethodHandles.constant(samBase, inst)创建MethdHandle,放到CallSite里面,完成整个LambdaMetafactory的工作。

MethodHandles.constant(samBase, inst)相当于一个总是返回inst的方法。

总结

到这里就结束了整个流程,文章有点长,总结一下:

1.虚拟机遇到invokedynamic,开始解析操作数
2.根据invokedynamic #0:#27获取到启动方法(#0)和一个名字+描述符(#27)

其中启动方法是

BootstrapMethods:
 0: #24 invokestatic java/lang/invoke/LambdaMetafactory.metafactory:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/Method
Type;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;
  Method arguments:
   #25 (LDerive;)V
   #26 invokevirtual Base.encrypt:()V
   #25 (LDerive;)V

名字+描述符是

 #27 = NameAndType        #18:#37        // encode:()LEncode;

1.启动方法指向LambdaMetafactory.metafactory,但是不会直接调用而是通过MethdHandle间接调用。调用位置位于CallSite.makeCallSite()
2.LambdaMetafactory.metafactory()其实使用InnerClassLambdaMetafactory.buildCallSite()创建了最后的CallSite
3.buildCallSite()会创建一个.class,
4.buildCallSite()会向最后的CallSite里面放入一个可调用的MethdHandle
5.这个MethodHandle指向的是一个总是返回刚刚创建的.class类的实例的方法,由MethodHandles.constant(samBase, inst)完成
6.最后,用invokevirtual调用CallSite里面的MethdHandle,返回.class类的示例,即inst,即new MethodReference$$Lambda$1

总结

以上所述是小编给大家介绍的Java中invokedynamic字节码指令问题,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对码农之家网站的支持!
如果你觉得本文对你有帮助,欢迎转载,烦请注明出处,谢谢!

以上就是本次给大家分享的关于java的全部知识点内容总结,大家还可以在下方相关文章里找到相关文章进一步学习,感谢大家的阅读和支持。

Java 相关电子书
学习笔记
网友NO.297261

Java多线程饥饿与公平介绍及代码示例

如果一个线程因为CPU时间全部被其他线程抢走而得不到CPU运行时间,这种状态被称之为“饥饿”。而该线程被“饥饿致死”正是因为它得不到CPU运行时间的机会。解决饥饿的方案被称之为“公平性” – 即所有线程均能公平地获得运行机会。 下面是本文讨论的主题: Java中导致饥饿的原因 在Java中,下面三个常见的原因会导致线程饥饿: 高优先级线程吞噬所有的低优先级线程的CPU时间。 线程被永久堵塞在一个等待进入同步块的状态,因为其他线程总是能在它之前持续地对该同步块进行访问。 线程在等待一个本身(在其上调用wait())也处于永久等待完成的对象,因为其他线程总是被持续地获得唤醒。 高优先级线程吞噬所有的低优先级线程的CPU时间 你能为每个线程设置独自的线程优先级,优先级越高的线程获得的CPU时间越多,线程优先级值设置在1到10之间……

网友NO.273786

Java中遍历Map的多种方法示例及优缺点总结

前言 关于java中的map遍历有多种方法,从最早的Iterator,到java5支持的foreach,再到java8 Lambda,让我们一起来看下具体的用法以及各自的优缺点 先初始化一个map public class TestMap { public static MapInteger, Integer map = new HashMapInteger, Integer();} keySet values 如果只需要map的key或者value,用map的keySet或values方法无疑是最方便的 // KeySet 获取key public void testKeySet() { for (Integer key : map.keySet()) { System.out.println(key); } } // values 获取value public void testValues() { for (Integer value : map.values()) { System.out.println(value); } } keySet get(key) 如果需要同时获取key和value,可以先获取key,然后再通过map的 get(key) 获取value 需要说明的是,该方法不是最优选择,一般不推荐使用 // keySet get(key) 获取key and value public void testKeySetAndGetKey() { for (Integer key : map.keySet()) { System.out.println(key + ":" + map.get(key)); } } entrySet 通过对m……

网友NO.715527

java使用POI实现html和word相互转换

项目后端使用了springboot,maven,前端使用了ckeditor富文本编辑器。目前从html转换的word为doc格式,而图片处理支持的是docx格式,所以需要手动把doc另存为docx,然后才可以进行图片替换。 一.添加maven依赖 主要使用了以下和poi相关的依赖,为了便于获取html的图片元素,还使用了jsoup: dependency groupIdorg.apache.poi/groupId artifactIdpoi/artifactId version3.14/version/dependencydependency groupIdorg.apache.poi/groupId artifactIdpoi-scratchpad/artifactId version3.14/version/dependencydependency groupIdorg.apache.poi/groupId artifactIdpoi-ooxml/artifactId version3.14/version/dependencydependency groupIdfr.opensagres.xdocreport/groupId artifactIdxdocreport/artifactId version1.0.6/version/dependencydependency groupIdorg.apache.poi/groupId artifactIdpoi-ooxml-schemas/artifactId version3.14/version/dependencydependency groupIdorg.apache.poi/groupId artifactIdooxml-schemas/artifactId version1.3/version/depe……

网友NO.777281

Java语言实现反转链表代码示例

问题描述 定义一个函数,输入一个链表的头结点,反转该链表并输出反转后的链表的头结点。链表结点如下: public class ListNode { int val; ListNode next = null; ListNode(int val) { this.val = val; }} 思路1: 要想反转链表,对于结点i,我们要把它的next指向它的前趋,因此我们需要保存前趋结点,同时,如果我们已经把i的next重新赋值,会无法找到i的后继,因此,在重新赋值之前,我们要保存i的后继。 代码: public ListNode ReverseList(ListNode head) { if(head == null){ return null; } ListNode rHead = null; ListNode prior = null;//store prior ListNode q = head;//store current while(q != null){ ListNode next = q.next;//store the next if(next == null){ rHead = q; } q.next = prior; prior = q; q = next; } return rHead; } 思路2: 使用递归的思想(暂时没有想到,因为如果用递归的话,每次应该是:链表的第一个结点—递归返回的链表的尾指针……

网友NO.546383

Java selenium截图操作的实现

方法一: Selenium中截图类TakeScreenshout,这个类主要是获取浏览器窗体内的内容,不包括浏览器的菜单和桌面的任务栏区域,我们用百度首页来截图,看看截图效果。 FileUtils.copyFile(srcFile, new File("屏幕截图", time + ".png"));“屏幕截图”是我们自己创建的文件夹用来存放截图文件,此文件夹在project(工程)的更目录 ; 当然也是可以设置保存到其他目录下: FileUtils.copyFile(srcFile, new File("D:\\资料图片", time + ".png")); 示例代码如下: package com.sandy; import java.io.File;import java.text.SimpleDateFormat;import java.util.Calendar; import org.apache.commons.io.FileUtils;import org.openqa.selenium.OutputType;import org.openqa.selenium.TakesScreenshot;import org.openqa.selenium.WebDriver;import org.openqa.selenium.chrome.ChromeDriver; public class ScreenShot { private static WebDriver driver;public static void main(String[] args) throws Exception { System.setProper……

<
1
>

Copyright 2018-2020 www.xz577.com 码农之家

版权投诉 / 书籍推广 / 赞助:520161757@qq.com