java面试题集中营-01javase基础

由于本人要准备面试,所以更新一些常见的,自己收集的面试题

javase基础

1.短路运算符和位运算

短路运算

& 和 && 都可以实现 "和" 这个功能 区别:& 两边都运算,而 && 先算 && 左侧,若左侧为false 那么右侧就不运算,判断语句中推荐使用 &&,效率更高

| 和 || 和上面类似,都可以实现 "或" 这个功能 区别:||只要满足第一个条件,后面的条件就不再判断,而|要对所有的条件进行判断 把&&和||称之为短路运算符

&,| 称为按位与(只有对应的两个二进制数为1时,结果位才为1),按位或(有一个为1的时候,结果位就为1)

例如:3 &5 即 00000011 & 00000101 = 00000001 ,所以 3 & 5的值为1。

位运算

例如 2 << 3 原理:将一个数左移n位,相当于乘以2的n次方,位运算是CPU直接支持的,所以效率高,2 << 3是等于 16

为啥要使用位运算

  1. 基本的整型都是0和1表示的,那么位运算的使用在一些场合下就是很自然的事情。使用位运算肯定比做乘法和除法效率要高的多。但是现在很多编译器应该也是会优化的。(一些源码都是采用的位运算例如Map int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16)
  2. 设置一些权限的时候可以用位运算来操作。(目前大部分公司的权限设都是控制N多个字段来操作的)
java中如何表现各种进制

二进制:由0,1组成。以0b开头。 八进制:由0,1,...7组成。以0开头。 十进制:由0,1,...9组成。默认整数是十进制。 十六进制:由0,1,...9,a,b,c,d,e,f(大小写均可)组成。以0x开头。

异或运算 ^

一个数与另一个数异或两次是其本身, 一个数和自身异或结果是0 例子:实现两个变量交换

System.out.printf("a=%d, b=%d",a,b);
b = b^a;   // b = b^a^b
a = a^b;   // a = a1^b = a^b^a
System.out.printf("\na=%d, b=%d",a,b);

上述的方式变量交换就不需要多建一个中间变量,减少内存开支,提高运行效率

数据类型

java类型分类: 基础数据类型:byte、short、int、long、float、double、char、boolean 引用数据类型:其他都是引用类型(所有引用类型的父类是Object类) String和Enum分别是什么类型:引用类型

i++ 与 ++i

i++ 是先把i拿出来使用,然后再+1;++i 是先把i+1,然后再拿出来使用。两者都线程不安全的。

== 和 equals的区别
  • 基本数据类型比较 要用==判断是否相等
  • 引用数据类型: ==比较的是内存地址是否一样,不同对象的内存地址不一样,equals比较的是具体的内容, 也可以让开发者去定义什么条件去判断两个对象是否一样(通过重写对象的equals的方法)

try-catch-finally

下面代码 的try-catch-finally语句,try里面有个return, finally里面也有个return,结果会返回什么?为什么

