春招Java后端开发面试 2021-10-8

春招Java后端开发面试

​ 春招Java后端开发面试总结包含了JavaOOP、Java集合容器、Java异常、并发编程、Java反射、Java序列化、JVM、Redis、Spring MVC、MyBatis、MySQL数据库、消息中间件MQ、Dubbo、Linux、ZooKeeper、 分布式&数据结构与算法等25个专题技术点,各个大厂总结出来的面试真题

一、JavaOOP面试题

1、short s1 = 1; s1 = s1 + 1;有错吗? short s1 = 1; s1 += 1; 有错吗?

int 占4个字节 4x8=32位 short占用两个字节 2x8=16位

short s1 = 1; s1 = s1 + 1; 有错

s1为short型 s1+1 变量提升为int型,不能显示转换为int型

short s1 = 1; s1 += 1; 没有错

s1+=1; 等价于 s1 = (short)(s1+1)

​ 第一个为简单赋值操作符,第二个为复合赋值操作符

在Java规范中提到,复合赋值(E1 op = E2)等价于简单赋值 E1 = (short)((E1)op (E2))

复合赋值表达式自动将执行的结果—>转化为左侧变量的类型。

假若结果类型与变量类型相同,转型不会造成影响;

然而结果类型比变量类型要宽,复合操作符执行一个窄化原生类型转换 例如:long t1; t1+=1;

2、重载和重写的区别
答:方法的重载和重写都是实现多态的方式,区别在于前者实现的是编译时的多态性,而后者实现的是运行时的多态性。
重载发生在一个类中,同名的方法如果有不同的参数列表(参数类型不同、参数个数不同或者二者都不同)则视为重载;
重写发生在子类与父类之间,重写要求子类重写方法与父类被重写方法有相同的参数列表,有兼容的返回类型,比父类被重写方法更好访问,不能比父类被重写方法声明更多的异常(里氏代换原则)。
重载对返回类型没有特殊的要求,不能根据返回类型进行区分。

重写(Overiide) —> 简单的说:就是子类把父类本身有的方法重新写一遍,子类继承父类原有的方法。

​ 子类可以对父类的方法体进行修改或重写(在方法名,参数列表,返回类型(除了子类方法的返回类型是父类方法返回类型的子类时))

​ 注意:子类函数的访问修饰符权限不能少于父类的

//重写(Overiide)
class Animal{
    public void skinColor(){
        System.out.println("皮肤有颜色");
    }
}
class Dog extends Animal{
    public void skinColor(){
        System.out.println("皮肤-->黄色");
    }
}
  • 重写 总结:
    1.发生在父类与子类之间
    2.方法名,参数列表,返回类型(除子类中方法的返回类型是父类返回类型的子类)必须相同
    3.访问修饰符的限制一定要大于被重写方法的访问修饰符(public>protected>default>private)
    4.重写方法一定不能抛出新的检查异常或者比被重写方法申明更加宽泛的检查型异常

重载(Override)

在一个类中,同名方法如果有同的参数列表(参数类型不同、参数个数不同甚至是参数顺序不同)则视为重载。

同时重载的返回类型没有要求,但是==不能通过返回类型是否相同来判断重载==。

//重载(Override)
class Animal{
    public void skinColor(){
        System.out.println("皮肤有颜色");
    }
    public void skinColor(String skColor){}
}
3、数组实例化有几种方式?

