该系列主要针对校招实习的同学,先写出基本常见的大厂面试题,后面会持续更新面试的题
文章目录
1.聊一聊java泛型
1.举例说明泛型只在编译期有效。
ArrayList<Integer> list = new ArrayList<Integer>();
list.add(1); //这样调用 add 方法只能存储整形,因为泛型类型的实例为 Integer
//通过反射获得add方法对象,再调用add方法,发现是可以添加指定的泛型以外的元素的。
//class是属于运行期加载进内存的。
//直接调用list.add("asd")会报错,这是jvm对程序进行的编译检查。但是通过运行期的class对象调用就可以,说明java是伪泛型,可以通过
//反射进行泛型擦除
list.getClass().getMethod("add", Object.class).invoke(list, "asd");
for (int i = 0; i < list.size(); i++) {
System.out.println(list.get(i));
}
2.泛型的extends与super
package fanx;
import java.util.ArrayList;
import java.util.List;
public class Person {
public void show(){
}
public static void main(String[] args) {
List<Women> womenList = new ArrayList<>();
//编译不通过:泛型在java中是起到限制作用。
//那么假设这个代码通过,personList集合的泛型是Person对象,可以存放Person及其子类
//就可以使用personList添加man对象了,但是womenList只能添加women对象,所以编译不通过
//1.那如果我们能需要写一个方法,既接收List<Women>又能接收List<Man>对象怎么写?
List<Person> personList =womenList;
//编译通过:上面的问题可以使用extends上限通配符,也就是指定了list上限是person
//那么有一个方法test(List<? extends Person> list) 就可以接收person极其子类
//只是指定了上限是person具体传入的泛型是women还是man不确定,所以该集合不能添加元素
//为什么不能添加?因为编译器无法确定你放入的是何种类型的泛型集合,如果能添加women对象
//那你指定的是man对象怎么办?也不能添加Person对象,因为women集合不能添加Person需要强转
//有用吗?有,比如封装的方法需要既能接收women集合也能接收man集合就可以。因为该方法可以取,
//为什么可以取?因为上限始终是person是确定了的。可以取就可以调用方法。
// 你在原来的man集合或者women集合上去存不就可以了吗
List<? extends Person> list = womenList;
//2.需要一个方法,可以接收man集合以及man集合的父类怎么写?
List<Person> personList1 = new ArrayList<>();
List<Man> manList = new ArrayList<>();
List<? super Man> superList = manList;
List<? super Man> superList2 = personList1;
//super表示了集合的下限是man,也就是可以接收man集合以及man父类的集合
//因为一个类可能有多个父类(比如接口),该super集合无法确定存放的何种父类。
//于是该集合只能存(只能存man对象),
// 不能取?假如能取,你用什么接收?用ManInterface,那传入的是person集合怎么办?
//用person传入的是ManInterface怎么办?用Object,可以是可以,但是有什么意义?
//有用吗?有,比如封装一个方法用于添加man对象,那么如果既可以传入ManInterface集合也能传入Person集合
//你在传入ManInterface集合跟person集合代码的地方取来用不就可以了吗
}
}
class Women extends Person{
}
class Man extends Person implements ManInterface{
}
interface ManInterface{
}
2.聊一聊java static关键字
1.static修饰成员变量,变量变为静态变量
2.static修饰代码块{},变成静态代码块
3.修饰方法变成静态方法
被static修饰的被称为类变量,类方法或者类代码块,jvm在把一个java类加载进内存,有如下过程
分为加载(找到资源),验证(验证是否符号jvm规范),准备,解析(符号引用替换直接引用),初始化
其中准备阶段,为类的静态变量分配空间,赋予初值(注意),为什么先分配空间,而不赋值(有类型那么空间就)。很好理解,如果空间都不足,那么该类信息无法进入元空间,假如第一个变量,分配空间,赋值,第二个变量发现空间不够了,那第一个变量的赋值不是白做了吗?因为赋值时可以调用方法的,可能经过很多流程。例如public static int i = getI();(这个地方会有疑问,基本变量能确定大小,那String类型这种引用类型,不确定空间大小怎么办,这个没有办法,jvm先给能确定的分配大小,这样至少可以早点发现空间不足)
我们把显示赋予初始值的过程称为显示初始化
全部显示初始化结束,才会进行后面的赋值初始化
另外静态代码块,主要的作用是在类加载的时候对静态变量赋值,以及调用某些方法,完成必要的初始化,我们先看对静态变量赋值,因为是赋值所以静态代码的执行时机跟赋值初始化相同,又因为代码从上往下执行,所以:
例如:
//1.执行public static int i 这个时候i=0;
//3.执行i=1
public static int i = 1;
//4.执行代码块
static{
//i变量已经可以使用,比如打印,因为i变量的赋值初始化在静态代码块前面
//也就是执行到这里i已经完成的赋值,所以可以使用
System.out.println(i);
//j不能使用,因为j在后面,赋值初始化会晚于静态代码块,j就是个初始值,使用有何意义?
System.out.println(j);//报错
//j变量能读取,因为显示初始化会在静态代码块前面执行
j=3;
}
//2.执行public static int j 这个时候j=0;
public static int j = 2;
无论是静态变量,还是static代码块都会在类加载的时候执行,并且只会执行一次,因为类加载进元空间就一次,而且java的双亲委派机制也保证了类只会被加载一次
那么触发一个类加载的时机是:(记住括号里面的哦)
1.new 一个对象,很好理解,对象的信息就是来源于类,对象的对象头的class pointer就是指向元空间的class信息
2.访问类的静态变量(final修饰的访问常量不会哦)
3.访问类的静态方法(main方法也算哦)
4.反射获得class对象
5.初始化其子类(注意:通过子类访问父类的静态变量只会初始父类加载不会初始子类加载哦)
所以:
public class InstanceInitializer {
private static int j = getI();
private static int i = 1;
public InstanceInitializer() {
i = 2;
}
private static int getI() {
return i;
}
public static void main(