当前位置: 首页 > news >正文

Java中的泛型类 --为集合的学习做准备

学习目标

● 掌握在集合中正确使用泛型
● 了解泛型类、泛型接口、泛型方法
● 了解泛型上下限
● 了解基本的使用场景

1.有关泛型

1.1泛型的概念

泛型(Generics)是Java中引入的参数化类型机制,允许在定义类、接口或方法时使用类型参数(如),实际类型在使用时才指定。其本质是通过类型参数化,增强代码的灵活性和安全性。

● 我们将要在集合中接触泛型。了解泛型,为学习集合做知识准备
● 泛型在集合中用于类型检查,那么为什么集合中 一定要用到泛型?
○ 若没有泛型的使用,那么在设计集合类型时,只能确定集合用来装对象,但是无法确定装什么类型的对象,即集合的元素类型未知。在使用时,编译器就无法进行更加具体的类型检查,存在类型安全问题;
○ 从集合中取出的集合元素对象,为了调用该元素对象的非Object类的方法,不得不向下转型,就存在ClassCastException的风险存在,代码繁琐的问题;
● 有了泛型的使用,既能保证安全,又能简化代码。
● 因为如果通过了编译,那么类型一定是符合要求的,因此就避免了类型转换;

● <类型>这种语法形式就叫泛型。
○ <类型>代表未知的数据类型,我们可以指定为String,Student等。相当于把某个具体的类型泛化为一般的类型,所以称为泛型,或者成为参数化数据类型。参数化类型必须是引用数据类型。
○ <类型> 一般使用一个泛型符号表示;泛型符号只是一个占位符 先占着位置,给引用类型占位置。
○ 泛型符号,名称不要求,个数不要求。 在使用的时候当成已知类型来进行使用。一般使用大写字母A-Z表示。
○ 泛型符号如果不指定 统统都是Object类型

1.2 泛型的分类

泛型符号,可以出现在类声明处 、接口声明处、方法定义中。
● 泛型在类声明处使用,一般称为泛型类
● 泛型在接口声明处使用,一般称为泛型接口
● 泛型在方法定义中使用,一般称为泛型方法

1.3泛型的作用

● 类型安全:编译时检查类型一致性,避免运行时因类型转换导致的ClassCastException
● 泛型核心作用: 实现数据类型的自动转换,避免出现强制转换(较少出现类型转换的异常)。
● 弊端: 泛型只在编译期间有效,运行期间泛型擦除,底层还是会将每一个参数化类型改为Object

2.泛型类

在类名后声明类型参数,用于成员变量或方法中
● 泛型符号 可以出现在 类声明处
● 泛型符号 在本类中当成已知类型来进行使用
● 静态方法中不能出现 类的泛型符号

2.1 创建泛型类

//泛型里面: <>里面必须编写参数化数据类型。引用数据类型  默认值null
public class MyClass<S, E> {
    //S,E就是2个参数化数据类型。具体什么类型我们是不清楚的,要看使用的时候,传过来的数据类型。
    //使用MyClass,没有传递S,E的真实的数据类型,S,E默认为Object。
    //S,E是一个数据类型,肯定可以作为形参,返回值,属性的数据类型。
}

2.2 使用泛型类

  • 创建泛型类对象,不指定参数化类型
private static void method1() {
    //调用MyClass里面的属性+方法。---> 面向对象编程
    //还会报警告。对于泛型类、接口 建议指定参数化数据类型
    //1.创建MyClass类对象
    MyClass myClass1 = new MyClass();
    //使用MyClass时候 没有指定S,E的真实数据类型。Object
    myClass1.setName("张三");
    String name = myClass1.getName();

    myClass1.setS(100);
    int s = (int) myClass1.getS();
    //对s进行类型强制转换----> 会有几率出现ClassCastException

    myClass1.setS("hello");
    String str = (String) myClass1.getS();

    System.out.println(myClass1.demo1(new int[]{1, 2, 4}));
}
  • 创建泛型类对象,指定参数化类型
