跳至主要內容

语言特性

chanchaw大约 9 分钟languagejava

final

概述

可修饰的位置有3处:

  • 修饰类表示不可被继承
  • 修饰方法表示不可被子类覆盖,但是可以重载
  • 修饰变量表示一旦被赋值就不可以更改它

修饰变量的特性

修饰成员变量

  • 如果修饰的是类变量,则必须在声明时指定初始值或者在静态代码块中指定
  • 如果修饰的是成员变量则可以在声明时指定、构造器中指定、非静态代码块中指定 即可以先声明,在之后代码中使用之前设置初始值

修饰局部变量

逻辑类似上面修饰成员变量,可以将声明和赋值拆分为两部,即在使用之前赋值。当同时使用 final static 时需要在声明的同时就赋值。没有使用 static 则可以先声明再赋值。

修饰基本类型和引用类型

修饰基本类型时一旦赋值就不可修改,修饰引用类型则指向的地址不变,但是引用类型中的属性值可以被修改。如果引用类型是数组则数组的首地址不变,其内的元素都可以被修改。

局部内部类和匿名内部类都只能访问 final 变量

final01
final01

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

下面是局部内部类的演示

final02
final02
final03
final03

虽然 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 与代码块

static与代码块
static与代码块
  1. 静态代码块在类加载时执行一次,且之后多次创建对象时不会执行
  2. 非静态代码块在每次创建对象时都会执行一次
  3. 两种代码块都在执行构造方法之前执行

hashCode,==,equals的区别

equals与==

== 检测两个变量/实例对象是否指向同一个内存空间,即进行内存地址的比较,如果相同则返回true,相反则返回 false 。而 equals 是比较两个变量/实例对象 的值是否相同。可以参照下图帮助理解

==和equals的区别
==和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 引用的对象都是被回收的对象

垃圾回收01
垃圾回收01

GC Roots 引用的对象有:

  1. 虚拟机栈中的引用对象
  2. 方法区中的类静态属性引用的对象
  3. 方法区中的常量引用的对象
  4. 本地方法栈中JNI(Native方法)引用的对象

垃圾回收算法

Marking-Sweep - 标记清除法

会产生碎片

垃圾回收02
垃圾回收02
垃圾回收03
垃圾回收03

Marking-Compat - 标记整理法

不会产生碎片

垃圾回收04
垃圾回收04

年轻代与老年代

垃圾回收05
垃圾回收05

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

垃圾回收06
垃圾回收06

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

垃圾回收08
垃圾回收08

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

垃圾回收09
垃圾回收09

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

垃圾回收10
垃圾回收10

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

垃圾回收11
垃圾回收11

垃圾回收器的种类

垃圾回收12
垃圾回收12