基础

Object类

equals方法

  1. public boolean equals(Object obj) {
  2. return (this == obj);
  3. }

比较两个对象是否相同,不重写直接比较两个对象。

hashcode方法

  1. public native int hashCode();

native关键字表明该方法是本地方法,使用C或C是写的代码。在调用时,java会通知操作系统来调用本地库中C脚本。

clone方法

  1. protected native Object clone() throws CloneNotSupportedException

native方法,对当前对象进行拷贝,创建一个相同对象,地址不同,对于x.clone() == x;//false x.clone().getClass() == x.getClass(); //true

不重写clone方法会抛出CloneNotSupportedException异常。

getClass方法

  1. public final native Class<?> getClass()

本地方法,final修饰不能被重写。返回当前运行对象的class对象。

toString方法

  1. public String toString() {
  2. return getClass().getName() + "@" + Integer.toHexString(hashCode());
  3. }

返回类名@十六进制hashcode

notify方法

  1. public final native void notify()

本地方法,用于唤醒一个wait的线程。如果存在 多个wait线程,只会随机唤醒一个。

notifyAll方法

  1. public final native void notifyAll()

本地方法,唤醒所有wait的线程。

wait方法

  1. public final native void wait(long timeout) throws InterruptedException

本地方法,不能重写,暂停线程的执行。传入等待时间。释放了锁。sleep方法没有释放锁。

  1. public final void wait(long timeout, int nanos) throws InterruptedException

wait的重载方法,多了nanos参数,超时时间 = timeout+ nanos

  1. public final void wait() throws InterruptedException

wait的重载方法,没有参数,一直等待。

finalize方法

  1. protected void finalize() throws Throwable { }

实例被垃圾回收器回收时触发

位运算

位运算将整型和字符型转换成二进制进行运算。

泛型

本质是参数化类型,编译时提供类型安全检测机制。

四种使用方式

  • 泛型类
  • 泛型接口
  • 泛型方法
  • 泛型变量

类型擦除

  • 如何能证明java进行了类型擦除?

    使用反射能够在List中插入不同类型的数据,反射修改的是编译后的class文件。说明在编译时进行了类型擦除。

  • 类型擦除后的原始类型是什么?

    没有进行类型限定的泛型,擦除后原始类型为Object

    存在类型限定的泛型,擦除后为限定类型。如,原始类型为comparable.

  • 类型擦除有哪些问题?怎么解决?
    类型擦除的时间。先类型检查,编译再擦除。支队标注泛型引用有效果。
    自动类型转换。获取数据时,获取方法已经添加了强转保证数据类型正确。
    多态冲突。jvm编译器通过桥方法和协变解决。(其实没看懂)
    不能使用基本数据类型,
    泛型类定义静态方法和变量时不能使用泛型类使用的泛型,自身引用的除外。
    类型查询时不能添加泛型。if( arrayList instanceof ArrayList<String>) //编译错误

  • 类型擦除后所有泛型类型会转换成原始类型,为什么在获取的时候不需要进行强转?
    因为获取方法里已经转换了return (E) elementData[index];(ArrayList.get源码)
  • ArrayList<String> list1 = new ArrayList();
    ArrayList list2 = new ArrayList<String>();
    泛型的类型检查是检查前边的引用,list2 没有泛型效果
  1. import java.util.ArrayList;
  2. //泛型
  3. //创建泛型类,标注<E>
  4. public class MyGeneric<E> {
  5. //创建静态泛型方法,需要标注<T>,此处的泛型属于该方法
  6. public static <T> void display(T[] arr ) {
  7. for(T t : arr) {
  8. System.out.println(t);
  9. }
  10. }
  11. public static void main(String[] args) {
  12. ArrayList<String> list = new ArrayList<>();
  13. System.out.println(list instanceof ArrayList);
  14. Integer[] arr = {1,2,4,5,6,2,5,75,21};
  15. display(arr);
  16. }
  17. }

