面试相关--JAVA基础部分

1.Java中的原始数据类型都有哪些,它们的大小及对应的封装类是什么?

数据类型大小(字节)内存空间(8位等于1字节)包装类
byte18位Byte
char216位Character
short216位Short
int432位Integer
long864位Long
float432位Float
double864位Double
booleanBoolean
关于boolean的说明:

boolean数据类型非true即false。这个数据类型表示1 bit的信息,但是它的大小并没有精确定义。
《Java虚拟机规范》中如是说:“虽然定义了boolean这种数据类型,但是只对它提供了非常有限的支持。在Java虚拟机中没有任何供boolean值专用的字节码指令,Java语言表达式所操作的boolean值,在编译之后都使用Java虚拟机中的int数据类型来代替,而boolean数组将会被编码成Java虚拟机的byte数组,每个元素boolean元素占8位”。这样我们可以得出boolean类型单独使用是4个字节,在数组中又是1个字节。那虚拟机为什么要用int来代替boolean呢?为什么不用byte或short,这样不是更节省内存空间吗?实际上,使用int的原因是,对于当下32位的CPU来说,一次进行32位的数据交换更加高效。
综上,我们可以知道:官方文档对boolean类型没有给出精确的定义,《Java虚拟机规范》给出了“单独时使用4个字节,boolean数组时1个字节”的定义,具体还要看虚拟机实现是否按照规范来,所以1个字节、4个字节都是有可能的。这其实是一种时空权衡。

2.谈一谈”==“与”equals()”的区别。

《Think in Java》中说:“关系操作符生成的是一个boolean结果,它们计算的是操作数的值之间的关系”。

  • “==”判断的是两个对象的内存地址是否一样,适用于原始数据类型和枚举类型(它们的变量存储的是值本身,而引用类型变量存储的是引用);
  • equals是Object类的方法,Object对它的实现是比较内存地址,我们可以重写这个方法来自定义“相等”这个概念。比如类库中的String、Date等类就对这个方法进行了重写。

综上,对于枚举类型和原始数据类型的相等性比较,应该使用”==”;对于引用类型的相等性比较,应该使用equals方法。

3.Java中的四种引用及其应用场景是什么?

  • 强引用: 通常我们使用new操作符创建一个对象时所返回的引用即为强引用。如果一个对象具有强引用,那垃圾回收器绝不会回收它。当内存空间不足,JVM宁愿抛出OutOfMemoryError错误,使程序异常终止,也不会靠随意回收具有强引用的对象来解决内存不足的问题。

  • 软引用: 若一个对象只能通过软引用到达,那么这个对象在内存不足时会被回收,可用于图片缓存中,内存不足时系统会自动回收不再使用的Bitmap

  • 弱引用: 若一个对象只能通过弱引用到达,那么它就会被回收(即使内存充足),同样可用于图片缓存中,这时候只要Bitmap不再使用就会被回收

  • 虚引用: 虚引用是Java中最“弱”的引用,通过它甚至无法获取被引用的对象,它存在的唯一作用就是当它指向的对象回收时,它本身会被加入到引用队列中,这样我们可以知道它指向的对象何时被销毁。

4.Object中定义了哪些方法?

Object是所有类的父类,任何类都默认继承Object。

  • clone
    保护方法,实现对象的浅复制,只有实现了Cloneable接口才可以调用该方法,否则抛出CloneNotSupportedException异常

  • equals
    在Object中与==是一样的,子类一般需要重写该方法

  • hashCode
    该方法用于哈希查找,重写了equals方法一般都要重写hashCode方法。这个方法在一些具有哈希功能的Collection中用到

  • getClass
    final方法,获得运行时类型

  • wait
    使当前线程等待该对象的锁,当前线程必须是该对象的拥有者,也就是具有该对象的锁。wait()方法一直等待,直到获得锁或者被中断。wait(long timeout)设定一个超时间隔,如果在规定时间内没有获得锁就返回。
    调用该方法后当前线程进入睡眠状态,直到以下事件发生:

  1. 其他线程调用了该对象的notify方法
  2. 其他线程调用了该对象的notifyAll方法
  3. 其他线程调用了interrupt中断该线程
  4. 时间间隔到了

