变量类型有:
- 类变量:独立于方法之外的变量,用 static 修饰;
- 实例变量:独立于方法之外的变量,不过没有 static 修饰;
- 局部变量:类的方法中的变量。
变量对比:
| 类变量(也叫静态变量) | 实例变量(也叫成员变量) | 局部变量 |
|---|---|---|
| 类变量声明在方法、构造方法和语句块之外,并且以 static 修饰。 | 实例变量声明在方法、构造方法和语句块之外。 | 局部变量声明在方法、构造方法或者语句块中。 |
| 类变量在第一次被访问时创建,在程序结束时销毁。 | 实例变量在对象创建的时候创建,在对象被销毁的时候销毁。 | 局部变量在方法、构造方法、或者语句块被执行的时候创建,当它们执行完成后,变量将会被销毁。 |
| 类变量具有默认值。 数值型变量的默认值是 0,布尔型变量的默认值是 false,引用类型变量的默认值是 null。 变量的值可以在声明时指定,也可以在构造方法中指定。 静态变量还可以在静态语句块中初始化。 |
实例变量具有默认值。 数值型变量的默认值是 0,布尔型变量的默认值是 false,引用类型变量的默认值是 null。 变量的值可以在声明时指定,也可以在构造方法中指定。 |
局部变量没有默认值,所以必须经过初始化,才可以使用。 |
| 类变量存储在静态存储区。 | 实例变量存储在堆。 | 对于局部变量,如果是基本类型,会把值直接存储在栈;如果是引用类型,会把其对象存储在堆,而把这个对象的引用(指针)存储在栈。 |
| 访问修饰符可以用于类变量。 | 访问修饰符可以用于实例变量。 | 访问修饰符不能用于局部变量。 |
| 局部变量只在声明它的方法、构造方法或者语句块中可见。 | 实例变量对于类中的方法、构造方法或者语句块是可见的。 一般情况下应该把实例变量设为私有。通过使用访问修饰符可以使实例变量对子类可见。 |
与实例变量具有相似的可见性。 为了对类的使用者可见,大多数静态变量声明为 public 类型。 |
| 静态变量可以通过:ClassName.VariableName 的方式访问。 | 实例变量可以直接通过变量名访问。但在静态方法以及其他类中,就应该使用完全限定名:ObejectReference.VariableName。 | |
| 无论一个类创建了多少个对象,类只拥有类变量的一份拷贝。 | ||
| 类变量除了被声明为常量外很少使用。 |
变量修饰符:
- 访问控制修饰符:public、protected、default、private。
- public:对所有类可见。使用对象:类、接口、变量、方法;
- protected:对同一包内的类和所有子类可见。使用对象:变量、方法。 注意:不能修饰类(外部类);
- default:仅在同一包内具有作用域;
- private:在同一类内可见。使用对象:变量、方法。注意:不能修饰类(外部类)
| 修饰符 | 当前类 | 同一包内 | 子孙类(同一包) | 子孙类(不同包) | 其他包 |
|---|---|---|---|---|---|
| public | ✔ | ✔ | ✔ | ✔ | ✔ |
| protected | ✔ | ✔ | ✔ | ✔/❌ | ❌ |
| default | ✔ | ✔ | ✔ | ❌ | ❌ |
| private | ✔ | ❌ | ❌ | ❌ | ❌ |
- 非访问控制修饰符:final、abstract、static、synchronized、...etc
- final:如果变量使用 fianl 修饰符,就表示这是一个常量,不能被修改;
- static:如果变量是类变量,需要添加 static 修饰;
简介:
- 特性:
- 数组的定义和使用通过方括号
[]; - Java 中,数组是一种引用类型;
- Java 中,数组是用来存储固定大小的同类型元素。
- 数组的定义和使用通过方括号
- 数组和容器:
- 大多数情况下,应该选择容器存储数据;
- 数组的效率要高于容器;
- 数组可以持有值类型,而容器则不能。
- Java 数组本质是对象。
- 数组和内存:
操作:
- 声明数组:
int[] arr; // 推荐风格int arr[]; // 效果相同
- 创建数组:
int[] array = new int[2]; // 指定数组维度int[] array = new int[] {1, 2}; // 不指定数组维度- Java 数组的数组维度可以是常量、变量、表达式,主要转换为整数即可;
- 数组过大,可能会导致栈溢出,超过一定数值编译器就会报错;
- 指定数组唯独后,无论后面是否初始化数组中的元素,数组都已经开辟了相应的内存。
- 通过在
[]中指定下标,访问数组元素,下标位置从 0 开始。 - 数组可以作为函数的入参或返回值。
多维数组:
- 多维数组可视为数组的数组。
Arrays:
- Arrays 类是一个很有用的数组工具类;
- sort - 排序;
- binarySearch - 查找;
- equals - 比较;
- fill - 填充;
- asList - 转列表;
- hash - 哈希;
- toString - 转字符串。
选择:
- if,else,else-if;
- switch
循环:
- while:只要条件成立就一直循环;
- do...while:do...while 循环至少会执行一次。
- for:① 最先执行初始化步骤,声明一种类型,可以初始化一个或多个循环控制变量,也可以为空。
for(;;)都是可以的。② 检测布尔表达式的值,true 循环体被执行,false 跳过循环体内的语句,执行后续语句。③ 执行一次循环后,更新循环控制变量,再次检测,执行上述的过程。 - foreach:声明新的局部变量,该变量必须和数组元素的类型匹配,作用域限定在循环语句块,值与此时的数组元素值相等。
中断(⬆ 终止循环):
- breack:跳出循环提,继续执行循环外的函数体;
- continue:跳出本次循环继续下一次循环;
- return:跳出整个函数体,函数体后面的部分不再执行。
实践中:
- 选择分支特别多的情况下,switch 语句优于 if...else 语句;
- switch 语句不要吝啬(偷懒)使用 default;
- switch 语句中的 default 放在最后;
- foreach 循环有先于传统的 for 循环;
- 不要循环遍历容器元素,然后删除特定元素。应该是遍历容器的迭代器(Iterator),删除元素。
Why Need 泛型?
- 编译时的强类型检查;
- 避免了类型转换;
- 泛型编程可以实现通用算法。
泛型类型:
-
泛型类型是被参数化的类或接口;
-
泛型类:
- 使用泛型时候,必须在创建对象的时候指定类型参数的值;
- 语法形式:
class name<T1, T2, T3, ..., Tn> { /* */}。
-
泛型接口 - 语法形式:
public interface Content<T> { T text(); }
泛型方法:
-
是否拥有泛型方法,与其所在的类是否是泛型没有关系;
-
使用泛型方法的时候,通常不必指明类型参数,编译器会找出具体类型。这称为类型参数推断(type argument interface),类型推断只对赋值操作有效,其他时候并不起作用。
-
泛型方法中也可以使用可变参数列表;
-
语法形式:
public <T> T func(T obj) { }
类型擦除:
- Java 泛型是使用类型擦除来实现的,使用泛型时任何具体的类型信息都被擦除了;
- 泛型不能用于显式引用运行时类型的操作中,如:转型、interfaceof 操作和 new 表达式,因为所有关于参数的类型信息都会丢失。
- 泛型类型无法向上转型。
泛型和继承:
- 泛型不能用于显式引用运行时类型的操作中,如:转型、interfaceof 操作和 new 表达式,因为所有关于参数的类型信息都会丢失;
- 泛型类型无法向上转型。
类型边界:
-
类型边界可以对泛型的类型参数设置限制条件;
-
语法形式:
<T extends Comparable <T>>
类型通配符:
上界通配符和下界通配符不能同时使用。
- 上界通配符:
- 可以使用上界通配符来缩小类型参数的类型范围;
- 语法形式:
<? extends Number>
- 下界通配符:
- 下界通配符将未知类型限制为该类型的特定类型或超类类型;
- 语法形式:
<? super Number>
- 无界通配符:
- 应用场景
- 可以使用 Object 类中提供的功能来实现的方法;
- 使用不依赖于类型参数的泛型类中的方法。
- 语法形式:
<?>
- 应用场景
- 泛型不能向上转型,但是可以通过使用通配符来向上转型。
泛型约束:
- 泛型类型的类型参数不能是值类型;
- 不能创建类型参数的实例;
- 不能声明类型为类型参数的静态成员;
- 参数类型不能使用类型转换或 instanceof;
- 不能创建参数类型的数组;
- 不能创建 catch 或 throw 参数化类型对象;
- 仅仅是泛型类相同,参数类型不同的方法不能重载。
最佳实践:
-
泛型命名:
- E -> Element;
- K -> Key;
- N -> Number;
- T -> Type;
- V -> Value;
- S/U/V etc. -> 2nd/3rd/4th types;
-
使用泛型的建议:
- 消除类型检查警告;
- List 优先于数组;
- 优先考虑使用泛型来提高代码通用性;
- 优先考虑泛型方法来限定泛型的范围;
- 利用有限制通配符来提升 API 的灵活性;
- 优先考虑类型安全的异构容器。
注解简介:
形式:
- 注解以 @ 字符开始的修饰符;
- 注解可以包含命名或未命名的属性,并且这些属性有值;
- 如果只有一个名为 value 的属性,那么名称可以省略;
- 如果注解没有属性,则称为标记注解。
什么是注解:
- 注解是一种标签,其实质上可以视为一种特殊的注释,如果没有解析它的代码,它并不比普通注释强;
- 解析注解的形式:
- 编译期直接的扫描 -> 适用于 JDK 内置的注解;
- 运行期的反射 -> 适用于自定义注解类。
注解的作用:
- 编译器信息:编译器可以使用注解来检测错误或抑制警告;
- 编译时和部署时的处理:程序可以处理注解信息以生成代码、XML 文件等;
- 运行时处理:可以在运行时检查某些注解并处理。
注解的代价:
- 侵入式编程,增加耦合度;
- 处理注解,利用了反射技术,破坏了封装性;
- 注解所产生的问题,相对而言更难以 debug 或定位。
注解的应用范围:
- 注解可以应用于类、字段、方法和其他程序元素的声明;
- 类实例初始化表达式:
new @Interned MyObject();
- 类型转换:
myString = (@NonNull String) str;
- 实现接口的声明:
class UnmodifiableList<T> implements @Readonly List<@Readonly T> {}
- 抛出异常声明:
void monitorTemperature() throw @Critical TemperatureException{}
元注解:
元注解的作用就是用于定义其它的注解。相关类型所支持的类在 java.lang.annotation 包中可以找到。
@Retention:
- 作用:用于约束被描述的注解的作用范围;
- RetentionPolicy 参数:
- SOURCE:在源文件中有效,当执行 javac 命令时将会去除该注解;
- CLASS:在 class 文件中有效,当执行 Java 命令时会去除该注解;
- RUNTIME:在运行时有效,可以通过反射动态获取该注解。
@Documented:
- 作用:用于指定 javadoc 生成 API 文档时显示该注解信息,它是一个标记注解没有成员。
@Target:
- 作用:用于约束被描述注解的使用范围,当被描述的注解超出使用范围则编译失败;
- ElementType 参数:
- CONSTRUCTOR:用于描述构造器;
- FIELD:用于描述域;
- LOCAL_VARIABLE:用于描述局部变量;
- METHOD:用于描述包;
- PARAMETER:用于描述参数;
- TYPE:用于描述类、接口(包括注解类型)或者 enum 声明。
@Inherited:
- @Inherited 表示注解类型可以被继承(默认情况下不是这样)。
@Repeatable:
- @Repeatable 表示注解可以重复使用。
内置注解
@Override:
- 作用:用于发明被修饰方法覆写了父类的方法。
@Deprecated:
- 作用:用于标明被修饰的类或类成员、类方法已经废弃、过时,不建议使用;
@Deprecated有一定的延续性:如果我们在代码中通过集成或者覆盖的方式使用了过时的类或类成员,即使子类或子方法没有标记为@Deprecated,但编译器仍然会告警。
@SuppressWarnings:
https://docs.oracle.com/javase/8/docs/api/java/lang/SuppressWarnings.html
- 作用:用于关闭对类、方法、成员编译时的特定警告;
- 参数:
- deprecation:抑制与淘汰相关的警告;
- unchecked:抑制与未检查的作业相关的警告;
- fallthrough:抑制与 switch 表达式中遗漏 break 相关的警告;
- ......
@SafeVarargs:
- 作用:告诉编译器,在可变长参数中的泛型是类型安全的,可变长参数是使用数组存储的,而数组和泛型不能很好的混合使用。
- 使用范围:
- @SafeVarargs 注解可以用于构造方法;
- @SafeVarargs 注解可以用于 static 或 final 方法。
@FunctionalInterface:
- 作用:告诉编译器被修饰的接口是一个函数接口;
- 如果一个接口符合函数接口定义,不加
@FunctionalInterface也没关系;但如果编写的不是函数式接口,却使用@FunctionalInterface那么编译器会报错。 - 什么是函数式接口:
- 函数式接口(Functional Interface)是一个有且仅有一个抽象方法,但是可以有多个非抽象方法的接口,函数式接口可以被隐式转换 Lambda 表达式。
- 特点:
- 有且只有一个抽象方法(抽象方法只有方法定义,没有方法体);
- 不能在接口中覆写 Object 类中的 public 方法(写了编译器也会报错);
- 允许有 default 实现方法。
自定义注解:
注解的定义:public @Interface 注解名(定义体)。 注解属性:
-
语法形式:
String value() default "";
-
在注解中定义属性时,属性后面需要加
(); -
定义注解属性要点:
- 注解属性只能使用 public 或默认访问级别(即不指定访问级别修饰符)修饰。
- 注解属性的数据类型有限制要求:
- 所有基本数据类型(int、float、boolean、byte、double、char、long、short)
- String 类型
- Class 类型
- enum 类型
- Annotation 类型
- 以上所有类型的数组
- 注解属性必须有确定的值,建议指定默认值;
- 如果注解中只有一个属性值,最好将其命名未 value。
注解处理器:
java.lang.annotation.Annotation是一个接口,程序可以通过反射来获取指定程序元素的注解对象,通过注解对象来获取注解里面的元数据;- Java 中支持注解处理器接口 java.lang.reflect.AnnotatedElement,该接口代表程序中可以接受注解的程序元素;
- AnnotatedElement 接口方法:
- getAnnotation:返回该程序元素上存在的、指定类型的注解,如果注解不存在则返回 null;
- getAnnotations:顾名思义,返回该程序元素上存在的所有注解;
- isAnnotationPresent:判断该程序元素上是否包含指定类型的注解,存在则返回 true,否则放回 false;
- getDeclaredAnnotations:返回直接存在于此元素上的所有注释。与此接口中的其他方法不同,该方法将忽略集成的注释。(如果没有注释直接存在于词元素上,则返回长度为零的一个数组)。该方法的调用者可以随意修改返回的数组,这不会对其他调用者返回的数组产生任何影响。
- AnnotatedElement 接口主要实现类:
- Class:类定义;
- Constructor:构造器定义;
- Field:类的成员变量定义;
- Method:类的方法定义;
- Package:类的包定义。
简介:
- 序列化(serialize):将对象转换为字节流;
- 反序列化(deserialize):将字节流转换为对象;
- 序列化用途:
- 可以将对象的字节序列持久化 ———— 保存在内存、文件、数据库中;
- 用于网络上传送对象的字节序列;
- RMI(远程方法调用)。
- 对象序列化不回关注类中的静态变量。
序列化和反序列化:
- Java 通过对象输入输出流来实现序列化和反序列化:
- 序列化:java.io.ObjectOutputStream 类的 writeObject() 方法可以实现序列化;
- 反序列化:java.io.ObjectInputStream 类的 readObject() 方法可以实现反序列化;
Serializable:
- 被序列化的类必须属于 Enum、Array 和 Serializable 类型其中的任何一种;
- 如果不是 Enum、Array 的类,如果需要序列化,必须实现 java.io.Serializable 接口,否则将抛出 NotSerializableException 异常。
- serialVersionUID:
- serialVersionUID 是 Java 为每个序列化类产生的版本标识;
- 建议在每一个序列化类中显示指定 serialVersionUID 的值,因为不同 JDK 编译很可能会生成不同 serialVersionUID 默认值,从而导致在反序列化时抛出 InvalidClassExceptions 异常;
- serialVersionUID 字段必须是 static final long 类型。
默认序列化机制:
- 如果仅仅只是让某个类实现 Serializable 接口,而没有其他任何处理的话,那么就是使用默认序列化机制;
- 使用默认机制在序列化对象时,不仅会序列化当前对象本身,还会对其父类的字段以及父类该对象引用的其它对象也进行序列化;同理这些其他对象引用的另外对象也将被序列化,以此类推,过程会比较复杂,开销较大。
非默序列化机制:
- transient 关键字:当某个字段被声明为 transient 后,默认序列化机制会忽略该字段;
- Externalizable 接口:
- Externalizable 继承于 Serializable,当使用该接口时,序列化的细节需要由程序员去完成;
- 如果使用 Externalizable 进行序列化,当读取对象时,会调用被序列化类的无参构造方法去创建一个新的对象,然后再将被保存对象的字段值分别填充到新对象中。
- Externalizable 接口替代方法:实现 Serializable 接口,并添加 writeObject 与 readObject 方法;
- readResolve 为了能再单例类中仍然保持序列的特性,可以使用 readResolve() 方法。
序列化问题:
- Java 官方的序列化性能不高,序列化后的数据相对于一些优秀的序列化工具还要大,影响存储和传输的效率;
- Java 官方的序列化一定要实现 Serializable 接口;
- Java 官方的序列化需要关注 serialVersionUID;
- 无法跨语言使用。
序列化工具:
- thrift、protobuf -> 适用于对性能敏感,对开发体验要求不高的内部系统;
- hessian -> 适用于对开发体验敏感,性能有要求的内外部系统;
- jackson、gson、fastjson -> 适用于对序列化后的数据要求有良好的可读性(转为 JSON、XML 形式)。
