概述
附上后续,后续持续更新
Java反射
特点:
动态语言
在程序运行期间可以改变其结构,引入新的函数,也可以改变已有函数结构。例如:JavaScript
从反射角度来看:java属于半动态语言。c++、c不是动态语言。
反射机制概念(运行状态中知道类的所有属性和方法)
在java的反射机制中处于运行状态时,对于任意一个类都能够知道它所有的属性和方法,并且对于任意一个对象,都能够调用它的任意一个方法。动态获取信息以及动态调用对象方法的功能。
本质上:从字节码中查找,动态获取类的结构,包括属性、构造器、动态调用对象方法,注意并不是修改类。
反射应用场景
编译时类型和运行时类型
编译时类型由声明的类型决定,运行时类型由实际赋予的对象类型决定。
Person p=new Student();
编译类型:Person 运行类型:Student
//引用类型Person是定义的,new Student()是在运行时创建的对象
当程序处于运行时从外部传入新的对象,该对象的编译时类型为Object,但程序运行时需要调用该对象的运行时类型方法,在编译时无法获取对象属于哪些类时,为了在运行时获取该对象的具体类型的方法,此时就要用到反射。
反射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 里面的成员变量也会跟着变化。
浅拷贝(复制引用但不复制引用的对象)
对基本数据类型进⾏值传递,对引⽤类型进⾏引⽤类型的拷⻉。
深拷贝(复制对象和其应用对象)
对基本数据类型进⾏值传递,对引⽤数据类型,创建⼀个新的对象,并复制其内容
序列化(深 clone 一中实现)
在 Java 语言里深复制一个对象,常常可以先使对象实现 Serializable 接口,然后把对象(实际上只是对象的一个拷贝)写到一个流里,再从流里读出来,便可以重建对象。
最后
以上就是幸福黑裤为你收集整理的Java知识总结(二)的全部内容,希望文章能够帮你解决Java知识总结(二)所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复