# static

叫静态,可以修饰成员变量、成员方法

# 修饰成员变量

成员变量按照有无 static 修饰,分为两种:

  • 类变量(静态成员变量):有 static 修饰,属于类,在计算机里只有一份,会被类的全部对象共享

    格式: 类名.类变量 (推荐)、 对象名.类变量 (不推荐)

    适用场景:数据只需要一份,且需要被共享时(访问,修改)

  • 实例变量(对象变量):无 static 修饰,属于每个对象的

    格式: 对象名.实例变量

    适用场景:每个对象都要有一份,数据各不同(如:name、score、age)

成员变量的执行原理:

1663978808670

应用场景

在开发中,如果某个数据只需要一份,且希望能够被共享(访问、修改),则该数据可以定义成类变量来记住

// 🌰:要求用户类记住创建了几个用户对象
public class User {
  public static int number;
  public User() {
    // 写法 1:
    User.number++;
    // 写法 2:访问自己类中的类变量,才可以省略类名
    number++;
  }
}

# 修饰成员方法

  • 类方法(静态方法):有 static 修饰的成员方法,属于类

    格式: 类名.类方法 (推荐)、 对象名.类方法 (不推荐)

  • 实例方法(对象的方法):无 static 修饰的成员方法,属于对象

    格式: 对象名.实例方法

成员方法的执行原理:

1664005554987

🔖 补充:main 方法是一个类方法

应用场景

可以用来设计工具类

  • 工具类:工具类中的方法都是类方法,每个方法都是用来完成一个功能的,工具类是给开发人员共同使用的

  • 好处:提高了代码复用;方便调用,提高了开发效率

    不用实例方法的原因:实例方法需要创建对象来调用,会浪费内存

// 🔖 多学一招:工具类不需要创建对象,建议将工具类的构造器私有化
public class MyUtil {
 private MyUtil() {
 }
}
MyUtil t = new MyUtil(); // 无法创建对象

# 注意事项

  • 类方法中可以直接访问类成员,不可以直接访问实例成员
  • 实例方法中可以直接访问类成员、实例成员
  • 实例方法中可以出现 this 关键字,类方法中不可以出现 this 关键字的

# static 应用(代码块)

代码块是类的 5 大成分之一(成员变量、构造器、方法、代码块、内部类)

  • 静态代码块

    • 格式: static {}

    • 特点:类加载时自动执行,由于类只会加载一次,所以静态代码块也只会执行一次

    • 作用:完成类的初始化,例如:对类变量的初始化赋值

  • 实例代码块

    • 格式: {}

    • 特点:每次创建对象时,执行实例代码块,并在构造器前执行

    • 作用:和构造器一样,都是用来完成对象的初始化的,例如:对实例变量进行初始化赋值

案例
public class Student {
  static int number = 80;
  static String schoolName = "呆呆";
  // 静态代码块
  static {
    System.out.println("静态代码块执行了~~");
    schoolName = "呆呆";
  }
}
//------------------------------------------------
public class Test {
  public static void main(String[] args) {
    System.out.println(Student.number);
    System.out.println(Student.number);
    System.out.println(Student.schoolName);
  }
}
运行结果:
  静态代码块执行了~~
  80
  80
  呆呆
public class Student {
 int age;
  {
    System.out.println("实例代码块执行了~~");
    age = 18;
    System.out.println("有人创建了对象:" + this);
  }
  public Student() {
    System.out.println("无参数构造器执行了~~");
  }
  public Student(String name) {
    System.out.println("有参数构造器执行了~~");
  }
}
//------------------------------------------------
public class Test {
  public static void main(String[] args) {
    Student s1 = new Student();
    Student s2 = new Student("呆呆");
    System.out.println(s1.age);
    System.out.println(s2.age);
  }
}
运行结果:
  实例代码块执行了~~
  有人创建了对象:
  无参数构造器执行了~~
  实例代码块执行了~~
  有人创建了对象:
  有参数构造器执行了~~
  18
  18

# static 应用(单例设计模式)

