编程语言
首页 > 编程语言> > 2021年Java面经分享:丹丹丹学妹哭着对我说:学长

2021年Java面经分享:丹丹丹学妹哭着对我说:学长

作者:互联网

aas类型擦除:

aa

aaasdass为了保证编译出来的Class文件可以在Java 5.0引入泛型之后继续运行,有两种方式:

aaasdaasdass①、将已有的类型泛型化(Java选择)

aaasdaasdass②、平行地加一套泛型化版本的新类型(C#选择)

aaasdass要让所有需要泛型化的已有类型,譬如ArrayList,原地泛型化后变成了ArrayList< T>,而且保证以前直接用ArrayList的代码在泛型新版本里必须还能继续用这同一个容器,这就必须让所有泛型化的实例类型,譬如ArrayList< Integer>、ArrayList< String>这些全部自动成为ArrayList的子类型才能可以,否则类型转换就是不安全的。由此就引出了“裸类型”的概念,裸类型应被视为所有该类型泛型化实例的共同父类型 。 只有这样,我们才能实现如下转型:


ArrayList<Integer> ilist = new ArrayList<Integer>();

ArrayList<String> slist = new ArrayList<String>();

ArrayList list; // 裸类型

list = ilist;

list = slist; 

aas如何实现裸类型呢(2种方法)?

aa

aaasdass①、在运行期由Java虚拟机来自动地、真实地构造出ArrayList< Integer>这样的类型,并且自动实现从ArrayList< Integer>派生自ArrayList的继承关系来满足裸类型的定义;

aa

aaasdass②、简单粗暴地直接在编译时把ArrayList< Integer>还原回ArrayList,只在元素访问、修改时自动插入一些强制类型转换和检查指令,这样看起来也是能满足需要,这两个选择的最终结果大家已经都知道了(Java选择的实现方式)。

aa

aasdsasdsadsadsadsadasdsadadasdas泛型擦除前的例子:


public static void main(String[] args) {

	Map<String, String> map = new HashMap<String, String>();

	map.put("hello", "你好");

	map.put("how are you?", "吃了没?");

	System.out.println(map.get("hello"));

	System.out.println(map.get("how are you?"));

} 

aaasdass【注】:把这段Java代码编译成Class文件,然后再用字节码反编译工具进行反编译后,将会发现泛型都不见了,程序又变回了Java泛型出现之前的写法,泛型类型都变回了裸类型。

aa

aasdsasdsadsadsadsadasdsadadasdas泛型擦除后的例子:


public static void main(String[] args) {

	Map map = new HashMap();

	map.put("hello", "你好");

	map.put("how are you?", "吃了没?");

	System.out.println((String) map.get("hello"));

	System.out.println((String) map.get("how are you?"));

} 

aas擦除式泛型的缺陷:

aa

aasdas①、使用擦除法实现泛型直接导致了 对原始类型数据的支持 又成了新的麻烦。

aasdsadasd

aasdsasdsadsadsadsadasdsadadasdas原始类型的泛型(目前的Java不支持)


ArrayList<int> ilist = new ArrayList<int>();

ArrayList<long> llist = new ArrayList<long>();

ArrayList list;

list = ilist;

list = llist; 

aasasaas这种情况下,一旦把泛型信息擦除后,到了要插入强制转型代码的地方就没办法往下做了,因为不支持int、long与Object之间的强制转型。

aasasaas解决方法:当遇到原生类型(int 、long)时,对其进行装箱、拆箱,变成ArrayList< Integer>、ArrayList< Long>。

aaasdass【注】:装箱、拆箱的开销是Java泛型慢的重要原因。也成为今天Valhalla项目要重点解决的问题之一。

aa

aasdas②、运行期无法取到泛型类型信息。会让一些代码变得相当啰嗦。比如不支持对泛型进行实例判断、不支持使用泛型创建对象、不支持使用泛型创建数组,都是由于运行期Java虚拟机无法取得泛型类型而导致的。

aaasdass【注】:比如我们去写一个泛型版本的从List到数组的转换方法,由于不能从List中取得参数化类型T,所以不得不从一个额外参数中再传入一个数组的组件类型进去。

aasdsadasd

aasdsasdsadsadsadssadsaadasdsadadasdas不得不加入的类型参数


public static <T> T[] convert(List<T> list, Class<T> componentType) {

	T[] array = (T[])Array.newInstance(componentType, list.size());

	...

} 

aa

aasdas③、通过擦除法来实现泛型,还丧失了一些面向对象思想应有的优雅,带来了一些模棱两可的模糊状况。

aasdsadasd

aasdsasdsadsadsadssadsaadasdsadadasdas当泛型遇见重载1


public class GenericTypes {

	public static void method(List<String> list) {

		System.out.println("invoke method(List<String> list)");

	}

	public static void method(List<Integer> list) {

		System.out.println("invoke method(List<Integer> list)");

	}

} 

aasasaas分析:这段代码是不能被编译的,因为参数List< Integer>和List< String>编译之后都被擦除了,变成了同一种的裸类型List,类型擦除导致这两个方法的特征签名变得一模一样。初步看来,无法重载的原因已经找到了,但是真的就是如此吗?其实这个例子中泛型擦除成相同的裸类型只是无法重载的其中一部分原因。再看:

aasdsadasd

aasdsasdsadsadsadssadsaadasdsadadasdas当泛型遇见重载2


public class GenericTypes {

	public static String method(List<String> list) {

		System.out.println("invoke method(List<String> list)");

		return "";

	}

	public static int method(List<Integer> list) {

		System.out.println("invoke method(List<Integer> list)");

		return 1;

	}

	public static void main(String[] args) {

		method(new ArrayList<String>());

		method(new ArrayList<Integer>());

	}

}

执行结果:

	invoke method(List<String> list)

	invoke method(List<Integer> list) 

aasasaas分析:这里的重载当然不是根据返回值来确定的,之所以这次能编译和执行成功,是因为两个method()方法加入了不同的返回值后才能共存在一个Class文件之中。 由于List< String>和List< Integer>擦除后是同一个类型,我们只能添加两个并不需要实际使用到的返回值才能完成重载,这是一种毫无优雅和美感可言的解决方案,并且存在一定语意上的混乱。

aaasdass【注】:前面的文章已经提到:方法重载要求方法具备不同的特征签名,返回值并不包含在方法的特征签名中,所以返回值不参与重载选择,但是在Class文件格式之中,只要描述符不是完全一致的两个方法就可以共存。也就是说两个方法如果有相同的名称和特征签名,但返回值不同,那它们也是可以合法地共存于一个Class文件中的。

aaasdass总结:

aaaasdsasas①、由于Java泛型的引入,各种场景(虚拟机解析、反射等)下的方法调用都可能对原有的基础产生影响并带来新的需求,如在泛型类中如何获取传入的参数化类型等。所以《Java虚拟机规范》做出了相应的修改,引入了诸如SignatureLocalVariableTypeTable等新的属性用于解决伴随泛型而来的参数类型的识别问题

aaaasdsasasdas⒈Signature:存储一个方法在字节码层面的特征签名,这个属性中保存的参数类型并不是原生类型,而是包括了参数化类型的信息。

aaaasdsasas②、从Signature属性的出现我们还可以得出结论:擦除法所谓的擦除,仅仅是对方法的Code属性中的字节码进行擦除,实际上元数据中还是保留了泛型信息,这也是我们在编码时能通过反射手段取得参数化类型的根本依据。 (很重要,后面会进一步分析,因为现在也不能很明白的表达。)


自动装箱、拆箱与遍历循环

aa

aaas就纯技术的角度而论,自动装箱、自动拆箱与遍历循环 这些语法糖,无论是实现复杂度上还是其中蕴含的思想上都不能与泛型相提并论,两者涉及的难度和深度都有很大差距。

aa

aaas我们看一段代码,其中包含了泛型、自动装箱、自动拆箱、遍历循环与变长参数5种语法糖:

aasdsadasd

aasdsasdsadsadsadssadsaadasdsadadasdas编译前的代码


public static void main(String[] args) {

List<Integer> list = Arrays.asList(1, 2, 3, 4);

int sum = 0;

for (int i : list) {

sum += i;

}

System.out.println(sum);

} 

aasdsasdsadsadsadssadsaadasdsadadasdas编译后的代码


public static void main(String[] args) {

	List list = Arrays.asList( new Integer[] {   //泛型消除

		Integer.valueOf(1),

		Integer.valueOf(2),

		Integer.valueOf(3),

		Integer.valueOf(4) });

	int sum = 0;

# 总结

蚂蚁面试比较重视基础,所以Java那些基本功一定要扎实。蚂蚁的工作环境还是挺赞的,因为我面的是稳定性保障部门,还有许多单独的小组,什么三年1班,很有青春的感觉。面试官基本水平都比较高,基本都P7以上,除了基础还问了不少架构设计方面的问题,收获还是挺大的。

**[资料领取方式:戳这里](https://gitee.com/vip204888/java-p7)**

* * *

**经历这次面试我还通过一些渠道发现了需要大厂真实面试主要有**:蚂蚁金服、拼多多、阿里云、百度、唯品会、携程、丰巢科技、乐信、软通动力、OPPO、银盛支付、中国平安等初,中级,高级Java面试题集合,附带超详细答案,希望能帮助到大家。

nt sum = 0;

# 总结

蚂蚁面试比较重视基础,所以Java那些基本功一定要扎实。蚂蚁的工作环境还是挺赞的,因为我面的是稳定性保障部门,还有许多单独的小组,什么三年1班,很有青春的感觉。面试官基本水平都比较高,基本都P7以上,除了基础还问了不少架构设计方面的问题,收获还是挺大的。

**[资料领取方式:戳这里](https://gitee.com/vip204888/java-p7)**

* * *

**经历这次面试我还通过一些渠道发现了需要大厂真实面试主要有**:蚂蚁金服、拼多多、阿里云、百度、唯品会、携程、丰巢科技、乐信、软通动力、OPPO、银盛支付、中国平安等初,中级,高级Java面试题集合,附带超详细答案,希望能帮助到大家。

![蚂蚁金服5面,总结了49个面试题,遇到的面试官都是P7级别以上](https://www.icode9.com/i/ll/?i=img_convert/a8bc86de3c451c2e1fab0dcc16a5839f.png)

标签:Java,学妹,ArrayList,list,学长,类型,泛型,List
来源: https://blog.csdn.net/m0_60388117/article/details/119387256