参考资料:https://blog.csdn.net/cheidou123/article/details/95041367

1. Java重载、重写与隐藏

  • 重载(overload):类中多态性的表现。
    • 根据方法参数列表中的参数类型或参数个数;
    • 不可以基于方法的返回值类型。
  • 重写(override):
    • “两小”:子类方法的返回值类型、抛出的异常均小于父类方法的;
    • “两同”:子类方法的方法名和参数列表均与父类方法的相同;
    • “一大”:子类方法的访问权限应大于父类方法的。
  • 隐藏:针对父类的成员变量或静态方法。
    • 子类声明了与父类相同的变量时,父类的同名变量被隐藏;
    • 子类声明了与父类同名的静态方法、且方法的参数列表与返回值类型均与父类方法相同时,父类的同名静态方法被隐藏。

2. 构造方法

  • 与类名相同;
  • 没有返回类型,也不能为void
  • 完成对象的初始化工作,将定义对象时的参数传递给对象的域;
  • 一个类可以定义多个构造方法,若不定义,则会自动生成一个无参数的构造方法,且方法内不执行任何代码;
  • 可以根据参数列表中参数的类型、个数、顺序对构造方法重载;
  • 不能被staticfinalsynchronizedabstractnative修饰,但可以被privateprotectedpublic修饰;
  • 不能被继承,只能被显式或隐式的调用;
  • 若父类没有无参的构造方法,则子类需在自己的构造方法中显式地调用父类的构造方法;
  • 构造方法每次都是构造新的对象,不存在多个线程同时读写同一个对象中属性的问题,所以不需要同步。

3. 访问修饰符

  • public:当前类、子类、包内、其他包;
  • protected:当前类、子类、包内;
  • default:当前类、包内;
  • private:当前类。
  • private的属性和方法可以被子类继承,但不能被子类调用。

4. 接口、抽象类及它们的联系与区别

  • 抽象类

    • 不能被实例化;
    • 抽象方法必须由子类重写,若未被子类重写,则子类也是抽象类;
    • 只要包含一个抽象方法就是抽象类;
    • 抽象类中可以包含具体方法,也可以不包含抽象方法;
    • 子类中的抽象方法不能与父类中同名;
    • abstractfinal不能并列修饰同一个类;
    • abstract不能与privatestaticfinalnative并列修饰同一个方法。
  • 接口

    • 一个类可以实现多个接口(实现多继承);
    • 接口中的常量和方法必须是public的;
    • 在接口中只有方法的声明,没有方法体;
    • 接口中所有方法都必须是抽象的,可以在接口中定义数据成员,但必须是常量;
    • 接口中没有构造方法,也不能实例化出接口的对象;
    • 接口中的方法需要由实现类实现,若实现类不去实现接口中所有的方法,则该类需被定义为抽象类;
    • 接口中不能包含静态方法。
  • 联系

    • 都不能被实例化;
    • 都可以包含抽象方法。
  • 区别

    • 一个子类只能继承一个抽象类,但可以实现多个接口;
    • 抽象类可以有构造方法,但接口不能;
    • 抽象类可以有普通成员变量,但接口不能;
    • 抽象类和接口都可以有静态成员变量,抽象类中静态成员变量可以是任意访问类型,接口只能为public static final
    • 抽象类可以没有抽象方法,抽象类可以有普通方法,Java8之前,接口中的方法都为抽象方法,Java8之后,接口中可以有默认方法;
    • 抽象类可以有静态方法,接口不能;
    • 抽象类中方法可以被publicprotected修饰,接口中只能由public修饰,Java9接口可以定义私有方法,但不能是抽象的。

5. 析构函数

构造函数相反,析构函数用于在对象结束其生命周期时,系统自动调用进行完成“清理善后”工作(如建立对象时使用new开辟了一片内存空间,退出前应在析构函数中使用delete释放)。

6. JDK中常用的包与功能

  • java.lang:提供使用Java编程语言进行程序设计的基础类;
  • java.util:提供各种实用功能类,如Collection框架、遗留的Collection类、事件模型、日期和时间、国际化、字符串标记生成器、随机数生成器、位数组等;
  • java.awt:抽象窗口工具包,用于编写GUI程序;
  • java.swing:轻量级窗口工具包,现最广泛使用的GUI程序设计包;
  • java.io:提供系统的输入输出类和接口,只要包括输入流类InputStream和输出流类OutputStream就可以实现文件的输入输出、管道的数据传输以及网络数据传输的功能;
  • java.net:提供实现网络应用程序的类,包括用于实现Socket通信的Socket类以及便于处理URL的类等;
  • java.sql:提供使用Java编程语言访问并处理存储在数据源(通常是一个关系数据库)中的数据的 API。

