JAVA面试 基础增强与巩固:反射、注解、泛型等

做者-焕然一璐,支持原创,转载请注明出处,谢谢合做。 原文连接:http://www.jianshu.com/p/aaf8594e02ebjava

企业重视的是学习能力:基础很重要android

###JDK1.5新特性程序员

  1. 泛型
  2. foreach
  3. 自动拆箱装箱
  4. 枚举
  5. 静态导入(Static import)
  6. 元数据(Metadata)
  7. 线程池
  8. 注解

###JDK1.6新特性算法

  1. Desktop类和SystemTray类
  2. 使用JAXB2来实现对象与XML之间的映射
  3. StAX
  4. 使用Compiler API
  5. 轻量级Http Server API
  6. 插入式注解处理API(Pluggable Annotation Processing API)
  7. 用Console开发控制台程序
  8. 对脚本语言的支持
  9. Common Annotations

###JDK1.7新特性编程

1 对集合类的语言支持; 2 自动资源管理; 3 改进的通用实例建立类型推断; 4 数字字面量下划线支持; 5 switch中使用string; 6 二进制字面量; 7 简化可变参数方法调用。设计模式

###JDK1.8新特性数组

  1. 接口的默认方法,也就是接口中能够有实现方法
  2. 函数式接口与静态导入
  3. Lambda 表达式
  4. 访问局部变量

静态导入:JDK1.5新特性缓存

//静态导入某一个方法
import static java.lang.Math.abs;
//静态导入一个类的全部静态方法
import static java.lang.Math.*;

public static void main(String[] args) {
	//方法能够直接使用
    System.out.println(abs(-100));
}


//若是类里面本来就有同名的方法的话,就会覆盖掉静态导入的方法了
public static void abs() {

}
复制代码

###可变参数安全

没有可变参数以前,实现不定长参数只能经过方法的重载实现,可是这样作工做量很大。微信

没有可变参数以前,实现可变参数的方法能够经过Obiect[]来实现。

  1. 可变参数只能放在参数列表的最后一个

  2. 编译器隐含为可变参数建立数组,所以能够经过数组的方式去使用可变参数

    public static int add(int x, int... args) {
         int sum = x;
    
         for (int i = 0; i < args.length; i++) {
             sum += args[i];
         }
    
         return sum;
     }
    复制代码

###加强for循环

//数组或者实现了Iterable接口的对象
    for 修饰符 Type arg : 要迭代的对象) {
        
    }
复制代码

###基本数据类型的自动装箱与拆箱

//自动装箱示例,自动将基本数据类型包装成对象
    Integer i1 = 1000;
    Integer i2 = 1000;

    //自动拆箱示例,自动将对象解包成基本数据类型
    System.out.println(i1 + 10);


    //若是数值在-128到127以前,对象会复用(享元设计模式)
    System.out.println(i1 == i2);
复制代码

-128到127会缓冲起来,节省内存。这是享元设计模式的应用,内部状态/外部状态(可变)

###枚举

####为何要使用枚举? 好比,咱们要使用1-7表明星期一到星期天,那么一般咱们会想到的作法作,定义一个类,而且提供一些公有的静态常量,例如:

public class WeekDay {
    public static final int SUN = 1;
    public static final int MON = 2;
}
复制代码

可是当咱们使用的时候,有些人可能不想去理会这个细节,好比会直接传入1(可能他本身以为1是表明星期一的),所以运行的时候就会出现一些意想不到的问题。

为了解决这个问题,java 5从新引入枚举,保证变量的值只能取指定值中的某一个,经过在代码编译的时候就能够知道传的值是否合法。

####枚举的模拟:

/**
 * 本身手动实现枚举类
 * 1. 构造方法必须是私有的
 * 2. 提供公有的静态成员变量表明枚举,而且经过匿名内部子类去生成对象
 * 3. 对外提供的方法必须是抽象的
 */
public abstract class WeekDay {

    public static WeekDay MON = new WeekDay(0) {
        @Override
        public WeekDay next() {
            return SUN;
        }

        @Override
        public String toString() {
            return "SUN";
        }

    };

    public static WeekDay SUN = new WeekDay(1) {
        @Override
        public WeekDay next() {
            return MON;
        }

        @Override
        public String toString() {
            return "MON";
        }
    };

    private int num;

    //私有构造方法
    private WeekDay() {

    }

    //在生成匿名内部类的时候,能够传参给父类的构造函数
    private WeekDay(int num) {
        this.num = num;
    }

    //对外提供的抽象方法,由子类去实现
    public abstract WeekDay next();

}
复制代码

一些关键的点已经在上面的注释中给出,在使用的时候,咱们只能经过这样去生成WeekDay对象。(实际上枚举内部也是生成对象嘛)

WeekDay weekDay = WeekDay.MON;
weekDay.next();
复制代码

枚举的实现

public enum WeekDay {

    //枚举对象必须放在最前面,匿名内部类的建立能够带参数,必须实现父类的抽象方法
    MON(1) {
        public WeekDay next() {
            return SUN;
        }
    },

    SUN(2) {
        public WeekDay next() {
            return MON;
        }
    };

    private int num;

    //枚举的构造函数是默认为private的,能够带参数
    WeekDay(int num) {
        this.num = num;
    }

    public abstract WeekDay next();

}
复制代码

枚举使用,以及一些枚举特有的方法:

//使用方法,跟通常的对象是如出一辙的
WeekDay w = WeekDay.MON;
//直接打印枚举对象其实是调用了toString
System.out.println(w);
//打印枚举的名字,实际上打印类的简短的名字w.getClass().getSimpleName()
System.out.println(w.name());
//打印枚举对象在枚举中的位置,0开始算
System.out.println(w.ordinal());
//经过字符串去或者获取(构造)枚举对象
System.out.println(WeekDay.valueOf("MON"));

//获取枚举类的全部对象,经过数组的方式去遍历
for (WeekDay value : WeekDay.values()) {
    System.out.println(value);
}
复制代码

####枚举的特殊用法---利用枚举能够简简单单就实现单例模式

###反射 -- JDK1.2就有了

了解反射的基础--Class类

用来描述Java类的类就是Class这个类。

每一个类在java虚拟机中占用一片内存空间,里面的内容就是对应这个类的字节码(Class)

####Class字节码的获取:

类名.class
对象.getClass
Class.forName("类的全名");
复制代码

其中,跟反射密切相关的就是forName这个方法,经过类名去获取字节码。前面两种都是虚拟机中已经加载过了。forName方法在当虚拟机中没有加载过对应字节码的时候,就会去动态地加载进来;当已经加载过的时候,直接复用加载过的。

####九种预约义好的基本Class字节码

八种数据类型,外加void也有对应的字节码。下面给出一些例子:

Class<Integer> type = Integer.TYPE;
Class<Integer> integerClass = int.class;
Class<Void> voidClass = Void.class;
复制代码

例子:

public static void main(String[] args) throws Exception {
    
    Class<?> c1 = Class.forName("java.lang.String");
    Class<String> c2 = String.class;
    Class<? extends String> c3 = new String("123").getClass();

	//返回的都是同一份字节码,所以==
    System.out.println(c1 == c2);
    System.out.println(c2 == c3);

	//判断是否是基本类型(九种)
    System.out.println(int.class.isPrimitive());
    System.out.println(int[].class.isPrimitive());
	
	//判断是否是数组(数组也是一种类型,全部类型均可以反射)
    System.out.println(int[].class.isArray());
    
}
复制代码

####反射的概念

一句话总结:反射就是把Java类中的各类成分经过java的反射API映射成相应的Java类,获得这些类之后就能够对其进行使用。好比方法,构造方法,成员变量,类型,包等。下面分别取讲解。

Constructor类

获得全部的构造方法

Constructor<?>[] constructors = Class.forName("java.lang.String").getConstructors();
复制代码

获得指定参数的某一个构造方法:

Constructor<?> constructor = Class.forName("java.lang.String").getConstructor(StringBuffer.class);
复制代码

建立对象

String s = (String) constructor.newInstance(new StringBuffer("abc"));
System.out.println(s.charAt(2));

也能够直接调用无参数的构造,实际上也是先找到Constructor再调用Constructor的newInstance
String s = (String) Class.forName("java.lang.String").newInstance();
复制代码

查看源码能够发现,Class的newInstance方法中有把Constructor缓存起来的。由于反射的使用会大大下降系统的性能,对于计算机来讲比较耗时,并且也容易发生运行时异常,所以须要慎重使用。

Field对象

Field表明类中的一个成员变量

例如咱们有一个测试的类Point

public class Point {

    public int x;
    private int y;
    protected int z;

    public Point(int x, int y, int z) {
        this.x = x;
        this.y = y;
        this.z = z;
    }
}
复制代码

那么,咱们能够利用发射机制去获得虚拟机中某个对象的成员变量,而且获取他们的值

Point point = new Point(1, 2, 3);

//经过反射拿到成员变量的值
Field x = point.getClass().getField("x");
System.out.println(x.get(point));

//若是是私有或者保护变量的话,不能拿到Field,会报找不到Field的错误
//Field y = point.getClass().getField("y");

//私有变量须要经过使用getDeclaredField去获取,若是要获取值的话,须要设置setAccessible为true
//这样的反射比较暴力
Field y = point.getClass().getDeclaredField("y");
y.setAccessible(true);
System.out.println(y.get(point));
复制代码

例子,把对象中全部String类型的值中的a修改成*:

public class Reflecttest1 {

    public static void main(String[] args) throws Exception {
        Test test = new Test();
        changeString(test);
        System.out.println(test);
    }

    private static void changeString(Object obj) throws Exception {
        for (Field field : obj.getClass().getFields()) {
            
            //判断是否是String类型,注意这里最好使用==
            if (field.getType() == String.class) {
                String oldV = (String) field.get(obj);
                String newV = oldV.replace('a', '*');
                field.set(obj, newV);
            }
        }
    }
}
复制代码

####Method类

String a = "0123";
//获取方法,而且调用
Method charAt = a.getClass().getMethod("charAt", int.class);
//经过反射调用方法,第一个参数是对象,若是为静态方法的话,应该传null
//第二个参数是可变长参数,传入的是实参
System.out.println(charAt.invoke(a, 1));
复制代码

调用指定对象的main方法,注意其中传递字符串数组的问题

因为可变长参数的问题,jdk为了兼容1.4如下的版本,会把传进去的数组进行拆包。所以注释代码会报参数不匹配的错。

解决方案是

一、再利用Object数组包装一层,告诉编译器,能够拆包,可是拆开以后就是一个String数组。

