Java面试题-Java核心基础-第九天(泛型)

目录

一、泛型的理解

二、泛型的作用

三、泛型有哪些使用方式

四、上限、下限通配符的使用

五、泛型的原理


一、泛型的理解

泛型在jdk5中开始有的,泛型其实就是将类型进行参数话,使得类型在编译时就确定了,这种类型参数可以用在类、接口、方法上面

泛型的初衷是为了安全和方便的 安全是类型转换安全 方便是指可以不需要类型转换了

它的这种安全体现在如果说编译的时候能够检测出来问题,最好,就不要拖到运行的时候再来出错

有句话正好贴合这里:越早出错,代价越好

例子:

Object s= new String();

Integer s1 = (Interger)s;这个编译的时候不会出错,但是实际运行会出现ClassCastException类型转换异常。

如果说使用了泛型的话,那么是那种类型就得是哪种类型

List<String> list = new ArrayList();

实现安全就体现在添加元素上面,其次如果取元素,那么就只能使用正确的类型进行接收,如果类型不一致接收都接收不了,不能强转成非法的类型

二、泛型的作用

其实作用大的来说就两点:安全 + 方便

安全:避免错误的强转,比如说现在定义一个泛型类型为String类型的集合,那么从中取出来元素就不能强转成其他类型,否则就会报错。如果说它让你强转的话,那么在运行时会出错

参考上面理解中的例子

方便:其实就是不需要手动的强转了,自动的为我们强转...这里的例子,比如说方法返回值接收

当然除了这两点主要的,还有其他的一些好处:

  1. 如果说使用了包装类型那么就根本就不需要拆箱与装箱

其实原因就在于指定了上面类型就会自动的返回什么类型 而不会像方法那么传递一个Integer返回一个int造成需要拆箱了

  1. 提高了代码的重用性,需要什么类型我就指定好什么类型就行了。比如说一个求和的方法,我不再需要写多个重载的方法了,参数传递什么类型 结果就能是什么类型

三、泛型有哪些使用方式

泛型类

java 复制代码
//此处T可以随便写为任意标识,常见的如T、E、K、V等形式的参数常用于表示泛型
//在实例化泛型类时,必须指定T的具体类型
public class Generic<T>{

    private T key;

    public Generic(T key) {
        this.key = key;
    }

    public T getKey(){
        return key;
    }
}

典型应用:用在接口统一结果返回对象

泛型方法

java 复制代码
  public static < E > void printArray( E[] inputArray )
   {
         for ( E element : inputArray ){
            System.out.printf( "%s ", element );
         }
         System.out.println();
    }

泛型方法不等于泛型类中的那个使用泛型属性的方法。那个有局限性:

我如果想要换一种类型,是不是就得重新创建另外一种类型的对象,不方便

典型应用:集合工具类中的排序方法,是需要对各种类型的集合都能排序的

如果是没有泛型方法,那么每次对一个新的类型的集合进行排序,就需要创建一个新的工具类对象

不够灵活

当然还有许多地方:

很多时候我们是需要直接就能得到对应的对象,而不想让得到一个Object类对象,我们再去进行强转。

可以使用泛型方法,参数就写Class<T> clazz

然后在方法中强转 成T类型

