Java基础

工作久了,很多知识点很难串起来,也是一看就忘。在工作中研发的技术组件或在开发过程与生产环境的问题,基本都是之前有了解或者看过类似的博客的,较为零散 。急迫需要整理自己的知识链路。Java基础篇,怎么说呢,就是概念比较多吧,但是在我们敲代码的过程中又一直存在,一份好的代码,免不了抽象,封装,继承等。基础篇,作为复习的指导篇吧。如果文章有问题的话,感谢指出。

面向对象的特征有哪些?

面向对象是一种思想,主要体现在代码开发模块化易于维护修改、复用性强,代码可读性和灵活性高。
5大原则: 单一职责、里氏替换、开放封闭、依赖倒置、接口分离原则

关注四点:封装、继承、多态、抽象。这里就不过多介绍这几种的含义了

JDK、JRE、JVM 分别是什么关系?

JDK(Java Development Kit) 是 Java 语言的软件开发工具包(SDK)。它是每一个Java软件开发人员必须安装的。JDK安装之后,它会自带一个JRE,因为软件开发人员编写完代码之后总是要运行的。注意:如果只是在这台机器上运行Java程序,则不需要安装JDK,只需要安装JRE即可。

  • 用于编译 Java 程序的 javac 命令
  • 用于启动 JVM 运行 Java 程序的 Java 命令
  • 用于生成文档的 Javadoc 命令
  • 用于打包的 jar 命令

JRE(Java Runtime Environment,Java运行环境),运行JAVA程序所必须的环境的集合,包含JVM标准实现及Java核心类库。JRE包括JVM

JVM是Java Virtual Machine(Java虚拟机)的缩写,提供了字节码文件(.class)的运行环境支持。

在这里插入图片描述

Java的平台无关性体现在哪里

Java虚拟机是一个可执行Java字节码的虚拟机进程。
.java文件编译成虚拟机执行的.class文件。而不需要开发人员重写编译。.class文件不面向特定的处理器、只面向虚拟机(所以有多个操作系统的JDK可供下载)。
Java 源程序经过编译器编译后变成字节码,字节码由虚拟机解释执行,虚拟机将每一条要执行的字节码送给解释器,解释器将其翻译成特定机器上的机器码,然后在特定的机器上运行。这也就是解释了 Java 的编译与解释并存的特点。

Java 源代码(.java) => 编译器 => JVM 可执行的 Java 字节码(.class)
=> JVM => JVM 中解释器 => 机器可执行的二进制机器码 => 程序运行

Java的基本数据类型

  • 整数值型:byte、short、int、long
  • 字符型:char
  • 浮点类型:float、double
  • 布尔型:boolean
  • 整数型:默认 int 型,小数默认是 double 型。Float 和 Long 类型的必须加后缀。比如:float f = 100f

值传递与引用传递

  • 值传递,是对基本型变量而言的,传递的是该变量的一个副本,改变副本不影响原变量
  • 引用传递,一般是对于对象型变量而言的,传递的是该对象地址的一个副本,并不是原对象本身
    一般认为,Java 内的传递都是值传递,Java 中实例对象的传递是引用传递。

方法的重写与重载有什么区别

方法重写

  • 方法名、参数、返回值都相同
  • 子类方法不能缩小父类方法的访问权限(父类是public的,子类不能是private等)
  • 子类方法不能抛出比弗雷方法更多的异常(子类可以不抛出异常)
  • 方法是final的不能重写

方法重载

  • 方法名相同、参数列表不同

在这里插入图片描述

String、StringBuffer、StringBuilder 的区别

三个类都能操作字符串

  • String: 是只读字符串,也就意味着 String 引用的字符串内容是不能被改变的。 每次对 String 类型进行改变的时候,都会生成一个新的 String 对象,然后将指针指向新的 String 对象。
  • StringBuffer/StringBuilder 类,表示的字符串对象可以直接进行修改。StringBuilder 和 StringBuffer 的方法完全相同,区别在于它是在单线程环境下使用的,因为它的所有方面都没有被 synchronized 修饰,因此它的效率也比 StringBuffer 要高。

