一网打尽Java基础

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

​ 面向过程:

​ 优点:性能比面向对象高,因为类调用时需要实例化,开销比较大,比较消耗资源;比如单片机、嵌入式开发、Linux/Unix等一般采用面向过程开发,性能是最重要的因素

​ 缺点:没有面向对象易维护、易复用、易扩展

​ 面向对象:

​ 优点:易维护、易复用、易扩展,由于面向对象有封装、继承、多态性的特征,可以设计出低耦合的系统,使系统更加灵活、更加易于维护

​ 缺点:性能比面向过程低

Java语言的特点

  1. 简单易学
  2. 面向对象(封装、继承、多态)
  3. 平台无关性(由Java虚拟机实现平台无关性)
  4. 可靠性
  5. 安全性
  6. 支持多线程(C++没有内置的多线程机制,因此必须调用操作系统的多线程功能来进行多线程程序设计,而Java语言提供了多线程支持)
  7. 支持网络编程
  8. 编译与解释并存

JVM JDK JRE

​ jvm:Java虚拟机是运行Java字节码的虚拟机。JVM有针对不同系统的特定实现(Windows,Linux,macOS),目的是使用相同的字节码,他们都会给出相同的结果。字节码和不同系统的JVM实现是Java语言“一次编译,到处运行”的关键所在


什么是字节码?采用字节码的好处是什么?

​ 在Java中,JVM可以理解的代码就叫做字节码(即扩展名为.class的文件),它不面向任何特定的处理器,只面向虚拟机。Java语言通过字节码的方式,在一定程度上解决了传统解释型语言执行效率低的问题,同时又保留了解释型语言可移植的特点。所以Java程序运行时比较高效,而且由于字节码并不专对一种特定的机器,因此,Java程序无需重新编译便可以在多种不同的操作系统上运行

java程序从源码到运行的步骤

​ .java文件(源代码)→(jdk中的javac编译)→.class文件(JVM可以理解的Java字节)→(JVM)→机器可以执行的二进制机器码

​ 需要注意的是 .class→机器码 这一步。这一步JVM类加载器首先加载字节码文件,然后通过解释器逐行解释执行,这种方式的执行速度会相对比较慢。而且,有些方法和代码块是经常需要被调用的,也就是所谓的热点代码,所以引进了JIT编译器,JIT属于运行时编译。当JIT编译器完成第一次编译后,其会将字节码对应的机器码保存下来,下次可以直接使用。而我们知道,机器码的运行效率肯定是高于Java解释器的。这也解释了我们为什么会经常说Java是编译与解释共存的语言


​ jdk:是Java Development Kit,是功能齐全的Java SDK。它拥有Jre所拥有的一切,还有编译器(javac)和工具(javadoc和jdb)。他能够创建和编译程序

​ jre:是Java运行时环境。它是运行已编译Java程序所需要的所有内容的集合,包括Java虚拟机(JVM),Java类库,Java命令和其他的一些基础构件。但是它不能用于创建新程序

Java和C++的区别

  • 都是面向对象的语言,都支持封装、继承和多态
  • Java不提供指针来直接访问内存,程序内存更加安全
  • Java的类是单继承的,C++支持多重继承;虽然Java的类不可以多继承,但是接口可以多继承
  • Java有自动内存管理机制,不需要程序员手动释放无用内存

字符型常量和字符串常量的区别

  • 形式上:字符常量是单引号引起的一个字符 字符串常量是双引号引起的若干个字符
  • 含义上:字符常量相当于一个整型值(ASCII值),可以参加表达式运算 字符串常量代表一个地址值(该字符串在内存中存放位置)
  • 占内存大小:字符常量只占两个字节 字符串常量占若干个字节(至少一个字符结束标志)
基本类型 大小 最小值 最大值 包装器类型
boolean - - - Boolean
char 16-bit Unicode 0 Unicode 2^16-1 Character
byte 8-bits -128 +127 Byte
short 16-bits -2^15 +2^15-1 Short
int 32-bits -2^31 +2^31-1 Integer
long 64-bits -2^63 +2^63-1 Long
float 32-bits IEEE754 IEEE754 Float
double 64-bits IEEE754 IEEE754 Double

构造器是否可以被重写

​ 父类的私有属性和构造方法并不能被继承,所以构造器就不能被重写,但是可以被重载。