此时该线程就可以被调度了,如果是被中断的话就抛出一个InterruptedException异常

  • notify
    唤醒在该对象上等待的某个线程

  • notifyAll
    唤醒在该对象上等待的所有线程

  • toString
    转换成字符串,一般子类都有重写,否则打印句柄

5.hashCode的作用是什么?

什么是HashCode,总结几个关键点:

  1. HashCode的存在主要是为了查找的快捷性,HashCode是用来在散列存储结构中确定对象的存储地址的
  2. 如果两个对象equals相等,那么这两个对象的HashCode一定也相同
  3. 如果对象的equals方法被重写,那么对象的HashCode方法也尽量重写
  4. 如果两个对象的HashCode相同,不代表两个对象就相同,只能说明这两个对象在散列存储结构中,存放于同一个位置

HashCode有什么用?举个例子:

  1. 假设内存中有0 1 2 3 4 5 6 7 8这8个位置,如果我有个字段叫做ID,那么我要把这个字段存放在以上8个位置之一,如果不用HashCode而任意存放,那么当查找时就需要到8个位置中去挨个查找
  2. 使用HashCode则效率会快很多,把ID的HashCode%8,然后把ID存放在取得余数的那个位置,然后每次查找该类的时候都可以通过ID的HashCode%8求余数直接找到存放的位置了
  3. 如果ID的HashCode%8算出来的位置上本身已经有数据了怎么办?这就取决于算法的实现了,比如ThreadLocal中的做法就是从算出来的位置向后查找第一个为空的位置,放置数据;HashMap的做法就是通过链式结构连起来。反正,只要保证放的时候和取的时候的算法一致就行了。
  4. 如果ID的HashCode%8相等怎么办(这种对应的是第三点说的链式结构的场景)?这时候就需要定义equals了。先通过HashCode%8来判断类在哪一个位置,再通过equals来在这个位置上寻找需要的类。对比两个类的时候也差不多,先通过HashCode比较,假如HashCode相等再判断equals。如果两个类的HashCode都不相同,那么这两个类必定是不同的

具体可以看以下文章:
谈谈HashCode的作用
散列表的原理与实现

6.ArrayList, LinkedList, Vector的区别是什么?

  • ArrayList: 内部采用数组存储元素,支持高效随机访问,支持动态调整大小

  • LinkedList: 内部采用链表来存储元素,支持快速插入/删除元素,但不支持高效地随机访问

  • Vector: 可以看作线程安全版的ArrayList

7.String, StringBuilder, StringBuffer的区别是什么?

  • String: 不可变的字符序列,若要向其中添加新字符需要创建一个新的String对象

  • StringBuilder: 可变字符序列,支持向其中添加新字符(无需创建新对象)

  • StringBuffer: 可以看作线程安全版的StringBuilder

8.Map, Set, List, Queue、Stack的特点及用法。

  • Map<K, V>: Java中存储键值对的数据类型都实现了这个接口,表示“映射表”。支持的两个核心操作是get(Object key)以及put(K key, V value),分别用来获取键对应的值以及向映射表中插入键值对。

  • Set<E>: 实现了这个接口的集合类型中不允许存在重复的元素,代表数学意义上的“集合”。它所支持的核心操作有add(E e),remove(Object o), contains(Object o),分别用于添加元素,删除元素以及判断给定元素是否存在于集中。

  • List<E>: Java中集合框架中的列表类型都实现了这个接口,表示一种有序序列。支持get(int index), add(E e)等操作。

  • Queue<E>: Java集合框架中的队列接口,代表了“先进先出”队列。支持add(E element), remove()等操作。

  • Stack<E>: Java集合框架中表示堆栈的数据类型,堆栈是一种“后进先出”的数据结构。支持push(E item), pop()等操作。

9.HashMap和HashTable的区别。

  • HashMap是非线程安全的,HashTable是线程安全的。

  • HashMap的键和值都允许有null值存在,而HashTable则不行。

  • 因为线程安全的问题,HashMap效率比HashTable的要高。

