主页 > 互联网  > 

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

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 { }
标签:

Java中的泛型类--为集合的学习做准备由讯客互联互联网栏目发布,感谢您对讯客互联的认可,以及对我们原创作品以及文章的青睐,非常欢迎各位朋友分享到个人网站或者朋友圈,但转载请说明文章出处“Java中的泛型类--为集合的学习做准备