精华内容
下载资源
问答
  • 枚举到底是个什么类型

    千次阅读 多人点赞 2020-07-26 22:30:02
    Java中也枚举类型,是enum关键字来表示的 枚举常用来表示一个常量集,用来限定变量的取值,只能在枚举的范围内,比如一年四季只有春、夏、秋、冬、一个星期只有周一到周日,这类固定的常量就比较适合枚举来...

    前言

    枚举是很多面向对象语言都会有的一种类型,它可以将表达同一类型的变量组合成一个集合,组成一个常量集

    在Java中也有枚举类型,是用enum关键字来表示的

    枚举常用来表示一个常量集,用来限定变量的取值,只能在枚举的范围内,比如一年四季只有春、夏、秋、冬、一个星期只有周一到周日,这类固定的常量就比较适合用枚举来表达

    当然你说我用静态的常量来表达行不行呢?也是可以的,只是枚举就是为这种场景而生的

    枚举这种类型其实没什么存在感,大家在使用的时候也是使用最基本的用法,甚至很多人都不用枚举,至少我自己在写这篇文章之前是很少用
    枚举类型的,经常会用静态常量来代替枚举,但枚举确实有他的好处,下面我们就一起来看一下

    枚举的简单用法

    public enum OrderState {
        ORDER_CONFIRM,ORDER_PAYED,ORDER_DELIVERY,ORDER_FINISH
    }
    

    OrderState列举了订单的几种状态

    • ORDER_CONFIRM 订单已确认
    • ORDER_PAYED 订单已支付
    • ORDER_DELIVERY 订单已出库
    • ORDER_FINISH 订单已完成
    OrderState orderState = OrderState.ORDER_FINISH;
    System.out.println(orderState);
    

    在使用枚举类型时,需要定义一个枚举类型的对象,如上代码,执行结果如下:

    ORDER_FINISH
    

    枚举类型常用的方法

    • values() 返回所有的枚举值数组
    • ordinal() 表示枚举值在枚举类型中的次序,从0开始
    • compareTo() 用于比较两个枚举类型
    • name() 返回枚举值
    // values()返回所有的枚举值
    for (OrderState os :OrderState.values()) {
        //枚举值的次序
        System.out.println(os + "|" + os.ordinal());
        //比较
        System.out.println(os.compareTo(OrderState.ORDER_FINISH));
        //枚举值
        System.out.println(os.name());
        System.out.println("------------------");
    }
    

    向枚举中添加方法

    在枚举的简单用法中,枚举类里面只定义了几个常量,其实枚举类型中还可以增加方法

    public enum OrderState {
        ORDER_CONFIRM(0,"订单已确认"),ORDER_PAYED(1,"订单已支付"),ORDER_DELIVERY(2,"订单已支付"),ORDER_FINISH(3,"订单已完成");
    
        private int state;
        private String stateText;
    
        OrderState(int state,String stateText){
            this.state = state;
            this.stateText = stateText;
        }
    
        public int getState(){
            return state;
        }
    
        public String getStateText(){
            return stateText;
        }
    
        public static void main(String[] args){
            for(OrderState orderState : OrderState.values()){
                System.out.println(orderState + "|" + orderState.getState() + "|" + orderState.getStateText());
            }
        }
    }
    

    从上面的代码可以看出来,枚举类型可以有构造方法,也可以有普通方法,枚举跟类很像,或者说它就是一种拥有限制的类

    switch中的枚举

    枚举跟switch语句简直是天造地设的一对,switch中可以天然的支持枚举类型

    switch (orderState){
        case ORDER_CONFIRM:
            System.out.println(orderState.getState());
            break;
        case ORDER_PAYED:
            System.out.println(orderState.getStateText());
            break;
        case ORDER_DELIVERY:
            System.out.println(orderState.getState());
            break;
        case ORDER_FINISH:
            System.out.println(orderState.getStateText());
            break;
    }
    

    枚举的真面目

    我们用编译/反编译的方法来看一下枚举到底是个什么鬼类型

    首先编译OrderState.java文件,注意你的枚举类型中有中文需要加-encoding参数用UTF-8进行编码,否则会编译不通过

    javac -encoding UTF-8 OrderState.java
    
    

    执行完成后,在同一目录下会生成一个OrderState.class文件,我们反编译回来

    javap -p OrderState.class
    

    会得到如下的代码

    //枚举就是一个继承自Enum的类
    public final class org.kxg.enumDemo.OrderState extends java.lang.Enum<org.kxg.enumDemo.OrderState> {
      //枚举中的常量就是类的静态变量
      public static final org.kxg.enumDemo.OrderState ORDER_CONFIRM;
      public static final org.kxg.enumDemo.OrderState ORDER_PAYED;
      public static final org.kxg.enumDemo.OrderState ORDER_DELIVERY;
      public static final org.kxg.enumDemo.OrderState ORDER_FINISH;
      //私有变量变成类的私有变量
      private int state;
      private java.lang.String stateText;
      private static final org.kxg.enumDemo.OrderState[] $VALUES;
      //values()很有意思,它是编译器自动生成的
      public static org.kxg.enumDemo.OrderState[] values();
      public static org.kxg.enumDemo.OrderState valueOf(java.lang.String);
      //构造方法
      private org.kxg.enumDemo.OrderState(int, java.lang.String);
      public int getState();
      public java.lang.String getStateText();
      public static void main(java.lang.String[]);
      static {};
    }
    

    从反编译回来的内容,可以看出来,枚举类型本质是就是继承自Enum的final类,枚举中定义的常量就是类的静态常量

    有一个很有意思的点就是,编译器会自动生成values()方法,因为在枚举本身和Enum类中都没有values()方法,只能是编译器在编译的时候自动生成的

    枚举可以继承,可以实现接口吗?

    枚举可以继承吗?可以实现接口吗?

    面试官就喜欢问类似的问题,如果你对枚举不了解,肯定就答不上来了

    从上面反编译的结果来看,枚举本质是个final类并且继承自Enum,Java里面不支持多继承,所以枚举不能继承其他类,同时枚举是个final类,也不能被继承

    但枚举可以实现接口,枚举本质上就是个类,所以它有类的一般特性

    定义一个接口

    public interface Color {
        public String getColor();
    }
    

    定义一个枚举类型,实现Color接口

    public enum EmnuInterface implements Color {
        RED("红色"),GREEN("绿色"),GRAY("灰色"),YELLOW("黄色");
    
        private String color;
        EmnuInterface(String color){
            this.color = color;
        }
    
        @Override
        public String getColor() {
            return color;
        }
    
        public static void main(String[] args){
            EmnuInterface color = EmnuInterface.GREEN;
            System.out.println(color.getColor());
        }
    }
    

    输入结果:

    绿色
    

    枚举的优缺点

    1、枚举常量简单安全

    不使用枚举的时候,我们是这样定义常量的

    public static final String RED = "红色";
    public static final String GREEN = "绿色";
    public static final String GRAY = "灰色";
    public static final String YELLOW = "黄色";
    

    这样使用看起来也没什么问题,可以达到常量的效果,但这个静态常量是不具有范围限定的,比如我有一个方法要限定只能传入指定的几个颜色,使用静态常量就无法限制范围,使用者可以随意传入静态常量,但如果使用了枚举就可以限定只能传入指定范围内的值

    2、枚举有内置方法

    如果要列出所有的常量,类和接口必须使用反射,比较繁琐,而枚举有内置的方法values()可以很方便的列举出静态常量

    3、枚举的缺点

    枚举不可继承,无法扩展,但枚举一般用来定义常量,也不需要扩展

    如果感觉对你有些帮忙,请收藏好,你的关注和点赞是对我最大的鼓励!
    如果想跟我一起学习,坚信技术改变世界,请关注【Java天堂】公众号,我会定期分享自己的学习成果,第一时间推送给您

    在这里插入图片描述

    展开全文
  • 疯狂JAVA讲义

    2014-10-17 13:35:01
    学生提问:为什么选择设置用户变量,用户变量和系统变量有什么区别呢? 11 1.5 第一个Java程序 12 1.5.1 编辑Java源代码 12 1.5.2 编译Java程序 13 学生提问:当我们使用编译C程序时,不仅需要指定存放目标文件...
  • 三:Java有什么 Java体系比较庞杂,功能繁多,这也导致很多人在自学Java的时候总是感觉无法建立 全面的知识体系,无法从整体上把握Java的原因。在这里我们先简单了解一下Java的版本 具体的Java体系知识结构,将在后面详细...
  • JAVA上百实例源码以及开源项目

    千次下载 热门讨论 2016-01-03 17:37:40
     用JAVA编写的指针式圆形电子钟,效果图如下所示,其实代码很简单,希望对你帮助。 Message-Driven Bean EJB实例源代码 2个目标文件 摘要:Java源码,初学实例,EJB实例  Message-Driven Bean EJB实例源代码,演示...
  • Java圆形电子时钟源代码 1个目标文件 内容索引:JAVA源码,系统相关,电子钟 用JAVA编写的指针式圆形电子钟,效果图如下所示,其实代码很简单,希望对你帮助。 Message-Driven Bean EJB实例源代码 2个目标文件 摘要:...
  • 通过一个类的全限定名来获取描述此类的二进制字节流这个动作放到Java虚拟机外部去实现,以便让应用程序自己决定如何去获取所需要的类。实现这个动作的代码模块称为“类加载器”。 哪几种类加载器 JVM预定义的三...

    什么是类加载器

    通过一个类的全限定名来获取描述此类的二进制字节流这个动作放到Java虚拟机外部去实现,以便让应用程序自己决定如何去获取所需要的类。实现这个动作的代码模块称为“类加载器”。

    有哪几种类加载器

    JVM预定义的三种类型类加载器:

    • 启动(Bootstrap)类加载器(BootstrapClassLoader):是用本地代码实现的类加载器,它负责将 %JRE_HOME%/lib下面的类库加载到内存中(比如rt.jar、resources.jar、charsets.jar、class文件等等)。另外需要注意的是可以通过启动jvm时指定-Xbootclasspath和路径来改变Bootstrap ClassLoader的加载目录。由于引导类加载器涉及到虚拟机本地实现细节,开发者无法直接获取到启动类加载器的引用,所以不允许直接通过引用进行操作。

    • 标准扩展(Extension)类加载器:是由ExtClassLoader(sun.misc.Launcher$ExtClassLoader)实现的。它负责将JRE/lib/ext或者由系统变量java.ext.dir指定位置中的类库加载到内存中。开发者可以直接使用标准扩展类加载器。

    • 系统(System)类加载器:是由AppClassLoader(sun.misc.Launcher$AppClassLoader)实现的。它负责将系统类路径(CLASSPATH)中指定的类库加载到内存中。开发者可以直接使用系统类加载器。

    除了以上列举的三种类加载器,还有一种比较特殊的类型叫线程上下文类加载器。线程上下文类加载器,它不是一个新的类型,更像一个类加载器的角色,ThreadContextClassLoader可以是上述类加载器的任意一种,但往往是AppClassLoader。

    Java虚拟机的第一个类加载器是Bootstrap,这个加载器很特殊,它不是Java类,因此它不需要被别人加载,它嵌套在Java虚拟机内核里面,也就是JVM启动的时候Bootstrap就已经启动,它是用C++写的二进制代码(不是字节码),它可以去加载别的类。然后BootstrapClassLoader去加载ExtClassLoader、AppClassLoader,并将AppClassLoader的parent设置为ExtClassLoader,并设置线程上下文类加载器。

    这也是我们在测试时为什么发现System.class.getClassLoader()结果为null的原因,这并不表示System这个类没有类加载器,而是它的加载器比较特殊,是BootstrapClassLoader,由于它不是Java类,因此获得它的引用肯定返回null。

    Launcher是JRE中用于启动程序入口main()的类,让我们看下Launcher的代码(jdk8)

    public Launcher() {
        //Launcher.ExtClassLoader 和 Launcher.AppClassLoader是protected的,他们都是单例模式。
        Launcher.ExtClassLoader var1;
        try {
            //加载扩展类类加载器
            var1 = Launcher.ExtClassLoader.getExtClassLoader();
        } catch (IOException var10) {
            throw new InternalError("Could not create extension class loader", var10);
        }
    
        try {
             //加载应用程序类加载器,并设置parent为extClassLoader
            this.loader = Launcher.AppClassLoader.getAppClassLoader(var1);
        } catch (IOException var9) {
            throw new InternalError("Could not create application class loader", var9);
        }
        //设置默认的线程上下文类加载器为AppClassLoader
        Thread.currentThread().setContextClassLoader(this.loader);
        String var2 = System.getProperty("java.security.manager");
        if (var2 != null) {
            //java安全管理器
            SecurityManager var3 = null;
            if (!"".equals(var2) && !"default".equals(var2)) {
                try {
                    var3 = (SecurityManager)this.loader.loadClass(var2).newInstance();
                } catch (IllegalAccessException var5) {
                } catch (InstantiationException var6) {
                } catch (ClassNotFoundException var7) {
                } catch (ClassCastException var8) {
                }
            } else {
                var3 = new SecurityManager();
            }
    
            if (var3 == null) {
                throw new InternalError("Could not create SecurityManager: " + var2);
            }
    
            System.setSecurityManager(var3);
        }
    
    }
    

    委托加载机制

    某个特定的类加载器在接到加载类的请求时,首先将加载任务委托给父 类加载器(不是父类),依次递归,如果父 类加载器可以完成类加载任务,就成功返回;只有父类加载器无法完成此加载任务时,才自己去加载。

    当Java虚拟机要加载一个类时,到底派出哪个类加载器去加载呢?

    • 首先当前线程的类加载器去加载线程中的第一个类(假设为类A)。

    • 如果类A中引用了类B,Java虚拟机将使用加载类A的类加载器去加载类B。

    • 还可以直接调用ClassLoader.loadClass()方法来指定某个类加载器去加载某个类。

    委托机制的意义是什么?

    委托加载机制能保证基础类仅加载一次,不会让jvm中存在重名的类。比如String.class,每次加载都委托给父 类加载器,最终都是BootstrapClassLoader,都保证java核心类都是BootstrapClassLoader加载的,保证了java的安全与稳定性。

    委托加载机制的实现是在ClassLoader的loadClass方法里

     protected Class<?> loadClass(String name, boolean resolve)
        throws ClassNotFoundException
    {
        synchronized (getClassLoadingLock(name)) {
            // First, check if the class has already been loaded
            Class<?> c = findLoadedClass(name);
            if (c == null) {
                long t0 = System.nanoTime();
                try {
                    if (parent != null) {
                        //委托给父 类加载器去加载
                        c = parent.loadClass(name, false);
                    } else {
                        c = findBootstrapClassOrNull(name);
                    }
                } catch (ClassNotFoundException e) {
                    // ClassNotFoundException thrown if class not found
                    // from the non-null parent class loader
                }
    
                if (c == null) {
                    // If still not found, then invoke findClass in order
                    // to find the class.
                    long t1 = System.nanoTime();
                    c = findClass(name);
    
                    // this is the defining class loader; record the stats
                    sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
                    sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
                    sun.misc.PerfCounter.getFindClasses().increment();
                }
            }
            if (resolve) {
                resolveClass(c);
            }
            return c;
        }
    }
    

    ClassLoader里的主要方法

    Class<?> loadClass(String name)Class<?> loadClass(String name, boolean resolve)方法,根据类的二进制名称去加载类,返回Class对象。

    protected Class<?> findClass(String name) 方法,由loadClass调用,根据类的二进制名称查找返回Class对象,此方法需要被子类覆盖实现。

    protected final Class<?> defineClass 将二进制数据转换成一个Class对象,底层是native方法,通常是findClass方法里查找到二进制数据然后调用此方法,返回Class对象。

    自定义类加载器

    继承ClassLoader类并覆写实现findClass方法,这样的自定义是遵循委托加载机制的。如下例子还覆写了loadClass方法,打破了委托加载机制。

    public class MyClassLoader extends ClassLoader {
        //用于读取.Class文件的路径
        private String swapPath;
        //用于标记这些name的类是先由自身加载的
        private Set<String> useMyClassLoaderLoad;
    
        public MyClassLoader(String swapPath, Set<String> useMyClassLoaderLoad) {
            this.swapPath = swapPath;
            this.useMyClassLoaderLoad = useMyClassLoaderLoad;
        }
    
        @Override
        public Class<?> loadClass(String name) throws ClassNotFoundException {
            Class<?> c = findLoadedClass(name);
            if (c == null && useMyClassLoaderLoad.contains(name)) {
                //特殊的类让我自己加载  打破委托加载机制
                c = findClass(name);
                if (c != null) {
                    return c;
                }
            }
            return super.loadClass(name);
        }
    
        @Override
        protected Class<?> findClass(String name) {
            //根据文件系统路径加载class文件,并返回byte数组
            byte[] classBytes = getClassByte(name);
            //调用ClassLoader提供的方法,将二进制数组转换成Class类的实例
            return defineClass(name, classBytes, 0, classBytes.length);
        }
    
        private byte[] getClassByte(String name) {
            String className = name.substring(name.lastIndexOf('.') + 1, name.length()) + ".class";
            try {
                FileInputStream fileInputStream = new FileInputStream(swapPath + className);
                byte[] buffer = new byte[1024];
                int length = 0;
                ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
                while ((length = fileInputStream.read(buffer)) > 0) {
                    byteArrayOutputStream.write(buffer, 0, length);
                }
                return byteArrayOutputStream.toByteArray();
            } catch (IOException e) {
                e.printStackTrace();
            }
            return new byte[]{};
        }
    }
    

    通过上面的例子和动手体验JVM中Class对象的唯一性我们知道,自定义类加载器不仅可以定义如何查找class,还可以实现类资源的隔离等等。

    线程上下文类加载器

    线程上下文类加载器只是一个概念。

    class Thread {
      ...
      private ClassLoader contextClassLoader;
    
      public ClassLoader getContextClassLoader() {
        return contextClassLoader;
      }
    
      public void setContextClassLoader(ClassLoader cl) {
        this.contextClassLoader = cl;
      }
      ...
    }
    

    首先 contextClassLoader 是那种需要显示使用的类加载器,如果你没有显示使用它,也就永远不会在任何地方用到它。你可以使用下面这种方式来显示使用它

    Thread.currentThread().getContextClassLoader().loadClass(name);
    

    当前线程的 contextClassLoader 是从父线程那里继承过来的,所谓父线程就是创建了当前线程的线程。程序启动时的 main 线程的 contextClassLoader 就是 AppClassLoader。这意味着如果没有人工去设置,那么所有的线程的 contextClassLoader 都是 AppClassLoader。

    如果我们对业务进行划分,不同的业务使用不同的线程池,线程池内部共享同一个 contextClassLoader,线程池之间使用不同的 contextClassLoader,显示的使用contextClassLoader去加载类,就可以很好的起到隔离保护的作用,避免类版本冲突,当热线程的 contextClassLoader 使用场合比较罕见。

    ClassLoader继承关系

    由上面的继承关系,我们看到ExtClassLoader和AppClassLoader都继承了URLClassLoader,上面还有SecureClassLoader。

    SecureClassLoader 继承ClassLoader,新增了几个与使用相关的代码源(对代码源的位置及其证书的验证)和权限定义类验证(主要指对class源码的访问权限)的方法,一般我们不会直接跟这个类打交道,更多是与它的子类URLClassLoader有所关联。

    ClassLoader是一个抽象类,很多方法是空的没有实现,比如 findClass()、findResource()等。而URLClassLoader这个实现类为这些方法提供了具体的实现,并新增了URLClassPath类协助取得Class字节码流等功能,在编写自定义类加载器时,如果没有太过于复杂的需求,可以直接继承URLClassLoader类,这样就可以避免自己去编写findClass()方法及其获取字节码流的方式,使自定义类加载器编写更加简洁。

    
     

    文章有帮助的话,小手一抖点击在看,并转发吧。

    谢谢支持哟 (*^__^*)

    展开全文
  • 泛型代码和虚拟机

    2017-09-08 23:52:54
    这个过程对Java程序员有什么影响?1.类型擦除无论何时定义一个泛型类型,都自动提供了一个相应的原始类型(raw type)。原始类型的名字就是删除类型参数后的泛型类型名。类型变量被擦除(erased),并被其限定类型...

    泛型代码和虚拟机

    虚拟机没有泛型类型对象—所有对象都属于普通类。
    接下来的部分,你将看到编译器是如何“擦除”类型参数的?这个过程对Java程序员有什么影响?

    1.类型擦除

    无论何时定义一个泛型类型,都自动提供了一个相应的原始类型(raw type)。原始类型的名字就是删除类型参数后的泛型类型名。类型变量被擦除(erased),并被其限定类型替代(无限定的变量用Object)。

    例如,泛型类型Pair<T>

    public class Pair<T> {
        private T first;
        private T second;
    
        public Pair(){
            first = null;
            second = null;
        }
    
        public Pair(T first, T second){
            this.first = first;
            this.second = second;
        }
    
        public T getFirst(){
            return first;
        }
    
        public T getSecond(){
            return second;
        }
    
        public void setFirst(T newValue){
            first = newValue;
        }
    
        public void setSecond(T newValue){
            second = newValue;
        }
    }

    其原始类型:

    public class Pair {
        private Object first;
        private Object second;
    
        public Pair(){
            first = null;
            second = null;
        }
    
        public Pair(Object first, Object second){
            this.first = first;
            this.second = second;
        }
    
        public Object getFirst(){
            return first;
        }
    
        public Object getSecond(){
            return second;
        }
    
        public void setFirst(Object newValue){
            first = newValue;
        }
    
        public void setSecond(Object newValue){
            second = newValue;
        }
    }

    因为T是一个未限定的类型变量,它只是被Object替换了。
    结果是一个普通类,就像泛型引入Java语言之前你已经实现的一样。
    你的程序可能包含不同的Pair类型,如Pair<String>Pair<LocalDate> ,但是擦除使它们都变成了原始的Pair类型。
    原始类型用第一个限定类型替换类型变量,没有给定限定类型用Object替换类型变量。例如,在类Pair<T> 中的类型变量没有显式的限定,因此原始类型用Object替换T。假设我们声明了一个稍微不同的类型:

    public class Interval<T extends Comparable & Serializable> implements Serializable {
        private T lower;
        private T upper;
    
        public Interval(T first, T second){
            if (first.compareTo(second) <= 0){
                lower = first;
                upper = second;
            }
            else {
                lower = second;
                upper = first;
            }
        }
    }

    泛型类Interval的原始类型:

    public class Interval implements Serializable {
        private Comparable lower;
        private Comparable upper;
    
        public Interval(Comparable first, Comparable second){
            if (first.compareTo(second) <= 0){
                lower = first;
                upper = second;
            }
            else {
                lower = second;
                upper = first;
            }
        }
    }

    注意
    你可能想要知道切换限定:class Interval<T extends Serializable & Comparable> 会发生什么。如果这样做,原始类型用Serializable替换T,而编译器在必要时要向Comparable插入强制类型转换。为了提高效率,应该将标签(tagging)接口(即没有方法的接口)放在限定列表的末尾。

    2.翻译泛型表达式

    当程序调用一个泛型方法时,当返回类型被擦除后,编译器会插入强制类型转换。例如,下面的语句序列:

    Pair<Employee> buddies = ...;
    Employee buddy = buddies.getFirst();

    擦除getFirst的返回类型后,有返回类型Object。编译器自动插入Employee的强制类型转换。即,编译器把这个方法调用翻译为两条虚拟机指令:

    • 对原始方法Pair.getFirst的调用
    • 将返回的Object类型强制转换为Employee类型。

    当访问一个泛型域时也要插入强制类型转换。假设Pair类的first域和second域都是公有的(这也许不是一种好的编程风格,但在Java中是合法的)。表达式:

    Employee buddy = buddies.first;

    也会在结果字节码中插入强制类型转换。

    3.翻译泛型方法

    类型擦除也会出现在泛型方法中。程序员通常认为一个泛型方法:

    public static <T extends Comparable> T min(T[] a)

    是一个完整的方法族,但擦除类型之后,只剩下一个方法:

    public static Comparable min(Comparable[] a)

    注意,类型参数T已经被擦除了,只留下了它的限定类型Comparable。
    方法的擦除带来了一些复杂性。看下面这个示例:

    public class DateInterval extends Pair<LocalDate> {
    
        public DateInterval(){}
    
        public DateInterval(LocalDate first, LocalDate second){
            super(first, second);
        }
    
        public void setSecond(LocalDate second){
            if (second.compareTo(getFirst()) >= 0){
                super.setSecond(second);
            }
        }
    }

    一个日期区间是一对LocalDate对象,并且想要在该类中覆盖父类的Pair<T> 的setSecond(T second)来确保第二个值永远不小于第一个值。这个类被擦除后变成:

    public class DateInterval extends Pair {
    
        public DateInterval(){}
    
        public DateInterval(LocalDate first, LocalDate second){
            super(first, second);
        }
    
        public void setSecond(LocalDate second){
            if (second.compareTo(getFirst()) >= 0){
                super.setSecond(second);
            }
        }
    }

    也许令人惊讶的是,存在另一个从Pair继承的setSecond方法,即

    public void setSecond(Object newValue){
        second = newValue;
    }

    这两个方法:

    public void setSecond(LocalDate second)//想要覆盖的方法
    public void setSecond(Object newValue)//继承而来的方法

    显然是不同的方法,因为它有不同类型的参数:Object和LocalDate。

    出现setSecond(Object newValue) 方法原因是:因为编译阶段Pair<LocalDate> 已经被类型擦除为Pair了,其setSecond方法变成了setSecond(Object newValue) ,所以DateInterval中的setSecond(LocalDate second) 是无法覆盖父类的setSecond(Object newValue) 的。

    考虑下面的语法序列:

    DateInterval interval = new DateInterval(LocalDate.of(2017,1,1), LocalDate.of(2017,8,1));
    Pair<LocalDate> pair = interval;
    pair.setSecond(LocalDate.of(2017, 9, 1));

    我们的期望是,setSecond的调用是多态的,并且最适合的方法被调用。即我们的期望是:pair.setSecond(LocalDate.of(2017, 9, 1)) 调用的方法应该是DateInterval的setSecond(Object newValue),因为pair的类型为Pair<LocalDate> 且有方法setSecond(Object newValue)。但是由于类型擦除导致子类并没有覆盖掉父类方法Pair的setSecond(Object second)而出现了的两个DateInterval.setSecond方法,进而导致这里的pair.setSecond(LocalDate)调用了DateInterval的setSecond(LocalDate),从而干扰了多态。排除这种干扰的关键在于,要成功的覆盖的父类方法Pair的setSecond(Object second)。
    要解决这个问题,编译器会自动在DateInterval类中生成一个桥方法(bridge method):

    public void setSecond(Object second){
        setSecond((LocalDate) second);
    }

    这样,桥方法就覆盖了泛型父类Pair的setSecond(Object second)了。对于多态来说就没有问题了。

    要想了解它的工作过程,请仔细地跟踪下面语句的执行:

    pair.setSecond(LocalDate.of(2017, 9, 1));

    变量pair已经声明为类型Pair<LocalDate> ,并且这个类型只有一个简单的方法叫setSecond,即setSecond(Object)。虚拟机用pair引用的对象调用这个方法。这个对象是DateInterval类型,因而将会调用DateInterval.setSecond(Object)方法。这个方法是合成的桥方法。该方法调用DateInterval.setSecond(LocalDate),这正是我们所期望的操作效果。

    桥方法可能会变得十分奇怪。假设DateInterval类也覆盖了getSecond方法:

    public LocalDate getSecond() {
        return (LocalDate) super.getSecond();
    }

    由于需要桥方法来覆盖父类中的getSecond,编译器会自动在DateInterval中生成一个桥方法:

    public Object getSecond(){
        getSecond();
    }

    在擦除的类型中,有两个getSecond方法:

    LocalDate getSecond()//想要覆盖的方法
    Object getSecond()//编译器覆盖父类的方法产生的桥方法

    不能这样编写Java代码。具有相同参数类型(它们都没有参数)的两个方法是不合理的。但是,在虚拟机中,用参数类型和返回类型指定一个方法。因此,编译器可能产生两个仅返回类型不同的方法字节码,虚拟机能够正确处理这一情况。

    注意:
    桥方法不仅用于泛型类型,在一个方法覆盖另一个方法时可以指定一个更严格的返回类型。例如:

    public class Employee implements Cloneable {
        public Employee clone() throws CloneNotSupportedException { ... }
    }

    Object.clone和Employee.clone方法被说成是具有协变的返回类型(covariarant return types)。
    实际上,Employee类有两个克隆方法:

    Employee.clone()// 定义
    Object.clone() // 覆盖了Object.clone

    合成的桥方法调用了新定义的方法。

    总之,需要记住有关Java泛型转换的事实:

    • 虚拟机中没有泛型,只有普通的类和方法。
    • 所有的类型参数都用它们的限定类型替换。
    • 桥方法被合成来保持多态。
    • 为保持类型安全性,必要时插入强制类型转换。
    展开全文
  • Array的配置项目没有上面介绍的那么直观,默认情况下DWR装载所有的基本类型和可装载的对象,这些包括String,Date等先前介绍的类型.java高级程序员可能会理解为什么match的这行有点奇怪. [Z"/> [B"/> [S"/> [I"/> [J"/>...
  • 2、同一种类型不管通过什么方式得到Class的实例都是相等的;一个类型的字节码对象只有一份,在元空间。 3、Class的实例就看成是Java中我们学过的所有的数据类型在JVM中存在的一种状态(字节码对象) String....
  • 本书论述运用UML(统一建模语言)和模式进行对象建模的方法和技巧,...第24章 用Java实现的程序方案 第六部分 分析阶段(2) 第25章 选择第二个开发周期的需求 25.1 第二个开发周期的需求 25.2 假定和简化 第...
  • 6.1 为什么GridFS 6.2 如何实现海量存储 6.3 语言支持 6.4 简单介绍 6.5 命令行工具 6.6 内部原理 6.7 本章小结 第7章 MapReduce统计 7.1 Map函数 7.2 Reduce函数 7.3 结果存储 7.4 对...
  •  61.4.1 符号与无符号类型  61.4.2 无符号字面量  61.4.3 类型转换  61.5 溢出  61.6 位域简介  61.7 可移植性  61.8 bitset类模板  第62讲 枚举  62.1 理想的枚举  62.2 作为位掩码的枚举  ...
  • 目录介绍 01.项目介绍 02.项目运行 03.项目部分介绍 ...的建议Clean然后Rebuild,的建议修改使用内存,的说是代码问题,也的说是资源问题,比如本来是jpg图片或者.9图片,文件后缀却是png也会导致...
  • 前腾讯、前阿里员工,从事Java后台工作; 对Docker和Kubernetes充满热爱; 所有文章均为作者原创; 关于这个代码仓库 CSDN博客地址:http://blog.csdn.net/boling_cavalry 这个代码仓库里是博客中涉及的源码、...
  • 2009达内SQL学习笔记

    2010-02-10 19:46:58
    NOT IN 时, NULL 则出错,必须排除空值再运算。 in :选择列表的条件 使用IN操作符的优点: 在长的选项清单时,语法直观; 计算的次序容易管理; 比 OR 操作符清单执行更快;最大优点是可以包含其他 SELECT ...
  • JavaScript笔记

    2018-09-28 11:21:56
    特点:元素个数不限定,元素类型不限制 13.Array 对象的常用方法: |--1.join()方法--用于把数组中的所有元素放入一个字符串 | eparato表示要使用的分隔符。如果省略该参数,则使用逗号作为分隔符 |--2....
  • easy-window是什么? easy-window 是一个旨在简化桌面开发难度的通用窗体,它可以让你使用当前主流的HTML5技术快速地创建Windows桌面应用程序。创作桌面应用,不需要掌握QT,C++,C#,Java那些繁琐的东西,只要会...
  • MYSQL中文手册

    2013-03-11 21:21:34
    全文限定条件 12.7.5. 微调MySQL全文搜索 12.8. Cast函数和操作符 12.9. 其他函数 12.9.1. 位函数 12.9.2. 加密函数 12.9.3. 信息函数 12.9.4. 其他函数 12.10. 与GROUP BY子句同时使用的函数和修改程序 ...
  • 全文限定条件 12.7.5. 微调MySQL全文搜索 12.8. Cast函数和操作符 12.9. 其他函数 12.9.1. 位函数 12.9.2. 加密函数 12.9.3. 信息函数 12.9.4. 其他函数 12.10. 与GROUP BY子句同时使用的函数和修改程序 12.10.1. ...
  • 全文限定条件 12.7.5. 微调MySQL全文搜索 12.8. Cast函数和操作符 12.9. 其他函数 12.9.1. 位函数 12.9.2. 加密函数 12.9.3. 信息函数 12.9.4. 其他函数 12.10. 与GROUP BY子句同时使用的函数和修改程序 12.10.1. ...
  • oracle数据库经典题目

    2011-02-17 15:05:20
    18. 声明%TPYE类型的变量时,服务器将会做什么操作?( A ) A. 为该变量检索数据库列的数据类型 B.复制一个变量 C.检索数据库中的数据 D.为该变量检索列的数据类型和值 19.下列哪一项可以正确地引用该记录变量中...
  • CruiseYoung提供的带详细书签的电子书籍目录 http://blog.csdn.net/fksec/article/details/7888251 Oracle SQL高级编程(资深Oracle专家力作,OakTable团队推荐) 基本信息 原书名: Pro Oracle SQL 原出版社: ...
  • CruiseYoung提供的带详细书签的电子书籍目录 http://blog.csdn.net/fksec/article/details/7888251 该资料是《Oracle SQL高级编程》的源代码 对应的书籍资料见: Oracle SQL高级编程(资深Oracle专家力作,...
  • 可通过文件类型限定遍历范围)的信息记录在一个资源映射的 json 里,具体内容如下: 资源映射 json 示例 <pre><code> { "packageId": "mwbp", "version": 1, "...

空空如也

空空如也

1 2
收藏数 25
精华内容 10
关键字:

java自限定类型有什么用

java 订阅