基本数据类型

8种基本数据类型及包装类

  • byte(8位、1字节)Byte
  • char(16位、2字节)Character
  • short(16位、2字节)Short
  • int(32位、4字节)Integer
  • long(64位、8字节)Long
  • float(32位、4字节)Float
  • double(64位、8字节)Double
  • boolean(true、false)Boolean

包装类的装箱与拆箱

1
2
3
4
// 装箱 Integer a = Integer.valueOf(10);
Integer a = 10;
// 拆箱 int b = a.intValue();
int b = a;

缓存池

Integer装箱源码,如果缓存池已存在数值,那么就直接返回该对象,不用创建一个新对象。

1
2
3
4
5
public static Integer valueOf(int i) {
if (i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)];
return new Integer(i);
}

所以以下结果是true,j是从缓存池中取的相同对象

1
2
3
Integer i = 1;
Integer j = 1;
System.out.println(i.equals(j));

缓存池是有范围的,如Integer是-128~127,范围外创建就是两个对象,不会从常量池拿取。注意float和double型是没有缓存池的

引用数据类型的浅拷贝与深拷贝

今天做唯品会笔试时,发现set复制出了问题,兜兜转转找到了问题的根源,引用数据类型以及深浅拷贝的问题。于是便补上了这一块的笔记。

首先Java分为基础数据类型和引用数据类型,这个我们都知道。对于八大基础数据类型,没有深浅拷贝的说法。我们主要是讨论引用数据类型。

  • 浅拷贝

    简单来说,浅拷贝就是将两个引用指向同一个对象。也就是使用 “=” 进行引用传递,引用数据类型存储的是一指向对象的地址。

    我们这里set和temp就是使用 “=” 进行浅拷贝,由temp对集合进行清空操作,set和temp指向的是同一对象,所以二者都为空。

    1
    2
    3
    4
    5
    6
    7
    Set<Character> set = new HashSet<>();
    Set<Character> temp = new HashSet<>();
    temp = set;
    System.out.println(temp == set); //true
    temp.clear();
    System.out.println(temp.isEmpty()); //true
    System.out.println(set.isEmpty()); //true
  • 深拷贝

    和浅拷贝相反,深拷贝就是创建一个新的对象,将原内容进行复制,仅仅是内容相同,但是是两个对象。对于容器来说可以使用 addAll() 或 putAll() 来进行深拷贝。

    这里set和temp是两个不同的对象,只是内容一致,也就是说这两个引用指向了两个不同的地址,但地址的内容一样。我们通常使用深拷贝对容器进行复制操作。

    1
    2
    3
    4
    5
    6
    7
    8
    Set<Character> set = new HashSet<>();
    Set<Character> temp = new HashSet<>();
    set.add('s');
    temp.addAll(set);
    System.out.println(temp == set); //false
    temp.clear();
    System.out.println(temp.isEmpty()); //true
    System.out.println(set.isEmpty()); //false

==与equals的区别

  • ==

    • 基本数据类型:比较值是否相等
    • 引用类型:比较变量是否引用同一个对象,也就是比较引用变量的值,因为变量存储的是对象的地址
  • equals()

    • 基本数据类型:没有equals()方法
    • 引用类型:根据当前类是否重写equals()方法
      • 类没有重写equals()方法:此时使用equals()相当于使用 == ,比较两个变量引用的对象地址,此时使用的equals()是Object类的方法
      • 类重写了equals()方法:此时equasl()按其重写方法的具体内容执行操作,一般重写都是为了比较引用对象的值是否相等,Integer、String等类都对equals()进行过重写,所以它们的equals()可比较值相等

a、b是直接创建一个abc的新对象,==不等、equals相等。

aa创建一个新对象,且放到了字符串的常量池(类似包装类的缓存池),所以bb创建时直接从常量池拿对象,也就是aa和bb是完全相同的对象,==、equals都相等。

1
2
3
4
5
6
7
8
9
10
11
String a = new String("abc");
String b = new String("abc");
String aa = "abc";
String bb = "abc";
System.out.println(a == b);//flase
System.out.println(a == aa);//flase
System.out.println(aa == bb);//true

System.out.println(a.equals(b));//true
System.out.println(a.equals(aa));//true
System.out.println(aa.equals(bb));//true

hashCode()与equals()

