前言
记录没有条理性,记一些自己没怎么用过,但是比较重要的内容。所以不是那么全面。
因为自己有一些java基础,为秋招准备的。大家看看就好,有错误的地方欢迎指正。
本节内容:面向对象-中,继承,多态,重写,super,单元测试,包装类等。
一、继承性
优点:代码的复用,功能的拓展,多态性的前提
格式:class A extends B{},A为子类(sub),B为父类(super、基类)
1、注意事项
- 子类继承父类中的所有属性和方法,直接拿过来即可用,不用重新定义。父类中的private方法子类可以继承,但是不能直接使用。(继承性不能打破封装性)
- 子类继承父类后,还可以自己拓展新的功能。
- 一个类只有一个父类,一个父类可以有多个子类,子类继承父类,也同时继承爷爷类的方法。
- object类:如没有特殊设定,类默认继承于java.lang.Object类,也具有它的属性和功能。即所有的类都是其子孙。
//person.java
package com.atguigu.java;
public class Person extends Creature{ //前面creature类中只有breathe方法
String name;
private int age;
public Person(){
}
public Person(String name,int age){
this.name = name;
this.age = age;
}
public void eat(){
System.out.println("吃饭");
sleep(); //调用下面sleep方法
}
private void sleep(){
System.out.println("睡觉");
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
//同package下的student.java
package com.atguigu.java;
public class Student extends Person{ //继承于person,拿下他的属性和方法
// String name; //所以不用再次声明
// int age;
String major;
public Student(){
}
public Student(String name,int age,String major){
this.name = name;
// this.age = age;
setAge(age);
this.major = major;
}
// public void eat(){
// System.out.println("吃饭");
// }
//
// public void sleep(){
// System.out.println("睡觉");
// }
public void study(){ //student自己独特的方法。
System.out.println("学习");
}
public void show(){
System.out.println("name:" + name + ",age:" + getAge());
}
}
番外:eclipse的debug功能
代码量小时,可以直接sysout输出。
双击左侧加断点,右键dubug as即为调试界面,变量在断点处时存储的值。
step over就是一行一行代码地往下走,左侧箭头所指的这一行还没有执行,只有再点一次箭头到下一行,本行才执行完毕。
step into即为进入两行代码间,即进入使用的方法体内部,看看方法。droptoframe跳到该方法的最前端。
step return即为出来,和into相反。
2、方法的重写
- override或者overwrite:子类继承父类后,对父类同名同参数的方法,进行覆盖。 重写后,创建子类对象后,子类调用的方法就是被重写后的方法了,就不是父类中的方法了。
- 规则:
- 书写格式:
权限public +返回类型int +方法名getAge(形参列表) + throws异常{
// 方法体
} 对上述的4个要素下面详细讨论如何重写 - 子类重写的方法a,父类中被重写的方法A:
- A和a的方法名和形参列表需要相同
- a的权限修饰符不小于A,public>缺省>protected>private。父类在这几个中往后面选,子类多往前面选。若不遵守,则调用的方法还是父类的方法而不是重写的方法。
- 特例:父类的private不能重写,即父类的方法为private时,不可商量,不容重写。
- 返回值类型:
- A中的void,那么a只能是void;
- A中的某引用类型,那么a的返回可以是该引用类型或其子类;
- A中基本数据类型,那么a中也必须相同;
- a中抛出的异常不能大于A抛出的异常
- Aa中方法要么都static,要么都不是
- 书写格式:
- 重写和重载的区别
- 重载:不同的函数使用相同的函数名,但是函数的参数个数不同。调用的时候根据函数的参数来区别不同的函数。 不表现为多态性
- 重写:子类继承父类后,对父类同名同参数的方法,进行覆盖。表现为多态性
3、super
父类的。
- 在子类中会重写父类的方法,但是偶尔想用一些原来的父类的方法,为了区分,使用super。
- 可以用来调用的结构:属性、方法、构造器。下面一一解释
- super调用属性、方法:
- 我们可以在子类的方法或构造器中。通过使用"super.属性"或"super.方法"的方式,显式的调用父类中声明的属性或方法。但是,通常情况下,我们习惯省略"super."
- 特殊情况:当子类和父类中定义了同名的属性时,我们要想在子类中调用父类中声明的属性,则必须显式的使用"super.属性"的方式,表明调用的是父类中声明的属性。
- 特殊情况:当子类重写了父类中的方法以后,我们想在子类的方法中调用父类中被重写的方法时,则必须显式的使用"super.方法"的方式,表明调用的是父类中被重写的方法。
- super调用构造器:
- 我们可以在子类的构造器中显式的使用"super(形参列表)"的方式,调用父类中声明的指定的构造器,"super(形参列表)"的使用,必须声明在子类构造器的首行!
- 我们在类的构造器中,针对于"this(形参列表)"或"super(形参列表)"只能二一,不能同时出现
- 在构造器的首行,没显式的声明"this(形参列表)“或"super(形参列表)”,则默认调用的是父类中空参的构造器:super()
- 在类的多个构造器中,至少一个类的构造器中使用了"super(形参列表)",调用父类中的构造器
package com.atguigu.java3;
//在本包下还有一个people.java文件
public class Student extends Person{
String major;
int id = 1002;//学号
public Student(){
super(); //父类构造器
}
public Student(String major){
super(); //构造器外加新构造器,都需要先super()或者this()一下
this.major = major;
}
public Student(String name,int age,String major){
// this.name = name;
// this.age = age;
super(name,age);
this.major = major;
}
@Override
public void eat() {
System.out.println("学生:多吃有营养的食物");
}
public void study(){
System.out.println("学生:学习知识");
this.eat(); //本类中的eat方法
super.eat(); //父类中的eat方法
walk(); //父类中继承过来的walk方法
}
public void show(){
System.out.println("name = " + name + ", age = " + age);
System.out.println("id = " + this.id);
System.out.println("id = " + super.id);
}
}
4、子类对象实例化过程
理解即可。
1.从结果上看:继承性
子类继承父类以后,就获取了父类中声明的属性或方法。
创建子类的对象,在堆空间中,就会加载所父类中声明的属性。
2.从过程上看:
当我们通过子类的构造器创建子类对象时,我们一定会直接或间接的调用其父类的构造器,进而调用父类的父类的构造器,…直到调用了java.lang.Object类中空参的构造器为止。正因为加载过所的父类的结构,所以才可以看到内存中父类中的结构,子类对象才可以考虑进行调用。
强调:虽然创建子类对象时,调用了父类的构造器,但是自始至终就创建过一个对象,即为new的子类对象。如下图。
二、多态性
1、概念
可以理解为一个事物的多种形态。对象的多态性表现为父类的引用指向子类的对象。
比如Person p = new Man();
,在栈空间的是p(父类),指向的却是堆空间的Man()对象(子类)。
2、多态的使用
在编译期,只能调用父类声明的方法;运行期,实际执行的是子类的重写。编译看左边,运行看右边。所以先有类的继承关系和方法的重写,才可能有多态。
注意:对象的多态性,只适用于方法,不适用于属性。
看下面例子,两个代码,man继承person类。
//person.java
package com.atguigu.java4;
public class Person {
String name;
int age;
int id = 1001;
public void eat(){
System.out.println("人:吃饭");
}
public void walk(){
System.out.println("人:走路");
}
}
//man.java
package com.atguigu.java4;
public class Man extends Person{
boolean isSmoking;
int id = 1002;
public void earnMoney(){
System.out.println("男人负责挣钱养家");
}
public void eat(){
System.out.println("男人多吃肉,长肌肉");
}
public void walk(){
System.out.println("男人霸气的走路");
}
}
package com.atguigu.java4;
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("*******************");
//对象的多态性:父类的引用指向子类的对象
Person p2 = new Man(); //内存中实际上加载了子类特有的属性和方法,但是变量被声明为父类,所以只能调用父类的属性和方法
// Person p3 = new Woman();
//多态的使用:当调用子父类同名同参数的方法时,实际执行的是子类重写父类的方法 ---虚拟方法调用
p2.eat();
p2.walk();
// p2.earnMoney(); //p2是person类,他无法调用man新加的方法
System.out.println(p2.id);//1001 //Person中的非Man中的
}
}
所以综上,Person p2 = new Man()
,p2是Person类型变量
- p2可以调用Person里面声明过的方法,但是实际用的时候是用的子类重写过的
- p2不可以调子类Man中新加的,Person中没有的方法
- p2调用的父子同名属性是父类中的属性,而非子类中的属性,编译运行都在左边。
- 所谓-编译看左边-是指在写代码的时候,如果写了p2.子类继承的某方法,点进去找到的是父类的方法,但是执行的时候还是子类重写过的方法。被重写的方法称为虚拟方法。
3、多态的用处:抽象类,接口
package com.atguigu.java4;
import java.sql.Connection;
//多态性的使用举例一:
public class AnimalTest {
public static void main(String[] args) {
AnimalTest test = new AnimalTest(); //自己new自己,调自己的方法,main作为入口
test.func(new Dog()); //匿名对象dog,同Dog d = new Dog()。这里匿名对象传入下面func的方法作为形参。因为eat和shout方法被重写,所以执行的不是animal的方法,而是dog重写后的方法。//狗吃骨头,汪汪汪
test.func(new Cat()); //执行猫重写后的方法 //猫吃鱼,喵喵喵
}
public void func(Animal animal){//Animal animal = new Dog();
animal.eat();
animal.shout();
if(animal instanceof Dog){
Dog d = (Dog)animal;
d.watchDoor();
}
}
//如果没有多态性,就只能造很多重载的方法
// 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{ // dog类,继承于animal,有重写eat,shout新建watchdoor方法
public void eat(){
System.out.println("狗吃骨头");
}
public void shout(){
System.out.println("汪!汪!汪!");
}
public void watchDoor(){
System.out.println("看门");
}
}
class Cat extends Animal{ //cat类,继承于animal,有重写eat和shout方法
public void eat(){
System.out.println("猫吃鱼");
}
public void shout(){
System.out.println("喵!喵!喵!");
}
}
//举例二:
class Order{
public void method(Object obj){ //形参是object,不管来什么对象,都有对应的重写过的方法,这样的话就不用一一定义各自的方法了。
}
}
//举例三:
class Driver{
//或者后面会有数据库,有mysql,oracle,db2,sqlserver,想用java连接并操作数据,在这之前需要先连接。Connection是父类,这里传入的不同数据库,都能有对应的方法
public void doData(Connection conn){//conn = new MySQlConnection(); / conn = new OracleConnection();
//规范的步骤去操作数据
// conn.method1(); //不同的数据库对应不同数据库的方法
// conn.method2();
// conn.method3();
}
}
4、多态周边:
- 上面,
Person p2 = new Man()
,p2为Person类型变量,可以调用父类的属性(父类的)和方法(被子类重写过的),无法调用子类特有的属性和方法。 - 即
Man m1 = p2
报错,左右类型不一样,可以Man m1 = (Man)p2
向下转型。但是可能出现classcastexception错误,比如p2定义为Woman w1 = (Woman)p1
p1指向的是Man,无法再定义为woman,定义错误。 - 引出
a instanceof A
,判断对象a是否是A的实例。如果不会出现classcast报错的话,instanceof返回true。比如
Person p2 = new Man();
p2 instanceof Man:True;
p2 instanceof Woman: False;
p2 instanceof Person: True;
//编译ok,运行不通过
Person p3 = new Woman();
Man m3 = (Man) p3 //女人变男人
Person p4 = new Person();
Man m4 = (Man) p4; //人转男人
//编译运行皆ok
Object obj = new Woman();
Person p = (Person) obj;
//编译不通过
Man m5 = new Woman();
String str = new Date();
Object o = new Date();
string str1 = (String) o;
5、多态面试题
1、谈谈你对多态性的理解:
- 实现代码通用性;
- Object类中定义的public boolean equals(Object obj){ }
JDBC:使用java程序操作(获取数据库连接、CRUD)数据库(MySQL、Oracle、DB2、SQL Server),不同的数据库作为形参放进去了就会有不同的方法对应上。 - 抽象类、接口的使用肯定体现了多态性。(抽象类、接口不能实例化)
2、多态是编译时行为还是运行时行为:运行时。
三、Object类的使用
1、java.lang.Object类的说明
是所有Java类的父类;类的声明中未使用extends关键字的,默认继承与Object类;
属性:无;
方法:equals() / toString() / getClass() / hashCode() / clone() / finalize() / wait() / notify() / notifyAll()
构造器:只一个空参构造器
2、equals()方法
只适用于引用数据类型
//比较两个对象的地址值是否相同,即两个引用是否指向同一个对象实体
public boolean equals(Object obj){
return (this == obj)
} //地址都相同,内容也一定相同
String,Data,File,包装类都重写了该equal()方法,所以功能不再是比较两个引用的地址是否相同,而是比较两个对象的实体(地址里存的值)是否相同。
通常情况下,我们自己写的类都会重新继承Object类,所以一般都需要重写。重写举例,以下代码是基本功,要求会默写。
class User{
String name;
int age;
public boolean equals(Object obj){
if(obj == this){
return true;
} //要是地址都一样,就不用比了,直接true
if(obj instanceof User){ //如果对象类型都不同,就直接到最后的false
User u = (User)obj;
//一一比较属性,直接放在return后面
return this.age == u.age && this.name.equals(u.name);
}
return false;
}
- 面试题 ==: 可以比较基本数据类型,也可以比较引用数据类型。
基本数据类型比较的是值;引用数据类型比较的是对象的地址值。使用时需要保证左右两边的变量类型一致。
Object类中的equals和 ==效果是相同的,但是其他的一些类String中重写后的equals()则比的是地址里的内容而不是地址值了。
3、toString()的使用
- 功能:返回类名和他的引用地址。(可以看一下Object中的定义源码)
public String toString(){
return getClass().getName() + "@" + Integer.toHexString(hashCode());
}
- 当输出一个对象的引用时,实际上调用的是当前对象的toString(),也即
syso(obj.toString())和syso(obj)输出结果一样
,因为system.out.println
中调用了toString
方法。 - 像String,Date,File,包装类都重写了Object类中的toString()方法,返回的是地址里的内容而不是地址了
- 自己重写该方法,返回对象的实体内容,如下
//自动实现,窗口source - generate toString - 选择关心的属性field还是方法method
@Override
public String toString() {
return "Customer [name=" + name + ", age=" + age + "]";
}
四、单元测试
-
步骤:
-
1.当前工程day14 - 右键选择:build path - add libraries - JUnit 4 - 下一步,生成一个软件库的包供测试调用。
-
2.创建Java包和类,JUnitTest.java,进行单元测试。
此时的Java类要求:① 此类是public的 ②此类提供公共的无参的构造器 -
3.此类中声明单元测试方法。
此时的单元测试方法:方法的权限是public,没返回值,没形参 -
4.此单元测试方法上需要声明注解:@Test,并在单元测试类JUnitTest.java中导入:import org.junit.Test;
-
5.声明好单元测试方法以后,就可以在方法体内测试相关的代码。写完代码以后,先左键双击单元测试方法名,右键:run as - JUnit Test
-
说明:
1.如果执行结果没任何异常:绿条
2.如果执行结果出现异常:红条
package com.atguigu.java2;
import java.util.Date;
import org.junit.Test;
public class JUnitTest { // 测试类需要是public的,并且有无参构造器,这里默认继承Object,所以有
int num = 10; //测试类也可以有自己的属性,并且调用该属性时,不像main中需要先创建对象再用,可以直接用-见下面的show函数里面
@Test
public void testEquals(){ //注意命名方式,testXXX测试某某某
String s1 = "MM";
String s2 = "MM";
System.out.println(s1.equals(s2));
//ClassCastException的异常
// Object obj = new String("GG");
// Date date = (Date)obj;
System.out.println(num);
show(); // 可以直接调用方法,不用实例化出一个对象调用
}
public void show(){
num = 20;
System.out.println("show()....");
}
@Test
public void testToString(){ //左键再右键,就可以只测试这一个方法里面的代码了,就不用全部整个java文件测试了
String s2 = "MM";
System.out.println(s2.toString());
}
}
实际开发中,只需要在某个java文件(即某一个public类)中
@Test
public void test1{ … }
这个时候Test报错,放上鼠标就直接自动添加JUnit包
五、包装类
也叫Wrapper,封装类。针对八种基本数据类型定义相应的引用数据类型。
- 下图中,除了int和char之外,其他的包装类都是直接把首字符大写即可。虚框中为数值型的,父类为Number
作用:比如之前的equals()函数,虽然理论上object类型的都可以传入,但是像int就传不进去。所以就有将基本数据类型放进Integer类中,这样就可以传进去了,才是真正的面向对象。 - 三种类型的转换:基本数据类型,String,包装类之间的转换。
public class Wrapper(){
//基本数据类型-->包装类
@Test
public void test1(){
int num1 = 10;
System.out.println(num1.toString()); //报错,因为num1是基本数据类型,不是某个对象,不能放入println函数中作为形参
Integer in1 = new Integer(num1); //先将基本数据类型作为形参传入Integer中
System.out.println(in1.toString());//10,Integer中的tostring被重写过了,输出地址中的值,所以输出10
Integer in2 = new Integer("123")
System.out.println(in2.toString());//123
Integer in3 = new Integer("123abc")
System.out.println(in1.toString());//报错,有非数字的内容
Float f1 = new Float("12.3");
Float f2 = new Float(12.3);
Float f3 = new Float(12.3f);
System.out.println(f1);//没有toString时,对应的重载函数会自动加toString,效果一样,输出f1,f1和f2和f3效果一样。
Boolean b1 = new Boolean(true); //true
Boolean b1 = new Boolean("true"); //true
Boolean b1 = new Boolean("True"); //true
Boolean b1 = new Boolean("true123"); //false
// 包装类转基本数据类型,调用包装类的xxxValue(),比如一些计算,包装类无法但是基本类型可以
@Test
public void test2(){
Integer in1 = new Integer(12);
int i1 = in1.intValue();
syso(i1+1) // 13
//新特性:自动装箱和自动拆箱
@Test
public void test3(){
int num1 = 10;
//基本数据类型-->包装类的对象
method(num1);
//自动装箱:基本数据类型 --->包装类
int num2 = 10;
Integer in1 = num2;//自动装箱,不用再new一个空间了
boolean b1 = true;
Boolean b2 = b1;//自动装箱,直接传即可
//自动拆箱:包装类--->基本数据类型
System.out.println(in1.toString());
int num3 = in1;//自动拆箱,不用intValue了
}
public void method(Object obj){
System.out.println(obj);
}
//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 = "true1";
boolean b1 = Boolean.parseBoolean(str2);
System.out.println(b1);//false
}
//基本数据类型、包装类--->String类型:调用String重载的valueOf(Xxx xxx)
@Test
public void test4(){
int num1 = 10;
//方式1:连接运算
String str1 = num1 + "";
//方式2:调用String的valueOf(Xxx xxx)
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);//"12.4",输出的是字符型
}
综上:
- 基本数据类型<—>包装类:JDK 5.0 新特性:自动装箱 与自动拆箱
- 基本数据类型、包装类—>String:调用String重载的valueOf(Xxx xxx)
- String—>基本数据类型、包装类:调用包装类的parseXxx(String s)
注意:转换时,可能会报NumberFormatException
面试题
public class InterviewTest {
@Test
public void test1() {
Object o1 = true ? new Integer(1) : new Double(2.0);
System.out.println(o1);// 1.0 ,:左右的类型一样,所以左侧的integer自动类型提升,所以是double型。
}
@Test
public void test2() {
Object o2;
if (true)
o2 = new Integer(1);
else
o2 = new Double(2.0);
System.out.println(o2);// 1,各论各的,所以输出1
}
@Test
public void test3() {
Integer i = new Integer(1);
Integer j = new Integer(1);
System.out.println(i == j);//false,==符号比得是地址,
//Integer内部定义了IntegerCache结构,IntegerCache中定义了Integer[],
//保存了从-128~127范围的整数。如果我们使用自动装箱的方式,给Integer赋值的范围在-128~127范围内时,可以直接使用数组中的元素,不用再去new了。目的:提高效率
//即超过这个范围的,都是需要重新new的,地址不同。在范围之内的,就保存在cache数组里,地址相同
Integer m = 1;
Integer n = 1;
System.out.println(m == n);//true,自动装箱且在范围内
Integer x = 128;//相当于new了一个Integer对象
Integer y = 128;//相当于new了一个Integer对象
System.out.println(x == y);//false,自动装箱且在范围外
}
}
应用场景
- Vector类中中添加元素,要变为形参传入addElement方法,思路:基本类型-包装类-多态(使用该数据类型对应的方法)
番外篇
- 快捷键 ctrl alt 下键:复制上面一段选中区域到下面
- 同一个包下,如果类中的属性权限是缺省的,则可以各个java文件之间相互调,public也是,private就不是了,只能java文件内部。
博学而笃志,切问而近思