Java面试复习--基础知识篇

Java基础知识

本文学习自GitHub上的JavaGuide项目,感谢大佬的资源,此处为自我学习与整理,原项目链接 JavaGuide

面向对象和面向过程的区别

  1. 面向过程: 比面向对象性能更高,类调用的时候需要实例化,开销大,消耗资源多,所以用于追求性能的情况。比如单片机,嵌入式开发,Linux/Unix。
  2. 面向对象: 易于维护,拓展,复用。拥有封装,继承,多态的特性,可以设计出低耦合的系统。但是性能比面向过程低。
  3. 性能优劣的原因: 面向过程语言虽然也要分配内存,计算内存偏移量,但是大多直接编译为机械码执行,而Java性能差的原因不是因为他是面向对象语言,而是因为Java是半编译语言,最终执行代码不能直接在CPU运行。

JVM,JDK,JRE

Java文件运行的实际过程

  1. Java虚拟机(JVM)是运行Java字节码的虚拟机,JVM对于不同操作系统有着不同的实现,目的是让他们用相同的字节码产生相同的结果。
  2. JDK(Java Developement Kit)是功能齐全的Java SDK。有JRE拥有的一切以及编译器(javac)和工具(javadoc,jdb)。它能够创建和编译程序。
  3. JRE是Java运行时环境。包括虚拟机,Java类库,Java命令和其他一些基础构件,但是不能用于开发。
  4. 如果只是需要运行可以只安装JRE,需要开发的话大部分情况是需要安装JDK的。

Java和C++的相同?区别?

  1. Java不提供指针去访问内存,内存更加安全
  2. 两者都是面向对象语言,都支持封装,继承,多态
  3. C++支持多重继承,Java不支持,但是Java有接口Interface,接口可以多重继承
  4. Java的垃圾回收机制使得程序员不用手动释放内存
  5. 待续。

Java主类

一个程序中只有一个主类,这个类是包含main()方法的类。主类是Java执行的入口点。

Java基本类型占用内存以及相关参数

在这里插入图片描述

构造器Constructor: 只能重载(overload)不能重写(Override)

一个类可以有多个构造器,不同的构造方式。

重载&重写

  1. 重载(overload)发生在同一个类中,方法名相同,参数类型,个数,顺序进行修改,返回类型也可以改变。但是不能只有返回类型不同,会报错。
  2. 重写(override)是子类继承父类时对方法的修改,名称参数必须一致,返回类型小于等于父类,访问权限大于等于父类,如果父类为private则不能重写。

Java面向对象编程三大特性:封装,继承,多态

  1. 封装:把对象属性私有化,访问方法公有化。
  2. 继承:子类具有父类的所有属性和方法,但是私有方法和属性无法访问,只是具有。子类具有扩展性,子类也可以重写父类的方法。
  3. 多态:多态是指定义变量时所指向的具体类型和通过引用变量发出的方法调用在编程时并不确定,而是在程序运行时才确定。Java中实现多态的方法有两种,继承(多个子类重写同一个方法)和接口(实现接口并覆盖同一个方法)。
    Example:
    List<String> s = new ArrayList();

String,StringBuffer和StringBuilder

  1. 可变性:String类中使用final关键字来修饰字符数组来保存字符串,private final char[] value,所以String是不可变的。Java9以后使用byte[]来保存。
  2. StringBuilder和StringBuffer都继承自AbstractStringBuilder类,也是用char[]来保存value,但是没有用final关键字修饰,所以他们是可变的。
abstract class AbstractStringBuilder implements Appendable, CharSequence {
    /**
     * The value is used for character storage.
*/
    char[] value;
    /**
     * The count is the number of characters used.
     */
int count;
    AbstractStringBuilder(int capacity) {
        value = new char[capacity];
}
  1. 线程安全性:String是不可变的,所以视作常量,线程安全。StringBuffer对调用的方法增加了同步锁,线程安全,StringBuilder不是线程安全的。
  2. 性能:String每次修改时会生成一个新的String对象然后将指针指向新的String,而StringBuffer则对自身进行修改。相同情况下StringBuilder相对于StringBuffer提升10%-15%的性能,但是线程不安全。
  3. 总结:少量数据操作用String,单线程用StringBuilder,多线程用StringBuffer。

Java的自动装箱和拆箱

