Java编程杂记三

参考

jdk8源码

java 版本 java version “1.8.0_191”

Unsafe类

在看JDK源码或者一些开源软件时,会是不是经常看到着这个类的使用,
这个类做什么?有什么用?

首先我们看一下源码。源码中这个类的绝大多数都是native 方法。有几个不是native方法的方法,内部调用的也是native方法,而且都是公共的方法,但是如果你直接调用里面的方法会发现报如下异常

Exception in thread "main" java.lang.SecurityException: Unsafe
	at sun.misc.Unsafe.getUnsafe(Unsafe.java:90)

这是为什么呢?

public static Unsafe getUnsafe() {
    Class var0 = Reflection.getCallerClass();
    //这里会首先获取调用这个方法的Class 对象
    //然后获取这个类的类加载器判断是不是根加载器
    if (!VM.isSystemDomainLoader(var0.getClassLoader())) {
        throw new SecurityException("Unsafe");
    } else {
        return theUnsafe;
    }
}

这个类的对象方法的使用只能是是jre/lib/rt.jar包里面的类调用。

难道真的就没有办法调用了么?也不是可以通过反射来实现,但是这样做会增加程序的风险。Java 文档不建议直接操作这个方法,除非你非常清除你要做什么,否则很有可能。内存溢出。

Field field = Unsafe.class.getDeclaredField("theUnsafe");

field.setAccessible(true);
Unsafe unsafe = (Unsafe) field.get(null);
//分配一块内存
Long id  = unsafe.allocateMemory(1024);
System.out.println(id);
//释放一块内存
unsafe.freeMemory(id);
field.setAccessible(false);

double-check 写法

今天查看代码,从同事那里学到一种写法。

private volatile Map<String, ThreadLocal<RunStatDto>> runStatThreadLocalMap = new ConcurrentHashMap<>();

/**这段代码的精髓在这里,
原先这个地方我以为只需要这样写就可以了。
synchronized (runStatThreadLocalMap) {
    if (!runStatThreadLocalMap.containsKey(processKey)) {
        runStatThreadLocalMap.put(processKey, new ThreadLocal<>());
    }
}
后来经过同事的讲解,我这种写法只保证了现成安全,但是没有考虑性能。
为什么?
因为,如果向上面那样写的话,不论map中是否包含key都要获取锁,
而double-check,就很好避免这个问题。同时利用了ConcurrentHashMap的并发特性。只要包含key就不需要竞争锁资源。最大可能保证并发性。
**/
if (!runStatThreadLocalMap.containsKey(processKey)) {

    // double-check
    synchronized (runStatThreadLocalMap) {
        if (!runStatThreadLocalMap.containsKey(processKey)) {
            runStatThreadLocalMap.put(processKey, new ThreadLocal<>());
        }
    }
}

空对象调用静态方法

class Person{
    public static void testA(){
        System.out.print("testA");
    }
    
    public void testB(){
        System.out.print("testB");
    }
}

public static void main(String[] args) {

    Person person = new Person();
    person = null;
    person.testA();//这一行不会报空指针异常,原因是静态方法不依对象而存在。
}

空对象instanceof

class Person{
    public static void testA(){
        System.out.print("testA");
    }
    
    public void testB(){
        System.out.print("testB");
    }
}

public static void main(String[] args) {

    Person person = new Person();
    person = null;
    //或persion = null;
    persion = (Person) null;
    if (persion instanceof Person){
        System.out.println(true);
    }else {
        System.out.println(false);//总是打印false
    }
}

以上打印false

ScheduledThreadPoolExecutor

Java远程调试

-agentlib:jdwp=transport=dt_socket,server=y,suspend=y,address=5005

String.intern

String s1 = new StringBuilder("go").append("od").toString();
System.out.println(s1.intern() == s1);
//2:
//String s0 = "good";
String s2 = new StringBuilder("go").append("od").toString();
System.out.println(s2.intern() == s2);
//3:
String s3 = new StringBuilder("ja").append("va").toString();
System.out.println(s3.intern() == s3);

不同jar完全相同的包名和类名的加载问题

