Android 知识整理(一、Java 基础知识)

一些 Java 基础知识的问题。

Java 中 == 和 equals() 和 hashCode() 的区别

  • 对于基本数据类型,== 是比较这两个基本数据类型的值
  • 对于对象,== 是比较两个对象的内存地址。equals() 是所有对象 Object 基类的一个方法,默认实现是比较两个对象的内存地址,在对于 Java 的有些类中重写了这个方法,内部使用了不同的逻辑,如 String 类的 equals() 方法是比较两个字符串是否相同。
  • hashCode()是计算一个对象的 hash 值,用在 HashMap 等对比冲突的地方
    • 相等(相同)的对象必须具有相等的哈希码(或者散列码)。
    • 如果两个对象的hashCode相同,它们并不一定相同。

扩展:Integer 等包装类,会有一个128大小的自有缓冲区,当一个新的 Integer 被创建时,它会先去缓冲区寻找,找到值会直接赋值给这个对象,所以两个 Integer 的值在这个缓冲区是,它们的 == 方法虽然是比较它们的内存地址,但是返回是 true。

String 对象比较特殊,是不可变的字符串,当有新的对象创建使用 String s = "Hello World.";时,会去缓冲区去找是否有相同的字符串,如果有就直接这个内存地址赋给这个对象。但是使用String s = new String("Hello World.");会直接开辟一块新的内存地址。

int、char、long各占多少字节数

int 与 Integer 的区别

int 是 Java 的基本数据类型,具体请看 int 与 Integer

谈谈对 Java 多态的理解

Java 面向对象的三大特性:继承、封装、多态。

  • 封装:封装私有变量,创建公共方法供外部调用,实现了访问控制,让客户端程序员无法触及不该触及的部分,创建者通过内外分离不担心自己的修改影响了外部调用。
  • 继承:
  • 多态:指允许不同类的对象对同一消息做出响应。即同一消息可以根据发送对象的不同而采用多种不同的行为方式。

实现多态的技术成为:动态绑定

String、StringBuffer、StringBuilder区别

  • String 字符串常量
    是不可变的对象, 因此在每次对 String 类型进行改变的时候其实都等同于生成了一个新的 String 对象,然后将指针指向新的 String 对象,所以经常改变内容的字符串最好不要用 String
  • StringBuilder 字符串变量(非线程安全)
    可变对象
  • StringBuffer 字符串变量(线程安全)
    append()相比起 StringBuilder 来说多了一个 synchronized 关键字,会执行线程同步。

为啥 Java 中 String 要设计成不可变的对象

  1. 字符串常量池的需要:当创建一个String对象时,假如此字符串值已经存在于常量池中,则不会创建一个新的对象,而是引用已经存在的对象。
  2. 安全性:String被许多的Java类(库)用来当做参数或者key,假若String不是固定不变的,将会引起各种安全隐患,容易引起中间篡改。
  3. 允许String对象缓存HashCode:字符串不变性保证了hash码的唯一性,因此可以放心地进行缓存.这也是一种性能优化手段,意味着不必每次都去计算新的哈希码.

什么是内部类?内部类的作用

内部类是在类中声明的一个类

内部类的 new 方法:

1
2
OuterClass outerObject = new OuterClass();
outerClass.InnerClass innerObject = outerObject.new InnerClass();

内部类的性质

  • 内部类提供了更好的封装,除了该外围类,其他类都不能访问。

当外部类的对象创建了一个内部类的对象时,内部类对象必定会秘密捕获一个指向外部类对象的引用,然后访问外部类的成员时,就是用那个引用来选择外围类的成员的。

  • 内部类可以直接访问外部类的属性和方法
  • 每个内部类都能独立地继承一个(接口的)实现
  • 内部类可以用多个实例,每个实例都有自己的状态信息,并且与其他外围对象的信息相互独立。
  • 创建内部类对象的时刻必须先创建了外部类之后才能创建,但之后并不依赖于外围类对象的创建。
  • 成员内部类中不能存在任何 static 的变量和方法,(因为1、static类型的属性和方法,在类加载的时候就会存在于内存中。2、要使用某个类的static属性或者方法,那么这个类必须要加载到jvm中。内部类在外部类创建时没有加载,因此不能使用其 static 属性和方法),除非内部类是 static 类。

