感谢牛客

前来还愿!!祝愿大家都能拿到自己心仪的offer。但行好事莫问前程。
放一些我的总结,如果需要到时候再继续放。


**1,接口和抽象类区别?**
1)区别:1接口成员变量只能是public static final。
2接口成员方法只能是public abstract没有实现,不能有static方法块和方法
3接口可以继承多个接口
2)怎么用?抽象类是什么,接口能做什么。抽象类固定属性,接口扩展功能。公共代码要考虑抽象类,可以继承下来方法体。
3)为什么static?因为可以多实现,那么有同样的变量,不就会报错。
为什么是final?接口遵循开闭原则,对修改关闭,对扩展开放。

**2,重载和重写??**
1.重载:不同的参数个数或者类型的同名函数(返回值类型可随意,访问修饰符随意、抛出异常随意),是一种多态的表现。
2.重写:父类与子类之间的多态性,实质是对父类的函数进行重新定义。参数列表必须完全与被重写的方法相同,返回类型必须与被重写的方法相同。子类函数的访问修饰权限>=父类。异常范围子类要小于父类。要调用父类用super.方法。
3.重载与重写是Java多态性的不同表现:
重载是一个类中多态性的表现,在编译时起作用(静态多态性,譬如实现静态绑定)
重写是父类与子类之间多态性的表现,在运行时起作用(动态多态性,譬如实现动态绑定)
4.①父类静态代码块>>②子类静态代码块>>③父类非静态代码块>>④父类构造函数>>⑤子类非静态代码块>>⑥子类构造函数

**3,乱码,java汉字占几个字节,**。
程序运行的底层设备只接受字节数据,但人需要字符。所以需要编码转换。
乱码:解码方式和编码方式不一致。会以不同的字节数长度去解码。
一个中文字符以utf-8编码会转成3个字节,如果以gbk编码会转成2个字节。iso-8859-1、utf-8、gbk中一个字节码表示一个英文字符。
String str = "你好";
byte[] bs = str.getBytes("iso-8859-1")//由于只能存英文,会把读不懂的字符用?存下来。
char占2个字节。汉字占几个字节是由编码来定的,java使用unicode编码,英文汉字都是占2个字节。其他编码就不一定了。


**4,String、StringBuffer、StringBuilder??**
String a = “aa”; //"aa"放在常量池
String b = new String(“aa”);//在堆中创建对象
String d=a.inern()a的内容在常量池,会直接返回常量池的地址。
String d=b.inern()b的内容在常量池没有,
String.intern()方法:string常量池没有,堆中有,会在常量池开辟空间,指向堆中,并且返回常量池的地址。哪都没有才在常量池创建。所以:常量池存引用,堆存数据。
jdk1.7之前:会在常量池子重新创建对象(把c的值复制到常量池,再返回常量池的地址)
2)关于+ ???
1.s = "abc" + i;//编译以后是下面这样的。
s = new StringBuilder(s).append(i).toString();
所以在for循环里面不停的+,那么会创建和销毁很多对象。每次都是先创建+前后两个对象,然后再创建合起来的对象,然后再回收。
2.当"+"两端均为字符串常量时,如string s=“a”+“b”、string s=两个final string相加。在编译时就会被合起来“ab”。
final string str="aa"有点像宏替换,str的地方都直接被替换常量。如果final string str=getstr()从方法获取值,不会宏替换。就算里面是final,依旧还是创建对象。
2)string为什么不可变,不可继承??
public final class String implements Serializable, Comparable<String>, CharSequence {
private final char value[];
1由于类是final,所以不能被继承。
2char数组是final,表明指向的内容不能变,但是数组的内容可以变。其实保证不可变的是因为private,因为private,string对象的内容不能被访问到。。。
3)StringBuffer和StringBuilder,直接在对象本身进行操作,不会产生新的对象,所以性能优于string。
1.StringBuffer是线程安全的,StringBuffer类中的方法都添加了synchronized关键字,给这方法添加了锁用来保证线程安全。
2.StringBuilder则不是线程安全的,所以性能高。
方法内new StringBuilder并且对他操作,那肯定是线程安全的咯!!
因为每个线程调用方法,都会自己在栈中创建,相当于线程私有!!!