重载和重写的区别

​ 重载:发生在同一个类中,方法名必须相同,参数类型不同、个数不同、顺序不同,方法返回值和访问修饰符可以不同,发生在编译时

​ 重写:发生在父子类中,方法名、参数列表必须相同,返回值范围小于等于父类,抛出的异常范围小于等于父类,访问修饰符范围大于等于父类;如果父类方法的访问修饰符为private则子类就不能重写该方法

Java面向对象编程三大特征:封装、继承、多态

​ 封装:把一个对象的属性私有化,同时提供一些可以被外界访问的属性的方法,如果属性不想被外界访问,我们大可不必提供方法给外界访问

​ 继承:继承是使用已存在的类的定义作为基础建立新类的技术,新类的定义可以增加新的数据或新的功能,也可以用父类的功能,但不能选择性地继承父类。通过使用继承我们能够非常方便地复用以前的代码

​ 关于继承:

  1. 子类拥有父类非private的属性和方法

  2. 子类可以拥有自己属性和方法,即子类可以对父类进行扩展

  3. 子类可以用自己的方式实现父类的方法

​ 多态:所谓多态就是指编程序中定义的引用变量所指向的具体类型和通过该引用变量发出的方法调用在编程时并不确定,而是在程序运行期间才确定,即一个引用变量到底会指向哪个类的实例对象,该引用变量发出的方法调用到底是哪个类中实现的方法,必须在由程序运行期间才能决定

​ 在Java中有两种形式可以实现多态:继承(多个子类对同一方法的重写)和接口(实现接口并覆盖接口中同一方法)

String StringBuffer和StringBuilder的区别 String为什么不可变

​ 可变性:简单来说,String类中使用final关键字字符数组保存字符串,private final char[] value;所以String对象是不可变的。而StringBuilder和StringBuffer都继承自AbstractStringBuilder类,在AbstractStringBuilder中也是使用字符数组保存字符串char[] value,但是没有用final关键字修饰,所以这两种对象都是可变的

​ 线程安全性:String中的对象是不可变的,也就可以理解为常量,线程安全。AbstractStringBuilder是StringBuilder和StringBuffer的公共父类,定义了一些字符串的基本操作,如append、insert、indexOf、expendCapacity等公共方法。StringBuffer对方法加了同步锁或者对调用的方法加了同步锁,所以是线程安全的。StringBuilder并没有对方法进行加同步锁,所以是非线程安全的

​ 性能:每次对String类型进行改变的时候,都会生成一个新的String对象,然后将指针指向新的String对象。StringBuffer每次都会对StringBuffer对象本身进行操作,而不是生成新的对象并改变对象引用。相同情况下使用StringBuilder相比使用StringBuffer仅能获得10%~15%左右的性能提升,但却要冒多线程不安全的风险

​ 总结:

​ 操作少量的数据 = String

​ 单线程操作字符串缓冲区下操作大量数据 = StringBuilder

​ 多线程操作字符串缓冲区下操作大量数据 = StringBuffer

自动装箱与拆箱

​ 装箱:将基本类型用它们对应的引用类型包装起来

​ 拆箱:将包装类型转换为基本数据类型

在Java中定义一个默认构造函数的作用

​ Java程序在执行子类的构造方法之前,如果没有用super()来调用父类特定的构造方法,则会调用父类中“没有参数的构造方法”。因此,如果父类中只定义了有参数的构造方法,而在子类中的构造方法中又没有用super()来显式调用父类中特定的构造方法,则在编译时将发生错误,因为Java程序在父类中找不到没有参数的构造方法可供执行。因此需要在父类中加上一个默认无参不执行操作的构造方法

import java和javax有什么区别

​ 刚开始的时候Java API所必须的包是java开头的包,javax当时只是作为扩展API包来使用。然而随着时间的推移,javax逐渐的扩展成为Java API的组成部分。但是将扩展从javax包移动到java包实在太麻烦,最终会破坏一堆现有的代码。因此,最终决定javax包将成为标准API的一部分

