泛型和枚举
一、泛型
1.基础:泛型(genericity)可以提高程序的复用性,减少数据类型的转换(个人感觉像是减少了重载从而减少了代码量?),从而增加代码的运行效率。
2.声明:实际就在声明接口或者类在名字后面加上类型参数,类型参数的定义格式有如下三种:
(1)类型变量标识符
public class GenericA<T>
{ //类体}
(2)类型变量标识符 extends 父类型
public class GenericA<T extends Number>
{ //类体}
(3)类型变量标识符 extends 父类型1 &父类型2 &……&父类型n
public class GenericA<T extends J_C1 & J_C2>
{ //类体}
3.使用
(1)对应2.(1)
class GenericA<X> { X p1; X p2; public GenericA(X x,X y) { p1=x; p2=y; } public String f() { return p1.toString()+p2.toString(); } } public class Genericties<T extends Number> { public static void main(String []args) { /*Genericity<T>*/ GenericA<Integer> a=new GenericA<Integer>(10,10); GenericA<String> c=new GenericA<String>("12","12"); System.out.println(a.f()); System.out.println(c.f()); } }
可以看出使用的字母是X,代表不止可以使用常用字母T,可以使用任何作为变量名称的标识符,若是不继承任何类或接口,那么就默认为只有和Object一样的操作方法,大致有getClass(),hashcode(),toString(),equals(Object obj)等,除此之外,还有notify()和notifyAll()以及wait()几种重载[后面这几个没怎么用过,应该是线程里面的东西,那就后面再讲。。]
2018.07.16
还有一些使用时需要注意的事项:1>GenericA<Integer> a=new GenericA<Integer>(10,10);这句代码有前后两个<Integer>,可以删除任一个或者都删除,并不影响结果。
2>可以删除<>中的内容只保留括号,例如GenericA<Integer> a=new GenericA<>(10,10);但是只适用于后面的那个,奇怪的是前面的不能适用,也即不存在以下这种形式:GenericA<>a=new GenericA<Integer>(10,10);
3>不能使用不同于参数的类型,例如不能有GenericA a=new GenericA<String>(10,10);这种形式,不通过编译。
(2)继承某类(可以是接口),对应于2.(2)
class GenericB<M extends Number> { public int sum(M a,M b,M c) { return a.intValue()+b.intValue()+c.intValue(); } } public static void main(String []args)
{ GenericB<Double> b=new GenericB<Double>(); System.out.println(b.sum(120.0, 12.0, 1.2));
}
输出133,以此为例,若是继承Number类,则在泛型可以使用的方法包括如下图(少截了个shortValue()):
(3)继承多个类或者接口,对应2.(3)
class C1 { static int A=8; public void print() { System.out.println("C1"); } } interface C2 { public void printA(); } class C3 extends C1 implements C2 { public C3(int A) { this.A=A; } int A; public void printA() { System.out.println(A); } } class C4 extends C1 implements C2 { public void print() { System.out.println("C4"); } public void printA() { System.out.println(A); } } class GenericC<X extends C1 & C2> { public void m(X x) { x.print(); x.printA(); } }
上面有两个类C3、C4都继承于C1,实现C2都符合带泛型类GenericC<X extends C1 & C2>的条件,我们来做验证:
GenericC<C3> gc=new GenericC<C3>(); GenericC<C4> gc0=new GenericC<C4>(); C3 c3=new C3(12); C4 c4=new C4(); gc.m(c3); gc0.m(c4);
C1
12
C4
8
结果显而易见。
4.泛型方法
泛型不只可以针对类,还可以针对方法。
public class GenerictiesFunction { public <K,V> void f(K k,V v) { System.out.println(k.getClass()); System.out.println(v.getClass()); } public static void main(String[] args) { GenerictiesFunction g = new GenerictiesFunction(); g.f(0.0,"generic"); } }
结果是
class java.lang.Double
class java.lang.String
可以看出泛型方法与泛型类还是有区别的。
主要就是那个<>的位置,一个在类名后面,一个在函数返回值前面;
还有就是似乎还没有发现泛型方法使用时需要<>这个东西;2018.07.16 找到需要使用的情况就是在调用的函数名前面添加,例如g.<Double,String>f(0.0,"generic");就等同于g.f(0.0,"generic");但不能使用g.<>f(0.0,"generic")或者<Double,>、<,String>这种。
另外就是泛型方法声明中也能继承其他类或接口,就像这样:public <K extends Number,V> void f(K k,V v){函数体}
5.通配符和边界
(1)类型擦除
2018.08.25 好久都没时间写博客了,这段时间感觉读研还是挺有压力的,特别是一系列事件的冲击更是心力交瘁,不过有对编程的喜爱,就能支撑下去。
类型擦除是指JVM在处理泛型时,在运行期间并不“认识”泛型参数,例如:
class A <T> {} public class GeneBound { public static void main (String []args) { A<Integer> a=new A<Integer>(); A<String> b =new A<String>(); System.out.println(a.getClass()==b.getClass()); } }
返回结果是true,虽然对象a和b的类型一个是A<String>,一个是A<Integer>,但却会被判断为一类,在C++中则是两个类型,这个现象就叫做类型擦除。
这样一来就会造成一定的问题(其实不算大问题),我的理解是,在Java里,你永远不能在泛型类使用超出该类泛型参数之外的方法。例如下面例子中T无extends任何类型,里面的obj只能调用Object类的方法,所以调用f()会出错。
class HasF { public void f() { System.out.println("HasF.f()"); } } public class GeneBound<T> { private T obj; public GeneBound(T obj) { this.obj = obj; } public void manipulate() { obj.f(); //无法编译 找不到符号 f() } public static void main(String[] args) { HasF hasF = new HasF(); GeneBound<HasF> manipulator = new GeneBound<>(hasF); manipulator.manipulate(); }
刚没写几个字的博客,老板又跟我打电话,让交材料,但我还没写先写那个去了,看来又要搁置一段时间了,从8.25-8.29就写了这么一点。先给下次开个头,不知道下次是什么时间了,被再给忘了。
下面写T extends XX 类型的例子。(2018.9.13)
public class GeneBound<T extends Number> { private T obj; public GeneBound(T obj) { this.obj = obj; } public int manipulate() { // obj.f(); //无法编译 找不到符号 f() int i=obj.intValue(); return i; } public static void main(String[] args) { HasF hasF = new HasF(); GeneBound<Double> manipulator = new GeneBound<>(10.3); System.out.println(manipulator.manipulate()); } }
最终输出10可以看出类型擦除到了Number,但不能使用Integer的方法例如VauleOf()或toOctalString()等等,即使你的泛型申请了Integer类型。这就是类型擦除。
(2)泛型数组
泛型数组还是要学一学的,用的地方还蛮多的,而且也没法进行下去了。
直接声明泛型数组是行不通的,例如:
class gen<T>{ } public class GeneArray { public static void main(String[] args) {
gen<Integer> []a=new gen<Integer>( ) [3]; //The type of the expression must be an array type but it resolved to gen<Integer> 意料之中不能这样写
gen<Integer> []a=new gen<Integer> [3]; //试一试对象数组的方法,可惜仍然不可以 Cannot create a generic array of gen<Integer>
}
}
我们可以使用ArrayList来进行代替:
ArrayList<gen<Integer>> a = new ArrayList<gen<Integer>>();
09.24
虽然无法创建泛型数组,但是却可以声明,例如:
gen <Integer> []a;//不但能通过编译还能运行,不过不知道怎么用啊。这里先留个尾巴吧。。。。
(3)数组的协变
package genericity; class Fruit {} class Apple extends Fruit {} class Jonathan extends Apple {} class Orange extends Fruit {} /*数组协变,代码来自 https://segmentfault.com/a/1190000005337789*/ public class Covariant { public static void main(String[] args) { Fruit[] fruit = new Apple[10]; fruit[0] = new Apple(); // OK fruit[1] = new Jonathan(); // OK // Runtime type is Apple[], not Fruit[] or Orange[]: try { // Compiler allows you to add Fruit: fruit[2] = new Fruit(); // ArrayStoreException } catch(Exception e) { System.out.println(e); } try { // Compiler allows you to add Oranges: fruit[3] = new Orange(); // ArrayStoreException } catch(Exception e) { System.out.println(e); } } }
在上例中,数组的引用被赋给了一个Apple数组,在此之后数组只能放入Apple及其子类的元素。现在的问题是当这个数组是使用ArrayList的泛型数组时就会失效。
因此引入通配符来解决这个问题。问题就是List<Apple>不是List<Fruit>的子类
<? extends XXX>来实现泛型向上转型。
然而你并不能这样:
Fruit a=new Fruit(); Apple b; Jonathan c; Orange d; List<? extends Fruit> alist = new ArrayList<>();/**/ alist.add(a); //Error The method add(capture#1-of ? extends Fruit) in the type List<capture#1-of ? extends Fruit> is not applicable for the arguments (Fruit) alist.add(b); //Error alist.add(c); //Error alist.add(d); //Error
代表除了null你啥都不能直接添加到通配符的List,那还有啥用啊?但是很明显通配符不是这么用的,List<? extends Fruit>代表List<Fruit>、List<Apple>和List<Orange>都是其子类。例如:
class Fruit { public String name; public Fruit(String name) { this.name=name; } public void getname() { System.out.println(name);; } } class Apple extends Fruit { public Apple(String name) { super(name); // TODO Auto-generated constructor stub } } class Jonathan extends Apple { public Jonathan(String name) { super(name); // TODO Auto-generated constructor stub }} class Orange extends Fruit { public Orange(String name) { super(name); // TODO Auto-generated constructor stub }} public class Covariant { public static void act(List<? extends Fruit> list) { for (Fruit fruit : list) { fruit.getname(); } } public static void act(List<? extends Fruit> list) { for (Fruit fruit : list) { fruit.getname(); } } public static void main(String[] args) { Apple b; Jonathan c; Orange d; List<Apple> alist = new ArrayList<>(); alist.add(new Apple("Wu")); alist.add(new Jonathan("Yi")); List<Fruit> blist = new ArrayList<>(); blist.add(new Apple("Mi")); blist.add(new Jonathan("Ng")); blist.add(new Fruit("Wu")); blist.add(new Orange("Lp")); act(alist); //输出WuYi 只能是Apple的子类,但仍能作为参数适配List<? extends Fruit> } }
同理,<? super Fruit>和<?>分别是下界通配符和无界通配符。
2018.10.28终于算是给通配符画上了一个逗号吧,几个月了都。。。。最近效率有点低。
遇到的两个错误:
(1)List有两个一个在util,一个在awt。不要弄错啊,否则就报错。
(2)其实List是不能被实例化的,Cannot instantiate the type List,
List<Fruit> blist = new List<>(); (×)
List<Fruit> blist = new ArrayList<>(); (√)
二、枚举
1.声明
声明方式与类和接口相同,不能被private、protected、abstract等修饰,若被public修饰,文件名应与枚举名相同。
enum M_DAYS { Monday,Tuesday,Wednesday,Thursday,Friday,Saturday,Sunday }
上面是一个枚举声明的例子。
2.枚举变量
(1)单个枚举变量。
M_DAYS m=M_DAYS.Monday; //赋值方法
System.out.println(m.toString());
System.out.println(m);
System.out.println(m.name());
以上三个打印都会输出Monday,方法都等于System.out.println(M_DAYS.Monday);
单个枚举变量的重要性质——可以调用所有枚举常量,当然用枚举名可以访问所有枚举常量:
System.out.println(m.Friday==M_DAYS.Friday); //有警告,就像警告对象名调用的静态域或方法一样。
当然返回true.
(2)多个枚举变量。
M_DAYS m1,m2,m3;
(3)枚举变量数组
M_DAYS []k=M_DAYS.values(); //M_DAYS.values()和m.values()是一样的效果 for(M_DAYS l:k) { System.out.println(l); }
values()方法通过枚举名或枚举变量访问,返回所有枚举常量。上述代码输出所有的枚举常量。
3.switch方法
M_DAYS m=M_DAYS.Sunday; switch(m) { case Monday: System.out.println(m.Monday); break; case Tuesday: System.out.println(m.Tuesday); break; case Wednesday: System.out.println(m.Wednesday); break; default: System.out.println("false"); }
一个值得注意的点是只要switch对象是枚举,case后面直接跟常量就好,例如上面直接用Monday,而不能M_DAYS.Monday或者m.Monday。
一个小case:default后面可以不加break.
4.枚举实现构造方法
enum Color
{
RED("红色",1),BlUE("蓝色",2),WHITE,GRAY(); //枚举常量
String name;
int index;
Color() //默认
{
name="null";
index=0;
}
Color(String name,int index) //构造方法
{
this.name=name;
this.index=index;
}
public void setName(String name) //普通方法
{
this.name=name;
}
public String toString() //覆盖的原有方法
{
return name+"_"+index;
}
}
public class enumclass {
public static void main(String []args)
{
Color c=Color.RED;
Color w=Color.WHITE;
c.setName("绿色");
System.out.println(c.toString());
System.out.println(w.toString());
}
}
对于枚举来说,前面说过枚举声明方式和类是相同的,那么它也能有自己的构造函数,但是构造函数只能是private或者是默认的,也是为了防止实例化枚举对象。如果没有显式声明构造函数,那么也会默认构造函数,也就是说没有构造函数的情况下有:
WHITE,GRAY()可以改为WHITE(),GRAY完全相同。都是创建一个静态的最终的类对象。
而添加方法、添加成员域、覆盖原有的方法与类一样(后期发现再来修改)。
5.实现接口、接口组织枚举
本来实现接口想不在叙述了的,后来发现一种奇特的实现方法,就是枚举的常量可以各自实现接口的方法
RED("红色",1)
{
public String getName() { return "111"; }
},
Blue("蓝色",2)
{
public String getName() { return "222"; }
},
WHITE
{ public String getName() { return "333"; } },
NULL()
{ public String getName() { return "444"; }
}; //各常量各自实现接口
public String getName() //枚举的实现接口 { return name; }
Color c=Color.RED; //不能声明为Color.RED() Color w=Color.WHITE; c.setName("绿色"); System.out.println(c.getName()); System.out.println(w.getName());
结果:111
333 说明常量中的方法会覆盖枚举体内的方法,同理普通类也会覆盖。
接口组织枚举听起来好像很高大上,其实就是枚举里面有枚举,