JDK5的新特性
1.泛型
泛型的好处
a. 提高了程序的安全性
b. 将运行期遇到的问题转移到了编译期
c. 省去了类型强转的麻烦
设计原则:及早失败原则。
a. 方便查找原因
b. 节省计算机资源
①泛型类
定义:把泛型定义在类上面,一个类可以定义多个泛型
泛型命名规则:
只要满足标识符的规则就行,规范:一般用大写字母表示:
T:type
E:element
K:key
V:value
注意事项:泛型只能代指引用数据类型。所以集合只能存储类。
public class Tool<T> { //T类比形参
T obj;
public T getObj() {
return obj;
}
public void setObj(T obj) {
this.obj = obj;
}
}
public class GenericDemo1 {
public static void main(String[] args) {
Tool<String> tool = new Tool<String>(); //new Tool<String>中的String可类比实参,告诉引用变量参数类型。
Tool<String> tool = new Tool<>(); // JDK1.7提供了菱形操作符,可以利用类型推断机制推断出对象的类型。
}
}
②泛型方法
意义:如果不使用泛型方法,当我们需要一个方法支持多种类型时,要定义多种方法来支持多种数据类型,或者把参数类型定义为Object。但是需要强制类型转换,不然不能用那些类的特有方法。
定义:把泛型定义在方法上
范围:在方法的声明和方法体内
格式:public <泛型类型> 返回类型 方法名(…)
Q:为什么泛型定义要在返回值类型前面?
A:因为返回值类型也可以是泛型,所以要先定义
Q:有泛型方法的类一定是泛型类吗?
A:不是!
public class Tool {
public <T> T echo(T t) {
return t;
}
}
class GenericDemo2 {
public static void main(String[] args) {
Tool tool = new Tool();
String s = "Hello";
String s = tool.echo(s); //s把参数类型传递给了T。一定有参数s,因为这样泛型才有意义。
}
}
③泛型接口
把泛型定义在接口上;
范围:整个接口内
public interface Auto<T> {
T run(T t);
}
泛型接口实现的类:
a.普通类
public class Car implements Auto<String> {
@Override
public String run(String s) {
return null;
}
}
public static void main(String[] args) {
Car car = new Car();
String s = car.run("hello");
}
b.泛型类
public class Bus<T> implements Auto<T> {
@Override
public T run(T s) {
return null;
}
}
public static void main(String[] args) { //类型传递过程:创建对象 --> Bus<T> --> Auto<T>
Bus<String> bus = new Bus<>();
String s = bus.run("hello");
}
④泛型通配符(了解):
作用:为了能有像数组那样的性质,但又不想有数组那样的弊端。
String[] strs = {"hello", "world", "java"};
Object[] objs = strs; // String[]虽然不是Object[]的子类,但Object[]仍然可以指向String[]。(那样的性质 Object[]可以指向String[])
objs[0] = new Object(); // 会出现ArrayStoreException。(那样的弊端)
/*
List<String> strs = new ArrayList<>();
strs.add("hello");
strs.add("world");
strs.add("java");
List<Object> objs = strs; // 不允许
*/
- 泛型通配符<?>:可以匹配任意类型
- ? extends E:可以匹配E及其子类
- ? super E:可以匹配E及其父类
class Animal {}
class Dog extends Animal {}
class Cat extends Animal {}
Collection<Object> c1 = new ArrayList<Object>(); //没有通配符的情况
Collection<Object> c2 = new ArrayList<Animal>(); //不允许
Collection<Object> c3 = new ArrayList<Cat>(); //不允许
Collection<Object> c4 = new ArrayList<Dog>(); //不允许
Collection<?> c1 = new ArrayList<Object>(); //<?> 可以匹配任意类
Collection<?> c2 = new ArrayList<Animal>(); //允许
Collection<?> c3 = new ArrayList<Cat>(); //允许
Collection<?> c4 = new ArrayList<Dog>(); //允许
c3.add(new Cat()); //编译看左边,为了避免数组的问题,不允许添加元素
c3.add(new Dog());
c3.add(new Object());
Collection<? extends Animal> c1 = new ArrayList<Object>(); //不允许
Collection<? extends Animal> c2 = new ArrayList<Animal>(); //可以匹配Animal及其子类
Collection<? extends Animal> c3 = new ArrayList<Cat>();
Collection<? extends Animal> c4 = new ArrayList<Dog>();
c3.add(new Cat()); //编译看左边,为了避免数组的问题,不允许添加元素
c3.add(new Dog());
c3.add(new Animal());
Collection<? super Animal> c1 = new ArrayList<Object>(); //可以匹配Animal及其父类
Collection<? super Animal> c2 = new ArrayList<Animal>();
Collection<? super Animal> c3 = new ArrayList<Cat>(); //不允许
Collection<? super Animal> c4 = new ArrayList<Dog>(); //不允许
c2.add(new Animal()); //允许
c2.add(new Object()); //不允许
⑤泛型擦除
将字节码文件.class反编译之后,我们不会看到泛型,说明在编译时会“擦除”泛型信息。
E ----> Object
? extends E —> E
? super E —> Object
练习:
向一个List<String>中添加一个整数。
因为编译后的.class字节码文件没有泛型信息,所以可以通过反射绕过泛型检查。
List<String> list = new ArrayList<>();
list.add("hello");
list.add("world");
list.add("java");
int i = 10;
Class<? extends List> c1 = list.getClass();
Method addMethod = c1.getMethod("add", Object.class) E ---> Object add(E e)
addMethod.invoke(list,i);
System.out.println(list); //[hello, world, java, 10]
//获取该值
//方法一:反射
//方法二:强转(子类转父类)
Object obj = list.get(3);
Integer a = (Integer) obj;
System.out.println(a); // 10
⑥Collection类的toArray方法
<T> T[] toArray(T[] a)
如果数组a能够容纳下集合的元素,那么就会把元素放入数组a中
如果数组a不能够容纳下集合的元素,那么就会创建一个新数组,并把元素放入新数组中。
Collection<String> c = new ArrayList<>();
c.add("hello");
c.add("world");
c.add("java");
String[] arr = new String[3];
String strs = c.toArray(str);
System.out.println(Arrays.toString(strs)); //[hello, world, java]
System.out.println(Arrays.toString(arr)); //[hello, world, java]
System.out.println(arr == strs); // true
String[] arr = new String[2];
String strs = c.toArray(str);
System.out.println(Arrays.toString(strs)); //[null, null]
System.out.println(Arrays.toString(arr)); //[hello, world, java]
System.out.println(arr == strs); // false
正确使用方式:
String[] strs = new String[c.size()];
c.toArray(strs);
System.out.println(Arrays.toString(strs));
⑦数组转化为集合(视图方法)
Arrays:
static <T> List<T> asList(T... a) //视图方法
String[] strs = {"hello", "world", "java"};
List<String> list = Arrays.asList(strs);
System.out.println(list);
list.set(0, "Hello"); //修改集合元素
System.out.println(list); //[Hello, world, java]
System.out.println(Arrays.toString(strs)); //[Hello, world, java]
list.add("wuhan"); //增加集合元素 UnsupportedOperationException。底层是一个数组,大小不能改变
System.out.println(list);
System.out.println(Arrays.toString(strs));
list.remove(0); //删除集合元素 UnsupportedOperationException。底层是一个数组,大小不能改变
System.out.println(list);
System.out.println(Arrays.toString(strs));
2.foreach方法
①格式:
for(元素数据类型 变量 : 数组或Collection集合) {
使用变量即可,该变量即元素
}
②好处:
- 简化数组和集合的遍历操作;
- 增强代码可读性
③坏处:
- 不能修改集合或者数组,只能查看元素
- 无法遍历部分元素
- 没有索引信息,不能操作索引
④底层实现:
遍历对数组还是进行了特殊处理
集合还是运用了迭代器
Q:那些对象可以使用foreach循环
A:数组和实现了Iterable接口的对象。
String[] s = {"hello", "world", "java"};
for(String element : s) {
System.out.println(element);
}
List<String> list = new ArrayList<>();
list.add("hello");
list.add("world");
list.add("java");
for(String s : list) {
System.out.println(s);
}
3.可变长参数
JDK1.5之前是使用数组。
①格式:
方法名(数据类型… 变量名)
②原理:
编译完成后实际底层是一个数组
③注意事项:
可变长参数必须在参数列表后面
Q:一个方法最多可以有几个可变长参数
A:一个
Q:如果一个方法需要多个"可变长参数",该怎么办?
A:前面的可以使用数组
int sum = add(1, 2, 3, 4);
public static int add(int... arr) {
int sum = 0;
for(int i : arr) sum += i;
return sum;
}