1、基本数据类型
1.1 Java是强类型语言
- 所有变量必须先声明后使用。
- 指定类型的变量只能接受与它类型匹配的值。
1.2 Java的类型分类
- 基本类型
- 引用类型
public class Main {
public static void main(String[] args) {
// 基本数据类型
byte a_byte = 1;
short a_short = 1;
int a_int = 1;
long a_long = 1;
float a_float = 1.0f;
double a_double = 1.0;
boolean a_boolean = true;
char a_char = 'c';
}
}
1.3 类型转换
1.3.1 自动类型转换
范围小可以自动转化为范围大的数据类型。
1.3.2 强制类型转换
范围大的向范围小转换。
语法:
(targetType)value
1.3.3 表达式类型的自动提升
- 所有的byte short char 提升到int
- 数据类型自动提升为最高等级操作数的类型
1.4 直接量
能指定直接量的三种类型: 基本类型,字符串类型,null类型。
tips: 如果程序第一次使用字符串直接量,java会使用常量池(constant pool)来缓存改字符串直接量。后面在使用该字符串直接量,会直接使用常量池中的字符串直接量。
1.5 运算符
1.5.1 算术运算符
+ - * / += -= *= /=
2、流程控制
public static void main(String[] args) {
// if else
int score = 50;
if (score > 90) {
System.out.println("优秀");
} else if (score > 70) {
System.out.println("中等");
} else if (score > 60) {
System.out.println("及格");
} else {
System.out.println("差的");
}
// 只能是 byte short char int enum String 不能是boolean
// 可以省略 case 后面的花括号
switch (score) {
case 50:
System.out.println("50分");
System.out.println("不够优秀");
break;
default:
System.out.println("考了 = " + score);
System.out.println("优秀吗");
break;
}
// while
int count = 10;
while (count < 20) {
System.out.println("---------" + count);
count++;
}
// do while
do {
System.out.println("----------" + count);
count--;
} while (count < 10);
// for
for (int i = 0; i < 10; i++) {
System.out.println("i = " + i);
}
}
3、 面向对象
3.1) 基本语法
public class Person {
// filed
private static String country;
private int age = 0;
private float height;
private boolean sex;
// 非静态变量的初始化代码块
{
System.out.println("非静态初始化代码块");
age = 10;
height = 12.9f;
sex = true;
}
// 静态变量的初始化块
static {
System.out.println("静态初始化代码块");
country = "china";
}
// 构造方法,如果不定义构造方法,系统自动会生成一个默认的没有参数的构造方法
public Person(int age, float height, boolean sex) {
this.age = age;
this.height = height;
this.sex = sex;
}
// 静态方法
public static void say() {
System.out.println("I'm " + country);
}
// 可变参数,values相当于一个数组
public void eat(Object... values) {
for (Object value : values) {
System.out.println(value);
}
}
// 方法的重装,方法名相同,参数列表不同,返回值类型,和修饰符和方法重载没有关系
public void work(String job) {
System.out.println("job is" + job);
}
public void work(String job, String where) {
System.out.println("at " + where + "job is" + job);
}
@Override
public String toString() {
return ("age =" + age + "height =" + height + "sex =" + sex);
}
public static void main(String[] args) {
Person person = new Person(10, 121.0f, false);
System.out.println(person);
}
}
局部变量定以后,必须显示初始化才能使用。
成员变量定义后,不必初始化,系统会给一个默认值。
3.1.1)访问权限修饰符:
public > protected > default > private
一个类就是一个小模块,在程序设计时,应尽量避免一个模块直接操作和访问另一个模块的数据,模块设计要求高内聚,低耦合。
// 声明包
package 包名
// 导包
import 包
// 静态导入,导入静态变量,静态方法
import static 包
java中常用的包
java.lang
java.util
java.net
java.io
java.text
java.sql
3.1.2)继承
java的类只能有一个直接父类。
子类可以复写父类的方法,遵循一个原则:两同两小一大。
两同:方法名相同,形参列表相同。
两小:子类方法返回值类型应比父类方法返回值类型更小或者相等,子类方法声明抛出的异常比父类方法声明抛出的异常更小或者相等。
一大:子类方法的访问权限应比父类方法的访问权限更大或者相等。
3.1.3 ) super
public class SubClass extends SuperClass {
private int a;
public SubClass(int a) {
// 调用父类被复写的方法
super(a);
}
@Override
public void test() {
// 调用父类被隐藏的变量
System.out.println(super.a);
}
}
class SuperClass {
public int a;
public SuperClass(int a) {
this.a = a;
}
public void test() {
System.out.println(a);
}
}
3.2) 多态
引用变量有两种类型:一个编译时类型,一个是运行时类型。
class Person {
private String name;
public Person(String name) {
this.name = name;
}
public void say() {
System.out.println("I'm " + name);
}
}
public class Man extends Person {
private int age;
public Man(String name, int age) {
super(name);
this.age = age;
}
@Override
public void say() {
super.say();
System.out.println("age is " + age);
}
public static void main(String[] args) {
// 编译时 Person 运行时 Man
Person person = new Man("wangbo", 20);
person.say();
}
}
3.2.1)引用变量的强制类型转换
- 基本类型之间的转换只能在数值类型之间进行。
- 引用类型之间的转换只能在具有继承关系的两个类型之间进行。
3.2.2)instanceof 运算符
注意:
- instanceof是运算符不是一个方法。
- 它的作用是判断前面的对象是否是后面的类或者子类的实例。如果是返回true,否则返回false。
- 前面的操作数编译时类型要么与后面的类型相同,要么与后面的类型具有父子继承关系,否则编译错误。
null instancof Person //返回永远为false,null不是
if (null instanceof Person){
System.out.println(true);
} else {
System.out.println(false);
}
继承破坏封装,建议采用组合实现复用
- 继承是 is-a关系
- 组合是 has-a关系
3.3 初始化块
它是类的第四个成员(成员变量,方法,构造器)。初始化块和构造器的作用类似,但比构造器率先执行。
static {
// 静态初始化块,对类进行操作,同样不能访问非静态成员
}
{
// 普通初始化块,对对象进行操作
}
执行顺序:
静态初始化块 --> 前面执行(普通初始化块,声明实例变量指定的默认值) --> 构造器
普通初始化块和声明实例变量指定的默认值,谁在前面谁先执行。
3.3.1) 初始化块与构造器
- 初始化块是一段固定执行的代码,不能接受任何参数。因此初始化块对同一个类的所有对象所执行初始化处理完全相同。
- 构造器接受参数,不同对象所执行的初始化处理不同。
- 初始化块其实是个假象,编译后初始化块会还原到每个构造器中,且位于构造器所有代码掐面。
java文件
public class InstanceInit {
// 初始化块和声明默认初始化,谁在后面谁的值放到构造器中
{
a = 6;
}
private int a = 9;
public static void main(String[] args) {
System.out.println(new InstanceInit().a);
}
}
class文件
public class InstanceInit {
private int a = 6;
public InstanceInit() {
this.a = 9;
}
public static void main(String[] args) {
System.out.println((new InstanceInit()).a);
}
}
3.4 包装类
基本数据类型对应的类。
byte -> Byte
short -> Short
int -> Integer
boolean -> Boolean
long -> Long
char -> Character
float -> Float
double -> Double
JDK1.5之前,装箱和拆箱比较麻烦,JDK1.5之后能够自动装箱和拆箱
3.4.1) 自动装箱和拆箱
// 自动装箱
Integer inObj = 8;
Object boolObj = true;
// 自动拆箱
int it = inObj;
3.4.2)String与基本类型之间的转换
// 字符串转基本类型
String intStr = "123";
int it1 = Integer.parseInt(intStr);
int it2 = new Integer(intStr);
// 基本类型转换字符串
String ftStr = String.valueOf(2.35f);
3.5 ) toString、== 、equals方法
toString方法在直接打印对象时,自动调用的方法。一般会自定义,来输出想输出的属性
==
基本类型:直接判断值是否相等
-
引用类型:判断引用是否是同一个对象,不能用于比较类型上没有父子关系的两个对象。
[图片上传失败...(image-e4fa44-1571732324683)]
equals()
- equals()方法是Object类提供的一个实例方法。
- 如果不复写的话,和
==
一样也是用来判断是否指向同一个对象。 - 一般复写,提供自定义的相等标准。
public class Person {
private String name;
private String idStr;
public Person(String name, String idStr) {
this.name = name;
this.idStr = idStr;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
// instanceof 运算符前面对象是后面类的实例活其子类的实例都返回true,所有用instanceof判断对象是否为同一个可能有问题
if (obj != null && obj.getClass() == Person.class) {
Person person = (Person) obj;
if (this.idStr.equals(person.idStr)) {
return true;
}
return false;
}
return false;
}
}
3.6)类成员
java类里只能含有成员变量,方法,构造器,初始化块,内部类(接口,枚举)五种。
3.7)final修饰符
final关键字可用于修饰类、变量和方法。
3.7.1)final成员变量
final变量获得初始值之后不能被重新赋值。
final修饰的成员变量必须由程序员指定初始值,系统不会指定默认值。
-
类变量:
- 静态初始化块
- 声明类变量时指定默认值
-
实例变量:
- 非静态初始化块
- 声明实例变量指定默认值
- 构造函数中指定初始值
只能是其中之一初始化
分析原因:
final修饰的成员变量,如果不指定初始值,系统分配默认值,之后不能修改。这些成员变量也就失去了意义,所以系统要求final成员变量必须主动初始化。
3.7.2)final修饰局部变量
局部变量系统不会提供默认值,final修饰的局部变量也可以不指定默认值,后面再指定。
public class Person {
public void test(final int a){
// 不能对final修饰的形参赋值
// a = 5;
}
public static void main(String[] args) {
final String name;
name = "hello";
System.out.println(name);
}
}
3.7.3)final修饰基本类型变量和引用类型变量的区别
public class Person {
public static void main(String[] args) {
// 修饰基本类型,不能修改值
final int age = 10;
// age = 11;
// 修饰引用类型
final List stringList = new ArrayList<>();
// 引用类型的指针地址不变,内容改变
for (int i = 0; i < 10; i++) {
stringList.add("--" + i);
}
}
}
3.7.4) final 方法
final修饰的方法不能被复写,但是可以重载。
public class FinalMethodTest {
public final void test(){}
}
class Sub extends FinalMethodTest{
// 下面方法定义将编译出错,不能复写final方法
public void test(){}
}
public class FinalMethodTest {
private final void test(){}
}
class Sub extends FinalMethodTest{
// 父类private的方法,子类不可见,子类中的test是重定义的
private void test(){}
}
3.7.5)final类
final类不能被继承。
3.7.6)不可变类
- private和final修饰成员变量
- 提供参数构造器,用于传入参数初始化类的成员变量
- 仅为该类的成员变量提供getter方法,不要为该类提供setter方法
- 如有必要,重写hasCode()和equals()方法。
- 线程安全的。
一般不可变量用于数据层的itemInfo。不可变类的引用类型的成员变量,在赋值的时候最好复制一份,避免直接赋值,因为外部可能修改这个引用类型的变量。
public class ImmutableClass {
private final int id;
private final String name;
public ImmutableClass(int id, String name) {
this.id = id;
this.name = name;
}
public int getId() {
return id;
}
public String getName() {
return name;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
ImmutableClass that = (ImmutableClass) o;
return id == that.id &&
Objects.equals(name, that.name);
}
@Override
public int hashCode() {
return Objects.hash(id, name);
}
}
3.8)抽象类
抽象类是从多个类中抽象出来的模板。
- 抽象类被 abstract 修饰,抽象方法也被abstract修饰,不能有方法体。
- 抽象类不能实例化,可以包含成员变量,方法(普通方法和抽象方法)构造器,初始化块,内部类(接口枚举)5种成分。
- 包含抽象方法的类,只能定义成抽象类。
- final 和 abstract 不能共存
- abstract 和 static不能同时修饰某个方法,但却可以修饰内部类。
- abstract 和 private 不能共存。
抽象类的作用: 提供模板,避免子类设计的随意性。
3.8.1)定义
public class Man extends Person {
@Override
void eat(String food) {
System.out.println("eat");
}
@Override
void work() {
System.out.println("work");
}
}
abstract class Person {
abstract void eat(String food);
abstract void work();
void sleep() {
System.out.println("睡觉");
}
}
3.9)接口
类是具体的实现,接口定义时多个类共同的公共行为规范,是与外部交流的通道。
软件设计:通常会采用面向结构的设计,来实现模块之间的低耦合,同时可扩展性和可维护性更好。
接口的特性:
- 接口可以多继承,类只能单继承。
- Java 8以上接口允许定义默认方法,类方法。
- 接口是一种规范,不能包含构造器和初始化块,可以包含承欢变量(只能是静态常量)方法(只能是抽象方法,类方法,默认方法)内部类(包含内部接口,枚举)定义。
- 接口是公共行为的规范,所以所有的成员都是public权限。
- 接口定义的内部类,内部接口,内部枚举都默认采用public static修饰。
- 接口是一种更加抽象的类,所以java源文件里只能有一个public接口且必须与文件重名。
3.9.1)定义
public interface Output {
// int MAX_CACHE_LINE = 50; 系统会自动为接口定义的成员变量增加public static修饰符
public static final int MAX_CACHE_LINE = 50;
// 接口定义的普通方法默认是public抽象方法
void out();
void getData(String msg);
// 接口里的默认方法,需要使用default修饰
default void print(String... msgs) {
for (String msg : msgs) {
System.out.println(msg);
}
}
// 接口里的类方法,需要static修饰
static String staticTest() {
return "接口里的方法";
}
}
3.9.2)接口和抽象类的区别
- 接口是模块与外界通信桥梁,体现的是一种规范。
- 抽象类是一种模板式设计,多个子类的共同父类。
3.10)内部类
- 内部类提供了更好的封装,不允许同一个包下的其他类访问
- 内部类可以直接访问外部的成员,反过来却不行
- 匿名内部类适用于仅需创建一次使用的类。
- 内部类比外部类可以多使用三个修饰符:private protected static,外部类不可以使用
- 非静态内部类,不能拥有静态成员。
- 非静态内部类实例必须寄生在外部类的实例中,如果其他地方需要使用,就没必要设计成内部类了。
成员内部类,局部内部类,匿名内部类,静态内部类,非静态内部类。
静态内部类
- 静态内部类不能访问外部类的非静态成员。
- 可以包含静态成员。
- 只需要把外部类当成静态内部类的包空间,静态内部类和外部类没啥差别。
- 优先使用静态内部类
匿名内部类:
- 一般是通过接口定义的形式实现匿名内部类
- 匿名内部类只有一个默认的无参构造方法
- java 8之前,要求被局部内部类,匿名内部类访问的局部变量必须是final修饰,java8之后取消了限制,符合上述规则,自动添加final修饰符。
3.11)Lambda表达式
用于简化匿名内部类的创建。
函数式接口代表只包含一个抽象方法的接口。函数式接口可以包含多个默认方法,类方法,但只能声明一个抽象方法。
Java8 为函数式接口提供了@FuncaitonInterface注解。
lambda表达式可以用来赋值。
3.12)引用方法
className::类方法
object::实例方法
className::new
3.13)枚举类
- 枚举类可以实现一个或多个接口,enum定义的枚举类默认继承java.lang.Enum类,Enum实现了Serializable和Comparable接口。
- enum定义非抽象的枚举类,默认使用final修饰。因此枚举类不能派生子类
- 枚举类的构造器只能使用private修饰,省略默认是使用private。
- 枚举类的所有势力必须在枚举类的第一行显示列出,否则这个枚举类永远不能产生实例,这些实例系统会自动加上public static final修饰。
- 枚举类默认提供了一个value()方法,该方法可以方面遍历所有的枚举值。
- 枚举类的实例只能是枚举值,而不是随意通过new来创建枚举类对象。
public enum SearchType {
// 此处的枚举值必须调用对应的构造函数来创造
All("all"),
Product("product"),
Article("review");
// 枚举成员一般不可变,这样更安全
private final String mId;
SearchType(String id) {
mId = (id == null ? "" : id.trim().toLowerCase(Locale.US));
}
public String getId() {
return mId;
}
public static SearchType getById(String id) {
id = (id == null ? "" : id.trim().toLowerCase(Locale.US));
for (SearchType type : SearchType.values()) {
if (id.equals(type.mId)) {
return type;
}
}
return null;
}
}
3.14)对象与垃圾回收
- 垃圾回收机制只负责回收堆内存中对象,不会回收任何物力资源
- 程序员无法精确控制垃圾回收的运行,垃圾回收会在合适的时候运行。当对象永久性地失去引用后,系统就会在合适的时候回收它所占内存。
- 在垃圾回收机制回收任何对象之前,总会先调用它的
finalize()
方法,该方法可能使该对象重新复活(让一个引用变量重新引用对象),从而导致垃圾回收机制取消回收。
3.14.1)对象在内存中状态
- 可达状态:当一个对象有一个以上的引用变量引用。
- 可恢复状态:某个对象不再有任何引用变量引用它,它就进入可恢复状态。在这状态下,系统的垃圾回收机制准备回收该对象占用的内存,在回收之前会调用
finalize()
方法进行资源清理,在finalize()
调用时可能重新被引用,变成了可达状态。如果确实没有被引用在进入不可达状态。 - 不可待状态:当对象与所有引用变量的关联都被切断,且系统已经调用了
finalize()
方法后依然没有使该对象编程可达状态,那么这个对象将永久性地失去应用,变成不可达状态。只有不可达状态,系统才会真正回收该对象占用资源。
[图片上传失败...(image-25ef57-1571732324683)]
3.14.2)强制垃圾回收
程序无法精确控制java垃圾回收机制,但是可以强制系统进行垃圾回收 ——这种强制只是通知系统进行垃圾回收,但系统是否进行垃圾回收依然不确定。
- 调用System 类的
gc()
静态方法:System.gc()
- 调用Runtime 对象的
gc()
实例方法:Runtime.getRuntime().gc()
finalize()方法
- 永远不用主动调用某个对象的
finalize()
方法,该方法应交给垃圾回收机制调用。 -
finalize()
方法合适被调用,是否被调用具有不确定性,不要把finalize()
方法当成一定会被执行的方法。 - 当JVM执行科恢复对象的
finilize()
方法时,可能是该对象或者系统中其他对象重新变成可达状态。 - 当JVM执行
finalize()
方法是出现异常时,垃圾回收机制不会报异常,程序继续执行。 -
finalze()
方法不定义会被执行,因此清理某个类的资源,则不要放在finalize()
方法中进行清理。
public class FinalizeClass {
private static FinalizeClass finalizeClass = null;
public void info() {
System.out.println("测试资源清理的finalize方法");
}
public static void main(String[] args) {
new FinalizeClass();
// 通知系统进行资源回收
System.gc();
// 强制垃圾回收机制调用可恢复对象的finalize()方法
// Runtime.getRuntime().runFinalization();
System.runFinalization();
finalizeClass.info();
}
@Override
protected void finalize() throws Throwable {
super.finalize();
// 让对象从可恢复状态变成了可达状态,内存泄漏
finalizeClass = this;
}
}
3.15)对象的强、软、弱、虚引用
- 强引用(StrongReference)最常见的方式。
- 软引用:通过SoftReference类来实现,当一个对象只有软引用是,他有可能被垃圾回收机制回收。垃圾机制运行时,当系统内存空间充足时,不会被系统回收。程序可使用该对象。当系统空间不足时,系统可能回收它,软引用用于内存敏感的程序中。用的比较少。
- 弱引用:通过WeakReference类实现,弱引用和软引用很像。但若引用的引用级别更低。只有弱引用的对象,当垃圾回收机制运行时,不管内存是否充足,他都会被回收。
- 虚引用:PhantomReference类实现。完全类似没有引用。主要用于跟踪对象被垃圾回收的状态。
软、弱、虚都有一个get()
方法,用于获取他们所引用的对象。
4、泛型
4.1) 定义泛型类和接口
public interface Map {
Set keySet();
V put(K key, V value);
}
// 设定泛型上限
public class Apple {
private T info;
public Apple(T info) {
this.info = info;
}
public Apple() {
}
public T getInfo() {
return info;
}
public void setInfo(T info) {
this.info = info;
}
public static void main(String[] args) {
// 菱形语法
Apple apple = new Apple<>("苹果");
Apple doubleApple = new Apple<>(13.0);
}
}
4.2)泛型类派生子类
// public class A extends Apple 错误的做法
// 派生类需要指定具体类型
public class A extends Apple
public class A extends Apple
注意
List 并不是 List的子类
List list1 = new List<>();
List list2 = new List<>();
List list3 = new List<>();
void test(List list){
// do something
}
test(list1); // 正确
test(list2); // 错误
test(list3); // 错误
// ? 泛型通配符
void print(List> list){
// do something
}
print(list1); // 通过
print(list2); // 通过
print(list3); // 通过
// 限定 ? 泛型通配符
void say(List extend Apple> list){
// do something
}
say(list1); // 通过
say(list2); // 通过
say(list3); // 错误
4.3)泛型方法
修饰符 返回值类型 方法名(形参列表){
}
public class TFuncation {
static void fromArrayToCollection(T[] a, Collection c) {
for (T o : a) {
c.add(o);
}
}
public static void main(String[] args) {
Object[] oa = new Object[100];
Collection
5、基础类库
5.1)系统相关
System:
// 获取所有系统变量
System.getenv();
// 获取指定系统变量
System.getenv("JAVA_HOME");
// 获取所有系统属性
System.getProperties();
// 获取指定系统属性
System.getProperty();
// 通知系统进行垃圾回收的
System.gc();
// 通知系统进行资源清理
System.runFinalization();
// 获取系统当前的时间,时间粒度取决于操作系统,
System.currentTimeMillis(); // 毫秒为单位,不准确,操作系统以几十毫秒为单位
System.nanoTime(); // 纳秒为单位,很少用,操作系统不支持
// 根据对象地址计算得到的hashCode值
System.identityHashCode(Object x);
Runtime
// 通知系统进行垃圾回收
Runtime.gc();
// 清理系统资源
Runtime.runFinalization();
// 加载文件
Runtime.load(String fileName);
// 加载动态链接库
Runtime.loadLibrary(String libname);
public class RuntimeTest {
public static void main(String[] args) {
// 获得运行时队形
Runtime runtime = Runtime.getRuntime();
// 处理器数量
System.out.println(runtime.availableProcessors());
// 空闲内存数
System.out.println(runtime.freeMemory());
// 总内存数
System.out.println(runtime.totalMemory());
// 最大内存数
System.out.println(runtime.maxMemory());
}
}
5.2)Clone
public class UserTest {
public static void main(String[] args) throws CloneNotSupportedException {
User user = new User(2);
User user1 = user.clone();
System.out.println(user == user1); // false
System.out.println(user.address == user1.address); // true
}
}
class Address {
String detail;
public Address(String detail) {
this.detail = detail;
}
}
class User implements Cloneable {
int age;
Address address;
public User(int age) {
this.age = age;
address = new Address("北京海淀");
}
public User clone() throws CloneNotSupportedException {
return (User) super.clone();
}
}
clone只是一种浅克隆,只是对引用变量进行复制,对引用变量所引用的对象没有复制。引用变量所指向的内存还是同一个实例。
5.3)新增Objects工具类
Java为工具类的命名习惯是添加一个字母s。Arrays,Collections。
public class ObjectsTest {
static ObjectsTest objectsTest;
private String name;
public ObjectsTest(String name) {
// 用来对方法形参进行输入校验,并自定义空指针异常
this.name = Objects.requireNonNull(name, "name 不能为null");
}
public static void main(String[] args) {
System.out.println(Objects.hashCode(objectsTest));
System.out.println(Objects.toString(objectsTest));
System.out.println(Objects.requireNonNull(objectsTest, "参数不能为null"));
}
}
public static T requireNonNull(T obj, String message) {
if (obj == null)
throw new NullPointerException(message);
return obj;
}
5.4)StringBuffer类
- String是不可变类
- StringBuffer 可变字符串,
append()
,insert()
,reverse()
,setCharAt()
,setLength()
.toString()
线程安全的。 - StringBuilder 可变字符串,和StringBuffer基本类似,没有实现线程安全功能,性能高。优先使用StringBuilder。
5.5)随机数
// 当前时间为随机数种子
Random random = new Random(System.currentTimeMillis());
int val = random.nextInt(10);
// 线程安全的随机数
ThreadLocalRandom threadLocalRandom = ThreadLocalRandom.current();
int val2 = threadLocalRandom.nextInt(10, 100);
5.6)BigDecimal类
- float、double两种基本浮点类型容易引起精度丢失。
- BigDecimal提供静态函数来对float double string等类型进行转换。
- float double不推荐使用构造
- String可以使用构造函数
BigDecimal f1 = new BigDecimal("0.05");
BigDecimal f2 = BigDecimal.valueOf(0.01);
// 不推荐使用
BigDecimal f3 = new BigDecimal(0.05);
System.out.println("0.05 + 0.01 = " + f1.add(f2)); // 0.06
System.out.println("0.05 - 0.01 = " + f1.subtract(f2)); // 0.04
System.out.println("0.05 * 0.01 = " + f1.multiply(f2)); // 0.0005
System.out.println("0.05 / 0.01 = " + f1.divide(f2)); // 5
5.7)Calendar
5.8)正则表达式
// 将字符串编译成pattern对象
Pattern p = Pattern.compile("a*b");
Matcher m = p.matcher("aaaabbbb");
boolean b = m.matches();
Pattern不可变类,可供多个线程并发使用。
-
Mather类提供了常用的方法
find();// 返回目标字符串中是否包含与pattern匹配的字符串 group(); // 返回上一次与Pattern匹配的字符串 start();// 返回上次与pattern匹配的字符串开始位置 end();// 返回上次与pattern匹配的字符串的结束位置 lookingAt(); // 返回目标字符串前面部分与pattern是否匹配 matches(); // 返回目标字符串是否与pattern匹配 reset();// 将现有的Matcher对象应用于新的字符序列。
6、集合
数组是不可变的,集合是可变的。
6.1)集合的常见操作
集合是实现了Collection的接口的,Collection是继承了interator接口的。
Collection collection = new ArrayList();
collection.add("hello");
// 自动装箱
collection.add(6);
//删除指定元素
collection.remove(6);
Collection books = new HashSet();
books.add("轻量级java EE 企业应用之战");
books.add("疯狂Java讲义");
// 删除集合
collection.removeAll(books);
// 清空
collection.clear();
// books集合里剩下Collection也包含的集合
books.retainAll(collection);
// lambda表达式遍历集合
books.forEach(obj -> System.out.println(obj));
6.2)Iterator遍历集合元素
// 迭代器遍历集合,并删除,迭代只是把集合元素的值传递给了迭代变量,修改迭代变量的值对集合元素本身没任何影响
Iterator iterator = books.iterator();
while (iterator.hasNext()) {
String book = (String) iterator.next();
if (book.equals("疯狂Java讲义")) {
iterator.remove();
// books.remove(book)将引发ConcurrentModificationException异常
}
book = "测试字符串";
}
6.3)HashSet类
- 不能保证排序
- 不是同步的
- 集合元素值可以为null
- 根据hashCode决定存储位置
HashSet集合判断两个元素相等的标准是两个对象通过equals()
并且两个对象的hashCode()
方法返回值也相等。
6.3.1) LinkedHashSet类
LinkedHashSet集合也是根据hashCode值来决定元素的存储位置。
它使用链表维护元素的次序,遍历输出有次序
性能低于LinkedHashSet,迭代访问性能较好
LinkedHashSet依然是HashSet,不允许集合元素重复
6.3.2)TreeSet类
是SortedSet接口的实现类,TreeSet可以确保集合元素处于排序状态
-
first()
last()
lower(Object e)
higher(Object e)
subSet()
headSet(Object toElement)
6.3.3) EnumSet类
专为枚举类设计的集合类
也是有序的集合
不允许加入null元素
6.3.4)HashSet和TreeSet性能对比
- HashSet性能总比TreeSet好
- TreeSet需要额外的红黑树算法来维护集合元素的次序。需要排序的Set用TreeSet。
6.4)List集合
- ArrayList不是线程安全的,Vector是线程安全的性能低
7、异常处理
7.1)异常概念
异常处理让程序具有极好的容错性和健壮性。发生异常系统自动生成一个Exception对象来通知程序。
java的异常分为Checked异常和Runtime异常。Checked异常可以在编译阶段被发现。Runtime异常只能在运行期间得到处理。
- try : 可能发生异常的代码块
- catch:用于处理这种类型的异常代码块
- finally:回收try里打开的资源,除非在try catch块里调用退出虚拟机的方法 System.exit(1),不然即使调用return finally块依然执行。避免在finally块里使用return throw等方法。
- throws:方法签名使用
- throw:抛出实际的异常
try {
// 异常由运行时环境抛出
int a = 0 / 2;
} catch (Exception e) {
// 处理异常
} finally {
// 无论如何都会执行
}
try业务逻辑出现异常,系统自动生成异常对象,该异常对象被提交给Java运行时环境,这个过程为抛出(throw)异常。Java运行时环境受到异常,寻找能处理该异常的catch块,找到就交给catch处理,找不到运行时环境终止,Java程序退出。
7.2)异常类
- 异常 Exception:可以用try catch处理。
- 错误 Error : 与虚拟机相关的问题,无法恢复,不能捕获,所以不能用try catch捕获Error、
都继承Throwable。
先捕获范围小的异常,再捕获异常范围大的。
getMessage():异常详细描述
printStackTrace(): 输出跟踪信息栈
printStackTrace(PrintStream s): 输出跟踪信息栈到s流
getStackTrace(): 获得跟踪栈信息
8、注解
注解是一种接口。都有一个 value属性。
JDK5增加了元数据(MetaData)支持,也就是Annotation注解。
注解和Python的装饰器一样的功能,在代码里添加标记,在编译、类加载、运行时被读取,并执行相应的处理。通过注解可以在不改变原有逻辑的情况下,在源文件里添加一些信息。
- Annotation 为程序元素(类,方法,成员变量)设置元数据。
- Annotation 增加删除不影响代码的执行
- 希望Annotation起作用,只能通过APT(Annotation Processing Tool)工具来访问和处理Annotation的信息。
8.1)基本Annotation
-
@Override
限定重写父类方法,保证父类中有一个和该方法重名的方法。
-
@Deprecated
标示某个类或者方法已过时。继续使用会有编译器警告
@Deprecated public static void getInfo() { System.out.println(""); }
-
@SuppressWarnings
抑制编译器警告
// 如果是为value设置,可以省略value @SuppressWarnings("unchecked") // 为Annotation成员变量value设值 @SuppressWarnings(value = "unchecked")
-
@SafeVarags
堆污染警告
-
@FuncationalInterface
指定某个接口必须是函数式接口。(接口中只有一个抽象方法,可以包含多个默认方法或多个static 方法,就是函数式接口)
@FunctionalInterface public interface FunInterface{ static void foo(){ System.out.println("类方法"); } default void bar(){ System.out.println("默认方法"); } // 只定义一个抽象方法 void test(); }
9、IO
输入流和输出流
- 输入流:只能从中读取数据,而不能向其写入数据。(字节输入流InputStream 字符输入流Reader)
- 输出流:只能向其写入数据,而不能从中去读数据。(字节输出流OutputStream 字符输出流Writer)
10、多线程
并行和并发
- 并行:指两个或多个事件在同一时刻点发生
- 并发:指两个或多个事件在同一时间段内发生
10.1)线程的创建和启动
10.1.1)继承Thread类创建线程类
所有的线程都必须是Thread类或其子类的实例。
- 定义Thread类,并重写
run()
方法,run()
方法体代表了线程需要完成的任务。 - 创建Thread子类实例
- 调用实例的start()方法启动该线程
new Thread() {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println("thread name " + this.getName() + i);
}
}
}.start();
// 当前运行的线程
Thread.currentThread()
// 获取当前线程的名字
.getName()
// 设置当前线程的名字
setName(name)
10.1.2)实现Runnable接口创建线程类
- 定义实现Runnable的类,重写接口的run()方法,run()同样是线程的执行体
- 创建Runnable实现类的实例,并以此实例作为 Thread的 target来创建Thread对象,该Thread对象才是真正的线程对象。
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println("thread Name " + Thread.currentThread().getName() + i);
}
}
}).start();
Runnable接口创建的多线程,可以共享线程的实例变量。
10.1.3)使用Callable和Future创建线程
Callable相当于Runnable的增强版。里面的call()方法可以作为线程的执行体。call()更强大。
- call()f方法可以有参数。
- call()方法可以声明抛出异常。
Callable对象不能直接作为Thread的target。
// FutureTask 包装 Callable,FutureTask继承Runnable
FutureTask futureTask = new FutureTask<>(new Callable() {
@Override
public Integer call() throws Exception {
for (int i = 0; i < 100; i++) {
System.out.println("current thread name " + Thread.currentThread().getName() + " " + i);
}
return 1000;
}
});
new Thread(futureTask).start();
try {
// 获取返回值
System.out.println(futureTask.get());
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
10.2)线程的生命周期
启动线程用start()方法,而不是run()方法。直接调用run()方法是不是启动线程的。也不要连续调用两次start()方法。
[图片上传失败...(image-51f2bf-1571732324683)]
10.3)控制线程
10.3.1) join
public class JoinThread extends Thread {
@Override
public void run() {
super.run();
for (int i = 0; i < 100; i++) {
System.out.println("JoinThread " + i);
}
}
public static void main(String[] args) {
for (int i = 0; i < 100; i++) {
if (i == 20) {
JoinThread joinThread = new JoinThread();
joinThread.start();
try {
// 在main线程中调用了joinThread的join()方法,main线程必须等待joinThread执行结束才会向下执行
joinThread.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("Main Thread " + i);
}
}
}
10.3.2)后台线程(Daemon Thread)
在后台运行,为其他线程提供服务。
特征:所有的前台线程都死亡了,后台线程会自动死亡。
调用Thread对象setDaemon(true)
方法可以将指定的线程设置成后台线程。
public class DaemonThread extends Thread {
@Override
public void run() {
for (int i = 0; i < 1000; i++) {
System.out.println(getName() + i);
}
}
public static void main(String[] args) {
DaemonThread daemonThread = new DaemonThread();
// 在start之前指定线程设置成后台线程
daemonThread.setDaemon(true);
daemonThread.start();
for (int i = 0; i < 4; i++) {
System.out.println(Thread.currentThread().getName() + i);
}
}
}
isDaemon()
判断线程是否为后台线程。
前台线程死亡后,JVM会通知后台线程死亡,但它收到指令做出响应,需要一定时间。
10.3.3)线程睡眠:sleep
Thread.sleep(1000) 静态方法让当前执行的线程休眠。
public class SleepThread {
public static void main(String[] args) throws InterruptedException {
for (int i = 0; i < 10; i++) {
System.out.println("当前时间:" + new Date());
Thread.sleep(1000);
}
}
}
10.3.4)线程让步:yield
yield()方法和sleep()方法相似。也是Thread的静态方法,他可以让当前线程停止,但不会阻塞当前线程,他是将线程转入就绪状态。
public class YieldThread extends Thread {
private YieldThread(String name) {
super(name);
}
@Override
public void run() {
for (int i = 0; i < 50; i++) {
System.out.println(getName() + i);
if (i == 20) {
Thread.yield();
}
}
}
public static void main(String[] args) {
YieldThread yieldThread = new YieldThread("高级");
yieldThread.setPriority(Thread.MAX_PRIORITY);
yieldThread.start();
YieldThread yieldThread1 = new YieldThread("低级");
yieldThread1.setPriority(Thread.MIN_PRIORITY);
yieldThread1.start();
}
}
sleep比yield有更好的可移植性。
10.3.5)改变线程优先级
每个线程都有优先级,默认和创建它的父线程的优先级相同。优先级高获得更多的执行机会。main线程具有普通优先级。
thread.setPriority()
int priotity = yieldThread.getPriority();
public final static int MIN_PRIORITY = 1;
public final static int NORM_PRIORITY = 5;
public final static int MAX_PRIORITY = 10;
10.4)线程同步
两个线程并发访问修改同一个文件,就可能造成异常。这就是线程安全问题。
10.4.1)同步代码块
synchronized(obj){
// 同步代码块
}
obj是同步监视器,线程开始执行同步代码块之前,必须先获得同步监视器的锁定。任何时刻只能有一个线程可以获得对同步监视器的锁定,当同步代码执行完成之后,该线程会释放对该同步监视器的锁定。
Java允许任何对象作为同步监视器的锁定,但建议将并发访问的共享资源作为同步监视器。
public class SysncThread extends Thread {
private Account account;
private int money;
public SysncThread(String name, Account account, int money) {
super(name);
this.account = account;
this.money = money;
}
@Override
public void run() {
// 符合加锁 -- 修改 -- 释放锁的逻辑,一般是要操作的对象作为同步监视器。
synchronized (account) {
for (int i = 0; i < 10; i++) {
if (account.getMoney() > money) {
account.removeMoney(money);
System.out.println("当前余额:" + account.getMoney());
} else {
System.out.println("余额不足,取钱失败");
}
}
}
}
public static void main(String[] args) {
Account account = new Account(1000);
new SysncThread("A", account, 100).start();
new SysncThread("B", account, 100).start();
}
}
class Account {
private int money;
public int getMoney() {
return money;
}
public void removeMoney(int money) {
this.money -= money;
}
public Account(int money) {
this.money = money;
}
}
10.4.2)同步方法
同步方法就是使用synchronized关键字来修饰某个方法,则该方法成为同步方法。被synchronized修饰的方法,无须指定同步监视器,同步方法的同步监视器就是this。
public class SysncThread extends Thread {
private Account account;
private int money;
private Object object = new Object();
public SysncThread(String name, Account account, int money) {
super(name);
this.account = account;
this.money = money;
}
// 可以去掉同步代码块了
@Override
public void run() {
for (int i = 0; i < 10; i++) {
account.removeMoney(money);
}
}
public static void main(String[] args) {
Account account = new Account(1000);
new SysncThread("A", account, 100).start();
new SysncThread("B", account, 100).start();
}
}
class Account {
private int money;
public int getMoney() {
return money;
}
// 同步方法:多线程操作访问的方法
public synchronized void removeMoney(int money) {
if (this.money >= money) {
this.money -= money;
System.out.println("当前余额:" + this.money);
} else {
System.out.println("当前余额不足");
}
}
public Account(int money) {
this.money = money;
}
}
synchronized尽量减少作用域。
可变类的线程安全是降低程序运行效率为代价的。为了减少线程安全带来的负面影响,程序可以采用的策略。
- 不要对线程安全类的所有方法都进行同步,只对那些会改变竞争资源的方法同步。
- 如果可变类有两种运行环境,单线程和多线程,则应为该可变类提供两种版本,即线程不安全版本和线程安全版本。
10.4.3)释放同步监视器的锁定
同步代码块同步方法在执行之前必须先获得同步监视器的锁定。如何释放同步监视的锁定呢。
- 同步方法,同步代码块执行结束
- 同步方法,同步代码块遇到break return终止了该代码块方法的执行
- 同步方法,同步代码块出现了未处理的Error或Exception,导致方法,代码块异常结束
- 执行同步代码块,同步方法时,程序执行了同步监视器对象的wait()方法,则当前线程暂停,并释放同步监视器。
如何几种情况线程不会释放同步监视器
- 执行同步代码块同步方法时,调用了Thread.sleep() Thread.yield()方法来暂停当前线程的执行,不会释放同步监视器。
- 线程执行了同步代码块是,其他线程调用了suspend()方法将该线程挂机,该线程不会释放同步监视器。程序硬尽量避免使用suspend() resume()方法来控制线程。
10.4.4)同步锁(Lock)
通过显示定义同步锁对象来实现同步。
Lock提供了比同步方法,同步代码块更广泛的操作,更灵活的结果。
ReadWriteLock(读写锁),ReentrantLock(可重入锁),StampedLock类。
class Account {
//定义锁
private final ReentrantLock lock = new ReentrantLock();
private int money;
public int getMoney() {
return money;
}
// 同步方法:多线程操作访问的方法
public void removeMoney(int money) {
// 加锁
lock.lock();
try {
if (this.money >= money) {
this.money -= money;
System.out.println("当前余额:" + this.money);
} else {
System.out.println("当前余额不足");
}
} finally {
// 释放锁
lock.unlock();
}
}
public Account(int money) {
this.money = money;
}
}
使用ReentrantLock对象来进行同步,加锁和释放所出现在不同的作用范围内时,建议用finally块来确保必要时释放锁。
10.5)volatile关键字
volatile关键字为实例的同步访问提供了免锁机制。如果一个filed声明为volatile,那么编译器和虚拟机就知道改filed可能是被另一个线程并发更新。
10.5.1)Java内存模型
堆内存
堆内存存储对象实例,是被线程共享的运行时内存区域。存在内存可见性问题。而局部变量,方法定义的参数则不会在线程之间共享,不会有内存可见问题。-
主存
java内存模型定义了线程和主存之间的抽象关系,线程之间共享变量存储在主存中,同时每个线程都有一个私有的本地内存,本地内存存储了该线程共享变量的副本。
[图片上传失败...(image-a05765-1571732324683)]
10.5.2)原子性,可见性,有序性
-
原子性
对基本数据类型变量的读取和赋值操作时原子性操作,即这些操作是不可被中断的。
x = 3; // 原子操作 y = x; // 先读取x再赋值,不是原子性操作 x++; // 也不是原子操作
只有简单的读取和赋值才是原子性操作。
-
可见性
一个线程修改的状态另一个线程立马就能看到。通常情况下,线程修改一个变量,并不会立即写入主存,何时写入主存也是不确定的,当其他线程读取改值是,此时主存中可能还是原来的旧值。
但是volatile修饰的变量,能保证修改的值立即被更新到主存,所以对其他线程是可见的。
有序性
java内存模型中允许编译器和处理器对指令进行重排序。volatile、synchronized、Lock保证每个时刻只有一个线程执行同步代码。相当于让线程顺序执行代码,从而保证了有序性。
volatile,不保证原子性,保证有序性和可见性。
volatile 在保证有序性方面的性能要高于synchronized,volatile是修饰一个filed的,s'ynchronized修饰一段代码或函数。
10.5)线程通信
10.5.1)传统线程通信
借助Object类提供的 wait()
notify()
notifyAll()
三个方法。这三个方法必须由同步监视器对象来调用。
- synchronized 修饰的同步方法,同步监视器是是this,所以可以在同步方法中直接调用这三个方法
- synchronized 修饰的代码块,同步监视器是synchronized括号里对象,所以必须使用这个对象调用这三个方法
wait(): 导致当前线程等待,直到调用了notify() notifyAll() 方法。wait()可以带时间参数,表示经过这么长时间会自动唤醒。
notify(): 唤醒此同步监视器上等待的线程,唤醒那个线程是任意的。
notifyAll(): 唤醒此同步监视器上等待的所有线程。
class Account {
private int money;
private boolean flag;
public Account(int money) {
this.money = money;
}
public int getMoney() {
return money;
}
public synchronized void draw(int drawAmount) {
try {
if (!flag) {
wait();
} else {
System.out.println(Thread.currentThread().getName() + "取钱:" + drawAmount);
money -= drawAmount;
System.out.println("余额:" + money);
flag = false;
notifyAll();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public synchronized void deposit(int depositAmount) {
try {
if (flag) {
wait();
} else {
System.out.println(Thread.currentThread().getName() + "存钱操作" + depositAmount);
money += depositAmount;
System.out.println("余额:" + money);
flag = false;
notifyAll();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
10.5.2) 使用Condition控制线程通信
如果使用synchronized关键字保证同步,而是用Lock保证同步,则系统中不存在同步监视器,也就不能用wait() notify() notifyAll() 方法进行线程通信了。
使用Lock对象保证线程同步,可以使用Condition对象来保持协调。
Condition将同步监视器方法 wait() notify() notifyAll() 分解成截然不同的对象。
Condition对象绑定在Lock对象上,可以 lock.newCondition()来获得Conditaion对象。
Condition对象的方法
await(): 类似同步监视器的 wait() 方法,也有类似的等待多久的await()方法。
sigal(): 唤醒在此Lock对象上等待的单个线程。选择哪个一线程是任意的。
sigalAll(): 唤醒在此Lock对象上等待的所有线程。
```java
class Account {
private Lock lock = new ReentrantLock();
private Condition condition = lock.newCondition();
private int money;
private boolean flag;
public Account(int money) {
this.money = money;
}
public int getMoney() {
return money;
}
public void draw(int drawAmount) {
lock.lock();
try {
if (!flag) {
condition.wait();
} else {
System.out.println(Thread.currentThread().getName() + "取钱:" + drawAmount);
money -= drawAmount;
System.out.println("余额:" + money);
flag = false;
condition.signalAll();
}
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void deposit(int depositAmount) {
lock.lock();
try {
if (flag) {
condition.await();
} else {
System.out.println(Thread.currentThread().getName() + "存钱" + depositAmount);
money += depositAmount;
System.out.println("余额:" + money);
flag = true;
condition.notifyAll();
}
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
10.5.3) 使用阻塞队列(BlockingQueue)控制线程通信
生产者向BlockingQueue中添加元素,如果队列已满,则会阻塞。
消费者向BlockingQueue中消费元素,如果队列已空,则会阻塞。
抛出异常 | 不同返回值 | 阻塞线程 | 指定超时时长 | |
---|---|---|---|---|
队尾插入元素 | add(e) | offer(e) | put(e) | offer(e, time,unit) |
队头删除元素 | remove(e) | poll | take() | poll(time,unit) |
获取,不删除元素 | element() | peek() |
- ArrayBlockingQueue:数组实现的
- LinkedBlockingQueue:链表实现
- PriorityBlockingQueue:不是标准的阻塞队列,
- SynchronousQueue:同步队列,存取必须交替进行
- DelayQueue
public class BlockQueue {
public static void main(String[] args) throws InterruptedException {
BlockingQueue blockingQueue = new ArrayBlockingQueue<>(2);
blockingQueue.put("java");
blockingQueue.put("java");
blockingQueue.put("java");
System.out.println(blockingQueue.size());
}
}
public class BlockQueue {
public static void main(String[] args) throws InterruptedException {
BlockingQueue blockingQueue = new LinkedBlockingDeque<>(1);
new Producer(blockingQueue).start();
new Producer(blockingQueue).start();
new Producer(blockingQueue).start();
new Consumer(blockingQueue).start();
}
}
class Producer extends Thread {
private BlockingQueue blockingQueue;
public Producer(BlockingQueue blockingQueue) {
this.blockingQueue = blockingQueue;
}
@Override
public void run() {
String[] strings = new String[]{
"java",
"Structs",
"Spring",
};
for (int i = 0; i < 100; i++) {
System.out.println(getName() + "生产者准备生产");
try {
Thread.sleep(200);
blockingQueue.put(strings[i % 3]);
} catch (Exception e) {
e.printStackTrace();
}
System.out.println(getName() + "生产完成" + blockingQueue);
}
}
}
class Consumer extends Thread {
private BlockingQueue blockingQueue;
public Consumer(BlockingQueue blockingQueue) {
this.blockingQueue = blockingQueue;
}
@Override
public void run() {
while (true) {
System.out.println(getName() + "消费者准备消费集合元素");
try {
Thread.sleep(200);
blockingQueue.take();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(getName() + "消费完成:" + blockingQueue);
}
}
}
10.6)线程组
线程组可以对一批线程进行分类管理,Java允许对线程组进行控制。如果没有显示指定线程属于哪个线程组,则该线程组属于默认线程组。子线程和创建它的父线程处于同一个线程组。一旦加入一个线程组,就一直属于这个线程组。
创建线程时指定线程组
Thread(ThreadGroup group, Runnable target)
Thread(ThreadGroup group, Runnable target, String name)
Thread(ThreadGroup group, String name)
设置了线程组后中途不能改变。只有getThreadGroup()没有setThreadGroup()
创建线程组
// 指定线程组的名字
ThreadGroup(String name)
// 指定父线程组和线程组的名字
ThreadGroup(ThreadGroup parent, String name)
线程组默认实现了线程异常处理类接口,当有子线程没有处理异常,则线程组会捕获该异常来处理。
ThreadGroup类提供了几个常用方法来操作整个线程组里的所有线程。
int activeCount(); // 活动线程数
interrupt(); // 中断线程
isDaemon(); // 判断线程组是否是后台线程
setDaemon(boolean daemon); // 设置为后台线程
setMaxPriority(int pri); // 设置线程组的最高优先级
10.7) 线程池
10.7.1) 线程池原理和好处
线程出现的原因:
系统启动一个新线程的成本是比较高,因为涉及与操作系统的交互,所以使用线程池可以很好提高程序的性能,尤其是程序需要创建大量生存期很短的线程,更应该考虑线程池。
线程池的原理:
与数据库连接池类似,线程池在系统启动时创建大量的空闲线程,程序将Runnable对象 Callable对象传递给线程池,线程池就会启动一个线程来执行他们的run()或者call()方法,当run()或者call()方法执行结束后,该线程并不会死亡,而是再次返回线程池中成为空闲状态,等待执行下一个Runnable对象的run()或call()方法。
线程池的好处
- 提高性能
- 有效控制系统中并发线程的数量,当系统中包含大量并发线城时,会导致系统性能剧烈下降,甚至导致JVM崩溃,线程池的最大线程数可以控制并发线程数不超过次数。
10.7.2)线程池的创建
// 创建一个具有缓存功能线程池
Executors.newCachedThreadPool();
// 创建一个固定个数线程的线程池
Executors.newFixedThreadPool(4);
Executors.newSingleThreadExecutor();
// 创建指定个数的线程池,在指定延迟后执行线程任务
Executors.newScheduledThreadPool(6);
Executors.newSingleThreadScheduledExecutor();
// 创建持有足够的线程的线程池,会使用多个队列来减少竞争
Executors.newWorkStealingPool(4);
Executors.newWorkStealingPool();
前三个方法返回一个ExecutorService对象,该对象是一个线程池,可以执行Runnable对象和Callable对象。后两个返回ScheduledExecutorService。
ExecutorService尽快执行线程的线程池。
可执行的方法
// 无返回值
Future> submit(Runnable task)
// 用result显示指定返回的结果,所以返回result
Future submit(Runnable task,T result)
ExecutorService executorService = Executors.newFixedThreadPool(6);
Runnable targer = () -> {
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName() + "的值为" + i);
}
};
executorService.submit(targer);
executorService.submit(targer);
executorService.submit(targer);
executorService.shutdown();
用完一个线程池后,应该调用该线程池的shutdown()方法,该方法将启动线程池的关闭序列,调用shutdown()后不再接受新任务。
10.7.3)增强的ForkJoinPool
ForkJoinPool支持多CPU,是ExecutorService的实现类。
常用的构造器
// 创建10个并行线程的线程池 10个是CPU个数
ForkJoinPool forkJoinPool = new ForkJoinPool(10);
// 可用CPU个数作为参数
ForkJoinPool forkJoinPool = new ForkJoinPool(Runtime.getRuntime().availableProcessors());
// 返回一个通用线程池,通用池的运行状态不受shutdown()或shutdownNow()影响
ForkJoinPool forkJoinPool = ForkJoinPool.commonPool();
// 返回通用池的并行级别
int commonPoolParallelism = ForkJoinPool.getCommonPoolParallelism();
创建ForkJoinPool之后就可以调用submit(ForkJoinTask task)或invoke(ForkJoinTask task)方法来执行指定的任务。
ForkJoinTask代表一个可以并行,合并的任务。他的两个抽象子类 RecursiveAction和RecursiveTask。其中RecursiveTask代表有返回值的任务,RecursiveAction没有返回值的任务。
public class ForkJoinPoolThread {
public static void main(String[] args) {
ForkJoinPool pool = new ForkJoinPool();
pool.submit(new PrintTask(0, 300));
try {
pool.awaitTermination(2, TimeUnit.SECONDS);
} catch (InterruptedException e) {
e.printStackTrace();
}
pool.shutdown();
}
}
class PrintTask extends RecursiveAction {
// 每个小任务最多只能打印50个数
private static final int THRESHOLD = 50;
private int start;
private int end;
public PrintTask(int start, int end) {
this.start = start;
this.end = end;
}
@Override
protected void compute() {
if (end - start < THRESHOLD) {
for (int i = start; i < end; i++) {
System.out.println(Thread.currentThread().getName() + "的i值:" + i);
}
} else {
// 将大任务分解
int middle = (start + end) / 2;
PrintTask left = new PrintTask(start, middle);
PrintTask right = new PrintTask(middle, end);
// 并行执行多个小任务
left.fork();
right.fork();
}
}
}
public class ForkJoinPoolThread {
public static void main(String[] args) throws ExecutionException, InterruptedException {
int arr[] = new int[100];
Random random = new Random();
int total = 0;
for (int i = 0, len = arr.length; i < len; i++) {
int tmp = random.nextInt(20);
total += (arr[i] = tmp);
}
System.out.println(total);
ForkJoinPool forkJoinPool = ForkJoinPool.commonPool();
Future future = forkJoinPool.submit(new CalTask(arr, 0, arr.length));
System.out.println(future.get());
}
}
class CalTask extends RecursiveTask {
private static final int THRESHOLD = 20;
private int arr[];
private int start;
private int end;
public CalTask(int[] arr, int start, int end) {
this.arr = arr;
this.start = start;
this.end = end;
}
@Override
protected Integer compute() {
int sum = 0;
if (end - start < THRESHOLD) {
for (int i = start; i < end; i++) {
sum += arr[i];
}
return sum;
} else {
int middle = (start + end) / 2;
CalTask left = new CalTask(arr, start, middle);
CalTask right = new CalTask(arr, middle, end);
// 并行执行小任务
left.fork();
right.fork();
// 将小任务的结果合并起来
return left.join() + right.join();
}
}
}
10.7.4)THreadPoolExecutor(重点)
Executor框架中最核心的成员是ThreadPoolExecutor。他是线程池的核心实现类。
mThreadPoolExecutor = new ThreadPoolExecutor(
int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue workQueue
ThreadFactory threadFactory,
RejectedExecutorHandler handler);
mThreadPoolExecutor.allowCoreThreadTimeOut(true);
①int coreSize : 核心线程数(不会被回收)。
②int maxSize : 最大线程数。
③long KeepAliveTime : 膨胀出来的线程回收时间。
④TimeUnit unit : 时间单位。(通过TimeUnit.时间单位调用)
⑤BlockingQueue queue : 线程的阻塞队列。(必须有界)
⑥ThreadFactory factory : 产生线程的工厂。
⑦RejectedExecutionHandler handler : 当线程大于总数(最大线程数 + 阻塞队列)时,将由handler拒绝任务。
![image-20190811143946990](/Users/wangbo/Library/Application Support/typora-user-images/image-20190811143946990.png)
![image-20190812173842577](/Users/wangbo/Library/Application Support/typora-user-images/image-20190812173842577.png)
如果我们执行ThreadPoolExecutor的execute方法,会遇到各种情况:
(1) 如果线程池中的线程数未达到核心线程数,则创建核心线程处理任务。
(2)如果线程数大于或者等于核心线程数,则将任务加入任务队列,线程池中的空闲线程会不断地从任务队列中取出任务进行处理。
(3)如果任务队列满了,并且线程数没有达到最大线程数,则创建非核心线程去处理任务。
(4)如果线程数超过了最大线程数,则执行饱和策略。
例子
package com.cari.cari.promo.diskon.util;
import android.os.Handler;
import android.os.Looper;
import com.cari.promo.diskon.BuildConfig;
import com.crashlytics.android.Crashlytics;
import java.util.concurrent.Future;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
public enum ThreadUtil {
Database(2),
General(4),
Network(6);
private static final Handler MAIN_HANDLER = new Handler(Looper.getMainLooper());
public static boolean checkIsInMainThread() {
return Looper.myLooper() == Looper.getMainLooper();
}
public static void confirmInMainThread() {
if (BuildConfig.DEBUG && (!checkIsInMainThread())) {
throw new RuntimeException("Confirm Ui Thread Error!");
}
}
public static void runInMainThread(boolean alwaysPost, Runnable runnable) {
if (runnable == null) {
return;
}
if ((!alwaysPost) && checkIsInMainThread()) {
runnable.run();
} else {
MAIN_HANDLER.post(runnable);
}
}
public static void runInMainThreadDelayed(Runnable runnable, long delayMillis) {
if (runnable == null) {
return;
}
MAIN_HANDLER.postDelayed(
runnable,
delayMillis < 0 ? 0 : delayMillis);
}
private final ThreadPoolExecutor mThreadPoolExecutor;
ThreadUtil(int poolSize) {
if (poolSize < 1) {
poolSize = 1;
}
mThreadPoolExecutor = new ThreadPoolExecutor(
poolSize,
poolSize,
30,
TimeUnit.SECONDS,
new LinkedBlockingQueue(),
new ThreadFactory() {
@Override
public Thread newThread(Runnable r) {
return new Thread(r, "ThreadUtil-" + name());
}
});
mThreadPoolExecutor.allowCoreThreadTimeOut(true);
}
public Future> execute(Runnable runnable) {
return mThreadPoolExecutor.submit(new RunnableTask(runnable));
}
private static final class RunnableTask implements Runnable {
private final Runnable mRunnable;
private RunnableTask(Runnable runnable) {
mRunnable = runnable;
}
@Override
public void run() {
try {
if (mRunnable != null) {
mRunnable.run();
}
} catch (Throwable t) {
if (checkIsInMainThread()) {
throw t;
} else {
final RuntimeException exception = new RuntimeException("Sub-Thread throws exception!", t);
if (BuildConfig.DEBUG) {
// 让主线程也崩溃
runInMainThread(false, new Runnable() {
@Override
public void run() {
throw exception;
}
});
} else {
// 记录到统计平台
runInMainThread(false, new Runnable() {
@Override
public void run() {
try {
Crashlytics.logException(exception);
} catch (Throwable ignore) {
}
}
});
}
}
}
}
}
}
10.8 线程池的种类
通过直接或间接配置ThreadPoolExecutor的参数可以创建不同类型的ThreadPoolExecutor。
4种线程池比较常用、FixedThreadPool
CachedThreadPool
SingleThreadExecutor
ScheduledThreadPool
。
这几个线程池都是Executors 类中的静态方法生成的。
10.8.1)FixedThreadPool
public static ExecutorService newFixedThreadPool(int nThreads, ThreadFactory threadFactory) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue(),
threadFactory);
}
10.8.2)CachedThreadPool
public static ExecutorService newCachedThreadPool(ThreadFactory threadFactory) {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue(),
threadFactory);
}
10.8.3) SingleThreadExecutor
public static ExecutorService newSingleThreadExecutor(ThreadFactory threadFactory) {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue(),
threadFactory));
}
10.8.4) ScheduleThreadPool
public static ScheduledExecutorService newScheduledThreadPool(
int corePoolSize, ThreadFactory threadFactory) {
return new ScheduledThreadPoolExecutor(corePoolSize, threadFactory);
}
10.9)线程相关类
ThreadLocal类
- 线程局部变量
- 把数据放到ThreadLocal中可以让每个线程创建一个该变量的一个副本,从而避免并发访问线程安全问题。
ThreadLocal的方法
// 放回当前线程中副本中的值
T get()
// 删除此线程局部变量中当前线程的值
void remove()
// 设置次线程局部变量中当前副本的值
void set(T value)
public class ThreadLocalVar extends Thread {
private Acount acount;
public ThreadLocalVar(Acount acount, String name) {
super(name);
this.acount = acount;
}
@Override
public void run() {
for (int i = 0; i < 10; i++) {
if (i == 6) {
acount.setName(getName());
}
System.out.println(acount.getName() + "账户I的值" + i);
}
}
public static void main(String[] args) {
Acount acount = new Acount("初始名");
new ThreadLocalVar(acount, "线程A").start();
new ThreadLocalVar(acount, "线程B").start();
}
}
class Acount {
private ThreadLocal name = new ThreadLocal<>();
public Acount(String str) {
this.name.set(str);
// 访问当前线程的name的副本
System.out.println(this.name.get());
}
public String getName() {
return this.name.get();
}
public void setName(String str) {
this.name.set(str);
}
}
11、类加载和反射
11.1) 类加载、链接和初始化
11.1.1)JVM和类
当调用java命令运行程序时,会启动一个JVM,同一个JVM下的所有线程,变量都处于同一个进程中,都使用该JVM进程内存区。不同JVM不共享内存区。
JVM终止的情况
- 程序运行到最后。
- 使用
System.exit()
或Runtime.getRuntime().exit()
代码处结束程序。 - 程序执行过程中遇到未捕获的异常或错误而结束。
- 程序所在平台强制结束了JVM进程。
11.1.2)类加载
第一次使用某个未被加载到内存中的类时,系统会通过加载、连接、初始化三个步骤对类进行初始化。
类加载: 是指将类的class文件读入内存,并为之创建一个java.lang.Class对象。
类的加载由加载器完成,可以自定义加载器。
11.1.3)类的连接
连接负责把类的二进制数据合并到JRE中。
- 验证:校验被加载的类是否有正确的内部结构,并和其他类协调一致。
- 准备:负责为类的类变量分配内存,并设置默认初始值。
- 解析:类的二进制数据中的符号引用替换成直接引用。
11.1.4)类的初始化
虚拟机对类进行初始化。
- 声明类变量时指定的初始化值
- 静态初始化块为类变量指定初始值
public class Test {
// 声明时初始化
static int a = 4;
// 默认值
static int b;
static int c;
// 静态初始化块
static {
b = 6;
}
}
类的初始化步骤:
- 如果类没有被加载和连接,则程序先加载并连接该类
- 如果该类的直接父类还没有初始化,则先初始化其直接父类
- 如果类中有初始化语句,则系统一次执行这些初始化语句。
11.1.5)类的初始化时机
- 创建类的实例。
- 调用某个类的类方法。
- 访问某个类或接口的类变量,或为该类变量赋值。
- 使用反射方式来强制创建某个类或接口对应的java.lang.Class对象。
- 初始化某个累的子类。
- 直接使用java.exe命令来运行某个主类。
类变量使用final修饰,它的值在编译时就能确定,在使用该变量时相当于宏替换。
ClassLoader classLoader = ClassLoader.getSystemClassLoader();
try {
classLoader.loadClass("com.company.baseLib.Tester");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
System.out.println("系统加载Tester类");
try {
Class.forName("com.company.baseLib.Tester");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
loadClass只是加载并没有初始化,forName做了初始化。
11.2)类加载器
类加载器负责将.class文件加载到内存中,并为之生成对应的java.lang.Class对象。同一个类不会被加载两次。JVM中的类有唯一的标识。全限定类名和加载器负责加载。不同加载器加载同类名是不同的标识。
- Bootstrap ClassLoader:根类加载器
- Extension ClassLoader:扩展类加载器
- System ClassLoader:系统类加载器
11.3)反射查看类信息
编译时类型和运行时类型。
Person p = new Student();
// 编译时 Person,运行时Student。
11.3.1)获得Class对象
- 字符串获取
// 1. Class累的forName()静态方法
Class.forName("com.company.baseLib.Tester");
- 类获取
// 2. 类的class属性
Tester.class;
- 对象获取
// 3. 对象的getClass()方法
Tester tester = new Tester();
tester.getClass();
推荐用第二种方式获取Class对象。
- 代码更安全。编译阶段就可以检查要访问的class对象是否存在。
- 程序性能更好。因为无需调用方法,所以性能更好。
11.3.2)从Class中获取信息
![image-20190713110505792](/Users/wangbo/Library/Application Support/typora-user-images/image-20190713110505792.png)
- 包路径
getPackage();
- 类名称
getName()
- 继承类
getSuperclass()
- 实现接口
getInterfaces()
-
获取Class对应类包含的构造器Constructor
// 带指定参数列表的public构造器 public Constructor
getConstructor(Class>... parameterTypes) // 所有public构造器 public Constructor>[] getConstructors() // 带指定参数列表的构造器 public Constructor getDeclaredConstructor(Class>... parameterTypes) // 所有构造器 public Constructor>[] getDeclaredConstructors() Constructor的常用方法
// 查看构造器方法是否允许带有可数量的参数 isVarArgs() //
-
获取Class对应类所包含的方法
// 带指定形参列表的public 方法 public Method getMethod(String name, Class>... parameterTypes) // 所有的public方法 public Method[] getMethods() // 带指定形参列表的方法 public Method getDeclaredMethod(String name, Class>... parameterTypes) // 所有方法 public Method[] getDeclaredMethods()
-
获取Class对应类所包含的成员变量
// 指定名称的public成员变量 public Field getField(String name) // 所有public成员变量 public Field[] getFields() // 指定名称的成员变量 public Field getDeclaredField(String name) // 所有成员变量 public Field[] getDeclaredFields()
-
获取Class对应类所包含的Annotation
// 指定类型的Annotation public A getAnnotation(Class annotationClass) // 直接修饰Class对象的,指定类型Annotation public A getDeclaredAnnotation(Class annotationClass) //
example
// Person 的Class Class
clazz = Person.class; System.out.println("person 类的所有构造器:"); Constructor[] constructors = clazz.getDeclaredConstructors(); for (Constructor constructor : constructors) { System.out.println(constructor); } System.out.println("person 类的所有方法"); Method[] methods = clazz.getDeclaredMethods(); for (Method method : methods) { System.out.println(method); } System.out.println("获取Person类指定的方法"); System.out.println("带一个字符串参数的info方法:" + clazz.getMethod("info", String.class)); System.out.println("Person类的全部注解"); Annotation[] annotations = clazz.getAnnotations(); System.out.println("Person 的全部Annotation:"); for (Annotation annotation : annotations) { System.out.println(annotation); } System.out.println("该Class元素上@SuppressWarning注解为:" + Arrays.toString(clazz.getAnnotationsByType(SuppressWarnings.class))); System.out.println("该Class元素上@Anno注解为:" + Arrays.toString(clazz.getAnnotationsByType(Anno.class))); System.out.println("Person类的内部类:"); Class inClazz = Class.forName("com.company.classObj.Person$Inner"); System.out.println("内部类对应的外部类:" + inClazz.getDeclaringClass()); System.out.println("person对应的包:" + clazz.getPackage()); System.out.println("person对应的父类:" + clazz.getSuperclass()); 11.4)反射生成并操作对象
11.4.1)创建对象
-
newInstance()
创建实例,实际是利用默认构造器来创建该类的实例。Class clazz = Class.forName("com.company.classObj.Person"); Person person = (Person) clazz.newInstance(); System.out.println(person.age); ```
-
Class对象获取指定的
Constructor
对象,在调用Constructor
对象的newInstance()
方法来创建实例Class clazz = Class.forName("com.company.classObj.Person"); // 构造器无需指定方法名,只需指定形参类型 Constructor constructor = clazz.getConstructor(String.class); Object object = constructor.newInstance("指定的构造器");
11.4.2)调用方法
Person person = new Person(); Class clazz = Person.class; // 指定方法名和形参类型 Method method = clazz.getMethod("info", String.class); // 指定对象调用方法 method.invoke(person,"调用info方法");
11.4.3)访问成员变量
public class Person { private String name; private int age; @Override public String toString() { return "Person{" + "name='" + name + '\'' + ", age=" + age + '}'; } public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException { Person p = new Person(); Class
personClass = Person.class; // 获取所有的指定名字Filed Field nameField = personClass.getDeclaredField("name"); // 取消访问权限 nameField.setAccessible(true); nameField.set(p, "wangbo"); Field ageField = personClass.getDeclaredField("age"); ageField.setAccessible(true); ageField.setInt(p, 30); System.out.println(p); } } -