泛型和枚举

一、泛型

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           说明常量中的方法会覆盖枚举体内的方法,同理普通类也会覆盖。

 

 接口组织枚举听起来好像很高大上,其实就是枚举里面有枚举,

posted @ 2018-05-15 23:53  LeftBody  阅读(259)  评论(0编辑  收藏  举报