private static void method2() {
    //创建泛型类对象 指定参数化数据类型(引用数据类型)
    //MyClass<Integer, String> myClass1 = new MyClass<Integer, String>();
    //jdk1.7+之后的写法
    MyClass<Integer, String> myClass2 = new MyClass<>();
    //S:Integer
    //E:String
    myClass2.setName("李四");
    String name = myClass2.getName();

    myClass2.setS(100);
    Integer s = myClass2.getS();

    Integer hello = myClass2.demo1("hello");
}

3.泛型方法

● 任意一个类/接口中,都可以存在泛型方法。 就是在方法上使用<>修饰方法。<>里面的参数化类型就可以是任意数据类型。
● 泛型符号 只出现本方法的声明处
● 泛型符号的声明 在返回值的前面

3.1 创建泛型方法

  • 参数化类型仅仅作为形参/返回值使用,不是泛型方法
//S 参数化数据类型 作为返回值类型使用
public S getS() {
    return s;
}
//S作为形参数据类型使用。
public void setS(S s) {
    this.s = s;
}
//getS  setS不是泛型方法,仅仅是参数化数据类型作为形参/返回值。

//普通的方法
//1.非静态的
public S demo1(E e) {
    System.out.println("demo1............e:" + e);
    return s;
}

//以上方法仅仅是普通方法 只不过是参数化类型作为形参和返回值使用而已。
//具体形参和返回值是什么类型,要看创建对象时指定的数据类型,因为这些属性/方法,称为实例变量/方法,必须对象访问。
  • 存在类中的泛型方法
//泛型里面: <>里面必须编写参数化数据类型。引用数据类型  默认值null
public class MyClass<S, E> {
    //普通的方法
    //1.非静态的
    public S demo1(E e) {
        System.out.println("demo1............e:" + e);
        return s;
    }

    //2.静态的方法---->类名.静态
    //静态方法里面使用参数化数据类型  必须将这个方法标识为"泛型方法"
    public static <A,Z> void staticMethod(A s1) {
        System.out.println("s1:" + s1);
    }
    //在任意一个类中  静态方法里面使用了参数类型  这个方法一定是泛型方法
    //普通的功能方法: 也可以是一个泛型方法
    public <B> void demo2(B b){
        System.out.println("b:"+b);
    }
}
  • 存在接口中的泛型方法
public interface MyInterface<K, S> {

    default <T> void demo1(T t) {
        System.out.println("t:" + t);
    }

    static <E> int demo2(E e) {
        return 100;
    }
}

3.2 使用泛型方法

private static void method3() {
    //调用staticMethod方法
    /*MyClass<String,String> myClass = new MyClass<>();
        myClass.staticMethod(100.0);*/

    MyClass.staticMethod("hello");

    MyClass<String,String> myClass = new MyClass<>();
    myClass.demo2(100);
}
private static void method3() {
    System.out.println(MyInterface.demo2("hello"));
}

4.泛型接口

接口定义时声明类型参数,实现类需指定具体类型或保留泛型
● 泛型符号 可以出现在 接口声明处
● 在本接口中当成 已知类型来使用

4.1 创建泛型接口

public interface MyInterface<K, S> {
    //封装行为
    //在接口中  定义方法的时候  可以让参数化类型作为形参/返回值使用
    S method1();
    void method2(K k);

    //接口中可以有泛型方法  一般都是静态的
    //自己很少编写
    public static <A> void aa(A a) {
    }
    public default <B> void bb(B b) {
    }
}
public interface GenericInterface<A, B, C> {
    void methodA(A a);
    void methodB(B b);
    void methodC(C c);
}

4.2 使用泛型接口

//封装/设计: 一个类实现类泛型接口  这个类一般也是一个泛型类。
public class MyInterfaceImpl<K,S> implements MyInterface<K,S>{
    @Override
    public S method1() {
        return null;
    }

    @Override
    public void method2(K k) {

    }
}

class Test{
    public static void main(String[] args) {
        MyInterface<Integer,String> myInterface = new MyInterfaceImpl<>();
    }
}
//一般在设计中  我们很少这样写
/**
 * 实现类取实现泛型接口 直接固定类型
 */
