java基础

直接接收键盘输入数据

//判断某一年的月份有多少天
//1-闰年,2月,有29,平年 28天
//2-1月, 3月,5月,7月,8月,10月,12月有31天    4月,6月,9月,11月有30天
//闰年: year%4==0&&year%100!=0||year%400==0
public class ScannerDateMonthDaysDemo {
    public static void main(String args[]) {
        //请输入年份,创建键盘扫描器
        Scanner key = new Scanner(System.in);
        System.out.println("请输入年份:  ");
        int year = key.nextInt();
        
        System.out.println("请输入月份: ");
        int month = key.nextInt();
        //关闭扫描器
        key.close();
        
        boolean isLeapYear = year%4==0&&year%100!=0||year%400==0;
        System.out.println(year+"年是闰年吗?"+isLeapYear);
        
        //判 断是大月还是小月(2月除外)
        boolean isLittleMonth = month==4||month == 6 || month ==9||month==11;
        boolean isLargeMonth = month==1||month == 3||month ==5||month ==7||month ==8||month ==10||month ==12;
        
        
        //方式一,三目运算符计算月里有多少天
        int days = isLittleMonth?30:(isLargeMonth?31:(isLeapYear?29:28));
        System.out.println(year+"年"+month+"月一共有"+days+"天");
        
        //方式二,if else语句运算符计算月里有多少天
        int ifdays = 0;
        if(isLargeMonth) {
            ifdays = 31;
        }else if(isLargeMonth) {
            ifdays = 30;
        }else {
            if(isLargeMonth) {
                ifdays = 29;
            }else {
                ifdays=28;
            }
        }
        System.out.println("if语句判断:  "+year+"年"+month+"月一共有"+ifdays+"天");
        
        //方式三,if+switch
        int issw = 0;
        switch (month) {
        case 1:
        case 3:
        case 5:
        case 7:
        case 8:
        case 10:
        case 12:
            issw=31;
            break;
        case 4:
        case 6:
        case 9:
        case 11:
            issw=30;
        case 2:
            if(isLeapYear) {
                issw = 29;
            }else {
                issw= 28;
            }
            break;
        }
        System.out.println("if+switch判断:  "+year+"年"+month+"月一共有"+issw+"天");
        
    }

}
--------------------------------------------------------------------------------------------------------------------
请输入年份:  
2022
请输入月份: 
2
2022年是闰年吗?false
2022年2月一共有28天
if语句判断:  2022年2月一共有28天
if+switch判断:  2022年2月一共有28天

抽象类与接口

抽象类

  1. 第一个问题:抽象类有什么用?它存在的意义是什么?
    这回答这个问题之前,先看一下动物界里的一个例子:首先,有一个父类Animal,接着有两个子类,分别是鸟Bird和狗Dog,如下:
public class Animal{

 public void bark(){}

}
public class Bird extends Animal{

 public void bark(){

 System.out.println("****唧唧~唧唧~");
}

}
public class Dog extends Animal{

 public void bark(){

 System.out.println("****汪汪~汪汪~");
}

}

---可以看到,父类Animal有一个叫唤的方法bark(),两个子类都继承了这个方法,并进行了重写,Bird是唧唧叫,Dog是汪汪叫,现在的问题是Animal怎么叫?它的bark()方法体里应该输出什么样的叫声,是“汪汪”还是“唧唧”?
----显然,动物是个抽象的集合名词,我们并不知道动物Animal怎么叫,所以,bark()方法在父类中实现不了,或者说实现了没有任何意义,bark()方法只能在子类中根据具体情况去实现。这样的话就可以把父类Animal中的bark()方法声明为abstract抽象方法,此时这个类也成了abstract抽象类。
----抽象类自己并不能实例化,它存在的意义就是为了让子类继承。对于一个父类,它的某个方法在父类中实现没有任何意义,必需在子类中根据具体情况实现,那么这个方法可以声明为abstract抽象方法,此时这个父类也成了(一定要写上abstract)abstract抽象类。
-------1.就像上面说的,这样弄“实现了没有任何意义”;2.Java里面不鼓励函数体的内容为空;3.用法上子类继承父类后,子类会被强制重写父类中的抽象方法,起到一个提醒和约束的作用。

  1. 抽象类在用的过程中有哪些关键点?

抽象类并不是只能包含象方抽法,他也可以包含普通的成员方法和成员变量。****它和普通类的区别主要有三点:

1.抽象类中的抽象方法只能用public或protected修饰。因为,抽象方法来到世间就是为了让子类继承重写的,而private的方法不能被子类继承,显然矛盾。
2.抽象类不能创建对象,即不能实例化。因为,抽象类中包含没有实现的抽象方法,是不完整的,所以不能用来创建对象。(有一种特殊情况,就是一个类中并没有抽象方法,但是类class有abstract修饰,被声明为抽象类,那么这个类也是抽象类,也不能实例化。)
3.如果一个类继承于一个抽象类,那么子类必须实现父类的抽象方法。否则,子类继承的方法还是抽象方法,子类也变成了抽象类,要用abstract修饰。
例:三个类,Animal.java,Dog.java,Test.java


    public abstract class Animal {  //抽象类中可以有非抽象方法,也可以有成员变量
          private int a = 10;
           
          public abstract void bark();  
            //如果没有此抽象方法,但是class前有absract修饰,也是抽象类,也不能实例化
          public void say() {            //普通成员方法
            System.out.println("我是抽象类中的非抽象方法,此抽象类中的私有成员变量a= " + a);
          }
         
          public int getA() {
            return a;
          }
          public void setA(int a) {
            this.a = a;
          }
        }

    public class Dog extends Animal{
          public void bark() {        //子类实现Animal的抽象方法
            System.out.println("汪汪~汪汪~");  
            System.out.println("我是子类,不能直接调用父类的私有变量a  :(");  
            System.out.println("我是子类,只有通过super.getA()调用父类的私有变量a:" + super.getA());  
          }
        }
        public class Test {
          public static void main(String[] args) {
            Dog dog = new Dog();
            dog.say();    //子类继承调用Animal的普通成员方法
            dog.bark();    //子类调用已经实现过的方法
          }
        }



两者区别与相同:
        1.java 中的接口与抽象类中都包含方法的定义(不实现)。
        2.接口是公开的,里面不能有私有变量和方法,是用于让别人使用的。而抽象类可以有私有变量和方法。另外抽象类可以包含某些方法的部分实现。
        3.抽象类的实现只能由这个类的子类给出,也就是说,这个实现处在抽象类所定义出的继承的等级结构中。由于java类的单继承性,因此抽象类作为类型定义工具的效率大大折扣。而一个java类可以实现多个接口。实际上大多数项目中是面向接口编程的。
抽象类举例:

public class abstractFruit {
    public static void main(String args[]) {
        Pear pear = new Pear();
        pear.harvest();
        String colorr = pear.color; // 创建对象
        System.out.println(colorr);
    }

}

// 抽像类不能创建成对象,要通过其子类来实现抽像方法(也可以没有抽像方法)来创建对象
abstract class Fruit {
    public String color;

    public Fruit() {
        color = "绿色";
    }

    public abstract void harvest();
}

class Pear extends Fruit {
    public void harvest() {
        System.out.println("大白梨已经熟了!!");

    }

}
----------------------------------------------------------------------------------------------------------
大白梨已经熟了!!
绿色

接口举例:

public class interfaceCalculate{
    public static void main(String args[]) {
            Round round =new Round();
            
            float r = 3f;
            float area = round.getArea(r);
            System.out.println("圆的面积是: "+area);
            float circumference = round.getCircumference(r);
            System.out.println("圆的周长是:"+circumference);
        
    }
}
class Round implements Calculate {

    @Override
    public float getArea(float r) {
        float area = PI * r * r;
        return area;
    }

    @Override
    public float getCircumference(float r) {
        float circumference = 2 * PI * r;
        return circumference;
    }

    

}
//接口
interface Calculate {
    final float PI = 3.14159f;

    float getArea(float r); // 求圆面积的方法

    float getCircumference(float r); // 求圆周长的方法

}
-------------------------------------------------------------------------------------------------------------------
圆的面积是: 28.274311
圆的周长是:18.84954

内部类

        内部类可以程序代码更加紧湊,程序更具模块化。例如,一个类中的程序代码要用到另一个类的实例对象,而另一个类中的程序代码又要访问第一个类中的成员,这时就可以将另一个类做成第一个类的内部类,这样就好的多。
        下面是一个多线程简单转账功能代码--锁代码块方式同步。我们不关心这个我们只要注意到 DeadLockDemo2 类中的Helper内部类,这个内部类的主要作用是让DeadLockDemo2 类中的方法调用转账功能方法,它希要该方法的变量money,所以用内部类(定义在方法中了),没听过调用子方法,这用内部类是很好的方式。

public class ThreadTest2 {
   public static void main(String args[]) {
       DeadLockDemo2 dl2 = new DeadLockDemo2();
       new Thread(dl2,"线程1").start();
       new Thread(dl2,"线程2").start();
   }
}

class DeadLockDemo2 implements Runnable{
    //初始化两个账户
    Account a=new Account("A", 1000);
    Account b=new Account("B", 1000);
    
    //加一把锁,解决死锁
    private Object jiasuo =new Object();
        
