静态导入
导入外部类的静态方法称为静态导入,导入形式如下:
(1)import static 包名.类名.静态方法; //导入某个类的某个静态方法
如:import static java.lang.Math.max; 导入java.lang包中Math类的静态方法max,
这样在导入的源文件访问静态方法max时就可以前面不用加上类名Math
(2)import static 包名.类名.*; //导入某个类的所有静态方法
如: import static java.lang.Math.*; 导入java.lang包中Math类的所有静态方法,
这样在导入的源文件内访问Math类的所有静态方法时都可以前面不用加上类名Math
注意:
当类名重名时,需要指定具体的包名;
当方法重名时,需要指定具体所属的对象或者类。
可变参数
在介绍可变参数之前,先看一下Overload(重载)和 Override(重写,即覆盖)。
Overload(重载) 是一个类中多态性的一种表现。就是在类中可以创建多个方法,它们具有相同的名字,但具有不同的参数类型或参数个数。调用方法时通过传递给它们的不同参数个数和参数类型来决定具体使用哪个方法,返回值类型可以相同也可以不相同。无法以返回类型的不同作为重载函数的区分标准
Override(重写,即覆盖)在Java中,子类可继承父类中的方法,而不需要重新编写相同的方法。但有时子类并不想原封不动地继承父类的方法,而是想作一定的修改,这就需要采用方法的重写。方法重写又称方法覆盖。即在子类中定义某方法与父类中的某一方法具有相同的方法名、返回类型和参数表,则子类的新方法将覆盖父类原有的方法。如需调用父类中原有的方法,可使用super关键字,该关键字引用了当前类的父类。子类函数的访问修饰权限不能低于父类的,如父类的被覆盖的方法的访问权限是protected的,子类的该方法可为protected或public的,但不能是private或default的
重写方法的规则:
1.参数列表必须完全与被覆盖的方法的相同.
2.返回类型必须与被覆盖的方法的返回类型相同
3.访问权限一定要高于被覆盖方法的访问权限(public>protected>default>private)
4.重写方法一定不能抛出新的检查异常或者比被覆盖方法申明更加宽泛的检查型异常.例如,
父类的一个方法申明了一个检查异常IOException,在重写这个方法是就不能抛出Exception,只能抛出IOException的子类异常,可以抛出非检查异常.
而重载的规则:
1.必须具有不同的参数列表;
2.可以有不同的返回类型,只要参数列表不同就可以了;
3.可以有不同的访问修饰符;
4.可以抛出不同的异常;
可变参数技术用于解决方法接收的参数个数不固定的情况,例如:
System.out.println(add(2,3,5));
System.out.println(add(1,2,3,5));
可变参数的特点:
(1) 只能出现在参数列表的最后;
(2) …位于变量类型和变量名之间,前后有无空格都可以;
(3) 调用可变参数的方法时,编译器为该可变参数隐含创建一个数组,在方法体中以数组的形式访问可变参数。
实例代码如下:
public class VariableParameter
{
public static void main(String[] args)
{
VariableParameter pv=new VariableParameter();
System.out.println(pv.add(1, 2,3));
}
public int add(int x,int ... args)//args为可变参数变量名,可为任意变量名称
{
int sum=x;
for(int i=0;i<args.length;i++)//编译器为该可变参数隐含创建一个以可变参数变量名为名的数组,方法体中以数组的形式访问可变参数
sum+=args[i];
return sum;
}
}
增强for循环
语法:
for(type 迭代变量名:集合变量名){…}
注意事项:
(1) 迭代变量必须在for( )的括号中定义
(2) 集合变量可以是数组或者实现了Iterable接口的集合类
把上面的实例用增强for循环的方式改写后的代码如下:
public class VariableParameter {
public int add(int x,int...args)//args为可变参数变量名,可为任意变量名称
{
int sum=x;
/*for(int i=0;i<args.length;i++)
sum+=args[i];*/
for(int arg:args) //迭代变量arg可改为任意变量名称,如 a或b等
sum+=arg;
return sum;
}
public static void main(String[] args) {
VariableParameter pv=new VariableParameter();
System.out.println(pv.add(1, 2,3));
}
}
}
基本数据类型的自动装箱与拆箱
自动装箱:
Integer iObj = 3; //将整数3赋给整数对象iObj,这样iObj对应的整数值就是3这利用的就是自动装箱技术,在jdk1.5之前,这是不行的,必须:Integer iObj=new Integer(3);
自动拆箱:
System.out.println(iObj + 12);//将Integer对象iObj对应的整数值直接与整数12相加,这利用的就是自动拆箱技术,这在jdk1.5之前是不行的,必须:iObj.intValue()+12
基本数据类型的对象缓存:
Integer num1=12;
Integer num2=12;
System.out.println(num1==num2); 结果:true
Integer i1 = 137;
Integer i2 = 137;
System.out.println(i1 == i2); 结果:false
原因:
对于1个字节所能表示的所有整数即-128至127,在堆内存中都为它们创建了相应的对
象,当程序中要为某个整数(范围在-128至127)创建多个对象时,这些对象的引用对应堆
内存中的同一个地址,对象的引用都相等, 所以结果:true;对与超出1个字节表示范围
的整数,在堆内存中并没有为它们创建相应的对象,所以当程序中要为某个整数(范围不在
-128至127)创建多个对象时,它们在堆内存中会各自创建各自的对象。对象的引用指向不同的堆内存地址,所以结果为false
附:
Integer i3 = Integer.valueOf(3);
Integer i4 = Integer.valueOf(3);
System.out.println(i3==i4); 结果:true
Integer i3 = Integer.valueOf(312);
Integer i4 = Integer.valueOf(312);
System.out.println(i3==i4); 结果:false
享元模式 flyweight
假设有一群小对象,它们有很多属性相同,则把它们变成一个对象,那些不相同的属性则把他们变成对象方法的参数,称之为这个对象的外部状态,那些相同的属性成之为这个对象的内部状态
枚举
枚举就是要让某个类型的变量的取值只能为若干个固定值中的一个,否则编译器就会报错,实际上定义枚举就相当于定义一个类,类中可以定义构造方法,成员变量,普通方法和抽象方法,类中的静态常对象就相当于枚举的元素值,且这个类的构造函数为私有,所以不能在类的外部实例化,当在外部要用到这个类的对象时只能调用这个类已定义好的静态常对象。枚举也是一样,当在用到枚举类型变量时,只能用枚举名.值得形式给枚举类型的变量赋值。
下面我用普通类来实现枚举功能,定义一个Weekday1的类来模拟枚举功能,Weekday1可用两种思路来写
思路一代码:
public class WeekDay1 {
private WeekDay1(){} //定义私有的构造方法,由此不能在类的外部创建这个类的对象
//SUN和MON是WeekDay1类的全局常对象,在这只写这两个,实际要把周一至周天全列出来
public final static WeekDay1 SUN = new WeekDay1();
public final static WeekDay1 MON = new WeekDay1();
public WeekDay1 nextDay(){
if(this == SUN){
return MON;
}else{
return SUN;
}
}
public String toString(){
return this==SUN?"SUN":"MON";
}
}
}
思路二代码:
public abstract class WeekDay1 { /*WeekDay1为抽象类,不能被实例化,即不能用new关键字产生对象,只有覆盖了其所有抽象方法的子类才能被实例化*/
private WeekDay1(){} //定义私有的构造方法,由此不能在此类的外部创建这个类的及其子类对象,因创建子类对象时需调用父类的默认构造方法
//SUN和MON是WeekDay1类的全局常对象,在这只写这两个,实际要把周一至周天全列出来
public final static WeekDay1 SUN = new WeekDay1(){ /*创建一个公有的全局常匿名内置类对象,这个匿名内置类为WeekDay1的子类,所以需覆盖其所有抽象方法,创建的这个匿名对象也是子类的,但子类对象可以当做父类对象来用,所以SUN的类型可为WeekDay1*/
@Override
public WeekDay1 nextDay() {
// TODO Auto-generated method stub
return MON;
}
};
public final static WeekDay1 MON = new WeekDay1(){
@Override
public WeekDay1 nextDay() {
// TODO Auto-generated method stub
return SUN;
}
};
public abstract WeekDay1 nextDay();//声明抽象方法,包含抽象方法的类必为抽象类,由此,此类必为抽象类,需用abstract修饰
public String toString(){
return this==SUN?"SUN":"MON";
}
}
}
之后:当我们在别的类中需要用到的Weekday1对象时就只能用类名引用两个值SUN或MON
例如:
public TestWeekDay1{
public static final void main(String [] args)
{
WeekDay1 wk1=WeekDay1.SUN; //只能引用,不能创建
System.out.println(wk1.toString()); //结果 SUN
}
}
下面我们定义二个枚举WeekDay、TrafficLamp,并总结其特性:
package cn.itcast.day1;
import java.util.Date;
public class EnumTest {
public static void main(String[] args) {
WeekDay weekDay2 = WeekDay.FRI;
System.out.println(weekDay2); //自动帮我们实现了toString,结果:FRI
System.out.println(weekDay2.name());//结果:FRI
System.out.println(weekDay2.ordinal());//排行第几从0开始,结果:5
System.out.println(weekDay2.getClass());//得到自己的类,结果:WeekDay
System.out.println(WeekDay.valueOf("SUN").toString());
System.out.println(WeekDay.values().length);/* WeekDay.values()返回一个包含WeekDay内所有成员变量的数组*/
}
public enum WeekDay{ /*相当于一个内部类,等级和成员方法一样,所以其访问权限也和方法一样有4种public protected default private*/
SUN(1),MON(),TUE,WED,THI,FRI,SAT;/*枚举的值,相当于类的全局常对象,当类被调用时,其内部的全局常对象也会自动初始化,它们默认的是调用的类的无参构造函数。同样,当枚举WeekDay被调用时,枚举的值(它们本来就是全局常对象)也会初始化,默认调用的也是枚举的无参构造函数。而如果想让它们调用有参构造函数则需要在其后加上(参数值列表)。如果枚举内有方法,需放在值列表得后面且列表之后需加上;如果枚举内无方法,则值列表后可加;也可不加*/
private WeekDay(){System.out.println("first");}//构造方法必须是私有的
private WeekDay(int day){System.out.println("second");}
}
public enum TrafficLamp{
RED(30){
public TrafficLamp nextLamp(){
return GREEN;
}
},
GREEN(45){
public TrafficLamp nextLamp(){
return YELLOW;
}
},
YELLOW(5){
public TrafficLamp nextLamp(){
return RED;
}
};
public abstract TrafficLamp nextLamp();/*在枚举中定义了一个抽象方法则这个枚举也成为抽象的了,因为枚举本身就是类,所以它就不能再实例化,即new对象,而需要调用覆盖它所有抽象方法的子类去创建对象,因为值列表中的元素本身就是静态常对象,所以此时也不能像枚举WeekDay里面的值列表那样定义了,而需要在元素后面加上子类的类体,即创建匿名内置类对象*/
@SuppressWarnings("unused")
private int time;
private TrafficLamp(int time){this.time = time;}
}
}
总结:
(1)枚举就相当于一个内部类,等级和成员方法一样,所以其访问权限也和方法一样有4种public protected default private体内也可以定义构造方法,成员变量,普通方法和抽象方法
(2)枚举元素必须位于枚举体中的最开始部分,枚举元素列表的后面要有分号与其他成员分隔。把枚举中的成员方法或变量等放在枚举元素的前面,编译器报错。
(3)带构造方法的枚举
构造方法必须定义成私有的
枚举元素MON()和MON的效果一样,都是调用默认的构造方法。
如想调用其他的构造方法需MON(参数值列表);
(4)带抽象方法的枚举
因为枚举本身就是类,所以它就不能再实例化,即new对象,而需要调用覆盖它所有抽象方法的子类去创建对象,因为值列表中的元素本身就是静态常对象,所以此时也不能像枚举WeekDay里面的值列表那样定义了,而需要在元素后面加上子类的类体,即创建匿名内置类对象
(5)枚举只有一个成员时,就可以作为一种单例的实现方式
反射(Reflection)
它不是jdk1.5的新特性,而从jdk1.2的时候就有了
Reflection。这个字的意思是“反射、映象、倒影”,用在Java身上指的是我们可以于运行时加载、探知、使用编译期间完全未知的类。换句话说,Java程序可以加载一个运行时才得知名称的类,获悉其完整构造(但不包括methods定义),并生成其对象实体、或对其fields设值、或唤起其methods。
JAVA类中的元素即为:包名、类名、成员变量、普通成员方法、构造方法等
JAVA中的元素类就是JAVA为每个元素都定义一个类,例如(构造方法类:Constructor 、成员变量类:Field 、成员方法类:Method)等,这些元素类提供操作相应元素、提供相应元素信息、实现相应元素功能的具体方法。
反射就是将JAVA类中所有的元素都映射到相应的java元素类上,而JAVA类中的每个元素的具体单位代码(如一个变量,或一个成员方法)则为其对应元素类的一个具体实例对象,由此可通过各个元素类的具体实例对象来操纵自身、实现自身的功能等。元素类并无构造函数,不能被实例化,怎样才能得到其实例对象呢?
Class类中提供了各种返回元素类实例的方法
JAVA虚拟机在加载JAVA字节码文件或当加载器(class loader)的defineClass()被JVM调用时,它就会创建一个Class对象与已加载到虚拟机的类的字节码相对应,
可以通过下面的3种常用方法获取JVM为字节码文件创建的那个对应的Class对象,通过此对象调用Class类的各种get方法即可返回对应的字节码文件元素类的实例
反射的基石Class类
Java中的各个类也都是一种实实在在存在的事物,和人一样,只不过人是动物,而它们是计算机中Java中的各个类,用于描述人的类是person类,而用于描述Java中的各个class的类叫Class类,Person类的一个实例,对应某个人的一些具体特性,及功能的描述。Class的一个具体的实例就对应一个class的所具有的功能及特性
Class 类十分特殊。它和一般类一样继承自Object,其实体用以表达Java程序运行时的类和接口,也用来表达enum、array、primitive Java types(boolean, byte, char, short, int, long, float, double)以及关键词void。
磁盘上的源程序经编译生成某个类的字节码文件,并存进磁盘中,当需要用到这个类的时候,java虚拟机就需要先将这个类的字节码文件加载到内存中,在加载的时候,JVM就会为这个字节码在内存中创建一个对应的Class类对象,这个对象就是加载到内存中的那个字节码对应的Class对象,例如:我们有一个Person类,当我们要创建一个人的实例时,Person类的字节码文件会被加载到内存中,而此时JVM就会在内存中自动创建一个对应Person类字节码Class对象,获取字节码对应的Class实例对象的3种常用方法:
(1) 类名.class,例如:Person.class//在源程序里直接写上
(2) 对象.getClass(),例如,new Person.getClass()//获得创建Person对象的字节码对应的Class对象。Person对象先创建后getClass(),所以字节码已被加载
(3) Class.forName(“类名”),例如:Class.forName(“java.lang.String”)// String类还没被加载到虚拟机中,这是用类加载机去加载磁盘上的名为:String的类,这是一种静态方法,
做反射的时候主要用第3种,类名是作为一个变量由外界传递过来的,在写源程序的时候还不知道类名,等运行的时候,外界才会送来
有9个预定义的Class对象
(1)8个基本类型(boolean,byte,char,short,int,long,float,double)和void,void是一个类,它们的Class对象都是基本类型对象
(2)每个类都可以称作一种类型
(3)int.class==Integer.TYPE// Integer.TYPE是其所包装的那个基本类型所对应的Class对象
System.out.println(int.class.isPrimitive());//判断int的Class对象是不是预定义的基本类型Class对象结果:true
System.out.println(int.class == Integer.class);//结果:false
System.out.println(int.class == Integer.TYPE);//结果:true
System.out.println(int[].class.isPrimitive());//判断数组类是不是一种基本类型,结果:false
System.out.println(int[].class.isArray()); //判断一种类型是不是数组类型 结果:true
数组类型的Class实例对象:
(1)[]数组也是一种类型,即也对应一种类,但不是基本类型
(2)用于判断某类型的Class实例对象是不是数组类型的: 类名.class.isArray()
总之,只要在源程序中出现的类型,都有各自的Class实例对象
Constructor类
代表某个类构造方法的类
得到某个类的所有的构造方法:
例子:
Constructor [] constructors=Class.forName(“java.lang.String”).getConstructors();
得到某个类的一个构造方法:
例子:
Constructor constructor1=Class.forName(“java.lang.String”).getConstructor(StringBuffer.class)
创建实例对象:
通常方式:String str=new String(new StringBuffer(“abc”));
反射方式:String str=(String) constructor1.newInstance(new StringBuffer(“abc”));//调用获得的方法时要用到上面相同类型的实例对象,此newInstance(String str)是Constructor类中的方法用于创建有参构造方法
Class.newInstrance()方法:
例子:String str=(String) Class.forName(“java.lang.String”). newInstance();
该方法内部会先得到String中默认的构造方法元素类对象,然后用该构造函数去创建String类的实例对象。所以当我们正需要调用String类中的无参构造函数用反射的方法创建String类的对象,这样就可以省点事了
Field类
Field类是对应字节码中成员变量的元素类,代表某个类中的成员方法
Field对象是字节码里面成员变量所对应的的对象,即字节码中成员变量元素类的具体实例,但不是字节码所创建的某个对象的成员变量对象,简单说,Field对象是字节码类的不是它的对象的
public class ReflectPoint
{
private int x;
public int y;
}
ReflectPoint pt1 = new ReflectPoint(3,5);
Field fieldY = pt1.getClass().getField("y");
//fieldY的值是多少?是5,错!fieldY不是对象身上的变量对象,而是类上,要用它去取某个对象上对应的值
System.out.println(fieldY.get(pt1));//获取字节码中成员变量y所对应的fieldY对象在由字节码所生成的对象pt1上对应的值 结果:5
Field fieldX = pt1.getClass().getDeclaredField("x");//x定义成私有的了,Class对象不能用getField("x");获取到字节码中变量x所对应的对象,必须用getDeclaredField("x");才能获得
fieldX.setAccessible(true);//获得了字节码中私有变量x所对应的对象后,并不能用,需要用此句将其强制改为可用的
System.out.println(fieldX.get(pt1));
Method类
Method类代表某个类中的成员方法
得到类中的某一个方法:
Method charAt=Class.forName(“java.lang.String”).getMethod(“charAt,int.class”);
调用方法:
通常方式:System.out.println(str.charAt(1));
反射方式:
Method methodCharAt = String.class.getMethod("charAt", int.class);
System.out.println(methodCharAt.invoke(str1, 1));//如果str1为null,说明这个方法是静态方法,不需要用对象去调用
Jdk1.4和jdk1.5的invoke方法的区别:
Jdk1.5: public Object invoke(Object obj,Object…args)
Jdk1.4: public Object invoke(Object obj,Object[]args)
用反射方式执行某个类中的main方法
目标:写一个程序,这个程序能够根据用户提供的类名,去执行该类中的main方法
(1)普通方法:TestArguments.main(new String[]{"111","222","333"});
缺点:在源程序中必须得指明执行哪个类中的main方法
(2)用反射方式执行某个类中的main方法
优点:不用在这个源程序中指明具体执行哪个类中的main方法,只须给这个源程序传递一个参数,参数即类名即可
String startingClassName = args[0];
Method mainMethod = Class.forName(startingClassName).getMethod("main", String[].class);
/*mainMethod.invoke(null, new Object[]{new String[]{"111","222","333"}});mainMethod所对应的main方法只接受一个数组参数, 而当这句执行的时候,到了main方法那边,它会把new Object[]{new String[]{"111","222","333"}}解开, 里面就有一个数组,传递给args就可以了,但如果不用Object数组包装,它就会解开String数组,把三个参数传递过去,编译就会出错,因为那边的main()方法只接受一个参数,且参数类型为字符串数组*/
mainMethod.invoke(null,(Object)newString[]{"111","222","333"}); //作用同上面的一句注释的代码相同,这样传递过去,就不拆包了
数组的反射
数组也是一种类型即对应一个类例如:int[] ,String[]对应的类分别为:[I, [Ljava.lang.String
int[] a1=new int[3];a1是数组类 [I的一个对象
(1)数组元素类型相同,维数也相也同的数组属于同一个类型,具有相同的Class对象(如a1.getClass())即属于同一个类
(2)代表数组的Class实例对象的getSuperClass()方法返回的父类的Object类对应的Class.
(3)基本类型的一维数组可以被当做Object类型使用,不能当做Object[]类型使用;非基本类型的一维数组,既可以当做Object类型使用,又可以当做Object[]类型使用。
(4)Array工具用于完成对数组的反射操作。
printObject(a4);
printObject("xyz");
private static void printObject(Object obj) {
Class clazz = obj.getClass();
if(clazz.isArray()){
int len = Array.getLength(obj);
for(int i=0;i<len;i++){
System.out.println(Array.get(obj, i));
}
}else{
System.out.println(obj);
}
}
int [] a1 = new int[]{1,2,3};
int [] a2 = new int[4];
int[][] a3 = new int[2][3];
String [] a4 = new String[]{"a","b","c"};
System.out.println(a1.getClass() == a2.getClass());//结果:true,即a1和a2属于同一个数组类
System.out.println(a1.getClass()== a4.getClass());//结果:false
System.out.println(a1.getClass()==a3.getClass());//结果:false
System.out.println(a1.getClass().getName());//结果:[I,即创建对象a1的类的名字是:[I,这是数组类的名称
System.out.println(a1.getClass().getSuperclass().getName());//结果:java.lang.Object即[I类的父类为Object
System.out.println(a4.getClass().getSuperclass().getName());//结果:java.lang.Object即[S类的父类为Object
Object aObj1 = a1;//Object是所有类的父类,a1是数组对象,可以这样
Object aObj2 = a4;
//Object[] aObj3 = a1;//Object不是int的父类,不能这样做
Object[] aObj4 = a3;/*a3是一个二维数组,相当于先定义了一个一维数组[] a3,每个数组元素又对应一个一维整数数组即 int[] []a3,而int[],即可作为一维数组
[] a3的每个元素的类型,类型int[]对应的类就是 [I, Object是[I类的父类,所以
这样是可以的*/
Object[] aObj5 = a4;//Object是String类的父类,可以将一个字符串数组转换成Object数组
System.out.println(a1);//结果:数组首地址值
System.out.println(a4); //结果:数组首地址值
System.out.println(Arrays.asList(a1)); //结果:数组首地址值
System.out.println(Arrays.asList(a4)); //结果:[a,b,c]