-
面向对象程序设计(OOP)
- 类(class):构造对象的模板或蓝图。由类构造(construct)对象的过程称为创建类的实例(instance),对象中的数据称为实例域(instance field),操纵数据的过程称为方法(method)。
- 对象:对象的行为(behavior):对对象施加哪些操作方法;状态(state):当施加那些方法时,对象如何响应;标识(identity):如何辨识具有相同行为与状态的不同对象。
- 识别类:识别类的简单规则是在分析问题的过程中寻找名词,而方法对应着动词。
- 类之间常见关系:依赖、聚合、继承
-
使用预定义类
- 对象与对象变量:使用构造器(constructor)构造新实例,构造器名字应该与类名相同。因此,要想构造一个类对象,需要在构造器前面加上new操作符。
- Java类库中的Gregorian Calendar类。
- 更改器方法与访问器方法:要查询某类信息,调用该类的get方法;改变对象的状态则调用set方法。对实例域做出修改的方法称为更改器方法(mutator method ),仅访问实例域而不进行修改的方法称为访问器方法(accessor method)。
-
用户自定义类
1.构造器与其他方法有一个重要的不同。构造器总是伴随着new操作符的执行被调用,而不能对一个已经存在的对象调用构造器来达到重新设置实例域的目的。
- 构造器与类同名
- 每个类可以有一个以上的构造器
- 构造器可以有0、1、多个参数,构造器没有返回值
- 构造器总是伴随着new操作符一起使用
!不要在构造器中定义与实例域重名的局部变量。例:
public Employee (String n, double s,...){
String name = n; //Error
double salary = s; //Error
}
此构造器将无法设置salary,此构造器声明了局部变量name和salary,这些变量只能在构造器内部访问,这些变量屏蔽了同名的实例域。
2.隐式参数和显示参数
- 隐式参数:出现在方法名前的类对象
- 显示参数:位于方法名后面括号中的数值
3.封装的优点:
- 可以改变内部实现,除了该类的方法之外,不会影响其他代码,解决设计上的缺陷。private、public、default、protected
- 更改器方法可以执行错误检查,直接对域进行赋值将不会进行这些处理。this指向当前对象。
!注意不要编写返回引用可变对象的访问器方法。如果需要返回一个可变对象的引用,应首先对它进行克隆(clone)。对象clone是指存放在另一个位置上的对象副本。
3.基于类的访问权限。方法可以访问所调用对象的私有数据。一个方法可以范文所属类的所有私有数据。
4.私有方法。在Java中,为了实现一个私有的方法,只需将关键字public改为private即可。
5.final实例域。可以将实例域定义为final,构建对象时必须初始化这样的域。final修饰符大都应用于基本(primitive)类型域,或不可变(immutable)类的域(如果类中的每个方法都不会改变其对象,这种类就是不可变的类)。如String就是一个不可变的类,对于可变的类,使用final修饰符可能会造成混乱。
使用类图描述类(参考工具:StarUML、Astah UML)。
注:
- +代表public,-代表private
- 属性名在前,后面跟着冒号和类型名
- 方法名在前,后面跟着冒号和返回值类型
- 如果有参数,参数类型写法同上
类和对象之间的关系:类是抽象的概念,仅仅是模板;对象是一个能看得见,摸得着的具体实体。
-
静态域与静态方法
1.静态域。如果将域定义为static,每个类中只有一个这样的域,而每一个对象对于所有的实例域都有自己的一份拷贝。
2.静态常量。public static void main(String[] args)另一个多次使用的静态常量是System.out
3.静态方法。静态方法是一种不能向对象操作的方法,可以认为静态方法是没有this参数的方法(在一个非静态的方法中this参数表示这个方法的隐式参数)。因为静态方法不能操作对象,所以不能在静态方法中访问实例域,但静态方法可以访问自身类中的静态域。在下面两种情况下使用静态方法:
- 一个方法不需要访问对象的状态,其所需参数都是通过显式参数提供
- 一个方法只需要访问类的静态域
4.工厂方法。NumberFormat类使用工厂方法产生不同风格的格式对象。不利用构造器完成相关操作的原因:①无法命名构造器。构造器的名字必须与类名相同。②当使用构造器时,无法改变所构造的对象类型。
5.main方法。
方法:为完成一个操作而组合在一起的语句组。内置方法:有JDK类库提供、需要导入相应的包。自定义方法:不带参数、带参数。
Math.ceil(x):向上取整x最接近的整数,取大于x的整数。
Math.floor(x):向下取整x最接近的整数,取小于x的整数。
- 定义方法
由方法名、返回值类型、参数列表和方法体组成。三要素:方法名、返回值类型、参数列表
修饰符 返回值类型 方法名([参数列表]){
//方法体
}
- 关于“定义方法”和“声明变量”
- 定义是指被定义的条目是什么。
- 声明通常是指为被声明的条目分配内存来存储数据。
-
方法参数
按值调用:表示方法接收的是调用者提供的值call by value
按引用调用:表示方法接收的是调用者提供的变量地址call 毕业reference
Java程序设计语言总是采用按值调用,即方法得到的是所有参数值的一个拷贝,特别是,方法不能修改传递给它的任何参数变量的内容。
Java程序设计语言中方法参数的使用情况:
- 一个方法不能修改一个基本数据类型的参数(即数值型和布尔型)
- 一个方法可以改变一个对象参数的状态
- 一个方法不能让对象参数引用一个新的对象
调用方法时,方法的参数个数无法确定。注意:使用不定长度参数时,必须是参数的最后一个,一个方法里只能有一个不定长参数。
构造方法:
- 没有返回值类型
- 名称与类名一致
- 可以指定参数及实现重载
- 注意隐式构造和参数化构造不能共存
-
对象的构造
1.重载(overloading)。如果多个方法有相同的名字,不同的参数,便产生了重载。编译器必须挑选出具体执行哪个方法,通过各个方法给出的参数类型与特定方法调用所使用的值类型进行匹配来挑选出相应的方法。如编译器找不到匹配的参数,或者找出多个可能的匹配,就会产生编译错误(此过程称为重载解析)
注:Java允许重载任何方法,而不只是构造器方法。因此,要完整地描述一个方法需要指出方法名和参数类型,这叫做方法的签名(signature)。返回类型不是方法签名的一部分,即不能有两个名字相同,参数类型也相同却返回不同类型的方法。
2.默认域初始化。数值为0;布尔值为false;对象引用为null
注:这是域与局部变量的主要不同点。必须明确初始化方法中的局部变量,如没有初始化类中的域,将会被初始化为默认值。
3.无参数的构造器。很多类都包含一个无参数的构造函数,对象由无参数构造函数创建时,其状态会设置为适当的默认值。
4.显式域初始化。
5.参数名。通常,参数用单个字符命名,但缺点是只有阅读代码才能了解其含义,于是,有些程序员在每个参数前面加上一个前缀“a”,还有一种常用的技巧,基于这样的事实:参数变量用同样的名字将实例域屏蔽起来。this.name = name;
6.调用另一个构造器。关键字this引用方法的隐式参数。然,此关键字还有另外一个含义,如构造器的另外一个语句形如this(...),这个构造器将调用同一类的另一个构造器。
7.初始化块
初始化数据域的方法:
- 在构造器中设置组
- 在声明中赋值
- 初始化块
调用构造器的具体处理步骤:
- 所有数据域被初始化为默认值(0、false或null)。
- 按照在类声明中出现的次序,依次执行所有域初始化语句或初始化块。
- 如果构造器第一行调用了第二个构造器,则执行第二个构造器主体。
- 执行这个构造器的主体。
8.对象析构与finalize方法
-
包(package-类似文件夹性质)
- 打包。package com.dilili.javabase
- 类的导入。可以采用两种方式访问另一个包中的公有类:第一种方式是在每个类名之前添加完整的包名,更简单常用的是使用import语句。import java.util.Scanner
- 静态导入。import语句步进可以导入类,还增加了导入静态方法和静态域的功能。
- 将类放入包中。!编译器在编译源文件的时候不检查目录结构。
- 包作用域。变量必须显式地标记为private,否则将默认包为可见,破坏封装性。
包的作用:
- 包允许将类组合成较小的单元(类似文件夹),使其易于找到和使用相应的类文件。
- 有助于避免命名冲突。在使用许多类时,类和方法的名称很难决定。有时需要使用与其他类相同的名称,包基本上隐藏了类,并避免了名称上的冲突。
- 包允许在更广的范围内保护类、数据和方法,可以在包内定义类,而在包外的代码不能访问该类。
注:1.包将类名空间划分为更加容易管理的块。2.包既是命名机制,也是可见度控制机制。
-
类路径
为了使类能够被多个程序共享,需要做到以下几点:
- 把类放到一个目录中,需注意这个目录是包树状结构的基目录。
- 将JAR文件放在一个目录中。
- 设置类路径(class path),类路径是包含所有类文件路径的集合。
类路径包括:
- 基目录 /home/user/clasdir或c:\classes;
- 当前目录(.)
- JAR文件
设置类路径,最好采用-classpath(或-cp)选项指定类路径。
-
文档注释
- 类注释必须放在import语句之后,类定义之前。
- 每一个方法注释必须放在所描述的方法之前。
- 域注释,只需要对公有域(通常指静态常量)建立文档。
- 通用注释。
- 包与概述注释。要想产生包注释,就需要在每一个包目录中添加一个单独的文件:提供一个以package.html命名的HTML文件;提供一个以package.info.java命名的Java文件。
- 注释的抽取。
-
类设计技巧
- 一定要保证数据私有
- 一定要对数据初始化
- 不要在类中使用过多的基本类型
- 不是所有的域都需要独立的域访问器和域更改器
- 将职责过多的类进行分解
- 类名和方法名要能够体现它们的职责
实例1:使用面向对象的方式创建一个圆类,分别计算周长和面积
import java.util.Scanner;
/**
* 使用面向对象的方式创建一个圆类,分别计算周长和面积
1.定义类:Circle
2.抽取属性
3.定义方法
4.调用方法并打印结果
* @author 棣哩哩
* @date 2018年11月9日 下午8:43:52
* @remarks TODO
*/
public class Circle {
/** 圆的半径*/
public double radius;
/** 圆的周长*/
public double perimeter;
/** 圆的面积*/
public double area;
public void inputRadius(){
Scanner input = new Scanner(System.in);
System.out.print("请输入圆的半径:");
radius = input.nextDouble();
input.close();
}
public void showPerimeter(){
if(radius <= 0){
inputRadius();//如果用户没有输入半径,那么强制用户输入半径
}
perimeter = 2 * Math.PI * radius;
System.out.println("周长为:" + perimeter);
}
public void showArea(){
if(radius <= 0){
inputRadius();//如果用户没有输入半径,那么强制用户输入半径
}
area = Math.PI * Math.pow(radius, 2);
System.out.println("面积为:" + area);
}
}
import java.util.Scanner;
/**
* 使用面向对象的方式创建一个圆类,分别计算周长和面积
1.定义类:Circle
2.抽取属性
3.定义方法
4.调用方法并打印结果
* @author 棣哩哩
* @date 2018年11月9日 下午8:43:52
* @remarks TODO
*/
public class Circlev2 {
/** 圆的半径*/
public double radius;
/** 圆的周长*/
public double perimeter;
/** 圆的面积*/
public double area;
public Circlev2(){
inputRadius();
}
public Circlev2(double radius1){
if(radius1 > 0){
radius = radius1;
}else{
inputRadius();
}
}
private void inputRadius(){
Scanner input = new Scanner(System.in);
System.out.print("请输入圆的半径:");
radius = input.nextDouble();
input.close();
}
public void showPerimeter(){
if(radius <= 0){
inputRadius();//如果用户没有输入半径,那么强制用户输入半径
}
perimeter = 2 * Math.PI * radius;
System.out.println("周长为:" + perimeter);
}
public void showArea(){
if(radius <= 0){
inputRadius();//如果用户没有输入半径,那么强制用户输入半径
}
area = Math.PI * Math.pow(radius, 2);
System.out.println("面积为:" + area);
}
}
public class Circlev3 {
/** 圆的半径*/
private double radius;
/** 圆的周长*/
private double perimeter;
/** 圆的面积*/
private double area;
public double getRadius(){
return radius;
}
public void show(){
System.out.println("周长为:" + this.getPerimeter());
System.out.println("面积为:" + this.getArea());
}
public Circlev3() {}
public Circlev3(double radius) {
//this.radius = radius;
this.setRadius(radius);
}
public void setRadius(double radius){
if(radius <= 0){//封装的好处,可以对传入的属性进行简单的验证
radius = 1;
}else{
//this->自指针 朕/寡人
this.radius = radius;
}
}
public double getPerimeter() {
perimeter = 2 * Math.PI * radius;
return perimeter;
}
public double getArea() {
area = Math.PI * Math.pow(radius, 2);
return area;
}
}
import java.util.Scanner;
public class CircleTest {
public static void main(String[] args) {
// Circle circle = new Circle();
// circle.inputRadius();
// circle.showPerimeter();
// circle.showArea();
// Circlev2 circle = new Circlev2();
// circle.showArea();
Scanner input = new Scanner(System.in);
System.out.print("请输入圆的半径:");
double radius = input.nextDouble();
Circlev3 circle = new Circlev3(radius);
circle.show();
}
}
实例2:书写代码,将数字星期转换为文字版并输出
import java.util.Scanner;
/**
* 书写代码,将数字星期转换为文字版并输出
* @author 棣哩哩
* @date 2018年10月30日 下午9:13:17
* @remarks TODO
*/
public class WeekDayDemo {
public static void main(String[] args) {
System.out.print("请输入今天是星期几:");
int dayOfWeek = new Scanner(System.in).nextInt();
//中文
ShowWeekDayByChinese(dayOfWeek-1);
//日文
ShowWeekDayByJapanese(dayOfWeek-1);
//英文
ShowWeekDayByEnglish(dayOfWeek-1);
}
/**
* 用来判断输入的参数是否在1-7之间
* @param dayOfWeek 要判断的整型数字,星期几
* @return 如果合法,返回true。非法,返回false
*/
private static boolean isRight(int dayOfWeek){
if(dayOfWeek < 1 || dayOfWeek> 7){
//System.out.println("必须输入1-7之间的数字");
return false;
}
return true;
}
/**
* 以中文的方式打印星期几
* @param dayOfWeek 1-7之间的整型数字,再来表示星期几
*/
public static void ShowWeekDayByChinese(int dayOfWeek){
if(!isRight(dayOfWeek)){
System.out.println("必须输入1-7之间的数字");
return;
}
String[] weekdays = {"星期一", "星期二", "星期三", "星期四", "星期五", "星期六", "星期日"};
System.out.println(weekdays[dayOfWeek]);
}
static void ShowWeekDayByJapanese(int dayOfWeek){
if(!isRight(dayOfWeek)){
System.out.println("必须输入1-7之间的数字");
return;
}
String[] weekdays = {"月曜日", "火曜日", "水曜日", "木曜日", "金曜日", "土曜日", "日曜日"};
System.out.println(weekdays[dayOfWeek]);
}
static void ShowWeekDayByEnglish(int dayOfWeek){
if(!isRight(dayOfWeek)){
System.out.println("必须输入1-7之间的数字");
return;
}
String[] weekdays = {"Monday", "Tuesday", "Wednessday", "Thursday", "Friday", "Saturday", "Sunday"};
System.out.println(weekdays[dayOfWeek]);
}
}
实例三:十六进制和十进制之间的转换
import java.net.StandardSocketOptions;
import java.util.Scanner;
/**
* 十六进制和十进制之间的转换
* @author 棣哩哩
* @date 2018年11月6日 上午9:12:36
* @remarks TODO
*/
public class HexToDecimalDemo {
public static void main(String[] args) {
Scanner input = new Scanner(System.in);
//1.让用户输入一个十进制的数字
System.out.print("请输入一个整型数字:");
int decNum = input.nextInt();
//2.调用一个方法,将十进制转换成十六进制
System.out.println(decToHex(decNum));
//3.再将十六进制转换回十进制
System.out.println(hexToDec(decToHex(decNum)));
}
/**
* 十六进制转换为十进制
* @param hexNum 要转换的十六进制数字
* @return 转换成功的十进制数字
*/
public static int hexToDec(String hexNum){
int dec = 0;
//6E -> 6 * 16 + 14 * 1
//需要遍历十六进制字符串
//如果数组中首元素下标为i,那么最后一个元素下标为:len - i - 1
for (int i = 0; i < hexNum.length(); i++) {
//取出字符串中的每一个字符串进行判断
char tempChar = hexNum.charAt(i);
//两种情况,字符0-9或字符A-F
if(tempChar >= '0' && tempChar <= '9'){
dec += (tempChar - '0') * Math.pow(16, hexNum.length() - i - 1);
}else if(tempChar >= 'A' && tempChar <= 'F'){
dec += (tempChar - 'A' + 10) * Math.pow(16, hexNum.length() - i - 1);
}
}
return dec;
}
/**
* 将十进制转换成十六进制
* @param decNum 要转换的十进制数字
* @return 换换成十六进制的数字(以字符串的方式返回)
*/
public static String decToHex(int decNum){
//45/16 反取余数
String hex = ""; //转换好的十六进制字符串
while(decNum != 0){
int temp = decNum % 16;//取余数
//余数可能是0-9或10-15之间的数字
if(temp >= 0 && temp <= 9){
hex = temp + hex; //每取一个余数,就往前拼接
}else if(temp >= 10 && temp <= 15){
//把数字转换成字符再拼接
hex = (char)(temp - 10 + 'A') + hex;
}
//卸磨杀驴
decNum /= 16;
}
return hex;
}
}
实例四:使用方法实现月历的打印
import java.util.Scanner;
/**
* 使用方法实现月历的打印
* @author 棣哩哩
* @date 2018年11月7日 上午9:01:53
* @remarks TODO
*/
public class PrintCalendarDemo {
/** 用户输入的年份*/
public static int year = Integer.MIN_VALUE;
/** 用户输入的月份*/
public static int month = Integer.MIN_VALUE;
/**对应每个月份的天数*/
private static int[] dayOfMonth = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
public static void main(String[] args) {
PrintCalendar();
//getSumDayOfYears();
}
/**
* 打印月历的核心方法
*/
private static void PrintCalendar() {
//1.让用户输入年份和月份
InputYearAndMonth();
//2.计算1900-1-1到用户输入月份的总天数(year=2017, month=7 2017-7-1)
// 2-1.计算各年的总天数
// 2-2.计算各月的天数之和
int sum = getSumDayOfYears();
sum += getSumDayOfMonth();
sum++;
if (sum % 7 == 0){
System.out.println(7);
}else{
System.out.println(sum % 7);
}
//3.打印年份和月份(英文)
//4.打印月份的标题(星期一-星期日)
PrintMonthTitle();
//5.根据某月1日是星期几打印月历内容
PrintCalendarContent(sum % 7);
}
/**
* 根据当月1号是星期几打印月历内容
* @param dayOfWeek 当月1号是星期几
*/
private static void PrintCalendarContent(int dayOfWeek){
//注意dayOfWeek取值范围是0-6
int sepCount = 0; //\t的数量
if(dayOfWeek == 0){
sepCount = 6;
}else{
sepCount = dayOfWeek - 1;
}
for (int i = 0; i < sepCount; i++) {
System.out.print("\t");
}
for (int i = 0; i < dayOfMonth[month - 1]; i++) {
System.out.print(i + 1);
if((dayOfWeek + i) % 7 == 0){
//星期日
System.out.println();
}else{
System.out.print("\t");
}
}
}
/**
* 打印标题
*/
private static void PrintMonthTitle(){
String[] monthNames = {"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"};
System.out.println("\t\t" + year + "\t" + monthNames[month -1]);
String[] weekdays = {"星期一", "星期二", "星期三", "星期四", "星期五", "星期六", "星期日"};
for (int i = 0; i < weekdays.length; i++) {
System.out.print(weekdays[i] + "\t");
}
System.out.println();
}
/**
* 获得1900-year年的总天数
* @return
*/
private static int getSumDayOfYears(){
//需要判断用户是否已经输入了年份
if(year == Integer.MIN_VALUE){
System.out.println("年份错误,请重新输入年份和月份");
InputYearAndMonth();
}
int sum = 0;
for (int i = 1900; i < year; i++) {
sum += 365; //每一年累积365天
if(isLeapYear(i)){
sum++; //闰年多加一天
}
}
return sum;
}
/**
* 得到year年1月1日到year年month-1月最后一天的总天数
* @return
*/
private static int getSumDayOfMonth(){
int sum = 0;
for (int i = 0; i < month - 1; i++) {
sum += dayOfMonth[i];
}
//如果year年是闰年,并且month>=3
if(isLeapYear(year) && month >= 3){
sum++;
}
return sum;
}
/**
* 用来判断传入年份是否为闰年
* @param year 要判断的年份
* @return 是闰年,返回true
*/
private static boolean isLeapYear(int year){
return year % 400 == 0 || year % 4 == 0 && year % 100 != 0;//先算与关系,再算或关系
}
/**
* 接收用户输入年份和月份
*/
private static void InputYearAndMonth(){
Scanner input = new Scanner(System.in);
System.out.print("请输入年份:");
year = input.nextInt();
System.out.print("请输入月份:");
month = input.nextInt();
input.close();
input = null;
}
}