**4,==和equals区别**
1)==
8大基本数据类型:(byte 1,short 2,int 4,long 8,float 4 ,double 8,char 2,boolean 1位),”==”比较的是两个变量的值是否相等。
对于引用类型如string,比较的是该变量是否所指向相同的地址。
2)equals
本来:if(this==anObject)。
原因:String类型对equals方法进行了重写:逐个字符进行比较。

**5,int和integer区别??**
1int基本数据类型,Integer是int包装类。
2Integer是指向一个对象的,默认值null。int直接存储值,默认值是0。
深入:
2)使用==比较:
1Integer变量和int变量比较时,Integer会自动拆箱为int,比较两者的值。
2Integer变量和Integer变量比较时,比的是地址。new出来看到不会相等。
Integer i=129 ;//可能会相等。
源码:Integer i=Integer.valueOf(129);
valueOf(int i) 方法中如果值在-128到127,会返回int缓存的地址,所以可能相等。
3)问:为什么有了int还要Integer
1对象封装可以把属性和处理这些数据的方法封装在一起。
2Java中绝大部分方法或类都是用来处理类类型对象的,如ArrayList集合类就只能以类作为他的存储对象。


**6,java四大引用类型**
1.强引用:java默认new 对象则为强引用
2.软引用:用来描述一些非必须,但仍有用的对象。内存足够时,软引用对象不会被回收,只有在内存不足时,系统会回收软引用对象。
Drawable drawable = new BitmapDrawable(bitmap);
SoftReference<Drawable> soft = new SoftReference<Drawable>(drawable);
if(soft!=null){
view.setImageResource(soft.get())
}// 去引用大图片
3.弱引用:只要JVM开始进行垃圾回收,那些被弱引用关联的对象都会被回收。
String str = new String("abc");
WeakReference<String> abcWeakRef = new WeakReference<String>(str);
4.虚引用:如果一个对象仅持有虚引用,那么它就和没有任何引用一样,它随时可能会被回收。PhantomReference<T>中的get返回null。
虚引用必须要和 ReferenceQueue 引用队列一起使用。唯一被回收时收到一个通知。


**6,hash算法?**
1)hash算法:指纹就所谓的Hash算法,可以用来唯一可靠标识资源。检查数据是否被篡改。HASH的设计以无法解密。
优秀的hash算法:(不同场景不同侧重)
正向快速:明文+hahs算法,很快时间得到hash值。
逆向困难:hash值不能推出明文
输入敏感:原始输入信息修改一点信息,hash就很不同
冲突避免:两段明文的hash一致很难。
2)java的超类java.lang.Object:有public boolean equals(Object obj)、public int hashCode()两个方法。
JDK8默认hashCode的计算方法:通过和当前线程有关的一个随机数+三个确定值。和对象内存地址无关。
JVM启动参数中添加-XX:hashCode=4,改变默认的hashCode计算方式。
hashCode==0:返回一个伪随机数生成器生成的随机数。Jdk7的默认实现
hashCode==1:将对象的内存地址,做移位运算后与一个随机数进行异或得到结果
hashCode==2:返回固定的1
hashCode==3:返回一个自增序列的当前值
hashCode==4:返回当前对象的内存地址
else 就是默认方案,上面说的。
2)hash算法的实现:
hash进行数据管理追求:优先速度和均匀分布。
乘加迭代运算:h = 31 * h + val[i];//h=hash,i每一个char。
hash算法的实现:异或+加法,移位+求模。(a + 2 + (a << 1)) % 8 ^ 5
3)流行的算法有md5、Sha1
md5算法:
1.处理原文。原文长度(bit)对 512 求余的结果,如果不等于 448,就需要填充原文使得原文对 512 求余的结果等于 448。填充的方法是第一位填充 1,其余位填充 0。填充完后,信息的长度就是 512*N+448。
之后,用剩余的位置(512-448=64 位)记录原文的真正长度,把长度的二进制值补在最后。这样处理后的信息长度就是 512*(N+1)。
2.设置初始值。MD5 的哈希结果长度为 128 位,按每 32 位分成一组共 4 组。这 4 组结果是由 4 个初始值 A、B、C、D 经过不断演变得到。
3.循环加工。假设处理后的原文长度是 M
主循环次数 = M / 512,取反、或、异或、
4.拼接结果。

