java复习系列[1] - Java 基础

Java 基础

final

修饰对象作用其他
不可继承
成员方法不可重写
成员变量基本数据类型:不可修改值
引用数据类型:不可修改引用
初值问题:
1. 定义时直接赋初值
2. 先不赋值,在构造方法中对其赋值(存在多个构造方法,每个构造方法都需要进行赋值)
局部变量基本数据类型:只能赋值一次,赋值之后不能更改
引用数据类型:不可修改引用
初值问题:
1. 对于基本数据类型和String,若进行了赋初值操作,那么编译之后就会看做常量(这是JVM的优化)
  1. abstract、final 不能同时使用:abstract 方法必须覆盖重写,但是 final 方法不能覆盖重写

static

static块的执行发生在“初始化”的阶段。初始化阶段,jvm主要完成对静态变量的初始化,静态块执行等工作。

执行static块的几种情况:

1、**第一次new A()**的过程会打印"";因为这个过程包括了初始化

2、**第一次Class.forName(“A”)**的过程会打印"";因为这个过程相当于Class.forName(“A”,true,this.getClass().getClassLoader());

3、**第一次Class.forName(“A”,false,this.getClass().getClassLoader())**的过程则不会打印""。因为false指明了装载类的过程中,不进行初始化。不初始化则不会执行static块。

Java的switch支持的数据类型

  • Java5以前,exper只能是byte,short,char,int类型

    byte、short、char类型可以在不损失精度的情况下向上转型成int类型。

  • 从Java5开始,java中引入了**枚举类型(enum类型)**和 byteshortcharint的包装类

    四个包装类的支持是因为java编译器在底层进行了拆箱操作;

    枚举类型的支持是因为枚举类有一个ordinal方法,该方法实际上是返回一个int类型的数值。

  • 从Java7开始,exper还可以是String类型

    String类中,因为有一个hashCode方法,结果也是返回int类型。

所以得出的结论是,switch在底层实现目前只支持整型数据

Java中字符串String Switch的实现原理

case 后面的Value值只能是整型、字符型、字符串的常量或常量表达式 和枚举

goto (扫盲 - 一般不用)

goto起源于汇编语言,在编程语言中一开始就有goto,它可以直接操纵源码,这正是上帝之手的魔力,但是权力太大也容易让人烦脑,goto变得臭名昭著。

在Java中我们没有采用goto,但是Java任然保留了标签机制,通过breakcontinue继续绽放goto之花。

public class Jump {
	public static void main(String[] args) {
		outer:
		for(int i = 0; i < 3; i++) {
			System.out.print("loop "+i);
			System.out.println();
			for(int j = 0; j < 10; j++)
			{
				System.out.println("looper "+j);
				if(j == 5)
					break outer;
			}
			System.out.println("Jump");
		}
		System.out.println("跳出循环");
	}
}/*Output:
loop 0
looper 0
looper 1
looper 2
looper 3
looper 4
looper 5
跳出循环*/

String

不可变

  1. final修饰
  2. 方法封装
public final class String implements java.io.Serializable, Comparable<String>,
CharSequence {
    /** String本质是个char数组. 而且用final关键字修饰.*/
    private final char value[];
    ...
    ...
}

不可变的优势

  1. 不可变类型,线程安全
  2. 保证HashMap的Key唯一性
  3. 节省内存空间

在这里插入图片描述

序列化

定义:将对象的信息(状态)转换为可存储或可传输的形式的过程。期间,将对象状态写入到内存或文件中。

**目的:**将对象持久化;便于数据传输

**Java实现序列化:**实现Serializable接口,然后使用ObjectOutputStream将对象写入目标位置(内存,文件),可以使用ObjectInputStream读取序列化的对象

  • transient 属性不会被序列化
  • static 属性不能被序列化:序列化保存的是对象的状态

序列化的 serialVersionUID 问题

在很多应用中,需要对某些对象进行序列化,让它们离开内存空间,入住物理硬盘,以便长期保存。比如最常见的是Web服务器中的 Session 对象,当有 10万用户并发访问,就有可能出现 10万个 Session 对象,内存可能吃不消,于是Web容器就会把一些 Seesion 先序列化到硬盘中,等要用了,再把保存在硬盘中的对象还原到内存中。

当两个进程在进行远程通信时,彼此可以发送各种类型的数据。无论是何种类型的数据,都会以二进制序列的形式在网络上传送。发送方需要把这个Java对象转换为字节序列,才能在网络上传送;接收方则需要把字节序列再恢复为Java对象

