java基础面试题B

该系列主要针对校招实习的同学,先写出基本常见的大厂面试题,后面会持续更新面试的题

文章目录

    • 1.聊一聊java泛型
      • 1.举例说明泛型只在编译期有效。
      • 2.泛型的extends与super
    • 2.聊一聊java static关键字
    • 3.初始化一个类的代码执行顺序
      • 综合案例:
    • 4.能否自定义java.lang.String类?
    • 5.聊一聊class对象
    • 6.聊一聊反射
      • 反射的操作:
      • 构造方法操作:
      • 属性操作:
      • 方法操作:
      • 注解操作:
      • 方法参数操作:

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又能接收List对象怎么写?
        List<Person> personList =womenList;
        //编译通过:上面的问题可以使用extends上限通配符,也就是指定了list上限是person
        //那么有一个方法test(List 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(String[] args) {
             
        System.out.println(InstanceInitializer .j);  //0
    }  
}  
//这个代码输出是0 因为在执行private int j = getI(); 代码的时候 i只有默认值0 所以return的是0

3.初始化一个类的代码执行顺序

如图:

java基础面试题B_第1张图片

参照第四题跟上图得出结论,构造方法的第一行是super父类**,后面是本类的成员变量跟构造代码块,每次new 构造都会执行**,成员变量(非static修饰),先执行显示初始化,在执行赋值初始化,赋值初始化跟构造代码块{}一个级别,从上自下依次执行。

例如:

public class InstanceInitializer {
       
    private int j = getI();  
    private int i = 1;  

    public InstanceInitializer() {
       
        i = 2;  
    }  

    private int getI() {
       
        return i;  
    }  

    public static void main(String[] args) {
       
        InstanceInitializer ii = new InstanceInitializer();  
        System.out.println(ii.j);  //0
    }  
}  
//这个代码输出是0 因为在执行private int j = getI(); 代码的时候 i只有默认值0 所以return的是0

总顺序:

1.执行父类的静态变量与静态代码块

2.执行子类的静态变量与静态代码块

3.执行父类的构造方法(构造方法执行之前需要执行完父类的实例变量与构造代码块)

4.执行子类的构造方法(构造方法执行之前需要执行完父类的实例变量与构造代码块)

综合案例:

package cn.junge.jvm;

public class StaticTest {
     
    //main方法会促使该类加载
    public static void main(String[] args) {
     
        //当前方法属于哪个类  因为要调用这个方法 类首先要加载
        staticFunction();
        //2
        //3
        //a=110,b=112
        //new StaticTest();
    }
    //显示初始化 st     等号后面叫赋值初始化
    //只会执行一次
    //1.static StaticTest st  3.new StaticTest() new构造方法会促使成员变量跟代码块执行
    static StaticTest st = new StaticTest();
    //为变量赋值
    //4.执行顺序
    //static  跟赋值初始化是一个级别的 从上往下依次执行
    static {
     //st赋值后执行 先于b变量赋值执行  静态代码块后于前面的静态变量赋值执行 先于后面的静态变量赋值执行
        System.out.println("1");//D:打印1
    }

    {
      //实例代码块  构造代码块
        System.out.println("2");//A:打印2
    }

    StaticTest() {
     //构造方法执行前 全部变量都要到位 构造代码块要到位 每调用一次构造方法
        // 都会执行一次实例变量跟构造代码块
        System.out.println("3");//B:打印3
        System.out.println("a=" + a + ",b=" + b);//C:110  b=0
    }

    public static void staticFunction() {
     
        System.out.println("4");//E:打印4
    }

    int a = 110;
    // 2.static int b
    static int b = 112;
}



    public static int k = 0;
    public static StaticTest s1 = new StaticTest("s1");
    public static StaticTest s2 = new StaticTest("s2");
    public static int i = print("i");//7
    public static int n = 99;//n=99
    public int j = print("j");//A: j=9

    {
     
        print("构造块");
    }

    static {
     //初始化静态变量
        print("静态块");
    }

    public static int print(String s) {
     
        //
        //A:1:j i=0 n=0      k=1 n=1 i=1
        //B:2:构造块 i=1 n=1   k=2 n=2 i=2
        //D:4:j i=3 n=3       i=4 n=4
        //E:5:构造块 i=4 n=4    n=5 i=5
        //G:7:i i=6 n=6        i=7 n=7
        //H:8:静态块 i=7 n=99  n=100 i=8
        //I:9:j i=8 n=100     n=101 i=9
        //J:10:构造块 i=9 n=101   n=102 i=10
        System.out.println(++k + ":" + s + "\ti=" + i + "\tn=" + n + "\tprint");
        ++n;
        return ++i;
    }

    public StaticTest(String s) {
     
        //C: 3:s1 i=2 n=2 staticTest  k=3
        //F: 6:s2 i=5 n=5 staticTest  k=6
        //k: 11:init i=10 n=102
        System.out.println(++k + ":" + s + "\ti=" + i + "\tn=" + n
                + "\tStaticTest");
        ++i; //i=6
        ++n; //n=6
    }

    public static void main(String[] args) {
     
        //促使类加载
        StaticTest init = new StaticTest("init");
        System.out.println(init.j);
    }

