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<String> 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<K, V> {
    Set<K> keySet();

    V put(K key, V value);
}
// 设定泛型上限
public class Apple<T extends Number> {
    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<String> apple = new Apple<>("苹果");
        Apple<Double> doubleApple = new Apple<>(13.0);
        
    }
}

4.2)泛型类派生子类

// public class A extends Apple<T> 错误的做法
// 派生类需要指定具体类型
public class A extends Apple<String>

public class A extends Apple

注意

List<A> 并不是 List<Apple>的子类
List<Apple> list1 = new List<>();
List<A> list2 = new List<>();
List<String> list3 = new List<>();

void test(List<Apple> 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)泛型方法

修饰符 <T,S> 返回值类型<N,M> 方法名(形参列表){

}
public class TFuncation {
    static <T> void fromArrayToCollection(T[] a, Collection<T> c) {
        for (T o : a) {
            c.add(o);
        }
    }

    public static void main(String[] args) {
        Object[] oa = new Object[100];
        Collection<Object> 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> 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类或其子类的实例。

  1. 定义Thread类,并重写run()方法,run()方法体代表了线程需要完成的任务。
  2. 创建Thread子类实例
  3. 调用实例的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<Integer> futureTask = new FutureTask<>(new Callable<Integer>() {
    @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<String> 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<String> 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<String> blockingQueue;

    public Producer(BlockingQueue<String> 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<String> blockingQueue;

    public Consumer(BlockingQueue<String> 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
<T> Future<T> 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<Integer> future = forkJoinPool.submit(new CalTask(arr, 0, arr.length));
        System.out.println(future.get());
    }
}

class CalTask extends RecursiveTask<Integer> {
    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<Runable> 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<Runnable>(),
                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<Runnable>(),
                                      threadFactory);
    }

10.8.2)CachedThreadPool

 public static ExecutorService newCachedThreadPool(ThreadFactory threadFactory) {
        return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                      60L, TimeUnit.SECONDS,
                                      new SynchronousQueue<Runnable>(),
                                      threadFactory);
    }

10.8.3) SingleThreadExecutor

 public static ExecutorService newSingleThreadExecutor(ThreadFactory threadFactory) {
        return new FinalizableDelegatedExecutorService
            (new ThreadPoolExecutor(1, 1,
                                    0L, TimeUnit.MILLISECONDS,
                                    new LinkedBlockingQueue<Runnable>(),
                                    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<String> 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)类的初始化

虚拟机对类进行初始化。

  1. 声明类变量时指定的初始化值
  2. 静态初始化块为类变量指定初始值
public class Test {
    // 声明时初始化
    static int a = 4;
    // 默认值
    static int b;
    static int c;

    // 静态初始化块
    static {
        b = 6;
    }
}

类的初始化步骤:

  1. 如果类没有被加载和连接,则程序先加载并连接该类
  2. 如果该类的直接父类还没有初始化,则先初始化其直接父类
  3. 如果类中有初始化语句,则系统一次执行这些初始化语句。

11.1.5)类的初始化时机

  1. 创建类的实例。
  2. 调用某个类的类方法。
  3. 访问某个类或接口的类变量,或为该类变量赋值。
  4. 使用反射方式来强制创建某个类或接口对应的java.lang.Class对象。
  5. 初始化某个累的子类。
  6. 直接使用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<T> getConstructor(Class<?>... parameterTypes)
    // 所有public构造器
    public Constructor<?>[] getConstructors()
    // 带指定参数列表的构造器
    public Constructor<T> 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 extends Annotation> A getAnnotation(Class<A> annotationClass) 
      
    // 直接修饰Class对象的,指定类型Annotation
    public <A extends Annotation> A getDeclaredAnnotation(Class<A> annotationClass)
      
    // 
    

    example

    // Person 的Class
    Class<Person> 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<Person> 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);
    
        }
    }
    
    
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,126评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,254评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,445评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,185评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,178评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,970评论 1 284
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,276评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,927评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,400评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,883评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,997评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,646评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,213评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,204评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,423评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,423评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,722评论 2 345

推荐阅读更多精彩内容

  • Swift1> Swift和OC的区别1.1> Swift没有地址/指针的概念1.2> 泛型1.3> 类型严谨 对...
    cosWriter阅读 11,089评论 1 32
  • 整理来自互联网 1,JDK:Java Development Kit,java的开发和运行环境,java的开发工具...
    Ncompass阅读 1,534评论 0 6
  • 第6章类文件结构 6.1 概述 6.2 无关性基石 6.3 Class类文件的结构 java虚拟机不和包括java...
    kennethan阅读 905评论 0 2
  • 一:java概述: 1,JDK:Java Development Kit,java的开发和运行环境,java的开发...
    慕容小伟阅读 1,766评论 0 10
  • 一:java概述:1,JDK:Java Development Kit,java的开发和运行环境,java的开发工...
    ZaneInTheSun阅读 2,629评论 0 11