java面试常问的面试题是什么,最近有粉丝向我请教Java泛型,2024最新腾讯Java面试分享

三 一个简单的泛型类


编写基本的容器类

此时,您可以开始编写简单的泛型类了。到目前为止,泛型类最常见的用例是容器类(比如集合框架)或者值持有者类(比如 WeakReference 或 ThreadLocal)。我们来编写一个类,它类似于 List,充当一个容器。其中,我们使用泛型来表示这样一个约束,即 Lhist 的所有元素将具有相同类型。为了实现起来简单,Lhist 使用一个固定大小的数组来保存值,并且不接受 null 值。

Lhist 类将具有一个类型参数 V(该参数是 Lhist 中的值的类型),并将具有以下方法:

public class Lhist {

public Lhist(int capacity) { … }

public int size() { … }

public void add(V value) { … }

public void remove(V value) { … }

public V get(int index) { … }

}

要实例化 Lhist,只要在声明时指定类型参数和想要的容量:

Lhist stringList = new Lhist(10);

实现构造函数


在实现 Lhist 类时,您将会遇到的第一个拦路石是实现构造函数。您可能会像下面这样实现它:

public class Lhist {

private V[] array;

public Lhist(int capacity) {

array = new V[capacity]; // illegal

}

}

这似乎是分配后备数组最自然的一种方式,但是不幸的是,您不能这样做。具体原因很复杂,当学习到 底层细节 一节中的“擦除”主题时,您就会明白。分配后备数组的实现方式很古怪且违反直觉。下面是构造函数的一种可能的实现(该实现使用集合类所采用的方法):

public class Lhist {

private V[] array;

public Lhist(int capacity) {

array = (V[]) new Object[capacity];

}

}

另外,也可以使用反射来实例化数组。但是这样做需要给构造函数传递一个附加的参数 —— 一个类常量,比如 Foo.class。后面在 Class 一节中将讨论类常量。

实现方法


实现 Lhist 的方法要容易得多。下面是 Lhist 类的完整实现:

public class Lhist {

private V[] array;

private int size;

public Lhist(int capacity) {

array = (V[]) new Object[capacity];

}

public void add(V value) {

if (size == array.length)

throw new IndexOutOfBoundsException(Integer.toString(size));

else if (value == null)

throw new NullPointerException();

array[size++] = value;

}

public void remove(V value) {

int removalCount = 0;

for (int i=0; i<size; i++) {

if (array[i].equals(value))

++removalCount;

else if (removalCount > 0) {

array[i-removalCount] = array[i];

array[i] = null;

}

}

size -= removalCount;

}

public int size() { return size; }

public V get(int i) {

if (i >= size)

throw new IndexOutOfBoundsException(Integer.toString(i));

return array[i];

}

}

注意,您在将会接受或返回 V 的方法中使用了形式类型参数 V,但是您一点也不知道 V 具有什么样的方法或域,因为这些对泛型代码是不可知的。

使用 Lhist 类

使用 Lhist 类很容易。要定义一个整数 Lhist,只需要在声明和构造函数中为类型参数提供一个实际值即可:

Lhist li = new Lhist(30);

编译器知道,li.get() 返回的任何值都将是 Integer 类型,并且它还强制传递给 li.add() 或 li.remove() 的任何东西都是 Integer。除了实现构造函数的方式很古怪之外,您不需要做任何十分特殊的事情以使 Lhist 是一个泛型类。

四 Java类库中的泛型


集合类

到目前为止,Java 类库中泛型支持存在最多的地方就是集合框架。就像容器类是 C++ 语言中模板的主要动机一样(参阅 附录 A:与 C++ 模板的比较)(尽管它们随后用于很多别的用途),改善集合类的类型安全是 Java 语言中泛型的主要动机。集合类也充当如何使用泛型的模型,因为它们演示了泛型的几乎所有的标准技巧和方言。

所有的标准集合接口都是泛型化的 —— Collection、List、Set 和 Map<K,V>。类似地,集合接口的实现都是用相同类型参数泛型化的,所以 HashMap<K,V> 实现 Map<K,V> 等。

集合类也使用泛型的许多“技巧”和方言,比如上限通配符和下限通配符。例如,在接口 Collection 中,addAll 方法是像下面这样定义的:

interface Collection {

boolean addAll(Collection<? extends V>);

}