public static int test1() {
        int a = 1;
        try {
            System.out.println(a / 0);
            a = 2;
        } catch (ArithmeticException e) {
            a = 3;
            return a; 
        } finally {
            a = 4;
        }
        return a;
    }
    
    public static int test2() {
        int a = 1;
        try {
            System.out.println(a / 0);
            a = 2;
        } catch (ArithmeticException e) {
            a = 3;
            return a;
        }finally{
            a = 4; 
            return a;
        }

test1返回3 test2 返回4

  • 在执行try、catch中的return之前一定会执行finally中的代码(如果finally存在),如果finally中有return语句,就会直接执行finally中的return方法,所以finally中的return语句一定会被执行的
  • 执行流程:finally执行前的代码里面有包含return,则会先确定return返回值,然后再执行finally的代码,最后再执行return

try-with-resource

jdk1.7后增加了try-with-resources,他是一个声明一个或多个资源的try语句。一个资源作为一个对象,必须在程序结束之后关闭。try-with-resources语句确保在语句的最后每个资源都被关闭,任何实现了java.lang.AutoCloseable和java.io.Closeable的对象都可以使用try-with-resource来实现异常处理和关闭资源。

 try (
FileInputStream fis = new FileInputStream("/Users/xdclass/Desktop/test.txt");
BufferedInputStream bis = new BufferedInputStream(fis);
FileOutputStream fos = new FileOutputStream("/Users/xdclass/Desktop/copy.txt");
BufferedOutputStream bos = new BufferedOutputStream(fos);
      ) {
            int size;
            byte[] buf = new byte[1024];
            while ((size = bis.read(buf)) != -1) {
                bos.write(buf, 0, size);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
  • 需要关闭的资源只要实现了java.lang.AutoCloseable,就可以⾃动被关闭,关闭的时候会调用文件流的onClose方法。
  • try()⾥⾯可以定义多个资源,它们的关闭顺序是最后在try()定义的资源先关闭

字符串考察

String str = new String("abc")创建了几个对象

如果常量池存在,则直接new一个对象; 如果常量池不存在,则在常量池创建一个对象,也在堆里面创建一个对象

字符串比较
 String str1= new String("hello word"); 
 String str2= "hello word"
 String str3= "hello word"
 System.out.println(str1 == str2) //false 
 System.out.println(str2 == str3) //true

原因:new创建新的对象会开辟新的空间,地址不同。str2与str3都是从常量池中获取,"hellow world"存在于常量池中。

常量池知识补充

Java中的常量池,实际上分为两种形态:静态常量池和运行时常量池。

静态常量池,即*.class文件中的常量池,class文件中的常量池不仅仅包含字符串(数字)字面量,还包含类、方法的信息,占用class文件绝大部分空间。这种常量池主要用于存放两大类常量:字面量(Literal)和符号引用量(Symbolic References),字面量相当于Java语言层面常量的概念,如文本字符串,声明为final的常量值等,符号引用则属于编译原理方面的概念,包括了如下三种类型的常量:

  • 类和接口的全限定名
  • 字段名称和描述符
  • 方法名称和描述符

运行时常量池,则是jvm虚拟机在完成类装载操作后,将class文件中的常量池载入到内存中,并保存在方法区中,我们常说的常量池,就是指方法区中的运行时常量池。

运行时常量池相对于CLass文件常量池的另外一个重要特征是具备动态性,Java语言并不要求常量一定只有编译期才能产生,也就是并非预置入CLass文件中常量池的内容才能进入方法区运行时常量池,运行期间也可能将新的常量放入池中,这种特性被开发人员利用比较多的就是String类的intern()方法。 String的intern()方法会查找在常量池中是否存在一份equal相等的字符串,如果有则返回该字符串的引用,如果没有则添加自己的字符串进入常量池。

网上的常量池例子:

String s1 = "Hello";
String s2 = "Hello";
String s3 = "Hel" + "lo";
String s4 = "Hel" + new String("lo");
String s5 = new String("Hello");
String s6 = s5.intern();
String s7 = "H";
String s8 = "ello";
String s9 = s7 + s8;

System.out.println(s1 == s2);  // true
System.out.println(s1 == s3);  // true
System.out.println(s1 == s4);  // false
System.out.println(s1 == s9);  // false
System.out.println(s4 == s5);  // false
System.out.println(s1 == s6);  // true
  • s1 == s2这个非常好理解,s1、s2在赋值时,均使用的字符串字面量,说白话点,就是直接把字符串写死,在编译期间,这种字面量会直接放入class文件的常量池中,从而实现复用,载入运行时常量池后,s1、s2指向的是同一个内存地址,所以相等。
  • s1 == s3这个地方有个坑,s3虽然是动态拼接出来的字符串,但是所有参与拼接的部分都是已知的字面量,在编译期间,这种拼接会被优化,编译器直接帮你拼好,因此String s3 = "Hel" + "lo";在class文件中被优化成String s3 = "Hello",所以s1 == s3成立。只有使用引号包含文本的方式创建的String对象之间使用“+”连接产生的新对象才会被加入字符串池中。
  • s1 == s4当然不相等,s4虽然也是拼接出来的,但new String("lo")这部分不是已知字面量,是一个不可预料的部分,编译器不会优化,必须等到运行时才可以确定结果,结合字符串不变定理,鬼知道s4被分配到哪去了,所以地址肯定不同。对于所有包含new方式新建对象(包括null)的“+”连接表达式,它所产生的新对象都不会被加入字符串池中。 如图
  • s1 == s9也不相等,道理差不多,虽然s7、s8在赋值的时候使用的字符串字面量,但是拼接成s9的时候,s7、s8作为两个变量,都是不可预料的,编译器毕竟是编译器,不可能当解释器用,不能在编译期被确定,所以不做优化,只能等到运行时,在堆中创建s7、s8拼接成的新字符串,在堆中地址不确定,不可能与方法区常量池中的s1地址相同。 如图
  • s4 == s5已经不用解释了,绝对不相等,二者都在堆中,但地址不同。
  • s1 == s6这两个相等完全归功于intern方法,s5在堆中,内容为Hello ,intern方法会尝试将Hello字符串添加到常量池中,并返回其在常量池中的地址,因为常量池中已经有了Hello字符串,所以intern方法直接返回地址;而s1在编译期就已经指向常量池了,因此s1和s6指向同一地址,相等。

还有一个经典的例子

public static final String A = "ab"; // 常量A
public static final String B = "cd"; // 常量B
public static void main(String[] args) {
     String s = A + B;  // 将两个常量用+连接对s进行初始化 
     String t = "abcd";   
    if (s == t) {   
         System.out.println("s等于t,它们是同一个对象");   
     } else {   
         System.out.println("s不等于t,它们不是同一个对象");   
     }   
 } 
s等于t,它们是同一个对象

A和B都是常量,值是固定的,因此s的值也是固定的,它在类被编译时就已经确定了。也就是说:String s=A+B; 等同于:String s="ab"+"cd";

public static final String A; // 常量A
public static final String B;    // 常量B
static {   
     A = "ab";   
     B = "cd";   
 }   
 public static void main(String[] args) {   
    // 将两个常量用+连接对s进行初始化   
     String s = A + B;   
     String t = "abcd";   
    if (s == t) {   
         System.out.println("s等于t,它们是同一个对象");   
     } else {   
         System.out.println("s不等于t,它们不是同一个对象");   
     }   
 } 
s不等于t,它们不是同一个对象

A和B虽然被定义为常量,但是它们都没有马上被赋值。在运算出s的值之前,他们何时被赋值,以及被赋予什么样的值,都是个变数。因此A和B在被赋值之前,性质类似于一个变量。那么s就不能在编译期被确定,而只能在运行时被创建了。

三个结论:

  • 必须要关注编译期的行为,才能更好的理解常量池。
  • 运行时常量池中的常量,基本来源于各个class文件中的常量池。
  • 程序运行时,除非手动向常量池中添加常量(比如调用intern方法),否则jvm不会自动添加常量到常量池。

除了最常用的字符串常量池,还有整型常量池、浮点型常量池(java中基本类型的包装类的大部分都实现了常量池技术,即Byte,Short,Integer,Long,Character,Boolean;两种浮点数类型的包装类Float,Double并没有实现常量池技术) 等等。

但都大同小异,只不过数值类型的常量池不可以手动添加常量,程序启动时常量池中的常量就已经确定了,比如整型常量池中的常量范围:-128~127,(Byte,Short,Integer,Long,Character,Boolean)这5种包装类默认创建了数值[-128,127]的相应类型的缓存数据,但是超出此范围仍然会去创建新的对象。

例如在自动装箱时,把int变成Integer的时候,是有规则的,当你的int的值在-128-IntegerCache.high(127) 时,返回的不是一个新new出来的Integer对象,而是一个已经缓存在堆 中的Integer对象,(我们可以这样理解,系统已经把-128到127之 间的Integer缓存到一个Integer数组中去了,如果你要把一个int变成一个Integer对象,首先去缓存中找,找到的话直接返回引用给你就 行了,不必再新new一个),如果不在-128-IntegerCache.high(127) 时会返回一个新new出来的Integer对象。

String、StringBuffer与StringBuilder的区别?分别在哪些场景下使用

三者都是final, 不允许被继承 在本质都是char[]字符数组实现 String、StringBuffer与StringBuilder中,String是不可变对象,另外两个是可变的

StringBuilder 效率更快,因为它不需要加锁,不具备多线程安全 StringBuffer里面操作方法用synchronized ,效率相对更低,是线程安全的;

使用场景:

  • 操作少量的数据用String,但是常改变内容且操作数据多情况下最好不要用 String ,因为每次生成中间对象性能会降低
  • 单线程下操作大量的字符串用StringBuilder,虽然线程不安全但是不影响
  • 多线程下操作大量的字符串,且需要保证线程安全 则用StringBuffer

javase类

面向对象的四大特性是?分别解释下

封装,继承,多态,抽象

overload与override的区别

overload重载:表示同一个类中可以有多个名称相同的方法,但这些方法的参数列表各不相同,参数个数或类型不同 override重写:表示子类中的方法可以与父类中的某个方法的名称和参数完全相同,也就是重写了父类的方法。

接口是否可以继承接口?接口是否支持多继承?类是否支持多继承?接口里面是否可以有方法实现

接口可以继承接口使用extends关键字。
接口与类都不支持多继承,但一个类可以实现多个接口
接口里面可以有方法实现(java8之后可以的)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值