java面试技巧总结

作者 Lee.QJ 创建于2003-01-20 02:45:26 修改者Lee.QJ 修改于2003-01-20 02:45:26字数25415
文档摘要:java面试技巧总结
关键词:
1. 说说集合框架。 2. Map遍历的两种方式 3. 说说迭代器 4. 说说线程池的理解?创建线程的两种方式 5. IO流中newLine()方法能不能用于字节流中? 6. 复制mp3、复制文件夹下所有文件的思路 7. 反射、类加载器、动态代理之间的关系 8. 类加载器的委托机制 9. 为什么自定义类加载器 10. 系统默认的3个类加载器? 11. 加载类字节码的3种方式 12动态代理的3种实现方式 集合的体系以及各子类的特点,多线程的两种创建方式,7k中用到的多线程的创建方式(Executors)什么是反射, 字节码的三种获取方式,动态代理,单例的饿汉式和懒汉式,synchronized在单例中哪里用到了, 什么是同步,为什么需要用同步,sleep和notify的区别. 面向对象基础 JAVA基础语法自行掌握. 三大特性: 一 封装:★★★★★ 概念:是指隐藏对象的属性和实现细节,仅对外提供公共访问方式。 好处:将变化隔离;便于使用;提高重用性;安全性。 封装原则:将不需要对外提供的内容都隐藏起来,把属性都隐藏,提供公共方法对其访问。 单例设计模式:★★★★★(必问的) 解决的问题:保证一个类在内存中的对象唯一性。 比如:多程序读取一个配置文件时,建议配置文件封装成对象。会方便操作其中数据,又要保证多个程序读到的是同一个配置文件对象,就需要该配置文件对象在内存中是唯一的。 Runtime()方法就是单例设计模式进行设计的。 如何保证对象唯一性呢? 思想: 1,不让其他程序创建该类对象。 2,在本类中创建一个本类对象。 3,对外提供方法,让其他程序获取这个对象。 步骤: 1,因为创建对象都需要构造函数初始化,只要将本类中的构造函数私有化,其他程序就无法再创建该类对象; 2,就在类中创建一个本类的对象; 3,定义一个方法,返回该对象,让其他程序可以通过方法就得到本类对象。(作用:可控) 代码体现: 1,私有化构造函数; 2,创建私有并静态的本类对象; 3,定义公有并静态的方法,返回该对象。 --------------------------------------------- //饿汉式 class Single{ private Single(){} //私有化构造函数。 private static Single s = new Single(); //创建私有并静态的本类对象。 public static Single getInstance(){ //定义公有并静态的方法,返回该对象。 return s; } } --------------------------------------------- //懒汉式:延迟加载方式。 class Single2{ private Single2(){} private static Single2 s = null; public static Single2 getInstance(){ if(s==null) s = new Single2(); return s; } } 二 继承 ★★★★★ 好处: 1:提高了代码的复用性。 2:让类与类之间产生了关系,提供了另一个特征多态的前提。 注意: 子类中所有的构造函数都会默认访问父类中的空参数的构造函数,因为每一个子类构造内第一行都有默认的语句super(); 如果父类中没有空参数的构造函数,那么子类的构造函数内,必须通过super语句指定要访问的父类中的构造函数。 如果子类构造函数中用this来指定调用子类自己的构造函数,那么被调用的构造函数也一样会访问父类中的构造函数。 final特点: 1:这个关键字是一个修饰符,可以修饰类,方法,变量。 2:被final修饰的类是一个最终类,不可以被继承。 3:被final修饰的方法是一个最终方法,不可以被覆盖。 4:被final修饰的变量是一个常量,只能赋值一次。 抽象类的特点: 1:抽象方法只能定义在抽象类中,抽象类和抽象方法必须由abstract关键字修饰(可以描述类和方法,不可以描述变量)。 2:抽象方法只定义方法声明,并不定义方法实现。 3:抽象类不可以被创建对象(实例化)。 4:只有通过子类继承抽象类并覆盖了抽象类中的所有抽象方法后,该子类才可以实例化。否则,该子类还是一个抽象类。 5: 抽象类只能单继承。 抽象类的细节: 1:抽象类中是否有构造函数?有,用于给子类对象进行初始化。 2:抽象类中是否可以定义非抽象方法? 可以。其实,抽象类和一般类没有太大的区别,都是在描述事物,只不过抽象类在描述事物时,有些功能不具体。所以抽象类和一般类在定义上,都是需要定义属性和行为的。只不过,比一般类多了一个抽象函数。而且比一般类少了一个创建对象的部分。 3:抽象关键字abstract和哪些不可以共存?final , private , static 4:抽象类中可不可以不定义抽象方法?可以。抽象方法目的仅仅为了不让该类创建对象。 接 口:★★★★★ 抽象类和接口的区别:(问的比较多,概念性的问题) 1:抽象类只能被继承,而且只能单继承。 接口需要被实现,而且可以多实现。 2:抽象类中可以定义非抽象方法,子类可以直接继承使用。 接口中都有抽象方法,需要子类去实现。 3:抽象类使用的是 is a 关系。 接口使用的 like a 关系。 4:抽象类的成员修饰符可以自定义。 接口中的成员修饰符是固定的。全都是public的。 三 多态(在后期开发中常用) 多 态★★★★★(面向对象特征之一):函数本身就具备多态性,某一种事物有不同的具体的体现。 体现:父类引用或者接口的引用指向了自己的子类对象。//Animal a = new Cat(); 多态的好处:提高了程序的扩展性。 多态的弊端:当父类引用指向子类对象时,虽然提高了扩展性,但是只能访问父类中具备的方法,不可以访问子类中特有的方法。(前期不能使用后期产生的功能,即访问的局限性) 多态的前提: 1:必须要有关系,比如继承、或者实现。 2:通常会有覆盖操作。 匿名内部类:没有名字的内部类。就是内部类的简化形式。一般只用一次就可以用这种形式。匿名内部类其实就是一个匿名子类对象。想要定义匿名内部类:需要前提,内部类必须继承一个类或者实现接口。 匿名内部类的格式:new 父类名&接口名(){ 定义子类成员或者覆盖父类方法 }.方法。 匿名内部类的使用场景: 当函数的参数是接口类型引用时,如果接口中的方法不超过3个。可以通过匿名内部类来完成参数的传递。 其实就是在创建匿名内部类时,该类中的封装的方法不要过多,最好两个或者两个以内。 异 常:★★★ 异常处理原则:功能抛出几个异常,功能调用如果进行try处理,需要与之对应的catch处理代码块,这样的处理有针对性,抛几个就处理几个。 特殊情况:try对应多个catch时,如果有父类的catch语句块,一定要放在下面。 throw 和throws关键字的区别: throw用于抛出异常对象,后面跟的是异常对象;throw用在函数内。 throws用于抛出异常类,后面跟的异常类名,可以跟多个,用逗号隔开。throws用在函数上。 3多线程 线程的2种创建方式(必问) 创建线程的第一种方式:继承Thread ,由子类复写run方法。 步骤: 1,定义类继承Thread类; 2,目的是复写run方法,将要让线程运行的代码都存储到run方法中; 3,通过创建Thread类的子类对象,创建线程对象; 4,调用线程的start方法,开启线程,并执行run方法。 线程状态: 新建:start() 运行:具备执行资格,同时具备执行权; 冻结:sleep(time),wait()—notify()唤醒;线程释放了执行权,同时释放执行资格; 临时阻塞状态:线程具备cpu的执行资格,没有cpu的执行权; 消亡:stop() 创建线程的第二种方式:实现一个接口Runnable。 步骤: 1,定义类实现Runnable接口。 2,覆盖接口中的run方法(用于封装线程要运行的代码)。 3,通过Thread类创建线程对象; 4,将实现了Runnable接口的子类对象作为实际参数传递给Thread类中的构造函数。 为什么要传递呢?因为要让线程对象明确要运行的run方法所属的对象。 5,调用Thread对象的start方法。开启线程,并运行Runnable接口子类中的run方法。 Ticket t = new Ticket(); /* 直接创建Ticket对象,并不是创建线程对象。 因为创建对象只能通过new Thread类,或者new Thread类的子类才可以。 所以最终想要创建线程。既然没有了Thread类的子类,就只能用Thread类。 */ Thread t1 = new Thread(t); //创建线程。 /* 只要将t作为Thread类的构造函数的实际参数传入即可完成线程对象和t之间的关联 为什么要将t传给Thread类的构造函数呢?其实就是为了明确线程要运行的代码run方法。 */ 多线程安全问题的原因: 通过图解:发现一个线程在执行多条语句时,并运算同一个数据时,在执行过程中,其他线程参与进来,并操作了这个数据。导致到了错误数据的产生。 涉及到两个因素: 1,多个线程在操作共享数据。 2,有多条语句对共享数据进行运算。 原因:这多条语句,在某一个时刻被一个线程执行时,还没有执行完,就被其他线程执行了。 解决安全问题的原理: 只要将操作共享数据的语句在某一时段让一个线程执行完,在执行过程中,其他线程不能进来执行就可以解决这个问题。 如何进行多句操作共享数据代码的封装呢? java中提供了一个解决方式:就是同步代码块。 格式: synchronized(对象) { // 任意对象都可以。这个对象就是锁。 需要被同步的代码; } Synchronized(自己得会写得出) wait和sleep区别: 分析这两个方法:从执行权和锁上来分析: wait:可以指定时间也可以不指定时间。不指定时间,只能由对应的notify或者notifyAll来唤醒。 sleep:必须指定时间,时间到自动从冻结状态转成运行状态(临时阻塞状态)。 wait:线程会释放执行权,而且线程会释放锁。 Sleep:线程会释放执行权,但不是不释放锁。 什么时候产生死锁,该怎么办? 12. 集合框架 自己能画出来,并且附带方法和步骤以及特性以及各自的遍历方式. 常用的ArrayList HashSet Collection: |--List:有序(元素存入集合的顺序和取出的顺序一致),元素都有索引。元素可以重复。 |--Set:无序(存入和取出顺序有可能不一致),不可以存储重复元素。必须保证元素唯一性。 1,添加: add(object):添加一个元素 addAll(Collection) :添加一个集合中的所有元素。 2,删除: clear():将集合中的元素全删除,清空集合。 remove(obj) :删除集合中指定的对象。注意:删除成功,集合的长度会改变。 removeAll(collection) :删除部分元素。部分元素和传入Collection一致。 3,判断: boolean contains(obj) :集合中是否包含指定元素 。 boolean containsAll(Collection) :集合中是否包含指定的多个元素。 boolean isEmpty():集合中是否有元素。 4,获取: int size():集合中有几个元素。 5,取交集: boolean retainAll(Collection) :对当前集合中保留和指定集合中的相同的元素。如果两个集合元素相同,返回flase;如果retainAll修改了当前集合,返回true。 6,获取集合中所有元素: Iterator iterator():迭代器 7,将集合变成数组: toArray(); List本身是Collection接口的子接口,具备了Collection的所有方法。现在学习List体系特有的共性方法,查阅方法发现List的特有方法都有索引,这是该集合最大的特点。 List:有序(元素存入集合的顺序和取出的顺序一致),元素都有索引。元素可以重复。 |--ArrayList:底层的数据结构是数组,线程不同步,ArrayList替代了Vector,查询元素的速度非常快。 |--LinkedList:底层的数据结构是链表,线程不同步,增删元素的速度非常快。 |--Vector:底层的数据结构就是数组,线程同步的,Vector无论查询和增删都巨慢。 1,添加: add(index,element) :在指定的索引位插入元素。 addAll(index,collection) :在指定的索引位插入一堆元素。 2,删除: remove(index) :删除指定索引位的元素。 返回被删的元素。 3,获取: Object get(index) :通过索引获取指定元素。 int indexOf(obj) :获取指定元素第一次出现的索引位,如果该元素不存在返回-1; 所以,通过-1,可以判断一个元素是否存在。 int lastIndexOf(Object o) :反向索引指定元素的位置。 List subList(start,end) :获取子列表。 4,修改: Object set(index,element) :对指定索引位进行元素的修改。 5,获取所有元素: ListIterator listIterator():list集合特有的迭代器。 List集合支持对元素的增、删、改、查。 List集合因为角标有了自己的获取元素的方式: 遍历。 for(int x=0; x-- Set接口: Set接口中的方法和Collection中方法一致的。Set接口取出方式只有一种,迭代器。 |--HashSet:底层数据结构是哈希表,线程是不同步的。无序,高效; HashSet集合保证元素唯一性:通过元素的hashCode方法,和equals方法完成的。 当元素的hashCode值相同时,才继续判断元素的equals是否为true。 如果为true,那么视为相同元素,不存。如果为false,那么存储。 如果hashCode值不同,那么不判断equals,从而提高对象比较的速度。 |--LinkedHashSet:有序,hashset的子类。 |--TreeSet:对Set集合中的元素的进行指定顺序的排序。不同步。TreeSet底层的数据结构就是二叉树。 哈希表的原理: 1,对对象元素中的关键字(对象中的特有数据),进行哈希算法的运算,并得出一个具体的算法值,这个值 称为哈希值。 2,哈希值就是这个元素的位置。 3,如果哈希值出现冲突,再次判断这个关键字对应的对象是否相同。如果对象相同,就不存储,因为元素重复。如果对象不同,就存储,在原来对象的哈希值基础 +1顺延。 4,存储哈希值的结构,我们称为哈希表。 5,既然哈希表是根据哈希值存储的,为了提高效率,最好保证对象的关键字是唯一的。 这样可以尽量少的判断关键字对应的对象是否相同,提高了哈希表的操作效率。 对于ArrayList集合,判断元素是否存在,或者删元素底层依据都是equals方法。 对于HashSet集合,判断元素是否存在,或者删除元素,底层依据的是hashCode方法和equals方法。 TreeSet: 用于对Set集合进行元素的指定顺序排序,排序需要依据元素自身具备的比较性。 如果元素不具备比较性,在运行时会发生ClassCastException异常。 所以需要元素实现Comparable接口,强制让元素具备比较性,复写compareTo方法。 依据compareTo方法的返回值,确定元素在TreeSet数据结构中的位置。 TreeSet方法保证元素唯一性的方式:就是参考比较方法的结果是否为0,如果return 0,视为两个对象重复,不存。 注意:在进行比较时,如果判断元素不唯一,比如,同姓名,同年龄,才视为同一个人。 在判断时,需要分主要条件和次要条件,当主要条件相同时,再判断次要条件,按照次要条件排序。 TreeSet集合排序有两种方式,Comparable和Comparator区别: 1:让元素自身具备比较性,需要元素对象实现Comparable接口,覆盖compareTo方法。 2:让集合自身具备比较性,需要定义一个实现了Comparator接口的比较器,并覆盖compare方法,并将该类对象作为实际参数传递给TreeSet集合的构造函数。 第二种方式较为灵活。 ------------------------------------------------------------ Map集合: |--Hashtable:底层是哈希表数据结构,是线程同步的。不可以存储null键,null值。 |--HashMap:底层是哈希表数据结构,是线程不同步的。可以存储null键,null值。替代了Hashtable. |--TreeMap:底层是二叉树结构,可以对map集合中的键进行指定顺序的排序。 Map集合存储和Collection有着很大不同: Collection一次存一个元素;Map一次存一对元素。 Collection是单列集合;Map是双列集合。 Map中的存储的一对元素:一个是键,一个是值,键与值之间有对应(映射)关系。 特点:要保证map集合中键的唯一性。 1,添加。 put(key,value):当存储的键相同时,新的值会替换老的值,并将老值返回。如果键没有重复,返回null。 void putAll(Map); 2,删除。 void clear():清空 value remove(key) :删除指定键。 3,判断。 boolean isEmpty(): boolean containsKey(key):是否包含key boolean containsValue(value) :是否包含value 4,取出。 int size():返回长度 value get(key) :通过指定键获取对应的值。如果返回null,可以判断该键不存在。当然有特殊情况,就是在hashmap集合中,是可以存储null键null值的。 Collection values():获取map集合中的所有的值。 5,想要获取map中的所有元素: 原理:map中是没有迭代器的,collection具备迭代器,只要将map集合转成Set集合,可以使用迭代器了。之所以转成set,是因为map集合具备着键的唯一性,其实set集合就来自于map,set集合底层其实用的就是map的方法。 ★ 把map集合转成set的方法: Set keySet(); Set entrySet();//取的是键和值的映射关系。 Entry就是Map接口中的内部接口; 为什么要定义在map内部呢?entry是访问键值关系的入口,是map的入口,访问的是map中的键值对。 --------------------------------------------------------- 取出map集合中所有元素的方式一:keySet()方法。 可以将map集合中的键都取出存放到set集合中。对set集合进行迭代。迭代完成,再通过get方法对获取到的键进行+值的获取。 Set keySet = map.keySet(); Iterator it = keySet.iterator(); while(it.hasNext()) { Object key = it.next(); Object value = map.get(key); System.out.println(key+":"+value); } -------------------------------------------------------- 取出map集合中所有元素的方式二:entrySet()方法。 Set entrySet = map.entrySet(); Iterator it = entrySet.iterator(); while(it.hasNext()) { Map.Entry me = (Map.Entry)it.next(); System.out.println(me.getKey()+"::::"+me.getValue()); } -------------------------------------------------------- 使用集合的技巧: 看到Array就是数组结构,有角标,查询速度很快。 看到link就是链表结构:增删速度快,而且有特有方法。addFirst; addLast; removeFirst(); removeLast(); getFirst();getLast(); 看到hash就是哈希表,就要想要哈希值,就要想到唯一性,就要想到存入到该结构的中的元素必须覆盖hashCode,equals方法。 看到tree就是二叉树,就要想到排序,就想要用到比较。 比较的两种方式: 一个是Comparable:覆盖compareTo方法; 一个是Comparator:覆盖compare方法。 LinkedHashSet,LinkedHashMap:这两个集合可以保证哈希表有存入顺序和取出顺序一致,保证哈希表有序。 集合什么时候用? 当存储的是一个元素时,就用Collection。当存储对象之间存在着映射关系时,就使用Map集合。 保证唯一,就用Set。不保证唯一,就用List。 ------------------------------------------------------------------------------------------------ Collections:它的出现给集合操作提供了更多的功能。这个类不需要创建对象,内部提供的都是静态方法。 静态方法: Collections.sort(list);//list集合进行元素的自然顺序排序。 Collections.sort(list,new ComparatorByLen());//按指定的比较器方法排序。 class ComparatorByLen implements Comparator{ public int compare(String s1,String s2){ int temp = s1.length()-s2.length(); return temp==0?s1.compareTo(s2):temp; } } Collections.max(list); //返回list中字典顺序最大的元素。 int index = Collections.binarySearch(list,"zz");//二分查找,返回角标。 Collections.reverseOrder();//逆向反转排序。 Collections.shuffle(list);//随机对list中的元素进行位置的置换。 将非同步集合转成同步集合的方法:Collections中的 XXX synchronizedXXX(XXX); List synchronizedList(list); Map synchronizedMap(map); 原理:定义一个类,将集合所有的方法加同一把锁后返回。 Collection 和 Collections的区别: Collections是个java.util下的类,是针对集合类的一个工具类,提供一系列静态方法,实现对集合的查找、排序、替换、线程安全化(将非同步的集合转换成同步的)等操作。 Collection是个java.util下的接口,它是各种集合结构的父接口,继承于它的接口主要有Set和List,提供了关于集合的一些操作,如插入、删除、判断一个元素是否其成员、遍历等。 ------------------------------------------------------- Arrays: 用于操作数组对象的工具类,里面都是静态方法。 asList方法:将数组转换成list集合。 String[] arr = {"abc","kk","qq"}; List list = Arrays.asList(arr);//将arr数组转成list集合。 将数组转换成集合,有什么好处呢?用aslist方法,将数组变成集合; 可以通过list集合中的方法来操作数组中的元素:isEmpty()、contains、indexOf、set; 注意(局限性):数组是固定长度,不可以使用集合对象增加或者删除等,会改变数组长度的功能方法。比如add、remove、clear。(会报不支持操作异常UnsupportedOperationException); 如果数组中存储的引用数据类型,直接作为集合的元素可以直接用集合方法操作。 如果数组中存储的是基本数据类型,asList会将数组实体作为集合元素存在。 集合变数组:用的是Collection接口中的方法:toArray(); 如果给toArray传递的指定类型的数据长度小于了集合的size,那么toArray方法,会自定再创建一个该类型的数据,长度为集合的size。 如果传递的指定的类型的数组的长度大于了集合的size,那么toArray方法,就不会创建新数组,直接使用该数组即可,并将集合中的元素存储到数组中,其他为存储元素的位置默认值null。 所以,在传递指定类型数组时,最好的方式就是指定的长度和size相等的数组。 将集合变成数组后有什么好处?限定了对集合中的元素进行增删操作,只要获取这些元素即可。 ------------------------------------------------------------------------------------------------ JAVA5.0新特性 Jdk5.0新特性: Collection在jdk1.5以后,有了一个父接口Iterable,这个接口的出现的将iterator方法进行抽取,提高了扩展性。 -------------------------------------------------- 增强for循环: foreach语句,foreach简化了迭代器。 格式:// 增强for循环括号里写两个参数,第一个是声明一个变量,第二个就是需要迭代的容器 for( 元素类型 变量名 : Collection集合 & 数组 ) { … } 高级for循环和传统for循环的区别: 高级for循环在使用时,必须要明确被遍历的目标。这个目标,可以是Collection集合或者数组,如果遍历Collection集合,在遍历过程中还需要对元素进行操作,比如删除,需要使用迭代器。 如果遍历数组,还需要对数组元素进行操作,建议用传统for循环因为可以定义角标通过角标操作元素。如果只为遍历获取,可以简化成高级for循环,它的出现为了简化书写。 高级for循环可以遍历map集合吗?不可以。但是可以将map转成set后再使用foreach语句。 1) 、作用:对存储对象的容器进行迭代: 数组 collection map 2) 、增强for循环迭代数组: String [] arr = {"a", "b", "c"};//数组的静态定义方式,只试用于数组首次定义的时候 for(String s : arr) { System.out.println(s); } 3) 、单列集合 Collection: List list = new ArrayList(); list.add("aaa"); // 增强for循环, 没有使用泛型的集合能不能使用增强for循环迭代?能 for(Object obj : list) { String s = (String) obj; System.out.println(s); } 4) 、双列集合 Map: Map map = new HashMap(); map.put("a", "aaa"); // 传统方式:必须掌握这种方式 Set entrys = map.entrySet(); // 1.获得所有的键值对Entry对象 iter = entrys.iterator(); // 2.迭代出所有的entry while(iter.hasNext()) { Map.Entry entry = (Entry) iter.next(); String key = (String) entry.getKey(); // 分别获得key和value String value = (String) entry.getValue(); System.out.println(key + "=" + value); } // 增强for循环迭代:原则上map集合是无法使用增强for循环来迭代的,因为增强for循环只能针对实现了Iterable接口的集合进行迭代;Iterable是jdk5中新定义的接口,就一个方法iterator方法,只有实现了Iterable接口的类,才能保证一定有iterator方法,java有这样的限定是因为增强for循环内部还是用迭代器实现的,而实际上,我们可以通过某种方式来使用增强for循环。 for(Object obj : map.entrySet()) { Map.Entry entry = (Entry) obj; // obj 依次表示Entry System.out.println(entry.getKey() + "=" + entry.getValue()); } 5)、集合迭代注意问题:在迭代集合的过程中,不能对集合进行增删操作(会报并发访问异常);可以用迭代器的方法进行操作(子类listIterator:有增删的方法)。 6)、增强for循环注意问题:在使用增强for循环时,不能对元素进行赋值; int[] arr = {1,2,3}; for(int num : arr) { num = 0; //不能改变数组的值 } System.out.println(arr[1]); //2 -------------------------------------------------- 可变参数(...): 用到函数的参数上,当要操作的同一个类型元素个数不确定的时候,可是用这个方式,这个参数可以接受任意个数的同一类型的数据。 和以前接收数组不一样的是: 以前定义数组类型,需要先创建一个数组对象,再将这个数组对象作为参数传递给函数。现在,直接将数组中的元素作为参数传递即可。底层其实是将这些元素进行数组的封装,而这个封装动作,是在底层完成的,被隐藏了。所以简化了用户的书写,少了调用者定义数组的动作。 如果在参数列表中使用了可变参数,可变参数必须定义在参数列表结尾(也就是必须是最后一个参数,否则编译会失败。)。 如果要获取多个int数的和呢?可以使用将多个int数封装到数组中,直接对数组求和即可。 --------------------------------------------------- 静态导入:导入了类中的所有静态成员,简化静态成员的书写。 import static java.util.Collections.*; //导入了Collections类中的所有静态成员 --------------------------------------------------- 枚举:关键字 enum 问题:对象的某个属性的值不能是任意的,必须为固定的一组取值其中的某一个; 解决办法: 1)、在setGrade方法中做判断,不符合格式要求就抛出异常; 2)、直接限定用户的选择,通过自定义类模拟枚举的方式来限定用户的输入,写一个Grade类,私有构造函数,对外提供5个静态的常量表示类的实例; 5) 、jdk5中新定义了枚举类型,专门用于解决此类问题; 6) 、枚举就是一个特殊的java类,可以定义属性、方法、构造函数、实现接口、继承类; ------------------------------------------------------------------------------ 自动拆装箱:java中数据类型分为两种 : 基本数据类型 引用数据类型(对象) 在 java程序中所有的数据都需要当做对象来处理,针对8种基本数据类型提供了包装类,如下: int --> Integer byte --> Byte short --> Short long --> Long char --> Character double --> Double float --> Float boolean --> Boolean jdk5以前基本数据类型和包装类之间需要互转: 基本---引用 Integer x = new Integer(x); 引用---基本 int num = x.intValue(); 1) 、Integer x = 1; x = x + 1; 经历了什么过程?装箱 à 拆箱 à 装箱; 2) 、为了优化,虚拟机为包装类提供了缓冲池,Integer池的大小 -128~127 一个字节的大小; 3) 、String池:Java为了优化字符串操作 提供了一个缓冲池; ---------------------------------------------------------- 泛型:jdk1.5版本以后出现的一个安全机制。表现格式:< > 好处: 1:将运行时期的问题ClassCastException问题转换成了编译失败,体现在编译时期,程序员就可以解决问题。 2:避免了强制转换的麻烦。 只要带有<>的类或者接口,都属于带有类型参数的类或者接口,在使用这些类或者接口时,必须给<>中传递一个具体的引用数据类型。 泛型技术:其实应用在编译时期,是给编译器使用的技术,到了运行时期,泛型就不存在了。 为什么? 因为泛型的擦除:也就是说,编辑器检查了泛型的类型正确后,在生成的类文件中是没有泛型的。 在运行时,如何知道获取的元素类型而不用强转呢? 泛型的补偿:因为存储的时候,类型已经确定了是同一个类型的元素,所以在运行时,只要获取到该元素的类型,在内部进行一次转换即可,所以使用者不用再做转换动作了。 什么时候用泛型类呢? 当类中的操作的引用数据类型不确定的时候,以前用的Object来进行扩展的,现在可以用泛型来表示。这样可以避免强转的麻烦,而且将运行问题转移到的编译时期。 ---------------------------------------------------------- 泛型在程序定义上的体现: //泛型类:将泛型定义在类上。 class Tool
{ private Q obj; public void setObject(Q obj) { this.obj = obj; } public Q getObject() { return obj; } } //当方法操作的引用数据类型不确定的时候,可以将泛型定义在方法上。 publicvoid method(W w) { System.out.println("method:"+w); } //静态方法上的泛型:静态方法无法访问类上定义的泛型。如果静态方法操作的引用数据类型不确定的时候,必须要将泛型定义在方法上。 public static void function(Q t) { System.out.println("function:"+t); } //泛型接口. interface Inter{ void show(T t); } class InterImpl implements Inter { public void show(R r) { System.out.println("show:"+r); } } ------------------------------------------------------------ 泛型中的通配符:可以解决当具体类型不确定的时候,这个通配符就是 ? ;当操作类型时,不需要使用类型的具体功能时,只使用Object类中的功能。那么可以用 ? 通配符来表未知类型。 泛型限定: 上限:?extends E:可以接收E类型或者E的子类型对象。 下限:?super E:可以接收E类型或者E的父类型对象。 上限什么时候用:往集合中添加元素时,既可以添加E类型对象,又可以添加E的子类型对象。为什么?因为取的时候,E类型既可以接收E类对象,又可以接收E的子类型对象。 下限什么时候用:当从集合中获取元素进行操作的时候,可以用当前元素的类型接收,也可以用当前元素的父类型接收。 泛型的细节: 1)、泛型到底代表什么类型取决于调用者传入的类型,如果没传,默认是Object类型; 2)、使用带泛型的类创建对象时,等式两边指定的泛型必须一致; 原因:编译器检查对象调用方法时只看变量,然而程序运行期间调用方法时就要考虑对象具体类型了; 3)、等式两边可以在任意一边使用泛型,在另一边不使用(考虑向后兼容); ArrayList al = new ArrayList