Java基础面试题总结

一. 概述

1.JVM、JRE和JDK的关系
  • JVM是Java虚拟机,Java程序需要运行在虚拟机上
  • JRE是Java运行时环境,包括Java虚拟机和Java程序所需的核心类库等。(核心类库主要是java.lang包)

如果想要运行一个开发好的Java程序,计算机中只需要安装JRE即可。

  • JDK是Java开发工具,其中包含了Java的开发工具,也包括了JRE。
  • 因此JDK>JRE>JVM
2. Java 和 C++的区别?
  1. C++是纯编译型语言,Java是解释型语言和编译型语言的混合
  2. Java有自动内存管理垃圾回收机制(GC),不需要手动开辟释放内存
  3. Java 的类是单继承的,C++ 支持多重继承;虽然 Java 的类不可以多继承,但是接口可以多继承。

二. 基础语法

1. ‘+’,StringBuilder,StringBuffer的区别
  • 使用+直接拼接,String 是final对象,不会被修改,每次使用 +进行拼接都会创建新的对象,而不是改变原来的对象,效率低,是线程安全的。
  • 使用StringBuffer可变字符串,效率较高,是线程安全的(StringBuffer的方法使用了synchronized关键字进行修饰)。
  • 使用StringBuilder可变字符串,效率最高,但是线程不安全
2.什么是字符串常量池

字符串常量池位于堆内存中,专门用来存储字符串常量,可以提高内存的使用率,避免开辟多块空间存储相同的字符串,在创建字符串时 JVM 会首先检查字符串常量池,如果该字符串已经存在池中,则返回它的引用,如果不存在,则实例化一个字符串放到池中,并返回其引用。

¥3.String为什么是不可变的

从源码上看String类利用了final修饰的char类型数组存储字符private final char value[];

在 Java 9 之后,String 类的实现改用 byte 数组存储字符串 private final byte[] value;

这么设计的原因:

  1. 由于字符串常量池的存在,一个字符串可能被引用多次,如果是可变的,其中一个引用将其修改则会影响其他引用,造成逻辑混乱
  2. 由于字符串是不可变的,那么就可以缓存其hashCode,不用每次都去计算,提升效率
  3. 字符串被很多基础包所引用,如果能够改变会存在安全隐患
3.5 String的hashcode()方法
public int hashCode() {
    int h = hash;
    if (h == 0 && value.length > 0) {
        char val[] = value;

        for (int i = 0; i < value.length; i++) {
            h = 31 * h + val[i];
        }
        hash = h;
    }
    return h;
}

在String类中有个私有实例字段hash表示该串的哈希值,在第一次调用hashCode方法时,字符串的哈希值被计算并且赋值给hash字段,之后再调用hashCode方法便可以直接取hash字段返回。

String类中的hashCode计算方法还是比较简单的,就是以31为权,每一位为字符的ASCII值进行运算,用自然溢出来等效取模。

哈希计算公式可以计为s[0]*31^(n-1) + s[1]*31^(n-2) + … + s[n-1]

关于为什么取31为权,看下面的hashcode解释

3.6 jdk9为何要将String的底层实现由char[]改成了byte[]?

这个特性是JDK9放出来的,主要是为了节约String占用的内存。众所周知,在大多数Java程序的堆里,String占用的空间最大,并且绝大多数String只有Latin-1字符,这些Latin-1字符只需要1个字节就够了。JDK9之前,JVM因为String使用char数组存储,每个char占2个字节,所以即使字符串只需要1字节/字符,它也要按照2字节/字符进行分配,浪费了一半的内存空间。JDK9是怎么解决这个问题的呢?一个字符串出来的时候判断,它是不是只有Latin-1字符,如果是,就按照1字节/字符的规格进行分配内存,如果不是,就按照2字节/字符的规格进行分配(UTF-16编码),提高了内存使用率。

