黑马程序员 第八篇 枚举类 反射机制 泛型(继承part 2) (Foundation; completed)

——- android培训java培训、期待与您交流! ———-

1.final与继承的关系

final关键字有最终,不变的意思,可以修饰成员变量,方法和类; 而且会对类的继承产生很大的影响。

  • final关键字修饰类时,说明其类不能有子类,也就是说该类不能被继承,该类的成员变两个在这里讲不起作用
  • final关键字修饰方法时,说明该方法不能被重写,因为累都不能继承了,方法就更不能重写了
  • 类里可以含有final关键字修饰的方法
  • final关键字修饰的成员变量的对象引用不能修改
  • final关键字修饰的类里的方法默认被修饰为final

错误示范

public final static int i = 1;
i = i + 1;

错误分析
i被修饰为final类型,因此i = i + 1 将使程序发生异常

2.abstract与继承的关系

abstract关键字表示抽象的意思,和final的含义完全是相反的。所谓抽象,就好比在日常生活中人们设计的图纸,而这个图纸就好比是一个抽象的房子,需要把房子盖起来实现这个图纸。

在Java里,抽象类里最少要含有一个抽象方法,让它的子类去实现这个抽象方法,抽象类里也可以定义一些方法。
Java中提供抽象类,并且其永远不能实例化,它的唯一作用就是集成扩展。

abstract class bike
{
    public String name = "抽象类的成员变量";

    public String getMessage()
    {
        return name;
    }

    abstract public String getMes();

}


public class racing_cycle extends bike
{
    public String getMes()
    {
        return getMessage();
    }

    public static void main(String[] args)
    {
        racing cycle rc = new racing_cycle();
        System.out.println(rc.getMes());
    }
}


运行结果:
    抽象类的成员变量

代码解析:

  • 从此例子可以看出,父类bike被修饰为abstract,是一个抽象类,继承与bike的子类racing_cycle,实现了父类的抽象方法。
    -如果是抽象类的子类就必须实现抽象类的抽象方法。对于接就好比是一个功能的模型
    抽象类是不能实例化的,只能去实现所有的抽象方法,否则就只能还是抽象类

抽象类与普通继承类的区别就是,使用父类的方法,重写父类的方法或通过父类与子类相互引用来完成功能

继承与抽象的相同点与不同的

  • 继承了抽象类就必须去实现该抽象类的方法
  • 如果抽象类的子类没有实现父类的抽象方法,就必须为抽象类
  • 抽象类和继承一样,都必须是单继承,或是多层继承

3.什么是多态

多态的实际生活中的含义大体就是不同的对象有着大体相同的轮廓或者形态,但具体的执行过程不大相同。这里拿苹果和馒头来打个比方,苹果属于水果的一种,馒头属于面食的一种,而苹果和馒头都可以属于实物。一种物品的两种状态表现,这就是多态。

所谓多态,就好比一个物品在一个时期有两种状态一样。在Java里,通过运用多态可以使对象有更大的灵活性,这一点仅通过继承无法实现,但是多态也是基于继承的。

这里写图片描述

class fruit
{
    public void getMes()
    {
        System.out.println("父类");
    }
}


class apple extends fruit
{
    public void getMes()
    {
        System.out.println("apple子类");
    }

}


class orange extends fruit
{
    public void getMes()
    {
        System.out.println("orange子类");
    }
}



public class racing_cycle
{
    public void show(fruit f)
    {
        f.getMes();
    }

    public static void main(String[] arg)
    {
        racing_cycle rc = new racing_cycle();
        fruit f = new fruit();
        apple a = new apple();
        orange o = new orange();

        rc.show(a);
        rc.show(o);

    }
}


运行结果:
    apple 子类
    orange 子类

4.什么是枚举类

在Java里,枚举类就是一组连续的基本类型的数值。

public enum Grade {A, B, C, D, E, F};

枚举类的完整例子

enum colors{green, yellow}

public class racing_cycle
{
    public colors color;
    public String speed;

    public racing_cycle(colors color, String speed)
    {
        this.color = color;
        this.speed = speed;
    }

