一、开胃小菜

入门

JAVA的八种基本数据类型分别是什么,各占用多少字节?

image.png

一个char 型变量占能不能存贮一个中文汉字,为什么?

答:char类型可以存储一个中文汉字,因为Java中使用的编码是Unicode(不选择任何特定的编码,直接使用字符在字符集中的编号,这是统一的唯一方法),一个中文汉字占两个字节,正好一个char类型占2个字节(16比特),所以放一个中文是没问题的。
补充:
使用Unicode意味着字符在JVM内部和外部有不同的表现形式,在JVM内部都是Unicode,当这个字符被从JVM内部转移到外部时(例如存入文件系统中),需要进行编码转换。所以Java中有字节流和字符流,以及在字符流和字节流之间进行转换的转换流,如InputStreamReader和OutputStreamReader,这两个类是字节流和字符流之间的适配器类,承担了编码转换的任务;对于C程序员来说,要完成这样的编码转换恐怕要依赖于union(联合体/共用体)共享内存的特征来实现了。

JDK和JRE的区别是什么?

JRE顾名思义是JAVA的运行时环境,包含了JAVA虚拟机,JAVA基础类库。是使用JAVA语言编写的程序运行所需要的软件环境,是提供给想运行JAVA程序的用户使用的。
JDK顾名思义是JAVA开发工具包,是程序员使用JAVA语言编写JAVA程序所需的开发工具包,是提供给程序员使用的。JDK包含了JRE,同时还包含了编译java源码的编译器javac,还包含了很多java程序调试和分析的工具:jconsole,jvisualvm等工具软件,还包含了java程序编写所需的文档和demo例子程序。
如果你需要运行程序,只需安装JRE就可以了。如果你需要编写程序,需要安装JDK。
扩展:java的跨平台性
Java源程序先经过javac编译器编译成二进制的.class字节码文件(java的跨平台指的就是.class字节码文件的跨平台,.class字节码文件是与平台无关的),.class文件再运行在jvm上,java解释器(jvm的一部分)会将其解释成对应平台的机器码执行,所以java所谓的跨平台就是在不同平台上安装了不同的jvm,而在不同平台上生成的.class文件都是一样的,而.class文件再由对应平台的jvm解释成对应平台的机器码执行。

一个”.java”源文件中是否可以包含多个类(不是内部类)?有什么限制?

答:可以,一个源文件中最多只能有一个公开类(public class)而且文件名必须和公开类的类名完全保持一致。

内部类可以引用它的包含类(外部类)的成员吗?有没有什么限制?

答:一个内部类对象可以访问创建它的外部类对象的成员,包括私有成员

数据类型之间的转换: 如何将字符串转换为基本数据类型?如何将基本数据类型转换为字符串?

答:

  • 调用基本数据类型对应的包装类中的方法parseXXX(String)或valueOf(String)即可返回相应基本类型
  • 一种方法是将基本数据类型与空字符串(””)连接(+)即可获得其所对应的字符串;另一种方法是调用String 类中的valueOf()方法返回相应字符串

想转换成字符串类型就调用字符串类型的valueOf()方法;同理想转换成基本类型就调用基本类型对应包装类的valueOf()方法。

float f=3.4;是否正确?

答:不正确。3.4是双精度数,将双精度型(double)赋值给浮点型(float)属于下转型(down-casting,也称为窄化)会造成精度损失,因此需要强制类型转换float f =(float)3.4; 或者写成float f =3.4F;。

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

答:对于short s1 = 1; s1 = s1 + 1;由于1是int类型,因此s1+1运算结果也是int 型,需要强制转换类型才能赋值给short型
image.png
而short s1 = 1; s1 += 1;可以正确编译,因为s1+= 1;相当于s1 = (short)(s1 + 1);其中有隐含的强制类型转换。

Java有没有goto?

答:goto 是Java中的保留字,在目前版本的Java中没有使用。(根据James Gosling(Java之父)编写的《The Java Programming Language》一书的附录中给出了一个Java关键字列表,其中有goto和const,但是这两个是目前无法使用的关键字,因此有些地方将其称之为保留字,其实保留字这个词应该有更广泛的意义,因为熟悉C语言的程序员都知道,在系统类库中使用过的有特殊意义的单词或单词的组合都被视为保留字)

==和equals的区别是什么?

== 是比较运算符,如果进行比较的两个操作数都是数值类型,即使他们的数据类型不相同,只要他们的值相等,也都将返回true.
如果两个操作数都是引用类型,那么只有当两个引用变量的类型具有父子关系时才可以比较,而且这两个引用必须指向同一个对象,才会返回true.(在这里我们可以理解成==比较的是两个引用变量的内存地址)

equals()方法是Object类的方法,在Object类中的equals()方法体内实际上返回的就是使用==进行比较的结果.但是我们知道所有的类都继承Object,而且Object中的equals()方法没有使用final关键字修饰,那么当我们使用equal()方法进行比较的时候,我们需要关注的就是这个类有没有重写Object中的equals()方法。比如String类就重写了equals方法。

两个对象值相同(x.equals(y) == true),但却可有不同的hash code,这句话对不对?

答:不对,如果两个对象x和y满足x.equals(y) == true,它们的哈希码(hash code)应当相同(因为这是要根据哈希码来判断哈希冲突,两个哈希码相同的对象就产生了哈希冲突,但是它们的值不一定相等)。
Java对于eqauls方法和hashCode方法是这样规定的:
(1)equals方法返回true),那么它们的hashCode值一定要相同;(2)如果两个对象的hashCode相同,它们并不一定相同。
当然,你未必要按照要求去做,但是如果你违背了上述原则就会发现在使用容器时,相同的对象可以出现在Set集合中,同时增加新元素的效率会大大下降(对于使用哈希存储的系统,如果哈希码频繁的冲突将会造成存取性能急剧下降)。

int和Integer有什么区别?

答:Java是一个近乎纯洁的面向对象编程语言,为了编程的方便还是引入了基本数据类型,但是为了能够将这些基本数据类型当成对象操作,Java为每一个基本数据类型都引入了对应的包装类型(wrapper class),int的包装类就是Integer,从Java 5开始引入了自动装箱/拆箱机制,使得二者可以相互转换。
Java 为每个原始类型提供了包装类型:

  • 原始类型: boolean,char,byte,short,int,long,float,double
  • 包装类型:Boolean,Character,Byte,Short,Integer,Long,Float,Double

    1. class AutoUnboxingTest {
    2. public static void main(String[] args) {
    3. Integer a = new Integer(3);
    4. Integer b = 3; // 将3自动装箱成Integer类型
    5. int c = 3;
    6. System.out.println(a == b); // false 两个引用没有引用同一对象
    7. System.out.println(a == c); // true a自动拆箱成int类型再和c比较
    8. }
    9. }

