Java基础面试题干货系列(二)

创作不易,如果觉得这篇文章对你有帮助,欢迎各位老铁点个赞呗,您的支持是我创作的最大动力!

前言

越来越感觉到基础知识的薄弱,所以,这里作一下总结,加深下自己对基础知识的理解。Java基础知识实在是太多了,这里打算持续更新,总结下常见的面试题和知识点。

博主打算每期更新10个面试知识点,后续慢慢迭代更新。

博主这里总结的目的在于,为童鞋们提供面试的参考知识点之外,博主也可以巩固自己的基础知识,工作几年的人都知道,如果稍微不留神,慢慢的,你的基础知识忘记的差不多了

面试基础知识总结

1 Java中的运算符有哪些

Java中有大概六种运算符,分别是算术运算符连接运算符赋值运算符关系运算符逻辑运算符三目运算符

1.1 算术运算符

算数运算符大概有7种:+ - * / %(求余数,取模) ++ --

  • + 运算符
    当+两边的操作数都是数值类型时,则做加法运算

  • - 运算符
    当做减法运算时,必须保证两个操作数都是数值类型

  • * 运算符
    该运算符用于计算两个数值的乘积

  • / 运算符
    该运算符用于两个数值进行相除,需要注意的是,除数不能为0

  • % 运算符
    该运算符用于求余数,即进行取模。就是如果被除数 % 除数可以整除,返回结果为0,否则返回的结果为余数

    举例:

    	@Test
        public void test1() {
            System.out.println(2 % 3);//2
            System.out.println(2 % 2);//0
            System.out.println(3 % 2);//1
            System.out.println(11 % 4);//3
        }
    

    执行结果为:

    2
    0
    1
    3
    
  • ++ 运算符
    ++运算符表示自身加1
    ++运算符又分为:前置++、后置++

    前置++:将++编写在变量名称前面,先自身加1,然后再做其他运算

    int a = 3;
    int b = ++a;  //a = 4   b = 4
    

    后置++:将++编写在变量名称后面,先做其他运算,然后再自身加1

    int x = 5;
    int y = x++;  //y = 5  x = 6
    

    代码示例:

    	@Test
        public void test2() {
            //前置++
            int a = 5;
            int b = ++a;
            System.out.println("前置++结果是:a= " + a + ",b= " + b);
            //前置++结果是:a= 6,b= 6 通过结果说明,前置++先进行了自身+1操作,后进行了赋值运算
    
            //后置++
            int num = 5;
            int numTwo = num++;
            System.out.println("后置++结果是:num= " + num + ",numTwo= " + numTwo);
            //后置++结果是:num= 6,numTwo= 5 结果说明,后置++先进行了赋值运算,后进行了自身+1操作
        }
    

    执行结果:

    前置++结果是:a= 6,b= 6
    后置++结果是:num= 6,numTwo= 5
    
  • -- 运算符
    --运算符表示自身减1
    --运算符又分为前置、后置,这个的具体使用和++运算符类似,这里不再阐述

1.2 连接运算符

连接运算符+作用:求和、连接

当+两边的操作数都是数值类型时,则做加法运算
当+两边的操作数中,只要有一个为非数值类型,则做字符串连接,最终连接后的结果为String类型。

1.3 赋值运算符

  • 赋值运算符:=
    表示将右侧的值赋给左侧的变量名称

    如:int a = 10;
    表示把值10赋值给左边的变量a

  • 扩展赋值运算符大概有5种:+= -= *= /= %=

    当使用扩展赋值运算符时,变量最终的数据类型没有发生改变

    举例:

    	@Test
        public void test3() {
            byte a = 3;
            //a = a + 10;//这种写法会报编译时错误,因为 = 号后面计算的结果为int类型,但是编译时不进行结果的计算,直接把int赋值给byte会提示编译错误,需要byte,找到int
    
            a += 10;//计算过程等价于a = (byte)(a + 10); 扩展赋值运算符a += 10;这种写法就可以,因为扩展赋值运算符时,变量最终的数据类型没有发生改变,但是,可能损失精度
            System.out.println(a);//13
    
            int x = 15;
            //x = x + 0.5; //出现编译错误,需要int,发现double
            x += 0.5;//计算过程等价于x = (int)(x + 0.5)
            System.out.println(x);//结果x = 15
        }
    

