java基础学习之访问权限与设计模式(四)

包及访问控制权限

包的定义

所谓的包实际上指的就是文件夹。
定义包

// An highlighted block
package com.yootk.demo;
public class Hello{
	public static void main(String args[]){
		System.out.println("hello world");
	}
}

由上可知,Hello类定义在了一个com.yootk.demo包中
打包编译:javac -d .Hello.java

  • “-d”:生成目录,根据package的定义生成;
  • “.”:设置保存路径,表示在当前所在路径下生成。
    解释运行:java com.yootk.Hello
    注意:在解释运行程序的时候不要进入到包里面,应该在包外面输入类的完整名称(包.类)。

包的导入

使用import

// An highlighted block
package com.yootk.demo;
import com.yootk.util.Message;
public class Hello{
	public static void main(String args[]){
		System.out.println("hello world");
	}
}

如果存在多个*.java 文件中的类相互引用的情况,为了解决编译顺序的问题,提供了通配符 的操作:javac -d ..java,这样就会自动根据代码的调用顺序进行程序编译。
如果不使用通配符*,则进行二步操作

  • 第一步:首先编译Message.java文件,执行:javac -d .Message.java
  • 第二步:编译Hello.java文件,执行:javac -d .Hello.java
    导入一个包中的多个类
    import com.yootk.util.;
    导入一个包中的全部静态属性和方法
    import static com.yootk.util.
    ;
    注意:以上做法并不会导致性能问题,因为类加载时也只是加载所需要的类,不使用的类不会被加载,所以import com.yootk.util.Message 和 import com.yootk.util.*性能是一样的。

访问控制权限

NO.范围privatedefaultprotectedpublic
1在同一个包的同一个类
2同一包的不同类
3不同包的子类
4不同包的非子类

简单理解为:private只能在一个类中访问;default只能在一个包中访问;protected在不同包的子类中;public为所有都可以。

定义com.yootk.demoa.A类

// An highlighted block
package com.yootk.demoa;
public class A{
	protected String info = "Hello";
}

定义com.yootk.demoa.B类

// An highlighted block
package com.yootk.demoa;
import com.yootk.demoa.A
public class B extends A{
	public void print(){
		System.out.println("A类的info = "+super.info);
	}
}

那么一般情况下如何去选择权限呢?
答:属性声明主要使用private权限;方法声明主要使用public权限。

单例设计模式(Singleton)

在之前大部分的属性定义时都使用了private进行声明,而对于构造方法也可以使用private声明,则此时的构造方法就被私有化了。
构造方法非私有化

// An highlighted block
class Singleton{
    public void print(){
        System.out.println("Hello");
    }
}
public class Test {
    public static void main(String[] args) {
        Singleton singleton = null;
        singleton = new Singleton();
        singleton.print();
    }
}

私有化构造方法

// An highlighted block
package chapter5;
class Singleton{
    private Singleton(){
        
    }
    public void print(){
        System.out.println("Hello");
    }
}
public class Test {
    public static void main(String[] args) {
        Singleton singleton = null;
        singleton = new Singleton();
        singleton.print();
    }
}
程序出现了编译错误,因为构造方法被私有化了,无法在外部调用,即无法在外部实例化Singleton类的对象。

现在就需要思考:在保证Singleton类中的构造方法不修改不增加,以及print()方法不修改的情况下,如何操作才可以让类的外部通过实例化对象去调用print()方法?
思考过程一:使用private 访问权限定义的操作只能被本类所访问,外部无法调用,现在既然构造方法被私有化,就证明,这个类的构造方法只能被本类所调用,即只能在本类中产生本类实例化对象

// An highlighted block
package chapter5;
class Singleton{
	Singleton instance = new Singleton();
    private Singleton(){
       }
    public void print(){
        System.out.println("Hello");
    }
}

思考过程二:对于一个类中的普通属性,默认情况下一定要在本类存在实例化对象后才可以进行调用,可是本程序在Singleton类的外部无法产生实例化对象,就必须想一个办法,让Singleton类中的instance属性可以在没有Singleton类实例化对象时来进行调用。因此想到可以使用static完成。

// An highlighted block
package chapter5;
class Singleton{
    static Singleton instance = new Singleton();
    private Singleton(){

    }
    public void print(){
        System.out.println("Hello");
    }
}
public class Test {
    public static void main(String[] args) {
        Singleton singleton = null;
        singleton = Singleton.instance;			直接这样获得类的实例化对象
        singleton.print();
    }
}
结果:Hello

思考过程三:首先按之前的学习,类中的全部属性都应该封装,然后通过getter、setter去访问获取,只不过getter方法也定义为static型。

// An highlighted block
package chapter5;
class Singleton{
    static Singleton instance = new Singleton();
    private Singleton(){

    }
    public void print(){
        System.out.println("Hello");
    }
    public static Singleton getInstance(){
    	return instance;
    }
}
public class Test {
    public static void main(String[] args) {
        Singleton singleton = null;
        singleton = Singleton.getInstance();			直接这样获得类的实例化对象
        singleton.print();
    }
}