如果用户没有自己声明一个 serialVersionUID,接口会默认生成一个***serialVersionUID***;

但是强烈建议用户自定义一个serialVersionUID,因为默认的 serialVersinUID 对于 class的细节 非常敏感,反序列化时可能会导致 InvalidClassException 这个异常。

class的细节:class的字段的修改

在这里插入图片描述

序列化代码

Java对象序列化为什么要使用SerialversionUID

可以测试,使用 serialVersionUID 前后,对 Person 类进行修改。可以进行测试!

package com.alvin.SerialXXX;

import java.io.Serializable;

public class Person implements Serializable {

    // 添加序列号之后,可以避免 InvalidClassException 这一问题
    private static final long serialVersionUID = -5809782578272943999L;
    private int age;
    private String name;
    private String sex;
    
	public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getSex() {
        return sex;
    }

    public void setSex(String sex) {
        this.sex = sex;
    }
}
package com.alvin.SerialXXX;

import java.io.*;

public class SerialVersion {
        public static void main(String[] args) throws Exception {
            // serializePerson();
            Person p = deserializePerson();
            System.out.println(p.getName()+";"+p.getAge());


        }

        private static void serializePerson() throws FileNotFoundException, IOException {
            Person person = new Person();
            person.setName("测试实例");
            person.setAge(25);
            person.setSex("male");

            ObjectOutputStream oo = new ObjectOutputStream(new FileOutputStream(
                    new File("E:/person.txt")));
            oo.writeObject(person);
            System.out.println("序列化成功");
            oo.close();
        }

        private static Person deserializePerson() throws IOException, Exception {
            ObjectInputStream ois = new ObjectInputStream(new FileInputStream(new File("E:/person.txt")));
            Person person = (Person) ois.readObject();
            System.out.println("反序列化成功");
            return person;
        }
    }

IO流是什么?

  1. 流的本质是数据传输
  2. 是一组有顺序,具有源点和终点的字节集合
  3. 流可以看作为有方向的字节传输通道

传输的过程中一般都是使用的字节流,就算是字符流在传输过程中也是使用的字节进行传输,然后在内存中进行字符编码转换为字符。

类型

  1. 数据类型:字节,字符
  2. 方向:输入,输出
  3. 操作对象:内存,文件,对象
  4. 转化:字节 -> 字符,

多个IO流需要关闭而重复嵌套try-catch-finally

写了一个工具类进行实现。

public class Test {
    public static void main(String[] args) {
        try {
            System.out.println("第一try");
            throw new Exception("异常");
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            System.out.println("finally");
            try {
                throw new Exception("又是异常");
            } catch (Exception e) {
                e.printStackTrace();
            }
            System.out.println("会执行吗");
        }
    }
}

public class Test2 {
    public static void main(String[] args) {
        try {
            ...
            is.read();
            ...
            os.write();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
			IOUtil.close(is, os);
        }
    }
}

