Java 面试B

1、String的split(String regex)方法参数
      使用这个方法时,当我们直接以“.”为参数时,是会出错的,如:

String str = "12.03";
String[] res = str.spilt("."); //出错!!!

此时,我们得到的res是为空的(不是null),即str = [];
因为String的split(String regex)根据给定的正则表达式的匹配来拆分此字符串,而"."是正则表达式中的关键字,没有经过转义split会把它当作一个正则表达式来处理的,需要写成str.split("\\.")进行转义处理。

2、关于hashCode方法
     HashMap中是不允许插入重复元素的,如果是插入的同一个元素,会将前面的元素给覆盖掉,那势必在HashMap的put方法里对key值进行了判断,检测其是否是同一个对象。其put源码如下:    

public V put(K key, V value) {
if (table == EMPTY_TABLE) { //key的hashCode值放在了table里面
inflateTable(threshold);
}
if (key == null)
return putForNullKey(value);
int hash = hash(key); //计算我们传进来的key的hashcode值
int i = indexFor(hash, table.length);
for (Entry<K,V> e = table[i]; e != null; e = e.next) {
Object k;
if (e.hash == hash && ((k = e.key) == key || key.equals(k))) { //将传进来的key的hashcode值于HashMap中的table里面存放的hashCode值比较
V oldValue = e.value;
e.value = value;
e.recordAccess(this);
return oldValue;
}
}
modCount++;
addEntry(hash, key, value, i);
return null;
}


    可以看到这里的判断语句 if (e.hash == hash && ((k = e.key) == key || key.equals(k))),里面通过&&逻辑运算符相连,先判断e.hash == hash,即判断传进来的key的hashCode值与table中的已有的hashCode值比较,如果不存在该key值,也就不会再去执行&&后面的equals判断;当已经存在该key值时,再调用equals方法再次确定两个key值对象是否相同。从这里可以看出,hashCode方法的存在是为了减少equals方法的调用次数,从而提高程序效率。
    可以看到,判断两个对象是否相同,还是要取决于equals方法,而两个对象的hashCode值是否相等是两个对象是否相同的必要条件。所以有以下结论:
    (1)如果两个对象的hashCode值不等,根据必要条件理论,那么这两个对象一定不是同一个对象,即他们的equals方法一定要返回false;
    (2)如果两个对象的hashCode值相等,这两个对象也不一定是同一个对象,即他们的equals方法返回值不确定;
    反过来,
    (1)如果equals方法返回true,即是同一个对象,它们的hashCode值一定相等;
    (2)如果equals方法返回false,hashCode值也不一定不相等,即是不确定的;

(hashCode返回的值一般是对象的存储地址或者与对象存储地址相关联的hash散列值)

然而,很多时候我们可能会重写equals方法,来判断这两个对象是否相等,此时,为了保证满足上面的结论,即满足hashCode值相等是equals返回true的必要条件,我们也需要重写hashCode方法,以保证判断两个对象的逻辑一致(所谓的逻辑一致,是指equals和hashCode方法都是用来判断对象是否相等)。

如下例子:

public class Person {
private String name;
private int age;
public Person(String name,int age){
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}

@Override
public boolean equals(Object obj) {
return this.name.equals(((Person)obj).name) && this.age== ((Person)obj).age;
}
}


在Person里面重写了equals方法,但是没有重写hashCode方法,如果就我们平时正常来使用的话也不会出什么问题,如:    

Person p1 = new Person("lly",18);
Person p2 = new Person("lly",18);
System.out.println(p1.equals(p2)); //返回true

上面是按照了我们重写的equals方法,返回了我们想要的值。但是当我们使用HashMap来保存Person对象的时候就会出问题了,如下:        

Person p1 = new Person("lly", 18);
System.out.println(p1.hashCode());
HashMap<Person, Integer> hashMap = new HashMap<Person, Integer>();
hashMap.put(p1, 1);
System.out.println(hashMap.get(new Person("lly", 18))); //此时返回了null,没有按我们的意愿返回1  

