Android复习之Java基础

119 阅读13分钟

1.重载和重写的区别

  • 重写是一般是用于子类在继承父类时,重写(重新实现)父类中的方法。
  • 重载是一般是用于在一个类内实现若干重载的方法,这些方法的名称相同而参数形式不同。

2.接口是否可继承接口?抽象类是否可实现接口?抽象类是否可继承具体类?

接口可以继承接口,而且支持多重继承。抽象类可以实现(implements)接口,抽象类可继承具体类也可以继承抽象类。

3.访问修饰符 public,private,protected,以及不写(默认)时的区别

4.String 是最基本的数据类型吗?

不是。Java 中的基本数据类型只有 8 个 :byte、short、int、long、float、double、char、boolean;除了基本类型(primitive type),剩下的都是引用类型(referencetype),Java 5 以后引入的枚举类型也算是一种比较特殊的引用类型。

5.是否可以继承 String 类?

String 类是 final 类,不可以被继承。

补充:继承 String 本身就是一个错误的行为,对 String 类型最好的重用方式是关联关系(Has-A)和依赖关系(Use-A)而不是继承关系(Is-A)

6.String 和 StringBuilder、StringBuffer 的区别?

String 是只读字符串,也就意味着 String 引用的字符串内容是不能被改变的。而 StringBuffer/StringBuilder 类表示的字符串对象可以直接进行修改。

  • String 只读不写,不可被继承
  • StringBuilder 可读可写,线程不安全的,速度快。
  • StringBuffer 可读可写,线程安全的,速度慢。

7.char 型变量中能不能存贮一个中文汉字,为什么?

char 类型可以存储一个中文汉字,因为 Java 中使用的编码是 Unicode(不选择任何特定的编码,直接使用字符在字符集中的编号,这是统一的唯一方法),一个 char 类型占 2 个字节(16 比特),所以放一个中文是没问题的。

8.String s = new String(“xyz”);创建了几个字符串对象?

两个对象,一个是静态区的”xyz”,一个是用 new 创建在堆上的对象。

9.Java 中的 final 关键字有哪些用法?

  1. 修饰类:表示该类不能被继承;
  2. 修饰方法:表示方法不能被重写;
  3. 修饰变量:表示变量只能一次赋值以后值不能被修改(常量)。

10.阐述 final、finally、finalize 的区别。

  • final:修饰符(关键字)有三种用法:如果一个类被声明为 final,意味着它不能再派生出新的子类,即不能被继承,因此它和 abstract 是反义词。将变量声明为 final,可以保证它们在使用中不被改变,被声明为 final 的变量必须在声明时给定初值,而在以后的引用中只能读取不可修改。被声明为 final 的方法也同样只能使用,不能在子类中被重写。
  • finally:通常放在 try…catch…的后面构造总是执行代码块,这就意味着程序无论正常执行还是发生异常,这里的代码只要 JVM 不关闭都能执行,可以将释放外部资源的代码写在 finally 块中.
  • finalize:Object 类中定义的方法,Java 中允许使用 finalize()方法在垃圾收集器将对象从内存中清除出去之前做必要的清理工作。这个方法是由垃圾收集器在销毁对象时调用的,通过重写 finalize()方法可以整理系统资源或者执行其他清理工作。

11.List、Set、Map 是否继承自 Collection 接口,它们的区别?

List、Set 是 ,Map 不是。Map 是键值对映射容器,与 List 和 Set 有明显的区别:

  • Set无序、不能重复
  • List有序,可以重复
  • Map无序,Key不能重复,Value能重复

12.Collection 和 Collections 的区别?

Collection 是一个接口,它是 Set、List 等容器的父接口;Collections 是个一个工具类,提供了一系列的静态方法来辅助容器操作,这些方法包括对容器的搜索、排序、线程安全化等等。

13.ArrayList、Vector、LinkedList 的区别?

  • ArrayList 数组结构,非线程安全,内存利用率差,查找快,增删慢;
  • Vector 数组结构,线程安全,内存利用率差,查找快,增删慢;
  • LinkedList 双向链表结构,非线程安全的,内存的利用率更高,查找慢,增删快;

14.创建线程的方式

  1. 继承Thread类,重写run()方法
  2. 实现Runnable接口,并实现该接口的run()方法
  3. 实现Callable接口,重写call()方法

Callable接口实际是属于Executor框架中的功能类,Callable接口与Runnable接口的功能类似,但提供了比Runnable更强大的功能:

