Java工程师成神之路 基础篇 Java基础知识 异常&注解&泛型

异常

Error 和 Exception

Exception 和 Error ,二者都是 Java 异常处理的重要子类,各自都包含大量子类,均继承自Throwable 类。

Error 表示系统级的错误,是java 运行环境内部错误或者硬件问题,不能指望程序来处理这样的问题,除了退出运行外别无选择,它是Java 虚拟机抛出的。

Exception 表示程序需要捕捉、需要处理的异常,是由与程序设计的不完善而出现的问题,程序必须处理的问题。

异常类型

Java中的异常,主要可以分为两大类:即受检异常(checked exception)和 非受检异常(unchecked exception)

受检异常

对于受检异常来说,如果一个方法在声明的过程中证明了其要有受检异常抛出:

public void test() throws xxxExcetion{  }

那么,当我们在程序中调用它的时候,一定要对该异常进行处理(捕获或者向上抛出),否则是无法编译通过的,这是一种强制规范。

这种异常在IO操作中比较多。比如FileNotFoundException,当我们使用IO流处理一个文件的时候,有一种特殊情况,就是文件不存在,所以,在文件处理的接口定义时他会显式抛出FileNotFoundException,目的就是告诉这个方法的调用者,我这个⽅法不保证⼀定可以成功, 是有可能找不到对应的⽂件 的, 你要明确的对这种情况做特殊处理哦。

所以说, 当我们希望我们的⽅法调⽤者, 明确的处理⼀些特殊情况的时候, 就应该使⽤受检异常。

非受检异常

对于非受检异常来说,一般是运行时异常,继承自RuntimeException。在编写代码的时候,不需要显式的捕获,但是如果不捕获,在运行期如果发生异常就会中断程序的执行。

这种异常一般可以理解为是代码原因导致的。比如发生空指针、数组越界等。所以,只要代码写的没问题, 这些异常都是可以避免的。 也就不需要我们显⽰的进⾏处理。

试想⼀下, 如果你要对所有可能发⽣空指针的地⽅做异常处理的话, 那相当于你的所有代码都需要做这件事。

异常相关的关键字

throwsthrowtrycatchfinally

try:用来指定⼀块预防所有异常的程序;

catch:子句紧跟在try块后面,用来指定你想要捕获的异常的类型;

finally:为确保⼀段代码不管发⽣什么异常状况都要被执⾏;

throw:语句⽤来明确地抛出⼀个异常;(通常是对象)

throws:⽤来声明⼀个⽅法可能抛出的各种异常;(通常是类型)


正确处理异常

异常的处理方式有两种:1. 自己处理。 2.向上抛出,交给调用者处理。

异常,千万不能捕获了之后什么也不做。 或者只是使⽤e.printStacktrace

具体的处理⽅式的选择其实原则⽐较简明: ⾃⼰明确的知道如何处理的, 就要处理掉。 不知道如何处理的, 就交给调⽤者处理。


自定义异常

自定义异常就是开发人员自己定义的异常,一般通过继承Exception的子类的方式实现。

编写⾃定义异常类实际上是继承⼀个API标准异常类, ⽤新定义的异常处理信息覆盖原有信息的过程。

这种⽤法在Web开发中也⽐较常见, ⼀般可以⽤来⾃定义业务异常。 如余额不⾜、 重复提交等。 这种⾃定义异常有业务含义, 更容易让上层理解和处理。


异常链

“异常链”是Java中非常流行的异常处理的概念。是指在进行一个异常处理时抛出了另外一个异常,由此产生了一个异常链条。

该技术大多用于将“ 受检查异常” ( checked exception) 封装成为“⾮受检查异常”( unchecked exception)或者RuntimeException。

顺便说⼀下, 如果因为因为异常你决定抛出⼀个新的异常, 你⼀定要包含原有的异常, 这样, 处理程序才可以通过getCause()和initCause()⽅法来访问异常最终的根源。

