java基础学习08(重写方法、jdk自带的Object类、封装)
第一章 接上次讲继承中的方法重写
在子类继承父类后,在调用子类的功能后后发现子类的功能不满足我们的需求时,我们就要在子类中重新编写相同的方法,达到覆盖父类中该方法的效果,这样再次调用就能满足我们的需求了。其实就是对于子类的一些行为的扩展。
出现方法重写的前提:一定发生继承关系!!
1.1 怎样达到子类方法覆盖父类方法?
- 子类中方法名和父类中方法名一模一样;
- 子类中方法的参数列表和父类方法中的参数列表一模一样;
- 子类中方法返回值类型也好和父类方法返回值也要一模一样。
实例代码:
public class Father {
String name;
int age;
int height;
public Father(){
}
public int getAge(){
this.age = 45;
return this.age;
}
}
public class Son extends Father{
int id;
public Son(){
}
@Override
public int getAge(){
this.age = 20;
return this.age;
}
}
public class Text {
public static void main(String[] args) {
Son t = new Son();
System.out.println(t.getAge());
//输出结果是20
}
}
代码分析:在上述代码中,Son类继承了Father类,所以Son类是子类,Father是父类,我们发现子类和父类中都存在getAge方法,且两个类中该方法参数列表都是空,返回值也是一样的,所以在子类中我们重写了getAge方法,这样的就不会调用父类的getAge方法了,所以输出结果是20。
@Override:我们在子类重写的那个方法上面一行(上述代码所示)写上@Override,如果会报错,那么说明该方法不是重写方法,这也是判断该方法是不是重写方法的一种方法。
**tips:**子类对象调用子类的方法和属性是,现在子类中找,如果子类中没有,那么就会去父类中找,会沿着继承链一直往上找。
1.2 所有类的默认存在的父类Object
1.2.1 什么是Object类?
Object类是JDK中存在的一个默认类,是所有类的直接父类或者间接父。在Object中存在的方法所有类中都具备。
1.2.2 Object类中的重要方法(暂只讲两个)
1、toString();
Object类中的toString方法:
public String toString() {
return getClass().getName() + "@" + Integer.toHexString(hashCode());
}
示例代码:
public class Text01 {
int num = 10;
public static void main(String[] args) {
Text01 t = new Text01();
System.out.println(t);
}
}//输出结果com.mage.methodRewrite.Text01@15db9742
代码分析:我们在打印一个对象的时候,其实默认调用的就是该对象的toString方法,该类的对象如果该类中不存在toString方法,那么就会去该类默认的父类Object类中寻找,从而输出的是上述代码简称为包名.类名@16进制的值(地址值)。
那么如果我们不要输出这样的结果,我们就要去当前类中改写toString方法,那我们看下面的实例代码:
public class Text02 {
int age;
String name;
public Text02(){
}
public static void main(String[] args) {
Text02 t = new Text02();
System.out.println(t);
//输出结果是张三 30
}
public String toString(){
this.name = "张三";
this.age = 30;
return (this.name+"\t"+age);
}
}
分析上述代码:我们说打印一个对象就是调用该对象的toString方法即输出该对象的地址值,那么我们在本类中改写toString方法,这样调用的toString方法就是本类中的了,而不会去调用该类默认父类的toString方法了。这样结果就是上述代码中的了。
2、equals();
比较两个对象是否相等。
Object中的equals方法代码:
public boolean equals(Object obj) {
return (this == obj);
}
示例代码:
public class Text03 {
int num = 10;
public static void main(String[] args) {
Text03 t = new Text03();
Text03 t1 = new Text03();
System.out.println(t);
//输出结果是com.mage.methodRewrite.Text03@15db9742
System.out.println(t1);
//输出结果是com.mage.methodRewrite.Text03@6d06d69c
System.out.println(t.equals(t1));
//输出结果是false
}
}
**总结:**对象的比较就是比较其地址值,而每次new出来都会在堆内存开出新的内存,也就意味着对象的不同那么地址值也是不一样的,所以去比较两个不同的对象是结果一定是false。
**tips:**基本对象的比较就是比较变量值,而我们的String类型属于引用数据类型,它比较的是地址值。
又是我们比较对象可以去看对象的属性功能是不是相等,这个时候我们说对象的属性和功能相等我们就输出ture,不相等我们就输出false。那么我们就要去对象类中改写equals方法达到这种效果。看如下示例代码:
public class Person {
String name;
int age;
char gender;
public Person(){
}
public boolean equals(Object obj){
Person other = (Person)obj;
if(this.name.equals(other.name)&&this.age==other.age){
return true;
}
return false;
}
}
public class PersonText {
public static void main(String[] args) {
Person t = new Person();
t.name = "德玛西亚之力";
t.age = 18;
Person t1 = new Person();
t1.name = "德玛西亚之力";
t1.age = 18;
boolean flag = t.equals(t1);
System.out.println(flag);
//输出结果是ture
}
}
代码分析:在上述代码中调用了JDK中默认存在的String类,如果equals比较的是String类型的时候,会比较字符串中的每一个元素,全部相等即为相等。
那么又有下引申:我们要去比较随意字符串和对象是否相等?想法:我们这里要使用if判定语句,如果该字符串是类的属性,且字符串相等那么两个才会相等,该字符串如果都不是类的属性,那么都不用比较就知道肯定不会相等了,这里就要用到比较引用属性的运算符了:instanceof。看下面实例代码:
public class Animals {
String name;
int age;
int weight;
public Animals(){
}
public boolean equals(Object obj){
if(!(obj instanceof Animals)){
return false;
}
Animals other = (Animals) obj;
if(this.name.equals(other.name)&&this.age==other.age){
return true;
}
return false;
}
}
public class AnimalsText {
public static void main(String[] args) {
Animals t = new Animals();
t.name = "狐狸";
t.age = 6;
String strs = "老虎";
boolean flag = t.equals(strs);
System.out.println(flag);
}
}
**代码分析和结论:**这个代码比较的是数组和对象,但是我们可以想到一个类的对象和一个不属于这个类的数组去比较得出的值就是false;上述代码体现的是Object中变量obj可以传入任意的值,所以我们在Animals类中改写的equals方法中加入了新的代码即if语句,如果传入的值都不属于该类,那么就不用做后续操作,直接输出false即可。不论这里strs的值是不是和this.name的值一不一样,都会输出false,就是因为strs中的值不是属于Animals这个类的。
下面我们再来测试其他的结果:
public class Animals {
String name;
int age;
int weight;
public Animals(){
}
public boolean equals(Object obj){
if(!(obj instanceof Animals)){
return false;
}
Animals other = (Animals) obj;
if(this.name.equals(other.name)&&this.age==other.age){
return true;
}
return false;
}
}
public class AnimalsText {
public static void main(String[] args) {
Animals t = new Animals();
t.name = "狐狸";
t.age = 6;
//当String strs赋值为null时会不会报错(不会报错)
String strs = null;
boolean flag = t.equals(strs);
System.out.println(flag);
//一个给属性赋值的对象t和一个没有给属性赋值的对象t1做比较(不会报错)
Animals t1 = new Animals();
System.out.println(t.equals(t1));
//一个直接赋值为null的对象和给属性赋值的对象做比较(不会报错)
Animals t2 = new Animals();
System.out.println(t.equals(t2));
}
}
总结:
- 注意调用equals方法的对象值一定不能为null,否则会出现空指针异常,而equals方法中的形式参数可以是null值,这样不会报错,输出值为false;
- 在给equals方法带入实参null时,输出值为false,原因是null不属于任何的引用类型。但是引用数据类型可以赋值null。
1.3 类型转换
引用数据类型转换和基本数据类型基本相似。
1、自动转换
表现形式为:大类型 变量名 = 小类型值
实例代码:
class F{ }
class S extends F{
S s = new S();
System.out.println(s);
F f = s;
System.out.println(f);
}
2、强制转换
小类型 变量名 = (小类型)大类型值;
子类型 变量名 = (子类型)父类型变量;
实例代码:
S s1 = (S)f;
System.out.println(s1);
F f1 = new F();
S s2 = (S)f1;
注意事项:引用类型的强制转换要格外的小心,基本不要使用,你一定要知道当前变量的实际类型是什么才能强转,例如我们将一个子类A赋值给父类,而后我们强转将父类赋值给另外一个子类B,这个时候就会报错,这里不能肯定子类A和子类B会相等。
第二章 封装
2.1 什么是封装?
封装就是我们通过一些修饰的关系词来降低类,构造器、属性,功能的可见率来控制其他类对这些类,构造器、属性和功能的访问。
2.2 如何去封装?
2.2.1 属性的封装
属性的封装就是通过不同的修饰符去修饰属性,导致某些属性外部的可见性降低。外部不能随意修改以及获
取。看下述代码:
public class Quality {
private String name;
public Quality(){
}
public void method(){
System.out.println(name+"这是未设置的name");
}
//设置方法,使得调用该方法可以获得name的值
public void setName(String name){
this.name = name;
}
public String getName(){
return name;
}
}
public class QualityText {
public static void main(String[] args) {
Quality t = new Quality();
//t.name 这是会报错,因为在类Quality类中name被private修饰了,不可以调用
t.method();
t.setName("张三");
System.out.println(t.getName());
}
}
- 这里就不去细说每个修饰符修饰属性后会表示什么结果,后期说到修饰符的时候我们再去细谈;
- 用private修饰后就不可以通过对象.属性去调用属性了,它在其他类中是不可见的;
- 一旦我们的属性被封装后,我们内部要有可见的方法去获取属性和设置属性,这样的话代码的可操作空间就获得了提升。
2.2.2 构造器的封装
实例代码:
public class Constructor {
//将构造器封装,即用修饰词修饰
private Constructor(){
}
//创建一个方法去让其他类获取该类的对象 这个方法一定要用static修饰,没有对象所以要直接类名.方法调用到该方法
public static Constructor getConstructor(){
return new Constructor();
}
}
public class ConstructorText {
public static void main(String[] args) {
Constructor t = Constructor.getConstructor();
System.out.println(t);
//输出结果是com.mage.encapsulation.Constructor@15db9742
}
}
-
我们假设一个类中所有的方法都是用static修饰的,那么我们就没有必要去创建该类的对象从而用对象.方法去调用方法了,我们只要类名.方法就可以直接拿过来用了,这个时候我们就要把该类的构造器给封装起来,避免在去创造该类的对象了,例如JDK中的Math类就是这样的,它里面的方法都是用static修饰的,就没必要再去创建对象了,这是Math类的构造器就被封装起来了。
-
一般情况下对于构造器的封装都是有意义的,后期的单例模式、后期的一些工具类的创建都会将构
造器私有起来。本质上构造器是否私有,构造器是否封装是有当前类所处的角色事先就定义好了。
2.2.3 方法的封装
编写代码,完成数组的添加元素,不考虑后期的维护
public class Arrays {
private int[] arrs;
private int index;//咱们设置数的添加位置
public Arrays(){
arrs = new int[4];//一声明对象就给数组赋值为长度为4的数组
index = 0;//默认从数组索引为0的位置上添加元素
}
//往创建的对象中添加元素
public void add(int value){//表示添加的数
if(index>=arrs.length){
resize();
}
arrs[index] = value;
index++;
}
//这里的resize就可以是private修饰的,不需要给别人看到的
private void resize(){
int[] newarrs = new int[arrs.length*2];
for(int i=0;i<=arrs.length-1;i++){
newarrs[i] = arrs[i];
}
arrs = newarrs;
}
//编写toString方法,用法是直接输出该数值就是调用该数组的toString方法
public String toString(){
String result = "[";
String result1 = "";
for(int i:arrs){
result1+=i+" ";
}
String result2 = "]";
String set = result+result1+result2;
return set;
}
public int getLength(){
return arrs.length;
}
}
对于这串代码中的resize即给数组扩容的方法我们就可以用private修饰,它不需要在其他类中调用,只要在本类中能看的到就可以,这样就将该方法给封装起来了。
2.3 包和导包
2.3.1 包
1、 什么是包?
包就是在java中用来存放.java文件的,类似文件夹的形式,它可以区分类。
- 需要注意的是包下面的.java文件没有父子关系,都是独立存在的。
- 编写代码是给类起名要注意不要和JDK内置的类同名了,java中的对象在通过输出语句直接输出是打印的内容是全限定名(包名.类名),通过全限定名去唯一标示一个类。java中的最小的单位就是类。
2、如何定义一个类的包
实例代码
package com.mage.dingyi;
public class Package {
int big;
public Package(){
}
public static void main(String[] args) {
System.out.println("我的第一个包文件");
}
}
- 通过package关键词来定义一个包
- package必须放在类的外面且是第一行
2.3.3 导包
导包指的是你想调用的方法所属的类不在一个包下的时候,就要进行导包。
示例代码:
package com.mage.dingyi;
//获取用户键盘输入的值
import java.util.Scanner;
public class PutIn {
public static void main(String[] args) {
Scanner input = new Scanner(System.in);
System.out.println("输入一个数");
int num = input.nextInt();
System.out.println(num);
}
}
总结:
- 如果两个类处在同一个包下,那么不需要导包;
- 不同包下的类想要互相调用必须导包;
- 在package下面,类声明的上面通过import关键词+导入的类的全限定名;
- 在java中java.lang包下的类是不需要导包的,可以直接使用;
- 编写代码时如果出现需要导包的时候,可以使用ctrl+1来完成快速导包;
- import java.util.*; 导入java.util下的所有内容;都是这样的,后面跟星符号就是表示为导入该包下的所有类;
- 导包的目的就是为了能够使用其它包下的类。
2.4 补充四个修饰符public,protected,默认的(即不写),private用法
修饰符 | 同包子类 | 同包类 | 异包类 | 异包子类 | 本类 |
---|---|---|---|---|---|
public | 可以 | 可以 | 可以 | 可以 | 可以 |
protect | 可以 | 可以 | 不可以 | 可以 | 可以 |
默认的 | 可以 | 可以 | 不可以 | 不可以 | 可以 |
private | 不可以 | 不可以 | 不可以 | 不可以 | 可以 |
- 使用修饰符一定程度上保证了方法,属性以及类的安全性;
- 封装一定程度上打破了继承关系。
port java.util.*; 导入java.util下的所有内容;都是这样的,后面跟星符号就是表示为导入该包下的所有类;
- 导包的目的就是为了能够使用其它包下的类。
2.4 补充四个修饰符public,protected,默认的(即不写),private用法
修饰符 | 同包子类 | 同包类 | 异包类 | 异包子类 | 本类 |
---|---|---|---|---|---|
public | 可以 | 可以 | 可以 | 可以 | 可以 |
protect | 可以 | 可以 | 不可以 | 可以 | 可以 |
默认的 | 可以 | 可以 | 不可以 | 不可以 | 可以 |
private | 不可以 | 不可以 | 不可以 | 不可以 | 可以 |
- 使用修饰符一定程度上保证了方法,属性以及类的安全性;
- 封装一定程度上打破了继承关系。