1.4 关系运算符

关系运算符大概有6种:> < >= <= == !=
关系运算符:最终结果为boolean类型的值
运算符计算时优先级别:算术运算符 > 关系运算符 > 赋值运算符

1.5 逻辑运算符

逻辑运算符分类大概有6种:
逻辑与& 逻辑或| 逻辑异或^ 逻辑非! 短路与&& 短路或||

  • 逻辑与&,表示并且
    当两个条件同时为true时,则结果为true,否则结果为false

  • 逻辑或|,表示或者
    当两个条件中有一个为true时,则结果为true,否则为false

  • 逻辑异或^
    当两个条件的值不同时,则结果为true,否则为false
    true ^ true ------- > false
    true ^ false------- > true
    fasle ^ false ------ > false
    false ^ true ------ > true

  • 逻辑非!
    表示对boolean类型的值进行取反

  • 短路与&&,类似于逻辑与,都表示并且
    短路与、逻辑与运行结果都相同,但是执行过程可能不同,当使用短路与,并且第一个条件为false时,则结果直接为false,不会进行第二个条件的判断,而短路与会进行后面条件的判断。

  • 短路或||,类似于逻辑或,都表示或者
    短路或、逻辑或运行结果都相同,但是执行过程可能不同,当使用短路或,并且第一个条件为true时,则结果直接为true

总结:
逻辑运算符,最终结果为boolean类型

博主建议使用短路与和短路或,这样有些场景下可以减少一次判断,有利于提高性能

1.6 三目运算符

三目运算符也叫做条件运算符,Java中常用于不同条件下赋不同的值

语法格式: 条件 ? 代码1 : 代码2

举例:

 	@Test
    public void test4() {
        int x = 15;
        System.out.println(x == 15 ? "yes" : "no");//yes
    }

2 =与==、&与&&、|与||有什么区别

  • = 与 ==
    =属于赋值运算符,将右侧的值赋给左侧的变量名称
    ==属于关系运算符,如果两边是数值类型,判断左右两边的值是否相等。如果左右两边是引用类型,比较的是内存地址是否相等,计算的结果为boolean类型

  • & 与 &&
    &是逻辑与, &&是短路与,都属于逻辑运算符,都表示并且,执行结果都相同
    不同的是,当使用短路与&&时,如果第一个条件为false时,不会进行后面的判断,结果直接为false

  • | 与 ||
    |是逻辑或,||是短路或,都属于逻辑运算符,都表示或者,结果都相同
    当使用短路或||时,并且第一个条件为true时,则结果直接为true

3 说说Java中的switch case穿透现象

switch case中的case穿透现象指的是: 由于break是可有可无的,当没有编写break,则从当前第一个匹配的case一直向下执行,下一个case的java语句也会执行,这种现象就是case穿透

代码举例:

	/**
     * Java中case穿透现象演示
     */
    @Test
    public void test6() {
        char a = 'A';
        switch (a) {
            case 'A':
                System.out.println("执行结果:A");
            case 'B':
                System.out.println("执行结果:B");
            case 'C':
                System.out.println("执行结果:C");
                break;
            case 'D':
                System.out.println("执行结果:D");
                break;
            default:
                break;
        }
    }

执行结果:

执行结果:A
执行结果:B
执行结果:C

以上执行结果也说明了这一点,虽然只有A匹配上了,但是由于A和B没有写break,打印了"执行结果:A"之后,又向下一个case执行了,打印了“执行结果:B”和“执行结果:C”,直到遇到了C中的break,程序才结束。

