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());//"[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实现聊天机器人