public class InterfaceImpl1 implements GenericInterface<String, Integer, Double> {
    @Override
    public void methodA(String s) {

    }

    @Override
    public void methodB(Integer integer) {

    }

    @Override
    public void methodC(Double aDouble) {

    }
}
/**
 * 实现类 也带上泛型符号
 * @param <A>
 * @param <B>
 * @param <C>
 */
public class InterfaceImpl3<A, B, C> implements GenericInterface<A, B, C> {
    private A a;

    @Override
    public void methodA(A a) {
    }

    @Override
    public void methodB(B b) {
    }
    @Override
    public void methodC(C c) {
    }
}

5.泛型上下限

通配符与上下界

  • 通配符<?>:表示未知类型,用于接受任意泛型类型:
public void printList(List<?> list) { /* 可处理List<String>、List<Integer>等 */ }
  • 上界<? extends T>:限定类型为T或其子类,用于读取操作:
List<? extends Number> numbers = new ArrayList<Double>(); // 允许Double
Number num = numbers.get(0); // 安全读取
  • 下界<? super T>:限定类型为T或其父类,用于写入操作:
List<? super Integer> list = new ArrayList<Number>();
list.add(123); // 允许写入Integer

5.1 问题

  • 创建父类Animal
public class Animal {

    public void eat() {
    }
}
  • 创建子类 Dog 重写 eat()方法
public class Dog extends Animal {
    @Override
    public void eat() {
        System.out.println("狗喜欢吃骨头");
    }
}
  • 创建饲养员类
public class Feeder {
    public void feed(List<Animal> animals) {
        for (Animal animal : animals) {
            animal.eat();
        }
    }
}
  • 测试:饲养员喂食不同小动物
public static void main(String[] args) {
        Feeder feeder = new Feeder();
        List<Animal> animals = new ArrayList<>();
        animals.add(new Dog());
        animals.add(new Cat());

        List<Dog> dogList = new ArrayList<>();
        dogList.add(new Dog());
        feeder.feed(animals);
        //编译报错,dogList不能传入 
        feeder.feed(dogList);
    }

5.2 原因

● 泛型类型的指定上和多态并不相同,它要求两边类型必须一致 才能使用,因此如何解决上述问题,因为在实际业务开发中,子类集合也应该是可以传入的,此时就需要使用泛型上限进行解决;
● ? 通配符 代表任意类型;
○ 完整形式为:类名 或接口名,此时?代表上限类型本身或者上限的子类,即?代表

5.3 解决

/**
 * 饲养员类
 */
public class Feeder {
    /**
     * @param animals 泛型的上限
     *                ? 通配符
     */
    public void feed(List<? extends Animal> animals) {
        for (Animal animal : animals) {
            animal.eat();
        }
    }

    /** 
     * 指定了下限 下限就到Animal
     * 上不要求 所有Animal父类型都可以传
     */
    public void method1(List<? super Animal> animals) {
        for (Object animal : animals) {
//            animal.eat();
        }
    }
}

5.4 测试

public static void main(String[] args) {
        Feeder feeder = new Feeder();
        List<Animal> animals = new ArrayList<>();
        animals.add(new Dog());
        animals.add(new Cat());

        List<Dog> dogList = new ArrayList<>();
        dogList.add(new Dog());

        List<Object> objectList = new ArrayList<>();
        // 泛型上限
        feeder.feed(dogList);
        feeder.feed(animals);
        // 泛型下限
        feeder.method1(animals);
        feeder.method1(objectList);
    }

6.泛型擦除

public static void main(String[] args) {

        List<Integer> list = new ArrayList<>(10);
        list.add(100);
        list.add(200);
        //我就是想存一个“hello”到list集合中
        //编译的class文件中 不存在List<Integer> 只有List<Object>
        //泛型在运行期间会被擦除 还是Object类型
        try {
            Method addMethod = list.getClass().getMethod("add", Object.class);
            System.out.println(addMethod.invoke(list,"hello"));
        } catch (Exception e) {
            e.printStackTrace();
        }
        System.out.println(list);
    }

