Java基础内容

什么是二进制?

人们在生活中多数采用十进制,而计算机中全部采用二进制数进行表示,它只包含0、1两个数字,逢二进一,每个0或1叫做一个bit。

什么是字节?

字节是计算机中最小的存储单元,计算机中存储的任何数据都是以字节的形式存储的。

IDEA中Debug调试

F7进入到方法中;F8逐行执行程序;F9跳到下一个断点,如果没有则结束程序;Shift+F8跳出方法;Ctrl+F2退出debug模式并停止程序。

注意 : 局部变量位于栈中,随方法进栈而诞生,随方法出栈而消失;而成员变量位于堆中,随对象被创建而诞生,随对象被垃圾回收而消失。

static
static关键字
  · static修饰的成员放于方法区中,随类而加载
  · 使用static关键字修饰的成员变量表示静态的含义,此时成员变量由对象层级提升为类层级,
        也就是整个类只有一份并被共享,该成员变量随着类的记载而准备就绪,与是否创建对象无关
  · static关键字的成员可以使用引用.的方式访问,但推荐类名.的方式
  · 在非静态成员方法中既能访问非静态的成员又能访问静态的成员,静态成员被所有对象共享
  · 在静态成员方法中只能访问静态成员不能访问非静态成员,此时可能还没有创建对象
  ·
  · 在以后的开发中只有隶属于类层级并被所有对象共享的内容才可以使用static关键字修饰(不能滥用static关键字)

一旦使用静态static修饰了类中的成员,那么这个成员将不再属于对象自己,而是属于类本身;且无需创建对象即可通过类名称使用,一般推荐使用类名称进行调用静态方法。静态不能访问非静态,因为在程序中是先有的静态内容,后有的非静态内容,“先人不知后人,后人却知先人!”静态方法不能使用this,因为this代表的是当前对象,通过谁调用的方法,谁就是当前对象,而静态成员是属于类的,不属于某个对象。

静态代码块的执行优先级高于构造方法,它会在类初始化的时候执行一次,执行完成便销毁,它仅能初始化类变量,即static修饰的数据成员。

class Person {
    static {
        System.out.println("静态代码块执行啦!");
    }
    
    public static int age = 100;

    public static void method() {
        System.out.println("Man is a superior animal !");
    }
}

public class Demo {
    public static void main(String[] args) {
        System.out.println(Person.age);
        Person.method();
    }
}
/*----------------------------------------
    静态代码块执行啦!
    100
    Man is a superior animal !
----------------------------------------*/

  · 引用类型变量用于存放对象的地址,可以给引用类型赋值为null,表示不指向任何对象
  · 当某个引用类型变量为null时无法对对象实施访问,因为它没有指向任何对象,
        此时如果通过引用访问成员变量或调用方法,会产生NullPointerException(空指针异常)
final

final关键字可以用来修饰引用、方法和类,一旦使用final关键字进行修饰,说明不可改变。

  • final修饰基本数据类型后,该引用的值为常量且无法修改。
  • final修饰引用数据类型后,该引用的地址值无法修改。
  • final修饰类的成员变量后,必须立马赋值,否则会编译报错。
  • final用来修饰一个方法后,该方法将成为最终方法,可以被子类继承,但无法重写此方法。
  • final用来修饰一个个类后,该类将成为最终类,无法被继承,即“断子绝孙类”。
final class SuperClass {
    public SuperClass() {
        System.out.println("最终类中的构造方法执行");
    }

    public static final int I = 11;
    public final String STR = "无法改变的String";

    final void method() {
        System.out.println("无法改变的method");
    }
}

public class Demo {
    public static void main(String[] args) {
        System.out.println(SuperClass.I);
        System.out.println(new SuperClass().STR);
        new SuperClass().method();
    }
}
/*----------------------------------------
    11
	最终类中的构造方法执行
	无法改变的String
	最终类中的构造方法执行
	无法改变的method
----------------------------------------*/
继承

继承是多态的前提,没有继承就没有多态,继承主要解决的问题就是共性抽取。java是单继承的,一个类的直接父类只能有唯一一个,但一个父类却可以拥有多个子类;java可以多级继承,即父类还可以有自己的父类。

继承关系中的特点 :

  • 在继承关系中,子类除了可以继承父类中所有可继承的内容,还可以拥有自己的专有内容。
  • “子类就是一个父类”,也就是说子类可以被当成父类看待。
  • 无论是访问成员方法还是成员变量,如果没有都是向上找父类,绝不会向下找子类的。
class SuperClass {
    public int num = 10;

    public SuperClass() {
        System.out.println("父类构造方法执行!");
    }

    public void methodA() {
        System.out.println("父类方法methodA执行,num为" + num);
    }
}

class SubClass extends SuperClass {
    public int num = 20;

    public SubClass() {
        System.out.println("子类构造方法执行!");
    }

    public void methodB() {
        int num = 30;
        System.out.println("子类方法methodB执行,num为" + num);
    }
}

class Grandson extends SubClass {
    public void methodC() {
        System.out.println("孙类方法methodC执行,num为" + num);
    }
}

public class Demo {
    public static void main(String[] args) {
        new SuperClass().methodA();
        new SubClass().methodB();
        new Grandson().methodC();
    }
}
/*----------------------------------------
    父类构造方法执行!
    父类方法methodA执行,num为10
    父类构造方法执行!
    子类构造方法执行!
    子类方法methodB执行,num为30
    父类构造方法执行!
    子类构造方法执行!
    孙类方法methodC执行,num为20
----------------------------------------*/
抽象类、抽象方法

如果父类中的方法不确定如何进行{}方法体实现,那么这就应该是一个抽象方法。抽象方法就是在权限修饰符后加上abstract关键字,然后去掉大括号,直接分号结束的方法;抽象方法所在的类必须是抽象类才行,在class前加上abstract关键字即可。一个抽象类不一定含有抽象方法,只要保证抽象方法所在的类是抽象类即可。这样没有抽象方法的抽象类也不能直接创建对象,但在一些特殊场景下有用途。

抽象类的使用,抽象类不能直接创建new抽象类对象;必须用一个子类来继承抽象方法,且子类必须覆盖重写抽象父类中所有的抽象方法。对于类、方法来说,abstract和关键字final不能同时使用,因为矛盾。

abstract class Person {
    public abstract void work();
}

class Programmer extends Person {
    @Override
    public void work() {
        System.out.println("Programmers type code every day !");
    }
}

public class Demo {
    public static void main(String[] args) {
        new Programmer().work(); // Programmers type code every day !
    }
}
接口

在我们的日常生活中,接口就是一种公共的规范标准,只要符合规范标准,就可以大家通用,而在java程序中,接口是一种引用数据类型,最重要的内容就是其中的抽象方法。定义接口的格式其实就是将类关键字class替换成为interface接口,替换成interface后,仍是将.java源文件编译生成为.class字节码文件。

  • 在任何版本的JDK中,接口都能定义抽象方法。

接口中的抽象方法,修饰符必须是两个固定的关键字 : public abstract,这两个关键字修饰符可以选择性的省略;方法的三要素也可以随意定义。

接口的使用,接口不能直接使用,必须有一个实现类来实现该接口;接口的实现类必须覆盖重写(实现)接口中所有的抽象方法。

  • 在JDK7中,接口可以包含常量。

在接口中其实也可以定义“成员变量”,但是必须使用public static final三个关键字进行修饰,从效果上看,这其实就是接口中的常量。接口中的常量必须得进行赋值,且可以省略public static final关键字。

  • 在JDK8中,接口可以包含默认方法和静态方法;

接口中的默认方法可以解决接口升级的问题;可通过接口实现类对象直接调用;可被接口实现类进行覆盖重写。

接口中的静态方法不能通过接口实现类的对象来调用接口中的静态方法,可通过接口名称直接调用其静态方法。

  • 在JDK9中,接口可以包含私有方法。

普通私有方法可以解决多个默认方法之间重复代码的问题,而静态私有方法则可以解决多个静态方法之间重复代码的问题。私有方法只有接口自己才能调用,不能被实现类或别人使用。

interface Test {
    public static final String STR = "Java";

    public abstract void methodA();

    public default void methodB() {
        System.out.println("接口中的默认方法执行啦!");
        methodD();
    }

    public static void methodC() {
        System.out.println("接口中的静态方法执行啦!");
    }
    
    private void methodD() {
        System.out.println("接口中的普通私有方法执行啦!");
        methodE();
    }

    private static void methodE() {
        System.out.println("接口中的静态私有方法执行啦!");
    }
}

class TestImpl implements Test {
    @Override
    public void methodA() {
        System.out.println("实现类覆盖重写接口中的抽象方法执行啦!");
    }

    @Override
    public void methodB() {
        System.out.println("实现类覆盖重写接口中的默认方法执行啦!");
    }
}

public class Demo {
    public static void main(String[] args) {
        Test.methodC(); // 接口中的静态方法执行啦!
    }
}

注意事项 :

  • 接口是没有静态代码块或者构造方法的。
  • 一个类的直接父类是唯一的,但是一个类可以同时实现多个接口。
  • 如果实现类所实现的多个接口中存在重复的抽象方法,那么只需要覆盖一次即可。
  • 如果实现类没有覆盖重写接口中所有的抽象方法,那么这个实现类自己就必须是抽象类。
  • 如果实现类实现的多个接口中存在重复的默认方法,那么实现类一定要对冲突的默认方法进行覆盖重写。
  • 如果一个类的直接父类的方法和接口中的默认方法产生了冲突,优先使用父类的方法。
  • 类与类之间是单继承的,直接父类只有一个。
  • 类与接口之间是多实现的,一个类可以实现多个接口。
  • 接口与接口之间是多继承的。
  • 多个父接口中的抽象方法如果重复是没关系的,如果多个父接口中的默认方法重复,那么子接口必须进行默认方法的覆盖重写,而且要带着default关键字。
多态

继承或接口的实现是多态的前提,代码中体现多态性,其实就是一句话 : 父类引用指向子类对象。访问成员变量,编译看左边,运行还看左;访问成员方法,编译看左边,运行看右边。

对象的向上转型,其实就是多态的写法,将子类对象当成父类看待使用。向上转型一定是安全的,从小范围转向了大范围,但也有一个弊端,就是对象一旦向上转型为父类,那么就无法调用子类原本特有的内容了。解决方法就是用对象的向下转型,也就是还原动作,就是将父类对象还原成为本来的子类对象。向下转型必须保证对象本来创建的时候就是这个对象,才能向下转型成功,否则就会抛出ClassCastException类转换异常。

如何才能知道一个父类引用的对象原本就是一个什么子类?使用(对象名 instanceof 类名称)即可得到一个boolean值结果,也就是判断前面的对象能不能做后面类型的实例。

class Animal {
    public void eat() {
        System.out.println("动物吃东西!");
    }
}

class Dog extends Animal {
    public void sleep() {
        System.out.println("小狗睡觉觉!");
    }
}

class Cat extends Animal {
    public void work() {
        System.out.println("猫戏耍耗子!");
    }
}

public class Demo {
    public static void main(String[] args) {
        Animal animal = new Dog();
        
        if (animal instanceof Cat) {
            Cat mao = (Cat) animal;
            mao.work();
        }
        if (animal instanceof Dog) {
            Dog gou = (Dog) animal;
            gou.sleep(); // 小狗睡觉觉
        }
    }
}
权限修饰符
publicprotected(default)private
在同一个类中
在同一个包中
不同包父子类
不同包非子类
内部类

内部类就是在一个事物的内部包含另一个事物,即一个类的内部包含另一个类。

  • 成员内部类

成员内部类就是跟成员方法并排的类,使用时内用外可以随意访问,外用内则需要内部类对象。使用成员内部类的方式可分为直接方式和间接方式。直接方式为new 外部类名().new 内部类名();间接方式为在外部类的方法中使用内部类,然后main方法调用外部类的方法。

class Test {
    public int num = 10;

    public void method() {
        int num = 20;
        System.out.println(num);                    // 20
        System.out.println(new TestInternal().num); // 30
    }
    
    class TestInternal {
        int num = 30;
        
        public void method() {
            int num = 40;
            System.out.println(num);                // 40
            System.out.println(this.num);           // 30 
            System.out.println(Test.this.num);      // 10
        }
    }
}

public class Demo {
    public static void main(String[] args) {
        new Test().method();
        new Test().new TestInternal().method();
    }
}
  • 局部内部类

如果一个类定义在一个方法的内部,那么这就是一个局部内部类,包含匿名内部类。只有当前所属的方法才能使用,出了这个方法外面就不能用了。局部内部类如果希望访问所在方法的局部变量,那么这个局部变量必须是有效final的。

从JDK8开始,只要局部变量事实不变,那么final关键字就可以省略。因为new出来的对象在堆内存中,局部变量是跟着方法走的,在栈内存中,方法运行结束后会立刻出栈,局部变脸就会立刻消失,但new出来的对象会在堆中持续存在,直到被垃圾回收才会消失。

如果接口的实现类,或继承自父类的子类只需要使用唯一的一次,那么这种情况下就可以省略掉该类的定义而改为使用匿名内部类。定义匿名内部类时(new 接口名称() {···}),new代表创建对象的动作,接口名称就是匿名内部类需要实现哪个接口,{···}才是匿名内部类的内容。

定义一个类时,外部类可以使用public/(default);成员内部类四个权限修饰符都可以用;局部内部类什么都不能写。

匿名内部类在创建对象的时候,只能使用唯一的一次,如果希望多次创建对象,而且类的内容一样的话,那就需要单独定义实现类了;匿名对象在调用方法时,只能调用唯一的一次,如果希望同一个对象调用多次方法,那就必须给对象取个名字了;匿名内部类是省略了实现类/子类名称,而匿名对象是省略了对象名称,匿名内部类和匿名对象不是一回事!

public class Demo {
    public static void main(String[] args) {

        class TestInternal {
            final int NUM = 10;

            public void method() {
                System.out.println(this.NUM);
            }
        }
        new TestInternal().method(); // 10

        new TestInternal() {
            final int NUM = 20;

            public void method() {
                System.out.println(this.NUM);
            }
        }.method();                  // 20

        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName() + "线程执行啦!");
            }
        }).start();                 // Thread-0线程执行啦!
    }
}

常用API

Scanner

java.util.scanner类用来从键盘输入数据到程序中,创建对象的格式为Scanner sc = new Scanner(System.in);

import java.util.Scanner;

public class Demo {
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        
        int num = sc.nextInt();
        System.out.println("你输入的数字为:" + num);
        String str = sc.next();
        System.out.println("输入的字符串为:" + str);
    }
}
Random

java.util.Random类可以用来生成随机数字,创建对象的格式为Random r = new Random();

import java.util.Random;

public class Demo {
    public static void main(String[] args) {
        Random r = new Random();
        
        int num =  r.nextInt(10) + 1;
        System.out.println("1到10间的随机数为:" + num);
    }
}

注意 : 引用类型的一般使用步骤为导包、创建、使用。只有位于java.lang包下的内容才无需导包。

String

java.lang.String类代表字符串,程序中所有的双引号字符串都是String类的对象,字符串的内容永不可变,所以字符串是可以共享使用的,字符串效果上相当于char[]数组,但底层原理是byte[]数组。创建字符串的常见3+1种方式为使用空参构造、根据char[]数组创建、根据byte[]数组创建以及直接创建。