    @Override
    public void run() {
        transferMoney(a, b, 100);
        transferMoney(b, a, 100);
        
    }
    //锁代码块方式保同步,这里两个锁,转出账户,转入账户。
    public void transferMoney(Account fromAccount,Account toAccount,int money) {
        
        //获取两把锁的哈希值
        int fromHash =System.identityHashCode(fromAccount);
        int toHash = System.identityHashCode(toAccount);
        
        class Helper{                     //内部类
            public void transfer() {
                System.out.println("线程"+Thread.currentThread().getName()+"得到锁"+toAccount.getName());
                if(fromAccount.getMoney()< money) {
                    System.out.println("转出账号的余额不足!");
                }else {
                    //进行转账,就是一个减,一个加
                    fromAccount.setMoney(fromAccount.getMoney()-money);
                    toAccount.setMoney(toAccount.getMoney()+money);
                    
                    
                    //执行完转账后,显示一下两个账号的余额
                    System.out.println("转账后:"+fromAccount.getName()+"有:"+fromAccount.getMoney());
                    System.out.println("转账后:"+toAccount.getName()+"有:"+toAccount.getMoney());
                    
                }
            }
        }
        
        if(fromHash > toHash) {
            synchronized (fromAccount) {
                System.out.println("线程"+Thread.currentThread().getName()+"得到锁"+fromAccount.getName());
                synchronized (toAccount) {
                    new Helper().transfer();
                    
                }
            }
        }else if(fromHash < toHash) {
            synchronized (toAccount) {
                System.out.println("线程"+Thread.currentThread().getName()+"得到锁"+toAccount.getName());
                synchronized (fromAccount) {
                    new Helper().transfer();
                    
                }
            }
        }else {
            synchronized (jiasuo) {
                synchronized (fromAccount) {
                    System.out.println("线程"+Thread.currentThread().getName()+"得到锁"+fromAccount.getName());
                    synchronized (toAccount) {
                        new Helper().transfer();
                    }
                }
            }
        }
        
    }
    
    
}

class Account{
    public Account(String name,int money) {
        this.name=name;
        this.money=money;
    }
    private String name;
    private int money;
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public int getMoney() {
        return money;
    }
    public void setMoney(int money) {
        this.money = money;
    }
    
}

重写equals()方法

        两个基本数据类型的数值可以直接用"==”判断是否相等,但是两个对象, 是在入在不同的内存空间中的两个数据结构,要判断它们的内容是否相等不能使用"=="操作符,那样只会对比两个对象所引用的内存地址,而不是对象内容。判断两个对象的空容是否相等,必须用到equals()方法, 这个方法在Object类中定义。它是所有类的父类,也就是说任何类都隐式的继承了该方法,只是没有具体的实现的代码。一个完整的的数据存储类(也就是业务类)都应该重写该方法,实现自已的相等性判断。
        下面是一个业务类User和一个测试主类EqualsDemo

public class User {    
    private String name; // 用户名
    private int id; // 用户ID
    private int level = 1; // 用户级别    
    public boolean equals(Object obj) {
        if (this == obj)
            return true;
        if (obj == null)
            return false;
        if (getClass() != obj.getClass())
            return false;
        User other = (User) obj;
        if (id != other.id)
            return false;
        if (level != other.level)
            return false;
        if (name == null) {
            if (other.name != null)
                return false;
        } else if (!name.equals(other.name))
            return false;
        return true;
    }    
    public String getName() {// 获取用户名方法
        return name;
    }    
    public void setName(String name) { // 设计用户名方法
        this.name = name;
    }    
    public int getId() { // 获取用户编号方法
        return id;
    }    
    public void setId(int id) { // 设置用户编号方法
        this.id = id;
    }    
    public int getLevel() { // 获取用户级别方法
        return level;
    }    
    public void setLevel(int level) {
        this.level = level;
    }    
}

EqualsDemo.java

public class EqualsDemo {
    public static void main(String[] args) {
        User userA=new User();                  // 创建用户A
        userA.setId(1);                         // 初始化用户A
        userA.setLevel(5);
        userA.setName("飞龙");        
        User userB=new User();                  // 创建用户B
        userB.setId(1);                         // 初始化用户B
        userB.setLevel(5);
        userB.setName("飞龙");
        System.out.println("userA equals userB:"+userA.equals(userB));
        userB.setName("绿茶");                   // 给用户B改名
        System.out.println("用户B改名后。");
        System.out.println("userA equals userB:"+userA.equals(userB));
    }
}
----------------------------------------------------------------------------------------------------------------------
userA equals userB:true
用户B改名后。
userA equals userB:false

加密算法 MD5与 SAH1

/**
 * 讲明文的字符串加密层密文字符串的工具类
 * @author Administrator
 *
 */
public class SecurityUtil {
    
    public static void main(String[] args) {
        System.out.println(md5("admin加密后为:", "admin123"));
    }
    
    //项目中使用的加密的算法,比较流行的算法:MDX(MD2,MD5),SHA-X(SHA-1,SHA-256,384,512)  可以说不能反过来推算到明文,
    public static String md5(String str) {
        try {
            MessageDigest md = MessageDigest.getInstance("MD5");//这个参数就是加密的算法
            md.update(str.getBytes()); // 明文字符串更新到md对象里
            byte[] rs = md.digest(); //做加密计算,计算结果就是密文
            //byte[]转16进制的字符串,BigInteger大整数
            return new BigInteger(1, rs).toString(16);   //byte数据不可返回,要转成字符串或int啥的,1表十六制,不可为其它数,16表十六进制
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        }
        return null;
    }
    
    //加密深一点
    public static String md5(String str1,String str2) {
        try {
            MessageDigest md = MessageDigest.getInstance("MD5");//这个参数就是加密的算法
            md.update(str1.getBytes()); // 明文字符串更新到md对象里
            md.update(str2.getBytes());
            //md.update("abcxiong".getBytes());  //自定义自已加的盐值,也是进一步加密
            byte[] rs = md.digest(); //做加密计算,计算结果就是密文
            //byte[]转16进制的字符串,BigInteger大整数
            return new BigInteger(1, rs).toString(16);
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        }
        return null;   //万一MD5不存在,返回null,不太可能执行到这一步
    }
    
    //SHA-1加密算法
    public static String sha1(String str) {
        try {
            MessageDigest md = MessageDigest.getInstance("SHA-512");
            md.update(str.getBytes());
            byte[] rs = md.digest();
            StringBuffer sb = new StringBuffer();  //因为是一个一个字符的拼且,StringBuffer比String高效
            for(byte b:rs) {
                //可以将byte类型转换16进制格式是数字符串
                sb.append(String.format("%02x", b));  //%02x,一个byte类型整数----> 2位十六进制字符来表示
            }
            return sb.toString();
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        }
        return null;
    }
    
    
    
    
}

结果,且每次运行都不一样,这是一个例子:65a79ae28cbe847d4bffff62bca0bb01

加密算法springmvc+easyui+hibernate中的应用。

StuController.java -----password = SecurityUtil.md5(stuname,password);

 @ResponseBody
    @RequestMapping(value="/addStudent",method=RequestMethod.POST)
    public String addStudent(@RequestBody Student student) throws ParseException{  //直接接收对象,
        String password = student.getPassword();
        String stuname = student.getStuname();
        password = SecurityUtil.md5(stuname,password);
        student.setPassword(password);
        try {
            stuService.add(student);
        }catch(Exception e) {
            return "error";
        }
        return "ok";
    }

student_manager.jsp中的js代码

