J2SE基础
1. 九种基本数据类型的大小,以及他们的封装类
注:事实上应该是八种基本数据类型,String类并不属于基本类型。
String a = "hello world"; | String b = new String ("hello world"); |
---|---|
先创建一个字符串对象“hello world”,而这个字符串实际上是放在字符串缓冲区(敲黑板💯)中,然后把a指向这个对象 | 创建两个对象一个是“hello world”这个放在字符串缓冲区中的,另一个是new String()这个对象,新对象中保存的是“hello world”对象罢了,这个对象是放在堆内存(敲黑板💯)中,而b指向这个new String ()对象 |
字符串缓冲区中对相同的字符串只会存一次。
假如我们同时写了String a ="hello world",String b = new String("hello world"),那么字符串缓冲区实际只有一个hello world字符串。
基本数据类型 | 大小 | 封装类 |
---|---|---|
byte | 1字节 | Byte |
short | 2字节 | Short |
int | 4字节 | Integer |
long | 8字节 | Long |
float | 4字节 | Float |
double | 8字节 | Double |
char | 2字节 | Character(不推荐使用) |
boolean | 1字节或者4字节 | Boolean |
往ArrayList,HashMap中放东西时,像int,double这种内建类型是放不进去的,因为容器都是装object的
《Java虚拟机规范》一书中的描述:“虽然定义了boolean这种数据类型,但是只对它提供了非常有限的支持。在Java虚拟机中没有任何供boolean值专用的字节码指令,Java语言表达式所操作的boolean值,在编译之后都使用Java虚拟机中的int数据类型来代替,而boolean数组将会被编码成Java虚拟机的byte数组,每个元素boolean元素占8位”。这样我们可以得出boolean类型占了单独使用是4个字节,在数组中又是1个字节。
PS:使用int的原因是,对于当下32位的处理器(CPU)来说,一次处理数据是32位(这里不是指的是32/64位系统,而是指CPU硬件层面)更高效。
char是unsigned(无符号的),byte/short/int/long/float/double是signed(有符号的),boolean应该是unsigned(个人的猜想,并未验证过)
PS:因为char要用Unicode(敲黑板💯)表示,必须16位,所以无符号,是实在挤不出来位了,要不然Java设计者肯定也设计成有符号的,因为他觉得unsigned大部分人并不能真正搞懂🙃(不是针对你,是说在座的都是...)
//验证char有几位
public class Test {
public static void main(String[] args) {
System.out.println(java.nio.charset.Charset.defaultCharset());
String str = "中";
char x = '中';
//也可以getBytes('utf-8'),我的系统默认是utf-8
byte[] bytesStr = str.getBytes();
byte[] bytesChar = charToByte(x);
System.out.println("bytesStr 大小:" + bytesStr.length);
byteResult(bytesStr);
//其实这个输出是没意义的,只是纯粹的格式整齐而已
System.out.println("bytesChar 大小:" + bytesChar.length);
byteResult(bytesChar);
}
private static byte[] charToByte(char c) {
/**
* 定义成3个字节,只是为了更好的验证,b[0]都是0,并未使用
* 当然英文字母的情况下b[1]都是0,但这情况下实际是的确使用了的
*/
byte[] b = new byte[3];
b[0] = (byte) ((c & 0xFF0000) >> 16);
b[1] = (byte) ((c & 0xFF00) >> 8);
b[2] = (byte) (c & 0xFF);
return b;
}
private static void byteResult(byte[] bs) {
for (byte b : bs) {
for (int i = 7; i >= 0; i--) {
System.out.print(b >> i & 1);
}
System.out.println();
}
}
}
结果如下:
UTF-8
bytesStr 大小:3
11100100
10111000
10101101
bytesChar 大小:3
00000000
01001110
00101101
为什么String和char按位输出的结果不一样呢?
因为char输出的是Unicode格式,而String在本机的编译器上输出的是UTF-8格式
美国的ASCII全部编码范围是0-127(PS:编号从0开始的32种状态分别规定了特殊的用途)
⬇️
西欧国家规定从128 到255这一页的字符集被称扩展字符集
⬇️
中国的GB2312(对ASCII 的中文扩展)规定:一个小于127的字符的意义与原来相同,但两个大于127的字符连在一起时,就表示一个汉字,前面的一个字节(高字节)从0xA1用到0xF7,后面一个字节(低字节)从0xA1到0xFE,这样我们就可以组合出大约7000多个简体汉字了(还包括了数学符号、罗马希腊的字母、日文的假名们)
PS:GB2312下一个字节大于0xA1,就是汉字的内码的一部分(因为两个字节才组成一个汉字)
⬇️
但是中国的汉字太多了
0xA1十进制是161,那还有128->160的空间可以使用
还是不够用
所以又规定只要第一个字节是大于127就固定表示这是一个汉字的开始,不管后面跟的是不是扩展字
这就是GBK标准,包括了GB2312 的所有内容,同时又增加了近20000个新的汉字(包括繁体字)和符号
后来少数民族也要用电脑了,又加了几千个新的少数民族的字
GBK扩成了GB18030
⬇️
计算机的巴比伦塔命题
ISO废了所有的地区性编码方案,重新搞一个包括了地球上所有文化、所有字母和符号的编码!
他们打算叫它”Universal Multiple-Octet Coded Character Set”,简称UCS, 俗称Unicode。
Unicode开始制订时,计算机的存储器容量极大地发展了,空间再也不成为问题了,于是ISO就直接规定必须用两个字节。
这种大气的方案在保存英文文本时会多浪费一倍的空间(其高8位永远是0)
⬇️
直到互联网的出现,为解决Unicode如何在网络上传输的问题,于是面向传输的众多UTF(UCS Transfer Format)标准出现了,顾名思义,UTF-8就是每次8个位传输数据,而UTF-16就是每次16个位
UTF-8最大的一个特点,就是它是一种变长的编码方式。它可以使用1~4个字节表示一个符号,根据不同的符号而变化字节长度
当字符在ASCII码的范围时,就用一个字节表示,保留了ASCII字符一个字节的编码做为它的一部分,注意的是Unicode一个中文字符占2个字节,而UTF-8一个中文字符占3个字节
Unicode符号范围(十六进制) | UTF-8编码方式(二进制) |
---|---|
0000 0000-0000 007F | 0xxxxxxx |
0000 0080-0000 07FF | 110xxxxx 10xxxxxx |
0000 0800-0000 FFFF | 1110xxxx 10xxxxxx 10xxxxxx |
0001 0000-0010 FFFF | 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx |
其中byte和short应用于底层文件处理或者需要占据存储空间量的大数组(如:byte[]),举个🌰,⬇️
对象的序列化就是将你程序中实例化的某个类的对象,比如,你自定一个类MyClass,或者任何一个类的对象,将它转换成字节数组,也就是说可以放到一个byte数组中,这时候,你既然已经把一个对象放到了byte数组中,那么你当然就可以随便处置了它了,用得最多的就是把他发送到网络上远程的计算机上了
EJB概念
运行在一个独立的服务器上,并封装了业务逻辑的组件就是EJB(Enterprise JavaBean)
客户端是通过网络对EJB 对象进行调用的。在Java中,能够实现远程对象调用的技术是RMI,而EJB 技术基础正是RMI
2. Switch能否用string做参数
Java 7之后可以
在jdk1.7之前,switch只能支持byte、short、char、int这几个基本数据类型和其对应的封装类型。switch后面的括号里面只能放int类型的值(敲黑板💯),但由于byte,short,char类型,它们会自动转换为int类型(精精度小的向大的转化),所以它们也支持。
注意,对于精度比int大的类型,比如long、float,doulble,不会自动转换为int,如果想使用,就必须强转为int,如(int)float;
为什么jdk1.7后又可以用string类型作为switch参数呢?
其实,jdk1.7并没有新的指令来处理switch string,而是通过调用switch中string.hashCode(),将string转换为int从而进行判断。
3. equals与==的区别
- 基本数据类型byte,short,char,int,long,float,double,boolean
他们之间的比较,应用==,比较的是他们的值。- 复合数据类型(类)
当他们用==进行比较的时候,比较的是他们在内存中的存放地址,所以除非是同一个new出来的对象,他们的比较后的结果为true,否则比较后结果为false。
- Java中默认的equals方法实现如下:
public boolean equals(Object obj) {
return (this == obj);
}
- 而String类则覆写了这个方法,直观的讲就是比较字符是不是都相同
public boolean equals(Object anObject) {
if (this == anObject) {
return true;
}
if (anObject instanceof String) {
String anotherString = (String)anObject;
int n = count;
if (n == anotherString.count) {
char v1[] = value;
char v2[] = anotherString.value;
int i = offset;
int j = anotherString.offset;
while (n-- != 0) {
if (v1[i++] != v2[j++])
return false;
}
return true;
}
}
return false;
}
4. Object有哪些公用方法
- clone方法
实现对象的浅拷贝(敲黑板💯),只有实现了Cloneable接口才可以调用该方法,否则抛出CloneNotSupportedException异常。
在java语言中,有几种方式可以创建对象呢(敲黑板💯)
使用new操作符创建一个对象 | 使用clone方法复制一个对象 |
---|---|
看new操作符后面的类型知道分配多大的内存空间 | 分配和源对象的相同大小的内存空间 |
再调用构造函数,填充对象的各个域,这一步叫做对象的初始化 | 然后再使用原对象中对应的各个域,填充新对象的域 |
构造方法返回后,一个对象创建完毕,可以引用(地址)发布到外部 | clone方法返回,一个新的相同的对象被创建,同样可以把这个新对象的引用发布到外部 |
复制引用 or 复制对象
//复制引用
public static void main(String[] args) {
Person p = new Person(23, "zhang");
Person p1 = p;
System.out.println(p);
System.out.println(p1);
}
public static class Person {
private int age ;
private String name;
public Person(int age, String name) {
this.age = age;
this.name = name;
}
}
输出结果:
java_study.CloneTest$Person@60e53b93
java_study.CloneTest$Person@60e53b93
//复制对象
public static void main(String[] args) {
Person p = new Person(23, "zhang");
Person p1 = null;
try {
p1 = (Person) p.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
System.out.println(p);
System.out.println(p1);
}
public static class Person implements Cloneable {
private int age ;
private String name;
public Person(int age, String name) {
this.age = age;
this.name = name;
}
public int getAge() {
return age;
}
public String getName() {
return name;
}
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
输出结果:
java_study.CloneTest$Person@60e53b93
java_study.CloneTest$Person@5e2de80c
由于age是基本数据类型, 那么对它的拷贝没有什么疑议,直接将一个4字节的整数值拷贝过来就行。但是name是String类型的, 它只是一个引用, 指向一个真正的String对象,那么对它的拷贝有两种方式
//验证原生clone是浅拷贝还是深拷贝
String result = p.getName() == p1.getName() ? "clone是浅拷贝的" : "clone是深拷贝的";
System.out.println(result);
输出结果:
clone是浅拷贝的
//覆盖Object中的clone方法, 实现深拷贝
public static void main(String[] args) throws CloneNotSupportedException {
Body body = new Body(new Head());
Body body1 = (Body) body.clone();
System.out.println("body == body1 : " + (body == body1));
System.out.println("body.head == body1.head : " + (body.head == body1.head));
System.out.println("body.head.face == body1.head.face : " + (body.head.face == body1.head.face));
}
static class Body implements Cloneable {
public Head head;
public Body() {
}
public Body(Head head) {
this.head = head;
}
@Override
protected Object clone() throws CloneNotSupportedException {
Body newBody = (Body) super.clone();
newBody.head = (Head) head.clone();
return newBody;
}
}
static class Head implements Cloneable {
public Face face;
public Head() {
}
public Head(Face face) {
this.face = face;
}
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
static class Face {
}
输出结果:
body == body1 : false
body.head == body1.head : false
body.head.face == body1.head.face : true
如果想要深拷贝一个对象, 这个对象必须要实现Cloneable接口,实现clone方法,并且在clone方法内部,把该对象引用的其他对象也要clone一份 , 这就要求这个被引用的对象必须也要实现Cloneable接口并且实现clone方法。
(PS:clone的前提是对象new过,
例如Body body = new Body(new Head());
后来改造Face时忘记把new Head()里加上new Face(),一直报空指针😶)
- getClass方法
final方法,获得运行时对象类型。
public static void main(String[] args) {
A a = new A();
B b = new B();
A ab = new B();
System.out.println(a.getClass() + " " + A.class);
System.out.println(b.getClass() + " " + B.class);
System.out.println(ab.getClass());
ab = a;
System.out.println(ab.getClass());
}
static class A {
}
static class B extends A {
}
输出结果:
class A class A
class B class B
class B
class A
- toString方法
该方法用得比较多,一般子类都有覆盖。
打印对象时默认调用toString方法
public static void main(String[] args) {
Person p = new Person(23, "zhang");
System.out.println(p);
}
public static class Person {
private int age ;
private String name;
public Person(int age, String name) {
this.age = age;
this.name = name;
}
@Override
public String toString() {
return name;
}
}
输出结果:
zhang
- finalize方法
垃圾回收器要回收对象的时候,首先要调用这个类的finalize方法,一般的纯Java编写的Class不需要重新覆盖这个方法。
- equals方法
该方法是非常重要的一个方法。一般equals和==是不一样的,但是在Object中两者是一样的。子类一般都要重写这个方法。
- hashCode方法
该方法用于哈希查找,可以减少在查找中使用equals的次数,重写了equals方法一般都要重写hashCode方法。
public class HashTest {
private int i;
public int getI() {
return i;
}
public void setI(int i) {
this.i = i;
}
public int hashCode() {
return i%10;
}
public boolean equals(Object object) {
if (object == null || !(object instanceof HashTest))
return false;
HashTest other = (HashTest) object;
if (object == this || other.getI() == this.getI())
return true;
return false;
}
public static void main(String[] args) {
HashTest a = new HashTest();
HashTest b = new HashTest();
a.setI(1);
b.setI(1);
Set<HashTest> set = new HashSet<>();
set.add(a);
set.add(b);
System.out.println(a.hashCode() == b.hashCode());
System.out.println(a.equals(b));
System.out.println(set);
}
}
输出结果:
true
true
[java_study.HashTest@1]
Set的集合里不允许对象有重复的值,List允许有重复,它对集合中的对象进行索引,Queue的工作原理是FCFS算法(First Come, First Serve)
如果不重写hashCode(),在HashSet中添加两个equals的对象(hashCode有可能会相同,所以最后还是要用equals()比较),会将两个对象都加入进去,这样HashSet就失去了他本身的意义了。
- wait方法
wait方法就是使当前线程等待该对象的锁,当前线程必须是该对象的拥有者,也就是具有该对象的锁。wait()方法一直等待,直到获得锁或者被中断。wait(long timeout)设定一个超时间隔,如果在规定时间内没有获得锁就返回。
调用该方法后当前线程进入睡眠状态,直到以下事件发生。
(1)其他线程调用了该对象的notify方法。
(2)其他线程调用了该对象的notifyAll方法。
(3)其他线程调用了interrupt中断该线程。
(4)时间间隔到了。
此时该线程就可以被调度了,如果是被中断的话就抛出一个InterruptedException异常。- notify方法
该方法唤醒在该对象上等待的某个线程。- notifyAll方法
该方法唤醒在该对象上等待的所有线程。
5. Java的四种引用,强弱软虚,用到的场景
(1) 强引用(StrongReference)
强引用是我们在编程过程中使用的最简单的引用,如代码String s=”abc”中变量s就是字符串对象”abc”的一个强引用。任何被强引用指向的对象都不能被垃圾回收器回收,这些对象都是在程序中需要的。
(2) 软引用(SoftReference)
软引用是用来描述一些有用但并不是必需的对象。对于软引用关联着的对象,只有在内存不足的时候JVM才会回收该对象。因此,这一点可以很好地用来解决OOM的问题,并且这个特性很适合用来实现缓存:比如网页缓存、图片缓存等。
软引用可以和一个引用队列(ReferenceQueue)联合使用,如果软引用所引用的对象被JVM回收,这个软引用就会被加入到与之关联的引用队列中。
(3) 弱引用(WeakReference)
弱引用也是用来描述非必需对象的,当JVM进行垃圾回收时,无论内存是否充足,都会回收被弱引用关联的对象。
(4) 虚引用(PhantomReference)
虚引用和前面的软引用、弱引用不同,它并不影响对象的生命周期。如果一个对象与虚引用关联,则跟没有引用与之关联一样,在任何时候都可能被垃圾回收器回收。
要注意的是,虚引用必须和引用队列关联使用,当垃圾回收器准备回收一个对象时,如果发现它还有虚引用,就会把这个虚引用加入到与之关联的引用队列中。程序可以通过判断引用队列中是否已经加入了虚引用,来了解被引用的对象是否将要被垃圾回收。如果程序发现某个虚引用已经被加入到引用队列,那么就可以在所引用的对象的内存被回收之前采取必要的行动。
public class ReferenceTest {
public static void main(String[] args) {
// 软引用,没有队列,可以有
String hello = new String("hello");//强引用
SoftReference<String> sr = new SoftReference<>(hello);
hello = null;
System.out.println(sr.get());
System.out.println();
// 弱引用,没有队列,可以有
String hello1 = new String("hello1");
WeakReference<String> sr1 = new WeakReference<>(hello1);
hello1 = null;
System.out.println(sr1.get());
System.gc();
System.out.println(sr1.get());
System.out.println();
// 虚引用,有队列
ReferenceQueue<String> queue = new ReferenceQueue<>();
String hello2 = new String("hello2");
PhantomReference<String> pr = new PhantomReference<>(hello2, queue);
hello2 = null;
System.out.println(pr.get());
System.out.println();
}
}
输出结果:
hello
hello1
null
null
6. Hashcode的作用
见4.Object有哪些公用方法
7. ArrayList、LinkedList、Vector的区别
ArrayList | LinkedList | Vector |
---|---|---|
一个可改变大小的数组 | 一个双链表 | ArrayList类似,但属于强同步类 |
大小将会动态地增长.内部的元素可以直接通过get与set方法进行访问 | 在添加和删除元素时具有比ArrayList更好的性能 | 程序本身是线程安全的(thread-safe,没有在多个线程之间共享同一个集合/对象),那么使用ArrayList是更好的选择 |
每次对size增长50% | 还实现了 Queue 接口 | 每次请求其大小的双倍空间 |
8. String、StringBuffer与StringBuilder的区别
String | StringBuffer | StringBuilder |
---|---|---|
不可变类,任何对String的改变都会生成新的对象 | 可变类,支持并发操作,适合多线程中使用 | 可变类,不支持并发操作,线程不安全的,单线程中的性能比StringBuffer高 |
经常改变内容的字符串最好不要用String,因为每次生成对象都会对系统性能产生影响,而如果是使用 StringBuffer 类则结果就不一样了,每次结果都会对 StringBuffer 对象本身进行操作。而在某些特别情况下,String 效率是远要比 StringBuffer 快的:
String S1 = "This is only a " + "simple " + "test";
StringBuffer Sb = new StringBuilder( "This is only a ").append( "simple ").append( "test");
你会很惊讶的发现,生成 String S1 对象的速度简直太快了,而这个时候 StringBuffer 居然速度上根本一点都不占优势。其实这是 JVM 的一个把戏,在 JVM 眼里,这个
String S1 = "This is only a " + "simple " + "test";其实就是:
String S1 = "This is only a simple test"; 所以当然不需要太多的时间了。但大家这里要注意的是,如果你的字符串是来自另外的 String对象的话,速度就没那么快了,譬如:
String S2 = "This is only a ";
String S3 = "simple " ;
String S4 = "test";
String S1 = S2 +S3 + S4;
这时候 JVM 会规规矩矩的按照原来的方式去做
9. Map、Set、List、Queue、Stack的特点与用法
图其实画的不准确了,LinkedHashSet应该是继承自HashSet
LinkedList也只是实现了Queue,并不是继承哦😯
Collection-------Iterable是该的父接口,iterator()方法用于遍历操作
├ List-----------List可以通过下标 (1,2..) 来取得值,值可以重复
│├ LinkedList---线程不安全的,双向链表实现
│├ ArrayList----线程不安全的,数组实现
│└ Vector-------线程安全的,数组实现
│ └ Stack----一个后进先出的栈,push、pop、peak、empty、search
├ Set-----------Set只能通过Iterable(迭代器)来取值,并且值不能重复
│├ HashSet-----使用hash来存储元素,因此具有良好的存取和查找性能
││ └ LinkedHashSet--使用链表维护元素的次序,遍历时按添加顺序来访问
│└ TreeSet------是SortedSet接口的实现类,可以确保元素处于排序状态
└ Queue--------Queue保持一个队列(先进先出)的顺序,offer、poll、peek
└ PriorityQueue--按照队列元素的大小进行重新排序
Map------不关心元素添加的顺序,采用了hash,因此查找元素比ArrayList快
├ Hashtable---------线程安全的,key和value不能为null
├ HashMap----------线程不安全的,key和value可以为null
│├ LinkedHashMap--双向链表来维护key-value对的次序,与插入顺序一致
│└ WeakHashMap
├ TreeMap---红黑树数据结构,每个key-value对即作为红黑树的一个节点
└ IdentifyHashMap--当且仅当key1 == key2时,才认为两个key相等
10. HashMap和HashTable的区别
主要区别在多个线程访问Hashtable时,不需要自己为它的方法实现同步,
而HashMap 就必须为之提供外同步(Collections.synchronizedMap)。
11. HashMap和ConcurrentHashMap的区别,HashMap的底层源码
Hashmap本质是数组加链表。根据key取得hash值,然后计算出数组下标,如果多个key对应到同一个下标,就用链表串起来,新插入的在前面。
ConcurrentHashMap:在hashMap的基础上,ConcurrentHashMap将数据分为多个segment,默认16个(concurrency level),然后每次操作对一个segment加锁,避免多线程锁的几率,提高并发效率。
后面的太多了💔提取不了了http://blog.csdn.net/stephenxe/article/details/52386786
12. TreeMap、HashMap、LindedHashMap的区别
见9. Map、Set、List、Queue、Stack的特点与用法
13. Collection包结构,与Collections的区别
Collections是一个包装类(java.util.Collections),此类不能实例化,就像一个工具类,用于对集合中元素进行排序、搜索以及线程安全等各种操作
Collection接口的意义是为各种具体的集合提供了最大化的统一操作方式
14. try catch finally,try里有return,finally还执行么
public static void main(String[] args) {
fun();
}
static void fun() {
try {
System.out.println("代码段");
return;
} catch (Exception e) {
}finally {
System.out.println("finally执行");
}
}
输出结果:
代码段
finally执行
(1) 在try中没有异常的情况下try、catch、finally的执行顺序 try — finally
(2) 如果try中有异常,执行顺序是try — catch — finally
(3) 如果try中没有异常并且try中有return这时候正常执行顺序是try —- finally — return
(4) 如果try中有异常并且try中有return这时候正常执行顺序是try—-catch—finally— return
(5) 总之 finally 永远执行!
15. Excption与Error包结构。OOM你遇到过哪些情况,SOF你遇到过哪些情况
(1) Exception与Error都继承了Throwable
(2) Error类体系描述了Java运行系统中的内部错误,一般都是由JVM抛出,一般我们都不关注
(3) Exception类体系,如RuntimeException和IOException等继承与它,一般都是由于程序本身的因数或是外部环境因数造成,这是我们需要关注尽量解决的异常
JVM中常见的OutOfMemory和StackOverFlow产生的机理
首先必须了解JVM运行时数据区域
方法区
用于存储已被JVM加载的类信息,常量,静态变量,即时编译器编译后的代码,线程共享。
运行时常量池
方法区一部分。存放编译期生成的各种字面量和符号引用。
虚拟机栈
内部创建栈帧,来存放局部变量表,操作数栈,动态链接,方法出口等,线程私有。
本地方法栈(HotSpot不区分虚拟机栈和本地方法栈)
类似虚拟机栈,但是只为Native方法服务。
堆
存放实例对象和数组,线程共享。
程序计数器
存放当前线程执行的字节码的行号。
//堆OutOfMemory
public class HeapOOM {
static class OOMObject {
}
public static void main(String[] args) {
List<OOMObject> list = new ArrayList<OOMObject>();
while (true) {
list.add(new OOMObject());
}
}
}
//栈OutOfMemory
public class JavaVMStackOOM {
private void dontStop() {
while (true) {
}
}
public void stackLeakByThread() {
while (true) {
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
dontStop();
}
});
thread.start();
}
}
public static void main(String[] args) {
JavaVMStackOOM oom = new JavaVMStackOOM();
oom.stackLeakByThread();
}
}
//StackOverFlow
public class JavaVMStackSOF {
private int stackLength = 1;
public void stackLeak() {
stackLength++;
stackLeak();
}
public static void main(String[] args) throws Throwable {
JavaVMStackSOF oom = new JavaVMStackSOF();
try {
oom.stackLeak();
} catch(Throwable e) {
System.out.println("stack length:" + oom.stackLength);
e.printStackTrace();
}
}
}
16. Java面向对象的三个特征与含义
封装:将可以描述这一类事物的属性和行为归纳到一个类中,以方便使用,提高了代码的利用和效率,降低了代码的重复。
继承:封装的属性和行为没有包含到具体某一事物,这时我们就需要继承与封装类添加这一具体事物所独有的特征。
多态:多态是以封装和继承为基础的,多态是站在一个抽象的层面去实施一个统一的行为,具体到个体时这个统一的行为会施行自己的特征行为。
17. Override和Overload的含义去区别
Override(重写、覆盖)
方法名、参数、返回值相同
存在于子父类之间
定义成final不能被覆写
子类方法不能缩小父类的访问权限
子类不能抛出比父类更多的异常
Overload(重载,过载)
参数类型、个数、顺序至少一个不同
返回值不同是不行的
存在于子类、父类、同类
区别
Override是子类与父类之间的多态表现,Overload是一个类中的多态
18. Interface与abstract类的区别
Interface | abstract |
---|---|
所有方法都是公开、抽象方法,所有属性都是公开、静态、常量 | 如果一个类如果要添加抽象方法就必须将类设置为abstract |
类只能是实现接口,并且可以实现多个接口,类必须实现接口的方法,否则为抽象类 | abstract类必须被继承使用,不能生成对象(所以Final和abstract永远不会同时出现) |
接口可不写public,但子类中实现接口的过程中public不可省 | abstract的方法不可以private修饰 |
接口和接口之间可以允许多继承 | 其子类必须覆盖父类的抽象方法 |
接口是实现了不同层次、不同体系对象的共同属性 | abstract和static不能在方法中放在一起(测了下类是可以放在一起的) |
19. Static class 与not static class的区别
static class | not static class |
---|---|
可以脱离外部类被创建 | 必须先new外部类,再new内部类 |
只能访问外部类的静态成员 | 可以访问外部类的数据和方法,因为他就在外部类里面 |
20. java多态的实现原理
靠的是父类或接口定义的引用变量可以指向子类或具体实现类的实例对象,而程序调用的方法在运行期才动态绑定(敲黑板💯),就是引用变量所指向的具体实例对象的方法,也就是内存里正在运行的那个对象的方法,而不是引用变量的类型中定义的方法
21. 实现多线程的两种方法:Thread与Runable
public class ThreadRunableTest {
public static void main(String[] args) {
MyThreadRunnable mr = new MyThreadRunnable();
new Thread(mr).start();
new Thread(mr).start();
new MyThread().start();
new MyThread().start();
}
static class MyThreadRunnable implements Runnable {
@Override
public void run() {
System.out.println("hello");
}
}
static class MyThread extends Thread {
@Override
public void run() {
System.out.println("world");
}
}
}
输出结果:
hello
hello
world
world
22. 线程同步的方法:sychronized、lock、reentrantLock等
如果你向一个变量写值,而这个变量接下来可能会被另一个线程所读取,或者你从一个变量读值,而它的值可能是前面由另一个线程写入的,此时你就必须使用同步
synchronized会在进入同步块的前后分别形成monitorenter和monitorexit字节码指令.在执行monitorenter指令时会尝试获取对象的锁,如果此没对象没有被锁,或者此对象已经被当前线程锁住,那么锁的计数器加一,每当monitorexit被锁的对象的计数器减一.直到为0就释放该对象的锁.由此synchronized是可重入的,不会出现自己把自己锁死.
synchronized关键字可以作为函数的修饰符,也可作为函数内的语句,也就是平时说的同步方法和同步语句块。如果再细的分类,synchronized可作用于instance变量、object reference(对象引用)、static函数和class literals(类名称字面常量)身上。
在进一步阐述之前,我们需要明确几点:
A.无论synchronized关键字加在方法上还是对象上,它取得的锁都是对象(敲黑板💯),而不是把一段代码或函数当作锁――而且同步方法很可能还会被其他线程的对象访问。
B.每个对象只有一个锁(lock)与之相关联。
C.实现同步是要很大的系统开销作为代价的,甚至可能造成死锁,所以尽量避免无谓的同步控制。
一、当两个并发线程访问同一个对象object中的这个synchronized(this)同步代码块时,一个时间内只能有一个线程得到执行。另一个线程必须等待当前线程执行完这个代码块以后才能执行该代码块。
public class Thread1 implements Runnable {
public void run() {
synchronized(this) {
for (int i = 0; i < 5; i++) {
System.out.println(Thread.currentThread().getName() + " synchronized loop " + i);
}
}
}
public static void main(String[] args) {
Thread1 t1 = new Thread1();
Thread ta = new Thread(t1, "A");
Thread tb = new Thread(t1, "B");
ta.start();
tb.start();
}
}
输出结果:
A synchronized loop 0
A synchronized loop 1
A synchronized loop 2
A synchronized loop 3
A synchronized loop 4
B synchronized loop 0
B synchronized loop 1
B synchronized loop 2
B synchronized loop 3
B synchronized loop 4
二、然而,当一个线程访问object的一个synchronized(this)同步代码块时,另一个线程仍然可以访问该object中的非synchronized(this)同步代码块。
public class Thread2 {
public void m4t1() {
synchronized (this) {
int i = 5;
while (i-- > 0) {
System.out.println(Thread.currentThread().getName() + " : " + i);
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public void m4t2() {
int i = 5;
while (i-- > 0) {
System.out.println(Thread.currentThread().getName() + " : " + i);
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
final Thread2 myt2 = new Thread2();
Thread t1 = new Thread(new Runnable() {
public void run() {
myt2.m4t1();
}
}, "t1");
Thread t2 = new Thread(new Runnable() {
public void run() {
myt2.m4t2();
}
}, "t2");
t1.start();
t2.start();
}
}
输出结果:
t2 : 4
t1 : 4
t1 : 3
t2 : 3
t1 : 2
t2 : 2
t1 : 1
t2 : 1
t1 : 0
t2 : 0
三、尤其关键的是,当一个线程访问object的一个synchronized(this)同步代码块时,其他线程对object中所有其它synchronized(this)同步代码块的访问将被阻塞。
//修改Thread2.m4t2()方法:
public void m4t2() {
synchronized (this) {
int i = 5;
while (i-- > 0) {
System.out.println(Thread.currentThread().getName() + " : " + i);
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
输出结果:
t1 : 4
t1 : 3
t1 : 2
t1 : 1
t1 : 0
t2 : 4
t2 : 3
t2 : 2
t2 : 1
t2 : 0
四、第三个例子同样适用其它同步代码块。也就是说,当一个线程访问object的一个synchronized(this)同步代码块时,它就获得了这个object的对象锁。结果,其它线程对该object对象所有同步代码部分的访问都被暂时阻塞。
//修改Thread2.m4t2()方法如下:
public synchronized void m4t2() {
int i = 5;
while( i-- > 0) {
System.out.println(Thread.currentThread().getName() + " : " + i);
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
输出结果:
t1 : 4
t1 : 3
t1 : 2
t1 : 1
t1 : 0
t2 : 4
t2 : 3
t2 : 2
t2 : 1
t2 : 0
具体见http://www.cnblogs.com/GnagWang/archive/2011/02/27/1966606.html😍
Lock是一个接口,reentrantLock是Lock接口的一个实现类
Lock lock = new ReentrantLock();
public void fun() {
lock.lock();//得到锁
try {
/ /同步代码段
} finally{
lock.unlock();//释放锁
}
}
具体见http://blog.csdn.net/kai_wei_zhang/article/details/8196130
在并发量比较小的情况下,使用synchronized是个不错的选择,但是在并发量比较高的情况下,其性能下降很严重,此时ReentrantLock是个不错的方案。
23. 锁的等级:方法锁、对象锁、类锁
对象锁是用来控制实例方法之间的同步,类锁是用来控制静态方法(或静态变量互斥体)之间的同步
对于类锁,则会把整个类锁住,也就说只能有一个对象拥有当前类的锁。当一个对象拥有了类锁之后,另外一个对象还想竞争锁的话则会被阻塞。两个对象A,B,如果A正在访问一个被类锁修饰的方法function,那么B则不能访问。因为类锁只能在同一时刻被一个对象拥有。相对于对象锁,则是不同。还是A,B两个对象,如果A正在访问对象锁修饰的function,那么这个时候B也可以同时访问。
对于对象锁,当一个对象拥有锁之后,访问一个加了对象锁的方法,而该方法中又调用了该类中其他加了对象锁的方法,那么这个时候是不会阻塞住的。这是java通过可重入锁机制(敲黑板💯)实现的。可重入锁指的是当一个对象拥有对象锁之后,可以重复获取该锁。因为synchronized块是可重入的,所以当你访问一个对象锁的方法的时候,在该方法中继续访问其他对象锁方法是不会被阻塞的。
// 对象锁:形式1(方法锁)
public synchronized void Method1() {
System.out.println(“我是对象锁也是方法锁”);
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 对象锁:形式2(代码块形式)
public void Method2() {
synchronized (this) {
System.out.println("我是对象锁");
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
// 类锁:形式1
public static synchronized void Method1() {
System.out.println("我是类锁一号");
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 类锁:形式2
public void Method2() {
synchronized (Test.class) {
System.out.println("我是类锁二号");
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
24. 写出生产者消费者模式
public class ProduceConsumerTest {
public static void main(String[] args) {
MyService myService = new MyService();
ProduceThread[] pt = new ProduceThread[2];
ConsumeThread[] ct = new ConsumeThread[2];
for (int i = 0; i < 2; i++) {
pt[i] = new ProduceThread(myService);
pt[i].setName(String.valueOf(i + 1));
ct[i] = new ConsumeThread(myService);
ct[i].setName(String.valueOf(i + 1));
pt[i].start();
ct[i].start();
}
}
static class MyService {
ArrayList<Integer> list = new ArrayList<>(); //用list存放生产之后的数据,最大容量为1
synchronized void produce() {
try {
while (!list.isEmpty()) { //只有list为空时才会去进行生产操作
System.out.println("生产者" + Thread.currentThread().getName() + " waiting");
this.wait();
}
int value = 9999;
list.add(value);
System.out.println("生产者" + Thread.currentThread().getName() + " Runnable");
this.notifyAll(); //然后去唤醒因object调用wait方法处于阻塞状态的线程
} catch (InterruptedException e) {
e.printStackTrace();
}
}
synchronized void consumer() {
try {
while (list.isEmpty()) { //只有list不为空时才会去进行消费操作
System.out.println("消费者" + Thread.currentThread().getName() + " waiting");
this.wait();
}
list.clear();
System.out.println("消费者" + Thread.currentThread().getName() + " Runnable");
this.notifyAll(); //然后去唤醒因object调用wait方法处于阻塞状态的线程
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
static class ProduceThread extends Thread {
private MyService p;
ProduceThread(MyService p) {
this.p = p;
}
@Override
public void run() {
while (true) {
p.produce();
}
}
}
static class ConsumeThread extends Thread {
private MyService c;
ConsumeThread(MyService c) {
this.c = c;
}
@Override
public void run() {
while (true) {
c.consumer();
}
}
}
}
输出结果:
生产者1 Runnable
生产者1 waiting
消费者2 Runnable
消费者2 waiting
生产者2 Runnable
生产者2 waiting
消费者1 Runnable
消费者1 waiting
...
static class MyService {
private ReentrantLock lock = new ReentrantLock();
private Condition condition = lock.newCondition();
private boolean hasValue = false;
void produce() {
lock.lock();
try {
while (hasValue == true) {
System.out.println("生产者" + Thread.currentThread().getName() + " waiting");
condition.await();
}
hasValue = true;
System.out.println("生产者" + Thread.currentThread().getName() + " Runnable");
condition.signalAll();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
void consumer() {
lock.lock();
try {
while (hasValue == false) {
System.out.println("消费者" + Thread.currentThread().getName() + " waiting");
condition.await();
}
hasValue = false;
System.out.println("消费者" + Thread.currentThread().getName() + " Runnable");
condition.signalAll();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
输出结果:
生产者1 Runnable
生产者1 waiting
消费者1 Runnable
消费者1 waiting
生产者2 Runnable
生产者2 waiting
消费者2 Runnable
消费者2 waiting
25. ThreadLocal的设计理念与作用
他并不是一个Thread,而是thread local variable(线程局部变量)
虽然所有的线程都能访问到这个ThreadLocal实例,但是每个线程却只能访问到自己通过调用ThreadLocal的set()方法设置的值。即使是两个不同的线程在同一个ThreadLocal对象上设置了不同的值,他们仍然无法访问到对方的值。
public class ThreadLocalTest {
public static void main(String[] args) {
MyThreadRunnable mr = new MyThreadRunnable();
Thread thread1 = new Thread(mr);
Thread thread2 = new Thread(mr);
thread1.start();
thread2.start();
}
static class MyThreadRunnable implements Runnable {
private ThreadLocal threadLocal = new ThreadLocal();
@Override
public void run() {
threadLocal.set(new Random().nextInt(30));
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(threadLocal.get());
}
}
}
26. ThreadPool用法与优势
线程池可以应对突然大爆发量的访问,通过有限个固定线程为大量的操作服务,减少创建和销毁线程所需的时间。
public class DifferentKindsThreadPool {
public static void main(String[] args) {
//displayScheduledThreadPool();
//displaySingleThreadPool();
//displayCachedThreadPool();
displayThreadPool();
}
/**
* 创建一个定时任务的线程池
*/
public static void displayScheduledThreadPool() {
ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(4);
//它可以向固定线程池一样执行任务
distributeTaskForThreadPool(scheduledThreadPool);
//这是它的特殊之处,可以定时任务
scheduledThreadPool.schedule(
new Runnable() {
@Override
public void run() {
System.out.println("开始执行任务1");
}
},
5,
TimeUnit.SECONDS);
//每隔2秒再次重新执行任务
scheduledThreadPool.scheduleAtFixedRate(
new Runnable() {
@Override
public void run() {
System.out.println("开始执行任务2");
}
},
5,
2,
TimeUnit.SECONDS);
}
/**
* 创建只有一个线程的线程池,如果线程终止,
* 他将会创建一个新的线程加入到池子中,这
* 个线程池会保证池子中始终有一个线程
*/
public static void displaySingleThreadPool() {
ExecutorService singleThreadPool = Executors.newSingleThreadExecutor();
distributeTaskForThreadPool(singleThreadPool);
}
/**
* 创建一个可根据需要创建线程的线程池,但是
* 当先前创建的线程可得到时就会重用先前的线
* 程,如果不存在可得到的线程,一个新的线程
* 将被创建并被加入到池子中。60秒没有被用到
* 的线程将被终止并从缓存中移除
*/
public static void displayCachedThreadPool() {
ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
distributeTaskForThreadPool(cachedThreadPool);
}
/**
* 创建一个带有固定线程的线程池
*/
public static void displayThreadPool() {
// 创建一个带有4个固定线程的线程池
ExecutorService threadPool = Executors.newFixedThreadPool(4);
distributeTaskForThreadPool(threadPool);
}
/**
* 为线程池分配8个任务,使其驱动
*/
public static void distributeTaskForThreadPool(ExecutorService threadPool) {
// 让线程池驱动8个任务
for (int i = 1; i <= 8; i++) {
// 由于内部类里面不能放一个非final的变量,所以我把i的值赋予task
final int task = i;
threadPool.execute(new Runnable() {
@Override
public void run() {
System.out.println("我是" + Thread.currentThread().getName()
+ "," + "拿到了第" + task + "个任务,我开始执行了");
}
});
}
}
}
public class BankTest {
public static void main(String[] args) {
BankCount bankCount = new BankCount();
ExecutorService executor = Executors.newFixedThreadPool(10);
executor.execute(new Runnable() { //存钱线程
@Override
public void run() {
int i = 5;
while (i-- > 0) {
bankCount.addMoney(200);
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
});
Future future = executor.submit(new Runnable() { //取钱线程
@Override
public void run() {
int i = 5;
while (i-- > 0) {
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
bankCount.getMoney(200);
}
}
});
try {
Object res = future.get();
System.out.println(res);
} catch (Exception e) {
e.printStackTrace();
} finally {
executor.shutdown(); // 关闭线程池
}
}
static class BankCount {
public synchronized void addMoney(int money) {
System.out.println(Thread.currentThread().getName() + ">存入:" + money);
}
public synchronized void getMoney(int money) {
System.out.println(Thread.currentThread().getName() + ">取钱:" + money);
}
}
}
27. Concurrent包里的其他东西:ArrayBlockingQueue、CountDownLatch等等
...
28. wait()和sleep()的区别
(1) 这两个方法来自不同的类分别是Thread和Object
(2) 最主要是sleep方法没有释放锁(OS认为该线程正在工作,不会让出系统资源),而wait方法释放了锁,使得其他线程可以使用同步控制块或者方法
(3) wait、notify和notifyAll只能在同步控制方法或者同步控制块里面使用,而sleep可以在任何地方使用
4,sleep必须捕获异常,而wait、notify和notifyAll不需要捕获异常
29. foreach与正常for循环效率对比
使用foreach的对象必须实现Iterator接口,对Iterator进行了更多操作,效率相比for更低。但是也有例外:
public class ForAndForeachTest {
public static void main(String[] args) {
//实例化arrayList
List<Integer> arrayList = new ArrayList<Integer>();
//实例化linkList
List<Integer> linkList = new LinkedList<Integer>();
//插入10万条数据
for (int i = 0; i < 100000; i++) {
arrayList.add(i);
linkList.add(i);
}
int array = 0;
//用for循环arrayList
long arrayForStartTime = System.currentTimeMillis();
for (int i = 0; i < arrayList.size(); i++) {
array = arrayList.get(i);
}
long arrayForEndTime = System.currentTimeMillis();
System.out.println("用for循环arrayList 10万次花费时间:" + (arrayForEndTime - arrayForStartTime) + "毫秒");
//用foreach循环arrayList
long arrayForeachStartTime = System.currentTimeMillis();
for (Integer in : arrayList) {
array = in;
}
long arrayForeachEndTime = System.currentTimeMillis();
System.out.println("用foreach循环arrayList 10万次花费时间:" + (arrayForeachEndTime - arrayForeachStartTime) + "毫秒");
//用for循环linkList
long linkForStartTime = System.currentTimeMillis();
int link = 0;
for (int i = 0; i < linkList.size(); i++) {
link = linkList.get(i);
}
long linkForEndTime = System.currentTimeMillis();
System.out.println("用for循环linkList 10万次花费时间:" + (linkForEndTime - linkForStartTime) + "毫秒");
//用froeach循环linkList
long linkForeachStartTime = System.currentTimeMillis();
for (Integer in : linkList) {
link = in;
}
long linkForeachEndTime = System.currentTimeMillis();
System.out.println("用foreach循环linkList 10万次花费时间:" + (linkForeachEndTime - linkForeachStartTime) + "毫秒");
}
}
输出测试:
用for循环arrayList 10万次花费时间:3毫秒
用foreach循环arrayList 10万次花费时间:3毫秒
用for循环linkList 10万次花费时间:7037毫秒
用foreach循环linkList 10万次花费时间:3毫秒
30. Java IO与NIO
IO | NIO |
---|---|
面向流 | 面向缓冲 |
阻塞IO | 非阻塞IO |
无 | 选择器 |
具体见http://ifeve.com/java-nio-vs-io/
31. 反射的作用与原理
在程序运行期间获得类里面的信息
反射的常用方法:
(1) forName(String className)
返回与带有给定字符串名的类或接口相关联的 Class 对象
(2) forName(String name, boolean initialize, ClassLoader loader)
使用给定的类加载器,返回与带有给定字符串名的类或接口相关联的 Class 对象
(3) getAnnotation(Class<A> annotationClass)
如果存在该元素的指定类型的注释,则返回这些注释,否则返回 null
(4) getAnnotations()
返回此元素上存在的所有注释
(5) getConstructor(Class<?>... parameterTypes)
返回一个 Constructor 对象,它反映此 Class 对象所表示的类的指定公共构造方法
(6) getDeclaredField(String name)
返回一个 Field 对象,该对象反映此 Class 对象所表示的类或接口的指定已声明字段
(7) getDeclaredMethod(String name, Class<?>... parameterTypes)
返回一个 Method 对象,该对象反映此 Class 对象所表示的类或接口的指定已声明方法
public class ImitateTest {
public static void main(String[] args) {
//1. 首先我们创建一个bean,模拟从前端获取的数据
ImitateTest t = new ImitateTest();
Bean bean = t.getBean();
//2.生成我们需要的SQL 并设值
t.save(bean);
}
private Bean getBean() {
//模拟用反射实现
Bean bean = null;
try {
Class c = Class.forName("java_study.Bean");
bean = (Bean) c.newInstance();
// 私有字段无法访问,我们通过方法赋值
Method m1 = c.getDeclaredMethod("setId", Integer.class);
Method m2 = c.getDeclaredMethod("setName", String.class);
Method m3 = c.getDeclaredMethod("setPassword", String.class);
m1.invoke(bean, 1);
m2.invoke(bean, "admin");
m3.invoke(bean, "123456");
} catch (Exception e) {
e.printStackTrace();
}
return bean;
}
//假设我们的表 就是 BEAN
private void save(Bean bean) {
Field[] fields = Bean.class.getDeclaredFields();
StringBuffer sb = new StringBuffer("INSERT INTO BEAN VALUES");
sb.append(getInsertStr(fields.length));
//这里我们可以看到SQL 已经生成
System.out.println(sb);
//这里就是我们的JDBC 根据字段名字赋值 的操作了。
//当然hibernate 写得肯定会复杂很多,但是基本原理不变
}
private String getInsertStr(int fields) {
StringBuffer sb = new StringBuffer("(");
for (int i = 0; i < fields; i++) {
sb.append("?,");
}
sb.delete(sb.length() - 1, sb.length());
sb.append(")");
return sb.toString();
}
}
32. 泛型常用特点,List<String>能否转为List<Object>
一个方法如果接收List<Object>作为形式参数,那么如果尝试将一个List<String>的对象作为实际参数传进去,却发现无法通过编译。虽然从直觉上来说,Object是String的父类,这种类型转换应该是合理的。但是实际上这会产生隐含的类型转换问题,因此编译器直接就禁止这样的行为。
Java中的泛型基本上都是在编译器这个层次来实现的。在生成的Java字节代码中是不包含泛型中的类型信息的。使用泛型的时候加上的类型参数,会被编译器在编译的时候去掉。这个过程就称为类型擦除(敲黑板💯)。如在代码中定义的List<Object>和List<String>等类型,在编译之后都会变成List。JVM看到的只是List,而由泛型附加的类型信息对JVM来说是不可见的。Java编译器会在编译时尽可能的发现可能出错的地方,但是仍然无法避免在运行时刻出现类型转换异常的情况。
//所声明的类型参数在Java类中可以像一般的类型一样作为方法的参数和返回值,或是作为域和局部变量的类型。
//但是由于类型擦除机制,类型参数并不能用来创建对象或是作为静态变量的类型。
class ClassTest<X, Y , Z extends Number> {
private X x;
private static Y y; //编译错误,不能用在静态变量中
public X getFirst() {
//正确用法
return x;
}
public void wrong() {
Z z = new Z(); //编译错误,不能创建对象
}
}
33. 解析XML的几种方式的原理与特点:DOM、SAX、PULL
SAX解析器的优点是解析速度快,占用内存少。
DOM在内存中以树形结构存放,因此检索和更新效率会更高。但是对于特别大的文档,解析和加载整个文档将会很耗资源。
PULL解析器的运行方式和SAX类似,都是基于事件的模式。PULL解析器小巧轻便,解析速度快,简单易用。
具体见http://blog.csdn.net/cangchen/article/details/44034799
34. Java与C++对比
具体见http://www.cnblogs.com/sunyoung/p/5975995.html
35. Java1.7与1.8新特性
具体见http://blog.csdn.net/ludx212/article/details/17281729
36. 设计模式:单例、工厂、适配器、责任链、观察者等等
37. JNI的使用
具体见http://blog.csdn.net/jiangwei0910410003/article/details/17465085