switch case语法基础介绍:

  • switch case:只能做等值操作

    语法格式:

    switch(表达式){
    	case 常量值1:
    		break;
    	case 常量值2:
    		break;
    	default:
    		break;
    }
    
  • switch case中,表达式的值的类型
    基本数据类型:java 1.6(包含)以前可以是byteshortintchar,因为(byte、short、char)类型的值可以默认转换为int类型

    JDK5以后可以是枚举类型

    在JDK1.7及以后还可以是String类型

    不支持long、float、double、boolean四种基本类型

    在测试时发现,包装器类型Byte,Short,Character,Integer后发现都可以正确打印,于是便说switch也支持byte,short,char,int的包装类。这种说法不完全正确,之所以switch能够支持他们的包装类,是因为自动拆箱(从JDK1.5开始支持自动拆箱和自动装箱,自动拆箱就是自动将引用数据类型转化为基本数据类型,自动装箱就是自动将基本数据类型转化为引用数据类型)的原因。

  • 在switch中可以编写任意多个case

  • case后面常量值的类型必须与表达式的类型一致

  • break 表示中断,当遇到break则执行switch case外面的语句

  • default是可有可无的,如果有default,一个switch中最多编写一个default,当所有case都不满足时则执行default

  • 常见的注意事项:
    a、case后面常量值的顺序可以任意,一般按顺序编写
    b、default顺序可以编写在switch中的任意位置
    c、当所有case都不满足时则执行default
    d、建议default编写在所有case的后面
    e、case也可以合并
    如:

    	switch(表达式){
    		case 常量值1: case 常量值2:
    			java语句;
    			break;
    		default:
    			break;
    	}
    
    	@Test
        public void test5() {
            char a = 'A';
            switch (a) {
                case 'A': case 'B':
                    System.out.println("执行结果:A-B");//执行结果:A-B
                    break;
                case 'C':
                    System.out.println("执行结果:C");
                    break;
                default:
                    break;
            }
        }
    

    f、break是可有可无的,当没有编写break,则从当前第一个匹配的case一直向下执行(也就是穿透)

    因此,开发中,需要根据需求编写合适的break

4 Java中break、continue、return有什么区别

  • 使用场合不同
    break: 表示中断,可以在switch case中使用,也可以在循环中使用

    continue: 表示继续,只能在循环中使用

    return: 表示返回,只能在方法中使用

  • 作用不同
    break: 表示中断,当在switch case中或在循环中遇到break,则结束当前整个switch case或循环,执行外面的语句

    continue: 表示继续,当在循环中遇到continue,则结束当次(本次)循环,继续执行下一次循环

    return: 表示返回结果,当方法中遇到return时,则返回到方法的调用处
    注意:return有一种特殊形式,当方法是无返回类型时,则可以在方法体中编写return,但是必须编写为return;

5 Java中方法的重载与方法的覆盖

请参考我的博客:Java中方法的重载与方法的覆盖

6 Java中抽象类与接口有什么区别

请参考我的博客:Java中抽象类与接口

