可变参数
早期如果想让一个方法可以接收任意数量的参数,实现方式是把多个数据封装为一个数组。而有了可变参数Java会自动把传递的多个参数封装成数组,使用起来就方便多了。
可变参数定义格式
修饰符 返回值类型 方法名(数据类型… 变量名) { }
可变参数的注意事项
- 这里的变量其实是一个数组
- 如果一个方法有多个参数,包含可变参数,可变参数要放在最后
public class VariableParas {
public static void main(String[] args) {
System.out.println(sum(10,20));
System.out.println(sum(new int[]{1,2,3}));
System.out.println(sum(1,2,3));
}
public static int sum(int... data){ //可变参数
int sum = 0;
for(int i : data){ //JVM会自动把多个参数封装成数组
sum += data[i];
}
return sum;
}
}
/*执行结果:
30
6
6
*/
增强for循环
增强for循环用来简化读取数组元素的代码,非常好用。
public class TestDemo{
public static void main(String[] args){
int[] ints = new int[]{1,2,3,4,5};
for(int x: ints){
System.out.println(x);
}
}
}
泛型
对象的向上转型解决了方法参数类型的统一,但是向下转型可能会存在参数类型转换异常(ClassCastException),所以向下转型的操作并不安全。为了解决向下转型的安全问题,java提供了泛型技术。
加入要设计一个坐标系,可以使用整数或浮点数当作坐标,也可以用字符串描述坐标,那么为了实现参数的统一,使用所有对象的父类,Object类作为属性类型。
class Point{
private Object x; //可以保存任意数据
private Object y; //可以保存任意数据
public void setX(Object x){
this.x = x;
}
public void setY(Object y){
this.y = y;
}
public Object getX(){
return x; //注意这里返回的是对象
}
public Object getY(){
return y;
}
}
public class TestDemo{
public static void main(String[] args){
Point pa = new Point();
pa.setX(10.2);
pa.setY(21.2);
double x = (Double)pa.getX();//getX()方法返回一个Object对象,通过向下转型变为Double对象,再通过自动拆箱取出值赋值给一个double变量
double y = (Double)pa.getY();
System.out.println("坐标为:("+x+","+y+")");
Point pb = new Point();
pb.setX("东经123度");
pb.setY("北纬34度");
String bx = (String)pb.getX();//这里向下转型只有在知道了是String类型时才可以,否则很容易出错
String by = (String)pb.getY();
System.out.println("坐标为:("+bx+","+by+")");
}
}
/*执行结果:
坐标为:(10.2,21.2)
坐标为:(东经123度,北纬34度)
*/
Object属性可以接收任意类型的对象,这样在编译时不会出错。但在实际运行时可以会出现参数类型不匹配的问题。下列代码在接收数据时传入的浮点数,通过向上转型变为Object不会出错。但在将数据取出时会强制转换为String类型对象,就发生了ClassCastException异常。
public class TestDemo{
public static void main(String[] args){
Point pa = new Point();
pa.setX(10.2);
pa.setY(21.2);
String x = (String)pa.getX();//double类型的不能向下转型成为String对象
String y = (String)pa.getY();
System.out.println("坐标为:("+x+","+y+")");
}
}
/*执行结果:
Exception in thread "main" java.lang.ClassCastException: class java.lang.Double cannot be cast to class java.lang.String (java.lang.Double and java.lang.String are in module java.base of loader ‘bootstrap‘)
at Test.main(Test.java:23)
*/
JDK1.5引入了泛型技术,此技术的核心在于:类属性或方法的参数在定义数据类型时,可以直接使用一个标记进行占位,在具体使用时才设置其实际数据类型,这样当设置的数据类型出错时,就可以在程序编译时检测出来。
//使用泛型的方式重写Point类
class Point<T>{ //T是type的简写,是一个类型标记
private T x; //此属性的类型不知道,在使用时动态决定
private T y;
public void setX(T x){
this.x = x;
}
public void setY(T y){
this.y = y;
}
public T getX(){
return x;
}
public T getY(){
return y;
}
}
public class NewTest {
public static void main(String[] args) {
Point<String> p = new Point<String>();
p.setX("东经123度");
p.setY("北纬32度");
String x = p.getX();
String y = p.getY();
System.out.println("坐标为:("+x+","+y+")");
}
}
//执行结果:坐标为:(东经123度,北纬32度)
使用了泛型后,所有类中属性都是动态设置的,避免了向下转型的安全问题,这样的操作才属于安全的操作。不过泛型可以采用的类型必须是引用类型,不可以使用基本数据类型。注意,在一个类上可能会定义多种泛型声明。下面的代码除了定义参数类型外,还定义了返回值类型。
泛型定义格式
- <类型>:指定一种类型的格式。这里的类型可以看成是形参
- <类型1,类型2…>:指定多种类型的格式,多种类型之间用逗号隔开。这里的类型可以看成是形参
- 将来具体调用时候给定的类型可以看成是实参,并且实参的类型只能是引用数据类型
- 注意:泛型标记加在类名和接口名后,但是加在方法名前的修饰符位置。
泛型的好处
- 把运行时期的问题提前到了编译期间
- 避免了强制类型转换
泛型类、泛型接口
泛型类的定义
//泛型类定义格式: 修饰符 class 类名<类型> {}
class Point<E,T>{
public R fun(E e){
return null;
}
}
测试:
package GenericTest;
class Student<T> {
private T t;
public Student(T t){
this.t = t;
}
public void show(){
System.out.println(t);
}
}
public class GenericClassTest{
public static void main(String[] args) {
Student<String> s1 = new Student<String>("风清扬");//jdk1.7后不用写后面的String了
Student<Double> s2 = new Student<Double>(3.14);
Student<Integer> s3 = new Student<Integer>(20);
student<> s4 = new Student<("hello");//如果实例化时不指定类型,默认是Object类型
s1.show();
s2.show();
s3.show();
s4.show();
}
}
泛型类的继承
-
父类指定泛型的类型,子类就不是泛型类了
public class<E> fu{//父类是泛型类 public void method(E e){} } public class Zi extends Fu<Integer>{//父类指定了Integer类型,子类就被限定成了Integer类型,不用再写泛型标记了 }
-
父类不指定泛型类型,子类必须是泛型类,类型在使用时确定
public class Zi<E> extends Fu<E>{ }
泛型类注意事项
-
泛型类可以定义多个类型参数
public class TestGenerics<A,B,C>{ A name; B age; public C method(A a){} }
-
泛型类的构造器不能加泛型标记
public TestGenerics<A,B,C>{}//错误,构造器不能加泛型 public TestGenerics(){}//正确
-
泛型类中的静态方法不能使用泛型
public class TestGenerics<E e>{ public static method<E e>{}//错误,因为静态方法在实例化之前就存在,而泛型的类型是在创建对象时才确定 public static method2(){}//正确 }
-
不能直接创建泛型数组
public class TestGenerics<E e>{ E[] array = new E[];//错误 E[] array = (E[])new Object[];//正确,可以先创建Object数组,再向下转型 }
-
泛型没有继承的概念(不能进行多态操作)
Object obj = new Object(); String str = new String(); obj = str;//多态操作,可行 List<Object> objList = new ArrayList<>(); List<String> strList = new ArrayList<>(); objList = strList;//错误,指定了泛型就没有继承关系了,不能再转型了
泛型接口
如果把接口定义为泛型,那么他的实现类也必须是泛型类。
定义格式
修饰符 interface 接口名<类型> { }
示例代码
public interface Generic<T> {
void show(T t);
}
泛型接口实现类
public class GenericImpl<T> implements Generic<T> {//泛型的接口名和类名后面都要加上<T>
@Override
public void show(T t) {
System.out.println(t);
}
}
测试类
public class GenericDemo {
public static void main(String[] args) {
Generic<String> g1 = new GenericImpl<String>();
g1.show("林青霞");
Generic<Integer> g2 = new GenericImpl<Integer>();
g2.show(30);
}
}
泛型方法
定义格式
修饰符 <类型> 返回值类型 方法名(类型 变量名) { }
示例:带有泛型方法的类。泛型方法不一定是在泛型类中。
package GenericTest;
class Teacher{
public <T> void show(T t){
System.out.println(t);
}
}
public class GenericMethodTest {
public static void main(String[] args) {
show("风清扬");
show(123);
show(3.14);
}
}
泛型参数:类型通配符
如果声明了泛型,但在使用时没有明确声明类型,那么JVM默认采用Object类型。虽然泛型实现了不同类型参数的传递问题,但是当实例化对象之后,泛型的类型就确定了,这时就不能直接进行引用操作了。
//下面这样写法不对,不会被java识别为重载,而是重复定义
public void a(List<Object> list){}
public void a(List<String> list){}
public void a(List<Integer> list){}
上面代码中的a()方法只能接收一种类型的参数,而且这里不能用方法重载来改写。因为Java中的方法重载只要求参数类型不同,对于泛型类型没有任何要求,所以无法通过方法重载来解决此类问题。
那么能否通过将a()的参数类型声明为List