面向对象
面向对象是一种编程思想,从业务中抽象出一个个类,类中包含属性和方法,相比直接实现函数功能的面向过程,更容易维护和扩展,有封装、继承、多态三大特性:
封装:将对象的属性私有化,对外只提供接口访问,从而隐藏内部实现,提高程序安全性。
继承:从已有类得到继承信息创建新类的过程,实现了代码的复用。
- 子类拥有父类所有属性和方法,但无法访问访问父类私有属性和方法。
- 子类可以拥有自己的属性和方法。
- 子类可以重写父类方法。
多态:分为编译时多态(重载)和运行时多态(重写)
- 重载:指同一个类中同名的方法有不同的参数列表,注意不能根据返回类型进行区分。
- 重写:发生在子类与父类之间,子类重写父类的方法具有相同的返回类型、更好的访问权限。
实现多态分两步,一是子类继承父类并重写父类的方法,二是用父类型引用子类型对象。这样,同样的引用调用同样的方法,就会因为子类对象的不同而执行不同的操作。
但面向对象不能很好的实现业务逻辑与非业务代码,如安全、日志、事务,的解耦,需要 AOP 来进行补充。
String
String:
- 是不可变类,原生的保证了线程安全;
- 底层用被 final 修饰的 char 数组保存数据,Java 9 改用 byte;
- 由于不可变,所以每次改变,都要产生新的 String 对象,影响性能,所以只适合操作少量数据。
操作大量数据要用 StringBuffer、StringBuilder,它们继承于 AbstractStringBuilder,底层用可修改的 char 数组,Java 9 改为 byte;不同在于:
- StringBuffer 通过 synchronized 保证了线程安全,适合并发下使用,但有额外的性能开销;
- StringBuilder 不是线程安全的,性能更高,适合单线程下使用。
拼接字符串用的加号,底层就是调用了 StringBuilder,但不建议在多表达式或循环内直接用,因为会频繁创建 StringBuilder 对象。更好的做法是,手动创建 StringBuilder 对象,调用它的 append 方法进行拼接。
数据类型
Java 的数据类型分为基础类型和引用类型两种。
基础类型有:
- 整型:byte、short、int、long,分别为 8、16、32、64 位,数据范围是 -2 的位数 -1次幂 ~ 2 的位数 -1 次幂 -1。比如:int,-231 ~ 231-1 。
- 浮点型:float、double,分别为 32、64 位,float 赋值必须加 F。
- 布尔型 boolean:只有 true,false 两个取值。
- 字符型 char:16位,存储 Unicode 码,用单引号赋值。
每种基本类型都有对应的包装类型:
- 自动装箱:自动将基本类型转换为包装类型。底层通过调用包装类型的 valueOf 方法实现,比如 Integer.valueOf(),它会缓存 -128 到 127 的数据,以提高性能,。
- 自动拆箱:自动将包装类型转换为基本类型。
引用类型可细分为类、接口、数据类和泛型。Java 的泛型是通过类型擦除实现的伪泛型,即泛型信息在编译过后便会擦除。数组类由 JVM 直接生成,没有字节流。只有类和接口才有对应的字节流,需要通过类加载器去加载。
抽象类和接口
抽象类:不能实例化的类,用于重用代码。
- 可以定义构造函数;
- 访问权限可以是 public、protected、default。(public 所有类,protected 包内类及子类,default 包内类,private 本类)
- 方法可以有抽象方法和具体方法;(可以没有抽象方法)
接口:抽象方法的集合,用于定义 API 和分离实现。
- 不能实例化和定义构造函数。
- 变量默认为 public static final。public 允许所有类访问。static 修饰为类变量,因为接口不能实例化。final 则避免被实现类修改。
- 方法只能是抽象方法。Java 8 后,开始有静态方法和默认实现。
Java 中定义了非常多接口,常用的比如集合的 Collection 和 Map。
equals
==
== 是运算符:
- 对于基本数据类型,比较的是值;
- 对于引用类型,比较的是内存地址。
equals() 是 Object 的方法,用于比较对象是否相等,不能比较基本类型。
- 默认实现是,用 == 比较对象的内存地址;
- 一般会重写,转换成值的比较。
hashCode
equals()、hashCode() 都是 Object 类的方法:
- equals():用于比较两个对象是否相等,不能比较基本数据类型;默认实现是用 == 比较对象的引用地址,一般会重写转换成值的比较。
- hashCode():返回对象的哈希值(一个 int 整数),用于确定对象在哈希表中的索引位置。
它们常用在 HashSet、HashMap 等集合的判重:
- 如果仅用 equals(),那么集合中有多少对象,equals() 就要执行多少次,时间开销很大。
- 如果加入 hashCode,就能确定对象在哈希表的索引位置。后续比较中,若两个对象 hashCode 不同,也无需调用 equals(),减少 equals() 的次数,提高执行效率。
因为我们工作中常用到这些集合,为了提高效率,官方建议重写 equals() 一定要重写 hashCode()。
另外,如果两个对象相等,那么调用 equals() 返回 true,hashCode 也相等。
而如果 hashCode 相等,两对象却不一定相等,可能出现了散列冲突。
final
修饰类,表示被不可继承。修饰方法,表示不可被重写。从而保护类的基础功能不被篡改。
修饰变量:
- 对于基础类型,使其数值不变,可用于保护只读数据,减少额外的同步开销。
- 对于引用类型,使其引用不变,但对象本身行为不受影响。
异常
Exception、Error
Exception 和 Error 都是 Throwable 的子类,区别在于:
- Exception 表示程序可以处理的异常,可以捕获且可能恢复,分为受检查异常和运行时异常:
- 受检查异常:必须显式地进行捕获处理,否则编译不通过,如 IOException 等;
- 运行时异常:通常是可以编码避免的逻辑错误,编译能通过,但是一运行就终止,如 NullPointerException、ClassCastException(类型转换)、ConcurrentModificationException 等。
- Error 表示程序无法处理的错误,多跟 JVM 相关,如 OutOfMemoryError(内存溢出)、StackOverflowError(栈溢出) 等。
finally
finally 是异常处理语句 try-catch-finally 中的一部分,用于保证其中的代码总是被执行。
不执行情况:程序在进入 try 前就异常,或者在 try 中执行 exit() 强制退出了。
- 当 finally 有 return 时,无论 try、catch 是否有,最终返回的都是 finally 中 return 的值。
- 当 finally 无 return,try、catch 有时,程序会先将返回值暂存,等执行完 finally 再返回;如果返回值是基础类型,那么 finally 对它无影响,如果是引用类型,因为暂存的是内存地址,所以会返回 finally 对它的修改值。