同一个JVM中使用不同jar包下完全相同的类

背景:在引入第三方jar包以后,偶尔会遇到不同jar包中的类冲突。这里所说的冲突,是指类的包名和类型完全相同(有的时候希望同时使用相同类的不同版本)。

参考地址:https://baijiahao.baidu.com/s?id=1636309817155065432&wfr=spider&for=pc

处理思路:见上图,使用原生的类加载是实现不了这个功能的,需要使用自定义类加载器,分别从不同jar中或者目录加载class文件,然后进行实例化,最后使用反射来调用(因Class动态变化,所以需要反射机制)

代码实现:

package com.mp.util;
 
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
 
public class MyClassloader extends ClassLoader {
 
  private String libPath;
 
  public MyClassloader(String path) {
    libPath = path;
  }
 
  @Override
  public Class<?> findClass(String name) throws ClassNotFoundException {
    if (libPath.endsWith(".zip") || libPath.endsWith(".jar")) {
      try {
        return this.loadClassFromJarOrZip(name);
      } catch (MalformedURLException e) {
        e.printStackTrace();
      }
    }
    String fileName = getFileName(name);
    File file = new File(libPath, fileName);
    try (FileInputStream is = new FileInputStream(file);
        ByteArrayOutputStream bos = new ByteArrayOutputStream()) {
      int len = 0;
      while ((len = is.read()) != -1) {
        bos.write(len);
      }
      byte[] data = bos.toByteArray();
      return defineClass(name, data, 0, data.length);
    } catch (IOException e) {
      e.printStackTrace();
    }
    return super.findClass(name);
  }
 
  // 获取要加载 的class文件名
  private String getFileName(String name) {
    int index = name.lastIndexOf(".");
    if (index > 0) {
      return name.substring(index + 1) + ".class";
    } else {
      return name + ".class";
    }
  }
 
  private Class<?> loadClassFromJarOrZip(String className)
      throws MalformedURLException, ClassNotFoundException {
    File file = new File(libPath);
    URL url = file.toURI().toURL();
    URLClassLoader classLoader = null;
    try {
      classLoader =
          new URLClassLoader(new URL[] {url}, Thread.currentThread().getContextClassLoader());
      return classLoader.loadClass(className);
    } finally {
      if (classLoader != null) {
        try {
          classLoader.close();
        } catch (IOException e) {
          // pass
        }
      }
    }
  }
}
package com.mp.util;
 
import java.lang.reflect.Method;
 
public class ClassLoaderTest {
  public static void main(String[] args) throws ClassNotFoundException {
    System.out.println("使用目录加载class");
    call("/tmp/t1", "basic.javastd.HelloWorld", "printVersion");
    call("/tmp/t2", "basic.javastd.HelloWorld", "printVersion");
    System.out.println("使用jar加载class");
    call("/tmp/t1/javastd-0.0.1-SNAPSHOT.jar", "basic.javastd.HelloWorld", "printVersion");
    call("/tmp/t2/javastd-0.0.1-SNAPSHOT.jar", "basic.javastd.HelloWorld", "printVersion");
  }
 
  public static void call(String classFilePath, String className, String methodName) {
    ClassLoader loader = new MyClassloader(classFilePath);
    try {
      Class<?> loadClass = loader.loadClass(className);
      Method declaredMethod = loadClass.getDeclaredMethod(methodName);
      declaredMethod.invoke(loadClass.newInstance());
    } catch (Exception e) {
      e.printStackTrace();
    }
  }
}

输出:

使用目录加载class

v1 static code

version 1

v2 static code

version 2

使用jar加载class

v1 static code

version 1

v2 static code