类的加载与jar的导入顺序有关。只会加载第一个。第二个包会在classloader加载类时判断重复而忽略。

java 获取泛型的实际类

Class<K> ss = ((Class<K>) (((ParameterizedType)(this.getClass().getGenericSuperclass())).getActualTypeArguments()[0]));

java为什么有些异常throw出去需要在函数头用throws声明,一些就不用。

Excepiton分两类:checked exception、runtime exception;

直接继承自Exception就是checked exception,继承自RuntimeException就是runtime的exception。

你可以简单地理解checked exception就是要强制你去处理这个异常(不管你throws多少层,你终归要在某个地方catch它);而runtime exception则没有这个限制,你可以自由选择是否catch。
那些强制异常处理的代码块,必须进行异常处理,否则编译器会提示“Unhandled exception type Exception”错误警告。

clone

浅clone

被复制对象的所有变量都含有与原来的对象相同的值,而所有的对其他对象的引用仍然指向原来的对象。换言之,浅复制仅仅复制所考虑的对象,而不复制它所引用的对象。

public class Bird implements Cloneable,Serializable{
    /**bird种类*/
    String type;
    /**age*/
    int age;
    /**羽毛*/
    Feather feather;

    public Bird(String type, int age, Feather feather) {
        this.type = type;
        this.age = age;
        this.feather = feather;
    }

    public String getType() {
        return type;
    }

    public void setType(String type) {
        this.type = type;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    /**
     * 深度克隆:通过递归的把内嵌的对象复制实现深度克隆。
     * @return
     */
    public Bird clone(){
        Bird o = null;
        Feather f = null;
        try{
            o = (Bird)super.clone();
            o.feather = (Feather) this.feather.clone();
        }catch (CloneNotSupportedException e){}

        return o;
    }
    @Override
    public String toString() {
        return "type= " + type + ", age= " + age + ", " + feather.toString() ;
    }
}
/**羽毛*/
class Feather implements Cloneable,Serializable{
    /**color*/
    String color;
    /**length*/
    int length;

    public Feather(String color, int length) {
        this.color = color;
        this.length = length;
    }

    public String getColor() {
        return color;
    }

    public void setColor(String color) {
        this.color = color;
    }

    public int getLength() {
        return length;
    }

    public void setLength(int length) {
        this.length = length;
    }

    /**
     * 浅克隆
     * @return
     */
    public Object clone(){
        Object o = null;
        try{
            o = super.clone();
        }catch(CloneNotSupportedException e){}

        return o;
    }

    @Override
    public String toString() {
        return "color= " + color + ", length= " + length ;
    }
}

深clone

被复制对象的所有变量都含有与原来的对象相同的值,除去那些引用其他对象的变量。那些引用其他对象的变量将指向被复制过的新对象,而不再是原有的那些被引用的对象。换言之,深复制把要复制的对象所引用的对象都复制了一遍。
实现深度克隆有两种方式:

  • 实现Cloneable接口并重写Object类中的clone()方法;(递归的把内嵌的对象复制)如果引用类型里面还
    包含很多引用类型,或者内层引用类型的类里面又包含引用类型,使用clone方法就会很麻烦。这时我们可以
    用序列化的方式来实现对象的深克隆。
  • 实现Serializable接口,通过对象的序列化和反序列化实现克隆,可以实现真正的深度克隆。
通过序列化实现深度复制。
Feather f = new Feather("red",10);
Bird bird = new Bird("布谷鸟",2,f);
System.out.println(bird.toString());
//Bird copybird = bird.clone();
Bird copybird = null;
try {
    ByteArrayOutputStream out = new ByteArrayOutputStream();
    ObjectOutputStream o = new ObjectOutputStream(out);
    o.writeObject(bird);
    ByteArrayInputStream in = new ByteArrayInputStream(out.toByteArray());
    ObjectInputStream oo = new ObjectInputStream(in);
    copybird = (Bird)oo.readObject();
}catch (IOException e){
    System.out.println(e.getMessage().toString());
}catch(ClassNotFoundException e){
    System.out.println(e.getMessage().toString());
}

System.out.println(copybird.toString());
bird.feather.setColor("black");//原对象修改引用变量指向的对象的属性。
System.out.println("############################");
System.out.println(bird.toString());
System.out.println(copybird.toString());

serialVersionUID

参考
https://www.cnblogs.com/duanxz/p/3511695.html

serialVersionUID适用于Java的序列化机制。简单来说,Java的序列化机制是通过判断类的serialVersionUID来验证版本一致性的。在进行反序列化时,JVM会把传来的字节流中的serialVersionUID与本地相应实体类的serialVersionUID进行比较,如果相同就认为是一致的,可以进行反序列化,否则就会出现序列化版本不一致的异常,即是InvalidCastException。

具体的序列化过程是这样的:

  • 序列化操作的时候系统会把当前类的serialVersionUID写入到序列化文件中;
  • 反序列化时系统会去检测文件中的serialVersionUID,判断它是否与当前类的serialVersionUID一致,如果一致就说明序列化类的版本与当前类版本是一样的,可以反序列化成功,否则失败。

serialVersionUID有两种显示的生成方式:

一是默认的1L,比如:

private static final long serialVersionUID = 1L;

二是根据类名、接口名、成员方法及属性等来生成一个64位的哈希字段,比如:

private static final  long   serialVersionUID = xxxxL;

当实现java.io.Serializable接口的类没有显式地定义一个serialVersionUID变量时候,Java序列化机制会根据编译的Class自动生成一个serialVersionUID作序列化版本比较用,这种情况下,如果Class文件(类名,方法明等)没有发生变化,就算再编译多次,serialVersionUID也不会变化的。

这里需要注意的是:对对象方法的变更,不会导致Class文件的变更,也就是改变对象方法,不会导致serialVersionUID变化

如果我们不希望通过编译来强制划分软件版本,即实现序列化接口的实体能够兼容先前版本,就需要显式地定义一个名为serialVersionUID,类型为long的变量,不修改这个变量值的序列化实体都可以相互进行串行化和反串行化。

下面用代码说明一下serialVersionUID在应用中常见的几种情况。

序列化实体类

package com.sf.code.serial;

import java.io.Serializable;

public class Person implements Serializable {
    private static final long serialVersionUID = 123456789L;
    public int id;
    public String name;

    public Person(int id, String name) {
        this.id = id;
        this.name = name;
    }

    public String toString() {
        return "Person: " + id + " " + name;
    }
}

序列化功能:

package com.sf.code.serial;

import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;

public class SerialTest {

    public static void main(String[] args) throws IOException {
        Person person = new Person(1234, "wang");
        System.out.println("Person Serial" + person);
        FileOutputStream fos = new FileOutputStream("Person.txt");
        ObjectOutputStream oos = new ObjectOutputStream(fos);
        oos.writeObject(person);
        oos.flush();
        oos.close();
    }
}

反序列化功能:

package com.sf.code.serial;

import java.io.FileInputStream;
import java.io.IOException;
import java.io.ObjectInputStream;

public class DeserialTest {
    public static void main(String[] args) throws IOException, ClassNotFoundException {
        Person person;

        FileInputStream fis = new FileInputStream("Person.txt");
        ObjectInputStream ois = new ObjectInputStream(fis);
        person = (Person) ois.readObject();
        ois.close();
        System.out.println("Person Deserial" + person);
    }

}

情况一:假设Person类序列化之后,从A端传输到B端,然后在B端进行反序列化。在序列化Person和反序列化Person的时候,A端和B端都需要存在一个相同的类。如果两处的serialVersionUID不一致,会产生什么错误呢?

【答案】可以利用上面的代码做个试验来验证:
先执行测试类SerialTest,生成序列化文件,代表A端序列化后的文件,然后修改serialVersion值,再执行测试类DeserialTest,代表B端使用不同serialVersion的类去反序列化,结果报错:

Exception in thread "main" java.io.InvalidClassException: com.sf.code.serial.Person; local class incompatible: stream classdesc serialVersionUID = 1234567890, local class serialVersionUID = 123456789
    at java.io.ObjectStreamClass.initNonProxy(ObjectStreamClass.java:621)
    at java.io.ObjectInputStream.readNonProxyDesc(ObjectInputStream.java:1623)
    at java.io.ObjectInputStream.readClassDesc(ObjectInputStream.java:1518)
    at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:1774)
    at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1351)
    at java.io.ObjectInputStream.readObject(ObjectInputStream.java:371)
    at com.sf.code.serial.DeserialTest.main(DeserialTest.java:13)

