欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

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

程序员文章站 2022-03-08 18:53:21
...

一、面向对象的概念

在计算机编程中,我们可以把现实生活中的一切事物视为对象,而事物产生变化的动作我们视为对象实现某种目的的方法。
通过将不同的对象按照其属性进行分类,从而具体化任何一个对象的实例,在这里,实例是某一对象的具体表现形式,例如,如果我们创建一个名为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());//"[email protected]"

注意:对于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));
    }
}
相关标签: Java 学习心得