3.7 Java的内码为什么Character默认使用UTF-16
  • 因为String有随机访问的方法,所谓随机访问,就是charAt、subString这种方法,随便指定一个数字,String要能给出结果。如果字符串中的每个字符占用的内存是不定长的,那么进行随机访问的时候,就需要从头开始数每个字符的长度,才能找到你想要的字符
  • 但是又有人会问了,UTF-16也是变长的啊,一个字符可能在UTF-16里面占用4个字节咧。是的,是的,UTF-16是变长的,但这是在现实世界里是这样。在java的世界里,一个字符(char)就是2个字节,从\u0000到\uFFFF,占4个字节的字符,在java里是用两个char来存储的,而String的各种操作,都是以java的字符(char)为单位的,charAt是取得第几个char,subString取的也是第几个到第几个char组成的子串,甚至length返回的都是char的个数,从来没有哪个方法可以让你“通过下标取出字符串中第几个’现实意义’中的字符”,所以UTF-16在java的世界里,就可以视为一个定长的编码。
4.字符串完全不可变吗?

不是,可以通过反射获取value数组进行修改

5.在使用 HashMap 的时候,用 String 做 key 有什么好处

HashMap 内部实现是通过 key 的 hashcode 来确定 value 的存储位置,因为字符串是不可变的,所以当创建字符串时,它的 hashcode 被缓存下来,不需要再次计算,所以相比于其他对象更快。

5.Math.round(11.5) 等于多少?Math.round(-11.5)等于多少

Math.round(11.5)的返回值是 12,Math.round(-11.5)的返回值是-11。四舍五入的原理是在参数上加 0.5 然后进行下取整。

6.short s1 = 1; s1 = s1 + 1;有错吗?short s1 = 1; s1 += 1;有错吗

对于 short s1 = 1; s1 = s1 + 1;由于 1 是 int 类型,因此 s1+1 运算结果也是 int型,需要强制转换类型才能赋值给 short 型。

而 short s1 = 1; s1 += 1;可以正确编译,因为 s1+= 1;相当于 s1 = (short(s1 + 1);其中有隐含的强制类型转换。

7.Java中boolean占几个字节

boolean,官方文档未明确定义,它依赖于 JVM 厂商的具体实现

《Java虚拟机规范》一书中的描述:“虽然定义了boolean这种数据类型,但是只对它提供了非常有限的支持。在Java虚拟机中没有任何供boolean值专用的字节码指令,Java语言表达式所操作的boolean值,在编译之后都使用Java虚拟机中的int数据类型来代替,而boolean数组将会被编码成Java虚拟机的byte数组,每个元素boolean元素占8位”。

这样我们可以得出boolean类型占了单独使用是4个字节,在数组中又是1个字节

8. 字符串拼接后比较
 		  String str1 = "str";
		  String str2 = "ing";
		 
		  String str3 = "str" + "ing";//常量池中的对象
		  String str4 = str1 + str2; //在堆上创建的新的对象	  
		  String str5 = "string";//常量池中的对象
		  System.out.println(str3 == str4);//false
		  System.out.println(str3 == str5);//true
		  System.out.println(str4 == str5);//false

尽量避免多个字符串拼接,因为这样会重新创建对象。如果需要改变字符串的话,可以使用 StringBuilder 或者 StringBuffer。

9. String s1 = new String(“abc”);这句话创建了几个字符串对象?

将创建 1 或 2 个字符串。如果池中已存在字符串常量“abc”,则只会在堆空间创建一个字符串常量“abc”。如果池中没有字符串常量“abc”,那么它将首先在池中创建,然后在堆空间中创建,因此将创建总共 2 个字符串对象。

10. 8 种基本类型的包装类和常量池

Java 基本类型的包装类的大部分都实现了常量池技术,即 Byte,Short,Integer,Long,Character,Boolean;前面 4 种包装类默认创建了数值[-128,127] 的相应类型的缓存数据Character创建了数值在[0,127]范围的缓存数据,Boolean 直接返回True Or False,两种浮点数类型的包装类 Float,Double 并没有实现常量池技术

如果超出对应范围仍然会去创建新的对象。

11. Integer比较
  Integer i1 = 40;
  Integer i2 = 40;
  Integer i3 = 0;
  Integer i4 = new Integer(40);
  Integer i5 = new Integer(40);
  Integer i6 = new Integer(0);
  
  System.out.println("i1=i2   " + (i1 == i2));
  System.out.println("i1=i2+i3   " + (i1 == i2 + i3));
  System.out.println("i1=i4   " + (i1 == i4));
  System.out.println("i4=i5   " + (i4 == i5));
  System.out.println("i4=i5+i6   " + (i4 == i5 + i6));   
  System.out.println("40=i5+i6   " + (40 == i5 + i6));  
i1=i2   true
i1=i2+i3   true
i1=i4   false
i4=i5   false
i4=i5+i6   true
40=i5+i6   true

语句 i4 == i5 + i6,因为+这个操作符不适用于 Integer 对象,首先 i5 和 i6 进行自动拆箱操作,进行数值相加,即 i4 == 40。然后 Integer 对象无法与数值进行直接比较,所以 i4 自动拆箱转为 int 值 40,最终这条语句转为 40 == 40 进行数值比较

12. finally什么情况下不会执行
  1. 没有进入try语句块
  2. System.exit()
  3. 守护线程被终止
13. Java创建对象的四种方式
  1. new
  2. clone
  3. newInstance
  4. 反序列化
14. Random是怎么实现获取随机数的

Random类中有一个AtomicLong类型的种子数,在获取随机数时通过算法根据当前的种子生成下一个种子,并CAS地修改当前种子,如果修改成功,再做位运算并返回

protected int next(int bits) {
        long oldseed, nextseed;
        AtomicLong seed = this.seed;
        do {
            oldseed = seed.get();
            nextseed = (oldseed * multiplier + addend) & mask;
        } while (!seed.compareAndSet(oldseed, nextseed));
        return (int)(nextseed >>> (48 - bits));
    }

三. 面向对象

0.面向对象和面向过程的区别
  • 1)面向过程是具体化的,流程化的,解决一个问题,你需要一步一步的分析,一步一步的实现。

  • 优点:性能比面向对象高,因为类调用时需要实例化,开销比较大,比较消耗资源

  • 缺点:没有面向对象易维护、易复用、易扩展

  • 2)面向对象是模型化的,你只需抽象出一个类,这是一个封闭的盒子,在这里你拥有数据也拥有解决问题的方法

