我是靠谱客的博主 幸福黑裤,最近开发中收集的这篇文章主要介绍Java知识总结(二),觉得挺不错的,现在分享给大家,希望可以做个参考。

概述

附上后续,后续持续更新

Java反射

特点:

动态语言

在程序运行期间可以改变其结构,引入新的函数,也可以改变已有函数结构。例如:JavaScript

从反射角度来看:java属于半动态语言。c++、c不是动态语言。

反射机制概念(运行状态中知道类的所有属性和方法)

在java的反射机制中处于运行状态时,对于任意一个类都能够知道它所有的属性和方法,并且对于任意一个对象,都能够调用它的任意一个方法。动态获取信息以及动态调用对象方法的功能。

本质上:从字节码中查找,动态获取类的结构,包括属性、构造器、动态调用对象方法,注意并不是修改类

反射应用场景

编译时类型和运行时类型

编译时类型由声明的类型决定,运行时类型由实际赋予的对象类型决定。

Person p=new Student();
编译类型:Person   运行类型:Student
//引用类型Person是定义的,new Student()是在运行时创建的对象    

当程序处于运行时从外部传入新的对象,该对象的编译时类型为Object,但程序运行时需要调用该对象的运行时类型方法,在编译时无法获取对象属于哪些类时,为了在运行时获取该对象的具体类型的方法,此时就要用到反射。

img

反射API

  • Class 类:位于java.lang包下,反射的核心类,可以获取类的属性,方法、构造方法等信息。
  • Field 类:Java.lang.reflec 包中的类,表示类的成员变量,可以用来获取和设置类之中的属性值。
  • Method 类: Java.lang.reflec 包中的类,表示类的方法,它可以用来获取类中的方法信息或者执行方法。
  • Constructor 类: Java.lang.reflec 包中的类,表示类的构造方法。

反射的核心操作:获取想要操作的Class对象,用来调用类方法。

获取Class对象方式

  • 调用Object类的getClass方法
Person p=new Person();
Class clazz=p.getClass();
  • 通过类名获取:类名.class(字节码文件)
Class clazz=Person.class;
  • 使用Class.forName(“完整的类名”)方法**(最安全、性能最好)**
Class clazz=Class.forName("类的全路径"); (最常用)

创建对象的两种方法

Class对象的newInstance()

使用Class对象的newInstance()方法创建该Class对象对应类的实列,这种方法要求该Class对象对应的类有默认的空构造器。

调用Constructor对象的newInstance()

先通过获取Class对象中对应类指定的Constructor对象,调用Constructor对象的newInstance()方法创建Class对象对应类的实例。

//获取 Person 类的 Class 对象
 Class clazz=Class.forName("reflection.Person"); 
 //使用.newInstane 方法创建对象
 Person p=(Person) clazz.newInstance();
//获取构造方法并创建对象
 Constructor c=clazz.getDeclaredConstructor(String.class,String.class,int.class);
 //创建对象并设置属性
 Person p1=(Person) c.newInstance("李四","男",20);

JAVA注解

(7条消息) java注解-最通俗易懂的讲解_Tanyboye的博客-CSDN博客_java 注解

在正真对注解进行理论上的学习之前,可以对注解进行形象化方便理解。

可以把注解想象成标签,标签是干什么的呢,表明名称、属性、作用的,类似一种说明,反映事物的特性的。

标签是对事物行为的评价与解释,注解的作用类似于标签。

概念:

Annotation(注解)是一个接口,程序可以通过反射来获取指定程序中元素的 Annotation对象,然后通过该 Annotation 对象来获取注解中的元数据信息。

注解定义:

public @interface TestAnnotation {
}// 注解通过 @interface 关键字进行定义。 

定义了一个名为TestAnnotation的注解(标签)。

注解应用:

@TestAnnotation
public class Test {
}
//将TestAnnotation注解贴在Test类上

元注解:

负责注解其他注解, 作用在注解中,方便我们使用注解实现想要的功能。元注解分别有@Retention、 @Target、 @Document、 @Inherited和@Repeatable(JDK1.8加入)五种。