3.1 静态实例化:创建数组的时候已经指定数组中的元素`

int [] a= new int[]{ 1 , 3 , 3}

3.2 动态实例化:实例化数组的时候,只指定了数组程度,数组中所有元素都是数组类型的默认值

int [] a= new int[2];
a[0]=1;//给数组元素赋值
a[2]=2;

3.3 数组的默认初始化

数组是引用类型,他的元素相当于类的实例变量,因此数组一经分配空间,其中的每个元素也被按照实例变量同样的方式被隐式初始化

实例:int a2[]=new int[2];//默认值0,0

boolean[] b=new boolean[2];//默认值 false,false

String[] s=new String[2];//默认值null
4、Java中各种数据默认值
  • Byte,short,int,long默认是都是0
  • Boolean默认值是false
  • Char类型的默认值是\u0000
  • Float与double类型的默认是0.0
  • 对象类型的默认值是null
5、Object类常用方法有哪些?

Equals
Hashcode
toString
wait
notify
clone
getClass

6、java中是值传递引用传递?

​ Java是值传递。 当传的是基本类型时,传的是值的拷贝,对拷贝变量的修改不影响原变量;

当传的是引用类型时,传的是引用地址的拷贝,但是拷贝的地址和真实地址指向的都是同一个真实数据,

因此可以修改原变量中的值;当传的是String类型时,虽然拷贝的也是引用地址,指向的是同一个数据,

但是String的值不能被修改,因此无法修改原变量中的值。

7、形参与实参区别

7.1 主体不同

  1. 实参:在调用有参函数时,函数名后面括号中的参数为“实际参数”。
  2. 形参:不是实际存在变量,又称虚拟变量。

7.2 目的不同

  1. 实参:可以是常量、变量或表达式, 无论实参是何种类型的量,在进行函数调用时,都必须具有确定的值, 以便把这些值传送给形参。
  2. 形参:定义函数名和函数体的时候使用的参数,目的是用来接收调用该函数时传入的参数。

7.3 特点不同

  1. 实参:在调用函数过程中,系统会把实参的值传递给被调用函数的形参。或者说,形参从实参得到一个值。该值在函数调用期间有效,可以参加该函数中的运算。
  2. 形参:形参的本质是一个名字,不占用内存空间。
8、构造方法能不能重写?能不能重载?

构造方法不可以被重写,因为重写发生在父类和子类之间,要求方法名称相同,而构造方法的名称是和类名相同的,而子类类名不会和父类类名相同,所以不可以被重写

构造方法可以被重载

9、内部类与静态内部类的区别?

​ 定义在一个类内部的类叫内部类,包含内部类的类称为外部类。内部类可以声明public、protected、private等访问限制,

可以声明 为abstract的供其他内部类或外部类继承与扩展,或者声明为static、final的,也可以实现特定的接口。

10、final在java中的作用,有哪些用法?

​ final关键字可以用来修饰类、方法和变量(包括成员变量和局部变量)

10.1 修饰类

  • 当用final修饰一个类时,表明这个类不能被继承。final类中的成员变量可以根据需要设为final,但是要注意final类中的所有成员方法都会被隐式地指定为final方法。(尽量不要将类设计为final类)

10.2 修饰方法

  • 使用final方法的原因有两个。第一个原因是把方法锁定,以防任何继承类修改它的含义;

  • 第二个原因是效率。在早期的Java实现版本中,会将final方法转为内嵌调用。

    但是如果方法过于庞大,可能看不到内嵌调用带来的任何性能提升。在最近的Java版本中,不需要使用final方法进行这些优化了。“

    因此,如果只有在想明确禁止 该方法在子类中被覆盖的情况下才将方法设置为final的。即父类的final方法是不能被子类所覆盖的,也就是说子类是不能够存在和父类一模一样的方法的。

    final修饰的方法表示此方法已经是“最后的、最终的”含义,亦即此方法不能被重写(可以重载多个final修饰的方法)。

    此处需要注意的一点是:因为重写的前提是子类可以从父类中继承此方法,如果父类中final修饰的方法同时访问控制权限为private,

    将会导致子类中不能直接继承到此方法,因此,此时可以在子类中定义相同的方法名和参数,此时不再产生重写与final的矛盾,

    而是在子类中重新定义了新的方法。(注:类的private方法会隐式地被指定为final方法。)

10.3 修饰变量

  • final成员变量表示常量,只能被赋值一次,赋值后值不再改变。

  • 当final修饰一个基本数据类型时,表示该基本数据类型的值一旦在初始化后便不能发生变化;

    如果final修饰一个引用类型时,则在对其初始化之后便不能再让其指向其他对象了,但该引用所指向的对象的内容是可以发生变化的

    本质上是一回事,因为引用的值是一个地址,final要求值,即地址的值不发生变化。

    final修饰一个成员变量(属性),必须要显示初始化。这里有两种初始化方式,一种是在变量声明的时候初始化;

    第二种方法是在声明变量的时候不赋初值,但是要在这个变量所在的类的所有的构造函数中对这个变量赋初值。

    当函数的参数类型声明为final时,说明该参数是只读型的。即你可以读取使用该参数,但是无法改变该参数的值。

二、Java集合/泛型面试题

1、ArrayList和linkedList的区别
  1. ArrayList的实现是基于数组的数据结构,LinkedList的基于链表的数据结构。这两个数据结构的逻辑关系是不一样,当然物理存储的方式也会是不一样。

  2. LinkedList比ArrayList更占内存,因为LinkedList的节点除了存储数据,还存储了两个引用,一个指向前一个元素,一个指向后一个元素

  3. 对于随机访问,ArrayList要优于LinkedList。因为LinkedList要移动指针

  4. 对于插入和删除操作,LinkedList优于ArrayList。因为ArrayList要移动数据

//参考 https://www.cnblogs.com/huzi007/p/5550440.html
 static final int N=50000;
 static long timeList(List list){
     long start=System.currentTimeMillis();
     Object o = new Object();
     for(int i=0;i<N;i++) {
         list.add(0, o);
     }
     return System.currentTimeMillis()-start;
 }
 static long readList(List list){
     long start=System.currentTimeMillis();
     for(int i=0,j=list.size();i<j;i++){

     }
     return System.currentTimeMillis()-start;
 }

 static List addList(List list){
     Object o = new Object();
     for(int i=0;i<N;i++) {
         list.add(0, o);
     }
     return list;
 }
 public static void main(String[] args) {
     System.out.println("ArrayList添加"+N+"条耗时:"+timeList(new ArrayList()));
     System.out.println("LinkedList添加"+N+"条耗时:"+timeList(new LinkedList()));

     List list1=addList(new ArrayList<>());
     List list2=addList(new LinkedList<>());
     System.out.println("ArrayList查找"+N+"条耗时:"+readList(list1));
     System.out.println("LinkedList查找"+N+"条耗时:"+timeList(list2));
 }
2、HashMap排序题
//参考 https://blog.csdn.net/qq_34273888/article/details/82899122
public class Student {
    private String name;
    private int age;

    public Student(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}

public class Main {
    public static void main(String[] args) {
        HashMap<Integer,Student> hashMap = new HashMap<>();

        Student student1 = new Student("张三",15);
        Student student2 = new Student("李四",13);//模拟一些数据存放进HashMap
        Student student3 = new Student("王武",18);

        hashMap.put(1,student1);
        hashMap.put(2,student2);
        hashMap.put(3,student3);

        System.out.println("排序前:"+hashMap);

        hashMap = sortHashMap(hashMap);

        System.out.println("排序后:"+hashMap);

    }

    public static HashMap<Integer,Student> sortHashMap(HashMap<Integer,Student> hashMap ){
        /*
        * 首先想到HashMap是无序的,所以我们应该用他有序的子类LinkedHashMap
        * */

        LinkedHashMap linkedHashMap = new LinkedHashMap();
        Set<Map.Entry<Integer,Student>> set =hashMap.entrySet();    //将HashMap中的键值对取出放入set
        ArrayList<Map.Entry<Integer,Student>> list = new ArrayList<>(set);  //把set转化为List

        list.sort(new Comparator<Map.Entry<Integer, Student>>() {
            @Override
            public int compare(Map.Entry<Integer, Student> o1, Map.Entry<Integer, Student> o2) {
                return o2.getValue().getAge() - o1.getValue().getAge();     //自定义比较方法,倒序的话,就是后面的减前面的
            }
        });

        for (Map.Entry<Integer,Student> e:list){
            linkedHashMap.put(e.getKey(),e.getValue());
        }   // 遍历排序过的List,将Entry<Integer,Student>的键和值分别取出,放入LinkedHashMap

        return linkedHashMap;


    }
}
3、Collection包结构,与Collections的区别

3.1 Collection:
是集合类的上层接口。本身是一个Interface,里面包含了一些集合的基本操作。
Collection接口是Set接口和List接口的父接口

​ ava.util.Collection 是一个集合框架的父接口。它提供了对集合对象进行基本操作的通用接口方法。

​ Collection接口在Java 类库中有很多具体的实现。Collection接口的意义是为各种具体的集合提供了最大化的统一操作方式。

 Collection   
├List   
│├LinkedList   
│├ArrayList   
│└Vector   
│ └Stack   
└Set 

3.2 Collections
Collections是一个集合框架的帮助类,里面包含一些对集合的排序,搜索以及序列化的操作。
最根本的是Collections是一个类,Collections 是一个包装类,Collection 表示一组对象,这些对象也称为 collection 的元素。一些 collection 允许有重复的元素,而另一些则不允许,一些 collection 是有序的,而另一些则是无序的。

​ java.util.Collections 是一个包装类。它包含有各种有关集合操作的静态多态方法。此类不能实例化,就像一个工具类,服务于Java的Collection框架。他提供一系列静态方法实现对各种集合的搜索、排序、线程安全化等操作。

4、带集合参数的构造器

暂无答案

//参考
1.this()super()必须放在第一行,且不能同时出现在一个函数中
2.this()super都指的是对象,所以不能出现在static3,构造方法不能被继承
4,构造方法不是类的成员方法
5、说说List,Set,Map三者的区别
// 参考https://blog.csdn.net/qq_40588579/article/details/79945935
List1. 可以允许重复的对象。
    2. 可以插入多个null元素。
    3. 是一个有序容器,保持了每个元素的插入顺序,输出的顺序就是插入的顺序。
    4. 常用的实现类有 ArrayListLinkedListVectorArrayList 最为流行,它提供了使用索引的随意访问,而 LinkedList 则对于经常需要从 List 中添加或删除元素的场合更为合适。
 Set1. 不允许重复对象
    2. 无序容器,你无法保证每个元素的存储顺序,TreeSet通过 Comparator  或者 Comparable 维护了一个排序顺序。
    3. 只允许一个 null 元素
    4.Set 接口最流行的几个实现类是 HashSetLinkedHashSet 以及 TreeSet。最流行的是基于 HashMap 实现的 HashSetTreeSet 还实现了 SortedSet 接口,因此 TreeSet 是一个根据其 compare()compareTo() 的定义进行排序的有序容器。
 Map:
1. Map不是collection的子接口或者实现类。Map是一个接口。
2. Map 的 每个 Entry 都持有两个对象,也就是一个键一个值,Map 可能会持有相同的值对象但键对象必须是唯一的。
3. TreeMap 也通过 Comparator  或者 Comparable 维护了一个排序顺序。
4. Map 里你可以拥有随意个 null 值但最多只能有一个 null 键。
5. Map 接口最流行的几个实现类是 HashMapLinkedHashMapHashtableTreeMap。(HashMapTreeMap最常用)

5.2 面试题:什么场景下使用list,set,map呢?

(或者会问为什么这里要用list、或者set、map,这里回答它们的优缺点就可以了)

1、如果你经常会使用索引来对容器中的元素进行访问,那么 List 是你的正确的选择。如果你已经知道索引了的话,那么 List 的实现类比如 ArrayList 可以提供更快速的访问,如果经常添加删除元素的,那么肯定要选择LinkedList2、如果你想容器中的元素能够按照它们插入的次序进行有序存储,那么还是 List,因为 List 是一个有序容器,它按照插入顺序进行存储。
3、如果你想保证插入元素的唯一性,也就是你不想有重复值的出现,那么可以选择一个 Set 的实现类,
  比如 HashSetLinkedHashSet 或者 TreeSet。所有 Set 的实现类都遵循了统一约束比如唯一性,
  而且还提供了额外的特性比如 TreeSet 还是一个 SortedSet,所有存储于 TreeSet 中的元素可以使用 Java 里的 Comparator 或者 Comparable 进行排序。   LinkedHashSet 也按照元素的插入顺序对它们进行存储。
4、如果你以键和值的形式进行数据存储那么 Map 是你正确的选择。
  你可以根据你的后续需要从 HashtableHashMapTreeMap 中进行选择。
6、并发集合和普通集合如何区别?

​ 线程安全集合常见的有Vector、HashTable(由于性能原因,都比较少用了)

​ 并发集合常见的有ConcurrentHashMap、ConcurrentLinkedQueue、ConcurrentLinkedDeque 等。并发集合位 于 java.util.concurrent 包 下 , 是 jdk1.5 之 后 才 有 的 ,

​ 在 java 中有普通集合、同步(线程安全)的集合、并发集合。
​ 1、普通集合通常性能最高,但是不保证多线程的安全性和并发的可靠性。
​ 2、线程安全集合仅仅是给集合添加了 synchronized 同步锁,严重牺牲了性能,而且对并发的效率就更低了。
​ 3、并发集合则通过复杂的策略不仅保证了多线程的安全又提高了并发时的效率

7、Map有什么特点

https://www.php.cn/java-article-369611.html

Map集合的特点:
1.是一个双列集合,赋值的时候必须同时给key和value赋值
2.是一个无序的集合(存入和取出元素的顺序可能不一致)
3.key值不能重复,value可以重复
4.一个key只能对应一个vlaue
5.定义集合时,数据类型key和value可以使用相同的数据类型,也可以使用不同的数据类型

Map集合的特点
java.util.Map接口:集合,是一个双列集合

Map集合遍历的第一种方式
Map集合遍历的第一种方式:通过健查找值的方式
Map集合中有一个方法:keySet
Set keySet() 返回此映射中包含的键的 Set 视图。 把Map集合中的健存储到一个Set集合中
遍历Map集合的步骤:
1.定义一个Map集合,往集合中添加元素
2.调用Map集合中的方法keySet,把Map集合中的健存储到一个Set集合中
3.遍历Set集合,获取Map集合所有的健
4.通过获取到的健,使用Map集合的方法get查找值

Map集合遍历的第二种方式
Map集合遍历的第二种方式:遍历键值对的方式
Map集合中有一个方法:entrySet
Set> entrySet() 返回此映射中包含的映射关系的 Set 视图。
遍历步骤:
1.定义一个Map集合,往集合中添加元素
2.调用Map集合中的方法entrySet,把Map集合中的每一个映射关系(结婚证)放入到Set集合中
3.遍历Set集合,获取每一个映射关系Entry
4.使用Entry中的方法getKey和getValue获取健和值

8、集合类存放于 Java.util 包中, 主要有几 种接口

主要包含Set(集)、List(列表)、map(映射)。
1.Collection:Collection是集合List、Set、Queue的父接口。
2.Iterator:迭代器,用于遍历集合中的数据
3.Map:是映射表的基础接口
在这里插入图片描述

List接口是非常常用的数据类型,List是有序的Collection,共有三个实现类,分别是:
ArrayList、Vector、LinkedList
在这里插入图片描述
Set接口注重独一无二的特性,该体系的集合用于存储无序、不可重复的值。
在这里插入图片描述

9、什么是list接口 java.util.list接口 extends Collection接口
//参考 https://www.cnblogs.com/cainiao-chuanqi/p/11223151.html

List接口的特点:

  1. 有序的集合,存储元素和取出元素的顺序是一致的(存储123 取出123)
  2. 有索引,包含了一些带索引的方法
  3. 允许存储重复的元素

List接口中带索引的方法(特有):

public void add(int index,E element):将指定的元素,添加到该集合中的指定位置上。
public E get(int index):返回集合中指定位置的元素。
public E remove(int index):移除列表中指定位置的元素,返回的是被移除的元素。
public E set(int index,E element):用指定元素替换集合中指定位置的元素,返回值的更新前的元素。

注意:
操作索引的时候,一定要防止索引越界异常

  • IndexOutOfBoundsException:索引越界异常,集合会报
  • ArrayIndexOutOfBoundsException:数组索引越界异常
  • StringIndexOutOfBoundsException:字符串索引越界异常
//创建一个List集合对象,多态
List<String> list = new ArrayList<>();
{
    //public void add(int index,E element):将指定的元素,添加到该集合中的指定位置上。
    //在索引2和索引3之间添加一个cainiao
    list.add(3,"cainiao");//{a,b,c,d}-->{a,b,c,cainiao,d}
    //移除元素
    String removeE = list.remove(2)
    //替换元素
    String setE = list.set(4,"A");
}
10、List的子类
  • ArrayList集合

java.util.ArrayList集合数据存储的结构是数组结构元素增删慢,查找快,由于日常开发中使用最多的功能为查询数据、遍历数据,所以ArrayList是最常用的集合。

  • LinkedList集合

java.util.LinkedList集合数据存储的结构是链结构。方便元素添加,删除的集合。

ArrayList集合的特点:

  1. 底层是一个链的结构:查询慢,增删快
  2. 里边包含了大量操作首尾元素的方法

注意:
使用ArrayList集合特有的方法,不能使用多态

  • public void addFirst(E e):将指定元素插入此列表的开头

  • public void addLast(E e):将指定元素添加到此列表的结尾

  • public void addpush(E e):将元素推如此列表所表示的推栈

  • public E getFirst():返回此列表的第一个元素。

  • public E getLast():返回此列表的最后一个元素。

  • public E removeFirst():移除并返回此列表的第一个元素。

  • public E removeLast():移除并返回此列表的最后一个元素。

  • public E pop():从此列表所表示的推栈处弹出一个元素。相当于removeFirst

  • public boolean isEmpty():如果列表不包含元素,则返回true

  • Vector集合

Vector 类可以实现可增长的对象数组。
与新collection不同,Vector是同步的。

10、说说ArrayList(数组)

System.Collections. ArrayList类是一个特殊的数组。 通过添加和删除元素,就可以动态改变数组的长度。
一、优点
1、支持自动改变大小的功能
2、可以灵活的插入元素
3、可以灵活的删除元素

二、局限性
跟-般的数组比起来,速度上差些

三、Java异常面试题

1、Java中异常分为哪两种?
//参考 https://blog.csdn.net/pipizhen_/article/details/107387343

在这里插入图片描述

答案:编译时异常 和 运行时异常

java中异常分类:

1、异常在java中以类和对象的形式存在,那么异常的继承结果是怎样的?

最先是Object

  • Object下有Throwable

    • Throwable下有两个分支:Error(不可处理,直接退出JVM)和Exception(可处理的)
      Exception下有两个分支: Exception直接子类,RuntimeException。
  • Exception的直接子类:编译时异常(要求程序员在编写程序阶段必须预先对这些异常进行处理,不处理,编译不通过)
    编译时异常也被称为:受检异常(CheckedException)、受控异常。

  • RuntimeException:运行时异常(在编写程序阶段程序员可以处理,也可以不处理)。
    运行时异常也被称为:未受检异常(UnCheckedException)、非受控异常。

2、常见异常类的继承结构图:
在这里插入图片描述
3、编译时异常和运行时异常,都是发生在运行阶段。编译阶段异常是不会发生的。

编译时异常因为什么而得名?因为编译时异常必须在编译前先处理,如果不处理编译器报错,因此得名。

4、所有异常都是在运行阶段发生的。因为只有程序运行阶段才可以new对象。因为异常的发生就是new异常对象。

编译时异常和运行时异常的区别是什么?

答:编译时异常一般发生的概率比较高。对于一些发生概率高的异常,需要在编译时预先对其处理。
运行时异常一般发生的概率很低。你可以预先处理,也可以不处理。

假设java中没有对异常进行划分,没有分编译时异常和运行时异常,所有的异常都需要在编写阶段对其预处理,将是怎样的效果?
首先,如果这样做的话,程序肯定是绝对的安全。
但是程序员就太累,到处都是处理异常的代码,显得很乱。

5、java中对异常的处理方式包括两种方式:
第一种:在方法声明的位置上,使用throws关键字,抛给上一级。谁掉用我我就抛给谁。
第二种:使用try…catch语句进行异常的捕捉。

异常发生之后,如果选择了上抛,抛给了调用者,调用者需要对这个异常继续处理,同样调用者处理这个异常也有两种方式。

注意:java中异常发生之后如果一直上抛,最终抛给了main方法,main方法继续上抛,抛给了调用者JVM,JVM知道这个异常发生,只有一个结果,终止程序执行。

public class Test03 {
    public static void main(String[] args) {
        System.out.println(100 / 0);
        /*
        * 程序执行完100/0,发生了ArithmeticException异常,底层new了一个ArithmeticException对象
        * 因为没有进行捕捉异常,所以进行异常上抛,抛给调用者main方法,main方法也没有处理,
        * 将这个异常抛给了JVM,JVM最终终止程序的执行。
        *
        * ArithmeticException 继承 RuntimeException,属于运行时异常
        * 在编写阶段不需要对这种异常进行预先处理,编译通过。
        *
        * */

        System.out.println("hello world!");  // 没有执行
    }
}
2、异常的处理机制有几种?
  • 异常捕捉:try…catch…finally

    • 在Java程序运行过程中系统得到一个异常对象是,它将会沿着方法的调用栈逐层回溯,寻找处理这一异常的代码。

      找到能够处理这种类型异常的方法后,运行时系统把当前异常交给这个方法处理;

      如果找不到可以捕获异常的方法,则运行时系统将终止,相应的Java程序也将退出

  • 异常抛出:throws

    • 当Java程序运行时系统得到一个异常对象时,如果一个方法并不知道如何处理所出现的异常,

      则可在方法声明时,声明抛弃异常。声明抛弃异常是在一个方法声明中的throws子句中指明的

3、如何自定义一个异常

继承一个异常类,通常是RumtimeException或者Exception

自定义异常的语法形式为:

//参考 http://c.biancheng.net/view/1051.html
<class><自定义异常名><extends><Exception>

自定义异常类一般包含两个构造方法:一个是无参的默认构造方法,另一个构造方法以字符串的形式接收一个定制的异常消息,并将该消息传递给超类的构造方法。

例如,以下代码创建一个名称为 IntegerRangeException 的自定义异常类:

class IntegerRangeException extends Exception {
    public IntegerRangeException() {
        super();
    }
    public IntegerRangeException(String s) {
        super(s);
    }
}
4、try catch finally,try里有return,finally还执行么?
//参考 https://blog.csdn.net/xzw_123/article/details/51679455

执行,并且finally的执行早于try里面的return

结论:

1、不管有木有出现异常,finally块中代码都会执行;

2、当trycatch中有return时,finally仍然会执行;

3finally是在return后面的表达式运算后执行的(此时并没有返回运算后的值,而是先把要返回的值保存起来,管finally中的代码怎么样,返回的值都不会改变,任然是之前保存的值),所以函数返回值是在finally执行前确定的;

在java语言的异常处理中,finally经常被用在需要释放资源的情况下,无论出现什么情况,finally块里面的代码一定会被执行。

(除了两种情况,

  • 第一种是在程序进入try语句块之前就出现异常,会直接结束,不会执行finally块里面的代码。

  • 第二种是程序在try语句块中强制退出时也不会去执行finally块里面的代码。)。

由于程序执行return就意味着结束对当前函数的调用并跳出这个函数体。因此任何语句要执行都只能在return前执行(除非碰到exit函数), 因此 finally块里的代码也是在return前执行的。此外,如果try-finally或者catch- finally中都有return,那么 finally块中的return语句将会覆盖别处的return语句,最终返回到调用者那里的是 finally中return的值。

5、 Excption与Error包结构
//参考 https://blog.csdn.net/l55iuming/article/details/53241613
ExcptionError包结构。OOM你遇到过哪些情况,SOF你遇到过哪些情况
Java语言把异常当做对象来处理,并定义了一个基类(java.util.Throwable)作为所有异常的父类。异常分为ErrorException两大类。

1Error
        不可恢复的异常。
        程序中不推荐去捕获Error类型的异常,主要原因是:运行时异常多是由于逻辑错误导致的,属于应该解决的错误。也就是说,一个正确的程序中是不应该存在Error的。当这些异常发生时,JVM一般会选择将线程终止。

2Exception
        可恢复的异常。
        (1) 检查异常
              所有继承Exception并且不是运行时异常的异常都是检查异常。
              这种异常发生在编译阶段,Java编译器强制程序去捕获此类异常。
        (2) 运行时异常
             编译器没有强制对其进行捕获并处理。当出现这种异常时,会由JVM来处理,eg. NullPointerExceptionClassCastExceptionArrayIndexOutOfBoundsExceptionArithmeticException等。

             出现运行时异常后,系统会把异常一直往上层抛出,直到遇到处理代码为止。若没有处理块,则跑到最上层。
             如果不对运行时的异常进行处理,后果是非常严重的,一旦发生,要么是线程中止,要么是主程序终止。
在使用异常处理时,要注意以下几个问题:

        (1) 先捕获子类,再捕获基类的异常信息。
        (2) 尽早抛出异常。
        (3) 异常能处理就处理,不能处理就抛出。
        (4) 可以根据实际的需求自定义异常类,只要继承自Exception类即可。
OOM(OutOfMemoryError)        
        除了PC外,其他几个运行时区域都有发生OOM异常的可能性。
    
        (1) Java堆
              如果堆中没有内存完成实例分配,且堆无法扩展,抛出OOM。

        (2) Java栈和本地方法栈
              若线程请求的栈深度大于虚拟机所允许的最大深度,将抛出SOF;
              如虚拟机在扩展栈时,仍无法申请到足够的内存空间,则抛出OOM。

        (3) 方法区
              如果方法区无法满足内存分配需求时,将抛出OOM。

        (4) 运行时常量池
              如果要向运行时常量池中添加内容,最简单的做法就是使用String.intern()这个Native方法。
    该方法的作用是:如果池中已经包含一个等于此String的字符串,则返回代表池中这个字符串的String对象;
    否则,将此String对象包含的字符串添加到常量池中,并且返回此String对象的引用。由于常量池分配在方法区内,
    我们可以通过-XX:PermSize-XX:MaxPermSize限制方法区的大小,从而间接限制其中常量池的容量。
SOF(StackOverFlowError)
         程序中一旦出现死循环或者是大量的递归调用,在不断的压栈过程中,造成栈容量超过默认大小而导致溢出。
         栈溢出的原因:

              (1) 递归调用
              (2) 大量循环或死循环
              (3) 全局变量是否过多
              (4) 数组、ListMap数据过多
6、Throw与thorws区别
//参考 https://blog.csdn.net/ltcz99/article/details/114544985#:~:text=throw%20s%E5%92%8C%20throw%20%E7%9A%84%20%E5%8C%BA%E5%88%AB%20%28%20%E9%9D%A2%E8%AF%95%E9%A2%98%20%29,%E5%BC%82%E5%B8%B8%20%2C%E8%80%8C%E4%BA%A4%E7%BB%99%E6%96%B9%E6%B3%95%E8%B0%83%E7%94%A8%E5%A4%84%E8%BF%9B%E8%A1%8C%E5%A4%84%E7%90%86%E3%80%82%20%E4%BD%BF%E7%94%A8%20throw%20s%20%E6%8A%9B%E5%87%BA%E7%9A%84%20%E8%BF%90%E8%A1%8C%E6%97%B6%E5%BC%82%E5%B8%B8%20%E7%9B%B8%E5%BD%93%E4%BA%8E

2.throws和throw的相同点和区别
区别:
1) throw 是手动抛出异常,throw new Exception(); 抛出的是某一个异常类型的实例
2) throws 是方法抛出异常,写在方法声明处
public void show()throws Exception.紧跟throws后的是异常类型,而非异常实例,且可以声明抛出多个异常,同时这些异常类型大多都为编译时异常类型

​ 3)throw 是程序员手动抛出异常,一般可用在某种流程控制,需要显示操作失误情况下可
对外抛出异常,进入catch代码块,明示操作有误等. 明确这个地方要抛出这个异常.

​ 4)throws 方法抛出异常,通常是告知调用此方法者,本方法有可能抛出一个异常,在调用
时应当要进行异常监控。且因为throws方法抛出异常为编译时异常类型,这样在编译阶段就
要求用户调用时对编译时异常类型作出捕获[try…catch(){}语句块],或者再次向上抛出。

​ 5)throws可以单独使用,但throw不能.throw要么和try-catch-finally语句配套使用,要么与throws配套使用。但throws可以单独使用,然后再由处理异常的方法捕获。
​ 6)程序会在throw语句后立即终止,它后面的语句执行不到,然后在包含它的所有try块中
(可能在上层调用函数中)从里向外寻找含有与其匹配的catch子句的try块。

相同点:
只是抛出或者可能抛出异常,但是不会由函数去处理异常,真正的处理异常由函数的上层调
用处理。

7、Error与Exception区别?

含义不同

​ Error类一般是与虚拟机相关的问题,如系统崩溃,虚拟机错误,内存空间不足,方法调用栈溢等。对于这类错误的导致的应用程序中断,仅靠程序本身无法恢复和和预防,遇到这样的错误,建议让程序终止。

​ Exception类表示程序可以处理的异常,可以捕获且可能恢复。遇到这类异常,应该尽可能处理异常,使程序恢复运行,而不应该随意终止异常。

用途不同

​ Exception和Error体现了Java平台设计者对不同异常情况的分类。Exception是程序正常运行中,可以预料的意外情况,可能并且应该被捕获,进行相应处理。

​ Error是指在正常情况下,不大可能出现的情况,绝大部分的Error都会导致程序处于非正常的、不可恢复状态。既然是非正常情况,所以不便于也不需要捕获,常见的比如OutOfMemoryError之类,都是Error的子类。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1pomtpDm-1633919099908)(%E9%9D%A2%E8%AF%95%E7%AC%94%E8%AE%B0.assets/023b5bb5c9ea15ced8578f68a6003af33a87b212)]

好处

​ 让异常处理与业务逻辑的主线分离,我们可以对可以遇见的异常作分支处理,其实将业务逻辑与异常处理分离也是Exception设计的主旨,其次Java Exception 不需要像C语言那样在程序的多个地方去检测同一个错误,并就地作异常的处理,相比老式的错误处理,现行的错误处理的结构则来的更加清晰。

8、error和exception有什么区别
//参考 

二者的不同之处:
Exception:
1.可以是可被控制(checked) 或不可控制的(unchecked)。
2.表示一个由程序员导致的错误。
3.应该在应用程序级被处理。
Error:
1.总是不可控制的(unchecked)。
2.经常用来用于表示系统错误或低层资源的错误。
3.如何可能的话,应该在系统级被捕捉。

9、final、finally、finalize 的区别?
//参考 https://www.cnblogs.com/smart-hwt/p/8257330.html

1.简单区别:
final用于声明属性,方法和类,分别表示属性不可交变,方法不可覆盖,类不可继承。
finally是异常处理语句结构的一部分,表示总是执行。
finalize是Object类的一个方法,在垃圾收集器执行的时候会调用被回收对象的此方法,供垃圾收集时的其他资源回收,例如关闭文件等。

2.中等区别:
虽然这个单词在Java中都存在,但是并没太多关联:
final:java中的关键字,修饰符。
A).如果一个类被声明为final,就意味着它不能再派生出新的子类,不能作为父类被继承。因此,一个类不能同时被声明为abstract抽象类的和final的类。
B).如果将变量或者方法声明为final,可以保证它们在使用中不被改变.
  1)被声明为final的变量必须在声明时给定初值,而在以后的引用中只能读取,不可修改。
  2)被声明final的方法只能使用,不能重载。
finally:java的一种异常处理机制。
  finally是对Java异常处理模型的最佳补充。finally结构使代码总会执行,而不管无异常发生。使用finally可以维护对象的内部状态,并可以清理非内存资源。特别是在关闭数据库连接这方面,如果程序员把数据库连接的close()方法放到finally中,就会大大降低程序出错的几率。
finalize:Java中的一个方法名。
Java技术使用finalize()方法在垃圾收集器将对象从内存中清除出去前,做必要的清理工作。

​ 这个方法是由垃圾收集器在确定这个对象没被引用时对这个对象调用的。它是在Object类中定义的,因此所的类都继承了它。子类覆盖finalize()方法以整理系统资源或者执行其他清理工作。finalize()方法是在垃圾收集器删除对象之前对这个对象调用的。

3.详细区别:
这是一道再经典不过的面试题了,我们在各个公司的面试题中几乎都能看到它的身影。final、finally和finalize虽然长得像孪生兄弟一样,但是它们的含义和用法却是大相径庭。
final关键字我们首先来说说final。它可以用于以下四个地方:
1).定义变量,包括静态的和非静态的。
2).定义方法的参数。
3).定义方法。
4).定义类。
定义变量,包括静态的和非静态的。定义方法的参数
第一种情况:
如果final修饰的是一个基本类型,就表示这个变量被赋予的值是不可变的,即它是个常量;
如果final修饰的是一个对象,就表示这个变量被赋予的引用是不可变的
这里需要提醒大家注意的是,不可改变的只是这个变量所保存的引用,并不是这个引用所指向的对象。
第二种情况:final的含义与第一种情况相同。
实际上对于前两种情况,一种更贴切的表述final的含义的描述,那就是,如果一个变量或方法参数被final修饰,就表示它只能被赋值一次,但是JAVA虚拟机为变量设定的默认值不记作一次赋值。被final修饰的变量必须被初始化。初始化的方式以下几种:
1.在定义的时候初始化。
2.final变量可以在初始化块中初始化,不可以在静态初始化块中初始化。
3.静态final变量可以在定义时初始化,也可以在静态初始化块中初始化,不可以在初始化块中初始化。
4.final变量还可以在类的构造器中初始化,但是静态final变量不可以。

定义方法
当final用来定义一个方法时,它表示这个方法不可以被子类重写,但是并不影响它被子类继承

​ 这里需要特殊说明的是,具有private访问权限的方法也可以增加final修饰,但是由于子类无法继承private方法,因此也无法重写它。

​ 编译器在处理private方法时,是照final方来对待的,这样可以提高该方法被调用时的效率。不过子类仍然可以定义同父类中private方法具同样结构的方法,但是这并不会产生重写的效果,而且它们之间也不存在必然联系。

定义类
最后我们再来回顾一下final用于类的情况。这个大家应该也很熟悉了,因为我们最常用的String类就是final的。

​ 由于final类不允许被继承,编译器在处理时把它的所方法都当作final的,因此final类比普通类拥更高的效率。而由关键字abstract定义的抽象类含必须由继承自它的子类重载实现的抽象方法,因此无法同时用final和abstract来修饰同一个类。

同样的道理,
final也不能用来修饰接口。 final的类的所方法都不能被重写,但这并不表示final的类的属性(变量值也是不可改变的,要想做到final类的属性值不可改变,必须给它增加final修饰

finally语句
接下来我们一起回顾一下finally的用法。finally只能用在try/catch语句中并且附带着一个语句块,表示这段语句最终总是被执行。

运行结果说明了finally的作用:

1.程序抛出了异常

2.执行了finally语句块请大家注意,捕获程序抛出的异常之后,既不加处理,也不继续向上抛出异常,并不是良好的编程习惯,它掩盖了程序执行中发生的错误,这里只是方便演示,请不要学习。
那么,没一种情况使finally语句块得不到执行呢?
return、continue、break这个可以打乱代码顺序执行语句的规律。

很明显,return、continue和break都没能阻止finally语句块的执行。从输出的结果来看,return语句似乎在finally语句块之前执行了,事实真的如此吗?我们来想想看,return语句的作用是什么呢?是退出当前的方法,并将值或对象返回。如果 finally语句块是在return语句之后执行的,那么return语句被执行后就已经退出当前方法了,finally语句块又如何能被执行呢?因此,正确的执行顺序应该是这样的:编译器在编译return new ReturnClass();时,将它分成了两个步骤,new ReturnClass()和return,前一个创建对象的语句是在finally语句块之前被执行的,而后一个return语句是在finally语句块之后执行的,也就是说finally语句块是在程序退出方法之前被执行的。同样,finally语句块是在循环被跳过(continue和中断(break之前被执行的
finalize方法
最后,我们再来看看finalize,它是一个方法,属于java.lang.Object类,它的定义如下:protected void finalize()throws Throwable{}众所周知,finalize()方法是GC(garbagecollector运行机制的一部分,在此我们只说说finalize()方法的作用是什么呢?finalize()方法是在GC清理它所从属的对象时被调用的,如果执行它的过程中抛出了无法捕获的异常(uncaughtexception,GC将终止对改对象的清理,并且该异常会被忽略;直到下一次GC开始清理这个对象时,它的finalize()会被再次调用。请看下面的示例:

10、Java 异常处理机制的理解?
//参考 https://blog.csdn.net/didong8506/article/details/101335005

​ 先谈谈我的理解:异常处理机制可以说是让我们编写的程序运行起来更加的健壮,无论是在程序调试、运行期间发生的异常情况的捕获,都提供的有效的补救动作,任何业务逻辑都会存在异常情况,这时只需要记录这些异常情况,抛出异常,绝不能生吞异常,不要再finally中处理返回值。

​ 先丢个问题:请对比 Exception 和 Error,另外,运行时异常与一般异常有什么区别?

经典回答

​ Exception 和 Error 都是继承了 Throwable 类,在 Java 中只有 Throwable 类型的实例才可以被抛出(throw)或者捕获(catch),它是异常处理机制的基本组成类型。

​ Exception 和 Error 体现了 Java 平台设计者对不同异常情况的分类。

​ Exception 是程序正常运行中,可以预料的意外情况,可能并且应该被捕获,进行相应处理。

​ Error 是指在正常情况下,不大可能出现的情况,绝大部分的 Error 都会导致程序(比如 JVM 自身)处于非正常的、不可恢复状态。既然是非正常情况,所以不便于也不需要捕获,常见的比如 OutOfMemoryError 之类,都是 Error 的子类。

​ Exception 又分为可检查(checked)异常不检查(unchecked)异常,可检查异常在源代码里必须显式地进行捕获处理,这是编译期检查的一部分。前面我介绍的不可查的 Error,是 Throwable 不是 Exception。

​ 不检查异常就是所谓的运行时异常,类似 NullPointerException、ArrayIndexOutOfBoundsException 之类,通常是可以编码避免的逻辑错误,具体根据需要来判断是否需要捕获,并不会在编译期强制要求。

分析

​ 分析 Exception 和 Error 的区别,是从概念角度考察了 Java 处理机制。总的来说,还处于理解的层面。
我们在日常编程中,如何处理好异常是比较考验功底的,我觉得需要掌握两个方面。

​ 第一,理解 *Throwable*、*Exception*、*Error* 的设计和分类。比如,掌握那些应用最为广泛的子类,以及如何自定义异常等。

​ 你了解Error、Exception或者RuntimeException?
​ 画了一个简单的类图,并列出来典型例子,可以给你作为参考,至少做到基本心里有数。
在这里插入图片描述

​ 这里也可以思考下:NoClassDefFoundError 和 ClassNotFoundException 有什么区别?

​ 第二,理解 Java 语言中操作 Throwable 的元素和实践。掌握最基本的语法是必须的,如 try-catch-finally 块,throw、throws 关键字等。与此同时,也要懂得如何处理典型场景。

​ 异常处理代码比较繁琐,比如我们需要写很多千篇一律的捕获代码,或者在 finally 里面做一些资源回收工作。随着 Java 语言的发展,引入了一些更加便利的特性,比如 try-with-resources 和 multiple catch,具体可以参考下面的代码段。在编译时期,会自动生成相应的处理逻辑,比如,自动按照约定俗成 close 那些扩展了 AutoCloseable 或者 Closeable 的对象。

try (BufferedReader br = new BufferedReader();
     BufferedWriter writer = new BufferedWriter()) {// Try-with-resources
// do something
catch ( IOException | XEception e) {// Multiple catch
   // Handle it
} 

知识扩展

​ 前面谈的大多是概念性的东西,下面我来谈些实践中的选择,我会结合一些代码用例进行分析。

​ 先开看第一个吧,下面的代码反映了异常处理中哪些不当之处?

try {
  // 业务代码
  // …
  Thread.sleep(1000L);
} catch (Exception e) {
  // Ignore it
}

​ 这段代码作为一段实验代码,它是没有任何问题的,但是在产品代码中,通常都不允许这样处理。你先思考一下这是为什么呢?

​ 我们先来看看[printStackTrace()][1]尤其是对于分布式系统,如果发生异常,但是无法找到堆栈轨迹(stacktrace),这纯属是为诊断设置障碍。所以,最好使用产品日志,详细地输出到日志系统里。的文档,开头就是“Prints this throwable and its backtrace to the standard error stream”。问题就在这里,在稍微复杂一点的生产系统中,标准出错(STERR)不是个合适的输出选项,因为你很难判断出到底输出到哪里去了。

​ 尤其是对于分布式系统,如果发生异常,但是无法找到堆栈轨迹(stacktrace),这纯属是为诊断设置障碍。所以,最好使用产品日志,详细地输出到日志系统里。

​ 我们接下来看下面的代码段,体会一下Throw early, catch late 原则。

public void readPreferences(String fileName){
     //...perform operations... 
    InputStream in = new FileInputStream(fileName);
     //...read the preferences file...
}

​ 如果 fileName 是 null,那么程序就会抛出 NullPointerException,但是由于没有第一时间暴露出问题,堆栈信息可能非常令人费解,往往需要相对复杂的定位。这个 NPE 只是作为例子,实际产品代码中,可能是各种情况,比如获取配置失败之类的。在发现问题的时候,第一时间抛出,能够更加清晰地反映问题。

​ 我们可以修改一下,让问题“throw early”,对应的异常信息就非常直观了。

public void readPreferences(String filename) {
    Objects. requireNonNull(filename);
    //...perform other operations... 
    InputStream in = new FileInputStream(filename);
     //...read the preferences file...
}

​ 至于“catch late”,其实是我们经常苦恼的问题,捕获异常后,需要怎么处理呢?最差的处理方式,就是我前面提到的“生吞异常”,本质上其实是掩盖问题。如果实在不知道如何处理,可以选择保留原有异常的 cause 信息,直接再抛出或者构建新的异常抛出去。在更高层面,因为有了清晰的(业务)逻辑,往往会更清楚合适的处理方式是什么。

有的时候,我们会根据需要自定义异常,这个时候除了保证提供足够的信息,还有两点需要考虑:

  • 是否需要定义成 Checked Exception,因为这种类型设计的初衷更是为了从异常情况恢复,作为异常设计者,我们往往有充足信息进行分类。
  • 在保证诊断信息足够的同时,也要考虑避免包含敏感信息,因为那样可能导致潜在的安全问题。如果我们看 Java 的标准类库,你可能注意到类似 java.net.ConnectException,出错信息是类似“ Connection refused (Connection refused)”,而不包含具体的机器名、IP、端口等,一个重要考量就是信息安全。类似的情况在日志中也有,比如,用户数据一般是不可以输出到日志里面的。

业界有一种争论(甚至可以算是某种程度的共识),Java 语言的 Checked Exception 也许是个设计错误,反对者列举了几点:

  • Checked Exception 的假设是我们捕获了异常,然后恢复程序。但是,其实我们大多数情况下,根本就不可能恢复。Checked Exception 的使用,已经大大偏离了最初的设计目的。

  • Checked Exception 不兼容 functional 编程,如果你写过 Lambda/Stream 代码,相信深有体会。

    ​ 从性能角度来审视一下 Java 的异常处理机制,这里有两个可能会相对昂贵的地方:

  • try-catch 代码段会产生额外的性能开销,或者换个角度说,它往往会影响 JVM 对代码进行优化,所以建议仅捕获有必要的代码段,尽量不要一个大的 try 包住整段的代码;与此同时,利用异常控制代码流程,也不是一个好主意,远比我们通常意义上的条件语句(if/else、switch)要低效。

  • Java 每实例化一个 Exception,都会对当时的栈进行快照,这是一个相对比较重的操作。如果发生的非常频繁,这个开销可就不能被忽略了。

    所以,对于部分追求极致性能的底层类库,有种方式是尝试创建不进行栈快照的 Exception。这本身也存在争议,因为这样做的假设在于,我创建异常时知道未来是否需要堆栈。问题是,实际上可能吗?小范围或许可能,但是在大规模项目中,这么做可能不是个理智的选择。如果需要堆栈,但又没有收集这些信息,在复杂情况下,尤其是类似微服务这种分布式系统,这会大大增加诊断的难度。

​ 当我们的服务出现反应变慢、吞吐量下降的时候,检查发生最频繁的 Exception 也是一种思路。关于诊断后台变慢的问题,我会在后面的 Java 性能基础模块中系统探讨。

​ 今天,我从一个常见的异常处理概念问题,简单总结了 Java 异常处理的机制。并结合代码,分析了一些普遍认可的最佳实践,以及业界最新的一些异常使用共识。最后,我分析了异常性能开销,希望对你有所帮助。

异常补充

异常机制

异常:程序在编译或运行过程中出现的错误
ThrowableJava中使用Throwable表示所有的异常
Java中异常分为两类:
1.Error:错误 一般是JVM或者是操作系统的问题
一旦发生,无法恢复
常见的错误:
IOError
VirtualMachineError
OutOfMemoryError [内存溢出或没有可用的内存提供给垃圾回收器时,抛出该错误]
StackOverflowError [当应用程序递归太深而发生堆栈溢出时,抛出该错误]

2.Exception:异常 主要是在程序运行期间发生的一些不正常事件中止了程序的运行,
可以通过JAVA异常处理机制捕获异常并处理,使得程序正常运行下去。
发生之后 可以捕获并处理的
常见的异常:
编译时异常:
IOException 输入输出流异常
FileNotFoundException 文件找不到的异常
ClassNotFoundException 类找不到异常
DataFormatException 数据格式化异常
NoSuchFieldException 没有匹配的属性异常
NoSuchMethodException 没有匹配的方法异常
SQLException 数据库操作异常
TimeoutException 执行超时异常


运行时异常: RuntimeException
ArrayIndexOutofBoundsException 数组越界异常
ClassCastException 类型转换异常
NullPointerException 空指针异常
IllegalAccessException 非法的参数异常
InputMismatchException 输入不匹配异常

Exception分两类 :
运行时异常, 不需要强制处理 所有的RuntimeException的子类都是运行时异常
编译时异常,需要强制处理 在Exception范围内,除了运行时异常的类都是编译时异常



如何处理编译时异常?
方法一:将需要处理的代码块放在一个try...catch...try{

//需要处理异常的代码
}catch(XXXException ef){
ef.printStackTrace();
}try一下,如果try能够执行完,就说明没有发生异常,catch就不工作
一旦在try的过程中出现异常,放弃执行try,马上转到执行catchcatch中可以捕获异常信息,根据异常信息进行补救措施
方法二:在出现异常的方法上 直接向上抛出异常
void ff() throws XXXException
[可以一直继续向上抛 直到主函数向上抛到JVM处理]

注意:在catchthrows的时候如果不确定是什么异常
就直接写一个Exception(老大)


如何处理运行时异常?
一般情况下,运行时异常是不用处理的
在某些情况下,如果对发生异常的结果进行处理,也可以对运行时异常进行
try...catch...


自定义异常:
当程序出现意外的时候,可以抛出异常对象来结束程序
抛出运行时异常对象
RuntimeException ef=new RuntimeException("下标越界index:"+index+"
size:"+size());
throw ef;

对于编译时异常,同样可以抛出异常对象
在方法定义的时候 必须throws
public void test(int t) throws Exception{
if (t < 0 || t > 100) {
Exception ef = new Exception("数据错误");
throw ef;
}
}

finally语句 
try所限定的代码中,当抛弃一个例外时,其后的代码不会被执行。通过finally语句可以

指定一块代码。无论try所指定的程序块中抛弃或不抛弃例外,也无论catch语句的例外类型

是否与所抛弃的例外的类型一致,finally所指定的代码都要被执行,它提供了统一的出口

。通常在finally语句中可以进行资源的清除工作。如关闭打开的文件等。


总结:

1.运行时异常和编译时异常的区别
编译时异常要求对它进行显式的try..catch 捕获处理或者向上一层方法抛出,否则在编译期
间就显示错误.而运行时异常在编译阶段不予检查,语法上不会显示任何错误。

2.throwsthrow的相同点和区别
区别:
1)throw 是手动抛出异常,throw new **Exception(); 抛出的是某一个异常类型的实例
2)throws 是方法抛出异常,写在方法声明处
public void show()throws **Exception.紧跟throws后的是异常类型,而非异常实例,且可以声明抛出多个异常,同时这些异常类型大多都为编译时异常类型。
3)throw 是程序员手动抛出异常,一般可用在某种流程控制,需要显示操作失误情况下可
对外抛出异常,进入catch代码块,明示操作有误等. 明确这个地方要抛出这个异常.
4)throws 方法抛出异常,通常是告知调用此方法者,本方法有可能抛出一个异常,在调用
时应当要进行异常监控。且因为throws方法抛出异常为编译时异常类型,这样在编译阶段就
要求用户调用时对编译时异常类型作出捕获[try..catch(){}语句块],或者再次向上抛出。
5)throws可以单独使用,但throw不能.throw要么和try-catch-finally语句配套使用,要么与throws配套使用。但throws可以单独使用,然后再由处理异常的方法捕获。
6)程序会在throw语句后立即终止,它后面的语句执行不到,然后在包含它的所有try块中
(可能在上层调用函数中)从里向外寻找含有与其匹配的catch子句的try块。

相同点:
只是抛出或者可能抛出异常,但是不会由函数去处理异常,真正的处理异常由函数的上层调
用处理。

3.如何对编译时异常进行处理
方法一:将需要处理的代码块放在一个try...catch...中
方法二:在出现异常的方法上 直接向上抛出异常 [可以一直继续向上抛 直到主函数向上抛到JVM处理]

//Error:
public class Demo {
	public static void main(String[] args) {
		Demo d = new Demo();
		d.change();       //StackOverflowError
 
//	while(true){
//	long[] a = new long[100000000];  //OutOfMemoryError
//     }
	}
	public void change(){
		change();
	   }	
  }

//try...catch{}
public class Demo2 {
 
	public static void main(String[] args) {
	//先try一下,如果try能够执行完,就说明没有发生异常,catch就不工作
	//一旦在try的过程中出现异常,放弃执行try,马上转到执行catch
	//在catch中可以捕获异常信息,根据异常信息进行补救措施
 
	      try{
		   //创建一个读取文件数据的对象
   FileOutputStream f = new FileOutputStream("C:\\Users\\kowloon\\Desktop\\");
	     }catch(Exception ex){
		//输出异常信息
		//ex.printStackTrace();
		System.out.println("找不到文件!!请想别的办法");
		}
	}
}

//throws
public class Demo3 {
	//在主函数中可以继续往上抛,抛给了JVM  
	public static void main(String[] args) throws Exception {
		Demo3 d = new Demo3();
		d.change();
	}	
	//将异常抛给调用change的方法处理  
	public void change()  throws Exception {
		read();
	}
	//将异常抛给调用read的方法处理   
	public void read()  throws Exception{
		FileOutputStream f = new FileOutputStream("C:\\Users\\kowloon\\Desktop\\");
	}
}

//运行时异常
 
public class Demo4 {
	public static void main(String[] args) {
 
	// 生成随机数
	Random rd = new Random();
	int num = rd.nextInt(100);
 
	Scanner sc = new Scanner(System.in);
	int t = -1;
 
	System.out.println("请输入您猜的数字:");
	do {
		try {
			// 获得输入的数字
			t = sc.nextInt();
		} catch (Exception ef) {
	System.out.println("必须输入0~100的数字,请重新输入您猜的数字:");
		sc = new Scanner(System.in);
		continue;
			}
 
			if (t < num) {
				System.out.println("太小了");
			} else if (t > num) {
				System.out.println("太大了");
			}
		} while (t != num);
		System.out.println("您猜对了:" + t);
	}
}

//自定义异常
public class Demo5 {
	//自定义运行时异常
	//在定义方法的时候,抛出运行时异常
	public void change(int t) {
	  if (t < 0 || t > 100) {
	    RuntimeException ef = new RuntimeException("数据错误");
	     throw ef;
		}
 
	}
	//自定义编译时异常
	//在定义方法的时候,抛出的不是运行时异常
	//在方法的后面,就必须要throws
	public void test(int t)  throws Exception{
		if (t < 0 || t > 100) {
			Exception ef = new Exception("数据错误");
			throw ef;
		}
	}
}
 
public class Demo5Test {
	public static void main(String[] args) {
		Demo5 d = new Demo5();
		d.change(200);
 
//      try {
//			d.test(200);
//		} catch (Exception ef) {
//			ef.printStackTrace();
//		}
 
	}
 
}

四、Java中的IO与NIO面试题

1、Java中的IO流
//参考 https://www.cnblogs.com/lanqingzhou/p/13609317.html

1.1 字符流和字节流。
在这里插入图片描述
1.2 字节流继承inputStream和OutputStream

1.3 字符流继承自InputSteamReader和OutputStreamWriter

1.4 总体结构图
在这里插入图片描述

//2.字节流和字符流哪个好?怎么选择?
	大多数情况下使用字节流会更好,因为大多数时候 IO 操作都是直接操作磁盘文件,所以这些流在传输时都是以字节的方式进行的(图片等都是按字节存储的)
如果对于操作需要通过 IO 在内存中频繁处理字符串的情况使用字符流会好些,因为字符流具备缓冲区,提高了性能
        
//3. 什么是缓冲区?有什么作用?
	缓冲区就是一段特殊的内存区域,很多情况下当程序需要频繁地操作一个资源(如文件或数据库)则性能会很低,所以为了提升性能就可以将一部分数据暂时读写到缓存区,以后直接从此区域中读写数据即可,这样就显著提升了性能。
	对于 Java 字符流的操作都是在缓冲区操作的,所以如果我们想在字符流操作中主动将缓冲区刷新到文件则可以使用 flush() 方法操作。

//4. 字符流和字节流有什么区别?
	字符流和字节流的使用非常相似,但是实际上字节流的操作不会经过缓冲区(内存)而是直接操作文本本身的,而字符流的操作会先经过缓冲区(内存)然后通过缓冲区再操作文件
        
//5. 什么是Java序列化,如何实现Java序列化?
	序列化就是一种用来处理对象流的机制,将对象的内容进行流化。可以对流化后的对象进行读写操作,可以将流化后的对象传输于网络之间。序列化是为了解决在对象流读写操作时所引发的问题
序列化的实现:将需要被序列化的类实现Serialize接口,没有需要实现的方法,此接口只是为了标注对象可被序列化的,然后使用一个输出流(如:FileOutputStream)来构造一个ObjectOutputStream(对象流)对象,再使用ObjectOutputStream对象的write(Object obj)方法就可以将参数obj的对象写出

//6. PrintStream、BufferedWriter、PrintWriter的比较?
	PrintStream类的输出功能非常强大,通常如果需要输出文本内容,都应该将输出流包装成PrintStream后进行输出。它还提供其他两项功能。与其他输出流不同,PrintStream 永远不会抛出 IOException;而是,异常情况仅设置可通过 checkError 方法测试的内部标志。另外,为了自动刷新,可以创建一个 PrintStream
BufferedWriter:将文本写入字符输出流,缓冲各个字符从而提供单个字符,数组和字符串的高效写入。通过write()方法可以将获取到的字符输出,然后通过newLine()进行换行操作。BufferedWriter中的字符流必须通过调用flush方法才能将其刷出去。并且BufferedWriter只能对字符流进行操作。如果要对字节流操作,则使用BufferedInputStream
	PrintWriter的println方法自动添加换行,不会抛异常,若关心异常,需要调用checkError方法看是否有异常发生,PrintWriter构造方法可指定参数,实现自动刷新缓存(autoflush)
//7. BufferedReader属于哪种流,它主要是用来做什么的,它里面有那些经典的方法?
	属于处理流中的缓冲流,可以将读取的内容存在内存里面,有readLine()方法,它,用来读取一行

//8. 什么是节点流,什么是处理流,它们各有什么用处,处理流的创建有什么特征?
	节点流 直接与数据源相连,用于输入或者输出
	处理流:在节点流的基础上对之进行加工,进行一些功能的扩展
	处理流的构造器必须要 传入节点流的子类
//9.流一般需要不需要关闭,如果关闭的话在用什么方法,一般要在那个代码块里面关闭比较好,处理流是怎么关闭的,如果有多个流互相调用传入是怎么关闭的?
	流一旦打开就必须关闭,使用close方法
	放入finally语句块中(finally 语句一定会执行)
	调用的处理流就关闭处理流
	多个流互相调用只关闭最外层的流
//10. InputStream里的read()返回的是什么,read(byte[] data)是什么意思,返回的是什么值?
	返回的是所读取的字节的int型(范围0-255)
	read(byte [ ] data)将读取的字节储存在这个数组。返回的就是传入数组参数个数
//11. OutputStream里面的write()是什么意思,write(byte b[], int off, int len)这个方法里面的三个参数分别是什么意思?
	write将指定字节传入数据源
	Byte b[ ]byte数组
	b[off]是传入的第一个字符、b[off+len-1]是传入的最后的一个字符 、len是实际长度
2、字节流如何转为字符流?
//参考 https://blog.csdn.net/qq_45722267/article/details/113756778

InputStreamReader

构造方法:

InputStreamReader(InputStream s);

参数s:要转换的字节流。

InputStreamReader(InputStream s , String charsetName);

s为待转换的字节流,charsetName为指定的字符编码

​ 将其转换后可直接使用字符流的方法进行读写。

InputStreamWriter

构造方法:

OutputStreamWriter(OutputStream s);

参数s:要转换的字节流。

OutputStreamWriter(OutputStream s , String charsetName);

s为待转换的字节流,charsetName为指定的字符编码

​ 将其转换后可直接使用字符流的方法进行读写。

3、常用io类有哪些
//参考 https://www.cnblogs.com/sunqian/p/5221818.html

在这里插入图片描述
字符流常用底层类为:

FileReader、FileWriter,BufferedReader、BufferedWriter,区别为缓存机制;

字节流常用底层类:

FileInputStream、FileOutputStream、BufferedInputStream、BufferedOutputStream,区别为缓存机制。

字符流,字节流转换类:

InputStreamReader、OutputStreamWriter,本身属于字符流体系,通过传入字节流和字符编码通过构造方法创建字符流实例。

此外,还有打印流,序列流,随机存取流等,慢慢研究吧!

java的IO类操作主要包括如下几类
1、File类的使用。
2、字节操作流:OutputStream、InputStream
3、字符操作流:Reader、Writer
4、对象序列化:serializable
(1)File类
从定义看,File类是Object的直接子类,同时它继承了Comparable接口可以进行数组的排序。
File类的操作包括文件的创建、删除、重命名、得到路径、创建时间等,以下是文件操作常用的函数。
(2)字节操作流(byte)
(1)字节输出流OutputStream
(2)字节输入流InputStream
(3)字符输出流Write
(4)字符输入流Reader
(5)字节流和字符流的区别(重点)

4、如何将一个 java 对象序列化到文件里?
链接:https://www.jianshu.com/p/cb970e5cd5b8

将对象序列化到文件

1)对象需要实现Seralizable接口
public class StudentBean implements Serializable {
······
}

2)通过ObjectOutputStreamwriteObject()方法写入
和ObjectInputStreamreadObject()方法来进行读取

//存进去
try {
    ObjectOutputStream os = new ObjectOutputStream(new FileOutputStream("D:/student.txt"));
    os.writeObject(studentList);
    os.close();
} catch (FileNotFoundException e) {
    // TODO Auto-generated catch block
    e.printStackTrace();
} catch (IOException e) {
    // TODO Auto-generated catch block
    e.printStackTrace();
}
//读出来
try {
    ObjectInputStream is = new ObjectInputStream(new FileInputStream("D:/student.txt"));
    ArrayList<StudentBean> list = new ArrayList<StudentBean>();
    list = (ArrayList<StudentBean>) is.readObject();
    for (int i = 0; i < list.size(); i++) {
    System.out.println(list.get(i).toString());
}
} catch (FileNotFoundException e) {
    // TODO Auto-generated catch block
    e.printStackTrace();
} catch (IOException e) {
    // TODO Auto-generated catch block
    e.printStackTrace();
} catch (ClassNotFoundException e) {
    // TODO Auto-generated catch block
    e.printStackTrace();
}
5、阻塞 IO 模型
//参考 https://blog.csdn.net/tjiyu/article/details/52959418

在这里插入图片描述

进程发起IO系统调用后,进程被阻塞,转到内核空间处理,整个IO处理完毕后返回进程。操作成功则进程获取到数据。

1、典型应用:阻塞socket、Java BIO;

2、特点:

进程阻塞挂起不消耗CPU资源,及时响应每个操作

实现难度低、开发应用较容易;

适用并发量小的网络应用开发;

不适用并发量大的应用:因为一个请求IO会阻塞进程,所以,得为每个请求分配一个处理进程(线程)以及时响应,系统开销大。

6、字节流和字符流的区别?
//参考 https://blog.csdn.net/qq_35122713/article/details/88793019

实际上字节流在操作时本身不会用到缓冲区(内存),是文件本身直接操作的,

而字符流在操作时使用了缓冲区,通过缓冲区再操作文件,如下图所示。

在这里插入图片描述
下面以两个写文件的操作为主进行比较,但是在操作时字节流和字符流的操作完成之后都不关闭输出流。

范例:使用字节流不关闭执行

package org.lxh.demo12.byteiodemo;    
import java.io.File;    
import java.io.FileOutputStream;    
import java.io.OutputStream;    
public class OutputStreamDemo05 {    
public static void main(String[] args) throws Exception {   // 异常抛出,  不处理    
// 第1步:使用File类找到一个文件    
     File f = new File("d:" + File.separator + "test.txt"); // 声明File  对象    
// 第2步:通过子类实例化父类对象    
     OutputStream out = null;            
// 准备好一个输出的对象    
     out = new FileOutputStream(f);      
// 通过对象多态性进行实例化    
// 第3步:进行写操作    
     String str = "Hello World!!!";      
// 准备一个字符串    
     byte b[] = str.getBytes();          
// 字符串转byte数组    
     out.write(b);                      
// 将内容输出    
 // 第4步:关闭输出流    
    // out.close();                  
// 此时没有关闭    
        }    
    } 

程序运行结果:
在这里插入图片描述
此时没有关闭字节流操作,但是文件中也依然存在了输出的内容,证明字节流是直接操作文件本身的。而下面继续使用字符流完成,再观察效果。

范例:使用字符流不关闭执行

package org.lxh.demo12.chariodemo;    
import java.io.File;    
import java.io.FileWriter;    
import java.io.Writer;    
public class WriterDemo03 {    
    public static void main(String[] args) throws Exception { // 异常抛出,  不处理    
        // 第1步:使用File类找到一个文件    
        File f = new File("d:" + File.separator + "test.txt");// 声明File 对象    
        // 第2步:通过子类实例化父类对象    
        Writer out = null;                 
// 准备好一个输出的对象    
        out = new FileWriter(f);            
// 通过对象多态性进行实例化    
        // 第3步:进行写操作    
        String str = "Hello World!!!";      
// 准备一个字符串    
        out.write(str);                    
// 将内容输出    
        // 第4步:关闭输出流    
        // out.close();                   
// 此时没有关闭    
    }    
}   

程序运行结果:
在这里插入图片描述
​ 程序运行后会发现文件中没有任何内容,这是因为字符流操作时使用了缓冲区,而 在关闭字符流时会强制性地将缓冲区中的内容进行输出,但是如果程序没有关闭,则缓冲区中的内容是无法输出的,所以得出结论:字符流使用了缓冲区,而字节流没有使用缓冲区。

提问:什么叫缓冲区?

​ 在很多地方都碰到缓冲区这个名词,那么到底什么是缓冲区?又有什么作用呢?

​ 回答:缓冲区可以简单地理解为一段内存区域。

可以简单地把缓冲区理解为一段特殊的内存。

​ 某些情况下,如果一个程序频繁地操作一个资源(如文件或数据库),则性能会很低,此时为了提升性能,就可以将一部分数据暂时读入到内存的一块区域之中,以后直接从此区域中读取数据即可,因为读取内存速度会比较快,这样可以提升程序的性能。

​ 在字符流的操作中,所有的字符都是在内存中形成的,在输出前会将所有的内容暂时保存在内存之中,所以使用了缓冲区暂存数据。

​ 如果想在不关闭时也可以将字符流的内容全部输出,则可以使用Writer类中的flush()方法完成。

范例:强制性清空缓冲区

package org.lxh.demo12.chariodemo;    
import java.io.File;    
import java.io.FileWriter;    
import java.io.Writer;    
public class WriterDemo04 {    
    public static void main(String[] args) throws Exception { // 异常抛出不处理    
        // 第1步:使用File类找到一个文件    
        File f = new File("d:" + File.separator + "test.txt");// 声明File    
对象    
        // 第2步:通过子类实例化父类对象    
        Writer out = null;                   
// 准备好一个输出的对象    
        out = new FileWriter(f);             
// 通过对象多态性进行实例化    
        // 第3步:进行写操作    
        String str = "Hello World!!!";      
// 准备一个字符串    
        out.write(str);                    
// 将内容输出    
        out.flush();                       
// 强制性清空缓冲区中的内容    
        // 第4步:关闭输出流    
        // out.close();                
// 此时没有关闭    
    }    
}   

程序运行结果:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-XPLOzGGn-1633919099927)(%E9%9D%A2%E8%AF%95%E7%AC%94%E8%AE%B0.assets/163055734.jpg)]
此时,文件中已经存在了内容,更进一步证明内容是保存在缓冲区的。这一点在读者日后的开发中要特别引起注意。

提问:使用字节流好还是字符流好?

​ 学习完字节流和字符流的基本操作后,已经大概地明白了操作流程的各个区别,那么在开发中是使用字节流好还是字符流好呢?

回答:使用字节流更好。

​ 在回答之前,先为读者讲解这样的一个概念,所有的文件在硬盘或在传输时都是以字节的方式进行的,包括图片等都是按字节的方式存储的,而字符是只有在内存中才会形成,所以在开发中,字节流使用较为广泛。

​ 字节流与字符流主要的区别是他们的的处理方式

流分类:
1.Java的字节流
InputStream是所有字节输入流的祖先,而OutputStream是所有字节输出流的祖先。
2.Java的字符流
Reader是所有读取字符串输入流的祖先,而writer是所有输出字符串的祖先。
InputStream,OutputStream,Reader,writer都是抽象类。所以不能直接new

​ 字节流是最基本的,所有的InputStream和OutputStream的子类都是,主要用在处理二进制数据,它是按字节来处理的
但实际中很多的数据是文本,又提出了字符流的概念,它是按虚拟机的encode来处理,也就是要进行字符集的转化
这两个之间通过 InputStreamReader,OutputStreamWriter来关联,实际上是通过byte[]和String来关联
在实际开发中出现的汉字问题实际上都是在字符流和字节流之间转化不统一而造成的

​ 在从字节流转化为字符流时,实际上就是byte[]转化为String时,
public String(byte bytes[], String charsetName)
有一个关键的参数字符集编码,通常我们都省略了,那系统就用操作系统的lang
而在字符流转化为字节流时,实际上是String转化为byte[]时,
byte[] String.getBytes(String charsetName)
也是一样的道理

​ 至于java.io中还出现了许多其他的流,按主要是为了提高性能和使用方便,如BufferedInputStream,PipedInputStream等。需注意,使用BufferedOutputStream输出数据时如果没有关闭流,数据也是不会输出到文件当中的,即并不是所有字节流都不用到缓冲区,输入缓冲字节流BufferedInputStream和输出缓冲字节流BufferedOutputStream还是要用到缓冲区的。

补充:
字符流处理的单元为2个字节的Unicode字符,分别操作字符、字符数组或字符串,而字节流处理单元为1个字节,操作字节和字节数组。所以字符流是由Java虚拟机将字节转化为2个字节的Unicode字符为单位的字符而成的,所以它对多国语言支持性比较好!如果是音频文件、图片、歌曲,就用字节流好点,如果是关系到中文(文本)的,用字符流好点. 所有文件的储存是都是字节(byte)的储存,在磁盘上保留的并不是文件的字符而是先把字符编码成字节,再储存这些字节到磁盘。在读取文件(特别是文本文件)时,也是一个字节一个字节地读取以形成字节序列. 1.字节流可用于任何类型的对象,包括二进制对象,而字符流只能处理字符或者字符串; 2. 字节流提供了处理任何类型的IO操作的功能,但它不能直接处理Unicode字符,而字符流就可以。 字节流与字符流主要的区别是他们的的处理方式

7、多路复用 IO 模型
//参考 https://blog.csdn.net/rekingman/article/details/104243041

​ IO复用全称为IO多路复用,这里做个简单的描述,IO理解为网络IO,多路意味着多通道、多连接,复用是指用一个线程、一组线程去处理多个通道的IO请求。进程通过将一个或者多个fd传递给select/poll调用,这样select/poll就能检测多个fd的状态,当有fd就绪时,立刻调用rollback回调函数,执行对应操作。
在这里插入图片描述
IO复用有多种实现机制,具体的实现机制在后面进行详细的说明。

8、如何实现对象克隆?
//参考 https://www.cnblogs.com/wanghx-0713/p/7879913.html

有两种方式:
1). 实现Cloneable接口并重写Object类中的clone()方法;
2). 实现Serializable接口,通过对象的序列化和反序列化实现克隆,可以实现真正的深度克隆,代码如下。

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
 
public class MyUtil {
 
    private MyUtil() {
        throw new AssertionError();
    }
 
    public static <T> T clone(T obj) throws Exception {
        ByteArrayOutputStream bout = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(bout);
        oos.writeObject(obj);
 
        ByteArrayInputStream bin = new ByteArrayInputStream(bout.toByteArray());
        ObjectInputStream ois = new ObjectInputStream(bin);
        return (T) ois.readObject();
 
        // 说明:调用ByteArrayInputStream或ByteArrayOutputStream对象的close方法没有任何意义
        // 这两个基于内存的流只要垃圾回收器清理对象就能够释放资源,这一点不同于对外部资源(如文件流)的释放
    }
}
import java.io.Serializable;
 
/**
 * 人类
 * @author 
 *
 */
class Person implements Serializable {
    private static final long serialVersionUID = -9102017020286042305L;
 
    private String name;    // 姓名
    private int age;        // 年龄
    private Car car;        // 座驾
 
    public Person(String name, int age, Car car) {
        this.name = name;
        this.age = age;
        this.car = car;
    }
 
    public String getName() {
        return name;
    }
 
    public void setName(String name) {
        this.name = name;
    }
 
    public int getAge() {
        return age;
    }
 
    public void setAge(int age) {
        this.age = age;
    }
 
    public Car getCar() {
        return car;
    }
 
    public void setCar(Car car) {
        this.car = car;
    }
 
    @Override
    public String toString() {
        return "Person [name=" + name + ", age=" + age + ", car=" + car + "]";
    }
 
}
/**
 * 小汽车类
 * @author 
 *
 */
class Car implements Serializable {
    private static final long serialVersionUID = -5713945027627603702L;
 
    private String brand;       // 品牌
    private int maxSpeed;       // 最高时速
 
    public Car(String brand, int maxSpeed) {
        this.brand = brand;
        this.maxSpeed = maxSpeed;
    }
 
    public String getBrand() {
        return brand;
    }
 
    public void setBrand(String brand) {
        this.brand = brand;
    }
 
    public int getMaxSpeed() {
        return maxSpeed;
    }
 
    public void setMaxSpeed(int maxSpeed) {
        this.maxSpeed = maxSpeed;
    }
 
    @Override
    public String toString() {
        return "Car [brand=" + brand + ", maxSpeed=" + maxSpeed + "]";
    }
 
}
class CloneTest {
 
    public static void main(String[] args) {
        try {
            Person p1 = new Person("Hao LUO", 33, new Car("Benz", 300));
            Person p2 = MyUtil.clone(p1);   // 深度克隆
            p2.getCar().setBrand("BYD");
            // 修改克隆的Person对象p2关联的汽车对象的品牌属性
            // 原来的Person对象p1关联的汽车不会受到任何影响
            // 因为在克隆Person对象时其关联的汽车对象也被克隆了
            System.out.println(p1);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
9、异步 IO 模型
//参考 https://blog.csdn.net/tjiyu/article/details/52959418

在这里插入图片描述
当进程发起一个IO操作,进程返回(不阻塞),但也不能返回果结;内核把整个IO处理完后,会通知进程结果。如果IO操作成功则进程直接获取到数据。

1、典型应用:JAVA7 AIO、高性能服务器应用

2、特点:

不阻塞,数据一步到位;Proactor模式;

需要操作系统的底层支持,LINUX 2.5 版本内核首现,2.6 版本产品的内核标准特性;

实现、开发应用难度大;

非常适合高性能高并发应用;

10、什么是 java 序列化,如何实现 java 序列化?
//参考 https://www.cnblogs.com/shoshana-kong/p/10538661.html

简要解释:
  序列化就是一种用来处理对象流的机制,所谓对象流也就是将对象的内容进行流化。可以对流化后的对象进行读写操作,也可将流化后的对象传输于网络之间。
  序列化是为了解决在对对象流进行读写操作时所引发的问题。序列化的实现:将需要被序列化的类实现Serializable接口,该接口没有需要实现的方法,implements Serializable只是为了标注该对象是可被序列化的,

然后使用一个输出流(如:FileOutputStream)来构造一个ObjectOutputStream(对象流)对象,接着,使用ObjectOutputStream对象的writeObject(Object obj)方法就可以将参数为obj的对象写出(即保存其状态),要恢复的话则用输入流。

详细解释:

 当两个进程在进行远程通信时,彼此可以发送各种类型的数据。无论是何种类型的数据,都会以**二进制序列**的形式在网络上传送。**发送方需要把这个Java对象转换为字节序列**,才能在网络上传送;**接收方则需要把字节序列再恢复为Java对象**。

只能将支持 java.io.Serializable 接口的对象写入流中。每个 serializable 对象的类都被编码,编码内容包括类名和类签名、对象的字段值和数组值,以及从初始对象中引用的其他所有对象的闭包

1.概念

序列化:把Java对象转换为字节序列的过程。
  反序列化:把字节序列恢复为Java对象的过程。

2.用途

对象的序列化主要有两种用途:
  1) 把对象的字节序列永久地保存到硬盘上,通常存放在一个文件中;
  2) 在网络上传送对象的字节序列。

3.对象序列化

序列化API

java.io.ObjectOutputStream代表对象输出流,它的writeObject(Object obj)方法可对参数指定的obj对象进行序列化,把得到的字节序列写到一个目标输出流中。只有实现了Serializable和Externalizable接口的类的对象才能被序列化。

java.io.ObjectInputStream代表对象输入流,它的readObject()方法从一个源输入流中读取字节序列,再把它们反序列化为一个对象,并将其返回。

代码示例

 1 import java.io.*;
 2 import java.util.Date;
 3 
 4 public class ObjectSaver {
 5     public static void main(String[] args) throws Exception {
 6         /*其中的  D:\\objectFile.obj 表示存放序列化对象的文件*/
 7 
 8         
 9         //序列化对象
10         ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("D:\\objectFile.obj"));
11         Customer customer = new Customer("王麻子", 24);    
12         out.writeObject("你好!");    //写入字面值常量
13         out.writeObject(new Date());    //写入匿名Date对象
14         out.writeObject(customer);    //写入customer对象
15         out.close();
16 
17         
18         //反序列化对象
19         ObjectInputStream in = new ObjectInputStream(new FileInputStream("D:\\objectFile.obj"));
20         System.out.println("obj1 " + (String) in.readObject());    //读取字面值常量
21         System.out.println("obj2 " + (Date) in.readObject());    //读取匿名Date对象
22         Customer obj3 = (Customer) in.readObject();    //读取customer对象
23         System.out.println("obj3 " + obj3);
24         in.close();
25     }
26 }
27 
28 class Customer implements Serializable {
29     private String name;
30     private int age;
31     public Customer(String name, int age) {
32         this.name = name;
33         this.age = age;
34     }
35 
36     public String toString() {
37         return "name=" + name + ", age=" + age;
38     }
39 }

执行结果
img

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值