这是因为,我们没有重写Person的hashCode方法,使hashCode方法与我们equals方法的逻辑功能一致,此时的Person对象调用的hashCode方法还是父类的默认实现,即返回的是和对象内存地址相关的int值,这个时候,p1对象和new Person("lly",18);对象因为内存地址不一致,所以其hashCode返回值也是不同的。故HashMap会认为这是两个不同的key,故返回null。
    所以,我们想要正确的结果,只需要重写hashCode方法,让equals方法和hashCode方法始终在逻辑上保持一致性。

在《Java编程思想》一书中的P495页有如下的一段话:

  “设计hashCode()时最重要的因素就是:无论何时,对同一个对象调用hashCode()都应该产生同样的值。如果在将一个对象用put()添加进HashMap时产生一个hashCdoe值,而用get()取出时却产生了另一个hashCode值,那么就无法获取该对象了。所以如果你的hashCode方法依赖于对象中易变的数据,用户就要当心了,因为此数据发生变化时,hashCode()方法就会生成一个不同的散列码”。

如下一个例子:

public class Person {
private String name;
private int age;
public Person(String name,int age){
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public int hashCode() {
return name.hashCode()*37+age; //hashCode的返回值依赖于对象中的易变数据
}
@Override
public boolean equals(Object obj) {
return this.name.equals(((Person)obj).name) && this.age== ((Person)obj).age;
}
}


此时我们继续测试:        

Person p1 = new Person("lly", 18);
System.out.println(p1.hashCode());
HashMap<Person, Integer> hashMap = new HashMap<Person, Integer>();
hashMap.put(p1, 1);
p1.setAge(13);//改变依赖的一个值
System.out.println(hashMap.get(p1)); //此时还是返回为null,这是因为我们p1的hashCode值已经改变了

所以,在设计hashCode方法和equals方法的时候,如果对象中的数据易变,则最好在hashCode方法中不要依赖于该字段。

3、Override和Overload的区别
Override(重写):    
    在子类中定义与父类具有完全相同的名称和参数的方法,通过子类创建的实例对象调用这个方法时,将调用子类中的定义方法,这相当于把父类中定义的那个完全相同的方法给覆盖了,是子类与父类之间多态性的一种体现。特点如下:

  1. 子类方法的访问权限只能比父类的更大,不能更小(可以相同);
  2. 如果父类的方法是private类型,那么,子类则不存在覆盖的限制,相当于子类中增加了一个全新的方法;
  3. 子类覆盖的方法所抛出的异常必须和父类被覆盖方法的所抛出的异常一致,或者是其子类;即子类的异常要少于父类被覆盖方法的异常;

Overload(重载):
  同一个类中可以有多个名称相同的方法,但方法的参数个数和参数类型或者参数顺序不同
  关于重载函数返回类型能否不一样,需分情况:

  1. 如果几个Overloaded的方法的参数列表不一样(个数或类型),它们的返回者类型当然也可以不一样;
  2. 两个方法的参数列表完全一样,则不能通过让其返回类型的不同来实现重载。
  3. 不同的参数顺序也是可以实现重载的;

如下:    

public String getName(String str,int i){
return null;
}
public String getName(int i,String str){
return null;
}

