Bootstrap

java基础小细节

Java基础面试题

首先,在这里说明一下,这篇文章是我结合自身目前所学到的知识点,从网上摘录总结的一些java面试基础题。

1.逻辑与&和短路与&&的区别?

相同点:
二者都要求运算符左右两端的布尔值都是true整个表达式的值才是true。
不同点:
1. &&具有短路的功能,而&不具备短路功能。
&&左边的表达式的值是false,右边的表达式会被直接被直接短路掉,不会进行运算。而&左边为false,还是对其右边进行运算
2. &还可以用作位运算符,当&操作符两边的表达式不是boolean类型时,&表示按位与操作,我们通常使用0x0f来与一个整数进行&
运算,来获取该整数的最低4个bit位,例如:0x31 & 0x0f的结果为0x01

 注意:很多时候我们可能都需要用&&而不是&,例如在验证用户登录时判定用户名不是null而且不是空字符串,
 应当写为:username != null &&!username.equals(“”),二者的顺序不能交换,更不能用&运算符,
 因为第一个条件如果不成立,根本不能进行字符串的equals比较,否则会产生NullPointerException异常。
 注意:逻辑或运算符(|)和短路或运算符(||)的差别也是如此。

2.JDK 和 JRE 有什么区别?

JDK:Java Development Kit 的简称,java 开发工具包,提供了 java 的开发环境和运行环境。
JRE:Java Runtime Environment 的简称,java 运行环境,为 java 的运行提供了所需环境。

具体来说 JDK 其实包含了 JRE,同时还包含了编译 java 源码的编译器 javac,还包含了很多 java 程序调试和分析的工具。
简单来说:如果你需要运行 java 程序,只需安装 JRE 就可以了,如果你需要编写 java 程序,需要安装 JDK。

3.jvm内存结构有哪些?分别是什么作用?

在这里插入图片描述

堆:存放对象实例
方法区:用于存储已被虚拟机加载的类信息、常量、静态变量【即:编译器编译后的代码等数据】
虚拟机栈:方法调用的数据都是通过栈传递的
本地方法栈:传递数据
区别:
虚拟机栈为虚拟机执行 Java 方法服务
本地方法栈则为虚拟机使用到的 Native 方法服务
程序计数器:当前线程所执行的字节码的行号指示器【分支、循环、跳转、异常处理、线程恢复等功能】

4.1写出类的生命周期,分别介绍每个周期的作用?【10分】

加载 -->验证–>准备–>解析–>初始化–>使用–>卸载
1. 类加载;2、类使用【对象实例化】; 3、类卸载【垃圾回收】;】
加载:将二进制流加载到虚拟机,存储到方法区
验证:验证文件格式
准备:为类变量分配内存并赋初识值
解析:将符号引用转换为直接引用
初识化:对类变量进行赋值

4.2写出对象的生命周期,分别介绍每个周期的作用?【10分】

创建阶段、使用阶段、不可视阶段、不可达阶段、可收集阶段、终结阶段、释放阶段。
不可视阶段→释放阶段:
使用已经结束,在可视区域不再使用
→找不到该对象直接或间接的强引用
→GC已经发现该对象不可达
→finalize方法已经被执行【垃圾回收器准备释放内存的时候,会先调用这个方法】
→对象空间已被重用

5.访问修饰符public,private,protected,以及不写(默认)时的区别?

在这里插入图片描述

6.面向对象的三大特征

这个应该是大家都知道的:封装、继承、多态嘛。
封装:也叫信息隐藏或数据访问保护。

意义:
		1.保护数据不被随意修改,提高代码可维护性
		2.仅暴露有限的必要接口,提高类的易用性。
		3.封装方法可以有效减少耦合
		 耦合:模块与模块之间,代码与代码之间的关联程度,对属性封装后,和调用相关的代码就会变得相对简单,可以降低耦合