接口和抽象类的区别是什么

  1. 接口的方法默认是public,所有方法在接口不能有实现(Java 8开始接口方法可以有默认实现),抽象类可以有非抽象的方法
  2. 接口中的实例变量默认是final修饰的,而抽象类中则不一定
  3. 一个类可以实现多个接口,但最多只能继承一个抽象类
  4. 一个类实现接口的话要实现接口的所有方法,而抽象类则不一定
  5. 抽象类可以有main方法,并且我们能运行它;接口不能有main方法
  6. 接口不能用new实例化,但可以声明,但是必须引用一个实现该接口的对象,从设计层面来说,抽象是对类的抽象,是一种模板设计;接口是行为的抽象,是一种行为的规范
  7. 在jdk8中,接口也可以定义静态方法,可以直接用接口名调用

成员变量和局部变量的区别有哪些

  1. 从语法形式上看,成员变量是属于类的,而局部变量是在方法中定义的变量或是方法的参数;成员变量可以被public、private、static等修饰符所修饰,而局部变量不能被访问控制修饰符及static所修饰;但是,成员变量和局部变量都能被final所修饰
  2. 从变量在内存中的存储方式来看:如果成员变量是使用static修饰的,那么这个成员变量是属于类的,如果没有使用static修饰,则这个成员变量是属于实例的。而对象存在于堆内存,局部变量存在于栈内存
  3. 从变量在内存中的生存时间上看:成员变量是对象的一部分,它随着对象的创建而存在,而局部变量随着方法的调用而自动消失
  4. 成员变量如果没有被赋初值,则会自动以类型的默认值而赋值(一种情况例外:被final修饰的成员变量也必须显式的赋值);而局部变量则不会自动赋值

创建一个对象用什么运算符?对象实体与对象引用有什么不同

​ new运算符,new创建对象实例(对象实例存在堆内存中),对象引用指向对象实例(对象引用存放在栈内存中)。一个对象引用可以指向0个或1个对象实体;一个对象实体可以有n个引用指向它

什么是方法的返回值?返回值在类的方法里的作用

​ 方法的返回值是指我们获取到的某个方法体中的代码执行后产生的结果

​ 返回值的作用:接收输出结果,使得它可以用于其他的操作

一个类的构造方法的作用是什么

​ 主要作用是完成对类对象的初始化工作。一个类即使没有声明构造方法也会有默认的不带参数的构造方法

构造方法有哪些特性

​ 名字与类名相同

​ 没有返回值,但不能用void声明构造函数

​ 生成类的对象时自动执行,无需调用

静态方法和实例方法有什么不同

​ 在外部调用静态方法时,可以使用“类名.方法名”的方式,也可以使用“对象名.方法名”的方式。而实例方法只有后面这种方式。也就是说,调用静态方法可以无需创建对象

​ 静态方法在访问本类的成员时,只允许访问静态成员(即静态成员变量和静态方法),而不允许访问实例成员变量和实例方法;实例方法则无此限制

==和equals

:作用是判断两个对象的地址是不是相等。即,判断两个对象是不是同一个对象(基本数据类型比较的是值,引用数据类型==比较的是内存地址)

​ equals():它的作用也是判断两个对象是否相等。但它一般有两种使用情况:

​ 情况一:类没有重写equals()方法。则通过equals()比较该类的两个对象时,等价于通过“==”比较两个对象

​ 情况二:类重写了equals()方法。一般我们都重写equals()方法来判断两个对象的内容相等;若他们的内容相等,则返回true

hashCode()介绍

​ hashCode()的作用是获取哈希码,也称散列码;它实际上是返回一个int整数。这个哈希码的作用是确定该对象在哈希表中的索引位置。hashCode()定义在JDK的Object类中,这就意味着Java中的任何类都包含有hashCode()函数

​ 散列表存储的是键值对,它的特点是:能根据“键”快速的检索出对应的”值“。

为什么要有hashCode

​ 当我们把对象加入HashSet时,HashSet会先计算对象的hashCode值来判断对象加入的位置,同时也会与其他已经加入的对象的hashCode值作比较,如果没有相同的hashCode,HashSet会假设对象没有重复出现。但是如果发现有相同hashCode值的对象,这时会调用equals()方法来检查hashCode相等的对象是否真的相同。如果两者相同,HashSet就不会让其加入操作成功。如果不同的话,就会重新散列到其他位置。这样我们就大大减少了equals的次数,相应就提高了执行速度

fianl关键字