7. superthis关键字

  • this表示当前对象,即当前类对象,super表示当前类的父类对象;
  • this()super()需放在构造函数第一行;
  • 都是对象,所以不能在static环境中使用;
  • this()super()不能在同一个构造函数内使用。

8. Object类的基本方法

  • clone():实现对对象的浅复制,需实现Cloneable接口才可调用该方法,否则抛出CloneNotSupportedException
  • getClass()final方法,获得运行时类型。
  • toString():返回对象的字符串表示。
  • finalize():用于资源释放,无法确定该方法何时被调用。
  • equals():注意与==的区别(equals()与==的区别)。
  • hashCode():用于哈希查找(hashCode()与equals()的区别)。
  • wait():使当前线程等待该对象的锁,当前线程必须是该对象的拥有者,即具有该对象的锁。wait()一直等待直到获得锁或者被中断。wait(long timeout)设定一个超时间隔,若超时内未获得锁就返回。调用该方法后当前线程进入睡眠状态,直到发生下列事件:
    • 其他线程调用了该对象的notify()
    • 其他线程调用了该对象的notifyAll()
    • 其他线程调用interrupt()中断该线程,此时抛出InterruptedException异常;
    • 超时。
  • notify():唤醒在该对象上等待的某个线程。
  • notifyAll():唤醒在该对象上等待的所有线程。

9. final关键字

  • 用于修饰类、方法、属性,但不可修饰抽象类,因为抽象类一般需要被继承,final修饰的类无法被继承;
  • 修饰的方法不能被重写(override);
  • 修饰的属性会变为常量,无法被再赋值。

10. J2EE常见名词

  • EJB:Enterprise Java Bean;
  • JNDI:Java Naming & Directory Interface,Java命名目录服务;
  • JMS:Java Message Service,Java消息服务;
  • JTA:Java Transaction API,Java事务服务;
  • JAF:Java Action Framework,Java安全认证框架。

11. 加载驱动的三种方式

  • Class.forName("com.microsoft.sqlserver.jdbc.SQLServerDriver")
  • DriverManager.registerDriver(new com.mysql.jdbc.Driver());
  • System.setProperty("jdbc.drivers", "com.mysql.jdbc.Driver")

12. 静态方法、静态变量、静态代码块

  • 静态方法不能引用非静态方法和非静态属性,因为静态方法不需要经过实例化就可以使用;
  • 静态方法中没有this指针;
  • 静态方法可以被重载;
  • 静态方法在装载class时首先完成,比构造方法早,此时非静态方法还没有完成初始化,因此无法被调用。
  • 静态代码块优先于主方法,且只执行一次。
  • 只有类才存在静态变量,非静态方法可以调用静态变量,但不能在方法内定义静态变量。

13. Java表达式转型规则

由低到高转换:byte -> short(char) -> int -> long -> float -> double

14. 基本数据类型和引用数据类型

基本类型(原生类)只能保存一些常量数据;而引用类型除了可以保存数据,还可以提供操作这些数据的功能。为了操作基本数据类型,Java对它们进行了封装,将8中基本数据类型包装为包装类。

  • 引用类型包括类、接口、数组和特殊的null类型;
  • 引用数据类型的变量名指向存储数据对象的内存地址,即变量名指向hash值。

15. Java变量初始值问题

类的成员变量有初始值,局部变量没有初始值,final修饰的变量一定要有初始值。

16. Java的四种引用

  • 强引用:强引用是使用最普遍的引用。如果一个对象具有强引用,那垃圾回收器绝不会回收它。
  • 软引用:内存不足回收。可以用来做缓存。
  • 弱引用:无论内存是否充足,都会回收被弱引用关联的对象,弱引用能用来在回调函数中防止内存泄露。因为回调函数往往是匿名内部类,隐式保存有对外部类的引用,所以如果回调函数是在另一个线程里面被回调,而这时如果需要回收外部类,那么就会内存泄露,因为匿名内部类保存有对外部类的强引用。
  • 虚引用:它并不影响对象的生命周期。在Java中用java.lang.ref.PhantomReference类表示。如果一个对象与虚引用关联,则跟没有引用与之关联一样,在任何时候都可能被垃圾回收器回收。

