Java摘记

Publish by https://www.leachchen.com/

Java内存分配

静态变量方法加载时机,参数传递,局部变量成员变量存放在堆还是栈,内存区域。。。待完善

java静态变量和静态方法会在程序初始化运行时候就加载到内存。 优点:不需要在进行实例化。静态变量的值,直接赋新值即可,不需要参数传递,之后可以直接进行参数引用即可;静态方法可以直接通过”类名.方法”的形式进行方法调用

JVM概念

1. GC机制

垃圾回收分为两个过程,一个是找到垃圾,一个是回收垃圾

找到垃圾有两种方法:

1.引用计数法
当一个对象被引用时,其引用计数就会加1,当垃圾回收时,就会清空其引用计数变为0,但两个对象相互引用时,就会导致引用计数不为0,无法清理的情况,于是就有另外一种方法

2.可达性分析法
把Java中对象的引用关系链接起来,不可达根级对象的对象会被垃圾收集器清除,根级对象一般包括Java虚拟机中栈中对象,本地方法JNI栈中的对象,方法区中的静态对象,常量池中的常量

垃圾回收的四种方法:

1.标记清除算法
首先标记要清除的对象,然后进行清除,缺点是会造成内存碎片化

2.复制算法
将存活的对象复制到另一块内存区域,并做相应的内存整理工作。复制算法的优点可以避免内存碎片化,但内存需要双倍

3.标记整理算法
将要回收的垃圾对象进行标记,清除掉垃圾对象后,会对存活的对象进行压缩整理,避免了内存碎片化

4.分代算法
将对象分为新生对象和老年代对象,新生代对象分为三个区,Eden和两个Survivor区,新建的对象放在Eden区,当Eden区的对象达到阀值后会触发Minor GC,然后会将存活的对象复制到一个Survivor区,这些存活的对象生命存活计数会加1.这时Eden区会闲置,当Eden区的对象再次到达阀值时会触发Minor GC,这是会将Eden和这个Survivor区的对象复制到另一个Survivor区,这些存活的对象生命存活计数又会加1。重复以上过程,对象的生命存活计数达到阀值时,会将该对象放到老年代中。老年代中是多次GC依然存活的生命周期较长的对象,当老年代中对象达到阀值时就会触发Minor GC,采用标记整理算法进行处理。

2. JVM内存区域的划分

JVM内存区域可以分为两类,线程的私有区域和公有区域。

线程的私有区域:程序计数器、JVM虚拟机栈、本地方法栈

线程的公有区域:堆、方法区、运行时常量池

程序计数器:每个线程有个私有的程序计数器,任何时间线程只有一个方法在执行,程序计数器执行的就是当前方法的JVM地址。

JVM虚拟机栈:栈中存放着一个个的栈帧,对应着一个个方法的调用。栈帧中存放着局部变量表、方法返回值、方法正常和异常退出的定义等等。

本地方法栈:类似虚拟机栈,支持的是Native方法。

堆:用来存放对象实例,几乎所有创建的对象都会分配到堆上,堆作为垃圾回收最主要的区域。

方法区:方法区主要存放类的结构信息,比如静态属性和方法等。

运行时常量池:运行时常量池位于方法区,存放各种常量信息。

发生OOM区域:

其中除了程序计数器,其它地方都会发生OOM,堆区对象引用释放不及时造成内存泄漏,栈区,方法递归调用可能导致栈空间扩展失败导致OOM,方法区如果在内存加载时,做的事情过多也会导致OOM。

3. 类的加载过程及双亲委派模型

Java中类的加载分为加载、链接、初始化三个过程。

加载,是将字节码(jar文件,class文件)数据读取到JVM内存,并映射为JVM认可的数据结构,如果数据不是ClassFile的结构,则会报ClassFormatError。

链接,分为三步:验证、准备、解析

验证:JVM校验字节信息是否规范,避免恶意信息或者不规范数据危害JVM安全

准备:这一步会创建静态变量,并为静态变量开辟内存空间

解析:这一步会将符号引用替换为直接引用

