基础知识
计算机中,二进制都是补码的形式表示的,补码的首位是符号位。
为什么在计算机中使用补码?
正数在计算机中表示形式与真实世界的二进制表示形式一致;
正数不存在什么补码。采用补码就是用来表示负数的。
例如:
1的原码: [0000 0000 0000 0000 0000 0000 0000 0001]
1的反码:[1111 1111 1111 1111 1111 1111 1111 1110]
则-1在计算机中就用1的补码表示:
1的补码: [1111 1111 1111 1111 1111 1111 1111 1111]
那么0没有正数和负数之分,怎么办。
正数0:[0000 0000 0000 0000 0000 0000 0000 0000]
负数0:按照补码的作用来看,可以对正数0进行补码操作后得到负数0:
[1000 0000 0000 0000 0000 0000 0000 0000]
0不区分正负数,却在计算机中占用两个编码?
这样吧,规定其中的[1000 0000 0000 0000 0000 0000 0000 0000] = -2147483648 ;
不仅修复了0的符号以及存在两个编码的问题, 而且还能够多表示一个最低数。
要知道int的最大正数 [0111 1111 1111 1111 1111 1111 1111 1111]的十进制是2147483647。 按理说最小值应该为-2147483647(即正数的补码): [1000 0000 0000 0000 0000 0000 0000 0001];但是0不能占两种码,其中被“嫌弃”的码给了最小的负数,在int中就是-2147483648;所以int取值范围:-2147483648 ~2147483647。

public class App {
public static void main(String[] args) {
// 左移 左移后,低位补0
//无符号左移<<<和左移<<是一样的概念
// 5的二级制:0000 0000 0000 0000 0000 0000 0000 0101
System.out.println(5<<5); // 160=5*2*2*2*2*2
// >>> 无符号右移 低位溢出,高位补0
//无符号的意思是将符号位当作数字位看待
// 5的二级制:0000 0000 0000 0000 0000 0000 0000 0101
// >>> 0000 0000 0000 0000 0000 0000 0000 0101
// 0000 0000 0000 0000 0000 0000 0000 0001
System.out.println(5>>>2); //1
//位与&
// 0000 0000 0000 0000 0000 0000 0000 0101 5
// & 0000 0000 0000 0000 0000 0000 0000 0011 3
// 0000 0000 0000 0000 0000 0000 0000 0001 1
System.out.println(5&3); //1
//位异或 ^
// 0000 0000 0000 0000 0000 0000 0000 0101 5
// ^ 0000 0000 0000 0000 0000 0000 0000 0011 3
// 0000 0000 0000 0000 0000 0000 0000 0110 6
System.out.println(5^3); // 6
// 位非 ~ 一元操作符 按位取反
// 0000 0000 0000 0000 0000 0000 0000 0101 5
// ~ 1111 1111 1111 1111 1111 1111 1111 1010 -6
// 原理:11111111111111111111111111111111 = -1
System.out.println(~5);// -6
}
}
其中整数的数据类型除了符号位,剩下的都是数值位;而浮点数值除了符号位,剩下的位数由指数位+尾数位组成。这就解释以下问题:

a占32位;b占64位;a == b; 两者32中的数值完全一致,故相等。
c占32位;d占64位;c == d; 两者符号位相等,指数位相等,但尾数位不相等,故不相等。
关于编码的事情:
由于计算机最先由美国发明,为了将英文和符号存入计算机,必须先将英文和符号用二进制做编码和对照。这个对照️表:ASCII。
后来发展到中国有了GBK。
为了统一全世界编码,最后unicode制定了unicode 规范,用三个字节表示!其中第一个字节表示:后面两个字节表示的是一个字符还是两个字符。
美国、西欧和中国不干了,美国和西欧的字符只需要一个字节就够,凭什么用三个字节,中国也不干,中国的字符用两个字节就够了!
后来在unicode规范下,衍生出了UTF-8/16/32编码方式,其特点就是根据编码分类,根据不同的组,其字符占用不同的字节!
char 类型:
- 其默认值:16进制的'\u0000'。
- 可以运算:按照UTF-16编码表对应的数值进行运算
- 数字取值范围是0~65535。
public class PrimitiveType {
char c; // 使用默认值
@Test
public void testChar() {
PrimitiveType t = new PrimitiveType();
char c1 = '\u0000'; // 指定默认值
char c2 = '\0';
char c3 = '\u2400'; // 对应字符 NUL
char c4 = '\u0020'; // 对应字符 空格
System.out.println("c1: " + c1); //c1:
System.out.println("c2: " + c2);//c2:
System.out.println("c3: " + c3);//c3: NUL
System.out.println("c4: " + c4);//c4:
System.out.println("c: " + c);//c:
System.out.println("c1==c: " + (c1 == t.c));//c1==c: true
System.out.println("c1==c2: " + (c1 == c2));//c1==c2: true
System.out.println("c1==c3: " + (c1 == c3));//c1==c3: false
System.out.println("c1==c4: " + (c1 == c4));//c1==c4: false
}
}
运算:
char m = 'a'; // 输出 a
char m = 'a' + 'b'; // 提升为 int 类型相加 97 + 98, 然后输出对应的字符.
int m = 'a' + 'b'; // 输出 195.
char m = 'a' + b; // 报错, 因为b是一个赋值的变量
char m = 195; // 输出字符编码表中对应的字符
char m = '195'; // 报错, 因为有单引号, 表示字符, 只允许放单个字符
char m = 'a' + 1; // 输出 b, 提升为 int 类型相加, 计算结果 98 对应的字符是b
char m ='中'+'国'+'国'+'国'; // 报错. 计算结果 86820 超出范围 65535
int m ='中'+'国'+'国'+'国'; // 输出 86820
System.out.println('中'+"国"); // 输出中国.
char m ='中'+"国"; // 报错. String 无法转换为 char.
String str = "我";
byte[] c1 = str.getBytes();
System.out.println(c1.length);
//结果:可能是 2、3、4 之一。
//因为getBytes() 方法会根据当前默认的字符编码格式获取字节数组,
//gbk/gb2312 占 2 位, utf-8 占 3 位, utf-16 占 4 位, unicode 占 4 位。
//通过 str.getBytes("GBK") 或 str.getBytes("UTF-8") 来指定编码方式。
一、面向对象特性
1.封装
将类的某些信息隐藏在类内部,不允许外部程序直接访问,而是通过该类提供的方法来实现对隐藏信息的操作和访问。
1.1 访问修饰符
- default : 在同一包内可见,无修饰符。使用对象:类、接口、变量、方法。
- private : 在同一类内可见。使用对象:变量、方法。
- public : 对所有类可见。使用对象:类、接口、变量、方法。
- protected : 同一包内的类和所有子类可见。使用对象:变量、方法。
protected详解