    我们可以用反证法来说明这个问题,因为我们有时候调用一个方法时也可以不定义返回结果变量,即不要关心其返回结果,例如,我们调用map.remove(key)方法时,虽然remove方法有返回值,但是我们通常都不会定义接收返回结果的变量,这时候假设该类中有两个名称和参数列表完全相同的方法,仅仅是返回类型不同,java就无法确定编程者倒底是想调用哪个方法了,因为它无法通过返回结果类型来判断。
    所以,Overloaded重载的方法是可以改变返回值的类型;只能通过不同的参数个数、不同的参数类型、不同的参数顺序来实现重载。

4、ArrayList、Vector、LinkedList区别
    ArrayList、Vector、LinkedList都实现了List接口,其关系图如下:

三者都可以添加null元素对象,如下示例:    

ArrayList<String> arrayList = new ArrayList<String>();
arrayList.add(null);
arrayList.add(null);
System.out.println(arrayList.size()); //输出为2

LinkedList<String> linkedList = new LinkedList<String>();
linkedList.add(null);

Vector<String> vectorList = new Vector<String>();
vectorList.add(null);

ArrayList和Vector相同点:
ArrayList和Vector两者在功能上基本完全相同,其底层都是通过new出的Object[]数组实现。所以当我们能够预估到数组大小的时候,我们可以指定数组初始化的大小,这样可以减少后期动态扩充数组大小带来的消耗。如下:

ArrayList<String> list= new ArrayList<String>(20);
Vector<String> list2 = new Vector<String>(15);


由于这两者的数据结构为数组,所以在获取数据方面即get()的时候比较高效,而在add()插入或者remove()的时候,由于需要移动元素,效率相对不高。(其实对于我们平常使用来说,由于一般使用add(String element)都是让其加在数组末尾,所以并不需要移动元素,效率还是很好的,如果使用add(int index, String element)指定了插入位置,此时就需要移动元素了。)

ArrayList和Vector区别:
ArrayList的所有方法都不是同步的,而Vector的大部分方法都加了synchronized同步,所以,就线程安全来说,ArrayList不是线程安全的,而Vector是线程安全的,也因此Vector效率方面相较ArrayList就会更低,所以如果我们本身程序就是安全的,ArrayList是更好的选择。

大多数的Java程序员使用ArrayList而不是Vector,因为同步完全可以由程序员自己来控制。

LinkedList:
LinkedList其底层是通过双向循环链表实现的,所以在大量增加或删除元素时(即add和remove操作),由于不需要移动元素有更好的性能,但是在获取数据(get操作)方面要差。

所以,在三者的使用选择上,LinkedList适合于有大量的增加/删除操作和较少随机读取操作,ArrayList适合于大规模随机读取数据,而较少插入和删除元素情景下使用,Vector在要求线程安全的情况下使用。

5、String、StringBuffer、StringBuilder区别
String(since JDK1.0):
    字符串常量,不可更改,因为其内部定义的是一个final类型的数组来保存值的,如下:
    private final char value[];
    所以,当我们每次去“更改”String变量的值的时候(包括重新赋值或者使用String内部的一些方法),其实是重新新建了一个String对象(new String)来保存新的值,然后让我们的变量指向新的对象。因此,当我们需要频繁改变字符串的时候,使用String会带来较大的开销。

定义String的方法有两种:
(1)String str = "abc";
(2)String str2 = new String("def");
第一种方式创建的String对象“abc”是存放在字符串常量池中,创建过程是,首先在字符串常量池中查找有没有"abc"对象,如果有则将str直接指向它,如果没有就在字符串常量池中创建出来“abc”,然后在将str指向它。当有另一个String变量被赋值为abc时,直接将字符串常量池中的地址给它。如下:        
<span style="white-space:pre"> </span>String a = "abc";
String b = "abc";
System.out.println(a == b); //打印 true
也就是说通过第一种方式创建的字符串在字符串常量池中,是可共享的。同时,也是不可更改的,体现在:     
<span style="white-space:pre"> </span>String a = "abc";
String b = "abc";
b = b + "def";
此时,字符串常量池中存在了两个对象“abc”和“abcdef”。

第二种创建方式其实分为两步:    
String s = "def";
String str2 = new String(s);
第一步就是上面的第一种情况;第二步在堆内存中new出一个String对象,将str2指向该堆内存地址,新new出的String对象内容,是在字符串常量池中找到的或创建出“def”对象,相当于此时存在两份“def”对象拷贝,一份存在字符串常量池中,一份被堆内存的String对象私有化管理着。所以使用String str2 = new String("def");这种方式创建对象,实际上创建了两个对象。

StringBuffer(since JDK1.0)和StringBuilder(since JDK1.5):
    StringBuffer和StringBuilder在功能上基本完全相同,它们都继承自AbstractStringBuilder,而AbstractStringBuilder是使用非final修饰的字符数组实现的,如:char[] value;  ,所以,可以对StringBuffer和StringBuilder对象进行改变,每次改变还是再原来的对象上发生的,不会重新new出新的StringBuffer或StringBuilder对象来。所以,当我们需要频繁修改字符串内容的时候,使用StringBuffer和StringBuilder是很好地选择。
    两者的核心操作都是append和insert,append是直接在字符串的末尾追加,而insert(int index,String str)是在指定位置出插入字符串。 StringBuffer和StringBuilder的最主要区别就是线程安全方面,由于在StringBuffer内大部分方法都添加了synchronized同步,所以StringBuffer是线程安全的,而StringBuilder不是线程安全的。因此,当我们处于多线程的环境下时,我们需要使用StringBuffer,如果我们的程序是线程安全的使用StringBuilder在性能上就会更优一点。
    
三者的效率比较:
    如上所述,
    (1)当我们需要频繁的对字符串进行更改的时候,使用 StringBuffer或StringBuilder是优先选择,对于 StringBuffer和StringBuilder来说,只要程序是线程安全的,我们尽量使用StringBuilder来处理,要求线程安全的话只能使用StringBuffer。平常情况下使用字符串(不常更改字符串内容),String可以满足需求。
    (2)有一种情况下使用String和 StringBuffer或StringBuilder的效率是差不多的,如下:        
<span style="white-space:pre"> </span>String a = "abc" + "def"; //速度很快
StringBuilder sb = new StringBuilder();
sb.append("abc").append("def");
    对于第一条语句,Java在编译的时候直接把a编译成 a = "abcdef",但是当我们拼接的字符串是其他已定义的字符串对象时,就不会自动编译了,如下:        
<span style="white-space:pre"> </span>String a = "abc";
String b = "def";
String c = a + b;
根据String源码中的解释,这种情况下是使用concatenation操作符(+),内部是新创建StringBuffer或StringBuilder对象,利用其append方法进行字符串追加,然后利用toString方法返回String串。所以此时的效率也是不高的。

6、Map、Set、List、Queue、Stack的特点与用法
Java集合类用来保存对象集合,对于基本类型,必须要使用其包装类型。Java集合框架分为两种:
(1)Collection
    以单个对象的形式保存,其关系图如下:
    
其中,Statck类为Vector的子类。由于Collection类继承Iterable类,所以,所有Collection的实现类都可以通过foreach的方式进行遍历。

(2)Map
    以<key,value>键值对的形式保存数据。Map常用类的结构关系如下:


List:
    List集合里面存放的元素有序、可重复。List集合的有序体现在它默认是按照我们的添加顺序设置索引值(即我们可以通过get(索引值index)的方式获取对象);可重复,是由于我们给每个元素设置了索引值,可以通过索引值找到相应的对象。
    关于List集合下的具体实现类ArrayList、Vector、LinkedList可以参考上面的第四点总结。对于Statck,它是Vector的子类,模拟了栈后进先出的数据结构。

Queue:
    接口,模拟了队列先进先出的数据结构。

Set:
    Set里面的元素无序、不可重复。由于无序性,我们不能通过get方式获取对象(因为set没有索引值)。如下:        
<span style="white-space:pre"> </span>Set<String> set = new HashSet<String>();
set.add("ddd");
set.add("4444");
set.add("555");
set.add("777");
for(String s : set){
System.out.println(s);
}
    打印结果如下:
     ddd
        777
        4444
        555