元注解负责解释定义注解的属性、范围、功能的。

@Retention

Retention 的英文意为保留期的意思。当 @Retention 应用到一个注解上的时候,它解释说明了这个注解的的存活时间。

它的取值如下:

  • RetentionPolicy.SOURCE 注解只在源码阶段保留,在编译器进行编译时它将被丢弃忽视。
  • RetentionPolicy.CLASS 注解只被保留到编译进行的时候,它并不会被加载到 JVM 中。
  • RetentionPolicy.RUNTIME 注解可以保留到程序运行的时候,它会被加载进入到 JVM 中,所以在程序运行时可以获取到它们。
//元注解
@Retention(RetentionPolicy.RUNTIME)
public @interface TestAnnotation {
}
//指定TestAnnotation可以在运行期间被获取到

@Documented

  • Document的英文意思是文档。它的作用是能够将注解中的元素包含到 Javadoc 中去。

@Target

  • Target的英文意思是目标,这也很容易理解,使用@Target元注解表示我们的注解作用的范围就比较具体了,可以是类,方法,方法参数变量等,同样也是通过枚举类ElementType表达作用类型
  • @Target(ElementType.TYPE) 作用接口、类、枚举、注解
  • @Target(ElementType.FIELD) 作用属性字段、枚举的常量
  • @Target(ElementType.METHOD) 作用方法
  • @Target(ElementType.PARAMETER) 作用方法参数
  • @Target(ElementType.CONSTRUCTOR) 作用构造函数
  • @Target(ElementType.LOCAL_VARIABLE)作用局部变量
  • @Target(ElementType.ANNOTATION_TYPE)作用于注解(@Retention注解中就使用该属性)
  • @Target(ElementType.PACKAGE) 作用于包
  • @Target(ElementType.TYPE_PARAMETER) 作用于类型泛型,即泛型方法、泛型类、泛型接口 (jdk1.8加入)
  • @Target(ElementType.TYPE_USE) 类型使用.可以用于标注任意类型除了 class (jdk1.8加入)
  • 一般比较常用的是ElementType.TYPE类型
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface MyTestAnnotation {

}

@Inherited

Inherited 是继承的意思,但是它并不是说注解本身可以继承,而是说如果一个超类被 @Inherited 注解过的注解进行注解的话,那么如果它的子类没有被任何注解应用的话,那么这个子类就继承了超类的注解。
说的比较抽象。代码来解释。

@Inherited
@Retention(RetentionPolicy.RUNTIME)
@interface Test {}
@Test
public class A {}
public class B extends A {}

注解Test被@Inherited修饰,之后A类被Test注解修饰,B类是A类的子类,因此B类也拥有Test这个注解。

@Repeatable

Repeatable 自然是可重复的意思。@Repeatable 是 Java 1.8 才加进来的,所以算是一个新的特性。

什么样的注解会多次应用呢?通常是注解的值可以同时取多个。

//一个人他既是程序员又是产品经理,同时他还是个画家。
@interface Persons {
    Person[]  value();
}

@Repeatable(Persons.class)
@interface Person{
    String role default "";
}

@Person(role="artist")
@Person(role="coder")
@Person(role="PM")
public class SuperMan{
}

@Repeatable 注解了 Person。而 @Repeatable 后面括号中的类相当于一个容器注解。

//容器注解
@interface Persons {
    Person[]  value();
}

注解的属性

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface TestAnnotation {
    int id();
    String msg();
}

//属性又叫成员变量
//属性:id 和msg
//返回类型: int、String 

赋值

//赋值的方式是在注解的括号内以 value=”” 形式,多个属性之前用 ,隔开。
@TestAnnotation(id=3,msg="hello annotation")
public class Test {
}
//注解中属性可以有默认值,默认值需要用 default 关键值指定。
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface TestAnnotation {
    public int id() default -1;
    public String msg() default "Hi";
}

java中其他三个标准注解

@Deprecated

