Java学习笔记
移位计算
计算机中整数总是以二进制的形式表示,例如int类型的整数7使用4字节表示的二进制如下:
00000000 00000000 00000000 00000111


类型自动提升与强制转型
运算过程中,如果参与运算的两个数的类型不一样,那么计算结果为较大类型的整型。例如short和int计算,结果总是int,原因是short首先自动被转型为int。
强制转型
int i =12345
short s=(short)i
**注意:超出范围的强制转型会得到错误的结果。
浮点数
整数运算在除数为0时会报错,而浮点数运算在除数为0时,不会报错,但会返回一个特殊值

浮点数的强制转型
浮点数可以强制转型为整数,在转型时,浮点的小数部分会被丢掉,如果转换后超过整数能表示的最大范围,将返回整型的最大值
如果要四舍五入 可以对浮点数加上0.5再强制转型
布尔运算—短路运算
如果布尔运算的表达式能提前确定结果,则后续的计算不再执行,直接返回结果。

三元运算符
Java还提供一个三元运算符b? x : y,根据第一个布尔表达式的结果,分别返回后续两个表达式之一的计算结果。
三元运算符会首先计算b,如果b为true,则只计算x,否则只计算y。
此外,x,y类型必须相同,因为返回值不是boolean,而是x和y之一。
java中字符和字符串是两种不同的类型
字符类型
字符类型char是基本数据类型,一个char保存一个Unicode字符
char c1='A'
char c2='中'
要显示一个字符的Unicode编码,只需将char类型直接赋值给int类型即可:
int n1='A'
字符串类型
和char类型不同,字符串类型String是引用类型,用“…”表示字符串,一个字符串可以存储0个到任意个字符。
转义字符 **

可以使用**+**连接任意字符串和其他数据类型
public class Main {
public static void main(String[] args) {
String s1 = "Hello";
String s2 = "world";
String s = s1 + " " + s2 + "!";
System.out.println(s);
}
}
输出是Hello world
如果用**+**连接字符串和其他数据类型,会将其他数据类型先自动转型为字符串,在连接。
public class Main {
public static void main(String[] args) {
int age = 25;
String s = "age is " + age;
System.out.println(s);
}
}
age is 25
可以使用""" …"""表示多行字符串。
字符串不可变的特性
public class Main {
public static void main(String[] args) {
String s = "hello";
System.out.println(s); // 显示 hello
s = "world";
System.out.println(s); // 显示 world
}
}
hello world
执行String s=“hello"时,JVM虚拟机先创建字符串"hello”,然后把字符串变量s指向它:

紧接着,执行s=“world"时,JVM虚拟机先创建字符串"world”,然后把字符串变量s指向它:

字符串"hello"还在,只是我们无法通过变量s访问,字符串的不可变是指字符串内容不可变。
空值null
引用类型的变量可以指向一个空值null,他表示不存在,即该变量不指向任何对象。例如:

练习
请将一组int值视为字符的Unicode编码,然后将它们拼成一个字符串:
public class
practice {
public static void main(String[] args){
int a =72;
int b =105;
int c=65281;
char a1=(char)a;
char b1=(char)b;
char c1=(char)c;
String s = ""+a1 + b1 + c1;
System.out.println(s);
}
}
输入和输出
System.out.println() 用于向屏幕输出一些内容,println是print line的缩写,表示输出并换行。
格式化输出
public class Main {
public static void main(String[] args) {
double d = 3.1415926;
System.out.printf("%.2f\n", d); // 显示两位小数3.14
System.out.printf("%.4f\n", d); // 显示4位小数3.1416
}
}
java的格式化功能提供了多种占位符,可以把各种数据类型格式化成指定的字符串:

输入
从控制台读取一个字符串和一个整数:
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in); // 创建Scanner对象
System.out.print("Input your name: "); // 打印提示
String name = scanner.nextLine(); // 读取一行输入并获取字符串
System.out.print("Input your age: "); // 打印提示
int age = scanner.nextInt(); // 读取一行输入并获取整数
System.out.printf("Hi, %s, you are %d\n", name, age); // 格式化输出
}
}
- 通过import语句导入java.util.Scanner,
- 创建Scanner对象并传入System.in。System.in代表标准输入流,System.out代表标准输出流。
- 有了Scanner对象之后,使用scanner.nextline()读取用户输入的字符串,使用scanner.nextInt()读取用户输入的整数。
java可变参数
可变参数用“类型…”定义,相当于数组类型:
class Group {
private String[] names;
public void setNames(String... names){
this.name=names;
}
}
上面的setNames()就定义了一个可变参数。
调用时可以这么写:
Group p =new Group();
g.setNames("Xiao Ming", "Xiao Hong", "Xiao Jun"); // 传入3个String
g.setNames("Xiao Ming", "Xiao Hong"); // 传入2个String
g.setNames("Xiao Ming"); // 传入1个String
g.setNames(); // 传入0个String
可以把可变参数改写为String[] 类型:
class Group {
private String[] names;
public void setNames(String[] names) {
this.names = names;
}
}
但是 调用方需要自己先构造String[]
Group g = new Group();
g.setNames(new String[] {"Xiao Ming", "Xiao Hong", "Xiao Jun"}); // 传入1个String[]
基本类型参数的传递,是调用方值的复制。双方各自的后续修改,互不影响。
引用类型参数的传递,调用方的变量,和接收方的参数变量,指向的是同一个对象。双方任意一方对这个对象的修改,都会影响对方(因为指向同一个对象嘛)。
构造方法
创建实例的时候,实际上是通过构造方法来初始化实例。
//构造方法
public class Main {
public static void main(String[] args) {
Person p = new Person("Xiao Ming", 15);
System.out.println(p.getName());
System.out.println(p.getAge());
}
}
class Person {
private String name;
private int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return this.name;
}
public int getAge() {
return this.age;
}
}
构造方法的名称就是类名。构造方法没有返回值(也没有void),调用构造方法,必须用new操作符。
默认构造方法
如果一个类没有定义构造方法,编译器会自动为我们生成一个默认构造方法,没有参数没有执行语句。如果自定义了一个构造方法,那么编译器就不再自动创建默认构造方法。
如果既要使用带参数的构造方法,有想保留不带参数的构造方法,只能把两个构造方法都定义出来。
class Person{
public Person(){
}
}
没有在构造方法中初始化字段时,引用类型的字段默认是null,数值类型的字段用默认值。
class Person {
private String name; // 默认初始化为null
private int age; // 默认初始化为0
public Person() {
}
}
多构造方法
可以定义多个构造方法,再通过new操作符调用时,编译器通过构造方法的参数数量,位置,类型自动区分。
一个构造方法可以调用其他构造方法,这样做的目的是便于代码复用,调用其他构造方法的语法是this(…)
class Person {
private String name;
private int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public Person(String name) {
this(name, 18); // 调用另一个构造方法Person(String, int)
}
public Person() {
this("Unnamed"); // 调用另一个构造方法Person(String)
}
}
方法重载
在一个类中,可以定义多个方法,如果有一系列方法,他们的功能类似。只有参数有所不同,可以把这一组方法名做成同名方法,
public class Main {
public static void main(String[] args) {
// TODO: 给Person增加构造方法:
Person ming = new Person("小明", 12);
System.out.println(ming.getName());
System.out.println(ming.getAge());
}
}
这种方法名相同,但各自的参数不同,称为方法重载(Overload)
继承
继承是面向对象编程中非常强大的一种机制,首先可以复用代码,Student从Person继承时,Student就获得了Person的所有功能,只需要为Student编写新增的功能。
Java使用extends关键字来实现继承
class Person {
private String name;
private int age;
public String getName() {...}
public void setName(String name) {...}
public int getAge() {...}
public void setAge(int age) {...}
}
class Student extends Person {
// 不要重复name和age字段/方法,
// 只需要定义新增score字段/方法:
private int score;
public int getScore() { … }
public void setScore(int score) { … }
}
注意:子类自动获得了父类的所有字段,严禁定义与父类重名的字段
在OOP的术语中,我们把person称为超类,父类,超类,把student类称为子类,扩展类。
继承树
在定义person的时候没有写extend,在Java中没有明确写extends的类,编译器会自动加上extends object。

