@yufan
2015-12-20T07:04:45.000000Z
字数 13212
阅读 480
学习笔记
java将基本类型直接存储在数组中,至于对象数组,java在数组的每个单元中存储指向其它内存位置的引用。
int[][] martix = new int[rows][];
martix[0] = new int[]{0};
martix[1] = new int[]{1,2};
Arrays:
执行折半搜索
对数组排序
将数组转换成list的对象
equals
获取哈希值
用指定值填充数组的所有元素,或者子集
这里补充说明一下,Java中的数组的数据结构,数组其实是一种特殊的对象。但是它没有实现Object的全部方法,比如equals方法对于数组而言,就是比较引用是否相同。
因为数组是对象,所以,它的定义就是一个引用存于栈,实际的调用数据存储与堆中,但是是连续的一段空间。具体可以参考此文章:http://www.programcreek.com/2013/04/what-does-a-java-array-look-like-in-memory/
Arrays是一个很有意思的工具类,封装的比较有意思,排序部分值得好好看。
错误的调用:Arrays.asList(name.split(" "));
该方法返回某个数组的列表形式,返回的列表只是列表的另一种视图,
数组本身没有消失,对列表的任何操作最终都反映在数组上。
由于列表的背后是数组,又不能删除数组的任何元素,所以java禁止
remove调用。
Asterisk 这里说的可能不是很清晰,但是看代码就很直观了。下面是代码:
public static <T> List<T> asList(T... a) {
return new ArrayList<>(a); // 实际是由下面的内部类实现
}
private static class ArrayList<E> extends AbstractList<E>
implements RandomAccess, java.io.Serializable {
}
对
对列表的任何操作最终都反映在数组上
这句话补充说明一下,数组的使用,默认都是传引用。也就是说,拿它作为方法参数的话,它的修改会影响全局。所以在数据实体中如果对数组使用getter、setter的话,会要求对于数组使用拷贝。所谓的Java禁止remove调用,是因为AbstractList这个类没有实现remove方法,使用会抛出异常,属于比较好的设计。
如需安全操作。可以看一下System.arraycopy,PS,这个无源码,只能看注释意淫。
try包含可能抛出异常的代码块,catch包含处理异常的代码
策略:在尽可能靠近源头(异常产生的地方)的地方捕获异常。一旦
捕获某个异常,就编写代码对其进行处理。从而把问题转变成另一种
可以接受的合理行为。
这边需要说一下,try catch的性能问题,似乎很多人在学Java的时候,老师都说,尽量避免try catch,会影响性能,其实不然。try catch的代码,在没有发生异常的时候,执行效率和不加try catch的代码没有差别。
try catch在出现异常时,才会有性能问题,所以这里的策略会说:
在尽可能靠近源头(异常产生的地方)的地方捕获异常
,原因也是如此。具体的异常行为分析可以看《Java技术手册:第6版》P50页底部~P51页顶部。
检查异常是必须在代码中显式进行处理的异常。可以忽略非检查异常。(如:NumberFormatException),但是忽略异常可能是不安全的。
这里不得不说异常的分类,已检异常和未检异常,本质上属于编程习惯的问题,大部分情况下,需要在方法签名处规范,显式对可能发生的异常说明。大家写代码的时候,也至少要在最后一层(最外侧那层尽量try catch)。
使用throws声明某个方法需要传递异常。
非检查应用程序异常必须继承自RuntimeException -> 直接继承自Exception
避免传递异常,但也不要创建空Catch块
非检查应用程序异常必须继承自RuntimeException 属于代码规范,没什么好说,catch块为空的可能也有,就是此种异常可以忽略,但是我们为了不影响外面业务,只要自己catch掉。在Findbugs中,对于这种情况,会要求写日志,logger一下,也是一种不错的方法。
捕获多个异常:
如果try块中的代码抛出某个异常,那么JVM将控制转移到第一个catch块,如果第一个catch块中声明的异常类型和抛出的异常类型想匹配,
那么虚拟机执行第一个catch块中的代码,否则虚拟机将控制转移到下一个catch块,一旦某个catch块被执行,虚拟机将忽略其他的catch块
这个的补充就是异常的捕获的顺序定义问题,一定程度上,我不推荐直接
catch(Exception e)
。我曾经看过某人,直接那么做,然后调用外部的一个处理方法,在处理方法里面instanceof,有点脱裤子放屁。catch的异常先从具体异常开始定义,到最后可以定义catch(Exception e)
。但千万别catch(Throwable e)
没人可以阻止你捕获异常,然后在catch块中重新抛出这个异常,这是“重新抛出异常”。
目的在于:在尽可能接近源头的地方捕获异常,并进行日志,然后再产地该异常。
catch(e){
log(e);
throw e;
}
ReThrow的目的可不仅仅是记录日志那么简单哦,小董浩。
这边不得不说到链式调用机制,就是对于一个异常,可能需要有多个地方处理的情况。内层处理Java异常,然后包装业务异常抛出,外层捕获业务异常,按照异常编码号定义包装为页面显示异常。页面显示异常在mvc的v层展示为用户可视异常。
Throwable的构造函数,以throwable作为参数或者以消息字符串和throwable作为参数
catch(expectedExpection){
Throwable cause = expectedExpection.getCause();
}
这边使用这个的原因,其实在于对于异常的共性处理,有很多种异常,但是他们虽然不属于同一个类,但是异常的原因相同,通过此,获取他们的Throwable,然后比较。
finally的目的是回收局部变量。
finally的目的可不仅仅如此哦,之前在学IO的时候,finally还有一个目的,就是保证io一定会被关闭。当然,在Java8中有了更优雅的写法,不需要finally了。
^ 只有两个表达式相反的布尔值,如果源表达式为true,那么整个表达式的结果为false,反之亦然。
异或符号,不同为真,无短路功能,前后两个表达式都会判断处理。一般用于二进制字符判断,另一个有意思的是三次异或交换两个变量。
高到低的优先级-> ! > && > ^^ > ||
Object中的equals比较引用--比较接受者和参数对象的内存位置
子类没有提供实现,就使用缺省的。
需要同时注意的是,如果想要重载equals方法,比需要重载hashCode()方法。
如果两个引用指向内存中的同一个对象,那么这两个引用就内存而言是相等的。
自发性 :x.equals(x)
对称性: x.equals(y)当且仅当y.equals(x)
传递性: 如果x.equals(y)并且y.equals(z),那么x.equals(z)
一致性: 给定一致的状态,x.equals(y)返回一致的结果
public boolean equals(Object obj){
if(obj == null)
return false;
if(!(obj instanceof Course))
return false;
if(this.getClass() != obj.getClass())
return false;
Course that = (Course)obj;
return this.depart.equals(that.depart) && this.num.equals(that.num);
}
这个,是一个实体的equals方法重载吧。需要注意的问题是,equals的重载,在绝大多数情况下,是不必要的。
HashMap在内存中也是一块连续的空间,基于哈希表结构。
向哈希表插入元素,首先需要计算哈希值,简单的哈希值是一个整数,理想下是唯一的。哈希值的定约是基于类的相等性定义,如果两个对象相同。那么他们的哈希值必须相同。如果两个对象不相同,那么他们的哈希值在理想的情况下不相同。
提问,如果想使用HashMap存放Hash值相同的两个对象,怎么办呢?他们的引用实际不同。这是一个很有意思的话题。
hash code % table size = slot number
内存单元的起始地址:offset + (slot size * slot number)
哈希值是整型的,发送hashcode到对象。
hashcode如何实现的呢? 该方法必须返回一个整型值,两个相同的对象必须返回同一个哈希值,最简单最差的方案是返回1
hashCode的重载实现问题,默认的方法是native的,实际重载的时候推荐使用commons-lang3的HashCodeBuilder,如果非要自己实现,注意17、31这两个有趣的数字。
如果两个不同的对象返回相同的哈希值,那么这两个不同的对象在哈希表中对应同一个内存单元,这种情况就是冲突。冲突意味着需要额外的逻辑和时间去维护一个冲突对象列表,有多个解决冲突的方案。
最简单的方案是为每个内存单元维护一个冲突对象列表。
如果冲突的话必须遍历冲突对象列表,找到匹配的元素
如果所有对象返回同一个哈希值,那么所有对象之间相互冲突,多有对应哈希表的同一个内存单元,结果导致所有插入和删除操作,都必须遍历冲突对象列表。该列表包含了所有插入的对象。在这种极端情况下,最好使用ArrayList。
这块可能比较难理解,先得说一下HashMap对于key唯一的依据,HashMap在判断key是不是重复的时候使用了hashCode这个方法,也用到了equals方法。这也是我前面批注特别提到的,重载equals方法,必须重载hashCode方法。
换言之,hashCode和equals方法,只要有一个不等就好了。所有就会有了上面的问题。源码如下:
/**
* Implements Map.get and related methods
*
* @param hash hash for key
* @param key the key
* @return the node, or null if none
*/
final Node<K,V> getNode(int hash, Object key) {
Node<K,V>[] tab; Node<K,V> first, e; int n; K k;
if ((tab = table) != null && (n = tab.length) > 0 &&
(first = tab[(n - 1) & hash]) != null) {
if (first.hash == hash && // always check first node
((k = first.key) == key || (key != null && key.equals(k))))
return first;
if ((e = first.next) != null) {
if (first instanceof TreeNode)
return ((TreeNode<K,V>)first).getTreeNode(hash, key);
do {
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
return e;
} while ((e = e.next) != null);
}
}
return null;
}
public int hashCode(){
final int hashMultiplier = 41;
int result = 7;
result = result + hashMultiplier + depart.hashCode();
result = result + hashMultiplier + depart.hashCode();
}
这个hashCode的重载,并不是很优雅,请不要学习这种写法。
Set也基于同样的哈希表实现。
确保生成的hash值具备良好的正态分布。
另一种测试hashcode的技术是保证哈希值具有较大的方差
这块必须说,有点坑,前面提到了两个魔力数字,可以阅读思考一下为何是那两个数字的倍数?
EnumMap EnumSet AllOf() of() range ...一些工厂方法
TreeSet元素按照自然顺序(使用comparable定义的顺序)或者通过
comparator对象定义的顺序进行排序
TreeMap按照类似的方式对所有关键字进行排序
如果使用hashMap或者HashSet,执行时间相对集合尺寸会有对数的增长。
为何
执行时间相对集合尺寸会有对数的增长
,这块不得不说它们的内部实现机制。这块希望有人能分享一下,非常有意思的话题,顺便还能扯到并发问题。
LinkedHashSet维护了一个链表,元素的顺序就是元素被插入的顺序,性能和hashSet不相上下,add,contains以及remove操作都在常量时间内完成。但是相对于hashSet略有增加。迭代访问linkedHashSet比普通hashSet要快。linkedHashMap和linkedHashSet类似
为了最小化内存的消耗,类string使用了一个字符池,主要思想是:如果两个string对象包含了相同的字符,那么这些字符共享同样的内存空间(字符池)。字符池实现了享元的设计模式。设计目标是有效的处理大量的细粒度的对象。
这个不光是String,Integer也有类似的设计,所以大家往往在包装类上会坑,不能直接==比较。String这边,不得不说到它的一个native方法
intern()
,目的也在于此。所以,换句话说,代码中,无需思考某些地方的"xx"这种变量是否有必要专门一个常量类省内存。这边常量池的目的不仅仅是省内存哦,回收也是很有意思的一个话题。(Asterick看看能不能深入探讨一下。)
常量Integer.MAX_VALUE 和 Integer.MIN_VALUE 分别表示Int的最大值(2^31 -1) 和最小值(-2^31)
不光Integer有,基本上包装类都有。
java内部使用二进制存储数字,抛弃任何溢出的位
浮点数溢出会导致无穷大
NaN问题,相关的还有计算时的上溢和下溢问题。
java 用32位表示Int,最高位0表示正数,最低位1表示负数,java用自然的二进制位表示正数,用补码表示负数。计算数字补码的算法是:正数的补码是其本身,忽略负数的负号,对应的正数求反码,再对反码加1
& 按位乘法 都是1 结果1
| 按位加法 都是0 那么结果为0
^ 按位求异 都相同 返回0 否则总是1
~ 按位求反 把0变1 1变0 处理整数的每一位
异或操作时奇偶校验的基础。数据传输中,可能会有若干数据位失真。奇偶校验对发送的数据进行异或运算,从而计算出校验和。并将校验和作为附加信息发送出去,接收方针对数据和校验执行同样的算法。如果校验和不匹配,那么发送方需要重新发送数据。
奇偶校验是二进制的,对数据流可以进行奇校验,如果数据流中1的位数是偶数位,那么校验位是偶数。1个数如果是奇数,那么校验位是奇数。
public int xorAll(int first,int ...rest){
int parity = first;
for(int num:rest){
parity ^= num;
}
return parity;
}
异或相当于两个数字相加,然后对2进行求余运算。
对2求余,说明结果要么是1,要么是0,若干二进制数字相加,值为1的二进制位决定总和。所以,用总和对2求余,可以知道1的个数为奇数还是偶数。
左移操作使所有二进制位都向左移动一位,最左的数据位丢失,最右的数据位用0填充。
左移一位相当于乘2 右移一位相当于除以2
上述的内容均为二进制情况下,某些特定的场景,使用它们会有很有趣的现象,需要注意的是进制的转换问题。(Integer.parseInt可以指定进制为2),10进制转换2进制可以自己累除2。
Random如果没有指定种子,那么Random采用系统时钟作为种子的基础。
这个不得不说,Random的实现问题,系统时钟这里用的是System.nanoTime(),这个值在先后两次调用中可能出现相等问题,其次,Random容易出现先后两个Seed一致的问题,也就是大家常说的随机度不够。那么最好的方法,就是new Random()时参数指定为System.currentTimeMiles()
有更高的随机性要求时,请使用SecureRandom
字符流来处理人类可读的文本。
字节流处理8位的二进制数据。字节流的类名中含有Input和Output的字样。通常使用字节流来处理非文本的数据。例如图像文件或者编译生成的字节码。
System.in和System.out都是字节流,而非字符流。
StringBuffer input = new StringBuffer();
byte[] buffer = input.toString().getBytes();
InputStream inputStream = new ByteArrayInputStream(buffer);
OutputStream outputStream = new ByteArrayOutputStream();
InputStream consoleIn = System.in;
PrintStream consoleOut = System.out;
System.setIn(inputStream);
System.setOut(new PrintStream(outputStream));
DataOutputStream是过滤流的例子。
要将对象写入到对象流中,该对象的类必须是可序列化的。
关于流,字节字符等,请阅读阿里的那本Java Web技术内幕,有详细讲解。
transient修饰符会让在序列化时跳过这个对象。
private transient List stus = new ArrayList<>();
这里需要批注一下,transient,指的是那种继承了Serialize定义了versionId的类。也就是说,它的序列化由JDK实现。还有一种自行实现序列化逻辑的,transient这个时候无效。
内联类可以访问定义在外围类中的实例变量,静态内嵌类则不可以。
内联类是完全被封装在外围类中的,因为内联类可以引用外围类的实例变量,让其它代码又能力去创建实例内联类的实例是没意义的。
静态内嵌类可以被外部代码所使用,只要访问限定符不是private。将内嵌类声明为静态的主要原因是让其它的类可以使用它,你可以将类声明为顶层类。但是你可能希望把它紧密的绑定在容器类中。
第二个声明为静态的原因是:允许将其序列化,你不能序列化内联类对象,因为他们能够访问外围类的实例变量。要让序列化工作,序列化机制必须处理外围类的成员变量。
常见的静态内嵌类使用场景,Builder模式。
没力气了,先看到这吧……
proxy应用:延时加载(lazy load) 写时拷贝(copy on write)
池(pooling) 缓存(caching) 事务性标记
透明的安全机制
return Proxy.newProxyInstace(Accountable.class.getClassLoader(),new Class[]{Accountable.class},(类型是SecurProxy)secureAccount);
public class SecureProxy implements InvocationHandler {
private Object target;
public SecureProxy(Object target){
this.target = target;
}
public Object invoke(Object proxy,Method,Object[] args){
return method.invoke(target,args);
}
}
proxy.invoke(proxy,secureMethod,new Object[]{});
方法返回一个对象,必须将返回值强制转型成Accountable接口的引用,第一个参数需要这个接口的类加载器,从源地址读取一个代表java编译单元的字节流,java包含缺省的类加载器,从磁盘读取类文件,但是可以创建自定义的类加载器。直接从数据库或者远程的源读取类文件。大多数情况,你希望调用class对象本身的getClassLoader方法,这个方法返回这个类的类加载器。
第二个参数是接口类型的数组,你希望为它创建动态代理。在幕后,java会使用这个列表来动态的构造一个实现了所有接口的方法。
最后一个参数的类型是invocationHandler,这个接口只包括一个方法,你的动态代理类必须四线这个方法来截获传入的调用。将你的代理对象作为第三个参数传入。
invoke()接收的参数:代理对象本身,要调用的方法,以及方法的参数数组
在多处理器的机器上,多线程确实可能同时运行,每个线程占据单独的处理器。在一台单处理器的机器上,每个线程都会从处理器得到一小段时间片(通常在同一时刻只能执行一件事情),让线程表现出好像在同时运行一样。
回调这个术语是从C语言衍生而来的,它允许你创建函数的指针,当你得到一个函数指针时,你可以将这个指针传递给其他函数,就如传递其他引用一样。接收指针的代码然后可以使用这个函数指针,回调位于发起代码中的函数。
run方法是一个无线循环,它将持续的运行直至其他代码显示的终止这个线程或运行该线程的应用被终止。
yield方法可以让其他线程在其他线程在后台冲被调度之前,有机会得到处理器的运行时间片。
OS可以中断当前执行的线程,关于此线程的信息被保存起来。同时Os提供一个时间片给下一个线程。这种环境下,所有线程最终都会得到线程调度器的一定关照。
一个拙略的线程可能完全占用调度器,组织其他线程进行任何处理。
当线程阻塞在IO操作或被挂起时,可以通过显式调用yield方法或进入睡眠来让出时间。
public void run(){
while(true){
execute(queue.remove(0));
}
}
执行代码的两个线程并不必须以同样的速率来遍历代码。线程调度器交错每个执行线程的代码片。
java使用了监视器的概念保护数据。每个对象都关联有一个监视器,这个监视器保护对象的实例数据,一个监视器同每个类想关联。它保护类的静态数据,当你获得一个锁时,你同时也获得了想关联的监视器。在任何给定时刻,只有一个线程可以获得一个锁。
线程基于某种条件自然死亡,一种方式是使用一个初始化为true的布尔变量。当你希望线程结束时将之设为false
让linkedBlockingQueue停止等待的一种方法是通过调用线程的interrupt方法来中断线程,这将产生一个InterruptedException,如果捕获到一个异常你就可以从while的无限循环中跳出来。
catch(){
break;
}
一个线程等待另一个线程完成其工作,可以从一个线程内部调用wait方法来使其停转,另一线程可以通过调用notify方法来唤醒这个等待的线程。
List<Date> tic = new ArrayList<Date>();
ClockListener listener = new ClockListener(){
private int count = 0;
private void update(Date date){
tic.add(date);
if(++count == seconds){
synchronized(monitor){
monitor.notifyAll();
}
}
}
};
synchronized(monitor){
monitor.wait();
}
测试线程会等待直至监听器(运行在另一个线程)示意可以继续,
侦听器通过调用测试所等待的同一对象的notifyAll()方法来示意可以继续,为了调用notifyAll,你必须首先得到监视器对象上的锁,再一次使用synchronized代码块
对wait的调用是位于synchronized代码块中的,这意味着其他代码在synchronized代码块退出之前,都无法得到锁,包围了notifyAll的同步快也不例外,他在等待结束之前不会退出,而等待在notifyAll方法被调用之前也不会结束。 -> ?
在幕后,wait方法将当前进程放到监视器对象的所谓等待集合中,然后他会在当前线程进入空闲状态之前,释放所有的锁,这样,当其他的线程的代码啊遇到了包围了notifyAll调用的synchronized代码块时,监视器并没有锁,然后他可以得到一个锁,notifyAll调用需要这个锁,以便将它的消息发送给监视器。
wait重载版本包括允许你指定一个超时时间。当使用这种版本挂起一个线程时,他会等待直至另一个线程通知它或者超过超时时间段。
可能发生不合逻辑的唤醒。为了提防这种情况可以将wait语句套入到一个while循环,每次迭代都检测标志执行是否可以可以继续的条件。
线程池收集了它创建并启动运行的Thread对象。线程池将这个任务加入到队列的尾部,然后发起一个notify调用,所有已完成工作的工人线程,检查队列中是否有待处理的任务,如果没任务可执行,工人线程通过阻塞的wait调用进入空闲状态,如果有待处理的任务,notify调用将唤醒他们中的一个,然后被唤醒的工人线程得到下一个任务并进行处理。
ReadWriteLock实现只允许一个线程对共享资源进行写操作,而同时其他线程可以从同一资源进行读取,还可以添加一个可重入锁添加适当的规则,来完成例如允许等待时间最长的线程最先获得锁的功能。
一旦线程获得了这个锁,其他线程会一直试图尝试锁住这段代码,直至第一个线程释放了这个锁,一个线程通过调用Lock对象的unlock方法来释放锁,始终确保发生在try-finally中。
共享资源是condition对象,通过lock的newCondition方法得到一个condition对象,一旦获得了condition对象,可以使用await方法阻塞他。await会释放锁同时挂起当前的线程,和condition对象的使用相结合,另一线程中的代码通过向condition对象发送signal或signalAll消息,发信号表示条件已满足。
解决死锁的方案:
把要上锁的对象排定次序,并保证在获得锁时使用相同的次序。
锁住一个共同的对象。
ThreadLocal initialValue 初始化 get set 方法允许你访问当前线程的ThreadLocal 实例。remove方法允许你删除当前线程的ThreadLocal实例。
List 被称为类型参数列表(type parameter list)
java使用了一种不同的方法,叫做 ”擦拭法“,不同于创建一个独立的类型定义。java擦拭了参数化类型的信息,并创建一个单一的等效类型。 每个类型参数与一个称为它的上限(upper bound)的约束相关联,缺省是Obect,客户端的绑定信息被擦去。并替换为适当的强制转型
可以使用extends来指定某个类型的上限。
只有从数据结构读取时,可以使用有界的通配符(bounded)
将pad方法声明为一个泛型方法
public static <T> void pad(List<T> list,T object,int count){
for(int i = 0; i < count; i++){
list.add(object);
}
}
编译器可以根据传递给pad的参数,提取或推断出T的类型。它会使用能够从参数中推断冲的最确定的类型。
泛型方法类型参数同样有上限。
static void inPlaceReverse(List<?> list){
int size = list.size();
for(int i = 0; i < size / 2; i++){
swap(list,i,size - 1 - i);
}
}
private static <T> void swap(List<T> list,int i,int oppsite){
T temp = list.get(i);
list.set(i,list.get(oppsite));
list.set(oppsite,temp);
}
下限: ? super V 这意味着目标的value类型(V),可以是V或者V的父类型。
附加界限:>
通过使用附加限界,限制了传入的参数要实现多于一个接口,
引入的目的很大程度是为了解决向后兼容性。
public static > T max(Collecton c)
max接收一个任意类型对象的集合,但这个类型必须实现了comparable接口,且这个类型必须是在集合中存储的元素锁绑定的类型或者父类,问题在于擦拭后,结果会产生一个不同的max方法原型特征。
不能创建与参数化类型绑定的数组。
允许创建无界限的参数化类型的数组 List nameTable = new List[100];
可以调用Class.getTypeParameters取得一个TypeParameter对象数组,为你提供了足够的信息重新构造这些类型参数。
@Retention
RetentionPolicy.SOURCE 在编译时被丢弃
RetentionPolicy.CLASS 保存在类文件中,运行时可以被VM丢弃
RetentionPolicy.RUNTIME 保存在类文件中,运行时由VM保留
@Target 参数必须是ElemType的枚举
注解可以帮助你将放入代码中的注释结构化,一个例子:对接口的声明进行注解,让工具可以为这些相关的方法生成代码。
缺点是:当你的代码使用它时,便会对注解类型产生依赖关系。对注解类型声明的更改,依然会对你的代码产生负面影响。你还必须提供注解类型的源文件以进行编译。
注解类型是一个接口。应该表示一种稳定的抽象,同其他接口一样,确保你已经仔细考虑了在系统中引入注解类型所带来的影响。
public\s+void\s+test\w*\s*\s*\{
Matcher实例保存了有关上一个被发现的子串的信息。可以调用Matcher对象的statrt和end方法来得到秒回匹配子串的索引,调用matcher的group方法返回匹配的子串文本。
matches方法:如果整个输入字符串匹配了某个正则表达式,他会返回true,调用LookingAt的话,当输入字符串的开始处,或者整个字符串和正则表达式匹配时,会返回true.
java协变:子类方法返回一个对象,其类型为父类定义的返回类型的子类。克隆是java协变的典型实例。
按值调用意味着被调用的函数在幕后对参数进行了拷贝,函数中的代码操作这个拷贝,意味着对参数进行的任何改动,都会在函数执行完毕时被丢弃。原因是改动实际只作用域局部的拷贝,而非传入的参数。参数的拷贝只在方法范围中有效。
按引用调用意味着函数操作的和传入的参数,在物理上是相同的。对参数的任何改动都会保持下来。
java完全是按值调用的。如果你将一个int传给一个方法,该方法操作int值的拷贝,如果你将一个对象的引用传给一个方法,方法锁操作的是引用的拷贝,而不是对象本身的拷贝。
引用是一个指针 -- 对象在内存中的地址。被调用的方法拷贝这个地址,创建一个新的指针指向同样的内存位置。如果你在被调用方法的么内部,将一个新的内存地址(也就是一个不同的对象)赋值给这个引用,新的地址在方法完成时也会被丢弃。
被调用方法中的代码,可以调用引用所指向对象的方法。对这些方法的调用,回导致对象状态的永久改变。
你可能希望实现一种缓存机制,缓存用来载入经常被访问的对象。但是,因为缓存数据结构必须(按定义)引用被缓存的对象,垃圾回收器将永远不会认为他们需要回收。有时,你必须编写复杂额外代码管理从缓存中删除对象。否则,缓存将会持续增长,直至得到一个内存溢出的错误。
java弱引用(weak reference)。对象的一个弱引用,不会被计入垃圾收集的考量。垃圾收集器将回收所有使用弱引用的对象,但强引用不会。
java弱引用级别: -> phantom(幻象) weak soft
phantom引用可以用于在对象结束之后的特殊清除处理
weak引用可以导致被引用的对象在垃圾收集执行时被删除。
soft引用导致被引用的对象,只有在垃圾收集器认为这块内存的确需要时才被删除。
从NIO获得的主要速度提升,来自对直接缓冲的使用。通常数据会在java数组和VM缓冲之间拷贝,直接缓冲是直接分配在VM中的,可以让你的代码直接访问他们,避免了昂贵的拷贝操作。