总结Java基础面试题

Java基础部分

1. int、char、long各占多少位?

类型

位数

字节数

boolen

8

1

int

32

4

float

32

4

double

64

8

char

16

2

byte

8

1

short

16

2

long

64

8

2. int和Integer的区别?

Java 为每一个基本数据类型都引入了对应的包装类,从Java 5 开始引入了自动装箱 / 拆箱机制,使得两者可以相互转化。所以最基本的一点区别就是:Integer 是int的包装类,int的初始值为零,而Integer的初值为null。int与Integer相比,会把Integer自动拆箱为int再去比。

3. 分别讲讲 final,static,synchronized 关键字可以修饰什么,以及修饰后的作用?

  • static修饰变量、方法、内部类,静态变量和静态方法不用创建对象也能直接通过类名调用, 静态内部类可以当作普通类使用而不需要实例外部类;
  • final修饰的变量不能被重新赋值;方法不能被子类重写;类不能被继承;
  • synchronized 修饰方法和代码块,表示同一时间只能被一个线程访问,确保线程互斥的访问同步代码;
  • volatile 修饰的成员变量在每次被线程访问时,都强制从共享内存中重新读取该成员变量的值。而且,当成员变量发生变化时,会强制线程将变化值回写到共享内存。这样在任何时刻,两个不同的线程总是看到某个成员变量的同一个值;

4. 请简述一下String、StringBuffer和StringBuilder的区别?

  • String 为字符串常量,一旦创建不可以被修改,是线程安全的;String 类使用 final 修饰符,不可以被继承;String 的长度是不变的。适用于少量操作的字符串。
  • StringBuffer 为字符串变量,长度是可变的,线程安全。适用于多线程下在字符缓冲区进行大量字符串操作;
  • StringBuilder 为字符串变量,长度是可变的,线程不安全。适用于单线程下在字符缓冲区进行大量字符串操作。
  • 字符串操作在执行速度:(快)StringBuilder > StringBuffer > String(慢)

5. “equals”与“==”、“hashCode”的区别和使用场景?

  • “==”:Java中的数据类型可分为两类,基本数据类型和引用类型。基本数据类型:byte、short、char、int、long、float、double、boolean。他们之间的比较用“==”,比较的是值;引用类型(类、接口、数组等)之间用“==”进行比较的时候,比较的是栈中的值,也就是他们在内存中的存放地址。因为对象是放在堆中的,栈中存放的是对象的引用(地址)。所以如果要比较堆中对象是否相同,那么就要重写equals方法了。
  • “equals”:如果一个类没有重写equals 方法 那么就是调用 object 的 equals 其实就是 “==”;
  • hashcode( ) 方法返回一个 int 数,在object 类中的默认实现是 “将该对象的内存地址 转换成一个整数返回”
  • 使用场景:equals:一般比较内容相等 比如字符串相等,==一般比较数值 或者null判断,hashcode我们一般用来判断来两个对象是否相等;(一般比较两个对象是否相等,先判断其 hashCode,后判断 equals。hashCode 不相等,肯定就不是同一对象。hashCode 相等,则 equals() 不一定相等。hash冲突发生概率很低)

6. Java中浅拷贝和深拷贝的区别?

  • 同:对基本数据类型值传递
  • 异:浅拷贝-对引用数据类型进行引用传递的拷贝

深拷贝-对引用数据类型,创建一个新的对象并复制其内容

7. Error和Exception的区别?

  • 错误是不可预料和捕获的,发生后会直接导致JVM不可处理或者不可恢复的情况,只能通过修改代码,使错误不再出现;eg:内存溢出、线程死锁;
  • 异常是可预料可捕获的,可以通过捕获处理异常,或者抛出异常;
  • 异常又分为运行时异常(可以忽略,如空指针、下标越界、算数异常、类型转换)和编译时异常(必须try-catch捕获)

8. Java异常体系?

定义:异常是阻止当前方法继续执行的问题,如:文件找不到、网络连接失败、非法参数等等。发现异常的理想时期是编译阶段,然而编译阶段不能找出所有的异常,余下的问题必须在运行期间。

分类:Java中的异常分为可查异常和不可查异常。

可查异常:

即编译时异常,只编译器在编译时可以发现的错误,程序在运行时很容易出现的异常状况,这些异常可以预计,所以在编译阶段就必须手动进行捕捉处理,即要用try-catch语句捕获它,要么用throws子句声明抛出,否者编译无法通过,如:IOException、SQLException以及用户自定义Exception异常。

不可查异常:

不可查异常包括**运行时异常**和**错误**,他们都是在程序运行时出现的。异常和错误的区别:异常能被程序本身处理,错误是无法处理的。**运行时异常指的是**程序在运行时才会出现的错误,由程序员自己分析代码决定是否用try...catch进行捕获处理。如空指针异常、类转换异常、数组下标越界异常等等。**错误是指**程序无法处理的错误,表示运行应用程序中较严重的问题,如系统崩溃、虚拟机错误、动态连接失败等等。大多数错误与代码编写者执行的操作无关,而表示代码运行时JVM出现的问题。

 

 

 

 

从上图可以看出Java通过API中Throwable类的众多子类描述各种不同的异常。因而,Java异常都是对象,是Throwable子类的实例。

异常处理机制:

当异常发生时,将使用new在堆上创建一个异常对象,对于这个异常对象,有两种处理方式:

  1. 使用throw关键字将异常对象抛出,则当前执行路径被终止,异常处理机制将在其他地方寻找catch快对异常进行处理
  2. 使用try...catch在当前逻辑中进行捕获处理

throws 和 throw:

throws:一个方法在声明时可以使用throws关键字声明可能会产生的若干异常

throw:抛出异常,并退出当前方法或作用域

使用 finally 清理:

在 Java 中的 finally 的存在并不是为了释放内存资源,因为 Java 有垃圾回收机制,因此需要 Java 释放的资源主要是:已经打开的文件或者网络连接等。

在 try 中无论有没有捕获异常,finally 都会被执行。

9. Java中IO流分为几种?BIO、NIO、AIO有什么区别?

按照流的的操作单元划分:可以分为字节流(按照8位传输)和字符流(2个字节的Unicode字符);

  • BIO:Block IO 同步阻塞式 IO,一个连接一个线程,就是我们平常使用的传统 IO,即客户端有连接请求时服务器端就需要启动一个线程进行处理,如果这个连接不做任何事情会造成不必要的线程开销,当然可以通过线程池机制改善。它的特点是模式简单使用方便,并发处理能力低。
  • NIO:Non IO 同步非阻塞 IO,一个请求一个线程,客户端发送的连接请求都会注册到多路复用器上,多路复用器轮询到连接有I/O请求时才启动一个线程进行处理。是传统 IO 的升级,客户端和服务器端通过 Channel(通道)通讯,实现了多路复用。
  • AIO:Asynchronous IO 异步非堵塞 IO,一个有效请求一个线程,是 NIO 的升级,也叫 NIO2,客户端的I/O请求都是由OS先完成了再通知服务器应用去启动线程进行处理。异步 IO 的操作基于事件和回调机制。

10. 谈谈对Java多态的理解?实现多态的机制/原理?

定义:多态即同一个行为具有多个不同表现形式或形态的能力。

在Java中,我们在程序中定义的引用变量所指向的具体类型和通过该引用变量的方法调用在编程的时候并不确定,当处于运行期间才确定。就是这个引用变量究竟指向哪一个实例对象,在编译期间是不确定的,只有运行期才能确定,这样不用修改源码就可以把变量绑定到不同的类实例上,让程序拥有了多个运行状态,这就是多态。

多态的实现原理即两种分派:

  • 静态分派(实现编译时多态):根据变量的「静态类型(外观类型)」匹配调用方法的过程称为静态分派。发生的场景为方法重载(Overload)。
  • 动态分派(实现运行时多态):根据变量的「实际类型」匹配调用方法的过程称为动态分派。发生的场景为方法重写(Override)。当调用一个可能被子类重写或继承的方法时,就会触发动态分派。

多态的存在有三个前提:要有继承关系、子类重写父类方法、父类引用指向子类对象(Parent p = new Child();)。

多态优点:消除类耦合

多态缺点:不能使用子类特有的成员属性和子类特有的成员方法。如果非要用到不可,可以强制类型转换。