public class TestCallable {  
    //创建线程类
    public static class MyTestCallable  implements Callable {  
        public String call() throws Exception {  
            retun "Hello World";
        }  
    }  
    public static void main(String[] args) {  
        MyTestCallable mMyTestCallable= new MyTestCallable();  
        ExecutorService mExecutorService = Executors.newSingleThreadPool();  
        Future mfuture = mExecutorService.submit(mMyTestCallable);  
        try { 
            //等待线程结束,并返回结果
            System.out.println(mfuture.get());  
        } catch (Exception e) {  
            e.printStackTrace();
        } 
    }  
} 

15.线程的状态

  1. 新建状态(New):新创建了一个线程对象。
  2. 就绪状态(Runnable):线程对象创建后,其他线程调用了该对象的start()方法。该状态的线程位于可运行线程池中,变得可运行,等待获取CPU的使用权。
  3. 运行状态(Running):就绪状态的线程获取了CPU,执行程序代码。
  4. 阻塞状态(Blocked):阻塞状态是线程因为某种原因放弃CPU使用权,暂时停止运行。直到线程进入就绪状态,才有机会转到运行状态。阻塞的情况分三种:
  • 等待阻塞:运行的线程执行wait()方法,JVM会把该线程放入等待池中。
  • 同步阻塞:运行的线程在获取对象的同步锁时,若该同步锁被别的线程占用,则JVM会把该线程放入锁池中。
  • 其他阻塞:运行的线程执行sleep()或join()方法,或者发出了I/O请求时,JVM会把该线程置为阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态。
  1. 死亡状态(Dead):线程执行完了或者因异常退出了run()方法,该线程结束生命周期。

16.sleep和wait的区别

  1. sleep是线程中的方法,但是wait是Object中的方法。
  2. sleep方法不依赖于同步器synchronized,但是wait需要依赖synchronized关键字。
  3. sleep方法不会释放lock,但是wait会释放,而且会加入到等待队列中。
  4. sleep不需要被唤醒(休眠之后推出阻塞),但是wait需要(不指定时间需要被别人中断)。

17.Synchronized同步方法和同步代码块

  • 同步方法的锁对象是this
  • 同步静态方法的锁对象是类.class

对象锁: synchronized(object) 锁住的是对象,每个对象自己拥有一个锁
类锁: synchronized(Class) 锁住的是类,也就是同一个类的实例,任意时刻只会有一个线程能获得资源

18.Java 中 ++ 操作符是线程安全的吗?

不是线程安全的操作。它涉及到多个指令,如读取变量值,增加,然后存储回内存,这个过程可能会出现多个线程交差。

19.a = a + b 与 a += b 的区别

+= 隐式的将加操作的结果类型强制转换为持有结果的类型。如果两这个整型相加,如 byte、short 或者 int,首先会将它们提升到 int 类型,然后在执行加法操作。如果加法操作的结果比 a 的最大值要大,则 a+b 会出现编译错误,但是

byte a = 127; byte b = 127; 
b = a + b; // error : cannot convert from int to byte
b += a; // ok

(译者注:这个地方应该表述的有误,其实无论 a+b 的值为多少,编译器都会报错,因为 a+b 操作会将 a、b 提升为 int 类型,所以将 int 类型赋值给 byte就会编译出错)

20.int 和 Integer 哪个会占用更多的内存?

Integer 对象会占用更多的内存。Integer 是一个对象,需要存储对象的元数据。但是 int 是一个原始类型的数据,所以占用的空间更少。

21.“a==b”和”a.equals(b)”有什么区别?

如果 a 和 b 都是对象,则 a==b 是比较两个对象的引用,只有当 a 和 b 指向的是堆中的同一个对象才会返回 true,而 a.equals(b) 是进行逻辑比较,所以通常需要重写该方法来提供逻辑一致性的比较。例如,String 类重写 equals() 方法,所以可以用于两个不同对象,但是包含的字母相同的比较。

22.a.hashCode() 有什么用?与 a.equals(b) 有什么关系?

hashCode() 方法是相应对象整型的 hash 值。它常用于基于 hash 的集合类,如 Hashtable、HashMap、LinkedHashMap 等等。它与 equals() 方法关系特别紧密。根据 Java 规范,两个使用 equal() 方法来判断相等的对象,必须具有相同的 hash code。

  • 两个obj,如果equals()相等,hashCode()一定相等。
  • 两个obj,如果hashCode()相等,equals()不一定相等(Hash散列值有冲突的情况,虽然概率很低)。

23.浅拷贝和深拷贝的区别?

  • 浅拷贝:在拷贝一个对象时,对对象的基本数据类型的成员变量进行拷贝,但对引用类型的成员变量只进行引用的传递,并没有创建一个新的对象,当对引用类型的内容修改会影响被拷贝的对象。

实现浅拷贝的方法:

java中clone方法是一个浅拷贝,引用类型依然在传递引用

  • 深拷贝:在拷贝一个对象时,除了对基本数据类型的成员变量进行拷贝,对引用类型的成员变量进行拷贝时,创建一个新的对象来保存引用类型的成员变量。