    public String toString()
    {
        String emp = "";

        switch(color)
        {
            case green:
                emp = "绿色";
                break;
            case yellow:
                emp = "黄色";
                break;
        }

        return emp + " : " + speed;
    }



    public static void main(String[] args)
    {
        racing_cycle rc = new racing_cycle(colors.green, "快的");
        System.out.println(rc);
    }

}



运行结果:

    绿色 : 快的

枚举类型就好比一个类封装了一些成员变量,提高了开发的效率,避免了一些编译期的错误。在对象的取值确定的情况下,用枚举会很方便。

5.什么是反射机制

在日常生活中,通过放大镜可以看清楚物体的内部结构。
在Java中,反射机制就起到放大镜的效果,可以通过类名,加载这个类,显示出这个类的方法等信息。使用反射机制可以在程序的运行中对程序进行动态的控制。

(以下文段,参考及摘录来源于网络博客)

基本概念

  在Java运行时环境中,对于任意一个类,能否知道这个类有哪些属性和方法?对于任意一个对象,能否调用它的任意一个方法?

  答案是肯定的。

  这种动态获取类的信息以及动态调用对象的方法的功能来自于Java语言的反射(Reflection)机制。

  Java反射机制主要提供了以下功能

  1.在运行时判断任意一个对象所属的类。

  2.在运行时构造任意一个类的对象。

  3.在运行时判断任意一个类所具有的成员变量和方法。

  4.在运行时调用任意一个对象的方法。

  Reflection是Java被视为动态(或准动态)语言的一个关键性质。

  这个机制允许程序在运行时透过Reflection APIs取得任何一个已知名称的class的内部信息。

  包括其modifiers(诸如public、static等)、 superclass(例如Object)、实现了的 interfaces (例如Serializable)、也包括其fields和methods的所有信息,并可于运行时改变fields内容或调用methods。

动态语言
  动态语言的定义“程序运行时,允许改变程序结构或者变量类型,这种语言称为动态语言”。

  从这个观点看,Perl,Python,Ruby是动态语言,C++,Java,C#不是动态语言。

  尽管在这样的定义与分类下Java不是动态语言,它却有着一个非常突出的动态相关机制:Reflection。这个字的意思是:反射、映像、倒影,用在Java身上指的是我们可以于运行时加载、探知、使用编译期间完全未知的classes。

  换句话说,Java程序可以加载一个运行时才得知名称的class,获悉其完整构造(但不包括methods定义),并生成其对象实体、或对其fields设值、或唤起其methods。

  这种“看透”class的能力(the ability of the program to examine itself)被称为introspection(内省、内观、反省)。Reflection和introspection是常被并提的两个术语。

Java Reflection API简介
  在JDK中,主要由以下类来实现Java反射机制,这些类(除了第一个)都位于java.lang.reflect包中

  Class类:代表一个类,位于java.lang包下。

  Field类:代表类的成员变量(成员变量也称为类的属性)。

  Method类:代表类的方法。

  Constructor类:代表类的构造方法。

  Array类:提供了动态创建数组,以及访问数组的元素的静态方法。

Class对象
  要想使用反射,首先需要获得待操作的类所对应的Class对象。

  Java中,无论生成某个类的多少个对象,这些对象都会对应于同一个Class对象。

  这个Class对象是由JVM生成的,通过它能够获悉整个类的结构。

  常用的获取Class对象的3种方式:

  1.使用Class类的静态方法。例如:  

Class.forName(“java.lang.String”);

  2.使用类的.class语法。如:

String.class;

  3.使用对象的getClass()方法。如:

String str = “aa”;
Class

import java.lang.reflect.Method;

public class DumpMethods
{
    public static void main(String[] args) throws Exception //在方法后加上这句,异常就消失了
    {
        //获得字符串所标识的类的class对象
        Class<?> classType = Class.forName("java.lang.String");//在此处传入字符串指定类名,所以参数获取可以是一个运行期的行为,可以用args[0]

        //返回class对象所对应的类或接口中,所声明的所有方法的数组(包括私有方法)
        Method[] methods = classType.getDeclaredMethods();

        //遍历输出所有方法声明
        for(Method method : methods)
        {
            System.out.println(method);
        }
    }

}

