【Android面试】2023最新面试专题四:Java核心基础(上)

1 Java中提供了抽象类还有接口,开发中如何去选择呢?

这道题想考察什么?

Java是面向对象编程的,抽象是它的一大特征,而体现这个特征的就是抽象类与接口。抽象类与接口某些情况下都能够互相替代,但是如果真的都能够互相替代,那Java为何会设计出抽象与接口的概念?这就需要面试者能够掌握两者的区别。

考察的知识点

OOP(面向对象)编程思想,抽象与接口的区别与应用场景;

考生应该如何回答

抽象类的设计目的,是代码复用;接口的设计目的,是对类的行为进行约束。

  • 当需要表示is-a的关系,并且需要代码复用时用抽象类
  • 当需要表示has-a的关系,可以使用接口

比如狗具有睡觉和吃饭方法,我们可以使用接口定义:

public interface Dog {
    
    
	public void sleep();
	public void eat();
}

但是我们发现如果采用接口,就需要让每个派生类都实现一次sleep方法。此时为了完成对sleep方法的复用,我们可以选择采用抽象类来定义:

public abstract class Dog {
    
    
    public void sleep(){
    
    
        //......
    }
	public abstract void eat();
}

但是如果我们训练,让狗拥有一项技能——握手,怎么添加这个行为? 将握手方法写入抽象类中,但是这会导致所有的狗都能够握手。

握手是训练出来的,是对狗的一种扩展。所以这时候我们需要单独定义握手这种行为。这种行为是否可以采用抽象类来定义呢?

public abstract Handshake{
    
    
    abstract void doHandshake();
}

如果采用抽象类定义握手,那我们现在需要创建一类能够握手的狗怎么办?

public class HandShakeDog extends Dog //,Handshake

大家都知道在Java中不能多继承,这是因为多继承存在二义性问题。

二义性问题:一个类如果能够继承多个父类,那么如果其中父类A与父类B具有相同的方法,当调用这个方法时会调用哪个父类的方法呢?

所以此时,我们就需要使用接口来定义握手行为:

public interface Handshake{
    
    
    void doHandshake();
}
public class HandShakeDog extends Dog implements Handshake

不是所有的狗都会握手,也不止狗会握手。我们可以同样训练猫,让猫也具备握手的技能,那么猫Cat类,同样能够实现此接口,这就是"has-a"的关系。

抽象类强调从属关系,接口强调功能,除了使用场景的不同之外,在定义中的不同则是:

在这里插入图片描述

2 重载和重写是什么意思,区别是什么? (京东)

这道题想考察什么?

Java基础

考察的知识点

面向对象多态的基础概念

考生应该如何回答

重写(Override)

重写就是重新写的意思,当父类中的方法对于子类来说不适用或者需要扩展增强时,子类可以对从父类中继承来的方法进行重写。

比如Activity是Android开发中四大组件之一。在Activity中存在各种声明周期方法:onCreate、onStart …等等。而我们应用中需要使用Activity来展示UI,那么我们会需要编写自己的类继承自Activity。

public class MainActivity extends Activity{
    
    
    @Override
    protected void onCreate(Bundle savedInstanceState) {
    
    
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }
}

在上述代码中,onCreate就是被我们重写的Activity中定义方法。我们在onCreate增加了setContentView

的调用,完成了对超类Activity中对应方法的修改与扩展。

重载(Overload)

重载则是在同一个类中,允许存在多个同名方法,只要它们的参数列表不同即可。比如在Android开发中,我们会使用LayoutInflater的inflate方法加载布局,inflate方法存在多个定义,其中包括两个参数的,与三个参数的:

public View inflate(@LayoutRes int resource, @Nullable ViewGroup root) {
    
    
	return inflate(resource, root, root != null);
}
public View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot) {
    
    
    //......
}

可以看到在两个参数的inflate方法中,会为我们调起三个参数的inflate方法,而定义第一个inflate方法的目的就是为了提供默认的attachToRoot参数值。因为Java中无法定义方法参数默认值,所以我们经常使用重载达成此目的。


3 静态内部类是什么?和非静态内部类的区别是什么?

这道题想考察什么?