初始化,初始化会为静态变量赋值,并执行静态代码块逻辑

双亲委派模型,类加载器分为3类,启动类加载器(主要加载 jre/lib下的jar文件)、扩展类加载器(主要加载 jre/lib/ext 下的jar文件)、应用程序类加载器(主要加载 classpath下的文件),所谓双亲委派,就是加载一个类时,会优先使用父类加载器加载,当父类加载器无法加载时才会使用子类加载器加载

线程概念

1. 线程和进程的区别

进程是资源分配的最小单位,线程是程序执行的最小单位。进程有自己独立的地址空间,线程共享进程中的数据,使用相同的地址空间。进程直接互不影响,线程会直接影响到整个进程。

进程是资源分配的最小单位,线程是程序执行的最小单位。进程有自己的独立地址空间,进程创建的时候,系统就会为它分配地址空间,建立数据表来维护代码段,堆栈段和数据段,这种非常昂贵。而线程共享进程中的数据,使用相同的地址空间,因此线程切换或者创建要比进程小的多。但是多进程更健壮,一个进程挂掉不影响另外的进程,因为进程有自己独立的地址空间,多线程一个线程挂掉,整个进程都结束了。

2. synchronized的理解与作用

synchronized,称为同步锁,用来解决线程同步的问题。synchronized用来修饰方法或者代码段,当一个线程执行到,被synchronized修饰的方法或者代码段时,需要获取对象锁,如果锁没有被其它线程占用,则可进入synchronized修饰的代码段,同时获取到对象锁,执行完毕后会释放对象锁。当其它线程执行到该代码段时,只能等待,直到对象锁没有被其它线程占用。

3. 为什么wait和notify方法要在同步块中调用

放到外面会抛出异常,而且放到外面,可能线程并发执行造成死锁或者提前唤醒

if(!condition){ step1
wait();step2
}
condition=true step3
notify(); step4

举个例子:
线程A执行step1,此次条件为false,如果是队列,那就是队列为空。
线程B执行step3,将condition置为true,并执行step4发起唤醒通知。
线程A继续执行step2,这样就可能一直阻塞了。
而获取锁就不会出现这种问题了。

4. Java中notify 和 notifyAll有什么区别

notify通知的是一个线程,具体哪个线程被唤醒,不确定。notifyAll是所有等待的线程都会被唤醒。

5. 为什么wait()一定要放在循环中

因为线程被唤醒后,条件可能并不满足,若不满足继续等待,而不执行条件满足的逻辑

在多线程的编程实践中,wait()的使用方法如下:

synchronized (monitor) {
    //  判断条件谓词是否得到满足
    while(!locked) {
        //  等待唤醒
        monitor.wait();
    }
    //  处理其他的业务逻辑
}

那为什么非要while判断,而不采用if判断呢?如下:

synchronized (monitor) {
    //  判断条件谓词是否得到满足
    if(!locked) {
        //  等待唤醒
        monitor.wait();
    }
    //  处理其他的业务逻辑
}

这是因为,如果采用if判断,当线程从wait中唤醒时,那么将直接执行处理其他业务逻辑的代码,但这时候可能出现另外一种可能,条件谓词已经不满足处理业务逻辑的条件了,从而出现错误的结果,于是有必要进行再一次判断,如下:

synchronized (monitor) {
    //  判断条件谓词是否得到满足
    if(!locked) {
        //  等待唤醒
        monitor.wait();
        if(locked) {
            //  处理其他的业务逻辑
        } else {
            //  跳转到monitor.wait();
        }
    }
}
而循环则是对上述写法的简化,唤醒后再次进入while条件判断,避免条件谓词发生改变而继续处理业务逻辑的错误。

6. Java中的volatile 变量是什么

volatile用来修饰成员变量,被volatile修饰的变量,在线程并发时,可以保证下一个读取操作会在前一个写操作之后执行。线程都直接从内存中读取该变量并且不缓存它,保证线程读取到的变量同内存中是一致的。

7. 如何在两个线程间共享数据