该定义组合了通配符类型参数和有限制类型参数,允许您将 Collection 的内容添加到 Collection。

如果类库将 addAll() 定义为接受 Collection,您就不能将 Collection 的内容添加到 Collection。不是限制 addAll() 的参数是一个与您将要添加到的集合包含相同类型的集合,而有可能建立一个更合理的约束,即传递给 addAll() 的集合的元素 适合于添加到您的集合。有限制类型允许您这样做,并且使用有限制通配符使您不需要使用另一个不会用在其他任何地方的占位符名称。

应该可以将 addAll() 的类型参数定义为 Collection。但是,这不但没什么用,而且还会改变 Collection 接口的语义,因为泛型版本的语义将会不同于非泛型版本的语义。这阐述了泛型化一个现有的类要比定义一个新的泛型类难得多,因为您必须注意不要更改类的语义或者破坏现有的非泛型代码。

作为泛型化一个类(如果不小心的话)如何会更改其语义的一个更加微妙的例子,注意 Collection.removeAll() 的参数的类型是 Collection<?>,而不是 Collection<? extends V>。这是因为传递混合类型的集合给 removeAll() 是可接受的,并且更加限制地定义 removeAll 将会更改方法的语义和有用性。

其他容器类


除了集合类之外,Java 类库中还有几个其他的类也充当值的容器。这些类包括 WeakReference、SoftReference 和 ThreadLocal。它们都已经在其包含的值的类型上泛型化了,所以 WeakReference 是对 T 类型的对象的弱引用,ThreadLocal 则是到 T 类型的线程局部变量的句柄。

泛型不止用于容器


泛型最常见最直观的使用是容器类,比如集合类或引用类(比如 WeakReference)。Collection 中类型参数的含义很明显 —— “一个所有值都是 V 类型的集合”。类似地,ThreadLocal 也有一个明显的解释 —— “一个其类型是 T 的线程局部变量”。但是,泛型规格说明中没有指定容积。

像 Comparable 或 Class 这样的类中类型参数的含义更加微妙。有时,就像 Class 中一样,类型变量主要是帮助编译器进行类型推理。有时,就像隐含的 Enum 中一样,类型变量只是在类层次结构上加一个约束。

Comparable


Comparable 接口已经泛型化了,所以实现 Comparable 的对象声明它可以与什么类型进行比较。(通常,这是对象本身的类型,但是有时也可能是父类。)

public interface Comparable {

public boolean compareTo(T other);

}

所以 Comparable 接口包含一个类型参数 T,该参数是一个实现 Comparable 的类可以与之比较的对象的类型。这意味着如果定义一个实现 Comparable 的类,比如 String,就必须不仅声明类支持比较,还要声明它可与什么比较(通常是与它本身比较):

public class String implements Comparable { … }

现在来考虑一个二元 max() 方法的实现。您想要接受两个相同类型的参数,二者都是 Comparable,并且相互之间是 Comparable。幸运的是,如果使用泛型方法和有限制类型参数的话,这相当直观:

public static <T extends Comparable> T max(T t1, T t2) {

if (t1.compareTo(t2) > 0)

return t1;

else

return t2;

}\

在本例中,您定义了一个泛型方法,在类型 T 上泛型化,您约束该类型扩展(实现) Comparable。两个参数都必须是 T 类型,这表示它们是相同类型,支持比较,并且相互可比较。容易!

更好的是,编译器将使用类型推理来确定当调用 max() 时 T 的值表示什么意思。所以根本不用指定 T,下面的调用就能工作:

String s = max(“moo”, “bark”);

编译器将计算出 T 的预定值是 String,因此它将进行编译和类型检查。但是如果您试图用不实现 Comparable 的 类 X 的参数调用 max(),那么编译器将不允许这样做。

Class


类 Class 已经泛型化了,但是很多人一开始都感觉其泛型化的方式很混乱。Class 中类型参数 T 的含义是什么?事实证明它是所引用的类接口。怎么会是这样的呢?那是一个循环推理?如果不是的话,为什么这样定义它?

在以前的 JDK 中,Class.newInstance() 方法的定义返回 Object,您很可能要将该返回类型强制转换为另一种类型:

class Class {

Object newInstance();

}

但是使用泛型,您定义 Class.newInstance() 方法具有一个更加特定的返回类型:

class Class {

T newInstance();

}