设计模式:设计模式就是具体问题的最优解决方案

单例设计模式:确保一个类只有一个对象

写法:

  • 把类的构造器私有
  • 定义一个类变量存储类的一个对象
  • 定义一个类方法返回对象

应用场景:任务管理器对象、获取运行时对象,使用单例模式可以避免浪费内存

# 饿汉式单例

特点:拿对象时,对象早就创建好了

public class A {
  private static A a = new A();
  private A() {
  }
  public static A getInstance() {
    return a;
  }
}
--------------------------------
A a1 = A.getInstance();
A a2 = A.getInstance();
System.out.println(a1); // com.itheima.A@4eec7777
System.out.println(a2); // com.itheima.A@4eec7777

# 懒汉式单例

特点:拿对象时,才开始创建对象

public class B {
  private static B b;
  private B() {
  }
  public static B getInstance() {
    if (b == null) {
      System.out.println("第一次创建对象");
      b = new B();
    }
    return b;
  }
}
--------------------------------
B b1 = B.getInstance();
B b2 = B.getInstance();
System.out.println(b1 == b2);
// 第一次创建对象
// true

# 继承

extends 关键字,让一个类和另一个类建立起一种父子关系

特点:子类能继承父类的非私有成员(成员变量、成员方法)

好处:减少了重复代码的编写,提高了代码的复用性

继承后对象的创建:子类的对象是由子类、父类共同完成的

对象能直接访问什么成员,是由子父类这多张设计图共同决定的,这多张设计图对外暴露了什么成员,对象就可以访问什么成员

public class B extends A {}
A类:父类(基类或超类)
B类:子类(派生类)

继承的执行原理:

1664010590126

# 权限修饰符

用来限制类中的成员(成员变量、成员方法、构造器、代码块…)能够被访问的范围

修饰符在本类里同一个包下中的其他类里任意包下的子类里任意包下的任意类里
private🗸
缺省🗸🗸
protected🗸🗸🗸
public🗸🗸🗸🗸

private < 缺省 < protected < public

# 单继承、Object

Java 是单继承的:一个类只能继承一个直接父类

Java 中的类不支持多继承,但是支持多层继承

object 类是 java 所有类的祖宗类。我们写的任何一个类,其实都是 object 的子类或子孙类

# 方法重写

子类可以重写一个方法名称、参数列表一样的方法,去覆盖父类的这个方法

注意事项:

  • 重写后,方法的访问,Java 会遵循就近原则
  • 建议加上: @Override 注解,可以校验重写是否正确,同时可读性好
  • 子类重写父类方法时,访问权限必须大于或者等于父类被重写的方法的权限( public > protected > 缺省 )
  • 重写的方法返回值类型,必须与被重写方法的返回值类型一样,或者范围更小
  • 私有方法、静态方法不能被重写

应用场景:当子类觉得父类的方法不好用,或者不满足自己的需求时,就可以用方法重写

  • 子类重写 Object 类的 toString () 方法,以便返回对象的内容

    🌰
    public class Student extends Object {
      private String name;
      private int age;
      public Student() {
      }
      public Student(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 "Student{name=" + name + ", age=" + age + "}";
      }
      //idea 会自动生成:鼠标右键 ➡ 生成(Generate)➡ toString () ➡ 按住 shift 全选中
      @Override
      public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
      }
    }
    public class Test {
      public static void main(String[] args) {
        Student s = new Student("呆呆", 18);
        // System.out.println(s.toString());
        System.out.println(s); // Student {name=' 呆呆 ', age=18}
      }
    }

# 子类中访问成员的特点

  • 在子类方法中访问其他成员(成员变量、成员方法),是依照就近原则

    • 先子类局部范围找

    • 然后子类成员范围找

    • 然后父类成员范围找,如果父类范围还没有找到则报错

  • 如果子父类中,出现了重名的成员,会优先使用子类的

  • 可以通过 super 关键字,指定访问父类的成员: super.父类成员变量/父类成员方法

