什么是对象?之前我们讲过,对象就是计算机中的虚拟物体。例如 System.out,System.in 等等。然而,要开发自己的应用程序,只有这些现成的对象还远远不够。需要我们自己来创建新的对象。
代码里怎么表示类呢,看一下类的语法
class 类 {
字段;
构造方法(参数) {
}
返回值类型 方法名(参数) {
代码
}
}
字段定义与之前学过的局部变量、方法参数的定义类似,它们本质都是变量
构造方法也是一种方法,在将来创建对象时被使用,作用是为对象字段赋初始值
从属于对象的方法不用 static 修饰,它就代表对象的行为,这里先放一下
例如:
public class Phone {
// 类型 名字
String brand; // 品牌
String memory; // 内存
String size; // 大小
String color; // 颜色
double price; // 价格
public Phone(String b, String m, String s, String c, double p) {
brand = b;
memory = m;
size = s;
color = c;
price = p;
}
}
有了类,才能创建对象,创建对象的语法
new 类构造方法(参数)
例如
public class TestPhone {
public static void main(String[] args) {
Phone p1 = new Phone("苹果", "128G", "6.1寸", "星光色", 5799.0);
Phone p2 = new Phone("红米", "4G", "6.53寸", "金色", 1249.0);
Phone p3 = new Phone("华为", "4G", "6.3寸", "幻夜黑", 999.0);
System.out.println(p1.color); // 获取p1的颜色
System.out.println(p1.price); // 获取p1的价格
p1.price = 3000.0; // 修改p1的价格
System.out.println(p1.price); // 获取p1的价格
}
}
总结一下:
如果字段没有通过构造方法赋值,那么字段也会有个默认值
类型 | 默认值 | 说明 |
---|---|---|
byte short int long char | 0 | |
float double | 0.0 | |
boolean | false | |
其它 | null |
比如说,想加一个字段 available 表示手机上架还是下架,这时就可以使用默认值统一设置
public class Phone {
// 类型 名字
String brand; // 品牌
String memory; // 内存
String size; // 大小
String color; // 颜色
double price; // 价格
boolean available; // 是否上架
public Phone(String b, String m, String s, String c, double p) {
brand = b;
memory = m;
size = s;
color = c;
price = p;
}
}
当方法参数与字段重名时,需要用在字段前面加 this 来区分
public class Phone {
// 类型 名字
String brand; // 品牌
String memory; // 内存
String size; // 大小
String color; // 颜色
double price; // 价格
boolean available; // 是否上架
public Phone(String brand, String memory, String size, String color, double price) {
this.brand = brand;
this.memory = memory;
this.size = size;
this.color = color;
this.price = price;
this.available = true;
}
}
提示
- 如果觉得自己写 this 比较麻烦,可以使用 IDEA 的快捷键 ALT + Insert 来生成构造方法
- 用 IDEA 生成的构造方法字段和方法参数都是用 this 区分好的
带参数的构造并不是必须的,也可以使用无参构造,例如
public class Student {
String name; // 姓名 null
int age; // 年龄 0
Student() {
}
}
使用无参构造创建对象示例如下
public class TestStudent {
public static void main(String[] args) {
Student s1 = new Student();
s1.name = "张三";
s1.age = 18;
System.out.println(s1.name);
System.out.println(s1.age);
}
}
无参构造有个特性:
方法执行总得需要一些数据,以前我们学习的主要是这种 static 方法,它的数据全部来自于方法参数。
今天开始,要学习对象方法,顾名思义,它是从属于对象的方法,语法上要去掉 static,变成这个样子
public class Cal {
double p;
int m;
double yr;
public Cal(double p, int m, double yr) {
this.p = p;
this.m = m;
this.yr = yr;
}
double cal() {
double mr = yr / 100.0 / 12;
double pow = Math.pow(1 + mr, m);
return m * p * mr * pow / (pow - 1);
}
}
看看改动成对象方法后,都有哪些代码发生了变化?为啥不需要参数了呢?
这种对象方法执行需要的数据:
既然我们讲的这种对象方法都从属于 Calculator 对象了,那么方法参数这里是不是就没必要再加一个 Calculator 对象了啊
方法体内这些本金、月份、利率,都来自于方法所从属的对象的字段。不用写前面的 c. 了
最后,方法调用时,为了表达与对象的这种从属关系,格式也应变化为:对象.方法()
public class TestCal {
public static void main(String[] args) {
// 对比相同本金,不同利率和贷款月份,计算总还款额(等额本息),看哪个更划算一些
Cal c1 = new Cal(100000.0, 24, 4.5);
double r1 = c1.cal();
System.out.println("4.5% 利率借 2 年:" + r1);
Cal c2 = new Cal(100000.0, 12, 6.0);
double r2 = c2.cal();
System.out.println("6.0% 利率借 1 年:" + r2);
}
}
例如:
对象的方法演化完毕
静态方法 vs 对象方法
- 而 static 方法需要的数据,全都来自于方法参数,它没有关联对象,没有对象的那一部分数据
- 对象方法执行的数据,一部分数据从方法参数转移至对象内部
public class Circle {
double r; // 半径
static double pi = 3.14; // 静态变量, 所有圆对象共享它
public Circle(double r) {
this.r = r;
}
double area() {
return pi * r * r;
}
}
回到测试代码
public class TestCircle {
public static void main(String[] args) {
Circle c1 = new Circle(1.0);
c1.pi = 3.14;
Circle c2 = new Circle(1.0);
c2.pi = 3;
System.out.println(c1.area()); // 圆的面积计算结果为 3
System.out.println(c2.area()); // 圆的面积计算结果为 3
}
}
两次计算结果相同,因为 c1.pi 和 c2.pi 都是修改的同一个变量。注意几点
Circle.pi = 3
static final pi = 3.14
Math.PI
,上面我们自己写的 pi 只是为了举例需要至今为止,一共学习了四种变量,下面就对它们做一个简单对比
public class TestVariable {
public static void main(String[] args) {
m(10);
if (true) {
C c1 = new C(30); // 出了if语句块,c1 对象就无法使用了,随带的它内部的对象变量也失效
}
}
static void m(int a) { // 1. 参数变量, 作用范围是从方法调用开始,直到方法调用结束
for (int i = 0; i < 10; i++) {
int b = 20; // 2. 局部变量, 作用范围从定义开始,到包围它的 } 为止, 必须赋初值才能使用
System.out.println(b);
}
}
}
class C {
int c; // 3. 对象变量(成员变量) 从对象创建开始, 到对象不能使用为止
public C(int c) {
this.c = c;
}
static int d = 40; // 4. 静态变量, 从类加载开始, 到类卸载为止
}
阅读代码发现,这两个类中有一些相同的对象变量和方法代码,能否减少这两个类的重复代码,答案是继承
继承的语法
class 父类 {
字段;
方法() {}
}
class 子类 extends 父类 {
}
可以用父子类继承的方式减少重复声明,例如 A 是父类,B,C 类是子类,那么
class A {
String name;
void test() {}
}
class B extends A {
}
class C extends A {
}
但注意,构造方法不能继承
class A {
String name;
A(String name) {
this.name = name;
}
void test() {}
}
class B extends A {
B(String name) {
super(name);
}
}
class C extends A {
C(String name) {
super(name);
}
}
java 中的类型分成了两大类
基本类型 primitive type
引用类型 reference type
包装类型 | 基本类型 | 备注 |
---|---|---|
Byte | byte | |
Short | short | |
Integer | int | |
Long | long | |
Float | float | |
Double | double | |
Character | char | |
Boolean | boolean |
隐式转换
byte a = 10;
int b = a; // 自动从 byte 转换为 int
强制转换
int c = 20;
byte d = (byte) c; // 在圆括号内加上要转换的目标类型
强制转换可能损失精度
int c = 1000;
byte d = (byte) c; // byte 的数字范围就是在 -128 ~ 127,存不下 1000,最后结果是 -24
int a = 20;
Integer b = a;
Integer c = new Integer(30);
int d = c;
顺箭头方向(向上)隐式转换,即子类可以用它的更高层的类型代表,表达一种是一个的关系
例如一个猫对象,可以隐式转换为动物
Animal a = new Cat(); // 用父类型的变量 a 代表了一只猫对象
Object b = new Cat(); // 用祖先类型的变量 b 代表了一只猫对象
逆箭头方向(向下)首先要符合是一个规则,然后用显式强制转换
如果一个动物变量,它代表的是一个猫对象,可以通过强制转换还原成猫
Animal a = new Cat();
Cat c = (Cat) a;
如果一个动物变量,它代表的是一个猫对象,即使强制转换,也不能变成狗,编译不报错,但运行就会出现 ClassCastException
Animal a = new Cat();
Dog d = (Dog) a;
为什么需要向上转型?主要是为了使用父类统一处理子类型
例1:
static void test(Animal a) {
}
这时,此方法既可以处理猫对象,也可以处理狗对象
test(new Cat());
test(new Dog());
例2:用父类型的数组,可以既装猫对象,也装狗对象
Animal[] as = new Animal[]{ new Cat(), new Dog() };
Animal a = new Cat();
如果想知道变量 a 代表对象的实际类型,可以使用
System.out.println(a.getClass()); // 输出结果 class com.itheima.Cat
如果想检查某个对象和类型之间是否符合【是一个】的关系,可以使用
Animals a = new Cat();
Object b = new Cat();
System.out.println(a instanceof Cat); // true
System.out.println(a instanceof Dog); // false
System.out.println(b instanceof Animal);// true
经常用在向下转型之前,符合是一个的关系,再做强制类型转换
除了以上转换规则,在赋值、方法调用时,一旦发现类型不一致,都会提示编译错误,需要使用一些转换方法才行
例如:两个字符串对象要转成整数做加法
String a = "1";
String b = "2";
System.out.println(a + b); // 这样不行,字符串相加结果会拼接为 12
System.out.println(Integer.parseInt(a) + Integer.parseInt(b)); // 转换后再相加,结果是 3
同一个方法在执行时,表现出了不同的行为,称这个方法有多态的特性。
不是所有方法都有多态,像之前写过的静态方法,不管怎么调用,表现出的行为都是一样的。那么要成为这种多态方法要满足哪些条件呢?先来看看多态这个词是怎么来的
多态,英文 polymorphism 本意是多种形态,是指执行一个相同的方法,最终效果不同。为什么会有这种效果?
方法虽然都是同一个,但调用它们的对象相同吗?看起来都是 Animal 啊,其实不是
方法具备多态性的两个条件:
用父类型代表子类对象,有了父类型才能代表多种子类型,只有子类型自己,那将来有多种可能吗,不行吧?
第二个条件,是子类和父类得有一个相同的 say 方法。如果子类存在与父类相同的方法,称发生了方法重写。重写方法要满足:
只有重写了,才能表现出多种形态,如果没有重写,调用的都是父类方法,最终的效果是相同的,没有多态了
在控制器代码中,需要用 if else 来判断使用哪个 Calculator 对象完成计算,Calculator0 还是 Calculator1,将来如果贷款类型越来越多,就要写好多 if else,如何避免呢?利用多态的原理,让 jvm 帮我们做判断
原来的
显然不行
改写如下:
class Calculator {
// ...
String[] cal() {
return null;
}
String[][] details() {
return null;
}
}
class Calculator0 extends Calculator {
Calculator0(double p, int m, double yr) {
super(p, m, yr);
}
@Override
String[] cal() {
// ...
}
@Override
String[][] details() {
// ...
}
}
class Calculator1 extends Calculator {
Calculator1(double p, int m, double yr) {
super(p, m, yr);
}
@Override
String[] cal() {
// ...
}
@Override
String[][] details() {
// ...
}
}
根据类型创建不同 Calculator 对象有点小技巧(避免了创建对象时的 if else),如下:
Calculator[] getCalculator(double p, int m, double yr) {
return new Calculator[] {
new Calculator0(p, m, yr),
new Calculator1(p, m, yr)
};
}
最后通过父类型来执行,表面上是调用 Calculator 父类的 cal() 和 details() 方法,但实际执行的是某个子类的 cal() 和 details() 方法,通过多态,避免了方法调用时的 if else 判断
@Controller
public class CalController {
Calculator[] getCalculator(double p, int m, double yr) {
return new Calculator[] {
new Calculator0(p, m, yr),
new Calculator1(p, m, yr)
};
}
@RequestMapping("/cal")
@ResponseBody
String[] cal(double p, int m, double yr, int type) {
Calculator[] cs = getCalculator(p, m, yr);
return cs[type].cal();
}
@RequestMapping("/details")
@ResponseBody
String[][] details(double p, int m, double yr, int type) {
Calculator[] cs = getCalculator(p, m, yr);
return cs[type].details();
}
}
cs[type] 是根据类型找到对应的子类对象,例如
关于多态的应用的例子讲完了,总结一下
前提
效果
什么时候使用多态
Java 中可以用访问修饰符来对字段或方法进行访问权限控制,一共有四种
名称 | 访问权限 | 说明 |
---|---|---|
public | 标识的【字段】及【方法】及【类】,谁都能使用 | |
protected | 标识的【字段】及【方法】,只有同包类、或是子类内才能使用 | |
标识的【字段】及【方法】及【类】,只有同包类才能使用 | 默认访问修饰符 | |
private | 标识的【字段】及【方法】只有本类才能使用(或内部类) |
package com.itheima.encapsulation;
public class Car {
private int y; // 私有的
private void test() { } // 私有的
void update() {
// 本类内可以使用
System.out.println(this.y);
this.test();
}
}
package com.itheima.encapsulation; // 同包测试类
public class Test1 {
public static void main(String[] args) {
Car car = new Car();
System.out.println(car.y); // 错误,不能访问 private 字段
car.test(); // 错误,不能访问 private 方法
}
}
package com.itheima.encapsulation;
public class Car {
int y; // 默认的
void test() {} // 默认的
void update() {
// 本类内可以使用
System.out.println(this.y);
this.test();
}
}
package com.itheima.encapsulation; // 同包测试类
public class Test2 {
public static void main(String[] args) {
Car car = new Car();
System.out.println(car.y); // 同包可以使用
car.test(); // 同包可以使用
}
}
package com.itheima; // 不同包测试类
import com.itheima.encapsulation.Car;
public class Test3 {
public static void main(String[] args) {
Car car = new Car();
System.out.println(car.y); // 错误,不同包不能访问 默认 字段
car.test(); // 错误,不同包不能访问 默认 方法
}
}
package com.itheima.encapsulation;
public class Car {
protected int y; // 受保护的
protected void test() {} // 受保护的
// 本类内可以使用
void update() {
System.out.println(this.y);
this.test();
}
}
package com.itheima; // 不同包子类
import com.itheima.encapsulation.Car;
public class SubCar extends Car {
void display() {
System.out.println(this.y); // 不同包子类内可以使用
this.test(); // 不同包子类内可以使用
}
}
尽可能让访问范围更小
JavaBean 规范
例子
class Teacher implements Serializable {
private String name; // 小写
private boolean married; // 已婚
private int age;
public boolean isMarried() { // 对 boolean 类型,用这种 isXXX
return this.married;
}
public void setMarried(boolean married) {
this.married = married;
}
// get 方法 用来获取私有字段值
public String getName() { // get 后面单词首字母要大写
return this.name;
}
// set 方法 用来修改私有字段值
public void setName(String name) {
this.name = name;
}
public Teacher(String name, boolean married) {
this.name = name;
this.married = married;
}
public Teacher() {
}
}
测试类
public class TestJavaBean {
public static void main(String[] args) {
Teacher t = new Teacher("张老师", false);
// 全部改用公共方法来间接读写字段值
System.out.println(t.getName());
System.out.println(t.isMarried());
t.setMarried(true);
System.out.println(t.isMarried());
}
}
Java Bean 主要用来封装数据,不会提供哪些包含业务逻辑的方法
最后要区分两个名词:字段和属性