类和对象

类的定义

类的组成是由属性和行为两部分组成

  • 属性:在类中通过成员变量来体现(类中方法外的变量)

  • 行为:在类中通过成员方法来体现(和前面的方法相比去掉static关键字即可)

    1
    2
    3
    4
    5
    6
    7
    public void 类名{
    //成员变量
    //成员方法
    //构造器(可选)
    //代码块(可选)
    //内部类(可选)
    }

对象的使用

  • 创建对象的格式:
    • 类名 对象名 = new 类名();
  • 调用成员的格式:
    • 对象名.成员变量
    • 对象名.成员方法();

工具类

  • 帮助我们做一些事情,但不描述任何事物的类

私有化构造方法

  • 外界不可以创建此类的对象,因为这个类不描述任何事物,因此创建这个类的对象没有意义

    1
    2
    3
    private class Math {
    private Math(){}
    }

方法定义为静态

  • 因为外界不可以创建此类对象,因此需要将方法全部定义为静态,以供使用

封装

封装思想

  • 对象代表什么,就得封装对应的数据,并提供数据对应的行为。eg:人画圆,画圆这种行为应定义在圆中

  • 封装代码实现:

    将类的某些信息隐藏在类内部,不允许外部程序直接访问,而是通过该类提供的方法来实现对隐藏信息的操作和访问
    成员变量private,提供对应的getXxx()/setXxx()方法

private

private是一个修饰符,可以用来修饰成员(成员变量,成员方法)

  • 被private修饰的成员,只能在本类进行访问,针对private修饰的成员变量,如果需要被其他类使用,提供相应的操作
    • 提供“get变量名()”方法,用于获取成员变量的值,方法用public修饰
    • 提供“set变量名(参数)”方法,用于设置成员变量的值,方法用public修饰

成员变量|局部变量|this

成员变量:直接定义在类中

局部变量:在类的某个方法中定义的变量

this修饰的变量用于指代成员变量,其主要作用是(区分局部变量和成员变量的重名问题)

  • this:代表方法调用者的地址值
  • 方法的形参如果与成员变量同名,不带this修饰的变量指的是形参,而不是成员变量
  • 方法的形参没有与成员变量同名,不带this修饰的变量指的是成员变量

构造方法

1
2
3
4
5
6
//格式
//功能:完成对象数据的初始化
public class 类名{
修饰符 类名(参数){
}
}
  • 构造方法的创建

如果没有定义构造方法,系统将给出一个默认的无参数构造方法
如果定义了构造方法,系统将不再提供默认的构造方法

  • 构造方法的重载

如果自定义了带参构造方法,还要使用无参数构造方法,就必须再写一个无参数构造方法

static关键字

  • static表示静态,是Java中的一个修饰符,可以修饰成员方法,成员变量
  • 被static修饰的成员变量/方法,叫做静态变量/方法

静态变量

特点

  • 被该类所有对象所共享,即当所用对象的此变量皆相同时,使用static修饰此成员变量
  • 不属于对象,属于类
  • 随着类的加载而被加载到静态区中,优先于对象存在

访问

1
2
3
4
//方法1(推荐)
类名.静态变量;
//方法2
对象名调用

静态方法

特点

  • 多用在测试类和工具类中
  • javabean类中很少使用

访问

1
2
3
4
//方法1(推荐)
类名.静态方法();
//方法2
对象名调用

static注意事项

  • 静态方法只能访问静态变量和静态方法

    ​ 静态随类的加载而加载,在对象之前便存在,故不可以访问非静态的东西

    ​ 因此测试类中Main()方法外的其他方法也必须被static修饰

  • 非静态方法可以访问所有变量和方法

  • 静态方法中没有this关键字,也没有super关键字

    ​ 静态方法不属于对象,而属于类,不需要对象来调用,故没有this关键字

继承

概念

  • 继承是面向对象三大特征之一,可以让类跟类之间产生子父的关系,子类可以调用父类中的代码,提高复用性

  • 格式

    1
    public class 子类 extends 父类 {}

特点

