Java 中文官方教程 2022 版(四十四)(2)

简介: Java 中文官方教程 2022 版(四十四)

Java 中文官方教程 2022 版(四十四)(1)https://developer.aliyun.com/article/1488313

检索和解析构造函数修饰符

原文:docs.oracle.com/javase/tutorial/reflect/member/ctorModifiers.html

由于构造函数在语言中的作用,比方法更少的修饰符是有意义的:

  • 访问修饰符:publicprotectedprivate
  • 注解

ConstructorAccess示例在给定类中搜索具有指定访问修饰符的构造函数。它还显示构造函数是否是合成的(由编译器生成)或具有可变参数。

import java.lang.reflect.Constructor;
import java.lang.reflect.Modifier;
import java.lang.reflect.Type;
import static java.lang.System.out;
public class ConstructorAccess {
    public static void main(String... args) {
  try {
      Class<?> c = Class.forName(args[0]);
      Constructor[] allConstructors = c.getDeclaredConstructors();
      for (Constructor ctor : allConstructors) {
    int searchMod = modifierFromString(args[1]);
    int mods = accessModifiers(ctor.getModifiers());
    if (searchMod == mods) {
        out.format("%s%n", ctor.toGenericString());
        out.format("  [ synthetic=%-5b var_args=%-5b ]%n",
             ctor.isSynthetic(), ctor.isVarArgs());
    }
      }
        // production code should handle this exception more gracefully
  } catch (ClassNotFoundException x) {
      x.printStackTrace();
  }
    }
    private static int accessModifiers(int m) {
  return m & (Modifier.PUBLIC | Modifier.PRIVATE | Modifier.PROTECTED);
    }
    private static int modifierFromString(String s) {
  if ("public".equals(s))               return Modifier.PUBLIC;
  else if ("protected".equals(s))       return Modifier.PROTECTED;
  else if ("private".equals(s))         return Modifier.PRIVATE;
  else if ("package-private".equals(s)) return 0;
  else return -1;
    }
}

没有明确对应于“包私有”访问权限的Modifier常量,因此需要检查所有三个访问修饰符的缺失来识别包私有构造函数。

此输出显示了java.io.File中的私有构造函数:

$ *java ConstructorAccess java.io.File private*
private java.io.File(java.lang.String,int)
  [ synthetic=false var_args=false ]
private java.io.File(java.lang.String,java.io.File)
  [ synthetic=false var_args=false ]

合成构造函数很少见;但是SyntheticConstructor示例说明了可能发生这种情况的典型情况:

public class SyntheticConstructor {
    private SyntheticConstructor() {}
    class Inner {
  // Compiler will generate a synthetic constructor since
  // SyntheticConstructor() is private.
  Inner() { new SyntheticConstructor(); }
    }
}
$ *java ConstructorAccess SyntheticConstructor package-private*
SyntheticConstructor(SyntheticConstructor$1)
  [ synthetic=true  var_args=false ]

由于内部类的构造函数引用了封闭类的私有构造函数,编译器必须生成一个包私有构造函数。参数类型SyntheticConstructor$1是任意的,取决于编译器的实现。依赖于任何合成或非公共类成员存在的代码可能不具有可移植性。

构造函数实现了java.lang.reflect.AnnotatedElement,提供了用于检索运行时注解的方法,使用java.lang.annotation.RetentionPolicy.RUNTIME。有关获取注解的示例,请参见检查类修饰符和类型部分。

创建新的类实例

原文:docs.oracle.com/javase/tutorial/reflect/member/ctorInstance.html

创建类实例的两种反射方法:java.lang.reflect.Constructor.newInstance()Class.newInstance()。前者更受青睐,因此在这些示例中使用,原因如下:

有时可能希望从仅在构造后设置的对象中检索内部状态。考虑一个场景,需要获取java.io.Console使用的内部字符集。(Console字符集存储在私有字段中,并且不一定与java.nio.charset.Charset.defaultCharset()返回的 Java 虚拟机默认字符集相同)。ConsoleCharset示例展示了如何实现这一点:

import java.io.Console;
import java.nio.charset.Charset;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import static java.lang.System.out;
public class ConsoleCharset {
    public static void main(String... args) {
  Constructor[] ctors = Console.class.getDeclaredConstructors();
  Constructor ctor = null;
  for (int i = 0; i < ctors.length; i++) {
      ctor = ctors[i];
      if (ctor.getGenericParameterTypes().length == 0)
    break;
  }
  try {
      ctor.setAccessible(true);
      Console c = (Console)ctor.newInstance();
      Field f = c.getClass().getDeclaredField("cs");
      f.setAccessible(true);
      out.format("Console charset         :  %s%n", f.get(c));
      out.format("Charset.defaultCharset():  %s%n",
           Charset.defaultCharset());
        // production code should handle these exceptions more gracefully
  } catch (InstantiationException x) {
      x.printStackTrace();
  } catch (InvocationTargetException x) {
      x.printStackTrace();
  } catch (IllegalAccessException x) {
      x.printStackTrace();
  } catch (NoSuchFieldException x) {
      x.printStackTrace();
  }
    }
}