从 Java 1.4版本开始,几乎所有的异常都支持异常链。

以下是Throwable中支持异常链的方法和构造函数。

Throwable getCause()
Throwable initCause(Throwable)
Throwable(String, Throwable)
Throwable(Throwable)

initCause 和 Throwable 构造函数的Throwable参数是导致当前异常的异常。getCause返回导致当前异常的异常,initCause设置当前异常的原因。

以下示例显示如何使用异常链。

try {

} catch (IOException e) {
    throw new SampleException("Other IOException", e);
}

在此示例中,当捕获到IOException时,将创建一个新的SampleException异常,并附加原始的异常原因,并将异常链抛出到下一个更高级别的异常处理程序。


try-with-resources

Java里,对于文件操作IO流、数据库连接等开销非常昂贵的资源,用完之后必须及时通过close方法将其关闭,否则资源会一直处于打开状态,可能会导致内存泄露等问题。

关闭资源的常用方式就是在finally块里是释放,即调用close方法。比如,我们经常会写这样的代码:

public static void main(String[] args) {
    BufferedReader br = null;
    try {
        String line;
        br = new BufferedReader(new FileReader("d:\\hollischuang.xml"));
        while ((line = br.readLine()) != null) {
            System.out.println(line);
        }
    } catch (IOException e) {
        // handle exception
    } finally {
        try {
            if (br != null) {
                br.close();
            }
        } catch (IOException ex) {
            // handle exception
        }
    }
}

从Java7开始,jdk提供了一种更好的方式关闭资源,使用try-with-resources语句,改写一下上面的代码,效果如下:

public static void main(String... args) {
    try (BufferedReader br = new BufferedReader(new FileReader("d:\\ hollischuang.xml"))) {
        String line;
        while ((line = br.readLine()) != null) {
            System.out.println(line);
        }
    } catch (IOException e) {
        // handle exception
    }
}

看,这简直是一大福音啊,虽然我之前一般使用IOUtils去关闭流,并不会使用在finally中写很多代码的方式,但是这种新的语法糖看上去好像优雅很多呢。看下他的背后:

public static transient void main(String args[])
    {
        BufferedReader br;
        Throwable throwable;
        br = new BufferedReader(new FileReader("d:\\ hollischuang.xml"));
        throwable = null;
        String line;
        try
        {
            while((line = br.readLine()) != null)
                System.out.println(line);
        }
        catch(Throwable throwable2)
        {
            throwable = throwable2;
            throw throwable2;
        }
        if(br != null)
            if(throwable != null)
                try
                {
                    br.close();
                }
                catch(Throwable throwable1)
                {
                    throwable.addSuppressed(throwable1);
                }
            else
                br.close();
            break MISSING_BLOCK_LABEL_113;
            Exception exception;
            exception;
            if(br != null)
                if(throwable != null)
                    try
                    {
                        br.close();
                    }
                    catch(Throwable throwable3)
                      {
                        throwable.addSuppressed(throwable3);
                    }
                else
                    br.close();
        throw exception;
        IOException ioexception;
        ioexception;
    }
}

其实背后的原理也很简单,那些我们没有做的关闭资源的操作,编译器都帮我们做了。所以,再次印证了,语法糖的作用就是方便程序员的使用,但最终还是要转成编译器认识的语言。


finally 和 return 的执行顺序

try()里面有一个return语句, 那么后⾯的finally{}⾥⾯的code会不会被执⾏, 什么时候执⾏, 是在return前还是return后?

如果try中有return语句, 那么finally中的代码还是会执⾏。因为return表⽰的是要整个⽅法体返回, 所以,finally中的语句会在return之前执⾏。

但是return前执行的finally块内,对数据的修改效果对于引用类型和值类型会不同

// 测试 修改值类型
static int f() {
    int ret = 0;
    try {
        return ret;  // 返回 0,finally内的修改效果不起作用
    } finally {
        ret++;
        System.out.println("finally执行");
    }
}