public class Test {
  public static void main(String[] args) {
    B b = new B();
    b.showName();
    b.showMethod();
  }
}
public class A {
  String name = "父类成员变量";
  public void print1() {
    System.out.println("父类成员方法");
  }
}
public class B extends A {
  String name = "子类成员变量";
  public void showName() {
    String name = "局部名称";
    System.out.println(name); // 局部名称
    System.out.println(this.name); // 子类成员变量
    System.out.println(super.name); // 父类成员变量
  }
  @Override
  public void print1() {
    System.out.println("子类成员方法");
  }
  public void showMethod() {
    print1(); // 子类成员方法
    super.print1(); // 父类成员方法
  }
}

# 子类构造器的特点

子类的全部构造器,都会先调用父类的构造器,再执行自己

# super(...)

子类构造器如何实现调用父类构造器的:

  • 默认情况下,子类全部构造器的第一行代码都是 super(); (写不写都有) ,它会调用父类的无参数构造器

    1664160225526

  • 如果父类没有无参数构造器,则我们必须在子类构造器的第一行手写 super(...); ,指定去调用父类的有参数构造器

应用场景

1664163881728

子类构造器的应用场景

# this(...)

在构造器中调用本类的其他构造器

this(): 调用本类的无参数构造器
this(参数): 调用本类的有参数构造器

⚠️ 注: this(…) 、super(…) 都只能放在构造器的第一行,因此,有了 this(…) 就不能写 super(…) 了,反之亦然

案例 🌰

1664170865036

# 总结

访问本类成员:
  this.成员变量 // 访问本类成员变量
  this.成员方法 // 调用本类成员方法
  this() // 调用本类无参数构造器
  this(参数) // 调用本类有参数构造器
访问父类成员:
  super.成员变量 // 访问父类成员变量
  super.成员方法 // 调用父类成员方法
  super() // 调用父类无参数构造器
  super(参数) // 调用父类有参数构造器
注意:thissuper 访问构造方法,只能用到构造方法的第一句,否则会报错

# 多态

在继承 / 实现情况下的一种现象,表现为:对象多态、行为多态

前提:

  • 继承 / 实现关系
  • 存在父类引用子类对象
  • 存在方法重写

⚠️ 注:多态是对象、行为的多态,Java 中的属性(成员变量)不谈多态

// 对象多态
People p1 = new Student(); // 编译看左边,运行看右边
People p2 = new Teacher();
// 行为多态
p1.run();
p2.run();

好处:

  • 在多态形式下,右边对象是解耦合的,更便于扩展和维护

  • 定义方法时,使用父类类型的形参,可以接收一切子类对象,扩展性更强、更便利

    public class Test2 {
      public static void main(String[] args) {
        Teacher t = new Teacher();
        go(t);
        Student s = new Student();
        go(s);
      }
      public static void go(People p) {
        p.run();
      }
    }
  • 多态下不能直接调用子类的独有方法,需要类型转换

# 类型转换

  • 自动类型转换: 父类 变量名 = new 子类();

    People p = new Teacher();
  • 强制类型转换: 子类 变量名 = (子类) 父类变量

    Teacher t = (Teacher) p;

⚠️ 注:

  • 存在继承 / 实现时,就可以进行强制类型转换,编译阶段不会报错

  • 运行时,如果发现对象的真实类型与强转后的类型不同会报错(ClassCastException)

    People p = new Teacher();
    Student s = (Student) p; // java.lang.ClassCastException

使用 instanceof 关键字,判断当前对象的真实类型,再进行强转

格式:对象 instanceof 类型
p instanceof Student

# final

final 关键字是最终的意思,可以修饰(类、方法、变量)

  • 修饰类:该类被称为最终类,不能被继承
  • 修饰方法:该方法被称为最终方法,不能被重写
  • 修饰变量:该变量只能被赋值一次

⚠️ 注:

  • 修饰基本类型的变量:变量存储的数据不能被改变
  • 修饰引用类型的变量:变量存储的地址不能被改变,但地址所指向对象的内容是可以被改变的

# 常量

使用 static final 修饰的成员变量,通常用于记录系统的配置信息

