1. java异常分类及处理
1.1 异常分类:受检异常、非受检异常
非受检异常:RuntimeException,如NullPointerException、ClassCastException等异常,要在程序运行时才能抛出。
受检异常:CheckedException,一般是外部错误,这种异常发生在编译阶段,如SQLException、IOException等异常。
1.2 throw 和 throws的区别
throws 用来声明异常,让调用者知道该功能可能出现的问题,声明在函数签名。
throw 是用来抛出异常的,抛出具体问题。
1.3 Error异常
Error异常为不可捕获的异常,即发生在JVM底层的异常,如内存溢出等异常。
2. java反射
2.1 反射 API
- Class类:反射的核心类,获取类的属性、方法等信息。
- Field类:表示类的成员变量,可以用来获取和设置类之中的属性值
- Method 类: 表示类的方法,用来获取类中的方法信息或者执行方法
- Constructor类:表示类的构造方法
2.2 获取 Class 对象的三种方法
- 调用某个对象的 getClass() 方法
Person p = new Person();
Class clazz = p.getClass();
- 调用某个类的 class 属性来获取该类对应的 Class 对象
Class clazz = Person.class;
- 使用 Class 类中的 forName() 静态方法(最安全/性能最好)
Class clazz = Class.forName("com.demo.peroson"); //参数为类的全路径
- 获取类的方法和属性
//获取Class对象
Class clazz = Class.forName("reflection.Person");
//获取类的所有方法信息
Method[] method = clazz.getDeclaredMethods();
//获取类的成员属性信息
Field[] field = clazz.getDeclaredFields();
//获取类的所有构造方法信息
Constructor[] constructor = clazz.getDeclaredConstructors();
2.3 创建对象的几种方法
- Class 对象的 newInstance()
使用 Class 对象的 newInstance() 方法来创建 Class 对象的实例,要求该 Class 对象对应的类有默认的空构造器
//获取 Person 类的 Class 对象
Class clazz = Class.forName("reflection.Person");
//使用.newInstane 方法创建对象
Person p=(Person) clazz.newInstance();
- 先使用 Class 对象获取指定的 Constructor 对象,再调用 Constructor 对象的 newInstance() 方法来创建 Class 对象对应类的实例,通过这种方法可以选定构造方法创建实例
//获取构造方法并创建对象
Constructor c = clazz.getDeclaredConstructor(String.class,String.class,int.class);
//创建对象并设置属性
Person p1 = (Person) c.newInstance("李四","男",20);
- 调用对象的clone()方法
Hello h2 = (Hello)h1.clone();
- 采用序列化机制
使用序列化时,要实现实现Serializable接口,将一个对象序列化到磁盘上,而采用反序列化可以将磁盘上的对象信息转化到内存中。
ObjectOutputStream oos = new ObjectOutputStream(fos);
FileInputStream fis = new FileInputStream(f);
ObjectInputStream ois = new ObjectInputStream(fis);
//序列化对象,写入到磁盘中
oos.writeObject(h);
//反序列化对象
Hello newHello = (Hello)ois.readObject();
3. java注解
Annotation(注解)是 Java 提供的一种对元程序中元素关联信息和元数据(metadata)的途径和方法。
Annatation(注解)是一个接口,程序可以通过反射来获取指定程序中元素的 Annotation对象,然后通过该 Annotation 对象来获取注解中的元数据信息。
4. java内部类
内部类分为四种:静态内部类、成员内部类、局部内部类、匿名内部类。
注意:一般使用频繁比较高的是:成员内部类、匿名内部类
4.1 静态内部类
即定义在类内部的静态内部类
public class Out {
private static int a;
private int b;
public static class Inner {
public void print() {
System.out.println(a);
}
}
}
静态内部类可以访问外部类的所有静态变量和静态方法,即使是 private级别。
其它类使用静态内部类需要使用“外部类.静态内部类”方式,如下所示: Out.Inner inner = new Out.Inner(); inner.print();
4.2 成员内部类
定义在类内部的非静态类,就是成员内部类。成员内部类不能定义静态方法和变量(final 修饰的除外)
public class Out {
private static int a;
private int b;
public class Inner {
public void print() {
System.out.println(a);
System.out.println(b);
}
}
}
4.3 局部内部类
定义在方法中的类,称为局部内部类,如果一个类只在某个方法中使用,可以考虑使用局部内部类
public class Out {
private static int a;
private int b;
public void test(final int c) {
final int d = 1;
class Inner {
public void print() {
System.out.println(c);
}
}
}
}
4.4 匿名内部类
继承一个父类或者实现一个接口时,直接使用 new 来生成一个对象的引用。
public abstract class Bird {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public abstract int fly();
}
public class Test {
public void test(Bird bird){
System.out.println(bird.getName() + "能够飞 " + bird.fly() + "米");
}
public static void main(String[] args) {
Test test = new Test();
test.test(new Bird() {
public int fly() {
return 10000;
}
public String getName() {
return "大雁";
}
});
}
}
5. java泛型
5.1 泛型数组
可以指定数组列表保存的元素对象类型
ArrayList<E> list = new ArrayList<>();
5.2 泛型类
public class Teacher<T>{
private T student;
}
5.3 泛型接口
泛型接口与泛型类的定义与使用基本相同,但是泛型接口常被用于各种类的生产器中。
当类来实现泛型接口时,要传入准确的泛型实参,不然会报 Unknown class 异常
//定义一个泛型接口
public interface Generator<T> {
public T next();
}
//实现类,传入泛型实参String,如果不是准确的实参类型,是会报错的
public class FruitGenerator implements Generator<String> {
@Override
public String next() {
return null;
}
}
5.4 泛型方法
public <U,V> U int get(U[] arr,V ele){}
5.5 泛型通配符
上界通配符
<? extends T> //上界通配符匹配一个类及其所有子类
//举个例子 Basket<? extends Fruit> 只能匹配说过水果类和水果类的子类、苹果、香蕉等类
下界通配符
<? super T> //下界通配符匹配一个类及其所有的父类
//举个例子,Basket<? super Fruit> 只能匹配水果类和水果类的父类(食物类)
无界通配符
List<?> list
上下界通配符的副作用
- 下界通配符<? super T>
能存,能取,但取得部分功能被限制
,取出来得东西只能放到Object类中。 - 上界通配符 <? extends T> 不
能往里存,只能往外取
。因为如果支持存入则会破坏类型安全。
//上界通配符举个例子,只能取数据,不能存数据
<T> void foo(List<? extends T> list);
public <T> void foo(List<? extends T> list) {
// 只能遍历list,不能修改list
for (T t : list) {
System.out.println(t);
}
// list.add(new Object()); // 编译器报错
}
泛型【T】与通配符【?】的区别
泛型和通配符最根本的区别就是Java编译器会把泛型【T】推断成具体类型,而把通配符【?】推断成未知类型。
T 取出来是具体类型,而 ? 取出来是 object 类型,如果不需要修改数据时,则用 ? 比较简便。
6. java序列化
对象保存到磁盘或者进行网络传输需要序列化
- 序列化:将对象写入到IO流中
- 反序列化:从IO流中恢复对象
- 意义:序列化机制允许将实现序列化的Java对象转换位字节序列,这些字节序列可以保存在磁盘上,或通过网络传输,以达到以后恢复成原来的对象。序列化机制使得对象可以脱离程序的运行而独立存在。
- 使用场景:所有可在网络上传输的对象都必须是可序列化的,比如RMI(remote method invoke,即远程方法调用),传入的参数或返回的对象都是可序列化的,否则会出错;所有需要保存到磁盘的java对象都必须是可序列化的。通常建议:程序创建的每个JavaBean类都实现Serializeable接口
7. JAVA深拷贝、浅拷贝、直接赋值
两种传递方式:引用传递、值传递。
对于基本类型来说,直接赋值是直接值传递。
对于对象来说:
直接赋值:拷贝了对象的引用地址而已,没有在内存生成新的对象,即两个对象共用一个引用地址
public class Person implements Cloneable{
private String address;
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
public void display() {
System.out.println("Person{" +
"address='" + address + '\'' +
'}');
}
}
public class Chap6Main {
public static void main(String[] args) {
Person person1 = new Person();
person1.setAddress("汕头");
System.out.println(person1);
System.out.println("person1的hashCode: " + person1.hashCode());
Person person2 = person1;
System.out.println(person2);
System.out.println("person2的hashCode: " + person2.hashCode());
}
}
输出结果:
chap6.Person@1d44bcfa
person1的hashCode: 491044090
chap6.Person@1d44bcfa
person2的hashCode: 491044090
可以看出 person1 和 person2 的对象实例相同,并且 hashCode也相等,证明拷贝的是引用对象的地址,并没有在内存中开辟新对象
浅拷贝:分为以下两种
- 对象是基本数据类型:则复制一份给克隆对象,拷贝的是基本数据类型,也就是说在内存中拥有独立空间
- 对象是引用类型:拷贝父对象,不会拷贝对象的内部的子对象。其实是两个变量代表两个新的对象,父对象的地址不同,但对于子对象两个变量共同引用一个内存地址(即一个变量的子对象的值改变会导致另一个变量的子对象的值改变)。
注意:浅拷贝引用对象和直接赋值有点区别,虽然两者拷贝的都是引用对象的地址,但是前者克隆对象会在内存中开辟新的空间,后者不会,但是对于前者而言,即内部的子对象的引用变量共用的是同一个内存地址。
//浅拷贝例子
class Teacher{
private Student student;
}
//这时候浅拷贝,即有2个对象,老师A和老师B,老师A和老师B的内存地址是不同的,但是两位老师的成员变量学生,是共享一个引用地址的,即我改变老师A的成员变量学生,会影响到老师B的成员变量学生
深拷贝:对基本数据类型进⾏值传递,对引⽤数据类型,创建⼀个新的对象,并复制其内容,此为深拷⻉,两者为完全不同的两个对象变量,即使拥有成员子对象,也是完全不同的。
8. JAVA基本数据类型
Java语言提供了八种基本数据类型。六种数字类型(四个整数型,两个浮点型),一个字符类型,一种布尔型
byte:
- byte 数据类型是8位、有符号的,以二进制补码表示的整数,字节长度为1位
- 最小值是 -128(-2^7);
- 最大值是 127(2^7-1);
- 默认值是 0;
- byte 类型用在大型数组中节约空间,主要代替整数,因为 byte 变量占用的空间只有 int 类型的四分之一;
- 例子:byte a = 100,byte b = -50。
short:
- short 数据类型是 16 位、有符号的以二进制补码表示的整数,字节长度为2位
- 最小值是 -32768(-2^15);
- 最大值是 32767(2^15 - 1);
- Short 数据类型也可以像 byte 那样节省空间。一个short变量是int型变量所占空间的二分之一;
- 默认值是 0;
- 例子:short s = 1000,short r = -20000。
int:
- int 数据类型是32位、有符号的以二进制补码表示的整数,字节长度为4位
- 最小值是 -2,147,483,648(-2^31);
- 最大值是 2,147,483,647(2^31 - 1);
- 一般地整型变量默认为 int 类型;
- 默认值是 0 ;
- 例子:int a = 100000, int b = -200000。
long:
- long 数据类型是 64 位、有符号的以二进制补码表示的整数,字节长度为8位
- 最小值是 -9,223,372,036,854,775,808(-2^63);
- 最大值是 9,223,372,036,854,775,807(2^63 -1);
- 这种类型主要使用在需要比较大整数的系统上;
- 默认值是 0L;
- 例子: long a = 100000L,Long b = -200000L。
"L"理论上不分大小写,但是若写成"l"容易与数字"1"混淆,不容易分辩。所以最好大写。
float:
- float 数据类型是单精度、32位、符合IEEE 754标准的浮点数,字节长度为4位
- float 在储存大型浮点数组的时候可节省内存空间;
- 默认值是 0.0f;
- 浮点数不能用来表示精确的值,如货币;
- 例子:float f1 = 234.5f。
double:
- double 数据类型是双精度、64 位、符合IEEE 754标准的浮点数,字节长度为8位
- 浮点数的默认类型为double类型;
- double类型同样不能表示精确的值,如货币;
- 默认值是 0.0d;
- 例子:double d1 = 123.4。
boolean:
- boolean数据类型表示一位的信息,1/8字节,实际上按照1字节处理
- 只有两个取值:true 和 false;
- 这种类型只作为一种标志来记录 true/false 情况;
- 默认值是 false;
- 例子:boolean one = true。
char:
- char类型是一个单一的 16 位 Unicode 字符,字节长度为2位
- 最小值是 \u0000(即为 0);
- 最大值是 \uffff(即为65、535);
- char 数据类型可以储存任何字符;
- 例子:char letter = ‘A’;
9. JAVA中的引用
java 四种引用包括强引用,软引用,弱引用,虚引用。
注意:用的比较多的是软引用。
强引用
只要引用存在,垃圾回收器永远不会回收
Object obj = new Object();
可直接通过obj取得对应的对象 如obj.equels(new Object());
而这样 obj对象对后面new Object的一个强引用,只有当obj这个引用被释放之后,对象才会被释放掉,这也是我们经常所用到的编码形式
软引用
非必须引用,内存溢出之前进行回收,可以通过以下代码实现
Object obj = new Object();
SoftReference<Object>
sf = new SoftReference<Object>
(obj);
obj = null;
sf.get();//有时候会返回null
这时候sf是对obj的一个软引用,通过sf.get()方法可以取到这个对象,当然,当这个对象被标记为需要回收的对象时,则返回null;
软引用主要用户实现类似缓存的功能,在内存足够的情况下直接通过软引用取值,无需从繁忙的真实来源查询数据,提升速度;当内存不足时,自动删除这部分缓存数据,从真正的来源查询这些数据(可以做图片缓存功能)
弱引用
弱引用与软引用的区别在于:只具有弱引用的对象拥有更短暂的生命周期。在垃圾回收器线程扫描它所管辖的内存区域的过程中,一旦发现了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存。不过,由于垃圾回收器是一个优先级很低的线程,因此不一定会很快发现那些只具有弱引用的对象
Object obj = new Object();
WeakReference<Object>
wf = new WeakReference<Object>
(obj);
obj = null;
wf.get();//有时候会返回null
wf.isEnQueued();//返回是否被垃圾回收器标记为即将回收的垃圾
弱引用是在第二次垃圾回收时回收,短时间内通过弱引用取对应的数据,可以取到,当执行过第二次垃圾回收时,将返回null。
弱引用主要用于监控对象是否已经被垃圾回收器标记为即将回收的垃圾,可以通过弱引用的isEnQueued方法返回对象是否被垃圾回收器标记
虚引用
垃圾回收时回收,无法通过引用取到对象值,可以通过如下代码实现
Object obj = new Object();
PhantomReference<Object>
pf = new PhantomReference<Object>
(obj);
obj=null;
pf.get();//永远返回null
pf.isEnQueued();//返回是否从内存中已经删除
虚引用是每次垃圾回收的时候都会被回收,通过虚引用的get方法永远获取到的数据为null,因此也被成为幽灵引用。
虚引用主要用于检测对象是否已经从内存中删除
10. 使用软引用
为什么要使用软引用
软引用的特性:使用SoftReference引用的对象会有很长的生命周期,只有当系统的内存不足的时候,才会去释放这些软引用对象。所以可以使用软引用来缓存一些比较昂贵的资源,比如获取的网络图片数据
例子:当应用从网络中获取网络图片数据时,用户完全有可能做一些重复性的操作去查看相同的图片信息。对于这样的问题,通常会有两种解决方法: 一种是把过去查看过的图片信息保存在内存中,每一个存储了图片信息的 Java 对象的生命周期都贯穿整个应用程序生命周期,另一种是当用户开始查看其他图片信息的时候,把存储了当前的图片信息的 Java 对象结束引用,使得垃圾收集线程可以回收其所占用的内存空间,当用户再次需要浏览该图片信息的时候,重新获取图片信息
很显然,第一种实现方法将造成大量的内存浪费,而第二种实现的缺陷在于即使垃圾收集线程还没有进行垃圾收集,包含图片信息的对象仍然完好地保存在内存中,应用程序也要重新构建一个对象
像访问磁盘文件、访问网络资源、查询数据库等操作都是影响应用程序执行性能的重要因素,如果能重新获取那些尚未被回收的 Java 对象的引用,必将减少不必要的访问,大大提高程序的运行速度
这样看来,使用软引用是非常有必要的一件事情
如何使用软引用
SoftReference 的特点是它的一个实例保存着一个 Java 对象的软引用,该软引用的存在不妨碍垃圾收集器线程对该 Java 对象的回收。也就是说,一旦SoftReference 保存着一个 Java 对象的软引用之后,在垃圾收集器线程对这个 Java 对象回收之前, SoftReference 类所提供的 get() 方法都会返回 这个Java 对象的强引用。另外,一旦垃圾线程回收该 Java 对象之后, get() 方法将返回 null
软引用的使用方法如下面的Java代码所示 :
MyObject aRef = new MyObject();//创建一个对象
SoftReference aSoftRef = new SoftReference( aRef );//创建对象的软引用
上面的代码执行后,对于MyObject 对象,有两个引用路径,一个是来自 aSoftRef对象的软引用,一个来自变量 aRef 的强引用,所以 MyObject对象是强可及对象。紧跟着,可以使用下面的java的代码结束 Reference 对 MyObject 实例的强引用
aRef = null ;//断开对象的强引用
此后, MyObject 对象成为了软可及对象。如果垃圾收集线程进行内存垃圾收集,并不会因为有一个 SoftReference 对该对象的引用而始终保留该对象。 Java 虚拟机的垃圾收集线程对软可及对象和其他一般 Java 对象进行了区别对待 ,软可及对象的清理是由垃圾收集线程根据其特定算法按照内存需求决定的。也就是说,垃圾收集线程会在虚拟机抛出 OutOfMemoryError 之前回收软可及对象,而且虚拟机会尽可能优先回收长时间闲置不用的软可及对象,对那些刚刚构建的或刚刚使用过的“新”软可及对象会被虚拟机尽可能保留。如果想获取软引用中包含的对象,可以使用下面的Java代码
MyObject anotherRef = (MyObject) aSoftRef.get();//通过软引用获取对象
在回收这些对象之前,可以通过上面的代码重新获得对该实例的强引用。而回收之后,当调用软引用的get() 方法时,返回的是 null
如何使用 ReferenceQueue
作为一个 Java 对象, SoftReference 对象除了具有保存软引用的特殊性之外,也具有 Java 对象的一般性。所以,当软可及对象被回收之后,虽然这个 SoftReference 对象的 get() 方法返回 null, 但这个 SoftReference 对象已经不再具有存在的价值,需要一个适当的清除机制,避免大量 SoftReference 对象带来的内存泄漏。在 java.lang.ref 包里还提供了 ReferenceQueue 。如果在创建 SoftReference 对象的时候,使用了带有一个 ReferenceQueue 对象作为参数的构造方法,如下面的Java代码
ReferenceQueue queue = new ReferenceQueue();//创建引用队列
SoftReference ref = new SoftReference( aMyObject, queue );// 把引用加入到引用队列
当这个 SoftReference 所软引用的 aMyOhject 被垃圾收集器回收的同时,ref 所强引用的 SoftReference 对象被列入 ReferenceQueue 。也就是说, ReferenceQueue 中保存的对象是 Reference 对象,而且是已经失去了它所软引用的对象的 Reference 对象。另外从 ReferenceQueue 这个名字也可以看出,它是一个队列,当调用它的 poll() 方法的时候,如果这个队列中不是空队列,那么将返回队列前面的那个 Reference 对象
在任何时候,都可以调用 ReferenceQueue 的 poll() 方法来检查是否有它所关心的非强可及对象被回收。如果队列为空,将返回一个 null, 否则该方法返回队列中最前面一个 Reference 对象。利用这个方法,可以检查哪个 SoftReference 所软引用的对象已经被回收。可以把这些失去所软引用的对象的 SoftReference 对象清除掉,如下面的Java代码所示
SoftReference ref = null;
while ((ref = (EmployeeRef) q .poll()) != null ) {
// 清除 ref
}
实例分析:通过软引用缓存图片
public class BitmapCache {
private static BitmapCache mCache;
/**
* 用于Chche内容的存储
*/
private Hashtable<Integer, MySoftRef> mHashRefs;
/** 垃圾Reference的队列(所引用的对象已经被回收,则将该引用存入队列中) */
private ReferenceQueue<Bitmap> mReferenceQueue;
private BitmapCache() {
mHashRefs = new Hashtable<Integer, MySoftRef>();
mReferenceQueue = new ReferenceQueue<Bitmap>();
}
/**
*
* 取得缓存器实例
*/
public static BitmapCache getInstance() {
if (mCache == null) {
mCache = new BitmapCache();
}
return mCache;
}
/**
* 继承SoftReference,使得每一个实例都具有可识别的标识。
*/
private class MySoftRef extends SoftReference<Bitmap> {
private Integer _key = 0;
public MySoftRef(Bitmap bmp, ReferenceQueue<Bitmap> q, int key) {
super(bmp, q);
_key = key;
}
}
/**
*
* 以软引用的方式对一个Bitmap对象的实例进行引用并保存该引用
*/
private void addCacheBitmap(Bitmap bmp, Integer key) {
cleanCache();// 清除垃圾引用
MySoftRef ref = new MySoftRef(bmp, mReferenceQueue, key);
mHashRefs.put(key, ref);
}
/**
* 依据所指定的drawable下的图片资源ID号(可以根据自己的需要从网络或本地path下获取),重新获取相应Bitmap对象的实例
*/
public Bitmap getBitmap(int resId, Context context) {
Bitmap bmp = null;
// 缓存中是否有该Bitmap实例的软引用,如果有,从软引用中取得。
if (mHashRefs.containsKey(resId)) {
MySoftRef ref = (MySoftRef) mHashRefs.get(resId);
bmp = (Bitmap) ref.get();
Log.i("GetBitmap", "Contain_" + resId);
}
// 如果没有软引用,或者从软引用中得到的实例是null,重新构建一个实例,
// 并保存对这个新建实例的软引用
if (bmp == null) {
// 传说decodeStream直接调用JNI>>nativeDecodeAsset()来完成decode,
// 无需再使用java层的createBitmap,从而节省了java层的空间。
bmp = BitmapFactory.decodeStream(context.getResources()
.openRawResource(resId));
this.addCacheBitmap(bmp, resId);
Log.i("GetBitmap", "NULL_" + resId);
}
return bmp;
}
/**
* 清除垃圾引用
*/
private void cleanCache() {
MySoftRef ref = null;
while ((ref = (MySoftRef) mReferenceQueue.poll()) != null) {
mHashRefs.remove(ref._key);
}
}
/**
*
* 清除Cache内的全部内容
*/
public void clearCache() {
cleanCache();
mHashRefs.clear();
System.gc();
System.runFinalization();
}
}