**7,如何写一个不可变类?**
1类声明为final,不可继承
2将所有成员声明为private,不允许直接访问
3将所有可变成员设为final,只能赋值一次
4对变量不提供set方法
5构造函数,进行深拷贝。(有成员指向一个对象,那个对象不能被改变)
6getter方法,不会返回对象本身,而是克隆对象。
2)final修饰:修饰变量数据PI、修饰对象引用(不能改变指向,对象可变)、修饰形参、修饰方法(不能被继承修改)、修饰类(不能被继承)



**8,浅拷贝和深拷贝?**
Java数据类型:基本数据类型、引用类型
1)浅拷贝:如果属性是基本类型,拷贝的就是基本类型的值;如果属性是引用类型,拷贝的就是内存地址,因此如果其中一个对象改变了这个地址,就会影响到另一个对象。
实现:类implements Cloneable,
public Object clone() {//重写clone方法
return super.clone();}
Student studentB = (Student) studentA.clone();
ps:对象拷贝:直接对象指针赋值
2)深拷贝
对于引用类型,比如数组或者类对象,深拷贝会新建一个对象空间,然后拷贝里面的内容,所以它们指向了不同的内存空间。改变其中一个,不会对另外一个也产生影响。
实现:对于有多层对象的,每个对象都需要实现 Cloneable并重写 clone() 方法,进而实现了对象的串行层层拷贝。
public Object clone(){//类实现Cloneable接口,重写clone()
Student student=(Student) super.clone();
student.subject=(Subject)subject.clone();//成员变量,引用类型,该类也重写clone方法。
return student;
}
Student studentB = (Student) studentA.clone();引用成员变量不再受影响。

**9,内部类、匿名内部类**
1)普通内部类
内部类可以调用外部类的方法。如果方法同名:外类名.this.方法
内部类可以访问外部类的任何成员变量,因为内部类相当于外部类类的成员变量的位置。
OuterClass为外部类。new OuterClass().new Innerclass();
实例化后外部类能访问内部类,反过来一直可以。
2)静态内部类
访问外部类非静态变量,需要先把外部类new出来。访问外部同名静态变量只要外部类名.变量就可以。不同名直接访问。
创建静态内部类的对象时,不需要外部类的对象,可以直接创建。相当于访问类中的静态成员变量。
3)方法内部类
方法内部类只在该方法内可以用。
方法内部类不能使用访问控制符和static修饰符。因为没必要,只能在方法中用。
访问局部变量(所在方法中的变量)必须是final,不然方法执行完就没了,1.8以后没这个要求。
内部类实例化:不能在静态方法里去new非静态的内部类。这和静态方法访问非静态变量一个道理。
ps:每个内部类都能独立地继承自一个(接口的)实现,所以无论外层类是否已经继承了某个(接口的)实现,对于内部类都没有影响。