7 说一说Java中,什么是类,什么是对象


  • Java中的是一种概念,是一种数据引用类型。类就是现实世界中,具有共同特征的一堆事物,对这些事物进行抽象,就形成了类。实际上,类并不存在,类只是人类大脑抽象的结果。

    是对具有共性事物的抽象描述,是在概念上的一个定义,类不是具体的。那么,如何发现类呢?通常根据名词(概念)来发现类。比如说:成绩管理系统中,学生、老师、班级、课程等,都可以抽象为一个类。

    类的定义:
    格式:类的修饰符 class 类名 extends 父对象的名称 implements 接口名称

    如:public class Student extends Person implements SpeakInterface
    

    描述对象的特征,称为属性,如:颜色、价格、尺寸…….
    对象所做的事情,称为方法行为
    如:

    张三对象
    	属性:名字、性别、年龄、身高、体重、住址
    	方法:学习、说话、吃饭
    李四对象
    	属性:姓名、年龄、住址、性别
    	方法:睡觉、学习、游泳
    

    将多个对象找到相同的属性和方法组合在一起,就形成了类

    简单来说,可理解为类 = 属性 + 方法,属性(成员变量)来源于类的状态,而方法来源于动作(成员方法)。

  • 对象
    万物皆对象,也就是说,对象是一个具体的实例。对象是一种实际存在的个体,从类到对象的过程,就是具体化(实例化)的过程
    从对象到类的过程,就是抽象的过程

    总的来说,对共性事物进行了抽象,就形成了类,这些类具体化,就成为实例(对象)。也就是说,一个类的具体化(实例化)的过程,就是对象(实例)。

8 谈一谈面向对象与面向过程

请参考我的博客:Java基础知识总结 中1.8节 面向对象与面向过程的介绍

9 参数传递

面试笔试题中,容易考察参数的传递。

  • 基本数据类型作为参数传递
    传递的是真正的值,在一个方法中改变变量的值,对另一个方法中变量的值没有任何影响,各自变量是独立的

  • 引用数据类型作为参数传递
    传递的是地址,也就数说多个引用名称共用同一个对象

具体的,可以参考我的另一篇博文Java中参数传递,进行深入了解。

10 请谈谈你对Java多态的理解

10.1 多态是什么

多态(Polymorphism)按字面的意思就是“多种状态”。在面向对象语言中,接口的多种不同的实现方式即为多态。用白话来说,就是多个对象调用同一个方法,得到不同的结果。

10.2 多态的语法格式

父类类名 引用名称 = new 子类类名();

当是多态时,该引用名称只能访问父类中的属性和方法,但是访问的时候,会优先访问子类重写以后的方法。

10.3 满足多态的条件

  • 子类必须继承父类
  • 子类必须重写父类的方法
  • 父类引用指向子类对象,即:父类类名 引用名称 = new 子类类名();

10.4 使用多态好处

  • 使用多态可以使代码之间的耦合度降低

  • 减少冗余代码的同时,也使得项目的扩展能力更强

    注:耦合度指的是代码(程序)之间的关联程度

10.5 多态中的类型转换

Java多态中,有两种类型转换:向上转型向下转型

  • 向上转型
    向上转型,也叫做自动类型转换,子类型赋值给父类型(父类型的引用指向子类型),构成多态
    父类类型 引用名称 = new 子类类名();

    当使用多态方式调用方法时,该引用名称只能访问父类中的属性和方法。编译器首先检查父类中是否有该方法,如果没有,则编译错误。如果有,再去调用子类的同名(重写)方法。

  • 向下转型
    向下转型,也叫做强制类型转换,父类型赋值给子类型

    当使用多态时,并且访问子类独有的属性或方法时,则必须进行向下转型

    当进行向下转型时,建议先使用 instance of 关键字进行判断,判断合法时,则在转为对应的类型,否则可能会出现类型转换异常 java.lang.ClassCastException。

    说明:instance of 关键字用于判断一个对象,是否属于某个指定的类或其子类的实例。

10.6 多态的实现方式

  • 普通子类重写父类方法

  • 接口
    生活中的接口最具代表性的就是插座,例如一个三接头的插头都能接在三孔插座中,因为这个是每个国家都有各自规定的接口规则,有可能到国外就不行,那是因为国外自己定义的接口类型。

    USB接口也很典型,有了这个,使得接口统一,生活更加方便

  • 抽象类和抽象方法

10.7 多态简单使用案例

场景:假如有个饲养员,需要给不同的宠物喂食,下面给出使用多态不使用多态的实现方式。

不使用多态的实现:

首先定义一个抽象类Animal、一个饲养员类AnimalKeeper、一个宠物类Dog和一个宠物类Cat

