语言特性
final
概述
可修饰的位置有3处:
- 修饰类表示不可被继承
- 修饰方法表示不可被子类覆盖,但是可以重载
- 修饰变量表示一旦被赋值就不可以更改它
修饰变量的特性
修饰成员变量
- 如果修饰的是类变量,则必须在声明时指定初始值或者在静态代码块中指定
- 如果修饰的是成员变量则可以在声明时指定、构造器中指定、非静态代码块中指定 即可以先声明,在之后代码中使用之前设置初始值
修饰局部变量
逻辑类似上面修饰成员变量,可以将声明和赋值拆分为两部,即在使用之前赋值。当同时使用 final static 时需要在声明的同时就赋值。没有使用 static 则可以先声明再赋值。
修饰基本类型和引用类型
修饰基本类型时一旦赋值就不可修改,修饰引用类型则指向的地址不变,但是引用类型中的属性值可以被修改。如果引用类型是数组则数组的首地址不变,其内的元素都可以被修改。
局部内部类和匿名内部类都只能访问 final 变量

上图中虽然 new Thread() 处于类 Test 内,但是java代码编译后会产生独立的两个 class ,并且 Test 的结束并不能结束匿名内部类,此时匿名内部类引用了 Test 中的变量,通过 final 保证匿名内部类使用的地址是合法可以访问的。
下面是局部内部类的演示


虽然 InClass 在方法 outPrint 方法内,其并不会因为方法的结束而被销毁。上面代码的 age 被赋值为局部内部类的属性进而被内部类访问。
重载和重写
重载
- 发生在一个类中
- 多个方法重名,参数类型不同、个数不同、顺序不同,访问修饰符和返回值也可以不同
- 如果单单只有返回值类型不同则不是重载,编译时报错
重写
- 发生在子类对于父类的情况下
- 方法名称相同,参数类型、个数、顺序相同
- 返回值类型小于等于父类、抛出的异常小于等于父类、访问修饰符大于等于父类 如果父类方法访问修饰符是 private 则不可重写
接口和抽象类
- 抽象类只能单继承,接口可以多实现
- 抽象类可以存在普通成员函数,而接口只能 public abstract 方法
- 抽象类可以存在各种类型的成员变量,而接口只能常量 - public static final
- 抽象类是对类的本质抽象,大而全,目的是代码复用。所有子类共用的方法提取到抽象类中实现,特有的方法在各个子类中实现。英文中是 is 的关系,比如 benz is a car
- 接口是对类的行为进行约束,约束有该行为,不关心如何实现该行为。英文表达是 like a 的关系,例如 bird like a aircraft .它像一个飞行器
static 与 final 的区别
概述
static 的属性存放在栈上,多个实例对象都指向该地址空间,即共用一个变量。final 是最终的意思,即不可修改,一旦赋值就不可再修改。
案例
public class Demo1 {
public static void main(String[] args) {
MyClass myClass1 = new MyClass();
MyClass myClass2 = new MyClass();
System.out.println(myClass1.i);//0.3222977275463088
System.out.println(myClass2.i);//0.2565532218939688
System.out.println(myClass1.j);//0.36856868882926397
System.out.println(myClass2.j);//0.36856868882926397
}
}
class MyClass {
public final double i = Math.random();
public static double j = Math.random();
}
说明每个对象都有自己的 final 属性 - 成员变量,其值可能不同,而 static 属性则是多个对象间共用的
static 与代码块

- 静态代码块在类加载时执行一次,且之后多次创建对象时不会执行
- 非静态代码块在每次创建对象时都会执行一次
- 两种代码块都在执行构造方法之前执行
hashCode,==,equals的区别
equals与==
== 检测两个变量/实例对象是否指向同一个内存空间,即进行内存地址的比较,如果相同则返回true,相反则返回 false 。而 equals 是比较两个变量/实例对象 的值是否相同。可以参照下图帮助理解