function student_openAdd(){
          $("#student_dialog_form").form("clear");   //清理表单
          $("#student_dialog").dialog({
              title:"添加用户",
              closed:false,                               //显示弹出对话框
              modal:true,
              onOpen:function(){    //加载成功对话框时,它里的用户不能修改,且处于不可编辑状态
                  //$("#stuname").attr("disabled","disabled");  //jq代码不起作用,从浏览器中查看器中可以看到,它打开时隐藏了
                  $("#stuname").textbox({disabled:false})   //这时添加用户要处于可编辑状态
              },
              buttons:[
                  {
                      text:"确定",iconCls:"icon-ok",
                      handler:function(){                   //确定按钮,要干的活(添加学生信息到数据库中,
                          //1.第一种思路,在js中封装数据,其实就是处理对象类型数据,转为json格式发送到后台处理到数据库中。
                          //先拿到rolesId:[1,2,3...]--->1,2,3--->[{id:1},{id:2}..]
                          var rolesId =$("#rolesId").val();       //该对象是combotree对象发送的请求从浏览器的网络-请求中可以看到是数组[{},{}...]对象
                          var rolesArr = rolesId.split(",");
                          var roleObjStr = "[";
                          for(var i=0;i[{id:1},{id:2},....,{id:n}]
                          var roleObj = eval("("+roleObjStr+")");    //([{id:1},{id:2},....,{id:n}])为一个json对象
                          $.ajax({
                              url:"/sshwebtest/addStudent",
                              method:"post",
                              contentType:"application/json;charset=utf-8",  //指定传送请求的数据为json格式,如果是8种基本数据类型,就不用这种格式
                              data:JSON.stringify({    //封装请求
                                  stuname:$("#stuname").val(),
                                  password:$("#password").val(),
                                  state:$("#state").val(),
                                  regDate:$("#regDate").val(),
                                  roles:roleObj
                              }),
                              success:function(data){  //"ok","error"
                                  if(data == "ok"){
                                      $("#student_dialog").dialog("close");   
                                      $("#studentsDatagrid").datagrid("reload");
                                  }else{
                                      $.messager.alert("信息提示","提交失败!","info");
                                  }
                              }
                              
                          }); 
                           } */
                    
                     
                  },
                  {
                      text:"取消",iconCls:"icon-cancel",
                      handler:function(){
                          $("#student_dialog").dialog("close");   //关闭弹出对话框
                      }
                  }
              ]
          })
      }

http://localhost/sshwebtest/main---用户管理---添加----

image.png

数据库中表:
image.png

方法重载

----如果有两个方法的方法名相同,但参数不一致,哪么可以说一个方法是另一个方法的重载。 具体要注意的知识点:

①****方法名相同

②****方法的参数类型,参数个,要么类型不一样,要么个数不一样,要么都不一样

③****方法的返回类型可以不相同

④****方法的修饰符可以不相同

⑤main 方法也可以被重载

static关键字

  1. static关键字的用途
    ---“static方法就是没有this的方法。在static方法内部不能调用非静态方法,反过来是可以的。而且可以在没有创建任何对象的前提下,仅仅通过类本身来调用static方法。这实际上正是static方法的主要用途。”--- 方便在没有创建对象的情况下来进行调用(方法/变量)即类.方法直接调用。
    ---在静态方法中不能访问非静态成员方法和非静态成员变量,但是在非静态成员方法中是可以访问静态成员方法/变量的。
public class StaticExample {
    private static String str1 = "staticProperty";
    private String str2 = "property";
    
    public StaticExample() {
        
    }
    public void print1() {
        System.out.println(str1);
        System.out.println(str2);
        println2();
        
    }
    public static void println2() {
        System.out.println(str1);
        //Cannot make a static reference to the non-static field str2
        System.out.println(str2);  
        //Cannot make a static reference to the non-static method print1() from the type StaticExample
        print1();        
        
    }
}

  1. static变量
    ---static变量也称作静态变量,静态变量和非静态变量的区别是:静态变量被所有的对象所共享,在内存中只有一个副本,它当且仅当在类初次加载时会被初始化。而非静态变量是对象所拥有的,在创建对象的时候被初始化,存在多个副本,各个对象拥有的副本互不影响。
    static成员变量的初始化顺序按照定义的顺序进行初始化。


    image.png
  2. static代码块
      static关键字还有一个比较关键的作用就是用来形成静态代码块以优化程序性能。static块可以置于类中的任何地方,类中可以有多个static块。在类初次被加载的时候,会按照static块的顺序来执行每个static块,并且只会执行一次。
     为什么说static块可以用来优化程序性能,是因为它的特性:只会在类加载的时候执行一次。下面看个例子:

public class StaticDemo {
   static {
         int age =32;
         System.out.println("age的值为:"+age);
   }
   public static void main(String args[]) {
       new  StaticDemo();
       new StaticDemo();
   }
}
------------------------------------
age的值为:32
  1. 单例设计模式:
    static一个很重要的用途就是实现单例设计模式。单利模式的特点是该类只能有一个实例,为了实现这一功能,必须隐藏类的构造函数,即把构造函数声明为private,并提供一个创建对象的方法,由于构造对象被声明为private,外界无法直接创建这个类型的对象,只能通过该类提供的方法来获取类的对象,要达到这样的目的只能把创建对象的方法声明为static,程序实例如下:
class Singleton{
    private static Singleton instance=null;
    private Singleton(){}
    public static Singleton getInstance(){
        if(instance==null){
            instance=new Singleton();
        }
        return instance;
    }
}

  1. 静态变量作为计数器。
public class StaticJusuqi {
    public static void main(String args[]) {
        Student[] arr = new Student[5];
        arr[0]= new Student("张三",19);
        arr[1]=new Student("李四",18);
        arr[2]=new Student("王二",20);
        arr[3]=new Student("麻子",20);
        for(int i=0;i<4;i++) {
            System.out.println(arr[i].getName()+"  "+arr[i].getAge()+"  "+arr[i].getNumber());
        }
    }
    
}

class Student {
    private  static int num;
    private String name;
    private int age;
    private int number;
    public Student() {};
    
    public  Student(String name,int age) {
        num++;
        this.number=num;
        this.name = name;
        this.age =age;
    }
    public void setNmae(String name) {
        this.name=name;
        
    }
    
    public String getName() {
        return name;
    }
    public void setAge(int age) {
        this.age=age;
    }
    public int getAge() {
        return age;
    }
    
    public int getNumber() {
        return number;
    }
    
}
--------------------------------------------------------------------------------------------------------------------
张三  19  1
李四  18  2
王二  20  3
麻子  20  4
//如果把Student里的成员变量改成非静态成员变量的话 结果如下
张三  19  1
李四  18  1
王二  20  1
麻子  20  1

File(java.io.File)

  1. servlet中创建文件夹与过滤获取该文件夹下的后缀为.class的文件.
    ----方法功能简介:获取指定包下的.class文件,即运行时的java类的编译文件 。再通过该文件名拿到纯粹的文件名(cn.ybzy.xiong.class---->cn.ybzy.xiong),再拿到xiong的Class对象,然后,拿到带有注解@AuthClass的方法,再去拿这种方法上的注解@RequestMapping(value="ccc",xxx,xx),再我后,保存value[0]到pathes数组链表中。
@Override // 点source---Override/implements...--init()
    public void init() throws ServletException {
        // ICO容器获取
        ServletContext context = getServletContext();// web项目中最大的域对象
        applicationContext = WebApplicationContextUtils.getWebApplicationContext(context);
        try {
            // 权限控制初始化
            // packageName实施权限控制的包全名
            String packageName = "cn.ybzy.qihangkt.controller";
            String packageNamePath = packageName.replace(".", "/"); // --->cn/ybzy/qihangkt/controller
            // 进一步的获取到控制层对应在服务器磁盘上的绝对路径
            String packageNameRealPath = this.getClass().getClassLoader().getResource(packageNamePath).getPath();
            System.out.println(packageNameRealPath); // -->/C:/Users/Administrator/Desktop/apache-tomcat-8.5.55/webapps/qihangkttest/WEB-INF/classes/cn/ybzy/qihangkt/controller/
            File file = new File(packageNameRealPath); // 产生控制层的文件夹
            String[] classFileNames = file.list(new FilenameFilter() {
                @Override
                public boolean accept(File dir, String name) { // 过滤后缀为.class的文件,这是系统自定的方法我们在些重写
                    if (name.endsWith(".class")) {
                        return true;
                    }
                    return false;
                }
            });
            // 构造出这个class文件的包全名
            List pathes = new ArrayList<>();   //权限控制路径集合
            for (String classFileName : classFileNames) {
                // .class这个后缀给删除掉
                classFileName = classFileName.substring(0, classFileName.indexOf(".class"));
                // 拿到纯粹的类的包全名如:cn.ybzy.qihangkt.controller.AdminController
                String classAllpackageName = packageName + "." + classFileName;
                // 通过反射,获取到对应类的对象
                Class clazz = Class.forName(classAllpackageName);
                // 拿到这些controller的对象,获取到在他们身上的注解,如果没有@AuthClass,则表明不执行此类,放掉
                //剩下的控制类,都是有@AuthClass这个注解的类,这些类都要进行权限控制
                if (!clazz.isAnnotationPresent(AuthClass.class)) continue;
                Method[] methods = clazz.getDeclaredMethods(); //包括private方法
                for(Method method:methods) {
                    if(!method.isAnnotationPresent(AuthMethod.class)) continue;
                    //都是有@AuthMethod的方法,拿到要保存到权限表里t_resource-->path字段中
                    RequestMapping requestMapping = method.getAnnotation(RequestMapping.class);
                    //System.out.println(requestMapping.value()[0]);  //-->/admin/admin.html ,/admin/user_manager.html...
                    pathes.add(requestMapping.value()[0]);
                }
            }
            //List pathes: 包含了controller包里,所有被@AuthClass和@AuthMethod共同作用的方法上面的@RequestMapping的value值,都在里面
            //pathes放到表t_resource,获取到ResourceService,通过ResourceService对象调用ResourceDao里sql语句执行添加到表的操作
            ResourceService resourceService = (ResourceService) applicationContext.getBean("resourceService");
            resourceService.initPathes(pathes);
            context.setAttribute("allPermissionPathes", pathes);  //全局域空间中要保存这些路径(权限)
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

异常处理

        在程序开发中,有些错误是程序员没有预料到的,或者是因为超 出了程序员控制之外的环境因素。比如用户的坏数据,试图打开一个根本不存在的文件等。Java中将异常通过一个类对象来表示,程序运行时挽出的异常,实际上抛出的就是一个异常类对象。
         使用异常处理的好处---出现异常的语句后面的代码可以继续执行。

public class Test1 {
    public static void main(String args[]) {
        String age = "30d";
        int number = Integer.parseInt(age);
        System.out.println("程序结束!");
        
    }

}
--------------------------------------------------------------------------------------------------------------------
Exception in thread "main" java.lang.NumberFormatException: For input string: "30d"
    at java.lang.NumberFormatException.forInputString(NumberFormatException.java:65)
    at java.lang.Integer.parseInt(Integer.java:580)
    at java.lang.Integer.parseInt(Integer.java:615)
    at ExceptionTest.Test1.main(Test1.java:6)

加了异常后的效果,可以看到输出了‘程序结束’

public class Test1 {
    public static void main(String args[]) {
        try {
            String age = "30d";
            int number = Integer.parseInt(age);
        } catch (Exception e) {

        }

        System.out.println("程序结束!");

    }

}
--------------------------------------------------------------------------------------------------------------------
程序结束

捕获异常的几种方式
一,使用try-catch语句。

try{
    //程序执行语句;
}catch(Exception e){   //Exception是异常类的最大范围类
   e.getMessage();      //输出异常性质
   e.printStackTrace();   //指出异常的类型,性质,栈层次及出现在程序中的位置。
}
.....
finally{
}

二,使用throws关键字抛出异常,可以逗号分隔多个异常。
       该方一般用在方法声明处,如果是ECPLISE等开发工具,我们会自动抛出,不用手写。

public class Test1 {
    public static void main(String args[]){
        try {
            int result = pop(3, 0);
            System.out.println(result);
        }catch(NumberFormatException e) {
            e.printStackTrace();
        }
        System.out.println("程序结束!");
        
    }
    
    static int pop(int number1,int number2) throws NumberFormatException {
        int result = number1/number2;
        return result;
    }

}

三,自定义异常类与throw关键字
       自定义异常必须继承Exception类。
1,创建自定义异常类。
2,在方法中通过throw关键字抛出异常对象。
3,如果在当前抛出异常的方法中处理异常,可以使用try-catch语句捕获并处理,否则在方法的声明处通过throws关键字指明要抛出给方法调用者的异常。继续进行下一步操作。
4,在出现异常方法的调用者中捕获并处理异常。

public class Test2 {
    static int quotient(int number1, int number2) throws MyException { // 定义方法抛出异常
        if (number2 < 0) {                                     // 判断参数是否小于0
            throw new MyException("除数不能是负数");            // 异常信息
        }
        return number1 / number2;                              // 返回值
    }   
 
    public static void main(String args[]) { // 主方法
        try {                                                    // try语句包含可能发生异常的语句
            int result = quotient(3, -1);                        // 调用方法quotient()
            System.out.println("计算结果为:"+result);
        } catch (MyException e) {                                // 处理自定义异常
            System.out.println(e.getMessage());        // 输出异常信息
        } catch (ArithmeticException e) {                  // 处理ArithmeticException异常
            System.out.println("除数不能为0");              // 输出提示信息
        } catch (Exception e) {                                    // 处理其他异常
            System.out.println("程序发生了其他的异常");  // 输出提示信息
        }
    }    

}

//自定义异常类
class MyException extends Exception{
    
    private static final long serialVersionUID = 1L;
    
    String message;
    public MyException(String ErrorMessage) {
        message= ErrorMessage;
    }
    
    public String getMessage() {
        return message;
    }
}

日期时间一般应用

public class DateTest1 {
    public static void main(String args[]) throws ParseException {
        /*
         * Date d1 = new Date(); System.out.println("当前年份: "+(d1.getYear()+1900));
         * 
         * Date d2 = new Date(2022,6-1,12); System.out.println("指定的年份为: "+d2.getYear());
         * System.out.println("指定日期的月份:"+d2.getMonth());
         * System.out.println("指定日期的日子:"+d2.getDate());
         */

        // Calendar类是一个抽象类,由于是抽象类,且它构造方法是protected的,所以无法使用它构造对象,API中提供了
        // getInstance方法来创建对象
        Calendar cd = Calendar.getInstance();
        cd.set(2022, 10, 14);

        // 日期类型与整数,字符串之间的相互转换
        Date d = new Date();
        long dLong = d.getTime(); // 获取时间戳 1665710235659毫秒(2022/10/14 9:17)
        System.out.println(dLong);
        Date d2 = new Date(dLong);
        System.out.println("年份为: " + (d2.getYear() + 1900));

        // Calendar类型与长整型之间的转换
        Calendar cd2 = Calendar.getInstance();
        long ct1 = cd2.getTimeInMillis();
        long t = ct1;
        cd2.setTimeInMillis(t);

        // Calendar与Date对象之间的转换
        Calendar cd3 = Calendar.getInstance();
        Date d3 = cd3.getTime();
        System.out.println(d3.getYear() + 1900);

        cd3.setTime(d3);

        // Date 与String互转
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        String dateStr = sdf.format(new Date());
        System.out.println("SimpleDateFormat sdf 格式化后的日期字符串为:  " + dateStr); // 显示时已处理好了1900问题

        String dateStr2 = "2022-10-14";
        SimpleDateFormat sdf2 = new SimpleDateFormat("yyyy-MM-dd");
        Date date = sdf2.parse(dateStr2);
        System.out.println(date.getYear() + 1900);
        // 判断是否为闰年
        System.out.println("2022年是否为闰年: " + isLeapYear(2022));
        // 判断是星期几
        System.out.println("2022-10-14是星期几: " + getWeek("2022-10-14"));
        // 得今天n天以后或者n天以前是那一天
        System.out.println("4天前是那一天: " + getDistanceDay(-4));
        //获得给定两个日期相差的天数
        System.out.println("2010-10-24到2022-10-14有多少天: "+getGapDays("2010-10-14", "2022-10-14"));
    }

    // 判断是否为闰年
    public static boolean isLeapYear(int year) {
        GregorianCalendar calendar = new GregorianCalendar();
        return calendar.isLeapYear(year);
    }

    // 判断是星期几
    public static int getWeek(String date) {
        // 注意参数的大小写格式
        SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
        Calendar c = Calendar.getInstance();
        try {
            Date d = dateFormat.parse(date);
            c.setTime(d);
        } catch (ParseException e) {
        }
        return c.get(Calendar.DAY_OF_WEEK) - 1;
    }

    // 得今天n天以后或者n天以前是那一天
    public static String getDistanceDay(int day) {
        Calendar calen = Calendar.getInstance();
        calen.add(Calendar.DAY_OF_MONTH, day);
        Date date = calen.getTime();
        // 这里也个用SimpleDateFormat的format()进行格式化,然后以字符串形式返回格式化后的date
        SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
        return dateFormat.format(date);
    }

    // 获得给定两个日期相差的天数
    public static long getGapDays(String date1, String date2) {
        String[] d1 = date1.split("-");
        String[] d2 = date2.split("-");
        Calendar c = Calendar.getInstance();
        c.set(Integer.parseInt(d1[0]), Integer.parseInt(d1[1]), Integer.parseInt(d1[2]), 0, 0, 0);
        long l1 = c.getTimeInMillis();
        c.set(Integer.parseInt(d2[0]), Integer.parseInt(d2[1]), Integer.parseInt(d2[2]), 0, 0, 0);
        long l2 = c.getTimeInMillis();
        return (Math.abs(l1 - l2) / (24 * 60 * 60 * 1000));
    }

}
-----------------------------------------------------------------------------------------------------------------------------------------------
1665712911753
年份为: 2022
2022
SimpleDateFormat sdf 格式化后的日期字符串为:  2022-10-14 10:01:51
2022
2022年是否为闰年: false
2022-10-14是星期几: 5
4天前是那一天: 2022-10-10
2010-10-24到2022-10-14有多少天: 4383

super关键字

  1. 重写父类方法时,但又要调用父类的那个方法。
            子类重写了父类的相关方法,但是在子类的方法中仍然包含着父类方法中的代码。如果在子类中想调用父类中被覆盖的方法,可以使用super关键字。子类重写父类方法的访问权限不能低于父类的方法访问权限。如父类的权限为public,而子类中重写时不能为protected或private.
    例:
    一,Person.java
public class Person {
    public String name;
    public int age;
    public void getInfo() {
        System.out.println("姓名为:"+name);
        System.out.println("年龄为:"+age);
    }
    

}

二,SpuerDemo.java

public class SuperDemo extends Person{
    String company;
    public void getInfo() {
        super.getInfo();                  //super.getInfo()相当于下面注释的两行代码
        //System.out.println("姓名为:"+name);
        //System.out.println("年龄为:"+age);
        System.out.println("公司为:"+company);
    }
    public static void main(String args[]) {
        SuperDemo worker = new SuperDemo();
        worker.name="熊少文";
        worker.age=19;
        worker.company="江西省南昌市";
        worker.getInfo();
    }
}
--------------------------------------------------------------------------------------------------------------------
姓名为:熊少文
年龄为:19
公司为:江西省南昌市
  1. 使用super关键字调用父类的构造方法。
            子类并不继承父类的构造方法。如果子类要使用父类的构造方法,需要使用super关键字
public class Animal{
    String skin;
    public Animal(String str) {
        skin = str;
    }
}


public class Bird extends Animal{
    public Bird() {
        super("yimao");
    }
}

单例模式

       即整个程序运行阶段只有一个对象在应用。不能由外部创建多个对象,通过‘类.静态方法’实例化一个对象,如很多工具类就是这样的。

  1. 饿汉模式。
public class ThreadTest1 {
    public static void main(String args[]) {
        for(int i = 0;i<20;i++) {
            new Thread(new Runnable() {
                
                @Override
                public void run() {
                    System.out.println(Singleton4.getInstance());
                    
                }
            }).start();
        }
    }

}
class Singleton1{
    private static Singleton1 singleton = new Singleton1();
    private Singleton1() {}                           //设置无参构造方法为私有的,即外部创建不了对象
    public static Singleton1 getInstance() {
        return singleton;
    }
}
---------------------------------------------------
cn.ybzy.thread.sychonized.singlemodule.Singleton1@32a95b24
cn.ybzy.thread.sychonized.singlemodule.Singleton1@32a95b24
cn.ybzy.thread.sychonized.singlemodule.Singleton1@32a95b24
cn.ybzy.thread.sychonized.singlemodule.Singleton1@32a95b24
----------

饿汉式是在类加载的时候创建实例,故不存在线程安全问题。
----虚拟机会保证一个类的()方法在多线程环境中被正确地加锁、同步,如果多个线程同时去初始化一个类,那么只会有一个线程去执行这个类的()方法,其他线程都需要阻塞等待,直到活动线程执行()方法完毕。
优点:
类加载时完成初始化,获取对象的速度较快.
缺点:
类加载较慢.

  1. 懒汉模式
class Singleton2 {
    private static Singleton2 singleton=null;
    private Singleton2() {}
    public static Singleton2 getInstance() {
        if(singleton == null) {
            singleton = new Singleton2();
        }
        return singleton;
    }
}
  1. 静态内部类单例模式
class Singleton3{
    private Singleton3() {}
    
    private static class SingleTonHoler{
        private static Singleton3 singleton = new Singleton3();
    }
    public static Singleton3 getInstance() {
        return SingleTonHoler.singleton;
    }
}

静态内部类的优点是:外部类加载时并不需要立即加载内部类,内部类不被加载则不去初始化INSTANCE,故而不占内存。
只有当getInstance()方法第一次被调用时,才会去初始化INSTANCE,第一次调用
getInstance()方法会导致虚拟机加载SingleTonHoler类,这种方法不仅能确保线程安全,也能保证单例的唯一性,同时也延迟了单例的实例化。

  1. 枚举类型单例模式
enum Singleton4 {
    INSTANCE;
    public static Singleton4 getInstance() {  //可以省略此方法,通过Singleton.INSTANCE进行操作
      return Singleton4.INSTANCE;
   }
}

枚举类隐藏了私有的构造器。
枚举类的域是相应类型的一个实例对象

字符集合---字符串

        字符串是由许多单个字符连接而组成的,可由任何字符组成(如数字等),但是它们必面包含在一对英文双引号之内。绝大多数字符串处理都是java.lang包中String类型对象。

  1. 产生字符串对象。
    一,String str = " xiong";
    二,String str = new String("xiong");
    三,String str = new String(byte[] bytes);
byte[] bytes=new byte[]{65,66,67};
String str =new Stryng(bytes);
System.out.println(str);

四,String str = new String(byte[] bytes,charset); charset为编码格式(例:UTF-8)
五,String str =new String(char[] c);

char[] chararray = new char[]{'j','a','v','a','编','程','课','程'};
String str = new String(chararray);

-------空字符串并不是null.例:"a"+null+"b"等于 "anullb"

  1. 常用的字符串方法。
    一,获取字符串的长度
    str.length();//单个汉字与字母都作为一个处理
public static void main(String args[]) {
        String name="熊少文xiongshaowen12/n";
        System.out.println("字符串name的长度为:"+name.length());
    }
//字符串name的长度为: 19

二,对字符串进行截取。
substring(int begin); substring(int begin,int end); //end指定的是截取的最后的索引位置,但不包含这个位置的字符

public class StringTest {
    public static void main(String args[]) {
        String name="熊少文xiongshaowen12/n";
        String namepart  = name.substring(0,4);
        System.out.println("0-4截取的字符串为:"+namepart);
    
    }

}
-----------------------------------------------------------------------------------------------------------------------
0-4截取的字符串为:熊少文x

三,分割字符串,返回字符串数组。
String[] strarray = str.split(String sign);/(String sign,int limit); //sign是隔符,limit分割次数
------分割字符串的分隔符也可以是正则表达式。如果想定义多个分隔符,可以使用符号‘|’,例如:“,|=”表示分割符分别为","和"="号。

public static void main(String args[]) {
        String string="公司名称: 明日科技!公司所在城市: 长春市。公司电话:122333333";
        String[] info = null;
        info = string.split("!|。");
        for(int i= 0;i

四,去除字符串尾部空格。
str.trim();
五,查找字符串(位置)
indexOf(int ch); lastIndexOf(int ch); lastIndexOf(String str);indexOf(int ch,int startIndex);//ch为字符,字符可用对应的数字表示

        String name="熊少文xiongshaowen12/n";
        System.out.println("文字在字符串的位置为: "+name.indexOf('文'));
--------------------------------------------------------------------------------------------------------------------
文字在字符串的位置为: 2

五,比软字符串是否相等。
        字符比软是否相等,不能像基本数据类型那样用"=="号,因为比较运算符"=="比软的是两个字符中的地址是否相等。即使两个字符串的内容相同,但两个对象的内存地址是不同的。
          如果要比较两个字符串是否相同就要使用String的equals(String str)方法和equalsIgnoreCase(),注equals()方法严格区分大小写。

       String str1 = new String("java");
        String str2 = new String("JAVA");
        String str3 = new String("java");
        boolean s1s2 = str1.equals(str2);
        boolean s1s3 = str1.equals(str3);
        boolean s1s2Igon = str1.equalsIgnoreCase(str2);
        System.out.println("str1,str2两个字符串内内容是否相同:"+s1s2);
        System.out.println("str1,str3两个字符串内内容是否相同:"+s1s3);
        System.out.println("str1,str2两个字符串内内容不区大小写是否相同:"+s1s2Igon);

可变字符串StringBuffer

          应用String类创建的字符串对象,它的长度是固定的,内容不能被改变和编译,而使用StringBuffer类创建的对象内容是可以改变的,还可以转化为String。

  1. 常用方法
    一,append()方法实现向字符串生成器中追加内容,内容可以是任何数据类型,包括对象。
    
        StringBuffer bf = new StringBuffer("Java");
        bf.append("编程宝典!");
        System.out.println("java串后追加内容后为: "+bf.toString());
        bf.delete(4, 8);
        System.out.println("字符删除后的内容: "+bf.toString());
--------------------------------------------------------------------------------------------------------------------
java串后追加内容后为: Java编程宝典!
字符删除后的内容: Java!

二,delete(start,end)删除指定的字符串。
三,insert(int start,Object arg)

递归recursion

什么是递归?
递归,在计算机科学中是指一种通过重复将问题分解为同类的子问题而解决问题的方法。简单来说,递归表现为函数调用函数本身。
-----递归最恰当的比喻,就是查词典。我们使用的词典,本身就是递归,为了解释一个词,需要使用更多的词。当你查一个词,发现这个词的解释中某个词仍然不懂,于是你开始查这第二个词,可惜,第二个词里仍然有不懂的词,于是查第三个词,这样查下去,直到有一个词的解释是你完全能看懂的,那么递归走到了尽头,然后你开始后退,逐个明白之前查过的每一个词,最终,你明白了最开始那个词的意思。
递归的特点
实际上,递归有两个显著的特征,终止条件和自身调用:
1终止条件:递归必须有一个终止的条件,即不能无限循环地调用本身。
2自身调用:原问题可以分解为子问题,子问题和原问题的求解方法是一致的,即都是调用自身的同一个函数。
递归应用场景
哪些问题我们可以考虑使用递归来解决呢?即递归的应用场景一般有哪些呢?
1阶乘问题
2二叉树深度
3汉诺塔问题
4斐波那契数列(不太用,用迭代法)
5快速排序、归并排序(分治算法体现递归)
6历文件,解析xml文件
例1:阶乘

public class Njiecheng {
    public static int fac(int num) {
        if (num == 1) {                           //如果n为1时跳出循环
            return 1;
        }
        return num * fac(num - 1);
    }

    public static void main(String[] args) {
        int n = 5;
        System.out.println("result = " + fac(n));
    }

}

例2:打印一个数的位数出来

public class DigitbitPrint {
    public static void printDigitBit(int n) {
        if (n > 9) {
            printDigitBit(n / 10);
        }
        System.out.print(n % 10);

    }

    public static void main(String[] args) {
        printDigitBit(65473232);
    }

}

例3:求一个数的各位数数字之和 5+2+5+6+1+5

public class SumDigitBit {
    public static int sum(int n){
           if(n < 10){
               return n;
           }
           return n %10 + sum( n/10 );

        }
        public static void main(String[] args) {
            int n = 525615;
            int ret = sum( n);
            System.out.println("the sum of "+n +" = "+ ret);
        }

}

例4:求出斐波那契数列,本例是求出10列的最后一列

//10个数列最后一位是什么,输出来
public class FeiBoNaLieSum {
    private static int i=0;
    public static int fib(int n){
        //System.out.println(i++);
           if(n == 1 || n == 2){
               return 1;
           }
           
           return fib(n - 1) + fib(n - 2 );
        }
        public static void main(String[] args) {
            System.out.println(fib(10));
        }
        
        
        
        //实际开发中,当数字很大时(大于100就会很拆磨人了),会有大量重复计算,不建议上面递归这样做,用迭代法做
          /*public static int fib(int n){
            int n1 = 1;
            int n2 = 1;
            int num = 1;
            for( int i=3; i<=n ;i++){
                num = n1 + n2;
                n1 = n2;
                n2 = num;
            }
            return num;
         }
         public static void main(String[] args) {
             System.out.println(fib(10));
         }*/
     

}

例6:打印各文件的绝对路径,一个目录中有n个子目录,子目录用到递归去打印

public class FileDirPrint {

        public static void main(String[] args) {
            // 创建File对象
            File dir  = new File("D:\\English");
            // 调用打印目录方法
            printDir(dir);
        }

        public static void  printDir(File dir) {
            // 获取子文件和目录
            File[] files = dir.listFiles();
     
            for (File file : files) {
                // 判断
                if (file.isFile()) {
                    // 是文件,输出文件绝对路径
                    System.out.println("文件名:"+ file.getAbsolutePath());
                } else {
                    // 是目录,输出目录绝对路径
                    System.out.println("目录:"+file.getAbsolutePath());
                    
                     // 继续遍历,调用printDir,形成递归
                    printDir(file);
                }
            }
        }
    }
    

例7:猴子吃桃问题

/*猴子第一天摘下若干桃子,当即吃了一半,觉得好不过瘾,于是又多吃了一个,
第二天又吃了前天剩余桃子数量的一半,觉得好不过瘾,于是又多吃了一个,以后每天都是吃前天剩余桃子数量的一半,觉得好不过瘾,又多吃了一个,
等到第10天的时候发现桃子只有1个了。
需求:请问猴子第一天摘了多少个桃子?*/
public class MonkeyEatTaozi {
    /**
     * f(x)-f(x)/2-1=f(x+1) 
     * 2f(x)-f(x)-2=2f(x+1) 
     * f(x)=2f(x+1)+2
     * 
     * @author JiaShuai
     * @date 2022/4/16 22:36
     */
    public static void main(String[] args) {
        System.out.println(f(1));                        //输入最小天数
    }

    public static int f(int n) {
        if (n == 10) {
            return 1;
        } else {
            return 2 * f(n + 1) + 2;
        }
    }

}

例8:买酒问题--假设一瓶酒2元,4个瓶盖也换一瓶酒,2个空瓶可换一瓶酒,问10元可喝多少瓶酒,乘多少个盖子,多少个空瓶

public class MaiJiu {

    public static int totalNumber;       // 总数量
    public static int lastBottleNumber;  // 记录每次剩余的瓶子个数
    public static int lastCoverNumber;   // 记录每次剩余的盖子个数

    public static void main(String[] args) {
        buy(10);
        System.out.println("总数:" + totalNumber);
        System.out.println("剩余盖子数:" + lastCoverNumber);
        System.out.println("剩余瓶子数:" + lastBottleNumber);
    }

    // 买酒
    public static void buy(int money) {
        // 可以直接买多少瓶
        int buyNumber = money / 2;
        totalNumber += buyNumber;
        // 把盖子 和瓶子换算成钱
        // 统计本轮总的盖子数 和 瓶子数
        int coverNumber = lastCoverNumber + buyNumber;
        int bottleNumber = lastBottleNumber + buyNumber;

        // 统计可以换算的钱
        int allMoney = 0;
        if (coverNumber >= 4) {
            allMoney += (coverNumber / 4) * 2;
        }
        lastCoverNumber = coverNumber % 4;

        if (bottleNumber >= 2) {
            allMoney += (bottleNumber / 2) * 2;
        }
        lastBottleNumber = bottleNumber % 2;

        if (allMoney >= 2) {
            buy(allMoney);
        }
        
    }

}
-------------------------------------------
总数:15
剩余盖子数:3
剩余瓶子数:1

例9:汉诺塔问题
-------记得在大一的时候 C语言讲到递归的时候,这个汉诺塔就折磨了我很久,可是依然没怎么搞明白。现在学到Java了,下决心给他搞定一下。弄明白了之后其实也并不是那么难。其中心思想,就是递归的中心思想。只需要把复杂问题的实现抽象成几部分问题的实现。至于每一步的问题如何实现,我们程序员没必要过于关心,只需要利用递归,继续调用递归的中心思想,将其拆分为相同的几部分问题的实现。 但是这样一直递归递归递归 总有结束的时候,这就是递归出口。
hanio(int n,char x, char y, char z )函数中参数的意义:hanio这个函数的作用就是将n个盘子 从 x塔 经过y辅助塔, 最终到达目标z塔。
为了贴合我的代码 , 我们将tower1 定义为x塔 tower2定义为y塔 tower3定义为z塔
如何将x塔中的n个盘子大小顺序不变放到z塔呢?----------> hanio(n,x,y,z);

image.png

第一步 将x塔中n-1个盘子 通过辅助塔 z 先放到y 塔 -------> hanio(n-1,x,z,y)
image.png

第二步 将x塔中第n个盘子移动到 z 塔 --------->move(n,x,z)
image.png

第三步 再将y塔上的 n-1个盘子 通过辅助塔x 全部移动到 z塔上面,就完成了。 --------> hanio(n-1,y,x,z)
image.png

仔细的思考一下,我们好像还没有设置递归的出口。于是在每次大幅度移动(调用hanio 函数)时 先判断 塔中盘子是否为1个,如果只有一个则直接移动到目的地( move(x,n,z) 无需借助辅助塔 直接将第n个盘子从初始塔 移动到目标塔。)

public class HanoiDemo {
     
    private static int count = 0;
    
    public static void main(String[] args) {
 
        hanio(4,'x','y','z');
        System.out.println(" 总共移动了 " + count + " 次" );
    }
/**
 * 移动
 * @param n 共需要移动的盘子
 * @param x  从起始位置
 * @param y  借助辅助塔 
 * @param z  移动到终止位置
 */
    public static void hanio(int n, char x, char y, char z) {
        count++;
        if(n==1) {
            move(x,n,z);
        }else {
            hanio(n-1,x,z,y);
            move(x,n,z);
            hanio(n-1,y,x,z);
        }
        
    }
/**
 * 打印移动
 * @param x  从起使位置
 * @param n  移动第几个盘子
 * @param y  要移动到的最终位置
 */
    private static void move(char x, int n, char y) {
        System.out.println(" Move " + n + " from " + x + " to " + y);
    }
}


例10:求三解形数组向下或向右下最大值问题

//递归计算数字三角形的路径在上面的数字三角形中寻找一条从顶部到底边的路径,使得路径上所经过的数字之和最大。
//路径上的每一步都只能往下或右下走。只需要求出这个最大和即可,不必给出具体路径。
//三角形的行数大于1小于等于100,数字为 0 - 99
public class RecursionTest2 {
    private static int[][] data,data1;
    private static int n;
    public static void main(String args[]) {
        Scanner sc = new Scanner(System.in);
        System.out.println("请输入数组的行数与列数:");
        n=sc.nextInt();
        data1=new int[][]{{1},{2,3},{19,9,10}};
        
        data = new int[n][n];
        for(int i=1;i<=n;i++) {
            for(int j=1;j<=i;j++) {
                System.out.println("请输入第"+i+"行,第"+j+"列的数字");
                data[i-1][j-1]=sc.nextInt();
            }
        }
        System.out.println("-------------------------数字三角形如下------------------------");
        for(int i=1;i<=n;i++) {
            System.out.println();
            for(int j=1;j<=i;j++) {
                System.out.print(data[i-1][j-1]+"   ");
            }
        }
        System.out.println();
        System.out.println("-----------------------------------最终结果显示-------------------------");
        System.out.println(maxSum(1, 1));
    }
    public static int maxSum(int i,int j) {           //i,j是指定从那儿开始往下走,一般是1 ,1
        if(i==n) {                                    //边界条件,如果到底了,就是底下第{i,j}元素
            return data[i-1][j-1];      
        }else {
            int x = maxSum(i+1, j);                   //往下走
            int y = maxSum(i+1, j+1);                 //往右下走
            return Math.max(x,y)+data[i-1][j-1];
        }
    }

}
-----------------------------------------------------------------------------------------------------------------------------------------------
请输入数组的行数与列数:
3
请输入第1行,第1列的数字
11
请输入第2行,第1列的数字

1
请输入第2行,第2列的数字
11
请输入第3行,第1列的数字
12
请输入第3行,第2列的数字
58
请输入第3行,第3列的数字
47
-------------------------数字三角形如下------------------------

11   
1   11   
12   58   47   
-----------------------------------最终结果显示-------------------------
80

递归分治法(如汉诺塔问题,递归二分查找法)

           当我们求解某些问题时,由于这些问题要处理的数据相当多,或求解过程相当复杂,使得直接求解花费时间会相当长,或者根本无法直接求出。对于这类问题,我们往往先把它分解成几个子问题,找到求出这几个子问题的解法后,再找到合适的方法,把它们组合成求整个问题的解法。如果这些子问题还较大,难以解决,可以再把它们分成几个更小的子问题,以此类推,直至可以直接求出解为止。这就是分治策略的基本思想。
  上面讲的递归的二分查找法就是一个分治算法的典型例子,分治算法常常是一个方法,在这个方法中含有两个对自身的递归调用,分别对应于问题的两个部分。
  二分查找中,将查找范围分成比查找值大的一部分和比查找值小的一部分,每次递归调用只会有一个部分执行。

public class RecursionTest3 {
    public static void main(String args[]) {
        int array[] = new int[]{1,2,3,4,5,6,7,8,9,10};
        System.out.println("假设要查找的元素为5:返回下标  ");
        //System.out.println(binarySearchwhile(array,7));
        int length = array.length;
        System.out.println("递归二分查找法   "+binarySearchrecursion(array,7, 0, length-1));
        
    }
    
    //递归二分查找法
    public static int binarySearchrecursion(int[] array,int value,int l,int h) {
        int mid = (h-l)/2+l;
        if(value == array[mid]) {
            return mid;
        }else if(l > h) {
            return -1;
        }else {
            if( value > array[mid]) {
                return binarySearchrecursion(array, value, mid+1, h);
            }
            if(value < array[mid]) {
                return binarySearchrecursion(array, value, l,mid-1);
            }
            //return -1;
        }
        return -1;
    }
}


//while普通二分查找法
    public static int binarySearchwhile(int[] array,int value) {
        int low=0;
        int h = array.length-1;
        int mid;
        while(low<=h) {                 //下边界大小上边界说明越界了,没有有元素
            mid = (h-low)/2+low;          //不能重复元素下标
            if(value == array[mid]) {
                return mid;
            }else {
               if(value > array[mid]) {
                   low=mid+1;
               }
               if(value < array[mid]) {
                   h=mid-1;
               }
               //return -1;        
            }
              
        }
        return -1;                    //数组中根本就不存在你要你的数据项
    }



下面是把计算1-300作为一个大任务,再通过递归二分成左右n个小任,再利用少量线程去执行,再合并。

           //把大任,分成左右,两半多个任务执行求和,有合并功能,再返回值(join()来做)
public class ThreadTest2 {
    public static void main(String args[]) throws InterruptedException, ExecutionException {
        // 大任务是求300元素的数组里的,成员之和
        // 构造一个数组
        int[] arr = new int[300];
        int sum=0;                                     //验证结果对不对用到的变量
        Random random = new Random();
        for (int i = 0; i < arr.length; i++) {
            int tmp =random.nextInt(20);                  //返回0-19的随机正整数
             // System.out.println(tmp);
            arr[i] = tmp;
            sum+=arr[i];
        }
        System.out.println("主线程单独做的求和: "+sum);
        //用工具类SumTask来实现求和
        SumTask st = new SumTask(arr, 0, arr.length-1);
        ForkJoinPool pool = ForkJoinPool.commonPool();             //获取线程池对象
        Future future = pool.submit(st);                           //返回一个ForkJoinPool,
        System.out.println("多子任任多Cpu最终的数组元素所有之和为:  "+future.get());
        pool.shutdown();
    }
}

//该类有返回值的任务拆分合并功能
class SumTask extends RecursiveTask{
    
    private static final long serialVersionUID = 1L;
    private static final int THRESHOLD = 20 ;    //设一个极限值
    private int[] arr;
    private int start;
    private int end;
    
     public SumTask(int[] arr,int start,int end) {
        this.arr= arr;
        this.start = start;
        this.end = end;
    }
    @Override
    protected Integer compute() {
        int sum = 0;
        if(end - start <= THRESHOLD) {                   //一序列数个数小于极限值时,直接(主方法)做
            for(int i = start;i<=end;i++) {
                sum+= arr[i];
            }
            return sum;
        }else {
            int middle = (start+end)/2;
            SumTask left = new SumTask(arr, start, middle);
            SumTask right = new SumTask(arr, middle+1, end);
            left.fork();
            right.fork();
            //要把结果进行合并,
            return left.join()+right.join();
        }
        
    }
    
    
}

-----例:1---100左半为: 1(start)到[(1+100)/5=50(middle)]之间,右半为:51(middle+1)到100(end)之间
-----例:101---200左半为:101(start)到[(101+200)/2=150(middle)],右半为:151(middle+1)到200(end)之间

递归归并排序数组

---归并算法的中心是归并两个已经有序的数组。归并两个有序数组A和B,就生成了第三个有序 数组C。数组C包含数组A和B的所有数据项。

image.png

归并两个数组算法为:这个前提是两个数组为有序数组

public class RecursionTest4 {
    public static void main(String args[]) {
        int[] c = merge(new int[] { 1, 3, 5, 7 }, new int[] { 2, 4, 6, 8, 10 });
        System.out.println(Arrays.toString(c));
    }

    public static int[] merge(int[] a, int[] b) {
        int[] c = new int[a.length + b.length];
        int aIndex = 0;
        int bIndex = 0;
        int cIndex = 0;
        // 实现归并
        while (aIndex < a.length && bIndex < b.length) {
            if (a[aIndex] < b[bIndex]) {
                c[cIndex++] = a[aIndex++];
            } else {
                c[cIndex++] = b[bIndex++];
            }
        }

        // 如果b归并完,a有剩余
        while (bIndex == b.length && aIndex < a.length) {
            c[cIndex++] = a[aIndex++];
        }

        // 如果a归并完,b有剩余
        while (aIndex == a.length && bIndex < b.length) {
            c[cIndex++] = b[bIndex++];
        }

        return c;
    }
}
-----------------------------------------------------------------------------------------------------------------------------------------------
[1, 2, 3, 4, 5, 6, 7, 8, 10]

利用归并和递归的思想实现排序的
--------归并排序的思想是把一个数组分成两半,排序每一半,然后用上面的sort()方法将数组的两半归并成为一个有序的数组。如何来为每一部分排序呢?用递归:
  把每一半都分为四分之一,对每个四分之一进行排序,然后把它们归并成一个有序的一半。类似的,如何给每个四分之一数组排序呢?把每个四分之一分成八分之一,对每个八分之一进行排序,以此类推,反复的分割数组,直到得到的子数组是一个数据项,那这就是这个递归算法的边界值,也就是假定一个数据项的元素是有序的。

image.png

import java.util.Arrays;
//归并排序的思想是把一个数组分成两半,排序每一半,然后用上面的sort()方法将数组的两半归并成为一个有序的数组。如何来为每一部分排序呢?用递归:
public class RecuresionTest5 {
    public static void main(String args[]) {
        int[] c= new int[] {2,4,1,7,9};
        System.out.println(""+Arrays.toString(mergeSort(c,0,4)));
    }
    
    public static int[] mergeSort(int [] c,int l,int h) {
        if(l>=h) {
            return null;
        }else{
            int mid=(l+h)/2;
            mergeSort(c, l, mid);
            mergeSort(c, mid+1, h);
            //把上面分出来的子数组来归并,归并方法参考 上面的例子
            merge(c,l,mid,h);
        }
        return c;
    }
    
    //实现子数组合并
    public static void merge(int[] c,int l,int mid,int h) {
        int i=l;
        int j = mid+1;
        int[] temp = new int[h-l+1];      //临时数组,
        int k = 0;                        //临时数组的下标
        
         //实现合并
         //1.比较没有结束时的插入到临时数组中
        while(i<= mid && j<=h) {
            if(c[i] < c[j]) {
                temp[k++] = c[i++];
            }else {
                temp[k++] = c[j++];
            }
        }
        //2.左边有剩余的部分转移到临时数组中
        while(i<= mid && j>h) {
            temp[k++] = c[i++];
        }
        
        //3.右边有剩余的部分转移到临时数组中
        while(i> mid && j<=h) {
            temp[k++] = c[j++];
        }
        
        //4.把临时数组里的数据,覆盖到C数组里
        for(int g=0;g

消除递归

----递归对于分析问题比较有优势,但是基于递归的实现效率就不高了,而且因为函数栈大小的限制,递归的层次也有限制。所以消除递归就显得很重要了,这样我们可以在分析阶段采用递归思想,而实现阶段把递归转变成非递归算法。
----递归绝全都可用循环来消除,但也有极其复杂的情况如汉诺塔,用循环代码十分复杂,这时用递归代码相当简洁,这里我们用栈来消除也是一个不错的选择。
用例子玩玩:
题目: 求1+2+3+4+....+n的值:
递归来分析这个问题: 1+2+3+4+....+n=n+(n-1+....+1)
递归和栈有着紧密的联系,而且大多数编译器都是用栈来实现递归的,这里我们就模拟一下底层编译器的处理方法来转换递归算法。
现在把上面的递归算法,利用栈变成非递归算法:
1.先建一个栈类Params 两个成员变量,一个是即将要递归求和的数,一个是case 地址。
2.利用栈,压入Params对象进栈,求和的数有多大入栈就有几个元素。
3.再出栈,依次出n个栈每一次出栈都加于n-1,再保存到临时变量中最后

//消除递归,利用栈
public class RecursionTest6 {
    public static void main(String args[]) {
        int n = 10;
        System.out.println(sum(10));
        System.out.println(addn(10));
    }

    // 递归方法,计算1+2+3+4+5.。。。。
    public static int sum(int n) {
        if (n == 1) {
            return n;
        } else {
            return n + sum(n - 1);
        }
    }

    // 消除递归方法,先建一个栈类,再建栈类型为这个类类型
    public static int addn(int n) {
        Stack stack = new Stack<>();
        int currentReturnValue = 0;
        Params params = new Params(n,5);
         stack.push(params);
        int currentReturnAddress = 1;
        
        boolean flag = false;
        while (flag==false) {
            switch (currentReturnAddress) {
            
                 //模拟递归算法的边界条件,满 足边界条件,把值赋给currentReturnValue,设定一跳为5,否则下一跳为3
            case 1:
                params= stack.peek();
                if(params.getN()==1) {
                    currentReturnValue = params.getN();
                    currentReturnAddress = 4;
                }else {
                     currentReturnAddress = 2;
                }
                break;
                //模拟递归,n-1作为新的参数压入栈,参数对象里的返回地址4,设置下一跳为2
            case 2:
                params = stack.peek();
                params = new Params(params.getN()-1,3);
                stack.push(params);
                currentReturnAddress =1;
                break;
                //将栈中的参数取出,作叠加操作,加到currentReturnValue保存
            case 3:
                params = stack.peek();
                currentReturnValue += params.getN();
                currentReturnAddress = 4;
                break;
                //把前面3分支已经叠加的参数,从栈里删除,如果栈里的参数都加完了,跟参数对象中的返回地址,设定下一跳地址
                
            case 4:
                params=stack.pop();    //删除并返回
                currentReturnAddress = params.getReturnAddress();
                break;

            case 5:
                
                flag = true;    //完成运算结束while循环

            }
            
        }
    return currentReturnValue;
    }
}

class Params {
    private int n;
    private int returnAddress;

    public Params(int n, int address) {
        this.n = n;
        returnAddress = address;
    }

    public int getN() {
        return n;
    }

    public void setN(int n) {
        this.n = n;
    }

    public int getReturnAddress() {
        return returnAddress;
    }

    public void setReturnAddress(int returnAddress) {
        this.returnAddress = returnAddress;
    }
    
}

递归的一些小应用

  1. 求一个数的乘方
    数学公式如下是成立的:
    image.png

    我们可以将乘方的运算转换为乘法的运算
    image.png

    ------求x的y次方的值,当y是偶数的时候,最后能转换成两个数相乘,当时当y是奇数时候,最后我们必须要在返回值后面额外的乘以一个x。
    x^y= (x^2)^(y/2),定义a=x^2,b=y/2, 则得到形如: x^y= a^b;
    image.png
//求x^y方,
public class RecursionTest7 {
    public static void main(String args[]) {
        long x,y;
        Scanner sc = new Scanner(System.in);
        System.out.println("请输入x值: ");
        x= sc.nextLong();
        System.out.println("请输入Y值");
        y=sc.nextLong();
        System.out.println("x^y:  "+pow(x, y));
    }
    
    public static long pow(long x,long y) {
        if(y==0)
            return 1;
        if(y==1)
            return x;
        if(y%2==1)                     //如果y为单数
            return pow(x*x, y/2)*x;
        else                           //否则y为偶数  
            return pow(x*x, y/2);
    }
}

  1. 背包问题
    背包问题也是计算机中的经典问题。在最简单的形式中,包括试图将不同重量的数据项
    放到背包中,以使得背包最后达到指定的总重量。
      比如:假设想要让背包精确地承重20磅,并且有 5 个可以放入的数据项,它们的重量
    分别是 11 磅,8 磅,7 磅,6 磅,5 磅。这个问题可能对于人类来说很简单,我们大概就可以计算出 8 磅+ 7 磅 + 5 磅 = 20 磅。但是如果让计算机来解决这个问题,就需要给计算机设定详细的指令了。
    算法的关键点:
    1、递归的边界:
    选择的数据项的总和符合目标重量——找到
    试验了所有的组合没有符合目标重量——没找到
    2、怎么递归:
    从选择的第一个数据项开始,剩余的数据项的和必须符合背包的目标重量减去第一个数据项的重量——递归
    如果没有合适的组合,放弃第一个数据项,并且从第二个数据项开始再重复一遍整个过程——递归
      具体实现过程:
public class RecursionTest8 {
    public static void main(String args[]) {
        int[] arr = new int[] {11,8,7,6,5,6,20,9,4,3,2};
        Knapsack k = new Knapsack(arr);
        k.doKnapsack(25,0);
    }
}

class Knapsack {
    private int[] data;
    private boolean[] selects;

    // 构造方法初始化上面的属性
    public Knapsack(int[] data) {
        this.data = data;
        selects = new boolean[data.length]; // 这个数组和上面面的data数组长度一样,true表示选择了它
    }

    // 具体的解决背包问题的方法
    public void doKnapsack(int aim, int index) {
        // 边界条件
        if (aim != 0 && index >=data.length) {
            return; // 已经把所有的组合方案都测试过了,没找到返回不执行下面的代码了
        }

        if (aim == 0) { // 找到了
            for (int i = 0; i < selects.length; i++) {
                if (selects[i]) {
                    System.out.print(data[i] + "  ");
                }
            }
            System.out.println();
            return;
        }
        selects[index] = true;                            //最初从第一个元素开始找组合
        doKnapsack(aim-data[index], index+1);
        selects[index]=false;
        doKnapsack(aim, index+1);                        //前面的数据项放进背包对应的所有组合都不对,从下一个数据项从来组合
        
        
    }
}

  1. 走台阶
    ------每次你可以跨 1 个台阶或者 2 个台阶,请问走这 n 个台阶有多少种走法?类似于费波那列数列,它是1 , 2 ,3, 5而不是1,1,2,3,5。
//走台阶算法,一个n阶台阶,走一步和走两步有多少种走法。
public class RecursionTest9 {
    public static int i=1;
    public static void main(String args[]) {
       System.out.println(f(5));
    }

    public static int f(int n) {
        
        if (n == 1)
            return 1;
        if (n == 2)
            return 2;
        System.out.println(i++);
        return f(n - 1) + f(n - 2);
    }

}

  1. 组合:选择一支队伍
    在数学中,组合是对事物的一种选择,而不考虑他们的顺序。
    比如有5个登山队员,名称为 A,B,C,D和E。想要从这五个队员中选择三个队员去登峰,这时候如何列出所有的队员组合。(不考虑顺序)
      还是以递归的思想来解决:首先这五个人的组合选择三个人分成两个部分,第一部分包含A队员,第二部分不包含A队员。假设把从 5 个人中选出 3 个人的组合简写为(5,3),规定 n 是这群人的大小,并且 k 是组队的大小。那么根据组合公式可以有:
      (n,k) = (n-1,k-1) + (n-1,k)
      对于从 5 个人中选择 3 个人,有:
      (5,3) = (4,2)+(4,3)
      (4,2)表示已经有A队员了,然后从剩下的4个队员中选择2个队员,(4,3)表示从5个人
    中剔除A队员,从剩下的4个队员中选择3个队员,这两种情况相加就是从5个队员中选择3个队员。
      现在已经把一个大问题转换为两个小问题了。从4个人的人群中做两次选择(一次选择2个,一次选择3个),而不是从5个人的人群中选择3个。
      从4个人的人群中选择2个人,又可以表示为:(4,2) = (3,1) + (3,2),以此类推,很容易想到递归的思想。
      具体实现代码:
//组合:从n个人中选k个人,问有多少种组合,这里选3个人一组
public class RecursionTest10 {
   public static void main(String[] args) {
       char[] arr= new char[] {'A','B','C','D','E'};
       Comb comb = new Comb(arr);
       comb.doComb(3, 0);
   }

}


class Comb{
    private char[] persons;         //n个人 'A','B','C'...
    private boolean[] selects;      //选中标记,两数组长度相等
    
    public Comb(char[] persions) {
        this.persons= persions;
        selects = new boolean[persions.length];
        
    }
    
    //具体实现组合的递归方法
    public void doComb(int selectNum,int index) {        //selectNum选 中的人数
       //边界
        if(selectNum ==0) {            //找到了组合,输出
            for(int i=0;i=persons.length) {         //所有的组合都找完了,没有找到组合,直接退出
            return;
        }
        
        selects[index]= true;              //初始时,第一个人被选中,假设为'A',下面开始递归找人
        doComb(selectNum-1, index+1);
        selects[index]=false;
        doComb(selectNum, index+1);
    }
    
}


//下面的代码是倒过来打印输出,更符合(n,k)=(n-1,k-1)+(n-1,k)形式


/**
//组合:从n个人中选k个人,问有多少种组合,这里选3个人一组
public class RecursionTest10 {
   public static void main(String[] args) {
       char[] arr= new char[] {'A','B','C','D','E'};
       new Comb(arr,5,3);
       
   }

}


class Comb{
    private char[] persons;         //n个人 'A','B','C'...
    private boolean[] selects;      //选中标记,两数组长度相等
    public Comb(char[] persions,int n,int selectNum) {
        this.persons= persions;
        selects = new boolean[persions.length];
        n=persions.length-1;
        doComb(n, selectNum);
    }
    
    //具体实现组合的递归方法
    public void doComb(int index,int selectNum) {        //selectNum选 中的人数
        
       //边界
        if(selectNum ==0) {            //找到了组合,输出
            for(int i=0;i
  1. 二叉树递归遍历
    二叉树

你可能感兴趣的:(java基础)