实现多态的具体方式:

  • 重写:这个内容已经在上一章节详细讲过,就不再阐述,详细可访问:Java 重写(Override)与重载(Overload)。
  • 接口:java中的接口就是一些方法特征的集合,但没有方法的实现。具体可以看 java接口 这一章节的内容。
  • 抽象类和抽象方法

11. 什么是内部类?内部类的作用?

内部类就是在一个类的内部定义一个类。

Java 常见的内部类有四种:

  • 成员内部类:成员内部类相当于外部类的一个成员,成员内部类可以无条件访问外部类的所有成员属性和成员方法(包括private成员和静态成员)。
  • 方法(局部)内部类:定义在一个方法或者一个作用域里面的的类,它和成员内部类的区别在于局部内部类的访问权限仅限于方法内或者该作用域内。注意,局部内部类就像是方法里面的一个局部变量一样,是不能有public、protected、private以及static修饰符的。
  • 静态内部类:定义在另一个类里面的类,只不过在类的前面多了一个关键字static。静态内部类是不需要依赖外部类的,这点和类的静态成员属性有点相似,并且它不能使用外部类的非static成员变量或者方法。这点很好理解,因为在没有外部类的对象的情况下,可以创建静态内部类的对象,如果允许访问外部类的非static成员就会产生矛盾,因为外部类的非static成员必须依赖于具体的对象。
  • 匿名内部类:不能有访问修饰符和static修饰符的,一般用于编写监听代码。匿名内部类是唯一一个没有构造器的类。正因为没有构造器,所以匿名内部类的适用范围非常有限,大部分匿名内部类用于接口回调。一般来说,匿名内部类用于继承其他类或者实现接口,并不需要增加额外的方法,只是对继承方法的实现或者是重写。

内部类可以很好的实现隐蔽,一般的非内部类,是不允许有private和protectd等权限的,但内部类(除方法内部类)可以通过这些修饰符来实现隐蔽。内部类拥有外部类的访问权限(分静态与非静态情况),通过这一特性可以比较好的处理类之间的关联性,将一类事物的流程放在一起内部处理。通过内部类可以实现多重继承,Java默认是单继承的,我们可以通过多个内部类继承实现多个父类,接着由于外部类完全可访问内部类,所以就实现了类似多继承的效果。

12. 抽象类和接口的区别?

抽象类使用abstract class 定义,抽象类既可以有抽象方法也可以有其他类型的方法,既可以有静态属性也可以有非静态属性。

接口使用interface定义,属性用public final static 修饰,如果没有写关键字,虚拟机默认会加上关键字。JDK8之后接口里的方法既可以有抽象方法也可以有默认方法。

  • 构造方法:抽象类是一种类别,具有构造方法。接口是一种行为规范,没有构造方法。
  • 继承方式:一个类只能继承一个抽象类,而一个类却可以实现多个接口;接口和接口之间可以多继承。
  • 使用方式:抽象类可以由默认的方法实现,接口根本不存在方法的实现。实现抽象类使用extends关键字来继承抽象类,并提供抽象类中所有抽象方法的实现。子类使用关键字implements来实现接口,它需要提供接口中所有声明的方法的实现。
  • 成员变量:抽象类中的成员变量可以是各种类型的,而接口中的成员变量只能是 public static final 类型的;
  • 成员方法:抽象类的方法可以有public、protectd 和 default 这些修饰符,而接口的方法默认修饰符是public,不可以使用其他修饰符;
  • 速度:抽象类访问速度比接口速度要快,这是因为接口需要时间去寻找类中实现的方法;
  • 意义:接口更多的为了约束类的行为,可用于解耦,而抽象类更加侧重于代码复用。
  • 小声哔哔:抽象类可以没有方法和属性,但是含有抽象方法的类一定是抽象类。

13. 抽象类和接口的意义和应用场景?

抽象类的意义:为子类提供一个公共的类型;封装子类中重复的内容;定义有抽象方法,子类虽然有不同的实现,但该方法的定义是一致的;