使用上: 操作少量的数据 = String,单线程操作字符串缓冲区下操作大量数据 = StringBuilder,多线程操作字符串缓冲区下操作大量数据 = StringBuffer(几乎用不上)。

一般我们在循环里面对字符串操作,建议使用StringBuilder作为字符串的操作类。这个在我几年前的工作中遇到过生产问题。原因是用string做字符串拼接导致的系统卡顿。

String s = new String(“xyz”) 会创建几个对象?

  • 首先,在 String 池内找,找到 “xyz” 字符串,不创建 “xyz” 对应的 String 对象,否则创建一个对象。然后,遇到 new 关键字,在内存上创建 String 对象,并将其返回给 s ,又一个对象。
    这个问题涉及到堆栈、常量池的引用。具体可以看下 Java String s = new String(“hello”)和String s = "hello"的区别

String 为什么是不可变的?

String 类中使用 final 关键字字符数组保存字符串,String是final的不可被继承

//string.java
private final char[] value;

而 StringBuilder 与 StringBuffer 都继承自 AbstractStringBuilder 类,在 AbstractStringBuilder 中也是使用字符数组保存字符串 char[] value ,但是没有用 final 关键字修饰。

// AbstractStringBuilder.java
char[] value;

String 不可变的好处

  • String对象的hash值经常被使用。例如作为HashMap的key,因为String类型是不可变的,所以其hash值也是不变的。因此只需要计算一次。
  • String对象只要被创建过,就可以从String pool中取到该对象的引用。只有String是不可变的,才能从String pool中取得引用。
  • String作为参数,String的不可变可以保证参数的不变性,保证参数安全
  • 线程安全

String.intern方法

常量池存放于方法区中, 这个方法在jdk不同版本实现的都不一样

  1. String对象可能会在堆内存中分配一块空间创建一个String对象,在常量池中创建该对象的复制jdk 1.6(或引用jdk 1.7及以上),也可能直接指向常量池(永久带)中的引用(例String str=“xxx”)。还有一种可能是直接在堆内存中分配一块空间创建一个String对象(例 String str=new String(xxx))。
  2. intern方法可以看成返回常量池中该字符串对象的引用。如果没有该字符串对象就把这个对象(或引用)加到常量池。
  3. jdk1.6跟jdk1.7以上的区别是当常量池中不存在这个字符串,jdk1.6是直接复制对象到常量池,而jdk1.7以上是把对象的引用加入常量池。
  4. 类似于”abc”这样的字符串,在第一次被使用到(比如String a=”abc”或者String a=new String(“abc”)或者"abc".equals(xxx))后就会被加载到常量池中去。

详细的例子可查看String中intern方法的作用

自动拆装箱

自动装箱和拆箱,就是基本类型和引用类型之间的转换。

int 和 Integer 有什么区别?

int 是基本数据类型。 Integer 是其包装类,注意是一个类。

Integer 的缓存策略

    public static Integer valueOf(int i) {
        assert IntegerCache.high >= 127;
        //当前值在缓存数组区间段,则直接返回该缓存值
        if (i >= IntegerCache.low && i <= IntegerCache.high)
            return IntegerCache.cache[i + (-IntegerCache.low)];
        //否则创建新的Integer实例
        return new Integer(i);
    }

大致上: 缓存了-128 - 127区间的数字。

//代码来源于《深入理解Java虚拟机》第4章4.3.1 P121。
public class SynAddRunnable implements Runnable {

    int a, b;

    public SynAddRunnable(int a, int b) {
        this.a = a;
        this.b = b;
    }

    @Override
    public void run() {
        synchronized (Integer.valueOf(a)) {
            synchronized (Integer.valueOf(b)) {
                System.out.println(a + b);
            }
        }
    }