比较相关的常用方法

  • public boolean equals(Object anObject)区分大小写进行内容比较。
  • public boolean equalsIgnoreCase(String anotherString)忽略大小写进行字符串内容比较。
  • public boolean startsWith(String prefix)判断字符串中第一个字符是否为传递的参数。

获取相关的常用方法

  • public int length()获取字符串中含有字符的个数并返回。
  • public String concat(String str)将当前字符串和参数字符串拼接成一个新的字符串并返回。
  • public char charAt(int index)获取指定索引位置的单个字符并返回。
  • public int indexOf(String str)查找参数字符串在本字符串中首次出现的索引位置并返回。

截取相关的常用方法

  • public String substring(int beginIndex)截取从参数位置到字符字符串末尾的字符串并返回新的字符串。
  • public String substring(int beginIndex, int endIndex)截取从beginIndex到endIndex间的字符串并返回新的字符串。

转换相关的常用方法

  • public String toUpperCase()将当前字符串转换成大写字符串并返回。

  • public String toLowerCase()将当前字符串转换成小写字符串并返回。

  • public char[] toCharArray()将当前字符串拆分成字符数组并返回。

  • public byte[] getBytes()获取当前字符串底层的字节数组并返回。

  • public String replace(CharSequence target, CharSequence replacement)将所有出现的老字符串替换成新字符串并返回替换后的新字符串。

分割相关的常用方法

  • public String[] split(String regex)按参数规则将字符串切割成若干个部分并返回一个String[]数组,split其实是一个正则表达式,如需按英文句点".“进行切割则需写”\"。
import java.util.Arrays;

public class Demo {
    public static void main(String[] args) {
        String str = "HelloWorld";

        System.out.println(str.equals("helloWorld"));           // false
        System.out.println(str.equalsIgnoreCase("helloWorld")); // true

        System.out.println(str.length());        // 10
        System.out.println(str.concat("World")); // HelloWorldWorld
        System.out.println(str.charAt(5));       // W
        System.out.println(str.indexOf("oWo"));  // 4

        System.out.println(str.substring(5));    // World
        System.out.println(str.substring(4, 7)); // oWo

        System.out.println(str.toUpperCase());   // HELLOWORLD
        System.out.println(str.toLowerCase());   // helloworld
        System.out.println(str.startsWith("H")); // true
        System.out.println(Arrays.toString(str.toCharArray())); 
        	// [H, e, l, l, o, W, o, r, l, d]
        System.out.println(Arrays.toString(str.getBytes()));    
        	// [72, 101, 108, 108, 111, 87, 111, 114, 108, 100]
        System.out.println(str.replace("World", "WORLD"));   // HelloWORLD

        System.out.println(Arrays.toString(str.split("o"))); // [Hell, W, rld]
    }
}

注意 : 程序中直接写上双引号的字符串是在字符串常量池中的,new的不在池中,==对于基本类型来说是进行数值的比较;对于引用类型来说是进行对象的地址值比较。

Arrays

java.util.Arrays类是数组相关的工具类,提供了大量静态方法来实现数组的常用操作。

  • public static String toString(数组)将参数数组按指定格式转换成字符串。
  • public static void sort(数组)按默认升序对数组元素进行排序,如果是自定义类型,那这个自定义的类就需要有Comparable或Comparator接口的支持。
import java.util.Arrays;

public class Demo {
    public static void main(String[] args) {
        int[] arr = {100, 300, 200};
        System.out.println(Arrays.toString(arr)); // [100, 300, 200]
        Arrays.sort(arr);
        System.out.println(Arrays.toString(arr)); // [100, 200, 300]
    }
}
Math

java.util.Math类是数学相关的工具类,提供了大量的静态方法用来完成数学运算相关的操作。

  • public static double abs(double a)获取绝对值,有多种重载。

  • public static double ceil(double a)向上取整,进一法。

  • public static double floor(double a)向下取整,去尾法,也可强转。

  • public static long round(double a)四舍五入。

  • public static final double PI圆周率近似值常量。

public class Demo {
    public static void main(String[] args) {
        System.out.println(Math.abs(-22.2)); // 22.2
        System.out.println(Math.ceil(14.1)); // 15.0
        System.out.println(Math.floor(5.9)); // 5.0
        System.out.println(Math.round(6.1)); // 6
        System.out.println(Math.round(6.5)); // 7
        System.out.println(Math.PI);         // 3.141592653589793
    }
}
Object、Objects

java.lang.Object类是java中的根类,每个类都直接或间接地继承自该类。

  • public String toString()返回对象的地址值信息等,生成重写后可返回该对象的属性信息。
  • public boolean equals(Object obj)比较两个对象地址值是否相等,生成重写后可比较两个对象的属性信息。

java.util.Objects类是实用程序类,提供了一些静态方法来操作对象。

  • public static boolean equals(Object a, Object b)比较两个对象是否相等,更具健壮性。
  • public static T requireNonNull(T obj)查看指定引用对象是否为null。
import java.util.Objects;

class Person {
    private String name;

    public Person() {
    }

    public Person(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                '}';
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Person person = (Person) o;
        return Objects.equals(name, person.name);
    }
}

public class Demo {
    public static void main(String[] args) {
        Person person1 = new Person("铁头娃");
        System.out.println(person1); // Person{name='铁头娃'}

        Person person2 = new Person("铁头娃");
        System.out.println(person2); // Person{name='铁头娃'}

        System.out.println(person1.equals(person2));          // true
        System.out.println(Objects.equals(person1, person2)); // true
    }
}
Date、DateFormat

java.util.Date类是时间/日期类,表示一个特定的瞬间,具体精确到毫秒。时间原点为1970年1月1日00:00:00。而中国位于东八区,原点时间会增加8小时。

构造方法

  • public Date()创建一个日期对象,获取当前系统的具体时间并转换为默认格式的日期。
  • public Date(long date)创建一个日期对象,将传入的具体毫秒值转换为默认格式的日期。

常用方法

  • public long getTime()获取当前日期对象的具体毫秒值。

java.text.SimpleDateFormat类是日期格式化/解析的具体类,继承自抽象父类DateFromat,SimpleDateFormat可完成日期和文本间的转换,即Date对象和String对象的相互转换。

构造方法

  • public SimpleDateFormat(String pattern)创建一个日期格式化/解析的具体类,参数传递日期规则。

常用方法

  • public final String format(Date date)按指定规则将Date对象转换成String对象。
  • public Date parse(String source) throws ParseException按指定规则将String对象转换成Date对象。
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;

public class Demo {
    public static void main(String[] args) throws ParseException {
        Date date1 = new Date();
        System.out.println(date1);

        Date date2 = new Date(1234567654321L);
        System.out.println(date2);
        System.out.println(date2.getTime());

        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-HH-mm HH:mm:ss");
        System.out.println(sdf.format(new Date()));
        System.out.println(sdf.parse(sdf.format(new Date())));
    }
}
Calendar

java.util.Calendar类是日历抽象类,该类将所有可能用到的时间信息封装为静态成员变量,方便获取各个时间的属性。

  • public static Calendar getInstance()创建一个日历抽象类的子类对象并返回。
  • public int get(int field)返回给定字段的值,常用的日历字段值有YEAR、MONTH、DAY_OF_MONTH、HOUR_OF_DAY、MINUTE、SECOND。
  • public void set(int field, int value)给指定的日历字段设定给定值。
  • public abstract void add(int field, int amount)根据日历规则,为给定的日历字段添加或减少时间值。
  • public final Date getTime()将Calendar对象转换为Date对象并返回。
import java.util.Calendar;
import java.util.Date;

public class Demo {
    public static void main(String[] args) {
        Calendar calendar = Calendar.getInstance();
        System.out.println(calendar);

        System.out.println(calendar.get(Calendar.YEAR));
        System.out.println(calendar.get(Calendar.MONTH));
        System.out.println(calendar.get(Calendar.DAY_OF_MONTH));
        System.out.println(calendar.get(Calendar.HOUR_OF_DAY));
        System.out.println(calendar.get(Calendar.MINUTE));
        System.out.println(calendar.get(Calendar.SECOND));

        calendar.set(Calendar.YEAR, 9999);
        System.out.println(calendar.get(Calendar.YEAR)); // 9999

        calendar.add(Calendar.YEAR, -999);
        System.out.println(calendar.get(Calendar.YEAR)); // 9000

        Date date = calendar.getTime();
        System.out.println(date);
    }
}
System

java.lang.System类是系统类,提供了大量的静态方法,可以获取与系统相关的信息或系统级操作。

  • public static native long currentTimeMillis()返回以毫秒为单位的当前系统时间。
  • public static native void arraycopy(Object src, int srcPos, Object dest, int destPos, int length)将数组中的数据拷贝到另一数组中(数组源, 源数组起始索引, 目标数组, 目标数组起始索引, 复制元素的个数)。
import java.util.Arrays;

public class Demo {
    public static void main(String[] args) {
        System.out.println(System.currentTimeMillis());

        int[] arr1 = {100, 200, 300, 400, 500};
        int[] arr2 = {500, 400, 300, 200, 100};
        System.arraycopy(arr1, 2, arr2, 1, 3);
        System.out.println(Arrays.toString(arr2)); // [500, 300, 400, 500, 100]
    }
}
StringBuffer、StringBuilder

java.lang.StringBuffer/StringBuilder类都是可变字符串类,也叫可变序列,类似于字符串缓冲区。其字符长度可变,但StringBuffer效率低,线程安全;而StringBuilder效率高,但线程不安全。所以多数情况下建议使用StringBuilder类,如果应用程序对线程安全有要求的话则必须使用StringBuffer类。StringBuffer中的成员方法大都在权限修饰符和返回值类型间加上了synchronized关键字。

构造方法

  • public StringBuffer/StringBuilder()创建一个空的可变字符串容器。默认占用16个字节。
  • public StringBuffer/StringBuilder(String str)创建一个可变字符串容器,并将参数字符串添加进去。

常用方法

  • public StringBuffer append(String str)往字符串容器中添加任意类型的数据。
  • public StringBuffer delete(int start, int end)删除字符串容器中指定索引位置内的字符。
  • public StringBuffer deleteCharAt(int index)删除字符串容器中指定索引位置的单个字符。
  • public StringBuffer reverse()将字符串容器进行反转。
  • public String toString()将当前的字符串容器对象转换为String对象。
public class Demo {
    public static void main(String[] args) {
        StringBuffer strBuffer = new StringBuffer();
        StringBuilder strBuilder = new StringBuilder("abbcccdddd");

        System.out.println(strBuffer.append("abbcccdddd")); // abbcccdddd
        System.out.println(strBuffer.delete(1, 3));         // acccdddd
        System.out.println(strBuffer.deleteCharAt(0));      // cccdddd

        System.out.println(strBuilder.reverse());           // ddddcccbba
        String str = strBuilder.toString();
        System.out.println(str);                            // ddddcccbba
    }
}

何时使用哪种字符串 ? 操作少量数据多用String,多线程操作大量数据使用StringBuffer,单线程操作大量数据则使用StringBuilder。

包装类

java.lang.Integer类等都是其对应的基本数据类型的包装类,java提供了两个类型系统,基本类型和引用类型,如果想要基本类型像对象一样进行操作,就可以使用基本类型所对应的包装类,如果字符串参数的内容无法正确转换为对应的基本类型,则会抛出NumberFormatException数字格式化异常。如果集合中想存储基本类型的数据,则集合中的泛型必须使用其对应的包装类。泛型只能是引用类型不能为基本类型。

装箱就是从基本类型转换为包装类,而拆箱就是从包装类转换为基本类型。

  • public static Integer valueOf(int i)将传递的参数转换为包装类型并返回。
  • public int intValue()将包装类型数据转换为基本类型数据并返回。

基本类型转换为对应的String字符串

  • 直接在基本类型的后面加上""即可。
  • public static String toString(基本数据类型)将传递的参数以字符串形式返回。这是String类的静态方法。
  • public static String valueOf(基本类型数据)将传递的参数以字符串形式返回。这是包装类的静态方法。

String字符串转换为对应的基本类型

  • public static int parseInt(String s) throws NumberFormatException将字符串参数转换为对应的int类型数据。parseInt可以换为parseFloatparseDouble等,但Character除外。
public class Demo {
    public static void main(String[] args) {
        Integer num1 = Integer.valueOf(100);
        System.out.println(num1 + 1); // 101

        int num2 = num1.intValue();
        System.out.println(num2 + 1); // 101

        String str1 = 100 + "";
        System.out.println(str1 + 1); // 1001
        String str2 = Integer.toString(100);
        System.out.println(str2 + 1); // 1001
        String str3 = String.valueOf(100);
        System.out.println(str3 + 1); // 1001

        int num3 = Integer.parseInt(str1);
        System.out.println(num3 + 1); // 101
    }
}

常用集合

Collection

java.util.Collection接口是单列集合类的根接口,用于存储一系列符合某种规定的元素,它有两个重要的子接口,java.util.List和java.util.Set。集合本身是一个工具,存放在java.util包中,在Collection接口中定义着单列集合中最共性的内容。底层为数组的集合增删慢,查找快;底层为链表的集合增删快,查找慢。

  • boolean add(E e)添加并判断指定元素是否成功添加到集合中。
  • boolean remove(Object o)删除并判断指定元素是否从集合中删除成功。
  • boolean contains(Object o)判断集合中是否包含指定元素。
  • boolean isEmpty()判断集合是否为空。
  • void clear()清空集合中所有元素。
  • public static String toString(Object[] a)将集合中的元素存储到数组中。
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;

public class Demo {
    public static void main(String[] args) {
        Collection<Integer> list = new ArrayList<>();
        list.add(100);
        list.add(400);
        list.add(300);
        System.out.println(list);                // [100, 400, 300]

        System.out.println(list.add(200));       // true
        System.out.println(list.remove(400));    // true
        System.out.println(list.contains(100));  // true
        System.out.println(list.isEmpty());      // false
        
        System.out.println(list);                // [100, 300, 200]
        System.out.println(Arrays.toString(list.toArray()));
                                                 // [100, 300, 200]
        list.clear();
        System.out.println(list);                // []
    }
}

集合框架的学习方式 : 学习顶层,使用底层。

Iterator、for-each

java.util.Iterator接口主要用于迭代访问,也就是遍历Collection集合中的元素,因此Iterator对象也被称为迭代器。而Collection跟Map接口主要用于存储元素,因为Iterator是一个接口,所以我们无法直接使用,需要使用Iterator接口的实现类对象,也就是Collection接口中的iterator()方法。这个方法返回的就是迭代器的实现类对象,想要遍历Collection集合就需要获取该集合的迭代器来完成迭代操作。

迭代就是Collcetion集合元素的通用获取方式,在取出元素之前会先判断集合中有没有元素,如果有就把这个元素取出来,再继续判断,如果还有就再取出来,直到把集合中所有元素全部取出,这种取出方式的专业术语就称之为迭代。

  • Iterator iterator()获取集合对应的迭代器对象,用来遍历集合中的元素。
  • boolean hasNext()判断集合中是否存在下一个元素。
  • E next()返回迭代的元素。

