Featured image of post Java基础之面向对象

Java基础之面向对象

Java语言基本之java介绍

本文阅读量

面向对象

对象本质上是一种特殊的数据结构

类是对象的模版,

对象执行原理

一些注意事项

  1. 成员变量本身存在默认值

    数据类型 明细 默认值
    基本类型 byte、short、char、int、long 0
    基本类型 double,float 0.0
    基本类型 boolean false
    引用类型 数组、string null
  2. 一个代码文件中,可以有多个class类,但只能一个用public修饰,且public修饰的类名必须称为代码文件名

  3. 对象与对象之间的数据不会相互影响,但多个变量指向同一个对象时就会相互影响了

  4. 如果某个对象没有一个变量引用它(对象设置为null),则该对象无法被操作了,该对象会成为所谓的垃圾对象

关键字this

可以用在方法中,代表当前对象

主要在类方法中,访问类属性(常用来解决对象的成员变量与方法内部变量的名称一样时,导致访问冲突问题)

构造器

构造器的名称必须与类名一样,且没有返回值类型。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
public class Student{
  // 无参构造器
  public Student() {
    
  }
  
  // 有参构造器
  public Student(String name) {
    
  }
}

特点:

  1. 对象在被创建时,对象会去调用构造器Student s = new Student(),这句代码执行了2步操作:1)new Student创建对象 2)()匹配调用无参构造器

场景:

常用于对对象成员变量(属性)进行初始化赋值

注意

类在设计时,如果不写构造器,java会为类自动生成一个无参构造器

一旦定义了有参构造器,java就不会帮我们的类自动生成无参构造器了,此时就建议自己手写一个无参构造器出来

子类构造器

子类的全部构造器,都会先调用父类的构造器,再执行自己

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
public class Animal{
  public Animal(){
    System.out.println("父类Animal的无参构造器执行了");
  }
  public Animal(String name){
    System.out.println("父类Animal的有参构造器执行了");
  }
}

public class Wolf extends Animal{
  public Wolf(){
    // super();  // 默认都会调用父类的无参构造器,如果父类没有无参构造器就会报错
    System.out.println("子类Wolf的无参构造器执行了");
  }
  
   public Wolf(String n){
     // super(); // 默认都会调用父类的无参构造器
     // super(n); // 我们可以手动去调用父类的有参构造器,这样即使没有无参构造器,也不会报错了
    System.out.println("子类Wolf的有参构造器执行了"); // 父类Animal的无参构造器执行了    子类Wolf的有参构造器执行了
  }
}

// main方法
public static void main(String[] args){
  Wolf w1 = new Wolf();
  Wolf w2 = new Wolf("七匹狼");
}

子类的全部构造器,都会先调用父类的构造器,在执行自己,那是因为:子类构造器会在代码最前方调用super()方法

this()调用兄弟构造器

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
public class Student {

    private String name;
    private int age;
    private String school;

    public Student() {

    }

    public Student(String name,int age){
      // this调用兄弟构造器
        this(name,age,"默认学校");
    }

    public Student(String name, int age, String school) {
        this.name = name;
        this.age = age;
        this.school = school;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public String getSchool() {
        return school;
    }

    public void setSchool(String school) {
        this.school = school;
    }

}

调用student类

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
public static void main(String[] args) {
        Student s1  = new Student("孙悟空",500,"水帘洞");
        System.out.println(s1.getName());
        System.out.println(s1.getAge());
        System.out.println(s1.getSchool());

        Student s2  = new Student("蜘蛛精",550,"盘丝洞");
        System.out.println(s2.getName());
        System.out.println(s2.getAge());
        System.out.println(s2.getSchool());

        Student s3  = new Student("白骨精",450);
        System.out.println(s3.getName());
        System.out.println(s3.getAge());
        System.out.println(s3.getSchool());
}

注意

this()super()都是调用构造器,这俩不能同时出现,且必须在构造器的第一行

不能同时出现因为:

就像案例中的s3一样,如果同时存在,那么会先调用一次父类的构造器,在调用兄弟类构造器时,又调用了一次super()父类构造器,这样就会调用两次父类构造器

特点

封装

就是对某个类进行设计时,应该把要处理的数据,以及处理这些数据的方法,设计到一个类中

设计规范

继承

使用extends关键字,让一个类与另一个类建立起父子关系

1
2
3
public class B extends A{
  
}
  1. A类称为父类(基类或超类),B类称为子类(派生类)

特点

