javaSE知识概括

286 阅读29分钟

@TOC

java类中各成员初始化的顺序

代码示例:

class Father
{
   static{
          System. out.println("父类静态代码块初始化" );
   }
   {
          System. out.println("父类代码块初始化" );
   }
   private static String s=print();
   public static String print()
   {
          System. out.println("父类静态方法" );
          return "父类静态成员变量的初始化" ;
   }
   public Father()
   {
          System. out.println("父类无参构造函数初始化完成" );
          show();
   }
   public void show()
   {
          System. out.println("父类show()方法" );
   }
}
class Son extends Father
{
   static{
          System. out.println("子类静态代码块初始化" );
   }
   {
          System. out.println("子类代码块初始化" );
   }
   private static  int i=1;
   private String s="子类私有成员变量" ;
   public void show()
   {
          System. out.println("子类show()方法:i=" +i);
   }
   public Son()
   {
          System. out.println("子类构造函数初始化完成" );
          show();
   }
}
public class TestClassLoadSeq {
        public static void main(String[] args)
       {
               new Son();
       }
  
}
  • 输出结果:
父类静态代码块初始化
父类静态方法
子类静态代码块初始化
父类代码块初始化
父类无参构造函数初始化完成
子类show()方法:i=1
子类代码块初始化
子类构造函数初始化完成
子类成员变量初始化完成:s=子类私有成员变量
子类show()方法:i=1

从结果可以看出类中各成员初始化的顺序是:

  • 如果类中存在继承关系(像 Son 继承 Father) 则首先会初始化导出类(Son)的基类(Father),然后再是导出类
  • 在基类首先会初始化静态 的东西 静态块>静态变量 而且只初始化一次 (因为静态的东西都是跟着类的加载而加载的)
  • 随后就是初始化导出类的静态东西 跟基类的静态初始化一样(同上)
  • 初始化基类无参构造器(调用有参就初始化有参构造器)
  • 初始化导出类无参构造器(注意:导出类的成员变量和代码块都是是比构造函数的初始化要早。。看输出结果可知)

在上面输出结果可能会看到在基类中的构造器中调用了show() 方法:

  • 这样的输出可能不奇怪,代码示例:
class Father
{
          private static String s= print();

   static{
          System. out.println("父类静态代码块初始化" );
   }
   {
          System. out.println("父类代码块初始化" );
   }
   public static String print()
   {
          System. out.println("父类静态方法" );
          return "父类静态成员变量的初始化" ;
   }
   public Father()
   {
          System. out.println("父类无参构造函数初始化完成" );
          show();
   }
   public void show()
   {
          System. out.println("父类show()方法" );
   }
}
class Son extends Father
{
   static{
          System. out.println("子类静态代码块初始化" );
   }
   {
          System. out.println("子类代码块初始化" );
   }
   private int i =1;
   private String s="子类私有成员变量" ;
   public void show()
   {
          System. out.println("子类show()方法:i=" +i);
   }
   public Son()
   {
          System. out.println("子类构造函数初始化完成" );
          System. out.println("子类成员变量初始化完成:s=" +s);
          show();
   }
}
public class TestClassLoadSeq {
        public static void main(String[] args)
       {
               new Son();
       }

}
  • 输出结果:
子类静态代码块初始化
父类代码块初始化
父类无参构造函数初始化完成
子类show()方法:i=0
子类代码块初始化
子类构造函数初始化完成
子类成员变量初始化完成:s=子类私有成员变量
子类show()方法:i=1
  • 可以看出跟上面的代码没有什么不同,唯一的不同就是我把导出类中的静态成员变量i 变成了成员变量i,可以看出结果是截然不同,现在的结果是:0
  • 原因:因为上面的静态成员变量是跟着类的加载而初始化的 所以结果是1 而下面的是成员变量又因为现在还在基类构造器中所以导出类中的成员变量还没有得到初始化 所以是0
  • 所以上面的初始化顺序不够完整,现在补全下: ①首先jvm加载类时 会对每个对象的默认初始化 为0,对通过组合方式的引用类型默认初始化为null

总结:

  • java中首先初始化基类在初始化导出类(有多少个基类初始化多少个)

创建对象的步骤: 在这里插入图片描述