    public static void main(String[] args) {
        for (int i = 0; i < 100; i++) {
            new Thread(new SynAddRunnable(1, 2)).start();
            new Thread(new SynAddRunnable(2, 1)).start();
        }
    }
}

上面这段程序会发生死锁。造成死锁的原因:[-128,127]之间的数字会被缓存,而Integer.valueOf()会返回缓存的对象。因为在缓存中的Integer对象,整个JVM内部就只有一个(如Integer(1))。因此代码中200次for循环实际上总共只创建了两个对象,当线程A持有Integer.valueOf(1)时,如果线程B持有Integer.valueOf(2),则就会出现死锁。将a,b的值调大到127及以上,整个线程运行完好。

equals 与 == 的区别?

值类型(int,char,long,boolean 等 都是用 == 判断相等性。
引用类型: == 判断引用所指的对象是否是同一个。equals 方法,是 Object 的成员函数,有些类会覆盖(override) 这个方法,用于判断对象的等价性

例如 String 类,两个引用所指向的 String 都是 “abc” ,但可能出现他们实际对应的对象并不是同一个(和 JVM 实现方式有关),因此用 == 判断他们可能不相等,但用 equals 方法判断一定是相等的。

如何在父类中为子类自动完成所有的 hashCode 和 equals 实现?这么做有何优劣?

父类的 equals ,一般情况下是无法满足子类的 equals 的需求。

  • 比如所有的对象都继承 Object ,默认使用的是 Object 的 equals 方法,在比较两个对象的时候,是看他们是否指向同一个地址。但是我们的需求是对象的某个属性相同,就相等了,而默认的 equals 方法满足不了当前的需求,所以我们要重写 equals 方法。
  • 如果重写了 equals 方法,就必须重写 hashCode 方法,否则就会降低 Map 等集合的索引速度。

equals 方法,用于比较对象的内容是否相等。
hashCode 方法,大多在集合中用到。

有没有可能 2 个不相等的对象有相同的 hashCode?

可能会发生,这个被称为哈希碰撞。当然,相等的对象,即我们重写了 equals 方法,一定也要重写 hashCode 方法,否则将出现我们在 HashMap 中,相等的对象作为 key ,将找不到对应的 value 。

所以说,equals 和 hashCode 的关系会是:

  • equals 不相等,hashCode 可能相等。
  • equals 相等,请重写 hashCode 方法,保证 hashCode 相等。

一般来说,hashCode 方法的重写,可以看看 《科普:为什么 String hashCode 方法选择数字31作为乘子》 方法。

final、finally、finalize 的区别

这三个毫无关联,放一起做比较可能是长的比较像吧

  1. final 是修饰符关键字
  • 如果一个类被声明为 final ,意味着它不能再派生出新的子类,不能作为父类被继承。因此一个类不能既被声明为 abstract 的,又被声明为 final 的。
  • 将变量或方法声明为 final ,可以保证它们在使用中不被改变。被声明为 final 的变量必须在声明时给定初值,而在以后的引用中只能读取,不可修改。被声明为 final 的方法也同样只能使用,不能重写。
  1. finally
  • 在异常处理时提供 finally 块来执行任何清除操作。一般常用于关闭资源

在以下 4 种特殊情况下,finally块不会被执行:

  • 在 finally 语句块中发生了异常。
  • 在前面的代码中用了 System.exit() 退出程序。
  • 程序所在的线程死亡。
  • 关闭 CPU 。
  1. finalize

Java 允许使用 #finalize() 方法,在垃圾收集器将对象从内存中清除出去之前做必要的清理工作。这个方法是由垃圾收集器在确定这个对象没有被引用时对这个对象调用的。通俗来说: 就是对象自救的方法。一般我们都不会用到这个方法

  • 它是在 Object 类中定义的,因此所有的类都继承了它。
  • 子类覆盖 finalize() 方法,以整理系统资源或者执行其他清理工作。
  • #finalize() 方法,是在垃圾收集器删除对象之前对这个对象调用的。

Java 对象创建的方式

  1. 使用 new 关键字创建对象
  2. 反射机制(包含class与Constructor)
  3. clone
  4. 反序列化对象

抽象类和接口有什么区别?

从设计层面来说,抽象是对类的抽象,是一种模板设计,接口是行为的抽象,是一种行为的规范。

  • Java 提供和支持创建抽象类和接口。它们的实现有共同点,不同点在于:接口中所有的方法隐含的都是抽象的,而抽象类则可以同时包含抽象和非抽象的方法。
  • 类可以实现很多个接口,但是只能继承一个抽象类。类可以不实现抽象类和接口声明的所有方法,当然,在这种情况下,类也必须得声明成是抽象的。
  • 抽象类可以在不提供接口方法实现的情况下实现接口。
  • Java 接口中声明的变量默认都是 final 的。抽象类可以包含非 final 的变量。
  • Java 接口中的成员函数默认是 public 的。抽象类的成员函数可以是 private,protected 或者是 public 。
  • 接口是绝对抽象的,不可以被实例化。抽象类也不可以被实例化,但是,如果它包含 #main(String[] args) 方法的话是可以被调用的。

为什么有抽象类又要有接口

因为类都是单继承的,接口可以多实现。

因为组合能带来比继承更好的灵活性,所以有句话叫做“组合优于继承”。

内部类

简单的说,就是在一个类、接口或者方法的内部创建另一个类。

  • 内部类的作用:内部类提供了更好的封装,除了该外围类,其他类都不能访问。
  • Anonymous Inner Class(匿名内部类) 可以继承其他类或实现其他接口。
  • 内部类可以引用它的包含类(外部类) 的成员,包括私有成员。

类的实例化顺序

  1. 父类静态变量
  2. 父类静态代码块
  3. 子类静态变量、
  4. 子类静态代码块
  5. 父类非静态变量(父类实例成员变量)
  6. 父类构造函数
  7. 子类非静态变量(子类实例成员变量)
  8. 子类构造函数

IO流

  • 按照流的流向分,可以分为输入流和输出流
  • 按照操作单元划分,可以划分为字节流和字符流
  • 按照流的角色划分为节点流和处理流

Java的io流都是通过如下4个抽象类基类中派生出来的

  • InputStream/Reader: 所有的输入流的基类,前者是字节输入流,后者是字符输入流。
  • InputStream/Reader: 所有的输入流的基类,前者是字节输入流,后者是字符输入流。

在这里插入图片描述

关于序列化

序列化就是一种用来处理对象流的机制,所谓对象流也就是将对象的内容进行流化。
对象转流为序列化,流转对象为反序列化

JDK的序列化:
将需要被序列化的类,实现 Serializable 接口,该接口没有需要实现的方法,implements Serializable 只是为了标注该对象是可被序列化的。

  1. 序列化
    然后,使用一个输出流(如:FileOutputStream)来构造一个 ObjectOutputStream(对象流)对象
    接着,使用 ObjectOutputStream 对象的 #writeObject(Object obj) 方法,就可以将参数为 obj 的对象写出(即保存其状态)。
  2. 反序列化
    要恢复的话则用输入流。
    transient 关键字用来修饰不许序列化的属性

其他序列化方式: fastjson jackson protobuf等

对象的克隆

  1. 实现 Cloneable 接口,并重写 Object 类中的 #clone() 方法。可以实现浅克隆,也可以实现深克隆。
  2. 实现 Serializable 接口,通过对象的序列化和反序列化实现克隆。可以实现真正的深克隆。

像我们用到的属性的拷贝,BeanUtils.copyProperties 就是浅拷贝

反射中,Class.forName 和 ClassLoader 区别

这两者,都可用来对类进行加载

  • Class#forName(…) 方法,除了将类的 .class 文件加载到JVM 中之外,还会对类进行解释,执行类中的 static 块。
  • ClassLoader 只干一件事情,就是将 .class 文件加载到 JVM 中,不会执行 static 中的内容,只有在 newInstance 才会去执行 static 块。

Class#forName(name, initialize, loader) 方法,带参函数也可控制是否加载 static 块,并且只有调用了newInstance 方法采用调用构造函数,创建类的对象。

异常体系

在这里插入图片描述

  1. Throwable
    Throwable 是 Java 语言中所有错误与异常的超类。
    Throwable 包含两个子类:Error(错误)和 Exception(异常),它们通常用于指示发生了异常情况。
    Throwable 包含了其线程创建时线程执行堆栈的快照,它提供了 printStackTrace() 等接口用于获取堆栈跟踪数据等信息。

  2. Error(错误)
    定义:Error 类及其子类。程序中无法处理的错误,表示运行应用程序中出现了严重的错误。

特点:此类错误一般表示代码运行时 JVM 出现问题。通常有 Virtual MachineError(虚拟机运行错误)、NoClassDefFoundError(类定义错误)等。比如 OutOfMemoryError:内存不足错误;StackOverflowError:栈溢出错误。此类错误发生时,JVM 将终止线程。

这些错误是不受检异常,非代码性错误。因此,当此类错误发生时,应用程序不应该去处理此类错误。按照Java惯例,我们是不应该实现任何新的Error子类的!

  1. Exception(异常)
    程序本身可以捕获并且可以处理的异常。Exception 这种异常又分为两类:运行时异常和编译时异常。

异常被处理后异常对象会发生什么

异常对象会在下次 GC 执行时被回收。

try-catch-finally 中,如果 catch 中 return 了,finally 还会执行吗?

public static int getInt() {
    int a = 10;
    try {
        System.out.println(a / 0);
        a = 20;
    } catch (ArithmeticException e) {
        a = 30;
        return a;
        /*
         * return a 在程序执行到这一步的时候,这里不是return a 而是 return 30;这个返回路径就形成了
         * 但是呢,它发现后面还有finally,所以继续执行finally的内容,a=40
         * 再次回到以前的路径,继续走return 30,形成返回路径之后,这里的a就不是a变量了,而是常量30
         */
    } finally {
        a = 40;
    }
	return a;
}

返回的值是30

public static int getInt() {
    int a = 10;
    try {
        System.out.println(a / 0);
        a = 20;
    } catch (ArithmeticException e) {
        a = 30;
        return a;
    } finally {
        a = 40;
        //如果这样,就又重新形成了一条返回路径,由于只能通过1个return返回,所以这里直接返回40
        return a; 
    }
}

返回的值是40;

异常使用注意

  • 不要将异常处理用于正常的控制流(设计良好的 API 不应该强迫它的调用者为了正常的控制流而使用异常)。
  • 对可以恢复的情况使用受检异常,对编程错误使用运行时异常。
  • 避免不必要的使用受检异常(可以通过一些状态检测手段来避免异常的发生)。
  • 优先使用标准的异常。
  • 每个方法抛出的异常都要有文档。
  • 保持异常的原子性
  • 不要在 catch 中忽略掉捕获到的异常。

反射的作用及实现

  • 在运行时构造一个类的对象
  • 判断一个类所具有的成员变量和方法
  • 调用一个对象的方法
  • 生成动态代理

反射的应用很多,很多框架都有用到

  • Spring 框架的 IoC 基于反射创建对象和设置依赖属性
  • Spring MVC 的请求调用对应方法,也是通过反射
  • JDBC 的 Class#forName(String className) 方法,也是使用反射

基础篇先写这么多吧,后面在工作中遇到这类问题再慢慢收录进去

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

kaisnm

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值