2)匿名类
匿名内部类没有类名,因此就不可能有构造函数,不能创建对象。所以
匿名内部类前提条件:必须继承一个父类或实现一个接口,且只能做一样。
匿名内部类是写在方法中的。
匿名内部类可以有实例初始化块。
interface Person {public void speak();}
class的某个方法里{//匿名类
Person per = new Person() {//person是个抽象类或者接口
@Override
public void speak() {// 实现接口的的方法speak
System.out.println("speak方法调用");}
public void say() {// 匿名内部类自定义的方法say
System.out.println("say方法调用");}(.say()//除非这样)
}//per.say()不能被调用,废话父类指针。
作用:1封装性,隐藏你不想让别人知道的操作。2一个内部类对象可以访问创建它的外部类对象的内容,甚至包括私有变量。3多继续的效果。万一你想实现一个接口,他的方法和你写的一摸一样,那么可以用内部类。
4.调用一个类的protect方法,new 某类{
void callm(){ super.callm}
}.callm。很强吧。//protected修饰的成员或方法,对于本包和其子类可见


**10,错误和异常**
throwable子类:Error、Exception
Exception:runtimeException、nonruntimeException
1)Error:是程序无法处理的错误,代码运行时Java虚拟机出现的问题。如虚拟机错误、线程死锁、内存溢出
2)Exception:是程序本身可以处理的异常。
1.运行时异常:都是RuntimeException类及其子类异常,如NullPointerException(空指针异常)、IndexOutOfBoundsException(下标越界异常)等。这些异常一般是由程序逻辑错误引起的。运行时异常的特点是Java编译器不会检查它,当程序中可能出现这类异常,即使没有用try-catch语句捕获它,也没有用throws子句声明抛出它,也会编译通过。
2.非运行时异常 (编译异常):是RuntimeException以外的异常。从程序语法角度讲是必须进行处理的异常,如果不处理,程序就不能编译通过。如IOException、SQLException等。
3)异常的处理机制:异常抛出、异常捕获。
1异常抛出:当一个方法出现异常,方法创建异常对象并交给运行时系统,异常对象中包含了异常类型和异常信息,运行时系统负责找处理异常的代码并执行。我们也可以手动throw new Exception(“aaa”);
2捕获异常:系统从发生异常的方法开始,依次回查调用栈中的方法,直至找到含有合适异常处理器的方法并执行。当运行时系统遍历调用栈而未找到合适的异常处理器,则系统终止。异常处理器是异常发生时依次存留在调用栈中的方法的集合。
捕捉异常通过try-catch语句或者try-catch-finally语句实现。
1.错误:error永远不允许被发生,所以不抛出不捕获。
2.运行时异常:运行时异常将由Java运行时系统自动抛出,允许应用程序编码时忽略运行时异常。
3.可查异常(编译异常):一个方法必须捕捉,或者声明抛出方法之外
try-catch-finally:try异常后的代码不执行、catch和finally会被执行,可以由多个catch范围从子到父从小到大。//finally的return的值。
throw:抛出异常,throw new exception(“aaa”);
throws excption:在方法后面,声明要抛出什么异常。


