Java基础问题

基础问题

  • 谈谈你对面向对象的理解 — 结合场景

为何要使用对象编程?

可重复利用,方便拓展

面向对象有三大特征:封装、继承和多态

  1. 封装:为什么要封装?可以使类的 成员(数据和行为)有选择性的暴露,这里面就有了四个不同的修饰符 — private protected default public
  2. 继承: 提高代码的可重用性,往往都是子 继承父 中的成员;
  3. 多态: 主要目的是实现 接口。通过 父类的引用指向子类的实例,来动态完成各个不同子类方法的调用。这里面就要涉及到重写和重载问题

  4. 重写 和 重载 的区别

作用范围不同:重载发生在同一个类中,重写发生在父子类中

对于一个函数来说:

重载方法名必须相同,参数类型不同、个数不同、顺序不同;

返回值,可以不同;访问修饰符,可以不同

重写 方法名、参数列表必须相同 ;

返回值,范围要小于父类;访问修饰符,范围要大于父类,其中父类修饰符为private,子类就不能进行重写

  • 泛型 generic

作用:通过在编译的时候能够检查类型安全(严格意义上是一种伪泛型)

什么时候用:

1、类、接口 — 刷题倒是常用

2、方法 — 来限定传入参数

static int countLegs (List animals ) {
    int retVal = 0;
    for ( Animal animal : animals )
    {
        retVal += animal.countLegs();
    }
    return retVal;
}

常用的通配符:

? 表示不确定的 java 类型
T (type) 表示具体的一个 java 类型
K V (key value) 分别代表 java 键值中的 Key Value
E (element) 代表 Element

  • == 和 equals 的区别

这里就要提到Java中存在两种数据类型,基本数据类型 + 引用类型

== 在基本类型中,就是简单的进行数值比较,equals方法在基本类型中不存在

但是针对引用类型时,== 比较的 对象的地址值,Object 的 equals 方法就是比较对象的内存地址;因为所有的类都默认继承Object类,所以理论上这些类的equals方法都是进行地址值比较;

但一般常用的类,比如String,都将equals方法进行重写,主要比较的还是对象的内容

基本数据类型有几种? 6 + 2 数值型 + 字符 + 布尔(byte short int long float double character boolean)

  • 为什么重写 equals 时必须重写 hashCode 方法?

hashCode()定义在 JDK 的 Object 类中,这就意味着 Java 中的任何类都包含有 hashCode() 函数。

那么hashCode() 的作用是啥?主要还是应用在HashMap这种需要涉及到散列表的类中,通过它获取对象的散列码,我们判断两个对象是否相等,需要:

通过hashCode() 获得两个对象的散列码,在两者相同的基础上,再进行equals判断内容是否相等。

  • Java中存在一个空参构造器干嘛?

构造器的作用:初始化对象。

在Java框架中,往往通过反射来创建对象,之后动态的向对象赋值

Student s = (Student)Class.forName("Student类全限定名").newInstance(); 

另外,在父子类中,实例化子类对象往往是一个递归过程,也就是说会先实例化父类对象,一直向上,最终是先实例化Object类,这就要求父类要存在一个无参构造器,都为有参构造器的话,子类无法利用super()来实例化父类,另外父类也参与了子类变量的初始化工作。

  • 抽象类和接口的区别

变量 抽象类可以是各种类型的,接口只能是 public static final

方法 抽象类可以实现,接口中只能存在 public abstract 方法

静态代码块和静态方法,抽象类可以有,但是接口不可以包含

继承:一个类只能继承一个抽象类,却能实现多个接口

String问题

  • 内存问题
String st1 = new String("abc");

Q:这句话在内存中创建了几个对象

A:在内存中创建两个对象,一个在堆内存,一个在常量池,堆内存对象是常量池对象的一个拷贝副本。new这个关键字,就要想到new出来的对象都是存储在堆内存,”abc”属于字符串,字符串属于常量,所以应该在常量池中创建。

String st1 = "abc";
String st2 = "abc";
System.out.println(st1 == st2);   //true
System.out.println(st1.equals(st2));  //true

String st1 = new String("abc");
String st2 = "abc";
System.out.println(st1 == st2);    //false  (一个在堆,一个在常量池)
System.out.println(st1.equals(st2)); //true
  • String与 StringBuilder、StringBuffer区别

String因为被声明为 final class,是不可变字符串。因为它的不可变性,所以拼接字符串时候会产生很多无用的中间对象;
StringBuffer 就是为了解决这个问题而提供的一个类。它设计之初是一个线程安全的可修改的字符序列,但在很多情况下我们的字符串拼接操作不需要线程安全,所以就有了StringBuilder 。StringBuilder 是 JDK1.5 发布的,它和 StringBuffer 本质上没什么区别,就是去掉保证线程安全的那部分,减少开销,提升性能。

Object类

1、clone()   -- 复制一个对象并返回,是一种浅拷贝(单纯复制对象的引用)
2、equals()、toString()  -- 重点  几乎每次都要重写
3、finalize()  -- 对象被回收之前调用,交给Garbage Collector处理,自己不要用
4、getClass()  -- 对象获取自己的类