情况二:假设两处serialVersionUID一致,如果A端增加一个字段,然后序列化,而B端不变,然后反序列化,会是什么情况呢?

package com.sf.code.serial;

import java.io.Serializable;

public class Person implements Serializable {
    private static final long serialVersionUID = 1234567890L;
    public int id;
    public String name;
    public int age;

    public Person(int id, String name) {
        this.id = id;
        this.name = name;
    }
    
    public Person(int id, String name, int age) {
        this.id = id;
        this.name = name;
        this.age = age;
    }

    public String toString() {
        return "Person: " + id 
                + ",name:" + name 
                + ",age:" + age;
    }
}


public class SerialTest {

    public static void main(String[] args) throws IOException {
        Person person = new Person(1234, "wang", 100);
        System.out.println("Person Serial" + person);
        FileOutputStream fos = new FileOutputStream("Person.txt");
        ObjectOutputStream oos = new ObjectOutputStream(fos);
        oos.writeObject(person);
        oos.flush();
        oos.close();
    }
}

Person DeserialPerson: 1234,name:wang
【答案】新增 public int age; 执行SerialTest,生成序列化文件,代表A端。删除 public int age,反序列化,代表B端,最后的结果为:执行序列化,反序列化正常,但是A端增加的字段丢失(被B端忽略)。

情况三:假设两处serialVersionUID一致,如果B端减少一个字段,A端不变,会是什么情况呢?

package com.sf.code.serial;

import java.io.Serializable;

public class Person implements Serializable {
    private static final long serialVersionUID = 1234567890L;
    public int id;
    //public String name;
    
    public int age;

    public Person(int id, String name) {
        this.id = id;
        //this.name = name;
    }

    public String toString() {
        return "Person: " + id 
                //+ ",name:" + name 
                + ",age:" + age;
    }
}

Person DeserialPerson: 1234,age:0
【答案】序列化,反序列化正常,B端字段少于A端,A端多的字段值丢失(被B端忽略)。

情况四:假设两处serialVersionUID一致,如果B端增加一个字段,A端不变,会是什么情况呢?
验证过程如下:
先执行SerialTest,然后在实体类Person增加一个字段age,如下所示,再执行测试类DeserialTest.

package com.sf.code.serial;
import java.io.Serializable;

public class Person implements Serializable {
    private static final long serialVersionUID = 1234567890L;
    public int id;
    public String name;
    public int age;

    public Person(int id, String name) {
        this.id = id;
        this.name = name;
    }
    
    /*public Person(int id, String name, int age) {
        this.id = id;
        this.name = name;
        this.age = age;
    }*/

    public String toString() {
        return "Person: " + id 
                + ",name:" + name 
                + ",age:" + age;
    }
}

结果:Person DeserialPerson: 1234,name:wang,age:0
说明序列化,反序列化正常,B端新增加的int字段被赋予了默认值0。

graph LR
Person类A端-->|生成序列化文件|Person.txt
Person.txt-->|读取序列化文件|Person类B端
Person类B端-->Person对象
graph LR
Person类增加字段-->|生成序列化文件|Person.txt
Person.txt-->|读取序列化文件|Person类B端
Person类B端-->Person对象读取不到新增字段
graph LR
Person类A端-->|生成序列化文件|Person.txt
Person.txt-->|读取序列化文件|Person类B端减少字段
Person类B端减少字段-->Person对象读取不到减少字段
graph LR
Person类A端-->|生成序列化文件|Person.txt
Person.txt-->|读取序列化文件|Person类B端增加字段
Person类B端增加字段-->Person对象新增字段为默认值
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

风吹千里

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值