16.2.2 创建对象
之前咱们创建对象是很简单的事情,直接new。
总结,到目前为止,我们要拿到某个类的对象用过哪些方式?
(1)直接new
Student stu = new Student();
(2)调用方法
A:案例1
ArrayList<String> list = new ArrayList();
list.add("hello");
....
Iterator<String> iter = list.iterator();
//获取的是Iterator接口的实现类对象,这个实现类在ArrayList中以内部类Itr形式存在。
B:案例2
LocalDate today = LocalDate.now();
(3)获取某个类的常量对象
Month m = Month.MARCH;
今天我们要教大家另一种获取/创建某个类的对象的方式。
(4)通过Class对象直接new对象
步骤:
①先获取这个类型的Class对象
(4种方式可选,选择任意一种都相同)
②调用Class对象的newInstance()方法来创建这个类的对象
这种方式要求该类型必须有公共的无参构造。
建议大家写类的时候尽量保留公共的无参构造。
(5)通过Class对象 + Constructor对象来new对象
步骤:
①先获取这个类型的Class对象
(4种方式可选,选择任意一种都相同)
②根据Class对象来获取你要调用的这个类的某个构造器的Constructor对象
Constructor<T> getConstructor(Class<?>... parameterTypes)
返回一个 Constructor 对象,它反映此 Class 对象所表示的类的指定公共构造方法。
Constructor<T> getDeclaredConstructor(Class<?>... parameterTypes)
返回一个 Constructor 对象,该对象反映此 Class 对象所表示的类或接口的指定构造方法。
③调用Constructor对象的newInstance()方法来创建这个类的对象
如果构造器不是public,或者说在当前位置是不可见,那么通过(5)方式,也可以创建它的对象,
但是需要再调用newInstance()方法创建对象之前,需要调用一下下面的代码:
Constructor对象.setAccessible(true)
import org.junit.Test;
import java.lang.reflect.Constructor;
public class TestCreateObject {
@Test
public void test1()throws Exception{
Class<?> clazz = Class.forName("com.atguigu.bean.Student");
Object obj = clazz.newInstance();
System.out.println(obj);
}
@Test
public void test2()throws Exception{
Class<?> clazz = Class.forName("com.hh.bean.Student");
Constructor<?> constructor = clazz.getDeclaredConstructor(int.class, String.class);
constructor.setAccessible(true);
Object obj = constructor.newInstance(1, "张三");
System.out.println(obj);
}
}
16.2.3 操作属性
回忆之前如何操作某个类的成员变量?
(1)方式一:要求该成员变量在使用的位置是可见的
①静态
类名.静态变量
②非静态
对象名.实例变量
(2)方式二:要求该成员变量有对应的get/set方法,当然该get/set方法在使用的位置必须是可见的
①静态
类名.setXxx(值)
变量 = 类名.getXxx()
②非静态
对象名.setXxx(值)
变量 = 对象名.getXxx()
今天要教大家新的方式,通过反射操作某个类的成员变量?
(3)方式三:
静态变量:
步骤:
①先获取该类的Class对象
②直接根据Class获取你要操作的静态变量对应的Field对象
Field getField(String name) :返回一个 Field 对象,它反映此 Class 对象所表示的类或接口的指定公共成员字段。
Field getDeclaredField(String name) :返回一个 Field 对象,该对象反映此 Class 对象所表示的类或接口的指定已声明字段。
③获取该静态变量的值 或者 设置该静态变量值
获取该静态变量的值 :Field的对象.get(null);
设置该静态变量值 : Field的对象.set(null, 值);
这里的null表示不需要Class对象所表示的类型的实例对象。
如果该静态变量是私有的,需要在get/set它的值之前,调用如下代码:
Field的对象.setAccessible(true);
非静态实例变量:
步骤:
①先获取该类的Class对象
②创建Class对象所表示的类型的实例对象(见通过反射创建对象的笔记)
③直接根据Class获取你要操作的静态变量对应的Field对象
Field getField(String name) :返回一个 Field 对象,它反映此 Class 对象所表示的类或接口的指定公共成员字段。
Field getDeclaredField(String name) :返回一个 Field 对象,该对象反映此 Class 对象所表示的类或接口的指定已声明字段。
四获取该静态变量的值 或者 设置该静态变量值
获取该静态变量的值 :Field的对象.get(实例对象名);
设置该静态变量值 : Field的对象.set(实例对象名, 值);
这里的实例对象名表示操作哪个对象的xx属性值。
如果该静态变量是私有的,需要在get/set它的值之前,调用如下代码:
Field的对象.setAccessible(true);
import org.junit.Test;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
public class TestField {
@Test
public void test1()throws Exception{
Class clazz = Class.forName("com.hh.bean.Student");
Field schoolField = clazz.getDeclaredField("school");
schoolField.setAccessible(true);
Object value = schoolField.get(null);
System.out.println(value);
System.out.println("----------------------");
schoolField.set(null, "嘿嘿");
value = schoolField.get(null);
System.out.println(value);
}
@Test
public void test2()throws Exception{
Class clazz = Class.forName("com.hh.bean.Student");
Constructor<?> constructor = clazz.getDeclaredConstructor();
constructor.setAccessible(true);
Object obj1 = constructor.newInstance();
Object obj2 = constructor.newInstance();
Field idField = clazz.getDeclaredField("id");
idField.setAccessible(true);
Object obj1IdValue = idField.get(obj1);
System.out.println(obj1IdValue);
System.out.println("----------------------");
idField.set(obj1, 1001);
obj1IdValue = idField.get(obj1);
System.out.println(obj1IdValue);
System.out.println(obj1);
System.out.println(obj2);
}
}
16.2.4 操作方法
之前调用方法的方式:要求该方法在使用的位置是可见的
(1)静态方法
类名.静态方法(【实参列表】)
(2)非静态方法
对象名.非静态方法(【实参列表】)
今天要教大家用反射来调用方法:
静态方法:
步骤:
①获取该类对应的Class对象
②通过Class对象获取你要调用的方法的Method对象
Method getMethod(String name, Class<?>... parameterTypes)
返回一个 Method 对象,它反映此 Class 对象所表示的类或接口的指定公共成员方法。
Method getDeclaredMethod(String name, Class<?>... parameterTypes)
返回一个 Method 对象,该对象反映此 Class 对象所表示的类或接口的指定已声明方法。
③无论该方法是否是私有的(可见的),都可以加如下这句代码:
Method的对象.setAccessible(true);
④通过调用 Method对象的invoke方法,来调用Method对象对应的方法
Method对象.invoke(null); 表示该方法是无参的
Method对象.invoke(null,实参列表); 表示该方法是有参
null表示不需要这个类的实例对象。
非静态方法:
步骤:
①获取该类对应的Class对象
②要创建该Class对象对应类型的实例对象(见反射创建对象的笔记)
例如:Class对象代表Student类的话,那么这里要创建Student的对象
③通过Class对象获取你要调用的方法的Method对象
Method getMethod(String name, Class<?>... parameterTypes)
返回一个 Method 对象,它反映此 Class 对象所表示的类或接口的指定公共成员方法。
Method getDeclaredMethod(String name, Class<?>... parameterTypes)
返回一个 Method 对象,该对象反映此 Class 对象所表示的类或接口的指定已声明方法。
④无论该方法是否是私有的(可见的),都可以加如下这句代码:
Method的对象.setAccessible(true);
⑤通过调用 Method对象的invoke方法,来调用Method对象对应的方法
Method对象.invoke(实例对象名); 表示该方法是无参的
Method对象.invoke(实例对象名,实参列表); 表示该方法是有参
实例对象名表示该方法的调用需要这个类的实例对象。
import org.junit.Test;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
public class TestMethod {
@Test
public void test1()throws Exception{
//获取该类对应的Class对象
Class clazz = Class.forName("com.atguigu.bean.Student");
//②通过Class对象获取你要调用的方法的Method对象
Method getSchoolMethod = clazz.getDeclaredMethod("getSchool");//这里表是getSchool方法,没有参数
/*
③无论该方法是否是私有的(可见的),都可以加如下这句代码:
Method的对象.setAccessible(true);
*/
getSchoolMethod.setAccessible(true);//如果该方法是public的,这句话也可以不写
//④通过调用 Method对象的invoke方法,来调用Method对象对应的方法
Object value1 = getSchoolMethod.invoke(null);
//这里传null表示不需要Student的对象
//这里的value1是值 getSchool方法的返回值
System.out.println("value1 = " + value1);
//value1 = null,因为school变量没有赋值,默认值是null
System.out.println("------------------------");
//②通过Class对象获取你要调用的方法的Method对象
Method setSchoolMethod = clazz.getDeclaredMethod("setSchool",String.class);
//这里表是setSchool方法,有参数,1个参数,参数的类型是String类型
setSchoolMethod.setAccessible(true);//如果该方法是public的,这句话也可以不写
Object value2 = setSchoolMethod.invoke(null,"尚硅谷");
//这里传null表示不需要Student的对象
//这里传"尚硅谷"表示 setSchool方法需要一个实参"尚硅谷"
//这里的value2是值 setSchool方法的返回值
System.out.println("value2 = " + value2);
// value2 = null,是因为setSchool没有返回值,该方法的返回值类型是void
value1 = getSchoolMethod.invoke(null);
System.out.println("value1 = " + value1);
}
@Test
public void test2()throws Exception{
//获取该类对应的Class对象
Class clazz = Class.forName("com.atguigu.bean.Student");
//创建Student类的对象
Constructor<?> constructor = clazz.getDeclaredConstructor();
constructor.setAccessible(true);
Object obj1 = constructor.newInstance();//创建的是Student类的对象1
Object obj2 = constructor.newInstance();//创建的是Student类的对象2
//②通过Class对象获取你要调用的方法的Method对象
Method getIdMethod = clazz.getDeclaredMethod("getId");
/*
③无论该方法是否是私有的(可见的),都可以加如下这句代码:
Method的对象.setAccessible(true);
*/
getIdMethod.setAccessible(true);//如果该方法是public的,这句话也可以不写
//④通过调用 Method对象的invoke方法,来调用Method对象对应的方法
Object value1 = getIdMethod.invoke(obj1);
//这里传obj1表示需要Student的对象,通过obj1对象调用getId方法
//这里的value1是值 obj1对象调用getId方法的返回值
System.out.println("value1 = " + value1);
//value1 = 0,因为id变量没有赋值,默认值是0
System.out.println("------------------------");
//②通过Class对象获取你要调用的方法的Method对象
Method setIdMethod = clazz.getDeclaredMethod("setId",int.class);
//这里表是setId方法,有参数,1个参数,参数的类型是int类型
setIdMethod.setAccessible(true);//如果该方法是public的,这句话也可以不写
Object value2 = setIdMethod.invoke(obj1,1001);
//这里传obj1表示需要Student的对象,通过obj1对象调用setId方法
//这里传1001表示 setId方法需要一个实参1001
//这里的value2是值 setId方法的返回值
System.out.println("value2 = " + value2);
// value2 = null,是因为setId没有返回值,该方法的返回值类型是void
value1 = getIdMethod.invoke(obj1);
System.out.println("value1 = " + value1);
System.out.println(obj1);
System.out.println(obj2);
}
}
16.3 注解
16.3.1 注解的概念
1、什么是注解?
在Java代码中出现 @注解名。
例如:@Override
2、注解有什么用?
注解的作用是对代码进行注释。这个注释不仅仅是给人看的,也可以被程序读取,
程序读取该注解之后可以执行相应的代码。
例如:
当我们在某个方法上加上@Override,那么编译器(编译器也是一段程序)会读取到该注解,
读取之后会对该方法进行“更加”严格的格式检查,看该方法是否符合“重写”的要求。
如果不加@Override,编译器也会对方法进行格式检查,只是检查更宽泛、更松一些。
加或不加@Override本身,并不会改变方法是否重写的本质。
3、JavaSE阶段,基础的注解只有少数的几个:
(1)@Override:标记重写的方法
(2)@Deprecated:标记已过时的类、方法、属性等
在之前的版本,JDK不会删除已过时的类、方法等,只是标记它们已过时,不建议程序使用。
这种方式称为“逻辑”删除。
后续的新JDK版本,可能会把已过时的类、方法等删除,而且是“物理”删除。
(3)@SuppressWarnings:抑制警告
public class TestOverride {
}
class Father{
public static void method(){
}
public void print1n(){
}
public void fun(){
}
}
class Son extends Father{
public static void method(){
}
public void println(){
}
@Override
public void fun(){
}
}
import java.util.Date;
public class TestSuppressWarnings {
@SuppressWarnings("all")
public static void main(String[] args) {
int a = 10;
Date d = new Date(2023,4,8);
System.out.println(d);
}
}
16.3.2 注解的定义、使用、读取
4、一个完整的注解,需要包含3个部分,缺少一个都没有意义。
(1)声明
(2)使用
(3)读取:决定注解的意义靠它
5、如何声明一个注解?
(1)形式一:没有任何成员的注解
【修饰符】 @interface 注解名{
}
(2)形式二:没有任何成员的注解
【修饰符】 @interface 注解名{
成员
}
6、使用
(1)对于形式一的注解,使用的时候非常简单
@注解名
注意:使用的位置如果需要限制的话,需要在注解定义时,用元注解加以说明。
7、元注解
元注解(加在注解上面的注解,给注解加的注解)
(1)@Target:标记注解可以使用的目标位置
@Target(指定位置),这里的位置必须通过ElementType枚举类的几个常量对象来指定。
一共是10个位置可以选,可以选择其中的1个或多个。
TYPE(类型定义上面), FIELD(属性上面),METHOD(方法上面),
PARAMETER(形参上面),CONSTRUCTOR(构造器上面),LOCAL_VARIABLE(局部变量),
ANNOTATION_TYPE(注解类型),PACKAGE(包),TYPE_PARAMETER(泛型变量<T>),TYPE_USE(泛型使用<String>)
(2)@Retention:标记注解的生命周期
@Retention(生命周期),这里的生命周期必须通过RetentionPolicy枚举类的3个常量对象之一来指定。
SOURCE(源码阶段),CLASS(字节码阶段),RUNTIME(运行时)
SOURCE(源码阶段):该注解只能被编译器读取
CLASS(字节码阶段):该注解可以被编译器读取,也可以被类加载器加载过程中读取,JVM中不保留它
RUNTIME(运行时):该注解可以被编译器、类加载器、反射代码读取,JVM中保留它
(3)@Documented:该注解可以被javadoc.exe读取,生成到API文档中
咱们自己定义的注解,加不加它没什么用。
(4)@Inherited:标记该注解是否可以被子类继承
8、读取
自定义的注解必须通过“反射”读取。
注意:如果使用的注解要被“反射”读取到,那么要求该注解的生命周期必须是RUNTIME。
注解的生命周期,需要在注解定义时,用元注解加以说明。
16.3.3 注解的成员定义、使用
9、注解的成员
【修饰符】 @interface 注解名{
返回值类型 方法名();
返回值类型 方法名() default 默认返回值;
}
注意:
(1)注解中的抽象方法,不能有形参,()必须是空的。
(2)注解中的抽象方法,返回值类型只能是
八种基本数据类型、String类型、Class类型、enum类型、Annotation类型、以上所有类型的数组
10、包含成员的注解的使用
@注解名(抽象方法名=返回值)
注意:
(1)如果抽象方法名是value,且只有一个抽象方法需要指定返回值,那么可以省略value=
换句话说,如果要为多个抽象方法指定返回值,那么必须在每一个返回值前面写明"方法名=",
如果抽象方法名不是value,那么也必须在返回值前面写明"方法名="。
(2)抽象方法可以指定默认返回值
总结一下default关键字的使用位置:
①switch-case
②接口中定义默认方法
③注解中给抽象方法指定默认返回值
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface TwoAnnotation {
String value();
String method() default "hello";
}
@OneAnnotation
@TwoAnnotation("哈哈")
public class MyClass {
@OneAnnotation
private int a;
@OneAnnotation
public void method(){
int num = 1;
}
}
16.4 类加载
16.4.1 类加载器
1、类加载的过程需要用到类加载器,类加载器分为几种:
(1)引导类加载器或根加载器
它负责加载jre/rt.jar核心库
根加载器不是Java语言实现的,是C/C++语言实现的。
(2)扩展类加载器(Extension ClassLoader)
它负责加载JRE\lib\ext
(3)应用程序类加载器
它负责加载项目的classpath下的类型(自定义类型,第三方依赖的jar)
2、类加载器为什么要这么多种,它们是如何合作工作的?
工作模式:双亲委托模式
这么设计的目的:安全
双亲:
角度一:继承
角度二:组合(选的是它)
扩展类加载器里面有一个parent的成员变量,记录了引导类加载器
应用程序类加载器里面有一个parent的成员变量,记录了扩展类加载器
换句话说:
扩展类加载器对象.getParent()可以得到引导类加载器
应用程序类加载器对象.getParent()可以得到扩展类加载器
双亲委托模式:(了解)
(1) 当 应用程序类加载器 接到加载某个类的任务时,
会先在JVM中检查一下,这个类是否已经被加载过了,如果已经加载过了,就直接返回它的Class对象,
就不用重复加载。
如果这个类没有被加载过,它会把这个任务先“委托”给parent加载器,这里就是委托给扩展类加载器。
(2)当扩展类加载器 接到加载某个类的任务时,
会先在JVM中检查一下,这个类是否已经被加载过了,如果已经加载过了,就直接返回它的Class对象,
就不用重复加载。
如果这个类没有被加载过,它会把这个任务先“委托”给parent加载器,这里就是委托给引导类加载器。
(3)当引导类加载器 接到加载某个类的任务时,
会先在JVM中检查一下,这个类是否已经被加载过了,如果已经加载过了,就直接返回它的Class对象,
就不用重复加载。
如果这个类没有被加载过,它会在自己负责的路径下搜索这个类的.class文件,如果可以正确加载,
那么返回它的Class对象,加载完毕。
如果在它负责的路径下没有找到这个类的.class文件或者无法正确加载,那么它会把任务或异常往回传,
给扩展类加载器。
(4)当扩展类加载器 再次接到parent回传的加载某个类的任务时,
它会在自己负责的路径下搜索这个类的.class文件,如果可以正确加载,
那么返回它的Class对象,加载完毕。
如果在它负责的路径下没有找到这个类的.class文件或者无法正确加载,那么它会把任务或异常往回传,
给应用程序类加载器。
(5) 当 应用程序类加载器 再次接到parent回传的加载某个类的任务时,
它会在自己负责的路径下搜索这个类的.class文件,如果可以正确加载,
那么返回它的Class对象,加载完毕。
如果在它负责的路径下没有找到这个类的.class文件或者无法正确加载,就报ClassNotFoundException或NoClassDefError。
import org.junit.Test
public class TestClassLoader {
@Test
public void test1()throws Exception{
Class clazz = Class.forName("java.lang.String")
ClassLoader loader = clazz.getClassLoader()
System.out.println(loader)
}
@Test
public void test2()throws Exception{
Class clazz = Class.forName("com.sun.nio.zipfs.ZipUtils")
ClassLoader loader = clazz.getClassLoader()
System.out.println(loader)
}
@Test
public void test3()throws Exception{
Class clazz = Class.forName("com.haha.load.TestClassLoader")
ClassLoader loader = clazz.getClassLoader()
System.out.println(loader)
ClassLoader parent = loader.getParent()
System.out.println(parent)
ClassLoader grandParent = parent.getParent()
System.out.println(grandParent)
}
}
16.4.2 类加载过程
3、类加载器如何加载一个类?
(1)加载:load
就是指将类型的class字节码数据读入内存 ==> IO过程
(2)连接:link
①验证:校验合法性等
例如:字节码文件一定是以cafebabe开头
版本等问题
②准备:准备对应的内存(方法区),创建Class对象,为类变量(静态变量)赋默认值,为静态常量赋初始值。
如果此时加载的是Student类,school和value有分配内存,并且有默认值,分别是null和0.
但是并没有为id和name属性分配存储值的内存,因为它们的内存需要在new Student对象时才会
在堆中开辟内存存储值。
public class Student {
private static String school;
private static int value;
private int id;
private String name;
}
③解析:把字节码中的符号引用替换为对应的直接地址引用
(3)初始化:initialize(类初始化)即执行<clinit>类初始化方法,
大多数情况下,类的加载就完成了类的初始化,有些情况下,会延迟类的初始化。
类初始化为类的静态变量赋值用的。
16.5 类初始化
16.5.1 类初始化过程
1、类初始化的目的:为类的静态变量赋值用的。
如果这个类没有定义静态变量,那么这个过程相当于什么都没的干。
2、类初始化和哪些代码有关
(1)静态变量的显式赋值
(2)静态代码块
(1)和(2)是按照编写顺序执行。
本质上,类在编译时其实会把和类初始化有关的代码,重新组装为一个<clinit>()的方法,
称为类初始化方法。
cl:class 类
init:initialize初始化
3、如果子类加载和初始化时,发现父类还没有被加载和初始化,应该会先加载和初始化父类。
但是如果父类已经初始化过了,不会重复初始化父类。
注意:每一个类只会初始化1次。先父后子。
public class Demo {
private static int a = 1;
static{
System.out.println("a = " + Demo.a);
System.out.println("静态代码块");
a = 2;
System.out.println("a = " + Demo.a);
}
public static void method(){
System.out.println("Demo.method ");
System.out.println("a = " + a);
}
}
public class TestDemo {
public static void main(String[] args) {
Demo.method();
}
}
public class Base {
private static int a = 1;
static{
System.out.println("a = " + a);
System.out.println("父类的静态代码块");
}
}
public class Sub extends Base{
private static int b = 2;
static{
System.out.println("b = " + b);
System.out.println("子类的静态代码块");
}
public static void method(){
System.out.println("Sub.method");
}
}
public class TestSub {
public static void main(String[] args) {
Sub.method();
}
}
16.5.2 触发类初始化?
4、哪些行为会触发类的初始化呢?
(1)main方法所在的类,在执行main方法之前,会先初始化主类
(2)new对象之前,会先初始化这个类
(3)调用某个类的静态成员(静态方法、静态变量等),会先初始化这个类
(4)用反射代码操作某个类,会先初始化这个类
(5)子类初始化时,如果父类没有初始化,会触发父类初始化
5、某些行为不会触发类的初始化,即只会加载这个类,但是类初始化被延迟了。
(1)使用某个类的静态的常量(static final)
(2)通过子类调用父类的静态变量,静态方法,只会导致父类初始化,不会导致子类初始化,即只有声明静态成员的类才会初始化
(3)用某个类型声明数组并创建数组对象时,不会导致这个类初始化
public class TestClinit {
static {
System.out.println("主类的初始化");
}
public static void main(String[] args) throws Exception{
System.out.println("hello");
Class<?> clazz = Class.forName("com.haha.clinit.Example");
}
}
class Example{
static {
System.out.println("Example类的初始化");
}
}
class MyDemo{
static {
System.out.println("MyDemo类的初始化");
}
public static void method(){
System.out.println("MyDemo.method");
}
}
public class TestNonClinit {
public static void main(String[] args) {
System.out.println(MyMath.PI);
Son.method();
Son[] arr = new Son[5];
arr[0] = new Son();
System.out.println(arr.length);
}
}
class MyMath{
public static final double PI = 3.14;
static{
System.out.println("MyMath类初始化");
}
}
class Father{
static{
System.out.println("Father类初始化");
}
public static void method(){
System.out.println("Father.method");
}
}
class Son extends Father{
static{
System.out.println("Son类初始化");
}
}
16.6 实例初始化
16.6.1 实例初始化过程
1、什么是实例初始化?
“创建对象”的过程中为对象的“实例变量”赋值的过程,称为实例初始化。
实例变量是指非静态的成员变量。
【修饰符】 class 类名{
【修饰符】 static 数据类型 静态变量;
【修饰符】 数据类型 实例变量;
static{
数据类型 变量名;
}
{
数据类型 变量名;
}
【修饰符】 返回值类型 方法名(【形参列表】){
数据类型 变量名;
}
}
2、实例初始化过程和哪些代码有关?
(1)super()或super(实参列表)
(2)实例变量的显式赋值
private int a = 1; 其中 a= 1;就是实例变量的显式赋值
(3)非静态代码块,又称为构造块(了解)
(4)构造器中除了(1)语句,剩下的代码
它们的执行顺序是(1)(2)(3)(4)
它们的执行顺序是(1)(3)(2)(4)
(2)(3)按照编写的顺序。绝大部分类没有(2)和(3)
结论:
每次new对象,上面(1)(2)(3)(4)都要走一遍。
只不过new后面的构造器不同,那么(1)(4)可能会不同。
本质上,上面4个部分的代码会在编译时组装成一个一个的<init>方法,实例初始化方法。
public class Demo {
private int a = 1;
{
System.out.println("a = " + a);
System.out.println("非静态代码块");
a = 2;
}
public Demo(){
System.out.println("无参构造");
}
public Demo(int a){
this.a = a;
System.out.println("有参构造");
}
}
import java.lang.reflect.Constructor
public class TestDemo {
public static void main(String[] args) throws Exception{
/* Demo d1 = new Demo()
Demo d2 = new Demo(3)
Class clazz = Class.forName("com.haha.instance.Demo")
Constructor constructor = clazz.getDeclaredConstructor(int.class)
}
}
16.6.2 先父后子
3、原则:子类的实例初始化过程中,一定会执行父类相应的实例初始化代码。
super() ==> 对应父类的无参构造 ==> 对应父类的<init>()
super(实例列表)==> 对应父类的有参构造 ==> 对应父类的<init>(形参列表)
public class Father {
private int a;
{
System.out.println("1.父类的非静态代码块");
}
public Father() {
System.out.println("2.父类的无参构造");
}
public Father(int a) {
this.a = a;
System.out.println("3.父类的有参构造");
}
}
public class Son extends Father{
{
System.out.println("4.子类的非静态代码块");
}
public Son() {
System.out.println("5.子类的无参构造");
}
public Son(int a) {
super(a);
System.out.println("6.子类的有参构造");
}
}
public class TestSon {
public static void main(String[] args) {
Son s = new Son();
Son s2 = new Son(1);
}
}
16.6.3 先类初始化后实例初始化
4、先完成类的初始化,才能进行实例初始化。
类初始化只会执行一次,实例初始化每次new都执行。
public class Base {
private static int a;
private int b;
static{
System.out.println("1.Base父类的静态代码块");
}
{
System.out.println("2.Base父类的非静态代码块");
}
public Base() {
System.out.println("3.Base父类的无参构造");
}
public Base(int b) {
this.b = b;
System.out.println("4.Base父类的有参构造");
}
}
public class Sub extends Base{
static{
System.out.println("5.Sub子类的静态代码块");
}
{
System.out.println("6.Sub子类的非静态代码块");
}
public Sub() {
System.out.println("7.Sub子类的无参构造");
}
public Sub(int b) {
super(b);
System.out.println("8.Sub子类的有参构造");
}
}
public class TestSub {
public static void main(String[] args) {
Sub s = new Sub();
Sub s2 = new Sub(5);
}
}