方法
概念
在JS中,我们把方法称之为函数。在java我们称之为方法。
方法就是一个黑匣子。我们不需要知道内部是如何执行的,只要按照要求调用,就能完成必要的功能。
方法申明的语法:
访问修饰符 其他修饰 方法返回值类型 方法名(参数列表){
方法体;// 这个方法要执行的代码
}
参看入口方法:
public static void main(String [] args){
System.out.println("主方法");
}
我们自定义一个方法:
public static void method(String name,int age){
System.out.println("name = " + name+";age = " + age);
}
-
访问修饰符 public : 这个方法是一个公共的方法。
-
其他修饰 static : 表示这个方法是静态方法。
-
返回值类型 void : 返回值类型表示这个方法在执行结束之后得到的结果类型。 void表示这个方法没有返回值。
-
方法名 method: 自定义的方法的名字。
-
参数列表(String name,int age) : 这个方法在执行时需要提前设置的值。这里的参数可以写多个使用“,”,必须指明参数的类型。
-
方法体 : 写在{}中的要执行的程序。
方法的命名规则:
-
不能以数字开头
-
不能有特殊符号
-
不能是java的关键字或者保留字符
方法命名规范:
-
和变量一样的驼峰规则。第一个单词全部小写,其余单词的首字母要大写。
-
方法都是动词。言简意赅。
-
其他的和变量一样。
方法的申明和调用
[1]没有返回值也没有参数的方法
public static void main(String[] args) {
//调用其他方法
bark();
}
// 没有返回值也没有参数的方法
public static void bark(){
System.out.println("This is method bark()");
}
[2]有参数的方法
有一个参数的方法
public static void main(String[] args) {
//调用其他方法
//调用有参数的方法 必须传入对应类型的参数
String message = "今天是个好日子";
say(message);
say("果然是个好天气");
}
// 有一个参数的方法
/**
* 有参数的方法,参数的类型是String,参数的名字是msg
* @param msg
*/
public static void say(String msg){
System.out.println("大声的说出:"+msg);
}
// public static void main(String[] args) {
//调用其他方法
// 调用有多个参数的方法 分别按照顺序传入对应类型的参数
add(1,1);
int a = 3,b = 4;
add(a,b);
eat("披萨",5);
}
// 有多个参数的方法
/**
* 有两个参数的方法
* @param x
* @param y
*/
public static void add(int x,int y){
System.out.println(x +" + "+ y + " = " + (x+y) );
}
public static void eat(String food,int count){
System.out.println("今天吃了" + count + "份" + food);
}
[3]有返回值的方法
tips: 当一个方法的返回值类型不是void
的时候。这时必须在方法中使用return语句返回对应的返回值类型的数据。
public static void main(String[] args) {
//调用其他方法
// 调用有返回值的方法
String result = join("您今天","漂亮","非常");
System.out.println(result);
}
//有返回值的方法
/**
* 使用op将msg1和msg2链接,并且返回
* @param msg1
* @param msg2
* @param op
* @return 返回值类型为String
*/
public static String join(String msg1,String msg2,String op){
String str = msg1 + op + msg2;
// 返回对应类型的数据
return str;
}
方法栈
看一段程序:
public class Demo4 {
public static void main(String[] args) {
int m = 100;
methodA(m);
System.out.println("main方法: m = " + m);
}
public static void methodA(int x){
int a = x;
methodB(a);
System.out.println("methodA : a = " + a);
}
public static void methodB(int y){
int b = y;
System.out.println("methodB : b = " + b);
}
}
看图理解上面的程序:
JVM中每次执行一个方法,都会往方法栈中放一个栈帧。
tips:画图仅仅是方便理解。并不是所有的内容都完全准确。
methodB执行结束,methodB方法栈帧出栈。继续执行methodA,methodA执行结束,methodA栈帧出栈。继续执行main方法。
这里也能看出,在任何一个方法中定义的局部变量都是相互独立的,相互不影响的。
包
java中的包就和我们文件系统中的文件夹是一样的,纯粹的是为了方便管理java的类。
我们可以在项目的src下创建一个package。
包的命名规则:不能有特殊符号,不能以数字开头,不能是关键字
包的命名规范:一般都是域名倒着写。不能有大写字母。
如果一个类在某一个包中,则整个类最上方是package的申明.
面向对象是一个思想,并不是一个编程语言。java语言就是实现了面向对象这个思想。
面向对象要相对于面向过程来描述:
我要开车回老家:
面向过程的描述:上车,启动,给油,出江苏-->安徽-->河南-->陕西
面向对象的描述:找到汽车,go("陕西")。车自己走。
这个事件中有两个对象:人和车。 车只是对外提供一个方法 go(目的地)。 我们只需要告诉车目的地是什么,车怎么去的,人不需要关心。
现实中有很多事物的思考和设计都是基于面向对象的思想的。
很多事物都是将一些功能封装在自己内部,然后对外提供了一些可以调用的接口,我们可以通过这接口使用这个事务,比如:笔记本电脑的适配器。
面向对象有啥好处?
降低耦合,增强系统可扩展性。 可以让系统像搭积木一样搭建。
类和对象的关系
java语言是用类来组织。 类是java语言的最小组织单元。
类是什么?(你是如何认知周边的事物的?)
类就是一类具有相同或者相似的特征和行为的事物的抽象描述。
跑车:四轮,两门....... 跑,装X.....
java中的 public class XXXX{} 是一个类是啥意思?
当JVM启动的时候,就相当于建立了一个虚拟的世界。这个世界中所有的事物都是存储在内存中的,每一个事物的存储方式都是由这个事物的类别来确定的。
java中已经定义了大量的数据类型。这些数据类型就是java中类规定的。每个数据类型都会对应一个java类。
数据类型=java类。
所以我们定义任何一个类,就相当于在整个JVM中有创建了一个新的数据类型。
java已有的数据类型:基本类型,String,Scanner,System等等。
我们自定义一个类: public class Hello{ ..... }
一旦使用这个类,这个JVM中就多了一种数据类型 "Hello"。
对象是什么?
“万物皆对象”。
人类----类别,是一个概念。描述人类的特征和行为:姓名,身高,体重,吃饭,思考。。。。
"王大锤"这个具体的人就是一个人类的对象。我们每一个人都是人类这个类别的一个对象。
我们准备一个类:
public class Animal { }
创建这个类对象:语法: 类名 对象名 = new 类名();
其中类名和变量名的要求是一样的。
public static void main(String[] args) {
// 通过类创建类对象
Animal a1 = new Animal();
Animal a2 = new Animal();
}
上面创建对象的过程就是在内存中根据类Animal的申明开辟了一个新的空间,存储了一些数据。
面试题:
类和对象是什么关系?
类是一个抽象的描述,对象是根据类创建的实例。
java中的类和对象
java中创建任何类的对象:
// 申明对象
类名 对象名; // 和申明变量是一样的。
// 创建对象
对象名 = new 类名();
所有的我们申明类(类型)都是引用数据类型。
基本类型和引用类型在内存中存储的结构是不一样的。
JVM的内存结构:
所有绿色区域是线程共享的。所有的粉色区域是线程独有的。
申明的类是一个源文件,被编译之后就会成为字节码文件,字节码文件被加载之后,就会放在方法区。
我们申基本类型的变量和对象的存储状态:
int x = 10;
boolean f = false;
String name = "大锤";
// 通过类创建类对象
Animal a1 = new Animal();
所有的基本类型它的引用和值都是存储在方法栈中的。
所有的引用类型在栈中存储的只是引用和地址。具体的数据是存储在堆中。
什么是赋值?
int x = 10;// 给x赋值为10
int y = x;// 把x值赋值给y
Animal a1 = new Animal();
Animal a2 = a1; // 把a1的值赋值给a2
public static void method1(int a){}
method1(x);// 将x的值赋值给形参a
public static void method2(Animal ax){}
method2(a1); // 将a1的值赋值给 ax
所谓赋值就是将这个引用栈中的内容复制一份给另外一个引用。
所以: y = x。 就是将x的值10赋值一份给y。
a2 = a1 将a1的地址复制一份给a2。 a1和a2就是指向同一块内存的。
面试题: 什么是值传递和引用传递? 值传递和引用传递有什么区别?
java中都是传值,没有引用传递的概念。
属性和方法
现实世界中所有的事物都可以使用特征和行为来描述。
我们将特征称之为属性,将行为称之为方法。
我们要在我们的JVM中创建一个类别:Dog。我们应该如何描述。
语法: 使用成员变量表示特征(属性)
使用成员方法表示行为(方法)
public class Dog {
// 特征 属性
String name = "大黄";
int age = 5;
// 行为 方法
public void sleep(){
System.out.println("大黄在睡觉zzzzzzzzzzz");
}
public void think(){
System.out.println("大黄在思考狗生.........");
}
}
我们通过Dog类创建对象:
// 创建一个dog对象
Dog dog = new Dog();
Dog dog1 = new Dog();
属性的访问
所谓属性的访问就是给一个对象的属性赋值或者获取这个对象的属性值。
获取一个对象的属性值: 对象名.属性名。
给一个对象的属性赋值: 对象名.属性名 = 值。
// 创建一个dog对象 Dog dog = new Dog(); Dog dog1 = new Dog(); // 我们可以通过类名 String name = dog.name; String name1 = dog1.name; System.out.println("name = "+name); System.out.println("name1 = "+name1); // 修改dog的name dog.name = "旺财"; // 再次获取 name = dog.name; name1 = dog1.name; System.out.println("name = "+name); System.out.println("name1 = "+name1);
tips:修改某一个对象的属性,不会影响其他对象的属性值。
方法的调用
成员方法(没有static修饰的方法)
调用的语法: 对象名.方法名(实参)。
tisp: 成员方法必须使用对象名来调用。
成员方法本身就是对象的方法。
静态方法是类的方法(在现实世界中不存在)。
人类可以吃饭,吃饭就是一个方法。但是要执行这个方法,必须要具体的人来执行。
// 调用方法
dog.sleep();
dog.think();
dog1.sleep();
dog1.think();
属性封装
按照规范的说法,成员变量就仅仅成员变量,只有经过封装才是属性。
java规范-->javaBean: 任何一个合法的java类都是javaBean。
所谓javaBean的规范,就是默认的java类的规范。
在javabean中的属性的规范是:
public class Cat {
// 属性
public void setName(String catName){
}
public String getName(){
return "小花";
}
}
上面的cat类就有属性 "name"。 跟成员变量没关系。
setName(String catName) : 给属性name设置值。
getName(): 获取属性值。
标准的写法:
public class Cat {
// 私有的成员变量
private String name;
// 属性
public void setName(String catName){
name = catName;
}
public String getName(){
return "小花";
}
}
有一个私有的成员变量。 private String name;
任何一个成员,被private修饰之后,这个成员就只能在当前类中使用,不能在任何其他地方使用。
之前的写法
public class Cat{
String name;
}
public class Demo(){
public static void main(String [] args){
Cat cat = new Cat();
cat.name = "小花";
}
}
当 name使用private修饰的时候:私有的成员不能在外部访问
我们认为属性不应该可以随意修改,如果不是private修饰就可以在任何地方随意修改,这是不安全的。
但是被private修饰的成员,无法访问。所以在当前的类中又提供了public的setter和getter方法用来设置和获取这个属性的值。
使用方式:
Cat cat = new Cat(); //cat.name = "小花"; // 设置值 cat.setName("团子"); // 获取值 String name = cat.getName(); System.out.println("cat的name是:"+name);
这样的规范让我们对属性的操作更麻烦了。但是也更安全了。
给Cat再加一个属性:age
public class Cat { // 其他代码省略.... private int age; public void setAge(int catAge){ // 我们可以在这里校验传入的值 if(age > 20){ age = 20; }else{ age = age; } } public int age(){ return age; } }
所谓属性的封装,就是给成员变量添加setter和getter方法,在外部使用这个属性的时候,要调用这个属性的setter和getter方法。
属性封装的语法规范::
[1]成员变量私有化 所有的成员变量都是用private修饰。
javabean规范的一个要求: 任何一个符合javaBean规范的类都不能出现非private的成员变量。
[2]给成员变量添加setter和getter方法 方法要求:两个方法都是public的成员方法。
setter方法的命名: 成员变量的第一个字母大写,在前面加上set
比如:成员name -> setName stuScore ->setStuScore
tisp: 成员变量的名字的第二个字母不要大写。
setter方法是没有返回值的。返回值是void。setter方法必须有一个参数,这个参数的类型和成员变量的类型是一致的。
getter方法命名:成员变量的第一个字母大写,在前面加上get
比如: 成员name -> getName stuScore ->getStuScore
getter是有返回值,返回值类型和成员变量的类型是一致的。getter方法是没有参数的。
案例1:
定义一个图书类型 Book,拥有属性:ISBN,title,author,publisher,price。拥有方法:showInfo()
实现: public class Book {
private String isbn;
private String title;
private String author;
private String publisher;
private double price;
public void showInfo(){
String info = title+","+isbn+","+author+","+publisher+","+price;
System.out.println(info);
}
//省略getset方法
}
创建对象并且测试:
public class Demo2 {
public static void main(String[] args) {
Book book = new Book();
book.setIsbn("987654321");
book.setTitle("java从入门到入土");
book.setAuthor("大锤");
book.setPublisher("有一说一出版社");
book.setPrice(12.5);
book.showInfo();
}
}
读程序
程序1:
public class Test1 {
public static void main(String[] args) {
int x = 10;
System.out.println("x = " + x);// 10
change(x);
System.out.println("x = " + x); // 10
}
public static void change(int x ){
System.out.println("x = " + x); // 10
x = 100;
System.out.println("x = " + x); // 100
}
}
程序2:
public class Cat { private int age; public int getAge() { return age; } public void setAge(int age) { this.age = age; } } public class Test2 { public static void main(String[] args) { Cat c = new Cat(); c.setAge(5); System.out.println(c.getAge());// 5 change(c); // 将c传入change方法 赋值 System.out.println(c.getAge());// 8 } public static void change(Cat c){ System.out.println(c.getAge());// 5 c.setAge(8); System.out.println(c.getAge()); // 8 } }
this关键字
public class Cat {
private String name;
public void setName(String catName){
name = catName;
}
public String getName(){
return name;
}
public void sleep(){
System.out.println("小猫【"+name+"】正在睡觉zzzzzz");
}
}
创建这个类对象
Cat cat = new Cate();
cat.setName("小花");
获取小花的名字: cat.getName();
在java对象中,默认有一个私有的成员变量 “this”指向当前对象本身。
所以如果我创建了两个对象:
Cat c1 = new Cat(); Cat c2 = new Cat();
这个this是每个对象私有的,不能在其他的地方调用。只能被当前对象自己调用。可以在任何的成员方法中使用this(静态方法中绝对不能使用this)
public class Cat {
private String name;
public void setName(String catName){
this.name = catName;
}
public String getName(){
return this.name;
}
public void sleep(){
System.out.println("小猫【"+this.name+"】正在睡觉zzzzzz");
}
}
其实在成员方法中使用成员变量或者调用成员方法,默认都是使用this调用的。只不过this可以省略。
this表示当前对象本身。谁调用这个方法,this就表示谁。
断点查看!
正因为如此,属性的封装的常见的做法是:
private int age;
public void setAge(int age){
this.age = age;
}
public int age(){
return age;
}
private String name;
public void setName(String name){
this.name = name;
}
public String getName(){
return name;
}
练习
[1]定义一个类Car,拥有属性color,brand,price。 拥有方法:showInfo() 输出所有的属性值, move() 输出 xxx颜色 xxxx汽车正在移动。
public class Car {
private String color;
private String brand;
private int price;
public void showInfo(){
System.out.println("当前的汽车的属性是:" + color+","+brand+","+price);
}
public void move(){
System.out.println(color + "颜色的" + brand + "车正在移动");
}
// setter和getter省略
}
public class Test {
public static void main(String[] args) {
Car car = new Car();
car.setColor("红色");
car.setBrand("法拉利");
car.setPrice(50);
car.showInfo();
car.move();
}
}
[2]定义一个Driver。 拥有属性: name,age,拥有方法: drive(String v) 传入交通工具的名字。
创建两个驾驶员,让他们分别驾驶不同的交通工具进行测试。
public class Driver {
private String name;
private int age;
public void drive(String v){
System.out.println(age + "岁的驾驶员" + name + "正在驾驶" + v);
}
// setter和getter省略
}
public class Demo {
public static void main(String[] args) {
Driver driver = new Driver();
driver.setAge(58);
driver.setName("老司机");
driver.drive("火箭");
Driver driver1 = new Driver();
driver1.setAge(18);
driver1.setName("小司机");
driver1.drive("小火车");
}
}
[3]有一个Cook厨师: 拥有属性name,age。 拥有方法:cook(String food1,String food)。传入两个实现,最终返回一道菜的名字为 xxx岁xxxx厨师炒的 《food1炒food2》
public class Cooker {
private String name;
private int age;
// 有返回值
public String cook(String food1,String food2){
String str = age + "岁的厨师" + name + "炒的 《"+food1 +"炒" +food2 + "》";
return str;
}
// setter和getter省略
}
public class Test {
public static void main(String[] args) {
Cooker cooker = new Cooker();
cooker.setAge(38);
cooker.setName("卡卡西");
// 调用有返回值的方法
String result = cooker.cook("西红柿","番茄");
System.out.println(result);
}
}
java中所有的变量都是必须在赋值(初始化)之后才能使用。
但是我们会发现成员变量不需要赋值就可以使用
因为所有的成员变量都是有默认值的。成员变量的默认值和数组的默认值一致:
所有的整数类型都是 0或者0L。 float是0.0F 。double是0.0。 char是空字符\u000
。 boolean是 false。 其他类型全部都是null。
当然我们也可以给成员变量赋默认值,一旦设置值,这个类创建的所有的对象的这个属性值全部都是这个值。
看案例: public class Cat {
private String name;
private String color = "黑色";
// stter和geeter省略
}
public static void main(String[] args) {
Cat cat = new Cat();
System.out.println(cat.getColor());// 黑色
Cat cat1 = new Cat();
System.out.println(cat1.getColor());// 黑
cat1.setColor("白色");
System.out.println(cat1.getColor());// 白色
}
对象之间的关系
[1]将我们自定义的类型作为方法的参数。
看程序1: 类Computer有work方法。 类Corder有work方法需要传入参数Computer
public class Computer {
private String brand;
public void work(){
System.out.println(brand + "品牌的计算机开始自己写代码!!!!");
}
}
public class Coder {
private String name;
private double faliang;
// work方法的参数是我们自定义的类型Computer
public void work(Computer com){
System.out.println("发量为"+faliang+"的程序猿"+name+"到了一杯咖啡。。。。。");
// 调用形参对象的work方法
com.work();
}
}
测试程序:
public class Demo1 {
public static void main(String[] args) {
Computer com = new Computer();
com.setBrand("华为");
Coder coder = new Coder();
coder.setName("大锤");
coder.setFaliang(0.5);
// 调用coderwork方法,传入上面的com
coder.work(com);
System.out.println("-------------------");
// 跟换一台计算机开始工作
Computer com1 = new Computer();
com1.setBrand("奔驰");
coder.work(com1);
}
}
[2]自定义的类型作为方法的返回值
案例:准备一个汽车工厂,可以生产汽车。
public class Car {
private String color;
private String brand;
public void showInfo(){
System.out.println(color + ":"+ brand);
};
}
public class CarFactory {
private String brand;
// 生产汽车
public Car createCar(String color){
// 创建一个汽车对象
Car car = new Car();
// 设置汽车的属性
car.setBrand(brand);
car.setColor(color);
// 返回这个汽车对象
return car;
}
}
测试程序:
public class Demo3 {
public static void main(String[] args) {
CarFactory factory = new CarFactory();
factory.setBrand("布加迪-威龙");
// 创建一辆布加迪
Car car1 = factory.createCar("蓝色");
car1.showInfo();
Car car2 = factory.createCar("红色");
car2.showInfo();
}
}
[3]自定义类型作为一个类属性
主板是一个类型,主板本身是计算机的属性
public class MainBoard {
private String brand;
private int price;
public String getInfo(){
return "价格为" + price + "的" + brand + "主板";
}
}
public class Computer {
private String brand;
// 自定义类型作为其他类的属性
private MainBoard mainBoard;
public void showInfo(){
System.out.println(brand + "的计算机,使用" +mainBoard.getInfo());
}
}
测试程序:
public class Demo5 {
public static void main(String[] args) {
//创建主板
MainBoard board = new MainBoard();
board.setBrand("路基亚");
board.setPrice(50);
//创建电脑
Computer com = new Computer();
com.setBrand("奔驰");
// 将上面创建的主要对象设置到computer中
com.setMainBoard(board);
//输出信息
com.showInfo();
}
}
方法重载
重载是overload直译过来的,意思是重新加载。
我们可以在一个类中定义同名的方法,通过不同的参数类区分这些方法,在调用的时候,JVM会根据传入的参数自动调用对应的方法。
看案例:
我们准备三种不同的交通工具和驾驶员。让驾驶员分别驾驶三种不同的交通工具。
public class Train {
public void move(){
System.out.println("一辆火车在铁轨上奔驰.....");
}
}
public class Car {
public void move(){
System.out.println("一辆汽车在马路上奔跑。。。。");
}
}
public class Bicycle {
public void move(){
System.out.println("一辆自行车在校园里飞驰....");
}
}
创建一个驾驶员:有三个驾驶的方法
public class Driver {
// 开火车
public void drive(Train train){
System.out.println("老司机-检查-启动-触发--");
train.move();
};
// 开汽车
public void drive(Car car){
System.out.println("老司机-启动-给油-出发--");
car.move();
}
// 自行车
public void drive(Bicycle bicycle){
System.out.println("老司机-上车-使劲--登起来--");
bicycle.move();
};
}
tips: 在driver类中有三个 同名的方法drive,但是他们的参数类型不同,所以可以同时存在,这个三个方法就构成了方法的重载。
调用测试:
public class Demo5 {
public static void main(String[] args) {
Driver driver = new Driver();
// JVM会根据传入的参数调用对应的方法
Car car = new Car();
driver.drive(car);
Train train = new Train();
driver.drive(train);
Bicycle bicycle = new Bicycle();
driver.drive(bicycle);
}
}
方法重载
理论上在一个作用域下不能定义同名的变量。
在一个类中不能定义相同的方法。因为相同的方法在调用的时候无法区分。
这里的相同的方法是说,名称相同,参数相同。
方法重载是在同一个类中,同名,但是参数不同的方法。
所谓参数不同,有三种情况:
-
参数的类型不同。---
-
参数的个数不同。---
-
参数的顺序不同。---
tip:方法重载和方法的返回值无关。
看程序:
三种参数不同的情况:
public class MethodOverload {
// 参数的类型不同
public void method(String name){}
public void method(int age){}
// 参数个数不同
public void method1(int x,int y){}
public void method1(int x,int y,int z){}
// 参数顺序不同
public void method2(int x,String name){}
public void method2(String name,int x){}
}
与访问修饰符无关: 访问修饰符不同,不构成重载
与返回值类型无关:返回值类型不同,也不构成重载
静态方法和非静态方法之间的情况不能“构成”重载
练习:实现一个简单的计算器类
计算两个数的加法,减法,乘法
public class C {
public long add(long x,long y){
return x + y;
};
public double add(double x,double y){
return x + y;
};
// 其他的计算形式省略
}
测试:
public class Demo6 {
public static void main(String[] args) {
C c = new C();
long add = c.add(1, 2);
System.out.println(add);
double add1 = c.add(1.2f, 1.2F);
System.out.println(add1);
}
}
构造方法
创建一个对象的方式
public class Person(){}
Person p1 = new Person();
我们在创建对象的时候,需要使用 new 类名() 我们只有在调用方法的时候才会有()。
所以其实我们就是使用 new 关键字调用了 类的构造方法
在每一个类中都有构造方法,构造方法就是用来创建这个类的对象的。
构造方法的要求:
-
构造方法的方法名和类名完全一致。
-
构造方法没有返回值。也不能写void。
和访问修饰符无关,和参数无关。
案例:
public class Person {
// 构造方法
public Person(){
}
// 实例方法
public void Person(){}
// 错误的方法
public person(){}
}
问题:我们之前写的所有的类都没有写过构造方法,为什么可以使用的呢?
如果一个类没有显式的指定构造方法(所谓显式指定,就是手动写了构造方法),编译器会默认给这个类增加一个无参数的,public的构造方法。
如果一个类显式的指定了构造方法,那么编译器就不会再给默认的构造方法了。
public class Person {
// 构造方法 显式的指定构造方法
public Person(){
System.out.println("创建了一个Person");
}
}
目前为止,构造方法只能使用new关键字调用。
自定义的构造方法
[1] 通过构造方法给属性设置初始化的值
看问题:构造方法与参数无关。
看下面的类:
public class Student {
private String name;
private int age;
// 构造方法
public Student(String name){
System.out.println("创建一个学生对象");
}
}
上面的错误原因:当我们使用new关键字调用Student的构造方法的时候报错,因为Student构造方法是有一个参数的但是在调用的时候没有传参数。
当我们指定了一个有参数的构造方法的时候,编译器不会再给无参数的构造方法了。
所以我们只能调用有参数的构造方法。所以必须传入参数。
public class Demo7_1 {
public static void main(String[] args) {
// 创建学生对象,调用构造方法,必须传入参数
Student st = new Student("卡卡西");
}
}
构造方法的参数有什么用?
我们可以通过构造方法的参数直接初始化对象的属性值: 我们之前给一个对象的属性赋值,基本都是通过setter方法赋值。
看下面的情况:
public class Student {
private String name;
private int age;
// 构造方法 (通过构造方法初始化属性值)
public Student(String name,int age){
this.name = name;
this.age = age;
};
public void showInfo(){
System.out.println(name+":"+age);
}
}
测试:
Student st = new Student("旗木卡卡西",28); st.showInfo();// 旗木卡卡 : 28
[2]构造方法的重载
javaBean规范:
-
任何符合规范的类中都不应该出现非私有的成员变量。
-
有setter和getter方法的才是属性。
-
任何一个符合规范的类都因该有无参数的构造方法。
构造方法也可以重载,看情况:
public class Student {
private String name;
private int age;
// 构造方法 (通过构造方法初始化属性值)
public Student(String name,int age){
this.name = name;
this.age = age;
};
// 有一个参数的构造方法
public Student(String name){
this.name = name;
}
// 有五个参数的构造方法
public Student(int x,int y,int z, String name, int age){
this.name = name;
this.age = age;
}
// 重载的构造方法
public Student(){}
}
调用的时候,JVM会自动根据传入的参数调用对应的构造方法:
Student st1 = new Student();//无参数构造方法
Student st2 = new Student("佐助");
Student st3 = new Student("鸣人",15);
Student st4 = new Student(1,2,3,"小樱",15);
构造方法中的this
this表示当前对象。 在构造方法中有两种情况,第一种情况就是表示当前对象。第二种情况就是表示调用其他的的构造方法。
看案例:
当使用this调用其他构造方法的时候:必须写在当前的构造方法的第一行。
继承
基本概念
“继承”:表示继承者自动拥有被继承者的事物(财富)。
程序中的继承和现实中的继承意思差不多。
在java中继承发生在两个类之间或者两个接口之间。比如A类继承了B类,A类会自动拥有B类的财富(属性和方法)。
程序实现:
public class Animal {
private String name;
private int age;
public void bark(){
System.out.println(age + "岁的" + name + "在大声吼叫");
}
// setter和getter省略
}
/**
* Dog类没有自己的属性和方法
*/
// 通过extends关键字让Dog了继承Animal
public class Dog extends Animal {
}
测试:
// 创建一个Dog
Dog dog = new Dog();
dog.setName("土豆");
dog.setAge(3);
dog.bark();
执行结果是正常的。
说明: Dog类中拥有属性name和age,而且有bark方法,而且bark的执行的内容和Animal是一样的。
目前继承能看到的最明显的作用就是代码的重用。父类中的定义的方法和属性,子类都是自动拥有。
语法:类之间的继承的实现,通过关键字extends
public class 子类 extends 父类{}
继承者称之为子类,被继承者称之为父类或者基类。
父类只能有一个。 这个概念其实就是java中单继承的概念。
所谓单继承就是:任何一个类最多只能有一个直接父类。 但是一个父类可以有多个子类。
继承中构造方法的问题
在类的继承中,构造方法是不能继承的。
问题:
给父类Animal一个有参数的构造方法: 编译器就不会再给无参数的构造方法了。
Dog类报错:
错误的提示是父类中没有默认的构造器了。
这个问题的产生原因,看图:
当我们创建了一个子类的时候,内存的情况:
当我们创建一个子类的对象时,会首先在内存中创建一个父类的对象,父类对象的创建使用父类的构造方法,默认情况下就是使用父类的默认构造方法。 当父类没有默认的构造方法的时候,自然就报错
解决方案:
[1] 在父类中添加默认的构造方法。
[2] 在子类的构造方法中通过super()主动调用父类的有参数的构造方法。
// 通过extends关键字让Dog了继承Animal
public class Dog extends Animal {
public Dog(){
// 通过super调用父类有参数的构造方法
super("山芋",6);
}
}
当然,也可以这样写:
// 一个参数的构造方法
public Dog(String name){
super(name,5);
}
// 两个参数的构造方法
public Dog(String name,int age){
super(name,age);
}
tips: 当在子类的构造方法中使用super调用父类的构造方法时。必须写在第一行。
其实.........任何一个类的默认的构造方法中都默认的调用类父类的无参数的构造方法
public Dog(){
super(); // 默认的
}
继承中的两个问题
[1] 父类的私有属性和私有方法是否可以继承?
public class Father {
// 私有的成员变量和方法
private String name;
private void say(){
System.out.println("My name is "+name);
}
}
public class Son extends Father {
}
在子类中使用父类私有的成员:
给父类的私有的成员变量添加setter和getter
测试:
子类中可以使用父类的getName和setName方法:
Son son = new Son();
// son.name
son.setName("鸣人");
System.out.println(son.getName());
问题: son.setName("鸣人") setName 中有 this.name = name; 这个this是谁?
总结:
子类继承了父类的所有属性和方法或子类拥有父类的所有属性和方法是对的,
只不过父类的私有属性和方法,子类是无法直接访到的。即只是拥有,但是无法使用。 (这里不考虑Java反射机制)
所有的父类的私有的成员,在子类中都是不能直接使用的。表面上来看就是没有继承。
[2]父类的静态的方法和成员是否可以继承
静态方法和静态成员是可以被继承。
在父类中添加一个静态的方法
public class Father {
public static void showStaticInfo(){
System.out.println("我是父类的静态方法");
}
// ........
}
使用子类对象调用这个静态方法:
Son son = new Son();
son.showStaticInfo();
结果:正常调用而且执行。
从现象来看,子类整的继承了父类的静态的方法。
static修饰的任何成员都是属于类的。不是属于对象的。应该直接使用 类名.方法名。 或者 类名.属性名。 不应该使用对象调用。 而且一定要使用这个成员所申明的位置的类直接调用。
本质上来说,静态方法和成员是不能被子类继承的。但是我们可以使用子类的类名或者对象直接调用父类的静态方法。 但是这种用法是不规范的。
总结:
子类是不继承父类的static变量和方法的。因为这是属于类本身的。但是子类是可以访问的。
子类和父类中同名的static变量和方法都是相互独立的,并不存在任何的重写的关系。
继承-> 扩展
继承的关键字是extends,翻译过来是扩展。 所以其实子类就是扩展于父类,所以子类大于父类。
看案例:
public class Animal {
private String name;
public void bark(){
System.out.println("动物"+name+"在吼叫");
}
// ....
}
在子类中扩展自己专有的属性和方法
public class Dog extends Animal {
// 父类的属性和方法无法满足子类的需求
// 可以定义自己的属性和方法
private String color;
public void lookHome(){
System.out.println(color + "颜色的" + getName() + "正在看家....");
}
}
看程序:
public class Demo10 {
public static void main(String[] args) {
Dog dog = new Dog();
dog.setName("大黄"); // 继承的属性
dog.setColor("黑色");// 自己扩展的属性
dog.bark();// 继承的方法
dog.lookHome();// 自己扩展的方法
}
}
看内存图:
父子类之间的类型转换
案例:
public class Animal {
private String name;
public void bark(){}
}
public class Dog extends Animal {
private String color;
public void sleep(){}
}
测试程序: 将子类对象赋值给父类型的变量(父类的引用),可以直接赋值
public class Demo1 {
public static void main(String[] args) {
Dog dog = new Dog();
Animal a1 = dog;
}
}
看图理解:
当我们把a1当作Animal类型看待的时候,JVM会从a1这个变量所指的数据区域(蓝色的区域)去找Animal中申明的属性(name)和方法(bark())。 肯定可以找得到。
所以我们将子类对象dog赋值给父类的对象a1是不会有任何问题。
上面的情况反过来就会有问题。
这里的强制类型转化的语法:
目标类型 变量 = (目标类型)源数据;
Animal a2 = new Animal();
// 父类对象赋值给子类,需要强制类型转换
Dog dog2 = (Dog)a2;
内存分析:
由于dog对象需要color 和 sleep,所以无法将父类对象转换为子类对象。
既然不能转换为啥会有这样的语法?
有些情况是可以转换的: 当父类对象所指向的区域本身就是子类实例对象。就可以强制类型转换。
看案例:
// 申明父类型对象, 赋值 创建子类对象。
Animal a1 = new Dog();
a1.bark();
//a1.sleep();// 这个方法是子类的方法,父类对象不能调用
// 将父类对象 a1 强制转换为子类
Dog dog = (Dog) a1;
dog.sleep();
dog.bark();
凡是需要父类的地方,都可以使用子类代替。
向上转型和向下转型。。。
方法重写/覆盖
我们之前讲过方法重载overload。
现在说的是方法重写overwrite。
请问:方法重写和方法重载有什么区别。
什么是方法重写/覆盖?
方法重写就是在子类中重新申明和实现父类的非私有的方法。
要求:
子类方法的访问修饰符的范围必须大于等于父类方法的访问修饰符范围。
子类方法的名字和参数必须和父类方法的名字和参数一致。
子类方法的返回值类型必须和父类方法的返回值类型一致或者是父类方法的返回值类型的子类。
统一的来说:子类方法的签名和父类一致。
看案例:
父类:
public class Animal {
public void bark(){
System.out.println("动物在吼叫!!!!");
}
}
子类: 重写父类的bark方法
public class Dog extends Animal{ // 重写父类的bark方法 public void bark(){ System.out.println("汪汪汪!!!"); } } public class Cat extends Animal { public void bark(){ System.out.println("喵喵喵!!!"); } }
有啥用?
先看测试:
// 申明父类,创建父类对象 Animal a = new Animal(); a.bark();// 动物在吼叫 // 申明子类,创建子类对象 Dog dog = new Dog(); dog.bark();// 汪汪汪 // 申明父类,创建子类对象 Animal a1 = new Dog(); a1.bark(); // 汪汪汪 Animal a2 = new Cat(); a2.bark();// 喵喵喵
测试结果说明,被重写的方法在调用的时候,使用的时候 new 创建的对象类型中申明的方法。
看图理解:
当我们创建一个子类的实例的时候,被覆盖的方法无法在外部调用了,无论将这个实例看作什么类型,方法表现的状态永远都是这个实例的方法。
Dog dog1 = new Dog(); Animal a3 = dog1; a3.bark(); // 汪汪汪
方法重写作用案例
案例1:
有一系列的交通工具:
public class Vehicle { public void move(){ System.out.println("动次打次"); } } // 两个子类 都重写父类的move方法。 public class Car extends Vehicle { public void move() { System.out.println("小汽车在奔跑....."); } } public class Train extends Vehicle { public void move() { System.out.println("火车在飞速的奔跑....."); } }
准备一个驾驶员: 有一个drive方法
public class Driver { public void drive(Vehicle v){ System.out.println("老司机开始驾驶...."); v.move();// 调用交通工具的move方法。 }; }
测试:
public static void main(String[] args) { // 准备两个交通工具对象 Car c = new Car(); Train t = new Train(); // 创建驾驶员对象 Driver d = new Driver(); // 分别传入两个交通工具 d.drive(c); d.drive(t); }
在现有的结构下,在增加一个Truck
public class Truck extends Vehicle { @Override public void move() { System.out.println("卡车在吃力的行动...."); } }
原则: 开闭原则。 对扩展开放,对修改关闭。
super
this关键字的作用是什么?
-
可以表示当前对象
-
可以在构造方法使用使用this()调用其他的构造方法。一定要写在第一行。
super关键字的作用是什么?
-
在子类的构造方法中,可以使用super()调用父类的构造方法。一定要写在第一行。
-
在子类的实例方法或者构造方法中,super表示父类对象。可以使用super.xxxx调用父类的非私有的成员属性或者方法。包括被覆盖的方法。
tips:任何使用都不能在static修饰的方法中使用this和super。
案例:
父类,有两个方法和一个属性
public class Animal { private String name; public void bark(){ System.out.println(name + " 在吼叫 "); } public void sleep(){ System.out.println(name + "在睡觉"); } }
子类实现:我们在子类中使用super关键字调用父类的方法
public class Dog extends Animal { @Override public void bark() { // 这里的super表示父类的对象 System.out.println(super.getName() + "在汪汪叫!"); } public void lookHome(){ System.out.println(getName()+" 在看家时...."); // 这里的super表示父类对象 super.bark();// 这里调用的是父类被覆盖的方法 super.sleep();// 这里调用的父类没有被覆盖的方法。 没有被覆盖就是被继承,所以这里使用super或者this效果一样 } }
测试:
public class Demo4 { public static void main(String[] args) { Dog dog = new Dog(); dog.setName("大黄"); dog.bark();// 子类的bark方法 dog.lookHome(); } }
结果:
访问修饰符
java中我们要知道的修饰符:
访问修饰符: public、protected、[default] 、private 抽象修饰符: abstract 静态修饰符: static 最终修饰符: final 同步修饰符: synchronized 其他修饰符: volitail 本地方法修饰符: native
访问修饰符:
访问修饰符都是用来修饰成员的。(成员变量,成员方法,成员内部类)这里的成员包含了静态和非静态
访问修饰符使用来修饰这些成员的访问范围的。
访问修饰符的访问范围:
访问修饰符 | 访问范围 |
---|---|
public | 可以在任何位置访问 |
protected | 在当前包和子孙类中可以访问 |
[default]没有访问修饰符 | 在当前包中访问 |
private | 在当前类中可以访问 |
案例:
public class Animal { // 只能在当前类中访问(使用) private String name; // default 的方法 可以在当前包中的任何类中访问 void methodDefault(){ System.out.println("这个是Animal中的默认的方法"); } // protected修饰的方法 可以在当前包和子孙类中访问 protected void methodProtected(){ System.out.println("这个是Animal中的protected修饰的方法"); } // public修饰的方法 可以在任何位置访问 public void method(){} }
当前包中访问:默认的和protected的方法。
package com.demo5.ex1; public class Test1 { public static void main(String[] args) { Animal a1 = new Animal(); // 当前包,访问默认方法 a1.methodDefault(); // 当前包,访问protected修饰的方法 a1.methodProtected(); } }
其他包:
子孙类:
对象数组
数组:就是一组数据。
对象:通过new调用构造方法创建的对象。
对象数组:就是一组对象。
看案例:
准备一个学生类:
public class Student { private String no; private String name; public Student() { } public Student(String no, String name) { this.no = no; this.name = name; } }
测试类:
public class Demo6 {
public static void main(String[] args) {
// 整形数组
int [] arr = new int[5]; // 5个0
arr[0] = 1;arr[1]=1;//....
// Student数组
Student [] stus = new Student[5]; // 5 个 null
// 数组stus的每一个元素都是一个学生对象
stus[0] = new Student("9527","卡卡西");
stus[1] = new Student("9528","佐助");
stus[2] = new Student("9529","鸣人");
stus[3] = new Student("9530","小樱");
stus[4] = new Student("9531","大锤");
// 遍历数组
for (int i = 0;i < stus.length;i ++){
System.out.println(stus[i].toString());
}
}
}
看图理解:
数组中的每个元素存储的是每个对象的地址。我们可以通过数组元素找到这个对象数组名[index]
。
面向对象相关的内容:
-
面向对象的思想。
-
类和对象。
-
属性封装:setter和getter
-
成员方法。
-
方法重载。
-
构造方法。
-
this和super。
-
继承。
-
方法重写。
-
访问修饰符。
从程序阅读得到多态的概念
看一段程序:
/**
* 银联卡
*/
public class UniCard {
public void withdraw(double money){
System.out.println("使用银联卡取款"+money+"扣除手续费0.0元");
}
}
public class ATM {
public void withdrawMoney(UniCard card,double money){
System.out.println("使用ATM提款.....");
// 调用银联卡的提款的方法进行提款
card.withdraw(money);
}
}
public class Test1 {
public static void main(String[] args) {
// 准备一个银联卡
UniCard card = new UniCard();
// 准备一个ATM提款
ATM atm = new ATM();
// 传入银联卡和金额
atm.withdrawMoney(card,5000000);
}
}
在创建两个银联卡:工商银行和农业银行 这两个类都继承UniCard,并且重写提款得方法
public class ICBCCard extends UniCard {
// 重写父类中提款的方法
再使用工商卡和农业卡从ATM中提款。
(我们增加的两个卡,不需要修改之前的程序)
修改测试程序:
public class Test1 {
public static void main(String[] args) {
// 准备一个银联卡
UniCard card = new UniCard();
// 准备一个ATM提款
ATM atm = new ATM();
// 传入银联卡和金额
atm.withdrawMoney(card,5000000);
// 准备工商卡和农行卡
ICBCCard icbcCard = new ICBCCard();
ABCCard abcCard = new ABCCard();
atm.withdrawMoney(icbcCard,5000000);
atm.withdrawMoney(abcCard,5000000);
}
}
上面程序中:
ATM的提款的方法需要的参数类型是UniCard(父类)。而且在ATM提款的方法中:调用card的提款方法的时候是按照父类UniCard的申明调用的。
当我们传入参数是子类ICBCCard或者ABCCard的时候,在ATM中依然把这些子类对象当作父类来看待。但是每个不同的card,表现的状态确实自己本身的状态。
这个就是同一种类型的不同表现形态。这就是多态。
所谓同一种类型的不同的表现形态,就是我们将不同的子类对象当作父类来看,这时这些对象表现的是子类本身的形态。
多态产生的条件就是:第一要继承(实现),第二要重写方法。
我们一般所说的多态都是运行时多态。
多态产生的原理
常见的多态产生的场景就是需要父类对象的地方使用子类对象代替。
父类的引用指向子类对象。
public class Animal {
public void bark(){
System.out.println("动物在吼叫");
}
}
public class Dog extends Animal {
public class Test1 {
public static void main(String[] args) {
Animal a1 = new Dog();
a1.bark();
}
}
看图分析:
当创建子类对象的时候,子类中重写的方法会覆盖父类中的方法,之后,无论你将这个对象当作父类还是子类,执行的永远都是子类的方法。
运行时多态:因为只有在运行时,创建了子类对象之后,才能确定这个对象要执行得方法是谁。
多态的好处:多态可以让我们在不修改之前的程序的情况下,扩展型的功能(模块)
什么是多态?
总:概念。
分:多态的产生情况。多态的引用。
总:多态的好处(增强程序扩展性)。
修饰符
static修饰符
static修饰符可以修饰:成员变量,方法,成员内部类,静态代码块。
使用和现象:static修饰的成员可以使用类名直接调用。
比如:修饰的变量可以使用 类名.变量名直接调用,修饰的方法可以使用类名.方法名直接调用。 不需要创建对象。 非静态方法或者非静态的成员变量,必须创建对象,使用对象名.xxx去调用。
案例: public class Tool {
public static String msg = "这是一个静态的成员变量";
// static修饰的静态方法
public static void showMsg(){
System.out.println("这是一个静态方法");
}
public void showInfo(){
System.out.println("这是一个非静态的方法");
}
}
测试:
问题:什么是类? 抽象的概念(描述)。人类,动物类,植物类。。。。。
所有的特征和行为都是属于某一个具体的对象的。不是属于类别的。这是现实世界。
但是在程序中,类本身是可以有特征和行为的。static修饰的任何成员都是属于类的。不是属于某一个对象的。所以才可以使用类名直接调用,当然了也可以使用对象调用,但是这种方式是不合理。
非static的方法或者属性不能使用类名调用,就是因为这些方法或者属性是属于对象的不是属于类的。
看图理解:
-
所有的类在加载完成之后都会放在方法区。
-
所有的对象在创建之后都是放在堆内存中的。
通过内存分析会发现:只要类被加载,所有的static修饰的内容都会在方法区中存在,这时就可以使用了。但是所有的非static修饰的内容,在没有创建对象的情况下是不能使用的。
所以才会有:在任何的静块(方法)中都不能使用非静态的任何引用。
常见的案例:
我们这里是main方法,其他的静态方法也是一样的。
因为静态方法在执行的时候,这个类的类对象可能还没有创建。类对象只要没有创建,非static的(name)是不存在的。
当然也有可能在调用静态方法的时候,对象已经创建类。
案例:
public class Test3_1 {
String name= "旗木卡卡西";
public static void main(String[] args) {
//System.out.println(name);
// 先创建对象
Test3_1 t31 = new Test3_1();
System.out.println(t31.name);// 使用对象名.xxxx
}
}
static修饰的代码块:
案例:
public class Data {
// 普通代码块
{
System.out.println("这里是一个普通的代码块");
}
// 静态代码块
static {
System.out.println("这里是一个静态代码块");
}
}
普通代码块:在创建这个类对象的时候会执行一次。
静态代码块:在类加载的时候会执行一次。
类加载:简单的认为就是JVM将字节码文件读到内存的。JAVA的类加载是按需加载。JVM在加载一个类之后,就不在重复加载了。一个类理论上只被加载一次,之后就可以重复的使用。
案例:
// 加载,并且创建对象
Data d1 = new Data();
// 创建对象
Data d2 = new Data();
静态代码块和静态方法一样,不能在静态代码块中使用任何非静态的成员或方法。
静态代码块往往使用来做初始化工作的。
笔试题:
排执行顺序:
A 父类的构造方法
B 子类的构造方法
C 父类的普通代码块
D 子类的普通代码块
E 父类的静态代码块
F 子类的静态代码块
E -> F -> C -> A -> D -> B
当创建子类对象的时候,上面程序的执行顺序是什么?
写程序测试:
public class F {
public F(){
System.out.println("父类的构造方法");
}
{
System.out.println("父类的普通代码块");
}
static{
System.out.println("父类的静态代码块");
}
}
public class S extends F{
public S(){
System.out.println("子类的构造方法");
}
{
System.out.println("子类的普通代码块");
}
static{
System.out.println("子类的静态代码块");
}
}
测试:
// 创建子类对象
S s = new S();
结果:
final修饰符
final修饰符表示是最终的。
-
final修饰的变量必须在申明的同时赋值,而且不能被重新赋值。final修饰的量一般都是常量。
-
final修饰的方法不能被子类重写。
-
final修饰的类不能被继承。默认情况下final修饰的类中的所的方法都是final的。
面试题:
java.lang.String 这个类是否可以被继承?(为什么?)
不能,因为String是一个final类,不可被继承,代表不可变的字符序列,是一个类类型的变量。Java程序中的所有字符串字面量(如"abc")都作为此类的实例实现,"abc"是一个对象。
字符串是常量,创建之后不能更改,包括该类后续的所有方法都是不能修改该对象的,直至该对象被销毁(该类的一些方法看似改变了字符串,其实内部都是创建一个新的字符串)。
String对象的字符内容是存储在一个字符数组 value[] 中的。
常量
常量一般都是不可以修改的量。比如:一年12个月。一天24小时。圆周率pi等等。
在java定义常量的方式:
public static final 数据类型 常量名 = 值。
案例:
public ,static,final修饰符的顺序是可以改变。
所有的常量命名规则和变量一致。
常量的命名规范:
-
-
如果由多个单词组成,要使用“_”连接。
常量也可以是一个对象,这个对象的引用是不能改变的,但是这个对象的属性是可以修改的。
案例:
public class User {
private String name;
}
测试程序:
public class Test {
// 常量, 是一个对象
public static final User USER = new User();
public static void main(String[] args) {
// 重新赋值是不行的
// USER = new User();
// 可以修改USER的属性值。
USER.setName("卡卡西");
}
}
本文来自博客园,作者:{潇潇消消气},转载请注明原文链接:{https://www.cnblogs.com/xiaoxiaodeboke/}