hook钩子

简介:

  • Hook是Windows中提供的一种用以替换DOS下“中断”的系统机制,中文译为“挂钩”或“钩子”。在
  • 对特定的系统事件进行hook后,一旦发生已hook事件,对该事件进行hook的程序就会收到系统的通知,这时程序就能在第一时间对该事件做出响应。

Java中的hook:

  • 请问在Spring中,如果JVM异常终止,Spring是如何保证会释放掉占用的资源,比如说数据库连接等资源呢? 答:定义钩子方法。
  • 我们知道在Spring中定义销毁方法有两种方式: ①实现 DisposableBean 的 destroy 方法。使用@PreDestroy 注解修饰方法
@Component
public c1ass DatacollectBean implements DisposableBean {

	//第一种方法实现DisposableBean destroy方法·
	public void deatroy() throws Exception {
		System.err.print1n(“执行销毁方法”):
	}
	
	//第二种方法使用PreDestroy注解声明销毁方法
	@PreDestroy
	public void customerDestrOy(){
		system.err.printin( "执行自定义销毁方法");
	}
  • 那么在什么时候执行销毁方法? 在这里插入图片描述
  • 主动执行销毁bean
pablic static void main(String1 args){
	ConfigurableApplicationContext run = 
	SpringApplication.run(DemoApplication.class,args);
	DatacollectBean bean = run.getBean (DatacollectBean.class);
	//主动销毁bean
	run.getBeanraetory().dastroyBean(bean);
  • JVM关闭时候自动执行销毁方法。 这里就要用到钩子函数了, Spring 的钩子函数在 AbstractApplicationContext#shutdownHook属性。如果我们是SpringBoot项目我们看到在SpringApplication启动时候会注册一个钩子函数
private void refreshContext(ConfigurableApplicationContext context){
	refresh(context):
	if(this.registerShutdowniHook){
		try {
			context.registerShutdownHook():
		}
		catch (AccesscontrolException ex){
		}
	}
}
  • 如何定义钩子函数?
publie classHooksTester {
	publie static void main(String[] args) {
		Runtime.getRuntime().addShutdownHook(new Thread(new Runnable(){
			@override
			public void run(){
				System.out-println[一钩子函数执行”;
			}
	   }));	
	   //当主动关闭应用
	   while (true);
}

总结:

  • 触发钩子函数的场景: 在这里插入图片描述
  • 钩子函数能做什么? ①正如上图所示优雅停机,在项目将要关闭时候,主动释放程序占用的资源信息,释放db连接池的连接等其他占用的资源信息。 如果我们是 Spring 项目其实我们不用自己定义钩子函数,我们只要使用Spring提供给我们的销毁方法即可。因为 Spring定义的钩子函数中会去执行, DisposableBean.destory() 和被 PreDestroy 修饰的方法。
  • 链接:Hook钩子函数

内部类和对象与类的匿名

内部类和对象与类的匿名简介:

  • 对象与类的匿名: ①匿名对象就是该类实例化后直接使用,不赋予标识符。 ②匿名类就是直接实例化一个类且该类只能被实例化一次,形式与实例化一个对象差不多。 ③区别:一个在于类的一次性,一个在于对象的一次性。
  • 内部类: ①内部类分为成员内部类和局部内部类 <1>成员内部类跟其他成员属性一样声明同时声明形式和普通类一样。 <2>局部内部类也就是匿名内部类,因为声明在函数中且只声明一次在函数内使用后就不使用了。 ②成员内部类又主要分为静态成员内部类和非静态成员内部类: <1>静态内部类对象和其外围的类对象之间没有联系,然而普通的内部类对象隐式地保存了一个引用指向创建它的外围类对象内部类与外部类之间底层通过getXX()和setXX()方法实现,静态成员内部类除外(静态内部类不持有外部类的引用)。 <2>因此要创建静态内部类的对象,并不需要外围的类的对象。 <3>静态成员内部类不能访问外部类非静态的成员,只能访问静态成员。 <4>成员内部类可以访问外部类所有成员。 <5>外部类可以访问内部类所有成员。但是外部类的静态成员也只能访问静态内部类 ③注意: <1>成员内部类是声明在主类中的。 <2>如果一个内部类声明在主类外那么该类就是一个普通类了。 <3>静态成员内部类不像其他静态成员一样在类加载时加载,而是和非静态内部类一样,都是在被调用时才会被加载。不然的话静态成员内部类就是单例的了。 ④链接:静态内部类何时初始化 ⑤举例:
----------假设外部类A有静态内部类B和非静态内部类C,创建B和C的区别为:---------------
A a=new A();
A.B b=new A.B();//静态内部类直接通过外部类A来new。
A.C c=a.new C();//非静态内部类须通过外部类A实例对象来new。

java文件中多个类理解:

  • JAVA一个文件写多个类,并且是同级类,需注意: ①在一个.java文件中可以有多个同级类, 其修饰符只可以public/abstract/final/无修饰符(default)。其中public修饰的只能有一个,且必须要与文件名相同。 <1>因为jvm虚拟机为了提高查找类的速度,使用import语句导入的时候,只会导入对应空间的文件名所对应的class文件,而public文件是大家都要使用的,因此直接导入这个类名对应的class文件即可。 ③若没有public的则可与文件名不同 <1>Java编译器在编译的时候,如果整个Java文件(编译单元)都没有public类(对外的公开接口类),类加载器子就无需从这方面直接去加载该编译单元产生的所有的字节码文件(.class文件),那么也就是无需去寻找编译后字节码文件存放位置。而类名和文件名一致是为了方便虚拟机在相应的路径中找到相应的类所对应的字节码文件。所以在没有public类的Java文件中,文件名和类名都没什么联系。 ④该文件同级的类之间可以互相调用,但是除了public的类,其他不能够在其他文件调用。 ⑤在一个.java文件中由类/Enum/接口/Anontation其中至少一个类型组成。 ⑥单独一个方法/变量不能独自存在与文件中,所以公用方法的封装也是做成类方法。原因是java是类加载机制,需要编译一个java文件成多个class文件,当类来使用。 ⑦用javac 编译这个.java文件的时候,它会给每一个类生成一个.class文件

类路径与jar包

Build Path 和 WEB-INF/lib 简介:

  • Build Path:可以看成是引用 ①Idea编译项目时,是根据Build Path找jar包,如果不用Idea来发布项目的话,就会出现找不到jar包
  • WEB-INF/lib:可以看成是固定在一个地方 ②Tomact运行项目,首先是在它自己的公共lib里找jar包,如果找不到就会去项目的WEB-INF/lib目录找,如果找不到就报错。

不同类型项目加载jar的位置:

  • 纯Java项目: ①通俗的讲和classLoder有关,对于纯Java项目来说,它不存在WEB-INF目录,所以在引入jar包的时候一般都是通过Build Path直接引入。 ②纯Java项目使用的是自己本地的JRE,那么classLoder在加载jar和class的时候时分开的们对于我们自己编写的class,会在APP_HOME/bin下。导入的jar包或者user library的配置信息会出现在APP_HOME/.classpath文件中,ClassLoader会很智能去加载这些classes和jar。
  • Java Web项目: ①对于Java Web项目来说,它最终不是通过本地是JRE去运行的,而是部署到Web服务器上,如Tomact、Weblogic、WebSphere、Jetty等,这些服务器都实现了自身的类加载器。 ②比如Tomact服务器,它有自己的类加载器,根据J2EE的规范去%web-project%/WEB-INF/lib目录下找相应的lib,这就是我们发布的Web应用要符合那个格式。 ③以Tomact典型结果为例,它的目录结构分别对应4个不同的类加载器,关系如下: <1>common ------ CommonClassLoder <2>server ------ CatalinaClassLoader <3>shared ------ SharedClassLoder <4>webapps ------ WebappClassLoder ④我们的Web应用都是部署到webapps目录下,而WebappClassLoder类加载器专门负责加载webapps下所有的web项目的WEB-INF下的类库和类文件。而我们通过Build Path引入的jar包自然不会被WebappClassLoder加载器加载,所以才会出现ClassNotFoundException。 ⑤IDEA引用Library是为了编译代码生成WEB-INF/classes里面的class文件使用,使用IDEA时,会将WEB-INF/lib中所有的lib自动加入到library中。 ⑥IDEA工程下的library是用来编译src下的java文件,实际发布发到Tomact时,仅仅只复制了WEB-INF/lib里面的jar包,所有可能会出现IDEA可以正常编辑但Tomact运行时找不到类。 ⑦说白了就是用IDEA开发web的时候,如果是编译java代码用到的jar可以作为library引用,如果是框架非java代码部分用到的jar就必须放在lib下面。

Build Path 和 WEB-INF/lib 总结:

  • IDEA可以通过多种方式加入第三方的jar包(Java Build Path): ①add jar:是表示从你的工程里添加JAR,前提是你把jar已经放到自己的工程目录里。add external jar:表示这个jar的位置需要URI来定位,需要给出全路径。add library: 是一些已经定义好的jar的集合,因为它们经常是一起用,所以简化了些操作,比如你做RCP开发的时候就会有个plugin library包含了运行工程所需要的基本插件。 ④Add classes Loader :这个跟添加jar是一个意思,就是告诉ClassLoader去哪找class
  • 虽说无论用什么方式导入包在本地运行都是一样的,但实事上我运行时,有的只有Java Build Path才起作用,有的只有导入到lib下才行。 ①用Java Build Path导入包和把包复制到lib下是有区别的,它俩其实不会冲突,也没有什么关系的。 ②Java Build Path是我们编译需要的包,在比如在import *.*.*时如果没用Java Build Path导入包的话类里面就有红叉,说不识别这个类。 ③导入到lib下是程序运行时需要的包,即便用Java Build Path导入过的包,没放到lib下,运行项目时会出现ClassNotFoundException的异常。

classpath和jar包:

  • classpath: ①classpath是JVM用到的一个环境变量,它用来指示JVM如何搜索class。所以,classpath就是一组目录的集合,它设置的搜索路径与操作系统相关。 ②强烈不推荐在系统环境变量中设置classpath,那样会污染整个系统环境。在启动JVM时设置classpath才是推荐的做法。实际上就是给java命令传入-classpath或-cp参数。 ③不要把任何Java核心库添加到classpath中!JVM根本不依赖classpath加载核心库! ④更好的做法是,不要设置classpath!默认的当前目录.对于绝大多数情况都够用了。
  • jar包: ①如果一个项目有很多个.class文件,且存放在不同的目录下,管理起来很不方便,如果能够将众多目录打包成一个文件,就方便多了。 在这里插入图片描述 ②jar包包含一个特殊的/META-INF/MANIFEST.MF文件,MANIFEST.MF是纯文本,可以指定Main-Class和其它信息。JVM会自动读取这个MANIFEST.MF文件,如果存在Main-Class,我们就不必在命令行指定启动的类名,而是用更方便的命令。 <1>如下:java -jar hello.jar //没有指定启动的类,因为存在Main-Class

包名、类名、方法名冲突问题:

  • 当存在相同包名、类名、方法名时: ①若存在相同jar包中直接编译报错。 ②若存在不同jar包中,具体加载哪个类跟JVM发现它的顺序有关,也就是配置类路径的先后顺序相关。
  • 当存在不同包名,相同类名、方法名时:若存在相同jar包中引用时: <1>只能导入一个包,另外一个你声明的时候需要全类名。 <2>因为你有2个一样的对象名称,都import进来的时候 java编译器不知道你调用的是哪一个。所以如果2个类名相同的时候一个用import,一个用全类名。不同类名可以Import就没有问题。
  • 注意: ①改变编译器优先选择的jar顺序(这个顺序是可以改变的):在IDEA中是在Java Build Path-》Order and Export里面,通过"up"/"down"按钮改变顺序。

总结:

  • 链接:java类加载过程中,如果有包名、类名、方法名冲突,是怎样个情况
  • jar包问题: ①classpath:是用来指示JVM如何搜索class。 ②jar:是多个class的集合。 ③library:是多个jar的集合。 ④Build Path:Idea编译项目根据Build Path找jar包。 ⑤WEB-INF/lib:Tomact运行项目时会去该目录找jar包。
  • package包问题: ①不同jar包时无论package如何都没问题,问题在jar包的加载顺序问题。 ②相同jar包时若存在相同类名,则其中一个需要全类名(package+class)区分。
  • SpringBoot中: ①多模块扫描中,一般来说自己创建的各个模块的各个package的各个class不会存在相同的。(顶多包名相同,但类名不会相同)自己module的jar包和第三方的jar包在打包时都会打进Web-INF的classes中。 ③由于jar包是多个class的集合(方便class加载),因此jar包不会形成目录分隔,多个jar下的class会全部融合在一起,目录分隔还得看package。因此package中存在相同前缀的,例如com.george,它们就都属于com.george下。 <1>因此test测试类和config配置类和C、S、D层的类可以写在其他jar包中,前提是与WEB模块的启动类存在相同package下。

jdk和cglib反射与动态代理

jdk动态代理(底层动态生成代理类和编译过程):

  • 链接: ①jdk动态代理底层jdk的动态代理及为什么需要接口
  • JDK动态代理理解: ①Java动态代理就是动态的生成代理对象,解决的问题就是增强类。其实实现代理的方式很多种,比如继承,组合。但这种增强没有通用性。 ②Java是编译型语言,因此jdk动态代理就是在程序运行时生成代理类的java文件再通过某种方式编译生成.claas文件加载。JDK动态代理在程序运行时生成Java文件(通过模板引擎,与html拼串类似),然后再通过ToolProvider类的getSystemJavaCompiler方法获取JavaCompiler类,再通过JavaCompiler类的方法来编译Java文件。
  • Java 动态代理的过程可以简化成为: ①提供一个基础的接口,作为被调用类型(如com.test.MyInterfaceImpl)和代理类(如com.test.MyInterface)之间的统一入口; ②通过实现InvocationHandler接口来自定义自己的MyInvocationHandler,对代理对象方法的调用,会被分派到其 invoke 方法来真正实现动作; ③调用java.lang.reflect.Proxy类的newProxyInstance 方法,生成一个实现了相应基础接口的代理类实例。
-----------Porxy类提供了一个静态方法创建动态代理类---------
public static Object newProxyInstance(ClassLoader loader,           
    Class<?>[] interfaces,                                      
    InvocationHandler h)
throws IllegalArgumentException

---InvokationHandler(调用处理器)通过用户类来实现,来激发一个动态代理类的方法。它只有一个方法:---
public Object invoke(Object proxy, Method method, Object[] args)    throws Throwable;
  • 生成的代理类class文件理解: ①调用Proxy.newProxyInstance()方法时,最终会调用到ProxyGenerator.generateProxyClass()方法,该方法的作用就是生成代理对象的class文件。 ②通过源码我们发现,$Proxy0类继承了Proxy类,同时实现了Subject接口。 ③$Proxy0类实现了Subject接口,重写了doSomething()方法,它也同时重写了Object类中的几个方法。所以当我们调用doSomething()方法时,先是调用到$Proxy0.doSomething()方法,在这个方法中,直接调用了super.h.invoke()方法,父类是Proxy,父类中的h就是我们定义的InvocationHandler,所以这儿会调用到SubjectInvocationHandler.invoke()方法。因此当我们通过代理对象去执行目标对象的方法时,会先经过InvocationHandler的invoke()方法,然后在通过反射method.invoke()去调用目标对象的方法。
public class $Proxy0 extends Proxy implements Subject {}

cglib动态代理(底层动态生成代理类和编译过程):

  • 链接:Java动态代理机制详解(JDK和CGLIB,Javassist,ASM)
  • asm简介: ①ASM是一个Java字节码操纵框架,它能被用来动态生成类或者增强既有类的功能。ASM可以直接产生二进制class文件,也可以在类被加载入Java虚拟机之前动态改变类行为。Java class被存储在严格格式定义的.class文件里,这些类文件拥有足够的元数据来解析类中的所有元素:类名称、方法、属性以及 Java 字节码(指令)。ASM从类文件中读入信息后,能够改变类行为,分析类信息,甚至能够根据用户要求生成新类。 ②目前许多框架如cglib、Hibernate、Spring都直接或间接地使用ASM操作字节码,有些语言如Jython、JRuby、Groovy也是如此。而类ASM字节码工具还有: <1>BCEL:Byte Code Engineering Library (BCEL),这是Apache Software Foundation 的Jakarta 项目的一部分。BCEL是 Java classworking 最广泛使用的一种框架,它可以让您深入 JVM 汇编语言进行类操作的细节。BCEL与Javassist 有不同的处理字节码方法,BCEL在实际的JVM 指令层次上进行操作(BCEL拥有丰富的JVM 指令级支持)而Javassist 所强调的源代码级别的工作。 <2>JBET:通过JBET(Java Binary Enhancement Tool )的API可对Class文件进行分解,重新组合,或被编辑。JBET也可以创建新的Class文件。JBET用一种结构化的方式来展现Javabinary (.class)文件的内容,并且可以很容易的进行修改。 <3>Javassist:Javassist是一个开源的分析、编辑和创建Java字节码的类库。是由东京技术学院的数学和计算机科学系的 Shigeru Chiba 所创建的。它已加入了开放源代码JBoss 应用服务器项目,通过使用Javassist对字节码操作为JBoss实现动态AOP框架。 <4>cglib:是一个强大的,高性能,高质量的Code生成类库。它可以在运行期扩展Java类与实现Java接口,cglib封装了asm,可以在运行期动态生成新的 class,Hibernate和Spring都用到过它。cglib用于AOP,jdk中的proxy必须基于接口,cglib却没有这个限制。 ③而ASM与cglib、serp和BCEL相比,ASM有以下的优点 : <1>ASM 具有简单、设计良好的 API,这些 API 易于使用。 <2>ASM 有非常良好的开发文档,以及可以帮助简化开发的 Eclipse 插件。 <3>ASM 支持 Java 6(ASM3)、Java7(ASM4)、Java(ASM5)。 <4>ASM 很小、很快、很健壮。 <5>ASM 有很大的用户群,可以帮助新手解决开发过程中遇到的问题。 <5>ASM 的开源许可可以让你几乎以任何方式使用它。
  • Cglib动态代理的过程可以简化成为: ①查找被代理类上的所有非final 的public类型的方法定义; ②将这些方法的定义转换成字节码; ③将组成的字节码转换成相应的代理的class对象; ④实现 MethodInterceptor接口,用来处理 对代理类上所有方法的请求(这个接口和JDK动态代理InvocationHandler的功能和角色是一样的)
-----------Cglib提供了一个静态方法创建动态代理类---------
public static Object create(Class type, Callback callback) {}

---MethodInterceptor(调用处理器)通过用户类来实现,来激发一个动态代理类的方法。它只有一个方法:---
MethodInterceptor methodInterceptor = new MethodInterceptor() {
   @Override
   public Object intercept(Object o, Method method, Object[] objects,
    MethodProxy methodProxy) throws Throwable {
       return null;
   }
};

LRU算法四种实现方式

简介:

  • LRU全称是Least Recently Used,即最近最久未使用的意思。
  • LRU算法的设计原则是:如果一个数据在最近一段时间没有被访问到,那么在将来它被访问的可能性也很小。也就是说,当限定的空间已存满数据时,应当把最久没有被访问到的数据淘汰。
  • 实现LRU: ①用一个数组来存储数据,给每一个数据项标记一个访问时间戳,每次插入新数据项的时候,先把数组中存在的数据项的时间戳自增,并将新数据项的时间戳置为0并插入到数组中。每次访问数组中的数据项的时候,将被访问的数据项的时间戳置为0。当数组空间已满时,将时间戳最大的数据项淘汰。 ②利用一个链表来实现,每次新插入数据的时候将新数据插到链表的头部;每次缓存命中(即数据被访问),则将数据移到链表头部;那么当链表满的时候,就将链表尾部的数据丢弃。 ③利用链表和hashmap。当需要插入新的数据项的时候,如果新数据项在链表中存在(一般称为命中),则把该节点移到链表头部,如果不存在,则新建一个节点,放到链表头部,若缓存满了,则把链表最后一个节点删除即可。在访问数据的时候,如果数据项在链表中存在,则把该节点移到链表头部,否则返回-1。这样一来在链表尾部的节点就是最近最久未访问的数据项。
  • 总结: ①对于第一种方法, 需要不停地维护数据项的访问时间戳,另外,在插入数据、删除数据以及访问数据时,时间复杂度都是O(n)。 ②对于第二种方法,链表在定位数据的时候时间复杂度为O(n)。 ③所以在一般使用第三种方式来是实现LRU算法。
  • 链接:LRU算法四种实现方式介绍

单元测试

JUnit单元测试步骤:

  • 首先要在当前工程导入jar包。
  • 创建Java类,进行单元测试,此时Java类的要求是此类是public,且此类提供公共的无参构造器。
  • 此类中声明单元测试方法。此时的单元测试方法权限是public,没有返回值,没有形参。
  • 在单元测试方法上需要声明注解:@Test并在单元测试类中导入Test。
  • 运行测试相关代码。

说明:

  • 如果执行结果没有任何异常:绿条
  • 如果执行结果出现异常:红条

jdk常用命令

JDK自带命令行工具:

  • 给一个系统定位问题的时候,知识、经验是关键基础,数据是依据,工具是运用知识处理数据的手段。
  • 这里的数据包括:运行日志、异常堆栈、GC日志、线程快照(threaddump/javacore文件)、堆转储快照(heapdump/hprof文件)等。经常使用适当的虚拟机监控和分析的工具可以加快我们分析数据和定位解决问题的速度,但我们在学习工具前,也应当意识到工具永远都是知识技能的一层包装,没有什么工具是“秘密武器”,学会了就能包医百病。
  • Java开发人员肯定都知道JDK的bin目录下有“java.exe“和”javac.exe“这两个命令行工具,但并非所有程序员都了解过JDK的bin下其他命令行程序的作用。每逢JDK更新版本之时,bin目录下命令行工具的数量和功能总会不知不觉地增加和增强。
  • 链接:jdk常用命令
名称主要功能
jpsJVM Process Status Tool,显示指定系统内所有HotSpot虚拟机进程
jstatJVM Statistics Minitoring Tool,用于收集HotSpot虚拟机各方面的运行数据
jinfoConfiguration Info for Java,显示虚拟机配置信息
jmapMemory Map for Java,生成虚拟机的内存转储快照(heapdump)文件
jhatJVM Heap Dump Browser,用于分析heapdump文件,它会建立一个HTTP/HTML服务器,让用户可以在浏览器上查看分析结果
jstackStack Trace for Java,显示虚拟机的线程快照
javacjavac是用来编译.java文件的
java执行该字节码文件
javap主要用于帮助开发者深入了解 Java 编译器的机制,javap是JDK自带的反汇编器,可以查看java编译器为我们生成的字节码

七种java字符串拼接详解

简介:

  • “+”号操作符: ①要说姿势,“+”号操作符必须是字符串拼接最常用的一种了。 ②原来编译的时候把“+”号操作符替换成了 StringBuilder 的 append 方法。也就是说,“+”号操作符在拼接字符串的时候只是一种形式主义,让开发者使用起来比较简便,代码看起来比较简洁,读起来比较顺畅。
  • StringBuilder: ①除去“+”号操作符,StringBuilder 的 append 方法就是第二个常用的字符串拼接姿势了。
  • StringBuffer: ①先有 StringBuffer 后有 StringBuilder,两者就像是孪生双胞胎,该有的都有,只不过大哥 StringBuffer 因为多呼吸两口新鲜空气,所以是线程安全的。 ②StringBuffer 类的 append 方法比 StringBuilder 多了一个关键字 synchronized。
  • String 类的 concat 方法: ①通过源码分析我们大致可以得出以下结论: 1)如果拼接的字符串是 null,concat 时候就会抛出 NullPointerException,“+”号操作符会当做是“null”字符串来处理。 2)如果拼接的字符串是一个空字符串(""),那么 concat 的效率要更高一点。毕竟不需要 new StringBuilder 对象。 3)如果拼接的字符串非常多,concat 的效率就会下降,因为创建的字符串对象越多,开销就越大。
  • String 类的 join 方法: ①JDK 1.8 提供了一种新的字符串拼接姿势:String 类增加了一个静态方法 join。 ②发现了一个新类 StringJoiner,类名看起来很 6,读起来也很顺口。StringJoiner 是 java.util 包中的一个类,用于构造一个由分隔符重新连接的字符序列。
  • StringUtils.join:通过查看源码我们可以发现,其内部使用的仍然是 StringBuilder。
  • 链接:七种java字符串拼接详解

为什么阿里巴巴不建议在 for 循环中使用”+”号操作符进行字符串拼接?

  • 第一段,for 循环中使用”+”号操作符。
String result = "";
for (int i = 0; i < 100000; i++) {
    result += "六六六";
}
  • 第二段,for 循环中使用 append。
StringBuilder sb = new StringBuilder();
for (int i = 0; i < 100000; i++) {
    sb.append("六六六");
}
  • 这两段代码分别会耗时多长时间呢? 1)第一段代码执行完的时间为 6212 毫秒 2)第二段代码执行完的时间为 1 毫秒

  • 答案:第一段的 for 循环中创建了大量的 StringBuilder 对象,而第二段代码至始至终只有一个 StringBuilder对象。