这个元素是用来标记过时的元素,想必大家在日常开发中经常碰到。编译器在编译阶段遇到这个注解时会发出提醒警告,告诉开发者正在调用一个过时的元素比如过时的方法、过时的类、过时的成员变量。

public class Hero {
    @Deprecated
    public void say(){
        System.out.println("Noting has to say!");
    }
    public void speak(){
        System.out.println("I have a dream!");
    }
}
//say()方法被标注为过时

@Override

子类要复写父类中被 @Override 修饰的方法

@SuppressWarnings

阻止警告的意思。之前说过调用被 @Deprecated 注解的方法后,编译器会警告提醒,而有时候开发者会忽略这种警告,他们可以在调用的地方通过 @SuppressWarnings 达到目的。

@SuppressWarnings("deprecation")
public void test1(){
    Hero hero = new Hero();
    hero.say();
    hero.speak();
}
//编辑器不会发出say()的过时警告

注解与反射。

注解通过反射获取。首先可以通过 Class 对象的 isAnnotationPresent() 方法判断它是否应用了某个注解

public boolean isAnnotationPresent(Class<? extends Annotation> annotationClass) {}

然后通过 getAnnotation() 方法来获取 Annotation 对象。

 public <A extends Annotation> A getAnnotation(Class<A> annotationClass) {}

或者是 getAnnotations() 方法。

public Annotation[] getAnnotations() {}

测试:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface TestAnnotation {
    public int id() default -1;
    public String msg() default "Hi";
}

@TestAnnotation()
public class Test {
    public static void main(String[] args) {
        // 注解通过反射获取。
        boolean hasAnnotation = Test.class.isAnnotationPresent(TestAnnotation.class);
        if ( hasAnnotation ) {
            //通过 getAnnotation() 方法来获取 Annotation 对象。 
            TestAnnotation testAnnotation = Test.class.getAnnotation(TestAnnotation.class);
            System.out.println("id:"+testAnnotation.id());
            System.out.println("msg:"+testAnnotation.msg());
        }
    }
}
//id:-1
//msg:Hi

注解同样无法改变代码本身,注解只是某些工具的的工具。

注解的应用实例

JUnit 这个是一个测试框架,典型使用方法如下:

public class ExampleUnitTest {
    @Test
    public void addition_isCorrect() throws Exception {
        assertEquals(4, 2 + 2);
    }
}

自定义的注解为了实现某个目的而定义注解,比如要实现单元测试,可以自定义一个类似于@Test的注解,来完成单元测试达到目的。

注解有许多用处,主要如下:

  • 提供信息给编译器: 编译器可以利用注解来探测错误和警告信息
  • 编译阶段时的处理: 软件工具可以用来利用注解信息来生成代码、Html文档或者做其它相应处理。
  • 运行时的处理: 某些注解可以在程序运行的时候接受代码的提取
    值得注意的是,注解不是代码本身的一部分。

总结

  • 如果注解难于理解,你就把它类同于标签,标签为了解释事物,注解为了解释代码
  • 注解的基本语法,创建如同接口,但是多了个 @ 符号。
  • 注解的元注解。
  • 注解的属性。
  • 注解主要给编译器及工具类型的软件用的。
  • 注解的提取需要借助于 Java 的反射技术,反射比较慢,所以注解使用时也需要谨慎计较时间成本

Java内部类

Java 类中不仅可以定义变量和方法,还可以定义类,这样定义在类内部的类就被称为内部类。根

据定义的方式不同,内部类分为静态内部类,成员内部类,局部内部类,匿名内部类四种。

静态内部类

定义在类内部的被static关键字修饰的类

public class innerClass {
    private static  int a  = 1;
    private int b = 2;
    public static class staticClass{
        private int n =3;
        private static  int c = 6;
        int b = innerClass.a;

        public static void main(String[] args) {
            innerClass.print1();
        }

    }
    public void print(){
        System.out.println(1);
    }