  1. 子类能继承父类的非私有成员(成员变量,成员方法)

好处

  1. 减少重复性代码书写,提高代码的复用性
  2. 为多态提供支持

单继承

java是单继承的,java中的类不支持多继承,但是支持多层继承

多层继承

1
2
3
public class M{}
public class N extends M{}
public class P extends N{}

子类访问成员特点

在子类方法中访问其他成员(成员变量、成员方法),都是按照就近原则

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
public class Fu{
  String name = "父类名称";
}
public class Zi extends Fu{
  String name = "子类名称";
  
  public void showName(){
    String name = "局部名称";
    System.out.println(name); // 局部名称
    System.out.println(this.name); // 子类名称
    System.out.println(super.name); // 父类名称
  }
}

多态

多态在继承/实现情况下的一种现象,表现为:对象多态、行为多态(同样的形态,在不同的对象中表现是不同的)

对象多态表现形式

1
2
People p1 = new Student();
Perple p2 = new Teacher();

行为多态

1
2
3
4
People p1 = new Student();
p1.run();//行为多态
Perple p2 = new Teacher();
p2.run();//行为多态

前提

  1. 有继承/实现关系(Student,Teacher都继承People)
  2. 存在父类引用子类对象(People p1是父类,引用的是子类Student或Teacher对象)
  3. 存在方法重写(p1.run(),编译看左(People中是否有run方法),运行看右(运行Student中的run方法)其中左右时People p1 = new Student()等号的左右)
  4. 成员变量:编译看左,运行也看左(java中的属性(成员变量)不谈多态)

多态好处

在多态形式下,右边对象是解耦合的,更便于扩展和维护

解耦合:我们将每个功能或模块,开发成一个个的,当我们需要哪个就使用哪个

1
2
3
People p1 = new Student();
// 当我们某天需要使用老师的一些信息,这时候我们直接修改成 People p1 = new Teacher();其他代码不用做修改
// 这样就达到了解耦和

定义方法时,使用父类类型的形参,可以接收一切子类对象,扩展型更强、更便利

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
public static void go(Animal c) { // 如果参数写成:Cat c,那么下方的go(d)就无法实现
  c.cry();
}

public static void main(String[] args){
  Dog d = new Dog();
  go(d);
  
  Cat c = new Cat();
  go(c);
}

类型转换

在上方中,我们如果给Dog定义自己独有的方法LookDoor(),那下面代码就会执行错误:多态下,不能直接调用子类独有的功能

1
2
3
Animal a = new Dog();
a.cry();
a.lookDoor(); // 报错

那我们怎么解决这个问题呢?答案是类型转换

自动类型转换

父类 变量名 = new 子类();

1
People p = new Teacher();

强制类型转换

只要有继承或实现关系的两个类,就可以强制转换

子类 变量名 = (子类)父类变量

1
Teacher t = (Teacher)p;

我们上面的问题,就可以使用强制类型转换来进行解决了

1
2
3
4
Animal a = new Dog();
a.cry();
Dog d1 = (Dog)a;
d1.lookDoor();

类型判断

强制转换可能会碰到一个问题:这个问题在编译阶段(写代码)不报错,但是运行时可能出现强制类型转换异常ClassCastException

1
2
3
4
Animal a = new Dog();
a.cry();

Cat c1 = (Cat)a; // 编译不报错,运行时报错

那么我们怎么解决这个问题呢?类型判断

1
2
3
if(a instanceof Cat){
  Cat c1 = (Cat)a;
}

这样代码就会安全很多

实体类

一种特殊形式的类。

特点:

  1. 实体类中的成员变量都要私有,并且要对外提供相应的get与set方法
  2. 实体类中必须要有一个公共的无参构造器

作用