// 测试 修改引用类型
static int[] f2(){
    int[] ret = new int[]{0};
    try {
        return ret;  // 返回 [1],finally内的修改效果起了作用
    } finally {
        ret[0]++;
        System.out.println("finally执行");
    }
}

注解

元注解

说简单点,就是定义其他注解的注解,比如Override这个注解,就不是一个元注解。而是通过元注解定义出来的。

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Override {
}

这里面的@Target @Retention 就是元注解。

元注解有四个:

  • @Target:表示该注解可以用到什么地方
  • @Retention:表示在什么级别保存该注解信息。
  • Documented:将此注解包含在javadoc中
  • Inherited:允许子类继承父类的注解。

自定义注解

除了元注解,都是自定义注解。通过元注解定义出来的注解。 如我们常用的Override 、Autowire等。 日常开发中也可以自定义一个注解,这些都是自定义注解。


Java中常见注解使用

  • @Override:表示当前方法覆盖了父类的方法
  • @Deprecation 表示方法已经过时,方法上有横线,使用时会有警告。
  • @SuppressWarnings 表示关闭一些警告信息(通知java编译器忽略特定的编译警告)
  • SafeVarargs(jdk1.7更新) 表示:专门为抑制“堆污染”警告提供的。
  • @FunctionalInterface(jdk1.8更新) 表示:用来指定某个接口必须是函数式接口,否则就会编译出错。

Spring常用注解

  • @Configuration把一个类作为一个IoC容器,它的某个方法头上如果注册了@Bean,就会作为这个Spring容器中的Bean。
  • @Scope注解 作用域
  • @Service用于标注业务层组件
  • @Controller用于标注控制层组件
  • @Repository用于标注数据访问组件,即DAO组件。
  • @Component泛指组件,当组件不好归类的时候,我们可以使用这个注解进行标注。
  • @Scope用于指定scope作用域的(用在类上)
  • @PostConstruct用于指定初始化方法(用在方法上)
  • @PreDestory用于指定销毁方法(用在方法上)
  • @DependsOn:定义Bean初始化及销毁时的顺序
  • @Primary:自动装配时当出现多个Bean候选者时,被注解为@Primary的Bean将作为首选者,否则将抛出异常
  • @Autowired 默认按类型装配,如果我们想使用按名称装配,可以结合@Qualifier注解一起使用。如下: @Autowired @Qualifier("personDaoBean") 存在多个实例配合使用
  • @Resource默认按名称装配,当找不到与名称匹配的bean才会按类型装配。
  • @PostConstruct 初始化注解
  • @PreDestroy 摧毁注解 默认 单例 启动就加载

Spring中的这几个注解有什么区别:@Component、@Repository、@Service、@Controller

  1. @Component指的是组件,

    @Controller,@Repository和@Service 注解都被@Component修饰,用于代码中区分表现层,持久层和业务层的组件,代码中组件不好归类时可以使用@Component来标注

  2. 当前版本只有区分的作用,未来版本可能会添加更丰富的功能


注解和反射的结合

注解和反射经常结合在一起使用,在很多框架的代码中都能看到他们结合使用的影子

可以通过反射来判断类、方法、字段上是否有某个注解以及获取注解中的值,获取某个类中方法上的注解代码示例如下:

Class<?> clz = bean.getClass();
Method[] methods = clz.getMethods();
for (Method method : methods) {
    if (method.isAnnotationPresent(EnableAuth.class)) {
        String name = method.getAnnotation(EnableAuth.class).name();
    }
}

通过isAnnotationPresent判断是否存在某个注解,通过getAnnotation获取注解对象,然后获取值。

示例

示例参考:https://blog.csdn.net/KKALL1314/article/details/96481557

自己写了一个例子,实现功能如下:

一个类的某些字段上被注解标识,在读取该属性时,将注解中的默认值赋给这些属性,没有标记的属性不赋值

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
@Documented
@Inherited
public @interface MyAnno {
    String value() default "有注解";
}