泛型中的通配符

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

< ? extends E> 上界通配符,参数化类型可能是E,或者E的子类

< ? super E> 下界通配符,参数类型是E,或者E的父类,直到object

  • ?和 T的区别
    1. T是确定的类型,用于类和方法的定义。?是不确定的类型,通常用于方法的调用代码和形参。
    2. T可以使用&符号来设定多重边界。?不可以
    3. ?可以使用super下界通配符,T不可以

== 和 euqals

对于==:

  1. 基本数据类型比较的是值。
  2. 引用数据类型比较的是地址。

对于equals():

  1. 属于Object类的方法,不重写的情况下等于“==”比较地址,因为是方法所以不能用于基本类型。
  2. 重写后equals的根据重写方法比较,一般会比较值,如String,重写后比较值是否相等。

String 的 equals方法

  1. //String的equals方法源码
  2. public boolean equals(Object anObject) {
  3. //如果两个完全相等,返回true
  4. if (this == anObject) {
  5. return true;
  6. }
  7. //判断传入参数是否为字符串。
  8. if (anObject instanceof String) {
  9. //Object 转 String
  10. String anotherString = (String)anObject;
  11. //获取对象长度
  12. int n = value.length;
  13. //参数与对象长度不一致直接返回false
  14. if (n == anotherString.value.length) {
  15. //使用两个数组存放对象与参数的每个字符
  16. char v1[] = value;
  17. char v2[] = anotherString.value;
  18. int i = 0;
  19. //比较每个字符是否相等
  20. while (n-- != 0) {
  21. if (v1[i] != v2[i])
  22. return false;
  23. i++;
  24. }
  25. return true;
  26. }
  27. }
  28. return false;
  29. }

为什么重写equals时,必须同时重写hashcode方法?

重写hashcode是因为在使用集合时会使用hash值。

不使用集合时,hashcode不会影响equals比较。

java中的hashcode方法是本地方法,使用c或c++实现。 目的是获取哈希码,返回的是一个int型的整数。 转换的是对象的地址。地址—》int hashcode是怎样影响集合插入的? 在使用hashSet时,插入操作首先对对象进行hash操作,然后到哈希表中比较,产生哈希冲突则进行equals判断是否存在相同值。如果只重写equals不重写hashcode,那么hashcode将对象地址进行hash(此处非String和基本数据类型包装类),那么无论如何也不会相等。 只重写hashcode方法而不重写equals方法可行么? 不可以,之重写hashcode没有意义,hashcode和equals方法配合一起解决,在使用集合时的频繁比较的效率问题,hashcode只是用来解决效率问题,主要的比较方法是equals。

hashcode方法和equals方法是为了在集合里添加数据时,避免重复使用的。

通过hashcode方法,减少比较次数,只需要在插入时和哈希表的索引比较一次确定位置。

在出现哈希冲突时,通过equals方法比较值是否存在。

同时重写的时候,必须保证对象相等时hashcode也相同,反之,则不一定,这就要求两个方法在重写的时候最好要保证对同样的属性(或者hashcode包含equals比较的属性)进行比较或者hash。否则在涉及就会出现对象相等而hashcode不同的情况。

如果equals比较name和age

则hashcode至少对name和age中的一个进行hash。才能保证equals相同时,hashcode也是相同的。因为p1和p2相同则name和age都相同,如果hashcode只hash了name,因为name相同,那么hashcode也相同,不违反规则。

如果不对任何属性进行hash,那么相当于没有重写hashcode,不影响equals比较但是影响集合操作。

如果hash的属性多于equals,那么会出现equals相同,但是hashcode不同的情况,违反规则。

基本数据类型及包装类

4类8种

整型:byte,short,int,long

浮点型:float,double

字符型:char

布尔型:boolean

基本类型 位数 字节 默认值
int 32 4 0
short 16 2 0
long 64 8 0L
byte 8 1 0
char 16 2 ‘u0000’
float 32 4 0f
double 64 8 0d
boolean 1 false