注意:

如果构造函数没有参数且已经可访问,则Class.newInstance()才会成功。否则,需要像上面的示例一样使用Constructor.newInstance()


UNIX 系统的示例输出:

$ *java ConsoleCharset*
Console charset          :  ISO-8859-1
Charset.defaultCharset() :  ISO-8859-1

Windows 系统的示例输出:

C:\> *java ConsoleCharset*
Console charset          :  IBM437
Charset.defaultCharset() :  windows-1252

另一个常见的 Constructor.newInstance() 应用是调用需要参数的构造函数。RestoreAliases 示例找到一个特定的单参数构造函数并调用它:

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import static java.lang.System.out;
class EmailAliases {
    private Set<String> aliases;
    private EmailAliases(HashMap<String, String> h) {
  aliases = h.keySet();
    }
    public void printKeys() {
  out.format("Mail keys:%n");
  for (String k : aliases)
      out.format("  %s%n", k);
    }
}
public class RestoreAliases {
    private static Map<String, String> defaultAliases = new HashMap<String, String>();
    static {
  defaultAliases.put("Duke", "duke@i-love-java");
  defaultAliases.put("Fang", "fang@evil-jealous-twin");
    }
    public static void main(String... args) {
  try {
      Constructor ctor = EmailAliases.class.getDeclaredConstructor(HashMap.class);
      ctor.setAccessible(true);
      EmailAliases email = (EmailAliases)ctor.newInstance(defaultAliases);
      email.printKeys();
        // production code should handle these exceptions more gracefully
  } catch (InstantiationException x) {
      x.printStackTrace();
  } catch (IllegalAccessException x) {
      x.printStackTrace();
  } catch (InvocationTargetException x) {
      x.printStackTrace();
  } catch (NoSuchMethodException x) {
      x.printStackTrace();
  }
    }
}

这个示例使用 Class.getDeclaredConstructor() 来找到一个参数类型为 java.util.HashMap 的构造函数。请注意,只需传递 HashMap.class 就足够了,因为任何 get*Constructor() 方法的参数只需要类来确定类型。由于 类型擦除,以下表达式求值为 true

HashMap.class == defaultAliases.getClass()

然后,示例使用这个构造函数使用 Constructor.newInstance() 创建类的新实例。

$ *java RestoreAliases*
Mail keys:
  Duke
  Fang

故障排除

原文:docs.oracle.com/javase/tutorial/reflect/member/ctorTrouble.html

开发人员在尝试通过反射调用构造函数时,有时会遇到以下问题。

由于缺少零参数构造函数而导致的 InstantiationException

ConstructorTrouble 示例说明了当代码尝试使用Class.newInstance()创建类的新实例时,且没有可访问的零参数构造函数时会发生什么:

public class ConstructorTrouble {
    private ConstructorTrouble(int i) {}
    public static void main(String... args){
  try {
      Class<?> c = Class.forName("ConstructorTrouble");
      Object o = c.newInstance();  // InstantiationException
        // production code should handle these exceptions more gracefully
  } catch (ClassNotFoundException x) {
      x.printStackTrace();
  } catch (InstantiationException x) {
      x.printStackTrace();
  } catch (IllegalAccessException x) {
      x.printStackTrace();
  }
    }
}
$ *java ConstructorTrouble*
java.lang.InstantiationException: ConstructorTrouble
        at java.lang.Class.newInstance0(Class.java:340)
        at java.lang.Class.newInstance(Class.java:308)
        at ConstructorTrouble.main(ConstructorTrouble.java:7)

提示:InstantiationException可能发生的原因有很多。在这种情况下,问题在于具有int参数的构造函数的存在阻止了编译器生成默认(或零参数)构造函数,并且代码中没有显式的零参数构造函数。请记住,Class.newInstance()的行为非常类似于new关键字,只要new失败,它就会失败。


Class.newInstance() 抛出意外异常

ConstructorTroubleToo 示例展示了在Class.newInstance()中出现的无法解决的问题。即,它传播构造函数抛出的任何异常(已检查或未检查)。

