一、十天速通Java面试(第三天)
目录
1. ThreadLocal
2. finalize、finalization、finally
2.1 finalize用途
2.2 finally
2.3 finally代码块和finalize()方法区别
3.修饰权限
4.Object
5.equls和==的区别
6.异常
6.1 问题一
6.2 问题二
7.序列化
7.1 定义
7.2 作用
7.3 序列化和反序列化
8.comparable接口和comparator接口
8.1 定义
8.2 区别
9.接口和抽象类
10.Socket
10.1 网络分层
10.2 通信
10.3 UDP通信特点
11.Runtime类
11.1 作用
11.2 实例获取
11.3 设计模式
12.值的传递与引用传递
12.1 值传递
12.2 引用传递
13.泛型中的“T”与“?(通配符)”
14.枚举类Enum
15.Java注解
16.字节流,字符流
16.1 字节流
16.2 字符流
16.3 转换桥梁
16.4 主要区别
17.静态内部类
18.匿名类
1. ThreadLocal
ThreadLocal不是解决对象的共享访问问题,通过ThreadLocal.set()到线程中的对象是该线程自己使用的对象,其他线程是不需要访问的,也访问不到的。各个线程中访问的是不同的对象。
各个线程独立的对象不是通过ThreadLocal.set()创建的,而是线程新New的对象。
ThreadLocal.set()将新建对象的引用保存到线程独有的map中,每当有其他线程对这个引用指向的对象做出修改时,其他线程Thread对象中保存的值也会发生变化。
//ThreadLocal源码
//Thread类中有threadlocalmap对象
public void set(T value){Thread t=Thread.currentThread();ThreadLocalMap map=getMap(t);if(map!=null){map.set(this,value);}else {createMap(t,value);}
}public T get(){Thread t=Thread.currentThread();ThreadLocalMap map=getmap(t);if(map!=null){ return (T)map.get(this);}T value=initialValue();createMap(t,value);return value;
}
当使用ThreadLocal维护变量时,ThreadLocal为每个使用该变量的线程提供独立的变量副本,所以每一个线程都可以独立地改变自己的副本,而不会影响其他线程所对应的副本。
2. finalize、finalization、finally
2.1 finalize用途
(一)垃圾回收器:garbage colector决定回收某对象时,就会运行该对象的finalize()方法--但在Java中很不幸,如果内存总是充足的,垃圾回收可能永远不会执行。
(二)主要用途:回收特殊渠道申请的内存--如:JNI调用non-Java程序,它的工作就是回收这部分内存。
2.2 finally
常用于Try--catch--finally中释放资源,回收内存,因为finally块中的内容一定会被执行。
需要注意:
--在try中return之前会执行finally,如果finally中有return则直接return,值为finally中修改的。
--如果finally中没有return,则执行try中return,数值仍是try中的。
2.3 finally代码块和finalize()方法区别
(一)无论是否抛出异常,finally代码块都会执行,主要用于释放占用内存;
(二)finalize()是Object中的一个protected方法,在对象被垃圾回收之前由Java虚拟机调用的。
3.修饰权限
4.Object
了解常用方法:hashcode()、equals()、toString()、getCalss()、wait()、noitfy()、notifyAll()、finalize()。
5.equls和==的区别
基本数据类型:==进行比较,比的是实际值;
复合数据类型:==进行比较,比的是内存地址;
复合数据类型:equal进行比较:(String、Ingeter、date)等重写了equal比较的是内容,没有重写equal的,比较的还是内存地址
StringBuffer和StringBuilder特殊,==和equal都是比较地址
6.异常
6.1 问题一
Java中的两种异常类型是什么?有什么区别?
运行时异常(不受检查)和编译时异常(受检查)。
运行时异常不必抛出,编译时异常必须修改或抛出。
6.2 问题二
throw和throws有什么区别?
throw关键字用来在程序中明确的抛出异常;
throws语句用来表明方法不能处理的异常。
7.序列化
7.1 定义
将那些实现了Serializable接口的对象转换成一个字节序列,并能够在以后将这个字节序列完全恢复为原来的对象,序列化可以弥补不同操作系统之间的差异。
7.2 作用
Java远程方法调用(RMI);
对JavaBeans进行序列化。
7.3 序列化和反序列化
(一)实现方式:
实现Serializable接口:该接口仅为可序列化的标志,无实际属性和方法。若不添加readObject()和writeObject()方法,采用默认序列化机制;
实现Externalizable接口:能自主控制序列化内容,决定那些属性可被序列化。
(二)反序列化:
实现Serializable接口的对象,反序列化时不调用类的构造方法,完全基于字节操作;
实现Externalizable接口的对象,反序列化时会调用构造方法。
(三)注意事项:
被static修饰的属性不参与序列化;
对象的类名、属性会被序列化,方法不会;
确保序列化对象所在类的属性也可被序列化;
通过网络、文件序列化时,需按写入顺序读取对象;
反序列化必须有对应类的class文件。
(四)常见序列化协议
COM、CORBA、XML&SOAP、JSON、Thirft、Protobuf、Avro。
协议对比:XML序列化性能和简洁性差,Thrift与Protobuf相比,在时空开销方面有劣势;Protobuf和Avro在性能和简洁性等方面表现优越。
8.comparable接口和comparator接口
8.1 定义
comparable接口(自然排序):使用Array或者Collection的排序方法时,自定义类需要实现该接口的compareTo(TOBJ)方法。
comparator接口(比较器排序):可以实现两个对象的特定字段比较(如比较两个员工年龄),该接口的compare(Object o1,Object o2)方法可以传递两个对象。
8.2 区别
自然排序用于单逻辑:比如我一直需要按照年龄从小到大排序。
比较器排序适用于多逻辑:比如我现在需要从小到大排,后面我又需要从大到小排,此时只需要修改传入的比较器对象即可完成这一部分改变。
9.接口和抽象类
10.Socket
Socket(套接字)是在传输层提供的一种抽象,它使得不同主机之间的进程能够进行通信,以下是 Socket 通信原理的详细介绍:
10.1 网络分层
常见的是 TCP/IP 四层模型(应用层、传输层、网络层、数据链路层);
Socket 位于应用层和传输层之间,为应用程序提供了一个访问传输层服务的接口,应用程序通过 Socket 来发送和接收数据,无需关心底层网络协议的复杂细节。
10.2 通信
(一)准备
创建Socket对象:
//创建客户端Socket
Socket socket=new Socket();
绑定端口:
//在服务器端创建监听指定接口:port的sserverSocket
ServerSocket serverSocket=new ServerSocket(port);
监听连接(服务器端):
serverSocket.listen();
(二)建立连接(以TCP为例)
TCP 是一种面向连接的可靠传输协议,建立连接的过程通常被称为三次握手:
第一次握手:客户端向服务器发送一个带有 SYN(同步序列号)标志的 TCP 报文段,该报文段包含客户端的初始序列号(client_isn),表示客户端请求建立连接。
第二次握手:服务器接收到客户端的 SYN 报文后,向客户端发送一个带有 SYN 和 ACK(确认)标志的 TCP 报文段。其中 ACK 标志位用于确认收到客户端的 SYN,确认号为client_isn + 1,同时服务器也发送自己的初始序列号(server_isn) 。
第三次握手:客户端收到服务器的 SYN + ACK 报文后,向服务器发送一个带有 ACK 标志的 TCP 报文,确认号为server_isn + 1,表示客户端确认收到服务器的 SYN + ACK 报文,至此连接建立完成。
(三)数据传输
发送数据:连接建立后,应用程序可以通过 Socket 发送数据。数据会被传递给传输层,传输层根据选择的协议(如 TCP 或 UDP)对数据进行封装。以 TCP 为例,数据会被分割成合适大小的 TCP 报文段,添加 TCP 头部信息(包含源端口、目的端口、序列号、确认号等),然后交给网络层。网络层再添加 IP 头部信息(包含源 IP 地址、目的 IP 地址等),继续向下传递,最终通过物理网络发送出去。
接收数据:当数据到达目标主机时,从物理层开始逐层向上解封装。网络层去掉 IP 头部信息,将数据交给传输层;传输层检查 TCP 头部信息,进行确认、排序等操作后,去掉 TCP 头部信息,将数据交给应用层的 Socket,应用程序就可以从 Socket 中读取接收到的数据。
(四)关闭连接(以TCP为例)
TCP 连接的关闭通常需要四次挥手:
第一次挥手:客户端向服务器发送一个带有 FIN(结束)标志的 TCP 报文段,表示客户端数据发送完毕,请求关闭连接。
第二次挥手:服务器收到客户端的 FIN 报文后,向客户端发送一个带有 ACK 标志的 TCP 报文,确认收到客户端的 FIN,此时客户端到服务器的连接关闭,但服务器到客户端的连接仍然保持。
第三次挥手:服务器处理完剩余数据后,向客户端发送一个带有 FIN 标志的 TCP 报文,表示服务器也准备关闭连接。
第四次挥手:客户端收到服务器的 FIN 报文后,向服务器发送一个带有 ACK 标志的 TCP 报文,确认收到服务器的 FIN,此时服务器到客户端的连接也关闭,整个 TCP 连接完全关闭。
10.3 UDP通信特点
UDP 是一种无连接的不可靠传输协议,与 TCP 不同,它不需要建立连接和进行挥手操作。应用程序直接通过 UDP Socket 发送数据,数据被封装成 UDP 数据报,添加 UDP 头部信息(包含源端口、目的端口、长度等)后交给网络层发送。UDP 不保证数据的可靠传输,可能会出现数据丢失、乱序等情况,但它的传输效率高,适合对实时性要求高、对数据准确性要求相对较低的应用场景,如视频直播、网络游戏等。
11.Runtime类
11.1 作用
Runtime类封装了JVM(Java虚拟机),每个Java程序启动会对应一个JVM进程,每个进程对应一个由JVM实例化的Runtime实例。
11.2 实例获取
由于Runtime类的构造方法被私有化,不能直接实例化,应用通过public static Runtime getRuntime()这个静态方法获取当前Runtime运行时对象的引用。
11.3 设计模式
这种通过静态方法获取唯一实例的方式,符合单例设计模式。获取实例后,可调用Runtime对象的方法控制Java虚拟机的状态和行为。
12.值的传递与引用传递
12.1 值传递
针对基本型变量,传递的是变量的一个副本,修改副本不会对原变量产生影响。
12.2 引用传递
通常针对对象型变量,传递的是对象地址的一个副本,并非原对象本身。
一般观点认为,Java 里的传递都属于值传递,但 Java 中实例对象的传递是引用传递。
13.泛型中的“T”与“?(通配符)”
T(类型参数):在使用泛型方法(如:show1方法)时,T是一个具体的类型占位符,调用方法时会确定T代表的实际类型。
如List<Student> list1,调用此方法T就为Student类型,若添加其他类型就会报错。
?(通配符):表示集合可以是任意类型的泛型集合。
14.枚举类Enum
定义的枚举类(如Day)会被编译为一个继承Enum类的最终类(final class),这限制了枚举类不能被继承。
编译器会为枚举类自动添加一些方法:
- value()方法:返回包含所有枚举实例的数组,方便遍历枚举值,其内部是返回枚举实例数组的克隆。
- valueOf(String s)方法:根据字符串参数获取对应的枚举实例,内部间接调用了Enum类的valueOf方法。
枚举类有私有构造函数,确保枚举类只能在类内部创建。
枚举类中定义的每个枚举实例(如:MONDAY,TUESDAY等),都会被编译成public static final修饰的枚举类类型变量,且在静态代码块中完成这些枚举实例的初始化,同时还会创建一个包含所有枚举实例的数组(如$VALUES),用于支持values()方法等操作。
15.Java注解
@Override:用于标明方法覆盖了父类的方法(重写)。
@Deprecated:用于标明已过时的方法或类--传递废弃信息--保持旧代码兼容,平滑过渡。
@SuppressWarnings:用于有选择地关闭编译器对类、方法、成员变量、变量初始化等的警告。其作用目标涵盖类型、字段、方法、参数、构造方法、局部变量等,保留策略为源码级别,且有String[] value()方法,用于指定要抑制的警告类型。
16.字节流,字符流
16.1 字节流
基类:InputStream(输入)和OutputStream(输出);
文件:FileInputStream(文件字节输入)和FileOutputStream(文件字节输出);
数组:ByteArrayInputStream(字节数组输入)和ByteArrayOutputStream(字节数组输出);
操作时不使用缓冲区,直接与文件交互,若不关闭资源(close方法),文件也能输出。
16.2 字符流
基类:Reader(输入)和Writer(输出);
缓冲:BufferedReader(带缓冲的字符输入)和BufferedWriter(带缓冲字符输出);
文件:FileReader(文件字符输入)和FileWriter(文件字符输出);
用于处理字符、字符数组或字符串,操作时使用缓冲区,若不使用close方法且不强制刷新(flush方法),则不会输出内容。
16.3 转换桥梁
InputStreamReader类是字节流到字符流的桥梁,能读入字节并根据指定编码转换为字符流。
16.4 主要区别
(一)缓冲区使用:字节流操作不使用缓冲区,字符流使用缓冲区。
(二)资源关闭与输出:字节流不关闭资源也可能输出,字符流不关闭且不刷新则无输出。
(三)读取返回值范围:Reader的read()方法返回0-65535范围的字符(占2字节),InputStream的read()方法返回0-255范围的字节,对于像汉字这类不能用0-255表示的值,需用字符流读取。
(四)处理对象:字节流处理字节、字节数组或二进制对象;字符流处理字符、字符数组或字符串。
17.静态内部类
// 外部类
class Outer {// 外部类静态成员static int staticVar = 10;// 外部类非静态成员int nonStaticVar = 20;// 静态内部类(嵌套类)static class Inner {// 静态内部类静态成员static int innerStaticVar = 30;// 静态内部类非静态成员int innerNonStaticVar = 40;// 静态内部类方法void innerMethod() {// 可访问外部类静态成员System.out.println("外部类静态变量:" + staticVar);// 不可访问外部类非静态成员(编译报错)// System.out.println("外部类非静态变量:" + nonStaticVar);// 访问自身静态和非静态成员System.out.println("内部类静态变量:" + innerStaticVar);System.out.println("内部类非静态变量:" + innerNonStaticVar);}}// 外部类方法void outerMethod() {// 访问静态内部类静态成员(直接通过内部类访问)System.out.println("内部类静态变量:" + Inner.innerStaticVar);// 访问静态内部类非静态成员(需实例化内部类)Inner inner = new Inner();System.out.println("内部类非静态变量:" + inner.innerNonStaticVar);}
}// 测试类
public class Test {public static void main(String[] args) {// 实例化静态内部类(无需外部类实例)Outer.Inner inner = new Outer.Inner();inner.innerMethod();// 实例化外部类并调用其方法Outer outer = new Outer();outer.outerMethod();}
}
代码展示了Java中静态内部类的使用规则:
定义与实例化:静态内部类被声明为static,可独立于外部类实例化,无需依赖外部类对象。普通内部类需外部类实例化后才能实例化,而静态内部类可直接通过外部类.内部类方式实例化。
成员访问规则:
静态内部类内部:不能访问外部类的非静态成员(变量和方法),但可访问外部类的静态成员;自身可定义静态或非静态成员。
外部类访问静态内部类成员:不能直接访问,需通过静态内部类的实例来访问其非静态成员,可直接通过内部类.静态成员访问其静态成员
与非静态内部类的区别:
静态内部类可拥有静态成员,非静态内部类不能有静态成员。
静态内部类的非静态成员可访问外部类静态变量,不可访问外部类非静态变量;非静态内部类的非静态成员可访问外部类非静态变量。
非静态内部类可随意访问外部类成员(包括私有成员),静态内部类对象无法访问外部类非静态成员。
作用域:
静态内部类(如Person类)仅在其外部类(如StaticTest类)范围可见,其他类中引用或初始化会报错。
18.匿名类
基本定义:没有名字的内部类。
成员与方法限制:不能定义任何静态成员、方法;其中的方法不能是抽象的。
实现要求:必须实现接口或抽象父类的所有抽象方法,且因为创建时会立即实例化,所以不能是抽象类。
继承与实现:会继承一个父类(有且只有一个)或实现一个接口(有且只有一个),可实现父类或接口中所有抽象方法,也能改写父类方法、添加自定义方法。
外部类成员访问:访问的外部类成员变量或成员方法必须用static修饰
构造器与同名成员:因为没有类名,不能定义构造器;当与外部类有同名变量(方法)时,默认访问匿名内部类的,要访问外部类的需加上外部类的类名。