此种不能同时存在的类均与 Java 的生命周期相关,内部类的生命周期是从外内部类创建之后才有的,但是静态属性是随着类的加载的,这是相互矛盾的。当内部类设为静态类时生命周期就一致了

静态内部类

  • 静态内部类:
  • 它的创建是不需要依赖于外围类的。
  • 它不能使用任何外围类的非static成员变量和方法。

匿名内部类

  • 可以简化代码。
  • 匿名内部类是没有访问修饰符的。
  • 匿名内部类是没有构造方法的。
  • 匿名内部类中不能存在任何静态成员或方法。

内部类的作用

  • 使用内部类可以间接实现多重继承
  • 提供了更好的封装,只有外部类能访问内部类,其他类不能直接访问内部类(即是内部类是public 的也只能通过其承载的外部类访问)

抽象类和接口区别

抽象类

  • 抽象类可以有自己的方法实现:接口只能定义方法,是抽象的。
  • 抽象类中子类使用 extends 来继承抽象类,如果子类不是抽象类,它需要实现所有的抽象方法:接口中子类使用关键字implements来实现接口。它需要提供接口中所有声明的方法的实现
  • 抽象类可以有构造器:
抽象类 接口
继承 子类使用extends关键字来继承抽象类。如果子类不是抽象类的话,它需要提供抽象类中所有声明的方法的实现。 子类使用关键字implements来实现接口。它需要提供接口中所有声明的方法的实现
构造器 抽象类可以有构造器 接口不能有构造器
与实体Java类的区别 除了你不能实例化抽象类之外,它和普通Java类没有任何区别 接口是完全不同的类型
访问修饰符 抽象方法可以有public、protected和default这些修饰符 接口方法默认修饰符是public。你不可以使用其它修饰符。
main方法 抽象方法可以有main方法并且我们可以运行它 接口没有main方法,因此我们不能运行它。
多继承 抽象方法可以继承一个类和实现多个接口 接口只可以继承一个或多个其它接口
速度 它比接口速度要快 接口是稍微有点慢的,因为它需要时间去寻找在类中实现的方法。
添加新方法 如果你往抽象类中添加新的方法,你可以给它提供默认的实现。因此你不需要改变你现在的代码。 如果你往接口中添加方法,那么你必须改变实现该接口的类。
默认的方法实现 它可以有默认的方法实现 接口完全是抽象的。它根本不存在方法的实现

抽象类的意义

  1. 为子类提供一个公共的类型;
  2. 封装子类中重复内容(成员变量和方法);
  3. 定义有抽象方法,子类虽然有不同的实现,但该方法的定义是一致的。

抽象类与接口的应用场景

抽象类

  • 如果想定义一些公共方法并给其中一些方法默认实现,使用抽象类;
  • 抽象类是用来捕捉子类的通用特性的,一般作为类的基类,在其中添加了一些方法不回影响到所有子类(子类不需要全部覆盖);

接口

  • 多重继承只能使用接口;
  • 作为两个类的通信协议,传递一些信息或者数据时,定义接口;

抽象类是否可以没有方法和属性?

可以没有方法和属性。
抽象类的作用在于子类对其的继承和实现,也就是多态;而没有抽象方法的抽象类的存在价值在于:实例化了没有意义,因为类已经定义好了,不能改变其中的方法体,但是实例化出来的对象却满足不了要求,只有继承并重写了他的子类才能满足要求。所以才把它定义为没有抽象方法的抽象类

  • 抽象类专用于派生出子类,子类必须实现抽象类所声明的抽象方法,否则,子类仍是抽象类。
  • 包含抽象方法的类一定是抽象类,但抽象类中的方法不一定是抽象方法。

接口的意义

  • 接口体现了抽象的特点,抽象的定义就是抽取像的部分,可以提取一些类的共性。
  • 接口并不负责具体的操作,具体的操作是由接口的实现类完成的
  • 接口具有简单,规范性:实现相同接口的类可以替换,他们遵循相同的规范,实现了相同的功能。
  • 接口具维护、拓展性:在之后代码中可以随时替换类,只要这些类实现了相同的接口,接口具有规范性
  • 接口具有安全、严密性:在两个类进行通信的时候,并不公开全部的内部代码,只要通过接口进行通信即可