二、相强制转换为Object对象,告诉编译器,不要拆包。

public class ReflectTest3 {

    public static void main(String[] args) throws Exception {

        Method mainMethod = Class.forName("com.nan.test.T").getMethod("main", String[].class);
//        mainMethod.invoke(null, new String[]{"123"});
        mainMethod.invoke(null, new Object[]{new String[]{"123"}});
        mainMethod.invoke(null, (Object) new String[]{"123"});

    }

}

class T {
    public static void main(String[] args) {
        for (String s : args) {
            System.out.println(s);
        }
    }
}
复制代码

####数组类型

具备相同的维度以及元素类型的数组,属于同一种Class类型。

例子:

int[] a1 = new int[3];
int[] a2 = new int[4];
int[][] a3 = new int[3][4];
String[] a4 = new String[3];

System.out.println(a1.getClass());
System.out.println(a2.getClass());
System.out.println(a3.getClass());
System.out.println(a4.getClass());
复制代码

输出结果:

class [I
class [I
class [[I
class [Ljava.lang.String;
复制代码

其中[表明是数组类型,I表明元素类型(int)

八种基本类型的数组不能转换成Object数组。所以下面的语句不合法:

Object[] objects = a1;

//由于基本类型的一维数组不能当成Object数组,所以只能当作是一整个数组对象
//所以打印出来的结果是数组的对象的值,而不是里面的内容
//那么,按照JDK1.5的可变长参数语法,只能解析成一个数组对象
System.out.println(Arrays.asList(a1));
//String类型能够转换成Object数组打印的是内容
System.out.println(Arrays.asList(a4));
复制代码

####数组

能够经过反射来对数组进行操做,由于是Object数组,所以不能肯定整个数组是同一个类型,所以只能肯定的是每一项的类型。

private static void printObj(Object o) {
    //判断是否是数组类型
    if (o.getClass().isArray()) {
        //经过反射APIArray去遍历数组
        int length = Array.getLength(o);
        for (int i = 0; i < length; i++) {
            Object item = Array.get(o, i);
            System.out.println(item);
        }
    } else {
        System.out.println(o);
    }
}
复制代码

equals与hashCode的联系与区别。

  1. 一个类的两个实例对象equals的时候,hashCode也必须相等,反之不成立。
  2. hashCode只有在hash集合中才有意义,好比hashMap、hashSet等。当对象被存入hash集合或者从hash集合中移除、contains方法调用等的时候,先会经过计算hash值,算出对象应该在的存储区域,而后再经过在这块存储区域,里面经过equals方法去判断对象是否重复。(hash相等,equals能够不相等)

通常来讲,两个都须要重写,并且在对象插入了hash集合之后,不要再修改这个对象与hash计算有关的数值了,由于这样会致使hash集合根据变化以后的hash值找不到这个对象了,对象不能被清理,从而形成内存泄漏。

HashSet<Point> set = new HashSet<>();
Point p0 = new Point(1,2,3);
Point p1 = new Point(1,2,3);

set.add(p0);
//若是重写了hashCode方法,这个时候不重写equals方法,那么这个对象能够被插入
//若是重写了hashCode以及equals方法,那么这个对象不能够被插入
set.add(p1);

//数值改变了,对象不能被移除(找到),从而形成内存泄漏
p0.x = 10000;
set.remove(p0);

System.out.println(set.size());
复制代码

而通常的非hash集合,例如ArrayList,只保存数据的引用,数据是能够重复的。

Java反射的做用 -- 实现框架功能

框架与工具的区别:

相同:都是其余人提供的

不一样点:

  1. 工具类是被用户调用
  2. 框架是调用用户提供的类

例如:

  1. 框架提供配置文件,用户能够配置,例如类名
  2. 读取用户的配置文件
  3. 经过反射加载对应的类,而且动态去使用

配置文件的读取

//必定要用完整的路径,不是硬编码,而是运算出来的
InputStream is = new FileInputStream("文件目录");
Properties properties = new Properties();
properties.load(is);

String value = properties.getProperty("key");
复制代码

通常配置文件的加载基本都是利用类加载器来加载。

//经过类加载器能够把与类放在一块儿的配置文件读取出来,这里是与类相对路径。若是写上/表明是绝对路径,须要写完整/包名。。。。。
InputStream res = ReflectTest3.class.getResourceAsStream("文件目录");
//InputStream res = ReflectTest3.class.getClassLoader().getResourceAsStream("文件目录");
properties.load(res);
复制代码

内省,与java Bean

java bean的概念:符合必定get、set规则的java类

java bean的属性名为get、set方法去掉get、set前缀,剩下的-- 若是第二个字母也是小写的话,那么 -- 首字母须要变成小写

例如:

getAge-->age

setCPU-->CPU
复制代码

内省:操做Java Bean对象的API

Bean b = new Bean(1);

String propertyName = "x";

//普通方法x-->X-->getX-->调用
//下面使用内省的方法去获取get方法,set方法也是同一个道理
PropertyDescriptor pd = new PropertyDescriptor(propertyName, b.getClass());
Method getXMethod = pd.getReadMethod();
System.out.println(getXMethod.invoke(b, null));
复制代码

比较麻烦的写法,经过BeanInfo去作

BeanInfo beanInfo = Introspector.getBeanInfo(b.getClass());
PropertyDescriptor[] pds = beanInfo.getPropertyDescriptors();
for (PropertyDescriptor p : pds) {
    if (p.getName().equals("x")) {
        Method readMethod = p.getReadMethod();
        readMethod.invoke(b, null);
    }
}
复制代码

注解 Annotation

小知识:

  1. 类型名:形容词+名词
  2. 方法名:动词+名词

####注解的概念 注解其实是一个类,写注解其实是建立注解的对象。注解至关于为程序打一种标记。javac编译工具、开发工具以及其余程序能够利用反射来了解你的类以及各类元素上有没有何种标记,就能够去作相应的事。

标记能够加在包、类型(Type,包括类,枚举,接口)、字段、方法、方法的参数以及局部变量上面。

下面是java的一些比较常见的注解:

Deprecated				//标记方法过期
Override 				//标记该方法是子类覆盖父类的方法,告诉编译器去检查
SuppressWarnings 		//去掉过期的删除线
复制代码

####注解的应用结构图

一共须要三个类:

例子:

这是一个注解:

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * Created by Administrator on 2016/9/16.
 */

@Retention(RetentionPolicy.RUNTIME)//注解保留在哪一个声明周期
@Target({ElementType.METHOD, ElementType.TYPE})//做用于什么元素身上
public @interface A {

}
复制代码

注解做用的类:

@A
public class AnnotationTest {

}
复制代码

获取注解(经过对AnnotationTest进行反射):

Class<AnnotationTest> c = AnnotationTest.class;
//判断是否有对应的注解
if (c.isAnnotationPresent(A.class)) {
	//获取注解
    A a = c.getAnnotation(A.class);
}
复制代码

####元注解,用来给注解添加注解的注解

@Target(ElementType.METHOD)			//声明注解能够做用在什么地方,特别注意TYPE是类、枚举、接口身上
@Retention(RetentionPolicy.SOURCE)	//声明注解是在哪一个声明周期的,分别是源代码,class文件,运行时
复制代码

####注意,多个属性的话须要用大括号括起来:

@Target({ElementType.METHOD, ElementType.TYPE})
复制代码

####注解的生命周期

  1. SOURCE:注解被保留到源文件阶段。当javac把源文件编译成.class文件的时候,就将相应的注解去掉。例如常见的Override、SuppressWarnings都属于SOURCE类型的生命周期,由于一旦代码编译以后该注解就没用了。
  2. CLASS:java虚拟机经过类加载器向内存中加载字节码的时候,就将相应的注解去掉,所以没法经过反射获取相应的注解。
  3. RUNTIME:注解保留在内存中的字节码上面了,虚拟机在运行字节码的时候,仍然可使用的注解。例如Deprecated,类被别人使用的时候,加载到内存,扫描,从二进制代码去看是否过期,而不是检查源代码。

####为注解增长属性

注解参数的可支持数据类型:

  1. 全部基本数据类型(int,float,boolean,byte,double,char,long,short)
  2. String类型
  3. Class类型
  4. enum类型
  5. Annotation类型
  6. 以上全部类型的数组

例子:

@Retention(RetentionPolicy.RUNTIME)//注解保留在哪一个声明周期
@Target({ElementType.METHOD, ElementType.TYPE})//做用于什么元素身上
public @interface A {

    String stringAttr();

    Class value() default Object.class;

    int[] arrAttr();

    Deprecated annoAttr() default @Deprecated;
}
复制代码

在使用注解的时候注意点:

  1. 当只有value须要设置值的时候(即只有value属性或者其余属性已经制定了default的时候),能够直接不写value,直接在括号里面写值,例如:

    @SuppressWarnings("deprecation")

  2. 当类型是数组的时候,若是元素只有一个,那么能够省略大括号,直接写一个元素便可。

经过反射得到注解以后,就能够随心去使用了:

Class<AnnotationTest> c = AnnotationTest.class;
if (c.isAnnotationPresent(A.class)) {
    A a = c.getAnnotation(A.class);
	//得到stringAttr属性的值
    System.out.println(a.stringAttr());
}
复制代码

泛型

####概念

集合,反射等等地方都使用到了泛型,免去了强制类型转换的不安全性问题,包括code阶段以及运行阶段。泛型是给编译器看的,让编译器拦截源程序中的非法输入,编译完之后就会去掉类型信息,保证程序的运行效率。对于参数化的泛型类型,getClass方法的返回值和原始类型彻底同样。

因此编译完之后,跳过编译器,经过反射就能够向集合添加其余类型的数据,例子以下:

List<Integer> list = new ArrayList<>();
//经过反射的方式取添加“非法类型”到集合当中
list.getClass().getMethod("add", Object.class).invoke(list, "abc");
System.out.println(list.get(0));
复制代码

####关于泛型的一些术语:

ArrayList<E>						泛型类型
ArrayList<E>中的E					类型变量或者类型参数
ArrayList<String>					参数化的类型
ArrayList<String>中的String			实际类型参数
ArrayList<String>中的<>读做type of
复制代码
  1. 用不用泛型,程序最终的运行结果都同样,用了有好处而已
  2. 参数化类型,不考虑类型参数的继承关系

例如,下面的这行代码是错误的,由于不考虑父子关系:

List<Object> list1 = new ArrayList<String>();
复制代码

泛型中的通配符 ?

不用Object,而是使用?表示任意类型。?通配符能够引用各类其余参数化的类型,?通配符定义的变量主要用做引用。类型参数没有赋值的时候,不能调用与类型参数有关的方法(方法中有泛型参数的方法)。

例子:

public static void printSise(List<?> l) {

    l.add(new Object());//这一局编译就报错
    Object o = l.get(1);//返回值有泛型,可是咱们能够转换为Object

    for (Object obj : l) {
        System.out.println(obj);
    }
}
复制代码

####泛型的上下边界,能够用&实现多个接口的限定

//上边界
List<? extends Number> l1 = new ArrayList<Integer>();

//下边界
List<? super Integer> l2 = new ArrayList<Object>();
复制代码

泛型的案例,以及各类集合(主要是MAP)的迭代方法:

####1.加强for循环遍历MAP,这是最多见的而且在大多数状况下也是最可取的遍历方式。注意:for-each循环在java 5中被引入因此该方法只能应用于java 5或更高的版本中。若是你遍历的是一个空的map对象,for-each循环将抛出NullPointerException,所以在遍历前你老是应该检查空引用。

Map<Integer, Integer> map = new HashMap<Integer, Integer>();

for (Map.Entry<Integer, Integer> entry : map.entrySet()) {

    entry.getKey();
	entry.getValue();

}
复制代码

####2.在for-each循环中遍历keys或values.若是只须要map中的键或者值,你能够经过keySet或values来实现遍历,而不是用entrySet。该方法比entrySet遍历在性能上稍好(快了10%),并且代码更加干净。

Map<Integer, Integer> map = new HashMap<Integer, Integer>();

//遍历map中的键

for (Integer key : map.keySet()) {

}

//遍历map中的值

for (Integer value : map.values()) {

}
复制代码

####3.使用迭代器,这种方法能够在迭代之中删除元素。

Map<Integer, Integer> map = new HashMap<Integer, Integer>();

Iterator<Map.Entry<Integer, Integer>> entries = map.entrySet().iterator();

while (entries.hasNext()) {

    Map.Entry<Integer, Integer> entry = entries.next();

    entry.getKey();
	entry.getValue());

}
复制代码

####4.经过键值去遍历,效率低下,通常不采用。

Map<Integer, Integer> map = new HashMap<Integer, Integer>();

for (Integer key : map.keySet()) {

    Integer value = map.get(key);

    System.out.println("Key = " + key + ", Value = " + value);

}
复制代码

####总结:

若是仅须要键(keys)或值(values)使用方法二。若是你使用的语言版本低于java 5,或是打算在遍历时删除entries,必须使用方法三。不然使用方法一(键值都要)。

####由C++的模板函数,引入自定义泛型

####java中的泛型不能彻底作到C++中的模板功能的缘由:

java中的泛型相似于C++中的模板,可是这种类似性仅限于表面,java中的泛型基本上是在编译器中实现,用于编译器执行类型检查和推断,而后生成普通的非泛型字节码,这种技术成为擦除。由于扩展虚拟机指令集来支持泛型被认为是没法接受的,这会为java厂商升级JVM形成困难。所以,泛型参数不一样不能构成重载。

  1. 泛型的实际类型不能是基本类型,只能是引用类型。
  2. 修饰符以后,返回值类型以前,用声明一种新的类型。
  3. 能够有多个类型变量,用逗号隔开。
  4. 类型参数的名字通常用一个大写字母来命名。
  5. 编译器不容许直接new T的数组或者对象。
  6. 能够用类型变量表示异常,称为参数化异常,用得很少。

例子:

private static <T> T[] swap(T[] arr, int i, int j) {
    T tmp = arr[i];
    arr[i] = arr[j];
    arr[j] = tmp;
    return arr;
}
复制代码

####参数的类型推断(比较复杂)

类型参数的类型推断:编译器判断泛型方法的实际类型参数的过程称为类型推断,类型推断是相对于知觉推断的,其实现方法是一种很是复杂的过程。根据调用泛型方法时实际传递参数类型或返回值的类型来推断,具体规则以下:

  1. 当某个类型变量值在整个参数列表中的全部参数和返回值中的一处被应用类,那么根据调用方法时该处的实际应用类型来肯定,这很容易凭着感受推断出来,即直接根据调用方法时传递的参数类型或返回值来决定泛型参数的类型,例如: swap(new String[3],3,4) --> static void swap(E[] a,int i,int j)
  2. 当某个类型变量在整个参数列表中的全部参数和返回值中的多处被应用了,若是调用方法时这多处的实际应用类型都对应同一种类型来肯定,这很容易凭着感受推断出来,例如: add(2,5) -->static T add (T a, T b)
  3. 当某个类型变量在整个参数列表中的全部参数和返回值中的多处被应用了,若是调用方法时这多处的实际应用类型对应到类不一样的类型,且没有使用返回值,这时候取多个参数中的最大交集类型,例如,下面语句实际对应的类型就是Number了,编译没问题,只是运行时出问题: fill(new Integer[3],3.5f)-->static void fill(T[], T v)//Integer∩Float = Number ,它们都是Number的子类
  4. 当某个类型变量在整个参数列表中的全部参数和返回值中的多处被应用了,若是调用方法时这多处的实际应用类型对应到了不一样的类型,而且使用返回值,这时候优先考虑返回值的类型,例如,下面语句实际对应的类型就是Integer了,编译将报告错误,将变量x的类型改成float,对比eclipse报告的错误提示,接着再将变量x类型改成Number,则没有了错误: int x = add(3,3.5f) -->static T add(T a,T b)

####定义泛型的类型

  1. 方法级别(上面已经讲过)

  2. 泛型的类型(类):多个方法使用的是同一个类型

    class A {

    }

注意,类里面的静态方法不能含有对象的泛型。可是能够有通常的泛型静态方法。例子:

public class A<T> {
    //编译器报错,由于静态方法能够避开对象的建立嘛
    public static void add(T t) {

    }

    //编译器不报错,单独分开
    public static <E> void set(E e) {

    }
}
复制代码

####难点:经过反射的方法把方法中的参数的类型提取出来

public static void main(String[] args) throws Exception {

    //经过反射的方法把方法中的参数的类型提取出来
    Method method = A.class.getMethod("test", List.class);
    //其返回的是参数的参数化的类型,里面的带有实际的参数类型
    Type[] types = method.getGenericParameterTypes();
    //将类型向参数化类型转换
    ParameterizedType type = (ParameterizedType) types[0];
    //获得原始类型(interface java.util.List)
    System.out.println(type.getRawType());
    //获得实际参数类型的类型(class java.lang.String)
    System.out.println(type.getActualTypeArguments()[0]);

}


public static void test(List<String> list) {

}
复制代码

类加载器的介绍

####类加载器的父子关系以及管辖范围

例子:获取而且打印类加载器:

public static void main(String[] args) {

    //打印出当前线程的类加载器
    System.out.println(Thread.currentThread().getContextClassLoader().getClass().getName());

    //第一个类默认由当前线程的类加载器去进行加载
    System.out.println(ClassLoaderTest.class.getClassLoader().getClass().getName());
    //System是由BootStrap类加载器加载的,是C/C++写的,BootStrap不须要其余加载器去加载
    //在java层面不能获取该类的引用
    System.out.println(System.class.getClassLoader() == null);

}
复制代码

类加载器的委托机制,至关于android中的事件传递,防止字节码的重复加载。

####自定义加载器

原理:ClassLoader有loadClass和findClass两个方法,loadClass会首先去找父加载器,若是找不到就会回传,若是传到本身的话,就会回调findClass方法来加载class。为了保证这一个流程不被破坏(模板方法设计模式),所以咱们须要覆盖的是findClass方法。

####自定义加载器能够实现字节码的加密解密

下面仅仅写出一些关键的步骤:

  1. 写一个须要被加密的类,而且编译生成.class文件

  2. 利用加密算法(好比与0xff异或)对.class文件文件进行加密,用输入流读进来,再用输出流输出到文件中。

  3. 自定义类加载器,继承ClassLoader。复写findClass方法,把加密事后的.class加载进来,转换成字节数组而且解密,利用ClassLoader的下面这个方法把class文件转换成字节码。

  4. 获得字节码就能够经过反射的方式进行newInstance等使用了。

    //获得class文件转换成字节码
     protected final Class<?> defineClass(byte[] b, int off, int len)
    复制代码

###代理

要为已存在的多个具备相同接口的目标类(已经开发好,或者没有源码)的各个方法增长一些系统功能,例如异常处理、日志、计算方法的运行时间、事务管理等。可使用代理,代理就有这样的好处。

JVM能够在运行期间动态生成出类的字节码,这种动态生成的类每每用做代理,成为动态代理。JVM生成的类必须实现一个或者多个接口,因此JVM生成的类智能用做具备相同家口的目标类的代理。

CGLIB库能够动态生成一个类的子类,一个类的子类也能够用做该类的代理类,因此若是要为一个没有实现接口的类生成动态代理类的话,可使用这个库。

###AOP面向切面编程的概念

####面向切面编程(AOP是Aspect Oriented Program的首字母缩写),在运行时,动态地将代码切入到类的指定方法、指定位置上的编程思想就是面向切面的编程。 通常而言,咱们管切入到指定类指定方法的代码片断称为切面,而切入到哪些类、哪些方法则叫切入点。有了AOP,咱们就能够把几个类共有的代码,抽取到一个切片中,等到须要时再切入对象中去,从而改变其原有的行为。 这样看来,AOP其实只是OOP的补充而已。OOP从横向上区分出一个个的类来,而AOP则从纵向上向对象中加入特定的代码。有了AOP,OOP变得立体了。若是加上时间维度,AOP使OOP由原来的二维变为三维了,由平面变成立体了。从技术上来讲,AOP基本上是经过代理机制实现的。 AOP在编程历史上能够说是里程碑式的,对OOP编程是一种十分有益的补充。

####例子

使用反射API,JVM动态生成Collection接口的代理类

//使用反射API,JVM动态生成Collection接口的代理类
Class<?> clazzProxy0 = Proxy.getProxyClass(Collection.class.getClassLoader(), Collection.class);

//输入这个动态生成的类的名字
System.out.println(clazzProxy0.getName());

//输出全部构造方法
printConstructor(clazzProxy0);

//输出全部方法
printMethod(clazzProxy0);

//使用这个类去构建对象
//不能使用无参数的构造函数,由于代理类只有一个有参数的构造函数
//clazzProxy0.newInstance();

Constructor<?> constructor = clazzProxy0.getConstructor(InvocationHandler.class);
Collection collection = (Collection) constructor.newInstance(new InvocationHandler() {
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        return null;
    }
});
复制代码

生成代理类对象须要传入InvocationHandler对象,代理类的方法调用会触发InvocationHandler的分发,InvocationHandler内部会对被代理类的对象的方法进行调用,而且插入一些指定的功能。

下面是打印全部构造方法与方法的函数

private static void printMethod(Class<?> clazz) {
    System.out.println("全部方法");
    for (Method m : clazz.getMethods()) {
        StringBuilder builder = new StringBuilder(m.getName());
        builder.append("(");

        Class[] types = m.getParameterTypes();
        for (Class<?> t : types) {
            builder.append(t.getName()).append(",");
        }
        if (types.length != 0) {
            builder.deleteCharAt(builder.length() - 1);
        }

        builder.append(")");

        System.out.println(builder.toString());
    }
}

private static void printConstructor(Class<?> clazz) {
    System.out.println("全部构造方法");
    for (Constructor c : clazz.getConstructors()) {
        StringBuilder builder = new StringBuilder(c.getName());
        builder.append("(");

        Class[] types = c.getParameterTypes();
        for (Class<?> t : types) {
            builder.append(t.getName()).append(",");
        }
        if (types.length != 0) {
            builder.deleteCharAt(builder.length() - 1);
        }

        builder.append(")");

        System.out.println(builder.toString());
    }
}
复制代码

一步到位:

//一步到位,获取代理类而且生成对象
Collection collection1 = (Collection) Proxy.newProxyInstance(Collection.class.getClassLoader(), new Class[]{Collection.class}, new InvocationHandler() {
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        return null;
    }
});
复制代码

下面给出实用的案例,在每一个方法的调用的时候插入广告:

/**
 * Created by Administrator on 2016/9/18.
 */
public class ProxyTest1 {

    //被代理的对象
    private static ArrayList<String> target = new ArrayList<>();

    public static void main(String[] args) {

        //一步到位,获取代理类而且生成对象
        Collection collection = (Collection) Proxy.newProxyInstance(Collection.class.getClassLoader(), new Class[]{Collection.class}, new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

                System.out.println("----广告开始----");

                Object returnVal = method.invoke(target, args);

                System.out.println("----广告结束----");

                return returnVal;


            }
        });

        collection.add("aad");
        collection.add("aad");
        collection.add("aad");
        collection.add("aad");
        System.out.println(collection.size());
    }

}
复制代码

固然,咱们但愿的是调用的东西是框架完成之后用户(配置)输入的,所以,咱们须要再提供接口:

public interface Advice {

    void before(Object proxy, Method method, Object[] args);

    void after(Object proxy, Method method, Object[] args);
}
复制代码

如上所示,为了方便,接口只提供两个简单的方法,分别在方法执行先后执行。

而后,咱们也把获取代理对象的方法封装一下,用户只须要传入接口的实现类便可。

public class ProxyTest1 {

    //被代理的对象
    private static ArrayList<String> target = new ArrayList<>();

    public static void main(String[] args) {

        //一步到位,获取代理类而且生成对象
        Collection collection = (Collection) getProxyInstance(Collection.class, new Advice() {
            @Override
            public void before(Object proxy, Method method, Object[] args) {
                System.out.println(method.getName() + "开始执行");
            }

            @Override
            public void after(Object proxy, Method method, Object[] args) {
                System.out.println(method.getName() + "结束执行");
            }
        });

        collection.add("aad");
        collection.size();
    }

    private static Object getProxyInstance(Class<?> clazz, Advice advice) {
        return Proxy.newProxyInstance(clazz.getClassLoader(), new Class[]{clazz}, new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

                advice.before(proxy, method, args);
                Object returnVal = method.invoke(target, args);
                advice.after(proxy, method, args);

                return returnVal;	
            }
        });
    }

}
复制代码

