控制与输入输出
新的for循环
声明语句:声明新的局部变量,该变量的类型必须和数组元素的类型匹配。其作用域限定在循环语句块,其值与此时数组元素的值相等。
表达式:表达式是要访问的数组名,或者是返回值为数组的方法。
print&&println的自动换行
print()
输出完毕后不换行,而println()
输出完毕后会换行
这个区别主要体现在新的for循环里面
1 2 3 4 5 6 7 8 9 10 11 12
| int [] numbers = {10, 20, 30, 40, 50};
for(int x : numbers ){ System.out.print( x ); System.out.print(","); } System.out.print("\n"); String [] names ={"James", "Larry", "Tom", "Lacy"}; for( String name : names ) { System.out.print( name ); System.out.println(","); }
|
输出为
1 2 3 4 5
| 10,20,30,40,50, James, Larry, Tom, Lacy,
|
输入
Ⅰ. 用法
引入 import java.util.Scanner;
1 2
| Scanner scan = new Scanner(System.in); <赋值给谁?>=sc.nextLine();
|
Ⅱ .next() 与 nextLine() 区别
next():
- 一定要读取到有效字符后才可以结束输入。
- 对输入有效字符之前遇到的空格,next() 方法会自动将其去掉。
- 只有输入有效字符后才将其后面输入的空格作为分隔符或者结束符。
- next() 不能得到带有空格的字符串。
nextLine():
- 以Enter为结束符,也就是说 nextLine()方法返回的是输入回车之前的所有字符。
- 可以获得空。
Ⅲ.进一步
1 2 3 4 5 6 7
| Scanner scan = new Scanner(System.in); char grade = scan.next().charAt(0);
scan.nextInt(); scan.nextDouble(); scan.nextline();
|
方法与类
extends继承
对于一些类,我们常常有相同的变量,比如
那么我们可以
1 2 3 4 5 6 7 8 9 10 11 12 13
| class Item{ int price; int color; } public class Book extends Item{ int num; }
public class Book extends Item{ int num; int price; int color; }
|
这样Book
就继承了Item
的定义的数据,也就是Book有自身的num,也有Item的price和color
一个类只能继承一个类,一个类可以被多个类继承!
子类不会继承任何父类的构造方法。子类默认的构造方法是编译器自动生成的,不是继承的。
protected关键字
继承有个特点,就是子类无法访问父类的private
字段或者private
方法。
例如,Student
类就无法访问Person
类的name
和age
字段:
为了让子类可以访问父类的字段,我们需要把private
改为protected
。用protected
修饰的字段可以被子类访问:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| class Person { private String name; private int age; }
class Student extends Person { public String hello() { return "Hello, " + name; } }
class Person { protected String name; protected int age; }
class Student extends Person { public String hello() { return "Hello, " + name; } }
|
创建对象用构造方法
用来创建对象的方法叫做构造方法
其基本格式是方法名和类名一样(包括大小写)&&没有返回类型!!!!!!!!!!
1 2 3 4 5
| public Hero() { System.out.println("实例化一个对象的时候,必然调用构造方法"); }
|
我们常常使用
ADHero bh = new ADHero();
来构造一个对象,可是我们并没有设置ADHero()
这个方法
是Java
帮助我们自动设定了一个方法,我们也可以自定义
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| public class Book{ String name; int price;
public Hero(String bookname){ name = bookname; System.out.println("构造成功!"); } public static void main(String[] args) { Hero garen = new Hero("汤姆索亚历险记"); } }
|
构造方法允许使用重载
注意:引用变量的声明类型可能与其实际类型不符,例如(student为person的子类)
1
| Person p = new Student();
|
注意:可以有以类为元素的数组,初始化数组时即直接new(A、B、C、Income为4个类)
1 2 3 4 5
| Income[] incomes = new Income[] { new A(3000), new B(7500), new C(15000) };
|
this的使用
常见使用
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
| public class Book2 {
String name; float hp; int price;
public Book2(String name){ System.out.println("AAA"); this.name = name; }
public Book2(String name,float hp){ this(name); System.out.println("BBB"); this.hp = hp; } public void setPrice(int pri){ this.price=price }
public static void main(String[] args) { Book2 teemo = new Book2("提莫",383); System.out.println(teemo.name);
}
}
|
输出
函数传参数
基本类型(如 int、float、bool
)传入拷贝,不改外值
类类型(含 String
)传入地址,指针改变。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| public class Hero { String name; float hp; public Hero(String name,float hp){ this.name = name; this.hp = hp; } public void revive(Hero h){ h = new Hero("提莫",383); } public static void main(String[] args) { Hero teemo = new Hero("提莫",383); teemo.hp = teemo.hp - 400;
teemo.revive(teemo); } }
|
答案: -17
原因:这个练习不同在于,main方法中的teemo引用与revive方法中的teemo引用不是一个引用。revive中用构造方法重新生成了一个teemo引用。main方法中还是调用的原来的引用(相当于main函数中先malloc了一次,再在revive函数里面malloc一次),所以结果为-17,没有在函数里面赋值。
尽量在main函数里面new
再如:
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
| public class Dog { String name;
Dog(String name) { this.name = name; }
String getName() { return this.name; }
void setName(String name) { this.name = name; }
String getObjectAddress() { return super.toString(); } } public class PassByValueExample { public static void main(String[] args) { Dog dog = new Dog("A"); System.out.println(dog.getObjectAddress()); func(dog); System.out.println(dog.getObjectAddress()); System.out.println(dog.getName()); }
private static void func(Dog dog) { System.out.println(dog.getObjectAddress()); dog = new Dog("B"); System.out.println(dog.getObjectAddress()); System.out.println(dog.getName()); } }
|
可以看到经过了 func(dog)
但没有把新的dog B给dog A
Dog dog 的 dog 是一个指针,存储的是对象的地址。在将一个参数传入一个方法时,本质上是将对象的地址以值的方式传递到形参中。因此在方法中改变指针引用的对象,那么这两个指针此时指向的是完全不同的对象,一方改变其所指向对象的内容对另一方没有影响。
但是如果改变对象的字段值会改变源对象字段值
1 2 3 4 5 6 7 8 9 10 11 12
| class PassByValueExample { public static void main(String[] args) { Dog dog = new Dog("A"); func(dog); System.out.println(dog.getName()); }
private static void func(Dog dog) { dog.setName("B"); } }
|
方法的重载
对于某东西,可能攻击一个,也可能攻击多个
1 2 3 4
| public void attack() public void attack(Hero h1) public void attack(Hero h1, Hero h2)
|
那么,在调用方法attack的时候 ,会根据传递的参数类型以及数量,自动调用对应的方法
方法名字可以一样
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
| public class ADHero extends Hero { public void attack() { System.out.println(name + " 进行了一次攻击 ,但是不确定打中谁了"); } public void attack(Hero h1) { System.out.println(name + "对" + h1.name + "进行了一次攻击 "); } public void attack(Hero h1, Hero h2) { System.out.println(name + "同时对" + h1.name + "和" + h2.name + "进行了攻击 "); }
public static void main(String[] args) { ADHero bh = new ADHero(); bh.name = "赏金猎人"; Hero h1 = new Hero(); h1.name = "盖伦"; Hero h2 = new Hero(); h2.name = "提莫"; bh.attack(); bh.attack(h1); bh.attack(h1, h2); }
}
|
输出如下
1 2 3
| 赏金猎人 进行了一次攻击 ,但是不确定打中谁了 赏金猎人对盖伦进行了一次攻击 赏金猎人同时对盖伦和提莫进行了攻击
|
对于不确定数量的参数可以用升级的for(可变数量参数)
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
| public class ADHero extends Hero {
public void attack(Hero... heros) {
for (int i = 0; i < heros.length; i++) { System.out.println(name + " 攻击了 " + heros[i].name); } }
public static void main(String[] args) { ADHero bh = new ADHero(); bh.name = "赏金猎人"; Hero h1 = new Hero(); h1.name = "盖伦"; Hero h2 = new Hero(); h2.name = "提莫"; bh.attack(h1, h2);
} }
|
输出
方法的重写(覆写)
什么是真正的覆写
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| class Person { public void run() { } }
class Student extends Person { public void run(String s) {} public int run() {} public void run() { } }
|
对于代码2,程序只需判定他的参数是否符合即可。对于代码3/4程序如何判定呢?请看下面这个例子
覆写的实际例子
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| class Animal{ public void move(){ System.out.println("动物可以移动"); } } class Dog extends Animal{ public void move(){ System.out.println("狗可以跑和走"); } } public class TestDog{ public static void main(String args[]){ Animal a = new Animal(); Animal b = new Dog(); a.move(); b.move(); } }
|
以上实例编译运行结果如下:
我们发现:尽管 b 属于 Animal 类型,但是它运行的是 Dog 类的 move方法。
在编译阶段,只是检查参数的引用类型。
在运行时,Java 虚拟机指定对象的类型并且运行该对象的方法。
因此在上面的例子中,之所以能编译成功,是因为 Animal 类中存在 move 方法,然而运行时,运行的是特定对象的方法。
请务必区分编译和运行的差异!!!!!!!
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
| class Animal{ public void move(){ System.out.println("动物可以移动"); } } class Dog extends Animal{ public void move(){ System.out.println("狗可以跑和走"); } public void bark(){ System.out.println("狗可以吠叫"); } } public class TestDog{ public static void main(String args[]){ Animal a = new Animal(); Animal b = new Dog(); a.move(); b.move(); b.bark(); } }
|
以上实例编译运行结果如下:
1 2 3 4 5
| TestDog.java:30: cannot find symbol symbol : method bark() location: class Animal b.bark(); ^
|
该程序将抛出一个编译错误,因为b的引用类型Animal没有bark方法。
super关键字
当需要在子类中调用父类的被重写方法时,要使用 super 关键字。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| class Animal{ public void move(){ System.out.println("动物可以移动"); } } class Dog extends Animal{ public void move(){ super.move(); System.out.println("狗可以跑和走"); } } public class TestDog{ public static void main(String args[]){ Animal b = new Dog(); b.move(); } }
|
以上实例编译运行结果如下:
也就是先运行父类方法,再运行子类方法。
同理,子类引用父类的字段时,可以用super.fieldName
。大多数情况下,使用super只是便于理解,并无实质性差别,但是
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| public class Main { public static void main(String[] args) { Student s = new Student("Xiao Ming", 12, 89); } }
class Person { protected String name; protected int age;
public Person(String name, int age) { this.name = name; this.age = age; } }
class Student extends Person { protected int score;
public Student(String name, int age, int score) { this.score = score; } }
|
因为在Java中,任何class
的构造方法,第一行语句必须是调用父类的构造方法。如果没有明确地调用父类的构造方法,编译器会帮我们自动加一句super();
,所以,Student
类的构造方法实际上是这样:
1 2 3 4 5 6 7 8
| class Student extends Person { protected int score;
public Student(String name, int age, int score) { super(); this.score = score; } }
|
但是,Person
类并没有无参数的构造方法,因此,编译失败。
解决方法是调用Person
类存在的某个构造方法。例如:
1 2 3 4 5 6 7 8
| class Student extends Person { protected int score;
public Student(String name, int age, int score) { super(name, age); this.score = score; } }
|
final关键字
final
类似于常量const
,只能初始化一次
如果一个父类不允许子类对它的某个方法进行覆写,可以把该方法标记为final
。用final
修饰的方法不能被Override
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| class Person { protected String name; public final String hello() { return "Hello, " + name; } }
Student extends Person { @Override public String hello() { } }
|
如果一个类不希望任何其他类继承自它,那么可以把这个类本身标记为final
。用final
修饰的类不能被继承:
1 2 3 4 5 6 7 8
| final class Person {
protected String name; }
Student extends Person { }
|
对于一个类的实例字段,同样可以用final
修饰。用final
修饰的字段在初始化后不能被修改。例如:
1 2 3 4 5 6 7
| class Person { public final String name = "Unamed"; }
Person p = new Person(); p.name = "New Name";
|
但可以在构造方法中初始化final字段:
1 2 3 4 5 6
| class Person { public final String name; public Person(String name) { this.name = name; } }
|
多态
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| public class Main { public static void main(String[] args) { Person p = new Student(); p.run(); } }
class Person { public void run() { System.out.println("Person.run"); } }
class Student extends Person { @Override public void run() { System.out.println("Student.run"); } }
|
我们由上一个知识点容易知道上述代码的运行结果是:Student.run
Java的实例方法调用是基于运行时的实际类型的动态调用,而非变量的声明类型。
多态实例
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 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59
| public class Main { public static void main(String[] args) { Income[] incomes = new Income[] { new Income(3000), new Salary(7500), new StateCouncilSpecialAllowance(15000) }; System.out.println(totalTax(incomes)); }
public static double totalTax(Income... incomes) { double total = 0; for (Income income: incomes) { total = total + income.getTax(); } return total; } }
class Income { protected double income;
public Income(double income) { this.income = income; }
public double getTax() { return income * 0.1; } }
class Salary extends Income { public Salary(double income) { super(income); }
@Override public double getTax() { if (income <= 5000) { return 0; } return (income - 5000) * 0.2; } }
class StateCouncilSpecialAllowance extends Income { public StateCouncilSpecialAllowance(double income) { super(income); }
@Override public double getTax() { return 0; } }
|
抽象类
从Person
类派生的Student
和Teacher
都可以覆写run()
方法。
那么父类Person
的run()
方法没有实际意义,但是我们不能啥也不写或者只写声明
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| class Person { public void run() { … } public void run(); }
class Student extends Person { @Override public void run() { … } }
class Teacher extends Person { @Override public void run() { … } }
|
正确的做法是使用abstract关键字
1 2 3
| abstract class Person { public abstract void run(); }
|
把一个方法声明为abstract
,表示它是一个抽象方法,本身没有实现任何方法语句。因为这个抽象方法本身是无法执行的,所以,Person
类也无法被实例化。编译器会告诉我们,无法编译Person
类,因为它包含抽象方法。
必须把Person类本身也声明为abstract,才能正确编译
因为为抽象类,所以new是错误的
1
| Person p = new Person();
|
其多用于被继承
接口
1 2 3 4
| abstract class Person { public abstract void run(); public abstract String getName(); }
|
可以被改写为
1 2 3 4
| interface Person { void run(); String getName(); }
|
interface
:比抽象类还要抽象的纯抽象接口,因为它连字段(如public)都不能有。接口定义的所有方法默认都是public abstract
的,所以这两个修饰符不需要写出来(写不写效果都一样)
implements关键字
当一个具体的class
去实现一个interface
时,需要使用implements
关键字
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| class Student implements Person { private String name; public Student(String name) { this.name = name; } @Override public void run() { System.out.println(this.name + " run"); } @Override public String getName() { return this.name; } }
|
在Java中,一个类只能继承自另一个类,不能从多个类继承。
但是,一个类可以实现多个interface
,例如:
1 2 3
| class Student implements Person, Hello { ... }
|
常见错误
属性和属性名一样
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 44
| public class Hero { String name; float hp; float armor; int moveSpeed;
public void setName1(String name){ name = name; System.out.println(h.name); } public void setName2(String heroName){ name = heroName; } public void setName3(String name){ this.name = name; } public static void main(String[] args) { Hero h =new Hero(); h.setName1("teemo"); System.out.println(h.name); h.setName2("garen"); System.out.println(h.name); h.setName3("sige"); System.out.println(h.name); } }
|
输出为(注意setName1()
也有输出)
说明setName1()
出错了
属性和属性名一样;在方法体中,只能访问到参数name
建议用 this.name=name
这种写法