Java是动态语言吗?从《Java核心编程》探索真知

Posted 哪 吒

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java是动态语言吗?从《Java核心编程》探索真知相关的知识,希望对你有一定的参考价值。


目录

一、Java是动态语言吗?

1、动态语言

动态语言是指程序在运行时可以改变其结构:新的函数可以被引进,已有的函数可以被删除等在结构上的变化。比如javascript、Python都是典型的动态语言,而C、C++、Java等语言则不属于动态语言。

动态类型语言,就是类型的检查是在运行时做的,是不是合法的要到运行时才判断,例如JavaScript就没有编译错误,只要运行错误。

2、静态类型

静态类型语言的类型判断是在运行前判断(如编译阶段),比如java就是静态类型语言,静态类型语言为了达到多态会采取一些类型鉴别手段,如继承、接口,而动态类型语言却不需要。

(1)优点:

在于其结构非常规范,便于调试,方便类型安全;

(2)缺点:

需要写更多的类型相关代码,导致不便于阅读、不清晰明了。动态类型语言的优点在于方便阅读,不需要写非常多的类型相关的代码;缺点自然就是不方便调试,命名不规范时会造成读不懂,不利于理解等。

3、《Java核心编程》中探索~~为什么Java可以称之为"准动态语言"?

体现在以下几个方面:

1.反射机制

2.动态编译

3.动态执行javascript代码

4.动态字节码操作

5.动态转换类型

Java的反射机制被视为Java为准动态语言的主要的一个关键性质,这个机制允许程序在运行时透过反射取得任何一个已知名称的class的内部信息,包括:

正在运行中的类的属性信息,正在运行中的类的方法信息,正在运行中的类的构造信息,正在运行中的类的访问修饰符,注解等等。

动态语言无时不刻在体现动态性,而静态语言也在通过其他方法来趋近于去弥补静态语言的缺陷。

二、了解ClassLoader

ClassLoader是类加载器,具体的作用就是将class文件加载到jvm虚拟机中,程序就可以正常运行了。但是,JVM启动的时候,并不会一次性的加载所有的class文件,而是根据需要动态的去加载。如果一次性加载全部的jar包,内存肯定会扛不住。
###1、ClassLoader
所有类加载器的基类,它是抽象的,定义了类加载最核心的操作。所有继承classloader的加载器,都会先判断是否被父类加载器加载过,防止多次加载,防止加载冲突。

2、Bootstrap classLoader

位于java.lang.classload,所有的classload都要经过这个classload判断是否已经被加载过,采用native code实现,是JVM的一部分,主要加载JVM自身工作需要的类,如java.lang.、java.uti.等; 这些类位于$JAVA_HOME/jre/lib/rt.jar。Bootstrap ClassLoader不继承自ClassLoader,因为它不是一个普通的Java类,底层由C++编写,已嵌入到了JVM内核当中,当JVM启动后,Bootstrap ClassLoader也随着启动,负责加载完核心类库后,并构造Extension ClassLoader和App ClassLoader类加载器。

3、URLClassLoader

继承自SecureClassLoader,支持从jar文件和文件夹中获取class,继承于classload,加载时首先去classload里判断是否由bootstrap classload加载过,1.7 新增实现closeable接口,实现在try 中自动释放资源,但捕获不了.close()异常。

4、AppClassLoader

应用类加载器,继承自URLClassLoader,也叫系统类加载器(ClassLoader.getSystemClassLoader()可得到它),它负载加载应用的classpath下的类,查找范围System.getProperty(“java.class.path”),通过-cp或-classpath指定的类都会被其加载,没有完全遵循双亲委派模型的,它重要的是loadClass方法。

三、双亲委派机制

主要体现在ClassLoader的loadClass()方法中,思路很简单:先检查是否已经被加载过,若没有加载则调用父类加载器的loadClass()方法,若父类加载器为空则默认使用启动类加载器作为父类加载器。如果父类加载器加载失败,抛出ClassNotFoundException异常后,调用自己的findClass()方法进行加载。

四、JavaCompiler动态编译

使用JavaCompiler进行动态编译。

//使用ToolProvider.getSystemJavaCompiler来得到一个JavaCompiler接口的实例。
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
//JavaCompiler中最核心的方法是run()。通过这个方法能编译java源代码。
int run(InputStream in, OutputStream out, OutputStream err, String... arguments)

参数分别用来为:

  1. java编译器提供参数
  2. 得到Java编译器的输出信息
  3. 接收编译器的错误信息
  4. 一个或多个Java源程式文件

如果run编译成功,返回 0。

如果前3个参数传入的是null,那么run方法将以标准的输入、输出代替,即System.in、System.out和System.err。如果我们要编译一个test.java文件,并将使用标准输入输出,run的使用方法如下:
int results = tool.run(null, null, null, "D:\\\\test\\\\Student.java");

五、通过URLClassLoader加载程序外的jar包,并进行动态编译

1、实体类Student

package com.guor.bean;
 
