03-Java基础

nobility 发布于 2020-01-03 1883 次阅读


Java基础

面向对象和面向过程的区别

面向过程是分析和解决问题的步骤,然后用函数把这些步骤实现,使用时进行调用

面向对象是一种思想,万物皆对象,Java是一个支持并发、基于类、[纯面向对象](# 接口、抽象类、普通类的区别)编程语言,面向对象的优点是代码模块化、复用性强、可读性高,更易维护

  • 抽象:提取现实世界中事物的关键特征,进行建模的过程,这个抽象模型就是类,对类进行实例化得到对象,对同一事物在不同需求的情况下,需要提取的特征可能不一样

  • 封装:隐藏对象的属性和实现细节,只暴露给外面必须的属性和操作;从而提高安全性、复用性,同时也将变化隔离,方便维护,保证了类的高内聚

  • 继承:对现有类的一种复用机制,一个类继承现有的类,这个类将拥有被继承类的所有,非私有属性和方法

  • 多态:多态是在继承的基础上实现的,[主要体现](# 重载和重写的区别)在父类引用指向子类对象,指向不同的子类对象时,调用相同的方法,呈现出不同的行为

重载和重写的区别

重载和重写都是多态的一种表现

重载

  1. 方法名相同,参数列表不同(类型、个数、顺序)
  2. 与访问修饰符无关
  3. 与抛出异常无关
  4. 与返回值类型无关,因为不能通过这些来判断重载

重写

  1. 方法名、参数列表、返回类型都相同
  2. 访问修饰符大于等于被重写方法
  3. 抛出异常的范围小于等于被重新方法
  4. 参数和返回类型小于等于被重写方法

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()即可)
  • 通过序列化机制(实现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(数字格式化异常)
此作者没有提供个人介绍
最后更新于 2020-01-03