最近还遇到一个面试题,也是和自动装箱和拆箱有点关系的,代码如下所示:

  1. public static void main(String[] args) {
  2. Integer f1 = 100, f2 = 100, f3 = 150, f4 = 150;
  3. System.out.println(f1 == f2);
  4. System.out.println(f3 == f4);
  5. }

如果不明就里很容易认为两个输出要么都是true要么都是false。首先需要注意的是f1、f2、f3、f4四个变量都是Integer对象引用,所以下面的==运算比较的不是值而是引用。装箱的本质是什么呢?当我们给一个Integer对象赋一个int值的时候,会调用Integer类的静态方法valueOf, 简单的说,如果整型字面量的值在-128到127之间那么不会new新的Integer对象,而是直接引用常量池中的Integer对象,所以上面的面试题中f1f2的结果是true,而f3f4的结果是false。

  1. /*
  2. 比如Integer
  3. */
  4. public static void main(String[] args) {
  5. Integer i1=10;
  6. Integer i2=10;
  7. System.out.println(i1==i2);
  8. Integer i3=new Integer(5);
  9. Integer i4=new Integer(5);
  10. System.out.println(i3==i4);
  11. Integer i5=new Integer(500);
  12. Integer i6=new Integer(500);
  13. System.out.println(i5==i6);
  14. Integer i7=500;
  15. Integer i8=500;
  16. System.out.println(i7==i8);
  17. }

注意这里直接赋值跟用new关键字创建出来的对象的区别;
image.png

&和&&的区别?

答:&运算符有两种用法:(1)按位与;(2)逻辑与。&&运算符是短路与运算。逻辑与跟短路与的差别是非常巨大的,虽然二者都要求运算符左右两端的布尔值都是true整个表达式的值才是true
&&之所以称为短路运算是因为,如果&&左边的表达式的值是false,右边的表达式会被直接短路掉,不会进行运算。很多时候我们可能都需要用&&而不是&,例如在验证用户登录时判定用户名不是null而且不是空字符串,应当写为:username != null &&!username.equals(“”),二者的顺序不能交换,更不能用&运算符,因为第一个条件如果不成立,根本不能进行字符串的equals比较,否则会产生NullPointerException异常。

按位与运算,两个当且仅当都为1的时候结果才为1,即1&1== 1,1&0== 0&1== 0&0==0
参加运算的数要换算为二进制进行运算,例如7 & 2的结果是2 。

注意:逻辑或运算符(|)和短路或运算符(||)的差别也是如此。

解释内存中的栈(stack)、堆(heap)和方法区(method area)的用法。

答:通常我们定义一个基本数据类型的变量,一个对象的引用,还有就是函数调用的现场保存都使用JVM中的栈空间;而通过new关键字和构造器创建的对象则放在堆空间,堆是垃圾收集器管理的主要区域,由于现在的垃圾收集器都采用分代收集算法,所以堆空间还可以细分为新生代和老生代,再具体一点可以分为Eden、Survivor(又可分为From Survivor和To Survivor)、Tenured;方法区和堆都是各个线程共享的内存区域,用于存储已经被JVM加载的类信息、常量、静态变量、JIT编译器编译后的代码等数据;程序中的字面量(literal)如直接书写的100、”hello”和常量都是放在常量池中,常量池是方法区的一部分,。栈空间操作起来最快但是栈很小,通常大量的对象都是放在堆空间,栈和堆的大小都可以通过JVM的启动参数来进行调整,栈空间用光了会引发StackOverflowError,而堆和常量池空间不足则会引发OutOfMemoryError。

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

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

switch 是否能作用在byte 上,是否能作用在long 上,是否能作用在String上?

答:在Java 5以前,switch(expr)中,expr只能是byte、short、char、int。从Java 5开始,Java中引入了枚举类型,expr也可以是enum类型,从Java 7开始,expr还可以是字符串(String),但是长整型(long)在目前所有的版本中都是不可以的。

用最有效率的方法计算2乘以8?

答: 2 << 3(左移3位相当于乘以2的3次方,右移3位相当于除以2的3次方)左乘右除

数组有没有length()方法?String有没有length()方法?

数组有属性字符串是方法
答:数组没有length()方法,有length 的属性。String 有length()方法。JavaScript中,获得字符串的长度是通过length属性得到的,这一点容易和Java混淆。

什么是自动装箱、自动拆箱?

Java支持的数据类型包括两种:一种是基本数据类型,包含byte,char,short, boolean ,int , long, float,double; 另一种是引用类型:如String等,其实是对象的引用,JVM中虚拟栈中存的是对象的地址,创建的对象实质在堆中,通过地址来找到堆中的对象的过程,即为引用类型。
自动装箱(将对应的基本类型转化为对应的包装类型)是Java编译器在基本数据类型和对应的对象包装类型之间做的一个转化。比如:把int转化成Integer,double转化成Double,等等。反之就是自动拆箱。

比较一下Java和JavaSciprt。


答:JavaScript 与Java是两个公司开发的不同的两个产品。

Java 是原Sun Microsystems公司推出的面向对象的程序设计语言,特别适合于互联网应用程序开发;

而JavaScript是Netscape公司的产品,为了扩展Netscape浏览器的功能而开发的一种可以嵌入Web页面中运行的基于对象和事件驱动的解释性语言。

JavaScript的前身是LiveScript;而Java的前身是Oak语言。

下面对两种语言间的异同作如下比较:

  • 基于对象和面向对象:Java是一种真正的面向对象的语言,即使是开发简单的程序,必须设计对象;JavaScript是种脚本语言,它可以用来制作与网络无关的,与用户交互作用的复杂软件。它是一种基于对象(Object-Based)和事件驱动(Event-Driven)的编程语言,因而它本身提供了非常丰富的内部对象供设计人员使用。
  • 解释和编译:Java的源代码在执行之前,必须经过编译。JavaScript是一种解释性编程语言,其源代码不需经过编译,由浏览器解释执行。(目前的浏览器几乎都使用了JIT(即时编译)技术来提升JavaScript的运行效率)
  • 强类型变量和类型弱变量:Java采用强类型变量检查,即所有变量在编译之前必须作声明;JavaScript中变量是弱类型的,甚至在使用变量前可以不作声明,JavaScript的解释器在运行时检查推断其数据类型。
  • 代码格式不一样。