接口的意义:规范、扩展、回调。对一些类是否具有某个功能非常关心,但不关心功能的具体实现,那么就需要这些类实现一个接口。

  • 如果你拥有一些方法并且想让它们中的一些默认实现,那么就用抽象类;
  • 如果你想实现多继承,那么必须实用接口;
  • 如果基本功能在不断变化,那么就需要使用抽象类;
  • 如果不断改变基本功能并且使用接口,那么就要改变所有实现了该接口的类;

14. final、finally、finalize的区别?

  • final用于声明属性、方法和类,分别表示属性不可变,方法不可覆盖,类不可继承。
  • finally是异常处理语句结构的一部分,表示总是执行。
  • finalize是Object类的一个方法,在垃圾收集器执行的时候会回调被回收对象的finalize()方法,可以覆盖此方法提供垃圾收集时其他资源的回收,例如关闭文件。

15. 泛型的原理?Java泛型中类型擦除的理解?

泛型的原理:类型擦除

  • Java中的泛型基本上都是在编译器这个层次来实现的。在生成的Java字节码中是不包含泛型中的类型信息的。使用泛型的时候加上的类型参数,会在编译器在编译的时候去掉。这个过程就称为类型擦除。
  • 声明了泛型的 .java 源代码,在编译生成 .class 文件之后,泛型相关的信息就消失了。可以认为,源代码中泛型相关的信息,就是提供给编译器用的。泛型信息对 Java 编译器可以见,对 Java 虚拟机不可见。如在代码中定义的 List<object> 和 List<String> 等类型,在编译后都会变成List。JVM看到的只是List,而由泛型附加的类型信息对JVM来说是不可见的。

总结:Java 中的泛型有 3 种形式,泛型方法,泛型类,泛型接口。Java 通过在编译时类型擦除的方式来实现泛型。擦除时使用 Object 或者界定类型替代泛型,同时在要调用具体类型方法或者成员变量的时候插入强转代码,为了保证多态特性,Java 编译器还会为泛型类的子类生成桥接方法。类型信息在编译阶段被擦除之后,程序在运行期间无法获取类型参数所对应的具体类型。

16. 泛型通配符?泛型中的extends和super的区别?

泛型通配符:

  • 约定俗成的通配符

E - Element (在集合中使用,因为集合中存放的是元素)

T - Type(Java 类)

K - Key(键)

V - Value(值)

N - Number(数值类型)

? - 表示不确定的 java 类型

  • 无边界的通配符(Unbounded Wildcards),

采用 <?> 的形式,比如 List<?>,无边界的通配符的主要作用就是让泛型能够接受未知类型的数据。

  • 固定上边界的通配符(Upper Bounded Wildcards)

使用固定上边界的通配符的泛型,就能够接受指定类及其子类类型的数据。要声明使用该类通配符,采用 <? extends E> 的形式,这里的 E 就是该泛型的上边界。

注意:这里虽然用的是 extends 关键字,却不仅限于继承了父类 E 的子类,也可以代指实现了接口 E 的类。

  • 固定下边界的通配符(Lower Bounded Wildcards)

使用固定下边界的通配符的泛型,就能够接受指定类及其父类类型的数据。要声明使用该类通配符,采用 <? super E> 的形式,这里的 E 就是该泛型的下边界。

注意:你可以为一个泛型指定上边界或下边界,但是不能同时指定上下边界。

?和 T 都表示不确定的类型,区别在于我们可以对 T 进行操作,但是对 ?不行,T 是一个 确定的 类型,通常用于泛型类和泛型方法的定义,?是一个 不确定 的类型,通常用于泛型方法的调用代码和形参,不能用于定义类和泛型方法。

17. 说说你对String的了解?String为什么设计成不可变的?

基础:Java 提供了 String 类来创建和操作字符串。需要明确的是String不是基本数据类型,是引用类型,即储存的是Strng对象的地址,不是String的内容值。String 类是不可改变的,所以你一旦创建了 String 对象,那它的值就无法改变了。如果需要对字符串做很多修改,那么应该选择使用 StringBuffer & StringBuilder 类。

