Java

前言

  • Java特点

    • 面向对象(封装,继承,多态)
    • 平台无关性(Java 虚拟机实现平台无关性)
    • 可靠性(具备异常处理和自动内存管理机制)
    • 支持多线程
  • Java工作方式

    • 源代码:demo.java文件
    • 编译器:运行源代码检查错误,将demo.java编译为demo.class文件,由字节码组成
    • 输出:输出demo.class
    • java虚拟机:运行demo.class文件,将字节码转换为平台能理解的形式
  • Java程序结构

    1. 源文件(source file)
      2. 类(class file)
      3. 方法(method): 函数或过程
      4. 语句(statement)

JDK 包含 JRE

  • JDK(Java Development Kit):包含了 JRE,同时还包括了 javac、javadoc、jdb、jconsole、javap 等工具,可以用于 Java 应用程序的开发和调试。
  • JRE(Java Runtime Environment):Java 运行时环境,仅包含 Java 应用程序的运行时环境和必要的类库。主要包括 Java 虚拟机(JVM)、Java 基础类库(Class Library)。
  • JVM:是运行 Java 字节码的虚拟机。JVM 有针对不同系统的特定实现(Windows,Linux,macOS),目的是使用相同的字节码,它们都会给出相同的结果。

基本概念

  • 基本数据类型(8个)

    • 整数型:byte(1字节),short(2字节),int(4字节),long(8字节)
    • 浮点型:float(4字节),double(8字节)
    • 字符类型:char(2字节)
    • 布尔类型:boolean(1字节)
    1
    2
    3
    4
    5
    6
    int x =0
    string[] str = 'hello world!'
    println() //输出加换行
    int len = str.length
    int x = (int) 24.6 //类型转换
    while(flag){}//java中integer与boolean两个类型不相容,故flag必须为boolean类型
  • 包装类型

    • 八种基本类型都有对应的包装类分别为:ByteShortIntegerLongFloatDoubleCharacterBoolean
    • 包装类型可用于泛型,而基本类型不可以。
    • 对于基本数据类型来说,== 比较的是值。对于包装数据类型来说,== 比较的是对象的内存地址。所有整型包装类对象之间值的比较,全部使用 equals() 方法。
    • 成员变量包装类型不赋值就是 null ,而基本类型有默认值且不是 null
  • 自动拆装箱

    • 装箱:将基本类型用它们对应的引用类型包装起来;
    • 拆箱:将包装类型转换为基本数据类型
    1
    2
    Integer i = 10;  // 装箱
    int n = i; // 拆箱
    • 装箱其实就是调用了包装类的valueOf()方法,拆箱其实就是调用了 xxxValue()方法。
    1
    2
    Integer i = Integer.valueOf(10) // 装箱
    int n = i.intValue(); // 拆箱
  • Java数组

    1
    2
    3
    4
    5
    6
    7
    int[] myList;         // 数组变量声明,首选的方法
    int myList[]; // 效果相同,但不是首选方法
    array = new int[arraySize]; // 创建数组
    int[] array = new int[arraySize]; // 数组变量声明 + 创建数组
    int[] array = {0,1,2};
    Arrays.sort(array); // 数组排序
    int[][] a = new int[2][3];// 二维数组创建
  • 在Java中,动态数组是通过ArrayList实现的。可以使用add()方法向ArrayList中添加元素。以下是添加元素的示例代码:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    ArrayList<String> arrayList = new ArrayList<String>();

    // 向数组末尾添加元素
    arrayList.add("元素1");
    arrayList.add("元素2");
    arrayList.add("元素3");

    // 在指定位置添加元素
    arrayList.add(1, "新元素");

    System.out.println(arrayList); // 输出 [元素1, 新元素, 元素2, 元素3]

    arrayList.sort(Comparator.naturalOrder());// 升序排列
    arrayList.sort(Comparator.reverseOrder()); // 降序
    arrayList.size()
  • 输入输出

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    import java.util.Scanner;

    class Input {
    public static void main(String[] args) {
    Scanner input = new Scanner(System.in);
    System.out.print("输入一个整数: ");
    int number = input.nextInt();
    System.out.println("您输入 " + number);

    //获取double输入
    double myDouble = input.nextDouble();

    //获取字符串输入
    String myString = input.next(); //以空格结束
    input.useDelimiter("\n"); // 设置为以换行结束
    String myString = input.nextline(); // 以回车结束

    //关闭scanner对象
    input.close();
    }
    }

JDK 1.5 引进了一种新的循环类型,被称为 For-Each 循环或者加强型循环,它能在不使用下标的情况下遍历数组。

