1. java中Hashtable, Vector, TreeSet, LinkedList哪些线程是安全的?
HashTable Vector
线程安全概念:
如果你的代码所在的进程中有多个线程在同时运行,而这些线程可能会同时运行这段代码。如果每次运行结果和单线程运行的结果是一样的,而且其他的变量的值也和预期的是一样的,就是线程安全的。
线程安全问题都是由全局变量及静态变量引起的。
若每个线程中对全局变量、静态变量只有读操作,而无写操作,一般来说,这个全局变量是线程安全的;若有多个线程同时执行写操作,一般都需要考虑线程同步,否则的话就可能影响线程安全。
继承体系
LinkedList 和 ArrayList 都是不同步的,线程不安全;
Vector 和 Stack 都是同步的,线程安全;
Set是线程不安全的;
Hashtable的方法是同步的,线程安全;
HashMap的方法不是同步的,线程不安全;
2.尝试编译以下程序会产生怎么样的结果?
public class MyClass {
long var;
public void MyClass(long param) { var = param; }//(1)
public static void main(String[] args) {
MyClass a, b;
a =new MyClass();//(2)
b =new MyClass(5);//(3)
}
}
答案:编译错误将在(3)处发生,因为该类没有构造函数,该构造函数接受一个int类型的参数
MyClass方法并不是构造参数,而是返回类型为void的普通方法,普通方法自然需要实例化对象然后去调用它,因为没有带参数的构造器,所以自然不能传一个int进去。
3.抽象类方法的访问权限默认都是public吗?(F)
关于抽象类
JDK 1.8以前,抽象类的方法默认访问权限为protected
JDK 1.8时,抽象类的方法默认访问权限变为default
关于接口
JDK 1.8以前,接口中的方法必须是public的
JDK 1.8时,接口中的方法可以是public的,也可以是default的
JDK 1.9时,接口中的方法可以是private的
4.关于操作符
int x,y;
x=5>>2;
y=x>>>2;
System.out.println(y);
5的二进制是0101。
x=5>>2 (>>带符号右移)
将0101右移2位,为:0001。
y=x>>>2 (>>>无符号右移,左边空缺补充为0)
将0001右移2位,补0。结果为:0000。
所以得出答案0
这道题首先要知道二进制的计算方式
举例:0011
二进制换算十进制,从右到左,以当前位数字 乘以 2 的索引字幂,然后相加,索引从0开始计算。
如:
右一数字为1,索引为0。所以为:12^0(1乘以2的0次方)
右二数字为1,索引为1。所以为:12^1(1乘以2的1次方)
右三数字为0,索引为2。所以为:02^2(0乘以2的2次方)
右四数字为0,索引为3。所以为:02^3(0乘以2的3次方)
最后把结果相加。所以计算方式为:
02^3+022+1*21+1*2^0
=0+0+2+1
=3
5.关于抽象类和接口叙述正确的是?
接口方法的访问权限默认都是public
抽象类
特点:
1.抽象类中可以构造方法
2.抽象类中可以存在普通属性,方法,静态属性和方法。
3.抽象类中可以存在抽象方法。
4.如果一个类中有一个抽象方法,那么当前类一定是抽象类;抽象类中不一定有抽象方法。
5.抽象类中的抽象方法,需要有子类实现,如果子类不实现,则子类也需要定义为抽象的。
6,抽象类不能被实例化,抽象类和抽象方法必须被abstract修饰
关键字使用注意:
抽象类中的抽象方法(其前有abstract修饰)不能用private、static、synchronized、native访问修饰符修饰。
接口
1.在接口中只有方法的声明,没有方法体。
2.在接口中只有常量,因为定义的变量,在编译的时候都会默认加上public static final
3.在接口中的方法,永远都被public来修饰。
4.接口中没有构造方法,也不能实例化接口的对象。(所以接口不能继承类)
5.接口可以实现多继承
6.接口中定义的方法都需要有实现类来实现,如果实现类不能实现接口中的所有方法则实现类定义为抽象类。
7,接口可以继承接口,用extends
6.访问权限修饰符的访问范围
访问权限修饰符的访问范围
7.JDBC链接数据库的步骤 贾琏预执释
//声明数据库驱动,数据源的url,用于登录数据库的账户和密码(将其他功能封装成方法的时候方便使用)
String driver = "数据库驱动名称";
String url = "数据库连接地址"
String user = "用来连接数据库的用户名";
String pwd = "用来连接数据库的密码";
//加载数据库驱动
Class.forName(driver);
//根据url创建数据库连接对象Connection
Connection con = DriverManage.getConnection(url,user,pwd);
//用数据库连接对象创建Statement对象(或PrepareStatement)
Statement s = con.createStatement();
或
PrepareStatement ps = con.PrepareStatement(sql);
//做数据库的增删改查工作
ResultSet rs = s.executeQuery();
//关闭结果集对象Resultset,statement对象,connection对象,
rs.close();
s.close();
con.close();
//各个步骤的异常处理
8.Servlet的声明周期
Servlet的生命周期分为5个阶段:加载、创建、初始化、处理客户请求、卸载。
(1)加载:容器通过类加载器使用servlet类对应的文件加载servlet
(2)创建:通过调用servlet构造函数创建一个servlet对象
(3)初始化:调用init方法初始化
(4)处理客户请求:每当有一个客户请求,容器会创建一个线程来处理客户请求
(5)卸载:调用destroy方法让servlet自己释放其占用的资源
9.关于运行时常量池,下列哪个说法是正确的?
运行时常量池大小受方法区大小的影响
存放了编译时期生成的各种字面量
存放编译时期生成的符号引用
在JDK1.8之前运行时常量池被放在方法区,属于线程共享,JDK1.8之后,元空间取代了方法区,运行时常量池被也被放在元空间中,运行时常池 主要存放, class文件元信息描述,编译后的代码,引用类型数据,类文件常量池。所谓的运行时常量池其实就是将编译后的类信息放入运行时的一个区域中,用来动态获取类信息。运行时常量池是在类加载完成之后,将每个class常量池中的符号引用值转存到运行时常量池中,也就是说,每个class都有一个运行时常量池,类在解析之后,将符号引用替换成直接引用,与全局常量池中的引用值保持一致。
运行时常量池是方法区的一部分。Class 文件中除了有类的版本、字段、方法、接口等描述信息外,还有常量池信息(用于存放编译期生成的各种字面量和符号引用)
10.在Web应用程序中,(web容器)负责将HTTP请求转换为HttpServletRequest对象
11.关于java线程安全和线程不安全
线程安全(Thread-safe)的集合对象:
Vector 线程安全
HashTable 线程安全
StringBuffer 线程安全
非线程安全的集合对象
ArrayList
LinkedList
HashMap
HashSet
TreeMap
TreeSet
StringBulider
12.String, StringBuffer,StringBuilder的区别
java中String、StringBuffer、StringBuilder是编程中经常使用的字符串类,他们之间的区别也是经常在面试中会问到的问题。现在总结一下,看看他们的不同与相同。
1.可变与不可变
String类中使用字符数组保存字符串,如下就是,因为有“final”修饰符,所以可以知道string对象是不可变的。
private final char value[];
String 为不可变对象,一旦被创建,就不能修改它的值. . 对于已经存在的String对象的修改都是重新创建一个新的对象,然后把新的值保存进去.
StringBuilder与StringBuffer都继承自AbstractStringBuilder类,在AbstractStringBuilder中也是使用字符数组保存字符串,如下就是,可知这两种对象都是可变的。
char[] value;
StringBuffer:是一个可变对象,当对他进行修改的时候不会像String那样重新建立对象 , 它只能通过构造函数来建立, 如: StringBuffer sb = new StringBuffer();
不能通过赋值符号对他进行付值. , 如 sb = "welcome to here!";//error
对象被建立以后,在内存中就会分配内存空间,并初始保存一个null.向StringBuffer中赋值的时候可以通过它的append方法. sb.append("hello");
2.是否多线程安全
String中的对象是不可变的,也就可以理解为常量, 显然线程安全 。
AbstractStringBuilder是StringBuilder与StringBuffer的公共父类,定义了一些字符串的基本操作,如expandCapacity、append、insert、indexOf等公共方法。
StringBuffer对方法加了同步锁或者对调用的方法加了同步锁,所以是 线程安全的 。看如下源码:
public synchronized StringBuffer reverse() {
super.reverse();
return this ;
}
public int indexOf(String str) {
return indexOf(str, 0);
//存在 public synchronized int indexOf(String str, int fromIndex) 方法
}
StringBuilder并没有对方法进行加同步锁,所以是 非线程安全的 。
3.StringBuilder与StringBuffer共同点
StringBuilder与StringBuffer有公共父类AbstractStringBuilder( 抽象类 )。
抽象类与接口的其中一个区别是:抽象类中可以定义一些子类的公共方法,子类只需要增加新的功能,不需要重复写已经存在的方法;而接口中只是对方法的申明和常量的定义。
StringBuilder、StringBuffer的方法都会调用AbstractStringBuilder中的公共方法,如super.append(...)。只是StringBuffer会在方法上加synchronized关键字,进行同步。
最后,如果程序不是多线程的,那么使用StringBuilder效率高于StringBuffer。
效率比较String < StringBuffer < StringBuilder,但是在String S1 ="This is only a"+"simple"+"test"时,String效率最高。
13.is-a:继承关系 has-a:从属关系 like-a:组合关系
14.两同两小一大原则:
方法名相同,参数类型相同
子类返回类型小于等于父类方法返回类型,
子类抛出异常小于等于父类方法抛出异常,
子类访问权限大于等于父类方法访问权限。
15.JVM的命令
1、jps:查看本机java进程信息。
2、jstack:打印线程的栈信息,制作线程dump文件。
3、jmap:打印内存映射,制作堆dump文件
4、jstat:性能监控工具
5、jhat:内存分析工具
6、jconsole:简易的可视化控制台
7、jvisualvm:功能强大的控制台
16.正则表达式
元字符
描述
\
将下一个字符标记符、或一个向后引用、或一个八进制转义符。例如,“\n”匹配\n。“\n”匹配换行符。序列“\”匹配“\”而“(”则匹配“(”。即相当于多种编程语言中都有的“转义字符”的概念。
^
匹配输入字符串的开始位置。如果设置了RegExp对象的Multiline属性,^也匹配“\n”或“\r”之后的位置。
$
匹配输入字符串的结束位置。如果设置了RegExp对象的Multiline属性,$也匹配“\n”或“\r”之前的位置。
*
匹配前面的子表达式任意次。例如,zo能匹配“z”,也能匹配“zo”以及“zoo”。等价于o{0,}
+
匹配前面的子表达式一次或多次(大于等于1次)。例如,“zo+”能匹配“zo”以及“zoo”,但不能匹配“z”。+等价于{1,}。
?
匹配前面的子表达式零次或一次。例如,“do(es)?”可以匹配“do”或“does”中的“do”。?等价于{0,1}。
{n}
n是一个非负整数。匹配确定的n次。例如,“o{2}”不能匹配“Bob”中的“o”,但是能匹配“food”中的两个o。
{n,}
n是一个非负整数。至少匹配n次。例如,“o{2,}”不能匹配“Bob”中的“o”,但能匹配“foooood”中的所有o。“o{1,}”等价于“o+”。“o{0,}”则等价于“o*”。
{n,m}
m和n均为非负整数,其中n<=m。最少匹配n次且最多匹配m次。例如,“o{1,3}”将匹配“fooooood”中的前三个o为一组,后三个o为一组。“o{0,1}”等价于“o?”。请注意在逗号和两个数之间不能有空格。
?
当该字符紧跟在任何一个其他限制符(*,+,?,{n},{n,},{n,m})后面时,匹配模式是非贪婪的。非贪婪模式尽可能少的匹配所搜索的字符串,而默认的贪婪模式则尽可能多的匹配所搜索的字符串。例如,对于字符串“oooo”,“o+”将尽可能多的匹配“o”,得到结果[“oooo”],而“o+?”将尽可能少的匹配“o”,得到结果 ['o', 'o', 'o', 'o']
.点
匹配除“\r\n”之外的任何单个字符。要匹配包括“\r\n”在内的任何字符,请使用像“[\s\S]”的模式。
(pattern)
匹配pattern并获取这一匹配。所获取的匹配可以从产生的Matches集合得到,在VBScript中使用SubMatches集合,在JScript中则使用
9属性。要匹配圆括号字符,请使用“(”或“)”。
[xyz]
字符集合。匹配所包含的任意一个字符。例如,“[abc]”可以匹配“plain”中的“a”。
[^xyz]
负值字符集合。匹配未包含的任意字符。例如,“[^abc]”可以匹配“plain”中的“plin”。
[a-z]
字符范围。匹配指定范围内的任意字符。例如,“[a-z]”可以匹配“a”到“z”范围内的任意小写字母字符。 注意:只有连字符在字符组内部时,并且出现在两个字符之间时,才能表示字符的范围; 如果出字符组的开头,则只能表示连字符本身.
[^a-z]
负值字符范围。匹配任何不在指定范围内的任意字符。例如,“[^a-z]”可以匹配任何不在“a”到“z”范围内的任意字符。
\b
匹配一个单词边界,也就是指单词和空格间的位置(即正则表达式的“匹配”有两种概念,一种是匹配字符,一种是匹配位置,这里的\b就是匹配位置的)。例如,“er\b”可以匹配“never”中的“er”,但不能匹配“verb”中的“er”。
\B
匹配非单词边界。“er\B”能匹配“verb”中的“er”,但不能匹配“never”中的“er”。
\cx
匹配由x指明的控制字符。例如,\cM匹配一个Control-M或回车符。x的值必须为A-Z或a-z之一。否则,将c视为一个原义的“c”字符。
\d
匹配一个数字字符。等价于[0-9]。grep 要加上-P,perl正则支持
\D
匹配一个非数字字符。等价于[^0-9]。grep要加上-P,perl正则支持
\f
匹配一个换页符。等价于\x0c和\cL。
\n
匹配一个换行符。等价于\x0a和\cJ。
\r
匹配一个回车符。等价于\x0d和\cM。
\s
匹配任何不可见字符,包括空格、制表符、换页符等等。等价于[ \f\n\r\t\v]。
\S
匹配任何可见字符。等价于[^ \f\n\r\t\v]。
\t
匹配一个制表符。等价于\x09和\cI。
\v
匹配一个垂直制表符。等价于\x0b和\cK。
\w
匹配包括下划线的任何单词字符。类似但不等价于“[A-Za-z0-9_]”,这里的"单词"字符使用Unicode字符集。
\W
匹配任何非单词字符。等价于“[^A-Za-z0-9_]”。
(?:pattern) 非获取匹配,匹配pattern但不获取匹配结果,不进行存储供以后使用。这在使用或字符“(|)”来组合一个模式的各个部分时很有用。例如“industr(?:y|ies)”就是一个比“industry|industries”更简略的表达式。
(?=pattern) 非获取匹配,正向肯定预查,在任何匹配pattern的字符串开始处匹配查找字符串,该匹配不需要获取供以后使用。例如,“Windows(?=95|98|NT|2000)”能匹配“Windows2000”中的“Windows”,但不能匹配“Windows3.1”中的“Windows”。预查不消耗字符,也就是说,在一个匹配发生后,在最后一次匹配之后立即开始下一次匹配的搜索,而不是从包含预查的字符之后开始。
(?!pattern) 非获取匹配,正向否定预查,在任何不匹配pattern的字符串开始处匹配查找字符串,该匹配不需要获取供以后使用。例如“Windows(?!95|98|NT|2000)”能匹配“Windows3.1”中的“Windows”,但不能匹配“Windows2000”中的“Windows”。
(?<=pattern) 非获取匹配,反向肯定预查,与正向肯定预查类似,只是方向相反。例如,“(?<=95|98|NT|2000)Windows”能匹配“2000Windows”中的“Windows”,但不能匹配“3.1Windows”中的“Windows”。
(?
此处用或任意一项都不能超过2位,如“(?
x|y 匹配x或y。例如,“z|food”能匹配“z”或“food”(此处请谨慎)。“[zf]ood”则匹配“zood”或“food”。
17.java语言的下面几种数组复制方法中,哪个效率最高?
for 循环逐一复制
System.arraycopy
Array.copyOf
使用clone方法
复制的效率System.arraycopy>clone>Arrays.copyOf>for循环,这个有兴趣自己测试一下就知道了。这里面在System类源码中给出了arraycopy的方法,是native方法,也就是本地方法,肯定是最快的。而Arrays.copyOf(注意是Arrays类,不是Array)的实现,在源码中是调用System.copyOf的,多了一个步骤,肯定就不是最快的。前面几个说System.copyOf的不要看,System类底层根本没有这个方法,自己看看源码就全知道了。
18.在创建派生类对象,构造函数的执行顺序
父类静态域 => 子类静态域 => 父类成员初始化 => 父类构造块 => 父类构造方法 => 子类成员初始化 => 子类构造块 => 子类构造方法
规律就是 父类先于子类 静态的先于非静态的
其中静态域包含静态代码块与静态方法,这个谁在前面,则先执行谁。
19.在java语言中,判断一块内存空间是否符合垃圾收集器收集标准的标准只有两个
1.给对象赋值为null,以下没有调用过。
2.给对象赋了新的值,重新分配了内存空间
20.关于ASCII码和ANSI码
1.标准ASCII只使用7个bit,扩展的ASCII使用8个bit
2.ANSI通常使用 0x00~0x7f 范围的1 个字节来表示 1 个英文字符。超出此范围的使用0x80~0xFFFF来编码,即扩展的ASCII编码。不同 ANSI 编码之间互不兼容。在简体中文Windows操作系统中,ANSI 编码代表 GBK 编码;在繁体中文Windows操作系统中,ANSI编码代表Big5;在日文Windows操作系统中,ANSI 编码代表 Shift_JIS 编码。
3.ANSI通常使用 0x00~0x7f 范围的1 个字节来表示 1 个英文字符,即ASCII码
4.ASCII码包含一些特殊空字符
21.以下代码执行的结果是多少
public class Demo {
public static void main(String[] args) {
Collection>[] collections =
{new HashSet(), new ArrayList(), new HashMap().values()};
Super subToSuper = new Sub();
for(Collection> collection: collections) {
System.out.println(subToSuper.getType(collection));
}
}
abstract static class Super {
public static String getType(Collection> collection) {
return “Super:collection”;
}
public static String getType(List> list) {
return “Super:list”;
}
public String getType(ArrayList> list) {
return “Super:arrayList”;
}
public static String getType(Set> set) {
return “Super:set”;
}
public String getType(HashSet> set) {
return “Super:hashSet”;
}
}
static class Sub extends Super {
public static String getType(Collection> collection) {
return "Sub"; }
}
}
Super:collection
Super:collection
Super:collection
考察点1:重载静态多分派——根据传入重载方法的参数类型,选择更加合适的一个重载方法
考察点2:static方法不能被子类覆写,在子类中定义了和父类完全相同的static方法,则父类的static方法被隐藏,Son.staticmethod()或new Son().staticmethod()都是调用的子类的static方法,如果是Father.staticmethod()或者Father f = new Son(); f.staticmethod()调用的都是父类的static方法。
考察点3:此题如果都不是static方法,则最终的结果是A. 调用子类的getType,输出collection
22.哪些操作会使线程释放锁资源?
wait()
join()
所谓的释放锁资源实际是通知对象内置的monitor对象进行释放,而只有所有对象都有内置的monitor对象才能实现任何对象的锁资源都可以释放。又因为所有类都继承自Object,所以wait()就成了Object方法,也就是通过wait()来通知对象内置的monitor对象释放,而且事实上因为这涉及对硬件底层的操作,所以wait()方法是native方法,底层是用C写的。
其他都是Thread所有,所以其他3个是没有资格释放资源的
而join()有资格释放资源其实是通过调用wait()来实现的
//代码如下:
wait()方法
public final native void wait(long timeoutMillis) throws InterruptedException;
join()方法
public final synchronized void join(long millis)
throws InterruptedException {
long base = System.currentTimeMillis();
long now = 0;
if (millis < 0) {
throw new IllegalArgumentException("timeout value is negative");
}
if (millis == 0) {
while (isAlive()) {
wait(0);
}
} else {
while (isAlive()) {
long delay = millis - now;
if (delay <= 0) {
break;
}
wait(delay);
now = System.currentTimeMillis() - base;
}
}
}
1.sleep()方法
在指定时间内让当前正在执行的线程暂停执行,但不会释放“锁标志”。不推荐使用。
sleep()使当前线程进入阻塞状态,在指定时间内不会执行。
2.wait()方法
在其他线程调用对象的notify或notifyAll方法前,导致当前线程等待。线程会释放掉它所占有的“锁标志”,从而使别的线程有机会抢占该锁。
当前线程必须拥有当前对象锁。如果当前线程不是此锁的拥有者,会抛出IllegalMonitorStateException异常。
唤醒当前对象锁的等待线程使用notify或notifyAll方法,也必须拥有相同的对象锁,否则也会抛出IllegalMonitorStateException异常。
waite()和notify()必须在synchronized函数或synchronized block中进行调用。如果在non-synchronized函数或non-synchronized block中进行调用,虽然能编译通过,但在运行时会发生IllegalMonitorStateException的异常。
3.yield方法
暂停当前正在执行的线程对象。
yield()只是使当前线程重新回到可执行状态,所以执行yield()的线程有可能在进入到可执行状态后马上又被执行。
yield()只能使同优先级或更高优先级的线程有执行的机会。
4.join方法
join()等待该线程终止。
等待调用join方法的线程结束,再继续执行。如:t.join();//主要用于等待t线程运行结束,若无此句,main则会执行完毕,导致结果不可预测
23.关于Java中的ClassLoader下面的哪些描述是错误的?
A.默认情况下,Java应用启动过程涉及三个ClassLoader: Boostrap, Extension, System
B.一般的情况不同ClassLoader装载的类是不相同的,但接口类例外,对于同一接口所有类装载器装载所获得的类是相同的
C.类装载器需要保证类装载过程的线程安全
D.ClassLoader的loadClass在装载一个类时,如果该类不存在它将返回null
E.ClassLoader的父子结构中,默认装载采用了父优先
F.所有ClassLoader装载的类都来自CLASSPATH环境指定的路径
从java虚拟机的角度讲,只有两种不同的类加载器:一种是启动类加载器(Bootstrap ClassLoader),这个类加载器使用的是c++实现的,是虚拟机的一部分,另一类是就是所有其他类加载器,这些类加载器都由java语言实现,独立于虚拟机外部,并且全都继承自抽象类。
从开发人员的角度看,类加载器还可以划分为3种系统类加载器,启动类加载器(Bootstrap ClassLoader),负责加载存放在/lib目录中的,或者被-Xbootclasspath参数所指定的路径中的,并且是虚拟机识别的(仅按照文件名识别,如rt.jar,名字不符的类库即使放在lib目录中也不会被加载)类库加载到虚拟机中内存中。启动类加载器无法被java程序直接引用,用户在编写自定义类加载器是,如果需要把加载请求委派给引导类加载器,那直接使用null代替即可。
扩展类加载器(Extension ClassLoader):这个类加载器有sun.misc.Launcher
AppClassLoader实现。由于这个类加载器是ClassLoader中的getSystemClassLoader()方法的返回值,所以也称它为系统类加载器(System ClassLoader)。他负责加载用户类路径(ClassPath)上所指定的类库,开发者可以直接使用这个类加载器,如果应用程序中没有自定义过自己的类加载器,一般情况下这个就是程序中默认的类加载器。对此,如果有必要开发者可以加入自己定义的类加载器。
一般对于我们java程序员来说,类的加载使用的是双亲委派模型,即当一个类需要加载时,会将类传给Application ClassLoader,但是Application ClassLoader并不会加载,不管它是否能加载,而是传给它的"父类" Extension ClassLoader,Extension ClassLoader同样不会加载,同样传给 Bootstrap ClassLoader(注意不是我们常说的那种父类,但是可以这样理解),这时Bootstrap ClassLoader会判断它是否能加载,能加载就直接加载了,不能加载就传给Extension ClassLoader,Extension ClassLoader同样的判断是否能加载,能加载就直接加载,不能加载就传给Application ClassLoader,然后Application ClassLoader也判断能否加载,如果还是不能加载应该就是报ClassNotFoundException了。这就是双亲委托模型的简单理解了。
对于上面的"父类"为什么要打引号,因为它们并不是真的像java中继承的关系,而是组合的关系,即在"子类"中存在一个成员变量指向"父类"的引用。
所以AE对DF错了。
对于C,很容易理解,因为我们知道一个类只需要加载一次就够了,所以要保证线程安全。
难点就在B了,其实也好理解,就是体现双亲委托模型的优势的时候了,之所以使用双亲委托机制是为了保证java程序的稳定运作,比如当你使用的不是双亲委托模型的时候,然后刚好开发者又定义了一个类,一个java.lang.String这样一个类,如果不使用双亲委托模型,当类加载的时候就有可能会加载开发者定义的String类,这导致了java代码的一片混乱,可读性极差。(PS:但这并不意味着类加载器只要双亲委托模型就行了,没有完美的模型,只有最合适的模型,有不同的需求使用不同的模型。比如破坏双亲委派模型,有兴趣的牛友可以自行了解),所以可以这么说,不同的类加载器加载出来的类是不一样的,不同的类加载器加载同一个类会在方法区产生两个不同的类,彼此不可见,并且在堆中生成不同的Class实例。对于接口,其实就是一个特殊的类,和类一样,在堆中产生不同的class对象。
A.Java系统提供3种类加载器:启动类加载器(Bootstrap ClassLoader) 扩展类加载器(Extension ClassLoader) 应用程序类加载器(Application ClassLoader). A正确
B.《深入理解Java虚拟机》P228:对于任意一个类,都需要由加载它的类加载器和这个类本身一同确立其在Java虚拟机中的唯一性,每一个类加载器,都拥有一个独立的类名称空间。这句话可以表达得更通俗一些:比较两个类是否“相等”,只有在这两个类是由同一个类加载器加载的前提下才有意义,否则,即使这两个类来源于同一个Class文件,被同一个虚拟机加载,只要加载它们的类加载器不同,那么这两个类必定不相等。接口类是一种特殊类,因此对于同一接口不同的类装载器装载所获得的类是不相同的。B错误
C.类只需加载一次就行,因此要保证类加载过程线程安全,防止类加载多次。C正确
D. Java程序的类加载器采用双亲委派模型,实现双亲委派的代码集中在java.lang.ClassLoader的loadClass()方法中,此方法实现的大致逻辑是:先检查是否已经被加载,若没有加载则调用父类加载器的loadClass()方法,若父类加载器为空则默认使用启动类加载器作为父类加载器。如果父类加载失败,抛出ClassNotFoundException异常。D错误
E.双亲委派模型的工作过程:如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器去完成,每一个层次的类加载器都是如此,因此所有的加载请求最终都应该传送到顶层的启动类加载器中,只有当父加载器反馈自己无法完成这个加载请求时,子加载器才会尝试自己去加载。E正确
F.应用程序类加载器(Application ClassLoader)负责加载用户类路径(ClassPath)上所指定的类库,不是所有的ClassLoader都加载此路径。F错误