java基础总结、增强、细节

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是某个类的某个方法的名字)

(原理:通过变量自己是没法知道实际类型参数的,但是当把一个变量当作参数或返回值交给一个方法去使用的时候,通过这个方法是可以知道这个变量的泛型类型的。)


【7、equals()和hashcode()】
hashcode也是面试中经常会问的东西,这个知识点也特别考察对hashcode的理解深度。

『什么是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】

关于类的字段和属性:

java基础总结、增强、细节_第1张图片

内省其实就是一种特殊的反射,它是专门用来操作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();//抛空指针异常
	}


2、对于从Object类中继承的方法,只有hachCode、equals、toString这三个方法会交给代理对象去干,其他的方法不会交给handler去看(具体可看Proxy文档)。

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、代理对象的返回值问题。

java基础总结、增强、细节_第2张图片

5、invoke方法为什么传入的参数是这种形式:handler.invoke(Object proxy,Method m,Object[] args)

java基础总结、增强、细节_第3张图片

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类对象的方法。






















你可能感兴趣的:(java基础总结、增强、细节)