Java字符

\| 和 \\| 的区别是什么?

  • Java中:\\ =反斜杠,正则中:\| = |。因此在正则中\\| =|
  • 类似: 在这里插入图片描述

高内聚低耦合

类设计:

  • 模块独立: ①独立的模块容易开发。 ②独立的模块容易测试和维护。
  • 模块的独立程度 可以由两个标准衡量:内聚、耦合
  • 耦合:耦合是对软件结构内不同模块之间联系程度的度量。耦合强弱取决于模块间接口的复杂程度,进入或访问一个模块的点,以及通过接口的数据。
  • 模块耦合分为数据耦合、控制耦合、特征耦合、公共环境耦合、内容耦合
//数据耦合:
//1、两个模块间 只通过参数交换信息。数据耦合是低耦合,系统中必然存在。
public class Main {
	public static void main(String[] args) {
		Dog d = new Dog("旺财");
	}
}
class Dog {
	private String name;
	public Dog(String name) {
		this.name = name;
	}
}

//控制耦合:
//1、传递的信息中有控制信息(尽管有时这种控制信息以数据的形式出现),则这种耦合称为控制耦合。
//2、控制耦合是中等程度的耦合。
public static void update(boolean insertFlag) {
	if (insertFlag) {
		// 插入
	} else {
		// 更新
	}
}