17. Java序列化

  • 序列化:把Java对象转换成字节序列;
  • 反序列化:把字节序列转换成Java对象。
  • 序列化的作用:
    • (1)实现数据持久化,通过序列化可以把数据永久存储在磁盘中;
    • (2)利用序列化实现远程通信,即在网络上传送对象的字节序列。
  • 注意事项:
    • transient修饰的变量在序列化之后对应的值没了;
    • 不能序列化static变量,静态变量属于类,保存的是类的状态;
    • 需要加上序列化ID。

18. 类型转换

  • 自动类型转换:目标类型大于原类型,如int可以直接赋值给double型。
  • 强制类型转换:目标类型小于原类型,将对于的小数位截断。
  • 类型提升:当一个Java算术表达式中包含多个基本类型的值时,整个算术表达式的数据类型将发生自动提升:
    • 所有byte型、short型和char型将被提升到int型;
    • 整个算术表达式的数据类型自动提升到与表达式中最高等级操作数同样的类型。
1
2
byte b1 = 3, b2 = 4, b3;
b3 = (b1 + b2); // 编译不通过,b1+b2被自动提升为int型,b3为byte型,若要赋值需要进行强转。

19. 多态

(1)多态的作用

  • 一个接口,多种实现,通过抽象化来提高程序的扩展性。

(2)多态的条件

  • 有继承关系;
  • 子类要重写父类的方法;
  • 父类引用指向子类。

(3)多态的注意事项

  • 多态后不能使用子类特有的属性和方法;
  • 成员变量和静态方法使用父类的;
  • 子类重写的普通方法使用子类的。

(4)基于JVM解释多态

方法调用是依照方法的符号引用得到具体内存引用的过程。调用类的方法(静态方法)在类加载时就直接转化为具体内存引用,为静态绑定;调用对象的方法则是动态绑定,最后得到真正的方法引用,完成调用。

20. 数组

  • 数组是引用类型,不属于原生类,可以看成是一种对象,一旦指定了大小就无法改变。
  • 数组的初始化:
    • 左边的名称和括号顺序可以颠倒(如int[] aint a[]),但右边的第一个括号内一定要有值;
    • 定义数组,左边的括号内不能有值,即数组的大小定义放在右边。
  • 对于多维数组,array.length取的是最外层括号包含的元素个数。
  • 数组的四种拷贝方式效率对比:System.arraycopy > clone > Arrays.copyOf > for

21. ComparableComparator的区别

  • Comparable内部比较器:若一个类实现了Comparable接口,则意味着该类支持排序。实现该接口的类的对象的列表或数组可以通过Collections.sort()Arrays.sort()进行自动排序,一般要重写compareTo()方法;
  • Comparator外部比较器:位于java.util包下,Comparator是比较接口。若需要控制某个类的次序,而该类本身不支持排序(即没有实现Comparable),则可以通过建立一个“该类的比较器”进行排序,这个比较器只需实现Comparator接口,一般要重写compare()方法。

22. 类的生命周期以及双亲委托机制

生命周期:加载 -> 验证 -> 准备 -> 解析 -> 初始化 -> 使用 -> 卸载

(1)加载

整个类加载过程中,除了在加载阶段用户应用程序可以自定义类加载器参与之外,其余所有的动作完全由虚拟机主导和控制。

双亲委托机制:Java的类加载使用双亲委派模式,即一个类加载器在加载类时,先把这个请求委托给自己的父类加载器去执行,如果父类加载器还存在父类加载器,就继续向上委托,直到顶层的启动类加载器。如果父类加载器能够完成类加载,就成功返回,如果父类加载器无法完成加载,那么子加载器才会尝试自己去加载。

  • 启动类加载器(Bootstrap ClassLoader),JVM的一部分,负责将放置在Jre\lib目录中或者被-Xbootclasspath参数所指定路径中的,并且是虚拟机能识别的类库加载到虚拟机内存中。启动类加载器无法被Java程序直接使用。
  • 扩展类加载器(Extension ClassLoader),这个类加载器由sun.misc.Launcher$ExtClassLoader实现,它负责加载Jre\lib\ext目录中或者被java.ext.dirs系统变量所指定的路径中的所有类库,开发者可以直接使用扩展类加载器。
  • 应用程序类加载器(Application ClassLoader),负责加载ClassPath所有jar或目录,开发者可以直接使用这个类加载器。
  • 自定义加载器