    public  static void print1(){
        System.out.println(1);
    }
}
/*
1. 静态内部类可以访问外部类所有的静态变量和方法,即使是 private 的也一样。不能访问非静态变量和方法
2. 静态内部类和一般类一致,可以定义静态变量和非静态变量、方法,构造方法等。
3. 其它类使用静态内部类需要使用“外部类.静态内部类”方式,如下所示:
Out.Inner inner = new Out.Inner();
inner.print();
*/

成员内部类

定义在类内部的非静态类,就是成员内部类。成员内部类不能定义静态方法和变量(final 修饰的

除外)。这是因为成员内部类是非静态的,类初始化的时候先初始化静态成员,如果允许成员内

部类定义静态变量,那么成员内部类的静态变量初始化顺序是有歧义的。

补充:成员内部类本身可以访问外围类的所有资源。

public class innerClass {
    private static  int a  = 1;
    private int b = 2;
    public  class staticClass{
        private int n =3;
        //不能定义静态变量和方法
//        private static  int c = 6;
        int b = innerClass.a;

//        public static void main(String[] args) {
//            innerClass.print1();
//        }

    }
    public void print(){
        System.out.println(1);
    }

    public  static void print1(){
        System.out.println(1);
    }
}

局部内部类(定义在方法中的类)

定义在方法中的类,就是局部类。如果一个类只在某个方法中使用,则可以考虑使用局部类。

public class innerClass {
    private static  int a  = 1;
    private int b = 2;

    public void test(){
        int o = 1;
        class constClass{
            //不允许定义静态变量和方法
//            private static int  j = 0;
//            public static  void print(){
//                System.out.println(1);
//            }
            public  int i = 0;
            public  void print(){
                System.out.println(2);
            }
        }
        System.out.println(1);
    }

}

匿名内部类(要继承一个父类或者实现一个接口、直接使用new 来生成一个对象的引用)

匿名内部类我们必须要继承一个父类或者实现一个接口,当然也仅能只继承一个父类或者实现一

个接口。同时它也是没有 class 关键字,这是因为匿名内部类是直接使用 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();
        //new关键字实现抽象类Bird的fly方法
        test.test(new Bird() {
            public int fly() {
                return 10000;
            }
            public String getName() {
                return "大雁";
            }
        });
    } 
}

Java泛型

Java泛型详解,通俗易懂只需5分钟 - 会偷袭的猫 - 博客园 (cnblogs.com)

一般情况下,我们在使用变量时会先进行定义,确定变量具体的类型,定义方法时也需要给定具体的类型的参数。

当遇到同一个变量需要不同类型或者方法的参数类型多样时,一般不是定义多个变量就是重载方法,这样做的话会让代码显得臃肿。虽然java中有一个顶级父类,任何一个类的实例都可以向上转型成Object,但是具体到某个数据时又需要将Object类强制转换,当中可能存在着风险,因此使用java通过的泛型就可以很好的解决这样的问题。

所谓泛型:就是宽泛的数据类型,它可以接受任意的数据类型。

应用:

泛型类

public class Demo {
    public static void main(String[] args){
        // 实例化泛型类
        Point<Integer, Integer> p1 = new Point<Integer, Integer>();
        p1.setX(10);
        p1.setY(20);
        int x = p1.getX();
        int y = p1.getY();
        System.out.println("This point is:" + x + ", " + y);

        Point<Double, String> p2 = new Point<Double, String>();
        p2.setX(25.4);
        p2.setY("东京180度");
        double m = p2.getX();
        String n = p2.getY();
        System.out.println("This point is:" + m + ", " + n);
    }
}

// 定义泛型类
class Point<T1, T2>{
    T1 x;
    T2 y;
    public T1 getX() {
        return x;
    }
    public void setX(T1 x) {
        this.x = x;
    }
    public T2 getY() {
        return y;
    }
    public void setY(T2 y) {
        this.y = y;
    }
}

特点:与普通类相比,泛型类的类名后多了<T1, T2>, T1, T2 是自定义的标识符,也是参数,用来传递数据的类型,而不是数据的值,我们称之为类型参数

