目录
包
包 (package) 是组织类的一种方式 .使用包的主要目的是保证类的唯一性 .
导入包中的类
例如:
import java.util.Arrays;
public class TestDemo {
public static void main(String[] args) {
int[] array = {1, 2, 3, 4, 5, 6};
System.out.println(Arrays.toString(array));
}
}
使用toString就必须导入Arrays这个包,即java.util.Arrays。因为这个方法由类名Arrays进行调用则这个方法就是静态方法。
并且只能导入一个具体的类,不能导入一个具体的包。
或者可以这样写:
public class TestDemo {
public static void main(String[] args) {
java.util.Date date = new java.util.Date();
// 得到一个毫秒级别的时间戳
System.out.println(date.getTime());
}
}
如果需要使用 java.util 中的其他类 , 可以使用 import java.util.*
import java.util.*;
public class Test {
public static void main(String[] args) {
Date date = new Date();
// 得到一个毫秒级别的时间戳
System.out.println(date.getTime());
}
}
import java.util.*中的*叫做通配符。作用:导入这个包低下所有的类。
并且在Java处理的时候,用到谁,就拿谁。
import java.util.*;
import java.sql.*;
public class Test {
public static void main(String[] args) {
// util 和 sql 中都存在一个 Date 这样的类, 此时就会出现歧义, 编译出错
Date date = new Date();
System.out.println(date.getTime());
}
}
// 编译出错
Error:(5, 9) java: 对Date的引用不明确
java.sql 中的类 java.sql.Date 和 java.util 中的类 java.util.Date 都匹配
import java.util.*;
import java.sql.*;
public class Test {
public static void main(String[] args) {
java.util.Date date = new java.util.Date();
System.out.println(date.getTime());
}
}
import static java.lang.System.*;
public class Test {
public static void main(String[] args) {
out.println("hello");
}
}
静态导入的方式写起来更方便一些但是静态导入用的比较少,了解即可
将类放到包中
- 在文件的最上方加上一个 package 语句指定该代码在哪个包中.
- 包名需要尽量指定成唯一的名字, 通常会用公司的域名的颠倒形式(例如 com.bit.demo1 ).
包名要和代码路径相匹配 . 例如创建 com.bit.demo1 的包 , 那么会存在一个对应的路径 com/bit/demo1 来存储代码. 如果一个类没有 package 语句 , 则该类被放到一个默认包中 .
操作步骤
2) 在弹出的对话框中输入包名, 例如 com.bit.demo1
3) 在包中创建类, 右键包名 -> 新建 -> 类, 然后输入类名即可.
4) 此时可以看到我们的磁盘上的目录结构已经被 IDEA 自动创建出来了
5) 同时我们也看到了, 在新创建的 Test.java 文件的最上方, 就出现了一个 package 语句
包名必须是小写字母。
包的访问权限控制
我们已经了解了类中的 public 和 private包访问权限,(只能在当前包中使用)当你不加任何访问修饰限定词的时候。
在不同包中
此时val默认包访问权限,使用报错
常见的系统包
继承
面向对象编程
封装:不必公开的数据成员和方法 使用private进行修饰 意义:安全性
继承:对共性的抽取。使用关键字进行处理的。 意义:对代码进行重复的使用
多态:
组合:
// Animal.java
public class Animal {
public String name;
public Animal(String name) {
this.name = name;
}
public void eat(String food) {
System.out.println(this.name + "正在吃" + food);
}
}
// Cat.java
class Cat {
public String name;
public Cat(String name) {
this.name = name;
}
public void eat(String food) {
System.out.println(this.name + "正在吃" + food);
}
}
// Bird.java
class Bird {
public String name;
public Bird(String name) {
this.name = name;
}
public void eat(String food) {
System.out.println(this.name + "正在吃" + food);
}
public void fly() {
System.out.println(this.name + "正在飞 ︿( ̄︶ ̄)︿");
}
}
这个代码我们发现其中存在了大量的冗余代码 .仔细分析 , 我们发现 Animal 和 Cat 以及 Bird 这几个类中存在一定的关联关系 :
- 这三个类都具备一个相同的 eat 方法, 而且行为是完全一样的.
- 这三个类都具备一个相同的 name 属性, 而且意义是完全一样的.
- 从逻辑上讲, Dog 和 Bird 都是一种 Animal (is - a 语义).
Animal 这样被继承的类, 我们称为 父类 , 基类 或 超类 , 对于像 Cat 和 Bird 这样的类, 我们称为 子类 , 派生类 和现实中的儿子继承父亲的财产类似, 子类也会继承父类的字段和方法, 以达到 代码重用的效果。
如:
package com.bit.demo2;
class Animal {
public String name;
public int age;
public void eat() {
System.out.println("eat()");
}
}
class Dog extends Animal{
public void eat() {
System.out.println("eat()");
}
}
class Bird extends Animal{
public String wing;
public void eat() {
System.out.println("eat()");
}
public void fly() {
System.out.println(name+"fly()");
}
}
public class TestDemo {
public static void main(String[] args) {
Dog dog = new Dog();
System.out.println(dog.name);
dog.eat();
}
}
语法规则
class 子类 extends 父类 {
}
- 使用 extends 指定父类.
- Java 中一个子类只能继承一个父类 (而C++/Python等语言支持多继承).
- 子类会继承父类的所有 public 的字段和方法.
- 对于父类的 private 的字段和方法, 子类中是无法访问的.
- 子类的实例中, 也包含着父类的实例. 可以使用 super 关键字得到父类实例的引用
当子类构造的同时,先帮助父类进行构造。并且通过super 来调用父类的构造器
class Animal {
public String name;
public int age;
public Animal(String name,int age) {
this.name = name;
this.age = age;
}
public void eat() {
System.out.println("eat()");
}
}
class Dog extends Animal{
public Dog(String name, int age) {
super(name, age);
}
public void eat() {
System.out.println("eat()");
}
}
一个类没有构造方法时,它会默认有一个不带参数的构造方法。但是一个类提供了带有参数的构造方法时就不会有不带参数的构造方法了。
而super只能在当前构造器中使用,并且只能在第一行。
super的使用:
super:【不能出现在静态方法中】父类对象的引用
- super();//调用父类的构造方法
- super.func();
- super.data;
下面是他们的内存布局:
class Animal {
public String name;
public int age;
private int count;
public Animal(String name,int age) {
this.name = name;
this.age = age;
}
public void eat() {
System.out.println("eat()");
}
}
class Dog extends Animal{
public Dog(String name, int age) {
super(name, age);
}
public void eat() {
System.out.println("eat()");
}
}
class Bird extends Animal{
public String wing;
public Bird(String name, int age,String wing) {
super(name, age);
this.wing =wing;
}
public void fly() {
System.out.println(super.name+"fly()"+super.age);
}
}
public class TestDemo {
public static void main(String[] args) {
Dog dog = new Dog("haha",19);
dog.eat();
Bird bird = new Bird("heihei",18,"我要的飞翔");
bird.fly();
bird.eat();
}
}
注意:当子类和父类有同名的字段时优先访问自己的字段,除非用super关键字。
protected 关键字
刚才我们发现 , 如果把字段设为 private, 子类不能访问 . 但是设成 public, 又违背了我们 " 封装 " 的初衷 .两全其美的办法就是 protected 关键字 .
- 对于类的调用者来说, protected 修饰的字段和方法是不能访问的
- 对于类的 子类 和 同一个包的其他类 来说, protected 修饰的字段和方法是可以访问的
小结 : Java 中对于字段和方法共有四种访问权限
- private: 类内部能访问, 类外部不能访问
- 默认(也叫包访问权限): 类内部能访问, 同一个包中的类可以访问, 其他类不能访问.
- protected: 类内部能访问, 子类和同一个包中的类可以访问, 其他类不能访问.
- public : 类内部和类的调用者都能访问
- 我们希望类要尽量做到 "封装", 即隐藏内部实现细节, 只暴露出 必要 的信息给类的调用者.
- 因此我们在使用的时候应该尽可能的使用 比较严格 的访问权限. 例如如果一个方法能用 private, 就尽量不要用public.
- 另外, 还有一种 简单粗暴的做法: 将所有的字段设为 private, 将所有的方法设为 public. 不过这种方式属于是对访问权限的滥用, 还是更希望同学们能写代码的时候认真思考, 该类提供的字段方法到底给 "谁" 使用(是类内部自己用, 还是类的调用者使用, 还是子类使用)
final 关键字
如果一个类不想被继承,我们可以设置为final修饰。final int a = 10; //常量 不可以被修改。final class A //代表整个类不可以被继承
我们平时是用的 String 字符串类, 就是用 fifinal 修饰的, 不能被继承。
组合
public class Student {
...
}
public class Teacher {
...
}
public class School {
public Student[] students;
public Teacher[] teachers;
}
多态
向上转型
Dog dog =new Dog("haha",19);
Animal animal = dog;
Animal animal = new Dog("haha",19);
如上代码,父类引用 引用 子类对象这种写法称为 向上转型.
什么时候是向上转型的时机呢?
- 直接赋值
- 方法传参
- 方法返回
public class TestDemo {
public static void func(Animal animal) {
}
public static Animal func2() {
Dog dog = new Dog("heihei",20);
return dog;
}
public static void main(String[] args) {
Dog dog = new Dog("xiaoxiao",21);
Animal animal = new Dog("haha",19);//直接赋值
func(dog); //方法传参
func2(); //方法返回
}
}
动态绑定
class Animal {
public String name;
public int age;
private int count;
public Animal(String name,int age) {
this.name = name;
this.age = age;
}
public void eat() {
System.out.println("eat()");
}
}
class Dog extends Animal{
public Dog(String name, int age) {
super(name, age);
}
public void eat() {
System.out.println(name+"狼吞虎咽的eat()");
}
}
class Bird extends Animal{
public String wing;
public Bird(String name, int age,String wing) {
super(name, age);
this.wing =wing;
}
public void fly() {
System.out.println(super.name+"fly()"+super.age);
}
}
public class TestDemo {
public static void func(Animal animal) {
}
public static Animal func2() {
Dog dog = new Dog("heihei",20);
return dog;
}
public static void main(String[] args) {
Animal animal = new Dog("haha",19);
animal.eat();
}
}
结果是:
haha狼吞虎咽的eat()
方法重写
关于重写的注意事项1. 重写和重载完全不一样 . 不要混淆 ( 思考一下 , 重载的规则是啥 ?)2. 普通方法可以重写 , static 修饰的静态方法不能重写 .3. 重写中子类的方法的访问权限不能低于父类的方法访问权限 .4. 重写的方法返回值类型不一定和父类的方法相同 ( 但是建议最好写成相同, 特殊情况 除外).
Java 有两种多态:1、编译时多态。 2、运行时多态。
理解多态
代码示例 : 打印多种形状
class Shape {
public void draw() {
}
}
class Cycle extends Shape {
@Override
public void draw() {
System.out.println("○");
}
}
class Rect extends Shape {
@Override
public void draw() {
System.out.println("□");
}
}
class Flower extends Shape {
@Override
public void draw() {
System.out.println("♣");
}
}
// Test.java
public class Test {
public static void main(String[] args) {
Shape shape1 = new Flower();
Shape shape2 = new Cycle();
Shape shape3 = new Rect();
drawMap(shape1);
drawMap(shape2);
drawMap(shape3);
}
// 打印单个图形
public static void drawShape(Shape shape) {
shape.draw();
}
}
当类的调用者在编写 drawMap 这个方法的时候 , 参数类型为 Shape ( 父类 ), 此时在该方法内部并 不知道 , 也不关注 当 前的 shape 引用指向的是哪个类型 ( 哪个子类 ) 的实例 . 此时 shape 这个引用调用 draw 方法可能会有多种不同的表现 (和 shape 对应的实例相关 ), 这种行为就称为 多态
class Shape {
public void draw() {
System.out.println("Shape::draw()");
}
}
class Rect extends Shape{
@Override
public void draw() {
System.out.println("♦");
}
}
class Flower extends Shape {
@Override
public void draw() {
System.out.println("❀");
}
}
class Triangle extends Shape{
@Override
public void draw() {
System.out.println("△");
}
}
class Cycle extends Shape {
@Override
public void draw() {
System.out.println("●");
}
}
public class TestDemo {
public static void main(String[] args) {
Rect rect = new Rect();
Flower flower = new Flower();
Triangle triangle = new Triangle();
Shape[] shapes = {triangle,rect,triangle,rect,flower,new Cycle()};
for (Shape shape : shapes) {
shape.draw();
}
}
输出结果:
△
♦
△
♦
❀
●
向下转型
class Animal {
public String name;
public int age;
private int count;
public Animal(String name,int age) {
this.name = name;
this.age = age;
}
public void eat() {
System.out.println("eat()");
}
}
class Dog extends Animal{
public Dog(String name, int age) {
super(name, age);
}
public void eat() {
System.out.println(name+"狼吞虎咽的eat()");
}
}
class Bird extends Animal{
public String wing;
public Bird(String name, int age,String wing) {
super(name, age);
this.wing =wing;
}
public void fly() {
System.out.println(super.name+"fly()"+super.age);
}
}
public class TestDemo {
public static void func(Animal animal) {
}
public static Animal func2() {
Dog dog = new Dog("heihei",20);
return dog;
}
public static void main(String[] args) {
Animal animal = new Dog("haha",19);
Dog dog = (Dog) animal;
dog.eat();
}
}
以上就是向下转型。
注意事项编译过程中 , animal 的类型是 Animal, 此时编译器只知道这个类中有一个 eat 方法 , 没有 fly 方法 .虽然 animal 实际引用的是一个 Bird 对象 , 但是编译器是以 animal 的类型来查看有哪些方法的 .对于 Animal animal = new Dog("haha",19); 这样的代码 ,
- 编译器检查有哪些方法存在, 看的是 Animal 这个类型
- 执行时究竟执行父类的方法还是子类的方法, 看的是 Bird 这个类型.
那么想实现刚才的效果 , 就需要向下转型
但是这样的向下转型有时是不太可靠的. 例如
animal 本质上引用的是一个Dog 对象, 是不能转成 Bird 对象的. 运行时就会抛出异常.
所以, 为了让向下转型更安全, 我们可以先判定一下看看 animal 本质上是不是一个 Bird 实例, 再来转换
Animal animal = new Dog("haha",19);
if (animal instanceof Bird) {
Bird bird = (Bird)animal;
bird.fly();
}
instanceof 可以判定一个引用是否是某个类的实例 . 如果是 , 则返回 true. 这时再进行向下转型就比较安全了
在构造方法中调用重写的方法(一个坑)
class B {
public B() {
// do nothing
func();
}
public void func() {
System.out.println("B.func()");
}
}
class D extends B {
private int num = 1;
@Override
public void func() {
System.out.println("D.func() " + num);
}
}
public class Test {
public static void main(String[] args) {
D d = new D();
}
}
// 执行结果
D.func() 0
- 构造 D 对象的同时, 会调用 B 的构造方法.
- B 的构造方法中调用了 func 方法, 此时会触发动态绑定, 会调用到 D 中的 func
- 此时 D 对象自身还没有构造, 此时 num 处在未初始化的状态, 值为 0.
抽象类
1.包括抽象方法的类叫做抽象类。
2.什么是抽象方法,一个没有具体实现的方法,被abstract修饰。
3.抽象类是不可以被实例化的。new
4.因为不能实例化,使用只能被继承。
5.抽象类中也可以包含普通类一样的成员和方法
6.一个普通类继承一个抽象类,那么这个普通类当中,需要重写这个抽象类当中所有的抽象 方法。
7.抽象类最大的作用就是被继承。
8.一个类A如果继承了一个类B,那么这个抽象类A,可以不用实现抽象父类B的抽象方法。
9.结合第八点,当类A再次被普通类继承后,那么A和B这两个抽象类当中的抽象方法,必须 重写。
10.抽象类不能被final修饰,抽象方法也不可能被final修饰。
11.抽象方法不能是 private 的
如:
abstract class Shape {
public abstract void draw();//抽象方法
}
abstract class A extends Shape{
public abstract void funcA();
}
class B extends A {
@Override
public void funcA() {
}
@Override
public void draw() {
}
}
class Rect extends Shape {
@Override
public void draw() {
System.out.println("♦");
//super.func();
}
}
class Flower extends Shape {
@Override
public void draw() {
System.out.println("❀");
}
}
class Triangle extends Shape {
@Override
public void draw() {
System.out.println("△");
}
}
class Cycle extends Shape {
@Override
public void draw() {
System.out.println("●");
}
}
public class TestDemo {
public static void drawMap(Shape shape) {
shape.draw();
}
public static void main(String[] args) {
Shape shape = new Rect();
drawMap(shape);
Cycle cycle = new Cycle();
drawMap(cycle);
//Shape shape2 = new Shape();
}
Shape shape = new Shape();
// 编译出错
Error:(30, 23) java: Shape是抽象的; 无法实例化
abstract class Shape {
abstract private void draw();
}
// 编译出错
Error:(4, 27) java: 非法的修饰符组合: abstract和private
abstract class Shape {
abstract public void draw();
void func() {
System.out.println("func");
}
}
class Rect extends Shape {
...
}
public class Test {
public static void main(String[] args) {
Shape shape = new Rect();
shape.func();
}
}
// 执行结果
func
接口
接口:
- 使用interface来修饰的。interface IA{ }
- 接口当中的普通方法,不能有具体的实现。非要实现,只能通过关键字default来修饰,这个方法。
- 接口当中,可以有static的方法。
- 里面的所有的方法都是public的。
- 抽象方法,默认是public abstract的。
- 接口是不可以被通过关键字new来实例化的。
- 类和接口之间的关系是通过implements实现了。
- 当一个类实现了一个接口,就必须要重写接口当中的抽象方法。
interface IShape {
void draw();//抽象方法
/* default public void func(){
System.out.println("fsafa");
}*/
/* default public void func2(){
System.out.println("fsafa");
}
public static void funcStatic() {
System.out.println("fafa");
}*/
}
class Rect implements IShape {
@Override
public void draw() {
System.out.println("♦");
}
/*@Override
public void func() {
System.out.println("重写接口当中的默认方法");
}*/
}
class Flower implements IShape {
@Override
public void draw() {
System.out.println("❀");
}
}
class Triangle implements IShape {
@Override
public void draw() {
System.out.println("△");
}
}
class Cycle implements IShape {
@Override
public void draw() {
System.out.println("●");
}
}
public class TestDemo {
public static void drawMap(IShape iShape) {
iShape.draw();
}
public static void main(String[] args) {
Rect rect = new Rect();
Flower flower = new Flower();
drawMap(rect);
drawMap(flower);
//IShape iShape = new IShape();error
IShape iShape = new Rect();
iShape.draw();
}
}
- 接口当中的成员变量,默认是publicstatic final修饰的
- 当一个类实现一个接口之后,重写这个方法的时候,这个方法前面必须加上public
- 一个类可以通过extends继承一个抽象类或者普通类,但是只能继承一个类。同时,也可以通过implements实现多个接口,接口之间用逗号隔开就好。
interface IA {
int A = 10;
void funcA();//public abstract
}
interface IB {
void funcB();
}
abstract class BClass {
}
class AClass extends BClass implements IA,IB {
public void funcA() {
System.out.println("A::funcA()");
System.out.println(A);
}
@Override
public void funcB() {
System.out.println("A::funcB()");
}
}
public class TestDemo {
public static void main(String[] args) {
final int a = 10;
System.out.println(a);
}
}
interface IShape {
void draw();
public static final int num = 10;
}
其中的 public, static, final 的关键字都可以省略. 省略后的 num 仍然表示 public 的静态常量.
1. 我们创建接口的时候 , 接口的命名一般以大写字母 I 开头 .2. 接口的命名一般使用 " 形容词 " 词性的单词 .3. 阿里编码规范中约定 , 接口中的方法和属性不要加任何修饰符号 , 保持代码的简洁性
一个错误的代码
interface IShape {
abstract void draw() ; // 即便不写public,也是public
}
class Rect implements IShape {
void draw() {
System.out.println("□") ; //权限更加严格了,所以无法覆写。
}
}
接口与接口直接可以通过extends来操作他们的关系,此时,这里面意为:拓展
一个接口A 通过 extends来 拓展另一个接口B的功能。此时当一个类C 通过implements实现这个A接口时,此时重写的方法不仅仅是A的抽象方法,还有他从C接口拓展来的方法。
interface IA1 {
void funcA();
}
interface IB1 extends IA1{
void funcB();
}
class C implements IB1{
@Override
public void funcB() {
System.out.println("fsafsa");
}
@Override
public void funcA() {
System.out.println("ewqrewqrq");
}
}
public class TestDemo {
}
实现多个接口
有的时候我们需要让一个类同时继承自多个父类 . 这件事情在有些编程语言通过 多继承 的方式来实现的 .然而 Java 中只支持单继承, 一个类只能 extends 一个父类. 但是可以同时实现多个接口, 也能达到多继承类似的效果.
class Animal {
protected String name;
public Animal(String name) {
this.name = name;
}
}
//不是所有的动物都会飞,所以,不能写到Animal类当中。如果写到另一个类当中,也不行,因为
//一个类不能继承多个类。 所以,接口。
//另外我们再提供一组接口, 分别表示 "会飞的", "会跑的", "会游泳的"
interface IFlying {
void fly();
}
interface IRunning {
void run();
}
interface ISwimming {
void swimming();
}
//鸟,会飞的
class Bird extends Animal implements IFlying{
public Bird(String name) {
super(name);
}
@Override
public void fly() {
System.out.println(this.name+" 正在飞!");
}
}
//青蛙, 既能跑, 又能游(两栖动物)
class Frog extends Animal implements IRunning,ISwimming{
public Frog(String name) {
super(name);
}
@Override
public void run() {
System.out.println(this.name+" 正在跑!");
}
@Override
public void swimming() {
System.out.println(this.name+" 正在游泳!");
}
}
//还有一种神奇的动物, 水陆空三栖, 叫做 "鸭子"
class Duck extends Animal implements IRunning,ISwimming,IFlying{
public Duck(String name) {
super(name);
}
@Override
public void fly() {
System.out.println(this.name+" 正在飞!");
}
@Override
public void run() {
System.out.println(this.name+" 正在跑!");
}
@Override
public void swimming() {
System.out.println(this.name+" 正在游泳!");
}
}
//机器人
class Roobot implements IRunning{
@Override
public void run() {
System.out.println("机器人再跑!");
}
}
public class Test4 {
public static void runFunc(IRunning iRunning) {
iRunning.run();
}
public static void swimmingFunc(ISwimming iSwimming) {
iSwimming.swimming();
}
public static void flyingFunc(IFlying iFlying) {
iFlying.fly();
}
public static void main(String[] args) {
runFunc(new Duck("鸭子"));
runFunc(new Frog("青蛙"));
runFunc(new Roobot());
}
}
这样设计有什么好处呢? 时刻牢记多态的好处, 让程序猿忘记类型. 有了接口之后, 类的使用者就不必关注具体类型, 而只关注某个类是否具备某种能力.
Clonable 接口和深拷贝
Java 中内置了一些很有用的接口 , Clonable 就是其中之一 .Object 类中存在一个 clone 方法 , 调用这个方法可以创建一个对象的 " 拷贝 ". 但是要想合法调用 clone 方法 , 必须要先实现 Clonable 接口 , 否则就会抛出 CloneNotSupportedException 异常 .
class Animal implements Cloneable {
private String name;
@Override
public Animal clone() {
Animal o = null;
try {
o = (Animal)super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return o;
}
}
public class Test {
public static void main(String[] args) {
Animal animal = new Animal();
Animal animal2 = animal.clone();
System.out.println(animal == animal2);
}
}
// 输出结果
// false
深拷贝与浅拷贝
浅拷贝 指的是你的类本身被拷贝,而没有拷贝类本身属性中的类
深拷贝 指的是包含类本身和属性类在内的所有类的拷贝。
简单点说:
就是浅拷贝的两个对象中的属性还会指向同一个类,而深拷贝则全部单独了。也就是说深拷贝把关联关系也拷贝了。
public class Test {
static class A implements Cloneable {
public int num = 0;
@Override
public A clone() throws CloneNotSupportedException {
return (A)super.clone();
}
}
static class B implements Cloneable {
public A a = new A();
@Override
public B clone() throws CloneNotSupportedException {
return (B)super.clone();
}
}
public static void main(String[] args) throws CloneNotSupportedException {
B b = new B();
B b2 = b.clone();
b.a.num = 10;
System.out.println(b2.a.num);
}
}
// 执行结果
10
Comparable和Comparator接口
当排序一个类的数组时:
import java.util.Arrays;
class Student {
public int age;
public String name;
public double score;
public Student(int age, String name, double score) {
this.age = age;
this.name = name;
this.score = score;
}
@Override
public String toString() {
return "Student{" +
"age=" + age +
", name='" + name + '\'' +
", score=" + score +
'}';
}
}
public class TestDemo {
public static void main(String[] args) {
int[] array = {1,21,3,14,5,16};
System.out.println(Arrays.toString(array));
Arrays.sort(array);
System.out.println(Arrays.toString(array));
}
}
//结果
//[1, 21, 3, 14, 5, 16]
//[1, 3, 5, 14, 16, 21]
但如果换成类的数组
Student[] students = new Student[3];
students[0] = new Student(12,"bit",98.9);
students[1] = new Student(6,"abc",88.9);
students[2] = new Student(18,"zhangsan",18.9);
Arrays.sort(students);
Exception in thread "main" java.lang.ClassCastException: Student cannot be cast to java.lang.Comparable因为它不知道排序什么元素。
如果自定义的类型数据要进行比较 一定要实现可以比较的接口。
如Comparable接口:
class Student implements Comparable<Student>{
public int age;
public String name;
public double score;
public Student(int age, String name, double score) {
this.age = age;
this.name = name;
this.score = score;
}
@Override
public String toString() {
return "Student{" +
"age=" + age +
", name='" + name + '\'' +
", score=" + score +
'}';
}
//谁调用这个方法 谁就是this
@Override
public int compareTo(Student o) {
return this.age-o.age;
//根据不同的类型去比较
//return this.age-o.age;
//return (int) (this.score-o.score);
//return this.name.compareTo(o.name);
}
}
public class Test {
public static void main(String[] args) {
Student[] students = new Student[3];
students[0] = new Student(12,"bit",98.9);
students[1] = new Student(6,"abc",88.9);
students[2] = new Student(18,"zhangsan",18.9);
Arrays.sort(students);
System.out.println(Arrays.toString(students));
}
}
//输出结果:
//[Student{age=6, name='abc', score=88.9}, Student{age=12, name='bit', score=98.9},
Student{age=18, name='zhangsan', score=18.9}]
- 如果当前对象应排在参数对象之前, 返回小于 0 的数字;
- 如果当前对象应排在参数对象之后, 返回大于 0 的数字;
- 如果当前对象和参数对象不分先后, 返回 0;
注意事项 : 对于 sort 方法来说 , 需要传入的数组的每个对象都是 " 可比较 " 的 , 需要具备 compareTo 这样的能力 . 通过重写 compareTo 方法的方式 , 就可以定义比较规则。
- 这个接口有个非常大的缺点:就是侵入性非常强,一旦写好不敢轻易改动。
接下来有比Comparable更好的方法
Comparator根据比较器进行比较
import java.util.Arrays;
import java.util.Comparator;
class Student {
public int age;
public String name;
public double score;
public Student(int age, String name, double score) {
this.age = age;
this.name = name;
this.score = score;
}
@Override
public String toString() {
return "Student{" +
"age=" + age +
", name='" + name + '\'' +
", score=" + score +
'}';
}
}
class AgeComparator implements Comparator<Student> {
@Override
public int compare(Student o1, Student o2) {
return o1.age-o2.age;
}
}
class ScoreComparator implements Comparator<Student> {
@Override
public int compare(Student o1, Student o2) {
return (int)(o1.score-o2.score);
}
}
class NameComparator implements Comparator<Student> {
@Override
public int compare(Student o1, Student o2) {
return o1.name.compareTo(o2.name);
}
}
public class TestDemo {
public static void main(String[] args) {
Student[] students = new Student[3];
students[0] = new Student(12,"bit",98.9);
students[1] = new Student(6,"abc",88.9);
students[2] = new Student(18,"zhangsan",18.9);
System.out.println(Arrays.toString(students));
AgeComparator ageComparator = new AgeComparator();
ScoreComparator scoreComparator = new ScoreComparator();
NameComparator nameComparator = new NameComparator();
Arrays.sort(students,nameComparator);//默认是从小到大的排序
System.out.println(Arrays.toString(students));
}
}
结果:
- [Student{age=12, name='bit', score=98.9}, Student{age=6, name='abc', score=88.9}, Student{age=18, name='zhangsan', score=18.9}]
- [Student{age=6, name='abc', score=88.9}, Student{age=12, name='bit', score=98.9}, Student{age=18, name='zhangsan', score=18.9}]
Comparator接口更加灵活,侵入性非常弱。