详细分析请参考深入解析HashMap、HashTable

10.HashMap的实现原理。

简单说,HashMap就是将key做hash算法,然后将hash所对应的数据映射到内存地址,直接取得key所对应的数据。在HashMap中。底层数据结构使用的是数组,所谓的内存地址即数组的下标索引。HashMap的高性能需要保证以下几点:

  • hash算法必须高效
  • hash值到内存地址(数组索引)的算法是快速的
  • 根据内存地址(数组索引)可以直接取得对应的值

具体可参考从源码分析HashMap

11.ConcurrentHashMap的实现原理

ConcurrentHashMap是支持并发读写的HashMap,它的特点是读取数据时无需加锁,写数据时可以保证加锁粒度尽可能的小。由于其内部采用“分段存储”,只需对要进行写操作的数据所在的“段”进行加锁。关于ConcurrentHashMap底层实现的详细分析请参考Java并发编程:并发容器之ConcurrentHashMap

12.TreeMap, LinkedHashMap, HashMap的区别是什么?

  • HashMap的底层实现是散列表,因此它内部存储的元素是无序的;

  • TreeMap的底层实现是红黑树,所以它内部的元素的有序的。排序的依据是自然序或者是创建TreeMap时所提供的比较器(Comparator)对象。

  • LinkedHashMap可以看作能够记住插入元素的顺序的HashMap。

13.Collection与Collections的区别是什么?

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

14.Set、List、Map之间的区别是什么?

Collection是最基本的集合接口,Set 和List 都继承了Conllection,Map没有。

  • Set是最简单的一种集合。集合中的对象不按特定的方式排序,并且没有重复对象。
  • List的特征是其元素以线性方式存储,集合中可以存放重复对象。
  • Map 是一种把键对象和值对象映射的集合,它的每一个元素都包含一对键对象和值对象。

详细可参考java 中的Set,List,Map

15.特殊关键字:final

  • 可以修饰类、函数、变量;
  • 被final修饰的类不可以被继承。为了避免被继承,被子类复写。final class Demo { }
  • 被final修饰的方法不可以被复写。final void show () { }
  • 被final 修饰的变量是一个常量,只能赋值一次。
  • 内部类定义在类中的局部位置上时,只能访问该局部被final修饰的局部变量。

16.Switch能否用string做参数?

在Java7之前,switch只支持byte、short、char、int及其对应的封装类,以及Enum类型,在Java 7中,String类型被加上。

17.对于“try-catch-finally”,若try语句块中包含“return”语句,finally语句块会执行吗?

答案是会执行。只有两种情况finally块中的语句不会被执行:

  • 调用了System.exit()方法;

  • JVM“崩溃”了。

18.泛型的优缺点

优点:

使用泛型类型可以最大限度地重用代码、保护类型的安全以及提高性能。

泛型最常见的用途是创建集合类。

缺点:

在性能上不如数组快。

19.Java中的异常层次结构

Java中的异常层次结构如下图所示:

《面试相关--JAVA基础部分》 java中的异常

我们可以看到Throwable类是异常层级中的基类。Error类表示内部错误,这类错误使我们无法控制的;Exception表示异常,RuntimeException及其子类属于未检查异常,这类异常包括ArrayIndexOutOfBoundsException、NullPointerException等,我们应该通过条件判断等方式语句避免未检查异常的发生。IOException及其子类属于已检查异常,编译器会检查我们是否为所有可能抛出的已检查异常提供了异常处理器,若没有则会报错。对于未检查异常,我们无需捕获(当然Java也允许我们捕获,但我们应该做的事避免未检查异常的发生)。

20.Interface与abstract类的区别。

抽象类和接口都不能够实例化,但可以定义抽象类和接口类型的引用。一个类如果继承了某个抽象类或者实现了某个接口都需要对其中的抽象方法全部进行实现,否则该类仍然需要被声明为抽象类。接口比抽象类更加抽象,因为抽象类中可以定义构造器,可以有抽象方法和具体方法,而接口中不能定义构造器而且其中的方法全部都是抽象方法。抽象类中的成员可以是private、默认、protected、public的,而接口中的成员全都是public的。抽象类中可以定义成员变量,而接口中定义的成员变量实际上都是常量。有抽象方法的类必须被声明为抽象类,而抽象类未必要有抽象方法。

