静态导入
一般导入import是导入一个类或某个包中的所有类。而静态导入是import static 导入的类名或者类名的某个静态方法。
其作用是简化书写,调用静态方法时不用再指定包名了,前提是方法名没有重复。
例:
package cn.itcast.day1;
//静态导入
import static java.lang.Math.*;
public class StaticImport {
public static void main(String[] arg)
{
int x=1;
x++;
System.out.println(x);
System.out.println(max(3, 6));
System.out.println(abs(3 - 6));
}
}
可变参数
当写代码时,不知道所写方法的参数个数时,可以用可变参数 ,如public static int add(int x,int ...args)
可变参数注意点:
1、只能出现参数列表最后
2、。。。位于变量类型与变量名之间
3、调用可变参数方法时,实际上是为该可变参数创建一个数组
例:
public class VariableParam {
/**
* @param args
*/
public static void main(String[] args) {
// TODO Auto-generated method stub
System.out.println(add(1, 2, 3, 4, 5, 6, 7));;
}
public static int add(int x,int...args){
int sum=x;
for (int i = 0; i < args.length; i++) {
sum+=args[i];
}
return sum;
}
}
增强for循环
语法:for ( type 变量名:集合变量名 ) { … }
注意:集合变量可以是数组或实现了Iterable接口的集合类
上例用增强for循环演示:
public static int add(int x,int...args){
int sum=x;
for (int arg : args) {
sum+=arg;
}
return sum;
自动拆装箱
//自动装箱 :Integer num1=new Integer(num1);
Integer num1=3;
//自动拆箱:Integer.valueOf(num1)+3
System.out.println(num1+3);
//当数值在-128~127时,对象会被重用
Integer i1=120;
Integer i2=120;
System.out.println(i1==i2); //true
//不在-128~127时,与其他对象一样都再new一个对象
Integer a1=200;
Integer a2=200;
System.out.println(a1==a2);//false
在-128~127内对象重用是一种设计模式,称为享元设计模式:对象一样,对象位置可能不一样,要接受位置参数
枚举
用途:
枚举就是要让某个类型的变量的取值只能为若干个固定值中的一个,否则,编译器就会报错。
枚举可以让编译器在编译时就可以控制源程序中填写的非法值,普通变量的方式在开发阶段无法实现这一目标
枚举可以让编译器在编译时就可以控制源程序中填写的非法值,普通变量的方式在开发阶段无法实现这一目标
注意:
1、枚举类型是一种类型。也有构造函数(必须是私有的),也有成员变量以及成员函数。
2、枚举元素其实就是对象。枚举元素必须位于枚举体的最开始部分。
3、知道如何选择带参数的构造方法。
4、定义抽象成员函数时,知道如何实现成员函数。
例子:
//基本的:包含构造函数的星期类
enum WeekDay{
SUN(2),MON,TUE,WED,THE,FRI,SAT;
//枚举的构造函数,不能是public
private WeekDay(){System.out.println("first");}
private WeekDay(int num){System.out.println("second"+"....."+num);}
}
//高级的:交通灯枚举,包含构造函数及成员
enum TrafficLamp{
GREEN{
public TrafficLamp nextLamp(){
return RED;
}
}
,RED(){
public TrafficLamp nextLamp(){
return YELLOW;
}
}
,YELLOW(1){
public TrafficLamp nextLamp(){
return GREEN;
}
};
public abstract TrafficLamp nextLamp();
private TrafficLamp() {
System.out.println("不带参数的构造函数");
}
private TrafficLamp(int num){
System.out.println("带参数的构造函数");
}
}
public class EnumDemo {
public static void main(String[] args) {
TrafficLamp trafficLampRed=TrafficLamp.RED;
System.out.println(trafficLampRed.nextLamp());
}
}
注解
三个基本注解
@SuppressWarnings
该注解的作用是给编译器一条指令,告诉它对被注解的代码元素内部的某些警告保持静默。比如使用了过时的方法,eclipse会在方法上画一条横线。JAVAC命令也会产生警告。
@SuppressWarnings("dedeprecation)
此注解作用就是告诉编译器,虽然我用的方法过时了,但是我还是坚持要用,你就不要再提示了。
@Deprecated
该注解是告诉用户此方法过时了,不建议使用了。
@Override
该注解是确保重写父类函数成功。
注意:
注解是一个特殊的类型。使用注解,就相当于创建了对象。
注解相当于一种标记,在程序中加了注解就等于为程序打上了某种标记,没加,则等于没有某种标记。
标记可以加在包,类,字段,方法,方法的参数以及局部变量上。
注解的应用
定义注解类————>给一个类加注解————>应用被注解过得类
测试:定义一个注解类,并给一个类加注解时。
public class AnnotationDemo {
@MyAnnotation
public static void main(String[] args) {
//isAnnotationPresent判断是否使用了某个注解
if(AnnotationDemo.class.isAnnotationPresent(MyAnnotation.class)){
System.out.println("yes");
}else{
System.out.println("no");
}
}
}
发现输出结果是no,也就是我们并没有使用注解。这是为何?
答:
注解是一种类型,注解其实也是可以有自己的注解的,称为元注解。其中有一个Retention元注解。
Retention元注解默认值是RetentionPolicy.CLASS,也就是说@MyAnnotation注解在运行的时候已经被过滤掉了。
解决这个问题的方式就是在注解类上在加个元注解@Retention(RetentionPolicy.RUNTIME)。
解决这个问题的方式就是在注解类上在加个元注解@Retention(RetentionPolicy.RUNTIME)。
Tip:
@Retention元注解有三种取值:RetetionPolicy.SOURCE、RetetionPolicy.CLASS、RetetionPolicy.RUNTIME,
分别对应:java源文件-->class文件-->内存中的字节码。
分别对应:java源文件-->class文件-->内存中的字节码。
但还是没有解决,输出的还是no!
因为上面的代码,@MyAnnotation给主函数加注解了。
而 AnnotationDemo.class.isAnnotationPresent(MyAnnotation.class)判断的是类是否使用了注解MyAnnotation。所以输出no。
除了Retention元注解,还有其他的元注解。如Target元注解。限定注解适用位置(类上,方法上,变量上等)。
默认是任意位置。如果设置Target等于ElementType.METHOD,原来加在类上的注解会报错。可以通过数组限定多个适用的位置。
为注解添加属性
注意:注解的属性不是像成员变量一样,而是像成员方法一样。
比如在注解类中添加一个color属性。
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation {
String color();
}
这样在给类添加注解时给属性一个标示,属性是为了更细的区分。
一个注解相当于一个胸牌,如果你胸前贴了胸牌,就是传智播客的学生,否则,就不是。
如果还想区分出是传智播客哪个班的学生,这时候可以为胸牌在增加一个属性来进行区分。
所以在给类添加注解时需要传入属性:
如果还想区分出是传智播客哪个班的学生,这时候可以为胸牌在增加一个属性来进行区分。
所以在给类添加注解时需要传入属性:
public class AnnotationDemo {
@MyAnnotation(color="red")
public static void main(String[] args) {
//isAnnotationPresent判断是否使用了某个注解
if(AnnotationDemo.class.isAnnotationPresent(MyAnnotation.class)){
System.out.println("yes");
}
}
}
泛型
基本应用
Jdk 1.5的集合类希望你在定义集合时,明确表示你要向集合中装哪种类型的数据,无法加入指定类型以外的数据。
没有使用泛型时,只要是对象,不管是什么类型的对象,都可以存储进同一个集合中。
使用泛型集合,可以将一个集合中的元素限定为一个特定类型,集合中只能存储同一个类型的对象,这样更安全;
并且当从集合获取一个对象时,编译器也可以知道这个对象的类型,不需要对对象进行强制类型转换,更方便。
没有使用泛型时,只要是对象,不管是什么类型的对象,都可以存储进同一个集合中。
使用泛型集合,可以将一个集合中的元素限定为一个特定类型,集合中只能存储同一个类型的对象,这样更安全;
并且当从集合获取一个对象时,编译器也可以知道这个对象的类型,不需要对对象进行强制类型转换,更方便。
泛型进阶
泛型是用在编译时候的。即让问题在编译的时候就发现。而在运行的时候是否泛型是没有区别的,编译器编译带类型说明的集合时会去除掉“类型”信息,
目的就是使程序运行效率不受影响。因此,
对于参数化的泛型类型,getClass()方法的返回值和原始类型完全一样。
由于编译生成的字节码会去掉泛型的类型信息,只要能跳过编译器,就可以往某个泛型集合中加入其它类型的数据,
例如,用反射得到集合,再调用其add方法即可。
例如,用反射得到集合,再调用其add方法即可。
import java.util.ArrayList;
public class GenericsDemo {
public static void main(String[] args) throws Exception{
//只能放Integer类型的数据
ArrayList<Integer> al=new ArrayList<Integer>();
al.add(1);//自动装箱
// al.add("abc"); 错误!但可以通过反射越过编译
al.getClass().getMethod("add", Object.class).invoke(al, "abc");
System.out.println(al);
}
}
//输出[1, abc]
附:泛型术语:
ArrayList<E>称为泛型类型
ArrayList<E>中的E称为类型变量或类型参数
ArrayList<Integer>称为参数化的类型
ArrayList<Integer>中的Integer称为类型参数的实例或实际类型参数
ArrayList<Integer>中的<>念着typeof
ArrayList称为原始类型
ArrayList<E>中的E称为类型变量或类型参数
ArrayList<Integer>称为参数化的类型
ArrayList<Integer>中的Integer称为类型参数的实例或实际类型参数
ArrayList<Integer>中的<>念着typeof
ArrayList称为原始类型
参数化类型与原始类型的兼容性:
参数化类型可以引用一个原始类型的对象,编译报告警告,例如,ArrayList<String> a2=new ArrayList();//考虑到对以前代码的兼容性,编译器是可以通过的
原始类型可以引用一个参数化类型的对象,编译报告警告,例如,ArrayList a1=new ArrayList<String>();//原来的方法接受一个集合参数,新的类型也要能传进去
简单地说:两边缺一个没有关系!
参数化类型不考虑类型参数的继承关系:
Vector<String> v = new Vector<Object>(); //错误!不写<Object>没错,写了就是明知故犯
Vector<Object> v = new Vector<String>(); //也错误!
参数化类型可以引用一个原始类型的对象,编译报告警告,例如,ArrayList<String> a2=new ArrayList();//考虑到对以前代码的兼容性,编译器是可以通过的
原始类型可以引用一个参数化类型的对象,编译报告警告,例如,ArrayList a1=new ArrayList<String>();//原来的方法接受一个集合参数,新的类型也要能传进去
简单地说:两边缺一个没有关系!
参数化类型不考虑类型参数的继承关系:
Vector<String> v = new Vector<Object>(); //错误!不写<Object>没错,写了就是明知故犯
Vector<Object> v = new Vector<String>(); //也错误!
简单地说:两边要是都有尖括号,其内容必须相同!
tip:
编译器不允许创建泛型变量的数组。即在创建数组实例时,数组的元素不能使用参数化的类型。
例如,下面语句有错误:
例如,下面语句有错误:
思考题:
下面的代码会报错误吗?
Vector v1 = new Vector<String>();
Vector<Object> v = v1;
答案:编译的时候是不会报错的,因为编译器是一行一行按照语法检查代码的,因此不会出错。
Vector<Integer> vectorList[] = new Vector<Integer>[10];
下面的代码会报错误吗?
Vector v1 = new Vector<String>();
Vector<Object> v = v1;
答案:编译的时候是不会报错的,因为编译器是一行一行按照语法检查代码的,因此不会出错。
Vector<Integer> vectorList[] = new Vector<Integer>[10];
泛型练习:
public class GenericsDemo {
public static void main(String[] args) throws Exception{
HashMap<String,Integer> hm=new HashMap<String, Integer>();
hm.put("xiaoming", 19);
hm.put("zhangsan", 21);
Set<Map.Entry<String,Integer>> entrySet=hm.entrySet();
for(Map.Entry<String , Integer> en:entrySet){
System.out.println(en.getKey()+"::"+en.getValue());
}
}
}
通配符基础
问题:
定义一个方法,该方法用于打印出任意参数化类型的集合中的所有数据,该方法如何定义呢?
如果将printCollection中的参数设置为Collection<Object>类型,那么传入参数的时候,如果参数化的类型是其他类型,就会报错。
定义一个方法,该方法用于打印出任意参数化类型的集合中的所有数据,该方法如何定义呢?
如果将printCollection中的参数设置为Collection<Object>类型,那么传入参数的时候,如果参数化的类型是其他类型,就会报错。
例:
public class GenericsDemo {
public static void main(String[] args) throws Exception{
printCollection(new ArrayList<String>());
//错误。等价于:ArrayList<Object> a=new ArrayList<String>()
}
public static void printCollection(ArrayList<Object> a)
{
//函数体
}
}
Java1.5中使用?通配符可以引用其他各种参数化的类型,?通配符定义的变量主要用作引用。
此时,printCollection方法中 可以调用与参数化无关的方法,不能调用与参数化有关的方法——即方法中不能有泛型。
例如ArrayList类中的add(E e)方法就与参数化有关,不能调用。size()方法与参数化无关就可以调用。
public class GenericsDemo {
public static void main(String[] args) throws Exception{
printCollection(new ArrayList<String>());
}
public static void printCollection(ArrayList<?> a)
{
// a.add("aaa");错误 ,与参数化有关的函数
a.size(); //可以,与参数无关的函数
}
}
之所以add方法是错的,是因为他不知道自己未来匹配的一定是String。
比如:如果add方法可以用,那么如果你是new ArrayList<Interger>岂不是运行会出错?但编译没出错。
注:上述例子没有意义,只是为了说明哪些可以编译通过,哪些不可以。
通配符拓展
限定通配符的上边界:
正确:Vector<? extends Number> x = new Vector<Integer>();
错误:Vector<? extends Number> x = new Vector<String>(); //只能是Number及其子类
限定通配符的下边界:
正确:Vector<? super Integer> x = new Vector<Number>();
错误:Vector<? super Integer> x = new Vector<Byte>();//只能是Integer及其父类
正确:Vector<? extends Number> x = new Vector<Integer>();
错误:Vector<? extends Number> x = new Vector<String>(); //只能是Number及其子类
限定通配符的下边界:
正确:Vector<? super Integer> x = new Vector<Number>();
错误:Vector<? super Integer> x = new Vector<Byte>();//只能是Integer及其父类
自定义泛型方法
由C++的模板函数引入自定义
如下函数的结构很相似,仅类型不同:
int add(int x,int y) {
return x+y;
}
float add(float x,float y) {
return x+y;
}
double add(double x,double y) {
return x+y;
}
C++用模板函数解决,只写一个通用的方法,它可以适应各种类型,示意代码如下:
template<class T>
T add(T x,T y) {
return (T) (x+y);
}
如下函数的结构很相似,仅类型不同:
int add(int x,int y) {
return x+y;
}
float add(float x,float y) {
return x+y;
}
double add(double x,double y) {
return x+y;
}
C++用模板函数解决,只写一个通用的方法,它可以适应各种类型,示意代码如下:
template<class T>
T add(T x,T y) {
return (T) (x+y);
}
Java中的泛型类型(或者泛型)类似于 C++ 中的模板。但是这种相似性仅限于表面,Java 语言中的泛型基本上完全是在编译器中实现,
用于
编译器执行类型检查和类型推断,然后生成普通的非泛型的字节码,这种实现技术称为擦除(erasure)(编译器使用泛型类型信息保证类型安全,
然后在生成字节码之前将其清除)。这是因为扩展虚拟机指令集来支持泛型被认为是无法接受的,
这会为 Java 厂商升级其 JVM 造成难以逾越的障碍。所以,java的泛型采用了可以完全在编译器中实现的擦除方法。
然后在生成字节码之前将其清除)。这是因为扩展虚拟机指令集来支持泛型被认为是无法接受的,
这会为 Java 厂商升级其 JVM 造成难以逾越的障碍。所以,java的泛型采用了可以完全在编译器中实现的擦除方法。
擦除,这就是之前提到的,编译器最终会去掉“类型”信息!!
比如:
例如,下面这两个方法,编译器会报告错误,它不认为是两个不同的参数类型,而认为是同一种参数类型。
private static void applyGeneric(Vector<String> v){
}
private static void applyGeneric(Vector<Date> v){
}
private static void applyGeneric(Vector<String> v){
}
private static void applyGeneric(Vector<Date> v){
}
注意:
Java的泛型方法没有C++模板函数功能强大,java中的如下代码无法通过编译:
<T> T add(T x,T y) {
return (T) (x+y);
}
因为T类型的对象可能并不具备加法运算的功能。
用于放置泛型的类型参数的尖括号紧邻返回值之前。按照惯例,类型参数通常用单个大写字母表示。
虽然上述加法不能通过编译,但其他的一些简单的可以。
<T> T add(T x,T y) {
return (T) (x+y);
}
因为T类型的对象可能并不具备加法运算的功能。
用于放置泛型的类型参数的尖括号紧邻返回值之前。按照惯例,类型参数通常用单个大写字母表示。
虽然上述加法不能通过编译,但其他的一些简单的可以。
例如:交换数组中的两个元素的位置的泛型方法语法定义如下:
public static void main(String[] args) throws Exception{
String[] s=new String[]{"abc","def","rtg"};
int[] i=new int[]{1,2,3};
Integer[] in=new Integer[]{1,2,3};
swap(s,0,1);
//swap(i,0,1);错误
swap(in,0,1); //Ok
}
public static <T> void swap(T[] a,int x,int y){
T temp=a[x];
a[x]=a[y];
a[y]=temp;
}