**11,Linux IO**
同步:阻塞、非阻塞都是同步的。
异步:只有使用了特殊的API才是异步IO,如linux的AIO
阻塞和非阻塞的区别:调用之后是否立即返回。描述的是调用者调用方法后的状态,比如:线程A调用了B方法,A线程处于阻塞状态。
异步与同步的区别:主动与被动的通知方式。调用者调用方法,被调用方(方***通过回调通知结果则为异步。不能被通知需要等待则为同步。

在Linux下的操作对象是利用文件描述符来实现的。
在UNIX可以归纳成5种I/O模型:(信号驱动I/O不讲)
IO操作的两个阶段:
阶段1:数据被拷贝到操作系统内核的缓冲区(内核)需要一个过程(阻塞非阻塞:可否立即返回)
阶段2:用户调用read从内核拿到数据。(异步同步:自己调还是通知)

1.阻塞I/O:用户进程请求后进行阶段1、2。直到成功或者发生异常才会返回,调用到返回这整段时间内是被阻塞的。
2.非阻塞I/O:用户进行轮询请求数据,没有数据则马上返回,直到数据到达(阶段1完成),则用户进程调用read。只阻塞了阶段2的时间。
3.I/O多路复用:(阻塞io需要多线程才能这样)
定义:让单个进程可以监视多个文件描述符,一旦某个描述符就绪(一般是读就绪或写就绪),能够通知程序再进行请求(这里是请求)。
过程:1select poll epoll请求数据,在阶段1被阻塞(不会立即返回),当某个socket有数据到达了就通知用户进程
2用户进程调用read操作,将数据从kernel拷贝到用户内存,在阶段2被阻塞
1)select、poll==>时间复杂度O(n)
select和poll后阻塞,在有数据到达才返回后才遍历文件描述符来获取已经就绪的socket,
轮询所有流,找出能读出数据,或者写入数据的流,对他们进行操作。
差别:poll没有最大连接数的限制,原因是它是基于链表来存储的.
2)epoll==>时间复杂度O(1)
epoll基于事件驱动的I/O方式取事件的时候。  事件通知方式:每当fd就绪,系统注册的回调函数就会被调用,将就绪fd放到readyList里面。所以只需要遍历Ready队列的描述符集合就可以了。调用的所有事件都是有用的。
进化:通过监听回调的机制,不再需要遍历文件描述符。
select,poll,epoll都是IO多路复用的机制、本质上都是同步I/O。
4.异步io
用户进程发起read操作后立即返回去做其他事,并且kernel收到asynchronous read后也立刻返回。在数据准备完成后,kernel将数据拷贝到用户内存,并发送给用户signal


**11,java io:BIO、NIO、AIO???**
Java语言对操作系统的各种IO模型的封装。java io流分为:字符流和字节流。
1)BIO(blocking io)??
同步阻塞:数据的读取写入必须阻塞在一个线程内等待其完成。
1传统bio:聊天室,为每个客户端连接请求创建一个新的线程处理,输入流接受用户的数据、业务处理、通过输出流返回给全部客户端,线程销毁。
2伪异步bio:通过线程池来对任务进行处理。同时线程池可以设置消息队列和线程数,来防止请求过多线程过多。改进点:支持多客户端连接。
缺点:每个请求对应一个线程,所以并发量大时,需要创建大量线程。
server端建立连接后,如果没有数据可读会阻塞inputStream.read(byte)
2)NIO???
同步非阻塞:客户端发送请求注册到多路复用器上,多路复用器轮询io处理请求
IO是面向流(一个个字节流)的处理,NIO是面向块(面向缓冲区)的处理。
传统io是单向的(阻塞io),nio是双向的(非阻塞、阻塞的)。
1.NIO三个核心组件:
Selector<-Channel<-Buffer<-客户端
Selector选择器:一个选择器为单个线程可以监控多个通道。实现多路复用。
Channel通道:不与数据打交道。双向可读可写。注册到选择器
Buffer数据:直接读写缓冲区。传统io基于流进行操作。

selector对应一个线程,对应者多个channel
每个channel都会对应一个Buffer
selector会根据不同的事件Event,在各个channel切换。
buffer是一个内存块,底层有一个数组
Buffer是双向的,数据读写都是通过Buffer,通过flip切换。
channel是双向的。


Buffer的介绍:
1.Capacity容量:缓冲区的大小,创建后不能改变。(始终指向末尾)
2.Limit界限:写时:写上限(指向Capacity)。读时:读上限(指向postion,已写数据的最后一位)
3.Position位置:写时:指向可开始写的位置。读时:指向可开始读位置
4.Mark标记:可将当前position的前一个位置记录下来,当调用reset的时候position会恢复mark记录下来的值
5.flip()变成读模式。clear()变成写模式(读完了就clear呗)
6.compact():未读完的放到最开始的位置,postion指向未读的后一个位置,准备开始写。