命名规范:建议使用大写英文单词,多个单词使用下划线连接起来

public class Constant {
  public static final String SCHOOL_NAME  = "xxx大学";
}

优势、执行原理:

  • 代码可读性更好,可维护性也更好
  • 程序编译后,常量会被 “宏替换”:出现常量的地方全部会被替换成其记住的字面量,这样可以保证使用常量和直接用字面量的性能是一样的

# 抽象

abstract 关键字修饰类、成员方法

// 抽象类
修饰符 abstract class 类名 {
  // 抽象方法:只有方法签名,不能有方法体
 修饰符 abstract 返回值类型 方法名称(形参列表);
 }

注意事项、特点:

  • 抽象类中不一定有抽象方法,有抽象方法的类一定是抽象类
  • 类该有的成员(成员变量、方法、构造器)抽象类都可以有
  • 抽象类不能创建对象,仅作为一种特殊的父类,让子类继承并实现
  • 一个类继承抽象类,必须重写完抽象类的全部抽象方法,否则这个类也必须定义成抽象类

应用场景

  • 用抽象类可以把子类中相同的代码,包括方法签名都抽上来,这样能更好地支持多态,以提高代码的灵活性
  • 反过来用,我们不知道系统未来具体的业务实现时,我们可以先定义抽象类,将来让子类去继承实现,以方便系统的扩展
案例 🌰
// 父类:Animal 类
public abstract class Animal {
  private String name;
  public abstract void cry();
  public String getName() {
    return name;
  }
  public void setName(String name) {
    this.name = name;
  }
}
// 子类:Dog 类
public class Dog extends Animal {
    public void cry() {
        System.out.println(getName() + "汪汪汪的叫~~");
    }
}
// 子类:Cat 类
public class Cat extends Animal {
    public void cry(){
        System.out.println(getName() + "喵喵喵的叫~~");
    }
}
// 测试类
public class Test {
    public static void main(String[] args) {
        Animal a = new Cat("叮当猫");
        a.cry(); // 这时执行的是 Cat 类的 cry 方法
    }
}

# 模板方法设计模式

解决方法中存在重复代码的问题

写法:

  • 定义一个抽象类

  • 在里面定义 2 个方法

    • 模板方法:把相同代码放里面去

    • 抽象方法:具体实现交给子类完成

建议使用 final 关键字修饰模板方法

  • 模板方法是给对象直接使用的,不能被子类重写
  • 一旦子类重写了模板方法,模板方法就失效了
案例 🌰
public abstract class C {
  // 模板方法
  public final void sing() {
    System.out.println("唱一首你喜欢的歌:");
    doSing();
    System.out.println("唱完了!");
  }
  public abstract void doSing();
}
public class A extends C {
  @Override
  public void doSing() {
    System.out.println("《海盗船长》");
  }
}
public class B extends C {
  @Override
  public void doSing() {
    System.out.println("《原本住在北极的人选择了冒险》");
  }
}
// 测试类
public class Test {
  public static void main(String[] args) {
    B b = new B();
    b.sing();
  }
}

# 接口

使用 interface 关键字定义出一种特殊的结构,JDK 8 之前,接口中只能定义成员变量和成员方法

public interface 接口名 {
  成员变量(常量)
  成员方法(抽象方法)
}
  • 接口不能创建对象;接口是用来被类实现(implements)的,实现接口的类称为实现类

    修饰符 class 实现类 implements 接口1, 接口2, 接口3, ... {
    }
  • 一个类可以实现多个接口,实现类实现多个接口,必须重写完全部接口的全部抽象方法,否则实现类需要定义成抽象类

好处:

  • 弥补了类单继承的不足,类可以同时实现多个接口
  • 让程序可以面向接口编程,这样既不用关心实现的细节,也可以灵活方便的切换各种实现