面向对象以及三大特性

面向对象的特征有哪些方面?

答:面向对象的特征主要有以下几个方面:

  • 封装:通常认为封装是把数据和操作数据的方法绑定起来,对数据的访问只能通过已定义的接口。面向对象的本质就是将现实世界描绘成一系列完全自治、封闭的对象。我们在类中编写的方法就是对实现细节的一种封装;我们编写一个类就是对数据和数据操作的封装。可以说,封装就是隐藏一切可隐藏的东西,只向外界提供最简单的编程接口(可以想想普通洗衣机和全自动洗衣机的差别,明显全自动洗衣机封装更好因此操作起来更简单;我们现在使用的智能手机也是封装得足够好的,因为几个按键就搞定了所有的事情)。
  • 抽象:抽象是将一类对象的共同特征总结出来构造成类的过程,包括数据抽象和行为抽象两方面。抽象只关注对象有哪些属性和行为,并不关注这些行为的细节是什么。
  • 继承:继承是从已有类得到继承信息创建新类的过程。提供继承信息的类被称为父类(超类、基类);得到继承信息的类被称为子类(派生类)。继承让变化中的软件系统有了一定的延续性,同时继承也是封装程序中可变因素的重要手段(如果不能理解请阅读阎宏博士的《Java与模式》或《设计模式精解》中关于桥梁模式的部分)。
  • 多态性:多态性是指允许不同子类型的对象对同一消息作出不同的响应。简单的说就是用同样的对象引用调用同样的方法但是做了不同的事情。多态性分为编译时的多态性和运行时的多态性。如果将对象的方法视为对象向外界提供的服务,那么运行时的多态性可以解释为:当A系统访问B系统提供的服务时,B系统有多种提供服务的方式,但一切对A系统来说都是透明的(就像电动剃须刀是A系统,它的供电系统是B系统,B系统可以使用电池供电或者用交流电,甚至还有可能是太阳能,A系统只会通过B类对象调用供电的方法,但并不知道供电系统的底层实现是什么,究竟通过何种方式获得了动力)。
    方法重载(overload)实现的是编译时的多态性(也称为前绑定),而方法重写(override)实现的是运行时的多态性(也称为后绑定)。运行时的多态是面向对象最精髓的东西,要实现多态需要做两件事:1). 方法重写(子类继承父类并重写父类中已有的或抽象的方法);2). 对象造型(用父类型引用引用子类型对象,这样同样的引用调用同样的方法就会根据子类对象的不同而表现出不同的行为)

重载(Overload)和重写(Override)的区别。重载的方法能否根据返回类型进行区分?

答:方法的重载和重写都是实现多态的方式,区别在于前者实现的是编译时的多态性,而后者实现的是运行时的多态性。(多态反面的区别)
(发生的场景的区别)
重载发生在一个类中,同名的方法如果有不同的参数列表(参数类型不同、参数个数不同或者二者都不同)则视为重载;
重写发生在子类与父类之间,重写要求子类被重写方法与父类被重写方法有相同的返回类型,比父类被重写方法更好访问,不能比父类被重写方法声明更多的异常(里氏代换原则)。重载对返回类型没有特殊的要求。

抽象类的特点

1、 抽象类的组成结构上,是指在普通类的结构里面增加抽象方法的组成部分,即 拥有抽象方法的类就是抽象类,抽象类要使用abstract关键字声明。
2、 抽象方法的访问修饰符上,必须为public或者protected(因为如果为private,则不能被子类继承,子类便无法实现该方法),缺省情况下默认为public;没有方法体,有abstract关键字做修饰 。
3、是否必须有抽象方法,抽象类可以没有抽象方法,但是如果你的一个类已经声明成了抽象类,即使这个类中没有抽象方法,它也不能再实例化,即不能直接构造一个该类的对象。
4、抽象类能否创建对象上,不能直接实例化,需要依靠子类采用向上转型的方式处理;
5、抽象类能否被static修饰,外部抽象类不允许使用static声明,而内部的抽象类运行使用static声明。使用static声明的内部抽象类相当于一个外部抽象类,继承的时候使用“外部类.内部类”的形式表示类名称。
6、 静态抽象方法如何调用,如果要执行类中的static方法的时候,都可以在没有对象的情况下直接调用,对于抽象类也一样。 也就是说 可以直接调用抽象类中用static声明的方法 。
7、抽象类的子类上,必须有子类所以抽象类不能被 final 修饰。使用extends继承,一个子类只能继承一个抽象类;而且子类(如果不是抽象类)则必须覆写抽象类之中的全部抽象方法(如果子类没有实现父类的抽象方法,否则必须将子类也定义为为abstract类。);

普通类跟抽象类的区别:

(1)抽象类继承子类里面有明确的方法覆写要求,而普通类可以有选择性的来决定是否需要覆写;
(2)抽象类实际上就比普通类多了一些抽象方法而已,其他组成部分和普通类完全一样;
(3)普通类对象可以直接实例化,但抽象类的对象必须经过向上转型之后才可以得到。
虽然一个类的子类可以去继承任意的一个普通类,可是从开发的实际要求来讲,普通类尽量不要去继承另外一个普通类,而是去继承抽象类。

接口的特点

1、什么是接口: Java接口是一系列方法的声明,是一些方法特征的集合,一个接口只有方法的特征没有方法的实现,因此这些方法可以在不同的地方被不同的类实现,而这些实现可以具有不同的行为(功能)
通俗来说接口是 一种特殊的类,里面全部是由全局常量公共的抽象方法所组成。接口是解决Java无法使用多继承的一种手段,但是接口在实际中更多的作用是制定标准的。或者我们可以直接把接口理解为100%的抽象类
2、 就像一个类一样,一个接口也能够拥有方法和属性,但是在接口中声明的方法默认是抽象的。(即只有方法标识符,而没有方法体)所以 不能直接去实例化一个接口
我们可以使用接口类型的引用指向一个实现了该接口的对象,并且可以调用这个接口中的方法
3、 一个接口可以继承于另外的接口,并且可以是多继承。
4、 一个类如果要实现某个接口的话,那么它必须要实现这个接口中的所有方法。
5、 接口中所有的方法都是抽象的和public的,所有的属性都是public,static,final的。

抽象类(abstract class)和接口(interface)有什么异同?