1.简述面向对象的特性
  • 封装:建议成员变量私有,然后提供公有的getter/setter方法来获取值/赋值,封装的核心思想是合理隐藏,合理暴露,可以提高安全性,实现代码的组件化。
  • 继承:提高代码的复用性,相同代码可写到父类,子类的功能更加强大,不仅得到了父类的功能,还有自己的功能。
  • 多态同一个类型的对象执行相同的行为,在不同的状态下表现出不同的特征。多态可以降低类之间的耦合度,右边对象可以实现组件化切换,业务功能随之改变,便于扩展和维护

关于继承如下 3 点请记住:

  1. 子类拥有父类对象所有的属性和方法(包括私有属性和私有方法),但是父类中的私有属性和方法子类是无法访问,只是拥有
  2. 子类可以拥有自己属性和方法,即子类可以对父类进行扩展。
  3. 子类可以用自己的方式实现父类的方法。
2.列举Object类的方法
  • equals(Object obj):判断其他对象是否与当前对象相等。
  • toString():打印当前对象的字符串表示。
  • wait():导致当前线程等待,等待其他线程唤醒,会释放锁。
  • notify()/notifyAll():随机唤醒一个/全部线程。
  • hashCode():返回当前对象的hashCode值。
  • finalize():当垃圾回收器要回收对象前调用。
  • clone():创建并返回对象的一个副本。
3.方法重载和方法重写的区别
  • 方法重载是同一个类中具有不同参数列表的同名方法与方法返回值和访问修饰符无关!,即重载的方法不能根据返回类型进行区分),方法重写是子类中具有和父类相同参数列表的同名方法,会覆盖父类原有的方法。
  • 重载的返回值类型和权限修饰符,异常抛出类型没有要求,
  • 重写要求:
  1. 方法的返回值类型小于等于父类被重写方法的返回值类型
  2. 修饰符权限大于等于父类被重写方法权限修饰符
  3. 抛出的异常类型小于等于父类被重写方法抛出的异常类型