定义一个类

@Data
@ToString
public class Person {
    @MyAnno
    private String stra;
    private String strb;
    private String strc;

    public Person(String str1,String str2,String str3){
        super();
        this.stra = str1;
        this.strb = str2;
        this.strc = str3;
    }

}

这里给str1加了注解,并利用反射解析并赋值:

public class MyTest {
    public static void main(String[] args) {
        //初始化全都赋值无注解
        Person person = new Person("无注解","无注解","无注解");
        //解析注解
        doAnnoTest(person);
        System.out.println(person.toString());
    }

  private static void doAnnoTest(Object obj) {
        Class clazz = obj.getClass();
        Field[] declareFields = clazz.getDeclaredFields();
        for (Field field:declareFields) {
            //检查该类是否使用了某个注解
            if(clazz.isAnnotationPresent(MyAnno.class)){
                MyAnno anno = field.getAnnotation(MyAnno.class);
                if(anno!=null){
                    String fieldName = field.getName();
                    try {
                        Method setMethod = clazz.getDeclaredMethod("set" + fieldName.substring(0, 1).toUpperCase() + fieldName.substring(1),String.class);
                        //获取注解的属性
                        String annoValue = anno.value();
                        //将注解的属性值赋给对应的属性
                        setMethod.invoke(obj,annoValue);
                    }catch (NoSuchMethodException e){
                        e.printStackTrace();
                    } catch (IllegalAccessException e) {
                        e.printStackTrace();
                    } catch (InvocationTargetException e) {
                        e.printStackTrace();
                    }

                }
            }

        }
    }

}

运行结果:

Person(stra=有注解, strb=无注解, strc=无注解)

当开发者使用了Annotation 修饰了类、方法、Field 等成员之后,这些 Annotation 不会自己生效,必须由开发者提供相应的代码来提取并处理 Annotation 信息。这些处理提取和处理 Annotation 的代码统称为 APT(Annotation Processing Tool)。

注解的提取需要借助于 Java 的反射技术,反射比较慢,所以注解使用时也需要谨慎计较时间成本。


如何定义一个注解

在Java中,类使用class定义,接口使用interface定义,注解和接口的定义差不多,增加了一个@符号,即@interface,代码如下:

public @interface EnableAuth {

}

注解中可以定义成员变量,用于信息的描述,跟接口中方法的定义类似,代码如下:

public @interface EnableAuth {
    String name();
}

还可以添加默认值:

public @interface EnableAuth {
    String name() default "猿天地";
}

上面的介绍只是完成了自定义注解的第一步,开发中日常使用注解大部分是用在类上,方法上,字段上,示列代码如下:

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface EnableAuth {

}

Target

用于指定被修饰的注解修饰哪些程序单元,也就是上面说的类,方法,字段

Retention
用于指定被修饰的注解被保留多长时间,分别SOURCE(注解仅存在于源码中,在class字节码文件中不包含,即编译后消失),CLASS(默认的保留策略,注解会在class字节码文件中存在,但运行时无法获取),RUNTIME(注解会在class字节码文件中存在,在运行时可以通过反射获取到)三种类型,如果想要在程序运行过程中通过反射来获取注解的信息需要将Retention设置为RUNTIME

Documented

用于指定被修饰的注解类将被javadoc工具提取成文档

Inherited

用于指定被修饰的注解类将具有继承性


泛型

什么是泛型

Java泛型(generics) 是JDK5 中引入的一个新特性, 允许在定义类和接⼜的时候使⽤类型参数( type parameter) 。

声明的类型参数在使⽤时⽤具体的类型来替换。 泛型最主要的应⽤是在JDK 5中的新集合类框架中。

泛型最⼤的好处是可以提⾼代码的复⽤性。 以List接⼜为例,我们可以将String、 Integer等类型放⼊List中, 如不⽤泛型, 存放String类型要写⼀个List接口, 存放Integer要写另外⼀个List接口, 泛型可以很好的解决这个问题。