掌握static的作用与注意事项

考察的知识点

Java中关键字static

考生应该如何回答

在定义内部类时,如果内部类被static声明,则该内部类为静态内部类。

public class OuterClass{
    
    
    static class InnerClass{
    
    
        
    }
}

当内部类被static声明,那么在内部类中就无法直接使用外部类的属性。比如编写普通内部类:

public class OuterClass{
    
    
    int i;
    public class InnerClass{
    
    
        public InnerClass(){
    
    
            i = 10;
        }
    }
}

此时对OuterClass.java 进行编译,会生成:OuterClass.class 与 OuterClass$InnerClass.class 两个文件。对后者反编译我们将看到:

public class OuterClass$InnerClass {
    
    
    public OuterClass$InnerClass(OuterClass var1) {
    
    
        this.this$0 = var1;
        var1.i = 10;
    }
}

可以看到,普通内部类构造方法中实际上会隐式的传递外部类实例对象给内部类。在内部类中使用外部类的属性:i。实际上是通过外部类的实例对象:var1获取的。同时如果我们需要构建出InnerClass的实例对象,非静态内部类也无法脱离外部类实体被创建。

下面我们将InnerClass定义为static静态内部类:

public class OuterClass{
    
    
    int i;
    public static class InnerClass{
    
    
        public InnerClass(){
    
    
            //i = 10; 
        }
    }
}

此时无法使用外部类的普通成员属性:i。其对应字节码为:

public class OuterClass$InnerClass {
    
    
    public OuterClass$InnerClass() {
    
    
       
    }
}

静态内部类中不再隐式的持有外部类的实例对象。但是如果我们将属性i定义为static,那么在静态内部类中也是可以直接使用外部类的静态成员属性的,此时字节码为:

public class OuterClass$InnerClass {
    
    
    public OuterClass$InnerClass() {
    
    
        OuterClass.i = 10;
    }
}

内部静态类不需要有指向外部类的引用。但非静态内部类需要持有对外部类的引用。但是静态内部类能够直接利用new OuterClass.InnerClass() 实例化。

因此静态内部类与非静态内部类的区别有:

  1. 非静态内部类能够访问外部类的静态和非静态成员,静态类只能访问外部类的静态成员。
  2. 非静态内部类不能脱离外部类被创建,静态内部类可以。

4 Java中在传参数时是将值进行传递,还是传递引用?

这道题想考察什么?

是否了解什么是值传递和引用传递与真实场景使用,是否熟悉什么是值传递和引用传递在工作中的表现是什么?

考察的知识点

什么是值传递和引用传递的概念,两者对开发中编写的代码的影响

考生应该如何回答

值传递:在方法调用时,传递的参数是这个参数指向值的拷贝;

引用传递:在方法调用时,传递引用的地址

在Java中对于参数的传递可以分为两种情况:

1.基本数据类型的参数

 1 public class TransferTest {
    
    
 2     public static void main(String[] args) {
    
    
 3         int num = 1;
 4         System.out.println("changeNum()方法调用之前:num = " + num);
 5         changeNum(num);
 6         System.out.println("changeNum()方法调用之后:num = " + num);
 7     }
 8 
 9     public static void changeNum(int x) {
    
    
10         x = 2;
11     }
12 }

运行结果:

img

传递过程的示意图如下:

img

分析:num作为参数传递给changeNum()方法时,是将内存空间中num所指向的那个存储单元中存放的值1复制了一份传递给了changeNum()方法中的x变量,而这个x变量也在内存空间中分配的一个存储单元。这时就把num对的值1传递给了x变量所指向的存储单元中。此后在changeNum()方法中对x变量的一切操作都是针对于x所指向的这个存储单元,与num所指向的存储单元无关。

所以,在changeNum()方法被调用后,num所指向的存储单元的值还是没有发生变化,这就是所谓的“值传递”。

值传递的精髓是:传递的是存储单元中的内容,而不是存储单元的引用。