迭代器的实现原理,当遍历集合时,先调用Collection集合中的iterator()方法获取迭代器对象,然后使用hashNext()方法判断集合中是否存在下一个元素,如果存在则调用next()方法将元素取出,否则说明已经达到了集合的末尾,需停止遍历集合,否则会抛出NoSuchElementException没有集合元素异常。

Iterator迭代器对象在遍历集合时,内部采用指针的方式来跟踪集合中的元素。在调用Iterator对象中的next()方法之前,迭代器的索引位于第一个元素之前,不指向任何元素,当第一次调用迭代器的next()方法后,迭代器的索引会向后移动一位,指向第一个元素并将该元素返回,当再次调用next()方法时,迭代器的索引就会指向第二个元素并将该元素返回,以此类推,直到hashNext()方法返回false,表示到达了集合的末尾,终止对集合的遍历。

增强for循环也称为for-each循环,是专门用来遍历数组跟集合的。内部原理其实就是一个Iterator迭代器,所以在遍历的过程中不能对集合中的元素进行增删操作,此循环必须有被遍历的目标,且目标只能是Collection或数组。

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;

public class Demo {
    public static void main(String[] args) {
        Collection<Integer> list = new ArrayList<>();
        Collections.addAll(list, 100, 300, 200);

        Iterator<Integer> iterator = list.iterator();

        while (iterator.hasNext()) {
            System.out.println(iterator.next());
        }

        for (Integer integer : list) {
            System.out.println(integer);
        }

        for (Iterator<Integer> i = iterator; iterator.hasNext();) {
            System.out.println(i.next());
        }
    }
}
/*----------------------------------------
	100
	300
	200
	100
	300
	200
----------------------------------------*/
泛型

集合中是可以存放任意对象的,将对象存入集合后,它们都会被提升为Object类型,但当我们在取出每一个对象并进行相应的操作时就必须采用类型转换。Collection虽然可以存储各种对象,但实际上通常Collection只存储同一类型的对象。泛型语法就可以让你在设计API时可以指定类或方法支持泛型,这样我们使用API时也变得更为简洁,并得到编译时期的语法检查。泛型就是可以在类或方法中预支地使用未知的类型,一般在创建对象时,将未知的类型确定具体的类型,当没有指定泛型时,默认类型就是Object类型。

泛型可以将运行期的ClassCastException转移到编译时期的运行失败,也避免了类型转换的麻烦,但指定泛型的类型后就只能存储指定的类型了。

  • 定义和使用含有泛型的类,修饰符 class 类名<代表泛型的变量> {···}。
  • 创建时确定泛型的类型,如Collection list = new ArrayList<>();
  • 含有泛型的方法,修饰符 <代表泛型的变量> 返回值类型 方法名(参数列表) {···}
  • 含有泛型的接口,修饰符 interface 接口名<代表泛型的变量> {···}
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;

// 定义一个泛型类,泛型的类型为E
class GenericClass<E> {
    // 定义一个成员方法,泛型使用类的泛型E
    public void method(E e) {
        System.out.println(e);
    }

    // 定义一个泛型方法,泛型使用自身方法的泛型,泛型的类型为M
    public <M> void genericMethod(M m) {
        System.out.println(m);
    }

    // 定义一个静态泛型方法,泛型使用自身方法的泛型,泛型类型为N
    public static <N> void genericStaticMethod(N n) {
        System.out.println(n);
    }
}

// 定义一个泛型接口,泛型的类型为I
interface GenericInterface<I> {
    void method(I i);
}

// 定义一个实现类来实现泛型接口,并在此时指定接口中泛型的具体类型
class GenericImpl1 implements GenericInterface<String> {
    @Override
    public void method(String s) {
        System.out.println(s);
    }
}

// 定义一个实现类来实现泛型接口,在创建对象时指定泛型的具体类型
class GenericImpl2<I> implements GenericInterface<I> {
    @Override
    public void method(I i) {
        System.out.println(i);
    }
}

public class Demo {
    public static void main(String[] args) {
        GenericClass<String> gc = new GenericClass<>();
        gc.method("这是泛型类中的成员方法,泛型只能使用类的泛型!");
        gc.genericMethod("这是类中的泛型方法,泛型可以使用任意数据类型!");
        GenericClass.genericStaticMethod("这是类中的静态泛型方法,泛型也可以使用任意数据类型!");

        new GenericImpl1().method("这是接口的实现类对象中的方法,在创建实现类时就指定了泛型!");
        new GenericImpl2<String>().method("这也是接口中的实现类对象方法,在创建对象的时候来定义泛型的具体类型!");

        Collection<Integer> list = new ArrayList<>();
        Collections.addAll(list, 100, 300, 200);
        
        getList(list); // 100 300 200 
    }

    // 定义一个遍历集合的方法,使用泛型通配符进行传参
    public static void getList(Collection<?> list) {
        for (Object o : list) {
            System.out.print(o + " ");
        }
    }
}

当使用泛型类或接口时,传递的数据中,泛型的类型不确定,可以通过泛型通配符<?>表示,一旦使用泛型的通配符后,就只能使用Object类中的共性方法了,集合中元素自身的方法将无法使用。在不知道使用什么类型来接收的时候,就可以使用泛型通配符。?表示未知通配符,此时只能接收数据,不能往该集合中存储数据,泛型中是不存在泛型关系的,这样写Collection list = new ArrayList();是错误的。

通配符的高级使用,受限泛型。之前设置泛型的时候,实际上是可以任意设置的,只要是类就可以设置,但在java中,泛型里是可以指定一个泛型的上限和下限的。

  • 泛型的上限格式,类型名称<? extends 类> 对象名称;即只能接收该类型及其子类。
  • 泛型的下限格式,类型名称<? super 类> 对象名称;即只能接收该类型及其父类。
import java.util.ArrayList;
import java.util.Collection;

public class Demo {
    public static void main(String[] args) {
        // 创建一个集合,泛型指定为String
        Collection<String> list1 = new ArrayList<>();
        getElement1(list1); // error
        getElement2(list1); // error

        // 创建一个集合,泛型指定为Integer
        Collection<Integer> list2 = new ArrayList<>();
        getElement1(list2);
        getElement2(list2); // error

        // 创建一个集合,泛型指定为Object
        Collection<Object> list3 = new ArrayList<>();
        getElement1(list3); // error
        getElement2(list3);

        // 创建一个集合,泛型指定为Number
        Collection<Number> list4 = new ArrayList<>();
        getElement1(list4);
        getElement2(list4);
    }

    // 指定泛型的下限,参数中的泛型?只能传递Number类型或Number类型的子类
    public static void getElement1(Collection<? extends Number> list) {
    }

    // 指定泛型的上限,参数中的泛型?只能传递Number类型或Number类型的子类
    public static void getElement2(Collection<? super Number> list) {
    }
List

java.util.List接口继承自Collection接口,List集合是一个元素存取有序的集合,所以可以保证存储的顺序;是一个带有索引的集合;集合中可存储重复的元素。List接口的主要实现类有java.util.ArrayList和java.util.LinkedList。

  • void add(int index, E element)在集合中的指定索引位置添加指定元素。
  • E get(int index)获取集合中参数索引的元素。
  • E remove(int index)移除集合中参数索引位置的元素并返回。
  • set(int index, E element)将集合中参数索引位置的元素替换为指定元素并返回被替换前的元素。
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

public class Demo {
    public static void main(String[] args) {
        List<Integer> list = new ArrayList<>();
        Collections.addAll(list, 100, 300, 200);

        list.add(1, 400);
        System.out.println(list.get(1));          // 400
        System.out.println(list.remove(2));       // 300
        System.out.println(list.set(1, 500));     // 400
        System.out.println(list);                 // [100, 500, 200]
    }
}
ArrayList

java.util.ArrayList集合中数据存储底层结构为数组,增删慢,查找快。

  • public boolean add(E e)添加并判断元素是否添加成功。
  • public E get(int index)获取参数索引位置的元素。
  • public E remove(int index)移除集合中参数索引位置的元素并返回。
  • public int size()获取集合的长度。
import java.util.ArrayList;
import java.util.Collections;

public class Demo {
    public static void main(String[] args) {
        ArrayList<Integer> list = new ArrayList<>();
        Collections.addAll(list, 100, 300, 200);

        System.out.println(list.add(400));  // true
        System.out.println(list.get(1));    // 300
        System.out.println(list.remove(2)); // 200
        System.out.println(list.size());    // 3
        System.out.println(list);           // [100, 300, 400]
    }
}
LinkedList

java.util.LinkedList集合中数据存储的底层结构为链表,且是一个双向链表,增删快,查找慢。

  • public void addFirst(E e)将指定元素添加到集合开头。
  • public void addLast(E e)将指定元素添加到集合末尾。
  • public E getFirst()获取集合中的开头元素。
  • public E getLast()获取集合中的末尾元素。
  • public E removeFirst()删除并返回集合中的开头元素。
  • public E removeLast()删除并返回集合中的末尾元素。
  • public boolean isEmpty()判断集合是否为空。
  • public void push(E e)将指定元素添加到集合开头。
  • public E pop()删除并返回集合中的开头元素。
import java.util.Collections;
import java.util.LinkedList;

public class Demo {
    public static void main(String[] args) {
        LinkedList<Integer> list = new LinkedList<>();
        Collections.addAll(list, 100, 300, 200);

        list.addFirst(400);
        list.addLast(500);
        System.out.println(list.getFirst());    // 400
        System.out.println(list.getLast());     // 500
        System.out.println(list.removeFirst()); // 400
        System.out.println(list.removeLast());  // 500
        System.out.println(list.isEmpty());     // false
        list.push(600);
        System.out.println(list.pop());         // 600
        System.out.println(list);               // [100, 300, 200]
    }
}
Set

java.util.Set接口继承自Collection接口,与Collection接口中的方法基本一致,与List接口不同的是Set集合不允许存储重复元素,且没有索引。Set主要实现类有java.util.HashSet、java.util.TreeSet和java.util.LinkedHashSet。Set集合取出元素的方式可以采用迭代器和for循环。Set集合在调用add()方法的时候,add()方法会先调用元素的hashCode()方法计算元素的哈希值,哈希值是一个十进制的整数,由对象随机给出,其实就是对象的地址值,是一个逻辑地址,在集合中寻找有没有相同哈希值的元素,如果没有就将此元素存储到集合中,如果有则会调用equals()方法再和集合中相同哈希值的元素进行比较,结果为false就认定两个元素不同,就会把元素存储到集合中,结果为true则认定两个元素相同,就不会把元素存储到集合中。Set集合想要存储不重复的元素就必须重写hashCoe()方法和equals()方法。

  • boolean add(E e)添加并判断指定元素是否成功添加到集合中。
  • boolean remove(Object o)删除并判断指定元素是否从集合中删除成功。
  • boolean contains(Object o)判断集合中是否包含指定元素。
  • boolean isEmpty()判断集合是否为空。
  • void clear()清空集合中所有元素。
  • public static String toString(Object[] a)将集合中的元素存储到数组中。
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;

public class Demo {
    public static void main(String[] args) {
        Set<Integer> set = new HashSet<>();
        Collections.addAll(set, 100, 300, 200);

        set.add(400);
        System.out.println(set.remove(300));   // true
        System.out.println(set.contains(300)); // false
        System.out.println(set.isEmpty());     // false
        System.out.println(set.toString());
        System.out.println(set);
        set.clear();
    }
}
HashSet

java.util.HashSet集合是一个无索引、不可以存储重复元素且存取无序的集合。存储数据的底层结构是一个哈希表结构,查询的速度非常快。在JDK8前是数组+链表结构,JDK8后哈希表结构为数组+链表/红黑树,提高了查询的速度。其原理就是用数组对元素进行了分组,存储数据到集合中会先计算元素的哈希值,即相同哈希值的元素是一组,再使用链表/红黑树把相同哈希值的元素连接到一起。如果两个元素不同,但哈希值却相同,就会发生哈希冲突,如“重地”和“通话”。如果链表的长度超过了8位,那么就会把链表转换为红黑树来提高查询的速度。

import java.util.HashSet;

public class Demo {
    public static void main(String[] args) {
        HashSet<String> set = new HashSet<>();
        set.add("哈哈");
        set.add("重地");
        set.add("通话");
        set.add("哈哈");

        System.out.println(set);
    }
}
TreeSet

java.util.TreeSet集合是一个无序的且不可以存储重复元素的集合。 底层数据结构是红黑树(自平衡的二叉树)。

import java.util.Collections;
import java.util.TreeSet;

public class Demo {
    public static void main(String[] args) {
        TreeSet<Integer> set = new TreeSet<>();
        Collections.addAll(set, 100, 300, 200);

        System.out.println(set.add(100)); // false
        System.out.println(set);
    }
}
LinkedHashSet

java.util.LinkedHashSet集合继承自HashSet,底层数据结构就是一个哈希表(数组+链表/红黑树)+链表。多了条链表来记录元素的顺序,保证元素有序。

import java.util.Collections;
import java.util.LinkedHashSet;

public class Demo {
    public static void main(String[] args) {
        LinkedHashSet<Integer> set = new LinkedHashSet<>();
        Collections.addAll(set, 100, 300, 200);
        
        System.out.println(set.add(400)); // true
        System.out.println(set);          // [100, 300, 200, 400]
    }
}
可变参数

当方法的参数列表数据类型已经确定,但参数列表的个数不确定,就可以使用可变参数。可变参数的底层其实就是一个数组,根据传递参数的个数不同,会创建不同长度的数组来存储这些数据,传递参数的个数可以是0个或多个。可变参数可在定义方法时使用,一个方法的参数列表只能有一个可变参数,如果方法的参数有多个,那么可变参数就必须写在参数列表的末尾,可变参数的终极写法为(Object…object)。

public class Demo {
    public static void main(String[] args) {
        method(6, 6, 6);
    }

    public static void method(int... i) {
        for (int s : i) {
            System.out.print(s + " "); // 6 6 6
        }
    }
}
Collections

java.utils.Collections是集合工具类,用来对集合进行操作。

  • public static boolean addAll(Collection<? super T> c, T… elements)往集合中添加一些元素。
  • public static void shuffle(List<?> list)打乱集合中的元素。
  • public static <T extends Comparable<? super T>> void sort(List list)将集合中的元素按指定规则排序,被排序的集合中存储的元素类型必须实现Comparable接口,并重写接口中compareTo()方法来定义排序规则,排序规则为this - 参数为升序,参数 - this为降序。
  • public static void sort(List list, Comparator<? super T> c)将集合中的元素按指定规则排序,重写Comparator接口中的compare()方法来定义排序规则,排序规则为o1 - o2为升序,o2 - o1为降序。

java.lang.Comparable接口是强行对实现它的每个类的对象进行整体排序,这种排序被称为类的自然排序,类的compareTo()方法被称为它的自然比较方法,只能在类中实现一次compartTo()方法,不能经常修改类的代码来实现自己想要的排序,实现此接口的对象列表或数组可以通过Collections.sort或Arrays.sort进行自动排序,对象可以用作有序映射中的键或有序集合中的元素,无序指定比较器。

java.util.Comparator接口是强行对某个对象进行整体排序,可以将Comparator传递给sort()方法,从而允许在排序顺序上实现精确控制,还可以使用Comparator来控制某些数据结构(如有序set或有序映射)的顺序,或者为那些没有自然顺序的对象Collection提供排序。

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;

// 定义一个Person类,实现含有泛型的Comparable接口
class Person implements Comparable<Person> {
    private String name;
    private int age;