hashCode()即获得散列值,其定义在Object类中,这代表Java中所有类都包含hashCode()。两个对象等价,其hashcode一定相等,而hashcode相等的两个对象不一定等价,这是因为散列值会发生哈希冲突,使得两个不同的对象有相同的散列值。

而我们重写equals()方法,其hashCode()也要重写,因为两个对象等价,其散列值一定相等。例如我们使用HashSet存储对象时,会先通过散列值判断,若没有相同的散列值则加入对象,有相等的值则用equals再次判断,确定是否是相同的对象。

所以HashSet是先通过hashcode判断对象,再用equals判断对象,如果我们重写equals()时不重写hashCode(),则可能将两个相同的对象存放到HashSet中。而在HashSet存储中使用hashCode()还可以减少equals的判断次数,提高了执行效率,为此我们重写equals()时hashCode()也要重写,为了保证相同的对象有相同的散列值。

泛型擦除

Java只在编译阶段检测非法类型,实际执行时不检测,也就是在编译阶段就将泛型的类型擦除了。如下面的例子,list是存储Integer型的,test被list赋值也应该存储Integer的,但test添加字符串却没有问题,因为他虽然是存储Integer,但在运行时,list和test都会进行泛型擦除,视为ArrayList。可以说泛型只是表面进行限制,实际执行的时候没有用。

list添加字符串是一定不行的,因为表面上它就是用来存储Integer型的,但test虽然等于list,逻辑上是用来存储Integer的,但看上去它没有受泛型限制,所以它可以添加字符串。泛型只是在编译阶段进行限制,也就是表面限制,在实际执行阶段list、test都会被视为ArrayList。

1
2
3
List<Integer> list = new ArrayList<>();
List test = list;
test.add("aaa");

重载与重写

重载

重载发生在同一个类中,多个方法名相同的方法,但参数数量、参数顺序、参数类型至少有一个不同。多用于构造器重载。

注意方法返回值类型可以不同,但它不是判断重载的标准,也就是说参数没有改变时,只有返回值类型不同则不是重载。

由于子类继承父类,其父类方法在子类中全部实现,此时我们在对方法重载相当于在子类中重载。也就是同一个类,并没有子类对父类重载。

重写

重写是子类对父类进行操作,方法名、参数必须完全相同,访问权限>=父类,返回值类型和抛出异常可以是父类返回类型或其子类型。

父类是private/final/static修饰,其子类不能重写。子类同名方法相当于定义了一个新方法。

构造方法也不能被子类重写。

String

String的特点

String被声明为final,String对象不可变、不可继承。

1
private final char value[];

String不可变性,使其天生具有线程安全性。

String、StringBuffer、StringBuilder

  • String不可变,线程安全,每次发生改变都会新生成一个对象。操作少量数据可用。
  • StringBuffer可变,其内部方法加了同步锁,是线程安全的,改变对象是对该对象本身操作。多线程操作字符串可用。
  • StringBuilder可变,不是线程安全。单线程操作字符串可用。

字符串常量池(String Pool)

1
2
3
4
5
6
7
8
9
//a != b,这是两个对象
String a = new String("aaa");
String b = new String("aaa");
//c == d,
String c = a.intern();
String d = b.intern();
// c == d == e == f
String e = "aaa";
String f = "aaa";

这里new String是直接创建一个新对象,即a、b是两个不同对象,但值相等。

使用intern()方法会将对应字符串的值存入常量池,然后当常量池存在值相等的字符串时,直接返回常量池中的引用,所以c、d都是aaa字符串在常量池中的引用,所以是相同对象。

e、f就不用说了,会直接引用常量池,没有则新建对象,并放入常量池。

关键字

final

  • final修饰的类不可继承,final类中所有成员方法被隐式指定为final方法
  • final修饰方法不可重写
  • final修饰的变量是常量
    • 基础数据类型,数值在初始化后不可更改
    • 引用类型,初始化后不可指向另一个对象

static

  • 静态变量(类变量),静态方法

    被static修饰的成员属于类,不单属于类的某个对象,被类中所有对象共享。可通过类名直接调用。

  • 静态代码块:在类初始化时运行一次。该类无论创建多少次,静态代码块只执行一次。

  • 静态内部类

  • 静态导包:import static,可导入某个类的指定静态资源。