答:抽象类和接口都不能够实例化,但可以定义抽象类和接口类型的引用。
在方法的实现上,一个类如果继承了某个抽象类或者实现了某个接口都需要对其中的抽象方法全部进行实现,否则该类仍然需要被声明为抽象类。
(类成员方面区别),接口比抽象类更加抽象,因为抽象类中可以定义构造器,可以有抽象方法和具体方法,而接口中不能定义构造器而且其中的方法全部都是抽象方法
访问修饰符,抽象类中的成员可以是private、默认、protected、public的,而接口中的成员全都是public的。
抽象类中可以定义成员变量,而接口中定义的成员变量实际上都是常量。即Java接口中声明的变量默认都是final的。抽象类可以包含非final的变量。
有抽象方法的类必须被声明为抽象类,而抽象类未必要有抽象方法。

Java支持多继承么?

Java中不支持多继承,只支持单继承(即一个类只有一个父类)。
但是java中的接口支持多继承,即一个子接口可以有多个父接口。(接口的作用是用来扩展对象的功能,一个子接口继承多个父接口,说明子接口扩展了多个功能,当类实现接口时,类就扩展了相应的功能)。

接口是否可继承(extends)接口?抽象类是否可实现(implements)接口?抽象类是否可继承具体类(concrete class)?

答:接口可以继承接口,而且支持多重继承
抽象类可以实现(implements)接口,抽象类可继承具体类也可以继承抽象类

Anonymous Inner Class(匿名内部类)是否可以继承其它类?是否可以实现接口?

答:可以继承其他类或实现其他接口,在Swing编程和Android开发中常用此方式来实现事件监听和回调。

静态嵌套类(Static Nested Class)和内部类(Inner Class)的不同?

静态(static)的内部类,它可以不依赖于外部类实例被实例化。而通常的内部类需要在外部类实例化后才能实例化。 是否依赖外部类实例化进行区别。

  1. class Outer {
  2. class Inner {}
  3. public static void foo() { new Inner();===报错 ===}
  4. public void bar() { new Inner(); }
  5. public static void main(String[] args) {
  6. new Inner();===报错 ===
  7. }
  8. }

注意:Java中内部类对象的创建要依赖其外部类对象,上面的面试题中foo和main方法都是静态方法,静态方法中没有this,也就是说没有所谓的外部类对象,因此无法创建内部类对象,如果要在静态方法中创建内部类对象,可以这样做:

  1. new Outer().new Inner();

抽象的(abstract)方法是否可同时是静态的(static),是否可同时是本地方法(native),是否可同时被synchronized修饰?

答:都不能。抽象方法需要子类重写,而静态的方法是无法被重写的,因此二者是矛盾的。

本地方法是由本地代码(如C代码)实现的方法,而抽象方法是没有实现的,也是矛盾的。

synchronized方法的实现细节有关,抽象方法不涉及实现细节,因此也是相互矛盾的。

阐述静态变量和实例变量的区别。

静态变量是被static修饰符修饰的变量,也称为类变量,它属于类,不属于类的任何一个对象,一个类不管创建多少个对象,静态变量在内存中有且仅有一个拷贝;
实例变量必须依存于某一实例,需要先创建对象然后通过对象才能访问到它。
静态变量可以实现让多个对象共享内存。

是否可以从一个静态(static)方法内部发出对非静态(non-static)方法的调用?

答:不可以,静态方法只能访问静态成员,因为非静态方法的调用要先创建对象,而在调用静态方法时可能对象并没有被初始化。

如何实现对象克隆?

Java中的方法覆盖(Overriding)和方法重载(Overload)是什么意思?

Java中的方法重载发生在同一个类里面两个或者是多个方法的方法名相同但是参数不同的情况。与此相对,方法覆盖是说子类重新定义了父类的方法。方法覆盖必须有相同的方法名,参数列表和返回类型。覆盖者可能不会限制它所覆盖的方法的访问。

String 是最基本的数据类型吗?

答:不是。Java中的基本数据类型只有8个:byte、short、int、long、float、double、char、boolean;除了基本类型(primitive type),剩下的都是引用类型(reference type),Java 5以后引入的枚举类型也算是一种比较特殊的引用类型。

构造器(constructor)是否可被重写(override)?

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

Java中,什么是构造方法?什么是构造方法重载?什么是复制构造方法?

当新对象被创建的时候,会默认去调用构造方法进行初始化。每一个类都有构造方法。在程序员没有给类提供构造方法的情况下,Java编译器会为这个类创建一个默认的构造方法,若是有了有参构造方法,就不会有默认的无参构造方法了。
Java中构造方法重载和方法重载很相似。可以为一个类创建多个构造方法。每一个构造方法必须有它自己唯一的参数列表。
Java不支持像C++中那样的复制构造方法,这个不同点是因为如果你不自己写构造方法的情况下,Java不会创建默认的复制构造方法。

当一个对象被当作参数传递到一个方法后,此方法可改变这个对象的属性,并可返回变化后的结果,那么这里到底是值传递还是引用传递?

答:是值传递。Java语言的方法调用只支持参数的值传递。当一个对象实例作为一个参数被传递到方法中时,参数的值就是对该对象的引用。对象的属性可以在被调用过程中被改变,但对对象引用的改变是不会影响到调用者的。C++和C#中可以通过传引用或传输出参数来改变传入的参数的值。

说明:Java中没有传引用实在是非常的不方便,这一点在Java 8中仍然没有得到改进,正是如此在Java编写的代码中才会出现大量的Wrapper类(将需要通过方法调用修改的引用置于一个Wrapper类中,再将Wrapper对象传入方法),这样的做法只会让代码变得臃肿,尤其是让从C和C++转型为Java程序员的开发者无法容忍。

在java中为什么很多人说有值传递和引用传递?引用传递的本质是什么? (从Java虚拟机栈、堆、引用以及实际对象解答)

  1. https://www.nowcoder.com/questionTerminal/b296e9e1c40542ec8677c1e452b6b576

访问修饰符public,private,protected,以及不写(默认)时的区别?

image.png

类的成员不写访问修饰时默认为default。默认对于同一个包中的其他类相当于公开(public),对于不是同一个包中的其他类相当于私有(private)。受保护(protected)对子类相当于公开,对不是同一包中的没有父子关系的类相当于私有。Java中,外部类的修饰符只能是public或默认,类的成员(包括内部类)的修饰符可以是以上四种。

关键字