使用同一个Runnable对象,或者不同的Runnable,参数为同一个类对象或者Runnable作为内部类,共享数据作为成员变量

8. 线程池

a. 原理

线程池是用来管理线程的,线程池管理的线程数为CPU*2+1时,效率最高。当线程池中的线程数超过核心线程数时,超过的就会加入到阻塞队列,当阻塞队列也满了的时候,就会创建新的线程来执行任务,当执行的线程数达到了最大线程数,此时还有任务来的话,就会进行丢弃

它的好处是降低资源消耗,提高响应速度,提高线程的可管理性

b. 初始化线程的几种方式

newFixedThreadPool:初始化一个指定线程数的线程池,使用LinkedBlockingQuene作为阻塞队列;即使线程池没有可执行任务,也不会释放线程

newCachedThreadPool:初始化一个可以缓存线程的线程池,默认缓存60s,线程池的线程数可达Integer.MAX_VALUE,即2147483647,2^31(有符号整数),内部使用SynchronousQueue作为阻塞队列;在没有任务执行时,线程的空闲时间超过keepAliveTime,会自动释放线程资源,当提交任务时,如果没有空闲线程,则创建新线程执行任务,会导致一定的系统开销,要注意控制并发的任务数,防止创建大量线程,内存超出

newSingleThreadExecutor:初始化只有一个线程的线程池,内部使用LinkedBlockingQueue作为阻塞队列;如果该线程异常,会创建一个新的线程继续执行任务,唯一的线程可以保证所提交的任务顺序执行

newScheduledThreadPool:初始化的线程可以在指定时间内周期性的执行所提交的任务,在实际的任务场景中可以使用该线程池定期的同步数据;

总结:除了newScheduledThreadPool的内部实现特殊一点之外,其它线程池内部都是基于ThreadPoolExecutor类(Executor的子类)实现的。

c. ThreadPoolExecutor内部具体实现

ThreadPoolExecutor类构造器语法形式:

ThreadPoolExecutor(corePoolSize,maxPoolSize,keepAliveTime,timeUnit,workQueue,threadFactory,handle);

方法参数:

corePoolSize:核心线程数

maxPoolSize:最大线程数

keepAliveTime:线程存活时间(在corePore<*<maxPoolSize情况下有用)

workQueue:阻塞队列(用来保存等待被执行的任务)

  注:关于workQueue参数的取值,JDK提供了4种阻塞队列类型供选择:

  ArrayBlockingQueue:基于数组结构的有界阻塞队列,按FIFO排序任务;

  LinkedBlockingQuene:基于链表结构的阻塞队列,按FIFO排序任务,吞吐量通常要高于  

  SynchronousQuene:一个不存储元素的阻塞队列,每个插入操作必须等到另一个线程调用移除操作,否则插入操作一直处于阻塞状态,吞吐量通常要高于ArrayBlockingQuene;

  PriorityBlockingQuene:具有优先级的无界阻塞队列;

threadFactory:线程工厂,主要用来创建线程;

handler:表示当拒绝处理任务时的策略,有以下四种取值

  注: 当线程池的饱和策略,当阻塞队列满了,且没有空闲的工作线程,如果继续提交任务,必须采取一种策略处理该任务,线程池提供了4种策略:

  ThreadPoolExecutor.AbortPolicy:丢弃任务并抛出RejectedExecutionException异常。

  ThreadPoolExecutor.DiscardPolicy:也是丢弃任务,但是不抛出异常。

  ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列最前面的任务,然后重新尝试执行任务(重复此过程)

  ThreadPoolExecutor.CallerRunsPolicy:由调用线程处理该任务

  当然也可以根据应用场景实现RejectedExecutionHandler接口,自定义饱和策略,如记录日志或持久化存储不能处理的任务。

d. 线程池状态

1.新建(new),新建一个线程对象

2.可运行(runnable),线程位于线程池中,等待被线程调度选中,获取cpu使用权

3.运行(running),获取到了cpu时间片,可运行

4.阻塞(blocked),线程因为某种原因,被放到阻塞队列,暂时停止运行,直到再次获取到cpu时间片转到运行状态