  1. 装箱:将基本引用类型用他们对应的引用类型包装起来
  2. 拆箱:将包装类型转化为基本数据类型
    这里有很多内容值得研究,比如int‘和Integer的装箱拆箱问题,后面在进行补充。

在一个静态方法中调用非静态成员为什么非法?

静态方法储存在静态方法区,无需创建对象即可调用。所以没有对象的情况下是无法访问非静态成员的。

为什么要定义一个没有参数也什么都不做的构造函数?

Java运行时子类如果没有使用super()关键字来调用父类的特定的构造函数,则会默认调用父类的没有参数的构造函数,如果此时父类中只有含有参数的重载构造函数而且子类中没有用super()来调用的话,则会报错。

接口和抽象类的区别?

  1. 接口的方法默认是public,并且不能有实现(Java8以后接口方法可以有默认实现),抽象类中可以有非抽象方法
  2. 接口中除了static和final变量,不能有其他变量,而抽象类不一定
  3. 一个类可以实现多个接口,但是只能实现一个抽象类,接口可以通过extends关键字扩展别的接口。
  4. 接口的方法就是为了被覆盖,所以不能使用private关键字
  5. 从设计层面出发,抽象是对类的抽象,而接口是对行为的规范
  6. jdk8中接口可以开始有实现的默认方法和静态方法功能
  7. jdk9中接口可以引入私有方法和私有静态方法

成员变量和局部变量的区别?

  1. 语法层面:成员变量属于类或实例,而局部变量属于方法,成员变量可以被public,static等修饰,而局部变量不能被访问权限和static修饰。两者都可以被final修饰。
  2. 内存层面:如果成员变量被static修饰,那么它属于类,如果没有,它就属于实例。而对象存储在Java堆中,局部变量随方法存在于栈中。
  3. 生存周期:成员变量随类或者对象创建和消亡,而局部变量随这方法调用而存在消亡。
  4. 初始赋值:成员变量如果没有赋值,则会被赋予该类型的默认值,除非被final修饰,必须显性赋值,而局部变量不会自动赋值。

创建一个对象

创建对象使用new关键字,例如
Cat c = new Cat();
c是引用变量,new后面的是创建的对象,返回了一个地址,并赋给了c。
一个对象可以有多个引用指向,而一个引用只能存储一个对象的地址,或者指向空,那么它可能会被GC。对象实例存放在堆中,而对象的引用存放在栈中。

构造方法

没有返回类型,和类名相同,作用是初始化。如果不写的话有默认的无参数构造函数,如果写了有参数的构造方法,就要补充无参数的构造函数,否则会给子类带来麻烦。

静态方法和实例方法

静态方法可以直接类名.方法,也可以对象.方法,而实例方法必须是后者,因为静态方法存储在静态方法区,可以不创建对象直接调用。所以静态方法在访问本类时只能访问静态成员。

对象的相等和引用的相等

对象的相等比较的是内存中存放的内容是否相等,而引用相等比较的是指向的内存地址是否相同。

==与equals() 【重要】