”static”关键字是什么意思?Java中是否可以覆盖(override)一个private或者是static的方法?

“static”关键字表明一个成员变量或者是成员方法可以在没有所属的类的实例变量的情况下被访问。

Java中static方法不能被重写覆盖,因为方法覆盖是基于**运行时动态绑定的,而static方法是编译时静态绑定的。static方法跟类的任何实例都不相关,所以概念上不适用。
而且java中也不可以重写覆盖private的方法,因为private修饰的变量和方法
只能在当前类中使用,如果是其他的类继承当前类是不能访问到private变量或方法的**,当然也不能覆盖。

是否可以在static环境中访问非static变量?

static变量在Java中是属于类的,它在所有的实例中的值是一样的。当类被Java虚拟机载入的时候,会对static变量进行初始化。如果你的代码尝试不用实例来访问非static的变量,编译器会报错,因为这些变量还没有被创建出来,还没有跟任何实例关联上。 也就是说你要用实例化之后才能访问非静态成员变量。

Java 中的final关键字有哪些用法?

从修饰的对象入手
答:
(1)修饰类:表示该类不能被继承;
(2)修饰方法:表示方法不能被重写;
(3)修饰变量:表示变量只能一次赋值以后值不能被修改(常量)。

字符串常量池

代码

  1. public class Main {
  2. public static void main(String[] args) {
  3. String s1= "a";
  4. String s2="b";
  5. String s3= "ab";
  6. }
  7. }

当该类Main被加载到内存以后[每个类有每个类的常量池],它的常量池**信息就会放入运行时常量池, 这个时候的 a b ab 都还只是符号,还没有变为字符串对象。等到代码执行到他们的时候才会变成字符串对象,并且将其加入到字符串常量池中。
当一个字符串对象要进入字符串常量池中的时候会首先判断串池中是否存在该字符串对象,不存在才会将其放进串池。** 已经存在的话会怎样?

字符串的拼接的本质

  1. public class Main {
  2. public static void main(String[] args) {
  3. String s1= "a";
  4. String s2="b";
  5. String s3= "ab";
  6. String s4=s1=+s2;
  7. }
  8. }

当代码执行到String s4=s1=+s2; 也就是字符串拼接的时候,是先创建了一个new StringBuilder对象,然后调用该对象的init无参构造方法;然后开始加载s1变量,并将其作为StringBuilder对象调用append方法的参数,然后加载s2且将其作为append方法的参数,最后调用StringBuilder对象的toString方法。而toString方法内部又new了一个新的String对象,它的值就是s1,s2的拼接
image.png

  1. public class Main {
  2. public static void main(String[] args) {
  3. String s1= "a";
  4. String s2="b";
  5. String s3= "ab";
  6. String s4=s1+s2;
  7. System.out.println(s3==s4);//面所题:结果是什么?
  8. }
  9. }

s3是在串池中的,而s4的底层是new出来的,放在堆内存中的,所以他们两个的地址空间不是同一个,答案是false;

  1. public class Main {
  2. public static void main(String[] args) {
  3. String s1= "a";
  4. String s2="b";
  5. String s3= "ab";
  6. String s4=s1+s2;
  7. String s5= "a"+"b";
  8. System.out.println(s3==s4);//
  9. System.out.println(s3==s5);//结果是什么?
  10. }
  11. }

当代码执行到String s3= “ab”; 时,串池中还没有“ab”这个对象,所以把它添加到串池中去,当执行到 String s5= “a”+”b”; 它也是去字符串常量池中去找“ab”,此时已经存在该对象了,所以不会再次将其放入串池中,而是继续使用串池中存在的“ab”对象。所以此时的结果是true。

编译器的优化

为什么上一个例子当代码执行到 String s5= “a”+”b”; 时去串池找的是”ab”而不是”a” “b” 呢?这是因为javac 在编译期间的优化,才会认为”a” “b” 都是常量,其内容值不会发生变化,所以他们的拼接结果是确定的,故在编译期间我就能知道它的编译结果,所以找的是”ab”。

而String s4=s1=+s2; 拼接的参数不是常量,而是变量,既然是变量它的引用就有可能被修改,故它的结果是不能确定的,编译期间是不可知的,所以才会在运行期间使用StringBuilder来进行拼接。

intern方法 1.8

调用字符串对象的 intern 方法,会将该字符串对象尝试放入到串池中
如果串池中没有该字符串对象,则放入成功
如果有该字符串对象,则放入失败
无论放入是否成功,都会返回串池中的字符串对象

代码:

  1. public class Main {
  2. public static void main(String[] args) {
  3. String s = new String("a") + new String("b");
  4. }
  5. }

代码执行的时候”a” ,”b”是常量,且串池中还没有存在,**要将其放入串池中。**

new String(“a”); new出来的这个则放在堆内存中,他的值是跟串池中的”a”相等,但是对象并不是同一个,”b”同理;s底层则是相当于调用了StringBuilder对象来将两个new出来的变量进行拼接,得到结果String(“ab”), 但是注意,**这个”ab”仅存在于堆中而不存在于串池中,目前串池中只存在常量字符串[“a,”b”]**

但是如果我们硬是要将"ab"放入串池中的话,可以调用字符串对象s的intern方法

  1. public class Main {
  2. public static void main(String[] args) {
  3. String s = new String("a") + new String("b");
  4. String s2 = s.intern();
  5. System.out.println(s2=="ab"); //true
  6. System.out.println(s=="ab"); //true
  7. System.out.println(s2==s); //true
  8. }
  9. }

换一种情况,“ab”先存在于堆中是什么结果?

  1. public class Main {
  2. public static void main(String[] args) {
  3. String x="ab" ;
  4. String s = new String("a") + new String("b");
  5. String s2 = s.intern();
  6. System.out.println(s2==x); //true
  7. System.out.println(s==x); //false;
  8. }
  9. }

为什么s==x是false呢?因为代码执行到 String x=”ab” ;的时候已经将”ab”放入

串池中了,执行到 String s = new String(“a”) + new String(“b”);的时候s是被放在堆里面的,由于串池中已经有了”ab”,等到执行String s2 = s.intern(); 方法的时候,就不能将s放入串池中,所以这s 与 x 两玩意儿就不会相等。但是由于intern方法的特点会返回一个对象,这个对象是在串池中返回的,所以这个s2跟x就是相等的。

