|
6.4类的继承
1.扩展类
扩展=继承;
扩展之后的类有被扩展类的属性和方法;
被扩展类叫父类,扩展类叫子类;
关键字 extends
无论如何在使用继承的过程中要记得父类的不同访问权限
2.子类的实例化过程
class Person3
{
private String name; // 姓名
private int height; // 身高
private double weight; // 体重
public Person3()
{
System.out.println("我是父类的无参数的构造器");
}
public Person3(double weight,int height)
{
this.height=height;
this.weight= weight;
System.out.println("我是父类的有参数的构造器");
}
// 设定姓名
public void setName(String name)
{
if(name != null || !name.trim().equals(""))
{
this.name = name.trim();
}
}
// 取得姓名
public String getName()
{
if(name == null || name.trim().equals(""))
{
return "无名氏";
}
return name;
}
// 设定身高
public void setHeight(int height)
{
if(height >= 40 && height <= 260)
{
this.height = height;
}
}
// 取得身高
public int getHeight()
{
return height;
}
// 设定体重
public void setWeight(double weight)
{
if(weight >= 2 && weight <= 200)
{
this.weight = weight;
}
}
// 取得体重
public double getWeight()
{
return weight;
}
}
class Student extends Person3
{
private String StuId;
public Student()
{
super();//显式调用父类没有参数的构造器
}
public Student( int height,double weight)//这里的参数顺序可以和父类里面的参数顺序不一致
{
super(weight,height);
//这个里面的参数顺序必须和父类里面参数的顺序一致,
//如果想要调用父类的有参数的构造器就必须有显式调用,
//但是如果没有显式调用的话,后面创建该类的对象的时候
//即Student stu1 = new Student(130,175);会出错,
//换言之如果希望使用该语句,必须满足两个条件,1父类中有需要输入两个参数的构造器,2子类中有对该构造函数有显式调用的构造函数
//但是无论在子类Student中有没有对父类不带参数的构造函数进行显式调用
// Student stu = new Student();始终会调用父类中的不带参数的默认构造函数
//估计如果父类中没有人工定义构造器,该语句会调用系统配给的构造函数
}
public void setStuId(String StuId)
{
this.StuId = StuId;
}
public String getStuId()
{
return StuId;
}
}
public class TestStudent {
public static void main(String[] args)
{
Student stu = new Student();
Student stu1 = new Student(130,175);
stu.setName("li wang");\\调用父类方法
System.out.println("student'name is "+stu.getName());
stu.setStuId("123456");\\调用子类自己的方法
System.out.println("student'Id is "+stu.getStuId());
System.out.println("student1'height is "+stu1.getHeight());
System.out.println("student1'weight is "+stu1.getWeight());
}
}
3.重写父类方法
如果子类在继承父类后自己定义了一个方法,而该方法名和父类的某一个方法重名,而且参数个数和类型和父类同名方法一样,那么在子类对象调用这个重名的方法时,父类的方法将不再有效。
子类在重写父类方法时,可以更改返回类型(但是该返回类型必须是父类返回类型的子类)和方法体以及该方法的访问权限,但是修改访问权限时只能将该访问权限变得更加公开,而不能缩小访问权限。
class MotorCar
{
protected void brake()
{
System.out.println("woshi motorcar brake");
}
}
class Car extends MotorCar
{
public void brake()//这里改变了访问范围和方法体,返回值就没改了,函数名和参数不能变
{
System.out.println("woshi car brake");
}
}
public class TestRewrite {
public static void main(String[] args)
{
Car c = new Car();
c.brake();
}
}
4.Object类
如果定义一个类没有指定的类,那么这个类的父类就是Object类。
该类的常见方法 equals toString
该类中的equals在比较两个对象时,只有两个对象的引用值相等才会显示相等,就像前面关于非对象的比较时==的作用一样。
不同存储空间中对象相等的定义:书上说只要这两个对象包含的属性相同就可以了,我觉得这句话有问题,为什么在equels重写的时候必须重写hashcode
class Point
{
int x;
int y;
}
public class Testequals {
public static void main(String[] args)
{
Point p1 = new Point();
p1.x = 10;
p1.y = 20;
Point p2 = new Point();
p2.x = 10;
p2.y = 20;
System.out.println(p1.equals(p2));
System.out.println(p1.equals(p1));
}
}
输出结果:
false
True
改写equels
class Point
{
int x;
int y;
public boolean equals(Object obj)
{
Point p = (Point)obj;
if( p.x == this.x && p.y ==this.y)//这里有一点不理解,前面==不是只能比较引用么在字符串池和装箱那里,而equels比较对象的内容,在装箱那里也是这样说的
return true;
else
return false;
}
}
中间蓝色部分我把它剪短为这样运行结果也没有问题:
public boolean equals(Point obj)
{
if( obj.x == this.x && obj.y ==this.y)
return true;
else
return false;
}
对toString进行测试
public class TesttoString {
public static void main(String[] args)
{
Point p1 = new Point();
p1.x = 10;
p1.y = 20;
System.out.println(p1);
}
}
输出结果:
Point(类名)@11cfb549(hashcode)
改写
public String toString()
{
String info;
info= "x ="+x+",y ="+y;
return info;
}
6.5 多态 抽象类和接口
1.多态及其实现
当子类有重写父类方法时,将子类实例的引用赋予子类类型变量,调用重写函数是子类中的重写函数被调用,但如果将子类实例赋值给父类类型变量调用重写函数仍然是调用的子类中重写的函数而不是父类的。实现多态可以使用父类定义相关接口操作,具体操作则可以由子类来实现
package polytest;
class Student
{
public void introduce()
{
System.out.println("我是学生!");
}
}
class UniversityStudent extends Student
{
public void introduce()
{
System.out.println("我是da学生!");
}
}
class MiddleStudent extends Student
{
public void introduce()
{
System.out.println("我最小我是初中生!");
}
}
class Polymorphism//实现多态的类
{
public void SayMe(Student stu)//这里是父类作为参数
{
stu.introduce();
}
}
public class TestPoly {
public static void main(String[] args)
{
UniversityStudent us = new UniversityStudent();
MiddleStudent ms = new MiddleStudent();
Polymorphism p = new Polymorphism();
Student stu = new UniversityStudent();//子类实例赋值给父类变量
stu.introduce();//调用子类重写的函数
p.SayMe(us);
p.SayMe(ms);
}
}
2.基于抽象类实现多态
因为多态最终希望调用的是子类重写父类的函数,所以最好是父类不要成为参数传递进来,为了解决这里问题可以将父类定义为抽象类从而使父类只能被继承而无法生成实例。
抽象类:使用abstract修饰,可以包括或者不包括抽象类(只有用abstract修饰的方法才可以不带花括号及花括号里面的方法体),也可以包括实现了的方法体,里面什么都没有也可以,不过根据用途来看里面至少要有一个方法,abstract主要是让该类不可以实例化。
抽象类还可以用来定义两个模块之间调用的接口,以及用来设计“适配器”比较不懂还有就是如果该类没有被abstract修饰但是包括抽象方法算不算抽象类呢?
抽象方法:使用abstract修饰,只有访问类型;返回类型;方法名和参数。
Abstract只能修饰class或者未实现的方法,不可以修饰数据成员
abstract class student\\抽象类
{
abstract void introduce();\\抽象方法
}
3.基于接口实现多态
接口通过关键字interface 定义
接口中的方法不能有方法体,即一定是抽象的,但是方法不需要用abstract修饰;
接口中可以定义数据成员,但是接口中的数据成员将被视为常量
接口的定义
[public] interface 接口名称(习惯上接口名称以I开头)
{
[访问权限] 方法类型 方法1([参数列表]); //访问权限可以省略,如果省略方法的访问权限默认是公开的
}
接口必须被类实现之后才可以使用,类通过关键字implements指定要实现的接口,接口中声明的方法在类中必须全部实现,除非这个类被声明为抽象的(“这个类”是指的实现接口的类)
如果接口只是用来定义一些常量,那么使用这些常量的类没有必要实现这个接口,需要使用接口中的常量时,以”接口名.常量名”的形式使用就可以了.
package polytest;
interface IStudent
{
String ID = "2345";//形式上ID是变量实际上是常量,不需要用final修饰的常量
public void introduce();
}
class UniversityStudent implements IStudent
{
public void introduce()//实现接口的方法必须是public的
{
System.out.println("我是da学生!我的学号是"+ID);
}
}
class MiddleStudent implements IStudent
{
public void introduce()
{
System.out.println("我最小我是初中生!");
}
}
class Polymorphism//实现多态的类
{
public void SayMe(IStudent stu)
{
stu.introduce();
}
}
public class TestPoly {
public static void main(String[] args)
{
UniversityStudent us = new UniversityStudent();
MiddleStudent ms = new MiddleStudent();
Polymorphism p = new Polymorphism();
p.SayMe(us);
p.SayMe(ms);
}
}
4.实现接口和类之间的适配器
因为如果要使用接口就需要将接口中所有的方法进行实现,这在不需要使用接口所有方法的情况下是很麻烦的,所以有了适配器;
适配器是一个实现接口的抽象类,在这个类中对接口的所有方法都进行了简单的实现,当编程者需要实现相关接口是,继承这个抽象类就可以了.
适配器中不能有抽象类,同时适配器中的方法都是空的,为了避免实例化,所以通常将适配器声明为抽象类.
interface IWelcome
{
void sayHello();
void sayMorning();
void sayNight();
}
abstract class WelcomeAdapter implements IWelcome
{
public void sayHello(){};
public void sayMorning(){};
public void sayNight(){};
}
class WelcomeChinese extends WelcomeAdapter
{
public void sayMorning()
{System.out.println("早上好~");}
}
public class TestAdapter {
public static void main(String[] args)
{
WelcomeChinese wc = new WelcomeChinese();
wc.sayMorning();
}
}
6.6 包
1.Java的管理机制
个人觉得包就可以直接作为文件夹来理解,因为在程序执行之后就会生成相应的文件夹;
解决了同名文件和同名类的问题;
明确指明代类所属的包需要关键字 package;
含有该关键字的代码必须是源文件的第一行正式代码(前面可以有注释或者是空白行),而且该语句只能出现一次,存储时包名和目录对应,类文件存储于与包同名的目录之下
命名规则:首先包名都用小写字母,其次为了避免命名冲突建议包名使用开发者或者开发者所在公司的或组织的域名(前提是这些名称不包括字母以外的字符?还是只是不能包含数字?只能包括大小写字母和点,而且点不能作为开头或者结尾)反相命名。如果作者拥有域名nihao.com,则包名可为com.nihao,同时还可以在这个包名下继续使用子包,如果将学习代码均放在子包edu中,那么完整包名为com.nihao.study
2.导入其他包中的类
如果想要使用其他包中的类,就必须在使用时给出完整的包名或者在调用之前使用import进行导入
方法 | 优点 | 缺点 | 备注 |
Import 时写出该包中所有需要使用到的类 | 编译效率比使用通配符的高 有利于代码的维护,知道使用了那些别的包中的类和这些类的来源 程序效率两者一样 |
|
|
Import时使用通配符“*” | 可以一次表示多个类 |
| 表示这个包下的所有公共类都可以在当前类中被使用,但是不包含子包中的类 |
在调用相关类的时候明确给出相关包的名称 | 代码太长了 |
|
|
准备用来导入的类:
package usingpackage;
public class Hello {
public void getword()
{
System.out.println("Hello, java! Hello new day!");
}
}
使用import:
import usingpackage.Hello;
public class Testimport {
public static void main(String[] args)
{
Hello h = new Hello();
h.getword();
}
}
不使用import:
public class Testimport {
public static void main(String[] args)
{
usingpackage.Hello h = new usingpackage.Hello();
h.getword();
}
}
3.类成员访问权限控制补充及类的的访问权限
类的访问权限:
在定义类的访问权限时 有默认和public
默认的类只能在包内进行访问,也就是说无法使用import等语句将其给包外的类使用
Public定义的类可以被保外进行访问,而且定义为public的类所在的源码文件的文件名必须和该public类的类名相一致,所以一个源码文件里面只能有一个被定义为public的类
6.7 内嵌类
1.成员内嵌类
定义于类中的类,也可以设定其访问权限
class Book
{
public class Chapter
{
public String title;
public String content;
}
private Chapter[] chapterList;
public Book(int chapterNum){
chapterList = new Chapter[chapterNum];
}
public void setChapter(int chapterNum, String title, String content)
{
if(chapterNum>0&&chapterNum<chapterList.length )
{
chapterList[chapterNum-1].title = title;
chapterList[chapterNum-1].content = content;
}
}
public String gettChapterTitle(int chapterNum)
{
if(chapterNum>0&&chapterNum<chapterList.length )
{
return chapterList[chapterNum-1].title;
}
else
{
return " ";
}
}
public String gettChapterContent(int chapterNum)
{
if(chapterNum>0&&chapterNum<chapterList.length )
{
return chapterList[chapterNum-1].content;
}
else
{
return " ";
}
}
}
public class MakeInnerClass {//因为在上面的代码中Chapter的访问权限是公开的所以可以在外部对其进行定义,如果在上述Chapter前加上static,就可以直接用Book.Chapter chapter = new Book.Chapter;来产生内嵌类实例,(个人感觉)但是作为内嵌类一般定义为默认会比较好
public static void main(String[] args)
{
Book book = new Book(3);
Book.Chapter chapter = book.new Chapter();//这里是用外部类的实例来定义新的内部类,而且new是在book的后面
}
}
2.局部内嵌类
定义于方法中的类,不能设定其访问权限,该类可以访问该类所在方法中以final修饰的常量,但是不能访问其中的变量.
典型应用:图形界面编程时为相关组件注册监听器事件
class LocalInnerClass
{
// 外部类方法
public void fun()
{
// 内嵌类
class InnerClass
{
void dsp(String info)
{
System.out.println(info);
}
}
// 产生内嵌类实例
InnerClass inn = new InnerClass();
// 调用内嵌类方法
inn.dsp("路漫漫其修远兮,吾将上下而求索。");
}
// 主方法
public static void main(String[] args)
{
LocalInnerClass lic = new LocalInnerClass();
lic.fun();//总感觉这样调用就直接输出了很神奇……
}
}
3.匿名内嵌类
没有名字的内嵌类叫做匿名内嵌类,一般该类只需要产生一个实例并且只使用这个实例一次.
其定义格式是在new操作符后跟上匿名内嵌类的父类或者接口的构造器,然后在构造器后使用用一对花括号将匿名内嵌类的代码包含进来即可
只能重写父类的方法,而不能添加新的可供内嵌类外部使用的的方法;
如果在匿名类中定义了父类中所没有的方法,语法上是没有问题,但是只能在类内部被访问,因为匿名类产生的实例是赋值给父类的变量,父类是没有办法调用匿名类中新定义的方法的。
(比较粗略的浏览,只是把代码看懂)
package com.yanzhijun.edu;
// 接口
interface IDspInfo
{
void dsp(String info);
}
// 包含局部内嵌类的类
class AnonymityInnerClass
{
// 外部类方法
public void fun()
{
// 定义匿名内嵌类并产生实例
IDspInfo inn = new IDspInfo()
{
public void dsp(String info)
{
System.out.println(info);
}
};
// 调用内嵌类方法
inn.dsp("路漫漫其修远兮,吾将上下而求索。");
}
// 主方法
public static void main(String[] args)
{
AnonymityInnerClass aic = new AnonymityInnerClass();
aic.fun();
}
}
习题
1.建立定义教工,员工,学生对象的类,体现面向对象思想//不太会体现TAT
class Person
{
String name;
String IDnumber;
String status;
public Person(String status)
{
name ="";
IDnumber ="";
this.status = status;
}
public void setName(String name)
{
this.name = name;
}
public String getName()
{
return name;
}
public void setIDnumber(String IDnumber)
{
this.IDnumber = IDnumber;
}
public String getIDnumber()
{
return IDnumber;
}
public String getStatus()
{
return status;
}
public void selfIntroducttion()
{
System.out.println("my name is "+ getName()+", and my IDnumber is"+getIDnumber()+",and my status is"+getStatus());
}
}
class Student extends Person
{
public Student(String status)
{
super(status);
}
}
class Personnel extends Person
{
public Personnel(String status)
{
super(status);
}
}
class Teacher extends Person
{
public Teacher(String status)
{
super(status);
}
}
public class Question6_1 {
public static void main(String[] args){
Student st = new Student("我是学生");
st.selfIntroducttion();
Personnel pe = new Personnel("我是职工");
pe.selfIntroducttion();
Teacher te = new Teacher("我是老师");
te.selfIntroducttion();
}
}
2.编写MyMath类,其中实现名为mod的重载方法,它们以完成对long或者int型数据求余运算
class MyMath
{
public static long mod(long a, long b)
{
long c;
c = a%b;
return c;
}
public static int mod(int a, int b)
{
int c;
c = a%b;
return c;
}
}
public class Question6_2 {
public static void main(String[] args)
{
int a=4,b=3;
long c = 9, d = 5;
System.out.println("int a mod b ="+MyMath.mod(a,b));
System.out.println("long c mod d ="+MyMath.mod(c,d));
}
}
3.为第一题中编写的学生类重写判断两个学生是否相等的equals方法
class Student extends Person
{
public Student(String status)
{
super(status);
}
public boolean equals(Student stu)
{
if (this.name == stu.name&&this.IDnumber == stu.IDnumber)
return true;
else
return false;
}
}
public class Question6_3 {
public static void main(String[] args){
Student st = new Student("我是学生");
st.setName("wangfang");
st.setIDnumber("123456");
Student st1 = new Student("我是学生");
st1.setName("yuanjun");
st1.setIDnumber("223456");
Student st2 = new Student("我是学生");
st2.setName("wangfang");
st2.setIDnumber("123456");
System.out.println("st equals st1? "+st.equals(st1));
System.out.println("st equals st2? "+st.equals(st2));
}
}
小黑屋|手机版|Archiver|鱼C工作室 ( 粤ICP备18085999号-1 | 粤公网安备 44051102000585号)
GMT+8, 2024-5-17 11:31
Powered by Discuz! X3.4
© 2001-2023 Discuz! Team.