String是常量,其值是放在方法区内存的,但是如果是new出来的的话,它就会有对象在堆内存里的。
StringBuffer是线程安全版的String,但是其值是和对象一样在堆内存里的,而不是常量。
StringBuilder是线程不安全版的StringBuffer,和StringBuffer其值不是常量。
extends Number>表示这个泛型只能放Number的子类,放其它的在编译器里报错
super Number>表示这个泛型只能放Number的父类,放其它的编译器报错
但是请注意是编译器报错,不是编译时报错。
在这里要提一嘴的是就是java中的泛型是假泛型,假泛型是咋回事呢?其实在java编译阶段会将泛型中的类型转换为他们“最高的父类”。
编译器是经过编译检查之后再进行类型消除
jvm中采取了一种桥接方法来解决的。比如现在我的父类用泛型T定义类型
class Pair {
private T value;
public T getValue() {
return value;
}
public void setValue(T value) {
this.value = value;
}
}
现在有个类继承了Pair
class DateInter extends Pair {
@Override
public void setValue(Date value) {
super.setValue(value);
}
@Override
public Date getValue() {
return super.getValue();
}
}
那么对于子类来说,它的setValue到底是重写还是重载呢?
我们来试验一下。
public static void main(String[] args) throws ClassNotFoundException {
DateInter dateInter = new DateInter();
dateInter.setValue(new Date());
dateInter.setValue(new Object()); //编译错误
}
编译报错了,说明是重写,那么这就有矛盾了,前面不是说类型消除会将类型都替换成它们最高的父类吗?那么不应该是重写啊,返回类型都变了,为啥是重写呢?其实这是因为jvm采取了一种方法桥接的方法让我们以为我们重写的是我们看到的这些方法,其实本质并不是这样的,
首先,我们用javap -c className的方式反编译下DateInter子类的字节码,结果如下:
class com.tao.test.DateInter extends com.tao.test.Pair {
com.tao.test.DateInter();
Code:
0: aload_0
1: invokespecial #8 // Method com/tao/test/Pair."":()V
4: return
public void setValue(java.util.Date); //我们重写的setValue方法
Code:
0: aload_0
1: aload_1
2: invokespecial #16 // Method com/tao/test/Pair.setValue:(Ljava/lang/Object;)V
5: return
public java.util.Date getValue(); //我们重写的getValue方法
Code:
0: aload_0
1: invokespecial #23 // Method com/tao/test/Pair.getValue:()Ljava/lang/Object;
4: checkcast #26 // class java/util/Date
7: areturn
public java.lang.Object getValue(); //编译时由编译器生成的桥方法
Code:
0: aload_0
1: invokevirtual #28 // Method getValue:()Ljava/util/Date 去调用我们重写的getValue方法;
4: areturn
public void setValue(java.lang.Object); //编译时由编译器生成的桥方法
Code:
0: aload_0
1: aload_1
2: checkcast #26 // class java/util/Date
5: invokevirtual #30 // Method setValue:(Ljava/util/Date; 去调用我们重写的setValue方法)V
8: return
从编译的结果来看,我们本意重写setValue和getValue方法的子类,竟然有4个方法,其实不用惊奇,最后的两个方法,就是编译器自己生成的桥方法。可以看到桥方法的参数类型都是Object,也就是说,子类中真正覆盖父类两个方法的就是这两个我们看不到的桥方法。而打在我们自己定义的setvalue和getValue方法上面的@Oveerride只不过是假象。而桥方法的内部实现,就只是去调用我们自己重写的那两个方法。
我们拿String的equals来做例子
public boolean equals(Object anObject) {
if (this == anObject) {
return true;
}
if (anObject instanceof String) {
String anotherString = (String)anObject;
int n = value.length;
if (n == anotherString.value.length) {
char v1[] = value;
char v2[] = anotherString.value;
int i = 0;
while (n-- != 0) {
if (v1[i] != v2[i])
return false;
i++;
}
return true;
}
}
如果是==的话基础类型就是比较值,如果是引用类型就是比较两个引用指向的地址,但是如果是equals的话就是比较是否为同一个对象。 如同上面的equals就是先比较地址,然后再比较里面的每一个字母是否一致。
list可以放重复的值,并且可以放多个null值,保存数据都是顺序保存,有序,并且可以方便的随机查找,也可以用迭代器顺序遍历。
set可以不能放重复的值,只能放一个null值,无须,不可以随机查找,只能用迭代器无序遍历。
ArrayList的底层是用数组实现的,所以其随机查找十分方便,但是其增加和删除比较麻烦。
LinkedList的底层是链表实现的,所以其随机查找不方便,但是其增加和删除比较方便。其继承了java中的双端队列(deque接口)所以还可以当双端队列使用。
两者都可以顺序遍历。时间复杂度都是一样的。
hashmap是用数组的下标表示key值的然后每个数组里面存的值是红黑树的头结点或者链表的头节点。
1.将原先的数组丢弃,新数组扩容到原先的两倍。
2.然后将每个数组节点下的元素都要重新hash,并放到不同的key值下存放的链表和红黑树下
3.然后看每个数组节点下的元素
1.如果是链表的话,看是否超过8个,如果没有超过8个,就尾插法将新元素插入进去。
2.如果是红黑树的话,就直接插入到红黑树里面就行。
大致和hashmap一样,但是移动元素的时候是多线程去移动的,然后有几个线程是取决于现在有几条线程可用