1.serializable
1.对象流
ObjectInputStream 和ObjectOutputStream
2.作用
objectoutputStream:内存中的对象—->存储中的文件、通过网络传输出去
0bjectInputStream:存储中的文件、通过网络接收过来—->内存中的对象
3.对象的序列化机制
对象序列化机制允许把内存中的Java对象转换成平台无关的二进制流,从而允许把这种二进制流持久地保存在磁盘上,或通过网络将这种三进制流传输到另一个网络节点。当其它程序获取了这种二进制流,就可以恢复成原来的Java对象
public static void deserializable(){
ObjectOutputStream oos = null;
try {
oos = new ObjectOutputStream(new FileOutputStream("./new-study/Object.bat"));
oos.writeObject(new String("序列化测试"));
oos.writeObject(new Person("姓名","性别"));
oos.flush();
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
assert oos != null;
oos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
public static void serializable(){
ObjectInputStream ois = null;
try {
File file = new File("./new-study/Object.bat");
if (file.exists()) {
ois = new ObjectInputStream(new FileInputStream(file));
String s = (String) ois.readObject();
System.out.println(s);
Person p = (Person) ois.readObject();
System.out.println(p);
}
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
6.实现序列化的对象所属的类需要满足:
1.需要实现接口: serializabLe
2.当前类提供—个全局常量: serialVersionUID
3.除了当前Person类需要实现Serializable接口之外,还必须保证其内部所属性也必须是可序列化的。(默认情况下,基本数据类型可序列化)
补充: 0bjectOutputStream和ObjectInputStream不能序列化static和transient修饰的成员变量
2.枚举类
1.若枚举只有一个对象,则可以作为一种单例模式的实现方式。
2.枚举类的属性
枚举类对象的属性不应允许被改动,所以应该使用private final修饰
枚举类的使用private final修饰的属性应该在构造器中为其赋值
若枚举类显式的定义了带参数的构造器,则在列出枚举值时也必须对应的传入参数
1.如何定义枚举类
方式1:jdk5.0之前 自定义枚举类
//自定义枚举类
class Season{
//声明season对象的属性 private final
private final String seasonName;
private final String seasonDesc;
//1.私有化构造器
private Season(String seasonName,String seasonDesc) {
this.seasonName = seasonName;
this.seasonDesc = seasonDesc;
}
//2.提供当前枚举类的多个对象 public static final
public static final Season SPRING = new Season("春天", "暖");
public static final Season SUMMER = new Season("春天", "炎");
public static final Season AUTUMN = new Season("春天", "凉");
public static final Season WINTER = new Season("春天", "冻");
//3.获取枚举类对象属性
public String getSeasonName() {
return seasonName;
}
public String getSeasonDesc() {
return seasonDesc;
}
//4.提供其他方法
@Override
public String toString() {
return "Season{" +
"seasonName='" + seasonName + '\'' +
", seasonDesc='" + seasonDesc + '\'' +
'}';
}
}
方式2:jdk5.0时可以使用enum关键字定义
public class SeasonTest1 {
public static void main(String[] args) {
Season1 summer = Season1.SUMMER;
System.out.println(summer);
System.out.println(Season1.class.getSuperclass());
//SUMMER
//class java.lang.Enum
}
}
//自定义枚举类
enum Season1{
//1.创建当前枚举类的对象,多个对象之间用逗号,末尾对象;结束
SPRING("春天", "暖"),
SUMMER("春天", "炎"),
AUTUMN("春天", "凉"),
WINTER("春天", "冻");
//2.声明season对象的属性 private final
private final String seasonName;
private final String seasonDesc;
//3.私有化构造器
private Season1(String seasonName,String seasonDesc) {
this.seasonName = seasonName;
this.seasonDesc = seasonDesc;
}
//4.获取枚举类对象属性
public String getSeasonName() {
return seasonName;
}
public String getSeasonDesc() {
return seasonDesc;
}
}
enum TestEnum {
BUSY,FREE,STOP
}
public class SeasonTest1 {
public static void main(String[] args) {
TestEnum free = TestEnum.FREE;
System.out.println(free);
//FREE
}
}
2.Enum类中的常用方法:
values()方法:返回枚举类型的对象数组。该方法可以很方便地遍历所有的枚举值。
valueOf(String str):可以把一个字符串转为对应的枚举类对象。要求字符串必须是枚举类对应字符串。
tostring():返回当前枚举类对象常量的名称。
public class SeasonTest1 {
public static void main(String[] args) {
Season1 summer = Season1.SUMMER;
System.out.println(Arrays.toString(Season1.values()));
System.out.println(Season1.valueOf("WINTER"));
//[SPRING, SUMMER, AUTUMN, WINTER]
//WINTER
}
}
3.枚举类实现接口
情况一:实现接口,在enum类中实现抽象方法
interface info{
void show();
}
//自定义枚举类
enum Season1 implements info{
//1.创建当前枚举类的对象,多个对象之间用逗号,末尾对象;结束
SPRING("春天", "暖"),
SUMMER("夏天", "炎"),
AUTUMN("秋天", "凉"),
WINTER("冬天", "冻");
//2.声明season对象的属性 private final
private final String seasonName;
private final String seasonDesc;
//3.私有化构造器
private Season1(String seasonName,String seasonDesc) {
this.seasonName = seasonName;
this.seasonDesc = seasonDesc;
}
//4.获取枚举类对象属性
public String getSeasonName() {
return seasonName;
}
public String getSeasonDesc() {
return seasonDesc;
}
@Override
public String toString() {
return this.seasonName;
}
@Override
public void show() {
System.out.println(this.seasonDesc);
}
}
public static void main(String[] args) {
Season1 summer = Season1.SUMMER;
summer.show();
}
情况二:枚举类的对象分别实现接口中的方法每个对象的方法体都不一样
interface info{
void show();
}
//自定义枚举类
enum Season1 implements info{
//1.创建当前枚举类的对象,多个对象之间用逗号,末尾对象;结束
SPRING("春天", "暖"){
@Override
public void show() {
System.out.println("春");
}
},
SUMMER("夏天", "炎") {
@Override
public void show() {
System.out.println("夏");
}
},
AUTUMN("秋天", "凉") {
@Override
public void show() {
System.out.println("秋");
}
},
WINTER("冬天", "冻") {
@Override
public void show() {
System.out.println("冬");
}
};
//2.声明season对象的属性 private final
private final String seasonName;
private final String seasonDesc;
//3.私有化构造器
private Season1(String seasonName,String seasonDesc) {
this.seasonName = seasonName;
this.seasonDesc = seasonDesc;
}
@Override
public String toString() {
return this.seasonName;
}
//无效
@Override
public void show() {
System.out.println("四季");
}
}
public static void main(String[] args) {
Season1 summer = Season1.SUMMER;
summer.show();
}
3.注解
注解的使用
1.理解Annotation:
jdk 5.新增的功能
Annotation其实就是代码里的特殊标记,这些标记可以在编译,类加载,运行时被读取,并且程序员可以在不改变原有逻辑的情况下,在源文件中嵌入一些补充信息。
在JavaSE中,注解的使用目的比较简单,例如标记过时的功能,忽略警告等。在JavaEE/Andro中注解占据了更重要的角色,例如用来配置应用程序的任何切面,代替JavaEE旧版中所遗留的繁冗代码和XML配置等。
2.使用示例
示例一:生成文档相关的注解
示例二:在编译时进行格式检查(JDK内置的三个基本注解)
@override:限定重写父类方法,该注解只能用于方法
@Deprecated:用于表示所修饰的元素(类,方法等)已过时。通常是因为所修饰的结构危险或存在更好的选择@Suppresswarnings:抑制编译器警告
示例三:跟踪代码依赖性,实现替代配置文件功能
3.定义注解
参照@Suppresswarnings定义
注解声明为:@interface
内部定义成员,通常使用value表示
可以指定成员的默认值,使用default定义
如果自定义注解没有成员,表明是一个标识作用。
如果注解有成员,在使用注解时,需要指明成员的值。
自定义注解必须配上注解的信息处理流程(使用反射)才有意义。
4.jdk 提供的4中元注解
jdk的元注解用于修饰其他注解的含义:对现有的注解进行解释说明的注解
元数据的理解:对现有数据的修饰就是元数据
Retention
@Retention:只能用于修饰一个Annotation定义,用于指定该Annotation的生命周期,@Rentention包含一个 RetentionPolicy类型的成员变量,使用
@Rentention时必须为该value成员变量指定值:
RetentionPolicy.SOURCE:在源文件中有效(即源文件保留),编译器直接丢弃这种策略的注释
RetentionPolicy.CLASS:在class文件中有效(即class保留),当运行Java程序时,JVM不会保留注解。这是默认值
RetentionPolicy.RUNTIME:在运行时有效(即运行时保留),当运行Java程序时,JVM会保留注释。程序可以通过反射获取该注释。
Traget
用于指明被修饰的注解可以用于那些地方 比如类,接口,方法,字段。
Document
表示所修饰的注解在被javadoc解析时,会保留下来。
Inherited
被它修饰的的注解将具有继承性,被修饰注解修饰的父类,子类继承后也会有此注解。
5.通过放射获取注解信息
Annotation[] annotations = Student.class.getAnnotations();
for (Annotation annotation : annotations) {
System.out.println(annotation);
}
6.jdk8 中 注解的新特性可重复注解,类型注解
6.1可重复注解:
1.在MyAnnotation上声明@Repeatable,成员值为MyAnnotations.class
2.MyAnnotation的target和Retention和MyAnnotation等元注解相同
//jdk8之前@MyAnnotations({@MyAnnotation("hello"),@MyAnnotation("hi")})package annotation;public @interface MyAnnotations { MyAnnotation[] value();}package annotation;import java.lang.annotation.Retention;import java.lang.annotation.RetentionPolicy;@Retention(RetentionPolicy.RUNTIME)public @interface MyAnnotation { String value();}
//jdk 8 之后@MyAnnotation(value = "hello")
@MyAnnotation(value = "hi")
class Student extends Person implements Info
{ @Override
public void walk() {
System.out.println("学生走");
}
@Override
public void eat() {
System.out.println("学生吃");
}
@Override
public void show() {
System.out.println("展示");
}
public void drink() {
System.out.println("喝水");
}
}
package annotation;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotations {
MyAnnotation[] value();
}
package annotation;
import java.lang.annotation.Repeatable;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@Retention(RetentionPolicy.RUNTIME)
@Repeatable(MyAnnotations.class)
public @interface MyAnnotation {
String value();
}
6.2 类型注解:
TYPE_PARAMETER, TYPE_USE
ELementType.TYPE_PARAMETER 表示该注解能写在类型变量的声明语句中(如:泛型声明)。ELementType.TYPE_USE 表示该注解能写在使用类型的任何语句中。
@Retention(RetentionPolicy.RUNTIME)@Repeatable(MyAnnotations.class)@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE, TYPE_PARAMETER, TYPE_USE})public @interface MyAnnotation { String value();}class Generic<@MyAnnotation("类型注解") T>{ public void show() throws @MyAnnotation("") Exception { ArrayList<@MyAnnotation("泛型类型") String> list = new ArrayList<>(); int num = (@MyAnnotation("") int) 10L; }}
4.反射机制
Reflection(反射)是被视为动态语言>的关键,反射机制允许程序在执行期T借助于Reflection API取得任何类的内部信息,并能直接操作任意对象的内部属性及方法。
加载完类之后,在堆内存的方法区中就产生了一个class类型的对象(一个类只有一个Class对象),这个对象就包含了完整的类的结构信息。我们可以通过这个对象看到类的结构。这个对象就像一面镜子,透过这个镜子看到类的结构,所以,我们形象的称之为:反射。
1、动态语言
是一类在运行时可以改变其结构的语言:例如新的函数、对象、甚至代码可以被引进,已有的函数可以被删除或是其他结构上的变化。通俗点说就是在运行时代码可以根据某些条件改变自身结构。
主要动态语言:Object-C、C#、JavaScript、PHP、Python、Erlang。
2、静态语言
与动态语言相对应的,运行时结构不可变的语言就是静态语言。如Java、C、C++。
Java不是动态语言,但Java可以称之为“准动态语言”。即Java有一定的动态性,我们可以利用反射机制、字节码操作获得类似动态语言的特性。
Java的动态性让编程的时候更加灵活!
1.反射的举例
public static void test2() throws Exception { Class clazz = Person.class; //1.通过放射创建person类的对象 Constructor cons = clazz.getConstructor(String.class,int.class); Object obj = cons.newInstance("Tome",45); System.out.println(obj); Person p = (Person) obj; //2.通过反射,调用对象指定的属性和方法 Field age =clazz.getDeclaredField("age"); age.set(p, 10); System.out.println(p); //3.调用方法 Method show = clazz.getDeclaredMethod("show"); show.invoke(p); //通过反射,可以调用Person类中的思又的结构。比如私有的构造器,方法,属性。 Constructor constructor = clazz.getDeclaredConstructor(String.class); constructor.setAccessible(true); Person p1 = (Person) constructor.newInstance("Jerry"); System.out.println(p1); //调用私有属性 Field name = clazz.getDeclaredField("name"); name.setAccessible(true); name.set(p1,"AZZ" ); System.out.println(p1); //调用私有方法 Method showHide = clazz.getDeclaredMethod("showHide",String.class); showHide.setAccessible(true); String result = (String) showHide.invoke(p1,"内容"); System.out.println(result);}
2.Class理解
java.lang.Class类
1.类的加载过程
程序在经过javac.exe命令后,会生成一个或多个字节码文件(.class结尾),接着使用java.exe命令对某个字节码文件进行解释运行。将字节码文件加载到内存中。这个过程就是类的加载。加载到内存中的类被称为运行时类。运行时类,就作为Class的一个实例。
2.Class的实例就对应着一个运行时类。
3.加载到内存中的运行时类,会缓存一定的时间。在此时间之内,我们可以通过不同的方式来获取此运行时类。
获取Class的实例的方式
@Test public void test3() throws ClassNotFoundException { //方式1 调用运行时类的属性 .class Class<Person> clazz1 = Person.class; System.out.println(clazz1); //方式2 通过运行时类的对象,调用getClass()方法 Person p1 = new Person(); Class class2 = p1.getClass(); System.out.println(class2); //方式3 调用Class的静态方法 forName(String classPath) Class class3 = Class.forName("reflection.Person");// Class class3 = Class.forName("java.lang.String"); System.out.println(class3); //都是获取的同一个运行时类 System.out.println(clazz1 == class2); System.out.println(clazz1 == class3); //方式四 使用类的加载器 classloader ClassLoader classLoader = ReflectionTest.class.getClassLoader(); Class class4 = classLoader.loadClass("reflection.Person"); System.out.println(class4 == class3); }
Class实例可以是那些结构的说明
(1) class:外部类,成员(成员内部类,静态内部类),局部内部类,匿名内部类(2) interface: 接口(3) []: 数组(4) enum:枚举(5) annotation: 注解@interface(6)primitivetype:基本数据类型(7) void
@Testpublic void test4() { Class c1 = Object.class; Class c2 = Comparable.class; Class c3 = String[]. class; Class c4 = int[][].class; Class c5 = ElementType. class; Class c6 = Override.class; Class c7 = int.class; Class c8 = void.class; Class c9 = Class. class; int[] a = new int[10]; int[] b = new int[100]; Class c10 = a.getClass(); Class c11 = b.getClass(); //只要元素类型与维度- -样,就是同一个CLass System. out. println(c10 == c11);}
理解类的加载过程
●加载:将class文件字节码内容加载到内存中,并将这些静态数据转换成方法区的运行时数据结构,然后生成- -个代表这个类的java.lang.Class对象,作为方法区中类数据的访问入口(即引用地址)。所有需要访问和使用类数据只能通过这个Class对象。这个加载的过程需要类加载器参与。
●链接:将Java类的二进制代码合并到JVM的运行状态之白的过程。
➢验证:确保加载的类信息符合JVM规范,例如:以eafe开头, 没有安全方面的问题
➢准备:正式为类变量(static) 分配内存并设置类变量默认初始值的阶段,这些内存都将在方法区中进行分配。初始化为默认值 而不是赋值 具体赋值操作在初始化阶段。
➢解析:虚拟机常量池内的符号引用(常量名)替换为直接引用(地址)的过程。
●初始化:
➢执行类构造器()方法的过程。类构造器()方法是由编译期自动收集类中所有类变量的赋值动作和静态代码块中的语句合并产生的。(类构造器是构造类信息的,不是构造该类对象的构造器)。
➢当初始化一个类的时候,如果发现其父类还没有进行初始化,则需要先触发其父类的初始化。
➢虚拟机会保证一个类的()方法在多线程环境中被正确加锁和同步。
public class A { static { m = 300; } static int m = 100;}@Testpublic void test5() { System.out.println(A.m); //第一步 类加载到内存 //第二步 链接结束后m=0 //第三步 初始化m的值由<clinit>()方法执行决定 //这个A的类构造器<clinit>( )万法由类变量的赋值和静态代码块中的语句按照顺序合并 //产生,类似于 // m = 300 // m = 100}
3.类的加载和ClassLoader的理解
//对于自定义类 使用系统类加载器//调用系统类加载器获取扩展类加载器//调用扩展类加载器获取引导类加载器 无法获取到 //引用类加载器主要负载加载java的核心库,无法加载自定义@Testpublic void test1 () { ClassLoader classLoader = ClassLoaderTest.class.getClassLoader(); System.out.println(classLoader); ClassLoader parent = classLoader.getParent(); System.out.println(parent); ClassLoader parent1 = parent.getParent(); System.out.println(parent1);}//sun.misc.Launcher$AppClassLoader@18b4aac2//sun.misc.Launcher$ExtClassLoader@a09ee92//null
3.1使用classloader加载配置文件
/** * Properties : 用来读取配置文件。 * */ @Test public void test2() throws IOException { Properties properties = new Properties(); //此时的文件默认在当前module下 //读取配置文件的方式一// FileInputStream fis = new FileInputStream("config/properties.properties");// properties.load(fis); //读取配置文件的方式二 //此时的文件默认只能读取在当前module的src下 ClassLoader classLoader = ClassLoaderTest.class.getClassLoader(); InputStream resourceAsStream = classLoader.getResourceAsStream("properties.properties"); properties.load(resourceAsStream); System.out.println(properties.get("username")); }
4.通过反射创建运行时类对象
@Testpublic void test1() throws IllegalAccessException, InstantiationException { Class clazz = Person.class; /* * newInstance() 调用此方法,创建对应的运行时类的对象 调用的是空参构造器 * 造对象都是构造器的方法 * * 用此方法正常创建运行时类的对象 * 1.运行时类必须提供空参的构造器 * 2.空参的构造器的访问权限得够 一般为public * * 在javabean中要求提供一个public 的空参构造器。原因: : * 1.便于通过反射,创建运行时类的对象 * 2.便于子类继承此运行时类时,默认调用super()时,保证父类有此构造器 */ Person person = (Person) clazz.newInstance(); System.out.println(person);}
反射的动态性
@Testpublic void test2() throws IllegalAccessException, InstantiationException, ClassNotFoundException { System.out.println(getInstance("reflection.Person"));}/** * 创建一个指定类的对象 */public Object getInstance(String path) throws ClassNotFoundException, IllegalAccessException, InstantiationException { Class<?> clazz = Class.forName(path); return clazz.newInstance();}