5.消亡(dead),线程结束退出

e. 向线程池提交任务(2种) Executor.execute(Runnable command);

ExecutorService.submit(Callable task);

execute()内部实现: 1.首次通过workCountof()获知当前线程池中的线程数,

如果小于corePoolSize, 就通过addWorker()创建线程并执行该任务;

 否则,将该任务放入阻塞队列;

2.如果能成功将任务放入阻塞队列中,

如果当前线程池是非RUNNING状态,则将该任务从阻塞队列中移除,然后执行reject()处理该任务;

如果当前线程池处于RUNNING状态,则需要再次检查线程池(因为可能在上次检查后,有线程资源被释放),是否有空闲的线程;如果有则执行该任务;

3、如果不能将任务放入阻塞队列中,说明阻塞队列已满;那么将通过addWoker()尝试创建一个新的线程去执行这个任务;如果addWoker()执行失败,说明线程池中线程数达到maxPoolSize,则执行reject()处理任务;

sumbit()内部实现:

会将提交的Callable任务会被封装成了一个FutureTask对象

FutureTask类实现了Runnable接口,这样就可以通过Executor.execute()提交FutureTask到线程池中等待被执行,最终执行的是FutureTask的run方法;

比较:两个方法都可以向线程池提交任务,execute()方法的返回类型是void,它定义在Executor接口中, 而submit()方法可以返回持有计算结果的Future对象,它定义在ExecutorService接口中,它扩展了Executor接口,其它线程池类像ThreadPoolExecutor和ScheduledThreadPoolExecutor都有这些方法。

f. 线程池的关闭(2种)

ThreadPoolExecutor提供了两个方法,用于线程池的关闭,分别是shutdown()和shutdownNow(),其中:

shutdown():不会立即终止线程池,而是要等所有任务缓存队列中的任务都执行完后才终止,但再也不会接受新的任务

shutdownNow():立即终止线程池,并尝试打断正在执行的任务,并且清空任务缓存队列,返回尚未执行的任务

g. 线程池容量的动态调整

ThreadPoolExecutor提供了动态调整线程池容量大小的方法:setCorePoolSize()和setMaximumPoolSize(),

MAP、List原理

1. hashmap的原理如何。hashcode和equal这两者有什么区别吗

a. 什么时候会使用HashMap?他有什么特点?

当需要存储键值对数据时需要用到hashmap,特点是可以接收null的键值对,存储的是Entry(hash, key, value, next)对象

b. hashmap工作原理

hashmap是通过数组+链表+红黑树来实现的,当往map里面put数据时,首先会根据key计算出hash值,通过hash值确定数组所在位置,然后判断该位置元素是否为NULL,若不为NULL,直接调用addEntry函数创建一个Entry对象,将键值对存入,若不为NULL,则遍历该处Entry链表,通过equals()匹配到key,value均相同的数据,若存在则替换,不存在则添加到链表上,链表默认链接的元素是8个,若超过,则会转成红黑树来处理数据,提高效率

(HashMap默认大小1 « 4 2^4,最大1 «30,2^30,1.7之前HashMap是用数组+链表实现的,1.8是通过数组+链表+红黑树实现,链表数组长度超过8,就会转成红黑树,从而提高HashMap效率。在存入key,value值时,计算key的hash值,通过hash值来计算在数组中的位置,然后判断该位置处元素是否为NULL,如果是,则调用addEntry创建新的Entry,若果不为NULL,则遍历Entry,查找是否存在于key hash值相同且key值也相同的元素,有则替换,否则调用addEntry创建新的Entry)

数组:存储区间连续,占用内存严重,寻址容易,插入删除困难;

链表:存储区间离散,占用内存比较宽松,寻址困难,插入删除容易;

Hashmap综合应用了这两种数据结构,实现了寻址容易,插入删除也容易。

c. 你知道get和put的原理吗?equals()和hashCode()的都有什么作用