类型参数(泛型参数)由尖括号包围,多个参数由逗号分隔,如<T1, T2>。

T 代表一般的任何类。
E 代表 Element 的意思,或者 Exception 异常的意思。
K 代表 Key 的意思。
V 代表 Value 的意思,通常与 K 一起配合使用。
S 代表 Subtype 的意思,文章后面部分会讲解示意。

在真正运用泛型类时,要指出它具体的类型:

Point<Integer, Integer> p1 = new Point<Integer, Integer>();

和我们经常用到的Map,List接口非常的相似,它们是泛型接口罢了。

泛型方法

public class Demo {
    public static void main(String[] args){
        // 实例化泛型类
        Point<Integer, Integer> p1 = new Point<Integer, Integer>();
        p1.setX(10);
        p1.setY(20);
        p1.printPoint(p1.getX(), p1.getY());

        Point<Double, String> p2 = new Point<Double, String>();
        p2.setX(25.4);
        p2.setY("东京180度");
        p2.printPoint(p2.getX(), p2.getY());
    }
}

// 定义泛型类
class Point<T1, T2>{
    T1 x;
    T2 y;
    public T1 getX() {
        return x;
    }
    public void setX(T1 x) {
        this.x = x;
    }
    public T2 getY() {
        return y;
    }
    public void setY(T2 y) {
        this.y = y;
    }

    // 定义泛型方法
    public <T1, T2> void printPoint(T1 x, T2 y){
        T1 m = x;
        T2 n = y;
        System.out.println("This point is:" + m + ", " + n);
    }
}

定义了一个泛型方法 printPoint(),既有普通参数,也有类型参数,类型参数需要放在修饰符后面、返回值类型前面。一旦定义了类型参数,就可以在参数列表、方法体和返回值类型中使用了。

和泛型类相比,泛型方法在调用时不需要指出明确的参数类型,会根据出入的参数编译器自动识别,和普通方法调用是一致的。

泛型接口

public class Demo {
    public static void main(String arsg[]) {
        Info<String> obj = new InfoImp<String>("www.weixueyuan.net");
        System.out.println("Length Of String: " + obj.getVar().length());
    }
}

//定义泛型接口
interface Info<T> {
    public T getVar();
}

//实现接口
class InfoImp<T> implements Info<T> {
    private T var;

    // 定义泛型构造方法
    public InfoImp(T var) {
        this.setVar(var);
    }

    public void setVar(T var) {
        this.var = var;
    }

    public T getVar() {
        return this.var;
    }
}

通配符

通配符的出现是为了指定泛型中的类型范围

  • <?>被称作无限定的通配符。
  • <? extends T>表示该通配符所代表的类型是 T 类型及其子类。
  • <? super T>表示该通配符所代表的类型是 T 类型及其父类。

类型擦除

Java中的泛型基本上都是在编译器这个层次来实现的。在生成的Java字节码中是不包含泛型中的类型信息的。使用泛型的时候加上的类型参数,会在编译器在编译的时候去掉。这个过程就称为类型擦除。

(7条消息) Java 泛型,你了解类型擦除吗?_frank 的专栏-CSDN博客_java 泛型擦除

例如: 
List<String> l1 = new ArrayList<String>();
List<Integer> l2 = new ArrayList<Integer>();
		
System.out.println(l1.getClass() == l2.getClass());

结果:true List.class

​ List和List在编译阶段 泛型类型String和Integer都被擦除掉,但是到了JVM中字节码文件中它们都变成了原始类型List,通过反射获取运行时期的信息时,它们是一样的因此是true。

补充:JVM并不知道泛型,所有 泛型在编译阶段就已经被处理成了普通类和方法,JVM中没有泛型,只有普通类和方法。

JVM如何获取具体的类型呢?

答案就是强大 反射机制

String和Integer怎么办?

答案:泛型转义

public class Erasure <T>{
	T object;

	public Erasure(T object) {
		this.object = object;
	}
	
}