再补充一个题:

//父类
class Foo {
     
    int i = 1;

    Foo() {
     
        System.out.println(i);
        int x = getValue();//这里可以看成this.getValue
        System.out.println(x);
    }

    {
     
        i = 2;
    }

    protected int getValue() {
     
        return i;
    }
}
//子类
class Bar extends Foo {
     
    int j = 1;
    Bar() {
     
        System.out.println(j);
        j = 2;
    }
    {
     
        j = 3;
    }

    @Override
    protected int getValue() {
     
        return j;
    }
}
public class ConstructorExample {
     
    public static void main(String... args) {
     
        Bar bar = new Bar();
        System.out.println(bar.getValue());
    }
}/* Output:
            2 第一个i 因为要在构造代码块之后执行
            0 第二个根据多态调用的是子类的方法,因为现在是子类对象,子类的j还没有初始化,因为前面说了代码是在父类构造之后执行  getValue相当于省略了 this.getValue 这个时候根本没有父类对象(有争议,但是即使有父类对象,也是要靠super) 所以this指代的是子类
            3 第三个j 已经执行了构造代码块 j=3
            2 第四个打印的是j 因为构造代码块j=3 然后再构造中j=2  前面说了构造代码块跟实例变量都在构造方法的代码之前执行
 *///:~

如此这般,代码执行顺序题应该没问题了吧!!!

4.能否自定义java.lang.String类?

如下:

package java.lang;
 
public class String {
     
 
    public static void main(String[] args) {
     
        System.out.println("Hello String");
    }
 
}

能定义,但是运行出现异常,在类 java.lang.String 中找不到主方法, 请将主方法定义为: public static void main(String[] args)

因为双亲委派机制,底层类加载器不会先加载该类,会先去上层的扩展类加载器询问是否加载,扩展类加载器会先去循环启动类加载器,BootstrapClassloader已经加载过java.lang.string类型的类,所以直接返回,不会去加载这个类,而原生的String类中并没有main方法 所以出现异常

5.聊一聊class对象

每一个类在被jvm加载的时候都会生成一个class对象,描述了类的信息,这个对象会放在元空间,既然是对象就应该有一个类去描述它,这个类就是位于java.lang包下面的class类.

class对象用于描述这个类的详细信息,也是根据该信息来创建对象。每个存在于java堆中的普通java对象都会在对象头中有一个class pointer指向元空间的class对象,也就是说每个java对象的创建需要依赖其class对象的信息.

获得class对象

1.Class.forName(“类的全限定名”)
2.实例对象.getClass()
3.类名.class (类字面常量)

常用方法:

//传入类的全路径获得class对象。 比如cn.cdqf.pojo.Student
public static Class<?> forName(String className) throws ClassNotFoundException{
     }

//该方法用于通过A类的class对象,创建A类的普通对象。
//该方法默认调用A类的无参构造,如果没有无参构造会报错,IllegalAccessException:表示权限不足,比如无参构造被私有化了
public T newInstance()throws InstantiationException, IllegalAccessException{
     }
//A.class.isInstance(t1) 表示t1能否强转未A类
public native boolean isInstance(Object obj);
//A.class.isAssignableFrom(B.class) A类是否是B类的超类
public native boolean isAssignableFrom(Class<?> cls);
//A.class.isInterface();返回true说明A类是一个接口。比如spring中接口不创建对象,可以使用该方法判断
public native boolean isInterface();
//判断是否是数组
public native boolean isArray();
//判断是否是注解 A.class.isAnnotation()
public boolean isAnnotation()
//获得该类的类加载器,每个java文件都是先编译为class对象(存于磁盘),然后由类加载器加载进入内存才能使用
public ClassLoader getClassLoader();    
//获得该类实现的接口
 public Class<?>[] getInterfaces();