String s1 = "abc" 和 String s2 = new String("abc") 的区别 由于 java 的设计中存在概念“常量池”,前者的方式声明一个变量,java 会到常量池中寻找是否已经有值是 "abc" 的内存地址了,如果有则将 s1 指向该地址,即不会创建新的内存地址来保存 "abc",达到重复利用内存的效果,而后者通过 new 来创建的 s2 则不会查询常量池,无条件的创建新的内存地址并向其写入 "abc"
hashCode 与 equals
equals
Object 中的方法 equals 实际使用的是 ==,即判断引用地址是否相同,所以引用类型如果要判断内容是否相同应该要重写该方法。
hashCode
- 类似 euqals,hashCode 也是定义在 Object 中的方法
- hashCode 称作散列码,它返回一个 int 整数,其作用是确定在哈希表中的索引位置
- HashSet 在加入一个对象时先计算该对象的 hashcode,判断需要加入的位置是否有值,如果没有则直接加入,如果有再调用对象的 equals 方法进一步确认该位置上的对象和即将被加入的对象是否相等。
充分必要条件
- 两个对象相等则 hashcode 一定相同
- 两个对象相等,则对二者分别调用 equals 都返回 true
- 两个对象的 hashcode 相同并不一定相等
- equals 方法被覆盖过,则 hashcode 方法也必须被覆盖
内部类
内存泄露
创建非静态内部类对象时,该对象会持有外部类的引用,导致垃圾回收器无法回收这个外部类的资源,而使用 static 修改内部类,改造为静态内部类后在创建该静态内部类的对象,则该对象不会持有外部类的引用,就不会造成内存泄露。

Annotation
注解元数据
Target
在自定义注解类头上使用注解 @Target(ElementType.TYPE)表示该注解只可以用在类头上,把后面的 Type 更换为 Field 表示该注解可以用在类的属性上,多个枚举一起使用的话@Target(ElemntType.TYPE,ElementType.FIELD)
Retention
本元数据表示当前自定义注解的使用环境,分别是:SOURCE、CLASS、RUNTIME 前者表示该注解只会在源码中显示,编译后就看不到了,中间 CLASS 表示源码和编译后可以看到,但是运行时无法通过反射获取。后者表示运行时可以通过反射获取。通常都使用该选项,在项目运行中通过反射获取其他信息进而执行逻辑,自定义注解才有意义。本元数据的使用见下面代码。
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Table {
public String tableName();// 表名称
public String orderBy() default "";// 排序字段
public OrderPolicy orderPolicy() default OrderPolicy.ASC;// 升序、降序
}
使用案例
实体类头上使用注解
看下面代码,将上面的自定义注解的3个属性都使用了,中间使用逗号间隔。
@Table(tableName = "billtype",orderBy = "id",orderPolicy = OrderPolicy.ASC)
public class Billtype implements Serializable {
实体类属性上使用注解
下面是自定义的为类属性设计的自定义注解
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface TableField {
public String fieldName();
public boolean isPK() default false;
public String dataType() default "";
}
在实体类的属性上的使用案例如下:
@TableField(fieldName = "id",isPK = true,dataType = "INTEGER")
private Integer id;
@TableField(fieldName = "sId")
private String sId;
@TableField(fieldName = "accessMode")
private Integer accessMode;
反射获取注解信息
获取自定义注解属性值
下面演示上面自定义注解@Table 的使用,获取实体类关联的表名称
Class<?> clazz = record.getClass();
Table tableAnno = clazz.getAnnotation(Table.class);
String tableName = tableAnno.tableName();
反射获取实体类属性
下面演示如何获取上面的自定义注解 TableField
Class<?> objClazz = obj.getClass();// 通过对象的实体类
Field[] fields = objClazz.getDeclaredFields();// 获取实体类的所有属性
String attributeName = field.getName();// 获取属性名称(小驼峰样式的属性)
// 获取属性头上的注解 TableField
TableField tableFieldAnno = field.getAnnotation(TableField.class);
// 获取属性注解中的字段名称,一般是DB中的字段带下划线
String fieldName = tableFieldAnno.fieldName();
构建 getter / setter 方法
将属性的首字母大写,构建并返回 getter 方法名称
public static String getGetterMethodName(Object obj,Field field){
String fieldName = field.getName();
return "get" + fieldName.substring(0,1).toUpperCase() + fieldName.substring(1);
}
垃圾回收
为什么要有垃圾回收
jvm 回收垃圾内存则不需要程序员管理内存,相比于 c 系列的语言,更不容易发生内存泄露
回收哪些内存
没有被 GC Roots 引用的对象都是被回收的对象

GC Roots 引用的对象有:
- 虚拟机栈中的引用对象
- 方法区中的类静态属性引用的对象
- 方法区中的常量引用的对象
- 本地方法栈中JNI(Native方法)引用的对象
垃圾回收算法
Marking-Sweep - 标记清除法
会产生碎片


Marking-Compat - 标记整理法
不会产生碎片

年轻代与老年代

jvm 分配内存时先向年轻代的 eden 区域分配

如果没有空间则将被引用的对象转移到 S0 区域,然后在 eden 区域分配

再没有空间则将 S0 的对象转移到 S1中同时年龄增长

S0 与 S1 中的对象在来回转移位置时都会增加年龄

年龄超过8会被移动到老年代中

垃圾回收器的种类

