Java笔记——2017年3月3日
java的发展
Java1.0 Java1.1 Java 1.2(Java2 将Java分为J2SE,J2EE,J2ME) Java 1.3 Java 1.4 Java 5.0(将分成三个版本,改名为JavaSE,JavaEE,JavaME) Java 6.0 Java 7.0 Java 8.0Java数据类型
Java数据类型有两大种:引用数据类型和原始数据类型。 引用数据类型:interface、class、Array、enum 原始数据类型:byte(字节)、short(短整型)、int(整数型)、long(长整型)、float(单精度浮点数类型)、double(双精度浮点数类型)、char(字符类型)、boolean(布尔类型)总共8种。
Java Switch可接受的数据类型
在JDK5中,switch可以接受五种数据类型:byte、short、int、char、enum。 在JDK7中,switch可以接受六种数据类型:多了一种String。
Java提高扩展性
将做什么(interface)和怎么做分开(实现)。
JDK 5.0新特性
一、泛型Generic
泛型的由来
JDK5之前,对象保存到集合中就会失去其特性,取出的时候通常需要程序猿手工进行类型的强制转化,这样不可避免的就会引发程序的一些安全性问题。 众所周知,Java的开发者是一批C++工程师。在C++的语法中,有一门模板技术。事实上,Java5.0的泛型本质上就是C++的模板。
JDK5之前集合对象存在的问题
可以向集合添加任何类型对象;从集合取出对象的时候,数据类型丢失。取出的时候,需要使用与类型相关方法,进行强制类型转换操作。
程序是存在安全隐患。比如下面的例子:
/** * 泛型测试 * */ public class GenericTest { @Test public void demo1() { // JDK5之前集合对象 List list = new ArrayList(); // 因为JDK5之前没有泛型的类型检查,添加的对象可以是任意类型 list.add("abc"); list.add(123); // 操作集合中对象,遍历集合将数据取出来通过size()方法和get(index)方法 // 进行类型的判断处理 for (int i = 0; i < list.size(); i++) { // 提取对象的时候,数据类型丢失了 Object obj = list.get(i); // 操作String方法 -- 强制将数据转换成响应类型 String s = (String) obj; System.out.PRintln(s.toUpperCase()); } } }Java会报错:
java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String (Integer 类型不能转化为String类型)在这里,我们将上述代码放在main函数中进行javac编译
public class GenericTest { public static void main(String[] args) { // JDK5之前集合对象 List list = new ArrayList(); // 因为JDK5之前没有泛型的类型检查,添加的对象可以是任意类型 list.add("abc"); list.add(123); // 操作集合中对象,遍历集合将数据取出来通过size()方法和get(index)方法 // 进行类型的判断处理 for (int i = 0; i < list.size(); i++) { // 提取对象的时候,数据类型丢失了 Object obj = list.get(i); // 操作String方法 -- 强制将数据转换成响应类型 String s = (String) obj; System.out.println(s.toUpperCase()); } } }在cmd中javac编译会出现以下错误提示:
D:\Java\Workspaces\MyEclipse 2015\day03\src\cn\idcast\jdk5>javac -Xlint:unchecked GenericTest.java GenericTest.java:53: 警告: [unchecked] 对作为原始类型List的成员的add(E)的调用未经过检查 list.add("abc"); ^ 其中, E是类型变量: E扩展已在接口 List中声明的Object GenericTest.java:54: 警告: [unchecked] 对作为原始类型List的成员的add(E)的调用未经过检查 list.add(123); ^ 其中, E是类型变量: E扩展已在接口 List中声明的Object 2 个警告泛型的作用
类型安全检查 编写通用的Java程序(Java框架)JDK5中的泛型允许程序猿使用泛型技术限制集合的处理类型。
List list = new ArrayList<String>();注意:泛型是提供给javac编译器使用的,他用于限定集合的输入类型,让编译器在源代码级别上,即挡住喜爱那个集合中插入非法数据。但编译器编译完带有泛型的java程序之后,生成的class文件中将不在带有泛型信息,以此使程序运行效率不受到影响,这个过程称之为“擦除”。 泛型技术,其实只是编译器阶段技术,为javac命令起到了类型安全检查的作用,生成.class文件后,泛型信息将会被擦除。 泛型的基本术语,以ArrayList< E >为例:<> 念着typeof
ArrayList< E >中的E称为类型参数变量 ArrayList< Interger >中的Integer称为实际类型参数 整个ArrayList< Interger >称为参数化类型ParameterizedType List< String >称为参数化类型泛型语法
List < 泛型类型> 规定了List集合中的元素类型,取出集合中的元素的时候,获得具体数据类型元素(不需要进行类型强制转换)。
使用类型安全的List
使用类型安全的List并将List中的元素用三种不同的方式输出。
@Test public void demo3() { // 使用类型的List List<String> list = new LinkedList<String>(); list.add("abc"); list.add("qwe"); list.add("asd"); // 因为使用泛型,只能将list添加String类型元素 // 遍历List --三种方案 // 第一种是因为List是有序的(存入顺序和取出顺序是一样的),通过size和get方法进行遍历 for (int i = 0; i < list.size(); i++) { String str = list.get(i); System.out.println(str); } System.out.println("--------------------------"); // 第二种因为List继承了Collection接口通过Collection的iterator进行遍历 Iterator<String> iterator = list.iterator(); while (iterator.hasNext()) { String str = iterator.next(); System.out.println(str); } System.out.println("--------------------------"); // 第三种 JDK5引入了foreach循环结构,通过foreach结构遍历list for (String str : list) { System.out.println(str); } System.out.println("--------------------------"); }输出结果:
abc qwe asd -------------------------- abc qwe asd -------------------------- abc qwe asd --------------------------使用类型安全的Set
使用类型安全的Set并将Set中的元素用两种不同的方式输出。
@Test public void demo4() { // 使用类型安全的Set Set<String> set = new TreeSet<String>(); set.add("qwe"); set.add("asd"); set.add("zxc"); // 因为使用泛型,只能添加String类型元素 // 取出Set元素 -- 因为Set是无序的,所以比List少一种遍历方法 // 第一种,继承Collection 所以使用Iterator遍历 Iterator<String> iterator = set.iterator(); while (iterator.hasNext()) { String str = iterator.next(); System.out.println(str); } System.out.println("--------------------------"); // 第二种,由于JDK5引入了foreach 可以使用foreach遍历set for (String str : set) { System.out.println(str); } }输出结果:
asd qwe zxc -------------------------- asd qwe zxc使用类型安全的Map
使用类型安全的Map并将Map中的元素用两种不同的方式输出。
@Test public void demo5() { // 使用类型安全的Map,因为Map是一个键值对结构,执行两个类型泛型 Map<String,String> map = new HashMap<String, String>(); map.put("aaa","111"); map.put("sss", "222"); map.put("ddd", "333"); // 因为使用了泛型,所以key和value类型必须是String // 取出map元素 -- 两种方法 // 第一种 通过map中的keySet进行遍历 Set<String> keys = map.keySet(); for (String key : keys) { System.out.println(key + ":" + map.get(key)); } System.out.println("--------------------------"); // 第二种 通过map的entrySet ---- 获得每一个键值对 Set<Map.Entry<String, String>> entrySet = map.entrySet(); // 就是一个键值对 for (Entry<String, String> entry : entrySet) { // 通过entry的getkey和getValue获得每一个键和值 System.out.println(entry.getKey() + ":" + entry.getValue()); } }输出结果:
aaa:111 ddd:333 sss:222 -------------------------- aaa:111 ddd:333 sss:222自定义范型
1.定义泛型方法,必须在方法的返回值之前进行泛型类型声明 < 泛型类型 >
@Test public void demo6() { Integer[] arr1 = new Integer[] { 1, 2, 3, 4, 5 }; changePostion(arr1, 0, 2); System.out.println(Arrays.toString(arr1)); } // 使用泛型技术编写通用的数组位置交换代码 public <T> void changePostion(T[] arr, int index1, int index2) { T temp = arr[index1]; arr[index1] = arr[index2]; arr[index2] = temp; }2.自定义泛型类,如果一个类多处要用到同一个泛型,这时可以把泛型定义在类上(即类级别的泛型),语法格式如下:
public class GenericDao<T> { private T field1; public void save (T obj) {} public T getId(int id) {} }当类的泛型使用后,该类中所有方法都可以直接使用泛型。 对应泛型类型参数的起名可以是任何大写字母,但是建议使用有意义的字母来命名。 注意:类的泛型不对static起作用。
泛型高级应用之通配符
泛型:代表了任意类型; ?:任意泛型类型。
泛型通配符?的使用:
@Test public void demo7() { // 使用类型的List List<String> list = new LinkedList<String>(); list.add("abc"); list.add("qwe"); list.add("asd"); print(list); System.out.println("---------------------"); // 使用类型的List List<Integer> list1 = new LinkedList<Integer>(); list1.add(123); list1.add(234); list1.add(345); print(list1); } // 泛型通配符——? 代表任意类型 // 泛型类型可以是任何类型的 private void print(List<?> list) { for (Object str : list) { System.out.println(str); } } 当使用了通配符了后,不要使用和类型相关方法。上下边界
通过上下边界,限制通配符类型范围:
? extends Number :表示Number(包含Number)的任意子类型。 ?super String :表示String(包含String)的任意父类型。
注意: 上下边界不能同时使用。 即不存在 :?extends Object super Integer
@Test public void demo8() { List<?> list = new ArrayList<String>(); // list.add("qwer"); // list.add(123); // 报错,当使用了通配符了后,不要使用和类型相关方法。 List<? extends Number> list1 = new ArrayList<Integer>(); List<? super String> list2 = new ArrayList<String>(); }
上下边界的应用
范例一 比如在Set中的addAll(Collection< ? extends E > c) 方法。 Set中addAll(Collection< ? extends E > c) 表示将目标集合c的内容添加到当前set中,? extends E表示目标集合是E的子类型。
Set< Number > set = new HashSet< Number >(); List< Integer > list = new ArrayList< Integer >(); set.addAll(list); // list 中 Integer 自动转换为 Number范例二
比如在TreeSet中的构造方法:TreeSet(Comparator< ? super E > comparator)
// 默认需要苹果比较器排序,但是我们缺少Apple比较器,所以我们需要自定义一个 Set< Apple > set = new TreeSet< Apple >(); class FruitComparator implements Comparator< Fruit > { ...... } // 需要Apple比较器 ,传入 Fruit比较器。 // 因为能比较水果的比较器,那么也能比较Apple Set< Apple > set = new TreeSet< Apple >(new FruitComparator());总结
泛型用来在编译阶段对集合对象进行类型安全检查 需要掌握类型安全的遍历List、Set、Map中元素 泛型技术集合反射编写通用Java程序 定义通过数组交换位置方法 泛型的通配符和上下边界 在Java API中有很多应用,了解即可二、枚举
为什么需要枚举?
因为一些方法在运行的时候,他需要的数据不能是任意的,而必须是一定范围内的值,此类问题在JDK5以前采用自定义带有枚举功能的类解决,Java5之后可以直接使用枚举予以解决。 JDK5新增的enum关键字用于定义一个枚举类。 创建枚举格式: enum 枚举类型名称 { 枚举对象名称1, 枚举对象名称2, …… 枚举对象名称n; } 作用:可读性良好,阻止非法数据。
枚举的由来
最初Java是没有枚举的。 1.第一个版本之String类型
employee.role1 = "BOSS"; employee.role1 = "MANAGE"; // 因为角色是一个String // 如果经理字符拼错了,程序就会出现问题 employee.role1 = "WORKER";这个版本的确定就是字符可能输错,到时程序出错。当变量过于大的时候,管理难度大,出错率高。 2.第一个版本之int类型
employee.role2 = 1;// 可读性太差了 employee.role2 = 4;// 非法数据比如上述形式的变量,这种方法具有可读性差,无法阻止非法数据的缺点。 3.第二个版本之自定义类实现枚举
class Role3 { public static final Role3 BOSS = new Role3(); public static final Role3 MANAGER = new Role3(); public static final Role3 WORKER = new Role3(); // 私有构造方法,组织非法数据 private Role3() { } }提高了可读性,阻止了非法的初始化。 4.JDK5引入了enum
enum Role4 { // JDK5 以后引用枚举技术 简化对象创建 ---- 功能等价于 Role3 BOSS, MANAGER, WORKER; }事实上,enum类是于构造Role3。Enum主要的作用是简化了对象的创建…… 注意:枚举类构造器,必须是私有的(private)。
package cn.idcast.jdk5; public class EnumTest { } 全部代码: /** * 假如我们需要定义一个角色属性(公司内角色只有三种:BOSS,MANAGER,WORKER) 定义员工角色, * 在三个值中取一个 * 1 BOSS 2 MANAGE 3 WORKER * */ class Employee { private String role1; private int role2; // 在JDK5之前,没有枚举,只能通过自定义类,实现枚举功能 private Role3 role3; // 在JDK5之后,引入了枚举,使用枚举表示多个角色 private Role4 role4; public static void main(String[] args) { Employee employee = new Employee(); employee.role1 = "BOSS"; employee.role1 = "MANAGE"; // 因为角色是一个String // 如果经理字符拼错了,程序就会出现问题 employee.role1 = "WORKER"; // --------------------------- employee.role2 = 1;// 可读性太差了 employee.role2 = 4;// 非法数据 // --------------------------- // 定义Role2类,定义一组int常量,用来表示角色 employee.role2 = Role2.BOSS; // 可读性大大提高了 employee.role2 = -1; // 非法数据 // --------------------------- // 通过自定义Role3实现枚举功能 employee.role3 = Role3.BOSS; // employee.role3 = new Role3(); // 数据 非法 // 使用枚举之后 employee.role4 = Role4.BOSS;// 可读性良好,阻止非法数据、 // employee.role4 = new Role4(); // 私有构造方法,从这里可以看出在enum中的构造方法也是private的 } } class Role2 { public static final int BOSS = 1; public static final int MANAGER = 2; public static final int WORKER = 3; } class Role3 { public static final Role3 BOSS = new Role3(); public static final Role3 MANAGER = new Role3(); public static final Role3 WORKER = new Role3(); // 私有构造方法,组织非法数据 private Role3() { } } enum Role4 { // JDK5 以后引用枚举技术 简化对象创建 ---- 功能等价于 Role3 BOSS, MANAGER, WORKER; }枚举类的特性
枚举类也是一种特殊形式的Java类 枚举类中声明的每一个枚举值代表枚举类的一个实例对象,也就是说每一个枚举值,在编译为.class文件的时候,都会成为枚举成员对象(常量) 与Java中的普通类一样,在声明枚举类的时候,也可以声明属性、方法和构造函数,但是枚举类的构造函数必须是私有的 枚举类也可以实现接口,或者继承抽象类 JDK5中扩展switch语句,它除了可以接收int,byte,char,short外,还可以接收一个枚举类型 若枚举类只有一个枚举值,则可以当作单态设计模式使用单例设计模式的写法
单例设计模式须包含以下内容: 1、私有构造器 2、private static成员对象 3、public static获得成员对象的方法
/** * 单例设计模式,强调一个类只能有一个示例 * */ public class SingletonTest { } enum A { Test; // 该枚举中只有TRST实例,相当于**单例**。 } class B { // 1. 私有构造器 private B() { } // 2.private static 成员对象 private static B b = new B(); // 3.提供public static 获得成员方法,获得唯一的成员对象 public static B getInstance() { return b; } }饿汉式
定义: 在创建对象的时候,直接进行初始化。 如上面的单例设计模式的例子,就是饿汉式。
class B { // 1. 私有构造器 private B() { } // 2.private static 成员对象 private static B b = new B(); // 3.提供public static 获得成员方法,获得唯一的成员对象 public static B getInstance() { return b; } }懒汉式
定义: 在获取对象的时候,进行初始化。
// 懒汉式 class C { // 1. 私有构造器 private C() { } // 2.private static 成员对象 private static C c; // 3.提供public static 获得成员方法,获得唯一的成员对象 public static C getInstance() { if (c == null) { c = new C();// 标准的懒汉式 } return c; } }枚举高级应用
在枚举实例定义过程中,向枚举构造器传入参数,通过匿名内部类实现枚举中抽象方法。
/** * 定义特殊结构的枚举 * */ public enum EnumConstructorTest { // 创建枚举值的时候,传入构造方法参数 A(20) { @Override public void show() { // TODO Auto-generated method stub } }, B(30) { @Override public void show() { // TODO Auto-generated method stub } }; // 构造方法,带有参数 private EnumConstructorTest(int a) { } public String toSting() { return super.toString(); } public void print() { System.out.println("Test"); } // 抽象方法 public abstract void show(); }应用举例(包含两种写法):
public class WeekDayTest { public static void main(String[] args) { Weekday day = Weekday.Wed; day.show(); Weekdayabs dayabs = Weekdayabs.Fri; dayabs.show(); } } enum Weekday { Mon, Tue, Wed, Thu, Fri, Sat, Sun; // 编写方法show public void show() { // 根据枚举对象名字返回中文的的星期数 if (this.name().equals("Mon")) { System.out.println("星期一"); } else if (this.name().equals("Tue")) { System.out.println("星期二"); } else if (this.name().equals("Wed")) { System.out.println("星期三"); } else if (this.name().equals("Thu")) { System.out.println("星期四"); } else if (this.name().equals("Fri")) { System.out.println("星期五"); } else if (this.name().equals("Sat")) { System.out.println("星期六"); } else if (this.name().equals("Sun")) { System.out.println("星期日"); } } } enum Weekdayabs { Mon{ @Override public void show() { // TODO Auto-generated method stub System.out.println("星期一"); } }, Tue { @Override public void show() { // TODO Auto-generated method stub System.out.println("星期二"); } }, Wed { @Override public void show() { // TODO Auto-generated method stub System.out.println("星期三"); } }, Thu { @Override public void show() { // TODO Auto-generated method stub System.out.println("星期四"); } }, Fri { @Override public void show() { // TODO Auto-generated method stub System.out.println("星期五"); } }, Sat { @Override public void show() { // TODO Auto-generated method stub System.out.println("星期六"); } }, Sun { @Override public void show() { // TODO Auto-generated method stub System.out.println("星期日"); } }; // 编写方法show public abstract void show(); }枚举类API
Java中声明的枚举类,均是java.lang.Enum类的孩子,它继承了Enum类的所有方法。常用方法: name():返回枚举对象名称 ordinal():返回枚举对象下标 valueof(Class enumClass,String name):转换枚举对象(将String类型枚举对象名称,转换为对应Class类型对象) 自定义的枚举类,在Javac编译阶段自动生成下面两个新的方法 valueof(String name):转换枚举对象(将String类型枚举名称转换为枚举对象) values():获得所有枚举对象数组 public class EnumAPITest { @Test // 枚举对象,枚举对象下标、枚举对象名称表示之间的转换 public void demo2() { // 第一种:已知枚举对象,获得下标和名称 Color blue = Color.BLUE; // 获得下标 System.out.println(blue.ordinal()); // 获得名称 System.out.println(blue.name()); System.out.println("------------------"); // 第二种,已知枚举对象下标,获得枚举对象实例和名称 int index = 1; // 获得枚举对象 Color red = Color.values()[index]; System.out.println(red.name()); System.out.println("------------------"); // 第三种,已知枚举对象名称,获得枚举对象实例和下标 String name = "YELLOW"; // 获得实例 Color c1 = Enum.valueOf(Color.class, name); Color c2 = Color.valueOf(name); // 获得下标 System.out.println(c1.ordinal()); System.out.println(c2.ordinal()); } @Test public void demo1() { // 任何enum定义枚举类都是默认继承Enum类,使用Enum中方法 // 枚举对象不能使用new获得,使用已经创建好的对象 Color color = Color.RED; // name 方法返回枚举实例名称 System.out.println(color.name()); // ordinal 方法返回枚举对象下标 System.out.println(color.ordinal()); // valueOf 将String类型枚举对象名称 --转换为相应枚举对象 String name = "YELLOW"; Color yellow = Enum.valueOf(Color.class, name);// 将name 转换成响应枚举对象 System.out.println(yellow.ordinal()); // 使用枚举类编译后生成两个方法 // values 获得所有枚举对象数组 Color[] colors = Color.values(); System.out.println(Arrays.toString(colors)); // 生成valuOf,只接受String类型枚举名称,将名称转换成当前枚举类对象 String name2 = "BLUE"; Color blue = Color.valueOf(name2); System.out.println(blue.ordinal()); } }三、静态导入
JDK1.5增加的静态导入语法用于导入类的某个静态属性或方法。 使用静态导入(staitc import)可以简化程序对类静态属性和方法的调用。 语法: Import staic 包名,类名,静态属性|静态方法|* 例如: Import static java.lang.System.out; Import static java.util.Arrays.sort; Import static java.lang.Math.*; 缺点: 虽然静态导入可以简化编程,但是静态导入后的代码的可读性差。并且当导入了冲突方法的时候,静态导入将不可用(报错)。
import java.util.Arrays; import static java.util.Arrays.sort; /** * 静态导入 * */ public class StaticImportTest { public static void main(String[] args) { int[] arr = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 0 }; // 对数据进行排序 /** * 因为在类上方通过静态导入引用Arrays.sort,所以可以直接sort * */ sort(arr);// 这里的sort是Arrays的静态方法 // 注意:静态导入Arrays.toString是会报错的,因为和本地的toString发生了冲突了 System.out.println(Arrays.toString(arr)); } @Override public String toString() { // TODO Auto-generated method stub return super.toString(); } }四、自动装箱/拆箱
JDK5.0的语法允许开发人员把一个基本数据类型直接赋给对应的包装类变量,或者赋给Object类型的变量,这个过程称之为自动装箱。 自动拆箱和自动装箱与之相法,即把包装类对象直接赋给一个对应的基本类型变量。 典型应用:
import org.junit.Test; /** * 自动装箱和拆箱 * */ public class AutoBoxingTest { @Test public void demo1() { // 在JDK5之后将原始数据类型转换为包装类类型称之为自动装箱。 Integer i = 10;// 编译器将int转换成Integer // 在JDK5之后将封装类类型转换为原始数据类型称之为自动拆箱。 int n = i;// 编译器自动将Integer转换为int。 // 测试方法,将编译的JDK版本调为JDK1.5以下版本即可。将会报错: // Type mismatch:cannot convert from Integer to int } @Test public void demo2() { // JDK5之前如何进行原始数据类型和包装类类型转换? int m = 10; // 如何将int转换为Integer Integer i = new Integer(m); // 如何将Integer转换为int int n = i.intValue(); } }典型案例 看下列代码,思考代码结果:
@Test public void demo3() { int i = 10; doSomething(i); } public void doSomething(double d) { System.out.println("double 参数"); } public void doSomething(Integer d) { System.out.println("Integer 参数"); }说明:
/** * 注意:这个方法将会执行double参数的方法 * 原因:因为要兼容JDK1.4的执行效果,所以将会调用double的方法 * */五、for/in语句(foreach)
引入增强for循环的原因
替换Iterator的复杂写法,本质就是Iterator。
foreach语句的主要作用
遍历数组 遍历Collection集合对象 import java.util.Iterator; import java.util.List; import java.util.ArrayList; import org.junit.Test; /** * for in语句 * */ public class ForInTest { @Test public void demo1() { List<String> list = new ArrayList<String>(); list.add("qwer"); list.add("asdf"); list.add("zxcv"); // 在JDK5之前有两种遍历方式:通过下标遍历,通过iterator Iterator<String> iterator = list.iterator(); while (iterator.hasNext()) { System.out.println(iterator.next()); } System.out.println("----------------"); for (Iterator<String> iterator2 = list.iterator(); iterator2.hasNext();) { String str = (String) iterator2.next(); System.out.println(str); } System.out.println("----------------"); // for in 主要作用就是为了简化Iterator,事实上for in就是Iterator for (String s : list) {// String s表示list中的每一个字符串 System.out.println(s); } } }自定义对象使用for/in语句
如果一个对象,使用for/in语句中,该对象必须满足两个条件: 1、这个类必须实现Iterator接口 2、这个类必须重写Iterator方法
import java.util.Iterator; import org.junit.Test; /** * for in语句 * */ public class ForInTest { @Test public void demo2() { // Car用于for/in语句 Dota dota = new Dota(); for (String str : dota) { System.out.println(str); } } } // 实现让自定义了类Car可以用于for/in语句 class Dota implements Iterable<String> { String[] names = {"敌法师","龙骑士","黑暗游侠","圣堂武士"}; @Override public Iterator<String> iterator() { // TODO Auto-generated method stub // 自定义迭代器 return new MyIterator(); } class MyIterator implements Iterator<String> { int index = 0; // 当前遍历数组下标 @Override public boolean hasNext() { if (index >= names.length) { // 证明下标无法获取 return false; } return true; } @Override public String next() { String name = names[index]; index++; return name; } @Override public void remove() { // TODO Auto-generated method stub } } }for/in删除导致的并发异常处理
在使用迭代器和for/in语句进行list循环的时候,删除元素就会出现java.util.ConcurrentModificationException(并发异常)。 解决方案: 1、 使用Iterator自带的remove方法(以下的demo3)。 2、如果只是删除一个元素的时候,可以使用for/in语句删除元素后,直接break跳出循环(以下的demo5)。 3、使用解决这类异常线程安全集合CopyOnWriteArrayList< E >。(以下的demo6)
import java.util.Iterator; import java.util.List; import java.util.ArrayList; import org.junit.Test; /** * for in语句 * */ public class ForInTest { @Test public void demo6() { // 使用线程安全的集合对象,在for/in循环中删除 List<String> list = new CopyOnWriteArrayList<String>(); list.add("qwer"); list.add("asd"); list.add("zxc"); list.add("123"); list.add("apple"); // 删除apple // 如果只是删除一个的话可以试以下方法 for (String str : list) { if (str.equals("apple")) { list.remove(str); } } System.out.println(list); } /** * 如果for/in语句中只是删除一个元素,还可以使用break在删除后跳出循环,这是不会执行下一次next----就不会出现并发异常了 * */ @Test public void demo5() { List<String> list = new ArrayList<String>(); list.add("qwer"); list.add("asd"); list.add("zxc"); list.add("123"); list.add("apple"); // 删除apple // 如果只是删除一个的话可以试以下方法 for (String str : list) { if (str.equals("apple")) { list.remove(str); break; } } System.out.println(list); } @Test public void demo4() { List<String> list = new ArrayList<String>(); list.add("qwer"); list.add("asd"); list.add("zxc"); list.add("123"); list.add("apple"); // 遍历集合移除所有包含字母“a”的字符串 // 遍历list 三种写法 // 下标,Iterator,for/in // 下标 for (int i = 0; i < list.size(); i++) { String s = list.get(i); if (s.contains("a")) { // 需要将s从list移除 list.remove(s); // 防止元素被跳过 i--; } } System.out.println(list); } /** * List移除练习 * */ @Test public void demo3() { List<String> list = new ArrayList<String>(); list.add("qwer"); list.add("asd"); list.add("zxc"); list.add("123"); list.add("apple"); // 通过for/in for (String s : list) { if (s.contains("a")) { // 以下方法会产生并发异常 // list.remove(s);// // error,java.util.ConcurrentModificationException // 第一种解决方法 -- 使用迭代器自身的remove } } // 第一种解决方法 -- 使用迭代器自身的remove // 使用Iterator进行List遍历 Iterator<String> iterator = list.iterator(); while (iterator.hasNext()) { String s = iterator.next(); if (s.contains("a")) { iterator.remove(); } } System.out.println(list); } @Test public void demo1() { List<String> list = new ArrayList<String>(); list.add("qwer"); list.add("asdf"); list.add("zxcv"); // 在JDK5之前有两种遍历方式:通过下标遍历,通过iterator Iterator<String> iterator = list.iterator(); while (iterator.hasNext()) { System.out.println(iterator.next()); } System.out.println("----------------"); for (Iterator<String> iterator2 = list.iterator(); iterator2.hasNext();) { String str = (String) iterator2.next(); System.out.println(str); } System.out.println("----------------"); // for in 主要作用就是为了简化Iterator,事实上for in就是Iterator for (String s : list) {// String s表示list中的每一个字符串 System.out.println(s); } } }六、可变参数
可变参数主要是用来编写框架。 原理:int… args相当于int[] args,调用可变参数方法的时候,传入任意个数参数。任意个数参数都会被保存在参数数组中, 例如:add(int… args) add(10,20)相当于add(new int[]{10,20}); add(20,30,40)相当于add(new int[]{20,30,40}); add()相当于add(new int[]{});
测试Arrays.asList()方法
import java.util.Arrays; import java.util.List; import org.junit.Test; public class VariableTest { @Test public void demo1() { int[] arr = {10,20,30}; // 使用Arrays.asList将数组转换为List List<int[]> list = Arrays.asList(arr); System.out.println(list.size()); Integer[] arr2 = {10,20,30}; List<Integer> list2 = Arrays.asList(arr2); System.out.println(list2.size()); // 注意 -- 同时传递数组和整数的时候,程序会将数组和整数作为独立的对象使用 List list3 = Arrays.asList(arr2,200,300); System.out.println(list3.size()); List<Integer> list4 = Arrays.asList(100,200,300); System.out.println(list4.size()); // list3是不是ArrayList? // 注意:这个不是我们所知道的ArrayList,该ArrayList不能改变长度 list4.remove(100); // 会报以下错误,java.lang.UnsupportedOperationException } }从JDK5开始,Java允许为方法定义长度可变的参数,语法为:
public void foo(int... args) {}注意事项: 1、调用可变参数的方法的时候,编译器将自动创建一个数组保存传递给方法的可变参数,因此,程序猿可以在方法体中以数组的形式访问可变参数。 2、可变参数只能处于参数列表的最后,所以一个方法最多只能有一个长度可变的参数。
import java.util.Arrays; import java.util.List; import org.junit.Test; public class VariableTest { @Test public void demo2() { // 数据求和 System.out.println(add(10,20,30)); System.out.println(add()); System.out.println(add(new int[]{10,20,30,40})); } // 多个int类型参数求和,int... 表示任意个数int类型参数 public int add(int ... args) { int sum = 0; for (int i : args) { sum += i; } return sum; } }七、反射API的使用
如果你想要编写高效、功能强大的Java程序,必须使用发射技术。
什么是反射?
JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意方法和属性;这种动态获取信息以及动态调用对象方法的功能称为java语言的反射机制。
JAVA反射(放射)机制:“程序运行时,允许改变程序结构或变量类型,这种语言称为动态语言”。从这个观点看,Perl,Python,Ruby是动态语言,C++,Java,C#不是动态语言。但是JAVA有着一个非常突出的动态相关机制:Reflection,用在Java身上指的是我们可以于运行时加载、探知、使用编译期间完全未知的classes。换句话说,Java程序可以加载一个运行时才得知名称的class,获悉其完整构造(但不包括methods定义),并生成其对象实体、或对其fields设值、或唤起其methods。 总结部分: 1、剖析Java类的各个组成部分映射成一个个Java对象 2、类java.lang.Class 3、java.lang.reflect 4、构造方法Constructor 5、成员变量Field 6、方法Method
反射用在哪里?
多用于框架和组件,写出复用性高的通用程序。
反射流程
.java文件(源代码)——(编译)——>.class字节码(java类名)——(运行)——>< JVM中 >【 类加载器(字节码由类加载器加载)——(生成)——>Class类对象(位于java.lang包中)】
在.class字节码中,被加载器加载之后,整个字节码生成对象Class对象,字节码中的成员变量生成Field对象,字节码成员方法生成Method对象,构造器则生成Constructor对象。
public class A { // 成为内存Class对象 int n = 10; // Field public void show() {} // Method public static void init() {} // Method public A() {} // Constructor }获得Class对象的三种方法
1、已知类——可以通过类名.class获得 2、已知对象——对象.getClass获得 3、完整类名String格式——Class.forName(完整类名)——第三种方法是最常用的方法。
import org.junit.Test; /** * 编写反射API使用 * */ public class ReflfectTest { @Test public void demo1() { // 获得class对象的三种方式 // 第一种 已知类 Class c1 = Readable.class; // 第二种 已知对象 Object obj = new ReflfectTest(); Class c2 = obj.getClass(); // 第三种 未知类和对象,知道完整类名 String className = "cn.idcast.jdk5.ReflfectTest"; try { Class c3 = Class.forName(className); } catch (ClassNotFoundException e) { // TODO Auto-generated catch block e.printStackTrace(); } // --------第三种方式是最常用的方法 } }获得Class构造器的两种方法
通过类字节码对象获得构造器、成员变量、方法有两种方法: 1、获得所有getConstructors(); 2、获得指定的构造器,可以使用getConstructor(class
import java.lang.reflect.Constructor; import org.junit.Test; /** * 编写反射API使用 * */ public class ReflfectTest { @Test public void demo2() throws ClassNotFoundException, SecurityException, NoSuchMethodException { // 获得字节码对象 String className = "cn.idcast.jdk5.ReflfectTest"; Class c = Class.forName(className); // 通过字节码获得一个类构造器 Constructor<String>[] constructors = c.getConstructors(); System.out.println(constructors.length); // 获得指定构造器 // 带有String参数的构造器 Constructor constructors1 = c.getConstructor(String.class); System.out.println(constructors.length); } public ReflfectTest() { } public ReflfectTest(String str) { } }使用获取到的构造器新建对象
package cn.idcast.jdk5; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; import org.junit.Test; /** * 编写反射API使用 * */ public class ReflfectTest { @Test public void demo3() throws IllegalArgumentException, InstantiationException, IllegalaccessException, InvocationTargetException, SecurityException, NoSuchMethodException, ClassNotFoundException { String className = "cn.idcast.jdk5.ReflfectTest"; Class c = Class.forName(className); Constructor constructors = c.getConstructor(); Constructor constructors1 = c.getConstructor(String.class); // 调用无参数构造器的写法 ReflfectTest reflfectTest = new ReflfectTest(); // 使用反射实现相同的功能 Object obj = constructors.newInstance();// 调用无参数的构造器,构造对象 // 还有一种写法 Object obj_1 = c.newInstance(); // 通过Class对象newInstance,调用目标类无参数构造器 // 调用无参数构造器的写法 ReflfectTest reflfectTest1 = new ReflfectTest("abcd"); // 使用反射实现相同的功能 Object obj1 = constructors1.newInstance("abcd");// 调用String类型的构造器,构造对象 } }获得Class获取Field类
Field类代表某个类中的一个成员变量,并提供动态的访问权限。 Field对象的获得方法如下所示, 1、得到所有的成员变量:
Field[] fields = c.getFields(); // 获得所有的public成员变量,包括父类继承的。 Field[] fields = c.getDeclaredFields();// 取得所有声明的属性,包括private属性的成员变量2、得到指定的Field成员变量
Field fields = c.getDeclaredField(name); // 获得当前类中指定名称name的成员变量设置Field变量是否可以访问
// 设置private变量可以访问的 Field.setAccessible(true);Field变量值的读取和设置
// 获得对象指定name属性值 Field.get(name); // 修改Object的value的属性值 pnField.set(Object, value);总的代码:
package cn.idcast.jdk5; import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import org.junit.Test; /** * 编写反射API使用 * */ public class ReflfectTest { public String name; private String priname; public static void main(String[] args) { try { demo4(); } catch (SecurityException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (ClassNotFoundException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (NoSuchFieldException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (IllegalArgumentException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (IllegalAccessException e) { // TODO Auto-generated catch block e.printStackTrace(); } } // 反射可以对内存中任何字节码进行操作 @Test public static void demo4() throws ClassNotFoundException, SecurityException, NoSuchFieldException, IllegalArgumentException, IllegalAccessException { ReflfectTest reflfectTest = new ReflfectTest("Notzuonotdied"); // 使用反射操作类成员变量——Field类 // 1、必须获得目标类,字节码对象 Class c = Class.forName("cn.idcast.jdk5.ReflfectTest"); // 2.操作成员实例变量name Field[] fields = c.getFields(); // 获得所有的public 成员变量,包括父类继承的 System.out.println("这里变量的数量为1,是因为不包含私有:" + fields.length); Field[] fields2 = c.getDeclaredFields();// 获取当前类定义的所有成员,包括了private // 当前Field是private的(名称为"priname"的变量是private),我们是没有操作不了的,因此我们需要设置Field的变量操作权限 Field pnField = c.getDeclaredField("priname"); System.out.println("这里变量的数量为2,是因为公有加私有一共两个:" + fields2.length); System.out.println(pnField); // 设置private变量可以访问的 pnField.setAccessible(true); // 获得reflfectTest对象指定name属性值 Object pnobj = pnField.get(reflfectTest); System.out.println(pnobj); // 修改reflfectTest的name的属性值 pnField.set(reflfectTest, "I am Notzuonotdied"); System.out.println(pnField.get(reflfectTest)); /* 输出结果: * * 这里变量的数量为1,是因为不包含私有:1 * 这里变量的数量为2,是因为公有加私有一共两个:2 * private java.lang.String cn.idcast.jdk5.ReflfectTest.priname * Notzuonotdied * I am Notzuonotdied */ } }获得Class操作Method
Method类代表某个类中的一个成员方法 Method对象的获得,可以通过以下方法, 1、获得所有方法
getDeclareMethods() //获得当前类声明的方法 getMethods()2、获得指定的方法
getDeclaredMethod(String name,Class<?>... parameterTypes)// 获得指定方法 getMethod(String name,Class<?>... parameterTypes)通过反射执行方法
invoke(Object obj,Object... args)如果执行static方法,第一个参数obj传入null。
package cn.idcast.jdk5; import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import org.junit.Test; /** * 编写反射API使用 * */ public class ReflfectTest { public String name; private String priname; public void setName(String name) { this.name = name; } public String getName() { return name; } @Test public void demo5() throws ClassNotFoundException, SecurityException, NoSuchMethodException, IllegalArgumentException, IllegalAccessException, InvocationTargetException { ReflfectTest reflfectTest = new ReflfectTest(); // 调用reflfectTest对象中setName方法设置name的值 // 1、获得字节码对象 Class c = Class.forName("cn.idcast.jdk5.ReflfectTest"); // String类型参数setName方法 Method setName = c.getDeclaredMethod("setName", String.class); // String类型参数的setName方法 // 调用reflfectTest对象中的setName setName.invoke(reflfectTest, "this is a test demo"); // 等价于reflfectTest.setName("this is a test demo"); // String类型参数getName方法 Method getName = c.getDeclaredMethod("setName", String.class); // String类型参数的setName方法 // 调用reflfectTest对象中的setName getName.invoke(reflfectTest, "this is a test demo"); // 等价于reflfectTest.setName("this is a test demo"); } }反射小案例——晚会案例
1、编写晚会程序,测试程序; 2、为了程序更好维护和扩展,需要将做什么(interface)和怎么做(实现interface中的方法)分离; 问题:在晚会类中,要维护所有出演演员,但是不想修改晚会类,可以怎么写? 答:可以采用工厂结构(中介)来确定那一个对象将会被运行。 3、用工厂将做什么和怎么做进行解耦合; 4、将演员实例类写入配置文件。(写入配置文件之后,可以在不修改源代码的情况下修改程序) 工厂+反射+配置文件模式的作用:编写便于扩展、便于维护的程序。
主程序EveningParty.java
package cn.idcast.jdk5.demo; /** * 晚会 * * @author seawind * */ public class EveningParty { public static void main(String[] args) { new EveningParty().proccess(); // 晚会举行了 } /** * 这里仅仅定义了程序怎么做,但是没有定义要做什么,由哪个对象来做。 * */ public void proccess() { // 定义晚会流程 // 演出 歌曲、舞蹈、表演 ---- 程序做什么? System.out.println("晚会正式开始!"); // 这里是晚会的流程(执行逻辑部分),这里全部使用了接口,具体怎么做由实现接口的类来完成 // 接口的实现类由配置文件party.properties进行控制 Factory.getSingable().sing();// 准备 歌曲节目 Factory.getDanceable().dance(); // 准备舞蹈 Factory.getPerformable().perform(); // 准备表演类节目 System.out.println("晚会圆满结束!"); } }工厂类Factory.java:
工厂类主要通过获取配置文件party.properties中的定义来获得类名,
ResourceBundle.getBundle("party").getString( "Singable");并通过类名来创建对应的对象。
Class.forName(className).newInstance();下面是工厂类完整代码:
package cn.idcast.jdk5.demo; import java.util.ResourceBundle; import cn.idcast.jdk5.demo.interfaces.Danceable; import cn.idcast.jdk5.demo.interfaces.Performable; import cn.idcast.jdk5.demo.interfaces.Singable; /** * 中介类, * */ public class Factory { // 提供准备 歌手方法 public static Singable getSingable() { // 读取配置文件 src/party.properties String className = ResourceBundle.getBundle("party").getString( "Singable"); // 已知完整类名 获得对象 try { Object obj = Class.forName(className).newInstance(); return (Singable) obj;// 类型转换 } catch (Exception e) { e.printStackTrace(); throw new RuntimeException("中介出问题了!"); } } // 提供准备 舞蹈方法 public static Danceable getDanceable() { // 读取配置文件 src/party.properties String className = ResourceBundle.getBundle("party").getString( "Danceable"); // 已知完整类名 获得对象 try { Object obj = Class.forName(className).newInstance();// 通过反射新建对象 return (Danceable) obj; } catch (Exception e) { e.printStackTrace(); throw new RuntimeException("中介出问题了!"); } } // 提供准备 演员方法 public static Performable getPerformable() { // 读取配置文件 src/party.properties String className = ResourceBundle.getBundle("party").getString( "Performable"); // 已知完整类名 获得对象 try { Object obj = Class.forName(className).newInstance(); return (Performable) obj; } catch (Exception e) { e.printStackTrace(); throw new RuntimeException("中介出问题了!"); } } }