2.NIO用于文件io
1使用FileChannel+ByteBuffer
读文件:创建文件输入通道、创建buffer、把通道的内容读入到buffer
写文件:创建文件输出通道、往通道里写buffer
2通道传数据给通道,不需要buffer
fin.transferTo(0, fin.size(), fout);
3内存映射文件的方式:通道.map(),将文件直接映射到内存中创建。然后就可以直接读了。
MappedByteBuffer b=inchannel.map(MapMode.Read_ONLY,0,inchannel.size)
直接与非直接缓冲区
非直接缓冲区是需要经过一个:copy的阶段的(从内核空间copy到用户空间)。磁盘->内核地址空间->用户地址空间->应用
直接缓冲区不需要经过copy阶段--->内存映射文件(内核地址空间和用户地址空间都映射到一个物理内存)(方式2,3)。

3.NIO的网络通信(重点使用的地方)
channel状态:connect、accept、read、write
NIO步骤:
channel是设置是非阻塞、和阻塞。
1channel可以注册给selector,并指定关心的状态,selector监控多条channel。
2当感兴趣的事件就绪时,则会进去我们处理的方法进行处理
3每处理完一次就绪事件,删除该选择键
以上3种io都是同步操作。BIO是同步阻塞,NIO是同步非阻塞IO,实现多路复用。

3)AIO(又叫NIO2)
异步非阻塞:基于事件和回调机制实现的,客户端请求后直接返回不阻塞。由服务端操作系统检验请求是否有效,再通知服务端启动线程去处理,将结果异步返回给客户端。proactor模式。
异步通道获取结果方式:
1.client.read(buffer,new CompletionHandler<Integer, Void>(){实现override completed,failed两个方法。
操作完成后回调CompletionHandler接口的方法得到结果。
2.Future表示异步操作的结果:Future<Integer> readResult = clientChannel.read(byteBuffer);

Bio:连接数目少且固定,服务器资源多
Nio:连接数目多,时间短
Aio:连接数目多,时间长,处理时间需要较多,如图片下载

4.netty
是什么?高性能、异步事件驱动的NIO框架。
Reactor单线程模型:所有的IO操作都在同一个NIO线程上面完成
Rector多线程模型:一个NIO线程Acceptor线程用于监听服务端,一组NIO线程(线程池)处理IO操作。
主从Reactor线程模型:一个独立的NIO线程池用于接收客户端连接,Acceptor接收到连接后接入认证等,将新创建的SocketChannel注册到IO线程池(sub reactor线程池)的某个IO线程上,由它负责SocketChannel的读写和编解码工作。

**12,反射**
1)反射是什么呢?指在 Java 程序运行时
给定的一个类对象,通过反射获取这个类对象的所有成员结构。
给定的一个具体的对象,能够动态地调用它的方法及对任意属性值进行获取和赋值。
这种动态获取类的内容,创建对象、以及动态调用对象的方法及操作属性的机制为反射。即使该对象的类型在编译期间是未知,该类的 .class 文件不存在,也可以通过反射直接创建对象。
2)例子
程序在运行时,需要动态的加载一些类这些类之前用不到所以没有加载到jvm,而在运行时根据需要才加载,例如用mysql或者oracle,需要动态地根据实际情况加载驱动类,这个时候用了反射,假设com.java.dbtest.myqlConnection,com.java.dbtest.oracleConnection这两个类我们要用,这时候我们的程序就写得比较动态化,通过Class tc = Class.forName("com.java.dbtest.TestConnection");通过类的全类名让jvm在服务器中找到并加载这个类,而如果是oracle则传入的参数就变成另一个了。这时候就可以看到反射的好处了,这个动态性就体现出java的特性了! 加载到方法区。

利用反射机制可以获取 类对象:
1.Class.forName(具体的类名) //从源码获取
2.类名.class()   //从字节码获取(方法区)
3.对象名.getClass()  //从对象获取(堆)

创建对象、调用方法、访问成员变量:
User u = new User("小巴",18);
Method md = 类对象.getMethod("类中的公有方法名");
Method lm = 类对象.getMethod("有参方法方法名",参数的类对象...);
Method dm = 类对象.getDeclaredMethod("类中的私有方法名");
Object obj1= md.invoke(u2);
Object obj2 = lm.invoke(u2,"老赵","aixiaoba");
dm.setAccessible(true); //对于private方法
Object obj3 = dm.invoke(u2);