21.Java面向对象的三个特征与含义。

三大特征:封装、继承、多态。

  • 继承
    (1)继承是一种联结类的层次模型,并且允许和鼓励类的重用,它提供了一种明确表述共性的方法。
    (2)对象的一个新类可以从现有的类中派生,这个过程称为类继承,新类继承了原始类的特性,新类称为原始类的派生类(子类),而原始类称为新类的基类(父类)。
    (3)派生类可以从它的基类那里继承方法和实例变量,并且类可以修改或增加新的方法使之更适合特殊的需要。
  • 封装
    封装是把过程和数据包围起来,对数据的访问只能通过已定义的界面。面向对象计算始于这个基本概念,即现实世界可以被描绘成一系列完全自治、封装的对象,这些对象通过一个受保护的接口访问其他对象。
  • 多态性
    (1)多态性是指允许不同类的对象对同一消息作出响应。
    (2)多态性包括参数化多态性和包含多态性。
    (3)多态性语言具有灵活、抽象、行为共享、代码共享的优势,很好的解决了应用程序函数同名问题。

22.Override, Overload的含义与区别。

  • Override表示“重写”,是子类对父类中同一方法的重新定义

  • Overload表示“重载”,也就是定义一个与已定义方法名称相同但签名不同的新方法

23.接口与抽象类的区别

接口是一种约定,实现接口的类要遵循这个约定;抽象类本质上是一个类,使用抽象类的代价要比接口大。接口与抽象类的对比如下:

  • 抽象类中可以包含属性,方法(包含抽象方法与有着具体实现的方法),常量;接口只能包含常量和方法声明。

  • 抽象类中的方法和成员变量可以定义可见性(比如public、private等);而接口中的方法只能为public(缺省为public)。

  • 一个子类只能有一个父类(具体类或抽象类);而一个接口可以继承一个多个接口,一个类也可以实现多个接口。

  • 子类中实现父类中的抽象方法时,可见性可以大于等于父类中的;而接口实现类中的接口 方法的可见性只能与接口中相同(public)。

24.静态内部类与非静态内部类的区别。

内部静态类不需要有指向外部类的引用。但非静态内部类需要持有对外部类的引用。非静态内部类能够访问外部类的静态和非静态成员。静态类不能访问外部类的非静态成员。他只能访问外部类的静态成员。一个非静态内部类不能脱离外部类实体被创建,一个非静态内部类可以访问外部类的数据和方法,因为他就在外部类里面。

25.Java中多态的实现原理。

所谓多态,指的就是父类引用指向子类对象,调用方法时会调用子类的实现而不是父类的实现。多态的实现的关键在于“动态绑定”。详细介绍请戳Java动态绑定的内部实现机制

  • Java方法的存储机制
    当JVM执行Java字节码时,类型信息会存储在方法区中,为了优化对象的调用方法的速度,方法区的类型信息会增加一个指针,该指针指向一个记录该类方法的方法表,方法表中的每一个项都是对应方法的指针。

    方法区:方法区和JAVA堆一样,是各个线程共享的内存区域,用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。 运行时常量池:它是方法区的一部分,Class文件中除了有类的版本、方法、字段等描述信息外,还有一项信息是常量池,用于存放编译器生成的各种符号引用,这部分信息在类加载时进入方法区的运行时常量池中。 方法区的内存回收目标是针对常量池的回收及对类型的卸载。

  • 方法表的构造
    由于java的单继承机制,一个类只能继承一个父类,而所有的类又都继承Object类,方法表中最先存放的是Object的方法,接下来是父类的方法,最后是该类本身的方法。如果子类改写了父类的方法,那么子类和父类的那些同名的方法共享一个方法表项。

    由于这样的特性,使得方法表的偏移量总是固定的,例如,对于任何类来说,其方法表的equals方法的偏移量总是一个定值,所有继承父类的子类的方法表中,其父类所定义的方法的偏移量也总是一个定值。

  • 实例
    假设Class A是Class B的子类,并且A改写了B的方法的method(),那么B来说,method方法的指针指向B的method方法入口;对于A来说,A的方法表的method项指向自身的method而非父类的。

    流程:调用方法时,虚拟机通过对象引用得到方法区中类型信息的方法表的指针入口,查询类的方法表 ,根据实例方法的符号引用解析出该方法在方法表的偏移量,子类对象声明为父类类型时,形式上调用的是父类的方法,此时虚拟机会从实际的方法表中找到方法地址,从而定位到实际类的方法。 注:所有引用为父类,但方法区的类型信息中存放的是子类的信息,所以调用的是子类的方法表。