写法案例
// 定义 B 接口
public interface B {
  void testb1();
  void testb2();
}
// 定义 C 接口
public interface C {
  void testc1();
  void testc2();
}
// 实现类 D
public class D implements B, C{
  @Override
  public void testb1() {
  }
  @Override
  public void testb2() {
  }
  @Override
  public void testc1() {
  }
  @Override
  public void testc2() {
  }
}
// 测试类
public class Test {
  public static void main(String[] args) {
    D d = new D();
    d.testb1();
    d.testc1();
  }
}

# 接口案例

# JDK8 的新特性

新增了三种形式的方法,增强了接口的能力,更便于项目的扩展和维护

  • 默认方法(实例方法): default 修饰,使用实现类的对象调用
  • 静态方法(类方法): static 修饰,必须用当前接口名调用
  • 私有方法: private 修饰,jdk9 开始才支持,只能在接口内部被调用
  • 都默认被 public 修饰
public interface A {
  default void test1() {
    ...
  }
  private void test2() {
    ...
  }
  static void test3(){
    ...
  }
}

# 接口的多继承

一个接口可以同时继承多个接口,作用:便于实现类去实现

public interface C extends B, A {
}

注意事项(了解):

  • 一个接口继承多个接口,如果多个接口中存在方法签名冲突,则此时不支持多继承

  • 一个类实现多个接口,如果多个接口中存在方法签名冲突,则此时不支持多实现

    interface A {
      void test();
    }
    interface B {
      String test();
    }
    // 报错
    interface C implements A, B {
    }
    class C implements A, B {
    }
  • 一个类继承了父类,又同时实现了接口,父类中和接口中有同名的默认方法,实现类会优先用父类的

  • 一个类实现了多个接口,多个接口中存在同名的默认方法,可以不冲突,这个类重写该方法即可

    interface A {
      default void test() {
        System.out.println("A接口");
      }
    }
    interface B {
      default void test() {
        System.out.println("B接口");
      }
    }
    class C implements A, B {
      @Override
      public void test() {
        // 重写方法
      }
    }

# 内部类

类中的五大成分之一,如果一个类定义在另一个类的内部,这个类就是内部类

应用场景:当一个类的内部,包含了一个完整的事物,且这个事物没有必要单独设计时,就可以把这个事物设计成内部类

public class Car {
  // 内部类
  public class Engine {
  }
}

# 成员内部类

类中的一个普通成员,类似普通的成员变量和成员方法

public class Outer {
  public class Inner {
  }
}
// 创建对象的格式
外部类名.内部类名 对象名 = new 外部类(...).new 内部类(...);
Outer.Inner in =  new Outer().new Inner();

⚠️ 注:JDK 16 之前,成员内部类中不能定义静态成员,JDK 16 开始才支持定义静态成员

成员内部类的实例方法中,访问其他成员的特点:

  • 可以直接访问外部类的实例成员、静态成员
  • 可以拿到当前外部类对象,格式是: 外部类名.this

# 静态内部类

static 修饰的内部类,属于外部类自己持有

public class Outer {
  public static class Inner {
  }
}
// 创建对象的格式
外部类名.内部类名 对象名 = new 外部类.内部类(...);
Outer.Inner in = new Outer.Inner();

特点:可以直接访问外部类的静态成员,不可以直接访问外部类的实例成员(因为属于对象的成员)

# 局部内部类

定义在在方法中、代码块中、构造器等执行体中

鸡肋语法,仅作了解

public class Test {
  public static void main(String[] args) {
  }
  public static void go() {
    class A {}
    abstract class B {}
    interface C {}
  }
}

# 匿名内部类

一种特殊的局部内部类

匿名:指的是程序员不需要为这个类声明名字

new 父类/接口(参数值...) {
  类体(一般是方法重写);
};

特点:匿名内部类本质就是一个子类,并会立即创建出一个子类对象

作用:用于更方便地创建一个子类对象

应用场景:通常作为一个参数传输给方法