public class Student 
    public Integer id;
    public String name;
    
    public void hello(String param) 
        System.out.println(param);
    
 
    public Integer getId() 
        return id;
    
 
    public void setId(Integer id) 
        this.id = id;
    
 
    public String getName() 
        return name;
    
 
    public void setName(String name) 
        this.name = name;
    
    
    @Override
    public String toString() 
        return "Student [id=" + id + ", name=" + name + "]";
    

2、Java文件 -> class -> jar -> 动态编辑 -> 反射赋值

private void test01() throws Exception 
        final String javaPath = "D:\\\\test\\\\java";
        final String studentPath = javaPath + "\\\\Student.java";
        final String jarPath = "D:\\\\test\\\\jar\\\\student-1.0.0.jar";
        final String packageStudentPath = "com.guor.bean.Student";
        // 将java源文件编译成.class字节码文件
        String cmd = "javac " + studentPath;
        System.out.println(cmd);
        boolean execSysCmd = execCmd(cmd);
        System.out.println(execSysCmd);
 
        // 打成jar包
        cmd = "jar -cvf " + jarPath + " " + javaPath;
        System.out.println(cmd);
        execSysCmd = execCmd(cmd);
        System.out.println(execSysCmd);
 
        /**
         *  URLClassLoader:继承自SecureClassLoader,支持从jar文件和文件夹中获取class,
         *  继承于classload,加载时首先去classload里判断是否由bootstrap classload加载过
         */
        URL url = new URL("file:" + jarPath);
        URLClassLoader classLoader = new URLClassLoader(new URL[]  url ,
                Thread.currentThread().getContextClassLoader());
        CusCompiler compiler = new CusCompiler(classLoader);
 
        File file = new File(studentPath);
        String beanTxt = FileUtils.readFileToString(file, "utf-8");
 
        Map<String, byte[]> results = compiler.compile(packageStudentPath, beanTxt);
        Class<?> clazz = compiler.loadClass(packageStudentPath, results);
 
        Object object = clazz.newInstance();
        Method method = clazz.getDeclaredMethod("setId", Integer.class);
        method.invoke(object, 1);
        
        method = clazz.getDeclaredMethod("setName", String.class);
        method.invoke(object, "哪吒");
        
        System.out.println(object);
        
        method = clazz.getDeclaredMethod("hello", String.class);
        method.invoke(object, "我命由我不由天");
    

3、执行cmd命令

private boolean execCmd(String cmd) 
    try 
        Runtime rt = Runtime.getRuntime();
        Process proc = rt.exec(cmd);
        InputStream es = proc.getErrorStream();
        String line;
        BufferedReader br;
        br = new BufferedReader(new InputStreamReader(es, "GBK"));
        StringBuffer buffer=new StringBuffer();
        while ((line = br.readLine()) != null) 
            buffer.append(line+"\\n");
        
     catch (Exception e) 
        return false;
    
    return true;

六、编译非文件形式源代码

1、通过JavaCompiler动态编译

public static void test() 
    try 
        String beanName = "Student";
        String appendTxt = "private String realName";
        AppendBeanUtil.appendBean(beanName, appendTxt);
 
        //动态编译
        JavaCompiler javac = ToolProvider.getSystemJavaCompiler();
        String className = "D:\\\\workspace\\\\yygh_parent\\\\myTest\\\\src\\\\main\\\\java\\\\com\\\\guor\\\\bean\\\\Student.java";
        int status = javac.run(null, null, null, "-d", System.getProperty("user.dir")+"\\\\target\\\\classes",className);
        if(status!=0)
            System.out.println("没有编译成功!");
        
 
        ClassLoader classLoader = Student.class.getClassLoader();
        Class<?> classLoad = classLoader.loadClass(Student.class.getName());
 
        Object newInstance = classLoad.newInstance();
        Method setName = classLoad.getDeclaredMethod("setName", String.class);
        setName.invoke(newInstance,"哪吒");
 
        System.out.println("动态载入属性成功+++++"+newInstance);
     catch (Exception e) 
        System.out.println(e);
    

2、springboot中动态编译工程内存在的bean

JDK 6 的编译器 API 的另外一个强大之处在于,它可以编译的源文件的形式并不局限于文本文件。JavaCompiler 类依靠文件管理服务可以编译多种形式的源文件。比如直接由内存中的字符串构造的文件,或者是从数据库中取出的文件。这种服务是由 JavaFileManager 类提供的。

在Java SE6中最佳的方法是使用StandardJavaFileManager类。这个类能非常好地控制输入、输出,并且能通过DiagnosticListener得到诊断信息,而DiagnosticCollector类就是listener的实现。新的 JDK 定义了 javax.tools.FileObject 和 javax.tools.JavaFileObject 接口。任何类,只要实现了这个接口,就可以被 JavaFileManager 识别。

使用StandardJavaFileManager步骤:

  1. 建立一个DiagnosticCollector实例
  2. 通过JavaCompiler.getStandardFileManager()方法得到一个StandardFileManager对象。
  3. 使用StandardFileManager获取需要编译的源代码。从文件或者字符流中获取源代码。
  4. JavaCompiler.getTask()生成编译任务抽象。
  5. 通过CompilationTask.call()方法编译源代码。
  6. 关闭StandardFileManager。