详细可查看JAVA多态的实现原理(JVM实现方式)

26.简述Java中创建新线程的两种方法。

  • 继承Thread类(假设子类为MyThread),并重写run()方法,然后new一个MyThread对象并对其调用start()即可启动新线程。

  • 实现Runnable接口(假设实现类为MyRunnable),而后将MyRunnable对象作为参数传入Thread构造器,在得到的Thread对象上调用start()方法即可。

实现方式和继承方式有什么区别?

  • 实现方式相比继承方式的好处: 避免了单继承的局限性(单继承只能继承一个父类)。在定义线程时,建议使用实现方式。
  • 存放代码的位置不一样:
    继承Thread: 线程代码存放Thread子类的run方法中。
    实现Runnable: 线程代码存在接口的子类的run方法。

27.简述Java中进行线程同步的方法。

  • volatile: Java Memory Model保证了对同一个volatile变量的写happens before对它的读;

  • synchronized: 可以来对一个代码块或是对一个方法上锁,被“锁住”的地方称为临界区,进入临界区的线程会获取对象的monitor,这样其他尝试进入临界区的线程会因无法获取monitor而被阻塞。由于等待另一个线程释放monitor而被阻塞的线程无法被中断。

  • ReentrantLock: 尝试获取锁的线程可以被中断并可以设置超时参数。

28.简述Java中具有哪几种粒度的锁

Java中可以对类、对象、方法或是代码块上锁。

更加详细的介绍请查看:Java核心技术点之多线程

29.给出“生产者-消费者”问题的一种解决方案。

public class BlockingQueueTest {
    
    private int size = 20;    
    private ArrayBlockingQueue<Integer> blockingQueue = new ArrayBlockingQueue<>(size); 
       
    public static void main(String[] args)  { 
            BlockingQueueTest test = new BlockingQueueTest();        
            Producer producer = test.new Producer();        Consumer consumer = test.new Consumer();                   producer.start();        
            consumer.start();    
    }
        
    class Consumer extends Thread{
        
        @Override        
        public void run() {             
            while(true){                
                try {                    
                    //从阻塞队列中取出一个元素
                    queue.take();                    
                    System.out.println("队列剩余" + queue.size() + "个元素");                
                } catch (InterruptedException e) {                                  
                }            
            }        
        }    
    } 
       
    class Producer extends Thread{                  

        @Override
        public void run() {            
            while (true) {                
                try {                    
                    //向阻塞队列中插入一个元素    
                    queue.put(1);                    
                    System.out.println("队列剩余空间:" + (size - queue.size()));                
                } catch (InterruptedException e) {                              
                }            
            }        
        }    
    }

}

30.ThreadLocal的设计理念与作用。

ThreadLocal的作用是提供线程内的局部变量,在多线程环境下访问时能保证各个线程内的ThreadLocal变量各自独立。也就是说,每个线程的ThreadLocal变量是自己专用的,其他线程是访问不到的。ThreadLocal最常用于以下这个场景:多线程环境下存在对非线程安全对象的并发访问,而且该对象不需要在线程间共享,但是我们不想加锁,这时候可以使用ThreadLocal来使得每个线程都持有一个该对象的副本。
关于ThreadLocal的实现原理分析请戳深入剖析ThreadLocal

31.concurrent包的整体架构。

《面试相关--JAVA基础部分》 concurrent包的整体架构

