1.静态导入
概念:当我们想调用一个静态方法,但是又不想写类的名称的时候,可以使用静态导入
比如要导入Math类中的一些静态方法,原来是这么写:
Math.abs(num)//调用Math类中的abs方法计算一个数的绝对值
有了静态导入后,我们可以这样,先导入Math类的所有静态方法,然后直接就可以写方法名了:
import static java.lang.Math.*;
abs(num);
2.可变参数
概念:当一个函数中的参数不确定时,我们可以使用overload,就是有两个参数时,
我们就写两个参数的方法,有三个参数时就写三个参数的方法,但是方法的功能和名称是一样的,
只是参数个数不确定,但是现在jdk1.5提供了一个新特性,叫可变参数,对于不确定参数
个数的方法,写一个方法就可以了
2-1>原来我要写一个函数,这个函数支持两个数的相加运算,也支持三个数的相加运算,我得这么写:
public int add(int num1,int num2){
return num1+num2;
}
public int add(int num1,int num2,int num3){
return num1+num2+num3;
}
2-2>现在这样写就行了
public int add(int num1,int...nums){
int sum = num1;
for(int n : nums){
sum += n;
}
return sum;
}
注意:
>可变参数需要放在参数列表的最后面
>调用可变参数的方法时,编译器为该可变参数隐含创建一个数组,在方法中以数组的形式访问可变参数
3.增强for循环
语法结构:
for(修饰符 类型 变量:集合){
代码...
}
修饰符:static,final等
类型:要迭代的集合的每一个元素的类型
变量:迭代元素的变量名
集合:需要迭代的元素,必须实现Iterable接口,比如你弄个HashMap就不能用增强for循环了
具体更多信息可以参阅oracle公司提供的详细文档,参阅语言规范文档:langspec,
参考地址:http://docs.oracle.com/javase/
4.基本数据的自动拆装箱及享元设计模式(flyweight)
4-1>Integer a = 12;
Integer b = 12;
System.out.printn(a==b);
在以前看来,a和b可能是两个对象,引用地址是不一样的,
但是事实上不是这样的,这个打印结果是true,为什么呢?
对于数值类型装箱给Integer类型,如果它的值在-128~127之间,也就是一个字节的时候,
它会被存储到一个池中,下次再赋值给另外一个Integer对象的时候,会从池中去找,如果
有就直接将这个池子中的值给这个新对象,没有的话就创建,这样避免了浪费存储空间。
享元模式:所谓享元设计模式就是把相同的对象提炼出来,不同的属性作为方法的参数,
需要用到的时候引用它就行了,
比如:abcdabf,这个字符串,a和b出现了两次,按照以前的想法,需要有七个对象存储七个
字母,每个字母一个对象,但是现在只需要五个对象就行了,对于第二个a和b只是出现的位置
不同而已,把位置作为参数传入方法就行了,调用一个方法就可以获取到a或b的对象,
这就是享元模式
5.枚举:规定类的实例个数,把值规定好,让你在编译时就知道你传的值合不合法,不用枚举我们自己也可以用普通的java
类来实现类似枚举这样的功能,比如我们原来要定义一个星期的类可能会这么定义:
1>将星期这个类变成私有的,不允许外界进行实例化,因为我只需要七天,七个对象,不需要太多的对象实例了
2>创建七个实例
3>重写toString()方法
4>还可能有获取下一天是星期几的方法,这个方法我们需要在类里面定义成抽象的,然后在实例化七个对象的时候用匿名
内部类实现这个方法
用普通类实现枚举的功能代码如下:
abstract class WeekDay{
private WeekDay(){}
//定义一个星期七天的七个实例,并实现下一天的抽象方法
public static final WeekDay SUNDAY = new WeekDay(){
public WeekDay nextDay(WeekDay wk) {
return MONDAY;
}
};
public static final WeekDay MONDAY = new WeekDay(){
public WeekDay nextDay(WeekDay wk) {
return TUESDAY;
}
};
public static final WeekDay TUESDAY = new WeekDay(){
public WeekDay nextDay(WeekDay wk) {
return WEDNESDAY;
}
};
public static final WeekDay WEDNESDAY = new WeekDay(){
public WeekDay nextDay(WeekDay wk) {
return THURSDAY;
}
};
public static final WeekDay THURSDAY = new WeekDay(){
public WeekDay nextDay(WeekDay wk) {
return FRIDAY;
}
};
public static final WeekDay FRIDAY = new WeekDay(){
public WeekDay nextDay(WeekDay wk) {
return SATURDAY;
}
};
public static final WeekDay SATURDAY = new WeekDay(){
public WeekDay nextDay(WeekDay wk) {
return SUNDAY;
}
};
//今天的下一天是星期几
public abstract WeekDay nextDay(WeekDay wk);
//重写toString方法
public String toString(){
if(this == SUNDAY){
return "SUNDAY";
}else if(this == MONDAY){
return "MONDAY";
}else if(this == TUESDAY){
return "TUESDAY";
}else if(this == WEDNESDAY){
return "WEDNESDAY";
}else if(this == THURSDAY){
return "THURSDAY";
}else if(this == FRIDAY){
return "FRIDAY";
}else{
return "SATURDAY";
}
}
}
使用枚举的话我们可以这么写:
public enum WeekDayEnum {
// 定义一个星期七天,七个值,值后面需要有其他内容的话,必须以分号结束,而且枚举的值必须放在类的最上面
SUNDAY("今天是这个星期的第一天") {
public WeekDayEnum nextDay() {
return MONDAY;
}
},
MONDAY("今天是这个星期的第二天") {
public WeekDayEnum nextDay() {
return TUESDAY;
}
},
TUESDAY("今天是这个星期的第三天") {
public WeekDayEnum nextDay() {
return WEDNESDAY;
}
},
WEDNESDAY("今天是这个星期的第四天") {
public WeekDayEnum nextDay() {
return THURSDAY;
}
},
THURSDAY("今天是这个星期的第五天") {
public WeekDayEnum nextDay() {
return FRIDAY;
}
},
FRIDAY("今天是这个星期的第六天") {
public WeekDayEnum nextDay() {
return SATURDAY;
}
},
SATURDAY("今天是这个星期的第七天") {
public WeekDayEnum nextDay() {
return SUNDAY;
}
};
private WeekDayEnum() {
}
// 定义一个枚举的成员变量:提示今天是这个星期的第几天
private String msg;
//向外界提供获取今天是第几天的方法
public String getMsg(){
return this.msg;
}
private WeekDayEnum(String msg) {
this.msg = msg;
}
//定义枚举类的抽象方法
public abstract WeekDayEnum nextDay();
}
5>枚举类帮我们提供了一系列方法,包括获取枚举类中的所有值、获取当前枚举的名称、重写了toString()方法,
枚举类本身就是抽象的,如果在枚举类中定义了抽象方法后不需要我们在类的前面加上abstract关键字了。
6>默认所有枚举类型的父类都是Enum而不是Object
6.反射:将一个java类中的各种成分映射成对应的java类
1>.获取一个类的字节码的三种方式
1>Class cl1 = 类名.class;
2>Class cl2 = 实例名.getClass();
3>Class cl3 = Class.forName("类的全路径");//不用加后缀名.java或者.class
2>.java中的九个预定义的class对象:八个基本类型+void
byte、short、int、long、double、float、boolean、char、void
这些对象的字节码文件在对应的对象类型中都有封装,比如:
byte类型的字节码文件,可以通过Class c = byte.class;来获取也可以通过Class c = Byte.TYPE来获取
void类型的字节码文件,可以通过Class c = void.class;来获取也可以通过Class c = Void.TYPE来获取
int类型的字节码文件,可以通过Class c = int.class;来获取也可以通过Class c = Integer.TYPE来获取
这九个预定义对象都提供了TYPE字段来获取对应的字节码文件对象
总之,在源程序中出现的各种类型都有对应的Class实例对象
3>.如何判断一个字节码对象是否是基本数据类型或者是void类型?
java的Class类提供了一个静态方法isPrimitive()用来判断是否是九个预定义的Class对象
4>.获取到一个类的字节码后,就可以对这个类进行解剖了,包括获取类中所有的字段、所有的方法、构造函数等,
都可以解剖出来。
5>.演示反射:
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
public class ReflectTest {
/**
* 需求:
* 1.定义一个Person类
* 2.Person类提供name和age属性,提供set和get方法
* 3.提供有参和无参构造方法
* 4.利用反射给Person实例化,并通过构造函数给属性添加初始值
* 5.利用反射调用Person类的每一个方法(私有的无返回值的show方法和返回数组类型的方法)
*
* 思路:
* 1.定义一个Person类,具备私有成员变量name和age,并提供其set和get方法,提供有参和无参构造函数
* 2.要通过反射创建Person类的实例并给其成员变量赋值,必须先拿到其字节码文件对象
* 3.通过字节码文件对象获取有参的那个构造函数对象
* 4.通过调用构造函数对象的newInstance方法创建一个实例
* 5.反射如何调用字节码中的方法呢?
* 5.1 通过字节码文件对象中的getMethod方法调获取到节码文件中具体的那个方法对象
* 5.2调用Method对象的invoke方法执行一个指定对象的指定方法
* 5.3对于私有方法的调用,在获取Method对象后,必须对方法进行强制访问,
* 也就是设置方法的setAccessible(true);后才能访问到私有的方法
* 5.4对于静态方法的调用,与普通方法的调用类似,只是在指定调用的对象的时候可以指定为空即可,
* 代表该方法属于类不属于某个对象,当然肯定也可以指定具体的对象
* 5.5对于main方法的调用与静态方法的调用无异,只是传参数的时候需要特别注意,main函数的参数是一个字符串数组类型,
* 字符串数组类型是一个引用类型,引用类型与基本数据类型的数组类型作为参数传递的时候会有差异,
* 引用类型数组参数需要整体作为一个参数传递进来,而不是作为一个数组,
* 需要将传入进来的数组进行转换成Object或者是进行二维数组包装,因为函数在调用的时候会将这个传进来的数组中的每一个元素
* 当作一个参数处理,而对于int[],byte[]这几种基本数据类型的数组作为参数传递的时候则可以转成Object或进行二维数组
* 重新包装,也可以直接传进去,效果跟不包装和不强转成Object是一样的
*/
public static void main(String[] args) throws Exception {
Class<Person> clazz = Person.class;
Constructor<Person> constructor = clazz.getConstructor(int.class, String.class);
//=====================创建Person的实例并初始化成员变量的值===================
Person person = (Person)constructor.newInstance(23,"周乐");
System.out.println(person.getName()+":"+person.getAge());
//=====================调用Person类中的getIntArr方法=====================
List<Integer> iList = new ArrayList<Integer>();
iList.add(12);
iList.add(23);
iList.add(43);
Method getIntArrMethod = clazz.getMethod("getIntArr", List.class);
Integer[] iArr = (Integer[])getIntArrMethod.invoke(person, iList);
//对获取到的getIntArr方法中的值进行打印
System.out.println(Arrays.toString(iArr));
//=====================调用Person类中的私有方法:show=====================
Method showMethod = clazz.getDeclaredMethod("show");
showMethod.setAccessible(true);
showMethod.invoke(person, null);
//=====================调用Person类中的main方法=====================
Method mainMethod = clazz.getMethod("main", String[].class);
//调用方式一
mainMethod.invoke(null, (Object)new String[]{"张三11","李四22"});
//调用方式二
//mainMethod.invoke(person, new Object[]{new String[]{"张三11","李四22"}});
//=====================调用Person类中的strArrPrint方法(参数为字符串数组)=====================
Method intArrPrintMethod = clazz.getMethod("intArrPrint", int[].class);
intArrPrintMethod.invoke(person, new int[]{1,4,8});
}
}
==========================创建Person类===========================
import java.util.List;
public class Person {
//年龄
private int age;
//姓名
private String name;
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
//空参构造函数
public Person(){
}
//构造函数
public Person(int age, String name) {
super();
this.age = age;
this.name = name;
}
//获得一个数组的方法
public Integer[] getIntArr(List<Integer> list){
Integer[] iVal = new Integer[list.size()];
for(int i=0; i<list.size(); i++){
iVal[i] = list.get(i);
}
return iVal;
}
//打印属性的方法
private void show(){
System.out.println(this.name+":"+this.age);
}
public static void eat(){
System.out.println("我是吃货");
}
public static void main(String[] args){
System.out.println("==========我是main方法==========");
for(String arg : args){
System.out.println(arg);
}
}
public void intArrPrint(int[] iArr){
System.out.println("==========我是intArrPrint方法,我的长度是"+iArr.length+"==========");
for(int i : iArr){
System.out.println(i);
}
}
}
6>.关于数组的反射,比如如何利用反射技术
获取数组的长度,获取某个数组中的某个元素,都需要用到java.lang.reflect.Array类来操作,
以下是一个打印Object类型的方法,演示了如何利用反射技术操作数组:
//打印一个对象的值
@Test
public void printObject(){
Object obj = new int[]{22,22,44,55};
//Object obj = "我是一个字符串";
Class clazz = obj.getClass();
if(clazz.isArray()){
//如果是数组则当作数组处理,打印每个元素
//1.拿到数组的长度
int len = Array.getLength(obj);
for(int i=0; i<len; i++){
System.out.println(Array.get(obj, i));
}
}else{
//不是数组,直接打印
System.out.println(obj);
}
}
7>.加载配置文件的两种方式:
7.1>用文件流的方式读取,并加载进来,这种方式既能读取,还能通过output写入
InputStream inStream = new FileInputStream("config.properties");
需要注意的是,这个加载配置文件的路径是相对于java工程的路径,
也就是到你项目的名称下去找,跟src同一目录
7.2>用类加载器加载,但是类加载器只能读取,不能写
比如我要在Test这个类中用到config.properties文件,我就可以用Test这个类的类加载器来加载这个配置文件:
Test.class.getClassLoader().getResourceAsStream("config.properties");
这种方式默认是加载类路径下的配置文件,在源文件里面就是src目录下,如果你文件放在src/cn.mofit.test文件夹下,
那么应该应该这么加载:
Test.class.getClassLoader().getResourceAsStream("cn/mofit/test/config.properties");
需要注意的是cn前面不要加/
7.3>字节码文件每次通过类加载器来加载文件显得有些麻烦,于是,字节码文件对象自己也提供了一个加载配置文件
的方法,其实就是用的类加载器的方法:
Test.class.getResourceAsStream("config.properties");
默认,如果你的路径字符串最前面(也就是config.properties前面)没加/,就表示你的路径是相对路径,是相对于Test
这个类的路径,字节码文件对象默认会加载这个类的字节码文件所在的路径下的config.properties文件,当然也可以用
绝对路径,比如说config.properties配置文件在cn/mofit/test/resource/config.properties下面,而Test.java在
cn/mofit/test/Test.java下面,这时候,如果需要在Test.java里面用绝对路径加载这个配置文件的话,需要写上想对于
根的全路径:
Test.class.getResourceAsStream("/cn/mofit/test/resource/config.properties");
7.内省:
1>JavaBean:javabean其实也是一个java类,不过它的方法的书写都遵循一种规则,比如类中有一个属性age,
那么这个java类会提供一个外界访问的setAge(int age)方法和getAge()方法,关于javabean中的属性,一般是
把get或set方法后面的名字取出来,如果取出来的名称的第二个字母是小写,则把第一个字母变成小写,如果第二个
字母是大写则直接用这个名称作为javabean的属性,比如
根据getAge()获取到的属性为:age
根据getCPU()获取到的属性为:CPU
2>内省:introspector,用于操作javabean的一种方法,配合反射操作javabean
3>beanutils:Apache开源社区提供的一种操作javabean的技术,提供了一个commons-beanutils.jar包,相对于
内省,操作javabean更加方便,支持Map集合到bean的相互转换功能
8.注解:jdk5的新特性
1>.概念:注解相当于一种标记,在程序中加了注解就相当于打了某种标记,没加,则等于没有这种标记,
以后,java编译器、开发工具和其他程序可以用反射来了解你的类及各种元素上有无任何标记,
看你有什么标记就去干相应的事,标记可以加在包、类、字段、方法、方法的参数以及局部变量上
2>.java中提供的三个注解如下
>@SuppressWarnings:告诉编译器不显示警告信息
>@Override:覆盖父类方法啊,这个注解不加也可以,但是加上的话可以帮我们检查是否正确
覆盖了父类的方法
>@Deprecated:标记方法已经过时
3>.java中提供的4种元注解(Meta Annotation):
>Retention:指定注解可以保留多长时间
>Target:指定该annotation将会被用于哪些程序元素,也就是说你的注解是用于修饰方法的还是修饰类的,
指定注解的使用范围
>Documented:指定使用了该元annotation后的注解将会被javadoc工具所提取
>Inherited:指定被其修饰的annotation将具有继承性
4>.自定义注解
>格式:public @interface MyAnnotation{}
>把该注解加到某个类上:@MyAnnotation public class AnnotationTest{}
>用反射测试AnnotationTest的定义上是否有@MyAnnotation
>根据反射测试的问题,引出@Retention元注解的三种取值(所有的值被封装成了一个枚举类:Retention):
@Retention(RetentionPolicy.RUNTIME):运行时有效
@Retention(RetentionPolicy.CLASS):字节码(class文件)文件时有效(默认值是RetentionPolicy.CLASS)
@Retention(RetentionPolicy.SOURCE):java源文件时有效
这三个值的意思是指定你自己定义的注解被用到别的类身上时,保留多久,是保留到源文件时期呢,
还是字节码时期呢,还是在内存中加载后都一直有,Retetion的用法示例如下:
@Retention(RetentionPolicy.RUNTIME) public @interface MyAnnotation{}
>注解的第二个元注解:Target,可以跟Retention一起用在一个自定义的注解上,
它的用法跟Retention一样,它也提供了多个值,所有值
被封装在ElementType枚举类中:
@Target(ElementType.METHOD):表示自定义的这个注解只能用在方法上
@Target(ElementType.TYPE):表示自定义的这个注解只能用在类、接口(包括注释类型)或枚举上
@Target({ElementType.TYPE,ElementType.METHOD}):表示自定义的这个注解既能用在类、接口(包括注释类型)或枚举上
又能用在方法上
示例如下:
@Target({ElementType.TYPE,ElementType.METHOD}) public @interface MyAnnotation{}
5>.给自定义注解加上一些属性:
@Retention(RetentionPolicy.SOURCE)
@Target(ElementType.METHOD)
public @interface MyAnnotation{
String name();
String value();
}
// 如何使用该注解呢?
public class Test{
@MyAnnotation(name="张三",value="李四")
public void show(){
}
}
9.泛型
1>概念:说白了就是不确定的类型
2>好处:
>使用泛型后我们就可以避免不断的类型转换
>它将一些运行时的错误转成了编译时的错误
>使用泛型后避免编译器的警告了
3>泛型的擦除:java中的泛型是给编译器看的,也就是给javac命令用的,编译完了后是没有泛型的。如何知道是不是只给编译器用的而
运行时没有这些信息呢?举例如下:
List<String> strList = new ArrayList<String>();
List<Integer> itList = new ArrayList<Integer>();
System.out.println(strList.getClass()==itList.getClass());
上面的打印结果是true,说明两个对象的字节码文件是一样的,也就是说泛型在运行时
已经不存在了。只是在编译时存在。
第二种方式也可以知道泛型确实是给编译器看的,那就是先定义一个泛型,然后通过反射给它加值,这个值与
泛型要求的类型不一致,看能否打印出来,结果是可以打印出来的,说明在运行时,泛型没有起作用。
4>对于等号两边的泛型的使用,如果两边都指定了泛型,要求泛型的类型必须相等,否则编译报错,什么意思呢?
List<String> strList = new ArrayList<String>();
比如上面这个就没有问题,因为两边的泛型都是String,但是如果写成这样:
List<String> strList = new ArrayList<Object>();或者List<Object> strList = new ArrayList<String>();就有问题了
比如说第一个,你命名调用的是Object的泛型,说明你知道右边的是这个类型,左边你又搞一个String类型说你只能装String,
故意瞎搞就肯定给你报错了。但是,如果是这样的:List<String> strList = new ArrayList();就没问题了,因为右边这个没
指定泛型,编译器会认为你可能是原来jdk1.4的时候写的方法,没有用泛型的,而现在的高版本要兼容以前的版本,所以允许这么
写。当然这样也是可以的:List strList = new ArrayList<String>();意思就是不写可以,写就一定要两边一致
5>当一个类中的多个方法或多个元素需要用到同一个类型,这时候可以考虑将泛型
加到这个类的身上,比如:
public class GenericDao<T>{
public void add(T t){
}
public T findById(int id){
return null;
}
}
需要注意的是,如果类中有静态的方法或成员变量,是不能用这个类上面定义的泛型类型的,因为静态的是不需要
对象调用的
比如说下面的这个方法写法就会报错了:
public class GenericDao<T>{
public static void update(T t){
}
}
如果是静态方法又确实需要泛型怎么办?可以自己再单独定义一个泛型,比如:
public class GenericDao<T>{
public static <T> void update(T t){
//这时候这个方法参数中的T用的就不是类上面的T了,而是它自己定义的T
}
}
6>下面这两个方法是重载吗?
public void show(Vector<String> strVector){
}
public void show(Vector<Integer> intVector){
}
不是,这两个类型其实是一样的,都是Vector,两个类型的字节码对象是相等的,泛型在编译后会被擦除,
所以这两个方法在类中是不允许同时定义的。
7>如何获得方法中参数的泛型的类型呢?比如说public void show(Vector<String> v)(),如何获得这个String类型呢
通过v是没办法获得的,只能通过反射获取到Method对象后,再来获取这个泛型的具体的类型。
public static void main(String[] args) throws Exception {
Method method = GenericDao.class.getMethod("printVector", Vector.class);
Type[] types = method.getGenericParameterTypes();
//因为ParameterizedType是Type的一个子接口所以可以强制转换
ParameterizedType paramType = (ParameterizedType)types[0];
//打印泛型具体类型
System.out.println(paramType.getActualTypeArguments()[0]);
}
//带泛型的方法
public static void printVector(Vector<String> vector){
}