intern方法 1.6

  1. 调用字符串对象的 intern 方法,会将该字符串对象尝试放入到串池中
  2. 如果串池中没有该字符串对象,则会将其拷贝一份并将拷贝的放入串池中
  3. 如果有该字符串对象,则放入失败
  4. 无论放入是否成功,都会返回串池中的字符串对象

代码:

  1. public class Main {
  2. public static void main(String[] args) {
  3. String s = new String("a") + new String("b");
  4. String s2 = s.intern();
  5. String x="ab" ;
  6. System.out.println(s2==x); //true
  7. System.out.println(s==x); //false;
  8. }
  9. }

因为”ab”是被拷贝后放进去的,所以串池中的”ab”跟s不相等,因为这个s还是堆中的,而 String s2 = s.intern();的返回结果是串池中的被拷贝的对象,所以s与s2也不等。

字符串

String和StringBuilder、StringBuffer的区别?

Java平台提供了两种类型的字符串:String和StringBuffer/StringBuilder,它们可以储存和操作字符串。其中String是只读字符串,也就意味着String引用的字符串内容是不能被改变的。

而StringBuffer/StringBuilder类表示的字符串对象可以直接进行修改。

StringBuilder是Java 5中引入的,它和StringBuffer的方法完全相同,区别在于它是在单线程环境下使用的,因为它的所有方法都没有被synchronized修饰,因此它的效率也比StringBuffer要高。

  1. public static void main(String[] args) {
  2. String s1 = "Programming"; //直接放在常量池
  3. String s2 = new String("Programming");
  4. String s3 = "Program";
  5. String s4 = "ming";
  6. String s5 = "Program" + "ming";
  7. String s6 = s3 + s4;
  8. System.out.println(s1 == s2);//f
  9. System.out.println(s1 == s5);//t
  10. System.out.println(s1 == s6);//f
  11. System.out.println(s1 == s6.intern());//t
  12. System.out.println(s2 == s2.intern());//f
  13. }

补充:解答上面的面试题需要清除两点:

  1. String对象的intern方法会得到字符串对象在常量池中对应的版本的引用(如果常量池中有一个字符串与String对象的equals结果是true),如果常量池中没有对应的字符串,则该字符串将被添加到常量池中,然后返回常量池中字符串的引用;
  2. 字符串的+操作其本质是创建了StringBuilder对象进行append操作,然后将拼接后的StringBuilder对象用toString方法处理成String对象,这一点可以用javap -c StringEqualTest.class命令获得class文件对应的JVM字节码指令就可以看出来。

String类 是不可变类,即一旦一个String对象被创建以后,包含在这个对象中的字符序列是不可改变的,直至这个对象被销毁。 当再次给某个String类引用赋值时,并不是对原来堆中实例对象进行重新赋值,而是生成一个新的实例对象,并且指向新赋值的字符串实例对象,之前的实例对象仍然存在,如果没有被再次引用,则会被垃圾回收。

image.png

StringBuffer对象则代表一个字符序列可变的字符串,当一个StringBuffer被创建以后,通过StringBuffer提供的append()、insert()、reverse()、setCharAt()、setLength()等方法可以改变这个字符串对象的字符序列。一旦通过StringBuffer生成了最终想要的字符串,就可以调用它的toString()方法将其转换为一个String对象。 它没有重新生成一个对象,而且在原来的对象中可以连接新的字符串。

StringBuilder类也代表可变字符串对象。实际上,StringBuilder和StringBuffer基本相似,两个类的构造器和方法也基本相同。不同的是:StringBuffer是线程安全的,而StringBuilder则没有实现线程安全功能,所以性能略高。

  1. https://blog.csdn.net/jackfrued/article/details/44921941?

String s = new String(“xyz”);创建了几个字符串对象?

答:两个对象,一个是静态区(方法区??)的”xyz”????,一个是用new创建在堆上的对象。

如何实现字符串的反转及替换?

方法很多可以使用String或StringBuffer/StringBuilder中的方法。

可以自己写实现。

  1. public static String reverse(String originStr) {
  2. if(originStr == null || originStr.length() <= 1)
  3. return originStr;
  4. return reverse(originStr.substring(1)) + originStr.charAt(0);
  5. }

怎样将GB2312编码的字符串转换为ISO-8859-1编码的字符串?

  1. String s1 = "你好";
  2. String s2 = new String(s1.getBytes("GB2312"), "ISO-8859-1");

日期和时间

- 如何取得年月日、小时分钟秒?
- 如何取得从1970年1月1日0时0分0秒到现在的毫秒数?
- 如何取得某月的最后一天?
- 如何格式化日期?

什么时候用断言(assert)?
答:断言在软件开发中是一种常用的调试方式,很多开发语言中都支持这种机制。

断言检查通常在开发和测试时开启。为了保证程序的执行效率,在软件发布后断言检查通常是关闭的。

断言是一个包含布尔表达式的语句,在执行这个语句时假定该表达式为true;如果表达式的值为false,那么系统会报告一个AssertionError。

断言不应该以任何方式改变程序的状态。简单的说,如果希望在不满足某些条件时阻止代码的执行,就可以考虑用断言来阻止它。

异常相关

Error和Exception有什么区别?

答:Error表示系统级的错误和程序不必处理的异常,一种严重问题;比如内存溢出,不可能指望程序能处理这样的情况;

Exception表示需要捕捉或者需要程序进行处理的异常,是一种设计或实现问题;也就是说,它表示如果程序运行正常,从不会发生的情况。

try{}里有一个return语句,那么紧跟在这个try后的finally{}里的代码会不会被执行,什么时候被执行,在return前还是后?

答:会执行,在方法返回调用者前执行。
在finally中改变返回值的做法是不好的。因为如果存在finally代码块,try中的return语句不会立马返回调用者,而是记录下返回值待finally代码块执行完毕之后再向调用者返回其值,然后如果在finally中修改了返回值,虽然不会返回修改后的值,但是在finally代码块中还是能够修改应该返回的值
显然,在finally中返回或者修改返回值会对程序造成很大的困扰,C#中直接用编译错误的方式来阻止程序员干这种龌龊的事情,Java中也可以通过提升编译器的语法检查级别来产生警告或错误,Eclipse中可以在如图所示的地方进行设置,强烈建议将此项设置为编译错误。

throw 和 throws 的区别?

throws是用来声明一个方法可能抛出的所有异常信息,throws是将异常声明但是不处理,而是将异常往上传,谁调用我就交给谁处理。而throw则是指抛出的一个具体的异常类型。

Java语言如何进行异常处理,关键字:throws、throw、try、catch、finally分别如何使用?