import java.lang.reflect.InvocationTargetException;
import static java.lang.System.err;
public class ConstructorTroubleToo {
    public ConstructorTroubleToo() {
  throw new RuntimeException("exception in constructor");
    }
    public static void main(String... args) {
  try {
      Class<?> c = Class.forName("ConstructorTroubleToo");
      // Method propagetes any exception thrown by the constructor
      // (including checked exceptions).
      if (args.length > 0 && args[0].equals("class")) {
    Object o = c.newInstance();
      } else {
    Object o = c.getConstructor().newInstance();
      }
        // production code should handle these exceptions more gracefully
  } catch (ClassNotFoundException x) {
      x.printStackTrace();
  } catch (InstantiationException x) {
      x.printStackTrace();
  } catch (IllegalAccessException x) {
      x.printStackTrace();
  } catch (NoSuchMethodException x) {
      x.printStackTrace();
  } catch (InvocationTargetException x) {
      x.printStackTrace();
      err.format("%n%nCaught exception: %s%n", x.getCause());
  }
    }
}
$ *java ConstructorTroubleToo class*
Exception in thread "main" java.lang.RuntimeException: exception in constructor
        at ConstructorTroubleToo.<init>(ConstructorTroubleToo.java:6)
        at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
        at sun.reflect.NativeConstructorAccessorImpl.newInstance
          (NativeConstructorAccessorImpl.java:39)
        at sun.reflect.DelegatingConstructorAccessorImpl.newInstance
          (DelegatingConstructorAccessorImpl.java:27)
        at java.lang.reflect.Constructor.newInstance(Constructor.java:513)
        at java.lang.Class.newInstance0(Class.java:355)
        at java.lang.Class.newInstance(Class.java:308)
        at ConstructorTroubleToo.main(ConstructorTroubleToo.java:15)

这种情况是反射独有的。通常情况下,不可能编写忽略已检查异常的代码,因为这样的代码不会编译。可以通过使用Constructor.newInstance()而不是Class.newInstance()来包装构造函数抛出的任何异常。

$ *java ConstructorTroubleToo*
java.lang.reflect.InvocationTargetException
        at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
        at sun.reflect.NativeConstructorAccessorImpl.newInstance
          (NativeConstructorAccessorImpl.java:39)
        at sun.reflect.DelegatingConstructorAccessorImpl.newInstance
          (DelegatingConstructorAccessorImpl.java:27)
        at java.lang.reflect.Constructor.newInstance(Constructor.java:513)
        at ConstructorTroubleToo.main(ConstructorTroubleToo.java:17)
Caused by: java.lang.RuntimeException: exception in constructor
        at ConstructorTroubleToo.<init>(ConstructorTroubleToo.java:6)
        ... 5 more
Caught exception: java.lang.RuntimeException: exception in constructor

如果抛出InvocationTargetException,则表示方法已被调用。对问题的诊断与直接调用构造函数并抛出异常,然后通过InvocationTargetException.getCause()检索到的异常相同。此异常并不表示反射包或其使用存在问题。


**提示:**最好使用Constructor.newInstance()而不是Class.newInstance(),因为前者的 API 允许检查和处理构造函数抛出的任意异常。


定位或调用正确构造函数的问题

ConstructorTroubleAgain类展示了代码错误可能无法定位或调用预期构造函数的各种方式。

import java.lang.reflect.InvocationTargetException;
import static java.lang.System.out;
public class ConstructorTroubleAgain {
    public ConstructorTroubleAgain() {}
    public ConstructorTroubleAgain(Integer i) {}
    public ConstructorTroubleAgain(Object o) {
  out.format("Constructor passed Object%n");
    }
    public ConstructorTroubleAgain(String s) {
  out.format("Constructor passed String%n");
    }
    public static void main(String... args){
  String argType = (args.length == 0 ? "" : args[0]);
  try {
      Class<?> c = Class.forName("ConstructorTroubleAgain");
      if ("".equals(argType)) {
    // IllegalArgumentException: wrong number of arguments
    Object o = c.getConstructor().newInstance("foo");
      } else if ("int".equals(argType)) {
    // NoSuchMethodException - looking for int, have Integer
    Object o = c.getConstructor(int.class);
      } else if ("Object".equals(argType)) {
    // newInstance() does not perform method resolution
    Object o = c.getConstructor(Object.class).newInstance("foo");
      } else {
    assert false;
      }
        // production code should handle these exceptions more gracefully
  } catch (ClassNotFoundException x) {
      x.printStackTrace();
  } catch (NoSuchMethodException x) {
      x.printStackTrace();
  } catch (InvocationTargetException x) {
      x.printStackTrace();
  } catch (InstantiationException x) {
      x.printStackTrace();
  } catch (IllegalAccessException x) {
      x.printStackTrace();
  }
    }
}
$ *java ConstructorTroubleAgain*
Exception in thread "main" java.lang.IllegalArgumentException: wrong number of
  arguments
        at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
        at sun.reflect.NativeConstructorAccessorImpl.newInstance
          (NativeConstructorAccessorImpl.java:39)
        at sun.reflect.DelegatingConstructorAccessorImpl.newInstance
          (DelegatingConstructorAccessorImpl.java:27)
        at java.lang.reflect.Constructor.newInstance(Constructor.java:513)
        at ConstructorTroubleAgain.main(ConstructorTroubleAgain.java:23)

