Java学习笔记(二)——面向对象编程

一、面向对象的概念

在计算机编程中,我们可以把现实生活中的一切事物视为对象,而事物产生变化的动作我们视为对象实现某种目的的方法。
通过将不同的对象按照其属性进行分类,从而具体化任何一个对象的实例,在这里,实例是某一对象的具体表现形式,例如,如果我们创建一个名为Person的类,用来代表人类,那么在这个Person类中,可以创建两个实例:Man和Women分别代表男人和女人,那么Man和Women就分别是Person的具体化表现形式,他们都拥有相同的类型Person,但是自身却拥有不同的属性。
在Java编程中,我们把现实生活中的概念进行抽象,生成计算机模型,然后引导和操作Java对象,从而将虚拟与现实完美的联系起来,实现我们编程的目的。

(一)类(class)

1.class定义了如何创建实例
2.class的名字就是数据的类型

(二)实例(instanse)

1.instanse是根据class创建的实例,也就是数据本身
2.可以同时创建多个instanse
3.每一个instanse的类型相同,但是其各自拥有的属性也许是不同的
4.指向instanse的变量都是引用变量
5.每一个instanse都拥有独立的存储空间,修改不同的instanse互不影响

(三)定义class

在编写代码的过程中,我们首先需要定义class。在一个class中可以包含多个字段(field)用来描述class的特征,从而实现了class的数据封装

public class Person {//定义名为Person的class
    public String name;//定义一个String类型的name属性
    public int age;//定义一个int类型的age属性
}//完成名为Person的class定义

(四)创建instanse

在我们完成了class的定义之后,就可以创建instanse实例
1.使用new操作符创建一个实例
2.定义一个引用类型变量来指向实例
3.通过变量操作实例
4.通过 变量.字段 来访问实例

Person zero = new Person();//创建实例
zero.name = "零";//定义引用类型指向实例
zero.age = 25;//操作实例
System.out.println(zero.name);//访问字段

二、数据封装

(一)方法

1 . 数据封装的作用

在定义class时,可以让class包含多个field,但是直接将field用public暴露给外部可能会破坏封装为了避免外部代码直接修改class内部的数据,我们使用private来修饰field,以此来拒绝外部访问。
public:外部代码可以访问该字段
private:外部代码无法访问该字段

public class Person {
    private String name;
    private int age;

    public void setName(String name){
        this.name = name;//通过setName()来修改name字段
    }
    public String getName(){/
        return this.name;//通过getName()来读取name字段
    }
}   

2 . 封装方法

(1)外部代码不可访问private
(2)外部代码通过调用public方法间接的设置和获取private字段
(3)public方法封装了外部访问
(4)通过方法访问实例字段更加安全
(5)通过 变量.方法名() 来调用实例方法

Person zero = new Person();
zero.name = "零";//编译错误
zero.age = 25;//编译错误
zero.setName("零");//调用setName()方法修改name字段
System.out.println(zero.getName());//调用getName方法读取name字段

3 . 定义方法

(1)方法的定义:

①public修饰符
②方法的返回值
③方法名称(需要遵循Java命名规范)
④方法参数列表
⑤方法的返回值通过return语句实现
⑥没有返回值(void)可以省略return
⑦方法内部可以使用隐式变量this,this指向当前实例,this.field可以访问当前字段的实例

public class Person {
    private String name;
    private int age;
    public String getName(){//public修饰词 方法返回值 方法名称 方法参数列表
        return this.name;//在方法内部使用隐式变量this
    }
    public void setName(String name){
        if(name == null){
            throw new NullPointException();
        }
        this.name = name.trim();
    }
}
(2)隐式变量this

①方法内部可以使用隐式变量this
②this指向当前实例
③this.field可以访问当前字段的实例
④在不引起歧义的情况下可以省略this

public String getName(){//public修饰词 方法返回值 方法名称 方法参数列表
        return name;//return this.name
    }

⑤当字段名和局部变量名重名时,编译器优先查找局部变量名

    public void setName(String name){
        if(name == null){
            throw new NullPointException();
        }
        this.name = name.trim();//优先执行局部变量名
    }
(3)调用方法

在定义好一个方法后,可以进行该方法的调用
①使用 实例变量.方法名(参数) 格式
②可以忽略方法的返回值

Person zero = new Person();
zero.setName("零");//没有返回值
String s = zero.getName();//返回值为String

4 . 方法参数

(1)方法参数是用于接收传递给方法的变量值,在调用方法时,通过外部代码把一个变量通过参数传递给方法内部
(2)参数绑定:
(3)private方法:可以通过定义private方法,让外部代码无法访问private方法,而内部代码可以调用自己的private方法

(二)构造方法Constructor

在前面的内容中,如果需要创建实例并进行初始化,需要以下3行代码:

Person zero = new Person();//使用new操作符创建实例
zero.setName("零");//初始化字段
zero.setAge(25);//初始化字段

那么我们能否在创建实例的同时就把内部字段初始化为合适的值呢?
例如:

Person zero = new Person("零",25);

1 . 如何构造方法

(1)可以在创建对象实例的时候初始化方法
(2)而实例在创建时会调用构造方法
(3)构造方法用于初始化实例

public class Person {
    private String name;
    private int age;
    public Person(String name, int age){//构造方法名:Person
        this.name = name;
        this.age = age;
    }
}

(4)构造方法名就是类名
(5)构造方法的参数没有限制
(6)构造方法没有返回值也没有void
(7)必须用new操作符调用构造方法
(8)如果一个类没有定义构造方法,编译器会自动生成一个默认的构造方法(),没有参数和执行语句

2 . 如何初始化构造方法

(1)先初始化字段
(2)没有赋值的字段初始化为默认值(基本类型为0,引用类型为null)
(3)再执行构造方法的代码

public class Person {
    private String name = "Unnamed";//初始化字段
    private age;//没有赋值的字段初始化为默认值
    public Person(String name, int age){//定义构造方法
        this.name = name;//构造方法代码
        this.age = age;//构造方法代码
    }
}

3 . 定义多个构造方法

(1)可以同时定义多个构造方法,编译器通过构造方法的参数数量、位置和类型进行区分

public class Person {
    private String name;
    private int age;
    public Person(String name int age){//new Person("zero",25)
        this.name = name;
        this.age = age;
    }
    public Person(String name){//new Person("zero")
    this.age = 18;
    }
    public Person(){//new Person()
    }
}

(2)一个构造方法可以使用this(…)调用其他构造方法,目的是便于代码复用

public class Person {
    private String name;
    private int age;
    public Person(String name int age){
        this.name = name;
        this.age = age;
    }
    public Person(String name){
        this(name,25);//调用构造方法
    }
    public Person(){
        this("Unnamed");//调用构造方法
    }
}

(三)方法重载Overload

1 . 方法重载的概念

方法重载是指:
(1)多个方法的方法名相同
(2)各自方法的参数不同
——参数个数不同
——参数类型不同
——参数位置不同
(3)方法的返回值类型通常相同
例如:

public class Hello {
    public void hello(String name){
    }
    public void hello(int age){
    }
    public void hello(String name, int age){
    }
    public void hello(int age, String name){
    }
}

2 . 方法重载的目的

重载的目的是让相同功能的方法使用同一名字,便于方法的调用

public class Hello {
    public static void main(String[] args){
        String s = "LEGO";//String变量s有indexOf方法
        int s1 = s.indexOf('l');//传入字符
        int s2 = s.indexOf("LL");//传入字符串
        int s3 = s.indexOf("LL", 2);//传入字符串,指定从哪个位置开始搜索
    }
}

