java基本特征(封装、继承、多态)
来源:互联网 发布:ipadhd软件 编辑:程序博客网 时间:2024/05/16 11:20
JavaSE知识总结
1.面向对象之封装、继承、多态
Java是一种面向对象设计的高级语言,支持继承、封装和多态三大基本特征,首先我们从面向对象两大概念:类和对象(也称为实例)谈起。来看看最基本的类定义语法:
/*命名规则:*类名(首字母大写,多个单词组合时每个单词首字母大写,单词之间不加任何连接符号)*字段名、方法名(首字母小写,多个单词组合时第一个单词首字母小写,之后每个单词首字母大写,单词之间不加任何连接符号)*/[public][final] 类名 { [public|protected|private 类名() {}] //构造器 [public|protected|private][static|final] 类型 字段名 [=默认值];//字段列表 [public|protected|private][static|final|abstract] 返回值 方法名(参数) {} //方法列表}
在上面的基本语法中,我们发现以下几个知识点:
- 构造器名称为类名相同且没有任何返回值(甚至都不能返回void)
- 字段可以添加默认值
- 字段与方法都可以使用static进行修饰
- 类的修饰符要么为public,要么没有
- 方法的修饰符中final和abstract不能同时使用
此处,我将会着重介绍前面三条,后面两条我会在后面给大家总结出来。
首先是为什么不构造器没有任何返回值?
java中当使用new关键字来调用构造器时是有返回值的,总是返回当前的实例对象,因此无需定义返回值类型。 当我们利用构造器来实例化对象的时候,如果没有提供构造器,系统会隐式的为我们提供一个无参构造器,如果我们自己提供了一个构造器,那么系统将不会为我们提供构造器,所以当我们自己提供一个带参构造器的时候,最好手动写上无参构造器。
但是请注意了:虽然构造器负责创建对象,但是它不全是为了创建对象而存在的,实际上当调用构造器时,系统会为创建的对象分配内存空间并执行默认化操作,即在构造器执行之前,对象就已经产生,只是不能被访问,只能在构造器中用this来引用,当构造器执行完成之后,当前这个对象才能被外界访问。
不能理解我在说什么?好,我给大家举一个例子来加深大家对该知识的理解。
public class Encapsulation { public static void main(String[] args) { Person p=new Person(); System.out.println(p.getName()); }}/**Person类*/class Person{ private String name="qinling"; private int age=23; public String getName() { return name; } public Person(){ System.out.println("构造器中的对象"+this); }}
执行后,大家会发现在person的构造器中能够给输出当前对象,说明在构造器执行之前已经存在这个对象了,new对象的时候只是将这个对象返回给使用者。
下面给大家介绍一下类的存储关系,还是上面的例子,我们给出如下的关系图:
从上面可以看出,
对引用类型来说,堆内存仅仅存放的是引用类型的变量名,而堆内存中存放的是对象的实际数据,通过引用变量名来引用实际的对象,如果将该变量赋值给另一个引用变量,仅仅是将两个引用变量指向了同一个对象而已,而不会发生赋值对象数据
我们来谈论下一个知识点,java方法的参数传递问题,这个也是一个需要注意的地方。
public class TestPassPrimitiveArgs { public static void swap(int a, int b) { System.out.println("交换前,a = " + a + ", b = " + b);//a = 3,b = 5 int temp = a; a = b; b = temp; System.out.println("交换后,a = " + a + ", b = " + b);//a = 5,b = 3 } public static void main(String[] args) { int a = 3; int b = 5; swap(a, b); System.out.println("交换结束后,a = " + a + ", b = " + b);//a = 3,b = 5 }}
说明什么?
说明Java方法的参数传递方式只有一种:值传递,即将实参的副本传递给方法,参数本身不发生任何变化
从运行结果来看,在swap()中交换之前是3和5,交换后变成5和3,而实参在main()中始终变成不变,因此在main()传递给swap()的实参只是a和b的副本,而不是a和b本身。我们以内存分布来说明执行状况,当在main()中传参给swap()时,实际上就是在main()方法栈区向swap()方法栈区传递一份a和b的副本,如下图:
当执行swap()时,swap()方法栈区将a和b副本进行交换,交换完成后进入main()方法栈区,此时仅仅a和b的副本发生改变,其本身没有发生任何变化。接下来我们来看看引用类型的交换,前面我们说了只能通过值传递的方式来传参,可能对有些朋友来说稍显疑惑。
class DataSwap { public int a; public int b;}public class TestPassReferenceArgs { public static void swap(DataSwap ds) { System.out.println("交换前,ds.a = " + ds.a + ", ds.b = " + ds.b);//ds.a = 3, ds.b = 5 int temp = ds.a; ds.a = ds.b; ds.b = temp; System.out.println("交换后,ds.a = " + ds.a + ", ds.b = " + ds.b);//ds.a = 5, ds.b = 3 } public static void main(String[] args) { DataSwap ds = new DataSwap(); ds.a = 3; ds.b = 5; swap(ds); System.out.println("交换结束后,ds.a = " + ds.a + ", ds.b = " + ds.b);//ds.a = 5, ds.b = 3 }}
从运行结果来看,确实不仅在swap中交换成功,在main中仍然是交换之后的结果。让人一下觉得:从main中传递给swap似乎不是ds对象的副本了,而是ds本身,这与我们前面谈到的Java方法传参只能按值传递相违背了。
下面我详细说明一下,直接上图说明问题:
我们都知道,此时传递的是引用类型DataSwap,而引用类型的内存方式已经谈过了,在main()方法栈区中实际存放的是ds对象的地址,而实际的数据(a,b)是存放在堆内存中。现在将ds对象引用由main传递给swap,实际上是ds对象的地址复制一份到swap方法栈区中,此时main和swap中都已拥有ds对象的地址,且都指向在堆内存中实际存放的数据。也就是说引用类型参数数据传递方式是不折不扣的值传递方式,只不过传递的仅仅是引用变量,而不是引用变量所指向的引用类型数据。当然这里对main或swap中任何一个ds对象数据的更改都会影响到另一方,同时我们还可以验证main和swap中的ds是两个不同的引用变量,试着在swap种方法最后添加: ds=null.也就是切断swap中对ds的引用,查看一下main中ds对象的a和b是否受到影响(结果是不会)。
接下来,我们来谈一谈可变参数的实现方法(就是在类型后面添加三个点...),看实际的例子
public class TestVarityArgs { public static void readBooks(String name, String... books) { System.out.println(Arrays.toString(books)); if(books.length == 0) { System.out.println(name + " has not a book to read"); } else { String result = name + " is reading: "; for(String book : books) { result += book + " "; } System.out.println(result); } } public static void main(String[] args) { readBooks("qinling"); readBooks("qinling", "Java", "oracle", "J2EE"); readBooks("qinling", new String[] { "Java", "oracle", "J2EE" }); }}
谈到了可变参数,似乎跟重载函数非常相似,都是同一个方法有多种调用形式,但是它们有着显著的区别。重载函数必须满足"两同一不同":同一个的重载方法名的必须相同,但是形参列表不同(返回值、修饰符不能作为重载的标准)。请注意,尽量别对包含可变参数的方法进行重载,因为这样可能会引起歧义。
前面一直提到static这个概念,接下来我以例子来说明它的应用,可以看出static和非static字段和方法的区别所在。
public class TestCat { public static void main(String[] args) { Cat cat = new Cat("tomcat", 15); cat.display(); //static方法可通过类调用,也可通过实例调用,调用效果一致,会对该类的所有实例产生影响 cat.AddCourse(1);//p.AddCourse(1); cat.display(); //cat1.courses现在也变成3 Cat cat1 = new Cat("HelloKitty", 14); cat1.display(); }}class Cat { public String name; public int age; public static int courses = 2; public Cat() {} public Cat(String name, int age) { this.name = name; this.age = age; } public static void AddCourse(int count) { //在static方法中只能访问static字段,不能访问实例字段 //System.out.println(name + " 's course: "); courses += count; } public void display() { //在实例方法中可以访问static字段 System.out.println("Name: " + name + ", Age: " + age + ", Course: " + courses); }}
那在实际开发中,怎样例区分static和非static的引用呢?
简单的建议是:如果定义的变量是用来描述每个对象的固有信息(如每个人都有姓名、年龄),则应该使用实例变量,相反如果描述的类的固有信息(如只要是人就只能有两只眼睛),则应该使用类变量
下面开始讲解重点知识:
封装
封装就是将对象的属性等信息隐藏在类的内部,仅提供给外部一些满足预设条件的方法供调用。拿上面的例子来说明:每个人的年龄只能在0~150之间来进行浮动,现在的情况是我可以随意更改年龄(想多少岁就多少岁),那肯定就不对了。我们必须将这些不满足条件的操作及时的过滤掉,Java提供了访问权限控制: private->default->protected->public(权限依次扩大)来封装内部属性和提供外部接口(对字段采用private或protected等修饰符来限制,采用getter和setter来进行有效控制)
关于访问控制符,有以下建议:类的绝大部分字段(有些少数的static字段需要public修饰)和辅助方法都采用private来修饰,并提供getter和setter访问器来对其读取和修改;如果值希望同一个包中其他类访问,则不添加任何修饰符;如果只希望子类也能使用父类的成员而不被外界知晓,则采用protected来修饰;如果可以在任何地方都能访问到,则采用public来修饰
继承
继承就是在已有类的基础上扩展新的子类,而不改变原有父类的数据和行为,即我们通常所说的父类和子类(遵从"子类 is a 父类"原则),子类可继承父类的非私有成员(建议将成员修饰符改为protected),同时也可重写父类相同的成员
遵从"两同两小一大"原则:即方法名相同,方法形参相同;子类方法返回类型比父类更小或相等,子类方法抛出的异常比父类更小或相等;子类方法的访问权限比父类更大或相等,重写的方法要么都是类方法,要么都是实例方法,如父类有一个实例方法,子类添加一个同名的类方法则不算重写,而是子类的新方法
Father.java
public class Father { private String name; private int 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; } public void display(){ System.out.println("father-->display"); }}
Child.java
public class Child extends Father { @Override public void display() {//重写了父类的方法 System.out.println("child-->display"); } public static void main(String[] args) { Child c=new Child(); c.setAge(12); c.setName("child"); System.out.println(c.getAge()); System.out.println(c.getName()); c.display(); }}
如果在子类中先调用已重写的父类方法,该怎么办呢?Java提供了super引用,指向其直接父类的默认引用。当创建一个对象时,系统会隐式创建其父类的对象(Java所有类都继承自java.lang.Object),只要该类有子类存在,就一定会产生super引用,指向其对应的直接父类,当子类方法中使用某个成员变量时,首先会查找当前类中是否存在,如不存在则查找直接父类中是否存在,如不存在会依次追溯到java.lang.Object中是否存在,如仍然不存在将不能通过编译。
我们将上面的代码稍作调整Father c=new Child();
运行之后我们会发现,虽然是父类对象,但是还是调用了子类的方法。
则我们有如下的结论:
当引用变量的编译时类型和运行时类型不一致(父类 t = new 子类();)时,我们说表现出了对象的多态。而多态仅仅表现在调用重写方法时,将调用子类中的方法,调用非重写方法时,如果该方法在父类中将调用父类,如果在子类中将无法调用,而对象的字段不具多态性,即只能调用父类中对应的字段。
到现在为止,基本讲完了面向对象的三大特性,在结束本篇讲解之前,简单谈一下"初始化块"的应用。
所谓初始化块,就是在构造器执行之前,对整个类(所有对象)的字段进行初始化的过程,通常会将多个构造器中相同的部分放到初始化块中执行,可以把初始化块看成是没有形参的方法,只不过在构造器执行之前执行而已。与构造器执行顺序一致,初始化块也遵循从父类到子类依次执行的过程。与初始化块对应的还有静态初始化块,主要完成类属性的初始化,并且只在类加载时初始化一次。
Father.java
public class Father { static { a = 4; System.out.println("Father的静态初始化块执行"); } { b = 4; System.out.println("Father的初始化块执行"); } public static int a = 2; public int b = 3; public Father() { System.out.println("Father的构造器执行"); }}
Child.java
public class Child extends Father { static { // a = 10; System.out.println("Child的静态初始化块执行"); } { b = 6; System.out.println("Child的初始化块执行"); } public Child() { System.out.println("Child的构造器执行"); } public static void main(String[] args) { for(int i = 0; i < 2; i++) { Child c = new Child(); System.out.println("a: " + Father.a + ", b: " + c.b); } }}
运行结果:
Father的静态初始化块执行Child的静态初始化块执行Father的初始化块执行Father的构造器执行Child的初始化块执行Child的构造器执行a: 2, b: 6Father的初始化块执行Father的构造器执行Child的初始化块执行Child的构造器执行a: 2, b: 6
从运行结果来看:静态初始化块和初始化块都先于构造器执行,并都遵从父类到子类的执行过程,但静态初始化块最先执行且仅执行一次,子类初始化块在父类的初始化块和构造器执行完毕之后,在子类构造器之前执行
OK,到此为止,面向对象的三大基本特征就总结完成。希望大家能自行仔细琢磨一下其中原理.
- java基本特征(封装、继承、多态)
- java的基本特征:封装,继承,多态
- java面向对象基本特征:封装、继承、多态
- 三个基本特征:封装、继承、多态
- Java面向对象的三个基本特征:封装、继承、多态
- OOP三个基本特征:封装、继承、多态
- OOP三个基本特征:封装、继承、多态
- C++的三个基本特征:封装、继承、多态
- java面向对象的三大基本特征之封装,继承,多态
- 面向对象的三个基本特征:封装、继承、多态
- 面向对象的三个基本特征-----封装、继承、多态
- 面向对象的三个基本特征是:封装、继承、多态
- 面向对象的三个基本特征-----封装、继承、多态
- 面向对象的三个基本特征是:封装、继承、多态
- 面向对象的三个基本特征是:封装、继承、多态。
- 面向对象的三个基本特征是:封装、继承、多态
- 面向对象的三个基本特征是:封装、继承、多态
- 面向对象的基本特征是什么?什么是封装、继承、多态?
- cocos2dx 里CCArray的使用实践
- 《高效程序员45个习惯》-敏捷开发有感
- Objective-C 计算代码运行时间
- magento站点还原到本地
- 扫盲:中国经济的过去与未来(全篇完)
- java基本特征(封装、继承、多态)
- js 解析浏览器版本
- XML中的几种比较
- UISegmentedControl,UISlider,UISwitch,代码截屏,UIStepper的简单使用
- java内存分析
- 在任何地方获取程序的applicationContext。
- 正则表达式
- Connection reset by peer: socket write error错误分析
- linux中fork()系统调用总结