当前位置:网站首页 > Java基础 > 正文

常见java基础问题



常问到的一些问题的归纳和总结,复刻到记忆里,每个一级目录为大专题,二级目录为小专题,小专题内有连环提问。

基本概念

从几个区别出发去讨论让记忆更深刻。【Java SE基础 一】基本概念和语言特性

Java 语⾔有哪些特点

Java具有简单性、面向对象、分布式、健壮性、安全性、平台独立与可移植性、多线程、动态性等特点 。Java可以编写桌面应用程序、Web应用程序、分布式系统和嵌入式系统应用程序等,Java语言包含如下9个特性:

  1. Java是简单易学的
  2. Java是完全面向对象的(封装、继承、多态)常见java基础问题;
  3. 平台⽆关性,Java 虚拟机实现平台⽆关性、跨平台,可移植
  4. 安全性 (编写的都是中间语言,最后jvm解析
  5. 健壮性(垃圾回收机制,异常处理机制
  6. Java支持多线程,C++ 语言没有内置的多线程机制,因此必须调用操作系统的多线程功能来进行多线程程序设计,而 Java 语言却提供了多线程支持
  7. Java是编译与解释并存类型的(比编译型的慢,但可跨平台
  8. Java是高性能的(翻译class文件是即时的,用到才解析
  9. ⽀持⽹络编程并且很⽅便( Java 语⾔诞⽣本身就是为简化⽹络编程设计的,因此 Java 语⾔不
    仅⽀持⽹络编程⽽且很⽅便)

可以看的出为什么现在我国互联网大厂技术栈已经成了Java的天下了。

编译型和解释型语言的区别

什么是编译型语言、什么是解释型语言,什么又是先编译后解释语言:

  • 编译型语言:编译型语言在程序执行之前,先会通过编译器对程序执行一个编译的过程,把程序转变成机器语言。运行时就不需要翻译,而直接执行就可以了。最典型的例子就是C(C/C++)语言。
  • 解释型语言:而是在程序运行的时候,通过解释器对程序逐行作出解释,然后直接运行,最典型的例子是Ruby(JavaScript、Perl、Python、Ruby、MATLAB),而其中Shell,JavaScript又是脚本语言,(脚本语言通常属于解释型语言,也叫动态语言,在运行时可以改变其结构的语言 脚本语言的程序是文本文件,并且是解释执行的)
  • 先编译后解释语言,随着Java等基于虚拟机的语言的兴起,我们又不能把语言纯粹地分成解释型和编译型这两种,用Java来举例,Java首先是通过编译器编译成字节码文件,然后在运行时通过解释器给解释成机器文件。所以我们说Java是一种先编译后解释的语言。

总而言之,编译型语言会把源文件直接编译为机器码执行,然后直接运行,解释型语言会让解释器在代码运行时再逐行解释,先编译后解释语言例如Java,则是将源文件编译为字节码文件,然后通过不同的JVM在不同的操作系统上解释为不同的机器码再运行。

Java 和 C++的区别

我们大一的时候一般都学过C语言,后来也都学过C++,那么Java和这些语言的共性与区别是什么呢?

  1. Java 是纯面向对象语言(JAVA或C#都是),所有代码都必须在类中出现,不存在所谓的全局变量或全局函数,但是c++是半面向对象半面向过程,可以定义全局变量和全局函数,都是⾯向对象语言,但Java更纯
  2. Java 不提供指针来直接访问内存,程序内存更加安全,Java 不使用指针
  3. Java 的类是单继承的,C++ ⽀持多重继承;虽然 Java 的类不可以多继承,但是接⼝可以多继承。Java 单继承
  4. Java 有⾃动内存管理机制,对于内存的分配是动态的不需要程序员手动释放无用内存Java 内存自动管理
  5. Java可以跨平台,而C++不可以,Java 跨平台
  6. Java 是先编译后解释型语言,C++是编译型语言,java的执行速度慢于c++。Java执行速度慢于C++

就我而言还是非常喜欢Java的,没有了讨厌的指针、内存可以自动回收,单继承还不显的乱。

JVM、JDK和JRE的区别

JVM、JDK与JRE三者之间的关系类似套娃,JRE包含JVM,JDK包含JRE:

  • Java 虚拟机(JVM)是运⾏ Java 字节码的虚拟机。JVM 有针对不同系统的特定实现(Windows,Linux,macOS),⽬的是使⽤相同的字节码,它们都会给出相同的结果。JVM能将相同的.class文件(字节码)—》不同系统的机器码
  • JRE 是 Java 运⾏时环境。它是运行已编译 Java 程序所需的所有内容的集合,包括 Java 虚拟机(JVM),Java 类库,Java 命令和其他的⼀些基础构件。但是,它不能⽤于创建新程序。JRE只能运行.class文件(字节码)—》机器码
  • JDK 是 Java Development Kit,它是功能⻬全的 Java SDK。它拥有 JRE 所拥有的⼀切,还有编译器(javac)和⼯具(如 javadoc 和 jdb)。它能够创建和编译程序。JDK能运行.java文件—》.class文件(字节码)—》机器码

如果只是为了运⾏⼀下 Java 程序的话,那么只需要安装 JRE 就可以了
在这里插入图片描述

基础语法

包含如下内容,Java的基本数据类型和转换。【Java SE基础 二】Java基本语法

Java有哪些数据类型

Java 中有两类数据类型:

  • 基本数据类型变量:存的是数据本身,基本数据类型包括 boolean(布尔型、1字节)、float(单精度浮点型、4字节)、char(字符型、2字节)、byte(字节型、1字节)、short(短整型、2字节)、int(整型、4字节)、long(长整型、8字节)和 double (双精度浮点型、8字节)
  • 引用类型变量:存的是保存数据的空间地址,引用数据类型包括:类、接口和数组三种。

基本数据类型转换规则

基本数据类型之间满足以下条件可以实现互相转换:

  1. 布尔类型和其他类型不可以互相转换
  2. byte,short,char不能互相转换,在做运算之前会自动转换为int类型。
  3. 容量小的类型可以自动转换为容量大的数据类型【byte,short,char—》int—》long—》float—》double】
    • 特殊情况: Java语言整型默认为int型,声明long型常量时候需要后边加’L’或者‘l’,,当赋值给long变量没有加‘ l ’或‘ L ’ ,仍旧默认成int类型。如果赋值的时候没有超出int表述范围没关系,即使超出了int的表述范围,也不用担心,不会报错但会导致数值计算错误
  4. 容量大的转换为容量小的需要强制转化,可能会丢失精度,特殊情况:
    1. 赋值的时候只要int类型的数没有超出(byte,short,char)的表述范围,可以直接byte a=23,
    2. Java语言默认浮点型常量为double型,如果要声明一个常量float型,则需要在后边加f或者F, 直接转不过去,直接转会导致溢出现象
  5. 多种混合计算时,自动将所有数据类型转换为容量最大的一种数据类型。

switch的判断条件可以使用哪些类型

  1. 其中整数表达式可以是基本类型int(byte,short,char)其对应的包装类
  2. 如果要用long,float,double,必须强制转为int才可以
  3. String类型在jdk1.7支持(先对字符串里的String值调用hashcode获取一个int类型的hash值,然后遍历所有case里字符串对应的hash值进行匹配,如果没匹配成功,则说明不存在,如果匹配成功,则接着调用字符串的equals操作进行匹配)equals的范围小于hashcode

运算符有哪几种、优先级是什么

括号级别最高,逗号级别最低,单目 > 算术 > 位移 > 关系 > 逻辑 > 三目 > 赋值
在这里插入图片描述

break、continue、return 的区别

  • break 跳出总上一层循环,不再执行循环(结束当前的循环体)
  • continue 跳出本次循环,继续执行下次循环(结束正在执行的循环 进入下一个循环条件)
  • return 程序返回,不再执行下面的代码(结束当前的方法 直接返回)

面向对象思想

从以下内容学习面向对象的相关知识【Java SE基础 三】面向对象思想与类模型

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

其实Java与C的区别可以理解为面向对象与面向过程的区别:

  • ⾯向过程⾯向过程性能⽐⾯向对象⾼。 因为类调⽤时需要实例化,开销⽐较⼤,⽐较消耗资源,所以当性能是最重要的考量因素的时候,⽐如单⽚机、嵌⼊式开发、Linux/Unix 等⼀般采⽤⾯向过程开发。但是,⾯向过程没有⾯向对象易维护、易复⽤、易扩展。性能高,开销低,不易扩展、复用和维护
  • ⾯向对象⾯向对象易维护、易复⽤、易扩展。 因为⾯向对象有封装、继承、多态性的特性,所以可以设计出低耦合的系统,使系统更加灵活、更加易于维护。但是,⾯向对象性能⽐⾯向过程低。性能低,开销高,易扩展、复用和维护

所以往往在不同的应用场景下我们选择不同的方式。

对象间有哪几种关系

对象之间主要有四种关系,关联关系(一个类中的某个方法调用另一个类),继承关系(父类与子类),聚合关系(整体和部分),实现关系(接口)

  • 关联关系:一般是一个类【A类】中的某个方法里的某个参数是另一个类【B类】的对象:一般是一个类中的方法里的某个参数是另一个类的对象
  • 聚合关系:聚集是一种松耦合,表明部分【A类】是整体【B类】的一部分,也即【A类】不一定非属于父对象;而组合是一种紧耦合,部分【A类】是整体【B类】必不可少的一部分。在属性层面上区分,一般是一个类中的某个成员变量是另一个类的对象
  • 继承关系子类是一种父类,子类来自于父类,拥有父类的全部内容,自己还可以加以扩展,java没有多重继承
  • 实现关系:接口抽象出一种行为,实现类去实现,具体怎么实现不用去管。方法层面上区分

其实这四种关系构成了我们后来对类与对象的使用模式

讲讲面向对象三大特性

  • 封装,封装把⼀个对象的属性私有化,同时提供⼀些可以被外界访问的属性的⽅法,如果属性不想被外界访
    问,我们⼤可不必提供⽅法给外界访问。但是如果⼀个类没有提供给外界访问的⽅法,那么这个类也没有什么意义了。

  • 继承,继承是使⽤已存在的类的定义作为基础建⽴新类的技术,新类的定义可以增加新的数据或新的功能,也可以⽤⽗类的功能,但不能选择性地继承⽗类。通过使⽤继承我们能够⾮常⽅便地复⽤以前的代码。关于继承如下 3 点请记住:

    1. ⼦类拥有⽗类对象所有的属性和⽅法(包括私有属性和私有⽅法),但是⽗类中的私有属性和⽅法⼦类是⽆法访问,只是拥有
    2. ⼦类可以拥有⾃⼰属性和⽅法,即⼦类可以对⽗类进⾏扩展。
    3. ⼦类可以⽤⾃⼰的⽅式实现⽗类的⽅法,也就是重写
  • 多态,所谓多态就是指程序中定义的引⽤变量所指向的具体类型和通过该引⽤变量发出的⽅法调⽤在编程时并不确定,⽽是在程序运⾏期间才确定,即⼀个引⽤变量到底会指向哪个类的实例对象,该引⽤变量发出
    的⽅法调⽤到底是哪个类中实现的⽅法,必须在由程序运⾏期间才能决定

    • 在 Java 中有两种形式可以实现多态:继承(多个⼦类对同⼀⽅法的重写)接⼝(实现接⼝并覆盖接⼝中同⼀⽅法)

类模型

从以下内容学习面向对象的相关知识【Java SE基础 三】面向对象思想与类模型

成员变量和局部变量的区别

一个类可以包含以下类型变量:

  • 局部变量:在方法、构造方法或者语句块中定义的变量被称为局部变量。变量声明和初始化都是在方法中,方法结束后,变量就会自动销毁。
  • 成员变量:成员变量是定义在类中,方法体之外的变量。这种变量在创建对象的时候实例化。成员变量可以被类中方法、构造方法和特定类的语句块访问。
  • 类变量(静态变量):类变量也声明在类中,方法体之外,但必须声明为 static 类型。

经过static修饰的类变量,该类的所有对象共享一份,成员变量为该对象独享,局部变量作用域则为一个对象中的一个方法或语句块。

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

静态方法和实例方法区别

  • 从方法调用角度看,成员方法只能通过对象引用进行调用,静态方法可以直接通过对象引用和类名进行调用
  • 从方法体内容角度看,在成员方法里可以访问静态和非静态的变量或方法,在静态方法里不能直接访问非静态成员,但是可以通过在静态方法体中new出一个对象,再用这个对象显式调用非静态成员来访
  • 从方法的初始化时机角度看,成员方法只在该类实例初始化后贮存在内存中,当该类调用完毕后会被垃圾回收器收集释放,在静态方法里不能直接访问非静态成员,但是可以通过在静态方法体中new出一个对象,再用这个对象显式调用非静态成员来访

构造方法的特征和使用原则

每个类都有构造方法。如果没有显式地为类定义构造方法,Java 编译器将会为该类提供一个默认构造方法。在创建一个对象的时候,至少要调用一个构造方法。构造方法的名称必须与类同名且没有返回值(返回值也不能为void),用来初始化对象的函数,一个类可以重载多个构造方法

构造方法有如下几条使用原则:

  • 构造方法可以被重载,重载就是方法签名中方法明相同但是参数列表不同的意思,但是构造方法不可以被重写
  • 一个构造方法可以通过this关键字调用另一个构造方法this语句必须位于构造方法的第一行
  • 子类通过super关键字调用父类的一个构造方法,也必须位于构造方法第一行,所以super和this不能共存由于this函数指向的构造函数默认有super()方法,所以规定this()和super()不能同时出现在一个构造函数中
  • 当一个类中没有定义任何构造方法,Java将自动提供一个缺省构造方法;只有在不显示声明构造方法时,系统才提供默认无参构造方法,也就是说系统不会总是提供,要看你提供没有。
  • 当子类的某个构造方法没有通过super关键字调用父类的构造方法,通过这个构造方法创建子类对象时,会自动先调用父类的缺省构造方法如果子类既没有用super调用父类的构造方法,而父类中又没有无参的构造函数,则编译出错。父类有参,必须用super调用,如果是多个有参,则子类构造方法任意选一个即可,如果只有一个那就选那个,父类如果无参,也可以用super,或者不用,则默认调用

总结而言就是:Java会默认给类添加一个缺省构造方法,构造方法可以重载多个,同类中可以相互通过显式this调,但必须放在方法体第一行;子类在使用时默认自动super父类的缺省构造方法,如果父类没有缺省构造方法换言之就是父类显示声明了构造方法,则如果声明的构造方法无参不需要显式super,如果有参必须显式super

Object类的方法

Java Object 类是所有类的父类,也就是说 Java 的所有类都继承了 Object,子类可以使用 Object 的所有方法
在这里插入图片描述

== 与 equals的区别

==常用于相同的基本数据类型之间的比较,也可用于相同类型的对象之间的比较;

  • 如果== 比较的是基本数据类型,那么比较的是两个基本数据类型的值是否相等;
  • 如果 ==是比较的两个对象,那么比较的是两个对象的引用,也就是判断两个对象是否指向了同一块内存区域;

equals方法主要用于两个对象之间,检测一个对象是否等于另一个对象

  • 情况1,类没有覆盖equals()方法。则通过equals()比较该类的两个对象时,等价于通过“==”比较这两个对象,也就是比较引用地址。
  • 情况2,类覆盖了equals()方法。一般,我们都覆盖equals()方法来两个对象的内容相等;若它们的内容相等,则返回true(即,认为这两个对象相等)。

为什么要有 hashCode?

hashCode() 的作用是获取哈希码,也称为散列码;它实际上是返回一个int整数。这个哈希码的作用是确定该对象在哈希表中的索引位置,散列表存储的是键值对(key-value),它的特点是:能根据“键”快速的检索出对应的“值”。这其中就利用到了散列码!(可以快速找到所需要的对象)

以“HashSet 如何检查重复”为例子来说明为什么要有 hashCode

当你把对象加入 HashSet 时,HashSet 会先计算对象的 hashcode 值来判断对象加入的位置,同时也会与其他已经加入的对象的 hashcode 值作比较,如果没有相符的hashcode,HashSet会假设对象没有重复出现。

但是如果发现有相同 hashcode 值的对象,这时会调用 equals()方法来检查 hashcode 相等的对象是否真的相同。如果两者相同,HashSet 就不会让其加入操作成功。如果不同的话,就会重新散列到其他位置。这样我们就大大减少了 equals 的次数,相应就大大提高了执行速度

hashCode和equals的区别

equals和hashCode都会用于比较方法,用于比较两个对象是否相等。

  • equals() 方法比较两个对象,是判断两个对象引用指向的是同一个对象,即比较 2 个对象的内存地址是否相等。也就是说即使两个对象相等,但是内存地址不同,引用指向的不是同一个也不能说两个对象相等,所以equals一般需要重写
  • hashCode()方法是求出两个对象的hash值然后进行比较,equals相等hashCode一定相等,反之不成立

注意:如果子类重写了 equals() 方法,就需要重写 hashCode() 方法,比如 String 类就重写了 equals() 方法,同时也重写了 hashCode() 方法。

为什么重写 equals 方法必须重写 hashcode 方法 ?

判断的时候先根据hashcode进行的判断,相同的情况下再根据equals()方法进行判断。如果只重写了equals方法,而不重写hashcode的方法,会造成hashcode的值不同,而equals()方法判断出来的结果为true。

在Java中的一些容器中,不允许有两个完全相同的对象,插入的时候,如果判断相同则会进行覆盖。这时候如果只重写了equals的方法,而不重写hashcode的方法,Object中hashcode是根据对象的存储地址转换而形成的一个哈希值。这时候就有可能因为没有重写hashcode方法,造成相同的对象散列到不同的位置而造成对象的不能覆盖的问题

深拷贝和浅拷贝的区别

  • 浅拷⻉:对基本数据类型进⾏值传递,对引⽤数据类型进⾏引⽤传递般的拷⻉,此为浅拷⻉。
  • 深拷⻉:对基本数据类型进⾏值传递,对引⽤数据类型,创建⼀个新的对象,并复制其内容,此
    为深拷⻉

继承和多态

分别从这三个特性出发提出一些问题,【Java SE基础 四】封装、继承、多态

重载和重写的区别

方法的重载和重写都是实现多态的方式,区别在于前者实现的是编译时的多态性,而后者实现的是运行时的多态性。

  • 重载 ,在同一个类中 ;方法名相同;方法的形参列表不同,具体的不同表现为: 类型、个数、顺序的不同才可以构成重载。需要注意的是方法的重载与方法的返回值类型与访问权限或是抛出异常无关
  • 重写,发生在子类与父类之间, 相同方法名称、参数列表和返回类型,也就是相同的方法签名和返回类型,访问限制宽松于父类,异常小于父类

重写有以下原则:

  • 重写的方法和被重写的方法必须具有相同方法名称、参数列表和返回类型相同的方法签名和返回类型
  • 重写方法不能使用比被重写的方法更严格的访问权限。也就是如果父类是public,子类最多就是public,访问限制修饰必须宽松于父类
  • 子类方法抛出的异常必须和父类方法抛出的异常相同,或者是父类方法抛出的异常类的子类异常必须小于父类
  • 在Java中静态方法可以被继承,但是不能被覆盖,即不能重写,父类的静态方法是不能被子类覆盖为非静态方法,父类的非静态方法不能被子类覆盖为静态方法重写不能修改方法类型【静态方法、成员方法】

接口与抽象类区别

首先来看看二者的相同点,总结而言就是都不能实例化

  1. 相同点是接口和抽象类都不能实例化
  2. 接口的实现类或抽象类的子类实现了接口或抽象类中的方法后才能实例化。

不同点就比较多了:

  1. 首先类可以实现多个接口,但只能继承一个抽象类从继承角度
  2. 接口体只能用 public 和 abstract 修饰,而抽象类的类访问修饰符除了(和abstract不搭的关键字列表系列)final,abstract,private,static,synchorized,native之外基本都可以从类的修饰符角度
  3. 抽象类可以有普通的成员变量,静态变量。而接口的变量默认为public static final,只能有静态的不可修改的变量,而且必须赋初值从变量角度
  4. 接口里的方法只能用public修饰,而抽象类的方法可以用除了(和abstract不搭的关键字列表系列)final,abstract,private,static,protected,synchorized,native的方法修饰符。并且接口只有未实现方法,但抽象类可以有普通方法从方法角度
  5. 接口不能有构造函数,抽象类可以有构造函数。从构造函数角度
  6. 接口可以多重实现,抽象类不可以实现多个父类

什么场合下使用抽象类、什么场合下使用接口

如果发现某种东西会成为基础类,首先把其定义为接口,接口是极端的抽象类:

  • 如果创建的组件会有多个版本,则最好创建抽象类,如果创建的功能将在大范围内相异的小而简练的功能块,则使用接口。如果要设计大的功能单元则使用抽象类。
  • 抽象类主要用于关系密切的对象(共性大于个性),而接口适合为不相关的类提供通用功能(个性大于共性)。
  • 接口多定义对象的行为,抽象类多定义对象的属性(抽象类里不应该有太多方法)
  • 接口不变原则,尽量增加接口而不是更改原有接口。尽量将接口设计成功能单一的模块儿。一个接口最好只能做一件事情。

Java语言是如何实现多态的

多态是同一个行为具有多个不同表现形式或形态的能力。多态就是同一个接口,使用不同的实例而执行不同操作。多态实现的必要条件如下:

  • 要有继承或者接口实现
  • 要有重写,如果是继承就是重写父类方法,如果是接口就是重写接口实现的方法
  • 父类引用指向子类对象(向上转型),或者接口指向子类对象,总之就是向上转型。

关键字

聊一聊常用的关键字,用法和区别,整篇Blog都是干货,全文理解:【Java SE基础专题 一】常用关键字对比

String相关

关于String的一系列问题【Java SE基础专题 二】String类型详解

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

  • 形式上: 字符常量是单引号引起的一个字符,字符串常量是双引号引起的若干个字符;

  • 含义上: 字符常量相当于一个整型值( ASCII 值),可以参加表达式运算;字符串常量代表一个地址值(该字符串在内存中存放位置,相当于对象;

  • 占内存大小:字符常量只占2个字节;字符串常量占若干个字节(至少一个字符结束标志) (注意: char 在Java中占两个字节)

什么是字符串常量池

java中常量池的概念主要有三个:全局字符串常量池,class文件常量池,运行时常量池。我们现在所说的就是全局字符串常量池

  1. 全局常量池在每个VM中只有一份,存放的是字符串常量的引用值。全局字符串池里的内容是在类加载完成,经过验证,准备阶段之后在堆中生成字符串对象实例,然后将该字符串对象实例的引用值存到string pool中(记住:string pool中存的是引用值而不是具体的实例对象,具体的实例对象是在堆中开辟的一块空间存放的)
  2. class常量池是在编译的时候每个class都有的,在编译阶段,存放的是各种字面量和符号引用。
  3. 运行时常量池是在类加载完成之后,将每个class常量池中的符号引用值转存到运行时常量池中,也就是说,每个class都有一个运行时常量池,类在解析之后,将符号引用替换成直接引用,与全局常量池中的引用值保持一致

jvm为了提升性能和减少内存开销,避免字符的重复创建,其维护了一块特殊的内存空间,即字符串池,当需要使用字符串时,先去字符串池中查看该字符串是否已经存在,如果存在,则可以直接使用,如果不存在,初始化,并将该字符串放入字符串常量池中。

字符串常量池的位置也是随着jdk版本的不同而位置不同。在jdk6中,常量池的位置在永久代(方法区)中,此时常量池中存储的是对象。在jdk7中,常量池的位置在堆中,此时,常量池存储的就是引用了。在jdk8中,永久代(方法区)被元空间取代了

String str="aaa"与 String str=new String(“aaa”)一样吗?

  • 使用String a = “aaa” ;,程序运行时会在常量池中查找”aaa”字符串,若没有,会将”aaa”字符串放进常量池,再将其地址赋给a;若有,将找到的”aaa”字符串的地址赋给a。
  • 使用String b = new String(“aaa”); 程序会在堆内存中开辟一片新空间存放新对象,同时会将”aaa”字符串放入常量池,相当于创建了两个对象,无论常量池中有没有”aaa”字符串,程序都会在堆内存中开辟一片新空间存放新对象

new String(“aaa”);创建了几个字符串对象

程序会在堆内存中开辟一片新空间存放新对象,同时会将”aaa”字符串放入常量池,相当于创建了两个对象,无论常量池中有没有”aaa”字符串,程序都会在堆内存中开辟一片新空间存放新对象,

String a=new String(“b”+“c”)会创建几个对象?

一共会创建4个对象

  • 字符串常量 “b” 被JVM存放在数据段区常量池中
  • 字符串常量 “c” 被JVM存放在数据段区常量池中
  • 由于String不可变性,“b”+“c” 又生成了一个字符串 被JVM存放在数据段区常量池中
  • new String(“b”+“c”)对象被JVM存放在堆中

String比较时遵循哪些原则

判断时遵循以下几条原则:

  1. 对于字符串常量的相加,在编译时直接将字符串合并,而不是等到运行时再合并,也就是不会new对象出来
  2. Java对String的相加是通过StringBuffer实现的,这样构造的对象就是在堆内存中,也就是会new对象出来
    • intern方法会先检查 字符串常量池 中是否存在相同的字符串常量,如果有就从常量池中返回
    • final修饰的String相加也会经过编译器优化,因为是不可变的。
 

如果替换定位为:

 

String有哪些特性

  • 不变性:String 是只读字符串,是一个典型的 immutable 对象,对它进行任何操作,其实都是创建一个新的对象,再把引用指向该对象。不变模式的主要作用在于当一个对象需要被多线程共享并频繁访问时,可以保证数据的一致性;
  • 常量池优化:String 对象创建之后,会在字符串常量池中进行缓存,如果下次创建同样的对象时,会直接返回缓存的引用;
  • final:使用 final 来定义 String 类,表示 String 类不能被继承,提高了系统的安全性

在使用 HashMap 的时候,用 String 做 key 有什么好处

HashMap 内部实现是通过 key 的 hashcode 来确定 value 的存储位置,因为字符串是不可变的,所以当创建字符串时,它的 hashcode 被缓存下来,不需要再次计算,所以相比于其他对象更快

String、StringBuffer 和 StringBuilder 的区别是什么

  • 可变性角度: String 对象是不可变的,StringBuffer 和 StringBuilder都是可变的
  • 线程安全性角度:String 中的对象是不可变的,也就可以理解为常量,线程安全。StringBuffer 对⽅法加了同步锁或者对调⽤的⽅法加了同步锁,所以是线程安全的。StringBuilder 并没有对⽅法进⾏加同步锁,所以是⾮线程安全的
  • 性能角度:每次对 String 类型进⾏改变的时候,都会⽣成⼀个新的 String 对象,然后将指针指向新的 String
    对象。StringBuffer 每次都会对 StringBuffer 对象本身进⾏操作,⽽不是⽣成新的对象并改变对象引⽤。相同情况下使⽤ StringBuilder 相⽐使⽤ StringBuffer 仅能获得 10%~15% 左右的性能提升,但却要冒多线程不安全的⻛险

性能和线程安全是反比的,线程安全:,性能:

基本类型和包装类型

从以下几个方面了解下基本类型和包装类型【Java SE基础专题 三】装箱拆箱问题详解

包装类型是什么?基本类型和包装类型有什么区别

Java 为每一个基本数据类型都引入了对应的包装类型(wrapper class),int 的包装类就是 Integer,从 Java 5 开始引入了自动装箱/拆箱机制,把基本类型转换成包装类型的过程叫做装箱(boxing);反之,把包装类型转换成基本类型的过程叫做拆箱(unboxing),使得二者可以相互转换
在这里插入图片描述

  • 包装类型可以为 null,而基本类型不可以
  • 包装类型可用于泛型,而基本类型不可以
  • 基本类型比包装类型更高效。基本类型在栈中直接存储的具体数值,而包装类型则存储的是堆中的引用。 很显然,相比较于基本类型而言,包装类型需要占用更多的内存空间

自动装箱和自动拆箱

  • 自动装箱:将基本数据类型重新转化为对象,在进行 比较的时候包装类型将会自动拆箱变为基本型后再进行比较
  • 自动拆箱:将对象重新转化为基本数据类型,包装类型调用,参数是基本类型进行比较的时候,先会对基本类型进行自动装箱

== 比较的各种情况分析

 

异常处理

异常相关内如如下,相关blog地址:【Java SE基础 五】Java异常处理机制

异常有哪些分类,我们通常处理的是哪些

确定不处理错误,不推荐处理运行时异常(因为运行时异常一般为逻辑错误,程序应该从逻辑角度尽可能避免这类异常的发生),必须处理非运行时异常,也就是我们处理受检异常
这里写图片描述

非受检查异常(运行时异常)和受检查异常(一般异常)区别是什么

  • 非受检查异常:包括 RuntimeException 类及其子类,表示 JVM 在运行期间可能出现的异常。 Java 编译器不会检查运行时异常。例如:NullPointException(空指针)、NumberFormatException(字符串转换为数字)、IndexOutOfBoundsException(数组越界)、ClassCastException(类转换异常)、ArrayStoreException(数据存储异常,操作数组时类型不一致)等。

  • 受检查异常:是Exception 中除 RuntimeException 及其子类之外的异常。 Java 编译器会检查受检查异常。常见的受检查异常有: IO 相关的异常、ClassNotFoundException 、SQLException等

NoClassDefFoundError 和 ClassNotFoundException 区别

  • NoClassDefFoundError 是一个 Error 类型的异常,是由 JVM 引起的,不应该尝试捕获这个异常。引起该异常的原因是 JVM 或 ClassLoader 尝试加载某类时在内存中找不到该类的定义,该动作发生在运行期间,即编译时该类存在,但是在运行时却找不到了,可能是变异后被删除了等原因导致。

  • ClassNotFoundException 是一个受检查异常,需要显式地使用 try-catch 对其进行捕获和处理,或在方法签名中用 throws 关键字进行声明。当使用 Class.forName, ClassLoader.loadClass 或 ClassLoader.findSystemClass 动态加载类到内存的时候,通过传入的类路径参数没有找到该类,就会抛出该异常;另一种抛出该异常的可能原因是某个类已经由一个类加载器加载至内存中,另一个加载器又尝试去加载它

try-catch-finally 中哪个部分可以省略

  • try+catch后有没有finally无所谓,try+catch+finally可以使用;try+catch可以使用
  • try必不可少,try+finally可以,try+catch可以
  • 三个关键字不能单独使用任何一个,即使是try如果不加catch也必须有finally(声明了异常就一定要处理,不管是在catch还是finally中)

总结就是必须有三个组合中的一种

throw 和 throws 的区别是什么

  • throw 关键字用在方法内部,只能用于抛出一种异常,用来抛出方法或代码块中的异常,受查异常和非受查异常都可以被抛出。
  • throws 关键字用在方法声明上,可以抛出多个异常,用来标识该方法可能抛出的异常列表。一个方法用 throws 标识了可能抛出的异常列表,调用该方法的方法中必须包含可处理异常的代码,否则也要在方法签名中用 throws 关键字声明相应的异常

finally中有无return对程序执行的影响

  • finally没有return,但try或catch里有,finally仍然会执行,因此在return返回时不是直接返回变量的值,而是复制一份,然后返回,因此,对于基本类型的,finally的改变没有影响,对于引用类型的就有影响了
  • finally里有return,finally中最好不要包含return,否则程序会提前退出,返回值不是try或catch中保存的返回值。返回值是finally里的return返回的值

对于finally块中包含了return语句的情况,则在try块中的return执行之前,会先goto到finally块中,而在goto之前并不会对try块中要返回的值进行保护,而是直接去执行finally块中的语句,并最终执行finally块中的return语句,而忽略try块中的return语句

IO流

Java的IO流可以参照如下内容学习【Java SE基础 六】Java的IO流实现

IO流有哪些分类

  1. 依据功能不同,可以划分为节点流/处理流 , 节点流:直接从特定数据源(文件,内存)读写数据;处理流:套在其它已存在流之上的,为程序提供更强大的读写功能
  2. 依据数据单位,可以划分为字节流/字符流 , 字节流:按照8位二进制读;字符流:按照2个8位二进制读,是2个字节
  3. 依据IO流的方向,可以划分为输入流/输出流 ,对此的理解应该站在程序(控制台)的角度上,从文件到程序叫做输入流,从程序到文件叫输出流,读(输入)是读到为程序分配的内存空间中去啦,写(输出)是写到指定的文件中去了。

字节流如何转为字符流?

  • 字节输入流转字符输入流通过 InputStreamReader 实现,该类的构造函数可以传入 InputStream 对象。
  • 字节输出流转字符输出流通过 OutputStreamWriter 实现,该类的构造函数可以传入 OutputStream 对象

字符流与字节流的区别

  • 读写单位来看,读写的时候字节流是按字节读写,字符流按字符读写。
  • 适合场景来看,字节流适合所有类型文件的数据传输,因为计算机字节(Byte)是电脑中表示信息含义的最小单位。字符流只能够处理纯文本数据,其他类型数据不行,但是字符流处理文本要比字节流处理文本要方便。
    • 在读写文件需要对内容按行处理,比如比较特定字符,处理某一行数据的时候一般会选择字符流。
    • 只是读写文件,和文件内容无关时,一般选择字节流

BIO、NIO、AIO的区别

  1. BIO:同步并阻塞,在服务器中实现的模式为一个连接一个线程。也就是说,客户端有连接请求的时候,服务器就需要启动一个线程进行处理,如果这个连接不做任何事情会造成不必要的线程开销,当然这也可以通过线程池机制改善。BIO一般适用于连接数目小且固定的架构,这种方式对于服务器资源要求比较高,而且并发局限于应用中,是JDK1.4之前的唯一选择,但好在程序直观简单,易理解。
  2. NIO:同步并非阻塞,在服务器中实现的模式为一个请求一个线程,也就是说,客户端发送的连接请求都会注册到多路复用器上,多路复用器轮询到有连接IO请求时才会启动一个线程进行处理。NIO一般适用于连接数目多且连接比较短(轻操作)的架构,并发局限于应用中,编程比较复杂,从JDK1.4开始支持。
  3. AIO:异步并非阻塞,在服务器中实现的模式为一个有效请求一个线程,也就是说,客户端的IO请求都是通过操作系统先完成之后,再通知服务器应用去启动线程进行处理。AIO一般适用于连接数目多且连接比较长(重操作)的架构,充分调用操作系统参与并发操作,编程比较复杂,从JDK1.7开始支持

反射

关于反射的相关知识,参照这篇Blog【Java SE基础 七】Java反射机制

什么是反射

AVA反射机制是在运行状态中,获取任意一个类的结构 、创建对象 、得到方法、执行方法 、属性,这种在运行状态动态获取信息以及动态调用对象方法的功能被称为java语言的反射机制

获取类对象的几种方式

 

反射是怎么实现的

 
  1. 反射获取类实例 Class.forName(),并没有将实现留给了java,而是交给了jvm去加载!主要是先获取ClassLoader, 然后调用 native 方法,获取信息,加载类则是回调 java.lang.ClassLoader。最后,jvm又会回调 ClassLoader 进类加载!
  2. newInstance() 主要做了三件事:权限检测,如果不通过直接抛出异常;查找无参构造器,并将其缓存起来;调用具体方法的无参构造方法,生成实例并返回。
  3. 获取Method对象,每次getMethod获取到的Method对象都持有对根对象的引用,因为一些重量级的Method的成员变量(主要是MethodAccessor),我们不希望每次创建Method对象都要重新初始化,于是所有代表同一个方法的Method对象都共享着根对象的MethodAccessor,每一次创建都会调用根对象的copy方法复制一份
  4. 调用invoke()方法,执行反射的方法内容

反射有什么使用场景

  1. 反编译文件:.class–>.java,有了反射就可以看到源代码了
  2. 通过反射机制访问java对象的属性,方法,构造方法等并且使用
  3. 当我们在使用IDE时,当我们输入一个对象或者类,并想调用他的属性和方法是,一按点号,编译器就会自动列出他的属性或者方法,这里就是用到反射。
  4. 反射最重要的用途就是开发各种通用框架。比如很多框架(Spring)都是配置化的(比如通过XML文件配置Bean),为了保证框架的通用性,他们可能需要根据配置文件加载不同的类或者对象,调用不同的方法,这个时候就必须使用到反射了,运行时动态加载需要的加载的对象。
  5. 加载数据库驱动,用到的也是反射,Class.forName(“com.mysql.jdbc.Driver”); // 动态加载mysql驱动

泛型

泛型相关的内容参照如下Blog,【Java SE基础 八】Java泛型机制

什么是泛型,有什么作用

泛型,即“参数化类型”。就是将类型由原来的具体的类型参数化,类似于方法中的变量参数,此时类型也定义成参数形式(可以称之为类型形参),然后在使用/调用时传入具体的类型(类型实参)泛型的本质是参数化类型,也就是说所操作的数据类型被指定为一个参数

  1. 使用泛型可以避免过多的方法方法重载,更广泛的说,多种数据类型执行相同的代码时可以实现代码复用
  2. 泛型中的类型在使用时指定,不需要强制类型转换(类型安全,编译器会检查类型)

泛型的原理是什么、什么是类型擦除

泛型是一种语法糖,泛型这种语法糖的基本原理是类型擦除。Java中的泛型基本上都是在编译器这个层次来实现的,也就是说:泛型只存在于编译阶段,而不存在于运行阶段 在编译后的 class 文件中,是没有泛型这个概念的

  • 类型擦除:使用泛型的时候加上的类型参数,编译器在编译的时候去掉类型参数

什么是泛型中的限定通配符和非限定通配符

通配符解决的是同一泛型类中的泛型参数的继承关系,通配符有三种指定方式:

  1. 指定了泛型类型的上界,它通过确保类型必须是Parent的子类来设定类型的上界
  2. 指定了泛型类型的下界,它通过确保类型必须是Child的父类来设定类型的下界
  3. 指定了没有限制的泛型类型,List<?> 的意思是这个集合是一个可以持有任意类型的集合

判断ArrayList与ArrayList是否相等

 

相等,无论对于 ArrayList 还是 ArrayList,它们的 Class 类型都是一直的,都是 ArrayList.class, String 和 Integer 体现在编译器的时候,当 JVM 进行类编译时,会进行泛型检查,如果一个集合被声明为 String 类型,那么它往该集合存取数据的时候就会对数据进行判断,从而避免存入或取出错误的数据,但是运行期会被类型擦除。

序列化与反序列化

Java序列化与反序列化是什么,这部分进行辨析

Java序列化与反序列化是什么

Java序列化是指把Java对象转换为字节序列的过程,而Java反序列化是指把字节序列恢复为Java对象的过程

  • 序列化:序列化是把对象转换成有序字节流,以便在网络上传输或者保存在本地文件中。核心作用是对象状态的保存与重建。我们都知道,Java对象是保存在JVM的堆内存中的,也就是说,如果JVM堆不存在了,那么对象也就跟着消失了。而序列化提供了一种方案,可以让你在即使JVM停机的情况下也能把对象保存下来的方案。就像我们平时用的U盘一样。把Java对象序列化成可存储或传输的形式(如二进制流),比如保存在文件中。这样,当再次需要这个对象的时候,从文件中读取出二进制流,再从二进制流中反序列化出对象。

  • 反序列化:客户端从文件中或网络上获得序列化后的对象字节流,根据字节流中所保存的对象状态及描述信息,通过反序列化重建对象

为什么需要序列化与反序列化

  • 对象序列化可以实现分布式对象,RMI(即远程调用Remote Method Invocation)要利用对象序列化运行远程主机上的服务,就像在本地机上运行对象时一样
  • 对象序列化不仅保留一个对象的数据,而且递归保存对象引用的每个对象的数据,可以将整个对象层次写入字节流中,可以保存在文件中或在网络连接上传递。利用对象序列化可以进行对象的"深复制",即复制对象本身及引用的对象本身。序列化一个对象可能得到整个对象序列
  • 序列化可以将内存中的类写入文件或数据库中,将一个已经实例化的类转成文件存储,下次需要实例化的时候只要反序列化即可将类实例化到内存中并保留序列化时类中的所有变量和状态
  • 对象、文件、数据,有许多不同的格式,很难统一传输和保存,序列化以后就都是字节流了,无论原来是什么东西,都能变成一样的东西,就可以进行通用的格式传输或保存,传输结束以后,要再次使用,就进行反序列化还原,这样对象还是对象,文件还是文件

序列化中如果有些字段不想进行序列化,怎么办

对于不想进行序列化的变量,使用 transient 关键字修饰,transient 关键字的作用是控制变量的序列化,在变量声明前加上该关键字,可以阻止该变量被序列化到文件中,在被反序列化后,transient 变量的值被设为初始值,如 int 型的是 0,对象型的是 null。transient 只能修饰变量,不能修饰类和方法

静态变量会被序列化吗

版权声明


相关文章:

  • 零基础java软件2024-10-16 22:34:05
  • java基础面试录音2024-10-16 22:34:05
  • java基础函数库在哪个包下2024-10-16 22:34:05
  • java基础类型怎么记2024-10-16 22:34:05
  • java基础类库翻译2024-10-16 22:34:05
  • 0基础学java需要那个工具2024-10-16 22:34:05
  • java零基础自学资料2024-10-16 22:34:05
  • java基础编程学生成绩分析2024-10-16 22:34:05
  • Java基础是什么课程2024-10-16 22:34:05
  • java命名规则零基础2024-10-16 22:34:05