实现深拷贝有两种方法:

(1)序列化该对象,然后反序列化回来,就能得到一个新的对象了。
序列化:将对象写入到IO流中; 反序列化:从IO流中恢复对象 序列化机制允许将实现序列化的java对象转化为字节序列,这些字节序列可以保存到磁盘或者网络传输上,以达到以后恢复成原来的对象,序列化机制使得对象可以脱离程序的运行而独立存在。

(2)继续利用clone()方法,对该对象的引用类型变量再实现一次clone()方法。

24.Java四种引用

引用
强引用(Strongly Re-ference)不会被回收正常编码使用
软引用(Soft Reference)内存不够了,被GC可作为缓存
弱引用(Weak Reference)GC发生时可作为缓存(WeakHashMap)
虚引用(Phantom Reference)任何时候监控对象回收,记录日志

25.ArrayList扩容机制

//我们知道位运算的速度远远快于整除运算,整句运算式的结果就是将新容量更新为旧容量的1.5倍,
int newCapacity = oldCapacity + (oldCapacity >> 1);

26.为什么会设计String类?

字符串常量池:

String 不可变的第一个好处是可以使用字符串常量池。在 Java 中有字符串常量池的概念,比如两个字符串变量的内容一样,那么就会指向同一个对象,而不需创建第二个同样内容的新对象,例如:

String s1 = "lagou";

String s2 = "lagou";

其实 s1 和 s2 背后指向的都是常量池中的同一个“lagou”,如下图所示:

在图中可以看到,左边这两个引用都指向常量池中的同一个“lagou”,正是因为这样的机制,再加上 String 在程序中的应用是如此广泛,我们就可以节省大量的内存空间

用作 HashMap 的 key:

String 不可变的第二个好处就是它可以很方便地用作 HashMap (或者 HashSet) 的 key。通常建议把不可变对象作为 HashMap的 key,比如 String 就很合适作为 HashMap 的 key。

缓存 HashCode:

String 不可变的第三个好处就是缓存 HashCode

在 Java 中经常会用到字符串的 HashCode,在 String 类中有一个 hash 属性,代码如下:

/** Cache the hash code for the String */
 private int hash;

这是一个成员变量,保存的是 String 对象的 HashCode。因为 String 是不可变的,所以对象一旦被创建之后,HashCode 的值也就不可能变化了,我们就可以把 HashCode 缓存起来。

这样的话,以后每次想要用到 HashCode 的时候,不需要重新计算,直接返回缓存过的 hash 的值就可以了,因为它不会变,这样可以提高效率,所以这就使得字符串非常适合用作 HashMap 的 key。

而对于其他的不具备不变性的普通类的对象而言,如果想要去获取它的 HashCode ,就必须每次都重新算一遍,相比之下,效率就低了。

线程安全:

String 不可变的第四个好处就是线程安全,因为具备不变性的对象一定是线程安全的,我们不需要对其采取任何额外的措施,就可以天然保证线程安全。

由于 String 是不可变的,所以它就可以非常安全地被多个线程所共享,这对于多线程编程而言非常重要,避免了很多不必要的同步操作。

27.装箱和拆箱

装箱:把基本数据类型转为包装类对象。

转为包装类的对象,是为了使用专门为对象设计的API和特性

拆箱:把包装类对象拆为基本数据类型。

转为基本数据类型,一般是因为需要运算,Java中的大多数运算符是为基本数据类型设计的。比较、算术等


装箱: 基本数值---->包装对象

Integer obj1 = new Integer(4);//使用构造函数函数
Integer obj2 = Integer.valueOf(4);//使用包装类中的valueOf方法

拆箱: 包装对象---->基本数值

Integer obj = new Integer(4);
int num1 = obj.intValue();

28.为什么要进行拆箱和装箱?

Java是一种完全面向对象的语言。因此,包括数字、字符、日期、布尔值等等在内的一切,都是对象。似乎只需要一种方式来对待这些对象就可以了。对于CPU来说,处理一个完整的对象,需要很多的指令,对于内存来说,又需要很多的内存。如果连整数都是对象,那么性能自然很低。

于是创造了这样一种机制,使得这些基本类型在一般的编程中被当作非对象的简单类型处理,在另一些场合,又允许它们被视作是一个对象。

这就是装箱和拆箱。

作用:为了保证通用性和提高系统性能

一种最普通的场景是调用一个包含类型为Object的参数的函数(方法),该Object可支持任意 类型,以便通用。当你需要将一个值类型传入容器时,就需要装箱了。

另一种的用法,就是一个泛型 的容器,同样是为了保证通用,而将元素定义为Object类型的,将值类型的值加入该容器时,需要装箱。