get,put都是通过key的hashCode()进行hashing,计算找到数组(buckets)所在的位置,如果产生碰撞,hashmap通过链表或树将碰撞的元素组织起来,则调用key.equals()去链表或树中查找对应节点

d. 你知道hash的实现吗?为什么要这样实现?

在Java 1.8的实现中,是通过hashCode()的高16位异或低16位实现的:(h = k.hashCode()) ^ (h »> 16),主要是从速度、功效、质量来考虑的,这么做可以在bucket的n比较小的时候,也能保证考虑到高低bit都参与到hash的计算中,同时不会有太大的开销。

e. 如果HashMap的大小超过了负载因子(load factor)定义的容量,怎么办

如果超过了负载因子(默认0.75),则会重新resize一个原来长度两倍的HashMap,并且重新调用hash方法。

f. 为什么String, Interger这样的wrapper类适合作为键

String是不可变的,也是final的,而且已经重写了equals()和hashCode()方法了。其他的wrapper类也有这个特点。不可变性是必要的,因为为了要计算hashCode(),就要防止键值改变

g. 我们可以使用自定义的对象作为键吗

你可能使用任何对象作为键,只要它遵守了equals()和hashCode()方法的定义规则,并且当对象插入到Map中之后将不会再改变了。

h. 可以使用CocurrentHashMap来代替Hashtable吗

Hashtable是synchronized的,但是ConcurrentHashMap同步性能更好,因为它仅仅根据同步级别对map的一部分进行上锁。ConcurrentHashMap当然可以代替HashTable,但是HashTable提供更强的线程安全性。

i. hashcode和equal这两者有什么区别

在Java中任何一个对象都具备equals(Object obj)和hashcode()这两个方法,因为他们是在Object类中定义的。

equals(Object obj)方法用来判断两个对象是否“相同”,如果“相同”则返回true,否则返回false。

hashcode()方法返回一个int数,在Object类中的默认实现是“将该对象的内部地址转换成一个整数返回”。

1、如果两个对象equals,Java运行时环境会认为他们的hashcode一定相等。

2、如果两个对象不equals,他们的hashcode有可能相等。

3、如果两个对象hashcode相等,他们不一定equals。

4、如果两个对象hashcode不相等,他们一定不equals。

10.HashMap和Hashtable的区别

主要区别是Hashtable是线程安全的,HashMap是线程不安全

j. hashcode和equal为什么要重写

如果不被重写(原生)的hashCode和equals是什么样的?不被重写(原生)的hashCode值是根据内存地址换算出来的一个值。不被重写(原生)的equals方法是严格判断一个对象是否相等的方法(object1 == object2)。
为什么需要重写equals和hashCode方法?
在我们的业务系统中判断对象时有时候需要的不是一种严格意义上的相等,而是一种业务上的对象相等。在这种情况下,原生的equals方法就不能满足我们的需求了所以这个时候我们需要重写equals方法,来满足我们的业务系统上的需求。那么为什么在重写equals方法的时候需要重写hashCode方法呢?我们先来看一下Object.hashCode的通用约定(摘自《Effective Java》第45页)
    1.在一个应用程序执行期间,如果一个对象的equals方法做比较所用到的信息没有被修改的话,那么,对该对象调用hashCode方法多次,它必须始终如一地返回 同一个整数。在同一个应用程序的多次执行过程中,这个整数可以不同,即这个应用程序这次执行返回的整数与下一次执行返回的整数可以不一致。
    2.如果两个对象根据equals(Object)方法是相等的,那么调用这两个对象中任一个对象的hashCode方法必须产生同样的整数结果。
  3.如果两个对象根据equals(Object)方法是不相等的,那么调用这两个对象中任一个对象的hashCode方法,不要求必须产生不同的整数结果。然而,程序员应该意识到这样的事实,对于不相等的对象产生截然不同的整数结果,有可能提高散列表(hash table)的性能。
    如果只重写了equals方法而没有重写hashCode方法的话,则会违反约定的第二条:相等的对象必须具有相等的散列码(hashCode)