java只允许一个class继承自一个类,一个子类只能有一个父类。object没有父类。
protected
子类无法访问父类的private字段或者private方法。为了让子类可以访问父类的字段,我们需要把private改为protected,用protected修饰的字段可以被子类访问:
class Person {
protected String name;
protected int age;
}
class Student extends Person {
public String hello() {
return "Hello, " + name; // OK!
}
}
protected关键字可以把字段和方法的访问权限控制在继承树内部一个protected字段和方法可以被其子类,以及子类的子类所访问。
super
super关键字表示父类,子类引用父类的字段时,可以用super.filedName
class Student extends Person {
public String hello() {
return "Hello, " + super.name;
}
}
这里使用super.name,或者this.name,或者name,效果都是一样的,但是有些时候就必须使用super
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;
}
}
运行代码,会得到编译错误:在student构造方法中,无法调用Person的构造方法。
在Java中,任何class的构造方法,第一行语句必须是调用父类的构造方法。如果没有明确的调用父类的构造方法,编译器会帮我们自动加一句super();所以Student类的构造方法实际上应该是这样的:
class Student extends Person {
protected int score;
public Student(String name, int age, int score) {
super(); // 自动调用父类的构造方法
this.score = score;
}
}
但是,Person类没有无参数的构造方法,因此编译失败。
解决办法是调用Person类存在的某个构造方法。
例如:
class Student extends Person {
protected int score;
public Student(String name, int age, int score) {
super(name, age); // 调用父类的构造方法Person(String, int)
this.score = score;
}
}
结论:如果父类没有默认的构造方法,子类就必须显式的调用super()并给出参数以便让编译器定位到父类的一个合适的构造方法。
子类不会继承任何父类的构造方法,子类默认的构造方法是编译器自动生成的,不是继承的。
阻止继承
正常情况下,只要某个class没有final修饰符,那么任何类都可以从该cass继承。
从java15开始,允许使用sealed修饰calss,并通过permits明确的写出能够从该class继承的子类名称。
例如:
public sealed class Shape permits Rect,Circle,Triangle{
......
}
Shape类就是一个sealed类,他只允许指定的三个类继承它。如果没有出现在permit列表中,定义某类继承shape就会报错。
向上转型
如果一个引用变量的类型是那么他可以指向一个Student类型的实例,
Student s=new Student()
如果一个引用类型的变量是Person,那么可以指向一个Person类型的实例:
Person p=new Person();
如果Student是从Person继承下来的,那么,一个引用类型为Person的变量,可以指向Student类型的实例。
Person p=new Student();
因为Student继承自Person,因此,他拥有Person的全部功能。这种把子类类型安全地变为父类类型的赋值,被称为向上转型(upcasting)
向下转型
和向上转型相反,如果把一个父类类型强制转型为子类类型,就是向下转型(downscale),例如:
Person p1 = new Student(); // upcasting, ok
Person p2 = new Person();
Student s1 = (Student) p1; // ok
Student s2 = (Student) p2; // runtime error! ClassCastException!
Person类型的p1实际指向的是Student实例,Person类型变量p2实际指向Person实例,在向下转型的时候,把p1转型为Student会成功,p2转型为Student会失败,因为子类功能比父类多,多的功能不能凭空变出来。
为了避免向下转型出错,java提供可instanceof操作符,可以先判断一个实例究竟是不是某种类型:
Person p = new Person();
System.out.println(p instanceof Person); // true
System.out.println(p instanceof Student); // false
Student s = new Student();
System.out.println(s instanceof Person); // true
System.out.println(s instanceof Student); // true
Student n = null;
System.out.println(n instanceof Student); // false
利用instanceof在向下转型前可以先判断:
Person p = new Student();
if (p instanceof Student) {
// 只有判断成功才会向下转型:
Student s = (Student) p; // 一定会成功
}
区分继承和组合
继承是is关系,组合是has关系
Book类
class Book {
protected String name;
public String getName() {...}
public void setName(String name) {...}
}
可以利用组合,让Student可以持有一个Book实例:
class Student extends Person {
protected Book book;
protected int score;
}
多态
在继承关系中,子类如果定义了一个与父类方法签名完全相同的方法,被称为覆写(override)
在Person类中,定义了run方法
class Person {
public void run() {
System.out.println("Person.run");
}
}
在子类Student中,覆写这个run()方法:
class Student extends Person {
@Override
public void run() {
System.out.println("Student.run");
}
}
override是重写,overload是重载
override是方法重写,相当一方法的外壳不变,只改变方法内部。
在方法中加上@override可以让编译器检查是否进行了正确的覆写,如果不小心写错了方法签名就会报错。但是Override并不是必须的。
public class Main {
public static void main(String[] args) {
}
}
class Person {
public void run() {}
}
public class Student extends Person {
@Override // Compile error!
public void run(String s) {}
}
引用变量的声明类型可能与其实际类型不符,例如:Person p=new Student()
考虑如下情况:
public class Main {
public static void main(String[] args) {
Person p = new Student();
p.run(); // 应该打印Person.run还是Student.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的实例方法调用时基于运行时的实际类型的动态调用,而非变量的声明类型。
多态
多态是指针对某个类型的方法调用,真正执行的方法取决于运行时期实际类型的方法:
多态的特性就是,运行期才能动态决定调用的子类方法。
public class Main {
public static void main(String[] args) {
// 给一个有普通收入、工资收入和享受国务院特殊津贴的小伙伴算税:创建实例
Income[] incomes = new Income[] {
new Income(3000),
new Salary(7500),
new StateCouncilSpecialAllowance(15000)
};
//调用totalTax
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; // 税率10%
}
}
//继承
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;
}
}
对于totalTax,利用多态totalTax方法只需和Income打交道,完全不知salary和statecouncilespecialAllowances的存在,就可以正确计算出总的税,如需增加一种收入,只需从Income中派生,然后重写Income方法,把新类型传入totalTax()。
多态具有非常强大的功能,就是允许添加更多类型的子类实现功能扩展,却不需要修改基于父类的代码。
覆写Object方法
因为所有的class类最终都继承自Object,Object定义了几个重要的方法:
- toString(): 把instance输出为String;
- equals():判断两个instance是否逻辑相等
- hashCode():计算一个instance的哈希值
在必要情况下,可以覆写Object的这几个方法,例如:
class Person {
...
// 显示更有意义的字符串:
@Override
public String toString() {
return "Person:name=" + name;
}
// 比较是否相等:
@Override
public boolean equals(Object o) {
// 当且仅当o为Person类型:
if (o instanceof Person) {
Person p = (Person) o;
// 并且name字段相同时,返回true:
return this.name.equals(p.name);
}
return false;
}
// 计算hash:
@Override
public int hashCode() {
return this.name.hashCode();
}
}
调用super
在子类的覆写方法中,如果要调用父类的被覆写的方法,可以通过super来调用。
class Person {
protected String name;
public String hello() {
return "Hello, " + name;
}
}
Student extends Person {
@Override
public String hello() {
// 调用父类的hello()方法:
return super.hello() + "!";
}
}
final
继承可以允许子类覆写父类的方法,如果一个父类不允许子类对他的某个方法进行覆写,可以把该方法标记为final,用final修饰的方法不能被Override:
class Person {
protected String name;
public final String hello() {
return "Hello, " + name;
}
}
Student extends Person {
// compile error: 不允许覆写
@Override
public String hello() {
}
}
如果一个类不希望任何其他类继承他,那么可以把这个类本身标记为final
用final修饰的字段在初始化后不能被修改
这篇博客详细介绍了Java中的类型自动提升与强制转型,特别是浮点数运算和移位计算。此外,讲解了布尔运算的短路特性、三元运算符的使用以及字符串和字符类型的特性和操作。还涵盖了Java中的输入输出、构造方法、方法重载、继承和多态等核心概念,强调了多态在代码设计中的重要性及其动态调用的特性。

2051

被折叠的 条评论
为什么被折叠?



