java基础篇(二)

一、 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类中的所有静态成员

静态导入可以导入静态方法,这样就不必写类名而可以直接调用静态方法了。

发布了16 篇原创文章 · 获赞 12 · 访问量 163

猜你喜欢

转载自blog.csdn.net/qq_36435947/article/details/104814396
今日推荐