给小白讲java中两大怪物,附带面试题

Java
268
0
0
2023-06-04
标签   Java面试

最近老是有小伙伴问类和Object相关的问题,感觉还是很多人对此不是很明白,那我们今天就干掉这两个怪物。

类介绍

java 程序是由若干个类组成的,类也是面向对象编程思想的具体实现。

以下为类的定义:

 public class User {
    //私有属性
    private Long userId;
    private String name;
    private Integer age;
    //  构造方法 
    public User() {
    }
    //有残构造方法
    public User(Long userId,  String  name, Integer age) {
        this.userId = userId;
        this.name = name;
        this.age = age;
    }

    //普通方法
    public  void  say() {
        System.out.println("hello world");
    }
    // 对外包装属性
    public String getName() {
        return this.name;
    }
} 

关键字import的三种用法

单类型导入

当我们需要使用不同包下的类时,就需要使用 import 导入包或类,这个时候才能正常使用。例如,我们要使用 Java .util下的 ArrayList 就必须使用 import java.util.ArrayList,代码如下:

 // 导入 ArrayList 类
import java.util.ArrayList;
 Class  Test {
    public  static  void main(String[] args) {
        ArrayList list = new ArrayList();
    }
} 

按需类型导入

如果我们在同一个类中使用了同一个包下面的较多类时候,就会使用按需类型导入。

 // 用到了java.util包目录下的List、ArrayList和LinkedList类
//import java.util.ArrayList;
//import java.util.LinkedList;
//import java.util.List;
//如果不想类上有太多import,就可以直接import 包名.*
import java.util.*;
public class Test {
    public static void main(String[] args) {
        List list = new ArrayList<>();
        List list1 = new LinkedList();
    }
} 

这个只是表象,其实也是一个一个的导入的,只是在代码层面看起来是一次性全部倒入了。

静态导入

import 还可以导入 静态方法 和静态域的功能,比如以下代码:

 //**精准导入**
//直接导入具体的静态变量、常量、方法方法,注意导入方法直接写方法名不需要括号。
import static com.assignment.test. Static FieldsClass.staticField;
import static com.assignment.test.StaticFieldsClass.static Function ;

//或者使用如下形式:
//**按需导入**不必逐一指出静态成员名称的导入方式
//import static com.assignment.test.StaticFieldsClass.*;

public class StaticTest {
    public static void main(String[] args) {
        //这里直接写静态成员而不需要通过类名调用
        System.out.println(staticField);
        staticFunction();
    }
} 

以上代码也可以顺利的执行,这也是 import 好玩的一个地方。

构造方法