public abstract class Animal {

    public void eat() {
        System.out.println("动物吃东西!");
    }
}
/**
 * 饲养员
 */
public class AnimalKeeper {

    /**
     * 给宠物猫喂食
     *
     * @param cat
     */
    public void feed(Cat cat) {
        cat.eat();
    }

    /**
     * 给宠物狗喂食
     *
     * @param dog
     */
    public void feed(Dog dog) {
        dog.eat();
    }

}
public class Dog extends Animal {

    @Override
    public void eat() {
        System.out.println("狗啃骨头!");
    }
}
public class Cat extends Animal {

    @Override
    public void eat() {
        System.out.println("猫吃鱼!");
    }
}

测试类:

public class PolymorphicTest {

    public static void main(String[] args) {
        //创建饲养员对象
        AnimalKeeper animalKeeper = new AnimalKeeper();

        //创建宠物对象
        Cat cat = new Cat();
        animalKeeper.feed(cat);//猫吃鱼!

        Dog dog = new Dog();
        animalKeeper.feed(dog);//狗啃骨头!
    }
}

以上实现看起来没有什么问题,也容易理解,在目前情况下,饲养员可以满足喂养宠物的需求。但是,过了一周,饲养员又喂养了一只鸟,这时候不得不修改AnimalKeeper类,使其可以饲养宠物鸟,不仅违反了Java中的开闭原则,而且以上代码的实现,扩展性极差。

使用多态的实现:
只需要对以上代码中,饲养员类AnimalKeeper进行替换,新增一个饲养员类AnimalKeeperPolymorphic类。

/**
 * 饲养员
 */
public class AnimalKeeperPolymorphic {

    /**
     * 饲养员给宠物喂食
     *
     * @param animal
     */
    public void feed(Animal animal) {
        animal.eat();
    }

}

测试用例:

public static void change() {
   //创建饲养员对象
    AnimalKeeperPolymorphic animalKeeper = new AnimalKeeperPolymorphic();

    //创建宠物对象
    Cat cat = new Cat();
    animalKeeper.feed(cat);//猫吃鱼!

    Dog dog = new Dog();
    animalKeeper.feed(dog);//狗啃骨头!
}

这种实现有什么好处呢,当新需求来了,需要扩展时,不需要修改饲养员的代码。比如说刚才那个需求,新增加一个宠物鸟,只需要新建一个宠物鸟类,实现Animal接口,不仅遵循了OCP原则,也可以实现饲养宠物鸟的功能。

10.8 多态分析

以上文中示例代码进行分析,看看多态是如何使用的。

AnimalKeeperPolymorphic中的feed方法,使用了多态。

当饲养员喂养宠物狗时,其实执行的是:

Animal animal = new Dog();

当饲养员喂养宠物猫时,其实执行的是:

Animal animal = new Cat();

这种属于向上转型,里面有继承(cat继承Animal)关系,重写了父类eat方法,子类型赋值给父类型(父类型的引用指向子类型),构成了多态。

Animal animal = new Cat(); 程序在编译阶段,animal引用类型被编译器看做Animal类型,所以程序在编译阶段animal引用绑定的是Aninmal类中的eat()方法,这个过程叫做Java多态的静态绑定

程序在运行的时候,堆中的对象实际上是Cat类型,而Cat对象已经覆盖(重写)了父类Animal的eat()方法,所以程序在运行阶段,对象绑定的方法是Cat中的eat()方法,这个过程叫做Java多态的动态绑定

写博客是为了记住自己容易忘记的东西,另外也是对自己工作的总结,希望尽自己的努力,做到更好,大家一起努力进步!

如果有什么问题,欢迎大家一起探讨,代码如有问题,欢迎各位大神指正!

给自己的梦想添加一双翅膀,让它可以在天空中自由自在的飞翔!

猜你喜欢

转载自blog.csdn.net/smilehappiness/article/details/106155630