注意:接口的方法都会交给InvocationHandler分发处理,可是由Object继承过来的方法只有toString、equals、hashCode才会交给InvocationHandler处理。

###String、StringBuilder与StringBuffer的区别

  1. 在执行速度方面的比较:StringBuilder > StringBuffer

  2. StringBuffer与StringBuilder,他们是字符串变量,是可改变的对象,每当咱们用它们对字符串作操做时,其实是在一个对象上操做的,不像String同样建立一些对象进行操做,因此速度就快了。

  3. StringBuilder:线程非安全的   StringBuffer:线程安全的

    当咱们在字符串缓冲去被多个线程使用是,JVM不能保证StringBuilder的操做是安全的,虽然他的速度最快,可是能够保证StringBuffer是能够正确操做的。固然大多数状况下就是咱们是在单线程下进行的操做,因此大多数状况下是建议用StringBuilder而不用StringBuffer的,就是速度的缘由。

对于三者使用的总结:

  1. 若是要操做少许的数据用 = String
  2. 单线程操做字符串缓冲区 下操做大量数据 = StringBuilder
  3. 多线程操做字符串缓冲区 下操做大量数据 = StringBuffer

###Overload(重载)与Override(重写)的区别

  1. 重载是指不一样的函数使用相同的函数名,可是函数的参数个数或类型不一样。调用的时候根据函数的参数来区别不一样的函数。

  2. 覆盖(也叫重写)是指在派生类中从新对基类中的虚函数(注意是虚函数)从新实现。即函数名和参数都同样,只是函数的实现体不同。

  3. 隐藏是指派生类中的函数把基类中相同名字的函数屏蔽掉了。

  4. 方法的重写(Overriding)和重载(Overloading)是Java多态性的不一样表现。 重写(Overriding)是父类与子类之间多态性的一种表现,而重载(Overloading)是一个类中多态性的一种表现。

    override(重写)

    1. 方法名、参数、返回值相同。
     2. 子类方法不能缩小父类方法的访问权限。	
     3. 子类方法不能抛出比父类方法更多的异常(但子类方法能够不抛出异常)。	
     4. 存在于父类和子类之间。
     5. 方法被定义为final不能被重写。
    复制代码

    overload(重载)

    1. 参数类型、个数、顺序至少有一个不相同。 
     2. 不能重载只有返回值不一样的方法名。
     3. 存在于父类和子类、同类中。
    复制代码

