java基础
面向对象和面向过程的区别
面向过程:比面向对象性能高;面向过程以步骤划分问题,就是分析出解决问题所需要的步骤,然后用函数把这些步骤一步一步实现,使用的时候一个一个依次调用就可以了(蛋炒饭)
面向对象:比面向过程易维护,易复用,易拓展;面向对象以功能划分问题,是把构成问题事务分解成各个对象,建立对象的目的不是为了完成一个步骤,而是为了描叙某个事物在整个解决问题的步骤中的行为。(盖浇饭)
面向过程
优点:性能比面向对象高,因为类调用时需要实例化,开销比较大,比较消耗资源;比如单片机、嵌入式开发、
Linux/Unix等一般采用面向过程开发,性能是最重要的因素。 缺点:没有面向对象易维护、易复用、易扩展
面向对象
优点:易维护、易复用、易扩展,由于面向对象有封装、继承、多态性的特性,可以设计出低耦合的系统,使系统 更加灵活、更加易于维护
缺点:性能比面向过程低
java语言有哪些特点
-
简单易学;
-
⾯向对象(封装,继承,多态);
-
平台⽆关性( Java 虚拟机实现平台⽆关性);
-
可靠性;
-
安全性;
-
⽀持多线程( C++ 语⾔没有内置的多线程机制,因此必须调⽤操作系统的多线程功能来进⾏多线程程序设计,⽽ Java 语⾔却提供了多线程⽀持);
-
⽀持⽹络编程并且很⽅便( Java 语⾔诞⽣本身就是为简化⽹络编程设计的,因此 Java 语⾔不仅⽀持⽹络编程⽽且很⽅便);
-
编译与解释并存;
java如何实现平台无关
平台无关性就是一种语言在计算机上的运行不受平台的约束,一次编译,到处执行。Java减少了开发和部署到多个平台的成本和时间
- Java语言规范
- 通过规定Java语言中基本数据类型的取值范围和行为
- Class文件
- 所有Java文件要编译成统一的Class文件
- Java虚拟机
- 通过Java虚拟机将Class文件转成对应平台的二进制文件等
java文件-》class文件-》二进制文件
JVM: Java编译器可生成与计算机体系结构无关的字节码指令,.class 字节码文件面向虚拟机,不面向任何具体操作系统。所以.class字节码文件不仅可以轻易地在任何机器上解释执行,还可以动态地转换成本地机器代码,转换是由 JVM 实现的,JVM 是平台相关的,屏蔽了不同操作系统的差异。
java的安全性体现在哪里?
1、Java 不提供指针来直接访问内存,使用引用取代了指针,程序内存更加安全
2、拥有一套异常处理机制,使用关键字 throw、throws、try、catch、finally
3、强制类型转换需要符合一定规则
4、字节码传输使用了加密机制
5、运行环境提供保障机制:字节码校验器->类装载器->运行时内存布局->文件访问限制
6、不用程序员显示控制内存释放,JVM 有垃圾回收机制
JVM,JDK和JRE之间的区别
首先说明三者的关系是:
JDK(Java Develepment Kit)Java开发工具包
JRE(Java RunTime Environment)Java运行时环境
JVM(Java Virtual Machine)Java虚拟机
JDK = JRE + Java工具s + Java基础类库
JRE = JVM + JVM工作所需的类库
JVM是运⾏ Java 字节码的虚拟机。JVM 有针对不同系统的特定实现(Windows,Linux,macOS),⽬的是使⽤相同的字节码,它们都会给出相同的结果。字节码和不同系统的 JVM 实现是 Java 语⾔“⼀次编译,随处可以运⾏”的关键所在。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-KDE1ek6O-1632494423932)(C:\Users\LENOVO-LX\AppData\Roaming\Typora\typora-user-images\image-20210831091740889.png)]
JRE是运行基于Java语言编写的程序所不可缺少的运行环境。也是通过它,Java的开发者才得以将自己开发的程序发布到用户手中,让用户使用。
如果你只是为了运⾏⼀下 Java 程序的话,那么你只需要安装 JRE 就可以了。如果你需要进⾏⼀些 Java 编程⽅⾯的⼯作,那么你就需要安装 JDK 了。但是,这不是绝对的。有时,即使您不打算在计算机上进⾏任何 Java 开发,仍然需要安装 JDK。例如,如果要使⽤ JSP 部署 Web 应⽤程序,那么从技术上讲,您只是在应⽤程序服务器中运⾏ Java 程序。那你为什么需要 JDK 呢?因为应⽤程序服务器会将 JSP 转换为 Java servlet,并且需要使⽤ JDK 来编译 servlet。
JDK是Java开发工具包,JDK是整个JAVA的核心,包括了Java运行环境JRE(Java Runtime Envirnment)、一堆Java工具(javac/java/jdb等)和Java基础的类库(即Java API 包括rt.jar)。它能够创建和编译程序。
Oracle JDK和OpenJDK对比
- OpenJDK 是⼀个参考模型并且是完全开源的,⽽ Oracle JDK 是 OpenJDK 的⼀个实现,并不是完全开源的;
- Oracle JDK ⽐ OpenJDK 更稳定。OpenJDK 和 Oracle JDK 的代码⼏乎相同,但 OracleJDK 有更多的类和⼀些错误修复。因此,如果您想开发企业/商业软件,我建议您选择Oracle JDK,因为它经过了彻底的测试和稳定。某些情况下,有些⼈提到在使⽤ OpenJDK可能会遇到了许多应⽤程序崩溃的问题,但是,只需切换到 Oracle JDK 就可以解决问题;
- 在响应性和 JVM 性能⽅⾯,Oracle JDK 比 OpenJDK 相⽐性能更好;
- Oracle JDK 不会为即将发布的版本提供⻓期⽀持,⽤户每次都必须通过更新到最新版本获得⽀持来获取最新版本;
- Oracle JDK 根据⼆进制代码许可协议获得许可,⽽ OpenJDK 根据 GPL v2 许可获得许可。
java和c++区别
- 都是⾯向对象的语⾔,都⽀持封装、继承和多态
- Java 不提供指针来直接访问内存,程序内存更加安全
- Java 的类是单继承的,C++ ⽀持多重继承;虽然 Java 的类不可以多继承,但是接⼝可以多继承。
- Java 有⾃动内存管理机制,不需要程序员⼿动释放⽆⽤内存
- 在 C 语⾔中,字符串或字符数组最后都会有⼀个额外的字符‘\0’来表示结束。但是,Java 语⾔中没有结束符这⼀概念。
字符型常量和字符串常量区别
-
形式上: 字符常量是单引号引起的⼀个字符; 字符串常量是双引号引起的若⼲个字符
-
含义上: 字符常量相当于⼀个整型值( ASCII 值), 字符串常量代表⼀个地址值(该字符串在内存中存放位置)
-
字符常量只占 2 个字节; 字符串常量占若⼲个字节
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6AkzKylu-1632494423957)(C:\Users\LENOVO-LX\AppData\Roaming\Typora\typora-user-images\image-20210831093557593.png)]
什么是反射?
JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法,对于任意一个对象,都能够调用它的任意一个方法和属性,这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制。在 java 中,只要给定类的名字, 那么就可以通过反射机制来获得类的所有信息。
- Java 的动态就体现在这。通过反射我们可以实现动态装配,降低代码的耦合度;动态代理等。反射的过度使用会严重消耗系统资源。
**JDK 中 java.lang.Class 类,就是为了实现反射提供的核心类之一。**反射使用的不当,对性能影响比较大,一般项目中直接使用较少。
反射主要用于底层的框架中,Spring 中就大量使用了反射,比如:
- 用 IoC 来注入和组装 bean
- 动态代理、面向切面、bean 对象中的方法替换与增强,也使用了反射
- 定义的注解,也是通过反射查找
还用于:在编译时无法知道该对象或类可能属于哪些类,用作程序在运行时获取对象和类的信息
优点:提高了 Java 程序的灵活性和扩展性,降低耦合性,提高自适应能力。
缺点
使用反射的性能较低。 java 反射是要解析字节码,将内存中的对象进行解析。
-
性能问题:反射是一种解释操作,远慢于直接代码。因此反射机制主要用在对灵活性和扩展性要求很高的系统框架上,普通程序不建议使用
-
模糊程序内部逻辑:反射绕过了源代码,无法再源代码中看到程序的逻辑,会带来维护问题
-
增大了复杂性:反射代码比同等功能的直接代码更复杂
解决方案:
1.由于 JDK 的安全检查耗时较多,所以通过 setAccessible(true)的方式关闭安全检查 来(取消对访问控制修饰符的检查)提升反射速度。
2.需要多次动态创建一个类的实例的时候,有缓存的写法会比没有缓存要快很多:
3.ReflectASM 工具类 ,通过字节码生成的方式加快反射速度。
(2)使用反射相对来说不安全,破坏了类的封装性,可以通过反射获取这个 类的私有方法和属性。
反射的实现方式:
在 Java 中实现反射最重要的一步, 也是第一步就是获取 Class 对象, 得到Class 对象后可以通过该对象调用相应的方法来获取该类中的属性、方法以及调用该类中的方法。
有 4 种方法可以得到 Class 对象:
1.Class.forName(“类的路径”);
2.类名.class。
3.对象名.getClass()。
4.如果是基本类型的包装类,则可以通过调用包装类的 Type 属性来获得该包装类的 Class 对象。
例如: Class<?> clazz = Integer.TYPE;
Class类的作用是什么?如何获取Class对象?
Class 类是 Java 反射机制的起源和入口,用于获取与类相关的各种信息,提供了获取类信息的相关方法。
什么是泛型?为什么要使用泛型?
参数化类型,将类型由具体的类型参数化,具有在多种数据类型上皆可操作的含意,与模板有些相似。
为什么要用泛型?
- 使用泛型编写的程序代码,要比使用 Object 变量再进行强制类型转换的代码,具有更好的安全性和可读性。
- 多种数据类型执行相同的代码使用泛型可以复用代码。
比如集合类使用泛型,取出和操作元素时无需进行类型转换,避免出现 java.lang.ClassCastException 异常
String s = new String(“xyz”);创建几个String对象?
两个或一个
- 第一次调用 new String(“xyz”); 时,会在堆内存中创建一个字符串对象,同时在字符串常量池中创建一个对象 “xyz”
- 第二次调用 new String(“xyz”); 时,只会在堆内存中创建一个字符串对象,指向之前在字符串常量池中创建的 “xyz”
构造器Constructor是否可被override(重写)?
Constructor 不能被 override(重写),但是可以 overload(重载),所以你可以看到⼀个类中有多个构造函数的情况。
重载和重写的区别
重载就是同一个类中多个同名方法根据不同的传参来执行不同的逻辑处理,是一个类中多态性的一种表现。有不同的参数列表(静态多态性)。多个用户使用一个非开源项目,每个用户使用它做不同的事情
重写就是子类对父类方法的重新改造,外部样子不能改变,内部逻辑可以改变, 是父类与子类之间多态性的一种表现。相同参数,不同实现(动态多态性)。多个用户使用一个开源项目,每个用户都可以对其进行修改
⽅法的重写要遵循“两同两⼩⼀⼤”:
- “两同”即⽅法名相同、形参列表相同;
- “两⼩”指的是⼦类⽅法返回值类型应⽐⽗类⽅法返回值类型更⼩或相等,⼦类⽅法声明抛出的异常类应⽐⽗类⽅法声明抛出的异常类更⼩或相等;
- “⼀⼤”指的是⼦类⽅法的访问权限应⽐⽗类⽅法的访问权限更⼤或相等
构造方法可以重载,不可重写
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-a0UJpWuG-1632494423962)(C:\Users\LENOVO-LX\AppData\Roaming\Typora\typora-user-images\image-20210831093854285.png)]
静态成员变量和非静态成员变量
-
别名不同:非静态成员变量也称为实例变量;静态变量称为类变量
-
生命周期不同:非静态成员变量随着对象的创建而存在;静态成员变量随着类的加载而存在
-
调用方式不同:非静态成员变量用 对象名.变量名 调用;静态成员变量用 类名.变量名,JDK1.7 以后也能用对象名.变量名调用
-
数据存储位置不同:成员变量数据存储在堆内存的对象中,对象的特有数据;JDK1.6 静态变量数据存储在方法区(共享数据区)的静态区,对象的共享数据,JDK1.7 静态变量移到堆中存储
Java面向对象编程三大特性:封装,继承,多态
封装:是把⼀个对象的属性私有化,同时提供⼀些可以被外界访问的属性的⽅法,如果属性不想被外界访问,我们⼤可不必提供⽅法给外界访问。
继承:是使⽤已存在的类的定义作为基础建⽴新类的技术,新类的定义可以增加新的数据或新的功能,也可以⽤⽗类的功能,但不能选择性地继承⽗类。通过使⽤继承我们能够⾮常⽅便地复⽤以前的代码。
关于继承如下 3 点请记住:
- ⼦类拥有⽗类对象所有的属性和⽅法(包括私有属性和私有⽅法),但是⽗类中的私有属性和⽅法⼦类是⽆法访问,只是拥有。
- ⼦类可以拥有⾃⼰属性和⽅法,即⼦类可以对⽗类进⾏扩展。
- ⼦类可以⽤⾃⼰的⽅式实现⽗类的⽅法。
多态:多态是同一个行为具有多个不同表现形式或形态的能力。多态就是同一个接口,使用不同的实例而执行不同操作。(比如打印机都会打印这个行为,而使用黑白打印机打印会打印出黑白的图文,使用彩色打印机打印则会打印出彩色的图文。)(再比如地震来临前都会引发动物叫,如果是狗就会吠叫,如果是鸡就会啼叫)。在 Java 中有两种形式可以实现多态:继承(多个⼦类对同⼀⽅法的重写)和接⼝(实现接⼝并覆盖接⼝中同⼀⽅法)。
String,StringBuffer和StringBuilder三者区别
String 是 Java 语言非常基础和重要的类,提供了构造和管理字符串的各种基本逻辑。String 中的对象是不可变的,也就可以理解为常量,保证了线程安全。
StringBuffer 对⽅法加了同步锁,所以是线程安全的。性能低
StringBuilder 并没有对⽅法进⾏加同步锁,所以是**⾮线程安全的。**性能高,有效减小了开销
对于三者使⽤的总结:
-
操作少量的数据: 适⽤ String
-
单线程操作字符串缓冲区下操作⼤量数据: 适⽤ StringBuilder
-
多线程操作字符串缓冲区下操作⼤量数据: 适⽤ StringBuffer
StringBuffer、StringBuilder和String一样,也用来代表字符串。String类是不可变类,任何对String的改变都会引发新的String对象的生成。不同的是StringBuffer 每次都会对 StringBuffer 对象本身进⾏操作,而StringBuffer⽣成新的对象并改变对象引⽤。相同情况下使⽤ StringBuilder 相⽐使⽤ StringBuffer 仅能获得 10%~15% 左右的性能提升,但却要冒多线程不安全的⻛险。
String、StringBuffer和StringBuilder类都是CharSequence接口的子类
String类通过apend()方法转换成StringBuilder和StringBuffer类。
StringBuffer类和StringBuilder类通过to.String()方法转换成String类型
String 为什么是不可变的?
简单的来说:String 类中使⽤ final 关键字修饰字符数组来保存字符串, private final char value[] ,所以 String 对象是不可变的。(在 Java 9 之后,String 类的实现改⽤ byte 数组存储字符串private final byte[] value)
为什么String类被设计用final修饰?
- String 类是最常用的类之一,为了效率,禁止被继承和重写
- 为了安全。String 类中有很多调用底层的本地方法,调用了操作系统的 API,如果方法可以重写,可能被植入恶意代码,破坏程序。Java 的安全性也体现在这里。
为了实现修改字符序列的目的,StringBuffer 和 StringBuilder 底层都是利用可修改的(char,JDK 9 以后是 byte)数组,二者都继承了 AbstractStringBuilder,里面包含了基本操作,区别仅在于最终的方法是否加了 synchronized, StringBuffer 它的线程安全是通过把各种修改数据的方法都加上 synchronized 关键字实现。另外,这个**内部数组应该创建成多大的呢?**如果太小,拼接的时候可能要重新创建足够大的数组;如果太大,又会浪费空间。目前的实现是,构建时初始字符串长度加 16
自动拆箱与装箱
装箱:将基本类型⽤它们对应的引⽤类型包装起来;
拆箱:将包装类型转换为基本数据类型;
装箱过程是通过调用包装器的valueOf方法(-128,128]实现的,而拆箱过程是通过调用包装器的 xxxValue方法实现的(xxx代表对应的基本数据类型)。
装箱的过程会创建对应的对象,这个会消耗内存,所以装箱的过程会增加内存的消耗,影响性能
int和Integer区别,谈谈Integer值缓存范围
int 是我们常说的整形数字,是 Java 的 8 个原始数据类型(Primitive Types,boolean、byte 、short、char、int、float、double、long)之一。
Integer 是 int 对应的包装类,它有一个 int 类型的字段存储数据,并且提供了基本操作,比如数学运算、int 和字符串之间转换等。构建 Integer 对象的传统方式是直接调用构造器,直接 new 一个对象。这个值默认缓存是**-128 到 127 之间**
- 基本数据类型方便、简单、高效,但泛型不支持、集合元素不支持
- 不符合面向对象思维
- **包装类提供很多方法,方便使用,**如 Integer 类 toHexString(int i)、parseInt(String s) 方法等等
我们知道 Java 的对象都是引用类型,如果是一个原始数据类型数组,它在内存里是一段连续的内存,而对象数组则不然,数据存储的是引用,对象往往是分散地存储在堆的不同位置。这种设计虽然带来了极大灵活性,但是也导致了数据操作的低效,尤其是无法充分利用现代 CPU 缓存机制。Java 为对象内建了各种多态、线程安全等方面的支持。但有些始数据类型操作不能保证线程安全如float,double
,继续深挖缓存,Integer 的缓存范围虽然默认是 -128 到 127,但是在特别的应用场景,比如我们明确知道应用会频繁使用更大的数值,这时候应该怎么办呢?缓存上限值实际是可以根据需要调整的,JVM 提供了参数设置-XX:AutoBoxCacheMax=N
什么是包装类?为什么要有包装类?
在一个静态方法内调用一个非静态成员为什么是非法的?
类的静态成员(变量或方法)属于类本身,在类加载的时候就会分配内存,可以通过类名直接访问
非静态成员属于类的对象,只有在类的对象产生(实例化)时才会分配内存,然后通过类的对象(实例)去访问
由于静态⽅法可以不通过对象进⾏调⽤,所以,如果一个类的静态方法去调用非静态成员的时候,类的非静态成员可能不存在,访问一个内存中不存在的东西当然会出错因此在静态⽅法⾥,不能调⽤其他⾮静态变量,也不可以访问⾮静态变量成员。
在Java中定义一个不做事且没有参数的构造方法的作用
Java 程序在执⾏⼦类的构造⽅法之前,需要调用父类的构造方法(如果没有⽤ super() 来调⽤⽗类特定的构造⽅法,则会调⽤⽗类中“没有参数的构造⽅法”)。
因此,如果⽗类中只定义了有参数的构造⽅法,⽽在⼦类的构造⽅法中⼜没有⽤ super() 来调⽤⽗类中特定的构造⽅法,因为 Java 程序在⽗类中找不到没有参数的构造⽅法可供执⾏,则编译时发⽣错误。解决办法是在⽗类⾥加上⼀个不做事且没有参数的构造⽅法以防万一。
接口和抽象类的区别
接口:
Java中接口使用interface关键字修饰,接⼝的⽅法默认是 public ,所有⽅法在接⼝中不能有实现(Java 8 开始接⼝⽅法可以有默认实现)
接口可以包含变量、方法;变量被隐士指定为public static final,方法被隐士指定为public abstract(JDK1.8之前);
接口支持多继承,一个类可以实现多个接口
接口没有构造函数
- 接口中的访问权限修饰符只能是 public 或 default
- 接口中的方法必须要实现类实现,所以不能使用 final
- 接口中所有的方法默认都是 abstract,通常 abstract 省略不写
抽象类:
抽象类使用abstract关键字修饰,被abstract关键字修饰的方法称为抽象方法,抽象方法只有方法的声明,没有方法体
抽象类不能被实例化,允许被继承但不允许多继承(一个类只能继承一个抽象类,但可以实现多个接口)
抽象类可以包含属性、方法、构造方法,但是构造方法不能用于实例化,主要用途是被子类调用。
抽象类可以实现接口
除了不能被实例化外,它和普通Java类没有任何区别
区别:
抽象类有构造方法,接口无构造方法,都不能被实例化,
抽象类可以有非抽象方法,而接口中的方法都是抽象方法
接口比抽象类速度要慢,因为它需要时间去寻找在类中实现的方法
实现接口的关键字为implements,继承抽象类的关键字为extends。一个类可以实现多个接口,但一个类只能继承一个抽象类
接口成员变量默认为public static final,必须赋初值,不能被修改;其所有的成员方法都是public、abstract的。抽象类中成员变量默认default,可在子类中被重新定义,也可被重新赋值;抽象方法被abstract修饰,不能被private、static、synchronized和native等修饰,必须以分号结尾,不带花括号。
接口只能是功能的定义,而抽象类既可以为功能的定义也可以为功能的实现。
设计层面上的区别:
抽象类是对一种事物的抽象,即对类抽象,而接口是对行为的抽象。抽象类是对整个类整体进行抽象,包括属性、行为,但是接口却是对类局部(行为)进行抽象。举个简单的例子,飞机和鸟是不同类的事物,但是它们都有一个共性,就是都会飞。那么在设计的时候,可以将飞机设计为一个类Airplane,将鸟设计为一个类Bird,但是不能将 飞行 这个特性也设计为类,因此它只是一个行为特性,并不是对一类事物的抽象描述。此时可以将 飞行 设计为一个接口Fly,包含方法fly( ),然后Airplane和Bird分别根据自己的需要实现Fly这个接口。然后至于有不同种类的飞机,比如战斗机、民用飞机等直接继承Airplane即可,对于鸟也是类似的,不同种类的鸟直接继承Bird类即可。从这里可以看出,继承是一个 "是不是"的关系,而 接口 实现则是 "有没有"的关系。如果一个类继承了某个抽象类,则子类必定是抽象类的种类,而接口实现则是有没有、具备不具备的关系,比如鸟是否能飞(或者是否具备飞行这个特点),能飞行则可以实现这个接口,不能飞行就不实现这个接口。
子类初始化的顺序
① 父类静态代码块和静态变量。
② 子类静态代码块和静态变量。
③ 父类普通代码块和普通变量。
④ 父类构造方法。
⑤ 子类普通代码块和普通变量。
⑥ 子类构造方法。
成员变量与局部变量的区别有哪些?
-
从语法形式上看:成员变量是在类中定义的,⽽局部变量是在⽅法中定义的或是⽅法的参数;成员变量可以被 public , private , static 等修饰符所修饰,⽽局部变量不能被访问控制修饰符及 static 所修饰;但是,成员变量和局部变量都能被 final 所修饰。
-
从变量在内存中的存储⽅式来看:成员变量是对象的一部分,对象存于堆内存;如果局部变量类型为基本数据类型,那么存储在栈内存,如果为引⽤数据类型,那存放的是指向堆内存对象的引⽤或者是指向常量池中的地址。
-
从变量在内存中的⽣存时间上看:成员变量是对象的⼀部分,它随着对象的创建⽽存在,⽽局部变量随着⽅法的调⽤⽽⾃动消失。
-
成员变量如果没有被赋初值:则会⾃动以类型的默认值⽽赋值(⼀种情况例外:被 final 修饰的成员变量也必须显式地赋值),⽽局部变量则不会⾃动赋值。
创建一个对象用什么运算符?对象实例与对象引用有何不同?
new运算符
new 创建对象实例(对象实例在堆内存中),对象引⽤指向对象实例(对象引⽤存放在栈内存中)。⼀个对象引⽤可以指向 0 个或 1 个对象(⼀根绳⼦可以不系⽓球,也可以系⼀个⽓球);⼀个对象可以有 n 个引⽤指向它(可以⽤ n 条绳⼦系住⼀个⽓球)
什么是方法的返回值?返回值在类的方法里有什么作用?
⽅法的返回值是指我们获取到的某个⽅法体中的代码执⾏后产⽣的结果!
返回值的作⽤:接收出结果,使得它可以⽤于其他的操作
⼀个类的构造⽅法的作⽤是什么? 若⼀个类没有声明构造⽅法,该程序能正确执⾏吗? 为什么?
主要作⽤是完成对类对象的初始化⼯作。
可以执⾏,因为⼀个类即使没有声明构造⽅法也会有默认的不带参数的构造⽅法
构造方法有哪些特性
- 名字与类名相同。
- 构造方法无返回值类型(void也不行)
- ⽣成类的对象时⾃动执⾏,⽆需调⽤
- 构造方法不能被继承
- 构造方法可以没有(默认一个无参构造方法),也可以有多个构造方法。他们之间构成重载关系
- 构造方法可以重载,不可重写
作用:
(1)初始化对象,为对象赋初值。
(2)简化我们为类字段赋值的代码。
静态方法和实例方法有何不同
-
在外部调⽤静态⽅法时,可以使⽤"类名.⽅法名"的⽅式,也可以使⽤"对象名.⽅法名"的⽅式。⽽实例⽅法只有后⾯这种⽅式。也就是说,调⽤静态⽅法可以⽆需创建对象。
-
静态⽅法在访问本类的成员时,只允许访问静态成员(即静态成员变量和静态⽅法),⽽不允许访问实例成员变量和实例⽅法;实例⽅法则⽆此限制。
对象的相等和指向它们的应用相等,两者有什么不同?
对象的相等,⽐的是内存中存放的内容是否相等。
⽽引⽤相等,⽐的是他们指向的内存地址是否相等
在调用子类构造方法之前会先调用父类没有参数的构造方法,其目的是?
帮助⼦类做初始化⼯作
在初始化子类的时候,一定要使父类已经存在了(所以要先初始化父类对象,即要调用没有参数的构造方法来初始化对象).不然没办法调用父类的构造函数.父类必须在子类初始化之前就已经准备好.
Object类有哪些方法?
equals:检测对象是否相等,默认使用 == 比较对象引用,可以重写 equals 方法自定义比较规则。equals 方法规范:自反性、对称性、传递性、一致性、对于任何非空引用 x,x.equals(null) 返回 false。
hashCode:散列码是由对象导出的一个整型值,没有规律,每个对象都有默认散列码,值由对象存储地址得出。字符串散列码由内容导出,值可能相同。为了在集合中正确使用,一般需要同时重写 equals 和 hashCode,要求 equals 相同 hashCode 必须相同,hashCode 相同 equals 未必相同,因此 hashCode 是对象相等的必要不充分条件。
toString:打印对象时默认的方法,如果没有重写打印的是表示对象值的一个字符串。
clone:clone 方法声明为 protected,类只能通过该方法克隆它自己的对象,如果希望其他类也能调用该方法必须定义该方法为 public。如果一个对象的类没有实现 Cloneable 接口,该对象调用 clone 方抛出一个 CloneNotSupport 异常。默认的 clone 方法是浅拷贝,一般重写 clone 方法需要实现 Cloneable 接口并指定访问修饰符为 public。
finalize:确定一个对象死亡至少要经过两次标记,如果对象在可达性分析后发现没有与 GC Roots 连接的引用链会被第一次标记,随后进行一次筛选,条件是对象是否有必要执行 finalize 方法。假如对象没有重写该方法或方法已被虚拟机调用,都视为没有必要执行。如果有必要执行,对象会被放置在 F-Queue 队列,由一条低调度优先级的 Finalizer 线程去执行。虚拟机会触发该方法但不保证会结束,这是为了防止某个对象的 finalize 方法执行缓慢或发生死循环。只要对象在 finalize 方法中重新与引用链上的对象建立关联就会在第二次标记时被移出回收集合。由于运行代价高昂且无法保证调用顺序,在 JDK 9 被标记为过时方法,并不适合释放资源。
getClass:返回包含对象信息的类对象。
wait / notify / notifyAll:阻塞或唤醒持有该对象锁的线程。
==与equals区别(重要)
== : 它的作⽤是判断两个对象的地址是不是相等。即,判断两个对象是不是同⼀个对象(基本数据类型⽐较的是值,引⽤数据类型⽐较的是内存地址)。
equals() : 它的作⽤也是判断两个对象是否相等。但它⼀般有两种使⽤情况:
情况 1:类没有覆盖 equals() ⽅法。则通过 equals() ⽐较该类的两个对象时,等价于通过“==”⽐较这两个对象。
情况 2:类覆盖了 equals() ⽅法。⼀般,我们都覆盖 equals() ⽅法来⽐较两个对象的内容是否相等;若它们的内容相等,则返回 true (即,认为这两个对象相等)
说明:
String 中的 equals ⽅法是被重写过的,因为 object 的 equals ⽅法是⽐的对象的内存地址,⽽ String 的 equals ⽅法⽐的是对象的值。
当创建 String 类型的对象时,虚拟机会在常量池中查找有没有已经存在的值和要创建的值相同的对象,如果有就把它赋给当前引⽤。如果没有就在常量池中重新创建⼀个 String 对象。
hashCode与equals区别(重要)
hashCode()介绍:
hashCode() 的作⽤是获取哈希码,也称为散列码;它实际上是返回⼀个 int 整数。这个哈希码的作⽤是确定该对象在哈希表中的索引位置。
hashCode() 定义在 JDK 的 Object 类中,这就意味着 Java 中的任何类都包含有 hashCode() 函数。另外需要注意的是: Object 的 hashcode ⽅法是本地⽅法,也就是⽤ c 语⾔或 c++ 实现的,该⽅法通常⽤来将对象的 内存地址 转换为整数之后返回。
散列表存储的是键值对(key-value),它的特点是:能根据“键”快速的检索出对应的“值”。这其中就利⽤到了散列码!(可以快速找到所需要的对象)
为什么要有hashCode:
当你把对象加⼊ HashSet 时, HashSet 会先计算对象的 hashcode 值来判断对象加⼊的位置,同时也会与其他已经加⼊的对象的 hashcode 值作⽐较,如果没有相符的 hashcode, HashSet会假设对象没有重复出现。但是如果发现有相同 hashcode 值的对象,这时会调⽤ equals() ⽅法来检查 hashcode 相等的对象是否真的相同。如果两者相同, HashSet 就不会让其加⼊操作成功。如果不同的话,就会重新散列到其他位置。这样我们就⼤⼤减少了 equals 的次数,相应就⼤⼤提⾼了执⾏速度。
为什么重写equals 时必须重写 hashCode⽅法:
如果两个对象相等,则 hashcode ⼀定也是相同的。两个对象相等,对两个对象分别调⽤ equals⽅法都返回 true。但是,两个对象有相同的 hashcode 值,这两个对象不⼀定是相等的 。因此,equals⽅法被覆盖过,则 hashCode ⽅法也必须被覆盖。
为什么两个对象有相同的hashcode值,它们也不⼀定是相等的:
因为 hashCode() 所使⽤的杂凑算法也许刚好会让多个对象传回相同的杂凑值。越糟糕的杂凑算法越容易碰撞,但这也与数据值域分布的特性有关(所谓碰撞也就是指的是不同的对象得到相同的 hashCode 。我们刚刚也提到了 HashSet ,如果 HashSet 在对⽐的时候,同样的 hashcode 有多个对象,它会使⽤ equals() 来判断是否真的相同。也就是说 hashcode 只是⽤来缩⼩查找成本。
访问权限控制符有哪些?
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-dnV1eniR-1632494423973)(C:\Users\LENOVO-LX\AppData\Roaming\Typora\typora-user-images\image-20210909131559139.png)]
-
private:表示私有的,作用范围是当前类(类可见性)。
-
default:表示没有修饰符修饰,作用范围是当前类+当前包(包可见型)。
-
protected:表示受保护的,作用范围是当前类+当前包+包外的子类(子类可见性)。
-
public:表示公开的,作用范围是是当前类+当前包+其它包(项目可见性)。
访问权限控制符不能修饰局部变量
为什么Java中只有值传递?
Java程序设计语⾔总是采⽤按值调⽤。也就是说,⽅法得到的是所有参数值的⼀个拷⻉,也就是说,⽅法不能修改传递给它的任何参数变量的内容。
简述线程,程序,进程的基本概念,以及他们之间关系是什么?
进程:
-
程序由指令和数据组成,但这些指令要运行,数据要读写,就必须将指令加载至CPU,数据加载至内存,在指令进行运行过程中还需要用到磁盘,网络等设备。进程就是用来加载指令,管理内存,管理IO的
-
当一个程序被运行,从磁盘加载这个程序的代码至内存,这时就开启了一个进程
进程是程序的⼀次执⾏过程,是系统运⾏程序的基本单位,因此进程是动态的。系统运⾏⼀个程序即是⼀个进程从创建,运⾏到消亡的过程。简单来说,⼀个进程就是⼀个执⾏中的程序,它在计算机中⼀个指令接着⼀个指令地执⾏着,同时,每个进程还占有某些系统资源如 CPU 时间,内存空间,⽂件,输⼊输出设备的使⽤权等等。换句话说,当程序在执⾏时,将会被操作系统载⼊内存中。
线程:
- 一个进程可以分为一到多个线程
- 一个线程就是一个指令流,将指令流中的一条条指令以一定的顺序交给CPU执行
- Java中,线程作为最小调度单位,进程作为资源分配的最小单位,在windows中进程是不活动的,只是作为线程的容器
二者对比:
- 进程基本上是相互独立的,而线程存在于进程内,是进程的一个子集
- 进程拥有共享的资源,如内存空间等,供其内部的线程共享
- 与进程不同的是同类的多个线程共享同⼀块内存空间和⼀组系统资源,所以系统在产⽣⼀个线程,或是在各个线程之间作切换⼯作时,负担要⽐进程⼩得多,也正因为如此,线程也被称为轻量级进程
- 进程间通信较为复杂
同一台计算机的进程通信称为IPC
不同计算机之间的进程通信,需要通过网络,并遵守共同的协议,例如HTTP
- 线程通信相对简单,因为他们共享进程内的内存,一个例子是多个线程可以访问同一个共享变量
- 线程更轻量,线程上下文切换成本一般要比进程上下文低
线程有哪些基本状态
Java 线程在运⾏的⽣命周期中的指定时刻只可能处于下⾯ 6 种状态的其中⼀个状态:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qleWUhTu-1632494423975)(C:\Users\LENOVO-LX\AppData\Roaming\Typora\typora-user-images\image-20210901090110364.png)]
线程创建之后它将处于 NEW(新建) 状态,调⽤ start() ⽅法后开始运⾏,线程这时候处于READY(可运⾏)状态。可运⾏状态的线程获得了 cpu 时间⽚(timeslice)后就处于RUNNING(运⾏) 状态。
当线程执⾏ wait() ⽅法之后,线程进⼊ WAITING**(等待)状态。进⼊等待状态的线程需要依靠其他线程的通知才能够返回到运⾏状态,⽽ TIME_WAITING(超时等待) 状态相当于在等待状态的基础上增加了超时限制,⽐如通过 sleep(long millis) ⽅法或 wait(long millis) ⽅法可以将Java 线程置于 TIMED WAITING 状态。当超时时间到达后 Java 线程将会返回到 RUNNABLE 状态。
当线程调⽤同步⽅法时,在没有获取到锁的情况下,线程将会进⼊BLOCKED(阻塞)状态。线程在执⾏ Runnable 的 run() ⽅法之后将会进⼊到 TERMINATED(终⽌) 状态。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9XW2O2TL-1632494423978)(C:\Users\LENOVO-LX\AppData\Roaming\Typora\typora-user-images\image-20210901090757562.png)]
final关键字
-
当⽤ final 修饰⼀个类时,表明这个类不能被继承。final 类中的所有成员⽅法都会被隐式地指定为 final ⽅法。
-
使⽤ final ⽅法的原因有两个。第⼀个原因是把⽅法锁定,以防任何继承类修改它的含义;第⼆个原因是效率。在早期的 Java 实现版本中,会将 final ⽅法转为内嵌调⽤。但是如果⽅法过于庞⼤,可能看不到内嵌调⽤带来的任何性能提升(现在的 Java 版本已经不需要使⽤final ⽅法进⾏这些优化了)。类中所有的 private ⽅法都隐式地指定为 final。
-
修饰变量是final用得最多的地方,对于一个final变量,如果是基本数据类型的变量,则其数值一旦在初始化之后便不能更改;如果是引用类型的变量,则在对其初始化之后便不能再让其指向另一个对象。但是它指向的对象的内容是可变的。
.类的final变量和普通变量有什么区别?
当用final作用于类的成员变量时,成员变量必须在定义时或者构造器中进行初始化赋值,而且final变量一旦被初始化赋值之后,就不能再被赋值了。
我们可以将方法或者类声明为 final,这样就可以明确告知别人,这些行为是不许修改的。如果你关注过 Java 核心类库的定义或源码, 有没有发现 java.lang 包下面的很多类,相当一部分都被声明成为 final class?在第三方类库的一些基础类中同样如此,这可以有效避免API 使用者更改基础功能,某种程度上,这是保证平台安全的必要手段。使用 final 修饰参数或者变量,也可以清楚地避免意外赋值导致的编程错误,甚至,有人明确推荐将所有方法参数、本地变量、成员变量声明成 final。final 变量产生了某种程度的不可变(immutable)的效果,所以,可以用于保护只读数据,尤其是在并发编程中,因为明确地不能再赋值 final 变量,有利于减少额外的同步开销,也可以省去一些防御性拷贝的必要。final 也许会有性能的好处,很多文章或者书籍中都介绍了可在特定场景提高性能,比如,利用 final 可能有助于 JVM 将方法进行内联,可以改善编译器进行条件编译的能力等等。坦白说,很多类似的结论都是基于假设得出的,比如现代高性能 JVM(如 HotSpot)判断内联未必依赖 final 的提示,要相信 JVM 还是非常智能的。类似的,final 字段对性能的影响,大部分情况下,并没有考虑的必要。
public class Test {public static void main(String[] args) {String a = "hello2"; final String b = "hello";String d = "hello";String c = b + 2; String e = d + 2;System.out.println((a == c));//trueSystem.out.println((a == e));//false}
}
final与static的区别?
java中的异常处理
体检java平台设计者对不同异常的分类。
在 Java 中,所有的异常都有⼀个共同的祖先 java.lang 包中的 Throwable 类。 Throwable 类有两个重要的⼦类 Exception (异常)和 Error (错误)。 Exception 能被程序本身处理( try_catch ), Error 是⽆法处理的(只能尽量避免)。
Exception 和 Error ⼆者都是 Java 异常处理的重要⼦类,各⾃都包含⼤量⼦类。
Exception:程序本身可以处理的异常,是程序运行过程中常见的可以预料的情况,可以通过 catch 来进⾏捕获并进行相应的处理。 Exception ⼜可以分为 受检查异常(必须处理) 和 不受检查异常(可以不处理)。
- 受检查异常:Java 代码在编译过程中,如果受检查异常没有被 catch / throw 处理的话,就没办法通过编译,Exception 类及其⼦类都属于检查异常;常⻅的受检查异常有: IO 相关的异常、 ClassNotFoundException 、 SQLException …
- 不受检查异常(就是所谓的运行时异常):Java 代码在编译过程中 ,我们即使不处理不受检查异常也可以正常通过编译。RuntimeException 及其⼦类都统称为⾮受检查异常,例如: NullPointExecrption 、 NumberFormatException (字符串转换为数字)、 ArrayIndexOutOfBoundsException (数组越界)、 ClassCastException (类型转换错误)、 ArithmeticException (算术错误)等。
Error : **Error 属于程序⽆法处理的错误 ,是正常情况下不太可能出现的情况。**我们没办法通过 catch 来进⾏捕获 。例如,Java 虚拟机运⾏错误( Virtual MachineError )、虚拟机内存不够错误( OutOfMemoryError )、类定义错误( NoClassDefFoundError )等 。这些异常发⽣时,Java虚拟机(JVM)⼀般会选择线程终⽌。受检查异常
- 捕获异常后,不要生吞(swallow)异常。这是异常处理中要特别注意的事情,因为很可能会导致非常难以诊断的诡异情况。生吞异常,往往是基于假设这段代码可能不会发生,或者感觉忽略异常是无所谓的,但是千万不要在产品代码做这种假设!如果我们不把异常抛出来,或者也没有输出到日志(Logger)之类,程序可能在后续代码以不可控的方式结束。没人能够轻易判断究竟是哪里抛出了异常,以及是什么原因产生了异常
- 有的时候,我们会根据需要自定义异常。一个重要考量就是信息安全。比如,用户数据一般是不可以输出到日志里面的
- 我们从性能角度来审视一下 Java 的异常处理机制,这里有两个可能会相对昂贵的地方。try-catch 代码段会产生额外的性能开销,或者换个角度说,它往往会影响 JVM 对代码进行优化,所以建议仅捕获有必要的代码段,尽量不要一个大的 try 包住整段的代码。Java 每实例化一个 Exception,都会对当时的栈进行快照,这是一个相对比较重的操作。如果发生的非常频繁,这个开销可就不能被忽略了。当我们的服务出现反应变慢、吞吐量下降的时候,检查发生最频繁的 Exception 也是一种思路。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-2TowOvjc-1632494423980)(C:\Users\LENOVO-LX\AppData\Roaming\Typora\typora-user-images\image-20210901093446188.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-DBkS73Qc-1632494423983)(C:\Users\LENOVO-LX\AppData\Roaming\Typora\typora-user-images\image-20210901093500668.png)]
Throwable类常用方法:
public string getMessage() :返回异常发⽣时的简要描述
public string toString() :返回异常发⽣时的详细信息
public string getLocalizedMessage() :返回异常对象的本地化信息。使⽤Throwable 的⼦类覆盖这个⽅法,可以⽣成本地化信息。如果⼦类没有覆盖该⽅法,则该⽅法返回的信息与getMessage() 返回的结果相同
public void printStackTrace() :在控制台上打印 Throwable 对象封装的异常信息
异常处理总结:
try块: ⽤于捕获异常。其后可接零个或多个 catch 块,如果没有 catch 块,则必须跟⼀个 finally 块。
catch 块: ⽤于处理 try 捕获到的异常。
finally 块: ⽆论是否捕获或处理异常, finally 块⾥的语句都会被执⾏。当在 try 块或
catch 块中遇到 return 语句时, finally 语句块将在⽅法返回之前被执⾏。当 try 语句和 finally 语句中都有 return 语句时,在⽅法返回之前,finally 语句的内容将被执⾏,并且 finally 语句的返回值将会覆盖原始的返回值
在以下 3 种特殊情况下, finally 块不会被执⾏:
-
在 try 或 finally 块中⽤了 System.exit(int) 退出程序。但是,如果 System.exit(int) 在异常语句之后, finally 还是会被执⾏
-
程序所在的线程死亡。
-
关闭 CPU。
java序列化中如果有些字段不想进行序列化怎么办?
java序列化:即写文件,将对象转化为字节序列方便存储在文件中
java反序列化:即读文件,将字节序列转化为对象
对于不想进⾏序列化的变量,使⽤ transient 关键字修饰。transient 关键字的作⽤是:阻⽌实例中那些⽤此关键字修饰的的变量序列化;当对象被反序列化时,被 transient 修饰的变量值不会被持久化和恢复。transient 只能修饰变量,不能修饰类和⽅法。
获取用键盘输入的两种常用方法
通过Scanner:
Scanner input = new Scanner(System.in);
String s = input.nextLine();
input.close();
通过BufferedReader:
Scanner input = new Scanner(System.in);
String s = input.nextLine();
input.close();
java中的IO流
IO流:数据传输是需要通道的,而IO流就是数据传输的通道。IO流可以形象的比喻为运送货物的传输带。
java中的IO流分为几种
- 按照流的流向分,可以分为输⼊流和输出流;
- 按照操作单元划分,可以划分为字节流和字符流;
- 按照流的⻆⾊划分为节点流和处理流。
InputStream/Reader: 所有的输⼊流的基类,前者是字节输⼊流,后者是字符输⼊流。
OutputStream/Writer: 所有输出流的基类,前者是字节输出流,后者是字符输出流。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-TYgB12wq-1632494423986)(C:\Users\LENOVO-LX\AppData\Roaming\Typora\typora-user-images\image-20210901104125545.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-gBiCQC4i-1632494423988)(C:\Users\LENOVO-LX\AppData\Roaming\Typora\typora-user-images\image-20210901103711639.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1PoZeikQ-1632494423990)(C:\Users\LENOVO-LX\AppData\Roaming\Typora\typora-user-images\image-20210901101837461.png)]
有字节流为什么还要有字符流
问题本质想问:不管是⽂件读写还是⽹络发送接收,信息的最⼩存储单元都是字节,那为什么****I/O 流操作要分为字节流操作和字符流操作呢?
回答:字符流是由 Java 虚拟机将字节转换得到的,问题就出在这个过程⾮常耗时,并且,如果我们不知道编码类型就很容易出现乱码问题。所以, I/O 流就⼲脆提供了⼀个直接操作字符的接⼝,⽅便我们平时对字符进⾏流操作。如果⾳频⽂件、图⽚等媒体⽂件⽤字节流⽐较好,如果涉及到字符的话使⽤字符流⽐较好。
BIO,NIO和AIO区别
BIO (Blocking I/O): 同步阻塞 I/O 模式,数据的读取写⼊必须阻塞在⼀个线程内等待其完成。在活动连接数不是特别⾼(⼩于单机 1000)的情况下,这种模型是⽐较不错的,可以让每⼀个连接专注于⾃⼰的 I/O 并且编程模型简单,也不⽤过多考虑系统的过载、限流等问题。但是,当⾯对⼗万甚⾄百万级连接的时候,传统的 BIO 模型是⽆能为⼒的。
我们建立网络连接的时候采用BIO模式,需要先在服务端启动一个ServerSocket,然后在客户端启动Socket来对服务端进行通信,默认情况下服务端需要对每个请求建立一堆线程等待请求,而客户端发送请求后,先咨询服务端是否有线程相应,如果没有则会一直等待或者遭到拒绝请求,如果有的话,客户端会线程会等待请求结束后才继续执行。
NIO (Non-blocking/New I/O): 同步⾮阻塞的 I/O 模型,NIO 提供⽀持阻塞和⾮阻塞两种模式。阻塞模式使⽤就像传统中的⽀持⼀样,⽐较简单,但是性能和可靠性都不好;⾮阻塞模式对于⾼负载、⾼并发的(⽹络)应⽤,应使⽤ NIO 的⾮阻塞模式来开发
NIO本身是基于事件驱动思想来完成的,其主要想解决的是BIO的大并发问题: 在使用同步I/O的网络应用中,如果要同时处理多个客户端请求,或是在客户端要同时和多个服务器进行通讯,就必须使用多线程来处理。也就是说,将每一个客户端请求分配给一个线程来单独处理。这样做虽然可以达到我们的要求,但同时又会带来另外一个问题。由于每创建一个线程,就要为这个线程分配一定的内存空间(也叫工作存储器),而且操作系统本身也对线程的总数有一定的限制。如果客户端的请求过多,服务端程序可能会因为不堪重负而拒绝客户端的请求,甚至服务器可能会因此而瘫痪。使用BIO的时候往往会引入多线程,每个连接一个单独的线程,实现异步处理,降低服务器压力
AIO (Asynchronous I/O): (即 NIO 2)异步⾮阻塞的 IO 模型。AIO 是异步 IO 的缩写,虽然 NIO 在⽹络操作中,提供了⾮阻塞的⽅法,但是 NIO 的 IO ⾏为还是同步的。对于 NIO 来说,我们的业务线程是在 IO 操作准备好时,得到通知,接着就由这个线程⾃⾏进⾏ IO 操作,IO 操作本身是同步的。查阅⽹上相关资料,我发现就⽬前来说 AIO 的应⽤还不是很⼴泛。
以银行取款为例:
- 同步 : 自己亲自出马持银行卡到银行取钱(使用同步IO时,Java自己处理IO读写);
- 异步 : 委托一小弟拿银行卡到银行取钱,然后给你(使用异步IO时,Java将IO读写委托给OS处理,需要将数据缓冲区地址和大小传给OS(银行卡和密码),OS需要支持异步IO操作API);
- 阻塞 : ATM排队取款,你只能等待(使用阻塞IO时,Java调用会一直阻塞到读写完成才返回);
- 非阻塞 : 柜台取款,取个号,然后坐在椅子上做其它事,等号广播会通知你办理,没到号你就不能去,你可以不断问大堂经理排到了没有,大堂经理如果说还没到你就不能去(使用非阻塞IO时,如果不能读写Java调用会马上返回,当IO事件分发器会通知可读写时再继续进行读写,不断循环直到读写完成)
Java对BIO、NIO、AIO的支持:
- Java BIO : 同步并阻塞,服务器实现模式为一个连接一个线程,即客户端有连接请求时服务器端就需要启动一个线程进行处理,如果这个连接不做任何事情会造成不必要的线程开销,当然可以通过线程池机制改善。
- Java NIO : 同步非阻塞,服务器实现模式为一个请求一个线程,即客户端发送的连接请求都会注册到多路复用器上,多路复用器轮询到连接有I/O请求时才启动一个线程进行处理。
- Java AIO(NIO.2) : 异步非阻塞,服务器实现模式为一个有效请求一个线程,客户端的I/O请求都是由OS先完成了再通知服务器应用去启动线程进行处理,
BIO、NIO、AIO适用场景分析:
- BIO方式适用于连接数目比较小且固定的架构,这种方式对服务器资源要求比较高,并发局限于应用中,JDK1.4以前的唯一选择,但程序直观简单易理解。
- NIO方式适用于连接数目多且连接比较短(轻操作)的架构,比如聊天服务器,并发局限于应用中,编程比较复杂,JDK1.4开始支持。
- AIO方式使用于连接数目多且连接比较长(重操作)的架构,比如相册服务器,充分调用OS参与并发操作,编程比较复杂,JDK7开始支持。
深拷贝和浅拷贝
-
浅拷⻉:对基本数据类型进⾏值传递,对引⽤数据类型进⾏引⽤传递般的拷⻉,此为浅拷⻉。
-
深拷⻉:对基本数据类型进⾏值传递,对引⽤数据类型,创建⼀个新的对象,并复制其内容,此为深拷⻉。
深拷贝和浅拷贝最根本的区别在于是否真正获取一个对象的复制实体,而不是引用。
- 假设B复制了A,修改A的时候,看B是否发生变化:
- 如果B跟着也变了,说明是浅拷贝,拿人手短!(修改堆内存中的同一个值)
- 如果B没有改变,说明是深拷贝,自食其力!(修改堆内存中的不同的值)
浅复制:仅仅是指向被复制的内存地址,如果原地址发生改变,那么浅复制出来的对象也会相应的改变。
深复制:在计算机中开辟一块新的内存地址用于存放复制的对象。
内存泄漏和内存溢出的区别
- 内存溢出(out of memory):指程序在申请内存时,没有足够的内存空间供其使用,出现 out of memory。
- 内存泄露(memory leak):指程序在申请内存后,无法释放已申请的内存空间,内存泄露堆积会导致内存被占光。
- memory leak 最终会导致 out of memory。
java的垃圾回收机制
垃圾回收机制,简称 GC
- Java 语言不需要程序员直接控制内存回收,由 JVM 在后台自动回收不再使用的内存
- 提高编程效率
- 保护程序的完整性
特点
- 回收 JVM 堆内存里的对象空间,不负责回收栈内存数据
- 垃圾回收发生具有不可预知性,程序无法精确控制垃圾回收机制执行
- 可以将对象的引用变量设置为 null,垃圾回收机制可以在下次执行时回收该对象。
- 垃圾回收机制回收任何对象之前,会先调用对象的 finalize() 方法。
- 可以通过 System.gc() 或 Runtime.getRuntime().gc() 通知系统进行垃圾回收,会有一些效果,但系统是否进行垃圾回收依然不确定
java.sql.Date和java.util.Date的区别
- java.sql.Date 是 java.util.Date 的子类
- java.util.Date 是 JDK 中的日期类,精确到时、分、秒、毫秒
- java.sql.Date 与数据库 Date 相对应的一个类型,只有日期部分,时分秒都会设置为 0,如:2019-10-23 00:00:00
&和&&的作用和区别
&:
- 逻辑与,& 两边的表达式都会进行运算
- 整数的位运算符
&&:
- 短路与,&& 左边的表达式结果为 false 时,&& 右边的表达式不参与计算
Java中有几种基本数据类型?它们分别占多大字节?
- byte:1个字节,8位
- short:2个字节,16位
- int:4个字节,32位
- long:8个字节,64位
- float:4个字节,32位
- double:8个字节,64位
- boolean:官方文档未明确定义,依赖于 JVM 厂商的具体实现。逻辑上理解是占用 1位,但是实际中会考虑计算机高效存储因素
- char:2个字节,16位
补充说明:字节的英文是 byte,位的英文是 bit
什么是java序列化?什么情况下需要序列化?
序列化:将 Java 对象转换成字节流的过程。(写文件)
反序列化:将字节流转换成 Java 对象的过程。(读文件)
当 Java 对象需要在网络上传输 或者 持久化存储到文件中时,就需要对 Java 对象进行序列化处理。
序列化的实现:类实现 Serializable 接口,这个接口没有需要实现的方法。实现 Serializable 接口是为了告诉 jvm 这个类的对象可以被序列化。
注意事项:
- 某个类可以被序列化,则其子类也可以被序列化
- 对象中的某个属性是对象类型,需要序列化也必须实现 Serializable 接口
- 声明为 static 和 transient 的成员变量,不能被序列化。static 成员变量是描述类级别的属性,transient 表示临时数据
- 反序列化读取序列化对象的顺序要保持一致
throw和throws区别
throws是用来声明一个方法可能抛出的所有异常信息,throws是将异常声明但是不处理,而是将异常往上传,谁调用我就交给谁处理。
而throw则是指抛出的一个具体的异常类型。
什么是hash和hash表?
Hash,是把任意长度的输入通过散列算法变换成固定长度的输出,该输出就是散列值。使用Hash算法可以提高存储空间的利用率,可以提高数据的查询效率,也可以做数字签名来保障数据传递的安全性。
对不同的关键字可能得到同一散列地址,现象称碰撞。一组关键字和它们各自对应的散列地址映射到一个有限的连续的地址集(区间)上,这种表便称为散列表