¥4.接口和抽象类有什么区别
  1. 接口中只能定义public staic final修饰的常量,抽象类中可以定义普通变量
  2. 接口和抽象类都不能实例化,但接口没有构造器,抽象类有构造器
  3. 接口可以多实现,抽象类只能单继承。
  4. 接口在JDK1.8之前只能定义public abstract修饰的方法,JDK1.8开始可以定义默认方法和静态方法,JDK1.9开始可以定义私有方法,抽象类中的方法没有限制。
¥5.什么时候应该使用接口,什么时候应该使用抽象类
  1. 如果知道某个类应该成为基类,那么第一选择应该是让它成为一个接口只有在必须要有方法定义和成员变量的时候,才应该选择抽象类
  2. 在接口和抽象类的选择上,必须遵守这样一个原则:行为模型应该总是通过接口而不是抽象类定义。通过抽象类建立行为模型会出现的问题:由于Java不允许多继承,而如果是接口的话只需要同时实现两个接口即可。
6.内部类有什么作用?有哪些分类?

1)

  • 一个内部类对象可以访问创建它的外部类对象的内容,包括私有数据!
  • 内部类不为同一包的其他类所见,具有很好的封装性
  • 内部类有效实现了“多重继承”,优化 java 单继承的缺陷。
  • 匿名内部类可以很方便的定义回调

2)

  • 静态内部类:由static修饰,属于类本身,只加载一次。类可以定义的成分静态内部类都可以定义,可以访问外部类的静态变量和方法,通过new 外部类.静态内部类构造器来创建对象。
  • 成员内部类:属于外部类的每个对象,随对象一起加载。不可以定义静态成员和方法,可以访问外部类的所有内容,通过new 外部类构造器.new 成员内部类构造器来创建对象。
  • 局部内部类:定义在方法、构造器、代码块、循环中。只能定义实例成员变量和实例方法,作用范围仅在局部代码块中。
  • 匿名内部类:没有名字的局部内部类,可以简化代码,匿名内部类会立即创建一个匿名内部类的对象返回,对象类型相当于当前new的类的子类类型。
7.什么是多态机制?Java语言是如何实现多态的?
  • 多态是指程序中定义的引用变量所指向的具体类型和通过该引用变量发出的方法调用在编程时并不确定,而是在程序运行期间才确定。
  • 多态分为编译时多态和运行时多态。其中编辑时多态是静态的(静态分派),主要是指方法的重载,它是根据参数列表的不同来区分不同的函数,通过编辑之后会变成两个不同的函数。而运行时多态是动态的,它是通过动态绑定来实现的,运行时才将符号引用转换为直接引用,也就是我们所说的多态性。
8.抽象类能使用 final 修饰吗?

不能,定义抽象类就是让其他类继承的,如果定义为 final 该类就不能被继承,这样彼此就会产生矛盾,所以 final 不能修饰抽象类

9.在Java中定义一个不做事且没有参数的构造方法的作用

Java程序在执行子类的构造方法之前,如果没有用super()来调用父类特定的构造方法,则会调用父类中“没有参数的构造方法”。因此,如果父类中只定义了有参数的构造方法,而在子类的构造方法中又没有用super()来调用父类中特定的构造方法,则编译时将发生错误,因为Java程序在父类中找不到没有参数的构造方法可供执行。解决办法是在父类里加上一个不做事且没有参数的构造方法。

9.5 若一个类没有声明构造方法,该程序能正确执行吗? 为什么?

可以执行。因为一个类即使没有声明构造方法也会有默认的不带参数的构造方法

如果我们自己添加了类的构造方法(无论是否有参),Java 就不会再添加默认的无参数的构造方法了,这时候,就不能直接 new 一个对象而不传递参数了。因此如果我们重载了有参的构造方法,记得都要把无参的构造方法也写出来(无论是否用到),因为这可以帮助我们在创建对象的时候少踩坑。

10.在调用子类构造方法之前会先调用父类没有参数的构造方法,其目的是?

帮助子类做初始化工作。

11.内部类中的this
public class Outer {
    private int age = 12;

    class Inner {
        private int age = 13;
        public void print() {
            int age = 14;
            System.out.println("局部变量:" + age);
            System.out.println("内部类变量:" + this.age); //this引用内部类实例
            System.out.println("外部类变量:" + Outer.this.age); //Outer.this引用外部类实例
        }
    }