32.ArrayBlockingQueue, CountDownLatch类的作用.

  • CountDownLatch: 允许线程集等待直到计数器为0。适用场景: 当一个或多个线程需要等待指定数目的事件发生后再继续执行。

  • ArrayBlockingQueue: 一个基于数组实现的阻塞队列,它在构造时需要指定容量。当试图向满队列中添加元素或者从空队列中移除元素时,当前线程会被阻塞。通过阻塞队列,我们可以按以下模式来工作:工作者线程可以周期性的将中间结果放入阻塞队列中,其它线程可以取出中间结果并进行进一步操作。若工作者线程的执行比较慢(还没来得及向队列中插入元素),其他从队列中取元素的线程会等待它(试图从空队列中取元素从而阻塞);若工作者线程执行较快(试图向满队列中插入元素),则它会等待其它线程取出元素再继续执行。

33.wait(),sleep() 的区别

  • wait(): Object类中定义的实例方法。在指定对象上调用wait方法会让当前线程进入等待状态(前提是当前线程持有该对象的monitor),此时当前线程会释放相应对象的monitor,这样一来其它线程便有机会获取这个对象的monitor了。当其它线程获取了这个对象的monitor并进行了所需操作时,便可以调用notify方法唤醒之前进入等待状态的线程。

  • sleep(): Thread类中的静态方法,作用是让当前线程进入休眠状态,以便让其他线程有机会执行。进入休眠状态的线程不会释放它所持有的锁。

34.线程池的用法与优势

  • 优势: 实现对线程的复用,避免了反复创建及销毁线程的开销;使用线程池统一管理线程可以减少并发线程的数目,而线程数过多往往会在线程上下文切换上以及线程同步上浪费过多时间。

  • 用法: 我们可以调用ThreadPoolExecutor的某个构造方法来自己创建一个线程池。但通常情况下我们可以使用Executors类提供给我们的静态工厂方法来更方便的创建一个线程池对象。创建了线程池对象后,我们就可以调用submit方法提交任务到线程池中去执行了;线程池使用完毕后我们要记得调用shutdown方法来关闭它。

关于线程池的详细介绍以及实现原理分析请查看:深入理解java线程池—ThreadPoolExecutor

35.for-each与常规for循环的效率对比

关于这个问题我们直接看《Effective Java》给我们做的解答:
for-each能够让代码更加清晰,并且减少了出错的机会。下面的惯用代码适用于集合与数组类型:

for (Element e : elements) {
    doSomething(e);
}

使用for-each循环与常规的for循环相比,并不存在性能损失,即使对数组进行迭代也是如此。实际上,在有些场合下它还能带来微小的性能提升,因为它只计算一次数组索引的上限。

36.简述Java IO与NIO的区别

  • Java IO是面向流的,这意味着我们需要每次从流中读取一个或多个字节,直到读取完所有字节;NIO是面向缓冲的,也就是说会把数据读取到一个缓冲区中,然后对缓冲区中的数据进行相应处理。

  • Java IO是阻塞IO,而NIO是非阻塞IO。

  • Java NIO中存在一个称为选择器(selector)的东西,它允许你把多个通道(channel)注册到一个选择器上,然后使用一个线程来监视这些通道:若这些通道里有某个准备好可以开始进行读或写操作了,则开始对相应的通道进行读写。而在等待某通道变为可读/写期间,请求对通道进行读写操作的线程可以去干别的事情。

更进一步的说明请查看:Java NIO与IO

37.父类的静态方法能否被子类重写,为什么?

父类的静态方法可以被子类继承,也就是说子类可以调用父类的静态方法,但是不能重写。
可以写个例子进行验证:

父类代码:
public class Fu {  

    public static void show() {  

        System.out.println("父类的静态方法");  

    }  

    public void method() {  

        System.out.println("父类的普通方法");  

    }  

}

子类代码:
public class Zi extends Fu {  
    public static void main(String[] args) {  
        Fu fu = new Zi();  
        fu.show();  
        fu.method();  
    }  
    public static void show() {  
        System.out.println("子类的静态方法");  
    }  
    public void method() {  
        System.out.println("子类的普通方法");  
    }  
      
}  