答:Java通过面向对象的方法进行异常处理,把各种不同的异常进行分类,并提供了良好的接口。
也就是说在Java中,每个异常都是一个对象,它是Throwable类或其子类的实例。当一个方法出现异常后便会抛出一个异常对象,该对象中包含有异常信息,调用这个对象的方法可以捕获到这个异常并可以对其进行处理。
Java的异常处理是通过5个关键词来实现的:try、catch、throw、throws和finally。
一般情况下是用try来执行一段程序,如果系统会抛出(throw)一个异常对象,可以通过它的类型来捕获(catch)它,或通过总是执行代码块(finally)来处理;
try用来指定一块预防所有异常的程序;catch子句紧跟在try块后面,用来指定你想要捕获的异常的类型;throw语句用来明确地抛出一个异常;throws用来声明一个方法可能抛出的各种异常(当然声明异常时允许无病呻吟);finally为确保一段代码不管发生什么异常状况都要被执行;
try语句可以嵌套,每当遇到一个try语句,异常的结构就会被放入异常栈中,直到所有的try语句都完成。如果下一级的try语句没有对某种异常进行处理,异常栈就会执行出栈操作,直到遇到有处理这种异常的try语句或者最终将异常抛给JVM。

try-catch-finally 中哪个部分可以省略?

答:catch 可以省略
原因:
更为严格的说法其实是:try只适合处理运行时异常,try+catch适合处理运行时异常+普通异常。也就是说,如果你只用try去处理普通异常却不加以catch处理,编译是通不过的,因为编译器硬性规定,普通异常如果选择捕获,则必须用catch显示声明以便进一步处理。而运行时异常在编译时没有如此规定,所以catch可以省略,你加上catch编译器也觉得无可厚非。
理论上,编译器看任何代码都不顺眼,都觉得可能有潜在的问题,所以你即使对所有代码加上try,代码在运行期时也只不过是在正常运行的基础上加一层皮。但是你一旦对一段代码加上try,就等于显示地承诺编译器,对这段代码可能抛出的异常进行捕获而非向上抛出处理。如果是普通异常,编译器要求必须用catch捕获以便进一步处理;如果运行时异常,捕获然后丢弃并且+finally扫尾处理,或者加上catch捕获以便进一步处理。
至于加上finally,则是在不管有没捕获异常,都要进行的“扫尾”处理。

运行时异常与受检异常有何异同?

列出一些你常见的运行时异常?

答:

  • ArithmeticException(算术异常)
  • ClassCastException (类转换异常)
  • IllegalArgumentException (非法参数异常)
  • IndexOutOfBoundsException (下标越界异常)
  • NullPointerException (空指针异常)
  • SecurityException (安全异常)

阐述final、finally、finalize的区别。

答:

  • final:修饰符(关键字)有三种用法:如果一个类被声明为final,意味着它不能再派生出新的子类,即不能被继承,因此它和abstract是反义词。将变量声明为final,可以保证它们在使用中不被改变,被声明为final的变量必须在声明时给定初值,而在以后的引用中只能读取不可修改。被声明为final的方法也同样只能使用,不能在子类中被重写。
  • finally:通常放在try…catch…的后面构造总是执行代码块,这就意味着程序无论正常执行还是发生异常,这里的代码只要JVM不关闭都能执行,可以将释放外部资源的代码写在finally块中。
  • finalize:Object类中定义的方法,Java中允许使用finalize()方法在垃圾收集器将对象从内存中清除出去之前做必要的清理工作。这个方法是由垃圾收集器在销毁对象时调用的,通过重写finalize()方法可以整理系统资源或者执行其他清理工作。

类ExampleA继承Exception,类ExampleB继承ExampleA。

  1. try {
  2. throw new ExampleB("b")
  3. } catchExampleA e){
  4. System.out.println("ExampleA");
  5. } catchException e){
  6. System.out.println("Exception");
  7. }

请问执行此段代码的输出是什么?
答:输出:ExampleA。(根据里氏代换原则[能使用父类型的地方一定能使用子类型],抓取ExampleA类型异常的catch块能够抓住try块中抛出的ExampleB类型的异常


泛型

https://blog.csdn.net/s10461/article/details/53941091?

对象拷贝

深拷贝和浅拷贝区别是什么?

浅拷贝是创建一个新对象,这个对象有着原始对象属性值的一份精确拷贝。如果属性是基本类型,拷贝的就是基本类型的值,如果属性是引用类型,拷贝的就是内存地址
深拷贝是将一个对象从内存中完整的拷贝一份出来,从堆内存中开辟一个新的区域存放新对象
区别:浅拷贝基本类型之间互不影响,但是引用类型其中一个对象改变了地址,就会影响另一个对象深拷贝
改变新对象不会影响原对象,他们之间互不影响。

  • 浅拷贝只是复制了对象的引用地址,两个对象指向同一个内存地址,所以修改其中任意的值,另一个值都会随之变化,这就是浅拷贝(例:assign())
  • 深拷贝是将对象及值复制过来,两个对象修改其中任意的值另一个值不会改变,这就是深拷贝

如何实现对象克隆?

有两种方式:

  • 实现Cloneable接口并重写Object类中的clone()方法;
  • 实现Serializable接口,通过对象的序列化和反序列化实现克隆,可以实现真正的深度克隆

注意:基于序列化和反序列化实现的克隆不仅仅是深度克隆,更重要的是通过泛型限定,可以检查出要克隆的对象是否支持序列化,这项检查是编译器完成的,不是在运行时抛出异常,这种是方案明显优于使用Object类的clone方法克隆对象。让问题在编译的时候暴露出来总是好过把问题留到运行时。

反射

什么是Java反射机制?

JAVA反射机制是指在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制。
当然要想解剖一个类,前提是必须先要获取到该类的字节码文件对象。毕竟解剖类的时候使用的就是Class类中的方法,所以才先要获取到每一个字节码文件对应的Class类型的对象。
通俗点来说, 反射就是把Java类中的各种成分映射成一个个的Java对象 ,本质是JVM虚拟机得到class对象之后,再通过class对象进行反编译,从而获取对象的各种信息。 反射的核心就是JVM在运行时才动态加载类或调用方法,访问属性,它不需要事先(写代码的时候或编译期)知道运行对象是谁。
Java属于先编译再运行的语言,程序中对象的类型在编译期就确定下来了,而当程序在运行时可能需要动态加载某些类,这些类因为之前用不到,所以没有被加载到JVM。通过反射,可以在运行时动态地创建对象并调用其属性,不需要提前在编译期知道运行的对象是谁

反射能作什么?

  1. 在运行时判断任意一个对象所属的类
  2. 在运行时构造任意一个类的对象
  3. 在运行时判断任意一个类所具有的成员变量和方法
  4. 在运行时获取泛型信息
  5. 在运行时调用任意一个对象的成员变量和方法
  6. 在运行时处理注解
  7. 生成动态代理

其他关于反射的知识

学习反射之前,我们先来复习一下Java代码在计算机中加载的其中三个阶段:
1、Source源代码阶段:.java文件经过javac命令被编译成.class字节码文件。
2、Class类对象阶段:.class字节码文件被类加载器加载进内存,并将其封装成Class对象(用于在内存中描述字节码文件),这个Class对象将原字节码文件中的成员变量抽取出来封装成数组Field[],将原字节码文件中的构造函数抽取出来封装成数组Construction[],*将成员方法
封装成数组Method[]。当然Class类内不止这三个,还封装了很多,我们常用的就这三个。
3、RunTime运行时阶段:使用new创建对象的过程。
image.png
其中这个生成的Class类对象对我们理解反射尤为重要。
那这个类是什么呢? Class 类的实例表示正在运行的 Java 应用程序中的类和接口。也就是jvm中有N多的实例每个类都有该Class对象。(包括基本数据类型)
Class 没有公共构造方法。Class 对象是在加载类时由 Java 虚拟机以及通过调用类加载器中的defineClass 方法自动构造的。也就是说这个对象不需要我们自己去处理创建,JVM已经帮我们创建好了,我们只需要去获取调用即可。

获取Class的实例的四种方式

因为 在运行期间,一个类只有一个Class对象产生,所以不管用哪种方式获取的都是同一个Class对象;
1)、 前提:若已知具体的类,通过类的class属性获取,该方法最为安全可靠, 程序性能最高 。 【Class类对象阶段】 类名.class:通过类名的属性class获取;

  1. 实例:Class clazz = Person.class;