    public static void main(String[] args) {
        Outer.Inner in = new Outer().new Inner();
        in.print();
    }

}

运行结果:
局部变量:14
内部类变量:13
外部类变量:12
12.构造器(constructor)是否可被重写和重载

构造器不能被继承,因此不能被重写,但可以被重载。

13.hashCode()与equals()的相关规定
  • 如果两个对象相等,则hashcode一定也是相同的
  • 两个对象相等,对两个对象分别调用equals方法都返回true
  • 两个对象有相同的hashcode值,它们也不一定是相等的

因此,equals 方法被覆盖过,则 hashCode 方法也必须被覆盖

13.5 解决Hash冲突的方法有哪些
  • 开放定址法: 一旦发生了冲突,就去寻找下一个空的散列地址,只要散列表足够大,空的散列地址总能找到,并将记录存入
  • 再哈希法: 有多个不同的Hash函数,当发生冲突时,使用第二个,第三个,….,等哈希函数
  • 链地址法
13.6 JDK哪些类用开放地址法解决哈希冲突

ThreadLocalMap中使用开放地址法来处理散列冲突,因为在ThreadLocalMap中的散列值分散得十分均匀,很少会出现冲突。并且ThreadLocalMap经常需要清除无用的对象,使用纯数组更加方便。

13.7 Hashcode的底层原理

Object的hashcode底层是一个native方法

public native int hashCode();

其具体实现跟对象的内存地址相关

13.8 如何重写一个hashcode

生成一个 int 类型的变量 result,并且初始化一个值,比如17

对类中每一个重要字段,也就是影响对象的值的字段,也就是 equals 方法里有比较的字段,进行以下操作:a. 计算这个字段的值 filedHashValue = filed.hashCode(); b. 执行 result = 31 * result + filedHashValue;

之所以使用31,有两个原因:

  1. 31是质子数中一个“不大不小”的存在,如果你使用的是一个如2的较小质数,那么得出的乘积会在一个很小的范围,很容易造成哈希值的冲突。而如果选择一个100以上的质数,得出的哈希值会超出int的最大范围,这两种都不合适。
  2. 31 可以被 JVM 优化。31转换成位运算很方便, 如31 * i = (i << 5) - i,JVM就可以高效的进行计算啦。。。
14. 值传递与引用传递

Java 中只有值传递,方法得到的是所有参数值的一个拷贝

  • 一个方法不能修改一个基本数据类型的参数
  • 一个方法可以改变一个对象参数的状态。
  • 一个方法不能让对象参数引用一个新的对象。
15. 成员变量与局部变量的区别
  1. 从语法形式上看:成员变量是属于类的,而局部变量是在代码块或方法中定义的变量或是方法的参数;成员变量可以被 public,private,static 等修饰符所修饰,而局部变量不能被访问控制修饰符及 static 所修饰;但是,成员变量和局部变量都能被 final 所修饰
  2. 从变量在内存中的存储方式来看:如果成员变量是使用static修饰的,那么这个成员变量是属于类的,如果没有使用static修饰,这个成员变量是属于实例的。而对象存在于堆内存,局部变量则存在于栈内存
  3. 从变量在内存中的生存时间上看:成员变量是对象的一部分,它随着对象的创建而存在,而局部变量随着方法的调用而自动消失
  4. 成员变量如果没有被赋初值:则会自动以类型的默认值而赋值(一种情况例外:被 final 修饰的成员变量也必须显式地赋值),而局部变量则不会自动赋值
16. this、super能否用在static方法中

this、super不能用在static方法中。被 static 修饰的成员属于类,不属于单个这个类的某个对象,被类中所有对象共享。而 this 代表对本类对象的引用,指向本类对象;而 super 代表对父类对象的引用,指向父类对象;所以, this和super是属于对象范畴的东西,而静态方法是属于类范畴的东西