输出的结果是:
  父类的静态方法
  子类的普通方法

38.反射的作用与原理

JAVA反射机制是在运行状态中, 对于任意一个类, 都能够知道这个类的所有属性和方法; 对于任意一个对象, 都能够调用它的任意一个方法和属性; 这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制.

主要作用有三:

  • 运行时取得类的方法和字段的相关信息。
  • 创建某个类的新实例(.newInstance())
  • 取得字段引用直接获取和设置对象字段,无论访问修饰符是什么。

用处如下:

  • 观察或操作应用程序的运行时行为。
  • 调试或测试程序,因为可以直接访问方法、构造函数和成员字段。
  • 通过名字调用不知道的方法并使用该信息来创建对象和调用方法。

39.Java中的泛型机制

泛型:JDK1.5版本以后出现的新特性,用于解决安全问题,是一个类型安全机制。
其好处是:

  • 将运行时期出现的问题CLassCastException(类型匹配异常),转移到了编译时期,方便与程序员解决问题,让运行时期问题减少,安全了。
  • 避免了强制类型转换,且运行时不会提示程序类型没检查的不安全提示。

泛型技术是给编译器使用的技术,用于编译时期。确保了类型的安全。 运行时,会将泛型去掉,生成的class文件中是不带泛型的,这个称为泛型的擦除。 为什么擦除呢?因为为了兼容运行的类加载器。 泛型的补偿:在类加载器原有基础上,编写一个补偿程序。在运行时,通过反射, 获取元素的类型进行转换动作。不用使用者在强制转换了。

具体可查看Java核心技术点之泛型

40.内部类的作用

  1. 完善多重继承
  • C++作为比较早期的面向对象编程语言,摸着石头过河,不幸的当了炮灰。比如多重继承,Java是不太欢迎继承的。因为继承耦合度太高。比如你是一个人,你想会飞,于是就继承了鸟这个类,然后你顺便拥有了一对翅膀和厚厚的羽毛,可这些玩意你并不需要。所以Java发明了接口,以契约的方式向你提供功能。想想看,你的程序里成员变量会比函数多吗?况且多重继承会遇到死亡菱形问题,就是两个父类有同样名字的函数,你继承谁的呢?其实C++也可以做到这些,那就是定义没有成员变量的纯虚类,而且所有函数都是纯虚函数。可是这些都是要靠程序员自己把握,并没有把这些功能集成到类似Interface这样的语法里。
  • 所以Java只支持单重继承,想扩展功能,去实现接口吧。很快Java的设计者就发现了他们犯了矫枉过正的错误,多重继承还是有一定用处的。比如每一个人都是同时继承父亲和母亲两个类,要不然你的身体里怎么能留着父母的血呢?Java内部类应运而生。
  1. 实现事件驱动系统
    用来开发GUI的Java Swing使用了大量内部类,主要用来响应各种事件。Swing的工作就是在事件就绪的时候执行事件,至于事件具体怎么做,这由事件决定。这里面有两个问题:1.事件必须要用到继承2.事件必须能访问到Swing。所以必须把事件写成内部类。

  2. 闭包。
    内部类是面向对象的闭包,因为它不仅包含创建内部类的作用域的信息,还自动拥有一个指向此外围类对象的引用,在此作用域内,内部类有权操作所有的成员,包括private成员。一般使用一个库或类时,是你主动调用人家的API,这个叫Call,有的时候这样不能满足需要,需要你注册(注入)你自己的程序(比如一个对象),然后让人家在合适的时候来调用你,这叫Callback。
    当父类和实现的接口出现同名函数时,你又不想父类的函数被覆盖,回调可以帮你解决这个问题。

摘取至知乎中Java 中引入内部类的意义一文的回答

41.if和switch的区别:

  • if :
    1.对具体的值进行判断
    2.对区间判断
    3.对运算结果是boolean类型的表达式进行判断

  • switch :
    1.对具体的值进行判断;
    2.值的个数通常是固定的。

对于几个固定值的判断,建议使用switch语句,因为switch语句会将具体的答案加载进内存,相对高效一点。

42.进程和线程的区别

