基础类型 | 大小(字节) | 包装类型 |
---|---|---|
boolean | 1 | Boolean |
byte | 1 | Byte |
char | 2 | Character |
short | 2 | Short |
int | 4 | Integer |
float | 4 | Float |
long | 8 | Long |
double | 8 | Double |
基本类型与包装类型之间的赋值,通过自动装箱和自动拆箱完成
Byte、Character、Integer、Long、Short包装类型中存在数据缓存,调用吧valueOf方法或者自动装箱时,会先去缓存中查询该数据是否存在,存在则返回缓存数据
以Integer为例,Integer中有一个内部类IntegerCache,里面设置了缓存的数据范围是[-128,127],建立了一个cache数组,并在静态代码块中初始化了该数组
private static class IntegerCache {
static final int low = -128;
static final int high;
static final Integer cache[];
static {
// high value may be configured by property
int h = 127;
String integerCacheHighPropValue =
sun.misc.VM.getSavedProperty("java.lang.Integer.IntegerCache.high");
if (integerCacheHighPropValue != null) {
try {
int i = parseInt(integerCacheHighPropValue);
i = Math.max(i, 127);
// Maximum array size is Integer.MAX_VALUE
h = Math.min(i, Integer.MAX_VALUE - (-low) -1);
} catch( NumberFormatException nfe) {
// If the property cannot be parsed into an int, ignore it.
}
}
high = h;
cache = new Integer[(high - low) + 1];
int j = low;
for(int k = 0; k < cache.length; k++)
cache[k] = new Integer(j++);
// range [-128, 127] must be interned (JLS7 5.1.7)
assert IntegerCache.high >= 127;
}
private IntegerCache() {}
}
当我们调用valueOf方法时,会先到缓存的数组中查询数据,查询不到再创建
public static Integer valueOf(int i) {
if (i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)];
return new Integer(i);
}
String内部使用了一个char数组来存放数据,并且该数组被声明为final,即String被声明后就不能改变他的值了
String的hashCode根据值来计算,而String的值又是不能改变的,所以hashCode也是不变的,hash值不变的话有以下几个优点
可变性
线程安全
StringBuilder和StringBuffer可变是因为,在他们的父类AbstractStringBuilder中,char数组不是final声明的,并且提供了对这个数组的修改方法
/**
* The value is used for character storage.
*/
char[] value;
/**
* The count is the number of characters used.
*/
int count;
以StringBuilder为例
append方法中,传递一个字符串进去,会调用该字符串的getChars方法,并将当前StringBuilder的字符数组,传递进去
public AbstractStringBuilder append(String str) {
if (str == null)
return appendNull();
int len = str.length();
ensureCapacityInternal(count + len);
str.getChars(0, len, value, count);
count += len;
return this;
}
在getChars方法中,将当前字符串的字符数组的值通过System.arraycopy方法复制到StringBuilder传递的dst[]中,以此实现了StringBuilder的可变性
public void getChars(int srcBegin, int srcEnd, char dst[], int dstBegin) {
if (srcBegin < 0) {
throw new StringIndexOutOfBoundsException(srcBegin);
}
if (srcEnd > value.length) {
throw new StringIndexOutOfBoundsException(srcEnd);
}
if (srcBegin > srcEnd) {
throw new StringIndexOutOfBoundsException(srcEnd - srcBegin);
}
System.arraycopy(value, srcBegin, dst, dstBegin, srcEnd - srcBegin);
}
通过new创建的字符串不会出现在字符串常量池中,通过=创建的字符串会到字符串池中查询,如果没有就默认加入到常量池中,有的话,就直接使用该字符串的引用;
intern()方法会判断字符串是否在字符串池中,如果存在则返回该字符串的引用,如果不存在,会将字符串加入到字符串池中并返回应用;
下述代码中,str1通过new创建,值为aaa,str2和str3都是通过=创建,所以str1==str2返回false,而str3==str2返回true,此时调用str1的intern方法,在字符串池中查询到了该字符串,则直接返回了这个字符串的引用,所以str2==str4返回true,但是对str1本身是没有任何影响的,str1任然指向自己创建的引用,所以str1==str4返回false;
String str1 = new String("aaa");
String str2 = "aaa";
String str3 = "aaa";
System.out.println(str1 == str2); // false
System.out.println(str3 == str2); // true
String str4 = str1.intern();
System.out.println(str2 == str4); // true
System.out.println(str1 == str4); // false
声名一个类,里面有属性name
static class Dog {
private String name = "dog";
public Dog(){}
public String setName(String name){
return this.name = name;
}
public String getName(){
return this.name;
}
}
声明一个方法,参数是一个int整数和Dog类
public static void testParam(int a,Dog dog){
a = 100;
dog.setName("1");
}
在main方法中调用testParam方法
public static void main(String[] args) {
Dog dog = new Dog();
dog.setName("马");
int a = 10;
TestString.testParam(a,dog);
System.out.println(a); // 输出10
System.out.println(dog.getName()); // 输出马
}
在调用testParam时,传递的int a是传递的值10,在testParam中改变a的值为100,再main方法中输出,值仍然为10,而dog的name在传参时,传递的是dog变量的引用地址,修改了dog的属性值,但是dog的引用地址仍然不变。
Java 不能隐式执行向下转型,因为这会使得精度降低。例如double不能转成float,float也不能转成int。
java有以下几个权限访问修饰符,private、protected、public,如果不见修饰符(default)表示包级可见,权限从大到小依次为public>protected>default>private
如果子类重写了父类的方法,则子类中该方法的访问级别不能低于父类,一次满足里氏替换原则。
抽象类及内部的抽象方法使用abstract关键字声明,抽象类不能被实例化。
@Data
public abstract class Animal {
protected String name;
protected abstract void eat();
}
public class Cat extends Animal{
@Override
protected void eat() {
System.out.println(super.getName());
}
}
注意上述嗲马中,eat方法的权限为protected,那么在子类中,该方法的权限就不能比protected低
接口是抽象类的延申,在java8之前,接口中不允许有方法实现,但是从java8开始,接口也可以有默认的方法实现;
接口的字段和方法默认都是public,并且不允许定义为private或protected,接口的字段默认都是static和final的;
在实现类中,通过this即可访问接口定义的属性和默认方法
public interface EatInterface {
public abstract void takeFood(); // Modifier 'abstract' is redundant for interface methods
default void eat(){
System.out.println("干饭");
}
public static final int x = 100; //Modifier 'public|、static、final' is redundant for interface fields
int y = 100;
}
public class Eat implements EatInterface{
@Override
public void takeFood() {
eat();
System.out.println(this.x+this.y);
}
}
以下场景考虑使用抽象类
以下场景考虑使用接口
内部的几个重要方法
public final native Class<?> getClass();
public native int hashCode();
public boolean equals(Object obj);
protected native Object clone() throws CloneNotSupportedException;
public String toString();
public final native void notify();
public final native void notifyAll();
public final native void wait(long timeout) throws InterruptedException;
protected void finalize() throws Throwable { }
equals方法满足自反性、传递性、对成性、一致性,不是null的对象调用x.equals(null)均返回false
==在比较基本数据类型时,比较的时数据的值,在比较对象时,则比较对象的引用地址是否相同;
equals()比较的时对象所指向的地址值,equals方法可以重写;
hashCode() 返回散列值,而 equals() 是用来判断两个对象是否等价。等价的两个对象散列值一定相同,但是散列值相同的两个对象不一定等价。在覆盖 equals() 方法时应当总是覆盖 hashCode() 方法,保证等价的两个对象散列值也相等。
默认实现时返回了类名+hashCode值
clone方法在Object中使用了protected标识,在子类中,并需要显示重写该方法,一个类如果要调用clone方法,则必须实现Cloneable接口
public class CloneExample implements Cloneable{
public CloneExample clone1() throws CloneNotSupportedException {
return (CloneExample)super.clone();
}
}
public static void main(String[] args) throws CloneNotSupportedException {
CloneExample cloneExample1 = new CloneExample();
CloneExample cloneExample2 = cloneExample1.clone1();
}
原对象创建了一个新的对象,并把自身的属性的引用地址拷贝给新的对象
下述代码中,创建一个qh学校,qh里面有个学生叫龙,年龄17,然后对qh进行一个拷贝,建立一个bd学校,此时,修改qh中的哪个学生,年龄改为99,再去bd查询学生的年龄,发现也变成了99,然后通过==比较对象的地址,qh==bd返回false,而比较内部的对象则返回了一个true
public class School implements Cloneable {
private Student student;
public School(){
student = new Student();
student.setName("龙");
student.setAge(17);
}
public Student getStudent() {
return student;
}
@Override
protected School clone() throws CloneNotSupportedException {
return (School) super.clone();
}
}
public class Student {
private String name;
private Integer age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
}
public static void main(String[] args) throws CloneNotSupportedException {
School qh = new School();
School bd = qh.clone();
qh.getStudent().setAge(99);
System.out.println(bd.getStudent().getAge()); // 99
System.out.println(qh.getStudent() == bd.getStudent()); // true
System.out.println(qh == bd); // false
}
将拷贝对象以及它的属性都重新创建,重写clone方法,并在clone方法中重新创建即可
final可以声明一个常量数据,声明后不能修改该数据
添加了final的方法无法被子类重写,private方法被隐式声明了这是一个final方法
这个类不能被继承
静态变量,通过类名.属性名访问
public class Student {
static String SCHOOL_NAME="昆明理工大学";
}
修饰静态方法,通过类名.方法名访问
public class Student {
static void doSomething(){
System.out.println("你好");
}
}
修饰静态代码块,类初始化时运行一次
public class Student {
static{
System.out.println("你好");
}
}
修饰静态内部类,创建时通过new 外部类.静态内部类实现,静态内部类不能访问外部类的非静态的变量和方法
public class OutClass {
class InnerClass{
}
static class StaticInnerClass{
}
}
public static void main(String[] args){
OutClass outClass = new OutClass();
OutClass.InnerClass innerClass = outClass.new InnerClass();
OutClass.StaticInnerClass staticInnerClass = new OutClass.StaticInnerClass();
}
在使用静态变量和方法时不用再指明 ClassName,但是代码后面维护起来比较麻烦,可读性也比较差
下述代码中,使用了静态导包,导入了Math,访问下面的方法时,就可以不带Math.
import static java.lang.Math.*;
public class Main {
public static void main(String[] args){
System.out.println(Math.random());
System.out.println(random());
}
}
静态变量和静态语句块优先于实例变量和普通语句块,静态变量和静态语句块的初始化顺序取决于它们在代码中的顺序。
Class和java.lang.reflect一起提供了反射功能,java.lang.reflect主要包含以下三个类
具体内容可以参考注解和反射 - 问尤龙の时光 (wenyoulong.com)
Throwable是Java中所有异常(Exception)和错误(Error)的父类,error表示jvm异常,无法被捕获处理,Exception分为以下两类
几个常见的异常和错误
参考以下文档
注解和反射 - 问尤龙の时光 (wenyoulong.com)
自己定义一个注解 - 问尤龙の时光 (wenyoulong.com)
泛型的本质是为了参数化类型,可以减少代码量,实现的代码复用,方便后期维护,
在类名后添加泛型
public class Point {
public static void main(String[] args){
Node<String,Integer> node = new Node<>();
node.put("龙",11);
}
}
class Node<K,V>{
private K key;
private V value;
public void put(K key,V value){
this.key = key;
this.value = value;
}
}
在接口名后定义泛型,并在接口中使用泛型
interface A<T>{
public T getInfo();
}
class AImp<T> implements A<T>{
private T var;
public AImp(T var){
this.setVar(var);
}
public void setVar(T var){
this.var = var;
}
public T getVar(){
return this.var;
}
@Override
public T getInfo() {
return this.getVar();
}
}
public class Info{
public static void main(String[] args){
A<String> a = new AImp<>("龙");
System.out.println(a.getInfo());
}
}
泛型也可以只用在方法上,用法参考
示例代码
interface A<T>{
public T getInfo();
}
class AImp<T> implements A<T>{
private T var;
public AImp(T var){
this.setVar(var);
}
public void setVar(T var){
this.var = var;
}
public T getVar(){
return this.var;
}
@Override
public T getInfo() {
return this.getVar();
}
}
class Fun{
public <T,Y> void fun(A<T> a,A<Y> b){
System.out.println(a.getInfo()+" "+b.getInfo());
}
}
public class Info{
public static void main(String[] args){
A<String> a = new AImp<>("龙");
A<Integer> b = new AImp<>(11);
Fun fun = new Fun();
fun.fun(a,b);
}
}
在使用泛型的时候,我们可以为传入的泛型类型实参进行上下边界的限制,如:类型实参只准传入某种类型的父类或某种类型的子类。
通过extends指定泛型的只能为指定类型的子类
下述代码指定了传入的泛型只能是Number及其子类,传入Strng则报错
class Fun<T extends Number>{
private T var;
}
public class Info{
public static void main(String[] args){
Fun<Float> floatFunc = new Fun<>();
// Fun<String> stringFun = new Fun<>(); Type parameter 'java.lang.String' is not within its bound; should extend 'java.lang.Number'
}
}
通过super指定传入的泛型只能是指定类型的父类
下述代码指定了传入的泛型只能是String及其父类,传入Integer则报错
class Fun1<T>{
private T var;
}
}
public class Info{
public static void main(String[] args){
// Fun1<Float> floatFunc = new Fun1<>();
// fun(floatFunc); 报错
Fun1<String> stringFun1 = new Fun1<>();
fun(stringFun1);
}
public static void fun(Fun1<? super String> temp){
System.out.print(temp + ", ") ;
}
}
创建泛型数组使用 java.lang.reflect.Array.newInstance(Class<T> componentType, int length)
class ArrayWithType<T> {
private T[] array;
public ArrayWithType(Class<T> type, int size) {
array = (T[]) Array.newInstance(type, size);
}
public T[] create() {
return array;
}
}
public static void main(String[] args){
ArrayWithType<String> stringArrayWithType = new ArrayWithType<>(String.class,10);
String[] strings = stringArrayWithType.create();
System.out.println(strings.toString());
}
所有泛型类的类型参数在编译时都会被擦除,泛型类会被替换成具体的类,虚拟机运行时中没有泛型,只有普通类和普通方法。
原始类型就是擦除去了泛型信息,最后在字节码中的类型变量的真正类型,没有上下限的泛型,都会被替换成Object
class A<T> {
private T value;
}
上述代码替换后就变成了
class A {
private Object value;
}
如果使用了限制泛型,那原始类型就是限制的泛型
class A<T extends Number>{
private T value;
}
class A{
private Number value;
}