- 子类对象实例化过程图示:
思考:
1.为什么super(...)和this(...)调用语句不能同时在一个构造器中出现?
答: 因为他俩都只能出现在首行,所以只能写一个(好比古代皇后只能有一个)
2.为什么super(...)或this(...)调用语句只能作为构造器中的第一句出现?
答: 无论通过哪个构造器创建子类对象,需要保证先初始化父类.
目的: 当子类继承父类后,"继承"父类中所有的属性和方法,因此子类有必要知道父类如何为对象进行初始化.
所以加载顺序是首先加载间接父类,其次加载直接父类,然后才是加载到本类
强调说明:
虽然创建子类对象时,调用了父类的构造器,但是自始至终就创建过一个对象,即为new的子类对象
图示:
虽然加载了父类构造器了,但不认为创建了多个对象,对外是整个红框,体现的是一个类型的对象,就是new的Dog
5.6.多态性-多态性的使用
面向对象特征之三: 多态性
1.多态性的理解: 可以理解为一个事物的多种形态
2.什么是多态性: 父类的引用指向子类的对象(或子类的对象赋给父类的引用)
Person p = new Man();
Object obj = new 任意();
3.多态的使用: 虚拟方法使用
有了对象的多态性后,在编译期(编译器编译的时候),只能调用父类中声明的方法,但在运行期,实际执行的是子类重写父类的方法
如果子类中没有重写父类的方法,就执行父类的方法(这样使用多态性没有意义)
p.eat();
总结:编译时看等号左边的类型,运行时看右边的对象(编译时是父类,执行时是子类)
4.多态性的使用前提(只说方法的事没有属性的事,多态性方面跟属性没关系): (1)要有类的继承关系 (2)方法的重写
5.对象的多态性: 只适用于重写的方法,不适用于属性(属性的编译和运行都看左边).
如果Java中没有多态性的话,那抽象类和接口就没有存在的意义,因为抽象类和接口都不能造对象,开发中使用抽象类和接口,一定会提供子类的对象或实现类的对象,这里体现都是多态性
多态性的好处: 减少大量方法的重载
public class PersonTest {
public static void main(String[] args) {
Person p1 = new Person();
p1.eat();
Man man = new Man();
man.eat();
man.age = 25;
man.earnMoney();
System.out.println("*********************");
// 对象的多态性: 父类的引用(p2)指向子类的对象(new Man())
Person p2 = new Man(); // 多态的形式
Person p3 = new Woman();
// 多态的使用: 当调用子类同名同参数的方法时,实际执行的是子类重写父类的方法---虚拟方法调用
// 有了多态的形式后,通过引用调用子父类都声明过的方法,真正执行的是子类重写父类的方法
p2.eat(); // 编译看左,运行看右
p2.walk();
// 多态性的使用不能调父类没有的方法属性, 编译时,p2是Person类型
// p2.earnMoney(); 报错
// p2.isSmoking = true; 报错
System.out.println(p2.id); // 1001; 多态性与属性无关,p2编译时的类型就是属性的类型
}
}
public class Person {
String name;
int age;
int id = 1001;
public void eat(){
System.out.println("人吃饭");
}
public void walk(){
System.out.println("人走路");
}
}
public class Man extends Person{
boolean isSmoking;
int id = 1002;
public void earnMoney(){
System.out.println("男人负责挣钱");
}
/*@Override
public void eat() {
System.out.println("男人多吃肉,长肌肉");
}*/
@Override
public void walk() {
System.out.println("男人霸气的走路");
}
}
public class Woman extends Person{
boolean isBeauty;
public void goShopping(){
System.out.println("女人喜欢购物");
}
@Override
public void eat() {
System.out.println("女人少吃为了减肥");
}
@Override
public void walk() {
System.out.println("女人窈窕的走路");
}
}
- 多态性的使用举例:
import java.sql.Connection;
// 多态性的使用举例一:
public class AnimalTest {
public static void main(String[] args) {
AnimalTest test = new AnimalTest();
test.func(new Dog());
test.func(new Cat());
}
// 使用多态性: 凡是new子类的对象
// 每种动物对一件事表现出来的形态都不一样 就是多态
public void func(Animal animal){ // Animal animal = new Dog(); 对象多态性的形式
// 因为形参是Animal类型的,所以只能调Animal类里的方法,但实际new了一个Dog类的对象,真正运行的时候,是Dog重写父类方法的执行
animal.eat();
animal.shout();
}
// 不用多态性的写法: 声明什么类型只能new这个类型的对象
public void func(Dog dog){
dog.eat();
dog.shout();
}
public void func(Cat cat){
cat.eat();
cat.shout();
}
}
// 父类
class Animal{
public void eat(){
System.out.println("动物进食");
}
public void shout(){
System.out.println("动物叫");
}
}
// 子类
class Dog extends Animal{
@Override
public void eat() {
System.out.println("狗吃骨头");
}
@Override
public void shout() {
System.out.println("汪汪汪");
}
}
class Cat extends Animal{
@Override
public void eat() {
System.out.println("猫吃鱼");
}
@Override
public void shout() {
System.out.println("喵喵喵");
}
}
// 举例二:
class Order{
public void method(Object obj){
}
}
// 举例三:
class Driver{
// 形参里传哪个对象就建立跟哪个数据库的连接,因为子类方法都重写过了,自然就能实现对那个数据库中表的操作
public void doData(Connection conn){ // conn = new MySQLConnection();/ conn = new OracleConnection();/...
// 规范步骤去操作数据 都是子类重写过的方法
/*conn.method1();
conn.method2();
conn.method3();*/
}
}
- 虚拟方法调用的再理解
- 面试题: 多态是编译时行为还是运行时行为?
编译时期和运行时期的区别
面试题: 多态是编译时行为还是运行时行为?
运行时行为;看代码看不出来调的是哪个,因为有随机数,运行了才知道随机数是多少
/ 面试题: 多态是编译时行为还是运行时行为?
// 证明结论: 运行时行为,main函数中的animal引用指向哪个子类的对象,是由运行时产生的随机数再调用了判断得到的,最后运行结果是返回对象类型中eat()方法而不是编译时Animal类中的eat()方法
import java.util.Random;
public class InterviewTest {
public static void main(String[] args) {
int key = new Random().nextInt(3); // 取随机数: 0~2
System.out.println(key);
// 通过下面new的对象来看,这实际上就是多态
Animal animal = getInstance(key);
animal.eat();
}
public static Animal getInstance(int key){
switch (key){
case 0:
return new Cat();
case 1:
return new Dog();
default:
return new Sheep();
}
}
}
class Animal{
protected void eat(){
System.out.println("animal eat food");
}
}
class Dog extends Animal {
@Override
protected void eat() {
System.out.println("dog eat bone");
}
}
class Cat extends Animal {
@Override
protected void eat() {
System.out.println("cat eat fish");
}
}
class Sheep extends Animal {
@Override
protected void eat() {
System.out.println("sheep eat grass");
}
}
- 小结: 方法的重载和重写
5.7.向下转型的使用
内存解析的说明
引用类型的变量,只可能储存两类值:
- null
- 内存地址值,包含变量(对象)的类型
例如,直接打印一个实例化后对象的值
Phone p = new Phone();
System.out.println(p)
得到的结果为:
- instanceof关键字的使用
向下转型的目的: a instanceof A 判断 对象a 运行时是不是多态中子类的A类类型,如果是,则要向下转型,向下转型的目的是可以让对象a点出运行时实际(真实)类型对象的特有的属性或方法
a instanceof A: 判断对象a是否是类A的实例.如果是,返回true,如果不是,返回false
使用情境,为了避免在向下转型时出现ClassCastException的异常,在向下转型之前,
先进行instanceof的判断,一旦返回true,就进行向下转型.如果返回false,不进行向下转型.
如果 a instanceof A 返回 true,则 a instanceof B 也返回true,
其中类B和类A的不都是同一父类的子类
public class PersonTest {
public static void main(String[] args) {
Person p1 = new Person();
p1.eat(); // 父类调用自己的方法
Man man = new Man();
man.eat(); // 子类继承父类调用父类的方法
man.age = 25;
man.earnMoney();
System.out.println("*********************");
// 对象的多态性: 父类的引用(p2)指向子类的对象(new Man())
Person p2 = new Man(); // 多态的形式
Person p3 = new Woman();
Person p4 = new Person();
Man m1 = new Man();
// 多态的使用: 编译时当调用子类同名同参数的方法时,认为是父类的方法,实际执行的是子类重写父类的方法---虚拟方法调用
// 有了多态的形式后,通过引用调用子父类都声明过的方法,真正执行的是子类重写父类的方法
p2.eat(); // 编译看左,运行看右; 在编译期认为p2是Person类型,所以只能调用Person中声明的属性和方法
p2.walk();
System.out.println("===================");
p2.name = "tom";
// 多态性的使用不能调父类没有子类特有的方法,属性, 编译时,p2是Person类型
// p2.earnMoney(); // 报错
// p2.isSmoking = true; // 报错
System.out.println(p2.id); // 1001; 多态性与属性无关,p2编译时的类型就是属性的类型
// 有了对象的多态性后,内存中实际上是加载了子类特有的属性和方法的,但是由于变量声明为父类类型.
// 导致编译时,只能调用父类中声明的属性和方法,(子类特有的属性和方法相当于被屏蔽了)子类特有的属性和方法不能调用.
// 如何才能调用子类特有的属性和方法?
// Man m1 = p2; // java中左右赋值要么左右类型一样,要么是基本数据类型且有自动类型提升; 父类型变量不能赋给子类型变量
System.out.println(p4);
System.out.println(m1);
p4 = m1; // 类类型变量,子类型变量可以赋给父类型变量,表现为多态;
// 向下转型: 使用强制类型转换符
System.out.println(p2); // p2运行时是Man类型
Man m2 = (Man) p2; // 编译时,把Person类型的变量强转成Man类型的一个新变量,相当于m2是Man类型的p2
m2.earnMoney();
m2.isSmoking = true;
// 使用强转时,出现ClassCastException的异常
// Woman w1 = (Woman) p2; // 编译虽然通过,但运行时p2已经是Man类型了,子类类型之间不能不能互相转换,地址类型不相同,
// w1.goShopping(); // 执行时出错,出现ClassCastException的异常
/*
* instanceof关键字的使用
* a instanceof A: 判断对象a是否是类A的实例.如果是,返回true,如果不是,返回false
* 使用情境,为了避免在向下转型时出现ClassCastException的异常,在向下转型之前,
* 先进行instanceof的判断,一旦返回true,就进行向下转型.如果返回false,不进行向下转型.
* 如果 a instanceof A 返回 true,则 a instanceof B 也返回true,
* 其中类B是类A的父类
*/
// 本质就是把p2的new的类型能否赋值给声明时的类型,是子类对象一定是父类对象,是父类对象不一定是子类对象
if (p2 instanceof Person){
System.out.println("person");
}
if (p2 instanceof Man){
Man m3 = (Man) p2;
m3.earnMoney();
System.out.println("man");
}
if (p2 instanceof Woman){
Woman w1 = (Woman) p2;
w1.goShopping();
System.out.println("woman");
}
// 练习:
// 问题一: 编译时通过,运行时不通过
/*Person p5 = new Woman();
Man m4 = (Man) p5;*/
Person p6 = new Man();
Man m6 = (Man) p6;
// 问题二: 编译时通过,运行时也通过
Object obj = new Woman();
Person p = (Person) obj;
// 问题三: 编译时不通过,运行时也不通过
// Man m5 = new Woman();
// String s = new Date(); // Date类型和String类没任何关系
}
}
public class Person {
String name;
int age;
int id = 1001;
public void eat(){
System.out.println("人吃饭");
}
public void walk(){
System.out.println("人走路");
}
}
public class Man extends Person{
boolean isSmoking;
int id = 1002;
public void earnMoney(){
System.out.println("男人负责挣钱");
}
@Override
public void eat() {
System.out.println("男人多吃肉,长肌肉");
}
@Override
public void walk() {
System.out.println("男人霸气的走路");
}
}
public class Woman extends Person{
boolean isBeauty;
public void goShopping(){
System.out.println("女人喜欢购物");
}
@Override
public void eat() {
System.out.println("女人少吃为了减肥");
}
@Override
public void walk() {
System.out.println("女人窈窕的走路");
}
}
Person p2 = new Man();
Man m1 = (Man) p2;
父类向下转型图示:
- 多态练习: 调用属性和方法
子类继承父类,子类和父类同名的变量会继承父类的但不会把父类覆盖,也就是说子类的同名属性是独立的,但是方法会把父类覆盖
方法里想调父类的属性,就要用到super.属性,否则,输出this.属性结果还是子类属性的值
总结:
1.若子类重写了父类方法,就意味着紫烈里定义的方法彻底覆盖了父类里的同名方法,
系统将不可能把父类里的方法转移到子类中: 编译看左边,运行看右边
2.对于实例变量(属性)则不存在这样的现象,即使子类里定义了与父类完全相同的实例变量,
这个实例变量依然不可能覆盖父类中定义的实例边浪: 编译运行都看左边
public class FieldMethodTest {
public static void main(String[] args) {
Sub s = new Sub();
System.out.println(s.count); // 20 this关键字就近原则
s.display(); // 20 this: 当前类就近原则,super: 父类的变量
/*Base b = new Base();
System.out.println(b.count);*/
Base b = s; // 多态性
// == 对于引用数据类型来讲,比较的是两个引用数据类型变量的地址值是否相同
// true 指向同一堆空间中的对象
System.out.println(b == s); // true
// 多态性不适用于属性,属性值是谁就看声明这个属性的类型,编译运行都看左边
System.out.println(b.count); // 10
// 声明了一个多态,通过b的引用调用子父类都有的方法的时候,实际调用子类的方法(虚拟方法调用),运行看右边
b.display(); // 20
}
}
class Base{
int count = 10;
public void display(){
System.out.println(this.count);
}
}
class Sub extends Base {
int count = 20;
public void display(){
System.out.println(this.count);
}
}
- 多态练习: 基本操作
public class InstanceTest {
public static void main(String[] args) {
// 基类用来造对象的
InstanceTest test = new InstanceTest();
test.method(new Graduate());
}
public void method(Person e){ // 上面的实参赋给形参,这就构成了多态
// 虚拟方法调用
String info = e.getInfo();
System.out.println(info);
// 方式一:
// 输出条件先从小范围的类(子类)输出,如果从大的类(父类)输出,下面子类就无法输出了
/*if (e instanceof Graduate){
System.out.println("a graduate");
}else if (e instanceof Student){
System.out.println("a student");
// else 不用再判断了,因为能进来就一定是个Person类
}else{
System.out.println("a person");
}*/
// 方式二:
// 如果 a instanceof A 返回 true,则 a instanceof B 也返回true,
// 其中类B是类A的父类
if (e instanceof Graduate){
System.out.println("a graduate");
}
if (e instanceof Student){
System.out.println("a student");
}
if (e instanceof Person){
System.out.println("a person");
}
}
}
class Person{
protected String name = "person";
protected int age = 50;
public String getInfo(){
return "name: " + name + "\n" + "age: " + age;
}
}
class Student extends Person{
protected String school = "pku";
public String getInfo(){
return "name: " + name + "\n" + "age: " + age + "\nschool: " +school;
}
}
class Graduate extends Student{
protected String major = "IT";
public String getInfo(){
return "name: " + name + "\n" + "age: " + age + "\nschool: " +school + "\nmajor: " + major;
}
}
- 多态练习: 几何图形
public class GeometricTest {
public static void main(String[] args) {
// 下面的方法要想被调用,就要创建当前类的对象
GeometricTest test = new GeometricTest();
Circle c1 = new Circle(2.4, "white", 1.0);
test.displayGeomericObject(c1);
Circle c2 = new Circle(1.2, "green", 2.0);
test.displayGeomericObject(c2);
boolean isEquals = test.equalsArea(c1, c2);
System.out.println("c1和c2面积是否相等: " + isEquals);
MyReactangle react = new MyReactangle(2.4, 1.2, 1.0, "red");
System.out.println("矩形面积: " + react.findArea());
}
// 利用动态绑定,测试两个对象的面积是否相等
public boolean equalsArea(GeometricObject o1, GeometricObject o2){
return o1.findArea() == o2.findArea();
}
// 显示对象面积
// 这一块已经体现了多态性的使用
// 声明的时候是 GeomericObject(父类)的类型,实际调用放的是new Circle(...)子类类型,所以与抽象类不冲突
// 只要传的是该类子类的对象,就一定是多态
public void displayGeomericObject(GeometricObject o){
System.out.println("面积为: " + o.findArea());
}
}
// 将几何图形改为抽象类,因此不能再造对象,事实上也不会去造而是造子类对象
public abstract class GeometricObject {
protected String color;
protected double weight;
/*public GeometricObject(){
}*/
public GeometricObject(String color, double weight){
super();
this.color = color;
this.weight = weight;
}
public void setColor(String color){
this.color = color;
}
public String getColor(){
return this.color;
}
public void setWeight(double weight){
this.weight = weight;
}
public double getWeight(){
return this.weight;
}
// 因为几何图形的面积求法不一,所以将它抽象化,这样子类自然就需要重写求面积方法
public abstract double findArea(); // 父类被重写的方法
}
public class Circle extends GeometricObject{
private double radius; // 定义一个私有属性顺便在构造器中初始化
public Circle(double radius, String color, double weight){
super(color, weight); // 调父类指定构造器
this.radius = radius;
}
public void setRadius(double radius){
this.radius = radius;
}
public double getRadius(){
return this.radius;
}
@Override
public double findArea() {
return Math.PI * radius * radius;
}
}
public class MyReactangle extends GeometricObject {
double width; // 矩形宽
double height; // 矩形长
public MyReactangle(double width, double height, double weight, String color){
super(color, weight);
this.height = height;
this.width = width;
}
public void setWidth(double width){
this.width = width;
}
public double getWidth(){
return this.width;
}
public void setHeight(double height){
this.height = height;
}
public double getHeight(){
return this.height;
}
@Override
public double findArea() {
return this.height * this.width;
}
}
- 多态练习: 重写方法
public class InterviewTest1 {
public static void main(String[] args) {
Base1 base = new Sub1();
// 编译看左边,运行看右边
base.add(1,2,3); // sub1
Sub1 s = (Sub1) base;
// 调用子类特有的方法
// 父类add的形参是不确定多个,子类是确定多个,优先选确定多个的
s.add(1,2,3); // sub2
}
}
// 在可变形参时讲到,定义形参时,int...和int[]不能同时出现,意思是编译器认为他俩一样
// 所以下面两种方法可理解为重写
class Base1{
public void add(int a, int... arr){
System.out.println("Base1");
}
}
class Sub1 extends Base1{
public void add(int a, int[] arr){
System.out.println("sub1");
}
public void add(int a, int b, int c){
System.out.println("sub2");
}
}
5.8.Object类的使用-- == 和 equals()的区别
结论: 凡是基本数据类型之间比较就用 == ,引用数据类型之间比较久用equals()方法,特别地,除String,Date,File等包装类之外自定义的类之间的比较都要重写equals方法
面试题: == 和 equals()的区别
一.回顾 == 的使用:
==: 运算符
1.可以使用在基本数据类型变量和引用数据类型变量中
2.如果比较的是基本数据类型变量:比较两个变量保存的数据是否相等.(不一定类型要相同),一般不用特别关注类型,因为有自动类型提升
如果比较的是引用数据类型变量:比较两个对象的地址值是否相同(两个引用是否指向同一对象实体/堆空间当中是否同一对象)
补充: == 符号使用时,必须保证符号左右两边的变量类型一致(但不一定要相同),例如:
System.out.println("Hello" == new java.util.Date()); // 不同类类型进行比较,编译不通过
二.equals()方法的使用:
1.是一个方法,而非运算符
2.只能适用于与引用数据类型(只能通过类创建对象来调用)
3.Object类中equals()的定义:
public boolean equals(Object obj) {
return (this == obj);
}
说明: Object类中定义的equals()和 == 的作用是相同的,比较两个对象的地址值是否相同(两个引用是否指向同一对象实体/堆空间当中是否同一对象)
4.像String,Date,File,包装类等都重写了Object类中的equals()方法.重写以后,比较的不是两个引用的地址是否相同,
而是比较两个对象的"实体内容"是否相同. 实体内容实际指类中的属性是否相同
除了以上的特殊类,一般自定义类调用的默认是Object类中的equals()方法,其方法只是比较地址值是否相等
5.通常情况下,自定义的类如果使用equals()方法的话,也通常是比较两个对象的"实体内容"是否相同
那么这时候就需要对Object类中的equals()进行重写
equals方法重写原则: 比较两个对象"实体内容"是否相同
public class EqualsTest {
public static void main(String[] args) {
// 基本数据类型:
int i = 10;
int j = 10;
double d = 10.0;
// 除Boolean,其他七种都可以用 == 符号比较
// 不同基本类型也可以比,主要看存储的值是否相等
System.out.println(i == j); // true
System.out.println(i == d); // true 自动类型提升
// 基本数据类型运算不和Boolean类型一起使用
/*boolean b = true;
System.out.println(i == b);*/ // 编译报错
// 除了boolean型之外都可以
// char本质就是整数,用整数对应字符
char c = 10; // char型可以赋值数字
System.out.println(i == c); // true
char c1 = 'A';
char c2 = 65;
System.out.println(c1 == c2); // true
// 引用类型: 自定义类,jdk里提供的api都一样
Customer cust1 = new Customer("tom", 20);
Customer cust2 = new Customer("tom", 20);
// 每次new的时候堆空间都会创建一个地址,
System.out.println(cust1 == cust2); // false
String str1 = new String("java");
String str2 = new String("java");
System.out.println(str1 == str2); // false
// equals方法是Object中定义过的,这时equals方法相当于用了多态
// equals方法的形参是Object类型的,放了子类Customer类型的对象
System.out.println("====================");
System.out.println(cust1.equals(cust2));// false
// 他是String当中重写的equals方法,所以不会按照Object中来声明了
System.out.println(str1.equals(str2));// true
Date date1 = new Date(1615161L); // 构造器带的参数通常就是给类里的属性赋值的
Date date2 = new Date(1615161L);
System.out.println(date1.equals(date2));// true
}
}
public class Customer {
private String name;
private int age;
public Customer(){
}
public Customer(String name, int age){
this.name = name;
this.age = age;
}
public void setAge(int age){
this.age = age;
}
public int getAge(){
return this.age;
}
public void setName(String name){
this.name = name;
}
public String getName(){
return this.name;
}
@Override
// equals方法重写原则: 比较两个对象"实体内容"(即: name和age)是否相同
public boolean equals(Object obj){ // 形参这里已经用了多态
// 如果当前调用equals()方法的对象的地址值等于比较那个对象的形参的地址值
if (this == obj){
return true; // 地址相同返回true,否则执行下一步
}
// 将形参向下转型;形参是Object类型,调用equals的实参是Customer类型
// 编译的时候,将父类obj对象向下强转型才能调用子类Customer的对象的属性和方法,否则是点不出Customer类的结构的,编译看左边,编译时还是Object类型
// 虽然运行的时候,形参实际接收到的obj是Customer类,因为多态虚拟方法使用,但是不强转型,写不出Customer类的结构
if (obj instanceof Customer){
Customer cust = (Customer) obj;
// if (this.age == cust.age && this.name.equals(cust.name)) {
// return true;
// }else{
// return false;
// }
// 或
// name属性是String类型,比较内容不能用==(地址比较),要用String中重写的equals方法比较其实体内容
// 而age是基本数据类型只需比较数值是否相等即可
return this.age == cust.age && this.name.equals(cust.name);
}else{
return false;
}
}
}
String str1 = new String("java");
String str2 = new String("java");
System.out.println(str1 == str2); // false
这里用 == 判断结果是false是因为判断的不是常量池里的内容,这里用两个String类型的对象进行比较的是对象的地址值,而不是内容,所以结果为false
String s1 = "BB";
String s2 = "BB";
System.out.println(s1 == s2); // true
s1 == s2的原因: String类型的内容存放在方法区的字符串常量池中,再定义一个变量发现内容和已有的相同了,新变量直接复用了旧变量
意味着两变量赋过来的地址是一样的,所以用==判断就是true
String s1 = new String("BB");
String s2 = "BB";
System.out.println(s1 == s2); // false
当通过new来造的String类型对象来比较内容的时候,地址就不一样了,所以用 == 比较结果为false,如果是用equals()方法比较则是true,因为不管怎么写比的都是内容
- equals()方法练习:
public class OrderTest {
public static void main(String[] args) {
Order o1 = new Order(1001, "AA");
Order o2 = new Order(1001, "BB");
System.out.println(o1.equals(o2)); // false
Order o3 = new Order(1001, "BB");
System.out.println(o2.equals(o3)); // true // equals方法重写
// s1 == s2的原因: String类型的内容存放在方法区的字符串常量池中,再定义一个变量发现内容和已有的相同了,新变量直接复用了旧变量
// 意味着两变量赋过来的地址是一样的,所以用==判断就是true
String s1 = "BB";
String s2 = "BB";
String s3 = new String("BB");
System.out.println(s1 == s2); // true
System.out.println(s1 == s3); // false
}
}
class Order {
private int orderId;
private String name;
public Order(int orderId, String name) {
super();
this.name = name;
this.orderId = orderId;
}
public void setOrderId(int orderId) {
this.orderId = orderId;
}
public int getOrderId() {
return this.orderId;
}
public void setName(String name) {
this.name = name;
}
public String getName() {
return this.name;
}
public boolean equals(Object obj) {
if (this == obj) { // 判断两个对象地址是否相同
return true;
}
// 地址不相同则执行下面类型判断
if (obj instanceof Order) { // 形参传入的obj是不是个Order
Order order = (Order) obj;
// 正确写法: 结论: 只要比较基本属性类型就用== ,只要比较引用数据类型都用equals
return this.orderId == order.orderId && this.name.equals(order.name);
// 错误写法:
// return this.orderId == order.orderId && this.name == order.name;
} else {
return false;
}
}
}
- 单元测试方法的使用
5.9.Object类的使用--toString()方法
Object类中toString()的使用:
- 1.当输出一个对对象的引用时,实际上就是默认调用当前对象的toString()方法
- Object类中toString()的定义:
public String toString() {
return getClass().getName() + "@" + Integer.toHexString(hashCode());
}- 3.像String,Date,File,包装类等都重写了Object类中toString()方法
使得在调用欧冠对象的toString()时,返回"实体内容"信息- 4.自定义类也可以重写toString()方法,当调用此方法时,返回对象的"实体内容"
5.10.包装类的使用
1.java提供了8种基本数据类型对应的包装类,使得基本数据类型的变量具有类的特征
2.掌握的:基本数据类型,包装类,String三者之间的相互转换
public class WrapperTest {
// String类型 --> 基本数据类型,包装类: 调用包装类的parseXxx(String s) (主要掌握)
@Test
public void test5(){
String str1 = "123";
// 错误写法: 强转类型 必须要有子父类关系,无关系的两个类不能强转
// int num1 = (int) str1;
// Integer in1 = (Integer) str1;
// 如果数字里有包含其他字符可能会报NumberFormatException
int num2 = Integer.parseInt(str1);
System.out.println(num2 + 1);
String str2 = "true";
boolean b = Boolean.parseBoolean(str2);
System.out.println(b);
}
// 基本数据类型,包装类 --> String类型: 调用String重载的ValueOf(XXX xxx) (主要掌握)
@Test
public void test4(){
int num1 = 10;
// 方式一: 连接运算
String str1 = num1 + "";
// 方式二: 不管是基本数据类型还是包装类都可以调用String的ValueOf(哪一个类型的变量)
float f1 = 12.3f;
String str2 = String.valueOf(f1); // "12.3"
Double d1 = new Double(12.4);
String str3 = String.valueOf(d1);
System.out.println(str2);
System.out.println(str3);
}
/*
* jdk5.0新特性: 自动装箱与自动拆箱
*/
@Test
public void test3(){
/*int num1 = 10;
// 基本数据类型 --> 包装类的对象
method(num1);*/
// 自动装箱: 基本数据类型 --> 包装类的对象 (主要掌握)
int num2 = 10;
Integer i1 = num2; // 自动装箱
boolean b1 = true;
Boolean b2 = b1; // 自动装箱
// 自动拆箱: 包装类的对象 --> 基本数据类型 (主要掌握)
System.out.println(i1.toString());
int num3 = i1;// 自动拆箱
}
public void method(Object obj){
System.out.println(obj);
}
// 包装类 --> 基本数据类型: 调用包装类的xxxValue()方法
// 包装类作为类的对象是不能做加减乘除运算的,但转化为基本数据类型就可以了
@Test
public void test2(){
Integer in1 = new Integer(12);
int i1 = in1.intValue();
System.out.println(i1 + 1); // 13
Float f1 = new Float(12.3);
float f2 = f1.floatValue();
System.out.println(f2 + 1);
}
// 基本数据类型 --> 包装类: 调用包装类的构造器
// 例如:有些方法的形参如果是类类型,就必须把基本数据类型转化为包装类才能放到形参里去用
// 创建一个单元测试
@Test
public void test1(){
int num1 = 10;
Integer in1 = new Integer(num1);
// 把整数10转化为了字符串10
// Integer重写了toString方法,输出的值就是方法里存的实体内容
System.out.println(in1.toString()); // 10
// 还能放String类型的值
Integer in2 = new Integer("123"); // 123
System.out.println(in2.toString());
// 报异常 形参要是纯粹的数
/*Integer in3 = new Integer("123agc");
System.out.println(in3.toString());*/
Float f1 = new Float(12.3f);
Float f2 = new Float("12.3");
System.out.println(f1);
System.out.println(f2);
Boolean b1 = new Boolean(true);
Boolean b2 = new Boolean("false");
// Boolean类里有优化,根据Boolean类源码可知,如果传入的字符串不是正常的true值,则返回false
Boolean b3 = new Boolean("true123");
System.out.println(b3);
Order order = new Order();
System.out.println(order.isMale); // false
System.out.println(order.isFemale);// null
}
}
class Order{
boolean isMale;
Boolean isFemale;
}
- 包装类的练习:
import java.util.Scanner;
import java.util.Vector;
public class ScoreTest {
public static void main(String[] args) {
// 1.实例化Scanner,用于从键盘获取学生成绩
Scanner scan = new Scanner(System.in);
// 2.创建Vector对象: Vector v = new Vector(); 造一个容器,相当于原来的数组
Vector v = new Vector();
int maxScore = 0;
// 3.通过for(;;)或while(true)方式,给Vector中添加数组
for (; ; ) {
System.out.println("输出学生成绩(以负数代表输入结束): ");
int score = scan.nextInt();
// 3.2.当输入是负数时,跳出循环
if (score < 0) {
break;
}
if (score > 100) {
System.out.println("输出非法数据,请重新输入");
continue; // 输入超出范围,结束本次循环,重新开始
}
// 3.1.添加操作: v.addElement(Object obj)
// jdk5.0之前
/*Integer inScore = new Integer(score);
v.addElement(inScore); // 多态形式*/
// jdk5.0之后
v.addElement(score); // 自动装箱 --> 向上转型
// 4.获取学生成绩的最大值: 讲数组的时候,这个操作可以放循环里: 每次从键盘上获取一个学生成绩时候,从现有所谓最高分比较一下
if (maxScore < score) {
maxScore = score;
}
}
// 5.遍历Vector,得到每个学生的成绩,并与最大成绩比较,得到每个学生的等级
char level; // 没赋值不报错,因为在if..else..条件里一定会赋值,他就一定会有这个值
for (int i = 0; i < v.size(); i++) {
Object obj = v.elementAt(i); // 取到每一个元素,因为从v.addElement()往Vector里放的Object类型的对象,所以取的时候他自认为也是个Object类型的
/*Integer inScore = (Integer) obj; // 先从父类Object向下强转型为Integer子类型,
int score = inScore.intValue();*/ // 再将包装类转化为基本数据类型
int score = (int) obj; // 直接从Object类型拆箱成int型
if (maxScore - score <= 10) {
level = 'A';
} else if (maxScore - score <= 20) {
level = 'B';
} else if (maxScore - score <= 30) {
level = 'C';
} else {
level = 'D';
}
System.out.println("student " + i + " score is " + score + " grade is " + level);
}
}
}
6.1.static关键字
- 变量
- 类变量 vs 实例变量内存解析
static关键字的使用
1.static:静态的
2.static可以用来修饰: 属性,方法,代码块,内部类 (变量分成属性和局部变量)
3.使用static修饰属性: 静态变量(或类变量,一定是属性,不归具体对象多有,归类所有)
3.1.属性: 按是否使用static修饰,又分为: 静态属性(静态变量) vs 非静态属性(实例变量,归具体的某一个对象所有的)
实例变量: 创建了多个类的多个对象,每个对象都独立的拥有一套类中的非静态属性.当修改其中一个对象中的
非静态属性时,不会导致其他对象中同样的属性值的修改,可理解为:随着对象的创建而加载的
静态变量: 创建了类的多个对象,多个对象共享同一个静态变量.当通过某一个对象修改静态变量时,会导致其他对象调用此静态变量时,是修改过了的
3.2.static修饰属性的其他说明:
(1)静态变量随着类的加载而加载(加载指把它放到内存中了).可以通过"类.静态变量"的方式进行调用
(2)静态变量的加载要早于对象的创建.(而实例变量是在有了对象以后或在创建对象当中,才帮你把实例变量加载的
(3)由于类只会加载一次,则静态变量在内存中也只会存在一份,存在JVM的方法区的静态域中. 创建一次后就会存在缓存区中,可以加快调用速度
静态域指的是: 存放类当中声明为static的属性
(4)
类变量 | 实例变量 | |
---|---|---|
类 | yes | no |
对象 | yes | yes |
3.3.静态属性举例: System.out; Math.PI;
4.使用static修饰方法: 静态方法
(1)凡是静态的,都是随着类的加载而加载,可以通过"类.静态方法"的方式进行调用
(2)
静态方法 | 非静态方法 | |
---|---|---|
类 | yes | no |
对象 | yes | yes |
(3)静态方法中,只能调用静态的方法或属性,如果要调用非静态的则需要new对象
非静态方法中,既可以调用非静态方法或属性,也可以调用静态的方法或属性
生命周期: 1.类的加载(静态结构加载)放在缓存区 --> 2.对象出生(非静态结构加载) --> 3.对象消亡(非静态结构消失) --> 重复二三的情况 --> 内存中不再使用类:类从内存中消亡(静态结构消亡)
静态结构是完全跟类的生命周期同步的,而非静态结构跟对象的生命周期同步的
晚出生的(非静态结构)可以调用早出生的(静态结构),早出生的不能调用晚出生的,因为可能还没有(例如我可以向父辈借钱,但我不能向我的孩子借钱(还没出生))
5.static注意点:
5.1.在静态的方法内,不能使用this关键字,super关键字,因为static方法是随类加载而加载的,this关键字表示当前对象,static方法调用非静态方法时静态结构还没有产生对象,super则是调当前对象的父类,当前对象还没有就更没父类
5.2.关于静态属性和静态方法的使用,都从生命周期的角度去理解
6.开发中,如何确定一个属性是否要声明为static的?
属性是可以被多个对象所共享的,不会随着对象的不同而不同的
开发中,如何确定一个方法是否要声明为static的
操作静态属性的方法,通常设置为static的
工具类中的方法,习惯上声明为static的,不必造对象,直接用类就可以调.如:Math,Arrays.Collections
static修饰的属性,相较于实例变量,有哪些好处?
随着类的加载而加载;早于对象的创建;只要权限允许,可以通过"对象.static属性"的方式进行调用;存放在方法区中的静态域
public class StaticTest {
public static void main(String[] args) {
Chinese.nation = "中国";
// 每new一个对象,他们的属性都各自有一份,当我们修改其中一个对象的属性时,
// 不会导致另一个对象同样的属性值的修改
Chinese c1 = new Chinese();
c1.name = "姚明";
c1.age = 40;
c1.nation = "CHM";
Chinese c2 = new Chinese();
c2.name = "玛丽";
c2.age = 50;
c2.nation = "CHINA";
System.out.println(c1.nation);
// 编译不通过
//Chinese.name = "张三"; // 类不能调实例变量
c1.eat();
Chinese.show();
// 编译不通过
// Chinese.eat(); // 类不能调用非静态方法
// Chinese.info();
}
}
class Chinese {
String name;
int age;
static String nation;
public void eat() {
System.out.println("中国人吃中餐");
// 调用非静态结构
this.info(); // 谁调info,谁(指哪个对象)就是this
System.out.println("name: " + name);// 包括调非静态的属性
// 调用静态结构
walk();
// 凡是看到静态结构前没有明确声明的,都是省略了类
System.out.println("nation: " + nation); // 实际是 Chinese.nation
}
public static void show() {
System.out.println("我是中国人");
// 不能调用非静态结构
// 声明周期不够,静态方法随类加载而加载,但是非静态的结构还没加载出来,所以不能调用this和super
// eat(); 方法前默认有this关键字,只是省略了 ,编译报错
// name = "tom";
// 可以调用静态结构
System.out.println(nation); // 静态方法可以调用静态属性,属性前默认省略了类,实际是 Chinese.nation
}
public void info() {
System.out.println("name: " + name + ", age: " + age);
}
public static void walk() {
}
}
- static练习: 账户信息
public class AccountTest {
public static void main(String[] args) {
Account acct1 = new Account();
Account acct2 = new Account("qewwf", 2000);
System.out.println(acct1.toString());
System.out.println(acct2.toString());
Account.setInterestRate(0.012);
Account.setMinMoney(100);
System.out.println(acct1.getInterestRate()); // 0.012
System.out.println(acct2.getInterestRate()); // 0.012
System.out.println(acct1.getMinMoney()); // 100.0
System.out.println(acct2.getMinMoney()); // 100.0
}
}
public class Account {
private int id;
private String pwd = "000000";
private double balance;
private static double interestRate; // 利率
private static double minMoney; // 最小存钱数
private static int init = 1001; // 用于自动生成id使用
public Account() {
id = init++;
}
public Account(String pwd, double balance) {
this();
this.pwd = pwd;
this.balance = balance;
}
public int getId() {
return this.id;
}
public void setPwd(String pwd) {
this.pwd = pwd;
}
public String getPwd() {
return this.pwd;
}
public double getBalance() {
return this.balance;
}
public static void setInterestRate(double interestRate) {
Account.interestRate = interestRate;
}
public static double getInterestRate() {
return Account.interestRate;
}
public static void setMinMoney(double minMoney) {
Account.minMoney = minMoney;
}
public static double getMinMoney() {
return Account.minMoney;
}
// 重写Object类的toString方法
@Override
public String toString() {
return "Account{" + "id=" + id + ", pwd='" + pwd + '\'' + ", balance=" + balance + '}';
}
}
- 单例设计模式
单例设计模式:
1.所谓类的单例设计模式,就是采取一定的方法保证在整个的系统中,对某个类只能存在一个对象实例
2.如何实现?
饿汉式 vs 懒汉式
3.区别饿汉式和懒汉式 (面试优先写饿汉式)
饿汉式: 好处: 饿汉式是线程安全的,使用效率高,坏处: 在一开始就把对象造好了,对象加载时间过长
懒汉式: 好处: 延迟对象的创建; 目前写法的坏处: 线程不安全. --> 到多线程内容时,再修改
- 饿汉式
public class SingletonTest {
public static void main(String[] args) {
// 虚拟机只加载一次class文件,而静态的属性或方法也只加载一次,所以只会有唯一一个new的对象
Bank bank1 = Bank.getInstance();
Bank bank2 = Bank.getInstance();
System.out.println(bank1 == bank2); // true
}
}
// 饿汉式
class Bank {
// 单例要确保获取到的对象具有唯一性,声明为static,类加载到内存时,就会自动加载
// 1.私有化构造器
private Bank(){
}
// 2.内部创建类的对象 封装性
// 该类对象可理解为类的属性,这属性恰好就是该类对象
// 4.要求此属性也必须声明为静态的
private static Bank instance = new Bank();
// 3.提供公共方法,返回类的对象(实例)
// 当把方法静态化,返回对象报错,因为静态方法只能调静态的结构,所以创建的对象也只能是静态的
public static Bank getInstance() {
return instance;
}
}
- 懒汉式
public class SingleTest2 {
public static void main(String[] args) {
Order order1 = Order.getInstance();
Order order2 = Order.getInstance();
System.out.println(order1 == order2);
}
}
// 懒汉式
class Order{
// 1.私有化类的构造器
private Order() {
}
// 2.声明当前类的对象,但没有初始化
// 4.此对象必须声明为static
private static Order instance = null;
// 3.声明public,static的返回当前类对象的方法
// 因为外部类不能通过Order类造对象来调用Order类的方法,而只能通过类去调,所以要方法加上static
public static Order getInstance() {
// instance = new Order(); // 写法错误,如果Order类每调一次该方法就会创建一次类的对象,这样就不是唯一一个类对象了
// 用判断条件来造对象
if (instance == null) {
instance = new Order();
}
return instance;
}
}
6.2.理解main方法的语法
- main()方法的使用说明:
- main()方法作为程序的入口
- main()方法也是一个普通的静态方法
- main()方法可以作为与控制台交互的方式,也可以运行时候传入数据.(之前,使用Scanner从控制台上获取相应类型的变量)
// 每个源文件中只能声明一个public类
public class MainTest {
// 静态方法只能调静态结构,造了对象后才能调非静态结构
// 入口方法不需要返回值
// 形参是String类型的数组,也可以作为跟控制台交互的一种方式
public static void main(String[] args) { // 入口,要想他作为入口权限就要大
// 调哪个类的方法,就造那个类的对象
Main test = new Main();
Main.main(new String[100]);
}
public void show() {
// 方法里面不能再写方法
/*public stvoid eat(){
}*/
}
}
// 每个类里面可以写各自的main方法
class Main {
// 作为普通的静态方法出现
public static void main(String[] args) {
for (int i = 0; i < args.length; i++){
args[i] = "args_" + i;
System.out.println(args[i]);
}
}
}
6.3.类的成员之四: 代码块
- 类的成员之四: 代码块(或初始化块)
1.代码块的作用,用来初始化类,对象
2.代码块如果有修饰的话,只能用static修饰.
3.分类: 静态代码块 vs 非静态代码块
4.静态代码块
内部可以有输出语句
随着类的加载而执行,而且只执行一次;只要当前的类没有重新加载,代码块就不会重新执行
作用: 初始化类的信息
如果一个类中定义了多个静态代码块,则按照声明的先后顺序(从上到下)执行
静态代码块的执行要优先于非静态代码块的执行
静态代码块内只能调用静态的属性,静态的方法,不能调用非静态的结构
5.非静态代码块
内部可以有输出语句
随着对象的创建而执行,更严谨来说,随着构造器的this或super的调用后执行,且优先于构造器内部语句的执行而执行,因为如果有继承父类的情况下,当先执行到父类的时候,父类可能并没有创建对象
每创建一个对象,就执行一次非静态代码块,且优先于构造器的执行,实际上是先进入构造器,且先执行完this或super方法,但并不执行里面的语句,再是执行非静态代玛块,执行完之后才是构造函数语句,所以看起来是先非静态代玛块后构造函数
作用: 可以在创建对象时,对对象的属性等进行初始化
如果一个类中定义了多个非静态代码块,则按照声明的先后顺序(从上到下)执行
非静态代码块内可以调用静态也可以调用非静态
public class BlockTest {
public static void main(String[] args) {
String desc = Person.desc;
System.out.println(desc);
Person p1 = new Person();
Person p2 = new Person();
System.out.println(p1.age);
}
}
class Person {
String name;
int age;
static String desc = "我是一个人";
public Person() {
super();
System.out.println("我是构造器");
}
public Person(String name, int age) {
this();
this.name = name;
this.age = age;
}
// 不能够主动调代码块,通常都是自动执行
// 非static代码块: 随着对象的调用而执行
{
System.out.println("hello,block2");
}
{
System.out.println("hello,block1");
// 可以调用非静态结构
age = 1;
eat();
// 非静态代码块可以调用静态结构
desc = "我是一个爱学习的人1";
info();
}
// static代码块,随着类的加载而执行
// 类只加载一次,所以不管类调用多少次,静态代码块也只执行一次
static{
System.out.println("hello,static block2");
}
static {
// 可想象成方法体
System.out.println("hello,static block1");
// 静态代码块可以调用静态结构
desc = "我是个爱学习的人";
info();
// 静态代码块不能调非静态结构
// eat();
// name = "tom";
}
public void eat() {
System.out.println("吃饭");
}
public static void info() {
System.out.println("我是个快乐的人");
}
@Override
public String toString() {
return "Person:{name=" + name + ", age=" + age + "]";
}
}
- 代码块练习:
总结:由父及子,静态先行
执行顺序: 父级静态代码块 --> 子类静态代码块 --> 父类非静态代码块 --> 父类构造器 --> 子类非静态代码块 --> 子类构造器
只要类先加载,就一定先执行静态代码块
子类加载前要加载父类的所有信息,所以首先先执行父类的静态代码块和子类的静态代码块
class Root {
static {
System.out.println("Root的静态初始化块");
}
{
System.out.println("Root的普通初始化块");
}
public Root() {
super();
System.out.println("Root的无参数的构造器");
}
}
class Mid extends Root {
static {
System.out.println("Mid的静态初始化块");
}
{
System.out.println("Mid的普通初始化块");
}
public Mid() {
super();
System.out.println("Mid的无参数的构造器");
}
public Mid(String msg) {
//通过this调用同一类中重载的构造器
this();
System.out.println("Mid的带参数构造器,其参数值:"
+ msg);
}
}
class Leaf extends Mid {
static {
System.out.println("Leaf的静态初始化块");
}
{
System.out.println("Leaf的普通初始化块");
}
public Leaf() {
//通过super调用父类中有一个字符串参数的构造器
super("尚硅谷");
System.out.println("Leaf的构造器");
}
}
public class LeafTest {
public static void main(String[] args) {
new Leaf();
System.out.println();
new Leaf();
}
}
- 代码块练习二:
虽然main方法是作为入口但也是Son类里的普通静态方法,静态方法也得是通过Son类调的,类调之前类就得先加载
所以在执行main方法前就已经先加载了Son类的信息,又因为Son类继承了Father类,
所以子类加载前要加载父类的所有信息,所以首先先输出父类的静态代码块和子类的静态代码块
lic static void main(String[] args) { // 由父及子 静态先行
class Father {
static {
System.out.println("11111111111");
}
{
System.out.println("22222222222");
}
public Father() {
System.out.println("33333333333");
}
}
public class Son extends Father {
static {
System.out.println("44444444444");
}
{
System.out.println("55555555555");
}
public Son() {
System.out.println("66666666666");
}
// 虽然main方法是作为入口但也是Son类里的普通静态方法,静态方法也得是通过Son类调的,类调之前类就得先加载
// 所以在执行main方法前就已经先加载了Son类的信息,又因为Son类继承了Father类,
// 所以子类加载前要加载父类的所有信息,所以首先先输出父类的静态代码块和子类的静态代码块
public static void main(String[] args) { // 由父及子 静态先行
System.out.println("77777777777");
System.out.println("************************");
new Son();
System.out.println("************************");
new Son();
System.out.println("************************");
new Father();
}
}
- 对属性赋值的顺序:
对属性可以赋值的位置:
1.默认初始化
2.显示初始化/5.在代码块中赋值(并列,不分先后)
3.构造器中初始化
4.有了对象后,通过"对象.属性"或"对象.方法"的方式,进行赋值
执行先后顺序: 1 - 2 / 5 - 3 - 4 ,从后往前覆盖
6.4.final关键字的使用
final只能对属性赋值一次,不能变化了
final:最终的
1.final可以用来修饰的结构: 类,方法,变量
2.final用来修饰一个类: 此类不能被其他类所继承.比如: String类,System类,StringBuffer类
3.final用来修饰方法: 表明此方法不能被重写.比如:Object类中getClass();
4.final用来修饰变量: 此时的"变量"就称为是一个常量
4.1.final修饰属性:可以考虑赋值的位置有: 显式初始化,代码块中初始化,构造器中初始化
如果每个对象的属性值不一样,就在构造器中赋值
如果大家的值都一样,就可以显示显式初始化
如果不是简单的值,而是调了方法,方法可能抛异常,还得去处理,就放到代码块中赋值
4.2.final修饰局部变量: 尤其是用final修饰形参时,表明此形参时一个常量,这时候的常量不是说马上要给他赋值,
还是遵照规则,当调用此方法时,给常量形参赋一个实参,一旦赋值以后,就只能在方法体内用此形参,但不能进行重新赋值
static final用来修饰属性,明确表示属性是不能变的通常一定加final: 全局变量,全局体现在static,随着类的加载而加载,只有一份,通过类来调,final体现在常量的意思
public class FinalTest {
// final显式初始化
final int WIDTH= 10;
final int RIGHT;
// final int DOWN; // 可能创建多个对象,然后属性可能多次赋值,也有可能不调方法,也就不会被赋值
// final代码块中初始化
final int LEFT;
{
LEFT = 1;
}
// 构造器是独立的
public FinalTest() {
RIGHT = 2;
}
// 防止调用第二个构造器的时候没有给RIGHT赋值
public FinalTest(int n) {
RIGHT = n;
}
// 被final修饰的变量必须要有初始值,如果才在方法里赋值就太晚了
// 所以这种操作不靠谱
/*public void setDOWN() {
}*/
public void doWidth() {
// width = 20;
}
public void show() {
// final修饰方法体里普通的局部变量
final int NUM = 10; // 常量
// num += 20; // 修饰以后不能修改
}
// final修饰形参,在调此方法的时候才给方法的形参赋值实参
// 一旦赋值以后,在方法内只能对变量进行调用,不能再修改
public void show(final int num) {
// num = 20; // 编译不通过,修饰后不能再操作
System.out.println(num);
}
public static void main(String[] args) {
FinalTest test = new FinalTest();
test.show(10);
}
}
final class FinalA {
}
/*class B extends FinalA{
}*/
class AA {
public final void show() {
}
}
// final修饰符的方法不能被重写
/*class BB extends AA {
public void show() {
}
}*/
6.5.抽象类与抽象方法
abstract关键字的使用
1.abstract: 抽象的,可理解为不确定的
2.abstract可以用来修饰的结构: 类,方法 (不确定的)
3.abstract修饰类: 抽象类
此类不能实例化
抽象类中一定提供有构造器,只要是类就一定有构造器,便于子类实例化时使用(设计: 子类对象实例化的全过程)
开发中,都会提供抽象类的子类,让子类对象实例化,完成相关操作
4.abstract修饰方法: 抽象方法
抽象方法只有方法的声明,没有方法体
包含抽象方法的类,一定是一个抽象类.反之,抽象类中可以没有抽象方法的
若子类重写了所有父类中的所有的抽象方法,此子类放可实例化
若子类没有重写父类中的所有的抽象方法,则此子类也是一个抽象类,需要使用abstract修饰
abstract使用上的注意点:
1.abstract不能用来修饰: 属性,构造器(构造器只能重载)等结构
2.abstract不能修饰私有方法,因为abstract修饰过的方法,总会有子类需要重写,否则继承abstract方法类的只能是抽象类,
而private修饰的方法可以被子类继承,但不能被子类直接调用和不能被子类重写,所以abstract和private冲突
3.abstract不能修饰静态方法,因为静态方法不能被子类覆盖,也不能被重写
4.abstract不能修饰final的方法,final方法不能被重写
5.abstract不能修饰final的类,final类不能被继承
public class AbstractTest {
public static void main(String[] args) {
// 一旦Person类抽象了,就不可实例化
/*Person p1 = new Person();
p1.eat();*/
}
}
abstract class Ctreature {
public abstract void breath();
}
// Person类有两个抽象方法,一个是自己的,一个是父类的
abstract class Person extends Ctreature{
String name;
int age;
public Person() {
}
public Person(String name, int age) {
this.name = name;
this.age = age;
}
/*public void eat() {
System.out.println("人吃饭");
}*/
// 抽象方法
public abstract void eat();
public void walk() {
System.out.println("人走路");
}
}
// 因为调了父类构造器了,所以就加载了父类,也就可以用父类的属性和方法了
class Student extends Person {
public Student(String name, int age) {
super(name, age);
}
// 子类要重写所有父类的所有抽象方法
//子类要重写父类的抽象方法
public void eat() {
System.out.println("学生多吃有营养食物");
}
// 间接父类抽象方法的重写
@Override
public void breath() {
System.out.println("学生呼吸新鲜空气");
}
}
- 抽象类的练习: 基本操作
// 用继承思想,要求类中提供必要的方法进行属性访问
// 在原有继承基础之上,在需要在父类做修改,涉及到抽象类和抽象方法,子类必要的时候重写父类的方法,没重写就也是抽象类
public class EmployeeTest {
public static void main(String[] args) {
// 多态和抽象联系起来用,如果多态里有通用型方法,且形参是父类(抽象类)的类型,抽象类本身不能造对象,还不让用多态,抽象就没有意义了
// 多态性的意义还可以体现抽象类要用
// 除了Manager还能用Employee声明,Employee类表现为抽象,new的Manager类表现为多态
Employee manager = new Manager("库克", 1001, 5000, 50000);
manager.work();
CommonEmployee commonEmployee = new CommonEmployee();
commonEmployee.work();
}
}
// 员工数量太多,从此只造子类的对象
public abstract class Employee {
private String name;
private int id;
private double salary;
public Employee() {
super();
}
public Employee(String name, int id, double salary) {
super();
this.name = name;
this.id = id;
this.salary = salary;
}
// 每个员工工作方式不同,将方法抽象化
public abstract void work();
}
// 对于Manager类,他既是员工,还具有奖金(bonus)属性
public class Manager extends Employee{
private double bonus; // 奖金
public Manager(double bonus) {
super();
this.bonus = bonus;
}
public Manager(String name, int id, double salary, double bonus) {
super(name, id, salary);
this.bonus = bonus;
}
@Override
public void work() {
System.out.println("管理员工,提高公司运行效率");
}
}
public class CommonEmployee extends Employee{
@Override
public void work() {
System.out.println("员工在一线车间生产");
}
}
- 创建抽象类的匿名子类的对象
/*
* 抽象类的匿名子类
*/
public class PersonTest {
public static void main(String[] args) {
PersonTest.method(new Student()); // 匿名对象
Worker worker = new Worker();
method1(worker); // 非匿名类的非匿名对象
method1(new Worker()); // 非匿名类的匿名对象
// 创建了一个匿名子类的有名对象(对象是有个名的): p;相当于用多态的方式赋给了父类的引用
// 相当于需要用抽象类的一个对象,但又不想创建新的类,因此重写抽象方法就行了
Person p = new Person() { // new Person() 实际上是父类的子类,只是借父类的名字充当了一下
// 只能是子类才能重写方法
@Override
public void eat() {
System.out.println("吃东西");
}
@Override
public void breath() {
System.out.println("好好呼吸");
}
};
method1(p);
// 创建匿名子类的匿名对象
method1(new Person() {
@Override
public void eat() {
System.out.println("吃好吃的");
}
@Override
public void breath() {
System.out.println("好好呼吸新鲜空气");
}
});
}
public static void method1(Person p) { // 形参是(父类)抽象类的变量,多态的使用
// 编译调的是父类的方法,实际运行时子类重写的方法
p.eat();
p.breath();
}
public static void method(Student s) { // 形参是抽象类
}
}
class Worker extends Person {
@Override
public void breath() {
}
@Override
public void eat() {
}
}