JAVA基础回顾
一,基础语法
(一)开发环境搭建
1.JDK,JRE,JVM的关系
2.编译,运行命令
- 编译语法:javac 源文件名.java
- 运行语法:java 类名
javac HelloWorld.java //编译
java HelloWorld //运行
3.常用的windows命令
(二)变量的使用
1.数据类型分类
2.各个类型具体说明
- 整型:byte(1个字节,8bit),short(2个字节),int(4个字节),long(8个字节)。
- 声明long型变量。必须以l或者L结尾。
- 整型的常量,默认类型是:int型。
- 浮点型:float(4个字节),double(8个字节)
- float表示数值的范围比long还要大。
- 浮点型的常量,默认类型是:double。
- 字符型:char(1字符,2个字节)。
- 布尔型:boolean(1bit)。
- 与C语言不同的是:long类型C语言为4个字节,Java为8个字节,char类型C语言为1个字节,Java为两个字节,C语言中并没有布尔型变量。
3.自动类型转换
byte,char,short --> int --> long --> float --> double
- 当容量小的数据类型的变量与容量大的数据类型的变量做运算时,结果自动提升为容量大的数据类型。
- 特别的:当byte、char、short三种类型的变量做运算时,结果为int型
- 容量大小指的是:表示数的范围的大和小。比如:float容量要大于long的容量。
4.强制类型转换
- 强制类型转换可能会精度丢失。
(三)进制转换
- 所有的数值,不管正负,底层都以补码的方式存储。
- 正数的原码,反码和补码形式都一致。
- 负数的原码:符号位为1,其余位为该负数绝对值的原码。负数的反码:除符号位之外,所有位取反。负数的补码:反码加1。
(四)数组
- 数组加载内存分析:
@Test
public void fun2(){
int [] arr1 = new int[3]; // 动态初始化:默认值为0
int [] arr2 = new int[]{1,2,3}; // 静态初始化
System.out.println(Arrays.toString(arr1)); // [0, 0, 0]
System.out.println(Arrays.toString(arr2)); // [1, 2, 3]
String [] arr3 = new String[4];
arr3[0] = "刘德华";
arr3[1] = new String("刘德华");
arr3[2] = "刘德华";
System.out.println(Arrays.toString(arr3)); // [刘德华, 刘德华, 刘德华, null]
System.out.println(arr3[0] == arr3[1]); // false
System.out.println(arr3[0] == arr3[2]); // true
arr3 = new String[4];
System.out.println(Arrays.toString(arr3)); // [null, null, null, null]
}
二,面向对象
(一)属性和局部变量
1.属性和局部变量的不同点
-
在类中声明的位置的不同,属性直接定义在类的{}内,局部变量定义在方法内、方法形参、代码块内、构造器形参、构造器内部的变量。
-
权限修饰符的不同,属性可以指明其权限,局部变量不可以使用权限修饰符。
-
默认初始化值的情况,类的属性根据其类型都有默认初始化值,局部变量没有默认初始化值,调用局部变量之前,一定要显式赋值。
-
在内存中加载的位置:非静态属性加载到堆空间中,静态属性加载到方法区,局部变量加载到栈空间。
2.属性赋值的先后顺序
- 默认初始化。
- 显示初始化。
- 代码块中初始化。
- 构造器中赋值。
- 通过“对象.方法” 或 “对象.属性”的方式赋值。
以上赋值顺序:1,2/3,4,5。2和3的先后顺序跟在代码中的先后顺序一样。
(二)方法
1.方法的重载
- “两同一不同”:同一个类、相同方法名,参数列表不同、参数个数不同、参数类型不同。
- 跟方法的权限修饰符、返回值类型、形参变量名、方法体都没有关系。
2.可变个数形参的方法
- 可变个数形参在方法的形参中,必须声明在末尾。
- 可变个数形参在方法的形参中,最多只能声明一个可变形参。
3.值传递机制
- 赋值规则:
- 赋值运算符左侧是基本数据类型:赋值的变量所保存的是数据值。
- 赋值运算符左侧是引用数据类型:赋值的变量所保存的是数据的地址值。
- 参数传递规则:
- 参数是基本数据类型:实参赋给形参的是实参真实存储的数据值。
- 参数是引用数据类型:实参赋给形参的是实参真实存储的数据的地址值。
4.方法的重写
- 重写:子类继承父类后,可以对父类中同名同参数的方法,进行覆盖操作。
- 重写的规定:
- 子类重写的方法名和形参列表与父类被重写的方法名和形参列表相同。
- 子类重写方法的权限修饰符不小于父类被重写方法的权限修饰符。假如父类是public,子类必须是public。子类不能重写父类中private的方法。
- 返回值类型:①父类被重写的方法的返回值类型是void,则子类重写的方法的返回值类型也只能是void。②父类被重写的方法的返回值类型是A类型,则子类重写的方法的返回值类型可以是A类或A类的子类。③父类被重写的方法的返回值类型是基本数据类型,则子类重写的方法的返回值类型必须是基本数据类型。
- 子类重写的方法抛出的异常类型不大于父类被重写的方法抛出的异常类型。如:父类抛出Exception,子类抛出RuntimeException。
- 子类和父类中的同名同参的方法要么都声明为非static的(考虑重写),要么都声明为static的(不是重写)。静态方法不可以被重写。
(三)this
1.this调用构造器
-
在类的构造器中,可以显式的使用“this(形参列表)”来调用其他的构造器,不能通过“this(形参列表)”来调用自己。
-
规定:“this(形参列表)”必须声明在当前构造器的首行。
-
构造器内部,最多只能声明一个“this(形参列表)”,用来调用其他的构造器。
public class Person {
String name;
Integer age;
public Person(){
System.out.println(Person.class.getName() + "实例化40行代码");
}
public Person(String name){
this();
this.name = name;
}
public Person(String name,Integer age){
this(name);
this.age = age;
}
public static void main(String[] args) {
Person example = new Person("java", 18);
System.out.println(example.name + " " + example.age);
}
}
(四)super
1.super的使用场景
- 通常使用“super.属性”或“super.方法”的方式,显式的调用父类中声明的属性和方法。
- 特殊情况:当子类和父类中定义了同名的属性时,我们要想在子类中调用父类中声明的属性,则必须使用“super.属性”的方式,来显式调用父类中的属性。
public class Person {
int id = 1001;
String name;
Integer age;
public static void main(String[] args) {
new Student().show(); // 输出:1001 1002
}
}
class Student extends Person{
int id = 1002;
public void show(){
System.out.println(super.id + " " + this.id);
}
}
- 特殊情况:当子类和父类中出现方法的重写时,使用“super.方法”调用父类中被重写的方法。
public class Person {
int id = 1001;
String name;
Integer age;
public void eat(){
System.out.println("人:吃饭");
}
public static void main(String[] args) {
new Student().show();
// 输出:人:吃饭\n学生:吃饭
}
}
class Student extends Person{
int id = 1002;
@Override
public void eat() {
System.out.println("学生:吃饭");
}
public void show(){
super.eat();
this.eat();
}
}
2.super调用构造器
- 可以在子类的构造器中显式的使用“super(形参列表)”的方式,调用父类中声明的指定构造器。
public class Person {
Integer age;
String name;
public Person(){
}
public Person(Integer age,String name){
this.age = age;
this.name = name;
}
public static void main(String[] args) {
Student stu = new Student(17, "张三", 1001);
stu.show();
}
}
class Student extends Person{
Integer id;
public Student(Integer age,String name,Integer id){
super(age,name); // 调用父类构造器
this.id = id;
}
public void show(){
System.out.println(this.name + " " + this.age + " " + this.id);
}
}
/*
没有注释super(age,name);后输出:张三 17 1001
注释super(age,name);后输出:null null 1001
*/
- “super(形参列表)”的使用,必须声明在子类构造器的首行。
- 在类的构造器中,针对于“this(形参列表)”或“super(形参列表)”只能二选一,不能同时出现。
- 在构造器的首行,没有显式的声明“this(形参列表)”或“super(形参列表)”,则默认调用“super()”(构造器的回溯)。
(五)继承性
- 一旦子类A继承父类B以后,子类A中就获取了父类B中声明的所有的属性和方法。特别的,父类中声明为private的属性和方法,子类继承父类以后,仍然认为获取了父类中私有的结构,只是因为封装性的影响,使得子类不能直接调用父类的结构而已。
1.子类对象实例化的过程
- 从结果上来看:子类继承父类以后,就获取了父类中声明的属性或方法。创建子类的对象,在堆空间中就会加载所有父类中声明的属性。
- 从过程上来看:当我们通过子类的构造器创建子类对象时,我们一定会直接或间接的调用其父类的构造器,进而调用父类的父类的构造器,知道调用到Java.lang.Object类中的空参构造器为止,正因为加载过所有的父类的结构,所以才可以看到内存中有父类中的结构,子类对象才可以考虑调用。
- 虽然创建子类对象时调用了父类的构造器,但是自始至终就创建过一个对象,即为new的子类对象。
(六)多态性
1.多态性下的属性和方法
-
对象的多态性:父类的引用指向子类的对象(或子类的对象赋值给父类的引用)。
-
有了对象的多态性以后,我们在编译器,只能调用父类中声明的方法,但在运行期,我们实际执行的是子类重写父类的方法。
-
多态是运行时行为。
-
多态性调用方法:编译,看左边;运行,看右边。
-
多态性调用属性:编译,看左边;运行,看左边。
public class Person {
Integer id = 1001;
String name;
public void eat(){
System.out.println("人:吃饭");
}
public static void main(String[] args) {
Person p1 = new Student();
Student s1 = new Student();
// 多态性:只针对方法,不针对属性。
System.out.println(p1.id + " " + s1.id); // 1001 2001
p1.eat(); // 学生:多吃营养食品
s1.eat(); // 学生:多吃营养食品
}
}
class Student extends Person {
Integer id = 2001;
@Override
public void eat() {
System.out.println("学生:多吃营养食品");
}
}
2.自动向下转型
- 有了对象的多态性以后,内存中实际上是加载了子类特有的属性和方法的,但是由于变量声明为父类类型,导致编译时,只能调用父类中声明的属性和方法,子类的方法不能调用。
- 如果使用多态性后,想调用子类特有的方法时,可以使用自动向下转型来达到此效果。
public class Person {
Integer id = 1001;
String name;
public void eat(){
System.out.println("人:吃饭");
}
public static void main(String[] args) {
Person p1 = new Student();
// p1.study(); 因为多态性,不能够直接调用子类独有的方法。
if(p1 instanceof Student){
System.out.println("可以转型!!!");
Student s1 = (Student)p1;
s1.study();
}
}
}
class Student extends Person {
Integer id = 2001;
@Override
public void eat() {
System.out.println("学生:多吃营养食品");
}
public void study(){
System.out.println("学生:好好学习");
}
}
3.多态性练习
- 练习1:
/*
多态性只针对方法,不针对属性。
子类和父类的同名属性不存在覆盖问题,子类和父类的方法存在重写的关系。
方法:编译看左边,运行看右边。
属性:编译运行全看左边。
*/
public class FieldMethodTest {
public static void main(String[] args) {
Sub s1 = new Sub();
System.out.println(s1.count);
s1.display();
Base b = s1;
System.out.println(b == s1);
System.out.println(b.count); // 多态性与属性无关,所以输出父类的count
b.display(); // 多态性与方法有关,此处调用的是子类的display(),输出的是子类的count
/*
* 输出结果:
* 20
* 20
* true
* 10
* 20
* */
}
}
class Base {
int count = 10;
public void display(){
System.out.println(this.count);
}
}
class Sub extends Base {
int count = 20;
@Override
public void display() {
System.out.println(this.count);
}
}
- 练习2:
public class Base {
public void add(int a,int... arr){
System.out.println("base...");
}
public static void main(String[] args) {
Base b = new Sub();
b.add(1,2,3);
Sub s = (Sub)b;
s.add(1,2,3);
/*
* 输出结果:
* sub1...
* sub2...
*
* add(int a,int... arr)与add(int a, int[] arr)构成重写,所以b.add(1,2,3)输出结果为:sub1...
* add(int a, int[] arr)与add(int a,int b,int c)构成重载,s.add(1,2,3)调用的为后者,所以输出结果为:sub2...
* */
}
}
class Sub extends Base {
public void add(int a, int[] arr) {
System.out.println("sub1...");
}
public void add(int a,int b,int c){
System.out.println("sub2...");
}
}
(七)代码块
- 代码块的作用:用来初始化类,对象。
- 代码块的分类:静态代码块和非静态代码块。
- 静态代码块执行顺序大于非静态代码块。
1.静态代码块
- 随着类的加载而执行,而且只执行一次。可以初始化类的信息。
- 如果一个类中定义了多个静态代码块,则按照声明的先后顺序执行。
static{
System.out.println("hello,static,block");
}
2.非静态代码块
- 随着对象的创建而执行,每创建一个对象都执行一次非静态代码块。在创建对象时,可以对对象的属性进行初始化操作。
- 如果一个类中定义了多个非静态代码块,则按照声明的先后顺序执行。
{
System.out.println("hello,block");
}
3.代码块练习
- 总结:由父及子,静态先行。
(八)final
- final可以用来修饰的结构:类,方法,变量。
1.final修饰类
- final修饰的类不可以被继承,比如:String,System。
final class FinalA{
}
// 错误,final修饰类的不可以被继承。
class B extends FinalA{
}
2.final修饰方法
- final修饰的方法不可以被重写,如:Oject的getClass()。
class AA{
public final void show(){}
}
class BB extends AA {
// 错误,final修饰方法的不可以被重写。
public final void show(){}
}
3.final修饰变量
- final修饰的变量,此时的“变量”就称为常量。
- final修饰变量,可以考虑的赋值位置有:显示初始化,代码块中初始化,构造器中初始化。
(九)抽象类和抽象方法
- abstract可以用来修饰类,方法。
- abstract不能用来修饰:属性,构造器等结构。
- abstract不能用来修饰私有方法,静态方法,final的方法,final的类。
1.抽象类
- 此类不能实例化。
- 抽象类中一定有构造器,便于子类实例化时调用(构造器的回溯)。
- 开发中,都会提供抽象类的子类,让子类对象实例化,完成相关的操作。
2.抽象方法
- 抽象方法只有方法的声明,没有方法体。
- 包含抽象方法的类,一定是抽象类。反之,抽象类中可以没有抽象方法的。
- 若子类重写了父类中所有的抽象方法后,此子类可以实例化。若子类没有重写父类中所有的抽象方法,则次子类也是一个抽象类,需要使用abstract修饰。
3.匿名子类对象
public abstract class Person {
public abstract void eat();
public abstract void breath();
public static void method(Person p){
p.eat();
p.breath();
}
public static void main(String[] args) {
Person p = new Person(){
public void eat() {
System.out.println("吃饭");
}
public void breath() {
System.out.println("呼吸");
}
};
method(p);
}
}
(十)接口
- 接口和接口之间可以多继承。
interface AA {
public abstract void method1();
}
interface BB {
public abstract void method2();
}
interface CC extends AA,BB {
public abstract void method3();
}
1.接口的成员
- 全局变量:public static final的,但是在接口中书写时,可以省略不写。
- 抽象方法:public abstract的,但是在接口中书写时,可以省略不写。
- JDK7及以前:只能定义全局变量和抽象方法。JDK8:除了定义全局变量和抽象方法之外,还可以定义静态方法,默认方法。
- Java开发中,接口通过让类去实现(implements)的方式来使用。如果实现类覆盖了接口中所有的抽象方法,则此实现类就可以实例化。如果实现类没有覆盖接口中所有的抽象方法,则此实现类仍为一个抽象类。
2.JDK8中接口的新特性
- 接口中定义的静态方法,只能通过接口来调用。
- 通过实现类的对象,可以调用接口中的默认方法。
public interface A {
// 抽象方法
public abstract void method1();
// 静态方法
public static void method2(){
System.out.println("A-->静态方法");
}
// 默认方法
public default void method3(){
System.out.println("A-->默认方法");
}
}
public class SubClass implements A{
public static void main(String[] args) {
// 1.接口中定义的静态方法,只能通过接口来调用。
A.method2();
// 2.通过实现类的对象,可以调用接口中的默认方法。
SubClass subClass = new SubClass();
subClass.method3();
}
@Override
public void method1() {
System.out.println(this.getClass().getName() + "-->抽象方法实现");
}
}
/*
A-->静态方法
A-->默认方法
*/
- 如果实现类重写了接口中的默认方法,调用时,仍然调用的是重写以后的方法。
public class SubClass implements A{
public static void main(String[] args) {
// 1.接口中定义的静态方法,只能通过接口来调用。
A.method2();
// 2.通过实现类的对象,可以调用接口中的默认方法。
SubClass subClass = new SubClass();
// 3.如果实现类重写了接口中的默认方法,调用时,仍然调用的是重写以后的方法。
subClass.method3();
}
@Override
public void method1() {
System.out.println(this.getClass().getName() + "-->抽象方法实现");
}
@Override
public void method3() {
System.out.println(this.getClass().getName() + "-->重写默认方法");
}
}
/*
A-->静态方法
com.atguigu.oop6.SubClass-->重写默认方法
*/
- 如果子类(或实现类)继承父类和实现接口中声明同名同参数的方法,那么子类在没有重写此方法的情况下,默认调用的是父类同名同参数的方法(类优先)。
public class B {
public void method3(){
System.out.println("父类同名同参数方法");
}
}
public class SubClass extends B implements A{
public static void main(String[] args) {
// 1.接口中定义的静态方法,只能通过接口来调用。
A.method2();
// 2.通过实现类的对象,可以调用接口中的默认方法。
SubClass subClass = new SubClass();
// 3.如果实现类重写了接口中的默认方法,调用时,仍然调用的是重写以后的方法。
// 4.如果子类(或实现类)继承父类和实现接口中声明同名同参数的方法,那么子类在没有重写此方法的情况下,默认调用的是父类同名同参数的方法。
subClass.method3();
}
@Override
public void method1() {
System.out.println(this.getClass().getName() + "-->抽象方法实现");
}
}
/*
A-->静态方法
父类同名同参数方法
*/
- 如果实现类实现了多个接口,而这多个接口中定义了同名同参数的默认方法,那么在实现类没有重写此方法的情况下,就会报错(接口冲突)。这就需要我们必须在实现类中重写此方法。在实现类中调用实现接口中的默认方法的格式为:接口名.super.方法名()。
public class SubClass implements A,C{
public static void main(String[] args) {
SubClass subClass = new SubClass();
subClass.method3();
}
@Override
public void method3() {
System.out.println("SubClass");
// 调用实现接口中同名同参数的默认方法
A.super.method3();
C.super.method3();
}
}
interface A {
public default void method(){
System.out.println("A");
}
}
interface B {
public default void method(){
System.out.println("B");
}
}
(十一)内部类
- 内部类的分类:成员内部类(静态,非静态),局部内部类(构造器,代码块,方法)。
1.局部内部类
public class Anmial{
// 1.使用方式一
public Comparable getComparable(){
final int num = 3;
class MyComparable implements Comparable {
@Override
public int compareTo(Object o) {
// 此处使用外部类的局部变量,该局部变量必须为final。jdk8之前final不可以省略,jdk8及之后版本final可以省略,但并不可以修改该变量。
System.out.println(num);
return 0;
}
}
return new MyComparable();
}
// 2.使用方式二
public Comparable getComparable(int a){
return new Comparable() {
@Override
public int compareTo(Object o) {
return 0;
}
};
}
}
2.成员内部类
- 一方面,作为外部类的成员:内部类可以调用外部类的结构(外部类.this.方法)。内部类可以被static修饰(外部类不可以被static修饰)。内部类可以被四种不同的四种权限修饰。
- 另一方面,作为一个类:类内可以定义属性,方法,构造器等。可以被final修饰,表示此类不能被继承,言外之意,不使用final可以被继承。可以被abstract修饰,表示不能够被实例化。
public class InnerTest {
public static void main(String[] args) {
// 1.创建非静态成员内部类对象
Person.Dog dog1 = new Person().new Dog();
dog1.sing();
Person p = new Person();
Person.Dog dog2 = p.new Dog();
dog2.sing();
// 2.创建静态成员内部类对象
Person.Bird bird = new Person.Bird();
bird.sing();
// 3.非静态内部类与外部类成员变量区分
dog1.display("小猫");
}
}
class Person{
private String name = "小明";
class Dog{
private String name = "小狗";
public void sing(){
System.out.println("狗:汪汪汪");
}
public void display(String name){
System.out.println(name); // 局部变量
System.out.println(this.name); // 内部类变量
System.out.println(Person.this.name); // 外部类变量
}
}
static class Bird{
public void sing(){
System.out.println("鸟:叽叽叽");
}
}
}
/*
狗:汪汪汪
狗:汪汪汪
鸟:叽叽叽
小猫
小狗
小明
*/
三,常用类
(一)Object
1.finalize()
- 对象回收之前会调用此方法,永远不要主动调用某个对象的finalize(),应该交给垃圾回收机制调用。
public class Test {
@Override
protected void finalize() throws Throwable {
System.out.println(this + "被回收");
}
public static void main(String[] args) {
Test test = new Test();
System.out.println(test);
test = null; // 手动释放
System.gc(); // 通知垃圾回收器进行回收
/*
* 输出结果:
* com.atguigu.oop4.Test@1b6d3586
* com.atguigu.oop4.Test@1b6d3586被回收
* */
}
}
2.equals()
- Oject类中equals()的原码:
public boolean equals(Object obj) {
return (this == obj);
}
- 没有重写之前的equals()与运算符== 没有任何区别。
- 通常情况下,我们自定义的类使用equals()的话,也通常是比较两个类的实体内容是否相同。那么我们就需要对Object类中的equals()进行重写。
- ==和equals()的区别:
3.hashCode()
(二)String
- String声明为final,不可以被继承。
- String实现了Serializable接口,表示字符串是支持序列化的,同时实现了Comparable接口,可以比较字符串大小。
- String内部定义了final char [] value用于存储字符串数据。String代表不可变的字符序列。
- 当对字符串进行重新赋值时,需要重新指定内存区域赋值,不能使用原有的value进行赋值。
- 当对现有的字符串进行连接操作时,需要重新指定内存区域赋值,不能使用原有的value进行赋值。
- 当调用String的replace()修改指定字符或字符串时,需要重新指定内存区域赋值,不能使用原有的value进行赋值。
- 通过字面量的方式给一个字符串赋值,此时字符串值声明在字符串常量池中。
- 字符串常量池中不会存储相同内容的字符串的。
1.不同实例化的对比
- 不同拼接下的对比:
- 常量与常量的拼接结果在常量池,且常量池中不会存在任何相同内容的常量。
- 只要其中有一个是变量,结果就在堆中。
- 如果拼接的结果调用intern()方法,返回值就在常量池中。
@Test
public void fun1(){
String s1 = "Hello";
String s2 = "World";
String s3 = "HelloWorld";
String s4 = "Hello" + "World";
String s5 = "Hello" + s2;
String s6 = s1 + "World";
String s7 = s1 + s2;
final String s8 = "World";
String s9 = "Hello" + s8;
// 常量与常量的拼接结果在常量池,且常量池中不会存在任何相同内容的常量。
System.out.println(s3 == s4); // true
System.out.println(s3 == s9); // true
// 只要其中有一个是变量,结果就在堆中。
System.out.println(s3 == s5); // false
System.out.println(s3 == s6); // false
System.out.println(s3 == s7); // false
System.out.println(s5 == s6); // false
System.out.println(s5 == s7); // false
// 如果拼接的结果调用intern()方法,返回值就在常量池中。
System.out.println(s3 == s5.intern()); // true
}
- String s = new String(“abc”);方式创建对象,在内存中创建了几个对象?
两个,一个是堆空间中new结构,另一个是字符串常量池中的数据”abc“。 - 面试题:
2.常用方法
3.String与不同类型数据的转换
- String --> 基本数据类型,包装类:调用包装类的静态方法:parseXxx(str)。
- 基本数据类型,包装类 --> String:调用String重载的valueOf(xxx)。
- String --> char[]:调用String的toCharArray()。
- char[] --> String:调用String的构造器。
- String --> byte[]:调用String的getBytes()。
- byte[] --> String:调用String的构造器。
@Test
public void fun3() throws UnsupportedEncodingException {
String strNum = new String("1");
int num = Integer.parseInt(strNum);
strNum = String.valueOf(num);
System.out.println(strNum);
String strArray = new String("Array");
char[] array = strArray.toCharArray();
System.out.println(Arrays.toString(array));
strArray = new String(array);
System.out.println(strArray);
byte[] bytes = strArray.getBytes("utf8");
System.out.println(Arrays.toString(bytes));
strArray = new String(bytes);
System.out.println(strArray);
}
4.可变字符序列
- StringBuffer和StringBuilder都是可变字符序列,前者线程安全,效率低,后者线程不安全,效率高。
- String,StringBuffer,StringBuilder底层都是char型数组。
- 分析:
String str = new String(); // new char[0];
String str1 = new String("abc"); // new char[]{'a','b','c'};
StringBuffer sb1 = new StringBuffer(); // new char[16];
StringBuffer sb2 = new StringBuffer("abc"); // new char["abc".length + 16];
// 问题1:System.out.println(sb2.length); // 3
// 问题2:扩容问题,默认情况下,扩容为原来容量的2倍+2,同时将原有数组中的元素复制到新的数组中。
// 效率排名:StringBuilder > StringBuffer > String
(三)日期时间
1.两个Date类
/*
java.util.Date
|---java.sql.Date类
java.sql.Date对应数据库的Date。
*/
@Test
public void testDate(){
Date date = new Date();
System.out.println(date);
java.sql.Date sqlDate = new java.sql.Date(date.getTime());
System.out.println(sqlDate);
}
- System类中的currentTimeMillis():获取当前时间的毫秒数。
2.SimpleDateFormat
@Test
public void testSimpleDateFormat() throws ParseException {
Date date = new Date();
DateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
// 格式化
String format = simpleDateFormat.format(date);
System.out.println(format);
// 解析
date = simpleDateFormat.parse(format);
System.out.println(date);
}
3.Calendar
4.JDK8的时间API(JODA-TIME)
@Test
public void testLocalDateTime(){
// now():获取当前的日期,时间,日期+时间
LocalDate localDate = LocalDate.now();
LocalTime localTime = LocalTime.now();
LocalDateTime localDateTime = LocalDateTime.now();
// of():返回一个新的LocalDateTime对象,不改变对象原值,体现不可变性
LocalDateTime returnTime = LocalDateTime.of(2020, 10, 2, 10, 10, 10, 10);
// getXxx()
int dayOfMonth = returnTime.getDayOfMonth();
System.out.println(dayOfMonth);
System.out.println(returnTime.getMonthValue());
}
(四)比较器
- 如何实现比较对象的大小?
- 使用两个接口中的任何一个:Comparable或Comparator。
1.Comparable(自然排序)
- 像String,包装类等实现了Comparable接口,重写了CompareTo(obj),给出了比较两个对象大小的方法。
- 像String,包装类等实现了Comparable接口,进行了从小到大的排序。
- 重写CompareTo()的规则:
- 如果this大于形参obj,返回正整数。
- 如果this小于形参obj,返回负整数。
- 如果this等于形参obj,返回零。
- 对于自定义类来说,如果需要排序,我们可以让自定义类实现Comparable接口,重写CompareTo(),在CompareTo(obj)中指明如何排序。
public class Goods implements Comparable<Goods>{
private String name;
private Double price;
// 价格从小到大排序,相等之后名称从小到大排序
@Override
public int compareTo(Goods o) {
if(this.price > o.price){
return 1;
}else if(this.price < o.price){
return -1;
}else {
return this.name.compareTo(o.name);
}
}
public Goods(){}
public Goods(String name, Double price) {
this.name = name;
this.price = price;
}
@Override
public String toString() {
return "Goods{" +
"name='" + name + '\'' +
", price=" + price +
'}';
}
}
public class Test1 {
@Test
public void test3(){
Goods[] arr = new Goods[6];
arr[0] = new Goods("AA",10.0);
arr[1] = new Goods("XX",13.0);
arr[2] = new Goods("BB",9.0);
arr[3] = new Goods("GG",3.0);
arr[4] = new Goods("JJ",20.0);
arr[5] = new Goods("CC",9.0);
Arrays.sort(arr);
for(Goods i : arr){
System.out.println(i.toString());
}
}
}
2.Comparator(定制排序)
- 当元素的类型没有实现java.lang.Comparable接口而又不方便修改代码,或者实现了java.lang.Comparable接口的排序规则不适合当前的操作。那么,可以考虑使用Comparator的对象来排序。
- 重写compare(Object o1,Object o2),比较o1和o2的大小:如果返回正整数,则表示o1>o2,其余同理。
public class Test1 {
@Test
public void test3(){
Goods[] arr = new Goods[6];
arr[0] = new Goods("AA",10.0);
arr[1] = new Goods("XX",13.0);
arr[2] = new Goods("BB",11.0);
arr[3] = new Goods("GG",3.0);
arr[4] = new Goods("JJ",20.0);
arr[5] = new Goods("BB",9.0);
// 按照名称从小到达排序,名称相同按照价格从大到小排序
Arrays.sort(arr, new Comparator<Goods>() {
@Override
public int compare(Goods o1, Goods o2) {
if(o1.getName().compareTo(o2.getName()) == 0){
if(o1.getPrice() > o2.getPrice()){
return -1;
}else if(o1.getPrice() < o2.getPrice()){
return 1;
}else
return 0;
}
return o1.getName().compareTo(o2.getName());
}
});
for(Goods i : arr){
System.out.println(i.toString());
}
}
}
四,异常处理
(一)异常体系结构
- java.lang.Error:一般不编写针对性代码进行处理。
- java.lang.Exception:可以进行异常处理,分为编译时异常和运行时异常(RuntimeException),编译时异常必须进行处理,运行时异常考虑修改代码。
- “抓抛模型”:“抛”:程序在正常执行过程中,一旦出现异常,就会在异常代码处生成一个对应异常类的对象,并将此对象抛出。一旦抛出对象以后,其后的代码就不会再执行。“抓”:可以理解为异常的处理方式。
![在这里插入图片描述](https://img-blog.csdnimg.cn/712c0df23e0e407d896e1042629a4f00.png
(二)异常处理方式一:try-catch-finally
try{
// 可能出现的代码
}catch(异常类型1 变量名1){
// 处理异常类型方式1
}catch(异常类型2 变量名2){
}catch(异常类型3 变量名3){
}finally{
// 一定会执行的代码
}
- finally是可选的。
- 使用try将可能出现异常代码包装起来,在执行过程中,一旦出现异常,就会生成一个对应异常类的对象,根据对象的类型,取catch中进行匹配。一旦匹配到catch时,就会进入catch中进行异常的处理。一旦处理完成,就跳出当前的try-catch结构(无finally),继续执行其后的代码。
- catch中的异常类型,如果没有子父关系,则谁声明在上,谁声明在下无所谓。如果满足子父类关系,则子类声明在上,父类声明在下。否则,报错。
- finally中声明的是一定会被执行的代码。即使catch中又出现异常了,try或catch中有return语句等情况,finally中代码依然会执行。
- 像数据库连接,输入输出流,网络编程Socket等资源,JVM是不能自动回收的,我们需要自己手动的进行资源的释放。此时的资源释放,就需要声明在finally中。
(三)异常处理方式二:throws + 异常类型
- "throws + 异常类型"写在方法声明处。指明此方法执行时可能会抛出的异常类型。一旦当方法执行时,出现异常,仍会在异常代码处生成一个异常类的对象,此对象满足throws后异常类型时,就会被抛出。异常代码的后续代码,就不再执行。
- 子类重写方法抛出的异常类型不大于父类被重写方法抛出的异常类型。
- 父类被重写方法没有抛出异常,子类重写方法也不能抛出异常。意味着子类重写方法有异常时,必须使用try-catch-finally方式处理。
(四)手动抛出异常及自定义异常类
- 自定义异常:
- 继承于现有的异常:Exception,RuntimeException。
- 提供全局常量:serialVersionUID。
- 提供重载构造器。
public class ExceptionTest {
public static void main(String[] args) {
Student student = new Student();
try{
student.regist(-10);
}catch (MyException e){
System.out.println(e.getMessage() + ",请重新输入!!");
}
}
}
class MyException extends RuntimeException {
public MyException(){
}
public MyException(String message){
super(message);
}
}
class Student {
private Integer id;
public void regist(Integer id){
if(id > 0){
this.id = id;
}else {
throw new MyException("id输出错误");
}
}
}
五,多线程
(一)基本概念
- 进程:是程序的一次执行过程,或是正在运行的一个程序。
- 线程:进程可进一步细化为线程,是一个程序内部的一条执行路径。一个进程中的多个线程共享相同的内存单元。每一个线程有各自的虚拟机栈和程序计数器。多个线程共享一个进程的方法区和堆。
- 并行:多个CPU同时执行多个任务。比如:多个人同时做不同的事。
- 并发:一个CPU(采用时间片)同时执行多个任务。比如:秒杀,多个人做同一件事。
- 线程的生命周期:
(二)线程的创建
1.继承Thread类
- 创建步骤:
- 创建一个继承Thread类的子类。
- 重写Thread类的run()。
- 创建Thread类的子类对象。
- 通过此对象调用start()。
- 常用API:
- yield():释放CPU的执行权(礼让方法)。
- join():在线程A中调用线程B的join(),此时线程A进入阻塞状态,直到线程B完全执行完后,线程A才结束阻塞状态(插队方法)。
- stop():强制结束线程的生命,已过时。
- sleep(long millitime):让当前线程强制睡眠,在指定时间内,线程是阻塞状态。
- isAlive():判断当前是否存活。
- getPriority():返回当前线程优先值。
- setPriortiy():改变当前线程优先值。
- 优先级说明:高优先级的线程要抢占低优先级线程CPU的执行权。但是只是从概率上讲,高优先级的线程高概率的情况下被执行。并不意味着只有当高优先级的线程执行完以后,低优先级的线程才执行。
2.实现Runnable接口
- 创建步骤:
- 创建实现Runnable接口的类。
- 实现类取实现Runnable接口的run()。
- 创建实现类的对象。
- 将此对象作为参数传递到Thread类的构造器中。
- 通过Thread类对象调用start()。
3.实现Callable接口
- 如何理解实现Callable接口比实现Runnable接口更强大?
- FutureTask:
- 创建步骤:
- 创建一个实现Callable的实现类。
- 实现call(),将此线程需要执行的操作声明在call()中。
- 创建Callable接口实现类的对象。
- 将此Callable接口的实现类对象作为参数传递到FutureTask的构造器中,创建FutureTask的对象。
- 将FutureTask的对象作为参数传递到Thread类的构造器中,并调用start()。
- 获取Callable中call()的返回值。
public class CallableTest {
public static void main(String[] args) {
OneTask oneTask = new OneTask();
TwoTask twoTask = new TwoTask();
FutureTask<Integer> oneFuture = new FutureTask<>(oneTask);
FutureTask<Integer> twoFuture = new FutureTask<>(twoTask);
new Thread(oneFuture).start();
new Thread(twoFuture).start();
try {
Integer num1 = oneFuture.get();
Integer num2 = twoFuture.get();
System.out.println("100以内被2整除数之和为:" + num1 + ",100以内被三整除数之和为:" + num2);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
class OneTask implements Callable<Integer> {
@Override
public Integer call() throws Exception {
int sum = 0;
for (int i = 1; i <= 100; i++) {
if(i % 2 == 0){
sum += i;
}
}
return sum;
}
}
class TwoTask implements Callable<Integer> {
@Override
public Integer call() throws Exception {
int sum = 0;
for (int i = 1; i <= 100; i++) {
if(i % 3 == 0){
sum += i;
}
}
return sum;
}
}
4.线程池
- 线程池的优势:
- 提高响应速度(减少了创建新线程的时间)。
- 降低资源消耗(重复利用线程池中线程,不需要每次都创建)。
- 便于线程管理。
(三)sychronized
- 同步监视器,俗称:锁。任何一个类的对象,都可以充当锁。多个线程必须要共用一把锁。
1.单例模式线程安全问题
public class SingleMode {
private static SingleMode instance;
private SingleMode(){}
public static SingleMode getInstance(){
if(instance == null){
synchronized (SingleMode.class){
if(instance == null){
instance = new SingleMode();
}
}
}
return instance;
}
}
2.死锁问题
- 不同线程分别占用对方需要的同步资源不放弃,都在等待对方放弃自己需要的同步资源,就形成了线程的死锁。
- 出现死锁后,不会出现异常,不会出现提示,只是所有的线程都处于阻塞状态,无法继续。
public class DeadLock {
public static void main(String[] args) {
Object a = new Object();
Object b = new Object();
new Thread(()->{
synchronized (a){
try {
Thread.currentThread().sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (b){
}
}
}).start();
new Thread(()->{
synchronized (b){
try {
Thread.currentThread().sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (a){
}
}
}).start();
}
}
3.线程的通信
- 通信API:
- wati():当前线程就进入阻塞状态,并释放锁。
- notify():就会唤醒被waith的一个线程。如果有多个线程被wait,就唤醒优先级高的那个。
- notifyAll():唤醒所有被wait的线程。
- 说明:
- 三个方法必须使用在同步代码块和同步方法中。
- 三个方法的调用者必须是同步代码块或同步方法的同步监视器对象,否则,会出现IllegalMonitorException。
- 三个方法定义在Object类中。
- 生产者消费者问题:
public class Test {
public static void main(String[] args) {
Clerk clerk = new Clerk();
Consumer consumer = new Consumer(clerk);
Productor productor = new Productor(clerk);
new Thread(consumer,"消费者").start();
new Thread(productor,"消费者").start();
}
}
class Clerk{
private int productNum = 0;
public void product(){
synchronized (this) {
if (productNum < 20) { // 通知生产者开始生产
productNum++;
System.out.println(Thread.currentThread().getName() + "生产了" + productNum + "号产品");
// 生产成功,唤醒消费者进行消费
notify();
} else {
try {
// 生产过量,阻塞生产者
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public void consume(){
synchronized (this) {
if (productNum > 0) { // 通知消费者开始消费
System.out.println(Thread.currentThread().getName() + "消费了" + productNum + "号产品");
productNum--;
notify();
} else {
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
class Consumer implements Runnable{
private Clerk clerk;
public Consumer(Clerk clerk){
this.clerk = clerk;
}
@Override
public void run() {
while (true){
clerk.consume();
}
}
}
class Productor implements Runnable{
private Clerk clerk;
public Productor(Clerk clerk){
this.clerk = clerk;
}
@Override
public void run() {
while(true){
clerk.product();
}
}
}
4.面试题
- sychronized和lock的相同与不同?
- 相同:二者都可以解决线程安全问题。
- 不同:synchronized自动释放锁,lock需要手动释放锁。
- sleep()与wait()的异同?
- 相同:一旦执行方法,都可以使当前线程进入阻塞状态。
- 不同:
- 声明位置不同:Thread类中声明sleep(),Object类中声明wait()。
- 调用位置不同:sleep()可以在任何场景下使用,wait()必须使用在同步代码块或同步方法中。
- 锁释放:sleep()不会释放锁,wait()会释放锁。
六,枚举类和注解
(一)枚举类的使用
- 类的对象只有有限个,确定的。我们称此类为枚举类。
- 当需要定义一组常量时,强烈建议使用枚举类。
- 如果枚举类中只有一个对象,则可以作为单例模式的实现方式。
(二)自定义枚举类
- 自定义枚举类:
- JDK5.0之前,自定义枚举类。
- JDK5.0,可以使用enum关键字定义枚举类,该种方式定义的枚举类默认父类为java.lang.Enum。
// 方式一
public class Season {
// 1.声明Season对象的属性:private final修饰
private final String seasonName;
private final String seasonDesc;
// 2.私有化类的构造器,并给对象属性赋值
private Season(String seasonName,String seasonDesc){
this.seasonName = seasonName;
this.seasonDesc = seasonDesc;
}
// 3.提供当前枚举类的对象
public static final Season SPRING = new Season("春天","春暖花开");
public static final Season SUMMER = new Season("夏天","夏日炎炎");
public static final Season AUTUM = new Season("秋天","秋高气爽");
public static final Season WINTER = new Season("冬天","冰天雪地");
// 4.其他诉求:获取枚举类对象的属性
public String getSeasonName() {
return seasonName;
}
public String getSeasonDesc() {
return seasonDesc;
}
// 5.提供toString()
@Override
public String toString() {
return "Season{" +
"seasonName='" + seasonName + '\'' +
", seasonDesc='" + seasonDesc + '\'' +
'}';
}
}
// 方式二
public enum Season {
// 1.提供当前枚举类的对象
SPRING("春天","春暖花开"),
SUMMER("夏天","夏日炎炎"),
AUTUM("秋天","秋高气爽"),
WINTER("冬天","冰天雪地");
// 2.声明Season对象的属性:private final修饰
private final String seasonName;
private final String seasonDesc;
// 3.私有化类的构造器,并给对象属性赋值
private Season(String seasonName,String seasonDesc){
this.seasonName = seasonName;
this.seasonDesc = seasonDesc;
}
// 4.其他诉求:获取枚举类对象的属性
public String getSeasonName() {
return seasonName;
}
public String getSeasonDesc() {
return seasonDesc;
}
public static void main(String[] args) {
// 枚举类的父类为:class java.lang.Enum
System.out.println(Season.class.getSuperclass());
System.out.println(Season.SPRING);
}
}
- 常用方法:
- toString():输入枚举类对象名称。
- values():获取所有的枚举类对象。
- valueOf(String objName):根据提供的objName,返回枚举类中对象名时objName的对象。
(三)使用enum关键字定义枚举类实现接口
- 实现接口,在enum类中实现抽象方法。
interface Info {
public abstract void printInfo();
}
public enum Season implements Info{
// 1.提供当前枚举类的对象
SPRING("春天","春暖花开"),
SUMMER("夏天","夏日炎炎"),
AUTUM("秋天","秋高气爽"),
WINTER("冬天","冰天雪地");
// 2.声明Season对象的属性:private final修饰
private final String seasonName;
private final String seasonDesc;
// 3.私有化类的构造器,并给对象属性赋值
private Season(String seasonName,String seasonDesc){
this.seasonName = seasonName;
this.seasonDesc = seasonDesc;
}
// 4.其他诉求:获取枚举类对象的属性
public String getSeasonName() {
return seasonName;
}
public String getSeasonDesc() {
return seasonDesc;
}
@Override
public void printInfo() {
System.out.println("season...");
}
public static void main(String[] args) {
// 枚举类的父类为:class java.lang.Enum
Season.SPRING.printInfo();
}
}
- 让枚举类的对象分别实现接口中的抽象方法。
interface Info {
public abstract void printInfo();
}
public enum Season implements Info{
// 1.提供当前枚举类的对象
SPRING("春天","春暖花开"){
@Override
public void printInfo() {
System.out.println(this.getSeasonDesc());
}
},
SUMMER("夏天","夏日炎炎"){
@Override
public void printInfo() {
System.out.println(this.getSeasonDesc());
}
},
AUTUM("秋天","秋高气爽"){
@Override
public void printInfo() {
System.out.println(this.getSeasonDesc());
}
},
WINTER("冬天","冰天雪地"){
@Override
public void printInfo() {
System.out.println(this.getSeasonDesc());
}
};
// 2.声明Season对象的属性:private final修饰
private final String seasonName;
private final String seasonDesc;
// 3.私有化类的构造器,并给对象属性赋值
private Season(String seasonName,String seasonDesc){
this.seasonName = seasonName;
this.seasonDesc = seasonDesc;
}
// 4.其他诉求:获取枚举类对象的属性
public String getSeasonName() {
return seasonName;
}
public String getSeasonDesc() {
return seasonDesc;
}
public static void main(String[] args) {
// 枚举类的父类为:class java.lang.Enum
Season.SPRING.printInfo();
}
}
(四)常见的注解
- 自定义注解(参照@SuppressWarnings定义):
- 注解声明为:@interface。
- 内部定义成员,通常使用value表示。
- 可以指定成员的默认值,使用default定义。
- 如果自定义注解没有成员,表示是一个标识作用。
- 如果注解有成员,在使用注解时,需要指明成员的值。
- 自定义注解通常都会指明两个元注解:@Retention和@Target。
public @interface MyAnnotation{
}
(五)元注解
- 元注解:修饰注解的注解。
- @Retention:指定所修饰的Annotation的生命周期。只有声明为RUNTIME的注解才可以被反射获取。
- @Target:
@Documented和@Inherited:
(六)JDK1.8新特性:可重复注解,类型注解
- 可重复注解:
- JDK1.8之前实现:
@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE})
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation {
String value();
}
public @interface MyAnnotations {
MyAnnotation[] value();
}
@MyAnnotations(value = {@MyAnnotation("a"),@MyAnnotation("b")})
public class Test {
}
2. JDK1.8实现:
@Inherited
@Repeatable(value = MyAnnotations.class)
@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE})
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation {
String value();
}
@Inherited
@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE})
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotations {
MyAnnotation[] value();
}
@MyAnnotation("a")
@MyAnnotation("b")
public class Test {
}
七,集合
(一)分类
(二)Collection
1. 集合元素的遍历
- Iterator(迭代器)遍历:
@Test
public void test1(){
Collection coll = new ArrayList();
coll.add(123);
coll.add("Tom");
coll.add(false);
Iterator iterator = coll.iterator();
while(iterator.hasNext()){
// next():指针先下移,将下移后的集合位置上的元素返回
Object obj = iterator.next();
if("Tom".equals(obj)){
iterator.remove();
}
}
iterator = coll.iterator();
while (iterator.hasNext()){
System.out.println(iterator.next());
}
- foreach遍历:
@Test
public void test2(){
Collection coll = new ArrayList();
coll.add(123);
coll.add("Tom");
coll.add(false);
int[] arr = new int[]{1,2,3};
// 内部仍然调用了迭代器
// for(集合中元素的类型 变量 : 集合对象)
for(Object obj : coll){
System.out.println(obj);
}
for(int i : arr){
System.out.println(i);
}
}
2. List接口
- List集合类中的元素有序,且可以重复。
- 实现类:ArrayList,LinkedList,Vector。
- 三个实现类的异同:
- 相同:三个类都实现了List接口,元素有序,且可以重复。
- 不同:
- ArrayList:为主要实现类,线程不安全,效率高,底层使用Object[]存储。
- LinkedList:对于频繁的插入,删除操作,使用此类效率比ArrayList高,底层使用双向链表存储。
- Vector:作为List接口的古老实现类,线程是安全的,效率低,底层使用Object[]存储。
- ArrayList使用注意:
- 开发中使用带参数的构造器(底层创建了长度为10的数组,扩容为原来容量的1.5倍,同时需要将原有数组中的数据复制到新的数组当中)。
- jdk7中在构造器中创建了长度为10的数组,类似于单例模式的饿汉式,而jdk8中在第一次使用add()时才创建数组,类似于懒汉式,延迟了数组的创建,节省内存。
3. Set接口
- Set集合中的元素无序,不可重复。
- 实现类:HashSet,LinkedHashSet,TreeSet。
- 三个实现类的区别:
- HashSet:作为Set接口的主要实现类,线程是不安全的,可以存储null值。
- LinkedHashSet:作为HashSet的子类,遍历其内部数据时,可以按照添加的顺序进行遍历,内部维护了一个双向链表。
- TreeSet:可以按照添加对象指定的属性,进行排序。
- 要求:
- 为什么要重写hashCode()和equals()?
- Set要保证不可重复。如果要属性值相同的两个元素,但地址值不同的两个对象为相同元素。那么如果不重写hashCode(),那么两个对象的哈希值则可能不相同,会导致相同的元素直接插入。所以,需要重写hashCode(),重写equals()来判断各属性是否相同。
- 无序性:不等于随机性。存储的数据在底层数组中并非按照数组索引的顺序添加,而是根据数据的哈希值决定的。
- 不可重复性:保证添加的元素按照equals()判断时,不能返回true。即:相同的元素只能添加一个。
- 添加过程:
- TreeSet单独的特性:
- 向TreeSet中添加数据,要求是相同类的对象。
- 两种排序方式:自然排序(实现Comparable接口)和定制排序(Compartor)。
- 自然排序中,比较两个对象是否相同的标准为:compareTo返回0,不再是HashSet的equals()。
- 定制排序中,比较两个对象是否相同的标准为:compare返回0,不再是HashSet的equals()。
- 自然排序使用TreeSet:
public class Employee implements Comparable<Employee>{
private String name;
private Integer age;
private MyDate birthday;
@Override
public int compareTo(Employee o) {
return this.name.compareTo(o.getName());
}
}
public class TreeSetTest {
@Test
public void test1(){
TreeSet<Employee> treeSet = new TreeSet<>();
// TreeSet对于排序属性相同的元素,会进行去除操作。
treeSet.add(new Employee("Jack",10,new MyDate(1991,10,1)));
treeSet.add(new Employee("Air",10,new MyDate(1991,10,1)));
treeSet.add(new Employee("Jack",20,new MyDate(1991,10,1)));
treeSet.add(new Employee("Mike",10,new MyDate(1991,10,1)));
Iterator<Employee> iterator = treeSet.iterator();
while(iterator.hasNext()){
System.out.println(iterator.next().toString());
}
}
}
- 定制排序使用TreeSet:
public class MyDate {
private Integer year;
private Integer month;
private Integer day;
}
public class TreeSetTest {
@Test
public void test2(){
Comparator<Employee> comparator = new Comparator<Employee>() {
@Override
public int compare(Employee o1, Employee o2) {
MyDate b1 = o1.getBirthday();
MyDate b2 = o2.getBirthday();
int flag = b1.getYear() - b2.getYear();
if(flag != 0){
return flag;
}else {
flag = b1.getMonth() - b2.getMonth();
if(flag != 0){
return flag;
}else {
return b1.getDay() - b2.getDay();
}
}
}
};
TreeSet<Employee> treeSet = new TreeSet<>(comparator);
// TreeSet对于排序属性相同的元素,会进行去除操作。
treeSet.add(new Employee("Jack",10,new MyDate(1991,10,1)));
treeSet.add(new Employee("Air",10,new MyDate(1991,8,22)));
treeSet.add(new Employee("Bob",20,new MyDate(2001,8,2)));
treeSet.add(new Employee("Mike",10,new MyDate(2015,7,15)));
Iterator<Employee> iterator = treeSet.iterator();
while(iterator.hasNext()){
System.out.println(iterator.next().toString());
}
}
}
- 关于HashSet面试题:
public class TreeSetTest {
@Test
public void Test3(){
HashSet<Employee> hashSet = new HashSet<>();
Employee e1 = new Employee("Tom", 10, new MyDate(2001, 8, 2));
Employee e2 = new Employee("Jack", 10, new MyDate(2001, 8, 2));
hashSet.add(e1);
hashSet.add(e2);
System.out.println(hashSet); // 2个元素
e1.setAge(20);
// 这里并没有删除e1,由于e1属性的修改,导致删除方法并不能正确的找到e1所在的位置,所以并没有删除成功
hashSet.remove(e1);
System.out.println(hashSet); // 2个元素
hashSet.add(new Employee("Tom", 20, new MyDate(2001, 8, 2)));
System.out.println(hashSet); // 3个元素
hashSet.add(new Employee("Tom", 10, new MyDate(2001, 8, 2)));
System.out.println(hashSet); // 4个元素
}
}
良好的编程风格
- 正确的注释和注释风格
- 使用文档注
- 释来注释整个类或整个方法。
- 如果注释方法中的某一个步骤,使用单行或多行注释。
- 命名风格
- 包名:多单词组成时所有字母都小写:com.atguigu。
- 类名、接口名:多单词组成时,所有单词的首字母大写: XxxYyyZzz(大驼峰式)。
- 变量名、方法名:多单词组成时,第一个单词首字母小写,第二个单词开始每个单词首字母大写: xxxYyyZzz(小驼峰式)。
- 常量名:所有字母都大写。多单词时每个单词用下划线连接: XXX_YYY_ZZZ。