JAVA提高一:静态导入、可变参数、增强型for循环、装拆箱
国庆假期已结束,假期8天,全部在家带娃,体会到了妻子的不容易,需要好好努力来多赚钱了,言归正传。10月份开始进去JAVA 高级语法知识学习,本节复习学习的为:静态导入、可变参数、增强型for循环、装拆箱。【转摘,请注明来源:http://www.cnblogs.com/pony1223/p/7643842.html 】
一、静态导入
通常如果我们要使用静态的成员(方法和变量)必须给出提供这个静态成员的类。但是如果我们使用静态导入,使用这些静态成员的时候无需再给出他们的类名。
静态导入时JDK5.0引入的新特效,下面通过实例来说明静态导入的用法:
1.先定义一个公共类:
package study.javaenhance.util; public class Common { public static final int AGE = 10; public static void output() { System.out.println("Hello World!"); } }
2.在另一个包中使用时,如果不用静态导入,是这样用的:
package study.javaenhance; import study.javaenhance.util.Common; public class StaticImport { public static void main(String[] args) { int a = Common.AGE; System.out.println(a); Common.output(); } }
前面加入了导入语句,将Common类导入,使用其中的静态成员变量和静态方法时需要加上类名。
下面我们采用静态导入方式:
(1)语法:
import static 包名.类名.静态成员变量;
import static 包名.类名.静态成员函数;
注意导入的是成员变量和方法名。
(2)改写上面的例子
package study.javaenhance; import static study.javaenhance.util.Common.AGE; import static study.javaenhance.util.Common.output; public class StaticImport { public static void main(String[] args) { int a = AGE; System.out.println(a); output(); } }
或者:
package study.javaenhance; import static study.javaenhance.util.Common.*; public class StaticImport { public static void main(String[] args) { int a = AGE; System.out.println(a); output(); } }
注意点:
1.过度地使用静态导入会在一定程度上降低代码的可读性。
2.如果静态导入的多个类中存在重复的方法名称或者成员变量名称的时候,需要加上类名用于区分.
二、可变参数与重载覆写
在JDK5.0 之前,我们知道方法在接受参数的时候,其参数个数是固定的,但会存在某种场景,就该方法接受的某种类型的参数个数是不固定的,可能有1个参数,有2个参数,或者多个参数,但其内在功能是一样的,那么这个时候我们通常采用的重载的方式来来实现,代码举例如下:
package study.javaenhance; public class VarableParameter { public static void main(String[] args) { System.out.println(add(1,2)); System.out.println(add(1,2,5)); } public static int add(int x,int y) { return x+y; } public static int add(int x,int y,int z) { return x + y +z; } }
可以看到add 方法为求参数的和,但是传递过来的参数有2个和3个,后面也有可能传递多个过来,那就需要不断的重载,那么有没有办法优化呢?那就是可变参数的用途:减少代码量,方便输入
采用可变参数方式解决:
package study.javaenhance; public class VarableParameter { public static void main(String[] args) { System.out.println(add(1,2)); System.out.println(add(1,2,5)); } public static int add(int ... args) { int sum = 0; for (int i : args) { sum += i; } return sum; } /* public static int add(int x,int y,int z) { return x + y +z; }*/ }
可变参数(Varargs)使程序员可以声明一个接受可变数目参数的方法。在调用可变参数的方法时,编译器会为该可变参数隐含创建一个数组,在方法体中以数组的形式访问可变参数。本质上就是一个数组,对于某个声明了可变参数的方法来说,我们既可以传递离散的值,也可以传递数组对象。但如果将方法中的参数定义为数组,那么只能传递数组对象而不能传递离散的值。
注意点:
1.如果有多个参数,可变参数要定义在最后边 ... 位于变量类型和变量名之间,前后有无空格都可以;
2.一个方法不可能具有两个或两个以上的可变参数。
上面提到了重载,因此这里需要说明下重载和覆写的区别:
重载(Overloading)
(1) 方法重载是让类以统一的方式处理不同类型数据的一种手段。多个同名函数同时存在,具有不同的参数个数/类型。
重载Overloading是一个类中多态性的一种表现。
(2) Java的方法重载,就是在类中可以创建多个方法,它们具有相同的名字,但具有不同的参数和不同的定义。
调用方法时通过传递给它们的不同参数个数和参数类型来决定具体使用哪个方法, 这就是多态性。
(3) 重载的时候,方法名要一样,但是参数类型和个数不一样,返回值类型可以相同也可以不相同。无法以返回型别作为重载函数的区分标准。
重写(Overriding)
(1) 父类与子类之间的多态性,对父类的函数进行重新定义。如果在子类中定义某方法与其父类有相同的名称和参数,我们说该方法被重写 (Overriding)。在Java中,子类可继承父类中的方法,而不需要重新编写相同的方法。
但有时子类并不想原封不动地继承父类的方法,而是想作一定的修改,这就需要采用方法的重写。
方法重写又称方法覆盖。
(2)若子类中的方法与父类中的某一方法具有相同的方法名、返回类型和参数表,则新方法将覆盖原有的方法。
如需父类中原有的方法,可使用super关键字,该关键字引用了当前类的父类。
(3)子类函数的访问修饰权限不能少于父类的;
重写方法的规则:
1、参数列表必须完全与被重写的方法相同,否则不能称其为重写而是重载。
2、返回的类型必须一直与被重写的方法的返回类型相同,否则不能称其为重写而是重载。
3、访问修饰符的限制一定要大于被重写方法的访问修饰符(public>protected>default>private)
4、重写方法一定不能抛出新的检查异常或者比被重写方法申明更加宽泛的检查型异常。例如:
父类的一个方法申明了一个检查异常IOException,在重写这个方法是就不能抛出Exception,只能抛出IOException的子类异常,可以抛出非检查异常。
而重载的规则:
1、必须具有不同的参数列表;
2、可以有不责骂的返回类型,只要参数列表不同就可以了;
3、可以有不同的访问修饰符;
4、可以抛出不同的异常;
三、增强型for循环
For-Each循环也叫增强型的for循环,或者叫foreach循环。
其语法如下:
for(type element: array)
{
System.out.println(element);
}
直接看样例:
package study.javaenhance; import java.util.ArrayList; import java.util.Iterator; import java.util.List; public class ForeachTest { public static void main(String[] args) { int[] arr = {1, 2, 3, 4, 5}; System.out.println("----------旧方式遍历------------"); for(int i=0;i<arr.length;i++) { System.out.println(arr[i]); } System.out.println("---------新方式遍历-------------"); //新式写法,增强的for循环 for(int element:arr) { System.out.println(element); } System.out.println("---------遍历二维数组-------------"); //遍历二维数组 int[][] arr2 = {{1, 2, 3}, {4, 5, 6}, {7, 8, 9}} ; for(int[] row : arr2) { for(int element : row) { System.out.println(element); } } //以三种方式遍历集合List List<String> list = new ArrayList<String>(); list.add("a"); list.add("b"); list.add("c"); System.out.println("----------方式1-----------"); //第一种方式,普通for循环 for(int i=0;i<list.size();i++) { System.out.println(list.get(i)); } System.out.println("----------方式2-----------"); //第二种方式,使用迭代器 for(Iterator<String> iter = list.iterator();iter.hasNext();) { System.out.println(iter.next()); } System.out.println("----------方式3-----------"); //第三种方式,使用增强型的for循环 for(String str: list) { System.out.println(str); } } }
四、基本数据的自动装箱和拆箱
在这之前,我们首先了解回顾下,JAVA种类型的划分为基本数据类和引用类型,其中基本数据类型为:byte short int long float double char boolean;引用类型为:数组 接口 类
那么我们本小节讲的即为基本数据类中的装箱和拆箱。
自动装箱:基本类型自动转为包装类(int >> Integer)
自动拆箱:包装类自动转为基本类型(Integer >> int)
包装类是针对于原生数据类型的包装。因为有8个原生数据类型,所以对应有8个包装类。
所有的包装类(8个)都位于java.lang下。
Java中的8个包装类分别是:Byte, Short, Integer, Long, Float, Double, Character, Boolean,它们的使用方式都是一样的,可以实现原生数据类型与包装类型的双向转换。
下面以主要Integer类为例说明。
Integer类将int类型的值包装到一个对象中。
Integer通过下面这个构造方法构造相应的整型数的对象:
public Integer(int value);
public static Integer valueOf(int i);
public int intValue()方法则返回这个包装类所包装的整型值。
举例如下:
package study.javaenhance; import java.util.ArrayList; import java.util.Collection; public class AutoBox { public static void main(String[] args) { Integer iObj = 3; //自动将iObj 拆箱为了int 类型,然后参与运算. System.out.println(iObj + 12); Collection<Integer> c = new ArrayList<Integer>(); c.add(3);//将int类型的3转换为Integer类型并放到集合当中 for (Integer item : c) { System.out.println(item); } } }
下面说一个知识点,看下面的程序:
package study.javaenhance; import java.util.ArrayList; import java.util.Collection; public class AutoBox { public static void main(String[] args) { Integer i1 = 100; Integer i2 = 100; System.out.println(i1 == i2); Integer i3 = Integer.valueOf(100); Integer i4 = Integer.valueOf(100); System.out.println(i3==i4); //2 Integer i5 = 130; Integer i6 = 130; System.out.println(i5 == i6); Integer i7 = Integer.valueOf(130); Integer i8 = Integer.valueOf(130); System.out.println(i7==i8); } }
上面的输出结果为:
true
true
false
false
为什么呢?
当两个数都是100的时候==判断相等,当两个数都是130的时候判断不相等。
查看内部实现代码可知,Integer类有一个缓存,它会缓存-128~127之间的整数。
当调用valueOf的时候,不会生成新的对象,而是从缓存中取出对象。这样可以提高性能。
使用构造方法的时候肯定会生成新的对象。
关于自动装箱和拆箱知识扩充:
自动装箱拆箱要点:
1.自动装箱时编译器调用valueOf将原始类型值转换成对象,同时自动拆箱时,编译器通过调用类似intValue(),doubleValue()这类的方法将对象转换成原始类型值。
2.自动装箱是将boolean值转换成Boolean对象,byte值转换成Byte对象,char转换成Character对象,float值转换成Float对象,int转换成Integer,long转换成Long,short转换成Short,自动拆箱则是相反的操作。
何时发生自动装箱和拆箱
自动装箱的弊端:
自动装箱有一个问题,那就是在一个循环中进行自动装箱操作的情况,如下面的例子就会创建多余的对象,影响程序的性能。
Integer sum = 0; for(int i=1000; i<5000; i++){ sum+=i; }
上面的代码sum+=i可以看成sum = sum + i,但是+这个操作符不适用于Integer对象,首先sum进行自动拆箱操作,进行数值相加操作,最后发生自动装箱操作转换成Integer对象。其内部变化如下
1.sum = sum.intValue() + i;
2.Integer sum = new Integer(result);
由于我们这里声明的sum为Integer类型,在上面的循环中会创建将近4000个无用的Integer对象,在这样庞大的循环中,会降低程序的性能并且加重了垃圾回收的工作量。因此在我们编程时,需要注意到这一点,正确地声明变量类型,避免因为自动装箱引起的性能问题。
重载与自动装箱:
当重载遇上自动装箱时,情况会比较有些复杂,可能会让人产生有些困惑。在1.5之前,value(int)和value(Integer)是完全不相同的方法,开发者不会因为传入是int还是Integer调用哪个方法困惑,但是由于自动装箱和拆箱的引入,处理重载方法时稍微有点复杂。一个典型的例子就是ArrayList的remove方法,它有remove(index)和remove(Object)两种重载,我们可能会有一点小小的困惑,其实这种困惑是可以验证并解开的,通过下面的例子我们可以看到,当出现这种情况时,不会发生自动装箱操作。
public void test(int num){ System.out.println("method with primitive argument"); } public void test(Integer num){ System.out.println("method with wrapper argument"); } //calling overloaded method AutoboxingTest autoTest = new AutoboxingTest(); int value = 3; autoTest.test(value); //no autoboxing Integer iValue = value; autoTest.test(iValue); //no autoboxing Output: method with primitive argument method with wrapper argument
要注意的事项:自动装箱和拆箱可以使代码变得简洁,但是其也存在一些问题和极端情况下的问题,以下几点需要我们加强注意。
1.对象相等比较
这是一个比较容易出错的地方,”==“可以用于原始值进行比较,也可以用于对象进行比较,当用于对象与对象之间比较时,比较的不是对象代表的值,而是检查两个对象是否是同一对象,这个比较过程中没有自动装箱发生。进行对象值比较不应该使用”==“,而应该使用对象对应的equals方法。看一个能说明问题的例子。
public class AutoboxingTest { public static void main(String args[]) { // Example 1: == comparison pure primitive – no autoboxing int i1 = 1; int i2 = 1; System.out.println("i1==i2 : " + (i1 == i2)); // true // Example 2: equality operator mixing object and primitive Integer num1 = 1; // autoboxing int num2 = 1; System.out.println("num1 == num2 : " + (num1 == num2)); // true // Example 3: special case - arises due to autoboxing in Java Integer obj1 = 1; // autoboxing will call Integer.valueOf() Integer obj2 = 1; // same call to Integer.valueOf() will return same // cached Object System.out.println("obj1 == obj2 : " + (obj1 == obj2)); // true // Example 4: equality operator - pure object comparison Integer one = new Integer(1); // no autoboxing Integer anotherOne = new Integer(1); System.out.println("one == anotherOne : " + (one == anotherOne)); // false } }
Output:
i1==i2 : true
num1 == num2 : true
obj1 == obj2 : true
one == anotherOne : false
值得注意的是第三个小例子,这是一种极端情况。obj1和obj2的初始化都发生了自动装箱操作。但是处于节省内存的考虑,JVM会缓存-128到127的Integer对象。因为obj1和obj2实际上是同一个对象。所以使用”==“比较返回true。
2.容易混乱的对象和原始数据值
另一个需要避免的问题就是混乱使用对象和原始数据值,一个具体的例子就是当我们在一个原始数据值与一个对象进行比较时,如果这个对象没有进行初始化或者为Null,在自动拆箱过程中obj.xxxValue,会抛出NullPointerException,如下面的代码
private static Integer count; //NullPointerException on unboxing if( count <= 0){ System.out.println("Count is not started yet"); }
这一点需要特别注意,我之前就犯过这个错误。
3.缓存的对象
这个问题就是我们上面提到的极端情况,在Java中,会对-128到127的Integer对象进行缓存,当创建新的Integer对象时,如果符合这个这个范围,并且已有存在的相同值的对象,则返回这个对象,否则创建新的Integer对象。在Java中另一个节省内存的例子就是字符串常量池。