反射相关的方法,下面的题学习

6.聊一聊反射

首先万物皆对象,那么一个类中有属性,方法,构造方法,注解等等,java也用了相关类去描述它们。既然有相关的类去描述他们,那么程序员就可以拿到对应的描述对象,对其进行增删改查(增删的话可以用到cglib等技术)。

个人观点:因为class对象是描述一个类的所有信息,那么只要拿到这个类的class对象,就可以获得类的所有信息,既然能获得所有信息,就可以操作所有信息(动态),于是通过类的class对象操作类的信息,就叫做反射。

动态:new Student();必须要知道Student()这个构造方法的全名字,写死的就叫静态。

XXX.class.getConstructor().newInstance()不需要知道构造方法的名字,传入任意的class对象都是这个方法去获得public修饰的无参构造创建对象,不写死称为动态。

那么既然反射就是让程序员可以不需要写死的获得任意对象的任意属性、方法等,所以反射最大的用处就是完成通用代码的编码的编写。例如封装框架,框架就是通用工具。

反射的操作:

package reflect;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
//定义一个注解
@Target({
     ElementType.METHOD,ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface AAnno {
     
    //value方法 可以不赋值,默认为空串
    String value() default "";
}


定义一个A类继承B类
package reflect;

@AAnno("哈哈")
public class A extends B{
     
    public static String str = "abc";
    private static String str2 = "aaa";
    protected A(String a){
     }
    public int j;
    private String siYou;
    public int getJ(int k){
     
        System.out.println("公共带有返回值的方法被调用了:"+k);
        return 1;
    }
    @AAnno
    private void test(){
     
        System.out.println("私有的方法被调用了");
    }
    public A(){
     
        System.out.println("无参构造被调用了");
    }
    private A(int j){
     
        System.out.println("有参的私有构造被调用了");
        this.j = j;
    }
    public static void aStatic(){
     }
    private static void siYouStatic(){
     }
}
class B{
     
    public  static String str3 = "aaa";

    public int i=10;
    public void show(){
     };

    public static void bStatic(){
     }
}

构造方法操作:

        //获得class对象
        Class<A> aClass = A.class;
        System.out.println("---------构造方法操作---------");
        //获得A的公共无参构造 没有会报错
        Constructor<A> constructor = aClass.getConstructor();
        //通过A的无参构造创建a对象
        A a = constructor.newInstance();
        //获得A类的私有有参构造,参数是构造方法参数的class对象,有个多就写多个class即可
        Constructor<A> declaredConstructor = aClass.getDeclaredConstructor(int.class);
        //操作私有的构造方法、变量、属性等都需要加如下方法,否则权限不足
        declaredConstructor.setAccessible(true);
        A a1 = declaredConstructor.newInstance(3);
        //获得公共的所有构造方法数组
        Constructor<?>[] constructors = aClass.getConstructors();
        //获得所有的构造方法,任意访问修饰符的
        Constructor<?>[] declaredConstructors = aClass.getDeclaredConstructors();

属性操作:

        System.out.println("-----------属性操作------------");
        //获得A的class对象
        Class<?> aClass1 = Class.forName("reflect.A");
        //创建对象,默认调用的是公共无参构造
        A aa = (A)aClass1.newInstance();
        //获得A类的所有属性,包括私有与静态属性,但是不包括继承的
        Field[] declaredFields = aClass1.getDeclaredFields();
        //获得公共属性,包括继承自父类的属性,包括静态属性以及继承的静态属性
        Field[] fields = aClass1.getFields();
        //根据名字获得特有的属性
        Field j = aClass1.getField("j");
        //对j属性赋值,因为j是普通属性,依赖对象存在,所以赋值需要指定对象,也就是把对象aa的属性执行为值1
        j.set(aa,1);
        //获得普通私有属性
        Field siYou = aClass1.getDeclaredField("siYou");
        //对私有属性赋值前,设置权限
        siYou.setAccessible(true);
        siYou.set(aa,"abc");//赋值操作

方法操作:

        System.out.println("------------方法操作-------------");
        //根据对象的getClass()方法获得class对象
        Class<? extends A> aClass2 = new A().getClass();
        //根据class对象创建普通对象
        A a2 = aClass2.newInstance();
        //获得所有的公共方法,包括继承自Object类的方法、本类静态方法、父类静态方法
        Method[] methods = aClass2.getMethods();
        //获得本类的所有方法,包括父类继承的普通方法,本类的静态方法
        Method[] declaredMethods = aClass2.getDeclaredMethods();
        //根据方法名字获得指定的方法,后面是对应方法参数的类型,可以写多个
        Method getJ = aClass2.getMethod("getJ", int.class);
        //根据方法对象调用普通方法,因为普通方法依赖对象,所以是调用对象a2的getJ方法
        //传入的参数是3,返回值是invoke
        Object invoke = getJ.invoke(a2, 3);
        System.out.println(invoke);
        //根据方法名字获得私有方法
        Method test = aClass2.getDeclaredMethod("test");
        //私有调用前设置权限
        test.setAccessible(true);
        //返回值是void,那么invoke1就是null
        Object invoke1 = test.invoke(a2);

注解操作:

        System.out.println("------------注解操作------------");
        //获得aClass3对象
        Class<? extends A> aClass3 = new A().getClass();
        //判断A类上面是否有AAnno注解,比如spring就是通过扫描是否有service等注解来判断是否需要ioc
        boolean annotationPresent = aClass3.isAnnotationPresent(AAnno.class);
        //获得类上面的注解对象
        AAnno annotation = aClass3.getAnnotation(AAnno.class);
        //获得注解对象填入的值 哈哈
        String value = annotation.value();
        System.out.println(value);
        //获得私有方法 test
        Method test1 = aClass3.getDeclaredMethod("test");
        //判断方法上面是否有AAnno注解,比如spring事务@Transactional
        boolean annotationPresent1 = test1.isAnnotationPresent(AAnno.class);
        //获得方法对象上面的注解
        AAnno annotation1 = test1.getAnnotation(AAnno.class);
        //其它属性 构造方法类似 构造方法需要指定注解的Target:ElementType.CONSTRUCTOR

方法参数操作:

   System.out.println("---------操作方法的参数-----------");
        Class<A> aClass4 = A.class;
        Method getJ1 = aClass4.getMethod("getJ", int.class);
        //获得参数的个数
        int parameterCount = getJ1.getParameterCount();
        //获得所有参数的类型,按顺序
        Class<?>[] parameterTypes = getJ1.getParameterTypes();
        //获得参数的注解,需要在注解的Target指定ElementType.PARAMETER ,例如springMVC的@PathVarible
        //二维数组,因为每个参数可能有多个注解
        Annotation[][] parameterAnnotations = getJ1.getParameterAnnotations();
        //获得参数对象
        Parameter[] parameters = getJ1.getParameters();
        //获得第一个参数加的注解
        AAnno annotation2 = parameters[0].getAnnotation(AAnno.class);
        //获得参数的名字, 如果是idea需要设置
        //1.File->Settings->Build,Execution,Deployment->Compiler->Java Compiler
        //2在 Additional command line parameters: 后面填上 -parameters,设置好了后就rebuild一下项目
        String name = parameters[0].getName();
        //参数值的设置用上面的method对象,把所有的参数按类型,按顺序放在一个Object数组,
        //因为method.invoke()的第二个参数是可变参,可以传入数组.
        //比如springMvc的参数适配器就是通过方法名字去request里面获得参数值然后设置的
触发一个类加载:
1.遇到new(用new实例对象),getStatic(读取一个静态字段),putstatic(设置一个静态字段),invokeStatic(调用一个类的静态方法)这四条指令字节码命令时
2.使用Java.lang.reflect包的方法对类进行反射调用时,如果此时类没有进行init,会先init。
3.当初始化一个类时,如果其父类没有进行初始化,先初始化父类
4.jvm启动时,用户需要指定一个执行的主类(包含main的类)虚拟机会先执行这个类
5.当使用JDK1.7的动态语言支持的时候,当java.lang.invoke.MethodHandler实例后的结果是REF-getStatic/REF_putstatic/REF_invokeStatic的句柄,并且这些句柄对应的类没初始化的话应该首先初始。

不会触发类的加载:
1.通过子类来引用父类的静态字段,只会触发父类的初始化,不会触发子类的初始化。
2.superclass () sc = new superclass[];//不会触发superclass初始化,因为底层实现是直接生成object子类。
3.引用一个类的静态常量也不会触发初始化,因为常量在编译阶段已经确认。

为此专门新建了新群,欢迎加入交流
在这里插入图片描述

你可能感兴趣的:(java基础)