面向对象
对象本质上是一种特殊的数据结构
类是对象的模版,
对象执行原理
一些注意事项
-
成员变量本身存在默认值
数据类型 |
明细 |
默认值 |
基本类型 |
byte、short、char、int、long |
0 |
基本类型 |
double,float |
0.0 |
基本类型 |
boolean |
false |
引用类型 |
数组、string |
null |
-
一个代码文件中,可以有多个class类,但只能一个用public修饰,且public修饰的类名必须称为代码文件名
-
对象与对象之间的数据不会相互影响,但多个变量指向同一个对象时就会相互影响了
-
如果某个对象没有一个变量引用它(对象设置为null),则该对象无法被操作了,该对象会成为所谓的垃圾对象
关键字this
可以用在方法中,代表当前对象
主要在类方法中,访问类属性(常用来解决对象的成员变量与方法内部变量的名称一样时,导致访问冲突问题)
构造器
构造器的名称必须与类名一样,且没有返回值类型。
1
2
3
4
5
6
7
8
9
10
11
|
public class Student{
// 无参构造器
public Student() {
}
// 有参构造器
public Student(String name) {
}
}
|
特点:
- 对象在被创建时,对象会去调用构造器
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{
}
|
- A类称为父类(基类或超类),B类称为子类(派生类)
特点
- 子类能继承父类的非私有成员(成员变量,成员方法)
好处
- 减少重复性代码书写,提高代码的复用性
- 为多态提供支持
单继承
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();//行为多态
|
前提
- 有继承/实现关系(Student,Teacher都继承People)
- 存在父类引用子类对象(People p1是父类,引用的是子类Student或Teacher对象)
- 存在方法重写(p1.run(),编译看左(People中是否有run方法),运行看右(运行Student中的run方法)其中左右时
People p1 = new Student()
等号的左右)
- 成员变量:编译看左,运行也看左(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;
}
|
这样代码就会安全很多
实体类
一种特殊形式的类。
特点:
- 实体类中的成员变量都要私有,并且要对外提供相应的get与set方法
- 实体类中必须要有一个公共的无参构造器
作用
- 可以用于存取对象的数据
- 实体类只负责数据的存取,对于数据的业务处理应该交给另一个类的对象来处理(分层思想:一种软件设计思想)
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() {}
}
|
注意
- 静态方法中可以直接访问类的静态成员,不可以直接访问实例成员
- 实例方法中既可以直接访问静态成员,也可以直接访问实例成员
- 实例方法中可以出现this关键字,静态方法中不可以出现this关键字
代码块
一个类由5部分组成:成员变量、构造器、方法、代码块、内部类
静态代码块
特点
类加载时自动执行,由于类只会加载一次,所以静态代码块也只会执行一次
作用
完成类的初始化,例如:对类变量的初始化赋值
格式
案例
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
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
修改的类,我们称为抽象类
抽象类注意事项:
- 抽象类中不一定有抽象方法,有抽象方法的类一定是抽象类
- 类该有的成员(成员变量、方法、构造器)抽象类都可以有
- 抽象类不能创建对象,仅作为一种特殊的父类,让子类继承并实现
- 一个类继承抽象类,必须重写完抽象类的全部抽象方法,否则这个类也必须定义成抽象类
好处
- 简化代码(抽象类中的抽象方法,对应的方法体不用写)
- 强制子类重写(更好的支持了多态)
应用场景
模版方法设计模式
抽象方法
使用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
|
public class Student extends People implements Driver,Doctor{}
|
-
让程序可以面向接口编程,这样既不用关心实现的细节,也可以灵活方便的切换各种实现
多继承
接口与接口是多继承的,一个接口可以同时继承多个接口
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(){
}
}
|
好处:
- 安全:java编译器会检查我们方法 重写的格式是否正确
- 代码的可读性更好(看到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
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表达式只能简化函数式接口的匿名内部类
函数式接口:
- 有且仅有一个抽象方法的接口
将来我们见到的大部分函数式接口,上面都可能会有一个@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
|
特点
- 枚举类第一行只能罗列一些名称,这些名称都是常量,并且每一个常量记住的都是枚举类的一个对象
- 枚举类的构造器都是私有的(写不写都只能是私有的),因此,枚举类对外不能创建对象
A a = new A()会报错
- 枚举类都是最终类,不可以被继承
- 枚举类中,从第二行开始,可以定义类的其他各种成员
- 编译器为枚举类新增了结果方法,并且枚举类都是继承: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{}
|
注意
- 泛型是工作在编译阶段的,一旦程序编译成class文件,class文件中就不存在泛型了,这就是泛型擦除
- 泛型不能直接支持基本数据类型,只能支持对象类型(引用数据类型)
包装类
把基本类型的数据包装成对象
基本数据类型 |
对应包装类(引用数据类型) |
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
|
意义
由于泛型和集合中都不支持基本数据类型,因此包装类在集合和泛型中会被大量使用