【JavaSE】面向对象——关键字

183 阅读27分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 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关键字。

image.png

👇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的使用

  1. 为了更好地实现项目中类地管理,提供包(package)的概念
  2. 使用package声明类或接口所属的包,声明在原文件的首行
  3. 包,属于标识符,遵循标识符的命名规则、规范、做到“见名知意”
  4. 每"."一次,就代表一层文件目录 补充:同一个包下,不能命名同名的接口、类。

不同的包下,可以命名同名的接口、类。

✨import的使用

import:导入

  1. 在源文件中显式地使用import结构导入指定包下的类、接口
  2. 声明在包(package)的声明和类的声明之间
  3. 如果需要导入多个结构,则并列写出即可
  4. 可以使用“xxx.*”的方式,表示可以导入xxx包下的所有结构。但如果使用的是xxx子包下的结构,则仍需显示导入。
  5. 使用频繁的类或接口一般定义在lang包下(比如System、String),定义在java.lang包下的类或接口可以省略import结构
  6. 如果使用的类或接口是本包下定义的,则可以省略import结构
  7. 如果在源文件中,使用了不同包下的同名的类,则必须至少有1个类需要以全类名(包.类)的方式显示。
  8. import static:导入指定类或接口中的静态结构:属性或方法。

super

在子类中重写了父类的方法,这样我们创建子类对象的时候默认会调用重写后的方法。为了让重写的方法和被重写的方法区分开,我们就需要用到super关键字。

super关键字的使用

super理解为:父类的。可以用来调用:属性、方法、构造器。

  1. 我们可以在子类的方法或构造器中。通过使用“super.属性”或“super.方法”,显式的调用父类中声明的属性或方法。但是,通常情况下,我们习惯省略“super.”。
  2. 属性特殊情况:当子类和父类中定义了同名的属性时,我们要想在子类中调用父类中声明的属性时,则必须显式的使用“super.属性”的方式,表明调用的是父类中声明的属性。
  3. 方法特殊情况:当子类重写了父类中的方法以后,我们想在子类的方法中调用父类中被重写的方法时,则必须显式的使用“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)。

  1. super调用构造器
  • 我们可以在子类的构造器中显式的使用“super(形参列表)”的方式,调用父类中声明的指定的构造器。
  • “super(形参列表)”的使用,必须声明在子类构造器的首行
  • 在类的构造器中,针对于“this(形参列表)”或“super(形参列表)”只能二选一,不能同时出现。
  • 当我们在构造器的首行,没有显式地声明“this(形参列表)”或“super(形参列表)”,则默认调用的是父类中空参的构造器:super()。
  • 在类的多个构造器中,至少有一个类的构造器中使用了“super(形参列表)”,调用父类中的构造器。

子类对象实例化的全过程

  1. 从结果来看:(继承性)
  • 子类继承父类以后,就获取了父类中声明的属性或方法。
  • 创建子类的对象,在堆空间中,就会加载所有父类中声明的属性。
  1. 从过程看:
  • 当我们通过子类的构造器创建子类对象时,我们一定会直接或间接的调用其父类的构造器,进而调用父类的父类的构造器,直到调用了java.lang.Object类中空参的构造器为止。正因为加载过所有的父类的结构,所以才可以看到内存中有父类中的结构,子类对象才可以考虑进行调用。 虽然创建子类对象时,调用了父类的构造器,但是自始至终就创建过一个对象,即为new的子类对象。

instanceof

有了对象的多态性以后,内存中实际上是加载了子类特有的属性和方法的,但是由于变量声明为父类类型,导致编译时,只能调用父类中声明的属性和方法。子类特有的属性和方法不能调用。 那么如何调用子类特有的方法和属性呢?

  1. 向下转型:使用强制类型转换符 比如Peron为父类,Man为子类,当使用多态性创建父类类型的子类对象后,要想再用子类特有的属性和方法,可以下面这么写:
Person p = new Man();
Man m = (Man)p;

但如果你这么写:比如Woman也是Person子类,用上面造过的对象p

Woman w = (Woman)p;

就会出现ClassCastException的异常。因为p其实是Man的对象,不能转成Woman类型。

为了避免出现这个异常,我们就引入instanceof关键字。

  1. 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的使用

  1. 定义:静态的
  2. 可以修饰:属性、方法、代码块、内部类。不可以修饰构造器
  • 修饰属性:静态变量(或类变量) 属性,按是否有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修饰的变量。

  • 修饰方法:静态方法 ①静态方法随着类的加载而加载。可以通过“类.静态方法”的方式进行调用。

②不能使用类去调用非静态方法

③在静态方法中只能调用静态方法或属性,对于非静态方法中,既可以调用非静态方法或属性,也可以调用静态方法或属性。

  1. static注意点: 在静态方法内,不能使用this、super关键字

判断是否该用static

如何确定属性是否要声明为static?

属性是可以被多个对象所共享的,不会随着对象的不同而不同的。

类中的常量也常声明为static

如何确定方法是否要声明为static?

操作静态属性的方法,通常设置成static的

工具类中的方法,习惯上声明为static。比如:Math,Arrays,Collections

static练习

  1. 创建圆的类,每创建一个圆的对象就得到一个新的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;
    }	
}
  1. 编写一个类实现银行账户的概念,包含的属性有“账号”、“密码”、“存款余额”、“利率”、“最小余额”,定义封装这些属性的额方法。账号要自动生成。 编写主类,使用银行账户类,输入、输出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修饰变量

注意是变量而不是属性!变量包含属性和局部变量。

此时的“变量”就称为是一个常量

  1. final修饰属性:可以考虑的赋值位置有:
  • 显式初始化
final int WIDTH = 10;
  • 代码块中赋值