例程2:通过反射调用方法
  通过反射调用方法。详情见代码及注释:

import java.lang.reflect.Method;

public class InvokeTester
{
    public int add(int param1, int param2)
    {
        return param1 + param2;

    }

    public String echo(String message)
    {
        return "Hello: " + message;
    }

    public static void main(String[] args) throws Exception
    {

        // 以前的常规执行手段
        InvokeTester tester = new InvokeTester();
        System.out.println(tester.add(1, 2));
        System.out.println(tester.echo("Tom"));
        System.out.println("---------------------------");

        // 通过反射的方式

        // 第一步,获取Class对象
        // 前面用的方法是:Class.forName()方法获取
        // 这里用第二种方法,类名.class
        Class<?> classType = InvokeTester.class;

        // 生成新的对象:用newInstance()方法
        Object invokeTester = classType.newInstance();
        System.out.println(invokeTester instanceof InvokeTester); // 输出true

        // 通过反射调用方法
        // 首先需要获得与该方法对应的Method对象
        Method addMethod = classType.getMethod("add", new Class[] { int.class,
                int.class });
        // 第一个参数是方法名,第二个参数是这个方法所需要的参数的Class对象的数组

        // 调用目标方法
        Object result = addMethod.invoke(invokeTester, new Object[] { 1, 2 });
        System.out.println(result); // 此时result是Integer类型

        //调用第二个方法
        Method echoMethod = classType.getDeclaredMethod("echo", new Class[]{String.class});
        Object result2 = echoMethod.invoke(invokeTester, new Object[]{"Tom"});
        System.out.println(result2);

    }
}

生成对象

  若想通过类的不带参数的构造方法来生成对象,我们有两种方式:

  1.先获得Class对象,然后通过该Class对象的newInstance()方法直接生成即可:

 Class<?> classType = String.class;

 Object obj = classType.newInstance();

  2.先获得Class对象,然后通过该对象获得对应的Constructor对象,再通过该Constructor对象的newInstance()方法生成

  (其中Customer是一个自定义的类,有一个无参数的构造方法,也有带参数的构造方法):

 Class<?> classType = Customer.class;

    // 获得Constructor对象,此处获取第一个无参数的构造方法的
    Constructor cons = classType.getConstructor(new Class[] {});

    // 通过构造方法来生成一个对象
    Object obj = cons.newInstance(new Object[] {});

  若想通过类的带参数的构造方法生成对象,只能使用下面这一种方式:

  (Customer为一个自定义的类,有无参数的构造方法,也有一个带参数的构造方法,传入字符串和整型)

Class<?> classType = Customer.class;

Constructor cons2 = classType.getConstructor(new Class[] {String.class, int.class});

Object obj2 = cons2.newInstance(new Object[] {"ZhangSan",20});

  可以看出调用构造方法生成对象的方法和调用一般方法的类似,不同的是从Class对象获取Constructor对象时不需要指定名字,而获取Method对象时需要指定名字。

6.什么是泛型

本节参考于网络博文

我们在编写程序时,经常遇到两个模块的功能非常相似,只是一个是处理int数据,另一个是处理string数据,或者其他自定义的数据类型,但我们没有办法,只能分别写多个方法处理每个数据类型,因为方法的参数类型不同。有没有一种办法,在方法中传入通用的数据类型,这样不就可以合并代码了吗?泛型的出现就是专门解决这个问题的。读完本篇文章,你会对泛型有更深的了解。

为什么要使用泛型
为了了解这个问题,我们先看下面的代码,代码省略了一些内容,但功能是实现一个栈,这个栈只能处理int数据类型:

public class Stack

    {

        private int[] m_item;

        public int Pop(){...}

        public void Push(int item){...}

        public Stack(int i)

        {

            this.m_item = new int[i];

        }

}

上面代码运行的很好,但是,当我们需要一个栈来保存string类型时,该怎么办呢?很多人都会想到把上面的代码复制一份,把int改成string不就行了。当然,这样做本身是没有任何问题的,但一个优秀的程序是不会这样做的,因为他想到若以后再需要long、Node类型的栈该怎样做呢?还要再复制吗?优秀的程序员会想到用一个通用的数据类型object来实现这个栈:

public class Stack

    {

        private object[] m_item;

        public object Pop(){...}

        public void Push(object item){...}

        public Stack(int i)

        {

            this.m_item = new[i];

        }



    }

这个栈写的不错,他非常灵活,可以接收任何数据类型,可以说是一劳永逸。但全面地讲,也不是没有缺陷的,主要表现在:

当Stack处理值类型时,会出现装箱、折箱操作,这将在托管堆上分配和回收大量的变量,若数据量大,则性能损失非常严重。
在处理引用类型时,虽然没有装箱和折箱操作,但将用到数据类型的强制转换操作,增加处理器的负担。
在数据类型的强制转换上还有更严重的问题(假设stack是Stack的一个实例):

Node1 x = new Node1();

            stack.Push(x);

         Node2 y = (Node2)stack.Pop();

上面的代码在编译时是完全没问题的,但由于Push了一个Node1类型的数据,但在Pop时却要求转换为Node2类型,这将出现程序运行时的类型转换异常,但却逃离了编译器的检查。

针对object类型栈的问题,我们引入泛型,他可以优雅地解决这些问题。泛型用用一个通过的数据类型T来代替object,在类实例化时指定T的类型,运行时(Runtime)自动编译为本地代码,运行效率和代码质量都有很大提高,并且保证数据类型安全。

使用泛型
下面是用泛型来重写上面的栈,用一个通用的数据类型T来作为一个占位符,等待在实例化时用一个实际的类型来代替。让我们来看看泛型的威力:

public class Stack<T>

    {

        private T[] m_item;

        public T Pop(){...}

        public void Push(T item){...}

        public Stack(int i)

        {

            this.m_item = new T[i];

        }

}

类的写法不变,只是引入了通用数据类型T就可以适用于任何数据类型,并且类型安全的。这个类的调用方法:

//实例化只能保存int类型的类

Stack<int> a = new Stack<int>(100);

      a.Push(10);

      a.Push("8888"); //这一行编译不通过,因为类a只接收int类型的数据

      int x = a.Pop();

//实例化只能保存string类型的类

Stack<string> b = new Stack<string>(100);

b.Push(10); //这一行编译不通过,因为类b只接收string类型的数据

   b.Push("8888");

string y = b.Pop();

这个类和object实现的类有截然不同的区别:

  1. 他是类型安全的。实例化了int类型的栈,就不能处理string类型的数据,其他数据类型也一样。

  2. 无需装箱和折箱。这个类在实例化时,按照所传入的数据类型生成本地代码,本地代码数据类型已确定,所以无需装箱和折箱。

  3. 无需类型转换。

泛型类实例化的理论
C#泛型类在编译时,先生成中间代码IL,通用类型T只是一个占位符。在实例化类时,根据用户指定的数据类型代替T并由即时编译器(JIT)生成本地代码,这个本地代码中已经使用了实际的数据类型,等同于用实际类型写的类,所以不同的封闭类的本地代码是不一样的。按照这个原理,我们可以这样认为:

泛型类的不同的封闭类是分别不同的数据类型。

例:Stack和Stack是两个完全没有任何关系的类,你可以把他看成类A和类B,这个解释对泛型类的静态成员的理解有很大帮助。

泛型类中数据类型的约束
程序员在编写泛型类时,总是会对通用数据类型T进行有意或无意地有假想,也就是说这个T一般来说是不能适应所有类型,但怎样限制调用者传入的数据类型呢?这就需要对传入的数据类型进行约束,约束的方式是指定T的祖先,即继承的接口或类。因为C#的单根继承性,所以约束可以有多个接口,但最多只能有一个类,并且类必须在接口之前。这时就用到了C#2.0的新增关键字:

public class Node

public class Node<T, V> where T : Stack, new()

        where V: IComparable

需要注意的是,new约束只能是无参数的,所以也要求相应的类Stack必须有一个无参构造函数,否则编译失败。

C#中数据类型有两大类:引用类型和值类型。引用类型如所有的类,值类型一般是语言的最基本类型,如int, long, struct等,在泛型的约束中,我们也可以大范围地限制类型T必须是引用类型或必须是值类型,分别对应的关键字是class和struct:

public class Node<T, V> where T : class

        where V: struct

泛型方法
泛型不仅能作用在类上,也可单独用在类的方法上,他可根据方法参数的类型自动适应各种参数,这样的方法叫泛型方法。看下面的类:

public class Stack2

    {

        public void Push<T>(Stack<T> s, params T[] p)

        {

            foreach (T t in p)

            {

                s.Push(t);

            }

        }

}

原来的类Stack一次只能Push一个数据,这个类Stack2扩展了Stack的功能(当然也可以直接写在Stack中),他可以一次把多个数据压入Stack中。其中Push是一个泛型方法,这个方法的调用示例如下:

Stack<int> x = new Stack<int>(100);

    Stack2 x2 = new Stack2();

    x2.Push(x, 1, 2, 3, 4, 6);

    string s = "";

    for (int i = 0; i < 5; i++)

    {

        s += x.Pop().ToString();

    }    //至此,s的值为64321

泛型中的静态成员变量
在C#1.x中,我们知道类的静态成员变量在不同的类实例间是共享的,并且他是通过类名访问的。C#2.0中由于引进了泛型,导致静态成员变量的机制出现了一些变化:静态成员变量在相同封闭类间共享,不同的封闭类间不共享。

这也非常容易理解,因为不同的封闭类虽然有相同的类名称,但由于分别传入了不同的数据类型,他们是完全不同的类,比如:

Stack<int> a = new Stack<int>();

Stack<int> b = new Stack<int>();

Stack<long> c = new Stack<long>();

类实例a和b是同一类型,他们之间共享静态成员变量,但类实例c却是和a、b完全不同的类型,所以不能和a、b共享静态成员变量。

泛型中的静态构造函数
静态构造函数的规则:只能有一个,且不能有参数,他只能被.NET运行时自动调用,而不能人工调用。

泛型中的静态构造函数的原理和非泛型类是一样的,只需把泛型中的不同的封闭类理解为不同的类即可。以下两种情况可激发静态的构造函数:

  1. 特定的封闭类第一次被实例化。

  2. 特定封闭类中任一静态成员变量被调用。

泛型类中的方法重载
方法的重载在.Net Framework中被大量应用,他要求重载具有不同的签名。在泛型类中,由于通用类型T在类编写时并不确定,所以在重载时有些注意事项,这些事项我们通过以下的例子说明:

public class Node<T, V>

    {

        public T add(T a, V b)          //第一个add

        {

            return a;

        }

        public T add(V a, T b)          //第二个add

        {

            return b;

        }

        public int add(int a, int b)    //第三个add

        {

            return a + b;

        }

}

上面的类很明显,如果T和V都传入int的话,三个add方法将具有同样的签名,但这个类仍然能通过编译,是否会引起调用混淆将在这个类实例化和调用add方法时判断。请看下面调用代码:

Node<int, int> node = new Node<int, int>();

    object x = node.add(2, 11);

这个Node的实例化引起了三个add具有同样的签名,但却能调用成功,因为他优先匹配了第三个add。但如果删除了第三个add,上面的调用代码则无法编译通过,提示方法产生的混淆,因为运行时无法在第一个add和第二个add之间选择。

Node<string, int> node = new Node<string, int>();

        object x = node.add(2, "11");

这两行调用代码可正确编译,因为传入的string和int,使三个add具有不同的签名,当然能找到唯一匹配的add方法。

由以上示例可知,C#的泛型是在实例的方法被调用时检查重载是否产生混淆,而不是在泛型类本身编译时检查。同时还得出一个重要原则:

当一般方法与泛型方法具有相同的签名时,会覆盖泛型方法。

泛型类的方法重写
方法重写(override)的主要问题是方法签名的识别规则,在这一点上他与方法重载一样,请参考泛型类的方法重载。

泛型的使用范围
本文主要是在类中讲述泛型,实际上,泛型还可以用在类方法、接口、结构(struct)、委托等上面使用,使用方法大致相同,就不再讲述。

reference:
http://blog.csdn.net/shaily/article/details/2222195

——- android培训java培训、期待与您交流! ———-

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值