JAVA自学笔记27
1、类的加载
1)当程序要使用某个类时,如果该类还未被加载到内存中,则系统会通过加载,连接,初始化三步来实现对这个类进行初始化。
①加载:就是指将class文件读入内存,并为之创建一个Class对象。任何类被使用时系统都会为之建立一个Class对象。
②连接
验证:是否有正确的内部结构,并和其他类协调一致
准备:负责为类的静态成员分配内存,并设置默认初始化值
解析:将类的二进制数据中的符号应用为直接引用。
初始化:就是之间所说的初始化步骤
附初始化的时机:
–创建类的实例
–类的静态变量,或者为静态变量赋值
–调用类的静态方法
–使用反射方式来强制创建某个类或接口的java.lang.Class
–初始化某个类的子类
–直接使用java.exe命令来运行某个主类
类加载器:负责将.class文件加载到内在中,并为之生成对应的Class对象。
组成:
BootStrap ClassLoader 根类加载器
Extension ClassLoader 扩展类加载器
System ClassLoader 系统类加载器
2、反射
JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制
要想解剖一个类,必须先要获取到该类的字节码文件对象。而解剖使用的就是Class类中的方法。所以先要获取到每一个字节码文件对应的Class类型的对象。就是通过class文件对象,去使用该文件中的成员变量,构造方法,成员方法
Class类:
成员变量 Field
构造方法 Constructor
成员方法 Method
获取class文件对象的方式:
–Object类中的getClass()方法
–数据类型的静态属性class
–Class类中的静态方法:public static Class forName(String className)
第二种较为方便,开发中使用第三种,因为第三种是一个字符串而不是具体看到的类名。这样我们就可以把这样的字符串文件配置到配置文件中
//Person类省略
Person p=new Person();
Class c=p.getClass();
Person p1=new Person();
Class c2=p2.getClass();
System.out,println(p==p2);//false
System.out,println(c==c2);//true,拿的都是相同的字节码对象
//方法2
int.class;
String.class;
Class类中的静态方法:public staic Class forName(String className)
//方法三,必须带包名的全路径或在包目录下Copy Qualified Name
Class c4=Class.forName("cn.itcast_01.Person");
System.out.println(c==c4);//true
通过反射获取无参构造方法
//获取字节码文件对象
Class c=Class.forName("cn.itcast_01.Person");
//获取构造方法
//public Constructor[] getConstructors();
Constructors[] cons=c.getConstructors();
for(Constructor con:cons){
System.out.println(con);
}
public Cinstructors[] cons=c.getConstructors();
只能返回公共构造方法
public Cinstructors[] cons=c.getDeclaredConstructors();
能返回所有构造方法
public Cinstructors[] cons=c.getConstructor(…);
返回一个构造方法,参数需要的是相应数据类型的Class字节码文件对象
创建实例:
Constructor con=c.getConstructor();//返回构造方法对象
public T newInstance(Object…initargs)
可以使用此Consructor对象表示的构造方法来创建该构造方法的声明类的新实例,并用指定的初始化参数初始化该实例。
object obj=con.newInstance();
//通过反射获取构造方法并使用
//获取字节码文件对象
Class c=Class.forName("cn.itcast 01.Person");
//获取带参构造器
Constructor con=c.getConstructor(String.class,int.class,String.class);
//通过带参构造方法对象创建对象
Object obj=con.newInstance("cc","18","paris");
System.out.println(obj);
!通过反射获取私有构造方法并使用
//获取字节码文件对象
Class c=Class.forName(“cn.itcast 01.Person”);
//获取私有构造方法对象
Constructor con=c.getDeclaredConstructors(String.calss);
//通过私有构造方法创建对象
con.setAccessible(true);//设置可访问,将取消访问检查
Object obj=con.newInstance(“cc”);
System.out,println(obj);
!通过反射获取成员变量并使用
获取所有成员:
getFields,getDeclaredFields
获取单个成员
getField,getDeclaredField
修改成员的值
set(Object obj,Object value)
将指定变量上此Field对象表示的字段设置为指定的新值
Field类:提供有关类或接口的单个字段的信息以及对它的动态访问权限,反射的字段可能是一个类(静态)字段或实例字段
//获取字节码文件对象
Class c=Class.forName("cn.itcast 01.Person");
//获取所有的成员变量
Field[] fields=c.getDeclaredFields();
for(Field field:fields){
System.out,println(field);
}
//通过无参构造方法创建对象
Constructors con=c.getConstrucior();
Object obj=con.newInstance();
//获取单个成员变量并对其赋值
Field addressField=c.getField("address");//返回的是Field类
addressField.set(obj,"london");//给Obj对象的addressField字段设置值为"london"
System.out.println(obj);
!获取成员方法
//获取所有方法
Class c=class.forName("cn.itcast_01.Person");
//获取所有的方法
Method[] methods=c.getMethods();
//获取自己包括父亲的公共方法
Method[] methods=c.getDeclaredMethods();
//获取自己的所有方法
//获取单个方法并使用
Constructor con=c.getConstructor();
Object obj=con.newInstance();
//getMethod(方法名,参数的class类型)
//public Object invoke(Object obj,Object...args)
//返回值是Object接收,第一个参数表示对象是谁,第二参数表示调用该方法的实际参数
Method m1=c.getMethod("show");//无参方法
m1.invoke(obj);//将调用m1方法
//通过反射获取带参返回值成员方法并使用
Method m2=c.getMethod("method",String.class);
m2.invoke(obj,"hello");
!通过反射运行配置文件内容
需要有配置文件配合使用
键是固定的,值是可变的
键分别是className
methodName
//加载键值对数据
Properties prop=new Properties();
FileReader fr=new FileReader("class.text");
prop.load(fr);
fr.close();
//获取数据
String className=prop.getProperty("className");
String methodName=prop.getProperty("methodName");//需要调用的类名和方法名
//反射
class c=Class.forName(className);
Constructor con=c.getConstructor();
Object obj=con.newInstance();
//调用方法
Method m=c.getMethod(methodName);
m.invoke(obj);
!通过反射越过泛型检查
//创建集合对象
给定ArrayList的一个对象,在这个集合中添加一串字符串数据,如何实现呢?
ArrayList<Integer>array=new ArrayList<Integer>();
//集合ArrayList的class文件对象
Class c=array.getClass();
Method m=c.getMethod("add",Object.class);
m.invoke(array,"hello");//调用array的add方法,传入的值是hello,绕过了泛型检查
@例题1:写一个方法:public void setProperty(Object obj,String PropertyName,Object value){}
此方法可将obj对象名为propertyName的属性的值设置为value
public class Tool{
public void setProperty(Object obj,String propertyName,Object value){
//根据对象获取字节码文件对象
Class c=obj,getClass();
Field
//获取该对象的propertyName成员变量
field=c.getDeclared(propertyName);
//取消访问检查
field setAccessible(true);
//给对象的成员变量赋值为指定的值
field.set(obj,value);
}
}
public class ToolDemo{
public static void main(String[] args){
Person p=new Person();
Tool t=new Tool();
t.setProperty(p,"name","cc");
}
}
@例题2:通过用户的增删改查引出中介。即在每个方法前面检验权限,在操作后留下操作日志
public class UserDapImpl2 implements UserDao{
public void add(){
校验功能...
日志记录功能...
}
//其余类推
}
...
2、动态代理
在程序运行过程中产生的这个对象,通过反射生成一个代理
在Java中Java.lang.reflect包下提供一个Proxy类和一个InvocationHandler接口,通过使用这个类和接口就可以生成动态代理接口对象。JDK提供的代理只能针对接口做代理,还有更强大的代理cglib
Proxy类中的方法创建动态代理类对象.Proxy表示代理设置,通常为类型和套接字地址。是不可变对象
public static Object newProxyInstance(ClassLoader loader,Class
public class MyInvocationHandler implements InvocationHandler{
private Object target;//目标对象
public MyInvocationHandler(Object target){
this.target=target;
}
public Object invoke(Object proxy,Method method,Object[] args){
System.out.println("权限校验");
Object result=method.invoke(target,args);
System.out.println("日志记录");
return result;//返回代理对象
}
}
//测试类
public class Test{
public static void main(String[] args){
UserDao ud=new UserDaoImpl();
MyInvocationHandler handler=new MyInvocationHandler(ud);//想对谁做代理就传谁
UserDao proxy=(UserDao)Proxy.newProxyInstance(ud.getClass().getClassLoader,ud.getClass().getInterface(),handler);
proxy.add();
proxy.delete(); v
}
}
3、设计模式
1)单一职责原则
即“高内聚低耦合”,职责较为单一
2)开闭原则
一个对象对扩展开放,对修改关闭。对类的改动是通过增加代码进行的,而不是修改现有代码
3)里氏替换原则
在任何父类出现的地方都可以用它的子类替代
4)依赖注入原则
要依赖与抽象,不要依赖于具体实现编程
5)接口分离原则
不应该强迫程序依赖它们不需要使用的方法,一个借口不需要有太多的功能
6)迪米特原则
一个对象应当对其他对象尽可能地少的了解
7)设计模式
3)简单工厂模式
又被称为静态工厂模式,是创建型模式。它定义一个具体的工厂类负责创建一些类的实例
优点:客户端不需要在负责对象的创建,从而明确了各个类的职责
缺点:这个静态工厂类负责所有对象的创建,如果有新的对象增加,或者某些对象的创建方式不同,就需要不断地修改工厂类,不利于后期维护。
//一个简单的动物工厂类
public class AnimalFactory{
private AnimalFactory(){
}
public static Dog createDog(){
return new Dog();
}
public static Cat createDog(){
return new Cat();
//用if改进
if("dog".equals(type)){
return new Dog();
}else if("cat".equals(type)){
return new Cat();
}
else{return null;
}
}
}
//有了工厂后,通过工厂构造
public class AnimalDemo{
public static void main(String args[]){
Dog dd=AnimalFactory.createDog();
Dog cc=AnimalFactory.createCat();
}
}
4)工厂方法模式
工厂方法模式中抽象工厂类负责定义创建对象的接口,具体对象的创建工作由继承抽象工厂的具体类实现。
优点:客户端不需要再负责对象的创建,从而明确了各个类的职责,如果有新的对象增加,只需要增加一个具体的类和具体的工厂类即可,不影响已有的代码,后期维护容易,增强了系统的扩展性。
缺点:增加了编写代码的工作量
//
public abstract class Animal{
public abstract void eat();
}
public interface Factory{
public abstract Animal createAnimal();
}
//狗模型
public class Dog extends Animal{
public void eat(){
System.out.println("狗吃肉");
}
}
//狗工厂
public class DogFactory implements Factory{
public Animal createAnimal(){
return new Dog();
}
)
public class AnimalDemo{
public static void main(String[] args){
Factory f=new DogFactory();
Animal a=f.createAnimal();
a.eat();
}
}
5)单例设计模式
单例模式就是要确保类在内存中只有一个对象,该实例必须自动创建,并且对外提供
优点:保证在系统内存中只存在一个对象,因此可以节约系统资源。对于一些需要频繁创建和销毁的对象单例模式无疑可以提高系统的性能。
缺点:没有抽象层,扩展很难
职责过重
如何保证类内存中只有一个对象呢?
①构造方法私有
②在成员位置自己创建一个对象
③通过一个公共的方法提供访问
饿汉式
//public class StudentDemo{
public static void main(String args[]){
Student s1=new Student();
Student s2=new Student();
System.out.println(s1==s2);//false,并不是单例模式
}
public class Student{
private Student(){
}
//需使用静态方法,需加static
//外界不能修改,需加private
private static Student s=new Student();
public static Student getStudent(){
return s;
}
}
//懒汉式
public class Teacher{
private Teacher(){
}
private static Teacher t=null;
public static Teacher getTeacher(){
if(t==null){
t=new Teacher();
}
return t;
}
}
public class TeacherDemo{
public static void main(String[] args){
Teacher t1=Teacher.getTeacher();//造对象
Teacher t2=Teacher.getTeacher();//不造对象
System.out.println(t1==t2);
}
}
思考:单例模式的思想是什么?请写一个代码体现:
开发用饿汉式,面试一定要选择懒汉式。饿汉式在开发中不会出问题。
懒汉式:
A:懒加载(延迟加载)
B:会有线程安全问题
6)Runtime类的概述和使用
每个Java应用程序都有一个Runtime类实例,使应用程序能够与其运行的环境相连接。可以通过getRuntime方法获取当前运行时.
应用程序不能创建自己的Runtime类实例
使用:
public Process exec(String command)
7)装饰模式
装饰模式就是使用被装饰类的一个子类的实例,在客户端将这个子类的实例交给装饰类。是继承的替代方案。
优点:使用装饰模式,可以提供比继承更灵活的扩展对象的功能,它可以动态地添加对象的功能,并且可以随意地组合这些功能。
缺点:正因为可以随意组合,所以就可能出现一些不合理的逻辑
public interface Phone{
public abstract void call();
}
//装饰类
public abstract class Decorate implements Phone{
private Phone p;
public Decorate(Phone p){
this.p=p;
}
public void call(){
this.p.call();
}
}
//彩铃装饰类
public class Ring extends PhoneDecorate{
public Ring(Phone p){
super(p);
}
public void call(){
System.out.println("可以使用彩铃");
super.call();
}
}
public class IPhone implements Phone{
punlic void call(){
//重写方法
System.out.println("callIng");
}
}
public class PhoneDemo{
public static void main(Stirng args[]){
phone p=new IPhone();
p.call();
//加装饰
Decorate pd=new Ring(p);
pd.call();
}
}
4、JDK5新特性
自动装箱和拆箱,泛型,增强for循环,静态导入,可变参数,枚举
5、枚举
是指将变量的值一一列出来,变量的值只限于列举出来的范围。举例:一周只有7天,一年只有12个月等
//实现枚举类
public class Direction{
private Direction(){
}
创建变量
public static final Direction FRONT=new Direction("前");
public static final Direction BEHIND=new Direction();
public static final Direction LEFT=new Direction();
public static final Direction RIGHT=new Direction();
//以下属带参范围
public static final Direction FRONT=new Direction("前");
public static final Direction BEHIND=new Direction("后");
public static final Direction LEFT=new Direction("左");
public static final Direction RIGHT=new Direction("右");
private String name;
//加入成员变量
//带参构造
private Direction2(String name){
this.name=name;
}
public String getName(){
return name;
}
}
public class Direction{
public static void main(String[] args){
Direction d=Direction.Front;
System.out.println(d);//地址值
Direction d2=Direction.Front;//对于有参时
System.out.println(d2.getName);//前
}
}
2)enum类
public enum 枚举名{
枚举项1,枚举项2,枚举项3…;
}
public enum Direction2{
FRONT("前"),BEHIND("后"),LEFT("左"),RIGHT("右");
}
private String name;
//加入成员变量
//带参构造
private Direction2(String name){
this.name=name;
}
public String getName(){
return name;
}
}
//测试类
Direction d2=Direction2.Front;
System.out.println(d2);
System.out.println(d2.getName);//FRONT,前
3)使用枚举类时的注意事项
-定义枚举类要用关键字enum
-所有的枚举类都是Enum的子类
-枚举类的第一行必须是枚举项,最后一个枚举项后边的分号是可以省略的,但是如果枚举类有其他的东西,这个分号就不能省略。建议不要省略。
-枚举类可以有构造器,但必须是private的,它默认的也是private的。枚举类的用法比较特殊:枚举(“”);
-枚举类也可以有构造方法,但是枚举类必须重写该方法
//
Direction3 dd=Direction.FRONT;
switch(){
case FRONT:
System.out.println("FRONT");
break;
case BEHIND:
System.out.println("BEHIND");
break;
case LEFT:
System.out.println("LEFT");
break;
case RIGHT:
System.out.println("RIGHT");
break;}//FRONT
5)枚举类中的常见方法
int compareTo(E o)
String name()
int ordinal()
String toString()
T valueOf(Classtype.String name)
values()–此方法虽然在JDK文档中找不到,但每一个枚举类都具有该方法,它遍历枚举类的所有枚举值非常方便