面试必考:Java基础面试题(四)
28.反射的原理,反射创建类实例的三种方式是什么。
反射机制:
所谓的反射机制就是java语言在运行时拥有一项自观的能力。通过这种能力可以彻底的了解自身的情况为下一步的动作做准备。下面具体介绍一下java的反射机制。这里你将颠覆原来对java的理解。 Java的反射机制的实现要借助于4个类:class,Constructor,Field,Method;其中class代表的时类对 象,Constructor-类的构造器对象,Field-类的属性对象,Method-类的方法对象。通过这四个对象我们可以粗略的看到一个类的各个组 成部分。
类的实例对象的三种表示方式
//第一种表示方式–》实际在告诉我们任何一个类都有一个隐含的静态成员变量class
Class class1 = Foo.class;
//第二种表示方式 已经知道该类的对象通过getClass方法
Class class2 = foo1.getClass();
//第三种表达方式
Class class3 = null;
try {
class3 = Class.forName("com.imooc.reflect.Foo");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
29.反射中,Class.forName和ClassLoader区别 。
java中class.forName()和classLoader都可用来对类进行加载。
不同:
1)class.forName()除了将类的.class文件加载到jvm中之外,还会对类进行解释,执行类中的static块,还会执行给静态变量赋值的静态方法
2)classLoader只干一件事情,就是将.class文件加载到jvm中,不会执行static中的内容,只有在newInstance才会去执行static块。
注:Class.forName(name, initialize, loader)带参函数也可控制是否加载static块。并且只有调用了newInstance()方法采用调用构造函数,创建类的对象,附上Class.forName()源码
30.为什么CGlib方式可以对类实现代理。
JDK实现动态代理需要实现类通过接口定义业务方法,对于没有接口的类,如何实现动态代理呢,这就需要CGLib了。CGLib采用了非常底层的字节码技术,其原理是通过字节码技术为一个类创建子类,并在子类中采用方法拦截的技术拦截所有父类方法的调用,顺势织入横切逻辑。JDK动态代理与CGLib动态代理均是实现Spring AOP的基础。
31.如何在父类中为子类自动完成所有的hashcode和equals实现?这么做有何优劣。
在父类中实现即可,子类无需重写,不满足要求时需要重写
32.深拷贝和浅拷贝区别。
浅拷贝:复制基本类型的属性;引用类型的属性复制,复制栈中的变量 和 变量指向堆内存中的对象的指针,不复制堆内存中的对象。
深拷贝:复制基本类型的属性;引用类型的属性复制,复制栈中的变量 和 变量指向堆内存中的对象的指针和堆内存中的对象。
33.error和exception的区别,CheckedException,RuntimeException的区别。
Error:Error类对象由 Java 虚拟机生成并抛出,大多数错误与代码编写者所执行的操作无关。例如,Java虚拟机运行错误(Virtual MachineError),当JVM不再有继续执行操作所需的内存资源时,将出现 OutOfMemoryError。这些异常发生时,Java虚拟机(JVM)一般会选择线程终止;还有发生在虚拟机试图执行应用时,如类定义错误(NoClassDefFoundError)、链接错误(LinkageError)。这些错误是不可查的,因为它们在应用程序的控制和处理能力之 外,而且绝大多数是程序运行时不允许出现的状况。对于设计合理的应用程序来说,即使确实发生了错误,本质上也不应该试图去处理它所引起的异常状况。在Java中,错误通常是使用Error的子类描述。
Exception:在Exception分支中有一个重要的子类RuntimeException(运行时异常),该类型的异常自动为你所编写的程序定义ArrayIndexOutOfBoundsException(数组下标越界)、NullPointerException(空指针异常)、ArithmeticException(算术异常)、MissingResourceException(丢失资源)、ClassNotFoundException(找不到类)等异常,这些异常是不检查异常,程序中可以选择捕获处理,也可以不处理。这些异常一般是由程序逻辑错误引起的,程序应该从逻辑角度尽可能避免这类异常的发生;而RuntimeException之外的异常我们统称为非运行时异常,类型上属于Exception类及其子类,从程序语法角度讲是必须进行处理的异常,如果不处理,程序就不能编译通过。如IOException、SQLException等以及用户自定义的Exception异常,一般情况下不自定义检查异常。
34.请列出5个运行时异常。
ClassCastException(类转换异常)
IndexOutOfBoundsException(数组越界)
NullPointerException(空指针)
ArrayStoreException(数据存储异常,操作数组时类型不一致)
ArithmeticException算术异常5/0
35.在自己的代码中,如果创建一个java.lang.String类,这个类是否可以被类加载器加载?为什么。
1.、双亲委派模型
类加载器可分为两类:一是启动类加载器(Bootstrap ClassLoader),是C++实现的,是JVM的一部分;另一种是其它的类加载器,是Java实现的,独立于JVM,全部都继承自抽象类java.lang.ClassLoader。jdk自带了三种类加载器,分别是启动类加载器(Bootstrap ClassLoader),扩展类加载器(Extension ClassLoader),应用程序类加载器(Application ClassLoader)。后两种加载器是继承自抽象类java.lang.ClassLoader。
一般是: 自定义类加载器 >> 应用程序类加载器 >> 扩展类加载器 >> 启动类加载器
上面的层次关系被称为双亲委派模型(Parents Delegation Model)。除了最顶层的启动类加载器外,其余的类加载器都有对应的父类加载器。
再简单说下双亲委托机制:如果一个类加载器收到了类加载的请求,它首先不会自己尝试去加载这个类,而是把这个请求委派给父类加载器,每一个层次的类加载器都是加此,因此所有的加载请求最终到达顶层的启动类加载器,只有当父类加载器反馈自己无法完成加载请求时(指它的搜索范围没有找到所需的类),子类加载器才会尝试自己去加载。
各个类加载器之间是组合关系,并非继承关系。
当一个类加载器收到类加载的请求,它将这个加载请求委派给父类加载器进行加载,每一层加载器都是如此,最终,所有的请求都会传送到启动类加载器中。只有当父类加载器自己无法完成加载请求时,子类加载器才会尝试自己加载。
双亲委派模型可以确保安全性,可以保证所有的Java类库都是由启动类加载器加载。如用户编写的java.lang.Object,加载请求传递到启动类加载器,启动类加载的是系统中的Object对象,而用户编写的java.lang.Object不会被加载。如用户编写的java.lang.virus类,加载请求传递到启动类加载器,启动类加载器发现virus类并不是核心Java类,无法进行加载,将会由具体的子类加载器进行加载,而经过不同加载器进行加载的类是无法访问彼此的。由不同加载器加载的类处于不同的运行时包。所有的访问权限都是基于同一个运行时包而言的。
二、为什么要使用这种双亲委托模式呢?
因为这样可以避免重复加载,当父亲已经加载了该类的时候,就没有必要子ClassLoader再加载一次。
考虑到安全因素,我们试想一下,如果不使用这种委托模式,那我们就可以随时使用自定义的String来动态替代java核心api中定义类型,这样会存在非常大的安全隐患,而双亲委托的方式,就可以避免这种情况,因为String已经在启动时被加载,所以用户自定义类是无法加载一个自定义的ClassLoader。
思考:假如我们自己写了一个java.lang.String的类,我们是否可以替换调JDK本身的类?
答案是否定的。我们不能实现。为什么呢?我看很多网上解释是说双亲委托机制解决这个问题,其实不是非常的准确。因为双亲委托机制是可以打破的,你完全可以自己写一个classLoader来加载自己写的java.lang.String类,但是你会发现也不会加载成功,具体就是因为针对java.*开头的类,jvm的实现中已经保证了必须由bootstrp来加载。
因加载某个类时,优先使用父类加载器加载需要使用的类。如果我们自定义了java.lang.String这个类, 加载该自定义的String类,该自定义String类使用的加载器是AppClassLoader,根据优先使用父类加载器原理, AppClassLoader加载器的父类为ExtClassLoader,所以这时加载String使用的类加载器是ExtClassLoader, 但是类加载器ExtClassLoader在jre/lib/ext目录下没有找到String.class类。然后使用ExtClassLoader父类的加载器BootStrap, 父类加载器BootStrap在JRE/lib目录的rt.jar找到了String.class,将其加载到内存中。这就是类加载器的委托机制。
36.说说 ArrayList,Vector, LinkedList 的存储性能和特性。
ArrayList 和Vector他们底层的实现都是一样的,都是使用数组方式存储数据,此数组元素数大于实际存储的数据以便增加和插入元素,它们都允许直接按序号索引元素,但是插入元素要涉及数组元素移动等内存操作,所以索引数据快而插入数据慢。
Vector中的方法由于添加了synchronized修饰,因此Vector是线程安全的容器,但性能上较ArrayList差,因此已经是Java中的遗留容器。
LinkedList使用双向链表实现存储(将内存中零散的内存单元通过附加的引用关联起来,形成一个可以按序号索引的线性结构,这种链式存储方式与数组的连续存储方式相比,内存的利用率更高),按序号索引数据需要进行前向或后向遍历,但是插入数据时只需要记录本项的前后项即可,所以插入速度较快。
Vector属于遗留容器(Java早期的版本中提供的容器,除此之外,Hashtable、Dictionary、BitSet、Stack、Properties都是遗留容器),已经不推荐使用,但是由于ArrayList和LinkedListed都是非线程安全的,如果遇到多个线程操作同一个容器的场景,则可以通过工具类Collections中的synchronizedList方法将其转换成线程安全的容器后再使用(这是对装潢模式的应用,将已有对象传入另一个类的构造器中创建新的对象来增强实现)。