文章目录
Generics 泛型
A:引入
集合类中放入的是 Object 对象,而 Object 是Java类层次结构的超类,因为Java面向对象的多态特性,所以Object 类及其所有直接或间接子类(含自定义类)均可放入集合中,但是当从集合中取出时,为了进行使用需要进行强制类型转换。而实际使用中,我们却会因为这样那样的问题不知道集合中到底装入了什么,或者即使知道,但在使用时,颇为麻烦,并且易导致各种运行时异常,诸如 ClassCastException 等。因此,能不能通过一种方式,让集合和数组、字符串一样,装入的内容都是同一个数据类型的,并且能将这些运行时异常提前到编译时暴露出来,就像 String str = 10;
编译时抛出的的异常一样呢?答案是肯定的,JDK 5.0 之后,引入了新特性泛型--Generics
,就是为了解决这样一类问题。
B:定义
泛型是 Java SE 1.5 的新特性,泛型的本质是参数化类型
,也就是说将数据类型作为一个参数,动态指定。这种参数化类型可以用作类、接口、方法的创建中,分别称为 泛型类、泛型接口、泛型方法(实际上,就是将泛型定义在类、接口、方法上)。Java语言 引入泛型的原因是:安全简单。(简单实现,保证安全)
在 Java SE 1.5 之前,没有泛型的情况下,通过对类型 Object 的引用来实现参数的 “任意化”
,“任意化”带来的缺点是要做显示的强制类型转换,而这种转换是要求开发者对实际参数可以预知的情况下进行的。对于强制类型转换错误的情况,编译器可能不提示错误,在运行的时候才出现异常,这是一个安全隐患。
泛型的好处:在编译时检查类型安全,并且所有强制类型转换都是自动和隐式的(方法内部自动进行转换),以提高代码的重用率。
C:格式
T - Type:表示类型持有者,T的真实类型在创建对象和调用方法时才能明确
E - Element:引用数据类型,不可使用基本数据类型。(基本数据类型可以使用类类型的包装类)
注意:T、E没有本质的区别,是可以通用的。也可以自定义任何满足命名规则的字符序列。
- 泛型类:
public class 类名<泛型类型1, 泛型类型2...>
public class ArrayList<E>
- 泛型接口:
public interface 接口名<泛型类型1, 泛型类型2...>
public interface List<E> extends Collection<E>
//------------------------------------
public interface Map<K,V>
- 泛型方法:
public <泛型类型> 返回值类型 方法名(泛型类型1, 泛型类型2...)
public <T> T[] toArray(T[] a)
注意:如果在实际使用中,没有定义
<T>
的真实类型,T 默认是 Object,未没有使用泛型。
另:
T 可以作为 真实的数据类型一样使用,定义变量、方法返回值等等。
D:泛型深入
先来看一下 List 中的一个方法声明:
public class List<E>
{
boolean addAll(Collection<? extends E> c);
}
定义List类时,E(类型持有者)的范围没有限制,没有给出的时候,默认是 Object , 亦即是 Object泛型 ,相当于没有使用泛型。
而其中的方法声明中的参数:Collection <? extends E> 则表示 E 类及其子类对象的集合。
-
? : 泛型通配符,默认是 Object 及其子类(亦即是 Java中的所有类,包括自定义类)
-
extends : 代表的是原有的 extends 以及 implements ,也就是说其后可是 接口或类,但仍遵序 “单继承,多实现”
<E extends SuperClass & interface1 & interface2 & interface3 & ...>
仅可有一个类,且必须在extends后的第一位置,可多个接口,之间使用 & 连接。
-
E : 表示具体的类,默认是 Object , 亦即是 Object泛型 ,相当于没有使用泛型。
-
<? extends E> : E 及其 子类(实现类)
-
<? super E> : E 及其 父类
注意:
通配符泛型,泛型明确写,前后必一致
为了解决泛型类型不能根据实例动态确定的缺点(也就是 多态,面向对象设计的原则:针对接口编程,不针对实现编程;针对抽象编程,不针对具体编程。起其实就是为了 推迟实现 ),引入了 通配符泛型。
泛型方法 示例:
package com.rupeng.generics;
public class Demo
{
public <T> void show(T t)
{
System.out.println(t);
}
public static void main(String[] args)
{
Demo d = new Demo();
d.show(10);
d.show(true);
d.show(10.0);
d.show("10");
}
}
说明: 使用泛型方法,不必给出参数类型,编译器编译时自动给出具体类型。
注意: static方法无法访泛型类的类型参数,因为,static方法是随着类的加载而加载,早于 泛型类的 类型参数存在。如果需要使用 static 方法,须将其定义为 泛型方法。
public static void method(T t)
{
return ;
}
// 编译时异常,不能虽非静态内容进行静态引用
// T 是需要在运行时,动态给出的
// 使用 泛型方法 修改如下:
public static <T> void method(T t)
{
return;
}
泛型接口 存在两种情况:
1、实现类已知 T 的真实类型
public class InterImpl implements Inter<String>
2、实现类未知 T 的真实类型
public class InterImpl<T> implements Inter<T>
Java SE 1.5 特性
1、自动装箱和拆箱(auto-boxing & auto-unboxing)
基本数据类型 → 包装类 → 类类型
auto-boxing 自动装箱:基本数据类型 包转为 类类型 (通过对应类类型的静态成员方法valueOf
,譬如:Integer.valueOf(12);
)
auto-unboxing 自动拆箱:类类型 拆封为 基本数据类型(通过对应类类型的成员方法xxxValue
(xxx 代表的是基本数据类型),譬如:integer.intValue();
)
基本数据类型 和 类类型 的对应关系:
2、泛型(Generics)
泛型:为了解决 Object 类的任意化导致的安全隐患(使用时必须进行显示的强制类型转换,转换时导致的异常为 运行时异常),引入的泛型,本质是 参数化类型,将类型作为参数,在运行时动态确定。可以将 运行时的异常 提前到 编译时暴露出来,是解决安全隐患的最简单实现,类型之间的强制类型转换是 在方法内 隐式和自动完成的,提高了代码的重用率。
限制泛型:为了解决简单泛型中存在的 默认Object类型导致的Object泛型的任意化,引入了限制泛型,将泛型类型约定在一个范围之内,(多态)提高了代码的扩展性。
通配符泛型:为了解决泛型类型不能根据实例动态确定的缺点(更彻底地多态)
3、增强 for (foreach)
引入
增强for,通畅也称为 for/in 循环、或 foreach,它是Java 1.5中一个极为方便的特性。实际上,它并没有提供任何的新功能,但它显然能让一些日常的编码任务变得简单。
越短越好
这是资深程序员都知道的一条基本的原理:因为更短意味着打字更少。这个哲学造就了 vi 这样的 IDE。这个哲学还导致了一些神秘的代码,譬如:变量 ar 代表 Aglie Runner(也可能是 Argyle,或是Atomic Reactor等等,总之,你能明白就好)
有时候,在努力实现短小的时候,程序员将明确性跑到脑后。也就是说,过于短小和过于冗余的代码都会让人痛苦不堪。变量名 theAtomicReactorLocatedInPjliadephia 和 名为 ar的变量一样 让人讨厌和不方便。一定会有一个让人高兴的解决方法,不是吗?
这个让人高兴的解决方法(至少我是这么认为的)是以寻求完成某事的 方便 途径为出发点,不是为了短小而短小。作为这个问题的好例子,Java 1.5 引入了新的 for 循环,称为 for/ in,也叫做 增强 for 或 foreach,但这些指的都是同一个构造。
不使用 Iterator
使用 for/in 和普通的 for 之间最基本的区别就是,不必使用计数器(通常称为 i 或 count)或 Iterator。参见详单 1,它显示了一个使用 Iterator的 for循环,以及转换为 for/in 之后的清单2
清单 1. for 循环,旧式学院风格
public void testForLoop(PrintStream out) throws IOException
{
List list = getList(); // initialize this list elsewhere
for (Iterator i = list.iterator(); i.hasNext();)
{
Object listElement = i.next();
out.println(listElement.toString());
// Do something else with this list element
}
}
清单 2. 转换成 for/in
public void testForInLoop(PrintStream out) throws IOException
{
List list = getList(); // initialize this list elsewhere
for (Object listElement : list)
{
out.println(listElement.toString());
// Do something else with this list element
}
}
清单3. for/in 循环的基本结构
for(声明 : 表达式)
{
语句;
}
声明一个变量,变量的数据类型,要与 需要遍历的列表、集合、数组中的每一项兼容。
表达式就是一个表达式,计算的结果应是可遍历的。目前只要保证表达式结果是一个数组或集合 即可。表达式 可以简单到是一个 变量 或 是一个方法调用,亦或是包含布尔逻辑或三目运算符的复杂表达式。只要返回一个数组或集合,就 OK 了。
语句代表循环的内容,它对声明中定义的变量进行操作;当然,这是一个循环,所以, 语句 将应用到数组、集合中的每一个项目上。
其用法如下:创建一个变量,指向要遍历的数组或集合,然后对定义的变量进行操作。不用对数组、集合中的每个项目进行赋值,因为 for/in 替你处理了这件事。
清单 4 显示了在提供通用化类型时,实际发挥作用的 for/in 循环。
清单 4. 转换后的 for/in 循环,带有一个 Iterable
for(Iterator <E> #i = (expression).iterator(); #i.hasNext();)
{
declaration = #i.next();
statement
}
清单 5. 转换后的 for/in 循环,没有未经参数化的类型
for (Iterator #i = (expression).iterator(); #i.hasNext(); )
{
declaration = #i.next();
statement
}
使用数组
处理数组与集合也一样容易,数组也被赋值,然后这些值被逐个取出,并被处理。
清单 6. 用 for/in 对数组进行循环就是小菜一碟,甚至还可以在 for/in 循环中再加上一层循环(数组的每一项都是集合)
public void testObjectArrayLooping(PrintStream out) throws IOException
{
List[] list_array = new List[3];
list_array[0] = getList();
list_array[1] = getList();
list_array[2] = getList();
for (List l : list_array)
{
for (Object o : l)
{
out.println(o);
}
}
}
处理集合
清单 7. 以下程序中有许多简单循环,演示了如何使用 for/in
package com.rupeng.newfeatures;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
public class Demo
{
public static void main(String[] args)
{
List wordlist = new ArrayList();
Set wordset = new HashSet();
System.out.println("Assigning arguments to lists...");
for (String word : args)
{
System.out.print(word + " ");
wordlist.add(word);
wordset.add(word);
}
System.out.println();
System.out.println("Printing words from wordlist "
+ "(ordered, with duplicates)...");
for (Object word : wordlist)
{
System.out.print((String) word + " ");
}
System.out.println();
System.out.println("Printing words from wordset "
+ "(unordered, no duplicates)...");
for (Object word : wordset)
{
System.out.print((String) word + " ");
}
}
}
类型转换之痛
利用 JDK 1.5 的另一项特性 — 泛型(有时也叫作 参数化类型)
记得 for/in 语句的 声明 部分创建了一个变量,它代表要遍历的集合中每个项目的类型。在数组中,类型非常明确,因为类型是强类型的, int[] 只能包含整数,所以在循环中没有类型转换。在您通过泛型使用类型化列表时,也有可能做到这点。下面 演示了几个简单的参数化集合:
List<String> list = new ArrayList<String>();
Set<String> set = new hashSet<String>();
清单 8. 使用泛型重写 清单 7
package com.rupeng.newfeatures;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
public class ForInDemo
{
public static void main(String[] args)
{
List<String> wordlist = new ArrayList<String>();
Set<String> wordset = new HashSet<String>();
System.out.println("Assigning arguments to lists...");
for (String word : args)
{
System.out.print(word + " ");
wordlist.add(word);
wordset.add(word);
}
System.out.println();
System.out.println("Printing words from wordlist "
+ "(ordered, with duplicates)...");
for (String word : wordlist)
{
System.out.print((String) word + " ");
}
System.out.println();
System.out.println("Printing words from wordset "
+ "(unordered, no duplicates)...");
for (String word : wordset)
{
System.out.print((String) word + " ");
}
}
}
类与 for/in 的集成
迄今为止,我只是针对 Java 事先打包的类和类型(array、list、map、set 和其他集合)进行遍历。尽管这已经相当不错,但编程语言的美丽在于它们能帮助您定义自己的类。定制对象是大型应用程序的支柱。
清单 9. Iterator 长时间以来一直是 Java 语言的中流砥柱
package java.util;
public interface Iterator<E>
{
public boolean hasNext();
public E next();
public void remove();
}
清单 10. Iterable 接口是 for/in 构造的基础
package java.lang;
public interface Iterable<E>
{
public java.util.Iterator<E> iterator();
}
是 java.lang,而不是 java.util
请注意, Iterable 位于 java.lang 之中,而 不是位于java.util 中。至于为什么会这样,我没有找到任何明确的文档,但就我个人猜测,可能是为了避免必须导入接口( java.lang 位于为所有 Java 代码自动导入的名称空间集中)。
为了让对象或类能与 for/in 一起工作,对象和类需要实现 Iterable 接口。这留给您两个基本场景:
- 扩展现有的、已经实现了 Iterable(因此也就已经支持 for/in)的集合类。
- 手动处理遍历,定义自己的 Iterable 实现。
扩展已有集合
如果有可能,我极力建议您用定制对象扩展现有的集合。事情会变得极为简单,而您可以避免所有繁琐的细节。
清单 11. 扩展现有的集合是利用 for/in 的捷径
package com.rupeng.newfeatures;
import java.util.LinkedList;
public class GuitarManufacturerList extends LinkedList<String>
{
public GuitarManufacturerList()
{
super();
}
public boolean add(String manufacturer)
{
if (manufacturer.indexOf("Guitars") == -1)
{
return false;
} else
{
super.add(manufacturer);
return true;
}
}
}
因为 LinkedList 已经可以使用 for/in,所以,不需要特殊的代码,就可以在 for/in 中使用这个新类。清单 12 演示了这点,以及做到这一点需要做的工作是多么地少:
清单 12. Iterable 接口是 for/in 构造的基础
package com.rupeng.newfeatures;
import java.io.IOException;
import java.io.PrintStream;
public class CustomObjectTester
{
/** A custom object that extends List */
private GuitarManufacturerList manufacturers;
public CustomObjectTester()
{
this.manufacturers = new GuitarManufacturerList();
}
public void testListExtension(PrintStream out) throws IOException
{
// Add some items for good measure
manufacturers.add("Epiphone Guitars");
manufacturers.add("Gibson Guitars");
// Iterate with for/in
for (String manufacturer : manufacturers)
{
out.println(manufacturer);
}
}
public static void main(String[] args)
{
try
{
CustomObjectTester tester = new CustomObjectTester();
tester.testListExtension(System.out);
} catch (Exception e)
{
e.printStackTrace();
}
}
}
手动处理遍历
在某些不常见的情况下 —— 老实说,我费了很大劲想到了很多 —— 在您的定制对象可以遍历的时候,您可能需要执行特定的行为。在这些(相当不幸)的情况下,您必须自己处理这些事情。清单 13 演示了如何做,虽然需要做很多工作,但是并不复杂,所以我把代码留给您自己来看。以下这个类提供了文本文件的包装器,在遍历它的时候,它将列出文件中的每行内容。
清单 13. 耐心点,您自己也能实现 Iterable 接口,并在循环中提供定制行为
package com.rupeng.newfeatures;
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
import java.util.Iterator;
/**
* This class allows line-by-line iteration through a text file. The iterator's
* remove() method throws UnsupportedOperatorException. The iterator wraps and
* rethrows IOExceptions as IllegalArgumentExceptions.
*/
public class TextFile implements Iterable<String>
{
// Used by the TextFileIterator below
final String filename;
public TextFile(String filename)
{
this.filename = filename;
}
// This is the one method of the Iterable interface
public Iterator<String> iterator()
{
return new TextFileIterator();
}
// This non-static member class is the iterator implementation
class TextFileIterator implements Iterator<String>
{
// The stream being read from
BufferedReader in;
// Return value of next call to next()
String nextline;
public TextFileIterator()
{
// Open the file and read and remember the first line
// Peek ahead like this for the benefit of hasNext()
try
{
in = new BufferedReader(new FileReader(filename));
nextline = in.readLine();
} catch (IOException e)
{
throw new IllegalArgumentException(e);
}
}
// If the next line is non-null, then we have a next line
public boolean hasNext()
{
return nextline != null;
}
// Return the next line, but first read the line that follows it
public String next()
{
try
{
String result = nextline;
// If we haven't reached EOF yet...
if (nextline != null)
{
nextline = in.readLine(); // Read another line
if (nextline == null)
in.close(); // And close on EOF
}
// Return the line we read last time through
return result;
} catch (IOException e)
{
throw new IllegalArgumentException(e);
}
}
// The file is read-only; we don't allow lines to be removed
public void remove()
{
throw new UnsupportedOperationException();
}
}
public static void main(String[] args)
{
String filename = "src\\com\\rupeng\\newfeatures\\TextFile.java";
if (args.length > 0)
filename = args[0];
for (String line : new TextFile(filename))
System.out.println(line);
}
}
其中大部分工作是实现 Iterator,然后通过 iterator() 方法返回它。其他的事情就非常简单了。但是,您可以看到,与扩展一个现成的类来完成同样的工作相比,手动实现 Iterable 接口需要做的工作多得多。
不能做什么
我确实认为 for/in 是这些好东西中的一个,但是与所有的好东西一样,它们也有自身的局限性。原因是 for/in 设置的方式,特别是因为它没有显式地使用 Iterator,所以使用这个新构造时,有些事情是您不能做的。
定位
最明显的显然是不能确定您在列表或数组(或者定制对象)中的位置。请注意,索引变量不仅能是在列表中移动,还能指示其所在位置:
清单 14. 不可能在 for/in 循环中访问位置
public void determineListPosition(PrintStream out, String[] args) throws IOException
{
List<String> wordList = new LinkedList<String>();
// Here, it's easy to find position
for (int i=0; i<args.length; i++)
{
wordList.add("word " + (i+1) + ": '" + args[i] + "'");
}
// Here, it's not possible to locate position
for (String word : wordList)
{
out.println(word);
}
}
在这里,没有任何类型的计数器变量(或者 Iterator),也不存在任何侥幸。如果需要定位
,就得用“普通”的 for
。清单 22 显示了定位的另外一个常见用法 —— 处理字符串:
清单 15. 另一个问题 —— 字符串连接
StringBuffer longList = new StringBuffer();
for (int i=0, len=wordList.size(); i < len; i++)
{
if (i < (len-1))
{
longList.append(wordList.get(i)).append(", ");
} else {
longList.append(wordList.get(i));
}
}
out.println(longList);
删除项目
另外一个限制是项目删除,在列表遍历期间无法删除项目:
清单 16. 在 for/in 循环中无法删除项目
public void removeListItems(PrintStream out, String[] args) throws IOException
{
List<String> wordList = new LinkedList<String>();
// Assign some words
for (int i=0; i<args.length; i++)
{
wordList.add("word " + (i+1) + ": " '" + args[i] + "'");
}
// Remove all words with "1" in them. Impossible with for/in!
for (Iterator i = wordList.iterator(); i.hasNext(); )
{
String word = (String)i.next();
if (word.indexOf("1") != -1)
{
i.remove();
}
}
// You can print the words using for/in
for (String word : wordList)
{
out.println(word);
}
}
从整体来看,这些不算什么限制,只是什么时候使用 for、什么时候使用 for/in 的一个准则。可能是一些不值一提的细节。
最糟糕的结果是可能找不到 需要 for/in 的地方,这也正是我所担心的。请记住,for/in 是一项很方便的功能,它能让代码更清晰、更简洁,同时也能让代码简洁得让人头痛。
最后看一下 for/in 在遍历数组、集合的 反编译 的结果
数组 的反编译,实际就是 普通 for 循环:
package com.rupeng.newfeatures;
import java.io.PrintStream;
public class ForeachDemo
{
public ForeachDemo()
{
}
public static void main(String args[])
{
int arr[] = { 2, 0, 1, 5, 7, 5};
int ai[];
int k = (ai = arr).length;
for (int j = 0; j < k; j++)
{
int i = ai[j];
System.out.println(i);
}
}
}
集合 的反编译,实际就是 迭代器:
package com.rupeng.newfeatures;
import java.io.PrintStream;
import java.util.*;
public class ForeachDemo3
{
public ForeachDemo3()
{
}
public static void main(String args[])
{
Collection c = new ArrayList();
c.add(new Student("Yzk", 12));
c.add(new Student("Joian", 14));
c.add(new Student("Du", 18));
Student stu;
for (Iterator iterator = c.iterator(); iterator.hasNext(); System.out.println(stu))
stu = (Student)iterator.next();
}
}
案例:实现软件工程专业多个班级的多个学生的信息存储
package com.rupeng.newfeatures;
import java.util.ArrayList;
import java.util.List;
public class ForInDemo2
{
public static void main(String[] args)
{
Student stu1 = new Student("Yzk", 12);
Student stu2 = new Student("Du", 15);
Student stu3 = new Student("Ian", 18);
Student stu4 = new Student("Cathy", 10);
List<Student> se1 = new ArrayList<Student>();
se1.add(stu1);
se1.add(stu2);
se1.add(stu3);
List<Student> se2 = new ArrayList<Student>();
se2.add(stu4);
List<List<Student>> seClass = new ArrayList<>();
seClass.add(se1);
seClass.add(se2);
for (List<Student> list : seClass)
{
System.out.println("------------------------");
for (Student stu : list)
{
System.out.println(stu);
}
System.out.println("------------------------");
}
}
}
案例:获取 1~20 之间的随机整数 10个,不可重复
// 数组 作为 数据结构 实现
package com.rupeng.newfeatures;
import java.util.Arrays;
public class RandomDemo
{
public static void main(String[] args)
{
int[] arr = new int[10];
for (int i = 0; i < arr.length; i++)
{
arr[i] = (int) (Math.random() * 20) + 1;
for (int j = 0; j < i; j++)
{
if (arr[i] == arr[j])
{
arr[i] = (int) (Math.random() * 20) + 1;
i--;
}
}
}
System.out.println(Arrays.toString(arr));
}
}
// 可包含重复元素的 List集合 作为数据结构 实现
package com.rupeng.newfeatures;
import java.util.ArrayList;
import java.util.List;
public class RandomDemo2
{
public static void main(String[] args)
{
List<Integer> list = new ArrayList<Integer>();
while (list.size() < 10)
{
int ran = (int) (Math.random() * 20) + 1;
if (!list.contains(ran))
{
list.add(ran);
}
}
System.out.println(list);
}
}
// 可包含重复元素的 List集合 作为数据结构 实现
package com.rupeng.newfeatures;
import java.util.HashSet;
import java.util.Set;
public class RandomDemo3
{
public static void main(String[] args)
{
Set<Integer> set = new HashSet<Integer>();
while (set.size() < 10)
{
int ran = (int) (Math.random() * 20) + 1;
set.add(ran);
}
System.out.println(set);
}
}
说明:
集合的 toString 方法直接或间接继承自 AbstractCollection 类集合存储元素,加入泛型,使用增强 for 遍历
public abstract class AbstractCollection<E> extends Object implements Collection<E>
{
public String toString()
{
Iterator<E> it = iterator();
if (!it.hasNext())
return "[]";
StringBuilder sb = new StringBuilder();
sb.append('[');
for (;;)
{
E e = it.next();
sb.append(e == this ? "(this Collection)" : e);
if (!it.hasNext())
return sb.append(']').toString();
sb.append(',').append(' ');
}
}
}
4、静态导入(Static Imports)
这个是另外一个为了方便操作引入的 新特性。导入的方法必须是静态方法
,方便书写。格式:
import static 包名...类名.静态方法;
import java.lang.System.out;
out.println("Hello world");
对比 包的导入(导入的是类)
import 包名...类名;
请注意:如果当前包下有同名方法,即使 静态导入,也必须使用前缀,故此,基本不用。
package com.rupeng.newfeatures;
import static java.lang.Math.abs;
public class StaticImportDemo
{
/*
* private static double abs(double d) { return (d<0?-d:d); }
*/
public static void main(String[] args)
{
System.out.println(abs(-1.222));
}
}
5、可变参数(Variable Arguments - Varargs)
引入:求两个整数、三个整数、四个整数的和(方法实现)
如果是 n 个整数的和呢?
要求:一个求和方法,但不知道要求的数有几个。
解决方法:
- 个数已知,方法重写实现
- 个数未知,数组实现
// 求和工具类 SumUtil.class
package com.rupeng.newfeatures;
public class SumUtil
{
public static int sum(int a, int b)
{
return a + b;
}
public static int sum(int a, int b, int c)
{
return a + b + c;
}
public static int sum(int a, int b, int c, int d)
{
return a + b + c + d;
}
public static int sum(int[] arr)
{
int sum = 0;
for (int i : arr)
{
sum += i;
}
return sum;
}
}
// 测试类
package com.rupeng.newfeatures;
import static java.lang.System.out;
public class VarargsDemo1
{
public static void main(String[] args)
{
out.println(SumUtil.sum(2, 3));
out.println(SumUtil.sum(2, 3, 2));
out.println(SumUtil.sum(2, 3, 2, 3));
int[] arr = new int[] { 2, 3, 2, 3, 2, 3 };
out.println(SumUtil.sum(arr));
}
}
当整数的个数已知时,使用方法重写(多写几个参数列表长度不同的方法);当整数的个数不可知时,我们需要创建一个足够大的 数组,作为接收多个数的数据结构,具体这个数组多大 才是足够大,需要根据实际情况来看。
很显然,这样的实现造成了很大的麻烦,并且连最简单的**“对修改关闭”**面向对象设计原则都没有遵序。那么有没有统一的做法,只定义一个方法 就可以解决这个问题呢? JDK 1.5 提供了 一个 很方便
的新特性:可变参数 解决了这一类问题。
// 可变参数改进的 求和工具类 SumUtil.class
package com.rupeng.newfeatures;
public class SumUtil
{
public static int sum(int... a)
{
int sum = 0;
for (int i = 0; i < a.length; i++)
{
sum += a[i];
}
/*
for(int i : a)
{
sum += i;
}
*/
return sum;
}
}
仅需要提供一个可变参数的方法,就可以代替原来的很多的重写的方法,缩减了代码规模,提高了开发效率。
那么什么是可变参数,什么时候才可以使用呢?
Java 1.5 增加了新特性:可变长参数:适用于参数个数不确定,类型确定的情况,Java把可变参数当作数组处理。
格式:
// 位于方法的参数列表中的最后一项:
数据类型...变量名
说明:
1、本质
:参数是数组(在进行参数传入之后,确定了数组中元素,属于数组的静态赋值方式的定义)。
2、若方法有可变长参数,且有多个参数,则可变参数必须是最后一个参数。
6、Arrays工具类的asList 方法
JDK 1.4 中对 java.util.Arrays.asList 的定义public static List asList(Object[] a)
,参数是 Object [ ] ,所以,JDK 1.4中 不支持基本数据类型的数组作为参数。
JDK 1.5 中对 java.util.Arrays.asList 的定义public static <T> List<T> asList(T... a)
,参数是 Varags ,参数的类型 使用 泛型Generics 实现,同时由于 auto-boxing的支持,使得对所有数据类型均支持。
但是在传入基本数据类型的数组时,会将整个数组作为参数传递,最为List的第一个元素:
package com.rupeng.newfeatures;
import java.util.Arrays;
public class VarargsDemo2
{
public static void main(String[] args)
{
int[] arr = new int[] { 2, 3, 2, 3, 2, 3 };
String[] strs = new String[] { "Helo", "Rupeng", "Java" };
System.out.println(Arrays.asList(arr));
System.out.println(Arrays.asList(strs));
}
>>> [[I@659e0bfd]
改善方式:传入基本数据类型时,直接使用可变参数作为 asList 的参数 即可 Arrays.asList(1,2,3,4)
。
Arrays.asList 方法的返回值是 受指定数组支持的固定大小的列表。(对返回列表的更改会“直接写”到数组。但是实际上 不能更改,返回的列表不能进行 add 和 remove 操作)此方法同 Collection.toArray()
一起,充当了基于数组的 API 与基于 collection 的 API 之间的桥梁。此方法还提供了一个创建固定长度的列表的便捷方法,该列表被初始化为包含多个元素:List<String> stooges = Arrays.asList("Larry", "Moe", "Curly");
我们都知道,List 作为集合的典型特性就是 长度可变,我们可以很方便的对它进行插入和删除操作,并且其长度会随着操作而变化,这是它与数组最大的区别,数组的长度是固定的,而且我们不能从数组中删除元素,只能修改元素的值。利用 Arrays.asList(array)
将返回一个列表,然而这个列表并不支持 add 和 remove 操作。
很显然,这个和我们认识的不符合,是什么原因导致的呢?
public class Arrays
{
public static <T> List<T> asList(T...a)
{
return new ArrayList<>();
}
private static class ArrayList<E> extends AbstractList<E>
{}
}
public abstract class AbstractList<E>
{
public boolean add(E e)
{
add(size(), e);
return true;
}
public void add(int index, E element)
{
throw new UnsupportedOperationException();
}
public E remove(int index)
{
throw new UnsupportedOperationException();
}
}
查看源码,我们发现,这里的ArrayList并不是java.util.ArrayList,而是Arrays的内部类:Arrays$ArrayList
。我们还可以看到该内部类继承的是AbstractList,AbstractList对于 add 和 remove 方法的定义 使得 当我们对Arrays.asList返回的List进行添加或删除时将会报 java.lang.UnsupportedOperationException
异常。
7、语法糖(Syntax Sugar)
引入及其定义
语法糖不是 Java 1.5 新特性,新特性本身就是语法糖。
语法糖(Syntax Sugar),也称为 糖衣语法,英国计算机科学家 彼得.约翰.兰达 提出。指在计算机语言中,添加的某种语法,这些语法没有为语言提供任何的新功能,只是为了方便程序员的使用。语法糖了 增加程序的可阅读性,从而减少了程序代码出错的机会。
Java 语法糖
Java在现代编程语言中属于“低糖语法”(相对于 C# 及 其他 JVM语言),尤其在 JDK 1.5 之前,“低糖语法”也是Java语言被怀疑已经“落后”的表面理由。
Java语言中主要使用的语法糖有:泛型 (Generic)(泛型并不都是语法糖实现,如C#中就是直接由CLR支持的)、可变长参数(Varargs)、自动拆装箱(Wrapper Class)、枚举、增强for、条件逻辑 等,虚拟机运行时 不支持这些语法,编译阶段将被还原为基础语法结构,对于性能没有任何改善
,这个过程叫做解语法糖(Desugar)。Java源码中,解语法糖过程由 desugar()
方法触发
com.sun.tools.javac.comp.TransTypes 类 以及com.sun.tools.javac.comp.Lower 类中实现
最简单的语法糖 - 条件逻辑
if(true)
{
System.out.println("true");
}else
{
System.out.println("false");
}
语法糖(Syntax Sugar)优点:
- 提高效率
- 提升语法严谨程度
- 减少编程错误
无疑,语法糖方便了程序员的开发,提高了开发效率,提升了语法的严谨也减少了编码出错误的几率。我们不仅仅在平时的编码中依赖语法糖,更要看清语法糖背后程序代码的真实结构,这样才能更好的利用它们。
实际上,语法糖是编译器实现的一些 “小把戏”。
泛型和类型擦除
C# 和 Java 泛型 存在根本性分歧
C#:程序源码、编译之后的IL中(Intermediate Language,中间语言,泛型是一个占位符号)、运行期的CLR(Common Language Runtime,通用语言运行时)中都切实存在,List<int>
和 List<String>
是两个不同的类型,系统运行期 生成,有着不同的虚方法表和类型数据,这种实现 叫做 类型膨胀,基于这种的方法实现的泛型称为 真实泛型
Java:只存在在源码中,编译后的字节码文件中,就已经替换为 原来的原生类型(Raw Type,裸类型),并在相应的地方加入了 强制类型转换,因此,运行期的Java语言 List<Integer>
和 List<String>
就是同一种类型,所以 泛型技术对于Java而言 就是一种语法糖,Java语言中实现泛型的方法 称为 类型擦除,基于这种方法实现的泛型称为 伪泛型。
// Java 语言中的伪泛型
/** 源码 */
import java.util.ArrayList;
import java.util.List;
public class JavaGenericDemo
{
public static void main(String[] args)
{
List<Student> stuList = new ArrayList<Student>();
List<String> strList = new ArrayList<String>();
List<Integer> intList = new ArrayList<Integer>();
stuList.add(new Student());
strList.add("Java");
intList.add(12);
intList.add(9);
intList.add(20);
int sum = 0;
for (int i : intList)
{
sum += i;
}
System.out.println(sum);
}
}
/** 反编译 */
package com.rupeng.newfeatures;
import java.io.PrintStream;
import java.util.*;
public class JavaGenericDemo
{
public JavaGenericDemo()
{
}
public static void main(String args[])
{
List stuList = new ArrayList();
List strList = new ArrayList();
List intList = new ArrayList();
stuList.add(new Student());
strList.add("Java");
intList.add(Integer.valueOf(12));
intList.add(Integer.valueOf(9));
intList.add(Integer.valueOf(20));
int sum = 0;
for (Iterator iterator = intList.iterator(); iterator.hasNext();)
{
int i = ((Integer)iterator.next()).intValue();
sum += i;
}
System.out.println(sum);
}
}
说明:显然,编译之后 没有泛型标记,又变成了
Object泛型 “任意化”
,并在 迭代得时候加入了 强制类型装换, 泛型使用只是方便了 程序员,实际上对于性能没有任何的改变。这也就是语法糖的本质。
为了更好的理解 Java 的类型擦除的泛型,请看下面的两块代码:
public class GenericTest
{
public static void test(List<String> ls)
{
String s = ls.get(0);
System.out.println("This is String generic");
return;
}
public static void test(List<Integer> ls)
{
Integer i = (Integer) ls.get(0);
System.out.println("This is Integer generic");
return;
}
}
上面的代码 在 编译时 出错,因为 Java 泛型的类型擦除,实际上,两个方法的签名一模一样,这不符合方法重载的定义。
public class GenericTest
{
public static void test(List<String> ls)
{
String s = ls.get(0);
System.out.println("This is String generic");
return;
}
public static int test(List<Integer> ls)
{
Integer i = (Integer) ls.get(0);
System.out.println("This is Integer generic");
return 0;
}
}
对比之前的代码,发现只是为同名的下一个方法,提供了一个返回值,然方法重载和返回值是无关的,但是,却意外的编译通过了,这是为什么呢? 自己测试一下,如果不适用泛型的话,也是不可以通过的,于是断定说 这个是因为泛型的关系才逃脱了。
其实原因就是使用擦除法的缺点。因为如果不是使用擦拭法,如C#,List<Integer>
和 List<String>
是两个不同的类型,可以重载。而使用擦除法后,为了实现重载只能增加一个可能没必要的返回值作为签名的一部分。具体实现可以去看看java 规范。
擦除法并不代表编译后的字节码中就不包含我们在源代码定义的泛型类型了。而是在字节码中引入新属性Signature
和 LocalVariableTypeTable
来存储泛型。这也是为什么可以通过返回值重载,及通过反射获取到泛型的根本原因。具体细节 反射
学习。
面试题
看代码,写结果,并给出合理解释
public class WrapperClassDEmo
{
public static void main(String[] args)
{
Integer a = 1;
Integer b = 2;
Integer c = 3;
Integer d = 3;
Integer e = 321;
Integer f = 321;
Long g = 3L;
System.out.println(c == d); // true
System.out.println(e == f); // false
System.out.println(c == (a + b)); // true
System.out.println(c.equals(a + b)); //true
System.out.println(g == (a + b)); // true
System.out.println(g.equals(a + b)); // false
}
}
解答
c == d
返回 true,是因为 3 在 Integer的缓冲池中,可以直接获取,不创建
e == f
返回 false,是因为 321 不在 Integer的缓冲池中,需创建
g == (a+b)
返回true,是因为 a 和 b 拆箱之后进行了类型转换
g == (a+b)
返回false,是因为 a 和 b 拆箱之后运算,之后装箱,未进行了类型转换
明确:
1、包装类的==
运算,不遇到算法运算(四则、模运算等),则不进行自动拆箱
2、equals
方法 不处理数据类型的关系
8、枚举类(Enumeration)
在实际编程中,往往存在这样的“数据集”,它们的数值在程序中是稳定的,而且“数据集”中的元素是有限的。例如:东南西北四个方向、春夏秋冬四个季度、周一到周日一周七天、一年十二个月等等。在 Java 中如何实现这个呢?
首先,我们知道如果是 环境敏感 类,大家可以使用 一个只存在一个元素的“数据集” - 单例类 (Singleton Pattern)实现。那么如何实现有限个元素的“数据集”呢? Java 1.5 给我们提供了一个新特性 枚举(Enumeration)。
首先,先来回顾一下 饿汉式 单例模式:
public class SingleTon
{
private final static SingleTon instance = new SingleTon();
private SingleTon()
{
}
public static SingleTon getInstance()
{
return instance;
}
}
模仿饿汉式的单例模式,为Dir类创建4个实例 EAST、WORTH、SOUTH、NORTH。使用匿名内部类 实现抽象方法的重写。
public abstract class Dir
{
public final static Dir EAST = new Dir(0)
{
@Override
public void show()
{
System.out.println(getValue());
}
};
public final static Dir WEST = new Dir(1)
{
@Override
public void show()
{
System.out.println(getValue());
}
};
public final static Dir SOUTH = new Dir(2)
{
@Override
public void show()
{
System.out.println(getValue());
}
};
public final static Dir NORTH = new Dir(3)
{
@Override
public void show()
{
System.out.println(getValue());
}
};
private int value;
private Dir(int value)
{
this.value = value;
}
public int getValue()
{
return value;
}
public abstract void show();
}
等同于 下面的枚举类定义:
package com.rupeng.newfeatures;
public enum Direction
{
EAST(0)
{
@Override
public void show()
{
System.out.println(getValue());
}
},
WEST(1)
{
@Override
public void show()
{
System.out.println(getValue());
}
},
WOUTH(2)
{
@Override
public void show()
{
System.out.println(getValue());
}
},
NORTH(3)
{
@Override
public void show()
{
System.out.println(getValue());
}
};
private int value;
private Direction(int value)
{
this.value = value;
}
public int getValue()
{
return value;
}
public abstract void show();
}
为了更加清晰知道 枚举的实质,我们使用最简单的枚举类,并对 字节码文件进行反编译。
public enum Dir
{
EAST,WEST,SOUTH,NORTH;
}
反编译结果:
public final class Direction extends Enum
{
public static final Direction EAST;
public static final Direction WEST;
public static final Direction WOUTH;
public static final Direction NORTH;
private static final Direction ENUM$VALUES[];
private Direction(String s, int i)
{
super(s, i);
}
public static Direction[] values()
{
Direction adirection[];
int i;
Direction adirection1[];
System.arraycopy(adirection = ENUM$VALUES, 0, adirection1 = new Direction[i = adirection.length], 0, i);
return adirection1;
}
public static Direction valueOf(String s)
{
return (Direction)Enum.valueOf(com/rupeng/newfeatures/Direction, s);
}
static
{
EAST = new Direction("EAST", 0);
WEST = new Direction("WEST", 1);
WOUTH = new Direction("WOUTH", 2);
NORTH = new Direction("NORTH", 3);
ENUM$VALUES = (new Direction[] {
EAST, WEST, WOUTH, NORTH
});
}
}
说明:反编译之后可以清楚的知道,
枚举实质就是类
,只是方便了我们的书写,才引入的新特性。 极大的提高了程序员的开发效率、减少了出错的机会。
现在,我们来分析一下 反编译 的源码。
注意事项:
1、定义枚举类的关键字是enum
2、所有得枚举类都是Enum
的子类
3、枚举项必须
声明在 枚举类的第一行,中间使用 , 隔开,最后 的 ; 可以省略(当之后不存在任何代码时),但推荐永远写上。
4、可以 为 枚举类 自定义构造器,但必须是私有private
修饰的,默认就是私有修饰
的。枚举项的定义有些特殊:枚举项(实际参数列表);
5、枚举类可以有抽象方法,但是枚举项必须重写(一般是 匿名内部类方式重写)
6、JDK 5之后,枚举项 可以用于 switch 语句中
Enum 类
public abstract class Enum<E extends Enum<E>> extends Object implements Comparable<E>, Serializable
Enum 是所有 Java 语言枚举类型的公共基本类。Since JDK 1.5
构造方法(Constructors) 仅有一个
从 Direction 类的构造方法中 super(s,i);
调用可以知道,Enum中存在这样的构造方法 protected Enum(String name, int ordinal)
其中 name - - 此枚举常量的名称,它是用来声明该常量的标识符;ordinal - - 枚举常量的序数(它在枚举声明中的位置,其中初始常量序数为零)。
成员方法(Method)
比较功能
int compareTo(E o)
比较此枚举与指定对象的顺序。 返回值是两个 枚举常量 序数的差。
判断功能
boolean equals(Object other)
当指定对象等于此枚举常量时,返回 true。
获取功能
-
String name()
返回此枚举常量的名称,在其枚举声明中对其进行声明。 -
int ordinal()
返回枚举常量的序数(它在枚举声明中的位置,其中初始常量序数为零)。 -
Class<E> getDeclaringClass()
返回与此枚举常量的枚举类型相对应的 Class 对象。 -
static <T extends Enum<T>> T valueOf(Class<T> enumType, String name)
返回带指定名称的指定枚举类型的枚举常量。 -
String toString()
返回枚举常量的名称,它包含在声明中。
特别的获取功能:子类 自定义方法(编译器优化时自动补写) Ctrl + 鼠标左键点击
没有了
-
public static Direction valueOf(String s)
底层调用的父类 Enum 的Enum.valueOf(Class,s)
实现的 -
public static <T extends Enum<T>> T[] values()
返回所有的枚举常量
案例:自定义 枚举类 四季,测试所有方法
案例:枚举的常见应用
package com.rupeng.newfeatures;
public class EnumDemo
{
/** 普通枚举 */
public enum ColorEnum
{
RED, GREEN, YELLOW, BLUE;
}
/** 枚举像普通类一样可以添加属性和方法,可以为它添加静态和非静态属性或方法*/
public enum SeasonEnum
{
// 注:枚举写在最前面,否则编译出错
SPRING, SUMMER, AUTUMN, WINTER;
private final static String position = "test";
public static SeasonEnum getSeason()
{
if ("test".equals(position))
return SPRING;
else
return WINTER;
}
}
/** 性别 “实现带有构造器的枚举” */
public enum Gender
{
MAN("MAN"), WOMEN("WOMEN");
private final String value;
// 构造器默认也只能是private, 从而保证构造函数只能在内部使用
Gender(String value)
{
this.value = value;
}
public String getValue()
{
return value;
}
}
/** 订单状态 “ 实现带有抽象方法的枚举” */
public enum OrderState
{
CANCEL
{
public String getName()
{
return "已取消";
}
},
WAITCONFIRM
{
public String getName()
{
return "待审核";
}
},
WAITPAYMENT
{
public String getName()
{
return "等待付款";
}
},
ADMEASUREPRODUCT
{
public String getName()
{
return "正在配货";
}
},
WAITDELIVER
{
public String getName()
{
return "等待发货";
}
},
DELIVERED
{
public String getName()
{
return "已发货";
}
},
RECEIVED
{
public String getName()
{
return "已收货";
}
};
public abstract String getName();
}
public static void main(String[] args)
{
// 枚举是一种类型,用于定义变量,以限制变量的赋值;
// 赋值时通过“枚举名.值”取得枚举中的值
ColorEnum colorEnum = ColorEnum.BLUE;
switch (colorEnum)
{
case RED:
System.out.println("color is red");
break;
case GREEN:
System.out.println("color is green");
break;
case YELLOW:
System.out.println("color is yellow");
break;
case BLUE:
System.out.println("color is blue");
break;
}
// 遍历枚举
System.out.println("遍历ColorEnum枚举中的值");
for (ColorEnum color : ColorEnum.values())
{
System.out.println(color);
}
// 获取枚举的个数
System.out.println("ColorEnum枚举中的值有" + ColorEnum.values().length + "个");
// 获取枚举的索引位置,默认从0开始
System.out.println(ColorEnum.RED.ordinal());// 0
System.out.println(ColorEnum.GREEN.ordinal());// 1
System.out.println(ColorEnum.YELLOW.ordinal());// 2
System.out.println(ColorEnum.BLUE.ordinal());// 3
// 枚举默认实现了java.lang.Comparable接口
System.out.println("比较的结果是:序数之差");
System.out.println(ColorEnum.RED.compareTo(ColorEnum.GREEN));// -1
System.out.println("===========");
System.out.println("季节为" + SeasonEnum.getSeason());
System.out.println("===========");
for (Gender gender : Gender.values())
{
System.out.println(gender.value);
}
System.out.println("===========");
for (OrderState order : OrderState.values())
{
System.out.println(order.getName());
}
}
}