java基础(3) 异常处理 -反射

异常处理

处理异常一般有两种

  • 约定返回错误码。,比如读写一个文件,返回code为0则成功,返回1表示不存在,返回2表示没权限…
  • 在语言层面上提供一个异常处理机制。(java就属于第二种)
java的异常

java内置了一套异常处理机制,总是使用异常来表示错误。
异常本质也是一种class,因此它本身带有类型信息。异常可以在任何地方抛出,但只需要在上层捕获,这样就和方法调用分离了
用try catch俘获异常

try {String s = processFile(C:\\test.txt”);// ok:
} catch (FileNotFoundException e) {// file not found:
} catch (SecurityException e) {// no read permission:
} catch (IOException e) {// io error:
} catch (Exception e) {// other error:
}

在这里插入图片描述
从上图可以看到,Throwable是异常错误的根,下面分为两个体系,ErrorException

Error

表示严重的问题,程序对此一般无能为力,比如

  • OutOfMemoryError:内存耗尽
  • NoClassDefFoundError:无法加载某个Class
  • StackOverflowError:栈溢出
Exception

Exception一般是运行时的错误,可以被俘获处理。如
某些异常是应用程序逻辑处理的一部分,应该捕获并处理。例如:

  • NumberFormatException:数值类型的格式错误
  • FileNotFoundException:未找到文件
  • SocketException:读取网络失败

还有一些异常是程序逻辑编写不对造成的,应该修复程序本身。例如:

  • NullPointerException:对某个null的对象调用方法或字段
  • IndexOutOfBoundsException:数组索引越界
Exception又分为两大类:

RuntimeException以及它的子类;
非RuntimeException(包括IOException、ReflectiveOperationException等等)

Java规定:

  • 必须捕获的异常,包括Exception及其子类,但不包括RuntimeException及其子类,这种类型的异常称为Checked Exception。
  • 不需要捕获的异常,包括Error及其子类,RuntimeException及其子类。
    注意:编译器对RuntimeException及其子类不做强制捕获要求,不是指应用程序本身不应该捕获并处理RuntimeException。是否需要捕获,具体问题具体分析。

俘获异常

public class Main {public static void main(String[] args) {byte[] bs = toGBK("中文");System.out.println(Arrays.toString(bs));}static byte[] toGBK(String s) {try {// 用指定编码转换String为byte[]:return s.getBytes("GBK");} catch (UnsupportedEncodingException e) {// 如果系统不支持GBK编码,会捕获到UnsupportedEncodingException:System.out.println(e); // 打印异常信息return s.getBytes(); // 尝试使用用默认编码}}
}

如果不俘获,编译器会报错
在这里插入图片描述
这是因为,getBytes方法的定义是

public byte[] getBytes(String charsetName) throws UnsupportedEncodingException {...
}

throw UnsupportedEncodingException表示,该方法可能会抛出什么错误,调用方在调用的时候,必须强制捕获这些异常,否则编译器会报错。
如果实在不想俘获,可以在外层再套一个throw xx

 static byte[] toGBK(String s) throws UnsupportedEncodingException {return s.getBytes("GBK");}

但是main方法调用toGbk的时候就需要try catch了,否则还是会报错。

所有异常都可以调用e.printStackTrace()方法打印异常栈,这是一个简单有用的快速打印异常的方法。