  1. 可以用于存取对象的数据
  2. 实体类只负责数据的存取,对于数据的业务处理应该交给另一个类的对象来处理(分层思想:一种软件设计思想)

static关键字

可以用来修饰成员变量和成员方法

变量

使用static修饰变量,被称为静态变量

没有static修饰的变量,被称为实例变量(对象变量)

使用

1
2
3
4
5
6
7
# 静态变量
类名.变量名(推荐)
对象名.变量名(不推荐)
  
  
# 实例变量
对象.变量名

使用范围

静态变量属于类,在计算机中只有一份,会被类的全部对象共享。存储在对内存中

实例变量,属于每个对象

使用场景

某个数据只有一份,且希望能够被共享(访问、修改),这时,可以将该成员变量使用static来进行修饰。如:

方法

使用static修饰的成员方法,被称为静态方法

没有static修饰的成员方法,被称为实例方法

使用

1
2
3
4
5
6
7
# 静态方法
类名.类方法(推荐)
对象名.类方法(不推荐)
  
  
# 实例变量
对象.类方法

使用场景

静态方法常用于工具类(工具类中都是一些类方法,每个方法都是用来完成一个功能的)中

注意:

工具类没有创建对象的需求,建议将工具类的构造器进行私有化

1
2
3
4
public class DevelopUtil{
  // 私有化该类
  private DevelopUtil() {}
}

注意

  1. 静态方法中可以直接访问类的静态成员,不可以直接访问实例成员
  2. 实例方法中既可以直接访问静态成员,也可以直接访问实例成员
  3. 实例方法中可以出现this关键字,静态方法中不可以出现this关键字

代码块

一个类由5部分组成:成员变量、构造器、方法、代码块、内部类

静态代码块

特点

类加载时自动执行,由于类只会加载一次,所以静态代码块也只会执行一次

作用

完成类的初始化,例如:对类变量的初始化赋值

格式

1
2
3
static{
  
}

案例

1
2
3
4
5
6
7
public static String name;
public static ArrayList<String> names = new ArrayList<String>();

static{
  names.add("php");
  names.add("java");
}

实例代码块

特点

每次创建对象时自动执行,在构造器前执行

作用

对实例变量进行初始化赋值

格式

1
{}

案例

1
2
3
4
5
6
7
public String name;
public ArrayList<String> names = new ArrayList<String>();

{
  names.add("php");
  names.add("java");
}

final关键字

final可以修饰方法变量

使用final修饰的类,被称为最终类,最终类不能被继承,常见于工具类

使用final修饰的方法,被称为最终方法,最终方法不能被重写

使用final修饰的变量,只能被赋值一次

1
2
3
public static void buy(final double z){ // 使用final保证该参数无法被修改
  
}

final修饰基本数据类型的变量,变量存储的数据不能被改变

fianl修饰引用类型变量,变量存储的地址不能被改变,但地址所指向对象的内容是可以被改变的

常量

使用static final修饰的成员变量

1
public static final String CIRCLE_PAI = "3.14";

使用常量去记录系统的配置信息。

常量性能并不会下降?

因为,程序在编译后,出现常量的地方全部会被替换成设置的字面量,这样可以保证使用常量和直接使用字面量的性能是一样的

关键字abstract

抽象类

使用abstract修改的类,我们称为抽象类

抽象类注意事项:

  1. 抽象类中不一定有抽象方法,有抽象方法的类一定是抽象类
  2. 类该有的成员(成员变量、方法、构造器)抽象类都可以有
  3. 抽象类不能创建对象,仅作为一种特殊的父类,让子类继承并实现
  4. 一个类继承抽象类,必须重写完抽象类的全部抽象方法,否则这个类也必须定义成抽象类

好处

  1. 简化代码(抽象类中的抽象方法,对应的方法体不用写)
  2. 强制子类重写(更好的支持了多态)

应用场景

模版方法设计模式

抽象方法

使用abstract修改的方法,我们称为抽象方法

1
2
3
public abstract class A{
  public abstract void go();
}

关键字Interface

使用interface定义的类,我们称为接口

1
2
3
4
public interface aa{
  // 成员变量(常量)
  // 成员方法(抽象方法)
}

接口最重要的特点:接口不能创建对象

接口是用来被类实现(implements)的,实现接口的类我们称为实现类

1
2
3
修饰符 class 实现类 implement 接口1,接口2,接口3,...{
  
}

实现类实现多个接口,必须重写全部接口的全部抽象方法,否则这个类必须是抽象类

实现类名以Impl结束

好处

  1. 弥补了单继承的不足,类可以同时实现多个接口(面向接口编程时软件开发中目前很流行的开发模型,能更灵活的实现解耦合)

    1
    
    public class Student extends People implements Driver,Doctor{}
    