如何创建一个 Class 类型的实例?就像使用非泛型代码一样,有两种方式:调用方法 Class.forName() 或者使用类常量 X.class。Class.forName() 被定义为返回 Class<?>。另一方面,类常量 X.class 被定义为具有类型 Class,所以 String.class 是 Class 类型的。

让 Foo.class 是 Class 类型的有什么好处?大的好处是,通过类型推理的魔力,可以提高使用反射的代码的类型安全。另外,还不需要将 Foo.class.newInstance() 强制类型转换为 Foo。

考虑一个方法,它从数据库检索一组对象,并返回 JavaBeans 对象的一个集合。您通过反射来实例化和初始化创建的对象,但是这并不意味着类型安全必须完全被抛至脑后。考虑下面这个方法:

public static List getRecords(Class c, Selector s) {

// Use Selector to select rows

List list = new ArrayList();

for (/* iterate over results */) {

T row = c.newInstance();

// use reflection to set fields from result

list.add(row);

}

return list;

}

可以像下面这样简单地调用该方法:

List l = getRecords(FooRecord.class, fooSelector);

编译器将会根据 FooRecord.class 是 Class 类型的这一事实,推断 getRecords() 的返回类型。您使用类常量来构造新的实例并提供编译器在类型检查中要用到的类型信息。

用 Class 替换 T[]


Collection 接口包含一个方法,用于将集合的内容复制到一个调用者指定类型的数组中:

public Object[] toArray(Object[] prototypeArray) { … }

toArray(Object[]) 的语义是,如果传递的数组足够大,就会使用它来保存结果,否则,就会使用反射分配一个相同类型的新数组。一般来说,单独传递一个数组作为参数来提供想要的返回类型是一个小技巧,但是在引入泛型之前,这是与方法交流类型信息最方便的方式。

有了泛型,就可以用一种更加直观的方式来做这件事。不像上面这样定义 toArray(),泛型 toArray() 可能看起来像下面这样:

public T[] toArray(Class returnType)

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数Java工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
img
img
img
img
img
img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新

如果你觉得这些内容对你有帮助,可以添加V获取:vip1024b (备注Java)
img

最后

在面试前我整理归纳了一些面试学习资料,文中结合我的朋友同学面试美团滴滴这类大厂的资料及案例

MyBatis答案解析
由于篇幅限制,文档的详解资料太全面,细节内容太多,所以只把部分知识点截图出来粗略的介绍,每个小节点里面都有更细化的内容!

大家看完有什么不懂的可以在下方留言讨论也可以关注。

觉得文章对你有帮助的话记得关注我点个赞支持一下!

一个人可以走的很快,但一群人才能走的更远。如果你从事以下工作或对以下感兴趣,欢迎戳这里加入程序员的圈子,让我们一起学习成长!

AI人工智能、Android移动开发、AIGC大模型、C C#、Go语言、Java、Linux运维、云计算、MySQL、PMP、网络安全、Python爬虫、UE5、UI设计、Unity3D、Web前端开发、产品经理、车载开发、大数据、鸿蒙、计算机网络、嵌入式物联网、软件测试、数据结构与算法、音视频开发、Flutter、IOS开发、PHP开发、.NET、安卓逆向、云计算

结合我的朋友同学面试美团滴滴这类大厂的资料及案例
[外链图片转存中…(img-YuZCELZ6-1712435495230)]

[外链图片转存中…(img-JvmQqRPc-1712435495230)]
由于篇幅限制,文档的详解资料太全面,细节内容太多,所以只把部分知识点截图出来粗略的介绍,每个小节点里面都有更细化的内容!

大家看完有什么不懂的可以在下方留言讨论也可以关注。

觉得文章对你有帮助的话记得关注我点个赞支持一下!

一个人可以走的很快,但一群人才能走的更远。如果你从事以下工作或对以下感兴趣,欢迎戳这里加入程序员的圈子,让我们一起学习成长!

AI人工智能、Android移动开发、AIGC大模型、C C#、Go语言、Java、Linux运维、云计算、MySQL、PMP、网络安全、Python爬虫、UE5、UI设计、Unity3D、Web前端开发、产品经理、车载开发、大数据、鸿蒙、计算机网络、嵌入式物联网、软件测试、数据结构与算法、音视频开发、Flutter、IOS开发、PHP开发、.NET、安卓逆向、云计算

  • 18
    点赞
  • 30
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值