泛型中extends和super的区别

简单来说,<? super T>表示包括T在内的任何T的父类,<? extends T>表示包括T在内的任何T的子类。

  • 只能用于方法返回,告诉编译器此返参的类型的最小继承边界为T,T和T的父类都能接收,但是入参类型无法确定,只能接受null的传入
  • 只能用于限定方法入参,告诉编译器入参只能是T或其子类型,而返参只能用Object类接收
  • ? 既不能用于入参也不能用于返参

父类的静态方法能否被子类重写

不可以。
**所谓静态就是指:在编译之后所分配的内存会一直存在(不会被回收),直到程序退出内存才会释放这个空间。
**
静态方法只与类有关,不与实例有关,重写只适用于实例方法,不适用于静态方法。

因为静态方法是程序一运行就已经分配好了内存地址,而且该地址是固定的,所有引用到该方法的对象(父类或者子类)所指向的始终是同一个内存地址中的数据,即该静态方法。如果子类定义了相同名称的静态方法,只会新增一个内存地址,并不会重写。

进程和线程的区别

进程和线程都是一个时间段的描述,是CPU工作时间段的描述。

简而言之,一个程序至少有一个进程,一个进程至少有一个线程.
线程的划分尺度小于进程,使得多线程程序的并发性高。
另外,进程在执行过程中拥有独立的内存单元,而多个线程共享内存,从而极大地提高了程序的运行效率。
线程在执行过程中与进程还是有区别的。每个独立的线程有一个程序运行的入口、顺序执行序列和程序的出口。但是线程不能够独立执行,必须依存在应用程序中,由应用程序提供多个线程执行控制。
从逻辑角度来看,多线程的意义在于一个应用程序中,有多个执行部分可以同时执行。但操作系统并没有将多个线程看做多个独立的应用,来实现进程的调度和管理以及资源分配。这就是进程和线程的重要区别。

进程是具有一定独立功能的程序关于某个数据集合上的一次运行活动,进程是系统进行资源分配和调度的一个独立单位.
线程是进程的一个实体,是CPU调度和分派的基本单位,它是比进程更小的能独立运行的基本单位.线程自己基本上不拥有系统资源,只拥有一点在运行中必不可少的资源(如程序计数器,一组寄存器和栈),但是它可与同属一个进程的其他的线程共享进程所拥有的全部资源.
一个线程可以创建和撤销另一个线程;同一个进程中的多个线程之间可以并发执行.

进程包括自己独立的地址空间、堆栈和局部变量,线程共享进程的地址空间、堆栈和局部变量。
进程切换的时候需要切换上下文,耗费资源较大。

final,finally,finalize的区别

final

用于申明属性,方法和类,表示属性不可变,方法不可以被覆盖,类不可以被继承。

  • 变量
    final修饰变量表示该变量是不可变的。当指向一个对象时:指向的内存地址是不变的,但是被指的内存地址中的类是可以改变的。
  • 方法
    final修饰方法时表示该方法是不能被子类重写的。

  • final修饰类时表示该类是不能被继承的,由于java的单继承关系,所以该类是继承关系链中的终端。

接口中声明的所有变量都是final的;

finally

是异常处理语句结构中,表示总是执行的部分。  
具体请看 try、catch、finally、throw和throws

finalize

表示是object类一个方法,在垃圾回收机制中执行的时候会被调用被回收对象的方法。允许回收此前未回收的内存垃圾。所有object都继承了 finalize() 方法

序列化的方式

将序列化的数据实现 Serializable/Parcelable 接口,通过 Intent.putExtra 或者 Binder 传递数据。

Serializable 和 Parcelable 的区别

Serializable 接口

实现非常简单,出了需要继承 Serializable 接口外只要创建一个 serialVersionUID 属性就好。

serialVersionUID

serialVersionUID 在运行时唯一标识了一个可序列化的类。一个类序列化时,运行时会保存它的版本号,然后在反序列化时检查你要反序列化成的对象版本号是否一致,不一致的话就会报错:InvalidClassException。

序列化和反序列化