    public Person() {
    }

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", age='" + age + '\'' +
                '}';
    }

    // 重写Comparable接口中的compareTo方法来定义排序规则
    @Override
    public int compareTo(Person o) {
        return this.getAge() - o.getAge();
    }
}

public class Demo {
    public static void main(String[] args) {
        ArrayList<Person> list = new ArrayList<>();
        list.add(new Person("三", 15));

        Collections.addAll(list, new Person("一", 18), new Person("二", 17));
        System.out.println(list);

        // 打乱集合中的元素
        Collections.shuffle(list);

        // 将集合按指定规则进行排序
        Collections.sort(list);
        System.out.println(list);

        // 重写Comparator接口中的compare方法,将集合按指定规则进行排序
        Collections.sort(list, new Comparator<Person>() {
            @Override
            public int compare(Person o1, Person o2) {
                return o2.getAge() - o1.getAge();
            }
        });
        System.out.println(list);
    }
}
/*----------------------------------------
[Person{name='三', age='15'}, Person{name='一', age='18'}, Person{name='二', age='17'}]
[Person{name='三', age='15'}, Person{name='二', age='17'}, Person{name='一', age='18'}]
[Person{name='一', age='18'}, Person{name='二', age='17'}, Person{name='三', age='15'}]
----------------------------------------*/
Map

public interface Map<K, V>接口称为双列集合,Map集合中一个元素包含两个值,key和value。Map集合中元素key和value的数据类型可以相同,也可以不同;key是不允许重复的,value可重复;key和value是一一对应的。

  • V put(K key, V value)将指定的键值添加到集合中,并判断指定的键是否重写了,是就返回被重写前的值,否则返回null。

  • V remove(Object key)删除指定的键所对应的值并返回被删除的值。

  • V get(Object key)获取指定的键所对应的值。

  • boolean containsKey(Object key)判断集合中有没有包含指定的键。

  • Set keySet()将集合中所有的键存储到Set集合中。

  • Set<Map.Entry<K, V>> entrySet()将集合中的多个Entry键值对对象存储到Set集合中。Entry表示一对键和值,Entry<K, V>是Map接口中的内部成员接口。K getKey()获取Entry对象的键,V getValue()获取Entry对象的值。

import java.util.HashMap;
import java.util.Map;
import java.util.Set;

public class Demo {
    public static void main(String[] args) {
        Map<Integer, String> map = new HashMap<>();
        map.put(1, "one");
        map.put(2, "two");
        map.put(3, "three");

        System.out.println(map.put(2, "four"));
        System.out.println(map.remove(2));
        System.out.println(map.get(3));
        System.out.println(map.containsKey(4));
        
        Set<Integer> set = map.keySet();
        System.out.println(set);
        
        Set<Map.Entry<Integer, String>> entrySet = map.entrySet();
        for (Map.Entry<Integer, String> entry : entrySet) {
            System.out.print(entry.getKey() + "=" + entry.getValue() + ";");
        }
    }
}
/*----------------------------------------
	two
	four
	three
	false
	[1, 3]
	1=one;3=three
----------------------------------------*/

在JDK9中增加了一个静态方法staticList/Set/Map of(E…elements),如果集合中存储的元素的个数已经确定了,不再改变使用,那么就可以使用of方法。of方法只能够用于List、Set、Map接口,不能用于接口的实现类,of方法的返回值是一个不能改变的集合,集合不能再使用add()、pub()等方法添加元素,否则会抛出异常,Set接口和Map方法在调用of()方法的时候,不能有重复的元素,否则也会抛出异常。

import java.util.List;
import java.util.Map;
import java.util.Set;

public class Demo {
    public static void main(String[] args) {
        List<Integer> list = List.of(100, 300, 200);
        System.out.println(list);

        Set<Integer> set = Set.of(100, 300, 200);
        System.out.println(set);

        Map<Integer, Integer> map = Map.of(1, 100, 2, 300, 3, 200);
        System.out.println(map);
    }
}
HashMap

java.util.HashMap<K, V>集合的底层是哈希表,查询的速度特别快,JDK8前是数组+单向列表,JDK8后是数组+单向列表/红黑树(链表的长度超过8),提高了查询的速度。HashMap集合是一个无序的集合,存储元素和取出元素的顺序可能不一致。

import java.util.HashMap;

public class Demo {
    public static void main(String[] args) {
        HashMap<Integer, String> map = new HashMap<>();
        map.put(1, "one");
        map.put(2, "two");
        map.put(3, "three");

        System.out.println(map);
    }
}
LinkedHashMap

java.util.LinkedHashMap<K, V>集合继承自HashMap集合,LinkedHashMap底层数据结构是哈希表+链表,保证迭代的顺序,LinkedHashMap集合是一个有序的集合,存储和取出元素的顺序一致。

import java.util.LinkedHashMap;

public class Demo {
    public static void main(String[] args) {
        LinkedHashMap<Integer, String> map = new LinkedHashMap<>();
        map.put(1, "one");
        map.put(2, "two");
        map.put(3, "three");

        System.out.println(map);
    }
}
File

java.io.File类是文件和目录路径名的抽象类表示,即java把电脑中的文件和文件夹/目录封装成了一个File类,我们可以使用File类对文件和文件夹进行操作。File类主要用于文件和目录的创建、查找、删除等操作。File类是一个与系统无关的类,任何的操作系统都可以使用这个类中的方法。file文件、directory文件夹/目录、path路径。

  • public static final char pathSeparatorChar获取与系统有关的路径分隔符,windows为分号;Linux为冒号。
  • public static final String pathSeparator获取路径分隔符,String类型。
  • public static final char separatorChar获取与系统有关的默认名称分隔符,windows为反斜杠\;Linux为正斜杠/。
  • public static final String separator获取默认名称分隔符,String类型。

构造方法

  • File(String pathname)通过给定的路径名字符串转换为抽象路径名来创建一个新File实例。其中参数列表代表字符串的路径名,路径可以以文件结尾,也可以文件夹结尾;路径可以是相对路径,也可以是绝对路径;路径可以是存在的,也可以是不存在的;创建File对象,只是把字符串路径封装为File对象,不考虑路径的真假情况。
  • File(String parent, String child)根据parent路径名字符串和child路径名字符串创建一个新的File实例。其中参数列表分成了两部分,String parent父路径,String child子路径,父路径和子路径可以单独书写,使用起来非常的灵活,父路径和子路径都可以变化。
  • File(File parent, String child)根据parent抽象路径名和child路径名字符串创建一个新的File实例,其中参数列表分成了两部分,File parent父路径,String child子路径;父路径和子路径可以单独书写,使用起来非常的灵活,父路径和子路径都可以变化,父路径是File类型,可以使用File类的方法对路径进行一些操作,再使用路径创建对象。

获取信息的常用方法

  • public String getAbsolutePath()返回此File的绝对路径。
  • public String getPath()返回此File的路径,是什么就打印什么。
  • public String getName()返回由此File表示的文件或目录名称。
  • public long length()返回由此File表示的文件的长度,假文件和文件假则为0。

判断文件的常用方法

  • public boolean exists()判断此File表示的文件或目录是否实际存在。
  • public boolean isDirectory()判断此File表示的是否为目录,路径必须存在,否则返回false。
  • public boolean isFile()判断此File表示的是否为文件,路径必须存在,否则返回false。

创建删除的常用方法

  • public boolean createNewFile() throws IOException当File表示的文件尚不存在时,创建一个新的空文件。
  • public boolean mkdir()创建由此File表示的目录。
  • public boolean mkdirs()创建由此File表示的目录,也可多级创建。
  • public boolean delete()删除由此File表示的文件或文件夹。

遍历目录的常用方法

  • public String[] list()返回一个String数组,表示该File目录中的所有文件夹或目录。
  • public File[] listFiles()返回一个File数组,表示该File目录中的所有文件夹或目录。
import java.io.File;
import java.io.IOException;
import java.util.Arrays;

public class Demo {
    public static void main(String[] args) throws IOException {
        System.out.print(File.pathSeparatorChar + File.pathSeparator); // ;;
        System.out.println(File.separatorChar + File.separator);       // \\

        File file1 = new File("D:\\java");
        System.out.println(file1); // D:\java

        File file2 = new File("D:\\java", "Demo.java");
        System.out.println(file2); // D:\java\Demo.java

        File file3 = new File(file1, "\\Demo.java");
        System.out.println(file3); // D:\java\Demo.java
        System.out.println();

        // 创建多级文件夹并删除
        System.out.println(new File("D:\\java\\java").mkdirs());    // true
        System.out.println(new File("D:\\java\\java").delete());    // true
        System.out.println(new File("D:\\java").delete() + "\r\n"); // true

        // 创建文件夹和文件
        System.out.println(file1.mkdir());                  // true
        System.out.println(file3.createNewFile() + "\r\n"); // true

        // 遍历目录的常用方法
        String[] list = file1.list();
        System.out.println(Arrays.toString(list));          // [Demo.java]
        File[] file = file1.listFiles();
        System.out.println(Arrays.toString(file) + "\r\n"); // [D:\java\Demo.java]

        // 获取信息的常用方法
        System.out.println(file3.getAbsolutePath()); // D:\java\Demo.java
        System.out.println(file3.getPath());         // D:\java\Demo.java
        System.out.println(file3.getName());         // Demo.java
        System.out.println(file3.length() + "\r\n"); // 0

        // 判断文件的常用方法
        System.out.println(file1.exists());          // true
        System.out.println(file1.isDirectory());     // true
        System.out.println(file1.isFile() + "\r\n"); // false

        // 删除文件和文件夹
        System.out.println(file3.delete()); // true
        System.out.println(file1.delete()); // true
    }
}
过滤器

在File类中有两个和ListFiles重载的方法,方法的参数传递的就是过滤器。过滤器的原理为,listFiles()方法会遍历File对象下所有内容并封装为File对象,然后listFiles()方法会调用参数传递的过滤器中的accept()方法来过滤其目录下所有封装好的File对象,然后将遍历筛选后得到的每个File对象传递给accept()方法的参数pathname,根据返回值来确定是否将当前File对象保存到File数组中。

  • public File[] listFiles(FileFilter filter)过滤后返回一个File数组,参数传递FileFilter过滤器。
  • public File[] listFiles(FilenameFilter filter)过滤后返回一个File数组,参数传递FilenameFilter过滤器。
FileFilter、FilenameFilter

java.io.FileFilter接口用来过滤File文件。

  • boolean accept(File pathname)按指定规则过滤File对象目录下所有内容,参数File pathname是使用ListFiles()方法来遍历目录的,由此来得到每一个文件/文件夹。

java.io.FilenameFilter接口用来过滤File文件名。

  • boolean accept(File dir, String name)按指定规则过滤File对象目录下所有内容,参数File dir是被遍历的目录,String name是使用listFiles()方法来遍历目录的,由此来获取每一个文件/文件夹。

两个过滤器接口都没有实现类,需要我们自己手写实现类且重写过滤的accept()方法,在方法中定义规则,可使用匿名内部类来实现并使用Lambda进行优化。

import java.io.File;
import java.io.FileFilter;
import java.io.FilenameFilter;
import java.io.IOException;
import java.util.Random;

public class Demo {
    public static void main(String[] args) throws IOException {
        create(); // 创建文件
        filterFile(new File("D:\\java"));
        delete(); // 删除文件
    }

    private static void filterFile(File file) {
        // 随机执行,二选一
        if (new Random().nextInt(2) + 1 == 1) {
            // 参数传递并创建过滤器FileFilter接口的匿名实现类,重写过滤方法accept,返回一个File数组
            File[] files = file.listFiles(new FileFilter() {
                @Override
                // listFiles方法会遍历并调用accept方法来过滤File对象下所有内容(文件/文件夹),
                // 结果为true就将筛选得到的内容封装成File对象并存入数组中,否则不传
                public boolean accept(File pathname) {
                    // 定义过滤规则,pathname为文件夹 || pathname为小写的.java文件,返回boolean值
                    return pathname.isDirectory() || pathname.getName().toLowerCase().endsWith(".java");
                }
            });
            printFile(files);
        } else {
            // 参数传递并创建过滤器FilenameFilter接口的匿名实现类,重写过滤方法accept,返回一个File数组
            File[] files = file.listFiles(new FilenameFilter() {
                @Override
                public boolean accept(File dir, String name) {
                    // 定义过滤规则,(dir, name)为文件夹 || name为小写的.java文件,返回boolean值
                    return new File(dir, name).isDirectory() || name.toLowerCase().endsWith(".java");
                }
            });
            printFile(files);
        }
    }

    // 定义一个遍历数组的方法
    private static void printFile(File[] files) {
        // 遍历过滤后得到的File数组
        for (File f : files) {
            if (f.isDirectory()) { // 判断f是否为文件夹,如果是则递归调用再次过滤
                filterFile(f);
            } else {               // f不是文件夹就直接打印
                System.out.println(f);
            }
        }
    }

    private static void create() throws IOException {
        File file = new File("D:\\java");
        System.out.println(new File(file, "\\D1").mkdirs());
        System.out.println(new File(file, "\\D2").mkdirs());
        System.out.println(new File(file + "\\1.java").createNewFile());
        System.out.println(new File(file + "\\D1\\2.java").createNewFile());
        System.out.println(new File(file + "\\D2\\3.java").createNewFile());
        System.out.println(new File(file + "\\D1\\2.txt").createNewFile());
        System.out.println(new File(file + "\\D2\\3.txt").createNewFile());
    }

    private static void delete() {
        File file = new File("D:\\java");
        System.out.println(new File(file + "\\1.java").delete());
        System.out.println(new File(file + "\\D1\\2.java").delete());
        System.out.println(new File(file + "\\D2\\3.java").delete());
        System.out.println(new File(file + "\\D1\\2.txt").delete());
        System.out.println(new File(file + "\\D2\\3.txt").delete());
        System.out.println(new File(file, "\\D1").delete());
        System.out.println(new File(file, "\\D2").delete());
        System.out.println(file.delete());
    }
}

异常

异常是指在程序的执行过程中出现的非正常的情况,最终会导致JVM非正常停止。在java中异常本身就是一个类,产生异常就是在创建异常对象并抛出异常对象,java处理异常的方式是中断处理,异常并不是语法错误,语法错误是不会产生字节码文件的,而且根本不能运行。异常机制其实就是帮助我们找到程序中的问题。异常的根类是java.lang.Throwable,它有两个子类,java.lang.Error和java.lang.Exception,平常所说的异常就是指java.lang.Exception。而在Throwable体系中,Error指的是严重的、无法通过处理的错误,只能事先避免,而Exception表示异常,异常产生后程序员可通过修Bug使得程序继续运行,这是必须要处理的。

  • public void printStackTrace()打印异常信息,包含异常的类型、异常的原因以及异常出现的位置。
  • public String getMessage()获取发生异常的原因。
  • public String toString()获取异常的类型和描述信息。
