Java基础补充(二)—— 异常处理&反射

203 阅读3分钟

异常处理

  • 捕获异常

    • 处理多个异常的代码相同,用|合并到一起

      public static void main(String[] args) {
          try {
              process1();
              process2();
              process3();
          } catch (IOException | NumberFormatException e) { 
              System.out.println("Bad input");
          } catch (Exception e) {
              System.out.println("Unknown error");
          }
      }
      
  • 抛出异常

    • 为能追踪到完整的异常栈,在构造异常时,把原始Exception实例传进去,新的Exception就可以持有原始Exception信息

      public class Main {
          public static void main(String[] args) {
              try {
                  process1();
              } catch (Exception e) {
                  e.printStackTrace();
              }
          }
          static void process1() {
              try {
                  process2();
              } catch (NullPointerException e) {
                	// 将e传进去
                  throw new IllegalArgumentException(e);
              }
          }
          static void process2() {
              throw new NullPointerException();
          }
      }
      
      
      java.lang.IllegalArgumentException: java.lang.NullPointerException
          at Main.process1(Main.java:15)
          at Main.main(Main.java:5)
      Caused by: java.lang.NullPointerException
          at Main.process2(Main.java:20)
          at Main.process1(Main.java:13)
      

      Caused by:指向根源问题

      可以使用Throwable.getCause()获取原始异常。如果返回null说明已是根异常。

    • 异常屏蔽

      origin保存原始异常,调用Throwable.addSuppressed()把原始异常添加进来,最后在finally抛出:

      public class Main {
          public static void main(String[] args) throws Exception {
              Exception origin = null;
              try {
                  System.out.println(Integer.parseInt("abc"));
              } catch (Exception e) {
                  origin = e;
                  throw e;
              } finally {
                  Exception e = new IllegalArgumentException();
                  if (origin != null) {
                      e.addSuppressed(origin);
                  }
                  throw e;
              }
          }
      }
      
      
  • 断言assert

    断言条件x >= 0预期为true。如果计算结果为false,则断言失败,抛出AssertionError

    assert x >= 0: "x must >= 0";
    

反射(Reflection)

程序在运行期可以拿到一个对象的所有信息。

  • Class类

    一个Class实例包含了该class的所有完整信息。

    • 获取一个class的Class实例(3个方法)

      • 通过一个class的静态变量class

        Class cls = String.class;
        
      • 通过一个实例变量的getClass()获取

        String s = "Hello";
        Class cls = s.getClass();
        
      • 已知一个class的完整类名,通过静态方法Class.forName()获取

        Class cls = Class.forName("java.lang.String");
        

      因为Class实例在JVM中是唯一的,所以上述方法获取的是同一个实例,可以用==比较两个Class实例。

    • 获取父类

      public class Main {
          public static void main(String[] args) throws Exception {
              Class i = Integer.class;
              Class n = i.getSuperclass();
          }
      }
      
    • 获取interface

      public class Main {
          public static void main(String[] args) throws Exception {
              Class s = Integer.class;
              Class[] is = s.getInterfaces();
              for (Class i : is) {
                  System.out.println(i);
              }
          }
      }
      

      getInterfaces只返回当前类直接实现的接口类型,不包括父类实现的接口类型。

  • 访问字段

    • Class提供以下方法获取字段

      • Field getField(name):根据字段名获取某个public的field(包括父类)
      • Field getDeclaredField(name):根据字段名获取当前类的某个field(不包括父类)
      • Field[] getFields():获取所有public的field(包括父类)
      • Field[] getDeclaredFields():获取当前类的所有field(不包括父类)
    • Field包含一个字段所有信息:

      • getName():返回字段名称,例如,"name"
      • getType():返回字段类型,也是一个Class实例,例如,String.class
      • getModifiers():返回字段的修饰符,它是一个int,不同的bit表示不同的含义。
      Field f = String.class.getDeclaredField("value");
      f.getName(); // "value"
      f.getType(); // class [B 表示byte[]类型
      int m = f.getModifiers();
      Modifier.isFinal(m); // true
      Modifier.isPublic(m); // false
      Modifier.isProtected(m); // false
      Modifier.isPrivate(m); // true
      Modifier.isStatic(m); // false
      
    • 获取/设置字段值

      // 不管这个字段是不是public,一律允许访问
      f.setAccessible(true);
      Object value = f.get(p);
      f.set(p, "Xiao Peng");
      
  • 调用方法(类比访问字段)

    • 调用方法(invoke())

      遵循多态原则:即总是调用实际类型的覆写方法(如果存在)。

      public class Main {
          public static void main(String[] args) throws Exception {
              String s = "Hello world";
              Method m = String.class.getMethod("substring", int.class);
              String r = (String) m.invoke(s, 6);
            	
            	Method m = Integer.class.getMethod("parseInt", String.class);
              // 调用该静态方法
              Integer n = (Integer) m.invoke(null, "12345");
          }
      }
      
    • 构造方法

      • public无参数构造方法

        Person p = Person.class.newInstance();
        
      • 任意构造方法

        Class.newInstance()只能调用public无参数构造方法,其他不可。为了调用任意构造方法,Java反射API提供了Constructor对象

        public class Main {
            public static void main(String[] args) throws Exception {
              	// 获取构造方法Integer(int)
                Constructor cons1 = Integer.class.getConstructor(int.class);
                Integer n1 = (Integer) cons1.newInstance(123);
        
                // 获取构造方法Integer(String)
                Constructor cons2 = Integer.class.getConstructor(String.class);
                Integer n2 = (Integer) cons2.newInstance("456");
            }
        }
        
  • 动态代理(Dynamic Proxy)

    JDK提供的动态创建接口对象的方式: Proxy.newProxyInstance()

    1. 定义一个InvocationHandler实例,它负责实现接口的方法调用
    2. 通过Proxy.newProxyInstance()创建interface实例,它需要3个参数:
      1. 使用的ClassLoader,通常就是接口的ClassLoader
      2. 需要实现的接口数组,至少需要传入一个接口进去
      3. 用来处理接口方法调用的InvocationHandler实例
    3. 将返回的Object强制转型为接口
    public class Main {
        public static void main(String[] args) {
            InvocationHandler handler = new InvocationHandler() {
                @Override
                public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                    System.out.println(method);
                    if (method.getName().equals("morning")) {
                        System.out.println("Good morning, " + args[0]);
                    }
                    return null;
                }
            };
            Hello hello = (Hello) Proxy.newProxyInstance(
                Hello.class.getClassLoader(), // 传入ClassLoader
                new Class[] { Hello.class }, // 传入要实现的接口
                handler); // 传入处理调用方法的InvocationHandler
            hello.morning("Bob");
        }
    }
    
    interface Hello {
        void morning(String name);
    }