Serializable 的序列化与反序列化分别通过 ObjectOutputStream 和 ObjectInputStream 进行,实例代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
/**
* 序列化对象
*
* @param obj
* @param path
* @return
*/
synchronized public static boolean saveObject(Object obj, String path) {
if (obj == null) {
return false;
}
ObjectOutputStream oos = null;
try {
oos = new ObjectOutputStream(new FileOutputStream(path));
oos.writeObject(obj);
oos.close();
return true;
} catch (IOException e) {
e.printStackTrace();
} finally {
if (oos != null) {
try {
oos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
return false;
}

/**
* 反序列化对象
*
* @param path
* @param <T>
* @return
*/
@SuppressWarnings("unchecked ")
synchronized public static <T> T readObject(String path) {
ObjectInputStream ojs = null;
try {
ojs = new ObjectInputStream(new FileInputStream(path));
return (T) ojs.readObject();
} catch (IOException | ClassNotFoundException e) {
e.printStackTrace();
} finally {
close(ojs);
}
return null;
}

Parcelable 接口

Parcelable 是 Android 特有的序列化方式
实现 Parcelable 的方法如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
public class ParcelClass implements Parcelable {
public String name;
public int id;

/**
* 系统自动添加,给createFromParcel里面用
* @param in
*/
protected ParcelClass(Parcel in) {
name = in.readString();
id = in.readInt();
}

/**
* 序列话数据
* @param dest
* @param flags
*/
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(name);
dest.writeInt(id);
}

// 内容接口描述,默认返回0即可。
@Override
public int describeContents() {
return 0;
}

public static final Creator<ParcelClass> CREATOR = new Creator<ParcelClass>() {
/**
*
* @param in
* @return
* createFromParcel()方法中我们要去读取刚才写出的name和age字段,
* 并创建一个Person对象进行返回,其中 name 和 id 都是调用Parcel的readXxx()方法读取到的,
* 注意这里读取的顺序一定要和刚才写出的顺序完全相同。
* 读取的工作我们利用一个构造函数帮我们完成了
*/
@Override
public ParcelClass createFromParcel(Parcel in) {
return new ParcelClass(in);
}

//供反序列化本类数组时调用的
@Override
public ParcelClass[] newArray(int size) {
return new ParcelClass[size];
}
};
}

区别

  • 编码上:

Serializable代码量少,写起来方便
Parcelable代码多一些

  • 效率上:

Parcelable的速度比高十倍以上。
Serializable 这种方法的缺点是使用了反射,序列化的过程较慢。这种机制会在序列化的时候创建许多的临时对象,容易触发垃圾回收。

静态属性和静态方法是否可以被继承?是否可以被重写?以及原因?

可以被继承,不可以被重写。

  1. 静态方法和属性是属于类的,调用的时候直接通过类名.方法名完成对,不需要继承机制即可以调用。如果子类里面定义了静态方法和属性,那么这时候父类的静态方法或属性称之为”隐藏”。如果你想要调用父类的静态方法和属性,直接通过父类名.方法或变量名完成,至于是否继承一说,子类是有继承静态方法和属性,但是跟实例方法和属性不太一样,存在”隐藏”的这种情况。
  2. 多态之所以能够实现依赖于继承、接口和重写、重载(继承和重写最为关键)。有了继承和重写就可以实现父类的引用指向不同子类的对象。重写的功能是:”重写”后子类的优先级要高于父类的优先级,但是“隐藏”是没有这个优先级之分的。
  3. 静态属性、静态方法和非静态的属性都可以被继承和隐藏而不能被重写,因此不能实现多态,不能实现父类的引用可以指向不同子类的对象。非静态方法可以被继承和重写,因此可以实现多态。

静态内部类的设计意图

  • 它的创建是不需要依赖于外围类的。
  • 它不能使用任何外围类的非static成员变量和方法。

成员内部类、静态内部类、局部内部类和匿名内部类的理解,以及项目中的应用

具体请看 内部类

谈谈对kotlin的理解

//TODO

闭包和局部内部类的区别

局部内部类就像是方法里面的一个局部变量一样,是不能有public、protected、private以及static修饰符的。

String 转换成 integer的方式及原理

进过一系列判断,最后负着一直加首位的数,再乘以十,最后负的返回原值,正的返回相反数