(1)简单易学,有丰富的类库。
(2)面向对象 (Java最重要的特性,让程序耦合度更低,内聚性更高)
(3)与平台无关性 (JVM是java跨平台使用的根本)
(4)可靠安全
(5)支持多线程
基本数据类型 | 大小(字节) | 默认值 | 封装类 |
---|---|---|---|
byte | 1 | 0 | Byte |
short | 2 | 0 | Short |
int | 4 | 0 | Integer |
long | 8 | 0 | Long |
float | 4 | 0.0f | Float |
double | 8 | 0.0 | Double |
char | 2 | \u0000(null) | Character |
boolean | - | false | Boolean |
注意:
(1)基本数据类型与引用数据类型的差异:
int 是 基础数据类型,Integer 是 int 的封装类,属于引用数据类型。int 的默认值为 0 , Integer的默认值为 null,所以Integer能够区分出 0 和 null,当Java检测到null 时,就知道这个引用没有指向某个对象,在引用前必须指定一个对象,否则报错。
(2)关于数组对象赋值给另一个数组对象后,发生引用传递现象的原因:
基本数据类型在声明时系统会自动分配空间,而引用数据类型声明时只是分配了引用空间,必须通过实例化开辟数据空间之后可以赋值。数组对象赋值,也是将自己的地址,复制给另一个数组对象到引用空间,所以才会产生引用传递现象。
(3)boolean数据类型在Java中的特点
Java虽然定义了boolean这种数据类型,但是只对它提供了很有限的支持,在JVM中没有任何供boolean专用的字节码指令,Java语言表达式所操作的boolean值,在编译后使用JVM的 int 值代替,而 boolean 数组会被编码成JVM的 byte 数组,每个 boolean 元素占 8 位(1字节),这样我们能得出boolean 类型单独使用是 4个字节(int),在数组中使用则为 1 个字节(byte)。使用int的原因是对于 32 位CPU来说,一次处理数据是 32 位,具有高效存取的特点。
boolean reasult = obj instanceofClass;
其中 obj 是对象,Class表示一个类或接口,当 obj 为 Class 的一个对象,或者是直接或间接子类,或者是接口的实现类,结果 reasult 都会返回为 true,否则返回 false。
注意: 编译器会检查obj是否能转换成右边的class类型,如果不能转换则直接报错,如果不能确定类型,则通过编译,具体看运行而定。
int i = 0;
//编译不通过,说明int不能直接转换为Integer
i instanceof Integer;
//编译不通过,因为int是基本数据类型,并不是引用数据类型
i instanceof Object;
Integer integer = new Integer(1);
//编译通过,说明integer能直接转换为Integer
integer instanceof Integer; //true
//编译通过,因为integer是引用数据类型,引用数据类型就是一个对象,是Object类的子类。
integer instanceof Object; //true
返回值为false的情景: JaveSE规范中对 intstanceof 运算符的规定就是:如果obj 为null,则返回false。
null instanceof Object; //false
Integer.valueOf(int)
实现。Integer.intValue()
实现。在Java SE5 之前,如果要生成一个数值为 10 的 Integer 对象,必须使用 new 关键字实例化 Integer 对象。
Integer i = new Integer(10);
从Java SE5 之后,提供了自动装箱的特性,如果要生成一个数值为 10 的 Integer 对象,直接使用赋值进行生成。
Integer i = 10;
相关笔试题:
(1)以下代码会输出什么?
public class Main{
public static void main(String[] args){
Integer i1 = 100;
Integer i2 = 100;
Integer i3 = 200;
Integer i4 = 200;
System.out.println(i1 == i2);
System.out.println(i3 == i4);
}
}
运行结果
true
false
产生的原因: i1 与 i2 引用的对象是同一个,i3 与 i4 引用的对象是不同的。
Integer.valueOf 源码实现
当参数 i 的值在 [-128 , 127 ]内,则会返回一个指向IntegerCache.cache中已经存在的对象引用;否则创建一个新的Integer对象。
public static Integer valueOf(int i) {
if (i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)];
return new Integer(i);
}
(2)以下代码会输出什么?
public class Main{
public static void main(String[] args){
Double i1 = 100.0;
Double i2 = 100.0;
Double i3 = 200.0;
Double i4 = 200.0;
System.out.println(i1 == i2);
System.out.println(i3 == i4);
}
}
运行结果
false
false
Double.valueOf 源码实现
无论参数是多少,Double都会实例化一个Double对象。
public static Double valueOf(double d) {
return new Double(d);
}
(1)重写(Override)
从字面上看,重写就是“ 重新写一遍 ”的意思。其实就是子类将父类的方法重新写一遍。
子类继承了父类原有的方法,但有时子类想要修改父类的方法,所以在方法名,参数列表,返回类型(除子类方法的返回值是父类方法返回值的子类)都相同的情况下,对方法进行修改或重写。
在重写时,应当注意子类函数的访问权限不能低于父类。
public class Father {
public static void main(String[] args) {
Son son = new Son();
son.sayHello();
}
public void sayHello(){
System.out.println("Hello");
}
}
class Son extends Father{
@Override
public void sayHello() {
System.out.println("hello by son");
}
}
总结:
(2)重载(Overload)
在一个类中,同名方法有不同的参数列表(类型不同、个数不同、顺序不同)则被视为重载。
public class Father {
public static void main(String[] args) {
Father f = new Father();
f.sayHello();
f.sayHello("wintershii");
}
//同名方法1
public void sayHello() {
System.out.println("Hello");
}
//同名方法2
public void sayHello(String name) {
System.out.println("Hello" + " " + name);
}
}
总结:
(1)“==”
“==” 比较的是变量(栈)内存中的对象内存地址,用来判断是否是同一个对象。
比较的对象:
(2)equals
equals比较的是两个对象的内容是否相等。
由于所有类继承Object类的,若子类不重写equals方法,则调用Object.equals(),Object内部使用的是“==”进行比较。
总结: 在所有比较是否相等时,都使用 equals 并且对常量相比较时,以 常量.equals(xxx) 规范进行比较(因为使用Object的equals时,object可能会空指针)。
Java的集合有两类
(1)Set集合是可以如何查重的?
通过equals方法,但不适用于高数据量的Set集合,所以使用了哈希算法提高集合中查找元素的效率。
(2)哈希算法
这种方式将集合分成若干个存储区域,每个对象可以计算出一个哈希码,并将哈希码分组,每组分别对应某个存储区域,根据一个对象的哈希码就可以确定该对象应该存储的那个区域。
(3)hashCode方法
hashCode是根据对象的内存地址换算出的一个值。
当集合要添加新的元素时,先调用这个元素的hashCode方法,获取哈希码,再查找Hash存储区域中是否存在元素,若这个位置上没有元素,就直接存储在这个位置上,不用再进行任何比较了;若存在元素则调用equals方法进行比较,不相同则散列其它地址。
(1)String
String是一种 只读字符串,是一种引用数据类型。
从底层源码来看,是一个 final 类型的字符数组,引用的字符串不能被改变(因为final锁定了),一经定义则无法进行修改。
每次对String进行操作,都会生产新的String对象。
private final char[] value;
(2)String 的 “+” 操作符
每一次的 “+” 操作在堆上都会 new 一个跟原字符串相同的 StringBuilder 对象,再调用 append() 方法拼接新字符。
(3)StringBuffer和StringBuilder
两者继承了 AbstractStringBuilder 抽象类,该抽象类的底层是可变字符组。
操作建议
(2)List:是一个有序的集合,可包含重复元素,提供索引访问,继承于Collection
两种实现类
1、两者父类不同
(1)不同点
(2)相同点: 实现了Map、Cloneable(可复制)、Serializable(可序列化) 这三个接口。
2、对外提供的接口不同
(1)HashTable 多提供了 elements() 和 contains() 两个方法。
3、对null的支持不同
4、安全性不同
虽然 HashMap 是线程不安全的,但是它的效率远远高于 HashTable,这样设计是合理的,因为大部分的使用场景都是单线程。当需要多线程操作的时候可以使用线程安全的ConcurrentHashMap ,效率高于 HashTable(因为 ConcurrentHashMap 使用了 分段锁,并不对整个数据进行锁定)。
5、初始容量大小和每次扩充容量大小不同
HashTable 初始长度为 11,每次扩增原来的 2n+1
HashMap 初始长度为 16,每次扩增 2n
6、计算hash值的方法不同
HashTable 直接使用对象的hash值。
HashMap 为提高计算效率,将哈希表的大小固定为 2 的幂次,这样在取模计算时,不需要做除法,只需要做位运算。(更加高效)
(1)Collection 是集合类的上级接口,子接口有 Set、List、LinkedList、ArrayList、Vector(动态数组)、Stack(栈)。
(2)Collections 是集合类的一个工具类,它包含有各种有关集合操作的静态多态方法,用于实现对各种集合的搜索、排序、线程安全化等操作,服务于Java的Collection框架,此类不能实例化。
(1)强引用: 最常用的引用方式之一,在程序内存(OOM)不足时,不被回收。
String str = new String("str");
System.out.println(str);
(2)弱引用: JVM发现就回收。
WeakReference<String> wrf = new WeakReference<String>(str);
可用场景: Java源码中的 java.util.WeakHashMap 中的 key 就是使用弱引用,一旦不需要某个引用,JVM会自动处理,用户不需要做其它操作。
(3)软引用: 在程序内存(OOM)不足时,会被回收。
// 注意:wrf这个引用也是强引用,它是指向SoftReference这个对象的,
// 这里的软引用指的是指向new String("str")的引用,也就是SoftReference类中T
SoftReference<String> wrf = new SoftReference<String>(new String("str"));
可用场景: 创建缓存时,创建的对象放进缓存中,当内存不足时,JVM回收该对象。
(4)虚引用:
虚引用的回收机制跟弱引用相似,回收前放入 ReferenceQueue 中,而其它引用是被 JVM 回收后才被传入 ReferenceQueue 中的。
由于这个机制,所以虚引用大多被用于引用 销毁前 的处理工作,虚引用创建的时候,必须带有 ReferenceQueue。
PhantomReference<String> prf = new PhantomReference<String>(new String("str"),
new ReferenceQueue<>());
可用场景: 对象销毁前的一些操作,比如说资源释放等。 Object.finalize() 虽然也可以做这
类动作,但是这个方式即不安全又低效。
注意: 这几类引用都是针对对象本身进行引用,而不是指Reference的四个子类的引用(SoftReference等)。
(1)泛型的概念
泛型 是Java SE 1.5之后的特性。在《Java 核心技术》中对泛型的定义是 “泛型” 意味着编写的代码可以被不同类型的对象所重用。
(2)泛型的特点
我们提供了泛指的概念,但可以有具体的规则来约束。
以 ArrayList
List<Integer> list = new ArrayList<>();
(3)泛型的优点
(1)new创建新对象(通过构造器)
(2)通过反射机制(通过 Class.newInstance())
(3)采用clone机制(通过实现 Cloneable 接口,重写clone方法,通过已有的对象进行克隆一个新对象并且方法与属性相同)
(4)通过序列化机制(经过反序列化可以得到序列化后的对象)
有可能,在产生hash冲突时,两个不相等的对象就会有相同的 hashcode 值。
解决方案:
(1)拉链法: 每个 哈希节点 都有一个 next 指针,多个 哈希节点 都可以通过 next指针构建一条单向链表,hashcode 相同的对象,就会被挂在到这条链表上。(Set的查重机制)
(2)开放定址法: 一旦发生冲突,就散列到下一个空的地址上,只要散列表够大,总能找到并存储。(数据结构课上的hash表解决方法)
(3)再哈希: 又叫双哈希法,又多个hash函数,当发生冲突时,使用不同的计算hashcode,直至不冲突。
final的含义就是 最终 的意思,那么意味着它所修饰的所有东西都不能被修改。
常见回答:
(1)final修饰的类不能继承
(2)final修饰的方法不能被重写
(3)final修饰的变量不能修改,若修饰的是对象,则引用地址不变,引用内容可变
(4)final修饰的方法,JVM会尝试内联,以提高效率
(5)final修饰的常量,在编译阶段存入常量池
额外回答:
编辑器对final域要遵守的两个重排序规则(重新排列执行顺序):
import static
,是在JDK1.5引入的特性,用来指定导入某个类中的静态资源。import static java.lang.Math.*
public class Main{
public static void main(String[] args]{
System.out.println(random());
}
}
程序执行结果
false
结果分析
“==” 操作符是用来比较左右两边对象是否相同,很显然浮点数是会有精度不足的情况,3*0.1 = 0.30000000000000004,这是CPU执行的结果,由于数值是直接比较是否相等,所以是false。
执行过程是一致的,但最终输出的结果可能会发生编译异常。
a = a + b: 执行过程是 a + b 的结果赋值给 a,当发生结果超出赋值类型范围则会发生变异错误java: 不兼容的类型: 从int转换到byte可能会有损失
a += b: 执行过程是 a+b 后赋值给 a,但会进行隐式自动类型转换(计算结果超出类型范围会转换到更大的类型上)。
衍生问题
//下列代码是否有错
short s = 1;
s = s + 1;
有错误。short 在计算时,会自动提升为 int 类型,则 s+1 的结果是 int 类型,而 s 是short类型,所以编译不通过,若使用 s += 1,则正确。
执行,并且执行finally早于return。
结论:
Java可抛出的结构分为三种类型:被检查异常,运行时异常,错误
(1)CheckedException
定义: Exception类本身以及其子类,除RuntimeException之外的所有异常都为被检查异常。
特点: Java编译器会检查它,并且能通过throws进行声明抛出,要么通过try-catch进行捕获处理,否则编译不过。
(2)RuntimeException
定义: RuntimeException及其子类都被称为运行时异常。
特点: Java编译器并不会捕获该异常。例如除数为零异常,数组越界异常,fail-fast机制产生的异常(fail-fast是一种快速失败机制,它是一种Java集合的一种错误检测机制,当多个线程对集合进行结构上的改变,那么就有可能产生fail-fast机制)。
常见五种RuntimeException
(3)Error
定义: Error类及其子类。
特点: 和运行时异常一样,编译器也不会对错误进行检查。
当资源不足、约束失败、或是其它程序无法继续运行的条件发生时,就产生错误。
程序本身无法修复这些错误的。例如,VirtualMachineError、OutOfMemoryError、ThreadDeath就属于错误,出现这种错误会导致程序终止运行。
Java虚拟机规范:JVM的内存分为了好几块,比如堆,栈,程序计数器,方法区等。
(1)OOM
OOM,是OutOfMemoryError的简称,除了程序计数器外,虚拟机内存的其它几个运行时区域都有发生OOM异常的可能。
Java堆用于存储对象实例,只要不断创建对象,并且保证GC Roots到对象之间有可达路径来避免垃圾回收机制清除这些对象,当对象数量达到最大堆容量限制后产生OOM异常。
解决这种异常:
通过内存映像分析工具(如Eclipse Memory Analyzer)对dump出来的堆转存快照进行分析,重点是确认内存中的对象是否必要,先分清是因为内存泄漏(Memory Leak)还是内存溢出(Memory Overflow),如果是内存泄漏则进一步通过工具查看泄漏对象到GCRoots的引用链。如果不存在泄漏,则检查虚拟机参数(-Xmx与-Xms)的设置是否适当。
如果线程请求的栈深度大于虚拟机所允许的最大深度,将抛出StackOverflowError异常。
如果虚拟机在扩展栈时无法申请到足够的内存空间,则抛出OutOfMemoryError异常
这里需要注意当栈越大可分配的线程数越少。
异常信息:java.lang.OutOfMemoryError:PermGenspace
如果要向运行时常量池中添加内容,最简单的做法就是使用String.intern()这个Native方法。
该方法的作用是:如果池中已经包含一个等于此String的字符串,则返回代表池中这个字符串的String对象;否则,将此String对象包含的字符串添加到常量池中,并且返回此String对象的引用。
由于常量池分配在方法区内,我们可以通过-XX:PermSize和-XX:MaxPermSize限制方法区的大小,从而间接限制其中常量池的容量。
异常信息:java.lang.OutOfMemoryError:PermGenspace
方法区溢出也是一种常见的内存溢出异常,一个类如果要被垃圾收集器回收,判定条件是很苛刻
的。在经常动态生成大量Class的应用中,要特别注意这点。
(2)SOF
StackOverflowError 的定义:当应用程序递归太深而发生堆栈溢出时,抛出该错误。
因为栈一般默认为1-2mb,一旦出现死循环或者是大量的递归调用,在不断的压栈过程中,造成栈容
量超过1mb而导致溢出。
栈溢出的原因:递归调用,大量循环或死循环,全局变量是否过多,数组、List、map数据过大。
(1)线程:
(2)程序
(3)进程
线程、程序、进程的关系
关键字:transient
作用: 阻止实例中那些用此关键字修饰的变量序列化;当对象被反序列化时,被transient修饰的变量不会被持久化和恢复。
限制: 只能修饰变量。
(1)IO流的分类
(2)IO流的4中基类
(3)按照操作方式分类:
(4)按照操作对象分类:
NIO,即New IO,这个库是JDK1.4引入。NIO与IO的作用和目的相同,但实现方法不同。
NIO主要用到块,所以NIO的效率高于IO。
在Java API中提供了两套NIO
(1)定义: 反射机制是在运行时,对于任意一个类,都能知道这个类的所有属性和方法,对于任意个对象,都能调用它的任意一个方法。在Java中,只要给定类的名字,就可以通过反射机制来获取类的所有信息。
(2)Java 反射机制使用场景
Class.forName("com.mysql.jdbc.Driver.class"); #加载MySQL驱动类
其实Hibernate、struts等框架也都是通过反射机制实现的。
(3)反射的实现方式
第一步: 获取Class对象
(4)实现java反射的类
(5)反射机制的优缺点
(6)反射机制缺点的解决方案
obj1.equals(obj2)==true
,可以推出obj1.hashCode()==obj2.hashCode()
,但是hashCode相等不一定满足equals,所以为了效率,通常要使上面两个条件接近等价。总结:
其它的方法,还有toString() 和 getClass 方法。
User user = new User();
User user = User.class.newInstance();
Object obj = (Object)Class.forName(User.class).newInstance();
#默认User 实现了Cloneable且重写了clone方法
User user = new User();
User clone = user.clone();
#默认对象实现了Serializeable接口
Object obj = ObjectInputStream.readObject();
User user = new User();
user.getClass();
User.class
Class.forName(User.class)
ArrayList除了基本的可扩容特点以及数组特点外的一些特点。
高并发的情况下,线程不安全,当多个线程同时操作 ArrayList,会引发不可预知的异常或错误。
ArrayList 实现了 Cloneable接口,标识着它可以被复制。注意:该克隆是浅克隆。
ArrayList既有数组的高效性,又有List的扩容机制,在不明确数据量的情况下,不需要初始化空间就可以进行存储数据。
fail-fast机制是Java集合的一种错误机制,当多个线程多个线程对同个集合的内容进行操作时,就可能产生fail-fast 事件。
例如: 某个线程A 通过迭代器遍历集合的过程中,若该集合的内容被其它线程改变,则线程A 访问集合时,就会抛出 ConcurrentModificationException 异常,产生 fails-fast 事件。这里的操作主要是指 add、remove 和 clear,对集合元素个数进行修改。
解决方法: 使用 java.util.concurrent 包下的类 去代替 java.utils 包下的类。
可以这么理解:在遍历之前,把 modCount 记录下 expectModCount,后面 expectModCount 去和 modCount 做对比,如果不相等,证明已并发修改了,则抛出ConcurrentModificationException 异常
HashMao的key最多使用的是String类型,当我们想要使用某个自定义类作为 key 时,需要注意以下几点:
为了能让HashMap存取数据效率增加,尽可能地减少 hash 值冲突,尽量将数据均匀分配,每个链表或红黑树长度尽量相等。
问题的重要原因: 取余(%
)操作符,如果是2的幂次,则等价于与其除数减一的与(&
)操作符(即 hash % length == hash &(length - 1)
的前提是length 是 2 的 n 次方),并且采用二进制位操作&
,相对于%
能提高运算效率。
红黑树
try-catch-finally
自定义异常
通过继承 java.lang.Exception
类,若想自定义 RuntimeException
则通过继承 java.lang.RuntimeException
类,实现一个无参构造和一个带有字符串参数的有参构造方法。
在业务代码中,可以通过自定义针对性的异常进行捕获,比如 用户授权验证等等…