顶层父类是Object类。所有的类默认继承Object,作为父类。

  • Java只支持单继承,不支持多继承

    1
    2
    3
    4
    5
    //一个类只能有一个直接父类,不可以有多个直接父类
    class A {}
    class B {}
    class C1 extends A {} //√
    //class C2 extends A,B {} //×
  • 一个类可以有多个子类

    1
    2
    3
    4
    //A可以有多个子类
    class A {}
    class C1 extends A {}
    class C2 extends B {}
  • 可以多层继承

    1
    2
    3
    class A {}
    class C1 extends A {}
    class D extends C1 {}

字类可以继承父类的哪些内容?

内容
构造方法 非私有–不能 private–不能
成员变量 非私有–能 private–能
成员方法 虚方法–能 否则–不能

虚方法:非private 和 非static 和 非final修饰的方法

注意

  • 子类只能访问父类中的非私有成员
  • 子类不能继承父类的私有属性,但是如果子类中公有的方法影响到了父类私有属性,那么私有属性是能够被子类使用的。

继承后访问特点

成员变量

就近原则

  • 先在局部位置找,本类成员位置找,父类成员位置找,逐级往上

    1
    2
    3
    4
    //出现重名的成员变量怎么办?
    System.out.println(name); //从局部位置开始往上找 --通常指的是子类方法中定义的name
    System.out.println(this.name); //从本类成员位置开始往上找 --通常指的是子类成员变量的name
    System.out.println(super.name); //从父类成员位置开始往上找 --通常指的是父类成员变量的name

成员方法

  • this调用:就近原则
  • super调用:直接找父类

构造方法