    而对于不可重复性,Set的所有具体实现类其内部都是通过Map的实现类来保存对象的。如HashSet内部就是通过HashMap来保存数据的,如下源码:     
// Dummy value to associate with an Object in the backing Map--一个没有实际意义的Object对象
private static final Object PRESENT = new Object();
public HashSet() {
map = new HashMap<>();
}
public boolean add(E e) {
return map.put(e, PRESENT)==null;
}
    可以看到,new出一个HashSet的时候,里面new出了一个HashMap对象,在使用HashSet进行add添加数据的时候,HashSet将我们需要保存的数据作为HashMap 的key值保存了起来,而key值是不允许重复的,相当于HashSet的元素也是不可重复的。

Map:
    Map里面的元素通过<key,value>键值对的形式保存,不允许重复(具体分析可参考第二点总结内容)。其实Map<key,value>有点像Java中的Model对象类,如下使用:    
class Person{
private String name;
private int age;
//set、get方法省略
}
List<Person> persons ;
//也可使用如下方式
List<Map<String,String>> persons ;
Map<String,String> map = new HashMap<String,String>();
map.put("name","lly");
map.put("age","18");
persons .add(map);

1、HashMap、HashTable、ConcurrentHashMap的区别
    【参考:http://www.cnblogs.com/carbs/archive/2012/07/04/2576995.html】
    (关于HashMap的分析,在第三篇总结《Java笔试面试题整理第三波》中的hashCode有分析,同样在这篇中有关于Java容器的介绍。HashMap和HashTable都属于Map类集合。)