==、hashCode与equals的区别:

  1. 基本数据类型,也称原始数据类型。byte,short,char,int,long,float,double,boolean他们之间的比较,应用双等号(==),比较的是他们的值。
  2. 复合数据类型(类)复合数据类型之间进行equals比较,在没有覆写equals方法的状况下,他们之间的比较仍是基于他们在内存中的存放位置的地址值的,由于Object的equals方法也是用双等号(==)进行比较的,因此比较后的结果跟双等号(==)的结果相同。
  3. 将对象放入到集合中时,首先判断要放入对象的hashcode值与集合中的任意一个元素的hashcode值是否相等,若是不相等直接将该对象放入集合中。若是hashcode值相等,而后再经过equals方法判断要放入对象与集合中的任意一个对象是否相等,若是equals判断不相等,直接将该元素放入到集合中,不然不放入。

规则:

  1. 若是两个对象根据equals()方法比较是相等的,那么调用这两个对象中任意一个对象的hashCode方法都必须产生一样的整数结果。
  2. 若是两个对象根据equals()方法比较是不相等的,那么调用这两个对象中任意一个对象的hashCode方法,则不必定要产生相同的整数结果。可是程序员应该知道,给不相等的对象产生大相径庭的整数结果,有可能提升散列表的性能。

HashMap、HashTable的区别

  1. HashMap几乎能够等价于Hashtable,除了HashMap是非synchronized的,并能够接受null(HashMap能够接受为null的键值(key)和值(value),而Hashtable则不行)。
  2. HashMap是非synchronized,而Hashtable是synchronized,这意味着Hashtable是线程安全的,多个线程能够共享一个Hashtable;而若是没有正确的同步的话,多个线程是不能共享HashMap的。Java 5提供了ConcurrentHashMap,它是HashTable的替代,比HashTable的扩展性更好。
  3. 二者使用的迭代方式不同。

