Java Base
java文件结构快速了解
Java 文件的基本结构
一个简单的Java文件通常包含以下几个部分:
-
包声明:
- 每个Java源文件都属于某个包。包的作用类似于文件夹,用于组织和分类相关的类。
- 示例:
package com.example;
-
导入语句:
- 导入语句用于引入其他包中的类或接口,这样可以在当前文件中直接使用这些类或接口。
- 示例:
import java.util.ArrayList;
-
类声明:
-
类是Java程序的基本构建块。每个类定义了一组属性(变量)和方法(函数)。
-
示例:
public class HelloWorld { // 类体 }
-
-
构造方法:
-
构造方法是一种特殊的方法,用于创建类的新实例时初始化对象。
-
示例:
public HelloWorld() { // 初始化代码 }
-
-
属性(成员变量):
-
属性是类中存储数据的变量。
-
示例:
private String name;
-
-
方法:
-
方法是执行特定任务的代码块。
-
示例:
public void sayHello() { System.out.println("Hello, World!"); }
-
-
主方法(main方法):
-
主方法是Java应用程序的入口点。当程序启动时,JVM会寻找并执行这个方法。
-
示例:
java深色版本
public static void main(String[] args) { HelloWorld hello = new HelloWorld(); hello.sayHello(); }
-
示例代码
以下是一个完整的Java文件示例,结合了上述各个部分:
java深色版本
// 包声明
package com.example;
// 导入语句
import java.util.ArrayList;
// 类声明
public class HelloWorld {
// 成员变量
private String name;
// 构造方法
public HelloWorld() {
this.name = "World";
}
// 普通方法
public void sayHello() {
System.out.println("Hello, " + name);
}
// 主方法
public static void main(String[] args) {
HelloWorld hello = new HelloWorld();
hello.sayHello();
}
}
使用 IntelliJ IDEA (IJ)
- 创建新项目:
- 打开IJ,选择“File” -> “New” -> “Project”,选择Java项目,点击“Next”完成项目创建。
- 创建新类:
- 在项目的src目录下右键,选择“New” -> “Java Class”,输入类名(如
HelloWorld
),然后点击“OK”。
- 在项目的src目录下右键,选择“New” -> “Java Class”,输入类名(如
- 编写代码:
- 在打开的编辑器窗口中,输入上述示例代码。
- 运行程序:
- 右键点击编辑器中的类文件,选择“Run ‘HelloWorld.main()’”来运行程序。
- 或者点击工具栏上的绿色三角形按钮运行程序。
包和import
在Java中,包(package)是用来组织类和接口的一种机制,类似于文件系统的文件夹结构。导入(import)语句则允许你在当前类中使用其他包中的类或接口。下面详细介绍如何声明包和导入其他包中的类,并附带例子。
如果一个保重的类要想被其他包中的类所使用,那么这个类一定要定义为public class,而不嫩使用class声明,因为class声明的类只能够在同一个包中使用 。
public class:文件名称和类名称保持一致,在一个.java文件中只能存在一个public class定义,如果一个类要想被外部的包所访问必须定义为public。*
*class:文件名称可以和类名称不一致,在一个*java中可以同时存在多个class定义,并且编译完成之后会形成多个 .class文件,使用class定义的类只能够在一个包中访问,不同包无法访问
包的本质是目录
1. 声明包
每个Java源文件都可以属于一个包。包声明必须是源文件中的第一行非注释代码。语法如下:
package 包名;
例如,如果你希望将你的类放在 com.example
这个包中,可以在文件的开头这样声明:
package com.example;
2. 导入其他包中的类
单个类导入
如果你想导入某个包中的单个类,可以使用以下语法:
import 包名.类名;
例如,如果你想导入 java.util
包中的 ArrayList
类,可以这样写:
import java.util.ArrayList;
多个类导入
如果你想导入某个包中的多个类,可以逐个导入,也可以使用通配符 *
导入整个包中的所有类。语法如下:
import 包名.*;
例如,如果你想导入 java.util
包中的所有类,可以这样写:
import java.util.*;
注:
在java中无论使用"import 包.*" 导入或者是单独导入,从实际的操作性能上来讲是没有任何区别的,因为即使使用了"*"也表示只导入所需要的类,不需要的并不导入。
3. 静态导入
静态导入允许你直接使用类中的静态成员(方法或变量),而不需要指定类名。语法如下:
import static 包名.类名.静态成员名;
例如,如果你想导入 Math
类中的 PI
常量和 sqrt
方法,可以这样写:
import static java.lang.Math.PI;
import static java.lang.Math.sqrt;
然后你可以在代码中直接使用 PI
和 sqrt
,而不需要前缀 Math
:
double result = sqrt(16) * PI;
完整示例
假设你有一个项目结构如下:
src/
├── com/
│ └── example/
│ ├── Main.java
│ └── Person.java
Person.java
package com.example;
public class Person {
private String name;
private int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public int getAge() {
return age;
}
@Override
public String toString() {
return "Person{name='" + name + "', age=" + age + "}";
}
}
Main.java
package com.example;
import java.util.ArrayList;
import java.util.List;
public class Main {
public static void main(String[] args) {
List<Person> people = new ArrayList<>();
people.add(new Person("Alice", 30));
people.add(new Person("Bob", 25));
for (Person person : people) {
System.out.println(person);
}
}
}
解释
- 包声明:
Person.java
和Main.java
都声明了package com.example;
,表示它们属于com.example
包。
- 导入语句:
Main.java
中导入了java.util.ArrayList
和java.util.List
,以便在代码中使用这些类。
- 类和方法:
Person
类定义了一个简单的Person
对象,包含姓名和年龄。Main
类中的main
方法创建了一个Person
对象列表,并打印每个对象的信息。
总结注:
如果同时导入了不同包中名称相同的类,这时候就使用的时候就需要使用这个类的完整名称。
如:hh.gg.util.Message msg = new hh.gg.util.Message();
利用静态导入的优点在于,不同类的静态方法就好像在主类定义的一样,不需要类名称就可以直接进行调用。
jar文件
java中的压缩文件格式,即可以将*class文件以 *jar压缩包的形式给用户。
jar --help
用法: jar [OPTION...] [ [--release VERSION] [-C dir] files] ...
jar 创建类和资源的档案, 并且可以处理档案中的
单个类或资源或者从档案中还原单个类或资源。
示例:
# 创建包含两个类文件的名为 classes.jar 的档案:
jar --create --file classes.jar Foo.class Bar.class
# 使用现有的清单创建档案, 其中包含 foo/ 中的所有文件:
jar --create --file classes.jar --manifest mymanifest -C foo/ .
# 创建模块化 jar 档案, 其中模块描述符位于
# classes/module-info.class:
jar --create --file foo.jar --main-class com.foo.Main --module-version 1.0
-C foo/ classes resources
# 将现有的非模块化 jar 更新为模块化 jar:
jar --update --file foo.jar --main-class com.foo.Main --module-version 1.0
-C foo/ module-info.class
# 创建包含多个发行版的 jar, 并将一些文件放在 META-INF/versions/9 目录中:
jar --create --file mr.jar -C foo classes --release 9 -C foo9 classes
要缩短或简化 jar 命令, 可以在单独的文本文件中指定参数,
并使用 @ 符号作为前缀将此文件传递给 jar 命令。
示例:
# 从文件 classes.list 读取附加选项和类文件列表
jar --create --file my.jar @classes.list
主操作模式:
-c, --create 创建档案
-i, --generate-index=FILE 为指定的 jar 档案生成
索引信息
-t, --list 列出档案的目录
-u, --update 更新现有 jar 档案
-x, --extract 从档案中提取指定的 (或全部) 文件
-d, --describe-module 输出模块描述符或自动模块名称
--validate Validate the contents of the jar archive. This option
will validate that the API exported by a multi-release
jar archive is consistent across all different release
versions.
在任意模式下有效的操作修饰符:
-C DIR 更改为指定的目录并包含
以下文件
-f, --file=FILE 档案文件名。省略时, 基于操作
使用 stdin 或 stdout
--release VERSION 将下面的所有文件都放在
jar 的版本化目录中 (即 META-INF/versions/VERSION/)
-v, --verbose 在标准输出中生成详细输出
在创建和更新模式下有效的操作修饰符:
-e, --main-class=CLASSNAME 捆绑到模块化或可执行
jar 档案的独立应用程序
的应用程序入口点
-m, --manifest=FILE 包含指定清单文件中的
清单信息
-M, --no-manifest 不为条目创建清单文件
--module-version=VERSION 创建模块化 jar 或更新
非模块化 jar 时的模块版本
--hash-modules=PATTERN 计算和记录模块的散列,
这些模块按指定模式匹配并直接或
间接依赖于所创建的模块化 jar 或
所更新的非模块化 jar
-p, --module-path 模块被依赖对象的位置, 用于生成
散列
只在创建, 更新和生成索引模式下有效的操作修饰符:
-0, --no-compress 仅存储; 不使用 ZIP 压缩
--date=TIMESTAMP The timestamp in ISO-8601 extended offset date-time with
optional time-zone format, to use for the timestamps of
entries, e.g. "2022-02-12T12:30:00-05:00"
其他选项:
-?, -h, --help[:compat] 提供此帮助,也可以选择性地提供兼容性帮助
--help-extra 提供额外选项的帮助
--version 输出程序版本
如果模块描述符 'module-info.class' 位于指定目录的
根目录中, 或者位于 jar 档案本身的根目录中, 则
该档案是一个模块化 jar。以下操作只在创建模块化 jar,
或更新现有的非模块化 jar 时有效: '--module-version',
'--hash-modules' 和 '--module-path'。
如果为长选项提供了必需参数或可选参数, 则它们对于
任何对应的短选项也是必需或可选的
抽象类
一.关键字及定义
抽象类使用abstrat class进行定义,并且在一个抽象类中也可以利用abstract关键词定义若干个抽象方法,这样抽象类的子类就必须在继承抽象类时强制覆写全部抽象方法。
抽象类并不是一个完整的类,对于抽象类的使用需要按照以下原则进行。
—-抽象类必须提供有子类,子类使用extends继承一个抽象类
—-抽象类的子类(不是抽象类)一定要覆写抽象类的全部抽象方法。
—-抽象类的对象实例化可以利用对象多态性通过子类向上转型的方法完成
二.抽象类的相关说明(有参无参构造)
(1) 抽象类必须由子类继承,所以在定义时,不允许使用final关键字定义抽象类或抽象方法。
补充final关键字
final修饰的类不能被继承。
final修饰的方法不能被子类重写。
final修饰的变量只能被赋值一次,且对于引用类型来说,是指向的地址不能变,但对象的状态可以改变。
final变量可以提高并发程序的安全性和性能。
(2) 抽象类中可以定义成员属性与普通方法,为了可以为抽象类中的成员属性初始化,可以在抽象类中提供构造方法。子类在继承抽象类时会默认调用父类的无参构造,如果抽象类没有提供无参构造方法,则子类必须通过super()的形式调用指定参数的构造方法。
代码示例
abstract class Message{
private String type;//消息类型
public Message(String type) {
this.type = type;
}
public abstract String getConnectionInfo();
public String getType(){
return type;
}
}
class DatabaseMessage extends Message{
public DatabaseMessage(String type){
super(type);//调用单参构造。
}
@Override
public String getConnectionInfo() {
return "[" + super.getType() +"]数据库连接信息。";
}
}
public class JavaDome {
public static void main(String[] args) {
Message message = new DatabaseMessage("Java");//向上转型
System.out.println(message.getConnectionInfo());
}
}
这里补充些有参无参构造知识
抽象类: 抽象类是不能被实例化的类,它通常包含一个或多个抽象方法(没有具体实现的方法)。 抽象类可以包含成员属性、普通方法和构造方法。
构造方法: 构造方法用于初始化对象的属性。 抽象类中的构造方法可以用于初始化抽象类中的成员属性。
子类继承抽象类 当一个子类继承抽象类时,子类的构造方法会自动调用父类的构造方法。具体规则如下:
默认调用无参构造方法:
如果父类有无参构造方法,子类的构造方法会默认调用父类的无参构造方法。
如果父类没有无参构造方法,子类必须显式调用父类的有参构造方法。
显式调用有参构造方法:
如果父类只有有参构造方法,子类必须在构造方法中使用 super() 显式调用父类的有参构造方法。
super() 必须是子类构造方法中的第一条语句。
注:
如果你在抽象类中定义了有参构造方法,而没有定义无参构造方法,那么编译器不会自动生成默认的无参构造方法。这意味着子类在继承该抽象类时,必须显式调用父类的有参构造方法。
默认无参构造方法:
在Java中,如果你没有定义任何构造方法,编译器会自动为你生成一个无参构造方法。这个无参构造方法是默认的,不带任何参数。
自定义构造方法:
一旦你定义了任何一个构造方法(无论是有参还是无参),编译器就不会再生成默认的无参构造方法。因此,如果你需要无参构造方法,必须显式地定义它。
(3) 抽象类中允许没有抽象方法,即便没有抽象方法。也无法直接使用关键字new直接实例化抽象类对象
(4) 抽象类中可以提供static方法,并且该类方法不受到抽象类实例化对象的限制。
代码示例
abstract class Message{
public abstract String getInfo();
public static Message getInstance(){
return new DatabaseMessage();
}
}
class DatabaseMessage extends Message{
@Override
public String getInfo(){
return "hello";
}
}
public class JavaDome1 {
public static void main(String[] args) {
Message msg = Message.getInstance();//静态直接调用
System.out.println(msg.getInfo());
}
}
包装类
这里就演示一个double类型的装箱和拆箱
public class JavaDemo{
public static void main(String args[]){
Double obj = new Double(10.1);//装箱
double num = obj.doubleValue();//拆箱
System.out.println(num*num);
}
}
//其他包装的基本数据类型是同理
注:
JDK1.5以前是必须的操作,JDK1.5之后,java提供了自动装箱和拆箱机制,并且包装类的对象可以自动进行数学计算了。
JDK1.9出现了过期声明。也就说明后面的都是自动装箱。
自动装箱
public class JavaDome{
public static void main(String args[]){
Integer obj = 10;//自动装箱,无需关注构造方法了。
int num = obj;//自动拆箱,等价于调用了intValue()方法。
obj++;//包装类对象可以直接参与数学运算。
System.out.println(num*obj);
}
}
接口
定义
接口在实际开发中是由一种比抽象类更为重要的结构组成,接口的主要特点在于其用于定义开发标准,同时接口在JDK1.8之后也发生了重大变革。
关键字
interface
java中接口属于一种特殊的类,需要通过interface关键字进行定义,在接口中可以定义全局常量、抽象方法(必须是public访问权限)、default方法以及static方法。
接口中的方法默认是抽象的。
示例
//由于类名称与接口名称的定义要求相同,所以为了区分出接口,往往会在接口名称前加入字母I(interface)
interface IMessage{ //定义接口
public static final String INFO = "hh";//全局变量
public abstract String getInfo();//抽象方法
}
本程序定义一个IMessage接口,由于接口中存在有抽象方法,所以无法被直接实例化。其使用规则如下。
接口需要被子类实现,子类利用implements关键字可以实现多个父接口
子类如果不是抽象类,那么一定要覆写接口中的全部抽象方法
接口对象可以利用子类对象的向上转型进行实例化
注:
子类可以继承父类也可以实现父接口,其基本语法如下:
class 子类[extends 父类][implements 接口1,接口2,...]{}
如果出现混合应用,则要采用先继承(extends)再实现(implements)的顺序完成,同时一定要记住,子类接口的最大特点在于可以同时实现多个父接口,而每一个子类只能通过extends继承一个父类
感觉写法上跟继承差不多
接口的转型
接口和Object之间没有任何关系,但是如果有子类实现了这些接口和默认继承了Object父类,所以该子类向上转型的实例就可以进行任意父类接口转型。
接口的简化定义
在进行接口定义时,对于全局常量和抽象方法可以按照一下形式简化
interface IMessage{
public static final String INFO = "gg";
public abstract String getInfo();
}
//简化定义
interface IMessage{
String INFO = "gg";
String getInfo();
}
作用完全相同,但是建议保留public,这样定义会更清楚
在面向对象的设计中,抽象类是一种必不可少的结构,利用抽象类可以实现一些公共方法的定义。可以利用extends先继承父类再利用implements实现若干父接口的顺序完成子类的定义。
在Java中,接口中的成员属性(字段)默认是 public、static 和 final 的。这意味着:
公共的(public):可以在任何地方访问。
静态的(static):属于接口本身,而不是接口的实例。可以通过接口名称直接访问这些字段。
最终的(final):一旦赋值后不能更改。
因此,接口中的成员属性实际上是常量,通常用于定义一些固定的值,供实现该接口的类使用。
extends继承多个父接口
java中的extends关键字除了具有类继承作用以外,也可以在接口上使用以实现接口的继承关系,并且可以同时实现多个父接口。
示例
interface Imessage{
public static final String INFO = "hh";//全局常量
public abstract String getInfo();
}
interface IChannel{
public boolean connect();//抽象方法
}
//extends在类继承上只能够继承一个父类,但是接口上可以继承多个。
interface IService extends IMessage,IChannel{
public String service();
}
class MessageService implement IService{
@Override
public String getInfo(){
return IMessage.INFo;
}
@Override
public boolean connect(){
return true;
}
@Override
public String service(){
return "hello";
}
}
接口定义的加强(default、static)
JDK1.8以后,接口中的组成出了提供全局常量和抽象方法之外。还可以使用default定义普通方法或者使用static定义静态方法
interface IMessage{
public String message();//抽象方法
public default boolean connect(){ //普通方法,可以被子类继承和覆写
System..out.println("建立");
return true;
}
}
class MessageImpl implements IMessage{
public String message(){
return "hh";
}
}
public class JavaDome{
public static void main(){
IMessage msg = new MessageImpl();
if(msg.connect()){
System.out.println(msg.message());
}
}
}
static
使用default定义的普通方法需要通过接口实例化对象才可以调用,而为了避免实例化对象的依赖。在接口中也可以使用static定义的方法,此方法可以直接利用接口名称调用。
注:
接口在整体设计上是针对类的进一步抽象,其设计层次也要高于抽象类99
设计模式
工厂设计模式
最终最好都是用工厂设计模式来获取实例对象
class Factory{
public static IEat getInstance(){
return new EatProxy(new EatReal());
}
}
public class JavaDome{
public static void main(Sring arg[]){
IEat eat = Factory.getInstance();
eat.get();
}
}
代理设计模式
No. | 区别 | 抽象类 | 接口 |
---|---|---|---|
1 | 关键字 | abstract、class | interface |
2 | 组成 | 常量、变量、抽象方法、普通方法、构造方法 | 全局常量、抽象方法、普通方法、静态方法 |
3 | 权限 | 可以使用各种权限 | 只能是public |
4 | 关系 | 一个抽象类可以实现多个接口 | 接口不能够继承抽象类,却可以继承多接口 |
5 | 使用 | 子类使用extends继承抽象类、抽象类和接口的对象都是利用对象多态性的向上转型,进行接口或者抽象类的实例化操作 | 子类使用implements实现接口、抽象类和接口的对象都是利用对象多态性的向上转型,进行接口或者抽象类的实例化操作 |
6 | 设计模式 | 模版设计模式 | 工厂设计模式、代理设计模式 |
7 | 局限 | 一个子类只能继承一个抽象类 | 一个子类可以实现多个接口 |
泛型
泛型设计的核心思想在于:类中的属性或方法的参数与返回值的类型采用动态标记,在对象实例化的时候动态配置于鏊使用的数据类型
泛型可以定义在任意的程序结构体中
泛型是Java中的一种强大特性,它允许你在定义类、接口和方法时使用类型参数,从而提高代码的复用性和类型安全性。以下是关于泛型的一些关键点和示例:
泛型的基本概念
- 类型参数:用大写字母表示,如
T
、E
、K
、V
等。 - 泛型类:使用类型参数定义类。
- 泛型接口:使用类型参数定义接口。
- 泛型方法:使用类型参数定义方法。
- 泛型集合:使用泛型来指定集合中存储的元素类型。
泛型类
public class Box<T> {
private T item;
public Box(T item) {
this.item = item;
}
public T getItem() {
return item;
}
public void setItem(T item) {
this.item = item;
}
}
public class Test {
public static void main(String[] args) {
Box<String> stringBox = new Box<>("Hello");
System.out.println(stringBox.getItem()); // 输出: Hello
Box<Integer> integerBox = new Box<>(123);
System.out.println(integerBox.getItem()); // 输出: 123
}
}
泛型接口
public interface Container<T> {
void add(T item);
T get(int index);
}
public class ArrayListContainer<T> implements Container<T> {
private List<T> list = new ArrayList<>();
@Override
public void add(T item) {
list.add(item);
}
@Override
public T get(int index) {
return list.get(index);
}
}
public class Test {
public static void main(String[] args) {
Container<String> stringContainer = new ArrayListContainer<>();
stringContainer.add("Hello");
System.out.println(stringContainer.get(0)); // 输出: Hello
Container<Integer> integerContainer = new ArrayListContainer<>();
integerContainer.add(123);
System.out.println(integerContainer.get(0)); // 输出: 123
}
}
泛型方法
public class Util {
public static <T> void printArray(T[] array) {
for (T item : array) {
System.out.print(item + " ");
}
System.out.println();
}
}
public class Test {
public static void main(String[] args) {
String[] stringArray = {"Hello", "World"};
Util.printArray(stringArray); // 输出: Hello World
Integer[] integerArray = {1, 2, 3};
Util.printArray(integerArray); // 输出: 1 2 3
}
}
泛型约束
你可以使用泛型约束来限制类型参数的范围,确保传入的类型满足某些条件。
public class Box<T extends Number> {
private T item;
public Box(T item) {
this.item = item;
}
public T getItem() {
return item;
}
public void setItem(T item) {
this.item = item;
}
public double getDoubleValue() {
return item.doubleValue();
}
}
public class Test {
public static void main(String[] args) {
Box<Integer> integerBox = new Box<>(123);
System.out.println(integerBox.getDoubleValue()); // 输出: 123.0
// Box<String> stringBox = new Box<>("Hello"); // 编译错误,String 不是 Number 的子类
}
}
通配符
通配符 ?
用于表示未知类型,可以进一步限定其范围。
- 无界通配符:表示未知类型。
- 上界通配符:<? extends T>表示类型是
T
或其子类。 - 下界通配符:<? super T>表示类型是
T
或其父类。
public class Util {
public static void printList(List<?> list) {
for (Object item : list) {
System.out.print(item + " ");
}
System.out.println();
}
public static void addNumbers(List<? super Integer> list) {
list.add(1);
list.add(2);
}
}
public class Test {
public static void main(String[] args) {
List<Object> objectList = new ArrayList<>();
Util.addNumbers(objectList);
Util.printList(objectList); // 输出: 1 2
List<Number> numberList = new ArrayList<>();
Util.addNumbers(numberList);
Util.printList(numberList); // 输出: 1 2
}
}
总结
- 泛型类:使用类型参数定义类,提高代码复用性和类型安全性。
- 泛型接口:使用类型参数定义接口,增强接口的灵活性。
- 泛型方法:使用类型参数定义方法,使方法更加通用。
- 泛型约束:限制类型参数的范围,确保类型安全。
- 通配符:处理不确定类型的场景,增强代码的灵活性。
注:
在面向对象的程序设计中,Object类可以接收一切的数据类型。但是在泛型的概念中:Message<String>与Message<Object>属于两个不同的类型。
辨析两种细节:
1.public static void fun(Message<Object> temp){}
2.public static void fun(Message temp){}
//使用1这种方式定义接收参数,表示fun()方法只能够接收Message<Object>类型的引用
//使用2这种方式定义接收参数,不在fun()方法上设置泛型类型,实际上可以解决不同泛型类型的对象传递问题,但同时也会有新的问题产生:允许随意修改数据
---示例:
public class JavaDemo{
public static void main(String args[]){
Message<String> msg = new Message<String>();
msg.setContent("hh");
fun(msg);
}
}
//不设置泛型类型,表示可以接收任意的泛型类型对象
//默认泛型类型为Object,但不等同于Message<Object>
public static void fun(Message temp){
//原始类型为String,现在设置Interger
temp.setContent(18);
System.out.println(temp.getContent());
}
//虽然可以接收,但是无法对修改做出控制,而使用通配符"?"的泛型只允许获取,不允许修改。
泛型约束
类和方法
设置泛型的上限(?extends类):只能够使用当前类或者当前类的子类设置泛型类型。
?extends Number:可以设置Number或Number子类(例如Integer、Double)
方法
设置泛型的下线(?super类):只能够设置指定的类或者指定类的父类。
?super String:只能够设置String或String的父类Object
泛型接口
泛型接口在进行子类定义时就有两种实现方式:在子类中继续声明泛型和子类中为父类设置泛型类型