5、hashCode()  -- 集合章节
6、notify()、notifyAll()、wait()  --多线程章节

深浅拷贝 — Object拷贝方法是一种浅拷贝

  • 值传递和引用传递有什么区别?

Java中参数传递是值传递
基本类型作为参数被传递时肯定是值传递;
引用类型作为参数被传递时也是值传递,只不过”值”为对应的引用。(对象本身发生变化,但是指向没有变)

  • toString()
大部分的时候都需要进行重写
默认的形式为:
public String toString(){
    return getClass().getName() + "@" + Integer.toHexString(hashCode());
}
//可以看出此处用到两个 Object的方法

static 与 final

static可以用来修饰类的方法、变量,修饰类,只能是内部类
被static关键字修饰的方法或者变量不需要依赖于对象来进行访问,只要类被加载了,就可以通过类名去进行访问。可以在没有创建对象的情况下来进行调用(方法/变量)

主要看 修饰方法 、 变量

1、修饰 方法,最典型的不就是main函数吗?

因为程序在执行main方法的时候没有创建任何对象,因此只有通过类名来访问。

 public static void main(String[] args) {}

2、修饰变量

静态变量被所有的对象所共享,在内存中只有一个副本,它 当且仅当在类初次加载时会被初始化。

class MyClass {
    public final double i = Math.random();
    public static double j = Math.random();
}
//一旦类被加载后,j 的值就是一个确定的值,之后去访问它是不会变化其值的
//但是i 不同,创建不同实例,其值就随机变化一次

final关键字可以用来修饰类、方法和变量

当用final修饰 时,表明这个类不能被继承,典型的就是String

当final修饰 方法时,被修饰的方法无法被所在类的子类重写(覆写),典型的就是Object类中的getClass()方法

对于一个final 变量,如果是 基本数据类型的变量,则其数值一旦在初始化之后便不能更改;如果是 引用类型的变量,则在对其初始化之后便不能再让其指向另一个对象(但是对象里面的值是可以改变的,比如final修饰数组)

这里就有了与static的区别,static final 经常组合出现,作为 类常量使用

static 和 final的区别

  • static修饰变量,表示在一个类中只保存一份副本( 指向唯一),没说它值不可变;

所有由一个类创建的实例,它们的static变量都指向相同,就像createCarNum,不同实例都可以修改它,但内存中只有一个,存在【方法区】

  • final修饰变量,保证 在一个实例中变量值不可变,不同实例就不一定

不同实例,有不同的final变量,一旦确定就不能被修改。
可能会有疑惑,既然final变量要强制赋初值,那么不同实例不就是相同的值吗?反例请看:final double i = Math.random() 这个需要运行时才能确定

举例子:

Static修饰的createCarNum变量,一个类中只有一个指向,创建若干个CarFactory实例,能保证都只操作一个元素

public class CarConstants {
  // 全局配置,一般全局配置会和final一起配合使用, 作为共享变量
  public static final int MAX_CAR_NUM = 10000;
}

public class CarFactory {
  // 计数器  -- 保证不管创建多少个实例都是这一个变量
  private static int createCarNum = 0;

    public static Car createCar() {
      if (createCarNum > CarConstants.MAX_CAR_NUM) {
        throw new RuntimeException("超出最大可生产数量");
      }
      Car c = new Car();
      createCarNum++;
      return c;
    }
}
  • 关键词比对:final、finally 和 finalize比较

final用于修饰属性,方法和类,表示属性不可变,方法不可以被覆盖,类不可以被继承。
finally是异常处理语句结构中,表示总是执行的部分。
finallize表示是object类一个方法,在垃圾回收机制中执行的时候会调用被回收对象的方法。允许回收此前未回收的内存垃圾。所有object都继承了 finalize()方法

  • 反射

反射可以说是框架的灵魂,使得我们在运行时分析类和执行类的方法。
通过反射你可以获取任意一个类的所有属性和方法,你还可以调用这些方法和属性。

异常

好文

  • 异常分类

可以分为两个大类,Error 和 Exception,继承于 顶级父类 Throwable

Error,一般与虚拟机相关

分类:OOM(不断创建对象)、StackOverflow(递归)

Exception又分为:编译时异常、运行时异常

运行时异常:不可预知,代码运行时,才可能知道

其中 编译时异常,必须在编写代码时,加入try-catch、throws(将可能的异常延迟到运行时出现),否则无法通过编译

//自己进行处理
public static void method1() {
    try {
        method();
    } catch (FileNotFoundException e) {
        e.printStackTrace();
    } catch (IOException e) {
        e.printStackTrace();
    }
}

//交给调用者来处理异常,调用者要不解决try-catch 或者再丢给上级
public static void method() throws FileNotFoundException{
    File file = new File("hello.txt");
    FileInputStream fis = new FileInputStream(file);

    int data = fis.read();
    while(data != -1) {
        System.out.println((char)data);
        data = fis.read();
    }
    fis.close();
}
  • 你见过哪些异常?