7.使用场景

泛型类以及泛型接口的出现一般都是满足项目的整体设计。

7.1 泛型类

● 满足与整体的项目功能设计。通用的操作。
● 需求: 模拟前端请求,触发一个按钮的功能。后端就要返回固定格式数据

成功的数据:
   status
   msg
   data
    
失败的数据:
  status
  msg
      
满足所有的模块数据封装。    

1.普通类编写

  • 创建MyResult类
@Setter
@Getter    
public class MyResult {

    private int status;
    private String msg;
    private Object data;//查询成功的数据  数据类型不定的

    public MyResult(int status, String msg, Object data) {
        this.status = status;
        this.msg = msg;
        this.data = data;
    }

    public MyResult(int status, String msg) {
        this.status = status;
        this.msg = msg;
    }
}
  • 测试不同模块功能
private static MyResult testFindOneProduct() {
    ProductDao productDao = new ProductDaoImpl();
    Product product = productDao.findOne(1L);
    if (product == null) {
        return new MyResult(StatusEnum.ERROR.getStatus(), StatusEnum.ERROR.getMsg());
    }

    //状态码 msg  product
    return new MyResult(StatusEnum.SUCCESS.getStatus(), StatusEnum.SUCCESS.getMsg(), product);
}

private static MyResult testFindAllUser() {
    //模拟测试查询所有用户
    SysUserDao sysUserDao = new SysUserDaoImpl();
    SysUser[] users = sysUserDao.findAll();
    if (users == null || users.length == 0) {
        //查询失败  状态码  msg
        return new MyResult(StatusEnum.ERROR.getStatus(), StatusEnum.ERROR.getMsg());
    }
    //查询成功
    //状态码  msg  查询所有成功的数据  users
    return new MyResult(StatusEnum.SUCCESS.getStatus(), StatusEnum.SUCCESS.getMsg(), users);
}