People.java
package com.xzb.javase.enclosure;
public class People {
protected void say(){
System.out.println("protected……People");
}
}
Human.java
package com.xzb.javase.enclosure;
public class Human {
public void sayHuman(){
People people = new People();
people.say();
}
}
Client.java
package com.xzb.javase.enclosure2;
import com.xzb.javase.enclosure.People;
public class Client {
public void cloneTest(){
//protected : 同一包内的类和所有子类可见。使用对象:变量、方法。
//虽然People是Object的子类,但是Client作用范围内不能使用People爹的clone方法;只能用自己爹的clone方法。
// 虽然是一个爹。但是!我的地盘听我的。
People people = new People();
// people.clone(); //编译失败
try {
clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
People people = new People();
//people.say();//编译失败
}
}
default详解
package com.xzb.javase.enclosure2;
import com.xzb.javase.enclosure.People;
public class Client extends People{
public static void main(String[] args) {
People people = new People();
//people.say();//编译失败
}
}
1.2 this关键字
- this关键字代表当前对象。
- this.属性 操作当前对象的属性。
- this.方法 调用当前对象的方法。
- this() 构造方法之间的互相调用。
1.3 内部类
- Java内部类的分类:成员内部类、局部内部类、匿名内部类和静态内部类。
- 内部类的使用场景
1、几个类的逻辑关系很强,同时想对外隐藏这些类;
2、线程类中;
3、类中要实现多继承; - 内部类作用
Java不支持多继承,内部类可以完善Java的多继承机制。
public class OuterClass {
private String str;
//1.成员内部类:作为外部类的成员属性使用
public class MemberInnerClass {
public void display() {
str = "MemberInnerClass";
System.out.println(str);//可以访问外部类成员
}
}
//2.局部内部类:内部类作用仅限于作用域内
public void display(boolean b) {
//2.1 定义在方法内
class MethodInnerClass {
public void display() {
System.out.println("MethodInnerClass");
}
}
new MethodInnerClass().display();
//2.2 定义在作用域内
if (b) {
class RegionInnerClass {
public void display() {
System.out.println("RegionInnerClass");
}
}
new RegionInnerClass().display();
}
}
//3.静态内部类:创建是不需要依赖于外部类,不能使用任何外部类的非static成员变量和方法
static class StaticInnerClass {
public void display() {
System.out.println("StaticInnerClass");
}
}
//4.匿名内部类:
public void display() {
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("AnonymInnerClass");
}
}).start();
}
public static void main(String[] args) {
OuterClass outerClass = new OuterClass();
outerClass.new MemberInnerClass().display();
outerClass.display(true);
new StaticInnerClass().display();
outerClass.display();
}
}
2.继承
2.1 继承的初始化顺序
public class Test {
public static void main(String[] args) {
Child c=new Child();
}
}
class Parent {
public static PrintMessage a=new PrintMessage("父类静态成员被初始化");
private PrintMessage b=new PrintMessage("父类非静态成员被初始化");
static{
System.out.println("父类的静态代码块被执行");
}
{
System.out.println("父类的非静态代码块被执行");
}
public Parent(){
System.out.println("父类的构造方法被执行");
}
}
class Child extends Parent{
public static PrintMessage a1=new PrintMessage("子类静态成员被初始化");
private PrintMessage b1=new PrintMessage("子类非静态成员被初始化");
static {
System.out.println("子类的静态代码块被执行");
}
{
System.out.println("子类的非静态代码块被执行");
}
public Child(){
System.out.println("子类的构造函数被执行");
}
}
class PrintMessage{
public PrintMessage(String mes){
System.out.println(mes);
}
}

2.2 final关键字
- final 修饰类,则该类不允许被继承。
- final 修饰方法,则该方法不允许被重写。
- final 修饰属性,则该类的该属性不会进行隐式的初始化,所以 该final 属性的初始化属性必须有值,或在构造方法中赋值(但只能选其一,且必须选其一,因为没有默认值!),且初始化之后就不能改了,只能赋值一次。
- final 修饰变量,则该变量的值只能赋一次值,在声明变量的时候才能赋值,即变为常量。String的变量就是被final修饰,这样导致变量变成常量,被放在常量池中。
2.3 super关键字
在对象的内部使用,可以代表父类对象。
- 访问父类的属性:super.age
- 访问父类的方法:super.eat()
super的应用:
子类的构造过程中必须调用父类的构造方法。这个过程我们使用super关键字隐式调用。如果自己用super关键字在子类里调用父类的构造方法,则必须在子类的构造方法中的第一行。
1.子类构造方法没有通过super显式调用父类的有参构造方法,也没有通过this显式调用自身其他的构造方法,系统默认调用父类的无参构造方法。Super()。
2.子类构造方法通过super显式调用父类的有参构造方法,执行父类相应构造方法,而不执行父类无参构造方法。
3.多态
3.1 引用多态
父类的引用可以指向本类的对象,可以指向子类的对象;
3.2 方法多态
本类对象和子类对象,同样都是父类的引用,当我们指向不同的对象时,它们调用的方法也是多态的。
- 本类对象时,调用的方法为本类方法;
- 创建子类对象时,调用的方法为子类重写的方法或者继承的方法;
3.3 抽象类
抽象类前使用abstract关键字修饰,则该类为抽象类。
- 抽象类不能被实例化。只有抽象类的非抽象子类可以创建对象。
- 抽象类中不一定包含抽象方法,但是有抽象方法的类必定是抽象类。
- 抽象类中的抽象方法只是声明,不包含方法体。
- 构造方法,类方法(用 static 修饰的方法)不能声明为抽象方法。
- 抽象类的子类必须给出抽象类中的抽象方法的具体实现,除非该子类也是抽象类。
3.4 接口
- 接口中每一个方法也是隐式抽象的,接口中的方法会被隐式的指定为 public abstract(只能是 public abstract,其他修饰符都会报错)。
- 接口中可以含有变量,但是接口中的变量会被隐式的指定为 public static final 变量(并且只能是 public,用 private 修饰会报编译错误)。
- 接口中的方法是不能在接口中实现的,只能由实现接口的类来实现接口中的方法。
抽象类是定义模版。接口类是定义规范。
二、Lambda表达式
Lambda是为了简化匿名内部接口。
使用Lambda与匿名内部类的区别
- 所需类型不一样
匿名内部类所需的是类,可以是抽象类也可以是接口。Lambda所需的必须是接口。 - 实现原理不同
匿名内部类是在编译后生成一个class文件。
Lambda是在运行时动态生成的class文件,且会生成一个新的方法。