    HashMap和HashTable都实现了Map接口,里面存放的元素不保证有序,并且不存在相同元素;

区别(线程安全和保存值是否为null方面):
   (1) HashMap和HashTable在功能上基本相同,但HashMap是线程不安全的,HashTable是线程安全的;

HashMap的put源码如下:    
public V put(K key, V value) {
if (table == EMPTY_TABLE) {
inflateTable(threshold);
}
if (key == null)
return putForNullKey(value); //说明key和value值都是可以为null
int hash = hash(key);
int i = indexFor(hash, table.length);
for (Entry<K,V> e = table[i]; e != null; e = e.next) {
Object k;
if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
V oldValue = e.value;
e.value = value;
e.recordAccess(this);
return oldValue;
}
}

modCount++;
addEntry(hash, key, value, i);
return null;
}
(2)可以看到,HashMap的key和value都是可以为null的,当get()方法返回null值时,HashMap中可能存在某个key,只不过该key值对应的value为null,也有可能是HashM中不存在该key,所以不能使用get()==null来判断是否存在某个key值,对于HashMap和HashTable,提供了containsKey()方法来判断是否存在某个key。

HashTable的put源码如下:    
public synchronized V put(K key, V value) {
// Make sure the value is not null
if (value == null) { //当value==null的时候,会抛出异常
throw new NullPointerException();
}

// Makes sure the key is not already in the hashtable.
Entry tab[] = table;
int hash = hash(key);
int index = (hash & 0x7FFFFFFF) % tab.length;
for (Entry<K,V> e = tab[index] ; e != null ; e = e.next) {
if ((e.hash == hash) && e.key.equals(key)) {
V old = e.value;
e.value = value;
return old;
}
}

modCount++;
if (count >= threshold) {
// Rehash the table if the threshold is exceeded
rehash();

tab = table;
hash = hash(key);
index = (hash & 0x7FFFFFFF) % tab.length;
}

// Creates the new entry.
Entry<K,V> e = tab[index];
tab[index] = new Entry<>(hash, key, value, e);
count++;
return null;
}
  
(3)HashTable是不允许key和value为null的。HashTable中的方法大部分是同步的,因此HashTable是线程安全的。

拓展:
   (1) 影响HashMap(或HashTable)性能的两个因素:初始容量和load factor;
        HashMap中有如下描述:        When the number of entries in the hash table exceeds the product of the load factor and the current capacity, 
the hash table is <i>rehashed</i> (that is, internal data structures are rebuilt) 
        当我们Hash表中数据记录的大小超过当前容量,Hash表会进行rehash操作,其实就是自动扩容,这种操作一般会比较耗时。所以当我们能够预估Hash表大小时,在初始化的时候就尽量指定初始容量,避免中途Hash表重新扩容操作,如:      
HashMap<String, Integer> map = new HashMap<String, Integer>(20);
        
(类似可以指定容量的还有ArrayList、Vector)

   (2)使用选择上,当我们需要保证线程安全,HashTable优先选择。当我们程序本身就是线程安全的,HashMap是优先选择。
        其实HashTable也只是保证在数据结构层面上的同步,对于整个程序还是需要进行多线程并发控制;在JDK后期版本中,对于HashMap,可以通过Collections获得同步的HashMap;如下:
        Map m = Collections.synchronizedMap(new HashMap(...));
        这种方式获得了具有同步能力的HashMap。        

    (3)在JDK1.5以后,出现了ConcurrentHashMap,它可以很好地解决在并发程序中使用HashMap的问题,ConcurrentHashMap和HashTable功能很像,不允许为null的key或value,但它不是通过给方法加synchronized方法进行并发控制的。
     在ConcurrentHashMap中使用分段锁技术Segment,将数据分成一段一段的存储,然后给每一段数据配一把锁,当一个线程占用锁访问其中一个段数据的时候,其他段的数据也能被其他线程访问,能够实现真正的并发访问。效率也比HashTable好的多。