抛出IllegalArgumentException是因为请求零参数构造函数并尝试传递参数。如果构造函数传递了错误类型的参数,也会抛出相同的异常。

$ *java ConstructorTroubleAgain int*
java.lang.NoSuchMethodException: ConstructorTroubleAgain.<init>(int)
        at java.lang.Class.getConstructor0(Class.java:2706)
        at java.lang.Class.getConstructor(Class.java:1657)
        at ConstructorTroubleAgain.main(ConstructorTroubleAgain.java:26)

如果开发人员错误地认为反射会自动装箱或拆箱类型,则可能会出现此异常。装箱(将原始类型转换为引用类型)仅在编译期间发生。在反射中没有机会进行此操作,因此在定位构造函数时必须使用特定类型。

$ *java ConstructorTroubleAgain Object*
Constructor passed Object

在这里,可能期望调用接受String参数的构造函数,因为使用了更具体的String类型调用了newInstance()。然而,为时已晚!找到的构造函数已经是接受Object参数的构造函数。newInstance()不会尝试进行方法解析;它只是在现有构造函数对象上操作。


提示: newConstructor.newInstance()之间的一个重要区别是,new执行方法参数类型检查、装箱和方法解析。在反射中,这些都不会发生,必须做出明确选择。


尝试调用不可访问构造函数时出现 IllegalAccessException

如果尝试调用私有或其他不可访问的构造函数,则可能会抛出IllegalAccessExceptionConstructorTroubleAccess示例展示了产生的堆栈跟踪。

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
class Deny {
    private Deny() {
  System.out.format("Deny constructor%n");
    }
}
public class ConstructorTroubleAccess {
    public static void main(String... args) {
  try {
      Constructor c = Deny.class.getDeclaredConstructor();
//        c.setAccessible(true);   // solution
      c.newInstance();
        // production code should handle these exceptions more gracefully
  } catch (InvocationTargetException x) {
      x.printStackTrace();
  } catch (NoSuchMethodException x) {
      x.printStackTrace();
  } catch (InstantiationException x) {
      x.printStackTrace();
  } catch (IllegalAccessException x) {
      x.printStackTrace();
  }
    }
}
$ *java ConstructorTroubleAccess*
java.lang.IllegalAccessException: Class ConstructorTroubleAccess can not access
  a member of class Deny with modifiers "private"
        at sun.reflect.Reflection.ensureMemberAccess(Reflection.java:65)
        at java.lang.reflect.Constructor.newInstance(Constructor.java:505)
        at ConstructorTroubleAccess.main(ConstructorTroubleAccess.java:15)

提示: 存在访问限制,阻止通过直接调用无法访问的构造函数进行反射调用。(这包括但不限于在单独类中的私有构造函数和在单独私有类中的公共构造函数。)但是,Constructor被声明为扩展AccessibleObject,它提供了通过AccessibleObject.setAccessible()来抑制此检查的能力。

Java 中文官方教程 2022 版(四十四)(3)https://developer.aliyun.com/article/1488321

