参考
继承
class 父类 {
}
class 子类 extends 父类 {
}
继承的特性:
- 子类拥有父类非private的属性和方法
- 子类可以对父类进行扩展
- 子类可以重写父类的方法
- 使用extends只能单继承,使用implements可以变相的多继承,即一个类继承多个接口,如下。
public interface A {
public void eat();
public void sleep();
}
public interface B {
public void show();
}
public class C implements A,B {
}
- 通过super关键字引用当前对象的父类,通过this关键字引用自身。
class Animal {
void eat() {
System.out.println("animal : eat");
}
}
class Dog extends Animal {
void eat() {
System.out.println("dog : eat");
}
void eatTest() {
this.eat(); // this 调用自己的方法
super.eat(); // super 调用父类方法
}
}
public class Test {
public static void main(String[] args) {
Animal a = new Animal();
a.eat();
Dog d = new Dog();
d.eatTest();
}
}
- 使用final关键字修饰的类不能被继承,但类内的属性和方法不是final的。使用final关键字修饰的方法不能被子类重写。
- 子类不继承父类的构造器,只是调用,实例化子类对象时,先调用父类的构造器。若父类构造器有参,则子类构造器中需显式使用super调用父类构造器,若父类构造器无参,则系统自动调用。
/**
* @author wuming
* @date 2023-12-20 13:48
* @description
*/
class SuperClass{
private int n;
SuperClass(){
System.out.println("SuperClass 无参构造");
}
SuperClass(int n){
System.out.println("SuperClass 有参构造" + n);
this.n = n;
}
}
class SubClass extends SuperClass{
private int n;
// 系统自动调用父类的无参构造函数
SubClass(){
System.out.println("SubClass 隐式调用父类无参构造");
}
SubClass(int n){
super(n);
System.out.println("SubClass 显式调用父类有参构造");
this.n = n;
}
}
public class ExtendTest {
public static void main (String args[]){
new SubClass();
new SubClass(200);
}
}
SuperClass 无参构造
SubClass 隐式调用父类无参构造
SuperClass 有参构造200
SubClass 显式调用父类有参构造
重写
重写指子类对父类允许子类访问的方法的实现进行重写的过程,重写的函数名、返回值和形参与父类中的方法保持一致,具体功能可自己实现。
- 子类重写的方法的访问权限不能比父类中被重写的方法更低。
- 在异常处理方面,不允许子类抛出比父类方法更宽泛的异常。
- 声明为final、static的方法和构造方法不能被重写。
重载
重载是指在一个类里面方法名字相同但参数个数或类型不同,返回类型可以相同也可以不同。
基本类型与包装类型
一共有byte short int long char float double boolean八种基本类型,分别对应八种包装类型,有如下不同点。
- 用途:一般使用基本类型定义常量和局部变量,而在方法参数和对象属性中使用包装类型。
- 存储方式:基本数据类型的局部变量存放在java虚拟机栈中的局部变量表中,未被static修饰的基本类型成员变量存放在Java虚拟机的堆中。包装类型属于对象类型,存在于堆中。
- 占用空间:基本类型的占用空间非常小。
- 默认值:作为成员变量的包装类型默认值是null,基本类型各自有各自的默认值不为null。
- 比较方式:对于基本类型,使用
==
可作值比较,对于包装类型,使用==
是地址比较,使用equals方法才是值比较。
包装类型的缓存机制
包装类型大部分使用了缓存机制提升性能,如Byte Short Integer Long四种包装类型默认创建了数值 [-128, 127] 相应类型的缓存数据。
Integer缓存源码如下:
public static Integer valueOf(int i) {
if (i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)];
return new Integer(i);
}
private static class IntegerCache {
static final int low = -128;
static final int high;
static {
// high value may be configured by property
int h = 127;
}
}
看以下代码
Integer i1 = 40;
Integer i2 = new Integer(40);
System.out.println(i1==i2);
第一行会发生装箱,等价于Integer i1 = Integer.valueOf(40)
,而40在缓存区间中,所以 i1使用缓存中的对象,而 i2 是新创建的对象,使用 == 比较时,比较的是地址,结果为false。
自动拆装箱
Integer i = 10; //装箱
int n = i; //拆箱
静态方法为什么不能调用非静态成员
静态方法是属于类的,在类加载的时候就会分配内存,可以通过类名直接访问。而非静态成员属于实例对象,只有在对象实例化之后才存在,需要通过对象访问。
引用拷贝、深拷贝和浅拷贝
Object类
Object类是所有类的父类,提供以下11个方法
/**
* native 方法,用于返回当前运行时对象的 Class 对象,使用了 final 关键字修饰,故不允许子类重写。
*/
public final native Class<?> getClass()
/**
* native 方法,用于返回对象的哈希码,主要使用在哈希表中,比如 JDK 中的HashMap。
*/
public native int hashCode()
/**
* 用于比较 2 个对象的内存地址是否相等,String 类对该方法进行了重写以用于比较字符串的值是否相等。
*/
public boolean equals(Object obj)
/**
* native 方法,用于创建并返回当前对象的一份拷贝。
*/
protected native Object clone() throws CloneNotSupportedException
/**
* 返回类的名字实例的哈希码的 16 进制的字符串。建议 Object 所有的子类都重写这个方法。
*/
public String toString()
/**
* native 方法,并且不能重写。唤醒一个在此对象监视器上等待的线程(监视器相当于就是锁的概念)。如果有多个线程在等待只会任意唤醒一个。
*/
public final native void notify()
/**
* native 方法,并且不能重写。跟 notify 一样,唯一的区别就是会唤醒在此对象监视器上等待的所有线程,而不是一个线程。
*/
public final native void notifyAll()
/**
* native方法,并且不能重写。暂停线程的执行。注意:sleep 方法没有释放锁,而 wait 方法释放了锁 ,timeout 是等待时间。
*/
public final native void wait(long timeout) throws InterruptedException
/**
* 多了 nanos 参数,这个参数表示额外时间(以纳秒为单位,范围是 0-999999)。 所以超时的时间还需要加上 nanos 纳秒。。
*/
public final void wait(long timeout, int nanos) throws InterruptedException
/**
* 跟之前的2个wait方法一样,只不过该方法一直等待,没有超时时间这个概念
*/
public final void wait() throws InterruptedException
/**
* 实例被垃圾回收器回收的时候触发的操作
*/
protected void finalize() throws Throwable { }
hashCode
hashCode()的作用是获取哈希码(int整数),定义在Object类中。
HashSet 如何检查重复:当你把对象加入 HashSet 时,HashSet 会先计算对象的 hashCode 值来判断对象加入的位置,同时也会与其他已经加入的对象的 hashCode 值作比较,如果没有相符的 hashCode,HashSet 会假设对象没有重复出现。但是如果发现有相同 hashCode 值的对象,这时会调用 equals() 方法来检查 hashCode 相等的对象是否真的相同。如果两者相同,HashSet 就不会让其加入操作成功。如果不同的话,就会重新散列到其他位置。这样我们就大大减少了 equals 的次数,相应就大大提高了执行速度。
因为两个对象的hashCode相等不等价于两个对象相等(碰撞),所以在快速使用完hashCode判别后若相等,还需使用equals判断。
为什么重写 equals() 时必须重写 hashCode() 方法?
因为两个相等的对象的 hashCode 值必须是相等。也就是说如果 equals 方法判断两个对象是相等的,那这两个对象的 hashCode 值也要相等。
如果重写 equals() 时没有重写 hashCode() 方法的话就可能会导致 equals 方法判断是相等的两个对象,hashCode 值却不相等。
String、StringBuffer和StringBuilder
String是不可变的,在String类中,保存字符串的数组被final修饰,且为私有,没有提供修改字符串的方法。
StringBuilder 与 StringBuffer 都继承自 AbstractStringBuilder 类,在 AbstractStringBuilder 中也是使用字符数组保存字符串,不过没有使用 final 和 private 关键字修饰,最关键的是这个 AbstractStringBuilder 类还提供了很多修改字符串的方法比如 append 方法。
abstract class AbstractStringBuilder implements Appendable, CharSequence {
char[] value;
public AbstractStringBuilder append(String str) {
if (str == null)
return appendNull();
int len = str.length();
ensureCapacityInternal(count + len);
str.getChars(0, len, value, count);
count += len;
return this;
}
//...
}
线程安全性
String 中的对象是不可变的,也就可以理解为常量,线程安全。AbstractStringBuilder 是 StringBuilder 与 StringBuffer 的公共父类,定义了一些字符串的基本操作,如 expandCapacity、append、insert、indexOf 等公共方法。StringBuffer 对方法加了同步锁或者对调用的方法加了同步锁,所以是线程安全的。StringBuilder 并没有对方法进行加同步锁,所以是非线程安全的。
对于三者使用的总结:
操作少量的数据: 适用 String
单线程操作字符串缓冲区下操作大量数据: 适用 StringBuilder
多线程操作字符串缓冲区下操作大量数据: 适用 StringBuffer
在 Java 9 之后,String、StringBuilder 与 StringBuffer 的实现改用 byte 数组存储字符串,Latin-1 编码方案下,byte 占一个字节(8 位),char 占用 2 个字节(16),节省一半的内存空间。如果字符串中包含的汉字超过 Latin-1 可表示范围内的字符,byte 和 char 所占用的空间是一样的。
字符串对象通过“+”的字符串拼接方式,实际上是通过 StringBuilder 调用 append() 方法实现的,拼接完成之后调用 toString() 得到一个 String 对象 。
不过,在循环内使用“+”进行字符串的拼接的话,存在比较明显的缺陷:编译器不会创建单个 StringBuilder 以复用,会导致创建过多的 StringBuilder 对象,如果直接使用 StringBuilder 对象进行字符串拼接的话,就不会存在这个问题了。
String 中的 equals 方法是被重写过的,比较的是 String 字符串的值是否相等。 Object 的 equals 方法是比较的对象的内存地址。
字符串常量池
字符串常量池是JVM为了提升性能为String类专门开辟的内存区域,主要为了避免字符串的重复创建。
// 在堆中创建字符串对象”test“
// 将字符串对象”test“的引用保存在字符串常量池中
String aa = "test";
// 直接返回字符串常量池中字符串对象”test“的引用
String bb = "test";
System.out.println(aa==bb);// true