  1. ==如果比较基本类型,比较的是数值,而如果比较的是引用,则比较其引用地址是否相同,即判断两个引用是否引用同一个对象。
  2. equals()用来判断两个对象是否相等。值得注意的是,没有重写过的equals()方法比较对象时等价于==,比较的是引用的地址。所以equals()方法常常需要重写,例如String.equals()方法就是重写过的,它会比较两个String对象是否相同。
    代码示例:
public class test {
    public static void main(String[] args) {
        String a = "asd";
        String b = "asd";
        String c = new String("asd");
        String d = new String("asd");
        if(a==b){
            System.out.println("a==b");
        }else System.out.println("a!==b");
        //a==b
        
        if(a.equals(b)){
            System.out.println("a equals b");
        }else System.out.println("a doesn't equals b");
        //a equals b
        
        if(a.equals(c)){
            System.out.println("a equals c");
        }else System.out.println("a doesn't equals c");
        //a equals c
        
        if(c==d){
            System.out.println("c==d");
        }else System.out.println("c!==d");
        //c!==d
        
        if(c.equals(d)){
            System.out.println("c equals d");
        }else System.out.println("c doesn't equals d");
        //c equals d
    }
}

hashCode()

hashCode()方法返回一个int,作用是返回一个哈希码(散列码),确定对象在哈希表中的索引位置。hashCode()定义在Object中,意味着所有的类都有hashCode()方法。

HashSet中的hashCode()

在HashSet中是没有重复的元素的。当我们想要插入一个新的元素,HashSet先会调用hashCode()方法,查询当前对象的哈希码,如果当前哈希码对应的索引处没有对象,说明没有重复值出现,可以直接插入。如果当前位置已经有了元素,则会调用equals()方法来判断是否是相同对象,如果相同则不插入,不同的话会散列到其他的位置。通过调用hashCode()方法减少了equals()方法的使用次数,节约开销。

hashCode()和equals()

  1. 如果两个对象相等,那么他们的hashcode也相同
  2. 如果两个对象相等,那么equals()方法返回true
  3. 两个对象的hashcode相同,并不代表对象相等
  4. 因此如果equals()被覆盖,hashCode()也必须被覆盖
  5. hashCode()默认对堆上的对象产生哈希值,所以对于一个类的两个对象,如果没有重写hashCode(),无论如何也不会相等,即使指向同一个数据。

程序,进程,线程

  1. 程序是含有指令和数据的文件。被储存在硬盘中,也就是静态的代码。
  2. 进程是程序一次执行的过程,是系统运行程序的基本单位,系统运行一个程序就是这个程序从创建,运行到消亡的过程。
  3. 线程属于进程,一个进程可以有一个到多个线程,同一进程下的线程共享内存和系统资源。
  4. 进程之间是独立的,而线程不一定。

线程的基本状态

两张图表示进程状态以及状态转换过程:
图源《Java并发编程艺术》
图源《Java并发编程艺术》
在这里插入图片描述
线程被创建时处于NEW状态,调用start()后开始运行,处于READY状态,当CPU时间片轮转获得CPU后处于RUNNING状态,(对于虚拟机来说,READY和RUNNING状态都是RUNNABLE),执行wait()方法后处于进入WAITING状态,或者执行sleep(long)、wait(long)可以进入TIME_WAITING状态,经过一定时间线程返回到RUNNABLE状态。在syncronized中如果等待锁则会进入BLOCKED状态(阻塞)。线程执行完run()方法则进入TERMINATED状态。

final 关键字

  1. final会出现的地方:类,方法,变量
  2. 如果final修饰基本数据类型的变量,一旦初始化,数值不可以在改变。如果修饰的是引用,一旦初始化,不可以再去引用其他的对象。
  3. 如果final修饰类,那么这个类不能被继承,其中所有成员方法都为final方法。
  4. final的作用有二:一是防止继承的类修改方法的含义,二是早期的Java使用final可以提升效率,会将final转为内嵌调用,现在已经不需要用final来提升效率了。

获取键盘输入两种常见方法

  1. Scanner
Scanner sc = new Scanner(System.in);
String s = sc.nextLine();
sc.close();
  1. BufferedReader
BufferedReader br = new BufferedReader(new InputStreamReader(System,in));
String s = br.readLine();
发布了3 篇原创文章 · 获赞 0 · 访问量 377

猜你喜欢

转载自blog.csdn.net/weixin_40407203/article/details/105131697