HashMap中的比较key是这样的,先求出key的hashcode(),比较其值是否相等,若相等再比较equals(),若相等则认为他们是相等的。若equals()不相等则认为他们不相等。如果只重写hashcode()不重写equals()方法,当比较equals()时只是看他们是否为同一对象(即进行内存地址的比较),所以必定要两个方法一起重写。HashMap用来判断key是否相等的方法,其实是调用了HashSet判断加入元素是否相等

hashcode确定数组位置,hashcode和equal确定key是否相等,当以对象最为key时,因为equal默认比较的是内存地址,如果两个对象内容相同我们也认为他们相等,此时equal的话是不相等的,此时需要重写比较别的值进行判断

当以对象最为key时(可能是两个不同的对象,但是某种场景下认为他们相等): equal:默认情况下也就是从超类Object继承而来的equals方法与‘==’是完全等价的,比较的都是对象的内存地址,但我们可以重写equals方法,使其按照我们的需求的方式进行比较,如String类重写了equals方法,使其比较的是字符的序列,而不再是内存地址 hashcode:默认以内存地址进行计算的,当两个对象不是同一个却认为需要相等时,默认的hashcode计算出来的值是不同的,此时需要重写hashcode

2. SparseArray用法及原理

SparseArray初始化时只是简单创建了两个数组,没有继承其它数据结构,底层实际用两个数组来存放数据,一组存放key,key只能是整数,一组存放value。数据有冲突则直接覆盖,插入位置上的key的索引值为DELETE,则也直接覆盖。优点是:避免基本类型数据的装箱操作,不需要其它结构体,单个元素成本更低,数据量小时访问效率高,缺点是:增删改效率低,数据量大时效率低。

3. ArrayList、Vector 、LinkedList 的实现原理

ArrayList实现原理是可变数组,ArrayList线程不安全的,当元素超出数组内容,会产生一个新数组,将原来数组的数据复制到新数组中,再将新的元素添加到新数组中,ArrayList是按照原数组的50%来延长。优点是查询速度快,缺点是数据量大时,容易浪费空间。

Vector实现原理也是数组,Vector是线程安全的,可以设置容量增量,默认扩充容量为之前的2倍

LinkedList实现原理是双向链表,优点是方便添加删除元素,查询方法消耗的时间大于ArrayList

网络概念

1. TCP原理

a.TCP三次握手

第一次握手,首先客户端发送SYN包请求报文到服务器,并进入SYN_SEND状态,等待服务端确认

第二次握手,服务端收到请求后需确认客户端的数据包,同时自己也回复SYN+ACK包到客户端,此时服务器进入SYN_RECV状态

第三次握手,客户端收到SYN+ACK包后,再次发送ACK应答报文到服务端,发送完毕后客户端和服务端就进入ESTAB_LISHED已建立连接状态

b.为什么要三次握手

如果是两次的话只能确保客户端发的消息服务端收到了,服务端发的消息客户端不一定能收到,而三次握手的话确保服务端收到了客户端的消息,客户端也收到了服务端的消息,此时才真正认为双方建立了连接,第三次结束后就可以确认双方建立了连接,如果再进行第四次,那就造成资源浪费

c.四次挥手

第一次挥手,客户端发送FIN给服务端,告诉服务端关闭客户端与服务端的数据传输,然后进入等待关闭状态

第二次挥手,服务端收到这个FIN,然后给客户端回复一个ACK应答包,告诉客户端收到了,然后继续完成剩余数据传输

第三次挥手,服务端发送一个FIN到客户端,告诉客户端关闭服务端到客户端的连接,然后进入等待关闭状态

第四次挥手,客户端收到FIN后,发回一个ACK应答报文确认,收到后就可以进行关闭了。首先进行关闭的一方进行关闭,另一方进行被动关闭。

2. Http和Https的区别以及Https传输过程中用到的加密方式

a.HTTP、HTTPS区别

HTTP:是互联网上运用最广泛的协议,是客户端和服务端请求和应答的标准(TCP),数据传输为明文传输,交互过程及数据传输都没有加密,通信双方也没认证,容易被劫持,篡改,伪造,造成个人信息泄露等