构造方法的作用是初始化对象成员变量数据的。所以子类的初始化过程中,必须先执行父类的初始化动作。子类的构造方法中默认有一个super() ,表示调用父类的构造方法,父类成员变量初始化后,才可以给子类使用。(先有爸爸,才能有儿子

  • 子类不能继承父类的构造方法,但是可以通过super调用
  • 默认先访问父类中无参的构造方法,再执行自己
  • 子类构造方法的第一行,有一个默认的super(),默认先调用父类的无参构造方法,因此super()可以省略不写,但如果想要调用父类有参构造,必须在第一行手动书写super(参数...)

super()this()

调用构造方法

1
2
super(...) -- 调用父类的构造方法,根据参数匹配确认
this(...) -- 调用本类的其他构造方法,根据参数匹配确认

注意

  • super(…)是根据参数去确定调用父类哪个构造方法的。

  • super() 和 this() 都必须是在构造方法的第一行,所以不能同时出现。

  • 子类的每个构造方法中均有默认的super(),调用父类的空参构造。手动调用父类构造会覆盖默认的super()。

方法重写

概念与本质

  • 子类中出现与父类一模一样的方法时(返回值类型,方法名和参数列表都相同),会出现覆盖效果,也称为重写或者复写。
  • 覆盖虚方法表中的方法。因为只有可以添加到虚方法表中的虚方法才可以被继承

使用场景

  • 子类继承了父类的方法,但子类觉得父类的这方法不足以满足自己的需求,子类便可以重写了一个与父类同名的方法。

@override注解

  • @override可以校验重写是否正确,可读性更好

重写方法基本要求

  • 子类重写的方法尽量跟父类中的方法保持一致
  • 只有虚方法表里面的方法可以被重写。因为只有可以添加到虚方法表中的虚方法才可以被继承

多态

概念与注意

概念

  • 多态是继封装和继承之后,面向对象的第三大特征

  • 同种类型的对象,表现出的不同形态;即通过父类指向不同的子类,使传递父类对象后可以调用不同子类的方法

    1
    2
    //格式
    父类类型 对象名称 = 子类对象;

注意

  • 使用父类型作为参数,可以接收所有子类对象
  • 当一个方法的形参是一个类,我们可以传递这个类所有的子类对象。
  • 当一个方法的形参是一个接口,我们可以传递这个接口所有的实现类对象(后面会学)。
  • 而且多态还可以根据传递的不同对象来调用不同类中的方法。

使用前提

  • 有继承/实现关系 extends
  • 有父类引用指向子类对象 父类类型 对象名称 = 子类对象;
  • 有方法重写(因为不重写多态没意义) @override

多态运行的特点

调用成员变量

  • 编译看左边,运行看左边

  • fFu类型的,所以默认会从Fu这个类中去找

    1
    2
    3
    4
    Fu f = new Zi();
    //编译看左边的父类中有没有name这个属性,没有就报错
    //在实际运行的时候,把左边父类name属性的值打印出来
    System.out.println(f.name);

调用成员方法

  • 编译看左边,运行看右边

  • fFu类型的,所以默认会从Fu这个类中去找,但是多态使用时代表子类已经对方法进行了重写,那么虚方法表中是会把父类方法进行覆盖,因此调用的是子类中重写的方法

    1
    2
    3
    4
    Fu f = new Zi();
    //编译看左边的父类中有没有show这个方法,没有就报错
    //在实际运行的时候,运行的是子类中的show方法
    f.show();

多态的弊端和类型转换

多态的弊端

  • 多态编译阶段是看左边父类类型的,如果子类有些独有的功能(方法),此时多态的写法就无法访问子类独有功能了

类型转换

  • 因为多态的弊端,所以想要调用子类的独有功能必须向下转型

  • 向上转型(自动转换) – 小–>大,自动转换

    1
    2
    3
    //当父类引用指向一个子类对象时,便是向上转型。
    父类类型 变量名 = new 子类类型();
    如:Animal a = new Cat();
  • 向下转型(强制转换)

    1
    2
    3
    4
    //一个已经向上转型的子类对象,将父类引用转为子类引用,可以使用强制类型转换的格式,便是向下转型。
    子类类型 变量名 = (子类类型) 父类变量名;
    如:Aniaml a = new Cat();
    Cat c =(Cat) a;
  • 类型转换的异常

    1
    2
    3
    4
    5
    6
    7
    8
    // 向上转型  
    Animal a = new Cat();
    a.eat(); // 调用的是 Cat 的 eat

    // 向下转型
    Dog d = (Dog)a;
    d.watchHouse(); // 调用的是 Dog 的 watchHouse 【运行报错】
    //报出了ClassCastException,类型转换异常!这是因为,明明创建了Cat类型对象,运行时,当然不能转换成Dog对象的。

instanceof关键字

  • 为了避免ClassCastException的发生,Java提供了 instanceof 关键字,给引用变量做类型的校验,格式如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    // 向下转型  
    if (a instanceof Cat){
    Cat c = (Cat)a;
    c.catchMouse(); // 调用的是 Cat 的 catchMouse
    } else if (a instanceof Dog){
    Dog d = (Dog)a;
    d.watchHouse(); // 调用的是 Dog 的 watchHouse
    }

    //新特性
    //先判断a是否为Dog类型,如果是,则强转成Dog类型,转换之后变量名为d
    //如果不是,则不强转,结果直接是false
    if(a instanceof Dog d){
    d.lookHome();
    }else if(a instanceof Cat c){
    c.catchMouse();
    }else{
    System.out.println("没有这个类型,无法转换");
    }

  • 使用同一个包中的类时,不需要导包
  • 使用java.lang包中的类时,不需要导包
  • 其他情况都需要导包
  • 如果同时使用两个包中的同名类时,需要使用全类名:包名 + 类名

final关键字

概念

  • final:不可改变,最终的含义。可以用于修饰类、方法和变量。

  • 修饰类:表示该类为最终类,不能被继承

  • 修饰方法:表示该方法是最终方法,不能被重写

  • 修饰变量:叫做常量,只能被赋值一次

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    //注意:修饰成员变量时,涉及初始化问题,成员变量可以显示初始化or构造方法初始化,一个变量两者只能选择其一
    //显示初始化:
    public class Student {
    final int num = 10;
    }

    //构造方法初始化
    //注意:每个构造方法中都需要赋值一次
    public class Student {
    final int num;
    final int age;

    public Student(int num) {
    this.num = 20;
    }

    public Student(int num, int age) {
    this.num = name;
    }
    }

常量

命名规范

  • 单个单词:全部大写
  • 多个单词:全部大写,单词之间用下划线隔开

细节

  • final修饰的是基本数据类型,那么变量存储的数据值是不可改变的
  • final修饰的是引用数据类型,那么变量存储的地址值是不能发生改变的,对象内部的属性值是可以改变的

权限修饰符

public protected 默认 private
同一类中
同一包中的类
不同包的子类中
不同包中的无关类

public具有最大权限。private则是最小权限。编写代码时,如果没有特殊的考虑,建议这样使用权限:

  • 成员变量使用private ,隐藏细节。
  • 构造方法使用 public ,方便创建对象。
  • 成员方法使用public ,方便调用方法。
    • 如果类中的某个成员方法是抽取其他成员方法中的共性代码,这个方法一般也私有,因为这些共性代码一般不希望被别的类调用

小贴士:不加权限修饰符,就是默认权限

代码块

  • 分类
    • 局部代码块,构造代码块,静态代码块
  • 局部代码块的作用
    • 提前结束变量的声明周期(已淘汰)
  • 构造代码块的作用
    • 抽取构造方法中的重复代码(不够灵活)

静态代码块:star:

  • 格式

    • static {
          
      }
      
      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

      - 特点

      - 随着类的加载而加载,并且自动触发,<u>只执行一次</u>

      - 使用场景

      - 在类加载时,做一些数据初始化的时候使用

      - 执行顺序

      - 静态代码块 --> 构造代码块 --> 构造方法
      - [(3条消息) java中静态代码块详解_这辈子_安静的努力着的博客-CSDN博客_静态代码块](https://blog.csdn.net/qq_35868412/article/details/89360250)

      # 抽象类

      ## 作用

      - 抽取共性时,无法确定方法体,就把方法定义为抽象的。
      - 强制让子类按照某种格式重写
      - 抽象方法所在的类,必须是抽象类

      ## abstract格式

      ```java
      //抽象类
      public abstract class 类名{}
      //抽象方法
      public abstract 返回值类型 方法名(参数列表);

注意:star:

关于抽象类的使用,以下为语法上要注意的细节,虽然条目较多,但若理解了抽象的本质,无需死记硬背。

  • 抽象类不能创建对象,如果创建,编译无法通过而报错。只能创建其非抽象子类的对象。

    理解:假设创建了抽象类的对象,调用抽象的方法,而抽象方法没有具体的方法体,没有意义。

  • 抽象类中,可以有构造方法,是供子类创建对象时,初始化父类成员使用的。

    理解:子类的构造方法中,有默认的super(),需要访问父类构造方法。

  • 抽象类中,不一定包含抽象方法,但是有抽象方法的类必定是抽象类。

    理解:未包含抽象方法的抽象类,目的就是不想让调用者创建该类对象,通常用于某些特殊的类结构设计。

  • 抽象类的子类,必须重写抽象父类中所有的抽象方法,否则子类也必须定义成抽象类,编译无法通过而报错。

    理解:假设不重写所有抽象方法,则类中可能包含抽象方法。那么创建对象后,调用抽象的方法,没有意义。

  • 抽象类存在的意义是为了被子类继承。

    理解:抽象类中已经实现的是模板中确定的成员,抽象类不确定如何实现的定义成抽象方法,交给具体的子类去实现。

接口

作用

  • 由继承可知所有子类的共性被抽取到父类中,但当某个共性不是全部子类,只是部分子类的共性时,这时就需要使用接口来定义这一共性(规则),只需让这些部分子类实现接口即可
  • 接口是更加彻底的抽象,在JDK7之前,接口中全是抽象方法。接口同样是不能创建对象的

格式

1
2
3
4
5
6
7
8
9
//接口的定义格式,默认在interface前面会有abstract
interface 接口名{

}

//实现接口,接口是可以多实现的
class 类名 implements 接口1,接口2...{

}

接口成员的特点

  • 在JDK7,包括JDK7之前,接口中的只有包含:抽象方法和常量
  • 接口中没有构造方法,因为接口不能创建对象同时也不需要给子类的成员变量赋值。
  • JDK8的新特性,接口中可以定义有方法体的方法
  • JDK9的新特性,接口中可以定义私有方法

抽象方法

  • 注意:接口中的抽象方法默认会自动加上public abstract修饰程序员无需自己手写!!
  • 按照规范:以后接口中的抽象方法建议不要写上public abstract。因为没有必要啊,默认会加上。

常量

  • 在接口中定义的成员变量默认会加上: public static final修饰。也就是说在接口中定义的成员变量实际上是一个常量。这里是使用public static final修饰后,变量值就不可被修改,并且是静态化的变量可以直接用接口名访问,所以也叫常量。常量必须要给初始值。常量命名规范建议字母全部大写,多个单词用下划线连接。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public interface InterF {
// 抽象方法!
// public abstract void run();
void run();

// public abstract String getName();
String getName();

// public abstract int add(int a , int b);
int add(int a , int b);


// 它的最终写法是:
// public static final int AGE = 12 ;
int AGE = 12; //常量
String SCHOOL_NAME = "黑马程序员";

}

接口和类的关系

类和类的关系

  • 继承关系,只能单继承,不能多继承,但是可以多层继承

类和接口的关系

  • 实现关系,可以单实现,也可以多实现,还可以在继承一个类的同时实现多个接口

接口和接口的关系

  • 继承关系,可以单继承,也可以多继承
  • 细节:如果类实现了最下面的子接口,那么就需要重写所有的抽象方法

注意:star:

关于接口的使用,以下为语法上要注意的细节,虽然条目较多,但若理解了抽象的本质,无需死记硬背。

  1. 当两个接口中存在相同抽象方法的时候,该怎么办?

只要重写一次即可。此时重写的方法,既表示重写1接口的,也表示重写2接口的。

  1. 实现类能不能继承A类的时候,同时实现其他接口呢?

继承的父类,就好比是亲爸爸一样
实现的接口,就好比是干爹一样
可以继承一个类的同时,再实现多个接口,只不过,要把接口里面所有的抽象方法,全部实现。

  1. 实现类能不能继承一个抽象类的时候,同时实现其他接口呢?

实现类可以继承一个抽象类的同时,再实现其他多个接口,只不过要把里面所有的抽象方法全部重写。

  1. 实现类Zi,实现了一个接口,还继承了一个Fu类。假设在接口中有一个方法,父类中也有一个相同的方法。子类如何操作呢?

处理办法一:如果父类中的方法体,能满足当前业务的需求,在子类中可以不用重写。
处理办法二:如果父类中的方法体,不能满足当前业务的需求,需要在子类中重写。

  1. 如果一个接口中,有10个抽象方法,但是我在实现类中,只需要用其中一个,该怎么办?

可以在接口跟实现类中间,新建一个中间类(适配器类)
让这个适配器类去实现接口,对接口里面的所有的方法做空重写。
让子类继承这个适配器类,想要用到哪个方法,就重写哪个方法。
因为中间类没有什么实际的意义,所以一般会把中间类定义为抽象的,不让外界创建对象

新特性

JDK8以后接口新增的方法-默认方法

  • 概念

    • 允许在接口中定义默认方法,需要使用关键字default修饰
    • 作用:解决接口升级后,实现类未重写新升级的方法而报错的问题
  • 格式

    1
    2
    3
    public default 返回值类型 方法名(参数列表){

    }
  • 注意事项

    • 默认方法不是抽象方法,所以不强制被重写,但是如果被重写,重写的时候去掉default关键字
    • public可以省略,default不能省略
    • 如果实现了多个接口,多个接口存在相同名字的默认方法,子类就必须对该方法进行重写

JDK8以后接口新增的方法-静态方法

  • 允许在接口中定义静态方法,需要用static修饰

  • 格式

    1
    2
    3
    public static 返回值类型 方法名(参数列表){

    }
  • 注意事项

    • 静态方法只能通过接口名调用,不能通过实现类名或者对象名调用
    • public可以省略,static不能省略

JDK9以后接口新增的方法-私有方法

  • 格式

    1
    2
    3
    4
    5
    6
    7
    8
    9
    //格式1 --供默认方法使用
    private 返回值类型 方法名(参数列表){

    }

    //格式2 --供静态方法使用
    private static 返回值类型 方法名(参数列表){

    }

接口的应用

  • 接口代表规则,是行为的抽象。想要让哪个类拥有一个行为,就让这个类实现对应的接口就行
  • 当一个方法的参数是接口时,可以传递接口所有实现类的对象,这种方式称之为接口多态

适配器设计模式

作用

  • 解决接口和接口实现类之间的矛盾问题:接口实现类不想重写接口中的所有抽象方法

步骤

  • 编写中间类XXXAdapter,实现对应的接口
  • 对接口中的抽象方法进行空实现
  • 让真正的接口实现类继承中间类,并重写需要用的方法
  • 为了避免其他类创建适配器类的对象,中间的适配器类用abstract修饰
  • 如果真正的实现类有其他的父类,由于java不能多继承故可以让中间类继承真正实现类的父类,达到间接继承

内部类

概述

  • 内部类表示的事务是外部类的一部分,内部类单独出现没有任何意义,比如汽车内有一个发动机

访问特点

  • 内部类可以直接访问外部类的成员,包括私有
  • 外部类要访问内部类的成员,必须创建对象,用内部类对象访问内部类的成员

分类

  • 成员内部类
  • 静态内部类
  • 局部内部类
  • 匿名内部类

成员内部类

  • 写在成员位置的,属于外部类的成员

获取方式

  • 方式一
    • 当成员内部类被private修饰时,在外部类编写方法,返回内部类对象,达到对外提供内部类对象(类似set/get方法)
  • 方式二
    • 当成员内部类被非私有修饰时,直接创建对象:Outer.Inner oi = new Outer().new Inner();

注意

  • 外部类成员变量和内部类成员变量重名时,在内部类如何访问?Sout(Outer.this.变量名)
  • 内部类访问外部类的方法时,要用Outer.this.方法名()

静态内部类

获取方式

  • Outer.Inner oi = new Outer.Inner()

调用静态内部类中的方法

  • 非静态方法:先创建对象,用对象调用
  • 静态方法:外部类名.内部类名.方法名();

注意

  • 静态内部类可以直接访问外部类的静态成员。
  • 静态内部类不可以直接访问外部类的非静态成员,如果要访问需要创建外部类的对象。
  • 静态内部类中没有Outer.this,内外部类变量重名要访问外部类变量使用:外部类.变量名

局部内部类

1
2
3
4
5
6
7
8
9
10
11
class 外部类名 {
数据类型 变量名;

修饰符 返回值类型 方法名(参数列表) {
// …
class 内部类 {
// 成员变量
// 成员方法
}
}
}

匿名内部类:star:

概念

  • 隐藏了名字的内部类,可以写在成员位置,也可以写在局部位置

格式

1
2
3
new 类名/接口名() {
重写方法;
}

格式的细节

  • 包含了继承或实现,方法重写,创建对象。
  • 整体就是一个类的子类或者接口的实现类对象

使用场景

  • 当方法的参数是接口或者类时,以接口为例,可以传递这个接口的实现类对象,如果实现类只要使用一次,就可以用匿名内部类简化代码

泛型

概述

  • 泛型的介绍

    ​ 泛型是JDK5中引入的特性,它提供了编译时类型安全检测机制

  • 泛型的好处

    1. 把运行时期的问题提前到了编译期间
    2. 避免了强制类型转换
  • 用于约束集合中存储元素的数据类型

细节

  • 泛型中不能写基本数据类型

  • 指定泛型的具体类型后,传递数据时,可以传入该类型或者其子类类型

  • 如果不写泛型,类型默认是Object,这样多态的弊端就显现出来:不能调用子类特有的功能

  • 不能直接在静态方法中使用类的泛型类型参数,但静态方法本身可以声明自己的泛型类型参数

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    public class MyClass<T> {

    // 静态方法不能直接使用类的泛型参数 T
    // public static void print(T value) {
    // System.out.println(value);
    // }

    // 静态方法可以定义自己的泛型参数
    public static <U> void print(U value) {
    System.out.println(value);
    }
    }
    //因为静态方法属于类本身而不是类的实例。但静态方法可以声明自己的泛型参数,从而实现与泛型相关的功能。

泛型类

  • 当定义一个类时,某个变量的类型不确定,就可以使用带有泛型的类

    1
    2
    3
    修饰符 class 类名<E>{

    }

泛型方法

  • 当方法中形参类型不确定的时候,可以使用泛型

  • 只能在本方法中使用

    1
    2
    3
    修饰符<E> 返回类型 方法名(参数...){

    }

泛型接口

  • 当接口中类型不确定的时候,可以使用泛型

    1
    2
    3
    修饰符 interface 接口名<E>{

    }

泛型接口的使用

  • 实现类给出具体的接口

    1
    2
    3
    public class MyList implements List<此处给出具体的类型> {

    }
  • 实现类延续泛型,创建对象时在确定

    1
    2
    3
    4
    5
    public class MyList<E> implements List<E> {

    }

    MyList<此处给出具体的类型> list = new MyList<>{};

泛型的不可继承性和通配符

不可继承性

  • 泛型不具备继承性(一旦E的类型确定,便只能传递此类型),但是数据具备继承性

    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
    public class test {
    public static void main(String[] args) {

    ArrayList<Ye> list1 = new ArrayList<>();
    ArrayList<Fu> list2 = new ArrayList<>();
    ArrayList<Zi> list3 = new ArrayList<>();

    //泛型不具备继承性
    method(list1);
    method(list2); // 报错,只能传递ArrayList<Ye>
    method(list3); // 报错,只能传递ArrayList<Ye>

    //数据具备继承性
    list1.add(new Fu()); // 可以将子类对象传递至父类集合中


    }

    public static void method(ArrayList<Ye> list){

    }
    }

    class Ye{}
    class Fu extends Ye{}
    class Zi extends Fu{}
  • 那么此时我就是想要用method方法添加ArrayList和 ArrayList集合应该怎么办?

    1
    2
    3
    4
    5
    6
    7
    8
    //对method方法进行修改
    public static<E> void method(ArrayList<E> list){

    }

    //但是这样有一个弊端,method方法不仅ArrayList<Ye/Fu/Zi>可以接收,而且ArrayList<Student>也可以接收
    //那我就是只想接收ArrayList<Ye/Fu/Zi>,应该怎么办呢?
    //泛型的通配符 可以解决

泛型的通配符

  • 概述

    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
    ?也表示不确定的类型,它可以进行类型的限定
    ? extends E: 表示可以传递E或者E所有的子类类型
    ? super E: 表示可以传递E或者E所有的父类类型

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

    ArrayList<Ye> list1 = new ArrayList<>();
    ArrayList<Fu> list2 = new ArrayList<>();
    ArrayList<Zi> list3 = new ArrayList<>();
    ArrayList<Student> list4 = new ArrayList<>();

    //泛型不具备继承性
    method(list1);
    method(list2);
    method(list3);

    method(list4); // 报错,因为使用通配符后,只能传递Ye和它所有的子类类型
    }

    //注意:在方法中使用泛型的通配符时,修饰符后面的<E>去掉不用写
    public static void method(ArrayList<? extends Ye> list){

    }
    }

    class Student{}
    class Ye{}
    class Fu extends Ye{}
    class Zi extends Fu{}
  • 应用场景

    1
    2
    3
    1.如果我们在定义类、方法、接口的时候,如果类型不确定,就可以定义泛型类、泛型方法、泛型接口
    2.如果类型不确定,但是能知道以后只能传递某个继承体系中的,就可以使用泛型的通配符
    关键点:泛型的通配符可以限定类型的范围