public class IOUtil {
    public static void close(Closeable... closeableList) {
        for (Closeable closeable : closeableList) {
            try {
                if (closeable != null) {
                    closeable.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

传统的BIO与NIO

我们使用InputStream从输入流中读取数据时,如果没有读取到有效的数据,程序将在此处阻塞该线程的执行。其实传统的输入里和输出流都是阻塞式的进行输入和输出

read() 是从内核空间 拷贝数据到 用户空间

注解和反射

  1. 声明注解 public @interface MyAnno { }

  2. 使用注解 @MyAnno

  3. 解析注解(注解处理) java.lang.reflect

    不同的注解有不同的处理方法(如下):

    • SpringMVC中的 @RequestMapping 就是表示URL与处理方法的映射
    • Spring的事务,***@Transactional***的注解处理器就需要使用 AOP 对方法进行增强

注解

注解在编译后,编译器会自动继承 java.lang.annotation.Annotation 接口,这里我们反编译前面定义的 MyAnnotation 注解

/**
 * 对应数据表注解
 */
@Target(ElementType.TYPE)//只能应用于类上
@Retention(RetentionPolicy.RUNTIME)//保存到运行时
public @interface MyAnnotation {
    String name() default "";
}
// ---------------------------------------------------------------------
package com.alvin.annotationdemo;

import java.lang.annotation.Annotation;
//反编译后的代码
public interface MyAnnotation extends Annotation
{
    public abstract String name();
}

注解不支持继承But,注解编译后定义的注解会自动继承***java.lang.annotation.Annotation***接口

快捷方式:所谓的快捷方式就是注解中定义了名为value的元素,并且在使用该注解时,如果该元素是唯一需要赋值的一个元素,那么此时无需使用key=value的语法,而只需在括号内给出value元素所需的值即可。这可以应用于任何合法类型的元素,记住,这限制了元素名必须为value。

反射获取注解信息

  1. 实现几个注解有关SQL的注解

  2. 获取注解信息

    Java***在***java.lang.reflect 反射包下新增了***AnnotatedElement***接口,它主要用于表示目前正在 JVM 中运行的程序中已使用注解的元素

  3. 由于自定义了处理 SQL 的注解,其处理器必须由我们自己编写!

注解实例

本实例来自于:深入理解Java注解类型(@Annotation)

1. 构建注解
/**
 * 表注解
 */
@Target(ElementType.TYPE)//只能应用于类上
@Retention(RetentionPolicy.RUNTIME)//保存到运行时
public @interface DBTable {
    String name() default "";
}

/**
 * 注解Integer类型的字段
 */
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface SQLInteger {
    //该字段对应数据库表列名
    String name() default "";
    //嵌套注解
    Constraints constraint() default @Constraints;
}

/**
 * 注解String类型的字段
 */
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface SQLString {
    //对应数据库表的列名
    String name() default "";
    //列类型分配的长度,如varchar(30)的30
    int value() default 0;
    Constraints constraint() default @Constraints;
}

/**
 * 约束注解
 */
@Target(ElementType.FIELD)//只能应用在字段上
@Retention(RetentionPolicy.RUNTIME)
public @interface Constraints {
    //判断是否作为主键约束
    boolean primaryKey() default false;
    //判断是否允许为null
    boolean allowNull() default false;
    //判断是否唯一
    boolean unique() default false;
}
2. 自定义注解使用
/**
 * 数据库表Member对应实例类bean
 */
@DBTable(name = "MEMBER")
public class Member {
    //主键ID
    @SQLString(name = "ID",value = 50, constraint = @Constraints(primaryKey = true))
    private String id;

    @SQLString(name = "NAME" , value = 30)
    private String name;

    @SQLInteger(name = "AGE")
    private int age;

    @SQLString(name = "DESCRIPTION" ,value = 150 , constraint = @Constraints(allowNull = true))
    private String description;//个人描述

   //省略set get.....
}
3. 运行时注解处理
  1. 判断是否存在类注解(存在则继续)
    1. 获取表注解的信息
    2. 获取字段上的注解
      1. 判断字段注解类型
      2. 获取字段的注解信息
    3. (本例中)构建对应 SQL 语句
package com.zejian.annotationdemo;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;

/**
 * Blog : http://blog.csdn.net/javazejian [原文地址,尊重原创]
 * 运行时注解处理器,构造表创建语句
 */
public class TableCreator {

  public static String createTableSql(String className) throws ClassNotFoundException {
    Class<?> cl = Class.forName(className);
    DBTable dbTable = cl.getAnnotation(DBTable.class);
    //如果没有表注解,直接返回
    if(dbTable == null) {
      System.out.println(
              "No DBTable annotations in class " + className);
      return null;
    }
    String tableName = dbTable.name();
    // If the name is empty, use the Class name:
    if(tableName.length() < 1)
      tableName = cl.getName().toUpperCase();
    List<String> columnDefs = new ArrayList<String>();
    //通过Class类API获取到所有成员字段
    for(Field field : cl.getDeclaredFields()) {
      String columnName = null;
      //获取字段上的注解
      Annotation[] anns = field.getDeclaredAnnotations();
      if(anns.length < 1)
        continue; // Not a db table column

      //判断注解类型
      if(anns[0] instanceof SQLInteger) {
        SQLInteger sInt = (SQLInteger) anns[0];
        //获取字段对应列名称,如果没有就是使用字段名称替代
        if(sInt.name().length() < 1)
          columnName = field.getName().toUpperCase();
        else
          columnName = sInt.name();
        //构建语句
        columnDefs.add(columnName + " INT" +
                getConstraints(sInt.constraint()));
      }
      //判断String类型
      if(anns[0] instanceof SQLString) {
        SQLString sString = (SQLString) anns[0];
        // Use field name if name not specified.
        if(sString.name().length() < 1)
          columnName = field.getName().toUpperCase();
        else
          columnName = sString.name();
        columnDefs.add(columnName + " VARCHAR(" +
                sString.value() + ")" +
                getConstraints(sString.constraint()));
      }


    }
    //数据库表构建语句
    StringBuilder createCommand = new StringBuilder(
            "CREATE TABLE " + tableName + "(");
    for(String columnDef : columnDefs)
      createCommand.append("\n    " + columnDef + ",");

    // Remove trailing comma
    String tableCreate = createCommand.substring(
            0, createCommand.length() - 1) + ");";
    return tableCreate;
  }


    /**
     * 判断该字段是否有其他约束
     * @param con
     * @return
     */
  private static String getConstraints(Constraints con) {
    String constraints = "";
    if(!con.allowNull())
      constraints += " NOT NULL";
    if(con.primaryKey())
      constraints += " PRIMARY KEY";
    if(con.unique())
      constraints += " UNIQUE";
    return constraints;
  }

  public static void main(String[] args) throws Exception {
    String[] arg={"com.zejian.annotationdemo.Member"};
    for(String className : arg) {
      System.out.println("Table Creation SQL for " +
              className + " is :\n" + createTableSql(className));
    }

    /**
     * 输出结果:
     Table Creation SQL for com.zejian.annotationdemo.Member is :
         CREATE TABLE MEMBER(
         ID VARCHAR(50) NOT NULL PRIMARY KEY,
         NAME VARCHAR(30) NOT NULL,
         AGE INT NOT NULL,
         DESCRIPTION VARCHAR(150)
     );
     */
  }
}

元注解

//声明Test注解
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Test {

} 

@Target注解传入ElementType.METHOD参数来标明@Test只能用于方法上

@Retention(RetentionPolicy.RUNTIME)则用来表示该注解生存期是运行时

@Target@Retention是由Java提供的元注解,所谓元注解就是标记其他注解的注解

@Target( ElementType.XXX )

public enum ElementType {
    /**标明该注解可以用于类、接口(包括注解类型)或enum声明*/
    TYPE,
    /** 标明该注解可以用于字段(域)声明,包括enum实例 */
    FIELD,
    /** 标明该注解可以用于方法声明 */
    METHOD,
    /** 标明该注解可以用于参数声明 */
    PARAMETER,
    /** 标明注解可以用于构造函数声明 */
    CONSTRUCTOR,
    /** 标明注解可以用于局部变量声明 */
    LOCAL_VARIABLE,
    /** 标明注解可以用于注解声明(应用于另一个注解上)*/
    ANNOTATION_TYPE,
    /** 标明注解可以用于包声明 */
    PACKAGE,
    /**
     * 标明注解可以用于类型参数声明(1.8新加入)
     * @since 1.8
     */
    TYPE_PARAMETER,
    /**
     * 类型使用声明(1.8新加入)
     * @since 1.8
     */
    TYPE_USE
}

@Retention( XXX )

约束注解的生命周期,分别有三个值,源码级别(source),类文件级别(class)或者运行时级别(runtime)。

  • SOURCE:注解将被编译器丢弃(该类型的注解信息只会保留在源码里,源码经过编译后,注解信息会被丢弃,不会保留在编译好的class文件里)
  • CLASS:注解在class文件中可用,但会被VM丢弃(该类型的注解信息会保留在源码里和class文件里,在执行的时候,不会加载到虚拟机中)
  • RUNTIME:注解信息将在运行期(JVM)也保留,因此可以通过反射机制读取注解的信息(源码、class文件和执行的时候都有注解的信息),如SpringMvc中的@Controller、@Autowired、@RequestMapping等。

Hash

在这里插入图片描述

哈希函数:

直接定址法:

Hash(Key) = a * Key + b

除留取余法:

Hash(Key) = key % p

p一般为质数

折叠法:

关键字分割,取几个部分的叠加

hashcode()

默认返回对象存储地址

为什么要重写hashcode()

保证在两个对象相同时候,其hashcode()的值也一样

确保一件事:

hashcode()不一致,equals的结果为False

hashcode()一致,equals的结果不一定为False

equals的结果True,hashcode()一定一致

equals的结果False,hashcode()一定不一致

HashMap中红黑树的排序方式
  1. 判断是否实现了Comparable接口

  2. 未实现,TreeNode的tieBreakOrder方法就是处理这种key无法比较问题

    1. 使用类名进行比较

    2. 若类名一致,最终用了System.identityHashCode()这样一个native方法最终完成了比较

      这个方法的返回值就是Object没被重写的hashCode,通常理解就是内存地址。

public V put(K key, V value) {
    return putVal(hash(key), key, value, false, true);
}


putVal(int hash, K key, V value, boolean onlyIfAbsent, boolean evict) {
	...
    else if (p instanceof TreeNode)
         e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
    ...
}

final TreeNode<K,V> putTreeVal(HashMap<K,V> map, Node<K,V>[] tab, int h, K k, V v) {
	...
    else if ((kc == null &&
             (kc = comparableClassFor(k)) == null) || // 判断是否实现了 Comparable 接口
             (dir = compareComparables(kc, k, pk)) == 0) { 
    	...
    }
    ...
}

// 判断是否实现了 Comparable 接口,
static Class<?> comparableClassFor(Object x) {...}
static int tieBreakOrder(Object a, Object b) {
    int d;
    if (a == null || b == null || (d = a.getClass().getName().
         compareTo(b.getClass().getName())) == 0)
        d = (System.identityHashCode(a) <= System.identityHashCode(b) ?
             -1 : 1);
    return d;
}

冲突解决的方法

  1. 开放定址法
    1. 线性探测
    2. 二次探测
    3. 再散列法
  2. 拉链法

equals

默认比较地址

String中重写了equals,比较的是值!!!

equals 重写的规范

  1. 自反性 a.equals(a)

  2. 对称性 a.equals(b)

  3. 传递性 a.equals(b) b.equals©

  4. 一致性 a, b未发生变化结果一致

  5. null a.equals(null)

异常

在这里插入图片描述

异常都是Throwable类的实例,Throwable在下一层被分解为Error和Exception。

Error

Java运行时候,系统内部资源耗尽。应用程序不应该抛出这一类错误。当出现这一类错误,除了告知用户,并尽力让程序安全中止之外,一般也无能为力。

一般都是虚拟机错误:栈溢出、堆溢出…

常见的有:

  • 栈溢出(递归没有停止条件)
  • 堆溢出
  • 元空间溢出
  • 直接内存溢出
  • GC过于频繁
  • 线程创建过多

Exception

RuntimeException 运行时异常

一般报这个错误就表示:代码有问题

例如:

  • ClassCastException
  • NullPointerException
  • IndexOutOfBoundException
XXXException 非运行时异常(检查式异常)

程序本身没有问题,而由于其他问题导致的异常,这类异常需要被捕获

常见非运行异常如下:

  • IOException
  • SQLException
  • FileNotFoundExcetion

try{}catch(…){}finally{ }

try : ⽤于捕获异常。其后可接零个或多个 catch 块,如果没有 catch 块,则必须跟⼀个 finally 块。

catch:⽤于处理 try 捕获到的异常。

finally : ⽆论是否捕获或处理异常,finally 块⾥的语句都会被执⾏。当在 try 块或 catch 块中遇到 return 语句时,finally 语句块将在⽅法返回之前被执⾏。

catch

多catch语句时,记住异常子类必须在它们任何父类 之前使用是很重要的。这是因为运用父类的catch语句将捕获该类型及其所有子类类型的异常。

class SuperSubCatch {
    public static void main(String args[]) {
        try {
            int a = 0;
            int b = 42 / a;
        } catch(Exception e) {
            System.out.println("Generic Exception catch.");
        }
        /* This catch is never reached because
        ArithmeticException is a subclass of Exception. */
        catch(ArithmeticException e) { // ERROR - unreachable
            System.out.println("This is never reached.");
        }
    }
}

以上代码编译时候会报错!

该错误消息说明第二个catch语句不会到达,因为该异常已经被捕获。

ArithmeticException 是Exception的子类,第一个catch语句将处理所有的面向Exception的错误,包括ArithmeticException。这意味着第二个catch语句永远不会执行。

异常子类 必须在 它们任何父类 之前使用

throw 和 throws
try{
    ...
}catch(XXXException e){
    throw new Exception("输入的字符串必须能够转化成数字!", e);
}finally{
    
}
public void solve() throws IOException{
    ...
}

另外不得不说异常处理中的 throwsthrow 的区别了。

  1. throws 出现在方法的声明中,表示该方法可能会抛出的异常,允许 throws 后面跟着多个异常类型
  2. throw 出现在方法体中,用于抛出异常。当方法在执行过程中遇到异常情况时,将异常信息封装为异常对象,然后 throw
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值