八大基本数据类型
基本数据类型转换关系:byte→short(char)→int→long→float→double
所谓包装类,就是能够直接将简单类型的变量表示为一个类,在执行变量类型的相互转换时,我们会大量使用这些包装类。
以下用途
包装类都为final 不可继承
包装类型都继承了Number抽象类
Integer x = 2; // 装箱 调用了 Integer.valueOf(2)
int y = x; // 拆箱 调用了 X.intValue()
new Integer(123) 与 Integer.valueOf(123) 的区别在于:
valueOf() 方法的实现比较简单,就是先判断值是否在缓存池中,如果在的话就直接返回缓存池的内容
包装类型内存使用 private static class IntegerCache,声明一个内部使用的缓存池
如Integer中有个静态内部类IntegerCache,里面有个cache[],也就是Integer常量池,常量池的大小为一个字节(-128~127)
为啥把缓存设置为[-128,127]区间?性能和资源之间的权衡。
在 jdk 1.8 所有的数值类缓冲池中,Integer 的缓冲池 IntegerCache 很特殊,这个缓冲池的下界是 - 128,上界默认是 127,但是这个上界是可调的,在启动 jvm 的时候,通过 -XX:AutoBoxCacheMax=
来指定这个缓冲池的大小。
基本类型对应的缓冲池如下:
BigDecimal 主要用于处理解决精度丢失问题
float和double类型主要是为了科学计算和工程计算而设计的。执行二进制浮点运算,这是为了在广泛的数字范围上提供较为精确的快速近似计算而精心设计的。然而,它们并没有提供完全精确的结果
float a = 1.0f - 0.9f;
float b = 0.9f - 0.8f;
System.out.println(a);// 0.100000024
System.out.println(b);// 0.099999964
System.out.println(a == b);// false
String 被声明为 final,因此它不可被继承
对String对象的任何改变都不影响到原对象,相关的任何change操作都会生成新的对象
不可变的好处
关于String使用new创建的问题:
new String(“abc”)创建两String对象。(前提是String Pool 中还没有 “abc” 字符串对象)
使用new关键字创建的字符串对象存储在堆的普通内存部分。
java 8 字符串常量池放置于方法区中
参考资料:字符串常量池String Constant Pool
intern() 方法:
String、StringBuilder、StringBuffer三者的执行效率:
StringBuilder > StringBuffer > String。这个实验结果是相对而言的,不一定在所有情况下都是这样。
比如String str = “hello”+ "world"的效率就比 StringBuilder st = new StringBuilder().append(“hello”).append(“world”)要高。
对于三者使用的总结:
String a = "hello2";
String b = "hello";
String c = b + 2;
System.out.println((a == c));
输出结果为:false。由于有符号引用的存在,所以 String c = b + 2;不会在编译期间被优化,不会把b+2当做字面常量来处理的
String a = "hello2";
final String b = "hello";
String c = b + 2;
System.out.println((a == c));
输出结果为:true。对于被final修饰的变量,会在class文件常量池中保存一个副本,也就是说不会通过连接而进行访问
java 8 为char[] 数组
扩容的大小是新字符串的长度的2倍,然后再加上2。
在使用StringBuilder的时候,append()之后,我们一般会在后面在加上一个分隔符,例如逗号,也就是再加上一个char,而char在java中占2个字节,避免了因为添加分隔符而再次引起扩容。
final StringBuilder stringBuilder = new StringBuilder("123");
System.out.println(stringBuilder);
stringBuilder.append(11);
System.out.println(stringBuilder);
// 报错
// stringBuilder = new StringBuilder("123123");
修饰不同位置的作用:
静态变量:又称为类变量,也就是说这个变量属于类的,类所有的实例都共享静态变量,可以直接通过类名来访问它。静态变量在内存中只存在一份。
针对于静态变量储存在方法区中,而静态对象方法区存储引用,对象放在堆中。
实例变量:每创建一个实例就会产生一个实例变量,它与该实例同生共死。
public class A {
private int x; // 实例变量
private static int y; // 静态变量
}
静态方法:静态方法在类加载的时候就存在了,它不依赖于任何实例。所以静态方法必须有实现,也就是说它不能是抽象方法。
只能访问所属类的静态字段和静态方法,方法中不能有 this 和 super 关键字,这两个关键字与具体对象关联
静态语句块:静态语句块在类初始化时运行一次。
静态内部类:非静态内部类依赖于外部类的实例,也就是说需要先创建外部类实例,才能用这个实例去创建非静态内部类。而静态内部类不需要。
注意这边是内部类
public class OuterClass {
class InnerClass {
}
static class StaticInnerClass {
}
public static void main(String[] args) {
// InnerClass innerClass = new InnerClass(); // 'OuterClass.this' cannot be referenced from a static context
OuterClass outerClass = new OuterClass();
InnerClass innerClass = outerClass.new InnerClass();
StaticInnerClass staticInnerClass = new StaticInnerClass();
}
}
静态导包:在使用静态变量和方法时不用再指明 ClassName。增强可读性。
import static com.xxx.ClassName.*
关于类的初始化顺序:
静态变量和静态语句块优先于实例变量和普通语句块,静态变量和静态语句块的初始化顺序取决于它们在代码中的顺序。
存在继承的情况下,初始化顺序为:
// 1
public static String staticField = "静态变量";
// 2
static {
System.out.println("静态语句块");
}
// 3
public String field = "实例变量";
// 4
{
System.out.println("普通语句块");
}
// 5 最后才是构造函数的初始化。
public InitialOrderTest() {
System.out.println("构造函数");
}
public native int hashCode()
public boolean equals(Object obj)
protected native Object clone() throws CloneNotSupportedException
public String toString()
public final native Class<?> getClass()
protected void finalize() throws Throwable {}
// 线程间协作相关
//Exception in thread "main" java.lang.IllegalMonitorStateException
public final native void notify()
public final native void notifyAll()
public final native void wait(long timeout) throws InterruptedException
public final void wait(long timeout, int nanos) throws InterruptedException
public final void wait() throws InterruptedException
等价与相等:
如何重写:
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
EqualExample that = (EqualExample) o;
if (x != that.x) return false;
if (y != that.y) return false;
return z == that.z;
}
hashCode():hashCode是jdk根据对象的地址或者字符串或者数字算出来的int类型的数值.
重写hashcode方法:
private String name;
private int age;
@Override
public int hashCode() {
int result = 17;
result = result * 31 + name.hashCode();
result = result * 31 + age;
return result;
}
为什么要使用 31 ?
默认返回 对象名@4554617c 这种形式,其中 @ 后面的数值为散列码的无符号十六进制表示。
主要用于对象的拷贝克隆,如果一个对象不实现clone接口方法,默认抛出CloneNotSupportedException
对象的拷贝分为浅拷贝与深拷贝
如果当前线程没有获得一个对象的监听器,调用该方法就会抛出一个IllegalMonitorStateException
{
Test test = new Test();
// 直接调用报错
// test.wait();
synchronized (test) {
test.wait();
}
}
// synchronized 方法不抛异常
synchronized void test() throws InterruptedException {
this.wait();
}
获得当前对象的监听器的方式:
Sychronized
) 实例方法Java 中有三个访问权限修饰符:private、protected 以及 public,
private 和 protected 不能修饰类。
抽象类和普通类最大的区别是,抽象类不能被实例化,只能被继承。
接口是抽象类的延伸,在 Java 8 之前,它可以看成是一个完全抽象的类,也就是说它不能有任何的方法实现。
从设计层面上看
从使用上来看,一个类可以实现多个接口,但是不能继承多个抽象类。
设计上对比:
< ? super Apple>
重写:存在于继承体系中,指子类实现了一个与父类在方法声明上完全相同的一个方法。
重写有以下三个限制:
使用 @Override 注解,可以让编译器帮忙检查是否满足上面的三个限制条件。
存在于同一个类中,指一个方法与已经存在的方法名称上相同,但是参数类型、个数、顺序至少有一个不同。
char->int->long->float->double ->Character -> Serializable -> Object
,基本类型的重载方法会按此优先级寻找对应的方法,若重载的方法参数与调用的方法不一致,则会向父类查找匹配上相同类型的方法。 public static void sayHello(int arg) {
System.out.println("this is int " +arg);
}
public static void sayHello(long arg) {
System.out.println("this is long " +arg);
}
public static void main(String[]args) {
sayHello('a');
}
//output
this is int 97
反射可以提供运行时的类信息,并且这个类可以在运行时才加载进来,甚至在编译时期该类的 .class 不存在也可以加载进来。
Class 和 java.lang.reflect 一起对反射提供了支持,java.lang.reflect 类库主要包含了以下三个类:
反射的优点:
反射的缺点:如果一个功能可以不用反射完成,那么最好就不用。
通过反射创建对象
//获取 Person 类的 Class 对象
Class clazz=Class.forName("reflection.Person");
//使用.newInstane 方法创建对象
Person p=(Person) clazz.newInstance();
//获取构造方法并创建对象
Constructor c=clazz.getDeclaredConstructor(String.class,String.class,int.class);
//创建对象并设置属性
Person p1=(Person) c.newInstance("李四","男",20);
Throwable 可以用来表示任何可以作为异常抛出的类,分为两种: Error 和 Exception。
其中 Error 用来表示 JVM 无法处理的错误,
Exception 分为两种:
RuntimeException是一种Unchecked
Exception,即表示编译器不会检查程序是否对RuntimeException作了处理,在程序中不必捕获RuntimException类型的异常,也不必在方法体声明抛出RuntimeException类。一般来说,RuntimeException发生的时候,表示程序中出现了编程错误,所以应该找出错误修改程序,而不是去捕获RuntimeException。
常见RuntimeException异常:NullPointException、ClassCastException、IllegalArgumentException、IndexOutOfBoundException
try语句return问题:如果try语句里有return,返回的是try语句块中变量值。详细执行过程如下:
泛型的本质是参数化类型,也就是所操作的数据类型被指定为一个参数。
泛型有三种常用的使用方式:泛型类,泛型接口和泛型方法。
限定通配符和非限定通配符
extends T>
它通过确保类型必须是T的子类来设定类型的上界 super T>
它通过确保类型必须是T的父类来设定类型的下界类型擦除: Java的泛型基本上都是在编译器这个层次上实现的,在生成的字节码中是不包含泛型中的类型信息的,使用泛型的时候加上类型参数,在编译器编译的时候会去掉,这个过程成为类型擦除。
List
和List
等类型,在编译后都会变成List,JVM看到的只是List,而由泛型附加的类型信息对JVM是看不到的。泛型擦除的例子: 本应该只能储存Integer,在通过反射调用方法时,却可以添加String数据
public static void main(String[] args) throws Exception {
ArrayList<Integer> list = new ArrayList<Integer>();
list.add(1); //这样调用 add 方法只能存储整形,因为泛型类型的实例为 Integer
list.getClass().getMethod("add", Object.class).invoke(list, "asd");
for (int i = 0; i < list.size(); i++) {
System.out.println(list.get(i));
}
}
// output
//1
//asd
类型擦除后保留的原始类型:在调用泛型方法时,可以指定泛型,也可以不指定泛型。
Number f = Test.add(1, 1.2); //这两个参数一个是Integer,另一个是Float,所以取同一父类的最小级,为Number
Object o = Test.add(1, "asd"); //这两个参数一个是Integer,另一个是String,所以取同一父类的最小级,为Object
//这是一个简单的泛型方法
public static <T> T add(T x,T y){
return y;
}
泛型使用的一个例子
public class Box<T> {
// T stands for "Type"
private T t;
public void set(T t) { this.t = t; }
public T get() { return t; }
public <E> E get(E param) {
// do some logic
return (E)obj;
}
}
Java不能实现真正的泛型,只能使用类型擦除来实现伪泛型,这样虽然不会有类型膨胀问题,但是也引起来许多新问题
Java 定义了一套注解,共有 7 个,3 个在 java.lang 中,剩下 4 个在 java.lang.annotation 中。
作用在代码的注解:
@Override
- 检查该方法是否是重写方法。如果发现其父类,或者是引用的接口中并没有该方法时,会报编译错误。@Deprecated
- 标记过时方法。如果使用该方法,会报编译警告。@SuppressWarnings
- 指示编译器去忽略注解中声明的警告。作用在其他注解的注解(或者说元注解)是:
@Retention
- 标识这个注解怎么保存,是只在代码中,还是编入class文件中,或者是在运行时可以通过反射访问。@Documented
- 标记这些注解是否包含在用户文档中。@Target
- 标记这个注解应该是哪种 Java 成员。从 Java 7 开始,额外添加了 3 个注解:
@SafeVarargs
- Java 7 开始支持,忽略任何使用参数为泛型变量的方法或构造函数调用产生的警告。@FunctionalInterface
- Java 8 开始支持,标识一个匿名函数或函数式接口。@Repeatable
- Java 8 开始支持,标识某注解可以在同一个声明上使用多次。java.lang.annotation 提供了四种元注解,专门注解其他的注解(在自定义注解的时候,需要使用到元注解):
@Documented
:注解是否将包含在JavaDoc中@Retention
:什么时候使用该注解
RetentionPolicy.SOURCE
: 在编译阶段丢弃。这些注解在编译结束之后就不再有任何意义,所以它们不会写入字节码。@Override, @SuppressWarnings都属于这类注解。RetentionPolicy.CLASS
: 在类加载的时候丢弃。在字节码文件的处理中有用。注解默认使用这种方式RetentionPolicy.RUNTIME
: 始终不会丢弃,运行期也保留该注解,因此可以使用反射机制读取该注解的信息。我们自定义的注解通常使用这种方式。@Target
:注解用于什么地方
ElementType.CONSTRUCTOR
: 用于描述构造器ElementType.FIELD
: 成员变量、对象、属性(包括enum实例)ElementType.LOCAL_VARIABLE
: 用于描述局部变量ElementType.METHOD
: 用于描述方法ElementType.PACKAGE
: 用于描述包ElementType.PARAMETER
: 用于描述参数ElementType.TYPE
: 用于描述类、接口(包括注解类型) 或enum声明 常见的@Component、@Service@Inherited
– 是否允许子类继承该注解
@Inherited
元注解是一个标记注解,@Inherited
阐述了某个被标注的类型是被继承的。如果一个使用了@Inherited 修饰的annotation 类型被用于一个class,则这个annotation
将被用于该class 的子类
编写注解的规则
@interface。
@Target(FIELD)
@Retention(RUNTIME)
@Documented
public @interface FruitName {
String value() default "";
}
阻塞和等待的区别在于,阻塞是被动的,它是在等待获取 monitor lock。而等待是主动的,通过调用 Object.wait() 等方法进入。
进入方法 | 退出方法 |
---|---|
没有设置 Timeout 参数的 Object.wait() 方法 | Object.notify() / Object.notifyAll() |
没有设置 Timeout 参数的 Thread.join() 方法 | 被调用的线程执行完毕 |
LockSupport.park() 方法 | LockSupport.unpark(Thread) |
调用 Thread.sleep() 方法使线程进入限期等待状态时,常常用“使一个线程睡眠”进行描述。调用 Object.wait() 方法使线程进入限期等待或者无限期等待时,常常用“挂起一个线程”进行描述。睡眠和挂起是用来描述行为,而阻塞和等待用来描述状态。
进入方法 | 退出方法 |
---|---|
Thread.sleep() 方法 | 时间结束 |
设置了 Timeout 参数的 Object.wait() 方法 | 时间结束 / Object.notify() / Object.notifyAll() |
设置了 Timeout 参数的 Thread.join() 方法 | 时间结束 / 被调用的线程执行完毕 |
LockSupport.parkNanos() 方法 | LockSupport.unpark(Thread) |
LockSupport.parkUntil() 方法 | LockSupport.unpark(Thread) |
JVM 在背后帮我们做了哪些事情:
枚举类比较使用==,同样也可以使用equals方法,Enum类中重写了equals实际上还是调用==方法。
/**
* Returns true if the specified object is equal to this
* enum constant.
*
* @param other the object to be compared for equality with this object.
* @return true if the specified object is equal to this
* enum constant.
*/
public final boolean equals(Object other) {
return this==other;
}
为什么使用==比较?
因为枚举类在jvm编译成class文件后,实际编译成使用final 修饰的class,final修饰就意味着实例化后不可修改,且都指向堆中的同一个对象
普通的一个枚举类
public enum t {
SPRING,SUMMER,AUTUMN,WINTER;
}
反编译后的代码
public final class T extends Enum
{
private T(String s, int i)
{
super(s, i);
}
public static T[] values()
{
T at[];
int i;
T at1[];
System.arraycopy(at = ENUM$VALUES, 0, at1 = new T[i = at.length], 0, i);
return at1;
}
public static T valueOf(String s)
{
return (T)Enum.valueOf(demo/T, s);
}
public static final T SPRING;
public static final T SUMMER;
public static final T AUTUMN;
public static final T WINTER;
private static final T ENUM$VALUES[];
static
{
SPRING = new T("SPRING", 0);
SUMMER = new T("SUMMER", 1);
AUTUMN = new T("AUTUMN", 2);
WINTER = new T("WINTER", 3);
ENUM$VALUES = (new T[] {
SPRING, SUMMER, AUTUMN, WINTER
});
}
}
public static void main(String[] args) {
// TODO Auto-generated method stub
Student s1 = new Student("小张");
Student s2 = new Student("小李");
Test.swap(s1, s2);
System.out.println("s1:" + s1.getName());
System.out.println("s2:" + s2.getName());
}
public static void swap(Student x, Student y) {
Student temp = x;
x = y;
y = temp;
System.out.println("x:" + x.getName());
System.out.println("y:" + y.getName());
}
// output
x:小李
y:小张
s1:小张
s2:小李
transient
关键字的作用是:阻止实例中那些用此关键字修饰的的变量序列化;当对象被反序列化时,被 transient 修饰的变量值不会被持久化和恢复。transient 只能修饰变量,不能修饰类和方法。
序列化ID:serialVersionUID
决定着是否能够成功反序列化!简单来说,java的序列化机制是通过在运行时判断类的serialVersionUID来验证版本一致性的。
private static final long serialVersionUID
//通过 Scanner
Scanner input = new Scanner(System.in);
String s = input.nextLine();
input.close();
方法 2:通过 BufferedReader
BufferedReader input = new BufferedReader(new InputStreamReader(System.in));
String s = input.readLine();
Arrays.asList(): 返回的并不是 java.util.ArrayList ,而是 java.util.Arrays 的一个内部类,这个内部类并没有实现集合的add()、remove()、clear()
会抛出异常unSupportedOperationException。
对于基本类型,直接赋值复制,对于对象类型分为浅拷贝与深拷贝
深拷贝的另一种方式,使用序列化和反序列化,获取一个新对象。