构造方法也叫构造器或构造函数,它的作用是类在进行初始化的时候会调用对应的构造方法,比如以下代码:

 public class User {
    //私有属性
    private Long userId;
    private String name;
    private Integer age;
    // 构造方法
    public User() {
    }
    //有参构造方法
    public User(Long userId, String name, Integer age) {
        this.userId = userId;
        this.name = name;
        this.age = age;
    }

    //普通方法
    public void say() {
        System.out.println("hello world");
    }
    public static void think() {
        System.out.println("thinking");
    }
    // 对外包装属性
    public String getName() {
        return this.name;
    } 

构造方法五大原则

  1. 构造方法必须与类同名;
  2. 构造方法的参数可以没有或者有多个;
  3. 构造方法不能定义返回值(默认返回类型就是本类类型);
  4. 每个类可以有一个或多个构造方法;
  5. 构造方法总是伴随着 new 操作一起使用。

注意:如果勒种没有显示的定义构造方法,那么在编译的时候回默认为其添加一个无惨构造方法。构造方法实际开发中通常都是public修饰,还有就是我们想要单例的情况下搞成private修饰。

Object

Object 类是 Java 中的一个特殊类,它是所有类的父类,Java 中的类都直接或间接的继承自 Object 类。

Object 类的常用方法如下:

  • equals():对比两个对象是否相同
  • getClass():返回一个对象的运行时类
  • hashCode ():返回该对象的哈希码值
  • toString ():返回该对象的 字符串 描述
  • wait():使当前的线程等待
  • notify():唤醒在此对象监视器上等待的单个 线程
  • notifyAll():唤醒在此对象监视器上等待的所有线程
  • clone():克隆一个新对象

关于更多 Object 的内容,如克隆(深克隆、浅克隆)、线程的几个常用方法wait、notify、notifyAll,对象比较,对象的hashCode值等。

继承

Java 中只支持单继承:即一个子类只能继承一个父类,而一个父类可以被多个子类继承。

每个人都只能有一个亲生父亲,一个父亲是可以有多个儿子的。

用法:使用 extends 关键字来实现类的继承,示例代码如下:

 class Person {
    public void say() {
        System.out.println("hello");
    }
}
public class User extends Person {
    public static void main(String[] args) {
        Person user = new User();
        user.say();
    }
} 

以上程序执行结果:hello

继承的注意点

  1. 单一继承性。(在Java中是不支持多继承的,通俗的说子类只能有一个父类,而父类可以有很多子类。)
  2. 支持多层继承。(继承可以一直传下去,子类有父类,父类又有父类…)
  3. 如果父类成员使用private修饰,那么子类不能被继承。(private只是对本类有效)
  4. 如果一个子类继承了父类的属性和方法还可以有自己特有的属性和方法。(不光有父类的属性(可继承的)和方法(可继承的),也有自己独有的属性和方法。)
  5. 当子类和父类的成员变量重名的时候,子类优先。( 就近原则 )

继承使用技巧

  • 将公共的变量或者方法提取到超类中;
  • 除非所有的方法都有继承的意义,否则不要使用继承;
  • 在 方法覆盖 时不要改变原有方法的预期行为。
  • 一般在写代码的时候发现代码中存在重复代码,需要向上抽取,考虑继承。
  • 当某个类的设计非常复杂的时候可以考虑继承

继承的优点

  • 代码的可重用性。
  • 使用继承可以轻松的定义子类。
  • 父类的属性和方法可以用于子类中(非private修饰)。
  • 设计应用程序变得更加简单。

设计模式中大量使用

比如:模板方法模式,就是采用继承,子类自己去实现自己的业务逻辑。

面试题

类与对象有哪些区别?

类是一个抽象的概念,是对某一事物的描述;而对象是类的实例,是实实在在存在的个体。

比如:“男人”就是一个类(一个概念),而老田(田维常)就是实实在在的一个“对象”。

注意:对象中又有类对象,即Class对象,但是类对象始终还是对象,不是类,这两个概念别搞混淆了。

Java 中可以多继承吗?

Java 中只能单继承,但可以实现多接口,并且支持多层继承。

Java 中为什么不能实现多继承?

答:从技术的实现角度来说,是为了降低编程的复杂性。假设 A 类中有一个 m() 方法,B 类中也有一个 m() 方法,如果 C 类同时继承 A 类和 B 类,那调用 C 类的 m() 方法时就会产生歧义,这无疑增加了 程序开发 的复杂性,为了避免这种问题的产生,Java 语言规定不能多继承类,但可以实现多接口。

覆盖和重载有哪些区别?

  • 重写( Override ) 从字面上看,重写就是 重新写一遍的意思。其实就是在子类中把父类本身有的方法重新写一遍。子类继承了父类原有的方法,但有时子类并不想原封不动的继承父类中的某个方法,所以在方法名,参数列表,返回类型(除过子类中方法的返回值是父类中方法返回值的子类时)都相同的情况下, 对方法体进行修改或重写,这就是重写。但要注意子类函数的访问修饰权限不能少于父类的。
 public class Father {

    public static void main(String[] args) {
        // TODO Auto-generated method stub
        Son s = new Son();
        s.sayHello();
    }

    public void sayHello() {
        System.out.println("Hello");
    }
}

class Son extends Father{

    @Override
    public void sayHello() {
        // TODO Auto-generated method stub
        System.out.println("hello by ");
    }

} 

重写 总结

1.发生在父类与子类之间

2.方法名,参数列表,返回类型(除过子类中方法的返回类型是父类中返回类型的子类)必须相同

3.访问修饰符的限制一定要大于被重写方法的访问修饰符(public>protected>default>private)

4.重写方法一定不能抛出新的检查异常或者比被重写方法的更加宽泛的检查型异常

  • 重载(Overload) 在一个类中,同名的方法如果有不同的参数列表(参数类型不同、参数个数不同甚至是参数顺序不同)则视为重载。同时,重载对返回类型没有要求,可以相同也可以不同,但不能通过返回类型是否相同来判断重载。
 public class Father {

    public static void main(String[] args) {
        // TODO Auto-generated method stub
        Father s = new Father();
        s.sayHello();
        s.sayHello("wintershii");

    }

    public void sayHello() {
        System.out.println("Hello");
    }

    public void sayHello(String name) {
        System.out.println("Hello" + " " + name);
    }
} 

重载 总结 :1. 重载 Overload是一个类中多态性的一种表现 2.重载要求同名方法的参数列表不同(参数类型,参数个数甚至是参数顺序) 3.重载的时候,返回值类型可以相同也可以不相同。无法以返回型别作为 重载函数 的区分标准。

为什么方法不能根据返回类型来区分重载?

答:因为在方法调用时,如果不指定类型信息,编译器就不知道你要调用哪个方法了。比如,以下代码:

 float max(int x,int y);
int max(int x,int y);
// 方法调用
max(1,2); 

因为 max(1,2) 没有指定返回值,编译器就不知道要调用哪个方法了。

说说构造方法的特点有哪些?

答:构造方法的特征如下:

  • 构造方法必须与类名相同;
  • 构造方法没有返回类型(默认返回本类类型);
  • 构造方法不能被继承、覆盖、直接调用;
  • 类定义时提供了默认的无参构造方法;
  • 构造方法可以私有,外部无法使用私有构造方法创建对象。

构造函数能不能被覆盖?能不能被重载?

构造函数可以重载,但不能覆盖。

以下程序执行的结果是?

 class ExecTest {
    public static void main(String[] args) {
        Son son = new Son();
    }
}
class Parent{
    {
        System.out.print("1");
    }
    static{
        System.out.print("2");
    }
    public Parent(){
        System.out.print("3");
    }
}
class Son extends Parent{
    {
        System.out.print("4");
    }
    static{
        System.out.print("5");
    }
    public Son(){
        System.out.print("6");
    }
} 

结果是:251346

类加载顺序

整体

细分

以下程序执行的结果是?

 class A {
    public int x = 0;
    public static int y = 0;
    public void m() {
        System.out.print("A");
    }
}
class B extends A {
    public int x = 1;
    public static int y = 2;
    public void m() {
        System.out.print("B");
    }
    public static void main(String[] args) {
        A myClass = new B();
        System.out.print(myClass.x);
        System.out.print(myClass.y);
        myClass.m();
    }
} 

结果是:00B

注意 :在 Java 语言中,变量不能被重写。

Java 中的 this 和 super 有哪些区别?

this 和 super 都是 Java 中的关键字,起指代作用,在构造方法中必须出现在第一行,它们的区别如下。

  • 基础概念:this 是访问本类实例属性或方法;super 是子类访问父类中的属性或方法。
  • 查找范围:this 先查本类,没有的话再查父类;super 直接访问父类。
  • 使用:this 单独使用时,表示当前对象;super 在子类覆盖父类方法时,访问父类同名方法。

在静态方法中可以使用 this 或 super 吗?为什么?

在静态方法中不能使用 this 或 super,因为 this 和 super 指代的都是需要被创建出来的对象,而静态方法在类加载的时候就已经创建了,所以没办法在静态方法中使用 this 或 super。

静态方法的使用需要注意哪些问题?

静态方法的使用需要注意以下两个问题:

  • 静态方法中不能使用实例成员变量和实例方法;
  • 静态方法中不能使用 this 和 super。

final 修饰符的作用有哪些?

final也是很多面试喜欢问的地方,但我觉得这个问题很无聊,通常能回答下以下5点就不错了:

  • 被final修饰的类不可以被继承
  • 被final修饰的方法不可以被重写
  • 被final修饰的变量不可以被改变.如果修饰引用,那么表示引用不可变,引用指向的内容可变.
  • 被final修饰的方法, JVM 会尝试将其内联,以提高运行效率
  • 被final修饰的常量,在编译阶段会存入常量池中.

除此之外,编译器对final域要遵守的两个重排序规则更好:

在构造函数内对一个final域的写入,与随后把这个被构造对象的引用赋值给一个引用变量,这两个操作之间不能重排序 初次读一个包含final域的对象的引用,与随后初次读这个final域,这两个操作之间不能重排序.

经典使用场景: Integer ,String等类中有使用到。

覆盖 equals() 方法的时候需要遵守哪些规则?

Oracle 官方的文档对于 equals() 重写制定的规则如下。

  • 自反性:对于任意非空的引用值 x,x.equals(x) 返回值为真。
  • 对称性:对于任意非空的引用值 x 和 y,x.equals(y) 必须和 y.equals(x) 返回相同的结果。
  • 传递性:对于任意的非空引用值 x、y 和 z,如果 x.equals(y) 返回值为真,y.equals(z) 返回值也为真,那么 x.equals(z) 也必须返回值为真。
  • 一致性:对于任意非空的引用值 x 和 y,无论调用 x.equals(y) 多少次,都要返回相同的结果。在比较的过程中,对象中的数据不能被修改。
  • 对于任意的非空引用值 x,x.equals(null) 必须返回假。

此题目不要求记忆,能知道大概即可,属于加分项题目。

在 Object 中 notify() 和 notifyAll() 方法有什么区别?

notify() 方法随机唤醒一个等待的线程,而 notifyAll() 方法将唤醒所有在等待的线程。

如何使用 clone() 方法?

如果是同一个类中使用的话,只需要实现 Cloneable 接口,定义或者处理 CloneNotSupported Exception 异常即可,请参考以下代码:

 public class CloneTest implements Cloneable {
    int num;
    public static void main(String[] args) throws CloneNotSupportedException {
        CloneTest ct = new CloneTest();
        ct.num = 666;
        System.out.println(ct.num);
        CloneTest ct2 = (CloneTest) ct.clone();
        System.out.println(ct2.num);
    }
} 

如果非内部类调用 clone() 的话,需要重写 clone() 方法,请参考以下代码:

 class CloneTest implements Cloneable {
    int num;
    public static void main(String[] args) throws CloneNotSupportedException {
        CloneTest ct = new CloneTest();
        ct.num = 666;
        System.out.println(ct.num);
        CloneTest ct2 = (CloneTest) ct.clone();
        System.out.println(ct2.num);
    }
    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
}
public class CloneTest2 {
    public static void main(String[] args) throws CloneNotSupportedException {
        CloneTest ct = new CloneTest();
        ct.num = 666;
        System.out.println(ct.num);
        CloneTest ct2 = (CloneTest) ct.clone();
        System.out.println(ct2.num);
    }
} 

对象克隆是 原型模式 的经典实现。

java中对象的创建方式有哪几种?

java中提供了以下四种创建对象的方式:

  • new创建新对象
  • 通过 反射机制
  • 采用clone机制
  • 通过序列化机制

总结

本文讲述的是一些基本的java入门知识,也顺带着讲了些稍微有点挑战的,对于还是小白的同学,有些东西不懂没事,先混个眼熟,多见几次面后,就会慢慢熟悉了,如果有机会在深入的领悟一番,那不懂的也就懂了。

每天保持学习的心态,一个月后,一年后,如果你觉得再回看之前的你,如果觉得自己当初很傻逼,那证明你进不了,如果和之前没什么两样,那证明你还是没有进步,需要反思了。