第四章 面向对象
面向对象思想
面向对象思想的引入
前面我们讲过数组,当有多个数组都需要遍历时,我们可以将遍历的代码封装到方法中,需要遍历时,就调用相应的方法即可,提高代码的复用性。在对数组遍历的基础上继续增加需求,比如获取最值,数值逆序等,同样需要将这些功能封装到相应的方法中。这样继续封装会发现方法越来越多,于是就想能不能将这些方法继续进行封装呢?通过前面的讲解我们知道类是可以存放方法的,所以,我们就考虑使用类封装来这多个方法,将来再做数组的操作时,不用去找具体的方法,先找到这个类,然后使用这个类中的方法。这就是面向对象思想的编程方式。
1:面向对象思想
面向对象是基于面向过程的编程思想。
面向过程:强调的是每一个功能的步骤
面向对象:强调的是对象,然后由对象去调用功能
2:面向对象的思想特点
- A:是一种更符合我们思想习惯的思想
- B:可以将复杂的事情简单化
- C:将我们从执行者变成了指挥者
3.把大象装进冰箱
面向过程:
A:打开冰箱门
B:装进大象
C:关闭冰箱门
代码体现;
class Demo {
public static void main(String[] args) {
open();
in();
close();
}
public static void open() {
System.out.println("打开冰箱门");
}
public static void in() {
System.out.println("装进大象");
}
public static void close() {
System.out.println("关闭冰箱门");
}
}
面向对象:
A:有哪些类呢?
B:每个类有哪些东西呢?
C:类与类直接的关系是什么呢?
把大象装进冰箱的分析? (如何分析有哪些类呢?UML。名词提取法。)
- A:有哪些类呢?
大象
冰箱
Demo - B:每个类有哪些东西呢?
大象:
进去
冰箱:
开门
关门
Demo:
main方法 - C:类与类直接的关系是什么呢?
Demo中使用大象和冰箱类的功能。
代码体现:
class 大象 {
public static void in() {
System.out.println("装进大象");
}
}
class 冰箱 {
public static void open() {
System.out.println("打开冰箱门");
}
public static void close() {
System.out.println("关闭冰箱门");
}
}
class Demo {
public static void main(String[] args) {
冰箱调用开门
大象调用进去
冰箱调用关门
}
}
面向对象开发
就是不断的创建对象,使用对象,指挥对象做事情。
面向对象设计
其实就是在管理和维护对象之间的关系。
面向对象特征
封装(encapsulation)
继承(inheritance)
多态(polymorphism)
类与对象及其使用
区分:成员变量和局部变量的区别,静态变量和成员变量的区别
--确定成员变量和成员方法时要注意---
属性(成员变量)就是该事物(类)的描述信息
行为(成员方法)就是该事物(类)能够做什么
类:是一组相关的属性和行为的集合
对象:是该类事物的具体体现
定义类其实就是定义类的成员(成员变量和成员方法)
1:成员变量 和以前定义变量是一样的,只不过位置发生了改变。在类中,方法外。
2:成员方法 和以前定义方法是一样的,只不过把static去掉,后面在详细讲解static的作用。
定义一个学生类
/*StudentDemo.java*/
class Student{
String name;//默认null,堆中未初始化变量有默认值
int age;//0
String add;//null
public void study(){
System.out.println("学生学习");
}
public void eat(){
System.out.println("学生吃饭");
}
public void sleep(){
System.out.println("学生睡觉");
}
}
class StudentDemo{
public static void main(String[] args){
Student s = new Student();
System.out.println(s.name + "\t" + s.age + "\t" + s.add);
s.name = "小白";//给成员变量赋值
s.age = 10;
s.add = "北京";
System.out.println(s.name + "\t" + s.age + "\t" + s.add);
s.study();//调用方法
s.eat();
s.sleep();
System.out.println(p);//Student@19e0bfd 打印的是地址值!!!!
}
}
创建对象:
类名 对象名 = new 类名();
对象名.成员变量
对象名.成员方法
/*PhoneDemo.java*/
class Phone{
String brand;
int price;
String color;
public void call(String name) {
System.out.println("给"+name+"打电话");
}
public void sendMessage() {
System.out.println("群发短信");
}
public void playGame() {
System.out.println("玩游戏");
}
}
class PhoneDemo{
public static void main(String[] args){
Phone p = new Phone();
System.out.println(p.brand + "\t" + p.price + "\t" + p.color);
p.brand = "魅族";
p.price = 1000;
p.color = "白色";
System.out.println(p.brand + "\t" + p.price + "\t" + p.color);
p.call("黄章");
p.sendMessage();
p.playGame();
}
}
对象的内存图
一个对象的内存图,一个对象的基本初始化过程
![一个对象的内存图](http://7xr4lz.com1.z0.glb.clouddn.com/16-6-7/32043783.jpg)
两个对象的内存图,方法的共用
![两个对象的内存图](http://7xr4lz.com1.z0.glb.clouddn.com/16-6-7/57303247.jpg)
三个对象的内存图,其中有两个引用指向同一个(堆内的)对象
![两个引用指向同一对象](http://7xr4lz.com1.z0.glb.clouddn.com/16-6-7/98630159.jpg)
成员变量和局部变量的区别
(静态变量和成员变量的区别呢)
1.在类中的位置不同
成员变量 类中方法外
局部变量 方法内或者方法声明上
2.在内存中的位置不同
成员变量 堆内存
局部变量 栈内存
3.生命周期不同
成员变量 随着对象的存在而存在,随着对象的消失而消失
局部变量 随着方法的调用而存在,随着方法的调用完毕而消失
4.初始化值不同
成员变量 有默认的初始化值,可以不用赋值
局部变量 没有默认的初始化值,必须先定义,赋值,才能使用。
(栈内存的变量必须先赋值才能使用。
而堆内存中的变量有默认值,可以直接使用)
注:局部变量名称可以和成员变量同名,使用时采用就近原则。
class Variable{
//成员变量
int num = 10;//若未赋值,默认为0
public void show(){
int num2 = 20;//局部变量,必须赋值使用
}
}
class VariableDemo{
public static void main(String[] args){
Variable v = new Variable();
}
}
类类型形式参数问题
基本类型作为形式参数
引用类型(类类型)作为形式参数
//形式参数是基本类型
class Demo{
public int sum(int a,int b){
return a + b;
}
}
//形式参数是引用类型
class Student{
public void show(){
System.out.println("我爱学习");
}
}
class StudentDemo{
//若一个方法的形参为类类型(引用类型)
//调用时这里要传入这个类的一个对象。
public void method(Student s){
s.show();
}
}
class ArgsTest{
public static void main(String[] args){
//形参是基本类型的调用
Demo d = new Demo();
int result = d.sum(10,20);
System.out.println("result:"+result);
//形参是引用类型的调用
//需求:调用StudentDemo类中的method方法
Student s = new Student();//s实际是一个地址
StudentDemo sd = new StudentDemo();
sd.method(s);//传入一个Student的实例(地址)。
}
}
匿名对象
匿名对象:就是没有名字的对象。
是对象的一种简化表示形式
匿名对象的两种使用情况
1.对象调用方法仅一次的时候,多次时不适合。
匿名对象调用完毕成为垃圾,被垃圾回收器回收。
2.作为实际参数传递
class Student{
public void show(){
System.out.println("我爱学习");
}
}
class StudentDemo{
public void method(Student s){
s.show();
}
}
class NoNameDemo{
public static void main(String[] args){
Student s = new Student();
s.show();
s.show();//一个对象,调用两次
//匿名对象
//匿名对象调用方法
new Student().show();
new Student().show();//这里重新创建了一个新的对象
//匿名对象作为实际参数传递
StudentDemo sd = new StudentDemo();
sd.method(new Student());
new StudentDemo().method(new Student());
}
}
封装(private)
/*StudentDemo.java*/
/*通过对象给成员变量赋值,可以赋一些非法数据,
这是不合理的。
应该在赋值之前先判断。判断在哪里做?
解决办法:
1,类提供修改成员变量值的方法并含验证
2,将类成员变量定为 private
(被private修饰的成员只能在本类中使用)
*/
class Student{
String name;
int age;//设为private,外界将不能访问。
public void show(){
System.out.println("姓名:"+name);
System.out.println("年龄:"+age);
}
public void setAge(int a){//提供修改方法
if(a>=0)
age = a;
else
System.out.println("给定年龄错错误");
}
}
class StudentDemo{
public static void main(String[] args){
Student s = new Student();
s.show();
System.out.println("-----------");
s.name = "小白";
s.age = 10;
s.show();
System.out.println("-----------");
s.age = -27;//若age为private,则出错
s.show();//-27赋值不合理
System.out.println("-----------");
s.setAge(-27);//可以进行判断
s.show();//
System.out.println("-----------");
}
}
封装:指隐藏对象的属性和实现细节,仅对外提供公共访问方式。
好处:
隐藏实现细节,提供公共的访问方式
提高了代码的复用性
提高安全性。
封装原则:
将不需要对外提供的内容都隐藏起来。
把属性隐藏,提供公共方法对其访问。
private关键字:
是一个权限修饰符。
可以修饰成员(成员变量和成员方法)
被private修饰的成员只在本类中才能访问。
private最常见的应用:
把成员变量用private修饰
提供对应的getXxx()/setXxx()方法
一个标准的案例的使用
/*
private的应用:
把成员变量用private修饰
提供对用的getXxx()和setXxx()方法*/
class Student{
private String name;
private int age;
public int getAge(){
return age;
}
public String getName(){
return name;
}
public void setAge(int a){
age = a;
}
public void setName(String n){
name = n;
}
}
class StudentTest{
public static void main(String[] args){
Student s = new Student();
//错误,访问不到
//System.out.println(s.age);
s.setAge(10);
s.setName("小白");
System.out.println(s.getName()+": "+s.getAge());
}
}
this关键字
来看这样一段类定义
class Student{
private String name;
private int age;
public int getAge(){
return age;
}
public String getName(){
return name;
}
public void setAge(int age){
age = age;//根据就近原则,其实没有修改成员变量age
}
public void setName(String name){
name = name;//根据就近原则,其实没有修改成员变量age
}
}
这样在对象调用set方法时,其实没有改掉成员变量age和name。
由此引入 this
public void setAge(int age){
this.age = age;//可以改变成员变量age
}
public void setName(String name){
this.name = name;//可以改变成员变量name
}
this:代表所在类的对象引用
记住:
方法被哪个对象调用,this就代表那个对象
什么时候使用this呢?
1.局部变量隐藏成员变量(上例)
2.其他用法后面和super一起讲解
方法被哪个对象调用,this就代表那个对象
【0701this关键字的内存图解】
![this关键字](http://7xr4lz.com1.z0.glb.clouddn.com/16-6-8/38984399.jpg)
/*
private的应用:
把成员变量用private修饰
提供对用的getXxx()和setXxx()方法*/
class Phone{
private String brand;
private int price;
private String color;
public String getBrand(){
return brand;
}
public int getPrice(){
return price;
}
public String getColor(){
return color;
}
public void setBrand(String brand){
this.brand = brand;
}
public void setPrice(int price){
this.price = price;
}
public void setColor(String color){
this.color = color;
}
}
class PhoneTest{
public static void main(String[] args){
Phone p = new Phone();
System.out.println(p.getBrand()+"\t"+p.getPrice()+"\t"+p.getColor());
p.setBrand("魅族");
p.setPrice(999);
p.setColor("黑白");
System.out.println(p.getBrand()+"\t"+p.getPrice()+"\t"+p.getColor());
}
}
构造方法
构造方法作用:给对象的数据进行初始化
构造方法格式
方法名与类名相同
没有返回值类型,连void都没有
没有具体的返回值
构造方法注意事项---
如果你不提供构造方法,系统会给出默认构造方法
如果你提供了构造方法,系统将不再提供
注意:这时若还想用无参构造方法,要自己显式给出。
建议永远自己给出无参构造方法。
构造方法也是可以重载的
给成员变量赋值有两种方法:
- setXxx();
2.带参构造方法
class Student{
private String name;
private int age;
public Student(){
System.out.println("这是无参构造方法");
}
public Student(String name){
System.out.println("这是带String参构造方法");
this.name = name;
}
public Student(int age){
System.out.println("这是带int参构造方法");
this.age = age;
}
public Student(String name,int age){
System.out.println("这是带两参构造方法");
this.age = age;
this.name = name;
}
public void show(){
System.out.println(name+": "+age);
}
}
class ConstructDemo{
public static void main(String[] args){
//创建对象
Student s1 = new Student();//调用无参构造方法
System.out.println(s1);//打印地址
s1.show();
//创建对象
Student s2 = new Student("小白");//调用带参构造方法
System.out.println(s2);//打印地址
s2.show();
//创建对象
Student s3 = new Student(10);//调用带参构造方法
System.out.println(s3);//打印地址
s3.show();
//创建对象
Student s4 = new Student("小黑",20);//调用带参构造方法
System.out.println(s4);//打印地址
s4.show();
}
}
类的成员方法
成员方法其实就是我们前面讲过的方法
方法具体划分:
根据返回值
有明确返回值方法
返回void类型的方法
根据形式参数
无参方法
带参方法
一个基本类的标准写法
类
成员变量
构造方法
无参构造方法
带参构造方法
成员方法
getXxx()
setXxx()
给成员变量赋值的方式
无参构造方法+setXxx()
带参构造方法
一个基本类的标准代码案例
/*
一个标准代码的最终版(学生类)
*/
class Student{
private String name;
private int age;
public Student(){
}
public Student(String name,int age){
this.age = age;//构造方法给成员变量赋值
this.name = name;
}
public void setName(String name){
this.name = name;
}
public void setAge(int age){
this.age = age;//set方法赋值
}
public String getName(){
return name;
}
public int getAge(){
return age;
}
public void show(){
System.out.println(name+": "+age);
}
}
class StudentDemo{
public static void main(String[] args){
Student s1 = new Student();//调用无参构造方法
s1.setName("小白");
s1.setAge(10);
System.out.println(s1.getName()+": "+s1.getAge());
s1.show();
Student s2 = new Student("小黑",20);
System.out.println(s2.getName()+": "+s2.getAge());
s2.show();
}
}
/*
一个标准代码的最终版(手机类)
*/
class Phone{
private String brand;
private int price;
private String color;
public Phone(){
}
public void setBrand(String brand){
this.brand = brand;
}
public void setPrice(int price){
this.price = price;
}
public void setColor(String color){
this.color = color;
}
public String getBrand(){
return brand;
}
public int getPrice(){
return price;
}
public String getColor(){
return color;
}
}
class PhoneDemo{
public static void main(String[] args){
Phone p = new Phone();//调用无参构造方法
p.setBrand("魅族");
p.setPrice(1000);
p.setColor("黑白");
System.out.println(p.getBrand()+"-"+p.getPrice()+"-"+p.getColor());
}
}
类的初始化过程
Student s = new Student();在内存中做了哪些事情?
加载Student.class文件进内存
在栈内存为s开辟空间
在堆内存为学生对象开辟空间
对学生对象的成员变量进行默认初始化
对学生对象的成员变量进行显示初始化
通过构造方法对学生对象的成员变量赋值
学生对象初始化完毕,把对象地址赋值给s变量
![创建对象做了哪些事情](http://7xr4lz.com1.z0.glb.clouddn.com/16-6-8/69160791.jpg)
static关键字
static修饰成员变量:针对多个对象共享成员变量的时候
/*
定义一个人类
姓名和年龄都是变化的,因为每个人的姓名和年龄是不同的。
但是,我们现在选取的几个人都是中国人,他们的国籍是一样的。
一样的国籍,我每次创建对象,在堆内存都要开辟这样的空间,
我就觉得有点浪费了。怎么办呢?
针对多个对象有共同的成员变量的时候,
Java就提高了一个关键字来修饰:static。
*/
class Person {
//姓名
String name;
//年龄
int age;
//国籍
//String country;
static String country;//采用static,所有对象共享country
public Person(){}
public Person(String name,int age) {
this.name = name;
this.age = age;
}
public Person(String name,int age,String country) {
this.name = name;
this.age = age;
this.country = country;
}
public void show() {
System.out.println("姓名:"+name+",年龄:"+age+",国籍:"+country);
}
}
class PersonDemo {
public static void main(String[] args) {
//创建对象1
Person p1 = new Person("邓丽君",16,"中国");
p1.show();
//创建对象2
//Person p2 = new Person("杨幂",22,"中国");
//p2.show();
Person p2 = new Person("杨幂",22);
p2.show();//若country不是静态,则国籍为空
//创建对象3
//Person p3 = new Person("凤姐",20,"中国");
//p3.show();
Person p3 = new Person("凤姐",20);
p3.show();//若country不是静态,则国籍为空
p3.country = "美国";
p3.show();//美国
p1.show();//美国
p2.show();//美国
}
}
static 可以修饰成员变量和成员方法
static关键字特点
static 可以修饰成员变量和成员方法
1.随着类的加载而加载
2.优先于对象存在(对象在new以后才有)
3.被类的所有对象共享
这也是告诉我们是么时候使用静态关键字
若某个成员变量被所有对象共享,那么它应该定义为静态。
4.可以通过类名调用(也可以用对象调,建议用类名调用)
(main方法被虚拟机调用,直接 类名.main())
![static的内存图解](http://7xr4lz.com1.z0.glb.clouddn.com/16-6-8/28956180.jpg)
静态的内容存在于方法区的静态区
静态的东西随class的加载而加载
static关键字注意事项
A:在静态方法中是没有this关键字的
如何理解呢?
静态是随着类的加载而加载,this是随着对象的创建而存在。
静态比对象先存在。
B:静态方法只能访问静态的成员变量和静态的成员方法
静态方法:
成员变量:只能访问静态变量
成员方法:只能访问静态成员方法
非静态方法:
成员变量:可以是静态的,也可以是非静态的
成员方法:可是是静态的成员方法,也可以是非静态的成员方法。
简单记:静态只能访问静态。
class Teacher {
public int num = 10;
public static int num2 = 20;
public void show() {
System.out.println(num); //隐含的告诉你访问的是成员变量
System.out.println(this.num); //明确的告诉你访问的是成员变量
System.out.println(num2);//非静态方法可以访问静态变量
function();//非静态访问非静态
function2();//非静态访问静态
}
public static void method() {
//无法从静态上下文中引用非静态 变量 num
//System.out.println(num);
System.out.println(num2);
//无法从静态上下文中引用非静态 方法 function()
//function();//静态不能访问非静态
function2();//静态访问静态
}
public void function() {
}
public static void function2() {
}
}
class TeacherDemo {
public static void main(String[] args) {
//创建对象
Teacher t = new Teacher();
t.show();
System.out.println("------------");
t.method();
}
}
无法从静态上下文中引用非静态 变量 num
无法从静态上下文中引用非静态 方法 function()
静态变量和成员变量的区别
(成员变量和局部变量的区别呢?)
所属不同
静态变量属于类,所以也称为为类变量(随着类的加载而加载)
成员变量属于对象,所以也称为实例变量(对象变量)
内存中位置不同
静态变量存储于方法区的静态区
成员变量存储于堆内存
内存出现时间不同
静态变量随着类的加载而加载,随着类的消失而消失
成员变量随着对象的创建而存在,随着对象的消失而消失
调用不同
静态变量可以通过类名调用,也可以通过对象调用
成员变量只能通过对象名调用
main方法是静态的
main方法是静态的,因此main()里面调用的方法必须是静态的。
非静态的方法只能通过造一个对象来调用。
public static void main(String[] args) {}
public:公共的,访问权限是最大的。由于main方法是被jvm调用,所以权限要够大。
static:静态的,不需要创建对象,通过类名就可以调用。方便jvm的调用。
void:方法的返回值是返回给调用者,而main方法是被jvm调用。返回内容给jvm没有意义。
main:是一个常见的方法入口。我见过的语言都是以main作为入口。
String[] args:这是一个字符串数组。值去哪里了?这个东西到底有什么用啊?怎么给值啊?
这个东西早期是为了接收键盘录入的数据参数的。
格式是:java MainDemo text1 text2 text3
/*MainDemo.java*/
class MainDemo {
public static void main(String[] args) {
//System.out.println(args); //[Ljava.lang.String;@175078b
//System.out.println(args.length); //长度是0,没有元素
//System.out.println(args[0]); //ArrayIndexOutOfBoundsException,没有元素,越界
//编译完成后,使用 java MainDemo text1 text2 text3命令运行
//接收数据后
System.out.println(args); //打印地址
System.out.println(args.length); //长度是3
//System.out.println(args[0]);
for(int x=0; x<args.length; x++) {//输出后面的三个参数text1 text2 text3
System.out.println(args[x]);
}
}
}
静态的一个应用(有小技巧)
在写好的工具类中使用静态
来写一个数组遍历
class ArrayDemo {
public static void main(String[] args) {
int[] arr = {28,55,37,46,19};
//静态方法
printArray(arr);
}
public static void printArray(int[] arr) {//静态方法
for(int x=0; x<arr.length; x++) {
if(x == arr.length-1)
System.out.println(arr[x]);
else
System.out.print(arr[x]+", ");
}
}
}
因为main方法是静态的,因此main()里面调用的方法必须是静态的。
万一遇到main调用非静态的的情况呢?
非静态的方法只能通过造一个对象来调用。
class ArrayDemo {
public static void main(String[] args) {
int[] arr = {28,55,37,46,19};
//非静态方法
ArrayDemo ad = new ArrayDemo();//可以这样造对象!
ad.printArray(arr);//非静态的方法只能通过造一个对象来调用
}
//假设该方法不是静态的
public void printArray(int[] arr) {
for(int x=0; x<arr.length; x++) {
if(x == arr.length-1)
System.out.println(arr[x]);
else
System.out.print(arr[x]+", ");
}
}
}
我们现在的操作是跟数组相关的,所以,应该把这些操作定义到数组操作类中
定义一个数组的操作类,有了数组操作类之后再调用。
注1:在同一个文件夹下,类定义在两个文件中和定义在一个文件中其实一样的。
注2:编译的时候,只需编译主类即可,被调用的类已经由jvm自动编译。
三种实现方法
1.ArrayTool.java 中 public void printArray(int[] arr) {}
ArrayDemo.java 中
ArrayTool at = new ArrayTool();
at.printArray(arr);
2.ArrayTool.java 中 public static void printArray(int[] arr) {}
ArrayDemo.java 中
ArrayTool at = new ArrayTool();
at.printArray(arr);//对象可以访问静态、非静态方法。
3.ArrayTool.java 中 public static void printArray(int[] arr) {}
ArrayDemo.java 中
ArrayTool.printArray(arr);//方法改进为静态后,就可以直接通过类名调用
/*ArrayTool.java*/
class ArrayTool {
//!!!!高能!!!!
//把构造方法私有,外界就不能再创建对象了
private ArrayTool(){}
public static void printArray(int[] arr) {
for(int x=0; x<arr.length; x++) {
if(x == arr.length-1) {
System.out.println(arr[x]);
}else {
System.out.print(arr[x]+", ");
}
}
}
}
在一个类中,把构造方法设为私有,把成员方法设为静态。
这样,其他类只能通过类名来访问该类的方法。------高能!!
/*ArrayDemo.java*/
class ArrayDemo {
public static void main(String[] args) {
int[] arr = {28,55,37,46,19};
//方法改进为静态后,就可以直接通过类名调用
ArrayTool.printArray(arr);
}
}
帮助文档的制作
项目中,我们一般拿到的不是 ArraryTool.java 源文件,
而是 ArrayTool.class 文件和它的帮助文档(使用说明)。
如何制作一个说明书呢?
A:写一个工具类
B:对这个类加入文档注释
怎么加呢?
加些什么东西呢?
C:用工具解析文档注释
javadoc工具
D:格式
javadoc -encoding UTF-8 -d 目录 -author -version ArrayTool.java
-encoding UTF-8是为了解决 编码GBK的不可映射字符 错误。
-d 的意思可以命令行中javadoc后看到。
目录:将生成的文档放在当前路径的这个目录里,若无,则自动创建。通常为 doc
.表示当前目录
制作帮助文档出错:
找不到可以文档化的公共或受保护的类:告诉我们类的权限不够
范例:
/**
* 这是针对数组进行操作的工具类
* @author Fitz
* @version V.10
*/
public class ArrayTool {
//把构造方法私有,外界就不能在创建对象了
/**
* 这是私有构造
*/
private ArrayTool(){}
/**
* 这是遍历数组的方法,遍历后的格式是:[元素1, 元素2, 元素3, ...]
* @param arr 这是要被遍历的数组
*/
public static void printArray(int[] arr) {
System.out.print("[");
for(int x=0; x<arr.length; x++) {
if(x == arr.length-1) {
System.out.println(arr[x]+"]");
}else {
System.out.print(arr[x]+", ");
}
}
}
/**
* 这是获取数组中最大值的方法
* @param arr 这是要获取最大值的数组
* @return 返回数组中的最大值
*/
public static int getMax(int[] arr) {
int max = arr[0];
for(int x=1; x<arr.length; x++) {
if(arr[x] > max) {
max = arr[x];
}
}
return max;
}
/**
* 获取指定元素在数组中第一次出现的索引,如果元素不存在,就返回-1
* @param arr 被查找的数组
* @param value 要查找的元素
* @return 返回元素在数组中的索引,如果不存在,返回-1
*/
public static int getIndex(int[] arr,int value) {
int index = -1;
for(int x=0; x<arr.length; x++) {
if(arr[x] == value) {
index = x;
break;
}
}
return index;
}
}
命令行中执行
javadoc -d doc -author -version ArrayTool.java
若报 编码GBK的不可映射字符 错误,则在javadoc后添加参数 -encoding UTF-8
之后便在当前目录生成doc文件夹。
/*ArrayDemo.java*/
class ArrayDemo {
public static void main(String[] args) {
//定义数组
int[] arr = {28,55,37,46,19};
//遍历
ArrayTool.printArray(arr);//通过类名调用静态方法
//获取最值
int max = ArrayTool.getMax(arr);
System.out.println("max:"+max);
//获取55的索引值
int index = ArrayTool.getIndex(arr,55);
System.out.println("index:"+index);
}
}
将ArrayTool.class 和 ArrayDemo.class放于一个文件夹下即可。
JDK帮助文档
如何使用帮助文档
1:打开帮助文档
2:点击显示,找到索引,看到输入框
3:知道你要找谁?以Scanner举例
4:在输入框里面输入Scanner,然后回车
5:看包,Scanner属于java.util包
因此要导入:java.util.Scanner
(java.lang包下的类不需要导入,其他的全部需要导入。)
6:再简单的看看类的解释和说明,别忘了看看该类的JDK版本
7:看类的结构
成员变量 -> 字段摘要
构造方法 -> 构造方法摘要
成员方法 -> 方法摘要
8:学习构造方法
A:有构造方法 就创建对象
B:没有构造方法 成员可能都是静态的
9:看成员方法
A:左边
是否静态:如果静态,可以通过类名调用
返回值类型:人家返回什么,你就用什么接收。
B:右边
看方法名:方法名称不要写错
参数列表:人家要什么,你就给什么;人家要几个,你就给几个
以Math类为例:
由于Math类在java.lang包下,所以不需要导包。
特点:没有构造方法,因为它的成员全部是静态的。
例子:
1.Math类的一个方法:获取随机数random();
public static double random()
返回带正号的 double 值,该值大于等于 0.0 且小于
1.0。返回值是一个伪随机选择的数,在该范围内(近似)均匀分布。
class MathDemo {
public static void main(String[] args) {
//获取1-100之间的随机数
for(int x=0; x<100; x++) {
int number = (int)(Math.random()*100)+1;
System.out.println(number);
}
}
}
2.猜数字小游戏(数据在1-100之间)
程序产生一个随机数(被猜的),玩家有多次机会猜
程序给出玩家猜大了还是猜小了的提示。直到玩家猜中。
import java.util.Scanner;
class MathDemo{
public static void main(String[] args){
Scanner sc = new Scanner(System.in);
System.out.println("Input a number(1-100):");
int guessNum = sc.nextInt();
int machineNum = (int)(Math.random()*100+1);
while(true){
if(guessNum!=machineNum){
if(guessNum>machineNum)
System.out.print("Too big!");
else
System.out.print("Too small!");
System.out.println("Input a number again:");
guessNum = sc.nextInt();
}
else{
System.out.println("machine:"+guessNum+"\tplayer:"+machineNum+"\nYou win!");
break;
}
}
}
}
代码块(易错易混)
在Java中,使用{}括起来的代码被称为代码块。
根据其位置和声明的不同,可以分为局部代码块,构造代码块,静态代码块,同步代码块(多线程讲解)。
局部代码块
在方法中出现;限定变量生命周期,及早释放,提高内存利用率
构造代码块
在类中方法外出现;多个构造方法方法中相同的代码存放到一起做成构造代码块,每次调用构造都执行,
并且在构造方法前执行。无论书写的位置前后,都先执行构造代码块,最后执行构造方法。
作用:可以把--多个构造方法中的共同代码--放到一起,对对象进行初始化。
静态代码块 在类中方法外出现,加了static修饰
在类中方法外出现,并加上static修饰;
用于给类进行初始化,在加载的时候就执行,并且仅第一次使用该类时执行。
作用:一般是对类进行初始化。
面试题
静态代码块,构造代码块,构造方法的执行顺序?
静态代码块 -- 构造代码块 -- 构造方法
静态代码块:仅在第一次使用该类时,执行一次
构造代码块:每次调用构造方法都执行
class Code {
//System.out.println("");//输出语句不能放在方法或代码块外执行
static {
int a = 1;
System.out.println("静态代码块"+a);
}
//构造代码块
{
int x = 1;
System.out.println("构造代码块"+x);
}
//构造方法
public Code(){
System.out.println("无参构造方法");
}
//构造方法
public Code(int a){
System.out.println("带参构造方法");
}
//构造代码块
{
int y = 2;
System.out.println("构造代码块"+y);
}
//静态代码块
static {
int b = 2;
System.out.println("静态代码块"+b);
}
}
class CodeDemo {
public static void main(String[] args) {
//局部代码块
{
int x = 1;
System.out.println("局部代码块"+x);
}
//找不到符号,代码块外x不再起作用
//System.out.println(x);
{
int y = 2;
System.out.println("局部代码块"+y);
}
System.out.println("---------------");
Code c = new Code();
System.out.println("---------------");
Code c2 = new Code();
System.out.println("---------------");
Code c3 = new Code(1);
}
}
执行结果:
局部代码块1
局部代码块2
-------------
静态代码块1
静态代码块2
构造代码块1
构造代码块2
无参构造方法
-------------
构造代码块1
构造代码块2
无参构造方法
-------------
构造代码块1
构造代码块2
带参构造方法
-- 在同一个类中,静态代码块优先于main() 方法执行 --
/*
写程序的执行结果。
测试开始
我是main方法
Student 静态代码块
Student 构造代码块
Student 构造方法
Student 构造代码块
Student 构造方法
*/
class Student {
static {
System.out.println("Student 静态代码块");
}
{
System.out.println("Student 构造代码块");
}
public Student() {
System.out.println("Student 构造方法");
}
}
class CodeDemo {
static {
System.out.println("测试开始");//最先执行!!
}
public static void main(String[] args) {
System.out.println("我是main方法");
Student s1 = new Student();//用到Student()才加载Student.class
System.out.println("----------");
Student s2 = new Student();
}
}
--先加载 class CodeDemo{} ,再加载 static 静态代码块,最后才调用 main()方法--
继承
继承概述
多个类中存在相同属性和行为时,- 将这些内容抽取到单独一个类中, -
那么多个类无需再定义这些属性和行为,只要继承那个类即可。
通过extends关键字可以实现类与类的继承
class 子类名 extends 父类名 {}
单独的这个类称为父类,基类或者超类;这多个类可以称为子类或者派生类。
有了继承以后,我们定义一个类的时候,可以在一个已经存在的类的基础上,还可以定义自己的新成员。
好处:
A:提高了代码的复用性
多个类相同的成员可以放到同一个类中
B:提高了代码的维护性
如果功能的代码需要修改,修改一处即可
C:让类与类之间产生了关系,是多态的前提
其实这也是继承的- 一个弊端 -:类的耦合性很强
开发的原则:低耦合,高内聚。
耦合:类与类的关系
内聚:就是自己完成某件事情的能力
class Person {
public void eat() {
System.out.println("吃饭");
}
public void sleep() {
System.out.println("睡觉");
}
}
class Student extends Person {}
class Teacher extends Person {}
class ExtendsDemo {
public static void main(String[] args) {
Student s = new Student();
s.eat();
s.sleep();
System.out.println("-------------");
Teacher t = new Teacher();
t.eat();
t.sleep();
}
}
Java中继承的特点
Java只支持单继承,不支持多继承。
一个类只能有一个父类,不可以有多个父类。
class SubDemo extends Demo{} //ok
class SubDemo extends Demo1,Demo2...//error
Java支持多层继承(继承体系)
class A{}
class B extends A{}
class C extends B{}
Java中继承的注意事项
1.子类只能继承父类所有非私有的成员(成员方法和成员变量)
其实这也体现了继承的- 另一个弊端 -:打破了封装性
2.子类不能继承父类的构造方法,但是可以通过super(后面讲)关键字去访问父类构造方法。
3.不要为了部分功能而去继承
class A {
public void show1(){}
public void show2(){}
}
class B {
public void show2(){}
public void show3(){}
}
//我们发现B类中出现了和A类一样的show2()方法,所以,我们就用继承来体现
class B extends A {
public void show3(){}
}
这样其实不好,因为这样你不但有了show2(),还多了show1()。
有可能show1()不是你想要的。
我们到底在什么时候使用继承呢?
继承中类之间体现的是:”is a”的关系。
继承中成员变量的关系
在子类方法中访问一个变量
首先在子类局部范围找
然后在子类成员范围找
最后在父类成员范围找(肯定不能访问到父类局部范围)
如果还是没有就报错。(不考虑父亲的父亲…)
class Father {
public int num = 10;//父类成员变量
public void method() {
int num = 50;//父类局部变量
}
}
class Son extends Father {
public int num2 = 20;//子类成员变量
public int num = 30;
public void show() {
int num = 40;//子类局部变量
System.out.println(num);
System.out.println(num2);
}
}
class ExtendsDemo {
public static void main(String[] args) {
Son s = new Son();
s.show();
}
}
super关键字
class Father {
public int num = 10;
}
class Son extends Father {
public int num = 20;
public void show() {
int num = 30;
System.out.println(num);//30
System.out.println(this.num);//20
System.out.println(super.num);//10
}
}
class ExtendsDemo5 {
public static void main(String[] args) {
Son s = new Son();
s.show();
}
}
super的用法和this很像
this代表本类对应的引用。
super代表父类存储空间的标识(可以理解为父类引用)
用法(this和super均可如下使用)
访问成员变量
this.成员变量 super.成员变量
访问构造方法(子父类的构造方法问题讲)
this(para) super(para)
访问成员方法(子父类的成员方法问题讲)
this.成员方法() super.成员方法()
继承中构造方法的关系
子类中所有的构造方法默认都会先访问父类中的无参构造方法
为什么呢?
因为子类会继承父类中的数据,可能还会使用父类的数据。
所以,子类初始化之前,一定要先完成父类数据的初始化。
- 每一个构造方法的第一条语句默认都是:super() -
class Father {
int age;
public Father() {
System.out.println("Father的无参构造方法");
}
public Father(String name) {
System.out.println("Father的带参构造方法");
}
}
class Son extends Father {
public Son() {
//super();//自动加上,要有这个观念!!!!
System.out.println("Son的无参构造方法");
}
public Son(String name) {
//super();//自动加上
System.out.println("Son的带参构造方法");
}
}
class ExtendsDemo {
public static void main(String[] args) {
Son s = new Son();
System.out.println("------------");
Son s2 = new Son("小白");
}
}
"Father的无参构造方法
Son的无参构造方法
------------
Father的无参构造方法
Son的带参构造方法"
注:如果你提供了构造方法,系统将不再提供。
因此,当我们仅显式给出父类带参构造方法,系统将不会再给出无参构造。
- 如果父类中没有无参构造方法,该怎么办呢? -
1.子类通过super去显式调用父类其他的带参的构造方法
2.子类通过this去调用本类的其他构造方法
而这里的其它构造方法也必须首先访问了父类构造
一定要注意:
super(…)或者this(….)必须出现在第一条语句山
否则,就会有父类数据的多次初始化(因为这时系统会自动加上super()或者this())
综上:
子类中所有的构造方法都必须能访问父类中构造方法(默认找无参,若无,则手动找带参)。
若子类构造没有指明super\this,则系统默认加上 super();
class Father {
public Father(String name) {//本类没有无参构造方法
System.out.println("Father的带参构造方法");
}
}
class Son extends Father {
public Son() {
super("");//父类没有无参构造,手动调用父类带参构造
System.out.println("Son的无参构造方法");
//super("");//若放在后面,开始系统自动调用super(),报错
//因为父类没有无参构造
}
public Son(String name) {
//super("");//手动调用父类带参构造,或以下
this();//手动调用本类无参构造,进而调用父类构造
System.out.println("Son的带参构造方法");
}
}
class ExtendsDemo7 {
public static void main(String[] args) {
Son s = new Son();
System.out.println("----------------");
Son ss = new Son("林青霞");
}
}
Father的带参构造方法
Son的无参构造方法
------------
Father的带参构造方法
Son的无参构造方法
Son的带参构造方法
总结:写好子类后,要检查是否能调用父类的某一构造方法。
看程序写结果
面试题1.
class Fu{
public int num = 10;
public Fu(){
System.out.println("fu");
}
}
class Zi extends Fu{
public int num = 20;
public Zi(){
System.out.println("zi");
}
public void show(){
int num = 30;
System.out.println(num); //30
System.out.println(this.num); //20
System.out.println(super.num); //10
}
}
class ExtendsTest {
public static void main(String[] args) {
Zi z = new Zi();
z.show();
}
}
fu
zi
30
20
10
A:成员变量 就近原则
B:this和super的问题
this访问本类的成员
super访问父类的成员
C:子类构造方法执行前默认先执行父类的无参构造方法
D:一个类的初始化过程
成员变量进行初始化
默认初始化
显式初始化
构造方法初始化
面试题2.易错!!!!
class Fu {
static {
System.out.println("静态代码块Fu");
}
{
System.out.println("构造代码块Fu");
}
public Fu() {
System.out.println("构造方法Fu");
}
}
class Zi extends Fu {
static {
System.out.println("静态代码块Zi");
}
{
System.out.println("构造代码块Zi");
}
public Zi() {
System.out.println("构造方法Zi");
}
}
class ExtendsTest {
public static void main(String[] args) {
Zi z = new Zi();
}
}
静态代码块Fu
静态代码块Zi
构造代码块Fu
构造方法Fu
构造代码块Zi
构造方法Zi
- 说明 -
类加载器准备加载Zi的时候,发现Zi extends Fu,因此先加载Fu。
静态的内容随着类的加载而加载。因此先执行Fu的静态代码块,再执行Zi的静态代码块。
类加载完成后进行构造。要走子构造,要先走父构造;走父构造,要先走父构造代码块。
因此:
父静态代码块-子静态代码块-父构造代码块-父构造方法-子构造代码块-子构造方法
面试题3.没看懂!!!
/*
看程序写结果:
A:成员变量的问题
int x = 10; //成员变量是基本类型
Student s = new Student(); //成员变量是引用类型
B:一个类的初始化过程
成员变量的初始化
默认初始化
显示初始化
构造方法初始化
C:子父类的初始化(分层初始化)
先进行父类初始化,然后进行子类初始化。
结果:
YXYZ
问题:
虽然子类中构造方法默认有一个super()
初始化的时候,不是按照那个顺序进行的。
而是按照分层初始化进行的。
它仅仅表示要先初始化父类数据,再初始化子类数据。
*/
class X {
Y b = new Y();
X() {
System.out.print("X");
}
}
class Y {
Y() {
System.out.print("Y");
}
}
public class Z extends X {
Y y = new Y();
Z() {
//super();有没有都一样。
System.out.print("Z");
}
public static void main(String[] args) {
new Z();
}
}
继承中成员方法的关系
继承中成员方法的关系:
A:子类中的方法和父类中的方法声明不一样,这个太简单。
B:子类中的方法和父类中的方法声明一样,这个该怎么玩呢?
通过子类对象调用方法:
a:先找子类中,看有没有这个方法,有就使用,结束
b:若没有,再看父类中,有没有这个方法,有就使用,结束
c:如果没有就报错。
class Father {
public void show() {
System.out.println("show Father");
}
}
class Son extends Father {
public void method() {
System.out.println("method Son");
}
public void show() {//方法重写
System.out.println("show Son");
}
}
class ExtendsTest {
public static void main(String[] args) {
//创建对象
Son s = new Son();
s.show();//show son
s.method();//method son
//s.fucntion(); //找不到符号
}
}
继承的内存图?
方法重写概述
方法重写:子类中出现了和父类中方法声明(返回值类型参数列表)一模一样的方法。
也被称为方法覆盖,方法复写。
方法重载:
本类中出现的方法名一样,参数列表不同的方法。与返回值(可以不同)无关。
子类对象调用方法的时候:
先找子类本身,若无,再找父类。
方法重写的应用:
当子类需要父类的功能,而功能主体子类有自己特有内容时,可以重写父类中的方法。
这样,即沿袭了父类的功能,又定义了子类特有的内容。
子类重写了父类某方法后,若子类还想使用该方法,怎么办?super
class Phone {
public void call(String name) {
System.out.println("给"+name+"打电话");
}
}
class NewPhone extends Phone {
public void call(String name) {//重写了父类call方法
//System.out.println("给"+name+"打电话");
super.call(name);//还想使用父类功能,使用super
System.out.println("可以听天气预报了");
}
}
class ExtendsDemo {
public static void main(String[] args) {
NewPhone np = new NewPhone();
np.call("小白");
}
}
方法重写的注意事项
方法重写的注意事项
A:父类中私有方法不能被重写
因为父类私有方法子类根本就无法继承
B:子类重写父类方法时,访问权限不能更低
最好就一致
C:父类静态方法,子类也必须通过静态方法进行重写
其实这个算不上方法重写,但是现象确实如此,至于为什么算不上方法重写,多态中我会讲解
子类重写父类方法的时候,最好声明一模一样。
两个面试题
1:方法重写和方法重载的区别?方法重载能改变返回值类型吗?
方法重写:
在子类中,出现和父类中一模一样的方法声明的现象。
方法重载:
同一个类中,出现的方法名相同,参数列表不同的现象。
方法重载能改变返回值类型,因为它和返回值类型无关。
Override:方法重写
Overload:方法重载
2:this关键字和super关键字分别代表什么?以及他们各自的使用场景和作用。
this:代表当前类的对象引用
super:代表父类存储空间的标识。(可以理解为父类的引用,通过这个东西可以访问父类的成员)
场景:
成员变量:
this.成员变量
super.成员变量
构造方法:
this(...)
super(...)
成员方法:
this.成员方法
super.成员方法
继承案例
1.学生类继承人类
class Person{
private String name;
private int age;
public Person(){//不能去掉,继承它的子类要调用
//因为下面写了带参构造,系统不会给出无参构造,需显式给出
}
public Person(String name,int age){
this.name = name;
this.age = age;
}
public String getName(){
return name;
}
public int getAge(){
return age;
}
public void setName(String name){
this.name = name;
}
public void setAge(int age){
this.age = age;
}
private void say(){
System.out.println("人说话");
}
}
class Student extends Person{
public Student(){
}
public Student(String name,int age){
super(name,age);
}
}
class ExtendsTest{
public static void main(String[] args){
Student s1 = new Student();
s1.setAge(10);
s1.setName("小白");
System.out.println("我是Student"+s1.getName()+",年龄"+s1.getAge());
Student s2 = new Student("小黑",20);
System.out.println("我是Student"+s2.getName()+",年龄"+s2.getAge());
}
}
/* 猫:
成员变量:姓名,年龄,颜色
构造方法:无参,带参
成员方法:
getXxx()/setXxx()
eat()
palyGame()
狗:
成员变量:姓名,年龄,颜色
构造方法:无参,带参
成员方法:
getXxx()/setXxx()
eat()
lookDoor()
共性:
成员变量:姓名,年龄,颜色
构造方法:无参,带参
成员方法:
getXxx()/setXxx()
eat()
把共性定义到一个类中,这个类的名字叫:动物。
动物类:
成员变量:姓名,年龄,颜色
构造方法:无参,带参
成员方法:
getXxx()/setXxx()
eat()
猫:
构造方法:无参,带参
成员方法:palyGame()
狗:
构造方法:无参,带参
成员方法:lookDoor()
*/
class Animal{
private String name;
private int age;
private String color;
public Animal(){
}
public Animal(String name,int age,String color){
this.name = name;
this.age = age;
this.color = color;
}
public void setName(String name){
this.name = name;
}
public void setAge(int age){
this.age = age;
}
public void setColor(String color){
this.color = color;
}
public String getName(){
return name;
}
public int getAge(){
return age;
}
public String getColor(){
return color;
}
public void eat(){
System.out.println("eat");
}
}
class Dog extends Animal{
public Dog(){
}
public Dog(String name,int age,String color){
super(name,age,color);
}
public void watchDoor(){
System.out.println("The dog watch the door.");
}
}
class Cat extends Animal{
public Cat(){
}
public Cat(String name,int age,String color){
super(name,age,color);
}
public void playGame(){
System.out.println("The cat plays game.");
}
}
class ExtendsTest{
public static void main(String[] args){
Dog d1 = new Dog();
d1.setName("小白");
d1.setAge(10);
d1.setColor("白");
System.out.println("小狗"+d1.getName()+d1.getAge()+"岁 "+d1.getColor()+"色。");
d1.watchDoor();
Dog d2 = new Dog("小黑",20,"黑");
System.out.println("小狗"+d2.getName()+d2.getAge()+"岁 "+d2.getColor()+"色。");
d2.watchDoor();
Cat c = new Cat("小花",5,"花");
System.out.println("小猫"+c.getName()+c.getAge()+"岁 "+c.getColor()+"色。");
c.playGame();
}
}
final关键字
final可以修饰类,方法,变量
特点:
final可以修饰类,该类不能被继承,即不能用作父类。
final可以修饰方法,该方法不能被重写(覆盖,复写)。
final可以修饰变量,该变量不能被重新赋值。因为这个变量其实就是常量。
常量分为:
A:字面值常量
"hello",10,true
B:自定义常量
final int x = 10;
面试题(final的注意事项)
1.final修饰局部变量的问题
修饰基本类型:基本类型的值不能发生改变。
修饰引用类型:引用类型的(栈内存)地址值不能发生改变,
但是,该对象的堆内存的值(成员变量等)是可以改变的。
class Student {
int age = 10;
}
class FinalDemo {
public static void main(String[] args) {
//局部变量是基本数据类型
int x = 10;
x = 100;
System.out.println(x);
final int y = 10;
//无法为最终变量y分配值
//y = 100;
System.out.println(y);
System.out.println("--------------");
//局部变量是引用数据类型
Student s = new Student();
System.out.println(s.age);
s.age = 100;
System.out.println(s.age);
System.out.println("--------------");
final Student ss = new Student();
System.out.println(ss.age);
ss.age = 100;
System.out.println(ss.age);
//重新分配内存空间
//无法为最终变量ss分配值
ss = new Student();
//无法为最终变量ss分配值
ss = s;
}
}
2.final修饰变量的初始化时机
A:被final修饰的变量只能赋值一次。
B:初始化时机在构造方法完毕前。(非静态的常量)
class Demo {
//int num = 10;
//final int num2 = 20;
int num;
final int num2;
{
//num2 = 10;//代码块优先于构造方法
}
public Demo() {
num = 100;
//无法为最终变量num2分配值
num2 = 200;
}
}
class FinalDemo {
public static void main(String[] args) {
Demo d = new Demo();
System.out.println(d.num);
System.out.println(d.num2);
}
}