为什么设计成不可变?

  • 字符串常量池的需要:当创建一个 String 对象时,如果此字符串已经存在于常量池中,则不会创建一个新的对象,而是引用已经存在的对象。如果允许改变,那么将导致各种逻辑错误,比如改变一个对象将会影响另一个独立对象,严格来说,这种常量池的思想是一种优化手段;
  • 允许String对象缓存HashCode:Java 中 String 对象的哈希码会被频繁的使用,比如在 hashMap中。字符串的不变形保证了hash码的唯一性,因此可以放放心的进行缓存。这也是一种优化手段,意味着不必每次使用都计算新的哈希码;
  • 安全性:String被许多的类来当做参数,如 网络url,文件路径path 等等,如果String 不是固定的,将会引起各种安全隐患;

18. 谈谈你对Java注解的理解?

  • 定义:注解(Annotion)是一个接口(前加 @),程序可以通过反射来获取指定程序元素的Annotion对象,然后通过Annotion对象来获取注解里面的元数据。
  • 注解可以用于创建文档,跟踪代码中的依赖性,甚至执行基本编译时检查。从某些方面看,Annotion就像修饰符一样被使用,并应用于包、类型、构造方法、方法、成员变量、参数、本地变量的声明中。Annotation 的行为十分类似 public、final 这样的修饰符。每个 Annotation 具有一个名字和成员,每个 Annotation 的成员具有被称为 name=value 对的名字和值,name=value 装载了 Annotation 的信息,也就是说注解中可以不存在成员。
  • 使用注解的基本规则:Annotation 不能影响程序代码的执行,无论增加、删除 Annotation,代码都始终如一的执行。

19. 谈谈Java成员变量、局部变量和静态变量的创建和回收时机?

  • 成员变量:随着类对象的创建而创建,类对象不被引用时,会被标记为可以回收。在下一次内存回收时被回收。
  • 局部变量:随着方法的调用被创建,方法执行完毕,被标记为可以被回收,在下一次内存回收时被回收。
  • 静态变量:当类被加载时,直接创建。注意这里是加载,就算没有创建类对象,只是import,静态变量也会被创建。当类销毁时,被标记为可以回收。在下一次内存回收时,被回收。

20. 面向对象三大特性?

  • 封装:利用抽象数据类型将数据和基于数据的操作封装在一起,使其构成一个不可分离的独立实体。数据被保护在抽象数据类型的内部,尽可能的隐藏内部细节,只保留一些对外接口使之与外部发生联系。用户无需知道对象内部的细节,但可以通过对象对外提供的接口来访问该对象。优点:降低耦合:可以独立开发、测试、优化和修改等;减轻维护的负担:可以更容易的被程序猿理解,并且在调试的时候可以不影响其他模块;有效地调节性能:可以通过剖析确定哪些模块影响了系统的性能;提高软件的可重用性;降低了构建大型系统的分享:即使整个系统不可用,但是这些独立的模块却有可能是可用的;
  • 继承:继承实现了 IS - A 关系,继承应该遵循里氏替换原则,子类对象必须能够替换掉所有父类对象。
  • 多态:多态分为编译时多态和运行时多态。编译时多态主要指方法的重载,运行时多态指程序中定义的对象引用所指向的具体类型在运行期间才确定。

21. 序列化的方式,如何将一个Java对象序列化到文件里?

序列化:把Java对象转换成有序的二进制字节流,以便在网络上传输或者保存在本地文件中。

反序列化:客户端从文件中或网络上获得二进制字节流,进行重建Java对象的过程。

为什么要序列化/应用场景:

  • 数据持久化:长期持有对象
  • 网络传输:用socket传输Java对象

序列化实现:

实现Serializable接口(Java)和实现Parcelable接口(Android)。

序列化步骤:

1)定义一个类,实现Serializable接口;

2)在程序代码中创建对象后,创建对象输出流ObjectOutputStream对象并在构造参数中指定流的输出目标(比如一个文件),通过objectOutputStream.writeObject(obj)把对象序列化并输出到流目标处;

反序列化步骤:

3)在需要提取对象处:创建对象输入流ObjectInputStream对象并在构造参数中指定流的来源,然后通过readObject()方法获取对象,并通过强制类型转换赋值给类对象引用。