如果要调用类中定义的操作,那么很显然需要一个实例化对象,这时就可以在类的内部使用static方式来定义一个公共的对象,并且每一次通过static方法返回唯一的一个对象,这样外部不管有多少次调用,最终一个类只能产生唯一的一个对象,这样的设计就属于单例设计模式。

不过现在还有一个小问题,就是以下代码也可以使用

// An highlighted block
class Singleton{
    private static Singleton instance = new Singleton();
    private Singleton(){

    }
    public void print(){
        System.out.println("Hello");
    }
    public static Singleton getInstance(){
        instance = new Singleton();			//重新实例化对象
        return instance;
    }
}
public class Test {
    public static void main(String[] args) {
        Singleton singleton = null;
        singleton = Singleton.getInstance();
        singleton.print();
    }
}

所以可以尝试加一个final关键字,饿汉式单例如下:

// An highlighted block
class Singleton{
    private final static Singleton instance = new Singleton();   //在这一行加就好了
    private Singleton(){

    }
    public void print(){
        System.out.println("Hello");
    }
    public static Singleton getInstance(){
        return instance;
    }
}
结果:Hello

懒汉式单例

//懒汉式单例类.在第一次调用的时候实例化自己   
 2 public class Singleton {
 3     private Singleton() {}
 4     private static Singleton single=null;
 5     //静态工厂方法   
 6     public static Singleton getInstance() {
 7          if (single == null) {
 8              single = new Singleton();
 9          }
10         return single;
11     }
12 }

饿汉式和懒汉式区别
饿汉就是类一旦加载,就把单例初始化完成,保证getInstance的时候,单例是已经存在的了。

而懒汉比较懒,只有当调用getInstance的时候,才回去初始化这个单例。

多例设计模式

多例设计模式其实就是有限的定义多个对象。不管是单例设计还是多例设计,有一个核心不可动摇,就是构造方法私有化。

// An highlighted block
class Sex{
    private String title;
    private static final Sex MALE = new Sex("男");
    private static final Sex FEMALE = new Sex("女");
    private Sex(String title){
        this.title = title;
    }
    @Override
    public String toString(){
        return this.title;
    }
    
    public static Sex getInstance(int ch){
        switch (ch){
            case 1:
                return MALE;
            case 2:
                return FEMALE;
            default:
                return null;
        }
    }
}
public class Test {
    public static void main(String[] args) {
        Sex sex = null;
        sex = Sex.getInstance(2);
        System.out.println(sex);
    }
}
执行结果:女

在JDK1.7之前,switch只能利用int或者char进行判断,正因为如果纯粹是数字或字符意义不明确,所以增加了String的支持。

// An highlighted block
class Sex{
    private String title;
    private static final Sex MALE = new Sex("男");
    private static final Sex FEMALE = new Sex("女");
    private Sex(String title){
        this.title = title;
    }
    @Override
    public String toString(){
        return this.title;
    }
    
    public static Sex getInstance(String ch){
        switch (ch){
            case "man":
                return MALE;
            case "woman":
                return FEMALE;
            default:
                return null;
        }
    }
}
public class Test {
    public static void main(String[] args) {
        Sex sex = null;
        sex = Sex.getInstance("man");
        System.out.println(sex);
    }
}
执行结果:男

动态代理模式

什么是代理模式?
有一个打印类:

public class Printer{
    public void print(){
        System.out.println("打印!");
    }
}

我想在打印之前先记录一下日志怎么做?
最简单的方法:在打印的功能前面直接加上记录日志的功能。

public class Printer{
    public void print(){
    	System.out.println("记录日志!");
        System.out.println("打印!");
    }
}

看上去好像没有问题,但是我们修改了打印机的源代码,破坏了面向对象的开闭原则,有可能影响到其它功能。怎么解决呢?很容易可以想到,既然不能修改原来的代码,那我新建一个类吧。

public class LogPrinter extends Printer {
    public void print(){
        System.out.println("记录日志!");
        System.out.println("打印!");
    }
}

这个类继承了打印机的类,重写了打印机的print方法,提供了记录日志的功能,以后需要打印机的时候使用这个类就好。问题似乎得到了解决,我们可以在这个解决方案的基础上进一步的优化:

先抽象出一个接口:

public interface IPrinter {
    void print();
}

打印机类实现这个接口:

public class Printer implements IPrinter{
    public void print(){
        System.out.println("打印!");
    }
}

创建打印机代理类也实现该接口,在构造函数中将打印机对象传进去,实现接口的打印方法时调用打印机对象的打印方法并在前面加上记录日志的功能:

public class PrinterProxy implements IPrinter{
    private IPrinter printer;

    public PrinterProxy() {
        this.printer = new Printer();
    }

    @Override
    public void print() {
        System.out.println("记录日志");
        printer.print();
    }
}

试一把吧:

public class Test {
    public static void main(String[] args) {
        PrinterProxy proxy = new PrinterProxy();
        proxy.print();
    }
}
结果:记录日志
	  打印

以后我们就可以直接实例化PrinterProxy对象调用它的打印方法了,这就是静态代理模式,通过抽象出接口让程序的扩展性和灵活性更高了。

静态代理是完美无缺的吗?

考虑一下,如果我的打印机类中还有别的方法,也需要加上记录日志的功能,就不得不将记录日志的功能写n遍。进一步如果我还有电视机,电冰箱的类里面的所有方法也需要加上记录日志的功能,那要重复的地方就更多了。

怎么办?

动态代理闪亮登场:

要想不重复写记录日志的功能,针对每一个接口实现一个代理类的做法肯定不可行了,可不可以让这些代理类的对象自动生成呢?

Jdk提供了invocationHandler接口和Proxy类,借助这两个工具可以达到我们想要的效果。

invocationHandler接口上场:

//Object proxy:被代理的对象 
//Method method:要调用的方法 
//Object[] args:方法调用时所需要参数 
public interface InvocationHandler {
     public Object invoke(Object proxy, Method method, Object[] args) throws Throwable;
}

接口里只有一个方法invoke,这个方法非常重要,先混个脸熟,稍后解释。

Proxy类上场,它里面有一个很重要的方法 newProxyInstance:

//CLassLoader loader:被代理对象的类加载器 
//Class<?> interfaces:被代理类全部的接口 
//InvocationHandler h:实现InvocationHandler接口的对象 
 public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) throws IllegalArgumentException

调用Proxy的newProxyInstance方法可以生成代理对象

一切准备就绪动态代理模式千呼万唤始出来:

接口IPrinter 和 该接口的实现类 Printer的代码同前。

实现一个类,该类用来创建代理对象,它实现了InvocationHandler接口

public class ProxyHandler implements InvocationHandler {
    private Object targetObject;//被代理的对象
    //将被代理的对象传入获得它的类加载器和实现接口作为Proxy.newProxyInstance方法的参数。
    public  Object newProxyInstance(Object targetObject){
        this.targetObject = targetObject;
        //targetObject.getClass().getClassLoader():被代理对象的类加载器
        //targetObject.getClass().getInterfaces():被代理对象的实现接口
        //this 当前对象,该对象实现了InvocationHandler接口所以有invoke方法,通过invoke方法可以调用被代理对象的方法
        return Proxy.newProxyInstance(targetObject.getClass().getClassLoader(),targetObject.getClass().getInterfaces(),this);
    }
    //该方法在代理对象调用方法时调用
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("记录日志");
        return method.invoke(targetObject,args);
    }
}

被代理的对象targetObject可以通过方法参数传进来:

public Object newProxyInstance(Object targetObject){
       this.targetObject=targetObject;

我们重点来分析一下这段代码:

return Proxy.newProxyInstance(targetObject.getClass().getClassLoader(),targetObject.getClass().getInterfaces(),this);

动态代理对象就是通过调用这段代码被创建并返回的。

方法有三个参数:

第一个参数:

targetObject.getClass().getClassLoader():targetObject对象的类加载器。

第二个参数:

targetObject.getClass().getInterfaces():targetObject对象的所有接口

第三个参数:

this:也就是当前对象即实现了InvocationHandler接口的类的对象,在调用方法时会调用它的invoke方法。

再来看一下这段代码:

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
      //在这里可以通过判断方法名来决定执行什么功能
      System.out.println("记录日志");
      //调用被代理对象的方法
      return method.invoke(targetObject, args);
}

这个方法就是生成的代理类中的方法被调用时会去自动调用的方法,可以看到在这个方法中调用了被代理对象的方法: method.invoke(targetObject, args);

我们可以在这里加上需要的业务逻辑,比如调用方法前记录日志功能.
见证奇迹的时刻到了:

public class Test {
   public static void main(String[] args){
   ProxyHandler proxyHandler=new ProxyHandler();
   IPrinter printer=(IPrinter) proxyHandler.newProxyInstance(new Printer());
   printer.print();
 }
}
打印结果:记录日志
		 打印

当执行printer.print();时会自动调用invoke方法,很多初学者不理解为什么能调用这个方法,回忆一下创建代理对象的时候是通过

return Proxy.newProxyInstance(targetObject.getClass().getClassLoader(),targetObject.getClass().getInterfaces(),this);

来创建的,方法的第三个参数this是实现了 InvocationHandler 接口的对象, InvocationHandler 接口有invoke方法。现在有点思路了吧~

将被代理的对象作为参数传入就可以执行里面的任意方法,所有的方法调用都通过invoke来完成。不用对每个方法进行处理,动态代理是不是很简洁。

代理模式的定义:代理模式给某一个对象提供一个代理对象,并由代理对象控制对原对象的引用,通俗的来讲代理模式就是我们生活中常见的中介,动态代理和静态代理的区别在于静态代理我们需要手动的去实现目标对象的代理类,而动态代理可以在运行期间动态的生成代理类。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值