1、8大基本数据类型
1.1、基本数据类型
数据类型 | 说明 | 所占内存 | 举例 | 备注 |
---|---|---|---|---|
byte | 字节型 | 1 byte | 3, 127 | |
short | 短整型 | 2 bytes | 3, 32767 | |
int | 整型 | 4 bytes | 3, 21474836 | |
long | 长整型 | 8 bytes | 3L, 92233720368L | long最后要有一个L字母(大小写无所谓)。 |
float | 单精度浮点型 | 4 bytes | 1.2F, 223.56F | float最后要有一个F字母(大小写无所谓)。 |
double | 双精度浮点型 | 8 bytes | 1.2, 1.2D, 223.56, 223.56D | double最后最好有一个D字母(大小写无所谓)。 |
char | 字符型 | 2 bytes | ‘a’, ‘A’ | 字符型数据只能是一个字符,由单引号包围。 |
boolean | 布尔型 | 1 bit | true, false |
1.2、包装类(自动装箱和拆箱)
Java是一门面向对象的语言,为了符合这个思想,基本类型也有其对应的类,称为包装类,由基本类型变成对应的类的过程叫自动装箱,反之称为自动拆箱,这个过程是由Java自动实现的。
数据类型 | 包装类 |
---|---|
byte | Byte |
short | Short |
int | Integer |
long | Long |
float | Float |
double | Double |
char | Character |
boolean | Boolean |
- 相同类型的比较(包装类会自动拆箱成基本数据类型进行比较)
public static void main(String[] args) {
Integer a = new Integer(5);
int b = 5;
System.out.println(a==b);//结果为true
}
1.3、类型转换
- 自动转换:运算中,不同类型的数据先自动转化为同一类型,再进行运算(级别低的自动转换为级别高的)
操作数1类型 | 操作数2类型 | 转换后的类型 |
---|---|---|
byte、short、char | int | int |
byte、short、char、int | long | long |
byte、short、char、int、long | float | float |
byte、short、char、int、long、float | double | double |
- 强制类型转换
2、修饰符(如public,static)
- 访问控制修饰符
- default:包内可见
- public:公开
- private:私有,内部可见(内部类)
- protected:包内和子类可见
- 非访问控制修饰符
- static:静态,一处变处处变
- final:不能二次赋值,不能修改,修饰的类不能被继承(String)
- synchronized:同一时间只能有一个线程访问
- volatile:允许直接对变量的值进行操作(我们一般操作的是副本)
- transient :序列化的对象包含被 transient 修饰的实例变量时,java 虚拟机(JVM)跳过该特定的变量
3、String类
1、这个类不能被继承也不能被修改,因为其被final关键字修饰
2、Java1.8及之前底层使用的是char数组,之后使用的是byte数组
下面的代码看似修改了字符串,实则没有
public static void main(String[] args) {
String str = "hello";
str = str + ",world";
System.out.println(str);
}
-
上面的代码会导致一些问题:堆中开辟了新的空间,存放了数据,但是栈中却并没有指向其地址的引用,也就是说,我们没有办法使用这些数据,这些数据就成为了堆中的垃圾,所以String类适用于少量字符串的操作,上图只是为了方便理解画成这样,存在问题
-
对字符串的操作还有StringBuffer和StringBuilder类可以看看这个String,StringBuffer,StringBuilder
-
关于String a = "abc"和new String(“abc”)
对于第一种方式,JVM会使用常量池来管理字符串的直接量,在执行这句话时,JVM会先检查常量池中是否已经存在,若有则复用,若没有则将"abc"存入常量池
对于第二种方式,JVM会先使用常量池来管理字符串直接量,然后再创建一个新的String对象,这个对象会被保存到堆中,所以一般建议直接使用第一种方式创建字符串
4、Object类
这个类位于java.lang包下,在Java类中相当于祖先的地位,其有一些常用的方法需要掌握
- equals()
这是Object类中的equals方法的实现,可以看到,本质是用==来实现的,对于基本类型,==符号比较的是其数值,对于对象,==符号比较的是对象在内存中的地址,在其他的类中对equals方法进行了重写,例如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;
}
}
return false;
}
- hashCode()
这个方法用于获取哈希码,在使用equals比较两个对象是否相等时,如果相等,则它们必须有相同的哈希码,但是两个对象哈希码相同,他们未必相等 - getClass()
返回该对象的运行时类 - toString()
5、抽象类和接口
抽象类和接口在Java中都是用来抽象的,也就是定义类的公共标准,但是接口比抽象类更加抽象,更高级,抽象类用abstract关键字修饰,接口是interface
抽象类:含有抽象方法的类是抽象类,抽象方法只有声明而没有具体的实现,也用abstract修饰,但是Java中没有抽象方法也可以声明为抽象类,但是这就显得没有必要了
抽象类具有以下特点:
- 抽象方法必须为public或者protected(因为如果为private,则不能被子类继承,子类便无法实现该方法),缺省情况下默认为public
- 抽象类不能用来创建对象
- 如果一个类继承于一个抽象类,则子类必须实现父类的抽象方法。如果子类没有实现父类的抽象方法,则必须将子类也定义为为abstract类
接口:接口中可以含有变量和方法,变量被隐式指定为public static final,方法被隐式指定为public abstract,也就是说你可以不用写方法和变量的声明,因为只能是这样的,一个类实现接口就必须实现接口中的所有方法
接口和抽象类的区别
- 抽象类中可以有具体方法
- 抽象类中的成员变量可以是各种类型的
- 抽象类中可以有静态代码块和静态方法
- 一个类只能继承一个抽象类,但是可以实现多个接口(单继承,多实现)
6、ArrayList和LinkedList
二者主要有以下区别:
1、ArrayList是基于数组的,LinkedList是基于链表的
2、对于随机访问,ArrayList要优于LinkedList,因为LinkedList要移动指针
3、对于添加和删除操作,LinkedList要优于ArrayList,因为ArrayList要移动数据
LinkedList是个双向链表,它可以被当作栈、队列或双端队列来使用,在这样的对比下感觉ArrayList并没有什么优势,但是当插入大量的数据的时候,大约在容量的1/10之前,LinkedList会优于ArrayList,在其后就劣与ArrayList,且越靠近后面越差。所以个人觉得,一般首选用ArrayList,由于LinkedList可以实现多种数据结构,所以当特定需要时候,使用LinkedList,当然,数据量小的时候,两者差不多,视具体情况去选择使用;当数据量大的时候,如果只需要在靠前的部分插入或删除数据,那也可以选用LinkedList,反之选择ArrayList反而效率更高
7、HashMap
HashMap是一种key-value的结构,在开发中经常使用,HashMap最值得研究的是其put方法
public V put(K key, V value) {
return putVal(hash(key), key, value, false, true);
}
put方法是用putVal实现的
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
Node<K,V>[] tab; Node<K,V> p; int n, i;
if ((tab = table) == null || (n = tab.length) == 0)
n = (tab = resize()).length;
if ((p = tab[i = (n - 1) & hash]) == null)
tab[i] = newNode(hash, key, value, null);
else {
Node<K,V> e; K k;
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
e = p;
else if (p instanceof TreeNode)
e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
else {
for (int binCount = 0; ; ++binCount) {
if ((e = p.next) == null) {
p.next = newNode(hash, key, value, null);
if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
treeifyBin(tab, hash);
break;
}
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
break;
p = e;
}
}
if (e != null) { // existing mapping for key
V oldValue = e.value;
if (!onlyIfAbsent || oldValue == null)
e.value = value;
afterNodeAccess(e);
return oldValue;
}
}
++modCount;
if (++size > threshold)
resize();
afterNodeInsertion(evict);
return null;
}
从上面的代码可以看到,HashMap用的是结点的方式来实现数据存储的,使用节点的数据结构有链表和树,那么到底什么时候使用链表,什么时候使用树形结构来存储呢
从上图中可以发现,当链表长度达到8,可以转化为树形,另外,上图中可以看出,HashMap有一个默认的初始化容量为16,默认的负载系数为0.75,我们知道HashMap的容量是可以自行增加的
if (++size > threshold)
resize();
结合代码和图片中的描述可以发现,threshold = capacity*load factor(容量 * 负载系数),当达到对应的数值后会resize