public class Demo {
    public static void main(String[] args) {
        try {
            System.out.println(new int[]{1, 2, 3, 4, 5}[5]);
        } catch (ArrayIndexOutOfBoundsException e) {
            e.printStackTrace();
            System.out.println(e.getMessage());
            System.out.println(e.toString());
        }
    }
}
异常分类

异常可分为编译时期异常和运行时期异常。编译时期异常checked在编译时期就会检查异常,如果没有处理异常则编译失败,如日期格式化异常;而运行时期异常runtime异常则在运行时期检查异常,在编译时期运行异常是不会报错的,这类异常是可以处理的,但不一定处理,一般不处理。

异常处理
throw

throw关键字可以在指定的方法中抛出指定的异常。使用的格式为throw new xxxException(“异常产生的原因”);注意,throw关键字必须写在方法的内部;throw关键字后边new的对象必须是Exception或者Exception的子类对象;throw关键字抛出指定的异常对象,我们就必须得处理这个异常对象,throw关键字后边创建的如果是RuntimeException或者是RuntimeException的子类对象,我们可以不处理,默认交给JVM处理,即打印异常对象,中断程序;throw关键字后边创建的如果是编译异常,写代码时报错,我们就必须得处理这个异常,要么throws,要么try•••catch。在调用方法时,我们首先必须对方法传递过来的参数进行合法性校验,如果参数不合法,那么我们就必须使用抛出异常的方式告知方法的调用者,传递的参数有问题,NullPointerException、ArrayIndexOutOfBoundsException都是运行期异常,我们不用处理,可以默认交给JVM处理。Objects中的静态方法requireNonNull可以进行非空判断。

public class Demo {
    public static void main(String[] args) {
        int[] arr1 = {1, 2, 3, 4, 5};
        
        method(arr1, 5);
    }

    private static void method(int[] arr, int index) {
        if (index > arr.length - 1) {
            throw new ArrayIndexOutOfBoundsException("数组索引越界异常");
        } else {
            System.out.println(arr[index]);
        }
    }
}
throws

throws关键字是异常处理的一种方式,就是交给别人处理。当方法内部抛出异常对象的时候,那么我们就必须得处理这个异常对象,可以使用throws关键字来处理异常对象,会把异常对象声明抛出给方法的调用者处理,自己不处理,给别人处理,最终交给JVM中断处理。注意,throws关键字必须写在方法声明处;throws关键字后边声明的异常必须是Exception或者是Exception的子类;方法的内部如果抛出了多个异常,那么throws后边也必须声明多个异常,如果抛出的多个异常对象有子父类关系,那么直接声明父类异常即可;调用一个声明抛出异常的方法,我们就必须得处理声明的异常,要么继续使用throws声明抛出,交给方法的调用者处理,最终交给JVM,要么try•••catch自己处理异常。FileNotFoundException是编译异常,抛出了编译异常就必须处理这个异常,可以使用throws继续声明抛出FileNotFoundException这个异常对象,让方法的调用者处理。

import java.text.ParseException;
import java.text.SimpleDateFormat;

public class Demo {
    public static void main(String[] args) throws ParseException {
        new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").parse("2021-4-14 20:20:20");
    }
}
try···catch

捕获异常try•••catch也是处理异常的一种方式,即自己处理异常,格式为try {···} catch(异常类名 变量名) {···} finally {···}。在try中存放的是可能会出现异常的代码;而catch需定义一个异常的变量,用来接收try中抛出的异常对象,一般try中抛出什么异常对象,catch中就定义一个什么异常用来接收这个异常对象,catch中存放的是出现异常后执行的代码,也叫异常的处理逻辑,异常对象创建后如何处理异常对象,一般在工作中都会把异常的信息记录到一个日志中;finally中存放的是无论是否出现异常都会执行的代码。

try中可能会抛出多个异常对象,那么就可以使用多个catch来处理这些异常对象。如果try中产生了异常,那么就会执行catch中的异常处理逻辑,执行完毕catch中的处理逻辑,继续执行try•••catch之后的代码。如果try中没有产生异常,那么就不会执行catch中异常的处理逻辑,执行完try中的代码就继续执行try•••catch之后的代码。finally代码块不能单独使用,必须和try一起使用;finally一般用于资源释放,资源回收,无论程序是否出现异常,最后都要释放IO。

多个异常分别处理,即多个try•••catch;多个异常一次捕获,多次处理,即一个try中有多个异常,多个catch,如果catch里边定义的异常变量有父子类关系,那么子类的异常变量必须写在上面,否则就会报错;多个异常一次捕获一次处理,即catch中的异常类为Exception。

运行时异常被抛出可以不处理,即不捕获也不声明抛出,默认让JVM进行中断处理;如果finally有return语句,永远返回finally中的结果,要避免该情况;如果父类抛出多个异常,子类重写父类方法时只能抛出和父类相同的异常或者是不抛出异常;如果父类方法没有抛出异常,子类重写父类方法时也不可抛出异常,此时子类产生该异常,就只能捕获,不能声明抛出,即父类异常是什么样,子类异常就是什么样。

import java.util.Random;

public class Demo {
    public static void main(String[] args) {
        int a = new Random().nextInt(10) + 1;
        int b = new Random().nextInt(2);

        try {
            System.out.println(a / b);
        } catch (ArithmeticException e) {
            System.out.println(e + "算数运算异常");
        } finally {
            System.out.println("程序继续执行");
        }
    }
}
自定义异常

java提供的异常类不够我们使用,需要自己定义一些异常类。格式为public class XXXException extends Exception/RuntimeException {···}方法体中需添加一个空参构造方法和一个带异常信息的构造方法。注意,自定义异常类一般都是以Exception结尾,说明该类是一个异常类。自定义异常类必须得继承Exception或者RuntimeException,如果继承的是Exception,那么自定义的异常类就是一个编译期异常,如果方法内部抛出了编译器期异常,就必须得处理这个异常,要么throws,要么try•••catch;如果继承的是RuntimeException,那么自定义的异常类就是一个运行期异常,可以无需处理,交给JVM中断处理。

import java.util.Random;

class CustomException extends ArrayIndexOutOfBoundsException {
    public CustomException() {
    }

    public CustomException(String message) {
        super(message);
    }
}

public class Demo {
    public static void main(String[] args) {
        String str = null;
        
        if (new Random().nextInt(2) == 0) {
            throw new CustomException("自定义异常");
        } else {
            System.out.println("随机数不为0,不抛出异常");
        }
    }
}

多线程

并发、并行

并发就是指两个或多个事件在同一时间段内发生,交替执行;并行是指两个或多个事件在同一时期发生,即同时发生,也就是同时执行。

进程、线程

进程是指一个内存中运行的运用程序,每个进程都有一个独立的内存空间,一个应用程序可以同时运行多个进程,进程也是程序的一次执行过程,是系统运行程序的基本单位,系统运行一个程序就是一个进程从创建、运行到消亡的过程。线程是进程中的一个执行单元,负责当前进程中程序的执行,负责当前进程中程序的执行。一个进程中至少有一个线程,一个进程中是可以有多个线程的,这样的应用程序也可以称之为多线程程序。简而言之,一个程序运行后至少有一个进程,一个进程中可以包含多个线程。使用多线程可以提高效率,且多个线程之间互不影响,每创建一个线程,Thread中的start()方法就会开辟一块新的栈空间。

线程调度

线程调度可分为分时调度和抢占式调度。分时调度是指所有线程轮流使用CPU,平均分配每个线程占用CPU的时间;抢占式调度是指优先让优先级高的线程使用CPU,如果线程的优先级相同,那么会随机选择一个(线程随机性),java使用的就是抢占式调度。

主线程是指执行主方法(main)的线程,单线程程序在java中只有一个线程,执行从main方法开始,从上到下依次执行。JVM执行main方法,main方法会进入到栈空间,JVM会找操作系统开辟一条main方法通向CPU的执行路线,CPU就可以通过这个路径来执行main方法,而这个路径的名字就叫做mian(主)线程。对于CPU而言,在多线程程序中就有多条执行路径,CPU就有了选择的权力,即多个新线程和一个主线程一起抢夺cpu的执行权(执行时间),谁抢到了谁执行对应的代码。

创建多线程
Thread

java.lang.Thread类是描述线程的类,我们想要实现多线程程序就必须要继承Thread类,实现步骤为先创建一个Thread类的子类并重写Thread类中的run()方法来设置线程任务(开启线程用来做什么),再创建Thread类的子类对象来调用Thread类中的start()方法来开启新线程。JVM调试该线程的run()方法,结果是当前线程(main)和新线程并发地运行。多次启动一个线程是非法的,特别是当线程已经结束后,不能再重复启动。

  • public static native Thread currentThread()获取当前正在执行的线程对象的引用。
  • public final String getName()获取当前线程的名字。
  • public final synchronized void setName(String name)设置线程名称。也可创建带参构造方法,参数传递线程名称,调用父类的带参构造方法,将线程名称传递给父类,让父类给子线程起一个名字。
  • public synchronized void start()开启线程。
  • public static native void sleep(long millis) throws InterruptedException使当前正在执行的线程以指定的毫秒值暂停执行,毫秒值结束后继续执行。
class SubThread extends Thread {
    @Override
    public void run() {
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() + "线程执行了");
    }
}

public class Demo {
    public static void main(String[] args) throws InterruptedException {
        Thread thread2 = new Thread(new SubThread());
        thread2.setName("通过自建Thread子对象创建的");
        thread2.start();

        Thread.sleep(2000);
        System.out.println(Thread.currentThread().getName() + "线程执行了");
    }
}
Runnable

java.lang.Runnable接口应该由那些打算通过某一线程执行其实例的类来实现。实现步骤为先创建一个Runnable接口的实现类并重写Runnable接口中的run()方法来设置线程任务,再创建一个Runnable接口的实现类对象和一个Thread类对象,构造方法中传递Runnable接口的实现类对象并调用Thread类中的start()方法来开启新线程。实现Runnable接口创建多线程程序的好处,避免了单继承的局限性(一个类只能继承一个类,类继承了Thread类就不能继承其它类了);增强了程序的扩展性,降低了程序的耦合性(实现Runnable接口的方式把设置线程任务和开启线程进行了分离,解耦,即实现类中重写run()方法用来设置线程任务,创建Thread类对象并调用start()方法用来开启新线程)。

class RunnableImpl implements Runnable {
    @Override
    public void run() {
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() + "线程执行了");
    }
}

public class Demo {
    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(new RunnableImpl());
        thread.setName("通过实现Runnable接口创建的");
        thread.start();

        Thread.sleep(2000);
        System.out.println(Thread.currentThread().getName() + "线程执行了");
    }
}
简化代码

使用匿名内部类的方式实现线程的创建,简化代码。使用子类对象的方式创建就是将子类继承父类、重写父类方法、创建子类对象合成一步完成,而使用实现Runnable的方式创建则是将实现类实现类接口、重写接口方法、创建实现类对象一步完成。匿名内部类的最终产物,子类/实现类对象,但没有这个类的名字,格式为new 父类/接口() {···};

public class Demo {
    public static void main(String[] args) {
        new Thread() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName() + "线程执行了");
            }
        }.start();

        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName() + "线程执行了");
            }
        }).start();

        System.out.println(Thread.currentThread().getName() + "线程执行了");
    }
}
线程安全

单线程程序是不会出现线程安全问题的,多线程程序如果没有访问共享数据也是不会产生问题的,当多线程程序访问了共享的数据就会产生线程安全的问题。线程安全问题是不能产生的,我们可以让一个线程在访问共享数据的时候无论是否失去了CPU的执行权力,都让其他线程等待,等待到当前线程执行完毕,其他线程才能继续执行。

同步代码块

可以使用同步代码块来解决线程安全,格式为synchronized(锁对象) {可能出现线程安全问题的代码,即访问了共享数据的代码},通过代码中的锁对象可以使用任意的对象,但是必须保证多个线程使用的锁对象是同一个,锁对象的作用就是将同步代码块锁住,只让一个线程在同步代码块中执行。同步保证了只能有一个线程在同步代码块中执行共享数据,保证了安全,但程序频繁的判断锁、获取锁以及释放锁,程序的效率会降低。想提高安全问题出现的概率可以让程序睡眠。

同步技术的原理,使用了一个锁对象这个锁对象叫同步锁、对象锁,也叫对象监视器。即多个线程一起抢夺CPU的执行权,谁抢到了谁就执行run()方法,遇到synchronized代码块,这时此线程就会检查synchronized代码块是否有锁对象,发现有就会获取到锁对象,进入到同步中执行;这时另一个线程抢到了CPU的执行权,执行run()方法,遇到synchronized代码块,这时当前线程会检查代码块中是否有锁对象,发现没有,则当前线程就会进入到阻塞状态,会一直等待在同步代码块中执行的线程归还锁对象,直到其执行完毕,就会把锁对象归还给同步代码块,这时当前线程才能获取到锁对象从而进入到同步中执行。即同步中的线程没有执行完毕不会释放锁,同步外的线程没有锁进不去同步。

class RunnableImpl implements Runnable {
    private int ticket = 99;
    final Object obj = new Object();

    @Override
    public void run() {
        while (true) {
            synchronized (obj) {
                if (ticket > 9) {
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName() + "卖出去了倒数第" + ticket + "张票");
                    ticket--;
                } else {
                    System.out.println(Thread.currentThread().getName() + "真的没票了呢,不信你看");
                    break;
                }
            }
        }
    }
}

public class Demo {
    public static void main(String[] args) {
        RunnableImpl run = new RunnableImpl(); 
        new Thread(run, "窗口1").start();
        new Thread(run, "窗口2").start();
        new Thread(run, "窗口3").start();
    }
}
同步方法

可以使用同步方法来解决线程安全,使用步骤为将访问共享数据的代码抽取出来放到一个方法中,并在方法中的访问权限修饰符和返回值类型间加上synchronized修饰符即可。格式为修饰符 synchronized 返回值类型 方法名(参数列表) {可能会出现线程安全问题的代码,即访问了共享数据的代码}。定义一个同步方法,同步方法会把方法内部的代码锁住,只让一个线程执行,同步方法的锁对象是谁?就是实现类对象new RunnableImpl(),也就是this,静态的同步方法锁对象是谁?不能是this,this是创建对象之后产生的,静态方法优先于对象,静态方法的锁对象是本类的class属性,即class文件对象,反射机制,RunnableImpl.class。

class RunnableImpl implements Runnable {
    private int ticket = 99;

    @Override
    public void run() {
        while (true) {
            if (ticket > 9) {
                method();
            } else {
                System.out.println(Thread.currentThread().getName() + ":真的没票了呢,不信你看");
                break;
            }
        }
    }