17. static{}静态代码块与{}非静态代码块(构造代码块)
  • 静态代码块在非静态代码块之前执行(静态代码块 -> 非静态代码块 -> 构造方法)。静态代码块只在第一次new执行一次,之后不再执行,而非静态代码块在每new一次就执行一次。 非静态代码块可在普通方法中定义(不过作用不大);而静态代码块不行。
  • 一般情况下,如果有些代码比如一些项目最常用的变量或对象必须在项目启动的时候就执行的时候,需要使用静态代码块
18. 非静态代码块与构造函数的区别

非静态代码块是给所有对象进行统一初始化,而构造函数是给对应的对象初始化,因为构造函数是可以多个的,运行哪个构造函数就会建立什么样的对象,但无论建立哪个对象,都会先执行相同的构造代码块。也就是说,构造代码块中定义的是不同对象共性的初始化内容。

19. protected 关键字

protected修饰的成员对于本包和其子类可见

protected的可见性在于两点:

  1. 基类的protected成员是包内可见的,并且对子类可见;
  2. 若子类与基类不在同一包中,那么在子类中,子类实例可以访问其从基类继承而来的protected方法而不能访问基类实例的protected方法

四. 泛型

¥1.泛型和泛型擦除是什么
  • 泛型提供了编译时更强的类型安全检测机制,避免了编写代码时的强制类型转换
  • 在编译阶段采用泛型时加上的类型参数,会被编译器在编译时去掉,改为强制类型转换,这个过程就被称为类型擦除,因此泛型主要用于编译阶段,在编译后生成的Java字节代码文件中不包含泛型中的类型信息。
2. 泛型标记的规范
  • E:值Element,在集合中使用,表示在集合中存放的元素。
  • T:指Type,表示Java类,包括基本的类以及自定义类。
  • K:指Key,表示键,例如Map集合中的Key。
  • V:指Value,表示值,例如Map集合中的Value。
  • N:指Number,表示数值类型。
  • ?:表示不确定的Java类型。
3. 泛型的使用

泛型类:

public class Generic<T>{
    private T key;
}

泛型接口:

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

泛型方法:

 public static < E > void printArray( E[] inputArray )
{
         for ( E element : inputArray ){
            System.out.printf( "%s ", element );
         }
}
4. List[T]、List[?]、List[Object]的区别
  • List[T]是确定的某一个类型,表示的是List集合中的元素都为T类型,具体类型在运行期决定;List<?>表示任意类型List[Object]则表示List集合中的所有元素为Object类型,因为Object是所有类的父类,所以List[Object]也可以容纳所有的类类型,
  • List[T]可以进行诸如add、remove等操作,因为它的类型是固定的T类型,在编码期 不需要进行任何的转型操作;
  • List[?]是只读类型的,不能进行增加、修改操作,因为编译器不知道List中容纳的是 什么类型的元素,也就无毕校验类型是否安全了,而且List<?>读取出的元素都是Object类型的,需要主动转型,所以它经常用于泛型方法的返回值。
  • List[Object]也可以读写操作,但是它执行写入操作时需要向上转型(Upcast),在读 取数据后需要向下转型(Downcast),而此时已经失去了泛型存在的意义了

五. 异常

1.异常有哪些分类?出现的原因是什么?
  • Throwable是所有错误和异常的父类,Throwable分为Error和Exception。
  • Error:Java程序运行错误,出现Error通常是因为系统的内部错误或资源耗尽,Error不能在运行过程中被动态处理,如果程序运行中出现Error,系统只能记录错误的原因和安全终止
  • Exception指Java程序运行异常,即运行中发生了不期望的情况,分为RuntimeException和CheckedException
  1. RuntimeException指在Java虚拟机正常运行期间抛出的异常,可以被捕获并处理,例如空指针异常,数组越界等。
  2. CheckedException指编译阶段强制要求捕获并处理的异常,例如IO异常,SQL异常等。
    ( 除了RuntimeException及其子类以外,其他的Exception类及其子类都属于检查异常 )
2. Throwable 类常用方法
  • public string getMessage():返回异常发生时的简要描述
  • public string toString():返回异常发生时的详细信息
  • public string getLocalizedMessage():返回异常对象的本地化信息。使用 Throwable 的子类覆盖这个方法,可以生成本地化信息。如果子类没有覆盖该方法,则该方法返回的信息与 getMessage()返回的结果相同
  • public void printStackTrace():在控制台上打印 Throwable 对象封装的异常信息
