知识屋:更实用的电脑技术知识网站
所在位置:首页 > 科技

Java基础知识点应知应会

发表时间:2022-03-25来源:网络

前言

本文快速回顾了Java中最基础的知识点,用作面试复习,事半功倍。

说明

参考文章

http://how2j.cn/k/j2se-interview/j2se-interview-java/624.htmlhttps://github.com/CyC2018/CS-Notes/blob/master/docs/notes/Java%20%E5%9F%BA%E7%A1%80.md

基础知识点

面向对象的特性

答:封装、继承和多态。

多态分为编译时多态和运行时多态。

编译时多态:方法的重载运行时多态:指程序中定义的对象引用所指向的具体类型在运行期间才确定。

运行时多态有三个条件

多态的存在有三个前提:

要有继承关系子类要重写父类的方法父类引用指向子类对,

父类Animal

子类Cat

测试类Demo_Test1

以上的三段代码充分体现了多态的三个前提,即:

如果再深究一点呢,我们可以看看上面测试类的输出结果,或许对多态会有更深层次的认识。猜一猜上面
的结果是什么。

可以看出来

子类Cat重写了父类Animal的非静态成员方法am.eat();的输出结果为:猫吃饭。

子类重写了父类(Animal)的静态成员方法am.sleep();的输出结果为:动物在睡觉

未被子类(Cat)重写的父类(Animal)方法am.run()输出结果为:动物在奔跑

那么我们可以根据以上情况总结出多态成员访问的特点:

那么多态有什么弊端呢?

不能使用子类特有的成员属性和子类特有的成员方法。

参考:https://www.zhihu.com//30082151

很明显,执行强转语句Cat ct = (Cat)am;之后,ct就指向最开始在堆内存中创建的那个Cat类型的对象了。这就是多态的魅力吧,虽然它有缺点,但是它确实十分灵活,减少多余对象的创建,不用说为了使用子类的某个方法又去重新再堆内存中开辟一个新的子类对象。

数据类型

String是类类型,不是基本类型。

基本类型 有八种:整型 (4种)字符型 (1种)浮点型 (2种)布尔型(1种)

image

image

缓存池

使用==的情况:

如果比较Integer变量,默认比较的是地址值。特例:如果比较的某一边有操作表达式(例如a+b),那么比较的是具体数值

使用equals()的情况:

无论是Integer还是Long中的equals()默认比较的是数值特例:Long的equals()方法,JDK的默认实现:会判断是否是Long类型

new Integer(123) 与 Integer.valueOf(123) 的区别在于:

new Integer(123) 每次都会新建一个对象Integer.valueOf(123) 会使用缓存池中的对象,多次调用会取得同一个对象的引用。

valueOf() 方法的实现比较简单,就是先判断值是否在缓存池中,如果在的话就直接返回缓存池的内容。

基本类型对应的缓冲池如下:

boolean values true and falseall byte valuesshort values between -128 and 127int values between -128 and 127char in the range to

String

概览

String 被声明为 final,因此它不可被继承。

在 Java 8 中,String 内部使用 char 数组存储数据。

在 Java 9 之后,String 类的实现改用 byte 数组存储字符串,同时使用 coder 来标识使用了哪种编码。

不可变的好处

1. 可以缓存 hash 值

因为 String 的 hash 值经常被使用,例如 String 用做 HashMap 的 key。不可变的特性可以使得 hash 值也不可变,因此只需要进行一次计算。

2. String Pool 的需要

如果一个 String 对象已经被创建过了,那么就会从 String Pool 中取得引用。只有 String 是不可变的,才可能使用 String Pool。

注意:不是string创建后就默认进入池的,请看下方intern()

在这里插入图片描述

3. 安全性

String 经常作为参数,String 不可变性可以保证参数不可变。例如在作为网络连接参数的情况下如果 String 是可变的,那么在网络连接过程中,String 被改变,改变 String 对象的那一方以为现在连接的是其它主机,而实际情况却不一定是。

4. 线程安全

String 不可变性天生具备线程安全,可以在多个线程中安全地使用。

Program Creek : Why String is immutable in Java?

String, StringBuffer and StringBuilder

1. 可变性

String 不可变StringBuffer 和 StringBuilder 可变

2. 线程安全