2)、前提:已知某个类的实例对象,调用该实例的getClass()方法获取Class对象 。
【Runtime运行时阶段】对象.getClass():此方法是定义在Objec类中的方法,因此所有的类都会继承此方法。
多用于对象获取字节码的方式

  1. 实例:Class clazz = www.atguigu.com”.getClass();
  2. 或: Class clazz =p1.getClass();

3)【Source源代码阶段】 Class.forName(“全类名”):将字节码文件加载进内存,返回Class对象;但可能抛出ClassNotFoundException 异常。
多用于配置文件,将类名定义在配置文件中,通过读取配置文件加载类。

  1. 实例:Class clazz = Class.forName(“java.lang.String”);

4)使用类的加载器

  1. ClassLoader cl = this.getClass().getClassLoader();
  2. Class clazz4 = cl.loadClass(“类的全类名”);

几种方式中,常用第三种,第二种对象都有了还要反射干什么,第一种需要导入类包,依赖太强,不导包就抛编译错误。一般都使用第三种,一个字符串可以传入也可以写在配置文件中等多种方法。

哪些类型可以有Class对象?

  1. 1class:外部类,成员(成员内部类,静态内部类),局部内部类,匿名内部类
  2. 2interface:接口
  3. 3)[]:数组
  4. 4enum:枚举
  5. 5annotation:注解@interface
  6. 6primitive type:基本数据类型
  7. 7void

什么是 Java序列化?什么情况下需要序列化?

简单说就是为了保存在内存中的各种对象的状态(也就是实例变量,不是方法),并且可以把保存的对象状态再读出来。虽然你可以用你自己的各种各样的方法来保存object states,但是Java给你提供一种应该比你自己好的保存对象状态的机制,那就是序列化。
什么情况下需要序列化:
a)当你想把的内存中的对象状态保存到一个文件中或者数据库中时候;
b)当你想用套接字在网络上传送对象的时候;
c)当你想通过RMI传输对象的时候;
答:序列化就是一种用来处理对象流的机制,所谓对象流也就是将对象的内容进行流化。可以对流化后的对象进行读写操作,也可将流化后的对象传输于网络之间。序列化是为了解决对象流读写操作时可能引发的问题(如果不进行序列化可能会存在数据乱序的问题)。
要实现序列化,需要让一个类实现Serializable接口,该接口是一个标识性接口,标注该类对象是可被序列化的,然后使用一个输出流来构造一个对象输出流并通过writeObject(Object)方法就可以将实现对象写出(即保存其状态);如果需要反序列化则可以用一个输入流建立对象输入流,然后通过readObject方法从流中读取对象。
序列化除了能够实现对象的持久化之外,还能够用于对象的深度克隆。

动态代理是什么?有哪些应用?

学习动态代理,先来看看代理模式的设计原理: 即使用一个代理将对象包装起来, 然后用该代理对象取代原始对象。任何对原 始对象的调用都要通过代理对象来进行,代理对象决定是否以及何时将方法调用转到原 始对象上 。
静态代理的特征是代理类和目标 对象的类都是在编译期间确定下来[这一点是动态与静态代理的区别所在],不利于程序的扩展。同时,每一个代 理类只能为一个接口服务,这样一来程序开发中必然产生过多的代理,所以最 好可以通过一个代理类完成全部的代理功能。 要想让一个通用的代理类干完这些功能,这个通用的代理类是不可能在编译期间就确定下来的,可以利用反射在运行期间动态创建一个代理类,这样一来运行期间需要加载哪一个类就动态创建哪个类的代理类

动态代理:
当想要给实现了某个接口的类中的方法,加一些额外的处理。比如说加日志,加事务等。可以给这个类创建一个代理,故名思议就是创建一个新的类,这个类不仅包含原来类方法的功能,而且还在原来的基础上添加了额外处理的新类。这个代理类并不是定义好的,是动态生成的。具有解耦意义,灵活,扩展性强。
动代理的应用:

  • Spring的AOP
  • 加事务
  • 加权限
  • 加日志

怎么实现动态代理?

首先必须定义一个接口,还要有一个InvocationHandler(将实现接口的类的对象传递给它)处理类。再有一个工具类Proxy(习惯性将其称为代理类,因为调用他的newInstance()可以产生代理对象,其实他只是一个产生代理对象的工具类)。利用到InvocationHandler,拼接代理类源码,将其编译生成代理类的二进制码,利用加载器加载,并将其实例化产生代理对象,最后返回。