自动装箱与拆箱

int(4字节) Integer
byte(1字节) Byte
short(2字节) Short
long(8字节) Long
float(4字节) Float
double(8字节) Double
char(2字节) Character
boolean(未定) Boolean

自动装箱时通过valueOf方法实现。

自动装箱会创建对象,增加内存消耗。

自动拆箱时通过intValue等方法实现

什么时候会触发自动装箱操作?
  1. 包装类对象直接指向基本类型。 Integer i = 5;

什么时候会触发自动拆箱操作?
  1. 当基本数据类型与封装类进行运算时
  2. 当使用“==”比较的双方中存在一方包含算数运算表达式时,封装类一方会自动拆箱,进行值比较。双方都是包装类时,比较地址。
    1. int a = 10;
    2. int b = new Integer(3);
    3. int c = new Integer(7);
    4. int d = new Integer(10);
    5. sout(a == b+c); //ture
    6. sout(d == b+c); //false

包装类和常量池

不同的包装类对常量池又不同的实现。

Byte,Short,Integer,Long存在常量池。范围是【-128,127】

Character存在常量池。范围是【0,127】

Boolean存在常量池。范围是True 或者false。

float和double不存在常量池。

如果超过其对应范围,则会创建新对象。

  1. Integer i1 = 40;
  2. Integer i2 = 40;
  3. Integer i3 = 0;
  4. Integer i4 = new Integer(40);
  5. Integer i5 = new Integer(40);
  6. Integer i6 = new Integer(0);
  7. System.out.println("i1=i2 " + (i1 == i2)); //true, 在缓存范围内,指向相同常量
  8. System.out.println("i1=i2+i3 " + (i1 == i2 + i3)); //true,触发自动拆箱,比较值
  9. System.out.println("i1=i4 " + (i1 == i4)); // false,一个常量地址,一个对象地址
  10. System.out.println("i4=i5 " + (i4 == i5)); //false,两个对象地址
  11. System.out.println("i4=i5+i6 " + (i4 == i5 + i6)); //true,触发自动拆箱,比较值
  12. System.out.println("40=i5+i6 " + (40 == i5 + i6)); //true,触发自动拆箱,比较值

方法

重载与重写

深拷贝与浅拷贝区别

浅拷贝:对基本数据类型进行值传递,对引用类型进行引用传递般的拷贝

深拷贝:对基本类型进行值传递,对引用类型创建一个新的对象,复制内容。

深拷贝和浅拷贝分别何时使用?各自实现方式?

浅拷贝实现方式:

  1. 拷贝构造函数
  2. 重写clone方法

深拷贝实现方式:

  1. 对每一层的每个对象都使用重写clone方法的方式实现浅拷贝
  2. 通过对象序列化实现深拷贝

String

String是如何实现不可变的?为什么要设计成不可变的?

String类中使用字符数组存储字符串,并使用final修饰。final修饰的变量不可重新赋值。在java9之后使用byte数组来存储。

出于安全,效率等原因,

安全如:String类中存储了hashcode,因为要确保相同字符串的hashcode是相同的。

效率:不用创建重复的字符串对象。

字符串并非完全不可变,可以使用反射进行修改。因为String中value使用数组来进行保存,通过反射获取到私有变量进行修改。

String的substring() 方法

  1. String s = "21329jksjdks";
  2. s = s.substring(10);

在jdk7之前切割完的字符串指向的还是原来的地址。只不过修改了字符串开始的下标和字符数量。

引起的问题就是一个特别长的字符串在多次截取后剩余很小一部分,但是内存中还是继续持有特别长的那个字符串,造成内存浪费。

jdk7后改为创造一个新的字符串,指向新的地址。

String,StringBuilder和StringBuffer

每次对String进行修改都会创建新的字符串。

StringBuffer和StringBuilder修改时不会创建新对象,其中的char数组没有使用final修饰是可变对象。

