文章目录
- 反射的优缺点
- BIO、AIO、NIO
- 同步异步概念
- **阻塞与非阻塞**
- **BIO**
- **NIO**
- **AIO**
- 总结
- 设计模式的好处
- 设计模式一定是好用的吗
- Integer.ValueOf和new Integer的区别
- Integer.parseInt(s)与Integer.valueOf(s)的区别
- String是线程安全的吗?
- StringBuffer和StringBuilder的区别
- String、StringBuffer、StringBuilder的性能
- String#equals() 和 Object#equals() 有何区别?
- String不可边的好处
- List<? super T>和List<? extends T>有什么区别?
- Object类里面有哪些常用的方法
- == 和 equals() 的区别
- HashCode和equals
- 为什么要提供hash和equals两种方法?
- Java里面的异常类了解吗?
- JDK JVM JRE的关系区别
- Math类的取整函数
- 双亲委派机制
- 自定义加载器如何写
- 抽象类
- 抽象类和接口的区别
- 抽象类和接口的使用场景
- IO流
- final和finally的区别
- 泛型
反射的优缺点
动态获取程序信息以及动态调用对象的功能称为 Java 语言的反射机制。
- 程序运行时,可以通过反射获得任意一个类的Class对象,并通过这个对象查看这个类的信息;
- 程序运行时,可以通过反射创建任意一个类的实例,并访问该实例的成员;
- 程序运行时,可以通过反射机制生成一个类的动态代理类或动态代理对象。
在运行时判断任意一个对象所属的类; 在运行时构造任意一个类的对象;在运行时判断任意一个类所具有的成员变量和 方法;在运行时调用任意一个对象的方法;生成动态代理。
好处:反射可以让我们的代码更加灵活、为各种框架提供开箱即用的功能提供了便利。
不过,反射让我们在运行时有了分析操作类的能力的同时,也增加了安全问题,比如可以无视泛型参数的安全检查(泛型参数的安全检查发生在编译时)。另外,反射的性能也要稍差点,不过,对于框架来说实际是影响不大的。
BIO、AIO、NIO
同步异步概念
同步和异步关注的是*消息通信机制* (synchronous communication/ asynchronous communication)。同步,就是调用某个东西是,调用方得等待这个调用返回结果才能继续往后执行。异步,和同步相反 当一个异步过程调用发出后,调用者不会立刻得到结果。而是在调用发出后,被调用者通过状态来通知调用者,或通过回调函数处理这个调用。
阻塞与非阻塞
阻塞和非阻塞关注的是程序在等待调用结果(消息,返回值)时的状态. 阻塞调用是指调用结果返回之前,当前线程会被挂起。调用线程只有在得到 结果之后才会返回。 非阻塞调用指在不能立刻得到结果之前,该调用不会阻塞当前线程。
BIO
Java BIO: 同步并阻塞 (传统阻塞型),服务器实现模式为一个连接一个线程,即客户端有连接请求时服务器端就需要启动一个线程进行处理,如果这个连接不作任何事情会造成不必要的线程开销。
BIO问题分析
-
每个请求都需要创建独立的线程,与对应的客户端进行数据处理。
-
当并发数大时,需要 创建大量线程来处理连接 ,系统资源占用较大。
-
连接建立后,如果当前线程暂时没有数据可读,则当前线程会一直阻塞在 Read 操作上,造成线程资源浪费。
在客户端连接数量不高的情况下,是没问题的。但是,当面对十万甚至百万级连接的时候,传统的 BIO 模型是无能为力的。因此,我们需要一种更高效的 I/O 处理模型来应对更高的并发量
NIO
Java NIO: 同步非阻塞 ,服务器实现模式为一个线程处理多个请求(连接),即客户端发送的连接请求会被注册到多路复用器上,多路复用器轮询到有 I/O 请求就会进行处理。具体做法是多路复用器轮询到连接有 I/O 请求时才启动一个线程进行处理。 用户进程也需要时不时的询问 IO 操作是否就绪,这就要求用户进程不停的去询问。
NIO 和 BIO 对比
-
BIO 以流的方式处理数据,而 NIO 以块的方式处理数据,块 I/O 的效率比流 I/O 高很多。
-
BIO 是阻塞的,而 NIO 是非阻塞的。
-
BIO 基于字节流和字符流进行操作,而 NIO 基于 Channel(通道)和 Buffer(缓冲区)进行操作,数据总是从通道读取到缓冲区中,或者从缓冲区写入到通道中
AIO
AIO (异步非阻塞 I/O) 用户进程只需要发起一个 IO 操作然后立即返回,等 IO 操作真正的完成以后,应用程序会得到 IO 操作完成的通知。
总结
BIO、NIO、AIO适用场景分析
-
BIO 方式适用于连接数目比较小且固定的架构,这种方式对服务器资源要求 比较高,并发局限于应用中,JDK1.4 以前的唯一选择,但程序直观简单易理解。
-
NIO 方式适用于连接数目多且连接比较短(轻操作)的架构,比如聊天服务器,并发局限于应用中,编程比较复杂,JDK1.4 开始支持。
-
AIO 方式使用于连接数目多且连接比较长(重操作)的架构,比如相册服务 器,充分调用 OS 参与并发操作,编程比较复杂,JDK7 开始支持。
长连接指建立Socket连接后不管是否使用都保持连接,但安全性较差。
设计模式的好处
- 代码重用性 (即:相同功能的代码,不用多次编写)
- 可读性 (即:编程规范性, 便于其他程序员的阅读和理解)
- 可扩展性 (即:当需要增加新的功能时,非常的方便,称为可维护)
- 可靠性 (即:当我们增加新的功能后,对原来的功能没有影响)
- 使程序呈现高内聚,低耦合的特性
设计模式一定是好用的吗
设计模式是用来解决某一特定问题的通用解决方案
过度的设计可能会导致程序变得复杂
Integer.ValueOf和new Integer的区别
- new Integer() 每次都会新建一个对象;
- Integer.valueOf() :可以将基本类型int转换为包装类型Integer;
- 在-128-127之间会使用缓存池中的对象,多次调用会取得同一个对象的引用。
- 大于128的数首先会新建一个对象再去引用
Integer x = new Integer(100);
Integer y = new Integer(100);
System.out.println(x == y); // false
Integer z = Integer.valueOf(100);
Integer k = Integer.valueOf(100);
System.out.println(z == k); // true
Integer.parseInt(s)与Integer.valueOf(s)的区别
- Integer.parseInt(s)多次解析同一个字符串得到的int基本类型数据是相等的,可以直接通过“==”进行判断是否相等。
- Integer.valueOf(s)多次解析相同的一个字符串时,得到的是Integer类型的对象,得到的对象有时是同一个对象,有时是不同的对象,要根据把s字符串解析的整数值的大小进行决定:如果s字符串对应的整数值在 -128~ 127之间,则解析出的Integer类型的对象是同一个对象;如果s字符串对应的整数值不在-128~127之间,则解析出的Integer类型的对象不是同一个对象。不管对象是否相等,对象中的value值是相等的。
String是线程安全的吗?
String这个类是被final修饰的,String类的值是保存在value数组中的,并且value数组是被private final修饰的,并且String 类没有提供修改这个字符串的方法。
1、private修饰,表明外部的类是访问不到value的,同时子类也访问不到,当然String类不可能有子类,因为类被final修饰了
2、final修饰,表明value的引用是不会被改变的,而value只会在String的构造函数中被初始化,而且并没有其他方法可以修改value数组中的值,保证了value的引用和值都不会发生变化
所以我们说String类是不可变的。
为什么要有常量池?
常量池是为了避免频繁的创建和销毁对象而影响系统性能,其实现了对象的共享。
新版的变化
Java 9 将 String 的底层实现由 char[] 改成了 byte[]
StringBuffer和StringBuilder的区别
StringBuffer
对方法加了同步锁或者对调用的方法加了同步锁,所以是线程安全的。
StringBuilder
并没有对方法进行加同步锁,所以是非线程安全的。
StringBuilder、StringBuffer 在缓冲区上的区别:
StringBuffer 每次获取 toString 都会直接使用缓存区的 toStringCache 值来构造一个字符串。而
StringBuilder 则每次都需要复制一次字符数组,再构造一个字符串。
String、StringBuffer、StringBuilder的性能
每次对 String
类型进行改变的时候,都会生成一个新的 String
对象,然后将指针指向新的 String
对象。
StringBuffer
每次都会对 StringBuffer
对象本身进行操作,而不是生成新的对象并改变对象引用。
相同情况下使用 StringBuilder
相比使用 StringBuffer
仅能获得 10%~15% 左右的性能提升,但却要冒多线程不安全的风险。
String#equals() 和 Object#equals() 有何区别?
String
中的 equals
方法是被重写过的,比较的是 String 字符串的值是否相等。 Object
的 equals
方法是比较的对象的内存地址。
String不可边的好处
-
方便了实现字符串常量池
-
String 经常作为参数,String 不可变性可以保证参数不可变
譬如,数据库的用户名、密码都是以字符串的形式传入来获得数据库的连接,或者在socket编程中,主机名和端口都是以字符串的形式传入。因为字符串是不可变的,所以它的值是不可改变的,否则黑客们可以钻到空子,改变字符串指向的对象的值,造成安全漏洞。
-
保证了线程的安全
-
加快字符串处理速度,因为字符串是不可变的,所以在它创建的时候hashcode就被缓存了,不需要重新计算。这就使得字符串很适合作为Map中的键,字符串的处理速度要快过其它的键对象。这就是HashMap中的键往往都使用字符串。
List<? super T>和List<? extends T>有什么区别?
参考答案
- ? 是类型通配符,
List<?>
可以表示各种泛型List的父类,意思是元素类型未知的List; List<? super T>
用于设定类型通配符的下限,此处 ? 代表一个未知的类型,但它必须是T的父类型;List<? extends T>
用于设定类型通配符的上限,此处 ? 代表一个未知的类型,但它必须是T的子类型。
扩展阅读
在Java的早期设计中,允许把Integer[]数组赋值给Number[]变量,此时如果试图把一个Double对象保存到该Number[]数组中,编译可以通过,但在运行时抛出ArrayStoreException异常。这显然是一种不安全的设计,因此Java在泛型设计时进行了改进,它不再允许把 List<Integer>
对象赋值给 List<Number>
变量。
数组和泛型有所不同,假设Foo是Bar的一个子类型(子类或者子接口),那么Foo[]依然是Bar[]的子类型,但G<Foo>
不是 G<Bar>
的子类型。Foo[]自动向上转型为Bar[]的方式被称为型变,也就是说,Java的数组支持型变,但Java集合并不支持型变。Java泛型的设计原则是,只要代码在编译时没有出现警告,就不会遇到运行时ClassCastException异常。
Object类里面有哪些常用的方法
wait notifyAll notify getClass hashcode equals clone toString
== 和 equals() 的区别
==
对于基本类型和引用类型的作用效果是不同的:
- 对于基本数据类型来说,
==
比较的是值。 - 对于引用数据类型来说,
==
比较的是对象的内存地址。
equals()
不能用于判断基本数据类型的变量,只能用来判断两个对象是否相等。equals()
方法存在于Object
类中,而Object
类是所有类的直接或间接父类,因此所有的类都有equals()
方法。
equals()
方法存在两种使用情况:
- 类没有重写
equals()
方法 :通过equals()
比较该类的两个对象时,等价于通过“==”比较这两个对象,使用的默认是Object
类equals()
方法。 - 类重写了
equals()
方法 :一般我们都重写equals()
方法来比较两个对象中的属性是否相等;若它们的属性相等,则返回 true(即,认为这两个对象相等)。
HashCode和equals
equals如果不重写的话,默认的是“==”,对于引用对象来说比较的是内存地址,在业务中通过比较的是内容是否相等,所以需要重写equals
如果equals相等的话,那么根据java中的规则,对象的hashcode也是相等的
Hashcode方法是返回对象的内存地址映射为hash值,所以如果没有重写hashCode()方法,任何对象的hashCode()方法都是不相等的。
为什么要提供hash和equals两种方法?
这是因为在一些容器(比如 HashMap
、HashSet
)中,有了 hashCode()
之后,判断元素是否在对应容器中的效率会更高(参考添加元素进HastSet
的过程)!
我们在前面也提到了添加元素进HastSet
的过程,如果 HashSet
在对比的时候,同样的 hashCode
有多个对象,它会继续使用 equals()
来判断是否真的相同。也就是说 hashCode
帮助我们大大缩小了查找成本。
Java里面的异常类了解吗?
Exception 和Error都是继承于Throwable 类,在Java中只有Throwable类型的实例才能被程序抛出(throw)或者捕获(catch),它是异常处理机制的基本类型
Error,它表示程序无法处理的错误。例如 Java 虚拟机运行错误(Virtual MachineError
)、虚拟机内存不够错误(OutOfMemoryError
)、类定义错误(NoClassDefFoundError
)等 。会终止JVM的运行
Exception,它表示程序可能捕捉或者程序可以处理的异常。其中异常类 Exception 又分为运行时异常(RuntimeException)和编译时异常。编译时异常就是 ,Java 代码在编译过程中,如果受检查异常没有被 catch
或者throws
关键字处理的话,就没办法通过编译。运行时异常就是Java 代码在编译过程中 ,我们即使不处理不受检查异常也可以正常通过编译。比如:NullPointerException
(空指针错误)、ArrayIndexOutOfBoundsException
(数组越界错误)等
JDK JVM JRE的关系区别
Java 虚拟机(JVM)是运行 Java 字节码的虚拟机。JVM 有针对不同系统的特定实现(Windows,Linux,macOS),目的是使用相同的字节码,它们都会给出相同的结果。字节码和不同系统的 JVM 实现是 Java 语言“一次编译,随处可以运行”的关键所在。
JDK是java开发工具包,[Java Development Kit],包括了Java运行环境(Java Runtime Envirnment),一堆Java工具(javac/java/jdb等)和Java基础的类库(即Java API 包括rt.jar)。
bin:最主要的是编译器(javac.exe)
include:java和JVM交互用的头文件
lib:类库
jre:java运行环境
JRE(Java Runtime Environment,Java运行环境),包含JVM标准实现及Java核心类库。JRE是Java运行环境,并不是一个开发环境,所以没有包含任何开发工具(如编译器和调试器)
- JDK 用于开发,JRE 用于运行java程序 ;如果只是运行Java程序,可以只安装JRE,无序安装JDK。
- JDk包含JRE,JDK 和 JRE 中都包含 JVM。
- JVM 是 java 编程语言的核心并且具有平台独立性。
Math类的取整函数
- ceil的英文意义是天花板,该方法就表示向上取整,Math.ceil(11.3)的结果为12,Math.ceil(-11.6)的结果为-11;
- floor的英文是地板,该方法就表示向下取整,Math.floor(11.6)的结果是11,Math.floor(-11.4)的结果-12;
- 最难掌握的是round方法,他表示“四舍五入”,算法为Math.floor(x+0.5),即将原来的数字加上0.5后再向下取整,所以,Math.round(11.5)的结果是12,Math.round(-11.5)的结果为-11.
双亲委派机制
双亲委派,就是指调用类加载器的 loadClass 方法时,查找类的规则。
(1) 如果一个类加载器收到了类加载的请求,他首先不会自己去尝试加载这个类,而是把这个请求委派给
父类加载器去完成;
(2) 如果父类加载器还存在其父类加载器,则进一步向上委托,依次递归,请求最终到达顶层的启动类加
载器;
(3) 如果父类加载器可以完成类加载任务,就成功返回;如果父加载器无法完成这个加载任务时,子加载
器才会尝试自己去加载。
总结来说:
(1)查看已加载的类,如果没有,就委派上级,在上级查找。
(2)如果在上级中都没有找到,就在自己类加载器中找
(3)如果在上级中找到,就在上级中进行加载
优点:(1)避免类重复加载;(2)保护程序的安全,防止核心 API 被修改(沙箱安全机制);使得 Java 类随着它的类加载器一起具有一种带有优先级的层次关系,从而使得基础类得到统一。
Ps: Java 虚拟机是如何判定两个 Java 类是相同的:
(1) 全名要相同
(2) 加载此类的类加载器要一样
只有两者都相同的情况,才认为两个类是相同的。即便是同样的字节代码, 被不同的类加载器加载之后所得到的类,也是不同的。
双亲委派模型是为了保证 Java 核心库的类型安全。所有 Java 应用都至少需要引用 java.lang.Object类,也就是说在运行的时候,java.lang.Object 这个类需要被加载到 Java 虚拟机中。如果这个加载过程由 Java 应用自己的类加载器来完成的话,很可能就存在多个版本的 java.lang.Object 类,而且这些类之间是不兼容的。通过双亲委派模型,对于 Java 核心库的类的加载工作由启动类加载器来统一完成,保证了 Java 应用所使用的都是同一个版本的 Java 核心库的类,是互相兼容的。
自定义加载器如何写
需要继承ClassLoader类或URLClassLoader,并至少重写其中的findClass(String name)方法,若想打破双亲委托机制,需要重写loadClass方法
主要加载:自己指定路径的class文件
抽象类
在java中并不是所有的类都是用来描绘对象的,如果一个类中没有包含足够的信息来描绘一个具体的对象,这样的类就是抽象类。
抽象类除了不能实例化对象之外,类的其它功能依然存在,成员变量、成员方法和构造方法的访问方式和普通类一样。
由于抽象类不能实例化对象,所以抽象类必须被继承,才能被使用。也是因为这个原因,通常在设计阶段决定要不要设计抽象类。
父类包含了子类集合的常见的方法,但是由于父类本身是抽象的,所以不能使用这些方法。
抽象类和接口的区别
从设计目的上来说,二者有如下的区别:
**接口体现的是一种规范。对于接口的实现者而言,接口规定了实现者必须向外提供哪些服务;**对于接口的调用者而言,接口规定了调用者可以调用哪些服务,以及如何调用这些服务。当在一个程序中使用接口时,接口是多个模块间的耦合标准;当在多个应用程序之间使用接口时,接口是多个程序之间的通信标准。
抽象类体现的是一种模板式设计。抽象类作为多个子类的抽象父类,可以被当成系统实现过程中的中间产品,这个中间产品已经实现了系统的部分功能,但这个产品依然不能当成最终产品,必须有更进一步的完善,这种完善可能有几种不同方式。
从使用方式上来说,二者有如下的区别:
- 接口里只能包含抽象方法、静态方法、默认方法和私有方法,不能为普通方法提供方法实现;抽象类则完全可以包含普通方法。
- 接口里只能定义静态常量,不能定义普通成员变量;抽象类里则既可以定义普通成员变量,也可以定义静态常量。
- 接口里不包含构造器;抽象类里可以包含构造器,抽象类里的构造器并不是用于创建对象,而是让其子类调用这些构造器来完成属于抽象类的初始化操作。
- 接口里不能包含初始化块;但抽象类则完全可以包含初始化块。
- 一个类最多只能有一个直接父类,包括抽象类;但一个类可以直接实现多个接口,通过实现多个接口可以弥补Java单继承的不足。
总结:接口体现的是一种规范,而抽象类体现的是一种模板设计,从使用方法来看,接口中不能包含普通方法、普通成员变量,构造方法和初始化块,但是在抽象类中都可以包含,其中抽象类的构造方法并不是用于创建对象,而是让其子类调用这些构造器来完成抽象类的初始化操作。一个java类只能继承一个父类,但是可以实现多个接口,这样可以弥补Java单继承的不足
扩展阅读
接口和抽象类很像,它们都具有如下共同的特征:
- 接口和抽象类都不能被实例化,它们都位于继承树的顶端,用于被其他类实现和继承。
- 接口和抽象类都可以包含抽象方法,实现接口或继承抽象类的普通子类都必须实现这些抽象方法。
抽象类和接口的使用场景
抽象类的应用场景一般用于抽取不同事物的共有特性,然后可以用接口实现不同事物的不同行为。遇到需求,先分析不同的对象是否有共同点,比如门,都可以开和关,这个就可以抽取出来定义一个抽象类,而有的门有门铃可以摁,有的门没有,那就将有门铃的对象实现摁门铃这个动作的接口。
IO流
IO(Input Output)用于实现对数据的输入与输出操作,Java把不同的输入/输出源(键盘、文件、网络等)抽象表述为流(Stream)。流是从起源到接收的有序数据,有了它程序就可以采用同一方式访问不同的输入/输出源。
- 按照数据流向,可以将流分为输入流和输出流,其中输入流只能读取数据、不能写入数据,而输出流只能写入数据、不能读取数据。
- 按照数据类型,可以将流分为字节流和字符流,其中字节流操作的数据单元是8位的字节,而字符流操作的数据单元是16位的字符。
- 按照处理功能,可以将流分为节点流和处理流,其中节点流可以直接从/向一个特定的IO设备(磁盘、网络等)读/写数据,也称为低级流,而处理流是对节点流的连接或封装,用于简化数据读/写功能或提高效率,也称为高级流。
final和finally的区别
final 可以用来修饰类、方法、变量,分别有不同的意义,final 修饰的 class 代表不可以继承扩展,final 的变量是不可以修改的,而 final 的方法也是不可以重写的(override)。 finally 是 Java 保证重点代码一定要被执行的一种机制。
泛型
Java 泛型(generics) 是 JDK 5 中引入的一个新特性, 泛型提供了编译时类型安全检测机制,该机制允许程序员在编译时检测到非法的类型。泛型的本质是参数化类型,也就是说所操作的数据类型被指定为一个参数。
泛型一般有三种使用方式: 泛型类、泛型接口、泛型方法。
泛型类
//此处T可以随便写为任意标识,常见的如T、E、K、V等形式的参数常用于表示泛型
//在实例化泛型类时,必须指定T的具体类型
public class Generic<T> {
private T key;
public Generic(T key) {
this.key = key;
}
public T getKey() {
return key;
}
}
泛型接口:
public interface Generator<T> {
public T method();
}
泛型方法
public static <E> void printArray(E[] inputArray) {
for (E element : inputArray) {
System.out.printf("%s ", element);
}
System.out.println();
}
使用:
// 创建不同类型数组: Integer, Double 和 Character
Integer[] intArray = { 1, 2, 3 };
String[] stringArray = { "Hello", "World" };
printArray(intArray);
printArray(stringArray);