最新文章


JAVA——反射
反射能够分析类能力的程序成为反射。Class每一个类是Class类的实例对象。获取类类型的方法类名.class == 实例对象.getClass() == Class.forName(“类路径”)。当一个类存在无参构造器可以获取类类型,使用“类类型.newInstance()”实例该类对象。基本数据类型像基本诗句类型int、void关键字都有自己的类类型。动态加载反射可以实现动态加载类(程序运行时加载类)。通过API获取类信息获取类方法信息/** * 打印类信息 * @param object 对象 */public static void printClassInfo(Object object){    //先要获取类类型    Class classType = object.getClass();    //getName()包含类路径获取类名称,getSimpleName()获取不包含类路径的的方法名    System.out.println("类名:" + classType.getName());    /**     * getMethods():获取所有public方法信息,包括从父类继承而来的     * getDeclaredMethods():获取该类自声明的方法,不论访问权限。     */    System.out.println("方法:");    Method[] methods = classType.getMethods();    for (int i = 0; i < methods.length; i++) {        //获取方法返回值类类型        Class returnType = methods[i].getReturnType();        //获取返回值类型        System.out.print(returnType.getName() + " ");        //获取方法名        System.out.print(methods[i].getName() + "(");        //获取方法所有参数的类类型        Class[] params = methods[i].getParameterTypes();        for (Class param : params) {            //获取参数类型            System.out.print(param.getName() + ",");        }        System.out.print(")\n");    }}public static void main(String[] args) {    //如获取String对象的类信息    APITest.printClassInfo("test");}打印结果:类名:java.lang.String方法:boolean equals(java.lang.Object,)java.lang.String toString()int hashCode()......获取成员变量信息/** * 获取成员变量 * @param object */public static void printFields(Object object){    //先要获取类类型    Class classType = object.getClass();    /**     * getFields()获取所有public成员变量信息     * getDeclaredFields()获取所有自定义成员变量信息     */    //Field[] fields = classType.getFields();    Field[] fields = classType.getDeclaredFields();    for (Field field : fields) {        //获取成员变量类类型        Class fieldType = field.getClass();        System.out.print(fieldType.getName() + " ");        //成员变量名称        System.out.print(field.getName() + "\n");    }}public static void main(String[] args) {    //如获取String对象的类信息    //APITest.printClassInfo("test");    APITest.printFields("test");}打印结果:java.lang.reflect.Field valuejava.lang.reflect.Field hashjava.lang.reflect.Field serialVersionUIDjava.lang.reflect.Field serialPersistentFieldsjava.lang.reflect.Field CASE_INSENSITIVE_ORDER获取构造方法/** * 获取构造方法 * @param object */public static void printConstruct(Object object){    //先要获取类类型    Class classType = object.getClass();    /**     * getConstructors()获取所有public构造方法信息     * getDeclaredConstructors()获取所有自定义构造方法信息     */    Constructor[] constructors = classType.getDeclaredConstructors();    for (Constructor constructor : constructors) {        //获取方法名        System.out.print(constructor.getName() + "(");        //获取参数类类型        Class[] paramType = constructor.getParameterTypes();        for (Class aClass : paramType) {            System.out.print(aClass.getName() + ",");        }        System.out.print(constructor.getName() + ")\n");    }}public static void main(String[] args) {    APITest.printConstruct("test");}打印结果:java.lang.String([B,int,int,java.lang.String)java.lang.String([B,java.nio.charset.Charset,java.lang.String)java.lang.String([B,java.lang.String,java.lang.String)java.lang.String([B,int,int,java.nio.charset.Charset,java.lang.String)...... 备注:其他反射相关方法查看API。方法的反射一个方法有方法名称和参数决定。method.invoke(对象,参数列表);如果操作的方法没有返回值返回null,否则返回对应的值。public class MethodReflectTest {    public static void main(String[] args) {        //获取testA的类类型        TestA testA = new TestA();        Class aType = testA.getClass();        /**         * 操作方法A         */        try {            //获取方法            Method methodA = aType.getMethod("methodA", int.class, int.class);            //调用方法            System.out.println("方法A返回结果:" + methodA.invoke(testA, 10, 5));        } catch (Exception e) {            e.printStackTrace();        }        System.out.println("=======================================================");        /**         * 操作方法B         */        try {            //获取方法            Method methodB = aType.getMethod("methodB", new Class[]{int.class, int.class});            //调用方法            System.out.println("方法B返回结果:" + methodB.invoke(testA, new Object[]{10, 5}));        } catch (Exception e) {            e.printStackTrace();        }    }}class TestA {    public void methodA(int a, int b) {        System.out.println(a + b);    }    public int methodB(int a, int b) {        return a - b;    }}打印结果:15方法A返回结果:null=======================================================方法B返回结果:5集合的泛型集合的泛型只是在编译阶段用来防止用户输入错误,编译之后集合是去泛型的。绕开编译,向集合加不同类型的数据public static void main(String[] args) {    //定义一个泛型为String的list集合    List<String> list = new ArrayList<>();    //加入一个元素    list.add("a");    System.out.println(list);    //获取list的类类型    Class listType = list.getClass();    try {        //获取add方法        Method methodAdd = listType.getMethod("add", Object.class);        //通过反射向list加入一个int类型的数据        methodAdd.invoke(list, 10);    } catch (Exception e) {        e.printStackTrace();    }    System.out.println(list);}打印结果:[a][a, 10]
Mar 31, 2019 4:41:34 PM
276
2
SPRINGBOOT之QQ登录
最近在网站上做了一个QQ登录OAuth2.0接入的功能,下面我就来分享一下如何实现QQ登录。首先在开发之前我们需要为QQ登录做如下准备:1、登录QQ互联https://connect.qq.com/成为开发者(成为开发者需要填写资料,审核时间大概2天吧)。2、有了开发者账号之后就能创建应用,这时会要求你填写网站信息,之后又会进入审核阶段(本人当时几个小时就审核过了)。接下来就是开发阶段了根据QQ互联官方网站可以知道QQ登录开发流程如下:1、获取appid和appkey,appid和appkey在你创建的应用信息中查找。2、放置QQ登录按钮。3、获取Authorization Code。4、通过Authorization Code获取Access Token。5、获取QQ用户的openid。6、通过openid访问/修改QQ用户信息。以下为开发步骤和示例代码本项目是一个maven构建的项目。使用fastjson处理json数据,fastjson依赖:<dependency>    <groupId>com.alibaba</groupId>    <artifactId>fastjson</artifactId>    <version>1.2.47</version></dependency>一、写好配置功能1、配置基本信息## 第三方登录配置信息oauth:  qq:    app_id: ----------------    app_key: --------------------------------    # 获取ocde地址    get_authorization_code_uri: https://graph.qq.com/oauth2.0/authorize    # 获取ocde的参数,固定为code    response_type: code    # 回调地址    redirect_uri: ---------------------------    # 获取Access Token地址    get_access_token_uri: https://graph.qq.com/oauth2.0/token    # 获取Access Token,固定为authorization_code    token_grant_type: authorization_code    # 刷新Access Token,refresh_token    refresh_token_grant_type: refresh_token    # 获取openid的地址    get_openid_uri: https://graph.qq.com/oauth2.0/me    # 获取用户信息地址    get_user_info_uri: https://graph.qq.com/user/get_user_info 2、编写配置信息的配置类,通过该配置类获取配置信息@Component@ConfigurationProperties(prefix = "oauth.qq")public class QqOAuthConfig {    private String appId;    private String appKey;    /**     * 获取ocde地址     */    private String getAuthorizationCodeUri;    /**     * 获取ocde的参数,固定为code     */    private String responseType;    /**     * 回调地址     */    private String redirectUri;    /**     * 获取Access Token地址     */    private String getAccessTokenUri;    /**     * 获取Access Token,固定为authorization_code     */    private String tokenGrantType;    /**     * 刷新Access Token,refresh_token     */    private String refreshTokenGrantType;    /**     * 获取openid的地址     */    private String getOpenidUri;    /**     * 获取用户信息地址     */    private String getUserInfoUri;    public String getAppId() {        return appId;    }    public void setAppId(String appId) {        this.appId = appId;    }    public String getAppKey() {        return appKey;    }    public void setAppKey(String appKey) {        this.appKey = appKey;    }    public String getGetAuthorizationCodeUri() {        return getAuthorizationCodeUri;    }    public void setGetAuthorizationCodeUri(String getAuthorizationCodeUri) {        this.getAuthorizationCodeUri = getAuthorizationCodeUri;    }    public String getResponseType() {        return responseType;    }    public void setResponseType(String responseType) {        this.responseType = responseType;    }    public String getRedirectUri() {        return redirectUri;    }    public void setRedirectUri(String redirectUri) {        this.redirectUri = redirectUri;    }    public String getGetAccessTokenUri() {        return getAccessTokenUri;    }    public void setGetAccessTokenUri(String getAccessTokenUri) {        this.getAccessTokenUri = getAccessTokenUri;    }    public String getTokenGrantType() {        return tokenGrantType;    }    public void setTokenGrantType(String tokenGrantType) {        this.tokenGrantType = tokenGrantType;    }    public String getRefreshTokenGrantType() {        return refreshTokenGrantType;    }    public void setRefreshTokenGrantType(String refreshTokenGrantType) {        this.refreshTokenGrantType = refreshTokenGrantType;    }    public String getGetOpenidUri() {        return getOpenidUri;    }    public void setGetOpenidUri(String getOpenidUri) {        this.getOpenidUri = getOpenidUri;    }    public String getGetUserInfoUri() {        return getUserInfoUri;    }    public void setGetUserInfoUri(String getUserInfoUri) {        this.getUserInfoUri = getUserInfoUri;    }} 二、放置QQ登录按钮三、接下来就是QQ登录主要流程。此时我们需要httpclient工具来获取通过请求返回的信息,小伙伴们可以去百度了解一下httpclient。1、加入httpclient依赖<dependency>    <groupId>org.apache.httpcomponents</groupId>    <artifactId>httpclient</artifactId>    <version>4.5.7</version></dependency> 2、配置httpclient工具1)封装响应结果public class HttpClientResult implements Serializable {    /**     * 响应状态码     */    private int code;    /**     * 响应数据     */    private String content;    public HttpClientResult() {    }    public HttpClientResult(int code) {        this.code = code;    }    public HttpClientResult(int code, String content) {        this.code = code;        this.content = content;    }    public int getCode() {        return code;    }    public void setCode(int code) {        this.code = code;    }    public String getContent() {        return content;    }    public void setContent(String content) {        this.content = content;    }} 2)封装httpClient工具类public class HttpClientUtil {    // 编码格式。发送编码格式统一用UTF-8    private static final String ENCODING = "UTF-8";    // 设置连接超时时间,单位毫秒。    private static final int CONNECT_TIMEOUT = 6000;    // 请求获取数据的超时时间(即响应时间),单位毫秒。    private static final int SOCKET_TIMEOUT = 6000;    /**     * 发送get请求;不带请求头和请求参数     *     * @param url 请求地址     * @return     * @throws Exception     */    public static HttpClientResult doGet(String url) throws Exception {        return doGet(url, null, null);    }    /**     * 发送get请求;带请求参数     *     * @param url    请求地址     * @param params 请求参数集合     * @return     * @throws Exception     */    public static HttpClientResult doGet(String url, Map<String, String> params) throws Exception {        return doGet(url, null, params);    }    /**     * 发送get请求;带请求头和请求参数     *     * @param url     请求地址     * @param headers 请求头集合     * @param params  请求参数集合     * @return     * @throws Exception     */    public static HttpClientResult doGet(String url, Map<String, String> headers, Map<String, String> params) throws Exception {        // 创建httpClient对象        CloseableHttpClient httpClient = HttpClients.createDefault();        // 创建访问的地址        URIBuilder uriBuilder = new URIBuilder(url);        if (params != null) {            Set<Map.Entry<String, String>> entrySet = params.entrySet();            for (Map.Entry<String, String> entry : entrySet) {                uriBuilder.setParameter(entry.getKey(), entry.getValue());            }        }        // 创建http对象        HttpGet httpGet = new HttpGet(uriBuilder.build());        /**         * setConnectTimeout:设置连接超时时间,单位毫秒。         * setConnectionRequestTimeout:设置从connect Manager(连接池)获取Connection         * 超时时间,单位毫秒。这个属性是新加的属性,因为目前版本是可以共享连接池的。         * setSocketTimeout:请求获取数据的超时时间(即响应时间),单位毫秒。 如果访问一个接口,多少时间内无法返回数据,就直接放弃此次调用。         */        RequestConfig requestConfig = RequestConfig.custom().setConnectTimeout(CONNECT_TIMEOUT).setSocketTimeout(SOCKET_TIMEOUT).build();        httpGet.setConfig(requestConfig);        // 设置请求头        packageHeader(headers, httpGet);        // 创建httpResponse对象        CloseableHttpResponse httpResponse = null;        try {            // 执行请求并获得响应结果            return getHttpClientResult(httpResponse, httpClient, httpGet);        } finally {            // 释放资源            release(httpResponse, httpClient);        }    }    /**     * 发送post请求;不带请求头和请求参数     *     * @param url 请求地址     * @return     * @throws Exception     */    public static HttpClientResult doPost(String url) throws Exception {        return doPost(url, null, null);    }    /**     * 发送post请求;带请求参数     *     * @param url    请求地址     * @param params 参数集合     * @return     * @throws Exception     */    public static HttpClientResult doPost(String url, Map<String, String> params) throws Exception {        return doPost(url, null, params);    }    /**     * 发送post请求;带请求头和请求参数     *     * @param url     请求地址     * @param headers 请求头集合     * @param params  请求参数集合     * @return     * @throws Exception     */    public static HttpClientResult doPost(String url, Map<String, String> headers, Map<String, String> params) throws Exception {        // 创建httpClient对象        CloseableHttpClient httpClient = HttpClients.createDefault();        // 创建http对象        HttpPost httpPost = new HttpPost(url);        /**         * setConnectTimeout:设置连接超时时间,单位毫秒。         * setConnectionRequestTimeout:设置从connect Manager(连接池)获取Connection         * 超时时间,单位毫秒。这个属性是新加的属性,因为目前版本是可以共享连接池的。         * setSocketTimeout:请求获取数据的超时时间(即响应时间),单位毫秒。 如果访问一个接口,多少时间内无法返回数据,就直接放弃此次调用。         */        RequestConfig requestConfig = RequestConfig.custom().setConnectTimeout(CONNECT_TIMEOUT).setSocketTimeout(SOCKET_TIMEOUT).build();        httpPost.setConfig(requestConfig);        // 设置请求头      /*httpPost.setHeader("Cookie", "");      httpPost.setHeader("Connection", "keep-alive");      httpPost.setHeader("Accept", "application/json");      httpPost.setHeader("Accept-Language", "zh-CN,zh;q=0.9");      httpPost.setHeader("Accept-Encoding", "gzip, deflate, br");      httpPost.setHeader("User-Agent", "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.181 Safari/537.36");*/        packageHeader(headers, httpPost);        // 封装请求参数        packageParam(params, httpPost);        // 创建httpResponse对象        CloseableHttpResponse httpResponse = null;        try {            // 执行请求并获得响应结果            return getHttpClientResult(httpResponse, httpClient, httpPost);        } finally {            // 释放资源            release(httpResponse, httpClient);        }    }    /**     * 发送put请求;不带请求参数     *     * @param url 请求地址     * @param     * @return     * @throws Exception     */    public static HttpClientResult doPut(String url) throws Exception {        return doPut(url);    }    /**     * 发送put请求;带请求参数     *     * @param url    请求地址     * @param params 参数集合     * @return     * @throws Exception     */    public static HttpClientResult doPut(String url, Map<String, String> params) throws Exception {        CloseableHttpClient httpClient = HttpClients.createDefault();        HttpPut httpPut = new HttpPut(url);        RequestConfig requestConfig = RequestConfig.custom().setConnectTimeout(CONNECT_TIMEOUT).setSocketTimeout(SOCKET_TIMEOUT).build();        httpPut.setConfig(requestConfig);        packageParam(params, httpPut);        CloseableHttpResponse httpResponse = null;        try {            return getHttpClientResult(httpResponse, httpClient, httpPut);        } finally {            release(httpResponse, httpClient);        }    }    /**     * 发送delete请求;不带请求参数     *     * @param url 请求地址     * @param     * @return     * @throws Exception     */    public static HttpClientResult doDelete(String url) throws Exception {        CloseableHttpClient httpClient = HttpClients.createDefault();        HttpDelete httpDelete = new HttpDelete(url);        RequestConfig requestConfig = RequestConfig.custom().setConnectTimeout(CONNECT_TIMEOUT).setSocketTimeout(SOCKET_TIMEOUT).build();        httpDelete.setConfig(requestConfig);        CloseableHttpResponse httpResponse = null;        try {            return getHttpClientResult(httpResponse, httpClient, httpDelete);        } finally {            release(httpResponse, httpClient);        }    }    /**     * 发送delete请求;带请求参数     *     * @param url    请求地址     * @param params 参数集合     * @return     * @throws Exception     */    public static HttpClientResult doDelete(String url, Map<String, String> params) throws Exception {        if (params == null) {            params = new HashMap<String, String>();        }        params.put("_method", "delete");        return doPost(url, params);    }    /**     * Description: 封装请求头     *     * @param params     * @param httpMethod     */    public static void packageHeader(Map<String, String> params, HttpRequestBase httpMethod) {        // 封装请求头        if (params != null) {            Set<Map.Entry<String, String>> entrySet = params.entrySet();            for (Map.Entry<String, String> entry : entrySet) {                // 设置到请求头到HttpRequestBase对象中                httpMethod.setHeader(entry.getKey(), entry.getValue());            }        }    }    /**     * Description: 封装请求参数     *     * @param params     * @param httpMethod     * @throws UnsupportedEncodingException     */    public static void packageParam(Map<String, String> params, HttpEntityEnclosingRequestBase httpMethod)            throws UnsupportedEncodingException {        // 封装请求参数        if (params != null) {            List<NameValuePair> nvps = new ArrayList<NameValuePair>();            Set<Map.Entry<String, String>> entrySet = params.entrySet();            for (Map.Entry<String, String> entry : entrySet) {                nvps.add(new BasicNameValuePair(entry.getKey(), entry.getValue()));            }            // 设置到请求的http对象中            httpMethod.setEntity(new UrlEncodedFormEntity(nvps, ENCODING));        }    }    /**     * Description: 获得响应结果     *     * @param httpResponse     * @param httpClient     * @param httpMethod     * @return     * @throws Exception     */    public static HttpClientResult getHttpClientResult(CloseableHttpResponse httpResponse,CloseableHttpClient httpClient, HttpRequestBase httpMethod) throws Exception {        // 执行请求        httpResponse = httpClient.execute(httpMethod);        // 获取返回结果        if (httpResponse != null && httpResponse.getStatusLine() != null) {            String content = "";            if (httpResponse.getEntity() != null) {                content = EntityUtils.toString(httpResponse.getEntity(), ENCODING);            }            return new HttpClientResult(httpResponse.getStatusLine().getStatusCode(), content);        }        return new HttpClientResult(HttpStatus.SC_INTERNAL_SERVER_ERROR);    }    /**     * Description: 释放资源     *     * @param httpResponse     * @param httpClient     * @throws IOException     */    public static void release(CloseableHttpResponse httpResponse, CloseableHttpClient httpClient) throws IOException {        // 释放资源        if (httpResponse != null) {            httpResponse.close();        }        if (httpClient != null) {            httpClient.close();        }    }} 3、点击QQ登录的按钮获取会跳转到QQ登录页面。如果QQ授权登录成功将会跳转到指定的回调地址(创建应用是填写的地址),获取到Authorization Code,该code有效期10分钟。@GetMapping("/login/qq")public void loginQQ(HttpServletResponse httpServletResponse, String thirdLoginBackHref) {    try {        httpServletResponse.sendRedirect(qqOAuthConfig.getGetAuthorizationCodeUri() +                "?response_type=" + qqOAuthConfig.getResponseType() +                "&client_id=" + qqOAuthConfig.getAppId() +                "&redirect_uri=" + qqOAuthConfig.getRedirectUri() +                "&state=" + UuidUtil.getUuid());    } catch (Exception e) {        throw new BaseException("请求参数异常");    }} 1、编写回调地址。通过Authorization Code获取Access Token(2小时有效期);获取QQ用户的openid;通过openid访问QQ用户信息。1)回调controller@GetMapping("/login/qq/callback")public String qqCallbackUri(String code) {    loginService.qqLogin(code);    return "/"; //返回地址} 2)回调service,获取用户信息。@Override@Transactionalpublic void qqLogin(String code) {    //获取access_token的请求参数    HashMap<String, String> getTokenParams = new HashMap<>();    getTokenParams.put("grant_type", qqOAuthConfig.getTokenGrantType());    getTokenParams.put("client_id", qqOAuthConfig.getAppId());    getTokenParams.put("client_secret", qqOAuthConfig.getAppKey());    getTokenParams.put("code", code);    getTokenParams.put("redirect_uri", qqOAuthConfig.getRedirectUri());    //获取access_token    HttpClientResult resultOfGetAcccessToken = null;    try {        resultOfGetAcccessToken = HttpClientUtil.doGet(qqOAuthConfig.getGetAccessTokenUri(), getTokenParams);    } catch (Exception e) {        throw new BaseException("QQ登录异常");    }    //获取access_token返回参数    String resultParamsOfGetToken = resultOfGetAcccessToken.getContent();    if (resultOfGetAcccessToken.getCode() != 200 || (resultParamsOfGetToken == null || resultParamsOfGetToken.equals(""))) {        throw new BaseException("QQ登录异常");    }    //处理获取access_token返回参数成json    String[] tokenResultString = resultParamsOfGetToken.split("&");    Map<String, String> tokenResultMap = new HashMap<>();    for (String s : tokenResultString) {        String keyValue[] = s.split("=");        if (keyValue.length > 1) {            tokenResultMap.put(keyValue[0], keyValue[1]);        }    }    //access_token    String accessToken = tokenResultMap.get("access_token");    //获取openid    HttpClientResult resultOfGetOpenid = null;    try {        resultOfGetOpenid = HttpClientUtil.doGet(qqOAuthConfig.getGetOpenidUri() + "?access_token=" + accessToken);    } catch (Exception e) {        throw new BaseException("QQ登录异常");    }    //处理获取openid结果    String resultOfOpenidContent = resultOfGetOpenid.getContent();    if (resultOfGetOpenid.getCode() != 200 || (resultOfOpenidContent == null || resultOfOpenidContent.equals(""))) {        throw new BaseException("QQ登录异常");    }    JSONObject resultByGetOpenIdJson = JSON.parseObject(resultOfOpenidContent.replaceAll("callback\\( | \\);", "").trim());    //openid    String openid = (String) resultByGetOpenIdJson.get("openid");    if (resultByGetOpenIdJson.get("client_id") == null || openid == null) {        throw new BaseException("QQ登录异常");    }    //获取用户信息    HttpClientResult resultByGetUserInfo = null;    try {        resultByGetUserInfo = HttpClientUtil.doGet(qqOAuthConfig.getGetUserInfoUri() +                "?access_token=" + accessToken +                "&oauth_consumer_key=" + qqOAuthConfig.getAppId() +                "&openid=" + openid);    } catch (Exception e) {        throw new BaseException("QQ登录异常");    }    //处理用户数据    JSONObject qqUserInfoJson = JSON.parseObject(resultByGetUserInfo.getContent());    //qq头像    String chatHead = (String) qqUserInfoJson.get("figureurl_qq_1");    //qq昵称    String nickname = (String) qqUserInfoJson.get("nickname");    //以下便是登录逻辑,这里就省略不多写了,大家可以根据自己的需求编写登录逻辑。} 说明:上面代码最后的变量qqUserInfoJson便是一个包含了QQ用户信息的json对象,我们可以将自己需要的用户信息取出来,必要时保存在数据库。 虽然省略的登录部分的代码,但在这里也简要说一下QQ登录逻辑:首先QQ登录对于自己的项目肯定是免密登录;其次openid是每个QQ用户在本网站的唯一标识,所以我们QQ登录的用户表一般会存每个用户的openid,在获取到QQ用户信息时判断数据库是否存在与当前openid相同的用户数据,如果没有便添加一条与当前openid相同的用户信息然后登录,如果存在,可以选择直接进入登录逻辑或修改相关登录信息后进入登录逻辑。 这里只介绍简单的QQ登录流程,并没有持续去请求腾讯API获取相关信息,所以刷新access_token功能以后再加入。 至此QQ登录就分享完了,感谢大家的支持!
Mar 20, 2019 9:40:40 PM
278
45
JAVA核心技术基础学习笔记
java概述java“白皮书”关键术语1.      简单性、面向对象、分布式、健壮性、安全性、体系结构中立、可移植性、解释性、高性能、多线程、动态性。2.      在网页中运行的java程序叫applet。java基础程序设计结构数据类型1.      整型int(4字节 32位)、short(2字节 16位)、long(8字节 64位)、byte(1字节 8位)。long有一个后缀L或l,(如4000000000L);十六进制数值有前缀0x或0X(如0xCAFE);八进制有前缀0(如010代表8),易混淆,不建议使用八进制。1.      浮点型float(4字节)、double(8字节)。float的数值有一个后缀F或f(如3.12f);如果3.12没有后缀默认为double型,double型也可加后缀D或d。三个表溢出或出错的浮点型:正无穷大,负无穷大,NaN。如果在数值计算中不允许任何舍入误差,就用BigDecimal类。Double.isNaN(x)//判断不是数字。1.      char型有些Unicode字符用char值描述,另些Unicode字符需要两个char表示。码点:是指一个编码表中的某个字符对应的代码值。代码单元:在基本的多语言级别中,每个字符用16位表示,称为代码单元;辅助字符采用一对连续的代码单元。2.      boolean型整型和boolean之间不能相互转换。变量常量利用关键字final指示常量,习惯上常量名全大写。(如final double NUM = 2.23)。运算符 API: Math类运算符:+、-、*、/、等数学函数与常量在Math类中,提供了三角函数,指示函数,π等各式各样的数学函数和常量(如开方Math.sqrt(x)、Math.PI)。数值之间的类型转换如果两个数中有一个double,另一个就会转为double;否则其中一个为float,另一个转为float;否则其中一个为long,另一个转为long;否则另个都将转为int。括号与运算级别+=是右结合运算,所以表达式a += b += c等价于a += (b += c) 字符串API:java.lang.String、java.lang.StringBuffer、java.lang.StringBuilder拼接       用指定符号将多个字符串拼接成新字符串用静态的join()方法。如:String all = String.join("/","a","cc","s");不可变字符串优点:编译器可让字符串共享。构建字符串        StringBuilder(效率高,线程不安全)前身是StringBuffer(线程安全)。控制流程块作用域不能在嵌套的两个块声明相同变量名,如下:{int n;...{int n;...}}条件语句if(condition){statement...} if(condition){statement....}else{statement...} if(condition){statement....}else if(condition){statement...}else{statement...}循环while(condition){statement...} do{statement...}while(condetion);多重选择switch(param){case v1:...break;case v2:...break;case v3:...break;default:...break;}注意:case标签可以是:1.      类型为char、byte、short、int的常量表达式。2.      枚举常量。3.      java SE7开始也可以是字符串字面量。大数值API:java.math.BigInteger、java.math.BigDecimal如果基本的整数和浮点数精度不能满足要求,可使用java.math包的BigInter和BigDecimal(如金额)。使用静态的valueOf方法可以将普通数值转为大数值(如BigInteger a = BigInteger.valueOf(100);)。大数值不能用算术运算符(+、-、*、/等),必须使用大数值类中的方法处理大数值(如一个加法运算: BigInteger c = a.add(b);等价于c = a + b;)。数组API:java.util.Arraysfor each循环for(variable : conllection) {statement...}数组拷贝Arrays.copyOf(oldArray,oldArray.length);快速打印数组1.      Arrays.toString(a)2.      二维数组:Arrays.deepToString(a);对象与类OOP概述类类是构造对象的模板和蓝图。封装:实现封装的关键在于决不让类中的方法直接访问其他类的实例域(对象中的数据)。程序仅通过对象的方法与对象数据之间进行交互。对象对象的三个特性:1.      行为:可调用的方法。2.      状态:只能通过调用方法来改变。3.      标识:每个对象都不同,状态也常常不一样。类与类之间的关系    1.      依赖:A类的方法操纵B类的对象,A类依赖于B类。    2.      聚合:A对象包含B对象。    3.      继承:A类扩展B类,并且A类添加自己的额外功能。预定义类API:java.time.LocalDate对象与对象变量使用构造器构造一个新对象,关键字new。任何对象变量的值都是对存储在另一个地方的一个对象的引用。new操作的返回值是一个引用。一个方法不能作用于一个值为null的对象上。局部变量不会自动初始化为null,必须通过new或设置为null。LocalDate类Date类:表时间点。LocalDate类:日历表示法,用于维护日期;使用静态工厂方法代表调用构造器,而不是new关键字(如:LocalDate.now();//表构造这个对象的日期)。用户自定义类源(类)文件名必须与public类名字相匹配;在一个源文件中,只能有一个公共类,但可以有任意多的非公共类。构造器构造器与类同名,不要在构造器中定义与实例域同名的变量。隐式参数与显式参数1.      隐式参数:出现在方法名前的类对象(如User.add(x)中的User对象)。在每个方法中this表示隐式参数。2.      显式参数:方法名后括号中的值为显式参数(如User.add(x)中的x)。封装的优点封装一般需要三项类容:1.      一个私有的数据域。2.      一个共有的域访问器方法。3.      一个共有的与更改器方法。优点:数据域改变内部实现,不会影响其他类代码。更改器方法可以检验数据域的合法性。注意:不要编写返回引用可变对象的访问构造器;解决,先对可变数据域(可变对象)克隆再返回。私有方法一个计算代码划分成若干独立的辅助方法,这些辅助方法可设为私有方法。这是由于他们往往与当前实现机制非常紧密,或需要一个特别的协议以及一个特别的调用次序。静态域与静态方法静态域如果将域定义为static,每个类中只能有一个这样的域。它属于类,不属于任何独立对象。静态常量一般设为公有。静态方法静态方法不能向对象实施操作方法,即没有隐式对象或没有this参数的方法。静态方法可以访问自身的静态域。可以通过类名和对象调用。什么时候使用静态方法:1.      一个方法不需要访问对象状态,所需参数都是显式参数提供。2.      一个方法只需访问类的静态域。工厂方法可以使用静态工厂方法构造对象为什么有时不用构造器实例:3.      无法命名构造器(构造器名与类名不同)。4.      当使用构造器时,不能够改变所构造的对象类型。main方法        main方法将执行并创建所需对象方法参数方法得到的是对象引用的拷贝,对象引用及其他拷贝同时引用同一个对象。java对对象采用的不是引用调用,对象引用是按值传递的。对象构造重载返回类型不是方法签名的一部分。即不能有两个名字相同、参数类型也相同却返回值不同类型值的方法。无参构造器无参构造器将所有实例域设为默认值。如果类中至少有一个有参构造器,却没有无参构造器,则不能使用无参构造器实例对象。显式域初始化显式域初始化值不一定是常量,也可以是调用一个方法。调用另一个构造器关键字this不仅是引用方法的隐式参数;还可以通过this(...)调用同一个类的另一个构造器。这样可以对公共的构造器代码只写一次。初始化块只要构造类对象,这些块就会被执行。即先运行初始化块,再运行构造器主体部分。 初始化数据域的方法:1.      在构造器中设置。2.      在声明中赋值。3.      在初始化块中设置。可以提供静态域的初始值;但如果对类的静态域初始化代码比较复杂,也可以使用一个静态初始化块(static{})初始化。对象析构与finalize方法java有自动的垃圾回收器,不需人工回收内存,所以java不支持析构器。如果某个资源需要在使用完毕后立刻关闭,那么就需要人工来管理。对象用完时,可以应用一个close方法来完成相应的清理操作。finalize方法将在垃圾回收器清除对象之前调用。包java允许用包将类组织起来。所有标准的java包都处于java和javax包层次中。类的导入一个类可以使用所属包中的所有类,以及其他包中的公有类。静态导入import不仅可以导入类,还增加了导入静态方法和静态域的功能。包作用域如果没有指定public或private,这个部分(类、方法、或变量)可以被同一个包中的所有方法访问。类路径类路径:类路径是指所有包含类文件的路径集合。编译器任务:1.      定位文件(如定位一个类导入的其他类的类文件)。2.      查看源文件是否比类文件新,如果新,源文件会自动重新编译。类设计技巧1.      一定要保证数据私有。2.      不要在类中使用过多的基本类型。3.      不是所有的域都需要域访问器或域更改器。4.      将职责过多的类进行分解。5.      类和方法名要能体现他们的职责。6.      有限使用不可变的类。类、超类、子类子类构造器如果子类构造器没有显式的调用超类构造器,将自动调用超类默认构造器。如果超类没有不带参数的构造器,并且在子类构造器有没有显式的调用超类的其他构造器,将编译报错。super关键字作用:1.      调用超类方法。2.      调用超类的构造器。 调用构造器的语句只能出现在另一个构造器的第一句。一个对象变量可以指示多种实际类型的现象称为多态。在运行时能够自动的选择调用哪个方法的现象称为动态绑定。继承层次有一个公共超类派生出来的所有类集合称为继承层次。在继承层次中,某个特定的类到其祖先的路径称为该类的继承链。多态一个类变量既可以引用一个类对象,也可与引用其的任何一个子对象。但是,不能将一个超类的引用赋给子类变量。方法调用重载解析:编译器根据参数找到调用的对应的重载方法的过程。注:允许子类将覆盖方法的返回类型定义为原返回类型的子类型。private方法,static方法、final方法或者构造器,这些“方法”编译器可以准确的知道该调用哪个方法,这种调用方式称为静态绑定。虚拟机会预先为每个类创建一个方法表,其中列出了所有方法的签名和实际调用的方法。这样一来,在真正调用方法的时候虚拟机仅查找这张表。动态绑定的特性:无需对现存代码进行修改,就可以对程序进行扩展。      警告:在覆盖一个方法的时候,子类方法不能低于超类方法的可见性。阻止继承:final类和方法类的特定方法被声明为fina,子类将不能覆盖这个方法。如果将类声明为final,只有其中的方法自动成为final,而不包括域。强制类型转换只能在继承层次内进行转换。在将超类转换成子类前,应该用instanceof进行检查。抽象类类即使不含抽象方法,也可以将类声明为抽象类。抽象类不能被实例化。可以定义一个抽象类的对象变量,但是它只能引用非抽象子类的对象。编译器只允许调用声明在类中的方法。受保护访问java用于控制可见性的4个访问修饰符:1.      仅对本类可见——private。2.      对所有类可见——public。3.      对本包和多有子类可见——protected。4.      对本包可见——默认,不需要修饰符。Object类:所有类的超类equals方法Object类中的equals方法用于检验一个对象是否等于另一个对象。相等测试与继承从不同情况设计equals方法:1.      如果子类能够拥有自己相等的概念,则对称性需求经强制性采用getClass进行检测。2.      如果由超类决定相等的概念,那么就可以使用instanceof进行检测,这样可以在不同子类对象之间进行相等比较。            假设某个超类的id作为相等检测标准,并且这个相等概念适用于所有子类,就可以适用instanceof进行检测,并且应该将类名.equals方法声明为final。编写完美equals方法建议:1.      显示参数名为otherObject。2.      检测this和otherObject是否引用同一个对象。3.      检测otherObject是否为null,如果为null,返回false。4.      比较this与otherObject是否属于同一个类。5.      将otherObject转换为相应的类类型变量。6.      现在进行比较。如果在子类重新定义equals方法,就要在其中调用super.equals(other)。对于数组类型的域,可以使用静态的Arrays.equals方法检测相应的数组元素是否相等。hashCode方法API:java.util.Object、java.util.Objects、java.lang.基本数据类型的包装类、java.util.Arrays;这些类的hashCode相关方法。散列码:是由对象导出的一个整形值。由于hashCode方法定义在Object类中,因此每个对象都有一个默认的散列码,其值为对象的存储地址。如果重新定义equals方法,就必须重新定义hashCode方法,以便用户可以将对象插入到散列表中。最好使用null安全的方法Objects.hashCode。基本数据类型使用包装类名.hashCode来避免创建包装类对象。equals和hashCode的定义必须一致,如果x.equals(y)放回true,那么x.hashCode()就必须与y.hashCode()相同。      如果存在数组类型的域,那么可以使用静态的Arrays.hashCode方法计算一个散列码,这个散列码由元素的散列码组成。toString方法API:java.lang.Object的toString方法和eaqals方法,在自定义的类中,应该覆盖这两个方法。toString只要目的:只要一个对象与一个字符串通过操作符“+”连接起来,java就会自动的调用toString方法,以便获得这个对象的字符串描述。在调用x.toString方法时可以用""+x代替。如果x是基本类型,这条语句照样能执行。Object类定义了toString方法,用来打印输出对象所属的类名和散列码。泛型数组列表API:java.util.ArrayList<E>ArrayList:是一个采用类型参数的泛型类。使用add方法可以将元素添加到数组列表中。数组列表管理着对象引用的一个内部数组。如果调用add且内部数组已经满了,数组列表就会自动创建一个更大的数组,并将所有的对象从较小的数组中拷贝到较大的数组中去。访问数组列表元素API:java.util.ArrayList<T>使用add方法数组添加新元素(可以插入中间元素),而不用set方法,set方法会覆盖已经存在的元素内容。对数组实施插入和删除的效率比较低;如果数组储存元素比较多,就应该使用链表。类型化与原始数组的兼容性一旦能保证不会造成严重后果,可以用@SuppressWarnings("unchecked")标注来标记完成这个变量能够接受类型转换。如下:@SuppressWarnings("unchecked")      ArrayList<A> res = (ArrayList<A>) a.find(b);//fields warning.对象包装器与自动装箱API:java.lang.Integer、javaj.lang.NumberFormat所有基本类型都有一个对应的包装(器)类;整形和浮点型的包装器的公共超类Number。对象包装类是不可变的,即一旦构造了包装器,就不允许改变包装在其中的值。对象包装器还是final,因此不能定义它们的子类。警告:由于每个值分别包装在对象中,所以ArrayList<Integer>的效率远低于int[]的效率,因此乐意用它构造小型集合。自动装箱,如:list.add(3);自动转为list.add(Integer.valueOf(3));两个包装类比较时,用equals方法。如果在一个条件表达式中混合使用Integer和Double类型,Integer值就会拆箱,提升为double,再装箱为Double。拆箱和装箱是编译器认可的,而不是虚拟机。编译器在生成类的字节码时,插入必要的方法调用。虚拟机只是执行这些字节码。参数数量可变方法如:public static String method(String a,Object... args){..........}“...”是java代码的一部分,它表明这个方法可以接受任意数量的对象(除a参数外)。可以这样调用此方法:String m = method("cc",1,"bb","xx");编译器经new Object[]{1,"bb","xx"}传递给method方法。枚举类API:java.lang.Enum<E>一个简单的枚举类:public enum Size(){SMALL,MEDIUM,LARGE,EXTRAL_LARGE}在比较两个枚举类型是相等时,永远不要调用equals方法,而直接使用“==”就可以了。如果需要的话,可以在枚举类型中添加一些构造器、方法和域;构造器只是在构造枚举时调用。      所有的枚举类型都是Enum的子类。反射API:java.lang.Class、java.lang.reflect.Constructor、java.lang.reflect.Field、java.lang.reflect.Method、java.lang.reflect.Modifier反射库提供了一个非常丰富且精心设计的工具集,方便编写动态操纵java代码的程序。这项功能被大量用于javaBean中,他是java组件的体系结构。能够分析类能力的程序称为反射。Class类一个Class对象将表示一个特定类的属性获取Class类型对象的方法:1.      实例对象(名).getClass()。2.      类型(名).class。3.      Class.forName(类名路径或接口名的字符串)。无论何时,这个方法都要提供异常处理器。Class类实际上是一个泛型类。如Employee.class的类型是Class<Employee>。虚拟机为每个类型管理一个Class对象。因此可以用“==”运算符实现两个类对象比较。Class类型.newInstance()调用默认构造器初始化新创建的对象。如果这个类没有默认构造器,就会抛异常。如果要使用有参构造器,必须使用Constructor类的newInstance方法。捕获异常API:java.lang.Throwable异常有两种:未检查异常和已检查异常。利用Throwable类的printStackTrace方法打印出栈的轨迹。Throwable是Exception的超类。对于检查异常只需提供一个异常处理器。在运行时使用反射分析对象API:java.lang.reflect.ObjectAnalyzerAccessibleObject是Field、Method、Constructor类的公共超类。已知Class类对象,可以使用getDeclareFields获取所有数据域,然后使用Field类的setAccessible方法将所有域设置为可访问,这时可以用Field类的get和set方法获取每个域的值或者设置新值。泛型toString方法查看任意对象内部信息问题:循环引用可能导致无限递归,ObjectAnalyzer将记录已经被访问的对象。例:public String toString(){return new ObjectAnalyzer().toString(实例对象);}使用反射编写泛型编写数组代码API:java.lang.reflect.Array如过一个User[]对象零时转成Object[]数组,然后再把它装换回来是可以的,但从一开始就是Object[]的数组却永远不能转成User[]数组。整形数组类型int[]可以转换成Object,但不能转换成对象数组。使用反射编写泛型数组实现方法:public static Object goodCopyOf(Object a,int newLength){Class cl = a.getClass();//获取数组a的类对象if(!cl.isArray()) return null;//确认a是一个数组Class componentType = cl.getComponentType();//使用Class类的getComponentType方法确定数组的对应类型int length = Array.getLength(a);Object newArray = Array.newInstance(componentType, newLength);System.arraycopy(a, 0, newArray, 0, Math.min(length, newLength));//复制return newArray;}使用反射编写泛型数组实现方法:public static Object goodCopyOf(Object a,int newLength){Class cl = a.getClass();//获取数组a的类对象if(!cl.isArray()) return null;//确认a是一个数组Class componentType = cl.getComponentType();//使用Class类的getComponentType方法确定数组的对应类型int length = Array.getLength(a);Object newArray = Array.newInstance(componentType, newLength);System.arraycopy(a, 0, newArray, 0, Math.min(length, newLength));//复制return newArray;}继承的设计技巧1.      将公共操作和域放在超类。2.      不要使用受保护的域。3.      使用继承实现“is-a”关系。4.      除非所有的方法都有意义,否则不要使用继承。5.      在覆盖方法时,不要改变预期行为。6.      使用多态而非类型信息。7.      不要过多的使用反射。接口、lambda表达式、与内部类接口概念API:java.lang.Comparable<T>、java.util.ArraysArrays类中的sort方法可以对对象数组进行排序,但要满足一下前提:对象所属的类必须实现Comparable接口。接口中所有的方法都属于public,因此可以省略public关键字。接口不能含有实例域。提供实例域和方法实现的任务应该由实现接口的那个类来完成。接口的特性java规范建议不写多余的关键字。一个类可以实现多个接口。静态方法jse8允许在接口中添加静态方法。默认方法可以为接口方法提供一个默认实现(defaule修饰的方法)。默认方法可以调用任何其他方法。解决默认方法冲突1.      超类优先。2.      接口冲突。必须覆盖这个方法,或可以选择两个方法中的一个。注意:千万不要让一个默认方法重新定义Object类中的某个方法。接口示例Comparator 接口(Arrays.sort的第二个版本)一个数组和一个比较器(comparator)作为参数,比较器是实现Comparator接口类的实例。compare方法要在比较器对象上使用,而不是在字符串本身上调用。对象克隆cloneable是java提供的一组标记接口。标记接口不包含任何方法,他的唯一作用是允许在类查询中使用instanceof。即使clone的默认拷贝(浅拷贝)实现能够满足要求,还是需要实现Cloneale接口,将clone方法重新定义为public,再调用super.clone().深拷贝中,主要是对可变变量的拷贝(除开没有更改器的常量、不可变对象外)。前提是这些不可变子对象也实现了clone。深拷贝大致步骤:1.      实现Cloneable接口。2.      实现类中定义clone方法,先调用父类clone,再拷贝可变子对象,返回拷贝对象。3.      外部调用clone方法就实现深拷贝。lambda表达式API:java.util.function 常用函数式接口、基本类型函数式接口lambda表达式是一个可传递的代码块,可以在以后执行一次或多次。lambda表达式语法(参数)-> 表达式、(参数)->{表达式}即使lambda表达式没有参数仍要提供空括号,就像无参方法一样。如果可以推导出一个lambda表达式的参数类型,则可忽略其参数类型。如果方法只有一个参数,而且这个参数的参数类型可以推导的出,可省略小括号。无需指定lambda表达式的返回类型,返回类型总是会由上下文推导得出。如果一个lambda表达式只在某些分支返回一个值,而在另一些分支不返回值,这是不合法的。函数式接口对于只有一个抽象方法的接口,需要这个接口的对象时,就可以提供一个lambda表达式这种接口称为函数式接口。最好把lambda表达式看作是一个函数,而不是对象。另外lambda表达式可以传递到函数式接口。      不能把lambda表达式赋给类型为Object的变量,Object不是一个函数式接口。方法引用有时可能已经有现成的方法可以完成你想要传递到其他代码的某个动作。用::操作符分隔方法与对象类名,主要有以下3种情况:1.      object::instanceMethod。如System.out::println等价于System.out.println(x)2.      Class::staticMethod。如Math::pow等价于(x,y)->Math.pow(x,y)3.      Class::instanceMethod。如String::compareToIngnoreCase等同于(x,y)->x.compareToIngnoreCase(y)可以在方法中引用使用this参数。this::equals等同于x->this.equals(x)。使用super也是合法的,super::instanceMethod。使用this作为目标,会调用给指定方法的超类版本。构造器引用利用new来使用构造器引用。如Person::new。可以用数组类型建立构造器引用。如:int[]::new等价于x->new int[x]。变量作用域lambda表达式有3个部分:1.      代码块。2.      参数。3.      自由变量值。lambda表达式可以捕获外围作用域变量的值。要确保所捕获的值是明确定义的。lambda表达式捕获的变量必须实际上是最终变量(初始化后不再赋值的变量)。lambda表达式中声明一个与局部变量同名的参数或者局部变量是不合法的。处理lambda表达式使用lambda表达式的重点是延迟执行。如以下原因:1.      在一个单独线程运行的代码。2.      多次运行代码。3.      在算法的适当位置运行代码。4.      发生某种情况时运行代码。5.      只在必要事才运行代码。       如果是自己设计的接口,其中只有一个抽象方法,可以用@FunctionalInterface注解来标记这个接口。内部类内部类访问对象状态内部类既可以访问自身的数据域,也可以访问创建他的外部对象的数据域。内部类总有一个隐式引用outer,只想创建他的外部类对象。外部类引用在构造器中设置。如内部类没有定义构造器,会默认生成一个参数含外部引用的构造器。如构造器:public Time(Talk t){outer = t;    }注意:outer不是java关键字。内部类的特殊语法规则内部类声明的所有静态域都必须是final。内部类不能有static方法。表达式OuterClass.this表外围类引用。局部内部类局部类不能用public和private访问说明进行声明。他的作用域被限定在声明这个局部类的块中。他可以对外部世界完全的隐藏起来。由外部发方法访问变量局部内部类不仅能够访问包含他们的外部类,还可以访问局部变量,局部变量必须有final修饰。匿名内部类通常语法格式:new SuperType(construction paramters){inner class methods and data}匿名类不能有构造器,而是将构造器参数传给超类构造器。尤其是在内部类实现接口的时候,不能有任何构造参数。如下:new InterfaceType(){method snd data} 习惯用匿名内部类实现事件监听和其他回调。如今最好的做法是使用lambda表达式匿名内部类对于equals的方法使用要当心。new Object(){}.getClass().getEnclosingClass() //get class of static method静态内部类注:1.      在内部类不需要访问外围类对象的时候,应该使用静态内部类。2.      静态类可以有静态域和方法。3.      声明在接口中的内部类自动成为static和public类。
Mar 12, 2019 9:55:18 AM
291
34
博客项目介绍
    我创建这个博客的目的就是练习编程能力,以及和广大朋友们分享生活、交流技术。    我的博客前台使用bootstrap4布局,后台管理界面使用easyUI框架实现。涉及到的主要编程语言是HTML、CSS、java和mariadb数据库。现在spring官网也在推行springboot框架,大大减少了开发人员的配置,让开发人员更专注于业务的开发,所以我学习了如何使用springboot,并在此基础上整合各种框架。这个博客便是一个由maven构建的springboot项目,使用的是freemarker模板渲染数据。    这个博客项目分为前后台,前台的功能比较简单,主要就是数据的渲染以及用户的评论、留言等功能。而后台的功能对于第一次完全靠自己做完一个项目的我来说就稍显复杂了,自己不知查了多少资料才实现了功能。后台使用quartz定时删除30天的用户留言,点赞记录。当然,让我感到最复杂的便是使用shiro框架及其缓存对用户进行认证和授权,控制用户的访问权限。整个项目还具有以下功能:持久层使用mybatis框架,对数据库进行数据操作;博客的发表、评论留言等都通过百度ueditor进行编辑;所有用户上传的图片都保存在七牛云的对象存储中;AOP技术实现整个项目的全局异常捕获;通过fastjson处理json格式的数据等。    此博客后续还会不断更新,用更多技术实现更多功能。欢迎大家对我的博客进行持续关注!
Mar 6, 2019 5:59:50 PM
363
100
PLSQL基础学习笔记
基本概念关系型数据库三范式1、字段必须有单一属性,不可拆分。2、表具有唯一性的主键。3、表中的字段不能包含其他表中已出现的非主键字段。sql分类1、DML:数据操纵语言,完成数据的增删改查。2、DDL:数据定义语言,创建修改表、视图、过程及用户等。3、DCL:数据控制语言。4、DBA:数据库管理员。plsql语言plsql都是有语句块组成,块与块之间可以嵌套。每个块中可以包含多条sql,大大减少了网络开销。plsql执行环境plsql块被数据库内部的plsql引擎处理,plsql引擎块的sql语句交给sql引擎处理。1、服务器端的sql引擎:plsql客服端发送sql语句块给数据库服务器,服务端sql引擎对语句块进行处理,块中的sql语句交给sql执行器来执行。2、客服端的plsql引擎:客服端的plsql引擎直接执行块中的过程性语句,但如果plsql语句块中的包含sql语句或存服务器端储过程的调用,就需要将这些调用传给服务端,用服务端的sql执行器或plsql引擎执行。plsql块组成DECLARE     --可选  --定义部分BEGIN    --必须  --执行部分EXCEPTION   --可选  --异常处理部分END;     --必须注意:内部嵌套块可以访问外部快定义的变量,但外部块不能访问内部块定义的变量。变量和类型plsql四种变量类型:1、标量变量:存放单个数值的变量,如日期、时间。2、复合变量:存放多个值的变量,有plsql符合数据类型定义,如plsql记录,plsql表、嵌套表。3、参照变量:用于存放数值指针的变量,如游标和对象标量。4、LOB变量:用于存放大批量数据的变量。变量声明:v_name  [constant]  type  [not null]  [:= value];constant --声明常量value --初始值一旦出现not null或声明常量时后面必须赋一个初始值。赋值:null做任何运算,结果为null。在变量没有赋值之前不要直接使用变量进行变量运算。使用%type:基于已有的变量类型或数据库列类型来指定变量的类型。如:变量%type。使用%rowtype:绑定变量类型为数据库整行类型。如:v_a  emp_table%rowtype。作用域:如果块中定义了与外层块中相同的变量,使用“外层块名称.变量”这样的限定名称来获取外层变量。数据类型字符类型数字类型日期时间类型1、date:表示年月日时分秒。可以使用to_date和to_char函数在日期和字符串之间转换。2、timestamp:表示年月日时分秒(包括秒的小数部分)。3、timestamp with time zone:timestamp带时区。4、timestamp with local time zone:使用数据库时间。5、interval类型:用于存储两个时间戳之间的间隔。两种定义方式:1)interval year to mounth:年月之间的时间间隔。2)interval day to second :天数,时分秒之间的时间间隔。布尔LOB类型:最大可存4G。 引用类型:1、refcursor:引用游标。如:v_x  sys_refcursor2、ref:指向对象类型的实例指针。用户自定义子类型:语法:subtype 子类名称 is 基类名称[约束] [not null];子类型可以继承基类型大小约束,不能继承其他约束(not null)。程序控制语句1、条件控制:if-then-else-end if    --else在条件值为false或null是执行。case-when-then-when-then-end case等于if-then-elsif-end if。2、循环控制语句:1)简单循环:包含loop-end loop和用来退出循环的exit方法。2)数字for循环:循环带指定次数退出循环。3)while循环:当条件成立时执行循环。3、goto语句:无条件跳转语句。过程函数和包它们都是plsql语句块中的命名块。,过程和函数统称为子程序。过程和函数具有如下特点:1、都具有名称,可以接受传入和传出参数。2、都具有声明部分、执行部分和异常处理部分。3、在使用前或被编译保存到数据库中。函数和过程最大区别是函数具有返回值,过程没有返回值。包是一个逻辑单位,包分为包规范部分和包体部分。包规范部分用create package语句创建,相当于接口,用于声明常亮、变量、游标、子程序等;包体部分用create package body语句创建,用于实现包规范部分声明的子程序和游标。触发器触发器不能显示的被调用,而是在数据库发生特定事件时隐式执行,并且不接受任何参数。结构化异常处理plsql异常出来块用EXCEPTION开始。EXCEPTION采用when-then-when others then进行异常处理筛选。集合与记录记录类型    允许多个不同类型的变量当做一个整体处理。类似于数据库中的一条记录。先定义记录类型,在声明记录类型变量。集合类型允许数据类型相同的多个变量当做一个整体进行处理,类似于数组。plsql三种集合类型:1、关联表:又称索引表。元素个数不限,索引可为负数,字符型。2、嵌套表:下标从1开始,个数不限。嵌套表的类型可以作为表列的数据类型使用。在使用嵌套表之前必须使用构造函数进行初始化。3、变长数组:类型可以作为表的数据类型使用,下标从1开始,对元素个数有限制,使用前必须用构造方法初始化。游标一个指向上下文区域的指针。游标分为两种:1、隐式游标:由plsql自动为DML语句或select-into语句分配的游标。2、显示游标:在plsql声明块中显示定义,用来处理返回多行数据查询的游标。可以使用open打开游标,使游标指向第一行;fetch检索当前行信息,并把游标指向下一行;close语句用来在游标移到最后一行关闭游标。动态sql在运行时由字符串拼接而成的sql,然后使用EXECUTE IMMEDIATE来执行动态拼成的sql。编码规范1、标识符命名不超过30个字符。2、如果字符串中包含双引号,可以使用两个双引号代替。如字符串”I’m jack”可表示为’I’’m jack’。3、3空格缩进法。4、标识符命名规则:前缀_标识符内容_后缀。标识符命名建议表:开发基础数据表管理数据定义语言DDL1、create语句: create table2、alter语句:alter table 表名 add 字段 字段类型 字段约束;alter table 表名 drop comumn 字段;alter table 表名 rename column 原列明 to 新字段名;3、drop语句: drop table 表名;创建副本表语法:create table 附表 as select 列 from 原表 [where 1 = 2] --有这个where条件表示只创建表结构,没有数据。注意:1、不能复制约束条件及默认值。2、不能为新表指定空间。3、一些大对象数据(blob、long)的查询表不能创建成功。创建约束关键字:constraint。该关键字可以指定约束名,可同时用于多列。如:constraint 约束名 primary key (字段1,字段2...);外键约束:[constraint 约束名] references 表名(字段) [on delete {cascade | set null}]; --on delete表是否级联删除。检查约束:[constraint 约束名] check 条件;检查约束限制:1、不能为视图指定检查约束。2、检查约束不能包含子查询和标量子查询表达式。还有某些函数。3、不能包含自定义函数。4、不能包含伪劣。如:rownum、currval。查询约束约束信息可以从数据字典user_constraints和user_cons_columns查看。索引创建索引时,会对索引字段排序,并获取每条记录rowid,生成索引条目(索引列值,rowid对应存到索引段)。where自居查询时,先对索引检索,再通过对应的rowid到表中读取数据。创建索引:create [unoique/bitmap] index 索引名 on 表名(字段列表);注意:创建复合索引时一般将最常查询的列放在最前面。建议大数创建b树索引,小数创建位图索引。索引重命名:alter index 索引名 rename to 新索引名;合并索引:alter index 索引名 coalesce;重建索引:alter index 索引名 rebuild [tablespace 表空间];说明:合并索引和重建索引都能消除索引碎片。但是合并索引不能转移表空间,代价低,只能在B树的统一子树合并,不改变树高度。分配索引空间:alter index 索引名 allocate extent (size 容量大小); --防止索引段空间不足,动态扩容而降低装载速度。可以通过 alter index 索引名 deallocate unused多余索引空间。删除索引:drop index 索引名;  对于唯一索引,如果是在定义时由oracle自动创建,可通过disable禁用约束或删除约束的方法来删除索引。视图创建视图:create or replace view 视图名 as 查询语句; --可以设置视图只读,指定修改或插入的行。删除视图:drop view 视图名;查询dual表本身不具有任何意义,仅用来计算并返回结果。rownum伪列对结果动态集添加一个从1开始的序列号。rowid伪列它的值并没有存在表中,可以用它来删除重复数据。交叉连接表1 cross join 表2自然连接表1 natural 表2。自然连接只出现在两个表中具有相同名字和类型的列上。联合查询union与unionall:列数和列的数据类型一样。可在联合查询最后一句使用order by对结果排序。相交查询intersect:列数和列的数据类型一样。得到两个表去掉重复值的结果。相减查询minus:列数和列的数据类型一样。得到在查询一中存在,查询2中不存在的数据。层次化查询1、start with 条件1 connect by 条件2 --条件1可包含子查询;条件2不能包含子查询。2、prior操作符。prior后的表达式将被当成当前列的父列进行计算。3、level伪列。数据操纵语言DML插入数据使用子查询插入向同一张表插入多条数据,如果目标表与查询的表结构一样使用:insert into 目标表 select * from...;目标表自插入子查询表的部分字段使用:insert into 目标表(字段列表) select 字段列表 from...。同时想多张表插入数据:insert {first | all} [when 条件 then] into 表 [values(字段列表)]  [when 条件 then] into 表 [values(字段列表)]... else into table[values(字段列表)] 子查询;merge合并表行merge into 表名 别名1 using 表|视图|子查询 别名2 on (关联条件) when matched then update set语句 when not matched then insert(字段列表)values(字段值列表);序列用create sequence创建序列,主要工作是产生表的唯一值,序列存于数据字典,可以被多个对象共享。序列只用在nextval对序列进行初始化后才能调用currval。使用nextval时会产生一个新的伪列,使用currval不会产生新的伪列。nextval和currval使用地方:1、不是子查询一部分的select语句的字段列表。2、insert语句中子查询的select列表。3、insert语句中的values子句。4、update语句中的set子句。nextval和currval不能使用地方:1、视图的select列表。2、带distinct关键字的select列表。3、带group by、having、order by子句的select语句。4、在create table和alter table的default表达式。使用alter sequence修改序列。使用drop sequence 序列名删除序列。同义词同义词的目的是为了简化对目标对象的访问。同义词分为公用同义词和私有同义词。使用create [public] synonym 同义词名 for 要创建的同义词对象;使用drop sysnonym 同义词名;删除同义词。记录与集合记录类型记录类型只是用来组织其他标量类型的容器,他本身没有值,他里面的每一个变量拥有自己的值。记录类型可以在plsql块的声明区、子程序或包的声明部分定义。记录类型的定义:type 类型名 is record(字段名描述...);记录类型的赋值:记录名.字段名 := 值;insert语句使用记录:insert into 表名 values 记录名;在returning子句中使用记录:在DML语句中包含一个returning语句,用来返回insert、delete、update影响的行。如:insert...returning 字段列表 into 记录名。记录变量不允许出现在select列表、where子句、group by子句、order by子句中。集合索引集合定义:type 索引表名 as table of  plsql 预定义类型 [not null] index by 索引类型;操纵:索引表名(索引) := 值。嵌套表定义:type 嵌套表名 as table of 元素类型 [not null]; --元素类型不能是boolean、nchar、nvarchar2、nclob、ref cursor。变量名 嵌套表名 := 嵌套表(值列表);声明初始化嵌套表。用 “嵌套表名 is null”判断嵌套表是否被初始化。“嵌套表名(数字)”获取对应值。用delete删除嵌套表中的元素。数据库中的嵌套表:用“create or replace type 嵌套表名 as table of 元素类型 [not null];”创建。变长数组定义:type 数组名 {varray|varying array} (最大长度) of 数据类型 [not null];如:type alist is varray(50) of varchar(20)。可以通过extend扩展元素,但能超过最大长度。数据库中的变长数组:create or replace type 数组名 {varray|varying array} (最大长度) 数据类型 [not null]。基本结论:元素较少,优先考虑索引表;如果要存到数据库中,使用嵌套表;如果元素个数固定,使用变长数组。集合方法集合方法只能在plsql中使用,不能在sql语句中使用。调用方式:集合变量名.集合方法[(参数)]。exists方法:判断集合中指定元素是否存在。count方法:返回集合的长度。limit:返回集合元素的最大长度。对于嵌套表和索引表来说,最大长度不限,总是返回null。first和last:返回集合第一个和最后一个元素的索引数字。如果集合为空,返回null。prior和next:prior返回特定索引值参数前一个元素的索引值,next返回特定索引值参数后一个元素的索引值。如果没有返回null。extend:extend在末端加一个空元素;extend(n)在集合末端加n个空元素;extend(n,i)把第i个元素复制n份,加到集合末端。trim:从嵌套表或变长数组删除尾端元素。trim从集合末端删除一个元素;trim(n)从集合末端删除n个元素。delete:删除索引表或嵌套表一个或多个元素。delete删除所有元素;delete(n)删除第n个元素,如果索引为字符串,对应键值的元素会被删除。delete(m,n)删除m到n的元素。集合异常COLLECTION_IS_NULL:调用一个空集合的方法。NO_DATA_FOUND:小编索引指向一个不存在的索引。SUBSCRIPT_BEYOUND_COUNT:下标索引值超过集合中的元素个数。SUBSCRIPT_OUTSIDE_LIMIT:下标索引超出允许范围。VALUE_ERROR:下标索引为空,或不能转换成正确的键类型。批量绑定使用批量绑定一次性向sql引擎发送多条sql,提升sql执行效率。批量绑定语法:forall 集合下标 in 起始索引...结束索引 sql语句; --集合索引连续。BULK COLLECT批量地从sql引擎中批量接收数据到一个集合,可以在collect into、fetch into和returning into子句中使用。...BULK COLLECT INTO 集合名[,集合名]...使用的一些限制:1、索引不能为字符串。BULK COLLECT INTO2、只能在服务器端使用。3、BULK COLLECT INTO目标对象必须是集合。SQL内置函数分析函数基本语法:function_name() over();分析函数名1)等级函数:用于寻找前n种查询。ROW_NUMBER()行号;RANK()、DENSE_RANK()排名。2)开窗函数:用来进行累计值计算。与分组函数同名。3)制表函数:函数名与开窗函数相同;区别在于制表函数不能指定一个本地窗口,因此只是在整个分区或整个组上产生相同的结果(少了order by子句)。4)LAG、LEAD:允许在结果集中向前或向后检索值。可用于避免数据的自连接。5)其他统计函数:VAR_POP、VAR_SAMP、STDEV_POP等。用于计算任何未排序分区的统计值。分区子句使用partition by关键字将结果集分为n组。排序子句order by子句用于指定分组中数据的排序方式,排序方式会影响查询结果。分析函数的order by子句只能对各个分组进行排序,不能对查询结果排序。开窗子句开窗子句定义在order by子句之后,用来定义一个变化和固定的数据窗口方法,分析函数对这些数据进行操作。分析函数可用于如下场景:1、从当前记录开始到某个部分的最后一条记录。2、在统计时可以统计分组以外的记录。3、在当前的前几行或后几行进行滚动计算。分析函数列表1、first、last函数:first函数从DENSE_RANK返回的集合中取出排在第一的行,last函数与之相反。使用语法:“分组函数 KEEP(DENSE_RANK FIRST ORDER BY...)”。2、first_value、last_value函数:分别用来返回over子句查询的第一条记录和最后一条记录。3、lag、lead函数:lag的功能是返回指定列col前n1行的值,如果超出范围则返回n2,如果没有指定n2则返回null,如果不指定n1则默认为1。lead函数与之相反。lag(col[,n1][,n2]) over()。4、ntile() over():可以实现按层次查询。游标游标只是一个指向查询结果的指针。当游标打开后,数据被接收到一块内存区域存储,直到游标关闭。游标实际上指向的是一块内存区域,这块内存区域位于进程全局区内部,称为上下文区域。上下文区域一般保存:查询返回的数据行;查询所处理的数据行号;指向共享池中已分析的sql语句。分类显示游标:用cursor语句显示定义的游标,游标被定义之后需要打开并提取游标。隐式游标:每个不属于显示游标的是sql DML语句都会创建一个隐式游标。隐式游标是动态创建的,因此不能显式的打开、关闭或提取一个隐式类型的游标,能是隐式打开、关闭或提取。允许关键字SQL来访问游标属性。通过%FOUND、%ISOPEN、%NOTFOUND和%ROWCOUNT来访问右边相关属性(如:SQL%NOTFOUND和显式游标名%NOTFOUND)。定义游标通常使用显式游标来处理select语句返回的多行数据。定义方式:“cursor 游标名 [参数列表] [return 返回类型] is 查询语句 [for update [of (字段列表)] [NOWAIT]]”。游标名只是一个标识符,不能把一个值或赋给游标名或在表达式中使用它。游标和变量有同样的作用域规则。使用显式游标用fetch语句提取数据。游标的形式参数都是in模式,不能加not null约束,调用open 游标名(实参)。游标一旦打开就有指针指向第一行数据,然后就可以用fetch语句对数据进行提取。数据提取语法:“fetch 游标 into 变量名列表 | 记录类型”、“fetch 游标 bulk collect into 集合变量”。用“close 游标名”关闭游标。游标的操作loop循环:一般包含fetch、exit when这两个子句。游标for循环:使用for循环不用显式的打开、提取和关闭游标。修改游标数据1、for update子句:for update [of (字段列表) [nowait]]。of (字段列表)为字段添加互斥锁。nowait值当相同行被锁定时,select update for将不进行等待而直接返回。2、where current of 游标名:在使用for updat锁定了表的行后,可以在update或delete语句中使用该子句来得到当前游标检索出来的行。游标变量游标类型的声明:type 游标类型名 is ref cursor [return 游标类型]。声明完后将游标类型赋给游标变量。打开游标变量:open 游标名 for 查询语句。如果type语句中没有指定return子句,则可以连续打开多次,分别为其赋不同的select语句。控制游标变量:fetch 游标变量名 into 记录名 | 变量列表。可以在包中声明游标类型,不能在包中声明游标变量。事务处理事务的属性1、原子性2、一致性3、隔离性4、持久性commit提交事务commit事务会结束数据库事务。如果数据库使用了DML语句对数据进行了修改,那么这些修改会保存到数据库。加在事务上的所有锁及事务所占用的一切资源(游标、内存等)会被自动释放。commit声明:commit [work] [comment 50位以内的字符串]。rollback回滚事务rollback中止事务,相当于撤销操作。误操作或异常可使用rollback回滚。rollback基本语法:rollback [work] [to [savepoint] 保存节点名]。to子句回滚到某个保存节点。保存节点声明:savepoint 节点名。使用rollback to savepoint语句会发生:保存节点后的操作会撤销,但保存节点未被释放,如果需要,可以再次撤销该保存节点;该保存节点后的sql的锁和资源会被释放;整个事务并没有结束,sql处于挂起状态。set transaction设置事务属性允许开启一个只读或只写的事务,建立隔离级别或为当前事务分配一个特定的回滚段。该语句只能出现在事务第一句。声明语法:set transaction 参数。具体参数如下:1、read only:用于指定只读事务。任何修改都是非法的,不用指定回滚。查询过程不能使用for update子句。2、read write:建立读写事务。3、isolation level:用来设置事务的隔离级别。用法:set transaction isolation level seriallzation,设置序列隔离级别。set transaction isolation read comitted设置读提交隔离级别。set transaction isolation use rollback segment 回滚段名,给事务指定一个合适的回滚段。锁定表锁定对整个表实行锁定,防止其他会话或事务对表访问造成冲突。用于保护整张表数据。表锁定不会阻止用户对表的查询,查询也不会获取表锁。只有两个事务对修改统一数据是才会出现一个等待另一个事务的情况。使用表锁定的几种模式:1、row share:行共享锁,允许用户并发查询和修改表的数据。不允许任何事务独占式的进行写访问。2、row exclusive:行排他锁,与共享锁不同的是不能阻止别的事务对同一表的手工锁或独占式的读写。3、share lock:共享锁,只允许别的事务查询或锁定特定的记录。防止数据修改。share 4、share row exclusive:共享排他锁,用于事务和其他事务查询,不允许事务以共享模式锁定表或更新表中的记录。一般用于select update for语句。5、exclusive:排他锁,该事务以独占方式写一个表,其他用户只能读取数据。lock tablelock table允许用户使用一个特定的锁定模式锁定整个数据表。语法:“locak table 数据表列表 in 锁定模式 mode [nowait];”。如果要解除lock table对表的锁定,只需要简单的使用commit或rollback。lock table可以对视图进行锁定,实际上就是对组成视图的基础表进行锁定。记录锁定又称行锁定。对当前操作的行锁定,锁定总是以独占的方式进行,在当前事务结束之前,其他事务要等待该事务结束。这种锁称为互斥锁或排他锁。
Mar 6, 2019 4:38:15 PM
426
1
个人名片

  欢迎来到“浩瀚星尘”的个人博客!
  首先,该博客用于分享本人的生活事迹与兴趣爱好; 此外,该博客的主要作用便是与广大的小伙伴一起分享探讨开发技术, 希望大家多多关照。

网名: 浩瀚星尘
城市: 重庆
工作: java