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

目录

一、泛型的理解

二、泛型的作用

三、泛型有哪些使用方式

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

五、泛型的原理


一、泛型的理解

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

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

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

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

例子:

Object s= new String();

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

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

List<String> list = new ArrayList();

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

二、泛型的作用

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

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

参考上面理解中的例子

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

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

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

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

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

三、泛型有哪些使用方式

泛型类

//此处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;
    }
}

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

泛型方法

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

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

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

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

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

不够灵活

当然还有许多地方:

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

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

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

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;
    }
 public static <T> List<T> copyBean(List<?> list,Class<T> clazz){
        return list.stream()
                .map(o->copyBean(o,clazz))
                .collect(Collectors.toList());
    }
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;
	}
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;
    }

泛型接口:

public interface Generator<T> {
    public T method();
}

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

class GeneratorImpl<T> implements Generator<T>{
    @Override
    public T method() {
        return null;
    }
}

典型应用:

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

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

public interface CourseBaseMapper extends BaseMapper<CourseBase> {

}
public interface BaseMapper<T> extends Mapper<T> {
    int insert(T entity);
}

 service:

public interface CourseBaseService extends IService<CourseBase> {}
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类接收

使用例子:

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

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

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,因此泛型是几乎不消耗性能的,所以使用泛型的另外一个作用就是:潜在的性能收益  

本图文内容来源于网友网络收集整理提供,作为学习参考使用,版权属于原作者。
THE END
分享
二维码

)">
< <上一篇
下一篇>>