被你忽略掉的 Java 细节知识

本文主要介绍曾经让笔者混淆、模糊不清的四十五条 Java 细节知识,这些知识被你忽略掉了吗?来看看你是不是对这些概念掌握了…

  1. 静态代码块、构造代码块、构造函数同时存在时的执行顺序:静态代码块 -> 构造代码块 -> 构造函数

  2. 为什么不支持多继承呢?因为当一个类同时继承两个父类时,两个父类中有相同的功能,那么子类对象调用该功能时,运行哪一个呢?

  3. 父类中通常是不会出现同名成员变量的,因为父类中只要定义了,子类就不用在定义了,直接继承过来用就可以了。

  4. 子类的所有构造函数中的第一行,其实都有一条隐身的语句super();

  5. 接口: 成员变量:public static final; 成员方法:public abstract; 注意 abstract 与 static 不能共存,所以接口中的方法不是静态的。

  6. 抽象类和接口的区别:

    • 抽象类只能被继承,而且只能单继承,接口需要被实现,而且可以多实现
    • 抽象类中可以定义非抽象方法,子类可以直接继承使用。接口都有抽象方法,需要子类去实现
    • 抽象类使用的是 is-a 关系,接口使用的是 like-a 关系
    • 抽象类的成员修饰符可以自定义,接口中的成员修饰符是固定的
  7. 多态: 父类引用或者接口的引用指向了自己的子类对象

    • 成员变量: 无论编译和运行,成员变量参考的都是引用变量所属的类中的成员变量。(成员变量 — 编译运行都看 = 左边 )
    • 成员函数: 在子父类中,对于一模一样的成员函数,有一个特性:覆盖。(成员函数 — 编译看 = 左边,运行看 = 右边 )
    • 静态函数: 静态方法,其实不所属于对象,而是所属于该方法所在的类。(静态函数 — 编译运行都看 = 左边 )
  8. 内部类

    • 可以直接访问外部类中的成员。而外部类想要访问内部类,必须要建立内部类的对象
    • 如果内部类被静态修饰,相当于外部类,会出现访问局限性,只能访问外部类中的静态成员
    • 如果内部类中定义了静态成员,那么该内部类必须是静态的
    • 当内部类被定义在局部位置上,只能访问局部中被 final 修饰的局部变量
  9. 异常体系
    Throwable:可抛出的
    |–Error:错误,一般情况下,不编写针对性的代码进行处理,通常是 jvm 发生的,需要对程序进行修正
    |–Exception:异常,可以有针对性的处理方式

    try对应多个catch时,如果有父类的catch语句块,一定要放在下面

    异常分两种:

    • 编译时被检查的异常,只要是Exception及其子类都是编译时被检测的异常
    • 运行时异常,其中Exception有一个特殊的子类RuntimeException,以及RuntimeException的子类是运行异常,也就说这个异常是编译时不被检查的异常

    System.exit(0); //退出jvm,只有这种情况finally不执行

  1. Runnable 接口出现的原因:

    • 因为实现Runnable接口可以避免单继承的局限性
    • 将线程要执行的任务封装成了对象, 便于扩展
  2. 同步(synchronized):

    • 实例方法使用的锁是 this
    • 类方法使用的 类字节码
  3. 同步是隐式的锁操作,而 Lock 对象是显示的锁操作,它的出现就替代了同步。Lock 接口中并没有直接操作等待唤醒的方法,而是将这些方式又单独封装到了一个对象中。这个对象就是 Condition,将 Object 中的三个方法进行单独的封装。并提供了功能一致的方法 await()signal()signalAll() 体现新版本对象的好处。

  4. StringBuffer 和 StringBuilder 的区别:

    • StringBuffer线程安全, StringBuilder线程不安全
    • 单线程操作,使用StringBuilder 效率高; 多线程操作,使用StringBuffer 安全
  1. 基本数据类型包装类中,只有 Character 类没有 parse(...) 方法

  2. Map 集合对比:

    • Hashtable:底层是哈希表数据结构,是线程同步的。不可以存储null键,null值
    • HashMap:底层是哈希表数据结构,是线程不同步的。可以存储null键,null值。替代了Hashtable
    • TreeMap:底层是二叉树结构,可以对map集合中的键进行指定顺序的排序,JDK 1.8 中源码表现为红黑树

    Map 中要保证键的唯一性

  1. InputStreamReader isr = new InputStreamReader(new FileInputStream(“a.txt”),”gbk”); 指定编码

  2. 递归: 当一个功能被重复使用,而每一次使用该功能时的参数不确定,都由上次的功能元素结果来确定

  3. RandomAccessFile:

    • 该对象即可读取,又可写入
    • 该对象中的定义了一个大型的byte数组,通过定义指针来操作这个数组
    • 可以通过该对象的getFilePointer()获取指针的位置,通过seek()方法设置指针的位置
    • 该对象操作的源和目的必须是文件
    • 其实该对象内部封装了字节读取流和字节写入流

    注意:实现随机访问,最好是数据有规律,比如实现多线程下载

  4. 静态数据不能被序列化,因为静态数据不在堆内存中,是存储在静态方法区中;如何将非静态的数据不进行序列化?用transient 关键字修饰此变量即可

  5. 逻辑端口:用于标识进程的逻辑地址,不同进程的标识;有效端口:0~65535,其中0~1024系统使用或保留端口

  6. 反射技术:其实就是动态加载一个指定的类,并获取该类中的所有的内容

    • clazz.getMethods();//获取的是该类中的公有方法和父类中的公有方法
    • clazz.getDeclaredMethods();//获取本类中的方法,包含私有方法
  7. 类和接口的区别: 当一个类在初始化时,要求其父类全部都已经初始化过了,但是一个接口在初始化时,并不要求其父接口全部都完成了初始化,只有真正使用到父接口的时候(如引用接口中定义的常量)才会初始化

  1. wait()、notify()和notifyAll()是 Object类 中的方法 ;Condition是在java 1.5中才出现的,它用来替代传统的Object的wait()、notify()实现线程间的协作,相比使用Object的wait()、 notify(),使用Condition1的await()、signal()这种方式实现线程间协作更加安全和高效。 线程的互斥锁机制:synchronized,lock,condition
  1. 数组复制效率: System.arraycopy > clone > System.copyOf > for循环

  2. 方法的重写(override)两同两小一大原则:

    • 方法名相同,参数类型相同
    • 子类返回类型小于等于父类方法返回类型,
    • 子类抛出异常小于等于父类方法抛出异常,
    • 子类访问权限大于等于父类方法访问权限。
  3. 值传递不可以改变原变量的内容和地址;引用传递不可以改变原变量的地址,但可以改变原变量的内容;

  4. Byte,Short,Integer,Long,Character这5种整型的包装类也只是在对应值小于等于127并且大于等于-128时才可使用常量池,因为他们至占用一个字节(-128~127);
    再者Integer.valueOf方法中也有判断,如果传递的整型变量>= -128并且小于127时会返回IntegerCache类中一个静态数组中的某一个对象, 否则会返回一个新的Integer对象,代码如下:

    1
    2
    3
    4
    5
    6
    public static Integer valueOf(int i) {
    assert IntegerCache.high >= 127;
    if (i >= IntegerCache.low && i <= IntegerCache.high)
    return IntegerCache.cache[i + (-IntegerCache.low)];
    return new Integer(i);
    }