进程就是一个应用程在处理机上的一次执行过程, 它是一个动态的概念。而线程是进程的一部分,一个进程可以包含多个线程. 进程是一个具有独立功能的程序关于某个数据集合的一次运行活动。它可以申请和拥有系统资源,是一个动态的概念,是一个活动的实体。它不只是程序的代码,还包括当前的活动,通过程序计数器的值和处理寄存器的内容来表示。

进程是一个“执行中的程序”。程序是一个没有生命的实体,只有处理器赋予程序生命时,它才能成为一个活动的实体,我们称其为进程。

通常在一个进程中可以包含若干个线程,它们可以利用进程所拥有的资源。在引入线程的操作系统中,通常都是把进程作为分配资源的基本单位,而把线程作为独立运行和独立调度的基本单位。由于线程比进程更小,基本上不拥有系统资源,故对它的调度所付出的开销就会小得多,能更高效的提高系统内多个程序间并发执行的程度。

线程和进程的区别在于,子进程和父进程有不同的代码和数据空间,而多个线程则共享数据空间,每个线程有自己的执行堆栈和程序计数器为其执行上下文。多线程主要是为了节约CPU时间,发挥利用,根据具体情况而定。线程的运行中需要使用计算机的内存资源和CPU。

线程与进程的区别归纳:

  • 地址空间和其它资源**:进程间相互独立,同一进程的各线程间共享。某进程内的线程在其它进程不可见。
  • 通信:进程间通信IPC,线程间可以直接读写进程数据段(如全局变量)来进行通信——需要进程同步和互斥手段的辅助,以保证数据的一致性。
  • 调度和切换:线程上下文切换比进程上下文切换要快得多。
  • 在多线程OS中,进程不是一个可执行的实体。

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

来源于文章面试题二(谈谈进程与线程)

43.Java 7与Java 8的新特性

Java 7:

  • 对集合的支持
    创建List / Set / Map 时写法更简单了。
  • 对资源的自动回收管理
  • 泛型实例创建过程中类型引用的简化
  • 在数字中使用下划线
  • 对字符串进行switch case
  • 二进制符号
  • 一个catch里捕捉多个异常类型

Java 8:

  • Lambdas表达式与Functional接口
  • 接口的默认与静态方法
  • 方法引用
  • 重复注解
  • 更好的类型推测机制
  • 扩展注解的支持

这里有两篇总结的非常好的:
Java 7的新特性
Java 8的新特性

44.常见设计模式

所谓“设计模式”,不过是面向对象编程中一些常用的软件设计手法,并且经过实践的检验,这些设计手法在各自的场景下能解决一些需求,因此它们就成为了如今广为流传的”设计模式“。也就是说,正式因为在某些场景下产生了一些棘手的问题,才催生了相应的设计模式。明确了这一点,我们在学习某种设计模式时要充分理解它产生的背景以及它所解决的主要矛盾是什么。

常用的设计模式可以分为以下三大类:

  • 创建型模式: 包括工厂模式(又可进一步分为简单工厂模式、工厂方法模式、抽象工厂模式)、建造者模式、单例模式。

  • 结构型模式: 包括适配器模式、桥接模式、装饰模式、外观模式、享元模式、代理模式。

  • 行为型模式: 包括命令模式、中介者模式、观察者模式、状态模式、策略模式。

关于每个模式具体的介绍请参考图说设计模式

45.动态代理的定义、应用场景及原理

公共技术点之 Java 动态代理

46.注解的基本概念与使用

  • 注解可以看作是“增强版的注释”,它可以向编译器、虚拟机说明一些事情。

  • 注解是描述Java代码的代码,它能够被编译器解析,注解处理工具在运行时也能够解析注解。注解本身是“被动”的信息,只有主动解析它才有意义。

  • 除了向编译器/虚拟机传递信息,我们也可以使用注解来生成一些“模板化”的代码。

本文主要根据以下文章整理:

  1. Java面试知识点总结
  2. LearningNotes

后续会持续添加我认为重要的东西

    原文作者:大弃
    原文地址: https://www.jianshu.com/p/4765eef0c979
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