22. Serializable 和 Parcelable 的区别?

  • Serializable 是 Java的序列化接口。特点是简单,直接实现该接口就行了,其他工作都被系统完成了,但是对于内存开销大,序列化和反序列化需要很多的 I/O 流操作。
  • Parcelable 是Android的序列化方式,主要用于在内存序列化上,采用该方法需要实现该接口并且手动实现序列化过程,相对复杂点。如果序列化到存储设备(文件等)或者网络传输,比较复杂,建议用Serializable 方式。

最大的区别就是存储媒介的不同:Serializable 使用IO读写存储在硬盘上,Parcelable 是直接在内存中读写,内存读写速度远大于IO读写,所以Parcelable 更加高效。Serializable在序列化过程中会产生大量的临时变量,会引起频繁的GC,效率低下。

23.静态方法能否被子类重写?

不能,子类继承父类后,非静态方法覆盖父类的方法,父类的静态方法被隐藏。

24.静态属性和静态方法是否可以被继承?是否可以被重写?

静态属性和静态方法可以被继承,但是没有被重写而是被隐藏。

静态方法和属性是属于类的,调用的时候直接通过 类名.方法名 完成,不需要继承机制就可以调用。如果子类里面定义了静态方法和属性,那么这时候父类的静态方法或属性称之为“隐藏”。

多态之所以能够实现依赖于继承、接口和重写、重载(继承和重写最为关键)。有了继承和重写就可以实现父类的引用指向不同子类的对象。 重写的功能是:重写后子类的优先级高于父类的优先级,但是隐藏是没有这个优先级之分的。

总结:

  • 静态属性、静态方法和非静态的属性都可以被继承和隐藏而不能被重写,因此不能实现多态,不能实现父类的引用可以指向不同子类的对象。

 

  • 非静态方法可以被继承和重写,因此可以实现多态。

25.Enumeration 和 Iterator 的区别?

  • 接口不同
    Enumeration 只能读取集合数据,而不能对数据进行修改;Iterator 除了读取集合的数据之外,也能对数据进行删除操作;Enumeration 已经被 Iterator 取代了,之所以没有被标记为 Deprecated,是因为在一些遗留类(Vector、Hashtable)中还在使用。
  • Iterator 支持 fail-fast 机制,而 Enumeration 不支持
    Enumeration 是 JDK 1.0 添加的接口。使用到它的函数包括 Vector、Hashtable 等类,Enumeration 存在的目的就是为它们提供遍历接口。
    Iterator 是 JDK 1.2 才添加的接口,它是为了 HashMap、ArrayList 等集合提供的遍历接口。Iterator 是支持 fail-fast 机制的。
    Fail-fast 机制是指 Java 集合 (Collection) 中的一种错误机制。当多个线程对同一个集合的内容进行操作时,就可能会产生 fail-fast 事件。例如:当某个线程 A 通过 Iterator 去遍历某集合的过程中,若该集合的内容被其他线程所改变了,那么线程 A 访问集合时,就会抛出 ConcurrentModificationException 异常,产生 fail-fast 事件。

Java进阶

1. 常见的编码方式?

编码的原因:

计算机存储信息的最小单元是一个字节即八个bit,所以能表示的字符范围是0 - 225 个。然而要表示的符号太多了,无法用一个字节来完全表示,要解决这个矛盾必须需要一个新的数据结构char,从char到byte必须编码。编码方式规定了转化的规则,按照这个规则就可以让计算机正确的表示我们的字符。编码方式有以下几种:

  • ASCII码:总共有128个,用一个字节的低七位表示,0 - 31是控制字符,如回车键、换行等等。32 - 126是打印字符,可以通过键盘输入并且能够显示出来。
  • ISO-8859-1:ISO组织在ASCII码基础上又制定了一些列标准用来扩展ASCII编码,其中IS-8859-1涵盖了大多数西欧语言字符,单字节编码,总共表示256个字节,应用广泛。
  • GB2312、GBK:双字节编码,表示汉字。
  • UTF-16:UTF - 16具体定义了Unicode字符在计算机中的存取方法。UTF - 16用两个字节来表示Unicode转化格式,这个是定长的表示方法,不论什么字符都可以用两个字节表示,两个字节是十六个bit,所以叫UTF - 16.
  • UTF- 8:UTF-16统一采用两个字节表示一个字符,虽然表示上非常简单方便,但是有很大一部分字符用一个字节就可以表示,显然是浪费了存储空间。于是UTF-8应运而生,UTF-8采用一种变长技术,每个编码区域有不同的字码长度,不同类型的字符可以是由1-6个字节组成。