相关文章
|
4天前
|
Web App开发 JavaScript 前端开发
《手把手教你》系列技巧篇(三十九)-java+ selenium自动化测试-JavaScript的调用执行-上篇(详解教程)
【5月更文挑战第3天】本文介绍了如何在Web自动化测试中使用JavaScript执行器(JavascriptExecutor)来完成Selenium API无法处理的任务。首先,需要将WebDriver转换为JavascriptExecutor对象,然后通过executeScript方法执行JavaScript代码。示例用法包括设置JS代码字符串并调用executeScript。文章提供了两个实战场景:一是当时间插件限制输入时,用JS去除元素的readonly属性;二是处理需滚动才能显示的元素,利用JS滚动页面。还给出了一个滚动到底部的代码示例,并提供了详细步骤和解释。
32 10
|
4天前
|
Java 测试技术 Python
《手把手教你》系列技巧篇(三十六)-java+ selenium自动化测试-单选和多选按钮操作-番外篇(详解教程)
【4月更文挑战第28天】本文简要介绍了自动化测试的实战应用,通过一个在线问卷调查(&lt;https://www.sojump.com/m/2792226.aspx/&gt;)为例,展示了如何遍历并点击问卷中的选项。测试思路包括找到单选和多选按钮的共性以定位元素,然后使用for循环进行点击操作。代码设计方面,提供了Java+Selenium的示例代码,通过WebDriver实现自动答题。运行代码后,可以看到控制台输出和浏览器的相应动作。文章最后做了简单的小结,强调了本次实践是对之前单选多选操作的巩固。
25 0
|
1天前
|
存储 JavaScript Java
《手把手教你》系列技巧篇(四十七)-java+ selenium自动化测试-判断元素是否显示(详解教程)
【5月更文挑战第11天】WebDriver 的 `isDisplayed()` 方法用于检查页面元素是否可见,如果元素存在于DOM中且可视,返回`true`,否则返回`false`。在自动化测试中,这个方法常用于验证元素是否真正显示在页面上。示例代码展示了如何使用 `isDisplayed()` 判断百度登录页面的特定错误提示文字是否出现。
10 1
|
2天前
|
JavaScript Java 测试技术
《手把手教你》系列技巧篇(四十六)-java+ selenium自动化测试-web页面定位toast-下篇(详解教程)
【5月更文挑战第10天】本文介绍了使用Java和Selenium进行Web自动化测试的实践,以安居客网站为例。最后,提到了在浏览器开发者工具中调试和观察页面元素的方法。
12 2
|
2天前
|
算法 Java Python
保姆级Java入门练习教程,附代码讲解,小白零基础入门必备
保姆级Java入门练习教程,附代码讲解,小白零基础入门必备
|
3天前
|
Web App开发 JavaScript 测试技术
《手把手教你》系列技巧篇(四十五)-java+ selenium自动化测试-web页面定位toast-上篇(详解教程)
【5月更文挑战第9天】本文介绍了在Appium中处理App自动化测试中遇到的Toast元素定位的方法。Toast在Web UI测试中也常见,通常作为轻量级反馈短暂显示。文章提供了两种定位Toast元素的技巧.
10 0
|
4天前
|
Web App开发 缓存 前端开发
《手把手教你》系列技巧篇(四十四)-java+ selenium自动化测试-处理https 安全问题或者非信任站点-下篇(详解教程)
【5月更文挑战第8天】这篇文档介绍了如何在IE、Chrome和Firefox浏览器中处理不信任证书的问题。作者北京-宏哥分享了如何通过编程方式跳过浏览器的证书警告,直接访问不受信任的HTTPS网站。文章分为几个部分,首先简要介绍了问题背景,然后详细讲解了在Chrome浏览器中的两种方法,包括代码设计和运行效果,并给出了其他浏览器的相关信息和参考资料。最后,作者总结了处理此类问题的一些通用技巧。
16 2
|
4天前
|
Java Android开发
【Java开发指南 | 第十八篇】Eclipse安装教程
【Java开发指南 | 第十八篇】Eclipse安装教程
12 2
|
4天前
|
Web App开发 JavaScript 前端开发
《手把手教你》系列技巧篇(四十三)-java+ selenium自动化测试-处理https 安全问题或者非信任站点-上篇(详解教程)
【5月更文挑战第7天】本文介绍了如何在Java+Selenium自动化测试中处理浏览器对不信任证书的处理方法,特别是针对IE、Chrome和Firefox浏览器。在某些情况下,访问HTTPS网站时会遇到证书不可信的警告,但可以通过编程方式跳过这些警告。
13 1
|
4天前
|
前端开发 Java 测试技术
《手把手教你》系列技巧篇(四十二)-java+ selenium自动化测试 - 处理iframe -下篇(详解教程)
【5月更文挑战第6天】本文介绍了如何使用Selenium处理含有iframe的网页。作者首先解释了iframe是什么,即HTML中的一个框架,用于在一个页面中嵌入另一个页面。接着,通过一个实战例子展示了在QQ邮箱登录页面中,由于输入框存在于iframe内,导致直接定位元素失败。作者提供了三种方法来处理这种情况:1)通过id或name属性切换到iframe;2)使用webElement对象切换;3)通过索引切换。最后,给出了相应的Java代码示例,并提醒读者根据iframe的实际情况选择合适的方法进行切换和元素定位。
12 0