文章目录
一、面向对象及Java开发基础
java面向对象的特征
封装、抽象、继承、多态
- 封装:把对象封装成一个高度自治和相对封闭的个体 (private name getName setName)
- 抽象:找出一些事物相似共同之处,把现实生活中的事物抽象成一个类
- 继承:定义和实现一个类的时候,可以在已经存在的类的基础上来进行 可重写 可增加 (遗产)
- 多态:程序中定义的引用变量可以指向子类或具体类的实例对象,而程序调用的方法在运行期才动态绑定。
Object obj = new XXX();
UserDao userDao = new UserDaoJdbcImp();
UserDao userDao = new UserDaoHibernateImp();
JDK和JRE
JVM
Java虚拟机(JVM)是运行 Java 字节码的虚拟机。JVM有针对不同系统的特定实现(Windows,Linux,macOS),目的是使用相同的字节码,它们都会给出相同的结果。
什么是字节码?采用字节码的好处是什么?
在 Java 中,JVM可以理解的代码就叫做
字节码
(即扩展名为.class
的文件),它不面向任何特定的处理器,只面向虚拟机。Java 语言通过字节码的方式,在一定程度上解决了传统解释型语言执行效率低的问题,同时又保留了解释型语言可移植的特点。所以 Java 程序运行时比较高效,而且,由于字节码并不专对一种特定的机器,因此,Java程序无须重新编译便可在多种不同的计算机上运行。
Java 程序从源代码到运行一般有下面3步:
总结:Java虚拟机(JVM)是运行 Java 字节码的虚拟机。JVM有针对不同系统的特定实现(Windows,Linux,macOS),目的是使用相同的字节码,它们都会给出相同的结果。字节码和不同系统的 JVM 实现是 Java 语言“一次编译,随处可以运行”的关键所在。
JDK和JRE
JDK是Java Development Kit,它是功能齐全的Java SDK。它拥有JRE所拥有的一切,还有编译器(javac)和工具(如javadoc和jdb)。它能够创建和编译程序。
JRE 是 Java运行时环境。它是运行已编译 Java 程序所需的所有内容的集合,包括 Java虚拟机(JVM),Java类库,java命令和其他的一些基础构件。但是,它不能用于创建新程序。
如果你只是为了运行一下 Java 程序的话,那么你只需要安装 JRE 就可以了。如果你需要进行一些 Java 编程方面的工作,那么你就需要安装JDK了。但是,这不是绝对的。有时,即使您不打算在计算机上进行任何Java开发,仍然需要安装JDK。例如,如果要使用JSP部署Web应用程序,那么从技术上讲,您只是在应用程序服务器中运行Java程序。那你为什么需要JDK呢?因为应用程序服务器会将 JSP 转换为 Java servlet,并且需要使用 JDK 来编译 servlet。
二、Java基础程序设计结构
java中int数据占几个字节
基本类型
byte/8
char/16
short/16
int/32
float/32
long/64
double/64
boolean/~
有了基本的数据类型,为什么还需要包装类型?
8种数据类型,包装类型
每一个基本的数据类型都会一一对应一个包装类型
int -> Integer
java是一个面向对象的语言,而基本的数据类型不具有面向对象的特性。
比如用Integer和int分别表示Person这个类的ID
Integer-> null int-> 0 Integer表示形式更好
而且包装类型还有max.min等操作
装箱和拆箱
把基本的数据类型转换为对应的数据类型
string类为什么是final的
一、理解final
望文生义,final意为“最终的,最后的”,我理解为“不能被改变的”,它可以修饰类、变量和方法。
所以我是否可以理解为被它所修饰的类、变量和方法都不能被改变呢?答案是”是“,因为有以下约束条件的存在:
1、final修饰类
被final修饰的类不能被继承,即它不能拥有自己的子类,
2、final修饰方法
被final修饰的方法不能被重写
3、final修饰变量
final修饰的变量,无论是类属性、对象属性、形参还是局部变量,都需要进行初始化操作。
即类String1不能是final类String的子类,这里已经很明显地看出String类是final的,但是到底是为了什么呢?先给出答案吧:
主要是为了”安全性“和”效率“的缘故,因为:
1、由于String类不能被继承,所以就不会被修改,这就避免了因为继承引起的安全隐患;
2、String类在程序中出现的频率比较高,如果为了避免安全隐患,在它每次出现时都用final来修饰,这无疑会降低程序的执行效率,所以干脆直接将其设为final一提高效率;
那么为什么保证String不可变呢,因为只有当字符串是不可变的,字符串池才有可能实现。字符串池的实现可以在运行时节约很多heap空间,因为不同的字符串变量都指向池中的同一个字符串。但如果字符串是可变的,那么String interning将不能实现,因为这样的话,如果变量改变了它的值,那么其它指向这个值的变量的值也会一起改变。
如果字符串是可变的,那么会引起很严重的安全问题。譬如,数据库的用户名、密码都是以字符串的形式传入来获得数据库的连接,或者在socket编程中,主机名和端口都是以字符串的形式传入。因为字符串是不可变的,所以它的值是不可改变的,否则黑客们可以钻到空子,改变字符串指向的对象的值,造成安全漏洞。
因为字符串是不可变的,所以是多线程安全的,同一个字符串实例可以被多个线程共享。这样便不用因为线程安全问题而使用同步。字符串自己便是线程安全的。
因为字符串是不可变的,所以在它创建的时候HashCode就被缓存了,不需要重新计算。这就使得字符串很适合作为Map中的键,字符串的处理速度要快过其它的键对象。这就是HashMap中的键往往都使用字符串。
String和StringBuiider的区别
- java中有三个类 String和StringBuilder和StringBuiffer来表示和操作字符串 字符串就是多个字符的集合。
String是内容不可变的字符串
String str = new String("bbbb");
而StringBuilder和StringBuiffer是内容可以改变的字符串
因为String底层使用了一个不可变的字符数组(final char[])。StringBuilder和StringBuiffer底层使用的可变的字符数组(没有使用final修饰),
private final char values[]; # String
char[] values; # SB
- 最经典的是拼接字符串。SB可以使用append追加。这里的append需要重新开创数组空间。
String : String c="a"+"b";
StringBuilder或StringBuffer: StringBuilder sb = new StringBuilder(); sb.append("a");
拼接字符串不能使用String去拼接,要使用StringBuilder(或StringBuffer)来拼接。
因为String会创建很多的中间对象而StringBuilder不用,效率高
- StringBuilder是线程不安全的,但效率较高。
StringBuffer是线程安全的,但效率较低。
因为保证线程安全需要加锁,这样效率就低了。
以append方法为例,StringBuffer需要加同步锁synchronized
,而StringBuilder不需要,所以效率不同。
三、对象与类
成员变量与局部变量的区别有那些
VB1. 从语法形式上,成员变量是属于类的,而局部变量是在方法中定义的变量或是方法的参数;成员变量可以被 public,private,static 等修饰符所修饰,而局部变量不能被访问控制修饰符及 static 所修饰;但是,成员变量和局部变量都能被 final 所修饰;
2. 从变量在内存中的存储方式来看:如果成员变量是使用static
修饰的,那么这个成员变量是属于类的,如果没有使用使用static
修饰,这个成员变量是属于实例的。而对象存在于堆内存,局部变量存在于栈内存
3. 从变量在内存中的生存时间上看:成员变量是对象的一部分,它随着对象的创建而存在,而局部变量随着方法的调用而自动消失。
4. 成员变量如果没有被赋初值:则会自动以类型的默认值而赋值(一种情况例外被 final 修饰的成员变量也必须显示地赋值);而局部变量则不会自动赋值。
四、继承
五、接口、lambda表达式与内部类
六、异常
七、高级
反射中 class.forname 和classloader的区别
java中class.forName()和classLoader都可用来对类进行加载。
class.forName()前者除了将类的.class文件加载到jvm中之外,还会对类进行解释,执行类中的static块。
而classLoader只干一件事情,就是将.class文件加载到jvm中,不会执行static中的内容,只有在newInstance才会去执行static块。
Class.forName(name, initialize, loader)带参函数也可控制是否加载static块。并且只有调用了newInstance()方法采用调用构造函数,创建类的对象
一 Java类装载过程
装载:通过类的全限定名获取二进制字节流,将二进制字节流转换成方法区中的运行时数据结构,在内存中生成Java.lang.class对象;
链接:执行下面的校验、准备和解析步骤,其中解析步骤是可以选择的;
校验:检查导入类或接口的二进制数据的正确性;(文件格式验证,元数据验证,字节码验证,符号引用验证)
准备:给类的静态变量分配并初始化存储空间;
解析:将常量池中的符号引用转成直接引用;
初始化:激活类的静态变量的初始化Java代码和静态Java代码块,并初始化程序员设置的变量值。
二 分析 Class.forName()和ClassLoader.loadClass
Class.forName(className)方法,内部实际调用的方法是 Class.forName(className,true,classloader);
第2个boolean参数表示类是否需要初始化, Class.forName(className)默认是需要初始化。
一旦初始化,就会触发目标对象的 static块代码执行,static参数也也会被再次初始化。
ClassLoader.loadClass(className)方法,内部实际调用的方法是 ClassLoader.loadClass(className,false);
第2个 boolean参数,表示目标对象是否进行链接,false表示不进行链接,由上面介绍可以,
不进行链接意味着不进行包括初始化等一些列步骤,那么静态块和静态对象就不会得到执行
说一下"=="和equals的区别
非常经典
先说清楚一个,再说另一个
**==判断两个变量之间的值是否相等(基本数据类型、引用)
变量可以分为基本数据类型和引用。如果是基本数据类型直接判断值,如果是引用则比较引用的内存的首地址**
![在这里插入图片描述](https://ws2.sinaimg.cn/large/006tKfTcly1g13mgem93pj30vp0f00uv.jpg)
内存分为栈和堆
上面的int的i和j为基本类型,比较值
Integer的i和j为对象的引用,比较内存的首地址(0x00001)
Equals用来比较两个对象长得是否一样,判断两个对象的某些特征是否一样?
实际上就是调用对象的equals的方法。这时候需要重写Object的equals方法
hashCode 与 equals (重要)
面试官可能会问你:“你重写过 hashcode 和 equals 么,为什么重写equals时必须重写hashCode方法?”
hashCode()介绍
hashCode() 的作用是获取哈希码,也称为散列码;它实际上是返回一个int整数。这个哈希码的作用是确定该对象在哈希表中的索引位置。hashCode() 定义在JDK的Object.java中,这就意味着Java中的任何类都包含有hashCode() 函数。
散列表存储的是键值对(key-value),它的特点是:能根据“键”快速的检索出对应的“值”。这其中就利用到了散列码!(可以快速找到所需要的对象)
为什么要有 hashCode
我们以“HashSet 如何检查重复”为例子来说明为什么要有 hashCode:
当你把对象加入 HashSet 时,HashSet 会先计算对象的 hashcode 值来判断对象加入的位置,同时也会与其他已经加入的对象的 hashcode 值作比较,如果没有相符的hashcode,HashSet会假设对象没有重复出现。但是如果发现有相同 hashcode 值的对象,这时会调用 equals()方法来检查 hashcode 相等的对象是否真的相同。如果两者相同,HashSet 就不会让其加入操作成功。如果不同的话,就会重新散列到其他位置。(摘自我的Java启蒙书《Head first java》第二版)。这样我们就大大减少了 equals 的次数,相应就大大提高了执行速度。
hashCode()与equals()的相关规定
- 如果两个对象相等,则hashcode一定也是相同的
- 两个对象相等,对两个对象分别调用equals方法都返回true
- 两个对象有相同的hashcode值,它们也不一定是相等的
- 因此,equals 方法被覆盖过,则 hashCode 方法也必须被覆盖
- hashCode() 的默认行为是对堆上的对象产生独特值。如果没有重写 hashCode(),则该 class 的两个对象无论如何都不会相等(即使这两个对象指向相同的数据)
构造器 Constructor 是否可被 override
在讲继承的时候我们就知道父类的私有属性和构造方法并不能被继承,所以 Constructor 也就不能被 override(重写),但是可以 overload(重载),所以你可以看到一个类中有多个构造函数的情况。
重载和重写的区别
重载: 发生在同一个类中,方法名必须相同,参数类型不同、个数不同、顺序不同,方法返回值和访问修饰符可以不同,发生在编译时。
重写: 发生在父子类中,方法名、参数列表必须相同,返回值范围小于等于父类,抛出的异常范围小于等于父类,访问修饰符范围大于等于父类;如果父类方法访问修饰符为 private 则子类就不能重写该方法。
接口和抽象类的区别是什么
- 接口的方法默认是 public,所有方法在接口中不能有实现(Java 8 开始接口方法可以有默认实现),抽象类可以有非抽象的方法
- 接口中的实例变量默认是 final 类型的,而抽象类中则不一定
- 一个类可以实现多个接口,但最多只能实现一个抽象类
- 一个类实现接口的话要实现接口的所有方法,而抽象类不一定
- 接口不能用 new 实例化,但可以声明,但是必须引用一个实现该接口的对象 从设计层面来说,抽象是对类的抽象,是一种模板设计,接口是行为的抽象,是一种行为的规范。
Exception和Error有什么区别?
典型回答
Exception 和 Error 都是继承了 Throwable类。在Java中只有Throwable类型的实例才可以被抛出(throw)和捕获(catch)。
Exception是程序正常运行中,可以预料到的意外情况,并且应该被捕获,进行相应处理。
Error是正常情况下,不大可能出现的情况。绝大部分的Error都会导致程序处于非正常的、不可恢复的状态。既然非正常,不便于捕获。比如OutOfMemoryError
Exception又分为可检查的和不检查的。可检查的必须显示地在源代码中进行捕获处理,这是编译器检查的一部分
不检查的异常是所谓的运行时错误。比如ArrayIndexOutOfBoundsException等,并不会强制在编译期检查。
补充回答
Error(错误):是程序无法处理的错误,表示运行应用程序中较严重问题。大多数错误与代码编写者执行的操作无关,而表示代码运行时 JVM(Java 虚拟机)出现的问题。例如,Java虚拟机运行错误(Virtual MachineError),当 JVM 不再有继续执行操作所需的内存资源时,将出现 OutOfMemoryError。这些异常发生时,Java虚拟机(JVM)一般会选择线程终止。
这些错误表示故障发生于虚拟机自身、或者发生在虚拟机试图执行应用时,如Java虚拟机运行错误(Virtual MachineError)、类定义错误(NoClassDefFoundError)等。这些错误是不可查的,因为它们在应用程序的控制和处理能力之 外,而且绝大多数是程序运行时不允许出现的状况。对于设计合理的应用程序来说,即使确实发生了错误,本质上也不应该试图去处理它所引起的异常状况。在 Java中,错误通过Error的子类描述。
Exception(异常):是程序本身可以处理的异常。Exception 类有一个重要的子类 RuntimeException。RuntimeException 异常由Java虚拟机抛出。NullPointerException(要访问的变量没有引用任何对象时,抛出该异常)、ArithmeticException(算术运算异常,一个整数除以0时,抛出该异常)和 ArrayIndexOutOfBoundsException (下标越界异常)。
谈谈Java反射机制,动态代理是基于什么原理?
Java是静态的强类型语言(语言类型信息在编译时检查)。
但是由于提供了类似于反射等机制,也具有了部分动态语言的特性
典型回答
反射机制是 Java 语言提供的一种基础功能,赋予程序在运行时自省的能力。
通过发射可以直接操作类或对象,比如获取某个对象的类定义,获取类声明的属性和方法,调用方法或构造对象。
动态代理是一种方便运行时动态构建代理、动态处理代理方法调用的机制
考点分析
反射引用运行时自省能力,通过运行时操作元数据或对象,Java可以灵活地操作运行时才能确定的信息。而动态代理是延伸出来的一种应用
动态代理
首先动态代理是一个代理机制。代理可以看做是对调用目标的一个包装,这样我们对目标代码的调用不是直接发生的,而是通过代理发生的
通过代理可以让调用者与实现者解耦。通过代理,可以提高更加友好的界面。
代理的发展经过从静态到动态的过程,源于静态代理引入的额外工作增加了很多的繁琐工作。利用动态代理技术,运行时生成静态文件,极大提高生产力。
AOP 通过(动态)代理机制可以让开发者从这些繁琐事项中抽身出来,大幅度提高了代码的抽象程度和复用度。
Java提供了哪些IO方式? NIO如何实现多路复用?
典型回答
Java IO 方式有很多种,基于不同的 IO 抽象模型和交互方式,可以进行简单划分
java.io
首先,传统的 java.io 包,它基于流模型实现,提供了我们最熟知的一些IO功能,比如File抽象、输入输出流等。
交互方式是同步、阻塞的方式。也就是说,在读取输入流或写入输出流时,在读写完成之前,线程会一直阻塞在那里。它们之间的调度是可靠的线性顺序。
java.io好处是简单,缺点是IO效率和扩展性存在局限性,容易成为性能扩展的瓶颈
java.nio
java1.4以后引入了NIO框架,提供了Channel、Selector、Buffer等新的抽象。可以构建多路复用的、同步非阻塞的IO程序
AIO
Java 7 中,NIO 有了进一步的改进,也就是 NIO 2 。引入了异步非阻塞 IO 方式,也有很多人叫它 AIO(Asynchronous IO)。异步 IO 操作基于事件和回调机制,可以简单理解为,应用操作直接返回,而不会阻塞在那里。当后台处理完成,操作系统会通知相应线程进行后续工作。
基本概念
- 区分同步与异步 同步是一种可靠的有序运行机制。当我们进行同步时,后续任务需要等待当前调用返回,才能进行下一步。而异步不需要等待当前调用返回。通过依靠事件、回调等机制实现
- 区分阻塞与非阻塞 阻塞是指当前线程会处于阻塞状态,无法从事其他任务,只有当条件就绪才就绪。非阻塞指不管IO是否结束,直接返回。
Java IO
- IO 不仅仅是对文件的操作,网络编程中,比如 Socket通信,都是典型的 IO 操作目标。
- 输入流、输出流(InputStream/OutputStream)是用于读取或写入字节的
- 而 Reader/Writer 则是用于操作字符,增加了字符编码能力
- BufferedOutputStream 等带缓冲区的实现,可以避免频繁的磁盘读写,进而提高 IO 处理效率
Java NIO
首先,熟悉一下 NIO 的主要组成部分:
-
Buffer,高效的数据容器,除了布尔类型,所有原始数据类型都有相应的 Buffer 实现。
-
Channel,是 NIO 中被用来支持批量式 IO 操作的一种抽象。
-
Selector,是 NIO 实现多路复用的基础,它提供了一种高效的机制,可以检测到注册在 Selector 上的多个 Channel 中,是否有 Channel 处于就绪状态,进而实现了单线程对多 Channel 的高效管理。
主要步骤
Java10、Java11、Java12的新特性
JAVA10
Java 10 作为新周期的第一个版本,提供了 109 项新特性,其中最受关注的则是局部变量的类型推断。
Java开始引用像脚本语言JavaScript中的var类型(弱类型),允许你通过var定义任何类型的变量。
开发者将能够声明变量而不必指定关联的类型。比如:
List list = new ArrayList ();
Stream stream = getStream();
可以被简化为:
var list = new ArrayList ();
var stream = getStream();
局部变量类型推断将引入" var "关键字的使用,而不是要求明确指定变量的类型
Java11新特性
字符串加强
Java 11 增加了一系列的字符串处理方法,如以下所示。
// 判断字符串是否为空白
" ".isBlank(); // true
// 去除首尾空格
" Javastack ".strip(); // "Javastack"
// 去除尾部空格
" Javastack ".stripTrailing(); // " Javastack"
// 去除首部空格
" Javastack ".stripLeading(); // "Javastack "
// 复制字符串
"Java".repeat(3); // "JavaJavaJava"
// 行数统计
"A\nB\nC".lines().count(); // 3
Java12新特性
对switch进行了增强,除了使用statement还可以使用expression,比如原来的写法如下:
现在可以改为如下写法:
switch (day) {
case MONDAY, FRIDAY, SUNDAY -> System.out.println(6);
case TUESDAY -> System.out.println(7);
case THURSDAY, SATURDAY -> System.out.println(8);
case WEDNESDAY -> System.out.println(9);
}
以及在表达式返回值
int numLetters = switch (day) {
case MONDAY, FRIDAY, SUNDAY -> 6;
case TUESDAY -> 7;
case THURSDAY, SATURDAY -> 8;
case WEDNESDAY -> 9;
};