3 . String类的方法重载indexOf

(1)int indexOf(int ch)
(2)int indexOf(String str)
(3)int indexOf(int ch, int fromIndex)
(4)int indexOf(String str, int fromIndex)

String s = "Hello,World!";
s.indexOf('o');//4
s.iodexOf("11");//2
s.indexOf('o', 6);//8
s.indexOf("11", 6);//-1

下面是一个例子:
首先我们定义一个Person类,如果用户传入一个String参数,

public class Person {
    private String name;
    public String getName() {
        return this.name;
    }
    public void setName(String name) {
        this.name = name;
        //如果用户传入一个String参数,就将参数赋值给字段name
    }
    public void setName(String firstName, String lastName) {
        this.name = firstName + " " + lastName;
        //如果用户传入两个String参数,将参数分分别传入firstName和lastName,并在中间添加空格连接
    }
}

之后编写main方法

public class Main {

    public static void main(String[] args) {
        Person ming = new Person();
        ming.setName("小明");
        System.out.println(ming.getName());//小明
        //当传入一个参数"小明"时,调用的是第一个setName方法
        Person hong = new Person();
        hong.setName("Xiao", "Hong");
        System.out.println(hong.getName());//Xiao Hong
        //当传入两个参数"Xiao"和"Hong"时,调用的是第二个setName方法
    }
}

注意:在方法重载过程中不要交换参数顺序


三、继承和多态

(一)继承

1 . 什么是继承

继承是一种代码的复用方式
如果我们已经创建了一个Person类:

public class Person {
    private String name;
    private int age;
    public void run(){
    }
}

然后继续编写一个拥有Person类相同方法的Student类时,可以有两种办法实现
第一个方法:我们将Person类方法的代码复制到Student类中,然后再添加新增的代码

public class Student {
    private String name;//复制的代码
    private int age;//复制的代码
    public void run(){//复制的代码
    }
    private int score;//新增的代码
    public void setScore(int score){//新增的代码
    }
    public int getScore;//新增的代码
}

可以发现Student类中包含了Person类中已有的方法和代码,如果需要编写大量的相同方法时就会变得繁琐,工作量也特别大。
所以我们还可以使用第二种方法,也就是继承(extends)来实现这个功能:

public class Student extends Person {//使用extends关键字将Person的全部功能继承下来
    private int score;//新增的代码
    public void setScore(int score){//新增的代码
    }
    public int getScore;//新增的代码
}

1.Student获得Person的所有功能
2.继承使用关键字extends
3.Student包含Person已有的字段和方法
4.Student只需编写新的功能

2 . 继承树

在上面的例子中
(1)Person类称为超类(super)、父类或者基类
(2)Student类称为子类(subclass)或扩展类
可以注意到,在编写Person类的代码时,我们并没有编写extends继承关系。如果我们在编写类的代码时没有使用extends关键字,编译器将会自动从Object类继承。Object是Java提供的所有类的根类。
所以我们可以理解,Student继承自Person,而Person继承自Object,这样便形成了继承树的关系。
我们可以用生物学的概念将其理解为:狮子和老虎都属于猫科动物,而猫科动物都属于动物的子类,狮子和老虎都是猫科动物的子类,而猫科动物则是动物的一个子类。
(3)Java规定一个class只能继承自一个类
(4)一个类有且仅有一个父类(Object除外)
下面我们编写一个完整的方法功能继承代码:
首先编写Person类的代码:

public class Person {

    private String name;
    private int age;

    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 void run() {
        System.out.println(name + " is running!");
    }
}

然后编写Student类的代码将Person类的功能继承下来:

public class Student extends Person {//使用extends关键字将Person的全部功能继承下来
    private int score;//新增的代码
    public void setScore(int score){//新增的代码
    }
    public int getScore;//新增的代码
}

最后编写main方法:

public class Main {

    public static void main(String[] args) {
        Person p = new Person();//创建一个Person类的实例
        Student s = new Student();//创建一个Student类的实例
        p.setName("Zero");//设置实例的名称
        s.setName("One");//设置实例的名称
        run.p();//对变量p执行run()方法
        run.s();//对变量s执行run()方法
    }
}

在执行上面的代码后我们发现,run()方法的定义在Person内部,我们通过让Student类继承Person类,让Student类也可以直接调用run()方法,从而省去了大量的重复代码。

3 . 继承访问权限(protected字段)

我们来看一段代码:

public class Person {
    private String name;
    private int age;
    public void run(){
    }

public class Student extends Person {
    public String hello(){
        return "Hello," + this.name;//编译错误

上面这段代码运行后,编译器会产生编译错误,这是因为Java规定:
(1)Person定义的private字段无法被子类访问
(2)用protected修饰的字段可以被子类访问
我们将上面这段代码进行修改:

public class Person {
    protected String name;//将private修改为protected
    private int age;
    public void run(){
    }

public class Student extends Person {
    public String hello(){
        return "Hello," + this.name;

这样Student类便可直接访问Person类中的字段和方法,可见,protected把字段和方法的访问权限控制在继承树内部。

4 . 继承关系中的构造方法

(1)在我们编写Person类的时候,可以创建一个构造方法,或者或者由编译器自动为我们创建默认的构造方法
(2)在子类中,由于子类包含父类拥有的所有功能,所以子类必须手动调用父类的构造方法
(3)Java规定,子类的构造方法的第一行语句,必须调用父类的构造方法:super()
①super()关键字代表父类(超类)
②没有super()时编译器会自动生成super()

    public class Person {
        public Person(){
            System.out.println("create Person");
        }
    }
    public class Student extends Person {
        public String(){
            super();//调用父类构造方法
            System.out.println("create Student");
        }
    }

③如果父类没有默认构造方法,子类就必须显式调用super()

public class Person {
        public Person(String name){//Person类没有使用默认构造方法
            System.out.println("create Person");
        }
    }
    public class Student extends Person {
        public String(){
            super();//继续使用super()编译器将会报错
            System.out.println("create Student");
        }
    }
public class Person {
        public Person(String name){
            System.out.println("create Person");
        }
    }
    public class Student extends Person {
        public Student(String name){//传入参数
            super(name);//显式调用父类的构造方法
            System.out.println("create Student");
        }
    }

④在显式调用父类的构造方法后,main方法中的代码格式也需要进行修改

public class Main {
    public static void main(String[] args) {
        Person p = new Person("Zero");
        Student s = new Student("One");
        p.run();
        s.run();
        System.out.println(s.Hello());
    }
}

5 . 向上转型upcasting()

(1)当我们定义一个类的时候,实际上是定义了一种数据类型。在我们定义继承的时候,相当于在这些类型之间增加了继承的关系。
(2)在我们创建一个实例对象时,需要将它赋值给一个引用类型的变量,让变量指向实例对象。
(3)在这里产生了向上转型upcasting()的概念:实例变量可以进行向上转型

Person p = new Person();
Student s = new Student();

Person ps = new Student();
//把一个子类安全的转型为父类的过程称为向上转型
Object o1 = p;
Object o2 = s;

为什么面向对象编程中允许实例变量进行向上转型?
假如我们持有一个对象Student实例,那么我们可以保证,Student实例包含Person的全部功能,因此我们将其视为一个Person类型的变量。向上转型把一个子类型安全的变为更加抽象的类型

6 . 向下转型downcasting()

与向上转型原理类似,向下转型允许我们将一个父类类型强制转换为一个子类类型
(1)Java可以对实例变量进行向下转型
(2)向下转型把一个抽象的类型转变成一个具体的子类型
(3)向下转型很可能会报错:

Person p = new Person();
//在这里变量p有可能是Person类型也有可能是Student类型
Student s = (Student) p;//ClassCastException    

如果变量p指向的实际类型并不是Student实例,JVM在运行时会抛出ClassCastException

(4)instanceof操作符可以判断对象的实际类型
①为了判断对象的实际类型,JVM提供了instanceof操作符

    Person p = new Person();
    System.out.println(p instanceof Person); // true
    System.out.println(p instanceof Student); // false

②在使用instanceof判断对象的类型时,其判断的是是否是某个类型或者是某个类型的子类

    Student s = new Student();
    System.out.println(s instanceof Person); // true
    System.out.println(s instanceof Student); // true

③如果一个引用变量的值是null,对于任何instanceof判断的结果都是false

    Student s = new Student();
    System.out.println(s instanceof Person); // true
    System.out.println(s instanceof Student); // true

所以我们在做向下转型之前,首先用instanceof进行判断再进行向下转型

Person p = new Student();
if(p instanceof Student){
    Student s = (Student) p;//OK
}

7 . 区分继承与组合的区别

public class Person {
    private String name;
    private int age;
    public void run(){
    }
}
public class Book {
    private String name;
}
public class Student extends Book {
    private int score;
    public int getScore(){
    }
}

在上面这段代码中,定义了Person、Student、Book类,分别代表人、学生和书。我们可以看出Student类并不应该从Book类继承,因为在现实世界中,Student与Book并不是继承的关系。
所以我们将上面的代码进行修改:

public class Person {
    private String name;
    private int age;
    public void run(){
    }
}
public class Book {
    private String name;
}
public class Student extends Person {
    private Book book;//在Student类中声明一个Book实例
    private int score;
    public int getScore(){
    }
}

所以我们可以理解为:
(1)继承是是或不是的区别
(2)组合是有和没有的区别

(二)多态

1 . 方法的覆写Override

(1)子类重写父类的方法称作覆写

public class Person(){
    public void run(){
    }
}

public class Student extends Person {
    @override
    public void run(){//覆写方法的参数和返回值相同,方法的语句不同
    }
}

(2)方法签名如果不同就不是Override,而是创建了一个新方法

public class Person(){
    public void run(){
    }
}

public class Student extends Person {
    public void run(String s){//创建一个新方法
    }
}

(3)添加@Override可以让编译器帮助检查是否进行了正确的覆写(@Override不是必须的)

public class Person(){
    public void run(){
    }
}

public class Student extends Person {
    @override//Compile error! 让编译器检查是否进行了正确的覆写
    public void run(String s){//创建一个新方法
    }
}

在之前我们了解到,引用变量的声明类型可能与实际类型不符,例如:

public class Person(){
    public void run(){
    }
}

public class Student extends Person {
    @override
    public void run(){
    }

Person p = new Student();
p.run();//这里调用的是Person还是Student的run()方法呢?
}

2 . 动态调用

(1)实例对象的方法调用总是对应实际类型
(2)Java的实例方法调用是基于运行时实际类型的动态调用

public class Person(){
    public void run(){
    }
}

public class Student extends Person {
    @override
    public void run(){
    }

Person p = new Student();
p.run();//这里调用的是Student的run()方法
}

3 . 多态的意义

(1)多态是指针对某个类型的方法调用,其真正执行的方法取决于运行时其实际类型的方法
(2)对某个类型调用某个方法,执行的方法可能是某个子类的覆写方法
(3)利用多态,允许添加更多类型的子类实现功能扩展

4 . Object定义的重要方法

(1)toString:把instance输出为String
(2)equals:判断两个instance是否逻辑相等
(3)hashCode:计算一个instance的哈希值

public class Person {
    ...
    @Override
    public String toString(){//把实例instance输出位String类型
    }
    @Override
    public boolean equals(Object o){//判断两个实例是否逻辑相等
    }
    @Override
    public int hashCode(){//计算实例的哈希值
    }
}

5 . super关键字

当我们在一个子类的覆写方法中,想要调用父类被覆写的方法时,可以使用super关键字,允许我们调用父类的同名方法

public class Person {
    private String name;
    public String hello() {
        return "Hello," + name;
    }
}
public class Student extends Person {
    @Override
    public String hello() {
        return super.hello() + "!";//调用父类的hello()方法
    }
}

6 . final关键字

当我们定义一个类时,如果我们不希望某个方法被子类覆写,可以在方法前面添加final修饰符
(1)用final修饰的方法不能被Override
(2)用final修饰的类不能被继承
(3)用final修饰的字段在初始化后不能被修改

public class Person {
    public final void setName(String name) {//方法不能够被子类覆写
    }
}

public final class Student extends Person {//class不能够被继承
    private final int scroe;//字段初始化后不可修改
}

四、抽象类和接口

(一)抽象类

1 . 抽象类的概念

在前面我们已经学过,每一个子类都可以覆写父类的方法,如果父类的方法并没有实际意义,我们能否去掉执行语句呢?
例如:

public class Person {
    public void run();
}

public class Student extends Person {
    @Override
    public void run(){
    }
}

public class Teacher extends Person {
    @Override
    public void run(){
    }
}

如果我们将Person类的run()方法的执行语句去掉,会发生编译错误
如果我们通过注释的方式去掉run()方法,同时也会失去父类的多态特性
那么在这种情况下,我们可以使用抽象方法,也就是定义抽象类
我们可以使用abstract关键字声明父类的方法为抽象方法:

public abstract class Person {//定义了抽象方法的类是抽象类
    public abstract void run();//定义抽象方法,没有任何执行语句

public class Student extends Person {
    @Override
    public void run(){
    }
}

public class Teacher extends Person {
    @Override
    public void run(){
    }
}

(1)如果一个class定义了方法,但没有具体的执行代码,这个方法就是抽象方法
(2)抽象方法用abstract修饰
(3)抽象方法没有任何执行语句
(4)定义了抽象方法的类就是抽象类(abstract class)
(5)无法实例化一个抽象

public abstract class Person {
    public abstract void run();
}

Person p = new Person();//编译错误,因为抽象类不可以实例化

Person s = new Student();//可以实例化子类
Person t = new Teacher();//可以实例化子类

s.run();//调用抽象方法实现多态
t.run();//调用抽象方法实现多态

2 . 抽象类的作用

那么无法实例化的抽象类有什么作用呢?
(1)抽象类用于被继承
(2)从抽象类继承的子类必须实现其抽象方法
(3)抽象方法实际上相当于定义了子类必须实现的接口规范
(4)如果子类没有实现抽象方法,这个子类依然是一个抽象类

public class abstract Person {
    public abstract void run();
}

public class Student extends Person {
    @Override
    public void run();
}

public class Teacher extends Person {
    @Override
    public void run();
}

3 . 面向抽象编程的本质:

Person s = new Student();
Person t = new Teacher();
//不需要关心Person类型对象的具体类型
s.run();
t.run();

(1)上层代码只编译规范
(2)不需要子类就可以实现业务逻辑
(3)具体的业务逻辑由不同的子类实现,调用者无需关心

下面是一个具体的例子:

首先创建Shape类,表示图形,定义一个area()方法为抽象方法

public abstract class Shape {
    public abstract double area();
}

然后创建ShapeUtil类,编写一些图形计算的业务代码:

public class ShapeUtil {
    public double sum(Shape[] shapes) {//面积求和
        double s = 0;
        for (Shape shape : shapes) {
            s += shape.area();
        }
        return s;
    }

    public boolean isGreaterThan(Shape shape1, Shape shape2) {
        return shape1.area() > shape2.area();//比较面积大小
    }

    public boolean isLessThan(Shape shape1, Shape shape2) {
        return shape1.area() < shape2.area();//比较面积大小
    }
}

可以看到,我们定义了抽象类,并没有实际的执行代码,但是不影响编写业务代码
然后我们从Shape中继承第一个子类长方形Rect:

public class Rect extends Shape{//从Shape继承
    private final double width;
    private final double height;

    public Rect(double width, double height){//初始化Rect
        this.width = width;
        this.height = height;
    }
    @Override
    public double area(){
        return width * height;
    }
}

然后定义Shape的第二个子类圆形Circle:

public class Circle extends Shape{
    private final double radius;

    public Circle(double radius){
        this.radius = radius;
    }
    @Override
    public double area(){
        return Math.PI * radius * radius;
    }
}

最后进行Main方法的编写:

public class Main {

    public static void main(String[] args) {
        Shape s1 = new Rect(200, 100);
        Shape s2 = new Circle(60);
        System.out.println(s1.area());
        System.out.println(s2.area());
    }
}

在Main方法中我们可以看到,我们引用了Shape类型的抽象类,而area()方法调用的实际上是其子类的方法。在这里程序只关心子类是否包含area()方法,而不关心子类如何去实现area()方法

如果我们不使用抽象类方法去扩展子类的功能会是什么样子呢?

public class BadShape {//这里举出反例

    private String type;//使用一个type类型表示不同类型的Shape

    private double width;
    private double height;
    private double radius;

    //然后利用switch语句根据Shape的不同type类型进行计算
    public double area() {
        switch (type) {
        case "Rect":
            return width * height;
        case "Circle":
            return Math.PI * radius * radius;
        default:
            return 0;
        }
    //当Shape的种类逐渐增加时,我们需要不断的区修改switch语句的代码
    //每增加一种Shape类型,就需要对这段代码进行修改
    }
}

如果我们使用了抽象类,我们可以不对抽象的类的代码做任何修改,也不需要对Main方法进行修改。通过添加子类继承的方式,不断的增加我们需要的功能。

(二)接口

1 . 接口的概念

在抽象类的概念中我们知道,抽象方法的本质是定义接口规范。如果一个抽象类没有字段,所有方法都是抽象方法,就可以将该抽象类改写为接口interface
(1)在Java中我们使用interface声明一个接口
(2)接口定义的方法默认是public abstract

public interface Person {//使用interface声明一个接口
    void run();//定义的方法默认为public abstract,所以不需要手动编写
}

2 . Java内置的纯抽象接口

(1)interface是Java中内置的纯抽象接口
(2)实现interface使用implements
(3)可以实现多个interface
(4)接口也是数据类型,可以进行向上转型和向下转型
(5)接口不能定义实例字段

public interface Hello {//定义一个Hello接口
    String hello();
    String goodbye();
}

public class Person implements Hello {//实现Hello接口
}

public class Robot implements Hello, Comparable {//实现Hello接口
}

3 . 注意区分术语

(1)Java的接口特指interface定义的接口,只定义方法签名
(2)编程接口泛指接口规范,如方法签名、数据格式、网络协议等等

4 . 抽象类与接口的区别

(1)在继承关系中,一个子类只能继承一个抽象类。而在接口中,可以implenments多个interface
(2)对于字段,在抽象类中,我们可以定义实例字段。但在接口中我们不能定义实例字段
(3)对于抽象方法,在abstract class和interface中我们都可以定义抽象方法
(4)对于非抽象方法,在abstract class中可以定义非抽象方法,也就是实例方法。在interface中,我们只能定义default方法。

继续引用在抽象类中的例子:
首先,定义接口Shape:

public interface Shape {//这里定义接口

    double area();

}

然后,编写需要实现Shape的子类Rect:

public class Rect implements Shape {//由abstract变为implements

    private final double width;
    private final double height;

    public Rect(double width, double height) {
        this.width = width;
        this.height = height;
    }

    @Override
    public double area() {
        return width * height;
    }

}

然后,编写需要实现Shape的子类Circle:

public class Circle implements Shape {//由abstract变为implements

    private final double radius;

    public Circle(double radius) {
        this.radius = radius;
    }

    @Override
    public double area() {
        return Math.PI * radius * radius;
    }

}

然后,定义Main方法:

public class Main {

    public static void main(String[] args) {
        Shape s1 = new Rect(200, 100);
        Shape s2 = new Circle(80);
        System.out.println(s1.area());
        System.out.println(s2.area());
    }
}

在上面这段代码中,我们发现使用interface和过去使用abstract的结果并没有区别。如果我们给Shape接口增加一个新的方法,例如:

public interface Shape {//这里定义接口

    double area();
    double perimeter();//新增计算周长的方法
}

这时我们会发现所有的子类都会发生编译错误,因为子类目前还没有实现这个新增的方法接口。如果我们不希望在每一个子类中都去实现这个抽象方法,我们可以在接口中将这个方法定义为default方法:

public interface Shape {//这里定义接口

    double area();
    default double perimeter(){
        return 0;//在default方法中,设置方法的返回值为0
    }
}

定义default方法后,所有的子类都不必实现这个方法同时实现Shape接口。
任何子类也可以覆写这个default方法来实现自己的逻辑

5 . interface的继承

(1)一个Interface可以继承自另一个interface
(2)interface继承自interface使用extends关键字

public interface Person {
    String getName();
}

public interface Student extends Person {
    //定义Student类型的接口扩展到Person类型的接口
    String getSchool();//新增一个方法
}

public class PrimaryStudent implements Student {
    @Override
    public String getName(){
    }
    @Override
    public String getSchool(){
    }
}

(3)interface继承相当于扩展了接口的方法
上面的例子中,PrimaryStudent必须实现Student中定义的方法和Person中定义的方法
(4)需要合理设计继承关系
①合理设计interface和abstract class的继承关系
②公共逻辑需要放在abstract class中
③接口层次代表了抽象的程度

List list = new ArraysList();
Collection coll = list;
Iterable it = coll;

当我们通过List接口来引用一个ArraysList实例的时候,我们可以调用abstractList定义的所有方法。当我们把List接口向上转型至Collection接口时,我们就只能用abstractCollection定义的所有方法。最后我们把Collection接口向上转型为Iterable接口,我们就只能用abstractIterable定义的方法。


五、包和classpath

(一)静态字段和方法

1 . 静态字段

用static修饰的字段称为静态字段
(1)普通字段在每个实例中都有自己独立的空间
(2)静态字段只有一个共享空间,所有的实例都共享该字段

public class Person {
    public String name;
    public int age;
    public static int number = 100;
}

对于Person类中的所有实例,都共享Person类的Number字段

public class Person {
    public String name;
    public int age;
    public static int number = 100;

Person ming = new Person();
Person hong = new Person();

//可以通过 实例字段.静态变量名 来访问这个静态字段,但是不推荐这样做
ming.number = 99;//warning!
System.out.println(hong.number);//99

//可以通过类名来访问静态字段
Person.number = 88;
System.out.println(Person.number);//88
}

因为在Java程序中,实例对象并没有静态字段。在代码中,实例对象能够访问静态字段,只是因为编译器根据类型实例自动转化成类名来访问静态字段。
(3)我们可以把静态字段理解为:描述class本身的字段(非实例字段)

2 . 静态方法

(1)静态方法的概念

用static修饰的方法称为静态方法
①调用实例方法必须通过实例变量
②调用静态方法不需要通过实例变量
③静态方法类似于其他编程语言中的函数

public class Person {
    private String name;
    private static int number = 100;
    public static void setNumber(int num) {
        number = num;
    }
}

Person.setNumber(999);
(2)静态方法的限制

①静态方法不能访问this变量
②静态方法不可以访问实例字段
③静态方法可以访问静态字段

(3)静态方法的用途

①静态方法经常用于工具类
——Arrays.sort()
——Math.roandom()
②静态方法经常用于辅助方法
③Java程序的入口Main()也是静态方法

下面是一个例子:
创建一个Person类,计算一共创建了多少个Person对象:

public class Person {

    private static int number;//定义静态字段number

    private String name;

    public Person(String name) {
        this.name = name;
        number++;//每次调用构造方法,number+1
    }

    public String getName() {//定义静态方法用来获取number
        return this.name;
    }

    public static int getNumber() {
        return number;
    }
}

在Main方法中创建3个Person类的实例,每创建一个实例后,打印出Person的getNumber()静态方法返回的值

public class Main {

    public static void main(String[] args) {
        Person p1 = new Person("小明");
        System.out.println(Person.getNumber());//1
        Person p2 = new Person("小红");
        System.out.println(Person.getNumber());//2
        Person p3 = new Person("小军");
        System.out.println(Person.getNumber());//3
    }
}

可以看到,每创建一个实例,静态变量number就会+1

(二)包package

1 . package的概念

(1)解决Java编程中出现的类名冲突问题
假设:
明日香写了一个Person.Java
绫波丽写了一个Person.Java
淀真嗣写了一个Arrays.Java
JDK自带了一个Arrays.Java
可以看出,两个Person和两个Arrays的类名势必会引起冲突
在Java中,我们使用package机制来解决类名冲突问题
(2)Java定义了名字空间:包(package)的概念
(3)包名 + 类名 = 完整类名
所以,通过package机制,可以将上面的包名规定为下面的完整包名:
①明日香的Person类:Asuka.Person
②绫波丽的Person类:Rei.Person
③淀真嗣的Arrays类:Shinji.Arrays
④JDK的Arrays类:java.util.Arrays
(4)JVM只看完整类名,包名不同,类就不同
①包可以是一个多层结构,例如:java.util.Arrays
②包没有父子继承关系,例如java.util与java.util.zip是两个不同的包
③JVM在加载class并执行代码时,总是使用class的完整类名
④编译器编译后的class文件中全部是完整类名

2 . 包作用域

(1)位于同一个包的类,可以访问包作用域的字段和方法
(2)不使用public、private、protected、修饰的字段和方法就是包作用域

3 . 引用其他类

(1)使用完整类名
例如:java.util.Arrays.sort(ns)

package Hello;

public class World {
    public int findMin(int[] ns) {
        java.util.Arrays.sort(ns);
        return ns[0];
    }
}

(2)先使用import再使用类名
例如:import java.util.Arrays

package Hello;

import java.util.Arrays();//先使用import声明

public class World {
    public int findMin(int[] ns) {
        Arrays.sort(ns);//在这里使用简单的类名
        return ns[0];
    }
}

(3)在import语句中,我们可以使用通配符 * :
例如:import java.util.*
这时,会把util下面所有的包都加载进来,所以并不推荐这种写法
(4)静态导入
①import static可以导入一个类的静态字段和静态方法
②import static java.util.system.*

import static java.lang.system.*;

public class Main {
    public static void main(String[] args) {
        //System.out.println(...);
        out.println("Hello,World!");
        //由于out是System方法的静态字段,因此可以将System.out改写为out
    }
}

③import static这种方法很少使用

4 . 查找class

编译器最终编译出的.class文件类名只使用完整类名,因此,在代码中,编译器在遇到一个class名称时:
(1)如果是完整类名:直接根据完整类名查找

java.util.List list;//java.util.List

(2)如果是简单类名:
①查找当前package的class
②查找import的class
③查找java.long包的class

import java.text.Format;

java.util.List list;//java.util.List
Format format = null;//java.text.Format
String s = "hi";//java.lang.String
System.out.println(s);//java.lang.System

(3)如果无法确定类名:编译器将会报错
(4)自动import:
在编写class时:
①默认自动import当前package的其他class
②默认自动import java.long.*

package com.feiyangedu;
import.com.feiyangedu.*;//由于是编译器默认,所以不需要写出
import java.long.*;//由于是编译器默认,所以不需要写出

5 . java源文件

(1)Java源文件要按照package的层次按目录存放
①class:com.feiyangedu.sample.Hello
②src(源代码根目录)→com→feiyangedu→sample→Hello.java
(2)编译后的class文件也要按照package的层次目录存放
①class:com.feiyangedu.sample.Hello
②bin(编译输出的根目录)→com→feiyangedu→sample→Hello.class

6 . 最佳实战

(1)包名使用倒置的域名可以确保唯一性:例如org.apache.commons.log
(2)不要和java.long包的类重名:例如String,System,Runtime等
(3)不要和JDK常用名重名:例如java.util.list、java.util.Format等

(三)作用域

1 . 访问权限

(1)访问权限的概念:

Java的类、接口、字段和方法都可以设置访问权限
①访问权限是指在一个类的内部,能否引用另一个类的字段和方法
②访问权限有public、protected、private、package四种

(2)public
package abc;
public class Hello {
    public void hi(){
    }
}
package xyz;
class Main {
    void foo{
        //Main可以访问Hello
        Hello h = new Hello();
    }
}

①定义为public的class和interface可以被其他类访问
②定义为public的field和method可以被其他类访问

(3)private
package abc;
public class Hello {
    public void hi(){
        this.hi();
    }
}
private void hi();

①定义为private的field和method无法被其他类访问
②private权限限定在class内部,与方法的声明顺序无关
③定义在一个class内部的class称为内部类(inner class)

(4)protected
package abc;
public class Hello {
    protected void hi(){
    }
}
package xyz;
class Main extends Hello {
    void foo{
        Hello h = new Hello();
        h.hi();
    }
}

①protected作用于继承关系中
②定义为protected的字段和方法可以被子类访问

(5)package
package abc;
class Hello {
    void hi(){
    }
}
package xyz;
class Main {
    void foo{
        Hello h = new Hello();
        h.hi();
    }
}

①位于同一个包的类,可以访问包作用域的字段和方法
②没有public、private修饰的class
③没有public、private、protected修饰的字段和方法
④包名必须完全一致
⑤如果不确定是否需要public,就不声明为public,尽可能少地减少暴露对露方法

2 . 局部变量

(1)在方法内部定义的变量被称为局部变量
(2)局部变量的作用域是从声明变量处开始到对应的块结束处(也就是一组花括号对应的开始于结束位置)
(3)尽可能的将局部变量的作用域缩小
(4)尽可能延后声明局部变量

package abc;
public class Hello {
    void hi(String name) {
        String s = name.toLowerCase();
        int len = s.length();
        if(len < 10) {
            int p = 10 - len;
            for(int i - 0, i < 10, i++){
                System.out.println();
            }
        }
    }
}

一个Java文件中只能存在一个public class,但是可以存在多个非public class:

package com.feiyangedu.sample;

import static java.lang.System.out;

public class Main {//只能定义一个public class

    public static void main(String[] args) {
        Hello h = new Hello("World");
        out.println(h.hello());
    }

}

class Hello {//可以定义多个非public class

    private final String name;

    public Hello(String name) {
        this.name = name;
    }

    public String hello() {
        return "Hello, " + name + "!";
    }
}

3 . final关键字

(1)final与访问权限不冲突
(2)用final修饰class可以阻止被继承
(3)用final修饰method可以阻止被覆写
(4)用final修饰field可以阻止被重新赋值
(5)用final修饰局部变量可以阻止被重新赋值

package abc;//与访问权限不冲突
public final class Hello {//阻止class被继承
    private final int n = 0;//阻止field被重新赋值
    protected final void hi(int t){//阻止method被覆写
        final long i = t;//阻止局部变量被重新赋值
    }
}

(四)classpath和jar

1 . classpath

(1)classpath的概念

①classpath是一个环境变量
②classpath指示JVM如何搜索class
③classpath设置的搜索路径与操作系统有关:
——Windows系统:
C:\work\project1\bin;C:\shared;”D:\My Document\project2\bin”(如果目录含有空格,必须用双引号括起来)
——Mac OS X系统:
/usr/shared/:/usr/local/bin:/home/feiyangedu/bin
我们假设classpath的目录是.;C:\work\project1\bin;C:\shared
JVM在加载com.feiyangedu.Hello这个类时,依次查找:
——<当前目录>\com\feiyangedu\Hello.class
——C:\work\project1\com\feiyangedu\Hello.class
——C:\shared\com\feiyangedu\Hello.class
如果在某个路径下找到,将不再继续搜索
如果没有找到,编译器将会报错

(2)classpath的设置方法

①直接在系统环境中设置classpath环境变量(不推荐,因为在系统环境中设置的classpath会干扰到特定的程序)
②在启动JVM时设置classpath环境变量(推荐)
在启动JVM时,可以传入:
——java -classpath C:\work\bin;C:\shared com.feiyangedu.Hello
——java -cp C:\work\bin;C:\shared com.feiyangedu.Hello(简化的参数)
③没有设置环境变量,也没有设置-cp参数,默认的classpath为 . ,即当前目录
④在Eclipse中运行Java程序,Eclipse自动传入的-cp参数是当前工程的bin目录和引入的jar

2 . jar

(1)什么是jar包

①jar包是zip格式的压缩文件,包含若干.class文件
②jar包相当于目录
③classpath可以包含jar文件:C:\work\bin\all.jar
④查找com.feiyangedu.Hello类将在C:\work\bin\all.jar文件中搜索com\feiyangedu\Hello.class
⑤使用jar包可以避免大量的目录和class文件

(2)如何创建jar包

如果我们创建了大量的class文件,如何打包到jar包中呢?
①使用JDK自带的jar命令
②使用构建工具如Maven等
③由于jar包与zip格式是相同的,我们可以直接使用相关的zip软件来创建jar包

(3)jar包的其他功能

①jar包可以包含一个特殊的/META-IF/MAINIFEST.MF文件
②MAINIFEST.MF是纯文本,可以指定Main-Class和其他信息
③jar包可以包含其他jar包

(4)JDK自带的class

①JVM运行时会自动加载JDK自带的class
②JDK自带的class被打包在rt.jar中
③不需要在classpath中引用rt.jar


六、Java核心类

(一)字符串和编码

1 . 字符串

(1)String的特点

Java中的字符串用String表示,其特点有两个:
1.可以直接使用”…”调用
2.String的内容一旦创建不可变更

String s1 = "Hello";//使用"..."调用
String s2 = new String("World");//内容一旦创建不可变更
(2)字符串常用操作
比较String的内容:

1.equals(Object)方法(String覆写Object的方法)
2.equalsIgnoreCase(String) (忽略String内容的大小写来进行比较)

String s ="hello";
s.equals("Hello");//false
s.equalsIgnoreCase("Hello");//true
在String查找是否存在子串

1.返回是否存在某个子串:booleans contains(CharSequence)
2.返回子串的索引位置:
int indexOf(String),如果没有找到,返回值为 -1
int lastindexOf(String),从末尾开始查找
3.返回字符串由哪里开始:boolean startsWith(String)
4.返回字符串由哪里结束:boolean endWith(String)

String s = "hello";//是否存在某个子串
s.contains("ll");//是否存在某个子串:true
s.indexOf("ll");//ture子串的索引位置:2
s.startsWith("he");//字符串是否由这里开始:true
s.endWith("lo");//字符串是否由这里结束:true
移除首尾空白字符

String提供了一个trim()方法用来移除首位空白字符
空白字符包括:空格,\r,\t,\n

String s = " \t hello\r\n";
String s2 = s.trim();//"hello"
s = s.trim();

注意:trim()不改变字符串内容,而是返回新的字符串

提取子串

在Java中使用substring()方法来提取字符串的子串

String s = "Hello,World";
s.substring(7);//"World"
s.sunstring(1,5);//"ello"

注意:substring()并不改变字符串的内容,而是返回新字符串

大小写切换

1.大写切换:toUpperCase()
2.小写切换:toLowerCase()

String s = "hello";
s.toUpperCase() = "HELLO";
s.toLowerCase() = "hello";
替换子串

1.替换一个字符:replace(char,char)
2.替换一串字符:replace(CharSequence,CharSequence)

String s = "hello";
s.replace('l', 'w'); = "hewwo";
s.replace("l", "w~"); = "hew~w~o";

3.用正则表达式替换子串:replaceAll(String,String)

String s = "A, ,B; C, D";
s.replaceAll("[\\,\\;\\s)]+", ", ");//"A, B, C, D"

这个方法的功能要比replace()更加强大,之后我们会学习到正则表达式的用法

分割字符串:String[] split(String)
String s = "A,,B;C,D";
String[] ss = s.split("[\\,\\;\\s]+");//"A","B","C","D"
拼接字符串:static String join()
String[] arr = {"A", "B", "C"};
String s = String.join("~~", arr);//"A~~B~~C"
(3)字符串的类型转换
把任意数据转换为String

1.转换为整型:static String valueOf(int)
2.转换为布尔类型:static String valueOf(boolean)
3.转换为Object类型:static String valueOf(Object)

String valueOf(123);//123
String valueOf(true);//"true"
String valueOf(new Object());//"java.lang.Object@7852e922"

注意:对于Object类型,还可以使用toString()方法转换字符串

把String转换为其他类型

1.static int Integer.parseInt(String)
2.static Integer Integer.valueOf(String)

int i = Integer.parseInt("123");//123
Integer I = Integer.valueOf("123");
String与char[]的相互转换

1.String转换为char[]:char[] toCharArray()
2.char[]转换为String:new String(char[])

String s = "hello";
char[] cs = s.toCharArray();//{'h','e','l','l','o'}

String s2 = new String(cs);

注意:String是不可变类型,当我们调用toCharArray()时,字符串内部实际上是复制了一份char[],如果我们修改返回后的char[],不会影响到原来String中的内容

String与byte[]的相互转换

如果将String与byte[]进行转换,会涉及到编码
1.String转换为byte[]
①byte[] getBytes()(不推荐)
这个方法使用操作系统默认的编码格式,但是并不推荐这样做。因为在Windows系统上,系统默认的编码格式是GBK格式,而在Linux和Mac OS X系统上,系统的默认编码是UTF-8格式
②byte[] getBytes(String)
③byte[] getBytes(Charset)

String s = "hello";
byte[] bs1 = s.getBytes("UTF-8");
byte[] bs2 = s.getBytes(StandardCharsets.UTF-8);
//可以得到使用UTF-8编码的byte[]

2.byte[]转换为String
①new String(byte[],String)
②new String(byte[],Charset)

String s = "hello";
new String(bs1, "UTF-8");
new String(bs2, StandardCharsets.UTF-8);

2 . 编码

(1)编码的概念

由于计算机只能识别数字,如果我们希望表示字符,实际上是将字符转换为数字的形式传输给计算机识别。
1.ASCII编码
①ASCII编码是最早的计算机字符编码
②一个字符占用1个字节
③第一个字节最高位是0,最多表示128个字符
例如:字符’A’ = 0x41(具体字符编码请自行参照《ACSII编码规则》)
2.中文编码
①由于ASCII编码最多只能表示128个字符,对于中文字符显然是不够的,因此一个中文字符占用2个字符
②中文编码规范目前共有三种:GB2132、GBK、GB18030
③第一个字节最高位是1
例如:’中’ = d6d0(GBK)
3.其他编码:
①日文编码:Shift_JIS编码
②韩文编码:Euc-kr编码

(2)Unicode编码

不同的国家使用相同的2字节进行编码时,如果我们在同一个网页中即显示中文编码又显示日文或韩文编码,就可能产生编码冲突,从而产生乱码。为了解决各个国家编码不一致的情况,出现了Unicode全球统一编码格式
①Unicode是全球统一编码
②全球所有文字都拥有唯一编码
③一个Unicode字符通常占用2个字节

UTF-8编码

由于英文Unicode编码与ACSII编码不一致,包含大量英文文本会浪费空间,所以衍生出了UTF-8编码。
UTF-8编码是一种变长编码:
①英文UTF-8编码与ACSII编码一致
②其他Unicode编码需占用2-6个字节不等
③UTF-8编码容错能力更强

编码的最佳实践

①Java使用Unicode编码
②Java程序运行时使用Unicode编码
③输入输出时把String和byte[]转换,需要考虑编码
④编程时始终优先考虑UTF-8编码

(二)StringBuilder

我们在编写程序时,经常需要拼接字符串。字符串可以直接使用 + 来拼接,但是有些时候,如果我们在循环中去拼接字符串,每次循环都会创建新的字符串对象,这些字符串对象绝大多数都是临时对象,浪费内存空间,影响GC效率

String s = "";
for(int i = 0, 1 < 1000, i++){
    s = s + String.valueOf(i);
}

1 . 用StringBuilder高效拼接字符串

对于这种大量且零碎的字符串的拼接,Java提供了StringBuilder类,可以高效的进行字符串拼接。由于StringBuilder是可变对象,所以StringBuilder可以预分配缓冲区,可以把新的字符串不断的放进StringBuilder的缓冲区中,最终得到拼接后的字符串

StringBuilder sb = new StringBuilder(1024);
for(int i = 0, 1 < 1000, i++){
    sp.append(String.valueOf(i);
}
String s = sb.toString();

2 . StringBuilder可以进行链式操作

StringBuilder sb = new StringBuilder(1024);
String s = sb.append("Mr ")
    .append(name)
    .append("!")
    .insert(0, "Hello,")
    .tuString();//让StringBuilder对象返回一个String对象

注意:在编程时不需要特别改写字符串 + 操作,编译器在内部自动将连续的多个 + 操作优化为StringBuilder操作

package com.feiyangedu.sample;

public class Main {

    public static void main(String[] args) {
        String name = "World";
        StringBuilder sb = new StringBuilder();
        sb.append("Hello, ").append(name).append('!');
        String s = sb.toString();
        System.out.println(s);
    }
}

实现链式操作的关键是返回实例本身

3 . StringBuffer类

(1)StringBuilder与StringBuffer的接口完全相同
(2)StringBuffer是StringBuilder的线程安全版本
(3)StringBuffer只有在跨线程拼接时才会使用

(三)包装类型wrapper

1 . 包装类型的概念

我们知道Java拥有基本类型和引用类型两种数据类型,基本类型不可以看做是对象,那么我们如何将基本类型视为一个对象(引用类型)呢?
答案是不可以的,但是我们可以换一种方法:我们可以定义一个Integer类包含一个实例字段int,这样就把Integer视为int的包装类型(wrapper)。
包装类型本身是引用类型,只是其内部持有一个基本类型的变量。
例如:

public class Integer {
    private int value;
    public Integer(int value) {
        this.value = value;
    }
}
Integer n = null;
Integer n2 = new Integer(99);

但是我们并不需要直接去编写包装类型,JDK为每种基本类型都创建了对应的包装类型

2 . JDK创建的包装类型

(1)基本类型:boolean → 包装类型:Boolean
(2)基本类型:byte → 包装类型:Byte
(3)基本类型:short → 包装类型:Short
(4)基本类型:int → 包装类型:Integer
(5)基本类型:long → 包装类型:Long
(6)基本类型:float → 包装类型:Float
(7)基本类型:double → 包装类型:Double
(8)基本类型:char → 包装类型:Character
我们以基本类型int为例:
①如果需要将一个int类型变成它的包装类型Integer,我们可以直接创建一个new Integer()对象,传入基本类型int

Integer = new Integer(99);

②如果需要将一个Integer包装类型转化为int类型,需要调用intValue()方法,可以返回基本类型int

int i = n.intValue();

3 . int、Integer和String的相互转换

(1)可以使用Integer的静态方法valueOf,用来接收int类型或者String类型,并返回一个Integer包装类型:

int i = 100;

Integer n1 = new Integer(i);
Integer = Integer.valueOf(i);
Integer = Integer.valueOf("100");

(2)Integer intValue()方法可以返回基本类型int

int x1 = n1.intValue();

(3)Integer的静态方法paresInt()可以把String对象直接转化为基本类型int

int x2 = Integer.paresInt("100");

(4)Integer类型转化为String类型:

String s = n1.toString();

(5)特别注意:
Integer类提供的getInteger()方法并不能将String对象转化为Integer对象,这个方法是从系统环境中读取系统变量,得到系统变量对应的Integer的值

Integer prop = Integer.getInteger("cpus");

4 . int和Integer之间的自动转型

对于基本类型和其对应的包装类型,编译器可以在int和Integer之间自动转型
(1)自动装箱(auto boxing):
当我们定义包装类型Integer n = 99时,编译器自动将int类型转化为Integer类型,这个操作过程被称为自动装箱

Integer n = 99;//Integer.valueOf(99)

(2)自动拆箱(auto unboxing):
当我们将Integer类型转化为int类型时,编译器会自动调用Integer的intValue()方法,这个操作过程被称为自动拆箱

int i = n;//n.intValue()

(3)注意事项:
①自动装箱和自动拆箱仅仅发生在编译阶段
②自动装箱和自动拆箱操作会影响代码的执行效率
③编译后的class代码是没有自动装箱和自动拆箱的,所以它严格区分基本类型和引用类型
④执行自动拆箱时可能会发生报错:
如果一个Integer类型指向null,那么在进行自动拆箱时就会抛出NullPointException

Integer x = null;
int y = x;
//NullPointException

5 . 包装类型定义的静态变量

Boolean t = Boolean.TRUE;
Boolean f = Boolean.FALSE;
int max = Integer.MAX_VALUE;//2147483647
int min = Integer.MIN_VALUE;//-2147483648
int sizeOflang = Long.SIZE;//64(bits)
int bytesOflang = Long.BYTES;//8(bytes)

6 . 包装类型的继承

Java的包装类型实际上全部继承自Number这个class,所以我们可以利用向上转型的概念,将任意一种包装类型,先变为Number对象,然后利用Number提供的各种方法将他们变为任意基本类型

Number num = new Integer(999);
byte b = num.byteValue();
int n = num.intValue();
long ln = num.longValue();
float f = num.floatValue();
double d = num.doubleValue();

(四)JavaBean

1 . JavaBean的概念

许多class的定义都符合:
(1)若干private实例字段
(2)通过public方法读写实例字段
例如:

public class Person {
    private String name;
    public int age;

    public String getName(){
    }
    public void setName(String name){
    }

    public int getAge(){
    }
    public void setAge(int age){
    }
}

我们把符合这种命名规范的class称为JavaBean
简单来说就是,当我们定义了一个private Type field时,再通过定义一个get方法和set方法来访问这个field
(1)private Type field
(2)public Type getField()
(3)public void setField(Type value)

2 . boolean字段的读方法

(1)private boolean child
(2)public boolean isChild()
(3)public void setChild(boolean value)

3 . 属性Property

(1)通常把一组对应的getter和setter称为属性(Property)
例如对于name属性:
对应读方法:getName()
对应写方法:setName()

public class Person {
    private String name;
    public int age;

    public String getName(){
    }
    public void setName(String name){
    }
}

(2)只有getter的属性称为只读属性(Read-only)

public class Person {
    private String name;
    public int age;

    public String getName(){
    }
    public void setName(String name){
    }

    public int getAge(){
    }
}   

(3)只有setter的属性称为只写属性(Write-only)
例如对于age属性:

public class Person {
    private String name;
    public int age;

    public String getName(){
    }
    public void setName(String name){
    }

    public void setAge(int age){
    }
}   

(4)属性只需要对应相应的getter/setter方法,不一定需要有对应的字段

4 . JavaBean的作用

(1)方便IDE读写属性
(2)传递数据
(3)枚举属性

可以利用IDE快速生成JavaBean,使用Introspector.getBeanInfo()获取属性列表

(五)枚举类Enumeration

1 . 枚举类的作用

(1)用enum定义常量类型
(2)常量本身带有类型信息
(3)使用==操作符比较常量

2 . enum定义的类型

enum定义的类型实际上是class
(1)继承自java.lang.Enum
(2)不能通过new创建实例
(3)所有常量都是唯一实例(引用类型)
(4)可以用于switch语句
例如:

public enum Color {
    RED, GREEN, BLUE;
}

//编译器编译出的class:

public final class Color extends Enum {
    public static final Color RED = new Color();
    public static final Color GREEN = new Color();
    public static final Color BLUE = new Color();

    private Color(){
    }
}

在这里,如果我们直接编写class编译器是不通过的,Java的enum是一种特殊语法,编译器会进行特殊的处理

3 . name()和ordinal()

(1)name():获取常量定义的字符串(不要使用toString())
(2)ordinal():返回常量定义的顺序(无实际意义)

package com.feiyangedu.sample;

public enum Weekday {

    SUN, MON, TUE, WED, THU, FRI, SAT;
}
package com.feiyangedu.sample;

public class Main {

    public static void main(String[] args) {
        for (Weekday day : Weekday.values()) {
            System.out.println(day.name());
        }
        Weekday fri = Weekday.FRI;
        // enum -> String:
        System.out.println("FRI.name() = " + fri.name());
        // 定义时的序号:
        System.out.println("FRI.ordinal() = " + fri.ordinal());
        // String -> enum:
        System.out.println(Weekday.valueOf("FRI").name());
        // 不存在的name:
        Weekday.valueOf("ABC");
    }
}

4 .为enum编写构造方法、字段和方法

构造方法必须申明为private
例如:

package com.feiyangedu.sample;

public enum Weekday {

    SUN("星期日"), MON("星期一"), TUE("星期二"), WED("星期三"), THU("星期四"), FRI("星期五"), SAT("星期六");

    private String Chinese;

    private Weekday(String Chinses) {
        this.chinese = chinese;
    }
    public String toChinese() {
        return chinese;
    }
}
package com.feiyangedu.sample;

public class Main {

    public static void main(String[] args) {
        Weekday fri = Weekday.FRI;
        System.out.println(fri.toChinese());//星期五
    }

(六)常用工具类

1 . Math

(1)Math类提供了数学计算的静态方法:

Math.pow(2,10);
Math.sqrt(2);
Math.exp(2);
Math.log(4);
Math.log10(100);
Math.sin(Math.PI / 6);//sin(π/6) = 0.5
Math.cos(Math.PI / 3);//cos(π/3) = 0.5

(2)Math类提供了一些常量:
①PI = 3.14159…
②E = 2.71828…
(3)Math.random()
Math.random()方法用于生成一个随机数
①这个随机数介于0和1之间0 <= random < 1
②可用于生成某个区间的随机数

//0 <= R < 1
double x1 = Math.random();//首先生成一个随机数

//MIN <= R < MAX
long MIN = 1000;
Long MAX = 9000;
double x2 = Math.random() * (MAX - MIN) + MIN;
double r = (long) x2;

2 . Random

Random类用来创建伪随机数

Random r = new Random();

r.nextInt();//生成下一个随机int
r.nextLong();//生成下一个随机long
r.nextFloat();//生成下一个随机float,介于0~1之间
r.nextDouble();//生成下一个随机double,介于0~1之间
r.nextInt(10);//生成0~10的随机数(不包括10)
r.nextInt(N);//生成不大于N的随机数

伪随机数的概念:
(1)给定种子后伪随机数算法会生成完全相同的序列
例如我们给定一个种子为12345:

Random r = new Random(12345);
for(int i = 0; i < 10; i++){
    System.out.println(r.nextInt(100));
}//51, 80, 41, 28...

(2)不给定种子时Random使用系统当前时间戳作为种子

3 . SecureRandom

如果我们需要使用安全的随机数,可以使用JDK提供的SecureRandom

SecureRandom sr = new SecureRandom();
for(int i = 0; i < 10; i++){
    System.out.println(sr.nextInt(100));
}

SecureRandom的缺点是运行较为缓慢

4 . BigInteger

JDC提供了BigInteger用来表示任意大小的整数

BigInteger bi = new BigInteger("1234567890");
System.out.println(bi.pow(5));

5 . BigDecimal

JDC提供了BigDecimal用来表示任意精度的浮点数

BigDecimal bd = new BigDecimal("123.10");
System.out.println(bd.multiply(bd));

下面是一些演示:

package com.feiyangedu.sample;

import java.math.BigDecimal;
import java.math.BigInteger;
import java.security.SecureRandom;
import java.util.Random;

public class Main {

    public static void main(String[] args) {
        // Math
        System.out.println(Math.sqrt(2)); // 1.414

        // Random
        Random rnd = new Random(123456);
        System.out.println(rnd.nextInt());
        System.out.println(rnd.nextInt());

        // SecureRandom
        SecureRandom sr = new SecureRandom();
        System.out.println(sr.nextInt());
        System.out.println(sr.nextInt());

        // BigInteger
        BigInteger bi = new BigInteger("1234567890");
        System.out.println(bi.pow(5));

        // BigDecimal
        BigDecimal bd = new BigDecimal("123.10");
        System.out.println(bd.multiply(bd));
    }
}
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值