  2. 让程序可以面向接口编程,这样既不用关心实现的细节,也可以灵活方便的切换各种实现

多继承

接口与接口是多继承的,一个接口可以同时继承多个接口

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
class D implements A,B,C{
	// 实现A,B,C中所有方法
}
interface A{
  void a();
}
interface B{
  void b();
}
interface C{
  void c();
}

可以优化成

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
class D implements A{
	// 实现A,B,C中所有方法
}
interface A extends B,C{
  void a();
}
interface B{
  void b();
}
interface C{
  void c();
}

注意

jdk1.8之前,接口内只能定义常量抽象方法

1
2
3
4
5
6
7
8
// 1.8之前
public interface A{
  // 常量:接口中定义常量可以省略public static final不写,默认会加上
  String ERROR_CODE = "10000";
  
  // 抽象方法:接口中定义抽象方法可以省略 public abstract不写,默认会加上
  void run();
}

jdk8开始,接口新增了三种形式的方法

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
public interface A{
  // 1、默认方法(普通方法、实例方法),必须用default修饰
  // 默认会用public修饰
  // 必须用接口的实现类的对象来调用
  default void run(){
    go();
    System.out.println("run方法在执行");
  }
  
  
  // 2、私有方法(私有的实例方法)(JDK9开始才有的)
  // 只有当前接口内部的默认方法或私有方法来调用
  private void go(){
    System.out.println("go方法在执行");
  }
  
  
  // 3、静态方法
  // 默认会用public修饰
  // 接口的静态方法必须用接口名本身调用 A.inAddr();
 	static void inAddr(){
    System.out.println("inAddr方法在执行");
  }
}




public static void main(String[] args){
  A a = new A();
  a.run();
  
  A.inAddr();
}

新增3种方法目的:

增强了接口的能力,更便于项目的扩展和维护

(假设我们设计的接口A,已经设计好了抽象方法,有1000个类实现了接口A。这时我们的项目新增了一个功能,我们需要在接口A中新增一个抽象方法(jdk8之前),那我们需要修改这1000个实现类,这时候代码维护性就很差了。

而新增的3种方法后,我们可以将这个新增方法,使用这3种方法写到接口A中,直接调用就可以了,不用对1000个实现类进行修改了)

修饰符

修饰符 作用
private 只能本类使用
缺省 本类、同一个包中的类访问
protected 本类,同一个包中的类、子孙类中访问
public 任意位置访问

Object类

Object类是java所有类的祖宗类,我们写的任何一个类,都是object的子类或子孙类

方法重写

产生背景

当子类觉得父类中的某个方法不好用,或者无法满足自己的需求时,子类可以重写一个方法名称、参数列表一样的方法,去覆盖父类的这个方法,这就是方法重写

注意:

重写后,方法的访问,java会遵循就近原则

注意事项

使用Override注解

1
2
3
4
5
6
public class Tigger extends Animal{
  @Override
  public void run(){
    
  }
}

好处:

  1. 安全:java编译器会检查我们方法 重写的格式是否正确
  2. 代码的可读性更好(看到Override就会直到这是重写方法)

子类重写父类方法时,访问权限必须大于或等于父类该方法的权限

重写的方法返回值类型,必须与被重写方法的返回值类型一致,或则范围更小

私有方法、静态方法不能被重写,如果重写会报错的

私有的方法无法被继承,既然没有继承就没有重写之说了。

静态方法时属于父类的,xxxxxx

当我们去掉Override后,方法是覆盖而不是重写

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
public class Animal{
  private void run(){
    
  }
}
public class Tigger extends Animal{
  // 这里的run方法不是重写,而是覆盖
  public void run(){
    
  }
}

使用场景

子类重写Object类的toString()方法,以便返回对象内容

当我们打印一个对象的时候,默认会调用Object类中的toString()方法

内部类

就是定义在一个类内部的类,我们称为内部类。是类的五大成分之一(成员变量方法构造器内部类代码块

使用场景

当一个类的内部,包含了一个完整的事物,且这个事物没有必要单独设计时,就可以把这个事物设计成内部类

1
2
3
4
5
6
public class Car{
  // 内部类
  public class Engine{
    
  }
}

成员内部类

就是类中的普通成员,类似普通的成员变量、成员方法

1
2
3
4
5
6
7
8
public class Outer{
  public class Inner{
    
  }
}

// 使用内部类,Inner内部类是外部类的对象持有的
Outer.Inner in = new Outer().new Inner();

注意:

成员内部类的实例方法中,可以直接访问外部类的实例成员、静态成员

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class Outer{
  public static String schoolName = "学校";
  public static void inAddr(){
    System.out.println("我们在济南");
  }
  private String hobby;
  private void run(){
    System.out.println("run");
  }
  public class Inner{
   public void show(){
     // 成员内部类中,可以直接访问外部类的静态成员
     System.out.println(schoolName);
     // 成员内部类中,可以直接访问外部类的静态方法
     inAddr();
     
     
     // 成员内部类中,可以直接访问外部类的实例成员
     System.out.println(hobby);
     // 成员内部类中,可以直接访问外部类的实例方法
     run();
   } 
  }
}

获取当前外部类对象,格式:外部类名.this

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
public class People{
  private int heartBeat = 110;
  public class Heart{
    private int heartBeat = 95;
    public void show(){
      int heartBeat = 80;
      System.out.println(heartBeat); // 80
      System.out.println(this.heartBeat); // 95
      System.out.println(People.this.heartBeat); // 110 注意这里写法 
    }
  }
}

静态内部类

有static修饰的内部类,属于外部类自己持有

1
2
3
4
5
6
public class Outer{
  public static class Inner{}
}

// 创建静态内部类
Outer.Inner in = new Outer.Inner();

静态内部类也只加载一次

注意:

  1. 静态成员内部类中,可以直接访问外部类的静态成员、静态方法
  2. 静态成员内部类中,不可以直接访问外部类的实例成员、实例方法

局部内部类

定义在方法中、代码块中、构造器里等执行体中的类

1
2
3
4
5
6
public static void main(String[] args){
  // 局部内部类(鸡肋语法,没有什么用)
  class A{
    
  }
}

匿名内部类(重点)

就是一种没有名字的局部内部类

特点

匿名内部类本质是一个子类,并会立即创建一个子类对象

作用

用于更方便的创建一个子类对象

案例

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
abstract class Annimal{
  public abstract void cry();
}
class Dog extends Annimal{
  @Override
  public void cry(){
    System.out.println("狗叫");
  }
}

// 调用
Annimal d = new Dog();
d.cry();

我们使用匿名内部类进行简化:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
abstract class Annimal{
  public abstract void cry();
}

// 调用,我们把=右边的部分称为匿名内部类
Annimal d = new Annimal(){
  @Override
  public void cry(){
    System.out.println("狗叫");
  }
};
d.cry();

匿名内部类的名称:当前类名+$+编号,如:Test$1

使用场景

匿名内部类做为一个参数传输给方法

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
interface Swimming{
 	void swim();
}

public class Trial{
  public static void main(String[] args){
    Swimming s1 = new Swimming(){
      @Override
      public void swim(){
        System.out.print("学生在有用");
      }
    };
    go(s1);
  }
 
  public static void go(Swimming s){
    System.out.println("开始....");
    s.swim();
     System.out.println("结束....");
  }
}

典型的对象回调思想

以上的匿名类在编译后形成的文件名就是:Trial$1.class里面的内容为:

1
2
3
4
5
6
class Trial$1 extends Swimming{
  @Override
  public void swim(){
    System.out.println("学生在有用");
  }
}

Lambda表达式

Lambda表达式是JDK8开始新增的一种语法形式

作用:用于简化内名内部类的代码写法

格式

1
2
3
(被重写方法的形参列表) -> {
  被重写方法的方法体代码
}

Lambda表达式只能简化函数式接口的匿名内部类

函数式接口:

  1. 有且仅有一个抽象方法的接口

将来我们见到的大部分函数式接口,上面都可能会有一个@FunctionalInterface的注解,有该注解的接口就必定是函数式接口

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
// 错误案例
abstract class Animal{
  public abstract void run();
}

Animal a1 = new Animal(){
  @override
  public void run(){
    System.out.println("xxxx");
  }
}
a1.run();

// 以下代码编译错误:并非是函数式接口,因此无法使用Lambda表达式
Animal a2 = () -> {
  System.out.println("xxxx");
}
a2.run();


// 正确案例
@FunctionalInterface
interface Swimming{
  void swim();
}
Swimming s1 = new Swimming(){
  @override
  public void swim(){
    System.out.println("yyyy");
  }
}
s1.swim();

// Lambda简化函数式接口的匿名内部类
Swimming s2 = () -> {
    System.out.println("yyyy");
}
s2.swim();

为什么可以简化呢?

因为简化后的代码可以通过上下文推断出真实的代码形式

new Swimming() 因为上文(左边的接口)就是Swimming

public void swim()方法下文(右边的方法体),Swimming只有一个方法swim,所以就能推断出真实的代码形式

Lambda省略规则

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// 1、参数类型可以省略不写
Arrays.setAll(scores,(int index) -> {
  return scores[index]+10;
});
Arrays.sort(students, (Student a1,Student a2) ->{
  return Double.compare(a1.getHeight(),a2.getHeight());
});

Arrays.setAll(scores,(index) -> { // int类型可以省略不写
  return scores[index]+10;
});
Arrays.sort(students, (a1,a2) ->{ // Student类型可以省略不写
  return Double.compare(a1.getHeight(),a2.getHeight());
});

// 2、如果只有一个参数,参数类型可以省略,同时()也可以省略
Arrays.setAll(scores,index ->{
  return scores[index]+10;
});


// 3、如果Lambda表达式中的**方法体代码只有一行代码**, 可以省略大括号不写,同时要省略分号!此时,如果这行代码是return语句,也必须去掉return不写
Arrays.setAll(scores,index -> scores[index]+10);
Arrays.sort(student,(a1,a2)->Double.compare(a1.getHeight(),a2.getHeight()));

方法引用

进一步简化Lambda表达式的

静态方法引用

1
2
3
// 格式
类名::静态方法
// 使用场景:如果某个Lambda表达式里只是调用一个静态方法,并且前后参数的形式一致,就可以使用静态方法引用  
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
public class Student implements Comparable<Student>{
  public static int compareByHeight(Student s1,Student s2){
    return Double.compare(a1.getHeight(),a2.getHeight());
  }
}

Student[] students = new Student[4];
students[0] = new Student("青鸟",21,'女',168.3);
students[1] = new Student("红薯",25,'女',172.6);
students[2] = new Student("姜泥",19,'女',165.8);
students[3] = new Student("徐凤年",23,'男',183.1);

Arrays.sort(student,(a1,a2)->Double.compare(a1.getHeight(),a2.getHeight()));
Arrays.sort(student,Student::compareByHeight); // 终极简化代码

实例方法引用

1
2
3
// 格式
实例名::实例方法
// 使用场景:如果某个Lambda表达式里只是调用一个实例方法,并且前后参数的形式一致,就可以使用实例方法引用  
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
// Test class类
public int compare(Student s1,Student s2){
  retrun Double.compare(s1.getHeight(),s2.getHeight());
}

// Test class类main方法
Student[] students = new Student[4];
students[0] = new Student("青鸟",21,'女',168.3);
students[1] = new Student("红薯",25,'女',172.6);
students[2] = new Student("姜泥",19,'女',165.8);
students[3] = new Student("徐凤年",23,'男',183.1);

Test t = new Test();
Arrays.sort(students,(o1,o2) -> t.compare(o1,o2));
Arrays.sort(students,t::compare);// 终极简化

特定类型的方法引用

1
2
3
4
// 格式
类型::方法
// 使用场景:
// 如果某个Lambda表达式里只是调用一个实例方法,并且前面参数列表中的第一个参数是作为方法的主调,后面的所有参数都是作为该实例方法的入参的,则此时就可以使用特定类型的方法引用
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
String[] names = {"dle","Angle","caocao","曹超","deby","Cocah"};
// 对上面数组进行排序(默认按照首字母ascii码进行排序)
// 扩展(忽略大小写排序)
Arrays.sort(names,new Comparator<String>(){
  @Override
  public int compare(String s1,String s2){
    return s1.compareToIgnoreCase(s2);
  }
});

Arrays.sort(names,(s1,s2)->s1.compareToIgnoreCase(s2));

Arrays.sort(names,String::compareToIgnoreCase);// 终极简化

构造器引用

1
2
3
4
// 格式
类型::new
// 使用场景:
// 如果某个Lambda表达式里只是在创建对象,并且前后参数情况一致,就可以使用构造器引用

帮助理解硬造的场景

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
@FunctionalInterface
interface Create{
  Car createCar(String name);
}

class Car{
  private String name;
}

Create c1 = new Create(){
  @Override
  public Car createCar(String name){
    return new Car(name);
  }
};
Create c1 = (String name) -> {
  return new Car(name);
};

Create c1 = name -> new Car(name);
// 如果某个Lambda表达式里只是在创建对象,并且前后参数情况一致,就可以使用构造器引用
Create c1 = Car::new;

枚举

是一种特殊类

格式

1
2
3
4
5
// 枚举类的第一行必须罗列的是枚举对象的名称
public enum A{
  X,Y,Z;// 这些名称,本质是常量,每个常量都会记住枚举类的一个对象
  private String name;
}

源码反编译后代码

1
2
3
4
5
public final class A extends java.lang.Enum<A> {
  public static final A X = new A(); // 常量,每个常量记住的都是枚举类的一个对象
  public static final A Y = new A();
  public static final A Z = new A();
}

获取枚举类对象的索引

1
2
3
4
5
6
A a1 = A.X;
A a2 = A.Y;

// 获取索引
System.out.println(a1.ordinal()); // 0
System.out.println(a2.ordinal()); // 1

编译后新增的方法

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
// 新增values方法,用来拿到枚举类的全部对象,放到一个数组中并返回 
A[] al = A.values();
for(int i = 0;i<al.length;i++){
  A a = al[i];
  System.out.println(a);
}

// 新增valueOf()方法,
A y = A.valueOf("Y");
// 等同于
A a = A.Y;
System.out.println(y == a); // true

特点

  1. 枚举类第一行只能罗列一些名称,这些名称都是常量,并且每一个常量记住的都是枚举类的一个对象
  2. 枚举类的构造器都是私有的(写不写都只能是私有的),因此,枚举类对外不能创建对象A a = new A()会报错
  3. 枚举类都是最终类,不可以被继承
  4. 枚举类中,从第二行开始,可以定义类的其他各种成员
  5. 编译器为枚举类新增了结果方法,并且枚举类都是继承:java.lang.Enum类的,从Enum类也会继承到一些方法

应用场景

用来表示一组信息,然后作为参数进行传输

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
public enum Constant{
  DOWN,UP,HALF_UP,DELETE_LEFT;
}

public class Test{
  public static void main(String[] args){
    
	}
  
  public static double handleData(double number,Constat flag){
    switch(flag){
      case Constat.Down:// constant可以去掉
        // 向下取整
        number = Math.floor(number);
        break;
      case UP:
        // 向上取整
        number = Math.ceil(number);
        break;
      case HALF_UP:
        // 四舍五入
        number = Math.round(number);
        break;
       case DELEFT_LEFT:
        // 去掉小数部分
        number = (int)number;
        break;
    }
    return number;
  }
}

以上代码,枚举类相对于常量缺点:性能差点,因为常量是字面量,而枚举类需要new对象,相对来说性能有点影响。

拓展

使用枚举写一个单例

1
2
3
public Enum B{
  X;
}

泛型

定义类、接口、方法时,同时声明一个或多个类型变量(如:<E>),称为泛型类、泛型接口、泛型方法,他们统称为泛型

1
2
3
public class ArrayList<E>{
  ...
}

作用

泛型提供了在编译阶段约束所能操作的数据类型,并自动进行检查的能力!这样可以避免强制类型,及其可能出现的异常

来由

1
2
3
// 开发中很多时候需要统一数据类型(如果不使用泛型,类型没有办法统一,就需要进行强制转换,而进行强制转换可能会出现问题)
ArrayList<String> list = new ArrayList<String>(); // jdk1.7之后后面的<String>中的String可以不写
list.add("哈辉");

泛型的本质:**把具体的数据类型作为参数传递给类型变量 **

泛型类

1
2
3
public class ArrayList<E>{
  
}

类中有<E>这样的将类型作为参数传递给类,我们就把这种类称为泛型类

其中E被称为泛型变量,建议使用大写字母,常用有:E,T,K,V

泛型接口

1
2
3
4
5
6
修饰符 interface 接口名<类型变量,类型变量...>{
  
}
public interface A<E>{
  ....
}

案例

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
public class Student{

}

public interface Data<T> {
    void add(T t);
    void delete(T t);
    void update(T t);
    T get(T t);
}

public class StudentData implements Data<Student>{
    @Override
    public void add(Student student) {

    }

    @Override
    public void delete(Student student) {

    }

    @Override
    public void update(Student student) {

    }

    @Override
    public Student get(Student student) {
        return null;
    }
}

泛型方法

声明方法时,携带<E>的泛型类型的方法,我们称为泛型方法

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
修饰符 <类型变量,类型变量...> 返回值类型 方法名(形参列表){
  
}

String[] names = {"java","go","python","php"};
String rs1 = printArray(names); // 避免对类型进行强制转换

Student[] students = new Student[60];
Student rs2 = printArray(students); // 避免对类型进行强制转换

public static <T> T printArray(T[] arr){
  
}

好处:让方法更通用(如上方代码可以同时接收Student与String数组),同时避免对类型进行强制转换(传递什么类型返回什么类型)

通配符与泛型上下限

就是“?”,可以在“使用泛型”的时候代表一切类型;E、T、K、V是在定义泛型的时候使用

泛型的上下限

泛型上限:? extends Car,?能接收的必须是Car或者其子类

泛型下限:? super Car,?能接收的必须是Car或者其父类

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
public class Test {
    public static void main(String[] args) {
        ArrayList<TSL> tsls = new ArrayList<>();
        tsls.add(new TSL());
        go(tsls);

        ArrayList<LX> lxs = new ArrayList<>();
        lxs.add(new LX());
        go(lxs);

        ArrayList<Dog> dogs = new ArrayList<>();
        dogs.add(new Dog());
        // go(dogs);
    }

//    public static void go(ArrayList<TSL> tsls){} // go(tsls); 可以正常执行
//    public static void go(ArrayList<LX> lxs){}   // go(lxs); 可以正常执行
    // 那怎么才能让两个都可以正常执行能
    // public static void go(ArrayList<?> tsls){} // 使用通配符? 就可以都执行了
    // go(dogs); 也能执行,这就有问题了,那怎么才能排除Dog呢

    // ? extends Car 泛型上限:必须是Car或Car的子类
    // ? super Car 泛型下限:必须是Car或Car的父类
    public static void go(ArrayList<? extends Car> tsls){}
}

class Car{}
class TSL extends Car{}
class LX extends Car{}

class Dog{}

注意

  1. 泛型是工作在编译阶段的,一旦程序编译成class文件,class文件中就不存在泛型了,这就是泛型擦除
  2. 泛型不能直接支持基本数据类型,只能支持对象类型(引用数据类型)

包装类

把基本类型的数据包装成对象

基本数据类型 对应包装类(引用数据类型)
byte Byte
short Short
int Integer
long Long
char Character
float Float
double Double
boolean Boolean

案例

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
int a = 12;
// 1、手动包装:int成对象
Integer it1 = new Integer(a); // 过时了,jdk9就淘汰了
Integer it2 = Integer.valueOf(a); // 推荐,缓存了-128到127之间的数据成为一个个对象(不会每次都new),如果不在这个范围,就重新new一个返回

// 2、自动装箱机制:基本类型的数据可以直接变成对象赋值给包装类
Integer it3 = 12; // 等同于上面的手动包装

// 面试题:
Integer it4 = 12;
Integer it5 = 12;
System.out.println(it4 == it5); // true jdk9之后使用了缓存,缓存了-128到127之间的数据为一个个对象

Integer it6 = 128;
Integer it7 = 128;
System.out.println(it6 == it7); // false


// 3、自动拆箱机制:包装类的对象可以直接赋值给基本数据类型
int it8 = it7;
System.out.println(it8); // 128

新功能

包装类可以把基本数据类型转换成字符串

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
int a1 = 32;
String rs = Integer.toString(a1);
System.out.println(rs+1); // 231

Integer i = 23;
String rs2 = i.toString();
System.out.println(rs2+1); // 231

// 这种最简单
int a2 = 23;
String rs3 = a2 + "";
System.out.println(rs3+1); // 231

把字符串数值转换成对应的基本数据类型(使用包装类,很有用)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
String ageStr = "23";
int age = Integer.parseInt(ageStr);
// 等同于 int age = Integer.valueOf(ageStr);
System.out.println(age+1); // 24


String scoreStr = "99.5";
int score = Double.parseDouble(scoreStr);
// 等同于 int score = Integer.valueOf(scoreStr);
System.out.println(score+0.5); // 100.0

意义

由于泛型和集合中都不支持基本数据类型,因此包装类在集合和泛型中会被大量使用

使用 Hugo 构建
主题 StackJimmy 设计