java,玩的就是基础,所以时不时对java基础有新的理解或者体会,谨记于此,方便自己翻阅。
暂时没有新的理解或者体会的,先把标题列出来,以后有了再加上。
【1、静态导入】
【2、可变参数:】注意,为了保证向下的兼容性,编译器为该可变参数隐含创建了一个数组,方法体中以数组形式访问可变参数。
【3、增强for循环:】
for(int arg : args) for(final int arg : args)int前面可以加如final这样的修饰符;args必须实现Iterable接口才能使用增强for循环
【4、装箱拆箱】
Integer i1 = 10; Integer i2 = 10; System.out.println(i1 == i2);//true
Integer i1 = 129; Integer i2 = 129; System.out.println(i1 == i2);//false(这里涉及到一种设计模式:享元设计模式:如果很多很小的对象,它们有很多相同的东西,那就可以把它们共有的东西变成一个对象,把它们非共有的东西变成外部状态,那些相同的东西叫做内部状态。)
在java中,一个字节(-127-128)的数会“享元”,参考上面对于享元模式的阐述,可知,第一段示例代码中,由于10很小,所以两个Integer对象共享,故打印结果为true;而第二段示例代码中,129较大,两个Integer对象不会共享,故打印结果为false。
(Java中的其他类似问题分析同理,如Integer.valueOf()等。)
很多笔试题这样考!明白了这个能应付好多好多笔试题了。!
【5、枚举】
(用自己的话说,其实枚举就是把大量if...else语句转移成了一个独立的类。)
当枚举只有一个成员变量时,可以把枚举当作单例。
枚举在Web开发中还有一个非常常用的地方:例如,WeekDay.valueOf("XXX")。因为在前端,传过来的都是字符串,用这个例子可以将传过来的字符串直接转换成枚举。
【6、反射】
(反射的东西太多太大了,这里只写下来细节)
//三种方式 Class.forName("xxx");//若已加载,则拿来直接用;若未加载,则调用类加载器 Xxx.class; xxx.getClass();有9个预定义Class实例--------->8个基本类型加上void类型
System.out.println(int.class == Integer.TYPE);//true //其他类型装箱拆箱同理
//对接收数组参数的成员方法进行反射 Class clazz = Class.forName(""); Method m = clazz.getMethod("main", String[].class); //下面两种任选其一 m.invoke(null, new Object[]{new String[]{...,...}}); m.invoke(null, (Object)new String[]{...,...});(原因:由于JDK1.5才引入可变参数,JDK1.4及之前版本都是参数数组形式,为了保证兼容性,需要采取上述代码段的处理方式)
『一个常见的需求』:printObject(Object obj){...}:若传入一个对象,则打印该对象;若传入的是一个数组,则依次打印数组元素
public void printObject(Object obj){ Class<?> clazz = obj.getClass(); if(clazz.isArray()){ //当不知道一个obj是否为数组,但却要用到其维数的时候,要用到反射包的Array方法 int len = java.lang.reflect.Array.getLength(obj); for(int i=0;i<len;i++){ System.out.println(java.lang.reflect.Array.get(obj, i)); } }else { System.out.println(obj); } }
多说一点:此时无法得到数组元素的类型。
这点应该怎么理解呢?因为当传入一个object数组时,如
Object[] a = new Object[]{"abc",1,...};
里面既可以有String,又可以有int,还能有其他类型,所以也就无法一下子得到数组中每个元素的具体类型。
一个小知识点:命令行窗口下编译:C:\user\haha>java Hello db.properties
这里的db.properties的路径既不是相对于java虚拟机,也不是相对于Hello.class文件,而是相对于命令行路径C:\user\haha。
『反射和泛型』
几个约定俗成的称呼和概念:
原始类型:ArrayList
泛型类型:ArrayList<E> 其中的E称为“类型变量”或者“类型参数”
参数化类型:ArrayList<String> 其中的String称为“实际类型参数”或者“类型参数的实例”
下面通过反射,获取泛型的实际类型参数,即通过反射,获取例如ArrayList<String>中的String
Method m = xxx.class.getMethod("", ArrayList.class); Type[] types = m.getGenericParameterTypes(); ParameterizedType pt = (ParameterizedType) types[0]; pt.getRawType();//java.util.ArrayList pt.getActualTypeArguments();//java.lang.String(上面代码中xxx是某个类的某个方法的名字)
(原理:通过变量自己是没法知道实际类型参数的,但是当把一个变量当作参数或返回值交给一个方法去使用的时候,通过这个方法是可以知道这个变量的泛型类型的。)
『什么是hashcode?它有什么用?』
如果想查找一个集合中是否包含有某对象,大概的程序代码怎样写呢?你通常是逐一取出每个元素,然后与要查找的对象进行比较。当发现某元素与要查找的对象equals比较相等后,则停止继续查找并返回一些有用的信息。
若一个集合中元素超级超级多,这种做法肯定效率低下。于是有人发明了一种“哈希算法”来提高从集合中查找元素的效率。
这种方式将集合分成若干个存储区域,每个对象可以计算出一个哈希码;然后可以将哈希码分组,每组分别对应某个存储区域。这样就可以根据一个对象的哈希码来很快的确定对象存在哪个区域。
(例如分成3个区域,你给我一个对象,我通过哈希算法算出你这个对象的hashcode值,我不妨对其用模与3,得到0就放到第1个区域,得到1就放到第2个区域,得到2就放到第3个区域。这样一来,当需要查找对象的时候,我可以直接到对应区域去查找,这就大大提高了效率。)
『hashcode和equals的关系』
如果两个对象的equals相等,那么最好让它们的hashcode也相等。如果对象不存到hashcode集合里面,则不需要搞hashcode。
只有类的实例对象要被采用哈希算法的系统进行存储和检索时,这个类才需要覆盖hashcode方法。即使程序可能暂时不会用到当前类的hashcode方法,但是为它提供一个hashcode方法也没什么不好,万一以后需求变了用上了呢?所以通常要求hashcode方法和equals方法一并被同时覆盖。
『hachcode和内存泄漏』
(内存泄漏:某对象我不用了,但它一直占据着内存,未被释放,这就叫内存泄漏)
很多人都说java没有内存泄漏,其实这种说法不准确哦!
(例如一个对象被存储金了hashSet集合中以后,我修改对象中那些参与计算哈希值的字段。由于修改后的哈希值和存入时候的不想等,所以该对象一直会存在于集合中,我没有办法访问它,这就造成了内存泄漏。)
【8、内省&BeanUtils】
关于类的字段和属性:
内省其实就是一种特殊的反射,它是专门用来操作bean的反射。
(反射可以操作任何类,内省是专门操作bean的。因此要对bean进行反射时,用内省会显得更加专业。)
内省API常用的类:
java.lang.Introspector(入口类)
BeanInfo
PropertyDescriptor(常用其getReadMethod/getWriteMethod/getName方法)
MethodDescriptor
使用BeanUtils操作bean
好处:
1、String转换自动完成
2、支持属性级联操作(属性链)
3、调用populate方法可以把Map对象整到Bean中去;调用describe方法可以把Bean中的属性整到Map中去;调用copy方法可以将一个Bean中的属性整到另一个Bean中
4、JDK5以后map的初始化可以这样写:
Map map = {name:"xxx",age:18}; BeanUtils.setProperty(map,"name","myName");5、PropertyUtils(按本身类型进行操作)
【9、类加载器】
1、字节码:类加载器把.class文件里的内容加载到内存,再对它进行一些处理,处理完后的结果,就是字节码。
2、类加载的父亲委托机制。
3、自定义类加载器、loadClass、findClass、defineClass。
【10、注解】
(Servlet可以不在XML文件中配置,可以加一个@WebServlet(...,...)的注解,很直观!)
1、注解的返回值类型:基本类型、String、Class、注解类型、枚举类型、以及前面这些类型的一维数组。
2、元注解。元的概念要理解好。元信息的意思是:解释信息的信息;所以元注解的意思就是:解释注解的注解。主要是对元这个字的理解。
3、注解其实就是一种说明,给其他程序看的说明。
开发人员应该掌握(定义注解,解析注解):如何定义注解,如何获取注解并根据注解信息决定如何运行类(反射)
自定义注解的目标:原来卸载配置文件中的信息可以通过注解描述,好处就是直观,坏处就是一旦要修改配置就需要修改源代码。
4、注解在开发中两个应用场景:一是注入信息;而是注入对象。
【11、代理】
1、代理类为空时,调用某些方法可能不会抛空指针异常。例子如下:
public void test(){ proxy = Proxy.newProxyInstance(..., ..., new InvocationHandler() { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { return null; } }); //调用有返回值的方法,会抛空指针异常; /*调用返回值为void的方法,不会抛空指针异常。 (注意:此为代理类所特有的特性。普通类为null时,调用其任何方法都会抛空指针异常)*/ proxy.clear();//无空指针异常 proxy.hashCode();//抛空指针异常 }
3、一个容易被忽略的小细节:(先看代码)
public void test(){ @SuppressWarnings("unchecked") Collection<Object> proxy = (Collection<Object>) Proxy.newProxyInstance( Collection.class.getClassLoader(), new Class[]{Collection.class}, new InvocationHandler() { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { ArrayList<Object> list = new ArrayList<>(); return method.invoke(list, args); } }); proxy.add("haha"); proxy.add("xixi"); System.out.println(proxy.size());//打印结果为0 }(明明放进去了两个元素,为何打印结果为零?)
(这是因为执行proxy.size()方法时,又会去再一次找代理对象,即调用size()方法会执行内部类中的代码部分,重新new一个ArrayList对象,然后返回这个新new出来的ArrayList对象的size,当然是零啦。)
如果内部类中的代码改成这样:
new InvocationHandler() { ArrayList list = new ArrayList(); @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { return method.invoke(list, args); }则再执行两次add后,打印proxy.size(),此时结果就是2了!此时调用size()方法也会去找代理对象,但是由于list的初始化提到了上面,每次不会重新new了,因此结果为2。
4、代理对象的返回值问题。
5、invoke方法为什么传入的参数是这种形式:handler.invoke(Object proxy,Method m,Object[] args)
6、spring的AOP核心原理的自我实现:(核心就是两个类一个接口:BeanFactory、ProxyFactoryBean、Advice)
public class BeanFactory { private Properties props = new Properties(); public BeanFactory(InputStream in){ try { props.load(in); } catch (IOException e) { throw new ExceptionInInitializerError(e); }finally { try { if (in!=null) {in.close();} in = null; } catch (IOException e) { throw new RuntimeException(e); } } } public Object getBean(String beanName){ String clazzName = props.getProperty(beanName); try { Object bean = Class.forName(clazzName).newInstance(); if(bean instanceof ProxyFactoryBean){ ProxyFactoryBean proxyFactoryBean = (ProxyFactoryBean) bean; Advice advice = (Advice) Class.forName(props.getProperty(beanName + ".advice")).newInstance(); Object target = Class.forName(props.getProperty(beanName + ".target")).newInstance(); proxyFactoryBean.setAdvice(advice); proxyFactoryBean.setTarget(target); return proxyFactoryBean.getProxy(); }else { return bean; } } catch (Exception e) { throw new RuntimeException(e); } } }
public class ProxyFactoryBean { private Advice advice; private Object target; public void setAdvice(Advice advice) { this.advice = advice; } public void setTarget(Object target) { this.target = target; } public Object getProxy(){ Object proxy = Proxy.newProxyInstance( target.getClass().getClassLoader(), target.getClass().getInterfaces(), new InvocationHandler(){ @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { advice.beforeMethod(method); Object retVal = method.invoke(target, args); advice.afterMethod(method); return retVal; } }); return proxy; } }
public interface Advice { void beforeMethod(Method method); void afterMethod(Method method); }
在BeanFactory这个类中注意一下这里,对instanceof多说一点:
if(bean instanceof ProxyFactoryBean){ return bean.getProxy(); }else { return bean; }
if(!(bean instanceof ProxyFactoryBean)){ return bean; }else { return bean.getProxy(); }会发现编译不通过。因为只有ProxyFactoryBean的实例才能调用getProxy()方法。但是else中只能保证不是ProxyFactoryBean的实例,不能保证一定是ProxyFactoryBean的实例,所以报错。
对比上面一种写法,也可以更加了解xxx instanceof A放在条件判断中时,在条件判断模块主体中可以直接把xxx当作A类的实例去用,并调用A类对象的方法。