持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第21天,点击查看活动详情
this的使用
👇举例
public class PersonTest {
public static void main(String[] args) {
Person p1 = new Person();
p1.setAge(1);
System.out.println(p1.getAge());
}
}
class Person{
private String name;
private int age;
public void setName(String n) {
name = n;
}
public String getName() {
return name;
}
public void setAge(int a) {
age = a;
}
public int getAge() {
return age;
}
}
我现在写了一个Person类,里面有属性和方法,在setName和setAge这两个方法中,我的形参列表里写的是变量n和a。
但是我现在想做到见名知义,我想把形参列表改成String name和int age。让别人知道我调用这个方法传的值就是用来给属性name和age赋值的.
那么就要用到this关键字,修改后如下
public class PersonTest {
public static void main(String[] args) {
Person p1 = new Person();
p1.setAge(1);
System.out.println(p1.getAge());
}
}
class Person{
private String name;
private int age;
public void setName(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setAge(int age) {
this.age = age;
}
public int getAge() {
return age;
}
}
这样this.name编译器就会知道是当前Person类中的name属性,等号右边的name就会认为是形参。age同理。
this的修饰范围
this可以用来修饰、调用:属性、方法、构造器
✨this理解为当前对象或当前正在创建的对象
this修饰属性和方法:
在类的方法中,我们可以使用“this.属性”或“this.方法”的方式,调用当前对象属性或方法。
但是,通常情况下,我们都选择省略“this”。
特殊情况下,如果方法的形参和类的属性同名时,我们必须显示地使用“this.变量”的方式,表明此变量是属性,而非形参。
在类的构造器中,我们可以使用“this.属性”或“this.方法”的方式,调用当前正在创建的对象属性或方法。
但是,通常情况下,我们都选择省略“this”。
特殊情况下,如果构造器的形参和类的属性同名时,我们必须显示地使用“this.变量”的方式,表明此变量是属性,而非形参。
✨this修饰、调用构造器
- 我们在类的构造器中,可以显式地使用“this(形参列表)”的方式,调用本类的其他构造器
- 构造器不同通过“this(形参列表)”的方式调用自己
- 如果一个类中有n个构造器,则最多有n - 1构造器使用了“this(形参列表)”
- 规定:“this(形参列表)”必须声明在当前构造器的首行
- 构造器内部,最多只能声明一个“this(形参列表)”,用来调用其他的构造器
this练习
添加必要的构造器,综合应用构造器的重载,this关键字。
👇Boy
public class Boy {
private String name;
private int age;
//构造器
public Boy() {
}
public Boy(String name, int age) {
this.name = name;
this.age = age;
}
//get和set方法
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 marry(Girl girl) {
System.out.println("和" + girl.getName() + "结婚");
}
public void shout() {
if(this.age >= 22) {
System.out.println("可以合法登记结婚");
} else {
System.out.println("不满足法定结婚年龄");
}
}
}
👇Girl
public class Girl {
private String name;
private int age;
public Girl() {
}
public Girl(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public void marry(Boy boy) {
System.out.println("嫁给" + boy.getName());
boy.marry(this);
}
/**
* 比较两个对象的大小
* @param girl
* @return 正数:当前对象大; 负数:当前对象小 ; 0:当前对象和形参对象相等
*/
public int compare(Girl girl) {
if(this.age > girl.age) {
return 1;
} else if(this.age < girl.age) {
return -1;
} else {
return 0;
}
}
}
👇Test
public class BoyGirlTest {
public static void main(String[] args) {
Boy boy = new Boy("罗密欧",21);
boy.shout();
Girl girl = new Girl("朱丽叶",18);
girl.marry(boy);
Girl girl1 = new Girl("祝英台",19);
int compare = girl.compare(girl1);
if(compare > 0) {
System.out.println(girl.getName() + "大");
} else if(compare < 0) {
System.out.println(girl1.getName() + "大");
} else {
System.out.println("一样大");
}
}
}
package、import的使用
✨package的使用
- 为了更好地实现项目中类地管理,提供包(package)的概念
- 使用package声明类或接口所属的包,声明在原文件的首行
- 包,属于标识符,遵循标识符的命名规则、规范、做到“见名知意”
- 每"."一次,就代表一层文件目录 补充:同一个包下,不能命名同名的接口、类。
不同的包下,可以命名同名的接口、类。
✨import的使用
import:导入
- 在源文件中显式地使用import结构导入指定包下的类、接口
- 声明在包(package)的声明和类的声明之间
- 如果需要导入多个结构,则并列写出即可
- 可以使用“xxx.*”的方式,表示可以导入xxx包下的所有结构。但如果使用的是xxx子包下的结构,则仍需显示导入。
- 使用频繁的类或接口一般定义在lang包下(比如System、String),定义在java.lang包下的类或接口可以省略import结构
- 如果使用的类或接口是本包下定义的,则可以省略import结构
- 如果在源文件中,使用了不同包下的同名的类,则必须至少有1个类需要以全类名(包.类)的方式显示。
- import static:导入指定类或接口中的静态结构:属性或方法。
super
在子类中重写了父类的方法,这样我们创建子类对象的时候默认会调用重写后的方法。为了让重写的方法和被重写的方法区分开,我们就需要用到super关键字。
super关键字的使用
super理解为:父类的。可以用来调用:属性、方法、构造器。
- 我们可以在子类的方法或构造器中。通过使用“super.属性”或“super.方法”,显式的调用父类中声明的属性或方法。但是,通常情况下,我们习惯省略“super.”。
- 属性特殊情况:当子类和父类中定义了同名的属性时,我们要想在子类中调用父类中声明的属性时,则必须显式的使用“super.属性”的方式,表明调用的是父类中声明的属性。
- 方法特殊情况:当子类重写了父类中的方法以后,我们想在子类的方法中调用父类中被重写的方法时,则必须显式的使用“super.方法”的方式,表明调用的是父类中声明的方法。 👇下面是super的举例,以Person为父类,Student为子类
Person类
public class Person {
String name;
int age;
int id = 101;
public Person() {
}
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public Person(String name) {
this.name = name;
}
public void eat() {
System.out.println("吃饭");
}
public void walk() {
System.out.println("走路");
}
}
Student类
public class Student extends Person{
String major;
int id = 100;
public Student() {
}
public Student(String major) {
this.major = major;
}
@Override
public void eat() {
System.out.println("学生多吃有营养的食物");
super.eat();
}
public void study() {
System.out.println("学习专业知识");
}
//调用父类的属性
public void show() {
System.out.println("name = " + name + ", age = " + age);
System.out.println("id = " + this.id); //调用当前Student的id
System.out.println("id = " + super.id); //父类Person中的id
}
}
在Student类中,eat方法和show方法中就使用了super关键字,这样我们在调用子类这两个方法时,就会调用这两个方法体中父类的方法(eat)和属性(id)。
- super调用构造器
- 我们可以在子类的构造器中显式的使用“super(形参列表)”的方式,调用父类中声明的指定的构造器。
- “super(形参列表)”的使用,必须声明在子类构造器的首行。
- 在类的构造器中,针对于“this(形参列表)”或“super(形参列表)”只能二选一,不能同时出现。
- 当我们在构造器的首行,没有显式地声明“this(形参列表)”或“super(形参列表)”,则默认调用的是父类中空参的构造器:super()。
- 在类的多个构造器中,至少有一个类的构造器中使用了“super(形参列表)”,调用父类中的构造器。
子类对象实例化的全过程
- 从结果来看:(继承性)
- 子类继承父类以后,就获取了父类中声明的属性或方法。
- 创建子类的对象,在堆空间中,就会加载所有父类中声明的属性。
- 从过程看:
- 当我们通过子类的构造器创建子类对象时,我们一定会直接或间接的调用其父类的构造器,进而调用父类的父类的构造器,直到调用了java.lang.Object类中空参的构造器为止。正因为加载过所有的父类的结构,所以才可以看到内存中有父类中的结构,子类对象才可以考虑进行调用。 虽然创建子类对象时,调用了父类的构造器,但是自始至终就创建过一个对象,即为new的子类对象。
instanceof
有了对象的多态性以后,内存中实际上是加载了子类特有的属性和方法的,但是由于变量声明为父类类型,导致编译时,只能调用父类中声明的属性和方法。子类特有的属性和方法不能调用。 那么如何调用子类特有的方法和属性呢?
- 向下转型:使用强制类型转换符 比如Peron为父类,Man为子类,当使用多态性创建父类类型的子类对象后,要想再用子类特有的属性和方法,可以下面这么写:
Person p = new Man();
Man m = (Man)p;
但如果你这么写:比如Woman也是Person子类,用上面造过的对象p
Woman w = (Woman)p;
就会出现ClassCastException的异常。因为p其实是Man的对象,不能转成Woman类型。
为了避免出现这个异常,我们就引入instanceof关键字。
- instanceof的使用: 格式:a insranceof A:判断对象a是否为类A的实例。如果是:返回true;如果不是,返回false。 把之前的Woman w = (Woman)p;这么写就不会出现异常
if(p instanceof Woman){
Woman w = (Woman)p;
}
因为p是我们之前就创建过的一个对象,先判p是否为Woman的实例,再进行向下转型。
如果 a insranceof A返回true,且a insranceof B也返回true。其中,类B是类A的父类。
static
有时候我们希望有些属性不归具体的对象所有,就是没有必要给每个对象去分配一份。所以我们引入static关键字,它所描述的属性或方法就是大家共享的。
static的使用
- 定义:静态的
- 可以修饰:属性、方法、代码块、内部类。不可以修饰构造器
- 修饰属性:静态变量(或类变量) 属性,按是否有static修饰,又分为:静态属性和非静态属性(实例变量)。
非静态属性,就比如我们创建了类的多个对象,当修改其中一个对象的非静态属性时,不会导致其它对象的中的同样的属性值也修改。
静态属性:比如我们创建一个类的多个对象,多个对象就共享同一个静态变量。当通过某一个对象修改静态变量时,会导致其他对象调用此静态变量时,是修改后的值。看下面代码
public static void main(String[] args) {
Chinese c1 = new Chinese();
Chinese c2 = new Chinese();
c1.nation = "CHN";
System.out.println(c2.nation);
}
class Chinese{
static String nation;
}
结果:输出CHN; 我没有用c2调用静态属性nation,但还是可以打印出来c1给这个变量赋初值。所以说明c1和c2共享这一个静态变量。
public static void main(String[] args) {
Chinese c1 = new Chinese();
Chinese c2 = new Chinese();
c1.nation = "CHN";
c2.nation = "China";
System.out.println(c1.nation);
}
class Chinese{
static String nation;
}
结果:输出China
其他说明:①静态变量随着类的加载而加载。可以通过“类.静态变量”的方式进行调用。②静态变量的加载要早于对象的创建。③由于类只会加载一次,所以静态变量在内存中也只会存在一份:存在方法区的静态域中。④不能通过“类.实例变量”的方式调用没有用static修饰的变量。
- 修饰方法:静态方法 ①静态方法随着类的加载而加载。可以通过“类.静态方法”的方式进行调用。
②不能使用类去调用非静态方法
③在静态方法中只能调用静态方法或属性,对于非静态方法中,既可以调用非静态方法或属性,也可以调用静态方法或属性。
- static注意点: 在静态方法内,不能使用this、super关键字
判断是否该用static
如何确定属性是否要声明为static?
属性是可以被多个对象所共享的,不会随着对象的不同而不同的。
类中的常量也常声明为static
如何确定方法是否要声明为static?
操作静态属性的方法,通常设置成static的
工具类中的方法,习惯上声明为static。比如:Math,Arrays,Collections
static练习
- 创建圆的类,每创建一个圆的对象就得到一个新的id,并可以记录创建过多少圆。
public static void main(String[] args) {
Circle c1 = new Circle();
Circle c2 = new Circle();
Circle c3 = new Circle(3.1);
System.out.println("c1的id:" + c1.getId());
System.out.println("c2的id:" + c2.getId());
System.out.println("c3的id:" + c3.getId());
System.out.println("创建的圆的个数为:" + Circle.getTotal());
}
class Circle{
private double radius;//半径
private int id;//记录圆的编号
public Circle() {
id = init++;
total++;
}
public Circle(double radius) {
this();
this.radius = radius;
// id = init++;
// total++;
}
private static int total;//记录new了多少个圆的对象
private static int init = 1001;
public double findArea() {
return 3.14 * radius * radius;
}
public int getId() {
return id;
}
public static int getTotal() {
return total;
}
}
- 编写一个类实现银行账户的概念,包含的属性有“账号”、“密码”、“存款余额”、“利率”、“最小余额”,定义封装这些属性的额方法。账号要自动生成。 编写主类,使用银行账户类,输入、输出3个储户的上述信息。考虑哪些属性可以设计成static属性。
public class Account {
private int id;//账号
private String pwd = "000000";//密码
private double balance;//存款余额
private static double interstRate;//利率
private static double minMoney = 1.0; //最小余额
private static int init = 1001;//用于自动生成id
public Account() {
id = init++;
}
public Account(String pwd, double balance) {
id = init++;
this.pwd = pwd;
this.balance = balance;
}
public String getPwd() {
return pwd;
}
public void setPwd(String pwd) {
this.pwd = pwd;
}
public static double getInterstRate() {
return interstRate;
}
public static void setInterstRate(double interstRate) {
Account.interstRate = interstRate;
}
public static double getMinMoney() {
return minMoney;
}
public static void setMinMoney(double minMoney) {
Account.minMoney = minMoney;
}
public int getId() {
return id;
}
public double getBalance() {
return balance;
}
@Override
public String toString() {
return "Account [id=" + id + ", pwd=" + pwd + ", balance=" + balance + "]";
}
}
final
翻译为:最终的
可以修饰的结构:类、方法、变量
final修饰类
此类不能被其他类所继承。
比如:String,System类,StringBuffer类
比如我写一个类叫FinalA,在class前加final就表示不能被继承
final class FinalA{
}
final修饰方法
表明此方法不可被重写,在方法中,final的位置加在返回值类型前,权限修饰符后。
比如:Object类中getClass();获取当前对象所属的类。
举例:
class A{
public final void show() {
}
}
class B extends A{
public void show() {
}
}
B类继承A类并重写了show方法,但是A类中的show方法并声明为final,B重写这个方法就会报错。
final修饰变量
注意是变量而不是属性!变量包含属性和局部变量。
此时的“变量”就称为是一个常量。
- final修饰属性:可以考虑的赋值位置有:
- 显式初始化
final int WIDTH = 10;
- 代码块中赋值
final int LEFT;
{
LEFT = 1;
}
- 构造器中初始化
final int RIGHT;
public FinalTest() {
RIGHT = 2;
}
public FinalTest(int n) {
RIGHT = n;
}
- final修饰局部变量:
- 可以在方法内定义并赋值,这样就为常量
public void show() {
final int NUM = 10;
}
- 形参使用final,表明此形参是一个常量。在main方法内传递实参给形参,这样在方法内只能调用不能修改(可以进行运算,但不能改变它本身的数值)。 比如下面的例子,show方法我写在FinalTest方法内。
public void show(final int num) {
System.out.println(num);
}
main方法可以这么写
public static void main(String[] args) {
FinalTest test = new FinalTest();
test.show(10);
}
static final
可以用来修饰方法和属性
- 修饰属性:表示全局常量
- 修饰方法,表示只能通过类来调用此方法,且不能被继承和重写。
抽象类和抽象方法
举个例子:定义一个类叫“人”,我们在“人”这个类中定义了属性和方法,并把它作为父类,但随着我们子类定义地越来越多,它们在继承父类地同时还提供了方法的重写,和自己特有的属性和方法。之前我们提到过子类的功能通常比父类更强大一些。最起码要和父类一样,那在main方法内去new对象我们就可以直接new子类的对象。并告诉别人我以后不再new,“人”这个类的对象了。
这时我们就会用到abstract关键字
abstract的使用
abstract的意思:抽象的
abstract可以修饰的结构:类,方法
- 修饰类:抽象类
- 此类不能实例化(即不能造对象)
- 抽象类中依然有构造器,便于子类实例化时调用(设计:子类对象实例化全过程)
- 开发中:都会提供抽象类的子类,让子类对象实例化,完成相关操作。
比如下面代码中,Person类就不能在main方法中造对象
abstract class Person{
}
- 修饰方法:抽象方法
- 格式:没有方法体,并再返回值类型前加上abstract 比如我取一个方法名叫eat,并把它定义成抽象方法。
public abstract void eat();
- 抽象方法所在类必须也是抽象类。反之:抽象类中可以没有抽象方法。
- 若子类重写了父类中所有的抽象方法,此子类才可以实例化(造对象),若子类没有重写所有的抽象方法,则此子类也是个抽象类(人为去加,不然报错)。
abstract注意点
- 不能用来修饰:属性,构造器等结构。
- 不能用来修饰私有(private)方法,静态(static)方法,final的方法,final的类。
练习
编写一个Employee类,声明为抽象类,包含如下三个属性:name,id,salary。 提供必要的构造器和抽象方法:work()。
对于Manager类来说,他既是员工,还具有奖金(bonus)的属性。
使用继承的思想,设计CommonEmployee类和Manager类,要求类中提供必要的方法进行属性访问。
Employee类
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();
}
CommonEmployee类
public class CommonEmployee extends Employee {
@Override
public void work() {
System.out.println("员工在生产产品");
}
}
Manager类
public class Manager extends Employee{
private double 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("管理员工");
}
}
创建抽象类的匿名子类对象
比如我定义了一个抽象类叫Person,然后我给它里面定义了一个抽象方法叫eat()。
那么我在main里就可以这么写
//创建了匿名子类的对象:p
Person p = new Person() {
@Override
public void eat() {
// TODO Auto-generated method stub
}
};
模板方法设计模式
抽象类可以看作多个子类的一个通用模板,子类在抽象类的基础上进行扩展、改造。但子类总体上会保留抽象类的行为方式。
可以解决的问题:
- 当功能内部一部分实现是确定的,一部分实现是不确定的,这时可以把不确定的部分暴露出去,让子类去实现。
接口interface
之前我们讲过,java不支持多重继承(即从几个类中派生出一个子类),有了接口,就可以得到多重继承的效果。
举例:把“飞”这个功能定义为接口,可以让多个子类去实现这个接口,代表具有这个功能,同样的,也可以让这些子类去实现另外一个接口,表明两个功能都有。
接口的使用
- 使用interface来定义
- java中,接口和类是并列的结构,要么写类class,要么写接口interface
- 如何定义:定义接口的成员(和类相似) JDK7及之前:只能定义全局常量(public static final)和抽象方法(public abstract)。但书写时可以不写。
interface Flyable{
//全局常量
public static final int MAX_SPEED = 7900;
int MIN_SPEED = 1; //可以省略,因为接口中只要是变量都只能是全局常量
//抽象方法
public abstract void fly();
void stop(); //也可以省略public abstract
}
- 接口中不能定义构造器!意味着接口不能实例化。
- Java开发中,接口通过让类去实现(implements)的方式来使用;如果实现类覆盖了接口中的所有抽象方法,则此实现类就可以实例化。如果没有覆盖完,则此实现类仍为抽象类。 例如我造一个Plane类去实现上面第三点写的Flyable接口。
class Plane implements Flyable{
@Override
public void fly() {
// TODO Auto-generated method stub
}
@Override
public void stop() {
// TODO Auto-generated method stub
}
}
都重写好之后就可以在main方法中造Plane的对象调用方法。也可以通过这个对象调用接口Flyable中的全局常量。
- Java类可以实现多个接口 ---> 弥补了Java单继承性的局限性。 格式:calss A extends B implements C,D,E
比如:上面我定义过一个接口叫Flyable,现在再定义一个接口Attackable,里面加一个attack()的抽象方法,并创建一个类Bullet去实现这两个接口。
class Bullet implements Flyable,Attackable{
@Override
public void attack() {
// TODO Auto-generated method stub
}
@Override
public void fly() {
// TODO Auto-generated method stub
}
@Override
public void stop() {
// TODO Auto-generated method stub
}
}
- 接口和接口之间也叫继承,还可以多重继承
interface AA{
void method1();
}
interface BB{
void method2();
}
interface CC extends AA,BB{
}
- 接口的具体使用体现多态性
- 接口,实际上可以看成一种规范。
创建接口特殊对象
基于以下代码创建:
class Computer{
public void transferData(USB usb) {
usb.start();
System.out.println("具体传输数据的细节");
usb.stop();
}
}
interface USB{
//常量:定义长宽和最小最大的传输速度
void start();
void stop();
}
class Flash implements USB{
@Override
public void start() {
System.out.println("U盘开启工作");
}
@Override
public void stop() {
System.out.println("U盘结束工作");
}
}
class Printer implements USB{
@Override
public void start() {
System.out.println("打印机开启工作");
}
@Override
public void stop() {
System.out.println("打印机结束工作");
}
}
- 创建了接口的非匿名实现类的非匿名对象
Computer com = new Computer();
Flash flash = new Flash();
com.transferData(flash);
- 创建了接口的非匿名实现类的匿名对象
com.transferData(new Printer());
- 创建了接口的匿名实现类的非匿名对象
USB phone = new USB() {
@Override
public void stop() {
// TODO Auto-generated method stub
}
@Override
public void start() {
// TODO Auto-generated method stub
}
};
- 创建了接口的匿名实现类的匿名对象
com.transferData(new USB() {
@Override
public void stop() {
// TODO Auto-generated method stub
}
@Override
public void start() {
// TODO Auto-generated method stub
}
});
JKD8接口新特性
JDK8及以后:除了可以定义全局常量和抽象方法以外,还可以定义静态方法、默认方法。
public interface CompareA {
//静态方法
public static void method1() {
System.out.println("CompareA:北京");
}
//默认方法
public default void method2() {
System.out.println("CompareA:上海");
}
default void method3() {
System.out.println("CompareA:上海");
}
}
此时如果让别的类实现此接口,就不会要求重写什么,因为接口里连个抽象方法都没有
class SubClass implements CompareA{
}
注意:如果此时想通过SubClass的对象调用CompareA里的静态方法是行不通的!
- 接口中定义的静态方法,只能通过接口来调用 例如:
CompareA.method1();
- 通过实现类的对象,可以调用接口中的默认方法;如果实现类重写了接口中的默认方法,调用时,调用的是重写的方法。
public static void main(String[] args) {
SubClass s = new SubClass();
s.method2();
}
- 如果子类(或实现类)继承的父类和实现的接口中声明了同名同参数的方法,那么子类在没有重写此方法的情况下,默认调用的是父类中同名同参数的方法。——>类优先原则
比如我创建了接口CompareA和一个类SuperClass,里面都有一个方法叫method3(),代码如下。
CompareA接口
default void method3() {
System.out.println("CompareA:上海");
}
SuperClass类
public void method3() {
System.out.println("SuperClass:北京");
}
此时如果让一个类去继承SuperClass类并实现接口:
class SubClass extends SuperClass implements CompareA{
}
我们通过Subclass的对象去调用这个方法会显示调用父类中的方法
- 如果实现类实现了多个接口,而多个接口中定义了同名同参数的默认方法。那么在实现类没有重写此方法的情况下就会报错。——>接口冲突
- 如何在实现类的方法中调用父类或接口中被重写的方法
class SubClass extends SuperClass implements CompareA,CompareB{
public void myMethod() {
super.method3();//父类中
CompareA.super.method3();//调用接口中默认方法,静态方法可以不加super
CompareB.super.method3();
}
}
Object类的使用
-
Object类是所有的Java类的根父类。
-
如果在类的声明中未使用extends关键字指明其父类,则默认父类为java.long.Object类。
-
Object的功能(属性和方法)就具有通用性。
-
Object只声明了一个空参构造器。
-
主要结构:
| 方法名 | 描述 |
|---|---|
| public boolean equals(Object obj) | 对象比较 |
| public int hashCode() | 取得Hash码 |
| public String toString() | 对象打印时调用 |
- “==”和equals的区别 ✨“==”是运算符:可以使用在基本与引用数据类型变量中。
如果比较的是基本数据类型变量:比较两个变量保存的数据是否相等。(不一定类型要相同)
如果比较的是引用数据类型变量:比较两个变量保存的地址值是否相等。即两个引用是否指向同一个对象实体。
补充: ==符号使用时,必须保证符号左右两边的变量类型一致。
equals的使用:
- 是一个方法,而不是运算符。
- 只适用于引用数据类型
- Object类中equals()的定义:
public boolean equals(Object obj) {
return (this == obj);
}
说明:Object类中定义的equals()和==作用相同的:比较两个变量保存的地址值是否相等。即两个引用是否指向同一个对象实体。
-
像String、Date、File、包装类等都重写了Object类中的equals()方法。重写以后,比较的不是两个引用的地址相同,而是比较两个对象的“实体内容”是否相同。 意思是比如创建String类型的两个对象实体,内容是相同的,用euqals()方法进行比较话,结果为true;如果用"==",结果为false。
-
通常情况下,我们自定义的类,如果使用equals()的话,也通常是比较两个对象的“实体内容”是否相同。那么,我们就需要对Object类中的equals()进行重写。 例如,我在一个Customer类中写了两个属性,还有带形参的构造器。我在这个类中重写equals()方法,目的就是为了在测试类中可以比较两个实体内容是否相同。代码如下:
Customer类
public class Customer {
private String name;
private int age;
public Customer() {
super();
}
public Customer(String name, int age) {
super();
this.name = name;
this.age = age;
}
//比较两个对象的实体内容是否相同,
@Override
public boolean equals(Object obj) {
if(this == obj) {
return true;
}
if(obj instanceof Customer) {
Customer cust = (Customer)obj;
return this.age == cust.age && this.name.equals(cust.name);
}
return false;
}
}
测试类如下
public class EqualsTest {
public static void main(String[] args) {
Customer c1 = new Customer("Tom",21);
Customer c2 = new Customer("Tom",21);
System.out.println(c1.equals(c2));
}
}
注意:eclipse编译器中可以在“Source”下的“Generate hashCode() and equals()”下自动重写equals()。
总结
equals()重写原则:
- 对称性:如果x.equals(y)返回是“true”,那么y.equals(x)也应该返回是“true”。
- 自反性:x.equals(x)必须返回是“true”。
- 传递性:如果x.equals(y)返回是“true”,y.equals(z)返回是“true”,那么z.equals(x)也应该返回是“true”。
- 一致性:如果x.equals(y)返回是“true”,只要x和y的内容一直不变,不管你重复x.equals(y)多少次,返回都是“true”。
- 在任何情况下,x.equals(null),永远返回是“false”;x.equals(和x不同类型的对象)永远返回是“false”。
toString方法
- 当我们输出一个对象的引用时,实际上就是调用当前对象的toString()方法。
- Object类中toString()的定义:
public String toString() {
return getClass().getName() + "@" + Integer.toHexString(hashCode());
}
比如说你new了一个当前类的对象,打印“对象.toString()”和打印“对象”结果是一样的。
-
像String、Date、File、包装类等都重写了Object类中的equals()方法。使得在调用toString()时,返回“实体内容”信息。
-
自定义类可以重写toString()方法,当调用此方法时,返回“实体内容”信息。
注意:eclipse编译器中可以在“Source”下的“Generate toString()”下自动重写toString()。
包装类的使用
之前讲面向对象时,我们都是用类和对象去实现,而基本数据类型就没有参与进来,不好体现面向对象的思想。我们希望基本数据类型也让他们具有类的特征,所以给每一种基本数据类型对应生成了叫一种包装类。如下表
| 基本数据类型 | 包装类 |
|---|---|
| byte | Byte |
| short | Short |
| int | Integer |
| long | Long |
| float | Float |
| double | Double |
| boolean | Boolean |
| char | Character |
其中,Byte到Double的父类:Number
总结
基本类型、包装类与String类之间的转换
基本数据类型和包装类
✨基本数据类型——>包装类:调用包装类的构造器
举例:
int num1 = 10;
Integer in1 = new Integer(num1);
System.out.println(in1);
Integer in2 = new Integer("123");
System.out.println(in2);
Integer in3 = new Integer(100);
System.out.println(in3);
输出结果:
10
123
100 注意写String时不能写英文字母
Float f1 = new Float(12.3f);
System.out.println(f1);
Float f2 = new Float("12.3");
System.out.println(f2);
Float f3 = new Float(12.3);
System.out.println(f3);
输出结果:
12.3
12.3
12.3
特殊的:Boolean
布尔类型的包装类,在传入String类型的值时,除了"true"或"false",写其他的任何数字或字母,结果都为false。可以忽略大小写,比如“TrUe”结果为true
例如:
Boolean b1 = new Boolean("a");
System.out.println(b1);
结果:false
再看下面的例子:
boolean b;
Boolean c;
不加赋值的情况下:
b的结果为false,c的结果为null
✨包装类——>基本数据类型:调用包装类的xxxValue()
转换成基本数据类型后可以进行加减乘除的运算
例如int型:
Integer in1 = new Integer(12);
int i1 = in1.intValue();
System.out.println(i1);
结果为:12
这个i1已经是int类型,而不再是包装引用类型。比如我们输出i1+1,结果为13
float类型:
Float f1 = new Float(12.3);
float f = f1.floatValue();
System.out.println(f);
结果为12.3
自动装箱与自动拆箱
这是JDK5.0新特性
自动装箱:基本数据类型——>包装类
int num1 = 10;
Integer in1 = num1;
boolean b1 = true;
Boolean b2 = b1;
自动拆箱:包装类——>基本数据类型
int num3 = in1;
boolean b3 = b2;
基本数据类型、包装类与Stirng
既然有自动装箱与自动拆箱,那么我们简单地把基本数据类型和包装类看成一个整体,整体与String之间的转换。
✨基本数据类型、包装类——>String
方式一:连接运算
方式2:调用String重载的valueOf(Xxx xxx)
int num1 = 10;
//方式1:连接运算
String str1 = num1 + "";
//方式2:调用String重载的valueOf(Xxx xxx)
float f1 = 12.3f;
String str2 = String.valueOf(f1);
Double d1 = new Double(12.4);
String str3 = String.valueOf(d1);
✨String——>基本数据类型、包装类
调用包装类的parseXxx(String s)
String str1 = "123";
int num1 = Integer.parseInt(str1);
System.out.println(num1);
包装类的使用面试题
🎈例1:
下面两个题目输出结果相同吗?各是什么?
Object o1 = true? new Integer(1) : new Double(2.0);
System.out.println(o1);
Object o2;
if(true)
o2 = new Integer(1);
else
o2 = new Double(2.0);
System.out.println(o2);
先看结果:第一个输出的是1.0,第二个输出的是1
解析:第一个三元运算符整个都要去编译,也就是说结果要和Integer、Double都有关系,运行我们确实知道结果是Integer,但Double也参加编译了。比如我们把Double换成String,那么编译都会报错。
即编译时就要求“:”左右两边都能统一成一个类型。所以Integer类型会提升到Double类型,输出结果为1.0
🎈例2:
求输出结果
Integer i = new Integer(1);
Integer j = new Integer(1);
System.out.println(i == j);
Integer m = 1;
Integer n = 1;
System.out.println(m == n);
Integer x = 128;
Integer y = 128;
System.out.println(x == y);
结果为:
false
true
false
解析:
第一个:包装类是引用数据类型,“==”在引用数据类型中比较的是两个变量的地址。所以第一个为false。
第二个和第三个属于自动装箱。但Integer内部定义了IntegerCache结构,IntegerCache中定义了Integer[],保存了-128 ~ 127范围内的证书。如果我们使用自动装箱的方式,给Integer赋值的范围在-128 ~ 127,可以直接使用数组中的元素,不用再去new了。目的:提高效率。
🎈例3:
利用Vector代替数组处理:从键盘读入学生成绩(以负数代表输入结束),找出最高分,并输出学生成绩等级。
-
提示:数组一旦创建,长度就固定不变,所以在创建数组前就需要知道它的长度。而向量类java.util.Vector可以根据需要动态伸缩。
-
创建Vector对象:Vector v = new Vector();
-
给向量添加元素:v.addElement(Object obj); //obj必须是对象
-
取出向量中的元素:Object obj = v.elementAt(0);
✔注意第一个元素下标是0,返回值是Object类型的。
-
计算向量长度:v.size();
-
若与最高分相差10分内:A等;20分内:B等;30分内:C等;其他:D等
import java.util.*;
public static void main(String[] args) {
//1.实例化Scanner,用于从键盘获取学生成绩
Scanner scan = new Scanner(System.in);
//2.创建Vector对象:Vector v = new Vector();相当于原来的数组
Vector v = new Vector();
//3.通过for(;;)或while(true)方式,给Vector中添加数组
int maxScore = 0;
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)
v.addElement(score);//自动装箱 JDK5.0之后
//4.获取学生成绩的最大值
if(maxScore < score) {
maxScore = score;
}
}
//5.遍历Vector,得到每个学生的成绩,并于最大成绩比较,得到每个学生的等级
char level;
for(int i = 0;i<v.size();i++) {
Object obj = v.elementAt(i);
Integer inScore = (Integer)obj;
int score = inScore.intValue();
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 + ", level is " + level);
}
}
MVC设计模式
MVC是常用的设计模式之一,将整个程序分为三个层次:视图模型层,控制器层,与数据模型层。
这种将程序输入输出、数据处理,以及数据的展示分离开的设计模式是程序结构变得灵活而且清晰,同时也描述了程序各个对象间的通信方式,降低了程序的耦合性。
单例设计模式
设计模式:可以理解为“套路”。是在大量时间中总结和理论化之后优选的代码结构、编程风格、以及解决问题的思考方式。
单例设计模式独立于编程语言。
单例设计模式就是采取一定的方法保证在整个的软件系统中,对某个类只能存在一个对象实例。
单例模式的好处:由于单例模式只生成一个实例,这样就可以减少系统性能开销。
单例的饿汉式实现
比如我现在创建一个类叫Bank,我希望它是一个单例的,也就是只能造一个对象。
class Bank{
//1.私有化对象的构造器
private Bank() {
}
//2.内部创建类的对象
//4.要求此对象也必须声明为静态的
private static Bank instance = new Bank();
//3.提供公共的静态的方法,返回类的对象
public static Bank getInstance() {
return instance;
}
}
解析:
必须私有化构造器,不然的话就可以创建多个对象
既然私有化构造器,那么就不能再main方法里创建对象,所以只能在Bank类内部创建对象。
我们可以和之前一样创建私有属性并在类中创建get和set方法。所以在这里我也创建了一个get方法来返回创建的对象。
然后会发现,如果不加static,我们根本不能调用这个方法!因为调用类的方法我们还是要在main里去创建类的对象。
所以,方法应该加static,同样的对象也要加static,因为静态方法里要用静态的对象或属性方法。
main方法中调用get方法
Bank bank1 = Bank.getInstance();
Bank bank2 = Bank.getInstance();
System.out.println(bank1 == bank2);
结果返回true
单例的懒汉式实现
class Order{
//1.私有化对象的构造器
private Order() {
}
//2.声明当前类对象,没有初始化。
//4.要求此对对象也必须声明为静态的
private static Order instance = null;
//3.声明public,static的返回当前类对象的方法。
public static Order getInstance() {
if(instance == null) {
instance = new Order();
}
return instance;
}
}
区分饿汉式和懒汉式
饿汉式:对象加载时间过长。但线程是安全的。
懒汉式:更贴合程序。可以延迟对象的创建。但线程不安全。
单例的应用场景
- 网站的计数器,一般也是单例模式实现,否则难以同步。
- 应用程序的日志应用
- 数据库连接池
- 项目中,读取配置文件的类。
- Application也是单例的典型应用。
- Windows的Task Manager(任务管理器)就是很典型的单例模式
- Windows的Recycle Bin(回收站)