类型擦除

什么是类型擦除

类型擦除指的是通过类型参数合并,将泛型类型实例关联到同一份字节码上。编译器只为泛型类型生成一份字节码,并将其实例关联到这份字节码上。类型擦除的关键在于从泛型类型中清除类型参数的相关信息,并且再必要的时候添加类型检查和类型转换的方法。 类型擦除可以简单的理解为将泛型java代码转换为普通java代码,只不过编译器更直接点,将泛型java代码直接转换成普通java字节码。 类型擦除的主要过程如下: 1.将所有的泛型参数用其最左边界(最顶级的父类型)类型替换。2.移除所有的类型参数。

Java 编译器处理泛型的过程

code1:

public static void main(String[] args) {  
    Map<String, String> map = new HashMap<String, String>();  
    map.put("name", "hollis");  
    map.put("age", "22");  
    System.out.println(map.get("name"));  
    System.out.println(map.get("age"));  
}  

反编译后的code 1:

public static void main(String[] args) {  
    Map map = new HashMap();  
    map.put("name", "hollis");  
    map.put("age", "22"); 
    System.out.println((String) map.get("name"));  
    System.out.println((String) map.get("age"));  
}  

我们发现泛型都不见了,程序又变回了Java泛型出现之前的写法,泛型类型都变回了原生类型,


code2:

interface Comparable<A> {
    public int compareTo(A that);
}

public final class NumericValue implements Comparable<NumericValue> {
    private byte value;

    public NumericValue(byte value) {
        this.value = value;
    }

    public byte getValue() {
        return value;
    }

    public int compareTo(NumericValue that) {
        return this.value - that.value;
    }
}

反编译后的code 2:

 interface Comparable {
  public int compareTo( Object that);
} 

public final class NumericValue
    implements Comparable
{
    public NumericValue(byte value)
    {
        this.value = value;
    }
    public byte getValue()
    {
        return value;
    }
    public int compareTo(NumericValue that)
    {
        return value - that.value;
    }
    public volatile int compareTo(Object obj)
    {
        return compareTo((NumericValue)obj);
    }
    private byte value;
}

code3:

public class Collections {
    public static <A extends Comparable<A>> A max(Collection<A> xs) {
        Iterator<A> xi = xs.iterator();
        A w = xi.next();
        while (xi.hasNext()) {
            A x = xi.next();
            if (w.compareTo(x) < 0)
                w = x;
        }
        return w;
    }
}

反编译后的code 3:

public class Collections
{
    public Collections()
    {
    }
    public static Comparable max(Collection xs)
    {
        Iterator xi = xs.iterator();
        Comparable w = (Comparable)xi.next();
        while(xi.hasNext())
        {
            Comparable x = (Comparable)xi.next();
            if(w.compareTo(x) < 0)
                w = x;
        }
        return w;
    }
}

第2个泛型类Comparable<A> 擦除后A被替换为最左边界Object,Comparable<NumbericVavlue>的类型参数NumbericValue被擦除掉,但这直接导致NumbericValue没有实现接口Comparable的compareTo(Object object)方法,于是编译器充当好人,添加了一个桥接方法。第3个示例中限定了类型参数的边界<A extends Comparable<A>,A必须为Comparable<A>的子类,按照类型擦除的过程,先讲所有的类型参数替换为最左边界Comparable<A>,然后去掉参数类型A,得到最终的擦除后结果。


泛型带来的问题

一、泛型遇到重载

**public class GenericTypes {  

    public static void method(List<String> list) {  
        System.out.println("invoke method(List<String> list)");  
    }  

    public static void method(List<Integer> list) {  
        System.out.println("invoke method(List<Integer> list)");  
    }  
}  

