2020年最全Java基础知识面试题

原文链接:www.zxcoding.cn
java 正则表达式
用 lamda 表达式
java 导出 word
队列 栈
通过反射改变 String 类的值
java 类型转换
volatile 关键字 读写锁
LinkedBlockingQueue
PriorityQueue
CopyOnWriteArrayList
ReadWriteLock
volatile
ConcurrentHashMap
线程状态
设计模式

  • java 基础

局部变量必须有初始值

  • 变量


public static String a = "";   //静态变量
public static final String b = "";  //静态常量

  • 数组

    真数组: 数组元素在内存中是一个接着一个线性存放的,通过第一个元素就能访问随后的元素,避免了数据覆盖的可能性,和数据类型覆盖并没有关系。

  • 字符串 String

    当创建 String 类型的对象时,虚拟机会在常量池中查找有没有已经存在的值和要创建的值相同的对象,如果有就把它赋给当前引用。如果没有就在常量池中重新创建一个 String 对象。

  • 通过反射来修改不可变的字符串

    用反射可以访问私有成员, 然后反射出String对象中的value属性, 进而改变通过获得的value引用改变数组的结构


// 创建字符串"Hello World", 并赋给引用s
String s = "Hello World";

System.out.println("s = " + s); // Hello World

// 1.获取String类中的value字段
Field valueFieldOfString = String.class.getDeclaredField("value");

// 2.改变value属性的访问权限
valueFieldOfString.setAccessible(true);

// 3.获取s对象上的value属性的值
char[] value = (char[]) valueFieldOfString.get(s);

// 4.改变value所引用的数组中的第5个字符
value[5] = '_';

System.out.println("s = " + s); // Hello_World

  • 常见比较

