一、 Object类
Object是所有Java类的父类。
Object类的常用方法
1.equels方法
obj1==obj2
如果是引用类型,查看地址是否相等。
如果是基本数据类型,查看内容是否相等。
重写equels方法时,一定要重写hashcode方法。两个对象equels返回true,hashcode值一定相等。hashcode值相等,equels不一定返回true。hashcode值不等,equels方法一定不为true。
equals 重写原则:
对称性: 如果x.equals(y)返回是“true”,那么y.equals(x)也应该返回是“true” ;
自反性: x.equals(x)必须返回是“true” ;
类推性: 如果x.equals(y)返回是“true”,而且y.equals(z)返回是“true”,那么z.equals(x)也应该返回是“true” ;
一致性: 如果x.equals(y)返回是“true”,只要x和y内容一直不变,不管你重复x.equals(y)多少次,返回都是“true” ;
任何情况下,x.equals(null)【应使用关系比较符 ==】,永远返回是“false”;x.equals(和x不同类型的对象)永远返回是“false”
不规范重写equels和hashcode造成的问题:
比如hashset、hashMap 、hashTable,会出现重复元素。
2.hashCode方法
public native int hashCode() ;
native 说明该方法是原生方法,也就是这个方法是用 C/C++ 语言实现的,并且被编译成了DLL,由 java 去调用。
hashCode() 方法返回散列值。默认情况下,hashCode 是根据对象地址计算的,一般要重写根据对象数据计算,要求 equals() 返回 true 的同时,hashCode() 返回值相等,尤其对于 HashMap 和 HashSet 基于哈希表运算的数据结构。
3.toString方法
返回该对象的字符串。默认情况下,返回 ”运行时类名@十六进制 hashCode 值” 格式字符串,一般要重写,返回对象的重要信息。
4.clone方法
protected native Object clone() throws CloneNotSupportedException;
clone() 是 Object 的 protected 方法,它不是 public,一个类不显式去重写 clone(),其它类就不能直接去调用该类实例的 clone() 方法。
//Cloneable是一个标记性接口,里面什么都没定义
//如果一个类没有实现Cloneable接口而调用了clone()方法,会抛出异常
public class A implements Cloneable {
@Override
public A clone() throws CloneNotSupportedException {
//super.clone()会返回该对象的副本
return (A)super.clone();
}
public static void main(String[] args) {
A a1 = new A();
A a2 = null;
try {
a2 = a1.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
System.out.println(a1 == a2); //false
}
}
需要指出,Object 的 clone() 只是一种 “浅拷贝”,它只克隆该对象的所有成员变量值,不会对引用类型的成员变量值所引用的对象进行克隆,所以 “深拷贝” 还需要自己编写。
5.getClass方法
public final native Class<?> getClass();
返回该对象运行时类。
6.finalize方法
protected void finalize() throws Throwable;
当系统中没有引用变量引用到该对象时,垃圾回收器调用此方法来清理该对象的资源。
7.控制线程
public final native void notify();
public final native void notifyAll();
public final native void wait(long timeout) throws InterruptedException;
public final void wait(long timeout, int nanos) throws InterruptedException;
public final void wait() throws InterruptedException;
二、 反射
JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意方法和属性;这种动态获取信息以及动态调用对象方法的功能称为java语言的反射机制。
Reflection 是 Java 程序开发语言的特征之一,它允许运行中的 Java 程序对自身进行检查,或者说“自审”,并能直接操作程序的内部属性和方法。
Java的反射就是利用加载过程中加载到jvm中的.class文件来进行操作的。.class文件中包含java类的所有信息,当你不知道某个类具体信息时,可以使用反射获取class,然后进行各种操作。
常用类:
1.Class类
Class代表类的实体,在运行的Java应用程序中表示类和接口。在这个类中提供了很多有用的方法,这里对他们简单的分类介绍。
2. Field类
Field代表类的成员变量(成员变量也称为类的属性)。
3.Method类
Method代表类的方法。invoke方法。
4.Constructor类
Constructor代表类的构造方法。
获取类实例的三种方法:
1.Class class = obj.getClass(); Object中的方法。
2.Class class = Class.forName(“com.hanyuebb.domain.Person”);
3.Class class = Person.class;
三、 动态代理
为其他对象提供一个代理以控制对某个对象的访问。代理类主要负责为委托了(真实对象)预处理消息、过滤消息、传递消息给委托类,代理类不现实具体服务,而是利用委托类来完成服务,并将执行结果封装处理。
其实就是代理类为被代理类预处理消息、过滤消息并在此之后将消息转发给被代理类,之后还能进行消息的后置处理。代理类和被代理类通常会存在关联关系(即上面提到的持有的被带离对象的引用),代理类本身不实现服务,而是通过调用被代理类中的方法来提供服务。
静态代理
由程序员创建或工具生成代理类的源码,再编译代理类。所谓静态也就是在程序运行前就已经存在代理类的字节码文件,代理类和委托类的关系在运行前就确定了。
缺点:
代理对象的一个接口只服务于一种类型的对象,如果要代理的方法很多,势必要为每一种方法都进行代理,静态代理在程序规模稍大时就无法胜任了。
如果接口增加一个方法,除了所有实现类需要实现这个方法外,所有代理类也需要实现此方法。增加了代码维护的复杂度。
动态代理
动态代理类的源码是在程序运行期间由JVM根据反射等机制动态的生成,所以不存在代理类的字节码文件。代理类和委托类的关系是在程序运行时确定。
1.JDK动态代理
public class JdkProxy implements InvocationHandler {
private Object target;
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("hahahahhah");
Object invoke = method.invoke(target, args);
System.out.println("sdkhsjhdj");
return invoke;
}
private Object getJdkProxy(Object target){
this.target = target;
return Proxy.newProxyInstance(target.getClass().getClassLoader(),target.getClass().getInterfaces(),this::invoke);
}
public static void main(String[] args) {
JdkProxy jdkProxy = new JdkProxy();
Object jdkProxy1 = (Service)jdkProxy.getJdkProxy(new ServiceImpl());
((Service) jdkProxy1).addUser(1);
}
}
2.cglib动态代理
public class CglibProxy implements MethodInterceptor {
private Object target;
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
System.out.println("skadjsahdjsa");
Object invoke = method.invoke(target, objects);
System.out.println("skahdjsahdj");
return invoke;
}
private Object getCglibProxy(Object obj){
this.target = obj;
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(obj.getClass().getSuperclass());
enhancer.setCallback(this);
Object o = enhancer.create();
return o;
}
public static void main(String[] args) {
CglibProxy cglibProxy = new CglibProxy();
Service cglibProxy1 = (Service) cglibProxy.getCglibProxy(new ServiceImpl());
cglibProxy1.addUser(1);
}
}
总的来说,反射机制在生成类的过程中比较高效,而asm在生成类之后的相关执行过程中比较高效(可以通过将asm生成的类进行缓存,这样解决asm生成类过程低效问题)。还有一点必须注意:jdk动态代理的应用前提,必须是目标类基于统一的接口。如果没有上述前提,jdk动态代理不能应用。由此可以看出,jdk动态代理有一定的局限性,cglib这种第三方类库实现的动态代理应用更加广泛,且在效率上更有优势。
3.为什么cglib方式可以对接口实现代理?
答:JDK实现动态代理需要实现类通过接口定义业务方法,对于没有接口的类,如何实现动态代理呢,这就需要CGLib了。CGLib采用了非常底层的字节码技术,其原理是通过字节码技术为一个类创建子类,并在子类中采用方法拦截的技术拦截所有父类方法的调用,顺势织入横切逻辑。JDK动态代理与CGLib动态代理均是实现Spring AOP的基础。
四、 枚举
定义
枚举类型(enum type)是指由一组固定的常量组成合法的类型。Java中由关键字enum来定义一个枚举类型。下面就是java枚举类型的定义。
public enum Season {
SPRING, SUMMER, AUTUMN, WINER;
}
特点
Java定义枚举类型的语句很简约。它有以下特点:
1)使用关键字enum 2) 类型名称,比如这里的Season 3) 一串允许的值,比如上面定义的春夏秋冬四季 4) 枚举可以单独定义在一个文件中,也可以嵌在其它Java类中
除了这样的基本要求外,用户还有一些其他选择:
5)枚举可以实现一个或多个接口(Interface) 6) 可以定义新的变量 7) 可以定义新的方法 8) 可以定义根据具体枚举值而相异的类
实现
定义一个枚举类型:
public enum t {
SPRING,SUMMER;
}
反编译:
public final class T extends Enum
{
private T(String s, int i)
{
super(s, i);
}
public static T[] values()
{
T at[];
int i;
T at1[];
System.arraycopy(at = ENUM$VALUES, 0, at1 = new T[i = at.length], 0, i);
return at1;
}
public static T valueOf(String s)
{
return (T)Enum.valueOf(demo/T, s);
}
public static final T SPRING;
public static final T SUMMER;
private static final T ENUM$VALUES[];
static
{
SPRING = new T("SPRING", 0);
SUMMER = new T("SUMMER", 1);
ENUM$VALUES = (new T[] {
SPRING, SUMMER
});
}
}
通过反编译后代码我们可以看到,public final class T extends Enum,说明,该类是继承了Enum类的,同时final关键字告诉我们,这个类也是不能被继承的。
当我们使用enmu来定义一个枚举类型的时候,编译器会自动帮我们创建一个final类型的类继承Enum类,所以枚举类型不能被继承。
枚举的常用用法
1.单例
public enum EasySingleton{
INSTANCE;
}
线程安全的,因为反编译后可知,他的属性都是static修饰的。static类型的属性会在类被加载之后被初始化,我们在深度分析Java的ClassLoader机制(源码级别)和Java类的加载、链接和初始化两个文章中分别介绍过,当一个Java类第一次被真正使用到的时候静态资源被初始化、Java类的加载和初始化过程都是线程安全的(因为虚拟机在加载枚举的类的时候,会使用ClassLoader的loadClass方法,而这个方法使用同步代码块保证了线程安全)。所以,创建一个enum类型是线程安全的。
双检索单例模式,反序列化后破坏单例,通过枚举不会破坏单例。
我们知道,以前的所有的单例模式都有一个比较大的问题,就是一旦实现了Serializable接口之后,就不再是单例得了,因为,每次调用 readObject()方法返回的都是一个新创建出来的对象,有一种解决办法就是使用readResolve()方法来避免此事发生。但是,为了保证枚举类型像Java规范中所说的那样,每一个枚举类型极其定义的枚举变量在JVM中都是唯一的,在枚举类型的序列化和反序列化上,Java做了特殊的规定。
在序列化的时候Java仅仅是将枚举对象的name属性输出到结果中,反序列化的时候则是通过java.lang.Enum的valueOf方法来根据名字查找枚举对象。同时,编译器是不允许任何对这种序列化机制的定制的,因此禁用了writeObject、readObject、readObjectNoData、writeReplace和readResolve等方法。
2.switch支持枚举
Java 1.7 之前 switch 参数可用类型为 short、byte、int、char,枚举类型之所以能使用其实是编译器层面实现的,编译器会将枚举 switch 转换为类似 switch(s.ordinal()) { case Status.START.ordinal() } 形式,所以实质还是 int 参数类型,感兴趣的可以自己写个使用枚举的 switch 代码然后通过 javap -v 去看下字节码就明白了。1.7中支持String,使用hashcode的值,进行switch的实际是哈希值,然后通过使用equals方法比较进行安全检查,这个检查是必要的,因为哈希可能会发生碰撞。因此它的性能是不如使用枚举进行switch或者使用纯整数常量,但这也不是很差。
switch只支持一种数据类型,那就是整型,其他数据类型都是转换成整型之后在使用switch的。
五、 Java中各种关键字
fianl:
final关键字可用于修饰类、变量、方法,它有“这是无法改变的”或者“最终”的含义。具有以下特性:
final修饰的类不能继承
final修饰的方法不能被子类重写
final修饰的变量(成员变量、局部变量)是常量,只能赋值一次
instanceof:
java 中的instanceof 运算符是用来在运行时指出对象是否是特定类的一个实例。instanceof通过返回一个布尔值来指出,这个对象是否是这个特定类或者是它的子类的一个实例。
volatile:
用于修饰变量,保证内存可见性、禁止指令重排、不保证原子性。后续多线程编程重点讲解。
synchronized:
在Java中,synchronized关键字是用来控制线程同步的,就是在多线程的环境下,控制synchronized代码段不被多个线程同时执行。
修饰方法、同步代码块,后续多线程详解。
finally:
异常处理中,添加至try——catch后面,表示总是执行。
常见面试题:
try、catch、finally用法总结:
1、不管有没有异常,finally中的代码都会执行
2、当try、catch中有return时,finally中的代码依然会继续执行
3、finally是在return后面的表达式运算之后执行的,此时并没有返回运算之后的值,而是把值保存起来,不管finally对该值做任何的改变,返回的值都不会改变,依然返回保存起来的值。也就是说方法的返回值是在finally运算之前就确定了的。
4、finally代码中最好不要包含return,程序会提前退出,也就是说返回的值不是try或catch中的值
static:
用于修饰属性、方法、类。
修饰属性表示类属性,属于一个类的变量,可以使用类名直接调用,也可用对象调用。
修饰方法时表示类方法,类名调用,对象调用。不可以在静态方法中调用非静态方法。
修饰类,用于静态类部类。
const:
在Java中,const是作为保留字以备扩充,同样的保留字以备扩充还有goto。c中表示常量。
transient:
用于序列化,表示不被序列化的属性。
goto:
java保留关键字。
extends:
继承。
this:
当前对象的引用。
具体用法:
1.this.方法名称 ,用来访问本类的成员方法
2.this(), 访问本类的构造方法, ()中可以有参数的 如果有参数 就是调用指定的有参构造。
注意事项:
this() 不能使用在普通方法中 只能写在构造方法中;
必须是构造方法中的第一条语句。
super:
super可以理解为是指向自己超(父)类对象的引用,而这个超类指的是离自己最近的一个父类。
具体用法:
1.与this类似,super相当于是指向当前对象的父类,这样就可以用super.xxx来引用父类的成员。
2…子类中的成员变量或方法与父类中的成员变量或方法同名
3.引用构造函数
finalize:
finalize是方法名,java技术允许使用finalize()方法在垃圾收集器将对象从内存中清除之前做必要的清理工作。这个方法是在垃圾收集器确定这个对象没有被引用的情况下调用的。
finalize是在Object类中定义的,因此所有的类都继承了它。子类覆盖finalize()方法以整理系统资源或者被执行其他清理工作
六、 序列化
序列化和反序列化
序列化是将对象的状态信息转换为可存储或传输的形式的过程。一般是以字节码或XML格式传输。而字节码或XML编码格式可以还原为完全相等的对象。这个相反的过程称为反序列化。
如何对Java对象进行序列化与反序列化
package com.hollis;
import java.io.Serializable;
import java.util.Date;
/**
* Created by hollis on 16/2/2.
*/
public class User implements Serializable{
private String name;
private int age;
private Date birthday;
private transient String gender;
private static final long serialVersionUID = -6849794470754667710L;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public Date getBirthday() {
return birthday;
}
public void setBirthday(Date birthday) {
this.birthday = birthday;
}
public String getGender() {
return gender;
}
public void setGender(String gender) {
this.gender = gender;
}
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
", age=" + age +
", gender=" + gender +
", birthday=" + birthday +
'}';
}
}
package com.hollis;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;
import java.io.*;
import java.util.Date;
/**
* Created by hollis on 16/2/2.
*/
public class SerializableDemo {
public static void main(String[] args) {
//Initializes The Object
User user = new User();
user.setName("hollis");
user.setGender("male");
user.setAge(23);
user.setBirthday(new Date());
System.out.println(user);
//Write Obj to File
ObjectOutputStream oos = null;
try {
oos = new ObjectOutputStream(new FileOutputStream("tempFile"));
oos.writeObject(user);
} catch (IOException e) {
e.printStackTrace();
} finally {
IOUtils.closeQuietly(oos);
}
//Read Obj from File
File file = new File("tempFile");
ObjectInputStream ois = null;
try {
ois = new ObjectInputStream(new FileInputStream(file));
User newUser = (User) ois.readObject();
System.out.println(newUser);
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
} finally {
IOUtils.closeQuietly(ois);
try {
FileUtils.forceDelete(file);
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
//output
//User{name='hollis', age=23, gender=male, birthday=Tue Feb 02 17:37:38 CST 2016}
//User{name='hollis', age=23, gender=null, birthday=Tue Feb 02 17:37:38 CST 2016}
序列化及反序列化相关知识
1、在Java中,只要一个类实现了java.io.SerSerializable接口,那么它就可以被序列化。
2、通过ObjectOutputStream和ObjectInputStream对对象进行序列化及反序列化
3、虚拟机是否允许反序列化,不仅取决于类路径和功能代码是否一致,一个非常重要的一点是两个类的序列化 ID 是否一致(就是 private static final long serialVersionUID)
4、序列化并不保存静态变量。
5、要想将父类对象也序列化,就需要让父类也实现Serializable 接口。
6、Transient 关键字的作用是控制变量的序列化,在变量声明前加上该关键字,可以阻止该变量被序列化到文件中,在被反序列化后,transient 变量的值被设为初始值,如 int 型的是 0,对象型的是 null。
7、服务器端给客户端发送序列化对象数据,对象中有一些数据是敏感的,比如密码字符串等,希望对该密码字段在序列化时,进行加密,而客户端如果拥有解密的密钥,只有在客户端进行反序列化时,才可以对密码进行读取,这样可以一定程度保证序列化对象的数据安全。
ArrayList的序列化
ArrayList底层实现了SerSerializable接口,但是elementData是transient的。ArrayList底层是通过数组实现的。那么数组elementData其实就是用来保存列表中的元素的。通过该属性的声明方式我们知道,他是无法通过序列化持久化下来的。那么为什么code 4的结果却通过序列化和反序列化把List中的元素保留下来了呢?
在ArrayList中定义了来个方法: writeObject和readObject。
在序列化过程中,如果被序列化的类中定义了writeObject 和 readObject 方法,虚拟机会试图调用对象类里的 writeObject 和 readObject 方法,进行用户自定义的序列化和反序列化。
如果没有这样的方法,则默认调用是 ObjectOutputStream 的 defaultWriteObject 方法以及 ObjectInputStream 的 defaultReadObject 方法。
用户自定义的 writeObject 和 readObject 方法可以允许用户控制序列化的过程,比如可以在序列化的过程中动态改变序列化的数值。
ArrayList why transient
ArrayList实际上是动态数组,每次在放满以后自动增长设定的长度值,如果数组自动增长长度设为100,而实际只放了一个元素,那就会序列化99个null元素。为了保证在序列化的时候不会将这么多null同时进行序列化,ArrayList把元素数组设置为transient。
Externalizable接口
除了Serializable 之外,java中还提供了另一个序列化接口Externalizable。
需要重写这两个方法。
public void writeExternal(ObjectOutput out) throws IOException {
}
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
}
两者区别:
Externalizable继承了Serializable,该接口中定义了两个抽象方法:writeExternal()与readExternal()。当使用Externalizable接口来进行序列化与反序列化的时候需要开发人员重写writeExternal()与readExternal()方法。由于上面的代码中,并没有在这两个方法中定义序列化实现细节,所以输出的内容为空。还有一点值得注意:在使用Externalizable进行序列化的时候,在读取对象时,会调用被序列化类的无参构造器去创建一个新的对象,然后再将被保存对象的字段的值分别填充到新对象中。所以,实现Externalizable接口的类必须要提供一个public的无参的构造器。
实现代码:
package com.hollischaung.serialization.ExternalizableDemos;
import java.io.Externalizable;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectOutput;
/**
* Created by hollis on 16/2/17.
* 实现Externalizable接口,并实现writeExternal和readExternal方法
*/
public class User2 implements Externalizable {
private String name;
private int age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public void writeExternal(ObjectOutput out) throws IOException {
out.writeObject(name);
out.writeInt(age);
}
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
name = (String) in.readObject();
age = in.readInt();
}
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
package com.hollischaung.serialization.ExternalizableDemos;
import java.io.*;
/**
* Created by hollis on 16/2/17.
*/
public class ExternalizableDemo2 {
//为了便于理解和节省篇幅,忽略关闭流操作及删除文件操作。真正编码时千万不要忘记
//IOException直接抛出
public static void main(String[] args) throws IOException, ClassNotFoundException {
//Write Obj to file
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("tempFile"));
User2 user = new User2();
user.setName("hollis");
user.setAge(23);
oos.writeObject(user);
//Read Obj from file
File file = new File("tempFile");
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(file));
User2 newInstance = (User2) ois.readObject();
//output
System.out.println(newInstance);
}
}
//OutPut:
//User{name='hollis', age=23}
防止序列化破坏单例模式
使用反射可以破坏单例模式,除了反射以外,使用序列化与反序列化也同样会破坏单例。
只要在Singleton类中定义readResolve就可以解决该问题:
ackage com.hollis;
import java.io.Serializable;
/**
* Created by hollis on 16/2/5.
* 使用双重校验锁方式实现单例
*/
public class Singleton implements Serializable{
private volatile static Singleton singleton;
private Singleton (){}
public static Singleton getSingleton() {
if (singleton == null) {
synchronized (Singleton.class) {
if (singleton == null) {
singleton = new Singleton();
}
}
}
return singleton;
}
private Object readResolve() {
return singleton;
}
}
七、 静态导入
静态导入:导入了类中的所有静态成员,简化静态成员的书写。
import语句可以导入一个类或某个包中的所有类
import static语句导入一个类中的某个静态方法或所有静态方法
import static java.util.Collections.*; //导入了Collections类中的所有静态成员
静态导入可以导入静态方法,这样就不必写类名而可以直接调用静态方法了。