String 不可变,因此是线程安全的StringBuilder 不是线程安全的StringBuffer 是线程安全的,内部使用 synchronized 进行同步

对于三者使用的总结:

1.如果要操作少量的数据用 String

2.单线程操作字符串缓冲区下操作大量数据 StringBuilder

3.多线程操作字符串缓冲区下操作大量数据 StringBuffer

String 和StringBuffer的区别?

String是immutable的,其内容一旦创建好之后,就不可以发生改变。

StringBuffer 是可以变长的,内容也可以发生改变
改变的原理是StringBuffer内部采用了字符数组存放数据,在需要增加长度的时候,创建新的数组,并且把原来的数据复制到新的数组这样的办法来实现。

https://blog.csdn.net/yeweiyang16/article/details/51755552

初始化:可以指定给对象的实体的初始容量为参数字符串s的长度额外再加16个字符

扩容:尝试将新容量扩为大小变成原容量的1倍+2,然后if判断一下 容量如果不够,直接扩充到需要的容量大小。

StackOverflow : String, StringBuffer, and StringBuilder

String Pool

字符串常量池(String Pool)保存着所有字符串字面量(literal strings),这些字面量在编译时期就确定。不仅如此,还可以使用 String 的 intern() 方法在运行过程中将字符串添加到 String Pool 中。

当一个字符串调用 intern() 方法时,如果 String Pool 中已经存在一个字符串和该字符串值相等(使用 equals() 方法进行确定),那么就会返回 String Pool 中字符串的引用;否则,就会在 String Pool 中添加一个新的字符串,并返回这个新字符串的引用。

下面示例中,s1 和 s2 采用 new String() 的方式新建了两个不同字符串,而 s3 和 s4 是通过 s1.intern() 方法取得一个字符串引用。intern() 首先把 s1 引用的字符串放到 String Pool 中,然后返回这个字符串引用。因此 s3 和 s4 引用的是同一个字符串。

如果是采用 "bbb" 这种字面量的形式创建字符串,会自动地将字符串放入 String Pool 中。

在 Java 7 之前,String Pool 被放在运行时常量池中,它属于永久代。而在 Java 7,String Pool 被移到堆中。这是因为永久代的空间有限,在大量使用字符串的场景下会导致 OutOfMemoryError 错误。

StackOverflow : What is String interning?深入解析 String#intern

new String("abc")

使用这种方式一共会创建两个字符串对象(前提是 String Pool 中还没有 "abc" 字符串对象)。

"abc" 属于字符串字面量,因此编译时期会在 String Pool 中创建一个字符串对象,指向这个 "abc" 字符串字面量;而使用 new 的方式会在堆中创建一个字符串对象。

创建一个测试类,其 main 方法中使用这种方式来创建字符串对象。

使用 javap -verbose 进行反编译,得到以下内容:

在 Constant Pool 中,#19 存储这字符串字面量 "abc",#3 是 String Pool 的字符串对象,它指向 #19 这个字符串字面量。在 main 方法中,0: 行使用 new #2 在堆中创建一个字符串对象,并且使用 ldc #3 将 String Pool 中的字符串对象作为 String 构造函数的参数。

以下是 String 构造函数的源码,可以看到,在将一个字符串对象作为另一个字符串对象的构造函数参数时,并不会完全复制 value 数组内容,而是都会指向同一个 value 数组。

运算

参数传递

参数传递

Java 的参数是以值传递的形式传入方法中,而不是引用传递。

以下代码中 Dog dog 的 dog 是一个指针,存储的是对象的地址。在将一个参数传入一个方法时,本质上是将对象的地址以值的方式传递到形参中。因此在方法中使指针引用其它对象,那么这两个指针此时指向的是完全不同的对象,在一方改变其所指向对象的内容时对另一方没有影响。

如果在方法中改变对象的字段值会改变原对象该字段值,因为改变的是同一个地址指向的内容。

StackOverflow: Is Java “pass-by-reference” or “pass-by-value”?

float 与 double

Java 不能隐式执行向下转型,因为这会使得精度降低。

1.1 字面量属于 double 类型,不能直接将 1.1 直接赋值给 float 变量,因为这是向下转型。Java 不能隐式执行向下转型,因为这会使得精度降低。

1.1f 字面量才是 float 类型。

隐式类型转换

因为字面量 1 是 int 类型,它比 short 类型精度要高,因此不能隐式地将 int 类型下转型为 short 类型。

