zoukankan      html  css  js  c++  java
  • 泛型和枚举

    一、泛型

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

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

  • 相关阅读:
    排序算法的实现(冒泡,选择,插入 O(N*N)--理解方法实现
    HTTPS工作原理和TCP握手机制
    HTTP协议学习
    IP头,TCP头,UDP头,MAC帧头定义
    单链表的实现
    数字图像处理------中值滤波
    对于矩阵的理解-- by 孟岩老师
    java编码问题总结
    jsp数据库连接大全和数据库操作封装到Javabean
    构建一个高可扩展性javabean和jsp连接数据库操作
  • 原文地址:https://www.cnblogs.com/lbrs/p/9043813.html
Copyright © 2011-2022 走看看