private static MyResult testFindOneUser() {
    //模拟测试查询单个用户
    SysUserDao sysUserDao = new SysUserDaoImpl();
    SysUser sysUser = sysUserDao.findOne(1);
    if (sysUser == null) {
        //查询失败  状态码  msg
        return new MyResult(StatusEnum.ERROR.getStatus(), StatusEnum.ERROR.getMsg());
    }
    //查询成功
    //状态码  msg  查询成功的数据  sysUser
    return new MyResult(StatusEnum.SUCCESS.getStatus(), StatusEnum.SUCCESS.getMsg(), sy
  • 测试
public static void main(String[] args) {
    //前端触发了对用户模块的增删改查
    //成功/失败
    MyResult result = test2();
    int status = result.getStatus();
    String msg = result.getMsg();
    Object data = result.getData();//查询单个用户对象
    System.out.println(status);
    System.out.println(msg);
    if(status==200){
        SysUser sysUser = (SysUser) data;
        System.out.println(sysUser.getId());
        System.out.println(sysUser.getName());
    }
    System.out.println(data);

    System.out.println("----------------------------------");
    MyResult myResult = test4();
    int status1 = myResult.getStatus();
    if(status1==200){
        Object data1 = myResult.getData();
        Product product = (Product) data1;
    }

}

以上代码封装数据完全ok

  • 弊端: 每一次获得data 都要向下转型 会有几率出现类型转换的异常
    所以getData的时候 自动的转换成想要的数据类型想到使用 泛型

2.泛型类编写

  • 创建MyResult类
//T: 满足查询成功之后的数据的封装
//封装成功之后的任意类型的封装。
@Setter
@Getter  
public class ReturnResult<T> {

    private int status;
    private String msg;
    private T data;

    private ReturnResult(int status, String msg, T data) {
        this.status = status;
        this.msg = msg;
        this.data = data;
    }

    private ReturnResult(int status, String msg) {
        this.status = status;
        this.msg = msg;
    }
    
    public static <T> ReturnResult<T> success(T data){
        return new ReturnResult<>(StatusEnum.SUCCESS.getStatus(),StatusEnum.SUCCESS.getMsg(),data);
    }
    public static <T> ReturnResult<T> error(){
        return new ReturnResult<>(StatusEnum.ERROR.getStatus(),StatusEnum.ERROR.getMsg());
    }

}
  • 测试模块功能
public static ReturnResult<Product> testFindOneProduct(){
        ProductDao productDao = new ProductDaoImpl();
        Product product = productDao.findOne(1L);
        if(product==null){
            return ReturnResult.error();
        }
        return ReturnResult.success(product);
    }

    //测试查询单个用户
 public static ReturnResult<SysUser> testFindOneUser() {
        SysUserDao sysUserDao = new SysUserDaoImpl();
        SysUser sysUser = sysUserDao.findOne(1);
        if (sysUser == null) {
            return ReturnResult.error();
        }
        return ReturnResult.success(sysUser);
    }

7.2 泛型接口

//开发XXXX管理系统:
//1.系统用户模块----> 增加/删除/修改/查询用户 与角色/权限相关的一些行为
//2.订单模块----> 
//3.商品模块----> 
//......
//提高程序的扩展性。多态----> 继承类与类   实现 类与接口

//有3个实体类封装每个对象具备的信息
//有3个接口封装每个模块里面行为:
  • 对子接口的抽象: 封装模块里面共有的行为。

//E就是实体类型 T:id的数据类型
public interface BaseDao<E,T> {

    //封装的很多模块具备的一些行为
    void insert(E entity);

    void delete(T id);

    void update(E entity);

    E findOne(T id);

    E[] findAll();//还没讲解集合 先使用数组编写
}
  • 对共有的行为的实现进行 封装。
public abstract class BaseDaoImpl<E,T> implements BaseDao<E,T> {
    @Override
    public void insert(E entity) {

    }

    @Override
    public void delete(T id) {

    }

    @Override
    public void update(E entity) {

    }

    @Override
    public E findOne(T id) {
        return null;
    }

    @Override
    public E[] findAll() {
        return null;
    }
}
  • 具体子模块的编写
public interface SysUserDao extends BaseDao<SysUser,Integer> {

    //编写用户模块特有的行为
    void userLogout();

}
public class SysUserDaoImpl extends BaseDaoImpl<SysUser,Integer> implements SysUserDao {

    //编写用户模块特有行为的实现
}
public interface ProductDao extends BaseDao<Product,Long> {

    //维护每个模块独有的行为
}
  public class ProductDaoImpl extends BaseDaoImpl<Product,Long> implements ProductDao {

  }

相关文章:

  • AI时代保护自己的隐私
  • 【开发心得】SpringBoot Oauth2授权登录
  • 每日一题----------类变量
  • 靶场之路-Kioptix Level-5
  • path 路径模块
  • 【Java】Stream API
  • 数据集笔记:新加坡LTA MRT 车站出口、路灯 等位置数据集
  • 开源绝版经典小游戏合集
  • 第三十四:6.4.【v-model】
  • Kali换源
  • 【Linux高级IO】多路转接(poll epoll)
  • 第49天:Web开发-JavaEE应用SpringBoot栈模版注入ThymeleafFreemarkerVelocity
  • Oracle 认证为有哪几个技术方向
  • python中如何组织项目工程文件
  • 一文学会Volatile关键字
  • DeepSeek 开源周:第六天的“One More Thing” – DeepSeek-V3/R1 推理系统的概述
  • 【Web Cache Deception简介】
  • 将QT移植到RK3568开发板
  • HarmonyOS学习第11天:布局秘籍RelativeLayout进阶之路
  • 旁路挂载实验
  • 企业主页包含/seo的定义
  • 做网站开什么发票/推广图片制作
  • 购房网站系统建设方案/福州seo优化
  • 职业技能培训学校/优化公司治理结构
  • 网站建设汇报评估/太原网络营销公司
  • 可以做ppt的网站或软件/青岛网站建设微动力