定义一个Erasure泛型类,同反射看看它在运行时信息

Erasure<String> erasure = new Erasure<String>("hello");
Class eclz = erasure.getClass();
System.out.println("erasure class is:"+eclz.getName());

//结果:erasure class is: com.frank.test.Erasure

通过Class对象发现并不是**Erasure**的泛型类,只是普通类Erasure

Field[] fs = eclz.getDeclaredFields();
for ( Field f:fs) {
	System.out.println("Field name "+f.getName()+" type:"+f.getType().getName());
}
//结果:Field name object type:java.lang.Object

原本定义的类型参数T被擦除后,对应类型变成了Object类型(顶级父类),是不是所有类型参数都会转换成Object类型呢?

答案是否定的

配上通配符,指定类型参数的范围

//String类型的子类
public class Erasure <T extends String>{
//	public class Erasure <T>{
	T object;

	public Erasure(T object) {
		this.object = object;
	}
	
}

再来看看对应T擦除后的类型

Field name object type:java.lang.String

结论:在泛型类被类型擦除的时候,之前泛型类中的类型参数部分如果没有指定上限,如 <T>则会被转译成普通的 Object 类型,如果指定了上限如 <T extends String>则类型参数就被替换成类型上限。

增加一个add()泛型方法

public class Erasure <T>{
	T object;

	public Erasure(T object) {
		this.object = object;
	}
	
	public void add(T object){
		
	}
	
}

add方法中类型参数没有指定具体类型,最后应该是被转义成Object类型。

Erasure<String> erasure = new Erasure<String>("hello");
Class eclz = erasure.getClass();
System.out.println("erasure class is:"+eclz.getName());

Method[] methods = eclz.getDeclaredMethods();
for ( Method m:methods ){
	System.out.println(" method:"+m.toString());
}
// method:public void com.frank.test.Erasure.add(java.lang.Object)

如果通过反射来获取add方法,应该调用 getDeclaredMethod("add",Object.class),在泛型类中类型参数T被擦除后,转义成了Object类型。

public class Test2{  
    public static void main(String[] args) {  
        /*
        Number类是java.lang包下的一个抽象类,提供了将包装类型拆箱成基本类型的方法,所有基本类型(数据类型)的包装类型都继承了该抽象类,并且是final声明不可继承改变
        */
        /**不指定泛型的时候*/  
        int i=Test2.add(1, 2); //这两个参数都是Integer,所以T为Integer类型  
        Number f=Test2.add(1, 1.2);//这两个参数一个是Integer,以风格是Float,所以取同一父类的最小级,为Number  
        Object o=Test2.add(1, "asd");//这两个参数一个是Integer,以风格是String,所以取同一父类的最小级,为Object  
  
                /**指定泛型的时候*/  
        int a=Test2.<Integer>add(1, 2);//指定了Integer,所以只能为Integer类型或者其子类  
        int b=Test2.<Integer>add(1, 2.2);//编译错误,指定了Integer,不能为Float  
        Number c=Test2.<Number>add(1, 2.2); //指定为Number,所以可以为Integer和Float  
    }  
      
    //这是一个简单的泛型方法  
    public static <T> T add(T x,T y){  
        return y;  
    }  
}  

类型擦除的基本过程也比较简单,首先是找到用来替换类型参数的具体类。这个具体类一般是 Object。如果指定了类型参数的上界的话,则使用这个上界。把代码中的类型参数都替换成具体的类。

类型擦除的局限性

泛型擦除,是泛型能够与之前的java版本代码兼容共存原因,但也因此抹除了很多继承相关的特性。

通过泛型擦除的原理可以结合反射技术绕过被限制的操作。

例如:List接口,实现类ArrayList

public interface List<E> extends Collection<E>{
	
	 boolean add(E e);
}

通过反射调用add方法查看,会发现add方法中类型参数E已经转义成了Object类型

boolean add(Object obj);

绕过编译器,利用反射,非正常操作

public class ToolTest {