new Integer(59) 在堆中分配。

  1. () 和 () 中先执行父类,再初始化变量,最后执行语句块。

  2. 逻辑运算

    • 短路:在逻辑表达式中,如果能通过逻辑运算符左边表达式的值就能推算出整个表达式的值,那么将不再继续执行逻辑运算符右边的表达式。(&&,||)
    • 非短路:始终执行逻辑表达式两边的表达式。(&,|)
  3. super.getClass() 得到的依然是runtime当前类,若要得到真正的父类,需要用super.getClass().getSuperclass()

  4. 方法重载:同一类中的相同的方法名,参数表必须不同,方法的返回类型、修饰符可以相同,也可不同。

  1. volatile: volatile 变量提供顺序和可见性保证,例如,JVM 或者 JIT为了获得更好的性能会对语句重排序,但是 volatile 类型变量即使在没有同步块的情况下赋值也不会与其他语句重排序。 volatile 提供 happens-before 的保证,确保一个线程的修改能对其他线程是可见的。某些情况下,volatile 还能提供原子性,如读 64 位数据类型,像 long 和 double 都不是原子的,但 volatile 类型的 double 和 long 就是原子的。

  2. 在使用 wait() 中,使用 while(需要等待资源条件) 来代替直接使用,这样可以双重判断资源被满足并且被唤醒。

  3. 哪个类包含 clone 方法?是 Cloneable 还是 Object?java.lang.Cloneable 是一个标示性接口,不包含任何方法,clone 方法在 object 类中定义。并且需要知道 clone() 方法是一个本地方法,这意味着它是由 c 或 c++ 或 其他本地语言实现的。

  4. a = a + b 与 a += b 的区别: += 隐式的将加操作的结果类型强制转换为持有结果的类型。如果后者相加,如 byte、short 或者 int,首先会将它们提升到 int 类型,然后在执行加法操作。

  5. Switch 中可以使用 String 吗?从 Java 7 开始,我们可以在 switch case 中使用字符串,但这仅仅是一个语法糖。内部实现在 switch 中使用字符串的 hash code。

  6. JIT 代表即时编译(Just In Time
    compilation),当代码执行的次数超过一定的阈值时,会将 Java
    字节码转换为本地代码,如,主要的热点代码会被准换为本地代码,这样有利大幅度提高 Java 应用的性能。

  7. Java 中堆和栈: JVM 中堆和栈属于不同的内存区域,使用目的也不同。栈常用于保存方法帧和局部变量,而对象总是在堆上分配。栈通常都比堆小,也不会在多个线程之间共享,而堆被整个 JVM 的所有线程共享。

  8. LinkedList 中 getFirst()/element()/peek() 返回元素但不移除,当列表为空时,peek() 返回 null 而不抛出异常;removeFirst()/remove()/poll() 都是移除并返回头,当列表为空时,poll() 返回 null 而不抛出异常。

  9. Dalvik VM 并不是一个 Java 虚拟机,它没有遵循 Java 虚拟机规范,不能直接执行 Java 的 Class 文件,使用的是寄存器架构而不是 JVM 中常见的栈架构。但是它与 Java 又有着千丝万缕的联系,它执行的 dex(Dalvik Executable) 文件可以通过 Class 文件转化而来,使用 Java 语法编写应用程序,可以直接使用大部分的 Java API 等。

  10. HashMap 中, Entry是数组, 数组中的每个元素上挂这个一条链表. 链表法就是将相同hash值的对象组织成一个链表放在hash值对应的槽位; 开放地址法是通过一个探测算法, 当某个槽位已经被占据的情况下继续查找下一个可以使用的槽位

  11. 虚拟机在首次加载Java类时,会对静态初始化块、静态成员变量、静态方法进行一次初始化。我们不要去纠结这里的顺序,一般来说我们只需要知道,静态方法一般在最后。类实例创建过程:按照父子继承关系进行初始化,首先执行父类的初始化块部分,然后是父类的构造方法;再执行本类继承的子类的初始化块,最后是子类的构造方法。类实例销毁时候,首先销毁子类部分,再销毁父类部分。

  12. java 中可以有多个重载的 main 方法,只有 public static void main(String[] args){} 是函数入口。

  13. Java 中的函数都必须用 {} 括起来,不管是一条语句还是多条语句

  14. throws用于在方法上声明该方法不需要处理的异常类型,用在方法上后面跟异常类名 可以是多个异常类;throw用于抛出具体异常类的对象,用在方法内 后面跟异常对象只能是一个异常类型实体.