上面这段代码,有两个重载的函数,因为他们的参数类型不同,一个是List<String>另一个是List ,但是,这段代码是编译通不过的。因为我们前面讲过,参数List<Integer>List<String>编译之后都被擦除了,变成了一样的原生类型List,擦除动作导致这两个方法的特征签名变得一模一样。

二、当泛型遇到catch

如果我们自定义了一个泛型异常类GenericException,那么,不要尝试用多个catch取匹配不同的异常类型,例如你想要分别捕获GenericException、GenericException,这也是有问题的。

三、当泛型遇到静态变量

public class StaticTest{
    public static void main(String[] args){
        GT<Integer> gti = new GT<Integer>();
        gti.var=1;
        GT<String> gts = new GT<String>();
        gts.var=2;
        System.out.println(gti.var);
    }
}
class GT<T>{
    public static int var=0;
    public void nothing(T x){}
}

答案是 2

由于经过类型擦除,所有的泛型类实例都关联到同一份字节码上,泛型类的所有静态变量是共享的。


泛型中 K T V E ? object 等的含义

E–> Element(在集合中使用,因为集合中存放的是元素)
T --> Type (Java 类)
K --> Key (键)
V—> Value (值)
N—> Number(数值类型)
? - 表示不确定的java类型(无限制通配符类型)

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


限定通配符和非限定通配符

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

  • 表示类型的上界:格式为<? extends T>,即类型必须是T类型或者T子类
  • 表示类型的下界:格式为<? super T>,即类型必须为T类型或T的父类

泛型类型必须⽤限定内的类型来进⾏初始化,否则会导致编译错误。

⾮限定通配符表⽰可以⽤任意泛型类型来替代,类型为

先看一个例子:

public class Food {}
public class Fruit extends Food {}
public class Apple extends Fruit {}
public class Banana extends Fruit{}

public class GenericTest {

    public void testExtends(List<? extends Fruit> list){

        //报错,extends为上界通配符,只能取值,不能放.
        //因为Fruit的子类不只有Apple还有Banana,这里不能确定具体的泛型到底是Apple还是Banana,所以放入任何一种类型都会报错
        //list.add(new Apple());

        //可以正常获取
        Fruit fruit = list.get(1);
    }

    public void testSuper(List<? super Fruit> list){

        //super为下界通配符,可以存放元素,但是也只能存放当前类或者子类的实例,以当前的例子来讲,
        //无法确定Fruit的父类是否只有Food一个(Object是超级父类)
        //因此放入Food的实例编译不通过
        list.add(new Apple());
//        list.add(new Food());

        Object object = list.get(1);
    }
}

在testExtends方法中,因为泛型中用的是extends,在向list中存放元素的时候,我们并不能确定List中的元素的具体类型,即可能是Apple也可能是Banana。因此调用add方法时,不论传入new Apple()还是new Banana(),都会出现编译错误。

理解了extends之后,再看super就很容易理解了,即我们不能确定testSuper方法的参数中的泛型是Fruit的哪个父类,因此在调用get方法时只能返回Object类型。结合extends可见,在获取泛型元素时,使用extends获取到的是泛型中的上边界的类型(本例子中为Fruit),范围更小。

在使用泛型时,存取元素时用super,获取元素时,用extends。

频繁往外读取内容的,适合用上界Extends。经常往里插入的,适合用下界Super。


List和原始类型List之间的区别?

原始类型List 和带参数类型List<Object>直接的主要区别是,在编译时编译器不会对原始类型进行类型安全检查,却会对带参数的类型进行检查。

通过使用Object 作为类型,可以告知编译器该方法可以接受任何类型的对象。比如String或Integer。

它们之间的第二点区别是,你可以把任何带参数的类型传递给原始类型List,但却不能把List传递给接受 List的方法,因为会产生编译错误。

List 和 List 之间的区别是什么?

List<?> 是一个未知类型的List,而List<Object> 其实是任意类型的List。你可以把List<String>, List<Integer>赋值给List<?>,却不能把List<String>赋值给 List<Object>

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值