java输入一个数的语法_Java语法 1小时入门

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 co = new ArrayList<>();

fromArrayToCollection(oa, co);

}

}

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);

}

}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值