Java基础-泛型、反射、注解、异常

泛型

一般的类和方法,只能使用具体的类型:基本类型或自定义的类。如果要编写可以应用于多种类型的代码,这种刻板的限制会极大的束缚代码。

泛型实现了“参数化类型的概念”。泛型这个术语的意思是:“适用于许多许多的类型”。泛型旨在编写更通用的代码,要使代码能应用于“某种不具体的类型,而不是一个具体的接口或类。在代码中表现为允许在定义类、接口时通过一个标识表示类中某个属性的类型或者是某个方法的返回值参数类型。这个类型参数将在使用时(例如,继承或实现这个接口,用这个类型声明变量、创建对象时确定(即传入实际的类型参数,也称为类型实参)。

泛型一般有三种使用方式:泛型类、泛型接口、泛型方法

泛型类

//此处T可以随便写为任意标识,常见的如T、E、K、V等形式的参数常用于表示泛型
//在实例化泛型类时,必须指定T的具体类型
//静态方法中不能使用类的泛型(编译不会通过),但是泛型方法可以声明为静态
public class Generic<T> {
    private T key;
    public Generic(T key) {
        this.key = key;
    }
    public T getKey() {
        return key;
    }
}

泛型接口

public interface Generator<T> {
    public T method();
}
//实现泛型接口,指定类型
class GeneratorImpl implements Generator<String>{
    @Override
    public String method() {
        return "hello";
    }
}

泛型方法

public static <E> void printArray(E[] inputArray) {
    for (E element : inputArray) {
        System.out.printf("%s ", element);
    }
    System.out.println();
}

常用通配符

  • ? 表示不确定的 Java 类型
  • T (type) 表示具体的一个 Java 类型
  • K V (key value) 分别代表 Java 键值中的 Key Value
  • E (element) 代表 Element

Object是所有类的根类,任何类的对象都可以设置给Object引用变量,使用的时候可能需要进行类型强制转换,但是用了泛型T、E这类标识符后,在实际用之前类型就已经确定了,不需要再进行类型强制转换

限定通配符与非限定通配符

限定通配符对类型进行限制,泛型中有两种限定通配符:

  1. 表示类型的上界,格式为<? extends T>,表示类型必须为T类型或者T子类
  2. 表示类型的下界,格式为<? super T>,表示类型必须为T类型或T类型的父类。

反射

Reflection(反射)是被视为动态语言的关键,反射机制允许程序在执行期借助于Reflection API取得任何类的内部信息,并能直接操作任意对象的内部属性及方法。反射被称为框架的灵魂,框架 = 反射 + 注解 + 设计模式。通过反射机制,程序可以知道:

  • 任意一个类的所有属性和方法。

  • 任意一个对象的一个属性和方法

使用场景

IDE的自动提示功能,如:输入对象,提示其属性和方法。此时,IDE会通过反射,获取类和对象的具体信息。

Class类

Class类是反射实现的基础。在程序运行期间,JVM 始终为所有的对象维护一个被称为运行时的类型标识,这个信息跟踪着每个对象所属的类的完整结构信息,包括包名、类名、实现的接口、拥有的方法和字段等。可以通过专门的 Java 类访问这些信息,这个类就是 Class 类。我们可以把 Class 类理解为类的类型,一个 Class 对象,称为类的类型对象,一个 Class 对象对应一个加载到 JVM 中的一个 .class 文件。也就是说,在Java中,类也是对象,类是java.lang.Class类的实例对象,这个对象称为类的类类型(class type)。反射在读取一个类之前,必须获取到java.lang.Class对象。Class对象包含类的所有信息,通过Class对象的方法,可以获取到构造方法,成员变量,成员方法和接口等信息。获取方法有:

  • 字面量直接获取:对象名.class。这种方式不会触发类的初始化但会将类加载到方法区。
  • Object类的getClass方法,例如Object.getClass()。这种方式会触发类的初始化。
  • Class的静态方法。如:Class.forName()。这也会触发类的初始化。

常用API

Field

成员变量,类中的属性对象。通过Class类的getDeclaredField() (获取方法)或getDeclaredFields() (获取所有方法,包括私有方法)方法获取。

Class类的基本API



1. Class.getName()可以获取类的名称
2. Class.getSimpleName();//不包含包名的类的名称
3. Class.getMethods()获取类的【public方法】集合,【包括继承来的】
4. Class.getDeclaredMethods()获取的是所有该类【自己声明】的方法,【不问访问权限】

Method

类中的方法对象。包括了静态方法和成员方法(包括抽象方法在内)。通过invoke()来完成方法被动态调用的目的。

1. m.getReturnType()得到该方法的返回值类型的类类型(class),如int.class String.class
2. m.getName()得到方法的名称
3. m.getParameterTypes()获得参数列表类型的类类型,如参数为(int,int)则得到(int.class ,int class)

getDeclaredMethod

可以获取指定方法名和参数的方法对象 Method(对象,参数列表)。

privateGetDeclaredMethods

从缓存或JVM中获取该Class中申明的方法列表。

searchMethods

从返回的方法列表里找到一个匹配名称和参数的方法对象。如果找到一个匹配的Method,则重新copy一份返回,即Method.copy()方法。

ReflectionData

用来缓存从JVM中读取类的如下属性数据。

Constructor

构造函数。类的构造方法

1、通过Class.getConstructor()获得Constructor[]所有公有构造方法信息
2、建议getDeclaredConstructors()获取自己声明的构造方法
3Constructor.getName():String
4Constructor.getParameterTypes():Class[]

注解(Annotation)

  • 从 Java 5 开始出现
  • Annotaticm 能被用来为程序元素( 类、方法、成员变量等)设置元数据
  • 所有注解都隐式继承了 Annotation java.lang.annotation.Annotation 接口

注解可以看作是一种特殊的注释,主要用于修饰类、方法或者变量,本质上是一个继承了Annotation 的特殊接口。

注解只有被解析之后才会生效,常见的解析方法有两种:

  • 编译期直接扫描 :编译器在编译 Java 代码的时候扫描对应的注解并处理,比如某个方法使用@Override 注解,编译器在编译的时候就会检测当前的方法是否重写了父类对应的方法。
  • 运行期通过反射处理 :像框架中自带的注解(比如 Spring 框架的 @Value@Component)都是通过反射来进行处理的。

JDK 提供了很多内置的注解(比如 @Override@Deprecated),同时,我们还可以自定义注解。

基本注解

  • 在 java.lang 包下
  • @Override 限定重写父类方法
  • @Deprecated 标示已过时(与文档注释中的 @deprecated 标记的作用基本相同),其他程序使用已过时的类、方法时,编译器会发出警告
  • @SuppressWarnings("变量值") 抑制编译器警告(常见的变量值:unused, rawtypes, unchecked, serial, deprecation, all)
  • @SafeVarargs 抑制编译器“堆污染”警告(Java 7 新增)
  • @FunctionalInterface 函数式接口(接口中只有一个抽象方法)(Java 8 新增)

JDK 的元注解

  • 在 java.lang.annotation 包下,用于修饰其它的 Annotation 定义
  • @Target:用于指定被修饰的注解能用于修饰哪些程序元素常量值封装在 ElementType 枚举类中:TYPE、FIELD、CONSTRUCTOR、METHOD、LOCAL_VARIABLE、PACKAGE、PARAMETER、ANNOTATION_TYPE
  • @Retention:用于指定被修饰的注解可以保留多长时间常量值封装在 RetentionPolicy 枚举类中:SOURCE、CLASS(默认值)、RUNTIME。此修饰可以通过反射提取
  • @Documented:其修饰的注解会保存到 API 文档中
  • @Inherited:其修饰的注解可以被子类所继承

自定义注解

  • Annotation 中的属性以无参数的抽象方法的形式来定义

  • 属性的类型只能是基本类型、String、Class、annotation、枚举及这些类型一维数组

  • 在定义 Annotation 的属性时可以使用 default 为其指定默认值

  • 使用带属性的注解时,必须为该注解的所有没有默认值的属性指定值

  • 对于 Annotation 中变量名为 value属性,在使用该注解时可以直接在该注解后的括号里指定 value 属性的值,无须使用“value = 变量值”的形式

异常

异常是程序中的一些错误,但并不是所有的错误都是异常,并且错误有时候是可以避免的。

比如说,你的代码少了一个分号,那么运行出来结果是提示是错误 java.lang.Error;如果你用System.out.println(11/0),那么你是因为你用0做了除数,会抛出 java.lang.ArithmeticException 的异常。

异常发生的原因有很多,通常包含以下几大类:

  • 用户输入了非法数据。
  • 要打开的文件不存在。
  • 网络通信时连接中断,或者JVM内存溢出。

这些异常有的是因为用户错误引起,有的是程序错误引起的,还有其它一些是因为物理错误引起的。

Throwable

在Java中,所有的异常都有一个共同的祖先 java.lang 包中的 Throwable 。Throwable 类有两个重要的子类:

  • Exception:程序本身可以处理的异常,可以通过catch来进行捕获。Exception又可以分为Checked Exception (受检查异常,必须处理) 和 Unchecked Exception (不受检查异常,可以不处理)。

  • Error :Error 属于程序无法处理的错误 ,我们没办法通过 catch 来进行捕获不建议通过catch捕获 。例如Java 虚拟机运行错误(Virtual MachineError)、虚拟机内存不够错误(OutOfMemoryError)、类定义错误(NoClassDefFoundError)等 。这些异常发生时,Java 虚拟机(JVM)一般会选择线程终止。

Checked Exception 和 Unchecked Exception的区别:

​ 一般情况下,Java 代码在编译过程中,如果受检查异常没有被 catch/throw 处理的话,就没办法通过编译 。除了 RuntimeException(运行时异常) 及其子类(NullPointerException、NumberFormatException、ArrayIndexOutOfBoundsException…)以外,其他的Exception类及其子类都属于受检查异常 。常见的受检查异常有: IO 相关的异常、ClassNotFoundExceptionSQLException…。

以及重要的实例方法:

  • String getMessage():返回该异常的描述信息(提示给用户)
  • String toString():返回该异常的类型和描述信息
  • void printStackTrace():打印异常的跟踪栈信息到控制台,包括异常的类型、异常的原因、异常出现的位置(开发和调试)
  • String getLocalizedMessage(): 返回异常对象的本地化信息。使用 Throwable 的子类覆盖这个方法,可以生成本地化信息。如果子类没有覆盖该方法,则该方法返回的信息与 getMessage()返回的结果相同

捕获异常

使用 try 和 catch 关键字可以捕获异常。try/catch 代码块放在异常可能发生的地方。

不管 try 块中的代码是否出现异常,也不管哪一个 catch 块被执行,甚至在 try 块或 catch 块中执行了 return 语句,finally 块总会被执行(除非在 try 块或会执行的 catch 块中调用退出 JVM 的相关方法)。

try 块必须和 catch 块或和 finally 块同在,不能单独存在,catch 块或和 finally 块二者必须出现一个,finally 块必须位于所有的 catch 块之后。

当程序执行 try 块、catch 块时遇到 return 或 throw 语句时,系统不会立即结束该方法,而是去寻找该异常处理流程中是否包含 finally 块,如果有 finally 块,系统立即开始执行 finally 块——只有当 finally 块执行完成后,系统才会再次跳回来执行 try 块、catch 块里的 return 或 throw 语句;如果 finally 块里也使用了 return 或 throw 等导致方法终止的语句,finally 块已经终止了方法,系统将不会跳回去执行 try 块、catch 块里的任何代码

try{
  // 程序代码
}catch(异常类型1 异常的变量名1){
  // 程序代码
}catch(异常类型2 异常的变量名2){
  // 程序代码
}finally{
  // 程序代码
  // 关闭资源对象、流对象等
}

使用 throws 声明抛出异常

在可能出现异常的方法上声明可能抛出的异常类型,用于表示当前方法不处理异常,而是提醒该方法的调用者来处理异常,因为throws不具有处理异常的功能。

  • 当前方法使用 throws 声明抛出异常,该异常将交给上一级调用者处理,调用者要么 try … catch,要么也 throws

  • 如果 main 方法也使用 throws 声明抛出异常,该异常将交给 JVM 处理,JVM 对异常的处理方法是,打印异常的跟踪栈信息,并中止程序运行

  • 子类方法声明抛出的异常类和父类方法声明抛出的异常类相同或者是其子类(运行时异常除外)

    例:

    class MyMath{
     public static int div(int x, int y) throws Exception{
                 return x/y;
            }
    }
    

使用 throw 自行抛出异常

throw 语句可以单独使用,但throw 后面只能跟一个异常对象 。在有返回值的方法中,可以使用 throw 来避免返回一个空值

  • 如果 throw 语句抛出的异常是 Checked 异常,则该 throw 语句必须处于 try 块里,或处于带 throws 声明的方法中
  • 如果 throw 语句抛出的异常是 Runtime 异常,则该语句无须放在 try 块里,也无须放在带 throws 声明抛出的方法中
  • 在 catch 块中使用 throw 语句,方法既可以捕获异常,还可以抛出异常给方法的调用者

语法格式:

throw new ExceptionClass("异常信息");//终止方法

自定义异常

  • 自定义 Checked 异常,应继承 Exception

  • 自定义 Runtime 异常,应继承 RuntimeException

  • 所有异常都必须是 Throwable 的子类。

    可以像下面这样定义自己的异常类:

    class MyException extends Exception{
    
    }
    
    1. 只继承Exception 类来创建的异常类是检查性异常类。

    2. 一个异常类和其它任何类一样,包含有变量和方法。

异常处理规则

  • 不要过度使用异常:
    • 对于完全已知的错误,应该编写处理这种错误的代码,增加程序的健壮性
    • 对外部的、不能确定和预知的运行时错误才使用异常
  • 不要使用过于庞大的 try 块
  • 避免使用 Catch All 语句
  • 不要忽略捕获到的异常
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值