第一章节
JAVA类
类间关系有很多种,在大的类别上可以分为两种:纵向关系、横向关系。
纵向关系就是继承关系,它的概念非常明确,也成为OO的三个重要特征之一,这里不过多的讨论。
横向关系较为微妙,按照UML的建议大体上可以分为四种:
- 依赖 (Dependency)
- 关联 (Association)
- 聚合 (Aggregation)
- 组合 (Composition)
它们的强弱关系是没有异议的:依赖 < 关联 < 聚合 < 组合
然而它们四个之间的差别却又不那么好拿捏,需要好好体会。
- 依赖:
- UML表示法:虚线 + 箭头
- 关系:" ... uses a ..."
- 此关系最为简单,也最好理解,所谓依赖就是某个对象的功能依赖于另外的某个对象,而被依赖的对象只是作为一种工具在使用,而并不持有对它的引用。
- 典型的例子很多,比如:
class Human
{
public void breath()
{
Air freshAir = new Air();
freshAir.releasePower();
}
public static void main()
{
Human me = new Human();
while(true)
{
me.breath();
}
}
}
class Air
{
public void releasePower()
{
//do sth.
}
}
- 释义:一个人自创生就需要不停的呼吸,而人的呼吸功能之所以能维持生命就在于吸进来的气体发挥了作用,所以说空气只不过是人类的一个工具,而人并不持有对它的引用。
- UML表示法:虚线 + 箭头
- 关联:
- UML表示法:实线 + 箭头
- 关系:" ... has a ..."
- 所谓关联就是某个对象会长期的持有另一个对象的引用,而二者的关联往往也是相互的。关联的两个对象彼此间没有任何强制性的约束,只要二者同意,可以随时解除关系或是进行关联,它们在生命期问题上没有任何约定。被关联的对象还可以再被别的对象关联,所以关联是可以共享的。
- 典型的例子很多,比如:
class Human
{
ArrayList friends = new ArrayList();
public void makeFriend(Human human)
{
friends.add(human);
}
public static void main()
{
Human me = new Human();
while(true)
{
me.makeFriend(mySchool.getStudent());
}
}
}
- 释义:人从生至死都在不断的交朋友,然而没有理由认为朋友的生死与我的生死有必然的联系,故他们的生命期没有关联,我的朋友又可以是别人的朋友,所以朋友可以共享。
- UML表示法:实线 + 箭头
- 聚合:
- UML表示法:空心菱形 + 实线 + 箭头
- 关系:" ... owns a ..."
- 聚合是强版本的关联。它暗含着一种所属关系以及生命期关系。被聚合的对象还可以再被别的对象关联,所以被聚合对象是可以共享的。虽然是共享的,聚合代表的是一种更亲密的关系。
- 典型的例子很多,比如:
class Human
{
Home myHome;
public void goHome()
{
//在回家的路上
myHome.openDoor();
//看电视
}
public static void main()
{
Human me = new Human();
while(true)
{
//上学
//吃饭
me.goHome();
}
}
}
- 释义:我的家和我之间具有着一种强烈的所属关系,我的家是可以分享的,而这里的分享又可以有两种。其一是聚合间的分享,这正如你和你媳妇儿都对这个家有着同样的强烈关联;其二是聚合与关联的分享,如果你的朋友来家里吃个便饭,估计你不会给他配一把钥匙。
- UML表示法:空心菱形 + 实线 + 箭头
- 组合:
- UML表示法:实心菱形 + 实线 + 箭头
- 关系:" ... is a part of ..."
- 组合是关系当中的最强版本,它直接要求包含对象对被包含对象的拥有以及包含对象与被包含对象生命期的关系。被包含的对象还可以再被别的对象关联,所以被包含对象是可以共享的,然而绝不存在两个包含对象对同一个被包含对象的共享。
- 典型的例子很多,比如:
class Human
{
Heart myHeart = new Heart();
public static void main()
{
Human me = new Human();
while(true)
{
myHeart.beat();
}
}
}
- 释义:组合关系就是整体与部分的关系,部分属于整体,整体不存在,部分一定不存在,然而部分不存在整体是可以存在的,说的更明确一些就是部分必须创生于整体创生之后,而销毁于整体销毁之前。部分在这个生命期内可以被其它对象关联甚至聚合,但有一点必须注意,一旦部分所属于的整体销毁了,那么与之关联的对象中的引用就会成为空引用,这一点可以利用程序来保障。心脏的生命期与人的生命期是一致的,如果换个部分就不那么一定,比如阑尾,很多人在创生后的某个时间对其厌倦便提前销毁了它,可它和人类的关系不可辩驳的属于组合。
在UML中存在一种特例,就是允许被包含对象在包含对象销毁前转移给新的对象,这虽然不自然,但它给需要心脏移植的患者带来了福音。
- UML表示法:实心菱形 + 实线 + 箭头
一、面向对象简述
面向对象是一种现在最为流行的程序设计方法,几乎现在的所有应用都以面向对象为主了,最早的面向对象的概念实际上是由IBM提出的,在70年代的Smaltalk语言之中进行了应用,后来根据面向对象的设计思路,才形成C++,而由C++产生了Java这门面向对象的编程语言。
但是在面向对象设计之前,广泛采用的是面向过程,面向过程只是针对于自己来解决问题。面向过程的操作是以程序的基本功能实现为主,实现之后就完成了,也不考虑修改的可能性,面向对象,更多的是要进行子模块化的设计,每一个模块都需要单独存在,并且可以被重复利用,所以,面向对象的开发更像是一个具备标准的开发模式。
在面向对象定义之中,也规定了一些基本的特征:
(1)封装:保护内部的操作不被破坏;
(2)继承:在原本的基础之上继续进行扩充;
(3)多态:在一个指定的范围之内进行概念的转换。
对于面向对象的开发来讲也分为三个过程:OOA(面向对象分析)、OOD(面向对象设计)、OOP(面向对象编程)。
二、类与对象的基本概念
类与对象时整个面向对象中最基础的组成单元。
类:是抽象的概念集合,表示的是一个共性的产物,类之中定义的是属性和行为(方法);
对象:对象是一种个性的表示,表示一个独立的个体,每个对象拥有自己独立的属性,依靠属性来区分不同对象。
可以一句话来总结出类和对象的区别:类是对象的模板,对象是类的实例。类只有通过对象才可以使用,而在开发之中应该先产生类,之后再产生对象。类不能直接使用,对象是可以直接使用的。
三、类与对象的定义和使用
在Java中定义类,使用关键字class完成。语法如下:
class 类名称 {
属性 (变量) ;
行为 (方法) ;
}
- 1
- 2
- 3
- 4
- 1
- 2
- 3
- 4
范例:定义一个Person类
class Person { // 类名称首字母大写
String name ;
int age ;
public void tell() { // 没有static
System.out.println("姓名:" + name + ",年龄:" + age) ;
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 1
- 2
- 3
- 4
- 5
- 6
- 7
类定义完成之后,肯定无法直接使用。如果要使用,必须依靠对象,那么由于类属于引用数据类型,所以对象的产生格式(两种格式)如下:
(1)格式一:声明并实例化对象
类名称 对象名称 = new 类名称 () ;
- 1
- 1
(2)格式二:先声明对象,然后实例化对象:
类名称 对象名称 = null ;
对象名称 = new 类名称 () ;
- 1
- 2
- 1
- 2
引用数据类型与基本数据类型最大的不同在于:引用数据类型需要内存的分配和使用。所以,关键字new的主要功能就是分配内存空间,也就是说,只要使用引用数据类型,就要使用关键字new来分配内存空间。
当一个实例化对象产生之后,可以按照如下的方式进行类的操作:
对象.属性:表示调用类之中的属性;
对象.方法():表示调用类之中的方法。
范例:使用对象操作类
package com.wz.classandobj;
class Person {
String name ;
int age ;
public void get() {
System.out.println("姓名:" + name + ",年龄:" + age);
}
}
public class TestDemo {
public static void main(String args[]) {
Person per = new Person() ;// 声明并实例化对象
per.name = "张三" ;//操作属性内容
per.age = 30 ;//操作属性内容
per.get() ;//调用类中的get()方法
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
运行结果:
姓名:张三,年龄:30
- 1
- 1
以上完成了一个类和对象的操作关系,下面换另外一个操作来观察一下:
package com.wz.classandobj;
class Person {
String name ;
int age ;
public void get() {
System.out.println("姓名:" + name + ",年龄:" + age);
}
}
public class TestDemo {
public static void main(String args[]) {
Person per = null;//声明对象
per = new Person() ;//实例化对象
per.name = "张三" ;//操作属性内容
per.age = 30 ;//操作属性内容
per.get() ;//调用类中的get()方法
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
运行结果:
姓名:张三,年龄:30
- 1
- 1
那么,问题来了,以上两种不同的实例化方式有什么区别呢?
我们从内存的角度分析。首先,给出两种内存空间的概念:
(1)堆内存:保存对象的属性内容。堆内存需要用new关键字来分配空间;
(2)栈内存:保存的是堆内存的地址(在这里为了分析方便,可以简单理解为栈内存保存的是对象的名字)。
任何情况下,只要看见关键字new,都表示要分配新的堆内存空间,一旦堆内存空间分配了,里面就会有类中定义的属性,并且属性内容都是其对应数据类型的默认值。
于是,上面两种对象实例化对象方式内存表示如下:
两种方式的区别在于①②,第一种声明并实例化的方式实际就是①②组合在一起,而第二种先声明然后实例化是把①和②分步骤来。
另外,如果使用了没有实例化的对象,结果如何?
如下:
package com.wz.classandobj;
class Person {
String name ;
int age ;
public void get() {
System.out.println("姓名:" + name + ",年龄:" + age);
}
}
public class TestDemo {
public static void main(String args[]) {
Person per = null;//声明对象
//per = new Person() ;//实例化对象
per.name = "张三" ;//操作属性内容
per.age = 30 ;//操作属性内容
per.get() ;//调用类中的get()方法
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
运行结果:
Exception in thread "main" java.lang.NullPointerException
at com.wz.classandobj.TestDemo.main(TestDemo.java:15)
- 1
- 2
- 1
- 2
此时,程序只声明了Person对象,但并没有实例化Person对象(只有了栈内存,并没有对应的堆内存空间),则程序在编译的时候不会出现任何的错误,但是在执行的时候出现了上面的错误信息。这个错误信息表示的是“NullPointerException(空指向异常)”,这种异常只要是应用数据类型都有可能出现。
四、对象引用传递初步分析
引用传递的精髓:同一块堆内存空间,可以同时被多个栈内存所指向,不同的栈可以修改同一块堆内存的内容。
下面通过若干个程序,以及程序的内存分配图,来进行代码的讲解。
我们来看一个范例:
class Person {
String name ;
int age ;
public void tell() {
System.out.println("姓名:" + name + ",年龄:" + age) ;
}
}
public class TestDemo {
public static void main(String args[]) {
Person per1 = new Person() ; // 声明并实例化对象
per1.name = "张三" ;
per1.age = 20 ;
Person per2 = per1 ; // 引用传递
per2.name = "李四" ;
per1.tell() ;
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
对应的内存分配图如下:
再来看另外一个:
class Person {
String name ;
int age ;
public void tell() {
System.out.println("姓名:" + name + ",年龄:" + age) ;
}
}
public class TestDemo {
public static void main(String args[]) {
Person per1 = new Person() ; // 声明并实例化对象
Person per2 = new Person() ;
per1.name = "张三" ;
per1.age = 20 ;
per2.name = "李四" ;
per2.age = 30 ;
per2 = per1 ;// 引用传递
per2.name = "王五" ;
per1.tell() ;
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
对应的内存分配图如下:
垃圾:指的是在程序开发之中没有任何对象所指向的一块堆内存空间,这块空间就成为垃圾,所有的垃圾将等待GC(垃圾收集器)不定期的进行回收与空间的释放。
一、面向对象的封装性
封装(encapsulation)又叫隐藏实现(Hiding the implementation)。就是只公开代码单元的对外接口,而隐藏其具体实现。比如手机,手机的键盘,屏幕,听筒等,就是其对外接口。你只需要知道如何按键就可以使用手机,而不需要了解手机内部的电路是如何工作的。封装机制就像手机一样只将对外接口暴露,而不需要用户去了解其内部实现。
在研究封装性之前,我们先来看一段代码:
package com.wz.classandobj;
class Book{
String title;
double price;
public void getInfo(){
System.out.println("图书的名称:"+title+" 图书的价格:"+price);
}
}
public class TestDemo {
public static void main(String args[]) {
Book book = new Book();
book.title = "Java开发";
book.price = -89.9;
book.getInfo();
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
运行结果:
图书的名称:Java开发 图书的价格:-89.9
- 1
- 1
以上代码没有任何语法错误,却存在一个业务逻辑的错误,因为图书的价格不能为负数。造成这种情况的的原因在于:对象可以在一个类的外部直接访问属性。
如何解决?我们需要将Book类中的属性设置为对外不可见(只能是本类访问),可以使用private关键字来定义属性。
修改之前的代码如下:
package com.wz.classandobj;
class Book{
private String title;
private double price;
public void getInfo(){
System.out.println("图书的名称:"+title+" 图书的价格:"+price);
}
}
public class TestDemo {
public static void main(String args[]) {
Book book = new Book();
book.title = "Java开发";
book.price = -89.9;
book.getInfo();
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
运行:
Exception in thread "main" java.lang.Error: Unresolved compilation problems:
The field Book.title is not visible
The field Book.price is not visible
at com.wz.classandobj.TestDemo.main(TestDemo.java:16)
- 1
- 2
- 3
- 4
- 5
- 6
- 1
- 2
- 3
- 4
- 5
- 6
我们发现,在访问属性的时候,外部的对象无法再直接调用类中的属性了,此时就相当于Book类的属性对外部不可见。
但是,要想让程序可以正常运行,那么必须让外部可以操作Book类的属性。在Java开发中,针对属性有这样的定义,在类中定义的属性都要求使用private声明,如果属性需要被外部所使用,那么按照要求,定义属性相应的setter和getter方法,以Book类中的String title 为例:
(1)setter方法是设置属性内容:
public void setTitle(String t)
- 1
- 1
主要:有参数。
(2)getter方法是取得属性内容:
public String getTitle()
- 1
- 1
注意:无参数。
范例:为Book类的封装属性设置setter和getter。
package com.wz.classandobj;
class Book{
private String title;
private double price;
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public double getPrice() {
return price;
}
public void setPrice(double price) {
this.price = price;
}
public void getInfo(){
System.out.println("图书的名称:"+title+" 图书的价格:"+price);
}
}
public class TestDemo {
public static void main(String args[]) {
Book book = new Book();
book.setTitle("Java开发");
book.setPrice(-89.9);
book.getInfo();
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
运行结果:
图书的名称:Java开发 图书的价格:-89.9
- 1
- 2
- 1
- 2
发现,图书的价格是负数,需要加入检查业务逻辑错误的代码,可以在setter中增加验证,如果值为正,赋值,否则为默认值0.0:
public void setPrice(double price) {
if(price > 0.0){
this.price = price;
}
}
- 1
- 2
- 3
- 4
- 5
- 1
- 2
- 3
- 4
- 5
对于数据验证,在Java标准开发中应该由辅助代码完成。在实际开发中,setter往往只是简单的设置属性内容,getter只是简单的取得属性内容。
开发建议:以后在定义类的时候,所有的属性都要编写private封装,封装之后的属性如果需要被外部操作,则编写setter、getter。
二、类中的构造方法
先来看对象的产生格式:
①类名称 ②对象名称 = ③new ④类名称();
- 1
- 1
①类名称:规定了对象的类型。即:对象可以使用哪些属性和方法都是由类定义的;
②对象名称:如果需要使用对象,需要有一个名称,这是一个唯一的标记;
③new:分配新的堆内存空间;
④类名称():调用了名称和类名称相同的方法,这就是构造方法。
实际上,构造方法一直在被我们调用,但我们并没有去定义它,为什么能够使用呢?这是因为在整个Java开发中,为了保证程序可以正常执行,即便用户没有定义任何构造方法,也会在程序编译后自动为类增加一个没有参数,方法名称与类名称相同,没有返回值的构造方法。
构造方法的定义:方法名称和类名称相同,没有返回值声明。
//无参,无返回值的构造方法
public Book() {
}
- 1
- 2
- 3
- 4
- 1
- 2
- 3
- 4
如果在Book类中没有定义以上的构造方法,那么也会自动生成一个无参,无返回值的构造方法。
我们再看:
package com.wz.classandobj;
class Book{
//无参,无返回值的构造方法
public Book() {
System.out.println("无参构造方法");
}
}
public class TestDemo {
public static void main(String args[]) {
Book book = null;//声明对象
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
运行,什么也没有打印。
在主方法中加入实例化对象的代码:
public class TestDemo {
public static void main(String args[]) {
Book book = null;//声明对象
book = new Book();//实例化对象
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 1
- 2
- 3
- 4
- 5
- 6
运行:
无参构造方法
- 1
- 1
以上说明,构造方法是在对象使用关键字new实例化的时候被调用。
构造方法与普通方法最大的区别是:
构造方法在实例化对象(new)的时候只调用一次,而普通方法是在实例化对象之后可以随意调用多次。
在实际开发中,构造方法的作用是在类对象实例化的时候设置属性的初始化内容,范例如下:
package com.wz.classandobj;
class Book{
private String title;
private double price;
public Book(String title, double price) {
this.title = title;
this.price = price;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public double getPrice() {
return price;
}
public void setPrice(double price) {
this.price = price;
}
public void getInfo(){
System.out.println("图书的名称:"+title+" 图书的价格:"+price);
}
}
public class TestDemo {
public static void main(String args[]) {
Book book = new Book("Java开发",89.9);//设置属性的初始化内容
book.getInfo();
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
运行结果:
图书的名称:Java开发 图书的价格:89.9
- 1
- 1
如果一个类中已经明确定义了一个构造方法,则无参构造方法将不会自动生成。而且,一个类之中至少存在一个构造方法。另外,既然构造方法也属于方法,那么构造方法也可以重载,但是由于构造方法的特殊性,所以在构造方法重载时注意其参数的类型及参数的个数即可。
范例如下:
package com.wz.classandobj;
class Book{
private String title;
private double price;
public Book() {
System.out.println("无参的构造方法");
}
public Book(String title) {
this.title = title;
System.out.println("有一个参数的构造方法");
}
public Book(String title, double price) {
this.title = title;
this.price = price;
System.out.println("有俩个参数的构造方法");
}
public void getInfo(){
System.out.println("图书的名称:"+title+" 图书的价格:"+price);
}
}
public class TestDemo {
public static void main(String args[]) {
Book book1 = new Book();
book1.getInfo();
Book book2 = new Book("Java开发");
book2.getInfo();
Book book3 = new Book("Java开发",89.9);
book3.getInfo();
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
运行结果:
无参的构造方法
图书的名称:null 图书的价格:0.0
有一个参数的构造方法
图书的名称:Java开发 图书的价格:0.0
有俩个参数的构造方法
图书的名称:Java开发 图书的价格:89.9
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 1
- 2
- 3
- 4
- 5
- 6
- 7
在进行构造方法重载时有一个编写建议:所有重载的构造方法按照参数的个数由多到少,或者是由少到多排列。
我们再来看,如果在类中为属性直接设置默认值,结果会怎样?
package com.wz.classandobj;
class Book{
private String title = "Android开发";
private double price = 199.9;
public Book() {
System.out.println("无参的构造方法");
}
public Book(String title) {
this.title = title;
System.out.println("有一个参数的构造方法");
}
public Book(String title, double price) {
this.title = title;
this.price = price;
System.out.println("有俩个参数的构造方法");
}
public void getInfo(){
System.out.println("图书的名称:"+title+" 图书的价格:"+price);
}
}
public class TestDemo {
public static void main(String args[]) {
Book book1 = new Book();
book1.getInfo();
Book book2 = new Book("Java开发");
book2.getInfo();
Book book3 = new Book("Java开发",89.9);
book3.getInfo();
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
运行结果:
无参的构造方法
图书的名称:Android开发 图书的价格:199.9
有一个参数的构造方法
图书的名称:Java开发 图书的价格:199.9
有俩个参数的构造方法
图书的名称:Java开发 图书的价格:89.9
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 1
- 2
- 3
- 4
- 5
- 6
- 7
从上面可以发现:如果在类中为属性直接设置默认值,而且这个默认值是在构造方法执行完后才会设置的,且属性内容为对应数据类型的默认值时才设置类中定义的这个默认值。但是,要注意的是在构造方法没有执行之前,属性内容都是其对应数据类型的默认值。
三、类中的匿名对象
没名字的对象称为匿名对象,对象的名字按照之前的内存关系来讲,在栈内存之中,而对象的具体内容在堆内存之中保存,这样,没有栈内存指向堆内存空间,就是一个匿名对象。
范例:
package com.wz.classandobj;
class Book{
private String title;
private double price;
public Book(String title, double price) {
this.title = title;
this.price = price;
System.out.println("有俩个参数的构造方法");
}
public void getInfo(){
System.out.println("图书的名称:"+title+" 图书的价格:"+price);
}
}
public class TestDemo {
public static void main(String args[]) {
//匿名对象
new Book("Java开发",89.9).getInfo();
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
运行结果:
有俩个参数的构造方法
图书的名称:Java开发 图书的价格:89.9
- 1
- 2
- 1
- 2
匿名对象由于没有对应的栈内存指向,所以只能使用一次,一次之后就将成为垃圾,并且等待被GC回收释放。
一、成员变量
-
在Java中对象的属性称为成员变量,也可以称为属性。在JAVA中使用class关键字来定义类,下列代码中定义了两个成员变量,分别为ID和name。如果在成员变量中加private关键字,则这个变量就变成了一个私有成员
END
二、成员方法
-
在Java语言中使用成员方法对应于类对象的行为。以Book类为例,它包含getName()和setName()两个方法,这两个成员方法分别为获取图书名称和设置图书名称的方法。定义成员方法的语法格式如下所示:
END -
三、权限修饰符
-
Java中的权限修饰符主要包括private、public和protected,这些修饰符控制着对类和类的成员变量以及成员方法的访问。如果一个类的成员变量或成员方法被修饰为private,则该成员变量只能在本类中被使用,在子类中是不可见的,并且对其他包的类也是不可见的。如果将类的成员变量和成员方法的访问权限设置为public,则除了可以在本类使用这些数据之外,还可以在子类和其他包中的类中使用。如果一个类的访问权限被设置为private,这个类将隐藏其内的所有数据,以免用户直接访问它。如果需要使类中的数据被子类或其他包中的类使用,可以将这个类设置为public访问权限。如果一个类使用protected修饰符,那么只有本包内的该类的子类或其他类可以访问此类中的成员变量和成员方法。
END
static
提起static关键字,相信大家绝对不会陌生,但是,想要完全说明白,猛的一想,发现自己好像又说不太明白... ...比方说,昨天被一个同学问起的时候... ... 当然,不是所有人都像我一样学艺不精的,但是像这样的基础不牢的人应该不少,因为常用,所以用大家都会,但是谈到精细之处都够呛。这篇博客是我翻出我原来的学习笔记再加上自己看的一些博客整理出来的,供基础知识不怎么牢靠的同学参考参考。
1. static 关键字要解决的问题
这里摘录一下《Java编程思想(第四版)》里关于static关键字的一段原话:(P29)通常来说,当创建类时,就是在描述那个类的对象的外观与行为。除非用new创建那个对象,否则,实际上并未获得任何对象。执行new来创建对象的时候,数据存储空间才被分配,其方法才供外界调用。有两种情形用上述方法是无法解决的。一种情形是,只想为某特定域分配单一存储空间,而不去考虑究竟要创建多少个对象,甚至根本不需要创建任何对象。另一种情形是,希望某个方法不与包含他的类的任何对象关联在一起。也就是说,即使没有创建对象,也能够调用方法。简单来说,static的主要目的就是创建独立于具体对象的域变量与方法。
2. static修饰的变量或方法或类的加载时机
在加载类的同时加在static修饰的部分。(注意:这个时候,还不存在具体对象,并且这个过程只进行一次)
3. 通过代码示例来分别看看静态变量、静态方法、静态类的效果
3.1 静态变量
public class StaticTest{ public static int count =0; @SuppressWarnings("static-access") public static void main(String[] args) { // TODO Auto-generated method stub StaticTest test1 = new StaticTest(); System.out.println(test1.count); StaticTest test2 = new StaticTest(); test2.count++; System.out.println(test1.count+" "+test2.count+" "+StaticTest.count); } }
输出结果:
0 1 1 1
可见,static变量并不是所在类的某个具体对象所有,而是该类的所有对象所共有的,静态变量既能被对象调用,也能直接拿类来调用。
除此之外,静态变量不能引用非静态方法,原因正如前面描述静态加载时机中说的那样,加载静态的时候,非静态的变量、方法等还不存在,当然就无法引用了。但是,非静态方法或类却能正常引用静态变量或方法。因为非静态总是在静态之后出现的。
3.2 静态方法
静态方法和静态变量一样,属于类所有,在类加载的同时执行,不属于某个具体的对象,所有对象均能调用。对于静态方法需要注意以下几点:
- 它们仅能调用其他的static 方法。
- 它们只能访问static数据。
- 它们不能以任何方式引用this 或super。
class Simple { static void go() { System.out.println("Welcome"); } } public class Cal { public static void main(String[] args) { Simple.go(); } }
静态方法一般用于工具类中,可以直接拿类名调用工具方法进行使用。
3.3 静态类
一般来说,一个普通类是不允许被声明为static的,但是,在内部类中可以将其声明为static的,这个时候,外部类可以直接调用内部类,因为static的内部类是在加载外部类的同时加载的,所以也就是说,并不要实例化外部类就能直接调用静态内部类。看例子:
public class BaseStatic { static { System.out.println("Load base static"); } public BaseStatic(){ System.out.println("BaseStatic constructor"); } static class BaseInnerClass{ static{ System.out.println("Base inner class static"); } public BaseInnerClass(){ System.out.println("BaseInnerClass constructor"); } } } public class StaticLoadOrderTest{ public static void main(String[] args) { // TODO Auto-generated method stub new BaseStatic.BaseInnerClass(); } }
在看答案之前,自己想想这个输出结果是什么?
先不急着看答案,我们先来看看这个执行过程:首先,在进入StaticLoadOrderTest的main方法之前,加载StaticLoadOrderTest类,然后执行new BaseStatic.BaseInnerClass();这里需要注意:因为BaseInnerClass是静态的,所以这里并不需要加载外部类和实例化外部类,可以直接加载BaseInnerClass并实例化。所以输出:
Base inner class static BaseInnerClass constructor
这里留个坑:当直接使用外部类类名.静态内部类进行实例化的时候,如果外部类没有加载的话(实际上也是没有加载),那么这个statement: BaseStatic.BaseInnerClass中的BaseStatic是个什么存在????难道只是与静态内部类发生了简单的名称关联吗?若是这样还设计静态内部类干嘛呢?我觉得java设计者们不至于犯这种错误吧?也可能因为自己对于JVM并不熟悉,对于底层不太了解,若是路过的大神能帮忙解决一下,感激不尽!!!!
3.4 关于静态加载顺序的示例
下面这段代码的输出是什么?
public class BaseStatic { static { System.out.println("Load base static"); } public BaseStatic(){ System.out.println("BaseStatic constructor"); } static class BaseInnerClass{ static{ System.out.println("Base inner class static"); } public BaseInnerClass(){ System.out.println("BaseInnerClass constructor"); } } } public class StaticLoadOrderTest { static { System.out.println("Load test"); } public StaticLoadOrderTest(){ System.out.println("Test constructor"); } public static void main(String[] args) { // TODO Auto-generated method stub new BaseStatic(); new StaticLoadOrderTest(); new BaseStatic.BaseInnerClass(); } }
和上面一样,分析一下过程:在进入main方法之前,需要加载StaticLoadOrderTest类,这时候发现有static代码块,先加载静态代码块,然后进入main方法内部,new BaseStatic(),这时候需要加载BaseStatic类,同时也要先加载静态代码块,然后调用构造器。注意:这里并没有加载BaseInnerClass,因为它是内部类只有在真正用到的时候才会进行加载,相信聪明的读者看到这个是不是想到了又一种单例设计模式的实现方式?自己研究吧。回到main方法中,接下来该执行new StaticLoadOrderTest()了,因为StaticLoadOrderTest类之前已经被加载过一次了,并且类只加载一次,所以这里就直接构造了;然后是最后一句new BaseStatic.BaseInnerClass()了,和上面例子一样,这里就不再细讲。所以输出结果为:
Load test Load base static BaseStatic constructor Test constructor Base inner class static BaseInnerClass constructor
再考虑一下,如果我把上面的例子改成下面这样,输出结果又会是什么呢?
public class BaseStatic { static { System.out.println("Load base static"); } public BaseStatic(){ System.out.println("BaseStatic constructor"); } static class BaseInnerClass{ static{ System.out.println("Base inner class static"); } public BaseInnerClass(){ System.out.println("BaseInnerClass constructor"); } } } public class StaticLoadOrderTest extends BaseStatic{ static { System.out.println("Load test"); } public StaticLoadOrderTest(){ System.out.println("Test constructor"); } public static void main(String[] args) { // TODO Auto-generated method stub new BaseStatic.BaseInnerClass();new StaticLoadOrderTest(); new BaseStatic(); } }
以上就是关于java中static关键字的一些知识了。