编译时异常:IOException( — FileNotFoundException)、ClassNotFoundException

运行时异常:NullPointerException、

ArrayIndexOutOfBoundsException、

ArithmeticException — 算术异常(除0异常)、

NumberFormatException — 将字符串转为数字、

ClassCastException — 父子类强制转换。

  • 异常处理机制

异常有两个过程
一、抛出异常,创建异常对象,交给运行时系统处理
二、处理异常:寻找合适的异常处理器处理异常,否则就终止运行

抛出异常:

① 系统自动生成的异常对象

② 手动生成一个异常对象,并抛出(throw)

处理异常:

① try-catch-finally — 针对可能出现的异常,自己进行处理

② throws — 丢给”上级”进行处理

“throws + 异常类型”写在方法的声明处。指明此方法执行时,可能会抛出的异常类型,由该方法的调用者来处理。
try-catch-finally:真正的将异常给处理掉了。throws的方式只是将异常抛给了方法的调用者,并没有真正将异常处理掉。

try – catch – finally

try { //执行的代码,其中可能有异常。一旦发现异常,则立即跳到catch执行。否则不会执行catch里面的内容 }
catch { //除非try里面执行代码发生了异常,否则这里的代码不会执行 }
finally

将 finally中的 return语句 注释掉

IO

  • transient 关键字 — 和序列化有关

一旦变量被transient修饰,变量将不再是对象持久化的一部分,该变量内容在序列化后无法获得访问。

  • 什么时候需要序列化? 怎么序列化?

当我们的对象需要跨平台存储和进行网络传输时,就考虑到序列化问题
Java序列化是指把Java 对象转换为字节序列的过程,而Java反序列化是指把字节序列恢复为Java对象的过程

/*
序列化步骤:
1、实现接口 Serializable
2、提供一个全局变量:serialVersionUID 序列版本号
注意: static和transient修饰的成员变量不能被序列化,其他默认可序列化
*/
  • IO分类

流按照 传输单位分为 字节流字符流
字节流的抽象基类(父类)是: java.io.InputStream、 java.io.OutputStream
字符流的抽象基类(父类)是: java.io.Reader 、 java.io.Writer

访问文件,存在字符文件(word)和二进制文件(音视频)

那么就存在两种文件流:

1、FileInputStream、FileOutputStream — 字节

2、FileReader、FileWriter — 字符!!(别记混了)

File file = new File("hello.txt");
FileReader fr = new FileReader(file);

//字符流  -- 可以合为一步
FileReader fr = new FileReader("hello.txt");

//字节流
File srcFile = new File(srcPath);
fis = new FileInputStream(srcFile);

为了加快传输速度,有了缓冲流,它的操作对象是 字节、字符流,而不是文件

BufferedInputStream ← FileInputStream 字节缓冲流

BufferedReader ← FileReader 字符缓冲流

BufferedReader(new FileReader(new File("hello.txt")));

BufferedInputStream(new FileInputStream(srcFile));
  • Java的IO 和 NIO区别

NIO(即异步IO)和 经典IO(也就是常说的阻塞式(B)IO)

NIO主要有Channel、Buffer和Selector 三大核心组件

太难了orz~ 弃之

排序

排序名称 时间复杂度 空间复杂度 稳定 其他性质 直接插入 O(n^2) O(1) 稳定 冒泡排序 O(n^2) O(1) 稳定 能全局定位 快速排序 O(nlogn) O(logn) 递归需要栈 不稳定 能全局定位 选择排序 O(n^2) O(1) 不稳定 能全局定位 堆排序 O(nlogn) O(1) 不稳定 能全局定位 归并排序 O(nlogn) O(n) 需要等长的辅助表 稳定

  • Java排序接口

存在两种方式,一种是Comparable的自然排序,另一个为Comparator的定制排序

//Comparable 接口  -- 一劳永逸
重写int CompareTo(Object o)方法
规则:当前对象this大于形参对象obj,则返回正整数
public int compareTo(Object o) {
        if(o instanceof Goods){
            Goods goods = (Goods) o;
            if(this.price > goods.price){
                return 1;
            } else if(this.price < goods.price){
                return -1;
            }else{
                return 0;
            }
        }
}
//Comparator   -- 创建临时需要比较的规则
重写int compare(Object o1, Object o2) 方法
规则:o1大于o2,返回正
Arrays.sort(arr1, new Comparator(){
            @Override
            public int compare(Object o1, Object o2) {
                if(o1 instanceof String && o2 instanceof String){
                    String s1 = (String) o1;
                    String s2 = (String) o2;
                    return -s1.compareTo(s2);  //这里实现字符串逆序排序
                }
            }
});

Original: https://www.cnblogs.com/spongie/p/14652946.html
Author: spongie
Title: Java基础问题

原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/579120/

转载文章受原作者版权保护。转载请注明原作者出处!

(0)

大家都在看

亲爱的 Coder【最近整理,可免费获取】👉 最新必读书单  | 👏 面试题下载  | 🌎 免费的AI知识星球