final int LEFT;

{
    LEFT = 1;
}
  • 构造器中初始化
final int RIGHT;

public FinalTest() {
    RIGHT = 2;
}

public FinalTest(int n) {
        RIGHT = n;
}
  1. 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可以修饰的结构:类,方法

  1. 修饰类:抽象类
  • 此类不能实例化(即不能造对象)
  • 抽象类中依然有构造器,便于子类实例化时调用(设计:子类对象实例化全过程)
  • 开发中:都会提供抽象类的子类,让子类对象实例化,完成相关操作。

比如下面代码中,Person类就不能在main方法中造对象

abstract class Person{

}
  1. 修饰方法:抽象方法
  • 格式:没有方法体,并再返回值类型前加上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不支持多重继承(即从几个类中派生出一个子类),有了接口,就可以得到多重继承的效果。

举例:把“飞”这个功能定义为接口,可以让多个子类去实现这个接口,代表具有这个功能,同样的,也可以让这些子类去实现另外一个接口,表明两个功能都有。


接口的使用

  1. 使用interface来定义
  2. java中,接口和类是并列的结构,要么写类class,要么写接口interface
  3. 如何定义:定义接口的成员(和类相似) 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
}
  1. 接口中不能定义构造器!意味着接口不能实例化。
  2. 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中的全局常量。

  1. 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
		
	}	
}
  1. 接口和接口之间也叫继承,还可以多重继承
interface AA{
	void method1();
}

interface BB{
	void method2();
}

interface CC extends AA,BB{
	
}
  1. 接口的具体使用体现多态性
  2. 接口,实际上可以看成一种规范。

创建接口特殊对象

基于以下代码创建:

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里的静态方法是行不通的!

  1. 接口中定义的静态方法,只能通过接口来调用 例如:
CompareA.method1();
  1. 通过实现类的对象,可以调用接口中的默认方法;如果实现类重写了接口中的默认方法,调用时,调用的是重写的方法。
public static void main(String[] args) {
        SubClass s = new SubClass();
        s.method2();
}
  1. 如果子类(或实现类)继承的父类和实现的接口中声明了同名同参数的方法,那么子类在没有重写此方法的情况下,默认调用的是父类中同名同参数的方法。——>类优先原则

比如我创建了接口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的对象去调用这个方法会显示调用父类中的方法

  1. 如果实现类实现了多个接口,而多个接口中定义了同名同参数的默认方法。那么在实现类没有重写此方法的情况下就会报错。——>接口冲突
  2. 如何在实现类的方法中调用父类或接口中被重写的方法
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的使用:

  1. 是一个方法,而不是运算符。
  2. 只适用于引用数据类型
  3. Object类中equals()的定义:
    public boolean equals(Object obj) {
        return (this == obj);
    }

说明:Object类中定义的equals()和==作用相同的:比较两个变量保存的地址值是否相等。即两个引用是否指向同一个对象实体。

  1. 像String、Date、File、包装类等都重写了Object类中的equals()方法。重写以后,比较的不是两个引用的地址相同,而是比较两个对象的“实体内容”是否相同。 意思是比如创建String类型的两个对象实体,内容是相同的,用euqals()方法进行比较话,结果为true;如果用"==",结果为false。

  2. 通常情况下,我们自定义的类,如果使用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()重写原则:

  1. 对称性:如果x.equals(y)返回是“true”,那么y.equals(x)也应该返回是“true”。
  2. 自反性:x.equals(x)必须返回是“true”。
  3. 传递性:如果x.equals(y)返回是“true”,y.equals(z)返回是“true”,那么z.equals(x)也应该返回是“true”。
  4. 一致性:如果x.equals(y)返回是“true”,只要x和y的内容一直不变,不管你重复x.equals(y)多少次,返回都是“true”。
  5. 在任何情况下,x.equals(null),永远返回是“false”;x.equals(和x不同类型的对象)永远返回是“false”。

toString方法

  1. 当我们输出一个对象的引用时,实际上就是调用当前对象的toString()方法。
  2. Object类中toString()的定义:
    public String toString() {
        return getClass().getName() + "@" + Integer.toHexString(hashCode());
    }

比如说你new了一个当前类的对象,打印“对象.toString()”和打印“对象”结果是一样的。

  1. 像String、Date、File、包装类等都重写了Object类中的equals()方法。使得在调用toString()时,返回“实体内容”信息。

  2. 自定义类可以重写toString()方法,当调用此方法时,返回“实体内容”信息。

注意:eclipse编译器中可以在“Source”下的“Generate toString()”下自动重写toString()。


包装类的使用

之前讲面向对象时,我们都是用类和对象去实现,而基本数据类型就没有参与进来,不好体现面向对象的思想。我们希望基本数据类型也让他们具有类的特征,所以给每一种基本数据类型对应生成了叫一种包装类。如下表

基本数据类型包装类
byteByte
shortShort
intInteger
longLong
floatFloat
doubleDouble
booleanBoolean
charCharacter

其中,Byte到Double的父类:Number

总结

基本类型、包装类与String类之间的转换

image.png

基本数据类型和包装类

✨基本数据类型——>包装类:调用包装类的构造器

举例:

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;
	}
}

解析:

  1. 必须私有化构造器,不然的话就可以创建多个对象

  2. 既然私有化构造器,那么就不能再main方法里创建对象,所以只能在Bank类内部创建对象。

  3. 我们可以和之前一样创建私有属性并在类中创建get和set方法。所以在这里我也创建了一个get方法来返回创建的对象。

  4. 然后会发现,如果不加static,我们根本不能调用这个方法!因为调用类的方法我们还是要在main里去创建类的对象。

  5. 所以,方法应该加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(回收站)