StringBuffer在操作字符串时会加锁,所以是线程安全的,Stringbuilder不是线程安全,但是效率高于StringBuffer

面向对象

封装

把对象的状态信息隐藏在对象内部,不允许外部直接访问。但是提供可以被外界访问的方法。

目的:隐藏具体实现

继承

提取不同类的共同点,抽象成一个父类,子类继承父类中所有子类的共同特性。

目的:方便快速创建新类,提高开发效率,提高复用率。

多态

一个对象有多种状态。具体表现为父类的引用指向子类实例。

三要素:继承,重写,向上转型

  1. 继承:因为多态是父类引用指向子类对象,所以必须存在继承关系才能实现多态。
  2. 重写:重写父类方法能够实现不同子类的不同形态
  3. 向上转型是指子类会丢失父类中不存在(也就是子类中特有的)的属性和方法。在调用父类中存在的方法时,如果存在会执行子类的重写方法。
  • 方法调用的优先级:

该优先级为:this.show(O)、super.show(O)、this.show((super)O)、super.show((super)O)。

经典多态笔试题

  1. public class A {
  2. public String show(D obj) {
  3. return ("A and D");
  4. }
  5. public String show(A obj) {
  6. return ("A and A");
  7. }
  8. }
  9. public class B extends A{
  10. public String show(B obj){
  11. return ("B and B");
  12. }
  13. public String show(A obj){
  14. return ("B and A");
  15. }
  16. }
  17. public class C extends B{
  18. }
  19. public class D extends B{
  20. }
  21. public class Test {
  22. public static void main(String[] args) {
  23. A a1 = new A();
  24. A a2 = new B();
  25. B b = new B();
  26. C c = new C();
  27. D d = new D();
  28. System.out.println("1--" + a1.show(b));
  29. System.out.println("2--" + a1.show(c));
  30. System.out.println("3--" + a1.show(d));
  31. System.out.println("4--" + a2.show(b));
  32. System.out.println("5--" + a2.show(c));
  33. System.out.println("6--" + a2.show(d));
  34. System.out.println("7--" + b.show(b));
  35. System.out.println("8--" + b.show(c));
  36. System.out.println("9--" + b.show(d));
  37. }
  38. }

输出:

  1. --A and A
  2. --A and A
  3. --A and D
  4. --B and A
  5. --B and A
  6. --A and D
  7. --B and B
  8. --B and B
  9. --A and D
  10. // 在简单来说,以父类引用指向子类对象的形式中,虽然是创建了子类的对象,但是实际子类能够调用的函数必须是子类和父类都有的函数。

在向上转型时,调用不到子类特有的方法。调用的方法是父类中存在的方法,但是实现是子类中重写的实 现 向下转型是相对于向上转型存在的,自我理解向下转型是基于向上转型而存在的,因为先有父类引用指向子类对象是向上,再把父类的引用赋值给子类引用是向下。 向下转型需要强转。 Anmail a = new Dog(); //向上转型 Dog d = (Dog)a; //向下转型

接口

接口是一种特殊的抽象类,接口中的方法全部是抽象方法(但其前的abstract可以省略),所以抽象类中的抽象方法不能用的访问修饰符这里也不能用。而且protected访问修饰符也不能使用,因为接口可以让所有的类去实现(非继承),不只是其子类,但是要用public去修饰。接口可以去继承一个已有的接口。

类、方法、成员变量和局部变量的可用修饰符