String str1 = "hello"; //str1指向静态区常量池
String str2 = new String("hello");  //str2指向堆上的对象
String str3 = "hello";
String str4 = new String("hello");
System.out.println(str1.equals(str2)); //true
System.out.println(str2.equals(str4)); //true
System.out.println(str1 == str3); //true
System.out.println(str1 == str2); //false
System.out.println(str2 == str4); //false
System.out.println(str2 == "hello"); //false
str2 = str1;
System.out.println(str2 == "hello"); //true

  • switch

    1. 支持的类型
      基本数据类型: byte short int char
      包装数据类型:Byte, Short, Character, Integer
      枚举类型:Enum
      字符串类型:String(Jdk 7+ 开始支持)
    2. switch 只支持整型,其他类型都是通过转换成整型进行匹配。特殊:String 类型是通过 equals 和 hashcode 方式实现的
  • 基本类型

    1. 1字节: byte , boolean
    2. 2字节: short , char
    3. 4字节: int , float
    4. 8字节: long , double
    - ### 基本数据类型的转换
    1.若参与运算的数据类型不同,则先转换成同一类型,然后进行运算。
    2.转换按数据长度增加的方向进行,以保证精度不降低。例如int型和long型运算时,先把int量转成long型后再进行运算。
    3.所有的浮点运算都是以双精度进行的,即使仅含float单精度量运算的表达式,也要先转换成double型,再作运算。
    4.char型和short型参与运算时,必须先转换成int型。
    5.在赋值运算中,赋值号两边的数据类型不同时,需要把右边表达式的类型将转换为左边变量的类型。如果右边表达式的数据类型长度比左边长时,将丢失一部分数据,这样会降低精度。
    
    ![image](https://imgconvert.csdnimg.cn/aHR0cDovL3VwbG9hZGZpbGVzLm5vd2NvZGVyLmNvbS9pbWFnZXMvMjAxNTA5MTcvNDE1NjExXzE0NDI0NTg2NjExMDZfRjRBNjJGREQyNTRGNzEwRjM5Mzc4Qzc1NEVENjVFNjE?x-oss-process=image/format,png)
    
  • 访问控制符

     private修饰的东西,只是不能在别的类中访问,但是本类中还是可以的。同时利用反射也可以做到。
    1. private : 在同一类内可见。使用对象:变量、方法。 注意:不能修饰类(外部类)
    2. default (即缺省,什么也不写,不使用任何关键字): 在同一包内可见,不使用任何修饰符。使用对象:类、接口、变量、方法。
    3. protected : 对同一包内的类和所有子类可见。使用对象:变量、方法。 注意:不能修饰类(外部类)。
    4. public : 对所有类可见。使用对象:类、接口、变量、方法
    
    ![image](https://imgconvert.csdnimg.cn/aHR0cHM6Ly9yYXcuZ2l0aHVidXNlcmNvbnRlbnQuY29tL0pvdXJXb24vaW1hZ2UvbWFzdGVyL0phdmElRTUlOUYlQkElRTclQTElODAlRTglQUYlQUQlRTYlQjMlOTUvSmF2YSVFOCVBRSVCRiVFOSU5NyVBRSVFNCVCRiVBRSVFOSVBNSVCMCVFNyVBQyVBNi5wbmc?x-oss-process=image/format,png)
    
  • final finally finalize 区别

    1. final可以修饰类、变量、方法,修饰类表示该类不能被继承、修饰方法表示该方法不能被重写、修饰变量表示该变量是一个常量不能被重新赋值
    2. finally一般作用在try-catch代码块中,在处理异常的时候,通常我们将一定要执行的代码方法finally代码块
    中,表示不管是否出现异常,该代码块都会执行,一般用来存放一些关闭资源的代码。
    3. finalize是一个方法,属于Object类的一个方法,而Object类是所有类的父类,该方法一般由垃圾回收器来调
    用,当我们调用System.gc() 方法的时候,由垃圾回收器调用finalize(),回收垃圾,一个对象是否可回收的
    最后判断。
    
  • this 与 super 的区别

    1. super: 它引用当前对象的直接父类中的成员(用来访问直接父类中被隐藏的父类中成员数据或函数,基类与派生类中有相同成员定义时如:super.变量名 super.成员函数据名(实参)
    2. this:它代表当前对象名(在程序中易产生二义性之处,应使用 this 来指明当前对象;如果函数的形参与类中的成员数据同名,这时需用 this 来指明成员变量名)
    3. super()和 this()类似,区别是,super()在子类中调用父类的构造方法,this()在本类内调用本类的其它构造方法。
    4. super()和 this()均需放在构造方法内第一行。
    5. 尽管可以用 this 调用一个构造器,但却不能调用两个。
    6. this 和 super 不能同时出现在一个构造函数里面,因为 this 必然会调用其它的构造函数,其它的构造函数必然也会有 super 语句的存在,所以在同一个构造函数里面有相同的语句,就失去了语句的意义,编译器也不会通过。
    7. this()和 super()都指的是对象,所以,均不可以在 static 环境中使用。包括:static 变量,static 方法,static 语句块。
      从本质上讲,this 是一个指向本对象的指针, 然而 super 是一个 Java 关键字。
  • 重载和重写

    • 重载要求: 1. 同一个类中,方法名相同,参数列表不同的 2 个或多个方法构成方法的重载 2. 参数列表不同指参数的类型,参数的个数,参数的顺序至少一项不同 3. 方法的返回值类型,方法的修饰符可以不同。
      • 重写要求:
        1. 父类中被 final 修饰的方法不能被重写
        2. 子类的重写方法不能比父类的被重写方法有更严格的访问级别,但可以更广泛。也就是 子类覆盖方法的访问权限必须大于等于 父类被重写方法的访问权限
        3. 方法名必须一样
        4. 参数列表必须一样。
        5. 返回值类型必须一样
        6. 重写方法不能抛出新的异常或者比被重写方法声明的检查异常更广的检查异常。但是可以抛出更少,更有限或者不抛出异常。
        7. 如果一个方法不能被继承,则不能重写它。最典型的例子为,被覆盖的方法不能为 private,否则在其子类中只是新定义了一个方法,并没有对其进行覆盖。
        8. 如果想调用父类被覆盖的方法,用 super 关键字调用
  • hashCode 与 equals (重要)

    • 重写equals方法,还必须要重写hashcode方法

      1. 使用hashcode方法提前校验,可以避免每一次比对都调用equals方法,提高效率
      2. 保证是同一个对象,如果重写了equals方法,而没有重写hashcode方法,会出现equals相等的对象,hashcode不相等的情况,重写hashcode方法就是为了避免这种情况的出现
      3. equals()相等的两个对象他们的hashCode()肯定相等,也就是用equals()对比是绝对可靠的
      4. hashCode()相等的两个对象他们的equal()不一定相等,也就是hashCode()不是绝对可靠的
  • 装箱与拆箱

  • 自动装箱和拆箱

    自动装箱都是通过包装类的 valueOf()方法来实现的.自动拆箱都是通过包装类对象的 xxxValue()来实现的

    Integer integer=1; //装箱  Integer integer=Integer.valueOf(1);
    int i=integer; //拆箱  int i=integer.intValue();
    

    包装对象的数值比较,不能简单的使用==,虽然-128 到 127 之间的数字可以,但是这个范围之外还是需要使用 equals 比较
    前面提到,有些场景会进行自动拆装箱,同时也说过,由于自动拆箱,如果包装类对象为 null,那么自动拆箱时就有可能抛出 NPE
    如果一个 for 循环中有大量拆装箱操作,会浪费很多资源

  • java 中的包装类型缓存机制

    有 ByteCache 用于缓存 Byte 对象
    有 ShortCache 用于缓存 Short 对象
    有 LongCache 用于缓存 Long 对象
    有 CharacterCache 用于缓存 Character 对象
    有 IntegerCache 用户缓存 Integer 对象:-128 到 127.可以通过 XX:AutoBoxCacheMax=size 或者
    java.lang.Integer.IntegerCache.high 修改
    Byte, Short, Long 有固定范围: -128 到 127
    对于 Character, 范围是 0 到 127.除了 Integer 以外,这个范围都不能改变.

  • 集合类

  • List 与 Map

    List 接口和 Set 接口都继承自 Collection 接口,Collection 接口继承 Iterable 接口(Iterable 有一个 Iterator 方法),即可迭代的
    Collection 只能存储引用类型,并且是单个存储
    List 接口存储元素特点:有序(存进去什么顺序取出来还什么顺序),可重复
    Set 接口存储元素特点:无序,不可重复
    实现 List 接口主要的类包括 ArrayList,LinkedList,Vector;实现 Set 的主要类包括:hashSet,另外还有一个 TreeSet 接口继承它(自动排序)
    Map 接口以键值对方式存储元素,键无序不可重复,Map 和 Collection 没有任何关系

  • Collection 和 Collections 区别

    Collection 是一个集合接口. 它提供了对集合对象进行基本操作的通用接口方法.Collection 接口在 Java 类库中有很多具体的实现.是 list,set 等的父接口.
    Collections 是一个包装类. 它包含有各种有关集合操作的静态多态方法.此类不能实例化,就像一个工具类,服务于 Java 的 Collection 框架.

  • Set 和 List 区别

    List,Set 都是继承自 Collection 接口.都是用来存储一组相同类型的元素的.
    不同:
    List 特点:元素有放入顺序,元素可重复.有顺序,即先放入的元素排在前面
    Set 特点:元素无放入顺序,元素不可重复.

  • ArrayList&&LinkedList,Vector

    ArrayList 是实现了基于动态数组的数据结构,LinkedList 基于链表的数据结构
    这三者都实现了 List 接口,使用方式也很相似,主要区别在于因为实现方式的不同,所以对不同的操作具有不同的效率.
    Vector 和 ArrayList 在更多元素添加进来时会请求更大的空间.Vector 每次请求其大小的双倍空间,而 ArrayList 每次对 size 增长 50%.
    而 LinkedList 还实现了 Queue 接口,该接口比 List 提供了更多的方法,包括 offer(),peek(),poll()等.

  • 队列

    1. add() 和 offer()
      add() : 添加元素,如果添加成功则返回 true,如果队列是满的,则抛出异常
      offer() : 添加元素,如果添加成功则返回 true,如果队列是满的,则返回 false
    2. remove() 和 poll()
      remove() : 移除队列头的元素并且返回,如果队列为空则抛出异常
      poll() : 移除队列头的元素并且返回,如果队列为空则返回 null
    3. element() 和 peek()
      element() :返回队列头元素但不移除,如果队列为空,则抛出异常
      peek() :返回队列头元素但不移除,如果队列为空,则返回 null
    1. pop 和 peek
      相同点:都返回栈顶的值。
      不同点:peek 不改变栈的值(不删除栈顶的值),pop 会把栈顶的值删除。
  • SynchronizedList 和 Vector 最主要的区别

    1. SynchronizedList 有很好的扩展和兼容功能.他可以将所有的 List 的子类转成线程安全的类
    2. 使用 SynchronizedList 的时候,进行遍历时要手动进行同步处理
    3. SynchronizedList 可以指定锁定的对象
  • Set 如何保证元素不重复

    TreeSet 是二叉树实现的,Treeset中的数据是自动排好序的,不允许放入null值 2、HashSet 是哈希表实现的,HashSet中的数据是无序的,可以放入null,但只能放入一个null,两者中的值都不能重复,就如数据库中唯一约束
    
    TreeSet的底层是TreeMap的keySet(),而TreeMap是基于红黑树实现的,红黑树是一种平衡二叉查找树,它能保证任何一个节点的左右子树的高度差不会超过较矮的那棵的一倍.
    
    在HashSet中,基本的操作都是有HashMap底层实现的,因为HashSet底层是用HashMap存储数据的.当向HashSet中添加元素的时候,首先计算元素的hashcode值,然后通过扰动计算和按位与的方式计算出这个元素的存储位置,如果这个位置位空,就将元素添加进去;如果不为空,则用equals方法比较元素是否相等,相等就不添加,否则找一个空位添加.(HashMap 支持key=null 但是 Hashtable 不支持 key =null)
    
    TreeMap是按key排序的,元素在插入TreeSet时compareTo()方法要被调用,所以TreeSet中的元素要实现Comparable接口.TreeSet作为一种Set,它不允许出现重复元素.TreeSet是用compareTo()来判断重复元素的.
    
  • HashMap和HashTable有何不同

    1. 线程安全
    HashTable 中的方法是同步的,而HashMap中的方法在默认情况下是非同步的.在多线程并发的环境下,可以直接使用HashTable,但是要使用HashMap的话就要自己增加同步处理了
    2. 继承关系
    HashTable是基于陈旧的Dictionary类继承来的. HashMap继承的抽象类AbstractMap实现了Map接口
    3. 允不允许null值
    HashTable中,key和value都不允许出现null值,否则会抛出NullPointerException异常. HashMap中,null可以作为键,这样的键只有一个;可以有一个或多个键所对应的值为null
    4. 默认初始容量和扩容机制
    HashTable中的hash数组初始大小是11,增加的方式是 old*2+1.HashMap中hash数组的默认大小是16,而且一定是2的指数
    5. 哈希值的使用不同
    HashTable直接使用对象的hashCode. HashMap重新计算hash值
    6. 遍历方式的内部实现上不同
    Hashtable、HashMap都使用了 Iterator.而由于历史原因,Hashtable还使用了Enumeration的方式 . HashMap 实现 Iterator,支持fast-fail,Hashtable的 Iterator 遍历支持fast-fail,用 Enumeration 不支持 fast-fail
    
    • HashMap 和 ConcurrentHashMap 的区别

    ConcurrentHashMap和HashMap的实现方式不一样,虽然都是使用桶数组实现的,但是还是有区别,ConcurrentHashMap对桶数组进行了分段,而HashMap并没有
    ConcurrentHashMap在每一个分段上都用锁进行了保护.HashMap没有锁机制.所以,前者线程安全的,后者不是线程安全的

    • Properties 类 继承了 Hashtable 类,而 Hashtable 类则继承Dictionary 类

  • CopyOnWriteArrayList 适用于写少读多的并发场景

  • ReadWriteLock 即为读写锁,他要求写与写之间互斥,读与写之间互斥,

  • 读与读之间可以并发执行在读多写少的情况下可以提高效率

  • ConcurrentHashMap 是同步的 HashMap,读写都加锁

  • volatile 只保证多线程操作的可见性,不保证原子性

  • 抽象类

    抽象类是可以实现接口的,而且抽象类也可以继承自抽象类
    抽象类指有 abstract 修饰的 class,其可以包含抽象方法,也可以不包含
    抽象类和接口都是不能被实例化的,只有具体的类才可以被实例化

  • 抽象类和接口
    相同点:都不能被实例化,位于继承树的顶端,都包含抽象方法
    不同点:

    • 设计目的:
      接口体现的一种规范,类似与整个系统的总纲,制订了系统各模块应该遵循的标准,因此接口不应该经常改变,一旦改变对整个系统是辐射性的
      抽象类作为多个子类的共同父类,体现的是一种模板式设计,可以当作系统实现过程中的中间产品,已经实现了系统部分功能
    • 使用不同:
      接口只能包含抽象方法,抽象类可以包含普通方法
      接口里不能定义静态方法,抽象类可以
      接口只能定义静态常量属性不能定义普通属性,抽象类可以
      接口不包含构造器,抽象类可以(不是用于创建对象而是让子类完成初始化)
      接口里不能包含初始化块,抽象类完全可以
      接口多继承,抽象类单继承(只能有一个直接父类)
      一个类可以实现多个接口但是只能实现一个类
    • 总结:
      接口所有方法全是抽象方法只能 public abstract 修饰 (默认 public abstract 修饰 ),属性默认 public static final 修饰
      抽象类除了包含抽象方法外与普通类无区别
      抽象类和接口都可以定义静态成员变量,只是接口的静态成员变量要用static final public 来修饰
      抽象类可以含有私有成员变量,接口不含有私有成员变量
      抽象类中的方法是可以有方法体的。JDK1.8之后,接口中的方法也可以有方法体,用default关键字修饰方法
  • native 关键字表名修饰的方法是由其它非 Java 语言编写的

  • final

    final 修饰符,可以修饰类,方法,变量,不能修饰接口
    final 修饰的类不可以被继承
    final 修饰的方法不可以被覆盖
    final 修饰的变量为常量只能赋值一次
    一般 final 化的成员变量也会静态化

  • java 和 C++都有三个特征:封装、继承和多态

  • 继承

    如果父类中只有有参构造函数,则子类构造函数必须先显示调用即父类没有无参的构造函数,所以子类需要在自己的构造函数中显式调用父类的构造函数,
    执行父类的带参构造前要先对父类中的对象进行初始化

  • 内部类

    在 Java 中,可以将一个类定义在另一个类里面或者一个方法里边,这样的类称为内部类,广泛意义上的内部类一般包括四种:成员内部类,局部内部类,匿名内部类,静态内部类

    • 成员内部类

      该类像是外部类的一个成员,可以无条件的访问外部类的所有成员属性和成员方法(包括 private 成员和静态成员);
      成员内部类拥有与外部类同名的成员变量时,会发生隐藏现象,即默认情况下访问的是成员内部类中的成员如果要访问外部类中的成员,需要以下形式访问:【外部类.this.成员变量 或 外部类.this.成员方法】;
      在外部类中如果要访问成员内部类的成员,必须先创建一个成员内部类的对象,再通过指向这个对象的引用来访问;
      成员内部类是依附外部类而存在的,也就是说,如果要创建成员内部类的对象,前提是必须存在一个外部类的对象;创建方法为 外部类实例.new 内部类()
      内部类可以拥有 private 访问权限、protected 访问权限、public 访问权限及包访问权限如果成员内部类用 private 修饰,则只能在外部类的内部访问;如果用 public 修饰,则任何地方都能访问;如果用 protected 修饰,则 只能在同一个包下或者继承外部类的情况下访问;如果是默认访问权限,则只能在同一个包下访问外部类只能被 public 和包访问两种权限修饰

      class Circle {
           private double radius = 0;
           public static int count =1;
           public Circle(double radius) {
               this.radius = radius;
           }
      
           class Draw {     //内部类
               public void drawSahpe() {
                   System.out.println(radius);  //外部类的private成员
                   System.out.println(count);   //外部类的静态成员
               }
           }
       }
       Circle circle=new Circle();
       Circle.Draw draw=circle.new Draw(); //创建成员内部类实例
       draw.drawSahpe();
      
    • 局部内部类

      局部内部类是定义在一个方法或者一个作用域里面的类,它和成员内部类的区别在于局部内部类的访问仅限于方法内或者该作用域内;
      局部内部类就像是方法里面的一个局部变量一样,是不能有 public、protected、private 以及 static 修饰符的

      class People{
           public People() {
      
           }
       }
      
       class Man{
           public Man(){
      
           }
      
           public People getWoman(){
               class Woman extends People{   //局部内部类
                   int age =0;
               }
               return new Woman();
           }
       }
      
    • 匿名内部类

      一般使用匿名内部类的方法来编写事件监听代码; 匿名内部类必须继承或实现一个已有的接口
      匿名内部类是不能有访问修饰符和 static 修饰符的;
      匿名内部类是唯一一种没有构造器的类;
      匿名内部类用于继承其他类或是实现接口,并不需要增加额外的方法,只是对继承方法的实现或是重写

      history_bt.setOnClickListener(new OnClickListener() {
      
           @Override
           public void onClick(View v) {
               // TODO Auto-generated method stub
      
           }
       });
      
    • 内部静态类

      静态内部类是不需要依赖于外部类的,这点和类的静态成员属性有点类似;
      不能使用外部类的非 static 成员变量或者方法
      内部类可以是静态 static 的,也可用 public,default,protected 和 private 修饰,
      外部类的修饰符只能是 public,abstract,final
      静态内部类可以访问外部类所有的静态变量,而不可访问外部类的非静态变量;静态内部类的创建方式,new 外部类.静态内部类()

      public class Test {
        public static void main(String[] args)  {
            Outter.Inner inner = new Outter.Inner();
        }
      }
      
      class Outter {
        public Outter() {
      
        }
      
        static class Inner {
            public Inner() {
      
            }
      
  • 线程相关

    • 线程中断或停止运行:

      InterruptedException 异常被捕获
      线程调用了 wait 方法
    • 线程销毁

      run()方法的执行结束
    • start 方法和 run 方法的区别

      1. start 方法
        用 start方法来启动线程,是真正实现了多线程, 通过调用Thread类的start()方法来启动一个线程,这时此线程处于就绪(可运行)状态,
        并没有运行,一旦得到cpu时间片,就开始执行run()方法。但要注意的是,此时无需等待run()方法执行完毕,即可继续执行下面的代码。所以run()方法并没有实现多线程。
      
      1. run 方法
          run()方法只是类的一个普通方法而已,如果直接调用Run方法,程序中依然只有主线程这一个线程,其程序执行路径还是只有一条,还是要顺序执行,还是要等待run方法体执行完毕后才可继续执行下面的代码。
      
    • 让线程停止执行

      1. sleep(): sleep 只是让线程休眠一会.线程结束休眠后会进入 Runnable 状态,也就是停止执行了。要再次被调度程序选中然后获得 cpu 时间片才会被执行
      2. synchronized(): synchronized 修饰的方法和类,会让没抢到锁的线程停止执行
      3. yield(): 让当前执行的线程让出 cpu 时间片(并不是线程本身的方法),也算让线程停止执行了
    • 开启线程

      1. 继承 Thread 类创建线程
      2. 实现 Runnable 接口创建线程
      3. 通过 Callable 和 FutureTask 创建线程
      4. 通过线程池创建线程
        CyclicBarrier让一组线程等待其他线程;CountDownLatch让一组线程等待某个事件发生。
  • threadlocal

    • ThreadLocal 存放的值是线程封闭,线程间互斥的,主要用于线程内共享一些数据,避免通过参数来传递
    • 从线程的角度看,每个线程都保持一个对其线程局部变量副本的隐式引用,只要线程是活动的并且 ThreadLocal 实例是可访问的;在线程消失之后,其线程局部实例的所有副本都会被垃圾回收
    • 在 Thread 类中有一个 Map,用于存储每一个线程的变量的副本
    • 对于多线程资源共享的问题,同步机制采用了“以时间换空间”的方式,而 ThreadLocal 采用了“以 空间换时间”的方式
  • 调用构造方法

    1. 在类的其他构造方法中可以用 this()的方式调用其他构造方法;
    2. 在类的子类中则可以通过 super 调用父类中指定的构造方法;
    3. 在反射中可以使用 newInstance()的方式调用
    Person person=(Person) Class.forName("com.Person").newInstance(); //调用无参构造函数
    Constructor  constructor=Person.class.getConstructor (); //调用有参构造函数
    Person person=constructor.newInstance();
    
    Class.newInstance() 只能够调用无参的构造函数,即默认的构造函数;
    Constructor.newInstance() 可以根据传入的参数,调用任意构造构造函数。
    Class.newInstance() 抛出所有由被调用构造函数抛出的异常。
    Class.newInstance() 要求被调用的构造函数是可见的,也即必须是public类型的;
    Constructor.newInstance() 在特定的情况下,可以调用私有的构造函数
    
  • 异常

    异常分为 Error 和 Exception。Exception 和 Error, ⼆者都是 Java 异常处理的重要⼦类, 各⾃都包含⼤量⼦类。均继承自 Throwable 类。

    • 区别

      1. Error 表⽰系统级的错误, 是 java 运⾏环境内部错误或者硬件问题, 不能指望程序来处理这样的问题, 除了退出运⾏外别⽆选择, 它是 Java 虚拟机抛出的
      2. Exception 表⽰程序需要捕捉、 需要处理的常, 是由与程序设计的不完善⽽出现的问题, 程序必须处理的问题。
    • 异常类型

      1. 受检异常
        那么,当我们在程序中调⽤他的时候, ⼀定要对该异常进⾏处理( 捕获或者向上抛出) , 否则是⽆法编译通过的。 这是⼀种强制规范。
      public void test() throw new Exception{ }
      
      这种异常在 IO 操作中⽐较多。 ⽐如 FileNotFoundException , 当我们使⽤ IO 流处理⼀个⽂件的时候, 有⼀种特殊情况, 就是⽂件不存在, 所以, 在⽂件处理的接⼜定义时他会显⽰抛出 FileNotFoundException, ⽬的就是告诉这个⽅法的调⽤者,我这个⽅法不保证⼀定可以成功, 是有可能找不到对应的⽂件 的, 你要明确的对这种情况做特殊处理哦。
      所以说, 当我们希望我们的⽅法调⽤者, 明确的处理⼀些特殊情况的时候, 就应该使⽤受检异常。
      2. 非受检异常
      对于⾮受检异常来说, ⼀般是运⾏时异常, 继承⾃ RuntimeException。 在编写代码的时候, 不需要显⽰的捕获,但是如果不捕获, 在运⾏期如果发⽣异常就会中断程序的执⾏。
      这种异常⼀般可以理解为是代码原因导致的。 ⽐如发⽣空指针、 数组越界等。 所以, 只要代码写的没问题, 这些异常都是可以避免的。 也就不需要我们显⽰的进⾏处理。
      试想⼀下, 如果你要对所有可能发⽣空指针的地⽅做异常处理的话, 那相当于你的所有代码都需要做这件事。
    • 异常相关关键字

      throws、 throw、 try、 catch、 finally
      try ⽤来指定⼀块预防所有异常的程序;
      catch ⼦句紧跟在 try 块后⾯, ⽤来指定你想要捕获的异常的类型;
      finally 为确保⼀段代码不管发⽣什么异常状况都要被执⾏;
      throw 语句⽤来明确地抛出⼀个异常;
      throws ⽤来声明⼀个⽅法可能抛出的各种异常;
    • 总结

      Java的异常分为两种,一种是运行时异常(RuntimeException),一种是非运行异常也叫检查式异常(CheckedException)。
    1. 运行时异常(非受检异常)不需要程序员去处理,当异常出现时,JVM会帮助处理。常见的运行时异常有:
      ClassCastException(类转换异常)
      ClassNotFoundException
      IndexOutOfBoundsException(数组越界异常)
      NullPointerException(空指针异常)
      ArrayStoreException(数组存储异常,即数组存储类型不一致)
      还有IO操作的BufferOverflowException异常
    2. 非运行异常(受检异常)需要程序员手动去捕获或者抛出异常进行显示的处理,因为Java认为Checked异常都是可以被修复的异常。常见的异常有:
      IOException
      SqlException
  • 类方法和对象方法

    1. 类方法中可以直接调用本类的类方法
    2. 类方法属于整个类,实例方法属于某个对象
    • 类方法的对象方法的差异

      1. 类方法不能引用对象变量;
      2. 类方法不能调用类的对象方法;
      3. 类方法中不能有 this.和 super 关键字
      4. 类方法不可以被覆盖
    • 对象方法

      1. 可以引用对象变量,引用类变量
      2. 对象方法可以调用类方法
      3. 对象方法中可以使用 super.this 关键字
  • 向上转型和向下转型

    1. Java 中的 byte,short,char 进行计算时都会提升为 int 类型计算会向上转型
    2. 继承中的转型
       B extends A
       B b = (B) new A();   // 子类的引用 执行父类的对象 需要强转
       A a = new B();   //里氏替换原则 可以用父类的地方都可以用子类
      
  • JVM

    Image

    • 一. JVM分区

    1. 新生代:
      所有对象创建在新生代的 Eden 区,当 Eden 区满后触发新生代的 Minor GC,将 Eden 区和非空闲 Survivor 区存活的对象复制到另外一个空闲的 Survivor 区中
      保证一个 Survivor 区是空的,新生代 Minor GC 就是在两个 Survivor 区之间相互复制存活对象,直到 Survivor 区满为止
    2. 老年代:
      当 Survivor 区也满了之后就通过 Minor GC 将对象复制到老年代老年代也满了的话,就将触发 Full GC,针对整个堆(包括新生代、老年代、持久代)进行垃圾回收
    3. 持久代:
      持久代如果满了,将触发 Full GC
    • 二. 栈

    存放了编译时期生成的各种字面量
    存放编译时期生成的符号引用

  • 静态常量池和运行时常量池

    • 静态常量池

     所谓静态常量池,即*.class文件中的常量池,class文件中的常量池不仅仅包含字符串(数字)字面量,还包含类、方法的信息,占用class文件绝大部分空间。这种常量池主要用于存放两大类常量:字面量(Literal)和符号引用量(Symbolic References),字面量相当于Java语言层面常量的概念,如文本字符串,声明为final的常量值等,符号引用则属于编译原理方面的概念,包括了如下三种类型的常量:
     类和接口的全限定名
     字段名称和描述符
     方法名称和描述符
    
    • 运行常量池

    而运行时常量池,则是jvm虚拟机在完成类装载操作后,将class文件中的常量池载入到内存中,并保存在方法区中,我们常说的常量池,就是指方法区中的运行时常量池
    运行时常量池大小受方法区大小的影响

  • subString

    1. 不同版本的区别
      1.6: 当调用 substring 方法的时候,会创建一个新的 string 对象,但是这个 string 的值仍然指向堆中的同一个字符数组.问题:导致内存泄漏
    内存泄露:在计算机科学中,内存泄漏指由于疏忽或错误造成程序未能释放已经不再使用的内存. 内存泄漏并非指内存在物理上的消失,而是应用程序分配某段内存后,由于设计错误,导致在释放该段内存之前就失去了对该段内存的控制,从而造成了内存的浪费.
    

    以下代码会导致内存泄漏:

    String str = "abcdefghijklmnopqrst";
    String sub = str.substring(1, 3);
    str = null;
    

    原因:
    这段简单的程序有两个字符串变量 str、sub。sub 字符串是由父字符串 str 截取得到的,假如上述这段程序在 JDK1.6 中运行,我们知道数组的内存空间分配是在堆上进行的,那么 sub 和 str 的内部 char 数组 value 是公用了同一个,也就是上述有字符 a~字符 t 组成的 char 数组,str 和 sub 唯一的差别就是在数组中其实 beginIndex 和字符长度 count 的不同。在第三句,我们使 str 引用为空,本意是释放 str 占用的空间,但是这个时候,GC 是无法回收这个大的 char 数组的,因为还在被 sub 字符串内部引用着,虽然 sub 只截取这个大数组的一小部分。当 str 是一个非常大字符串的时候,这种浪费是非常明显的,甚至会带来性能问题,解决这个问题可以是通过以下的方法:

    String str = "abcdefghijklmnopqrst";
    String sub = str.substring(1, 3) + "";
    /*利用的就是字符串的拼接技术,它会创建一个
    新的字符串,这个新的字符串会使用一个新的内部char数组存储自己实际需要的字符,这样父数
    组的char数组就不会被其他引用,令str=null,在下一次GC回收的时候会回收整个str占用的空间*/
    str = null;
    

    1.7: 在 jdk 7 中得到解决.在 jdk 7 中,substring 方法会在堆内存中创建一个新的数组

  • String,StringBuffer 和 StringBuilder

    1. StringBuffer 是线程安全的
    2. "+"是默认调用的 StringBuilder.append
    3. 直接使用 StringBuilder 的方式是效率最高的.因为 StringBuilder 天生就是设计来定义可变字符串和字符串的变化操作的
    4. String i1 = “” + i 解释为
    String i1 = (new StringBuilder()).append(i).toString();
    
    • 字符串拼接的方式

      1. concat
      2. StringBuilder
      3. StringBuffer
      4. StringUtils.join
        注意:
    1. 如果不是在循环体中进行字符串拼接的话,直接使用+就好了
    2. 如果在并发场景中进行字符串拼接的话,要使用 StringBuffer 来代替 StringBuilder
  • 关键字

    transient

    transient 关键字的作⽤是控制变量的序列化, 在变量声明前加上该关键字, 可以阻⽌该变量被序列化到⽂件中, 在被反序列化后, transient 变量的值被设为初始值, 如 int 型的是 0,对象型的是 null。

    instanceof

    volatile

    volatile 能保证数据的可见性,但不能完全保证数据的原子性,synchronized 即保证了数据的可见性也保证了原子性

    synchronized

    final

    static

    const

  • IO流

    • 按照流的流向分,可以分为输入流和输出流;
    • 按照操作单元划分,可以划分为字节流和字符流;
    • 按照流的角色划分为节点流和处理流。
    • InputStream/Reader: 所有的输入流的基类,前者是字节输入流,后者是字符输入流。
      OutputStream/Writer: 所有输出流的基类,前者是字节输出流,后者是字符输出流。
  • 字符流和字节流

    • 字节与字符

      1. Bit(比特)最小的二进制单位 ,是计算机的操作部分。取值 0 或者 1
      2. Byte(字节)是计算机操作数据的最小单位由 8 位 bit 组成 取值(-128-127)
      3. Char(字符)是用户的可读写的最小单位,在 Java 里面由 16 位 bit 组成 取值(0-65535)
        1 字符=2 字节
    • 字节流

      操作 byte 类型数据,主要操作类是 OutputStream、InputStream 的子类;不用缓冲区,直接对文件本身操作
      流对象是字节流的类有:FileInputStream
      BufferedInputStream
      PushbackInputStream
      ByteArrayInputStream

    • 字符流

      操作字符类型数据,主要操作类是 Reader、Writer 的子类;使用缓冲区缓冲字符,不关闭流就不会输出任何内容

    • 互相转换

      1. 整个 IO 包实际上分为字节流和字符流,但是除了这两个流之外,还存在一组字节流-字符流的转换类
      2. OutputStreamWriter:是 Writer 的子类,将输出的字符流变为字节流,即将一个字符流的输出对象变为字节流输出对象
      3. InputStreamReader:是 Reader 的子类,将输入的字节流变为字符流,即将一个字节流的输入对象变为字符流的输入对象
  • BIO、NIO 和 AIO 的区别、三种 IO 的用法与原理

    • IO

    1. BIO 方式适用于连接数目比较小且固定的架构,这种方式对服务器资源要求比较高,并发局限于应用中,JDK1.4 以前的唯一选择,但程序直观简单易理解。

    2. NIO 方式适用于连接数目多且连接比较短(轻操作)的架构,比如聊天服务器,并发局限于应用中,编程比较复杂,JDK1.4 开始支持。

    3. AIO 方式适用于连接数目多且连接比较长(重操作)的架构,比如相册服务器,充分调用 OS 参与并发操作,编程比较复杂,JDK7 开始支持。

    • BIO

      BIO 就是传统的 java.io 包下面的代码实现.Java BIO 即 Block I/O , 同步并阻塞的 IO
      同步阻塞 I/O 模式,数据的读取写入必须阻塞在一个线程内等待其完成。这里假设一个烧开水的场景,有一排水壶在烧开水,BIO 的工作模式就是, 叫一个线程停留在一个水壶那,直到这个水壶烧开,才去处理下一个水壶。但是实际上线程在等待水壶烧开的时间段什么都没有做。
    • NIO

      原来的 I/O 以流的方式处理数据,而 NIO 以块的方式处理数据
      面向流 的 I/O 系统一次一个字节地处理数据。一个输入流产生一个字节的数据,一个输出流消费一个字节的数据。
      同时支持阻塞与非阻塞模式,但这里我们以其同步非阻塞 I/O 模式来说明,那么什么叫做同步非阻塞?如果还拿烧开水来说,NIO 的做法是叫一个线程不断的轮询每个水壶的状态,看看是否有水壶的状态发生了改变,从而进行下一步的操作。
    • AIO

      Java AIO 即 Async 非阻塞,是异步非阻塞的 IO。
      异步非阻塞 I/O 模型。异步非阻塞与同步非阻塞的区别在哪里?异步非阻塞无需一个线程去轮询所有 IO 操作的状态改变,在相应的状态改变后,系统会通知对应的线程来处理。对应到烧开水中就是,为每个水壶上面装了一个开关,水烧开之后,水壶会自动通知我水烧开了。
  • Files类的常用方法

    Files. exists():检测文件路径是否存在。
    Files. createFile():创建文件。
    Files. createDirectory():创建文件夹。
    Files. delete():删除一个文件或目录。
    Files. copy():复制文件。
    Files. move():移动文件。
    Files. size():查看文件个数。
    Files. read():读取文件。
    Files. write():写入文件。

  • 反射

    反射机制指的是程序在运行时能够获取自身的信息
    JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;
    对于任意一个对象,都能够调用它的任意一个方法和属性;
    这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制

  • 反射的作用

    1. 在运行时判断任意一个对象所属的类。
    2. 在运行时判断任意一个类所具有的成员变量和方法。
    3. 在运行时任意调用一个对象的方法
    4. 在运行时构造任意一个类的对象
  • java获取反射的三种方式

    1. 通过new对象实现反射机制
      - 通过Object类的getClass方法来获取
    2. 通过路径实现反射机制
      - 使用.class的方式
    3. 通过类名实现反射机制
      - 使用Class.forName方法
  • Java反射中getDeclaredField和getField的区别

getDeclaredField是可以获取一个类的所有字段.
getField只能获取类的public 字段

  • Class 类

    1. Java 的 Class 类是 java 反射机制的基础,通过 Class 类我们可以获得关于一个类的相关信息
    2. Java.lang.Class 是一个比较特殊的类,它用于封装被装入到 JVM 中的类(包括类和接口)的信息。当一个类或接口被装入的 JVM 时便会产生一个与之关联的 java.lang.Class 对象,可以通过这个 Class 对象对被装入类的详细信息进行访问。
    3. 虚拟机为每种类型管理一个独一无二的 Class 对象。也就是说,每个类(型)都有一个 Class 对象。运行程序时,Java 虚拟机(JVM)首先检查是否所要加载的类对应的 Class 对象是否已经加载。如果没有加载,JVM 就会根据类名查找.class 文件,并将其 Class 对象载入。
  • fast-fail 机制

  • 通过反射改变字符串

  • String

    • ==和 equals()

      (1) “” 用于比较基本数据类型时比较的是值,用于比较引用类型时比较的是引用指向的地址。
      (2) Object 中的 equals() 与 “
      ” 的作用相同,但 String 类重写了 equals()方法,比较的是对象中的内容
    • String 对象的两种创建方式:

    1. 第一种方式: String str1 = “aaa”; 是在常量池中获取对象(“aaa” 属于字符串字面量,因此编译时期会在常量池中创建一个字符串对象,如果常量池中已经存在该字符串对象则直接引用)
    2. 第二种方式: String str2 = new String(“aaa”) ; 一共会创建两个字符串对象一个在堆中,一个在常量池中(前提是常量池中还没有 “aaa” 象)
      System.out.println(str1==str2);//false
    • String 类型的常量池比较特殊。它的主要使用方法有两种:

    1. 直接使用双引号声明出来的 String 对象会直接存储在常量池中
    2. 如果不是用双引号声明的 String 对象,可以使用 String 提供的 intern 方法。 String.intern() 是一个 Native 方法,它的作用是: 如果运行时常量池中已经包含一个等于此 String 对象内容的字符串,则返回常量池中该字符串的引用; 如果没有,则在常量池中创建与此 String 内容相同的字符串,并返回常量池中创建的字符串的引用。
    String s1 = new String("AAA");
    String s2 = s1.intern();
    String s3 = "AAA";
    System.out.println(s2);//AAA
    System.out.println(s1 == s2);//false,因为一个是堆内存中的String对象,一个是常量池中的String对象,
    System.out.println(s2 == s3);//true, s1,s2指向常量池中的”AAA“
    
    • 字符串拼接

    String a = "a";
    String b = "b";
    
    String str1 = "a" + "b";//常量池中的对象
    String str2 = a + b; //在堆上创建的新的对象
    String str3 = "ab";//常量池中的对象
    System.out.println(str1 == str2);//false
    System.out.println(str1 == str3);//true
    System.out.println(str2 == str3);//false
    
    • 线程安全的类

    • 二叉树

      先序遍历的过程为根-左-右,中序遍历的过程为左-根-中,后序遍历的过程为 左-右-根
    • mysql

      FROM->WHERE->GROUP BY->HAVING->SELECT->ORDER BY
    • 序列化与反序列化

      1. 在 Java 中,只要一个类实现了 java.io.Serializable 接口,那么它就可以被序列化。
      2. 通过 ObjectOutputStream 和 ObjectInputStream 对对象进行序列化及反序列化
      3. 虚拟机是否允许反序列化,不仅取决于类路径和功能代码是否一致,一个非常重要的一点是两个类的序列化 ID 是否一致(就是 private static final long serialVersionUID)
      4. 序列化并不保存静态变量。
      5. 要想将父类对象也序列化,就需要让父类也实现 Serializable 接口。
      6. Transient 关键字的作用是控制变量的序列化,在变量声明前加上该关键字,可以阻止该变量被序列化到文件中,在被反序列化后,transient 变量的值被设为初始值,如 int 型的是 0,对象型的是 null。
      7. 服务器端给客户端发送序列化对象数据,对象中有一些数据是敏感的,比如密码字符串等,希望对该密码字段在序列化时,进行加密,而客户端如果拥有解密的密钥,只有在客户端进行反序列化时,才可以对密码进行读取,这样可以一定程度保证序列化对象的数据安全。
      8. 序列化保存的是对象的状态,静态变量属于类的状态,因此,序列化并不保存静态变量。所以i是没有改变的

你可能感兴趣的:(java面试专题)