继承: 继承是使用已存在的类的定义作为基础建立新类的技术,新类的定义可以增加新的数据或新的功能,也可以用父类的功能,但不能选择性地继承父类。通过使用继承可以提高代码复用性。继承是多态的前提。
关于继承如下 3 点请记住

子类拥有父类 private 的属性和方法。
子类可以拥有自己属性和方法,即子类可以对父类进行扩展。
子类可以用自己的方式实现父类的方法。

多态:允许不同类对象对同一消息做出响应,即同一消息可以根据发送对象的不同而采用多种不同的行为方式
好处:

  • 1.提高了代码的维护性(继承保证)
  • 2.提高了代码的扩展性(由多态保证)

Java实现多态有三个必要条件:继承、重写、向上转型。

继承:在多态中必须存在有继承关系的子类和父类。
重写:子类对父类中某些方法进行重新定义,在调用这些方法时就会调用子类的方法。
向上转型:在多态中需要将子类的引用赋给父类对象,只有这样该引用才能够具备技能调用父类的方法和子类的方法。
只有满足了上述三个条件,我们才能够在同一个继承结构中使用统一的逻辑实现代码处理不同的对象,从而达到执行不同的行为。

7.在.java文件内部可以有多少类(非内部类)?

在一个java文件中只能有一个public公共类,但是可以有多个default修饰的类.

8.构造器(constructor)是否可被重写(override)?

答:构造器不能被继承,因此不能被重写,但可以被重载。
另外关于继承这里补充一点:

在一个子类被创建的时候,首先会在内存中创建一个父类对象,然后在父类对象外部放上子类独有的属性,两者合起来形成一个子类的对象。
所以所谓的继承使子类拥有父类所有的属性和方法其实可以这样理解,子类对象确实拥有父类对象中所有的属性和方法,
但是父类对象中的私有属性和方法,子类是无法访问到的,只是拥有,但不能使用。
就像有些东西你可能拥有,但是你并不能使用。所以子类对象是绝对大于父类对象的,所谓的子类对象只能继承父类非私有的属性及方法的说法是错误的。
可以继承父类私有,只是无法访问到而已。

9.类的静态方法能否被子类重写?

不能.重写只适用于实例方法,不能用于静态方法,而子类当中含有和父类相同签名的静态方法,我们一般称之为隐藏.

10.请简述final在java中的作用

final 修饰的类叫最终类,该类不能被继承。
final 修饰的方法不能被重写。
final 修饰的变量叫常量,常量必须初始化,初始化之后值就不能被修改。 
被final修饰的方法,JVM会尝试将其内联,以提高运行效率
被final修饰的常量,在编译阶段会存入常量池中.

11.switch中可以存放什么类型参数?

在JDK 1.7之前,switch只能支持byte,short,char,int或者其对应的包装类以及Enum类型.
从JDK 1.7之后switch开始支持String类型.但到目前为止,switch都不支持long类型

  switch ("aaaa"){
           case "1":System.out.println("a");break;
           case "aaa":
               System.out.println("aaa");break;
           default:
               System.out.println("错误");    
       }

12.流程控制语句

break ,continue ,return 的区别及作用

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

13.如何正确的退出多层嵌套循环?

1.使用标号和break;
2.通过在外层循环中添加标识符

public static void main(String[] args) {
    ok:
    for (int i = 0; i < 10; i++) {
        for (int j = 0; j < 10; j++) {
            System.out.println("i=" + i + ",j=" + j);
            if (j == 5) {
                break ok;
            }
        }
    }
}

14.String和StringBuffer、StringBuilder的区别是什么?String为什么是不可变的

可变性

String类中使用字符数组保存字符串,private final char value[],所以string对象是不可变的。StringBuilder与StringBuffer都继承自AbstractStringBuilder类,在AbstractStringBuilder中也是使用字符数组保存字符串,char[] value,这两种对象都是可变的。

线程安全性

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

性能

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

对于三者使用的总结