sychronized意味着在一次仅有一个线程可以更改Hashtable。就是说任何线程要更新Hashtable时要首先得到同步锁,其它线程要等到同步锁被释放以后才能再次得到同步锁更新Hashtable。

HashMap能够经过下面的语句进行同步: Map m = Collections.synchronizeMap(hashMap);

总结:

Hashtable和HashMap有几个主要的不一样:线程安全以及速度。仅在你须要彻底的线程安全的时候使用Hashtable,而若是你使用Java 5或以上的话,请使用ConcurrentHashMap吧。

ArrayList、Vector的区别

  1. Vector是线程安全的,也就是说是它的方法之间是线程同步的,而ArrayList是线程序不安全的。
  2. 数据增加:ArrayList与Vector都有一个初始的容量大小,当存储进它们里面的元素的个数超过了容量时,就须要增长ArrayList与Vector的存储空间,每次要增长存储空间时,不是只增长一个存储单元,而是增长多个存储单元,每次增长的存储单元的个数在内存空间利用与程序效率之间要取得必定的平衡。Vector默认增加为原来两倍,而ArrayList的增加策略在文档中没有明确规定(从源代码看到的是增加为原来的1.5倍)。ArrayList与Vector均可以设置初始的空间大小,Vector还能够设置增加的空间大小,而ArrayList没有提供设置增加空间的方法。

###Java中建立对象的四种方法 收藏Java中建立对象的四种方式

  1. 用new语句建立对象,这是最多见的建立对象的方法。
  2. 运用反射手段,调用java.lang.Class或者java.lang.reflect.Constructor类的newInstance()实例方法。
  3. 调用对象的clone()方法。
  4. 运用反序列化手段,搜索调用java.io.ObjectInputStream对象的 readObject()方法。

###说说静态变量、静态代码块加载的过程和时机? 回答:当类加载器将类加载到JVM中的时候就会建立静态变量,静态变量加载的时候就会分配内存空间。静态代码块的代码只会在类第一次初始化,也就是第一次被使用的时候执行一次。

##MVC、MVVP、MVP设计模式

  1. MVC:MVC的耦合性仍是比较高,View能够直接访问Model
  2. MVP:Model、View、Presenter,View不能直接访问Model
  3. MVVM:View和Model的双向绑定,相似于Data-Binding

###JAVA容器的架构图

若是以为个人文字对你有所帮助的话,欢迎关注个人公众号:

公众号:Android开发进阶

个人群欢迎你们进来探讨各类技术与非技术的话题,有兴趣的朋友们加我私人微信huannan88,我拉你进群交(♂)流(♀)

相关文章
相关标签/搜索