为什么要封装?封装的作用和含义
我们程序设计追求“高内聚,低耦合”
隐藏对象内部的复杂性,只对外公开简单的接口。便于外界调用,从而提高系统的可扩展性、可维护性。通俗的说,把该隐藏的隐藏起来,该暴露的暴露出来。这就是封装的设计思想
。
class Animal {
public int legs;
public void eat(){
System.out.println("Eating");
}
public void move(){
System.out.println("Moving.");
}
}
public class Zoo {
public static void main(String args[]) {
Animal xb = new Animal();
xb.legs = 4;
System.out.println(xb.legs);
xb.eat();
xb.move();
}
}
/**
* 一、面型对象的特性一:封装与隐藏
* 当我们创建一个对象的类以后,我们可以通过 “对象.属性” 的方式,对对象的属性进行赋值。
* 这里,赋值的操作要受到属性的数据类型和存储范围的制约。除此之外,没有其他制约条件。
* 但是,在实际问题当中,我们往往需要给属性赋值加入额外的限制条件,这个条件就不能在属性声明时体现,我们只能通过方法进行限制条件的添加。
* 比如 setLegs(),同时我们需要避免用户再使用 “对象.属性” 的方式进行赋值。则需要将属性声明为私有的(private)
* 此时针对于属性就体现了封装性
*
* 二、封装性的体现
* 我们将类的属性私有化(private),同时,提供公共的方法来获取(getXxx)和设置(setXxx)
*
* 拓展:封装性的体现有很多
* (1) 如上
* (2)不对外暴露的私有的方法
* (3)单例模式 ...
*
* 三、封装性的体现,需要权限修饰符来配合
* 1、Java规定的4种权限:private、缺省、protected、public
* 2、4种权限可以用来修饰类及类的内部结构:属性、方法、构造器、内部类
* 修饰类的话只能用public、缺省
*
* 总结封装性:Java提供了4种权限修饰符来修饰类以及类的内部结构,体现类及类的内部结构在被调用时的可见性的大小。
*/
public class AnimalTest {
public static void main(String[] args) {
Animal animal = new Animal();
animal.name = "大黄";
// animal.age = 2; //'age' has private access
animal.setAge(2);
// animal.legs = 4; //'legs' has private access
animal.setLegs(4);
animal.show();
// animal.legs = -4;
animal.setLegs(-4);
animal.show();
int legs = animal.getLegs();
System.out.println(legs);
}
}
class Animal{
String name;
private int age;
// int legs; //腿的个数
private int legs; //腿的个数
//对属性的设置
public void setLegs(int l){
if (l >= 0 && l % 2 == 0){
legs = l;
}else {
legs = 0;
}
}
//对属性的获取
public int getLegs(){
return legs;
}
//提供age的get和set方法
public int getAge(){
return age;
}
public void setAge(int a){
if (age >= 0){
age = a;
}else {
age = 0;
}
}
public void eat(){
System.out.println("动物进食");
}
public void show(){
System.out.println("[ name = " + name + ", age = " + age + ", legs = " + legs + " ]" );
}
}
Java权限修饰符public、protected、private置于类的成员
定义前,用来限制对象对该类成员的访问权限。
修饰符 | 类内部 | 同一个包 | 不同包的子类 | 同一个工程 |
---|---|---|---|---|
private | yes | |||
(缺省) | yes | yes | ||
protected | yes | yes | yes | |
public | yes | yes | yes | yes |
对于class的权限修饰只可以用public和default(缺省)
创建对象;给对象进行初始化
如:Order o = new Order(); Person p = new Person(“Peter”,15);修饰符 类名(参数列表) {
初始化语句;
}
构造器重载使得对象的创建更加灵活,方便创建各种不同的对象。
public class Person{
public Person(String name, int age, Date d) {this(name,age);…}
public Person(String name, int age) {…}
public Person(String name, Date d) {…}
public Person(){…}
}
/**
*
* 总结:属性赋值的先后顺序
* (1)默认初始化
* (2)显示初始化
* (3)构造器初始化
* (4)通过 “对象.方法” 或者 “对象.属性”方式进行赋值
*
* 以上操作的先后顺序 (1) (2) (3) (4)
*/
public class UserTest {
public static void main(String[] args) {
User user = new User();
int age1 = user.getAge();
System.out.println("age: " + age1);
user.setAge(100);
int age2 = user.getAge();
System.out.println("age: " + age2);
}
}
class User{
String name;
int age = 1;
public User(){
age = 2;
}
public int getAge() {
return age;
}
public void setAge(int a) {
age = a;
}
}
JavaBean是一种Java语言写成的可重用组件。
所谓JavaBean,是值符合如下标准的Java类:
用户可以使用JavaBean将功能、处理、值、数据库访问和其他任何可以用Java代码创造的对象进行打包,并且其他的开发者 可以通过内部的JSP页面、Servlet、其他JavaBean、applet程序或者应用来使用这些对象。用户可以认为JavaBean提供了一种随时随地的复制和粘贴的功能,而不用关心任何改变。
示例:
/**
*
* JavaBean是一种Java语言写的可重用的组件
* 所谓JavaBean,是指符合以下标准的Java类
* (1)类是公共的
* (2)有一个无参的公共的构造器
* (3)有属性,且有对应的get、set方法
*/
public class Custom {
private int id;
private String name;
public int getId() {
return id;
}
public Custom() {
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
this可以用来修饰、调用:属性、方法、构造器
this理解为当前对象 或 当前正在创建的对象
使用 “this(形参列表)” 方式,调用本类中指定的其它构造器。
构造器中不能通过 “this(形参列表)” 方式调用自己
如果一个类中有n个构造器,则最多有 n-1 个构造器中使用了 “this(形参列表)”
规定:“this(形参列表)” 必须声明在当前构造器的首行。
构造器内部,最多只能声明一个 “this(形参列表)” 方式,用来调用其他构造器。
public class PersonTest {
public static void main(String[] args) {
}
}
class Person{
private String name;
private int age;
public Person() {
}
public Person(String name){
this(); // 调用本类中的无参构造器
this.name = name ;
}
public Person(String name, int age) {
this(name) ; // 调用有一个参数的构造器
this.name = name;
this.age = age;
}
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;
}
}
.
一次,就代表一层文件目录1. java.lang----包含一些Java语言的核心类,如String、Math、Integer、 System和Thread,提供常用功能
2. java.net----包含执行与网络相关的操作的类和接口。
3. java.io ----包含能提供多种输入/输出功能的类。
4. java.util----包含一些实用工具类,如定义系统特性、接口的集合框架类、使用与日期日历相关的函数。
5. java.text----包含了一些java格式化相关的类
6. java.sql----包含了java进行JDBC数据库编程的相关类/接口
7. java.awt----包含了构成抽象窗口工具集(abstract window toolkits)的多个类,这些类被用来构建和管理应用程序的图形用户界面(GUI)。
B/S C/S
可以使用 “xxx.*” 的方式可以导入xxx这个包下的所有结构
/**
*
*/
//import pack1.pack2.*;表示引入pack1.pack2包中的所有结构
import pack1.pack2.Test;
public class PackTest{
public static void main(String args[]){
//Test类在pack1.pack2包中定义
Test t = new Test();
t.display();
}
}
如果使用的类或接口是 java.lang 包下定义的,则可以省略import结构
如果使用的类或接口是本包下定义的,则也可以省略import结构
使用 “xxx.*” 方式表明可以调用xxx包下的所有结构,但是如果使用的是xxx子包下的结构,则仍需要显式的导入
import static: 导入指定类或接口中的静态结构 属性和方法
import static java.lang.System.*;
public class importDemo {
public static void main(String[] args) {
out.print("测试import");
}
}
class Subclass extends SuperClass {}
子类继承了父类,就继承了父类的方法和属性。
在子类中,可以使用父类中定义的方法和属性,也可以创建新的数据和方法。
在Java 中,继承的关键字用的是“extends”,即子类不是父类的子集,而是对父类的“扩展”。
子类不能直接访问父类中私有的(private)的成员变量和方法。
class SubDemo extends Demo{ } //ok
class SubDemo extends Demo1,Demo2...//erro
/**
*
* 面向对象的特征之二:继承性
*
* 一、继承的好处 why?
* (1)减少了代码的冗余,提高了代码的复用性
* (2)便于功能的扩展
* (3)为之后的多态性提供了前提
*
* 二、继承性的格式:class A extends B{}
* A:子类、派生类、subclass
* B:父类、超类、superclass
*
* 2.1 体现:一旦子类A继承了父类B以后,子类A就获取了父类B中声明的结构:属性和方法
* 特别的:父类声明为private的属性和方法,子类继承父类之后,仍然认为获取了父类中的私有结构。只是因为封装性的影响,使得子类不能直接调用父类的结构而已。
*
* 2.2 子类继承父类之后,还可以声明自己特有的属性或方法,实现功能的拓展。
* 子类和父类的关系,不同于子集和集合的关系
* extends:延展、扩展
*
* 三、Java中关于继承性的规定
* (1)一个类可以被多个子类继承
* (2)Java中类的单继承性:一个类只能有一个父类
* (3)子父类是相对的概念
* (4)子类直接继承的父类,称为直接父类。间接继承的父类,间接父类。
* (5)子类继承父类以后,就获取了直接父类以及所有间接父类中声明的属性和方法
*
* 四、1、如果我们没有显式的声明一个类的父类的话,则此类继承于java.lang.Object类
* 2、所有的java类(除java.lang.Object类之外)都直接或间接的继承于java.lang.Object类
* 3、意味着,所有的java类都具有java.lang.Object类声明的功能
*
*/
public class ExtendTest {
public static void main(String[] args) {
Person person = new Person();
person.setName("张三");
person.age = 28;
person.eat();
Student student = new Student();
student.setName("李四");
student.age = 30;
student.major = "计算机";
student.eat();
student.study();
student.breath(); //间接父类中的方法
Creature creature = new Creature();
}
}
在子类中可以根据需要对父类中继承来的方法进行改造,也称为方法的重置、覆盖
。在程序执行时,子类的方法将覆盖父类的方法。
子类重写的方法必须和父类被重写的方法具有相同的方法名称、参数列表
返回值类型
子类重写的方法使用的访问权限不能小于父类被重写的方法的访问权限(多态)
子类方法抛出的异常不能大于被重写方法的异常
子类与父类中同名参数的方法必须同时声明为非static的(即为重写),或者同时声明为static的(不是重写)。因为static方法属于类的,子类无法覆盖父类的方法
static修改的属于类的方法,不能被重写
/**
*
* 方法的重写(override / overwrite)
* 1、重写:子类继承父类以后,可以对父类中同名同参数的方法,进行覆盖操作
* 2、应用:重写以后,当创建子类对象以后,通过子类对象调用字父类中的同名同参数的方法时,实际执行的是子类重写父类的方法。
* 3、重写的规定:
* 方法的声明:权限修饰符 返回值类型 方法名(形参列表) throws 异常的类型 {方法体}
* 约定俗称:子类中的叫重写的方法,父类中的叫被重写的方法
* (1)子类重写的方法的方法名和形参列表与父类被重写的方法的方法名和形参列表相同。
* (2)子类重写的方法的权限修饰符不小于父类被重写的方法的权限修饰符
* 特殊情况:子类不能重写父类中声明为private权限的方法
* (3)返回值类型
* 父类被重写的方法返回值类型是void,则子类重写的方法的返回值类型只能是void
* 父类被重写的方法返回值类型是A类型,则子类重写的方法的返回值类型可以是A类或者A类的子类型
* 父类被重写的方法返回值类型是基本数据类型,则子类重写的方法的返回值类型必须是相同的基本数据类型
* (4)子类重写的方法抛出的异常类型不大于父类被重写的方法抛出的异常类型
*
* 子类和父类中同名同参数的方法,要么都声明为非static的(考虑重写),要么都声明为static的(不是重写)
*
*
* 面试题:区分方法的重载与重写
*/
public class StudentTest {
public static void main(String[] args) {
Student student = new Student("计算机技术");
student.setName("张三");
student.setAge(18);
student.walk(10);
student.study();
student.eat();
}
}
当子类和父类中定义了同名的属性时
,我们要想在子类中调用父类中声明的属性,则必须显示的使用“super.属性”的方式,声明调用的是父类中声明的属性。当子类重写了父类中的方法以后
,我们想在子类的方法中调用父类中被重写的方法时,则必须显示的使用“super.方法”的方式,表名调用的是父类中被重写的方法。必须声明在子类构造器的首行
则默认调用的是父类中空参的构造器“super()”
/**
*
* super关键字的使用
* 1、super理解为:父类的
* 2、super可以用来调用属性、方法、构造器
* 3、super的使用
* (1)我们可以在子类的方法或构造器中,通过使用 “super.属性” 或 “super.方法” 的方式,显示的调用父类中声明的属性或方法。
* 但是通常情况下,我们习惯省略 “super”
* (2)特殊情况:当子类和父类中定义了同名的属性时,我们想要在子类中调用父类声明的属性,则必须显示的使用 “super.属性” 的方式,表明调用的是父类中声明的属性。
* (3)特殊情况:当子类重写了父类的方法以后,我们想要在子类中调用父类中被重写的方法时,必须显式的使用 “super.方法” 的方式,表明调用的是父类中被重写的方法。
*
* 4、super调用构造器
* (1)我们可以在子类的构造器中显式的使用 “super(形参列表)” 的方式,调用父类中声明的指定的构造器
* (2)“super(形参列表)” 的使用,必须声明在构造器的首行
* (3)我们在类的构造器器中,针对“this(形参列表)” 和 “super(形参列表)” 只能二选一
* (4)在构造器的首行,没有显式的声明 “this(形参列表)” 和 “super(形参列表)”,则默认调用的是父类中空参的构造器 “super()”
* (5)在类的多个构造器中,至少有一个类的构造器中使用了“super(形参列表)”,调用父类中的构造器
*/
public class CylinderTest {
public static void main(String[] args) {
// Cylinder cylinder = new Cylinder();
Cylinder cylinder = new Cylinder(1,2);
double area = cylinder.findArea();
System.out.println("圆柱的表面积: " + area);
double volume = cylinder.findVolume();
System.out.println("圆柱的体积: " + volume);
}
}
public class Circle {
private double radius;
public Circle() {
System.out.println("Circle()");
radius = 1;
}
public Circle(double radius) {
System.out.println("Circle(double radius)");
this.radius = radius;
}
public void setRadius(double radius){
this.radius = radius;
}
public double getRadius() {
return radius;
}
/**
* 计算圆的面积
* @return 返回圆的面积
*/
public double findArea(){
return Math.PI * Math.pow(radius, 2);
}
}
public class Cylinder extends Circle {
private double length;
public Cylinder() {
// super(); //默认调用
length = 1;
}
public Cylinder(double length) {
this.length = length;
}
public Cylinder(double radius, double length) {
super(radius);//super调用父类的构造器
this.length = length;
}
public double getLength() {
return length;
}
public void setLength(double length) {
this.length = length;
}
/**
* 计算圆柱的表面积
* @return
*/
public double findArea() {
// return Math.PI * Math.pow(getRadius(), 2) * 2 + 2 * Math.PI * getRadius() * length;
return super.findArea() * 2 + 2 * Math.PI * getRadius() * length; //super调用父类的方法
}
/**
* 计算圆柱的体积
* @return 返回圆柱的体积
*/
public double findVolume(){
// return Math.PI * Math.pow(getRadius(), 2) * length;
return super.findArea() * length; super调用父类的方法
}
}
区别点 | this | super |
---|---|---|
访问属性 | 访问本类中的属性,如果本类没有此属性则从父类中继续查找 | 直接访问父类中的属性 |
调用方法 | 访问本类中的方法,如果本类没有此方法则从父类中继续查找 | 直接访问父类中的方法 |
调用构造器 | 调用本类构造器,必须放在构造器的首行 | 调用父类构造器,必须放在子类构造器的首行 |
super(...) //父类构造函数
..... //当前类构造函数语句
同样的道理,当一个类中有多个构造函数的时候,在其中一个构造函数中也可以先调用其他的构造函数来初始化对象,这种方法叫做“显式构造方法调用”,当那样的构造方法被调用,它将执行通常的super() 过程以及后续的操作。
然后在执行本构造函数中的构造语句,这个时候的顺序是这样的:
this(....) //当前类的其他构造函数
... //当前构造函数的其他语句
+ 为什么super(…)或this(…)调用语句只能作为构造器中的第一句出现?
解答:无论通过哪个构造器创建子类对象,需要保证先初始化父类。目的:当子类继承父类后,“继承”父类中所有的属性和方法,因此此子类有必要知道父类如何为对象进行初始化。
多态性,是面向对象中最重要的概念,在Java中的体现:对象的多态性:父类的引用指向子类的对象
Java引用变量有两个类型:编译时类型和运行时类型
。编译时类型由声明该变量时使用的类型决定,运行时类型由实际赋给该变量的对象决定。简称:编译时,看左边;运行时,看右边。
若编译时类型和运行时类型不一致,就出现了对象的多态性(Polymorphism)
“看左边”
:看的是父类的引用(父类中不具备子类特有的方法)“看右边”
:看的是子类的对象(实际运行的是子类重写父类的方法)Person e = new Person();
e.getInfo();
Student e = new Student();
e.getInfo();
子类中定义了与父类同名同参数的方法,在多态情况下,将此事父类的方法称为虚拟方法,父类根据赋给它的不同子类对象,动态调用属于子类的该方法。这样的方法调用在编译器是无法确定的。
Person e = new Student();
e.getInfo();//调用Student类的getInfo()方法
编译时e为Person类型,而方法的调用是在运行时确定的,所以调用的是Student类的getInfo()方法。————————动态绑定
①类的继承关系 ②方法的重写
前提:Person类中定义了welcome()方法,各个子类重写了welcome()。
执行:多态的情况下,调用对象的welcome()方法,实际执行的是子类重写的方法。
重载,是指允许存在多个同名方法,而这些方法的参数不同。编译器根据方法不同的参数表,对同名方法的名称做修饰。对于编译器而言,这些同名方法就成了不同的方法。它们的调用地址在编译期就绑定了
。Java的重载是可以包括父类和子类的,即子类可以重载父类的同名不同参数的方法。
所以:对于重载而言,在方法调用之前,编译器就已经确定了所要调用的方法,这称为**“早绑定”或“静态绑定”
**;
而对于多态,只有等到方法调用的那一刻,解释运行器才会确定所要调用的具体方法,这称为**“晚绑定”或“动态绑定”
**。
引用一句Bruce Eckel的话:“不要犯傻,如果它不是晚绑定,它就不是多态。
”
x instanceof A:检验x是否为类A的对象,返回值为boolean型
要求x所属的类与类A必须是子类和父类的关系,否则编译错误。
如果x属于类A的子类B,x instanceof A值也为true。
public class Person extends Object {…}
public class Student extends Person {…}
public class Graduate extends Person {…}
-------------------------------------------------------------------
public void method1(Person e) {
if (e instanceof Person)
// 处理Person类及其子类对象
if (e instanceof Student)
//处理Student类及其子类对象
if (e instanceof Graduate)
//处理Graduate类及其子类对象
}
基本数据类型的Casting
long g=20;
double d=12.0f
float f=(float)12.0;
int a=(int)1200L
练习
public class ParentClass {
int num = 10;
public void say() {
System.out.println("----------父类输出----------");
}
}
public class SonClass extends ParentClass {
int num = 5;
public void say() {
System.out.println("----------子类输出----------");
}
}
public class ClassDemo {
public static void main(String[] args) {
ParentClass parentClass = new SonClass();
//父类的变量
System.out.println("parentClass.num = " + parentClass.num);
//子类的方法
parentClass.say();
}
}
public class Person {
...
}
等价于
public class Person extends Object {
...
}
例如:
method(Object obj){…} //可以接收任何类作为其参数
Person o=new Person();
method(o);
方法名称 | 类型 | 描述 |
---|---|---|
public Object() | 构造 | 构造器 |
public boolean equals(Object obj) | 普通 | 对象比较 |
public int hashCode() | 普通 | 取得Hash码 |
public String toString() | 普通 | 对象打印时调用 |
int a=5; if(a==6){…}
引用类型比较引用(是否指向同一个对象):只有指向同一个对象时,==才返回truePerson p1=new Person();
Person p2=new Person();
if (p1==p2){…}
用“==”进行比较时,符号两边的数据类型必须兼容(可自动转换的基本数据类型除外),否则编译出错public boolean equals(Object obj) {
return (this == obj);
}
说明:Object类中定义的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”。
面试题:== 和equals的区别
Date now = new Date();
System.out.println("now=" + now);
相当于
System.out.println("now=" + now.toString());
s1 = "hello";
System.out.println(s1);//相当于System.out.println(s1.toString());
int a= 10;System.out.println("a=" + a);