	public static void main(String[] args) {
		List<Integer> ls = new ArrayList<>();
		ls.add(23);
//		ls.add("text");
		try {
			Method method = ls.getClass().getDeclaredMethod("add",Object.class);
			
			
			method.invoke(ls,"test");
			method.invoke(ls,42.9f);
		} catch (NoSuchMethodException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (SecurityException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (IllegalAccessException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (IllegalArgumentException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (InvocationTargetException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
		for ( Object o: ls){
			System.out.println(o);
		}
	
	}

}

/*
23
test
42.9
*/

发现可以在数组中添加字符,不可思议,正常情况下不允许的。


Java序列化

保存对象及其状态到内存或磁盘中

java平台允许在内存中创建可复用的对象,一般情况下,JVM在运行时对象才可能存在,对象的声明周期 <= JVM的生命周期。希望在JVM生命周期结束后,对象也能够存在,并且能够在未来被重新使用。

序列化的对象以Byte数组保存类中静态成员不会被序列化

java对象序列化,将其状态保存为一组字节,在未来,将这一组字节组装成对象(反序列化),对象序列化保存的是对象的状态,即它的成员变量。

序列化用户远程对象传输

除了在持久化对象时会用到对象序列化之外,当使用 RMI(远程方法调用),或在网络中传递对象时,

都会用到对象序列化。

Serializable接口实现对象序列化

java中一个类实现java.io.Serializable 接口,那么它就可以被序列化。

ObjectOutputStream ObjectInputStream 对对象进行序列化及反序列化

序列化 ID(private static final long serialVersionUID)

情境:两个客户端 A 和 B 试图通过网络传递对象数据,A 端将对象 C 序列化为二进制数据再传给 B,B 反序列化得到 C。

问题:C 对象的全类路径假设为 com.inout.Test,在 A 和 B 端都有这么一个类文件,功能代码完全一致。也都实现了 Serializable 接口,但是反序列化时总是提示不成功。

虚拟机是否允许反序列化,不仅取决于类路径和功能代码是否一致,非常重要的一点是两个

类的序列化 ID 是否一致。

序列化并不保存静态变量

序列化子父类说明

要想将父类对象也序列化,就需要让父类也实现 Serializable 接口。

Transient 关键字阻止该变量被序列化到文件中

  • 在变量声明前加上 Transient 关键字,可以阻止该变量被序列化到文件中,在被反序列化后,transient 变量的值被设为初始值,如 int 型的是 0,对象型的是 null。

  • 一定程度保证序列化对象的数据安全。

    服务器端给客户端发送序列化对象数据,对象中有一些数据是敏感的,比如密码字符串等,希望对该密码字段在序列化时,进行加密,而客户端如果拥有解密的密钥,只有在客户端进行反序列化时,才可以对密码进行读取。


Java复制

将一个对象的引用复制给另一个对象,一共有三种方式。

  • 直接赋值
  • 浅拷贝
  • 深拷贝

直接赋值

在 Java 中,A a1 = a2,A为对象,我们需要理解的是这实际上把a2所指向的对象的引用的拷贝赋值给a1,也就是说 a1 和 a2 指向的是同一个对象。因此,当 a1 变化的时候,a2 里面的成员变量也会跟着变化。

浅拷贝(复制引用但不复制引用的对象)

对基本数据类型进⾏值传递,对引⽤类型进⾏引⽤类型的拷⻉。

深拷贝(复制对象和其应用对象)

对基本数据类型进⾏值传递,对引⽤数据类型,创建⼀个新的对象,并复制其内容

image-20210711135323031

序列化(深 clone 一中实现)

在 Java 语言里深复制一个对象,常常可以先使对象实现 Serializable 接口,然后把对象(实际上只是对象的一个拷贝)写到一个流里,再从流里读出来,便可以重建对象。

最后

以上就是幸福黑裤为你收集整理的Java知识总结(二)的全部内容,希望文章能够帮你解决Java知识总结(二)所遇到的程序开发问题。

如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(54)

评论列表共有 0 条评论

立即
投稿
返回
顶部