修饰符 成员访求 构造方法 成员变量 局部变量
abstract(抽象的)
static (静态的)
public(公共的)
protected(受保护的)
private(私有的)
synchronized(同步的)
native(本地的)
transient(暂时的)
volatie(易失的)
final(不要改变的)
类 修饰符
Public 可以从其他类中访问
Abstract 本类不能被实例化
Final 不能再声明子类
构造函数修饰符
Public 可以从所有的类中访问
Protected 只能从自己的类和它的子类中访问
Private 只能在本类中访问
域/成员变量修饰符
Public 可以从所有的类中访问
Protected 只能从本类和它的子类中访问
Private 只能从本类中访问它
Static 对该类的所有实例只能有一个域值存在
transient 不是一个对象持久状态的一部份
Volatile 可以被异步的线程所修改
final 必须对它赋予初值并且不能修改它
局部变量 修饰符
final 必须对它赋予初值并且不能修改它
方法修饰符
Public 可以从所有的类中访问它
Protected 只能从本类及其子类中访问它
Private 只能从本类中访问它
abstract 没有方法体,属于一个抽象类
final 子类不能覆盖它
static 被绑定于类本身而不是类的实例
native 该方法由其他编程语言实现
asnchronized 在一个线程调用它之前必须先给它加

类的修饰符整合

一.类

类的修饰符:

Public:可以在其他任何类中使用,默认为统一包下的任意类。

Abstract:抽象类,不能被实例化,可以包含抽象方法,抽象方法没有被实现,无具体功能,只能衍生子类。

Final:不能被继承。

二.变量

变量修饰符:

一个类的成员变量的声明必须在类体中,而不能在方法中,方法中声明的是局部变量

  1. 可访问修饰符:
  2. static类变量:一个类所拥有的变量,不是类的每个实例有的变量。类变量是指不管类创建了多少对象,系统仅在第一次调用类的时候为类变量分配内存,所有对象共享该类的类变量,因此可以通过类本身或者某个对象来访问类变量。
  3. final常量
  4. volatile:声明一个可能同时被并存运行的几个线程所控制和修改的变量。

实例变量:和类变量对应,即每个对象都拥有各自独立的实例变量。

三.方法:(和变量对象分为实例方法和类方法,并用有无static修饰区别)

类方法:使用static关键字说明的方法

1.第一次调用含类方法的类是,系统只为该类创建一个版本,这个版本被该类和该类的所有实例共享。

2.类方法只能操作类变量,不能访问实例变量。类方法可以在类中被调用,不必创建实例来调用,当然也可以通过对象来调用。

实例方法:实例方法可以对当前对象的实例变量操作,而且可以访问类变量。

方法可以重载,要求:方法名相同,但是参数必须有区别。(参数不同可以使类型不同,顺序不同,个数不同)

方法的返回类型:若无返回类型,则声明为void.

方法中的变量作用域:

  1. 成员变量:整个类。
  2. 局部变量:定义起到方法块结束为止。
  3. 方法参数:整个方法或者构造方法。
  4. 异常处理参数:参数传递给异常处理方法。

构造方法:和类同名的方法。为新建对象开辟内存空间后,用于初始化新建的对象。不能用对象显式的调用。

静态初始化器:格式:static{<赋值语句组>}

静态初始化器与构造方法的区别:

静态初始化器 构造方法
对类的静态域初始化 对新建的对象初始化
类进入内存后,系统调用执行 执行new后自动执行
属特殊语句(仅执行一次) 属特殊方法

方法的修饰符:

抽象方法:用abstract修饰,只有声明部分,方法体为空,具体在子类中完成。

类方法:静态方法,用static修饰,

  1. 调用时,使用类名作为前缀,而不是类的某个实例对象名
  2. 不能被单独对象拥有,属于整个类共享。
  3. 不能处理成员变量。

最终方法:用final修饰,不能被子类重新定义的方法。

本地方法:用native修饰的方法,表示用其他语言书写的特殊方法,包括C,C++,FORTRAN,汇编语言等。

四.类成员的访问控制符

即类的方法和成员变量的访问控制符,一个类作为整体对象不可见,并不代表他的所有域和方法也对程序其他部分不可见,需要有他们的访问修饰符判断。

权限如下:

访问修饰符 同一个类 同包 不同包,子类 不同包,非子类
private
protected
public
默认