Java 基础
final
修饰对象 | 作用 | 其他 |
---|---|---|
类 | 不可继承 | |
成员方法 | 不可重写 | |
成员变量 | 基本数据类型:不可修改值 引用数据类型:不可修改引用 | 初值问题: 1. 定义时直接赋初值 2. 先不赋值,在构造方法中对其赋值(存在多个构造方法,每个构造方法都需要进行赋值) |
局部变量 | 基本数据类型:只能赋值一次,赋值之后不能更改 引用数据类型:不可修改引用 | 初值问题: 1. 对于基本数据类型和String,若进行了赋初值操作,那么编译之后就会看做常量(这是JVM的优化) |
- 与 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类型)**和 byte,short,char,int的包装类
四个包装类的支持是因为java编译器在底层进行了拆箱操作;
枚举类型的支持是因为枚举类有一个ordinal方法,该方法实际上是返回一个int类型的数值。
-
从Java7开始,exper还可以是String类型
String类中,因为有一个hashCode方法,结果也是返回int类型。
所以得出的结论是,switch在底层实现目前只支持整型数据
case 后面的Value值只能是整型、字符型、字符串的常量或常量表达式 和枚举
goto (扫盲 - 一般不用)
goto起源于汇编语言,在编程语言中一开始就有goto,它可以直接操纵源码,这正是上帝之手的魔力,但是权力太大也容易让人烦脑,goto变得臭名昭著。
在Java中我们没有采用goto,但是Java任然保留了标签机制,通过break和continue继续绽放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
不可变
- final修饰
- 方法封装
public final class String implements java.io.Serializable, Comparable<String>,
CharSequence {
/** String本质是个char数组. 而且用final关键字修饰.*/
private final char value[];
...
...
}
不可变的优势
- 不可变类型,线程安全
- 保证HashMap的Key唯一性
- 节省内存空间
序列化
定义:将对象的信息(状态)转换为可存储或可传输的形式的过程。期间,将对象状态写入到内存或文件中。
**目的:**将对象持久化;便于数据传输
**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流是什么?
- 流的本质是数据传输
- 是一组有顺序,具有源点和终点的字节集合
- 流可以看作为有方向的字节传输通道
传输的过程中一般都是使用的字节流,就算是字符流在传输过程中也是使用的字节进行传输,然后在内存中进行字符编码转换为字符。
类型
- 数据类型:字节,字符
- 方向:输入,输出
- 操作对象:内存,文件,对象
- 转化:字节 -> 字符,
多个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() 是从内核空间 拷贝数据到 用户空间
注解和反射
-
声明注解 public @interface MyAnno { }
-
使用注解 @MyAnno
-
解析注解(注解处理) 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。
反射获取注解信息
-
实现几个注解有关SQL的注解
-
获取注解信息
Java***在***java.lang.reflect 反射包下新增了***AnnotatedElement***接口,它主要用于表示目前正在 JVM 中运行的程序中已使用注解的元素
-
由于自定义了处理 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. 运行时注解处理
- 判断是否存在类注解(存在则继续)
- 获取表注解的信息
- 获取字段上的注解
- 判断字段注解类型
- 获取字段的注解信息
- (本例中)构建对应 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中红黑树的排序方式
-
判断是否实现了Comparable接口
-
未实现,TreeNode的tieBreakOrder方法就是处理这种key无法比较问题
-
使用类名进行比较
-
若类名一致,最终用了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;
}
冲突解决的方法
- 开放定址法
- 线性探测
- 二次探测
- 再散列法
- 拉链法
equals
默认比较地址
String中重写了equals,比较的是值!!!
equals 重写的规范
-
自反性 a.equals(a)
-
对称性 a.equals(b)
-
传递性 a.equals(b) b.equals©
-
一致性 a, b未发生变化结果一致
-
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{
...
}
另外不得不说异常处理中的 throws 和 throw 的区别了。
- throws 出现在方法的声明中,表示该方法可能会抛出的异常,允许 throws 后面跟着多个异常类型
- throw 出现在方法体中,用于抛出异常。当方法在执行过程中遇到异常情况时,将异常信息封装为异常对象,然后 throw。