3、动态编译工程内存在的bean

private void test02() 
    try 
        String beanName = "Student";
        String appendTxt = "private String realName;";
        AppendBeanUtil.appendBean(beanName, appendTxt);
 
        String class_name = "com.guor.bean.Student";
        URLClassLoader contextClassLoader = (URLClassLoader) Thread.currentThread().getContextClassLoader();
        CusCompiler compiler = new CusCompiler(contextClassLoader);
 
        String script = FileUtils.readFileToString(new File("D:\\\\workspace\\\\yygh_parent\\\\myTest\\\\src\\\\main\\\\java\\\\com\\\\guor\\\\bean\\\\Student.java"),"utf-8");
 
        Map<String, byte[]> results = compiler.compile(class_name, script);
        Class<?> clazz = compiler.loadClass(class_name, results);
        System.out.println("+++++++"+clazz);
 
        Object newInstance = clazz.newInstance();
        Method setName = clazz.getDeclaredMethod("setRealName", String.class);
        setName.invoke(newInstance,"哪吒");
 
        System.out.println("动态载入属性成功+++++"+newInstance);
     catch (Exception e) 
        System.out.println(e);
    

七、给大家推荐一本Java经典书籍

《Java核心编程》,国外广受赞誉的Java经典教科书,畅销十余年,版本三次升级,好评如潮。

1、作者简介

John Dean,帕克大学计算机科学与信息系统系的副教授,拥有堪萨斯大学的计算机科学硕士学位和诺瓦东南大学的计算机科学博士学位,拥有Java认证,曾在业界担任软件工程师和项目经理,专门研究Java和各种web技术。
Raymond Dean,堪萨斯大学电气工程系和计算机科学系的名誉教授,拥有麻省理工学院的硕士学位和普利斯顿大学的博士学位。他同时也是暖通空调行业的专业工程师,编写过空气分配系统和建筑系统中能耗与声音传播的计算机程序。在堪萨斯大学,他教授微处理程序、数据结构以及电气工程和计算科学的课程。
这是一本优秀的Java技术图书,它以一种容易理解的语言介绍了Java语言的概念、算法设计和编程方法,这对于初学者学习和加强Java语言和面向对象的设计原则是非常好的。它从各个层次详细分析了Java的各个组成技术,并且利用大量的示例进行了技术的分析,不仅可以作为技术学习的图书使用,同时也可以作为工具字典使用。

2、本书适合谁?

  1. 适合在校大学生作为教程使用
  2. 适合有一些编程经验并想学习Java的行业从业者
  3. 适合各类Java自学者
  4. 高中阶段的计算机科学的预科生也推荐学习本书

3、本书特色

(1)解决问题

本书通过强调算法开发和程序设计两个关键元素讲解程序化地解决问题的方法。

(2)基本原理优先

本书将需要复杂语法的概念延后,为了高效地编写代码,通过程序示例代码追踪与说明来确保彻底理解代码

(3)贴近现实

本书通过上手实践与贴近现实的方式来学习Java编程,因而引入了以下资源:编译工具、完整的程序示例源代码、程序设计中的实践指导、基于行业标准的代码风格指南、用于类关系图的统一建模语言(Unified Modeling Language,UML)、分配的实践性家庭作业。

本书在撰写过程中,引导读者去了解三种重要的编程方法:结构化编程、OOP和事件驱动编程。
为激励读者学习有趣的Java编程,本书在一些章末穿插GUI跟踪部分,大多数章末部分的内容是使用GUI代码来完成此章前面部分展示的非GUI内容。

4、本书组织

在撰写本书的过程中,引导读者去了解三种重要的编程方法:结构化编程、OOP和事件驱动编程。
我们提倡的内容和顺序使学生能够在编程基本原理的坚实基础上发展他们的技能,这也是一名熟练的OOP程序员的最高效的顺序。为培养这种基本原理优先的方法,本教材从一组最少的概念和细节开始,逐步扩展概念并添加细节,把相对不是很重要的细节延后到之后的章节中,以避免前面的章节负担过重。

5、推荐:Java核心编程从问题分析到代码实现(第3版)(上下册)

上一篇:Java学习路线总结,搬砖工逆袭Java架构师

下一篇:Java基础教程系列

以上是关于Java是动态语言吗?从《Java核心编程》探索真知的主要内容,如果未能解决你的问题,请参考以下文章

学java应该从哪里开始?

深入浅出Java并发编程指南「难点 - 核心 - 遗漏」让我们一起探索一下CountDownLatch的技术原理和源码分析

Java核心技术动态代理的原理

Java核心技术动态代理的原理

深入浅出Java并发编程指南「难点 - 核心 - 遗漏」让我们一起探索一下CompletionService的技术原理和使用指南

深入浅出Java并发编程指南「难点 - 核心 - 遗漏」让我们一起探索一下CyclicBarrier的技术原理和源码分析