java基础知识——泛型

  1. 泛型实现方式有两种,一种是类型膨胀,属于真实泛型(C#实现);另一种是类型擦除,称为伪泛型,java使用的是类型擦除,仅仅约束程序猿编码,编译后的class文件就替换回原生类型。
  2. 反射操作可以绕过泛型限制。

    package com.kevinlsui.base;
    
    import java.lang.reflect.Method;
    import java.util.ArrayList;
    import java.util.List;
    
    public class Test_fanxing {
    
    public static void main(String[] args) throws Exception {
        List<String> list = new ArrayList<String>();
        list.add("hahah");
        //list.add(1);//出错
    
        Class clazz = list.getClass();
        //List obj = (List) clazz.newInstance();
        Method m = clazz.getMethod("add",Object.class);
        m.invoke(list, 12);
    
        System.out.println(list);
    }
    

    }

  1. 泛型的弊端:在方法重载时,如果参数的区别仅仅是泛型,则无法重载,详细可见《Java虚拟机》相关章节。

    package com.kevinlsui.base;
    
    public class Test_fanxing2 {
    
        //方法重载
        public void ha(){
            System.out.println("method1...");
        }
        public void ha(String name){
            System.out.println("method2...");
        }
    
        //报错,原因是方法3,和方法4,参数区别仅仅是泛型,而泛型是类型擦除,编译完后就消失,这两个方法就成相同的了。
        /*public void ha(List<String> list){
            System.out.println("method3...");
        }*/
    
        /*public void ha(List<Integer> list){
            System.out.println("method4...");
        }*/
    
        //重载方法,加上返回值即可通过,但是方法重载仅仅与参数有关,与返回值无关的,此处为泛型的一个bug
        //《Java虚拟机》上的解决方案,在此并未成功,可能使用的jdk版本已经修复此bug
        /*public static Integer ha(List<String> list){
            System.out.println("method3...");
            return 1;
        }
        public static String ha(List<Integer> list){
            System.out.println("method4...");
            return "ok";
        }*/
    

    }

  1. 泛型的应用,在DbHelp类中,通过使用泛型,使得dbhelp得到通用。

Java基础知识——注解

学习代码

package com.kevinlsui.base;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * 元注解:负责注解其它注解的注解
 * @Target : Annotation所修饰的范围
 *              ElementType.{CONSTRUCTOR,构造器(constructor)} 
 *              ElementType.{FIELD,字段(field)} 
 *              ElementType.{LOCAL_VARIABLE,局部变量(local_variable)} 
 *              ElementType.{METHOD,方法(method)} 
 *              ElementType.{PACKAGE,包(package)} 
 *              ElementType.{PARAMETER,参数(parameter)} 
 *              ElementType.{TYPE,包括类、接口、注解、枚举(type)} 
 * @Retention : 用于标明生存时间
 *                 RetentionPolicy.RUNTIME (运行时保留)
 *                 RetentionPolicy.SOURCE (仅仅源码保留)
 *                 RetentionPolicy.CLASS (编译后class文件中保留)
 * @Documented : 生成javadoc时会包含注解
 * @Inherited : 允许子类继承 
 */

@Target({ElementType.TYPE,ElementType.FIELD})//使用大括号,定义多个范围
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface MyAnnotation {
    //返回值类型:基本数据类型(int/long/float/double/boolean/char/short/byte)、String、class、enum、annotation、以上类型的数组
    //修饰符:public/(默认)
    String name();
    String alias() default ""; 
}

package com.kevinlsui.base;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * 元注解:负责注解其它注解的注解
 * @Target : Annotation所修饰的范围
 *              ElementType.{CONSTRUCTOR,构造器(constructor)} 
 *              ElementType.{FIELD,字段(field)} 
 *              ElementType.{LOCAL_VARIABLE,局部变量(local_variable)} 
 *              ElementType.{METHOD,方法(method)} 
 *              ElementType.{PACKAGE,包(package)} 
 *              ElementType.{PARAMETER,参数(parameter)} 
 *              ElementType.{TYPE,包括类、接口、注解、枚举(type)} 
 * @Retention : 用于标明生存时间
 *                 RetentionPolicy.RUNTIME (运行时保留)
 *                 RetentionPolicy.SOURCE (仅仅源码保留)
 *                 RetentionPolicy.CLASS (编译后class文件中保留)
 * @Documented : 生成javadoc时会包含注解
 * @Inherited : 允许子类继承 
 */

@Target({ElementType.TYPE,ElementType.FIELD,ElementType.METHOD})
@Retention(RetentionPolicy.CLASS)
@Documented
public @interface MyAnnotation2 {
    //返回值类型:基本数据类型(int/long/float/double/boolean/char/short/byte)、String、class、enum、annotation、以上类型的数组
    //修饰符:public/(默认)

    String value() default "222"; 
}

package com.kevinlsui.base;

@MyAnnotation(name="people")
@MyAnnotation2("mya2")
public class People {
    public void say(){

    }
    public void haha(){

    }
}

package com.kevinlsui.base;

@MyAnnotation(name="kevinlsui",alias="ke")
public class User extends People{

    @MyAnnotation(name="haha")
    private String friend;

    @Override
    @MyAnnotation2("2222")
    public void say() {
        System.out.println("haha");
    }
}

package com.kevinlsui.base;

public class User2 extends People{

}

package com.kevinlsui.base;

import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Method;

public class Test {

    public static void main(String[] args) {
        //1.注解的继承性测试,@Inherited
        User2 u = new User2();
        MyAnnotation mya = u.getClass().getAnnotation(MyAnnotation.class);
        if(mya != null){
            System.out.println("name:"+mya.name());
            System.out.println("alias:"+mya.alias());
        }
        //此时将myAnnotation2切换为runtime
        MyAnnotation2 mya2 = u.getClass().getAnnotation(MyAnnotation2.class);
        System.out.println(mya2 == null);

        if(mya2 != null){
            System.out.println("value:"+mya2.value());
        }
        System.out.println("=======================");

        //2.注解运行时获取值(一般使用的都是运行时注解,源码和class级的无法用代码控制)
        //反射相关知识

        Class clasz = new User().getClass();
        //Class clasz = User.class;
        //MyAnnotation[] myas = (MyAnnotation[]) clasz.getAnnotations();
        if(clasz.isAnnotationPresent(MyAnnotation.class)){
            MyAnnotation an = (MyAnnotation) clasz.getAnnotation(MyAnnotation.class);
            System.out.println("name:"+an.name());
            System.out.println("alias:"+an.alias());
        }

        System.out.println("====方法====");
        Method[] me = clasz.getDeclaredMethods();//获取本类声明的方法,不分访问权限
        //Method[] me = clasz.getMethods();//可能包括父类继承来的
        for(Method m : me){
            System.out.println(m.getName());
            Annotation[] a = m.getAnnotations();
            //@Override的作用范围不到运行期
            //@MyAnotation2的作用域也不是运行期的
            //所以获取不到注解,length=0
            System.out.println(a.length);

        }

        System.out.println("====字段====");
        Field[] f = clasz.getDeclaredFields();
        //Field[] f = clasz.getFields();
        for(Field fi : f){
            System.out.println(fi.getName());
            MyAnnotation a = fi.getAnnotation(MyAnnotation.class);
            System.out.println(a.name());
            System.out.println(a.alias());
        }


    }

}

ArrayList删除元素的正确姿势

package com.kevinlsui.base;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

public class Test_listRemove {

    public static void main(String[] args) {
        List<String> list = new ArrayList<String>();
        list.add("1");
        list.add("2");
        list.add("3");
        list.add("4");
        System.out.println(list);

        /*for(String a : list){
            // 报错。java.util.ConcurrentModificationException
            list.remove(a);
        }*/

        /*for(Iterator<String> ti = list.iterator();ti.hasNext();){
            System.out.println(ti.next());
            //报错。java.util.ConcurrentModificationException
            list.remove(ti.next());
        }*/

        //1.迭代器删除元素,ok
        //2.只有内部实现迭代器的类(比如list),才可以使用foreach循环(本质是迭代器实现)
        for(Iterator<String> ti = list.iterator();ti.hasNext();){
            //System.out.println(ti.next());
            //使用迭代器删除,ok
            ti.remove();
        }
        System.out.println(list);
    }

}

字符串匹配模式

资料

讲的最好的kmp原理篇
讲解next[]推导

朴素模式

//字符串匹配的朴素算法
//长字符串s,从下标pos处开始,查找后续是否存在字符串t(仅第一匹配成功的),返回匹配成功的下标,否则返回-1
function indexof(s,t,pos){
    var slen = s.length;
    var tlen = t.length;
    for(var i = pos;i<=slen-tlen;i++){
        var j ;
        for(j=0;j<tlen;j++){
            if(s.charAt(i+j) != t.charAt(j)){
                break;
            }
        }
        if(j==tlen){
            return i;
        }

    }
    return -1;
}
indexof('ksdfnnksf','nk',0);

//第二种写法,便于后续理解kmp匹配算法
function indexof2(s,t,pos){
    var slen = s.length;
    var tlen = t.length;
    var i = pos;
    var j = 0;
    while(i < slen && j < tlen){

        if(s.charAt(i) == t.charAt(j)){
            i++;
            j++;
        }else{
            i = i-j+1;
            j = 0;

        }

    }
    if(j>=tlen){
        return i-tlen;

    }else{
        return -1;
    }
}

indexof2('ksdnfnksf','nk',0);

KMP模式

//kmp模式匹配算法
//相比较于朴素算法,减少不必要的重复比较,使得效率更高
//核心:每轮比较后,使得i不回溯,j值取决于t串内部的重复程度。
function getNext(t){
    var next = [];
    next[0] = -1;//当s[i] != t[j]时,j=0,则-1代表直接i+1,j=0,继续比较
    var m = 0;//后缀
    var n = -1;//前缀
    while(m < t.length-1){//每次算next[j]时,都是看的t[0]-t[j-1]中的最大前缀后缀子序列,这里j-1 = m
        if(n == -1 || t.charAt(m) == t.charAt(n)){//表示前缀的单个字符等于后缀的单个字符
            ++m;
            ++n;

            if(t.charAt(m) == t.charAt(n)){//改进的地方
                next[m] = next[n];
            }else{
                next[m] = n;
            }

        }else{//若字符不等,则n回溯
            n = next[n];
        }

    }
    return next;
}
function indexof3(s,t,pos){
    var slen = s.length;
    var tlen = t.length;
    var next = getNext(t);
    var i = pos;
    var j = 0;
    while(i < slen && j < tlen){

        if(j == -1 ||s.charAt(i) == t.charAt(j)){
            i++;
            j++;
        }else{
            //j根据next数组回溯,i不回溯
            j = next[j];

        }

    }
    if(j>=tlen){
        return i-tlen;

    }else{
        return -1;
    }
}

逆波兰式

逆波兰式

逆波兰式,是计算机计算四则混合运算(括号优先,乘除优先加减)时,对我们常规表达式(符号在中间)的一种转换方式。主要用到这种数据结构。
除了这种方式,网上讲用二叉树也可以实现计算机的四则混合运算,后续研究。链接博客

生成逆波兰式

  1. 一个栈存储符号,一个数组存储结果
  2. 对常规表达式(中缀表达式)数组,从左往右遍历
  3. 若是数字,直接放入结果数组
  4. 若是符号,对符号进行操作如下。
  5. 左括号,’(‘,直接压入操作符号栈
  6. 右括号,’)’,则将距离栈顶的最近的’(‘之间的运算符,逐个出栈,依次放入结果数组,此时抛弃’(‘’)’;
  7. 加减乘除,若栈为空,或者栈顶是左括号’(‘,直接入栈。否则与栈顶元素比较优先级,当前优先级大,则直接入栈。当前优先级小于或者等于,则将栈中元素依次弹出,存入结果数组,直至栈空,或者遇到栈顶为左括号’(‘,或者栈顶优先级小于(不包含等于)当前元素。此后再将当前运算符放入符号栈中(注意:不是结果数组)
  8. 遍历完成后,判断符号存储栈中是否有元素,若有,依次弹出,并放入结果数组


//逆波兰式生成
//仅仅考虑+-*/()

function create(arr){
    var result = [];//结果数组
    var stack = [];//操作符号栈

    for(var i = 0 ; i < arr.length;i++){
        var now = arr[i];
        if(isnum(now)){
            result.push(now);//1.数字,直接放入结果数组
        }else{

            //2.符号相关处理
            //2.1.左括号直接压入操作符号栈
            if(now == '('){
                stack.push(now);
            }

            //2.2.右括号,将操作符号栈左括号‘(’之间操作符放入结果数组,并抛弃左右括号'('')'
            if(now == ')'){
                var temp = stack.pop();
                while(temp != '('){
                    result.push(temp);//把操作栈中左括号中间的符号输出
                    temp = stack.pop();
                }

            }
            //2.3.加号,减号,乘号,除号
            //2.3.1.栈为空,或者栈顶为左括号‘(’,直接压入
            //2.3.2.比较now和栈顶,当now优先级高于(不包括等于)栈顶,直接压入
            //2.3.3.优先级小于等于栈顶,弹出栈顶压入结果数组,直至栈顶优先级低于(不包括等于)now,或者遇到左括号‘(’,或者操作栈空。此后,将now压入操作符号栈(不是结果数组)
            //此处只考虑,加减乘除。
            if(now == '-' || now == '+' || now == '*' || now == '/'){
                if(stack.length == 0 || stack[stack.length-1] == '('){
                    stack.push(now);
                }else{
                    var temp2 = stack[stack.length-1];

                    if(now == '*' || now == '/'){
                        if(temp2 == '*' || temp2 == '/'){

                        result.push(stack.pop());

                        while(true){
                            if(stack.length == 0){
                                break;
                            }
                            temp2 = stack[stack.length-1];

                            if(temp2 == '(' || temp2 == '+' || temp2 == '-'){
                                break;
                            }
                            result.push(stack.pop());
                        }
                        //压入
                        stack.push(now);

                        }else{//加减,优先级低
                            stack.push(now);
                        }

                    }

                    if(now == '+' || now == '-'){
                        result.push(stack.pop());
                        while(true){
                            if(stack.length == 0){
                                break;
                            }
                            temp2 = stack[stack.length-1];

                            if(temp2 == '('){
                                break;
                            }
                            result.push(stack.pop());
                        }
                        //压入
                        stack.push(now);
                    }
                }
            }
        }
    }
    //stack中全部输出
    while(stack.length>0){
        result.push(stack.pop());
    }
    return result;
}

//是不是数字
function isnum(a){
    if(a == '(' || a == ')' || a == '+' || a == '-' || a == '*' || a == '/'){
        return false;
    }
    return true;
}
//测试
//var h = create('9+(3-1)*3+10/2'.split(''));//数字10被分为1,0
var h = create(['9','+','(','3','-','1',')','*','3','+','10','/','2']);
//h:9 3 1 - 3 * + 10 2 / +
//["9", "3", "1", "-", "3", "*", "+", "10", "2", "/", "+"]

//var h = create('1+2-5*(5-4)*6-(6-1)'.split(''));
//h:1 2 + 5 5 4 - * 6 * - 6 1 - -
//["1", "2", "+", "5", "5", "4", "-", "*", "6", "*", "-", "6", "1", "-", "-"]

逆波兰式计算

//逆波兰式运算
//str 是右序表达式的数组
function result(str){
var len = str.length;

var stack = [];//数值放入栈,遇到符号,出栈两个数字,计算
    for(var i = 0;i<len;i++){
        if(str[i] != '+' && str[i] != '-' && str[i] != '*' && str[i] != '/'){
            stack.push(str[i]);
        }else{
            //字符转数字
            var p2 = Number(stack.pop());//注意此处顺序,先pop是被操作数(被加减乘除)
            var p1 = Number(stack.pop());

            switch(str[i]){
                case '+':
                    p1 +=p2;
                    break;
                case '-':
                    p1 -=p2;
                    break;
                case '*':
                    p1 *=p2;
                    break;
                case '/':
                    p1 /=p2;
                    break;

            }
            stack.push(p1);
        }

    }
    return stack.pop();


}
var s = result(["9", "3", "1", "-", "3", "*", "+", "10", "2", "/", "+"]);//20
var s1 = result(["1", "2", "+", "5", "5", "4", "-", "*", "6", "*", "-", "6", "1", "-",   "-"]);//-32
console.log(s+"::::"+s1);

8个小球,找出其中一个次品

第一次称重:8个球,分为3:3:2,分别3:3放到天平两侧
1.平衡;则表示有问题的球在2中,进行第二次称重
    第二次称重:2中随意取一个球,与正常的球分别放于天平两侧
        1.1 若不平衡,则选中的2中的球有问题,根据天平,可知是偏轻,还是偏重【两次完成】
        1.2 若平衡,则第三次称重
            第三次称重:2中剩下的一个球,与正常球放于天平两侧,根据天平,可知是偏轻,还是偏重【三次完成】

2.不平衡:则表示有问题的球在3:3中,进行第二次称重
    第二次称重:天平较重端(命名为B),选走2个球,将天平较轻端(命名为A)取出一个球,放于较重端,
                此时2(A中两个):2(B中一个+A中一个):2(B中两个),将前两组放于天平两侧,进行第二次称重
        2.1 若平衡,则有问题的球在第三份中,并且有问题的球是B中的,可推断有问题的球是偏重的,进行第三次称重
            第三次称重:将第3份中的球任意取一个,和正常的球放于天平两侧
                2.1.1 平衡,则有问题的是第三份球中未取走的那个,且偏重【三次完成】
                2.1.2 不平衡,则有问题的是第三份球中取走的那个,且偏重【三次完成】

        2.2 若不平衡,则有问题的是在2(A中两个):2(B中一个+A中一个)中,此时,要么3个A中有一个球轻,要么B中那个球重
            2.2.1 前端翘起,有两种可能,第一种,前两个A中球有问题,有一个较轻;第二种,B中原本的那个球有问题,较重,
                  而由A放入B的那个球肯定是正常的,进行第三次称重

                第三次称重:分别将A中的球放入天平两侧
                    2.2.1.1 ;平衡,则B中的那个球有问题,较重【三次完成】
                    2.2.1.2 ;不平衡,上翘那端的球有问题,较轻【三次完成】

            2.2.2 后端翘起,表示有问题的球是放于B端的那个A中的球,较轻【两次完成】

轻松理解设计模式——观察者模式

观察者模式,学习代码。

1
2
3
4
5
6
7
8
9
10
11
12
13
package com.kevinlsui.observer.example;
/**
* 被观察类接口
*/
public interface Subject {
// 观察者注册
public void add(Observer o);
//观察者注销
public void remove(Observer o);
// 消息发布
public void update();
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
package com.kevinlsui.observer.example;
import java.util.ArrayList;
import java.util.List;
/**
* 被观察类
*/
public class WeatherData implements Subject{
private int low;
private int hight;
private String weather;
private List<Observer> list = new ArrayList<Observer>();
public int getLow() {
return low;
}
public int getHight() {
return hight;
}
public String getWeather() {
return weather;
}
public void setData(int low,int hight,String weather){
this.low = low;
this.hight = hight;
this.weather = weather;
update();
}
@Override
public void add(Observer o) {
if(!list.contains(o)){
list.add(o);
}
}
@Override
public void remove(Observer o) {
if(list.contains(o)){
list.remove(o);
}
}
@Override
public void update() {
for(Observer o :list){
o.update(getLow(),getHight(),getWeather());
}
}
}
1
2
3
4
5
6
7
8
9
package com.kevinlsui.observer.example;
/**
* 观察类接口
*/
public interface Observer {
//坐等信息发布过来
public void update(int low,int hight,String weather);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package com.kevinlsui.observer.example;
/**
* 观察类1
*/
public class Ipad implements Observer{
private Subject sub;
public Ipad(Subject subject){
this.sub = subject;
subject.add(this);
}
public void cancle(){
sub.remove(this);
}
@Override
public void update(int low, int hight, String weather) {
System.out.println("Ipd....low:"+low+";hight:"+hight+";weather:"+weather);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
package com.kevinlsui.observer.example;
/**
* 观察类2
*/
public class Phone implements Observer{
private Subject sub;
public Phone(Subject subject){
this.sub = subject;
subject.add(this);
}
public void cancle(){
sub.remove(this);
}
@Override
public void update(int low, int hight, String weather) {
System.out.println("Phone..........low:"+low+";hight:"+hight+";weather:"+weather);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package com.kevinlsui.observer.example;
/**
*测试类
*/
public class Test {
public static void main(String[] args) {
System.out.println("观察者模式.........");
WeatherData we = new WeatherData();
Phone p = new Phone(we);
Ipad ipad = new Ipad(we);
we.setData(1, 12, "晴天");
p.cancle();
we.setData(5, 18, "大晴天");
}
}

简单解释下数据库三大范式

前言

数据库三大范式,是后端人员设计表要遵守的最基本准则。(现实中,时间换空间,可能会做数据冗余,没有遵守部分准则,但是只有在理解这个三大范式基础上,才能艺高人胆大的去做这类处理,否则会把数据库弄的一团糟)
很多书和博客上的专业术语让人不知所云。这里用通俗的语言介绍下,可能不太准确,权当辅助理解那些专业术语。

第一范式

1.确保每列的原子性
2.如果一个关系模式R的所有属性都是不可分的基本数据项,则R∈1NF。

简单的说,第一范式就是每一个属性(每一列)都不可再分的。

第二范式

1.在第一范式的基础上,确保每列和主键相关
2.若关系模式R∈1NF,并且每一个非主属性都完全函数依赖于R的码,则R∈2NF

首先,要符合第一范式。
有的表,可能是联合主键(多个列构成一个主键),这时其他的非主键列,必须完全依赖于全部主键,不是依赖部分主键。
比如:一个表(学生id,课程id,课程姓名),这里联合主键(学生id,课程id),这里的‘课程姓名’就是不遵守第二范式的,因为他只是依赖部分主键(课程id)。换个角度想,一个课程有多个学生选择,就是在这个表,某一个课程的名字会出现多次,应该只用课程id,新建一个课程表,记录(课程id,课程名)

如果表的主键只有一列属性,则非主键列依赖主键列即可

第三范式

  1. 在第二范式的基础上,确保每列都和主键直接相关,而不是间接相关
    2.关系模式R 中若不存在这样的码X、属性组Y及非主属性Z, 使得Z→Y,Y→X,成立,则称R ∈ 3NF。

首先,满足第一范式,第二范式
第三范式是为了消除数据库中关键字之间的依赖关系
比如表(用户id[主键]、角色id、角色职责),这里角色id依赖用户id,角色职责依赖角色id,造成了传递依赖,所以不符合第三范式

|