java 复制代码
public static <T> T copyBean(Object source,Class<T> clazz){
        T result = null;
        try {
            result = clazz.newInstance();
            BeanUtils.copyProperties(source,result);
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
        return result;
    }
java 复制代码
 public static <T> List<T> copyBean(List<?> list,Class<T> clazz){
        return list.stream()
                .map(o->copyBean(o,clazz))
                .collect(Collectors.toList());
    }
java 复制代码
public <T> T getData(TypeReference<T> typeReference) {
		Object data = get("data");	//默认是map
		String jsonString = JSON.toJSONString(data);
		T t = JSON.parseObject(jsonString, typeReference);
		return t;
	}
java 复制代码
public <R,ID> R queryShopWithCacheThrough(String cachePrefix, ID id, Class<R> type, Function<ID,R> dbFallBack,Long time, TimeUnit timeUnit){
        //1. 从redis中查询缓存
        String key = cachePrefix + id;
        String json = stringRedisTemplate.opsForValue().get(key);
        //2. 判断是否存在
        if(StrUtil.isNotBlank(json)){
            //3. 若存在,则直接返回
            R r = JSONUtil.toBean(json, type);
            return r;
        }
        if(json!=null){
            // 查到空数据,直接返回错误
            return null;
        }
        //4. 若不存在,则查询数据库
        R r = dbFallBack.apply(id);
        //5. 不存在,返回错误
        if (r == null) {
            //缓存空数据
            stringRedisTemplate.opsForValue().set(key,"",RedisConstants.CACHE_NULL_TTL, TimeUnit.MINUTES);
            return null;
        }
        //6. 存在,存入redis
        stringRedisTemplate.opsForValue().set(key,JSONUtil.toJsonStr(r),time,timeUnit);
        //7. 返回
        return r;
    }

泛型接口:

java 复制代码
public interface Generator<T> {
    public T method();
}

实现泛型接口,不指定类型:

java 复制代码
class GeneratorImpl<T> implements Generator<T>{
    @Override
    public T method() {
        return null;
    }
}

典型应用:

在MP中的mapper接口、service接口都使用到了泛型接口 :

MP中的mapper接口和service接口使用的就是上面那种 通过指定实体类的类型,从而确定是对哪张表进行操作,所以此时不能再传递参数的时候传递其他表对应的实体类了,以及wrapper也是

java 复制代码
public interface CourseBaseMapper extends BaseMapper<CourseBase> {

}
java 复制代码
public interface BaseMapper<T> extends Mapper<T> {
    int insert(T entity);
}

service:

java 复制代码
public interface CourseBaseService extends IService<CourseBase> {}
java 复制代码
public interface IService<T> {
    int DEFAULT_BATCH_SIZE = 1000;

    default boolean save(T entity) {
        return SqlHelper.retBool(this.getBaseMapper().insert(entity));
    }
}

四、上限、下限通配符的使用

首先介绍? 这个代表所有 是所有 T 的根父类

比如说我可以这样 List<?> list = new ArrayList();

List<String> list1 = new ArrayList();

将list1赋值给list list = list1 这样不会报错

但是如果我是 List<Object> list = new ArrayList();

List<String> list1 = new ArrayList(); 那么就会报错。

小插一句:这里如果是 Person<T> p = new Person(); Student<T> s = new Student();

那么可以将 s 赋值给 p p = s 这里就是单纯的子父类了

原因在于相当于 如果这样可以的话 相当于集合中现在就只能添加String类型的元素了,相当于就是可添加的范围减少了,显然这是不合理的,另外还有就是你声明的类型是 Object而实际的类型是String 那我如果取元素,那么取出来的元素到底应该是String还是Object呢?其实就说不清了,存也是一样,我到底是存String还是存Object呢?

其实通配符的作用就来了,使用?就可以代替Object 就可以接收任意泛型类型

如果只是使用? 那么就是对泛型类型没有约束,什么类型都可以。但是如果你想要指定的范围,比如说我要求你给我的泛型类型必须是某一个类的父类或者是子类 ,那么就可以使用带范围的通配符。

<? extends Person>这个代表泛型类型只能是Person类型或者是其子类 也就是规定了上限

<? super Person>这个代表泛型类型只能是Person类型或者是Person的父类,规定了下限

那么方法参数的泛型类型就必须老实按规定来。

注意:

当集合类型的泛型定义为通配符的时候,是不能往里面添加元素的。只能存null 只能取元素

比如说:

List<?> list = new ArrayList(); list.add("avc") 这是会编译报错的。但是可以存null

这里看似应该是可以传递的,因为是?可以代表是Object的意思,但是为什么不能传,只能存null可能就是它这么规定的吧。

这个获取到集合中的元素,元素类型是Object类型

另外还有有范围的,比如说:

List<? extends Person> list = new ArrayList<>()

也只能存null,这个好理解,因为怕我存Person的父类对象进去了,为了安全起见,干脆就只能让你存null进去,这个因为无论怎样元素一定是Person类型或者是其子类类型,那么所以就完全使用Person类就可以接收

反过来如果是super,它怕你存进去Person的子类所以直接不让你存,就只能让你存null

这里因为不知道存进去的到底有多大,因此这个取出来直接使用根父类Object类接收

使用例子:

java 复制代码
public static <T> List<T> copyBean(List<?> list,Class<T> clazz){
        return list.stream()
                .map(o->copyBean(o,clazz))
                .collect(Collectors.toList());
    }

因为这里我不确定到底是需要对什么类型的集合进行拷贝,但是这里又不能写死成Object,所以就只能使用?通配符了,当然也可以使用 T的方式,那么相当于就多了一个泛型参数,所以在前面定义的时候也需要多给一个,就是下面这样:

java 复制代码
public static <T,O> List<T> copyBean(List<O> list,Class<T> clazz){
        return list.stream()
                .map(o->copyBean(o,clazz))
                .collect(Collectors.toList());
    }

五、泛型的原理

原理就是泛型擦除,其实泛型信息只会在编译时候保持,运行时就全没了,字节码中其实有泛型和没泛型的是一样的 如果没有设定范围 那么会被擦除成Object类型 如果给定了范围 那么擦除之后就是对应类型的 所以在编译的时候可以使用到编译器进行检查,因此这里不涉及到JVM,因此泛型是几乎不消耗性能的,所以使用泛型的另外一个作用就是:潜在的性能收益

相关推荐
述雾学java24 天前
视图、MySQL、触发器、存储过程、流程控制语句
开发语言·mysql·java核心基础
述雾学java1 个月前
数据库分类、存储引擎、介绍、Mysql、SQL分类
sql·mysql·java核心基础
述雾学java1 个月前
JavaWeb,Tomcat基本思想,手写Tomcat
java·tomcat·java核心基础
述雾学java1 个月前
反射(第三篇)、代理模式、静态代理和动态代理、InvocationHandler实际应用
java·代理模式·java核心基础
述雾学java1 个月前
反射、 Class类、JVM的类加载机制、Class的常用方法
java·java基础·java核心基础
Java 第一深情4 个月前
面试题解,JVM的运行时数据区
jvm·java面试
非学无以质疑4 个月前
Java中的SPI机制
java·后端·编程语言·java面试
linweidong6 个月前
宁德时代Java面试题及参考答案
java·大厂面试·后端面试·java面试·java面经·java校招·后端offer
linweidong7 个月前
作业帮大数据面试题及参考答案
大厂面试·java面试·牛客网·flink面试·校招面经·flink面经·hadoop面试
北顾丶9 个月前
Redis作为缓存,如何与MySql的数据进行同步?
java·开发语言·redis·mysql·缓存·java基础·java面试