2.引用类型的参数

 1  public class TransferTest2 {
    
    
 2     public static void main(String[] args) {
    
    
 3         Person person = new Person();
 4         System.out.println(person);
 5         change(person);
 6         System.out.println(person);
 7     }
 8 
 9     public static void change(Person p) {
    
    
10         p = new Person();
11     }
12 }
13 
14 /**
15  * Person类
16  */
17 class Person {
    
    
18 
19 }

运行结果:

img
可以看出两次打印结果一致。即调用change()方法后,person变量并没发生改变。

传递过程的示意图如下:

img
分析:

01.当程序执行到第3行 Person person = new Person()时,程序在堆内存(heap)中开辟了一块内存空间用来存储Person类实例对象,同时在栈内存(stack)中开辟了一个存储单元来存储该实例对象的引用,即上图中person指向的存储单元。

02.当程序执行到第5行 change(person)时,person作为参数(实参)传递给了change()方法。这里是person将自己的存储单元的内容传递给了change()方法的p变量。 此后在change()方法中对p变量的一切操作都是针对于p变量所指向的存储单元,与perosn所指向的存储单元就没有关系了。

因此Java的参数传递,不管是基本数据类型还是引用类型的参数,都是按值传递!

5 使用equals和==进行比较的区别

这道题想考察什么?

在开发中当需要对引用类型和基本数据类型比较时应该怎么做,为什么有区别。

考察的知识点

equals 的实现以及栈和堆的内存管理。

考生应该如何回答

==

对于不同的数据类型,使用==进行比较的意义不同:

  • 基本数据类型(也称原始数据类型) :byte,short,char,int,long,float,double,boolean。用==,比较的是他们的值;
  • 引用数据类型:用(==)进行比较的时候,比较的是地址。

对于引用类型,除非是同一个new出来的对象,他们的比较的结果为true,否则为false。因为每new一次,都会重新开辟堆内存空间,哪怕他们的值一致,但是也是在不同的地址存放。所以对于引用类型的值比较应该使用equals方法。

equals()方法介绍

equals 方法是 Object 中定义的一个方法,源码如下:

public boolean equals(Object obj) {
    return (this == obj);
}

可以看到实际上就是用的 == 实现的,所以这个原始方法意义不大,一般在类中做比较的时候,都会重写这个方法,如String、Integer、Date等。

//Integer
public boolean equals(Object obj) {
    
    
	if (obj instanceof Integer) {
    
    
        //比较Integer中包装的int值
		return value == ((Integer)obj).intValue();
	}
	return false;
}
//String
public boolean equals(Object anObject) {
    
    
	if (this == anObject) {
    
     //同一个对象
		return true;
	}
	if (anObject instanceof String) {
    
    
		String anotherString = (String)anObject;
		int n = length();
		if (n == anotherString.length()) {
    
    
			int i = 0;
			while (n-- != 0) {
    
    
				if (charAt(i) != anotherString.charAt(i))  //逐个字符比较
					return false;
				i++;
			}
			return true;
		}
	}
	return false;
}

String的比较

public class StringDemo {
    
    
    public static void main(String args[]) {
    
    
        String str1 = "Hello";
        String str2 = new String("Hello");
        String str3 = str2; // 引用传递
        System.out.println(str1 == str2); // false
        System.out.println(str1 == str3); // false
        System.out.println(str2 == str3); // true
        System.out.println(str1.equals(str2)); // true
        System.out.println(str1.equals(str3)); // true
        System.out.println(str2.equals(str3)); // true
    }
}

栈和堆的内存分析图:

在这里插入图片描述

由此可见,equals 是比较字符串的内容是否一样,== 是比较字符串的堆内存地址是否一样。

结论

equals和==的区别,需要分情况讨论:

  1. 没有重写 equals ,则 equals 和 == 是一样的。
  2. 如果重写了 equals,则需看 equals 的方法实现。以 String 类为例:
    1. equals 是比较字符串的内容是否一样;
    2. == 是比较字符串的堆内存地址是否一样,或者说引用的值是否相等。

最后

此面试题会持续更新,请大家持续关注!!!

扫描下方二维码即可领取面试题

ps:群内设有ChatGPT机器人,可以解答各位在技术上遇到的难题哦!

猜你喜欢

转载自blog.csdn.net/datian1234/article/details/130976198
今日推荐