public class Test {
  public static void main(String[] args) {
    // 方法一
    Swimming s = new Swimming() {
      @Override
      public void swim() {
        System.out.println("游泳~~");
      }
    };
    // 方法二
    go(new Swimming() {
      @Override
      public void swim() {
        System.out.println("游泳~~");
      }
    });
  }
  public static void go(Swimming s) {
    System.out.println("--- start ---");
    s.swim();
    System.out.println("--- end ---");
  }
}
interface Swimming {
  void swim();
}
import javax.swing.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
public class Test2 {
  public static void main(String[] args) {
    // GUI 编程
    JFrame win = new JFrame("登录界面");
    JPanel panel = new JPanel(); // 桌布
    win.add(panel);
    JButton btn = new JButton("登录");
    panel.add(btn);
    // 绑定事件监听器
    btn.addActionListener(new ActionListener() {
      @Override
      public void actionPerformed(ActionEvent e) {
        JOptionPane.showMessageDialog(win, "登录成功");
      }
    });
    // 简化写法
    btn.addActionListener(e -> JOptionPane.showMessageDialog(win, "登录成功"));
    win.setSize(400, 400); // 窗口大小
    win.setLocationRelativeTo(null); // 居中
    win.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE); // 关闭窗口退出程序
    win.setVisible(true); // 可见
  }
}

# 枚举

枚举是一种特殊类

修饰符 enum 枚举类名 {
  名称1, 名称2, ... ;
  其他成员...
}
  • 枚举类的第一行只能罗列一些名称,这些名称都是常量,并且每个常量记住的都是枚举类的一个对象
  • 枚举类的构造器都是私有的(写不写都只能是私有的),因此,枚举类对外不能创建对象
  • 枚举都是最终类,不可以被继承
  • 枚举类中,从第二行开始,可以定义类的其他各种成员
  • 编译器为枚举类新增了几个方法,并且枚举类都是继承: java.lang.Enum 类 的,从 enum 类也会继承到一些方法
public enum A {
  X, Y, Z;
}
public class Test {
  public static void main(String[] args) {
    // 获取枚举 A 类的枚举项
    A a1 = A.X;
    A a2 = A.Y;
    A a3 = A.Z;
    A[] as = A.values(); // 拿到全部对象
    A a4 = A.valueOf("Z");
    System.out.println(a4.name()); // Z
    System.out.println(a4.ordinal()); // 2,索引值
  }
}

反编译来验证:枚举项实际上是枚举类的对象

// 反编译命令
// javap ./A.class
Compiled from "A.java"
public final class A extends java.lang.Enum<A> {
  public static final A X = new A();
  public static final A Y = new A();
  public static final A Z = new A();
  public static A[] values();
  public static A valueOf(java.lang.String);
  static {};
}
// 使用枚举类实现单例设计模式
public enum B {}
枚举深入

可以在枚举类中定义构造器、成员变量、成员方法

public enum A {
  // 定义枚举项
  X, Y, Z("呆呆"); // 执行枚举类的带参数构造方法
  // 成员变量
  private String name;
  // 定义空构造器
  public A() {
  }
  // 定义带参数构造器
  public A(String name) {
    this.name = name;
  }
  // 成员方法
  public String getName() {
    return name;
  }
  ...
}

# 应用场景

用来表示一组信息作为参数进行传输,做信息标志和分类

案例

需求:根据用户不同的身份选择,推荐不同的信息给用户

方式一:选择定义一个一个的常量来表示一组信息,并作为参数传输

优点:参数值不受约束

public class Constant {
  public static final int GRIL = 0;
  public static final int BOY = 1;
}
public class Test {
  public static void main(String[] args) {
    check(Constant.GRIL);
    // check(1);
    // check(11);
  }
  public static void check(int sex) {
    switch (sex) {
      case Constant.GRIL:
        System.out.println("言情小说");
        break;
      case Constant.BOY:
        System.out.println("武侠小说");
        break;
    }
  }
}

方式二:选择定义枚举表示一组信息,并作为参数传输

优点:代码可读性好,参数值得到了约束,对使用者更友好,建议使用

public enum Constant2 {
  GRIL, BOY;
}
public class Test {
  public static void main(String[] args) {
    check(Constant2.GRIL);
  }
  public static void check(Constant2 sex) {
    switch (sex) {
      case GRIL:
        System.out.println("言情小说");
        break;
      case BOY:
        System.out.println("武侠小说");
        break;
    }
  }
}