但是使用 += 运算符可以执行隐式类型转换。

上面的语句相当于将 s1 + 1 的计算结果进行了向下转型:

StackOverflow : Why don't Java's +=, -=, *=, /= compound assignment operators require casting?

switch

从 Java 7 开始,可以在 switch 条件判断语句中使用 String 对象。

switch 不支持 long,是因为 switch 的设计初衷是对那些只有少数的几个值进行等值判断,如果值过于复杂,那么还是用 if 比较合适。

继承

访问权限

Java 中有三个访问权限修饰符:private、protected 以及 public,如果不加访问修饰符,表示包级可见。

可以对类或类中的成员(字段以及方法)加上访问修饰符。

类可见表示其它类可以用这个类创建实例对象。成员可见表示其它类可以用这个类的实例对象访问到该成员;

protected 用于修饰成员,表示在继承体系中成员对于子类可见,但是这个访问修饰符对于类没有意义。

如果子类的方法重写了父类的方法,那么子类中该方法的访问级别不允许低于父类的访问级别:这是为了确保可以使用父类实例的地方都可以使用子类实例,也就是确保满足里氏替换原则。

字段决不能是公有的,因为这么做的话就失去了对这个字段修改行为的控制,客户端可以对其随意修改。

例如下面的例子中,AccessExample 拥有 id 共有字段,如果在某个时刻,我们想要使用 int 去存储 id 字段,那么就需要去修改所有的客户端代码。

可以使用公有的 getter 和 setter 方法来替换公有字段,这样的话就可以控制对字段的修改行为。

但是也有例外,如果是包级私有的类或者私有的嵌套类,那么直接暴露成员不会有特别大的影响。

抽象类与接口

1. 抽象类

抽象类和普通类最大的区别是:

抽象类不能被实例化,需要继承抽象类才能实例化其子类

2. 接口

接口是抽象类的延伸,在 Java 8 之前,它可以看成是一个完全抽象的类,也就是说它不能有任何的方法实现。

从 Java 8 开始,接口也可以拥有默认的方法实现,这是因为不支持默认方法的接口的维护成本太高了。

在 Java 8 之前,如果一个接口想要添加新的方法,那么要修改所有实现了该接口的类。

接口的成员(字段 + 方法)默认都是 public 的,并且不允许定义为 private 或者 protected。

接口的字段默认都是 static 和 final 的。

3. 比较

从设计层面上看,抽象类提供了一种 IS-A 关系,那么就必须满足里式替换原则,即子类对象必须能够替换掉所有父类对象。而接口更像是一种 LIKE-A 关系,它只是提供一种方法实现契约,并不要求接口和实现接口的类具有 IS-A 关系。从使用上来看,一个类可以实现多个接口,但是不能继承多个抽象类。接口的字段只能是 static 和 final 类型的,而抽象类的字段没有这种限制。接口的成员只能是 public 的,而抽象类的成员可以有多种访问权限。

4. 使用选择

使用接口:

需要让不相关的类都实现一个方法,例如不相关的类都可以实现 Compareable 接口中的 compareTo() 方法;需要使用多重继承。

使用抽象类:

需要在几个相关的类中共享代码。需要能控制继承来的成员的访问权限,而不是都为 public。需要继承非静态static和非常量final字段。

在很多情况下,接口优先于抽象类,因为接口没有抽象类严格的类层次结构要求,可以灵活地为一个类添加行为。并且从 Java 8 开始,接口也可以有默认的方法实现,使得修改接口的成本也变的很低。

深入理解 abstract class 和 interfaceWhen to Use Abstract Class and Interface

super

访问父类的构造函数:可以使用 super() 函数访问父类的构造函数,从而委托父类完成一些初始化的工作。访问父类的成员:如果子类重写了父类的中某个方法的实现,可以通过使用 super 关键字来引用父类的方法实现。

Using the Keyword super

继承相关小问题

接口是否可继承接口?

可以,比如List 就继承了接口Collection

抽象类是否可实现(implements)接口?

可以,比如 MouseAdapter鼠标监听适配器 是一个抽象类,并且实现了MouseListener接口

抽象类是否可继承实体类(concrete class)?

可以,所有抽象类,都继承了Object

Object 通用方法

经典:

https://fangjian0423.github.io/2016/03/12/java-Object-method/

概览

getClass方法 返回当前运行时对象的Class对象hashCode方法 该方法返回对象的哈希码,主要使用在哈希表中,比如JDK中的HashMap。(文章中有对hashcode的详细解释)equals方法 如果重写了equals方法,通常有必要重写hashCode方法,这点已经在hashCode方法中说明了。clone方法 创建并返回当前对象的一份拷贝。Object本身没有实现Cloneable接口,所以不重写clone方法并且进行调用的话会发生CloneNotSupportedException异常。 使用 clone() 方法来拷贝一个对象即复杂又有风险,它会抛出异常,并且还需要类型转换。Effective Java 书上讲到,最好不要去使用 clone(),可以使用拷贝构造函数或者拷贝工厂来拷贝一个对象。toString方法 Object对象的默认实现,即输出类的名字@实例的哈希码的16进制。notify方法: 唤醒一个在此对象监视器上等待的线程(监视器相当于就是锁的概念)。notifyAll方法 跟notify一样,唯一的区别就是会唤醒在此对象监视器上等待的所有线程,而不是一个线程。wait(long timeout) throws InterruptedException方法 wait方法会让当前线程等待直到另外一个线程调用对象的notify或notifyAll方法,或者超过参数设置的timeout超时时间。wait(long timeout, int nanos) throws InterruptedException方法 跟wait(long timeout)方法类似,多了一个nanos参数,这个参数表示额外时间(以毫微秒为单位,范围是 0-999999)。 所以超时的时间还需要加上nanos毫秒。wait() throws InterruptedException方法 跟之前的2个wait方法一样,只不过该方法一直等待,没有超时时间这个概念。 需要注意的是 wait(0, 0)和wait(0)效果是一样的,即一直等待。finalize方法 该方法的作用是实例被垃圾回收器回收的时候触发的操作,就好比 “死前的最后一波挣扎”。

补充:什么是Native Method

简单地讲,一个Native Method就是一个java调用非java代码的接口。一个Native Method是这样一个java的方法:该方法的实现由非java语言实现,比如C。这个特征并非java所特有,很多其它的编程语言都有这一机制,比如在C++中,你可以用extern "C"告知C++编译器去调用一个C的函数。

equals()

1. 等价关系

Ⅰ 自反性

Ⅱ 对称性

Ⅲ 传递性

Ⅳ 一致性

多次调用 equals() 方法结果不变

Ⅴ 与 null 的比较

对任何不是 null 的对象 x 调用 x.equals(null) 结果都为 false

2. 等价与相等

对于基本类型,== 判断两个值是否相等,基本类型没有 equals() 方法。对于引用类型,== 判断两个变量是否引用同一个对象,而 equals() 判断引用的对象是否等价。

3. 实现

检查是否为同一个对象的引用,如果是直接返回 true;检查是否是同一个类型,如果不是,直接返回 false;将 Object 对象进行转型;判断每个关键域是否相等。

4. 两个对象值相同(x.equals(y) == true),但却可有不同的hash code,这句话对不对?

因为hashCode()方法和equals()方法都可以通过自定义类重写,是可以做到equals相同,但是hashCode不同的

但是,在Object类的equals()方法中有这么一段话

翻译如下:

通常来讲,在重写这个方法的时候,也需要对hashCode方法进行重写,
以此来保证这两个方法的一致性——
当equals返回true的时候,这两个对象一定有相同的hashcode.

hashCode()

hashCode() 返回散列值,而 equals() 是用来判断两个对象是否等价。等价的两个对象散列值一定相同,但是散列值相同的两个对象不一定等价。

在覆盖 equals() 方法时应当总是覆盖 hashCode() 方法,保证等价的两个对象散列值也相等。

理想的散列函数应当具有均匀性,即不相等的对象应当均匀分布到所有可能的散列值上。这就要求了散列函数要把所有域的值都考虑进来。可以将每个域都当成 R 进制的某一位,然后组成一个 R 进制的整数。R 一般取 31,因为它是一个奇素数,如果是偶数的话,当出现乘法溢出,信息就会丢失,因为与 2 相乘相当于向左移一位。

一个数与 31 相乘可以转换成移位和减法:31*x == (x

收藏
  • 人气文章
  • 最新文章
  • 下载排行榜
  • 热门排行榜