  • Java使用异常来表示错误,并通过try … catch捕获异常;
  • Java的异常是class,并且从Throwable继承;
  • Error是无需捕获的严重错误,Exception是应该捕获的可处理的错误;
  • RuntimeException无需强制捕获,非RuntimeException(Checked Exception)需强制捕获,或者用throws声明;
  • 不推荐捕获了异常但不进行任何处理。
俘获异常

多catch语句
可以使用多个catch语句,每个catch分别捕获对应的Exception及其子类。JVM在捕获到异常后,会从上到下匹配catch语句,匹配到某个catch后,执行catch代码块,然后不再继续匹配。

存在多个catch的时候,catch的顺序非常重要:子类必须写在前面。例如:

try {process1();process2();process3();} catch (IOException e) {System.out.println("IO error");} catch (UnsupportedEncodingException e) { // 永远捕获不到System.out.println("Bad encoding");}

UnsupportedEncodingException属于IOException的字类,所以永远会被第一个catch俘获。
finally,最后执行,与js类似。
JVM会先执行finally,然后抛出异常。
异常合并

try {process1();process2();process3();} catch (IOException | NumberFormatException e) { // IOException或NumberFormatExceptionSystem.out.println("Bad input");} catch (Exception e) {System.out.println("Unknown error");}

如果处理异常的结果一样,可以用|放到一起俘获

抛出异常

当某个方法抛出了异常时,如果当前方法没有捕获异常,异常就会被抛到上层调用方法,直到遇到某个try … catch被捕获为止:
通过printStackTrace()可以打印出方法的调用栈,类似:

java.lang.NumberFormatException: nullat java.base/java.lang.Integer.parseInt(Integer.java:614)at java.base/java.lang.Integer.parseInt(Integer.java:770)at Main.process2(Main.java:16)at Main.process1(Main.java:12)at Main.main(Main.java:5)

抛出异常
创建某个Exception的实例;
用throw语句抛出。

void process2(String s) {if (s==null) {throw new NullPointerException();}
}

转换异常

    public static void main(String[] args) {try {Integer.parseInt("abc");} catch (Exception e) {System.out.println("catched");throw new RuntimeException(e);} finally {System.out.println("finally");}}
}

需要把原来的异常当作参数传入,这样控制台打印不会丢失原有的异常。

自定义异常

Java标准库定义的常用异常包括:

Exception
│
├─ RuntimeException
│  │
│  ├─ NullPointerException
│  │
│  ├─ IndexOutOfBoundsException
│  │
│  ├─ SecurityException
│  │
│  └─ IllegalArgumentException
│     │
│     └─ NumberFormatException
│
├─ IOException
│  │
│  ├─ UnsupportedCharsetException
│  │
│  ├─ FileNotFoundException
│  │
│  └─ SocketException
│
├─ ParseException
│
├─ GeneralSecurityException
│
├─ SQLException
│
└─ TimeoutException

在一个大型项目中,可以自定义新的异常类型,但是,保持一个合理的异常继承体系是非常重要的。

一个常见的做法是自定义一个BaseException作为“根异常”,然后,派生出各种业务类型的异常。

BaseException需要从一个适合的Exception派生,通常建议从RuntimeException派生:

public class BaseException extends RuntimeException {
}

其他业务类型的异常就可以从BaseException派生:

public class UserNotFoundException extends BaseException {
}public class LoginFailedException extends BaseException {
}

自定义的BaseException应该提供多个构造方法:

public class BaseException extends RuntimeException {public BaseException() {super();}public BaseException(String message, Throwable cause) {super(message, cause);}public BaseException(String message) {super(message);}public BaseException(Throwable cause) {super(cause);}

这样,抛出异常的时候,就可以选择合适的构造方法。

NullPointerException

NullPointerException即空指针异常,俗称NPE。如果一个对象为null,调用其方法或访问其字段就会产生NullPointerException,这个异常通常是由JVM抛出的,类似于js的 reading of undefined,从undefined访问字段。

使用断言

断言(Assertion)是一种调试程序的方式。在Java中,使用assert关键字来实现断言。

public static void main(String[] args) {double x = Math.abs(-123.45);assert x >= 0 : "x must >= 0";System.out.println(x);
}

语句assert x >= 0;即为断言,断言条件x >= 0预期为true。如果计算结果为false,则断言失败,抛出AssertionError。断言失败的时候,AssertionError会带上消息x must >= 0

Java断言的特点是:断言失败时会抛出AssertionError,导致程序结束退出。因此,断言不能用于可恢复的程序错误,只应该用于开发和测试阶段。

使用JDK Logging

Java标准库内置了日志包java.util.logging

import java.util.logging.Level;
import java.util.logging.Logger;
public class Hello {public static void main(String[] args) {Logger logger = Logger.getGlobal();logger.info("start process...");logger.warning("memory is running out...");logger.fine("ignored.");logger.severe("process will be terminated...");}
}

打印结果

Mar 02, 2019 6:32:13 PM Hello main
INFO: start process...
Mar 02, 2019 6:32:13 PM Hello main
WARNING: memory is running out...
Mar 02, 2019 6:32:13 PM Hello main
SEVERE: process will be terminated...

不仅打印出内容,还打印出执行栈,调用类,调用方法。
日志等级
SEVERE WARNING INFO CONFIG FINE FINER FINEST
默认级别是INFO,因此,INFO级别以下的日志,不会被打印出来。使用日志级别的好处在于,调整级别,就可以屏蔽掉很多调试相关的日志输出。

使用Commons Logging

和Java标准库提供的日志不同,Commons Logging是一个第三方日志库,它是由Apache创建的日志模块。

Commons Logging的特色是,它可以挂接不同的日志系统,并通过配置文件指定挂接的日志系统。默认情况下,Commons Loggin自动搜索并使用Log4j(Log4j是另一个流行的日志系统),如果没有找到Log4j,再使用JDK Logging。

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
public class Main {public static void main(String[] args) {Log log = LogFactory.getLog(Main.class);log.info("start...");log.warn("end.");}
}
使用Log4j
使用SLF4J和Logback

SLF4J类似于Commons Logging,也是一个日志接口,而Logback类似于Log4j,是一个日志的实现。

commons logging

int score = 99;
p.setScore(score);
log.info("Set score " + score + " for Person " + p.getName() + " ok.");

SLF4j

int score = 99;
p.setScore(score);
logger.info("Set score {} for Person {} ok.", score, p.getName());
  • SLF4J和Logback可以取代Commons Logging和Log4j;
  • 始终使用SLF4J的接口写入日志,使用Logback只需要配置,不需要修改代码。

反射

什么是反射?
反射就是Reflection,Java的反射是指程序在运行期可以拿到一个对象的所有信息

正常情况下,如果我们要调用一个对象的方法,或者访问一个对象的字段,通常会传入对象实例:

// Main.java
import com.itranswarp.learnjava.Person;public class Main {String getFullName(Person p) {return p.getFirstName() + " " + p.getLastName();}
}

只能传入特定的Person类才能知道他的信息。
反射是为了解决在运行期,对某个实例一无所知的情况下,如何调用其方法。

Class

除了基础类型 int long short byte char boolean float double 外,其他的引用类型基本都是类class。比如String

得出结论:class(包括interface)的本质是数据类型(Type)无继承关系的数据类型无法赋值:

Number n = new Double(123.456); // OK
String s = new Double(123.456); // compile error!

class是由JVM在执行过程中动态加载的。JVM在第一次读取到一种class类型时,将其加载进内存。
java在加载class的时候,会创建对应的Class实例,比如String s = new String();这个s就是String的实例变量,通过s.getClass就可以获取到String这个Class实例,再通过String这个Class实例,可以获取到String这个类的信息。.

public final class Class {private Class() {}
}

以String类为例,当JVM加载String类时,它首先读取String.class文件到内存,然后,为String类创建一个Class实例并关联起来:

Class cls = new Class(String);

如上,String就会创建String的Class实例。这个new Class只能JVM创建,他的构造函数是private,
所以,java每持有的一个Class的实例,都指向一种数据类型,比如String Random
在这里插入图片描述
此外,每个Class实例,都保存该class的所有完整信息。比如name field method,类名,父类等,如果获取了某个Class实例,我们就可以通过Class实例获取到该实例对应的class的所有信息,比如String的Class实例,可以获取String上对应的方法,比如indexOf…

这种通过Class实例获取class信息的方法称为反射(Reflection)。
获取Class实例的三种方法
方法一:直接通过一个class的静态变量class获取:

Class cls = String.class;

方法二:如果我们有一个实例变量,可以通过该实例变量提供的getClass()方法获取:

String s = "Hello";
Class cls = s.getClass(); //通过String的实例s,再通过getClass就可以获取到String这个Class实例

关系应该是

s(String的实例变量) ->getClass(获取到String的Class实例,上面存放着class的信息) -> getName(获取class的类名)

方法三:如果知道一个class的完整类名,可以通过静态方法Class.forName()获取:

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

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

==只能用于比较是否是同一个Class实例,而instanceOf则是判断是否是同一个类型,1比如属于Integet类型的变量,instanceOf Number的话也一定是true。

如果获取到了一个Class实例,我们就可以通过该Class实例来创建对应类型的实例:

// 获取String的Class实例:
Class cls = String.class;
// 创建一个String实例:
String s = (String) cls.newInstance();

上述代码相当于new String()。通过Class.newInstance()可以创建类实例,它的局限是:只能调用public的无参数构造方法。带参数的构造方法,或者非public的构造方法都无法通过Class.newInstance()被调用。

对于我们自己创建的类,也是同样的原理,比如

public class Person(){private int test;public Person(test){this.test = test}public setPerson(){}
}

jvm加载这个类的时候,会

Class cls = new Class(Person);

新建一个Person的Class 实例,后面可以通过这个Class 实例获取Person的相关信息学。

动态加载

JVM加载Class是动态加载的,也就是运行到什么类,再加载什么类。
利用JVM动态加载class的特性,我们才能在运行期根据条件加载不同的实现类。例如,Commons Logging总是优先使用Log4j,只有当Log4j不存在时,才使用JDK的logging。利用JVM动态加载特性,大致的实现代码如下:

小结

  • JVM为每个加载的class及interface创建了对应的Class实例来保存class及interface的所有信息;
  • 获取一个class对应的Class实例后,就可以获取该class的所有信息;
  • 通过Class实例获取class信息的方法称为反射(Reflection);
  • JVM总是动态加载class,可以在运行期根据条件来控制加载class。
获取属性

可以根据Class 实例获取到class的信息,比如

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

通过Class实例,可以获取到每个field对象,比如name等,就是一个field对象。如

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

Field对象,包含了一个字段的所有信息。
甚至可以获取该Field在实例上的值,设置新的值

  public static void main(String[] args) throws Exception {Person p = new Person("Xiao Ming");System.out.println(p.getName()); // "Xiao Ming"Class c = p.getClass();Field f = c.getDeclaredField("name");f.setAccessible(true);f.get(p)f.set(p, "Xiao Hong");System.out.println(p.getName()); // "Xiao Hong"}

f.setAccessible(true)用来设置private的字段
f.get(实例)用来获取 实例.field
f.set(实例,value)用来设置实例.field = value

小结
  • Java的反射API提供的Field类封装了字段的所有信息:
  • 通过Class实例的方法可以获取Field实例:getField(),getFields(),getDeclaredField(),getDeclaredFields();
  • 通过Field实例可以获取字段信息:getName(),getType(),getModifiers();
  • 通过Field实例可以读取或设置某个对象的字段,如果存在访问限制,要首先调用setAccessible(true)来访问非public字段。
  • 通过反射读写字段是一种非常规方法,它会破坏对象的封装。
调用方法

通过Class实例能获取字段信息,自然也能获取对应的方法信息。

Method getMethod(name, Class...):获取某个publicMethod(包括父类)
Method getDeclaredMethod(name, Class...):获取当前类的某个Method(不包括父类)
Method[] getMethods():获取所有publicMethod(包括父类)
Method[] getDeclaredMethods():获取当前类的所有Method(不包括父类)

其用法跟字段那些类似。

  • Java的反射API提供的Method对象封装了方法的所有信息:
  • 通过Class实例的方法可以获取Method实例:getMethod(),getMethods(),getDeclaredMethod(),getDeclaredMethods();
  • 通过Method实例可以获取方法信息:getName(),getReturnType(),getParameterTypes(),getModifiers();
  • 通过Method实例可以调用某个对象的方法:Object invoke(Object instance, Object… parameters);
  • 通过设置setAccessible(true)来访问非public方法;
  • 通过反射调用方法时,仍然遵循多态原则。即通过父的Class 实例获取到的method,调用对象为子类,且子类复写了该method,那么就会调用子类的method。
构造方法

之前说过,通过类实例可以创建新的实例,如

Person p = Person.class.newInstance();

但只能调用无参数的构造函数,若想调用传入参数的构造函数,需要

  public static void main(String[] args) throws Exception {// 获取构造方法Integer(int):Constructor cons1 = Integer.class.getConstructor(int.class);// 调用构造方法:Integer n1 = (Integer) cons1.newInstance(123);System.out.println(n1);// 获取构造方法Integer(String)Constructor cons2 = Integer.class.getConstructor(String.class);Integer n2 = (Integer) cons2.newInstance("456");System.out.println(n2);}

通过getConstuctor获取Constructor实例。
通过Class实例获取Constructor的方法如下:

getConstructor(Class...):获取某个publicConstructorgetDeclaredConstructor(Class...):获取某个ConstructorgetConstructors():获取所有publicConstructorgetDeclaredConstructors():获取所有Constructor

注意Constructor总是当前类定义的构造方法,和父类无关,因此不存在多态的问题。

调用非public的Constructor时,必须首先通过setAccessible(true)设置允许访问。setAccessible(true)可能会失败。

获取继承关系

之前说过,可以通过三种方法获取Class的实例,

Class cls = String.class; // 获取到String的Class 直接通过String类String s = ""; //通过String实例获取String CLass实例
Class cls = s.getClass(); // s是String,因此获取到String的ClassClass s = Class.forName("java.lang.String"); //通过传入完整类名
获取父类的Class

有了Class实例,可以获取父类Class 实例

    public static void main(String[] args) throws Exception {Class i = Integer.class;Class n = i.getSuperclass();System.out.println(n);Class o = n.getSuperclass();System.out.println(o);System.out.println(o.getSuperclass());}
获取interface
  public static void main(String[] args) throws Exception {Class s = Integer.class;Class[] is = s.getInterfaces();for (Class i : is) {System.out.println(i);}}
动态代理

先定义了接口Hello,但是我们并不去编写实现类,而是直接通过JDK提供的一个Proxy.newProxyInstance()创建了一个Hello接口对象。这种没有实现类但是在运行期动态创建了一个接口对象的方式,我们称为动态代码。JDK提供的动态创建接口对象的方式,就叫动态代理。

public class Main {public static void main(String[] args) {InvocationHandler handler = new InvocationHandler() {@Overridepublic 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(), // 传入ClassLoadernew Class[] { Hello.class }, // 传入要实现的接口handler); // 传入处理调用方法的InvocationHandlerhello.morning("Bob");}
}interface Hello {void morning(String name);
}

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

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

基于微信小程序的在线课堂的研究与实现,附源码

博主介绍:✌程序员徐师兄、7年大厂程序员经历。全网粉丝30W、csdn博客专家、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ 🍅文末获取源码联系🍅 👇🏻 精彩专栏推荐订阅👇…

基于matlab的密度散点图绘制

1. 什么是密度散点图? 密度散点图就是在普通散点图的基础上,基于样本点一定范围的样本数计算该样本点的密度,以不同的颜色来显示样本点密度的大小,这样能够直观的显示出数据的空间聚集情况,如下图分别是二维和三维密度…

MySQL表的基础操作

创建表 create table 表名(列名 类型,列名 类型……) 注意 1.在进行表操作之前都必须选中数据库 2.表名,列名等一般不可以与关键字相同,如果确定相同,就必须用反引号引住 3.可以使用comment来增加字段说…

大文件上传如何做断点续传?

文章目录 一、是什么分片上传断点续传 二、实现思路三、使用场景小结 参考文献 一、是什么 不管怎样简单的需求,在量级达到一定层次时,都会变得异常复杂 文件上传简单,文件变大就复杂 上传大文件时,以下几个变量会影响我们的用…

力扣hot100 -- 哈希

目录 🌼两数之和 暴力 二分 哈希 🌼字母异位词分组 unordered_map 排序 unordered_map 计数 🌼最长连续序列 unordered_set 跳过前驱 排序 dp 🌼两数之和 1. 两数之和 - 力扣(LeetCode) 暴…

Mom系统初步认知

什么是MOM系统? MOM指制造运营管理,是Manufacturing Operation Management的缩写;指通过协调管理企业的人员、设备、物料和能源等资源,把原材料或零件转化为产品的活动。MOM系统集成了生产计划、库存管理、生产调度、质量管理、设备维护、人员管理等功能,以实现生产效率和…

前后端分离好处多多,怕就怕分工不分人,哈哈

前后端分离倡导多年了,现在基本成为了开发的主流模式了,贝格前端工场承接的前端项目只要不考虑seo的,都采用前后端分离模式,这篇文章就来介绍一下前后端分离模式。 一、什么是前后端分离开发模式 前后端分离是一种软件开发的架构…

中国电子学会2019年12月份青少年软件编程Scratch图形化等级考试试卷三级真题(选择题、判断题)

一、单选题(共 25 题,每题 2 分,共 50 分) 1.怎样修改图章的颜色?( ) A. 只需要一个数字来设置颜色 B. 设置 RGB 的值 C. 在画笔中设置颜色、饱和度、亮度 D. 在外观中设置或修改角色颜色特效 2.以下程序的执…

根据三维点坐标使用matplotlib绘制路径轨迹

需求:有一些点的三维坐标(x,y,z),需要绘制阿基米德螺旋线轨迹图。 points.txt 0.500002, -0.199996, 0.299998 0.500545, -0.199855, 0.299338 0.501112, -0.199688, 0.298704 0.501701, -0.199497, 0.298…

二叉树、堆和堆排序(优先队列)

前言: 本章会讲解二叉树及其一些相关练习题,和堆是什么。 二叉树: 二叉树的一些概念: 一棵二叉树是有限节点的集合,该集合可能为空。二叉树的特点是每一个节点最多有两个子树,即二叉树不存在度大于2的节点…

ArcGIS学习(七)图片数据矢量化

ArcGIS学习(七)图片数据矢量化 通过上面几个任务的学习,大家应该已经掌握了ArcGIS的基础操作,并且学习了坐标系和地理数据库这两个非常重要且稍微难一些的专题。从这一任务开始,让我们进入到实战案例板块。 首先进入第一个案例一一图片数据矢量化。 我们在平时的工作学…

02.数据结构

一、链表 作用&#xff1a;用于写邻接表&#xff1b; 邻接表作用&#xff1a;用于存储图或树&#xff1b; 1、用数组模拟单链表 #include<iostream> using namespace std;const int N 100010;// head 表示头结点的下标 // e[i] 表示结点i的值 // ne[i] 表示结点i的ne…