# 泛型

定义类、接口、方法时,同时声明了一个或者多个类型变量(如: <E> ) ,称为泛型类、泛型接口,泛型方法、它们统称为泛型

作用:提供了在编译阶段约束所能操作的数据类型,并自动进行检查的能力!这样可以避免强制类型转换,及其可能出现的异常

本质把具体的数据类型作为参数传给类型变量

public class ArrayList<E> {
  ...
}

# 自定义泛型类

修饰符 class 类名<类型变量, 类型变量, ...> {
}

⚠️ 注:类型变量建议用大写的英文字母,常用的有: E、T、K、V

自定义 MyArrayList 类
public class MyArrayList<E> {
  private Object[] arr = new Object[10];
  private int size; // 记录当前位置
  public boolean add(E e) {
    arr[size++] = e;
    return true;
  }
  public E get(int index) {
    return (E) arr[index];
  }
}

# 自定义泛型接口

修饰符 interface 接口名<类型变量, 类型变量, ...> {
}
案例

需求:一个系统要处理学生和老师的数据,需要提供 2 个功能,保存对象数据、根据名称查询数据,要求:这两个功能处理的数据既能是老师对象,也能是学生对象

1、要有学生类和老师类

public class Teacher {}
public class Student {}

2、定义一个 Data<T> 泛型接口

import java.util.ArrayList;
public interface Data<T> {
  public void add(T t);
  public ArrayList<T> getByName(String name);
}

3、Teacher 对象的接口实现类

import java.util.ArrayList;
public class TeacherData implements Data<Teacher> {
  @Override
  public void add(Teacher teacher) {
  }
  @Override
  public ArrayList<Teacher> getByName(String name) {
    return null;
  }
}

4、Student 对象的接口实现类同理

# 泛型方法

修饰符 <类型变量, 类型变量, ...> 返回值类型 方法名(形参列表) {
}
// 例子
public static <T> void test(T t) {
  return t;
}
// 不是泛型方法
public E get(int index) {
  return (E) arr[index];
}

# 泛型限定

通配符:就是 ? ,使用泛型的时候代表一切类型

对泛型的数据类型进行范围的限制

  • <?> 表示任意类型
  • 上限: <? extends 数据类型> 表示指定类型或者指定类型的子类
  • 下限: <? super 数据类型> 表示指定类型或者指定类型的父类
案例
class Animal {}
class Cat extends Animal {}
class Dog extends Animal {}
public class Test {
  public static void main(String[] args) {
      //test1 方法接收:任意类型
      ArrayList<Cat> list1 = new ArrayList<>();
      ArrayList<Dog> list2 = new ArrayList<>();
      ArrayList<String> list3 = new ArrayList<>();
      test1(list1);
      test1(list2);
      test1(list3);
      //test2 方法接收:Animal 或者 Animal 的子类类型
      ArrayList<Animal> list4 = new ArrayList<>();
      ArrayList<Cat> list5 = new ArrayList<>();
      test2(list4);
      test2(list5);
      //test3 方法接收:Animal 或者 Animal 的父类类型
      ArrayList<Animal> list6 = new ArrayList<>();
      ArrayList<Object> list7 = new ArrayList<>();
      test3(list6);
      test3(list7);
  }
  public static void test1(ArrayList<?> list) {}
  public static void test2(ArrayList<? extends Animal> list) {}
  public static void test3(ArrayList<? super Animal> list) {}
}

# 泛型擦除

  • 泛型是工作在编译阶段的,一旦程序编译成 class 文件,class 文件中就不存在泛型了,这就是泛型擦除

  • 泛型不支持基本数据类型,只支持对象类型(引用数据类型)

// 若要添加 int 或 double 类型
ArrayList<Integer> list1 = new ArrayList<>();
list1.add(1);
ArrayList<Double> list2 = new ArrayList<>();
list2.add(1.0);