private static int add(int a, int b) {
System.out.println(a + "+" + b + "=" + (a + b));
return a + b;
}
private static float add(float a, float b) {
System.out.println(a + "+" + b + "=" + (a + b));
return a + b;
}
private static double add(double a, double b) {
System.out.println(a + "+" + b + "=" + (a + b));
return a + b;
}
private static double add(T a, T b) {
System.out.println(a + "+" + b + "=" + (a.doubleValue() + b.doubleValue()));
return a.doubleValue() + b.doubleValue();
}
泛型中的类型在使用时指定,不需要强制类型转换(类型安全,编译器会检查类型)。看下这个例子:
List list = new ArrayList();
list.add("流华追梦");
list.add(100d);
list.add(new Person());
我们在使用上述 list 时,list 中的元素都是 Object 类型(无法约束其中的类型),所以在取出集合元素时需要人为的强制类型转化到具体的目标类型,且很容易出现 java.lang.ClassCastException异常。
引入泛型,它将提供类型的约束,提供编译前的检查:
List list = new ArrayList<>();
// list中只能放String, 不能放其它类型的元素
三. 泛型的基本使用
3.1. 泛型类
从一个简单的泛型类看起:
class Point { // 此处可以随便写标识符号,T是type的简称
private T var; // var的类型由T指定,即:由外部指定
public T getVar() { // 返回值的类型由外部决定
return var;
}
public void setVar(T var) { // 设置的类型也由外部决定
this.var = var ;
}
}
public class GenericsDemo01 {
public static void main(String[] args) {
Point p = new Point<>(); // 里面的var类型为String类型
p.setVar("it"); // 设置字符串
System.out.println(p.getVar().length()) ; // 取得字符串的长度
}
}
多元泛型:
class Notepad { // 此处指定了两个泛型类型
private K key; // 此变量的类型由外部决定
private V value; // 此变量的类型由外部决定
public K getKey() {
return this.key;
}
public V getValue() {
return this.value;
}
public void setKey(K key) {
this.key = key;
}
public void setValue(V value) {
this.value = value;
}
}
public class GenericsDemo01 {
public static void main(String[] args) {
Notepad t = null ; // 定义两个泛型类型的对象
t = new Notepad<>() ; // 里面的key为String,value为Integer
t.setKey("流华追梦"); // 设置第一个内容
t.setValue(20); // 设置第二个内容
System.out.print("姓名;" + t.getKey()); // 取得信息
System.out.print(",年龄;" + t.getValue()); // 取得信息
}
}
3.2. 泛型接口
简单的泛型接口:
interface Info { // 在接口上定义泛型
public T getVar(); // 定义抽象方法,抽象方法的返回值就是泛型类型
}
class InfoImpl implements Info { // 定义泛型接口的子类
private T var; // 定义属性
public InfoImpl(T var) { // 通过构造方法设置属性内容
this.setVar(var);
}
public void setVar(T var) {
this.var = var;
}
public T getVar() {
return this.var;
}
}
public class GenericsDemo01 {
public static void main(String[] arsg) {
Info i = null; // 声明接口对象
i = new InfoImpl<>("流华追梦"); // 通过子类实例化对象
System.out.println("内容:" + i.getVar());
}
}
3.3. 泛型方法
泛型方法,是在调用方法的时候指明泛型的具体类型。
1. 定义泛型方法语法格式:
2. 调用泛型方法语法格式:
说明一下,定义泛型方法时,必须在返回值前边加一个 ,来声明这是一个泛型方法,持有一个泛型 T,然后才可以用泛型 T 作为方法的返回值。
Class 的作用就是指明泛型的具体类型,而 Class 类型的变量 c,可以用来创建泛型类的对象。
为什么要用变量 c 来创建对象呢?既然是泛型方法,就代表着我们不知道具体的类型是什么,也不知道构造方法如何,因此没有办法去 new 一个对象,但可以利用变量 c 的 newInstance 方法去创建对象,也就是利用反射创建对象。
泛型方法要求的参数是 Class 类型,而 Class.forName() 方法的返回值也是 Class,因此可以用 Class.forName() 作为参数。其中,forName() 方法中的参数是何种类型,返回的 Class 就是何种类型。在本例中,forName() 方法中传入的是 User 类的完整路径,那么返回的就是 Class 类型的对象,因而调用泛型方法时,变量 c 的类型就是 Class,因此泛型方法中的泛型 T 就被指明为 User,所以变量 obj 的类型为 User。
当然,泛型方法不是仅仅可以有一个参数 Class,可以根据需要添加其他参数。
为什么要使用泛型方法呢?因为泛型类要在实例化的时候就指明类型,如果想换一种类型,不得不重新 new 一次,可能不够灵活;而泛型方法可以在调用的时候指明类型,更加灵活。
那么如何解决呢?为了解决泛型中隐含的转换问题,Java 泛型加入了类型参数的上下边界机制。 extends A> 表示该类型参数可以是 A(上边界)或者 A 的子类类型。编译时擦除掉类型 A,即用 A 类型代替类型参数。这种方法可以解决开始遇到的问题,编译器知道类型参数的范围,如果传入的实例类型 B 是在这个范围内的话允许转换,这时只要一次类型转换就可以了,运行时会把对象当做 A 的实例看待。
public static void funC(List extends A> listA) {
// ...
}
public static void funD(List listB) {
funC(listB); // OK
// ...
}
class Info { // 此处泛型只能是数字类型
private T var; // 定义泛型变量
public void setVar(T var) {
this.var = var;
}
public T getVar() {
return this.var;
}
public String toString() { // 直接打印
return this.var.toString();
}
}
public class demo1 {
public static void main(String[] args) {
Info i1 = new Info<>(); // 声明Integer的泛型对象
}
}
下限:
class Info {
private T var; // 定义泛型变量
public void setVar(T var) {
this.var = var;
}
public T getVar() {
return this.var;
}
public String toString() { // 直接打印
return this.var.toString();
}
}
public class GenericsDemo01 {
public static void main(String[] args) {
Info i1 = new Info<>(); // 声明String的泛型对象
Info
3.5. 泛型数组
首先,我们泛型数组相关的申明:
List[] list1 = new ArrayList[10]; // 编译错误,非法创建
List[] list2 = new ArrayList>[10]; // 编译错误,需要强转类型
List[] list3 = (List[]) new ArrayList>[10]; // OK,但是会有警告
List>[] list4 = new ArrayList[10]; // 编译错误,非法创建
List>[] list5 = new ArrayList>[10]; // OK
List[] list6 = new ArrayList[10]; // OK,但是会有警告
那么通常我们如何用呢?
讨巧的使用场景:
public class GenericsDemo01 {
public static void main(String[] args) {
Integer[] i = fun1(1,2,3,4,5,6); // 返回泛型数组
fun2(i);
}
public static T[] fun1(T... arg) { // 接收可变参数
return arg; // 返回泛型数组
}
public static void fun2(T[] param) { // 输出
System.out.print("接收泛型数组:");
for(T t : param) {
System.out.print(t + "、");
}
}
}
合理使用:
public ArrayWithTypeToken(Class type, int size) {
array = (T[]) Array.newInstance(type, size);
}
3.6. 小结
> 无限制通配符
extends E> extends 关键字声明了类型的上界,表示参数化的类型可能是所指定的类型,或者是此类型的子类
super E> super 关键字声明了类型的下界,表示参数化的类型可能是指定的类型,或者是此类型的父类
// 使用原则《Effictive Java》
// 为了获得最大限度的灵活性,要在表示 生产者或者消费者 的输入参数上使用通配符,使用的规则就是:生产者有上限、消费者有下限
1. 如果参数化类型表示一个 T 的生产者,使用 < ? extends T>;
2. 如果它表示一个 T 的消费者,就使用 < ? super T>;
3. 如果既是生产又是消费,那使用通配符就没什么意义了,因为你需要的是精确的参数类型。
再看一个实际例子,加深印象:
private > E max(List extends E> e1) {
if (e1 == null) {
return null;
}
// 迭代器返回的元素属于 E 的某个子类型
Iterator extends E> iterator = e1.iterator();
E result = iterator.next();
while (iterator.hasNext()) {
E next = iterator.next();
if (next.compareTo(result) > 0) {
result = next;
}
}
return result;
}
上述代码中的类型参数 E 的范围是 >,我们可以分步查看:
要进行比较,所以 E 需要是可比较的类,因此需要 extends Comparable<…>(注意这里不要和继承的 extends 搞混了,不一样);
Comparable< ? super E> 要对 E 进行比较,即 E 的消费者,所以需要用 super;
而参数 List< ? extends E> 表示要操作的数据是 E 的子类的列表,指定上限,这样容器才够大。
多个限制(使用 & 符号):
public class Client {
// 工资低于2500元的上斑族并且站立的乘客车票打8折
public static void discount(T t) {
if (t.getSalary() < 2500 && t.isStanding()) {
System.out.println("恭喜你!您的车票打八折!");
}
}
public static void main(String[] args) {
discount(new Me());
}
}
public class Test {
public static void main(String[] args) {
ArrayList list1 = new ArrayList<>();
list1.add("abc");
ArrayList list2 = new ArrayList<>();
list2.add(123);
System.out.println(list1.getClass() == list2.getClass()); // true
}
}
public class Test {
public static void main(String[] args) throws Exception {
ArrayList list = new ArrayList<>();
list.add(1); // 这样调用 add 方法只能存储整形,因为泛型类型的实例为 Integer
list.getClass().getMethod("add", Object.class).invoke(list, "asd");
for (int i = 0; i < list.size(); i++) {
System.out.println(list.get(i));
}
}
}
public class Test {
public static void main(String[] args) {
/* 不指定泛型的时候 */
int i = Test.add(1, 2); // 这两个参数都是Integer,所以T为Integer类型
Number f = Test.add(1, 1.2); // 这两个参数一个是Integer,一个是Float,所以取同一父类的最小级,为Number
Object o = Test.add(1, "asd"); // 这两个参数一个是Integer,一个是String,所以取同一父类的最小级,为Object
/* 指定泛型的时候 */
int a = Test.add(1, 2); // 指定了Integer,所以只能为Integer类型或者其子类
int b = Test.add(1, 2.2); // 编译错误,指定了Integer,不能为Float
Number c = Test.add(1, 2.2); // 指定为Number,所以可以为Integer和Float
}
// 这是一个简单的泛型方法
public static T add(T x,T y) {
return y;
}
}
class Pair {
private Date value;
public Date getValue() {
return value;
}
public void setValue(Date value) {
this.value = value;
}
}
然后再子类中重写参数类型为 Date 的那两个方法,实现继承中的多态。
可是由于种种原因,虚拟机并不能将泛型类型变为 Date,只能将类型擦除掉,变为原始类型Object。这样,我们的本意是进行重写,实现多态。可是类型擦除后,只能变为了重载。这样,类型擦除就和多态有了冲突。JVM 知道你的本意吗?知道!!!可是它能直接实现吗,不能!!!如果真的不能的话,那我们怎么去重写我们想要的 Date 类型参数的方法啊。
public Object getValue() {
return super.getValue();
}
而子类重写的方法是:
public Date getValue() {
return super.getValue();
}
其实这在普通的类继承中也是普遍存在的重写,这就是协变。
并且,还有一点也许会有疑问,子类中的桥方法 Object getValue() 和 Date getValue() 是同时存在的,可是如果是常规的两个方法,他们的方法签名是一样的,也就是说虚拟机根本不能分别这两个方法。如果是我们自己编写 Java 代码,这样的代码是无法通过编译器的检查的,但是虚拟机却是允许这样做的,因为虚拟机是通过参数类型和返回类型来确定一个方法,所以编译器为了实现泛型的多态允许自己做这个看起来“不合法”的事情,然后交给虚拟器去区别。
4.7. 如何理解基本类型不能作为泛型类型?
比如,我们没有 ArrayList,只有 ArrayList,为何?
因为当类型擦除后,ArrayList 的原始类型变为 Object,但是 Object 类型不能存储 int 值,只能引用 Integer 的值。
另外需要注意,我们能够使用 list.add(1) 是因为 Java 基础类型的自动装箱拆箱操作。
4.8. 如何理解泛型类型不能实例化?
不能实例化泛型类型, 这本质上是由于类型擦除决定的。
我们可以看到如下代码会在编译器中报错:
T test = new T(); // ERROR
因为在 Java 编译期没法确定泛型参数化类型,也就找不到对应的类字节码文件,所以自然就不行了,此外由于 T 被擦除为 Object,如果可以 new T() 则就变成了 new Object(),失去了本意。 如果我们确实需要实例化一个泛型,应该如何做呢?可以通过反射实现:
static T newTclass (Class < T > clazz) throws InstantiationException, IllegalAccessException {
T obj = clazz.newInstance();
return obj;
}
4.9. 泛型数组:能不能采用具体的泛型类型进行初始化?
我们先来看下 Oracle 官网提供的一个例子:
List[] lsa = new List[10]; // Not really allowed.
Object o = lsa;
Object[] oa = (Object[]) o;
List li = new ArrayList();
li.add(new Integer(3));
oa[1] = li; // Unsound, but passes run time store check
String s = lsa[1].get(0); // Run-time error ClassCastException.
List>[] lsa = new List>[10]; // OK, array of unbounded wildcard type.
Object o = lsa;
Object[] oa = (Object[]) o;
List li = new ArrayList();
li.add(new Integer(3));
oa[1] = li; // Correct.
Integer i = (Integer) lsa[1].get(0); // OK
java.lang.reflect.Type 是 Java 中所有类型的公共高级接口, 代表了 Java 中的所有类型。Type 体系中类型的包括:数组类型(GenericArrayType)、参数化类型(ParameterizedType)、类型变量(TypeVariable)、通配符类型(WildcardType)、原始类型(Class)、基本类型(Class),以上这些类型都实现 Type 接口。
public class GenericType {
private T data;
public T getData() {
return data;
}
public void setData(T data) {
this.data = data;
}
public static void main(String[] args) {
GenericType genericType = new GenericType() {};
Type superclass = genericType.getClass().getGenericSuperclass();
// getActualTypeArguments 返回确切的泛型参数, 如Map返回[String, Integer]
Type type = ((ParameterizedType) superclass).getActualTypeArguments()[0];
System.out.println(type); // class java.lang.String
}
}
其中 ParameterizedType:
public interface ParameterizedType extends Type {
// 返回确切的泛型参数, 如Map返回[String, Integer]
Type[] getActualTypeArguments();
// 返回当前class或interface声明的类型, 如List>返回List
Type getRawType();
// 返回所属类型。如,当前类型为O.I, 则返回O。顶级类型将返回null
Type getOwnerType();
}
//关键字的使用探讨/*访问关键词private 只能在本类中访问public 只能在本工程中访问protected 只能在包中和子类中访问默认的 只能在包中访问*//*final 类 方法 变量 final 类 不能被继承 final 方法 不能被子类覆盖,但可以继承 final 变量 只能有一次赋值,赋值后不能改变 final 不能用来修饰构造方法*///this()
What’s new in Zabbix 2.0?
去年开始使用Zabbix的时候,是1.8.X的版本,今年Zabbix已经跨入了2.0的时代。看了2.0的release notes,和performance相关的有下面几个:
:: Performance improvements::Trigger related da
修改jboss端口
%JBOSS_HOME%\server\{服务实例名}\conf\bindingservice.beans\META-INF\bindings-jboss-beans.xml
中找到
<!-- The ports-default bindings are obtained by taking the base bindin
@echo off
::演示:删除指定路径下指定天数之前(以文件名中包含的日期字符串为准)的文件。
::如果演示结果无误,把del前面的echo去掉,即可实现真正删除。
::本例假设文件名中包含的日期字符串(比如:bak-2009-12-25.log)
rem 指定待删除文件的存放路径
set SrcDir=C:/Test/BatHome
rem 指定天数
set DaysAgo=1
HTML5的video和audio标签是用来在网页中加入视频和音频的标签,在支持html5的浏览器中不需要预先加载Adobe Flash浏览器插件就能轻松快速的播放视频和音频文件。而html5media.js可以在不支持html5的浏览器上使video和audio标签生效。 How to enable <video> and <audio> tags in