Java 语言

基础

基本数据类型

除八个基本类型,Java 的所有数据类型都是引用,Java 中没有指针的概念,所有数据传输都是传值(引用可以看作是地址值,所有引用值都占四个字节)

基本数据类型的包装类 Byte Short Integer Long Character 有常量池

编译运行

  • Java 编译器 Javac 是用 Java 实现的,用于将 .java 文件编译成字节码 .class
  • .class 通过解释器对这些字节码进行解释执行
  • Java 的运行环境 JVM 如 HotSpot VM,实现了其跨平台的特性

三大特性

封装

将数据和基于数据的操作封装在一起,使其构成一个不可分割的独立实体;

继承

支持类之间的单继承,但支持接口之间的多继承;

类可以实现多个接口,抽象类不能实例化但可以有构造方法,内部类只能通过外部类创建;

Object 是超类,是所有类的父类(无父类的子类默认继承 Object。jdk6之前是编译器处理,jdk7之后是虚拟机处理);

Java8 之后接口可以有默认方法,允许在接口中声明静态方法;

多态

全面支持动态绑定,动态绑定是实现多态(一个接口,多种实现)的基础;

  • 多态方式调用方法时,首先检查父类中是否有该方法,如果没有,则编译错误;如果有,再去调用子类的同名方法(A a=new B(),A是父类,B是子类)
  • 静态绑定:对象属性和 static private final 修饰的对象方法(构造函数),哪里声明,哪里使用
  • 动态绑定:运行时 JVM 实现绑定。若子类实现了这个方法,则调用子类的内存地址,若没有则调用当前对象(父类)的方法。只能调用父类的属性,虽然实际实现的是子类,如果父类没有这个属性,那就无法调用。如果要获取子类属性,就要重写子类方法获取该属性,前提是这个方法在父类中同样存在。

关键字

关键字 说明
final 常量
static 静态变量,无论一个类实例化多少对象,它的静态变量只有一份拷贝;静态方法,不能使用类的非静态变量,类名直接调用,不需要进行实例化对象。
native 本地、原生方法(非 Java 实现)
strictfp 严格浮点、精准浮点
synchronized 线程、同步
transient 修饰的成员属性变量不被序列化,仅存于调用者的内存中而不会写到磁盘里持久化,防止敏感信息泄露(与Serilizable接口一同使用)当对象被反序列化时,被 transient 修饰的变量值不会被持久化和恢复,直接设置为默认值。static 不属于任何对象,因此永远不会被序列化。
volatile 变量在 CPU 的寄存器中是不确定的,必须从主存中读取。保证多线程环境下变量的可见性;禁止指令重排序。
instanceof 实例

运算符优先级

自动装箱/拆箱

jdk1.5 i = Integer.valueOf(3) -128-127

自动装箱通常也不会通过显式的 valueOf 方法调用实现。编译器生成的字节码可能直接使用内部指令或方法来处理装箱和拆箱,而不是通过Java方法调用机制。因此,在 valueOf 方法上设置断点通常不会捕获到自动装箱的过程。
new String("abc")String.valueOf("abc") 的区别:前者在堆中创建了新的对象,后者返回字符串常量池对象的引用。拆箱:xxxValue();装箱:valueOf()

public void zhuangXiang() {
Integer a1 = 128;
Integer a2 = 128;
Integer a3 = 256;
Integer a4 = a1 + a2;
System.out.println(a1 == a2); // false
System.out.println(a4 == a3); // false
System.out.println(a1 + a2 == a3); // true 发生拆箱
}

NPE 问题

  • 数据库查询返回结果为 Null,无法拆箱
  • 三目运算符可能出现问题:flag ? 0 : i(应使用 flag ? new Integer(0) : i)

动态代理

静态代理:编译时完成动态代理:运行时动态生成类字节码,并加载到 JVM 中

  • JDK 动态代理:通过生成一个实现被代理类接口的对象来拦截被代理类的方法调用
    • 封装:Proxy.newProxyInstance() 指定 ClassLoader 对象和一组 interface
    • 调用:InvocationHandler 接口 invoke 方法
  • CGLIB 动态代理:通过生成一个被代理类的子类来拦截被代理类的方法调用(字节码实现,不能被 final 修饰,编译速度慢,运行速度快)
    • 封装:Enhancer 类
    • 调用:MethodInterceptor 接口 intercept 方法

二者的具体实现

  • JDK:运行时动态解析,无法应用一些 JVM 优化
  • CGLib:ASM 开源包,代理对象类的 class 文件加载时,修改其字节码生成代理子类(ASM 是在编译期处理字节码的,可以认为是一种编译期的 AOP 技术)

为什么 JDK 动态代理,要求被代理对象必须实现一个接口?

  • 因为 JDK 动态代理类已经继承了 Proxy 这个类,所以只能通过接口来与被代理类建立联系(两个类建立起联系,一是继承的关系【jdk已经不能通过这个方式了,因为java仅支持单继承】,另一种就是实现同一个接口【JDK动态代理选这种】),所以必须要求被代理类也得实现一个接口

原生工具类

Collections 集合操作类

  • sort(list)
  • reverse(list)
  • binarySearch(list, target)

Arrays 数组操作类

  • max(list)
  • sort(array, (o1, o2)->o1-o2)
  • copyOf(array, Length)
  • binarySearch(array, value)
  • fill(array, value)

比较器 集合排序工具

  • Comparator 类级别
    • 针对不同的比较,单独定义一个比较类
    • int compare(T o1, T o2);
      • 返回值为正数,交换 o1 o2 的顺序
      • 返回值为负数或零,不需要调整
      • 返回升序 o1-o2,降序 o2-o1
  • Comparable 方法级别
    • 若一个类实现了Comparable接口,就意味着“该类支持排序”,可以用Arrays.sort()排序
    • public int compareTo(T o);

接口实现

接口和抽象类的关系

  • 共同点

    • 都不能被实例化
    • 都可以包含抽象方法
    • 方法可以有默认实现
  • 区别

    • 接口主要用于对类的行为进行约束,你实现了某个接口就具有了对应的行为。抽象类主要用于代码复用,强调的是从属关系。
    • 一个类只能继承一个类,但是可以实现多个接口。
    • 接口中的成员变量只能是 public static final 类型的,不能被修改且必须有初始值,而抽象类的成员变量默认 default,可在子类中被重新定义,也可被重新赋值。
  • 接口中的所有成员变量都默认是由public static final修饰的

  • 接口中的所有方法都默认是由public abstract修饰的

  • 接口没有构造方法

  • 实现接口的类中必须提供接口中所有方法的具体实现内容

  • 多个无关的类可以实现同一个接口

  • 一个类可以实现多个无关的接口

  • 与继承关系类似,接口与实现类之间存在多态性

  • 接口可以继承多个接口,使用extends关键字

  • Java8 后,接口可以拥有普通方法,实现类不需要重写,可以被实现类继承

构造器

this(参数列表)来访问本类构造器需要注意以下几点

  • 只能在构造器中使用 this(参数列表);即在一个构造器中访问本类的另外一个构造器。
  • 显示使用 this() 时,默认的 super() 就被覆盖
  • this(参数列表)和 super(参数列表)在构造器中有且只能存在一个。
  • 若在构造器中使用 this(参数列表),则此语句只能位于构造器第一行

动态加载

反射(运行时获取类的信息、创建对象、调用方法)

类加载器 ClassLoader