this

用于引用类的当前实例,指向本类对象。

super

子类访问父类的变量和方法,指向父类对象。

方法初始化顺序

  • 父类(静态变量、静态语句块)
  • 子类(静态变量、静态语句块)
  • main方法
  • 父类(实例变量、普通语句块)
  • 父类(构造函数)
  • 子类(实例变量、普通语句块)
  • 子类(构造函数)

父 > 子,静态最优先

这里看一个特殊例子,我们声明一个全局静态对象,它在调用构造方法时会先执行普通方法块,但静态变量以及静态方法块相互不影响,只有按序走到对应静态变量或静态方法块时,这些静态才会执行,且只执行一次。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class A {
static A a1 = new A();
static A a2 = new A();
{
System.out.println("11111111");
}
static {
System.out.println("222222222");
}
public static void main(String[] args) {
A a2 = new A();
}
}

/* 输出
11111111
11111111
222222222
11111111
*/

面向对象

面向对象与面向过程

  • 面向过程:过程最重要,需要顺序执行按部就班,当性能是主要考量因素时使用,一般都是编译成机械码后运行,所以效率高。主要是后期不好维护,难以重复利用。

  • 面向对象:有封装、继承、多态的特性,易维护、易复用、易扩展,能降低耦合性,但性能相对于面向过程可能低一些。

成员变量与局部变量

  • 成员变量(类):可以被public、private、static、final等修饰,没有赋初值会自动以默认值赋值。
  • 局部变量(方法、代码块):一般不可被修饰符修饰,但final可以。没有赋初值会报错,局部变量不会以默认值自动赋值。

面向对象三大特征

  • 封装

    实体类的封装,例如项目中封装数据库表的字段成一个对象,然后对它进行赋值、取值操作。

  • 继承

    子类继承父类,提高代码的复用性,子类可以包括父类所有属性和方法(包括私有),但私有属性和方法是不能访问的,只是拥有

  • 多态(向上转型)

    简单说一个对象有多种状态。

    1
    2
    3
    //列表可以是数组或链表两种实现
    List list = new ArrayList();
    List list = new LinkedList();

    要有继承或实现的关系,子类对父类方法进行了重写,以实现多态。

反射(框架中大量使用反射机制)

通过forName、.class等方式获取类,然后再通过操作Field、Method、Constructor对类的元素、方法等进行操作。

异常(Throwable)

Throwable类是所有异常的共同祖先,其有两个重要子类Exception(异常)和Error(错误)。其中异常可以被try-catch捕获,错误无法处理。

21.png

  • Exception:程序可处理的异常,通过catch进行捕获

    • check可查异常:在源码中必须显性进行捕获处理,会在编译器进行检查。

      例:ClassNotFoundException、SQLException等

    • uncheck不可查异常:运行时异常(RuntimeException),通常是代码的逻辑错误,根据需求进行捕获,并不在编译期强制要求。

      例:NullPointerException、ArrayIndexOutOfBoundsException等

  • Error:程序无法处理的错误,如JVM运行错误、JVM内存不够等错误,该异常发生时,JVM会将线程终止。

I/O流

序列化(serialization)

  • 序列化:将Java对象转换为二进制字节流的过程。
  • 反序列化:将序列化生成的二进制字节流转换成Java对象的过程。

在Java中序列化,是对class对象进行操作,而在C++中,结构体定义的数据结构和class对应的对象都可以序列化。

transient

使用transient关键字,阻止被修饰的变量进行序列化,对象被反序列化时,被修饰的变量不会被持久化和恢复。

  • transient只能修饰变量,不能修饰方法、类。
  • transient修饰的变量,反序列化后其变量值置为类型默认值。
  • static变量不会序列化。因为static不属于任何对象,只属于类本身。

键盘输入

  • Scanner

    1
    2
    3
    Scanner input = new Scanner(System.in);
    String s = input.nextLine();
    input.close();
  • BufferedReader(更快)

    1
    2
    BufferedReader input = new BufferedReader(new InputStreamReader(System.in));
    String s = input.readLine();

IO流

  • 输入流、输出流
  • 字节流、字符流(有字符流更加方便我们对一些文件字符的操作,并非使用字节处理是最好的,分情况)
  • 节点流、处理流

22.png

23.png