Java基础知识面试题系列三:31~40题
- 31.static与final结合使用表示什么意思
- 32.使用switch时有哪些注意事项
- 33.volatile有什么作用
- 34.instanceof有什么作用
- 35.strictfp有什么作用
- 36.Java提供了哪些基本数据类型
- 37.在Java语言中null值时什么?在内存中null是什么
- 38.如何理解赋值语句String x = null?
- 39.数据类型常见的一些笔试题
- 40.什么是不可变类
31.static与final结合使用表示什么意思
- 在Java语言中,static关键字常与final关键字结合使用,用来修饰成员变量与成员方法,有点类似于C/C++语言中的“全局变量”。
- 对于变量,若使用static final修饰,则表示一旦赋值,就不可修改,并且通过类名可以访问。
- 对于方法,若使用static final修饰,则表示该方法不可覆盖,并且可以通过类名直接访问。
示例:
package com.bigdata.springboot;
public class Test {
public static int testStatic(){
static final int i = 0;
System.out.println(i++);
}
public static void main(String[] args) {
Test test = new Test();
test.testStatic();
}
}
上述程序的运行结果是什么?()
- A.0
- B.1
- C.2
- D.编译失败
答案:D。在Java语言中,不能在成员函数内部定义static变量。
32.使用switch时有哪些注意事项
- switch语句用于多分支选择,在使用switch(expr)时,expr只能是一个枚举常量(内部也是由整形或字符类型实现)或一个整数表达式
- 整数表达式 可以是基本类型int或其对应的包装类Integer,当然也包括不同的长度整型,例如short
- 由于byte、short和char类型的值都能够被隐式地转换为int类型,因此这些类型以及它们对应的包装类型都可以作为switch的表达式。
- long、float、double、String类型不能够隐式地转换为int类型,因此它们不能被用作switch的表达式。
- 如果一定要使用long、float或double作为switch的参数,必须将其强制转换为int类型才可以。
示例:以下对switech中参数的使用就是非法的。
float a = 0.123;
switch(a) //错误!a不是整形或字符类型变量
{
}
- 与switch对应的是case语句,case语句之后可以是直接的常量数值,例如1、2,也可以是一个常量计算式,例如1+2等,还可以是final型的变量,例如final int a = 0,但不能说变量或带有变量的表达式,例如i*2等。当然更不能是浮点型数,例如1.1或者1.2/2等。
- 在Java7中,switch开始支持String类型了,switch对字符串的支持,其实是int类型值的匹配。实现原理如下:通过对case后面的String对象调用hashCode()方法,得到一个int类型的hash值,然后用这个hash值来唯一标示这个case。当匹配时,首先调用这个字符串hashCode()函数,获取一个hash值,用这个hash值来匹配所有case,如果没有匹配成功,说明不存在。如果匹配成功了,会调用字符串的String.equals()方法进行匹配。String变量不能为null,同时,switch的case子句中使用的字符串也不能为null.
- 在使用switch时,需要注意另外一个问题:一般必须在case语句结尾添加break语句。因为一旦通过switch语句确定了入口点,就会顺序执行后面的代码,直到遇到关键字break。否则会执行满足这个case之后的其他case的语句而不管case是否匹配,直到switch结束或者遇到break为止。如果在switch语句中省略了break语句,那么匹配的case值后的所有情况(包括default情况)都会被执行。
33.volatile有什么作用
- 在Java语言编写的程序中,有时为了提高程序的运行效率,编译器会自动对其进行优化,把经常访问的变量缓存起来,程序在读取这个变量时有可能会直接从缓存(例如寄存器)中来读取这个值,而不会去内存中读取。这样做的一个好处是提高了程序的运行效率。
- 例如,在本线程内,当读取一个变量时,为提高存取速度,会先把变量读取到一个缓存中,当以后再取变量值时,就直接从缓存中取值,当变量值在本线程里改变时,会同时把变量的新值复制到该缓存中,以便保持一致
- 但当遇到多线程编程时,变量的值可能因为别的线程而改变了,而该缓存的值不会相应改变。从而造成应用程序读取的值和实际的变量值不一致。
- volatile是一个类型修饰符,被设计用来修饰被不同线程访问和修改的变量。
- 被volatile类型定义的变量,系统每次用到它时都是直接从对应的内存当中提取,而不会利用缓存。
- 在使用volatile修饰成员变量后,所有线程在任何时候所看到的变量的值都是相同的。
下面给出一个使用volatile的示例。
public class MyThread implements Runnable{
private volatile Boolean flag;
public void stop(){
flag = false;
}
public void run(){
while(flag)
//do something
}
}
- 以上代码示例是用来停止线程最常用的一种方法,如果boolean类型的变量flag没有被声明为volatile,当这个线程的run方法在判断flag值时,使用的有可能是缓存中的值,此时就不能及时地获取其他线程对flag所做的操作,因此会导致线程不能及时地停止。
- 需要注意的是,由于volatile不能保证操作的原子性,因此,一般情况下volatile不能代替sychronized。
- 使用volatile会阻止编译器对代码的优化,因此会降低程序的执行效率。
- 除非迫不得以,否则,能不使用volatile就尽量不要使用volatile
34.instanceof有什么作用
- instanceof是Java语言中的一个二元运算符,它的作用是判断一个引用类型的变量所指向的对象是否是一个类(或接口、抽象类、父类)的实例
- 即它左边的对象是否是它右边的类的实例该运算符返回boolean类型的数据。
- 常见的用法为:result=object instanceof class,如果object是class的一个实例,那么instanceof运算符返回true。如果object不是class的一个实例,或者object是null,那么instanceof运算符返回false。
以如下程序为例:
import java.lang.String;
public class InstanceOf {
public static void main(String[] args) {
String s = "Hello";
int[] a = {1,2};
if(s instanceof String)
System.out.println("true");
if(s instanceof Object)
System.out.println("true");
if(a instanceof int[])
System.out.println("true");
}
}
程序结果为:
true
true
true
35.strictfp有什么作用
- 关键字strictfp是strict float point的缩写,指的是精确浮点,用来确保浮点数运算的准确性。
- JVM在执行浮点数运算时,如果没有指定stricfp关键字,此时计算结果可能会不精确,而且计算结果在不同平台或厂商的虚拟机上会有不同的结果,导致意想不到的错误。
- 一旦使用了strictfp来声明一个类、接口或者方法,那么在所声明的范围内,Java编译器以及运行环境会完全依照IEEE二进制浮点数算术标准来执行,在这个关键字声明的范围内所有浮点数的计算都是精确的。
- 需要注意的是,当一个类被stricfp修饰时,所有方法都会自动被strictfp修饰。
- strictfp可以保证浮点数运算的准确性,而且在不同的硬件平台上会有一致的运行结果。
下次给出了strictfp修饰类的使用方法:
public strictfp class TestStrictfp {
public static void testStrictfp() {
float f = 0.12365f;
double d = 0.03496421d;
double sum = f+d;
System.out.println(sum);
}
public static void main(String[] args) {
testStrictfp();
}
}
程序运行结果为:
0.15861420949932098
36.Java提供了哪些基本数据类型
Java语言一共提供了8种原始的数据类型:
- byte
- short
- int
- long
- float
- double
- char
- boolean
- 这些数据类型不是对象,而是Java语言中不同于类的特殊类型
- 这些基本类型的数据变量在声明之后就会立刻在栈上被分配内存空间
- 除了这8种基本的数据类型外,其他类型都是引用类型(例如类、接口、数组等)
- 引用类型类似于C++中的引用或指针的概念
- 它以特殊的方式指向对象实体,这类变量在声明时不会被分配内存空间,只是存储了一个内存地址而已。
不同数据类型对比:
数据类型 | 包装类 |
int | Integer |
short | Short |
long | Long |
byte | Byte |
float | Float |
double | Double |
char | Character |
boolean | Boolean |
以上这些基本类型可以分为如下4种类型:
- int长度数据类型:byte(8bit)、short(16bit)、int(32bit)、long(64bit)
- float长度数据类型:单精度(32bit float)、双精度(64 bit double)
- boolean类型变量的取值:true、false。对于boolean占用空间的大小,从理论上讲,只需要1bit就够了,但在设计的时候为了考虑字节对齐等因素,一般会考虑使其占用一个字节。由于Java规范没有明确的规定,因此,不同的JVM可能会有不同的实现。
- char数据类型:unicode(16bit)
Java语言还提供了对这些原始数据类型的封装类(字符类型Character、布尔类型Boolean、数值类型Byte、Short、Integer、Long、Float、Double)。
- Java中的数值类型都是有符号的,不存在无符号的数,取值范围也是固定的,不会随着硬件环境或者操作系统的改变而改变。
- 除了8种基本数据类型外,Java语言中还存在着一种基本类型void,也有对应的封装类java.lang.void,只是无法直接对它进行操作而已。
封装类型和原始类型的区别:
- 原始数据类型在传递参数时都是按值传递,而封装类型是按引用传递的
- 当封装类型和原始类型用作某个类的实例数据时,它们所指定的默认值不同。
- 对象引用实例变量的默认值为null,而原始类型实例变量的默认值与它们的类型有关。
示例如下:
public class Test {
String s;
int i;
float f;
public static void main(String[] args) {
Test t = new Test();
System.out.println(t.s == null);
System.out.println(t.i);
System.out.println(t.f);
}
}
程序运行结果为:
true
0
0.0
需要注意的是:
- Java语言中,默认声明的小数是double类型的,因此在对float类型的变量进行初始化时需要进行类型转换。
- float类型的变量有两种初始化方法:float f=1.0f 或 float f = (float) 1.0
- 在Java语言中,直接写的整形数字是int类型的,如果在给数据类型为long的变量直接赋值时,int类型的值无法表示一个非常大的数字,因此,在赋值时可以通过如下的方法来赋值:long l = 26012402244L
37.在Java语言中null值时什么?在内存中null是什么
- null不是一个合法的Object实例,所以编译器并没有为其分配内存,仅仅用于表明该引用目前没有指向任何对象。
- 与C语言类似,null是将引用变量的值全部置为0
38.如何理解赋值语句String x = null?
- 在Java语言中,变量被分为两大类型:原始值与引用值。
- 声明为原始类型的变量,其存储的是实际的值。
- 声明为引用类型的变量,存储的是实际对象的地址(指针、引用)
- 对于赋值语句String x = null,定义了一个变量“x”,x中存放的是String引用,此处为null
39.数据类型常见的一些笔试题
(1)下列表达式中,正确的是()
- A.byte b = 128;
- B.boolean flag = null;
- C.float f = 0.9239
- D.long a = 2147483648L;
答案:D。A中byte能表示的取值范围为[-128,127],因此不能表示128。B中boolean的取值只能是true或false,不能为null。C中0.923为double类型,需要进行数据类型转换。
(2) String是最基本的数据类型么?
答案:不是。基本数据类型包括byte、int、char、long、float、double、boolean和short
(3) int和Integer有什么区别?
答案:
- Java语言提供两种不同的类型,即引用类型和原始类型(或内置类型)。
- int是Java语言的原始数据类型,Integer是Java语言为int提供的封装类。
- Java为每个原始类型提供了封装类。
- 引用类型与原始类型的行为完全不同,并且它们具有不同的语义。而且,引用类型与原始类型具有不同的特征和用法。
(4) 赋值语句float f = 3.4是否正确?
答案:不正确。
- 数据3.4默认情况下是double类型,即双精度浮点数,将double类型数值赋值给float类型的变量,会造成精度损失,因此需要对其进行强制类型转换
- 即将3.4转换成float类型或者将3.4强制写成float类型,所以float f = (float)3.4 或者float f = 3.4F写法都是可以的
40.什么是不可变类
- 不可变类是指当创建了这个类的实例后,就不允许修改它的值了,
- 也就是说一个对象一旦被创建出来,在其整个生命周期中,它的成员变量就不能被修改了。
- 类似于常量,即只允许别的程序读,不允许别的程序进行修改。
- 在Java类库中,所有基本类型的包装类都是不可变,例如Integer、Float等。
- String也是不可变类。
既然String是不可变类,为什么还可以写出如下代码来修改String类型的值?
public class Test {
public static void main(String[] args) {
String s = "hello";
s += " world";
System.out.println(s);
}
}
程序运行结果为:
Hello world
- 表明上看,好像是修改String类型对象s的值。其实不是,String s = "Hello"语句声明了一个可以指向String类型对象的引用,这个引用名字为s,它指向了一个字符串常量 “Hello”。
- s+=" world"并没有改变s所指向的对象(由于“Hello”是String类型的对象,而String又是不可变量),这句代码运行后,s指向了另外一个String类型的对象,该对象的内容为“Hello world”。
- 原来的那个字符串串常量“Hello”。还存在于内存中,并没有被改变。
介绍完不可变类的基本概念后,下面主要介绍如何创建一个不可变类。通常来讲,要创建一个不可变类需要遵循下面4条基本原则:
- 类中所有成员变量被private所修饰
- 类中没有写或者修改成员变量的方法,例如setxxx,只提供构造函数,一次生效,永不可改变。
- 确保类中所有方法不会被子类覆盖,可以通过把类定义为final或者把类中的方法定义为final来达到这个目的。
- 如果一个类成员是不是不可变量,那么在成员初始化或者使用get方法获取该成员变量时,需要通过clone方法来确保类的不可变性。
- 如果有必要,可使用覆盖Object类的equals()方法和hashCode()方法。在equals()方法中,根据对象的属性值来比较两个对象是否相等,并且保证用equals()方法判断为相等的两个对象的hashCode()方法的返回值也相等,这可以保证这些对象能被正确地放到HashMap或HashSet集合中。
需要注意的事项:
- 由于类的不可变性,在创建对象时就需要初始化所有成员变量,因此最好提供一个带参数的构造函数来初始化这些成员变量。
Java语言中之所以有很多不可变类,主要是不可变类具有使用简单、线程安全、节省内存等优点。
有利就有弊,不可变类自然也有其缺点,例如,不可变类的对象会因为值的不同而产生新的对象,从而导致无法预料的问题。所以,切不可滥用这种模式。
下面通过给出一个错误实现方法与一个正确的实现方法来说明在实现这种类时需要特别注意的问题。
首先给出一个错误的实现方法:
import java.util.Date;
class ImmutableClass{
private Date d;
public ImmutableClass(Date d){
this.d = d;
}
public void printState() {
System.out.println(d);
}
}
public class TestImmutable {
public static void main(String[] args) {
Date d = new Date();
ImmutableClass immuC = new ImmutableClass(d);
immuC.printState();
d.setMonth(5);
immuC.printState();
}
}
程序运行结果如下所示:
Thu Aug 12 09:48:51 CST 2021
Sat Jun 12 09:48:51 CST 2021
- 由于Date对象的状态是可以被改变的,而ImmutableClass保存了Date类型对象的引用,当被引用的对象的状态改变时会导致ImmutableClass对象状态的改变。
正确的实现方法如下所示:
import java.util.Date;
class ImmutableClass{
private Date d;
public ImmutableClass(Date d){
this.d = (Date)d.clone();
}
public void printState() {
System.out.println(d);
}
}
public class TestImmutable {
public static void main(String[] args) {
Date d = new Date();
ImmutableClass immuC = new ImmutableClass(d);
immuC.printState();
d.setMonth(5);
immuC.printState();
}
}
程序的运行结果如下所示:
Thu Aug 12 09:51:55 CST 2021
Thu Aug 12 09:51:55 CST 2021