Java基础
面向对象和面向过程的区别
面向过程是分析和解决问题的步骤,然后用函数把这些步骤实现,使用时进行调用
面向对象是一种思想,万物皆对象,Java是一个支持并发、基于类、[纯面向对象](# 接口、抽象类、普通类的区别)编程语言,面向对象的优点是代码模块化、复用性强、可读性高,更易维护
-
抽象:提取现实世界中事物的关键特征,进行建模的过程,这个抽象模型就是类,对类进行实例化得到对象,对同一事物在不同需求的情况下,需要提取的特征可能不一样
-
封装:隐藏对象的属性和实现细节,只暴露给外面必须的属性和操作;从而提高安全性、复用性,同时也将变化隔离,方便维护,保证了类的高内聚
-
继承:对现有类的一种复用机制,一个类继承现有的类,这个类将拥有被继承类的所有,非私有属性和方法
-
多态:多态是在继承的基础上实现的,[主要体现](# 重载和重写的区别)在父类引用指向子类对象,指向不同的子类对象时,调用相同的方法,呈现出不同的行为
重载和重写的区别
重载和重写都是多态的一种表现
重载
- 方法名相同,参数列表不同(类型、个数、顺序)
- 与访问修饰符无关
- 与抛出异常无关
- 与返回值类型无关,因为不能通过这些来判断重载
重写
- 方法名、参数列表、返回类型都相同
- 访问修饰符大于等于被重写方法
- 抛出异常的范围小于等于被重新方法
- 参数和返回类型小于等于被重写方法
super、this的区别
- this和super本质都是类中,所有方法的隐式参数,但在static方法中无法使用
- this和super都是指向当前对象
- 只不过super是指向的是父类成员的引用
- 而this是指向的是子类成员的引用
泛型的工作原理
泛型是JDK1.5引入,为了解决,不同数据类型执行相同的代码,达到代码复用的目的
类型在使用时指定,如果没指定类型,就会被替换为Object
泛型信息只存在于代码编译阶段,生成的字节码后,泛型信息会被类型擦除,所以只是伪泛型,这是为了与之前java版本的代码兼容,从而也导致了一些局限性
- 基本类型不能作为泛型
- 不能实例化泛型类
- 不能用instanceof判断泛型类型
- 不能实例化具体类型的泛型数组,但可实例化非具体类型的泛型数组,就是使用
?
无限定通配符
Java中基本数据类型
基本数据类型在声明时就会被分配空间,而引用类型声明时只是分配了引用空间, 必须通过实例化开辟空间之后才能赋值
- 整数型有:比特类、短整型、长整型、整形,大小分别是1、2、4、8字节
- 浮点型有:单精度浮点型、双精度浮点型,大小分别是4、8字节
- 还有布尔形和字符型,C语言字符类型占一个字节、Java字符类型占两个字节,因为Java使用的是Unicode编码
布尔类型比较特殊,在Java中整型和布尔类型不能相互转化,因为在JVM中没有供布尔值专用的字节码指令,所以
- 单个布尔类型在编译之后,使用int代替
- 而布尔数组在编译之后,使用byte数组代替
也就是说布尔类型单独使用是4个字节,在数组中是1个字节
之所以使用int,是因为32位的CPU,一次处理数据是32位,比较高效
Java自动装箱与拆箱
自动装箱就是,自动将基本数据类型转换为包装类型,隐式调用valueOf
方法,自动拆箱就是将这个操作反过来,隐式调用intValue
方法;在Java1.5后使用,直接将原始类型赋值给包装类,代替了new包装类的操作
- 使用Integer的
valueOf
方法创建Integer对象,如果是在-128到127闭区间内,会返回已缓存Integer对象 - Character类型中只缓存了0到127闭区间的Character对象
- 而在浮点类型的包装类中就没有实现缓存,因为在某个区间内的整型的个数是有限的,而浮点数却不是有限的
Object常用方法
toString()
:返回对象的字符串形式- [
getClass()
:返回该对象的Class对象](# Java创建对象的几种方式) - [
clone()
:受保护方法,用来实现对象的浅复制,只有实现了Cloneable接口才可以调用](# Java创建对象的几种方式) - [
finalize()
:对象被回收之前调用](# 垃圾回收算法) - [
equals()
:比较的是两个对象的内容是否相等](# ==、equals、hashCode的区别) - [
hashCode()
:返回根据对象的内存地址换算出的一个hash值](# ==、equals、hashCode的区别) - [
wait()/notify()/notifyALl()
:配合synchronized使用,当前线程等待唤醒方法](# wait/notify、sleep的区别)
==、equals、hashCode的区别
==
- 如果是对象的比较,比较的是地址,判断是否是同一个对象
- 两边的操作数必须是同一类型,也可以是父子类之间,才能编译通过
- 如果是数值的比较,值相等返回true
- 包括浮点数和整数的比较,比如double类型的10.0和int类型的10也是相等的,因为都指向地址为10的堆
equals
- equals继承自Object类的,用来比较的是两个对象的内容是否相等,如果没有重写equals方法,调用的是Object类中的equals方法,而Object中的equals方法返回的是==的判断
- 和常量进行比较时,把常量写在前面,来防止空指针异常
hashCode
- hashCode继承自Object类的,在Object中使用native修饰,返回根据对象的内存地址换算出的一个hash值
java的集合中的是Set集合,要求元素不重复,向set中插入元素的时候,可以通过equals方法判断集合中是否已存在该元素,但是如果元素太多,逐个判断就会很慢;然后就可以通过哈希算法来提高集合中查找元素的效率,将集合分成若干个区域,每个对象可以计算出一个哈希值,就可以通过哈希值进行分组,根据对象的哈希值就可以初步判断出对象的存储位置,这样一来,当要添加元素时,先调用hashCode方法得出存储区域
- 如果这个位置没有元素,就直接存上,不用再进行任何比较了
- 如果这个位置已有元素,就调用它的equals方法进行比较
- 相同的话就不存
- 不相同就散列其它的地址
这样一来就可以提高存储的效率
String、StringBuffer、StringBuilder的区别
String是只读字符串,是复合类型,字面量赋值字符串处于常量池中,new对象方式处于堆内存中,从底层源码来看是一个final类型的字符数组,一但定义,无法再增删改
每次对String的操作都会生成新的String对象,比如每次字符串拼接,会隐式在堆上创建一个和原字符串内容相同的StringBuilder对象,再调用append方法进行拼接
StringBuffer和StringBuilder他们两都继承了AbstractStringBuilder抽象类,底层都是可变的字符数组,所以在进行频繁的字符串操作时,要使用StringBuffer或StringBuilder,另外StringBuffer对方法加了同步锁,所以是[线程安全](# 多线程缺点)的,而StringBuilder没有
final、finally、finalize的区别
final
- 被final修饰的类不可以被继承、方法不可以被重写、变量引用不可变
- 被final修饰的常量,在编译阶段会存入常量池中
- 除了声明时赋值外
- 如果修饰类变量,还可以在静态代码块中赋初值
- 如果修饰成员变量,还可以在普通代码块、构造方法中赋初值
- 如果修饰局部变量,还可以在后续代码中赋初值
局部内部类和匿名内部类,只能访问局部final变量,因为内部类也会生成对应的Class文件,不会像局部变量那样,方法结束后就会被销毁,所以就可能会访问不存在的变量,为了解决这个问题,就将局部变量复制了一份,作为内部类的成员变量,为了保证复制的变量和原来的变量的一致性,也就是在内部类中修改,方法中的局部变量也会跟着被修改,就必须使用final局部变量,不会被改变,也就保证了一致性
finally
finally只能用在try catch语法中,并且try块是必须有,负责清理各种资源,不管是否出现异常都会执行,除非是在finally中出现异常,或者程序已经退出(System.exit()
),finally才不会被执行
当try catch中有return时,finally仍然会执行,因为finally是在方法返回之前、return表达式运算结束之后才执行,在方法返回前,会先将返回结果保存,不管finally中的代码怎么样,返回值都不可变,如果finally中包含return,程序会提前退出,返回值就不是之前保存的返回值了
finalize
是Object定义的方法,在对象被回收之前由[垃圾收集器](# 垃圾收集器)调用,是用来整理系统资源,或执行其他清理工作
接口、抽象类、普通类的区别
外部形式上
- 抽象类使用extends来继承,接口使用implements来实现
- 抽象类只能继承一个,接口可以实现多个
- 抽象类和接口都不能直接实例化,普通类可以直接实例化
内部形式上
- 抽象类除了可以有抽象方法外,还可以有普通方法,而接口中只能是public修饰的抽象方法,普通类不能包含抽象方法
- 抽象类中的成员变量可以是任意类型的,而接口中的成员变量只能是public static final修饰的类型
- 抽象类可以有静态代码块、静态方法、构造方法,而接口不能有
- 接口是为了定义行为,表示“就是”的关系
- 而抽象类是为了代码复用,表示“类似”的关系
jdk1.8新增了接口的默认方法和静态方法,为了解决接口内部的代码复用性,在jdk1.9新增了私有方法
如果接口多继承中,只有抽象方法冲突就等于没冲突,如果默认方法冲突子接口必须重写
Java创建对象的几种方式
- new创建新对象
- 通过反射机制,调用Class对象的
newInstance()
方法- 通过
getClass()
获取Class对象 - 通过
class
属性获取Class对象 - 通过Class的
forName()
静态方法获取Class对象
- 通过
- 采用clone机制(实现Cloneable接口的浅拷贝)
- 重写Object的
clone()
方法并调用(super.clone()
即可)
- 重写Object的
- 通过序列化机制(实现Serializable接口的深拷贝)
- 调用
ObjectOutputStream
类对象的writeObject()
方法序列化 - 调用
ObjectInputStream
类对象的readObject()
方法反序列化
- 调用
public Test deepClone() {
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bos);
oos.writeObject(this);
ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bis);
return (Test) ois.readObject();
}
Exception、Error的区别
Java中的所有异常和错误的顶级父类是Throwable,Throwable下有两个子类Exception和Error
- Error是程序无法处理的错误,编译器不检查
- 而Exception是程序可以处理的错误,不会导致程序停止,又分为两个部分,RunTimeException运行时异常和非RunTimeException(检查异常)
- RunTimeException是不可预知的,在程序的运行过程中出现,可以不进行捕获
- CheckedException是可预知的,在编译过程中出现,必须进行捕获
[Error](# OOM和SOF)
- NoClassDefFoundError(找不到class定义错误):从外存加载时字节码时,找不到class
- StackOverflowError(调用栈溢出错误)
- OutOfMemoryError(内存溢出错误)
CheckedException
- ClassNotFoundException(类找不到异常):从内存进行类连接时,找不到class
- IOException(IO操作异常)
- SQLException(SQL相关异常)
- FileNotFoundException(文件找不到异常)
RunTimeException
- NullPointerException(空指针异常)
- ClassCastException(类型转换异常)
- IllegalArgumentException(非法参数异常)
- IndexOutOfBoundsException(下标越界异常)
- NumberFormatException(数字格式化异常)
Comments NOTHING