如果要操作少量的数据用 = String

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

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

15.Java 中String str = null 与 String str = ""的区别是什么?

String str = null 表示声明了一个String 对象的引用str,但是没有为其分配内存空间,
而String str = ""则表示创建了一个长度为0 的空字符串,并在内存中为其分配了内存空间。

16.判断字符串是否为空有几种方式?

在Java 语言中,字符串为空有两层含义,一种是空值null另一种是0 长度的空字符串
(1)使用String 类声明字符串变量时,将其初始化为null,此时可以使用关系运算符“==”进行判断。
代码如下

String s = null ;
if(s==null){
		//字符串为空时需要执行的代码
}

(2)使用String 类声明字符串变量时,为其指定了0 长度的空字符串,此时可以使用equals()方法或length()方法进行判断。
使用equals()方法判断字符串是否为空,代码如下

String s = “” ;
if(s.equals()){
		//字符串为空时需要执行的代码
}

. 使用length()方法结合关系运算符“==”判断字符串是否为空,代码如下:

String s = “” ;
if(s.lenth()==0){
		//字符串为空时需要执行的代码
}

17.StringUtils类中isEmpty与isBlank的区别:

StringUtils.isEmpty(String str) 判断某字符串是否为空,为空的标准是 str==null 或 str.length()==0

System.out.println(StringUtils.isEmpty(null));        //true
System.out.println(StringUtils.isEmpty(""));          //true
System.out.println(StringUtils.isEmpty("   "));       //false
System.out.println(StringUtils.isEmpty("dd"));        //false

StringUtils.isBlank(String str) 判断某字符串是否为空或长度为0或由空白符(whitespace) 构成

System.out.println(StringUtils.isBlank(null));        //true
System.out.println(StringUtils.isBlank(""));          //true
System.out.println(StringUtils.isBlank("   "));       //true
System.out.println(StringUtils.isBlank("dd"));        //false    

补充:isNotEmpty

18.Integer a= 127 与 Integer b = 127相等吗?

对于对象引用类型:==比较的是对象的内存地址。
对于基本数据类型:==比较的是值。

如果整型字面量的值在-128到127之间,那么自动装箱时不会new新的Integer对象,而是直接引用常量池中的Integer对象,
超过范围 a1==b1的结果是false

public static void main(String[] args) {
    Integer a = new Integer(3);
    Integer b = 3;  // 将3自动装箱成Integer类型
    int c = 3;
    System.out.println(a == b); // false 两个引用没有引用同一对象
    System.out.println(a == c); // true a自动拆箱成int类型再和c比较
    System.out.println(b == c); // true

    Integer a1 = 128;
    Integer b1 = 128;
    System.out.println(a1 == b1); // false

    Integer a2 = 127;
    Integer b2 = 127;
    System.out.println(a2 == b2); // true
}

19.java 中的 Math.round(-2.5) 等于多少?(补充ceil和floor)

等于 -2,因为在数轴上取值时,中间值(0.5)向右取整,所以正 0.5 是往上取整,负 0.5 是直接舍弃。
Math类中提供了三个与取整有关的方法:ceil,floor,round,这些方法的作用于它们的英文名称的含义相对应,
例如:ceil的英文意义是天花板,该方法就表示向上取整,Math.ceil(11.3)的结果为12,Math.ceil(-11.6)的结果为-11;
floor的英文是地板,该方法就表示向下取整,Math.floor(11.6)的结果是11,Math.floor(-11.4)的结果-12;
最难掌握的是round方法,他表示“四舍五入”,算法为Math.floor(x+0.5),即将原来的数字加上0.5后再向下取整,
所以,Math.round(11.5)的结果是12,Math.round(-11.5)的结果为-11.
Math.round()符合这样的规律:小数点后大于5全部加,等于5正数加,小于5全不加。

20.用最有效率的方法计算2乘以8?