3. try-with-resources

在 try-with-resources 语句中,任何 catch 或 finally 块在声明的资源关闭后运行

try (Scanner scanner = new Scanner(new File("test.txt"))) {
    while (scanner.hasNext()) {
        System.out.println(scanner.nextLine());
    }
} catch (FileNotFoundException fnfe) {
    fnfe.printStackTrace();
}

面对必须要关闭的资源,我们总是应该优先使用 try-with-resources 而不是try-finally。随之产生的代码更简短,更清晰,产生的异常对我们也更有用。try-with-resources语句让我们更容易编写必须要关闭的资源的代码,若采用try-finally则几乎做不到这点。

六. 反射

1.反射的基本概念,优缺点和使用场景
  • 在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法,对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称为Java的反射机制。
  • 优点是运行时动态获取类的全部信息,缺点是破坏了类的封装性,泛型的约束性。且在性能上反射相当于一系列解释操作,通知 JVM 要做的事情,性能比直接的 java 代码要慢很多
  • 是框架的核心灵魂,动态代理设计模式采用了反射机制,还有 Spring、Hibernate 等框架也大量使用到了反射机制。
2.获取Class对象有哪几种方式?能通过Class对象获取类的哪些信息?
  1. 通过类名.class(知道具体类的情况下可以使用,但是我们一般是不知道具体类的,基本都是通过遍历包下面的类来获取 Class 对象,通过此方式获取Class对象不会进行初始化
  2. 通过对象.getClass()(通过对象实例获取)
  3. 通过Class.forName(类的全限名);(默认需要初始化
  4. 通过类加载器xxxClassLoader.loadClass()传入类路径获取
    如:class clazz = ClassLoader.LoadClass("cn.javaguide.TargetObject");
    通过类加载器获取Class对象不会进行初始化,意味着不进行包括初始化等一些列步骤,静态块和静态对象不会得到执行
  • 可以通过Class对象获取类的成员变量,方法或构造器。带declared的获取方法可以获取到类的一个或全部成员变量,方法,构造器,不带declared的方法只能获取到类的public修饰的成员变量、方法或构造器,包括父类public修饰的成员变量、方法或构造器。

七. 注解

1.注解是什么,元注解是什么,有什么作用?

1)注解是一种标记,可以使类或接口附加额外的信息,是帮助编译器和JVM完成一些特定功能的。
2)元注解就是自定义注解的注解,包括

  • @Target:用来约束注解的位置,值是ElementType枚举类,包括METHOD方法、VARIABLE变量、TYPE类/接口、PARAMETER方法参数、CONSTRUCTORS构造器和LOACL_VARIABLE局部变量;
  • @Rentention:用来约束注解的生命周期,值是RetentionPolicy枚举类,包括:SOURCE源码、CLASS字节码和RUNTIME运行时;
  • @Documented:表明这个注解应该被javadoc工具记录;-
  • @Inherited:表面某个被标注的类型是被继承的。

八. I/O流

1. Java 中 IO 流分为几种
  • 按照流的流向分,可以分为输入流输出流
  • 按照操作单元划分,可以划分为字节流字符流
  • 按照流的角色划分为节点流处理流
2. 既然有了字节流,为什么还要有字符流?

字符流是由 Java 虚拟机将字节转换得到的,问题就出在这个过程还算是非常耗时,并且,如果我们不知道编码类型就很容易出现乱码问题。所以, I/O 流就干脆提供了一个直接操作字符的接口,方便我们平时对字符进行流操作如果音频文件、图片等媒体文件用字节流比较好,如果涉及到字符的话使用字符流比较好

3. Java 序列化中如果有些字段不想进行序列化,怎么办?

对于不想进行序列化的变量,使用 transient 关键字修饰。

4. 序列化版本号serialVersionUID的作用

Java的序列化机制是通过在运行时判断类的serialVersionUID来验证版本一致性的

在进行反序列化时,JVM会把传来的字节流中的serialVersionUID与本地相应实体(类)的serialVersionUID进行比较,如果相同就认为是一致的,可以进行反序列化,否则就会出现序列化版本不一致的异常

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值