version 2

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
Jocky混淆JAVA代码(保护你的JAVA项目) 一、前言 1.1 什么是Jocky? 我们知道,Java是一种跨平台的编程语言,其源码(.java文件)被编译成与平台无关的字节码(.class文件),然后在运行期动态链接。这样,编译后的文件含有符号表,从而使得Java程序很容易被反编译。相信每一个Java开发人员,都曾经用过诸如Jad之的反编译器,对Java的class 文件进行反编译,从而观察程序的结构与实现细节。如此一来,对于那些需要严格进行知识产权保护的Java应用,如何有效的保护客户的商业投资,是开发人员经常需要面对的问题。 于是就出现了Java混淆编译器,它的作用是打乱class文件的符号信息,从而使反向工程变得非常困难。 Jocky就是这样一款优秀的Java混淆编译器。 1.2 为什么需要Jocky? 目前业界有不少商业的甚或是开源的混淆编译器,但它们普遍存在一些这样或者那样的问题。一般而言,现有的混淆器都是对编译好的 class文件进行混淆,这样就需要编译和混淆两个步骤。而事实上,并不是所有的符号都需要混淆。如果你开发的是一个库,或者某些需要动态装载,那些公共API(或者说:那些被publish出来的API)就必须保留符号不变,只有这样,别人才能使用你的库。现有的混淆器提供了GUI或脚本的方式来对那些需要保留的符号名称进行配置,但如果程序较大时,配置工作将变得很复杂,而程序一旦修改,配置工作又要重新进行。某些混淆器能够调整字节码的顺序,使反编译更加困难,但笔者经历过混淆之后的程序运行出错的情况。 而Jocky与其它混淆编译器最大的不同之处在于:它是直接从源码上做文章,也就是说编译过程本身就是一个混淆过程。 1.3 Jocky是如何工作的? Jocky混淆编译器是在Sun JDK提供的Java编译器(javac)的基础上完成的,修改了其的代码生成过程,对编译器生成的间代码进行混淆,最后再生成class文件,这样编译和混淆只需要一个步骤就可以完成。另外可以在源程序插入 符号保留指令 来控制哪些符号需要保留,将混淆过程与开发过程融合在一起,不需要单独的配置。 1.4 Jocky的作用 1.4.1代码混淆 如前文所述,混淆编译是Jocky的首要用途。我们举一个最简单的例子,下面的SimpleBean是未经混淆的class文件通过Jad反编译以后获得的源文件: 1 public class SimpleBean implements Serializable { 2 3 private String name = "myname"; 4 5 private List myList = null; 6 7 public void SimpleBean() { 8 myList = new ArrayList(10); 9 } 10 11 public void foo1() { 12 myList.add("name"); 13 } 14 15 private void foo2() { 16 } 17 18 private void writeObject(java.io.ObjectOutputStream out) 19 throws IOException { 20 21 } 22 23 } 下面是经Jocky混淆过的文件,通过Jad反编译后产生的源文件: 1 public class SimpleBean implements Serializable { 2 3 private String _$2; 4 5 private List _$1; 6 7 public SimpleBean() { 8 _$2 = "myname"; 9 this; 10 JVM INSTR new #4 ; 11 JVM INSTR dup ; 12 JVM INSTR swap ; 13 10; 14 ArrayList(); 15 _$1; 16 } 17 public void foo1() { 18 _$1.add("name"); 19 } 20 21 private void _$1() { 22
基本数据型的 •八大数据型的分别为:Byte、Short、Integer、Long、Character、 Float、Double、Boolean。 把基本数据型变量实例是通过对应的构造器来实现的,不仅如此,8个除了 Character之外,还可以通过传入一个字符串参数来构建对象。 •如果希望获得对象装的基本型变量,则可以使用提供的XxxValue()实例方法。 自动装箱与自动拆箱 •JDk还提供了自动装箱和自动拆箱。自动装箱就是把一个基本型的变量直接赋给对应的变量,自动拆箱 则与之相反。 •还可以实现基本型变量和字符串之间的转换,除了Character之外的所有都提供了一个 parseXxx(String s)静态方法。 •如果将基本型转换为这符串,只需在后面加+ “”进行连接运算。 Java 7对的增强 •Java 7为所有增加一个新方法: compare(x , y)的方法。该方法用于比较两个实例,当x>y, 返回大于0的数;当x==y,返回0;否则返回小于0的数。 对象的方法 •打印对象和toString方法:toString方法是系统将会输出该对象的“自我描述”信息,用以告诉外界对象具有的状 态信息。 •Object 提供的toString方法总是返回该对象实现名 + @ +hashCode值。 •==和equals比较运算符:==要求两个引用变量指向同一个对象才会返回true。equals方法则允许用户提供自 定义的相等规则。 •Object提供的equals方法判断两个对象相等的标准与==完全相同。因此开发者通常需要重写equals方法。 成员 •在java里只能含Field,方法,构造器,初始化块,内部(接口、枚举)等5种成员。 用static修饰的成员属 于成员,Field既可通过来访问,也可以通过的对象来访问。当通过对象来访问属性时,系统会在底 层转换为通过该来访问 属性。 成员规则 •成员并不是属于实例,它是属于本身的。只要存在,成员就存在。 •即使通过null对象来访问成员,程序也不会引发NullPointerException。   成员不能访问实例成员。 单例 •如果一个始终只能创建一个对象,称为单例。须符合以下几个条件:   –1.我们把该的构造器使用Private修饰,从而把该 的所有构造器隐藏起来。   –2.则需要提供一个public方法作为该的访问点,用于创建该的对象,且必须使用static修饰   –3.该还必须缓存已经创建的对象,必须用static修饰 final变量 •final修饰变量时,表示该变量一旦获得 初始值之后就不可被改变。 •final既可修饰成员变量,也可以修饰局部变量。 final修饰成员变量 •成员变量是随的初始化或对象初始化而初始化的。final修饰的成员变量必须由程序员指定初始值。 •对于属性而言,要么在静态初始化初始化,要么在声明该属性时初始化。 •对于实例属性,要么在普通初始化块指定初始值。要么在定义时、或构造器指定初始值。 final修饰局部变量 •使用final修饰局部变量时既可以在定义时指定默认值,也可以不指定默认值。 •给局部变量赋初始值,只能一次,不能重复。 final修饰基本型和引用型 •当使用final修饰基本数据型时,不能对其重新赋值,不能被改变。 •但对引用型的变量而言,它仅仅保存的是一个引用,final只能保证他的地址不变,但不能保证对象,所以引用 型完全可以改变他的对象。 可执行“宏替换”的final变量 •对一个final变量来说,不管它是变量、实例变量,还是局部变量,只要该变量满足3个条件,这个final变量就 不再是一个变量,而是相当于一个直接量。   –使用final修饰符修饰;   –在定义该final变量时指定了初始值;   –该初始值可以在编译时就被确定下来。 final方法 •final方法 •final 修饰的方法不可以被重写。 •final 修饰的方法仅仅是不能重写,但它完全可以被重载。 •final 修饰的不可以被继承 不可变的 •不可变的要满足以下几个条件:   –1.使用private和final修饰符来修饰该的属性   –2.提供带参数的构造器,用于根据传入参数来初始化里的属性   –3.仅为该的属性提供getter方法,不要为该的属性提供setter方法,因为普通方法无法修改final修饰的 属性   –4.如有必要,重写ObjecthashCode 和equals •缓存实例的不可变:如果程序经常需要使用不可变的实例,则可对实例进行缓存。 抽象方法和抽象 •抽象方法和都必须使用abstract来修饰,有抽象方法的只能定义成抽象,抽象里也可以没有抽象方法。 • 抽象不能被实例化,可以通过其子给他赋值,普通里有的抽象里也有,定义抽象方法只需在普通方法上增 加abstract修饰符,并把普通方法的方法体(也就是方法后花括号括起来的部分)全部去掉,并在方法后增加分号 即可。 抽象的特征 •抽象的特征:有得有失,得到了新能力,可以拥有抽象方法;失去了创建对象的能力。 抽象的作用 •抽象代表了一种未完成的设计,它体现的是一种模板。 •抽象与模板模式。 接口的概念 •接口定义的是多个共同的行为规范,这些行为是与外部交流的通道,这就意味着接口里通常是定义一组公用的 方法。 •接口体现了规范与实现分离的设计。 接口的定义 •和定义不同,定义接口不再用class关键字,而是使用interface关键字。语法如下: •[修饰符] interface接口名 extends 父接口1,父接口2 ... •{ • 零个到多个常量定义... • 零个到多个抽象方法定义... • 零个到多个内部、接口、枚举定义... • 零个到多个默认方法或方法定义... •} 接口里的成分 •在定义接口时,接口里可以含成员变量(只能是常量),方法(只能是抽象实例方法、方法或默认方法),内 部括内部接口、枚举   –常量都是:public static final修饰   –方法都是:public abstract 修饰   –内部的:public static 接口的继承 •接口的继承和继承不一样,接口完全支持多继承,子接口扩展某个父接口将会获得父接口的所有抽象方法,常 量属性,内部和枚举定义。 使用接口 •接口可以用于声明引用型的变量,但接口不能用于创建实例。 •当使用接口来声明引用型的变量时,这个引用型的变量必须引用到其实现的对象。 •一个可以实现一个或多个接口,继承使用extends关键字,实现接口则使用implements关键字。 实现接口 •一个实现了一个或多个接口之后,这个必须完全实现这些接口里所定义的全部抽象方法(也就是重写这些抽 象方法); •否则,该将保留从父接口那里继承到的抽象方法,该也必须定义成抽象。 接口和抽象的相似性 •接口和抽象都不能被实例化,它们都位于继承树的顶端,用于被其他实现和继承。 •接口和抽象都可以含抽象方法,实现接口或继承抽象的普通子都必须实现这些抽象方法。 接口与抽象的区别 •接口里只能含抽象方法,不同含已经提供实现的方法;抽象则完全可以含普通方法。 •接口里不能定义静态方法;抽象里可以定义静态方法。 •接口里只能定义静态常量属性,不能定义普通属性;抽象里则既可以定义普通属性,也可以定义静态常量属 性。 •接口不含构造器;抽象里可以含构造器,抽象里的构造器并不是用于创建对象,而让其子调用这些构 造器来完成属于抽象的初始化操作。 •接口里不能含初始化块,但抽象则完全可以含初始化块。 •一个最多只能有一个直接父括抽象;但一个可以直接实现多个接口,通过实现多个接口可以弥补 Java单继承的不足。 面向接口编程 •接口体现了规范与实现分离的原则。充分利用接口可以很好地提高系统的可扩展性和可维护性。 •接口与简单工厂模式、命令模式等。 内部 •我们把一个放在另一个的内部定义,这个定义在其他内部的就被称为内部,有的也叫嵌套含内   部也被称为外部有的也叫宿住。 •内部提供了更好的封装,内部成员可以直接访问外部的私有数据,因为内部被当成其他外部成员。 •匿名内部适合用于创建那些仅需要一次使用。 非静态内部 •定义内部非常简单,只要把一个放在另一个内部定义即可。 •当在非静态内部的方法内访问某个变量时,系统优先在该方法内查找是否存在该名字的局部变量,如果存在该 名字的局部变量,就使用该变量,如果不存在,则到该方法所在的内部查找是否存在该名字的属性,如果存在 则使用该属性。 •总之,第一步先找局部变量,第二步,内部的属性,第三步。外部的属性。 本文原创作者:pipi-changing 本文原创出处:http://www.cnblogs.com/pipi-changing/ 静态内部 •如果用static修饰一个内部,称为静态内部。 •静态内部可以含静态成员,也可以含非静态成员。所以静态内部不能访问外部的实例成员,只能访问   外部成员。 •静态内部的对象寄存在外部里,非静态内部的对象寄存在外部实例里 使用内部 •1.在外部内部使用内部-不要在外部的静态成员使用非静态内部,因为静态成员不能访问非静态成 员。 • 2.在外部以外使用非静态内部。   –private 修饰的内部只能在外部内部使用。   –在外部以外的地方使用内部,内部完整的名应该OuterClass.InnerClass.   –在外部以外的地方使用非静态内部创建对象的语法如下:OuterInstance.new InnerConstructor()   –在外部以外的地方使用静态内部创建对象的语法如下:new OuterClass.InnerConstructer(); 局部内部 •如果把一个内部放在方法里定义,这就是局部内部,仅仅在这个方法里有效。 •局部内部不能在外部以外的地方使用,那么局部内部也不能使用访问控制符和static修饰 匿名内部 •匿名内部适合创建那种只需要一次使用,定义匿名内部的语法格式如下: •new 父构造器(实例列表) |实现接口) •{ • //匿名内部体部分 •} •匿名内部不能是抽象,匿名内部不能定义构造器。 Lambda表达式入门 •Lambda表达式主要作用就是代替匿名内部的繁琐语法。它由三部分组成:   –形参列表。形参列表允许省略形参型。如果形参列表只有一个参数,甚至连形参列表的圆括号也可以省略。   –箭头(->),必须通过英文等号和大于符号组成。   –代码块。如果代码块只有含一条语句,Lambda表达式允许省略代码块的花括号,如果省略了代码块的花括 号,这条语句不要用花括号表示语句结束。Lambda代码块只有一条return语句,甚至可以省略return关键字。 Lambda表达式需要返回值,而它的代码块仅有一条省略了return的语句,Lambda表达式会自动返回这条语句的 值。 Lambda表达式与函数式接口 •如果采用匿名内部语法来创建函数式接口的实例,只要实现一个抽象方法即可,在这种情况下即可采用 Lambda表达式来创建对象,该表达式创建出来的对象的目标型就是这个函数式接口。 •Lambda表达式有如下两个限制:   –Lambda表达式的目标型必须是明确的函数式接口。   –Lambda表达式只能为函数式接口创建对象。Lambda表达式只能实现一个方法,因此它只能为只有一个抽 象方法的接口(函数式接口)创建对象。 •为了保证Lambda表达式的目标型是一个明确的函数式接口,可以有如下三种常见方式:   –将Lambda表达式赋值给函数式接口型的变量。   –将Lambda表达式作为函数式接口型的参数传给某个方法。   –使用函数式接口对Lambda表达式进行强制型转换。 方法引用与构造器引用 种 示例 说明 对应的Lambda表达式 引用方法 名::方法 函数式接口被实现方法的全部参数传给该方法作为参数。 (a,b,...) -> 名.方法(a,b, ...) 引用特定对象的实例方法 特定对象::实例方法 函数式接口被实现方法的全部参数传给该方法作为参数。 (a,b, ...) -> 特定对象.实例方法(a,b, ...) 引用某对象的实例方法 名::实例方法 函数式接口被实现方法的第一个参数作为调用者,后面的参数全部传给该方法作为参数。 (a,b, ...) ->a.实例方法(b, ...) 引用构造器 名::new 函数式接口被实现方法的全部参数传给该构造器作为参数。 (a,b, ...) ->new 名(a,b, ...) Lambda表达式与匿名内部 •Lambda表达式与匿名内部存在如下相同点:   –Lambda表达式与匿名内部一样,都可以直接访问“effectively final”的局部变量,以及外部的成员变 量(括实例变量和变量)。   –Lambda表达式创建的对象与匿名内部生成的对象一样, 都可以直接调用从接口继承得到的默认方法。 •Lambda表达式与匿名内部主要存在如下区别:   –匿名内部可以为任意接口创建实例——不管接口含多少个抽象方法,只要匿名内部实现所有的抽象方 法即可。但Lambda表达式只能为函数式接口创建实例。   –匿名内部可以为抽象、甚至普通创建实例,但Lambda表达式只能为函数式接口创建实例。   –匿名内部实现的抽象方法的方法体允许调用接口定义的默认方法;但Lambda表达式的代码块不允许调 用接口定义的默认方法。 手动实现枚举 •可以采用如下设计方式:   –通过private将构造器隐藏起来。   –把这个的所有可能实例都使用public static final属性来保存。   –如果有必要,可以提供一些静态方法,允许其他程序根据特定参数来获取与之匹配实例。 JDK 5新增的枚举支持 •J2SE1.5新增了一个enum关键字,用以定义枚举。正如前面看到,枚举是一种特殊的,它一样可以有自 己的方法和属性,可以实现一个或者多个接口,也可以定义自己的构造器。一个Java源文件最多只能定义一个 public访问权限的枚举,且该Java源文件也必须和该枚举名相同。 枚举 •枚举可以实现一个或多个接口,使用enum定义的枚举默认继承了java.lang.Enum,而不是继承Object 。其java.lang.Enum实现了java.lang.Serializable和java.lang. Comparable两个接口。 •枚举的构造器只能使用private访问控制符,如果省略了其构造器的访问控制符,则默认使用private修饰;如 果强制指定访问控制符,则只能指定private修饰符。 •枚举的所有实例必须在枚举显式列出,否则这个枚举将永远都不能产生实例。列出这些实例时系统会自 动添加public static final修饰,无需程序员显式添加。 •所有枚举都提供了一个values方法,该方法可以很方便地遍历所有的枚举值。 枚举的属性、方法和构造器 •枚举也是一种,只是它是一种比较特殊的,因此它一样可以使用属性和方法。 •枚举通常应该设计成不可变,也就说它的属性值不应该允许改变,这样会更安全,而且代码更加简洁。为 此,我们应该将枚举的属性都使用private final 修饰。 •一旦为枚举显式定义了带参数的构造器,则列出枚举值时也必须对应地传入参数。 实现接口的枚举 •枚举也可以实现一个或多个接口。与普通实现一个或多个接口完全一样,枚举实现一个或多个接口时,也 需要实现该接口所含的方法。 •如果需要每个枚举值在调用同一个方法时呈现出不同的行为方式,则可以让每个枚举值分别来实现该方法,每个 枚举值提供不同的实现方式,从而让不同枚举值调用同一个方法时具有不同的行为方式。 含抽象方法的枚举 •可以在枚举里定义一个抽象方法,然后把这个抽象方法交给各枚举值去实现即可。 •枚举里定义抽象方法时无需显式使用abstract关键字将枚举定义成抽象,但因为枚举需要显式创建枚举 值,而不是作为父,所以定义每个枚举值时必须为抽象方法提供实现,否则将出现编译错误。 垃圾回收机制 •垃圾回收机制只负责回收堆内存对象,不会回收任何任何物理资源(例如数据库连接,网络IO等资源)。 •程序无法精确控制垃圾回收的运行,垃圾回收会在合适时候进行垃圾回收。当对象永久性地失去引用后,系统就 会在合适时候回收它所占的内存。 •垃圾回收机制回收任何对象之前,总会先调用它的finalize方法,该方法可能使该对象重新复活(让一个引用变量 重新引用该对象),从而导致垃圾回收机制取消回收 对象在内存的状态 •激活状态:当一个对象被创建后,有一个以上的引用变量引用它。则这个对象在程序处于激活状态,程序可通 过引用变量来调用该对象的属性和方法。 •去活状态:如果程序某个对象不再有任何引用变量引用它,它就进入了去活状态。在这个状态下,系统的垃圾 回收机制准备回收该对象所占用的内存,在回收该对象之前,系统会调用所有去活状态对象的finalize方法进行资 源清理,如果系统在调用finalize方法重新让一个引用变量引用该对象,则这个对象会再次变为激活状态;否则该 对象将进入死亡状态。 •死亡状态:当对象与所有引用变量的关联都被切断,且系统会调用所有对象的finalize方法依然没有使该对象变成 激活状态,那这个对象将永久性地失去引用,最后变成死亡状态。只有当一个对象处于死亡状态时,系统才会真正 回收该对象所占有的资源。 强制垃圾回收 •强制系统垃圾回收有如下两个方法:   –调用System的gc()静态方法:System.gc()   –调用Runtime对象的gc()实例方法:Runtime.getRuntime().gc() finalize方法 •finalize方法有如下四个特点:   –永远不要主动调用某个对象的finalize方法,该方法应交给垃圾回收机制调用。   –finalize方法的何时被调用,是否被调用具有不确定性。不要把finalize方法当成一定会被执行的方法。   –当JVM执行去活对象的finalize方法时,可能使该对象,或系统其他对象重新变成激活状态。   –当JVM执行finalize方法时出现了异常,垃圾回收机制不会报告异常,程序继续执行。 对象的软、弱和虚引用 •强引用(StrongReference) •软引用-软引用需要通过SoftReference来实现,当一个对象只具有软引用时,它有可能被垃圾回收机制回 收。 •弱引用-弱引用通过WeakReference实现,弱引用和软引用很像,但弱引用的引用级别更低。对于只有弱引 用的对象而言,当系统垃圾回收机制运行时,不管系统内存是否足够,总会回收该对象所占用的内存。 •虚引用-虚引用通过PhantomReference实现,虚应用完全似于没有引用。虚引用对对象本身没有太大影 响,对象甚至感觉不到虚引用的存在。 修饰符的适用范围 顶层/接口 成员属性 方法 构造器 初始化块 成员内部 局部成员 public √ √ √ √ √ protected √ √ √ √ 访问控制符 √ √ √ √ ○ √ ○ private √ √ √ √ abstract √ √ √ final √ √ √ √ √ static √ √ √ √ strictfp √ √ √ synchronized √ native √ transient √ volatile √ 使用JAR文件的好处 •1.安全 •2.加快下载速度 •3.压缩 •4.封装 •5.可移植性 jar命令详解 •-c 创建新文档,-t 列出存档内容的列表, -x 展开存档的命名文件 •-u 更新已存在的存档,-v生成详细输出到标准输出上 •-f 指定存档文件名,-m 含 来自标文件的标明信息 •-o 只存储方式:未用ZIP压缩格式 •-m 不产生所有项的清单文件,- I 为指定的jar文件产生索引信息 •-c 改变到指定的目录, 创建可执行的JAR包 •1.使用平台相关的编译器将整个应用编译成平台相关的可执行性文件 •2.为整个应用编辑一个批处理文件 关于JAR包的技巧 •相当于一个压缩文件。 •可使用WinRAR来压缩JAR包。 •也可使用WinRAR来查看JAR包。 现在贴出代码: AutoBoxingUnboxing Primitive2String UnsignedTest WrapperClassCompare EqualTest Person OverrideEqualsRight PrintObject StringCompareTest ToStringTest NullAccessStatic Singleton Address CacheImmutaleTest FinalErrorTest FinalLocalTest FinalLocalVariableTest FinalMethodTest FinalReferenceTest FinalReplaceTest FinalVariableTest ImmutableStringTest IntegerCacheTest Person Sub extends PrivateFinalMethodTest StringJoinTest CarSpeedMeter Circle extends Shape abstract class Shape SpeedMeter Triangle 复制代码 public class AddCommand implements Command { public void process(int[] target) { int sum = 0; for (int tmp : target) { sum += tmp; } System.out.println("数组元素的总和是:" + sum); } } **************************************************** public class BetterPrinter implements Output { private String[] printData = new String[MAX_CACHE_LINE * 2]; // 用以记录当前需打印的作业数 private int dataNum = 0; public void out() { // 只要还有作业,继续打印 while (dataNum > 0) { System.out.println("高速打印机正在打印:" + printData[0]); // 把作业队列整体前移一位,并将剩下的作业数减1 System.arraycopy(printData, 1, printData, 0, --dataNum); } } public void getData(String msg) { if (dataNum >= MAX_CACHE_LINE * 2) { System.out.println("输出队列已满,添加失败"); } else { // 把打印数据添加到队列里,已保存数据的数量加1。 printData[dataNum++] = msg; } } } ************************************************ public interface Command { // 接口里定义的process()方法用于封装“处理行为” void process(int[] target); } ********************************************** public class CommandTest { public static void main(String[] args) { ProcessArray pa = new ProcessArray(); int[] target = { 3, -4, 6, 4 }; // 第一次处理数组,具体处理行为取决于PrintCommand pa.process(target, new PrintCommand()); System.out.println("------------------"); // 第二次处理数组,具体处理行为取决于AddCommand pa.process(target, new AddCommand()); } } ************************************************* public class Computer { private Output out; public Computer(Output out) { this.out = out; } // 定义一个模拟获取字符串输入的方法 public void keyIn(String msg) { out.getData(msg); } // 定义一个模拟打印的方法 public void print() { out.out(); } } ********************************************** interface interfaceA { int PROP_A = 5; void testA(); } interface interfaceB { int PROP_B = 6; void testB(); } interface interfaceC extends interfaceA, interfaceB { int PROP_C = 7; void testC(); } public class InterfaceExtendsTest { public static void main(String[] args) { System.out.println(interfaceC.PROP_A); System.out.println(interfaceC.PROP_B); System.out.println(interfaceC.PROP_C); } } ************************************************** public interface Output { // 接口里定义的成员变量只能是常量 int MAX_CACHE_LINE = 50; // 接口里定义的普通方法只能是public的抽象方法 void out(); void getData(String msg); // 在接口定义默认方法,需要使用default修饰 default void print(String... msgs) { for (String msg : msgs) { System.out.println(msg); } } // 在接口定义默认方法,需要使用default修饰 default void test() { System.out.println("默认的test()方法"); } // 在接口定义方法,需要使用static修饰 static String staticTest() { return "接口里的方法"; } } ********************************************** public class OutputFactory { public Output getOutput() { // return new Printer(); return new BetterPrinter(); } public static void main(String[] args) { OutputFactory of = new OutputFactory(); Computer c = new Computer(of.getOutput()); c.keyIn("轻量级Java EE企业应用实战"); c.keyIn("疯狂Java讲义"); c.print(); } } *********************************************** public class OutputFieldTest { public static void main(String[] args) { // 访问另一个的Output接口的MAX_CACHE_LINE System.out.println(lee.Output.MAX_CACHE_LINE); // 下面语句将引起"为final变量赋值"的编译异常 // lee.Output.MAX_CACHE_LINE = 20; // 使用接口来调用方法 System.out.println(lee.Output.staticTest()); } } ************************************************ public class PrintCommand implements Command { public void process(int[] target) { for (int tmp : target) { System.out.println("迭代输出目标数组的元素:" + tmp); } } } *********************************************** // 定义一个Product接口 interface Product { int getProduceTime(); } // 让Printer实现Output和Product接口 public class Printer implements Output, Product { private String[] printData = new String[MAX_CACHE_LINE]; // 用以记录当前需打印的作业数 private int dataNum = 0; public void out() { // 只要还有作业,继续打印 while (dataNum > 0) { System.out.println("打印机打印:" + printData[0]); // 把作业队列整体前移一位,并将剩下的作业数减1 System.arraycopy(printData, 1, printData, 0, --dataNum); } } public void getData(String msg) { if (dataNum >= MAX_CACHE_LINE) { System.out.println("输出队列已满,添加失败"); } else { // 把打印数据添加到队列里,已保存数据的数量加1。 printData[dataNum++] = msg; } } public int getProduceTime() { return 45; } public static void main(String[] args) { // 创建一个Printer对象,当成Output使用 Output o = new Printer(); o.getData("轻量级Java EE企业应用实战"); o.getData("疯狂Java讲义"); o.out(); o.getData("疯狂Android讲义"); o.getData("疯狂Ajax讲义"); o.out(); // 调用Output接口定义的默认方法 o.print("孙悟空", "猪八戒", "白骨精"); o.test(); // 创建一个Printer对象,当成Product使用 Product p = new Printer(); System.out.println(p.getProduceTime()); // 所有接口型的引用变量都可直接赋给Object型的变量 Object obj = p; } } ************************************************* public class ProcessArray { public void process(int[] target, Command cmd) { cmd.process(target); } } 复制代码 。。。。。。。。。。。。。。。。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

周易宅

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值