1. 静态导入
注意:这里指的静态导入不同于JavaWeb中的静态导入。
import语句可以导入一个类或某个包中的所有类。
import static语句导入一个类中的某个静态方法或所有静态方法
语法举例:
import static java.lang.Math.sin;
import static java.lang.Math.*;
静态导入就是为简化代码的书写,可以少写一个类名。
静态导入在Java EE中少用,在android中常用。
importstatic java.lang.Math.*;
publicclass StaticImport {
publicstaticvoid main(String[]args) {
System.out.println(PI);//这里不用写成Math.PI了
System.out.println(abs(E));
}
}
2. 可变参数
可变参数 add(int…b) 英文省略号(三个点),参数个数可以变化,不是类型可变。
可变参数的方法被调用的时候,传入的实际参数必须符合可变参数规定的类型,但是参数的个数不限。也可以传入该类型的数组。
例如:
System.out.println(add(2,3,5));
System.out.println(add(1,2,3,5));
可变参数的特点:
只能出现在参数列表的最后;
...位于变量类型和变量名之间,前后有无空格都可以;
调用可变参数的方法时,编译器为该可变参数隐含创建一个数组,在方法体中以数组的形式访问可变参数。
publicclass ChangeParam {
publicstaticvoid main(String[]args) {
System.out.println(add(1,2,3));//6
System.out.println(add(1,2,3,3));//9
int[] bs = {1,2,3};
System.out.println(add(bs));//6
}
privatestaticint add(int...b) {
int sum = 0;
for (int i : b) {
sum+=i;
}
return sum;
}
}
3.增强for循环
增强for循环是在JDK1.5中增加的。
语法:
for ( type 变量名:集合变量名 ) { … }
注意事项:
迭代变量必须在( )中定义!
集合变量可以是数组或实现了Iterable接口的集合类
Foreach其实是迭代器一种简写形式。
字符串连接常用StringBuilder(1.5)线程不安全,StringBuffer(1.0)线程安全。
1、 在什么时候用foreach循环,什么时候用普通for循环,普通for强调的是索引,foreach强调的是里面的元素。
2、 只要是在业务逻辑中使用到索引就需要普通for,元素要被修改也要用普通for,其他的用foreach。
注意:在foreach中修改(更改内容,删除元素)List的元素会并发性修改异常,可以在更改完或删除后break。
publicclass ForeachTest {
publicstaticvoid main(String[]args) {
List<Integer>nums = new ArrayList<Integer>();
nums.add(1);
nums.add(2);
nums.add(3);
nums.add(4);
nums.add(5);
for (Integer num :nums) {
if(num == 3) {
nums.remove(num);//出异常java.util.ConcurrentModificationException,并发性修改异常
break; //加上才不出异常
}
}
}
3. 基本数据类型的自动拆箱与装箱
基本数据类型和其包装类:
byte Byte
char Character
int Integer
short Short
long Long
float Float
double Double
boolean Boolean
基本数据类型都有默认值,如int的默认值为0,包装类型的默认值为null。
127 是byte取值的临界值。
如果是基本数据类型通过自动装箱转为包装类型,那么当基本数据类型的值在byte(-128----127)的取值范围内时,==比较是相等的,返回true。
==比较的是对象的内存地址值是否相同
equals比较的是对象的内容是否相同
// ==比较的是对象的内存地址值是否相同
// equals比较的是对象的内容是否相同
Integer a = 8;
Integerb = 8;
System.out.println(a == b); //true
System.out.println(a.equals(b));//true
Integerc = 127;
Integerd = 127;
System.out.println(c == d);//true
System.out.println(c.equals(d));//true
Integere = 128;
Integerf = 128;
System.out.println(e== f);//false 在内存中的引用不同了
System.out.println(e.equals(f));//true 内容是相同的
//直接创建对象时,都是不同的对象,==比较返回false
Integern1 = new Integer(8);
Integern2 = new Integer(8);
System.out.println(n1== n2); //false
System.out.println(n1.equals(n2));//true
4. 枚举
定义在枚举里的都是对象,对象用逗号隔开,用分号结束。枚举就相当于一个类,其中也可以定义构造方法、成员变量、普通方法和抽象方法。
枚举元素必须位于枚举体中的最开始部分,枚举元素列表的后要有分号与其他成员分隔。把枚举中的成员方法或变量等放在枚举元素的前面,编译器报告错误。
当没有调用无参的构造方法时,MON和MON()效果一样。
枚举的用途:
1、 在现实生活中表示有限的对象。
2、 用于分类
3、 枚举是java中的一种类型,可以定义成员变量(只能定义在枚举对象后面)
4、 也可以定义成员方法,也有构造方法(私有的)
5、 在枚举中所有的构造方法都是私有的,不能够直接在外面创建对象,默认的构造方法是一个私有的无参构造发方法,省略了括号。
枚举的API
valuesOf(Stting s) 将一个字符串转成指定的对象
values()返回对象数组。可以得到所有的对象
星期枚举类:
publicenum Weekday {
//定义7个对象,类加载时就创建了这七个对象
SUN("星期日"), MON("星期一"), TUE("星期二"), WED ("星期三"), THU("星期四"), FRI("星期五"), SAT("星期六");
//成员变量,要放在定义对象的和面
private String chineseName;
private Weekday() {
System.out.println("空参的构造方法");
} //私有的空参构造方法,防止在外面创建对象
private Weekday(StringchineseName) {
System.out.println("有一个参数的构造方法");
this.chineseName = chineseName;
}
public StringgetChineseName() {
returnchineseName;
}
publicvoidsetChineseName(String chineseName) {
this.chineseName = chineseName;
}
/**
* @return返回下一天是星期几
*/
public Weekday next() {
switch (this) {
caseSUN:
returnMON;
caseMON:
returnTUE;
caseTUE:
returnWED;
caseWED:
returnFRI;
caseFRI:
returnSAT;
caseSAT:
returnSUN;
}
returnnull;
}
}
测试类:
publicclass TestEnum {
publicstaticvoid main(String[]args) {
//枚举的api
Weekdaysun = Weekday.valueOf("SUN");//将一个字符串转成枚举对象,参数名要是枚举里对象的名字
System.out.println(sun==Weekday.SUN);//true
Weekday[]values = Weekday.values();//获取枚举类所有的值
for (Weekday weekday :values) {
System.out.println(weekday.getChineseName());
}
//调用枚举自定的方法
System.out.println(Weekday.SAT.next().getChineseName());//星期日
}
}
5. 反射
5.1反射的概念
Java程序中的各个[Java类]属于同一类事物,描述这类事物的Java类名就是Class,就所有的人用一个Person类来描述一样。
反射就是把Java类中的各种成分映射成相应的java类。例如,一个Java类中用一个Class类的对象来表示,一个类中的组成部分:成员变量,方法,构造方法,包等等信息也用一个个的Java类来表示,就像汽车是一个类,汽车中的发动机,变速箱等等也是一个个的类。表示java类的Class类显然要提供一系列的方法,来获得其中的变量,方法,构造方法,修饰符,包等信息,这些信息就是用相应类的实例对象来表示,它们是Field、Method、Contructor、Package等等。
5.2获取字节码对应的实例对象(Class类型)
一个类被类加载器加载到内存中,占用一片存储空间,这个空间里面的内容就是类的字节码,不同的类的字节码是不同的,所以它们在内存中的内容是不同的,这一个个的空间可分别用一个个的对象来表示,这些对象显然具有相同的类型,这个类型就是Class类型。
1)类名.class
如:Class clazz= Person.class
比较少用,因为执行该代码时静态代码块的代码不会执行(静态代码块是在.class文件生成后执行)
2)对象.getClass()
如:Person p =new Person(); Class clazz =p.getClass();
也少用,因为要先创建一个对象。执行时,静态代码块的代码会执行。
3)Class.forName(“全类名”)
Class clazz = Class.forName(“com.maple.test.Person”);//会抛类找不到异常
常用,只要指定全类名就可以了,而且执行时,静态代码块的代码会执行。(JDBC中注册驱动的用意就让其执行静态代码块的代码,以完成一些初始化内容)
// Classclazz = Person.class;//静态代码块不执行
// Personp = new Person();
// Classclazz = p.getClass();//静态代码块执行
try {
//静态代码块执行
Classclazz = Class.forName("com.maple.day1.test.Person");
}catch (ClassNotFoundException e) {
e.printStackTrace();
}
5.3 Class类的API
1)Constructor类
Constructor<?>[] cs =clazz.getConstructors();//获取所有由public修饰的构造方法
for(Constructor<?> c : cs) {
System.out.println(c.getName());
}
Constructor<?> c1 =clazz.getConstructor(); //获取public修饰的空参构造方法
Person p1 = (Person) c1.newInstance();//调用空参构造方法创建对象
Constructor<?> c2 =clazz.getConstructor(Integer.class, Integer.class, String.class); //获取满参构造方法,指定参数的类型
Person p2 = (Person) c2.newInstance(1, 20, "张三"); //调用满参构造方法创建对象,传入三个对应类型的实参
System.out.println(p2);
2)Method类
Method[]ms = clazz.getMethods();//获取所有由public修饰的方法
for (Method m : ms) {
System.out.println(m.getName());
}
Method m = clazz.getMethod("getName");//获取所有由public修饰getName方法
Personp = new Person(2, 23, "张三");
System.out.println(m.invoke(p));
5.4用反射调用main方法
mainMethod.invoke(null,new Object[]{newString[]{"xxx"}});//正确
mainMethod.invoke(null,(Object)newString[]{"xxx"}); ,编译器会作特殊处理,编译时不把参数当作数组看待,也就不会数组打散成若干个参数了
publicstaticvoid main(String[]args) throws Exception {
Class<?>clazz = TestMainMethod.class;
Methodmain = clazz.getMethod("main", String[].class); //得到main方法
String[]strs = {"aa", "bb", "cc", "dd"}; //也是Object的子类
// main.invoke(null,strs); //报错,参数要一个数组,strs虽然本身是一个数组,但它被当作一个单独的元素传入了
main.invoke(null, new Object[] { strs});//明确指定传入的是数组
}
6. 注解
6.1注解的概念
注解相当于一种标记,在程序中加了注解就等于为程序打上了某种标记,没加,则等于没有某种标记,以后,javac编译器,开发工具和其他程序可以用反射来了解你的类及各种元素上有无何种标记,看你有什么标记,就去干相应的事。标记可以加在包,类,字段,方法,方法的参数以及局部变量上。
如:
@override注解告诉编译器这是要覆盖父类中的某个方法,如果父类中没有该方法则报错。
注意:枚举和注解都是特殊的类,不能用new 创建它们的实例对象,创建枚举的实例对象就是在其中增加元素。
标签的属性都是以方法的形式存在的。String name() default “李四” 默认值,有默认值的属性在使用注解时可以不指定属性的值,否则必须指定。
数组类型时,当只有一个元素时可以只写一个字符串。
标签里面还可以有标签。
如果注解的属性只有一个,且名字是value,可以省略value,直接赋值就可以了。
6.2自定义注解类
@Target({ElementType.TYPE, ElementType.METHOD}) //作用位置,里面是一个数组
@Retention(RetentionPolicy.RUNTIME) //保留级别,有三个级别,程序员只会用到Runtime级别
public@interfaceMyAnno { //定义一个注解类,通过@inferface
//注解的属性,通过以方法的形式存在
Stringname() default"张三"; //默认值是“张三”
Stringage() default"30";
String[]lovers() default"sleep";
6.3应用注解
@MyAnno//应用自定义的注解在该类上
publicclass Person {
private Integer id;
private Integer age;
private String name;
@MyAnno//应用自定义注解在该方法上
public String getName() {
returnname;
}
}
6.4解析注解
Class<?>clazz = Person.class;
//判断类上是否有指定的注解
boolean flag =clazz.isAnnotationPresent(MyAnno.class);
System.out.println(flag); //true
MethodgetName = clazz.getMethod("getName");
//判断方法上是否有指定的注解
boolean flag2 =getName.isAnnotationPresent(MyAnno.class);
System.out.println(flag2);//true
//得到该注解
MyAnno myAnno =getName.getAnnotation(MyAnno.class);
Stringage = myAnno.age();//获取年龄属性
Stringname = myAnno.name();//获取姓名属性
System.out.println(name+ "---" + age);
7. 泛型
7.1泛型的概念
泛型是jdk1.5引入的。泛型是提供给javac编译器使用的,可以限定集合中的输入类型,让编译器挡住源程序中的非法输入,编译器编译带类型说明的集合时会去除掉“类型”信息,使程序运行效率不受影响,对于参数化的泛型类型,getClass()方法的返回值和原始类型完全一样。
普通方法、构造方法和静态方法中都可以使用泛型.
7.2 关于ArrayList<E>类定义和ArrayList<Integer>类
整个称为ArrayList<E>泛型类型
ArrayList<E>中的E称为类型变量或类型参数
整个ArrayList<Integer>称为参数化的类型
ArrayList<Integer>中的Integer称为类型参数的实例或实际类型参数
ArrayList<Integer>中的<>念着typeof
ArrayList称为原始类型
7.3参数化类型与原始类型的兼容性
//参数化类型可以引用一个原始类型的对象,只编译报告警告而已没影响
List<Integer> nums = new ArrayList();
//原始类型可以引用一个参数化类型的对象,只编译报告警告而已没影响
List nums2 = newArrayList<Integer>();
7.4参数化类型不考虑类型参数的继承关系
//两行代码都报错,编译不通过
List<String>strs = new ArrayList<Object>();
List<Object>str2 = new ArrayList<String>();
7.5不允许创建泛型变量的数组
编译器不允许创建泛型变量的数组。即在创建数组实例时,数组的元素不能使用参数化的类型。
List<Integer>[] ns = newArrayList<Integer>()[10];//错误
List<Integer>[] ns = new ArrayList[10];//正确
7.6泛型中?通配符
使用?通配符可以引用其他各种参数化的类型,?通配符定义的变量主要用作引用,可以调用与参数化无关的方法,不能调用与参数化有关的方法。
publicstaticvoidshow(Collection<?> cols) {
for (Object o : cols){
System.out.println(o);
}
// cols.add("dd");//错误,因为存放的元素不一定是String类型的,与参数化类型有关的方法不能调用
cols.size();//正确,与参数化类型无关的方法可以调用
cols = new ArrayList<String>(); //正确,ArrayList<String>是一种具体的类型
}
限定通配符的上边界(上限):
实际类型参数只能接收Number和其子类。
正确:Vector<?extends Number> x = new Vector<Integer>();
错误:Vector<?extends Number> x = new Vector<String>();
限定通配符的下边界(下限):
实际类型参数只能接收Integer和其父类。
正确:Vector<?super Integer> x = new Vector<Number>();
错误:Vector<?super Integer> x = new Vector<Byte>();
提示:
限定通配符总是包括自己。
?只能用作引用,不能用它去给其他变量赋值:
Vector<?extends Number> y = new Vector<Integer>();
Vector<Number>x = y;
上面的代码错误,原理与Vector<Object > x11= new Vector<String>();相似,
只能通过强制类型转换方式来赋值。
7.7泛型方法
/**
* <T>必须放在修饰符之后返回值之前
* 参数中的T在 <T>定义过
* 类型参数通常用大写字母
*/
publicstatic <T> void print(T t) {
System.out.println(t);
}
publicstatic <T> TautoType(Object o) {
// 类型参数的类型推断
// T由返回值和参数类型决定
return (T)o;
}
交换数组中的两个元素的位置的泛型方法语法定义如下:
static <E> void swap(E[] a,int i, int j) {
Et = a[i];
a[i]= a[j];
a[j]= t;
}//或用一个面试题讲:把一个数组中的元素的顺序颠倒一下
只有引用类型才能作为泛型方法的实际参数,swap(new int[3],3,5);语句会报告编译错误。
在泛型中可以同时有多个类型参数,在定义它们的尖括号中用逗号分,例如:
public static <K,V> V getValue(K key) { return map.get(key);}
7.8定义泛型类
classGenericDAO<T> {
//定义泛型
}
classSubGenericDAO<W> extendsGenericDAO<W> {
//扩展泛型,要指定类型
}
class WorkerDAO extendsGenericDAO<Worker> {
//继承泛型,指定具体的类型为Worker
}
注意:
在对泛型类型进行参数化时,类型参数的实例必须是引用类型,不能是基本类型。
当一个变量被声明为泛型时,只能被实例变量、方法和内部类调用,而不能被静态变量和静态方法调用。因为静态成员是被所有参数化的类所共享的,所以静态成员不应该有类级别的类型参数。
7.9类型参数的类型推断
编译器判断泛型方法的实际类型参数的过程称为类型推断,类型推断是相对于知觉推断的,其实现方法是一种非常复杂的过程。
根据调用泛型方法时实际传递的参数类型或返回值的类型来推断具体规则如下:
当某个类型变量只在整个参数列表中的所有参数和返回值中的一处被应用了,那么根据调用方法时该处的实际应用类型来确定,这很容易凭着感觉推断出来,即直接根据调用方法时传递的参数类型或返回值来决定泛型参数的类型,例如:
swap(new String[3],3,4) static <E> void swap(E[] a, int i, int j)
当某个类型变量在整个参数列表中的所有参数和返回值中的多处被应用了,如果调用方法时这多处的实际应用类型都对应同一种类型来确定,这很容易凭着感觉推断出来,例如:
add(3,5) static <T> T add(T a, Tb)
当某个类型变量在整个参数列表中的所有参数和返回值中的多处被应用了,如果调用方法时这多处的实际应用类型对应到了不同的类型,且没有使用返回值,这时候取多个参数中的最大交集类型,例如,下面语句实际对应的类型就是Number了,编译没问题,只是运行时出问题:
fill(new Integer[3],3.5f) static <T> void fill(T[] a, T v)
当某个类型变量在整个参数列表中的所有参数和返回值中的多处被应用了,如果调用方法时这多处的实际应用类型对应到了不同的类型,并且使用返回值,这时候优先考虑返回值的类型,例如,下面语句实际对应的类型就是Integer了,编译将报告错误,将变量x的类型改为float,对比eclipse报告的错误提示,接着再将变量x类型改为Number,则没有了错误:
int x =(3,3.5f) static <T> T add(T a, T b)
参数类型的类型推断具有传递性,下面第一种情况推断实际参数类型为Object,编译没有问题,而第二种情况则根据参数化的Vector类实例将类型变量直接确定为String类型,编译将出现问题:
copy(newInteger[5],new String[5]) static <T> void copy(T[] a,T[] b);
copy(newVector<String>(), new Integer[5]) static <T> voidcopy(Collection<T> a , T[] b);