    private synchronized void method() {
        if (ticket > 9) {

            System.out.println(Thread.currentThread().getName() + "卖出去了倒数第" + ticket + "张票");
            ticket--;
            try {
                Thread.sleep(200);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

public class Demo {
    public static void main(String[] args) {
        RunnableImpl run = new RunnableImpl();
        new Thread(run, "窗口1").start();
        new Thread(run, "窗口2").start();
        new Thread(run, "窗口3").start();
    }
}
Lock锁

java.util.concurrent.locks.Lock接口的实现提供了比使用synchronized方法和语句可获得更广泛的锁定操作。Lock接口中的方法void lock()用来获取锁,void unlock()用来释放锁。java.util.concurrent.locks.ReentrantLock实现了Lock接口,使用步骤为先在成员位置创建一个ReentrantLock对象,在可能会出现线程安全问题的代码前调用Lock接口中的Lock()方法获取锁,在可能会出现安全问题的代码后调用Lock接口中unLock()方法释放锁。

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

class RunnableImpl implements Runnable {
    private int ticket = 19;
    Lock l = new ReentrantLock();
    @Override
    public void run() {
        l.lock();
        while (true) {
            if (ticket > 9) {
                System.out.println(Thread.currentThread().getName() + "卖出去了倒数第" + ticket + "张票");
                ticket--;
                try {
                    Thread.sleep(200);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            } else {
                System.out.println(Thread.currentThread().getName() + ":真的没票了呢,不信你看");
                break;
            }
        }
        l.unlock();
    }
}

public class Demo {
    public static void main(String[] args) {
        RunnableImpl run = new RunnableImpl();
        new Thread(run, "窗口1").start();
        new Thread(run, "窗口2").start();
        new Thread(run, "窗口3").start();
    }
}
线程状态

当线程创建并启动后,它既不是一启动就进入了执行状态,也不是一直处于执行状态。在线程的生命周期中,有几种状态呢?在API中java.lang.Thread.Seate这个枚举中给出了六种线程状态。阻塞状态是指具有CPU的执行资格,等待CPU空闲时执行;而休眠状态则是放弃CPU的执行资格,尽管CPU空闲,也不执行。

  • NEW,新建状态,至今尚未启动的线程处于这种状态。
  • RUNNABLE,运行状态,正在java虚拟机中执行的线程处于这种状态。
  • BLOCKED,阻塞状态,受阻塞并等待某个监视器锁的线程处于这种状态。
  • WAITING,无限等待状态,无期限地等待另一个线程来执行某一特定操作的线程处于这种状态。
  • TIMED_WAITING,计时等待状态,等待另一个线程来执行取决于指定等待时间的操作的线程处于这种状态。
  • TERMINATED,死亡状态,已退出的线程处于这种状态。

Object类中等待唤醒的方法

  • public final void wait() throws InterruptedException在其它线程调用此处的notify()或notifyAll()方法前,导致当前线程等待。
  • public final native void notify()唤醒在此对象监视器上等待的单个线程。
  • public final native void notifyAll()唤醒在此对象监视器上等待的所有线程。

进入到TIMEWAITING计时等待状态的两种方式

  • 使用sleep()方法,等待完指定毫秒值后,线程睡醒进入到Runnable/Blocked状态。
  • 使用wait()方法,wait()方法如果等待完指定毫秒值后还没被notify()方法唤醒,就会自动醒来,线程进入到Runnable/Blocked状态。

等待唤醒案例,完成线程间的通信。创建消费者线程,告知生产者需求,调用wait()方法,放弃CPU的执行,进入到WAITING无限等待状态;创建生产者线程,花费指定时间来完成需求,调用notify()方法,唤醒消费者。消费者和生产者线程必须使用同步代码块包裹起来,保证等待和唤醒只能有一个在执行,同步使用的锁对象必须保证唯一,只有锁对象才能调用wait()和notify()方法。Object类中的wait()方法在其他线程调用此处的notify()或notifyAll()方法前,会使当前线程一直处于无限等待状态;notify()方法唤醒在此对象监视器上等待的单个线程后会继续执行wait()方法后的代码。

public class Demo {
    public static void main(String[] args) {
        final Object o = new Object();

        new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized (o) {
                    System.out.println("消费者:我要吃饭");
                    try {
                        o.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println("消费者:真不错呢");
                }
            }
        }).start();

        new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized (o) {
                    try {
                        Thread.sleep(5000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println("生产者:饭已做好");
                    o.notify();
                }
            }
        }).start();
    }
}
线程池

线程池是JDK5之后提供的,线程池就是一个容纳多个线程的容器,池中的线程可以反复使用,省去了频繁创建线程对象的操作,无需反复创建线程而消耗过多的资源。合理利用线程池能够降低资源消耗、提高响应速度以及提高线程的可管理性。

java.util.concurrent.Executors是线程池的工厂类,用来生成线程池。

  • public static ExecutorService newFixedThreadPool(int nThreads)用来创建一个可重用固定线程数量的线程池,参数为创建线程池中包含线程的数量,返回一个ExecutorService接口的实现类对象。可使用ExecutorService接口接口,面向接口编程。

java.util.concurrent.ExecutorService是线程池接口,用来从线程池中获取线程。

  • Future<?> submit(Runnable task)提交一个Runnable任务用于执行。
  • void shutdown()关闭/销毁线程池。
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

class RunnableImpl implements Runnable {
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName() + "线程执行了");
    }
}

public class Demo {
    public static void main(String[] args) {
        ExecutorService es = Executors.newFixedThreadPool(2);
        
        es.submit(new RunnableImpl());
        es.submit(new RunnableImpl());
        es.submit(new RunnableImpl());
        
        es.shutdown();
    }
}

Lambda

面向对象的思想,做一件事情,找个一个能解决这个事情的对象,调用对象的方法来完成。
函数式编程思想,只要能获取到结果,谁去做,怎么做都不重要,重视的是结果,不重视过程。

标准格式

Lambda的标准格式由三部分组成,一些参数、一个箭头、一段代码。格式为(参数列表) -> {一些重写方法的代码},其中()代表接口中抽象方法的参数列表,没有参数就空着什么都不写,有参数就写出参数列表,多个参数间使用逗号分隔;->代表传递的意思,将参数传递给方法体{};{}代表重写接口的抽象方法的方法体。

使用前提

Lambda的使用前提,使用Lambda必须具有接口,且要求接口中有且只有一个抽象方法;使用Lambda必须具有上下文推断;有且只有一个抽象方法的接口称为"函数式接口"。

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class Demo {
    public static void main(String[] args) {
        ExecutorService es = Executors.newFixedThreadPool(2);

        es.submit(new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName() + "线程执行了");
            }
        });

        es.submit(() -> System.out.println(Thread.currentThread().getName() + "线程执行了"));

        es.shutdown();
    }
}

递归

递归是指在当前方法内调用自己的这种现象,递归分为两种,直接递归和间接递归。直接递归称为方法自身调用自己;间接递归可以A方法调用B方法,B方法调用C方法,C方法调用A方法。递归一定要有条件限定,来保证递归能够停止下来,否则会发生栈内存溢出,即A方法会在栈内存中一直调用A方法,就会导致内存中有无数个A方法,方法太多而超出栈内存的大小就会导致内存溢出的错误。当一个方法调用其它方法的时候,被调用的方法没有执行完毕,当前方法会一直等待被调用的方法执行完毕才会继续执行;在递归中虽然有限定条件,但递归的次数不能太多,否则也会发生栈内存溢出;再有就是在构造方法中禁止使用递归。递归的使用前提,当调用方法的时候,方法的主体不变,每次调用方法的参数不同就可以使用递归。使用递归的必知条件,递归结束的条件以及递归的目的。

import java.util.Random;

public class Demo {
    public static void main(String[] args) {
        int i = new Random().nextInt(9) + 1;
        System.out.println("1到" + i + "的和为:" + sum(i));
    }

    private static int sum(int i) {
        if (i > 0) {
            return sum(i - 1) + i;
        } else {
            return i;
        }
    }
}

IO

我们可以把数据的传输称为是一种数据的流动。按照流动的方向并以内存为基准,分为Input输入和Output输出,即硬盘流向内存为输入,内存流向硬盘为输出。

根据数据的流向可分为输入流和输出流,输入流就是把数据从其他设备读取到内存中的流;输出流就是把数据从内存中写到其它设备上的流。根据数据的类型可分为字节流和字符流,字节流就是以字节为单位读取数据的流;字符流就是以字符为单位读取数据的流。

Stream输入流输出流
字节流字节输入流InputStream字节输出流OutputStream
字符流字符输入流Reader字符输出流Writer

一切文件数据在存储时都是以二进制数字的形式保存,所以字节流可以传输任意文件的数据。在操作流的时候,无论使用什么样的流对象,底层传输的始终为二进制数据。

OutputStream、FileOutputStream

java.io.OutputStream字节输出流,此抽象类是表示所有字节输出流的超类。

  • public abstract void write(int b)将单个十进制字节写入文件。
  • public void write(byte b[])将整个字节数组中的内容写入文件。
  • public void write(byte b[], int off, int len)将数组中off索引开始的len个字节内容写入文件。
  • public void flush()刷新此字节输出流。
  • public void close()关闭并释放此字节输出流。

java.io.FileOutputStream文件字节输出流继承自OutputStream,用于将内存中的数据写入到硬盘的文件中。

构造方法

  • public FileOutputStream(String name)创建FileOutputStream对象,参数传递文件路径。
  • public FileOutputStream(String name, boolean append)创建FileOutputStream对象,参数传递文件路径和append属性,为true则不覆盖源文件继续追加内容,默认或false则覆盖源文件并追加新内容。
  • public FileOutputStream(File file)创建FileOutputStream对象,参数传递File对象。
  • public FileOutputStream(File file, boolean append)创建FileOutputStream对象,参数传递File对象和append属性,为true则不覆盖源文件继续追加内容,默认或为false则覆盖源文件并重新添加新内容。

写入数据的原理,内存->硬盘,java程序->JVM->OS操作系统调用写数据的方法将数据写入到文件。在使用Write()方法存入数据时,会先将存入的十进制数转为二进制整数,硬盘中存储的数据都是字节,任何文本编辑器打开文件时都会查询编码表。

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;

public class Demo {
    public static void main(String[] args) throws IOException {
        OutputStream os = new FileOutputStream("D:\\Demo.java");

        os.write(97);
        byte[] bytes = {98, 99, 100};
        os.write(bytes);

        File file = new File("D:\\Demo.java");
        FileOutputStream fos = new FileOutputStream(file, true);
        fos.write(bytes, 1, 1);
        fos.write("ba".getBytes());

        os.flush();
        os.close();
        fos.close();
    }
}
InputStream、FileInputStream

java.io.InputStream字节输入流,此抽象类是表示所有字节输入流的超类。

  • public abstract int read()读取文件中的单个字节。
  • public int read(byte b[])读取文件中一定数量的字节。
  • public void close()关闭此输入流并释放此字节输入流。

java.io.FileInputStream文件字节输入流继承自InputStream,用于将硬盘文件中的数据读取到内存中使用。

构造方法

  • public FileInputStream(String name)创建FileInputStrem对象,参数传递文件路径。
  • public FileInputStream(File file)创建FileInputStream对象,参数传递File对象。

读取数据的原理,硬盘->内存,java程序->JVM->OS操作系统调用读取数据的方法来读取文件。使用参数传递数组的read()方法可以一次读取一定数量的字节,并将其存储在缓冲区数组b中,方法中byte[]数组起到了缓冲的作用,用来存储读取到的多个字节,数组的长度一般为1024的整数倍,方法的返回值int是每次读取的有效字节的个数。

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;

public class Demo {
    public static void main(String[] args) throws IOException {
        InputStream is = new FileInputStream("D:\\Demo.java");
        System.out.println((char) is.read());

        File file = new File("D:\\Demo.java");
        FileInputStream fis = new FileInputStream(file);

        byte[] bytes = new byte[1024];
        int len;

        while ((len = fis.read()) != -1) {
            System.out.print((char) len);
        }
        while ((len = fis.read(bytes)) != -1) {
            System.out.print(new String(bytes, 0, len));
        }
        is.close();
        fis.close();
    }
}

当使用字节流读取文本文件时,如果遇到中文字符,可能会出现乱码,因为一个中文字符可能占用多个字节进行存储,所有java提供了一些字符流,以字符为单位读写输入,专门用来处理文本文件。

Reader、FileReader

java.io.Reader字符输入流,此抽象类是表示所有字符输入流的超类。

  • public int read()读取文件中的单个字符。
  • public int read(char cbuf[])读取文件中一定数量的字符。
  • public abstract void close()关闭此流并释放此字符输入流。

java.io.FileReader文件字符输入流继承自Reader,用于将硬盘中的数据以字符的方式读取到内存中。

构造方法

  • public FileReader(String fileName)创建FileReader对象,参数传递文件路径。
  • public FileReader(File file)创建FileReader对象,参数传递File对象。
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.io.Reader;

public class Demo {
    public static void main(String[] args) throws IOException {
        Reader r = new FileReader("D:\\Demo.java");
        int len;
        while ((len = r.read()) != -1) {
            System.out.print((char) len);
        }
        System.out.println();

        File file = new File("D:\\Demo.java");
        FileReader fr = new FileReader(file);
        char[] chars = new char[1024];
        while ((len = fr.read(chars)) != -1) {
            System.out.print(new String(chars, 0, len));
        }
        r.close();
        fr.close();
    }
}
Writer、FileWriter

java.io.Writer字符输出流,此抽象类是表示所有字符输出流的超类。

  • public void write(int c)将当个十进制字符写入文件。
  • public void write(char cbuf[])将字符数组中的内容写入文件。
  • public abstract void write(char cbuf[], int off, int len)将数组中off索引开始的len个字符内容写入文件。
  • public void write(String str)将字符串内容写入文件。
  • public void write(String str, int off, int len)将字符串中off索引开始的len个字符内容写入文件。
  • public abstract void flush()刷新该流的缓冲。流对象可以继续使用
  • public abstract void close()关闭此流,是先刷新缓冲区,再通知系统和释放资源,流不可继续使用。

java.io.FileWriter文件字符输出流继承自Writer,用于将内存中的字符数据写入到硬盘文件中。

构造方法

  • public FileWriter(String fileName)创建FileWriter对象,参数传递文件路径。
  • public FileWriter(String fileName, boolean append)创建FileWriter对象,参数传递文件路径和append属性,为true则不覆盖源文件继续追加内容,默认或为false则覆盖源文件并重新添加新内容。
  • public FileWriter(File file)创建FileWriter对象,参数传递File对象。
  • public FileWriter(File file, boolean append)创建FileWriter对象,参数传递File对象和append属性,为true则不覆盖源文件继续追加内容,默认或为false则覆盖源文件并重新添加新内容。
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.Writer;

public class Demo {
    public static void main(String[] args) throws IOException {
        Writer w = new FileWriter("D:\\Demo.java");
        w.write(6666);
        w.write(new char[] {'学', '起', '来'});
        w.write(new char[] {'学','起','来'}, 1, 2);

        File file = new File("D:\\Demo.java");
        FileWriter fw = new FileWriter(file, true);
        fw.write("学学起来起");
        fw.write("来学起来", 0, 2);

        w.flush();
        w.close();
        fw.flush();
        fw.close();
    }
}
IOException

JDK7后可以在try的后边增加一个(),在括号中可以定义流对象,那么这个流对象的作用域就只在try中有效,try中的代码执行完毕后会自动把流对象释放,不用写finally。格式为try(定义流对象···) {可能会产生异常的代码} catch(异常类变量 变量名) {异常的处理逻辑}。