(关于ConcurrentHashMap具体可以参考:
http://qifuguang.me/2015/09/10/[Java并发包学习八]深度剖析ConcurrentHashMap/
http://blog.csdn.net/xuefeng0707/article/details/40834595
http://blog.csdn.net/xuefeng0707/article/details/40797085)

2、TreeMap、HashMap、LinkedHashMap的区别
    关于Map集合,前面几篇都有讲过,可以去回顾一下。而TreeMap、HashMap、LinkedHashMap都是Map的一些具体实现类,其关系图如下:

其中,HashMap和HashTable主要区别在线程安全方面和存储null值方面。HashMap前面讨论的已经比较多了,下面说说LinkedHashMap和TreeMap。
(1)LinkedHashMap保存了数据的插入顺序,底层是通过一个双链表的数据结构来维持这个插入顺序的。key和value都可以为null;
(2)TreeMap实现了SortMap接口,它保存的记录是根据键值key排序,默认是按key升序排列。也可以指定排序的Comparator。

HashMap、LinkedHashMap和TreeMap都是线程不安全的,HashTable是线程安全的。提供两种遍历Map的方法如下:
(1)推荐方式:     
Map<String, Integer> map = new HashMap<String, Integer>(20);
for(Map.Entry<String, Integer> entry : map.entrySet()){ //直接遍历出Entry
System.out.println("key-->"+entry.getKey()+",value-->"+m.get(entry.getValue()));
}
        这种方式相当于首先通过Set<Map.Entry<String,Integer>> set =  map.entrySet();方式拿到Set集合,而Set集合是可以通过foreach的方式遍历的。

(2) 普通方式:     
Map<String, Integer> map = new HashMap<String, Integer>(20);
Iterator<String> keySet = map.keySet().iterator(); //遍历Hash表中的key值集合,通过key获取value
while(keySet .hasNext()){
Object key = keySet .next();
System.out.println("key-->"+key+",value-->"+m.get(key));
}

3、Collection包结构,与Collections的区别。
Collection的包结构如下:

Statck类为Vector的子类。由于Collection类继承Iterable类,所以,所有Collection的实现类都可以通过foreach的方式进行遍历。

Collections是针对集合类的一个帮助类。提供了一系列静态方法实现对各种集合的搜索、排序、线程完全化等操作。 
当于对Array进行类似操作的类——Arrays。 
如,Collections.max(Collection coll); 取coll中最大的元素。 
       Collections.sort(List list); 对list中元素排序 

4、OOM你遇到过哪些情况,SOF你遇到过哪些情况
    OOM:OutOfMemoryError异常,
    即内存溢出,是指程序在申请内存时,没有足够的空间供其使用,出现了Out Of Memory,也就是要求分配的内存超出了系统能给你的,系统不能满足需求,于是产生溢出。
    内存溢出分为上溢和下溢,比方说栈,栈满时再做进栈必定产生空间溢出,叫上溢,栈空时再做退栈也产生空间溢出,称为下溢。
    
    有时候内存泄露会导致内存溢出,所谓内存泄露(memory leak),是指程序在申请内存后,无法释放已申请的内存空间,一次内存泄露危害可以忽略,但内存泄露堆积后果很严重,无论多少内存,迟早会被占光,举个例子,就是说系统的篮子(内存)是有限的,而你申请了一个篮子,拿到之后没有归还(忘记还了或是丢了),于是造成一次内存泄漏。在你需要用篮子的时候,又去申请,如此反复,最终系统的篮子无法满足你的需求,最终会由内存泄漏造成内存溢出。

    遇到的OOM:
    (1)Java Heap 溢出
    Java堆用于存储对象实例,我们只要不断的创建对象,而又没有及时回收这些对象(即内存泄漏),就会在对象数量达到最大堆容量限制后产生内存溢出异常。
    (2)方法区溢出
   方法区用于存放Class的相关信息,如类名、访问修饰符、常量池、字段描述、方法描述等。
异常信息:java.lang.OutOfMemoryError:PermGen space

方法区溢出也是一种常见的内存溢出异常,一个类如果要被垃圾收集器回收,判定条件是很苛刻的。在经常动态生成大量Class的应用中,要特别注意这点。


SOF:StackOverflow(堆栈溢出)
    当应用程序递归太深而发生堆栈溢出时,抛出该错误。因为栈一般默认为1-2m,一旦出现死循环或者是大量的递归调用,在不断的压栈过程中,造成栈容量超过1m而导致溢出。
    栈溢出的原因:
    (1)递归调用
    (2)大量循环或死循环
    (3)全局变量是否过多
    (4)数组、List、Map数据过大

OOM在Android开发中出现比较多:
   场景有: 加载的图片太多或图片过大时、分配特大的数组、内存相应资源过多没有来不及释放等。

解决方法:
    (1)在内存引用上做处理
        软引用是主要用于内存敏感的高速缓存。在jvm报告内存不足之前会清除所有的软引用,这样以来gc就有可能收集软可及的对象,可能解决内存吃紧问题,避免内存溢出。什么时候会被收集取决于gc的算法和gc运行时可用内存的大小。
    (2)对图片做边界压缩,配合软引用使用
    (3)显示的调用GC来回收内存,如:
        if(bitmapObject.isRecycled()==false) //如果没有回收  
       bitmapObject.recycle();
  (4)优化Dalvik虚拟机的堆内存分配
            》增强程序堆内存的处理效率    
        //在程序onCreate时就可以调用 即可
        privatefinalstaticfloat TARGET_HEAP_UTILIZATION = 0.75f; 
        VMRuntime.getRuntime().setTargetHeapUtilization(TARGET_HEAP_UTILIZATION);

            》设置堆内存的大小
            privatefinalstaticintCWJ_HEAP_SIZE = 6* 1024* 1024;
      //设置最小heap内存为6MB大小
      VMRuntime.getRuntime().setMinimumHeapSize(CWJ_HEAP_SIZE);

    (5)用LruCache 和 AsyncTask<>解决
        从cache中去取Bitmap,如果取到Bitmap,就直接把这个Bitmap设置到ImageView上面。
  如果缓存中不存在,那么启动一个task去加载(可能从文件来,也可能从网络)。

5、Java面向对象的三个特征与含义,多态的实现方式
Java中两个非常重要的概念:类和对象。类可以看做是一个模板,描述了一类对象的属性和行为;而对象是类的一个具体实现。Java面向对象的三大基本特征:
(1)封装
    属性用来描述同一类事物的特征,行为用来描述同一类事物可做的一些操作。封装就是把属于同一类事物的共性(属性和行为)归到一个类中,只保留有限的接口和方法与外部进行交互,避免了外界对对象内部属性的破坏。Java中使用访问控制符来保护对类、属性、方法的访问。
(2)继承
    子类通过这种方式来接收父类所有的非private的属性和方法(构造方法除外)。这里的接收是直接拥有的意思,即可以直接使用父类字段和方法,因此,继承相当于“扩展”,子类在拥有了父类的属性和特征后,可以专心实现自己特有的功能。
    (构造方法不能被继承,因为在创建子类时,会先去自动“调用”父类的构造方法,如果真的需要子类构造函数特殊的形式,子类直接修改或重载自己的构造函数就好了。)
(3)多态
    多态是程序在运行的过程中,同一种类型在不同的条件下表现不同的结果。比如:
        Animal  a = new Dog();    // 子类对象当做父类对象来使用,运行时,根据对象的实际类型去找子类覆盖之后的方法

多态实现方式:
    (1)设计时多态,通过方法的重载实现多态;
    (2)运行时多态,通过重写父类或接口的方法实现运行时多态;

6、interface与abstract类的区别
    abstract class 只能被继承extends,体现的是一种继承关系,而根据继承的特征,有继承关系的子类和父类应该是一种“is-a”的关系,也即两者在本质上应该是相同的(有共同的属性特征)。
    interface 是用来实现的implements,它并不要求实现者和interface之间在本质上相同,是一种“like-a”的关系,interface只是定义了一系列的约定而已(实现者表示愿意遵守这些约定)。所以一个类可以去实现多个interface(即该类遵守了多种约定)。
    很多情况下interface和abstract都能满足我们要求,在我们选择用abstract火interface的时候,尽量符合上面的要求,即如果两者间本质是一样的,是一种“is-a”的关系,尽量用abstract,当两者之间本质不同只是简单的约定行为的话,可以选择interface。
特点:
(1)abstract类其实和普通类一样,拥有有自己的数据成员和方法,只不过abstract类里面可以定义抽象abstract的方法(声明为abstract的类也可以不定义abstract的方法,直接当做普通类使用,但是这样就失去了抽象类的意义)。
(2)一个类中声明了abstract的方法,该类必须声明为abstract类。
(3)interface中只能定义常量和抽象方法。在接口中,我们定义的变量默认为public static final类型,所以不可以在显示类中修改interface中的变量;定义的方法默认为public abstract,其中abstract可以不明确写出。

7、static class 与non static class的区别
static class--静态内部类,non static class--非静态内部类,即普通内部类

普通内部类:
    内部类可以直接使用外部类的所有变量(包括private、静态变量、普通变量),这也是内部类的主要优点(不用生成外部类对象而直接使用外部类变量)。如下例子:
public class OutClass {
private String mName = "lly";
static int mAge = 12;

class InnerClass{
String name;
int age;
private void getName(){
name = mName;
age = mAge;
System.out.println("name="+name+",age="+age);
}
}

public static void main(String[] args) {
//第一种初始化内部类方法
OutClass.InnerClass innerClass = new OutClass().new InnerClass();
innerClass.getName();
//第二种初始化内部类方法
OutClass out = new OutClass();
InnerClass in = out.new InnerClass();
in.getName();
}
}
输出:name=lly,age=12

可以看到,内部类里面可以直接访问外部类的静态和非静态变量,包括private变量。在内部类中,我们也可以通过外部类.this.变量名的方式访问外部类变量,如:name = OutClass.this.mName; 
内部类的初始化依赖于外部类,只有外部类初始化出来了,内部类才能够初始化。

私有内部类(包括私有静态内部类和私有非静态内部类):
如果一个内部类只希望被外部类中的方法操作,那只要给该内部类加上private修饰,声明为private 的内部类只能在外部类中使用,不能在别的类中new出来。如上private class InnerClass{...},此时只能在OutClass类中使用。

静态内部类:
静态内部类只能访问外部类中的静态变量。如下:
public class OutClass {
private String mName = "lly";
static int mAge = 12;

static class StaticClass{
String name = "lly2";
int age;
private void getName(){
// name = mName; //不能引用外部类的非静态成员变量
age = mAge;
System.out.println("name="+name+",age="+age);
}
}

public static void main(String[] args) {
//第一种初始化静态内部类方法
OutClass.StaticClass staticClass = new OutClass.StaticClass();
staticClass.getName();
//或者直接使用静态内部类初始化
StaticClass staticClass2 = new StaticClass();
staticClass2.getName();
}
}
输出:name=lly2,age=12

可以看到,静态内部类只能访问外部类中的静态变量,静态内部类的初始化不依赖于外部类,由于是static,类似于方法使用,OutClass.StaticClass是一个整体。

匿名内部类:
匿名内部类主要是针对抽象类和接口的具体实现。在Android的监听事件中用的很多。如:    
textView.setOnClickListener(new View.OnClickListener(){ //OnClickListener为一个接口interface
public void onClick(View v){
...
}
});

对于抽象类:
public abstract class Animal {
public abstract void getColor();
}

public class Dog{
public static void main(String[] args) {
Animal dog = new Animal() {
@Override
public void getColor() {
System.out.println("黑色");
}
};
dog.getColor();
}
}
输出:黑色

对于接口类似,只需要把abstract class 改为interface即可。

猜你喜欢

转载自www.cnblogs.com/lucky1024/p/11078564.html