三、方法引用
函数式接口是Lambda的基础,而方法引用是Lambda的孪生兄弟。
3.1 应用场景
来看一个简单的函数式接口以应用Lambda表达式:
public class Demo {
private static void printString(String str,Consumer<String> sss) {
sss.accept(str);
}
public static void main(String[] args) {
printString("xzb", new Consumer<String>() {
@Override
public void accept(String s) {
System.out.println(s);//整个逻辑已经有实现了。
}
});
//lamdba
printString("xzb1",(s)-> System.out.println(s));
//方法引用
printString("xzb3",System.out::println);
}
}
双冒号 :: 为引用运算符,而它所在的表达式被称为方法引用。如果Lambda要表达的函数方案已经存在于某个方 法的实现中,那么则可以通过双冒号来引用该方法作为Lambda的替代者。
3.2 对象名引用成员方法
定义一个打印的函数式接口:
@FunctionalInterface
public interface Printable {
void print(String s);
}
package com.xzb.javase.lambda;
public class DemoMethodReference {
public static void printString(Printable p){
p.print("Hello");
}
public static void main(String[] args) {
// 使用匿名内部类
printString(new Printable() {
@Override
public void print(String s) {
System.out.println(s.toUpperCase());
}
});
//使用lamdba
printString((s)->{
System.out.println(s.toUpperCase());
});
//使用方法引用
MethodRerObject obj = new MethodRerObject();
printString(obj::printUpperCaseString);
}
static class MethodRerObject {
public void printUpperCaseString(String str){
System.out.println(str.toUpperCase());
}
}
}
3.3 静态方法引用
@FunctionalInterface
public interface Calcable {
int calsAbs(int number);
}
/*
通过类名引用静态成员方法
类已经存在,静态成员方法也已经存在
就可以通过类名直接引用静态成员方法
*/
public class Demo01StaticMethodReference {
public static int method(int number,Calcable c){
return c.calsAbs(number);
}
public static void main(String[] args) {
//lamdba
int number = method(-10,(n)->{
return Math.abs(n); //对参数进行绝对值得计算并返回结果;该逻辑已经有实现类:Math
})
//方法引用:
int number2 = method(-10,Math::abs);
}
}
3.4 super引用父类成员方法
@FunctionalInterface
public interface Greetable {
//定义一个见面的方法
void greet();
}
public class Human {
public void sayHello(){
System.out.println("Hello 我是Human!");
}
}
public class Man extends Human{
//定义一个方法参数传递Greetable接口
public void method(Greetable g){
g.greet();
}
public void show(){
//调用method方法,方法的参数Greetable是一个函数式接口,所以可以传递Lambda
//因为有子父类关系,所以存在的一个关键字super,代表父类,所以我们可以直接使用super调用父类的成员方法
/* method(()->{
super.sayHello();
});*/
method(super::sayHello);
}
public static void main(String[] args) {
new Man().show();
}
}
3.5 this引用本类方法
@FunctionalInterface
public interface Richable {
void buy();
}
public class Husband {
//定义一个买房子的方法
public void buyHouse(){
System.out.println("北京二环内买一套四合院!");
}
//定义一个结婚的方法,参数传递Richable接口
public void marry(Richable r){
r.buy();
}
//定义一个非常高兴的方法
public void soHappy(){
//调用结婚的方法,方法的参数Richable是一个函数式接口,传递Lambda表达式
/* marry(()->{
//使用this.成员方法,调用本类买房子的方法
this.buyHouse();
});*/
marry(this::buyHouse);
}
public static void main(String[] args) {
new Husband().soHappy();
}
}
3.6 类构造函数的引用
public class Person {
private String name;
public Person() {
}
public Person(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
@FunctionalInterface
public interface PersonBuilder {
//定义一个方法,根据传递的姓名,创建Person对象返回
Person builderPerson(String name);
}
public class Demo {
//定义一个方法,参数传递姓名和PersonBuilder接口,方法中通过姓名创建Person对象
public static void printName(String name,PersonBuilder pb){
Person person = pb.builderPerson(name);
System.out.println(person.getName());
}
//lamdba
public static void main(String[] args) {
printName("迪丽热巴",(String name)->{
return new Person(name);
});
//方法引用
printName("古力娜扎",Person::new);//使用Person类的带参构造方法,通过传递的姓名创建对象
}
}
3.7 数组构造器的引用
@FunctionalInterface
public interface ArrayBuilder {
//定义一个创建int类型数组的方法,参数传递数组的长度,返回创建好的int类型数组
int[] builderArray(int length);
}
public class Demo {
public static int[] createArray(int length, ArrayBuilder ab){
return ab.builderArray(length);
}
public static void main(String[] args) {
//调用createArray方法,传递数组的长度和Lambda表达式
int[] arr1 = createArray(10,(len)->{
return new int[len];
});
System.out.println(arr1.length);//10
//方法引用
int[] arr2 =createArray(10,int[]::new);
System.out.println(arr2.length);//10
}
}
四、反射
将类的各个组成部分封装为其他对象,这就是反射机制。
好处:可以在程序运行过程中,操作这些对象;可以解耦,提高程序的可扩展性。
内省和反射浅谈
反射可以操作各种类的属性,而内省只是通过反射来操作JavaBean的属性;
内省设置属性值肯定会调用seter方法,反射可以不用;
内省一般的做法是通过类Introspector来获取某个对象的BeanInfo信息,然后通过BeanInfo来获取属性的描述器(PropertyDescriptor),通过这个属性描述器就可以获取某个属性对应的getter/setter方法,然后我们就可以通过反射机制来调用这些方法。


获取Class对象的方式:
- Class.forName("全类名"):将字节码文件加载进内存,返回Class对象;多用于配置文件,将类名定义在配置文件中。读取文件,加载类。
- 类名.class:通过类名的属性class获取;多用于参数的传递。
- 对象.getClass():getClass()方法在Object类中定义着;多用于对象的获取字节码的方式。
同一个字节码文件(*.class)在一次程序运行过程中,只会被加载一次,不论通过哪一种方式获取的Class对象都是同一个。
Class对象功能
* 获取功能:
1. 获取成员变量们
* Field[] getFields()
* Field getField(String name)
* Field[] getDeclaredFields()
* Field getDeclaredField(String name)
2. 获取构造方法们
* Constructor<?>[] getConstructors()
* Constructor<T> getConstructor(类<?>... parameterTypes)
* Constructor<T> getDeclaredConstructor(类<?>... parameterTypes)
* Constructor<?>[] getDeclaredConstructors()
3. 获取成员方法们:
* Method[] getMethods()
* Method getMethod(String name, 类<?>... parameterTypes)
* Method[] getDeclaredMethods()
* Method getDeclaredMethod(String name, 类<?>... parameterTypes)
4. 获取类名
* String getName()
Person:
public class Person {
private String name;
private int age;
public String a;
protected String b;
String c;
private String d;
public Person() {
}
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
", a='" + a + '\'' +
", b='" + b + '\'' +
", c='" + c + '\'' +
", d='" + d + '\'' +
'}';
}
public void eat(){
System.out.println("eat...");
}
public void eat(String food){
System.out.println("eat..."+food);
}
}
1.获取成员变量
public class ReflectDemo2 {
public static void main(String[] args) throws Exception {
//0.获取Person的Class对象
Class personClass = Person.class;
//1.Field[] getFields()获取所有public修饰的成员变量
Field[] fields = personClass.getFields();
for (Field field : fields) {
System.out.println(field);
}
System.out.println("------------");
//2.Field getField(String name)
Field a = personClass.getField("a");
//获取成员变量a 的值
Person p = new Person();
Object value = a.get(p);
System.out.println(value);
//设置a的值
a.set(p,"张三");
System.out.println(p);
System.out.println("===================");
//Field[] getDeclaredFields():获取所有的成员变量,不考虑修饰符
Field[] declaredFields = personClass.getDeclaredFields();
for (Field declaredField : declaredFields) {
System.out.println(declaredField);
}
//Field getDeclaredField(String name)
Field d = personClass.getDeclaredField("d");
//忽略访问权限修饰符的安全检查
d.setAccessible(true);//暴力反射
Object value2 = d.get(p);
System.out.println(value2);
}
}
2.获取构造方法
public class ReflectDemo3 {
public static void main(String[] args) throws Exception {
//0.获取Person的Class对象
Class personClass = Person.class;
//Constructor<T> getConstructor(类<?>... parameterTypes)
Constructor constructor = personClass.getConstructor(String.class, int.class);
System.out.println(constructor);
//创建对象
Object person = constructor.newInstance("张三", 23);
System.out.println(person);
System.out.println("----------");
Constructor constructor1 = personClass.getConstructor();
System.out.println(constructor1);
//创建对象 空参构造方法使用Class.newInstance()
Object person1 = constructor1.newInstance();
System.out.println(person1);
Object o = personClass.newInstance();
System.out.println(o);
//constructor1.setAccessible(true);
}
}
3. 获取成员方法
public class ReflectDemo4 {
public static void main(String[] args) throws Exception {
//0.获取Person的Class对象
Class personClass = Person.class;
//获取指定名称的方法
Method eat_method = personClass.getMethod("eat");
Person p = new Person();
//执行方法
eat_method.invoke(p);
Method eat_method2 = personClass.getMethod("eat", String.class);
//执行方法
eat_method2.invoke(p,"饭");
System.out.println("-----------------");
//获取所有public修饰的方法
Method[] methods = personClass.getMethods();
for (Method method : methods) {
System.out.println(method);
String name = method.getName();
System.out.println(name);
//method.setAccessible(true);
}
//获取类名
String className = personClass.getName();
System.out.println(className);//cn.itcast.domain.Person
}
}
应用:模拟Spring
User :
public class User {
private String name;
private String nick;
private int age;
public User(){
System.out.println("我是无参构造方法");
}
public User(int age){
this.age=age;
System.out.println("我是有参构造方法,初始年龄:"+age);
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getNick() {
return nick;
}
public void setNick(String nick) {
this.nick = nick;
}
public void myFunction(){
System.out.println("我是自定义方法");
}
public void pay(){
System.out.println("我是pay()");
}
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
", nick='" + nick + '\'' +
", age=" + age +
'}';
}
}
Springs :
假设有个配置文件
<bean id="user" class="com.User">
<constructor-arg type="java.lang.Integer" value="30" />
<property name="name" value="xzb" />
<property name="nick" value="abo" />
<property name="age" value="20" />
</bean>
假设已经读取配置文件:
import com.User;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
public class Springs {
public static void main(String[] args) throws Exception {
// IOC 和 DI 只是操作对象属性。
//模拟IOC
//1.读取 class="com.User" 反射得到对象
Class clazz = Class.forName("com.User");
Object obj = clazz.newInstance();
System.out.println(obj);
//2.1 读取 <constructor-arg type="java.lang.Integer" value="30" />
// 对象属性注入 使用构造方法 模拟DI
Constructor con = clazz.getConstructor(int.class);
Object objCon = con.newInstance(30);
System.out.println(objCon);
//2.2 读取 <property />中的值
//无参对象属性注入 使用set方法 模拟DI
System.out.println("----------");
Field[] fields = clazz.getDeclaredFields();
for (Field field : fields) {
field.setAccessible(true);//暴力反射
if ("name".equals(field.getName())) {
//field.set(obj, "xzb"); 可以直接注入,为了模拟使用set方式
Method m = clazz.getDeclaredMethod("setName", String.class);
m.invoke(obj,"xzb");
}
if ("nick".equals(field.getName())) {
field.set(obj,"abo");
}
if ("age".equals(field.getName())) {
field.set(obj,20);
}
}
System.out.println(obj);
//3.放入容器
Map beans = new HashMap<String, Object>();
beans.put(clazz.getName(),obj);
//5.从容器取对象
User bean = (User) beans.get("com.User");
System.out.println(bean);
//AOP操作对象的方法
//模拟AOP
//CGLIB生成代理对象
final User user = bean;
User userProxy = (User) Enhancer.create(clazz,new MethodInterceptor(){
public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
if("pay".equals(method.getName())){
System.out.println("BEFORE");
method.invoke(user, args);
System.out.println("AFTER");
}else{
method.invoke(user, args);
}
return null;
}
});
//代理对象放入容器
beans.put("proxy",userProxy);
//6.从容器取对象
User beanProxy = (User)beans.get("proxy");
beanProxy.pay();
beanProxy.myFunction();
System.out.println(beanProxy.getClass());
}
}