//特征耦合:
//1、当把整个数据结构作为参数传递,而被调用的模块只需要使用其中一部分数据元素时,就出现了特征耦合。
public class Main {
	private static void 特征耦合(Dog d) {
		System.out.println(d.getName());
	}
	private static void 非特征耦合(String name) {
		System.out.println(name);
	}
	public static void main(String[] args) {
		Dog d = new Dog("旺财");
		特征耦合(d);
		非特征耦合(d.getName());
	}
}
class Dog {
	private String name;
	private String variety;
	public Dog(String name) {
		this.name = name;
	}
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	public String getVariety() {
		return variety;
	}
	public void setVariety(String variety) {
		this.variety = variety;
	}
}

//公共环境耦合:
//1、当多个模块通过一个公共数据环境相互作用时,它们之间的耦合称为公共环境耦合。公共环境可以是全局变
//量、共享数据、任何存储介质上的文件等。
//2、公共环境耦合的复杂程度随耦合的模块个数而变化,当耦合的模块个数增加时,复杂程度而显著增加。
//3、只有两个模块有公共环境,耦合有下面两种可能。
//(1) 一个模块往公共环境送数据,另一个模块从公共环境取数据。这是数据耦合的一种形式,是比较松散的耦合。
//(2) 两个模块都既往公共环境送数据又从里面取数据,这种耦合比较紧密,介于数据耦合和控制耦合之间。

//内容耦合:
1、最高程度的耦合。
2、表现为:一个模块直接访问另一模块的内部数据,则称这两个模块为内容耦合
public class Main {
	public static void main(String[] args) {
		Dog d = new Dog("旺财");
		System.out.println(d.name);//内容耦合
	}
}
class Dog {
	public String name;
	public Dog(String name) {
		this.name = name;
	}
}
  • 内聚:衡量模块内各元素结合的紧密程度。 ①内聚和耦合是密切相关的,模块内的高内聚往往意味着模块间的低耦合。 ②高内聚是指相关操作尽量聚集到单独的类或文件中,尽量少的牵连到其他类或者文件。 ③设计原则中的“信息隐藏和局部化”就是用于提高内聚的手段。