JDK9后可以在try的前边定义流对象,try后边的的()中可以直接引入流对象名称(变量名),在try代码执行完毕后流对象也可以释放掉,不用写Finally。格式为A a = new A();try(a···) {可能会产生异常的代码} catch(异常类变量 变量名) {异常的处理逻辑}。

import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;

public class Demo {
    public static void main(String[] args) throws IOException {
        try(FileWriter fw = new FileWriter("D:\\Demo.java")) {
            fw.write("你成长的速度一要赶上父母老去的速度,不要在需要努力的时候选择安逸!");
            fw.flush();
        } catch (IOException e) {
            System.out.println(e);
        }
        FileReader fr = new FileReader("D:\\Demo.java");
        try(fr) {
            char[] chars = new char[1024];
            System.out.println(new String(chars, 0, fr.read(chars)));
        } catch (IOException e) {
            System.out.println(e);
        }
    }
}
Properties

java.util.Properties集合类表示一个持久的数据集,继承自Hashtable<K, V>。Properties可保存在流中或从流中加载。Properties集合是唯一一个和IO流相结合的集合,属性列表中每个键及其对应值都是一个字符串。Properties集合是一个双列集合,key和value默认都是字符串。

构造方法

  • public Properties()创建一个Properties持久属性集对象。

成员方法

  • public void store(OutputStream out, String comments)将集合中的数据持久化写入到硬盘文件中存储,参数是字节输出流和comments注释,不能使用中文,会产生乱码,默认为Unicode编码,一般使用空字符串“”。
  • public void store(Writer writer, String comments)将集合中的数据持久化写入到硬盘文件中存储,参数是字符输出流和comments注释,可以使用中文,默认为Unicode编码,一般使用空字符串“”。
  • public synchronized void load(InputStream inStream)将硬盘中保存的键值对文件读取到集合中使用,参数为字节输入流,不能读取含有中文的键值对。
  • public synchronized void load(Reader reader)将硬盘中保存的键值对文件读取到集合中使用,参数为字符输入流,可以读取含有中文的键值对。
  • public synchronized Object setProperty(String key, String value)往集合中添加键和值。
  • public String getProperty(String key)通过key键获取value值。
  • public Set stringPropertyNames()将集合中的键取出并存入到一个Set集合中。
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.IOException;
import java.util.Properties;
import java.util.Set;

public class Demo {
    public static void main(String[] args) throws IOException {
        // 创建Properties集合并向集合中添加键与值
        Properties prop = new Properties();
        prop.setProperty("一", "壹");
        prop.setProperty("二", "贰");
        prop.setProperty("三", "叁");
        System.out.println(prop);

        // 使用Properties集合中的store()方法将集合中的临时数据持久化写入到硬盘中。匿名自动释放
        prop.store(new FileOutputStream("D:\\Demo.java"), "");
        // 使用Properties集合中的load()方法读取硬盘中保存键值对的文件。
        prop.load(new FileReader("D:\\Demo.java"));
        // 使用Properties集合中的stringPropertyNames()方法将集合中的键取出并存入到一个Set集合中
        Set<String> set = prop.stringPropertyNames();
        // 遍历每一个键,并打印键和值
        for (String key : set) {
            System.out.println(key + " " + prop.getProperty(key));
        }
    }
}
BufferedOutputStream

java.io.BufferedOutputStream字节缓冲输出流继承自OutputStream。

构造方法

  • public BufferedOutputStream(OutputStream out)创建一个新的字节缓冲输出流,参数传递FileOutputStream对象。缓冲流会给FileOutputStream增加一个缓冲区来提高FileOutputStream的写入效率。
  • public BufferedOutputStream(OutputStream out, int size)创建一个新的字节缓冲输出流,参数传递FileOutputStream对象和缓冲区大小。
import java.io.BufferedOutputStream;
import java.io.FileOutputStream;
import java.io.IOException;

public class Demo {
    public static void main(String[] args) throws IOException {
        // 创建BufferedOutputStream对象,构造方法中传递FileOutputStream对象和缓冲区大小
        BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("D:\\Demo.java", true), 1024);
        bos.write("HelloWorld".getBytes());
        bos.close();
    }
}
BufferedInputStream

java.io.BufferedInputStream字节缓冲输入流继承自InputStream。

构造方法

  • public BufferedInputStream(InputStream in)创建一个新的字节缓冲输入流,参数传递FileInputStream对象。缓冲流会给FileInputStream增加一个缓冲区来提高FileInputStream的读取效率。
  • public BufferedInputStream(InputStream in, int size)创建一个新的字节缓冲输入流,参数传递FileInputStream对象和缓冲区大小。
import java.io.BufferedInputStream;
import java.io.FileInputStream;
import java.io.IOException;

public class Demo {
    public static void main(String[] args) throws IOException {
        // 创建BufferedInputStream对象,构造方法中传递FileInputStream对象和缓冲区大小
        BufferedInputStream bis = new BufferedInputStream(new FileInputStream("D:\\Demo.java"), 1024);
        int len; while ((len = bis.read()) != -1) {
            System.out.print((char) len);
        }
        bis.close();
    }
}
BufferedWriter

java.io.BufferedWriter字符缓冲输出流继承自Writer。

构造方法

  • public BufferedWriter(Writer out)创建一个新的字符缓冲输出流,参数传递FileWriter对象。缓冲流会给FileWriter增加一个缓冲区来提高FileWriter的写入效率。
  • public BufferedWriter(Writer out, int sz)创建一个新的字符缓冲输出流,参数传递FileWriter对象和缓冲区大小。

成员方法

  • public void newLine()写入一个行分隔符。
import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;

public class Demo {
    public static void main(String[] args) throws IOException {
        // 创建BufferedWriter对象,构造方法中传递FileWriter对象和缓冲区大小
        BufferedWriter bw = new BufferedWriter(new FileWriter("D:\\Demo.java", true), 1024);
        bw.write("你只管努力,其它的交给天意!");
        bw.newLine();
        bw.close();
    }
}
BufferedReader

java.io.BufferedReader字符缓冲输入流继承自Reader。

构造方法

  • public BufferedReader(Reader in)创建一个新的字符缓冲输入流,参数传递FileReader对象。缓冲流会给FileReader增加一个缓冲区来提高FileReader的读取效率。
  • public BufferedReader(Reader in, int sz)创建一个新的字符缓冲输入流,参数传递FileReader对象和缓冲区大小。

成员方法

  • public String readLine()读取一行数据。
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;

public class Demo {
    public static void main(String[] args) throws IOException {
        // 创建BufferedReader对象,构造方法中传递FileReader对象和缓冲区大小
        BufferedReader br = new BufferedReader(new FileReader("D:\\Demo.java"), 1024);
        int len; while ((len = br.read()) != -1) {
            System.out.print((char) len);
        }

        String line; while ((line = br.readLine()) != null) {
            System.out.println(line);
        }
        br.close();
    }
}
字符编码

计算机中的数据都是用二进制数存储的,而我们肉眼所看见的都是二进制数转换后的结果。按某种规则将字符存储到计算机中的操作称为编码;将存储在计算机中的二进制数按某种规则解析显示出来的操作称为解码。按同种规则存储就必须按同种规则解析,否则会导致乱码现象。

计算机要准确存储任意数据,就需要进行字符编码,一套字符集必然至少有一套字符编码。当指定了编码,它所对应的字符集自然就指定了,所以编码才是我们最终要关心的。

InputStreamReader

java.io.InputStreamReader字符输入转换流继承自Reader。InputStreamReader是字节流向字符流的桥梁,它使用指定的字符集charset读取字节并将其解码为字符,即把看不懂的变成能看懂的。

构造方法

  • public InputStreamReader(InputStream in)创建一个使用默认字符编码(utf-8)的字符输入转换流对象,参数传递FileInputStream对象。
  • public InputStreamReader(InputStream in, String charsetName)创建一个使用指定字符编码的字符输入转换流对象,参数传递FileInputStream对象和指定的字符编码表。
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;

public class Demo {
    public static void main(String[] args) throws IOException {
        // 创建一个使用默认字符集的字符输入转换流对象
        InputStreamReader isr = new InputStreamReader(new FileInputStream("D:\\Demo.java"));
        int len; while ((len = isr.read()) != -1) {
            System.out.print((char) len);
        }
        isr.close();
    }
}
OutputStreamWriter

java.io.OutputStreamWriter字符输出转换流继承自Writer。OutputStreamWriter是字符流向字节的桥梁,它使用指定的字符集charset将要写入流中的字符编码为字节,即把能看懂的变成看不懂的。

构造方法

  • public OutputStreamWriter(OutputStream out)创建一个使用默认字符编码(utf-8)的字符输出转换流对象,参数传递FileOutputStream对象。
  • public OutputStreamWriter(OutputStream out, String charsetName)创建一个使用指定字符编码的字符输出转换流对象,参数传递FileOutputStream对象和指定的字符编码表。
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;

public class Demo {
    public static void main(String[] args) throws IOException {
        // 创建一个使用指定字符集的字符输出转换流对象
        OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("D:\\Demo.java", true), "GBK");
        osw.write("有方向的坚持才能真正的享受学习带来的乐趣!");
        osw.flush();
        osw.close();
    }
}
序列化

java提供了一种对象序列化的机制,用一个字节序列可以表示一个对象,该字节序列包含对象的数据、对象的类型和对象中存储的属性等信息。字节序列写出到文件之后,相当于文件中持久保存了一个对象的信息。反之,该字节序列还可以从文件中读取回来重构对象,对它进行反序列化。对象的数据、类型和对象中存储的数据信息都可以用来在内存中创建对象。

把对象以流的方式写入到文件中保存,叫写对象,也叫对象的序列化,对象中包含的不仅仅是字符,使用字节流;把文件中保存的对象以流的方式读取出来,叫读对象,也叫对象的反序列化,读取文件保存的都是字节,使用字节流。

ObjectOutputStream

java.io.ObjectOutputStream对象的序列化流继承自OutputStream。用于把对象以流的方式写入到文件中保存。

构造方法

  • public ObjectOutputStream(OutputStream out)创建一个序列化流对象,参数传递字节输出流。

成员方法

  • public final void writeObject(Object obj)将指定的对象写入序列化流对象中。
ObjectInputStream

java.io.ObjectInputStream对象的反序列化流继承自InputStream。用于把文件中保存的对象以流的方式读取出来使用。

构造方法

  • public ObjectInputStream(InputStream in)创建一个反序列化流对象,参数传递字节输入流。

成员方法

  • public final Object readObject()从反序列化流中读取对象。

readObject()方法会声明抛出ClassNotFoundException未找到类异常,即class文件找不到异常,当不存在对象的class文件时抛出此异常。反序列化的前提为,类必须实现Serializable接口,且必须存在类对应的class文件。序列化和反序列化时,会抛出NotSerializableException没有序列化异常,类通过实现java.io.Serializable接口以启用其序列化功能,未实现此接口的类将无法使其任何状态序列化的或反序列化。

Serializable接口也叫标记型接口,没有任何内容。要进行序列化和反序列化的类必须实现Serializable接口,用来给类添加一个标记,当我们进行序列化和反序列化的时候,就会检测类上是否有这个标记,如果有就可以进行序列化和反序列化,否则就会抛出NotSerializableException没有序列化异常。

被static修饰的成员变量是不能被序列化的,序列化的都是对象。transient为瞬态关键字,被transient修饰的成员变量也不能被序列化。

import java.io.*;

class Person implements Serializable {
    private String name;
    private int age;

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}

public class Demo {
    public static void main(String[] args) throws IOException, ClassNotFoundException {
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("D:\\Demo.java"));
        oos.writeObject(new Person("二哈", 11));
        oos.writeObject(new Person("小虎", 18));
        oos.close();

        ObjectInputStream ois = new ObjectInputStream(new FileInputStream("D:\\Demo.java"));
        Object o1 = ois.readObject();
        Object o2 = ois.readObject();
        ois.close();

        Person p1 = (Person) o1;
        Person p2 = (Person) o2;
        System.out.println(p1.getName() + p1.getAge());
        System.out.println(p2.getName() + p2.getAge());
    }
}
PrintStream

java.io.PrintStream打印流继承自OutputStream,为其它输出流添加了功能,使他们能够方便地打印各种数据值的表示形式。PrintStream打印流只负责数据的输出,不负责数据的读取;与其它输出流不同,PrintStream永远不会抛出IOException;打印流还有其特有的方法,print、println等。

构造方法

  • public PrintStream(OutputStream out)输出目的地是一个字节输出流。
  • public PrintStream(String fileName)输出目的地是一个文件路径。
  • public PrintStream(File file)输出目的地是一个文件。

如果使用继承自父类的write方法写数据,那么查看数据时会查询编码表;如果使用自己特有的方法print/pritnln写数据,写的数据就是原样输出。可以改变输出语句的目的地,也就是打印流的流向,输出语句默认在控制台输出。使用System.setOut()方法可以改变输出语句的目的地而改为参数中传递的打印流的目的地。

  • public static void setOut(PrintStream out)重新分配标准输出流。
import java.io.IOException;
import java.io.PrintStream;

public class Demo {
    public static void main(String[] args) throws IOException {
        PrintStream ps = new PrintStream("D://Demo.java");
        ps.write("只要学不死,就往死里学!".getBytes());
        System.setOut(ps);
        ps.close();
    }
}

网络编程

网络编程可分为C/S结构和B/S结构,但无论使用哪种结构都离不开网络的支持。网络编程就是在一定的协议下,实现两台计算机的通信的程序。常见协议有TCP传输控制协议和UDP用户数据报协议。网络编程的三要素为协议、IP地址和端口号。

Socket

java.net.Socket类实现客户端套接字。套接字是两台计算机间通信的端点,套接字包含了IP地址和端口号的网络单位。

构造方法

  • public Socket(String host, int port)创建一个流套接字并将其连接到指定主机上的指定端口号。参数传递服务器主机名/IP地址和服务器端口号。

成员方法

  • public OutputStream getOutputStream()获取客户端套接字的输出流。
  • public InputStream getInputStream()获取客户端套接字的输入流。
  • public void shutdownOutput()禁用此套接字的输出流。
  • public synchronized void close()关闭此客户端套接字。

客户端和服务器进行交互,必须使用Socket中提供的网络流,不能使用自己创建的流对象。当我们创建客户端对象Socket时,就会去请求服务器和服务器经过三次握手建立连接通路,这时如果服务器没有先启动的话就会抛出ConnectException连接异常;如果服务器已先启动,那么就可以进行正常的交互了。

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;

public class Client {
    public static void main(String[] args) throws IOException {
        Socket socket = new Socket("127.0.0.1", 8888);
        
        OutputStream os = socket.getOutputStream();
        os.write("客户端:你好,服务器!".getBytes());
        
        InputStream is = socket.getInputStream();
        byte[] bytes = new byte[1024];
        System.out.println(new String(bytes, 0, is.read(bytes)));
        
        socket.close();
    }
}
ServerSocket

java.net.ServerSocket类实现服务器套接字。

构造方法

  • public ServerSocket(int port)创建绑定到特定端口的服务器套接字。参数传递端口号。