答: 2 << 3(左移3位相当于乘以2的3次方,右移3位相当于除以2的3次方)。

21.Java中保留小数点几位的方法:

1.使用String.format("%.小数几位f",num);
2.使用DecimalFormat类设置其格式: DecimailFormat format = new DecimalFormat(“0.00”)

22.short s1 = 1; s1 = s1 + 1;有错吗?short s1 = 1; s1 += 1;有错吗

对于 short s1 = 1; s1 = s1 + 1;由于 1 是 int 类型,因此 s1+1 运算结果也是 int型,需要强制转换类型才能赋值给 short 型。

而 short s1 = 1; s1 += 1;可以正确编译,因为 s1+= 1;相当于 s1 = (short(s1 + 1);其中有隐含的强制类型转换

23.是否可以继承String类?

答:String 类是final类,不可以被继承。

24.普通类和抽象类有哪些区别?

普通类不能包含抽象方法,抽象类可以包含抽象方法。
抽象类不能直接实例化,普通类可以直接实例化。
抽象类不能使用 final 修饰,抽象类就是让其他类继承的。被final修饰后不能被继承,这就彼此产生了矛盾。

25.接口和抽象类的区别

在这里插入图片描述

26.static存在的主要意义

static的主要意义是在于创建独立于具体对象的域变量或者方法。以致于即使没有创建对象,也能使用属性和调用方法!

static关键字还有一个比较关键的作用就是 用来形成静态代码块以优化程序性能。static块可以置于类中的任何地方,类中可以有多个static块。在类初次被加载的时候,会按照static块的顺序来执行每个static块,并且只会执行一次。

为什么说static块可以用来优化程序性能,是因为它的特性:只会在类加载的时候执行一次。因此,很多时候会将一些只需要进行一次的初始化操作都放在static代码块中进行。

static的独特之处
1、被static修饰的变量或者方法是独立于该类的任何对象,也就是说,这些变量和方法不属于任何一个实例对象,
而是被类的实例对象所共享。

怎么理解 “被类的实例对象所共享” 这句话呢?就是说,一个类的静态成员,
它是属于大伙的【大伙指的是这个类的多个对象实例,我们都知道一个类可以创建多个实例!】,
所有的类对象共享的,不像成员变量是自个的【自个指的是这个类的单个实例对象】…我觉得我已经讲的很通俗了,你明白了咩?

2、在该类被第一次加载的时候,就会去加载被static修饰的部分,而且只在类第一次使用时加载并进行初始化,注意这是第一次用就要初始化,后面根据需要是可以再次赋值的。

3、static变量值在类加载的时候分配空间,以后创建类对象的时候不会重新分配。赋值的话,是可以任意赋值的!

4、被static修饰的变量或者方法是优先于对象存在的,也就是说当一个类加载完毕之后,即便没有创建对象,也可以去访问。

static应用场景
因为static是被类的实例对象所共享,因此如果某个成员变量是被所有对象所共享的,那么这个成员变量就应该定义为静态变量。
因此比较常见的static应用场景有:

1、修饰成员变量 2、修饰成员方法 3、静态代码块 
4、修饰类【只能修饰内部类也就是静态内部类】 5、静态导包

static注意事项
1、静态只能访问静态。 2、非静态既可以访问非静态的,也可以访问静态的。

27.List、Set、Map 之间的区别是什么?

在这里插入图片描述

28.如何实现数组 和 List 之间的转换?

List转换成为数组:调用ArrayList的toArray方法。
数组转换成为List:调用Arrays的asList方法。

//      数组转换成集合:调用Arrays的asList方法。
		 String[] arr = {"aa","bb","cc"};
         List<String> list = Arrays.asList(arr);
         System.out.println(list);
//      List转换成为数组:调用ArrayList的toArray方法。
        String[] array = (String[])list.toArray();
        System.out.println(Arrays.toString(arrasy));

提示:toArray()方法返回的数组为Object数组。上面的代码将Object数组强制转换为了String数组,属于将父类的引用强制转化为子类的引用。
java中,这种父类引用强制转化为子类引用只有在父类的引用指向的真实对象类型为所要强制转化的子类类型的时候,才不会报错。

拓展1.:String类型字符串和List间的相互转换

public  void test() {
        //字符串转list<String>
        String str = "asdfghjkl";
        List<String> lis = Arrays.asList(str.split(""));
        System.out.println(lis);
        //list<String>转字符串
        System.out.println(String.join("", lis));
    }

备注:string.join()的用法:构造集合的成员,在第一个参数是代表在每一个成员之间使用指定的分隔符

List<string> list = new List<string>();
            list.Add("I");
            list.Add("Love");
            list.Add("You");
            string kk = string.Empty;
            kk = string.Join("-", list);
            Response.Write(kk);
 
//结果  I-Love-You

拓展2.将参数数组与字符串之间的转换

1.数组类型[] arr = { };
String str = Arrays.toString(arr);//以数组的形式输出字符串
2.使用String类中的构造方法将数组变为字符串
数组类型[] arr = { };
String s = new String(arr);

将字符串变成数组
例如
char[] chars = ss.toCharArray();

        //将参数数组变为字符串
        char[] c = {'A','B','C'};
        String cc = Arrays.toString(c);
        System.out.println(cc);
        String ss = new String(c);
        System.out.println(ss);
//        将字符串变成数组
        char[] chars = ss.toCharArray();
        //这个方法也可以
        String[] split = ss.split("");

输出的结果

[A, B, C]
ABC

29.collection 和 Collections 有什么区别?

java.util.Collection 是一个集合接口(集合类的一个顶级接口)。它提供了对集合对象进行基本操作的通用接口方法。Collection接口在Java 类库中有很多具体的实现。Collection接口的意义是为各种具体的集合提供了最大化的统一操作方式,其直接继承接口有List与Set。
Collections则是集合类的一个工具类/帮助类,其中提供了一系列静态方法,用于对集合中元素进行排序、搜索以及线程安全等各种操作。

30.Iterator 和 ListIterator 有什么区别?

Iterator可用来遍历Set和List集合,但是ListIterator只能用来遍历List。
Iterator对集合只能是前向遍历,ListIterator既可以前向也可以后向。
ListIterator实现了Iterator接口,并包含其他的功能,比如:增加元素,替换元素,获取前一个和后一个元素的索引,等等。

ListIterator就是在Iterator的基础上添加的prev系列方法,可以实现反向操作,
最重要的是添加了add和set方法,可以实现遍历List的时候同时进行添加,修改的操作。


public static void sop(Object obj){
    System.out.println(obj);
}
 
public static void main(String[] args){
    ArrayList a1 = new ArrayList();
    a1.add("java01");
    a1.add("java02");
    a1.add("java03");
    sop(a1);
    
    ListIterator li = a1.listIterator();
    while(li.hasNext()){
        Object obj = li.next();
        if(obj.equals("java01")){
            li.set("java009");
        }
    }
    sop(a1);
}
运行结果为:

[java01, java02, java03]
[java009, java02, java03]

31.常见的异常类有哪些?

  • ArithmeticException(算术异常)
  • ClassCastException (类转换异常)
  • IllegalArgumentException (非法参数异常)
  • IndexOutOfBoundsException (下标越界异常)
  • NullPointerException (空指针异常)
  • SecurityException (安全异常)
  • FileNotFoundException(文件找不到异常)
  • IOException:(输入/输入异常)
  • RuntimeException:(运行时异常)

**NullPointerException:**当应用程序试图访问空对象时,则抛出该异常。
SQLException:提供关于数据库访问错误或其他错误信息的异常。
IndexOutOfBoundsException:指示某排序索引(例如对数组、字符串或向量的排序)超出范围时抛出。
NumberFormatException:当应用程序试图将字符串转换成一种数值类型,但该字符串不能转换为适当格式时,抛出该异常。
**FileNotFoundException:**当试图打开指定路径名表示的文件失败时,抛出此异常。
IOException:当发生某种I/O异常时,抛出此异常。此类是失败或中断的I/O操作生成的异常的通用类。
ClassCastException:当试图将对象强制转换为不是实例的子类时,抛出该异常。
ArrayStoreException:试图将错误类型的对象存储到一个对象数组时抛出的异常。
IllegalArgumentException:抛出的异常表明向方法传递了一个不合法或不正确的参数。
ArithmeticException:当出现异常的运算条件时,抛出此异常。例如,一个整数“除以零”时,抛出此类的一个实例。
NegativeArraySizeException:如果应用程序试图创建大小为负的数组,则抛出该异常。
NoSuchMethodException:无法找到某一特定方法时,抛出该异常。
SecurityException:由安全管理器抛出的异常,指示存在安全侵犯。
UnsupportedOperationException:当不支持请求的操作时,抛出该异常。
RuntimeException:是那些可能在Java虚拟机正常运行期间抛出的异常的超类。

32.重写equals方法为什么要重写hashCode方法?

hashCode():是根据对象的地址转换之后返回的一个哈希值
equals():判断两对象内存的地址引用是否相同,返回boolean值

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

33.在try catch finally 中有return时的返回结果

1、不管有木有出现异常,finally块中代码都会执行;
2、当try和catch中有return时,finally仍然会执行;
3、finally是在return后面的表达式运算后执行的(此时并没有返回运算后的值,而是先把要返回的值保存起来,不管finally中的代码怎么样, 返回的值都不会改变,任然是之前保存的值),所以函数返回值是在finally执行前确定的;
4、finally中最好不要包含return,否则程序会提前退出,返回值不是try或catch中保存的返回值。
5、如果finally 函数里面有return; 那么函数最终会返回finally 里面执行之后的return的值给函数,如果没有return,函数值会是try里面return的值,或者catch里面的return值。
举例:
情况1:try{} catch(){}finally{} return;
显然程序按顺序执行。
情况2:try{ return; }catch(){} finally{} return;
程序执行try块中return之前的(包括return语句中的表达式运算)代码;
再执行finally块,最后执行try中return;
finally块之后的语句return,因为程序在try中已经return所以不再执行。
情况3:try{ } catch(){return;} finally{} return;
程序先执行try,如果遇到异常执行catch块,
有异常:则执行catch中return之前的(包括return语句中的表达式运算)代码,再执行finally语句中全部代码,
最后执行catch块中return. finally之后也就是4处的代码不再执行。
无异常:执行完try再finally再return.
情况4:try{ return; }catch(){} finally{return;}
程序执行try块中return之前的(包括return语句中的表达式运算)代码;
再执行finally块,因为finally块中有return所以提前退出。
情况5:try{} catch(){return;}finally{return;}
程序执行catch块中return之前(包括return语句中的表达式运算)代码;
再执行finally块,因为finally块中有return所以提前退出。
情况6:try{ return;}catch(){return;} finally{return;}//
程序执行try块中return之前(包括return语句中的表达式运算)代码;
有异常:执行catch块中return之前(包括return语句中的表达式运算)代码;
则再执行finally块,因为finally块中有return所以提前退出。
无异常:则再执行finally块,因为finally块中有return所以提前退出。
最终结论:
任何执行try 或者catch中的return语句之前,都会先执行finally语句,如果finally存在的话。
如果finally中有return语句,那么程序就return了,所以finally中的return是一定会被return的,
编译器把finally中的return实现为一个warning。

34.泛型的高级使用(限定泛型的范围)

上限使用
类型名称 <? extends 类>对象名称
只能接收该类型及其子类
下限使用
格式:类型名称 <? super 类 > 对象名称
只能接收该类型及其父类

;