2. 什么是反射机制?应用场景?

  • 定义:Java的反射(reflection)机制是指在程序的运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;可以调用任意一个对象的属性和方法。这种动态获取程序信息以及动态调用对象的功能称为Java语言的反射机制。
  • 原理:我们知道,每个 Java 文件最终都会被编译成一个 .class 文件,这些 Class 对象承载了这个类的所有信息,包括父类、接口、构造函数、方法、属性等等,这些class文件在程序运行时会被 ClassLoader 加载到虚拟机中。当一个类被加载以后,Java 虚拟机就会在内存中自动产生一个 Class 对象,而一般情况下用 new 来创建对象,实际上本质都是一样的,只是底层原理对我们开发者透明罢了。有了 class 对象的引用,就相当于有了 Method、Field、Constructor 的一切信息,在 Java 中,有了对象的引用就有了一切,剩下就靠自己发挥了。
  • 应用场景:动态生成类框架 例如Gson;与注解相结合的框架,如 Retrofit;Retrofit之所以简单易用,Java的动态代理功不可没而动态代理中反射又是重中之重,interface相当于配置文件,之后动态代理进行方法的加强,完成请求。

3. 静态代理和动态代理的区别?什么场景使用?

定义:代理模式是常用的Java设计模式,它的特征是代理类与委托类有同样的接口,代理类主要负责为委托类预处理消息、过滤消息、把消息转发给委托类,以及事后处理消息等。代理类与委托类之间通常会存在关联关系,一个代理类的对象与一个委托类的对象关联,代理类的对象本身并不真正实现服务,而是通过调用委托类的对象的相关方法,来提供特定的服务。按照代理类的创建时期,代理类可分为两种。

静态代理(优:简单、实用、效率高;缺:代码冗余、灵活性差、手动增加):

  • 由程序员创建或由特定工具自动生成源代码,再对其编译。在程序运行前,代理类的.class文件就已经存在了
  • 静态代理通常只代理一个类
  • 静态代理事先知道要代理的是什么

静态代理实现:

(1)创建一个代理类,要与委托类实现同一个接口

(2)获得委托对象,可通过构造器或set方法

(3)将委托对象的方法夹在 代理对象的代码中间,完成代理

静态代理使用场景:

当我们只有少量接口需要代理,且需要代理的方法不是很多时,我们可以采用静态代理,因为它效率更高

 

动态代理(优:使用灵活、减少代码冗余;缺:效率低):

  • 在程序运行时,运用反射机制动态创建而成
  • 动态代理是代理一个接口下的多个实现类
  • 动态代理不知道要代理什么东西,只有在运行时才知道

动态代理实现:

(1)创建一个动态代理类,实现 InvocationHandler 接口

(2)获得委托对象,可通过构造器或set方法

(3)利用Proxy类的newProxyInstance()方法生成代理对象(三个参数看代码注释)

(4)在invoke 方法中写代理代码-->将委托对象的方法夹在代理对象的代码中。通过反射机制,执行 委托对象 指定的方法。

动态代理使用场景:

当我们有大量接口需要代理,或接口中有大量方法需要代理,那我们可以使用动态代理,因为它可以减少大量冗余代码

4. 为什么Java里的匿名内部类只能访问final修饰的外部变量?

因为匿名内部类最终会被编译成一个单独的类,而被该类使用的变量会以构造函数参数的形式传递给该类。如果变量不定义为final的,参数在匿名内部类中可以被修改,进而造成和外部的变量不一致的问题,为了避免这种不一致的情况,规定匿名内部类只能访问final修饰的外部变量。

感谢题目来源:

https://github.com/Moosphan/Android-Daily-Interview

https://www.wanandroid.com/

感谢部分答案参考:

https://github.com/Omooo/Android-Notes

posted @ 2022-05-26 10:39  一笼灌汤包  阅读(746)  评论(0)    收藏  举报