Class clz = Class.forName("cn.itcast_01.User");
Object obj = clz.newInstance();
Field nf = 类对象.getField("成员变量名");
Field af = 类对象.getDeclaredField("私有成员变量名");
af.setAccessible(true);//私有变量
nf.set(obj, "小巴")
Object obj = af.get(u);
当然也可以getConstructors、getDeclaredConstructor获取构造方法

3)private和emus
private并不是解决安全问题的。private的意义是面向对象编程的封装概念。
Constructor的newInstance方法中如果是枚举类型会抛出异常,所以保证了枚举不会被反射实例化。
序列化一个枚举类的对象,获取的时调用的是继承的Enum的valueOf方法T result = enumType.enumConstantDirectory().get(name);根据name去找存入的对象,所以不会生成多个对象。

**12,反射中Class.forName()和ClassLoader.loadClass()的区别**
Class.forName(className,true,classloader);//第2个boolean参数表示类需要初始化。初始化会执行static赋值,static块代码执行
ClassLoader.loadClass(className,false);//第2个 boolean参数表示目标对象不进行链接。
只是将.class文件加载到jvm中,不进行链接和初始化,不会执行static中的内容,只有在newInstance才会去执行static块。


**13,序列化和反序化**
序列化:把对象转换为字节序列的过程称为对象的序列化。
反序列化:把字节序列恢复为对象的过程称为对象的反序列化。
1)为什么序列化?
1把对象的字节序列永久地保存到硬盘上,通常存放在一个文件中。如session
2在网络上传送对象的字节序列。
2)序列化方法
只有实现了Serializable、Externalizable接口的类的对象才能被序列化。Serializable接口的类可以 采用默认的序列化方式 。
Externalizable接口继承自 Serializable接口,实现Externalizable接口的类完全由自身来控制序列化的行为。
serialVersionUID的取值是Java运行时环境根据类的内部细节自动生成的。类的代码作了修改,再重新编译,serialVersionUID也会变。指纹作用。
3)序列化过程
1写入序列化文件头:序列化表识和版本。2写入Stu对象类型标记。3写入Stu对象的类元数据信息。4写入对象的实例数据。
3)序列化和单例
序列化会通过反射调用无参数的构造方法创建一个新的对象。这破坏了对象的单例性。
解决:如果实现了serializable或者externalizable接口的类中包含readResolve则返回true。
需要的单例的对象类中添加:
private Object readResolve() {
return singleton;}//通过反射调用要被反序列化的类的readResolve()
4)序列化不安全
private字段的数据都以明文二进制的形式出现在网络。
解决: 序列化移位和复位、序列数据加密和签名、利用transient
(static和transient字段不能被序列化)

transient关键字的作用是:阻止实例中那些用此关键字修饰的的变量序列化;当对象被反序列化时,被transient修饰的变量值不会被持久化和恢复。transient只能修饰变量,不能修饰类和方法。

**15,泛型**
1.什么是泛型?
泛型:类型参数化。在泛型使用过程中,操作的数据类型被指定为一个参数,这种参数类型可以用在类、接口和方法中,分别被称为泛型类、泛型接口、泛型方法。

特性:泛型类型在逻辑上看以看成是多个不同的类型,实际上都是相同的基本类型。在编译过程中,正确检验泛型结果后,会将泛型的相关信息擦出,并且在对象进入和离开方法的边界处添加类型检查和类型转换的方法。泛型信息不会进入到运行时阶段。


可以解决当具体类型不确定的时候,这个通配符就是 ?  ;当操作类型时,不需要使用类型的具体功能时,只使用Object类中的功能。那么可以用 ? 通配符来表未知类型。


#还愿#
全部评论

相关推荐

1 8 评论
分享
牛客网
牛客企业服务