服务器必须知道是哪个客户端请求的服务器,所以可以使用其成员方法accept()获取到请求的客户端对象Socket。

  • public Socket accept()侦听并接受此套接字的连接。

服务器不停止运行的解决,上传完文件可以给服务器写一个结束标记,其实就是使用shutdownOutput()方法来禁用此套接字的输出流。程序的优化,可以使用多线程技术提高程序的效率,浏览器在解析服务器回写的html页面时,页面中如果有图片,浏览器就会单独的开启一个线程,读取服务器的图片。

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;

public class Server {
    public static void main(String[] args) throws IOException {
        ServerSocket server = new ServerSocket(8888);
        Socket socket = server.accept();
        
        InputStream is = socket.getInputStream();
        byte[] bytes = new byte[1024];
        System.out.println(new String(bytes, 0, is.read(bytes)));
        
        OutputStream os = socket.getOutputStream();
        os.write("服务器:收到,谢谢啦!".getBytes());
        
        socket.close();
        server.close();
    }
}

函数式接口

有且只有一个抽象方法的接口称为函数式接口,即适用于Lambda表达式使用的接口。注解@FunctionalInterface用来检测接口是否为一个函数式接口,无或者多个都会报错。Lambda延迟执行的使用前提必须为函数式接口必须存在。

Supplier

java.util.function.Supplier接口被称为生产型接口,指定接口的泛型是什么数据类型,那么接口中的get()方法就会产生什么数据类型。

抽象方法

  • T get();获取一个泛型参数指定类型的对象数据。
import java.util.function.Supplier;

public class Demo {
    public static void main(String[] args) {
        Supplier<String> str = () -> "生时何必久睡,";
        System.out.print(str.get());

        System.out.print(method(() -> "死后自会长眠!"));
    }

    public static String method(Supplier<String> s) {
        return s.get();
    }
}
Consumer

java.util.function.Consumer接口是一个消费型接口,与Supplier接口相反,它是用来消费一个数据的,其数据类型由泛型决定。

抽象方法

  • void accept(T t)消费一个指定泛型的数据。

默认方法

  • default Consumer andThen(Consumer<? super T> after)将多个Consumer接口组合到一起,再对数据进行消费。
import java.util.function.Consumer;

public class Demo {
    public static void main(String[] args) {
        String s = "尽管希望渺茫,也要梦在远方";
        Consumer<String> c = System.out::println;
        c.accept(s);

        method(s, System.out::println, System.out::println);
    }

    public static void method(String s, Consumer<String> t1, Consumer<String> t2) {
        t1.andThen(t2).accept(s);
    }
}
Predicate

java.util.function.Predicate接口用于某种数据类型的数据进行判断,结果返回一个boolean值。

抽象方法

  • boolean test(T t)对指定类型数据进行判断。

默认方法

  • default Predicate and(Predicate<? super T> other)表示并且关系,也可以用于连接两个判断条件,其底层源码中也是使用&&进行判断的。
  • default Predicate or(Predicate<? super T> other)表示或者关系,也可以用于连接两个判断条件,其底层源码中也是使用||进行判断的。
  • default Predicate negate()表示取反,其底层源码也是使用!进行判断的。
import java.util.function.Predicate;

public class Demo {
    public static void main(String[] args) {
        String s = "HelloWorld";
        Predicate<String> p1 = t -> t.length() > 5;
        Predicate<String> p2 = t -> t.startsWith("H");

        // p1 && p2
        System.out.println(p1.and(p2).test(s));
        // p1 || p2
        System.out.println(p1.or(p2).test(s));
        // p1
        System.out.println(p1.test(s));
        // !p1
        System.out.println(p1.negate().test(s));
    }
}
Function

java.util.function.Function<T, R>接口用于类型的转换,即根据一个类型的数据得到另一类型的数据。

抽象方法

  • R apply(T t)根据类型T的参数获取类型R的结果。

默认方法

  • default Function<T, V> andThen(Function<? super R, ? extends V> after)进行组合操作。
import java.util.function.Function;

public class Demo {
    public static void main(String[] args) {
        String s = "11";
        Function<String, Integer> f1 = t -> Integer.parseInt(s);
        Function<Integer, String> f2 = t -> t + "";

        System.out.println(f1.apply(s) + 1);
        System.out.println(f1.andThen(f2).apply(s) + 11);
    }
}

Stream

java.util.Stream是JDK8新加入的最常用的流接口。Stream流用于解决了已有集合类库现有的弊端,可以更好的操作数组和集合。Stream流关注的是做什么,而不是怎么做,其流式思想为拼接流式模型,建立一个生产线,按生产线需求来生产商品。java中的Stream并不会存储元素,而是按需计算。

Stream的中间操作结果都会返回流对象本身,串联形成管道,操作可以优化,如延迟执行、内部迭代等。使用流的基本步骤为获取数据源、数据转换、执行操作来获取想要的结果。每次转换,原有的Stream对象是不变的,会返回一个新的Stream对象。

所有Collection单列集合都可以通过默认方法stream来获取流。

  • default Stream stream()获取流对象。
  • public static Stream of(T… values)获取数组对应的流。

流模型方法可以分为延迟方法和终结方法,延迟方法的返回值仍是Stream接口自身类型的方法,因此支持链式调用;终结方法的返回值不再是Stream接口自身类型的方法,因此不再支持链式调用,常用的终结方法包括count()和forEach()方法等。

  • void forEach(Consumer<? super T> action)接收Consumer接口,对流进行消费,forEach()方法用来遍历流中的元素。
  • Stream filter(Predicate<? super T> predicate)接收Predicate接口,对流进行过滤。
  • Stream map(Function<? super T, ? extends R> mapper)接收Function接口,对流进行映射,将T类型数据转为R类型的流。
  • long count()获取流中元素的个数。
  • Stream limit(long maxSize)取用流中前的maxSize个元素。
  • Stream skip(long n)跳过流中的前n个元素。
  • ublic static Stream concat(Stream<? extends T> a, Stream<? extends T> b)将两个流组合在一起合并成一个流。

Stream流属于管道流,只能被消费一次,第一个Stream流调用完毕方法,数据就会流到下一个Stream流上,而这时第一个Stream流已经使用完毕就会关闭了,所以第一个Stream流就不能再调用方法了,否则会抛出IllegalStateException无效状态异常。

import java.util.stream.Stream;

public class Demo {
    public static void main(String[] args) {
        // of()方法获取数组对应的流
        Stream<String> stream1 = Stream.of("坚持", "努力", "方向", "方法", "自律");
        // forEach()方法遍历流中的元素
        stream1.forEach(t -> System.out.print(t + " "));
        System.out.println();

        // filter()方法过滤筛选流元素
        Stream<String> stream2 = Stream.of("坚持", "努力", "方向", "方法", "自律");
        stream2.filter(t -> t.startsWith("方")).forEach(t -> System.out.print(t + " "));
        System.out.println();

        // map()方法将T类型数据转换为R类型的流
        Stream<String> stream3 = Stream.of("1", "2", "3", "4", "5");
        stream3.map(t -> Integer.parseInt(t) + 1).forEach(t -> System.out.print(t + " "));
        System.out.println();

        // count()方法获取流中元素个数
        Stream<String> stream4 = Stream.of("1", "2", "3", "4", "5");
        System.out.println("stream4这个流中有" + stream4.count() + "个元素");

        // limit()方法只取用前N个元素
        Stream<String> stream5 = Stream.of("1", "2", "3", "4", "5");
        stream5.limit(3).forEach(t -> System.out.print(t + " "));
        System.out.println();

        // skip()方法跳过前N个元素
        Stream<String> stream6 = Stream.of("1", "2", "3", "4", "5");
        stream6.skip(2).forEach(t -> System.out.print(t + " "));
        System.out.println();

        // concat()方法组合两个流
        Stream<String> stream7 = Stream.of("坚持", "努力", "方向");
        Stream<String> stream8 = Stream.of("方法", "自律", "效率");
        Stream.concat(stream7, stream8).forEach(t -> System.out.print(t + " "));
    }
}
方法引用

方法引用是对Lambda的优化,代码中如已存在对应的类、对象、this、super就和直接引用。::为引用运算符,它所在的表达式被称为方法引用,如果Lambda要表达的函数方法已经存在某个方法实现中,那么就可以通过::来引用该方法作为Lambda的替代者。Lambda中传递的参数一定是方法引用中的哪个方法可以接收的类型,否则会抛出异常,函数式接口是Lambda的基础,而方法引用是Lambda的孪生兄兄弟。

方法引用的方式有对象名引用成员方法、类名引用静态方法、super引用父类成员方法、this引用本类成员方法、类的构造器引用(类名称::new)和数组构造器引用(int[]::new)。

import java.util.stream.Stream;

public class Demo {
    public static void main(String[] args) {
        Stream<String> stream = Stream.of("坚持", "努力", "方向", "方法", "自律");
        stream.forEach(System.out::println);
    }
}

Junit单元测试

程序测试可分为黑盒测试和白盒测试。黑盒测试不需要写代码,只需给定输入值,看程序的执行结果是否为我们期望的值;白盒测试则需要手写代码,关注的是程序的具体执行流程。而Junit单元测试就是白盒测试的一种。

Junit的使用步骤为先定义一个测试类;再定义测试方法;再给方法加上注解@Test;再导入Junit依赖环境。定义测试包、类、方法的格式如learn.test.XxxTest.testXxx()。测试一般都不会通过打印来得到测试的结果,而是通过断言操作来判定结果。测试方法独立执行的结果为绿色则测试成功,为红色则测试失败。

  • Assert.assertEquals(期望的结果, 运算的结果);判断期望的结果和程序执行的结果是否一样,一样则绿色通过,否则红色报错。

被注解@Before修饰的方法可以在测试类执行前自动执行;被注解@After修饰的方法可以在测试类执行完后自动执行。


反射

反射是框架的灵魂,框架类似于半成品软件,可以在框架的基础上进行软件的开发以简化代码;而反射机制就是将类的各个组成部分封装为其它对象。反射可以在程序的运行过程中操作这些对象,也可以解耦来提高程序的可扩展性。

获取Class对象
  • Class.forName(“全类名”);将字节码文件加载进内存,返回Class对象。多用于配置文件,将类名定义在配置文件中,读取文件、加载类。
  • 类名.class;通过类名的属性class获取并返回Class对象。多用于参数的传递。
  • 对象名.getClass();通过Object类中的getClass()方法获取并返回Class对象。多用于对象的获取字节码的方式。

注意 : 同一个.class字节码文件在一次程序的运行过程中,只会被加载一次,不论通过哪种方式获取得到的Class对象都是同一个。

Class对象获取成员变量
  • Class对象名.getFields();获取public修饰的所有成员变量对象。
  • Class对象名.getField(“成员变量名”);获取public修饰的指定成员变量对象。
  • Class对象名.getDeclaredFields();获取所有成员变量对象,不考虑权限修饰符。
  • Class对象名.getDeclaredField(“成员变量名”);获取指定的成员变量对象,不考虑权限修饰符。
  • 成员变量对象名.set(对象名, 要设置的值);设置成员变量的值。
  • 成员变量对象名.get(对象名);获取成员变量的值。
  • 成员变量对象名.setAccessible(true);暴力反射,忽略访问权限修饰符的安全检查。
Class对象获取构造方法
  • Class对象名.getConstructors();获取public修饰的所有构造方法对象。
  • Class对象名.getConstructor(构造参数的Class对象…);获取public修饰的指定构造方法对象。
  • Class对象名.getDeclaredConstructors();获取所有构造方法对象,不考虑权限修饰符。
  • Class对象名.getDeclaredConstructor(构造参数的Class对象…);获取指定构造方法对象,不考虑权限修饰符。
  • 构造方法对象名.newInstance(对应构造参数…)创建对象。
  • 构造方法对象名.setAccessible(true)暴力反射,忽略访问权限修饰符的安全检查。
Class对象获取成员方法
  • Class对象名.getMethods();获取public修饰的所有成员方法对象及Object类中继承过来的方法对象。
  • Class对象名.getMethod(“方法名”, 方法参数的Class对象…);获取public修饰的指定成员方法对象。
  • Class对象名.getDeclaredMethods();获取所有成员方法对象,不考虑权限修饰符。
  • Class对象名.getDeclaredMethod(“方法名”, 方法参数的Class对象…);获取指定成员方法,不考虑权限修饰符。
  • 成员方法对象名.invoke(对象名, 对应方法参数…);执行方法。
  • 成员方法对象名.getName();获取方法名。
  • 成员方法对象名.setAccessible(true);暴力反射,忽略访问权限修饰符的安全检查。
Class对象获取类名
  • Class对象名.getName();获取类名。

注解

注解是用来说明程序的,是给计算机看的;而注释是用来描述程序的,是给人看的。注解(Annotation)也叫元数据,是一种代码级别的说明,是JDK5后引入的新特性,与类、接口、枚举是在同一层次。注解可以声明在包、类、字段、方法、局部变量、方法参数等的前面,用来对这些元素进行说明。注解可以用来编译检查、生成API文档以及代码分析(使用反射)。

以后大多数时候,我们会使用注解,而不是自定义注解。注解可以给编译器和解析程序使用;注解不是程序的一部分,可以理解为注解就是一个标签。

预定义注解
  • @Override;检测是否为有效的方法重写。
  • @Deprecated;将方法标注为已过时。
  • @SuppressWarnings(“all”);压制警告。
自定义注解

自定义注解的格式为

@元注解
public @interface 注解名 {
    属性列表;
}

注解的本质其实就是一个接口,只不过该接口默认继承Annotation接口。

public interface CustomAnnotation extends java.lang.annotation.Annotation {}
属性

注解中属性(接口中的抽象方法)的返回值可以定义为基本数据类型、String、枚举、注解以及数组。注解中如果定义了属性的话,那么在使用注解的时候需要给属性赋值。

  1. 如果在定义属性时使用default关键字给属性默认初始化值,则在使用注解的时候可以不进行属性的赋值。
  2. 如果只有一个属性需要赋值,并且属性的名称是value,则value可以省略,直接定义即可。
  3. 数组在赋值时,使用{}包裹,如果数组中只有一个值,则{}可以省略。
元注解

元注解是用于描述注解的注解。

  • @Target;描述注解能够作用的位置,ElementType枚举的取值有TYPE(作用于类上)、METHOD(作用于方法上)、FIELD(作用于成员变量上)。
  • @Retention;描述注解被保留的阶段,RetentionPolicy枚举的取值有SOURCE(不会保留到.class字节码文件中)、CLASS(保留到.class字节码文件中,不会被JVM读取到)、RUNTIME(保留到字节码文件中并被JVM读取到)。
  • @Documented;描述注解是否被抽取到API文档中。
  • @Inherited;描述注解是否被子类继承。
解析注解

在程序中解析(使用)注解,获取注解中定义的属性值。步骤为先获取注解定义的位置(Class, Method, Field)的Class对象;再获取定义的注解对象;再通过注解对象调用其属性(抽象方法)来获取属性值。

  • Class对象名.getAnnotation(注解Class对象);获取指定的注解对象,其实就是在内存中生成了一个该注释接口的子类实现对象。
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值