(2)验证

验证的目的是为了确保Class文件中的字节流包含的信息符合当前虚拟机的要求。

(3)准备

准备阶段是正式为类变量分配内存并设置类变量初始值的阶段,这些内存都将在方法区中分配。

  • 这里内存分配只是类变量(static修饰的),而不包括实例变量,实例变量会在对象实例化时随着对象一起分配在Java堆中。
  • 为类变量赋值为0值(0、0L、null、false等)。
  • 例如public static int value = 3;,准备阶段会将value赋值为0,直到初始化阶段才将其赋值为3;若被final修饰,则直接赋值为3。

(4)解析

解析阶段是虚拟机将常量池内的符号引用替换为直接引用的过程。

(5)初始化

初始化,为类的静态变量赋予正确的初始值,JVM负责对类进行初始化,主要对类变量进行初始化。

(6)使用

使用该类所提供的功能,其中包括主动引用和被动引用。

  • 主动引用
    • 通过new关键字实例化对象,读取或设置类的静态变量,调用类的静态方法。
    • 通过反射方式执行上述三种行为。
    • 初始化子类时会触发父类的初始化。
    • 作为程序入口直接运行时(直接调用main方法)。
  • 被动引用
    • 引用父类的非final静态变量,只会引起父类初始化,不会引起子类初始化。
    • 引用父类的final静态变量,父类子类都不会初始化。
    • 定义类数组不会引起类的初始化。
    • 引用类的常量不会引起类的初始化。

(7)卸载

在使用完类后,若满足以下条件,就会被卸载。

  • 该类所有的实例都已经被回收,也就是Java堆中不存在该类的任何实例;
  • 加载该类的ClassLoader已经被回收;
  • 该类对应的java.lang.Class对象没有任何地方被引用,无法在任何地方通过反射访问该类的方法。

若上述三个条件全部满足,JVM就会在方法区垃圾回收的时候对类进行卸载,即在方法区中清空类信息,结束Java类的生命周期。

23. 动态绑定和静态绑定

  • 绑定的概念:一个方法的调用与所在的类(方法的主体)关联起来。
  • 静态绑定:在编译过程中就知道该方法是哪个类中的方法,只有finalstatic(重写static方法使用多态,调用的仍然是父类的static方法)、private和构造方法使用静态绑定。
  • 动态绑定:在运行时绑定。原理:父类不是接口,当子类和父类加载到虚拟机时,方法区就有了类的信息,方法区中有方法表,如果子类重写父类的方法,那么在各自方法表里方法的偏移量是一样的,当我们调用时,
    • 先找到方法调用的符号引用,
    • 查看父类偏移量,
    • 找到具体子类,
    • 根据父类偏移量查看子类方法表有没有重写。

如果父类是接口的话,无法根据偏移量来确定,则采用搜索方法表的形式,效率要低些。

24. HashMap

(1)基本结构

  • 构造函数

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    // 默认构造函数
    HashMap() // 默认大小为16

    // 指定容量大小
    HashMap(int capacity) // capacity必须是2的次幂,从而减少碰撞

    // 指定容量大小和加载因子
    HashMap(int capacity, float loadFactor) // loadFactor默认值是0.75

    // 包含 子Map 的构造函数
    HashMap(Map<? extends K, ? extends V> map)
  • 基本结构

    • jdk1.7:数组+链表(头插)
    • jdk1.8:数组+链表(尾插)/红黑树(链表长度到8时,将链表转换成红黑树来处理)

(2)存取元素

  • 存元素:HashMap会对key值为null的进行特殊处理,总是放到table[0]位置。put()过程是先计算哈希值然后通过哈希值与table.length取模计算index值,然后将key放到table[index]位置,当table[index]已存在其它元素时,会在table[index]位置生成一个链表,将新添加的元素放在table[index](头插),原来的元素连接到Entry.next,这样以链表形式解决哈希冲突问题,当元素数量达到临界值时,则进行扩容,使table数组长度变为原来的两倍。jdk1.8版本中,链表改为尾插,且当长度超过8时转换为红黑树。
  • 取元素:同样当key为null时会进行特殊处理,在table[0]的链表上查找key为null的元素。get()的过程是先计算哈希值然后通过哈希值与table.length取模计算index值,然后从table[index]上的链表或红黑树找到key,然后返回。