​ final关键字主要用在三个地方:变量、方法、类

  • 对于一个final变量,如果是基本数据类型的变量,则其数值一旦在初始化之后便不能更改;如果是引用类型的变量,则在对其初始化之后便不能再让其指向另一个对象
  • 当用final修饰一个类时,表明这个类不能被继承。final类中的所有成员方法都会被隐式地指定为final方法
  • 使用final方法的原因有两个。第一个原因是把方法锁定,以防任何继承类修改它的含义;第二个原因是效率。在早期的Java实现版本中,会将final方法转换为内嵌调用。但如果方法过于庞大,可能看不到内嵌调用带来的任何性能提升(现在的Java版本已经不需要使用final方法进行这些优化了)。类中所有private方法都隐式地指定为final

Java中的异常处理

一网打尽Java基础

以下四种特殊情况,finally块不会被执行
  • 在finally语句块第一行发生了异常。
  • 在前面的代码中用了System.exit(int)已退出程序。exit是带参函数;若该语句在异常语句之后,finally会执行
  • 程序所在的线程死亡
  • 关闭CPU
关于返回值
  • 如果有返回值,就把返回值保存到局部变量中
  • 执行jsr指令跳到finally语句里执行
  • 执行完finally语句后,返回之前保存在局部变量表里的值
  • 如果try,finally语句里均有return,忽略try的return,而使用finally里的return

Java序列化

​ 简单说就是为了保存在内存中的各种对象的状态(实例变量),并且可以把保存的对象状态再读出来。

​ 什么情况下需要序列化:

​ 当想要把内存中的对象状态保存到一个文件中或者数据库中的时候

​ 当想用套接字在网络上传输对象的时候

​ 当想通过RMI传输对象的时候

Java序列化中如果有些字段不想序列化

​ 对于不想序列化的变量,使用transient关键字修饰

​ transient关键字的作用是:阻止实例中那些用此关键字修饰的变量序列化;当对象被反序列化时,被transient修饰的变量不会被持久化和回复。transient只能修饰变量,不能修饰类和方法

反射

​ 反射主要是指程序可以访问、检测和修改它本身状态或行为的一种能力

​ Java反射机制主要提供了以下功能:

  • 在运行时判断任意一个对象所属的类
  • 在运行时构造任意一个类的对象
  • 在运行时判断任意一个类所具有的成员变量和方法
  • 在运行时调用任意一个对象的方法

动态代理

​ 当想要给实现了某个接口的类中的方法,加一些额外的处理。比如说加日志,加事务等。可以给这个类创建一个代理,顾名思义就是创建一个新的类,这个类不仅包含原来类方法的功能,而且还在原来的基础上添加了额外处理的新类。这个代理类并不是定义好的,而是动态生成的。具有解耦意义,灵活,扩展性强

​ 动态的代理的应用:

​ Spring的AOP

​ 加事务

​ 加权限

​ 加日志

为什么要使用克隆?如何实现克隆

​ 当想要对一个对象进行处理,又想保留原有的数据进行接下来的操作,就需要克隆了,Java语言中克隆针对的是类的实例

  1. 实现Cloneable接口并重写Object类中的clone()方法

  2. 实现Serializable接口,通过对象的序列化和反序列化实现克隆,可以实现真正的深度克隆

    基于序列化和反序列化实现的克隆不仅仅是深度克隆,更重要的是通过泛型限定,可以检查出要克隆的对象是否支持序列化,这项检查是编译器完成的,不是在运行时抛出异常,这种方案是明显优于使用Object类的clone方法克隆对象的。让问题在编译的时候暴露出来总是好过把问题留到运行时

深拷贝和浅拷贝的区别

​ 浅拷贝只是复制了对象的引用地址,两个对象指向同一个内存地址,所以修改其中任意的值,另一个值都会随之变化

​ 深拷贝是将对象及值复制过来,两个对象修改其中任意的值另一个值不会改变,这既是深拷贝

面向对象的“六原则一法则”

  • 单一职责原则:一个类只做它该做的事情(高内聚)
  • 开闭原则:软件实体应该对外扩展开放,对内修改关闭
  • 依赖倒置原则:面向接口编程
  • 里氏替换原则:任何时候都可以用子类类型替换掉父类类型
  • 接口分离原则:接口要小而专,不能大而全
  • 合成聚合复用原则:优先使用聚合或组合关系复用代码
  • 迪特米法则:一个对象应该对其他对象尽可能少的了解