参考JavaGuide和一些面经做的笔记,方便自己记忆
面向对象的设计思想是将需要解决的问题分解成一个个对象,这一个个对象可以用来描述在完成这件事中扮演的具体角色和行为。
面向过程的设计思想是将需要解决的问题拆分成一个个具体的步骤,然后设置这些步骤的实现方法,然后按照顺序一步步实现即可
优缺点:
面向对象相较于面向过程而言,有着易维护、易复用和易扩展的特点,由于面向对象有封装、继承和多态的特点,所以能够设计出低耦合的系统,易于去维护;而相对面向过程,面向对象性能较低
1.简单易学
2.面向对象(具有封装、继承和多态的特性)
3.安全性高
4.可靠性强
5.具有跨平台性(JVM),一处编译、处处运行
6.支持多线程
7.能够进行网络编程且很容易实现
8.java是一门半解释半编译的语言
JVM能够在不同的系统上,将源码编译成的字节码翻译成系统能够读懂的二进制机器码,进而可以达到一处编译,处处运行的特点
Java是半解释半编译语言,首先使用解释器进行预热编译,出现次数超过阈值的热点代码将会直接通过JIT编译器进行编译,热点代码编译后的机器指令将存储在方法区,下次可以直接使用
字节码是源代码编译后的产物,也就是.class文件,可以被JVM处理,只面向虚拟机。使用它的好处是Java 语言通过字节码的方式,在一定程度上解决了传统解释型语言执行效率低的问题,同时又保留了解释型语言可移植的特点。所以 Java 程序运行时比较高效,而且,由于字节码并不针对一种特定的机器,因此,Java 程序无须重新编译便可在多种不同操作系统的计算机上运行。
JDK是功能齐全的Java SDK。它拥有JRE拥有的一切,还拥有编译器(Javac)和一些工具(如javadoc)。他能够创建和编译程序。
JRE是Java运行时环境,是运行已编译的Java程序的所需所有内容的集合,包括有JVM,Java类库,java命令和其他的一些基础构件。但是他不能用于创建新程序。
简单来说,OpenJDK是sun公司开源的oracle jdk中除去商业功能部分的所有内容。
也就是说,oracle jdk是open jdk的一个实现,并不是完全开源的,open jdk是完全开源的。
oracle jdk比open jdk更加稳定,因为进过了彻底的测试和稳定,如果要进行商业用途,最好是用oracle jdk。
在响应性和jvm性能方面,oracle jdk性能同样比open jdk 更加优秀。
Oracle JDK 不会为即将发布的版本提供长期支持,用户每次都必须通过更新到最新版本获得支持来获取最新版本。
Oracle JDK 根据二进制代码许可协议获得许可,而 OpenJDK 根据 GPL v2 许可获得许可
1.都是面向对象的语言(有封装、继承、动态的特性)
2.java不提供指针直接访问内存,相对来说更加安全
3.java只支持单继承,cpp是多继承;java支持多实现接口
4.java内部有自动内存管理机制,不需要手动释放内存
5.C++字符串尾部会有一个’\0’作为结尾符,但是java语言中并没有
主类是java程序执行的入口点。一个程序可以有多个类,但是主类只能有一个。在应用程序中,主类可以不要求public修饰,是拥有main()方法的类;而在小程序中,主类必须要求是public修饰,这个主类是一个继承自系统类 JApplet 或 Applet 的子类
简单来说,java应用程序是从主线程中启动,也就是main()方法启动,app小程序主要是嵌在浏览器页面上运行(调用init()或者是run()方法来启动),嵌入浏览器这点和小游戏类似
1.形式上:字符型常量是用单引号’'引起来的单个字符;字符串常量是""若干个字符引起来的
2.含义上:字符型常量相当于一个整型值(ASCII值),可以参与表达式运算;字符串常量代表一个地址(描述字符串常量在内存中存放的位置)
3.占内存大小:字符型常量占2个字节;字符串常量占若干个字节
构造器不能被重写,但是可以被重载,重载方式:方法名一致,参数类型或参数个数不一样。
重载:同样的方法,根据输入数据的不同,做出不同处理
重写:子类继承父类的相同方法,输入数据需要一致,要做出有别于父类的处理和响应,覆盖父类方法
重载是发生在一个类中,方法名相同,参数类型、个数、顺序不同,方法返回值和访问修饰符也可以不同的方法
重写发生在运行期,能对子类对父类允许访问的方法内部逻辑进行改变实现
封装
封装是将一个对象的属性进行私有化,给外界访问提供一些方法来访问这些私有属性,如果不想外界访问也可以不提供这些方法,但这个类存在也没什么意义了。
继承
继承是使用已存在的类来建立新的类的技术,新的类可以定义新的属性和新的方法,能够使用父类的技术,但是必须完全继承自存父类,不能选择性的继承。通过使用继承我们可以很方便的复用以前的代码
多态
指在程序定义中引用变量所指向的具体类型和通过该引用变量发出的方法具体调用对象在编译的时候并不确定,即引用变量指向哪个类的实例对象,引用对象调用的方法到底是哪个类发出的是并不确定,只有在程序运行时才能确定。
Java实现多态可以是继承和实现
String的不可变性:
String类是已经被声明为final的, 不可被继承
String代表不可变的字符序列(简称不可变性)
StringBuilder 与 StringBuffer 的区别
StringBuilder 与 StringBuffer 都继承自 AbstractStringBuilder 类,都有相同的一些方法比如append,indexof等
从线程安全性上来说,String对象是不可变的,可以理解成常量,是线程安全的。StringBuffer对内部方法调用了同步锁,是线程安全的;而StringBuilder没有加锁,是线程不安全的。
从性能上来说,当然是加锁了StringBuffer性能比较慢,而没有加锁的StringBuilder比较快
对于三者使用的总结:
装箱:将基本类型用它们对应的引用类型包装起来;比如Integer i = 20;//将20包装成Integer
拆箱:将包装类型转换为基本数据类型;比如int a = i;//将上面的包装类型进行拆箱
使用integer时,数值在[-128,127]之间,便返回指向IntegerCache.cache中已经存在的对象的引用;否则创建一个新的Integer对象。
public class Main {
public static void main(String[] args) {
Integer i1 = 100;
Integer i2 = 100;
Integer i3 = 200;
Integer i4 = 200;
System.out.println(i1==i2);//true,指向同一个对象
System.out.println(i3==i4);//false指向不同对象
}
}
public class Test1 {
public static void main(String[] args) {
Double i1 = 100.0;
Double i2 = 100.0;
Double i3 = 200.0;
Double i4 = 200.0;
System.out.println(i1==i2);//false,直接创建两个不同对象
System.out.println(i3==i4);//false,直接创建两个不同对象
}
}
注意,Integer、Short、Byte、Character、Long这几个类的valueOf方法的实现是类似的。Double、Float的valueOf方法的实现是类似的。
由于静态方法可以不通过对象进行调用,因此在静态方法里,不能调用其他非静态变量,也不可以访问非静态变量成员。
一个非静态成员在未实例化前是不存在的,静态方法是不用实例化就可以调用的,所以调用不到非静态变量、成员和方法。
如果有该类有子类,就会在调用子类实例过程,在构造方法中默认调用super()。如果父类没有无参数构造方法,只有有参数构造方法,父类就不会默认创建一个无参构造方法,导致super()调用失败。
java和javax都是Java的API包,java为baijava语言的核du心包,javax为java语言的扩展包。
java包是java基础zhi核心类库,也就是Java Development kit ,提供java语言编程核dao心包,如io、awt、集合库(如Collection、List、Map)等;
javax是java基础上的扩展包,如swing、servlet、jsp、xml等类库。
总结一下 jdk7~jdk9 Java 中接口概念的变化:
1.成员变量属于类,局部变量属于方法。成员变量可以使用访问权限符和static来修饰,而成员变量不行。他们都可以用final来修饰
2.存储方式也有区别。如果成员变量用static修饰,这个成员变量是属于类的,是类成员变量;如果没有用static,这个成员变量是属于实例的,只有实例对象可以使用。局部变量为基本数据类型,则存储在栈内存内,如果是引用数据类型,则存储堆中引用对象的地址或者是指向常量池的地址
3.从生命周期来看,成员变量和实例对象是一样的;局部变量随着方法的生命周期一致。
4.成员变量会被默认赋值,而局部变量不会默认赋值,必须手动赋值
new运算法。new的引用放在栈中,new的实体放在堆内存中。一个对象引用可以指向0个或者1个对象实例,一个对象实例可以被0-n个对象引用指向
方法的返回值是指我们获取到的某个方法体中的代码执行后产生的结果。返回值就是一个值,可以用于操作运算和赋值
对一个类进行初始化。能。默认会创建一个无参构造方法
1.构造方法名字和类相同
2.没有返回值,但是不能用void
3.产生实例的时候自动调用
1.调用方式来说,实例方法的调用必须通过实例后的对象.方法进行调用,而静态方法无序实例化,直接类名.方法就可以调用,也可以通过对象.方法进行调用
2.静态方法只允许调用本类的静态方法和静态变量,因为执行前就已经初始化;而实例方法无限制
对象的相等是内存中的内容是否相等,而引用相等是地址是否相等
帮助子类做初始化工作
==:
如果==比较的是基本数据类型,那就是比较他们的值是否相等。
如果==比较是的引用数据类型,就是比较他们的地址是否相等。
equals():
比较两个对象是否相等。如果没有重写equals,作用和==类似。如果重写了equals,一般重写都是比较内容了
拿String来说:
面试官可能会问你:“你重写过 hashcode 和 equals 么,为什么重写 equals 时必须重写 hashCode 方法?”
hashCode() 的作用是获取哈希码,也称为散列码;它实际上是返回一个 int 整数。这个哈希码的作用是确定该对象在哈希表中的索引位置。hashCode() 定义在 JDK 的 Object.java 中,这就意味着 Java 中的任何类都包含有 hashCode() 函数。
散列表存储的是键值对(key-value),它的特点是:能根据“键”快速的检索出对应的“值”。这其中就利用到了散列码!(可以快速找到所需要的对象)
我们先以“HashSet 如何检查重复”为例子来说明为什么要有 hashCode: 当你把对象加入 HashSet 时,HashSet 会先计算对象的 hashcode 值来判断对象加入的位置,同时也会与该位置其他已经加入的对象的 hashcode 值作比较,如果没有相符的 hashcode,HashSet 会假设对象没有重复出现。但是如果发现有相同 hashcode 值的对象,这时会调用 equals()
方法来检查 hashcode 相等的对象是否真的相同。如果两者相同,HashSet 就不会让其加入操作成功。如果不同的话,就会重新散列到其他位置。(摘自我的 Java 启蒙书《Head first java》第二版)。这样我们就大大减少了 equals 的次数,相应就大大提高了执行速度。
通过我们可以看出:hashCode()
的作用就是获取哈希码,也称为散列码;它实际上是返回一个 int 整数。这个哈希码的作用是确定该对象在哈希表中的索引位置。hashCode()
在散列表中才有用,在其它情况下没用。在散列表中 hashCode() 的作用是获取对象的散列码,进而确定该对象在散列表中的位置。
public class Student {
int age;
String name ;
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Student student = (Student) o;
return age == student.age &&
Objects.equals(name, student.name);
}
@Override
public int hashCode() {
return Objects.hash(age, name);
}
}
方法参数是基本数据类型的话,就是将数据进行拷贝后执行方法体;如果是引用数据类型,就可以改变指向的具体内存中的数据。
值传递是对基本型变量而言的,传递的是该变量的一个副本,改变副本不影响原变量.
引用传递一般是对于对象型变量而言的,传递的是该对象地址的一个副本, 并不是原对象本身 。 所以对引用对象进行操作会同时改变原对象.一般认为java内的传递都是值传递.
线程与进程相似,但线程是一个比进程更小的执行单位。一个进程在其执行的过程中可以产生多个线程。与进程不同的是同类的多个线程共享同一块内存空间和一组系统资源,所以系统在产生一个线程,或是在各个线程之间作切换工作时,负担要比进程小得多,也正因为如此,线程也被称为轻量级进程。
程序是含有指令和数据的文件,被存储在磁盘或其他的数据存储设备中,也就是说程序是静态的代码。
进程是程序的一次执行过程,是系统运行程序的基本单位,因此进程是动态的。系统运行一个程序即是一个进程从创建,运行到消亡的过程。简单来说,一个进程就是一个执行中的程序,它在计算机中一个指令接着一个指令地执行着,同时,每个进程还占有某些系统资源如 CPU 时间,内存空间,文件,输入输出设备的使用权等等。换句话说,当程序在执行时,将会被操作系统载入内存中。 线程是进程划分成的更小的运行单位。线程和进程最大的不同在于基本上各进程是独立的,而各线程则不一定,因为同一进程中的线程极有可能会相互影响。从另一角度来说,进程属于操作系统的范畴,主要是同一段时间内,可以同时执行一个以上的程序,而线程则是在同一程序内几乎同时执行一个以上的程序段。
1.初始状态:线程被构建,未调用start()方法
2.运行状态:java将运行和就绪都成为运行状态
3.阻塞状态:表示线程被阻塞
4.等待状态:表示该线程进入等待,需要线程通知或中断
5.超时等待状态:不同于等待,可以在指定的时间自行返回
6.终止状态:表示当前线程已被执行完毕
当线程执行 wait()
方法之后,线程进入 **WAITING(等待)**状态。进入等待状态的线程需要依靠其他线程的通知才能够返回到运行状态,而 TIME_WAITING(超时等待) 状态相当于在等待状态的基础上增加了超时限制,比如通过 sleep(long millis)
方法或 wait(long millis)
方法可以将 Java 线程置于 TIMED WAITING 状态。当超时时间到达后 Java 线程将会返回到 RUNNABLE 状态。当线程调用同步方法时,在没有获取到锁的情况下,线程将会进入到 BLOCKED(阻塞) 状态。线程在执行 Runnable 的run()
方法之后将会进入到 TERMINATED(终止) 状态。
final主要用于变量、方法和类中
1.变量中:被final修饰的基本数据类型变量必须被显式地初始化,且初始化以后不能被改变;被final修饰的引用变量在初始化以后不能指向别的对象。
2.方法中:被final修饰的方法无法被其子类重写;同时能够在一定程度上提升方法执行效率。类中所有的private方法被隐式定义为了final方法
3.类中:被final修饰的类无法被继承,类中的所有成员方法都默认修饰final
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vz3l8SGc-1597310853805)(D:\学习笔记\面试题复习\imgs\java基础\Exception.png)]
在 Java 中,所有的异常都有一个共同的祖先 java.lang 包中的 Throwable 类。Throwable: 有两个重要的子类:Exception(异常) 和 Error(错误) ,二者都是 Java 异常处理的重要子类,各自都包含大量子类。
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 (下标越界异常)。
注意:异常和错误的区别:异常能被程序本身处理,错误是无法处理。
在以下 4 种特殊情况下,finally 块不会被执行:
序列化:将java对象转换成字节序列;反序列化:将字节序列恢复成java对象
对于不想进行序列化的变量,使用 transient 关键字修饰。
transient 关键字的作用是:阻止实例中那些用此关键字修饰的的变量序列化;当对象被反序列化时,被 transient 修饰的变量值不会被持久化和恢复。transient 只能修饰变量,不能修饰类和方法。
Scanner和BufferedReader都可以读入键盘输入
方法 1:通过 Scanner
Scanner input = new Scanner(System.in);
String s = input.nextLine();
input.close();Copy to clipboardErrorCopied
方法 2:通过 BufferedReader
BufferedReader input = new BufferedReader(new InputStreamReader(System.in));
String s = input.readLine();
IO流分类
基类是InputStream、Reader、OutputStream、Writer
既然有了字节流,为什么还要有字符流?
问题本质想问:不管是文件读写还是网络发送接收,信息的最小存储单元都是字节,那为什么 I/O 流操作要分为字节流操作和字符流操作呢?
回答:字符流是由 Java 虚拟机将字节转换得到,需要->字节流->另一端接收->转换成字符流读取。所以如果仅涉及文本内容的I/O流,直接使用字符流会更加方便,也不会产生乱码。
同步与异步
同步和异步的区别最大在于异步的话调用者不需要等待处理结果,被调用者会通过回调等机制来通知调用者其返回结果。
阻塞和非阻塞
BIO、NIO、AIO区别
Socket
和 ServerSocket
相对应的 SocketChannel
和 ServerSocketChannel
两种不同的套接字通道实现,两种通道都支持阻塞和非阻塞两种模式。阻塞模式使用就像传统中的支持一样,比较简单,但是性能和可靠性都不好;非阻塞模式正好与之相反。对于低负载、低并发的应用程序,可以使用同步阻塞 I/O 来提升开发速率和更好的维护性;对于高负载、高并发的(网络)应用,应使用 NIO 的非阻塞模式来开发简单来说,如果是对于基本数据类型而言,深拷贝和浅拷贝都仅仅是对值进行复制;而对于引用类型,深拷贝是拷贝一个内容完全一样的对象,而浅拷贝仅仅是拷贝地址。比如一个Int类型数据,无论深浅拷贝,都是拷贝这个数具体值,而例如一个int数组{1,2,3},深拷贝拷贝一个{1,2,3}的数组,地址不一致,浅拷贝则是拷贝数组地址
java序列化就是将对象转换成字节序列。实现方法就是让对应对象实现serialization接口,是一个标记性接口,不需要重写方法,然后构造一个ObjectOutPutStream对象输出流,使用writeObject方法将Obj写成字节序列
Lambda 表达式 :Lambda允许把函数作为一个方法的参数
方法引用: 方法引用提供了非常有用的语法,可以直接引用已有Java类或对象(实例)的方法或构造器。与lambda联合使用,方法引用可以使语言的构造更紧凑简洁,减少冗余代码。
默认方法:默认方法就是一个在接口里面有了一个实现的方法。
新工具:新的编译工具,如:Nashorn引擎 jjs、 类依赖分析器jdeps。
优点:
缺点:
java序列化就是把对象改成二进制的过程,可以保存到磁盘或者网络发送
1.实现java.io.serializable
2.java.io.externalizable
3.ObjectInputStream
4.ObjectOutputStream
用于跨平台数据交互
1.变量声明为静态变量 static
2.变量声明为瞬态变量 transient
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-r8hU6DYO-1597310853808)(D:\学习笔记\面试题复习\imgs\MySQL\瞬态变量.png)]
静态变量反序列化是直接从内存中拿值
修改属性,会报异常InvalidClassException无效类,因为每次修改会使得serialVersionUID改变,这时的办法是自己定义