Java基础知识
常见面试题:
- 面向对象和面向过程的区别
- Java和C++的区别
- JDK、JRE、JVM的区别和联系
- 字符型常量和字符串常量的区别
- Java泛型、类型擦除、常用的通配符?
- == 和equals的区别
- 为什么重写equals时必须重写hashCode方法?
- 基本数据类型,包装类、各种类型占用多少字节?
- String、StringBuffer、StringBuilder的区别?
- 为什么Java只有值传递?
- 深拷贝和浅拷贝的区别?
- 重载和重写的区别?
- 面向对象的三大特性?
- 抽象类和接口的局别?
- final、finally、finalize的区别?
- this和super的区别
- 反射
- 注解
- 场景异常
- transient关键字的作用
- 获取输入的两种方式
- BIO、NIO、AIO的区别
1. 面向对象和面向过程的区别
举一个实际的例子说明:(生产汽车)
-
⾯向对象 :汽车是又车窗、车轮、发动机等对象组成的,我们只需要将这些零件进行组装便能生产出一辆车,不用关心每个零件具体是怎么实现的。⾯向对象易维护、易复⽤、易扩展。但是 ⾯向对象性能⽐⾯向过程低。
-
⾯向过程 :在造车这个问题中,面向过程需要关心,车窗、车轮的具体的生成过程。⾯向过程性能⽐⾯向对象⾼。但是,⾯向过程没有⾯向对象易维护、易复⽤、易扩展。
2. Java和C++的区别
- 都是面向对象的语言,都支持封装、继承和多态
- Java 不提供指针来直接访问内存,程序内存更加安全
- Java 的类是单继承的,C++ 支持多重继承;虽然 Java 的类不可以多继承,但是接口可以多继承。
- Java 有自动内存管理垃圾回收机制(GC),不需要程序员手动释放无用内存。
- C ++同时支持方法重载和操作符重载,但是 Java 只支持方法重载(操作符重载增加了复杂性,这与 Java 最初的设计思想不符)。
3. JDK、JRE、JVM的区别和联系
- JDK:Java开发工具包,提供了Java的开发环境和运行环境,里面包含了JRE、Java源码编译器Javac和Java调试和分析工具。
- JRE: Java运行环境,为Java的运行提供了所需的环境。
- JVM:Java虚拟机,运行Java字节码的虚拟机,JVM有针对不同的系统的特定实现,目的是使用相同的字节码,运行出相同的结果。
- Java程序从源码到运行一般有三步:
- .java源文件 ---->JDK中的javac编译---->.class字节码文件---->JVM---->二进制机器码
4. 字符型常量和字符串常量的区别
- 形式上:字符型常量是单引号‘ ’引起来的常量,字符串常量采用的是双引号“ ”
- 含义上: 字符串常量是一个整形值(ASCII码值),字符串常量是一个地址值(该字符串在内存中存放的位置)
- 占用空间: 字符型常量占2个字节,字符串常量占用若干个字节。(注意:Java中一个字符占2个字节,16位)
5. Java泛型、类型擦除、常用的通配符?
- 泛型是JDK1.5引入的一个新特性,泛型提供了编译时类型安全检测机制,该机制允许程序员在编译时检测到非法的类型。
- Java的泛型是伪泛型,这是因为Java在编译期间,所有的泛型信息都会被擦掉,这也就是通常所说的类型擦除。
- 泛型一般有三种使用方式:泛型类、泛型接口、泛型方法
- 常用通配符:T、E、K、V、?
- ?表示不确定的java类型
- T(Type)表示具体的一个java类型
- KV(KeyValue)分别代表java中的key和value
- E(element)代表Element
6. == 和equals的区别
-
对于基本数据类型==比较的是值,对引用数据类型 ==比较的是对象的内存地址
因为 Java 只有值传递,所以,对于 == 来说,不管是比较基本数据类型,还是引用数据类型的变量,其本质比较的都是值,只是引用类型变量存的值是对象的地址。
-
equals()
作用不能用于判断基本数据类型的变量,只能用来判断两个对象是否相等。equals()
方法存在于Object
类中,而Object
类是所有类的直接或间接父类。 -
equals()
方法存在两种使用情况:-
类没有覆盖
equals()
方法 :通过equals()
比较该类的两个对象时,等价于通过“==”比较这两个对象,使用的默认是Object
类equals()
方法。 -
类覆盖了
equals()
方法 :一般我们都覆盖equals()
方法来比较两个对象中的属性是否相等;若它们的属性相等,则返回 true(即,认为这两个对象相等)。public class test1 { public static void main(String[] args) { String a = new String("ab"); // a 为一个引用 String b = new String("ab"); // b为另一个引用,对象的内容一样 String aa = "ab"; // 放在常量池中 String bb = "ab"; // 从常量池中查找 if (aa == bb) // true System.out.println("aa==bb"); if (a == b) // false,非同一对象 System.out.println("a==b"); if (a.equals(b)) // true System.out.println("aEQb"); if (42 == 42.0) { // true System.out.println("true"); } } }
-
-
总结:
==
对基本类型来说是值比较,对引用数据类型来说比较的是引用;而equals
默认情况下是引用比较,只是很多类重写了equals
方法,比如String
和Integer
等把它变成了值比较,所以一般情况下equals比较的是值是否相等。
7. hashCode()与equals()
-
两个对象的hashCode()相同,则equals也一定为true,对吗?
- 不对,两个对象hashCode()相同,equals不一定为true。
- String str1 = “通话”; String str2 = “重地”; hashCode: 都为1179395 equals: false。
-
为什么两个对象有相同的hashCode值,但是他们也不一定相等?
- 因为hashCode()使用的杂凑算法也许刚好会让多个对象传回相同的杂凑值,产生hash碰撞,同样的hashCode有多个对象时,采用equals()来判断是否真的相同。
- hashCode只是用来缩小查找成本的。
-
为什么重写equals时必须重写hashCode方法?
- 两个对象相等,则hashCode值一定是相同的。对两个对象分别调用equals方法都返回true。
- 但是两个对象hashCode值相同,equals不一定相等,因此,equals方法被重写时,hashCode方法也必须重写
- hashCode()的默认行为是对堆上的对象产生独特的值,如果没有重写hashCode(),则该class的两个对象无论如何都不会相等(即使两个对象指向相同的数据)。
总结一下: object类的常用方法
public final native Class<?> getClass()//native方法,用于返回当前运行时对象的Class对象,使用了final关键字修饰,故不允许子类重写。
public native int hashCode() //native方法,用于返回对象的哈希码,主要使用在哈希表中,比如JDK中的HashMap。
public boolean equals(Object obj)//用于比较2个对象的内存地址是否相等,String类对该方法进行了重写用户比较字符串的值是否相等。
protected native Object clone() throws CloneNotSupportedException//naitive方法,用于创建并返回当前对象的一份拷贝。一般情况下,对于任何对象 x,表达式 x.clone() != x 为true,x.clone().getClass() == x.getClass() 为true。Object本身没有实现Cloneable接口,所以不重写clone方法并且进行调用的话会发生CloneNotSupportedException异常。
public String toString()//返回类的名字@实例的哈希码的16进制的字符串。建议Object所有的子类都重写这个方法。
public final native void notify()//native方法,并且不能重写。唤醒一个在此对象监视器上等待的线程(监视器相当于就是锁的概念)。如果有多个线程在等待只会任意唤醒一个。
public final native void notifyAll()//native方法,并且不能重写。跟notify一样,唯一的区别就是会唤醒在此对象监视器上等待的所有线程,而不是一个线程。
public final native void wait(long timeout) throws InterruptedException//native方法,并且不能重写。暂停线程的执行。注意:sleep方法没有释放锁,而wait方法释放了锁 。timeout是等待时间。
public final void wait(long timeout, int nanos) throws InterruptedException//多了nanos参数,这个参数表示额外时间(以毫微秒为单位,范围是 0-999999)。 所以超时的时间还需要加上nanos毫秒。
public final void wait() throws InterruptedException//跟之前的2个wait方法一样,只不过该方法一直等待,没有超时时间这个概念
protected void finalize() throws Throwable { }//实例被垃圾回收器回收的时候触发的操作
8. 基本数据类型,包装类、各种类型占用多少字节?
-
Java中的8种基本数据类型:
- byte、short、int、 long 、 float、 double
- char
- boolean
-
对应的包装类
- Byte、Short 、Integer、Long、Float、Doubble
- Character
- Boolean
-
各类型占用的空间大小
基本数据类型 位数 字节数 默认值 boolean 8 1 false byte 8 1 0 short 16 2 0 char 16 2 ‘u0000’ int 32 4 0 float 32 4 0f long 64 8 0L double 64 8 0d
9. String、StringBuffer、StringBuilder的区别?
- String类中使用final关键字修饰字符数组来保存字符串,因此String声明的是不可变的对象,每次操作会生成新的String对象,然后将指针指向新的String对象。
- StringBuffer和StringBuilder可以在原有对象的基础上进行操作,StringBuffer和StringBuilder的最大区别在于:StringBuffer由于源码里面很多方法都加了Synchronized关键字,它是线程安全的,而StringBuilder没有Synchronized关键字,所有是非线程安全的,但StringBuilder的性能比StringBuffer要高一些。
- 使用:
- 操作少量数据的使用String;
- 单线程操作字符串缓冲区下的大量数据使用StringBuilder;
- 多线程操作字符串缓冲区下的大量数据使用StringBuffer.
10.为什么Java只有值传递?
- 参数传递给方法有两种方式: 值传递、引用传递
- Java 程序设计语言总是采用按值调用。也就是说,方法得到的是所有参数值的一个拷贝,也就是说,方法不能修改传递给它的任何参数变量的内容。
public static void main(String[] args) {
int num1 = 10;
int num2 = 20;
swap(num1, num2);
System.out.println("num1 = " + num1);
System.out.println("num2 = " + num2);
}
public static void swap(int a, int b) {
int temp = a;
a = b;
b = temp;
System.out.println("a = " + a);
System.out.println("b = " + b);
}
public static void main(String[] args) {
int[] arr = { 1, 2, 3, 4, 5 };
System.out.println(arr[0]);
change(arr);
System.out.println(arr[0]);
}
public static void change(int[] array) {
// 将数组的第一个元素变为0
array[0] = 0;
}
public class Test {
public static void main(String[] args) {
// TODO Auto-generated method stub
Student s1 = new Student("小张");
Student s2 = new Student("小李");
Test.swap(s1, s2);
System.out.println("s1:" + s1.getName());
System.out.println("s2:" + s2.getName());
}
public static void swap(Student x, Student y) {
Student temp = x;
x = y;
y = temp;
System.out.println("x:" + x.getName());
System.out.println("y:" + y.getName());
}
}
11. 深拷贝和浅拷贝?
-
浅拷贝: 对基本数据类型进行值传递,对引用数据类型进行引用传递般的拷贝。
-
深拷贝: 对基本数据类型进行值传递,对引用数据类型,创建一个新的对象,并复制其内容。
12. 重载和重写的区别?
-
重载: 同一类中多个同名方法根据不同的传参来执行不同的逻辑处理
- 重载发生在通一个类中,方法名必须相同,参数类型不同,个数不同,顺序不同,返回值和修饰符也可以不同
- 重载发生在编译期。
-
重写: 子类对父类允许访问的方法的实现过程进行重新编写。
-
重写遵循两同两小一大
- 子类的方法名和形参应和父类相同,
- 子类的返回值类型和抛出的异常应该比父类更小或相等,
- 子类的访问权限应该比父类更大或相等。
-
重写发生在运行期,
-
12. 封装、继承、多态指的是什么?
- 封装: 将类的属性用private关键字修饰,通过get和set方法访问。
- 继承: 子类继承父类的属性和方法,方便代码复用。
- 多态: 程序中定义的引用变量所指向的具体类型和通过引用变量发出的方法在编程时不指定,而是在允许时才确定。
- Java实现多态的两种形式:继承(多个子类对同一方法的实现)和接口(实现接口并覆盖接口中的同一方法)
13. 抽象类和接口的区别
- 接口中只能有抽象方法,抽象类中可以有非抽象的方法。
- 接口只能有static、final变量,抽象类则可以有其他变量。
- 一个类可以实现多个接口,但是只能实现一个抽象类。
- 接口的方法默认是
public
修饰,而抽象方法可以有public
、protected
、default
,不能用private。 - 总的来说: 抽象类关注的是类的本质是什么,是一种模版设计,接口关注类的操作行为是什么,是一种操作规范。
14. final,finally,finalize
- final: 在Java中
final
可以用来修饰类,方法和变量(成员变量或局部变量)。- 修饰类:使用final修饰的类的不能被其他类继承。当我们需要让一个类永远不被继承,此时就可以用final修饰,但要注意:final类中所有的成员方法都会隐式的定义为final方法。abstract类不能用final修饰。
- 修饰方法:使用final修饰的方法不能被重写。
- 修饰变量: 使用final修饰的变量表示的是常量,只能被赋值一次,并且不能在被修改。
- finally: 异常处理的一部分,只能用在try/catch语句中,无论是否捕获或处理异常,
finally
块里的语句都会被执行。当在 try块或 catch块中遇到 return语句时,finally
语句块将在方法返回之前被执行.在以下 3 种特殊情况下,finally块不会被执行:- 在
try
或finally
块中用了System.exit(int)
退出程序。但是,如果System.exit(int)
在异常语句之后,finally
还是会被执行 - 程序所在的线程死亡。
- 关闭 CPU。
- 在
- finalize:
finalize()
方法是在垃圾收集器删除对象之前调用的,用于确定这个对象没被引用。它是在Object类中定义的,因此所的类都继承了它。
15. this 和super
this
关键字用于引用类的当前实例- this.属性 访问引用类的当前实例的变量
- this.方法名() 调用引用类的当前实例的方法
super
关键字用于从子类访问父类的变量和方法- super.属性 访问父类的成员变量
- super.方法名() 调用父类的方法
- 使用
this
和super
需要注意的问题:- 在构造器中使用
super()
调用父类中的其他构造方法时,该语句必须处于构造器的首行,否则编译器会报错。 - this 调用本类中的其他构造方法时,也要放在首行。
- this、super不能用在static方法中。
- 在构造器中使用
16. 反射
- 反射机制: Java 中的反射机制是指在运行状态中,对于任意一个类都能够动态获取这个类所有的属性和方法的功能称为 Java 语言的反射机制。
- 反射的应用场景: 像 Spring/Spring Boot、MyBatis 等等框架中都大量使用了反射机制。这些框架中也大量使用了动态代理,而动态代理的实现也依赖反射。另外,像 Java 中的一大利器 注解 的实现也用到了反射。
- Java反射API
- Class类:反射的核心类,可以获取类的属性,方法等信息。
- Field类:Java.lang.reflec 包中的类,表示类的成员变量,可以用来获取和设置类之中的属性值。
- Method类: Java.lang.reflec 包中的类,表示类的方法,它可以用来获取类中的方法信息或者执行方法。
- Constructor类:Java.lang.reflec 包中的类,表示类的构造方法。
- 反射机制优缺点
- 优点 : 可以让咱们的代码更加灵活、为各种框架提供开箱即用的功能提供了便利
- 缺点 :让我们在运行时有了分析操作类的能力,这同样也增加了安全问题。
17. 注解
Annotation(注解)是 Java 提供的一种获取元程序中元素关联信息和元数据的途径和方法。Java5.0定义了4个标准的元注解
- @Target 修饰的对象范围
- @Retention **定义被保留的时间长短 **
- Source(源文件中保留)
- Class(class保留)
- Runtime(运行时保留)
- @Documented 描述javadoc
- Inherited 阐述了某个被标注的类型是被继承的
18. 异常
在 Java 中,所有的异常都有⼀个共同的祖先 java.lang 包中的 Throwable 类。 Throwable 类有两个重要的子类 Exception (异常)和 Error (错误)。 Exception 能被程序本身处理( try/catch ), Error 是⽆法处理的(只能尽量避免)。Exception 和 Error ⼆者都是 Java 异常处理的重要⼦类,各自都包含⼤量⼦类。
-
Exception :程序本身可以处理的异常,可以通过 catch 来进⾏捕获。 Exception ⼜可以分为 受检查异常(必须处理) 和 不受检查异常(可以不处理)。
- 受检查异常: IO相关的异常、ClassNotFoundException、SQLException
- 不受检查异常: RuntimeException及其子类(NullPointException、ArrayOutOfBoundsException、ClassCastException)
-
Error : Error 属于程序⽆法处理的错误 ,无法通过 catch 来进⾏捕获 。这些异常发⽣时,Java虚拟机⼀般会选择线程终⽌。
- Java 虚拟机运⾏错误( Virtual MachineError )
- 堆内存不足错误( OutOfMemoryError )
- 栈溢出错误(StackOverFlowError)
- 类定义错误( NoClassDefFoundError )
16. transient
对于不想进行序列化的变量,使用transient关键字修饰。transient 关键字的作⽤是:阻⽌⽤此关键字修饰的的变量被序列化;当对象被反序列化时,被 transient 修饰的变量值不会被持久化和恢复。transient 只能修饰变量,不能修饰类和⽅法。
17.IO流
17.1 获取输入的两种方式
- 通过Scanner
Scanner input = new Scanner(System.in);
String s = input.nextLine();
input.close();
- 通过BufferReader
BufferedReader input = new BufferedReader(new InputStreamReader(System.in));
String s = input.readLine();
17.2 常见的IO流及其分类
-
Java 中 IO 流分为⼏种?
-
按照流的流向分,可以分为输⼊流和输出流;
-
按照操作单元划分,可以划分为字节流和字符流;
-
按照流的⻆⾊划分为节点流和处理流。
-
-
Java I0 流的 40 多个类都是从如下 4 个抽象类基类中派⽣出来的。
-
InputStream/Reader: 所有的输⼊流的基类,前者是字节输⼊流,后者是字符输⼊流。
-
OutputStream/Writer: 所有输出流的基类,前者是字节输出流,后者是字符输出流。
-
17.3 BIO、NIO、AIO的区别
- BIO (Blocking I/O): 同步阻塞式 IO,就是我们平常使用的传统IO,数据的读取写⼊必须阻塞在⼀个线程内等待其完成。因此它的特点是模式简单、使用方便、并发处理能力低。
-
NIO (Non-blocking/New I/O): 同步⾮阻塞 I/O ,在 Java 1.4 中引⼊了NIO 框架,对应 java.nio 包,提供了 Channel , Selector,Buffer 等抽象。是传统IO的升级,⾯向缓冲区。对于低负载、低并发的应⽤程序,可以使⽤同步阻塞 I/O 来提升开发速率和更好的维护性;对于⾼负载、⾼并发的(⽹络)应⽤,应使⽤ NIO 的⾮阻塞模式来开发。
-
异步阻塞IO
-
AIO (Asynchronous I/O): 异步⾮阻塞的 IO 。异步 IO 是基于事件和回调机制实现的,也就是应⽤操作之后会直接返回,不会堵塞在那⾥,当后台处理完成,操作系统会通知相应的线程进⾏后续的操作。
17. BigDecimal
17.1 BigDecimal的用处
《阿里巴巴Java开发手册》中提到:浮点数之间的等值判断,基本数据类型不能用==来比较,包装数据类型不能用 equals 来判断。 具体原理和浮点数的编码方式有关,具体实例:
float a = 1.0f - 0.9f;
float b = 0.9f - 0.8f;
System.out.println(a);// 0.100000024
System.out.println(b);// 0.099999964
System.out.println(a == b);// false
具有基本数学知识的我们很清楚的知道输出并不是我们想要的结果(精度丢失),我们如何解决这个问题呢?一种很常用的方法是:使用使用 BigDecimal 来定义浮点数的值,再进行浮点数的运算操作。
BigDecimal a = new BigDecimal("1.0");
BigDecimal b = new BigDecimal("0.9");
BigDecimal c = new BigDecimal("0.8");
BigDecimal x = a.subtract(b);
BigDecimal y = b.subtract(c);
System.out.println(x); /* 0.1 */
System.out.println(y); /* 0.1 */
System.out.println(Objects.equals(x, y)); /* true */
17.2 BigDecimal的大小比较
a.compareTo(b)
: 返回 -1 表示 a
小于 b
,0 表示 a
等于 b
, 1表示 a
大于 b
。
BigDecimal a = new BigDecimal("1.0");
BigDecimal b = new BigDecimal("0.9");
System.out.println(a.compareTo(b));// 1
17.3 BigDecimal 保留几位小数
通过 setScale
方法设置保留几位小数以及保留规则。保留规则有挺多种,不需要记,IDEA会提示。
BigDecimal m = new BigDecimal("1.255433");
BigDecimal n = m.setScale(3,BigDecimal.ROUND_HALF_DOWN);
System.out.println(n);// 1.255
17.4. BigDecimal 的使用注意事项
- 我们在使用BigDecimal时,为了防止精度丢失,推荐使用它的 BigDecimal(String) 构造方法或者BigDecimal的valueOf来创建对象。
-
BigDecimal 主要用来操作(大)浮点数,BigInteger 主要用来操作大整数(超过 long 类型)。
-
BigDecimal 的实现利用到了 BigInteger, 所不同的是 BigDecimal 加入了小数位的概念