HTTPS:是HTTP的安全版,即在HTTP层下加入SSL层,数据传输进行了加密,并且会确认网站的真实性。HTTPS用SSL加密方式,有对称和非对称的加密算法。

b.HTTPS是如何保证数据的安全呢

1.客户端向服务器端发起SSL连接请求;(在此过程中依然存在数据被中间方盗取的可能,下面将会说明如何保证此过程的安全)

2 服务器把公钥发送给客户端,并且服务器端保存着唯一的私钥;

3.客户端用公钥对双方通信的对称秘钥进行加密,并发送给服务器端;

4.服务器利用自己唯一的私钥对客户端发来的对称秘钥进行解密,在此过程中,中间方无法对其解密(即使是客户端也无法解密,因为只有服务器端拥有唯一的私钥),这样保证了对称秘钥在收发过程中的安全,此时,服务器端和客户端拥有了一套完全相同的对称秘钥。

5.进行数据传输,服务器和客户端双方用公有的相同的对称秘钥对数据进行加密解密,可以保证在数据收发过程中的安全,即是第三方获得数据包,也无法对其进行加密,解密和篡改。

c.Http1.0、Http2.0和SPDY之间的区别,Http2.0做了哪些优化等

Http2.0默认支持长连接,更节约带宽,多路复用,数据压缩,服务器推送

其它

1. 反射(Reflection)和注解(Annotation )的作用及原理

反射,Java类需要装载到java虚拟机才能运行,正常情况,我们的程序在编译的时候就把那个类给装载了,但有些是在运行时才装载,编译期间并不知道的类,这就是反射的特点。

反射作用,假设开发者A需要用到开发者B的类,但是开发者还没有完成,此时用反射,可以不影响编译,有时需要反射调用一些系统的方法或者私有方法

注解,注解是插入你代码中的一种注释或者说是一种元数据(meta data)。这些注解信息可以在编译期使用预编译工具进行处理(pre-compiler tools),也可以在运行期使用 Java反射机制进行处理

2. 设计模式六大原则

单一原则。就是说一个类只有一个明确的职责,不易过多职责封装在一个类里面。

开闭原则。对于修改是关闭的,对于扩展的开放的,这样主要目的是为了提高可扩展性。

依赖倒置原则。高层不依赖于低层,两者都依赖于抽象,抽象不依赖于细节实现,具体实现依赖于抽象,也就是说要面向接口编程。

里氏替换原则。也就说子类运行的功能,父类也能运行,强调继承的重要性

迪米特原则。一个类要了解另外一个类最少的内容,强调低耦合,耦合分解

接口隔离原则。一个类不要继承不需要的接口,接口可拆分,不要冗余在一个总接口中,实现自己所需接口即可。

3.equals()和==的区别

1.==可以用来比较基本类型和引用类型,判断内容和内存地址

2.equals只能用来比较引用类型,它只判断内容。该函数存在于老祖宗类 java.lang.Object

java中的数据类型,可分为两类: 1.基本数据类型,也称原始数据类型。byte,short,char,int,long,float,double,boolean 他们之间的比较,应用双等号(==),比较的是他们的值。 2.复合数据类型(类) 当他们用(==)进行比较的时候,比较的是他们在内存中的存放地址, 所以,除非是同一个new出来的对象,他们的比较后的结果为true,否则比较后结果为false。

JAVA当中所有的类都是继承于Object这个基类的,在Object中的基类中定义了一个equals的方法。 这个方法的初始行为是比较对象的内存地 址,但在一些类库当中这个方法被覆盖掉了。 如String,Integer,Date在这些类当中equals有其自身的实现,而不再是比较类在堆内存中的存放地址了。

对于复合数据类型之间进行equals比较,在没有覆写equals方法的情况下, 他们之间的比较还是基于他们在内存中的存放位置的地址值的, 因为Object的equals方法也是用双等号(==)进行比较的,所以比较后的结果跟双等号(==)的结果相同。

Leach Chen

Leach Chen

I am an Android developer.I will add description latter.