1基本概念
1.1 Java语言的优点有哪些?
- Java是纯面向对象的语言。在Java中,万物皆对象。
- 具有平台无关性。(平台无关性实现原理请参考这里)
- Java提供了很多内置的类库(如对多线程的支持),简化了开发人员的程序设计工作。
- 具有较好的安全性和健壮性。
安全性保证:Java的安全机制(数组的安全边界检测和Bytecode 检验等)
健壮性保证:Java的强类型机制、垃圾回收器、异常处理和安全检查机制使得Java具有很好的健壮性。
1.2 Java与C/C++的异同点
- 相同点:都是面向对象的语言,都使用了面向对象思想,都有很好的可重用性。
- 不同点:
(1)Java为解释性语言,其运行过程为:Java源代码经过Java编译器编译成字节码,然后由JVM解释执行。而C/C++为编译型语言,源代码经过编译和链接后生成可执行的二进制代码。因此,Java的执行速度比C/C++慢。但是Java能够跨平台执行,而C/C++不能。
(2)与C/C++相比,Java中没有指针的概念,因此可以避免指针可能引起的安全问题。
(3)Java是纯面向对象的语言,多有代码必须在类中实现,除基本数据类型外,所有类型都是类。此外,Java语言中不存在全局变量和全局函数。而C++同时具有面向过程和面向对象的特点,且能设置全局变量和全局函数。
(4)Java语言不支持多继承,但是可以实现多个接口。C++能够支持多继承。
(5)在C++ 语言中,需要开发人员去管理内存分配,而Java中提供垃圾回收期来对垃圾进行自动回收,不需要显示的通过程序管理内存。
1.3 为什么需要public static void main(String[] args)这个方法呢?
此方法为Java程序的入口方法。JVM执行程序时 ,会首先找到这个方法执行。
注意:在同一个Java文件中,每个类中都可以定义main方法,但是只有与文件名相同的并用public修饰的类中main方法才能作为程序入口。
逐次解析:
public:权限修饰符,表明任何类或者对象都可以访问这个方法。
static:表明这是一个静态方法,即方法中的代码是存在静态代码区的,只要类被加载后,就可以使用该方法而不需要实例化对象来访问,可以直接根绝类名.main()来访问。JVM启动时就会根据上述方法的签名(必须有public和static,返回值为void,且方法的参数为字符串数组)来查找方法的入口地址,若能找到,就执行,若找不到,就会报错。
void:该方法没有返回值。
main:JVM识别的特殊方法名,是程序的入口方法。
args:字符串数组,,为开发人员在命令行状态下提与程序交互提供了一种手段。
为什么要用public static修饰main()方法?
因为main方法是程序的入口方法,此时还没有实例化对象,因此在编写main方法时就要求需要在不实例化对象的情况下就可以调用这个方法,因此采用public static修饰。注意:public与static没有先后顺序,main方法也可以用final或者synchronized修饰,但是由于要作为程序入口,不能用abstract修饰。
1.4 作为程序入口的main()是否是程序运行时执行的第一个模块呢?
答案:不是。在Java中,静态块在类被加载时就会被调用,因此可以在main()方法执行前。利用静态块实现一些输出的功能。
1.5 Java程序初始化顺序是怎样的?
在Java中,当实例化对象时,对象所在类的所有成员变量首先要进行初始化,只有当所有类成员变量进行初始化之后,才会调用对象所在类的构造函数创建对象。
Java程序初始化的三个原则:
- 静态变量(对象)优先于非静态变量的初始化,且静态对象只初始化一次。
- 父类优先于子类进行初始化。
- 按照成员变量的顺序进行初始化。
Java中代码块的初始化顺序:
父类静态变量->父类静态代码块->子类静态变量->子类静态代码块->父类非静态变量->父类非静态代码块->父类构造函数->子类非静态变量->子类非静态代码块->子类构造函数。
1.6 关于Java作用域的那些事儿
在和Java中,作用于是由花括号的位置决定的,它决定了其变量的可见性和生命周期。
Java的变量主要有三种:成员变量、静态变量与局部变量。其中被static修饰的成员变量称之为静态变量。
变量类型 | 作用范围与可见性 |
---|---|
局部变量 | 它所处的花括号内 |
成员变量 | 与类的实例化对象作用域相同 |
静态变量 | 与类同在 |
成员变量还可以分成四种作用域,如下表:
作用域与可见性 | 当前类 | 同一package | 子类 | 其他package |
---|---|---|---|---|
public | √ | √ | √ | √ |
private | √ | × | × | × |
protected | √ | √ | √ | × |
default | √ | √ | × | × |
- public:表明该成员变量或者方法对所有对象或者类都是可见的,所有类或者对象都是可以直接访问的。
- private:表明该成员变量或者方法是私有的,只有当前类有访问权限。
- protected:表明该成员变量或者方法对该类自身、与它在同一个包之中的其他类。在其他包中的子类都是可见的。
- default:表明该成员变量或者方法只有自己和与其位于同一包内的类可见。若父类与子类位于同一包内,则子类对父类的default的成员变量或者方法都有访问权限;若父类与子类不在同一个包内,则没有访问权限。
- 注意:这些修饰符只能修饰成员变量,不能用来修饰局部变量。private与protected不能用来修饰类(只有public、abstract或final能用来修饰)。
1.7一个Java文件中是否能定义多个类呢?
当然,一个Java文件中能定义多个类,但是只能有一个类被public修饰,并且这个被public修饰的类的名称必须与文件的名称相同。若文件中没有被public修饰的类,则文件名可以是任意一个类的名称。此外,在使用javac指令编译.java文件的时候,会给每一个类生成一个对应的.class文件。
1.8 为什么Java中有些接口没有任何方法?
在Java中没有任何方法的接口称之为“标识接口”。标识接口对于实现它的的类没有任何语义要求,这种接口仅仅起到一个标识作用。用来表明实现它的类属于一个特定的类型。
Java类库中已经存在的标识接口有Cloneable和Serializable等。在使用时经常会使用instance-of来判断实例对象的类型是否实现了一个给定的标识接口。
1.9 什么是JDK和JRE?
- JDK: Java Development Kit,开发工具包。提供了编译运行 Java 程序的各种工具,包括编译器、JRE 及常用类库,是 JAVA 核心。
- JRE: Java Runtime Environment,运行时环境,运行 Java 程序的必要环境,包括 JVM、核心类库、核心配置工具。
1.10 什么是值调用和引用调用?
- 按值调用指方法接收调用者提供的值,按引用调用指方法接收调用者提供的变量地址。
- Java 总是按值调用,方法得到的是参数的副本,传递对象时实际上传递的是对象引用的副本。其中,有两点需要注意:
(1)方法不能修改基本数据类型的参数,例如传递了一个 int 值 ,改变 int 值不会影响实参。
(2)方法可以改变对象参数的状态,但不能让对象参数引用新的对象。例如传递了一个 int 数组,改变数组内容会影响实参,而改变其引用并不会让实参引用新的数组对象。
1.11 什么是浅拷贝和深拷贝?
- 浅拷贝只复制当前对象的基本数据类型及引用变量,没有复制引用变量指向的实际对象。修改克隆对象可能影响原对象。
- 深拷贝完全拷贝基本数据类型和引用数据类型,修改克隆对象不会影响原对象。
1.12 什么是反射?
反射机制是Java语言中一个非常重要的特性,具体而言,反射机制提供的功能主要有:得到一个对象所属的类;获取一个类的所有成员变量和方法;在运行时创建对象;在运行时调用对象的方法。
class Father{
public void f(){
System.out.println("Father");
}
}
`class Son extends Father{
public void f(){
System.out.println("Son");
}
}`
public class Test{
try{//使用反射加载类
Class c=Class.forName("Son");
Father b=(Father)c.newInstance();
b.f();
}catch(Exception e){
e.printStackTrace();
}
}```
1.13 反射中使用的Class类是怎么获取的呢?
在程序运行期间,Java 运行时系统为所有对象维护一个运行时类型标识,这个信息会跟踪每个对象所属的类,虚拟机利用运行时类型信息选择要执行的正确方法,保存这些信息的类就是 Class,这是一个泛型类。
一共有三种方法可以获得Class类:
1. Class.forName("类路径")
2. 类名.Class();
3. 实例.getClass();
1.14 Java 创建对象的方式有几种?
- 通过new语句实例化一个对象。
- 通过反射机制创建一个对象。
- 通过clone()方法创建一个对象。
- 通过反序列化的方式创建一个对象。
1.15 package有什么作用
package,即“包”,其宗旨是将.java文件、class文件以及其他resource文件进行有条理的组织,以供使用。主要有两个作用:
- 提供多层命名空间,解决命名冲突,使得处于不同package中的类可以存在相同的名字。
- 对类按功能进行分类,使项目的组织更加清晰。
1.16 怎么使用package呢?
其用法一般如下(源文件目录为所在目录):
- 在每个源文件的开头加上“package
packagename;”,然后源文件所在目录下创建一个新目录,名字为“packagename”; - 用javac指令编译每个sourcename.java源文件,将生成的sourcename.classname文件复制到packagename目录。
- 用Java指令运行程序:java packagename.sourceename
2 面向对象相关
2.1面向对象与面向过程的区别以及优缺点?
(1)面向对象是把构成问题事务分解成各个对象,建立对象的目的不是为了完成一个步骤,而是为了描叙某个事物在整个解决问题的步骤中的行为。
优点:易维护、易复用、易扩展,由于面向对象有封装、继承、多态性的特性,可以设计出低耦合的系统,使系统 更加灵活、更加易于维护
缺点:性能比面向过程低
(2) 面向过程就是分析出解决问题所需要的步骤,然后用函数把这些步骤一步一步实现,使用的时候一个一个依次调用就可以了。
优点:性能比面向对象高,因为类调用时需要实例化,开销比较大,比较消耗资源;比如单片机、嵌入式开发、 Linux/Unix等一般采用面向过程开发,性能是最重要的因素。
缺点:没有面向对象易维护、易复用、易扩展
2.2 面向对象的几大特性? 多态怎么理解?
面向对象是一种思想,可以将复杂问题简单化,让我们从执行者变为了指挥者。面向对象的三大特性为:封装,继承与多态。
封装:将事物封装成一个类,减少耦合,隐藏细节。保留特定的接口与外界联系,当接口内部发生改变时,不会影响外部调用方。
继承: 从一个已知的类中派生出一个新的类,新类可以拥有已知类的行为和属性,并且可以通过覆盖/重写来增强已知类的能力。
多态: 多态的本质就是一个程序中存在多个同名的不同方法,主要通过三种方式来实现:
通过子类对父类的覆盖来实现
通过在一个类中对方法的重载来实现
通过将子类对象作为父类对象使用来实现
2.3如何通过将子类对象作为父类对象使用来实现多态?
把不同的子类对象都当作父类对象来看,可以屏蔽不同子类对象之间的差异,写出通用的代码,做出通用的编程,以适应需求的不断变化。这样操作之后,父类的对象就可以根据当前赋值给它的子类对象的特性以不同的方式运作。
对象的引用型变量具有多态性,因为一个引用型变量可以指向不同形式的对象
向上转型:
子类对象转为父类,父类可以是接口。
公式:Father f = new Son(); Father是父类或接口,Son是子类。
向下转型:
父类对象转为子类。公式:Son s = (Son) f;
【转型总结】
1.什么时候使用向上转型呢?
提高程序的扩展性,不关注子类类型(子类类型被隐藏)
需要用子类的的特有方法吗?不需要,向上转型
2.什么时候使用向下转型呢?
需要使用子类型的特有方法时
但是一定要使用instenceof进行类型的判断。避免发生ClassCastException
【多态成员调用的特点】
对于成员变量和静态函数,编译和运行都看左边
对于成员变量和静态函数,编译和运行都看左边
对于成员函数。编译看左边,运行看右边
2.4说一下覆盖和重载的区别
- 覆盖是子类和父类之间的关系,是垂直关系,而重载是一个类中各个方法之间的关系,是水平关系。
- 覆盖只能由一个方法或者一对方法产生关系;重载是多个方法之间的关系。
- 覆盖要求参数列表相同;重载要求参数列表不同。
- 覆盖关系中,调用方法体是根据对象的类型(对象对应存储空间的类型)来决定,而重载是根据调用时的实参列表与形参表来选择方法体的。
2.5 接口和抽象类的区别。(重点)
抽象类和接口的主要区别可以总结如下。
- 抽象类中可以没有抽象方法,也可以抽象方法和非抽象方法共存 .
- 接口中的方法在JDK8之前只能是抽象的,JDK8版本开始提供了接口中方法的default实现
- 抽象类和类一样是单继承的;接口可以实现多个父接口 抽象类中可以存在普通的成员变量;接口中的变量必须是static final类型的,必须被初始化,接口中只有常量,没有变量。
2.51 抽象类和接口应该如何选择?分别在什么情况下使用呢?
. 根据抽象类和接口的不同之处,当我们仅仅需要定义一些抽象方法而不需要其余额外的具体方法或者变量的时候,我们可以使用接口。反之,则需要使用抽象类,因为抽象类中可以有非抽象方法和变量。
2.52 关于接口的默认方法
当一个类实现该接口时,可以继承到该接口中的默认方法。
如果两个接口中存在同样的默认方法,实现类继承的是哪一个呢?这个时候,实现类那里会编译错误,大概意思就是说:有两个相同的方法,编译器不知道该如何选择了。
2.53 如果实现的接口中有多个相同的默认方法该怎么办呢?
两种处理方式,如下所示:
- 重写多个接口中的相同的默认方法
- 在实现类中指定要使用哪个接口中的默认方法
2.54 那么JDK8中为什么会出现默认方法呢?
使用接口,使得我们可以面向抽象编程,但是其有一个缺点就是当接口中有改动的时候,需要修改所有的实现类。在JDK8中,为了给已经存在的接口增加新的方法并且不影响已有的实现,所以引入了接口中的默认方法实现。
默认方法允许在不打破现有继承体系的基础上改进接口,解决了接口的修改与现有的实现不兼容的问题。该特性在官方库中的应用是:给java.util.Collection接口添加新方法,如stream()、parallelStream()、forEach()和removeIf()等等。在我们实际开发中,接口的默认方法应该谨慎使用,因为在复杂的继承体系中,默认方法可能引起歧义和编译错误。
2.6 this与super的区别
- 在Java语言中,this用来指向当前实例对象,它的一个非常重要的作用就是用来区分对象的成员变量与方法的形参(当一个方法的形参与成员变量的名字相同时,就会覆盖成员变量)
- super可以用来访问父类的成员变量或者方法。当子类的成员变量挥着方法与父类具有相同的名称时,也会覆盖父类的成员变量或者方法。要想访问父类的成员变量或者方法,只能通过super关键字来访问。
2.7内部类的作用是什么,有哪些分类?
内部类可对同一包中其他类隐藏,内部类方法可以访问定义这个内部类的作用域中的数据,包括 private 数据。
内部类是一个编译器现象,与虚拟机无关。编译器会把内部类转换成常规的类文件,用 $ 分隔外部类名与内部类名,其中匿名内部类使用数字编号,虚拟机对此一无所知。
- 静态内部类: 属于外部类,只加载一次。作用域仅在包内,可通过 外部类名.内部类名
直接访问,类内只能访问外部类所有静态属性和方法。HashMap 的 Node 节点,ReentrantLock 中的 Sync类,ArrayList 的 SubList 都是静态内部类。内部类中还可以定义内部类,如 ThreadLoacl 静态内部类ThreadLoaclMap 中定义了内部类 Entry。 - 成员内部类: 属于外部类的每个对象,随对象一起加载。不可以定义静态成员和方法,可访问外部类的所有内容。
- 局部内部类: 定义在方法内,不能声明访问修饰符,只能定义实例成员变量和实例方法,作用范围仅在声明类的代码块中。
- 匿名内部类: 只用一次的没有名字的类,可以简化代码,创建的对象类型相当于 new 的类的子类类型。用于实现事件监听和其他回调。
2.8 Object 类中的方法有哪些?
- equals:检测对象是否相等,默认使用 == 比较对象引用,可以重写 equals 方法自定义比较规则。equals方法规范:自反性、对称性、传递性、一致性、对于任何非空引用 x,x.equals(null) 返回 false。
- hashCode:散列码是由对象导出的一个整型值,没有规律,每个对象都有默认散列码,值由对象存储地址得出。字符串散列码由内容导出,值可能相同。为了在集合中正确使用,一般需要同时重写
equals 和 hashCode,要求 equals 相同 hashCode 必须相同,hashCode 相同 equals未必相同,因此 hashCode 是对象相等的必要不充分条件。
- toString:打印对象时默认的方法,如果没有重写打印的是表示对象值的一个字符串。
- clone:clone 方法声明为 protected,类只能通过该方法克隆它自己的对象,如果希望其他类也能调用该方法必须定义该方法为
public。如果一个对象的类没有实现 Cloneable 接口,该对象调用 clone 方***抛出一个 CloneNotSupport 异常。默认的 clone 方法是浅拷贝,一般重写 clone 方法需要实现 Cloneable接口并指定访问修饰符为 public。 - getClass:返回包含对象信息的类对象。
- wait / notify / notifyAll:阻塞或唤醒持有该对象锁的线程。
- finalize:确定一个对象死亡至少要经过两次标记,如果对象在可达性分析后发现没有与 GC Roots连接的引用链会被第一次标记,随后进行一次筛选,条件是对象是否有必要执行 finalize方法。假如对象没有重写该方法或方法已被虚拟机调用,都视为没有必要执行。如果有必要执行,对象会被放置在 F-Queue队列,由一条低调度优先级的 Finalizer 线程去执行。虚拟机会触发该方法但不保证会结束,这是为了防止某个对象的 finalize方法执行缓慢或发生死循环。只要对象在 finalize方法中重新与引用链上的对象建立关联就会在第二次标记时被移出回收集合。由于运行代价高昂且无法保证调用顺序,在 JDK 9被标记为过时方法,并不适合释放资源。
3 关键字
3.1变量命名有哪些规则?
在Java中,变量名、函数名、数组名统称为标识符。,Java语言标识符只能由字母(a-z,A_Z)、数字(0-9)、下划线(_)以及$组成。并且标识符的第一个字符必须是除了数字的以上之一。
3.2 boolean、break和continue有什么区别?
- break用于直接跳出当前循环,不再执行剩余代码。注意:当多层循环嵌套,并且break语句出现在嵌套循环的内层循环时,它将仅仅只是终止了内层循环的执行,而不影响外层循环的执行。
- continue用于停止当次循环,回到循环起始处,进入下一次循环操作。continue语句之后的语句将不再执行而是进入下一次循环。
- return语句是一个跳转语句,用来表示从一个方法返回,可以使程序控制回到调用该方法的的地方。当执行main()方法时,return语句可以使程序执行返回到Java运行系统。
3.3 final、finally、finalize有什么区别?
- final用于声明属性、方法和类。分别用来表示属性不可变、方法不可覆盖,类不可被继承。
- finally作为异常处理的一部分,它只能用在try/catch语句中,并且附带一个语句块,表示这段语句最终一定会被执行,经常被用在需要解放资源的情况下。
- finalize是object类的一个方法,在垃圾回收器执行时会调用被回收对象的finalize()方法,可以覆盖此方法来实现对其他资源的回收,例如关闭文件等。但是,一旦垃圾回收器准备好释放对象占用的空间,将首先调用nalize()方法,并且在下一次垃圾回收的时候,才会真正回收对象占用的内存。
3.4 详细说说final修饰符
final用于声明属性,方法和类。分别表示属性不可变,方法不可覆盖,类不可继承。
- . final属性:被final修饰的变量不可变。但是Java中的变量不可变有两种,一种是值不可变,另外一种是引用不可变。而final变量的不可变则是指的引用不可变,即它只能指向初始化时指向的那个对象,但是不关心对象内容是否变化。不过由于这个原因,final变量必须背初始化。
- . final方法:当一个方法声明为final方法时,该方法不允许被任何子类进行重写。但是子类仍然可以使用这个方法。
- . final参数:用来表示这个参数在函数内部不能被修改。
- . final类:当一个类被声明为final时,此类不能被继承。其中的方法不能被重写。但是里面的成员变量可以被修改。如果不想其被修改,可以将其设置成final的。注意:一个类,不能既被修饰成abstract的,又被修饰成final的。
3.5 JDK中哪些类是不可被继承的?
不能被继承的类是被final关键字修饰的类。一般比较基本的类型都是用final修饰的,这是为了防止扩展类无意间破坏原来方法的的实现。在JDK中,String,StringBuffer,StringBuilder等都是基本类型,都是不可被继承的。
3.6.1 static关键字的作用是什么?
(1)为某特定数据类型挥着对象分配单一的内存空间,而与创建的对象个数无关。
(2)在不创建对象的情况下也能通过类直接调用方法还或者使用类的属性。
3.6.2static关键字的使用情况是什么?
主要有以下四种使用情况:
(1)static成员变量
Java中主要有两种变量:用static修饰的静态变量和不用static修饰的实例变量。静态变量属于类,在内存中只有一个复制,所有实例都指向同一个内存地址。只要静态变量所在的类被加载,此静态变量就会被分配内存空间,因此就可以被使用了。
实例变量属于对象,只有对象被实例化之后,才能通过对象调用实例变量。它在内存中存在多个复制。
(2) static成员方法
与变量类似,存在静态方法和非静态方法。静态方法不需要对象创建即可被调用,而非静态方法需要在对象被创建之后才能被调用。
注意:static方法不能使用this和super关键字,不能调用非static方法和非static成员变量,只能访问所属类的静态成员变量和成员方法。因为,当static方法被调用时,这个类的对象可能还没被创建,即使已经被创建了,也无法确定调用哪个对象的方法。
(3)static代码块
静态代码块在类中是独立于成员变量和成员函数的代码块的。它不在任何一个方法体中,JVM在加载类时会执行静态代码块。如果存在多个代码块,JVM将会按照顺序来执行。静态代码块经常被用来初始化静态变量。这些静态代码块只会被执行一次。
(4)static内部类
static内部类是指被声明为static的内部类,它可以不依赖外部类对象而被实例化。而通常的内部类需要在外部类实例化之后才能实例化。
静态内部类的名字不能与外部类相同,不能访问外部类的普通成员变量,只能访问外部类的静态成员变量和静态方法。
注意:只有内部类才能被定义成static。
3.7 什么是实例变量,局部变量,类变量以及final变量?
- 实例变量:属于对象,只有在对象实例化之后才能使用实例变量。每当实例化一个对象时,就会创建一个副本并初始化,如果没有显式初始化,那么会初始化一个默认值。各个对象中的实例变量互不影响。
- 局部变量:在方法中定义的变量,在使用前必须进行初始化。
- 类变量:用static修饰的属性。变量,归类所有,只要类被加载,这变量就可以被使用。所有实例化的对象都可以共享这个类变量。
- final变量:表示这个变量为常量,不可修改。
3.8 说一下switch语句
3.8.1 什么是switch语句以及如何使用?
switch语句用于多分支选择,在使用switch(expr),expr只能是枚举常量(内部也是由整型或者字符类型实现)或一个整数表达式,其中整数表达式可以是基本类型int或者其对应的包装类Integer,当然也包括不同的长度整型,例如short。因为short、char、byte类型的值都能隐式的转换为int类型,因此这些类型以及其对应的包装类型都能作为switch语句的表达式。但是long、double、float、string则不能,因为他们不能隐式地转换为int类型。如果一定要使用上述类型作为表达式,需要将其强转为int类型才行。
此外,与switch语句对应的是case语句,case语句之后可以是直接的常量数值、常量计算式、final类型的变量,但不能是变量或者带有变量的表达式、浮点型数。
3.8.2 Java7switch语句开始支持String类型的实现原理是怎样的?
从本质上讲,switch语句对String类型的支持,其实是int类型值的匹配。
它的实现原理主要是:通过对case后面String对象调用hashcode()方法,得到一个int类型的hash值,然后用这个hash值来唯一表示这个case。那么当匹配时,会首先调用这个字符串的的hashcode()方法,获取一个hash值(int类型),用这个hash值来匹配所有case,如果没有成功,说明不存在,如果匹配成功,接着会调用字符串的equals()方法进行匹配。因此String变量不能为null,同时,switch的case字句中使用的字符串也不能为null。
另外需要注意的是一般必须在case语句后面添加break语句。因为一旦通过switch语句确定了入口点,就会顺序执行后面的代码,直到遇到关键字break。否则,会执行后面的语句,无论后面的语句是否符合当前的case情况。
4 基本类型与运算
4.1 java提供了那些基本类型?
Java提供了下面八种基本数据类型,这些数据类型的变量再声明之后就会立刻在栈上被分配内部空间。除了这些基本数据类型之外,其他变量都是引用类型变量,在声明之后并不会立即被分配空间,只是存储了一个内存地址而已。
数据类型 | 内存大小 | 默认值 | 取值范围 |
---|---|---|---|
byte | 1 B | (byte)0 | -128 ~ 127 |
short | 2 B | (short)0 | -215 ~ 215-1 |
int | 4 B | 0 | -231 ~ 231-1 |
long | 8 B | 0L或0l | -263 ~ 263-1 |
float | 4 B | 0.0F 或0.0f | ±3.4E+38(有效位数 6~7 位) |
double | 8 B | 0.0D | ±1.7E+308(有效位数 15 位) |
char | 英文 1B/ 中文 UTF-8 占 3B/ GBK 占 2B | '\u0000' | '\u0000' ~ '\uFFFF' |
boolean | 单个变量 4B / 数组 1B | false | true、false |
注意:Java中的数值类型都是有符号的,不存在无符号的数,它们的取值范围也是固定的,不会随着硬件环境或者操作系统的改变而改变。除了以上八种基本数据类型外,Java还提供了一种基本类型void。
4.2 自动装箱/拆箱是什么?
每个基本数据类型都对应一个包装类,除了 int 和 char 对应 Integer 和 Character 外,其余基本数据类型的包装类都是首字母大写即可。
- 自动装箱: 将基本数据类型包装为一个包装类对象,例如向一个泛型为 Integer 的集合添加 int 元素。
- 自动拆箱: 将一个包装类对象转换为一个基本数据类型,例如将一个包装类对象赋值给一个基本数据类型的变量。
- 比较两个包装类数值要用 equals ,而不能用 == 。
4.2.1包装类与基本类型的区别是什么?
原始数据类型在传递参数的时候是按照值传递的,封装类型按照引用传递。
当封装类型和原始类型用作某个类的实例数据时,他们所指定的默认值不同,。对象引用实例变量的默认值为null,而原始类型实例变量的默认值与它们的类型相关。
4.2.2 在Java语言中null值是什么?在内存中null是什么?
null不是一个合法的object实例,因此编译器没有为其分配内存,它仅仅用于表明该引用目前没有指向任何对象。其实,与C语言类似,null是将引用变量的值全部置为0.
4.3什么是不可变类?
不可变类是指创建了这个类的实例之后,就不允许修改它的值了,也就是说,一个对象一旦被创建出来,在其整个生命周期中,它的成员变量,就不能被修改了,有点类似于常量。
在Java类库中,所有基本类型的包装类都是不可变类,此外String也是不可变类。
4.3.1 String 是不可变类为什么值可以修改?
String 类和其存储数据的成员变量 value 字节数组都是 final 修饰的。对一个 String 对象的任何修改实际上都是创建一个新 String 对象,再引用该对象。只是修改 String 变量引用的对象,没有修改原 String 对象的内容。
4.3.2 String a = "a" + new String("b") 创建了几个对象?
常量和常量拼接仍是常量,结果在常量池,只要有变量参与拼接结果就是变量,存在堆。
使用字面量时只创建一个常量池中的常量,使用 new 时如果常量池中没有该值就会在常量池中新创建,再在堆中创建一个对象引用常量池中常量。因此 String a = "a" + new String("b")
会创建四个对象,常量池中的 a 和 b,堆中的 b 和堆中的 ab。
4.4字符串拼接的方式有哪些?
- 直接用 + ,底层用 StringBuilder 实现。只适用小数量,如果在循环中使用 + 拼接,相当于不断创建新的
StringBuilder 对象再转换成 String 对象,效率极差。 - 使用 String 的 concat 方法,该方法中使用 Arrays.copyOf 创建一个新的字符数组 buf 并将当前字符串
value 数组的值拷贝到 buf 中,buf 长度 = 当前字符串长度 + 拼接字符串长度。之后调用 getChars 方法使用
System.arraycopy 将拼接字符串的值也拷贝到 buf 数组,最后用 buf 作为构造参数 new 一个新的 String
对象返回。效率稍高于直接使用 +。 - 使用 StringBuilder 或 StringBuffer,两者的 append 方法都继承自
AbstractStringBuilder,该方法首先使用 Arrays.copyOf 确定新的字符数组容量,再调用 getChars
方法使用 System.arraycopy 将新的值追加到数组中。StringBuilder 是 JDK5
引入的,效率高但线程不安全。StringBuffer 使用 synchronized 保证线程安全。