1
2
3
4
for(type element: array)
{
System.out.println(element);
}
  • 可变参数列表:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    // 使用数组语法实现可变参数列表
    public void printArray(Object[] args){
    for(Object obj: args){
    System.out.println(obj + " ");
    }
    }
    // 使用省略号实现可变参数列表
    public void printArray(Object... args){
    for(Object obj: args){
    System.out.println(obj + " ");
    }
    }
  • 枚举类型:多用在swith语句中;

    1
    2
    3
    4
    5
    public enum Color{
    RED,BLUE,WHITE,GREEN
    }

    Color color = Color.RED;
  • java中的全局变量与全局方法:任何变量只要加上public,static和final,基本上都会变成全局变量取用的常数。这是一种近似全局的事物,他们也必须定义在类中。

  • java是通过值传递的,也就是通过拷贝传递;

    • 传递基本类型参数:方法接收到的是参数的拷贝,会创建副本;
    • 传递引用类型参数:传递的就是实参所引用的对象在堆中地址值的拷贝,同样也会创建副本;

面向对象

  • 面向对象和面向过程

    • 面向过程把解决问题的过程拆成一个个方法,通过一个个方法的执行解决问题。
    • 面向对象会先抽象出对象,然后用对象执行方法的方式解决问题。(面向对象开发的程序一般更易维护、易复用、易扩展。
  • 对象具有接口

    • 对象能够接受什么请求是由它的接口(interface)决定的,而接口由对象所归属的类定义;

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      // 类型
      class Light{
      public void on(){ // 接口

      }
      public void off(){ // 接口

      }
      }

      Light lt = new Light(); // 创建对象
      lt.on();// 对象发出请求
  • 对象和对象引用

    • new运算符创建对象,存放在堆内存中;
    • 对象引用指向对象,存放在栈内存中;
    1
    Person person = new Person()\\ person:对象引用
  • 对象相等和引用相等

    • 对象相等:内存中的内容相等;
    • 引用相等:内存的地址相等;
  • 面向对象三大特征

    • 封装

      • 实例变量标记为private
      • 提供公有的getters与setters,标记为public,来控制存取动作
      • public:可以被所有人访问;private:只能被类的创建者通过自身的方法访问;protected:类似于private,但是区别是继承的子类可以访问;
    • 继承

      • 子类拥有父类对象所有的属性和方法(包括私有属性和私有方法),但是父类中的私有属性和方法子类是无法访问,只是拥有
      • 子类可以拥有自己属性和方法,即子类可以对父类进行扩展。
      • 子类可以用自己的方式实现父类的方法。
    • 多态

      • 存在的三个必要条件:继承,重写,父类引用指向子类对象 Parent p = new Child()
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      class Shape {
      void draw() {}
      }

      class Circle extends Shape {
      void draw() {
      System.out.println("Circle.draw()");
      }


      class Square extends Shape {
      void draw() {
      System.out.println("Square.draw()");
      }
      }
  • 参数化类型(泛型)

    • 一个被参数化的类型是一个特殊的类,可以让编译器自动适配特定的类型;

    • 例如,编译器可以将集合定义为只接受放入shape的对象,因此集合中也只能取出shape对象;

      1
      ArrayList<shape> shapes = new ArraytList<>();\\ 创建一个防止shape对象的ArrayList
  • 对象的创建和生命周期

    • 创建一个新对象时,可以通过堆内存来创建,因为堆是在运行时动态管理内存的;
    • java只允许动态分配内存,当创建一个新对象时,都需要通过new操作符创建一个对象的动态实例;
    • java底层支持垃圾收集器机制,它知道一个对象何时不会再用,并自动释放该对象占用的内存;
  • 浅拷贝,深拷贝,引用拷贝

    • 浅拷贝:浅拷贝会在堆上创建一个新的对象(区别于引用拷贝的一点),不过,如果原对象内部的属性是引用类型的话,浅拷贝会直接复制内部对象的引用地址,也就是说拷贝对象和原对象共用同一个内部对象。
    • 深拷贝:深拷贝会完全复制整个对象,包括这个对象所包含的内部对象。
    • 引用拷贝:引用拷贝就是两个不同的引用指向同一个对象。

    浅拷贝、深拷贝、引用拷贝示意图

对象

  • 数据保存的地方

    • 寄存器:数据直接保存在中央处理器(CPU),存储速度最快;
    • 栈:数据存储在RAM里,效率仅次于寄存器;
    • 堆:是一个通用的内存池(也使用RAM),用于存放所有的java对象;
    • 常量:直接保存在程序代码中;
  • static关键字

    • 使用static的字段或方法不依赖于对象;
    • 即便没有为这个类创建对象,也可以调用该类的static方法和static字段;
    1
    2
    3
    4
    5
    6
    7
    8
    class test{
    static int x = 0;
    }

    test t1 = new test();
    test t2 = new test();
    // 即便创建了两个对象,test.x只会占用同一块空间
    // t1.x和t2.x均为0

操作符

  • ==equals () 的区别:

    • 对于基本数据类型来说,== 比较的是值。
    • 对于引用数据类型来说,== 比较的是对象的内存地址。
    • equals() 不能用于判断基本数据类型的变量,只能用来判断两个对象是否相等
    1
    2
    3
    4
    5
    6
    7
    8
    String a = new String("ab"); // a 为一个引用
    String b = new String("ab"); // b为另一个引用,对象的内容一样
    String aa = "ab"; // 放在常量池中
    String bb = "ab"; // 从常量池中查找
    System.out.println(aa == bb);// true
    System.out.println(a == b);// false
    System.out.println(a.equals(b));// true
    System.out.println(42 == 42.0);// true

初始化和请理

  • 构造器进行初始化

    • 构造器的名字就是类的名字;
    • 构造器也可以传入参数来指定如何创建对象;
    1
    2
    3
    4
    5
    6
    7
    8
    class Rock{
    Rock(){ //无参构造器
    System.out.print("Rock")
    }
    Rock(int x){ //构造器
    System.out.print("Rock" + x );
    }
    }
  • 方法重载

    • 允许不同参数类型的方法具有相同的名字;例如可以创建一个无参构造器和有参数传递的构造器;
    • 每个重载方法必须有独一无二的参数列表
  • 重写

    • @override注解,表示该方法是一个重写(Override)父类中的方法,而不是在子类中定义一个新的方法;
    • @Override注解只能应用于重写父类中的方法,而不能应用于接口中的方法或静态方法。
  • 无参构造器

    • 如果你没有创建构造器,系统会自动为这个类创建一个无参构造器;
    • 但如果你已经创建了构造器,即便没有无参构造器,系统都不会再自动创建了;
  • this关键字

    • this 表示对当前对象的引用;
    • 只能在非静态方法中使用;
    1
    2
    3
    4
    5
    6
    7
    8
    9
    class Person{
    private String name;
    private int age;

    public Person(String name,int age){
    this.name = name;
    this.age = age;
    }
    }
  • finalize()方法

    • Java中:
      • 对于一些特殊的内存,垃圾处理器可能不知道怎么释放;
      • 垃圾收集不是析构;
      • 垃圾收集仅与内存有关;
    • finalize()的使用仅限于一种特殊情况,对象以某种方式分配空间,而不是通过创建对象来分配;
  • 垃圾收集器的工作原理

    • java中的堆更像一个传送带,“堆指针”只是简单的移动到尚未分配的区域,而垃圾收集器的介入可以使“堆指针”移到靠近传送带的地方,由此构建了一个高速的,有无限空闲空间的堆模型;
    • 引用计数:每个对象都有一个引用计数器,每次该对象被引用时,引用计数都会增加;离开作用域或设置为null时计数器减小;
    • 停止-复制:该算法将可用内存空间分为两部分,每次只使用其中一部分。当一部分内存用完后,将未被回收的对象复制到另一部分内存中,并清除原来的内存。该算法的优点是简单高效,缺点是需要额外的内存空间。
    • 标记-清除:该算法分为标记和清除两个阶段,首先标记出所有需要回收的对象,然后进行清除。该算法的缺点是会产生内存碎片,容易导致频繁的内存分配和回收。

实现隐藏

  • 控制访问被称为实现隐藏
  • 将数据和方法包装在类中,并与实现隐藏结合,称为封装
  • 类不能是private或protected的;如果想要防止对该类的访问,可以将该类的构造器都设为priate;

复用

  • 组合

    • 组合就是 A类的对象是B类的成员变量。相当于 A类对象是B类对象的一个变量,A类中的所有功能,B类都可以通过A类对象的调用来实现。
    • 组合体现的是整体与部分、拥有的关系,即 has - a 的关系
  • 继承

    • 继承在面向对象语言中必不可少;其实创建一个类时,总是在继承,除非明确指定要继承某个类,否则都会隐式继承java的标准根类Object

    • 使用extends关键字,可以使用基类中的方法,还可以向子类中添加新方法;

    • 创建对象时,构造过程是由基类 “向外” 进行的;即便没有为子类创建构造器,编译器也会自动创建一个可以调用基类的无参构造器;

    • 如果基类没有无参构造器,那么就需要用 Super关键字来显示的调用构造器;

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      class Art{
      Art(int i){
      ...
      }
      }

      class Draw extends Art{
      Draw(int i){
      super(i); // 调用基类构造器
      ...
      }
      }

      public Cartoon extends Draw{
      Cartoon(){
      super(11); // 调用基类构造器
      ...
      }
      public static void main(){}
      }
  • 向上转型

    • 在Java中,子类继承了父类的所有方法和属性,因此子类对象可以被当做父类对象来使用。这种将子类对象转换为父类对象的过程就是向上转型。

    • 向上转型总是安全的,因为是从更具体的类型转换为更通用的类型;

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      class Animal {
      public void eat() {
      System.out.println("Animal is eating.");
      }
      }

      class Dog extends Animal {
      public void bark() {
      System.out.println("Dog is barking.");
      }
      }

      public class Test {
      public static void main(String[] args) {
      Animal animal = new Dog(); // 创建了一个Dog对象,并将其赋值给Animal类型的变量animal,完成向上转型
      animal.eat(); // 调用父类方法 // 动态绑定
      animal.bark(); // 编译错误,父类不能访问子类特有的方法
      }
      }
  • final

    • final,static表示是一个常量;
    • 空白 final:即没有被初始化的final字段;必须在构造器里进行初始化,保证了final字段在使用之前总是被初始化;
    • final参数:只能读取,不能修改参数;
    • final类:该类不能被继承;

多态

  • 多态是指同一个方法或者同一个类在不同的情况下表现出不同的行为。Java中的多态性主要通过方法的重载和覆盖以及向上转型实现。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    class Animal{
    public void eat(){}
    }

    public class cat extends Animal{
    @override public void eat(){}
    }

    public class zoo{
    public void manage(Animal i){ //方法接收一个Animal引用,他怎么知道这个引用在这里指的是cat,而不是dog,pig?
    i.eat();
    }
    public static void main(){
    cat cat1 = new cat();
    manage(cat1); // 向上转型
    }
    }
  • 方法调用绑定

    • 前期绑定:在程序运行之前执行绑定,面向过程语言中默认是前期绑定;在多态中,困惑的点在于,编译器中只有一个基类引用时,怎么确定哪个才是要调用的正确方法;
    • 后期绑定(动态绑定):是指在运行时根据对象的实际类型来调用相应的方法;java中所有方法都是动态绑定,除非方法是static或final的;
  • 构造器和多态

    • 构造器的调用顺序
      • 基类构造器被调用,然后是子类;
      • 按声明的顺序初始化成员变量;
      • 最后执行子类构造器的方法体;
    • 构造器内部的多态方法行为
      • 构造器负责创建对象,如果在构造器内调用动态绑定方法,则可以调用尚未被初始化的成员的方法——这将会导致灾难

抽象类

  • 抽象方法只有一个声明,没有方法体;

    1
    abstract void f();
  • 包含抽象方法的类称为抽象类,抽象类(Abstract Class)是一种不能被实例化的类,它只能作为其他类的父类,用于定义一些抽象方法和非抽象方法;

    1
    2
    3
    4
    5
    6
    absrtact class Basic{
    abstract void f();
    void m();
    }

    Basic b = new Basic(); // 错误的,抽象类不能被实例化
  • 如果要创建一个可实例化的类,继承抽象类后要为所有的抽象方法提供定义

    1
    2
    3
    4
    5
    public class A extends Basic(){
    @override void f(){
    System.out.print("hello!");
    }
    }

接口

  • interface关键字创建了一个完全抽象的类,它不代表任何实现;

    1
    2
    3
    4
    5
    6
    7
    8
    9
    public interface Instrument{
    void f1();
    int f2();
    public static final int N = 4; //接口中的字段必须被声明为 public static final

    default void f3(){
    System.out.print("hello!"); // default关键字允许我们在接口中实现方法
    }
    }
  • 使用implements关键字创建一个符合特定接口的类,表示要提供它的定义;

    1
    2
    3
    4
    5
    6
    7
    8
    public class guitar implements Instrument{
    @override public void f1(){ // 来自接口的方法必须被定义为public
    System.out.print("hello!");
    }
    @override public int f2(){
    return 1;
    }
    }
  • 多重继承

    • Java通过接口来解决多重继承问题。一个类可以实现多个接口,从而具有多个不同的行为。
  • 抽象类与接口

    特性 接口 抽象类
    组合 可在新类中组合多个接口 只能继承一个抽象类
    状态 不能包含字段(静态字段除外) 可以包含字段,且非抽象方法可以引用这些字段
    默认方法&抽象方法 默认方法不需要在子类中实现,它只能引用子接口中的方法 抽象方法必须在子类中实现
    构造器 不能有构造器 可以有构造器
    访问权限 隐式的public 可以为protected或包访问权限
    • 都不能被实例化。
    • 都可以包含抽象方法。
    • 都可以有默认实现的方法。
  • 工厂方法设计模式

    • 产生某个接口的对象时,不是直接调用构造器,而是在工厂对象上直接调用构建方法;

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      43
      44
      45
      46
      47
      48
      // 动物接口
      interface Animal {
      void speak();
      }

      // 工厂接口
      interface AnimalFactory {
      Animal createAnimal();
      }

      // 狗类
      class Dog implements Animal {
      public void speak() {
      System.out.println("汪汪汪!");
      }
      }

      // 猫类
      class Cat implements Animal {
      public void speak() {
      System.out.println("喵喵喵!");
      }
      }

      // 狗工厂类
      class DogFactory implements AnimalFactory {
      @override public Animal createAnimal() {
      return new Dog();
      }
      }

      // 猫工厂类
      class CatFactory implements AnimalFactory {
      @override public Animal createAnimal() {
      return new Cat();
      }
      }

      public class Factories{
      public static void animalAct(AnimalFactory fact){
      Animal a = fact.createAnimal();
      a.speak();
      }
      public static void main(){
      animalAct(new DogFactory());
      animalAct(new CatFactory());
      }
      }
  • 接口的新特性

    • 接口中可以加入private的方法,这些方法只能被接口内的其他方法调用;

      1
      2
      3
      4
      5
      6
      7
      8
      public interface Instrument{
      private void f1(){ // 默认是default的
      System.out.print("hello!");
      }
      default void f2(){
      f1();
      }
      }

内部类

  • 创建内部类

    • 内部类即定义在其他类内的类;
    • 在外部创建非静态内部类的对象的时候,必须将对象的类型指定为 OuterClassName.InnerClassName
  • 为何使用内部类

    • 可以隐藏细节和内部结构,封装性更好;
  • 内部类可以访问外围类的所有成员(当创建一个内部类时,内部类的对象会隐含一个链接指向创建该对象的外围对象)

  • 要使用外部类的名字,可以使用 OuterName.this ;

    1
    2
    3
    4
    5
    6
    7
    8
    public class DotThis{
    void f(){};
    public class Inner(){ // 内部类
    public DotThis outer(){
    return DoThis.this; // 如果使用”this“,引用的会是Inner的”This“
    }
    }
    }

集合

  • 利用泛型指定保存在集合中对象的类型

    1
    2
    import java.util.*;
    ArrayList<Apple> apple = new ArrayList<>(); // 编译时将阻止我们将错误的类型的对象放入某个集合中
  • Java 集合类库(可以表示为库的两个基本接口)

    • Collection:一个由单独元素组成的序列;

      • List :必须按元素插入顺序来保存他们;
        • ArrayList :底层是Object[]数组;
        • LinkedList:底层是双向链表;
      • Set:不能存在重复元素;
        • HashSet:查找速度最快;基于 HashMap 实现的,底层采用 HashMap 来保存元素;
        • TreeSet:按添加顺序升序保存对象;红黑树(自平衡的排序二叉树);
        • LinkedHashSet:既按插入顺序保存对象,也保留了HashMap的查找速度;LinkedHashSetHashSet 的子类,并且其内部是通过 LinkedHashMap 来实现的;
      • Queue:先进先出
        • ArrayQueueObject[] 数组 + 双指针实现;
        • PriorityQueue: Object[] 数组来实现二叉堆;
    • Map :一组键值对对象,使用键(key)来查找值(value);key 是无序的、不可重复的,value 是无序的、可重复的,每个键最多映射到一个值。

      • List使用数值来查找某个对象,而Map使用另一个对象来查找某个对象,它将对象与对象关联在了一起,因此也被称为关联数组 or 字典

      • 操作

        1
        2
        Map.put(key,value);  //添加键值对
        Map.get(key) // 查值
      • 三种风格

        • HashMap:查找顺序最快;

          JDK1.8 之前 HashMap数组+链表组成的,数组是 HashMap 的主体,链表则是主要为了解决哈希冲突而存在的(“拉链法”解决冲突)。

          JDK1.8 以后在解决哈希冲突时有了较大的变化,当链表长度大于阈值(默认为 8)(将链表转换成红黑树前会判断,如果当前数组的长度小于 64,那么会选择先进行数组扩容,而不是转换为红黑树)时,将链表转化为红黑树,以减少搜索时间。

        • TreeMap:按添加元素的升序排列;

        • LinkedHashMap:既按插入顺序保存对象,也保留了HashMap的查找速度;

  • List

    • 分类

      • ArrayList访问性能好,中间插入或删除元素的速度慢;
      • LinkedList访问性能差,但是中间插入或删除元素的成本低;
    • ArrayList常用方法

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      add();  // 添加元素
      addAll(); // 添加所有元素
      contains(); // 确定某个对象是否在列表中
      remove(); // 移除对象
      get(i); // 通过索引获取元素
      indexOf(value); // 获得List中的索引编号
      size(); // 元素数量
      set(i,v); // 替换指定索引的元素
      sort(); //排序
      toString(); // 转换为字符串
      isEmpty(); // 判空
    • 迭代器(Iterator

      • Iterator是一种用于遍历集合类(如ArrayListHashSet等)元素的接口。通过使用Iterator接口,可以在不暴露集合内部结构的情况下,逐个访问集合中的元素。
      • Iterator能够将序列的遍历操作和底层结构分离,即迭代器统一了对集合的访问
      • Iterator接口包含以下常用方法:
        • boolean hasNext():如果迭代器还有下一个元素,则返回true
        • E next():返回迭代器的下一个元素。
        • void remove():从迭代器返回的集合中删除迭代器最后一个元素。
    • LinkedList常用方法

      1
      2
      3
      4
      5
      getFirst()  element()  peek() // 均为返回列表的头部     
      removeFirst() remove() // 移除并返回列表头部
      add() addLast() offer() //在列表尾部插入元素
      addFirst() //列表开头插入元素
      removeLast() // 移除并返回列表的最后一个元素
  • Stack

    • 通过LinkedList提供
    1
    2
    3
    4
    Stack<String> s = new Stack<>();
    s.push("hello!");
    s.pop();
    s.peek(); // 返回栈顶元素
  • Set

    • 不允许有重复元素出现

    • 常用来测试成员身份

      1
      2
      3
      4
      Set<String> set1 = new HashSet<>();
      Collections.addAll(set1,"A B C D E".split(" "));
      set1.contains("D");// true
      set1.containsAll(set2);
  • Map

    • 对象映射到对象

      1
      2
      3
      4
      5
      6
      7
      Map<String,Pet> petMap = new Map<>();
      petMap.put("My cat",new Cat("mm"));
      petMap.put("My dog",new Dog("ww"));
      Pet dog = petMap.get("My dog"); // "ww"
      petMap.getOrDefault(key,"Not Found") // 获取指定 key 对应对 value,如果找不到则返回设置的默认值。
      petMap.containsKey("My cat"); // true
      petMap.containsValue(dog); // true
    • HashMap

      • HashMap 主要用来存放键值对,它基于哈希表的 Map 接口实现,是常用的 Java 集合之一,是非线程安全的

        HashMap 可以存储 null 的 key 和 value,但 null 作为键只能有一个,null 作为值可以有多个;

  • Queue

    • 典型的先进先出(FIFO)的集合。

    • LinkedList实现了Queue接口,提供了支持队列行为的方法,因此可以通过将LinkedList向上转型为Queue实现队列;

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      // 普通队列
      Queue<Integer> q = new LinkedList<>();
      q.offer(); // 队列特有的操作,队列尾部插入元素
      q.peek(); // 返回头部,若队列为空,则返回NULL
      q.element(); // 返回头部,若队列为空,则报错
      q.poll(); // 删除头部元素,若队列为空,则返回NULL
      q.remove(); // 删除头部元素,若队列为空,则报错

      // 双端队列
      Deque<Integer> dq = new LinkedList<>();
      dq.offerLast();
      dq.peekFirst();
      dq.getFirst();
      dq.pollFirst();
      dq.removeFirst();
    • PriorityQueue(优先级队列)

      • 优先级最高的元素先出;

      • 当一个元素被加入队列,会先进行排序再加入;

        1
        PriorityQueue<Integer> priorityQueue = new PriorityQueue<>(); 
    • BlockingQueue (阻塞队列)是一个接口,继承自 Queue

      • BlockingQueue阻塞的原因是其支持当队列没有元素时一直阻塞,直到有元素;还支持如果队列已满,一直等到队列可以放入新元素时再放入。
      • BlockingQueue 常用于生产者-消费者模型中,生产者线程会向队列中添加数据,而消费者线程会从队列中取出数据进行处理。
  • 集合转数组:使用集合转数组的方法,必须使用集合的 toArray(T[] array),传入的是类型完全一致、长度为 0 的空数组。

    1
    2
    3
    4
    String[] s = new String[]{"dog", "lazy", "a", "over", "jumps"};
    List<String> list = Arrays.asList(s);
    // 没有指定类型的话会报错
    String[] ss = list.toArray(new String[0]);
  • 数组转集合

    1
    2
    3
    4
    String[] myArray = {"Apple", "Banana", "Orange"};
    List<String> myList = Arrays.asList(myArray);
    //上面两个语句等价于下面一条语句
    List<String> myList = Arrays.asList("Apple","Banana", "Orange");

函数式编程

  • 通过整合现有代码来产生新功能,而不是从零开始编写所有内容;

    面向对象编程抽象数据,而函数式编程抽象行为

  • lambda 表达式

    • 基本语法:

      • 参数;
      • 后面跟 - >,可以读为产生;
      • -> 后面都是方法体。
      1
      2
      3
      4
      List<String> strings = Arrays.asList("hello", "world");
      List<String> upperCaseStrings = strings.stream()
      .map(str -> str.toUpperCase()) // map()方法接受一个lambda表达式作为参数,该表达式将字符串转换为大写字母。 //由于lambda表达式只有一个参数,因此可以省略参数列表的括号。
      .forEach(System.out::println);
    • 编写递归的lambda表达式;

  • 方法引用

    • 基本语法 :类名or对象名 + :: + 方法名
    1
    2
    List<String> list = Arrays.asList("Hello", "World");
    list.stream().map(String::toUpperCase).forEach(System.out::println);
    • 直接引用已有的方法或构造函数,而不需要像lambda表达式那样创建新的方法体。
    • Runnable
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    class Go {
    static void go() {
    System.out.println("go!");
    }
    }

    public class Main {
    public static void main(String[] args) {
    // Thread 对象接受一个Runnable作为其构造器参数,start()方法会调用该引用的方法(run())
    new Thread(new Runnable() {
    @Override
    public void run() {
    System.out.println("hello");
    }
    }).start();

    new Thread(Go::go).start();
    }
    }

  • “ 集合优化了对象的存储,而(stream)与对象的成批处理有关 ”

  • 声明式编程,直接说明想要完成什么(how),而不是怎么做(what)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    public class Stream {
    public static void main(String[] args){
    new Random(1)
    .ints(2, 10)
    .limit(5)
    .sorted()
    .forEach(System.out::print);
    }
    }

异常

  • 在 Java 中,所有的异常都有一个共同的祖先 java.lang 包中的 Throwable 类。Throwable 类有两个重要的子类:

    • Exception :程序本身可以处理的异常,可以通过 catch 来进行捕获。Exception 又可以分为 Checked Exception (受检查异常,必须处理) 和 Unchecked Exception (不受检查异常,可以不处理)。
    • ErrorError 属于程序无法处理的错误 ,我们没办法通过 catch 来进行捕获不建议通过catch捕获 。这种异常发生时,Java 虚拟机(JVM)一般会选择线程终止。
  • 捕获异常

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    try{
    // 可能会产生异常的代码
    }catch(){
    // 异常处理程序
    }

    try{
    // 可能会产生异常的代码
    }catch(){
    // 异常处理程序
    }catch(){

    }finally{
    // 不管怎么样都会执行
    }

反射

  • 反射可以在程序运行时发现并使用对象的类型的所有信息;反射允许对类的成员变量,成员方法和构造方法进行编程访问;

  • Class 对象

    • 程序中每个类都会有一个Class 对象,它包含了与类相关的信息;

    • 获取Class对象的三种方式:

      • Class.forName("类名")
      • 类名.class
      • 对象.getClass( ) (已经创建了对象后才能使用)
    • 万物皆对象

      • 字节码文件:Class类的对象;
      • 构造方法:Constructor类的对象;
      • 成员变量:Field类的对象;
      • 成员方法:Method类的对象;
    • 获取Class对象的方式

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      // 类名.class
      Class clazz1 = User.class;

      // 对象.getClass()
      Class clazz2 = new User().getClass();

      // Class.forName("全路径")
      Class clazz3 = Class.forName("com.lm.User");

      // 实例化
      User user =(User)clazz1.getDeclaredConstructor().newInstance();
    • 获取构造方法

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      Class clazz = User.class;

      // 获取所有public构造方法
      Constructor[] constructors1 = clazz.getConstructors();

      // 获取所有构造方法(包括private)
      Constructor[] constructors2 = clazz.getDeclaredConstructors();

      // 指定有参数构造创建对象
      //public
      Constructor c1 = clazz.getConstructor(String.class,int.class);
      User user1 = (User)c1.newInstance("ming",21);
      // private
      Constructor c2 = clazz.getDeclaredConstructor(String.class, int.class);
      c2.setAccessible(true); // 设置允许访问
      User user2 = (User)c2.newInstance("ming",21);
    • 获取属性

      1
      2
      3
      4
      5
      6
      Class clazz = User.class;
      // 获取所有public属性
      Field[] fields1 = clazz.getFields();

      // 获取所有属性(包括private)
      Field[] fields2 = clazz.getDeclaredFields();
    • 获取方法

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      User user = new User("ming",21);
      Class clazz = user.getClass();

      // public 方法
      Method[] methods1 = clazz.getMethods();
      for(Method m:methods1){
      if(m.getName().equals("toString")){
      m.invoke(user); // 执行 toString()方法
      }
      }

      // private
      Method[] methods2 = clazz.getDeclaredMethods();
      for(Method m:methods2){
      if(m.getName().equals("run")){
      m.setAccessible(true); // 设置允许访问
      m.invoke(user); // 执行 private run()方法
      }
      }
  • 转型前检查

    • 在java中,可以使用instanceof关键字检查一个对象是否是某个类的实例;
    1
    2
    3
    if(x instanceof Dog){
    ((DOg)x).bark();
    }
  • 动态代理(proxy)

    • 在程序运行期,创建目标对象的代理对象,并对目标对象中的方法进行功能性增强的一种技术;
    • AOP思想:基于动态代理思想,对原来目标对象创建代理对象,在不修改原对象代码情况下,通过代理对象调用增强功能的代码,从而对原有业务方法进行增强。

泛型

  • 让代码只需依赖于 " 某种不具体指定的类型 ",而不是特定的接口或类,那么就可以编写出更为通用的代码;

  • 泛型类:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    //此处T可以随便写为任意标识,常见的如T、E、K、V等形式的参数常用于表示泛型
    //在实例化泛型类时,必须指定T的具体类型
    public class Generic<T>{
    //key这个成员变量的类型为T,T的类型由外部指定
    private T key;

    public Generic(T key) { //泛型构造方法形参key的类型也为T,T的类型由外部指定
    this.key = key;
    }

    public T getKey(){ //泛型方法getKey的返回值类型为T,T的类型由外部指定
    return key;
    }
    }

  • 泛型接口

    1
    2
    3
    4
    //定义一个泛型接口
    public interface Generator<T> {
    public T next();
    }
  • 类型通配符

    1
    2
    3
    4
    5
    6
    public void showKeyValue1(Generic<?> obj){
    Log.d("泛型测试","key value is " + obj.getKey());
    }
    // 类型通配符一般是使用?代替具体的类型实参
    // 此处’?’是类型实参,而不是类型形参!!!
    // ?和Number、String、Integer一样都是一种实际的类型,可以把?看成所有类型的父类。是一种真实的类型。
  • 泛型方法

    1
    2
    3
    4
    // 定义一个泛型方法,需要将泛型参数列表(<T>)放在返回值之前(必不可少!)
    public <T> void f(T x){
    System.out.println(x.getClass().getName());
    }

String

  • String 类中使用 final 关键字修饰字符数组来保存字符串,所以String 对象是不可变的。

  • 线程安全性

    • String 中的对象是不可变的,也就可以理解为常量,线程安全;
    • StringBuffer 对方法加了同步锁或者对调用的方法加了同步锁,所以是线程安全的。
    • StringBuilder 并没有对方法进行加同步锁,所以是非线程安全的。
  • String 中的 equals 方法是被重写过的,比较的是 String 字符串的值是否相等。(Objectequals 方法是比较的对象的内存地址。)

  • 字符串常量池 是 JVM 为了提升性能和减少内存消耗针对字符串(String 类)专门开辟的一块区域,主要目的是为了避免字符串的重复创建。

    1
    2
    3
    4
    5
    6
    // 在堆中创建字符串对象”ab“
    // 将字符串对象”ab“的引用保存在字符串常量池中
    String aa = "ab";
    // 直接返回字符串常量池中字符串对象”ab“的引用
    String bb = "ab";
    System.out.println(aa==bb);// true
  • String.intern() 方法,其作用是将指定的字符串对象的引用保存在字符串常量池中。

    • 如果字符串常量池中保存了对应的字符串对象的引用,就直接返回该引用。
    • 如果字符串常量池中没有保存了对应的字符串对象的引用,那就在常量池中创建一个指向该字符串对象的引用并返回