五、注解
注解概念:说明程序的,给计算机看的。
定义:注解(Annotation),也叫元数据。一种代码级别的说明。它是JDK1.5及以后版本引入的一个特性,与类、接口、枚举是在同一个层次。它可以声明在包、类、字段、方法、局部变量、方法参数等的前面,用来对这些元素进行说明,注释。
注解作用分类:
①编写文档:通过代码里标识的注解生成文档【生成文档doc文档】
②代码分析:通过代码里标识的注解对代码进行分析【使用反射】
③编译检查:通过代码里标识的注解让编译器能够实现基本的编译检查【Override】
5.1 JDK内置注解
- @Override :检测被该注解标注的方法是否是继承自父类(接口)的。
- @Deprecated:该注解标注的内容,表示已过时。
- @SuppressWarnings:压制警告,一般传递参数all @SuppressWarnings("all")。
5.2 自定义注解
本质:注解本质上就是一个接口,该接口默认继承Annotation接口,自定义注解本质是一个接口类。它的实现类在jvm运行时会自动帮我们创建它的实现类。
public interface MyAnno extends java.lang.annotation.Annotation {}
格式:
元注解
public @interface 注解名称{
属性列表;
}
属性:接口中的抽象方法。
- 属性的返回值类型有下列取值: 基本类型、String、枚举、注解、以上类型的数组。
- 定义了属性,在使用时需要给属性赋值
- 如果定义属性时,使用default关键字给属性默认初始化值,则使用注解时,可以不进行属性的赋值。
- 如果只有一个属性需要赋值,并且属性的名称是value,则value可以省略,直接定义值即可。
- 数组赋值时,值使用{}包裹。如果数组中只有一个值,则{}可以省略。
元注解:用于描述注解的注解
- @Target:描述注解能够作用的位置, ElementType取值:
TYPE:可以作用于类上;METHOD:可以作用于方法上;FIELD:可以作用于成员变量上。 - @Retention:描述注解被保留的阶段, @Retention(RetentionPolicy.RUNTIME):当前被描述的注解,会保留到class字节码文件中,并被JVM读取到。
- @Documented:描述注解是否被抽取到api文档中。
- @Inherited:描述注解是否被子类继承。
5.2 注解使用 模拟注解版Spring
定义注解
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Service {
String value();
}
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface Resource {
}
Spring待管理的User:
@Service(value = "user888")
public class User {
@Resource
private String name;
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
'}';
}
}
使用注解
public class SpringAnoCheck {
public static void main(String[] args) throws Exception {
User user = new User();
Map beans = new HashMap<String, Object>();
// 1.获得字节码文件
Class cls = user.getClass();
// 2.检查User是否有@Service注解,有:放入IOC
if(cls.isAnnotationPresent(Service.class)){
Service service = (Service)cls.getAnnotation(Service.class);
beans.put(service.value(),user);
}
//3.检查属性是否有@Resource注解,有初始化
Field[] fields = cls.getDeclaredFields();
for (Field field : fields) {
field.setAccessible(true);//暴力反射
if (field.isAnnotationPresent(Resource.class)) {
field.set(user, "xzb");
}
}
//4.从容器取对象
User bean = (User) beans.get("user888");
System.out.println(bean);
}
}

总结:注解可替代配置文件,该“配置文件(注解)”的信息在Class文件中。